The C1 family encoding profile

The bytes all three projects must agree on.

Akson, kovee and byom are separate systems with separate stores and separate operators, and they still have to accept, canonicalize and digest the same bytes identically. The profile is the one place that is decided. Byom hosts it; the other two bind to this text through a lock manifest rather than to a copy, so there is exactly one source and no second copy to drift.

83 vector files 111 cases two rederivers RFC 8785 · RFC 9457 · RFC 7493

One profile, three projects

The profile pins six families of behaviour, one directory of vectors each. A vector is a JSON file naming its input and its expected output — canonical bytes, a digest, an error class — and it is frozen once merged.

FamilyCasesWhat it pins
digest-class/58The six classes, their wire shape, and the complete ordered substitution matrix.
idempotency/9The idempotency-domain derivations, byom's and kovee's side by side, pinned as distinct.
ijson/20Strict I-JSON acceptance: the ordered check list, its error classes, and the caps.
jcs/7RFC 8785 canonical bytes, including UTF-16 key sorting and ECMAScript number form.
privacy/4The PrivacyAccessRecord preimage and the chain link, including genesis absence.
problem/13The RFC 9457 problem shape, its closed kind enum, and the exact type prefix.

A complete vector — jcs/basic-ordering:

family-vectors/jcs/basic-ordering.json
{
  "name": "jcs/basic-ordering",
  "description": "object keys serialize in sorted order regardless of input order",
  "input": { "value": { "b": 2, "a": 1, "c": [3, "x"] } },
  "expected": {
    "canonical": "{\"a\":1,\"b\":2,\"c\":[3,\"x\"]}",
    "sha256_hex": "7f26f35fd8404944dd9a2441528c810b6c55fb2ae7b569b0438d309d81806acf"
  }
}

Strict I-JSON acceptance

A request body is a single UTF-8 I-JSON text within hard caps, checked in this order. The first failure names the error class, and the order-3 classes surface in token order during one parse — an early duplicate key beats a later unsafe number.

OrderCheckError class
1at most 262,144 bytes (the 256 KiB request cap)oversize
2valid UTF-8invalid-utf8
3parses as exactly one JSON textsyntax, trailing-data
3no duplicate member names, at any depth (RFC 7493)duplicate
3no $domain member name, at any depthreserved-domain-collision
3integers within ±(253 − 1)unsafe-integer
3no NaN / Infinity literalsnon-finite
3floats finite; integer-valued floats within the safe rangeunsafe-number
4no unpaired surrogates after escape decodingunpaired-surrogate
5container nesting depth at most 64over-depth
6at most 65,536 JSON values per documentover-nodes

Within a single member-name token, the reserved-name check precedes the duplicate check. Implementations must process tokens iteratively: input nested thousands of levels deep — bounded only by the size cap — may never crash a rederiver, and the depth cap is enforced after the scan rather than by running out of stack. A vector pins exactly that pathological case. Responses follow the same rules under the 1,048,576-byte cap, and a vector opts into that context explicitly.

Canonical bytes, and the $domain tag

Canonical bytes are RFC 8785 JCS over exactly the value space the acceptance rules admit: object keys sorted by UTF-16 code units, numbers in ECMAScript minimal form, short escapes plus \u00xx for the remaining C0 controls, everything else literal UTF-8. Key sorting by UTF-16 code units is not the same as UTF-8 byte order — an astral key sorts as its surrogate pair — and a vector pins that difference rather than leaving it to be discovered.

Type-tagged bytes inject a reserved top-level member and then canonicalize:

the type tag construction
tagged(tag, obj) = JCS(obj ∪ {"$domain": tag})   # obj must not carry $domain

$domain (0x24) sorts before every letter, so the tag always leads the canonical bytes. It is reserved at every depth of wire bodies and fails closed on collision, which is why it can never be smuggled in as data. This mirrors kovee's own $domain envelope member so the tag namespace is shared while the byte layouts stay distinct.

RFC 9457 problems, and deliberate non-substitutability

Byom's problem object requires type, title and kind; type is exactly https://byom.dev/problems/ + kind. Extension members are allowed only under reverse-domain names, and they carry no authority.

