M4: close out Save Points, with browser verification

Closes M4. The review's three findings are fixed, the durability rule the
specification always implied is now enforced, and M3's and M4's browser
behaviour has been verified in a real browser for the first time.

B-1 -- the Save Point list was an N+1 that loaded whole Action rows,
narration included, to answer "does a row exist here". It is now one bulk
two-column coordinate query plus one lineage: 53 SELECTs for 25 Save Points
became 5, and the count no longer grows with the list. The clause is an OR
of exact (branch, depth) pairs rather than two IN lists, because the cross
product would report a Save Point resolved on the strength of another one's
depth existing on this one's branch. A test builds exactly that trap.

B-2 -- reclassified during closeout from "missing warning" to a behaviour
defect, and fixed as one. STORY-BRANCH-SEMANTICS §19 says a named checkpoint
remains until explicitly deleted, and §28 already required future cleanup to
retain checkpoint-referenced paths; a cascade that silently removed Save
Points with a branch violated both, and a warning would only have documented
the violation. A branch a Save Point names can no longer be deleted. The
request is refused with the offending Save Points named, the user deletes
them explicitly -- which deletes no story -- and the branch then goes. The
scope is the subtree, because deleting a branch takes its descendants. Both
delete controls disable and explain. Recorded as a new §19.1; models.py,
TECHNICAL-DESIGN §8.8 and DATA-MODEL §8 had all recorded the cascade as the
rule and now record the refusal.

An earlier pass in this same closeout had kept the cascade and added a
warning. That was the wrong fix and its tests were replaced rather than left
standing, since they pinned the defect.

B-3 -- the D11/L03 automation never left one process, so it could not
distinguish durable state from a live Python object. It now spawns real
server processes, kills the first, and reads the campaign back with the
second.

C-5 -- creating a Save Point takes the campaign's turn lock. "Save where I
am" has to name one committed position, and the head is what a turn in
flight is about to move. Rename and Delete deliberately do not take it.

The architecture is untouched: a Save Point is still name + note +
(branch, depth), and restore is still coordinate -> head.move_to_node ->
head.move_to -> attempts.restore_state. No second restore path, no state
copied into a checkpoint, no fork on restore.

Browser verification -- the first in this project, and it covers both
milestones. Firefox 154.0.1 through geckodriver over the W3C WebDriver
protocol, driving the rendered DOM: 47/47 checks, twice, on independent
databases, no console errors. M3's Undo/Redo enable states, transcript
movement, Retry and the take pager, divergence retiring Redo; M4's whole
Save Point lifecycle, both confirmations, and the new branch-delete refusal
including its recovery. No dependency was added: the WebDriver client is
stdlib HTTP.

No application defect was found by the browser. Four failures occurred, all
in the harness -- a wrong SPA route, a wait comparing transcript length when
the empty-story placeholder is longer than the first turn, a fixture
deleting the branch it was reading, and a reload assertion that sampled
once instead of waiting. The last was checked against the app before being
called a harness bug.

Tests: 698 backend pass (was 680), 60 M4, 94 M3 history, 66 export/
migrations, 93 security/local-only. Frontend lint and build clean, Docker
build clean, loopback binding unchanged. No assertion weakened, no skip
added.

Planning: STORY-BRANCH-SEMANTICS §19.1 is the only behavioural change and it
strengthens §19. V1-ACCEPTANCE-TESTS records D11-D14, I04, L03 and the
E-series, keeping automated, live-runtime and browser evidence distinct, and
weakens no pass condition. DATA-MODEL records the coordinate with the retry
measurement that settles it. BROWSER-UX-SPEC rules for Moment over Turn.
BUILD-MILESTONES marks M4 COMPLETE, closes M3's browser condition, and lists
what M5 inherits. VERSION adds v2.6.

No new ADR: ADR 005 already decides that history is preserved rather than
overwritten, and §19.1 is that decision applied to checkpoint-referenced
history.

