This document specifies what a verifier does with one b1 record bundle (Bundle): the
four outcomes a check can have, the checks and the order they are reported in, the report, and the
exit codes of a command-line verifier. Two conforming verifiers given the same bundle and the same
inputs produce the same report, apart from the instant it records, and the same exit code.
The record itself is specified in Record, the anchor artifact and its checks in Anchor, and the tree proofs in Tree. Requirement keywords are used as Specs states.
1. Principles
- Nothing the bundle says about itself is an input. A verifier never reads the bundle's
exportermember, the envelope'sattestationStatus, ornonRepudiation.rfc3161Metadata, and never copies a summary or verdict the bundle carries. Every fact in a report is derived again from the artifacts. - Every input may be hostile. Verification MUST complete, and report, on any input. An
unforeseen failure inside the verifier is reported as the single row
bundle,not-checked,bundle_verifier_error(§4.2): a fault to report in the verifier, never a verdict on the bundle. - There is no overall verdict. A report lists what is decided and what is not. The auditor's regime (§3.2) and the exit code (§6.6) say what the rows add up to.
- Nothing present passes in silence. Material a verifier of this version does not verify, a
relation it does not know included, is reported by a row that abstains or is
not-checked.
2. Outcomes
2.1 The four outcomes
Every check, called a row below, has exactly one outcome:
| Outcome | Meaning |
|---|---|
checked-correct | Decided, and it holds |
checked-wrong | Decided, and it fails: a finding against the artifact |
not-checked | This verifier could not decide it; the reason says why |
abstain | The artifact names a construction or version this verifier does not implement |
2.2 Rules
- An abstention is neither a failure nor a pass. An unknown version tag makes the verifier abstain on the part of the record that the tag governs, and only on that part. An unknown bundle version or profile makes it abstain on the whole bundle.
- A failure is reported once, by the row that decided it. A row whose prerequisite is not
decided (the prerequisite is
checked-wrong,not-checkedorabstain) isnot-checked, with the reasonblocked_by:<id of the prerequisite>. - Only a missing artifact is absent. An artifact that is present and cannot be read is a
finding or an abstention, as its row states. It is never
absent, and never pending. - Facts are derived, never copied (§1).
- Reasons. A
checked-correctrow carries no reason; every other row carries exactly one.
2.3 Reasons
The reasons are closed lists. A verifier MUST NOT report a reason that neither this section nor Anchor §10.1 lists. The rows of §4 use:
| Outcome | Reasons |
|---|---|
checked-wrong | malformed_bundle, subject_mismatch, digest_mismatch, payload_unserializable, chain_link_mismatch, chain_link_missing, predecessor_mismatch, malformed_token, imprint_mismatch, verification_failed, malformed_signoff_expectation, session_signoff_not_expected, signoff_unbound, signoff_stripped, statement_digest_mismatch, session_seq_mismatch, mandate_not_expected, mandate_stripped, malformed_mandate_statement, mandate_assertion_mismatch, mandate_unbound, mandate_document_mismatch, malformed_decider, decider_inconsistent, decider_kind_contradicted, freshness_block_mismatch, malformed_contributions, step_chain_broken, decider_not_deciding_actor, deciding_output_mismatch, input_undeclared, review_unbound, decision_unbound, decided_output_differs, malformed_links, link_rule_violated, link_attrs_invalid, link_target_mismatch, issuance_not_expected, issuance_material_stripped, issuance_material_mismatch, malformed_mandate_document, agent_not_in_mandate, outside_mandate_scope, confirmation_evaded, mandate_totals_malformed, mandate_limit_exceeded |
abstain | unsupported_bundle_version, unsupported_bundle_profile, unsupported_envelope_version, unsupported_schema_version, unsupported_signoff, unsupported_extension, unsupported_slot, unsupported_member, unsupported_artifact, legacy_blockchain_anchor, unsupported_decider_kind, unsupported_identifier_scheme, unsupported_freshness_chain, unsupported_actor_kind, unsupported_pipeline, unsupported_link_relation, unsupported_link_rule, unsupported_link_edge, unsupported_mandate_document |
not-checked | absent, not_applicable, not_yet_published, blocked_by:<id>, not_in_bundle:predecessor, not_in_bundle:credential, not_in_bundle:mandate_document, not_in_bundle:link_target, signature_missing, confirmation_missing, material_unverified, scope_undeterminable, link_rule_undetermined, no_matching_trust_anchor, missing_dependency, bundle_verifier_error |
The anchor rows use the lists of Anchor §10.1, and the rows anchor and anchor.newer
also use publication_not_checked (Anchor §10.6). The row decider.freshness also uses
input_not_supplied:evm_header_source and the reasons of the header source's states listed in
Anchor §10.1 (§4.9). bundle_verifier_error and
missing_dependency report a fault in the verifier or a library it lacks, never in the artifact.
2.4 Rungs
Sign-off verification reports the strength of a signature as rungs: facts on a
checked-correct row, never outcomes. This version of the specification defines no row that
reports a rung. A report's summary.rungs is empty, and a rung that the regime requires is never
met (§3.2).
3. Inputs and the regime
3.1 Inputs
| Input | Used by |
|---|---|
| The bundle | Every row |
| One publisher manifest (Anchor §9), or none. A verifier of this version reads no supplementary manifest. | EVM publications (Anchor §10.4) |
| Header sources, each for one CAIP-2 chain or for any chain | anchor.evm.canonical[i] (Anchor §10.4), decider.freshness (§4.9) |
| RFC 3161 trust roots, and any intermediate certificates | ts.record.trust, anchor.tsa.trust[i] |
| A timeout, 30,000 ms unless the auditor sets another | One deadline shared by every header-source call of one verification (Anchor §10.4) |
| The verifier's network registry | anchorNetwork (Anchor §9) |
The auditor supplies these, and the bundle never does. Only roots the auditor supplies count: a verifier MUST NOT fall back on a trust store of its platform or its libraries. A supplied root or intermediate that the verifier cannot read, including one whose key algorithm or curve it does not implement, is an auditor input that cannot be read (§6.6): a verifier MUST NOT drop it and go on. A verifier MAY ship a default publisher manifest, and MUST let the auditor replace it or trust none.
The chain id that Anchor §10.2 takes as an input is the bundle's chain.chainId when it
is a string, and is not supplied otherwise (Bundle §5.3).
3.2 The regime
The auditor's regime has three parts:
require: a list of row ids. An entry names the row with that id; an entry that ends in[*]names every row whose id is the entry's stem followed by[, one or more decimal digits, and](soanchor.evm.canonical[*]namesanchor.evm.canonical[0]). The requirement is met only when every entry names at least one row of the report and every row it names ischecked-correct.requireRung: a list of rungs, met only when each is reported (§2.4).allowUnchecked: when set, rows left undecided do not lead to exit code 4 (§6.6).
Informative. tzun-verify takes these as --require <id>[,<id>…] and --require-rung <rung>,
both repeatable, and the switch --allow-unchecked.
4. The check catalogue
4.1 Report order
Rows are reported in this order. Consumers read a row by its id, but the order is normative, because reports are compared whole (§6.5).
bundle(§4.2). When it is notchecked-correct, it is the only row.subject(§4.2)record.digest,record.chainLink,custody.ledger(§4.3)ts.record.token,ts.record.trust(§4.4)signoff.expectation,signoff.key(§4.5)decider,decider.freshness(§4.9)contributions,contributions.binding, thencontributions.pipelinewhen reported (§4.10)links, then, for each link in list order,links[<i>]andlinks[<i>].edge, when reported (§4.11)mandate.issuance,mandate.scope,mandate.limits,mandate.keyandmandate.assertionTimestamp, each when reported (§4.12)anchor, the six step rows fromanchor.spectoanchor.anchorInclusion, then the rows of each publication in array order (§4.6; Anchor §10.7)anchor.newer, when reported (§4.6)anchor.consistency[<origin>], one per origin that has a row, in byte order of origin (§4.6)anchor.pending, when the slot is filled (§4.6)extension[<key>], in byte order of key (§4.7)relatedRecords, thenpolicy, each when filled (§4.7)payload.<name>, in byte order of name (§4.7)member[<key>], in byte order of key (§4.7)- rows for the members of
nonRepudiationthat no row above reads, in byte order of member name (§4.7)
"Byte order" compares the UTF-8 encodings of two strings byte by byte. A member name that would
name a row never holds a lone surrogate: a bundle with one is malformed_bundle (§4.2).
4.2 The bundle
bundle decides, in this order:
- The bundle is a JSON text (Record §3.1) whose value is an object. Otherwise
checked-wrong,malformed_bundle. bundleVersionis the string"b1". Otherwise, absent ornullincluded,abstain,unsupported_bundle_version.profileis the string"record". Otherwise, absent included,abstain,unsupported_bundle_profile. This version does not verify a bundle of any other profile.- The bundle has the frame of a record bundle, steps 4 to 9 of Bundle §4: all eleven
slots of Bundle §3.1 present, each an object or
null; the envelope, its payload and its integrity block objects; the members ofchainof the right types; theanchoringslot in the shape of Bundle §7.1, every checkpoint body filed under its own origin and at most two of them per origin; and no lone surrogate in a member name that a row id carries. Otherwisechecked-wrong,malformed_bundle. - Otherwise
checked-correct.
An unforeseen failure of the verifier makes bundle not-checked, bundle_verifier_error, as
the only row (§1).
subject (Bundle §5.1) is checked-correct when subject.evidenceId is a string equal to
envelope.payload.evidenceId, and subject.canonicalDigest and
envelope.integrity.canonicalDigest are hashes with the same 32 bytes. Otherwise it is
checked-wrong, subject_mismatch. It compares the stored digest, not a recomputed one, so that
a changed payload is reported once, by record.digest.
4.3 The record
record.digest decides, in this order:
envelope.envelopeVersionis not the string"e1":abstain,unsupported_envelope_version.payload.schemaVersionis not the string"v1.0":abstain,unsupported_schema_version.- The canonical serialization of the hashed object (Record §4.2) is refused
(Record §3.6):
checked-wrong,payload_unserializable. integrity.canonicalDigestis not a hash, or its 32 bytes differ from the digest recomputed by Record §4.2:checked-wrong,digest_mismatch. When the stored digest is a hash whose 32 bytes equal the digest recomputed over the payload under one of the earlier field sets of Record §4.1, the row carries the factfieldSet, the name of that set:"0.7"or"before-0.7". The fact says which earlier draft the record's digest follows. It never changes the outcome: whoever can rewrite a stored record can also rewrite it into an earlier set.- Otherwise
checked-correct.
The recomputed digest is the input of ts.record.token, ts.record.trust, signoff.expectation,
decider, contributions, links, mandate.issuance, anchor and its step rows, and
anchor.newer. Each of them is not-checked, blocked_by:record.digest, unless record.digest is
checked-correct, except where §4.4 and §4.6 report an artifact absent first.
record.chainLink checks Record §5 over the stored digests. It runs whatever
record.digest says. With n the payload's sequenceNumber, D the stored
integrity.canonicalDigest, L the stored integrity.chainLink and p the bundle's
chain.predecessor (Bundle §5.3):
nis not an integer of at least 1, orDis not a hash:checked-wrong,chain_link_mismatch.- When
nis 1:Lpresent giveschecked-wrong,chain_link_mismatch; elseppresent giveschecked-wrong,predecessor_mismatch; elsechecked-correct. - When
nis more than 1:Labsent:checked-wrong,chain_link_missing;pabsent:not-checked,not_in_bundle:predecessor;p.sequenceNumberis not the integern − 1, orp.canonicalDigestis not a hash:checked-wrong,predecessor_mismatch;Lis not a string equal, ignoring case, to the link computed fromp.canonicalDigestandD:checked-wrong,chain_link_mismatch;- otherwise
checked-correct.
custody.ledger reports on the bundle's custody slot (Bundle §6). This version
does not verify custody material, and the row is never checked-correct:
- The slot is
null, or an object whoseentriesmember is an empty array:not-checked,not_applicable. - Otherwise:
abstain,unsupported_artifact.
4.4 The record's timestamp
ts.record.token and ts.record.trust check nonRepudiation.rfc3161Token
(Record §10):
- No token: both
not-checked,absent. record.digestis notchecked-correct: bothblocked_by:record.digest.- The token is not a string, or is empty:
ts.record.tokenreadschecked-wrong,malformed_token. - Otherwise the steps of Anchor §10.5 run over the token, with the 32 bytes of the
recomputed digest in place of
P. Steps 1 to 3 decidets.record.token, with the reasonmalformed_tokenwhere Anchor §10.5 step 1 givesmalformed_publication. Step 4 decidests.record.trust:checked-correctwith the factsgenTimeandauthority,checked-wrong,verification_failed, ornot-checked,no_matching_trust_anchor. Step 5 givesnot-checked,missing_dependency, on the check whose step could not run.
Whenever ts.record.token is not checked-correct, ts.record.trust is
blocked_by:ts.record.token, unless step 1 or 2 above applies. A verifier MAY bound its search
for a certificate path; a search stopped by its bounds has not shown a path to a supplied root,
and reads no_matching_trust_anchor.
4.5 Sign-off
signoff.expectation compares what the record carries under nonRepudiation with its hashed
signoffExpectation (§5).
signoff.key says whether a key is available to check the record's WebAuthn signature. This
version verifies no WebAuthn signature. With "assertion" as Record §9.3 defines it, the row
decides, in this order:
signoff.expectationischecked-wrong:not-checked,blocked_by:signoff.expectation.signoff.expectationisnot-checked,signature_missing, and the record has no assertion:not-checked,signature_missing.- The bundle's
credentialsslot is notnull:abstain,unsupported_signoff. - The record has no assertion:
not-checked,absent. - Otherwise:
not-checked,not_in_bundle:credential.
4.6 The anchor
anchor and its step and publication rows are the checks of Anchor §10.3 to §10.7,
over:
- the artifact
nonRepudiation.anchor; - the digest recomputed by
record.digest; - the payload's
sequenceNumber, and the chain id of §3.1; - the auditor's inputs (§3.1);
- each
evm-calldata/1entry of the artifact, joined with the material the bundle carries for the same transaction (Bundle §7.4; Anchor §8.2).
When the record has neither anchor nor blockchainAnchor, anchor and the six step rows are
not-checked, absent, whatever record.digest says, and there are no publication rows
(Anchor §10.9). Otherwise, when record.digest is not checked-correct, anchor and the
six step rows are blocked_by:record.digest, with no publication rows. A blockchainAnchor
without an anchor is step 1's legacy abstention (Anchor §10.3); one beside an anchor
has a row of its own (§4.7).
anchor.newer checks the anchor that ties the subject to the latest anchor checkpoint in the
bundle. It is reported when an evidence-log checkpoint entry of the anchoring slot has any of
the members logPath, anchorLeafIndex, anchorSize and anchorPath; Bundle §7.5
states which entry is the target, how the verifier composes an anchor artifact from it, and how
that artifact is verified. Because the composed artifact carries the subject's own path, every step
of Anchor §10.3 applies to it, the inclusion of the subject included. The row takes the
outcome and reason that the composed artifact's anchor row would have; a member the bundle cannot
supply leaves the artifact short of step 2's shape (malformed_anchor). When it is
checked-correct its facts are treeSize and anchorSize, the sizes of the two bodies it
authenticated. It is blocked_by:record.digest when the digest does not hold. With no target
there is no row.
anchor.consistency[<origin>] is Anchor §10.8, read from the bundle as
Bundle §7.6 states: one row for each origin whose checkpoint bodies the bundle carries
or an authenticated anchor names. The bodies authenticated are
the log and anchor checkpoints of the record's anchor when anchor is checked-correct, and those
of the composed anchor when anchor.newer is checked-correct. For each origin:
- a carried body that neither authenticated makes the row
blocked_by:anchorwhileanchoris notchecked-correct, andblocked_by:anchor.neweronce it is, whether or not the report has ananchor.newerrow: with no target (Bundle §7.5), nothing can authenticate that body; - otherwise, with two authenticated bodies, the row compares them by Anchor §10.8, using the
first entry of the origin's
consistencylist whosefromSizeandtoSizeare the smaller and the larger of the two sizes (Bundle §7.6), andnot-checked,not_in_bundle:consistency_proof, when there is no such entry; - otherwise the origin has no row.
anchor.pending is reported when the bundle's anchorPending slot is not null
(Bundle §7.7):
not-checked, not_yet_published. It is never an abstention and never absent.
4.7 Material this version does not read
Each of these rows reports material that is present and that this version does not verify, so that it is never passed over in silence:
extension[<key>]: each member ofextensionswhose value is notnull:abstain,unsupported_extension(Bundle §9). This version implements no extension.relatedRecords,policy: the reserved slot of that name, when it is notnull:abstain,unsupported_slot(Bundle §8).payload.<name>: each member of the payload, whatever its value, whose name is not one of the twelve of Record §2.2:abstain,unsupported_member. Such a member is not hashed (Record §4.1), so nothing vouches for its content, and a consumer that showed it beside the verdict on the record would show unverified content as if it were verified. A payload that still carriesaiBom,humanActionorhumanReason, which an earlier draft hashed, is reported this way.member[<key>]: each top-level member of the bundle other thanbundleVersion,profileand the eleven slots of Bundle §3.1:abstain,unsupported_member(Bundle §3.3).- For each member of
nonRepudiationwhose value is notnull, other thananchor,rfc3161Token,rfc3161Metadata,webauthnAssertion,signoff,mandateandmandateIssuance, which the rows of §4.4 to §4.6, §4.12 and §5 read, and other thanconfirmationwhen the payload'ssignoffExpectationis an object with amandateAssertionmember and aconfirmmember that istrue, which §5 reads:assertionTimestamp: the rowts.assertion.token,abstain,unsupported_artifact;blockchainAnchor, whenanchoris also present: the rownonRepudiation.blockchainAnchor,abstain,legacy_blockchain_anchor;- any other member: the row
nonRepudiation.<name>,abstain,unsupported_artifact. This includes the reserved membersagentSignature,contributionSignaturesandruntimeQuote(Record §2.4), and aconfirmationthat the record does not owe.
The credentials slot has no row of its own: when filled it makes signoff.key abstain (§4.5).
The signatures of a checkpoint entry have no row in this version, whatever they hold: a verifier
does not read them (Bundle §7.2). exporter is never read.
4.8 What this version does not verify
This version verifies no WebAuthn signature, credential, sign-off statement beyond what §5 reads,
sign-off policy, related record, assertion timestamp, custody material or extension, and no bundle of
a profile other than record. Nor does it verify:
- who a decider is beyond what the record states: the level it reports is asserted (§4.9), and a human signature bound to the record is reported as bound, not as proven;
- material that would contradict a decider's kind, when that material carries a signature this
version cannot check (
material_unverified, §4.9); - agent descriptors (Record §6.9), and the credential registration a decider names;
- pipeline definitions, signatures over individual steps, and whether the contributors were independent of one another;
- the target rules of links, since a
b1bundle carries no target, the rules of relations it does not know, and link edges: that a target was on record before the record that names it, or sat at the position its locator names (§4.11); - who signed a mandate, whether the mandate was in force when a record was captured (its validity window, its revocation or its replacement), and the consumption of a mandate across records (Record §9.10);
- a payload member outside the twelve of Record §2.2, which is not hashed (§4.7).
Whatever of these a bundle carries is reported by a row that abstains or is not-checked, which
keeps the exit code at 4 or above unless the auditor allows unchecked rows (§6.6).
4.9 The decider
These rows read payload.decider (Record §6).
decider decides, in this order:
record.digestis notchecked-correct:not-checked,blocked_by:record.digest.signoff.expectationischecked-wrong,malformed_signoff_expectation:not-checked,blocked_by:signoff.expectation, since the agreement of step 7 needs the expectation's form.- The decider is
null:not-checked,absent. The decider is not recorded, and the verifier infers none (Record §6.7). - The decider is not well formed under Record §6.2:
checked-wrong,malformed_decider. The value rules of the schemes of Record §6.3 bind writers, and are not applied here. kindis not one of the kinds of Record §6.1:abstain,unsupported_decider_kind. An unknown kind is not malformed, so that the vocabulary can grow.- The kind, the agent reference and the scheme do not agree, or a
descvalue is notagent(Record §6.4):checked-wrong,decider_inconsistent. - The expectation's form contradicts the kind (Record §6.5):
checked-wrong,decider_kind_contradicted. - The scheme of
idis not one of Record §6.3:abstain,unsupported_identifier_scheme. - The kind is
agent,agent-mandatedorpolicy-engine, and the record carries human decision material that binds it (Record §6.5):not-checked,material_unverified, with the factkindContradiction"undetermined". A signature this version cannot check is never taken as settling the contradiction either way. - Otherwise
checked-correct, with these facts:kind, the decider's kind, andscheme, the scheme of itsid;level:"human-asserted"for the kindhuman,"agent-asserted"for the others. The decider is what the writer stated: this version verifies no signature that would prove it;levelUndetermined:"human-proven", only for the kindhumanwhen the record carries human decision material that binds it. A human signature is then bound to the record, and who made it is not verified by this version;committed:true, only for the schemecmt. The row then checks the member's structure and agreement; who the decider is stays committed and unopened.
decider.freshness checks the finalized-block reference (Record §6.6) against the auditor's
header source (Anchor §10.4). It decides, in this order:
- The decider is
null, or is an object whosefreshnessisnull:not-checked,absent. decideris notchecked-correct:not-checked,blocked_by:decider.- The reference names a chain outside the CAIP-2 namespace
eip155:abstain,unsupported_freshness_chain. - The auditor supplied no header source for that chain (§3.1):
not-checked,input_not_supplied:evm_header_source. - The header source is asked as Anchor §10.4 items 8 to 11 ask it, under the deadline shared by
every header-source call of the verification: a call still waiting at the deadline, a call that
fails, a source whose chain id is not the reference's, a block number above the source's final
height, and a source with no canonical block at that height give
not-checkedwithevm_header_source_timeout,evm_header_source_unavailable,evm_header_source_chain_mismatch,evm_block_not_finalizedandevm_header_source_inconsistent. - The source's canonical block at that height has a hash other than the reference's:
checked-wrong,freshness_block_mismatch. - Otherwise
checked-correct, with the factsblockTime, the canonical block's time, andcaptureLag, the payload'scapturedAtlessblockTime, in milliseconds (a JSON number, negative whencapturedAtis the earlier).capturedAtis read only when it is a string matching/[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(\.[0-9]+)?Z/whose fields name a real instant, as Record §9.7 states forvalidity; its fraction is truncated to whole milliseconds. OtherwisecaptureLagis left out.captureLagis informative.
From the point where the source's chain id has matched, the row carries the fact finality, the
mode the source declares, whatever its outcome, as Anchor §10.4 item 14 states for an EVM
publication. blockTime and finality are written as Anchor §10.7 writes them.
4.10 Contributions
These rows read payload.contributions (Record §7).
contributions checks the structure of the contributions. Its steps are ordered so that a
failure that does not depend on an actor's kind or scheme is never hidden behind an abstention. It
decides, in this order:
record.digestis notchecked-correct:not-checked,blocked_by:record.digest.contributionsisnull:not-checked,absent.- The contributions are not well formed under Record §7.1, apart from the vocabulary of an actor's
kind and scheme; or they break the rules of Record §7.3 on sets, verdicts and the deciding step;
or an actor whose kind is one of Record §6.1 is not consistent under Record §6.4:
checked-wrong,malformed_contributions. A step'saiBomis read only asnullor an object (Record §7.4). - The step chain is broken:
checked-wrong,step_chain_broken, with the factstep, the index of the first step whoseprevis wrong. - There is a deciding step, and the decider and the deciding step's actor differ:
checked-wrong,decider_not_deciding_actor. - The output of a step of the class
decideis not the output digest of the record'sactionandreason:checked-wrong,deciding_output_mismatch. - An actor's kind is not one of Record §6.1:
abstain,unsupported_actor_kind. Otherwise, an actor's scheme is not one of Record §6.3:abstain,unsupported_identifier_scheme. - Otherwise
checked-correct, with the factssteps, the number of steps;models, the distinctmodelDescriptorDigestvalues of the steps' bills of materials that are strings, in byte order, to which a step whoseaiBomisnull, or whosemodelDescriptorDigestis absent or not a string, adds nothing; anddecidingStep, the index of the deciding step, or"record"when there is none and the record's decider took the decision after the last step.
contributions.binding checks the process the contributions state (Record §7.5). A writer may
create a record that fails it, so its findings are about the decision's process, never about the
record's form. It decides, in this order:
contributionsisnot-checked,absent:not-checked,absent.contributionsis notchecked-correct:not-checked,blocked_by:contributions.- An input is undeclared:
checked-wrong,input_undeclared, with the factsstepanddigest, the first such input in order of step and then of input. Links declare inputs only whenpayload.linksmeets the grammar of Record §8.2, whatever thelinksrow says of its rules. - A review does not have the output it covers among its inputs:
checked-wrong,review_unbound, with the factstep, the first such review. - There is a deciding step, and its inputs do not include the output of
P*:checked-wrong,decision_unbound. outputDigest(aiOutput)is not the output ofP*:checked-wrong,decided_output_differs.- Otherwise
checked-correct.
Whenever it is decided, the row carries these facts, each an array of step indexes in increasing
order: proposals, the steps of the class propose; approvals, the current reviews whose
verdict is "approve"; staleApprovals, the stale reviews whose verdict is "approve"; and
uncoveredReviews, the reviews that cover no step. It also carries decisionBinding: "checked",
or "not-applicable" when there is no P*, in which case steps 5 and 6 do not apply. A stale
approval is never counted as an approval of the decision.
contributions.pipeline is reported when contributions is checked-correct and its
pipeline is not null: abstain, unsupported_pipeline. This version defines no pipeline
definition.
4.11 Links
These rows read payload.links (Record §8). A b1 bundle carries no target record (Bundle §8),
so a verifier of a bundle holds none, and the steps below that need a target apply only to a
verifier that holds one by other means, such as a store's own (Record §8.8).
links checks the member as a whole. The list-level rule mandate_link is not applied when
the expectation is malformed (Record §8.3), that is, when signoff.expectation is checked-wrong,
malformed_signoff_expectation. The row decides, in this order:
record.digestis notchecked-correct:not-checked,blocked_by:record.digest.linksisnullor absent, and no list-level rule of Record §8.3 that applies requires a link:not-checked,absent.linksbreaks the grammar of Record §8.2:checked-wrong,malformed_links. When the member is an array of 1 to 256 elements, the row carries the factindex: the index of the first link that breaks the grammar of a single link or, when each link meets it, the index of the first link that is not after the link before it in the order of Record §8.2.- A list-level rule of Record §8.3 fails, a
nullmember whose record requires a link included:checked-wrong,link_rule_violated, with the factrule, the name of the first rule that fails in the order of that table. The conditions on a link record's form (Record §8.6) are part ofsubject_link, so a link record that breaks one fails on this row, and each of itslinks[<i>]rows is thennot-checked,blocked_by:links. - Otherwise
checked-correct, with the factscount, the number of links;relations, the distinct values ofrel, in byte order; andextensions, the number of links whoserelis an extension name.
links[<i>] and links[<i>].edge are reported for each element of links when it is an
array of 1 to 256 elements, i being the element's index counting from 0: links[0],
links[0].edge, links[1] and so on.
links[<i>] checks one link against its relation. In a link record (Record §8.6), a rule of
a link other than the subject link that reads this record's members reads the subject's instead,
and so needs the subject. It decides, in this order:
linksis notchecked-correct:not-checked,blocked_by:links.- The link's
relismandateandsignoff.expectationischecked-wrong,malformed_signoff_expectation:not-checked,blocked_by:signoff.expectation. relis not a relation of the registry of Record §8.3, being an extension name or a name this version does not register:abstain,unsupported_link_relation, with the factrel.attrsbreaks the relation's attribute profile (Record §8.3):checked-wrong,link_attrs_invalid.- A record-local rule of the relation fails:
checked-wrong,link_rule_violated, with the factrule, its name. - The verifier holds the target, and a member of
targetthat is notnulldisagrees with it (Record §8.8):checked-wrong,link_target_mismatch, with the factmember,"evidenceId"or"locator", the first that disagrees in that order. target.locatoris notnull, the verifier holds the log it names, and the position it names holds another record or none (Record §8.8), whether or not the verifier holds the target:checked-wrong,link_target_mismatch, with the factmember"locator".- The verifier holds the records a target rule of the relation reads, and the rule fails:
checked-wrong,link_rule_violated, with the factrule, its name. - A rule of the relation is one this version does not define (the target rules of
delegatedBy,closesandfanoutOf), or the record-local rule ofrootis not decided (Record §8.3):abstain,unsupported_link_rule. - The verifier holds the records a target rule of the relation reads, and the rule is
undetermined (Record §8.3):
not-checked,link_rule_undetermined. - The relation has a target rule, or, in a link record, a rule that reads the subject, and the
verifier does not hold a record it needs:
not-checked,not_in_bundle:link_target. - Otherwise
checked-correct, with the facttargetHeld,truewhen the verifier holds the target andfalseotherwise, and, for arootlink, the factrootInconsistenttruewhen Record §8.5 calls for it.
links[<i>].edge is not-checked, blocked_by:links, when links is not checked-correct.
Otherwise it is abstain, unsupported_link_edge: this version verifies no edge (Record §8.8),
whatever the relation.
4.12 Mandates
These rows read the mandate form of the expectation, the mandate block and the issuance block (Record §9.6).
mandate.issuance is reported when the record carries an issuance block, or when its
aiSystemId is "tzun:mandate" and its action is "issue-mandate", which makes it an
issuance record. It decides, in this order:
record.digestis notchecked-correct:not-checked,blocked_by:record.digest.- The record carries an issuance block and is not an issuance record:
checked-wrong,issuance_not_expected. - An issuance record carries no issuance block:
checked-wrong,issuance_material_stripped. - A link of Record §9.6 does not hold:
checked-wrong,issuance_material_mismatch, with the factlink, the first that fails of"document"(the document's digest is notaiOutput.mandateDigestand the statement'smandateDigest, or itsprincipal.signeris not the statement'ssigner),"statement"(the statement's digest is notaiOutput.statementDigest),"challenge"(the assertion does not present the statement challenge),"assertion"(the assertion digest is notaiOutput.assertionDigest) and"decider"(the decider is not an object whosekindis"human"and whoseidisacct:followed by the statement'ssigner). Material that is missing, or of the wrong type, fails the first link that needs it. - Otherwise
checked-correct. Who made the assertion is not verified by this version.
mandate.scope is reported for the mandate form only. It holds the record to the scope of the
document in its mandate block (Record §9.9), and decides, in this order:
signoff.expectationis neitherchecked-correctnornot-checked,confirmation_missing:not-checked,blocked_by:signoff.expectation.- The mandate block carries no document, having no
documentor adocumentthat isnull(Record §9.6):not-checked,not_in_bundle:mandate_document. - The document's
vis not"tzun-mandate/1":abstain,unsupported_mandate_document. - The document is not well formed under Record §9.7 and §9.8, its size included:
checked-wrong,malformed_mandate_document. - The decider is not one of its
agents:checked-wrong,agent_not_in_mandate. - Another constraint fails:
checked-wrong,outside_mandate_scope, with the factfailed, the names of the constraints that fail, in this order:schemaVersion,aiSystemIds,actions,models, thenpredicate:<name>for each predicate ofscope.predicatesin byte order of name. confirmisfalsewhile a predicate of the document'sconfirmis true:checked-wrong,confirmation_evaded, with the factconfirm, the names of those predicates in byte order.- A constraint is undeterminable, or
confirmisfalsewhile no predicate of the document'sconfirmis true and one is undeterminable:not-checked,scope_undeterminable, with the factundeterminable, the names of each such constraint (models,predicate:<name>,confirm:<name>) in that order and then in byte order of name. - Otherwise
checked-correct.
mandate.limits is reported for the mandate form only. It holds the record to the document's
limits (Record §9.9), and decides, in this order:
signoff.expectationis neitherchecked-correctnornot-checked,confirmation_missing:not-checked,blocked_by:signoff.expectation.- The mandate block carries no document, as in
mandate.scopestep 2:not-checked,not_in_bundle:mandate_document. mandate.scopereports the documentunsupported_mandate_documentormalformed_mandate_document:not-checked,blocked_by:mandate.scope.totalsdoes not have exactly one member for each cap, or the value at a cap's path is not an integer:checked-wrong,mandate_totals_malformed.mandateSeqis abovelimits.maxRecords, or a running total is below the record's own value at its cap's path or above the cap'smax:checked-wrong,mandate_limit_exceeded.- Otherwise
checked-correct, with the factsslot, the expectation'smandateSeq, andtotals, itstotalsas it stands.
mandate.key is reported for the mandate form, and for a record that carries an issuance
block. It says whether a key is available to check the principal's signature, as signoff.key does
for a record's own (§4.5):
- For the mandate form,
signoff.expectationischecked-wrong:not-checked,blocked_by:signoff.expectation. For a record that is not of the mandate form,mandate.issuanceischecked-wrong:not-checked,blocked_by:mandate.issuance. - The bundle's
credentialsslot is notnull:abstain,unsupported_signoff. - Otherwise:
not-checked,not_in_bundle:credential.
mandate.assertionTimestamp is reported when the mandate block, for the mandate form, or the
issuance block, for an issuance record, is an object whose assertionTimestamp is not null:
abstain, unsupported_artifact. This version does not verify a timestamp over an assertion.
5. The signature expectation check
signoff.expectation compares the sign-off material under a record's nonRepudiation with
the record's payload.signoffExpectation, in the terms of Record §9. It reads no key and no
signature, and so it answers the same whatever a signature check would say.
The expectation is authenticated by the record digest and by nothing else. The row is therefore
not-checked, blocked_by:record.digest, unless record.digest is checked-correct. Under a
schema whose hashed field set does not include signoffExpectation it is not-checked,
not_applicable; no such schema is defined by this version.
Otherwise the rows of this table are tried in order, and the first that matches decides:
| # | signoffExpectation | The record carries | Outcome, reason |
|---|---|---|---|
| 1 | Malformed (Record §9.2) | anything | checked-wrong, malformed_signoff_expectation |
| 1a | null, the policy form or the session form | a mandate block | checked-wrong, mandate_not_expected |
| 2 | null | a session sign-off | checked-wrong, session_signoff_not_expected |
| 3 | null | a sign-off block and an assertion that do not bind as a batch sign-off | checked-wrong, signoff_unbound |
| 4 | null | anything else | checked-correct |
| 5 | the policy form | a session sign-off | checked-wrong, session_signoff_not_expected |
| 6 | the policy form | no assertion, with or without a sign-off block | not-checked, signature_missing |
| 7 | the policy form | a sign-off block and an assertion that bind as a batch sign-off | checked-correct |
| 8 | the policy form | a sign-off block and an assertion that do not | checked-wrong, signoff_unbound |
| 9 | the policy form | no sign-off block, and an assertion that binds as a per-record assertion | checked-correct |
| 10 | the policy form | no sign-off block, and an assertion that does not | not-checked, signature_missing |
| 11 | the session form | no sign-off block | checked-wrong, signoff_stripped |
| 12 | the session form | a sign-off block whose statement has no digest (it is missing, not an object, or outside the value domain) or a digest other than statementDigest | checked-wrong, statement_digest_mismatch |
| 13 | the session form | a sign-off block whose sessionSeq is not an integer equal to the expectation's | checked-wrong, session_seq_mismatch |
| 14 | the session form | that sign-off block, and no assertion | checked-wrong, signoff_stripped |
| 15 | the session form | that sign-off block, and an assertion that does not present the statement challenge for statementDigest | checked-wrong, signoff_unbound |
| 16 | the session form | that sign-off block, and an assertion that presents it | checked-correct |
| 17 | the mandate form | a session sign-off | checked-wrong, session_signoff_not_expected |
| 17a | the mandate form | a sign-off block and an assertion that do not bind as a batch sign-off | checked-wrong, signoff_unbound |
| 18 | the mandate form | no mandate block | checked-wrong, mandate_stripped |
| 19 | the mandate form | a mandate block whose statement is not a mandate statement of Record §9.6 in the value domain | checked-wrong, malformed_mandate_statement |
| 20 | the mandate form | a mandate block whose assertion has no assertion digest, or one other than mandateAssertion | checked-wrong, mandate_assertion_mismatch |
| 21 | the mandate form | a mandate block whose assertion does not present the statement challenge of its statement | checked-wrong, mandate_unbound |
| 22 | the mandate form | a mandate block with a document other than null whose digest (Record §9.7) is not the statement's mandateDigest, that has no digest, or whose principal.signer is not the statement's signer (a document whose principal is not an object with a signer member included) | checked-wrong, mandate_document_mismatch |
| 23 | the mandate form, confirm true | no confirmation that presents the record's confirmation challenge (Record §9.4) | not-checked, confirmation_missing |
| 24 | the mandate form | anything else | checked-correct |
Row 1a reads only the mandate block. An issuance record carries its material as an issuance block,
which the row mandate.issuance reads (§4.12).
What the outcomes say:
checked-correctsays that the record carries the sign-off its expectation names, as far as that can be read without a key. It does not say that a signature verifies, and it does not compare a batch statement's policy with the record'spolicyDigest.signature_missingis never final. A record under the policy form may receive its signature after capture, and a bundle alone cannot tell a signature still to come from one removed together with its assertion, or replaced by an assertion that binds nothing.- A session record without its sign-off is a finding, since a writer stores the two together (Record §9.1).
- A sign-off that does not bind is a finding in every form: it has been moved from another record, relabelled or made up.
- Under the mandate form,
checked-correctsays that the record carries the mandate block its expectation names: the principal's assertion has the digest the record committed to, it presents the challenge of the statement carried, and the document, when the block carries one, is the one that statement names, with the statement's signer as its principal. It does not say that the principal's signature verifies (mandate.key), nor that the record lies within the mandate (mandate.scope,mandate.limits, §4.12). confirmation_missingis never final, assignature_missingis not: a confirmation is added after capture.
So a session sign-off copied onto another record fails on that record alone: session_seq_mismatch
in another slot of the same session, statement_digest_mismatch in another session, and
session_signoff_not_expected on a record that names no session. A mandate block copied onto
another record fails the same way: mandate_assertion_mismatch on a record decided under another
mandate, and mandate_not_expected on a record that names none. A record decided under the same
mandate accepts the copy, and the copy then claims the slot that record committed to; that is seen
only across the records of the mandate (Record §9.10).
The row carries no facts.
6. Report and exit codes
6.1 The report
A report is a JSON object with these members:
{
"reportVersion": "tzun-verify-report/1",
"bundleVersion": …, // the bundle's bundleVersion value as it stands, or null
"subject": …, // the bundle's subject value as it stands, or null
"inputs": { … }, // §6.2
"checks": [ … ], // §6.3
"summary": { … }, // §6.4
"checkedAt": "…" // the instant the verification started; left out of comparisons
}
bundleVersion and subject are copied from a bundle that is a JSON object and has the member;
otherwise they are null.
6.2 inputs
inputs names what the auditor supplied, by fingerprint only, so that a reader can tell which
inputs produced the report:
| Member | Value |
|---|---|
trustAnchors | The hex SHA-256 of the DER encoding of each supplied RFC 3161 root, sorted; [] when none. Intermediates are not listed. |
evmHeaderSources | One { "chain", "finality" } per header source: chain is the CAIP-2 chain it serves, or null for a source that serves any chain; finality is the mode it declares (Anchor §10.4), written as the fact finality is (Anchor §10.7), null when it declares none. The source for any chain comes first, then the others in byte order of chain. A source's address never appears, since it can hold a credential. |
publisherManifest | The hex SHA-256 of the publisher manifest's bytes as read, or null when no manifest is trusted. A verifier that is handed a parsed manifest rather than its bytes names it by the SHA-256 of its canonical serialization (Record §3); the two agree for a manifest file written in canonical form. |
frameOrigins, vkeys, mdsRoots | null. Reserved for inputs this version does not take. |
6.3 checks
checks is the list of rows in the order of §4.1. Each row is an object:
| Member | Value |
|---|---|
id | The row's id (§4) |
outcome | One of the four outcomes (§2.1) |
reason | The reason (§2.3). Left out on a checked-correct row. |
facts | An object of the row's facts, left out when it has none. The facts of each row are named in §4 and in Anchor §10.7. |
6.4 summary
The summary is derived from the rows and from the verifications behind them, never read from the bundle:
| Member | Value |
|---|---|
mode, coverage, manifestEnforced, keyKind, keyAssurance, uv, actorBound | null. Reserved for sign-off verification, which this version does not define. |
rungs | [] (§2.4) |
existedBy | The earliest instant the report proves, as { "at", "source", "check" }, or null when it proves none. The bounds considered are: the record's anchor when anchor is checked-correct, with its existedBy and source ("evm-calldata" or "rfc3161", Anchor §10.6) and check "anchor"; the composed anchor when anchor.newer is checked-correct, likewise with check "anchor.newer"; and the record's own timestamp when ts.record.token and ts.record.trust are both checked-correct, with its genTime, source "record-timestamp" and check "ts.record.trust". Instants are compared to the millisecond, the digits beyond it dropped, as §6.5 writes them, so two that differ only below the millisecond are equal; of equal instants, the first in that order is named. |
anchorNetwork | Taken from anchorNetwork (Anchor §10.6) of the record's anchor and of the composed anchor, over those the verifier verified: their common value when both give the same one, the one value when only one gives a value, and null when they give different values or none gives one |
anchorNamespace | The same rule over anchorNamespace |
anchorOriginAuthenticated | true when anchorOriginAuthenticated is true for the record's anchor or for the composed anchor, false otherwise |
outcomes | An object keyed by outcome, with a member for each of the four (§2.1) even when its count is 0: how many rows have that outcome |
The record's anchor counts as verified when the record has an anchor artifact and record.digest
is checked-correct; the composed anchor, when anchor.newer is reported and not blocked.
6.5 Serialization and comparison
A report is written in canonical form (Record §3). Instants in a report (checkedAt, the
genTime facts, existedBy.at) are UTC, written YYYY-MM-DDTHH:MM:SS.sssZ with exactly three
fractional digits, finer precision truncated. Two reports are equal when their canonical forms,
without checkedAt, are the same bytes. A verifier that cannot write its report in canonical form,
for example because the bundle's subject holds a lone surrogate, exits 2.
6.6 Exit codes
| Code | Meaning |
|---|---|
| 0 | No row is checked-wrong, every row whose artifact is present is checked-correct, and the regime is met |
| 1 | Some row is checked-wrong |
| 2 | The bundle could not be read, the row bundle is not checked-correct, or the report could not be written |
| 3 | The regime's require or requireRung is not met (§3.2) |
| 4 | Without allowUnchecked: some row abstains, or is not-checked for any reason other than absent, not_applicable, or blocked_by a row that is checked-wrong |
The first code of the order 2, 1, 3, 4, 0 that applies is the exit code. The exemption of a row blocked by a failed row cannot change it, since any failed row gives 1 first.
Code 4 keeps an undecided artifact from reading as success: a bundle whose artifacts all abstained, for example because a forged future version tag stands in place of a failing one, would otherwise exit 0.
Exit 0 says nothing about artifacts that are absent. A record with no anchor artifact, no
anchoring and no anchorPending reads absent on every anchor row, and absent does not lead to
exit code 4. An auditor whose regime needs an anchor MUST require it: anchor for a record that
carries its own artifact, or anchor.newer for one exported before its own artifact is written.
One who needs the record's own timestamp requires ts.record.trust.
Informative. A record therefore exits 4, unless the auditor allows unchecked rows, whenever it
carries something this version does not verify: a decider kind or identifier scheme it does not
know, a finalized-block reference without a header source for its chain, human decision material
bound to a decider of an agent kind (material_unverified), a pipeline, any link (each
links[<i>].edge abstains, and so does a link of a relation this version does not register), a
mandate document of another version, a confirmation still owed, an assertion timestamp, a payload
member outside the twelve of Record §2.2, every record decided under a mandate and every record
that carries an issuance block, since mandate.key is never decided. The rows of §4.9 to §4.11
read absent where the record leaves their member null, which does not lead to exit code 4.
An error in the verifier's invocation, such as an unknown option or an auditor input that cannot be
read, is not a verification result, and a command-line verifier MUST NOT report it with a code
from 0 to 4. Informative: tzun-verify uses 64, and refuses a value given to a switch
(--allow-unchecked=false), so that a wrapper cannot turn a switch on by accident.
6.7 Human-readable output
A verifier's human-readable output MUST print every not-checked and every abstain row with its
reason, and every rung. A script reads the exit code; a person reads what is not checked.
7. Conformance
Specs §6 states what conformance requires, and Bundle §13 how a corpus case is
replayed: for every case of the b1 corpus, the report compared as in §6.5, and the exit code, are
those the case states. Where the corpus and this specification disagree, this specification
governs (Specs §7).