AI Initiative logoAI Initiative
Reading room / Manthan

INTERPRETATION — T-002

INTERPRETATION — T-002. Covers I-1 Tags: "EXT Appendix A" does not exist; I-2 Text strings: UTF-8 normalisation; I-3 Big integers: `bigint` vs tagged bignum; I-4 Float compaction rules.

Project explanation / technical method
This is the retained source text, not a new validation of its claims. Numerical results and comparative claims require the accompanying source review before public reuse.

Read the connected case study or article →

Not explicitly stated in source · Date not established

Provenance, authorship and editorial notes

INTERPRETATION — T-002

Id

manthan-herald-protocol-lib-cbor-det-interpretation

Project

Manthan

Source path

manthan/herald-protocol/lib/cbor-det/INTERPRETATION.md

Source sha256

e3742a71800ab7258e8dd0c9cb15bc78c03091036a1383777bb6f928832c953e

Local copy sha256

e3742a71800ab7258e8dd0c9cb15bc78c03091036a1383777bb6f928832c953e

Repo head

3a94084024a3434cef331fecac91c2c3b43f0313

Date
Not recorded
Date basis

Original publication date not established; Git revision date retained separately.

Author

Not explicitly stated in source

Author basis

Unknown; Git committer not substituted for document author.

Last source commit

8b75d1b0157c20a0a463f0e31008ef9f73099beb

Last source commit date

2026-06-08T07:40:14Z

Document status

Project explanation / technical method

Summary

INTERPRETATION — T-002. Covers I-1 Tags: "EXT Appendix A" does not exist; I-2 Text strings: UTF-8 normalisation; I-3 Big integers: `bigint` vs tagged bignum; I-4 Float compaction rules.

Topics
  • Exploration architecture and selection
  • I-1 Tags: "EXT Appendix A" does not exist
  • I-2 Text strings: UTF-8 normalisation
  • I-3 Big integers: `bigint` vs tagged bignum
Theme

Exploration architecture and selection

Priority

deep

Related ids
  • manthan-readme
  • manthan-docs-architecture
  • manthan-docs-business-overview
  • manthan-docs-demo-walkthrough
  • manthan-docs-principles
  • manthan-docs-qd-measurement-direction-plan
  • manthan-docs-swarm-evolution-integration
Evidence concerns
  • Recovered source text is preserved verbatim. Assertions, comparisons, commands and results belong to its original context; they were not re-executed or externally re-verified during this corpus review.
Word count

974

Bytes

6647

Source relative

herald-protocol/lib/cbor-det/INTERPRETATION.md

Remote mirror

manthan/herald-protocol/lib/cbor-det/INTERPRETATION.md

Full text preserved

True

INTERPRETATION — T-002

SPEC §3.5 is short. These are the four ambiguities the brief flagged
plus how I resolved them. Each interpretation is testable, conservative,
and reversible by a single change in src/profile.ts.


I-1 Tags: "EXT Appendix A" does not exist

Spec text:

No tags except those listed in EXT Appendix A (registered tag set).

Ambiguity: EXT.md does not currently contain an Appendix A. The
"registered tag set" referenced is therefore an empty set, an
unspecified set, or a TODO-set.

Interpretation (conservative): No tags are accepted in v0.2. The
encoder has no tag-emission path; the decoder rejects any major-6 byte
with E_TAG. Adding a tag later is a single change at
src/profile.ts:PROFILE.ALLOWED_TAGS plus a switch case in
decode.ts:decodeValue().

Why conservative is right here: rejecting now, opening up later, is
a forward-compatible move. Accepting unknown tags now and later
discovering they collide with v0.3 semantics would be a wire-incompat
break.

Test coverage: strict-mode.test.ts > "any tag rejected" (CBOR tag
0 / RFC date string).


I-2 Text strings: UTF-8 normalisation

Spec text:

Map keys MUST be text strings in v0.2.

(Plus the implicit "text string" usage throughout.)

Ambiguity: "Text string" doesn't pin a normalisation form. The same
visually-identical string can be encoded as different UTF-8 byte
sequences (NFC vs NFD; precomposed é vs decomposed e + combining
acute). If two encoders pick differently, signatures don't verify.

