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

108 lines
4.3 KiB
Markdown

---
id: CB-WP-0032
kind: product
title: "The comments in the account"
status: done
state_hub_workstream_id: "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](CB-WP-0031-the-comment-box-outlives-the-game.md)
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
```task
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.