SPEC §6 Verifier-Verdict Fix — rejected-lineage-cycle
Audit ID: D-319
Worker: worker-glossary-par-18
Date: 2026-05-05
Scope: Propose a minimal patch to SPEC §6 (../../../../../SPEC.md) (verifier-verdict enumeration) so that a verifier rejecting a lineage cycle has a normative verdict code to return. This is a paper proposal — no SPEC.md edit is performed by this audit.
1. Problem
SPEC.md §2.3 lines 110-113 (../../../../../SPEC.md) state, normatively:
Verifier MUST reject lineage that:
- Contains the new sigil itself (self-as-ancestor).
- Closes a cycle.
- References sigils in deprecated state at the birth timestamp.
But SPEC.md §6 lines 316-336 (../../../../../SPEC.md) — the canonical verdict enumeration that conformant verifiers MUST surface — contains no code for this rejection class. The 14 enumerated verdicts are:
valid, partial, signature-invalid, tampered, orphaned, parent-trace-mismatch, replayed-nonce, expired-envelope, expired-key, deprecated-sigil, unknown-sigil, unknown-alg, clock-skew, lineage-unverified.
None of these fit a lineage-cycle rejection:
signature-invalidis wrong — the signature verifies cleanly; the failure is graph-shape, not crypto.tamperedis wrong —idmatches the canonical body hash.lineage-unverifiedis explicitly informational, not a failure (per §6 line 336: "informational, not a failure on its own"). Reusing it for a hard MUST-reject would silently downgrade a normative rejection to a soft signal.deprecated-sigilcovers the third §2.3 bullet (deprecated-state ancestor) but not cycles or self-as-ancestor.
The §2.3 MUST clause therefore has no §6 surface, and §6 line 338 — "Verifiers MUST surface ALL applicable reasons" — cannot be honoured for §2.3 rejections.
2. Surfacing references
This gap was independently surfaced twice:
1. T-001 INTERPRETATION I-4 — see learning/herald-protocol/manthan/state/findings/T-001/INTERPRETATION.md lines 105-123. The T-001 vector author flagged the missing code while constructing test vector 7 (lineage-cycle-birth) and recommended a follow-on task to "Extend SPEC §6 verifier return codes to enumerate lineage-validation failure modes." D-319 is that follow-on.
2. T-017 conformance runner — see learning/herald-protocol/manthan/state/findings/T-017/RECEIPT.md. The runner's expected column for vector 7 currently has no §6-conformant value to assert against, so the vector is skipped. Adding the proposed verdict closes this skip.
3. Proposed amendment
3.1 Where in §6
Insert a new row immediately after the current lineage-unverified row (current SPEC.md line 336, the last row of the verdict table). Rationale: the two lineage-related verdicts cluster together, and lineage-unverified (informational) precedes rejected-lineage-cycle (failure) in the same rough increasing-severity order the table already uses (informational/partial codes earlier, hard rejections later within the lineage cluster).
3.2 Exact row text
| `rejected-lineage-cycle` | Birth-envelope `lineage` violates §2.3: contains self-as-ancestor, closes a cycle, or references a deprecated-state ancestor at birth `ts`. |The single verdict deliberately covers all three §2.3 MUST-reject bullets rather than splitting into three codes (rejected-self-as-ancestor, rejected-lineage-cycle, rejected-deprecated-ancestor). The lineage-cycle name in the task brief and in T-001 vector 7 (lineage-cycle-birth) is canonical; the third bullet (deprecated ancestor) is rare and folding it under the same code keeps the enumeration small. Reviewers who want finer granularity can split later via an additional D-task without breaking conformance.
3.3 One-line rationale (for inclusion in the SPEC patch commit message)
[SPEC §2.3] requires verifiers to reject lineage cycles, self-as-ancestor, and deprecated-state ancestors; §6 lacked a verdict code for this rejection, forcing conformant verifiers (T-017 vector 7) to skip — rejected-lineage-cycle is the missing surface.4. Non-goals (explicitly out of scope for D-319)
- No change to §2.3. The MUST clauses there are already correct; only §6 needs the surface.
- No change to the
lineage-unverifiedsemantics. The "informational, not a failure" disclaimer stays as written. The proposed code is a separate, hard-rejection verdict. - No reshuffling of the §6 table. Append-only insert at the lineage cluster; existing codes keep their position.
- No EXT/CHARTER/THREATS edits. THREATS §6.5 (../../../../../THREATS.md) on lineage laundering is unaffected — it discusses policy layered on top of the verifier verdict, not the verdict itself.
- No test-vector regeneration. T-001 vector 7 already declares
expected: rejected-lineage-cycle; this audit ratifies that name rather than changing the vectors.
5. Migration / compatibility
The amendment is strictly additive to the §6 enumeration. Existing verifiers that don't implement lineage-cycle detection will still pass cycles silently — that was already a §2.3 conformance violation before this audit, and remains one. Verifiers that do detect cycles (e.g. T-003 reference library) gain a normative name for the verdict they were already returning ad-hoc.
No envelope on the wire changes. No signing/canonicalisation changes. The patch is a single table-row insert in SPEC.md.
6. Recommended next steps
1. Human reviewer applies the §6 table-row insert above.
2. T-017 conformance runner removes the vector-7 skip and asserts expected === "rejected-lineage-cycle".
3. Follow-on D-task (optional): audit T-003 reference library to confirm it returns the new verdict string verbatim, not an ad-hoc local name.