Byom and kovee both emit RFC 9457 problems and their conventions diverge on purpose. A validator that accepts the other project's spelling or namespace fails these vectors' intent.

byom (BPP)kovee (KCP)
kind casingsnake_case — stale_revisionkebab-case — stale-revision
where the kind livesa separate required kind member and the type suffixonly inside the type URN
typehttps://byom.dev/problems/<kind>urn:kovee:error:<kind>
statusoptional; a JSON integer in 400–599 when presenta per-kind table value

There is no translation table, and no value from one convention ever satisfies a field of the other, even for semantically matching errors.

The six digest classes

Every digest field on the wire is a typed DigestRef with a closed member set, never an unlabelled hash:

the DigestRef wire shape
{ "class": "local_erasure_safe", "algorithm": "hmac-sha-256",
  "key_ref": "society-key:soc-garden/object:engram-42", "value_hex": "…" }

Exactly class, algorithm, value_hex, plus key_ref — and nothing else. Class and algorithm pairing is closed; key_ref is required exactly for the keyed classes and forbidden for the public ones; value_hex is exactly 64 lowercase hex characters. Raw keys, per-object secrets and salts are never part of a ref, and a member whose name looks like key material fails closed.

ClassAlgorithmkey_refWhat it is for
structural_publicsha-256forbiddenKnowingly non-sensitive, non-erasable protocol or schema bytes only.
portable_publicsha-256forbiddenContent whose owner explicitly accepted a durable, publicly dictionary-testable identifier — and the class the A8 rule demands of any digest one protocol asks the other for.
disclosed_partysha-256forbiddenBytes already disclosed to named recipients, always carrying the external-copy obligation.
ciphertext_publicsha-256forbiddenSealed blob bytes only — never a commitment to low-entropy plaintext.
local_erasure_safehmac-sha-256requiredOrdinary erasable local content and every authority subject, under a random per-object secret.
scope_erasure_safehmac-sha-256requiredShared-key index and chain constructions, under a per-scope key.

The classes are never interchangeable. A well-constructed digest of the wrong class is digest_class_mismatch even when the 32-byte value spaces coincide, and the vectors pin the complete ordered substitution matrix with values that are arithmetically correct under the class they are offered as — so every rejection is proven to be a typing decision rather than a wrong-bytes accident.

The two keyed classes are mutually non-substitutable for a concrete reason. Erasing one object must destroy exactly that object's verifiability, so an authority subject takes a per-object secret. Verifying an index or a chain must survive the erasure of one member, so the index takes a per-scope key. Swapping them silently changes what "delete this" means.

Acceptance is ordered, and the order is the point

A digest offered where a schema requires class R is checked in ten steps: typed at all, carries no key material, no unknown member, known class, class/algorithm pairing, key_ref presence, 64-hex value, per-class construction rule, class equals R, and finally value re-derivation where the verifier holds the content and key. Wire-shape failures are reported before construction failures, and construction failures before the generic mismatch — so an error message names the actual defect. A verifier without the key accepts a keyed ref as well-typed but unverified; that is the honest limit of keyed digests, and it is written into the rule rather than glossed.

The A8 cross-boundary rule

This is the profile's newest rule and the only one found by running code rather than by review. Wiring kovee's episode pipeline to a real byomd found that byom was demanding four keyed digests from kovee — values kovee could only echo as opaque blobs, one of which was obtainable in practice only by reading byom's database — and that a fifth digest byom required at placement was published nowhere at all.

The rule, both halves

A digest one protocol demands from the other MUST be portable_public, taken over a frozen cross-boundary fragment whose members both sides hold. Crossing the boundary is the durable-identifier disclosure that class requires, and because the fragment is never the owner's whole erasable record, the ban on public hashes over erasable content is untouched.

The converse is equally normative: a digest the owner recomputes from its own state keeps local_erasure_safe and is therefore never a request member at all. Asking a counterparty to echo a value you compute yourself proves only that your own value equals itself, while costing the counterparty per-object erasure storage for nothing.

A portable_public field does not mean "trust what the peer sent". The receiving side rebuilds the frozen fragment from its own committed state and refuses a value that does not re-derive. A digest that is merely stored because a caller sent it is asserted, not verified — even when its class is right.

