v0.7.9.8 — the test command did not typecheck, and the plan had gone stale

Housekeeping before v0.8.0: the answer to "anything else that should be
looked at first". One real hole, one stale document, and my own leavings.

#102 — `npm test` passed green on a type error. `pretest` ran
`scripts/build-web.ts`, which invokes `tsc --ignoreConfig` against three
web entry points, so it saw only what those three transitively import and
under a WEAKER configuration than tsconfig.json — no
`noUncheckedIndexedAccess`, no `exactOptionalPropertyTypes`,
`--types ''`. It never saw `src/server/` or a single file under `test/`.

Demonstrated rather than argued: a planted
`const DELIBERATE_TYPE_ERROR: number = 'not a number';` in
src/server/session.ts gives `npm run typecheck` a TS2322 and `npm test` a
clean `# fail 0`. `pretest` is `tsc --noEmit && node
scripts/build-web.ts` now, and the same error exits 1 with the tests
never running.

This mattered THIS week rather than generally: v0.8.0 is steps 2-7 of the
common board — display stream, credentials, persistence, Chromium
supervisor — which is almost entirely src/server/, exactly the half the
test command could not see.

#103 — the plan had drifted from the code it is the source for.
docs/plans/jitsi-common-board.md was written 2026-08-27, still said "No
implementation has been performed", and is what steps 2-7 get built from.
Step 1 shipped across four releases since, so every "current code
finding" under it described a fault that is now fixed — a document
reading as present tense and nine days stale sends the next reader to fix
things twice.

Measured: its PublicFrame sketch lists four properties never built
(protocolVersion, config, scoring, deckCounts) and omits 28 that exist,
and the shape is the real difference — the implementation is FLAT where
the plan grouped things into objects, so a renderer written from the
sketch would not compile. The plan now says so at the top and at step 1,
names src/sim/view.ts and the redaction allow-list as the authority,
keeps the original sketch for its reasoning, and calls out
`protocolVersion` as unbuilt rather than dropping it quietly — step 2 is
the reconnecting display stream and is the first thing that would want
one.

One step-1 item is STRUCK OFF rather than built: "add the Red Flag holder
to the public player projection". The premise does not hold here.
`decks.redFlags` is written once, in setup.ts, from
`optionalRules.emergencyToolbox`, and never again — `redFlag.play` emits
`phaseEnded` and does not spend it — so every player holds one or none
does, decided before the deal. A per-player `redFlagHeld` would be one
already-public option copied N times, while telling every reader of the
common board that it varies by player and might change mid-game. Worse
than the absence. Pinned by test so it is not re-raised from the plan.

Four dead imports removed, all mine: `HAND_LIMIT` left unused in
apply.ts, view.ts and web/game.ts when 0.7.9.6 consolidated the three
copies of the §6.2 test, and `actingPlayer` in web/game.ts, dead since
0.7.9.5 made `currentActor` delegate. Finding them re-measured #46:
`tsc --noUnusedLocals` now reports 40, up from 29 on 2026-08-30. That
entry's "without the flag this list simply regrows" is a measurement
rather than a forecast now. The other 36 and the flag stay open.

