Version. 0.1
Date. 2026-07-31
Executed by. Claude Code on DUNIN7-M4, one engine session.
Governing brief. inspection-briefs/loomworks-b29-b27-b5-engine-inspection-brief-v0_1.md (record 8d79e91).
Status. Read-only inspection, complete. Three questions answered, one ride-along partial. No code change, no change request.
Headline. Four positions in the evidence base are overturned by read: the target tag is not where the brief places it; §3.3's suspicion about the three "correct" sites is wrong in the other direction — they are correct; walk-audit W-16 is wrong; and the seam is not one seam. B-27 is resolved. B-5's staleness marker is a wire, not a build.
| Fact | Value | Read from |
|---|---|---|
| Engine HEAD | a3170518581e71c5f148ec6aaa7cc3fa67588efe (a317051) | git rev-parse HEAD |
| Branch | main | git rev-parse --abbrev-ref HEAD |
| Working tree | NOT clean — M uv.lock (unstaged, 10 insertions / 1 deletion) | git status --porcelain, git diff --stat |
| Tag registry-completeness-v0_1 | Points at c4014d6, not a317051 | git rev-parse registry-completeness-v0_1^{commit} |
| git describe | registry-completeness-v0_1-1-ga317051 | git describe --tags |
| Record HEAD at time of write | 8d79e91 (main) | git rev-parse HEAD in loomworks-record |
| Record tree | Clean except untracked candidate-seeds/loomworks/loomworks-seed-transfer-v0_1.zip (not touched) | git status --porcelain |
Correction to the brief's own target line. The brief states the target as "main a317051, annotated tag registry-completeness-v0_1", which reads as the tag sitting on the inspected commit. It does not. The tag is on c4014d6, the --no-ff merge commit; a317051 is one commit later — CR-2026-158 implementation notes v0.1, a docs-only commit. The inspection was run at a317051 as instructed. The two commits differ only in documentation, so nothing in this report turns on the distinction, but the manifest should carry the tag's real position.
Working tree. Left exactly as found. uv.lock was modified before this session began; it was not cleaned, staged, or inspected beyond git diff --stat.
Fences honoured. No database connection of any kind was opened — not playground_dev, not playground_test, not playground_walkaudit. No test suite was run. No request was made against the :8000 perimeter or any other. No branch, commit, stash, rebase, or dependency install occurred in loomworks-engine. Every finding below is a static read of source at a317051, of git history, or of in-repo documents.
append_event is at src/loomworks/memory/events.py:130-255. Read end to end.
Where the identifier is minted, and what mints it. src/loomworks/memory/events.py:171 — event_id=uuid.uuid4(), inline in the MemoryEventRow(...) constructor call. Nothing else mints it; there is no id-allocation helper.
Whether the object is still mutable at that point. No, on two counts.
MemoryObject is frozen — model_config = ConfigDict(frozen=True) at src/loomworks/memory/base.py:161. Provenance is separately frozen at base.py:144. Neither can be assigned into after construction.payload = object.model_dump(mode="json") at events.py:168 — three lines before the mint at 171.
The exact ordering. Read from events.py:151-223:
| Step | Line | Action |
|---|---|---|
| 1 | 152-158 | SELECT … FOR UPDATE row lock on the engagement |
| 2 | 164-165 | Read current_engagement_version, compute next_version |
| 3 | 168 | Serialise — object.model_dump(mode="json") |
| 4 | 171 | Mint — uuid.uuid4() |
| 5 | 179 | Serialise provenance again, separately — object.provenance.model_dump(mode="json") |
| 6 | 187 | db.add(event) |
| 7 | 190-195 | Advance the engagement version counter |
| 8 | 214-219 | Six projector calls, same transaction |
| 9 | 223 | await db.flush() — caller commits |
Serialise precedes mint. This is the load-bearing fact of §3.1 and it constrains §3.7 directly: any repair that stamps the field inside append_event must reorder these two steps, not merely insert a line.
Note on line 179. The fabricated value is written twice — once inside the payload JSONB (via line 168's dump of the whole object, which includes provenance) and once into the dedicated provenance JSONB column (line 179). A repair must reach both. Because line 179 dumps from the object rather than from payload, correcting the object before line 168 corrects both for free; correcting payload after the fact would fix only one.
Whether one call can produce more than one object version. No. The signature at events.py:130-137 takes a single object: MemoryObject, and the row is written with object_version=object.version (line 176). One call, one object version, one event id. There is no batching path. The uq_memory_events_object_version constraint at events.py:73 enforces one row per (object_id, object_version).
Whether any other function persists a memory object without going through append_event. This is where the brief's framing breaks.
Yes — three raw-SQL paths in src/ write to memory_events directly:
| Path | Line | Mints its own event_id at |
|---|---|---|
| persons/personal_engagement.py::_append_event | 241 | supplied by caller, param at 219 |
| engagement/wasderivedfrom_backfill.py | 121 | 74 |
| engagement/redirect_edge_backfill.py | 185 | 137 |
Each executes its own INSERT INTO memory_events (event_id, engagement_id, engagement_version, object_id, object_type, object_version, event_kind, payload, provenance, timestamp, actor_id, actor_kind, actor_instruction_version), its own FOR UPDATE lock, its own version advance, and its own projection upsert. None of them calls append_event.
memory/events.py:258 relocate_memory_object also mutates memory_events, but it is not a persist path for a new object — it preserves object_id/object_version and rewrites only engagement_id, engagement_version, and the payload's engagement_id key (events.py:335-359). It does not touch provenance. It is not part of the B-29 surface.
memory/projector.py constructs MemoryEventRow(...) at lines 648, 728, 911, 1147, 1417. These are not persist paths. Each builds a transient in-memory carrier from a SELECTed row to feed a view-replay function; grep -n "db.add\|session.add" src/loomworks/memory/projector.py returns nothing. Verified.
Consequence. The sizing findings' claim at §5 — "It is one seam" — is wrong as stated. It is one seam plus three bypasses. A repair landed only in append_event leaves wasderivedfrom_backfill.py:84 and redirect_edge_backfill.py:148 still fabricating, and leaves personal_engagement.py correct only by its own separate discipline. This is sized at §2.7 below.
Whether append_event has callers that pass provenance wholesale. No — it cannot. The signature (events.py:130-137) accepts engagement_id, object, event_kind, actor, db. There is no provenance parameter. Provenance always arrives already sealed inside the frozen object. This is precisely why the field cannot currently be correct: the caller must construct provenance before the event exists, and the event id does not exist until three lines after the object is serialised.
Read at src/loomworks/memory/base.py:138-152.
class Provenance(BaseModel):
model_config = ConfigDict(frozen=True) # 144
wasAttributedTo: ActorRef # 146
wasGeneratedBy: UUID # the memory event id that produced this version # 147
wasDerivedFrom: list[MemoryRef] = Field(default_factory=list) # 148
wasRevisionOf: MemoryRef | None = None # 149
wasInvalidatedBy: MemoryRef | None = None # 150
wasConsideredAndRejected: list[MemoryRef] = Field(default_factory=list) # 151
Required or optional. Required. wasGeneratedBy: UUID at base.py:147 — bare annotation, no = None, no Field(default=…).
Does it carry a default factory. — No. base.py:147 has no default_factory and no default of any kind. Contrast the two fields either side of it that do: wasDerivedFrom (line 148) and wasConsideredAndRejected (line 151) both carry Field(default_factory=list), and wasRevisionOf/wasInvalidatedBy (149/150) both default to None. The declaration distinguishes defaulted from required fields deliberately, and wasGeneratedBy is on the required side.
This is the fact the brief said would decide B-29's size, and it decides it against the smaller reading. Every construction of Provenance must name wasGeneratedBy or fail Pydantic validation at construction. There is no site that inherits a default, because there is no default to inherit.
The split the brief asked for: of the fabricating sites, 83 name the field explicitly, 0 inherit a default. The count is real.
Both spellings, reported separately as instructed.
grep -rn "wasGeneratedBy" --include="*.py" . → 420 lines, 173 files
grep -rn "was_generated_by" --include="*.py" . → 22 lines, 5 files
The second spelling does not change the number. All 22 was_generated_by occurrences are local Python parameter or variable names that feed the one camelCase wire key. Enumerated in full:
migrations/versions/0034_…py:90,94,292,336,374 — helper param _provenance(actor, was_generated_by), writing "wasGeneratedBy" at :94migrations/versions/0035_…py:177,188,313,352 — same helper shapemigrations/versions/0058_…py:77,80 — same helper shapetests/helpers/loomworks_engagement.py:218,229,377,417,490,539,574,642,681 — same helper shapesrc/loomworks/engagement/wasderivedfrom_backfill.py:75,84 — local variableThere is no second wire spelling and no second serialisation form. One field, one key.
Why 420 lines is not 420 sites — a distinction the change request needs. The codebase carries two different things named wasGeneratedBy:
Provenance field at base.py:147 — B-29's subject.Relationship vocabulary term, declared in RelationshipVocabulary at base.py:53, used as vocabulary="wasGeneratedBy" to draw a graph edge between two memory objects — e.g. engagement/boundary.py:264,386,690,915, engagement/drift_results.py:89, engagement/considerations.py:1596.
These are unrelated. The bulk of the 420 hits — and the overwhelming majority of the 312 in tests/ — are the vocabulary term. A sweep that does not separate them will mis-size the work in both directions.
The fabricating count, ground truth. Sweeping only the stamped-generated-value form:
grep -rnE 'wasGeneratedBy["]?[=:] ?(str\()?uuid4?\.?uuid4\(\)' --include="*.py" src/
→ 82 sites across 36 files. Plus engagement/wasderivedfrom_backfill.py:84, which fabricates via the local variable assigned at :75 and so escapes that pattern.
Total: 83 fabricating sites across 37 files.
Correction. The sizing findings' 83 is exactly right; its 36 files is one short. The missed file is engagement/wasderivedfrom_backfill.py — one of the two backfill modules the same findings separately note as "also fabricating," so the module was seen and the file count simply was not incremented.
Distribution, read from the sweep, heaviest first: engagement/creation.py 8 · engagement/assertions.py 6 · engagement/bootstrap.py 5 · engagement/shaping_process.py 4 · engagement/channels.py 4 · engagement/boundary.py 4 · credit/bootstrap.py 4 · api/routers/seed_conversation.py 4 · then 3s (declared_shape_types, declared_render_types, considerations, composition_orchestrator, cadences, credit/proposal_applier, api/routers/uploads), one 2 (assertion_groups), and 20 files carrying one each. This matches the sizing findings' published distribution exactly.
The brief named this "the single most likely place this brief's evidence base is wrong" and instructed: treat it as the question, not the confirmation.
Read. The three sites are correct. The brief's suspicion is overturned — the prior position stands.
All three are in src/loomworks/persons/personal_engagement.py, inside create_personal_engagement (:327). The mechanism, read in full:
Step one — the three identifiers are pre-minted, before any payload is built:
356 seed_event_id = uuid.uuid4()
357 cycle_event_id = uuid.uuid4()
358 engagement_event_id = uuid.uuid4()
Step two — each is threaded into its object's provenance:
| Object | Builder | Field stamped |
|---|---|---|
| Seed | build_personal_seed_payload(seed_event_id=…) :54,61 | "wasGeneratedBy": str(seed_event_id) :83 |
| Cycle | build_personal_cycle_payload(cycle_event_id=…) :122,128 | "wasGeneratedBy": str(cycle_event_id) :150 |
| Engagement | build_personal_engagement_payload(engagement_event_id=…) :171,177 | "wasGeneratedBy": str(engagement_event_id) :193 |
Step three — and this is what settles it — the same identifier is passed to the insert as that row's own event_id:
| Call | event_id= argument | Line |
|---|---|---|
| _append_event(… object_type="engagement" …) | engagement_event_id | 401 |
| _append_event(… object_type="seed" …) | seed_event_id | 426 |
| _append_event(… object_type="seed_induction_loop_cycle" …) | cycle_event_id | 450 |
And _append_event (:206-220) declares event_id: UUID at :219, binds it as "event_id": str(event_id) at :255, and writes it into the event_id column of INSERT INTO memory_events at :241-251.
Per site, answering the brief's three questions:
:356/:357/:358, held in a variable for the duration of the transaction.provenance.wasGeneratedBy and the value in the row's event_id column are the same variable, passed to both.
The names misled the sizing findings' reader, and misled the brief. seed_event_id, cycle_event_id, engagement_event_id read as "the id of the seed event" in the sense of a causing event. They are not. Each names the event id being produced for that object, pre-minted so it can be written into provenance before the row is inserted. The brief's hypothesis — that these are the events which caused the version rather than the event the append produces — is a reasonable reading of the names and a wrong reading of the code.
Consequence for the framing. The field does not have two live meanings in the tree. base.py:147's comment — the memory event id that produced this version — is the field's only meaning, and these three sites honour it exactly. The eighty-three-against-three framing keeps its anchor. The seam question does not change shape.
What these three sites additionally establish, which the sizing findings did not draw out. They are a working, in-tree implementation of seam option A — caller supplies the identifier. The pattern is proven: pre-mint, thread into provenance, pass the same value to the write. It works because _append_event accepts event_id as a parameter, which memory/events.py::append_event does not. This is direct evidence for §2.7's costing and should be read there.
Nothing reads it. Zero live consumers.
src/ and migrations/ for any SQL or JSONB access to the field (provenance->>'wasGeneratedBy', payload->'provenance'->>…, or the field in a WHERE/SELECT). No hits. The one near-match, engagement/considerations.py:1596 — AND r.payload->>'vocabulary' = 'wasGeneratedBy' — is the Relationship vocabulary term, not the provenance field. It filters relationship rows by edge type.memory/projector.py. Copies, does not derive. Every projector site passes provenance=row.provenance wholesale (:657, :737, :920, :1156, :1426) from a SELECTed row into a transient MemoryEventRow. The projector never reads a key inside provenance, never branches on wasGeneratedBy, and never writes a derived value from it.api/schemas.py's only occurrence of the string is :3093, inside the prose description= of the Relationship.vocabulary field — documentation of the edge vocabulary, not a provenance field exposure.tests/ for any read of provenance["wasGeneratedBy"] or .provenance.wasGeneratedBy returns nothing. The single structural hit, tests/test_drift_result.py:271 — [r for r in rels if r.vocabulary == "wasGeneratedBy"] — is again the vocabulary term. The 312 test-tree occurrences are construction-site boilerplate (wasGeneratedBy=uuid4() inside fixture objects) and vocabulary-term assertions about graph edges. Neither class constrains the field's value.engagement/wasderivedfrom_backfill.py mints event_id = str(uuid.uuid4()) at :74 and then, on the very next line, mints a second, different UUID: was_generated_by = str(uuid.uuid4()) at :75. The row's actual event id is :74's; :84 stamps :75's into provenance. The correct value is in scope one line above the wrong one.engagement/redirect_edge_backfill.py mints event_id = str(uuid.uuid4()) at :137 and stamps "wasGeneratedBy": str(uuid.uuid4()) at :148 — a fresh throwaway, with the row's real id already in a local variable eleven lines up.
Both are one-line repairs (was_generated_by = event_id; "wasGeneratedBy": event_id) that no seam change reaches, because neither module calls append_event.
Repair-cost consequence. A field with no readers, no filters, no schema exposure, and no value assertions has an unusually small blast radius for its site count. Nothing downstream can break from changing what is stamped, because nothing downstream looks. The 83 sites are a write-side-only surface.
The field enters no hashed, signed, or anchored payload today.
append_event fires one emit, at memory/events.py:247-253, marked # FORAY_RESERVED_LOCATION: memory.<event_kind>. Its payload is exactly two keys: {"event_id": str(event.event_id), "anchor_priority": _ANCHOR_PRIORITY.get(event_kind)} (:250-251). Provenance is not in it. Note that this emit already carries the correct event id — the one B-29's field should hold.src/loomworks/foray/reserved_emit.py:26-35 — _foray_reserved_emit is a no-op. Its entire body is logger.debug("FORAY reserved emit (no-op today): kind=%s", event_kind). Its docstring (:1-3) states: "Today: no-op (debug log only). Tomorrow: writes the attestation to the FORAY substrate via the dunin7-foray SDK."_ANCHOR_PRIORITY. src/loomworks/foray/anchor_priority.py:31. Its header (:26-30) records that "Today the registry is data" — a label map from event kind to anchor priority, consumed by nothing but the emit payload above.src/loomworks/memory/ and src/loomworks/foray/ for hashlib, sha256, hexdigest, digest, signature, canonical. No hits in either tree. Nothing computes a hash over payload or provenance.
The boundary the change request needs. The field sits inside two JSONB columns — memory_events.payload (written from events.py:168) and memory_events.provenance (written from events.py:179). Neither is hashed today. When FORAY integration opens at reserved_emit.py:26, whatever is in those columns at that moment becomes the anchored composition. So the ordering constraint for the Operator is: B-29 lands before FORAY integration opens, or the anchored history begins with fabricated provenance in it. Changing the stamped value after anchoring begins would change payload composition across the anchor boundary. Before it opens, the change is free of anchoring consequences entirely.
Line numbers verified individually at a317051 against the sizing findings' record.
| # | Path (recorded) | Line holds at a317051? | What is there | Same file stamps wasGeneratedBy? | Same lines? |
|---|---|---|---|---|---|
| 1 | api/routers/engagements.py:285 | ✅ | draft_actor = ActorRef(kind="contributor", id=uuid.uuid4()) | No — 0 in file | No |
| 2 | api/routers/engagements.py:347 | ✅ | actor = ActorRef(kind="contributor", id=uuid.uuid4()) | No — 0 in file | No |
| 3 | api/routers/engagements.py:392 | ✅ | actor = ActorRef(kind="contributor", id=uuid.uuid4()) | No — 0 in file | No |
| 4 | api/routers/considerations.py:195 | ✅ | id=body.agent_actor_id or uuid.uuid4(), | No — 0 in file | No |
| 5 | api/routers/seed_commit_from_brief.py:271 | ✅ | draft_actor = ActorRef( | Yes — :306 | Adjacent — see below |
| 6 | api/routers/me_create_engagement.py:393 | ✅ | draft_actor = ActorRef( | Yes — :842 | No — feeds draft_seed() |
| 7 | api/routers/me_create_engagement.py:728 | ✅ | person_actor = ActorRef( | Yes — :842 | Adjacent — see below |
| 8 | api/routers/seed_extraction.py:246 | ✅ | extraction_actor = ActorRef( | Yes — :259 | Same literal — see below |
| 9 | api/routers/seed_extraction.py:283 | ✅ | draft_actor = ActorRef( | Yes — :259 | No — feeds draft_seed() |
| 10a | orchestration/router.py:359 | ✅ | actor = actor_from_companion( | No — 0 in file | No |
| 10b | orchestration/router.py:3292 | ✅ | actor = actor_from_companion( | No — 0 in file | No |
| 10c | orchestration/routers/converse.py:1378 | ✅ | actor=actor_from_companion(id=person.id, companion_name=_companion_name), | No — 0 in file | No |
| 10d | api/deps.py:1176 | ✅ | actor_ref=actor_from_person(person), | No — 0 in file | No |
| 11 | orchestration/routers/converse.py:690 | ❌ moved or misrecorded | engagement_id = body.project_id or _current_engagement_id | No — 0 in file | No |
| — | engagement/assertions.py:404 | ❌ wrong file | return committed | n/a | No |
Three sites collide directly — same object literal, adjacent lines. In each, B-25's actor variable and B-29's fabricated field are the two arguments of one Provenance(...) construction:
api/routers/seed_extraction.py:258-259:
provenance=Provenance(wasAttributedTo=extraction_actor, wasGeneratedBy=uuid.uuid4(),)
extraction_actor is B-25 site 8, defined at :246.
api/routers/seed_commit_from_brief.py:305-306:
provenance=Provenance(wasAttributedTo=draft_actor, wasGeneratedBy=uuid.uuid4(),)
draft_actor is B-25 site 5, defined at :271.
api/routers/me_create_engagement.py:841-842:
provenance=Provenance(wasAttributedTo=person_actor, wasGeneratedBy=uuid.uuid4(),)
person_actor is B-25 site 7, defined at :728.
This is the concrete evidence for the Operator's one-engine-front ruling. At these three sites the two build items are not merely in the same file — they are the two keyword arguments of a single expression, on consecutive lines. Two sessions editing them concurrently conflict at the line level, not at the merge level.
Sites 6 and 9 are same-file but not same-expression: both draft_actor variables feed draft_seed(...) calls, not Provenance(...) literals. Sites 1-4 and 10a-10d are in files with zero occurrences of the field — verified by direct count per file — so B-29 does not reach them at all. Eight of B-25's fourteen recorded locations have no B-29 overlap whatsoever.
Two recorded locations do not resolve, reported alongside the recorded value as instructed:
orchestration/routers/converse.py:690. At a317051 this line is engagement_id = body.project_id or _current_engagement_id, inside the creation-scope resolution block (:687-690) — not an authorship construction. The only actor constructions in this file are at :1307 (actor=actor_from_person(person)) and :1378 (actor=actor_from_companion(…)), and :1378 is already recorded as site 10c. Whether site 11 was intended as :1307, or the line moved, is unread — settling it needs the sizing session's own sweep output, which is not in this repository.engagement/assertions.py:404. Wrong file. src/loomworks/engagement/assertions.py:404 is return committed, the tail of a commit function; the file contains zero occurrences of companion_name or the literal "Companion". The site described actually exists at src/loomworks/api/routers/assertions.py:405 — id=actor.person_id, companion_name="Companion", with an explanatory comment at :393. Recorded line 404 versus actual 405; recorded directory engagement/ versus actual api/routers/. That file carries zero wasGeneratedBy occurrences, so it too has no B-29 overlap.Costed, not chosen. The choice belongs to the change request and the Operator.
Neither option is complete on its own. §2.1 established three raw-SQL paths that bypass append_event. Both options below fix only what flows through the seam. Both therefore carry the same three-site rider, which should be priced once and attached to whichever is chosen:
| Rider site | Repair | Size |
|---|---|---|
| engagement/wasderivedfrom_backfill.py:75 | was_generated_by = event_id (delete the second mint) | one line |
| engagement/redirect_edge_backfill.py:148 | "wasGeneratedBy": event_id | one line |
| persons/personal_engagement.py | none — already correct (§2.3) | zero |
Option A — append_event accepts the identifier from its caller.
Files changed. memory/events.py (signature + mint removal) plus all 83 sites across 37 files, each of which must pre-mint the id, stamp it, and pass it through.
Call signatures changed. append_event gains a required (or defaulted) event_id: UUID. Every one of the ~215 append_event references in src/ must be checked, and each of the 83 construction sites must be restructured from
provenance=Provenance(wasAttributedTo=actor, wasGeneratedBy=uuid.uuid4())
…
await append_event(engagement_id=…, object=obj, event_kind=…, actor=…, db=…)
to pre-mint a local, thread it into the Provenance(...), and pass the same local to append_event.
Tests that would need to move. Near zero on assertions (§2.4 — nothing asserts the field's value), but the 83 sites include fixture-adjacent code, and any test constructing an object then appending it must thread the id too. The real test cost is mechanical breadth, not semantic rework.
Foreclosed by §2.1? No. Serialise-before-mint is irrelevant here, because the id exists before the object is built. Frozen models are no obstacle, because provenance is constructed correctly the first time.
Precedent. Already implemented and working at persons/personal_engagement.py — _append_event(… event_id: UUID …) at :219, called correctly at :401, :426, :450 (§2.3). Option A is not speculative; it is the pattern the one correct corner of the tree already uses.
Shape. Large, mechanical, low-risk, high-diff. Touches every write path.
Option B — append_event stamps the field itself before serialising.
Files changed. memory/events.py only, for the seam itself.
The required reorder. Today the payload is serialised at :168 and the id minted at :171. Option B must invert this: mint first, rebuild the object's provenance with the real id, then serialise. Because both Provenance (base.py:144) and MemoryObject (base.py:161) are frozen=True, "rebuild" means a nested copy, not an assignment:
event_id = uuid.uuid4()
object = object.model_copy(update={
"provenance": object.provenance.model_copy(
update={"wasGeneratedBy": event_id}
)
})
payload = object.model_dump(mode="json")
Both :168's payload dump and :179's provenance dump then read the corrected object, so both JSONB columns are fixed by the one change (§2.1's note on line 179).
Call signatures changed. None.
Tests that would need to move. Effectively none on assertions. The 83 sites keep compiling and keep passing a value; that value simply stops reaching the database.
Foreclosed by §2.1? No — but §2.1 is exactly what makes it more than a one-line insert. The mint/serialise ordering must be inverted and the frozen-model copy written correctly. Note that model_copy(update=…) skips validation in Pydantic; since the update is a UUID into a UUID field this is safe, but it is worth stating in the change request.
Residue Option B leaves. All 83 sites continue to mint a UUID that is written nowhere — dead work, and actively misleading to a future reader who sees wasGeneratedBy=uuid.uuid4() and reasonably assumes it is the stored value. Whether to sweep them is a separate decision from the seam, and could be deferred or done as a mechanical follow-on.
Shape. Small, surgical, one-file, near-zero test surface — plus a decision about the 83 dead stampers.
What is common to both. Because nothing reads the field (§2.4) and nothing hashes it (§2.5), neither option can break a downstream consumer, and neither has an anchoring consequence provided it lands before FORAY integration opens at reserved_emit.py:26.
The route does not exist at a317051.
POST /me/engagements/create-from-conversation is unmounted. At src/loomworks/api/routers/me_create_engagement.py:551-565, where the @router.post(...) decorator formerly stood, there is now a fifteen-line comment beginning:
> # CR-2026-158 rider (walk-audit W-2): this handler is deliberately NOT
> # mounted as an HTTP route.
The handler function create_engagement_from_conversation remains, immediately below at :566, with its Depends(...) defaults intact (:567-573) — but with no decorator above it, FastAPI never registers it and never resolves those dependencies. Swept src/ for any remaining mount of the path: the nine surviving occurrences of the string create-from-conversation are all in docstrings, comments, and schema descriptions. No decorator anywhere carries it.
The defect's mechanism, verified independently of the commit message. W-2's diagnosis is confirmed by reading the dependency chain at a317051:
| Link | Read at |
|---|---|
| get_llm_client_with_engagement_fallback(engagement_id: UUID = Path(...), …) | src/loomworks/api/deps.py:270-271 |
| get_seed_induction_agent(llm: LLMClient = Depends(get_llm_client_with_engagement_fallback)) | src/loomworks/api/deps.py:312-313 |
| Handler: agent: Optional[SeedInductionAgent] = Depends(get_seed_induction_agent) | me_create_engagement.py:571 |
Path(...) at deps.py:271 demands a path parameter named engagement_id. The former route path — /me/engagements/create-from-conversation — has no such segment. FastAPI could only 422. The brief's statement of W-2's mechanism is accurate, and it is decidable statically, as the brief predicted.
Sibling routes checked — the defect was isolated. Four other handlers depend on get_seed_induction_agent. All four are mounted on paths that do carry the segment, so the Path(...) resolves:
| Consumer | Route path | Satisfies Path(...)? |
|---|---|---|
| api/routers/seed_commit_from_brief.py:239 | /engagements/{engagement_id}/seed/converse/commit (decorator :220) | ✅ |
| api/routers/considerations.py:504 | /engagements/{engagement_id}/considerations/{consideration_id}/close (decorator :484) | ✅ |
| api/routers/engagements.py:385 | /engagements/{engagement_id}/seed/induct (decorator :368) | ✅ |
| api/routers/seed_extraction.py:103 | /engagements/{engagement_id}/seed/extract (decorator :86) | ✅ |
The unmounted route was the sole defective consumer. B-27 does not generalise to a class.
The live terminal, verified. The deployed door-1/2 path calls the handler in process. orchestration/routers/converse.py::_call_create_engagement_from_conversation (:416-489) imports the handler directly (:449-451) and invokes it at :481-489 with all seven parameters passed explicitly:
481 return await create_engagement_from_conversation(
482 body=body, 483 request=request,
484 session=session, 485 runner=runner,
486 agent=agent, 487 person=person,
488 llm_client=llm_client,
489 )
runner is resolved override-aware from request.app.state at :472-474; agent is constructed directly as SeedInductionAgent(llm_client, agent_id=_uuid.uuid4()) at :476-480. No Depends default is ever evaluated on this path, so get_seed_induction_agent — and therefore deps.py:271's Path(...) — is never reached. The defect cannot fire.
git log --oneline 1aac815..a317051
Seven commits, all CR-2026-158:
a317051 CR-2026-158 implementation notes v0.1
c4014d6 Merge CR-2026-158 into main: Memory Registry Completeness
a425ecd CR-2026-158 Step 4 (rider): unmount the uncallable create-from-conversation route
3a4c138 CR-2026-158 Step 3: replay-heal fixtures, both shapes + regressions
c9b6075 CR-2026-158 Step 2: register all eight event types; guard green
2ae9319 CR-2026-158 Step 1: structural guard — every written event type registered
89ed67f CR-2026-158 Step 0: archive CR v0.1 + v0.2; pre-flight findings
Was the defect present at 1aac815? Yes — verified on both sides of the chain, at that commit:
git show 1aac815:src/loomworks/api/routers/me_create_engagement.py → the route was mounted; the path string appears at :553 inside a live @router.post(...).git show 1aac815:src/loomworks/api/deps.py → engagement_id: UUID = Path(...) at :271, identical to a317051.
The walk audit was right about the engine it ran against. W-2 was a true finding at 1aac815.
Exactly one commit in the range altered it: a425ecd. Its diff against me_create_engagement.py removes the fourteen-line @router.post(...) decorator block and substitutes the fifteen-line explanatory comment now at :551-565. Nothing else in the file changed; the handler body is untouched. The commit also touched tests/test_openapi_schema.py (47 lines) and tests/test_phase_55_engagement_creation_assistance.py (66 lines) — flipping the endpoint-registered test to assert unmounted, adding route-absent-from-schema coverage with sibling door-2/3 routes asserted still mounted, and converting the 409 no-personal-engagement test to in-process invocation.
Read at docs/phase-crs/cr-2026-158-memory-registry-completeness-v0_2.md (in-repo; identical copy present in the record at change-requests/cr-2026-158-memory-registry-completeness-v0_2.md). The record also holds completion-records/cr-2026-158-checkpoint-a-report-v0_1.md.
What the CR authorised. §2b item 4 (:35) posed the rider as a binary and left it open:
> "The rider's question, unchanged from v0.1: create-from-conversation abandoned (remove, history in commit message) versus intended (halt and surface). The doors-1/2 deployed path committing via /operator/converse intents is now established fact from the perimeter probe — weigh it."
§1 (:20) scoped it: "A rider removes or repairs the unsatisfiable create-from-conversation route (W-2)." Step 4 of the plan (:57) is the rider. §79 lists a halt-and-surface condition: "the rider route shows intended-future evidence."
What "resolved by removal" removed. The HTTP mount only. Verified by diff (§3.2): the decorator went, the handler stayed. a425ecd's message records the weighing — the polished OpenAPI description suggested intended-direct-use once, but the route never worked, nothing could depend on it, and the converse seam supersedes it, so "abandoned in effect, so the mount is removed rather than repaired."
Did the removal touch this route or its dependency chain? It touched the route (removed its mount) and did not touch the dependency chain at all. api/deps.py is not in a425ecd's changed-file list, and deps.py:271's Path(...) is byte-identical at 1aac815 and a317051. The chain is unrepaired and remains unusable for any future mount on a path without an {engagement_id} segment — which the in-code comment at me_create_engagement.py:563-565 explicitly records: "If a direct HTTP terminal is ever wanted again, the dependency chain must be restructured first."
The route is not callable at a317051 because it no longer exists, and its absence is the resolution rather than the defect. B-27 does not block the acceptance gate.
What resolved it, and when. CR-2026-158 Step 4 (rider), commit a425ecd, authored 2026-07-30, merged to main at c4014d6, present at a317051. Authorised by CR-2026-158 v0.2 §2b item 4, "abandoned" branch.
The anchors this verdict rests on, all static reads at a317051:
me_create_engagement.py:551-565 is a comment where the mount was; no @router.post carrying the string exists anywhere in src/.converse.py:481-489, calling the handler in-process with all seven parameters explicit.get_seed_induction_agent (deps.py:312) is never resolved on the door-1/2 path, and deps.py:271's Path(...) is never evaluated.{engagement_id} paths and are unaffected.1aac815 (route mounted at :553, Path(...) at deps.py:271), so the walk audit was correct against its own engine.Explicitly not resolved by reasoning from the browser pass. The brief forbade that route to a verdict, and this verdict does not use it. Every step above is a source read at a named line. The Operator's 2026-07-30 browser observation is consistent with this finding but is not evidence for it.
The honest boundary of the verdict. What is established statically is that the mechanism of W-2 cannot fire on the door-1/2 path at a317051. That the in-process path completes end to end — that creation actually closes — is not statically decidable and was not tested here (it needs a run, which needs a database). CR-2026-158's own Checkpoint A live verification is the evidence for that, and it is outside this session's fences. B-27's stated defect is resolved; the door closing successfully is asserted by the CR's checkpoint, not by this inspection.
One residue worth carrying. The dependency chain that caused W-2 is unrepaired. deps.py:271's Path(...) inside a dependency that any route may Depends on is a live trap for the next route mounted without an {engagement_id} segment. It bit once, silently, from birth, and the test suite could not see it because conftest overrides that dependency. This is not B-27 — B-27 is resolved — but it is a standing hazard the build list may want as its own small item.
Four routes, all in src/loomworks/api/routers/manifestations.py:
| # | Method + path | Decorator | Response model | Parameters |
|---|---|---|---|---|
| 1 | POST /engagements/{engagement_id}/manifestations/preview | :321-322 | ManifestationPreviewResponse | path engagement_id |
| 2 | POST /engagements/{engagement_id}/manifestations/derive | :396-397 | ManifestationResponse (201) | path engagement_id |
| 3 | GET /engagements/{engagement_id}/manifestations | :441-442 | ManifestationListResponse | path engagement_id; query state, limit, cursor |
| 4 | GET /engagements/{engagement_id}/manifestations/{manifestation_id} | :490-491 | ManifestationResponse | path engagement_id, manifestation_id; query version |
Against the brief's four required capabilities:
:396).version. get_manifestation_route (:499) passes version=None to load_manifestation, which then selects ORDER BY object_version DESC LIMIT 1 (engagement/manifestation.py:674-687) — the current version. Also reachable as route 3 with state='current'.:441), filterable by state (current/superseded), newest-derivation-first, keyset-paginated on (derived_at, object_id) (:466-486).
Prior position — walk-audit report §5.3 and W-16 (:346-353):
> "There is no at-version route for manifestations (routes are: manifestations, /derive, /preview, /{manifestation_id} — verified against the live OpenAPI document). Assertions have /at-version/{version} and it works correctly; manifestations do not."
Current position — the capability exists, as a query parameter rather than a path segment.
src/loomworks/api/routers/manifestations.py:502:
502 version: int | None = Query(default=None, ge=1),
threaded at :507-512 into load_manifestation(engagement_id=…, manifestation_id=…, version=version, db=session). The route's own description says so, at :494-496: "Returns a single Manifestation. By default the current version is returned; pass a version to read an earlier one."
And the loader honours it. src/loomworks/engagement/manifestation.py:662-703 branches on version:
version is None (:674) → SELECT payload FROM memory_events WHERE engagement_id AND object_id AND object_type='manifestation' ORDER BY object_version DESC LIMIT 1 (:678-684) — current.version given (:688) → the same select with AND object_version = :ver (:692-697) — version-pinned.
load_manifestation's docstring (:669-672) is explicit: "Load a Manifestation by id; version=None returns current version. Raises ManifestationNotFoundError when the id (or pinned version) is absent."
And it was already there at the walk-audit engine. git show 1aac815:src/loomworks/api/routers/manifestations.py shows the identical version: int | None = Query(default=None, ge=1) on the same route. W-16 was wrong when it was filed, not resolved since.
What W-16 got right, preserved without smoothing. Its route enumeration is exactly correct — there are four routes and none is named at-version. Its method is what failed: it read the OpenAPI document for a route matching the assertion-side path-segment shape (/at-version/{version}), found none, and concluded the capability was absent. The capability was there as a query parameter on route 4, where an OpenAPI reader looking for a path would not see it. The walk itself confirms the mechanism of the miss: W-16 records that the call "returned version: 2, state: superseded, not the v1 the shape was built from" — which is exactly what route 4 returns when ?version= is omitted. The pin was available and unused.
Consequence for B-5. The Manifestation hop of a provenance walk can be version-pinned today, via GET /engagements/{eid}/manifestations/{mid}?version=1. B-5 needs no new route for this, and the walk-audit break list loses one entry. W-16 should be struck, not scheduled.
The assertion-side pattern, read as instructed. GET /engagements/{engagement_id}/assertions/{assertion_id}/at-version/{version} at api/routers/assertions.py:774-799, with version: int = Path(..., ge=1) at :788 and a delegation to get_assertion_at_version at :792-797. The two sides differ only in shape — path segment versus query parameter — not in capability. If the Operator wants surface consistency across the two, that is a cosmetic alignment (add a path-segment alias to manifestations), not a missing feature. Sizing it as such at §4.4.
One genuine asymmetry, which W-16 did not name. Assertions carry GET …/assertions/{assertion_id}/history (assertions.py:748), returning every version. Manifestations have no per-object history route; route 3 lists an engagement's Manifestations by state, which is a different axis. Whether the room needs per-object history is a B-5 design question, not a defect.
ManifestationResponse, read field by field at src/loomworks/api/schemas.py:6400-6481:
| Field | Line | Type |
|---|---|---|
| manifestation_id | 6411 | UUID |
| version | 6416 | int |
| engagement_version_at_derivation | 6421 | int |
| derived_by | 6429 | ActorRefResponse |
| derived_at | 6434 | datetime |
| assertion_count | 6439 | int |
| relationship_count | 6444 | int |
| seed_version | 6449 | int |
| state | 6454 | Literal["current","superseded"] |
| superseded_by_ref | 6464 | MemoryRefSchema \| None |
| organized_groups | 6472 | ManifestationOrderingSchema \| None |
Against the brief's five questions:
organized_groups (:6472) is a ManifestationOrderingSchema (:6310), which carries rationale: str (:6318) and groups: list[ManifestationGroupSchema] (:6326). Each ManifestationGroupSchema (:6293) carries label: str (:6295) and assertion_refs: list[AssertionRefSchema] (:6300) — described at :6304-6305 as "each by identifier and version." References, not text. assertion_count and relationship_count are integers. The room cannot render assertion content from this response; it must fetch each assertion separately. Nullable, too — None for pre-Phase-19 Manifestations (:6472, :6408-6409).version (:6416).engagement_version_at_derivation (:6421), described at :6425-6426 as "the engagement's version at the moment this manifestation was derived — the point in Memory's growth it captures." Plus seed_version (:6449).derived_at (:6434).engagement_version_at_derivation + seed_version + derived_by + the assertion_refs inside organized_groups together name it. There is no single explicit "derived from" ref list, and when organized_groups is null nothing enumerates the sources — only counts remain.The state-contract test — both versions, and from how many calls.
The brief requires that a populated room value carry both its derivation version and the current version of its source, so staleness is computable rather than optional.
ManifestationResponse alone: one of the two. It carries engagement_version_at_derivation but not the engagement's current version. Sweeping api/schemas.py for current_engagement_version returns exactly one hit, :6908 — and it is not in ManifestationResponse.It is a reusable engine read. A wire, not a build.
GET /engagements/{engagement_id}/memory-status — route at src/loomworks/api/routers/memory_status.py:45, handler memory_status_route at :54-61, response model MemoryStatusResponse at api/schemas.py:6863, implementation get_memory_status at src/loomworks/engagement/memory_status.py:66. Phase 12 §7; the module header (:1-6) names it.
MemoryStatusResponse carries, in one response:
| Field | Line | What it gives the room |
|---|---|---|
| has_manifestation | 6870 | whether the room has a value at all |
| manifestation_id | 6875 | which Manifestation |
| manifestation_version | 6883 | its own version |
| manifestation_derived_at | 6891 | derivation timestamp |
| manifestation_engagement_version | 6899 | the derivation version |
| current_engagement_version | 6908 | the source's current version |
| most_recent_change_at | 6916 | when Memory last moved |
| memory_has_changed | 6925 | the staleness boolean |
| changes_since_derivation | 6935 | MemoryChangeCounts breakdown |
| summary | 6942 | plain-language status string |
Both halves of the state contract arrive in one call, at :6899 and :6908. 6908's own description states the relationship: "The engagement's version now — how far Memory has moved since the Manifestation was derived."
How the twenty-five was computed. current_engagement_version − manifestation_engagement_version, both from this endpoint. The implementation reads them at engagement/memory_status.py:79-85 (SELECT current_engagement_version FROM engagements) and :98-114 (SELECT object_id, version, engagement_version_at_derivation, derived_at FROM manifestation_view WHERE state='current'). No hand arithmetic across two separate reads is required, and no one-off investigation query is needed — the endpoint predates CR-2026-158 by many phases.
I did not read the artifact that produced the literal figure 25 for E0005. Manifest Entry 126 records the number; reproducing it needs a query against playground_dev, which is fenced. What is established here is the capability: a standing engine read returns both operands in one call, so B-5's staleness marker is wiring an existing endpoint into the room, not building a computation. That is the decision §5.3 asked for.
One subtlety the change request must not miss. memory_has_changed is not a raw version comparison. engagement/memory_status.py:135-136 records the design decision inline:
> # CR-D2: memory_has_changed is driven by whether any Memory-content
> # category is non-zero — NOT by raw version comparison.
So "25 versions behind" and "Memory has changed" are two different measures. An engagement's version counter advances on events that are not Memory content, so a Manifestation can be N versions behind with memory_has_changed=false. If the room shows a version delta, it will sometimes show a non-zero number next to a "current" flag. The room must choose which it displays, or display both with the distinction made plain. This is a B-5 design question surfaced here, not resolved here.
The engine is much closer to what the room needs than the evidence base suggested. Three of the four capabilities the brief listed exist; the fourth (version-pinned retrieval) also exists and W-16 was wrong. The state contract is satisfiable from one call. What remains:
| Gap | Size | Migration implicated? |
|---|---|---|
| Organized content is refs, not text. The room must fan out from assertion_refs (schemas.py:6300) to fetch each assertion's content. | Either the room does N+1 fetches (no engine work), or the engine grows a content-inlining option on route 4 — one response-schema addition plus a join in load_manifestation (engagement/manifestation.py:662). Small. | No — the refs carry id + version; the content is already in memory_events. |
| No per-object Manifestation history route. Assertions have /history (assertions.py:748); Manifestations do not. | One route, mirroring the assertion pattern; the loader already selects by object_version. Small, and only if the room needs it — an open design question. | No. |
| Surface-shape inconsistency. Assertions pin by path segment (/at-version/{version}); Manifestations pin by query parameter (?version=). | Cosmetic. A path-segment alias delegating to the same load_manifestation call. Very small, and arguably should not be done — two shapes for one capability is worse than one unfamiliar shape. Recommend leaving as is and correcting the walk-audit record instead. | No. |
| Two staleness measures, one room. Version delta vs. memory_has_changed (§4.3). | Not engine work. A room-design decision about what to display. Zero engine change if the room consumes memory-status as designed. | No. |
| organized_groups is nullable for pre-Phase-19 Manifestations (schemas.py:6472). | The room needs an empty-state for older Manifestations. Not engine work unless a backfill is wanted. | Only if a backfill is chosen — not proposed here. |
Overall size: small. No migration is implicated by any item. No new table, no new column, no schema change to memory_events. The largest candidate — content inlining — is one optional response field and one join, and may not be needed at all if the room fans out.
No change is proposed and none is drafted, per the brief's instruction.
Per §7 of the brief and the Operator's 2026-07-31 ruling: any engine work question three finds queues behind B-29 rather than opening a second engine front. Nothing above is urgent, nothing above blocks the acceptance gate, and the largest item is optional. The engine belongs to B-29 for the duration.
Untouched by this session and still owed to change request A's Step 0: the Operator Layer side — the real shape of the room components and their current data fetching, and the RoomView.tsx vocabulary wall against the seed's plain-terms discipline. Both are reads in /Users/dunin7/loomworks, a different repository, and need their own brief.
In scope, both read:
| Number | Value | Read from |
|---|---|---|
| Alembic head (declared in the migrations directory on main at a317051) | 0102 — migrations/versions/0102_graph_stage_2_dependents_index.py. Single head, 102 revisions total. | Full revision/down_revision graph walk over migrations/versions/*.py; exactly one revision is named by no other revision's down_revision. |
| stele repository front | main at 689de41 (689de4192f68383edac2d0590ffa7ce195393e5c); tag v0.4.0 points at HEAD; working tree clean. | git rev-parse HEAD, git tag --points-at HEAD, git status --porcelain in /Users/dunin7/stele |
Excluded, and still owed — the manifest's residue stays open. This is a partial refresh and must not be read as a whole one. Manifest v0.76 §5's residue remains open for:
playground_dev — needs a database connection. Fenced. Note that 0102 above is the head declared in the repository; whether the live database is stamped at it is a different fact and is unread.99f7f64 — different repository, and they need a run. Not attempted.All three remain as they stood before this session.
The brief's two, carried in as instructed, plus four this session overturned.
1. Parallel tracks within the engine.
Prior — completion scoping note v0.2 §4 and §5: "CR-B may run in parallel with CR-A — different repository, no shared surface," and "three fronts can move at once without collision, provided the record-writing discipline holds."
Current — Operator ruling 2026-07-31: the parallel-track claim holds across repositories but not within the engine. One engine front at a time, B-29 first. The note's §5 sequencing diagram is superseded and owes an amendment at its next bump.
This session supplies concrete evidence for the ruling. §2.6 found three sites — seed_extraction.py:258-259, seed_commit_from_brief.py:305-306, me_create_engagement.py:841-842 — where B-25's actor variable and B-29's fabricated field are the two keyword arguments of a single Provenance(...) expression on consecutive lines. The collision is at the line level, not the merge level. The ruling is not merely prudent; it is required.
2. The three sites said to be correct.
Prior — B-25 sizing findings v0.1 §5: three sites in persons/personal_engagement.py "do it correctly," and "three sites prove the field means what its comment says."
Prior — this brief §3.3 and §8: unsettled; the findings may have inferred the semantics from what the sites thread rather than from what the field means at the moment of the append; if they pass a causing event the eighty-three-against-three framing loses its anchor.
Current — the sizing findings were right; the brief's doubt is overturned. Each identifier is pre-minted (:356-358), stamped into provenance (:83, :150, :193), and passed as that same row's event_id to the insert (:401, :426, :450, binding at :255, column at :241). It is the event this append produces, not a prior cause. The field has one live meaning in the tree. The framing holds. The variable names — seed_event_id, cycle_event_id — are what invited the misreading, in both documents.
3. The target tag's position.
Prior — this brief, Target line: "/Users/dunin7/loomworks-engine at main a317051, annotated tag registry-completeness-v0_1."
Current: the tag points at c4014d6, the merge commit; a317051 is one commit later (git describe → registry-completeness-v0_1-1-ga317051). The two differ only in documentation, so no finding here turns on it, but the manifest should carry the tag's real position.
4. "It is one seam."
Prior — B-25 sizing findings v0.1 §5: "the repair is not eighty-three corrections. It is one seam: either the append accepts the identifier from its caller, or it stamps the field itself before serialising. One change, eighty-three sites healed."
Current — it is one seam plus three bypasses. persons/personal_engagement.py:241, engagement/wasderivedfrom_backfill.py:121, and engagement/redirect_edge_backfill.py:185 each INSERT INTO memory_events directly, never calling append_event. A seam-only change heals neither backfill. The brief anticipated this exactly — §3.1's "If such a path exists, the seam is not one seam" — and the answer is that it does, three times. The two backfills need one-line repairs alongside whichever seam option is chosen; personal_engagement.py needs none, being already correct.
5. The file count for B-29.
Prior — B-25 sizing findings v0.1 §5: "83 sites … 36 files."
Current: 83 sites across 37 files. The 83 is exactly right. The missed file is engagement/wasderivedfrom_backfill.py, whose fabrication routes through a local variable (:75 → :84) and so escapes the direct-stamp grep — the same module the findings separately noted as "also fabricating," so it was seen and simply not counted.
6. Walk-audit W-16.
Prior — walk-audit report §5.3 and W-16: "There is no at-version route for manifestations… Assertions have /at-version/{version} and it works correctly; manifestations do not." Recorded as BROKEN, LEAVES-COMPANION.
Current — overturned. The capability exists and predates the walk. manifestations.py:502 declares version: int | None = Query(default=None, ge=1) on route 4, threaded into load_manifestation (:507-512), which branches to a version-pinned select at engagement/manifestation.py:688-703. Present identically at 1aac815, the walk-audit engine — so W-16 was wrong when filed, not resolved since. Its route enumeration was correct; its conclusion was not. The walk looked for the assertion-side path-segment shape and did not find it, because the pin lives as a query parameter. W-16's own transcript confirms the mechanism of the miss: the call returned version: 2, state: superseded — exactly what route 4 returns when ?version= is omitted. W-16 should be struck from the break list, not scheduled.
7. A recorded line that does not resolve, and a recorded file that is wrong.
Prior — B-25 sizing findings v0.1, recorded at a317051: site 11 at orchestration/routers/converse.py:690; the hardcoded companion name at engagement/assertions.py:404.
Current: converse.py:690 is engagement_id = body.project_id or _current_engagement_id, not an authorship construction; the file's only actor constructions are :1307 and :1378, and :1378 is already site 10c. Which of these site 11 meant is unread. The companion-name site is in the wrong file: engagement/assertions.py contains zero occurrences of companion_name or "Companion"; the real site is api/routers/assertions.py:405 — id=actor.person_id, companion_name="Companion". Recorded 404 vs. actual 405, and engagement/ vs. api/routers/. Neither file carries any wasGeneratedBy, so neither has a B-29 overlap.
Every question or sub-question that could not be settled inside the fences, and what would settle each.
| # | What is unread | Why | What would settle it |
|---|---|---|---|
| 1 | Whether the door-1/2 in-process path completes end to end — that creation actually closes, not merely that W-2's mechanism cannot fire. | Needs a run against a database. Fenced. | CR-2026-158's Checkpoint A live verification (completion-records/cr-2026-158-checkpoint-a-report-v0_1.md), or a walk on a spare port against playground_walkaudit. §3.4's verdict does not depend on this — it resolves the stated defect only. |
| 2 | B-25 site 11's intended location (converse.py:690 resolves to unrelated code). | The sizing session's raw sweep output is not in this repository. | The sizing session's own transcript, or a fresh authorship sweep of converse.py for B-25's Step 0. |
| 3 | The literal figure "25 versions stale" for E0005. | Needs a query against playground_dev. Fenced. | A memory-status call against the serving perimeter, or the CR-2026-158 investigation's own output. The capability question §5.3 asked is settled (§4.3) — only the number is unread. |
| 4 | The alembic head stamped on playground_dev. | Needs a database connection. Fenced. | alembic current against playground_dev. The repository-declared head is 0102 (§5). |
| 5 | Engine suite pass / skip / fail counts. | A suite run connects to a database. Fenced. | A suite run. Manifest v0.76 §5's residue stays open. |
| 6 | Operator Layer test and route counts at 99f7f64. | Different repository, and they need a run. Out of this brief's scope. | The Operator Layer brief that §5.5 says is still owed. |
| 7 | How many live records already carry a fabricated wasGeneratedBy. | Needs a query against the serving perimeter. Explicitly out of scope per brief §10. | Parked as a decision of a different kind, per the brief. |
| 8 | Whether the room needs per-object Manifestation history (§4.4). | A design question, not a read. | B-5's own design conversation. |
| 9 | Whether the :8000 perimeter is serving a317051. | No live call permitted. | Taken as given from manifest Entry 126, per brief §1. Not independently verified here. |
loomworks-record.playground_dev, not playground_test, not playground_walkaudit.:8000 or any other perimeter. M uv.lock was found and left.DUNIN7 — Done In Seven LLC — Miami, Florida Loomworks — combined engine inspection findings — B-29, B-27, B-5 — v0.1 — 2026-07-31 Read-only. One engine session. B-27 resolved; W-16 overturned; the seam is not one seam; four corrections filed.