The value model
One value, one address
Everything in Ring 0 rests on one promise: a CX value has the same identity and the same meaning whether it is stored, transmitted, queried, executed, replicated, rendered, or handled by an agent. Identity is a content address — the SHA-256 of the value's strict canonical text — and no surface, protocol, or internal representation is allowed to present a different address or a different value for the same canonical bytes.
That sentence is a contract, not a description. A store key, a wire frame, a query result, a cached computation, and a signed approval all name values by the same address, so they can be compared, deduplicated, and audited against each other without translation. When two systems disagree about a value, the address settles it.
$ cx hash order.cx
sha2-256:f09867e376c94f2f… # the same address on every machine,
# every OS, every binding
The address is computed over strict canonical text — the
one normalized spelling of a value (attribute order, whitespace,
number formatting, and quoting all pinned; see identity).
Reformatting a document does not move its address; changing one
field does. cx eq is exactly \"do the canonical bytes
agree\", and cx hash is exactly \"name those bytes\".
A surface that cannot honor the invariant for a value class must refuse loudly rather than improvise. Two classes refuse today by design: closures (they capture an environment no canonical text can carry) and secret-bearing values (hashing the plaintext would turn every address into a secret-confirmation oracle; hashing the redacted form would give two secrets one address). Refusal is a typed error — never a silent fallback address. Unbounded iterators (an address that depends on consumption state is not an identity) are today force-materialized before addressing; the typed refusal for them is approved surface that lands with the next identity revision.
Code is data — and has the same identity
CX is homoiconic: a program is a CX document. A [?for]
directive is an element whose name happens to start with ? —
attributes, body, closing bracket — structurally identical to
[user] or [order]. There is no separate syntax tree for
code; the reader emits the same element values the rest of the
system manipulates.
For Ring 0 this has a precise consequence: a Ring-0 consumer reads a program the way an XML parser reads XSLT — as an inert tree. It can hash it, diff it, validate it, convert it, and hand it on, with no means of running it (there is no evaluator in Ring 0). Identity therefore belongs to Ring 0: a definition hashes to the same bytes everywhere, whether or not the reader could execute it.
- One toolchain. Parser, formatter, linter, LSP,
tree-sitter grammar — one each, used identically on code
and data.
cx fmtformats both;cx lintchecks both; the same CXPath vocabulary that queries a user database queries the program that processes it. - One identity contract. A program has a content address just like a document does. Build caches key on the hash of canonicalized program text; if the program is semantically unchanged, the cache hits even when the layout differs.
- One projection table. Every CX value — code or data — projects to JSON, XML, YAML, TOML, Markdown, and the binary forms. Shipping code alongside data over the wire is shipping one more document.
- Macros are unnecessary. A directive is data; you
build or rewrite it with the same element construction and
[?modify]machinery you use on data. Deploy-time program rewrite is a path predicate plus an action — no string substitution, no template engine.
# A program IS a document — the same tools apply unchanged.
$ cat prog.cx
[?for [in $x (1, 2)] [yield [n $x]]]
# Identity works on code exactly as on data:
$ cx hash prog.cx
sha2-256:b9f0fe8ea88e5cbf…
# Reformat-stable: a relaid-out spelling of the same program
# hashes to the same address.
$ cx hash prog-reformatted.cx
sha2-256:b9f0fe8ea88e5cbf…
The four senses of identity
The word \"identity\" is overloaded in every data system. CX gives each sense its own name, and a specification (or this guide) that says \"identity\" without qualification always means the content address.
| Sense | Where | What it is |
|---|---|---|
| Document ID / IDREF | #name, [ref @name] |
intra-document naming — authored data; participates in the canonical bytes; NOT a content address |
| Content address | cx hash, store keys, claims |
THE identity: sha2-256:<hex> over strict canonical text |
| Canonical equality | cx eq, [$cx:equal] |
a predicate — true iff the canonical bytes agree; not an address |
| Collection equality | the value model (cxdm §5) | per-kind comparison semantics (order and dedup rules inside values); never an identity claim about documents |
Additional identity tiers exist above the content address — a code-equivalence tier for definitions and a computation address for cached results (see computation-identity in Ring 1) — but they are additional, never substitutes. Trust decisions bind to the content address of the exact text.
What rides with a value — the three lanes
Not everything about a value belongs in the value. CX admits exactly three ways to attach information to one, and the list is closed — no fourth lane, no intrinsic envelope under any name.
| Lane | Mechanism | Identity? | Use for |
|---|---|---|---|
| 1 — inert side-band | [?meta] — rides with the value |
excluded — annotating never moves the address | display hints, tooling notes, PII flags |
| 2 — detached claims | hash-keyed values ABOUT an address | each claim has its own address | provenance, approvals, type bindings, lineage |
| 3 — event envelopes | journal entries wrapping payloads verbatim | envelope hashed with its payload | attribution: who did what, when, in which stream |
The lane discipline is what keeps identity honest. Two records
that differ only in a [?meta] annotation are the same
value — same address, same cache hits, same approvals. Two
records that differ in an effective date are different
values, which is why validity dates are payload data and not
metadata (see bitemporal in Ring 2). And a claim about a
value — who produced it, who approved it, what schema it
satisfied — is a first-class value of its own, keyed by the
subject's address, so it can be signed, stored, and audited
without touching the subject.
Expression identity
Because programs are documents, expressions have addresses too.
[?quote] returns the expression as a value — after hole
substitution, exactly what would evaluate — and that value's
canonical text is the expression's identity. One expression,
one address, wherever it was authored and however it was
formatted.
[?quote [total-due
[$sum [$map $lines [?fn ($l) [* $l@qty $l@unit-price]]]]
[tax-for $region]]]
[; one Tier-1 address for this pricing expression — the same
address in the author's editor, the store, and the approval
that binds to it. ]
Expression identity is name-sensitive by design: $x and
$y are different expressions with different addresses.
(Alpha-normalized equivalence — treating $x and $y as the
same binder — exists as an additional tier for cache sharing,
never as a redefinition of identity.) This is what makes
address-bound approval workable: an agent proposes a concrete
expression, a principal approves that address, and no
rewording, re-parameterization, or reformatting can ride the
approval (see commands-effects in Ring 1).
Type identity
A type's identity is a pair: the element name plus the content hash of the schema text that defines it. Two parties adopt a type by exchanging that pair — nominal typing with zero registry, zero coordination service.
("order", sha2-256:9c1f0e442ab8...)
# a 3PL and a merchant agree on what an order IS by
# exchanging these two tokens out-of-band
The pair is primarily recoverable by computation — the name
comes from the value, the hash from the schema in force, so
there are no extra bytes to desync. When a type identity must
be asserted (at a trust boundary), it travels as a detached
Lane-2 [type-binding] claim, and asserting it obliges
fail-closed validation. A schema document that anchors a type
identity declares exactly one type, so editing one type can
never silently move another's identity. Schema revisions are
distinct type identities — the link between them is explicit
lineage, covered in schema-evolution.
Version identity
Values are immutable, so a value is never \"at version 17\" — a name is. Version identity belongs to refs: named pointers whose advance history is journal-backed, dense, and gap-free. \"v17\" means: the 17th advance of this ref.
Every advance is an act and every act appears in the history —
including force-moves and re-pointing a ref at its current
target. Racing writers resolve through one conflict
vocabulary: a compare-and-set that states its expectation
either as the prior address (expect=<address>, with \"\"
meaning must-not-exist) or as a position (expect-pos=N), and
the loser receives one well-known conflict error, locally and
over the wire.
No version fields on values. A document carrying
version=17 as authored data is domain data — it is not the
value's version, and nothing in the substrate reads it as one.
The version lives in the ref history, where it is auditable;
the value lives at its address, where it is immutable. The
store's use of refs and histories is covered in store.