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

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

Version: v0.1 Date: 2026-08-20 Status: Scoping note. Not a change request. No CR may be drafted from it until the Step 0 inspection at §7 has run, because the evidence base underneath it is two days old and known to have moved. Scopes: Stage 2 of loomworks-foray-coverage-resolution-proposal-v0_3 §10 — R-2, ruled at D3 (close the forward gaps). Grounded on: loomworks-foray-coverage-resolution-proposal-v0_3 (2026-08-18); loomworks-foray-mapping-v0_2 (engine HEAD eb9392f5, 2026-08-18); foray-api-reference-for-loomworks-v0_1 (2026-08-20). Being passed to: the FORAY project. §8 and §9 are written for FORAY's read; §1–§7 and §10 are Loomworks-side and are included so FORAY sees what its answers govern.


Plain-language summary

R-2 is the smallest real piece of FORAY work Loomworks has, and it is the one everything else waits behind. At sixteen places in the engine, a marker sits where a FORAY record will one day be built. At most of those places, 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 markers stay reserved.

Three things travel in the same stage because they live in the same functions: a real ordering bug at the suspension site, where a database row's identifier is read before the row is written; two settings sites carrying byte-identical emit code that should share one builder; and Memory's own site, which is the worst of the sixteen — the richest data available and the poorest payload sent.

Two things have changed since the proposal was written two days ago, and both need FORAY's word. The anchor endpoint now has a duplicate check it did not have on 2026-08-18, which softens but does not remove the no-retry rule. And the validator's treatment of references that point outside a record appears to have changed from rejection to a recorded note — which, if confirmed, removes the structural ceiling that has been blocking the cross-record question all along. Those are §8 and §9.

One caution about this note's own evidence. The site census was taken on 2026-08-18. Engine work since then has added FORAY call sites of its own. The count of sixteen is almost certainly wrong now, and the note says so rather than pretending otherwise.


1. What R-2 is

From the resolution proposal's own 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. Because 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

From the mapping at eb9392f5. Sites are numbered as the mapping numbers them. Line numbers are not reproduced here — they have moved (see §7).

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. But forwarding it does not by itself produce a reference — the proposal's §7 and the mapping's own Brief 24 recheck both establish that a single-Action record has no component for an F21.ref to resolve against. Forwarding proposal_id is still correct and still R-2's business: 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 as of the mapping. Close its gap anyway — it is live, correct, and will fire when Phase 48 wires a caller.

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 it is the one whose closure is worth most. 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.

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. Three items in the same stage that are not forward gaps

4a. The suspension-site ordering defect (D8, a correctness bug). write_suspension_flow calls db.add(flow), then emits, then await db.flush(). Its two siblings — write_reactivation_flow and write_deletion_flow — flush before emitting. So flow.id is read at the suspension site before the row is written, structurally, on every call. Whether it is populated depends on whether the primary key defaults client-side or server-side, which the mapping did not confirm. Fix the ordering to match the siblings. This is a real bug independent of FORAY and should be fixed on those grounds, not as a mapping concern.

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 to follow. A shared payload builder, not a centralised emit — canon Principle 1 is one emit per business event, and collapsing the two call sites into one emitter would violate it. The helper builds the dict; each site still calls the emit.

4c. The site 3 flush-timing question. Four rows are db.add-ed and the emit loop reads row.id before any flush. Unlike 4a this may be benign — it depends on the same unconfirmed primary-key default. Step 0 resolves it by reading the model. If client-side, note it and move on; if server-side, it is a second instance of 4a.

5. What binds this work

These are 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 be able to attest. Forward what is genuinely in scope; do not fabricate, do not reach outside the function for values it does not hold.
  5. Do not resolve the linkage question inside R-2. Where a forwarded value is obviously wanting 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 and is not R-2's to invent.
  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. This interacts with D14, which is unruled — see §10.

6. Sizing

The proposal calls Stage 2 small enough for a single change request. That holds for the gap closures themselves: they are payload-dict additions in three files plus one ordering fix and one shared builder.