Interpretation: Follow RFC 8949 §3.1: a CBOR text string is a
sequence of UTF-8 code units, with no normalisation applied. Bytes
round-trip verbatim. If a HERALD application wants Unicode-equivalence
checks, that's an application-layer concern, not a wire-format one.

Why this is right: RFC 8949 itself takes this position, and
agreeing with the underlying RFC means we agree with cbor2,
cbor-x, cborg, and every other deterministic CBOR implementation.
Imposing NFC on top of CBOR-deterministic would be a HERALD-specific
break.

Test coverage: round-trip.test.ts > "strings (ASCII and unicode)"
covers café, 日本語, 🦄✨. None of the seven worker-A vectors
contain non-ASCII keys, but the encoder does the same UTF-8 byte work
for all string sizes.


I-3 Big integers: bigint vs tagged bignum

Spec text:

No tags except those listed in EXT Appendix A.

Ambiguity: RFC 8949 §3.4 encodes integers outside [-(2^64), 2^64-1]
using tag 2 (positive bignum) or tag 3 (negative bignum). SPEC §3.5
prohibits tags. So integers outside that range cannot be encoded at all
under the v0.2 profile.

Interpretation: Support only integers in [-(2^64), 2^64-1]. Any
attempt to encode a bigger or smaller bigint throws E_INT_RANGE.
JS-side, the encoder accepts both number and bigint; the decoder
returns number for values within Number.MAX_SAFE_INTEGER and
bigint for everything else.

Why this is right: the spec literally forbids the only two tags
that can carry larger integers. No envelope field in v0.2 needs
greater range (timestamps fit in u64 forever; nonces are byte strings,
not integers).

Test coverage: edge-cases.test.ts > "bigint inside u64 range
round-trips"
, "bigint > 2^64-1 throws", "bigint < -(2^64) throws".


I-4 Float compaction rules

Spec text:

Floats MUST use the shortest representation that round-trips
losslessly; otherwise integers MUST be used.

Ambiguity: "Shortest" is interpreted by RFC 8949 §4.2.2 as "try
float16, then float32, then float64 — the smallest that preserves
value wins". The spec doesn't restate the algorithm; some readers
might interpret "shortest" as "use whichever IEEE-754 width the source
language uses natively".

Interpretation: Follow RFC 8949 §4.2.2 exactly. The encoder tries
float16, then float32, then float64. The decoder rejects any wider
representation when a narrower one would round-trip exactly
(E_FLOAT_WIDE).

The integer-prefer-over-float clause ("otherwise integers MUST be
used") is a separate check applied first: if the JS number is
integer-valued and within safe-integer range, encode as a CBOR
integer regardless of how IEEE-754 would represent it. The decoder
rejects integer-valued floats with E_FLOAT_IS_INT.

Why this is right: RFC 8949 §4.2.2 is the only canonical-encoding
algorithm widely implemented; agreeing with it is what gives us byte-
for-byte interop with cbor2. None of the seven worker-A vectors
contain floats, so this clause is not exercised by interop tests yet —
but T-003 attestations like temperature: 0.7 and confidence: 0.62
will hit it.

Test coverage:

"float64 value that fits in float32 ...",
"float32 value that fits in float16 ..."


Other small choices worth flagging

CBOR undefined (Simple 23) is rejected

The profile says nothing about undefined. RFC 8949 §3.3 lists it as a
valid simple value. JS's undefined and CBOR's Simple(23) are the
same shape. I rejected it on both encode and decode because:

the field shouldn't exist at all (just omit the key).

(omit vs explicit Simple(23)), breaking byte-uniqueness.

If a future HERALD use-case needs explicit absence-of-value, we'd add
it consciously rather than inheriting it.

Object.prototype instances only

encode.ts:isPlainObject() checks the prototype chain. This rejects
class instances, which would otherwise have non-enumerable methods
that Object.keys skips but a future Object.getOwnPropertyNames
would include. By rejecting at the boundary we don't need to defend
against that drift later.

Trailing bytes after top-level value: rejected

The decoder asserts cursor.pos === bytes.length after decoding the
top-level value. A real parser would let trailing bytes through
because they "might" be the start of a second value; a strict CBOR
deserialiser must reject them, because two CBOR items concatenated is
not a single CBOR item. This caught one subtle bug during early
testing where the cursor underran by 1 on float16.

Continue exploring