946 tests pass, up from 943. No behaviour changes: three new tests pin an
invariant, and the rest is a build command, dead imports and a document.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y5boPxP6JHRYMm8adXaF5R
This commit is contained in:
Jesse.Markowitz
2026-09-08 03:41:39 -04:00
co-authored by Claude Opus 5
parent 88a42ae9e2
commit 312e0301e0
8 changed files with 243 additions and 8 deletions
+62 -1
View File
@@ -1,6 +1,17 @@
# Station Master Jitsi Common Board Implementation Plan
**Status:** Final planning document. No implementation has been performed.
**Status (2026-09-07):** **STEP 1 IS BUILT AND SHIPPED. Steps 2-7 are unimplemented.**
Step 1 landed across four releases rather than one — v0.7.9.2 (the two narration leaks), v0.7.9.4
(the projection helpers and the redaction net), v0.7.9.5 (the narration path), and v0.7.9.8 (this
reconciliation). One of its items is struck off rather than built; see § Public game projection.
**This document has drifted from the code and is no longer the authority on what exists.** It was
written on 2026-08-27 against the code of that date, and the "Current code findings" under each step
describe faults that were then real — several are now fixed, and reading them as present tense will
send you to fix things twice. Where a step is marked built, `src/sim/view.ts`, `src/server/`, and the
tests named in TODO.md are the authority. Steps 2-7 were never implemented and their findings have
NOT been re-verified against the current code; check each before building on it.
## Summary
@@ -44,6 +55,38 @@ The sibling repository had unrelated local modifications and untracked files. Th
Introduce dedicated allow-listed types. Do not derive them with `Omit<Frame, ...>` because new private `Frame` fields could then leak automatically.
> **RECONCILED 2026-09-07 (v0.7.9.8).** Step 1 is BUILT, and what shipped is not shaped like the
> sketch below. This block was the design; `PublicFrame` in `src/sim/view.ts` is now the authority,
> and `test/redaction.test.ts`'s allow-list is the enumeration of it that fails when it changes.
> **Read those two, not this**, when building steps 2-7. The differences that matter:
>
> - **The shape is FLAT, not grouped.** There is no `clock`, `config`, `scoring` or `deckCounts`
> object. Their contents sit at the top level — `day`, `stage`, `clock` (a time string), `phase`,
> `phaseKey`, `actor`, `superintendent`, `deck`, `departments`, `departmentDepth`, `salvage`,
> `yards`, `mode`, `days`, `optionalRules`, `houseRules`, `minCombinedRevenue`,
> `maxCollisionsPerDay`, `maxCollisionsTotal`, `collisionsToday`, `collisionsTotal`, `status`,
> `outcome`, `extraDays`, `extensionVotes`, `official`, `tally`, `openingRolls`, `timetable`,
> `timetableWhat`, `trains`, `players`, `division`, `districts`.
> - **`protocolVersion` was NOT built** and exists nowhere in the repo. Step 2 is the reconnecting
> display stream, which is the first thing that would want one — decide there whether to add it,
> rather than assuming it is already on the wire.
> - **`redFlagHeld` is STRUCK OFF**, not deferred. See below.
> - **Fields gained since this was written** that the renderer should know about: `crewTrays` and
> `queued` (#98, the Crew Tray pool and the trains waiting for one), and on each district's cells
> `heldAtLimits` (#99, a train stopped on the Limit Track) and `enhancementsSpent` (#101, a
> dispatch device spent for the Day).
>
> **Why `redFlagHeld` is struck off rather than built.** The premise does not hold in this codebase.
> `decks.redFlags` is written in exactly one place — `setup.ts`, from
> `config.optionalRules.emergencyToolbox` — and never again; `redFlag.play` emits `phaseEnded` and
> does not spend it. So every player holds one or none does, decided before the deal. A per-player
> `redFlagHeld` would be `optionalRules.emergencyToolbox` copied N times, already public, while
> telling every reader of the common board that it varies by player and may change mid-game. The
> invariant is pinned by test (`test/display-gaps.test.ts`) so this does not get re-raised from the
> plan text: if the rule ever becomes per-player, that test fails.
The design as originally written, kept for the reasoning:
```ts
type PublicPlayerView = {
index: PlayerIndex;
@@ -145,6 +188,24 @@ Expose sanitized state, last transition time, and a safe error summary through t
## Step 1 — Secure public-state projection
> **BUILT — v0.7.9.2 through v0.7.9.5.** Everything in "Required changes" below shipped except the
> Red Flag holder, which is struck off (§ Public game projection). Mapping to the work items in
> TODO.md: the projection helpers and `currentActorOfState` are **#95**, the systematic redaction
> net and its allow-list are **#91**, the seed and blind-draw narration leaks are **#92**, and
> "stop passing the full game log into `frameFor()`" is **#97** — which also fixed a reconnect bug
> the duplicate had been masking, so read that entry before touching narration in step 2.
>
> **The findings below are as of 2026-08-27 and are now HISTORY, not a task list.** Two are worth
> carrying forward anyway: districts must be keyed by SEAT with ownership resolved through
> `playerAtSeat` (Employee Rotation), and the public view must be composed UPWARD from shared
> helpers, never by calling the player `snapshot()` once per seat. Both are load-bearing for step 3.
>
> One finding was struck off on measurement rather than fixed: it warns that a display reading
> `clock.currentActor` could highlight the wrong district during a decision. Across six seeds and
> 3,600 decision points that field and `actingPlayer` never disagreed. `currentActorOfState` exists
> anyway as the one place to ask — and **#96** later found a real instance of the same class in a
> state nobody had checked, the §3.3 vote, where the game is not `active` at all.
### Current code findings
`src/sim/view.ts` currently builds `Frame` for one real viewer and defaults that viewer to player zero. It combines shared table state, one district, and viewer-private fields. Calling it for a spectator would silently expose player zero’s district and private information.