docs(plan): D13 settles the contact address, and Phase 0 is done

The domain still has no mailbox. Rather than delay the beta on Cloudflare
Email Routing, the org lead elected to publish the existing address.

- D13 recorded: whitlocktech@gmail.com is the published contact, held only
  in brand.json so a later privacy@/security@ is a file copy, not a rebuild.
- Phase 0 PR 0.10 withdrawn -- SECURITY.md already carries that address.
- checkFacts.mjs gains the rule that keeps the promise true: no email
  address in the source outside brand-default/brand.json.
- Phase 0 marked complete (docs#172, docs#173, .profile#4, Module-uo#19).

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-08-19 18:39:40 -05:00
parent 650ea21ad4
commit 5f09b66742

57
PLAN.md
View File

@@ -1,7 +1,8 @@
# runicgateway.com — design of record # runicgateway.com — design of record
**Status:** decisions taken by the org lead on 2026-08-19 and recorded in §5. Nothing is built yet. **Status:** decisions taken by the org lead on 2026-08-19 and recorded in §5. **Phase 0 is
**Date:** 2026-08-19 (revision 2) complete**; Phase 1 is the next build step.
**Date:** 2026-08-19 (revision 3)
**Repo:** `RunicGateway/runicgateway.com` **Repo:** `RunicGateway/runicgateway.com`
**AI-assisted:** yes — drafted by Claude, per the org's AI-usage disclosure policy. **AI-assisted:** yes — drafted by Claude, per the org's AI-usage disclosure policy.
@@ -11,7 +12,9 @@ build modules and integrations for it. A third arrives with the Android closed b
who want the app. who want the app.
Revision 1 asked five open questions. Revision 2 records the answers as decisions and designs Revision 1 asked five open questions. Revision 2 records the answers as decisions and designs
against them. §14 lists the three facts still outstanding — none of them block starting. against them. Revision 3 marks Phase 0 shipped and adds **D13**, which settles the contact address
§14 N2 had left open — the domain still has no mailbox, and the org lead chose to publish the
existing one rather than delay the beta.
--- ---
@@ -197,7 +200,7 @@ verified values from §2.
| 0.7 | `docs` | `README.md` — index entries for `TEAMS.md`, `ARCHITECTURE.md`, `TRUSTED_DEVICES_MFA.md`, `MODERATION_APPEALS.md`. | | 0.7 | `docs` | `README.md` — index entries for `TEAMS.md`, `ARCHITECTURE.md`, `TRUSTED_DEVICES_MFA.md`, `MODERATION_APPEALS.md`. |
| 0.8 | `docs` | `website/BACKEND_DESIGN.md` — retitle for the game-agnostic core. | | 0.8 | `docs` | `website/BACKEND_DESIGN.md` — retitle for the game-agnostic core. |
| 0.9 | `Module-uo` | `README.md` — Phase 4 marked complete. | | 0.9 | `Module-uo` | `README.md` — Phase 4 marked complete. |
| 0.10 | `docs` | `SECURITY.md` — the public vulnerability contact moves from a personal Gmail to `security@runicgateway.com` (§14, N2). Conditional on that address existing. | | ~~0.10~~ | ~~`docs`~~ | ~~`SECURITY.md` — the public vulnerability contact moves from a personal Gmail to `security@runicgateway.com`.~~ **Withdrawn 2026-08-19 by D13.** The domain has no mailbox and the org lead elected to publish the existing address rather than wait; `SECURITY.md` already carries it and needs no change. |
0.50.9 are grouped where they touch one repo, so the real PR count is smaller than the row count; 0.50.9 are grouped where they touch one repo, so the real PR count is smaller than the row count;
`docs` PRs 0.50.8 land as one hygiene PR. `docs` PRs 0.50.8 land as one hygiene PR.
@@ -205,6 +208,10 @@ verified values from §2.
**Nothing in Phase 0 changes code or a contract.** Each PR corrects documentation to match what the **Nothing in Phase 0 changes code or a contract.** Each PR corrects documentation to match what the
code already does, and each cites the source of truth in its description. code already does, and each cites the source of truth in its description.
**Phase 0 is complete.** It shipped on 2026-08-19 as four merged PRs — `docs`#172 (0.10.3),
`docs`#173 (0.50.8), `.profile`#4 (0.4), `Module-uo`#19 (0.9) — plus 0.10 withdrawn as above.
Every fact the site is about to quote now agrees across the repositories.
--- ---
## 5. Decisions of record ## 5. Decisions of record
@@ -219,12 +226,13 @@ Taken by the org lead (Colby Whitlock) on 2026-08-19. Recorded so they are not r
| **D4** | **Real web screenshots**, captured from the local review stack, not placeholders. | §13 phase 6. Needs seeded, presentable demo content. | | **D4** | **Real web screenshots**, captured from the local review stack, not placeholders. | §13 phase 6. Needs seeded, presentable demo content. |
| **D5** | **Claude drafts `/privacy` and `/terms`** from what the code actually collects; the org lead reviews before ship. | §9. | | **D5** | **Claude drafts `/privacy` and `/terms`** from what the code actually collects; the org lead reviews before ship. | §9. |
| **D6** | **Ship the image and compose file; the org lead deploys.** DNS and TLS terminate at their existing reverse proxy. | §13 phase 9. This repo never touches the production host. | | **D6** | **Ship the image and compose file; the org lead deploys.** DNS and TLS terminate at their existing reverse proxy. | §13 phase 9. This repo never touches the production host. |
| **D7** | **The site sends no email at all.** No SMTP, no notifications, no mailbox behind the domain yet. | §8 designs the signup so it works anyway — see "The opt-in link removes the need for email". A contact address is still required (§14, N2). | | **D7** | **The site sends no email at all.** No SMTP, no notifications, no mailbox behind the domain yet. | §8 designs the signup so it works anyway — see "The opt-in link removes the need for email". A contact address is still required; D13 supplies it. |
| **D8** | **Understated honesty.** The site reads as finished; factual badges appear only where they save a reader wasted effort. **The Integration Kit stays marked draft until a second module is successfully built against it.** | §11, §10. A status with an exit criterion, not a mood. | | **D8** | **Understated honesty.** The site reads as finished; factual badges appear only where they save a reader wasted effort. **The Integration Kit stays marked draft until a second module is successfully built against it.** | §11, §10. A status with an exit criterion, not a mood. |
| **D9** | **No analytics.** No tracking scripts, no third-party requests, no cookie banner. | Reverse-proxy access logs are the only traffic data. | | **D9** | **No analytics.** No tracking scripts, no third-party requests, no cookie banner. | Reverse-proxy access logs are the only traffic data. |
| **D10** | **Support routes to Discord and Gitea issues.** Invite: `https://discord.gg/t2Jav8yT4g`. | §10. Discord is the front door; Gitea takes issues from anyone with an account, which today is nobody outside the org — §14, N3. | | **D10** | **Support routes to Discord and Gitea issues.** Invite: `https://discord.gg/t2Jav8yT4g`. | §10. Discord is the front door; Gitea takes issues from anyone with an account, which today is nobody outside the org — §14, N3. |
| **D11** | **Keep the existing emblem.** `runic-emblem.png` is the mark on both the website and the Android launcher icon; the site adopts it rather than drawing a new one. | §11. Revision 1's "draw a new geometric mark" recommendation is withdrawn. | | **D11** | **Keep the existing emblem.** `runic-emblem.png` is the mark on both the website and the Android launcher icon; the site adopts it rather than drawing a new one. | §11. Revision 1's "draw a new geometric mark" recommendation is withdrawn. |
| **D12** | **A public demo instance is planned but out of scope today** — a Proxmox VM running the full stack including ServUO, with restricted settings and an hourly automatic reset. | §15. The IA reserves the slot so it lands later without a restructure. | | **D12** | **A public demo instance is planned but out of scope today** — a Proxmox VM running the full stack including ServUO, with restricted settings and an hourly automatic reset. | §15. The IA reserves the slot so it lands later without a restructure. |
| **D13** | **Publish the existing address.** No mailbox has been created at the domain and the org lead elected not to wait for one: `whitlocktech@gmail.com` is the published contact wherever the site needs one — `/privacy`, `/terms`, `/community`, the Play listing — and `docs/SECURITY.md` keeps the address it already carries. | Taken 2026-08-19, superseding §14 N2 as a blocker. The address lives **only** in `brand.json` (§7), never in prose, so moving to `privacy@`/`security@` later is a file edit and a restart. Phase 0's PR 0.10 is withdrawn, not deferred. |
--- ---
@@ -466,7 +474,7 @@ Organised by what a reader is trying to do. A reader should never need to know t
| `/integrations/` | Discord, mobile + ntfy push, SSO — with an explicit "not built" list | | `/integrations/` | Discord, mobile + ntfy push, SSO — with an explicit "not built" list |
| `/app/` | The Android app: what it does, the 14 existing screenshots, and the beta CTA | | `/app/` | The Android app: what it does, the 14 existing screenshots, and the beta CTA |
| `/beta/` | The closed-beta signup (§8) | | `/beta/` | The closed-beta signup (§8) |
| `/community/` | Discord (`discord.gg/t2Jav8yT4g`) as the front door, the Gitea org for code and contributions, `security@` for vulnerabilities — the split in §14 N3 | | `/community/` | Discord (`discord.gg/t2Jav8yT4g`) as the front door, the Gitea org for code and contributions, the `brand.json` contact address for vulnerabilities (D13) — the split in §14 N3 |
| `/privacy/`, `/terms/` | §9 | | `/privacy/`, `/terms/` | §9 |
**Feature grouping**, using project terminology: **Feature grouping**, using project terminology:
@@ -594,6 +602,12 @@ a mechanism rather than diligence:
Same mechanism and the same intent as the Integration Kit's `checkCoreApi.js`: when the platform Same mechanism and the same intent as the Integration Kit's `checkCoreApi.js`: when the platform
moves, this repo goes red so someone updates the site. That failure is the feature. moves, this repo goes red so someone updates the site. That failure is the feature.
It carries one rule that is not a version: **no email address may appear anywhere in the source
except `brand-default/brand.json`.** D13 publishes a personal address that is meant to be
replaceable by a file copy, and that promise survives exactly as long as nobody types the address
into a paragraph. Same argument as `checkTokens.mjs` and colour literals — the check is the
mechanism, diligence is not.
- **`scripts/checkLinks.mjs`** — every internal link resolves; every outbound link into a - **`scripts/checkLinks.mjs`** — every internal link resolves; every outbound link into a
`RunicGateway` repo points at a branch path, not a commit permalink. `RunicGateway` repo points at a branch path, not a commit permalink.
- **`scripts/checkTokens.mjs`** — no colour literal outside the token file (§7). - **`scripts/checkTokens.mjs`** — no colour literal outside the token file (§7).
@@ -631,22 +645,33 @@ None of these block starting Phase 0 or Phase 1.
**N1 — Resolved.** `runicgateway.com` is registered through **Cloudflare**, with DNS on Cloudflare. **N1 — Resolved.** `runicgateway.com` is registered through **Cloudflare**, with DNS on Cloudflare.
The domain does not resolve to anything yet; the record is pointed at the host in phase 12. The domain does not resolve to anything yet; the record is pointed at the host in phase 12.
**N2 — A contact address at the domain. Recommendation: Cloudflare Email Routing**, now that N1 **N2 — Settled by D13, and no longer blocking anything.** As of 2026-08-19 no mailbox exists at the
confirms the domain is on Cloudflare. It is free, unlimited, receive-only, and adds its own MX and domain and the org lead chose not to wait for one. **`whitlocktech@gmail.com` is the published
SPF records automatically. Three addresses, all forwarding to the existing inbox: contact**, and `docs/SECURITY.md` keeps the address it already carries.
| Address | Used by | The site is built so this is reversible for the cost of a file edit. The address is a `brand.json`
field (§7), read by `/privacy`, `/terms`, `/community` and the OG/structured metadata; **it appears
in no page's prose and in no committed markdown**, and `checkFacts.mjs` (§12) fails the build if a
literal `@` address is found outside `brand-default/brand.json`. Changing the published contact is
then the same operation as changing the logo — copy a file, restart the container.
The recommendation stands for whenever it is wanted: **Cloudflare Email Routing**, now that N1
confirms the domain is on Cloudflare. Free, unlimited, receive-only, and it adds its own MX and SPF
records automatically. Three addresses, all forwarding to the existing inbox:
| Address | Would replace the Gmail in |
|---|---| |---|---|
| `hello@runicgateway.com` | The Play store listing's contact email; `/community` | | `hello@runicgateway.com` | The Play store listing's contact email; `/community` |
| `privacy@runicgateway.com` | `/privacy` — deletion and data requests (§9) | | `privacy@runicgateway.com` | `/privacy` — deletion and data requests (§9) |
| `security@runicgateway.com` | `docs/SECURITY.md`, which currently publishes a personal Gmail on a public repo | | `security@runicgateway.com` | `docs/SECURITY.md` (a one-line `docs` PR, whenever) |
Pair it with Gmail's "Send mail as" to reply from the addresses rather than the underlying inbox. Pair it with Gmail's "Send mail as" to reply from the addresses rather than the underlying inbox.
Routing is receive-only, which is sufficient — the site sends nothing (D7). If double opt-in is ever Routing is receive-only, which is sufficient — the site sends nothing (D7). If double opt-in is ever
wanted later, Resend or Brevo's free tier plus DKIM/DMARC would cover sending. wanted later, Resend or Brevo's free tier plus DKIM/DMARC would cover sending.
Retiring the personal address from `SECURITY.md` is a one-line `docs` PR and rides along with **One consequence to accept knowingly:** a Play store listing's contact email is public, and so is
Phase 0. this site, so the address will be scraped. That is the trade the org lead took against delaying the
beta; the mitigation is that switching later costs one file.
**N3 — Gitea registration.** Open registration previously drew a flood of spam accounts, so it is **N3 — Gitea registration.** Open registration previously drew a flood of spam accounts, so it is
disabled, and "file an issue" is therefore a wall for anyone outside the org. Reopening it safely on disabled, and "file an issue" is therefore a wall for anyone outside the org. Reopening it safely on
@@ -683,9 +708,9 @@ MAX_CREATION_LIMIT = 0 ; -1 is unlimited; 0 means no user may
**Consequently, `/community` should describe an honest split rather than one channel:** Discord **Consequently, `/community` should describe an honest split rather than one channel:** Discord
(`discord.gg/t2Jav8yT4g`) is the front door for questions, bug reports and the beta announcements — (`discord.gg/t2Jav8yT4g`) is the front door for questions, bug reports and the beta announcements —
instant and unauthenticated. Gitea is where the code lives, is publicly readable without an account, instant and unauthenticated. Gitea is where the code lives, is publicly readable without an account,
and takes issues and pull requests from contributors willing to request one. `security@` is the and takes issues and pull requests from contributors willing to request one. The `brand.json`
private channel for vulnerabilities. That page is written the same way whether or not N3 is contact address (D13) is the private channel for vulnerabilities. That page is written the same way
actioned; only one sentence changes. whether or not N3 is actioned; only one sentence changes.
--- ---