# 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.**

---

## 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 Kaspathon and Challenge 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)

1. **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.
2. **Everything a verifier must trust is inside the commitment.** Off-chain metadata is provable *against* the anchor, never trusted beside it (§4).
3. **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).
4. **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 Kaspathon/Challenge demonstration path — and it is **governed**, so that it can never become the volume story an evaluator holds against the protocol:

1. **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.
2. **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.
3. **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.
4. **Legacy class.** Envelopes produced before ABH adoption (the existing Kaspathon/Challenge demo output) verify as `legacy`: cryptographically checked to their original rules — **BATCH-001 through BATCH-008 as defined in `FORAY_Protocol_v4_1_3_Batch_Anchoring_Amendment-v0_1.md` §6**, which remains the normative rule source for this class and is retained in `spec/amendments/` for that reason — labeled `LEGACY` in 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):

1. **R1 — Presence.** The `extension` object is mandatory in every envelope from v4.1.3 onward. Absence is a conformance failure — never an implied `ext_version: 0`.
2. **R2 — Opacity.** Verifiers MUST parse and preserve the `extension` object and MUST NOT reject an envelope solely because `ext_version` is unknown to them. They verify what they understand; the extension travels intact for the next hop to use.
3. **R3 — Digest-only on chain.** Extension payloads never appear in the anchored commitment in cleartext. Only `ext_digest` is committed — matching FORAY's existing privacy posture (salted formula hashes, obfuscated identifiers).
4. **R4 — Version assignment.** New `ext_version` values are assigned only by a versioned protocol amendment. No private or ad hoc use of nonzero versions.
5. **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:**

