M9: a campaign you can actually get back

A campaign could already be exported and imported. What could not survive the
trip was everything that explains it: the state events behind the authoritative
document, the prompt each turn was actually given, the passages it was shown,
the summaries that carry long-story continuity, and which take belonged to which
turn. An imported campaign could be read and could no longer say why it was what
it was — and a manual correction, the one state change no narration explains,
was indistinguishable from something the story had established.

The bundle is now `ai-dnd-adventure-v3`, and the version is the design rather
than a side effect. Everything added here could have been another optional key,
the way persona, Save Points, narrative state and imported knowledge each were.
That mechanism stops working at exactly this addition: a v2 file with no prompt
provenance is ambiguous between "written before M9" and "written by M9 from a
campaign that has none", and those are different facts about a campaign. A
version number is how a recovery file states what it was capable of recording.
v1 and v2 still import, and every seam from pre-active-head onward is tested for
the rule that an older file is never reinterpreted under a newer assumption.

Two categories became three. "Chosen travels, derived is recomputed" was enough
until stored prompts had to be decided: they are derived, and they must travel
anyway. The test that separates evidence from cache is not "could this be
recomputed" but "would a recomputation answer the same question" — a rebuilt
search index answers the same question, a rebuilt prompt says what the turn
would be told *now*, which is the opposite of what the inspector is for.

Also here: a real SQLite backup, through the online backup API rather than a
file copy, taken while the application is running and verified before it is
kept; story cards settled as compatibility-only legacy data and taken out of the
narrator's prompt, because they were the untracked path around knowledge
authority that IMPORTED-KNOWLEDGE-DESIGN §73 already forbade; and no schema
change at all, proved against a database M8's own code wrote.

Three defects, found by running the milestone's own tests rather than by reading
them. Deleting a campaign leaked its FTS index rows, and SQLite then handed the
freed ids to the next source imported into any campaign, which failed with an
integrity error that Reindex could not repair — both ends are closed, and a
database already carrying the damage now repairs itself. An imported node with
no state snapshot was being stamped with the campaign's head state, so an Undo
to turn 2 showed what the story knew at turn 20. And the snapshot relink did not
persist at all, because it mutated a dict in place on a column SQLAlchemy tracks
by assignment: it looked correct in memory and wrote the wrong ids to disk.

Carrying per-turn prompts looked like it would halve the length of campaign that
can be restored. Measured — and after compressing them inside the file —
everything M9 added costs 12% of it: the import ceiling moves from about 318
turns to about 279, against a 100-turn certification target. The dominant cost
is not M9's at all. The per-position narrative state document is 74% of a
bundle, and v2 already carried it.

