Version. 0.1
Date. 2026-08-01
Executed by. Claude Code on DUNIN7-M4, one engine session.
Brief. inspection-briefs/loomworks-b25-step-0-inspection-brief-v0_1.md — confirmed the highest version present (numeric sort; v0.1 is the only one).
Charter. standing-notes/dunin7-standing-authorization-charter-v0_1 — R-5 inspection run.
Status. Complete. Four questions answered. One unread item, named at §7.
Headline. Ten of the eleven sites are unmoved; one moved, and B-29 moved it. B-29 did not change a single one of the eleven constructions — it changed the argument beside five of them. The material finding is not in the re-anchoring: sites 5 through 9 collapse into five calls to a helper that already exists, actor_from_person, which one of the affected files already imports and already uses correctly twelve lines below the defect. B-25 is not eleven heterogeneous fixes. One of the two judgment questions resolves against the defect list and should be struck; site 11 resolves, and was never unrecoverable — it was tested against the wrong question.
| | |
|---|---|
| Repo | /Users/dunin7/loomworks-engine |
| HEAD | e10691718485f756c16386f7586ddd94e8f8d2cf (e106917) |
| Branch | main |
| Tag | provenance-seam-v0_1 → e106917 (the tag object is f89e9c7) |
| Working tree | Clean. git status --porcelain returned nothing. |
Fences honoured. No database connection of any kind — not playground_dev, not playground_test. No test-suite run. No call against :8000 or any other perimeter. No branch, commit, stash, fix, or dependency install. Every finding below is a static read of source at e106917, or of git history.
Nothing inherited. Every line in the evidence base was recorded at a317051. Every line below was read at e106917. Where the two agree, they were still read here.
Ten unmoved, one moved. The mover is site 9, and B-29 moved it.
| # | Recorded at a317051 | At e106917 | Moved | B-29 touched the file | Construction today |
|---|---|---|---|---|---|
| 1 | api/routers/engagements.py:285 | :285 | no | no — 0 wasGeneratedBy | draft_actor = ActorRef(kind="contributor", id=uuid.uuid4()) |
| 2 | api/routers/engagements.py:347 | :347 | no | no | actor = ActorRef(kind="contributor", id=uuid.uuid4()) |
| 3 | api/routers/engagements.py:392 | :392 | no | no | actor = ActorRef(kind="contributor", id=uuid.uuid4()) |
| 4 | api/routers/considerations.py:195 | :195 | no | no | id=body.agent_actor_id or uuid.uuid4(), |
| 5 | api/routers/seed_commit_from_brief.py:271 | :271 | no | yes | draft_actor = ActorRef(kind="contributor", id=person.id, …) |
| 6 | api/routers/me_create_engagement.py:393 | :393 | no | yes | draft_actor = ActorRef(kind="contributor", id=person.id, …) |
| 7 | api/routers/me_create_engagement.py:728 | :728 | no | yes | person_actor = ActorRef(kind="contributor", id=person.id, …) |
| 8 | api/routers/seed_extraction.py:246 | :246 | no | yes | extraction_actor = ActorRef(kind="contributor", id=person.id, …) |
| 9 | api/routers/seed_extraction.py:283 | :285 | +2 | yes | draft_actor = ActorRef(kind="contributor", id=person.id, …) |
| 10a | orchestration/router.py:359 | :359 | no | no | actor = actor_from_companion(id=person.id, companion_name=_companion_name_for(host_account)) |
| 10b | orchestration/router.py:3292 | :3292 | no | no | same construction |
| 10c | orchestration/routers/converse.py:1378 | :1378 | no | no | actor=actor_from_companion(id=person.id, companion_name=_companion_name) |
| 10d | api/deps.py:1176 | :1176 | no | no | actor_ref=actor_from_person(person) |
| 11 | orchestration/routers/converse.py:690 | :690 | no | no | engagement_id = body.project_id or _current_engagement_id — see §4 |
Site 9's move is attributable. git log a317051..e106917 -- api/routers/seed_extraction.py returns exactly one commit, 4fd175f (CR-2026-159 step 2/1: the co-located call sites). B-29's pre-mint insertion above it shifted it two lines.
The compounding site. api/routers/assertions.py:405 — id=actor.person_id, companion_name="Companion" — confirmed at e106917, unmoved. engagement/assertions.py carries zero occurrences of companion_name or the literal. Corrections-note C-6 holds exactly as filed.
B-29 changed the argument beside these five. It did not change the five.
The reshaped neighbour, read at seed_extraction.py:246-278, is the option-A form: an identifier pre-minted into a local, threaded into Provenance(...) and passed to append_event(..., event_id=…):
extraction_actor = ActorRef( # 246 — SITE 8, unchanged by B-29
kind="contributor",
id=person.id,
display_name=getattr(person, "display_name", None),
)
extracted_at = datetime.now(timezone.utc)
extraction_event_obj_event_id = uuid.uuid4() # B-29 added
…
provenance=Provenance(
wasAttributedTo=extraction_actor, # ← B-25's subject
wasGeneratedBy=extraction_event_obj_event_id, # ← B-29's, now correct
),
The collision the one-engine-front ruling was made for is discharged. The engine inspection recorded three sites where B-25's actor variable and B-29's fabricated field were "the two keyword arguments of a single expression, on consecutive lines." At e106917 B-29's half is landed and stable; B-25 edits the ActorRef(...) construction, which is a separate statement above. The two items no longer touch the same line.
All five are the same defect, exactly. Each writes kind="contributor" while carrying id=person.id on a session-cookie route. The identifier is already correct; only the kind literal is wrong. 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 — so the true value is directly in scope at every one of the five.
memory/base.py:66-81 states the distinction verbatim and is unchanged: contributor is the bearer-token path, person the session-cookie path, and the two are kept distinct "so the actor record carries the auth path the action was taken through." On the engine's own terms these five records assert a bearer-token row that does not exist.
Site 1 and sites 2–3 are not the same fix, and the difference is structural.
draft_engagement declares person=Depends(get_current_principal_optional). The principal is in the handler's own scope. One-line fix.amend_candidate_seed_route (:330) and induct_engagement_seed (:386) declare _: None = Depends(assert_engagement_creator). That dependency is typed -> None at api/deps.py:518-523. It resolves the principal internally at deps.py:556 and discards it; the handler never sees it.
So for sites 2 and 3 the true value exists, is computed on every request, and is thrown away by the dependency's return type. Correcting them requires either changing assert_engagement_creator to return the principal — it is declared at 14 sites across 5 files and called explicitly 3 more times in orchestration/routers/converse.py — or adding a second dependency to each handler.
And a case where the true value does not exist at all. assert_engagement_creator has a legacy branch at deps.py:551-555: when the engagement's created_by_person_id is NULL it returns before resolving any person, deliberately, so pre-Phase-25 bearer-token candidates keep working. On that path there is no authenticated person to attribute to. A fix for sites 2 and 3 must say what it writes there. This is a design question for the change request, and it is not visible from the sizing findings' one-line "same" for sites 2 and 3.
Both read as instructed: what the engine already treats as true, not what should be done.
api/routers/considerations.py:111, called at :534 — the escalation recipient
Both anchors unmoved. _actor_from_schema is defined at :111; the call is at :534.
What the engine already treats as true — the escalation recipient is the outlier, and the contrast is in the same call. At :524-534 the same close_consideration(...) invocation passes:
closing_party=_actor_from_contributor(contributor) — resolved from the authenticated caller, body ignored.escalate_recipient=_actor_from_schema(body.escalate_recipient) — taken wholesale from the body, kind, id and version, unvalidated.
Elsewhere the engine validates a body-supplied actor reference. compositions.py accepts specialist_ref from the request body and validates it: composition_creation.py:315 runs _validate_specialist_for_step per step against the registry, and the route's own docstring states the rule (compositions.py:142-143, "specialist_ref resolves to a registered specialist of the correct category"). That same route resolves its acting party from auth — triggered_by = _actor_from_contributor(contributor) at compositions.py:149.
So the inconsistency the brief anticipated is real and one-sided. Of the body-supplied actor fields on request schemas (escalate_recipient, opener, closing_party, registered_specialist_ref, specialist_ref, triggered_by), the acting parties are resolved from auth and the specialist reference is registry-validated. escalate_recipient is the one written through with neither.
Is it ever dereferenced? No. It is flattened at write time into two scalar columns — escalate_recipient_id and escalate_recipient_kind (engagement/considerations.py:1030-1031, and the same pair at engagement/shape_confirmation.py:714-715). A sweep of src/ for any other read of escalate_recipient_id returns nothing: no join, no lookup, no filter, no resolution anywhere. It is written and never followed.
What still needs a ruling. The read establishes that the engine treats acting parties as its own to resolve and registry references as its own to validate, while treating this one field as the client's to assert. Whether that is the correct posture — an assertion about a party rather than a reference to one — is the Operator's, and the read does not settle it. What the read does settle is that the stakes are low today: nothing consumes the value, so a wrong recipient is inert until something reads it.
api/routers/seed_conversation.py:116 — not a defect. Recommend it be struck.The brief said not to assume a defect because it appears in a defect list. It is not one.
The identity is a module-level constant, read at seed_conversation.py:109-120:
# Stable system actor for companion turns. Same id across the
# substrate's lifetime so audit walks group every Loomworks
# companion turn under one identity.
_COMPANION_ACTOR_ID = UUID("00000000-0000-0000-0000-00000031c0a1")
def _companion_actor() -> ActorRef:
return ActorRef(
kind="agent",
id=_COMPANION_ACTOR_ID,
display_name="Loomworks companion",
)
It does not vary by person, and it cannot. It is a fixed sentinel with one call site (:459).
Kind companion is therefore structurally unavailable to it. memory/base.py:81-84 requires that companion's id carry the person's UUID — "the Companion's identity is bound to the person — there is no separate companions table." stele/actor.py:44-46 says the same in the constructor that builds the kind. A stable substrate-wide sentinel has no person to bind to.
So kind="agent" is correct rather than a mislabel, and writing companion here 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 one item the read decides outright. The two unclear items become one.
orchestration/routers/converse.py:690 is unchanged at e106917: engagement_id = body.project_id or _current_engagement_id. Byte-identical to a317051.
:1307 is not site 11. It reads actor=actor_from_person(person) — a direct construction with no coalesce and no wrong value. It is the typed door of site 10, not a separate finding.
Nor is there any other coalescing construction in the file. A sweep for actor constructions and for body.project_id returns exactly: :690, :1303-1307, :1376-1378. Nothing else coalesces.
The resolution is that C-6 asked the wrong question of it. C-6 tested :690 against "is this an authorship construction?", found it was not, and filed it unresolvable. But the sizing findings never claimed it was one. Their own entry for site 11 reads "Coalesces the request's project field against the last-viewed engagement", with the true value "Empty, when the client sent an explicit null" — which describes :690 exactly, and describes an engagement-scope coalesce, not an actor.
The engine's own comment at :678-686 states the intended contract: "project_id wins when supplied … otherwise the carried current engagement supplies the scope. When BOTH are absent the scope stays None and the intent handlers ask which engagement — never a silent fall-back." or cannot honour that contract: an explicitly-supplied null is indistinguishable from an omitted field after Pydantic parsing, so a caller who means no engagement silently receives the last-viewed one, and the turn is recorded against it.
Site 11 is real, correctly described, at its recorded line, and is a different defect class from the other ten. It is a falsy-coalesce on an engagement identifier, swept up by sweep B alongside authorship findings. The count stays eleven; the change request should not treat it as an attribution fix. No site was manufactured to preserve the number.
Not because B-29 clustered anything. B-29 introduced no actor helper; it added pre-minted event identifiers and an event_id= parameter. Of the eleven, none now sits inside a helper B-29 introduced or modified.
The clustering is pre-existing, and the sizing missed it. src/loomworks/stele/actor.py:25-37 already provides:
def actor_from_person(person: Principal) -> ActorRef:
return ActorRef(
kind="person",
id=person.id,
display_name=person.display_name,
)
That is precisely what sites 5 through 9 should construct. Each of the five currently hand-writes the same four lines with kind="contributor", over a person already typed Principal — the helper's own parameter type.
And one of the affected files already uses it. me_create_engagement.py imports actor_from_person at line 95 and calls it correctly at line 942 (operator_actor = actor_from_person(person)) — in the same file as sites 6 (:393) and 7 (:728), which hand-construct the wrong kind. The correct pattern and the defect coexist in one file, and the import is already there.
A sweep confirms the class is exactly five and no larger. Stating what it had to find first: every ActorRef whose kind is contributor while its id is person.id. The pattern was checked against the known multi-line form before being trusted. It returns five, and only five — seed_commit_from_brief.py:272, seed_extraction.py:247 and :286, me_create_engagement.py:394 and :729 — the kind= lines of sites 5–9. There is no sixth instance. The correct kind="person" form appears at 13 sites, including seed_conversation.py:541 and :1003, which write ActorRef(kind="person", id=person.id, display_name=person.display_name) inline.
Consequence for the change request. B-25 was sized as "eleven heterogeneous fixes." It is not:
The sizing changes materially, and it changes downward for eight of the eleven.
1. Site 4 did not move; an intermediate reading in this session said it had. Grepping for ActorRef( returns the constructor opening at :193, while the recorded site names :195 — the id= line carrying the defect. The file is byte-identical across a317051..e106917 (git log returns no commit touching it), and line 195 reads id=body.agent_actor_id or uuid.uuid4(), at both. Recorded because the near-miss is instructive: a recorded line number may name the defect line rather than the statement opening, and a grep for the opening will appear to show movement that has not happened.
2. C-6's "unresolvable" on site 11 is superseded, and its underlying reads were all correct. C-6 established that :690 is not an authorship construction and that the file's only actor constructions are :1307 and :1378. Every one of those reads holds at e106917. What is corrected is the conclusion drawn: site 11 was never an authorship claim, so "not an authorship construction" is not evidence that it is lost. C-6 stands as filed; §4 above supersedes only its disposition.
3. C-6's companion-name relocation is confirmed, not merely inherited. api/routers/assertions.py:405 carries the site; engagement/assertions.py carries zero occurrences. Both re-read here.
4. The sizing findings' "same" for sites 2 and 3 is too flat. Site 1's principal is in handler scope; sites 2 and 3's is resolved and discarded by a -> None dependency, with a legacy branch that resolves none at all. §2.2 records the difference. The sizing's finding stands — all three write a fabricated identifier — only its implied uniformity of repair does not.
5. The eighty-three-against-three framing is untouched by this read and is now historical: B-29 landed, and the three-correct sites in persons/personal_engagement.py are joined by the rest.
One.
| # | Unread | Why | What would settle it |
|---|---|---|---|
| 1 | Whether site 10's two doors can be reconciled without a behavioural choice — i.e. whether any existing consumer distinguishes kind="companion" from kind="person" on these records. | Deciding it needs either a database query over live records or a suite run exercising both doors. Both are outside the fences. | A read of every consumer that branches on actor.kind — feasible statically but larger than this brief's four questions; or the change request's own Step 0. The site itself is fully re-anchored (§2); only the choice between the two doors is unread. |
Nothing else was blocked. No question required a database, a suite run, a perimeter call, or a build.
DUNIN7 — Done In Seven LLC — Miami, Florida Loomworks — B-25 Step 0 findings — v0.1 — 2026-08-01 Ten of eleven where they were. The eleventh moved two lines. The fix moved further than any of them.