<!-- File: foray-protocol-primer-for-loomworks-substrate-v0_1.md Version: v0.1 Created: 2026-05-24T00:00:00Z Author: Marvin Percival Email: marvinp@dunin7.com GitHub: github.com/DUNIN7/foray-kaspathon
Purpose: Protocol-level primer for a sibling Claude.ai conversation grounding a Loomworks substrate-unification review in FORAY's protocol grammar rather than in Loomworks' current three-table implementation. -->
Version: v0.1 (working draft) Created: 2026-05-24T00:00:00Z
This is a primer on FORAY at the protocol level, written for a sibling Claude.ai conversation that is producing a Loomworks substrate review. That conversation has a draft recommendation grounded in the three existing Loomworks implementations — credit.foray_action_flows, audit.foray_events, and the readiness-wiring columns on memory_events — but not in FORAY's protocol-level shape.
Loomworks has a rare permission to wipe existing data and rebuild before production accumulates. This primer provides the protocol grammar so the substrate review can re-ground its recommendations against the protocol, not against the artifacts of how the three tables grew incrementally.
The published FORAY v4.1 specification (the version deployed at foray.dunin7.com today) calls it "the 4-Component Model (4A) + Attestations Extension", with Attestations described as an optional fifth for third-party validation. The protocol grammar as currently shipped matches that framing.
The refined position this primer uses is that FORAY is a five-component model in which Attestation is a native cross-cutting capability, not a downstream extension. Both framings produce the same JSON output; the difference is conceptual:
For a substrate rebuild, this matters at the table level. The four DAG components share a shape. Attestations are structurally different and should live in a sibling structure, not be folded into a single polymorphic table over all five.
They are five kinds of transaction component — units that decompose a business transaction. They are not parts of an actor identity, not four kinds of provenance, and not four parts of a single transaction record. They are five typed nodes that compose into a transaction DAG, with attestation being a structurally distinct fifth that hangs off the DAG rather than sitting in it.
| Component | Code | Question it answers | Contains | |---|---|---|---| | Arrangements | ARR | What did we agree to? | Parties, terms, effective dates, legal references | | Accruals | ACC | How is the amount computed? | Formulas, valuations, computed amounts | | Anticipations | ANT | What do we expect to happen? | Forecasts, conditions, scheduled amounts | | Actions | ACT | What actually happened? | Actual amounts, completion dates, status |
Critical distinctions the spec hammers on (these are easy to conflate and the protocol is precise about the difference):
| Component | Code | Question it answers | Contains | |---|---|---|---| | Attestations | ATT | Who vouches for this — and what did they vouch for? | Attestor identity, credentials, claims, evidence |
What makes attestation structurally different from the four DAG components:
arrangement_refs[]); Anticipations → Accruals AND/OR Arrangements (accrual_refs[], arrangement_refs[]); Actions → Anticipations AND/OR Accruals AND/OR Arrangements (anticipation_refs[], accrual_refs[], arrangement_refs[]).subject_refs[] which can point to any component type — including other attestations, forming attestation chains.
This is the deepest structural reason attestation is "cross-cutting": no DAG component has an attestation_refs[] field. The reference always goes from the attestation into the thing being attested to, never the reverse.
Per FORAY v4.1:
arrangements.length + accruals.length + anticipations.length + actions.length ≥ 1.arrangement_refs[], accrual_refs[], anticipation_refs[], subject_refs[] — never singular arrangement_ref. Many-to-many is the default. This was a v4.0 → v4.1 breaking change.To keep the substrate review using the canonical vocabulary:
transaction_id._refs[] arrays among the four peer components.
An attestation attests to one or more components, named in subject_refs[]. It can attest to:
ACT_DELIVERY_001 and certified it").What it explicitly does not attest to as a primitive:
subject_refs[].| Proves | Does NOT prove | |---|---| | A specific party made a specific claim at a specific time | The claim is true | | The claim has not been altered since anchoring | The attestor is competent | | The attestor's credentials are recorded | Physical reality matches the digital record |
This drives the Type 1 / Type 2 distinction in the FORAY trust model:
{
"id": "ATT_INSPECTION_001",
"foray_core": {
"attestor": "Quality Inspectors Ltd",
"attestor_hash": "sha256:...",
"attestor_type": "inspection_body",
"attestor_credentials": ["ISO_17020", "UKAS_Accredited"],
"subject_refs": ["ACT_DELIVERY_001", "ACC_INV_2026_001"],
"attestation_type": "inspection",
"attestation_date": "2026-01-15T14:30:00Z",
"validity_period": { "start": "2026-01-15", "end": "2027-01-15" },
"outcome": "certified",
"evidence_hash": "sha256:...",
"evidence_location": "off-chain",
"dependencies": ["ATT_LAB_ANALYSIS_001"]
}
}
Notable shapes:
dependencies or subject_refs.validity_period.end is in the past, the attestation's outcome should be expired (validation rule ATT-004).subject_refs[] is polymorphic — can point to ARR, ACC, ANT, ACT, or other ATT.certification — Formal certification by an authorized body (UMF, ISO, DOP).inspection — Physical examination by a qualified inspector (customs, quality, DCAA).analysis — Laboratory or technical analysis (spectroscopic fingerprint, MGO analysis).audit_opinion — Professional auditor's assessment (financial audit, compliance review).verification — Confirmation of facts or events (delivery confirmation, payment verification).oracle — Automated/sensor-based attestation (IoT readings, GPS coordinates, timestamps).
Attestations are components — their foray_core hashes into component_hashes.attestations, which contributes to the transaction's merkle_root. When the transaction is anchored to Kaspa, the attestations are anchored with it. An attestation is not a separate on-chain primitive; it lives inside the same transaction envelope as the four DAG components.
subject_refs[] pointing in), not from inside it (no DAG component has an attestation_refs[] field).
{
"transaction_id": "DOMAIN_YYYY_QN_DESCRIPTOR",
"schema_version": "4.1",
"timestamp": "ISO-8601",
"foray_core": {
"entity": "entity_name",
"entity_hash": "sha256:...",
"transaction_type": "type_code",
"total_value": 0.00,
"currency": "USD",
"status": "active|completed|reversed",
"compliance_flags": ["SOX_404", "DCAA", "..."]
},
"component_hashes": {
"arrangements": "sha256:...",
"accruals": "sha256:...",
"anticipations": "sha256:...",
"actions": "sha256:...",
"attestations": "sha256:..."
},
"arrangements": [...],
"accruals": [...],
"anticipations": [...],
"actions": [...],
"attestations": [...],
"merkle_root": "sha256:...",
"blockchain_anchor": {
"kaspa_tx_id": "kaspa:qr...",
"block_height": 0,
"confirmation_time_ms": 0,
"anchored_at": "ISO-8601"
},
"audit_data_anchor": {
"audit_data_hash": "sha256:...",
"audit_profile": "standard|dcaa_full|big4|minimal",
"storage_locations": [...]
},
"privacy_metadata": { /* ... */ }
}
Required top-level fields: transaction_id, schema_version, timestamp, foray_core, component_hashes, merkle_root, blockchain_anchor.
Optional top-level fields: the five component arrays individually (sum ≥ 1), audit_data_anchor, privacy_metadata.
{
"id": "ARR_DESCRIPTIVE_ID",
"foray_core": { /* protocol-required core fields */ },
"audit_data": { /* optional, audit-profile-dependent */ }
}
foray_core is hashed into component_hashes.<type>. audit_data is hashed separately into audit_data_anchor.audit_data_hash. This separation is deliberate: the core protocol runs without audit_data; audit_data extends the record for SOX, DCAA, Big 4 audit profiles. A system that omits audit_data entirely is still protocol-compliant.
This is the distinction asked about — and it maps cleanly onto FORAY's three-layer storage architecture:
| Concept | Layer | What it is | Size |
|---|---|---|---|
| Transaction record | Layer 2 (NoSQL) | The full transaction with all components and audit_data | ~15–20 KB |
| On-chain envelope | Layer 1 (Kaspa) | Hashes + merkle_root + minimal core metadata | ~200–500 bytes |
| Sealed archive | Layer 3 (Arweave + Glacier) | Encrypted complete record for litigation/dispute | ~5 KB compressed |
What gets anchored on-chain is not the transaction record. It is the envelope — a minimal payload containing transaction_id, schema_version, timestamp, foray_core_hash, audit_data_hash, merkle_root, and the anchor metadata. The full components live off-chain in Layer 2; the chain holds the cryptographic commitment.
The foundational architectural move:
> The blockchain is not a database. It is a cryptographic commitment store that enables off-chain logic to operate with on-chain guarantees.
Two distinct types of events get anchored on Kaspa:
transaction_id → merkle_root → kaspa_tx_id. Synchronous, ~1.2 seconds of user-perceived latency. One per transaction.
Layer 1 (Kaspa, ~500 B): hashes + merkle_root + anchor metadata
Layer 2 (NoSQL, ~15–20 KB): full foray_core + full audit_data, queryable
Layer 3 (Arweave+Glacier): sealed, encrypted, multi-party-ceremony access
For Loomworks, the relevant scope is Layer 2. Layer 1 is the FORAY anchor service's responsibility (Loomworks hands off the envelope to be anchored). Layer 3 is out of scope unless Loomworks needs litigation-grade preservation.
| Current Loomworks table | What it is now | Closest protocol concept |
|---|---|---|
| credit.foray_action_flows | Value flows | Actions (ACT) — possibly with adjacent Accruals (ACC) |
| audit.foray_events | Narrative state changes | Mixed: partly Attestations, partly state transitions on ARR/ANT/ACT, partly events about state |
| memory_events (with readiness-wiring columns) | Canonical append-only event log + DAG-readiness gating | The event log proper + the DAG dependency structure (currently muddled) |
The root problem the substrate review is responding to: three tables holding what is conceptually one protocol substrate, mixed with a second dimension (the append-only event log). The "three into one" instinct is right that there's overcollapse, but the protocol-respecting target is actually two state structures plus a clean event log, not a single mega-table.
The instinct to put all five constructs in one table seems clean. The protocol grammar pushes back for three reasons:
arrangement_refs[], accrual_refs[], anticipation_refs[]). Attestations have a single polymorphic reference (subject_refs[]) that can point anywhere including other attestations. Forcing both into one column shape loses type information that the protocol uses for validation.validity_period and outcome, which is a different lifecycle.Four tables, cleanly separated by what each represents:
foray_transactions — the transaction envelopeOne row per transaction. Populated at creation; finalized when ready to anchor.
foray_transactions
├─ transaction_id TEXT PRIMARY KEY
├─ schema_version TEXT NOT NULL -- '4.1'
├─ entity_hash TEXT NOT NULL
├─ transaction_type TEXT NOT NULL
├─ status TEXT NOT NULL -- 'open' | 'ready_to_anchor' | 'anchored' | 'sealed'
├─ total_value NUMERIC
├─ currency TEXT
├─ compliance_flags TEXT[]
├─ component_hashes JSONB -- populated at anchor time
├─ merkle_root TEXT -- populated at anchor time
├─ blockchain_anchor JSONB -- populated by anchor service
├─ audit_data_anchor JSONB
├─ privacy_metadata JSONB
├─ created_at TIMESTAMPTZ NOT NULL
├─ anchored_at TIMESTAMPTZ
foray_components — the four DAG component types (polymorphic by type)
One row per component. This is the unified replacement for credit.foray_action_flows plus the state-bearing parts of audit.foray_events.
foray_components
├─ component_id TEXT PRIMARY KEY -- e.g. 'ARR_...', 'ACC_...', 'ANT_...', 'ACT_...'
├─ transaction_id TEXT NOT NULL -- FK to foray_transactions
├─ component_type TEXT NOT NULL -- 'ARR' | 'ACC' | 'ANT' | 'ACT'
├─ status TEXT NOT NULL -- 'pending' | 'ready' | 'settled' | 'reversed'
├─ foray_core JSONB NOT NULL -- protocol-required core fields
├─ audit_data JSONB -- optional, audit profile dependent
├─ arrangement_refs TEXT[] -- populated by ACC, ANT, ACT
├─ accrual_refs TEXT[] -- populated by ANT, ACT
├─ anticipation_refs TEXT[] -- populated by ACT
├─ entity_hash TEXT NOT NULL
├─ created_at TIMESTAMPTZ NOT NULL
├─ component_hash TEXT -- sha256(canonicalize(foray_core)), populated when ready
Type-validity rules to enforce at write time:
| component_type | arrangement_refs | accrual_refs | anticipation_refs | |---|---|---|---| | ARR | (empty) | (empty) | (empty) | | ACC | allowed | (empty) | (empty) | | ANT | allowed | allowed | (empty) | | ACT | allowed | allowed | allowed |
Readiness is derived from this structure, not a column: a component's status becomes ready when every component named in any of its *_refs[] columns has a status of ready or settled. The current readiness-wiring on memory_events is doing this work; in the unified substrate it becomes a view or a computed predicate over foray_components.
foray_attestations — the cross-cutting fifth
Sibling table to foray_components, structurally distinct.
foray_attestations
├─ attestation_id TEXT PRIMARY KEY -- 'ATT_...'
├─ transaction_id TEXT NOT NULL -- FK to foray_transactions
├─ attestor TEXT NOT NULL
├─ attestor_hash TEXT NOT NULL
├─ attestor_type TEXT NOT NULL
├─ attestor_credentials TEXT[]
├─ subject_refs TEXT[] NOT NULL -- can reference foray_components OR foray_attestations
├─ attestation_type TEXT NOT NULL -- certification | inspection | analysis | audit_opinion | verification | oracle
├─ attestation_date TIMESTAMPTZ NOT NULL
├─ validity_period_start DATE
├─ validity_period_end DATE
├─ outcome TEXT NOT NULL -- certified | denied | conditional | expired | pending
├─ evidence_hash TEXT
├─ evidence_location TEXT
├─ dependencies TEXT[] -- other ATT this depends on (chain)
├─ created_at TIMESTAMPTZ NOT NULL
├─ attestation_hash TEXT -- sha256(canonicalize(...)), populated when ready
Notes:
subject_refs[] is polymorphic across foray_components.component_id and foray_attestations.attestation_id. Enforce via application logic; SQL can't express the union foreign key cleanly.validity_period_end < now()) does not invalidate the underlying components — it invalidates only the attestation.memory_events (cleaned) — the append-only event log
Keep memory_events, but strip the readiness-wiring columns. Readiness is now derived state over foray_components, not a column on the event log. memory_events returns to being a pure stream of state changes.
memory_events
├─ event_id ...
├─ event_type TEXT NOT NULL -- 'COMPONENT_CREATED', 'COMPONENT_STATE_CHANGED',
│ -- 'ATTESTATION_ATTACHED', 'TRANSACTION_ANCHORED', ...
├─ subject_id TEXT -- component_id, attestation_id, or transaction_id
├─ subject_kind TEXT -- 'COMPONENT' | 'ATTESTATION' | 'TRANSACTION'
├─ payload JSONB
├─ occurred_at TIMESTAMPTZ NOT NULL
The conceptual shift: memory_events is the stream of state changes. foray_transactions + foray_components + foray_attestations is the current state. Today those two dimensions are interleaved across your three tables. Pulling them apart is the unification.
credit.foray_action_flows → foray_components (component_type IN ('ACT', possibly 'ACC'))
audit.foray_events → three destinations depending on the row:
– foray_attestations (if it's an attestation)
– memory_events (if it's a narrative event about state)
– foray_components (if it's actually the state, not an event about it)
memory_events readiness → removed; derived from foray_components.*_refs[] and status
columns
In the unified substrate:
foray_transactions ⋈ foray_components ⋈ foray_attestations) grouped by transaction_id. There is no single row representing "the transaction" — it's a query, not a table.memory_events with event_type = 'TRANSACTION_ANCHORED', carrying the merkle_root and the kaspa_tx_id in its payload.ready_to_anchor. The envelope is constructed from the current state, recorded in foray_transactions once anchored, and emitted as a TRANSACTION_ANCHORED event.
This three-way distinction (current state / event log / on-chain envelope) is exactly what the current Loomworks substrate has muddled by putting readiness wiring on memory_events. Readiness is current-state machinery; events are an append-only log. Mixing them prevents either from being clean.
foray_transactions (envelope) + foray_components (four DAG types, polymorphic) + foray_attestations (cross-cutting fifth) + memory_events (cleaned event log). One mega-table over all five constructs over-collapses because attestation's reference shape is fundamentally different.memory_events. Readiness becomes derived state over foray_components.*_refs[] and status. The event log returns to being a pure append-only stream._refs[] arrays everywhere. Non-negotiable at the protocol level — v4.0 → v4.1 breaking change.merkle_root, component_hashes, or blockchain_anchor as columns on foray_components. Those are properties of a transaction (the grouping), computed when components are ready-to-anchor. They belong on foray_transactions, populated at anchor time.foray_core / audit_data separation inside JSONB. Even if Loomworks doesn't need audit_data for v0, the protocol's hash-everything-separately model assumes it. Leaving audit_data as a nullable JSONB column means later audit-profile work doesn't require schema migration.
A path that looked tempting during the analysis: collapse all five constructs into one polymorphic foray_substrate table with a kind discriminator and a single refs JSONB blob. It would unify the SQL surface area to one table.
Rejected because: the protocol distinguishes typed DAG references (which carry validation semantics — an Action can reference Anticipations, an Anticipation cannot reference Actions) from polymorphic subject references (which are intentionally unconstrained — an Attestation can point at anything). Collapsing these into one blob column hides the protocol's type information from the database layer and pushes all type-checking into application code, where it's easier for the next person to forget. The two-state-table shape preserves the protocol's typing at the schema level.
For verification of any of the above against the canonical FORAY documentation in the project knowledge:
_refs[] arrays.audit_data field definitions and audit profiles.