You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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-cbor0x71 — "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
raw0x55
BLAKE3 (0x1e); codes for sha256/512 exist
ciborium CBOR w/ BTreeMap ordering (content/canonical.rs), not dag-cbor; ObjectId in the store = bare digest
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 ContentIdmints.
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
Raw-leaf profile. Do we add a second frozen typed identifier — e.g. RawContentId (CIDv1, raw0x55, 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?).
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.
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?
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) #53DirNode/FileNode already covers Tree), so the migration is "depend on the crate" rather than "re-implement"?
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?
A migration checklist per consumer: kyln-core, nessie-store, agent-mesh-protocol envelope, agent-store — each ends with content-addressable as its identity dependency and no local hash/CID types.
agent-bridle ADR 0028 (artifact authority + evidence) cites the outcome as its identity contract rather than choosing a codec locally.
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.
Summary
Decide how
content-addressableuses 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)
ContentId)cid/multihashcratesb…(frozen);digest_hex()= bare 64-hex (frozen)dag-cbor0x71— "fixed forever for 0.1.x, not selectable" (README "Alpha status" §3)0x1e, 32 bytes)to_canonical_dagcbor)ContentId/crypto::Cid)[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 thefprefixraw0x550x1e); codes for sha256/512 existciboriumCBOR w/ BTreeMap ordering (content/canonical.rs), not dag-cbor;ObjectIdin the store = bare digestDigest)to_multihash_bytes()= spec multihash<code><len><digest>(nessie-backend-core/src/digest.rs:158-190)"<algo>:<hex>"with multiformats namesblake3/sha2-256(digest.rs:71-75)0x1e) and SHA-256 (0x12) first-class — REAPI face pins SHA-256nessie.Tree.v1,nessie.ActionResult.v1,nessie.Attestation.v1)payload_cid,Fingerprint; agent-storecontent_hash[u8; 32]BLAKE3blake3::Hash::to_hexRelated, 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 stamps0x71, 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)
f+ hex; nessieblake3:<hex>↔ multihash(0x1e, hex); bare-digest-hex is this crate'sdigest_hex().FromStralready tolerates non-base32 multibases "as a convenience, not a contract" (README §2).sha256/512codes, without changing whatContentIdmints.So the divergences that remain are policy, not encoding: codec (
0x71vs0x55vs none), text form, which algorithms may be minted vs merely verified, and canonicalization of structured values.Questions to decide
RawContentId(CIDv1,raw0x55, BLAKE3-256, 32) — for byte artifacts (files, chunks, envelope payloads, log lines), alongsideContentIdfor dag-cbor values? kyln'sContentIdwould 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 beChunkLeaf { kind, data: bytes }dag-cbor nodes (feat(chunked-dag): chunked file + directory DAG (sized links) #53's MVP stance), so every CID stays0x71? 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 aboutContentId?).Cid-parsing entry (ContentId::try_from_cid/ anAnyCidenum) that classifies by (codec, hash code):dag-cbor+blake3 → ContentId;raw+blake3 → RawContentId(if adopted); anything else (e.g.raw+sha2-256from REAPI) → verifiable-not-mintable interop value. This is how nessie's SHA-256 world is represented here without this crate ever minting SHA-256.b…. Do we bless explicitto_multibase(Base)/from_multibaseand document the legacy parsers (kyln envelope-hex = base16 sans prefix; nessiealgo:hex) behind a default-offlegacy-formatsfeature with a deprecation path, or refuse them and make consumers convert at their edge?to_canonical_dagcbor+ conformance vectors as the target and let each repo migrate, (b) additionally offer nessie'sTree/ActionResultand kyln'sProvenanceLinkas shapes in this crate (feat(chunked-dag): chunked file + directory DAG (sized links) #53DirNode/FileNodealready covers Tree), so the migration is "depend on the crate" rather than "re-implement"?from_blake3_content_digestdoor. After (1), should it be re-pointed at the raw profile (where a bare content digest is honest) and the0x71variant restricted to callers who know the digest is over canonical dag-cbor?Proposed acceptance
tests/vectors.jsondiscipline, test: golden conformance vectors + Rust↔Python byte-parity gate #12/feat(merkle): conformance vectors freeze structure bytes at rc1 #33/feat(conformance): portable vectors + TS/Dart/Java byte-identity #35): at minimum a kyln envelope-hex ↔RawContentId/Cidround-trip vector and a nessiesha2-256:<hex>↔ multihash vector.content-addressableas its identity dependency and no local hash/CID types.Non-goals
ContentId's frozen profile (BLAKE3-256 / dag-cbor0x71/ 32 bytes / v1) or its presentation contract.