DUNIN7 · LOOMWORKS · RECORD
record.dunin7.com
Status Current
Path foray-reference/loomworks-foray-r2-forward-gap-scoping-note-v0_2.html

Loomworks ��� FORAY R-2 Forward-Gap Closure ��� Scoping Note ��� v0.2

Loomworks — FORAY R-2 Forward-Gap Closure — Scoping Note — v0.2

Version: v0.2 Date: 2026-08-20 Status: Scoping note. Not a change request. Step 0 has run — the inspection v0.1 required is complete and folded in at §7. A CR may now be drafted from this note. Changes from v0.1: the Step 0 census reported and its findings absorbed; v0.1’s §7 premise corrected — it was wrong, and the correction is preserved rather than smoothed (§7a). The registry drift added as a fourth same-stage item (§4d) — the largest single finding of the census and not visible from the mapping. The F4 catalog contribution for FORAY is now real rather than promised (§9.3). §3’s inventory is confirmed current. v0.1 stands as sibling. 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; loomworks-foray-coverage-resolution-proposal-v0_3; loomworks-foray-mapping-v0_2 (engine eb9392f5); foray-api-reference-for-loomworks-v0_1. Being passed to: the FORAY project — §8 and §9 only. The rest is Loomworks-side.


Plain-language summary

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. Fourteen name events that are never written anywhere in the system. And forty-nine event kinds that are written have no entry at all — so for most of what Memory actually records, the hint resolves to nothing. Two of the dead entries have live tails: one is still being filtered for in a database query, and another names an event that lands in a different table entirely. That is Loomworks’ own drift, not FORAY’s, and it belongs in this stage.

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.


1. What R-2 is

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.

2. What closing a gap means, exactly

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.

3. The gap inventory

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.

3a. Credit family — 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.

3b. Governance-shaped family

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

4. Four items in the same stage that are not forward gaps

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
Registry entries with no write anywhere 14 Dead entries; the hint can never fire for them
Live event kinds with no registry entry 49 anchor_priority resolves to None for most of what Memory records

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.

Two dead entries have live tails and are ordinary bugs:

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: reconcile the registry against the census; fix the engagement_committed_divergent query; correct or remove the membership_created entry; dispose of the other six explicitly. Do not mass-add entries for all 49 unregistered kinds — assigning anchor priority to every memory event is a design act about what deserves anchoring, and it belongs with the room-posture work at Stages 3–5, not here. R-2’s business is that the registry stops lying about what exists.

5. What binds this work

Constraints, not choices; each already ruled or forced.

  1. Substrate only. Emission lives in the engine. Never in the Operator Layer, never in a surface, never anywhere a replaceable component could decline to emit and leave no trace. (D6; architecture §09.)
  2. One emit per business event. The reserved-location pattern with grep markers is the mechanism. No new emission mechanism is invented here, and no emit is centralised into a shared writer.
  3. Reserved stays reserved. Nothing in R-2 makes _foray_reserved_emit do anything.
  4. The payload is the specification. What a site forwards is what it will one day attest. Forward what is genuinely in scope; do not fabricate, do not reach outside the function.
  5. Do not resolve the linkage question inside R-2. Where a forwarded value obviously wants to be a reference — 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.
  6. The 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.
  7. Do not clean up the O7 pre-registrations. A registry entry with no writer is exactly what O7 asks for while the emitter sleeps. §4d names which six.

6. Sizing

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.

7. Step 0 — run, and what it returned

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:

7a. Correction — v0.1’s §7 premise was wrong

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.


8. For the FORAY project — what changed in the contract since v0.3

Unchanged from v0.1. 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.

9. For the FORAY project — the question that may unblock Stage 6

The item worth FORAY’s attention above everything else in this note.

The API reference describes N4 as a well-formed reference in F22F24 or an F21.ref that does not resolve to a component present in the record — and states this is not an error. As of 2026-08-20 a reference leaving the record’s own contents is recorded and reported, the record stays valid: true, and this is described as the intended shape for lineage pointing at a prior record or anything outside FORAY’s holdings. It further narrows E9 to fire only on a ref_type mismatch against a component that is present.

If that is current settled behaviour, the structural ceiling the cross-record question rests on is gone.

The proposal’s §7 records the position as: wire strictly in-record via E7; under a single-Action record an in-record reference cannot exist at all; FORAY has escalated Validation Scope R2 for an Operator ruling; Stage 6 deferred until it lands.

Three questions, in the order they matter:

  1. Is N4’s permissive treatment of out-of-record references the ruling on Validation Scope R2, or a separate change that leaves R2 open? The reference reads as the former. Loomworks would rather be told than infer it.
  2. If it is the ruling — what is E7 now? Both the mapping and the proposal attribute the in-record constraint to E7, and E7 remains in the current code space. Either its meaning narrowed or one of those documents was wrong. Loomworks needs the current definition before designing anything that depends on it.
  3. The F4 catalog — the contribution is now real. D13 offered Loomworks’ namespaced event kinds as input, on FORAY’s own concern that inventing the list unchecked risks immediate revision. The census has produced it: 65 distinct event kinds in live use across 94 append_event call sites, plus the six pre-registered agent-lifecycle kinds not yet written. This is a larger and more accurate list than D13 anticipated. It can travel with FORAY’s answers to 1 and 2 whenever FORAY wants it.

What Loomworks does meanwhile: R-2 proceeds either way. Forwarding a value that wants to be a reference is correct whether or not the reference mechanism exists, which is why R-2 is sequenced first.


10. Decisions still open that touch this stage

None block R-2 starting; each is recorded so R-2 does not silently answer it.

11. What this note does not do

It does not draft a change request. It does not design the emitter, the custody store, or any linkage mechanism. It does not rule D10–D14. It does not answer §9’s questions on FORAY’s behalf. It does not assign anchor priority to the 49 unregistered event kinds — that is a design act about what deserves anchoring and belongs at Stages 3–5. 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.2 — 2026-08-20