clay-borg/workplans/CB-WP-0032-the-comments-in-the-account.md
tegwick edab11c0e4
Some checks failed
ci / check (push) Failing after 3s
CB-WP-0033: a game is the unit
make trials reported "positions unreachable: 4, target 0 — the recording
exists but the position moved". All four were false. A recording holds one
hash, the final state, and reachability asked whether the note's hash was
in that file — so a mid-game note could never match, and a post-game note
from any but the last game could not either. Instance 8 of the ADR-0018
family: vary only WHEN a note was written and the answer flips, with
nothing having moved.

The root cause was not the metric. play again reused state belonging to a
game: it overwrote the previous game's recording (data loss), never
cleared the journal (game 2's log opened with game 1's commands), and so a
note's command index pointed into a recording without those commands.
Fixing reachability alone would have gone green while a session still
destroyed its own evidence.

A note now binds by (game, after) — an index into the recording's own
commands list, which a reader can replay to. The hash keeps a job as the
integrity check at the end of a game, where it can actually fail. Game 1
keeps the path it was given, so GameDesign §5's documented invocation is
unchanged; later games get -2, -3 and nothing is overwritten. Legacy
5-column logs stay readable and are reported as legacy, never as orphans —
an unsubstantiated orphan claim is the defect being fixed.

All three fixes mutation-proven, including at the call site via a real
two-game session.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 17:41:11 +02:00

4.3 KiB

id kind title status state_hub_workstream_id
CB-WP-0032 product The comments in the account done 71d744f9-7e40-49c3-994e-f3fb007a1430

Purpose

structural tier  S   (one existing renderer gains a second input; no new
                      decision, no dependency, no budget moved)
chaos            d8 = 6  → no override
declared tier    S

Declaration 3 of chaos window 3.

The report

"Commenting after the game is a good practice. We could show the comments in the log. I would think that as useful."

This closes the gap CB-WP-0031 named and did not fix: notes were written to a file and shown back nowhere. trials.py's own docstring calls that shape "this project's signature failure in a new medium" (ADR-0014 D6) — and the note channel had it, in the interface, for as long as it has existed.

The decision: what a comment is positioned against

A note carries round, step and state_hash. None of them can order it against the log, because the log is a sequence of commands and several commands share a step. Positioning by round would put a comment somewhere in the right round — for a remark about a specific move, the wrong position stated confidently.

So the server records how many commands had been played when the note was written, which it knows exactly (journal.len()), and the page places it there. after = 0 is before anyone moved, which is a real thing to have an opinion at.

The thing that must not happen

A comment must not be readable as something the game did. The log is the recorder's vocabulary — record::to_step, what the scenario file will say — so a note styled as a log line would be a sentence the game never produced, sitting in the account of what the game produced. ADR-0014 D4 turns on that distinction being visible: raw notes stay in this repo, and only a human promotes one to a finding.

Notes render in their own .note block, attributed "you — round N, step", with the words in quotes. The .note CSS already existed and was never used by anything — defined for this, and left unwired.

What was NOT done, and why it is the interesting half

No column was added to the trial log, though after is genuinely the anchor and persisting it would let a later reader interleave.

trials.py skipped any row that was not five cells — with continue, in silence. A sixth column would have made make trials report zero notes for every log at once, which is verbatim the failure the parser's own docstring says it exists to prevent. after is therefore held in memory for the session and the file format is untouched.

The latent defect was fixed on its own terms: a wrong column count now raises, and the walk that wraps it reports the real reason instead of blaming a missing block for every failure.

Task: show the comments where they were made

id: CB-WP-0032-T01
status: done
priority: high
state_hub_task_id: "31f60440-e049-45f7-99fa-87c01c00adb7"

Controls:

  • placement is asserted, not just presence — mutation-proven: append the notes at the end instead of interleaving and the test goes red;
  • a comment cannot be read as a game event — asserted on its own marking and its attribution;
  • a comment past the end of the log still appears, because a post-game note is written when every command has been played and a dropped comment is the failure the channel exists to avoid;
  • the round trip, not the write — the cb-play test posts a note and then GETs the page, which is the path a player takes.

Done 2026-08-07.

Account { lines, notes } replaces the bare &[LogLine] on both pages — a struct because document_with_log was already at seven arguments, and because the two are only meaningful together, which is the point.

trials.py hardened as described above, with two new controls: a wrong-shaped row raises, and the walk names the reason.

Not done here

  • Nothing lets a player edit or delete a comment. A note is a record of what they thought at the time and rewriting it would defeat that, but a typo is a real case and there is no answer for it yet.
  • The ending page shows the same interleaved account, so a long game puts the post-game comment at the bottom of a long log. Whether that is where a reader looks for it is untested.