Document Version: 0.2 (supersedes v0.1 — preserved unmodified in this directory, per the record-doc versioning convention)
Created: 2026-07-24T00:00:00Z
Status: DRAFT — F-15 gate artifact, HOLD for Operator read. This document is the independence artifact: review finding F-15 (register v0.2, adopted from review R2-A) names the Verifier Specification + offline conformance suite as the single largest credibility gap — "verification requires no access to DUNIN7" claimed ahead of the artifact that makes it true. This document and the conformance suite it specifies (foray-api/conformance/) are that artifact.
Normative sources, in authority order:
spec/amendments/FORAY_Protocol_v4_2_F_Wire_Migration_Amendment-v0_7.md (ACCEPTED, in force 2026-07-24) — the wire this verifier verifies: §2 breaking-change table, §4 migration function, §5 dual-version acceptance, §7 canonical serialization at both layers, §8 additions.spec/schema/foray-transaction-v4_2.schema.json — the 4.2 canonical schema, sole owner of wire names (D8), with docs/decisions/foray-42-schema-stage-contract-v0_1.md stating the enforced-vs-delegated split this specification restates as verification law (§5.1).spec/foray-abh-serialization-v0_2.md.Informative (never required): the reference implementation (foray-api/lib/foray-42-canonical.mjs, foray-validate-4-2.mjs, foray-migrate.mjs, foray-tree.mjs, foray-abh.mjs; foray-api/conformance/reference-verifier.mjs), the WP-1 reference verifier (foray/verifier/), and this document's worked derivations. An implementer must be able to proceed from this specification, its normative sources, and the conformance-suite vectors alone. Where this document and a normative source conflict, the source governs; conflicts are defects to be reported, never silently resolved.
Every guarantee in the protocol is realized — or squandered — inside the verifier. A verifier that accepts non-canonical bytes, skips the domain-separation byte, tolerates float error in the allocation sum, or displays ✓ without stating what the ✓ attests converts the architecture into theater. This specification exists so that "FORAY verified" has exactly one meaning — and so that meaning is checkable by a party with no access to DUNIN7 code, services, or people. Everything a conformant verifier needs is: this specification, the normative documents above (all published in this repository), the conformance-suite vectors, and — for anchor-level classes only — the public chain's own data.
This document supersedes Verifier Specification v0.1 (2026-07-11), which was drafted against the 4.1 wire and the proof-bundle input format. What carries forward from v0.1: the outcome taxonomy (§3), the mandatory-display discipline (§7.3), the threat lineage N1–N15 (re-keyed in §4.5), and the test-the-tests requirement (§8.4). What is new: the 4.2 record and legacy-migration input classes (v0.1 verified anchors, never records), the byte-exact serialization procedures of §6, and the offline conformance suite as a shipped artifact rather than a plan.
A verification request presents exactly one of four input classes. A conformant verifier states which class it evaluated; a submission fitting no class is UNSUPPORTED, never silently approximated.
| Class | Input | Question answered |
|---|---|---|
| R — 4.2 record | A record with schema_version: "4.2" — as parsed JSON (data-model judgment) or as bytes (data-model + byte-conformance judgment) | Is this a conformant 4.2 FORAY record? Are these bytes its one legal spelling? |
| L — legacy record | A record with schema_version: "4.1" or "4.0" | Is this record valid against its own version's canonical shape, and what 4.2 record does the frozen migration chain deterministically produce from it? |
| A — anchored record | A record's exact bytes + its leaf_salt + its lane name + direct-mode header context (§5.4), against an on-chain payload | Do these bytes, and no others, reproduce the 32-byte value actually anchored on the public chain? |
| H — batch header | An ABH batch_header — as field values (encode direction) or as bytes (decode direction) | Is this the one legal byte encoding of this header, and what anchored_value does it commit to? |
Classes compose: a full anchored-evidence verification is L (if legacy) or R, then A. Class H is A's serialization core, exposed as its own class because the amendment §7.2 makes the header layout independently normative and independently testable (vector family ABH).
The v0.1 proof-bundle class (Merkle path + header echo + chain/registry/repository context) is carried, not retired: batched-mode path verification remains governed by v4.1.3 §5–§6 and v4.1.4, with its executable vector suite in verifier/vectors/ (WP-1). This document restates its findings in the unified taxonomy (§4.4–§4.5) and adds nothing to and removes nothing from its chain-infrastructure obligations. The classes above are the ones the 4.2 wire break changed or created; the bundle class's record-independent machinery (tree math, registry, repository, streams) is untouched by the break (amendment §9.2–§9.3).
Carried from v0.1 unchanged in membership:
| Outcome | Meaning at v0.2 |
|---|---|
| VALID | Every mandatory check for the evaluated class passed |
| INVALID | A structural, semantic, byte-level, or anchor-level check failed (sub-verdict INVALID-CHRONOLOGY carried for the bundle class, v4.1.3 §7.7) |
| DISPUTED | Cryptographically valid but named by an anchored revocation/DISPUTE record (bundle class, v4.1.3 §8) |
| MATERIAL_UNAVAILABLE | Anchor verified on-chain; required holder-side material (body bytes, salt, header context) missing — the report MUST name the responsible custodian. Never conflated with INVALID: absence of proof material is not evidence of tampering |
| PROVISIONAL / PROVISIONAL-STALE | Repository-class outcomes (v4.1.4 §5), bundle class only |
| DEGRADED-CHAIN | De-registered chain, no history checkpoint (v4.1.4 §3C.5), bundle class only |
| UNSUPPORTED | Unrecognized class, unknown schema_version (§5.3 routing), unregistered chain, or a feature this verifier cannot evaluate — never silently approximated |
Labels (orthogonal to outcomes, always displayed): legacy (class L: record validated against its own version's shape and converted; source version named), non-conformance notice (class L after the conformance terminus — OQ-13: the record is accepted; its production is non-conformant; the notice's presence is configuration fixed at D9 site promotion), LEGACY / LATE_RECORDED / escape-anchored (bundle class, carried from v0.1).
Classes R, L, A, and H produce only VALID / INVALID / MATERIAL_UNAVAILABLE (A only) / UNSUPPORTED. The remaining outcomes belong to the bundle class's chain and repository machinery.
Findings are the reasons attached to a non-VALID outcome (or to warnings/notes beside a VALID one). Four planes. Every finding carries: its code, the JSONPath or byte offset of the culprit, and prose naming the specific violation — named-culprit discipline: "invalid" without the culprit named is itself non-conformant output.
The record's shape against the 4.2 canonical schema — the schema-enforced half of the stage contract:
| Code | Finding |
|---|---|
| E1 | schema_version missing, mismatched, or unroutable |
| E2 | No components: all four component arrays empty or absent |
| E3 | Structural violation of the 4.2 canonical shape: type, requiredness (including F2 presence/non-emptiness, OQ-7), pattern (including F18's ^hash_[0-9a-f]{32}$ and F3's sha256:-prefix form mechanism, OQ-8), enumeration, magnitude shadow (±10¹⁵) |
| E11 | An illegal key, with the specific ruling named: a §2.1 retiree (foray_core, terms, description, entity_hash, dependencies, condition, period, compliance_flags), any 4.1 wire spelling, any foray_* register name used as a wire key (register leak, D-W), a non-catalog F-code (catalog is F1–F29), or residue outside F25's two ruled positions (D6/OQ-6) |
| MIG_VERSION, MIG_INPUT, MIG_CORE_SHAPE, MIG_COLLISION, MIG_F25_COLLISION, MIG_F2_SOURCE, MIG_ELEMENT_SHAPE, MIG_PERIOD_SHAPE, MIG_D5_TYPE, MIG_CONDITION, MIG_UNKNOWN_KEY | Class-L migration failures — the §5.3 chain fails loudly with the named code and no partial output (amendment §4) |
The delegated checks the schema cannot express — stage-contract §2, restated in §5.2 as law:
| Code | Finding |
|---|---|
| E4 | Anticipation missing F27/F28 (named-culprit: superseded spellings expected_date/datetime, probability_factor/probability are called out when present) |
| E5 | Action missing F9 (named-culprit: amount_settled/actual_amount) |
| E7 | Dangling F22/F23/F24 reference |
| E8 | Allocation-sum violation: Σ F21 entry amounts ≠ F9 as exact decimals (§6.2) |
| E9 | F21 ref unresolvable or ref_type mismatch |
| E10 | extension object anywhere in transaction JSON (envelope-only, never record) |
| E12 | Numeric value exceeding 15 significant decimal digits (OQ-2) |
| E14 | Monetary fact with no resolvable currency: component F8 else transaction F8, chain exhausted (OQ-9) |
| N1 (note) | F2 == F1 — legal, defined, dual-reading (native origination / migrated legacy); the legacy label disambiguates |
Byte-level conformance of a record's claimed canonical form (§6.1). On a transport submission (JSON whose member order and whitespace are not asserted canonical), B1 is reported as warning W3 and B2–B4 are not evaluated — a transport submission is judged at the data model. On a canonical-bytes submission (class R bytes, MIG goldens, any hashing path), every B-finding is INVALID:
| Code | Finding |
|---|---|
| B1 | Non-canonical number spelling: exponent form, trailing fractional zeros, -0, or any spelling other than the shortest round-trip plain-decimal (§6.1 step 2). (Superfluous leading zeros and explicit + are additionally excluded by JSON grammar itself.) |
| B2 | Member-order violation: F-coded members not first in ascending numeric F-order; envelope tail not in the ruled enumeration; id not last in a component element; a non-lexicographic interior (§6.1 step 3) |
| B3 | Encoding violation: insignificant whitespace, non-JCS string escaping, non-UTF-8 bytes |
| B4 | One-spelling violation: the submitted bytes are not byte-identical to the canonical serialization of the value they parse to (the umbrella check — B1–B3 name the specific cause when identifiable) |
| Code | Finding | v0.1 lineage |
|---|---|---|
| A1 | Leaf-format/domain-separation violation: leaf ≠ SHA-256(0x00 ‖ tx_hash ‖ leaf_salt) with tx_hash and leaf_salt each exactly 32 bytes; internal node ≠ SHA-256(0x01 ‖ L ‖ R); any raw/64-byte leaf | N1 |
| A2 | Header-binding mismatch: SHA-256(0x02 ‖ serialize(header)) ≠ the anchored 32-byte value | N2 |
| A3 | Padded-duplicate membership claim: leaf_index ≥ true_leaf_count (bundle class) | N4 |
| A4 | ABH decode violation: illegal escape_flag byte, header_version 0x00, truncated segments, trailing bytes, segment-order violation, duplicate (stream_id, first_seq) pair (§6.3) | N15 |
| A5 | Commitment mismatch (class A): recomputed anchored_value ≠ the on-chain payload | — (new at v0.2) |
| A6 | Required holder-side material missing (salt, body bytes, header context) with the anchor itself verified → outcome MATERIAL_UNAVAILABLE, custodian named | N7 |
| A7 | Registry/dispute finding → DISPUTED; replay/duplication → INVALID (bundle class) | N5, N6 |
| A8 | Stream/continuity/chronology finding (GAP/FORK/INVALID-CHRONOLOGY), repository split-view/staleness, chain-access-independence failure (bundle class) | N8–N11, N14 |
v0.1's negative-vector suite maps forward as follows. "Suite home" names where the executable vector lives today: F-15 = foray-api/conformance/ (this document's suite), WP-1 = foray/verifier/vectors/ (carried, still runs offline against canned contexts).
| v0.1 | Attack encoded | v0.2 code | Suite home |
|---|---|---|---|
| N1 | 64-byte internal-node-as-leaf | A1 | WP-1 (structural 32-byte law also enforced by construction in class A) |
| N2 | true_leaf_count tampered vs anchored header | A2 | WP-1; F-15 ANCHOR negatives exercise the same binding via A5 |
| N3 | Dictionary attack against a sibling digest | documented, not runtime (unchanged) | WP-1 (N3-dictionary-attack.md) |
| N4 | Proof for a padded duplicate | A3 | WP-1 |
| N5 | Revoked-in-window key | A7 → DISPUTED | WP-1 |
| N6 | Duplicate (submitter, submitter_seq) | A7 → INVALID | WP-1 |
| N7 | Withheld blob/salt, anchor genuine | A6 → MATERIAL_UNAVAILABLE | WP-1; F-15 states the salt-handling law in §5.4 |
| N8 | Repository split-view pair | A8 | WP-1 |
| N9 | Overdue checkpoint | A8 → PROVISIONAL-STALE | WP-1 |
| N10 | Backdated stream insert | A8 (GAP/FORK) | WP-1 |
| N11 | Later-anchored _ref | A8 → INVALID-CHRONOLOGY | WP-1 |
| N12 | Missing extension vs ext_version: 0 | envelope: A-plane (WP-1); record: E10 | WP-1 + F-15 VAL |
| N13 | Unknown nonzero ext_version | preserved, never rejected (unchanged) | WP-1 |
| N14 | Single-RPC-source proof | A8 (verifier conformance failure) | WP-1 |
| N15 | Serialization edge cases | A4 + B1–B4 (the 4.2 wire made both layers byte-normative) | F-15 (ABH + CS families) |
Short-circuit is permitted only into a failing outcome — never into VALID. Every check below is mandatory for its class.
R-1. Parse. The submission must parse as a JSON object. Non-object → E3, INVALID.
R-2. Version routing. schema_version must be exactly "4.2" (const-pinned, OQ-3). "4.1"/"4.0" → class L. Anything else → UNSUPPORTED (E1 named).
R-3. Structural walk — the schema-enforced half of the stage contract, restated as law. At every fact-bearing object (the transaction top level and each component element):
^F[0-9]+$ and is in the ratified catalog F1–F29 at a scope the amendment §2 table permits; every other key is a member of the ruled envelope set (transaction: schema_version, timestamp, the four component arrays, attestations, component_hashes, merkle_root, blockchain_anchor, audit_data_anchor, privacy_metadata; component element: id alone). Any other key → E11 with the specific ruling named (§4.1).schema_version, timestamp, the component arrays present per the 4.1-parity carry, component_hashes, merkle_root, blockchain_anchor). Arrangement: id, F4, F5, F11. Accrual: id, F22. Anticipation: id, F22, F23. Action: id, F22, F23, F24.^sha256:[0-9a-f]{64}$; a malformed reserved-prefix value fails both arms → E3.^hash_[0-9a-f]{32}$, F19 object, F20 number → E3 on violation.ref, ref_type, amount, currency (+ ruled optionals per the schema); an extra entry key is residue outside F25 → E11.R-4. Semantic checks — the delegated half, all eight:
ids (E7).ref resolves and ref_type matches the referent's actual component kind (E9).amount == F9 exactly, as decimal values — decimal-string or scaled-integer arithmetic mandatory, binary-float comparison forbidden, no tolerance (E8). Operands are the submission's raw literals when bytes are available, else the canonical spelling of the parsed values (exact within the 15-digit profile).extension object anywhere in the record (E10). F2 == F1 → note N1, never an error.R-5. Byte conformance (when bytes are submitted as canonical): serialize the parsed record per §6.1; the result must be byte-identical to the submission → B4 (with B1/B2/B3 naming the specific cause). On transport submissions, number-literal spelling alone is checked against the raw bytes and reported as W3 warnings.
R-6. Verdict — outcome plus every applicable finding, warning, and note. Hiding any is non-conformance (§7.3).
L-1. Route. "4.0" → the v4.0→v4.1 hop (base spec §7.2: singular *_ref wraps to plural arrays, missing ref arrays materialize as [], restamp) — then continue as "4.1".
L-2. Own-shape validation first. The (possibly hopped) record validates against the 4.1 canonical shape (v4.1.5). Failure → INVALID with the 4.1-lineage findings, legacy label, conversion not attempted (amendment §5.2: legacy records validate against their own version's canonical shape first).
L-3. Migration — migrateV41toV42, amendment §4 steps 1–10, restated: restamp to "4.2"; flatten every foray_core (OQ-4) — a key present both inside and beside a wrapper is MIG_COLLISION; re-key per the §2 table scope-aware; F2 := legacy transaction_id (missing/empty → MIG_F2_SOURCE); entity cleartext → F3, a legacy entity_hash value preserved into transaction F25 (never dropped, never re-derivable); Accrual period {start, end} → F13/F14, unknown period member → MIG_UNKNOWN_KEY; dependencies deleted at every scope, nothing relocated (D4, scope corrected at v0.7); Arrangement terms — object members land in the element's F25 verbatim, a non-object keeps the key terms; description → the element's F25 at every component scope (v0.5); compliance_flags → transaction F25 (D3.5); legal_documentation → F29; D5 types gated, never coerced (MIG_D5_TYPE); condition presence → MIG_CONDITION; any unmapped fact-level key → MIG_UNKNOWN_KEY. F25 assembly is name-preserving; a residue-key collision → MIG_F25_COLLISION. Deterministic and mechanical: no clock, no randomness, no network, no interpretation; two conformant implementations produce byte-identical §6.1 output. Failure is loud: named code, no partial output.
L-4. Judge the output as class R (labels: legacy). The response names the source version and the applied chain.
L-5. Terminus notice (OQ-13). Legacy acceptance is permanent — it never sunsets. After the conformance terminus (D9 site promotion; a configuration fact, not a computation), the response additionally carries the stated non-conformance notice. The teeth are conformance status and engagement terms, never service refusal — the evidence is innocent of the producer's lateness.
Encode direction: field values → the one legal byte string per §6.3 → anchored_value = SHA-256(0x02 ‖ bytes).
Decode direction: bytes → fields, rejecting (A4) every §6.3 illegality: non-{0x00,0x01} escape_flag, header_version 0x00, truncation against segment_count, trailing bytes, segment order violating ascending (stream_id, first_seq), duplicate (stream_id, first_seq) pair. Decode-then-encode must reproduce the input bytes exactly; any difference → A4.
Inputs: the record's exact bytes (the bytes hashed at anchoring — for pre-migration anchors these are 4.1-wire bytes; the migration never re-anchors), the 32-byte leaf_salt, the lane name, the header context (header_version, level, true_leaf_count, window_id, escape_flag, prev_header_hash), and the on-chain payload (obtained by any means — explorer, node, archive; the comparison is offline and the payload's own authenticity is the chain's guarantee, not this class's subject).
A-1. tx_hash = SHA-256(body bytes). Exactly 32 bytes of hash over exactly the submitted bytes — no canonicalization, no whitespace adjustment: the bytes anchored are the bytes hashed.
A-2. leaf = SHA-256(0x00 ‖ tx_hash ‖ leaf_salt), both operands exactly 32 bytes (A1 on violation).
A-3. Direct mode (true_leaf_count = 1): tree_root = the leaf itself. (Batched mode: recompute the root through the Merkle path per v4.1.3 §6 — bundle-class machinery, carried.)
A-4. Assemble the header (lane_id = first 8 bytes of SHA-256(UTF-8 lane name)), serialize per §6.3, anchored_value = SHA-256(0x02 ‖ header bytes).
A-5. Compare to the on-chain payload byte-exactly. Match → VALID. Mismatch → A5, INVALID. Missing salt, body bytes, or header context with the anchor itself confirmed on-chain → A6, MATERIAL_UNAVAILABLE, custodian named — never INVALID.
Custody law this class implies (F-2 model, stated for implementers): verification requires holder-side material the chain does not carry — the body bytes, the salt, and window_id (drawn at signing time, request-granularity). A holder who retains none of these has an anchor and no proof; that is the holder's custody posture, not a protocol defect. The live population's custody sheets (docs/proofs-material/) are the worked demonstration, and the suite's ANCHOR vectors are built from them.
The canonical byte form is JCS (RFC 8785) with a spec-defined member order and a stricter number rule. Verification procedure — given bytes claimed canonical:
1e-8; the canonical spelling is 0.00000001 — this is the likeliest real divergence between implementations, not edge-case padding), no trailing fractional zeros, no superfluous leading zeros, no -0, no explicit +. Exactly one byte spelling per value → B1 on violation.schema_version, timestamp, arrangements, accruals, anticipations, actions, attestations, component_hashes, merkle_root, blockchain_anchor, audit_data_anchor, privacy_metadata (absent optionals omitted; the order is of members present); component element: id last. Interiors — envelope objects' inner members, F25 residue, F5 party entries, F21 allocation entries — sort JCS-lexicographic (UTF-16 code-unit order). Arrays are never reordered — element order is content, not encoding. Violation → B2.Idempotence is a theorem of these rules (canonicalize ∘ parse ∘ canonicalize = canonicalize) and a conformance vector (CS-5), not an extra rule.
Σ F21 entry amounts vs F9, compared as decimal values — parse each operand's decimal string into scaled integers (value = units × 10⁻ˢᶜᵃˡᵉ), rescale to the common maximum scale, compare integers. Binary-float comparison is forbidden; there is no tolerance. Zero-decimal (JPY) and three-decimal (BHD) currencies need no special casing — a binary-float-tolerance implementation fails the three-decimal vectors, which is the point of their inclusion.
The one legal encoding of batch_header, packed, no padding, no terminator:
| # | Field | Encoding | Bytes |
|---|---|---|---|
| 1 | header_version | uint8; 0x00 permanently illegal (first legal value is 1) | 1 |
| 2 | level | uint8 (protocol restricts values to 0–3 post-decode; not a width constraint) | 1 |
| 3 | tree_root | raw | 32 |
| 4 | true_leaf_count | uint64 big-endian | 8 |
| 5 | lane_id | raw (= first 8 bytes of SHA-256(UTF-8 lane name), per the ratified convention) | 8 |
| 6 | window_id | uint64 big-endian | 8 |
| 7 | segment_count | uint16 big-endian | 2 |
| 8 | stream_segments[] × count | per segment: stream_id raw 16 ‖ first_seq u64 BE ‖ last_seq u64 BE ‖ head_hash raw 32 | 64 each |
| 9 | escape_flag | 0x00 false / 0x01 true; any other byte illegal, never coerced | 1 |
| 10 | prev_header_hash | raw | 32 |
anchored_value = SHA-256(0x02 ‖ rows 1–10). A zero-segment header is 93 bytes before the domain byte. Big-endian for every multi-byte integer and segment_count as uint16 are ratifications of deployed, anchored reality — every mainnet anchor in the custody record was derived under this encoding and independently re-verified byte-identical on-chain (amendment §7.2).
Segment order (OQ-10, ruled fresh): ascending (stream_id, first_seq); equal pairs structurally illegal. Comparison bases, so no implementation can read the sort ambiguously: stream_id compares byte-lexicographic (opaque bytes, first differing byte decides); first_seq compares on numeric value — coinciding with its fixed-width big-endian bytes. Two segments with the same stream_id and different first_seq are legal and order by first_seq — this supersedes the at-most-one-segment-per-stream rule of foray-abh-serialization-v0_2.md §4.1, which the amendment's ruling replaced when it ruled the tiebreak; the v0_2 document remains the ratified authority for everything the amendment did not re-rule. Sorted order means the header does not preserve segment arrival sequence — no loss; intra-stream ordering lives in the segments' own seq_no ranges.
Decode rejections (A4), exhaustive at this layer: illegal escape_flag byte; header_version 0x00; declared segment_count with fewer than count × 64 bytes following (truncation); trailing bytes after prev_header_hash (the format has no trailer); segment order violation; duplicate (stream_id, first_seq) pair — checked independently of the sort check (two identical pairs are trivially "sorted").
| Class | A PASS attests | A PASS does NOT attest |
|---|---|---|
| R | The record conforms structurally, semantically, and (when bytes were judged) byte-exactly to the 4.2 wire | That the record was anchored; that its content is true; that its declared amounts, dates, or parties correspond to reality |
| L | The legacy record is valid against its own version's canonical shape and converts deterministically to the stated 4.2 record | Anything about the producer's conformance standing beyond the stated notice; any judgment of the record's content |
| H | These bytes are the one legal encoding of this header and commit to the stated anchored_value | That the header was ever anchored |
| A | Temporal + integrity proof: the exact submitted bytes existed no later than the chain block time of the anchoring transaction and have not been altered since — because SHA-256(body) ‖ salt ‖ lane ‖ header reproduce the on-chain payload byte-for-byte | Everything in §7.2 |
FORAY proves records existed unaltered from a point in time; it proves claims were made, not that claims are true. A PASS never attests: truth of the recorded assertions, physical reality, attestor competence, or data accuracy at source. FORAY makes the originating system's declarations tamper-evident; it does not make them true. Verification output that presents a PASS as proof of any of these is non-conformant regardless of its cryptography.
The F-18 rider (permanent claim-discipline law, carried verbatim):
no artifact may imply that salted formula IDs prove execution correctness — they prove definition retention. The hash proves which formula was declared and that it is unaltered since; it does not prove the formula ran, or ran correctly. Binds all future artifacts; the v1.8 site wording check already queued in the review register enforces it retroactively.
A verifier's display of F18 findings, and any prose a conformant verifier emits about formula facts (F18/F19/F20), is bound by this rider.
Every verification output, machine or human, MUST show: the outcome with every finding's code and named culprit; the class evaluated; all labels (legacy with source version and applied chain; the non-conformance notice when configured); for class A, the anchoring transaction id and the only time actually proven (the chain's block time — in-record timestamps are submitter assertions, v4.1.3 §7); for MATERIAL_UNAVAILABLE, the responsible custodian; for byte-level judgments, whether the submission was judged as transport or as canonical bytes. A verifier that displays ✓ without stating what the ✓ attests (§7.1) is non-conformant regardless of its cryptography.
The suite lives at foray-api/conformance/ — vectors, manifest, runner, reference adapter, and broken-verifier variants. It runs offline: one command, no network, no DUNIN7 service. Its on-chain expectations (ANCHOR family) are recorded values from the live custody population, checkable against the public chain independently but not fetched at run time.
| Family | Contents |
|---|---|
| CS | Canonical serialization: decimal accept/reject sets with the 15-digit boundary and tiny-magnitude plain-notation catchers; member-order goldens (numeric-vs-lexicographic F-order, envelope tail, id-last, F25 lexicographic interior, optional-envelope omission); string encoding (JCS escaping, non-ASCII, the sha256: F3 form); the golden record → bytes → SHA-256; idempotence; ordering negatives (lexicographic and insertion-ordered spellings rejected); exact-decimal allocation sums (JPY, BHD, the float trap) |
| ABH | Golden direct-mode header; multi-segment goldens including the same-stream-two-segments tiebreak; decode negatives: bad escape_flag, header_version 0, truncation, trailer, order violation, duplicate pair, wrong-endian probe |
| MIG | The byte-golden migration set — including the v0.5 (description scope), v0.6 (currency scope), and v0.7 (dependencies scope) correction vectors — plus the named-error failure set (D5 types, condition, unknown keys) and the 4.0 two-hop chain |
| VAL | Structural/semantic positives and negatives with named culprits; the register-leak probe; legacy acceptance with byte-equal conversion; the post-terminus notice case |
| ANCHOR | The real anchored population: record-a/b v2 bodies + salts + lane + header context → recomputed commitments → the actual on-chain payloads (provenance live-anchored-evidence); designed negatives (wrong salt, tampered body) |
conformance/manifest.json lists every vector: id, family/class, runner mode, file(s), expected outcome, and provenance — designed | golden-from-migration | live-anchored-evidence — with source citations for the live-evidence entries (custody sheet, Kaspa transaction id).
node conformance/run-conformance.mjs executes the full suite against the reference implementation; --verifier <module.mjs> executes any candidate implementing the documented interface (conformance/README.md): canonicalize, validateDual, migrateChain, abhEncode, abhDecode, abhAnchoredValue, directCommitment. The runner reads vectors, invokes the candidate, and emits a per-vector PASS/FAIL report with a non-zero exit on any failure. A candidate needs no DUNIN7 code: the interface is semantic, the vectors are data, and this specification defines every behavior tested.
At least one deliberately broken verifier variant MUST fail the suite before the suite is trusted. The shipped mutation set (conformance/broken/) demonstrates, per mutation, which vectors catch it — a stock number-formatter, a lexicographic member sort, a binary-float allocation sum, a little-endian ABH encoder, a decoder without the duplicate-pair check, an F8-materializing migration, a domain-separation-free leaf, and a pre-v0.5 Arrangement-only description scope. A suite change that stops catching any shipped mutation is a regression.
Only implementations passing the complete suite — positives and negatives, all families — may describe output as "FORAY verified." Partial implementations must use different words. For the bundle class, the WP-1 N-vector suite additionally applies (§4.5).
0f86f45b…) has full custody material except window_id (recorded for the demonstration records only); f23d499a… is NOT RECOVERABLE (terminal); d6d704ed… is body-only. The suite's live-evidence family therefore holds exactly the two fully-custodied demonstration anchors. Their sheets record window_id values recovered by brute force; the suite treats them as custody inputs, which is the F-2 model's own position.window_id semantics. v4.1.3 §4 says "monotonic per lane"; the deployed direct-mode path draws window_id from request time. This specification treats it as opaque custody material for class A. Whether a verifier should validate monotonicity is bundle-class stream machinery, unruled for the direct lane — carried as an open observation, not enforced.attestations elements. The envelope enumeration admits an optional attestations array; no 4.2 structural definition of its elements exists (the trust-model's exhibit is 4.1-era foray_core shape). The suite carries no attestations vector; a 4.2 record carrying one is judged by the schema's actual constraint for that member. Catalog/Stele-cadence work per the ruled sequencing; reported here so the silence is inspected.