Historical and normative record — wire 4.1. This page is the specification of FORAY wire format 4.1: the shape the validator accepts on its permanent legacy arm and converts to the current wire. It is preserved unchanged as the 4.1 authority. The current wire is 4.2 — see the specification publication edition.
Specification status
- Core grammar: v4.1.2 (presentational revision r1).
- Batch Anchoring Amendment: v4.1.3 — ACCEPTED 2026-07-13. Operates at the anchoring layer; additive and backward-compatible.
- Canonical Transaction Shape: RULED 2026-07-20 (Canonical Transaction Shape Ruling v0.3). The normative authority for transaction-JSON shape is the schema artifact
foray-transaction-v4_1.schema.json, served below and enforced by the live validator (POST /api/validate-foray). - The specification text below remains the normative document for everything the schema does not govern. Where artifacts differ, the schema governs transaction shape; the specification governs the rest.
Normative document
FORAY Protocol Specification v4.1
Document Control
| Property | Value | | Version | 4.1 | | Status | DRAFT - Under Review | | Previous Version | 4.0 (FORAY_Protocol_v4_0_Specification.md) | | Created | January 25, 2026 | | Author | DUNIN7 | | Classification | Technical Specification |
Version History
| Version | Date | Summary | Breaking Changes | | 1.0 | Jan 2026 | Initial protocol with 4-component model | N/A | | 2.0 | Jan 2026 | Added privacy layers, ERP adapters | No | | 3.0 | Jan 2026 | Compressed keys, tier-based storage, encrypted envelope | Yes - key mapping | | 4.0 | Jan 2026 | foray_core/audit_data separation, 3-layer architecture, sealed archive | Yes - structural | | 4.1 | Jan 2026 | Flexible entry points, many-to-many component references | Yes - reference fields |
What's New in v4.1
Summary of Changes
| Change | v4.0 | v4.1 | Impact | | Arrangements requirement | Required | Optional | Allows Action-only or Accrual-only transactions | | Component references | Singular (_ref) | Array (_refs[]) | Supports many-to-many relationships | | Minimum components | At least 1 Arrangement | At least 1 component of any type | Greater flexibility | | Entry points | Arrangements only | Any component type | Supports cash receipts, adjusting entries |
Design Rationale
Change 1: Flexible Entry Points
Problem: V4.0 required Arrangements as DAG roots, forcing artificial parent creation for transactions without natural arrangements (e.g., walk-in cash receipts, petty cash, adjusting journal entries).
Solution: Allow any component type as transaction entry point. Define valid component combinations based on real-world transaction patterns.
Change 2: Many-to-Many References
Problem: V4.0 used singular reference fields (arrangement_ref, accrual_ref, anticipation_ref), but real transactions often have many-to-many relationships:
- One payment clearing multiple invoices
- One invoice covering multiple contracts
- RMBS waterfall distributions across multiple tranches
Solution: Replace singular _ref fields with _refs[] arrays, supporting proper relationship modeling.
How to Use This Document
For Implementers
- Section 2 defines the core protocol with v4.1 changes
- Section 3 defines valid component combinations
- Section 4 defines migration from v4.0
For Reviewers Comparing to v4.0
- Component reference fields changed from singular to array
- Arrangements no longer required
- New validation rules for component combinations
- Breaking changes marked with +
Backward Compatibility
- V4.1 systems MUST accept v4.0 transactions (singular refs auto-converted to arrays)
- V4.0 systems MAY reject v4.1 transactions with multiple refs
Table of Contents
- Executive Summary
- Core Protocol Changes
- Valid Component Combinations
- Component Structures (Updated)
- Reference Allocation
- Validation Rules
- Migration Guide (v4.0 + v4.1)
- Examples
- Attestations Extension (Optional)
- Appendices
1. Executive Summary
1.1 What Changed in v4.1
| Aspect | v4.0 | v4.1 | | Arrangements | Required | Optional | | Entry Points | Arrangements only | Any component | | Reference Type | Singular (_ref) | Array (_refs[]) | | Relationship Model | Many-to-one | Many-to-many | | Min Components | 1 Arrangement | 1 of any type |
1.2 Design Principles (Extended from v4.0)
- Source System Trust — FORAY trusts validated ERP data
- 4-Component Model — Arrangements → Accruals → Anticipations → Actions
- Tamper-Evident Anchoring -- Blockchain provides verifiable timestamps
- Privacy Preservation — Hash-based verification without data exposure
- Separation of Concerns — Core protocol separate from audit metadata
- Layered Storage — Right data in right place at right cost
- Sealed Archive — Complete record preserved with forensic-grade controls
- Async Sealing — User-facing latency independent of archive operations
New in v4.1:
- Transaction Fidelity — Model what actually happened, not artificial structures
- Relationship Accuracy — Support real-world many-to-many flows
2. Core Protocol Changes
2.1 Top-Level Schema (Updated)
{
"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:..."
},
"arrangements": [],
"accruals": [],
"anticipations": [],
"actions": [],
"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": {...}
}2.2 Field Definitions (Updated)
| Field | Type | Required | Description | v4.0 Change |
|---|---|---|---|---|
transaction_id |
string | ✓ | Unique identifier | Unchanged |
schema_version |
string | ✓ | Protocol version ("4.1") | Updated value |
timestamp |
ISO-8601 | ✓ | Creation timestamp | Unchanged |
foray_core |
object | ✓ | Core transaction data | Unchanged |
component_hashes |
object | ✓ | Hash of each component | Unchanged |
arrangements |
array | Arrangement components | + Now optional | |
accruals |
array | Accrual components | Unchanged | |
anticipations |
array | Anticipation components | Unchanged | |
actions |
array | Action components | Unchanged | |
merkle_root |
string | ✓ | Root hash of all components | Unchanged |
blockchain_anchor |
object | ✓ | On-chain anchoring details | Unchanged |
audit_data_anchor |
object | Reference to audit_data | Unchanged | |
privacy_metadata |
object | Privacy settings | Unchanged |
+ Breaking Change: Component Requirement
v4.0: arrangements.length >= 1
v4.1: arrangements.length + accruals.length + anticipations.length + actions.length >= 1
At least one component of any type must be present.
3. Valid Component Combinations
3.1 Supported Transaction Patterns
| Pattern ID | Components | Use Case | Example | | FULL_LIFECYCLE | ARR + ACC + ANT + ACT | Complete transaction cycle | Invoice + Payment | | COMMITMENT_ONLY | ARR | Contract signed, no activity yet | Master Service Agreement | | RECOGNITION_ONLY | ACC | Adjusting entry, depreciation | Month-end depreciation | | SETTLEMENT_ONLY | ACT | Immediate cash transaction | Cash sale, petty cash | | EXPECTATION_CHAIN | ARR + ANT | Future obligation recorded | Earnout milestone | | IMMEDIATE_RECOGNITION | ACC + ACT | Sales receipt (instant) | POS transaction | | DEFERRED_SETTLEMENT | ARR + ACC + ANT | Accrued but not yet settled | Accrued expenses | | MULTI_ARRANGEMENT | [ARR, ARR] + ACC | Bundled services | Combined project billing | | CONSOLIDATED_PAYMENT | ACT + [ANT, ANT, ANT] | Single payment, multiple invoices | Batch payment run | | WATERFALL_DISTRIBUTION | ACT + [ANT × n] | Structured finance | RMBS monthly distribution |
3.2 Component Entry Points
Any component type can serve as a transaction entry point:
| Entry Point | Valid When | Typical Use Cases | | Arrangement | Transaction has contractual basis | Contracts, agreements, orders | | Accrual | Recognition without prior arrangement | Adjusting entries, estimates | | Anticipation | Future expectation without arrangement/accrual | Contingent liabilities (rare) | | Action | Immediate settlement, no prior components | Cash receipts, tips, donations |
3.3 Dependency Rules
Components may reference zero, one, or multiple upstream components:
Arrangements: No upstream references (entry points)
dependencies: []
Accruals: Zero or more Arrangements
arrangement_refs: [] | ["ARR_1"] | ["ARR_1", "ARR_2", ...]
Anticipations: Zero or more Accruals, and/or Arrangements
accrual_refs: [] | ["ACC_1"] | ["ACC_1", "ACC_2", ...]
arrangement_refs: [] | ["ARR_1"] | ["ARR_1", "ARR_2", ...]
Actions: Zero or more Anticipations, Accruals, and/or Arrangements
anticipation_refs: [] | ["ANT_1"] | ["ANT_1", "ANT_2", ...]
accrual_refs: [] | ["ACC_1"] | ["ACC_1", "ACC_2", ...]
arrangement_refs: [] | ["ARR_1"] | ["ARR_1", "ARR_2", ...]
4. Component Structures (Updated)
4.1 Arrangements (Unchanged)
{
"id": "ARR_DESCRIPTIVE_ID",
"foray_core": {
"type": "arrangement_type",
"effective_date": "ISO-8601",
"parties": [
{
"role": "party_role",
"name": "Party Name",
"party_id_hash": "sha256:...",
"jurisdiction": "US"
}
],
"description": "Arrangement description",
"total_value": 0.00,
"currency": "USD",
"terms": {
"...arrangement_specific_terms..."
},
"legal_documentation": ["Doc1", "Doc2"],
"dependencies": []
},
"audit_data": {
"...see audit_data specification..."
}
}4.2 Accruals (Updated References)
{
"id": "ACC_DESCRIPTIVE_ID",
"foray_core": {
"arrangement_refs": ["ARR_ID_1", "ARR_ID_2"],
"type": "accrual_type",
"description": "Accrual description",
"computation_method": "Calculated|Valuation|FixedAmount|Estimated",
"formula_id": "sha256:formula_hash",
"inputs": {
"input_name": "value"
},
"output": 0.00,
"currency": "USD",
"accounting_entry": {
"debit": {"account": "account_code", "amount": 0.00},
"credit": {"account": "account_code", "amount": 0.00}
},
"fiscal_period": {
"year": 2026,
"quarter": 1,
"month": 1,
"start": "ISO-8601",
"end": "ISO-8601"
},
"dependencies": ["ARR_..."]
},
"audit_data": {
"...see audit_data specification..."
}
}+ Breaking Change: arrangement_ref + arrangement_refs
| Field | v4.0 | v4.1 | | arrangement_ref | "ARR_ID" (string) | Deprecated | | arrangement_refs | N/A | ["ARR_ID_1", "ARR_ID_2"] (array) |
Migration: arrangement_ref: "ARR_ID" + arrangement_refs: ["ARR_ID"]
Empty allowed: arrangement_refs: [] (for entry-point accruals)
4.3 Anticipations (Updated References)
{
"id": "ANT_DESCRIPTIVE_ID",
"foray_core": {
"accrual_refs": ["ACC_ID_1", "ACC_ID_2"],
"arrangement_refs": ["ARR_ID_1"],
"type": "anticipation_type",
"description": "Anticipation description",
"expected_amount": 0.00,
"currency": "USD",
"expected_date": "ISO-8601",
"condition": "condition_hash_or_null",
"probability_factor": 0.95,
"assumptions": {
"...anticipation_specific_assumptions..."
},
"dependencies": ["ACC_...", "ARR_..."]
},
"audit_data": {
"...see audit_data specification..."
}
}+ Breaking Change: accrual_ref + accrual_refs
| Field | v4.0 | v4.1 | | accrual_ref | "ACC_ID" (string) | Deprecated | | accrual_refs | N/A | ["ACC_ID_1", "ACC_ID_2"] (array) | | arrangement_refs | N/A | ["ARR_ID"] (array) — New |
New capability: Anticipations can now directly reference Arrangements (bypassing Accruals for simple future obligations).
4.4 Actions (Updated References)
{
"id": "ACT_DESCRIPTIVE_ID",
"foray_core": {
"anticipation_refs": ["ANT_ID_1", "ANT_ID_2", "ANT_ID_3"],
"accrual_refs": ["ACC_ID_1"],
"arrangement_refs": ["ARR_ID_1"],
"type": "action_type",
"description": "Action description",
"amount_settled": 0.00,
"currency": "USD",
"settlement_date": "ISO-8601",
"settlement_status": "completed|pending|failed|reversed",
"payment_method": "wire|ach|check|crypto|cash|other",
"counterparty": "Counterparty Name",
"allocations": [
{
"ref": "ANT_ID_1",
"ref_type": "anticipation",
"amount": 0.00,
"currency": "USD"
},
{
"ref": "ANT_ID_2",
"ref_type": "anticipation",
"amount": 0.00,
"currency": "USD"
}
],
"variance": {
"expected": 0.00,
"actual": 0.00,
"difference": 0.00,
"explanation": "variance explanation or null"
},
"dependencies": ["ANT_...", "ACC_...", "ARR_..."]
},
"audit_data": {
"...see audit_data specification..."
}
}+ Breaking Change: anticipation_ref + anticipation_refs
| Field | v4.0 | v4.1 | | anticipation_ref | "ANT_ID" (string) | Deprecated | | anticipation_refs | N/A | ["ANT_ID_1", "ANT_ID_2"] (array) | | accrual_refs | N/A | ["ACC_ID"] (array) — New | | arrangement_refs | N/A | ["ARR_ID"] (array) — New | | allocations | N/A | Array of allocation objects — New |
New capability: Actions can reference multiple upstream components of any type, with optional allocation breakdown.
5. Reference Allocation
5.1 Allocation Object Structure
When an Action settles multiple Anticipations (or references multiple upstream components), use the allocations array to track how the settlement amount maps to each reference:
{
"allocations": [
{
"ref": "ANT_INVOICE_001",
"ref_type": "anticipation",
"amount": 3000.00,
"currency": "USD",
"allocation_type": "full|partial|overpayment",
"remaining_balance": 0.00
},
{
"ref": "ANT_INVOICE_002",
"ref_type": "anticipation",
"amount": 4000.00,
"currency": "USD",
"allocation_type": "full",
"remaining_balance": 0.00
},
{
"ref": "ANT_INVOICE_003",
"ref_type": "anticipation",
"amount": 3000.00,
"currency": "USD",
"allocation_type": "partial",
"remaining_balance": 500.00
}
]
}5.2 Allocation Field Definitions
| Field | Type | Required | Description |
|---|---|---|---|
ref |
string | ✓ | ID of referenced component |
ref_type |
enum | ✓ | anticipation | accrual | arrangement |
amount |
number | ✓ | Amount allocated to this reference |
currency |
string | ✓ | Currency code (ISO 4217) |
allocation_type |
enum | full | partial | overpayment |
|
remaining_balance |
number | Remaining balance after allocation |
5.3 Validation Rules for Allocations
- Sum must match:
SUM(allocations[].amount)MUST equalamount_settled - References must exist: All
refvalues must exist in the transaction - Type must match:
ref_typemust match the actual component type - Currency consistency: All allocations should use consistent currency (or specify conversion)
6. Validation Rules
6.1 Transaction-Level Validation
function validateTransaction(tx) {
const errors = [];
// Rule 1: At least one component required
const totalComponents =
tx.arrangements.length +
tx.accruals.length +
tx.anticipations.length +
tx.actions.length;
if (totalComponents === 0) {
errors.push("At least one component required");
}
// Rule 2: Schema version must be 4.1
if (tx.schema_version !== "4.1") {
errors.push("Schema version must be '4.1'");
}
// Rule 3: All referenced IDs must exist
const allIds = getAllComponentIds(tx);
const allRefs = getAllReferences(tx);
for (const ref of allRefs) {
if (!allIds.includes(ref)) {
errors.push(`Referenced ID '${ref}' does not exist`);
}
}
// Rule 4: No circular dependencies
if (hasCircularDependencies(tx)) {
errors.push("Circular dependencies detected");
}
// Rule 5: Allocation sums must match (if present)
for (const action of tx.actions) {
if (action.foray_core.allocations?.length > 0) {
const sum = action.foray_core.allocations
.reduce((acc, a) => acc + a.amount, 0);
if (Math.abs(sum - action.foray_core.amount_settled) > 0.01) {
errors.push(`Action ${action.id}: allocation sum (${sum}) != amount_settled (${action.foray_core.amount_settled})`);
}
}
}
return { valid: errors.length === 0, errors };
}6.2 Component-Level Validation
Arrangements
dependenciesmust be empty (arrangements are entry points)partiesmust have at least one entry
Accruals
arrangement_refsmay be empty or contain valid ARR_* IDsoutputmust be a numberaccounting_entrydebits must equal credits
Anticipations
accrual_refsand/orarrangement_refsmay be empty or contain valid IDsprobability_factormust be between 0.0 and 1.0expected_dateshould be in the future (warning, not error)
Actions
- Reference arrays may be empty (for entry-point actions)
amount_settledmust be a numbersettlement_statusmust be valid enum valueallocations(if present) must sum toamount_settled
6.3 Reference Type Validation
function validateReferences(component, componentType) {
const errors = [];
const core = component.foray_core;
switch (componentType) {
case 'arrangement':
// Arrangements have no upstream refs
if (core.arrangement_refs?.length > 0) {
errors.push("Arrangements cannot have arrangement_refs");
}
break;
case 'accrual':
// Accruals can only ref Arrangements
if (core.accrual_refs?.length > 0) {
errors.push("Accruals cannot have accrual_refs");
}
if (core.anticipation_refs?.length > 0) {
errors.push("Accruals cannot have anticipation_refs");
}
break;
case 'anticipation':
// Anticipations can ref Arrangements and Accruals
if (core.anticipation_refs?.length > 0) {
errors.push("Anticipations cannot have anticipation_refs");
}
break;
case 'action':
// Actions can ref any upstream type
// No restrictions
break;
}
return errors;
}7. Migration Guide (v4.0 + v4.1)
7.1 Breaking Changes Summary
| Change | v4.0 | v4.1 | Migration Action | | arrangement_ref | String | Array | Wrap in array | | accrual_ref | String | Array | Wrap in array | | anticipation_ref | String | Array | Wrap in array | | Arrangements | Required | Optional | No action needed | | schema_version | "4.0" | "4.1" | Update value |
7.2 Automated Migration Script
function migrateV40toV41(v40Transaction) {
const v41 = JSON.parse(JSON.stringify(v40Transaction));
// Update schema version
v41.schema_version = "4.1";
// Migrate Accruals
for (const accrual of v41.accruals || []) {
const core = accrual.foray_core;
// Convert singular ref to array
if (core.arrangement_ref !== undefined) {
core.arrangement_refs = core.arrangement_ref
? [core.arrangement_ref]
: [];
delete core.arrangement_ref;
}
// Ensure array exists
if (!core.arrangement_refs) {
core.arrangement_refs = [];
}
}
// Migrate Anticipations
for (const anticipation of v41.anticipations || []) {
const core = anticipation.foray_core;
// Convert singular ref to array
if (core.accrual_ref !== undefined) {
core.accrual_refs = core.accrual_ref
? [core.accrual_ref]
: [];
delete core.accrual_ref;
}
// Ensure arrays exist
if (!core.accrual_refs) core.accrual_refs = [];
if (!core.arrangement_refs) core.arrangement_refs = [];
}
// Migrate Actions
for (const action of v41.actions || []) {
const core = action.foray_core;
// Convert singular ref to array
if (core.anticipation_ref !== undefined) {
core.anticipation_refs = core.anticipation_ref
? [core.anticipation_ref]
: [];
delete core.anticipation_ref;
}
// Ensure arrays exist
if (!core.anticipation_refs) core.anticipation_refs = [];
if (!core.accrual_refs) core.accrual_refs = [];
if (!core.arrangement_refs) core.arrangement_refs = [];
}
return v41;
}7.3 Backward Compatibility
V4.1 implementations SHOULD accept v4.0 transactions by:
- Detecting
schema_version: "4.0" - Auto-converting singular
_reffields to_refsarrays - Processing as v4.1
function ensureV41Compatibility(transaction) {
if (transaction.schema_version === "4.0") {
return migrateV40toV41(transaction);
}
return transaction;
}7.4 Forward Compatibility
V4.0 implementations can process v4.1 transactions with single references by:
- Checking that all
_refsarrays have length <= 1 - Extracting single value:
arrangement_ref = arrangement_refs[0] || null - Rejecting transactions with multiple references
function downgradeToV40(v41Transaction) {
// Check for multi-reference usage
for (const accrual of v41Transaction.accruals || []) {
if (accrual.foray_core.arrangement_refs?.length > 1) {
throw new Error("Cannot downgrade: multiple arrangement_refs");
}
}
// ... similar checks for other components
// Perform downgrade
const v40 = JSON.parse(JSON.stringify(v41Transaction));
v40.schema_version = "4.0";
for (const accrual of v40.accruals || []) {
const core = accrual.foray_core;
core.arrangement_ref = core.arrangement_refs?.[0] || null;
delete core.arrangement_refs;
}
// ... similar for other components
return v40;
}8. Examples
8.1 Example: Cash Sale (Action-Only Transaction)
Scenario: Walk-in customer pays $50 cash for a product. No prior arrangement, no invoice.
{
"transaction_id": "RETAIL_2026_Q1_CASH_SALE_001",
"schema_version": "4.1",
"timestamp": "2026-01-25T14:30:00Z",
"foray_core": {
"entity": "Main Street Coffee Shop",
"entity_hash": "sha256:abc123...",
"transaction_type": "cash_sale",
"total_value": 50.00,
"currency": "USD",
"status": "completed",
"compliance_flags": []
},
"component_hashes": {
"arrangements": "sha256:e3b0c44...",
"accruals": "sha256:e3b0c44...",
"anticipations": "sha256:e3b0c44...",
"actions": "sha256:7d8f2a1..."
},
"arrangements": [],
"accruals": [],
"anticipations": [],
"actions": [
{
"id": "ACT_CASH_SALE_001",
"foray_core": {
"anticipation_refs": [],
"accrual_refs": [],
"arrangement_refs": [],
"type": "cash_receipt",
"description": "Walk-in cash sale - coffee and pastry",
"amount_settled": 50.00,
"currency": "USD",
"settlement_date": "2026-01-25T14:30:00Z",
"settlement_status": "completed",
"payment_method": "cash",
"counterparty": "Walk-in Customer",
"dependencies": []
}
}
],
"merkle_root": "sha256:9f8e7d6...",
"blockchain_anchor": {
"kaspa_tx_id": "kaspa:qr...",
"block_height": 2850000,
"confirmation_time_ms": 1100,
"anchored_at": "2026-01-25T14:30:02Z"
}
}8.2 Example: Depreciation Entry (Accrual-Only Transaction)
Scenario: Month-end depreciation adjustment for equipment.
{
"transaction_id": "ACCT_2026_Q1_DEPRECIATION_JAN",
"schema_version": "4.1",
"timestamp": "2026-01-31T23:59:00Z",
"foray_core": {
"entity": "Acme Corporation",
"entity_hash": "sha256:def456...",
"transaction_type": "adjusting_entry",
"total_value": 2500.00,
"currency": "USD",
"status": "completed",
"compliance_flags": ["GAAP", "SOX_404"]
},
"arrangements": [],
"accruals": [
{
"id": "ACC_DEPRECIATION_JAN_2026",
"foray_core": {
"arrangement_refs": [],
"type": "depreciation_expense",
"description": "January 2026 equipment depreciation - straight line",
"computation_method": "Calculated",
"formula_id": "sha256:straightline...",
"inputs": {
"asset_cost": 300000.00,
"salvage_value": 0.00,
"useful_life_months": 120,
"months_elapsed": 1
},
"output": 2500.00,
"currency": "USD",
"accounting_entry": {
"debit": {"account": "6100-Depreciation Expense", "amount": 2500.00},
"credit": {"account": "1510-Accumulated Depreciation", "amount": 2500.00}
},
"fiscal_period": {
"year": 2026,
"quarter": 1,
"month": 1,
"start": "2026-01-01",
"end": "2026-01-31"
},
"dependencies": []
}
}
],
"anticipations": [],
"actions": [],
"merkle_root": "sha256:abc789..."
}8.3 Example: Consolidated Payment (Many-to-Many)
Scenario: Single $10,000 payment clears three invoices.
{
"transaction_id": "AP_2026_Q1_BATCH_PAYMENT_001",
"schema_version": "4.1",
"timestamp": "2026-01-25T10:00:00Z",
"foray_core": {
"entity": "Acme Corporation",
"entity_hash": "sha256:def456...",
"transaction_type": "batch_payment",
"total_value": 10000.00,
"currency": "USD",
"status": "completed",
"compliance_flags": ["SOX_404"]
},
"arrangements": [
{
"id": "ARR_VENDOR_MSA_TECHSUPPLY",
"foray_core": {
"type": "master_service_agreement",
"effective_date": "2025-01-01T00:00:00Z",
"parties": [
{"role": "buyer", "name": "Acme Corporation", "jurisdiction": "US"},
{"role": "vendor", "name": "TechSupply Inc", "jurisdiction": "US"}
],
"description": "Master agreement for IT supplies",
"total_value": 100000.00,
"currency": "USD",
"dependencies": []
}
}
],
"accruals": [
{
"id": "ACC_INVOICE_001",
"foray_core": {
"arrangement_refs": ["ARR_VENDOR_MSA_TECHSUPPLY"],
"type": "vendor_invoice",
"description": "Invoice #INV-001 - Laptop computers",
"computation_method": "FixedAmount",
"output": 3000.00,
"currency": "USD",
"accounting_entry": {
"debit": {"account": "1400-IT Equipment", "amount": 3000.00},
"credit": {"account": "2100-Accounts Payable", "amount": 3000.00}
},
"dependencies": ["ARR_VENDOR_MSA_TECHSUPPLY"]
}
},
{
"id": "ACC_INVOICE_002",
"foray_core": {
"arrangement_refs": ["ARR_VENDOR_MSA_TECHSUPPLY"],
"type": "vendor_invoice",
"description": "Invoice #INV-002 - Monitors",
"computation_method": "FixedAmount",
"output": 4000.00,
"currency": "USD",
"accounting_entry": {
"debit": {"account": "1400-IT Equipment", "amount": 4000.00},
"credit": {"account": "2100-Accounts Payable", "amount": 4000.00}
},
"dependencies": ["ARR_VENDOR_MSA_TECHSUPPLY"]
}
},
{
"id": "ACC_INVOICE_003",
"foray_core": {
"arrangement_refs": ["ARR_VENDOR_MSA_TECHSUPPLY"],
"type": "vendor_invoice",
"description": "Invoice #INV-003 - Keyboards and mice",
"computation_method": "FixedAmount",
"output": 3000.00,
"currency": "USD",
"accounting_entry": {
"debit": {"account": "6200-Office Supplies", "amount": 3000.00},
"credit": {"account": "2100-Accounts Payable", "amount": 3000.00}
},
"dependencies": ["ARR_VENDOR_MSA_TECHSUPPLY"]
}
}
],
"anticipations": [
{
"id": "ANT_INVOICE_001_DUE",
"foray_core": {
"accrual_refs": ["ACC_INVOICE_001"],
"arrangement_refs": [],
"type": "payment_due",
"description": "Payment due for INV-001",
"expected_amount": 3000.00,
"currency": "USD",
"expected_date": "2026-02-01T00:00:00Z",
"probability_factor": 1.0,
"dependencies": ["ACC_INVOICE_001"]
}
},
{
"id": "ANT_INVOICE_002_DUE",
"foray_core": {
"accrual_refs": ["ACC_INVOICE_002"],
"arrangement_refs": [],
"type": "payment_due",
"description": "Payment due for INV-002",
"expected_amount": 4000.00,
"currency": "USD",
"expected_date": "2026-02-01T00:00:00Z",
"probability_factor": 1.0,
"dependencies": ["ACC_INVOICE_002"]
}
},
{
"id": "ANT_INVOICE_003_DUE",
"foray_core": {
"accrual_refs": ["ACC_INVOICE_003"],
"arrangement_refs": [],
"type": "payment_due",
"description": "Payment due for INV-003",
"expected_amount": 3000.00,
"currency": "USD",
"expected_date": "2026-02-01T00:00:00Z",
"probability_factor": 1.0,
"dependencies": ["ACC_INVOICE_003"]
}
}
],
"actions": [
{
"id": "ACT_BATCH_PAYMENT_001",
"foray_core": {
"anticipation_refs": [
"ANT_INVOICE_001_DUE",
"ANT_INVOICE_002_DUE",
"ANT_INVOICE_003_DUE"
],
"accrual_refs": [],
"arrangement_refs": [],
"type": "vendor_payment",
"description": "Consolidated payment for invoices 001, 002, 003",
"amount_settled": 10000.00,
"currency": "USD",
"settlement_date": "2026-01-25T10:00:00Z",
"settlement_status": "completed",
"payment_method": "ach",
"counterparty": "TechSupply Inc",
"allocations": [
{
"ref": "ANT_INVOICE_001_DUE",
"ref_type": "anticipation",
"amount": 3000.00,
"currency": "USD",
"allocation_type": "full",
"remaining_balance": 0.00
},
{
"ref": "ANT_INVOICE_002_DUE",
"ref_type": "anticipation",
"amount": 4000.00,
"currency": "USD",
"allocation_type": "full",
"remaining_balance": 0.00
},
{
"ref": "ANT_INVOICE_003_DUE",
"ref_type": "anticipation",
"amount": 3000.00,
"currency": "USD",
"allocation_type": "full",
"remaining_balance": 0.00
}
],
"dependencies": [
"ANT_INVOICE_001_DUE",
"ANT_INVOICE_002_DUE",
"ANT_INVOICE_003_DUE"
]
}
}
],
"merkle_root": "sha256:consolidated123..."
}8.4 Example: RMBS Waterfall Distribution (Complex Many-to-Many)
Scenario: Monthly distribution from RMBS trust to multiple tranches.
{
"transaction_id": "RMBS_2026_Q1_DISTRIBUTION_FEB",
"schema_version": "4.1",
"timestamp": "2026-02-20T15:00:00Z",
"foray_core": {
"entity": "RMBS Trust 2026-A",
"entity_hash": "sha256:rmbs789...",
"transaction_type": "waterfall_distribution",
"total_value": 6200000.00,
"currency": "USD",
"status": "completed",
"compliance_flags": ["SEC_REG_AB", "DODD_FRANK"]
},
"arrangements": [
{
"id": "ARR_TRANCHE_A_AAA",
"foray_core": {
"type": "tranche_certificate",
"parties": [
{"role": "issuer", "name": "RMBS Trust 2026-A"},
{"role": "holders", "name": "Class A Certificateholders"}
],
"total_value": 240000000.00,
"terms": {"coupon_rate": 0.045, "priority": 1}
}
},
{
"id": "ARR_TRANCHE_M1_AA",
"foray_core": {
"type": "tranche_certificate",
"parties": [
{"role": "issuer", "name": "RMBS Trust 2026-A"},
{"role": "holders", "name": "Class M-1 Certificateholders"}
],
"total_value": 30000000.00,
"terms": {"coupon_rate": 0.055, "priority": 2}
}
},
{
"id": "ARR_TRANCHE_B_BB",
"foray_core": {
"type": "tranche_certificate",
"parties": [
{"role": "issuer", "name": "RMBS Trust 2026-A"},
{"role": "holders", "name": "Class B Certificateholders"}
],
"total_value": 15000000.00,
"terms": {"coupon_rate": 0.075, "priority": 3}
}
}
],
"accruals": [
{
"id": "ACC_CLASS_A_INTEREST_FEB",
"foray_core": {
"arrangement_refs": ["ARR_TRANCHE_A_AAA"],
"type": "tranche_interest",
"computation_method": "Calculated",
"formula_id": "sha256:interest_calc...",
"inputs": {"principal": 240000000, "rate": 0.045, "days": 28},
"output": 829315.07,
"currency": "USD"
}
},
{
"id": "ACC_CLASS_M1_INTEREST_FEB",
"foray_core": {
"arrangement_refs": ["ARR_TRANCHE_M1_AA"],
"type": "tranche_interest",
"computation_method": "Calculated",
"output": 126712.33,
"currency": "USD"
}
},
{
"id": "ACC_CLASS_B_INTEREST_FEB",
"foray_core": {
"arrangement_refs": ["ARR_TRANCHE_B_BB"],
"type": "tranche_interest",
"computation_method": "Calculated",
"output": 86301.37,
"currency": "USD"
}
}
],
"anticipations": [
{
"id": "ANT_CLASS_A_DISTRIBUTION_FEB",
"foray_core": {
"accrual_refs": ["ACC_CLASS_A_INTEREST_FEB"],
"arrangement_refs": ["ARR_TRANCHE_A_AAA"],
"type": "tranche_distribution",
"expected_amount": 2329315.07,
"description": "Class A interest + principal",
"expected_date": "2026-02-20T00:00:00Z",
"probability_factor": 1.0
}
},
{
"id": "ANT_CLASS_M1_DISTRIBUTION_FEB",
"foray_core": {
"accrual_refs": ["ACC_CLASS_M1_INTEREST_FEB"],
"arrangement_refs": ["ARR_TRANCHE_M1_AA"],
"type": "tranche_distribution",
"expected_amount": 326712.33,
"expected_date": "2026-02-20T00:00:00Z",
"probability_factor": 1.0
}
},
{
"id": "ANT_CLASS_B_DISTRIBUTION_FEB",
"foray_core": {
"accrual_refs": ["ACC_CLASS_B_INTEREST_FEB"],
"arrangement_refs": ["ARR_TRANCHE_B_BB"],
"type": "tranche_distribution",
"expected_amount": 86301.37,
"expected_date": "2026-02-20T00:00:00Z",
"probability_factor": 1.0
}
}
],
"actions": [
{
"id": "ACT_WATERFALL_DISTRIBUTION_FEB",
"foray_core": {
"anticipation_refs": [
"ANT_CLASS_A_DISTRIBUTION_FEB",
"ANT_CLASS_M1_DISTRIBUTION_FEB",
"ANT_CLASS_B_DISTRIBUTION_FEB"
],
"accrual_refs": [],
"arrangement_refs": [],
"type": "waterfall_settlement",
"description": "February 2026 waterfall distribution to all tranches",
"amount_settled": 2742328.77,
"currency": "USD",
"settlement_date": "2026-02-20T15:00:00Z",
"settlement_status": "completed",
"payment_method": "wire",
"counterparty": "BNY Mellon (Trustee)",
"allocations": [
{
"ref": "ANT_CLASS_A_DISTRIBUTION_FEB",
"ref_type": "anticipation",
"amount": 2329315.07,
"currency": "USD",
"allocation_type": "full"
},
{
"ref": "ANT_CLASS_M1_DISTRIBUTION_FEB",
"ref_type": "anticipation",
"amount": 326712.33,
"currency": "USD",
"allocation_type": "full"
},
{
"ref": "ANT_CLASS_B_DISTRIBUTION_FEB",
"ref_type": "anticipation",
"amount": 86301.37,
"currency": "USD",
"allocation_type": "full"
}
],
"dependencies": [
"ANT_CLASS_A_DISTRIBUTION_FEB",
"ANT_CLASS_M1_DISTRIBUTION_FEB",
"ANT_CLASS_B_DISTRIBUTION_FEB"
]
}
}
],
"merkle_root": "sha256:waterfall456..."
}9. Attestations Extension (Optional)
9.1 Overview
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.
When to Use Attestations
| Transaction Type | Core 4A | Attestations Extension |
|---|---|---|
| Invoice payments | ✓ | n/a |
| Payroll processing | ✓ | n/a |
| Loan payments | ✓ | n/a |
| Manufacturing work orders | ✓ | n/a |
| Product provenance | ✓ | ✓ |
| Luxury authentication | ✓ | ✓ |
| Regulatory certifications | ✓ | ✓ |
| Third-party inspections | ✓ | ✓ |
| Audit sign-offs | ✓ | ✓ |
Design Rationale
Peer Review Concern:
"Without attestation/assertion, your audit trail is just 'he said, she said' with cryptographic wrapping."
Response: The Attestations extension records who made claims, what they claimed, when they claimed it, and what credentials they have. This creates accountability infrastructure—claimants are permanently associated with their claims.
Important Limitation: Attestations record claims, not truth. FORAY proves that a party made a claim at a specific time; it does not independently verify that the claim is accurate.
9.2 Attestation Component Structure
{
"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",
"dependencies": ["ATT_LAB_ANALYSIS_001"]
}
}9.3 Field Definitions
| Field | Type | Required | Description |
|---|---|---|---|
id |
string | ✓ | Unique identifier, prefix ATT_ |
attestor |
string | ✓ | Identity of the attesting party |
attestor_hash |
string | ✓ | Salted hash of attestor identity |
attestor_type |
enum | ✓ | Category of attestor |
attestor_credentials |
array | Qualifications giving attestation weight | |
subject_refs |
array | ✓ | Component IDs this attestation validates |
attestation_type |
enum | ✓ | Type of attestation |
attestation_date |
ISO-8601 | ✓ | When attestation was made |
validity_period |
object | Start/end dates if attestation expires | |
outcome |
enum | ✓ | Result of attestation |
evidence_hash |
string | Hash of supporting documentation | |
evidence_location |
string | Where evidence is stored | |
dependencies |
array | Other attestations this depends on |
Attestor Types
| Type | Description | Example |
|---|---|---|
certification_body |
Authorized certification organization | DOP Consorzio, ISO registrar |
laboratory |
Testing/analysis facility | Spectroscopic analysis lab |
auditor |
Professional auditor | Big Four firm, internal audit |
inspector |
Qualified inspection personnel | Customs, quality control |
oracle |
Automated/sensor-based system | IoT device, GPS tracker |
regulator |
Government regulatory body | FDA, DCAA, SEC |
Attestation Types
| Type | Description | Example |
|---|---|---|
certification |
Formal certification | DOP, ISO 9001, COSC |
inspection |
Physical examination | Customs clearance, QC check |
analysis |
Laboratory/technical analysis | Spectroscopic fingerprint |
audit_opinion |
Professional audit assessment | Unqualified opinion |
verification |
Fact confirmation | Delivery receipt, payment confirmation |
approval |
Authorization/sign-off | Manager approval, board resolution |
Outcome Values
| Value | Description |
|---|---|
certified |
Passed certification requirements |
approved |
Approved by attestor |
rejected |
Failed requirements |
conditional |
Approved with conditions |
expired |
Previously valid, now expired |
revoked |
Withdrawn after issuance |
pending |
Awaiting final determination |
9.4 Reference Relationships
Attestations can reference any other component type:
Attestations --references---> Arrangements
Attestations --references---> Accruals
Attestations --references---> Anticipations
Attestations --references---> Actions
Attestations --references---> Attestations (chains)
Attestation Chains
Attestations can reference other attestations to create validation chains:
ATT_LAB_ANALYSIS_001 (Laboratory analyzes sample)
|
v
ATT_CERTIFIER_REVIEW_001 (Certifier reviews lab results)
|
v
ATT_FINAL_CERTIFICATION_001 (Final certification issued)
9.5 Schema Extension
When using attestations, add to component_hashes:
"component_hashes": {
"arrangements": "sha256:...",
"accruals": "sha256:...",
"anticipations": "sha256:...",
"actions": "sha256:...",
"attestations": "sha256:..."
}Add attestations array to transaction:
{
"transaction_id": "...",
"schema_version": "4.1",
"arrangements": [...],
"accruals": [...],
"anticipations": [...],
"actions": [...],
"attestations": [...]
}9.6 Validation Rules
| Rule | Description |
|---|---|
| ATT-001 | id must be unique and begin with ATT_ |
| ATT-002 | subject_refs must reference valid component IDs |
| ATT-003 | attestation_date must not be in the future |
| ATT-004 | If validity_period.end < current date, outcome should be expired |
| ATT-005 | attestor_hash must be valid SHA-256 |
| ATT-006 | Circular attestation references are prohibited |
9.7 Trust Model
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
What Attestations Do NOT Prove:
- The claim is true
- The attestor is competent
- Physical reality matches the digital record
Accountability Value: If an attestation is later found to be false, the permanent record identifies who made the false claim and when.
For detailed trust model analysis, see: FORAY_Attestation_Trust_Model.md
10. Appendices
10.1 Reference Field Migration Cheatsheet
| Component | v4.0 Field | v4.1 Field | Type Change | | Accrual | arrangement_ref | arrangement_refs | string + array | | Anticipation | accrual_ref | accrual_refs | string + array | | Anticipation | N/A | arrangement_refs | New field | | Action | anticipation_ref | anticipation_refs | string + array | | Action | N/A | accrual_refs | New field | | Action | N/A | arrangement_refs | New field | | Action | N/A | allocations | New field |
10.2 Valid Reference Combinations
| Component | Can Reference | | Arrangement | Nothing (entry point) | | Accrual | Arrangements only | | Anticipation | Arrangements, Accruals | | Action | Arrangements, Accruals, Anticipations |
10.3 JSON Schema (Summary)
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "FORAY Protocol v4.1",
"type": "object",
"required": ["transaction_id", "schema_version", "timestamp", "foray_core", "merkle_root"],
"properties": {
"schema_version": {"const": "4.1"},
"arrangements": {
"type": "array",
"items": {"$ref": "#/definitions/Arrangement"}
},
"accruals": {
"type": "array",
"items": {"$ref": "#/definitions/Accrual"}
},
"anticipations": {
"type": "array",
"items": {"$ref": "#/definitions/Anticipation"}
},
"actions": {
"type": "array",
"items": {"$ref": "#/definitions/Action"}
}
},
"definitions": {
"Accrual": {
"properties": {
"foray_core": {
"properties": {
"arrangement_refs": {
"type": "array",
"items": {"type": "string", "pattern": "^ARR_"}
}
}
}
}
},
"Anticipation": {
"properties": {
"foray_core": {
"properties": {
"accrual_refs": {
"type": "array",
"items": {"type": "string", "pattern": "^ACC_"}
},
"arrangement_refs": {
"type": "array",
"items": {"type": "string", "pattern": "^ARR_"}
}
}
}
}
},
"Action": {
"properties": {
"foray_core": {
"properties": {
"anticipation_refs": {
"type": "array",
"items": {"type": "string", "pattern": "^ANT_"}
},
"accrual_refs": {
"type": "array",
"items": {"type": "string", "pattern": "^ACC_"}
},
"arrangement_refs": {
"type": "array",
"items": {"type": "string", "pattern": "^ARR_"}
},
"allocations": {
"type": "array",
"items": {"$ref": "#/definitions/Allocation"}
}
}
}
}
},
"Allocation": {
"type": "object",
"required": ["ref", "ref_type", "amount", "currency"],
"properties": {
"ref": {"type": "string"},
"ref_type": {"enum": ["anticipation", "accrual", "arrangement"]},
"amount": {"type": "number"},
"currency": {"type": "string"},
"allocation_type": {"enum": ["full", "partial", "overpayment"]},
"remaining_balance": {"type": "number"}
}
}
}
}Document Footer
Disclaimer
DISCLAIMER: This document describes the FORAY Protocol specification as of January 2026. All performance metrics, security claims, and compliance statements are design goals based on theoretical analysis. They do not constitute guarantees. Organizations should consult qualified professionals for specific compliance requirements.
Change Log
| Date | Version | Author | Changes | | 2026-01-24 | 4.0-DRAFT | DUNIN7 | Initial v4.0 specification | | 2026-01-25 | 4.1-DRAFT | DUNIN7 | Flexible entry points, many-to-many references |
Summary of v4.1 Changes
- Arrangements now optional — Any component type can serve as transaction entry point
- Many-to-many references —
_reffields replaced with_refs[]arrays - New allocation tracking — Actions can specify how settlements map to multiple references
- Enhanced validation — New rules for component combinations and reference integrity
- Backward compatible — V4.0 transactions auto-migrate to V4.1 format
Approval
| Role | Name | Date | Signature | | Protocol Lead | | | | | Technical Review | | | | | Legal Review | | | |
END OF FORAY PROTOCOL SPECIFICATION v4.1
Accepted amendment
FORAY Protocol v4.1.3 — Batch Anchoring Amendment
Publication edition of the FORAY Batch Anchoring Amendment v4.1.3 (ACCEPTED 2026-07-13). Normative content is unchanged from the committed amendment document. Internal changelog and open-items sections are omitted from this edition. Publication edition; historical project names in the committed source are rendered neutrally. Contact and attribution are rendered organizationally.
1. Purpose and Scope
The v4.1 specification leaves implicit that the protocol does not require one Kaspa write per transaction. This amendment makes batch anchoring explicit and extends it to a two-tier model serving many concurrent high-volume consumers (agentic identity systems, trading systems, large ERP estates) against Kaspa's finite, shared write capacity.
This amendment is additive and backward-compatible. Existing envelopes produced by the pre-ABH demonstration implementations remain verifiable (§3.1 legacy class); the live paths at foray.dunin7.com and foraychallenge.dunin7.com continue to work unchanged.
2. Design principles (normative posture)
- Minimal blockchain footprint unconditionally. Kaspa is shared public infrastructure with many tenants. FORAY plans against a small absolute write budget (§9), not a share of network capacity. FORAY yields under congestion; it does not compete.
- Everything a verifier must trust is inside the commitment. Off-chain metadata is provable against the anchor, never trusted beside it (§4).
- Claims vs. truth, including about ourselves. FORAY proves integrity (content unchanged since submission, existence by anchor time), never legitimacy — and its own infrastructure records (registry, attestations) are held to the same standard and the same limits (§13, §14).
- Self-similarity. One mechanism — collect, commit, record true count, pass one header upward — at every level: batch, super-batch, aggregate window, checkpoint.
3. Anchoring modes
| Mode | Meaning | Kaspa writes | Intended use |
|---|---|---|---|
direct |
A batch of exactly one transaction (true_leaf_count = 1) anchored via the standard header |
1 per transaction | Demonstrations, reference implementations, genuinely low-frequency streams |
batched |
One header anchors N transactions via Merkle batch, optionally through super-batches and Tier 2 | 1 per window per lane | Production; the default posture for all high-frequency streams |
There is one envelope and one verification path: a direct anchor is simply the degenerate batch. The former open item on direct vs batched envelope semantics is resolved by construction.
3.1 Direct-mode limits (normative)
Direct mode is preserved deliberately — it is the pedagogical on-ramp and the pre-ABH demonstration path — and it is governed, so that it can never become the volume story an evaluator holds against the protocol:
- Rate gate. Direct anchoring is permitted only for registered streams whose declared sustained rate is at or below the direct-mode ceiling: 1 write per minute sustained per registered stream, with direct-mode writes in aggregate capped at 25% of the §9 budget (adopted 2026-07-11; set above expected demonstration-site usage; measurement against actual observed usage owed (see governance record, Sheet 2 — verification pending)). Submissions above the ceiling are refused with guidance to batched mode.
- Budget-priced. Direct writes count fully against the §9 absolute budget. (Escape writes, §5.3, are exempt; direct writes are not.) Budget pressure structurally squeezes direct mode before it can squeeze the network.
- Positioning. Documentation presents direct mode as the reference/teaching mode with a deliberate governor, and batched mode as production. The cap is a feature to show examiners, not an apology.
- Legacy class. Envelopes produced before ABH adoption (the existing pre-ABH demo output) verify as
legacy: cryptographically checked to their original rules — BATCH-001 through BATCH-008 as defined inFORAY_Protocol_v4_1_3_Batch_Anchoring_Amendment-v0_1.md§6, which remains the normative rule source for this class and is retained inspec/amendments/for that reason — labeledLEGACYin verifier output, ineligible for the guarantees that require the ABH (anchored count, stream continuity). Grandfathered, bounded, honest. Note that v0.1's BATCH-007 padding question is answered for the ABH format by §6 of this document; legacy envelopes retain v0.1's stated behavior unchanged.
3.2 Envelope extension point (from FORAY_Envelope_Extension_Point-v0_1.md)
Every anchoring envelope MUST carry a reserved extension object (ext_version, ext_digest), stated normatively per the source document's five rules (source governs on conflict, per that document's own instruction — reconciled, not summarized, as of this version):
- R1 — Presence. The
extensionobject is mandatory in every envelope from v4.1.3 onward. Absence is a conformance failure — never an impliedext_version: 0. - R2 — Opacity. Verifiers MUST parse and preserve the
extensionobject and MUST NOT reject an envelope solely becauseext_versionis unknown to them. They verify what they understand; the extension travels intact for the next hop to use. - R3 — Digest-only on chain. Extension payloads never appear in the anchored commitment in cleartext. Only
ext_digestis committed — matching FORAY's existing privacy posture (salted formula hashes, obfuscated identifiers). - R4 — Version assignment. New
ext_versionvalues are assigned only by a versioned protocol amendment. No private or ad hoc use of nonzero versions. - R5 — No semantic load in v4.1.3. v4.1.3 defines the field and R1–R4 and nothing else. Every envelope produced under v4.1.3 carries
ext_version: 0,ext_digest: null.
Purpose: future amendments (covenant-based anchor lineage was the motivating case) can add semantics without changing envelope shape.
4. The Anchored Batch Header (ABH)
The unit of anchoring is not a bare tree root but a canonical header, serialized with exactly one legal byte encoding, hashed with domain separation, and committed to Kaspa:
batch_header {
header_version : uint8 // format agility; no per-batch hash negotiation
level : uint8 // 0=batch, 1=super-batch, 2=aggregate, 3=checkpoint
tree_root : bytes32
true_leaf_count : uint64 // pre-padding count — anchored, immutable
lane_id : bytes8
window_id : uint64 // monotonic per lane
stream_segments[]: { stream_id: bytes16, first_seq: uint64,
last_seq: uint64, head_hash: bytes32 }
escape_flag : bool
prev_header_hash : bytes32 // chains this submitter's headers
}
anchored_value = H( 0x02 ‖ serialize(batch_header) ) // H = SHA-256 in v4.1.3
Changing any field after anchoring is equivalent to breaking the hash function. Everything outside the header — blobs, trees, salts, sibling paths — is custody material (§11), provable against the header and never trusted on its own.
Figure 1 — inside the commitment vs. merely near it:
5. The two-tier pipeline
Application → FORAY Tx → [Tier 1: witness batch] → ABH → WAL → consumer database(s)
→ (optional §5.4 super-batch) → ABHs from many witnesses/consumers
→ [Tier 2: aggregation window] → aggregate ABH → root list published
→ anchored_value → Kaspa
5.1 Tier 1 — witness batching (per consumer)
- Batches close on count OR timer, whichever first (defaults 512 / 60 s; tunable per lane).
- R1 — never anchor empty. A batch window does not exist until its first item arrives; the max-wait timer arms on that arrival, not on a clock schedule. Consequently no anchor is ever written for an empty batch — every anchor carries at least one item that reached its latency bound. Applies at Tier 1 and Tier 2 alike (§5.2): a Tier-2 window with no closed Tier-1 batches writes nothing.
- Leaves are blinded commitments:
leaf = H(0x00 ‖ tx_hash ‖ leaf_salt),leaf_salt= 32 CSPRNG bytes, unique per leaf. Salts are proof material (§11): stored in consumer custody, covered by dual-write, never published, never sent to Tier 2. An inclusion proof carries only its own leaf's salt; sibling digests are untestable without theirs — dictionary/confirmation attacks against neighbors are dead. - The full batch blob (or at minimum the tree plus salts) stays in the consumer's database. Only headers cross custody boundaries.
5.2 Tier 2 — cross-consumer aggregation
- Accepts signed headers only (§8) from authenticated submitters; never content, never leaf hashes.
- Window closes on count OR timer per lane. The congestion signal is defined and auditable — widening is permitted while the trailing 600-block median fee exceeds 5× the trailing-24-hour median (both recomputable from public chain data; adopted 2026-07-11) — and widening is bounded by each lane's declared
W_max, a hard cap with no override path. Launch lanes: fast (Tier 1 close 512/2 s; W_std 2 s; W_max 10 s; ack_within 2 s; T_max ≈ 12 s) and standard (512/60 s; 60 s; 300 s; 30 s; T_max ≈ 6 min); tunable per lane registration, T_max contractual. Every widening event is published in the window records: timestamp, measured signal, window applied. An adversary who buys congestion buys a public, attributable log of doing so; the attack works only up toW_max, which is priced intoT_max(§7). - Fast-lane
W_maxis set against regulatory reporting obligations first, budget second. - One Kaspa write per window per lane. Writes scale with time, not volume.
5.3 Escape valve (censorship and availability)
Each lane declares an aggregator acknowledgment SLO. On breach, refusal, or unavailability, a consumer MAY anchor its header directly to Kaspa with escape_flag = true. Escape anchors are first-class — verifiers treat them identically; the flag is informational. Escape writes are exempt from the §9 budget but centrally logged; sustained escape volume triggers governance review. Censorship cannot suppress provability — only add one write of cost. (Multi-aggregator federation: v4.2 candidate, explicitly not a dependency.)
5.4 High-frequency channel (super-batches)
A witness closing many batches per second MAY accumulate them into a super-batch (level 1 ABH over batch headers), persisting structured storage per super-batch. Durability-before-propagation invariant (mandatory): no header propagates beyond consumer custody until the material beneath it is durable. The channel SHOULD WAL each batch on close (Stele Agentic ID's mint path already does natively); compaction follows at super-batch cadence. Violations manifest as MATERIAL_UNAVAILABLE (§10.1) with the consumer's name attached.
6. Tree construction: padding, domain separation, leaf format
- Domain separation, mandatory: leaf =
H(0x00‖…), internal node =H(0x01‖L‖R), header =H(0x02‖…). Any path element computed otherwise → INVALID. - Fixed leaf format: exactly the 32-byte blinded commitment of §5.1. Raw or 64-byte leaves are structurally illegal — the internal-node-as-leaf forgery is impossible by construction.
- Padding: duplicate-last-leaf to the next power of two. Safe because
true_leaf_countis anchored (§4) — set-ambiguity attacks now require moving an anchored field. Non-power-of-two batches are the normal case (timer closes); the rule applies recursively at every level. - SHA-256 throughout in v4.1.3; agility only via
header_version; no per-batch negotiation.
Figure 2 — duplicate-last-leaf padding (5 true leaves → 8), count anchored:
7. Temporal semantics and registered streams
Anchor-time proof (replaces "temporal proof" everywhere): FORAY proves a record existed no later than its anchor's Kaspa block time. Claimed in-record timestamps are submitter assertions, verified by no one. Verifier output always shows
anchored_atand the lane'sT_max.Declared worst case: each lane registers
T_max = W_std_max + congestion_cap + T2_window_max.T_maxis contractual and is the lane's official backdating window. All design, latency, and legal analysis usesT_max, never typical latency.Registered streams: a consumer declares
stream_id, scope, and a continuity commitment. Stream transactions carry monotonicseq_noandprev_tx_hash; segment ranges and heads are anchored in every covering ABH. Within a stream, order is structure: retroactive insertion or reordering breaks the chain against an anchored head.Continuity walk (auditor mode): for a stream and range, the verifier reports CONTINUOUS, GAP (missing seq range named), or FORK (two anchored transactions claiming one
(stream_id, seq_no), both anchor references cited). Forks are evidence, not options: anchoring both versions of a record anchors immutable proof of the fork itself.What batching does NOT prove (normative disclosure, mirrored on the site): inclusion never implies completeness; completeness is provable only as a registered stream's continuity; unregistered anchoring proves inclusion only; order inside an unregistered batch is unproven.
The anchor-interval ripple (LATE_RECORDED). A backdated record appended to a registered stream claims a timestamp from a period whose covering anchors already closed without it — that absence is a detectable ripple. Rule: within a registered stream, a transaction whose claimed timestamp precedes the stream's previous covering anchor time MUST carry a
late_recordedmarker with a declared reason code (outage, correction, migration…); verifiers and sentinels flag any such record lacking one, and flag all of them in audit output. This is the accounting posture for late journal entries: permitted, never silent. The ripple is proportional — the deeper the backdate, the more closed anchors it skipped, the louder the finding. Genuinely invisible backdating room in a disciplined stream is thereby the current anchor interval only, notT_maxat large.The causality ripple (reference chronology). FORAY records are reference-rich (
arrangement_refs,accrual_refs,anticipation_refs). Rule: every referenced record's first-anchor time MUST precede (within declared skew tolerance) the referencing record's claimed timestamp. A record "from Monday" that references anything first anchored Wednesday claims to predate something it demonstrably knows about — a self-betraying chronology violation, returned as INVALID-CHRONOLOGY in audit output. The 4A reference chain, designed for audit semantics, doubles as a backdating tripwire: an attacker must fabricate with no references to recent material, which in a genuine business record is itself conspicuous.Catch-up-on-resume (KIP-21 seqcommit horizon). Basis: the seqcommit script-visible finality window is ~12h (Kaspa upstream,
kaspanet/kipsKIP-21; verified at source 2026-07-13). Under R1 (§5.1), a low-activity stream may legitimately idle past this horizon between rare state changes — R1 forbids anchoring purely to stay within the window when there is nothing to anchor. Rule: on resume, the anchoring path MUST reference a recent in-range anchor rather than assuming its own last anchor is still script-visible. Heartbeat anchors (a deliberate, content-free liveness anchor issued before horizon expiry) are REJECTED as the default: they reintroduce exactly the anchor-for-nothing R1 exists to prevent. Kept available as a joint-cadence-pass option only if a consumer guarantee is found to need one.
8. Submitter registry, revocation, replay defense
- The registry is itself a FORAY registered stream — the infrastructure's records get the protocol's own guarantees. Events:
KEY_REGISTER,KEY_ROTATE,KEY_REVOKE(effective-from),DISPUTE(contesting named headers, with reason code). - Submissions:
(header, sig(key, header ‖ window_id ‖ submitter_seq)), per-submitter monotonic sequence; duplicates and non-monotonic sequence rejected. Re-anchoring a tree_root is legal only as a new header carryingreanchor_of— duplication is explicit, never ambient. - Verifier obligation: every verification evaluates the registry stream as of verification time. Headers signed by a key within a revocation window verify cryptographically but return DISPUTED — Kaspa's permanence honored, meaning annotated. Disputes and revocations, being anchored, are as tamper-evident as what they contest.
- Submitter keys are distinct from transaction-signing keys; rotation interval declared at registration; a compromise runbook is an onboarding deliverable. Registry authority governance (who may file revocations) is an acceptance-time decision.
9. Capacity planning (shared network, absolute budget)
- Kaspa demonstrated real-world peak: 5,584 TPS (October 2025 record at 10 blocks/s). The unsourced "10K+ TPS" figure MUST NOT be used (correction task open against site and comparison doc).
- Planning basis: a small absolute system-wide budget — 2 Kaspa writes per second sustained across all FORAY consumers, lanes, and direct-mode use combined (adopted 2026-07-11; ~0.04% of demonstrated peak) — chosen to be negligible to the network, not derived from network capacity. The demonstrated-peak figure appears here only to show the budget is conservative by orders of magnitude.
- Illustrative: one saturated witness core (~4,000 tx/s, Stele Agentic ID measured) closing 512-batches every ~128 ms ⇒ ~7.8 writes/s if anchored directly — which is why Tier 2 is the default posture for high-frequency streams and why direct mode is rate-gated (§3.1).
- Hard backstop: projected sustained writes exceeding the budget ⇒ Tier 2 routing mandatory for all high-frequency lanes; direct-mode onboarding suspended.
- Congestion response: widening per §5.2, bounded by
W_max. FORAY yields; it does not compete.
10. Verification
10.1 Outcome taxonomy (used identically in verifier, spec, and site)
VALID / INVALID / DISPUTED / MATERIAL_UNAVAILABLE / PROVISIONAL / UNSUPPORTED / LEGACY. MATERIAL_UNAVAILABLE = anchor verified, path incomplete (blob, tree, or salt missing); the report names the responsible custodian per §11 — the outcome is designed to be handed to a lawyer. PROVISIONAL belongs to v4.1.4 repository classes. LEGACY per §3.1.
10.2 The Verifier Specification is a first-class artifact
FORAY_Verifier_Specification (separate document, drafting scheduled) fixes input formats, the ordered mandatory check list (anchor lookup → header parse → domain separation → count binding → salt handling → path recomputation → registry/revocation → replay → continuity → checkpoint → mandatory display of assurance class and T_max), and a conformance suite whose negative vectors map one-to-one to the threat review's findings. Only implementations passing the full suite may describe output as "FORAY verified."
11. Proof retention and ownership
- The Kaspa anchor is permanent; proof material (blobs, trees, leaf salts) is not. A lost tree or salt makes an anchor an unprovable commitment.
- Ownership: the originating consumer, for a period aligned to declared
regulatory_complianceflags (SOX 404 ⇒ ~7 years); stated in onboarding terms. - Dual-write (normative): structured proof material is written to at least two independent storage systems at write time — dual-write, not periodic replication — in differing failure domains. (Recovered prior-art principle; §15.)
- Custody: at least two-party — consumer store plus a redundant archive: a WORM repository, duplicated/triplicated across independently administered locations; the archive's terms MUST state whether replicas are
independentorsingle_authority. - Retention attestations: each custodian periodically anchors, in its own registered stream, "material held for headers H_a…H_b, checksum C." Cadence: weekly with a 48-hour grace period (adopted 2026-07-11; revisit to daily at the first regulated trading consumer). Silent loss becomes detectable at the next missed attestation. Attestations are claims — spot-audit is governance's lever, and the spec says so.
12. Agentic-party identity assurance and the trading pattern
Unchanged from v0.8 in substance: agentic parties record identity_assurance (credentialed accepted; asserted defined but disabled pending a deliberate future activation), and the platform-neutral trading-class 4A mapping (mandate/reasoning/intent/execution + venue attestation) stands, with trading streams using the fast lane and — now — registered streams (§7) as the recommended default for any regulated trading deployment.
13. What this amendment does and does not claim
FORAY proves integrity: content unchanged since submission; existence by anchor time; continuity of registered streams; annotated meaning under dispute. It does not prove legitimacy, truth of claims, completeness against reality, attestor competence, or the accuracy of claimed timestamps beyond the anchor bound. Every tier of the pipeline is mechanical. This section is mirrored, verbatim in substance, on the public site (correction task open).
14. Residual-risk register (normative disclosure)
Adopted from the remediation specification; these remain true after every fix in this amendment ships, and stating them here is deliberate:
- Backdating: in unregistered streams, bounded only by
T_max— disclosed, irreducible without trusted time hardware. In registered streams with §7.6/§7.7 in force, invisible backdating shrinks to the current anchor interval, for records referencing nothing newer; deeper reaches are structurally flagged (LATE_RECORDED) or self-betraying (INVALID-CHRONOLOGY). The register keeps both statements because the difference is a reason to register streams, and evaluators should see it. - Compromise-to-detection window for submitter keys — bounded by dispute-and-annotate, not eliminated.
- Completeness against reality — permanently out of scope (claims vs. truth).
- Congestion attack up to
W_max— priced and publicly logged, not prevented. - Custodian attestations are claims — spot-audit is a governance option.
- Non-conformant third-party verifiers — the conformance program is the ceiling of protocol power.
- Kaspa itself failing — v4.1.4's documented Arweave-class alternative is the contingency path.
15. Prior art: the January 2026 three-layer persistence design (recovered)
Authored in the original implementation repository; removed 2026-02-06 in a commit labeled "archive old versions" that archived nothing; recovered from git history July 2026. Why it was removed (Operator, July 2026): a deliberate scope cut under the original delivery deadline — single-transaction persistence was sufficient to prove the model; multi-layer paths were future scope the time did not permit. The prior lineage of this document (v0.7) implied a record-integrity failure; corrected. The residual critique stands: the commit message mislabeled a scope cut as an archival move, which made the design unfindable until recovery.
Lineage: c732fbf (2026-01-26) generic three layers; 958f6e4 (2026-02-01) the specific version — Kaspa/Cardano ‖ MongoDB Atlas dual-written with DocumentDB/Cosmos ‖ Arweave + S3 Glacier. Taken forward: dual-write (§11, normative) and the three-layer shape. Narrowed: Kaspa-only. Consciously different: v4.1.4 checkpoints to Kaspa instead of pairing with Arweave (full comparison in v4.1.4 §5.1). Product names are history, not recommendations.
Contact
- Author: DUNIN7
- Contact: FORAY Information — foray@dunin7.com
- GitHub: github.com/DUNIN7/foray