Skip to content

multihash as the unifying substrate: subsume kyln / nessie / agent-mesh / agent-store BLAKE3 conventions #84

Description

@hartsock

Summary

Decide how content-addressable uses multihash / multicodec / multibase to subsume the other BLAKE3 identifier conventions in the workspace (kyln-core, nessie-store, agent-mesh-protocol envelope, agent-store), so that every consumer speaks this crate's real CIDv1 and the crate becomes the single home of content identity — which is why the crate exists.

This is a design issue, not a bug: nothing is broken, but four wire-compatible yet API/text-incompatible conventions coexist, and agent-bridle is about to lean on content identity for artifact authority + evidence (agent-bridle ADR 0028 sketch, in flight). That work should not be the fifth convention.

The four conventions today (verified 2026-08-16)

Where Binary form Text form Codec Hash algos Structured-value canonicalization
content-addressable (ContentId) real CIDv1 via cid/multihash crates multibase base32-lower b… (frozen); digest_hex() = bare 64-hex (frozen) dag-cbor 0x71"fixed forever for 0.1.x, not selectable" (README "Alpha status" §3) BLAKE3-256 only (0x1e, 32 bytes) canonical dag-cbor (to_canonical_dagcbor)
kyln-core (ContentId / crypto::Cid) hand-rolled [0x01, 0x55, 0x1e, 0x20, digest]byte-identical to a spec CIDv1(raw, blake3-256) (kyln-core/src/crypto.rs:165-201, all varints single-byte) to_hex() = hex of the whole CID envelope (content_id.rs:70-72) — i.e. multibase base16 minus the f prefix raw 0x55 BLAKE3 (0x1e); codes for sha256/512 exist ciborium CBOR w/ BTreeMap ordering (content/canonical.rs), not dag-cbor; ObjectId in the store = bare digest
nessie-store (Digest) to_multihash_bytes() = spec multihash <code><len><digest> (nessie-backend-core/src/digest.rs:158-190) "<algo>:<hex>" with multiformats names blake3 / sha2-256 (digest.rs:71-75) none (no CID envelope) BLAKE3 (0x1e) and SHA-256 (0x12) first-class — REAPI face pins SHA-256 bespoke domain-tagged length-delimited (nessie.Tree.v1, nessie.ActionResult.v1, nessie.Attestation.v1)
agent-mesh-protocol envelope payload_cid, Fingerprint; agent-store content_hash bare [u8; 32] BLAKE3 hex via blake3::Hash::to_hex none BLAKE3 n/a (opaque payload bytes / chained log)

Related, already in this crate: from_blake3_content_digest([u8;32]) is the guarded no-rehash door for BLAKE3-native upstreams (README lines 35-45), but it stamps 0x71, and the doc rightly warns that stamping dag-cbor on non-dag-cbor content mints a CID whose codec "is a lie" (content_id.rs:158-166). #53 (chunked-dag) explicitly defers "a deliberate raw-leaf profile decision" as a follow-up. agent-mesh-protocol's authority/provenance module already depends on this crate for typed CIDs (AuthorityId/GrantId), while its envelope layer still uses bare 32-byte digests.

What multiformats already give us (the good news)

  • Binary: every one of the four is already a valid multihash payload; kyln's is already a valid CIDv1. Nothing needs re-hashing to become spec-conformant.
  • Text: kyln hex is f + hex; nessie blake3:<hex> ↔ multihash(0x1e, hex); bare-digest-hex is this crate's digest_hex(). FromStr already tolerates non-base32 multibases "as a convenience, not a contract" (README §2).
  • Algorithm agility: multihash's code byte is exactly the seam for nessie's SHA-256 peer and for kyln's sha256/512 codes, without changing what ContentId mints.

So the divergences that remain are policy, not encoding: codec (0x71 vs 0x55 vs none), text form, which algorithms may be minted vs merely verified, and canonicalization of structured values.

Questions to decide

  1. Raw-leaf profile. Do we add a second frozen typed identifier — e.g. RawContentId (CIDv1, raw 0x55, BLAKE3-256, 32) — for byte artifacts (files, chunks, envelope payloads, log lines), alongside ContentId for dag-cbor values? kyln's ContentId would then be byte-identical to it and could migrate by type swap; agent-mesh/agent-store bare digests would wrap into it losslessly. Or do we keep exactly one profile and require artifacts to be ChunkLeaf { kind, data: bytes } dag-cbor nodes (feat(chunked-dag): chunked file + directory DAG (sized links) #53's MVP stance), so every CID stays 0x71? Trade-off: one profile = one presentation contract, but every foreign raw CID becomes "foreign forever"; two profiles = kyln/agent-mesh subsumed at zero cost, but the frozen-surface grows (does that violate FREEZE-MINIMALLY, or is an additional type outside the "0x71 fixed forever" clause because that clause is about ContentId?).
  2. Verified-import of foreign CIDs. A Cid-parsing entry (ContentId::try_from_cid / an AnyCid enum) that classifies by (codec, hash code): dag-cbor+blake3 → ContentId; raw+blake3 → RawContentId (if adopted); anything else (e.g. raw+sha2-256 from REAPI) → verifiable-not-mintable interop value. This is how nessie's SHA-256 world is represented here without this crate ever minting SHA-256.
  3. Text-form policy. Canonical stays base32-lower b…. Do we bless explicit to_multibase(Base)/from_multibase and document the legacy parsers (kyln envelope-hex = base16 sans prefix; nessie algo:hex) behind a default-off legacy-formats feature with a deprecation path, or refuse them and make consumers convert at their edge?
  4. Structured-value canonicalization. kyln's ciborium canonical CBOR and nessie's bespoke length-delimited encodings mint different CIDs from dag-cbor for the same logical value. Migrating them is a re-naming ceremony (identity moves). Do we (a) provide to_canonical_dagcbor + conformance vectors as the target and let each repo migrate, (b) additionally offer nessie's Tree/ActionResult and kyln's ProvenanceLink as shapes in this crate (feat(chunked-dag): chunked file + directory DAG (sized links) #53 DirNode/FileNode already covers Tree), so the migration is "depend on the crate" rather than "re-implement"?
  5. The from_blake3_content_digest door. After (1), should it be re-pointed at the raw profile (where a bare content digest is honest) and the 0x71 variant restricted to callers who know the digest is over canonical dag-cbor?

Proposed acceptance

Non-goals

  • Changing ContentId's frozen profile (BLAKE3-256 / dag-cbor 0x71 / 32 bytes / v1) or its presentation contract.
  • Minting SHA-256 CIDs in this crate.
  • Anything about the ADR-0025 branch in agent-bridle; this is about the crate as the shared substrate, not that document.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions