Satsignal conformance — bundle-v1 / .mbnt envelope
This is the conformance surface for the .mbnt proof bundle spec (docs/notary_spec/bundle-v1.md). It is the canonical "is this verifier / producer conformant with bundle-v1?" reference for third-party verifier authors, CLI / SDK implementers, adapter authors, and standards-adjacency reviewers.
bundle-v1.md is normative. This document is a flat checklist of the normative requirements with cross-references back to the relevant sections, plus pointers to the test vectors and executable test scripts that exercise each requirement. Where a requirement has no in-tree executable check, this is called out explicitly in the "Followups" section so adopters can supply their own.
This document complements — and does not duplicate — the prose conformance summary at bundle-v1.md §10. The summary there is authoritative for the list of MUSTs; this document maps each MUST to test material.
Relationship to CONFORMANCE.md (provenance-v1)
The two specs are intentionally independent:
CONFORMANCE.mdcoverssatsignal.provenance.v1— the canonical manifest schema, SCJ-v1-of-manifest, the rejection of unknown top-level keys, vendor / custom payloads inextensions.- This document covers
bundle-v1— the.mbntZIP envelope, the three JSON entries (manifest.json,canonical.json,proofs.json), the on-chain anchor binding, and the verification procedure.
A .mbnt bundle MAY carry a satsignal.provenance.v1 manifest as its content; in that case the envelope conformance is bundle-v1's concern, and the inner-content conformance is provenance-v1's. The two checks are independent and both apply.
A provenance manifest MAY be sealed (privacy.onchain_mode: "sealed", provenance-v1 §6): the bundle then carries the salted byte_exact commitment and the salt_b64 bearer secret but no manifest plaintext — the holder presents (manifest + salt) out-of-band to verify (B7.9–B7.10). This is the provenance analogue of the sealed file bundle (B7.1–B7.8).
Scope
A claim of conformance to bundle-v1 is a claim about one of two implementation roles:
- Verifier — code that consumes a
.mbntbundle, performs the ordered checks inbundle-v1.md §7, and reports an outcome from the error model inbundle-v1.md §8. - Producer — code that emits a
.mbntbundle for downstream consumption (the notary service itself, third-party signers, re-issuers).
A verifier claim covers bundle-v1.md §2–§8 (envelope, parse, crypto, doc-hash, chain confirmation, error model). A producer claim covers bundle-v1.md §2–§6 (envelope structure, JSON-entry shape, on-chain anchor binding) and the §2.2 hardening MUSTs (a producer MUST NOT emit any of the §2.2 malformations).
This document does not define a third "oracle" conformance class. The bundle-v1.md §6.2 chain resolver (any BSV node) is explicitly non-normative — verifiers are free to pick any explorer that exposes raw-tx-by-hash.
Conformance classes
| Class | Bound by | Scope |
|---|---|---|
| Verifier | bundle-v1.md §2, §4.4, §6, §7, §8, §9.1–§9.3 | parse, crypto, doc-hash, chain confirm, error model, version tolerance |
| Producer | bundle-v1.md §2, §3, §4, §5, §6.1 | emit a bundle that every conformant verifier will accept |
A verifier that omits the chain check (§7.4) by default is not conformant (bundle-v1.md §10 closing note). A verifier that implements §7.5 "offline mode" only behind an explicit caller opt-in is conformant.
Normative requirements — verifier checklist
Each item below is a MUST. The "see" reference points to the authoritative clause in bundle-v1.md; the "vector" and "script" columns point to the executable check, if one exists in-tree.
B1. Envelope structure (bundle-v1.md §2)
| # | Requirement | See | Vector | Script |
|---|---|---|---|---|
| B1.1 | Parse the .mbnt as a standard PKZIP archive (deflate or store); magic 50 4B 03 04 MUST identify it | §2 | tests/vectors/legacy-bundles/*.mbnt | scripts/test_legacy_bundles.py |
| B1.2 | Verifier MUST read manifest.json and canonical.json from the root of the archive | §2 table; §7.1 step 1 | tests/vectors/legacy-bundles/*.mbnt | scripts/test_legacy_bundles.py |
| B1.3 | Verifier MUST read proofs.json iff canonical.json.subject.proofs.chunk_merkle is present | §2 table; §7.1 step 2 | (no in-tree chunk_merkle vector) | (none) |
| B1.4 | Verifier MUST tolerate unknown ZIP entries (skip and continue) and MUST NOT treat them as proof material | §2 (post-table prose) | — | (none) |
| B1.5 | Verifier MAY use legacy attachments/<name> to source file bytes for legacy bundles, but MUST NOT rely on its presence | §2.1 | tests/vectors/legacy-bundles/v1-generic-attest-attach.mbnt | scripts/test_legacy_bundles.py |
B2. Envelope hardening — MUST reject (bundle-v1.md §2.2)
These are the five malformations a conformant verifier MUST reject before extracting any entry. Cross-references back to bundle-v1.md §10 items 8–12.
| # | Requirement | See | Vector | Script |
|---|---|---|---|---|
| B2.1 | Reject ZIP buffers whose first 4 bytes are not the local-file-header magic 50 4B 03 04 (leading-data smuggling) | §2.2 bullet 1; §10 item 8 | (none in-tree) | (none) |
| B2.2 | Reject ZIP buffers whose EOCD record reports a non-zero commentLen | §2.2 bullet 2; §10 item 9 | (none in-tree) | (none) |
| B2.3 | Reject ZIP buffers containing more than one EOCD signature (50 4B 05 06) anywhere in the buffer | §2.2 bullet 3; §10 item 10 | (none in-tree) | (none) |
| B2.4 | Reject ZIP central directories with duplicate filenames (most importantly duplicate manifest.json, canonical.json, or proofs.json) — JSZip / CPython zipfile resolve in opposite directions | §2.2 bullet 4; §10 item 11 | (none in-tree) | (none) |
| B2.5 | Reject central-directory entries whose name contains .., starts with /, or contains backslash \ (zip-slip) | §2.2 bullet 5; §10 item 12 | (none in-tree) | (none) |
Reference enforcement implementation: verifier.html lines 1944–2111 (shipped 2026-05-22 against M#36, H#17, H#20). A producer MUST NOT emit any §2.2 malformation; a producer that does is non-conformant even if some verifiers tolerate it.
B3. manifest.json — common fields (bundle-v1.md §3.1)
| # | Requirement | See | Vector | Script |
|---|---|---|---|---|
| B3.1 | manifest.mbnt_version MUST be present and MUST be one of "1.1", "2.0", "2.1" | §3.1; §7.1 step 3; §9.1 | tests/vectors/legacy-bundles/v1-*.mbnt ("1.1"), v2-multi-proof.mbnt ("2.0") | scripts/test_legacy_bundles.py |
| B3.2 | manifest.txid MUST be 64 lowercase hex chars | §3.1 table | tests/vectors/legacy-bundles/*.mbnt | scripts/test_legacy_bundles.py |
| B3.3 | manifest.network MUST be present; verifiers MUST refuse unknown networks unless explicitly supported | §3.1 table | tests/vectors/legacy-bundles/*.mbnt | scripts/test_legacy_bundles.py |
| B3.4 | manifest.doc_hash_expected MUST be 40 lowercase hex chars = first 20 bytes of sha256(canonical_bytes) | §3.1 table; §6.4; §7.3 | tests/vectors/legacy-bundles/*.mbnt (.expected.json.doc_hash_expected) | scripts/test_legacy_bundles.py |
| B3.5 | Verifier MUST tolerate optional informational fields (category, acceptance, proof_mode) | §3.1 post-table | (covered by proof_mode in v1-generic-file.mbnt) | scripts/test_legacy_bundles.py |
| B3.6 | Verifier MUST NOT switch standard/sealed dispatch on proof_mode; dispatch is on mode | §3.1 (proof_mode row) | — | (none) |
B4. mbnt_version per mode (bundle-v1.md §3.2–§3.4, §9.1)
| # | Requirement | See | Vector | Script |
|---|---|---|---|---|
| B4.1 | Standard-mode manifests use mbnt_version: "2.0" and have NO mode field | §3.2; §9.1 row 2 | tests/vectors/legacy-bundles/v2-multi-proof.mbnt | scripts/test_legacy_bundles.py |
| B4.2 | Legacy single-proof manifests use mbnt_version: "1.1" (canonical schema_version: 1) | §3.1; §4.5; §9.1 row 1 | tests/vectors/legacy-bundles/v1-generic-file.mbnt, v1-generic-attest-attach.mbnt | scripts/test_legacy_bundles.py |
| B4.3 | Sealed-mode manifests use mbnt_version: "2.1" and carry an explicit mode: "sealed" field | §3.3; §9.1 row 3 | (no in-tree sealed bundle — see Followups) | (none directly; scripts/test_sealed_flow.py exercises the wire flow against a live server) |
| B4.4 | Manifest-mode (Phase 8b) bundles use mbnt_version: "2.0" at the manifest layer; mode signal lives in canonical.json (subject.kind == "manifest") | §3.4 | (no in-tree manifest-mode vector) | (none) |
| B4.5 | Verifier MUST refuse unsupported mbnt_version values rather than attempt a partial parse | §7.1 step 3; §9.1 post-table | — | (none) |
B5. canonical.json — required fields (bundle-v1.md §4.1)
The eight top-level keys MUST all be present. Source of truth: canonical.schema.json required[] and schema.py _TOP_LEVEL_KEYS, kept byte-for-byte in lockstep by tests/test_canonical_schema_lockstep.py (a differential battery that asserts the published JSON Schema and schema.py agree on accept/reject for every v1 and v2 document). schema.py is the runtime validator and emits missing top-level keys: [...] on any omission. The published canonical.schema.json validates both versions — its top-level oneOf branches on schema_version + subtype, and selects the v2 public / sealed (subject.kind == "file_anchor") / manifest (subject.kind == "manifest") shape by structural marker — and carries "maximum": 9007199254740991 on every integer field (the JS-safe-integer bound, §B6.3).
| # | Requirement | See | Vector | Script |
|---|---|---|---|---|
| B5.1 | schema_version MUST be present (integer 1 or 2) | §4; §4.1; §4.5 | tests/vectors/provenance-v1/invalid/i01-missing-schema.json | scripts/run_vectors.py |
| B5.2 | subtype MUST be present | §4.1 | tests/vectors/provenance-v1/invalid/i04-unknown-source-type.json (related) | scripts/run_vectors.py |
| B5.3 | issued_at MUST be present (RFC 3339 UTC, second precision, Z suffix) | §4.1; canonical.schema.json | (validation gated by scripts/test_provenance.py) | scripts/test_provenance.py |
| B5.4 | issuer MUST be present (non-empty string) | §4.1 | (validation gated by scripts/test_provenance.py) | scripts/test_provenance.py |
| B5.5 | subject MUST be present | §4.1; §4.2 | (validation gated by scripts/test_provenance.py) | scripts/test_provenance.py |
| B5.6 | attestation MUST be present | §4.1 | (validation gated by scripts/test_provenance.py) | scripts/test_provenance.py |
| B5.7 | attachments MUST be present (always present, always empty in v2) | §4.1; post-shape prose | (validation gated by scripts/test_provenance.py) | scripts/test_provenance.py |
| B5.8 | nonce MUST be present (16 random bytes, 32 hex chars) | §4.1; canonical.schema.json | (validation gated by scripts/test_provenance.py) | scripts/test_provenance.py |
| B5.9 | subject.proofs.byte_exact MUST be present in v2; content_canonical and chunk_merkle are optional | §4.1; §7.2 step 1 | tests/vectors/legacy-bundles/v2-multi-proof.mbnt | scripts/test_legacy_bundles.py |
B6. canonical.json canonicalization (bundle-v1.md §4.4)
| # | Requirement | See | Vector | Script |
|---|---|---|---|---|
| B6.1 | canonical.json on disk MUST be byte-identical to the SCJ-v1 encoding whose SHA-256 was committed on-chain | §2 post-table; §4.4 | tests/vectors/legacy-bundles/*.mbnt (.expected.json.canonical_sha256) | scripts/test_legacy_bundles.py |
| B6.2 | doc_hash = sha256(SCJ_v1(canonical.json))[:20] and MUST equal manifest.doc_hash_expected | §4.4; §6.4; §7.3 | tests/vectors/legacy-bundles/*.mbnt | scripts/test_legacy_bundles.py |
| B6.3 | SCJ-v1 encoding rules (not RFC 8785): UTF-8 output, NFC normalization, keys sorted by Unicode code point, no whitespace, no NaN/±Inf, integers as bare decimals (floats forbidden) and within the JS-safe range ≤ 2^53−1 (canonical.schema.json "maximum" on every integer field) | §4.4 | tests/vectors/provenance-v1/valid/v05-key-reorder-stable.* | scripts/run_vectors.py, scripts/test_offline_verifier.py |
B7. Sealed-mode specifics (bundle-v1.md §3.3, §4.2, §5, §7.2)
| # | Requirement | See | Vector | Script | ||
|---|---|---|---|---|---|---|
| B7.1 | Sealed manifests MUST carry mode: "sealed" as the dispatch signal | §3.3; §7.2 (sealed branch); §10 item 3 | (no in-tree sealed .mbnt vector) | scripts/test_sealed_flow.py (end-to-end) | ||
| B7.2 | Sealed manifests MUST carry salt_version: "salt_v1" — the single currently-accepted value (per schema.py:_VALID_SALT_VERSIONS = {"salt_v1"} and sealed.py:SALT_VERSION) | §3.3 table | tests/vectors/provenance-v1/valid/v03-typed-authority-full.json (sealed-shape gating) | scripts/test_sealed_session_commitment.py, scripts/test_proof_set_sealed_schema.py | ||
| B7.3 | Sealed manifests MUST carry salt_b64 (base64url-encoded 32-byte master salt) and bearer_secret: true | §3.3 table | (none in-tree as .mbnt) | scripts/test_sealed_session_commitment.py | ||
| B7.4 | Sealed manifests MUST NOT carry sha256, file_size, or any other field that would leak the underlying file's identity | §3.3 closing prose | — (negative — checked by scripts/check_no_payload_upload.py at producer boundary) | scripts/check_no_payload_upload.py | ||
| B7.5 | Sealed byte_exact and content_canonical proofs use algo: "hmac-sha256", carry salt_version, replace hash with commitment | §4.2 table | tests/vectors/provenance-v1/valid/v03-typed-authority-full.json (related sealed-shape) | scripts/test_proof_set_sealed_schema.py, scripts/test_sealed_session_commitment.py | ||
| B7.6 | Sealed chunk_merkle uses algo: "merkle-hmac-sha256", carries salt_version, keeps the field name root (NOT commitment) | §4.2 table; §7.2 sealed step 4 closing note | (none in-tree) | (none) | ||
| B7.7 | Sealed Merkle leaves use per-leaf salts derived via HKDF with literal salt b"satsignal-sealed-v1/per-leaf" and `info = b"chunk/" | u32_be(i)` | §5 post-metadata prose | (none in-tree as vector) | scripts/test_sealed_flow.py (end-to-end) | |
| B7.8 | The blind-submission "manifest-stripped" variant omits server_retain_until_utc (no server-side copy exists) | §3.3 table (server_retain_until_utc row) | — | (none) | ||
| B7.9 | A sealed satsignal.provenance.v1 bundle MUST NOT carry a proofs.json.manifest plaintext key — the manifest is the holder's bearer secret, presented out-of-band, not shipped (contrast a hash_only provenance bundle, which carries "manifest": <clean manifest>) | provenance-v1 §6.5 | (no in-tree sealed-provenance .mbnt vector) | tests/test_provenance_sealed_e2e.py (no-leak assertions) | ||
| B7.10 | A sealed satsignal.provenance.v1 bundle's byte_exact proof carries {algo: "hmac-sha256", salt_version: "salt_v1", commitment} where commitment = HMAC-SHA256(master_salt, SCJ_v1(canonical_manifest)); the on-chain anchor is the unchanged 20-byte byte_exact sealed anchor (NO new merkle scheme literal) | provenance-v1 §6.3, §6.5 | (no in-tree vector) | tests/test_provenance_sealed_e2e.py, scripts/test_proof_set_sealed_schema.py |
B8. On-chain anchor binding (bundle-v1.md §6)
| # | Requirement | See | Vector | Script |
|---|---|---|---|---|
| B8.1 | OP_RETURN payload MUST begin with ASCII "MBNT" (0x4D 0x42 0x4E 0x54), version byte 0x01, subtype byte 0x01 | §6.1; §7.4 step 2 | (off-chain — payload reconstruction inside tests/vectors/legacy-bundles/*.mbnt) | scripts/test_legacy_bundles.py (40-hex truncation match) |
| B8.2 | doc_hash_on_chain = payload[8:28] MUST equal the 20-byte form of manifest.doc_hash_expected (constant-time compare) | §6.4; §7.4 step 3 | tests/vectors/legacy-bundles/*.mbnt | scripts/test_legacy_bundles.py |
| B8.3 | Verifier MUST tolerate unknown TLV tags (record opaquely) but MUST refuse unknown version bytes and unknown subtype bytes | §6.1 post-prose; §9.3 | — | (none) |
| B8.4 | A .mbnt that passes 7.1–7.3 with confirmations >= 1 is verified; with confirmations == 0 is pending (verifier MUST NOT claim "verified" on a 0-confirmation proof) | §7.4 step 4; closing prose of §7 | — | (none — requires live chain) |
B9. Verification procedure (bundle-v1.md §7)
| # | Requirement | See | Vector | Script |
|---|---|---|---|---|
| B9.1 | Verifier MUST perform checks §7.1–§7.4 in order; failing any check is a verification failure with the appropriate error class | §7 opening | (covered indirectly by legacy + offline tests) | scripts/test_legacy_bundles.py, scripts/test_offline_verifier.py |
| B9.2 | Standard-mode crypto MUST recompute sha256(file_bytes) and match byte_exact.hash; SHOULD recompute content_canonical / chunk_merkle when present | §7.2 standard branch | tests/vectors/legacy-bundles/v2-multi-proof.mbnt | scripts/test_legacy_bundles.py |
| B9.3 | Sealed-mode crypto MUST recompute HMAC-SHA256(master_salt, file_bytes) and match byte_exact.commitment; SHOULD recompute content_canonical / chunk_merkle when present | §7.2 sealed branch | (no in-tree sealed .mbnt vector — Followups) | scripts/test_sealed_flow.py (end-to-end) |
| B9.4 | Verifier MUST confirm len(proofs.json.merkle_leaves) == chunk_merkle.leaf_count when chunk_merkle is present | §7.2 standard step 3 | (no in-tree chunk_merkle vector) | (none) |
| B9.5 | Offline mode MUST report results as "cryptographic checks pass; on-chain status NOT verified" and MUST NOT downgrade the warning under any flag | §7.5 | — | scripts/test_offline_verifier.py (analogous offline invariant on the provenance-v1 surface) |
| B9.6 | Verifier MUST distinguish the error classes CRYPTO, CHAIN, VERSION, NETWORK, PENDING, OFFLINE per §8 | §8; §10 item 6 | — | (none) |
B10. Tolerance / forward-compat (bundle-v1.md §9.2–§9.3)
The B10 rules apply to JSON object keys inside an already-extracted entry. They are independent of the §2.2 envelope-entry duplicate / unknown rule (which is strict).
| # | Requirement | See | Vector | Script |
|---|---|---|---|---|
| B10.1 | Verifier MUST tolerate unknown top-level keys in manifest.json, canonical.json, and proofs.json (skip and continue) | §9.2; §2.2 bullet 4 closing note | (no in-tree positive vector — provenance-v1 rejects unknowns per i03) | (none — this is the deliberate asymmetry with provenance-v1 strict-mode) |
| B10.2 | Verifier MUST tolerate unknown TLV tags in the OP_RETURN payload (record as opaque (tag, value)) | §9.3 | — | (none) |
| B10.3 | Verifier MUST refuse unknown OP_RETURN version bytes and unknown subtype bytes | §9.3 | — | (none) |
Asymmetry note.
provenance-v1(the canonical-manifest layer) rejects unknown top-level keys (CONFORMANCE.mditem 3, vectori03-unknown-top-level-key);bundle-v1(the envelope-and-bundle layer) tolerates them (§9.2). These are different layers and the rules do not conflict. A producer that wants forward-compatible custom data inside the canonical manifest MUST use theextensionsobject (perprovenance-v1).
Test vector pointers
| Vector dir | Coverage |
|---|---|
tests/vectors/legacy-bundles/ | Frozen .mbnt bundles: re-validates schema, canonicalizer reproduces canonical bytes, 40-hex truncation matches doc_hash_expected. 3 fixtures: v1-generic-file.mbnt, v1-generic-attest-attach.mbnt, v2-multi-proof.mbnt. |
tests/vectors/provenance-v1/valid/ | 5 canonical-manifest vectors. Bundle-v1-relevant: B5.1–B5.8 (required-fields), B6.3 (SCJ-v1 stability). |
tests/vectors/provenance-v1/invalid/ | 12 rejection vectors. Bundle-v1-relevant: B5.1 (i01), B5.2-adjacent (i04). |
tests/vectors/sealed/ | session-commitment-v1/ — merkle-session-v1 session_commitment vectors (single-leaf / two-leaf / odd-width). Recorded, not verified (B-63): the session leaves are not in the canonical doc, so an independent verifier cannot re-derive the root; these pin the server-side derivation incl. the root == sha256(leaf‖leaf) single-leaf edge. Driven by tests/test_wg5b_merkle_conformance.py. |
tests/vectors/chain-anchor-v1/ | MBNT OP_RETURN reference-parser conformance (verifier/chain.mjs): valid/ positive parses (strip-optional-0x00 for both the raw-tx 00 6a and WhatsOnChain bare-6a script shapes; an out-of-registry subtype reports the commitment, subtype_known=false) + invalid/ rejection vectors (bad version, duplicate / overrunning TLV, TLV section > 192B, too-short). Driven by tests/verifier/test_chain_anchor_vectors.mjs. |
tests/vectors/merkle-single-leaf-v1/ | Cross-scheme Merkle single-leaf / odd-node conformance (chunk_merkle vs satsignal.disclosure.v1 vs merkle-session-v1). Pins the SPEC_mbnt §5.1 normative table. Driven by tests/test_wg5b_merkle_conformance.py. |
tests/vectors/adapters/ | Empty as of 2026-05-25 — Phase-5 deferred. |
Test script pointers
| Script | Asserts |
|---|---|
scripts/test_legacy_bundles.py | Re-validates frozen .mbnt bundles: schema, canonicalizer, doc_hash_expected truncation. Stdlib-only. (B1.1–B1.5, B3.1–B3.5, B4.1–B4.2, B5.9, B6.1–B6.2, B8.1–B8.2, B9.1–B9.2) |
tests/test_canonical_schema_lockstep.py | Differential battery: the published canonical.schema.json and schema.py MUST agree on accept/reject for every v1 + v2 (public/sealed/manifest) document, and a frozen v2 vector validates against the schema. Requires jsonschema (skips if absent). (B5.x; B-62/B-67/B-69) |
tests/test_wg5b_merkle_conformance.py | Merkle single-leaf / odd-node rules per scheme (chunk_merkle vs disclosure.v1 vs merkle-session-v1) against the in-tree builders. Stdlib-only. (B-66; SPEC_mbnt §5.1) |
tests/verifier/test_chain_anchor_vectors.mjs | MBNT OP_RETURN reference parser: strip-optional-0x00, version/subtype handling, TLV duplicate/overrun/192B-cap refusals. Node node:test (rides js-tests.yml). (B-64/B-65/B-68) |
scripts/run_vectors.py | Walks tests/vectors/provenance-v1/{valid,invalid}/; each input either canonicalizes to its frozen manifest_sha256 or raises an error containing the pinned needle. (B5.1, B6.3) |
scripts/test_offline_verifier.py | Re-validates every valid provenance-v1 vector with sockets blocked; proves the validator path never touches the network. (B9.5 analog) |
scripts/test_provenance.py | Canonical-manifest accept/reject rules. (B5.3–B5.8) |
scripts/test_proof_set_sealed_schema.py | Sealed-mode proof_set schema. (B7.2, B7.5) |
scripts/test_sealed_session_commitment.py | Sealed session_commitment derivation. (B7.2, B7.3, B7.5) |
scripts/test_sealed_flow.py | End-to-end sealed submit / fetch / verify against a live server. (B4.3, B7.1, B7.7, B9.3) |
scripts/test_sealed_no_plaintext_session_id.py | Sealed-mode plaintext session-label leak invariant. (B7.4 adjacent) |
scripts/check_no_payload_upload.py | Outbound boundary stays payload-free (no customer bytes leave the process). (B7.4) |
All of the above are stdlib-only unless noted; the legacy-bundle and provenance vector runners gate every PR via .github/workflows/conformance.yml.
CI gating
Bundle-v1's executable conformance checks ride the same workflow as provenance-v1: .github/workflows/conformance.yml. The legacy-bundle backward-compat suite is the load-bearing bundle-v1 gate — a failure there means a refactor silently broke the byte-exact promise to every existing customer bundle on disk.
The §2.2 envelope-hardening MUSTs (B2.1–B2.5) are currently enforced in verifier.html (reference implementation, lines 1944–2111) but do not yet have stdlib regression vectors in this tree. Adding crafted malformed-ZIP fixtures under tests/vectors/envelope-hardening/ is a tracked followup; until then, B2.1–B2.5 are conformance requirements without an in-tree executable check.
Cross-references
docs/notary_spec/bundle-v1.md— normative spec.docs/notary_spec/CONFORMANCE.md—provenance-v1conformance (independent surface; see "Relationship" above).docs/notary_spec/SPEC_mbnt.md— OP_RETURN wire format, TLV registry, subtype codes (cross-referenced frombundle-v1.md §6.1).docs/notary_spec/SPEC_v2_sealed.md— sealed-mode threat model, HKDF derivation (cross-referenced frombundle-v1.md §3.3,§4.2,§5).tests/vectors/— vector tree..github/workflows/conformance.yml— CI runner.
Followups (in-tree gaps)
- §2.2 envelope-hardening vectors. B2.1–B2.5 are reference- implemented in
verifier.htmlbut have no in-tree crafted-malformed ZIP fixtures. Suggested location:tests/vectors/envelope-hardening/with sibling.expected.jsonrecording the expected rejection reason. (M#36, H#17, H#20 references inbundle-v1.md §2.2.) - Full sealed
.mbntbundle vector.tests/vectors/sealed/now carriessession-commitment-v1/(themerkle-session-v1derivation, B-63), but a complete captured sealed.mbntbundle (with a publishedmaster_salt) is still absent: B4.3, B7.1, B7.6, B7.7, B9.3 rely on the live-server end-to-end scriptscripts/test_sealed_flow.pyrather than a frozen fixture. Such a bundle would let stdlib-only conformance cover the full sealed dispatch path. chunk_merklevectors. No in-tree vector exercises the chunk_merkle /proofs.jsoncodepath (B1.3, B9.4, B7.6). The live verifier atverifier.htmlcovers this; a frozen vector would close the regression-test gap.- Manifest-mode (Phase 8b) vectors. No in-tree vector for
subject.kind == "manifest"/scheme: "manifest-items-v1"(B4.4). - Chain-confirmation step (
§7.4). B8.4 and B9.6 are intrinsic to the chain check and cannot be exercised by a stdlib offline test. A mock-explorer harness would let the error-class distinctions (CHAIN,NETWORK,PENDING) gate on a vector rather than a live BSV node.
Questions about this specification? Email hello@satsignal.cloud.