Version: v0.4
Date: 2026-08-20
Status: Scoping note. Not a change request. Step 0 has run, FORAY has answered §9, and §10a is ruled. The R-2 CR may now be drafted.
Changes from v0.3: §9 replaced — FORAY answered all three questions and the section is now answers rather than questions. Validation Scope R2 is ruled PERMIT and Stage 6's structural ceiling is gone (§9.1). E7 and E9 have narrowed and their current definitions are recorded (§9.2). A ninth constraint added at §5 — permitting a reference is not verifying it (§5.9). §10a recorded as ruled with its dispositions (§10a). The mapping citation repointed to v0.3. v0.1–v0.3 stand as siblings; v0.1's wrong §7 premise stays preserved at §7a.
Scopes: Stage 2 of loomworks-foray-coverage-resolution-proposal-v0_3 §10 — R-2, ruled at D3.
Grounded on: the Step 0 census at engine main e951e60c, 2026-08-20; foray-answers-to-r2-note-section9-v0_2; foray-response-to-loomworks-census-v0_1; loomworks-foray-coverage-resolution-proposal-v0_3; loomworks-foray-mapping-v0_3; foray-api-reference-for-loomworks-v0_1.
Being passed to: the FORAY project — for information only. Nothing in this version asks FORAY anything. FORAY has answered nothing in §9 yet; those questions stand.
R-2 is the smallest real piece of FORAY work Loomworks has, and everything else waits behind it. At sixteen places in the engine, a marker sits where a FORAY record will one day be built. At most of them the useful facts — who did it, which engagement, which grant, which proposal — are sitting right there in the function, often passed into Loomworks' own audit write one line above, and then dropped. Closing the forward gaps means forwarding them. Nothing transmits, nothing anchors, nothing changes in production.
The inspection this note required has now run, and it corrected the note. Sixteen sites is still exactly right — no work since the mapping added any. What v0.1 predicted would be found was not there, and §7a says so plainly rather than quietly dropping the claim.
The inspection did find something larger, which no amount of reading the mapping would have surfaced. Memory's site forwards two things, one of which is a priority hint looked up in a registry. That registry has twenty-nine entries. Forty-nine event kinds that are actually written have no entry at all — so for most of what Memory records, the hint resolves to nothing. Fourteen entries point the other way, naming events never written anywhere. These two are not equally serious. Dead entries are visible clutter. Unregistered live kinds are a silent gap, which is the shape that survives unnoticed. Two of the dead entries do have live tails worth fixing: one is still being filtered for in a database query that therefore returns nothing, and another names an event that lands in a different table entirely.
And the census answered a question nobody asked it. Three of the seed's four rooms — Manifestation, Shaping, Rendering — were understood to have no FORAY presence at all. They have no dedicated call site, which is what was checked. But their work is already flowing through Memory's site today, unregistered and unprioritised. So closing that one site's gap is worth more than a payload fix: it is the only path those three rooms currently have.
Three other things travel in the same stage: a confirmed ordering bug at the suspension site, now proven rather than suspected; the same bug a second time in a four-row loop; and two settings sites carrying byte-identical emit code that should share one builder.
Two contract changes still need FORAY's word, unchanged from v0.1: the anchor endpoint gained a duplicate check, and the validator appears to have stopped rejecting references that point outside a record — which, if confirmed, removes the ceiling the cross-record question has been resting on. Those are §8 and §9.
From the resolution proposal's brief: the forward-gap sites are closed, or a reason is stated for each. Ruled close them all at D3.
A forward gap is not missing data. It is data that exists, in scope, at the call site, and does not reach the emit payload. The proposal's words: in every case the identifier is in scope at the call site, and at the settings sites it is passed into Loomworks' own audit write one line above the FORAY emit and then dropped.
Why this stage is first: it touches no new room, changes no architecture, is blocked by nothing, and it is the prerequisite for any cross-record linkage. A reference cannot be built out of a value the emit never received.
For each site: pass the in-scope values into _foray_reserved_emit's payload dict. That is the whole change at most sites.
What it does not mean. No emitter is built. No network call is made. _foray_reserved_emit remains a no-op stub under its # FORAY_RESERVED_LOCATION marker. No record is constructed, validated, or anchored. Production behaviour does not change anywhere — the payload argument is passed to a function that discards it.
Why do it before there is an emitter. The payload argument is the specification of what each site can attest. Building the emitter against sites that drop their identifiers would produce records that validate and say nothing, and the fix would then be a change to every call site instead of one module.
Confirmed current at e951e60c. Seven of the eight host files are unchanged since the mapping was taken. The eighth, memory/events.py, was touched by CR-2026-226, but the diff widens a database check constraint only — the emit call and its payload are byte-identical. One refinement, at §3b.
Line numbers are deliberately not reproduced; the CR pins them at drafting time.
credit/flows.py, credit/room_consumption.py, credit/proposal_applier.py
| # | Event kind | In scope and dropped |
|---|---|---|
| 1 | credit.issuance | grant_id — which grant this issuance claims |
| 2 | credit.provisioning | authorized_by — the operator identity, when supplied |
| 3 | credit.consumption_token (×4) | engagement_id |
| 4 | credit.consumption_credit | engagement_id |
| 5 | credit.suspension | requested_by, expires_at |
| 6 | credit.reactivation | auth_method |
| 7 | credit.deletion | deletion_kind |
| 8 | credit.balance_zeroing (0–3×) | Nothing dropped; payload is complete for what the site holds |
| 9 | credit.referral_credit | converted_person_id — which conversion this credit rewards |
| 10 | credit.consumption_token / _credit (×3) | engagement_id, room |
| 11 | credit.corrective | proposal_id, shape_event_id, applied_by |
Site 11 is the one to read twice. proposal_id is the thing the correction corrects, and it reaches neither the payload nor the metadata dict. Forwarding it does not by itself produce a reference — a single-Action record has no component for an F21.ref to resolve against. Forward it anyway: it makes the value present for whatever linkage mechanism is eventually ruled. Do not let R-2 be scoped into designing that mechanism.
Site 9 is unreached code. write_referral_credit_flow has no callers. Close its gap anyway — it is live, correct, and will fire when Phase 48 wires a caller.
| # | Event kind | In scope and dropped |
|---|---|---|
| 12 | memory.<event_kind> | object.id, object.object_type, engagement_id, actor.id, actor.kind — payload carries only event_id and anchor_priority |
| 13 | audit.setting_change (enum) | person_id, engagement_id, current, committed |
| 14 | audit.setting_change (numeric) | person_id, engagement_id, current_f, committed |
| 15 | conversation.turn | person_id, engagement_id, actor.id — payload carries only turn_id and actor_kind |
| 16 | audit.setting_change (button) | person.id from the authenticated principal, previous, normalized |
Site 12 is the largest gap in the set, and §4d makes it larger than the mapping recorded. It is Memory's single write path, carrying every memory event kind. The object id, engagement id, actor id and actor kind are all present, and none reach the payload — which is exactly why the mapping had to put the stated placeholder "UNATTRIBUTED" in F3. Closing this gap is what makes a Memory record able to name its own subject.
Refinement from the census: actor.kind at site 12 now ranges over five values — person, companion, contributor, agent, system — not four. CR-2026-226 added system. None of them reaches the payload, so the gap is unchanged in kind and one value wider in range.
Sites 13, 14 and 16 share one shape three times independently: the identity is passed to Loomworks' own audit write one line above and then not passed to the emit. Worth stating as one finding rather than three coincidences.
New in v0.3, from FORAY's response to the census.
foray-loomworks-audit-coverage-requirement-v0_1 — the document that opened this arc — states that three of the seed's four rooms have no FORAY presence. FORAY has withdrawn that statement. The search behind it looked for call sites, correctly found none in any Manifestation-, Shaping- or Rendering-named module, and drew a conclusion the search could not support.
What the census shows: render_produced, shape_produced, composition_ and shaping_ are live append_event kinds today, flowing through site 12, unregistered, with _ANCHOR_PRIORITY.get() returning None for each.
What this changes for R-2. It does not add work — site 12's gap was already the largest in the set and already in scope. It changes what closing it is worth. A Rendering act reaching FORAY through Memory's generic write path, in a payload carrying neither the object it produced nor the engagement it belongs to, is the current state of three rooms' attestability. Stage 3's dedicated Rendering sites remain the right destination; until they exist, site 12 is not a placeholder for those rooms, it is their only path.
What it does not license. Site 12 still emits under memory.<event_kind>, and R-2 does not re-badge Memory's write path as a Rendering site or invent room-specific emission there. Forward what is in scope; leave the room postures to Stages 3–5.
4a. The suspension-site ordering defect (D8) — now confirmed, not suspected. write_suspension_flow calls db.add(flow), emits, then await db.flush(). Its siblings flush before emitting. The mapping could not confirm whether flow.id was populated because it depended on the primary-key default. The census read it:
id: Mapped[UUID] = mapped_column(PG_UUID(as_uuid=True), primary_key=True,
server_default=text("gen_random_uuid()"))
Server-side. SQLAlchemy has no visibility into a server default before flush, so a pending row's .id is unpopulated. This is a confirmed defect on every call, not an ordering inconsistency. Fix the ordering to match the siblings. It is a real bug independent of FORAY and should be fixed on those grounds.
4b. The duplicated settings emit construction. Sites 13 and 14 carry byte-identical emit calls in two independent functions. room_consumption.py's _emit helper is the pattern. A shared payload builder, not a centralised emit — canon Principle 1 is one emit per business event. The helper builds the dict; each site still calls the emit.
4c. Site 3's flush timing — the same defect, second instance. Four rows are db.add-ed and the emit loop reads row.id before any flush. The same server-side default applies, so this is 4a again rather than a benign variant. Fix both.
4d. The anchor-priority registry has drifted from what Loomworks actually writes. New in v0.2; not visible from the mapping, only from the census.
_ANCHOR_PRIORITY holds 29 entries. Site 12 forwards _ANCHOR_PRIORITY.get(event_kind) as one of its two payload values. Against the 65 distinct event kinds actually passed to append_event across 94 call sites:
| | Count | Consequence |
|---|---|---|
| Live event kinds with no registry entry | 49 | anchor_priority resolves to None for most of what Memory records |
| Registry entries with no write anywhere | 14 | Dead entries; the hint can never fire for them |
So one of site 12's two forwarded values is empty for the majority of real writes. That reframes the site: not merely a thin payload, but a thin payload whose non-identifier half usually says nothing.
The two directions are not equally consequential, and v0.2 was wrong to weigh them evenly. FORAY's response names the asymmetry and it is correct:
The exceptions are the two dead entries with live tails, which are ordinary bugs and not tidiness:
Two dead entries have live tails and are ordinary bugs:
engagement_committed_divergent is never written. Both commit_engagement and commit_engagement_divergent write the literal event_kind="engagement_committed"; divergence is recorded as an is_divergent boolean on the same event. The registry entry is dead and a raw-SQL read query still filters for the kind that is never written — so that query returns nothing, silently, by construction.membership_created names an event written by a different function into MigrationEventRow. It never touches MemoryEventRow or site 12 at all.
Six of the fourteen dead entries are deliberate and correct: agent_session_minted, agent_session_closed, agent_session_refused, agent_identity_minted, agent_identity_frozen, agent_session_silent. These were pre-registered under standing obligation O7, which says new significant events get their seam while the emitter sleeps. They are not drift and must not be cleaned up. The remaining six — agent_registered, induction_cycle_recorded, candidate_engagement_discarded, contribution_credential_issued, contribution_credential_claimed, contribution_credential_revoked — need a disposition each: pre-registered like the O7 six, or genuinely dead.
Scope for R-2, ordered by the asymmetry above:
engagement_committed_divergent query, and the membership_created entry.Do not mass-add entries for all 49. Assigning anchor priority to every memory event decides what deserves anchoring, which is room-posture work at Stages 3–5. FORAY's response is explicit that it takes no position on whether every live kind needs an entry — what that registry is for is a Loomworks design question, and R-2 is not the place it gets answered. R-2's business is that the registry stops lying about what exists, and that the gap is visible rather than assumed away.
Constraints, not choices; each already ruled or forced.
_foray_reserved_emit do anything.proposal_id at site 11, the site 3/4 token-to-credit relationship, the FIFO lot linkage at site 10 — forward the value and state the wish. The mechanism is Stage 6.PROVIDER: scheme break at sites 3 and 10 stands. Provider token assets are genuinely Anthropic-denominated; claiming LOOMWORKS: over them would be false. Interacts with D14, unruled — see §10.N4 marks the trail leaves FORAY's holdings here — information for a reader, not a verification result. Nothing in Loomworks may present a cross-record reference as a verified link. Same discipline as D11's rule against saying "anchored" on a 200.E7 no longer means what the older documents say it means. Current definitions are at §9.2. loomworks-foray-mapping-v0_3 supersedes v0.2 on this point; any CR citing reference-validation behaviour cites v0.3.The proposal calls Stage 2 small enough for a single change request. With the census in, that holds. The site set did not grow, the gap closures are payload-dict additions across three files, and 4a/4c are a two-line ordering fix each.
§4d is the one item that could grow it. The reconciliation itself is small; the six undisposed entries need a ruling each, and the engagement_committed_divergent query fix touches read behaviour rather than emit behaviour. If drafting finds that fix reaching further than expected, split it out rather than letting it swell the CR.
Run read-only at engine main e951e60c on 2026-08-20. Findings are folded into §3 and §4 above rather than repeated. The headline results:
CreditFlowRow.id is server-side defaulted, confirming 4a and making 4c a second instance.v0.1 asserted that engine work since the mapping had added FORAY call sites, and instructed the inspection to report them separately on the grounds that their payloads had never been screened. No such sites exist. The census found zero new call sites under any label.
The prior position and the correction, both preserved:
The inference was reasonable and it was still wrong, and the mechanism it missed is worth keeping: a dynamic dispatch site absorbs new event kinds without producing new call sites. A census that counts call sites will therefore under-report growth in what is being emitted. §4d is what that growth looks like when you go and check.
v0.1 also used the label "H0" in an instruction aimed at a reader with no exposure to it — it is Boundaries-arc vocabulary and appears nowhere in the engine. Naming conventions do not travel between arcs by themselves.
Worth stating once because it has now produced an error in each project.
| | The reasoning | The error | |---|---|---| | Loomworks (v0.1 §7) | New significant events happened, O7 requires call sites for them, so new call sites exist | Over-counted. Predicted sites that were never added | | FORAY (audit-coverage v0.1) | No call site exists in any Manifestation, Shaping or Rendering module, so those rooms have no FORAY presence | Under-counted. Missed activity already flowing through an existing site |
Both projects treated a dynamic dispatch site as if it were static. Site 12 emits memory.<event_kind> for whatever kind it is handed. New kinds arrive at it without new code; existing kinds route through it without naming their room. So:
The general form: where dispatch is dynamic, the census must be of the values dispatched on, not the places dispatch occurs. Neither project's search was careless — both asked a precise question and got a correct answer to it. The question was the wrong shape.
Unchanged from v0.1 and v0.2, and not yet answered. The resolution proposal's §8 tabulated the anchor contract as of 2026-08-18; foray-api-reference-for-loomworks-v0_1, dated 2026-08-20 and verified against live source the same day, contradicts it in one row.
8a. Idempotency — the proposal's §8 row is now wrong.
| | Proposal §8 (2026-08-18) | API reference (2026-08-20) |
|---|---|---|
| Idempotency | None. Two identical submissions anchor twice | 409 when SHA-256 of the submitted bytes matches an already-anchored body. Byte-identical only |
What this changes and what it does not. A byte-identical resubmission is now caught and spends nothing — a real improvement that removes the worst version of the double-anchor risk.
It does not rescind the no-retry rule. The check is byte-identical only, and Loomworks constructs an emit-time timestamp at most sites precisely because no source timestamp exists, so byte-stability across a retry is not something Loomworks can guarantee. And the 409 withholds leafSalt, jobId and fee, so a retry that is caught still does not recover the custody package the lost response carried.
Proposal §9 constraints 1–3 stand, and D11/D12 stand as leaned. One narrowing: constraint 2's rationale — a retry guarantees a second independent anchor — becomes a retry either recovers nothing or creates a second anchor, and Loomworks cannot tell in advance which.
8b. Material the proposal did not have.
E1–E5, E7–E12, E14; warning W3; notes N1–N4. E6 and E13 do not exist in 4.2.GET /api/anchor/{txHash} proves retention only — verbatim bytes, never leafSalt, jobId or fee, none of which are stored server-side. Not a custody-recovery route. Its 404 is ambiguous between absent, expired and mode-off, by design.404 on both anchor routes is checked before auth. Proposal §9 constraint 7 anticipated this correctly.POST /api/wp2-anchor-test was removed on 2026-08-20: live, unauthenticated, sharing the signing path with no dedupe, and confirmed to have caused one production incident before removal. Recorded so nobody builds against it.
v0.3 asked three questions. foray-answers-to-r2-note-section9-v0_2 answers all three. This section is now the answers.
N4 is the ruling, not a separate change. R2 is closed.
Provenance, as FORAY gave it: Validation Scope decision sheet v0.4 ruled R2 PERMIT on 2026-08-20 — F22–F24 may reference records outside this one, per Root §3.1's admission test, which routes a link to an authority, commitment or prior event to the reference arrays. Brief 26 implemented it in the validator the same day; N4 is that implementation. Brief 30 corrected the Verifier Specification and the wire skill, both of which still described the old behaviour.
A record may now carry a reference to something FORAY does not hold and remain valid. That was the whole substance of R2.
The consequence for the proposal's §7. Its observation — that under a single-Action record an in-record reference cannot exist at all — still holds and is still true. But it is no longer a ceiling, because the reference no longer has to be in-record. FORAY's own phrasing is the one to keep: what was structurally impossible is now merely unbuilt.
What this changes for R-2: nothing about the work, everything about what it feeds. Forwarding proposal_id at site 11 stops being a value parked against a mechanism that might one day exist and becomes direct input to one that does. Stage 6 is unblocked; R-2 still does not design it (§5.5 stands).
E7 and E9 have narrowed — current definitionsConfirmed by FORAY at source on 2026-08-20:
| Code | Fires when |
|---|---|
| E7 | A reference entry is not a string. Structural malformation only. No longer fires on non-resolution of any kind. |
| E9 | An F21.ref resolves to a component present in the record and the declared ref_type contradicts that component's actual kind. Internal incoherence, not a resolution failure. |
| N4 | Any well-formed reference — F22–F24 or F21.ref — that does not resolve within the record. Not an error; the record stays valid. |
On which document was wrong — FORAY corrected its own answer, and the correction is worth carrying. FORAY's v0.1 reported that loomworks-foray-mapping-v0_2 was wrong, having attributed the in-record constraint to E7, and called it the third instance of a documentation-drift pattern on FORAY's side. Its v0.2 withdraws both claims: the mapping contained two imprecise descriptive asides in a document that was substantively right — it never claimed references must be in-record, its own correction notice already stated Brief 26's change correctly, and no record in the set carries any reference for the distinction to bear on. The drift pattern was real at two instances, not three.
FORAY's stated reason for sending the correction rather than folding it in quietly: inflating a count of one's own failures is its own kind of inaccuracy. Recorded here because it is a discipline this arc should hold in both directions — over-reporting your own errors reads as rigour and is not.
loomworks-foray-mapping-v0_3 carries the two tightened phrases; all sixteen site sections are byte-identical. This note now cites v0.3. v0.2 stands as sibling.
F4 contribution is accepted, and the catalog was built for itFORAY accepts the 65 kinds. Context that changes how the contribution lands:
F4 (component type) and F26 (transaction type) were both ruled closed on 2026-08-20 — catalog-governed, snake_case canonical, assembled from FORAY's example generator, the sixteen mapping sites, and the conformance corpus. Initial, not final. The ruling adopted a light extension mechanism for exactly this case: a new engagement proposes values in its field-map sheet (Root §8.2, already required), and FORAY accepts by pattern. No amendment cycle.
So the 65 kinds are not a late addition to a closed list — they are the first exercise of the extension mechanism the ruling was designed around.
Two things that follow, both Loomworks' work and neither R-2's:
F4. F4 names what a component is within a record; F26 names what the transaction as a whole is. A Loomworks event kind may map to either, both, or — for kinds that never become FORAY records — neither. Sorting that is mapping work, and FORAY has no view on where any kind lands. It belongs with the room postures at Stages 3–5, not here.
Both ruled by the Operator on 2026-08-20 and filed to loomworks-record/foray-reference/. Recorded here so the CR inherits them rather than re-deriving them.
Ruling 1 — the six undisposed registry entries. Not treated as a block; they did not turn out to be one thing.
| Entry | Disposition | Why |
|---|---|---|
| agent_registered | Retire, superseded | W1 pre-registered agent_identity_minted for the same event under the current identity model. This is the Phase 62 name, predating the design that settled it. The supersession is recorded beside it; it is not silently deleted. |
| induction_cycle_recorded | Rename entry to induction_cycle | A live writer exists at two call sites under the shorter name, and that string is already written into memory_events rows. The recorded fact does not move; the registry moves to match it. The writer is not renamed. |
| candidate_engagement_discarded | Held, undisposed | Its disposition follows the memory-deletion design question below and must not be settled before it. |
| contribution_credential_issued / _claimed / _revoked | Keep all three, annotated | Removing them would erase the only evidence that anyone judged these events significant. Annotated with the fact that the lifecycle writes via raw SQL into its own table and never reaches append_event. |
Ruling 2 — the engagement_committed_divergent query fix splits out as its own small change. R-2 stays purely a forward-gap CR.
Two design questions filed alongside, neither answered, neither disposable as registry footnotes:
memory_events rows rather than as an event written about the discard — which is why no writer for candidate_engagement_discarded was ever going to exist. The seed commits Memory to accumulate-with-provenance and corrections-preserved-not-smoothed; deletion of event rows is the one operation that shape forbids. Either candidate events sit outside Memory proper and the deletion is correct, or Memory has a delete path that contradicts the seed. Open, and it gates the held entry above.None block R-2 starting; each is recorded so R-2 does not silently answer it.
F1 form. Leaned LOOMWORKS_<namespaced-kind>_<source-key>, unruled. R-2 mints no identifiers; Stage 1 owns this.enqueued / anchored / unprovable, unruled. Unaffected by §8a. R-2 claims no anchor state.F4 catalog. Leaned yes, unruled. The contribution now exists — §9.3.PROVIDER: as fallback, unruled. Touches R-2 directly: sites 3 and 10 carry Anthropic token assets and the mapping already broke its own scheme convention there. R-2 builds no F8 values, so it proceeds — but if D14 is ruled before the CR is written, the CR should cite it rather than leaving the scheme break unexplained.
It does not draft a change request. It does not design the emitter, the custody store, or any linkage mechanism — Stage 6 is unblocked but not designed here (§9.1). It does not rule D10–D14. It does not sort Loomworks' 65 event kinds into F4 and F26, which is mapping work for Stages 3–5 (§9.3). It does not assign anchor priority to the 49 unregistered event kinds. It does not answer either design question at §10a. And it does not clean up the six O7 pre-registrations, which are correct as they stand.
DUNIN7 — Done In Seven LLC — Miami, Florida Loomworks — FORAY R-2 Forward-Gap Closure — Scoping Note — v0.4 — 2026-08-20