DUNIN7 · LOOMWORKS · RECORD
record.dunin7.com
Status Current
Path change-requests/cr-2026-213-loomworks-turn-kinds-single-source-v0_1.md

DUNIN7-M4 — INFRASTRUCTURE CHANGE REQUEST

CR-2026-213 — The turn-kind vocabulary becomes single-source, the reader degrades instead of failing, and E0128's history comes back (B-101) (v0.1)

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.


0. Settled — the Operator's rulings. Do not re-raise.

  1. The single-source kinds enumeration is the POINT of the CR, not a hardening extra. One tag is the symptom; a writer that can mint vocabulary the reader never learned is the defect. The write side stores 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.
  2. The degrade is not optional either. Even with one shared enumeration, a turn written by an older deploy and read by a newer one is the same shape — version skew reopens the gap the enumeration closes at rest. Per-turn validation with the turn's text still served is what makes that survivable rather than fatal. One poison turn must never again cost a page.
  3. The invisibility mechanism is recorded where the next person will meet it (in the union's code comment and in this CR): 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.

1. The changes

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.

2. Consequences named, so they are chosen

3. Tests — each observed failing first

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.

4. seed-mutability impact

Kind C expected: read-path schema and vocabulary plumbing; no seed contact. Checked at Step 1; a null finding is an entry.

5. Build steps

| 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 |

6. Acceptance gate

  1. E0128's conversation history renders in production, read by the Operator — the committed engagement's record, readable.
  2. The enumeration is the single source: writer imports it; the identity fence test stands; §0.1's halt clause honored if it fought back.
  3. The degrade: one bad turn costs its marker, never a page (tested both ways — the E0128 shape and the synthetic unknown tag).
  4. Suites green both repos; all local gates including lint; runs watched with verdicts read directly; redeployed; seed-mutability recorded.

7. Kickoff prompt


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.