Version. v0.2
Date. 2026-08-01
Supersedes. v0.1 at record a1e95f7, which stands as a sibling, unaltered.
Status of the build. RESUME. Steps 0 and 1 are landed on branch cr-2026-161-authorship-truth. Engine main untouched at e106917.
Changes from v0.1. Three determinations came back and two removed work. Site 4 is struck — the engine's request schema declares the behaviour v0.1 called a defect, so it fails B-25's own distinguishing test. Site 10's kind-collapse is struck — and the read that struck it found the collapse would have removed an authority boundary. The legacy-path sentinel gains a prior question and a specified kind. One inherited error corrected in §3.
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-engine at main e106917, tag provenance-seam-v0_1. No work in any other repository.
Baseline. Not green. One long-standing unrelated failure, test_stele_router_mount.py::test_stele_router_mounts_and_begin_resolves_end_to_end. Baseline totals at e106917: 1 failed, 3444 passed, 68 skipped. The criterion is no new failures, against a set recorded at Step 0. Alembic head 0102.
Build-list item. B-25.
CR number. CR-2026-161. Highest confirmed is CR-2026-160. [EXECUTING SESSION: verify against change-requests/ and docs/phase-crs/; advance if taken.]
Grounding. inspection-briefs/loomworks-b25-step-0-findings-v0_1 — every anchor below comes from it, read at e106917. Superseded by it, in part: inspection-briefs/loomworks-b25-sizing-sweeps-findings-v0_1 and inspection-briefs/loomworks-inspection-corrections-note-v0_1 C-6.
Status. Pre-execution.
Eleven places where the engine writes into the permanent record something plausible and wrong. Nine are invisible from every surface. This is the last blocker on B-9, the provenance walk — a walk over false authorship demonstrates a lie convincingly.
B-25 is materially smaller than it was sized, and the Step 0 read is why. It was scoped as eleven heterogeneous fixes. It is not:
| Group | Sites | Shape | |---|---|---| | A | 5–9 | Five calls to a helper that already exists. The largest block and the cheapest. | | B | 1 | One line; the principal is in handler scope. | | C | 2–3 | The only hard part. A dependency resolves the principal and discards it, and its legacy branch resolves none at all. | | D | 4 | A falsy-coalesce on an agent identifier, self-contained. | | E | 10 | A consistency choice, not a repair — and its premise is checked before it is made. | | F | 11 | A different defect class: engagement scope, not attribution. |
One item leaves the list. seed_conversation.py:116 is not a defect — see D-1.
D-1 — seed_conversation.py:116 is struck from B-25. (Queue entry Q-6, taken.)
The identity is a module-level constant, _COMPANION_ACTOR_ID, fixed across the substrate's lifetime with one call site. memory/base.py:81-84 requires kind companion to carry the person's own UUID — "the Companion's identity is bound to the person — there is no separate companions table" — and stele/actor.py:44-46 enforces the same in its constructor. A substrate-wide sentinel has no person to bind to. kind="agent" is correct, and writing companion would produce a genuinely false record: an actor claiming to be a specific person's Companion under an id that is no person.
This is the item the defect list got wrong. It is struck on the substrate's own definition, not on preference.
D-2 — the escalation recipient is validated. (Queue entry Q-5, taken.)
considerations.py:534 passes escalate_recipient=_actor_from_schema(body.escalate_recipient) — kind, identifier and version straight from the request body. In the same call, closing_party is resolved from the authenticated caller. Elsewhere specialist_ref is registry-validated at composition_creation.py:315. This field is the lone outlier among the body-supplied actor fields.
Validate that the actor exists and that the kind matches the identifier, consistent with its two siblings.
Reasoning, and the honest counter. The field is inert today — flattened to escalate_recipient_id and escalate_recipient_kind, and a sweep of src/ finds no join, lookup or filter reading either. So nothing is broken now, and leaving it would break nothing tomorrow. The argument for acting is that the cost is near-zero now and rises the moment something reads it — and rises again once FORAY anchoring cements whatever the columns hold. Reversal cost: one line, before build.
D-3 — RESOLVED by Step 0. Site 10's two kinds are both correct and are not touched. (Queue entry Q-7, closed.)
v0.1 asked the executing session to establish what the spoken door records before choosing. It records Companion-authored content, so the spoken door's kind="companion" and the typed door's kind="person" are recording different acts, and the difference is provenance rather than inconsistency.
> The near-miss is the finding, and it is worth stating plainly. Two consumers use != "companion" as their test for whether an actor is human, and NON_HUMAN_ACTOR_KINDS = {"agent", "companion"} is what the commit gates reject. Collapsing the two doors to person — the sizing's recommendation, carried unexamined from the walk audit — would have made the Companion's own draft committable as a human act. It would have removed an authority boundary while presenting as a consistency tidy-up. The sizing's "one of the two, consistently" was never tested against a consumer; this is what testing it found.
What remains from site 10 is the compounding site alone, api/routers/assertions.py:405, which hardcodes the companion name rather than reading the per-person preference. That is a display defect and is real.
This also closes the Step 0 findings' one unread — whether any consumer branches on actor.kind for these records. Two do, and they gate commit authority.
D-4 — assert_engagement_creator returns Principal | None.
Sites 2 and 3 declare _: None = Depends(assert_engagement_creator). The dependency is typed -> None at api/deps.py:518-523, resolves the principal at :556, and discards it. The true value is computed on every request and thrown away by a return type.
Two candidate fixes, and the choice is made here. Adding a second dependency per handler resolves the principal twice per request. Changing the return type does not — and it makes the value available to every one of the fourteen declaration sites that might later need it. The declarations that ignore it keep working; their : None annotations need updating for the type checker.
| None is not a hedge — it is the legacy branch made visible. deps.py:551-555 returns before resolving any person when created_by_person_id is NULL, deliberately, so pre-Phase-25 bearer-token candidates keep working. Typing the return as Principal | None forces every handler to say what it does when there is no person, rather than leaving it implicit. That is the whole point.
D-5 — RESOLVED in part by Step 0: Provenance.wasAttributedTo is required, so the sentinel branch applies. But the sentinel's kind was left unspecified, and it is not a free choice.
One question comes first, and Step 2 answers it. The legacy branch exists for pre-Phase-25 bearer-token candidates — so the request is authenticated even though no person is resolved. Establish whether a contributor identity is available at that point. B-25's entire subject is a true value that exists and is discarded; if this is another instance, there is no sentinel to write.
kind="contributor".
Why contributor and not another kind. (Queue entry Q-9, taken.) person would assert a session-cookie path that was not taken. agent and companion sit in NON_HUMAN_ACTOR_KINDS and would revoke commit authority on records made by a human, which is a live consequence rather than a cosmetic one — see D-3. contributor is the only kind that falsifies nothing: the legacy path is a bearer-token path, and a human did act on it. What is unknown is which human, and that is what the sentinel identifier says.
The sentinel carries a fixed UUID, a display name naming exactly what it is — legacy bearer-token path, identity not resolved — and a comment at its definition explaining why it exists and the condition under which it stops being needed.
D-6 — site 4 is struck from B-25. (Queue entry Q-8, taken.)
v0.1 said site 4 should "distinguish absent from explicitly-null, and write the agent the client named." The remedy cannot be applied to the code it names. The coalesce at considerations.py:195 fires only when the client named no agent, and the request schema declares that as intended: agent_actor_id — optional; when omitted a fresh agent identity is allocated for the run.
Nothing is lost, so there is nothing to recover. This is the mirror image of site 11, where the engine's own comment states a contract the or cannot honour. Here the schema says the same thing the code does. The two sites look alike and are not, and that difference is B-25's distinguishing test — the same test that struck seed_conversation.py:116 at D-1.
A real question may remain and it is not this one. An ephemeral agent UUID naming no registered agent, stamped onto the summary and the event, is recognisably the B-25 class. But that is a change to a declared API contract, not the correction of an untrue write, and it may reach past site 4. It becomes its own build-list item. Deciding it inside this CR is the scope absorption C-3 forbids, and is what caused both CR-2026-159 halts.
The count. B-25 was eleven. Site 4 is struck here, site 10's kind-collapse at D-3. Nine sites remain, plus the compounding site and D-2's validation.
Group A — sites 5 to 9. Replace five hand-written ActorRef(kind="contributor", id=person.id, …) constructions with actor_from_person(person).
| Site | Anchor at e106917 |
|---|---|
| 5 | api/routers/seed_commit_from_brief.py:271 |
| 6 | api/routers/me_create_engagement.py:393 |
| 7 | api/routers/me_create_engagement.py:728 |
| 8 | api/routers/seed_extraction.py:246 |
| 9 | api/routers/seed_extraction.py:285 (moved +2 by B-29's 4fd175f) |
stele/actor.py:25-37 constructs exactly this over a person already typed Principal — the helper's own parameter type. me_create_engagement.py already imports it at line 95 and calls it correctly at line 942, in the same file as sites 6 (:393) and 7 (:728). (v0.1 said "twelve lines' worth of file away" — wrong, and a distance the finding never needed: 942 is 214 lines from site 7 and 549 from site 6. The error originated in the Step 0 findings' headline and was inherited here in good faith. The first instance of an unneeded specific crossing sessions. Corrected at v0.2; the findings owe the same correction.) Each handler declares person: Principal = Depends(get_current_principal), verified at seed_commit_from_brief.py:240, me_create_engagement.py:572, seed_extraction.py:104.
Only the kind literal is wrong at all five — the identifier is already correct. The Step 0 sweep confirms the class is exactly five and no larger.
Group B — site 1. api/routers/engagements.py:285. draft_engagement declares person=Depends(get_current_principal_optional); the principal is in scope. One line. Note the _optional — this handler may legitimately have no person, so the D-5 branch applies here too.
Group C — sites 2 and 3. api/routers/engagements.py:347 and :392, per D-4 and D-5. Includes updating the fourteen declaration sites across five files and the three explicit call sites in orchestration/routers/converse.py.
Group D — struck. See D-6.
Group E — the compounding site only. api/routers/assertions.py:405 hardcodes companion_name="Companion" instead of reading the per-person preference. Site 10's two kinds are correct and are not touched — see D-3 as resolved.
Group F — site 11. orchestration/routers/converse.py:690 — engagement_id = body.project_id or _current_engagement_id. The engine's own comment at :678-686 states the contract the or cannot honour: project_id wins when supplied, otherwise the carried engagement supplies scope, and when both are absent the scope stays None so the intent handlers ask. An explicitly-supplied null is indistinguishable from an omitted field after Pydantic parsing, so a caller meaning no engagement silently gets the last-viewed one and the turn is recorded against it.
This is not an attribution fix and must not be built as one. It is an engagement-scope defect that the sizing's sweep B collected alongside authorship findings.
Also in scope — D-2's validation at considerations.py:111/:534.
seed_conversation.py:116 — struck, D-1.test_stele_router_mount 401 — build-list item B-33.loomworks-engine.RESUME. Steps 0 and 1 are landed and are not repeated.
> What is already done, on branch cr-2026-161-authorship-truth. Step 0 at 6a1bb13 — pre-flight, baseline failure set recorded as the single entry test_stele_router_mount.py::test_stele_router_mounts_and_begin_resolves_end_to_end (1 failed / 3444 passed / 68 skipped), all three determinations settled. Step 1 at 725ef21 — Group A, sites 5–9 calling actor_from_person, suite at baseline. Gate item 2 is already met: the sweep returns zero. Engine main untouched at e106917; the tree is clean and site 4 is byte-unchanged.
>
> A resuming session re-confirms only: HEAD, working tree, that the branch is at 725ef21, and that the baseline failure set is still that one entry. Anything else diverging is an anomaly and halts.
Per-step commits, suite at each against the baseline set. Commit to the branch; check the current branch before the first commit.
Step 2 — site 1, and the D-5 prior question. api/routers/engagements.py:285. draft_engagement declares person=Depends(get_current_principal_optional), so the principal is in scope when there is one. Answer D-5's prior question here — is a contributor identity available on the legacy path? — because site 1 and Group C both depend on the answer. Report it before writing the sentinel.
Commit: CR-2026-161 step 2: site 1 authorship; legacy-path identity determination
Step 3 — site 11. The engagement-scope coalesce at orchestration/routers/converse.py:690.
Commit: CR-2026-161 step 3: engagement scope honours an explicit null
Step 4 — the compounding site. api/routers/assertions.py:405 reads the per-person companion name. Site 10 itself is not touched.
Commit: CR-2026-161 step 4: per-person companion name
Step 5 — D-2's validation at considerations.py:111/:534.
Commit: CR-2026-161 step 5: validate the escalation recipient
Step 6 — Group C. The dependency return type, the fourteen declarations, the three explicit calls, and the legacy-path branch per Step 2's determination. Last, because it is the largest and the only one that can cascade.
Commit: CR-2026-161 step 6: assert_engagement_creator returns the principal
Step 7 — regression tests. One per remaining group. Group C's must cover the legacy NULL-creator path explicitly. Assert specific exceptions and specific conditions, never a bare Exception. Add one test that fails if site 10's two kinds are ever collapsed — the authority boundary at D-3 has no test protecting it today.
Commit: CR-2026-161 step 7: authorship regression tests
CHECKPOINT A — report, then proceed. Failure set versus baseline; the tests; live verification on a throwaway database showing a session-cookie route writing kind="person" and the legacy path writing whatever Step 2 determined. No Operator confirmation. Halt and queue on: any failure outside the baseline set; any site whose remedy cannot be applied to the code it names, as at D-6; any charter §6 anomaly.
Step 8 — implementation notes, recording all three Step 0 determinations and Step 2's.
CHECKPOINT B — merge and tag. --no-ff to main, annotated tag authorship-truth-v0_1, push. Authorized under R-2 including the push. Deployment is never autonomous (F-1).
ActorRef anywhere in src/ carries kind="contributor" with id=person.id. The Step 0 sweep pattern, re-run, returns zero.actor_from_person; no hand-written equivalent remains.assert_engagement_creator returns Principal | None; all fourteen declarations and three explicit calls type-check.api/routers/assertions.py:405 reads the per-person companion name.seed_conversation.py:116 is unchanged — struck, not fixed.0102. No change outside loomworks-engine. No database touched except a throwaway.This block names the entry point and the fences. It does not restate §5's steps.
CR-2026-161 — B-25, the record writes that aren't true. Execution session.
CR: loomworks-record/change-requests/cr-2026-161-authorship-truth-v0_2.md
Confirm v0_2 is the highest version present — numeric sort.
THIS IS A RESUME. Steps 0 and 1 are landed on branch cr-2026-161-authorship-truth
at 725ef21. Do NOT repeat them. Enter at Section 5, Step 2.
Grounding, read before the CR:
inspection-briefs/loomworks-b25-step-0-findings-v0_1.md
Every anchor in the CR comes from that document, read at e106917. The older
sizing findings and corrections-note C-6 are superseded in part — do not work
from them.
Charter dunin7-standing-authorization-charter-v0_1 governs.
Section 5 is the step sequence; follow it there.
Target: /Users/dunin7/loomworks-engine at main e106917 or later. The engine
belongs to this CR for its duration.
playground_dev is the live production database and is not touched. Verification
runs on the walk-audit dev pattern: spare port, inline environment, throwaway
database, live processes and .env untouched, teardown after.
The baseline is NOT green — one long-standing unrelated failure. Record the
baseline failure set at Step 0. "No new failures" is the criterion.
All three Step 0 determinations are settled and recorded in Section 2.
Step 2 settles one more — whether a contributor identity is available on the
legacy path — and reports it before writing any sentinel.
Commit to a branch, never to main. Check the current branch before the first
commit of the session.
Steps 2 through 7 auto. CHECKPOINT A reports and proceeds on a clean report.
Step 8 auto. CHECKPOINT B merges, tags authorship-truth-v0_1, pushes.
Deployment is never yours.
Halt and queue on: any failure outside the baseline set; any determination
coming back ambiguous rather than settled; any charter section 6 anomaly.
Append the outcome to current-status/dunin7-status-brief at close.
DUNIN7 — Done In Seven LLC — Miami, Florida CR-2026-161 — B-25: the record writes that aren't true — v0.2 — 2026-08-01 Eleven sites, one struck, five collapsing into a helper that was already there.