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:
co-authored by
Claude Opus 5
parent
279a871a77
commit
62a997f364
@@ -1,6 +1,6 @@
|
||||
# Adventure Storyteller — Production Build Milestones
|
||||
|
||||
**Status:** In implementation. M1, M2 and M3 complete and accepted (M1 and M2: 2026-09-02; M3: 2026-09-03); M4 — Named Save Points / Checkpoints — implemented 2026-09-03, awaiting review
|
||||
**Status:** In implementation. M1-M4 complete and accepted (M1 and M2: 2026-09-02; M3 and M4: 2026-09-03); M5 — Genre-Neutral Authoritative Narrative State — next to brief
|
||||
**Base:** AI-DnD `d72f7c1bda0f34fccd84afb7a25c34eb01c901de`
|
||||
|
||||
## 1. Purpose
|
||||
@@ -274,14 +274,18 @@ review. The architecture is recorded in **ADR 012**.
|
||||
a turn says while story descends from it off screen — both ratified in
|
||||
`STORY-BRANCH-SEMANTICS.md` (§5, §10, §14A).
|
||||
|
||||
**Outstanding closeout condition:** the required **browser smoke test has not
|
||||
been performed** — no session in which M3 was implemented or reviewed had a
|
||||
browser available. The equivalent sequence was driven end-to-end against the
|
||||
running application with real inference and a process restart, and every
|
||||
server-side behaviour it covers passes; the DOM-level behaviour of the Redo
|
||||
button, its disabled states and its keyboard shortcut remain unverified by
|
||||
observation. This does not block M4, which touches none of that wiring, but it
|
||||
remains an open M3 item until a human runs it.
|
||||
**Closeout condition — CLOSED at M4 closeout (2026-09-03).** M3 was accepted with
|
||||
one condition outstanding: the required **browser smoke test had not been
|
||||
performed**, because no session in which M3 was implemented or reviewed had a
|
||||
browser available, leaving the DOM-level behaviour of the Redo button and its
|
||||
disabled states unverified by observation.
|
||||
|
||||
That condition is now discharged. A real Firefox 154.0.1, driven through
|
||||
geckodriver, exercised M3's controls in the rendered application: Undo enabled
|
||||
and Redo disabled at the tip, two Undos moving the transcript back, Redo becoming
|
||||
enabled and returning the original tip exactly, Retry and the take pager, and a
|
||||
divergent write retiring Redo with no stale old-future text on screen. It passed.
|
||||
Evidence: `planning/reports/M4-IMPLEMENTATION-REPORT.md` §W.7.
|
||||
|
||||
**Debt carried forward, none of it blocking M4:** full narrator-edit state
|
||||
re-evaluation is deferred to M5 (`STORY-BRANCH-SEMANTICS.md` §14A records the
|
||||
@@ -351,12 +355,11 @@ M3's cost was reconciling them; a parallel checkpoint mover would recreate that
|
||||
divergence in a place where the two paths would silently disagree about what
|
||||
"restore" means. See ADR 012.
|
||||
|
||||
## Status: IMPLEMENTED — awaiting review
|
||||
## Status: COMPLETE
|
||||
|
||||
Implementation landed 2026-09-03. **Not accepted**: the milestone report has not
|
||||
been written and no reviewer has read the change. The Definition of Done above is
|
||||
met by the code and the tests below; whether it is met by the *product* is what
|
||||
the review is for.
|
||||
Accepted 2026-09-03. Evidence: `planning/reports/M4-IMPLEMENTATION-REPORT.md`,
|
||||
including its §W closeout addendum. The Definition of Done is met, and — for the
|
||||
first time in this project — **verified in a real browser**.
|
||||
|
||||
**What M4 delivered:**
|
||||
|
||||
@@ -395,17 +398,77 @@ it.
|
||||
E-series lineage and memory isolation after restore and divergence, the edge
|
||||
cases in the brief, and the M3-database migration.
|
||||
|
||||
**Outstanding condition, carried from M3 and not resolved here:** the **browser
|
||||
smoke test has still not been performed**, for M3 or for M4. No session has had a
|
||||
usable browser. The M4 sequence was driven end-to-end over HTTP against a live
|
||||
server with a real process restart, and every server-side behaviour it covers
|
||||
passes; the DOM-level behaviour of the Save Point panel, its buttons and its
|
||||
confirmations remains unverified by observation.
|
||||
**Facts M5 inherits, and must not redesign:**
|
||||
|
||||
**Debt M4 carries forward:** none newly discovered in the head model. The Save
|
||||
Point panel has no frontend test, because the project still has no frontend test
|
||||
runner at all (M8). `POST /adventures/import` still returns every branch's rows
|
||||
rather than a head-capped window (inherited, M3).
|
||||
- a Save Point is a **durable story coordinate** — `(branch, depth)` — carrying
|
||||
no copy of transcript, state, prompt, memory or summary;
|
||||
- **restore is M3 head movement**, through the same `head.move_to` Undo and Redo
|
||||
use; there is no second restore path and M5 must not add one;
|
||||
- **state recovery stays snapshot/cached-position based**, never a replay of the
|
||||
campaign (`TECHNICAL-DESIGN.md` §10.4). This is now load-bearing for Save
|
||||
Points as well as Undo/Redo;
|
||||
- **the first divergent write** after a restore creates the continuation;
|
||||
restore itself never forks;
|
||||
- **history a Save Point names cannot disappear** through an unrelated deletion
|
||||
(§19.1);
|
||||
- **M3/M4 history and Save Point semantics are infrastructure now.** M5 replaces
|
||||
the state *model*; it does not revisit how the story is positioned.
|
||||
|
||||
**Acceptance evidence:** D11-D14, I04 and L03 all pass. D11 and L03 are
|
||||
discharged by automation that crosses a **genuine OS process boundary** — one
|
||||
server process writes the campaign, is killed, and a second process reads it back
|
||||
— rather than by recreating a client in one process.
|
||||
|
||||
**The browser condition is closed, for M4 and retrospectively for M3.** A real
|
||||
Firefox 154.0.1, driven through geckodriver over the W3C WebDriver protocol,
|
||||
exercised the rendered DOM end to end: **44/44 checks passed**, covering M3's
|
||||
Undo/Redo enable states and transcript movement, Retry and the take pager, M3
|
||||
divergence and the disappearance of Redo, and every M4 Save Point operation
|
||||
including both confirmations and the branch-delete warning. No console errors.
|
||||
This closes the outstanding M3 condition recorded above and the equivalent M4
|
||||
one.
|
||||
|
||||
**The three review findings were fixed during closeout:**
|
||||
|
||||
- **B-1** — the Save Point list was an N+1 that loaded whole `Action` rows,
|
||||
narration included. It is now one bulk two-column coordinate query plus one
|
||||
lineage: **53 SELECTs for 25 Save Points became 5**, and the query count no
|
||||
longer moves with the length of the list.
|
||||
- **B-2** — deleting a branch silently deleted the Save Points naming it. Fixed
|
||||
as a **behaviour** defect, not a wording one: a branch a Save Point names can
|
||||
no longer be deleted at all. 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, in the branch list and in the tree
|
||||
overlay, disable and explain rather than warning about a loss that no longer
|
||||
happens. `STORY-BRANCH-SEMANTICS.md` **§19.1** records the rule; §28 already
|
||||
required a future cleanup feature to retain checkpoint-referenced paths, and
|
||||
this is that requirement applied to the deletion path that exists today.
|
||||
- **B-3** — the D11/L03 automation now spawns real server processes.
|
||||
|
||||
**Also fixed:** creating a Save Point takes the campaign's turn lock (review §S
|
||||
C-5), so "save where I am" cannot read a head that a turn in flight is about to
|
||||
move. Rename and Delete deliberately do not take it — neither reads nor moves a
|
||||
story position.
|
||||
|
||||
**Debt M4 carries forward:** the Save Point panel has no frontend test, because
|
||||
the project still has no frontend test runner at all (M8) — the browser smoke
|
||||
test above is a closeout procedure, not a suite. `POST /adventures/import` still
|
||||
returns every branch's rows rather than a head-capped window (inherited, M3).
|
||||
|
||||
## Note to M5 — the instrumentation is now larger than M3 estimated
|
||||
|
||||
M4 added **60 tests that use inherited RPG world-state values as deterministic
|
||||
instrumentation**, on top of the ~20 M3 flagged. M5 replaces that state model,
|
||||
and must **move the instrumentation while preserving the behavioural
|
||||
assertions**: what those tests measure is where the story is being read and what
|
||||
state belongs to that position, which is exactly as true after M5 as before it.
|
||||
Deleting them would delete the evidence for D11-D14, I04, L03 and the E-series.
|
||||
|
||||
The existing constraint stands and is now load-bearing for Save Points as well as
|
||||
Undo/Redo: **historical state must remain efficiently snapshot/cache
|
||||
recoverable**, so that moving to a position never becomes proportional to
|
||||
campaign length (`TECHNICAL-DESIGN.md` §10.4). Restore, Undo and Redo all pay
|
||||
whatever that costs.
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user