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.
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.
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.
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.
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/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.
| # | 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.
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.
These are 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 and is not R-2’s to
invent.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.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.
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:
CreditFlowRow’s primary-key default and settle 4a
and 4c.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.
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.
E1–E5, E7–E12, E14; warning W3; notes
N1–N4. E6 and E13 do not exist in
4.2. The mapping’s 17/17 pass was against this space.GET /api/anchor/{txHash} proves retention
only — it returns the submitted bytes verbatim and never
leafSalt, jobId or fee, none of
which are stored server-side. It is 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 and denies the route’s existence rather than reporting
itself disabled. Proposal §9 constraint 7 already 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 or goes
looking.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 F22–F24 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:
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.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.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.
None of these block R-2 starting, and each is recorded so R-2 does not silently answer it.
F1 form. Leaned
LOOMWORKS_<namespaced-kind>_<source-key>,
unruled. R-2 does not mint identifiers, so it can proceed; Stage 1 owns
this.enqueued / anchored / unprovable,
unruled. Unaffected by §8a. R-2 does not claim any anchor state.F4
catalog. Leaned yes, unruled. See §9.3 — the offer improves if
it waits for the Step 0 census.PROVIDER: as fallback, unruled. This one
touches R-2 directly: sites 3 and 10 carry Anthropic
token assets, and the mapping already broke its own scheme convention at
those sites to avoid claiming Loomworks as scheme owner of an Anthropic
unit. R-2 does not build F8 values, so it can proceed — but
if D14 is ruled before the CR is written, the CR should cite it rather
than leaving the two sites’ scheme break as an unexplained
exception.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