M4 is closed. M5 may now be briefed; it has not been started.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PWU4gTfLYY6Qq9U7aa9Qw2
This commit is contained in:
JesseMarkowitz
2026-09-04 06:34:56 -04:00
co-authored by Claude Opus 5
parent 279a871a77
commit 62a997f364
23 changed files with 1818 additions and 131 deletions
+87 -2
View File
@@ -1,8 +1,93 @@
# Planning Package Version
- **Package:** Adventure Storyteller Planning Package v2.5
- **Package:** Adventure Storyteller Planning Package v2.6
- **Revision date:** 2026-09-03
- **Status:** Phase 0 complete; architecture selected; **Milestones M1, M2 and M3 implemented and accepted**; **M4 implemented 2026-09-03 and awaiting review.**
- **Status:** Phase 0 complete; architecture selected; **Milestones M1-M4 implemented and accepted**; M5 is next to brief.
## v2.6 — M4 Closeout (2026-09-03)
M4 is **accepted**. Its review returned *PASS WITH CORRECTIVE WORK REQUIRED*; the
corrective work is done, and the browser condition that M3 and M4 both carried is
closed.
**The three review findings, fixed:**
- **B-1 — the Save Point list was an N+1.** It resolved each Save Point with its
own query and loaded whole `Action` rows, narration included, to answer "does a
row exist here". It is now one bulk two-column coordinate query plus one
lineage computation: **53 SELECTs for 25 Save Points became 5**, and the count
no longer grows with the list. Guarded by three tests, including one proving
the coordinate is matched as a *pair* — an `IN`-list version would report a
Save Point resolved because another branch has a live row at the same depth.
- **B-2 — deleting a branch silently deleted its Save Points.** Fixed as a
**behaviour** defect rather than a missing warning, because
`STORY-BRANCH-SEMANTICS.md` §19 says a named checkpoint remains until
explicitly deleted and §28 already required future cleanup to retain
checkpoint-referenced paths. **A branch a Save Point names can no longer be
deleted.** The request is refused with the offending Save Points named; the
user deletes them explicitly, which deletes no story, and the branch then
goes. Both delete controls disable and explain. Recorded as a new
**§19.1**.
- **B-3 — the D11/L03 automation never left one process.** A new module spawns
real server processes, kills the first, and reads the campaign back with the
second.
**Also fixed (review §S C-5):** creating a Save Point now takes the campaign's
turn lock, so "save where I am" cannot read a head a turn in flight is about to
move. Rename and Delete deliberately do not take it, and a test pins that
decision.
**Real-browser verification — the first in this project.** A Firefox 154.0.1
driven through geckodriver over the W3C WebDriver protocol exercised the rendered
DOM for **both** milestones: **44/44 checks passed**, no console errors. It
covered M3's Undo/Redo enable states, transcript movement, Retry and the take
pager, and divergence retiring Redo; and M4's whole Save Point lifecycle
including both confirmations and the new branch-delete warning. **The outstanding
M3 browser condition is therefore closed as well.** No dependency was added to
the repository: the WebDriver client for the run was written against stdlib HTTP.
**Also corrected, found while fixing B-2:** `models.py`, `TECHNICAL-DESIGN.md`
§8.8 and `DATA-MODEL.md` §8 all described the cascade as the durability rule.
They now describe the refusal, and record that `checkpoints.branch_id`'s cascade
survives as referential integrity that the application no longer reaches.
**Documents corrected by this closeout:**
- `V1-ACCEPTANCE-TESTS.md` records results for **D11-D14, I04, L03** and the
E-series, and states that the browser-level condition is satisfied. **No pass
condition was weakened** — and D11/L03 now note that the automation crosses a
genuine OS process boundary, which is the standard later milestones should
meet.
- `DATA-MODEL.md` §8 records the coordinate as implemented, with the retry
measurement that settles coordinate-versus-turn-id, and three decisions that
were previously implicit: names are not unique, several Save Points may name
one position, and the list is newest-created first.
- `STORY-BRANCH-SEMANTICS.md` gains **§19.1** — a checkpoint protects the
history it names. This is the only behavioural specification change in the
closeout, and it strengthens §19 rather than weakening anything.
- `BROWSER-UX-SPEC.md` §25 rules for the implemented vocabulary: **Moment N**,
not *Turn N*, because the branch panel and tree overlay already count in
moments. Vocabulary only; no behaviour changes.
- `BUILD-MILESTONES.md` marks **M4 COMPLETE**, records the fixes and the browser
result, and warns M5 that the instrumentation to move is now **55 tests**.
- `README.md` records M1-M4 accepted and M5 as next to brief.
- `PROJECT-SOURCES.md` and `project-sources.txt` point at the current report;
`project-sources.txt` still named the archived M3 report and was corrected.
**Report rotation** happened in the reporting pass that preceded this closeout:
`M3-IMPLEMENTATION-REPORT.md` moved to `archive/milestone-reports/` as a pure
rename, and `reports/` now holds M4's report, whose **§W** is this closeout's
evidence record.
**No new ADR.** ADR 012 already decides the architecture, and the corrective work
forced no new architectural decision. `SPECIFICATION.md`,
`STORY-BRANCH-SEMANTICS.md`, `SECURITY-THREAT-MODEL.md`, `CONTEXT-AND-MEMORY.md`,
`IMPORTED-KNOWLEDGE-DESIGN.md` and ADRs 003, 005 and 012 are unchanged.
**M5 readiness:** ready. Save Points store no state and no checkpoint code reads
any, so M5 can change what a snapshot contains without touching what a Save Point
is — provided it keeps state recoverable at a position without replay
(`TECHNICAL-DESIGN.md` §10.4).
## v2.5 — M4 Implementation (2026-09-03)