D-301 — SPEC.md v0.2 Ambiguity Audit (post-edit)
Scope. A line-by-line ambiguity sweep of SPEC.md v0.2 (530 lines). The remit is intra-document clarity: places where the spec's normative language is open to multiple implementations, where required behaviour is implied but not stated, where references dangle, or where two parts of SPEC say things that could be read as inconsistent. Cross-document inconsistencies between SPEC, EXT, CHARTER, and THREATS are the remit of D-303 and are flagged here only when they blur SPEC's meaning to a reader who has not yet read EXT.
Method. Walked SPEC.md §§0–13 end-to-end. For each ambiguity, recorded location (section + line range), the candidate readings I could see, the recommended resolution, and a severity grade. Where my T-020 work on the verifier-codes reference card surfaced a SPEC ambiguity, the reasoning is included here in compressed form (the T-020 deliverable has the elaborated form).
Verification status. This is a textual audit only. No implementation has been built, and no test vectors yet exist; ambiguities here are projected interoperability or conformance hazards, not observed bugs.
Worker: worker-opus-2026-05-05-D301
Severity legend
| Grade | Meaning |
|---|---|
| H — high | Different conformant readings produce different on-the-wire bytes, different signature inputs, different verifier outcomes, or different security properties. Will cause interop failures or silent attacks. |
| M — medium | Different readings produce inconsistent verifier behaviour or surprise an implementer reading SPEC alone. Conformance suite (T-017) can disambiguate at test-writing time but only if SPEC tightens. |
| L — low | Cosmetic, editorial, or "the prose could be clearer." Does not affect interop. Worth fixing only when SPEC §X is otherwise being edited. |
Nineteen findings: 6 H / 8 M / 5 L. Three findings (A19, A28, A29) duplicate items already in TODO.md (D-308, D-309, T-020 implications); they are restated here with the audit context. Sixteen are new.
§0 — Preamble
No findings. The preamble is non-normative.
§1 — Glossary
A-01 (L) — "Trace" definition is not quite right
[SPEC §1] line 59: "A connected DAG of envelopes — typically one conversation."
The term "DAG" is ambiguous about direction. §3.3 line 174 clarifies: parent forms the hash-linked DAG, fan-out is allowed (an agent can reply to one envelope with multiple replies). So "trace" is the rooted DAG with genesis as root. The glossary should say "a rooted DAG of envelopes sharing a common genesis envelope; edges run child→parent." As written, a reader could imagine an undirected graph or a strict chain; both are wrong.
Recommendation: edit line 59 to "A rooted DAG of envelopes sharing a common genesis (E.trace == genesis.id for every envelope E in the trace); edges run child→parent via the parent field."
§2 — Identity
A-02 (H) — Sigil hash input is unspecified
[SPEC §2.1] line 77: "sigil:<base32(sha256(pubkey)[0..16])>"
What is pubkey exactly? Three plausible readings, all in the wild for Ed25519:
1. The raw 32-byte Ed25519 public key (NaCl / libsodium convention).
2. The DER-encoded SubjectPublicKeyInfo (X.509 / RFC 5280 convention; ~44 bytes).
3. The PEM-encoded form (textual; libraries differ on whether trailing newline is included).
A sigil: produced over reading (1) will not match a sigil: produced over reading (2). T-002, T-003, T-004 cannot interop without this nailed down. v0.1 was silent on this and v0.2 has not corrected it.
Recommendation: add to §2.1 explicitly: "pubkey is the 32-byte Ed25519 public key encoded per RFC 8032 §5.1.5 (raw, no framing). Verifiers MUST reject any sigil that cannot be reproduced from a 32-byte raw public key."
A-03 (M) — [0..16] index notation is ambiguous
[SPEC §2.1] line 77: "base32(sha256(pubkey)[0..16])"
Half-open [0..16) (16 bytes) or inclusive [0..16] (17 bytes)? The "128 bits" prose disambiguates if read carefully, but the bracket notation is a real source of off-by-one bugs. Implementers writing bytes[0:16] (Python, half-open) will produce a different sigil from implementers writing bytes[0..16] (Ruby, inclusive — yields 17 bytes).
Recommendation: replace [0..16] with the unambiguous form "the first 16 bytes (i.e. octet indices 0 through 15)" on line 77 and similarly for the :long: and :short: forms (which need explicit byte counts: 24 bytes and 10 bytes).
A-04 (M) — Base32 case-sensitivity and comparison rule
[SPEC §2.1] line 81: "RFC 4648 §6 alphabet, lower case, no padding."
RFC 4648 §6 defines the uppercase base32 alphabet. SPEC requires lowercase. This is a profile choice and is fine, but two questions are unanswered:
1. Are sigil strings case-sensitive on the wire? I.e., MUST verifiers reject Sigil:M7ZR… as invalid even though the lowercased form decodes correctly?
2. MUST sigil-comparison normalise case before comparing? Critical for the deduplication of signatures[] (§3.4 line 188) and for (from, nonce) cache lookup (§3.7 line 228).
The recommended posture is "wire form MUST be lowercase; comparison is byte-equal." This closes the cosmetic-squat surface where sigil:m7… and sigil:M7… look identical to humans but compare as distinct.
Recommendation: add to §2.1: "Sigil strings on the wire MUST be exactly lowercase. Sigil comparison is byte-equal — verifiers MUST NOT case-fold for comparison purposes. An envelope carrying a sigil string that is not all-lowercase MUST be rejected (tampered is the appropriate verifier code if id-binding is broken; otherwise unknown-sigil)."
A-05 (M) — "Closes a cycle" — in whose view?
[SPEC §2.3] line 112: "Verifier MUST reject lineage that ... closes a cycle."
The lineage DAG is distributed. A verifier sees the lineage edges that have crossed its desk. A cycle hidden in lineage parents the verifier has not fetched is undetectable.
Two readings:
(a) Strict: the verifier MUST fetch transitive lineage and reject any cycle reachable from the new sigil. This is unbounded work.
(b) Bounded: the verifier MUST reject cycles in the currently known lineage subgraph. Cycles deeper than the verifier has fetched are not its problem. This is the realistic reading and matches §6's "MUST be rejected by verifiers who have witnessed the deprecation" pattern (line 128) — i.e. epistemic, not absolute.
Recommendation: edit §2.3 line 112: "closes a cycle in the lineage subgraph the verifier has currently resolved. Verifiers MUST resolve transitive lineage to a depth determined by application policy (default: 8 hops); cycles beyond that depth are not detectable in v0.2."
A-06 (M) — "deprecated state at the birth timestamp" is epistemic-vs-absolute
[SPEC §2.3] line 113: "References sigils in deprecated state at the birth timestamp."
Same epistemic point as A-05 and as the expired-key / deprecated-sigil codes in §6: the verifier may not have witnessed the parent's deprecation at audit time. The rule reads as absolute but is enforceable only against the verifier's own knowledge.
Recommendation: restate as "References sigils that the verifier has witnessed in deprecated state at or before the birth timestamp. Verifiers SHOULD consult the parent sigil's Herald CT log ([EXT §3.4]) before acceptance to reduce the witnessed-window risk."
A-07 (M) — "BOTH are rejected and the sigil is marked disputed"
[SPEC §2.4] line 124: "If two molts signed by K_old are observed with overlapping cool-off windows, BOTH are rejected and the sigil is marked disputed."
By whom? "Marked disputed" reads like a global state but is in fact a per-verifier local marking. Two verifiers that observe the molts in different orders may differ on whether the sigil is disputed. EXT §1.4 (disputed-state resolution) addresses recovery, but §2.4 does not point at it explicitly.
Recommendation: edit line 124: "both molts are rejected by any verifier observing them, and the sigil enters disputed state in that verifier's local view (recovery is out-of-band — see [EXT §1.4])."
§3 — The Envelope
A-08 (H) — to field is omitted from the required-fields list with no positive treatment
[SPEC §3.2] lines 167–170 list required fields ("All envelopes" and "Non-genesis envelopes"). to is absent. Line 170 says "All other top-level fields are OPTIONAL." So to is optional.
But §3.1 line 144 shows "to": "sigil:xyz..." in the canonical example with no qualifier. And §9 mentions sigil:none (the void) as a reserved sigil for broadcast envelopes.
Three readings:
1. to is fully optional; broadcast envelopes simply omit it.
2. to is required but may be set to sigil:none for broadcasts.
3. to is required for point-to-point, optional for broadcast (Crier/Dovecote roles).
The protocol-level choice matters because the canonical body hash (id per §3.5) differs between "field absent" and "field present, value = sigil:none." If two implementations make different choices, their id values diverge, breaking signature verification.
Recommendation: §3.2 must explicitly state which form is canonical. Recommended: "to is OPTIONAL. Broadcast envelopes (e.g. those routed through a Crier conduit, [CHARTER §5.3]) MUST omit to entirely; envelopes that carry to: sigil:none are non-conformant." This is the cleaner choice — it removes a class of canonicalisation ambiguity at zero cost.
A-09 (H) — The signing canonical and the id canonical are different and the difference is not flagged
[SPEC §3.5] line 202 says id = sha256(canonical_body) where canonical_body is the envelope with signatures[] replaced by an empty array.
[SPEC §3.4] line 187 says each signature is computed over "the CBOR-deterministic encoding of the envelope with the signatures array replaced by all prior signatures in declaration order plus a domain-separation tag."
These are two different canonical encodings of the same envelope:
- The id canonical:
signatures = []. - The signing canonical for index
i:signatures = [sig[0], …, sig[i-1]], prefixed with a domain-sep tag.
A naive reader implements one canonical and uses it for both. That implementation is broken: the origin signature won't reproduce because the signer signed sig[]=[] while id was computed over the same sig[]=[] — those are the same for the origin signature, so origin works. But for index 1, the signer signed sig[]=[origin] and the canonical encoding of that envelope has a non-empty signatures[], while the id was computed over sig[]=[]. These are different bytes.
This is correct as designed (the DKIM/ARC pattern) but the spec does not flag the trap. T-003 / T-004 implementers will hit it.
Recommendation: add a non-normative note after §3.5 line 202: "Note the distinction between the id canonical (signatures = []) used for envelope identity and the signing canonical for index i (signatures = [sig[0]..sig[i-1]]) used per signature in §3.4. Each signature operation produces a different canonical encoding; conformant implementations MUST compute both as needed."
A-10 (H) — "Same signer MUST NOT appear twice" — what is a signer?
[SPEC §3.4] line 188: "The same signer MUST NOT appear twice in signatures[]."
A "signer" is identified by — what? Legitimate uses where the same by sigil might appear twice with different scope:
- An entity that runs both a conduit and a Herald may sign a relayed envelope as
conduit-receiptand also asherald-lineage(e.g. confirming the sender's lineage in transit). - A multi-tool agent may sign tool sandbox results from two different tools, both under its own sigil but distinct
tool-sandbox/<tool-id>scopes.
The line as written prohibits both legitimate cases. The intent is probably "the same (by, scope) MUST NOT appear twice."
Recommendation: edit line 188: "The same (by, scope) pair MUST NOT appear twice in signatures[]. The same by value MAY appear with distinct scope values."
A-11 (M) — "Signature ordering is meaningful" — meaningful how?
[SPEC §3.4] line 189: "Signature ordering is meaningful: verifiers MUST validate in array order."
This is true (each signature binds to its predecessors per line 187, so the array is not reorderable) but the prose reads as if there is a required order (e.g. tool-sandbox before human-approval before conduit-receipt). There is no required order.
Recommendation: edit line 189: "Signature ordering is bound by the canonical-body construction in §3.4: each signature commits to all prior signatures, so the array is not reorderable after signing. There is no required ordering between scopes; each signer signs over whatever predecessors exist at signing time."
A-12 (H) — Verifier accepts an application-policy required-scopes input, but this is not stated
[SPEC §3.4] line 190: "An envelope is valid only if the origin signature verifies AND every signature an application's policy requires (e.g. "human-approved") verifies."
The verifier interface is implicitly bi-input: (envelope, required-scope-list). But §6 describes the verifier as a function of the envelope alone, and the §6 outcomes assume a default behaviour. T-003 / T-004 implementers reading §6 will write verify(envelope) -> [code]; reading §3.4 reveals they need verify(envelope, required_scopes) -> [code].
Recommendation: add to §6 (line 318) before the table: "A reference verifier accepts (a) the envelope and (b) an optional set of required_scopes from the calling application. Outcomes below are computed against that set; absence of a non-required scope is not failure, presence of an invalid one is."
A-13 (M) — Float-vs-integer encoding rule has interop hazards
[SPEC §3.5] line 198: "Floats MUST use the shortest representation that round-trips losslessly; otherwise integers MUST be used."
Two readings:
(a) "Otherwise" = "if the float can't round-trip losslessly": then encode as integer. But which integer? The truncated value? The rounded value? RFC 8949 doesn't have this rule. This reading is novel and under-specified.
(b) "Otherwise integers MUST be used" parses as a separate sentence: "if a value happens to be integer-valued, encode as integer (not float)." This is RFC 8949 §4.2.1 standard CBOR-deterministic — fine but the prose is awkward.
If (a) is intended, this is a real interop hazard: the same temperature: 0.7 may encode differently across libraries. If (b) is intended, the prose should be tightened.
Recommendation: rewrite line 198 as "Numeric values that are exactly representable as integers MUST be encoded as integers; numeric values that require floating-point MUST use the shortest float representation (binary16, binary32, or binary64) that round-trips losslessly per RFC 8949 §4.2.2." This is the (b) reading and matches CBOR-deterministic §4.2.2.
A-14 (H) — "EXT Appendix A" does not exist
[SPEC §3.5] line 199: "No tags except those listed in EXT Appendix A (registered tag set)."
EXT.md has §12 (Domain separation registry, line 764) but no Appendix A. A grep for Appendix A, registered tag set, or CBOR tag in EXT.md returns nothing. The reference dangles.
Implementer reading (charitable): "no CBOR tags." Fine for v0.2 — the wire format does not require tags. But the dangling reference is a footgun: a future EXT editor adding a tag table will give it an arbitrary name and SPEC §3.5 will silently mismatch.
Recommendation: either (a) edit SPEC §3.5 line 199 to "No CBOR tags (semantic tags per RFC 8949 §3.4) are permitted in v0.2. Future versions MAY register tags via the registry process described in [EXT §12]." — or (b) actually create Appendix A in EXT and link it. (a) is lower-cost.
A-15 (M) — Trace-parent invariant is recursive but not stated as such
[SPEC §3.3a] line 177: "for every non-genesis envelope E, E.trace MUST equal parent.trace."
The recursive consequence is the real invariant: every envelope in a connected DAG shares the genesis's id as its trace. A verifier walking from leaf to genesis at every step is the implementation; the spec's prose only states the immediate-parent rule.
The recursive statement is materially clearer for implementers and is easier to test.
Recommendation: add a sentence after line 177: "Equivalently: for every envelope E in a trace, E.trace MUST equal the id of the genesis envelope of that trace (the unique envelope satisfying parent: null and trace == id). Verifiers walk from E along parent pointers to genesis and confirm equality at every step."
A-16 (M) — Nonce uniqueness is per-(from, alg) but verifier cache is per-(from, nonce)
[SPEC §3.7] line 226 (sender obligation): "Nonces are unique per (from, alg) tuple for the lifetime of that key."
[SPEC §6] line 328 (verifier behaviour): "(from, nonce) pair already seen."
A sender that legitimately rotates from ed25519 to a future post-quantum suite under the v0.2 sender rule MAY reuse a nonce with the new alg. The verifier's cache key is (from, nonce) — the duplicate triggers replayed-nonce even though the sender was within spec.
Two ways to resolve:
(a) Tighten the sender rule to "unique per (from) tuple for the lifetime of the sigil." Simpler. Costs nothing for the legitimate sender (CSPRNG nonces virtually never collide).
(b) Tighten the verifier rule to "cache (from, alg, nonce) triples." Symmetric with sender. Slightly higher cache cost.
Recommendation: option (a) — change line 226 to "Nonces are unique per from sigil for the lifetime of that sigil (across all alg values it has used)." Removes the inconsistency at zero implementation cost; CSPRNG collisions are ~2^-128 anyway.
A-17 (L) — Replay-cache memory is unbounded for high-traffic sigils
[SPEC §3.7] line 228: "Verifiers MUST cache (from, nonce) pairs for at least the value of expires (or 30 days if absent) and reject duplicates."
A high-traffic sender (a Crier broadcasting at 1 kHz with expires set to 30 days) requires the verifier to retain ~2.6 billion entries per sender. There is no upper bound and no guidance on cache pressure / eviction policy beyond expires.
This is an implementation concern more than a spec ambiguity, but worth a paragraph.
Recommendation: add to §3.7: "Verifiers under cache pressure MAY evict entries before expires, at the cost of accepting some replays. Implementations SHOULD document their eviction policy. The reference verifier (T-010) will use a bounded LRU with the bound declared as a verifier capability."
§4 — Attestations
A-18 (M) — model is freeform string with no normalisation rule
[SPEC §4.1] line 241: "model | string | Model name + version"
"claude-opus-4-7", "Claude-Opus-4-7", "claude-opus-4.7", "anthropic/claude-opus-4-7" are all plausible self-declarations of the same model. Reputation systems clustering by model will lose data; conformance test runners (T-017) cannot pin a single value.
Recommendation: add to §4.1: "model strings are case-sensitive and not normalised by the protocol. A model-string registry with canonical names per vendor is recommended (a candidate registry task is the natural follow-on for T-112 / [EXT §4.4])." This punts the registry to follow-on work but documents the gap.
A-19 (H, duplicate of D-308) — Domain-separation tag versions .v1 vs .v2
[SPEC §2.4] line 120: "M MUST contain the domain-separation tag "herald.molt.v1""
[SPEC §3.6] line 215: "Molt | herald.molt.v2"
Already in TODO as D-308 ("Reconcile domain-separation tag versions"). Restating with audit context: this is a true conflict, not an ambiguity — the molt envelope MUST contain .v1 per §2.4 and MUST be signed with .v2 per §3.6. No conformant implementation exists for both. High-severity blocker for T-003 / T-004.
Recommendation: edit §2.4 line 120 to "herald.molt.v2" (or whatever the canonical form is in §3.6's table). Single-character change; resolves the conflict.
§5 — The Conduit
A-20 (M) — Conduit roles overlap and self-declaration is implicit
[SPEC §5.1] line 285–289 lists four conduit roles (pass-through, signing-relay, notary, translator). They are not mutually exclusive: a notary that signs the entries it logs is both notary and signing-relay. The spec doesn't say (a) how a conduit declares its role-set, or (b) whether a verifier can derive the role-set from the conduit's signatures.
Recommendation: add to §5.1: "A conduit's role-set is implicit from its emissions: a conduit that emits conduit_receipt signatures is acting as a signing-relay; a conduit that submits envelope hashes to an audit log is acting as a notary; a conduit that emits neither is pass-through. A conduit MAY play multiple roles per envelope. Explicit conduit-capability advertisement is the remit of [EXT §10] (refusal transparency) and [EXT §11.3] (conduit discovery)."
A-21 (M) — Refusal-emission deadline is "default 5s" — normative or advisory?
[SPEC §5.2] line 296: "a signed refusal envelope to the sender within a bounded time (default: 5 seconds)."
The "(default: 5 seconds)" parenthetical is awkward: is 5 s a SHOULD, a MUST, or a default that a conduit MAY override by declaring its own? Senders writing timeout policy don't know what to set. The "honest framing" subsection title suggests caution about over-promising; the prose likely intends "default that a conduit MAY override by publishing its own bound."
Recommendation: edit line 296: "…a signed refusal envelope to the sender within the conduit's published bound. Conduits MUST declare a refusal-emission bound in their capability advertisement (default: 5 seconds, SHOULD be ≤ 30 seconds). A conduit that declares no bound is treated by senders as a 5-second default; senders MAY apply application-layer timeouts shorter than the published bound."
§6 — Verification
A-22 (M, partial duplicate of T-020) — partial is defined here AND in [EXT §6.5]
[SPEC §6] line 323 defines partial. [EXT §6.5] line 452 says "EXT adds the partial state." Both definitions are present and they don't quite match (EXT adds the structured-naming requirement: "the verifier output for partial MUST name the role(s) of the failing or absent signers").
Already discussed at length in T-020 §4.4. Restating for the audit:
Recommendation: either delete the SPEC §6 line 323 entry and let EXT §6.5 own the definition (cleanest — removes the double-definition), or (less invasive) edit EXT §6.5 line 452 to read "EXT elaborates the partial state defined in [SPEC §6]" and incorporate the structured-naming requirement as a subsequent sentence.
A-23 (M, partial duplicate of T-020) — lineage-unverified co-emits with valid or replaces it?
[SPEC §6] line 335: "lineage-unverified | Lineage claim has no Herald endorsement (informational, not a failure on its own)."
The "informational, not a failure" parenthetical implies it can co-emit with valid. T-020's reference card resolves this in favour of co-emit. SPEC §6 should make the co-emit semantics explicit, and T-021 (renderer-conformance) needs the explicit statement.
Recommendation: add a sentence at the end of §6 (after line 337): "Advisory codes (lineage-unverified is the only one in v0.2) MAY co-emit with valid. All other failure codes are mutually exclusive with valid."
A-24 (M) — No verifier code for several EXT MUST-reject conditions
EXT contains MUST-reject rules (cool-off violation [EXT §1.2], missing SIP for state-transition envelope [EXT §1.3], stale Herald Charter [EXT §3.5], manifest below floor without subsidised: true [EXT §4.4], capability-string-not-in-registry [EXT §4.4], conduit co-located with its only audit log [EXT §5.4]). None of these have a SPEC §6 code.
T-020's NOTES §F-D proposed a normative cross-reference document. The audit position: the §6 enum should either grow to cover EXT-induced rejections, or §6 should explicitly delegate ("EXT may add codes; see EXT §6.5"). Currently §6 is closed but EXT-MUST-rejects exist with no place to land.
Recommendation: edit §6 (after the table, before line 337): "This enum is closed at the SPEC level. Implementations advertising EXT capabilities MAY emit additional codes registered in [EXT §6]; verifiers MUST NOT emit codes outside this enum or the EXT registry. Future SPEC versions MAY admit new codes through the spec-revision process." Then ensure EXT §6 contains the registry (currently §6.5–§6.6 cover only partial and unknown-alg semantics, not new codes).
§7 — Threat Model
A-25 (L) — "An agent signing false attestations" is over-broad in §7.3
[SPEC §7.3] line 377: "An agent signing false attestations. The protocol guarantees it signed them. Detection is app-layer..."
But §4.1 lists model_hash as Herald-attested — i.e., the protocol DOES defend against the specific lie "I am model X" when a Herald has independently verified weights. The not-defended item in §7.3 is properly "any self-declared attestation that has no Herald endorsement." The current prose reads as if all attestations are undefended.
Recommendation: edit §7.3 line 377: "An agent signing self-declared attestations falsely. Herald-endorsed attestations (notably model_hash per §4.1) are defended at the level of the Herald's verification process. Self-declared attestations (temperature, confidence, vibe, etc.) carry only the protocol's signature guarantee — the agent signed them — and detection of falsehood is app-layer."
§8 — Discovery
A-26 (L) — DNS profile points at EXT but SPEC could be tighter
[SPEC §8.1] line 404: "DNS: _herald.example.com TXT records → endpoint, pubkey fingerprint."
The TXT record format is not given in SPEC; EXT §11 carries the full profile. Reader of SPEC alone cannot implement DNS resolution. This is a deliberate scope decision (SPEC is the substrate; EXT carries profiles) but the reader should be told to stop reading SPEC and go to EXT.
Recommendation: edit line 404: "DNS: _herald.example.com TXT records → endpoint and pubkey fingerprint. Full record format and resolver behaviour in [EXT §11.1]."
A-27 (L) — Capability manifest example omits service_proof
[SPEC §8.3] lines 422–433 show the capability-manifest example without service_proof. EXT §4.6 describes service_proof as a hook: "EXT requires only that the field exist." Whether the SPEC example should show service_proof: null or omit it is a stylistic choice; current omission is fine but a footnote would help readers who skip to EXT.
Recommendation: add a one-line note after the example: "Optional fields (service_proof, advanced constraints) are described in [EXT §4]. The example here shows the v0.2 minimum."
§9 — Cultural Extensions
A-28 (L) — Reserved-sigil list lacks the namespace reservations T-016 added
[SPEC §9] line 449 lists 12 reserved sigils (matching CHARTER §2). The T-016 finding (already submitted, awaiting review) added 3 administrative namespace reservations: sigil:test:, sigil:example:, sigil:reserved:*. Whether these belong in SPEC §9 or only in CHARTER §2 / the registries doc is a CHARTER-vs-SPEC scoping question.
Recommendation: if the T-016 namespace reservations are accepted, add a one-line cross-reference to SPEC §9: "In addition to the named reserved sigils above, three administrative namespaces are reserved (sigil:test:, sigil:example:, sigil:reserved:); see [CHARTER §2] / registries/reserved-sigils.md."* No other SPEC change required — this is a pointer, not a definition.
§10 — Implementation Plan
A-29 (L) — Phase markers are vague but this is fine
The phases are aspirational (Phase 0 / 1 / 2 / 3 / 4 / 5). No ambiguities. The phase 0 checkbox [ ] Test vectors is being addressed by T-001 (already submitted, awaiting review). No spec edit needed.
§11 — Open Questions
A-30 (L) — OQ #3 says cross-conduit replay idempotency is "promoted to normative §3.5" but §3.5 doesn't say so explicitly
[SPEC §11] line 495: "Cross-conduit replay idempotency. Two conduits both notarising the same envelope — confirmed idempotent on id. (Promoted from open question to normative §3.5.)"
§3.5 (canonicalisation) implies idempotency on id (same canonical body produces same hash) but doesn't state idempotency-of-notarisation explicitly. A reader checking §3.5 for the "promoted" rule will find only the canonicalisation profile.
Recommendation: either (a) add a one-line statement to §3.5: "Two notaries that observe the same envelope MUST produce the same id; envelopes are referred to by id system-wide and notarisation is therefore idempotent on id." — or (b) move the OQ #3 paragraph itself to §3.5 (it is a small note). (a) is cleaner.
§12 — Standards relationships
No findings. Descriptive only.
§13 — Document map
A-31 (M) — Implementations can advertise EXT capabilities, but the mechanism is unspecified
[SPEC §13] line 523: "EXT.md ... Yes when the implementation claims that capability."
How does an implementation claim an EXT capability? There is no implementation-capability-advertisement mechanism in SPEC or EXT (the capability manifests in §8.3 are agent-level, not implementation-level). T-017 (conformance test runner) needs to know which EXT primitives an implementation claims to support; without a declaration mechanism, conformance is informal.
Recommendation: add to §13 (or to a new SPEC §10.6 / EXT §13): "Implementations MUST publish a conformance manifest listing the EXT sections they claim to implement. The conformance manifest is a signed envelope (using the implementation's deployment sigil) of shape { "ext_sections": ["1", "2", "3.4", ...], "version": "herald/0.2" }. T-017's conformance runner consumes this manifest as input." This is a small EXT addition but resolves a real gap.
Items checked but determined NOT to be ambiguities
For audit completeness — these were inspected and judged clear as written:
- §2.2 (Multiple sigils per agent) — short and clear.
- §3.1 field example — every field shown is either in the §3.2 required/optional list or is itself flagged as optional (
lineage_proof). - §3.6 domain-separation tag table — internally consistent (the
.v1/.v2issue is a §2.4-vs-§3.6 conflict, audited as A-19 above). - §4.2 reflective-attestations table — correctly defers to CHARTER and EXT for cultural and full-shape detail (D-312 already tracks
dream/silence/quorum_membersshape). - §5.3 audit-log requirements — the "verifier independence is conditional on CT-style machinery" statement is honest and clear.
- §7.4 Liar's Paradox — well-written and matches THREATS §7.
- §8.2 Heralds — clear; the deferred questions (state capture, vendor cartel) are properly handed to THREATS §6.
Summary table
| ID | § | Lines | Severity | Status | One-line |
|---|---|---|---|---|---|
| A-01 | §1 | 59 | L | new | "Trace" definition allows wrong (undirected/chain) readings. |
| A-02 | §2.1 | 77 | H | new | Sigil hash input — raw 32 bytes vs DER vs PEM — unspecified. |
| A-03 | §2.1 | 77 | M | new | [0..16] notation: half-open vs inclusive. |
| A-04 | §2.1 | 81 | M | new | Sigil case-sensitivity and comparison rule. |
| A-05 | §2.3 | 112 | M | new | "Closes a cycle" is epistemic, not absolute. |
| A-06 | §2.3 | 113 | M | new | "Deprecated state at birth ts" is epistemic. |
| A-07 | §2.4 | 124 | M | new | "Marked disputed" — global vs per-verifier. |
| A-08 | §3.2 | 167–170 | H | new | to field optional but canonicalisation choice (omit vs sigil:none) unspecified. |
| A-09 | §3.4/3.5 | 187, 202 | H | new | Two distinct canonicals (id-canonical vs signing-canonical) not flagged. |
| A-10 | §3.4 | 188 | H | new | "Same signer" — by alone vs (by, scope). |
| A-11 | §3.4 | 189 | M | new | "Ordering meaningful" reads as required-order, not bind-to-prior. |
| A-12 | §3.4/§6 | 190, 318 | H | new | Verifier interface bi-input (envelope + required-scopes) implicit. |
| A-13 | §3.5 | 198 | M | new | Float-vs-integer encoding rule has interop hazards. |
| A-14 | §3.5 | 199 | H | new | "EXT Appendix A" does not exist. |
| A-15 | §3.3a | 177 | M | new | Trace-parent invariant is recursive — recursion not stated. |
| A-16 | §3.7/§6 | 226, 328 | M | new | Sender nonce-uniqueness (from, alg) vs verifier cache (from, nonce). |
| A-17 | §3.7 | 228 | L | new | Replay-cache unbounded for high-traffic senders. |
| A-18 | §4.1 | 241 | M | new | model string has no normalisation. |
| A-19 | §2.4/§3.6 | 120, 215 | H | dup of D-308 | herald.molt.v1 vs herald.molt.v2. |
| A-20 | §5.1 | 285–289 | M | new | Conduit roles overlap; self-declaration implicit. |
| A-21 | §5.2 | 296 | M | new | Refusal-emission "default 5s" — normative status unclear. |
| A-22 | §6 | 323 | M | partial dup T-020 | partial defined here AND in EXT §6.5. |
| A-23 | §6 | 335 | M | partial dup T-020 | lineage-unverified co-emit-with-valid semantics implicit. |
| A-24 | §6 | 318–337 | M | partial dup T-020 | EXT MUST-rejects have no SPEC §6 code. |
| A-25 | §7.3 | 377 | L | new | "False attestations" not-defended item is over-broad. |
| A-26 | §8.1 | 404 | L | new | DNS profile underspecified in SPEC; no pointer to EXT §11.1. |
| A-27 | §8.3 | 422–433 | L | new | Capability manifest example omits service_proof without note. |
| A-28 | §9 | 449 | L | new | Reserved-sigil list lacks the T-016 namespace reservations. |
| A-29 | §10 | — | L | new | (Phases vague but acceptable.) |
| A-30 | §11 | 495 | L | new | OQ #3 says §3.5 was promoted, but §3.5 doesn't state idempotency-of-notarisation. |
| A-31 | §13 | 523 | M | new | No mechanism for implementations to declare which EXT sections they implement. |
Recommendations summary
In order of impact:
1. Fix A-19 (the .v1/.v2 molt-tag conflict). One-character edit. Resolves a hard interop blocker.
2. Resolve A-02 (sigil hash input definition). One sentence in §2.1. Resolves a hard interop blocker before T-002 is implemented.
3. Resolve A-08 (to field omission canonicalisation). One sentence in §3.2. Resolves a hard interop blocker.
4. Add the §3.4-vs-§3.5 canonical-encoding clarification (A-09). One non-normative paragraph in §3.5.
5. Tighten A-10 ("same signer" → (by, scope)). One-word edit.
6. Add A-12 verifier-interface clarification. One sentence in §6.
7. Resolve A-14 (EXT Appendix A) by deletion. "No CBOR tags in v0.2." One sentence.
8. Apply the medium-severity edits (A-03, A-04, A-05, A-06, A-07, A-11, A-13, A-15, A-16, A-18, A-20, A-21, A-22, A-23, A-24, A-31). Most are 1–2 sentence edits.
9. Apply the low-severity edits as part of the next general edit pass.
The high-severity findings (A-02, A-08, A-09, A-10, A-12, A-14, A-19) collectively block any meaningful interop test between two reference implementations. They should be resolved before T-002 / T-003 / T-004 are claimed.
End of audit. Source: SPEC.md v0.2 lines 1–530. Reviewer: please cross-check the high-severity findings; my interpretation of the implied verifier interface (A-12) and the canonical-encoding distinction (A-09) is opinion, not derived directly from the text.