Byom's applied instances each name a $domain tag and an exact, closed member list; widening or narrowing that list changes the digest and is a wire change on both sides.

Frozen fragmentWho derives it, and why it has to be portable
bpp-resource-allocation-binding-v0 The allocation's cross-boundary identity. Kovee asserts which allocation revision it placed, so it must stay a request member — and byom now publishes a second, fragment-scoped digest on the episode_request result so the value can actually be derived. Mutable members and byom-minted internal ids are deliberately out: the pin names an identity that does not change under it.
bpp-mandate-use-binding-v0 On the consumption receipt. The consumer never supplied a mandate use and holds no per-object secret, so byom's keyed record commitment could only ever be echoed. That commitment is unchanged, stays keyed, and is now demanded from nobody.
bpp-execution-consumption-receipt-binding-v0 The receipt's own binding digest — the one value a broker relies on, derived by the consumer from the receipt it just received. Byom keeps no keyed twin, because nothing inside byom compares this value; it exists to cross the boundary.
bpp-parent-budget-fragment-v0 The parent budget worst case a subordinate reservation must stay under, pinned by a cross-repo vector so the check goes red on whichever side moves.
bpp-provider-context-source-v0 The provider-context source fields byom composes for kovee's context manifest.
kovee-host-effect-binding-v1 Kovee's tag, not byom's: a nine-member host effect fragment byom rebuilds from its own committed act and refuses if it does not re-derive. Byom's expectation is pinned against kovee's own recorded vector rather than against byom's code, so the cross-repo pin goes red on whichever side moves its domain tag.
Recorded, not silently changed

Two further optional pairs on the claim path — kovee's context-assembly digest and its provider-context-manifest digest — name kovee objects byom cannot recompute, so the rule would move them too. They fell outside the ratified decision and are recorded for the family-contract owner rather than re-classed unilaterally. This is what the rule looks like when it is being followed rather than announced.

Idempotency domains

Byom's idempotency domain is {actor_binding_digest, operation, endpoint_incarnation, society_id, society_recovery_epoch, idempotency_key}, digested as HMAC-SHA-256(per-society index key, tagged("bpp-idempotency-domain-v1", domain)) and emitted as a scope_erasure_safe ref.

It is keyed because the domain embeds an actor binding and a caller-chosen key — erasable, low-entropy material that must never carry a public hash. It is scope-keyed rather than object-keyed because the index key is one key across every entry: destroying it erases offline verifiability of the whole index, never a single entry. Saying so is the whole point of having six classes instead of one.

Kovee's command idempotency digest, by contrast, is a public structural SHA-256 over non-sensitive command material, and akson separates domains by DSSE payload type in the pre-authentication encoding rather than in JSON at all. All three are correct for their store, and a vector pins that no value from one ever satisfies a field of another — even byte-equal.

The privacy access chain

Allowed and denied sensitive reads both append to a hash-chained access record. The preimage members are exact and all required — society, access sequence, event id, endpoint incarnation, recovery epoch, actor binding, operation, purpose, query or scope digest, result object count, result bytes, outcome, dependency digest, occurred-at — plus the chain link, which at genesis is wholly absent: no member, never a null-valued pseudo-ref.

The record's own self-referential digest is excluded from its preimage, and a record that offers it inside the preimage fails closed. Both the record digest and the chain link are scope_erasure_safe under the chain key.

The release rule has two failure arms and neither of them serves bytes: an outcome other than allowed releases nothing and still chains a record; a failed journal commit releases nothing at all. Records carry cardinality and byte counts, never result plaintext.

Two independent rederivers

Every expected value in the corpus is re-derived from scratch by two programs that share no code and no dependencies — one Python stdlib only, one Node builtins only. Both go nonzero on any mismatch and on an empty vector tree, so a deleted corpus is a failure rather than a pass.

re-derive everything from the repository root
python3 family-vectors/xcheck.py           # exit 0 = every `expected` re-derived
node    family-vectors/tscheck/check.mjs   # the same, in a second language

Kovee and akson consume this directory through the family lock: their CI checks out the byom commit the manifest pins, asserts that the checked-out commit is that commit, and runs xcheck.py from that tree. A local copy alone is never provenance.