Version. 0.1 · Date. 2026-08-14 · Author. Claude Code (drafting, from [B-101] + scoping-notes/loomworks-b101-b99-scoping-note-v0_1) / Marvin Percival (approving).
Target. /Users/dunin7/loomworks-engine (the enumeration, the union member, the per-turn degrade); /Users/dunin7/loomworks (the mirror member or tolerate-unknown — Step 1 decides which against code). Baselines. Engine 3c925ba, surface 322a216. CC confirms at Step 0.
Companion to. [B-101] (build list v0.82), the scoping note, [B-98]'s completion (the walk that surfaced it).
Ordering. Ahead of [B-99]/CR-2026-214, per the Operator.
Charter. Autonomous regime. Status. DRAFT — awaiting the gate. Do not execute.
dict[str, Any] and only the reader validates — that asymmetry is what this CR removes: one enumeration of turn-kinds, imported by the writer and the reader alike, so an unregistered tag becomes unwritable rather than unreadable. If the build shows the enumeration is harder than adding the member and the degrade, HALT and say so rather than shipping the two halves and filing the third.save_offer is deliberately absent from ConverseResponse — an internal signal — so live rendering worked and only reload broke. Nobody who didn't reload an offer-bearing conversation could see the defect, which is why it sat until the Operator's walk. The testing lesson, stated as one: live correctness and reload correctness are different claims — a turn-persistence change must be verified through the history read, not only through the live response.
A. The enumeration (engine — the point). One module-level source of truth for turn structured_data kinds (home: beside the union in orchestration/schemas.py, or a small turn_kinds.py both sides import — Step 1 decides placement against the import graph). The write site (converse.py's composer block, the voice-attach path) constructs payloads whose kind comes from the enumeration; the union's members derive from or are checked against it (a test asserts the two sets are identical — the fence that catches tag N+1 at build time, not read time). save_offer joins as a first-class member with its payload model (assertion_display_number).
B. The degrade (engine — not optional). The history route validates each turn's structured_data individually: a payload that fails union validation projects as structured_data: null — the turn's text, role, timestamps, provenance marker all still served — with a warning logged naming the offending kind (so skew is observable, not silent). The route never 422s a page for one turn again.
C. The surface mirror (surface — Step 1 decides the smaller honest shape): either the mirror union gains save_offer (if the surface should render the offer marker) or the surface's structured_data handling tolerates unknown kinds as absent. The scoping leans tolerate-unknown (the engine already degrades; the surface consuming a tag it has no rendering for serves nobody) — decided at Step 1 against what the surface would actually render.
D. E0128's history comes back with NO data change — the turn was always valid; only the reader's vocabulary was behind. The post-deploy verification is the Operator reading their own committed engagement's history.
Engine: the enumeration-vs-union identity fence; a save_offer turn round-trips through the history read (the E0128 shape, observed failing today); a page containing an unknown-tag turn returns the readable turns with the bad turn's text served and marker nulled (the degrade, observed failing today as a full-page 422); the warning log carries the offending kind. Surface: unknown-kind tolerance (or the mirror member's rendering), per §1.C's decision. Existing history/converse suites re-run.
Kind C expected: read-path schema and vocabulary plumbing; no seed contact. Checked at Step 1; a null finding is an entry.
| Step | What | Mode | |---|---|---| | 0 | Pre-flight — baselines, clean trees. | Auto | | 1 | Verify the write sites and import graph AGAINST CODE (per the arc: probe the history read live on E0128 pre-change — the 422 observed, again, as the before-state); decide enumeration placement + §1.C's surface shape; tests written, observed failing. | Auto | | 2 | The enumeration + the member + the degrade (engine). | Auto | | 3 | The surface shape per §1.C. | Auto | | 4 | Full suites both repos; ruff; mypy; lint; tsc (the CR-212 lesson: the local gate list includes lint). | Auto | | 5 | Eye-test: E0128's history READS on the dev surface (the before-state 422 → the after-state turns, same engagement, same data); the save_offer turn's text present; a synthetic unknown-tag turn degrades to text-served (fixture, cleaned up). | Auto | | A | Checkpoint. | Checkpoint | | 6 | Tags; push; watch both runs (verdicts read DIRECTLY, never via the watch wrapper — the CR-212 lesson); redeploy; post-deploy: the Operator reads E0128's history. | Auto |
Execute CR-2026-213 v0.1 at change-requests/cr-2026-213-loomworks-
turn-kinds-single-source-v0_1.md in loomworks-record. BOTH repos
(engine 3c925ba, surface 322a216). Confirm baselines first.
§0: the enumeration is the POINT (halt if it's harder than the
member+degrade — never ship two halves and file the third); the
degrade is not optional (deploy skew is the same shape); live
correctness and reload correctness are different claims — verify
through the history read. Probe E0128's 422 first as the before-
state.
DUNIN7 — Done In Seven LLC — Miami, Florida CR-2026-213 — turn kinds single-source — v0.1 — 2026-08-14 The writer stops minting what the reader never learned; one turn never again costs a page; and the Operator reads their own record.