The primer summarised FORAY at the protocol level. It was high-quality but it summarised; it didn't enumerate. Claude.ai noticed the gap and asked Claude Code to fill it by reading the canonical material directly. This document is that fill-in: a focused read of seven specific things that the primer named but didn't reproduce.
Read alongside the primer, not as a replacement for it. Where the primer makes a load-bearing claim, this document either backs it up with the exact wording from the source, or flags that the source is silent on the question.
Verifying the deep-read is grounded in concrete files rather than memory:
| File | Size | Purpose in this deep-read |
|---|---|---|
FORAY_Protocol_v4_1_Specification.md | 1,561 lines / 48 KB | Attestation rules and framing |
guides/FORAY_Attestation_Trust_Model.md | 304 lines | Type 1 / Type 2 trust model |
docs/DUNIN7_FORAY_Schema_Reference.md | 621 lines | Submission envelope, edge cases, framing |
examples/manuka-honey-provenance-v41.json | 330 lines | The worked transaction |
integration-guide.html, README.md | ~46 KB total | Anchor service operational description |
foray-api-server.js, quickbooks-adapter.js, proxy-server.js | ~62 KB total | Cross-checks for the anchor service investigation |
FORAY_Protocol_v4_0_Specification.md — referenced for the Audit Data Extension. Not in repo. The v4.1 spec references audit_data but does not define it structurally.
FORAY_Transaction_Boundary_Analysis.md — referenced for boundary edge cases. Not in repo. The closest substitute is the Schema Reference's §5, §6, §7, §10.3, §11, §12 — treated as the canonical boundary material since it's what exists.
The v4.1 spec presents the six rules as a single short table — no per-rule examples, no error severity, no remediation guidance. The rules are reproduced verbatim in the left column below; the right column gives a plain-English interpretation of what each rule means in practice.
| Rule (verbatim from §9.6) | What it means in practice |
|---|---|
ATT-001: id must be unique and begin with ATT_ |
Two constraints in one. Identifier uniqueness across the attestations array, plus a string-prefix convention. The other component types follow analogous prefixes (ARR_, ACC_, ANT_, ACT_) — observable in worked examples but not codified for those types in numbered rules. |
ATT-002: subject_refs must reference valid component IDs |
Reference resolution against IDs present in the same transaction. subject_refs is how an attestation reaches into the DAG; it can point to ARR, ACC, ANT, ACT — or to another attestation (forming chains, governed by ATT-006). |
ATT-003: attestation_date must not be in the future |
A temporal sanity rule. There's no companion rule for the date being too far in the past — old attestations are valid. |
ATT-004: If validity_period.end < current date, outcome should be expired |
The only rule using "should" rather than "must." The spec doesn't say what happens if a stale validity-period attestation has a non-expired outcome — likely a warning rather than a blocking error. Treat as a non-blocking advisory absent clarifying text. |
ATT-005: attestor_hash must be valid SHA-256 |
Constrains the format of the hash field but not its derivation. The spec does not say what attestor identity gets hashed — legal entity name? credentialed identity? per-attestor key? Worked examples use descriptive natural-language labels (sha256:umf_assoc_nz_2026...) which are illustrative truncations, not real hashes. |
| ATT-006: Circular attestation references are prohibited | Attestation chains are explicitly supported (A → B is fine; lab → certifier → regulator is the canonical case). But A → B → A is rejected. The spec does not say at what depth circularity detection runs, nor whether the rule applies across transactions or only within one transaction's attestations array. |
No per-rule examples. No error severity, no error codes, no remediation guidance. The spec calls these "Validation Rules" without classification. Practical advice: treat all six as validation errors by default, with the noted caveat that ATT-004 uses should.
The kickoff asked for the structural definition of audit_data from the v4.0 spec. The v4.0 spec is not in the repository. What is available falls well short of what the kickoff anticipated, and it's important to be honest about the gap.
The top-level schema (§2.1) defines only the anchor field that points at audit_data — not the audit_data structure itself:
"audit_data_anchor": {
"audit_data_hash": "sha256:...",
"audit_profile": "standard|dcaa_full|big4|minimal",
"storage_locations": [...]
}
The four audit_profile values — standard, dcaa_full, big4, minimal — are mentioned by name. §2.2 marks audit_data_anchor itself as optional.
In §§4.2, 4.3, 4.4, 4.5 — the four worked examples (cash sale, depreciation, payment, RMBS) — the audit_data block consistently appears as:
"audit_data": {
"...see audit_data specification..."
}
This placeholder appears in all four worked examples in the spec body. There is no "audit_data specification" document in the repository to follow that pointer to.
In examples/manuka-honey-provenance-v41.json, audit_data_anchor carries only the three documented fields, and the actual audit_data payload is not inline:
"audit_data_anchor": {
"audit_data_hash": "sha256:manuka_audit_data...",
"audit_profile": "standard",
"storage_locations": ["s3://foray-audit/2026/Q1/PROV_2026_Q1_MANUKA_HONEY_BATCH_001"]
}
storage_locations points to off-chain storage (here, an S3 prefix). None of the 12 example files inline an audit_data block; all use the anchor-only pattern.
The four audit-profile names appear by name in the v4.1 spec and the HTML specification. Nowhere in the local repository is it stated what each profile contains, what the field-level differences are, whether minimal is a strict subset of standard, or whether profiles can be mixed within a transaction. Practical questions about audit_data shape that the substrate review may have to answer cannot be sourced from this repo. The v4.0 spec — which the kickoff names — is the most likely place for the missing structure. It is not in this clone.
The architecture has three layers and the deep read captures them succinctly: (a) foray_core is on-chain — the minimum to identify the transaction; (b) the component arrays carry the deal structure and are hashed individually into component_hashes; (c) audit_data is off-chain detail referenced by a single hash in audit_data_anchor. The primer's "hashed separately" claim is consistent with this layering. But the spec doesn't enumerate exactly what's in audit_data at each profile level.
standard, dcaa_full, big4, minimal each requireaudit_data can be mixed across components within a transactionaudit_data blockaudit_data_hash is over a canonical JSON serialization, and what the canonicalization isThe Manuka honey example is the canonical complex v4.1 example. It is a Type 2 (attestation) provenance transaction with all five component arrays populated — ARR×2 + ACC×2 + ANT×2 + ACT×2 + ATT×3 — and explicit dependency / _refs[] chains throughout.
The example is reproduced verbatim below. Before reading the JSON, the things worth noticing as you scan it:
ARR_BATCH_DEFINITION_2026_001 depends on ARR_UMF_LICENSE_2026 via the dependencies field. Arrangements can declare upstream Arrangements.ACC_LABORATORY_ANALYSIS_001 has arrangement_refs pointing at both arrangements. The Accrual lives below both of them in the DAG.ACT_JARRING_COMPLETE_001 references one ANT, two ACCs, and one ARR — every layer above it.sha256:something_descriptive... are illustrative truncations, not real hashes.{
"transaction_id": "PROV_2026_Q1_MANUKA_HONEY_BATCH_001",
"schema_version": "4.1",
"timestamp": "2026-01-15T09:00:00Z",
"foray_core": {
"entity": "Waikato Apiaries Ltd",
"entity_hash": "sha256:wal_2026_abc123...",
"transaction_type": "product_provenance",
"total_value": 185000.00,
"currency": "NZD",
"status": "completed",
"compliance_flags": ["MPI_Export_Certified", "UMF_Licensed", "ISO_22000"]
},
"component_hashes": {
"arrangements": "sha256:arr_manuka_a1b2c3...",
"accruals": "sha256:acc_manuka_d4e5f6...",
"anticipations": "sha256:ant_manuka_g7h8i9...",
"actions": "sha256:act_manuka_j0k1l2...",
"attestations": "sha256:att_manuka_m3n4o5..."
},
"arrangements": [
{
"id": "ARR_UMF_LICENSE_2026",
"foray_core": {
"type": "origin_certification",
"effective_date": "2026-01-01T00:00:00Z",
"parties": [
{ "role": "producer", "name": "Waikato Apiaries Ltd", "jurisdiction": "NZ" },
{ "role": "certifier", "name": "UMF Honey Association", "jurisdiction": "NZ" },
{ "role": "laboratory", "name": "Analytica Laboratories", "jurisdiction": "NZ" }
],
"description": "UMF certification license for authentic New Zealand Manuka Honey",
"total_value": 185000.00,
"currency": "NZD",
"terms": {
"certification_type": "UMF_Licensed_Producer",
"license_number": "UMF-2026-0847",
"valid_until": "2026-12-31",
"annual_audit_required": true
},
"dependencies": []
}
},
{
"id": "ARR_BATCH_DEFINITION_2026_001",
"foray_core": {
"type": "production_batch",
"effective_date": "2026-01-10T00:00:00Z",
"parties": [
{ "role": "producer", "name": "Waikato Apiaries Ltd", "jurisdiction": "NZ" }
],
"description": "Production batch MH-2026-001 - 500kg UMF 15+ Manuka Honey",
"total_value": 185000.00,
"currency": "NZD",
"terms": {
"batch_id": "MH-2026-001",
"weight_kg": 500,
"jar_count": 1000,
"jar_size_g": 500,
"harvest_date": "2025-12-20",
"extraction_date": "2025-12-22",
"apiary_region": "Waikato, North Island",
"hive_source_ids": ["HIVE-W-042", "HIVE-W-043", "HIVE-W-044"]
},
"dependencies": ["ARR_UMF_LICENSE_2026"]
}
}
],
"accruals": [
{
"id": "ACC_LABORATORY_ANALYSIS_001",
"foray_core": {
"arrangement_refs": ["ARR_BATCH_DEFINITION_2026_001", "ARR_UMF_LICENSE_2026"],
"type": "product_analysis",
"description": "Laboratory analysis results for batch MH-2026-001",
"computation_method": "Valuation",
"formula_id": "sha256:umf_analysis_protocol_v2...",
"inputs": {
"sample_id": "SAMPLE-MH-2026-001-A",
"analysis_date": "2026-01-12",
"methodology": "UMF_Grading_Standard_v4"
},
"output": 185000.00,
"currency": "NZD",
"analysis_results": {
"mgo_mg_kg": 514,
"dha_mg_kg": 1250,
"hmo_mg_kg": 42,
"leptosperin_mg_kg": 185,
"umf_rating": 15,
"umf_grade": "UMF 15+",
"npa_equivalent": 15.2,
"moisture_percent": 17.8,
"hmf_mg_kg": 12,
"diastase_number": 18,
"authenticity_markers": {
"c4_sugar_percent": 0.8,
"pollen_analysis": "Leptospermum scoparium dominant",
"dna_verified": true
},
"classification": "Authentic Monofloral Manuka",
"grade_confirmed": true
},
"dependencies": ["ARR_BATCH_DEFINITION_2026_001"]
}
},
{
"id": "ACC_BATCH_VALUATION_001",
"foray_core": {
"arrangement_refs": ["ARR_BATCH_DEFINITION_2026_001"],
"type": "inventory_valuation",
"description": "Batch valuation at export wholesale price",
"computation_method": "Calculated",
"formula_id": "sha256:valuation_weight_x_grade_price...",
"inputs": {
"weight_kg": 500,
"umf_grade": "UMF 15+",
"price_per_kg_nzd": 370.00
},
"output": 185000.00,
"currency": "NZD",
"dependencies": ["ARR_BATCH_DEFINITION_2026_001"]
}
}
],
"anticipations": [
{
"id": "ANT_EXPORT_DISTRIBUTION_2026_001",
"foray_core": {
"accrual_refs": ["ACC_BATCH_VALUATION_001"],
"arrangement_refs": ["ARR_BATCH_DEFINITION_2026_001"],
"type": "scheduled_distribution",
"description": "Expected export distribution to international partners",
"expected_amount": 185000.00,
"currency": "NZD",
"expected_date": "2026-02-15",
"probability_factor": 0.95,
"distribution_plan": {
"export_markets": ["China", "UK", "USA", "Japan"],
"jars_per_market": { "china": 400, "uk": 250, "usa": 200, "japan": 150 },
"mpi_export_certificate_required": true
},
"dependencies": ["ACC_BATCH_VALUATION_001"]
}
},
{
"id": "ANT_SHELF_LIFE_WINDOW_001",
"foray_core": {
"accrual_refs": ["ACC_LABORATORY_ANALYSIS_001"],
"arrangement_refs": ["ARR_BATCH_DEFINITION_2026_001"],
"type": "quality_validity_window",
"description": "Expected shelf life based on analysis results",
"expected_amount": 0,
"currency": "NZD",
"expected_date": "2031-01-15",
"probability_factor": 0.95,
"quality_parameters": {
"best_before_date": "2031-01-15",
"optimal_storage_temp_c": "below 25",
"light_exposure": "protected",
"note": "Honey does not spoil; UMF activity may decrease over time"
},
"dependencies": ["ACC_LABORATORY_ANALYSIS_001"]
}
}
],
"actions": [
{
"id": "ACT_JARRING_COMPLETE_001",
"foray_core": {
"anticipation_refs": ["ANT_EXPORT_DISTRIBUTION_2026_001"],
"accrual_refs": ["ACC_LABORATORY_ANALYSIS_001", "ACC_BATCH_VALUATION_001"],
"arrangement_refs": ["ARR_BATCH_DEFINITION_2026_001"],
"type": "production_completion",
"description": "Jarring completed for batch MH-2026-001",
"amount_settled": 185000.00,
"currency": "NZD",
"settlement_date": "2026-01-15T09:00:00Z",
"settlement_status": "completed",
"payment_method": "other",
"counterparty": "Internal",
"production_details": {
"jars_produced": 1000,
"jars_passed_qc": 998,
"jars_rejected": 2,
"rejection_rate_percent": 0.2,
"tamper_seal_applied": true,
"umf_label_applied": true
},
"dependencies": ["ANT_EXPORT_DISTRIBUTION_2026_001"]
}
},
{
"id": "ACT_UMF_CERTIFICATION_ISSUED_001",
"foray_core": {
"anticipation_refs": [],
"accrual_refs": ["ACC_LABORATORY_ANALYSIS_001"],
"arrangement_refs": ["ARR_UMF_LICENSE_2026", "ARR_BATCH_DEFINITION_2026_001"],
"type": "certification_issuance",
"description": "UMF certificate issued for batch MH-2026-001",
"amount_settled": 0,
"currency": "NZD",
"settlement_date": "2026-01-14T14:00:00Z",
"settlement_status": "completed",
"payment_method": "other",
"counterparty": "UMF Honey Association",
"certificate_details": {
"certificate_number": "UMF-BATCH-2026-00847",
"issued_by": "UMF Honey Association",
"valid_for_batch": "MH-2026-001",
"umf_grade_certified": "UMF 15+",
"mgo_verified_mg_kg": 514,
"qr_verification_url": "https://umf.org.nz/verify/UMF-BATCH-2026-00847"
},
"dependencies": ["ACC_LABORATORY_ANALYSIS_001"]
}
}
],
"attestations": [
{
"id": "ATT_LAB_ANALYSIS_001",
"foray_core": {
"attestor": "Analytica Laboratories",
"attestor_hash": "sha256:analytica_nz_2026...",
"attestor_type": "laboratory",
"attestor_credentials": ["IANZ_Accredited", "MPI_Recognized", "ISO_17025"],
"subject_refs": ["ACC_LABORATORY_ANALYSIS_001"],
"attestation_type": "analysis",
"attestation_date": "2026-01-12T16:30:00Z",
"validity_period": { "start": "2026-01-12", "end": "2027-01-12" },
"outcome": "certified",
"evidence_hash": "sha256:lab_report_mh2026001...",
"evidence_location": "off-chain",
"analysis_summary": {
"mgo_result_mg_kg": 514,
"leptosperin_result_mg_kg": 185,
"dna_verified": true,
"methodology": "UMF_Grading_Standard_v4"
},
"dependencies": []
}
},
{
"id": "ATT_UMF_CERTIFICATION_001",
"foray_core": {
"attestor": "UMF Honey Association",
"attestor_hash": "sha256:umf_assoc_nz_2026...",
"attestor_type": "certification_body",
"attestor_credentials": ["NZ_Govt_Recognized", "Trademark_Owner_UMF", "MPI_Partner"],
"subject_refs": ["ARR_UMF_LICENSE_2026", "ARR_BATCH_DEFINITION_2026_001", "ATT_LAB_ANALYSIS_001"],
"attestation_type": "certification",
"attestation_date": "2026-01-14T14:00:00Z",
"validity_period": { "start": "2026-01-14", "end": "2026-12-31" },
"outcome": "certified",
"evidence_hash": "sha256:umf_certificate_mh2026001...",
"evidence_location": "off-chain",
"certificate_details": {
"certificate_number": "UMF-BATCH-2026-00847",
"umf_grade": "UMF 15+",
"qr_verification_url": "https://umf.org.nz/verify/UMF-BATCH-2026-00847"
},
"dependencies": ["ATT_LAB_ANALYSIS_001"]
}
},
{
"id": "ATT_MPI_EXPORT_001",
"foray_core": {
"attestor": "Ministry for Primary Industries",
"attestor_hash": "sha256:mpi_nz_govt_2026...",
"attestor_type": "regulator",
"attestor_credentials": ["NZ_Government_Authority", "Food_Safety_Regulator", "Export_Certification_Authority"],
"subject_refs": ["ARR_BATCH_DEFINITION_2026_001", "ATT_UMF_CERTIFICATION_001"],
"attestation_type": "approval",
"attestation_date": "2026-01-15T08:00:00Z",
"validity_period": { "start": "2026-01-15", "end": "2026-07-15" },
"outcome": "approved",
"evidence_hash": "sha256:mpi_export_cert_mh2026001...",
"evidence_location": "off-chain",
"export_details": {
"export_certificate_number": "MPI-EXP-2026-NZ-00847",
"approved_markets": ["China", "UK", "USA", "Japan"],
"health_certificate_included": true
},
"dependencies": ["ATT_UMF_CERTIFICATION_001"]
}
}
],
"merkle_root": "sha256:manuka_batch_001_merkle_root...",
"blockchain_anchor": {
"kaspa_tx_id": "kaspa:qr_manuka_batch_001...",
"block_height": 2950000,
"confirmation_time_ms": 1100,
"anchored_at": "2026-01-15T09:00:02Z"
},
"audit_data_anchor": {
"audit_data_hash": "sha256:manuka_audit_data...",
"audit_profile": "standard",
"storage_locations": ["s3://foray-audit/2026/Q1/PROV_2026_Q1_MANUKA_HONEY_BATCH_001"]
},
"privacy_metadata": {
"formulas_obfuscated": 2,
"instance_pools": 3,
"attack_complexity": "2^96 operations"
}
}
The reference structure forms a directed acyclic graph. Two distinct reference fabrics run through this transaction.
The intra-DAG _refs[] fabric. Every component except Arrangements carries arrangement_refs[], every component below Accruals carries accrual_refs[], every component below Anticipations carries anticipation_refs[]. These are upstream references — an Action declares which Anticipations it resolves, which Accruals it satisfies, and which Arrangements authorised it. They form chains visible directly in the JSON: ARR_BATCH_DEFINITION depends on ARR_UMF_LICENSE; the lab-analysis Accrual references both arrangements; the jarring Action references one Anticipation, two Accruals, and one Arrangement — a full chain through every layer. This is the "DAG" the primer names.
The attestation fabric — structurally separate. Attestations do not appear in any _refs[] array on any component. They reach into the DAG via subject_refs[] on the attestation itself — a one-way pointer. Three patterns of attachment in this example: ATT_LAB_ANALYSIS_001 attaches to one Accrual; ATT_UMF_CERTIFICATION_001 attaches to two Arrangements and one other attestation (the chain primitive); ATT_MPI_EXPORT_001 attaches to one Arrangement and one prior attestation.
The DAG components have no knowledge of which attestations point at them. The reference always goes from the attestation into the subject, never back. This one-way arrow is what makes attestation a sibling structure, not a fifth peer in the DAG.
What the hashes commit to. Four hash-bearing fields commit at different layers:
component_hashes.{arrangements, accruals, anticipations, actions, attestations} — five hashes, one per component array, each over the canonical serialization of that array. The spec does not specify the canonicalization; the worked examples use truncated placeholders.merkle_root — the on-chain anchor commits to this single hash, presumably built over the five component_hashes values. The spec doesn't spell out the merkle tree shape; for five leaves a balanced tree gives a three-level structure.audit_data_anchor.audit_data_hash — separately hashed; the off-chain payload sits beside the blockchain anchor at the envelope level. Not part of merkle_root based on the schema structure.attestor_hash and evidence_hash on each attestation — point to off-chain identity and evidence respectively. The evidence_location field tells you where; the evidence_hash lets you verify what you fetch is what was committed.The Type 2 framing in this example. The transaction is anchored against three independent attestors — laboratory, certification body, regulator — with progressive chain dependency (lab → certifier → regulator). FORAY does not certify that the honey is Manuka. It certifies that the lab analysed it, that UMF certified the lab's analysis, and that MPI approved the certification for export. This is the canonical "attestation transaction" shape: identified parties making sequential claims under specific credentials, with the chain itself anchored.
The trust-model document is the most explicit place in the FORAY material about what attestations can and cannot prove. It's worth reproducing the key passages verbatim because the exact wording matters.
"FORAY transactions fall into two broad categories with different trust characteristics."
Type 1: System-of-Record Transactions — "These transactions record events within authoritative business systems where the system itself is the source of truth."
"Examples: ERP journal entries (SAP, Oracle, QuickBooks), Bank payment confirmations, Manufacturing work orders, Payroll processing."
"Trust Model: The ERP/financial system is authoritative. When QuickBooks records a payment, the payment occurred. FORAY anchors this fact with external, tamper-evident proof."
"FORAY Value: Proves the record existed at a point in time and hasn't been altered — even by system administrators."
Type 2: Attestation Transactions — "These transactions record claims about external reality made by identified parties."
"Examples: Product provenance (Manuka honey origin, watch authenticity), Laboratory certifications (spectroscopic analysis), Third-party inspections, Compliance attestations."
"Trust Model: FORAY anchors a chain of attestations, not independent truth. The value depends entirely on trusting the attestors."
"FORAY Value: Creates a tamper-evident record of who claimed what, when — enabling accountability and dispute resolution."
An important nuance: Type 1 / Type 2 is not a typed field on the transaction. The categorisation is imposed by the framing document, not by the schema. The distinction is inferred from structure:
attestations[], or with an empty array, are de facto Type 1 — there is no external claim infrastructure to anchor.attestations[] are de facto Type 2 — they exist to anchor claims by identified parties.There is no transaction_kind or trust_model field in foray_core. The Type 1 / Type 2 label is editorial categorisation, not a structural feature of the schema. The v4.1 spec's "When to Use Attestations" table is a usage guide, not a structural constraint — a product-provenance transaction can in principle ship without attestations (lossy, but valid); a cash sale can in principle ship with attestations (over-the-top, but valid).
From §9.7 of the v4.1 spec, reproduced exactly:
What Attestations Prove:
- A specific party made a specific claim at a specific time
- The claim has not been altered since anchoring
- The attestor's credentials are recorded
From the trust-model document, the equivalent framing:
- Temporal proof — A specific claim was recorded at a specific point in time
- Integrity proof — The recorded data has not been altered since anchoring
- Consistency proof — Multiple parties can verify they hold identical records
- Sequence proof — Events occurred in a verifiable order
From v4.1 spec §9.7, reproduced exactly:
What Attestations Do NOT Prove:
- The claim is true
- The attestor is competent
- Physical reality matches the digital record
The trust-model document expands this into a four-row explanation, reproduced exactly:
| Limitation | Explanation |
|---|---|
| Truth of claims | FORAY anchors assertions, not facts — if a party claims "this honey is authentic Manuka from New Zealand," FORAY proves they made that claim, not that it's true |
| Physical reality | Digital records cannot independently verify physical world states |
| Attestor competence | A laboratory's certification is only as reliable as the laboratory |
| Data accuracy at source | "Garbage in, garbage out" — FORAY trusts validated source system data |
This is not a weakness to hide — it is a boundary to understand. Every audit system shares these limitations. FORAY's contribution is making the recorded claims tamper-evident and the claimants accountable.
| Transaction Type | Source of Truth | FORAY Proves | Trust Assumption |
|---|---|---|---|
| System-of-Record | ERP/Financial System | Record integrity + timestamp | System is authoritative |
| Attestation | Identified Parties | Claims were made + timestamp | Attestors are trustworthy |
The trust-model document does not directly answer "does the substrate need a Type 1 / Type 2 discriminator?" Reading the protocol material:
attestations[]. A substrate that lifts each component into a table can recover the distinction by query without an explicit flag.transaction_kind enum: system_of_record | attestation) would be a substrate-level addition the protocol does not require. The benefit would be query efficiency; the cost would be carrying a derived classification that needs to stay in sync with the underlying structure.Honest reading: the trust model is editorial categorisation, not a structural feature of the schema. A substrate can implement a discriminator if it makes substrate-side queries cleaner; doing so is a substrate decision, not a protocol mandate.
The "FORAY anchor service" as a separately defined operational interface is not documented in the local repository as an HTTP API, library, or service contract. There is enough material to know the conceptual shape; there is not enough to know the wire interface.
quickbooks-adapter.js, salesforce-adapter.js) that call into an ForaySDK module.quickbooks-adapter.js:24 reads:
const ForaySDK = require('./foray-sdk');
— and the file foray-sdk.js is not in the repository. The adapter calls into this.sdk.anchorToBlockchain(transaction) (line 309, 389, 457 of quickbooks-adapter.js) and this.sdk.createArrangement(...) etc. for component construction. These are the only points where the calling code touches the anchor surface.
The two API servers in this repo are not the anchor service. foray-api-server.js exposes three endpoints — /api/generate-foray, /api/analyze-business, /api/describe-transaction. All three are Claude-powered demo endpoints for constructing FORAY JSON from natural language, not for anchoring it. proxy-server.js is a CORS proxy. Neither submits to a blockchain.
From the integration guide:
"The universal FORAY integration pattern follows five steps, regardless of source system:
- Extract — Pull transaction data from ERP via API, webhook, or scheduled export
- Transform — Map to FORAY's 4-component model
- Hash — Generate component hashes, compute merkle root, apply privacy obfuscation
- Anchor — Submit merkle root to Kaspa blockchain
- Store — Archive full transaction JSON with blockchain anchor reference"
The "service" is functionally: a process that takes a constructed FORAY JSON, computes (or accepts) the merkle root, broadcasts it to Kaspa, and writes back the blockchain_anchor block (kaspa_tx_id, block_height, confirmation_time_ms, anchored_at). The repo does not state whether this runs in-process, out-of-process, or as a hosted service.
The Schema Reference §4.1 defines a wrapper that is the most service-interface-like artifact in the repository:
{
"foray_submission": {
"schema_version": "1.0",
"transaction_id": "<SHA-256 hash>",
"parent_reference": { "type": null, "id": null },
"submission_type": "",
"submission_timestamp": "",
"persistence": [],
"boundaries": { ... },
"arrangement": {},
"accruals": [],
"anticipations": [],
"actions": []
}
}
| Field | Description |
|---|---|
submission_type | "new" / "amendment" / "component" — first submission for this transaction_id, append-only correction, or addition to an existing transaction |
persistence[] | Persistence layer declarations (see below) |
parent_reference | Null if originating; otherwise points to parent Transaction or Accrual |
§4.2 (Persistence Layer Declaration) clarifies the anchor relationship:
"FORAY is persistence-layer agnostic. The submitting party declares which persistence layer or layers to use. FORAY executes the anchoring according to the declaration. The cost is borne by the initiator."
And the required properties for FORAY-compatible persistence:
"Required persistence layer properties for FORAY compatibility: Tamper-evident · Independently verifiable · Append-only · Timestamped · Durable · Accessible"
persistence[] array is a valid submission — the transaction is recorded without anchoring, and the absence of anchoring is itself part of the record.The repo does not say. The adapter code uses await this.sdk.anchorToBlockchain(transaction) — consistent with synchronous-via-async (the call returns once the blockchain has confirmed). Kaspa's 1-second block times make a synchronous flow feasible (the integration guide names ~2 seconds for "Real-time (immediate anchoring)"). The operating model supports both real-time and batched/micro-batched submission; the choice is per-deployment.
Enough material exists to know the conceptual shape: the submitting system constructs a FORAY envelope with a declared persistence layer, submits it to a layer-specific service (probably via the absent foray-sdk.js), receives a blockchain_anchor block once the persistence layer confirms, and stores the resulting full transaction JSON with the anchor reference.
Not enough material exists to know the exact wire interface — endpoint URL, request body shape, response shape, error codes, auth model. If Loomworks' substrate-side design needs to integrate against an anchor service, the substrate is currently designing against an under-documented surface. The protocol-level abstraction is documented; the operational interface is not. Worth flagging.
The kickoff named FORAY_Transaction_Boundary_Analysis.md as the source for this section. That file is not in the repository. The closest material is in the Schema Reference (2026-03-26): §5 (Asset Behaviour Taxonomy), §6 (Transaction Record Configurations), §7 (Multi-Behaviour Combination Rules), §10.3 (Edge Cases & Unusual Transactions), §11 (Schema Additions from Second Validation Pass), and §12 (Initial Transaction Validation Reference — Set Two). This is the canonical boundary analysis available.
The Schema Reference frames edge cases not as anomalies that break the protocol, but as transactions where the asset's behaviour reveals a corner of the four-component model that simpler transactions don't exercise. §5 identifies nine asset behaviour types (extensively investigated; no tenth has emerged).
| # | Behaviour | Boundary feature exercised |
|---|---|---|
| 1 | Complete Transfer | Baseline — ARR + ACC + ANT + ACT, single asset moving one direction |
| 2 | Conditional Transfer | Two competing Anticipations exist simultaneously; one resolves with an Action, the other is retired without generating an Action. The retired Anticipation is the reversal-path artifact. |
| 3 | Transformation | Input asset is consumed, output asset is created — they are related but not the same asset. Two Actions in one Arrangement. |
| 4 | Consumption | The asset ceases to exist with no output. Action records disappearance, not transfer. |
| 5 | Governing Reference | The asset never moves — only its derivative obligation does. The reference asset appears in Accrual Properties, never in an Action. |
| 6 | Creation | The asset did not exist before the transaction. Brought into existence by the Arrangement itself. |
| 7 | External Destruction | An external event destroys the primary asset. Destruction Action has no recipient. Cause is observable in record but outside the protocol's scope. |
| 8 | Right | Right-grant and right-exercise are separate transactions linked by lineage. The right's existence is one Arrangement; each exercise is a child transaction. |
| 9 | Perceived Value | The valuation oracle is the only source of value. The transaction is real; the value is agreed, not derived. |
The Schema Reference does not name this term explicitly, but the protocol structurally admits it. The Action component (§2.4) admits two types: Transfer and State Change. State Change is "condition confirmed without asset movement — From Party and To Party are the same party." That's the structural admission of system-generated transactions: the protocol accepts state changes as Actions even when no asset moved. Concrete cases in §10.3: Tooling Setup & Changeover ("First transaction producing no transferable output. Action is a state change") and API Call Billing ("Highest-volume type mapped — potentially billions of child Actions against single Arrangement").
The Schema Reference doesn't use the phrase "date-range transaction" but the protocol supports non-point-in-time semantics in several ways:
Expected Date/Time is described in §2.3 as "Projected timing. May be a range or probability distribution for uncertain transactions." Explicit support for non-point-in-time projection.The protocol does not require timestamps to be point-in-time; ranges and distributions are first-class.
Reversal as a retired Anticipation (Conditional Transfer, §5):
"Two possible outcomes exist simultaneously — completion or reversal."
"Action: Resolves to one path only. The other Anticipation is retired without generating an Action."
Reversal as an Accrual that reverses direction (§12.5 Government and Public Sector):
"Tax Payment & Refund Cycle | Formula-based Accrual | Accrual obligation can reverse direction between parties."
"Government Grant with Clawback | Conditional Transfer | Clawback is conditional transfer in reverse — asset already transferred may return."
Reversal as a non-monotonic Accrual (§10.3 Carried Interest):
"Accrual is non-monotonic — reverses and rebuilds. Anticipation is awareness until liquidity event."
The protocol expresses reversal through the component lifecycle, not through a reversed flag on the transaction. There is a status: "reversed" value on the top-level envelope, but the Schema Reference's framing is that reversal is a structural lifecycle event, not a status flip — the underlying components carry the reversal mechanics.
§4.4 of the Schema Reference is the most precise statement of the substrate-relevant boundary:
"Amendments are append-only. The original record is immutable. Every amendment is a new anchored record that references the original
transaction_id. The full history — original plus all amendments — is always visible. Deletion is structurally impossible."
{
"submission_type": "amendment",
"transaction_id": "<hash of this amendment>",
"parent_reference": { "type": "transaction", "id": "<hash of original transaction>" }
}
"An amendment may add or clarify. It may never delete. Any attempt to submit an amendment that removes a previously anchored field is rejected."
This boundary is structurally identical to Loomworks' Memory non-erasure rule (R-A28). Both substrates enforce append-only correction. Whatever cleanup Loomworks does to its three current FORAY-shaped tables, the append-only discipline carries through.
| Edge case | Boundary feature |
|---|---|
| Open source bounty (§12.4) | Recipient is genuinely unknown at Arrangement creation. Resolved by an Open Recipient placeholder party type at the Arrangement, with the resolving Action carrying the actual party identifier. |
| Divorce asset settlement (§12.7) | A Directing Authority (court) shapes the Arrangement without participating in asset flows. Added as a party role in the optional schema; appears in the Arrangement only. |
| Organ procurement chain (§12.3) | Time-critical viability window in the Anticipation. Breach is structurally visible as a timestamp anomaly. |
| Revenue-based financing (§12.7) | Self-terminating Accrual — the Accrual carries a cumulative cap value. Ceases when the cap is reached. |
| Letter of Credit (§10.1) | "Attestation gates drawdown but is not mandatory for recording" — attestation is a real-world prerequisite for an Action, but FORAY records the transaction structure regardless. The gating is editorial, not structural. |
The Schema Reference is explicit (§11): all edge-case resolutions land in the optional schema layer, never in the core. Implicit substrate guidance:
From Party == To Party.Expected Date/Time field (polymorphic — point, range, or distribution).The substrate design that absorbs all of this with minimum core impact is the layered one the primer recommends — four polymorphic component tables plus an attestations table plus JSONB for optional schema additions.
The deep-read surfaces something the primer didn't fully reveal: three positions on Attestation framing exist in the canonical material, not two. The primer named two — the v4.1 spec's framing and the primer's own. The Schema Reference (2026-03-26) holds a third position, mid-way between them. Reproduced verbatim below.
Section header: "9. Attestations Extension (Optional)"
Opening paragraph:
"The Attestations extension provides a fifth component type for transactions requiring third-party validation. This is optional — the core 4A model (Arrangements, Accruals, Anticipations, Actions) remains sufficient for most enterprise transactions."
The default top-level schema in §2.1 has component_hashes with four entries (arrangements, accruals, anticipations, actions). Attestations are added only when used. §9.5 ("Schema Extension") frames this structurally: "When using attestations, add to component_hashes..."
Section §2 (Core Schema), opening:
"The core schema consists of four components — Arrangements, Accruals, Anticipations, and Actions (4A). Attestations are available as a FORAY capability but are not part of the core schema. A transaction is complete without them."
But §3.1 (Optional Schema Layer — Attestations) immediately reframes:
"Attestations are the event layer of the FORAY protocol. They are not business transactions. They are claims — made by an identified, credentialed party, at a defined point in time, anchored tamper-evidently alongside the transaction they reference."
"An Attestation establishes: who claimed what, under what credentials, at what time. The tamper-evident anchor makes the claim permanently attributable. Whether the claim was honest, accurate, or complete is outside the boundary of the protocol. A false Attestation is permanently attributable to its author. FORAY does not prevent false claims. It makes them impossible to deny."
"This is the layer where AI agent observability connects to business transaction accountability."
This position is mid-way between Position 1 and Position 3. It holds (a) "not part of the core schema" and (b) "the event layer of the FORAY protocol" — which is a structurally distinct framing from "optional extension."
From the primer's 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."
"Four component types (ARR, ACC, ANT, ACT) are peers in the transaction DAG; Attestation (ATT) is a fifth, structurally distinct kind — it does not sit in the DAG, it annotates it."
"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."
The three positions produce identical JSON output. A v4.1-compliant transaction looks the same whether attestations are "an extension," "the event layer," or "a cross-cutting fifth." The transaction validates against the same rules under all three framings.
The difference is conceptual and substrate-shaping:
subject_refs[] field rather than the components having attestation_refs[]. This is what the primer recommends for the Loomworks substrate.The structural one-way reference is the same in all three positions. Every reading of the spec has the same answer: attestations point at components, components do not point at attestations. The structural fact is not contested. What is contested is the conceptual posture (optional / layered / native), which informs substrate-level decisions like whether the attestations table is created in v1 or deferred, whether queries that walk the DAG include attestations by default, and whether the substrate needs a typed attestation_refs[] column on each DAG component (no — because the spec is structurally clear that reference goes the other way).
The v4.1 spec is dated 2026-02-14. The Schema Reference is dated 2026-03-26 — six weeks later. The Schema Reference is the more recent document and shows the conceptual evolution:
The primer goes one further step — past "event layer" to "native cross-cutting capability." That step is unambiguously not in either spec document; it's the primer's synthesis.
The drift is real but uniformly in one direction: each successive document strengthens attestation's structural standing relative to the v4.1 spec. There is no document anywhere in the repo that retracts the v4.1 spec's framing — it remains the canonical wire-format reference — but every document written after it positions attestations as more central than the v4.1 spec's "Extension (Optional)" language suggests.
For substrate design: the primer's framing (native cross-cutting) is the strongest of the three and is consistent with the trajectory of the documentation. A substrate that builds attestations in from v1 — with a peer table and the one-way reference fabric — is consistent with all three positions even though only Position 3 demands it.
Six observations that don't fit the seven topics but seem load-bearing for substrate work.
Schema Reference §4 defines a wrapper around the transaction (foray_submission) with three submission types: new, amendment, component. This is structurally distinct from the v4.1 spec's top-level transaction object. The substrate may want to model this layering: a foray_submissions table records every submission attempt; the foray_transactions row is the materialized result of one or more submissions for a given transaction_id. The three submission types map to substrate operations:
new — INSERT a new transaction with no parentamendment — INSERT a new transaction with parent_reference.type = "transaction"component — INSERT into one of the four component tables with parent_reference.type = "transaction" for an existing transaction§4.3 of the Schema Reference:
"
transaction_id = SHA-256( namespace + timestamp + entropy )""The pre-image is retained by the submitting agent's key management implementation. The submission agent never stores it in plaintext."
Substrate implication: Loomworks either generates the transaction_id (and retains the pre-image for the proving-ownership story) or accepts it from upstream. The substrate cannot synthesize a transaction_id on demand without also synthesizing the namespace/entropy/timestamp triple.
§4.2 makes explicit that a transaction may be anchored to multiple persistence layers simultaneously:
"Multiple layers may be declared simultaneously. A party that anchors to a credible, independently verifiable layer carries more weight in the execution fidelity model than one that anchors to an unverified private log."
A substrate that stores blockchain_anchor as a single embedded object on the transaction row is structurally one-layer-only. If the protocol is to be honoured, the substrate needs transaction_anchors (plural) — one row per (transaction_id, persistence_layer).
"At transaction inception, a completeness weight is calculated based on the data supplied relative to what the transaction type requires. This weight can be recalculated at any subsequent point as Actions resolve against Anticipations."
The substrate would need to record the inception-time weight, the recomputed weights, and the cumulative party-level weight derived from many transactions. The primer's four-table design doesn't allow for this without an additional layer.
Schema Reference §3.1:
"This is the layer where AI agent observability connects to business transaction accountability. An AI agent's decision cycle — authorisation check, reasoning, projection, execution — is a sequence of events, not a business transaction. Each event is a claim made by the agent about what it did and why."
Directly relevant to the substrate review: Loomworks itself is an AI-agent-operated substrate. The Schema Reference's framing suggests that Loomworks-as-a-FORAY-system would have its actor decisions (Companion turns, classifier outputs, agent dispatch records) modelled as Attestations against the engagement-as-Transaction. This is a deeper substrate implication than the primer states.
"The FORAY protocol is deployment-model neutral. Compliant implementations may be self-hosted, embedded in connectors, or offered as commercial services."
"Commercial implementations of the FORAY protocol — including hosted services offered to third parties — require a commercial license from DUNIN7."
For Loomworks: the substrate's relationship to FORAY can be embedded (calls into a local SDK), federated (each tenant declares its own persistence layer), or hosted (Loomworks operates the anchor service). The protocol doesn't pick one.
| Topic | What's missing | Where the answer might live |
|---|---|---|
| Audit data extension (Topic 2) | Structural definition of audit_data; field-level differences between standard, dcaa_full, big4, minimal; whether profiles can be mixed within a transaction |
FORAY_Protocol_v4_0_Specification.md (not in this clone); possibly in DUNIN7's private documentation |
| Anchor service interface (Topic 5) | Wire-level interface for anchoring: endpoint URL, request/response shapes, error codes, auth model, sync-vs-async semantics | foray-sdk.js (referenced by adapters but not in repo); possibly in DUNIN7's private documentation |
| Boundary edge cases (Topic 6) | FORAY_Transaction_Boundary_Analysis.md as a discrete document |
Substituted with Schema Reference §5/§6/§7/§10.3/§11/§12, which covers the same conceptual ground organised by asset behaviour |
| ATT-* validation rule examples | The §9.6 table gives statements but no per-rule example of violation or accepted outcome | Possibly in test fixtures (not searched exhaustively); no obvious test directory in the repo |
The substrate-review work can proceed against everything in the seven topics that was found, with the gaps above flagged. Topics 2 and 5 are the most consequential gaps — Topic 2 because audit-profile differences may shape a substrate-side audit_data table, and Topic 5 because the substrate will need to integrate against some anchor surface whose interface is not documented in the local clone.