# KCF — kno Canonical Form, v1 # KNO Schema Version: 0.1.0 # Status: Draft # Last Updated: 2026-08-04 # # The platform's canonical normalization algorithm for content hashing: # a deterministic projection from the kno data model to I-JSON, # canonicalized per RFC 8785 (JCS), framed per DSSE PAE, digested with # SHA-256. Closes RFC-018 PROV-04 (the open hash-normalization # question); satisfies the record-layer council ruling's Front 7 # conversion-profile gate (forge #3370); provides the "named canonical # profile" the R10 Reference Registry fact envelope requires. One # profile, multiple consumers. # # WHY A CANONICAL FORM (not raw-bytes hashing): the kno container # doctrine holds packed (`entity.kno`) and unpacked (`entity.kno/`) # to be equivalent representations of the same content — but they have # different YAML bytes, so any byte-derived identity makes hash depend # on representation and the equivalence claim unprovable (today's # `computeContentHash(rawYaml)` defect). KCF canonicalizes the DATA # MODEL, not the file: packed .kno, unpacked directory, API JSON, and # database row yield identical canonical bytes. Canonical bytes are a # derived machine artifact nobody authors or reads (P10 thin # transformation); .kno remains the authoring and projection surface. # # WHY I-JSON AS SUBSTRATE (evaluated, not inherited — operator-ratified # 2026-08-04, forge #3376): a digest is defined over bytes; the # substrate must be byte-deterministic over an enumerable grammar. # I-JSON + JCS is the smallest externally-audited such grammar with # cross-language implementations (JS/Go/Python/Rust/Java + published # vectors) and native fit to the signing ecosystem (DSSE/PAE, C2SP). # The declined alternative — a canonical-YAML profile — would put a # novel canonicalization over YAML's large ambiguous grammar (1.1/1.2 # scalar resolution, anchors/aliases, tags, block/folded/chomping # variants) inside the signature boundary with zero external # corroboration. Parser differentials in crypto-adjacent code are a # known attack class; KCF keeps our novel surface near zero. # # M0 NORMATIVE SCOPE (futro KU5 boundary): act-record shapes (the # messaging service's records and durable activities) + the recursion # and XRI reference rules + the test vectors. Kno-general adoption # (Hive content hashing, container-equivalence enforcement) is the # layer-2 reconciliation memo's TARGET statement — explicitly deferred; # adopting KCF estate-wide is its own migration with its own review. # # ============================================================================= # @kno:manifest # ============================================================================= $schema: kno@0.0.9 id: 01KZ6S0J67BMECP9ZBGTKF2MFH slug: kcf-spec type: spec version: 0.1.0 title: "kno Canonical Form (KCF) v1" purpose: | Define the deterministic projection from the kno data model to a single canonical byte stream and framed digest, so that every record-class entity has exactly one content identity regardless of representation (packed .kno, unpacked directory, API JSON, database row). **The pipeline:** ``` kno data model ──(§2 projection)──▶ I-JSON document ──(§3 JCS/RFC 8785)──▶ canonical_bytes ──(§4 FRAME = PAE(payload_type, canonical_bytes))──▶ framed bytes ──(§4 SHA-256)──▶ record digest "sha256:" ``` The record digest is simultaneously: the revision identity, the RFC-013 layer-2 content hash, and the identifier of the exact bytes signatures sign (council N1.3 — one digest per revision; no parallel hash of any alternate encoding exists). status: draft created: "2026-08-04" authors: - "Possibility Contributors" provenance: origin: id: 01KZ6S0J67BMECP9ZBGTKF2MFH timestamp: "2026-08-04T09:45:00Z" emerged_from: - kno://research/council-research/2026-08-03-communicative-acts-record-layer - kno://research/council-research/2026-06-01-r10-reference-registry-shape - kno://content/rfcs/018-provenance-assertion - kno://content/rfcs/023-communicative-acts tool: ai-assisted-authoring issue: "forge#3376" taxonomy: topics: - canonicalization - content-hashing - record-layer - integrity keywords: - kcf - jcs - i-json - frame - pae - digest - canonical relationships: conforms_to: - xri: "kno://specs/kno-spec" reason: "RFC-001 defines kno@0.0.9 schema" depends_on: - xri: "pspace://rfc:rfc-013-identity-separation" reason: "The record digest IS the layer-2 content-hash identity; XRI reference rules in §5" - xri: "pspace://rfc:rfc-018-provenance-assertion" reason: "Closes PROV-04 — this spec is the canonical normalization algorithm for provenance.content.hash on record-class entities" related_to: - xri: "pspace://rfc:rfc-023-communicative-acts" reason: "First consumer — act records and durable activities canonicalize through KCF (§9.1)" - xri: "kno://specs/act-schema" reason: "The M0 record shape KCF v1 normatively covers" # Public reference knowledge: the profile itself is a four-surface # registry artifact (partners verifying records need it). visibility: public quality: completeness: 0.7 review_status: draft _history: retention: full format: snapshot versions: - version: "0.1.0" date: "2026-08-04" author: "Possibility Contributors" summary: "Initial draft — data-model projection rules, JCS canonicalization, FRAME digest, recursion via child digests, XRI reference rules, fail-shut rejection list, digest labels. M0 scope: act-record shapes; kno-general adoption deferred to the layer-2 memo target." # ============================================================================= # NORMATIVE CONTENT # ============================================================================= content: | ## 1. Definitions and digest label - **KCF v1** — this profile. Versioned and hash-pinned: consumers reference it as `kcf/1` with this spec entity's own content hash. Any change to the rules below is a new profile version; profiles are immutable once published (council Position 3: version entities are immutable; evolution is data). - **Digest label** — digests produced under this profile are algorithm-prefixed strings `sha256:<64 lowercase hex>`. Where a consumer needs the profile made explicit (the R10 registry envelope), the labeled form is `sha256+kcf.v1`. - **Record class** — an entity shape declared (by its schema) to canonicalize through KCF. At M0: act records and durable activities (act-schema). The layer-2 reconciliation memo names the target for kno-general adoption. ## 2. Data-model projection (kno → I-JSON) KCF canonicalizes the **data model** — the typed field tree a kno entity denotes — never the serialized file. The projection rules: 1. **Signed scope only.** The input to canonicalization is the entity's signed-scope fields as declared by its schema (for act records: the fields marked [signed] in act-schema). Service metadata, physical locators, signature sidecars, and envelope fields are outside the scope and MUST NOT appear in the projection. 2. **Maps.** kno mappings project to JSON objects. Member names MUST be unique (duplicates are a §6 rejection — enforced at the parse boundary, before any general-purpose parser silently collapses them). Ordering is irrelevant (JCS sorts). 3. **Sequences** project to JSON arrays, order-preserving. 4. **Strings** MUST be well-formed Unicode (no lone surrogates — §6). NO Unicode normalization is applied (JCS §3.1 note: normalization is the author's concern; the profile hashes what was written). 5. **Booleans** project to JSON `true`/`false`. When authoring surfaces accept YAML, only the YAML 1.2 core-schema forms `true`/`false` denote booleans; the YAML 1.1 forms (`yes`/`no`/`on`/`off`/`y`/`n`) are NOT booleans and arriving as unquoted scalars in a boolean-typed field is a §6 rejection (never a silent coercion — the Norway problem is answered by refusal, not convention). 6. **Numbers.** Only I-JSON-safe numbers project as JSON numbers: finite, and within IEEE-754 double precision with exact representation (integers with |n| ≤ 2^53 − 1). NaN, Infinity, and precision-lossy values are §6 rejections. Integers outside the safe range MUST travel as strings (schema-typed, e.g. `format: bigint-string`). Trailing-zero and exponent forms are normalized by JCS's ECMAScript number serialization. 7. **Null vs absence.** Absence is ALWAYS omission. An explicit `null` where omission is meant is a §6 rejection. A schema MAY declare a field explicitly nullable; only then does `null` project. 8. **Timestamps** project as RFC 3339 UTC strings with a `Z` suffix and no sub-second trailing zeros beyond the precision written. They are strings to KCF; no date parsing occurs in the profile. 9. **Comments are not data.** kno file comments (`#`) never reach the data model and therefore never affect the digest. 10. **YAML intake constraints** (when the authoring surface is YAML): anchors/aliases MUST be fully expanded by the intake parser before projection, and expansion producing reference cycles is a §6 rejection; custom tags are §6 rejections; multi-document streams are §6 rejections. Per the council's Front 7 fail-closed arm, a service MAY structurally disable YAML intake entirely and accept UTF-8 JSON only — KCF is then the enable-gate for YAML. ## 3. Canonicalization (JCS) The projected I-JSON document is serialized per RFC 8785 (JSON Canonicalization Scheme): UTF-8, lexicographically sorted member names (by UTF-16 code unit), ECMAScript number serialization, minimal string escaping, no insignificant whitespace. The output is `canonical_bytes`. Canonicalization happens ONCE, at acceptance. Stored canonical bytes are authoritative (the DSSE verified-bytes rule); verification and consumption operate on stored bytes; re-canonicalization is a diagnostic cross-check only (council N1.2). ## 4. Framing and digest ``` FRAME = PAE(UTF8(payload_type), canonical_bytes) record_digest = "sha256:" + lowercase_hex(SHA-256(FRAME)) ``` where `PAE` is DSSE Pre-Authentication Encoding: ``` PAE(type, body) = "DSSEv1" || SP || LEN(type) || SP || type || SP || LEN(body) || SP || body ``` (`LEN` = ASCII decimal byte length; `SP` = 0x20; `||` = byte concatenation) and `payload_type` is the byte-exact registry-pinned schema identifier including version (e.g. `pspace://schemas/act-schema/0.1`) — no aliases, no generic media types. Signatures sign the same FRAME. One digest per revision. **Distinct digest scopes** (council N1.4): the record digest (over FRAME), a body digest (over body bytes — exact bytes for the internal class; ciphertext per PBEP for the private class when it ships), and the log leaf hash (over LCP leaf framing) are distinct, domain-separated constructions even though all use SHA-256. They are never interchangeable. ## 5. Recursion and references 1. **Contained entities canonicalize independently.** A record-class entity that contains another record-class entity (`_contains`, packed containers) does NOT inline the child's fields into its canonical form: the child appears as its own record digest (Merkle-shaped — the parent's digest commits to the child's). This is what makes the container doctrine's packed ↔ unpacked equivalence provable: both representations project to the same parent form referencing the same child digests. 2. **Entity references carry `{id, digest}` pairs** inside the signed scope: the identity XRI (`pspace:///`) plus the referenced revision's record digest. Verifiers RECOMPUTE digests from the referenced bytes; declared digests are never trusted (council Position 2). 3. **Names never appear in verification paths.** Named/slug XRIs (`pspace://:`) are display metadata and MUST NOT appear in signed-scope reference fields — RFC-013's mutable layer, enforced by construction. A signed field whose schema types it as a reference and whose value is a slug-form XRI is a §6 rejection. 4. **Bodies are not contained entities.** A body is a content- addressed byte stream referenced by `body_ref { class, digest, media_type }` — outside the signed scope, hashed as exact bytes (KAML bodies included; no conversion, no canonicalization). ## 6. Fail-shut rejection list (parse boundary) A record submitted for acceptance is REJECTED — before any digest is computed — if any of the following occur: | # | Rejection | Source rule | |---|-----------|-------------| | R1 | Duplicate member names anywhere in the document | §2.2 | | R2 | Lone surrogates / ill-formed Unicode in any string | §2.4 | | R3 | Non-I-JSON numbers (NaN, Infinity, precision loss, unsafe integers as numbers) | §2.6 | | R4 | Explicit `null` in a field not declared nullable | §2.7 | | R5 | Undeclared fields under a closed profile (authority-bearing field sets validate closed) | council Position 3 | | R6 | Unpinned schema/context references (`payload_type` or `schema_hash` missing/non-exact) | council N1.1 | | R7 | YAML 1.1 boolean forms in boolean-typed fields; custom tags; unexpanded or cyclic anchors; multi-document streams | §2.5, §2.10 | | R8 | Slug-form XRI in a signed-scope reference field | §5.3 | Rejections produce content-free findings (the rejected bytes are never durably stored — coordinate with the acceptance pipeline's secret-scan ordering: scan precedes digest; rejection precedes retention). ## 7. Test vectors Cross-language test vectors are a REQUIRED companion artifact (`specs/kcf-vectors/` — vector files pairing input data models with expected canonical_bytes, FRAME bytes, and record digests, plus rejection cases R1–R8). An implementation conforms iff it reproduces every vector. Vectors are registry artifacts versioned with the profile. ## 8. What KCF v1 does NOT cover (deferred) - Kno-general / Hive content hashing (`computeContentHash` raw-YAML replacement) — the layer-2 reconciliation memo names this target; migration is its own reviewed effort. - Container-equivalence enforcement tooling (pack/unpack proof). - The `private` body class encryption profile (PBEP v1 — separate pre-pour artifact with external review). - Non-JSON payload envelopes beyond body indirection (council N1.8's typed canonicalization seam — future profile version).