The honest qualification is that the census is stale (§7), so the true size is unknown until Step 0 reports. If Step 0 finds materially more sites than sixteen, the single-CR assumption should be re-tested rather than inherited.

7. Step 0 — required before any CR

The evidence under §3 was taken at engine HEAD eb9392f5 on 2026-08-18. It is known to be out of date, and the note states this rather than working around it.

Engine work since then has added FORAY call sites of its own, under standing obligation O7 — do not remove FORAY emission call sites; add them at new significant events, with grants, refusals, admissions and freezes named explicitly as the kind of event that gets one. Agent sessions, grantability and the freeze lane are all exactly that. Separately, the anchor-priority registry the memory site reads from has grown twice in the same period, which means the memory event-kind range has moved too.

Step 0 must therefore, at current main:

  1. Re-derive the site census by grep, not by trusting §3's count of sixteen. Report the new total and the delta with what each added site is.
  2. For every site, confirm what is genuinely in scope and what reaches the payload — the same discipline the mapping applied, since a site's function signature may have changed.
  3. Read CreditFlowRow's primary-key default and settle 4a and 4c.
  4. Report the current size of the anchor-priority registry and the memory event-kind range.
  5. Confirm whether any new site sits outside the three families in §3, which would mean R-2's shape is larger than payload-forwarding.

Do not skip to a CR on §3's numbers. They were accurate on 2026-08-18 and are being carried forward here as the starting point for inspection, not as current fact.


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

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 and adds material the proposal did not have.

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, with fresh salt and window each time | 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. That is a real improvement and it removes the worst version of the double-anchor risk.

It does not rescind the no-retry rule, for two reasons that survive intact. The check is byte-identical only — a regenerated timestamp or a reordered key anchors again and spends again, and Loomworks records carry a constructed emit-time timestamp at most sites precisely because no source timestamp exists, so byte-stability across a retry is not something Loomworks can currently guarantee. And the 409 response withholds leafSalt, jobId and fee, so a retry that is caught still does not recover the custody package the lost response carried. The first anchor remains permanently unprovable either way.

So constraints 1, 2 and 3 at proposal §9 stand as written, and D11/D12 stand as leaned. The change is that constraint 2's stated consequence — a retry guarantees a second independent anchor — should be narrowed to 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

This is the item worth FORAY's attention above everything else in this note.

The API reference describes note N4 as: a well-formed reference in F22F24 or an F21.ref that does not resolve to a component present in this record. And it states that this is not an error — as of 2026-08-20, a reference leaving the record's own contents is recorded and reported, the record remains 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 the current, settled behaviour, then the structural ceiling the whole cross-record question has been resting on is gone.

The proposal's §7 records the position as: current wire strictly in-record via E7; under the single-Action record shape an in-record reference cannot exist at all; FORAY has escalated Validation Scope R2 for an Operator ruling; Stage 6 deferred until it lands. The mapping's Brief 24 recheck sharpens the same point at site 11 — that no available data closes the gap, because the record shape cannot carry the pointer.

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 is it a separate change that leaves R2 still 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? The mapping and the proposal both attribute the in-record constraint to E7, and E7 is still in the current code space. Either its meaning has narrowed or one of those documents was wrong about it. Loomworks needs the current definition before designing anything that depends on it.
  3. The F4 catalog is still owed. The proposal's D13 offered Loomworks' namespaced event kinds as input, on FORAY's own stated concern that inventing the list without checking it risks immediate revision. That offer stands, and the R-2 Step 0 census will produce a current and larger list than the one D13 anticipated — worth waiting for rather than taking the 2026-08-18 version.

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


10. Decisions still open that touch this stage

None of these block R-2 starting, and 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 name line numbers, because they have moved. 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. And it does not treat its own §3 inventory as current fact — §7 exists precisely because it is not.


DUNIN7 — Done In Seven LLC — Miami, Florida Loomworks — FORAY R-2 Forward-Gap Closure — Scoping Note — v0.1 — 2026-08-20