DUNIN7 · LOOMWORKS · RECORD
record.dunin7.com
Status Current
Path change-requests/cr-2026-233-foray-r2-forward-gap-closure-v0_1.md

CR-2026-233 — FORAY R-2: Forward-Gap Closure — v0.1

Version: v0.1 Date: 2026-08-21 Status: Drafted, not pre-flighted. Markdown primary — the consumer is Claude Code. Implements: Stage 2 of foray-reference/loomworks-foray-coverage-resolution-proposal-v0_3 §10 — R-2, ruled close them all at D3. Scoped by: foray-reference/loomworks-foray-r2-forward-gap-scoping-note-v0_4, with its census correction at …-v0_4-census-correction-v0_1. Rulings carried: the six registry dispositions at foray-reference/loomworks-anchor-priority-registry-dispositions-v0_1 (Operator, 2026-08-20); the divergent-query fix splits out (§10a Ruling 2). Runtime direction screen: No conflict — every value forwarded is already in scope at its call site, most of them already passed to Loomworks' own audit write one line above. No new act becomes possible, no authority widens, and _foray_reserved_emit remains a no-op stub that discards its payload. Cites: standing-notes/loomworks-standing-note-two-records-and-what-crosses-between-them-v0_1 (STANDING) §6; standing-notes/loomworks-standing-note-migrations-and-the-code-that-runs-at-another-time-v0_2; standing-notes/loomworks-standing-note-a-test-that-pins-a-literal-cannot-follow-a-ruling-v0_1; O1, O5, O7, O10; R1; D3, D6, D8.


Plain-language summary

At seventeen places in the engine a marker sits where a FORAY record will one day be built. At most of them the useful facts — which grant, which engagement, which proposal, who authorized it — are sitting right there in the function, often handed to Loomworks' own audit write one line above, and then dropped. This change forwards them.

Nothing transmits. Nothing anchors. _foray_reserved_emit stays a stub that throws its payload away. The point is that the payload each site can attest is the specification of what it will one day attest, and a site that drops its identifiers would one day produce a record that validates and says nothing.

Three things travel with it because they live in the same functions: a confirmed ordering bug where a database row's id is read before the row exists, 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.

The anchor-priority registry gets the six dispositions the Operator already ruled — one retirement, one rename, three annotations, one held. It does not get entries for the forty-nine live event kinds that lack them; deciding what deserves anchoring is room-posture work, not this.


1. What R-2 is, and what it is not

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

Closing a gap means: pass the in-scope values into _foray_reserved_emit's payload dict. That is the whole change at most sites.

It does not mean: building an emitter, making a network call, constructing or validating or anchoring a record, or changing any production behaviour. The payload argument is passed to a function that discards it.

Why before there is an emitter. The payload 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.

2. Scope discipline — five things this CR must not do

  1. It does not design the linkage mechanism. 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 in a comment. Stage 6's ceiling is gone (R2 ruled PERMIT) but the mechanism is unbuilt and is not R-2's to invent.
  2. It does not present any reference as verified. Permitting a reference is not verifying it. N4 marks the trail leaves FORAY's holdings here — a note for a reader, not a verification result.
  3. It does not mass-add registry entries for the 49 live kinds with none. Assigning anchor priority decides what deserves anchoring, which is room-posture work at Stages 3–5.
  4. It does not clean up the six O7 pre-registrationsagent_session_minted, _closed, _refused, _silent, agent_identity_minted, agent_identity_frozen. A registry entry with no writer is exactly what O7 asks for while the emitter sleeps. §7 reports a consequence about four of them and fixes none.
  5. It does not fix the engagement_committed_divergent query. Ruled split out (§10a Ruling 2). R-2 stays a forward-gap CR.

3. Step 1 — the credit family

credit/flows.py, credit/room_consumption.py, credit/proposal_applier.py. Site numbers as the mapping numbers them.

| # | Event kind | Forward | |---|---|---| | 1 | credit.issuance | grant_id | | 2 | credit.provisioning | authorized_by, 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 | Nothing. Payload is complete for what the site holds | | 9 | credit.referral_credit | converted_person_id | | 10 | credit.consumption_token / _credit (×3) | engagement_id, room | | 11 | credit.corrective | proposal_id, shape_event_id, applied_by |

Site 11 — forward, and do not build. proposal_id is the thing the correction corrects and 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. Comment the wish; leave the mechanism to Stage 6.

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.

Sites 3 and 10 keep the PROVIDER: scheme break, and the comment explaining it stays. Provider token assets are genuinely Anthropic-denominated; claiming LOOMWORKS: over them would be false. D14 is unruled; if it is ruled before this executes, cite it rather than leaving the break unexplained.

4. Step 2 — the governance-shaped family

| # | Event kind | Forward | |---|---|---| | 12 | memory.<event_kind> | object.id, object.object_type, engagement_id, actor.id, actor.kind | | 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 | | 16 | audit.setting_change (button) | person.id from the authenticated principal, previous, normalized |

Site 12 is the largest gap in the set and the one worth most. It is Memory's single write path, carrying every memory event kind, and its payload today is event_id and anchor_priority alone. The object id, engagement id, actor id and actor kind are all in scope and none reaches the payload — which is exactly why the mapping had to put the stated placeholder "UNATTRIBUTED" in F3. Closing this is what lets a Memory record name its own subject.

