HERALD Verifier Return-Code Reference Card
Status: Reference (informative). Normative source is SPEC §6 (../../../SPEC.md) (lines 316–337) extended by EXT §6.5–6.6 (../../../EXT.md) (lines 450–462). Where this card and the spec disagree, the spec wins.
Audience: authors of reference verifiers (T-003, T-004), conformance test runners (T-017), audit-log walkers (T-009, T-010), and UI/renderer authors who must surface verifier output to end users (T-021, T-023, T-025).
Scope. This card enumerates every distinct return code a conformant HERALD verifier may emit when walking a single envelope or a trace, gives the canonical trigger conditions for each, names the spec rules that produce it, and tells the caller what to do with it. It does not redefine any code — it is a navigation aid layered over [SPEC §6].
0. The contract in one paragraph
A reference verifier walks a trace from leaf to genesis and emits a set of return codes per envelope. valid is empty-set-equivalent in the sense that no failure code applies. Every other code is a reason a caller might wish to refuse, downgrade, or annotate the envelope. The verifier MUST surface every applicable reason — the first failure does not short-circuit subsequent checks ([SPEC §6] line 337). Application policy decides how to act on the set.
1. Quick-reference table
| Code | Category | Disposition | Combines with | Source |
|---|---|---|---|---|
valid | terminal-success | — | mutually exclusive with every code below | [SPEC §6] |
partial | terminal-success-with-caveat | — | augmented by signature-invalid for named failing co-signers ([EXT §6.5]) | [SPEC §6], [EXT §6.5] |
signature-invalid | crypto | reject | tampered, unknown-alg, expired-key, deprecated-sigil | [SPEC §6], [SPEC §3.4] |
tampered | integrity | reject | any | [SPEC §6], [SPEC §3.5] |
orphaned | chain | defer | parent-trace-mismatch (if the orphan's claimed trace is also wrong on its face) | [SPEC §6] |
parent-trace-mismatch | chain | reject | tampered, signature-invalid | [SPEC §6], [SPEC §3.3a] |
replayed-nonce | replay | reject | signature-invalid (if the duplicate is also unsigned-correctly) | [SPEC §6], [SPEC §3.7] |
expired-envelope | time | reject | clock-skew, replayed-nonce | [SPEC §6], [SPEC §3.7] |
expired-key | identity | reject | signature-invalid (always — an envelope signed by a deprecated key fails both) | [SPEC §6], [SPEC §2.4] |
deprecated-sigil | identity | reject | signature-invalid, expired-key | [SPEC §6], [SPEC §2.4] |
unknown-sigil | identity | defer-or-reject | orphaned (frequently co-occurs) | [SPEC §6], [SPEC §8.1] |
unknown-alg | suite | reject | signature-invalid | [SPEC §6], [SPEC §3.4], [EXT §6.6] |
clock-skew | time | reject | expired-envelope | [SPEC §6] |
lineage-unverified | advisory | inform — render distinctly per [SPEC §2.3], [THREATS §3.3] | any | [SPEC §6], [SPEC §2.3] |
Categories defined. crypto, integrity, chain, replay, time, identity, suite are failure categories. advisory is informational and never alone causes rejection. terminal-success and terminal-success-with-caveat are the only positive outcomes; every other code is a failure reason.
Disposition vocabulary.
| Disposition | Meaning |
|---|---|
reject | Application MUST NOT treat the envelope as authoritative. |
defer | Verifier lacks information to decide; caller may retry after fetching the missing context (parent envelope, sigil discovery record). |
inform | Surface to the caller / UI; do not refuse on this alone. |
defer-or-reject | Behaviour is policy-governed; see per-code entry below. |
2. Per-code entries
Each entry below names: the trigger (what the verifier observed), the rule (the spec clause invoked), co-occurring codes the caller should expect to see in the same set, and caller guidance.
2.1 valid
Signatures verify, chain intact, all required scopes present, no replay. — [SPEC §6] line 322
- Trigger. Every other code in this card is absent.
- Rule. The conjunction of [SPEC §3.4] (signatures) ∧ [SPEC §3.3a] (trace-parent) ∧ [SPEC §3.7] (nonce) ∧ [SPEC §2.4] (sigil liveness) ∧ application-policy required co-signatures, all holding.
- Co-occurs with. Nothing.
validis mutually exclusive with every failure code. Note:validmay co-occur in the trace-level summary withlineage-unverifiedon individual envelopes — but per envelope,validandlineage-unverifiedare not both emitted; if the lineage is unverified the per-envelope outcome isvalidwith the advisory code surfaced separately. (This is a known terminological wrinkle. See §4 below.) - Caller guidance. Treat as authoritative within the limits of the protocol's threat model ([SPEC §7]).
2.2 partial
Origin signature valid, but at least one optional/required-by-policy signature is invalid or missing. Reasons enumerated. — [SPEC §6] line 323
- Trigger. The origin signature (index 0, scope
origin, MUST be fromfrom) verifies, and at least one of: - A non-origin signature in
signatures[]is cryptographically invalid; or - An application policy required a co-signature of a particular
scope(e.g.human-approvalfor high-stakes actions) and that scope is absent or invalid. - Rule. [SPEC §3.4] line 190; [EXT §6.5] line 454.
- Sub-detail. [EXT §6.5] line 456 requires the verifier to name the role(s) of the failing or absent signers in its output. Implementations MUST emit a structured payload alongside
partial, e.g.:
```
{ "code": "partial",
"failing_scopes": ["tool-sandbox"],
"missing_scopes": ["human-approval"] }
partial` emission that omits this structured naming.
Reviewers checking conformance MUST reject a
- Co-occurs with.
signature-invalidis not a substitute forpartial. If the origin signature is invalid, the code issignature-invalidandpartialdoes not apply.partialonly describes non-origin failures over a valid origin. - Caller guidance. Apply policy. A verifier returning
partialis not asserting refusal; the caller's policy must decide whether the missing/invalid co-signature is fatal for this action.
2.3 signature-invalid
Origin signature failed verification. — [SPEC §6] line 324
- Trigger. The Ed25519 (or future-registered suite) verification of the origin signature, computed per [SPEC §3.4] (over
dom_sep || canonical_body), fails. - Rule. [SPEC §3.4], [SPEC §3.6] (domain separation must be applied during verification — verifying without
dom_sepis a verifier bug, not the same assignature-invalid). - Co-occurs with.
tampered— if the body-hash mismatch is the cause of the signature failure, both codes are emitted ([SPEC §6] requires surfacing all applicable reasons; the verifier does not get to elide either).unknown-alg— if thealgis unregistered the verifier MUST return bothunknown-algandsignature-invalid(the unknown-alg rejection is itself a form of signature-invalidity from the caller's perspective).expired-key/deprecated-sigil— when the signing key was no longer authoritative at envelopets. Both codes are emitted.- Caller guidance. Reject. There is no caller policy that should accept an envelope whose origin signature does not verify; this is a hard floor.
2.4 tampered
id does not match the canonical body hash. — [SPEC §6] line 325- Trigger.
id ≠ sha256(canonical_body)wherecanonical_bodyis per [SPEC §3.5] line 202 (envelope withidfield omitted andsignatures[]replaced by an empty array, encoded CBOR-deterministic per [SPEC §3.5]). - Rule. [SPEC §3.5].
- Co-occurs with.
signature-invalid— almost always, because signatures bindcanonical_body. Atamperedenvelope with valid-looking signatures over the altered body indicates the attacker re-signed; in which case the signing key was either the legitimate one (in which case the attacker is the holder, not a man-in-the-middle) or compromised. Either way both codes fire. - Caller guidance. Reject. No path forward. The envelope is not what its
idclaims it is.
2.5 orphaned
parent references an envelope not yet known. — [SPEC §6] line 326- Trigger. A non-genesis envelope whose
parentvalue the verifier cannot resolve to a known prior envelope. This is not the same asparentbeing null on a non-genesis envelope (that is a structural error →tampered/signature-invalidonce verification is attempted). - Rule. Verifier-side state, not a normative spec rule by itself; produces
orphanedso the caller can decide whether to fetch and retry. - Co-occurs with.
parent-trace-mismatchis impossible while orphaned (you need the parent to compute the trace check).unknown-sigilfrequently co-occurs because the same conduit gap drops both records. - Caller guidance. Defer. Fetch the missing parent (via the conduit's audit log [SPEC §5.3], the trace's genesis-pinning gossip [THREATS §4.4], or out-of-band) and re-verify. If the parent cannot be obtained within the application's freshness budget, escalate to reject.
2.6 parent-trace-mismatch
E.trace ≠ parent.trace (violates §3.3a). — [SPEC §6] line 327- Trigger. The verifier has resolved
E.parentto envelopeP, andE.trace ≠ P.trace. - Rule. [SPEC §3.3a] line 177: "for every non-genesis envelope
E,E.traceMUST equalparent.trace." This closes cross-trace parent replay ([THREATS §4.5]). - Co-occurs with.
tampered— frequent (the attacker re-pointedparentand theidhash now does not match).signature-invalid— if the re-pointing was post-signing. - Caller guidance. Reject. There is no benign cause for trace-parent divergence; it is an attacker stitching one conversation onto another.
2.7 replayed-nonce
(from, nonce) pair already seen. — [SPEC §6] line 328- Trigger. The verifier's nonce cache (mandated by [SPEC §3.7] line 228) contains an entry for
(envelope.from, envelope.nonce). - Rule. [SPEC §3.7]. The cache lifetime is at least
expiresof the original envelope (or 30 days ifexpiresis absent). - Co-occurs with.
expired-envelope— possible if the duplicate is itself stale; both fire.signature-invalid— possible (re-signed replay). - Caller guidance. Reject. A duplicated
(from, nonce)is either a replay attack or a sender-side CSPRNG failure; both warrant refusal.
2.8 expired-envelope
expires < now - skew_tolerance. — [SPEC §6] line 329- Trigger. Envelope carries an
expiresfield whose Unix-seconds value is more thanskew_tolerance(default 300 s, [SPEC §3.7] line 229) before the verifier's current time. - Rule. [SPEC §3.7] line 229.
expiresis OPTIONAL ([SPEC §3.2] line 170); absence does not trigger this code. - Co-occurs with.
clock-skew— iftsis also outside the acceptable window.replayed-nonce— if the expired envelope is also a replay. - Caller guidance. Reject. An envelope with a self-declared expiry has explicitly limited its own lifetime.
2.9 expired-key
Signing key in deprecated state at envelope ts. — [SPEC §6] line 330- Trigger. The verifier has resolved
fromto a sigil whose key history shows the signing key was deprecated (post-molt, post-deprecation, or in adisputedmolt-overlap state per [SPEC §2.4] line 124) at the envelope's claimedts. - Rule. [SPEC §2.4]. Note: a verifier that has not yet witnessed the molt or deprecation cannot emit
expired-keyfor a key it still believes authoritative — this is correct and intentional. Verifiers MUST gossip witnessed molts ([EXT §2.5]) so the window of incorrectvalidresults closes. - Co-occurs with.
signature-invalid— always paired (a key the verifier deems expired produces an invalid signature even if the cryptography is intact).deprecated-sigil— when the whole sigil is deprecated, not just one key in its history. - Caller guidance. Reject. Distinguish from
deprecated-sigilin the UI:expired-keysays "this sigil is alive but used the wrong key for this timestamp," whiledeprecated-sigilsays "this sigil is dead."
2.10 deprecated-sigil
Sender's sigil deprecated. — [SPEC §6] line 331
- Trigger.
fromresolves to a sigil whosedeprecationenvelope ([SPEC §2.4] line 128) the verifier has witnessed, andenvelope.tsis at or after the deprecationts. - Rule. [SPEC §2.4] line 128. "Future envelopes from that sigil MUST be rejected by verifiers who have witnessed the deprecation."
- Co-occurs with.
expired-key(every signing key of a deprecated sigil is by definition expired);signature-invalid(downstream consequence). - Caller guidance. Reject. The sigil has signed its own funeral. Posthumous citations may still be received about this sigil ([CHARTER §3.5, §4.7]) but envelopes signed by it post-mortem are rejected.
2.11 unknown-sigil
Cannot resolve from to a public key. — [SPEC §6] line 332- Trigger. Discovery ([SPEC §8.1] line 399, DNS TXT or Herald lookup) has not yielded a public-key record for
fromwithin the verifier's freshness budget. - Rule. [SPEC §8.1].
- Co-occurs with.
orphaned(parent envelope from same unknown sigil), often. - Caller guidance. Defer-or-reject — policy-driven. A strict verifier rejects immediately; a permissive verifier with retry logic defers, fetches via the discovery layer, and re-verifies. Either is conformant. The verifier MUST emit the code so the caller can choose.
2.12 unknown-alg
alg not in registry. — [SPEC §6] line 333- Trigger. The
algfield on at least one signature is not present in the HERALD signature-suite registry ([EXT §6.6] line 460). v0.2 registers onlyed25519. - Rule. [SPEC §3.4] line 191; [EXT §6.6].
- Co-occurs with.
signature-invalid— always (an unverifiable signature from the verifier's perspective is invalid). If the unknownalgis on a non-origin signature, the appropriate code ispartial(with the failing scope named) andunknown-alg(so the caller can distinguish suite-unknown from cryptographic failure). - Caller guidance. Reject. Downgrade attacks ([EXT §6.6] line 462 references suite governance) are the threat model; verifiers MUST NOT attempt to coerce an unknown
alginto a known one.
2.13 clock-skew
ts outside acceptable window. — [SPEC §6] line 334- Trigger.
envelope.tsis more than the verifier'sskew_tolerance(default 300 s, [SPEC §3.7] line 229) into the future or is older than the verifier's freshness budget for this trace. - Rule. [SPEC §3.7] line 229. The 300 s default is for
expires; forts-future skew the verifier inherits the same default unless application policy overrides. - Co-occurs with.
expired-envelope— if bothtsandexpiresfall foul of their respective windows. - Caller guidance. Reject. A future-
tsenvelope has either a clock-broken sender or a backdating attacker; neither warrants acceptance.
2.14 lineage-unverified
Lineage claim has no Herald endorsement (informational, not a failure on its own). — [SPEC §6] line 335
- Trigger.
envelope.lineage_proofis absent, OR present but references a Herald-signed lineage cert that the verifier could not retrieve / has not validated. - Rule. [SPEC §2.3] line 102 (the consent-graph framing); [SPEC §6] line 335 (informational); [THREATS §3.3] (anonymous-lineage threat model). T-021 is the conformance task that turns this informational code into a rendering requirement: viewers MUST surface unverified lineage distinctly (TLS-style — like a missing certificate, not an error page).
- Co-occurs with. Any positive or negative outcome.
- Caller guidance. Inform. Render distinctly. Never reject on this alone. An agent with no provable lineage is allowed ([SPEC §2.3] line 108) but should be treated by the application layer as anonymous.
3. Combination rules and emission discipline
3.1 The "all reasons" rule
[SPEC §6] line 337: "Verifiers MUST surface ALL applicable reasons, not just the first."
- A verifier that returns the first failure and bails is non-conformant.
- A verifier that returns a deduplicated set of codes is conformant.
- A verifier that orders codes is conformant; the order is implementation-defined unless the conformance suite (T-017) pins it.
3.2 Severity is caller-determined
The disposition labels in §1 (reject/defer/inform/defer-or-reject) are default caller posture, not normative ordering. An application policy may:
- Reject on
lineage-unverified(e.g. a regulated context that requires endorsed lineage). Conformant. - Defer on
expired-envelopeif the caller wants to fetch a replacement from the original sender. Conformant if the application accepts the staleness window. - Treat
partialasvalid(i.e. ignore non-origin failures). Conformant only if the failing scopes were not application-required; otherwise non-conformant.
3.3 Precedence matrix (informative)
When multiple codes apply, this card recommends the following surface ordering for human-readable output (UI / logs). It is not normative.
1. tampered — the envelope is not what it claims to be at all; nothing else matters until this is resolved.
2. signature-invalid — cryptographic gate, primary security claim.
3. parent-trace-mismatch — chain integrity, second.
4. replayed-nonce / expired-envelope / clock-skew — temporal/replay.
5. expired-key / deprecated-sigil / unknown-sigil — identity.
6. unknown-alg — suite.
7. orphaned — verifier-side state, deferrable.
8. partial — co-signature policy.
9. lineage-unverified — advisory.
This is for UI hierarchy only; the verifier MUST emit every applicable code regardless of order.
4. Known wrinkles (interpretation notes)
These items reflect ambiguities in the spec language that this card has resolved one way; reviewers should sanity-check.
4.1 valid ⊕ lineage-unverified per envelope?
[SPEC §6] line 335 calls lineage-unverified "informational, not a failure on its own," implying it is emitted alongside valid. This card adopts that reading: per-envelope, the verifier may emit {valid, lineage-unverified} together, and a strict caller treats valid as the disposition while a UI surfaces the advisory. The §1 quick-reference's "mutually exclusive with every code below" line refers to failure codes; advisory codes are the documented exception.
4.2 partial on a genesis envelope
A genesis envelope has only signatures[0] (origin) and possibly Herald lineage co-signatures. If the Herald co-signature is invalid, this card directs the verifier to emit partial with failing_scopes: ["herald-lineage"]. The spec does not address genesis specifically — this card extrapolates from [EXT §6.5] uniformly.
4.3 unknown-alg on origin vs. non-origin
If the origin signature carries an unknown alg, both unknown-alg and signature-invalid fire. If a non-origin signature carries an unknown alg, unknown-alg fires together with partial. The combined emission preserves caller distinguishability (was it crypto failure or suite failure?) — this card recommends both codes always co-fire so that conformance vectors (T-017, T-026) can probe each leg.
4.4 EXT-only codes?
[EXT §6.5] line 452 says "EXT adds the partial state" — but partial is already in the [SPEC §6] table (line 323). This card treats EXT §6.5 as an elaboration of the same code, not a separate code. There is no EXT-only code in v0.2. Future EXTs adding new conformance dimensions (capability manifest non-conformance, conduit-receipt absence on state-transition envelopes, stale Herald Charter) should either map to existing codes or extend this enum with explicit registry entries.
4.5 What about MUST-reject rules elsewhere in EXT?
EXT contains several MUST-reject rules (cool-off violation [EXT §1.2], missing SIP [EXT §1.3], stale Herald Charter [EXT §3.5], manifest below economic floor [EXT §4.4], capability strings not in registry [EXT §4.4], conduit co-located log [EXT §5.4]). This card maps them as follows:
| EXT rule | Verifier code |
|---|---|
| Cool-off violation in molt | signature-invalid (the molt is treated as never having existed; subsequent envelopes from K_new produce expired-key/unknown-sigil until a valid molt is observed) |
| Missing SIP for state-transition envelope | signature-invalid (state-transition envelopes are inert without SIP) |
| Stale Herald Charter on a Herald-signed attestation | signature-invalid for the lineage scope → partial if non-origin |
Manifest below economic floor without subsidised: true | Out of scope for envelope verification; this is a manifest-validation code, not enumerated here. T-112 / [EXT §4.4] |
| Capability strings not in registry | Same as above — manifest-validation, not envelope-verification |
If a future spec revision wants verifier-level codes for any of these, they should be added to [SPEC §6] and this card updated.
5. Conformance pointers
- Test vectors. Each code in §2 above MUST have at least one positive vector (envelope that triggers exactly this code) and at least one negative vector (envelope that does not trigger it but might be confused for it). T-001 (test vectors) and T-017 (conformance runner) are the consumers.
- Renderer conformance. Codes
partial,lineage-unverified, and the absence-of-failure casevalidall have UI implications; T-021 and T-023 are the renderer-side conformance tasks. This card is the input to those tasks. - Verifier identity. A verifier emitting any code not in this table is non-conformant unless the spec has been amended. New codes require a SPEC update.
Appendix A — One-line gloss per code
For embedding in error toasts, log lines, and small UI surfaces.
| Code | Gloss |
|---|---|
valid | The envelope is what it claims to be. |
partial | The envelope is genuine but a co-signature is missing or invalid. |
signature-invalid | The origin signature does not verify. |
tampered | The body has been altered since id was computed. |
orphaned | The parent envelope is unknown to this verifier. |
parent-trace-mismatch | The parent's trace does not match this envelope's trace. |
replayed-nonce | This (from, nonce) pair has been seen before. |
expired-envelope | The envelope's expires time has passed. |
expired-key | The signing key was no longer authoritative at the envelope's ts. |
deprecated-sigil | The sender's sigil is retired. |
unknown-sigil | The sender's sigil cannot be resolved to a public key. |
unknown-alg | The signature suite is not in the registry. |
clock-skew | The envelope's timestamp is outside the acceptable window. |
lineage-unverified | No Herald has endorsed the claimed lineage. |
End of card. Authoritative source: [SPEC §6].