Backend 1,102 passed / 14 skipped / 0 failed. Frontend 145 passed. Lint,
production build and Docker build clean. Verified across two server processes
with two data directories, and in a real browser against a real narrator.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qyn3oRd4D6pi72nKBG725B
This commit is contained in:
JesseMarkowitz
2026-09-07 01:55:45 -04:00
co-authored by Claude Opus 5
parent 1ce9972760
commit 44edece67e
46 changed files with 9227 additions and 178 deletions
+48 -36
View File
@@ -3,7 +3,8 @@
**This file is the index. Start here.**
**Current state:** Phase 0 complete; AI-DnD forked as the production base;
milestones **M1 through M6 implemented and accepted** (M3 and M4: 2026-09-03;
milestones **M1 through M8 implemented and accepted**, and **M9 implemented and
awaiting review**. M1-M6 were accepted on the dates below (M3 and M4: 2026-09-03;
M5: 2026-09-04; M6: 2026-09-06). M5 and M6 were each accepted only after an
independent review found a real defect and a corrective pass fixed it.
@@ -19,8 +20,15 @@ carries the closeout: the build-evidence classification in its §P, finding 14's
operational resolution, and the acceptance record in its §V. The M8 tree is
staged and awaits the repository owner's signed commit.
**Next: M9 — Export, Backup, Recovery, and Migration Hardening.** It has not
been started.
**M9 — Export, Backup, Recovery, and Migration Hardening — is implemented and
awaiting independent review** (2026-09-07).
`reports/M9-IMPLEMENTATION-REPORT.md` is the implementer's account, written for
a reviewer: a set of claims with the measurements attached, not yet a record of
acceptance. M8's report has moved to `archive/milestone-reports/`, which is
where a milestone report goes once the next milestone's report replaces it.
**Next: M10 — Future Media Extension Hooks Only.** It has not been started, and
no brief for it exists.
**Package version:** see `VERSION.md`, which records what each revision changed
and why.
@@ -82,8 +90,8 @@ Two standing qualifications:
| Document | What it is for |
| --- | --- |
| `SPECIFICATION.md` | What the product must do. The top of the authority order. |
| `TECHNICAL-DESIGN.md` | The selected architecture, including what M1-M7 built, recorded as fact. |
| `DATA-MODEL.md` | Entities, the stored head, branch disposition, and the export shape. |
| `TECHNICAL-DESIGN.md` | The selected architecture, including what M1-M9 built, recorded as fact. |
| `DATA-MODEL.md` | Entities, the stored head, branch disposition, and the v3 export contract. |
| `STORY-BRANCH-SEMANTICS.md` | Undo/Redo/Retry/branch/take behavior, including the M3 ratifications. |
| `CONTEXT-AND-MEMORY.md` | Prompt assembly, summarization, branch-safe memory. |
| `IMPORTED-KNOWLEDGE-DESIGN.md` | Canon / Reference / Inspiration knowledge as a first-class subsystem. |
@@ -110,7 +118,7 @@ Two standing qualifications:
10. `BROWSER-UX-SPEC.md`
11. `V1-ACCEPTANCE-TESTS.md`
12. `DECISIONS/` — all of them; they are short.
13. `reports/M8-IMPLEMENTATION-REPORT.md`, for what the most recent milestone
13. `reports/M9-IMPLEMENTATION-REPORT.md`, for what the most recent milestone
actually left behind — reading it as a claim to check, not a record, until
it is reviewed. Nothing in `planning/archive/` unless sent there.
@@ -143,20 +151,18 @@ work until Phase 0 closes — which Phase 0 satisfied on 2026-09-01. It is in
`reports/` holds the report for the milestone most recently completed, because
that is the one the next milestone's planning has to consult:
- `reports/M8-IMPLEMENTATION-REPORT.md` — the M8 implementation, its baseline
UX measurement, and the browser evidence for every acceptance test it claims.
Written by the implementer for an independent reviewer, and completed at
closeout after that review accepted the milestone: it is a set of claims with
the measurements attached **and** the record of the acceptance. Its §U carries
the M9 handoff — the four questions the next brief has to decide.
- `reports/M9-IMPLEMENTATION-REPORT.md` — the M9 implementation: the measured M8
portability baseline it started from, the final bundle contract, and the
evidence for every acceptance test it claims. Written by the implementer for
an independent reviewer, so it is a set of claims with the measurements
attached and **not** a record of acceptance. Its §W carries the M10-M11
handoff.
**It stays here until M9's report replaces it.** A milestone report is useful
during the immediately following milestone; M8's is not archived merely
because M8 is accepted.
**It stays here until M10's report replaces it.**
Completed earlier milestones are in `archive/milestone-reports/`, which M7's
report joined when M8's was written: a milestone report is useful during the
immediate next milestone and historical afterwards. M1-M7 are all there,
Completed earlier milestones are in `archive/milestone-reports/`, which M8's
report joined when M9's was written: a milestone report is useful during the
immediate next milestone and historical afterwards. M1-M8 are all there,
unedited.
## The decision this package rests on
@@ -282,33 +288,39 @@ Milestone M8 COMPLETE / ACCEPTED (2026-09-06)
story operations review + closeout, in sequence
|
v
Milestone M9 NEXT — not started
export, backup, recovery, see BUILD-MILESTONES.md
migration hardening
Milestone M9 COMPLETE — awaiting review (2026-09-07)
export, backup, recovery, reports/M9-IMPLEMENTATION-REPORT.md
migration hardening bundle format v3; SQLite online backup
|
v
M10-M11, one at a time see BUILD-MILESTONES.md
Milestone M10 NEXT — not started
future media extension hooks see BUILD-MILESTONES.md
|
v
Milestone M11 see BUILD-MILESTONES.md
```
## Stop Rule
**One milestone at a time. Do not begin a milestone before its brief exists.**
**No M9 brief has been prepared.** Writing one is the current action, informed
by the M8 report and by the debt `BUILD-MILESTONES.md` records against M8 — in
particular that the campaign bundle still carries no context snapshots, so an
imported campaign has no historical prompt provenance; that story cards survive
in the backend and the bundle with no browser surface, and M9 should decide
deliberately whether the bundle keeps carrying them; and that a deployment whose
Ollama enforces a small context window truncates an imported long campaign
immediately unless the `DEVELOPMENT.md` procedure or a matching
`context_token_budget` is applied.
**No M10 brief has been prepared**, and M9 is not accepted — it is implemented
and awaiting an independent review. Writing the M10 brief is the action after
that review closes, informed by the M9 report's §W.
**M8's own carried debt** is recorded under M8 in `BUILD-MILESTONES.md`: story
cards have no browser editor, the RPG world state is read-only, copy is
per-message only, there is no discarded-history recovery screen, and the tablet
layout is usable but untuned. Each names the milestone that owns it; none is an
open M8 condition.
All three questions the M8 debt raised against M9 are settled and recorded:
the bundle carries historical context snapshots (`DATA-MODEL.md` §29); story
cards are compatibility-only legacy data and no longer reach the narrator
(`IMPORTED-KNOWLEDGE-DESIGN.md` §73); and the deployment context ceiling is
documented in `DEVELOPMENT.md` with the note that an imported long campaign
meets it on its first turn rather than gradually. The window itself stays M11's.
**M8's own carried debt** is recorded under M8 in `BUILD-MILESTONES.md`. Two of
its five items are now closed by M9 — story cards have a settled policy, and the
bundle carries the provenance. The RPG world state is still read-only, copy is
still per-message only, there is still no discarded-history recovery screen, and
the tablet layout is still untuned. Each names the milestone that owns it; none
is an open M8 condition.
The M6 retrieval debt this milestone was warned about is partly addressed and
partly still open. Imported material does **not** compete with story memory for