Version. v0.4
Date. 2026-08-01
Supersedes. v0.3 and v0.2, both drafted and never filed — each halted at pre-flight. v0.1 at record a276e5b stands as a sibling, unaltered. The record holds v0.1 and v0.4; the gaps are deliberate.
Changes from v0.3. One count dropped, in two places. v0.3 said a correction had reached the body and not the kickoff "for the fourth time"; two instances can be substantiated, not four — CR-2026-161 v0.2's residual imperative and CR-2026-162 v0.1's TWO LITERALS. The change request C brief is not a third: its kickoff would have drifted had only the named line been corrected, but the whole class was swept, and a prevented near-miss is not an occurrence. The number is removed rather than corrected to two — it was ornament in the sentence introducing the mechanism against ornament, and the argument stands without it.
Changes from v0.2, carried. Two stale counts, and a structural fix so the class stops recurring. §4.1's own correction block still said "two literals" directly beneath the sentence establishing four. And the kickoff said "TWO LITERALS in schemas.py" — contradicting §4.1, §6 and the gate, and naming one module when two of the four candidates are in others. A session following it would have changed two, looked in one place, and never found the rest. A correction has now reached the body and not the kickoff more than once, so the kickoff no longer restates any fact the body carries — it points, and a block that points cannot drift.
Changes from v0.1, carried. Two, both found by the executing session at v0.1's filing, and both would have changed what a session does. The source_mode literal is declared in four places, not two — v0.1 named two and asked for a sweep for a third. The two extra are upload-side, and whether they need the member is now Step 0's third determination rather than an absorption or a halt. And the Operator Layer baseline is corrected from 610 to 630 — 610 was the pre-B-5 figure, and CR-2026-160 added twenty tests, so a Step 0 comparison against the stated number would have read as a divergence and halted for nothing.
Author. Claude.ai (drafting session). Approving: Marvin Percival.
Charter. standing-notes/dunin7-standing-authorization-charter-v0_1. No session executes a change request it drafted (§1).
Target. /Users/dunin7/loomworks at main 7e81e30, and /Users/dunin7/loomworks-engine at main a6a3ed6 — two repositories, and §2 explains why.
Baseline. Neither repository is green. Operator Layer: 630 passed, 0 failed, exit code 1, twelve unhandled fetchPlatformLevel rejections. Engine: 1 failed (test_stele_router_mount), the rest passing. The criterion is no new failure and no new unhandled rejection, against sets recorded at Step 0. Exit 0 is not reachable and is not the criterion.
Build-list items. B-28, B-7.
CR number. CR-2026-162. Highest confirmed is CR-2026-161. [EXECUTING SESSION: verify; advance if taken.]
Grounding. inspection-briefs/loomworks-cr-c-step-0-findings-v0_1 — every anchor below comes from it.
Status. Pre-execution.
What changed from the plan. Change request C was scoped as B-6, B-7 and B-28 together. B-6 is split out — see D-1. This CR is the small, surface-facing half, and it ships a user-visible fix and a user-visible capability without waiting for the larger room.
B-28 — contributed Markdown is refused and the work is lost. A type declaration disagrees with what the system actually emits — in four places, two of which need a determination before they are touched. (v0.1 said "two lines". Wrong by at least two; see D-2.)
B-7, first half — there is no way to download a finished render. The engine has served download routes all along. The surface has never called them. A wire, not a build.
B-7, second half — when an older and a newer render sit side by side, nothing says which is current. The surface already receives three fields that bear on it and displays none.
It does not change the engine. The engine is read-only here, for one purpose: confirming the download routes' contracts before the surface calls them. /renders/{id}/download, /content and /files/ are established as existing by the Step 0 read; their exact parameters, response shapes and format-conversion behaviour must be read before an adapter is written against them.
No engine file is edited. No migration. No engine commit. If a route turns out to need changing, halt — that is an engine change request, not this one.
D-1 — B-6, the Shaping room, is split into its own change request.
The Step 0 read establishes that Shaping's engine surface is not what Manifestation's was: it carries a confirm/retire lifecycle, executors, and asynchronous jobs. B-5 needed no engine work, and inheriting that as though the rooms were the same size is the error the findings warn against.
B-6 gets its own scoping and its own Step 0, covering the lifecycle, what the surface must show while a job is running, and whether the shape-type-to-render-type mapping is enforced or merely exposed — the second of the findings' two unread items.
Nothing in this CR is blocked by that split. B-28 and B-7 touch neither Shaping's room nor its adapter.
D-2 — B-28 is fixed at both declarations, and the routing question is opened separately. (Queue entry Q-11, taken.)
text/markdown is registered to the discovery-to-seed skill, which emits source_mode="discovery". The assertion schema declares Literal["text", "voice", "pdf", "image"] at schemas.py:2524 and again at :2806. discovery is emitted and not allowed; text is allowed and emitted by no skill.
The literal is declared in four places, not two.
| Declaration | Model | Status |
|---|---|---|
| api/schemas.py:2524 | AddAssertionRequest | Fix. On the contribution path. |
| api/schemas.py:2806 | AssertionResponse | Fix. On the contribution path. |
| orchestration/schemas.py:1208 | UploadResultItem | Determination first. |
| api/routers/uploads.py:228 | PerFileUploadResponse | Determination first. |
The two on the contribution path are fixed unconditionally. A fix reaching one and not the other leaves the second to fail identically — that is the shape of the defect.
The two upload-side declarations are not touched until Step 0 answers one question: does a discovery extraction ever reach those responses? If it does, they need the same member and fail the same way. If it does not, adding it declares a value that path cannot produce — the mirror of the text member that is allowed and emitted by nobody, which is half of what made this defect possible.
> Corrections preserved. v0.1 named two declarations and asked the executing session to sweep for "any third." There are four. The undercount originated in the Step 0 findings, which read schemas.py only and concluded two lines, and this CR inherited it. The second time an undercount has crossed from a findings document into a change request — the first was a line-distance in CR-2026-161. The gate would have caught it; the headline would still have been wrong.
Reasoning, and the question deliberately not answered. The findings note that contributed Markdown currently routes through seed extraction, while that registration's own comment says the canonical path is elsewhere. Whether the routing is right is a real question and it is not this one. Making an allowed set match what the system actually emits is not a design commitment — it makes a declaration honest about current behaviour, and if the routing moves later the literal moves with it. The routing question becomes its own build-list item. Deciding it here is the scope absorption C-3 forbids.
What this does not require. Nothing is lost and nothing needs recovering. The assertion is written at contributions.py:268, the response is built after it, and get_db_session rolls back on any exception — so the durable state is as if nothing happened, and the uploaded file row survives, meaning the same file_id re-contributes once the mismatch is fixed. No migration, no backfill, no data repair. The loss is of the person's work, not of their bytes.
D-3 — the currency half is built surface-first, with no engine change. (Queue entry Q-12, taken.)
Renders carry no supersession marker, where Manifestations do. That is a real asymmetry and it is not necessarily this CR's problem. The surface already receives state, created_at and display_number on every render and displays none of them.
Display what is already sent. Then judge whether a marker is still missing.
Reasoning. Building an engine field before establishing that the surface needs it is the wrong order, and the Manifestation precedent is the direct evidence: its staleness marker looked like a build and turned out to be a wire. If, after this CR, the Operator still cannot tell which render is current, the engine marker becomes its own item — informed by what the display revealed rather than by a guess made before it existed.
The honest counter, recorded. Three fields the Operator can interpret is not the same as one field that states the answer. A derived judgement can be wrong in ways a marker cannot. If Step 0 finds that state and display_number cannot distinguish current from superseded — for instance if two renders can hold the same state with no ordering between them — halt and report, because then the surface genuinely cannot say it, and saying it anyway is face 2.
D-4 — the Download control appears only where a download is possible.
RenderingRoom carries a comment recording that no Download button was built because there was no route to call. There is one. The seed's only-show-what-is-available constraint forbids a disabled or greyed control — so the button appears when the render is downloadable and is absent otherwise, never present-and-inert.
And it obeys the room state contract. A download control rendered from a failed read would imply the render exists when no read established that. It appears on populated only.
4.1 B-28, engine repository. [This is the one exception to §2's read-only engine.] Add discovery to the source_mode literal at api/schemas.py:2524 and :2806 unconditionally, and at orchestration/schemas.py:1208 and api/routers/uploads.py:228 only if Step 0's third determination says those responses can carry a discovery extraction. [EXECUTING SESSION: re-confirm all four anchors and sweep for a fifth. Four were found by sweep after v0.1 named two; a fifth is less likely but the sweep is cheap and the count has already been wrong once.]
> Correction to §2, made explicit. §2 says no engine file is edited. That is wrong as written and this is the exception — B-28's fix is in the engine's schema module. What §2 protects is that no engine behaviour, route or migration changes. The source_mode literal gains a member the system already emits — two declarations unconditionally, two more per Step 0's third determination. A separate engine commit, separate from the surface work.
4.2 The render adapter — a new file in the Operator Layer's lib/api/. The wall does not constrain its name: the Step 0 read establishes that rendering is not a forbidden term and the room may be named for itself throughout. Add it to WIRE_BOUNDARY_FILES per the existing convention.
4.3 RenderingRoom.tsx — the Download control per D-4, and the currency display per D-3.
4.4 The strings for both, asserting the surface where no read stands behind them, per the governing rule.
lib/api/shape.ts and never shaping.ts — the wall reads import specifiers, so a room-named adapter trips every consumer. (Recorded here because it is the trap CR-2026-160 hit, and it is now pre-answered.)unread. It needs a perimeter call the fences forbid, and the defect's mechanism does not depend on it.fetchPlatformLevel rejections — build-list item B-34.Per-step commits. Two repositories, so two commit series — never one commit spanning both. Check the current branch before the first commit in each.
Step 0 — pre-flight and three determinations. Verify both HEADs, both tree states, the CR number. Record both baseline sets — the engine's failure set, and the Operator Layer's failure set and unhandled-rejection set and exit code.
Then settle, by reading:
state, created_at and display_number distinguish a current render from a superseded one? Per D-3's counter. If they cannot, halt and report rather than displaying a judgement the data does not support.UploadResultItem or PerFileUploadResponse? Per D-2. Decides whether two or four declarations change. Trace the path; do not infer from the model names.
Report all three before proceeding. Commit: CR-2026-162 step 0: pre-flight and three determinations
Step 1 — B-28, engine repository, two or four literals per Step 0. Suite against the engine baseline.
Commit (engine): CR-2026-162 step 1: allow the discovery source mode
Step 2 — the render adapter, plus its WIRE_BOUNDARY_FILES entry.
Commit (OL): CR-2026-162 step 2: render download adapter
Step 3 — the Download control, per D-4.
Commit (OL): CR-2026-162 step 3: download control on populated renders
Step 4 — the currency display, per D-3 and Step 0's determination.
Commit (OL): CR-2026-162 step 4: render currency display
Step 5 — tests. A test that a discovery contribution is accepted end to end; a test that the Download control is absent on failed and empty and present on populated; a test of the currency display. Assert specific conditions, never a bare Exception.
Commits: per repository.
CHECKPOINT A — report, then proceed. Both baseline comparisons; the tests; a live pass on the dev server showing a Markdown contribution accepted, a render downloaded, and two renders side by side with the current one distinguishable. No Operator confirmation. Halt and queue on: any new failure or rejection; either determination coming back ambiguous; any charter §6 anomaly.
Step 6 — implementation notes, in both repositories, recording all three determinations.
CHECKPOINT B — merge and tag, separately per repository. --no-ff in each; tag the Operator Layer render-flow-v0_1 and the engine discovery-source-mode-v0_1. Push both. Authorized under R-2 including the pushes. Deployment is never autonomous (F-1).
source_mode declarations accept discovery. The two upload-side declarations are changed if and only if Step 0's third determination says they carry discovery extractions, and the determination is recorded either way. A sweep finds no fifth.source_mode literals.populated only, and never as a disabled or inert control.WIRE_BOUNDARY_FILES; the vocabulary wall passes with no other new exemption.This block names the entry point and the fences. It restates nothing the body carries — not the steps, not the counts, not the file paths. (Structural change at v0.3. v0.1 and v0.2 restated facts here, and both drifted from the body when those facts changed — the kickoff said two literals in one module while §4.1 said two-or-four across three. This has happened before, and the executing session reads the kickoff. A block that points cannot drift from what it points at.)
CR-2026-162 — B-28 and B-7. Execution session.
CR: loomworks-record/change-requests/cr-2026-162-render-flow-v0_4.md
Confirm it exists, and that it is the highest version present — numeric sort.
Grounding, read before the CR:
inspection-briefs/loomworks-cr-c-step-0-findings-v0_1.md
Charter dunin7-standing-authorization-charter-v0_1 governs.
Read the CR in full before starting. It is the authority on WHAT changes and
WHERE — this block does not repeat any of it. Where this block and the CR
appear to differ, the CR is right and you halt rather than choosing.
Section 3 — the construction decisions and their reasoning
Section 4 — what changes, and the anchors
Section 5 — what is out of scope
Section 6 — the step sequence, both checkpoints, and their conditions
Section 7 — the acceptance gate
TWO REPOSITORIES — the CR's header names both targets and both baselines.
Separate branches, separate commit series, separate merges and tags. Never one
commit spanning both. Commit to a branch in each; check the current branch
before the first commit in each.
The engine is otherwise read-only: no route, no migration, no behaviour change.
If anything in the engine beyond what Section 4.1 names needs to change, halt —
that is a different change request.
playground_dev is the live production database and is not touched.
NEITHER BASELINE IS GREEN in either repository. Record both baseline sets at
Step 0 and compare against them. Exit 0 is not reachable and is not the
criterion.
Step 0 settles the determinations Section 6 lists, and reports every one of
them before any code changes. Some may legitimately come back as "no" or
"cannot" — Section 6 says what each halts on. Do not resolve an ambiguous
determination by choosing.
Deployment is never yours.
Append the outcome to current-status/dunin7-status-brief at close.
DUNIN7 — Done In Seven LLC — Miami, Florida CR-2026-162 — B-28 and B-7: the lost contribution and the Rendering flow — v0.4 — 2026-08-01 Four declarations where two were named, one wire, and three fields already arriving unused.