5 Commits

Author SHA1 Message Date
5e987518c6 fix(checks): read files through Gitea's contents endpoint, not raw
The three checks that read another repository -- checkFacts, checkQuickstart,
checkReference -- fetched source files from the API's `raw` route, which answers
`Cache-Control: public, max-age=21600`. The CDN in front of Gitea caches that,
so the checks can read a blob most of a working day old.

It bit on cutover day. checkFacts reported

    FAIL  moduleApi
          platform.json says : 1.9.0
          website main:server/src/modules/version.js says : 1.6.0

against a `main` that says 1.9.0 -- the served copy was two weeks old
(`cf-cache-status: HIT`, `Age: 15713`, `last-modified: 18 Aug`). No edit in this
repository could have made it pass, and the same run reported a bundle triple
that had already been republished as still current: a stale read fails BOTH
ways, and the false pass is the dangerous one.

The `contents` endpoint answers `private, must-revalidate`, which the CDN
bypasses, so it is always the ref's current blob. The cost is a JSON parse and a
base64 decode. checkReference's canonical-document existence loop already used
it, which is why that half was never affected.

PLAN.md 12 records the finding next to the check it constrains.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-09-01 12:41:01 -05:00
1313e748ae feat(beta): phase 5 — the app page and the closed-beta signup
All checks were successful
PR checks / checks (pull_request) Successful in 1m5s
Builds `/app/` and `/beta/`, the SQLite signup store, the rate limiting and the
export CLI of PLAN.md §8, and adds this repository's first test suite.

Four decisions of record, D26–D29 (§8, "How phase 5 built the app and the beta"):

- D26 — the screenshot slot ships empty, reserved for phase 9. §10 promised
  `/app/` "the 14 existing screenshots"; they are a July trusted-device smoke
  test against an unseeded dev instance, captured before the theming work, and
  five of the fourteen are two-factor prompts. Shipping them would break D4.
  Phase 9 already builds the rig, so it gains an emulator pass.
- D27 — the public demo is the tester target. `ConnectScreen.kt` gates the whole
  app on a validated deployment address, so a tester needs somewhere to point it.
  The beta therefore waits on the demo VM, and the page says so.
- D28 — `/beta` handles its own POST; there is no `/api/beta-signup`. An endpoint
  cannot report a validation error without JavaScript. §6's diagram is amended.
- D29 — the APK and the beta get equal billing, and the APK link is off:
  `androidApk.serviceable` is false because the published v0.5.0 build does not
  work. The panel stays and states that plainly rather than being removed.

Three mechanisms the plan did not anticipate:

- `liveBrand()` — a server-rendered page never passes through the boot rewrite,
  so `/beta` reads the mounted brand.json itself. Pasting the Play opt-in URL in
  takes effect on the next request rather than the next restart.
- `checkLinks.mjs` derives on-demand routes from `prerender = false` in the
  source. A PLANNED_ROUTES entry would have been wrong: its reverse check fires
  when a route has been built, and an on-demand route never produces a file, so
  the entry could never rot out.
- `npm test` — the five existing checks all read built output, and none of this
  logic appears there. A honeypot can stop working and leave the build identical.

Also: `checkFacts.mjs` gains the APK assets and `minSdk`, and learns that RFC 2606
reserved domains are not contact addresses; the D13 rule is otherwise unchanged.

Verified end to end against the built server: every outcome renders with no
JavaScript, cross-origin POSTs are refused, a mounted opt-in URL appears without
a restart, and the export CLI round-trips.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-24 03:50:51 -05:00
556dee7355 feat(home): phase 3 — the homepage
All checks were successful
PR checks / checks (pull_request) Successful in 49s
Replaces phase 1's scaffold with the real homepage: hero, the data path as
inline SVG, the self-hosted argument, all five capability groups, and the
get-started CTA. Three decisions the org lead took first are recorded in
PLAN.md as D17-D19.

The data path is drawn generically and captioned specifically (D17): the nodes
say "your game server" and "sidecar", the sub-labels and caption name ServUO and
uo-link. The SVG is aria-hidden because the four numbered steps beside it carry
the same path in prose — one telling, not two.

The capability list is data with a check behind it (D18). Every Game-intelligence
item names the module-uo capability slug it comes from, and the build fails if
the page and platform.json disagree either way. That needed a fifteenth fact in
checkFacts.mjs: §12 named the capability list as an externally-sourced fact and
nothing re-read it, so the chain rested on someone remembering. It also found
that the site was omitting two of the module's eight capabilities — guilds and
city governors are now listed, in the page and in §10.

The hero leads with the emblem (D19), derived from whichever logo.png is in
force so one file still changes the hero, header, tab icon and app icon
together.

Also here, both found by standing the build up rather than by review:

  - checkBrand.mjs now enforces the demo slot's markup contract. applyBrand.mjs
    reveals the demo link by replacing an exact pair of empty attributes; an
    attribute inserted between them produces a build where the mount sets a demo
    URL, the boot log says nothing and the link never appears. Both halves are
    checked and the literal is derived from the expression applyBrand.mjs uses,
    so they cannot drift.

  - The header nav overflowed at 390px — four links plus the lockup measured
    433px against a 390px viewport, so every phone got a horizontally scrolling
    page. Phase 1 left this to phase 3 expecting a disclosure control; it got a
    wrap instead, because with four links there is nothing to disclose and a
    hamburger costs state, script and duplicate markup.

Verified on a clean checkout of this commit: all four checks, astro check, a
production build, a live /brand/* smoke, and a demo URL mounted and reverted.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-20 00:03:04 -05:00
b287728c19 ci(facts): use the existing org-level REGISTRY_TOKEN
All checks were successful
PR checks / checks (pull_request) Successful in 9m11s
checkFacts needs to read link, servuo-plugins, website and installer, and
the automatic per-run token is scoped to this repo alone. Rather than mint
a new secret, the workflow uses REGISTRY_TOKEN, which already exists at the
org level with the right permissions.

The secret is named for the registry and the script reads GITEA_TOKEN; the
mapping stays in the workflow so the script keeps asking for what it
actually wants -- a Gitea token -- rather than this org's secret name.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-19 20:46:18 -05:00
66187dde5d feat(site): phase 1 — the foundation
Some checks failed
PR checks / checks (pull_request) Failing after 4m19s
Astro 7 with the Node adapter, Starlight mounted at /docs, the token file, both
self-hosted typefaces, the layout shell, and the two build-time checks from §12.

The palette's gold and cyan are sampled from runic-emblem.png rather than
guessed, per §11: 494,059 opaque pixels binned by hue, each value annotated with
its measured contrast against the ground, and restricted rather than brightened
where a ratio fails.

- checkTokens.mjs fails the build on any colour literal outside tokens.css,
  which is what keeps §7's "recolouring is a file copy" promise true.
- checkFacts.mjs re-reads all 14 externally-sourced facts from their authorities
  over the Gitea API and fails on disagreement. It also enforces D13: no email
  address in the source outside brand-default/brand.json.
- Both were negative-tested; neither has ever been allowed to pass by default.

§6 asks for output:'server' with per-page prerender=true. Astro 7 expresses the
same runtime shape as output:'static' with an adapter, opting individual routes
out — so the default is static rather than accidentally server-rendered.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-19 19:08:52 -05:00