Site 12 is also three rooms' only FORAY path. render_produced, shape_produced, composition_ and shaping_ are live event kinds flowing through it today. Manifestation, Shaping and Rendering have no dedicated call site; until Stage 3 gives them one, this is not a placeholder for those rooms, it is their only path. Do not re-badge site 12 as a Rendering site or invent room-specific emission there — it still emits under memory.<event_kind>.

actor.kind now ranges over five valuesperson, companion, contributor, agent, system. Pin against the constant, not a literal, per the literal-pinning note.

Sites 13, 14 and 16 are one shape three times. The identity is handed to Loomworks' own audit write one line above and not handed to the emit.

5. Step 3 — three defects in the same functions

5a — the suspension-site ordering defect (D8). Confirmed, not suspected. write_suspension_flow calls db.add(flow), emits, then await db.flush(). Its siblings write_reactivation_flow and write_deletion_flow flush first. CreditFlowRow.id is server_default=text("gen_random_uuid()"), so SQLAlchemy has no visibility into it before flush. flow.id is read before the row exists, on every call. Fix the ordering to match the siblings. This is a real bug independent of FORAY.

5b — site 3's flush timing is the same defect again. Four rows db.add-ed, the emit loop reads row.id before any flush. Same server-side default. Fix both.

5c — 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 its own emit.

6. Step 4 — the registry dispositions

Per loomworks-anchor-priority-registry-dispositions-v0_1, which rules all six. This CR is where the code lands.

| Entry | Action | |---|---| | agent_registered | Retire in place, annotated as superseded by agent_identity_minted. Not deleted — the annotation is why it is not silently dropped | | induction_cycle_recorded | Rename the key to induction_cycle, keeping "standard". A live writer already writes that string at engagement/creation.py and persons/personal_engagement.py. Do not rename the writer — the registry moves to match reality | | candidate_engagement_discarded | No action. Held, pending the candidate-discard design question | | contribution_credential_issued / _claimed / _revoked | Keep, annotated: the lifecycle persists via raw SQL into contribution_credentials and never calls append_event, so these acts touch neither record and no reserved location. Point the comment at the filed bypass finding |

The annotation on the credential three should use the standing note's phrasing: not wrong for being outside Memory — wrong for being outside both records.

7. Step 5 — report, do not fix

Two findings to record in the CR's own closing report, neither actionable here.

7a — four pre-registered entries have no destination. _ANCHOR_PRIORITY is consulted by Memory's write path. CR-2026-231 established that grant-lifecycle events go to the operational record, not Memory. So agent_session_minted, _closed, _refused and _silent were registered in good faith against a mechanism that turns out to be the wrong one. Do not delete them and do not implement against them. Refer all four to the dispositions document, joining agent_identity_minted and candidate_engagement_discarded.

That family now has six members, and the pattern is worth stating there: pre-registering an event kind commits to a destination as well as a name.

7b — the 49 unregistered live kinds. Record the count and that the gap is the serious half — real activity, including all of Shaping's and Rendering's, moving through the one FORAY-adjacent site with no priority assigned. Nothing in the code looks wrong, no query fails, no test goes red. A silent gap, stated so it stops being silent. R-2 does not close it.

8. The seventeenth site

agents/grant_lifecycle carries a reserved location added by CR-2026-231 §7. Screen it like any other site: report what is in scope at the writer and what reaches its payload. CC's Gate 2 report says it arrives with a complete payload — verify rather than inherit. If complete, it adds a site to the census and no work to this closure list.

On the census number. loomworks-foray-r2-forward-gap-scoping-note-v0_4 §7 reports 16 sites; the correction note establishes there were already 17 markers because the emitter's own docstring carries the format string, and 18 after the lifecycle writer. Sites, not strings: 16 before CR-2026-231, 17 now. Do not re-derive by grep | wc -l — it over-counts the docstring and under-counts the collapsed multi-emit markers. AST or nothing.

9. Acceptance

  1. Every gap in §3 and §4 is closed, or a reason is stated for that site in a comment. Site 8 states its reason: nothing is dropped.
  2. _foray_reserved_emit is unchanged and still discards its payload. No production behaviour changes anywhere — asserted by the suite being green with no test's outcome changed except those added.
  3. Site 12 forwards all five of its in-scope values, and a test asserts actor.kind against the constant rather than a literal.
  4. write_suspension_flow flushes before emitting, matching its siblings; site 3's loop likewise. A test asserts the id is populated at emit time.
  5. Sites 13 and 14 share one payload builder and retain two separate emit calls.
  6. The four registry actions in §6 land; the two held entries are untouched.
  7. §7's two findings and §8's screening result are in the closing report.
  8. Suite green with the delta accounted for; ruff clean; mypy against the unchanged baseline; no migration.

10. Gates

Explicit path staging; no git add -A; halt before push at every gate. No migration in any gate — if one appears necessary, halt rather than adding it.


DUNIN7 — Done In Seven LLC — Miami, Florida CR-2026-233 — FORAY R-2: Forward-Gap Closure — v0.1 — 2026-08-21