DUNIN7 · LOOMWORKS · RECORD
record.dunin7.com
Status Current
Path change-requests/cr-2026-161-authorship-truth-v0_1.md

DUNIN7-M4 — INFRASTRUCTURE CHANGE REQUEST

CR-2026-161 — B-25: the record writes that aren't true — v0.1

Version. v0.1 Date. 2026-08-01 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_1every 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.


1. What this fixes

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.


2. Construction decisions

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 — site 10's premise is verified before the choice is made. (Queue entry Q-7, taken with a check.)

The spoken door writes kind="companion" with a companion name; the typed door writes kind="person" with the person's own name. Both carry the person's UUID, so both are structurally valid.

The sizing assumed the answer is "one of the two, consistently." That assumption is not tested anywhere. If the spoken door records the Companion authoring on the person's behalf and the typed door records the person writing directly, then the difference is provenance, not inconsistency, and collapsing it destroys the tie-to-source the seed's provenance-threads principle protects.

Step 0 answers one question: does the spoken door write Companion-authored content, or person-authored content the Companion transcribed?

Do not decide this from the CR. Read it, then apply the branch. (The unread item at the findings' §7 asks a larger question — whether any consumer branches on actor.kind for these records. That sweep rides here as a Step 0 item.)

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 — what sites 2 and 3 write on the legacy path is a Step 0 determination, with both branches specified here.

On that path there is no authenticated person. Fabricating one is precisely what B-25 exists to end. Omitting the write loses the event.

Step 0 establishes one fact: is Provenance.wasAttributedTo required or optional?

The drafting session did not read this field's optionality and does not assert it. Both branches are specified so the executing session applies one rather than inventing a third.


3. In scope

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, twelve lines' worth of file away from sites 6 and 7. 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 — site 4. api/routers/considerations.py:195id=body.agent_actor_id or uuid.uuid4(). An explicit null yields a fresh agent identity per call, stamped onto both the summary and the event. Distinguish absent from explicitly-null, and write the agent the client named.

Group E — site 10 and the compounding site, per D-3. orchestration/router.py:359 and :3292, orchestration/routers/converse.py:1378, against api/deps.py:1176. Compounding: api/routers/assertions.py:405.

Group F — site 11. orchestration/routers/converse.py:690engagement_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.


4. Out of scope


5. Order of operations

Per-step commits. Suite run at each, against the Step 0 baseline set. Check the current branch before the first commit — two prior sessions committed a first step to main.

Step 0 — pre-flight and three determinations. Verify HEAD, tree, alembic head, CR number. Record the baseline failure set by name. Re-confirm all eleven anchors and the compounding site.

Then settle, by reading:

  1. Is Provenance.wasAttributedTo required or optional? → selects D-5's branch.
  2. Does the spoken door write Companion-authored or transcribed content? → selects D-3's branch.
  3. Does any consumer branch on actor.kind for site 10's records? → the findings' one unread, static sweep.

Report all three before proceeding. Commit: CR-2026-161 step 0: pre-flight and three determinations

Step 1 — Group A. Five helper calls. The smallest, cleanest diff; do it first so the suite is exercised early against the most sites. Commit: CR-2026-161 step 1: sites 5-9 use actor_from_person

Step 2 — Group B and Group D. Site 1 and site 4. Independent, small. Commit: CR-2026-161 step 2: site 1 authorship, site 4 agent coalesce

Step 3 — Group F. Site 11's engagement-scope coalesce. Commit: CR-2026-161 step 3: engagement scope honours an explicit null

Step 4 — Group E, per Step 0's determination. May reduce to the compounding site alone. Commit: CR-2026-161 step 4: site 10 per determination; per-person companion name

Step 5 — D-2's validation. 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. 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 group. Group C's must cover the legacy NULL-creator path explicitly — that is the path with no true value and the one most likely to be wrong later. Assert specific exceptions and specific conditions, never a bare Exception. Commit: CR-2026-161 step 7: authorship regression tests

CHECKPOINT A — report, then proceed. Failure set versus baseline; the tests; a live verification on a throwaway database showing a session-cookie route writing kind="person" and the legacy path writing whatever D-5 selected. No Operator confirmation. Halt and queue on: any failure outside the baseline set; either determination coming back ambiguous rather than settled; any charter §6 anomaly.

Step 8 — implementation notes.

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


6. Acceptance gate

  1. No failure outside the Step 0 baseline set, at every step.
  2. No ActorRef anywhere in src/ carries kind="contributor" with id=person.id. The Step 0 sweep pattern, re-run, returns zero.
  3. Sites 5–9 call actor_from_person; no hand-written equivalent remains.
  4. Site 1 writes the authenticated person, or D-5's legacy value when there is none.
  5. Sites 2 and 3 write the resolved principal; the legacy NULL-creator path writes D-5's selected value and never a fabricated identifier.
  6. assert_engagement_creator returns Principal | None; all fourteen declarations and three explicit calls type-check.
  7. Site 4 distinguishes an absent agent identifier from an explicitly-null one.
  8. Site 10 resolved per D-3's determination, with the determination recorded in the implementation notes.
  9. api/routers/assertions.py:405 reads the per-person companion name.
  10. Site 11 leaves the engagement scope empty on an explicit null, and the intent handlers ask.
  11. The escalation recipient is validated for existence and kind-identifier agreement.
  12. seed_conversation.py:116 is unchanged — struck, not fixed.
  13. Regression tests per group, exceptions named specifically.
  14. Alembic head unchanged at 0102. No change outside loomworks-engine. No database touched except a throwaway.
  15. The status brief is appended, per charter §7.

7. Claude Code kickoff

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_1.md
Confirm it is the highest version present — numeric sort.

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.

Step 0 settles THREE determinations before any code changes. Two of them select
between branches the CR specifies; do not invent a third branch. Report all
three before proceeding.

Commit to a branch, never to main. Check the current branch before the first
commit of the session.

Steps 0 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.1 — 2026-08-01 Eleven sites, one struck, five collapsing into a helper that was already there.