INTERPRETATION — T-001
Three places where the SPEC (or the task brief) was ambiguous and a
choice had to be made. These are flagged here per AGENT_PROTOCOL.md §
"How to handle ambiguity".
1. Domain-separation tag version: v1 vs v2
Tension. The task brief states:
Signatures are over dom_sep || canonical_body, where for the originscope dom_sep = "herald.envelope.v1" (per [SPEC §3.6]).But the current SPEC.md §3.6 lists:
| Role | Domain-separation tag |
|------|------------------------|
| Originating envelope | herald.envelope.v2 |Resolution. SPEC.md is the source of truth (AGENT_PROTOCOL.md §
"Cite specifically": "read those documents as written today — do not
assume they match your training. Documents evolve.") The task brief
predates a SPEC update from v1 → v2 tags. We use herald.envelope.v2.
If a reviewer wants v1 tags for backward compatibility, regenerate by
changing DOM_SEP_ENVELOPE in generate.py and re-running. The vector
file is mechanically regenerated from a single constant.
2. Genesis trace fixed-point (trace == id is self-referential under hash)
Tension. SPEC §3.2:
Genesis envelopes: MUST setparent: nullandtrace: id(i.e.
self-trace).
SPEC §3.5:
idis computed assha256(canonical_body)wherecanonical_bodyis
the CBOR-deterministic encoding of the envelope **with the id fieldomitted and the signatures array replaced by an empty array**.Since the canonical body contains trace, and on a genesis envelopetrace == id == sha256(canonical_body), the equation has no
closed-form solution: id cannot be computed without first knowingid.
Resolution. We treat the canonical body of a genesis envelope as
having trace = null (a sentinel meaning "self-trace, value to be
filled by the recipient"). Concretely:
1. Build canonical body with parent: null and trace: null.
2. Compute id = sha256(canonical_body).
3. The wire envelope sets trace = id (display only); the canonical
bytes used for signing and id-verification keep trace = null.
A verifier receiving a genesis envelope (one whose wire parent is
null and whose wire trace equals the wire id) MUST recompute the
canonical body with trace = null before re-hashing.
This convention is the only consistent reading of §3.2 + §3.5 absent
an explicit fixed-point definition. It's worth a follow-on task to add
a paragraph to SPEC §3.5 explicitly defining the genesis convention.
Alternative considered: treat trace = id literally, computing a
fixed-point via brute force or an iterative substitution. Rejected:
no hash function admits a closed-form fixed point; brute force would
make genesis envelopes computationally expensive and non-deterministic.
3. Deterministic nonces vs SPEC §3.7 CSPRNG requirement
Tension. SPEC §3.7:
Senders MUST use a CSPRNG and MUST NOT derive nonces fromtsorid.
But test vectors must regenerate byte-identically.
Resolution. Test-vector nonces are deterministically derived from a
fixed string label:
nonce = sha256(b"herald.testvector.nonce/" + label)[:16]This is not derived from ts or id (two specifically forbidden
sources), so the prohibition's letter is not violated; the CSPRNG
requirement is. We accept the relaxation explicitly, only for test
vectors, and call it out in:
generate.py:deterministic_nonce()(docstring)vectors.jsondeterministic_seed_notefieldREADME.md(Nonces section)
Production senders MUST use a CSPRNG. Test-vector consumers should
treat the nonces as opaque 16-byte values; their derivation is an
implementation detail of this generator, not part of the protocol.
4. The rejected-lineage-cycle verifier code
Tension. SPEC §2.3 says the verifier MUST reject lineage cycles,
but SPEC §6's enumerated return codes (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) does not include a "lineage cycle" code.
Resolution. We surface the result as rejected-lineage-cycle
explicitly. None of the existing §6 codes fit:
signature-invalidis wrong (signature is fine).tamperedis wrong (id matches body).lineage-unverifiedis informational, not a failure.
A correct fix is to add a lineage-rejected (or similar) entry to
SPEC §6. Worth a follow-on task: *"Extend SPEC §6 verifier return
codes to enumerate lineage-validation failure modes."*