<svg viewBox="0 0 860 300" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Anchored batch header binding diagram">
  <style>
    .hl{font:600 12px 'Avenir Next','Segoe UI',sans-serif;fill:#1a2332}
    .hs{font:10.5px 'Avenir Next','Segoe UI',sans-serif;fill:#5a6b7f}
    .hin{fill:#e3efee;stroke:#0e6e6e;stroke-width:1.3}
    .hout{fill:#ffffff;stroke:#1a2332;stroke-width:1.2;stroke-dasharray:4,3}
    .harrow{stroke:#1a2332;stroke-width:1.6;fill:none;marker-end:url(#hah)}
  </style>
  <defs><marker id="hah" markerWidth="9" markerHeight="9" refX="7" refY="3" orient="auto">
    <path d="M0,0 L7,3 L0,6 z" fill="#1a2332"/></marker></defs>
  <rect x="20" y="30" width="480" height="240" rx="10" fill="#f1efe9" stroke="#0e6e6e" stroke-width="2"/>
  <text class="hl" x="40" y="56" fill="#0e6e6e">INSIDE the anchored commitment (immutable once on Kaspa)</text>
  <rect class="hin" x="40"  y="75" width="130" height="38" rx="5"/><text class="hl" x="105" y="98" text-anchor="middle">tree_root</text>
  <rect class="hin" x="185" y="75" width="140" height="38" rx="5"/><text class="hl" x="255" y="92" text-anchor="middle">true_leaf_count</text><text class="hs" x="255" y="106" text-anchor="middle">count tampering dead</text>
  <rect class="hin" x="340" y="75" width="140" height="38" rx="5"/><text class="hl" x="410" y="92" text-anchor="middle">lane_id · window_id</text><text class="hs" x="410" y="106" text-anchor="middle">replay dead</text>
  <rect class="hin" x="40"  y="130" width="285" height="38" rx="5"/><text class="hl" x="182" y="147" text-anchor="middle">stream_segments (seq ranges + heads)</text><text class="hs" x="182" y="161" text-anchor="middle">omission/fork detectable</text>
  <rect class="hin" x="340" y="130" width="140" height="38" rx="5"/><text class="hl" x="410" y="147" text-anchor="middle">prev_header_hash</text><text class="hs" x="410" y="161" text-anchor="middle">submitter chain</text>
  <rect class="hin" x="40"  y="185" width="130" height="38" rx="5"/><text class="hl" x="105" y="202" text-anchor="middle">escape_flag</text><text class="hs" x="105" y="216" text-anchor="middle">censorship valve</text>
  <rect class="hin" x="185" y="185" width="140" height="38" rx="5"/><text class="hl" x="255" y="202" text-anchor="middle">level + version</text><text class="hs" x="255" y="216" text-anchor="middle">tier confusion blocked</text>
  <path class="harrow" d="M500,150 H560"/>
  <rect x="565" y="118" width="130" height="64" rx="8" fill="#1a2332"/>
  <text x="630" y="145" text-anchor="middle" font-family="'Avenir Next','Segoe UI',sans-serif" font-size="12" font-weight="700" fill="#f4f6f8">H(0x02 ‖ header)</text>
  <text x="630" y="163" text-anchor="middle" font-family="'Avenir Next','Segoe UI',sans-serif" font-size="10" fill="#c9d2dc">→ Kaspa payload</text>
  <rect class="hout" x="565" y="210" width="270" height="60" rx="8"/>
  <text class="hl" x="700" y="232" text-anchor="middle">OUTSIDE (custody, replaceable):</text>
  <text class="hs" x="700" y="250" text-anchor="middle">blobs, trees, leaf salts, sibling paths — provable</text>
  <text class="hs" x="700" y="263" text-anchor="middle">against the header, never trusted on their own</text>
</svg>

## 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 to `W_max`, which is priced into `T_max` (§7).
- Fast-lane `W_max` is 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

1. **Domain separation, mandatory:** leaf = `H(0x00‖…)`, internal node = `H(0x01‖L‖R)`, header = `H(0x02‖…)`. Any path element computed otherwise → INVALID.
2. **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.
3. **Padding: duplicate-last-leaf** to the next power of two. Safe because `true_leaf_count` is 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.
4. 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:**

<svg viewBox="0 0 720 300" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Padding rule diagram">
  <style>
    .lbl2{font:600 12px 'Avenir Next','Segoe UI',sans-serif;fill:#1a2332}
    .sm2{font:11px 'Avenir Next','Segoe UI',sans-serif;fill:#5a6b7f}
    .leaf{fill:#ffffff;stroke:#1a2332;stroke-width:1.3}
    .pad{fill:#f1efe9;stroke:#8a5a00;stroke-width:1.3;stroke-dasharray:4,3}
    .node{fill:#e3efee;stroke:#0e6e6e;stroke-width:1.3}
    .edge{stroke:#5a6b7f;stroke-width:1.2;fill:none}
  </style>
  <g>
    <rect class="leaf" x="30"  y="230" width="66" height="34" rx="5"/><text class="lbl2" x="63"  y="251" text-anchor="middle">L1</text>
    <rect class="leaf" x="116" y="230" width="66" height="34" rx="5"/><text class="lbl2" x="149" y="251" text-anchor="middle">L2</text>
    <rect class="leaf" x="202" y="230" width="66" height="34" rx="5"/><text class="lbl2" x="235" y="251" text-anchor="middle">L3</text>
    <rect class="leaf" x="288" y="230" width="66" height="34" rx="5"/><text class="lbl2" x="321" y="251" text-anchor="middle">L4</text>
    <rect class="leaf" x="374" y="230" width="66" height="34" rx="5"/><text class="lbl2" x="407" y="251" text-anchor="middle">L5</text>
    <rect class="pad"  x="460" y="230" width="66" height="34" rx="5"/><text class="lbl2" x="493" y="251" text-anchor="middle">L5</text>
    <rect class="pad"  x="546" y="230" width="66" height="34" rx="5"/><text class="lbl2" x="579" y="251" text-anchor="middle">L5</text>
    <rect class="pad"  x="632" y="230" width="66" height="34" rx="5"/><text class="lbl2" x="665" y="251" text-anchor="middle">L5</text>
  </g>
  <text class="sm2" x="220" y="288" text-anchor="middle">Ln = H(0x00 ‖ tx_hash ‖ salt) — blinded, fixed 32 bytes</text>
  <text class="sm2" x="579" y="288" text-anchor="middle" fill="#8a5a00">last leaf duplicated ×3 → 8</text>
  <g>
    <rect class="node" x="73"  y="160" width="66" height="30" rx="5"/><text class="lbl2" x="106" y="180" text-anchor="middle">N12</text>
    <rect class="node" x="245" y="160" width="66" height="30" rx="5"/><text class="lbl2" x="278" y="180" text-anchor="middle">N34</text>
    <rect class="node" x="417" y="160" width="66" height="30" rx="5"/><text class="lbl2" x="450" y="180" text-anchor="middle">N55'</text>
    <rect class="node" x="589" y="160" width="66" height="30" rx="5"/><text class="lbl2" x="622" y="180" text-anchor="middle">N5'5'</text>
  </g>
  <path class="edge" d="M63,230 L100,190"/><path class="edge" d="M149,230 L112,190"/>
  <path class="edge" d="M235,230 L272,190"/><path class="edge" d="M321,230 L284,190"/>
  <path class="edge" d="M407,230 L444,190"/><path class="edge" d="M493,230 L456,190"/>
  <path class="edge" d="M579,230 L616,190"/><path class="edge" d="M665,230 L628,190"/>
  <rect class="node" x="159" y="95" width="80" height="30" rx="5"/><text class="lbl2" x="199" y="115" text-anchor="middle">N1234</text>
  <rect class="node" x="503" y="95" width="80" height="30" rx="5"/><text class="lbl2" x="543" y="115" text-anchor="middle">N55'5'5'</text>
  <path class="edge" d="M106,160 L193,125"/><path class="edge" d="M278,160 L205,125"/>
  <path class="edge" d="M450,160 L537,125"/><path class="edge" d="M622,160 L549,125"/>
  <rect x="221" y="24" width="300" height="36" rx="6" fill="#1a2332"/>
  <text x="371" y="47" text-anchor="middle" font-family="'Avenir Next','Segoe UI',sans-serif" font-size="12.5" font-weight="700" fill="#f4f6f8">tree_root + true_leaf_count=5 → ABH → Kaspa</text>
  <path class="edge" d="M199,95 L340,60"/><path class="edge" d="M543,95 L400,60"/>
</svg>

## 7. Temporal semantics and registered streams

1. **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_at` and the lane's `T_max`.
2. **Declared worst case:** each lane registers `T_max = W_std_max + congestion_cap + T2_window_max`. `T_max` is contractual and is the lane's official backdating window. All design, latency, and legal analysis uses `T_max`, never typical latency.
3. **Registered streams:** a consumer declares `stream_id`, scope, and a continuity commitment. Stream transactions carry monotonic `seq_no` and `prev_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.
4. **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.
5. **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.

6. **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_recorded` marker 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, not `T_max` at large.

7. **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.

8. **Catch-up-on-resume (KIP-21 seqcommit horizon).** Basis: the seqcommit script-visible finality window is ~12h (Kaspa upstream, `kaspanet/kips` KIP-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

1. **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).
2. 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 carrying `reanchor_of` — duplication is explicit, never ambient.
3. **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.
4. 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_compliance` flags (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 `independent` or `single_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:

1. 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.
2. Compromise-to-detection window for submitter keys — bounded by dispute-and-annotate, not eliminated.
3. Completeness against reality — permanently out of scope (claims vs. truth).
4. Congestion attack up to `W_max` — priced and publicly logged, not prevented.
5. Custodian attestations are claims — spot-audit is a governance option.
6. Non-conformant third-party verifiers — the conformance program is the ceiling of protocol power.
7. 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 `foray-kaspathon`; 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 Kaspa hackathon 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:** Marvin Percival
- **Email:** marvinp@dunin7.com
- **GitHub:** github.com/DUNIN7/foray
