Audit D-318 — Cultural and reserved-sigil conformance vectors regenerated
Worker: worker-glossary-par-17
Date: 2026-05-05
Closes: D-317 follow-on (audits/t001-t003-applied.md "Patches NOT applied" §3).
Result
Regenerated against the post-D-317 reference library (lib/herald-core-ts/src/):
| File | Vector count | Conformance category | Result against post-D-317 lib |
|---|---|---|---|
tests/conformance/cultural/vectors/cultural-vectors.json | 14 | cultural-attestation | 14 pass / 0 fail |
tests/conformance/reserved-sigils/vectors/reserved-sigil-vectors.json | 8 | reserved-sigil | 8 pass / 0 fail |
Total: 22/22 pass. (The brief said "9 entries" for reserved-sigils;
the actual T-026 SPECS array has always defined 8 — see Notes §1.)
The regenerated JSONs live in this sandbox at:
manthan/state/findings/D-318/vectors/cultural-vectors.jsonmanthan/state/findings/D-318/vectors/reserved-sigil-vectors.json
The reviewer copies them in-place during promotion (sandbox discipline:
no edits outside manthan/state/findings/D-318/).
Why regeneration was necessary
D-316 / D-317 changed two things in the reference library:
1. Origin domain-separation tag. ORIGIN_DOMAIN was renamed from
"herald.sig.origin.v1" to "herald.envelope.v1"
(lib/herald-core-ts/src/envelope.ts:6 and
lib/herald-core-ts/src/sign.ts:10). The old string is now a
documentation-only synonym and MUST NOT appear on the wire.
2. Opaque-value encoding (SPEC §3.5a, added by D-316). The fields
id, parent, trace, nonce are now CBOR byte strings
(major type 2) when canonically encoded; the previous
implementation encoded them as CBOR text strings of their hex
representation. The conversion lives in bodyForSigning() at
lib/herald-core-ts/src/envelope.ts:182-190.
Both changes alter the bytes that get signed. A naive substitution
of the domain_separation_tag_origin field in the JSON would leave
the cached signature_hex and id_hex stale (because the old
signatures cover the old bytes). The conformance verifier would
either:
- recompute
idfrom inputs and reject the vector with
tampered (the cultural case — id derives from canonical body, so
every byte change reshuffles the hash); or
- attempt to verify the cached
signature_hexagainst the new
domain tag and fail with signature-invalid.
The pre-D-318 vectors fail the conformance categories under the
current library — confirmed before regeneration:
$ node --experimental-strip-types scripts/run-conformance-on-old.ts
[cultural-OLD] 0 pass / 14 fail / 14 total
[reserved-sigils-OLD] 7 pass / 1 fail / 8 total(7/8 of reserved-sigils only "pass" by short-circuit — the policy
check fires before wire-verification, so they never exercise the
broken signature. The single wire-valid vectorrole-recipient-none-valid fails as tampered.)
After regeneration, all 22 pass cleanly.
What changed (file-level summary)
domain_separation_tag_origin field
| File | OLD count of herald.sig.origin.v1 | NEW count of herald.sig.origin.v1 | NEW count of herald.envelope.v1 |
|---|---|---|---|
cultural-vectors.json | 15 | 0 | 15 |
reserved-sigil-vectors.json | 9 | 0 | 9 |
Each per-vector entry in the JSON also embeds the domain tag in adomain_separation_tag field; that's the source of the per-vector
+1 count above the file-level header. All references swept.
Signature and id changes (sample: cultural / mood-valid-precise)
OLD id_hex: 4bb408c30d6118336af1e4f3ae35eb78b5a523874f4c7dc174e8626a83da1d3f
NEW id_hex: 65567c1da28b30d3ff1badaa1f8b86e74edbf5b0c7ee6aeda86e73750891f60d
OLD signature_hex: 1ffba9e4a03b38107b687a97114563efe89bc6b0764c7f45f039e46ef3763813
dd574ac101fb3ca9b63758a82a5172a184558ce53c60296dcd366298d6f4a808
NEW signature_hex: 0f0bb0a6fdbc843d55add82d76189e5d77f981854523534f98a5e8bf010fcfc5
70f7d47552a962d1753f59cc262c09cd86ffc75ee9c5e1ce25650c54cca64e0dEvery single signature and every single id in both files differs from
the pre-D-318 file. That is correct: under SPEC §3.5a the nonce
field alone moves from a 32-byte ASCII-hex CBOR text-string to a
16-byte CBOR byte-string, which changes the signed canonical body
even before the new domain tag is mixed in.
CBOR canonical-body shape (sample: cultural / mood-valid-precise)
The first divergence between OLD and NEW canonical bodies is at
byte offset 67, immediately after the field name nonce (CBOR
text string 656e6f6e6365):
OLD: ... 6e 6f 6e 63 65 | 78 20 [32 hex ASCII bytes of the nonce]
NEW: ... 6e 6f 6e 63 65 | 50 [16 raw bytes of the nonce]78 20 = CBOR text-string major-type-3 with 32-byte length. 50 =
CBOR byte-string major-type-2 with 16-byte length (0x40 | 0x10).
This is exactly the encoding rule SPEC §3.5a (added by D-316) now
mandates. The OLD canonical body is 201 bytes; the NEW is 184 bytes
(saving 17 bytes per vector — the 32 ASCII characters + length-prefix
overhead reduce to 16 raw bytes + 1-byte tag).
For parent: null genesis vectors the parent and trace fields
hit the parent === null short-circuit in bodyForSigning, so the
hex→bytes conversion only fires for nonce here. (id is excluded
from canonical-body hashing per bodyForHash.) Reply / lineage
vectors would also see parent and trace reshape, but the
T-024 / T-026 corpora are all genesis envelopes.
Generators and regeneration procedure
Original generators (kept in their T-024 / T-026 sandboxes):
manthan/state/findings/T-024/tests/conformance/cultural/src/build-vectors.tsmanthan/state/findings/T-026/tests/conformance/reserved-sigils/src/build-vectors.ts
Both import from ../../../../../T-003/src/index.ts — the pre-D-316
reference library. The single change required to retarget them at the
post-D-317 library was the import path:
- } from "../../../../../T-003/src/index.ts";
+ } from "../../../../../lib/herald-core-ts/src/index.ts";Updated generators in this sandbox:
scripts/build-cultural-vectors.ts(cultural)scripts/build-reserved-sigil-vectors.ts(reserved-sigil)
All SPECS arrays, deterministic seeds (alpha = 0x01\32),
nonce derivations (sha256("herald.t02{4,6}...nonce.<vector-name>")[0:16]),
and timestamps (BASE_TS = 1746518400 cultural, 1746604800
reserved-sigil) are preserved verbatim. The signature change is
entirely* a consequence of swapping the library, not of any spec
change.
To regenerate from this sandbox:
cd learning/herald-protocol/manthan/state/findings/D-318/scripts
node --experimental-strip-types build-cultural-vectors.ts
node --experimental-strip-types build-reserved-sigil-vectors.tsOutput lands in vectors/{cultural,reserved-sigil}-vectors.json
under the D-318 sandbox.
Verification protocol
scripts/verify-vectors.ts — wire-layer only: reconstructs eachexpected: valid vector and runs verifyEnvelope fromlib/herald-core-ts/src/envelope.ts. 9 cultural + 1 reserved-sigil
wire-valid vectors all pass.
scripts/run-conformance-categories.ts — full conformance: imports
sandbox copies of cultural-category.ts andreserved-sigil-category.ts (with their stale../../../../../T-017/src/... imports rewritten to../../../../../../tests/conformance/src/..., since the runner has
been promoted but those category files still hold the old paths —
flagged separately, see Notes §2). Drives the actual category
dispatcher over the regenerated JSONs:
=== cultural ===
PASS mood-valid-precise expected=valid observed=valid
PASS mood-shape-invalid expected=shape-invalid observed=shape-invalid
PASS flinch-valid expected=valid observed=valid
PASS flinch-shape-invalid expected=shape-invalid observed=shape-invalid
PASS tilt-valid expected=valid observed=valid
PASS tilt-out-of-range expected=shape-invalid observed=shape-invalid
PASS weariness-valid expected=valid observed=valid
PASS weariness-negative expected=shape-invalid observed=shape-invalid
PASS dread-valid expected=valid observed=valid
PASS aphasia-valid expected=valid observed=valid
PASS vibe-freeform expected=valid observed=valid
PASS haiku-three-lines expected=valid observed=valid
PASS haiku-not-three expected=shape-invalid observed=shape-invalid
PASS mixed-multiple-attestations expected=valid observed=valid
[cultural] 14 pass / 0 fail / 14 total
=== reserved-sigils ===
PASS role-recipient-none-valid expected=valid observed=valid
PASS role-sender-crier-rejected expected=reserved-sigil-sender observed=reserved-sigil-sender
PASS role-signature-by-scribe-rejected expected=reserved-sigil-signer observed=reserved-sigil-signer
PASS example-recipient-production-rejected expected=reserved-sigil-production observed=reserved-sigil-production
PASS test-sender-production-rejected expected=reserved-sigil-production observed=reserved-sigil-production
PASS future-reserved-recipient-unknown expected=unknown-sigil observed=unknown-sigil
PASS lineage-parent-none-rejected expected=reserved-sigil-lineage-parent observed=reserved-sigil-lineage-parent
PASS claim-sigil-dreamer-rejected expected=reserved-sigil-claim observed=reserved-sigil-claim
[reserved-sigils] 8 pass / 0 fail / 8 total
FINAL: 22/22 conformance vectors pass under the post-D-317 library.scripts/run-conformance-on-old.ts — same harness but pointed at
the un-regenerated files in tests/conformance/.../vectors/. Used
to demonstrate the old vectors fail (results above in §"Why
regeneration was necessary").
Cross-references (signature-derivation sites)
| File | Line(s) | Purpose | ||
|---|---|---|---|---|
lib/herald-core-ts/src/envelope.ts | 6 | ORIGIN_DOMAIN = "herald.envelope.v1" | ||
lib/herald-core-ts/src/envelope.ts | 106 | buildEnvelope signs via signingBytes(...) | ||
lib/herald-core-ts/src/envelope.ts | 165-167 | signingBytes = `domain_tag | canonicalBody` | |
lib/herald-core-ts/src/envelope.ts | 182-190 | bodyForSigning hex→bytes conversion (the SPEC §3.5a fix) | ||
lib/herald-core-ts/src/sign.ts | 4-22 | DOMAIN_TAGS.origin = "herald.envelope.v1" | ||
lib/herald-core-ts/src/sign.ts | 37-39 | signWithDomainTag (lower-level signing primitive) | ||
manthan/state/findings/D-318/scripts/build-cultural-vectors.ts | 35-42 | regen import (post-D-317 lib) | ||
manthan/state/findings/D-318/scripts/build-cultural-vectors.ts | 67-77 | buildEnvelope(...) call → signature derivation | ||
manthan/state/findings/D-318/scripts/build-reserved-sigil-vectors.ts | 18-25 | regen import (post-D-317 lib) | ||
manthan/state/findings/D-318/scripts/build-reserved-sigil-vectors.ts | 44-58 | buildEnvelope(...) call → signature derivation | ||
SPEC.md | §3.5a | opaque-value encoding rule (added by D-316) | ||
SPEC.md | §3.6 | origin domain tag table | ||
EXT.md | §6.3 | origin scope mapping | ||
EXT.md | §12 | domain-separation registry |
What I did NOT do
- No edits outside the D-318 sandbox. The regenerated JSON files
live under manthan/state/findings/D-318/vectors/, not in
tests/conformance/.../vectors/. The reviewer promotes them in
place.
- No edits to T-024 or T-026 generators. I copied them into this
sandbox and changed only the library import path. The originals at
manthan/state/findings/T-{024,026}/tests/conformance/... are
untouched.
- **No fix to the stale
T-017paths in
tests/conformance/{cultural,reserved-sigils}/src/.ts.* Those
files still import from a non-existent
../../../../../T-017/src/... location (they were written before
T-017 was promoted to tests/conformance/). The regen task does
not require fixing them — only the JSON files. Flagged as a
follow-on, see Notes §2.
- No SPEC / EXT edits. The library change was already made by
D-316 / D-317.
- No Python regen. T-026 / T-024 are TypeScript-only deliverables
(build-vectors.ts); there is no parallel Python generator for
these conformance categories. The Python lib at
lib/herald-core-py/ was already updated by D-317.
Cross-implementation interop check
Not run for these two files. The cultural and reserved-sigil
generators only exist in TypeScript; the cross-language interop
fixture lives in lib/herald-core-py/tests/test_envelope.py and
uses the T-001 envelope-core vectors only. Adding cross-language
interop for cultural / reserved-sigil corpora is a separate
follow-on (would require a Python build_vectors.py mirror of
T-024 and T-026).
Follow-ons noted
1. *Fix stale T-017 import paths in
tests/conformance/{cultural,reserved-sigils}/src/.ts.* Six
files import from ../../../../../T-017/src/..., which doesn't
exist post-promotion of T-017. The cultural and reserved-sigil
conformance categories therefore can't actually be invoked from
the deployed location — they only run via the sandbox shim built
here. This is independent of D-318 (the JSONs can* be promoted
even if the test runner is broken; the JSONs would then be
exercised once those imports are fixed).
2. *Add a top-level npm task that runs the cultural and
reserved-sigil conformance categories* alongside envelope-core.
Today only tests/conformance/test/runner.test.ts is in the
default npm test for the conformance package; the per-category
tests in tests/conformance/{cultural,reserved-sigils}/test/
have separate package.json files but aren't aggregated.
3. *Document the regen procedure in
tests/conformance/cultural/README.md and
tests/conformance/reserved-sigils/README.md. Currently the
READMEs reference the T-024/T-026 generator paths, which still
work but produce pre-D-316* signatures. Should at minimum point
readers at the post-D-317 procedure (or, better, move the
generators into tests/conformance/.../src/build-vectors.ts
with corrected imports — same pattern as the T-017 promotion).