Version: v0.4 Date: 2026-08-20
Status: Scoping note. Not a change request. Step 0 has
run, FORAY has answered §9, and §10a is ruled. The R-2 CR may
now be drafted. Changes from v0.3: §9 replaced
— FORAY answered all three questions and the section is now answers
rather than questions. Validation Scope R2 is ruled PERMIT and
Stage 6’s structural ceiling is gone (§9.1). E7
and E9 have narrowed and their current definitions are
recorded (§9.2). A ninth constraint added at §5 — permitting a reference
is not verifying it (§5.9). §10a recorded as ruled with its dispositions
(§10a). The mapping citation repointed to v0.3. v0.1–v0.3 stand as
siblings; v0.1’s wrong §7 premise stays preserved at §7a.
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-answers-to-r2-note-section9-v0_2;
foray-response-to-loomworks-census-v0_1;
loomworks-foray-coverage-resolution-proposal-v0_3;
loomworks-foray-mapping-v0_3;
foray-api-reference-for-loomworks-v0_1. Being
passed to: the FORAY project — for information only. Nothing in
this version asks FORAY anything. 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.N4 marks the trail leaves FORAY’s holdings
here — information for a reader, not a verification result.
Nothing in Loomworks may present a cross-record reference as a
verified link. Same discipline as D11’s rule against saying
“anchored” on a 200.E7 no longer means what the older documents say
it means. Current definitions are at §9.2.
loomworks-foray-mapping-v0_3 supersedes v0.2 on this point;
any CR citing reference-validation behaviour cites v0.3.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.v0.3 asked three questions.
foray-answers-to-r2-note-section9-v0_2 answers all three.
This section is now the answers.
N4 is the ruling, not a separate change. R2 is
closed.
Provenance, as FORAY gave it: Validation Scope decision sheet v0.4
ruled R2 PERMIT on 2026-08-20 — F22–F24 may
reference records outside this one, per Root §3.1’s admission test,
which routes a link to an authority, commitment or prior event to the
reference arrays. Brief 26 implemented it in the validator the same day;
N4 is that implementation. Brief 30 corrected the Verifier
Specification and the wire skill, both of which still described the old
behaviour.
A record may now carry a reference to something FORAY does not hold and remain valid. That was the whole substance of R2.
The consequence for the proposal’s §7. Its observation — that under a single-Action record an in-record reference cannot exist at all — still holds and is still true. But it is no longer a ceiling, because the reference no longer has to be in-record. FORAY’s own phrasing is the one to keep: what was structurally impossible is now merely unbuilt.
What this changes for R-2: nothing about the work,
everything about what it feeds. Forwarding proposal_id at
site 11 stops being a value parked against a mechanism that might one
day exist and becomes direct input to one that does. Stage 6 is
unblocked; R-2 still does not design it (§5.5
stands).
E7
and E9 have narrowed — current definitionsConfirmed by FORAY at source on 2026-08-20:
| Code | Fires when |
|---|---|
E7 |
A reference entry is not a string. Structural malformation only. No longer fires on non-resolution of any kind. |
E9 |
An F21.ref resolves to a component present in the
record and the declared ref_type contradicts that
component’s actual kind. Internal incoherence, not a resolution
failure. |
N4 |
Any well-formed reference — F22–F24 or
F21.ref — that does not resolve within the record. Not an
error; the record stays valid. |
On which document was wrong — FORAY corrected its own answer,
and the correction is worth carrying. FORAY’s v0.1 reported
that loomworks-foray-mapping-v0_2 was wrong, having
attributed the in-record constraint to E7, and called it
the third instance of a documentation-drift pattern on FORAY’s side. Its
v0.2 withdraws both claims: the mapping contained two imprecise
descriptive asides in a document that was substantively right — it never
claimed references must be in-record, its own correction notice already
stated Brief 26’s change correctly, and no record in the set carries any
reference for the distinction to bear on. The drift pattern was real at
two instances, not three.
FORAY’s stated reason for sending the correction rather than folding it in quietly: inflating a count of one’s own failures is its own kind of inaccuracy. Recorded here because it is a discipline this arc should hold in both directions — over-reporting your own errors reads as rigour and is not.
loomworks-foray-mapping-v0_3 carries the two tightened
phrases; all sixteen site sections are byte-identical. This note
now cites v0.3. v0.2 stands as sibling.
F4 contribution is accepted, and the catalog was built
for itFORAY accepts the 65 kinds. Context that changes how the contribution lands:
F4 (component type) and F26 (transaction
type) were both ruled closed on 2026-08-20 — catalog-governed,
snake_case canonical, assembled from FORAY’s example generator, the
sixteen mapping sites, and the conformance corpus. Initial, not
final. The ruling adopted a light extension mechanism for
exactly this case: a new engagement proposes values in its field-map
sheet (Root §8.2, already required), and FORAY accepts by pattern. No
amendment cycle.
So the 65 kinds are not a late addition to a closed list — they are the first exercise of the extension mechanism the ruling was designed around.
Two things that follow, both Loomworks’ work and neither R-2’s:
F4. F4 names what a component is
within a record; F26 names what the transaction as a whole
is. A Loomworks event kind may map to either, both, or — for kinds that
never become FORAY records — neither. Sorting that is mapping
work, and FORAY has no view on where any kind lands. It belongs
with the room postures at Stages 3–5, not here.Both ruled by the Operator on 2026-08-20 and filed to
loomworks-record/foray-reference/. Recorded here so the CR
inherits them rather than re-deriving them.
Ruling 1 — the six undisposed registry entries. Not treated as a block; they did not turn out to be one thing.
| Entry | Disposition | Why |
|---|---|---|
agent_registered |
Retire, superseded | W1 pre-registered agent_identity_minted for the same
event under the current identity model. This is the Phase 62 name,
predating the design that settled it. The supersession is recorded
beside it; it is not silently deleted. |
induction_cycle_recorded |
Rename entry to induction_cycle |
A live writer exists at two call sites under the shorter name, and
that string is already written into memory_events rows. The
recorded fact does not move; the registry moves to match it. The writer
is not renamed. |
candidate_engagement_discarded |
Held, undisposed | Its disposition follows the memory-deletion design question below and must not be settled before it. |
contribution_credential_issued / _claimed
/ _revoked |
Keep all three, annotated | Removing them would erase the only evidence that anyone judged these
events significant. Annotated with the fact that the lifecycle writes
via raw SQL into its own table and never reaches
append_event. |
Ruling 2 — the engagement_committed_divergent
query fix splits out as its own small change. R-2 stays purely
a forward-gap CR.
Two design questions filed alongside, neither answered, neither disposable as registry footnotes:
memory_events rows rather than as an event written about
the discard — which is why no writer for
candidate_engagement_discarded was ever going to exist. The
seed commits Memory to accumulate-with-provenance and
corrections-preserved-not-smoothed; deletion of event rows is the one
operation that shape forbids. Either candidate events sit outside Memory
proper and the deletion is correct, or Memory has a delete path that
contradicts the seed. Open, and it gates the held entry
above.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 design the emitter,
the custody store, or any linkage mechanism — Stage 6 is
unblocked but not designed here (§9.1). It does not rule
D10–D14. It does not sort Loomworks’ 65 event kinds into F4
and F26, which is mapping work for Stages 3–5 (§9.3). It
does not assign anchor priority to the 49 unregistered event kinds. It
does not answer either design question at §10a. 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.4 — 2026-08-20