# Adventure Storyteller v1.1 — Integrated Release Validation **Status:** COMPLETE — **V1.1 RELEASE VALIDATION: PASS**. The decision, and what it deliberately does not cover, is in §W. This report answers one question: **does this exact candidate preserve the complete v1 contract and satisfy every accepted v1.1 package on one integrated release tree?** It is release validation, not a work package. Nothing here adds a feature, and no release tag is created by it. --- ## A. Repository / provenance | | | | --- | --- | | **Candidate SHA** | **`87a40326a29533c8d52c9f9f41022e7b499b1de7`** | | Branch | `v1.1-development`, up to date with `origin/v1.1-development` | | Working tree at freeze | **clean** — nothing modified, nothing staged | | Commit | *v1.1: harden recovery and control boundaries* (WP-D + WP-E) | | **Owner signature** | **Good signature**, RSA key `02C9BF7D8A4A77DF7A8905617D8AE19DB5C68569`, made 2026-09-16 05:37:13 EDT | | Tag at HEAD | **none** — no `v1.1.0` tag exists | | v1.0.0 baseline | `432f04100b9a67198bcdc46c6ff8ee0f181e1667`, **an ancestor** | | Package ancestry | `d63804f` (WP-A1/A2), `beb17ad` (WP-B.1), `0c1ba83` (WP-B.2), `59b5ebc` (WP-C) — **all ancestors** | | Diff v1.0.0..HEAD | 61 files, +14,833 / −366 | | LICENSE / PROVENANCE | **unchanged since v1.0.0** (empty diff) | ### A.1 Frozen candidate identity | | | | --- | --- | | Dependency locks | `backend/requirements.txt` `sha256:ed28bc0f8970cf4e…`, `frontend/package-lock.json` `sha256:355cb370837ade01…`, `frontend/package.json` `sha256:2016580ddfa176a9…`, `backend/requirements-dev.txt` `sha256:06d7695816b201e9…` | | Schema | `LATEST_VERSION` **94**, 93 migrations (`PRAGMA user_version`) | | Bundle format | **`ai-dnd-adventure-v3`** | | Import ceiling | 20 MB (`MAX_IMPORT_BODY_BYTES`), unchanged | | Frontend build | `dist` built 2026-09-16T05:39:55, 16 files, `sha256(dist) = ea2753ad24f61959fe084f4674911acc` | | Docker image | `storyteller:release-87a4032`, `sha256:fe10e8a2511395958882e489bfdf53fa00d981597cc81aa5587235f7b7836398`, 312 MB | | Firefox / geckodriver | 155.0.1 / 0.37.1 (2026-09-04) | | Docker | 29.8.0, build 88096ef | | CPU HTTPS reference host | Ollama **0.33.0**, serving `qwen2.5:3b-instruct`, `qwen2.5:3b-instruct-16k`, `nomic-embed-text:latest`; certificate verifies through the machine's CA store with no bypass | | GPU inference host | Ollama **0.34.0**; `qwen2.5:3b-instruct-16k` digest `21ff8cc52f375f19`, `nomic-embed-text:latest` digest `0a109f422b47e3a3`; no model resident at start | | Evidence root | `$HOME/v11-evidence/release-87a4032/` — never `/tmp`, and no real hostname appears in any committed file | --- ## B. Package acceptance inventory | Package | Status | Source | | --- | --- | --- | | **WP-A1** context-window safety reserve | **ACCEPTED** | `V1.1-WP-A1-A2-REPORT.md`, signed `d63804f` | | **WP-A2** protocol-echo cleanup, genre-neutral prompting | **ACCEPTED** | same report and commit | | **WP-B** independent memory | **ACCEPTED WITH DOCUMENTED REAL-MODEL LIMITATION** | `V1.1-WP-B1-REPORT.md`, `V1.1-WP-B2-REPORT.md` §S, signed `beb17ad` / `0c1ba83` | | **WP-C** browser release coverage | **ACCEPTED** | `V1.1-WP-C-REPORT.md`, signed `59b5ebc` | | **WP-D** recovery honesty | **ACCEPTED** | `V1.1-WP-D-REPORT.md`, signed `87a4032` | | **WP-E** control-boundary contrast | **ACCEPTED** | `V1.1-WP-E-REPORT.md`, signed `87a4032` | ### B.1 WP-B's qualification, carried whole The WP-B disposition is **not** shortened to "WP-B passed" anywhere in this report. Its own §S records: ```text B2.1 RANKING: PASS B2.2 EVICTION: PASS B2.3 EXCERPT CREATION: PASS B2.4 SUMMARIZER-PROMPT EXPERIMENT: FAIL — REVERTED DETERMINISTIC WP-B: PASS REAL-MODEL WP-B: FAIL WP-B OVERALL: ACCEPTED WITH DOCUMENTED REAL-MODEL LIMITATION ``` In the release contract's own words (§11 items 12), that is: ```text DETERMINISTIC WP-B: PASS REFERENCE-MODEL INDEPENDENT MEMORY: FAIL OWNER ACCEPTED THE LIMITATION FOR v1.1 ``` The failing stage is **memory creation** — the summariser's content selection — not ranking, eviction or injection, each of which passes deterministically. ### B.2 WP-E screenshot approval — a correction of record The committed WP-E report read `OWNER SCREENSHOT APPROVAL: PENDING`, because it was written before the owner reviewed the images. The owner's release-validation brief (2026-09-16) states the before/after screenshots are approved and instructs this validation to record it. The WP-E report is updated to `APPROVED` as part of this closeout (§V), sourced to that brief and dated. No visual code changed during release validation, so the approval stands (§Q). --- ## C. v1 acceptance matrix Every test marked **REQUIRED FOR V1** — there are **82** — against evidence taken on **this candidate**. Evidence types follow M11's: `browser` (the 101-check run, §G), `campaign` (the 102-turn integrated run, §H), `container` (the offline run on the candidate image, §F), `process` (spawned server processes — recovery §M, upgrade §N), `suite` (the 1,723-test backend suite, §D). No historical result from different product code is used where the contract asks for candidate evidence. **Result: 81 PASS, H09 NOT APPLICABLE, 0 waived, 0 weakened, 0 reclassified.** ### A — Local-first operation | ID | Result | Evidence on this candidate | | --- | --- | --- | | A01 Start application offline | **PASS** | container: first page load, fresh volume, no route and no DNS | | A02 Storyteller loopback default | **PASS** | suite; every harness reached it on `127.0.0.1`; `docker-compose.yml` publishes `127.0.0.1:8000:8000` | | A03 No cloud API key | **PASS** | suite; container: no secret in an export | | A04 Campaign survives restart | **PASS** | campaign: **3 process restarts, 4 process starts**, state compared across each; container: campaigns survive a container restart | | A05 Failed model call does not corrupt story | **PASS** | campaign: a real `failed_call` at turn 69 against an unserved model, play resumed; container: same with no model reachable | | A06 Trusted-LAN Ollama inference | **PASS** | browser: the whole 101-check run over **trusted-LAN HTTPS with a private CA**, verification on, no bypass, storyteller loopback-bound | ### B — Core play | ID | Result | Evidence | | --- | --- | --- | | B01 Natural language action | **PASS** | browser (real turns through the UI) + campaign (102 accepted) | | B02 Dialogue input | **PASS** | campaign: dialogue beats in the turn list | | B03 Continue | **PASS** | suite; browser: the Continue control present and enabled | ### C — Story authority and state | ID | Result | Evidence | | --- | --- | --- | | C01 Campaign canon is preserved | **PASS** | campaign: canon present in the prompt on **102 of 102** turns | | C02 Possession state | **PASS** | campaign (the silver key) + suite | | C03 Character knowledge is not invented | **PASS** | suite | | C04 Manual state correction | **PASS** | campaign: **2 state corrections**; browser: C3's accepted and refused corrections; suite | | C05 Canon beats reference | **PASS** | suite | | C06 Structured state matches accepted narrative consequence | **PASS** | campaign: real extraction across 102 turns, every event validated or refused; suite | ### D — Non-destructive history | ID | Result | Evidence | | --- | --- | --- | | D01 Undo one turn | **PASS** | browser + campaign (`undo`) | | D02 Minimum five undos | **PASS** | suite; campaign (`undo_redo`) | | D04 Redo | **PASS** | browser + campaign | | D05 Redo invalidated by new continuation | **PASS** | campaign: `diverged`, after which Redo is gone | | D06 Retry narrator response | **PASS** | campaign: **2 retries** | | D07 Select prior retry take | **PASS** | campaign: `take_selected` | | D08 Retry does not delete prior take | **PASS** | campaign + suite | | D09 Edit earlier user input | **PASS** | suite | | D10 Edit narrator output | **PASS** | suite; browser (hostile-Markdown plants through the narrator-edit path) | | D11 Named checkpoint | **PASS** | campaign: **2 Save Points**; browser; suite | | D12 Restore checkpoint | **PASS** | campaign: `save_point_restored`; browser | | D13 Restore does not delete later history | **PASS** | campaign: retained actions after the restore; recovery §M | | D14 Delete checkpoint | **PASS** | suite; browser: the delete confirmation dialog | ### E — Branch and derived-data isolation | ID | Result | Evidence | | --- | --- | --- | | E01 Abandoned future cannot affect active state | **PASS** | suite `test_m11_leakage.py`, with a positive control | | E02 Abandoned memory cannot leak | **PASS** | as above | | E03 Abandoned summary cannot leak | **PASS** | as above | | E04 Scene state is lineage-safe | **PASS** | as above | ### F — Long-term memory and context | ID | Result | Evidence | | --- | --- | --- | | F01 Recent turns remain coherent | **PASS** | campaign: history populated every turn, newest always included | | F02 Old important event retrieval | **PASS** | campaign §K: the planting turn outside the window and the fact recovered — **through authoritative state**, not independent memory (§K states which) | | F03 Prompt remains bounded | **PASS** | campaign: 1,602–14,982 tokens against a 16,384 budget across 102 turns | | F04 Output token reserve | **PASS** | campaign: `output_reserve` 500 present and subtracted on every turn | | F05 Prompt inspector | **PASS** | browser: the context panel shows the assembled prompt | | F06 Retrieval provenance | **PASS** | campaign: knowledge and memory provenance per turn; suite | | F07 Heuristic memory is not canon | **PASS** | suite | | F08 Memory failure is non-fatal | **PASS** | suite; container: derived work fails with no model and turns still commit; campaign: **0 post-turn failures**, no database-lock errors | ### G — Imported knowledge | ID | Result | Evidence | | --- | --- | --- | | G01 Import local text | **PASS** | browser: through the real file input; container: offline | | G02 Import local Markdown | **PASS** | campaign: **3 sources imported**; browser | | G03 Classification | **PASS** | campaign: all three classes; recovery §M confirms them after a move | | G04 Disable knowledge source | **PASS** | suite | | G05 Canon retrieval | **PASS** | campaign: canon passages in stored prompts; suite | | G06 Reference retrieval | **PASS** | suite | | G07 Inspiration is low authority | **PASS** | suite | | G08 No automatic URL fetch | **PASS** | suite `test_egress.py`; container: no network at all, import still works | | G09 Remote Markdown image does not auto-load | **PASS** | browser: no remote image src in the rendered story | | G10 Prompt injection in source is treated as data | **PASS** | suite; browser: injection text rendered as text | ### H — Security | ID | Result | Evidence | | --- | --- | --- | | H01 No unexpected outbound connections | **PASS** | container (no network at all) + suite `test_egress.py` | | H02 No telemetry | **PASS** | suite | | H03 No cloud provider required | **PASS** | container: a full campaign offline | | H04 Model output cannot execute shell | **PASS** | browser + suite | | H05 Invalid state event rejected | **PASS** | suite; campaign: refusals recorded | | H06 Stored XSS protection | **PASS** | browser: `onerror` and `