Satsignal conformance — disclosure-v1
This is the conformance surface for the selective-disclosure spec (docs/notary_spec/disclosure-v1.md). It is the canonical "is this verifier / SDK / adapter conformant with satsignal.disclosure.v1?" reference for third-party verifier authors, SDK implementers, adapter authors, and standards-adjacency reviewers.
disclosure-v1.md is normative; this document is a flat checklist of the normative requirements with cross-references back to the relevant sections, the triggering input vector for each MUST, and the required verifier behavior. Per-profile preimage / canonicalization / leaf-id rules are normatively defined in the per-profile specs under docs/notary_spec/profiles/; this document binds those rules into the disclosure verifier contract by cross-reference. A verifier conformant with disclosure-v1 is conformant only for the specific profile literals it actually implements — partial-profile conformance is explicitly contemplated in §D below.
Relationship to sibling conformance docs
The three conformance docs cover three independent (but layered) surfaces:
CONFORMANCE.mdcoverssatsignal.provenance.v1— the canonical manifest schema, JCS-of-manifest, the rejection of unknown top-level keys inside the canonical doc, vendor / custom payloads inextensions.CONFORMANCE_bundle.mdcoversbundle-v1— the.mbntZIP envelope, the JSON entries, the on-chain anchor binding, and the ordered verification procedure.- This document covers
disclosure-v1— themanifest.disclosureblock, the linked-anchor binding chain, the per-revealed-leaf recomputation, the claims-rendering contract, and the merkle-proof invariants over the original anchor's leaf-set.
Disclosure conformance is layered above bundle conformance: a verifier processing a disclosure bundle first satisfies the CONFORMANCE_bundle ordered checks against the disclosure .mbnt's own envelope (and against the original anchor's bundle when fetched or carried), THEN — if manifest.disclosure is present — additionally satisfies the §A–§E checks below. A verifier that does not implement disclosure rendering is still a conformant bundle-v1 verifier; it satisfies §A.1 of this document by tolerating the unknown manifest.disclosure key per bundle-v1.md §9.2 and parsing the rest of the bundle unchanged.
Normative-source pointer
docs/notary_spec/disclosure-v1.md is the normative master spec. Per-profile rules (CSV row / JSON field / text paragraph_sentence) bind into this document by cross-reference; the profile specs at docs/notary_spec/profiles/csv-row-v1.md, docs/notary_spec/profiles/json-field-v1.md, and docs/notary_spec/profiles/text-paragraph-sentence-v1.md are themselves normative for their own preimage / canonicalization / leaf-id / salting rules. This document is a flat checklist mapping each MUST in those sources to a triggering input vector and the required verifier output.
Conformance classes
| Class | Bound by | Scope |
|---|---|---|
| Disclosure-tolerant verifier | bundle-v1.md §9.2; this doc §A.1 | parses bundles whose manifest.disclosure is present, but does not render the disclosure view |
| Disclosure-aware verifier | disclosure-v1.md §1–§8; this doc §A–§E, §F | runs the full linked-anchor binding chain, per-revealed-leaf recompute, claims rendering, view-hash check |
| Fully-conformant verifier | as above + all five v1 profiles | additionally implements all five v1 profile literals (CSV row + CSV column / JSON top-level-key + JSON deep-field / text) per §D.2 |
| Partial-profile verifier | as disclosure-aware + ≥ 1 v1 profile | implements a strict subset of the v1 profile literals; fails closed with unsupported_profile on the others (§D.3) |
A verifier that runs the disclosure checks out of order, that silent-skips a missing fail code, or that downgrades a closed failure to a warning under any flag is not conformant.
§A — Manifest tolerance and discovery
Conformance items derived from disclosure-v1.md §2 (where disclosure lives in the bundle) and bundle-v1.md §9.2 (unknown-JSON-key tolerance).
A.1 — Tolerate manifest.disclosure when not implementing disclosure
| Source | Triggering input | Required verifier behavior |
|---|---|---|
disclosure-v1.md §2 post-table prose; bundle-v1.md §9.2 | A valid .mbnt whose manifest.json carries an additive top-level disclosure object, presented to a verifier that does NOT implement disclosure-v1. | Skip the unknown disclosure key, parse the rest of the bundle exactly as if the key were absent, and complete bundle verification per CONFORMANCE_bundle. MUST NOT fail closed on the key's presence; MUST NOT treat the disclosure key as proof material. |
A.2 — Run §A–§E checks in order on a disclosure-aware verifier
| Source | Triggering input | Required verifier behavior |
|---|---|---|
disclosure-v1.md §7 (numbered procedure) | Any .mbnt whose manifest.disclosure is present, presented to a disclosure-aware verifier. | Run the checks of §A.3, §B.1–B.5, §C.1–C.6, §D.1, §E.1–E.3 in the order given here (mirroring disclosure-v1.md §7 steps 1–5). The first failing check terminates the disclosure verification closed with the indicated fail code; subsequent checks MUST NOT be reported as "passed." Partial results (e.g. "3 of 5 revealed leaves verified") MUST NOT be reported as a success state per disclosure-v1.md §7 closing prose. |
A.3 — Version literal pinning
| Source | Triggering input | Required verifier behavior |
|---|---|---|
disclosure-v1.md §3.1, §7 step 1 | A disclosure bundle whose manifest.disclosure.version is any string other than the exact literal "satsignal.disclosure.v1" (e.g. "satsignal.disclosure.v2", "satsignal.disclosure.V1", the empty string, or the field omitted). | Fail closed with unsupported_disclosure_version. MUST NOT attempt a "best-effort" v1 parse of an unrecognized literal. |
A.4 — Forever-prohibition: no original-anchor proofs.json carried
| Source | Triggering input | Required verifier behavior |
|---|---|---|
disclosure-v1.md §4 closing "Forever-prohibition" prose | A disclosure bundle whose .mbnt archive contains a proofs.json of the original anchor (a proofs.json carrying the original document's full leaf-set, salts, or merkle leaves — distinct from any proofs.json of the disclosure bundle's own re-anchor if one exists). Triggering example: extract a valid disclosure bundle, copy the original .mbnt's proofs.json into the disclosure bundle's archive root, re-emit. | Fail closed with original_proofs_carrier_forbidden. The bundle is malformed per the §4 forever-prohibition, which now mints this dedicated code (see disclosure-v1.md §4 closing prose). MUST NOT silently ignore the file; MUST NOT process its leaves; MUST NOT report the disclosure as verified. |
§B — Linked-anchor binding chain
The five conformance items below map one-to-one to the five steps of the binding chain in disclosure-v1.md §4. A verifier MUST run them in order; the first failure terminates closed with the indicated code.
Triggering inputs are described as minimal mutations of a known-good disclosure bundle — concretely, of fixture G1 (text disclosure) or G2 (JSON disclosure) of §G, with the named field altered as described.
B.1 — Original-canonical-doc carrier missing
| Source | Triggering input | Required verifier behavior |
|---|---|---|
disclosure-v1.md §4 step 1 | From a valid disclosure bundle, delete the linked_anchor/canonical.json entry from the .mbnt archive, and run the verifier in offline-only mode (no /api/v1/proofs/<bundle_id> fetch permitted). | Fail closed with linked_anchor_carrier_missing. (Online verifiers MAY instead fetch the original .mbnt via linked_anchor.bundle_id from /api/v1/proofs/<bundle_id> and extract its canonical.json; that fallback is implementation-defined and OUT of the offline conformance suite per disclosure-v1.md §4 step 1 closing note.) |
B.2 — Carrier-to-on-chain commit mismatch
| Source | Triggering input | Required verifier behavior |
|---|---|---|
disclosure-v1.md §4 step 2 | From a valid disclosure bundle that includes the linked_anchor/canonical.json carrier, mutate any single byte of the carrier file (e.g. flip the last hex char of an inner digest, or change one byte of issuer). The carrier no longer JCS-canonicalizes to the bytes whose sha256 was committed at linked_anchor.txid. | Fail closed with linked_anchor_canonical_hash_mismatch. The verifier MUST compute sha256(linked_anchor/canonical.json bytes as stored) and compare against the on-chain document_hash at linked_anchor.txid (resolved via /lookup_hash or any other bundle-v1-conformant verification path). |
Note — on-chain commit well-formedness floor (defense-in-depth). Before performing the carrier-to-commit comparison, a conformant verifier MUST reject an on-chain
document_hashthat is absent, is not lowercase hex, or is shorter than 40 hex characters (the width of the 20-byte on-chain commitment), failing closed with the samelinked_anchor_canonical_hash_mismatchcode. This closes a vacuous-match hole: a verifier that compares only the commitment's leading width — e.g.sha256(carrier).slice(0, commit.length) === commit— would accept an empty or truncated commit (slice(0, 0) === "") and bind an arbitrary carrier to the chain. The floor is an additional trigger of the existing §B.2 code, not a new fail-code, so the §B/§C registries are unchanged. Reference implementations pin_MIN_ONCHAIN_COMMIT_HEX_LEN = 40in both the Python (disclosure/verifier.py) and JS (verify-disclosure.mjs) verifier twins.
B.3 — Carrier root vs. disclosure.linked_anchor.root mismatch
| Source | Triggering input | Required verifier behavior |
|---|---|---|
disclosure-v1.md §4 step 3 | From a valid disclosure bundle, mutate manifest.disclosure.linked_anchor.root by flipping the last hex character (0 ↔ 1). The carrier's subject.proofs.chunk_merkle.root is unchanged; the asserted linked_anchor.root no longer equals it. | Fail closed with linked_anchor_root_mismatch. The verifier parses the carrier, reads subject.proofs.chunk_merkle.root, and compares against disclosure.linked_anchor.root. |
B.4 — Carrier scheme vs. disclosure.linked_anchor.subject_profile mismatch
| Source | Triggering input | Required verifier behavior |
|---|---|---|
disclosure-v1.md §4 step 4 | From a valid disclosure bundle, mutate manifest.disclosure.linked_anchor.subject_profile to a different valid literal (e.g. change "satsignal.text.paragraph_sentence.v1" to "satsignal.json.field.v1"). The carrier's subject.proofs.chunk_merkle.scheme is unchanged. | Fail closed with linked_anchor_profile_mismatch. (This is the linked-anchor-scope profile mismatch and is distinct from the per-leaf profile_mismatch of §C.3.) The verifier reads subject.proofs.chunk_merkle.scheme from the carrier and compares against disclosure.linked_anchor.subject_profile. |
B.5 — Unsupported linked-anchor algo
| Source | Triggering input | Required verifier behavior |
|---|---|---|
disclosure-v1.md §4 step 5; profiles/csv-row-v1.md §5b, profiles/csv-column-v1.md §5b, profiles/text-line-v1.md §5b, profiles/json-keypath-v1.md §5b, profiles/json-ast-v1.md §5b | The accepted-algo set is: "sha256" for every implemented profile (incl. the native csv-column-v1) except the sealed-only native json-ast-v1, AND "merkle-hmac-sha256" (salt_version == "salt_v1") for every sealed-enabled native profile — all five: csv-row-v1 / csv-column-v1 / text-line-v1 / json-keypath-v1 / json-ast-v1 (csv-column-v1's sealed §5b uses the bare "chunk/" info; json-ast-v1's uses the scheme-prefixed "json-ast-v1/chunk/"). The native json-ast-v1 is sealed-ONLY: a standard (algo == "sha256") json-ast-v1 carrier is rejected at submit with scheme_requires_sealed (finer per-node granularity lowers per-leaf entropy), so its only accepted algo is "merkle-hmac-sha256". unsupported_linked_algo fires for any of: (a) a carrier subject.proofs.chunk_merkle.algo that is neither "sha256" nor "merkle-hmac-sha256" (e.g. "sha512", "blake3"); (b) the sealed algo "merkle-hmac-sha256" on a profile that is not sealed-enabled — a non-native profile (a salted dotted / json-field / sentence profile claiming it); (c) a sealed native carrier (algo == "merkle-hmac-sha256") whose chunk_merkle.salt_version is anything other than "salt_v1" (e.g. "salt_v2"), since salt_version is part of the sealed algo dispatch. | Fail closed with unsupported_linked_algo. Sealed-mode native (algo == "merkle-hmac-sha256", salt_version == "salt_v1") is supported for csv-row-v1, csv-column-v1, text-line-v1, json-keypath-v1, and json-ast-v1 (the additive sealed lift — no version-literal bump; json-ast-v1 is sealed-ONLY); see profiles/csv-row-v1.md §5b / profiles/csv-column-v1.md §5b / profiles/text-line-v1.md §5b / profiles/json-keypath-v1.md §5b / profiles/json-ast-v1.md §5b and disclosure-v1.md §4.5. A carrier presenting the supported sealed pair does NOT fire this code — it proceeds to the §C per-leaf checks under the sealed HMAC leaf rule. |
§C — Per-revealed-leaf verification
Items map to disclosure-v1.md §7 step 4 (per-revealed-leaf recompute) and §3.4 (merkle-proof invariants). A verifier MUST run these checks for each entry in disclosure.revealed[] before reporting the disclosure verified. Per §7 closing prose, partial verification ("3 of 5 leaves passed") MUST NOT be reported as a success state.
C.1 — Leaf-hash mismatch
| Source | Triggering input | Required verifier behavior |
|---|---|---|
disclosure-v1.md §7 step 4 (preimage recompute); per-profile §4 preimage rules | From a valid disclosure bundle, keep revealed[0].leaf_hash unchanged but mutate revealed[0].value by one byte (e.g. change a character of a text line, a CSV row, or a digit of a numeric JSON top-level-key entry — or de-canonicalize a JSON entry by adding a space after the colon, as in json_keypath_v1_native/N3_non_jcs_value_mistake). The published leaf_hash no longer matches the profile rule applied to the mutated value. | Fail closed with leaf_hash_mismatch. The verifier MUST apply the per-profile leaf rule — for a native profile the bare sha256(utf8(value)) (csv-row-v1.md §4 / text-line-v1.md §4 / json-keypath-v1.md §4) or sealed HMAC(salt_b64, utf8(value)) (§5b), selected by the carrier chunk_merkle.algo; for a salted dotted profile the (profile, leaf_id, value, salt_b64) preimage (json-field-v1.md §4, text-paragraph-sentence-v1.md §7) — selected by revealed[i].profile — and compare its output against the published revealed[i].leaf_hash. For json-keypath-v1 the value is the canonical entry "key":jcs(value) (whitespace-free, NFC); a non-JCS entry recomputes to a different hash and fires this code. |
C.2 — Merkle path mismatch
| Source | Triggering input | Required verifier behavior | ||||
|---|---|---|---|---|---|---|
disclosure-v1.md §3.4 invariants 1–3; §7 step 4 proof-walk | From a valid disclosure bundle, keep revealed[0].leaf_hash unchanged but mutate one byte of revealed[0].proof_path[0].hash (e.g. flip the last hex character). The proof-walk from leaf_hash no longer terminates at linked_anchor.root. | Fail closed with merkle_path_mismatch. The verifier walks proof_path per §3.4: at each step it decodes the 64-char lowercase hex sibling and frontier to raw 32 bytes, concatenates `(sibling \ | \ | frontier) if side == "L" else (frontier \ | \ | sibling) to a 64-byte buffer, SHA-256s, and uses the result as the new frontier. After the final step the frontier MUST equal linked_anchor.root. Verifiers MUST NOT concatenate ASCII hex strings (per §3.4` raw-byte rule). |
C.3 — Per-leaf profile mismatch
| Source | Triggering input | Required verifier behavior |
|---|---|---|
disclosure-v1.md §3.4 revealed[].profile row; §7 step 4 final sub-bullet | From a valid disclosure bundle, mutate revealed[0].profile to a different valid literal while leaving linked_anchor.subject_profile unchanged (e.g. linked_anchor.subject_profile == "satsignal.csv.row.v1" but revealed[0].profile == "satsignal.json.field.v1"). | Fail closed with profile_mismatch. This is distinct from the linked-anchor-scope linked_anchor_profile_mismatch of §B.4: §C.3 fires when the disclosure-block-internal revealed[i].profile disagrees with the same block's linked_anchor.subject_profile; §B.4 fires when the disclosure-block's linked_anchor.subject_profile disagrees with the original anchor's carrier chunk_merkle.scheme. |
C.4 — Invalid hash format
| Source | Triggering input | Required verifier behavior |
|---|---|---|
disclosure-v1.md §3.4 invariants 1 and 5 | A disclosure bundle in which any hash-typed field (linked_anchor.root, revealed[i].leaf_hash, any revealed[i].proof_path[j].hash, or presentation.view_sha256) fails to match ^[0-9a-f]{64}$. Triggering examples: uppercase hex (5D41402A…), partial-length (63 chars or 65 chars), base64, or any non-hex character. | Fail closed with invalid_hash_format. The check applies uniformly to every hash-typed field in the disclosure block. |
C.5 — View hash mismatch
| Source | Triggering input | Required verifier behavior |
|---|---|---|
disclosure-v1.md §7 step 5 | From a valid disclosure that includes a presentation block and a rendered artifact, mutate one byte of the rendered artifact's bytes as supplied to the verifier — the redacted copy delivered ALONGSIDE the .mbnt (the shipped bundle carries only manifest.json + linked_anchor/canonical.json; it does not embed the view) — e.g. change a character in the HTML view, or flip a byte in the canonical CSV view. The artifact's sha256 no longer equals presentation.view_sha256. | Fail closed with view_hash_mismatch. The verifier sha256s the raw bytes of the supplied redacted copy and compares against disclosure.presentation.view_sha256. The check applies ONLY when disclosure.presentation is present; a disclosure without presentation has no view artifact to check (per disclosure-v1.md §5 Optional table). A verifier GIVEN the redacted copy MUST run the check; one that skips it (no copy supplied) MUST NOT report the presentation as verified. |
C.6 — Sealed leaf missing salt
| Source | Triggering input | Required verifier behavior |
|---|---|---|
disclosure-v1.md §7 step 4 (sealed branch); profiles/csv-row-v1.md §5b.1, profiles/text-line-v1.md §5b.1, profiles/json-keypath-v1.md §5b.1 | A sealed native disclosure (csv-row-v1 / text-line-v1 / json-keypath-v1; carrier chunk_merkle.algo == "merkle-hmac-sha256", salt_version == "salt_v1") one of whose revealed leaves omits its salt_b64. This block is schema-valid: salt_b64 is structurally OPTIONAL for a native profile (the structural validator cannot see the carrier algo to know the leaf is sealed), so the missing salt passes the structural layer and reaches the §7 step 4 recompute. Triggering example: from a positive sealed bundle, delete revealed[0].salt_b64 entirely (fixtures csv_row_v1_native_sealed/.../S2_missing_salt, text_line_v1_native_sealed/.../S2_missing_salt, json_keypath_v1_native_sealed/.../S2_missing_salt). | Fail closed with sealed_leaf_missing_salt. For the sealed native leaf rule the per-leaf HKDF salt is REQUIRED (csv-row-v1.md §5b.1 / text-line-v1.md §5b.1 / json-keypath-v1.md §5b.1); the verifier recomputes HMAC-SHA256(base64decode(salt_b64), utf8(value)), which cannot proceed without the salt. The verifier MUST NOT synthesize an empty / zero salt, MUST NOT skip the leaf, and MUST NOT report the disclosure verified. (This is distinct from the standard native rule, where salt_b64 is legitimately ABSENT — the bare sha256(utf8(value)) leaf needs none; §C.6 fires ONLY under the sealed algo.) |
§D — Profile-literal handling
Items derived from disclosure-v1.md §7 step 3, §8 closing bullet, and §11 (profile registry pointer).
D.1 — Unsupported profile literal fails closed
| Source | Triggering input | Required verifier behavior |
|---|---|---|
disclosure-v1.md §7 step 3; §8 "Fail closed with unsupported_profile…" | A disclosure bundle whose linked_anchor.subject_profile is a literal the verifier does not implement, e.g. "satsignal.email.sentence.v99" (a hypothetical future or unknown profile), or "satsignal.csv.row.v2" against a verifier that ships only v1 profiles. | Fail closed with unsupported_profile. Silent skip is explicitly not a legal behavior per §7 step 3 — silently skipping would let a future bad-profile leaf appear "verified." The verifier MUST NOT render the leaf as verified without running the named profile's preimage rule. |
D.2 — Five v1 profile literals MUST be recognized by a fully-conformant verifier
| Source | Required verifier behavior |
|---|---|
disclosure-v1.md §11 profile registry | A fully-conformant verifier (class "Fully-conformant verifier" above) MUST recognize the five native v1 profile literals — the live set that anchors actually commit and disclosures actually bind to — and apply the profile-specific preimage / leaf-id / canonicalization rule from each profile's spec when verifying revealed leaves: |
csv-row-v1— preimage (standard baresha256) atdocs/notary_spec/profiles/csv-row-v1.md §4and leaf-id (data-row ordering, header excluded) at§3; canonicalization at§2; salt handling at§5(standard is UNSALTED) plus the sealedmerkle-hmac-sha256mode at§5b.csv-column-v1— preimage (standard baresha256of the LF-joined column) atdocs/notary_spec/profiles/csv-column-v1.md §4and leaf-id (per-column by 0-based header INDEX, header cells excluded from values) at§3; canonicalization (same RFC-4180 §2 ascsv-row-v1) at§2; salt handling at§5(standard UNSALTED) plus the sealedmerkle-hmac-sha256mode at§5b(frozen — the bare"chunk/"per-leaf HKDF info shared with the other three native profiles;csv-column-v1is in the native-sealed set on equal terms).text-line-v1— preimage (standard baresha256) atdocs/notary_spec/profiles/text-line-v1.md §4and leaf-id (non-empty-line ordering, no header) at§3; canonicalization (text-norm-v1) at§2; salt handling at§5(standard is UNSALTED) plus the sealed mode at§5b.json-keypath-v1— preimage (standard baresha256ofkey:jcs(value)) atdocs/notary_spec/profiles/json-keypath-v1.md §4and leaf-id (sorted top-level-key ordering, no header) at§3; canonicalization (json-jcs-v1) at§2; salt handling at§5(standard is UNSALTED) plus the sealed mode at§5band the two render modes at§7.json-ast-v1— preimage (sealedHMAC-SHA256(per-leaf salt, utf8(entry))of the canonical"<pointer>":<value>node entry) atdocs/notary_spec/profiles/json-ast-v1.md §4/§5band leaf-id (one leaf per JSON node — root, every object, every array, every primitive — keyed by RFC-6901 JSON Pointer, no header) at§3; canonicalization (json-jcs-v1, same asjson-keypath-v1) at§2. SEALED-ONLY: the only accepted algo ismerkle-hmac-sha256(salt_version: "salt_v1"); a standard (algo: "sha256")json-ast-v1carrier is rejected at submit withscheme_requires_sealed(finer per-node granularity lowers per-leaf entropy), andjson-ast-v1is in the native-sealed set (see §B.5) but NOT the standard-sha256set.
The deprecated dotted literals — satsignal.csv.row.v1, satsignal.json.field.v1, satsignal.text.paragraph_sentence.v1 — are NOT part of the required live set. They are inert per disclosure-v1.md §11 (retained in the merkle-scheme allowlist forever and kept as a frozen regression-guard corpus, but no production flow emits or consumes them). A verifier MAY still recognize them to walk legacy bundles, but a fully-conformant verifier is not required to implement them; the live successors are csv-row-v1 (← satsignal.csv.row.v1), json-keypath-v1 (← satsignal.json.field.v1, top-level-key vs. deep RFC-6901 pointer), and text-line-v1 (← satsignal.text.paragraph_sentence.v1, line vs. sentence granularity).
Each profile's preimage / leaf-id construction / canonicalization is normatively defined in the profile spec, not in this document. A verifier that disagrees with the profile spec on any of these rules is non-conformant for that profile literal, regardless of whether the disclosure-v1 plumbing is implemented correctly.
D.3 — Partial-conformance disclosure
| Source | Required verifier behavior |
|---|---|
disclosure-v1.md §7 step 3; §8 profile bullet | A verifier that implements a strict subset of the five v1 profile literals (e.g. CSV row + JSON top-level-key but not text, CSV column, or JSON deep-field) MUST: |
- Advertise its supported subset to its caller (the exact surface — CLI flag, API field, README — is implementation-defined).
- Fail closed with
unsupported_profileon any disclosure whoselinked_anchor.subject_profileis outside the supported subset. - MUST NOT skip the disclosure check entirely just because the disclosure's profile literal happens to be one the verifier does not support.
- MUST NOT mark the disclosure "verified" or "partially verified" when its profile is unsupported.
A disclosure bundle that involves more than one profile literal is out of scope for v1 — every revealed[i].profile MUST equal linked_anchor.subject_profile (per disclosure-v1.md §3.3 and §C.3 above), so a single disclosure bundle binds exactly one profile. A consumer verifying an audit packet that contains multiple disclosure bundles (each binding its own profile) MUST run the partial-conformance rule per bundle: any bundle whose profile is unsupported fails closed; bundles whose profile is supported verify normally.
§E — Claims rendering
Items derived from disclosure-v1.md §3.6 (claims contract) and §8 (verifier rendering behavior). The claims block is part of the cryptographic commitment (§6 JCS canonicalization) and a verifier that mis-renders it has broken the contract.
E.1 — Required does_not_prove codes present
| Source | Triggering input | Required verifier behavior |
|---|---|---|
disclosure-v1.md §3.6; required-codes list | A disclosure bundle whose disclosure.claims.does_not_prove array is missing any of: incomplete_by_design, redacted_view_not_original, satsignal_does_not_certify. Triggering example: delete the {code: "incomplete_by_design", text: "…"} entry from the array. | Fail closed with missing_claim_code. All three codes MUST be present in v1; anchorer-defined codes MAY be added alongside but the three required codes can NEVER be removed. |
E.2 — Required proves codes present
| Source | Triggering input | Required verifier behavior |
|---|---|---|
disclosure-v1.md §3.6; required-codes list (proves) | A disclosure bundle whose disclosure.claims.proves array is missing leaf_set_membership or leaf_value_match; OR whose disclosure.presentation is present but whose proves array is missing presentation_integrity. (Conversely: a disclosure whose presentation is absent is conformant if proves lacks presentation_integrity — the precondition for that code is presentation presence.) | Fail closed with missing_claim_code. The verifier MUST check the three required codes with presentation_integrity gated on the precondition. |
E.3 — Verbatim display of does_not_prove text
| Source | Required verifier behavior |
|---|---|
disclosure-v1.md §8 bullets 6 and "The verifier MUST NOT silently drop, rewrite, or summarize…" | The verifier MUST display the text field of every entry in disclosure.claims.does_not_prove verbatim, in full array order, prominently next to the disclosure view. The verifier MUST NOT: (a) drop any entry; (b) re-word the displayed text (paraphrase, summarize, translate, simplify); (c) hide an entry behind a click / expand / "more info" affordance that is not visible by default; (d) render the code instead of the text. Anchorer-supplied text is part of the cryptographic commitment per §3.6 and §6; a verifier that suppresses or rewords it has broken the contract. Identical rules apply to the proves array's entries whose preconditions are met (per §8 bullet 5). |
§F — Merkle-proof invariants
Items cross-reference disclosure-v1.md §3.4 (the merkle-proof invariants). The proof-path walk — the raw-byte fold, the L/R direction encoding, the empty-proof_path single-leaf case — is uniform and shared across every v1 profile and carrier. What is not uniform is the odd-node tree shape: that is a property of the original anchor's tree, encoded into the proof_path, and it differs by anchor. The legacy salted profiles (the deprecated dotted satsignal.csv.row.v1 and the salted JSON / text profiles) use promote-unchanged; the native csv-row-v1 profile (standard and sealed) uses duplicate-last (disclosure-v1.md §3.4). The verifier walks whatever siblings it is given and MUST NOT rebuild the root from an assumed odd-node convention — see §F.3.
F.1 — Raw-byte concatenation (positive vector)
| Source | Positive test vector | Required verifier behavior | ||
|---|---|---|---|---|
disclosure-v1.md §3.4 invariant 1 ("hash algorithm") and post-prose ("Before concatenation, each 64-character lowercase hex digest MUST be decoded to its raw 32-byte value…") | Use fixture C1 of text-paragraph-sentence-v1.md §11.1: two leaves with leaf_hash a0a9a9edaa1638eeb4122f3a295afa8138b8577364e8d921092b9c9f89a08aae (p0/s0) and 1bff79cb195d06b2602dc44ce1f7f8e4fc854e621d20954f7cf4fc47ad8e91e5 (p0/s1). Combining them per the spec's pinned rule (side: "R" from p0/s0's vantage) gives the merkle root ec0f6274cddd13fa394c0ba8f024b8b23184ccc946bc4cd01130d72cb5659094. | The verifier MUST reproduce the published root from the raw-bytes concatenation rule. Concretely: decode both 64-char hex digests to their 32-byte values, concatenate to a 64-byte buffer in the `(p0/s0_raw \ | \ | p0/s1_raw) order, SHA-256 the buffer, re-encode to 64-char lowercase hex. Result MUST equal ec0f6274cddd13fa394c0ba8f024b8b23184ccc946bc4cd01130d72cb5659094`. A verifier that ASCII-concatenates the hex strings (producing a 128-character ASCII string) and SHA-256s that instead will produce a different digest and is non-conformant. |
F.2 — Single-leaf tree (proof_path = [])
| Source | Positive test vector | Required verifier behavior |
|---|---|---|
disclosure-v1.md §3.4 invariant 2 | Use fixture B6 of json-field-v1.md §8 (single-leaf tree from {"x": 10000000000}): one leaf at /x with leaf_hash = 8c1c6fc64c987c97126d6f89a673fcfd7ae8021dff940b56658cd9b36254a49f; the merkle root equals the leaf_hash directly, and any disclosure of this leaf carries proof_path = []. | The verifier MUST accept an empty proof_path and check that the recomputed leaf_hash equals linked_anchor.root directly (no proof-walk iterations). MUST NOT inject a synthetic sibling or report merkle_path_mismatch on a legitimately-empty path; MUST NOT reject the disclosure for "proof_path too short." |
F.3 — Odd-node behavior: the anchor rule, encoded in the proof_path (structure-agnostic walk)
The odd-node convention is not a verifier-implemented uniform rule — it is the original anchor's tree shape, baked into the proof_path. A conformant verifier is structure-agnostic: it walks the supplied proof_path (folding each {side, hash} sibling into the frontier per §3.4) and compares the final frontier to linked_anchor.root. It does NOT rebuild the root and does NOT assume any single odd-node convention. The two live conventions differ only in how the builder emitted the odd-promoted node; the walk of either is identical machinery.
A verifier that rebuilds the root from an assumed convention is the bug — not one that walks. Concretely it MUST NOT assume promote-unchanged and reject a self-sibling proof_path entry as spurious, and it MUST NOT assume duplicate-last and synthesize a self-sibling level where the proof_path skipped one. Both of the positive vectors below MUST verify.
Positive vector (a) — LEGACY salted, PROMOTE-UNCHANGED (1-entry path).
| Source | Positive test vector | Required verifier behavior |
|---|---|---|
disclosure-v1.md §3.4 invariant 3 (legacy salted bullet); the frozen salted corpus tests/vectors/disclosure-v1/csv_row_v1/A1.fixture.json | The deprecated salted satsignal.csv.row.v1 fixture A1 (frozen at tests/vectors/disclosure-v1/csv_row_v1/A1.fixture.json — not csv-row-v1.md §8, whose §8 is now the native N1 below): three leaves (r000000 Alice, r000001 Bob, r000002 Carol). Level-0 pairs Alice+Bob into L1[0] = e7b134fe26e5e635dd4034eb61e01ff12a1762074733c919c7808d6a4d82df7d; Carol's L0[2] promotes unchanged to L1[1] = 083b5fa9f3666a62b29f911d81257f370314ebef4e9494eee3cbc73bc182f984. ROOT = SHA-256 of those two = 624152d7a5f8df218b275eb7561bdad6e630797e41821e6db621a9b26b01e538. The proof_path for revealing r000002 (Carol) is a single entry [{ "side": "L", "hash": "e7b134fe…df7d" }] (= L1[0]): the promoted node skipped level 0, so its path carries no level-0 sibling and folds Carol's leaf directly against L1[0] to reach the root. | The verifier MUST walk the single-entry path and reach 624152d7…e538. It MUST NOT inject a synthetic self-sibling level (i.e. MUST NOT "fix" the path by duplicating Carol's leaf), and MUST NOT reject the 1-entry path as too short. (This is the legacy salted promote-unchanged convention; it is unchanged by the native reframe and remains valid for the deprecated dotted profile and the salted JSON / text profiles.) |
Positive vector (b) — NATIVE csv-row-v1, DUPLICATE-LAST (2-entry self-sibling path).
| Source | Positive test vector | Required verifier behavior | ||||
|---|---|---|---|---|---|---|
disclosure-v1.md §3.4 invariant 3 (native duplicate-last bullet); csv-row-v1.md §6/§8 N1; frozen tests/vectors/disclosure-v1/csv_row_v1_native/N1.fixture.json | The native standard csv-row-v1 fixture N1 (csv-row-v1.md §6/§8): three bare-sha256 leaves (r000000 Alice 3147617d…800a, r000001 Bob 701287f2…9731, r000002 Carol f5edf8ce…988d). Level-0 pairs Alice+Bob into L1[0] = 4d6704c7c8fe0ad82fefbd7c7b530d8eb6087ff369568d6e763801ab9f07b5e6; Carol is the odd last node and duplicate-last self-pairs — `L1[1] = SHA-256(raw(Carol) \ | \ | raw(Carol)) = e49b438fe484c909f9795172f8aea598123c422bcf7e11f257c53ecd5875609d. ROOT = SHA-256 of those two = 19d82f92265bc904b4f356b1f69bb418e96bca56e57785d2d1ae7c1acc8d5e3e. The proof_path for revealing r000002 (Carol) is a **two-entry self-sibling path** [{ "side": "R", "hash": "f5edf8ce…988d" }, { "side": "L", "hash": "4d6704c7…b5e6" }]: the first entry is **Carol's own leaf hash** (the self-sibling that folds H(Carol\ | \ | Carol) = L1[1]), the second is L1[0] at level 1. The same duplicate-last shape applies to the native **sealed** carrier (S1, csv-row-v1.md §5b), whose Carol path is also a two-entry self-sibling path over HMAC leaves, AND to the native **text-line-v1** carriers (text_line_v1_native/N1, text_line_v1_native_sealed/S1), whose odd-last line l000002 is likewise a two-entry self-sibling path. The native **json-keypath-v1** carriers (json_keypath_v1_native/N1, json_keypath_v1_native_sealed/S1) share the **same** duplicate-last rule, though their 4-key example is **even** at every level (root 3c06af94…dbec8 standard / 8d8a2a6d…463c sealed) so no odd-last self-sibling arises in that fixture — the rule is identical, only the leaf count differs. Duplicate-last is shared by every native profile (csv-row-v1 / text-line-v1 / json-keypath-v1`, both modes). | The verifier MUST walk the two-entry path — fold Carol's leaf against the self-sibling first entry, then against L1[0] — and reach 19d82f92…5e3e. It MUST NOT reject the self-sibling first entry as a spurious / duplicate sibling, and MUST NOT assume promote-unchanged (which would skip Carol's level-0 fold and miss the root). The convention is already in the path; the verifier just folds it. |
Required behavior, precisely. The verifier MUST fold the supplied proof_path siblings (§3.4 raw-byte rule) and compare the final frontier to linked_anchor.root; it MUST verify both a promote-unchanged (skip-level, 1-entry) path and a duplicate-last (self-sibling, 2-entry) path correctly, because the convention is baked into the path it is handed. A verifier that hard-codes one convention and rebuilds the root will fail one of the two vectors above and is non-conformant.
F.4 — Leaf ordering: profile-defined, verifier does NOT re-sort
| Source | Required verifier behavior |
|---|---|
disclosure-v1.md §3.4 invariant 4; per-profile ordering rules | The merkle leaf-set order is profile-defined. The disclosure carries revealed[] entries in the order they appear in the leaf-set (which, per profile, is): |
csv-row-v1(the native literal, standard + sealed) — data-row document order, zero-indexed, header (row 0) EXCLUDED; leafiis data rowi= file rowi + 1(csv-row-v1.md §3, "Leaf ordering is data-row document order, zero-indexed").satsignal.csv.row.v1(the deprecated salted dotted literal) — document order, zero-indexed row position, every row a leaf including row 0 (the legacy ordering;csv-row-v1.md §9deprecation note + the frozen salted corpus). Retained only as the inert promote-unchanged example; new CSV disclosures use the native literal above.satsignal.json.field.v1— depth-first walk in JCS-canonical-key order (json-field-v1.md §3.3).satsignal.text.paragraph_sentence.v1— document order, paragraph then sentence (text-paragraph-sentence-v1.md §9).
The verifier MUST NOT re-sort revealed[], MUST NOT impose a verifier-local sort (e.g. by leaf_id ASCII), and MUST walk each revealed leaf's proof_path from the published leaf_hash exactly as written. A verifier that re-sorts will compute proof-walk paths against the wrong sibling indices and will spuriously report merkle_path_mismatch on conformant disclosures.
§G — Positive conformance vectors
Two end-to-end "happy path" cases that a fully-conformant verifier MUST accept. These fixtures already exist in the profile specs; this section binds them into the disclosure verifier contract by reference.
G.1 — Text disclosure (cites fixture C15)
Source fixture: text-paragraph-sentence-v1.md §11.15 (C15 — the e.g. / i.e. multi-dot-abbreviation fixture).
Construction. Wrap C15 in a disclosure bundle whose manifest.disclosure is:
version: "satsignal.disclosure.v1"disclosure_id: "<any opaque string>"linked_anchor.root: 65bc540ee29ee81834460758069e4ed48c2bc05ca8958f11db98ea5b08a060ba(the C15 merkle root, from §11.15)linked_anchor.txid: <txid of the original anchor>linked_anchor.subject_profile: "satsignal.text.paragraph_sentence.v1"linked_anchor.bundle_id: <opaque pointer to the original proof>(bundle_idis the proof id's spelling in the frozen disclosure format)revealed[0]:leaf_id=p0/s0,profile=satsignal.text.paragraph_sentence.v1,value="He said e.g. before lunch.",salt_b64=Dw8PDw8PDw8PDw8PDw8PDw==,leaf_hash=be173bfa3463ca7be346c197d5271764a3320c5ecf32408ee0b3aa1fce279882,proof_path=[{"side":"R","hash":"1a455dea1a4456b1bb6af79022bb3bdd77900367a4a91184904d3c395cb9dcb4"}]revealed[1]:leaf_id=p0/s1,profile=satsignal.text.paragraph_sentence.v1,value="Then i.e. after.",salt_b64=8PDw8PDw8PDw8PDw8PDw8A==,leaf_hash=1a455dea1a4456b1bb6af79022bb3bdd77900367a4a91184904d3c395cb9dcb4,proof_path=[{"side":"L","hash":"be173bfa3463ca7be346c197d5271764a3320c5ecf32408ee0b3aa1fce279882"}]claims: with the three requireddoes_not_provecodes and the two requiredprovescodes (nopresentation_integrityunlesspresentationis included).linked_anchor/canonical.jsonin the.mbntarchive: a canonical doc whosesubject.proofs.chunk_merkle = {scheme: "satsignal.text.paragraph_sentence.v1", algo: "sha256", leaf_count: 2, root: "65bc540ee29ee81834460758069e4ed48c2bc05ca8958f11db98ea5b08a060ba"}.
Expected verifier outputs at each §4 / §7 step:
§7step 1 — version literal matches"satsignal.disclosure.v1"→ pass.§4step 1 — carrierlinked_anchor/canonical.jsonpresent → pass.§4step 2 —sha256(carrier_bytes) == on_chain_document_hash→ pass.§4step 3 — carrierchunk_merkle.root == 65bc540…0ba == disclosure.linked_anchor.root→ pass.§4step 4 — carrierchunk_merkle.scheme == "satsignal.text.paragraph_sentence.v1" == disclosure.linked_anchor.subject_profile→ pass.§4step 5 — carrierchunk_merkle.algo == "sha256"→ pass.§7step 3 —subject_profileliteral recognized by a fully-conformant verifier → pass.§7step 4 (per-leaf,p0/s0) — preimage recompute viatext-paragraph-sentence-v1.md §7yieldsbe173bfa…9882, matches publishedleaf_hash; proof-walk from leaf with oneside: Rsibling yields65bc540…0ba, matcheslinked_anchor.root;revealed[0].profile == subject_profile→ pass.§7step 4 (per-leaf,p0/s1) — analogous walk yields65bc540…0ba→ pass.§7step 5 — nopresentationblock, no view-hash check → not applicable.§8claims rendering — three requireddoes_not_provecodes present; requiredprovescodes present (withpresentation_integrityomitted by precondition) → pass.
Disclosure VERIFIED.
G.2 — JSON disclosure (cites fixture B1)
Source fixture: json-field-v1.md §8 (B1 — minimal {"name":"Alice","age":42}).
Construction. Wrap B1 in a disclosure bundle whose manifest.disclosure is:
version: "satsignal.disclosure.v1"disclosure_id: "<any opaque string>"linked_anchor.root: c1f5e68c87dcbf89ebd99b0967a34e81fb730b70569733c295f9a4769132e17c(the B1 root, from §8)linked_anchor.txid: <txid of the original anchor>linked_anchor.subject_profile: "satsignal.json.field.v1"linked_anchor.bundle_id: <opaque pointer to the original proof>(bundle_idis the proof id's spelling in the frozen disclosure format)revealed[0]:leaf_id=/age,profile=satsignal.json.field.v1,value=42,salt_b64=/+N9Hd4a6Sp3vMDBX4kBvQ==,leaf_hash=533e320213e48d42a8a9472c8ad12739a576ab172fd126b632ad4f27a79ae687,proof_path=[{"side":"R","hash":"e5cb099fc0fe04f443c0ff86879162159cfed1fa92c80862a86021d391f9563c"}]- (Alternatively reveal
/nameonly, with the symmetricside: Lproof path. Or reveal both.) claims: with the three requireddoes_not_provecodes and the two requiredprovescodes.linked_anchor/canonical.jsonin the.mbntarchive: a canonical doc whosesubject.proofs.chunk_merkle = {scheme: "satsignal.json.field.v1", algo: "sha256", leaf_count: 2, root: "c1f5e68c87dcbf89ebd99b0967a34e81fb730b70569733c295f9a4769132e17c"}.
Expected verifier outputs at each §4 / §7 step: mirror G.1's walk verbatim, substituting the B1 root and the json-field-v1.md §4 preimage rule. The preimage rule for revealed[0] is documented explicitly in json-field-v1.md §4.3 (48-byte preimage, sha256 → 533e320213e48d42a8a9472c8ad12739a576ab172fd126b632ad4f27a79ae687).
Disclosure VERIFIED.
G.3 — Native CSV standard disclosure (frozen fixture N1)
Source fixture: tests/vectors/disclosure-v1/csv_row_v1_native/N1.fixture.json the frozen native standard corpus; the inline values mirror profiles/csv-row-v1.md §4/§8 N1 (root 19d82f92265bc904b4f356b1f69bb418e96bca56e57785d2d1ae7c1acc8d5e3e).
Construction. A disclosure bundle over the native standard CSV anchor (name,age,role\nAlice,42,Engineer\nBob,35,Designer\nCarol,29,Writer):
linked_anchor.subject_profile: "csv-row-v1"(the hyphenated native literal).linked_anchor/canonical.jsoncarrier whosesubject.proofs.chunk_merkle = {scheme: "csv-row-v1", algo: "sha256", leaf_count: 3, root: 19d82f…5e3e}.revealed[]entries carry nosalt_b64(standard is UNSALTED,profiles/csv-row-v1.md §5); each leaf is the baresha256(utf8(value)).
Expected verifier outputs: the §4 binding chain passes (§4 step 5 admits algo == "sha256"); per-leaf §7 step 4 recomputes each leaf as the bare sha256(utf8(value)) (no salt read) and walks the duplicate-last proof path to the root. Disclosure VERIFIED.
G.4 — Native CSV sealed disclosure (frozen fixture S1)
Source fixture: tests/vectors/disclosure-v1/csv_row_v1_native_sealed/S1.fixture.json the frozen native sealed corpus; the inline values mirror profiles/csv-row-v1.md §5b (root 2207e09f1cafe3cb7099d905d47eef8c998d42a0b2413b3a0a0413110f47f6a3).
Construction. A sealed disclosure bundle over the same CSV anchored in sealed mode:
linked_anchor.subject_profile: "csv-row-v1".linked_anchor/canonical.jsoncarrier whosesubject.proofs.chunk_merkle = {scheme: "csv-row-v1", algo: "merkle-hmac-sha256", salt_version: "salt_v1", leaf_count: 3, root: 2207e0…f6a3}.revealed[]revealsr000000(Alice) andr000002(Carol, the odd-last self-sibling node) and REDACTSr000001(Bob). Each revealed entry carriessalt_b64= the per-leaf HKDF salt (never the master salt;profiles/csv-row-v1.md §5b.1master-salt-strip rule) andleaf_hash= the sealed HMAC leaf.
Expected verifier outputs: §4 step 5 admits ("csv-row-v1", "merkle-hmac-sha256") with salt_version == "salt_v1"; per-leaf §7 step 4 recomputes each leaf as HMAC-SHA256(base64decode(salt_b64), utf8(value)) under the published per-leaf salt and walks the duplicate-last proof path (including Carol's two-entry self-sibling path) to the root. Disclosure VERIFIED.
G.5 — Native TEXT standard disclosure (frozen fixture N1)
Source fixture: tests/vectors/disclosure-v1/text_line_v1_native/N1.fixture.json the frozen native standard text corpus; the inline values mirror profiles/text-line-v1.md §4/§8 N1 (root 5e4f6278d3e8f1a8175e8f635e76f828262e48b3f4e5b6ffd326357cc04a607c).
Construction. A disclosure over a native standard TEXT anchor (a .txt with a BOM, a blank line, and a trailing-whitespace line — canon drops the blank line and strips trailing whitespace → 3 non-empty-line leaves; NO header):
linked_anchor.subject_profile: "text-line-v1"(the hyphenated native literal).linked_anchor/canonical.jsoncarrier whosesubject.proofs.chunk_merkle = {scheme: "text-line-v1", algo: "sha256", leaf_count: 3, root: 5e4f62…607c}.revealed[]revealsl000000+l000002(the odd-last self-sibling) and REDACTSl000001; entries carry nosalt_b64; each leaf is the baresha256(utf8(line)).
Expected verifier outputs: §4 binding passes (admits algo == "sha256"); per-leaf §7 step 4 recomputes the bare sha256(utf8(value)) and walks the duplicate-last path to the root. Disclosure VERIFIED.
G.6 — Native TEXT sealed disclosure (frozen fixture S1)
Source fixture: tests/vectors/disclosure-v1/text_line_v1_native_sealed/S1.fixture.json ; the inline values mirror profiles/text-line-v1.md §5b (root 0c108f30ce50f1348d572a9480806d6e0bf990971abb88e440206cd5175b0358).
Construction. A sealed disclosure over the same text anchored in sealed mode:
linked_anchor.subject_profile: "text-line-v1".- carrier
subject.proofs.chunk_merkle = {scheme: "text-line-v1", algo: "merkle-hmac-sha256", salt_version: "salt_v1", leaf_count: 3, root: 0c108f…0358}. revealed[]revealsl000000+l000002(odd-last self-sibling), REDACTSl000001; each revealed entry carriessalt_b64= the per-leaf HKDF salt (never the master salt;text-line-v1.md §5b.1) and the sealed HMACleaf_hash.
Expected verifier outputs: §4 step 5 admits ("text-line-v1", "merkle-hmac-sha256") with salt_version == "salt_v1"; per-leaf §7 step 4 recomputes HMAC-SHA256(base64decode(salt_b64), utf8(value)) and walks the duplicate-last path (including the odd-last two-entry self-sibling path) to the root. Disclosure VERIFIED.
G.7 — Native JSON standard disclosure (frozen fixture N1)
Source fixture: tests/vectors/disclosure-v1/json_keypath_v1_native/N1.fixture.json the frozen native standard JSON corpus; the inline values mirror profiles/json-keypath-v1.md §4/§6/§8 N1 (root 3c06af94a0af735cd19cc77349b7a962464cda703be16c66bbad2395913dbec8).
Construction. A disclosure over a native standard JSON anchor (the compact object {"name":"AcmeCorp","ssn":"123-45-6789","balance":1000,"public_id":42} → JCS canon, objects-only, sorted top-level keys balance,name,public_id,ssn → 4 leaves; NO header):
linked_anchor.subject_profile: "json-keypath-v1"(the hyphenated native literal).linked_anchor/canonical.jsoncarrier whosesubject.proofs.chunk_merkle = {scheme: "json-keypath-v1", algo: "sha256", leaf_count: 4, root: 3c06af…dbec8}.revealed[]revealsk000001("name":"AcmeCorp") +k000002("public_id":42) and REDACTSk000000(balance) +k000003(ssn); entries carry nosalt_b64; each leaf is the baresha256(utf8(entry))over the canonical"key":jcs(value)entry.presentationis the drop-mode copy{"name":"AcmeCorp","public_id":42},view_sha256 = 4d07c173…a444b.
Expected verifier outputs: §4 binding passes (admits algo == "sha256"); per-leaf §7 step 4 recomputes the bare sha256(utf8(value)) over the canonical entry and walks the duplicate-last path to the root. Disclosure VERIFIED.
G.8 — Native JSON sealed disclosure (frozen fixture S1)
Source fixture: tests/vectors/disclosure-v1/json_keypath_v1_native_sealed/S1.fixture.json ; the inline values mirror profiles/json-keypath-v1.md §5b (root 8d8a2a6d5b0490b19501452a729a967e1306df3164d2e8fc129c9afd52cb463c).
Construction. A sealed disclosure over the same object anchored in sealed mode:
linked_anchor.subject_profile: "json-keypath-v1".- carrier
subject.proofs.chunk_merkle = {scheme: "json-keypath-v1", algo: "merkle-hmac-sha256", salt_version: "salt_v1", leaf_count: 4, root: 8d8a2a…463c}. revealed[]revealsk000001+k000002, REDACTSk000000+k000003; each revealed entry carriessalt_b64= the per-leaf HKDF salt (never the master salt;json-keypath-v1.md §5b.1) and the sealed HMACleaf_hash.
Expected verifier outputs: §4 step 5 admits ("json-keypath-v1", "merkle-hmac-sha256") with salt_version == "salt_v1"; per-leaf §7 step 4 recomputes HMAC-SHA256(base64decode(salt_b64), utf8(value)) and walks the duplicate-last path to the root. Disclosure VERIFIED.
§H — Test vector registry
Every fixture used in this document, with file pointers back to the profile-spec source.
| Fixture handle | Source file | Section | Purpose under this CONFORMANCE doc |
|---|---|---|---|
| C1 (text minimal) | docs/notary_spec/profiles/text-paragraph-sentence-v1.md | §11.1 | F.1 (raw-bytes concat positive vector); root ec0f6274…5094 |
| C15 (text multi-dot abbreviations) | docs/notary_spec/profiles/text-paragraph-sentence-v1.md | §11.15 | G.1 (text-disclosure happy path); root 65bc540e…060ba |
A1 (CSV minimal 3-row — DEPRECATED salted satsignal.csv.row.v1) | tests/vectors/disclosure-v1/csv_row_v1/A1.fixture.json (frozen salted corpus) | — | F.3 vector (a): legacy salted promote-unchanged positive vector (Carol = 1-entry path); root 624152d7…e538. NOT csv-row-v1.md §8 (whose §8 is the native N1 below). |
| B1 (JSON minimal) | docs/notary_spec/profiles/json-field-v1.md | §8 | G.2 (JSON-disclosure happy path); root c1f5e68c…e17c |
| B6 (JSON single-leaf) | docs/notary_spec/profiles/json-field-v1.md | §8 | F.2 (single-leaf-tree positive vector); leaf-and-root 8c1c6fc6…a49f |
| §3 example disclosure | docs/notary_spec/disclosure-v1.md | §3 | Reference shape for §A.2 and the §B mutation-vector descriptions |
The above are the profile-spec prose fixtures. In addition, two frozen JSON disclosure corpora for the native csv-row-v1 profile ship on disk and a conformance runner consumes them directly via tests/vectors/disclosure-v1/schema/fixture.schema.json. Each fixture pins expected.verify_ok (and, for negatives, the expected_fail_code / expected.fail_code); they bind the §B / §C / §D codes to concrete native payloads.
Native CSV — STANDARD corpus (tests/vectors/disclosure-v1/csv_row_v1_native/, algo: "sha256", unsalted):
| Fixture handle | File | Maps to | Expected |
|---|---|---|---|
N1 (CSV minimal 3-row — NATIVE csv-row-v1, duplicate-last) | csv_row_v1_native/N1.fixture.json; profile source csv-row-v1.md §6/§8 N1 | §G.3 positive end-to-end; F.3 vector (b) (native duplicate-last self-sibling; Carol = 2-entry path); root 19d82f92…5e3e | verify_ok: true |
| N2_linked_anchor_profile_mismatch | csv_row_v1_native/N2_linked_anchor_profile_mismatch.fixture.json | §B.4 | linked_anchor_profile_mismatch |
| N3_header_included_mistake | csv_row_v1_native/N3_header_included_mistake.fixture.json | §C.2 (a header-included leaf-set yields a wrong path) | merkle_path_mismatch |
| N1_leaf_hash_mismatch | csv_row_v1_native/negatives/N1_leaf_hash_mismatch.fixture.json | §C.1 | leaf_hash_mismatch |
| N1_merkle_path_mismatch | csv_row_v1_native/negatives/N1_merkle_path_mismatch.fixture.json | §C.2 | merkle_path_mismatch |
| N1_linked_anchor_root_mismatch | csv_row_v1_native/negatives/N1_linked_anchor_root_mismatch.fixture.json | §B.3 | linked_anchor_root_mismatch |
| N1_linked_anchor_canonical_hash_mismatch | csv_row_v1_native/negatives/N1_linked_anchor_canonical_hash_mismatch.fixture.json | §B.2 | linked_anchor_canonical_hash_mismatch |
Native CSV — SEALED corpus (tests/vectors/disclosure-v1/csv_row_v1_native_sealed/, algo: "merkle-hmac-sha256", salt_version: "salt_v1", per-leaf HKDF salts):
| Fixture handle | File | Maps to | Expected |
|---|---|---|---|
| S1 | csv_row_v1_native_sealed/S1.fixture.json | §G.4 positive end-to-end (HMAC leaf) | verify_ok: true |
| S1_leaf_hash_mismatch | csv_row_v1_native_sealed/negatives/S1_leaf_hash_mismatch.fixture.json | §C.1 | leaf_hash_mismatch |
| S1_wrong_salt | csv_row_v1_native_sealed/negatives/S1_wrong_salt.fixture.json | §C.1 (swapped per-leaf salt) | leaf_hash_mismatch |
| S1_merkle_path_mismatch | csv_row_v1_native_sealed/negatives/S1_merkle_path_mismatch.fixture.json | §C.2 | merkle_path_mismatch |
| S1_linked_anchor_root_mismatch | csv_row_v1_native_sealed/negatives/S1_linked_anchor_root_mismatch.fixture.json | §B.3 | linked_anchor_root_mismatch |
| S1_linked_anchor_canonical_hash_mismatch | csv_row_v1_native_sealed/negatives/S1_linked_anchor_canonical_hash_mismatch.fixture.json | §B.2 | linked_anchor_canonical_hash_mismatch |
| S2_missing_salt | csv_row_v1_native_sealed/negatives/S2_missing_salt.fixture.json | §C.6 (sealed leaf lacking salt_b64) | sealed_leaf_missing_salt |
| S3_wrong_salt_version | csv_row_v1_native_sealed/negatives/S3_wrong_salt_version.fixture.json | §B.5(c) (salt_version != "salt_v1") | unsupported_linked_algo |
| S4_linked_anchor_profile_mismatch | csv_row_v1_native_sealed/negatives/S4_linked_anchor_profile_mismatch.fixture.json | §B.4 (sealed carrier scheme == "csv-row-v2" != subject_profile) | linked_anchor_profile_mismatch |
Native TEXT — STANDARD corpus (tests/vectors/disclosure-v1/text_line_v1_native/, scheme: "text-line-v1", algo: "sha256", unsalted; NO header, empty lines dropped, duplicate-last):
| Fixture handle | File | Maps to | Expected |
|---|---|---|---|
N1 (text minimal 3-line — NATIVE text-line-v1, duplicate-last) | text_line_v1_native/N1.fixture.json; profile source text-line-v1.md §4/§6/§8 N1 | §G.5 positive end-to-end; F.3 vector (b) shape (native duplicate-last self-sibling; l000002 = 2-entry path); root 5e4f6278…607c | verify_ok: true |
| N2_linked_anchor_profile_mismatch | text_line_v1_native/N2_linked_anchor_profile_mismatch.fixture.json | §B.4 (carrier scheme text-line-v2) | linked_anchor_profile_mismatch |
| N3_empty_line_included_mistake | text_line_v1_native/N3_empty_line_included_mistake.fixture.json | §C.2 (a blank-line-included leaf-set yields a wrong path — pins text-line-v1.md §3 drop-empties) | merkle_path_mismatch |
| N1_leaf_hash_mismatch | text_line_v1_native/negatives/N1_leaf_hash_mismatch.fixture.json | §C.1 | leaf_hash_mismatch |
| N1_merkle_path_mismatch | text_line_v1_native/negatives/N1_merkle_path_mismatch.fixture.json | §C.2 | merkle_path_mismatch |
| N1_linked_anchor_root_mismatch | text_line_v1_native/negatives/N1_linked_anchor_root_mismatch.fixture.json | §B.3 | linked_anchor_root_mismatch |
| N1_linked_anchor_canonical_hash_mismatch | text_line_v1_native/negatives/N1_linked_anchor_canonical_hash_mismatch.fixture.json | §B.2 | linked_anchor_canonical_hash_mismatch |
Native TEXT — SEALED corpus (tests/vectors/disclosure-v1/text_line_v1_native_sealed/, scheme: "text-line-v1", algo: "merkle-hmac-sha256", salt_version: "salt_v1", per-leaf HKDF salts):
| Fixture handle | File | Maps to | Expected |
|---|---|---|---|
| S1 | text_line_v1_native_sealed/S1.fixture.json | §G.6 positive end-to-end (HMAC leaf); root 0c108f30…0358 | verify_ok: true |
| S1_leaf_hash_mismatch | text_line_v1_native_sealed/negatives/S1_leaf_hash_mismatch.fixture.json | §C.1 | leaf_hash_mismatch |
| S1_wrong_salt | text_line_v1_native_sealed/negatives/S1_wrong_salt.fixture.json | §C.1 (swapped per-leaf salt) | leaf_hash_mismatch |
| S1_merkle_path_mismatch | text_line_v1_native_sealed/negatives/S1_merkle_path_mismatch.fixture.json | §C.2 | merkle_path_mismatch |
| S1_linked_anchor_root_mismatch | text_line_v1_native_sealed/negatives/S1_linked_anchor_root_mismatch.fixture.json | §B.3 | linked_anchor_root_mismatch |
| S1_linked_anchor_canonical_hash_mismatch | text_line_v1_native_sealed/negatives/S1_linked_anchor_canonical_hash_mismatch.fixture.json | §B.2 | linked_anchor_canonical_hash_mismatch |
| S2_missing_salt | text_line_v1_native_sealed/negatives/S2_missing_salt.fixture.json | §C.6 | sealed_leaf_missing_salt |
| S3_wrong_salt_version | text_line_v1_native_sealed/negatives/S3_wrong_salt_version.fixture.json | §B.5(c) | unsupported_linked_algo |
| S4_linked_anchor_profile_mismatch | text_line_v1_native_sealed/negatives/S4_linked_anchor_profile_mismatch.fixture.json | §B.4 (sealed carrier scheme == "text-line-v2") | linked_anchor_profile_mismatch |
Native JSON — STANDARD corpus (tests/vectors/disclosure-v1/json_keypath_v1_native/, scheme: "json-keypath-v1", algo: "sha256", unsalted; JCS canon, objects-only, sorted top-level keys, NO header, duplicate-last):
| Fixture handle | File | Maps to | Expected |
|---|---|---|---|
N1 (JSON minimal 4-key — NATIVE json-keypath-v1, duplicate-last) | json_keypath_v1_native/N1.fixture.json; profile source json-keypath-v1.md §4/§6/§8 N1 | §G.7 positive end-to-end; shares the native duplicate-last merkle (even 4-leaf example, no odd-last self-sibling in this fixture); root 3c06af94…dbec8 | verify_ok: true |
| N2_linked_anchor_profile_mismatch | json_keypath_v1_native/N2_linked_anchor_profile_mismatch.fixture.json | §B.4 (carrier scheme json-keypath-v2) | linked_anchor_profile_mismatch |
| N3_non_jcs_value_mistake | json_keypath_v1_native/N3_non_jcs_value_mistake.fixture.json | §C.1 (a non-JCS entry — space after the colon — recomputes to a different hash; pins json-keypath-v1.md §4 the value is the canonical whitespace-free "key":jcs(value)) | leaf_hash_mismatch |
| N1_leaf_hash_mismatch | json_keypath_v1_native/negatives/N1_leaf_hash_mismatch.fixture.json | §C.1 | leaf_hash_mismatch |
| N1_merkle_path_mismatch | json_keypath_v1_native/negatives/N1_merkle_path_mismatch.fixture.json | §C.2 | merkle_path_mismatch |
| N1_linked_anchor_root_mismatch | json_keypath_v1_native/negatives/N1_linked_anchor_root_mismatch.fixture.json | §B.3 | linked_anchor_root_mismatch |
| N1_linked_anchor_canonical_hash_mismatch | json_keypath_v1_native/negatives/N1_linked_anchor_canonical_hash_mismatch.fixture.json | §B.2 | linked_anchor_canonical_hash_mismatch |
Native JSON — SEALED corpus (tests/vectors/disclosure-v1/json_keypath_v1_native_sealed/, scheme: "json-keypath-v1", algo: "merkle-hmac-sha256", salt_version: "salt_v1", per-leaf HKDF salts):
| Fixture handle | File | Maps to | Expected |
|---|---|---|---|
| S1 | json_keypath_v1_native_sealed/S1.fixture.json | §G.8 positive end-to-end (HMAC leaf); root 8d8a2a6d…463c | verify_ok: true |
| S1_leaf_hash_mismatch | json_keypath_v1_native_sealed/negatives/S1_leaf_hash_mismatch.fixture.json | §C.1 | leaf_hash_mismatch |
| S1_wrong_salt | json_keypath_v1_native_sealed/negatives/S1_wrong_salt.fixture.json | §C.1 (swapped per-leaf salt) | leaf_hash_mismatch |
| S1_merkle_path_mismatch | json_keypath_v1_native_sealed/negatives/S1_merkle_path_mismatch.fixture.json | §C.2 | merkle_path_mismatch |
| S1_linked_anchor_root_mismatch | json_keypath_v1_native_sealed/negatives/S1_linked_anchor_root_mismatch.fixture.json | §B.3 | linked_anchor_root_mismatch |
| S1_linked_anchor_canonical_hash_mismatch | json_keypath_v1_native_sealed/negatives/S1_linked_anchor_canonical_hash_mismatch.fixture.json | §B.2 | linked_anchor_canonical_hash_mismatch |
| S2_missing_salt | json_keypath_v1_native_sealed/negatives/S2_missing_salt.fixture.json | §C.6 | sealed_leaf_missing_salt |
| S3_wrong_salt_version | json_keypath_v1_native_sealed/negatives/S3_wrong_salt_version.fixture.json | §B.5(c) | unsupported_linked_algo |
| S4_linked_anchor_profile_mismatch | json_keypath_v1_native_sealed/negatives/S4_linked_anchor_profile_mismatch.fixture.json | §B.4 (sealed carrier scheme != subject_profile) | linked_anchor_profile_mismatch |
The profile-spec prose fixtures above are the existing inline material; this CONFORMANCE document does NOT mint new fixtures. The frozen native corpora are the on-disk frozen material captured against the profile rules; this registry binds them to the §A–§E codes by reference.
§I — Out-of-scope for v1 conformance
Per disclosure-v1.md §12, the following are explicitly out of scope for satsignal.disclosure.v1 conformance. A claim of conformance under this document does NOT imply any of:
- Email-profile conformance. No
satsignal.email.sentence.v1or any other email-shaped profile is part of v1 (perdisclosure-v1.md §12"Email profiles" bullet). A verifier that ships an email-profile implementation does so outside this conformance suite. - PDF / DOCX native-redaction conformance. Disclosure under v1 generates a new presentation artifact (HTML / TXT / canonical CSV / canonical JSON); it does not mutate the original PDF / DOCX. Native-format redacted-PDF verification is not in scope.
- Word-level / token-level disclosure conformance. No
text.word.v1ortext.token.v1. v1's leaf granularity is profile-defined (CSV row, text line, JSON top-level key, text sentence). Sub-line / sub-sentence disclosure is not in scope; sentence-level text disclosure is a planned FUTURE anchor scheme (text-sentence-v1), not part of v1. Deep-field / RFC-6901-pointer JSON disclosure (a leaf per nested field) is likewise NOT this profile —json-keypath-v1granularity is top-level key only; a deep-field anchor scheme is a planned future scheme, not part of v1. - Native
text-line-v1disclosure conformance — IN scope. Standard AND sealed disclosure of a nativetext-line-v1anchor IS conformant, perprofiles/text-line-v1.mdanddisclosure-v1.md §4.5/§11 (repeats thecsv-row-v1pattern for text; no version-literal bump). A disclosure-aware verifier MUST verify it under the SAME native leaf rule ascsv-row-v1(baresha256(utf8(value))standard /HMAC(per-leaf salt, utf8(value))sealed) — the value is the canonical non-empty line — walking the duplicate-last proof path to the committed root. Thetext-line-v1leaf-set EXCLUDES empty lines and has NO header (leaf 0 = first non-empty line). - Native
json-keypath-v1disclosure conformance — IN scope. Standard AND sealed disclosure of a nativejson-keypath-v1anchor IS conformant, perprofiles/json-keypath-v1.mdanddisclosure-v1.md §4.5/§11 (repeats thecsv-row-v1/text-line-v1pattern for JSON; no version-literal bump). A disclosure-aware verifier MUST verify it under the SAME native leaf rule ascsv-row-v1/text-line-v1(baresha256(utf8(value))standard /HMAC(per-leaf salt, utf8(value))sealed) — the value is the canonical top-level-key entry"key":jcs(value)(JCS, whitespace-free, NFC) — walking the duplicate-last proof path to the committed root. Thejson-keypath-v1leaf-set is the top-level keys sorted by code point, objects-only (a top-level array/scalar has nochunk_merkleand is not redactable), with NO header (leaf 0 = first sorted key). The redacted copy ships in one of TWO owner-chosen render modes (drop default / mask), which are presentation-only —presentation.view_sha256binds the copy bytes; the proof binds the revealed keys, independent of render mode. - Sealed-mode native disclosure conformance — IN scope (
csv-row-v1,text-line-v1,json-keypath-v1). Sealed-mode disclosure of a native anchor (algo: "merkle-hmac-sha256",salt_version: "salt_v1") IS conformant, perprofiles/csv-row-v1.md §5b/profiles/text-line-v1.md §5b/profiles/json-keypath-v1.md §5banddisclosure-v1.md §4.5(the additive sealed lift — no version-literal bump). A disclosure-aware verifier MUST verify it: recompute each revealed leaf asHMAC-SHA256(base64decode(salt_b64), utf8(value))under the published per-leaf salt (never the master salt), walk the proof path to the committed root, and report the disclosure verified when the chain holds. The verifier MUST fail closed withsealed_leaf_missing_salt(§C.6) on a sealed revealed leaf lacking its requiredsalt_b64, and withunsupported_linked_algo(§B.5) on a sealed native carrier whosesalt_versionis not"salt_v1"or on the sealed algo presented for a non-native profile. (Genuinely out of scope: sealed-mode for the salted dotted /json-field-v1/text-paragraph-sentence-v1profiles —merkle-hmac-sha256is native-only, and a verifier encountering it on those profiles fails closed withunsupported_linked_algoper §B.5(b); and any futurealgoother than"sha256"/"merkle-hmac-sha256".) - Disclosure Builder (client-side) conformance. The Builder is a client-side artifact; the server only ever sees the disclosure bundle hash for the optional T2 anchor. UI conformance for the Builder is not in scope.
- Canonical schema-extension conformance.
canonical.schema.jsonis not extended for disclosure (per§2and§12); nosubtype: "disclosure"exists. Anyone who emits a canonical doc with adisclosurefield has produced a non-conformant canonical doc, and abundle-v1-conformant verifier will reject it under the existingadditionalProperties: falserule (independent of this document). - New on-chain subtype conformance. No new MBNT subtype byte is allocated for disclosure (per
§12). The T2 anchor of a disclosure bundle is mechanically identical to any other standard anchor; on-chain conformance isbundle-v1's concern, not this document's.
§J — Followups (in-tree gaps)
Disclosure conformance has zero executable checks in-tree as of 2026-05-27 — there is no scripts/test_disclosure_*.py runner, no tests/vectors/disclosure-v1/ directory, and no scripts/run_vectors.py-equivalent entry that exercises a frozen disclosure .mbnt against the §A–§F checklist. Every fixture cited in §G and §H lives in a profile spec as prose; none have been captured as a frozen .mbnt. The categories below are tracked followups; adopters needing immediate executable coverage should supply their own against the cited profile fixtures.
- Disclosure-bundle fixture set. RESOLVED (block layer); partial-RESOLVED (archive layer). A frozen JSON fixture corpus at
tests/vectors/disclosure-v1/carries positive vectors (csv_row_v1/A1.fixture.jsonthroughA10— the DEPRECATED saltedsatsignal.csv.row.v1corpus, promote-unchanged, retained as an inert regression guard, distinct from the LIVE nativecsv-row-v1vectors N1 (standard) + S1 (sealed) undercsv_row_v1_native/+csv_row_v1_native_sealed/;json_field_v1/B1throughB13;text_paragraph_sentence_v1/C1throughC15) and negative end-to- end vectors for every fail code in §A.3, §B.1–B.5, §C.1–C.6, §D.1, and §E.1–E.2 (one fixture per code underdisclosure_end_to_end/negatives/). A stdlib-only conformance runner (Python or any other language) consumes these viatests/vectors/disclosure-v1/schema/fixture.schema.jsonand runs the §A–§E checklist against the corpus'sdisclosure_block+linked_anchor_carrierpayloads. The fixture-corpus layer also satisfies the cross-language drift-prevention contract: any conformant implementation in any language proves itself by matching every fixture'sexpected_*byte-for-byte.
Still residual. A frozen .mbnt archive set (binary-zip format with manifest.json + canonical.json + optional proofs.json + optional rendered-view sidecar) is not yet in-tree. The corpus models the disclosure-block and carrier-bytes layers above the bundle envelope; archive-level checks for §A.4 (original_proofs_carrier_forbidden triggered by an unrevealed proofs.json in the zip) and §C.5 (view_hash_mismatch against the rendered artifact delivered with the archive) are not exercised by the JSON corpus and remain open as the residual §J.1 archive-layer gap. Adopters needing archive-layer coverage should ship their own .mbnt test archives wrapped around these fixtures' disclosure blocks.
- Positive G.1 / G.2 vector capture. The two happy-path vectors in §G are currently described as constructions over profile-spec fixtures (C15 / B1). Capturing them as concrete
.mbntbundles with a published canonical-doc carrier and a published on-chaindocument_hash(mock or real) would letscripts/test_legacy_bundles.pygain a disclosure sibling —scripts/test_disclosure_bundles.py— that walks §A–§E end-to-end against a frozen archive. - Merkle-machinery positive vectors as standalone tests. §F.1 (raw-byte concat), §F.2 (single-leaf tree), §F.3 (odd-node walk — both the legacy salted promote-unchanged 1-entry path and the native
csv-row-v1duplicate-last 2-entry self-sibling path) are crisp arithmetic checks that could be exercised with a tiny stdlib script over the cited profile fixtures without needing a full.mbnt. A targetedscripts/test_merkle_invariants.pywould close this gap quickly. - §A.4 fail-code mint. RESOLVED.
disclosure-v1.md §4's forever-prohibition on the original anchor'sproofs.jsonriding in the disclosure bundle now mints a dedicated fail code:original_proofs_carrier_forbidden. The code is pinned indisclosure-v1.md §4closing "Forever-prohibition" prose and cited as the required verifier output in §A.4 above. Verifier authors now have a stable identifier to surface for this case; no fallback to generic envelope-level "malformed bundle" is required. - Multi-disclosure audit-packet conformance. §D.3 contemplates a consumer verifying an audit packet containing multiple disclosure bundles (each binding its own profile). The verifier behavior is implied by per-bundle conformance, but no audit-packet-level conformance contract is pinned in
disclosure-v1.md. A follow-up spec (e.g.audit-packet-v1revision) should pin the per-bundle roll-up rules. - Claims-rendering executable check. §E.3 (verbatim display of
does_not_provetext) is a UI invariant and cannot be exercised by a stdlib data-level test. A reference-implementation invariant inverifier.html(analogous to the §2.2 envelope-hardening enforcement at lines 1944–2111 cited inCONFORMANCE_bundle.md) would let adopters point at a known-correct implementation; a linter check would be even better. Currently both are absent.
Cross-references
docs/notary_spec/disclosure-v1.md— normative spec.docs/notary_spec/profiles/csv-row-v1.md— normative nativecsv-row-v1profile (the live CSV literal; §9 carries the deprecation pointer for the inertsatsignal.csv.row.v1).docs/notary_spec/profiles/text-line-v1.md— normative nativetext-line-v1profile (the live TEXT literal).docs/notary_spec/profiles/json-keypath-v1.md— normative nativejson-keypath-v1profile (the live JSON literal).docs/notary_spec/profiles/json-field-v1.md— DEPRECATED / INERTsatsignal.json.field.v1profile, retained as a frozen regression-guard corpus only; live successor isjson-keypath-v1.docs/notary_spec/profiles/text-paragraph-sentence-v1.md— DEPRECATED / INERTsatsignal.text.paragraph_sentence.v1profile, retained as a frozen regression-guard corpus only; live successor istext-line-v1.docs/notary_spec/CONFORMANCE.md—provenance-v1conformance (independent surface).docs/notary_spec/CONFORMANCE_bundle.md—bundle-v1conformance (the layer disclosure conformance sits above).docs/notary_spec/bundle-v1.md—.mbntenvelope spec;§9.2is the unknown-JSON-key tolerance rule §A.1 leans on.- The manifest-as-bundle architectural decision (internal) — decision authority for the whole shape.
Questions about this specification? Email hello@satsignal.cloud.