Agentic-emission schema versioning — additive-only evolution + frozen canonical projection as integrity anchor
title: Agentic-emission schema versioning — additive-only evolution + frozen canonical projection as integrity anchor date: 2026-05-18 status: accepted outranks: [] mr_compliance: [MR-4, MR-7] relates_to:
- docs/specs/2026-05-18-sp2-cairnet-production-erid-design.md
- docs/plans/2026-05-18-sp2-cairnet-production-erid-implementation.md
- docs/decisions/2026-05-18-cairnet-bridge-principal-binding.md
Agentic-emission schema versioning — additive-only evolution + frozen canonical projection as integrity anchor
Section titled “Agentic-emission schema versioning — additive-only evolution + frozen canonical projection as integrity anchor”Context
Section titled “Context”SP-2 introduces an append-only integrity ledger over CAIRNET agentic-emission stones. The ledger’s value depends on a hash chain that stays valid as the emission schema evolves. Without a stated evolution rule, any future field addition to a stone risks silently invalidating every historical hash, destroying the chain’s audit value. This ADR fixes the schema-evolution contract and names the exact version-stability anchor that enforces it.
Decision (append-only history)
Section titled “Decision (append-only history)”-
2026-05-18 — agentic-emission schema evolution is additive-only.
- New fields are additive-only. A change that adds a field is a minor-version bump.
- No field may be removed or repurposed within a major version. Repurposing (same key, changed meaning/type) is a breaking change, not additive.
- Readers MUST tolerate unknown fields (forward-compatibility): an older reader encountering a newer minor-version stone ignores fields it does not recognise rather than failing.
-
2026-05-18 — the SP-2 ledger canonical projection is the version-stability anchor. The anchor is the fixed projection in
pebble/src/pebble/domain/stone_canonical.py(_PROJECTION, in the devarno-cloud/pebble repo). Its frozen canonical (right-side) key set is exactly, and only:stone_idagent_idstoneTypecreatedAtpayload
content_hashhashes ONLY this projection (sorted-key, compact, UTF-8 canonical JSON). The mixed casing —snake_casestone_id/agent_idalongsidecamelCasestoneType/createdAt— is the deliberately frozen schema, not an oversight: the right-side names are byte-frozen by SP-2 spec §3.3 and this ADR. The persisted-stone source keys (left side of_PROJECTION) may be re-mapped, but only as a code change to the ledger normalisation layer, which is itself part of the frozen contract boundary — not a free or independent change. Concretely: the real_stone_to_dictemitsagentId/content(camelCase); the ledger normalisation layer mapsagentId→agent_idandcontent→payloadbefore the projection runs, andstone_canonical.canonical_projectionhard-raises if any required source key is missing. The canonical right-side names may change only via a superseding ADR (MR-7) that also issues a new projection version. -
2026-05-18 — the
unverified-legacyboundary is by deliberate design. Pre-SP-2 history is forward-only verifiable via lazy legacy-genesis: the ledger anchors from the first SP-2 emission forward, and pre-SP-2 stones are admitted as anunverified-legacygenesis boundary rather than retro-hashed. This is a deliberate design choice, NOT a defect — pre-SP-2 stones predate the canonical projection contract and cannot be hashed under a contract that did not exist when they were written.
Consequences
Section titled “Consequences”- A v2 ADDITIVE field cannot invalidate v1 chains. Because
content_hashcovers only the frozen 5-key projection, a new emission field is invisible to the hash. This is exactly what the Pebble T1 canonical projection plus its additive-field-immunity unit test guarantee. - A BREAKING change (field removal, repurpose, or projection key
rename/casing change) requires: a major version bump, a
migration ADR, and a NEW projection version — i.e. a
superseding ADR per MR-7. Concretely, a new projection version means
a new named projection constant plus a parallel hash function in
stone_canonical.py(e.g._PROJECTION_V2/canonical_content_hash_v2), not an in-place edit of the existing_PROJECTION/canonical_content_hash(so v1 chains stay independently verifiable). It is never a silent in-place edit ofstone_canonical.py. - The canonical projection and
content_hashare computed only in Pebble (stone_canonical.pycanonical_content_hash). The CLI does not independently re-derivecontent_hashfrom raw stone fields:cli/src/cairnet/chain.tsrowHashcomputes the ledger ROW hashsha256(content_hash ‖ prev_hash ‖ op ‖ ts ‖ actor), andrecomputeAndVerify(SP-2 T7) checks chain linkage and recomputes the row hash over the committedcontent_hashas received from Pebble’s/events— it trusts thatcontent_hashas authoritative. The cross-repo byte-identity invariant SP-2 actually depends on is the ROW hash construction (thesha256(content_hash ‖ prev_hash ‖ op ‖ ts ‖ actor)join — U+2016 separator, exact field order) being identical between Pebblestone_ledger._row_hashand CLIchain.ts rowHash, not a duplicated canonical projection. Independent CLI re-derivation ofcontent_hashfrom raw stone payloads is deliberately out of scope for SP-2 (consistent with the chain.ts scope comment); it is not an unfulfilled obligation. The additive-only rule keeps the Pebble-side projection stable so this committed-content_hashtrust remains sound without coordinated lock-step releases. - Readers tolerating unknown fields (Decision §1) means the concrete readers — the dashboard, the rocky-status Tier-1 head-check, and the cairnet-verify Tier-2 deep verifier — degrade gracefully across minor versions; only a major version requires a coordinated upgrade.
Status
Section titled “Status”Accepted. The frozen canonical projection lives in
pebble/src/pebble/domain/stone_canonical.py (_PROJECTION, in the
devarno-cloud/pebble repo); the SP-2 cross-repo seam the CLI shares
with Pebble is the row-hash construction in cli/src/cairnet/chain.ts
(rowHash, SP-2 T7), not the projection itself. Cross-references in
frontmatter relates_to.