Planning: close M3 and record active-head architecture
M3's review recommended planning changes and, following the M2 pattern, reported rather than applied them. This applies them, and adds the ADR the review asked for. ADR 012 records the architecture rather than the requirement. ADR 005 already says that going backward must preserve abandoned history and that the user sees Undo/Redo/Retry rather than branch management; it names a movable active head as the direction and stops. What M3 settled is the shape: the head is stored rather than derived, every read of the story is capped at it in one place, one mechanism moves it, the state of a position comes off the node rather than from a replay, the first write below a moved-back head is the divergence, and whether Redo exists is decided by the lineage rather than by a flag that could be stale. The last of those is the property worth keeping — a flag can be wrong and make the story wrong; a lineage cannot. Two semantics are ratified in STORY-BRANCH-SEMANTICS.md, both of them reversals or narrowings that a reader would otherwise take for bugs. Undo now crosses fork points and continues to the campaign opening, because refusing at the fork was a consequence of deleting rows the parent line was also reading, and nothing is deleted any more. And the system refuses to switch which take is live while a later story is off screen, because doing it quietly would leave retained history continuing from words the story no longer says. A new §14A covers editing in place. §14-15 describe the finished behaviour — the edit becomes authoritative, the state it implies is re-evaluated, a new continuation is created, the original is retained — and that requirement is intact and explicitly not weakened here. It is also not built, because re-evaluating state from prose a user typed needs M5's extraction pass. §14A says what exists in the meantime and why refusing is the minimum that holds the invariant rather than the destination. TECHNICAL-DESIGN.md gains §8.7 and §9.1, recording the implemented model and the bundle behaviour as fact in the way §5.2 records M1 and M2. §10.4 gains a constraint that is easy to lose: the snapshot half of the hybrid state model is a requirement, not an optimization. Head movement is a row lookup plus a restore, which is why Undo, Redo and Save Point restore cost the same at any distance into a campaign; a state model recoverable only by replaying from the opening would make all three proportional to campaign length, on exactly the long campaigns this product is for. DATA-MODEL.md records the head as stored on the campaign rather than derived from its newest turn — two campaigns holding identical turns can be read at different places, and nothing about the turns can tell them apart — and the branch disposition as implemented: the depth a divergent write left the branch at, deliberately advisory, and carried through export because every row of an abandoned line is exported either way. BUILD-MILESTONES.md marks M3 complete and states the one condition still open. M4 is told a Save Point is a durable pointer and that restoring one is head movement with a bounds check, not a restore system: a second mover is the specific failure to avoid, because the two paths would silently disagree about what restore means. M5 gets three constraints — keep state efficiently recoverable, move the test instrumentation rather than the assertions when the world-state protocol goes, and finish the narrator edit §14A defers. V1-ACCEPTANCE-TESTS.md clarifies ownership without lowering a bar. D10 keeps all three pass conditions and is explicitly recorded as *not* satisfied at the end of M3; what changed is that the document now says which milestone delivers which condition. D03's result is recorded as a full pass rather than the partial the text allowed for, I07 gains the pre-M3 bundle clause, and L01 gains the note that resolves its apparent conflict with A05 — a failed turn does advance the head by one, onto the player's retained input, and that is A05 working rather than L01 failing. README.md described a different application: a hosted demo, guest accounts, cloud providers, Postgres, a Render blueprint, an analytics dashboard, a QuickJS scripting engine, and 549 tests. M2 removed all of that and the README was never updated — a gap M2's own debt table missed. It now describes what this fork is, including the endpoint policy and the TLS behaviour, and the numbers in it are the current ones. M3's report is included here as its own evidence record: no separate baseline report was produced, so it carries the raw counts and runtime observations as well as the review, and §W records this closeout. SPECIFICATION.md and SECURITY-THREAT-MODEL.md are unchanged. M3 altered no product requirement and touched no path in the threat model. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QF5TcoB86QADgjHz1GZe8u
This commit is contained in:
co-authored by
Claude Opus 5
parent
7f082b61d8
commit
c8755c21c2
@@ -361,6 +361,58 @@ Abandoned history must:
|
||||
- stop influencing current state/context/memory/summary,
|
||||
- remain available for future recovery/cleanup features.
|
||||
|
||||
### 8.7 As implemented in M3
|
||||
|
||||
M3 built this model. The following is fact rather than direction, and ADR 012
|
||||
records it as the architectural decision. Sections 8.1-8.6 stand; this says how
|
||||
they were realised.
|
||||
|
||||
**The head is stored, not derived.** A campaign carries a branch and a depth,
|
||||
and that pair is the active head. No read may recompute it from the newest row —
|
||||
that was the pre-M3 behavior, and it is what made Redo impossible and made an
|
||||
export reopen an undone campaign at its tip.
|
||||
|
||||
**Lineage reads are capped at the head, in one place.** The path abstraction that
|
||||
already resolved a branch's ancestry now also limits every entry to the head, so
|
||||
the transcript, the assembled narrator context, take/parent resolution and memory
|
||||
retrieval narrow together. There is exactly one way to read past the head — a
|
||||
named, uncapped view of the same lineage — and only two callers may use it: Redo,
|
||||
and the check that decides whether a write must fork. Any new feature that reads
|
||||
story rows directly, rather than through the capped lineage, will see retained
|
||||
history the story is not telling.
|
||||
|
||||
**Head movement is one mechanism.** Undo, Redo, and anything later that restores
|
||||
a position resolve a target depth and then call a single move operation, which
|
||||
sets the coordinate and restores the state recorded at it. Undo and Redo differ
|
||||
only in which way they resolve the target. Both step over a whole turn — a
|
||||
player's action and the reply to it — so the head never rests between the two
|
||||
halves of one turn.
|
||||
|
||||
**State comes from the node, not from a replay.** Each node records the state it
|
||||
left behind, so moving the head is a row lookup plus a restore: the same cost at
|
||||
any distance, in either direction, and identical whether the position is reached
|
||||
from in front of it or from behind. This is the property §10.4's hybrid storage
|
||||
must preserve.
|
||||
|
||||
**Divergence is a property of the lineage, not a flag.** The first write below a
|
||||
moved-back head forks; Undo alone never does. After the fork, the displaced
|
||||
future is no longer on the lineage being read, so ordinary Redo finds nothing
|
||||
ahead and reports that it has nowhere to go. Nothing has to be invalidated,
|
||||
cleared, or kept in step.
|
||||
|
||||
**A branch the story leaves records the depth and time it was left**, as metadata
|
||||
nothing reads to decide behavior (§8.6's "implementation-appropriate metadata").
|
||||
It makes a divergence observable and gives later cleanup and recovery features
|
||||
something to select on; because no decision depends on it, a stale or hand-edited
|
||||
value cannot make the story wrong.
|
||||
|
||||
**Operations that change what the story says at a position must ask whether
|
||||
story descends from that position and is off screen.** Switching which take is
|
||||
live, and editing a turn's text in place, both refuse in that situation rather
|
||||
than act silently, because retained history must not be made to disagree with
|
||||
itself in a way the user cannot see. See `STORY-BRANCH-SEMANTICS.md` §10 and
|
||||
§14A.
|
||||
|
||||
## 9. Export / Import and Head Position
|
||||
|
||||
AI-DnD's current export carries branch information but reconstructs the imported head at the branch tip.
|
||||
@@ -383,6 +435,31 @@ For compatibility with earlier bundles, import may fall back to the retained tip
|
||||
|
||||
Export/import regression tests must include an undone campaign and verify the imported story reopens at the exact exported head rather than silently redoing later turns.
|
||||
|
||||
### 9.1 As implemented in M3
|
||||
|
||||
The bundle carries the active head depth beside the active branch, and the import
|
||||
honors it. This moved the head depth across the format's own rule about what a
|
||||
bundle carries: a bundle carries what was *chosen* and recomputes what is
|
||||
*derived*, and before M3 the head depth was genuinely derived — the newest row was
|
||||
the only place a story could be read. It is a decision now, because the same tree
|
||||
exports identically whether the user undid three turns or none, so the file has to
|
||||
say.
|
||||
|
||||
A file that does not state a head is opened at the tip of its active branch. That
|
||||
is a fallback only in form: such a file was written when the head could not be
|
||||
anywhere else, so deriving the tip reproduces the position it actually recorded.
|
||||
Pre-tree bundles take the same path. No format version bump was required, because
|
||||
an absent field is unambiguous.
|
||||
|
||||
The head depth is validated before any row is written — a depth past the branch's
|
||||
own retained story is a file disagreeing with itself and is refused, while a depth
|
||||
*behind* it is the feature.
|
||||
|
||||
The bundle also carries which branches the story has left, and at what depth.
|
||||
Every row of an abandoned line is exported either way, so without that metadata a
|
||||
restored campaign could not distinguish abandoned history from active history —
|
||||
which is precisely what a later cleanup or recovery feature has to select on.
|
||||
|
||||
## 10. Authoritative Narrative State
|
||||
|
||||
### 10.1 Do not retain the RPG state protocol as the product model
|
||||
@@ -457,6 +534,17 @@ Selected direction:
|
||||
|
||||
Events provide audit/reconstruction value. Snapshots/cache make normal reads, Undo/Redo, and context construction fast.
|
||||
|
||||
**Constraint added by M3 (see ADR 012).** The snapshot half is not an
|
||||
optimization to be traded away. M3's head movement is a row lookup plus a
|
||||
restore, which is why Undo, Redo and — later — Save Point restore cost the same
|
||||
at any distance into a campaign's history. A state model that could only be
|
||||
reconstructed by replaying events from the campaign opening would make every one
|
||||
of those operations proportional to campaign length, on exactly the long
|
||||
campaigns this product exists for. Whatever M5 introduces must keep the
|
||||
authoritative state at a retained position efficiently recoverable — a per-node
|
||||
snapshot, or an equivalent cache with the same property — while adding the typed
|
||||
event model.
|
||||
|
||||
## 11. Context and Memory
|
||||
|
||||
Retain AI-DnD's useful lineage-aware memory foundation, but align it with the product authority model.
|
||||
|
||||
Reference in New Issue
Block a user