Version: v0.3 Date: 2026-08-20
Status: Scoping note. Not a change request. Step 0 has
run and FORAY has responded to it. A CR may be drafted from this note
once the two rulings at §10a are made. Changes from
v0.2: FORAY’s response to the census absorbed. §4d reweighted —
the two directions of the registry gap are not equally consequential and
v0.2 gave them equal weight (§4d). Site 12 reframed: three of the seed’s
four rooms already emit through it today, which changes what closing its
gap is worth (§3c). The dynamic-dispatch lesson stated once as a shared
finding, with both projects’ errors named (§7b). Two rulings pulled out
of the body into §10a so they are not lost in prose. v0.1 and v0.2 stand
as siblings; v0.1’s wrong §7 premise stays preserved at §7a and is not
rewritten. 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-response-to-loomworks-census-v0_1;
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. 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.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.The item worth FORAY’s attention above everything else in this note.
The API reference describes N4 as a well-formed
reference in F22–F24 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:
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.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.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.
Pulled out of the body so they are not lost in prose. Both are small and neither is FORAY’s.
Ruling 1 — the six undisposed registry entries.
agent_registered, induction_cycle_recorded,
candidate_engagement_discarded,
contribution_credential_issued,
contribution_credential_claimed,
contribution_credential_revoked. Each is either a
deliberate pre-registration in the O7 pattern — a seam waiting for a
writer — or genuinely dead and removable. They cannot be treated as a
block; the credential three and the engagement-lifecycle three plausibly
differ.
Ruling 2 — where the
engagement_committed_divergent query fix lives. It
changes read behaviour, not emit behaviour, and the query currently
returns nothing by construction. Either it rides in the R-2 CR as an
incidental fix, or it splits out as its own small change so R-2 stays
purely a forward-gap CR. §6 leans split-if-it-grows; this decides it up
front instead.
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 rule §10a. 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.3 — 2026-08-20