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,8 +1,28 @@
|
||||
# Adventure Storyteller — V1 Acceptance Tests
|
||||
|
||||
**Status:** v1.2 planning/release contract — updated after Phase 0B, after M2 for
|
||||
the security contract (H10 strengthened, H12 added), and after M3 for history
|
||||
ownership and results (D03, D10, I07, L01)
|
||||
**Status:** v1.3 planning/release contract — updated after Phase 0B, after M2 for
|
||||
the security contract (H10 strengthened, H12 added), after M3 for history
|
||||
ownership and results (D03, D10, I07, L01), and after M4 for Save Point results
|
||||
(D11-D14, I04, L03, E-series) and the browser condition below
|
||||
|
||||
> **Browser-level verification (M4 closeout, 2026-09-03).** The browser smoke
|
||||
> condition that M3 and M4 both carried is **satisfied**. A real Firefox 154.0.1,
|
||||
> driven through geckodriver over the W3C WebDriver protocol, exercised the
|
||||
> rendered DOM: M3's Undo/Redo enable states, transcript movement, Retry and the
|
||||
> take pager, divergence and the loss of Redo; and M4's full Save Point
|
||||
> lifecycle including both confirmations and the branch-delete warning. 44/44
|
||||
> checks passed with no console errors, on two independent runs. No pass
|
||||
> condition anywhere in this document was changed to achieve it. See
|
||||
> `reports/M4-IMPLEMENTATION-REPORT.md` §W.
|
||||
>
|
||||
> **Three kinds of evidence are recorded separately below, and are not
|
||||
> interchangeable.** *Automated* means a test in the repository's suite, which
|
||||
> runs on every future change. *Live runtime* means a real server exercised over
|
||||
> HTTP — stronger than a unit test about process boundaries, weaker than a
|
||||
> browser about anything a user sees. *Browser* means the rendered DOM driven by
|
||||
> a real browser, which is the only evidence that a control is visible, enabled
|
||||
> and wired. Where a result cites more than one, the strongest is named last.
|
||||
|
||||
**Purpose:** Define black-box acceptance tests for finalist evaluation during Phase 0B and for the eventual v1 release.
|
||||
|
||||
## 1. Test Philosophy
|
||||
@@ -737,6 +757,14 @@ Before entering the abbey
|
||||
### Pass
|
||||
Checkpoint persists across application restart.
|
||||
|
||||
### Result — PASS (M4 closeout, 2026-09-04)
|
||||
*Automated:* `backend/tests/test_process_restart.py` starts the application as a
|
||||
subprocess, writes the campaign, **terminates the process**, and starts a second
|
||||
process against the same database — the Save Point, its name and its
|
||||
`(branch, depth)` coordinate all survive.
|
||||
*Browser:* the Save Point is still listed after a full page reload
|
||||
(`reports/M4-IMPLEMENTATION-REPORT.md` §W.7 section E).
|
||||
|
||||
---
|
||||
|
||||
## D12 — Restore Checkpoint
|
||||
@@ -751,6 +779,11 @@ Checkpoint persists across application restart.
|
||||
### Pass
|
||||
Transcript/state return to checkpoint position.
|
||||
|
||||
### Result — PASS (M4 closeout, 2026-09-04)
|
||||
*Automated:* `test_d12_restore_returns_the_transcript_and_the_state`.
|
||||
*Browser:* the visible transcript and the state both move back, and the view
|
||||
refreshes without a manual reload (§W.7 section F).
|
||||
|
||||
---
|
||||
|
||||
## D13 — Restore Does Not Delete Later History
|
||||
@@ -760,6 +793,13 @@ Transcript/state return to checkpoint position.
|
||||
### Pass
|
||||
Later story is retained as abandoned/disposable history.
|
||||
|
||||
### Result — PASS (M4 closeout, 2026-09-04)
|
||||
*Automated:* measured on **row identity**, not on counts — the set of action row
|
||||
ids before a restore equals the set after it. Ordinary Redo still walks the retained
|
||||
continuation until a divergent write, and after that write the displaced rows are
|
||||
still present while Redo reports nothing ahead. Four `test_d13_*` tests, and
|
||||
confirmed in the browser with a database check behind it.
|
||||
|
||||
---
|
||||
|
||||
## D14 — Delete Checkpoint
|
||||
@@ -773,10 +813,31 @@ Delete named checkpoint.
|
||||
- checkpoint pointer disappears,
|
||||
- referenced story turn/history remains intact.
|
||||
|
||||
### Result — PASS (M4 closeout, 2026-09-04)
|
||||
*Automated:* the pointer row goes; the referenced turn, the later history and the
|
||||
active head are all unchanged (`test_d14_delete_removes_the_pointer_and_no_story`).
|
||||
*Browser:* the confirmation states that deleting the Save Point does not delete
|
||||
the story, and the story remains afterwards (§W.7 section I).
|
||||
|
||||
A Save Point is also the **only** thing that can remove itself: deleting a branch
|
||||
whose history a Save Point names is refused rather than cascading
|
||||
(`STORY-BRANCH-SEMANTICS.md` §19.1), verified automatically and in the browser.
|
||||
|
||||
---
|
||||
|
||||
# E. Branch and Lineage Safety
|
||||
|
||||
> **M4 result (2026-09-03).** E01 and E04 were re-exercised through a Save Point
|
||||
> restore rather than only through Undo, and pass: a memory derived past a
|
||||
> restored head stops being retrievable and becomes eligible again on Redo,
|
||||
> without being deleted or re-embedded; after restore-plus-divergence the old
|
||||
> future's memory stays ineligible even as the new line grows past its depth; and
|
||||
> the transcript after a restore holds only the active lineage while the
|
||||
> displaced rows remain in the tree. **E03** (summary lineage over a long story)
|
||||
> remains **NOT PERFORMED** — it needs a long-run campaign and is owned by
|
||||
> M6/M11, unchanged from M3.
|
||||
|
||||
|
||||
## E01 — Abandoned Future Cannot Affect Active State
|
||||
|
||||
**Priority:** REQUIRED FOR V1
|
||||
@@ -1353,6 +1414,15 @@ Export preserves retained alternate/disposable history needed for recovery, unle
|
||||
### Pass
|
||||
Named checkpoints survive export/import.
|
||||
|
||||
### Result — PASS (M4 closeout, 2026-09-04)
|
||||
*Automated and live runtime.* A real round trip with three Save Points across two
|
||||
branches: names, notes and
|
||||
coordinates survive, branch references are remapped to the imported rows
|
||||
(1→3, 2→4), and each restores to a distinct position and state in the new
|
||||
campaign. Importing Save Points does **not** move the active head — the head
|
||||
still comes from the bundle's `headDepth`. Bundles written before M4 carry no
|
||||
`checkpoints` key, import cleanly, and create none.
|
||||
|
||||
---
|
||||
|
||||
## I05 — Knowledge Provenance Export
|
||||
@@ -1581,6 +1651,14 @@ State at each position matches original accepted state.
|
||||
### Pass
|
||||
Correct historical state is reconstructed.
|
||||
|
||||
### Result — PASS (M4 closeout, 2026-09-04)
|
||||
*Automated, across a genuine OS process boundary:* the state at the Save Point
|
||||
was recorded before the first process exited, and a second process restored
|
||||
exactly that value after the campaign had been advanced past it
|
||||
(`backend/tests/test_process_restart.py`). This replaced a same-process
|
||||
`TestClient` restart, which could not distinguish durable state from a live
|
||||
object.
|
||||
|
||||
---
|
||||
|
||||
## L04 — Derived Data Can Be Rebuilt
|
||||
|
||||
Reference in New Issue
Block a user