The planning package still described M01 as outstanding. It now records the evidence run on96c1bf5and the two product defects found on the way. It also corrects three statements that were never true. - V1-ACCEPTANCE-TESTS.md: result blocks for M01-M04. M04 is recorded as recovered through authoritative state, with the owner's acceptance of that on 2026-09-13 and the positional precondition explained. Correction: v3.7 said this file carried M11 results against every REQUIRED test. None were written, and the per-test matrix is the M11 report's §F. The §P3 M11 disposition said the report records the identity diagnostic's findings. It does not, and the disposition now says so. - BUILD-MILESTONES.md: the M11 status block records the long-run evidence, the write-lock and protocol-leak defects, and what is left for the reviewer. - DATA-MODEL.md §28B: M11 added two columns, not one. settings.context_window_override (migration 94,ef25b0a) was never recorded. - TECHNICAL-DESIGN.md: "Background failure observability" gains the rule that nothing in a turn writes before the model call, and new §15.4 records that stored narration carries story only, with the extractor's rules. - CONTEXT-AND-MEMORY.md §51 and ADR 013: as-implemented notes for the same two fixes. - README.md and VERSION.md: status, milestone map, stop rule, and the v3.9 entry. - M11 report §Q: the "not revised" note is replaced by what v3.9 revised. No requirement changes. No code changes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0136VBTMUKWYeU6G9HgbDbND
123 lines
5.5 KiB
Markdown
123 lines
5.5 KiB
Markdown
# ADR 013 — The Authoritative Narrative State Document
|
|
|
|
**Status:** Accepted; implemented in M5
|
|
**Date:** 2026-09-04
|
|
|
|
## Context
|
|
|
|
[ADR 010](010-explicit-typed-narrative-state-events.md) settled the **protocol**:
|
|
the model proposes change as explicit, typed, absolute events drawn from a fixed
|
|
allowlist, never as relative deltas. It did not settle what those events write
|
|
into.
|
|
|
|
That shape turned out to be load-bearing. The prompt renders it, the browser's
|
|
state panel groups it, export carries it, migration has to produce it for
|
|
positions that predate it, and every Undo, Redo, take switch and Save Point
|
|
restore reads a copy of it. The M5 review recommended recording it as a decision
|
|
rather than leaving it an implementation detail of `narrative/model.py`. This ADR
|
|
records what was built; it does not extend it.
|
|
|
|
## Decision
|
|
|
|
### The document
|
|
|
|
One JSON document is the campaign's authoritative account of its own story. It
|
|
is genre-neutral: nothing in it names a stat, a level, a currency or a class.
|
|
|
|
```text
|
|
entities who and what exists — people, places, things, groups.
|
|
Each carries a name, a type, aliases, a description, a status,
|
|
free-form attributes, conditions, and a current location.
|
|
facts what has been established, as subject/predicate/object.
|
|
Each carries an id, a status, and where it came from.
|
|
relationships how entities stand to one another, directionally.
|
|
possessions which entity holds which item.
|
|
threads open story threads, with a title and a status.
|
|
scene where the story is now, and a short summary of the moment.
|
|
```
|
|
|
|
Two fields appear throughout and are the reason the document can be trusted:
|
|
|
|
- **authority** — who established this: the story, or the reader's own
|
|
correction. A reader's correction outranks the narration, and the prompt says
|
|
so in words.
|
|
- **provenance** — the branch and depth the change was made at, so a fact can be
|
|
traced to the moment it entered the story.
|
|
|
|
Nothing is deleted. A fact a correction takes back is marked `invalidated`, with
|
|
the reason and the position, because a record that vanished would audit nothing.
|
|
|
|
### The pipeline
|
|
|
|
```text
|
|
model output
|
|
-> extraction the protocol block is separated from the prose
|
|
-> validation envelope, allowlist, schema, references, canon
|
|
-> validated events recorded in `state_events`, with their provenance
|
|
-> the document applied to produce the new authoritative state
|
|
-> per-position snapshot stored on the node the turn produced
|
|
```
|
|
|
|
Each stage has one job, and the order is deliberate: the allowlist is checked
|
|
before any field is read, so an unknown event type is rejected before its
|
|
contents are touched.
|
|
|
|
**Implementation note (post-M11).** "The protocol block is separated from the
|
|
prose" covers more than one fenced block. A small local model also pastes the
|
|
rendered state into its prose, writes its proposal unfenced or quoted, and runs
|
|
out of tokens partway through it. All of that is removed before the prose is
|
|
stored, because stored prose is replayed as history. `TECHNICAL-DESIGN.md` §15.4
|
|
has the rules.
|
|
|
|
### Where it lives
|
|
|
|
- `adventures.narrative_state` — the current authoritative document. This is
|
|
what the narrator is told and what the reader is shown.
|
|
- `actions.narrative_state_after` — the whole document as it stood after that
|
|
position played, on **every** node.
|
|
- `state_events` / `state_proposals` — the audit: what was proposed, what was
|
|
accepted, what was refused and why.
|
|
|
|
### What each is for
|
|
|
|
**Event history is audit and provenance, not a source of truth.** Restoring a
|
|
position never replays it. This was proven by renaming the table away
|
|
mid-campaign: Undo and Redo continued to work.
|
|
|
|
**Snapshots are what make restore bounded.** Arriving at a position is a single
|
|
row read whose cost does not grow with the length of the story (ADR 012 §10.4).
|
|
A position without a snapshot is a position the head cannot be restored to, so
|
|
every node has one — including nodes written before M5, which migration 88
|
|
backfills with the empty document. A missing snapshot restores the empty
|
|
document rather than leaving the previous position's state standing.
|
|
|
|
**The document is the authority; the model only proposes.** Every event, whether
|
|
it came from the model or from a reader's correction, passes the same
|
|
validation. The model cannot write a field directly, cannot invent an event
|
|
type, and cannot refer to an entity that does not exist in this campaign.
|
|
|
|
## The invariant
|
|
|
|
```text
|
|
visible active transcript position == stored head == authoritative state
|
|
```
|
|
|
|
Everything above exists to hold this. The M5 review found two ways it had been
|
|
broken — a narrator edit that rewound live state while the head stayed at the
|
|
tip, and a restore to a migrated position that left a later position's state
|
|
standing — and both were fixed by making the rule absolute rather than by adding
|
|
a special case.
|
|
|
|
## Consequences
|
|
|
|
- The document is larger than the RPG dict it replaced, so both it and the
|
|
per-node snapshots are stored compressed.
|
|
- Genre lives in the campaign's canon and in the words the story uses, never in
|
|
the schema. A science-fiction campaign and a fantasy one produce the same
|
|
shapes.
|
|
- A reader's correction is durable and outranks narration, and the prompt states
|
|
both what holds and what has been withdrawn.
|
|
- Export carries the document, the canon and every snapshot. It does **not**
|
|
yet carry `state_events` / `state_proposals`, so an imported campaign keeps a
|
|
correction's effect but not its audit trail. Deferred to M9.
|