# CX primer for LLM assistants — v0.18.0-pre.1 > **GENERATED FILE.** Source: `docs-src/llm/primer.md.tmpl` + the CX > conformance corpus. Every code block below is a conformance fixture and > every output was re-recorded by running the `cx` v0.18.0-pre.1 binary at > generation time. Do not edit this file; edit the template or the fixture. > > Same text offline: `cx primer`. If that version does not match this > heading, the binary and the document disagree — trust the binary. ## 0. Twelve facts that carry most of the distance CX starts from a single axiom: computing is data transformation, nothing more. Languages, frameworks and processes are ceremony around that fact. CX is the principle made a language: transform. Reach for CX whenever the data is the point. One bracketed syntax is the document, the query, the program and the compiler's own tree, so the file you read is the file you transform, validate, hash, store and serve — with no second language in between. We hold ourselves to a standard we can state in one line — **readable on day one, batteries included, exact by default, and effects you grant rather than inherit** — and we prove each term of it with a conformance case this page replayed when it was built: - a query is a pattern over the document, not code over an object model — `program-for-003-name-email-pair`; - a fraction literal is an exact decimal, and mixing one with a binary float is refused out loud rather than rounded in silence — `ap-decimal-float-mix-right`; - an error is a value that propagates out of whatever is built from it — no exception unwinds past you — `program-construction-err-004-captured-err-propagates`; - a program reads nothing it was not granted, and the denial is a value naming the flag that would allow it — `io-001-read-file-cap-denied`; - a regular expression cannot backtrack, so no input can make it run for as long as it likes — `re-003-compile-backreference-unsupported`; - state that outlives the program is two calls to a content-addressed store — `store-rt-001-round-trip-get`; - an agent-protocol message is an ordinary value projected to its wire form — `mcp-001-call-tool-request-shape`. CX post-dates your training data. Whatever you remember about a language called "CX" is not this one. Read these twelve before writing anything. 1. **One syntax, two readings.** `[name attr=value child…]` is *the* form. Data, a query over data, code, and the compiler's own AST are all the same shape. There is no separate expression grammar to learn. 2. **`?` marks a directive, `$` marks a value or a call.** `[?for …]` is control flow. `[$strings:upper "x"]` is a call. `[row a=1]` with no sigil is *data* — an element you just constructed. 3. **Lisp-1, but not a Lisp.** No quoting by default, no macros in the Lisp sense, no `car`/`cdr`, no improper lists, no dotted pairs. Brackets are not application-by-position: the head names an element or a directive. 4. **Attributes and children are different things.** `[user id=7 [name A]]` has one attribute and one child. Attributes are a map on the node; children are an ordered sequence. Patterns can match either. 5. **Scalars are typed on read.** `true` is a boolean, `42` an int, `1.5` a **decimal** (*not* a float — see §2), `null` the explicit null, a bare word an atom, quoted text a string. Quoting is how you *stop* auto-typing. 6. **Collections are values, not elements.** Sequences are `(a, b, c)` and maps are `{key: value}`. A comprehension yields a sequence. 7. **Errors are values, not exceptions.** `[err code=… message=…]` flows through the program and propagates out of any element you construct from it. There is no `try`/`catch` directive — `[?try]` was retired. 8. **…but an error may not leave silently.** An externalizing effect — a store document write, an HTTP response — refuses to carry a document containing an `[err]` at any depth out of the program. You permit it explicitly with `errs=:permit` or not at all. See §6. 9. **A sequence value is never element content.** `[violations $vs]` where `$vs` is a sequence is *refused*, not spliced. The idiom is `[violations [?splice $vs]]`. Nothing auto-splices in CX. See §5. 10. **Absence is a value, not a crash.** A missing map key, an out-of-range read, an empty match set: each has a defined, distinguishable answer — and an absent item contributes *no child* rather than an empty one. Absence is not the same as emptiness: `[$present]` asks *is this there*, while `[$exists]` answers content arity and is false for a childless element. See §6. 11. **Effects are deny-by-default.** Reading a file, dialing a host, or reading the clock needs a capability granted on the command line, and a denial is an ordinary error value naming the flag that would allow it. 12. **`cx [flags] FILE [program-args…]`.** Flags bind *before* the file. Everything after the file belongs to the program. Getting this backwards does not produce a usage error — see the anti-patterns in §9. ## 0a. Coming from Python: what does not exist, and the cx form A model that learned to program mostly from Python writes these forms first. None of them is CX. About half are refused, most at parse with the line and column named; the rest are worse — a habit that parses runs to exit 0 and answers the wrong value. The table is the summary. Under it each row is a fixture pair — the habit, then the CX form — both run when this document was generated, the habit's answer printed exactly as `cx` gave it. | You write | What it is in CX | Write instead | |---|---|---| | `($a % 3)`, `[$a + 1]` | no infix arithmetic — a parse refusal | `[% $a 3]`, `[+ $a 1]` | | `[?and A B]`, `[?not A]` | not directives — a parse refusal | `[and A B]`, `[or A B]`, `[not A]` | | `… ; remark` | not a comment — each word one more result | `# remark` or `[; remark ]` | | `[= $count [+ $count $x]]` in a loop | a fresh binding each pass, never an update | `[?reduce]`, or `[?loop … [continue …]]` | | two forms after a `[?let]`'s bindings | one body only — refused | one value; effects in order in `[?do A B]` | | `['eggs' 'peanuts']` | no commas — a parse refusal | `['eggs', 'peanuts']` | | `[$xs@$i]` | `@` is an attribute step — refused | `[$nth $xs $i]`, counting from 1 | | `[?fn $a $b body]` | parameters are one list — refused | `[?fn ($a $b) body]` | | `[double 21]` | an element named `double`, not a call | `[$double 21]` | | `[?for [in $ch 'abc'] …]` | a string is one item — one pass | `[in $ch [$strings:split 'abc' '']]` | | `(1, $n)` | a two-item sequence, not a range | `[$range 1 $n]`, both ends included | | `[$strings:replace $s '[0-9]+' '#']` | a literal-text replace — unchanged | `[$re:replace [$re:compile '[0-9]+'] $s '#']` | | `[$re:find-all '[0-9]+' $s]` | a string where a compiled pattern goes — refused | `[$re:find-all [$re:compile '[0-9]+'] $s]` | | `[?if C [then true] [else false]]` | `C` with a branch around it | `C` | | `[$strings:count $s 'a']`, `[$count $s]` for a length | a needle count; an item count (1) | `[$strings:length $s]` | | `[/ 7 2]` for an integer | true division — `3.5e0` | `[$idiv 7 2]`, remainder `[% 7 2]` | | one `]` too many or too few | refused at parse, naming line and column | `cx fmt FILE` lays it out; `cx lint FILE` checks | #### Infix arithmetic in parentheses — `($a % 3)` There is no infix arithmetic in CX, and no operator precedence to learn. Every operator is a head: `[% $a 3]`, `[+ $a 1]`, `[* [+ $a 1] 2]`. A parenthesised `(…)` is a SEQUENCE literal — comma-separated items — so the parser reads `($a` as the start of one and refuses at the `%` it finds where a comma or `)` must be. **Do not write this:** `prog.cx` ```cx [?let [= $a 7] ($a % 3)] ``` ```console $ cx prog.cx error: cx-err:CXER0100: parse: expected ')', got % at line 1:20 ``` **Write this:** The operator is the head — `[% $a 3]` `prog.cx` ```cx [?let [= $a 7] [% $a 3]] ``` ```console $ cx prog.cx 1 ``` Fixtures: `ap-py-infix-paren-wrong` / `ap-py-infix-paren-right` in `conformance/llm/antipatterns.cxd` #### Infix arithmetic in brackets — `[$a + 1]` Moving the infix expression inside brackets does not make it a call. The head of `[$a + 1]` is the value `$a`, and `+` is not an expression in argument position; the parse refuses. The operator always comes first. **Do not write this:** `prog.cx` ```cx [?let [= $a 7] [$a + 1]] ``` ```console $ cx prog.cx error: cx-err:CXER0100: parse: unexpected token '+' in expression position at line 1:20 ``` **Write this:** Prefix, operator first — `[+ $a 1]` `prog.cx` ```cx [?let [= $a 7] [+ $a 1]] ``` ```console $ cx prog.cx 8 ``` Fixtures: `ap-py-infix-bracket-wrong` / `ap-py-infix-bracket-right` in `conformance/llm/antipatterns.cxd` #### `[?and …]` — `and` is not a directive `?` marks a control-flow DIRECTIVE from a closed registry (§4): `[?if]`, `[?let]`, `[?for]`, `[?match]` and the rest. `and`, `or` and `not` are not in it — they are reserved operator heads, written bare like `+` and `=`: `[and A B]`, `[or A B]`, `[not A]`. `and`/`or` short-circuit left to right. **Do not write this:** `prog.cx` ```cx [?and true false] ``` ```console $ cx prog.cx error: cx-err:CXER0100: parse: unknown directive '[?and]' — not in §4.1 registry at line 1:1 ``` **Write this:** `[and …]` and `[or …]` are operator calls `prog.cx` ```cx [r both=[and true false] either=[or false true]] ``` ```console $ cx prog.cx [r both=false either=true] ``` Fixtures: `ap-py-and-directive-wrong` / `ap-py-and-directive-right` in `conformance/llm/antipatterns.cxd` #### `[?not …]` — `not` is not a directive either The same rule for negation: `not` is a one-operand operator head, not a directive. `[not P]` negates; `[not P Q]` is refused for its arity. Absence is spelled `[not [$present $v]]` (§6). **Do not write this:** `prog.cx` ```cx [?not true] ``` ```console $ cx prog.cx error: cx-err:CXER0100: parse: unknown directive '[?not]' — not in §4.1 registry at line 1:1 ``` **Write this:** `[not …]` is an operator call `prog.cx` ```cx [not true] ``` ```console $ cx prog.cx false ``` Fixtures: `ap-py-not-directive-wrong` / `ap-py-not-directive-right` in `conformance/llm/antipatterns.cxd` #### A `;` remark after code `;` does not start a comment. Outside a bracket each bare word is a top-level form of the program, so the "remark" runs as three more results — `;` itself (printed `';'`), then `the`, then `sum` — after the one you meant. CX has a line comment `# …` to the end of the line and a block comment `[; … ]`; the raw-text block is TEXT, not a comment. **Do not write this:** `prog.cx` ```cx [r total=[+ 1 2]] ; the sum ``` ```console $ cx prog.cx [r total=3] ';' the sum ``` **Write this:** A block comment `[; … ]`, or a line comment `# …` `prog.cx` ```cx [r total=[+ 1 2]] [; the sum ] # or a line comment ``` ```console $ cx prog.cx [r total=3] ``` Fixtures: `ap-py-remark-wrong` / `ap-py-remark-right` in `conformance/llm/antipatterns.cxd` #### `[= $count …]` read as reassignment `[= $x V]` is a BINDING CLAUSE, never an assignment: bindings are immutable, and a clause inside `[?for]` binds a fresh `$count` for that one iteration, shadowing the outer `0`. So the running total Python's `count += x` builds (1, 3, 6) never accumulates — each pass answers `0 + x`. Carried state is a fold: `[?reduce]` with an accumulator, or `[?loop]`, whose `[continue …]` rebinds its declared bindings positionally for the next pass. **Do not write this:** `prog.cx` ```cx [?let [= $count 0] [?for [in $x (1, 2, 3)] [= $count [+ $count $x]] [yield $count]]] ``` ```console $ cx prog.cx 1 2 3 ``` **Write this:** Fold with `[?reduce]`, or carry state through `[?loop]` `prog.cx` ```cx [total fold=[?reduce (1, 2, 3) [using [?fn ($acc $x) [+ $acc $x]]] [init 0]] loop=[?loop [= $i 1] [= $count 0] [?if [> $i 3] [then [break $count]] [else [continue [+ $i 1] [+ $count $i]]]]]] ``` ```console $ cx prog.cx [total fold=6 loop=6] ``` Fixtures: `ap-py-reassign-wrong` / `ap-py-reassign-right` in `conformance/llm/antipatterns.cxd` #### Two body forms in one `[?let]` A `[?let]` is bindings, then exactly ONE body — the value it answers. There is no statement list: a second form is not "the next line", so the clause before the last is read as a malformed binding and refused. Build one value that holds both results; to run effects in order, put them in one `[?do A B]` (it answers `null`, and the first `[err]` stops the sequence). **Do not write this:** `prog.cx` ```cx [?let [= $a 2] [first [* $a 10]] [second [+ $a 1]]] ``` ```console $ cx prog.cx error: cx-err:CXER0112: [?let] expected [= $x V] binding clause before the body at line 1:1 ``` **Write this:** One body — one value holding both results `prog.cx` ```cx [?let [= $a 2] [pair [first [* $a 10]] [second [+ $a 1]]]] ``` ```console $ cx prog.cx [pair [first 20] [second 3]] ``` Fixtures: `ap-py-let-two-bodies-wrong` / `ap-py-let-two-bodies-right` in `conformance/llm/antipatterns.cxd` #### Array items without commas — `['eggs' 'peanuts']` Array `[a, b]` and sequence `(a, b)` literals separate their items with commas. Without them `['eggs' 'peanuts']` is not a two-item array; the parse refuses at the second item. **Do not write this:** `prog.cx` ```cx [?let [= $allergens ['eggs' 'peanuts']] [$count $allergens]] ``` ```console $ cx prog.cx error: cx-err:CXER0100: parse: expected ']', got peanuts at line 1:29 ``` **Write this:** Commas between the items — `['eggs', 'peanuts']` `prog.cx` ```cx [?let [= $allergens ['eggs', 'peanuts']] [$count $allergens]] ``` ```console $ cx prog.cx 2 ``` Fixtures: `ap-py-array-commas-wrong` / `ap-py-array-commas-right` in `conformance/llm/antipatterns.cxd` #### Indexing with `@` — `[$array@$i]` `@` is the attribute step of a path (`$row@id`), not a subscript, and a call head `[$f …]` carries no path step. There is no `xs[i]`. Positional access is the call `[$nth $xs $i]`, and positions are 1-BASED, as in every CX position (strings, `re` matches, `[$range]`): `[$nth $xs 1]` is the first item, and `[$nth $xs 0]` is refused out of range. **Do not write this:** `prog.cx` ```cx [?let [= $xs [10, 20, 30]] [= $i 2] [$xs@$i]] ``` ```console $ cx prog.cx error: cx-err:CXER0100: parse: call head `[$fn …]` may not carry path steps; bind the value first at line 1:38 ``` **Write this:** `[$nth $xs $i]`, counting from 1 `prog.cx` ```cx [?let [= $xs [10, 20, 30]] [= $i 2] [r first=[$nth $xs 1] at-i=[$nth $xs $i]]] ``` ```console $ cx prog.cx [r first=10 at-i=20] ``` Fixtures: `ap-py-index-at-wrong` / `ap-py-index-at-right` in `conformance/llm/antipatterns.cxd` #### `[?fn $a $b …]` — parameters without parentheses A function's parameters are ONE parenthesised list, then exactly one body: `[?fn ($a $b) body]`, as in `[?def name ($a $b) body]`. Written bare, `$b` is read as the body and the real body as a second one, and the form is refused. **Do not write this:** `prog.cx` ```cx [?let [= $add [?fn $a $b [+ $a $b]]] [$add 2 3]] ``` ```console $ cx prog.cx [err code=cx-err:CXER0100 message='[?fn]/[?def] takes exactly one body: the form is [?fn (params) body] (code.md §8.6), and a third positional is a second body — a `:body` here is the ATOM :body in body position, not a slot (1558-b, #1558)' at='1:1'] ``` **Write this:** Parameters in one list — `[?fn ($a $b) …]` `prog.cx` ```cx [?let [= $add [?fn ($a $b) [+ $a $b]]] [$add 2 3]] ``` ```console $ cx prog.cx 5 ``` Fixtures: `ap-py-fn-params-wrong` / `ap-py-fn-params-right` in `conformance/llm/antipatterns.cxd` #### A call without `$` — `[double 21]` A call is `[$f args…]`. Without the `$`, `[double 21]` is not a call of the function you just defined — it is an ELEMENT named `double`, legal data, so the program succeeds and answers the element. Every call of a function, builtin or module verb carries `$`; only operator heads (`+`, `=`, `and`) and directives (`?`) are written without it. **Do not write this:** `prog.cx` ```cx [?def double ($c) [* $c 2]] [double 21] ``` ```console $ cx prog.cx [double 21] ``` **Write this:** `[$double 21]` — the `$` makes it a call `prog.cx` ```cx [?def double ($c) [* $c 2]] [$double 21] ``` ```console $ cx prog.cx 42 ``` Fixtures: `ap-py-call-sigil-wrong` / `ap-py-call-sigil-right` in `conformance/llm/antipatterns.cxd` #### A `[?for]` over a string A string is one scalar, not a sequence of characters. A generator over it walks one item — the whole string — so the loop runs once. Split it first: `[$strings:split $s '']` answers its code points (and `strings:graphemes` its user-perceived characters). **Do not write this:** `prog.cx` ```cx [?lib 'cx-stdlib/strings' as=strings] [?for [in $ch 'abc'] [yield [$strings:upper $ch]]] ``` ```console $ cx prog.cx ABC ``` **Write this:** Over `[$strings:split $s '']` `prog.cx` ```cx [?lib 'cx-stdlib/strings' as=strings] [?for [in $ch [$strings:split 'abc' '']] [yield [$strings:upper $ch]]] ``` ```console $ cx prog.cx A B C ``` Fixtures: `ap-py-for-string-wrong` / `ap-py-for-string-right` in `conformance/llm/antipatterns.cxd` #### `(1, $n)` as a range `(1, $n)` is a two-item SEQUENCE — the items `1` and `$n` — not a range, so the loop runs twice. A range is the call `[$range lo hi]`, and both ends are INCLUSIVE (`[$range 1 4]` is 1, 2, 3, 4; Python's `range(1, n)` stops before `n`). **Do not write this:** `prog.cx` ```cx [?let [= $n 4] [?for [in $i (1, $n)] [yield [* $i $i]]]] ``` ```console $ cx prog.cx 1 16 ``` **Write this:** `[$range 1 $n]`, both ends included `prog.cx` ```cx [?let [= $n 4] [?for [in $i [$range 1 $n]] [yield [* $i $i]]]] ``` ```console $ cx prog.cx 1 4 9 16 ``` Fixtures: `ap-py-range-wrong` / `ap-py-range-right` in `conformance/llm/antipatterns.cxd` #### `strings:replace` with a regular expression `strings:replace` replaces a LITERAL substring. Handed a pattern it looks for the characters `[0-9]+` themselves, finds none, and answers the string unchanged — no error. Pattern replacement is `re:replace` over a compiled pattern from `[$re:compile]`. **Do not write this:** `prog.cx` ```cx [?lib 'cx-stdlib/strings' as=strings] [$strings:replace 'a1b22c333' '[0-9]+' '#'] ``` ```console $ cx prog.cx a1b22c333 ``` **Write this:** `re:replace` with `[$re:compile …]` `prog.cx` ```cx [?lib 'cx-stdlib/re' as=re] [$re:replace [$re:compile '[0-9]+'] 'a1b22c333' '#'] ``` ```console $ cx prog.cx 'a#b#c#' ``` Fixtures: `ap-py-replace-regex-wrong` / `ap-py-replace-regex-right` in `conformance/llm/antipatterns.cxd` #### `re:find-all` with a pattern string Every `re` verb that matches takes a COMPILED pattern, never a pattern string: compile once with `[$re:compile]` and pass the value (bind it with `[?const]` to reuse it). A string there is refused, naming the verb and `re:compile`. `re:find-all` answers `[match …]` elements; `re:find-all-text` answers just the matched texts, Python's `re.findall`. **Do not write this:** `prog.cx` ```cx [?lib 'cx-stdlib/re' as=re] [$re:find-all '[0-9]+' 'a1b22c333'] ``` ```console $ cx prog.cx [err code=cx-err:CXER0100 message='re:find-all: argument 1 expects a compiled regex from [$re:compile] (compile the pattern string first), got string — operand-kind fault (E_ARG)' at='2:1'] ``` **Write this:** Compile the pattern first `prog.cx` ```cx [?lib 'cx-stdlib/re' as=re] [?let [= $digits [$re:compile '[0-9]+']] [?for [in $m [$re:find-all $digits 'a1b22c333']] [yield [r start=$m@start end=$m@end]]]] ``` ```console $ cx prog.cx [r start=2 end=2] [r start=4 end=5] [r start=7 end=9] ``` Fixtures: `ap-py-find-all-string-wrong` / `ap-py-find-all-string-right` in `conformance/llm/antipatterns.cxd` #### `[?if C [then true] [else false]]` A comparison already answers a boolean. Wrapping it in an `[?if]` that maps `true` to `true` and `false` to `false` adds a branch and changes nothing — both halves below answer the same value. Write the condition itself. **Do not write this:** `prog.cx` ```cx [?let [= $n 7] [?if [> $n 5] [then true] [else false]]] ``` ```console $ cx prog.cx true ``` **Write this:** The condition is the value — `C` `prog.cx` ```cx [?let [= $n 7] [> $n 5]] ``` ```console $ cx prog.cx true ``` Fixtures: `ap-py-if-true-false-wrong` / `ap-py-if-true-false-right` in `conformance/llm/antipatterns.cxd` #### `strings:count` (or `$count`) for a string's length `strings:count` counts the occurrences of a NEEDLE (`'a'` in `'banana'` is 3), and `$count` counts the items of a sequence — a string is one item, so it answers 1. Neither is Python's `len(s)`. The length in code points is `strings:length`. **Do not write this:** `prog.cx` ```cx [?lib 'cx-stdlib/strings' as=strings] [r count=[$count 'banana'] needle=[$strings:count 'banana' 'a']] ``` ```console $ cx prog.cx [r count=1 needle=3] ``` **Write this:** `strings:length` `prog.cx` ```cx [?lib 'cx-stdlib/strings' as=strings] [r length=[$strings:length 'banana']] ``` ```console $ cx prog.cx [r length=6] ``` Fixtures: `ap-py-string-length-wrong` / `ap-py-string-length-right` in `conformance/llm/antipatterns.cxd` #### `/` for an integer result `/` is true division. Over two ints it answers an int only when the quotient is whole; otherwise a float (`[/ 7 2]` is `3.5e0`) — never Python 2's floor. Integer division is `[$idiv a b]` (truncating toward zero) and the remainder is `[% a b]`. **Do not write this:** `prog.cx` ```cx [r half=[/ 7 2] even=[/ 6 2]] ``` ```console $ cx prog.cx [r half=3.5e0 even=3] ``` **Write this:** `[$idiv a b]` for the quotient, `[% a b]` for the remainder `prog.cx` ```cx [r quot=[$idiv 7 2] rem=[% 7 2]] ``` ```console $ cx prog.cx [r quot=3 rem=1] ``` Fixtures: `ap-py-int-division-wrong` / `ap-py-int-division-right` in `conformance/llm/antipatterns.cxd` #### Miscounted closing brackets Every form closes with its own `]`, and a long one-line program is where a count goes wrong. CX refuses a stray `]` or an unclosed `[` at parse, naming the line and column, before anything runs. Do not count by eye: `cx fmt FILE` prints the program in canonical layout (indent 2, width 80, a form too wide broken across lines) so each closer sits at the end of its form, and refuses the same miscount at the same column; `cx lint FILE` checks without printing. **Do not write this:** `prog.cx` ```cx [?let [= $xs (1, 2, 3)] [report total=[?reduce $xs [using [?fn ($acc $x) [+ $acc $x]]] [init 0]]]]] ``` ```console $ cx prog.cx error: cx-err:CXER0100: parse: unmatched `]` — no open `[` for this closer (a stray `]`, or one `]` too many on a construct that already closed) at line 1:99 ``` **Write this:** Let `cx fmt` lay the program out `prog.cx` ```cx [?let [= $xs (1, 2, 3)] [report total=[?reduce $xs [using [?fn ($acc $x) [+ $acc $x]]] [init 0]]]] ``` ```console $ cx fmt prog.cx [?let [= $xs (1, 2, 3)] [report total=[?reduce $xs [using [?fn ($acc $x) [+ $acc $x]]] [init 0]]]] ``` Fixtures: `ap-py-brackets-wrong` / `ap-py-brackets-right` in `conformance/llm/antipatterns.cxd` ## 1. Running CX You can check every claim in this document. Do that rather than trusting it. ```console cx FILE.cx # run a program interpreted, or print a document cx --allow-read FILE.cx # grant one capability cx --allow-read=./data FILE.cx # …scoped to a directory subtree cx --probe FILE.cx # the preset: --allow-read=. --allow-clock cx --allow-common FILE.cx # the common working set (no secret-reveal) cx -e '[+ 1 2]' # a one-liner cx --data=input.cx prog.cx # bind a document as $doc / $input cx --from=json --to=cx in.json # the convert surface (the data reading) cx canonical FILE.cx # strict canonical text — what identity is cx hash FILE.cx # SHA-256 over those canonical bytes cx lint FILE.cx # style + correctness findings cx build prog.cx -o prog # compiled: one executable that runs prog (same OS/arch) cx build --native prog.cx -o prog # compiled, hot pure defs as machine code cx primer # this document, for the installed binary ``` Exit codes: `0` success, `1` the program produced an error value (or a check found findings), `2` a usage error. Unknown flags are hard errors — nothing is silently ignored. ## 2. The shape of CX ### Elements, attributes, children `input.cx` ```cx [a href=https://example.com/path Click here] ``` ```console $ cx --from=cx --to=cx input.cx [a href=https://example.com/path Click here] ``` Attributes only, no children: `input.cx` ```cx [input type=text name=q] ``` ```console $ cx --from=cx --to=cx input.cx [input type=text name=q] ``` Nesting is just children that happen to be elements: `input.cx` ```cx [config [db host=localhost port=5432] [server port=8080] ] ``` ```console $ cx --from=cx --to=cx input.cx [config [db host=localhost port=5432] [server port=8080] ] ``` Text and element children interleave freely: `input.cx` ```cx [p text [b bold] more] ``` ```console $ cx --from=cx --to=cx input.cx [p 'text ' [b bold] ' more'] ``` ### Scalars and their types `input.cx` ```cx [active true] ``` ```console $ cx --from=cx --to=cx input.cx [active true] ``` Quoting is meaningful. A value that must survive as *text* — a version string, a dotted id, a URL, anything with a space — has to be quoted, and the canonical form keeps the quotes exactly where they are needed: `input.cx` ```cx [m {k: 'a b', j: 'a.b', u: 'https://a.com', ok: 'x', p: 'a/b'} [note a b]] ``` ```console $ cx --from=cx --to=cx input.cx [m {k: 'a b', j: 'a.b', u: 'https://a.com', ok: x, p: a/b} [note a b] ] ``` The same rule inside a sequence: `input.cx` ```cx [s ('x', 'a b', 'a.b')] ``` ```console $ cx --from=cx --to=cx input.cx [s (x, 'a b', 'a.b')] ``` ### `1.5` is a decimal, not a float Worth stopping on, because almost every language you know reads a bare fraction as a binary float and CX does not. | Kind | How you get one | |---|---| | **decimal** | a plain fraction literal: `1.5`, `2.50`, `0.1`. Exact, base-10, arbitrary precision. Arithmetic over decimals stays decimal, and so does `[$avg …]` of decimals: `[$avg [0.5, 1.5]]` is `1.00` | | **float** | an `e`-exponent literal (`1.5e0`), a `::float` annotation, `[cast x :float]`, a division of ints whose quotient is not whole (`[/ 7 2]`), a float operand, `[$avg …]` of ints or floats (`[$avg [2, 4, 6]]` is `4.0e0`), and the float-returning `math` functions | `[price 19.99]` is a decimal and stays exact. Division follows its operands: `[/ 6 2]` is the int `3` and `[/ 7 2]` the float `3.5` (an int result only when every operand is an int and the quotient is whole — write `[/ [cast $a :float] $b]` when a function must always answer a float), and a decimal or bigint operand divides exactly into a decimal (`[/ 1 [cast 4 :bigint]]` is `0.25`) or refuses a quotient that does not terminate, naming `$math:div-decimal` (code.md §6.5, CO-17). Arithmetic keeps a decimal a decimal — `[* 2 1.5]` is `3.0`, and ints mix in freely — but the two kinds never mix with each other: #### Mixing a decimal literal with a float in arithmetic Almost every language reads a bare fraction as a binary float. CX does not: `1.5` is a **decimal** — exact, base-10, arbitrary precision — and only an `e`-exponent literal (`1.5e0`), a `::float` annotation, a `[cast … :float]`, a division, `[$avg]` of ints or floats (of decimals it stays an exact decimal), or a float-returning `math` function produces a float. The two kinds do not silently mix. Adding them is a loud refusal naming the only bridge, rather than a quiet promotion that would put binary rounding into a money column — which is precisely the class this rule exists to close. **Do not write this:** `prog.cx` ```cx [+ 1.5 1.0e0] ``` ```console $ cx prog.cx [err code=cx-err:CXER0100 message='+: decimal/bigint arithmetic admits only int/bigint/decimal operands — [cast] is the only decimal↔float bridge (L44)' at='1:1'] ``` **Write this:** Keep exact quantities in decimals `prog.cx` ```cx [?let [= $subtotal 19.99] [= $shipping 5.01] [total [+ $subtotal $shipping]]] ``` ```console $ cx prog.cx [total 25.00] ``` Fixtures: `ap-decimal-float-mix-wrong` / `ap-decimal-float-mix-right` in `conformance/llm/antipatterns.cxd` The formatters keep decimals exactly as written: `prog.cx` ```cx [?lib 'cx-stdlib/format'] [$format:pretty {a: 1, b: 1.5, c: :ops, d: "s"}] ``` ```console $ cx prog.cx '{\n a: 1\n b: 1.5\n c: :ops\n d: "s"\n}' ``` The kinds are also distinct to **identity and to equality**: `1.5` and `1.5e0` denote the same number but are different values, so they canonicalize differently, hash differently, and `[$eq]` answers `false`. (Contrast §6's decimal-vs-*string* case, which is `true` — atomization crosses the scalar/text line, never the decimal/float one.) `reference-data-language.md` has the image rule. ### Collections Sequences `( , )` and maps `{k: v}` are values. Map equality is order-independent — a map is a map, not a list of pairs: `prog.cx` ```cx [?let [= $z 0] ([= {a: 1, b: 2} {b: 2, a: 1}], [= {a: 1, b: 2} {a: 1, b: 3}], [= {a: 1} {a: 1, b: 2}])] ``` ```console $ cx prog.cx (true, false, false) ``` ### Comments and raw text `[; … ]` is a comment element. `[# … #]` is **raw text**: opaque, no entity expansion, no escaping, no reinterpretation. Raw text is how CX carries a payload in another language — SQL, a shell script, a regex, prose full of brackets — without the reader touching a byte. In **code**, `[# … #]` stays a node — raw text the program carries as data — so a verb that wants a string refuses it. The code spelling of an unescaped string, a regex above all, is the raw string `r'''…'''`: `[$re:compile r'''a+\d''']`. `input.cx` ```cx [;a comment] [p text] ``` ```console $ cx --from=cx --to=cx input.cx [;a comment] [p text] ``` ### Namespaces `ns:name` on a head or an attribute is namespaced, and namespaces are part of identity: `input.cx` ```cx [doc xmlns=urn:doc xmlns:xl=http://www.w3.org/1999/xlink [xl:a href=https://example.com Link] ] ``` ```console $ cx --from=cx --to=cx input.cx [doc xmlns=urn:doc xmlns:xl=http://www.w3.org/1999/xlink [xl:a href=https://example.com Link] ] ``` ## 3. Which `cx` build do I install — then the two rings `cx` ships as four builds of one binary name. The build answers *how much of CX you have to take on*; take the smallest one that answers your question. | You want | Build | What you get | When NOT to use it | |---|---|---|---| | Read, write, convert, diff, hash, validate documents — including untrusted ones | **`data`** | Ring 0 only: the reading, the canonical form, fmt, lint. No evaluator is compiled in, so nothing can execute | The moment you need a value computed rather than re-projected | | Run CX inside a host application | **`embed`** | Rings 0–1 core: directives, CXPath, comprehensions, the pure stdlib; no local-effect packs | When the program itself must read files, the clock or the network | | Query, transform, generate; run a program | **`cli`** | Rings 0–1 plus the local-effect packs (io, env, process, time, random, log, term) and the http client. Effects still need explicit grants | When the result must outlive the process, or a second party must see it | | Persist, audit, replay; serve HTTP/XSP; host features | **`platform`** | The rings plus the platform group: store + journal, session layer, HTTP surface, XAP host, drivers | For a value you are about to print — the platform is reached over a network, not embedded | Under every build sit **two rings**, and the import contract between them is enforced by the build, not by convention. Ring 0 is the data format: it imports nothing internal and cannot execute. Ring 1 is the language: it adds execution and cannot reach a socket, a store or a protocol. Everything above them is a **group** — the platform, the bindings, the ecosystem — that imports the rings and what its manifest declares, and nothing ever reaches upward. ### Ring 0 — the document *is* the answer `input.cx` ```cx [p Hello] ``` ```console $ cx --from=cx --to=cx input.cx [p Hello] ``` Nothing executed. `cx` read the document and printed the projection asked for. That is the whole of Ring 0, and it is why Ring 0 is safe to point at untrusted bytes: there is no evaluator compiled into it to attack. **When not to use it:** the moment you need a value computed. ### Ring 1 — a program over a document The canonical Ring-1 shape is a comprehension over a pattern. `[?for]` binds by *matching shape*, not by index: `input.cx` ```cx [users [user [name Alice] [email a@x.com]] [user [name Bob] [email b@x.com]]] ``` `prog.cx` ```cx [?for [user [name $n] [email $e]] [yield [pair name=$n email=$e]]] ``` ```console $ cx --data=input.cx prog.cx [pair name=Alice email='a@x.com'] [pair name=Bob email='b@x.com'] ``` Read the pattern as a claim about the document: "for every `user` that has a `name` and an `email`, bind them." Nodes that do not match are skipped, not errors. **When not to use it:** when the result has to survive the process, or more than one party must see it. That is the platform group. ### The platform group — state that outlives the program The store is content-addressed: `put-doc` returns a handle derived from the document's canonical bytes, and `get-doc` returns the document. `mem://` is the capability-free substrate, which is why this runs with no grants at all: `prog.cx` ```cx [?lib 'cx-platform/store'] [?let [= $s [$store:open "mem://"]] [= $h [$store:put-doc $s [doc [item "hello"]]]] [$store:get-doc $s $h]] ``` ```console $ cx prog.cx [doc [item hello]] ``` Swap `mem://` for a real URL and the same two calls talk to a served store. **When not to use it:** for a value you are about to print. A store handle is a promise about durability and identity; if you need neither, stay in Ring 1. ### The ecosystem and binding groups — the edge These groups are where CX meets everything around it: the registry and distribution, the bindings that reach CX from another language, and agent protocols. The MCP and A2A shapes are ordinary CX values projected to their wire form — there is no bespoke serializer anywhere in the path: `prog.cx` ```cx [?lib 'cx-platform/mcp' as=mcp] [?lib 'cx-stdlib/json' as=json] [$json:emit [$mcp:call-tool-request 1 "get_weather" {location: "NYC"}]] ``` ```console $ cx prog.cx '{"id":1,"jsonrpc":"2.0","method":"tools/call","params":{"arguments":{"location":"NYC"},"name":"get_weather"}}' ``` **When not to use it:** for anything internal; inside your own program the CX value is better currency. These groups exist for crossing a boundary. ## 4. The complete directive registry This is the whole normative set, projected from the specification. If a directive is not in this table, **it does not exist** — do not invent one, and do not carry one over from another Lisp. | Directive | Category | |---|---| | `[?match]` | Core — pattern dispatch (single-arm + multi-arm) | | `[?modify]` | Core — pure-functional update | | `[?with-open]` | Core — scoped-resource RAII | | `[?with-scope]` | Core — dynamic-scoped context | | `[?str]` | Core — compile-time string interpolation | | `[?element]` | Core — computed-name element construction | | `[?attr]` | Core — computed-name attribute (attr position only) | | `[?entry]` | Core — computed-key map entry (inside `{…}` only) | | `[?name]` | Core — shared name sub-form (`set-attr`/`rename`) | | `[?quote]` | Core — quasiquote (eager; two-color hygiene) | | `[?unquote]` | Core — quasiquote hole (single value) | | `[?splice]` | Core — adopts a sequence value's members into element content (R-A1: a sequence value cannot BE element content; `[?splice]` is the syntactic adopter, alongside contributing `[?for]`) | | `[?eval]` | Core — tree-eval (reuses `cx:eval` sandbox) | | `[?for]` | Core — for-comprehension (Sequence outer) | | `[?for-array]` | Core — for-comprehension (Array outer) | | `[?for-map]` | Core — for-comprehension (Map outer) | | `[?let]` | Core — local binding | | `[?fn]` | Core — function literal | | `[?def]` | Module — module-level function | | `[?lib]` | Module — module import | | `[?const]` | Module — module-level constant | | `[?if]` | Core — conditional | | `[?else]` | Core — value-or-default coalesce (`getOrElse`; on err + absence) | | `[?do]` | Core — evaluate-for-effect sequencing (values discarded; first `[err]` propagates; yields `null`) | | `[?loop]` | Core — condition-driven loop; `[break]`/`[continue]` clause-heads, all-explicit exits | | `[?pipe]` | Core — pipeline (prefix-only; bare stages; `[tap]`) | | `[?map]` | Core — map (sequential or `[par]`) | | `[?reduce]` | Core — reduce / fold (sequential or `[par]`) | | `[?filter]` | Iterator stdlib — filter by predicate | | `[?take]` | Iterator stdlib — prefix of `count` items | | `[?drop]` | Iterator stdlib — suffix after `count` items | | `[?take-while]` | Iterator stdlib — the leading items a `[using PRED]` passes, stopping at the first it fails (GAPS-1; case program-gap-259-take-while-lazy-and-stops, gap-259) | | `[?drop-while]` | Iterator stdlib — every item after the leading run a `[using PRED]` passes, the predicate no longer consulted once one item fails; with `[?take-while]` the predicate prefix pair beside the count pair `[?take]` / `[?drop]` (Python's `itertools.takewhile` / `dropwhile`), lazy as `[?filter]` is, so `[?take-while [$range 1 *] [using …]]` is finite (GAPS-1; case program-gap-259-drop-while, program-gap-259-take-while-lazy-and-stops, gap-259) | | `[?zip]` | Iterator stdlib — per-position tuples | | `[?enumerate]` | Iterator stdlib — emit `(position, item)` pairs, the position 1-based (D4) | | `[?chunks]` | Iterator stdlib — group by `count` | | `[?concat]` | Iterator stdlib — flatten one level across sources | | `[?cycle]` | Iterator stdlib — bounded repeat: a leading round count `[?cycle N SOURCE]` or an item bound `max=K` | | `[?scan]` | Iterator stdlib — running-fold prefixes | | `[?flatten]` | Iterator stdlib — flatten one level of nesting | | `[?partition]` | Iterator stdlib — split by predicate | | `[?group-by]` | Iterator stdlib — group by key: `(KEY, (ITEMS…))` pairs in first-seen key order, keys partitioned by value equality and each KEY the value the key function returned, its kind kept (an int stays an int, an atom an atom) *(RULED: GAPS-2, GAPS-1; #1819 §7, `program-gap-207-group-by-key-kind`, `program-gap-207b-group-by-atom-and-mixed-keys`)* | | `[?to-sequence]` | Force-materialise — Iterator → Sequence | | `[?to-array]` | Force-materialise — Iterator → Array | | `[?to-map]` | Force-materialise — Iterator-of-pairs → Map | | `[?view]` | View opt-in — zero-copy slice intent on one expr | | `[?views]` | View opt-in — scoped view flip over a block | | `[?retry]` | Resilience | | `[?timeout]` | Resilience | | `[?circuit-breaker]` | Resilience | | `[?fallback]` | Resilience | | `[?rate-limit]` | Resilience | | `[?bulkhead]` | Resilience | | `[?http-service]` | Services | | `[?service-handle]` | Services | | `[?http-client]` | Clients | | `[?worker]` | Concurrency | | `[?worker-handle]` | Concurrency | | `[?channel]` | Concurrency | | `[?subscribe]` | Concurrency | | `[?monitor]` | Concurrency | | `[?send]` | Concurrency | | `[?receive]` | Concurrency | | `[?try-send]` | Concurrency | | `[?try-receive]` | Concurrency | | `[?close]` | Concurrency | | `[?select]` | Concurrency | | `[?stop]` | Lifecycle | | `[?wait-for]` | Lifecycle | | `[?async]` | Async | | `[?await]` | Async | | `[?await-all]` | Async | | `[?await-any]` | Async | | `[?await-race]` | Async | | `[?cancel]` | Async | | `[?check-cancel]` | Async | | `[?sleep]` | Async | | `[?with-error-hook]` | Core — error observe / enrich / report hook | | `[?with-caps]` | Core — capability narrowing (deny-only) | | `[?secret]` | Core — mark a value secret (redacted at boundaries) | | `[?reveal]` | Core — declassify a secret (gated by `secret-reveal`) | | `[?meta]` | Core — inert metadata annotation on a value | ## 5. The idioms that carry most CX programs ### Bind with a flat multi-binding `[?let]` `[?let]` takes any number of `[= $name value]` bindings followed by one body. Bindings are **sequential** — each sees the ones before it — so a nested `[?let]` per binding is never needed. This fixture is the discriminator that pins the semantics (`$y` binds the inner `$x`, not the outer shadow): `prog.cx` ```cx [?let [= $x 9] [?let [= $x 1] [= $y $x] [pair x=$x y=$y]]] ``` ```console $ cx prog.cx [pair x=1 y=1] ``` The house form for three dependent bindings: `prog.cx` ```cx [?let [= $a 1] [= $b [+ $a 1]] [= $c [+ $b 1]] [row a=$a b=$b c=$c]] ``` ```console $ cx prog.cx [row a=1 b=2 c=3] ``` ### Iterate with `[?for]` One directive covers map, filter, and reduce. `[in $x SOURCE]` is the generator (more than one nests), `[where]` filters, `[yield]` emits. `input.cx` ```cx [users [user [name Alice] [email a@x.com]] [user [name Bob] [email b@x.com]]] ``` `prog.cx` ```cx [?for [user [email $e]] [yield $e]] ``` ```console $ cx --data=input.cx prog.cx 'a@x.com' 'b@x.com' ``` Attribute predicates go in the pattern: `input.cx` ```cx [users [user active=true [name Alice] [email a@x.com]] [user active=false [name Bob] [email b@x.com]] [user active=true [name Carol] [email c@x.com]]] ``` `prog.cx` ```cx [?for [user @active=true [email $e]] [yield $e]] ``` ```console $ cx --data=input.cx prog.cx 'a@x.com' 'c@x.com' ``` `[group-by]` partitions the stream and binds `$count` and the group aggregates: `input.cx` ```cx [users [user dept=eng [name Alice]] [user dept=sales [name Bob]] [user dept=eng [name Carol]]] ``` `prog.cx` ```cx [?for [user @dept=$d] [group-by $d] [yield [row dept=$d count=$count]]] ``` ```console $ cx --data=input.cx prog.cx [row dept=eng count=2] [row dept=sales count=1] ``` The full grouped shape, with per-group aggregates over a bound key: `prog.cx` ```cx [?let [= $orders [orders [order region=east [line qty=2 price=10] [line qty=1 price=5]] [order region=west [line qty=3 price=4]] [order region=east [line qty=1 price=100]]]] [?for [in $o $orders/order] [in $l $o/line] [= $r [* $l@qty $l@price]] [group-by $o@region] [yield [row region=$key revenue=[$sum $group/r] n=$count]]]] ``` ```console $ cx prog.cx [row region=east revenue=125 n=3] [row region=west revenue=12 n=1] ``` `[order-by]` sorts. Its keys go in one clause, in order — the items sort by the first key, ties by the second, and so on, and items equal on every key keep their input order — and each key takes its own direction, `asc` (the default) or `desc`, written after it: `input.cx` ```cx [u-set [u name="C" age=30] [u name="A" age=25] [u name="B" age=30]] ``` `prog.cx` ```cx [?for [in $u //u] [order-by $u/@age desc $u/@name asc] [yield [r n=$u/@name a=$u/@age]]] ``` ```console $ cx --data=input.cx prog.cx [r n=B a=30] [r n=C a=30] [r n=A a=25] ``` A second `[order-by]` clause in the same `[?for]` is refused at parse time, naming it, rather than read as a re-sort — so a sort always reads left to right, first key first (`program-order-by-multikey-009-second-clause-diagnostic-names-it`). ### Query with CXPath Paths are values: `$doc/child`, document-rooted `/…`, descendant `//…`, with predicates. A path works as a `[?for]` source, as an `[?if]` condition (truthy iff the match set is non-empty), and anywhere a value is expected. `input.cx` ```cx [users [user [name Alice] [email a@x.com]] [user [name Bob] [email b@x.com]]] ``` `prog.cx` ```cx //user ``` ```console $ cx --data=input.cx prog.cx [user [name Alice] [email a@x.com]] [user [name Bob] [email b@x.com]] ``` `input.cx` ```cx [users [user active=true [name Alice]] [user active=false [name Bob]] [user active=true [name Carol]]] ``` `prog.cx` ```cx //user[= $_@active true] ``` ```console $ cx --data=input.cx prog.cx [user active=true [name Alice]] [user active=true [name Carol]] ``` `input.cx` ```cx [users [user active=true [name Alice]] [user active=false [name Bob]] [user active=true [name Carol]]] ``` `prog.cx` ```cx [$count //user[= $_@active true]] ``` ```console $ cx --data=input.cx prog.cx 2 ``` Descendant search with a binding: `input.cx` ```cx [section [para [span "hello"] [span "world"]]] ``` `prog.cx` ```cx [?for [in $s //section] [yield $s//span]] ``` ```console $ cx --data=input.cx prog.cx [span hello] [span world] ``` ### Branch with `[?if]` The clause names are mandatory. `[?if c a b]` is a syntax error, not a positional shorthand — see §9. `[else]` may be omitted, and a false condition then yields empty: `prog.cx` ```cx [out [ok [?if false [then 'a']]]] ``` ```console $ cx prog.cx [out [ok]] ``` Truthiness is defined per type rather than coerced: zero and empty string are falsy, an *empty element* is truthy (it exists), and an empty match set is falsy. That last one is what makes the existence idiom read naturally: `input.cx` ```cx [app [flag]] ``` `prog.cx` ```cx [?if //flag [then "on"] [else "off"]] ``` ```console $ cx --data=input.cx prog.cx on ``` ### Destructure with `[?match]` Arms are tried in order. `[case PATTERN result]` matches shape, `[when PREDICATE result]` matches a condition, `[case _ result]` is the wildcard, and `[else]` is the fallback. `prog.cx` ```cx [?let [= $n [prose "hello"]] [?match $n [case [prose $p] [p $p]] [case [code $c] [pre $c]] [else ()]]] ``` ```console $ cx prog.cx [p hello] ``` `prog.cx` ```cx [?let [= $x 120] [?match $x [when [> $x 100] :big] [when [> $x 50] :medium] [else :small]]] ``` ```console $ cx prog.cx :big ``` `prog.cx` ```cx [?let [= $v "surprise"] [?match $v [case 200 :http-ok] [case _ :other]]] ``` ```console $ cx prog.cx :other ``` With no subject at all, a `[?match]` of `[when …]` arms is the **cond** — CX's one spelling of `if … elif … else`: the first condition that holds answers, and `[else]` answers when none does. Write no decorative subject: `prog.cx` ```cx ([?let [= $x 1] [?match [when [> $x 3] big] [when [> $x 0] mid] [else small]]], [?let [= $x -1] [?match [when [> $x 3] big] [when [> $x 0] mid] [else small]]]) ``` ```console $ cx prog.cx (mid, small) ``` ### Destructure in one line — single-arm `[?match]` Drop the arm keywords and `[?match]` takes one pattern and one body: `[?match SUBJECT PATTERN BODY]`. This is the **destructure-or-refuse** bind — the pattern is the whole contract, and a shape that does not fit is a loud refusal, never a silent skip. The full pattern grammar applies here, rest marker included: `prog.cx` ```cx [?def apply-op ($stack $op) [?match $stack ($b, $a, *$rest) [yield [$op $a $b]]]] [$apply-op (3, 4, 9) [?fn ($x $y) [+ $x $y]]] ``` ```console $ cx prog.cx 7 ``` The body may be bare, as every other body is — `[yield …]` is the long form of the same thing: `prog.cx` ```cx [?match (3, 4) ($a, $b) [+ $a $b]] ``` ```console $ cx prog.cx 7 ``` The refuse half is the point. A stack too short to take apart stops the program instead of computing something from absent operands: `prog.cx` ```cx [?def apply-op ($stack $op) [?match $stack ($b, $a, *$rest) [yield [$op $a $b]]]] [$apply-op (3) [?fn ($x $y) [+ $x $y]]] ``` ```console $ cx prog.cx error: cx-err:CXER0100: [?match] no match for value (single-arm form) at line 2:3 ``` Reach for `[?match]` when you are taking a value *apart*, and for the multi-arm form when you genuinely have several shapes to tell apart. **On cost:** a structural single-arm destructure runs at parity with the hand-written bind chain, and so do bind-free multi-arm arms (scalar literals, the wildcard) against a flat `[?if]` chain — neither is worth rewriting for speed. The one hot-path shape that still bites is a *wide* dispatch over **element patterns**, once per node of a document: see §9. ### Enums in CX — the closed set travels with the data CX has no `enum` keyword, and that is the point. A closed set is declared in the **schema**, which is an artifact that travels — so the same set is enforced in the store, on the wire, in every binding, in the validator, and in `[?match]`. Rust's exhaustiveness stops at Rust's border. Protobuf's enum stops at deserialization. CX's does not stop. **The member values are atoms.** An atom is a tag-shaped scalar, spelled `:name` — the thing a symbol is for. It carries no ordinal and no order, so inserting a member cannot renumber the world. **`[enum …]` in a schema closes the set.** An out-of-set value is `S007`, and the diagnostic names the value in canonical spelling — `:gone`, not `'gone'`, because those are different values (`schema.md` §10.3). `:ok` and `'ok'` are distinct values in distinct kinds with no coercion between them, so a string does **not** satisfy an atom-declared enum: it is `S005`, a kind mismatch, before membership is even asked (`schema.md` §7.4). `cast` is the one bridge, and it is always explicit. **`[?match]` eliminates the set, and `[of TypeName]` tells it which set.** Name the schema in scope, then declare what the scrutinee ranges over: `prog.cx` ```cx [?cx schema-inline [schema of=job] [type status::atom [enum :ok :err :pending]]] [?match :ok [of status] [case :ok 'done'] [case :err 'failed'] [case :pending 'waiting']] ``` ```console $ cx prog.cx done ``` That buys three things a hand-rolled dispatch cannot have: 1. **`cx lint` reports coverage by name.** Drop the `:pending` arm and it says *misses :pending*. Misspell one — `[case :not-fuond …]` — and it says *arm :not-fuond is not a member of status — unreachable*. That second class used to fail in perfect silence, because a misspelled atom simply never matches. Under `--strict` both become load-time refusals. 2. **An out-of-set value refuses at the boundary, loudly**, carrying the S-code and the value in canonical spelling. This is what makes the coverage claim sound rather than advisory — no unexpected value can thread the arms the linter just proved complete. 3. **No silent fall-through.** A `[of]` match that claims nothing refuses: `prog.cx` ```cx [?cx schema-inline [schema of=job] [type status::atom [enum :ok :err :pending]]] [?match :pending [of status] [case :ok 'done'] [case :err 'failed']] ``` ```console $ cx prog.cx error: cx-err:CXER0100: [?match] [of status]: no arm claimed :pending — a match over a declared-closed type has no silent fall-through; add the missing [case …] arm or an explicit [else] (code.md §8.2) at line 4:1 ``` `[else …]` is the opt-out, and writing one is how you say you meant it. **Evolution composes.** Widening an enum upstream turns a previously exhaustive match into a named-gap report at the next lint; narrowing is refused with the member named (`schema.md` §16.5). An out-of-set value arriving at a pinned-older match refuses at the `[of]` boundary — the protobuf unknown-value problem, answered loudly and by name instead of with a silent default. **Closed KEY sets, too.** `[keys …]` closes the key domain of a map — the honest `Record`/`EnumMap`. `[keys [enum …]]` alone is subset-closed; add `[req]` and every member must be present — `S021` names the absent member (`schema.md` §7.2): ```cx [type flags::[map string bool] [keys [enum read write exec] [req]]] ``` **Ask a schema for its members** with one verb, in declared order and kind-faithful — atoms come back as atoms, so a member feeds `[case :ok …]` directly: `prog.cx` ```cx [?lib 'cx-stdlib/validate'] [?let [= $h [$validate:register-schema 'Jobs' "[schema of=job] [type status::atom [enum :ok :err :pending]]"]] [$validate:enum-values 'Jobs' 'status']] ``` ```console $ cx prog.cx (:ok, :err, :pending) ``` **What CX deliberately does not have:** ordinal / int-backed enums (identity is the value; an ordinal renumbers the world when you insert a member), a program-layer `enum` declaration separate from schema (it would stop at the language border, which defeats the whole point — declare it inline with `[?cx schema-inline …]` instead), sortable members, and any implicit atom↔string bridging. ### Callable values — one rule at every site A **callable value** is one of exactly five things: | Kind | Written | |---|---| | a `[?def]` value | `double` or `$double` | | a `[?fn]` value | `[?fn ($x) …]` | | a partial | `[$f 5 _]` | | an operator value | `[+ _ _]`, `[cast _ :int]` | | a builtin | `$upper`, `$floor` | All five are **bindable**, **passable** and **applicable** at the same sites. So there is no adapter lambda to write and no second spelling of an operator to learn: a `_` hole in an operator form makes that operator the value, and the holes show its arity. `prog.cx` ```cx [?let [= $f [+ _ 1]] [$f 41]] ``` ```console $ cx prog.cx 42 ``` `prog.cx` ```cx [?let [= $add [+ _ _]] [$add 2 3]] ``` ```console $ cx prog.cx 5 ``` Which is what makes an **operator table** ordinary data — build the map, look the operator up by a runtime token, apply it: `prog.cx` ```cx [?lib 'cx-stdlib/map'] [?let [= $ops {'+': [+ _ _], '-': [- _ _], '*': [* _ _]}] [?let [= $k '*'] [?let [= $f [$map:get $ops $k]] [$f 6 7]]]] ``` ```console $ cx prog.cx 42 ``` A `[using …]` clause, and every higher-order std-lib parameter, takes the operator value directly. (A data value in one of those slots is refused for *callability* — `cx-err:CXER0106` — and the diagnostic lists all five kinds, never one of them.) `prog.cx` ```cx [?reduce (1, 2, 3, 4) [using [+ _ _]] [init 0]] ``` ```console $ cx prog.cx 10 ``` ### Look a key up with a computed key Two reads answer a map entry whose key is only known at run time. They differ in **one** thing — what a miss does — and that is how you choose: `[$map:get $m $k]` answers the **absence channel** on a miss, so `[?else]` supplies the default. This is the read the operator table above uses: `prog.cx` ```cx [?lib 'cx-stdlib/map'] [?let [= $m {'+': plus, '-': minus}] [= $k '-'] [$map:get $m $k]] ``` ```console $ cx prog.cx minus ``` `prog.cx` ```cx [?lib 'cx-stdlib/map'] [?let [= $m {'+': plus}] [= $k 'zz'] ([$present [$map:get $m $k]], [?else [$map:get $m $k] :default])] ``` ```console $ cx prog.cx (false, :default) ``` `$m.$k` — the path step with a computed name — reads the same entry and **fails loud** on a miss. Take it where an absent key is a bug at that line: `prog.cx` ```cx [?let [= $m {'+': plus}] [= $k 'zz'] $m.$k] ``` ```console $ cx prog.cx error: cx-err:CXER0001: no member (string) "zz" ``` `prog.cx` ```cx [?let [= $m {'+': plus}] [= $k 'zz'] [?else $m.$k :default]] ``` ```console $ cx prog.cx :default ``` A non-scalar key is refused by both rather than quietly missing. ### Read your own arguments, and turn text into values `[$env:argv]` is your own argument vector, in the `[resource, …args]` shape — **ungated**, because reading your own arguments is not an effect. It hands you **strings**, and there is no `[$parse]` verb: three routes take the text to a value, and they differ by what a bad input does. | Route | Bad input | |---|---| | `type=` on an `[argspec]` flag — the value arrives already typed | refuses `cx-err:CXER2502`, naming the flag the user typed | | `[cast $v :int]` — the language's single coercion path | refuses `cx-err:CXER0290` | | `[$strings:to-number $v]` | **absence**, so `[?else]` supplies a default | Declare the flag and let `parse-args` do the coercion — then the refusal can name the flag, which no later `cast` can: `prog.cx` ```cx [?lib 'cx-stdlib/env'] [?let [= $spec [argspec [flag name=verbose short=v type=bool] [flag name=limit short=n type=int default=100]]] [= $parsed [$env-parse-args $spec ("prog", "--verbose", "--limit", "10")]] [$env:flag $parsed "limit"]] ``` ```console $ cx prog.cx 10 ``` `prog.cx` ```cx [?lib 'cx-stdlib/env'] [?let [= $spec [argspec [flag name=limit short=n type=int]]] [$env-parse-args $spec ("prog", "--limit", "notanint")]] ``` ```console $ cx prog.cx [err code=cx-err:CXER2502 message='E_ENV_FLAG_TYPE_MISMATCH: notanint is not a valid int' at='3:3'] ``` In production the argv sequence those two pass explicitly is `[$env:argv]`. For a value that did not come from a declared flag, `cast` is the coercion: `prog.cx` ```cx [cast "42" :int] ``` ```console $ cx prog.cx 42 ``` And where a non-numeric input is an ordinary case rather than an error, take the absence-signalling route and coalesce: `prog.cx` ```cx [?lib 'cx-stdlib/strings'] [?else [$strings:to-number "3.07abc"] "not-a-number"] ``` ```console $ cx prog.cx not-a-number ``` ### Define with `[?def]` `[?def name [clauses] (params) body]` defines a function. `scope=public` exports it. `pure`/`impure` is checked, not decorative. `[returns T]` and `::T` annotations document by default and are enforced under `--strict`. An `[effects …]` clause makes the definition a *command* — the unit the capability and agent-tool layers understand. `prog.cx` ```cx [?def greet scope=public [returns string] ($name::string) [$concat "hello, " $name]] [$greet "world"] ``` ```console $ cx prog.cx 'hello, world' ``` Definitions are module-level, order-independent, and mutually recursive: `prog.cx` ```cx [?def even-p ($n) [?if [= $n 0] [then true] [else [$odd-p [- $n 1]]]]] [?def odd-p ($n) [?if [= $n 0] [then false] [else [$even-p [- $n 1]]]]] [$even-p 10] ``` ```console $ cx prog.cx true ``` A def is a first-class value, usable wherever a function is: `prog.cx` ```cx [?def dbl ($x) [* $x 2]] [?map (1, 2, 3) [using $dbl]] ``` ```console $ cx prog.cx (2, 4, 6) ``` ### Import with `[?lib]` `cx-stdlib/…` is the frozen standard set's **Ring 1** — pure or purely local; `cx-platform/…` is **the platform group** — the modules that serve, or that reach a store or a protocol. Both are bundled in the binary and both are frozen; the prefix tells you whether a line stays in Ring 1 or reaches the platform group. `as=` renames. `prog.cx` ```cx [?lib 'cx-stdlib/strings'] [$strings:upper "hello"] ``` ```console $ cx prog.cx HELLO ``` `prog.cx` ```cx [?lib 'cx-stdlib/math' as=m] [$m:abs -7] ``` ```console $ cx prog.cx 7 ``` ### Thread values with `[?pipe]` `prog.cx` ```cx [?pipe (1, 2, 3, 4) [?fn $xs [?for [in $x $xs] [where [> $x 2]] [yield $x]]] $count] ``` ```console $ cx prog.cx 2 ``` The infix sugar parses to exactly the same thing: `prog.cx` ```cx [?pipe (1, 2, 3, 4) [?fn $xs [?for [in $x $xs] [where [> $x 2]] [yield $x]]] $count] ``` ```console $ cx prog.cx 2 ``` ### Build strings with `[?str]` `prog.cx` ```cx [?let [= $name "Ann"] [?str "Hi {$name}"]] ``` ```console $ cx prog.cx 'Hi Ann' ``` ### Construct with a computed name `[?element]` builds an element whose head is computed; `[?attr]` and `[?entry]` do the same for an attribute and a map entry. `prog.cx` ```cx [?let [= $n "pizza"] [?element $n size=large]] ``` ```console $ cx prog.cx [pizza size=large] ``` ### Build element content with `[?splice]`, never by dropping a sequence in This is the single rule most likely to break the first program you write, because every templating language you have seen splices a list into a slot silently. **CX does not.** A non-empty sequence *value* in element-content position is refused, and the diagnostic names the idiom you meant: `prog.cx` ```cx [?let [= $vs ([violation kind=a], [violation kind=b])] [violations $vs]] ``` ```console $ cx prog.cx error: cx-err:CXER0100: a sequence value (2 members) cannot be element content — adopt its members with [?splice EXPR] (in [violations …]; first member [violation …]; code.md §6.4.1, #847-1a) ``` `prog.cx` ```cx [?let [= $vs ([violation kind=a], [violation kind=b])] [?let [= $bag [violations [?splice $vs]]] [$count $bag/violation]]] ``` ```console $ cx prog.cx 2 ``` Four consequences worth memorising: * **`[?for]` and `[?splice]` contribute siblings; everything else contributes one child.** A binding read, a call result, an `[?if]` / `[?let]` / `[?match]` result each contribute exactly one — so one of those yielding a sequence refuses. Conditional multi-contribution is `[?splice [?if …]]`. * **An argument is not content.** A `$`-head call, an operator head, or a closure call *consumes* a sequence argument happily. Only plain element construction refuses. To pass a sequence's items *as* the arguments — a variadic builtin, a rest parameter — spread it: `[$concat 'a' *$xs]` (`program-call-spread-001`). `[?splice]` among a call's arguments is refused, because a call's arguments are not children. * **Absence contributes nothing.** `[c $u/nothing]` builds `[c]`, not an element with an empty child. * **`[break]` / `[continue]` are exempt.** They are `[?loop]`'s positional value carriers, not documents: absence rides as the empty sequence and a sequence rides whole, because the loop rebinds positionally. `reference-code-language.md` has the worked example for each. ### Transform with `[?modify]` `[?modify]` rewrites a document by matching positions and replacing them. It is the transform half of the language, and the reason CX needs no separate template dialect. `prog.cx` ```cx [?let [= $d [teams [team name="alpha" [member id=1]] [team name="beta" [member id=2]]]] [?let [= $flag "alpha"] [?modify $d //team[= $_@name $flag]/member [set-attr flagged true]]]] ``` ```console $ cx prog.cx [teams [team name=alpha [member id=1 flagged=true]] [team name=beta [member id=2]]] ``` ### Stream with the iterator family `[?map]`, `[?filter]`, `[?take]`, `[?drop]`, `[?zip]`, `[?enumerate]`, `[?chunks]` are lazy; a consumer forces them. `[?reduce]` folds. `prog.cx` ```cx [?to-sequence [?take 3 [?map (10, 20, 30, 40, 50) [using [?fn $i [+ $i 1]]]]]] ``` ```console $ cx prog.cx (11, 21, 31) ``` `prog.cx` ```cx [?reduce (1, 2, 3, 4) [using [?fn ($a $b) [- $a $b]]] [init 10]] ``` ```console $ cx prog.cx 0 ``` ### Bring names in from data with `[?with-scope]` `prog.cx` ```cx [?with-scope {a: 1, b: 2} [?test-current-scope]] ``` ```console $ cx prog.cx 'a=1,b=2' ``` ### Sequence effects with `[?do]` `prog.cx` ```cx [?do 1 2 3] ``` ```console $ cx prog.cx null ``` ### The builtins you will reach for without a `[?lib]` `[$concat]`, `[$count]`, `[$distinct]`, `[$first]`, `[$nth]`, `[$string]`, `[$name]`, `[$text]`, `[$sort]`, `[$empty]`, `[$exists]`, `[$present]`, `[$position]`, `[$avg]`, `[$abs]`, `[$floor]`, `[$ceiling]` and the comparison/arithmetic operators are always available. `prog.cx` ```cx [$concat "hello" " " "world"] ``` ```console $ cx prog.cx 'hello world' ``` `prog.cx` ```cx [$distinct (1, 2, 2, 3, 1, 4)] ``` ```console $ cx prog.cx (1, 2, 3, 4) ``` ## 6. Errors and absence ### An error is a value, and position decides The discriminator is **position, not value**. A source-literal `[err …]` is data and stays data, so err-shaped documents remain expressible: `prog.cx` ```cx [foo [err code='x' message='y']] ``` ```console $ cx prog.cx [foo [err code=x message=y]] ``` But a *computed* child that evaluates to an err propagates instead of being adopted, at any depth. Reading a binding is a computed position, so this propagates rather than building `[report [err …]]`: `prog.cx` ```cx [?let [= $e [err code='c' message='m']] [report $e]] ``` ```console $ cx prog.cx [err code=c message=m] ``` That is the stated cost of the rule, not an accident. The documented escape is to rebuild from parts — path navigation does not propagate, so `@code` and `@message` read cleanly off a captured err: `prog.cx` ```cx [?let [= $e [err code='c' message='m']] [report [code $e@code] [message $e@message]]] ``` ```console $ cx prog.cx [report [code c] [message m]] ``` A paren **sequence** is not element construction, so it does not propagate — which is how you collect outcomes including the failures: `prog.cx` ```cx [?let [= $e [err code='c' message='m']] ($e, 'kept', [box $e@code])] ``` ```console $ cx prog.cx ([err code=c message=m], kept, [box c]) ``` Keep that sequence standing on its own. Once an element adopts it, or adopts a map or array that carries an err the same way, the err propagates out of the element like any other computed child. A refusal never rests in a document, and a collection inside one is no exception. ### `[?else]` is the recovery idiom `[?else VALUE FALLBACK]` yields `VALUE` unless it errored or is absent. There is no `try`/`catch`; `[?try]` and `[?chain]` were retired from the surface and now appear only in negative fixtures asserting their rejection. ### Absence has a defined answer A missing map key reads as empty rather than raising: `prog.cx` ```cx [?lib 'cx-stdlib/map'] [$map:get {a: 1} 'zz'] ``` ```console $ cx prog.cx () ``` ### Absence is not emptiness — `[$present]`, not `[$exists]` Two predicates, two different questions, and both answers are correct: * **`[$present VALUE]`** — is this value *there*? It never looks inside. A childless element, `""`, `null` and an empty array are all present. It is false only for the absence channel, the empty sequence `()`. * **`[$exists VALUE]`**, and `[$count]` / `[$empty]` with it — what is this value's **content arity**? Over an element that is its own child count, so a childless element answers `exists=false`, `count=0`, `empty=true`. That is the settled meaning and it does not change. `prog.cx` ```cx [?str 'present={[$present [input]]}|exists={[$exists [input]]}|count={[$count [input]]}|empty={[$empty [input]]}'] ``` ```console $ cx prog.cx 'present=true|exists=false|count=0|empty=true' ``` The difference decides the commonest guard in the language — *did this step match* — because `[$exists]` cannot tell a matched childless element from no match at all: it answers false for both. `[$present]` separates them: `prog.cx` ```cx [?let [= $h [box [h [input] [sel [opt]]]]] [?str 'match-present={[$present [$first $h/h/*]]}|match-exists={[$exists [$first $h/h/*]]}|nomatch-present={[$present $h/nope]}|nomatch-exists={[$exists $h/nope]}|field-present={[$present $h/h/input]}|field-count={[$count $h/h/input]}']] ``` ```console $ cx prog.cx 'match-present=true|match-exists=false|nomatch-present=false|nomatch-exists=false|field-present=true|field-count=0' ``` So `[$first]` handing back an element and `[$exists]` answering false about that same element are not in conflict. `[$first]` found a value; the value has no children. Write the guard with `[$present]`, or test the **sequence** before collapsing it — `[> [$count $matches] 0]` is total, where `[$exists [$first $matches]]` is not. ### An error may not *leave* the program silently Everything above is about errors *inside* a program, where they are ordinary values. Leaving is different. An **externalizing effect** refuses to carry a document containing an `[err]` at any depth out of the program: `prog.cx` ```cx [?lib 'cx-platform/store'] [?let [= $s [$store:open "mem://"]] [= $rows ([row [v 2.5]], [err code=cx-err:CXER0100 message="a refusal at rest"], [row [v 4.5]])] [$store:put-doc $s [report [count [$count $rows]] [?splice $rows]]]] ``` ```console $ cx prog.cx [err code=cx-err:CXER0275 message='E_ERR_AT_BOUNDARY: refusing the store document write — the document contains cx-err:CXER0100 at /report/err; a refusal must not leave the program as silent data (pass errs=:permit on the effect to externalize it deliberately)' err-path='/report/err' err-code=cx-err:CXER0100] ``` `cx-err:CXER0275` names the first contained err's code and its path. Two families are guarded, and they are the two that turn a refusal into someone else's data: * **Store document writes** — `put-doc`, `put-doc-stream`, `put-doc-text` (it stores a *parsed* document), and `modify-doc` (its action payload is the injection vehicle). * **HTTP response emission** — a handler result, whether a `[response]` envelope, a bare document, or a bare `[err]`. The refusal is a loud 500; the bare-err-as-200 shape is exactly the silent class this closes. Externalizing a refusal *deliberately* is legal, and it has **one spelling everywhere**: `errs=:permit` — a key in the trailing opts map on the store verbs, an attribute on `[response]` / `[sse-subscribe]`: `prog.cx` ```cx [?lib 'cx-platform/store'] [?let [= $s [$store:open "mem://"]] [= $h [$store:put-doc $s [report [err code=cx-err:CXER0100 message="stored deliberately"]] {errs: :permit}]] [$store:get-doc $s $h]] ``` ```console $ cx prog.cx [report [err code=cx-err:CXER0100 message='stored deliberately']] ``` Exempt by construction: blob and byte writes (bytes carry no `[err]` to find), channels, bindings, matching, formatting, the journal (an error event is exactly what a journal is for), fabric emit, and the run-surface print. An err in a collection is still just a value: `prog.cx` ```cx [?let [= $rows ([row [v 2.5]], [err code=cx-err:CXER0100 message="a value, in process"], [row [v 4.5]])] [summary [n [$count $rows]] [first-err-code [?for [in $e $rows] [where [?match $e [case [err] true] [else false]]] [yield [$concat "" $e@code]]]]]] ``` ```console $ cx prog.cx [summary [n 3] [first-err-code cx-err:CXER0100]] ``` This is what closes the `written=0 errors=25`, exit 0 class — the generator that reports success while its output carries the refusals as data. ### Two equality notions, both deliberate `[$eq]` / `[=]` is **value** equality under the atomization policy: a scalar compares against the string of its image, so these are `true` by design. `prog.cx` ```cx [probe dec-str=[$eq [u a=1.5] [u a='1.5']] atom-str=[$eq [u a=:ops] [u a='ops']]] ``` ```console $ cx prog.cx [probe dec-str=true atom-str=true] ``` `cx canonical` / `cx hash` / `cx eq` / a store handle are the **identity** notion — type-faithful over the strict-canonical bytes — and they distinguish exactly those pairs. When you are asking "is this the same document" for a cache key or a content address, you want identity, not `[$eq]`. ## 7. Capabilities The nine capabilities are `read write net env clock random subprocess eval secret-reveal`. Deny-by-default. A denial is an ordinary error value that names the flag which would have granted it, so you never guess: `prog.cx` ```cx [?lib 'cx-stdlib/io'] [$io:read-file "/var/log/app.log"] ``` ```console $ cx prog.cx [err code=cx-err:CXER0271 message='E_CAP_DENIED: read capability required for io-read-file; none granted (grant via --allow-read)' at='2:1'] ``` `--allow-common` is everything except `secret-reveal`; `--allow-all` includes it and therefore declassifies secrets. Three grants take a scope: `--allow-net=example.com:443` a host, and `--allow-read=PATH` / `--allow-write=PATH` a directory-component **subtree root** — `--allow-read=/data` admits `/data/x` and refuses `/database`, the flag repeats and the roots union, a bare `--allow-read` still grants the whole filesystem, and a path outside the roots refuses with the same error value, naming the path and the roots. `--probe` is the preset for looking around without changing anything: it expands to `--allow-read=. --allow-clock`, and a probe that writes or reaches the network refuses. A resource suffix on any other capability (`--allow-env=HOME`) is a usage error (exit 2, before evaluation), not a narrowing — that scoping is unimplemented and the surface refuses rather than granting the blanket capability behind it. Grant the narrowest thing that works. `env:argv` and `env:parse-args` are **ungated** — reading your own arguments is not an effect. ## 8. The standard library, in one screen The bundled set, projected from each module's own documentation. **If a module is not in this table it does not exist** — the most expensive mistake an assistant makes with a young language is inventing a plausible module name. ### Standard tier, Ring 1 — `[?lib 'cx-stdlib/']` Pure or purely local: no serving, no store, no protocol. A Ring-1 module never imports a platform-group one. | Module | Scope | |---|---| | `authz` | The XAP authority model as data plus a single decision function. | | `bus` | In-process publish/subscribe that delivers each published message to its matching subscribers synchronously and in a defined order. | | `bytes` | Byte-level operations on the CX bytes scalar kind. | | `crypto` | Operations involving a key, a secret, or authentication. | | `csv` | Parse and emit CSV/TSV following RFC 4180 with Excel-pragmatic extensions. | | `diagram` | The §10.1.2 reference diagram renderer as a pure CX program (RULED #758, DR-1…DR-11). | | `did` | Decentralized identifiers (DIDs): the decentralized identity source that identifies a principal, counterpart to crypto's centralized JWT/JWKS. | | `email` | Parse and build RFC 5322 + MIME multipart email messages. | | `env` | Expose process-level metadata to CX code: environment variables, command-line arguments, and process identity. | | `format` | Emit CX values back to CX text in four forms: canonical, pretty, compact, and diff-friendly. | | `fp` | Functional composition over the four value channels. | | `ft` | In-program fulltext search with structured ranking and snippet generation. | | `geo` | Coordinate primitives without a new scalar kind — geometries are ordinary CXDM elements. | | `graphql` | The GraphQL client codec: text and values in, text and values out. | | `hash` | Content-addressable hashing — fixed-length digests of arbitrary byte payloads. | | `pdf` | Text extraction from a PDF byte string, pure and with no I/O. | | `image` | Image derivatives from an uploaded byte string, pure and with no I/O: decode JPEG, PNG, GIF (first frame), BMP and WebP (lossy, lossless, alpha, the first frame of an animation); turn the frame upright by its EXIF orientation; crop; resize with a stated filter (fill, contain or cover a box); encode PNG, JPEG or WebP with every metadata block dropped. | | `html` | HTML as a first-class document format: parse, sanitize, serialize, and extract text. | | `http-client` | HTTP/1.1 request/response semantics from the CLIENT side, plus the message codec both halves of HTTP share: method verbs, headers, materialized bodies, redirects, content decoding, connection pooling, and the Server-Sent Events read side. | | `i18n` | The message and translation layer of CX's internationalization surface. | | `io` | File and stream I/O — whole-file reads and writes, streaming handles, line iteration, filesystem queries, directory operations, globbing, tempfiles, and advisory file locking. | | `json` | Parse JSON (RFC 8259) into CXDM values and emit CXDM values back to JSON. | | `locale` | The primitives layer of CX's internationalization surface. | | `log` | Structured logging: leveled emit with arbitrary structured fields. | | `math` | Numeric utilities over CX int and float scalar kinds. | | `mime` | MIME data-shape handling: a built-in extension-to-type registry, Content-Type and Content-Disposition parsing, multipart boundary generation, type classification, and Accept-header content negotiation. | | `oidc` | The OpenID Connect relying party: the login half of enterprise SSO. | | `path` | Filesystem path manipulation — splitting, joining, normalizing, and comparing OS paths. | | `process` | Run child processes: spawn them, capture their output, stream their stdio, connect them into pipelines, deliver signals, manage process groups, and drive pseudo-terminals. | | `prof` | In-program profiling, callable directly from CX code. | | `random` | Two distinct randomness facilities, kept cleanly separated so PRNG output can never accidentally stand in for crypto. | | `re` | Regular expressions backed by the RE2 engine, guaranteeing linear-time matching against any input with no catastrophic backtracking. | | `saml` | The SAML 2.0 service-provider verify core: XML-DSig verification and assertion validation as a pure codec, the enterprise counterpart to cx-stdlib/oidc. | | `sasl` | The SASL mechanism registry and framing codec, shared by cx-platform/smtp and cx-platform/imap. | | `sched` | Scheduled events and timers on the event loop. | | `scim` | SCIM 2.0 provisioning semantics — the PROVISIONING half of enterprise SSO, counterpart to cx-stdlib/oidc's login half. | | `similar` | Graded comparison as a generalization of exact equality: where = returns a boolean, the core ~ operator returns a score in [0,1] plus evidence, and a decision policy maps the score to :match / :review / :no-match bands. | | `soap` | The SOAP codec: octets and values in, octets and values out. | | `strings` | String inspection, search, and transformation for general text work. | | `supervise` | Restart policies over monitored workers: run a set of named children (each an arity-0 callable spawned as a worker) under a declared policy — strategy (:one-for-one, :one-for-all, :rest-for-one), restart intensity (max-restarts within a window), and per-child exponential backoff — restarting them when they die according to each child's restart type (:permanent, :transient, :temporary). | | `tar` | Tar archive codec over the CX bytes scalar kind. | | `term` | Native raw-mode terminal input for interactive TUIs (#30; Ring 1 since RS-5, #1591 item 7). | | `test` | Authoring primitives for unit-test-style programs written in CX: assertions, fixtures, lifecycle hooks, and structured reporting. | | `time` | Dates, datetimes, durations, and instants, with wall-clock and monotonic time sources. | | `url` | RFC 3986 and WHATWG-URL-aligned URL parsing, building, and component encoding. | | `uuid` | Generate, parse, format, and validate Universally Unique Identifiers. | | `validate` | Validate a CX data record against a record-schema at runtime, in the JSON-Schema / pydantic style. | | `vc` | Verifiable credentials: portable, signed, attenuating delegations that carry authority between DIDs and verify offline (the §22.2 delegation transport, counterpart to `did` for identity). | | `ws` | The RFC 6455 WebSocket frame codec: octets and values in, octets and values out. | | `xsp` | The XAP Stream Protocol frame codec — a self-describing, self-delimiting frame [version · type · stream-id · principal-DID · flags · len · payload] that carries XAP over any transport. | | `zip` | Zip archive codec over the CX bytes scalar kind. | ### Standard tier, the platform group — `[?lib 'cx-platform/']` The modules that SERVE, or that reach a store or a protocol. Bundled and frozen exactly as Ring 1 is; the separate prefix is what makes the reach visible in the import line. | Module | Scope | |---|---| | `audit` | The audit sink: the one durable, attributed, append-only, queryable record of what the platform did, who asked, under whose authority, on what, and what was decided. | | `authz-store` | The durable half of the XAP authority model: the trust store itself. | | `connector` | The connector kit: one engine every connector feature runs on, and the vocabulary that declaration is written in. | | `did-web` | Resolving a did:web — the one DID method whose document is not derivable from the identifier and has to be fetched. | | `flow` | General workflow on the saga substrate: flows as documents, runs as journaled records, and a runner that is a pure function rather than an engine. | | `fabric` | Platform-level eventing over the shipped primitives: one subscribe/emit surface with an explicit durability axis. | | `ftp` | FTP and FTPS (RFC 959, explicit AUTH TLS and implicit TLS, both channels protected) over cx-platform/net, and the seven-verb file surface of file_surface.md instantiated over it: list, stat, get, put, rename, delete, mkdir under a bound root no path and no link escapes, resumable transfers over REST, size, time and stall bounds. | | `http` | The HTTP/1.1 SERVER: an event-loop accept/respond surface over the `cx-platform/net` transport, with Server-Sent Events streaming for held-open push. | | `imap` | Both halves of IMAP4rev2 (RFC 9051) on one token grammar: the client an agent or a human client reads a mailbox with, and the server core a CX process serves a mailbox from. | | `journal` | An append-only, hash-chained, tenant-partitioned event log and the deterministic projection of that log into state. | | `live` | The live modes over the one planar comprehension: the same quoted `[?for]` that runs once as a query is also a delta feed. | | `net` | Transport-level (L4) networking: opening and accepting stream connections (TCP, Unix-stream, TLS), exchanging datagrams (UDP, Unix-datagram, DTLS), name resolution, and TLS upgrade or termination. | | `secrets` | The minimal keystore: the one place a credential's value is reached. | | `session` | The server-held (principal, tenant) session for a web app. | | `sftp` | SSH file transfer: an SSH session to one peer, its host key verified against bound known-hosts rows before any credential is used and never trusted on first use, and the seven-verb file surface of file_surface.md (list, stat, get, put, rename, delete, mkdir) over the SFTP v3 subsystem, with resumable transfers, size and time bounds and a bound root no path and no link escapes. | | `smtp` | Both halves of ESMTP on one wire grammar: the submission client a CX program sends mail with (RFC 6409) and the receive server core it accepts mail with (RFC 5321 §4). | | `sso` | The enterprise-SSO deployment surface: the half of enterprise single sign-on that RECEIVES a request. | | `store` | A content-addressed object store with URL-dispatched backends. | | `sync` | Incremental sync: the cursor/watermark contract that makes a source updated in place instead of polled whole, and the three ways a source is captured — polling-diff (the portable baseline every connector gets), provider-delta (a vendor token carried, compared and resumed, never interpreted) and log (a backend change-log position). | | `vc-revocation` | The durable half of credential lifecycle: recording that a verifiable credential is revoked, and reading back the set of revoked ids. | | `xap` | The XAP orchestrator — the experience layer at the top of the CX web stack. | | `xsp-auth` | The XSP-AUTH mutual proof-of-control handshake calculus — SIGMA-style signed ephemeral X25519 over four messages riding ordinary XSP v1 frames on stream 0, with the per-request possession proof and the rotation-continuity proof beside it. | | `a2a` | A minimal A2A (Agent-to-Agent) protocol client (EXPERIMENTAL x/ tier, #6 Y2) — completing the agentic triad (S9 MCP client, Y1 MCP server, Y2 A2A) on the shared substrate (jsonrpc + http + json), no new transport. | | `a2a-xap` | A2A tasks over the xap substrate (EXPERIMENTAL x/ tier, #6 Y2b). | | `adjudicate` | Out-of-band agent adjudicator for the similar review band (EXPERIMENTAL x/ tier; cx-private #376, similar.md §5.3 ruling Q4). | | `llm` | A minimal LLM provider (EXPERIMENTAL x/ tier, #6 D2/S10) — the first Runnable. | | `mcp` | A minimal MCP (Model Context Protocol) client (EXPERIMENTAL x/ tier, #6 S9). | | `mcp-server` | Minimal MCP server helpers (EXPERIMENTAL x/ tier, #6 Y1; stream 18) — the server counterpart to cx-x/mcp, at the 2025-06-18 protocol revision (one target). | | `run` | The Runnable convention + combinator library (EXPERIMENTAL x/ tier, #6 D2/M2). | | `tools` | The agent-tool projection (EXPERIMENTAL x/ tier; stream 18): ONE tool-descriptor model derived from command definitions ([effects]-bearing [?def]s — clause presence is the discriminator) at list time, no materialized manifest. | | `ux` | The SEMANTIC CORE of the UX projection (EXPERIMENTAL x/ tier; #787): the vocabulary, the fragment addressing, the validation, the three projections (command→form, query→table, feature-grammar→form/columns), the hint claims, the patch algebra a live feed lowers onto, and the surface document's routing correspondence, and the automations plane's law — the ux:automate gate, the closed pick-lists, the server's fill → validate → simulate build and the fold of the tenant's automation stream (P0-130). | | `ux-tui` | The TERMINAL RENDERER of the UX projection (EXPERIMENTAL x/ tier; #787 W5): the second of two peers over `cx-x/ux`'s semantic vocabulary, and the reason R5's renderer-agnostic claim is testable rather than asserted. | | `ux-web` | The WEB RENDERER of the UX projection (EXPERIMENTAL x/ tier; #787): one of two peers over `cx-x/ux`'s semantic vocabulary, not the privileged one. | ### Five you will reach for immediately `strings` is UTF-8 and codepoint-positioned — indexing, length, and slicing count codepoints, and case folding follows the Unicode tables: `prog.cx` ```cx [?lib 'cx-stdlib/strings'] [$strings:length "héllo"] ``` ```console $ cx prog.cx 5 ``` `re` is RE2-backed: linear time, and therefore **no lookaround and no backreferences**. Those are refused at compile time rather than silently mis-executed, which matters when you are porting a PCRE pattern: `prog.cx` ```cx [?lib 'cx-stdlib/re'] [$re:matches [$re:compile "a+"] "aaa"] ``` ```console $ cx prog.cx true ``` `json` parses and emits, with CX's type discipline on the way in: `prog.cx` ```cx [?lib 'cx-stdlib/json'] [$json:parse "42"] ``` ```console $ cx prog.cx 42 ``` `fp` is the functional layer over sequences: `prog.cx` ```cx [?lib 'cx-stdlib/fp'] [$fp:map (1, 2, 3) [+ _ 1]] ``` ```console $ cx prog.cx (2, 3, 4) ``` `time` gives typed instants and refuses nonsense rather than normalising it: `prog.cx` ```cx [?lib 'cx-stdlib/time'] [$time:date 2026 5 26] ``` ```console $ cx prog.cx 2026-05-26 ``` ### Tables are a data form, not a convention `[table[…]]` is the columnar block — typed columns, rows as records. It is first-class, which is what lets `cx table` project it to Parquet/Arrow without guessing at your intent: `input.cx` ```cx [users [table[name::string age::int active::bool]] alice 30 true bob 25 false ] ``` ```console $ cx --from=cx --to=cx input.cx [users [table[name::string age::int active::bool]] alice 30 true bob 25 false ] ``` ## 9. Anti-patterns Each entry below is a fixture *pair*: the form that pattern-matches to a Lisp, Clojure, or shell prior, and the CX form beside it. Both halves are executed at generation time, so the wrong half's output is what you will actually see — not a description of it. #### cx flags placed after the file The run surface is `cx [cx-flags] FILE [program-args...]` — the interpreter convention. Everything AFTER the file is the PROGRAM's argv (#926), so `--allow-env` there is a string handed to the program, not a grant handed to `cx`. The capability is never granted and the program dies at its first effect. Since #1424 (RULED: OL-10) one line on STDERR says so as it happens — `cx: '--allow-env' after FILE is program argv, not a cx flag — flags bind before FILE (cx [flags] FILE [args])` — unless the program's own `[argspec]` declares that argument. The line is a diagnostic: argv, the rendered result below and the exit status are exactly what they were. **Do not write this:** `prog.cx` ```cx [?lib 'cx-stdlib/env' as=env] [probe args=[$count [$env:argv]] home=[$env:var-or-default 'CX_PRIMER_ABSENT' 'unset']] ``` ```console $ cx prog.cx --allow-env [err code=cx-err:CXER0271 message='E_CAP_DENIED: env capability required for env-var-or-default; none granted (grant via --allow-env)' at='2:39'] ``` **Write this:** cx flags placed before the file `prog.cx` ```cx [?lib 'cx-stdlib/env' as=env] [probe args=[$count [$env:argv]] home=[$env:var-or-default 'CX_PRIMER_ABSENT' 'unset']] ``` ```console $ cx --allow-env prog.cx [probe args=1 home=unset] ``` Fixtures: `ap-flags-after-file-wrong` / `ap-flags-after-file-right` in `conformance/llm/antipatterns.cxd` #### A bare head name silently becomes DATA CX is homoiconic: `[name …]` is a legal ELEMENT, so a bare head that resolves to nothing is not an error — it is a document. A misspelled call therefore succeeds, exit 0, and hands the rest of the program a `[greeet 'world']` element where a string was expected. The `$` sigil is the assertion "this is a call": with it, the same typo fails loud and names itself. **Do not write this:** `prog.cx` ```cx [?def greet ($who) [$concat "hello " $who]] [greeet "world"] ``` ```console $ cx prog.cx [greeet world] ``` **Write this:** The $ call sigil fails loud on the same typo — and names the candidate `prog.cx` ```cx [?def greet ($who) [$concat "hello " $who]] [$greeet "world"] ``` ```console $ cx prog.cx [err code=cx-err:CXER0136 message='E_NO_CALLABLE: no callable "greeet" — did you mean `greet`?' at='2:1'] ``` Fixtures: `ap-bare-head-typo-wrong` / `ap-bare-head-typo-right` in `conformance/llm/antipatterns.cxd` #### Nested [?let] (the Scheme let* habit) `[?let]` is ALREADY sequential — each `[= $name value]` sees every binding before it (EV-LET-SEQ). Nesting adds a scope level per binding and buys nothing: the two forms below are output-identical. Flat is the house form; three nested `[?let]`s is a rewrite an assistant should never emit. **Do not write this:** `prog.cx` ```cx [?let [= $a 1] [?let [= $b [+ $a 1]] [?let [= $c [+ $b 1]] [row a=$a b=$b c=$c]]]] ``` ```console $ cx prog.cx [row a=1 b=2 c=3] ``` **Write this:** Flat multi-binding [?let] `prog.cx` ```cx [?let [= $a 1] [= $b [+ $a 1]] [= $c [+ $b 1]] [row a=$a b=$b c=$c]] ``` ```console $ cx prog.cx [row a=1 b=2 c=3] ``` Fixtures: `ap-nested-let-wrong` / `ap-nested-let-right` in `conformance/llm/antipatterns.cxd` #### A wide [?match] on a per-node hot path `[?match]` is a pattern-matching dispatch, not a switch statement, and what an arm COSTS depends on what its pattern binds. Bind-free arms — scalar literals, the wildcard — are at PARITY with the equivalent flat `[?if]`/`[=]` chain (measured ~0.96x on a 40-arm literal table), and a structural single-arm destructure runs at 0.94-0.98x of a hand-written flat bind chain, so neither is worth rewriting for speed. Arms carrying a BINDING pattern are the remaining hot-path shape: an element pattern, a `$bind`, a collection pattern each pay a per-arm environment rollback whose cost scales with the PROGRAM (how much is in scope), not with the pattern — so a wide element-pattern dispatch run once per node of a document gets dramatically more expensive as the program grows around it, which is the trap. The rule: reach for `[?match]` to take a value APART; on a per-node branch over element shapes, dispatch on the head name with a flat `[?if]` chain. The pair below proves that rewrite is output-preserving. (Historic note for anyone holding older numbers: the "~500x / ~20-45us per arm" figure this entry used to quote was retired by the dispatch work in cx-private #850, #1094 and #1139 — it no longer describes any shape.) **Do not write this:** `prog.cx` ```cx [?def kind-of ($n) [?match $n [case [leaf] 'leaf'] [case [twig] 'twig'] [case [branch] 'branch'] [case [trunk] 'trunk'] [else 'other']]] [?for [in $n ([leaf], [twig], [branch], [trunk], [root])] [yield [$kind-of $n]]] ``` ```console $ cx prog.cx leaf twig branch trunk other ``` **Write this:** A flat [?if] chain on the same dispatch `prog.cx` ```cx [?def kind-of ($n) [?let [= $t [$name $n]] [?if [= $t 'leaf'] [then 'leaf'] [else [?if [= $t 'twig'] [then 'twig'] [else [?if [= $t 'branch'] [then 'branch'] [else [?if [= $t 'trunk'] [then 'trunk'] [else 'other']]]]]]]]]] [?for [in $n ([leaf], [twig], [branch], [trunk], [root])] [yield [$kind-of $n]]] ``` ```console $ cx prog.cx leaf twig branch trunk other ``` Fixtures: `ap-wide-match-hot-path-wrong` / `ap-wide-match-hot-path-right` in `conformance/llm/antipatterns.cxd` #### `cx eval 'PROGRAM'` — the subcommand takes a FILE `cx eval` is a legacy ALIAS of the default run action and, like it, takes a FILE. An inline program needs `-e`. The habit of writing `cx eval ''` (from other interpreters) makes cx try to OPEN the program text as a path. Two rules avoid the whole class: run files with `cx FILE`, and reach for `cx -e 'PROGRAM'` only for a one-liner. **Do not write this:** `prog.cx` ```cx [+ 1 2] ``` ```console $ cx eval '[+ 1 2]' cx eval: error reading program "[+ 1 2]": failed to open file "[+ 1 2]"; code: 2 ``` **Write this:** `cx -e 'PROGRAM'` for a one-liner `prog.cx` ```cx [+ 1 2] ``` ```console $ cx -e '[+ 1 2]' 3 ``` Fixtures: `ap-cx-eval-inline-wrong` / `ap-cx-eval-inline-right` in `conformance/llm/antipatterns.cxd` #### Wrapping an operator in a [?fn] to pass it somewhere A `[?fn]` whose entire body is one application of one operator to its own parameters is an ADAPTER — it exists only because the operator could not be named as a value. It can be: a hole-bearing operator form IS the operator's value (`[+ _ _]` binary, `[+ _ 1]` unary), accepted at every site a callable goes — a `[using …]` clause, a higher-order std-lib parameter, an argument, a map entry. Nothing stands between the operator and the slot, the arity is visible in the holes, and the same value goes in an operator table (`{'+': [+ _ _], '-': [- _ _]}`) that a runtime token can look up. The two forms below are output-identical; the adapter is pure ceremony. **Do not write this:** `prog.cx` ```cx [?reduce (1, 2, 3, 4) [using [?fn ($a $b) [+ $a $b]]] [init 0]] ``` ```console $ cx prog.cx 10 ``` **Write this:** The operator value itself `prog.cx` ```cx [?reduce (1, 2, 3, 4) [using [+ _ _]] [init 0]] ``` ```console $ cx prog.cx 10 ``` Fixtures: `ap-adapter-lambda-wrong` / `ap-adapter-lambda-right` in `conformance/llm/antipatterns.cxd` #### Multi-arm [?match] with one real arm and an else that re-raises This is the shape you write when you want "destructure it or fail", and the only match form you know is the multi-arm one: one `[case]` doing the work, one `[else]` inventing an error code for the shape that did not fit. The single-arm form is exactly that, built in — `[?match SUBJECT PATTERN BODY]` takes the full pattern grammar (sequence, array, map, element, rest `*$name`, type tests) and a miss is the language's own loud NO_MATCH refusal, so the bespoke error code and its wording stop being yours to maintain. One line, and the pattern is still the whole contract. Reach for the multi-arm form when you genuinely have SEVERAL shapes to dispatch between. **Do not write this:** `prog.cx` ```cx [?match (3, 4, 9) [case ($b, $a, *$rest) [+ $a $b]] [else [err code='bad-stack' message='need at least two operands']]] ``` ```console $ cx prog.cx 7 ``` **Write this:** Single-arm [?match] — destructure or refuse `prog.cx` ```cx [?match (3, 4, 9) ($b, $a, *$rest) [yield [+ $a $b]]] ``` ```console $ cx prog.cx 7 ``` Fixtures: `ap-match-else-rethrow-wrong` / `ap-match-else-rethrow-right` in `conformance/llm/antipatterns.cxd` #### Dispatching a closed set as bare strings, with [else] as the safety net The set is closed in your head and open in the program. Nothing declares the members, so nothing can check them: a misspelled arm never matches and never complains, a member you forgot lands in `[else]`, and a value the upstream producer added last week is indistinguishable from one you decided to ignore. The `[else]` arm is doing two unrelated jobs at once — handling the cases you chose not to enumerate, and swallowing the ones you did not know about. Below, `:pending` is a real member the author simply forgot. It is reported as `unknown`, at exit 0, forever. Declare the set in a schema and say what the scrutinee ranges over (`[of TypeName]`): `cx lint` then names the missing member, an out-of-set value refuses loudly at the boundary, and `[else]` returns to meaning only "I deliberately do not enumerate the rest". **Do not write this:** `prog.cx` ```cx [?match 'pending' [case 'ok' 'done'] [case 'err' 'failed'] [else 'unknown']] ``` ```console $ cx prog.cx unknown ``` **Write this:** [?match … [of TypeName]] — the closed set declared where it travels `prog.cx` ```cx [?cx schema-inline [schema of=job] [type status::atom [enum :ok :err :pending]]] [?match :pending [of status] [case :ok 'done'] [case :err 'failed'] [case :pending 'waiting']] ``` ```console $ cx prog.cx waiting ``` Fixtures: `ap-open-string-dispatch-wrong` / `ap-open-string-dispatch-right` in `conformance/llm/antipatterns.cxd` #### [$exists] as a presence test — false for the element [$first] just returned `[$exists]` reads like a presence test and is not one. Over an ELEMENT it answers that element's CONTENT ARITY — how many children it has — which is the ruled meaning (#584) and is unchanged. A `[grant verb= to=]` declaration carries everything in attributes and has no children, so it reads `exists=false` while `[$first]` hands it back and `[$name]` reads it as `grant`. The guard below therefore takes its `[else]` branch for two grants that are right there, at exit 0, with nothing printed to say why — the silent-wrong-answer class, not the refusal class. The same guard over `[rule name=a [statement 'x']]` would answer true, and the only difference is whether the element happens to have children. The predicate that asks the question you meant is `[$present]` (#854, #849): it does not look inside at all. A childless element, `""`, `null` and an empty array are all present; only the absence channel — the empty sequence `()` — is absent. Reach for `[$exists]` / `[$count]` / `[$empty]` when you genuinely mean *does this have contents*, and for `[$present]` when you mean *did this match*. **Do not write this:** `prog.cx` ```cx [?let [= $d [grants [grant verb=read to=alice] [grant verb=write to=bob]]] [= $g [$first $d//grant]] [?if [$exists $g] [then [$string $g@verb]] [else 'no grants declared']]] ``` ```console $ cx prog.cx 'no grants declared' ``` **Write this:** [$present] — the presence test that does not look inside `prog.cx` ```cx [?let [= $d [grants [grant verb=read to=alice] [grant verb=write to=bob]]] [= $g [$first $d//grant]] [?if [$present $g] [then [$string $g@verb]] [else 'no grants declared']]] ``` ```console $ cx prog.cx read ``` Fixtures: `ap-exists-as-presence-wrong` / `ap-exists-as-presence-right` in `conformance/llm/antipatterns.cxd` ### One more, from the language itself `[?if]` requires its clause names. A bare positional branch is refused rather than guessed at: `prog.cx` ```cx [?if true 'a' 'b'] ``` ```console $ cx prog.cx error: cx-err:CXER0111: [?if] expects [then …] / [else …] clause children after the condition — got a bare positional branch expression; write [?if cond [then thenExpr] [else elseExpr]?] at line 1:11 ``` ## 10. Before you emit CX, check these 1. Every call has a `$`. A bare head is data — that is legal, and silent. 2. Flags come **before** the file; program arguments after it. 3. `[?let]` is flat. One `[?let]`, many `[= …]`, one body. 4. `[?if]` has `[then]` and `[else]` clause names. 5. Every directive you used is in the §4 registry. 6. Every module you imported is in the §8 catalog. You did not invent one. 7. Every effect has its `--allow-…` grant, and you granted the narrowest one. 8. Strings that must stay text are quoted. 9. No sequence value sits in element content. Where you meant many children, you wrote `[?splice …]` or `[?for …]`. 10. Every externalizing effect either carries no `[err]` or says `errs=:permit` — deliberately. 11. Where you compared for identity you used `cx hash` / canonical bytes, not `[$eq]`. 12. Every *did this match* guard uses `[$present]`. `[$exists]` is content arity and is false for the childless element `[$first]` just returned. 13. You did not write `[?try]`, `[?chain]`, `[?find]`, or `[?par-map]` — all retired. 14. You ran it. `cx FILE.cx` is one second; a plausible-looking CX program that does not parse costs the reader much more than that. ## 11. Where to go next Load one only when the work needs it — this primer is meant to be the whole context for ordinary work. | File | Load it when | |---|---| | `reference-data-language.md` | you are shaping documents, tables, schemas, or conversions | | `reference-code-language.md` | you need a directive this primer did not show | | `reference-stdlib.md` | you are looking for a module or a capability | | `reference-cli.md` | you are scripting `cx` itself | | `reference-platform.md` | you are persisting, serving, or distributing | | `playbook-xap.md` | **you are building a feature deployment** — composing feature grammars, authoring the `*.xap.cxd` deployment document, bootstrapping identity, hosting, or projecting a ux-web surface | | `model-customization.md` | **someone is asking what a tenant may change** — one tenant's variant versus a change for everyone, how a release reaches a fleet, and why the toolchain refuses tenant-specific behaviour. Read it before answering a customization question, not after a refusal | The normative source is `spec/03-approved/`. The conformance corpus under `conformance/` is the executable truth: every claim in this primer is a case in it, and the release gate fails if any of them stops holding. Section 12 below is the PLATFORM chapter: the seams a module may use, the directions that are refused, the closed set of composition patterns with their skeletons, and the runtimes a deployment runs. Read it before composing an adopter's systems — it is generated from the two specifications that own that material, so it does not drift from them. ## 12. The platform — the seams, the patterns, the runtimes > **GENERATED CHAPTER.** Every section below §12.1 is projected from > `spec/03-approved/platform/composition.md` §1-§3 and > `spec/03-approved/platform/deployment-topology.md` §1-§4 by > `scripts/gen_docs/primer_platform.cx`. `make primer-platform-check` fails > the run when this text and those pages disagree, so what you read here is > what those pages say — edit the pages, never this chapter. Read this chapter before composing anything on the platform. It answers four questions an adopter's agent otherwise guesses at: what an element's head means, which module may use which (and which direction is refused), which compositions exist (the set is CLOSED), and what changes when the same composition is deployed across four runtimes instead of one. `cx xap scaffold ` emits any of §12.5's patterns as a declaration skeleton with an authoring TODO wherever the pattern fixes no value. ### 12.1 The three head shapes, before any of the rest Every skeleton in this chapter is a document of ELEMENTS, and the head of an element decides what it is. There are three head shapes and no fourth (`core/code.md` §6.3, §8). **Bare `[name …]` is data-element construction.** The head names an element you are building, never a function to run — even when `name` happens to match a built-in. `[first "a"]` constructs an element named `first`; the built-in is `[$first "a"]`. ```cx [step name="pull-contacts" needs="start"] ``` **`[$name …]` is the call form.** The `$` sigil is what makes a word head a call: `$`-bound name in head position, applied to the positional arguments. The sigil is declared, never inferred — a bare word head can never be a call. ```cx [$strings:upper "acme"] ``` **`[?name …]` is a special form.** A directive: control flow, binding, definition — `[?for …]`, `[?let …]`, `[?def …]`, `[?if …]`. Directives are a closed, registered set. ```cx [?for [in $c $contacts] [yield $c@email]] ``` (One footnote to the first shape: a closed set of reserved operator tokens — `+ - * / %`, `= != < <= > >=`, `~`, `and or not`, `cast`, `union intersect except` — is admitted BARE as an expression head, because a symbolic token is never a valid data-element name. `[> $x 100]` is the comparison, not an element named `>`.) **This is why a flow's `[do …]` and a feature's `[intent [do …]]` are DATA.** Neither is a call and neither runs where it is written: they are data elements the flow runner (for `[do …]` in a step) or the agent surface (for the `[intent]` a verb declares) hands to the platform, which resolves the verb, decides it, and records the result. An agent that reads a `[do …]` as an invocation has misread every pattern below. ### 12.2 Orthogonality — composition.md §1 **§1. Scope, and the orthogonality rule** **Each module owns exactly one thing, dependencies point downward, and a seam is DECLARED rather than reached for.** A module lives in the ring of its highest verb and a Ring-1 module never imports a platform-group one (`README.md` §1; `../stdlib/README.md` §1; `../core/cx_partition.md` §10), which is the downward direction stated structurally; within the platform group the same rule is stated seam by seam in §2 below, where each module names what it USES of a neighbor and what it must NOT do to it — the shape `connector.md` §12 fixed and `flow.md` §6.1 adopted. A dependency that is neither a ring edge nor a §2 row does not exist: a module never reaches into another module's state, never re-implements what a neighbor owns, and never requires a neighbor that does not require it back — `flow` depends on features and no feature depends on `flow` (`flow.md` §6, RULED: #728 CK-6), the connector kit owns the walk while the protocol module owns the bytes (`connector.md` §1.1, §3.9.1, I-13), `sync` owns the watermark while `sched` owns the cadence (`sync.md` §2.6, §12), `audit` owns the record while `journal` owns its durability (`audit.md` §1.1, §11), and a surface reads a feature's readout without reaching past it (`../xap/xap_feature_distribution_market.md` §1.2, §6.3). The whole of §2 is that paragraph made checkable. ### 12.3 The seam table — composition.md §2 **§2. The seam table** **§2.1. The declared seams** One row per ordered pair. **Direction** is read as *the left module uses the right module*; what crosses is what the left module may take, and the refusing sentence is what the same specification forbids it in the same breath. Every row is a quotation of a specification section, named in the `Cited` column; a row's authority is that section, never this table. **Carrier** says what carries the row when its two sides sit in different runtimes (`deployment-topology.md` §3): `in-process` is a seam whose two sides always share a runtime, so it has no cross-runtime form; `XSP ()` is a seam whose callee is a runtime of its own in the distributed shape, naming the profile the session runs under and the deployment-binding field where it is declared. A carrier is never discovered and never inferred from a row: it is deployment data, and the seam itself is unchanged by it. **Every row carries one** — an empty cell is a seam whose deployment story nobody wrote down, and `check-composition-seams` refuses it (§4). | # | Direction | What crosses | The refusing sentence | Cited | Carrier | |---|---|---|---|---|---| | S-1 | `flow` → `connector` | a connector feature's VERBS as `[do …]` heads, resolved through the one resolver, and each verb's recorded result as record data | reach past a verb into a gateway, a cursor, a token, a budget window or an ingestion rung — none is addressable from a document | `flow.md` §6.1 | in-process — the runner resolves an act through its `[env …]` module tree | | S-2 | `flow` → `sync` | nothing directly — a sync's verbs are named by a step like any other verbs, and its recorded result is record data | own a cadence, or restate a diff, a dedup key, a horizon or a reseed plan in the document | `flow.md` §6.1 | in-process — same resolver | | S-3 | `flow` → `fabric` | intent bindings as one of the five binding kinds that START a run; `:peer` steps reaching non-CX systems through its adapters; its dead-letter precedent | invent a second delivery or durability story, and never carry the trigger INSIDE the document | `flow.md` §6.1, §4.24 | XSP XAP (the deployment's fabric URL) | | S-4 | `flow` → surface (`ux`) | the approval widget for a `:principal` proposal, the inbox and claim surface, the operator console, the studio and the picture | render, or let an operator act on, a value the run record does not hold | `flow.md` §6.1 | XSP XAP (the host's serve URL) | | S-5 | `flow` → `audit` | nothing of its own — a step's act carries whatever audit obligation its own definition declares, at the act's own effect point | write to `cx:audit`, or copy an act's audit record into the run record as a second story of the same event | `flow.md` §6.1 | in-process — the obligation is the act's own | | S-6 | `flow` → `journal` | the home stream per run; transitions as entries; the run record as fold plus snapshots; locator triples on effects | — (the substrate is the journal's; a second durability story is refused at S-3) | `flow.md` §6 | XSP STORE (`[runner [journal url=]]`) | | S-7 | `flow` → `authz` | the authority basis at start; `[bounds]` attenuation for agent steps; `approve`/`commit` for `:principal` proposals; guardian grants | contribute authority itself — the process calling `advance` is a courier and contributes NO authority to any step | `flow.md` §6, §4.5 | in-process — the session's compiled basis | | S-8 | `flow` → `sched` | every `deadline=`, `every=` and rung `within=` as a durable timer whose callback is `advance` | act on a fired timer naming a construct that is no longer `:running`/`:pending` — the record decides | `flow.md` §6, §4.2 | in-process — the runner's own timers | | S-9 | `connector` → `http` | `client`, `send`, `request` and the `[request]`/`[response]` shapes; the rule that a non-2xx is a value | build a second HTTP client; re-parse a header `http` parsed; read a status any way but through `http`'s accessors | `connector.md` §12 | in-process | | S-10 | `connector` → `audit` | `record`, `emit`, `require` — one record per outbound call | write to `cx:audit` directly; the stream is reserved and `emit` is its one writer | `connector.md` §12, §4.4 | in-process | | S-11 | `connector` → `journal` | the adapter stream ingested records land on, under the gateway's declared rung | invent a second durability story | `connector.md` §12 | XSP STORE (the binding's `[journal url=]`) | | S-12 | `connector` → `authz` | the PEP decision, asked like any verb: ρ resolves it, the runtime admits the emit, and only then does `apply` run | be the decision point for whether a verb may be emitted | `connector.md` §12.1 | in-process | | S-12b | `connector` → `db_access` | a cursor-column query per page, the backend handle reached through §4.2's credential chain — `kind=db` executes over the four SQL builtins and never opens its own connection | put SQL text in any record, in either direction (I-5: the audit `subject=` is the qualified statement or table name and never the text); build a second dialect story; hold a handle the deployment did not bind | `connector.md` §3.8, §12 | in-process | | S-12c | `connector` → `codec` | the protocol codec an ADAPTER calls — the `graphql` and `soap` envelopes behind `kind=graphql` and `kind=soap`. The bytes remain `http`'s: the codec reads a body `http` already delivered | parse a protocol envelope in the CORE — an adapter calls the codec and the core sees neither the envelope nor the codec (§3.9.1's I-13: the engine never sees a transport's bytes) | `connector.md` §12, §3.9.1 | in-process | | S-13 | `connector` → `live` | the closed rung atoms a gateway declares | invent a fifth rung | `connector.md` §12 | in-process | | S-14 | `connector` → `sched` | nothing directly — the deployment schedules ingestion and token warming | own a cadence | `connector.md` §12 | in-process — nothing crosses | | S-15 | `sync` → `connector` | `open`, `run`, `walk`, and the `[gateway]`/`[paginate]` declaration its `[capture]` is a child of; `classify`'s error value for a failed read | build a second walk, a second retry loop or a second credential story; re-parse a `[response]` the kit parsed | `sync.md` §12 | in-process | | S-16 | `sync` → `store` | `put-doc`/`get-doc` for the watermark and the index; the content address for every dedup key | invent a second address; hold a document the journal owns | `sync.md` §12 | XSP STORE (the binding's `[store url=]`) | | S-17 | `sync` → `journal` | `append` for every delta, on the feature's adapter stream under the gateway's declared rung | fold, snapshot, verify, prune or rewrite anything; invent a second durability story | `sync.md` §12 | XSP STORE (the binding's `[journal url=]`) | | S-18 | `sync` → `audit` | `record`, `emit`, `require` — one record per capture run | write to `cx:audit` directly; emit per delta rather than per run | `sync.md` §12, §4.4 | in-process | | S-19 | `sync` → `fabric` | the dead-letter stream a refused record lands on | build a second dead-letter queue | `sync.md` §12, §4.5 | XSP XAP (the deployment's fabric URL) | | S-20 | `audit` → `journal` | the reserved stream `cx:audit`; retention, tamper-evidence and export are all the journal's | keep a second store, or prove more than the chain proves | `audit.md` §2.5, §3.2, §3.3, §11 | XSP STORE (the binding's `[journal url=]`) | | S-21 | `sso` → `session`, `store`, `authz`, `journal`, `http`, `crypto` | the store seam and the pending, replay and link tables; the grant names as a session's authority basis; one `journal:append` per recorded decision; the request/response message model; every signature | decide anything or hold policy — this module decides nothing and holds no policy, and there is no metrics subsystem | `sso.md` §11 | in-process, except `store` and `journal` — XSP STORE (`[store url=]`) | | S-22 | `sso` → `flow` | a rotation and a provisioning sequence are flow documents whose steps are this module's operator verbs | make a login a flow | `sso.md` §11 | in-process — a document, not a call | | S-23 | `fabric` → `journal`, `store` | the durable plane IS the journal point served; consumer groups and cumulative offsets are store-persisted | invent anything below itself — fabric is the delivery layer | `../xap/fabric.md` §2, §6, §9; `fabric.md` | XSP STORE (`fabric.service.cx`'s mount) | | S-24 | feature → `connector` | the kit: `apply` delegates to it, so a connector is a declaration plus two definitions | be a second kind of anything — there is no connector document and no fourth package kind | `connector.md` §1, §1.2 I-1, §13.2 | in-process — the kit runs inside the feature | | S-25 | feature → `sync` | `[capture]` as a child of the feature's `[gateway]` — one source's incrementality, declared once | declare a schedule, a budget or an endpoint in the capture | `sync.md` §2.1 | in-process — `[capture]` is a child of the `[gateway]` | | S-26 | surface → feature | `GET /surface/` calls the feature's `readout` over `$host`; `GET /stream` pushes on the readout's change digest | reach past the readout — the projection reads the feature's grammar and the deployment's data, and no materialized descriptor exists to drift | `../xap/xap_feature_distribution_market.md` §1.2, §6.3; `connector.md` §5.2 | in-process — the readout is called with `$host` | | S-27 | `xap` host → feature | the `readout` / `apply` / `simulate` contract surface, keyed by the composed grammar's qualified verbs, each call taking the deployment context `$host` | boot a feature whose code entry is not at `.cx` — the host REFUSES TO ENABLE it rather than answering reads with an empty readout | `../xap/xap_feature_distribution_market.md` §1.2, §6.3 | in-process — the host boots the feature's code | | S-28 | agent surface → feature verbs | the tool-descriptor projection: `name` is the qualified verb, `description` is the verb's `[summary]`, `input-schema` comes from the `[intent]` slots, `read-only` is `effect = observe` | let a verb with no `[summary]` reach an agent — `validate` reports `CXER6325` where the author is looking | `connector.md` §5.1; `../x/tools.md` §2–§3 | in-process — the projection is computed in the host | | S-29 | `connector` → `http`, the SERVICE surface | mounting a gateway's DECLARED route on the deployment's `[?http-service]`, and reading the request the vendor pushed. S-9 is the client half — this is the inbound one `kind=webhook` needs | mount a route no `[gateway]` declared, serve one at an origin the feature chose, or bind a listener of its own — the listener, the address and the TLS are the deployment's, and a delivery on an unmounted route refuses `CXER6327` | `connector.md` §3.10.1, §3.10.6, §3.10.7 | in-process — the kit mounts a route on a service the deployment bound | | S-30 | `connector` → `db_access` | the `[sql-db]` / `[redis-db]` handle its `open` answers as a `kind=db` gateway's connection, and its four SQL verbs as the bytes; one cursor-column query per page, with the backend handle bound through §4.2's chain | write SQL text into a `[call]`, an audit record or any persisted artifact (I-5); build a second connection pool, a second retry loop or a second isolation story | `connector.md` §3.8.1, §3.8.2, §3.8.4, §3.8.6; `../core/db_access.md` §3 | in-process — the kit calls the module that holds the handle | | S-31 | `connector` → `graphql` | the GraphQL codec: a document parsed and emitted in its canonical form, the request envelope its declared variables are carried in, the response envelope a failure arrives inside and its three outcomes, an error's `extensions.code`, the schema reader introspection and the ingest both use, and the connection page the `connection` pagination style walks | open a socket, hold a handle, read a clock, classify a retry, decide a wait, or yield a partial page — every one of those is the kit's, once; and the kit for its part never re-parses a document the codec parsed (I-13) | `connector.md` §3.11.1, §3.11.2, §3.11.3, §3.11.9; `../stdlib/graphql.md` §9 | in-process — the kit calls the codec | | S-32 | `connector` → `soap` | the SOAP codec: the envelope built and read in one representation for both versions, the canonical `[err]` one reading makes of two fault vocabularies, the WS-Addressing action the record carries, the WS-Security header the gateway's posture declares, the signature's SCOPE over the `Body`, and the WSDL its discovery and its ingest both read | open a socket, hold a handle, read a clock, resolve a credential from a handle, classify a retry or decide a wait — every one of those is the kit's, once; and the kit for its part never parses an envelope the codec parsed, nor signs one itself (I-13) | `connector.md` §3.12.1, §3.12.2, §3.12.3, §3.12.9; `../stdlib/soap.md` §9 | in-process — the kit calls the codec | | S-33 | `connector` → `sftp` | the SSH session and the file surface behind a `kind=sftp` gateway: the host key verified against a bound known-hosts handle before any credential is resolved, the path and link resolution against the bound root, the directory listing the `listing` pagination style walks, the transfer plan the audit detail is built from, and the SFTP status classification | hold a socket outside the `[link]` the adapter opened; re-implement a path resolution, a bound or an error class the module already decided; walk, retry, budget, record or wait — every one of those is the kit's, once (I-4) | `connector.md` §3.13.1, §3.13.2, §3.13.3, §3.13.7; `sftp.md` §9 | in-process — the kit calls the module that holds the session | | S-34 | `connector` → `ftp` | the RFC 959 control channel, its data connections and the SAME file surface behind a `kind=ftp` gateway: the reply reading, the data endpoint whose host is the control connection's peer, the TLS posture check, the listing, the transfer plan and the reply classification | dial an address a `227` reply named — the bounce; implement TLS, which is `net`'s §3.6 upgrade and the one `http` uses; or let a transfer mode rewrite octets beneath the contract's content address | `connector.md` §3.14.1, §3.14.3, §3.14.4, §3.14.8; `ftp.md` §9 | in-process — the kit calls the module that holds the control connection | | S-35 | `sftp`, `ftp` → `file_surface` | the one contract both INSTANTIATE: the seven verbs `list`, `stat`, `get`, `put`, `rename`, `delete`, `mkdir`, the `[listing]`/`[entry]` elements, the resume arithmetic and its idempotency statement, the three bounds and the stall floor, and the nine error classes each maps its own protocol codes onto — so a connector feature written against a drop directory does not care which of the two carries it (RULED: 1430-e) | restate one of that page's sentences in a module page, raise one of its bounds, add a class to its closed set — or reach the OTHER instantiation: the two modules share a contract, never a dependency, and a call between them would be a second seam | `file_surface.md` §7, §8; `sftp.md` §3.3; `ftp.md` §3.3 | in-process — a contract is instantiated in the module, never called across a runtime | | S-36 | `connector` → `http`, the HELD-OPEN surface | `ws-connect` and the `[ws-conn]` handle of `http.md` §3.7, and `sse-connect`'s `[sse-source]` of §3.6, as the `[link]` a `kind=stream` gateway's `open` answers and the drain its `perform` reads. S-9 is the request/response half and S-29 the inbound one — this is the held-open one | dial a socket itself; follow a redirect on an upgrade; hold a connection outside the `[link]`, or decide whether a dropped one is retried — the classification is the kit's (`connector.md` §3.15.3) | `connector.md` §3.15, §12; `http.md` §3.7, §3.6 | in-process — the deployment binds the base URL the declared `path=` opens beneath (§4.6) | | S-37 | `http` → `ws` | the RFC 6455 frame codec `../stdlib/ws.md`: one frame encoded and decoded, the masking transform, fragmentation and assembly, the three control frames, and the `Sec-WebSocket-Accept` derivation §3.7's handshake compares | frame, mask, fragment or validate anything itself; re-code one of `ws`'s `CXER69xx` refusals into its own band; ask `ws` to know that a connection exists | `http.md` §3.7, §15; `../stdlib/ws.md` §1.1, §9 | in-process — a pure Ring 1 codec, no deployment data of its own | | S-38 | `connector` → `imap` | the session and the mailbox behind a `kind=imap` gateway: `select`, `search`, `fetch` and `sync` as the bytes, the `(UIDVALIDITY, UID)` addressing a page is a range of, the `MODSEQ` the resume point reads and the `UIDNEXT` it degrades to, the folder list the discovery and the ingest share, and the response classification — `CXER5813`, the epoch change, re-raised unchanged as the one refusal a walk answers directly | address a message by sequence number; re-parse a response the module parsed; re-code an epoch, size or syntax refusal into its own band; walk, retry, budget, record or wait — every one of those is the kit's, once (I-4) | `connector.md` §3.16.1, §3.16.2, §3.16.3, §3.16.9; `imap.md` §2.3, §2.4, §3.4, §8 | in-process — the deployment binds the server URL the declared `folders=` are read beneath (§4.6) | | S-39 | `connector` → `smtp` | the session and the submission behind a `kind=smtp` gateway: `send` and `submit` as the bytes, the `[envelope]` that is independent of the message's headers, the `[smtp-reply]` the 4xx/5xx classification reads, the `EHLO` answer the discovery and the ingest share, and the CRLF and dot-stuffing strictness that settles where a message ends | synthesize an envelope from a message's headers — that is how a `Bcc` leaks; hold a second opinion about which byte sequence terminates a body; re-code a line-ending, size or refusal code into its own band; own a send queue, a cadence or a wait | `connector.md` §3.17.1, §3.17.2, §3.17.3, §3.17.9; `smtp.md` §2.4, §5.3, §5.4, §8 | in-process — the deployment binds the submission URL the declared `envelope-from=` submits through (§4.6) | | S-40 | `connector` → `fabric` | the delivery layer behind a `kind=bus` gateway: `publish`, `subscribe` and the cumulative `ack` as the bytes, the durable plane whose stream IS a journal stream, the consumer group and its store-persisted offset as the resume identity the walk's stream sequence is read beneath, the subscription vocabulary `bus.md` §2.2 owns, and the §9.1 dead-letter policy the gateway declares rather than passes | add a delivery semantic to `cx-stdlib/bus`, whose contract `../xap/fabric.md` §8 pins as untouched; build a second durability story, a second dead-letter queue or a second offset store; wrap the §12.1 request–reply, which is a call and not a walk; walk, retry, budget, record or wait — every one of those is the kit's, once (I-4) | `connector.md` §3.18.1, §3.18.2, §3.18.3, §3.18.9; `../xap/fabric.md` §6, §7, §8, §9, §9.1 | in-process toward an EMBEDDED fabric; XSP XAP (the deployment's fabric URL) toward a served one. The NATS bridge is an integration edge to a non-CX party and is never this column's subject (`deployment-topology.md` §3) | | S-41 | `xap` host → `flow` | the deployment document's `[on …]` rows as the binding set; `start`, `advance` and `rearm` over the deployment's journal; the courier as a `sched` cadence on the boot fiber | contribute authority — the host is a courier, and every step is admitted against the run's recorded basis (1265-WF-40b); serve a kind it does not declare; tick through `fleet` (WF-32) | `../xap/xap_feature_distribution_market.md` §6.3.1; `flow.md` §4.9, §4.23, §4.24 | in-process — the host runs the runner's law in its own runtime | | S-42 | `flow` → `xsp`, the XAP host's session | a `by=:peer` step to `to=peer:did:…` as a DELEGATED INTENT — `[delegated-intent run= step= to= from= [do …] [acted-on stream= seq= hash=]]` — carried over the executing environment's authenticated session, which it holds as a resolver row `[peer [xsp-auth-established …] [fn $carry]]`, and the `flow` semantic-feature token that session's transcript-covered selection must carry | open, authenticate or hold a session of its own; emit to a counterparty whose confirmed selection lacks `flow` (`CXER4962`, at validation and again when the runner emits); read the run id as an authority input | `flow.md` §4.12b; `xsp.md` §5.0 | XSP XAP (the counterparty's session — the resolver's `[peer …]` row) | | S-43 | `flow` → `vc`, `did` | the counterparty's `[step-ack …]` as a T1 claim (`[tier :t1]`, one principal signature): `[signature suite=ed25519 …]` over the claim's canonical normative form, verified fail-closed under the key the counterparty's DID resolves to, the suite gated first | complete, or on a wind-back compensate, a step on any ack that did not verify (`CXER4963`); accept a tier or a suite the claim does not name; hold a signing key of its own for the counterparty's half | `flow.md` §4.12b; `../stdlib/vc.md` §4 | in-process — verification is a pure function of the claim and the resolved key | | S-44 | `flow` → `secrets` | the runner's signing key, named by its HANDLE in `opts.snapshot-key` beside the host's `opts.keystore` and resolved through the one seam for `cx-platform/flow` at each anchor; the key lives for the one snapshot it signs (cx-platform-secrets#2) | carry the key itself — a key value in `opts.snapshot-key`, or a handle with no keystore, refuses `CXER4965`; no snapshot, err or log carries the key | `flow.md` §4.23; `secrets.md` §3 | in-process | **§2.2. The refused directions** A refused direction is a row of its own, because a direction nobody wrote down is a direction every agent infers differently. **Check** says whether the refusal is mechanically detectable in a specification's own sentences — the `yes` rows are exactly the pair table of §4's step; a `no` row is a rule a person reads and a reviewer holds. **Carrier** is the §2.1 column, and a refused direction's carrier is `none`: a refusal holds in the same-process shape and in the distributed one alike, so no carrier can make it legal (`deployment-topology.md` §3). | # | Refused direction | The refusing sentence | Check | Cited | Carrier | |---|---|---|---|---|---| | R-1 | `connector` → `flow` | Flow depends on features; a feature never depends on flow. A read-only connector feeding a dashboard must not require a workflow engine it never calls | **yes** | `connector.md` §12.3, §1.2 I-10 (RULED: CK-6) | none — refused in both shapes | | R-2 | `sync` → `flow` | A sync runs on the deployment's or the runner's cadence with no run in existence; a feature's ingest never depends on flow | **yes** | `sync.md` §12.3, §1.2 I-10 (RULED: CK-6) | none — refused in both shapes | | R-3 | feature → `flow` | `flow` is a CONSUMER of feature verbs; no feature — connector features included — lists `flow` in its `needs`, and none is required to | no | `flow.md` §6 (RULED: #728 CK-6) | none — refused in both shapes | | R-4 | `audit` → `store` | `store`: **nothing** — reached only through journal | **yes** | `audit.md` §11 | none — refused in both shapes | | R-5 | `connector` → `sched` | `sched`: nothing directly — the deployment schedules ingestion and token warming; the kit must not own a cadence | **yes** | `connector.md` §12 | none — refused in both shapes | | R-6 | `sync` → `sched` | A capture declares no schedule: there is no `every=`, no `cron=` and no `interval=` attribute and there will not be one; an attribute naming a cadence refuses `CXER6416` | **yes** | `sync.md` §2.6, §2.1, §12 | none — refused in both shapes | | R-7 | `connector` engine → a transport's bytes | The engine never sees a transport's bytes: the kind's own protocol module builds, sends and parses them | no | `connector.md` §1.1, §3.9.1 (I-13) | none — refused in both shapes | | R-8 | `flow` → a module's state, through a transform step | A transform step performs no act, opens no connection, reads no store and holds no capability — only the run record | no | `flow.md` §4.4a (RULED: WF-42) | none — refused in both shapes | | R-9 | `flow` → a feature's internals, as a mapping | Putting the MAPPING between two connectors' nouns inside either connector is refused: that is a transform step's pure definition, so a step can sit between two features without either learning the other exists | no | `flow.md` §6.1, §4.4a | none — refused in both shapes | | R-10 | any module → `cx:audit` | The stream is reserved and `emit` is its one writer; a specification sentence that says "audit" with no row in the audit integration map is a defect in that specification | no | `audit.md` §2.5, §11 | none — refused in both shapes | | R-11 | `audit` → the act it records | Emitting never changes the act; the record is what was decided, not the deciding | no | `audit.md` §6.5, §2.2 | none — refused in both shapes | | R-12 | surface → a value the record does not hold | A surface READS the record, and a value it needs is recorded by a step | no | `flow.md` §6.1 | none — refused in both shapes | | R-13 | library → feature | A library never `uses` a feature: code does not depend on grammar, and authority cannot be smuggled through the code plane | no | `../xap/xap_feature_distribution_market.md` §1.1 (N-DIST-2) | none — refused in both shapes | | R-14 | feature → a co-tenant feature's bound host | Every invocation runs inside a resource-scoped `[deny net …]`; a feature reaching another's host refuses `CXER0271` naming it, and that scoped deny IS the isolation between co-tenant features | no | `../xap/xap_feature_distribution_market.md` §6.3 step 4 (RULED: CK-10, 1437-a) | none — refused in both shapes | | R-15 | Ring 1 → the platform group | A Ring-1 module never imports one of these; nothing in the Ring-1 directory imports a platform-group one | no | `README.md` §1; `../stdlib/README.md` §1 (RULED: 1427-a) | none — refused in both shapes | **Why R-15 is not mechanical, stated rather than left to be rediscovered.** The two `[?lib 'cx-platform/…']` lines that stand in the Ring-1 tree today — `../stdlib/README.md` §1 and `../stdlib/http-client.md` — are a PROGRAM's imports shown in an example, not a module's, and the second one is the worked case of a program importing both halves. The refusal is about a module's import graph, which a specification's prose does not carry; `ring-import-gate` reads the import graph itself. A check that read those two lines as violations would cry wolf on the two pages that state the rule. ### 12.4 The composition patterns — composition.md §3 **§3. The composition patterns** The set is **closed**: a composition an adopter needs that is not one of the seven below is a decision for the owner, not a variation to improvise. Each pattern names the enterprise need it answers in one sentence, the modules it uses, a declaration skeleton drawn from the specifications' own examples, the §2 rows it crosses, and the reference example that grades it. **A pattern with no reference example is a defect of this page**, and §3.8 is where those defects are listed with the issue that owes each one (RULED: COMP-1). **§3.1. Poll–transform–sink** **The need.** An outside system is read on a cadence, its records are mapped into another system's nouns, and the result is written to a sink — without either system's declaration learning that the other exists. **Modules.** `connector` (the walk), `flow` (the ordering and the transform step), `journal` or a second connector (the sink). ```cx [flow name="poll-transform-sink" stall-after=1d [args [since::string] [region::string]] [step name="pull-contacts" [do 'crm-rest/list-contacts' [updated-since $args/since]]] [step name="to-order-lines" needs="pull-contacts" [compute 'mapping/contacts-to-lines' [contacts $steps/pull-contacts/result/contact] [region $args/region]]] [step name="sink-lines" needs="to-order-lines" [do 'orders-db/insert-lines' [lines $steps/to-order-lines/result/line]]]] ``` **Seams crossed.** S-1 (the verbs), S-6 (the transitions), R-8 and R-9 (the transform reaches no module's state and is where the mapping lives), R-1 (neither feature names `flow`). **Graded by.** No reference example — §3.8. **§3.2. Approval with pivot and compensation** **The need.** A sequence in which one step must be admitted by a person, one step cannot be undone, and every step before the irreversible one is reversed automatically when a later step fails. **Modules.** `flow` (the ordering, the offer, the pivot), `authz` (the PEP that admits the approval act), `journal` (the transitions and the compensation entries), the surface (the approval widget and the inbox). ```cx [flow name="approval-with-pivot" stall-after=1d [args [order::string] [amount::decimal]] [step name="approve-large" by=:principal to=role:finance when="$args[> $_/amount 10000]" deadline=1d [do 'orders-db/approve-order' [order $args/order] [amount $args/amount] [decision]] [escalate [notify to=role:finance-lead within=2h] [to role:finance-lead within=4h]]] [step name="commit-order" needs="approve-large" pivot=true [do 'crm-rest/create-contact' [email $args/order] [name $args/order]]]] ``` Compensation is **not** written in the document: `[compensates]` lives on the command definition, and a failure before the pivot reverses each `:done` step in reverse document order (`flow.md` §4.8); a failure after the pivot is `:incomplete` and is never compensated (`flow.md` §4.7). **Seams crossed.** S-1, S-4, S-5, S-6, S-7; R-12 (the surface reads the record). **Graded by.** `order-pipeline` — **#1472** (`../../../reference/connectors/README.md` §2.3, cases `order-pipeline-003-approval`, `-004-escalate`, `-006-compensation`, `-007-pivot`). **§3.3. Async bulk export** **The need.** An outside system answers a large read by accepting a job, making the caller wait, and serving a file — three verbs and an ordering, with no verb that requires the ordering. **Modules.** `connector` (the three verbs), `flow` (the ordering and the bounded wait). ```cx [flow name="async-bulk-export" stall-after=1d [args [tenant::string]] [step name="submit" pivot=true [do 'acme/submit-export' [tenant $args/tenant]]] [until name="await-export" needs="submit" when="$steps/poll-export/result/@ready" attempts=20 every=1m [step name="poll-export" [do 'acme/poll-export' [job $steps/submit/result/job]]]] [step name="download" needs="await-export" [do 'acme/download-export' [job $steps/submit/result/job]]]] ``` This is the case that looks like a dependency on `flow` and is not: the flow supplies the ordering, the retry ladder, the approval and the compensation, and an adopter without `flow` orders the same three verbs from a script or a surface (`connector.md` §12.3). **Seams crossed.** S-1, S-6, S-10; R-1 (the three verbs stand alone). **Graded by.** `bulk-export` — **#1492** (`reference/connectors/bulk-export/`, cases `bulk-export-001-job-state-transitions` … `-008-a-deadline-expiry-refuses-loud`), which grades the three verbs over a real socket ordered by a script and the flow document's `until` — its iterations, its durable `every=` wait and its deadline — over the verbs' recorded answers. The skeleton above is itself a document `flow` validates, its submit the pivot because a job cannot be withdrawn (`xap-compose-178`; RULED: BEX-1). The flow run over the socket is §3.8's row. **§3.4. Inbound webhook** **The need.** An outside system pushes; the deployment verifies the signature, refuses a replay, answers back-pressure with a `429` and a `Retry-After`, and records each accepted delivery exactly once. **Modules.** `connector` (the `kind=webhook` adapter, the credential chain run in the verify direction, the budget arithmetic run in reverse), `http` (the mount), `journal` (the landing), `audit` (the record). ```cx [source [gateway name=hook kind=webhook priority=1 signing=hmac-sha256-header rung=snapshot-diff doc='the inbound route; the deployment mounts it and binds the verification key' [route path='/hooks/orders' method=post] [verify header='X-Signature' over=raw-body replay-window=5m delivery-id='/delivery_id' dedup=content-address]]] ``` **Seams crossed.** S-9, S-10, S-11, S-12, S-24; R-7 (the bytes are `http`'s). **Graded by.** `inbound-webhook` — **#1468** (`../../../reference/connectors/README.md` §2.7). **§3.5. Fan-out over a bus** **The need.** One committed act is delivered to consumers that the producer does not name, durably, with each consumer group keeping its own position. **Modules.** `fabric` (the delivery layer, both planes), `connector` (the `kind=bus` adapter), `flow` (the publish and the confirmation), `journal` (the durable plane). ```cx [source [gateway name=order-events kind=bus priority=1 auth=none rung=complete-ordered subject='cx.orders.*' group=order-consumers ack=on-process backend=nats doc='the deployment binds an embedded fabric in one environment and the bridge in another' [paginate style=sequence max-pages=500 size=256] [dead-letter max-deliveries=5 dlq='orders.dlq']]] ``` ```cx [step name="publish-order" needs="approve-large" [do 'order-events/publish' [order $args/order] [tenant $args/tenant]]] ``` The core `[gateway]` element carries only `name=`, `kind=`, `priority=`, `auth=`, `signing=` and `rung=`; everything else this row says — the subject, the consumer group, the acknowledgement mode, the backend and the dead-letter policy — is validated by the fragment the KIND declares, never hard-coded in the engine (RULED: 1430-d, `connector.md` §3.9.3, §3.18.1). **Seams crossed.** S-3, S-11, S-23, S-24, S-40; R-7. **Graded by.** `order-pipeline` — **#1472** (`../../../reference/connectors/README.md` §2.3, case `order-pipeline-005-bus-round-trip`), which grades one publish and one consume over `kind=bus`. Fan-out across more than one consumer group is graded by `fabric`'s own conformance corpus and by no reference example (`fabric.md`; `../xap/fabric.md` §9). **§3.6. Incremental sync into a store** **The need.** A source is read repeatedly without re-reading what has not changed, resumably across a restart, with each record landing exactly once. **Modules.** `sync` (the cursor, the watermark, the dedup index), `connector` (the walk beneath it), `store` (the watermark and the index), `journal` (the deltas), `live` (the view over them). ```cx [source [gateway name=orders-db kind=db priority=1 auth=basic rung=snapshot-diff [paginate style=cursor max-pages=50 path='/id' param=cursor] [capture mode=polling-diff kind=instant key='/id' version='/updated_at' since-param=since overlap=5m deletes=unobservable]]] ``` **Seams crossed.** S-13, S-15, S-16, S-17, S-18, S-25; R-2 and R-6 (no run and no cadence in the declaration). **Graded by.** `orders-db` — **#1467** (`../../../reference/connectors/README.md` §2.2, §1.2's `sync` column). **§3.7. Agent surface over verbs** **The need.** An agent is given a system's verbs as described tools, decides what to do, and never sees or argues with how it is done. **Modules.** the feature's grammar (the verbs), `x/tools` (the projection), `authz` (the PEP at the effect point), the `xap` host (the serving surface). ```cx [verbs [verb name=list-contacts effect=observe via=acme-api [summary 'List the contacts the tenant can see.'] [intent [do :list-contacts [updated-since]]] [reads contact]]] ``` The agent sees a tool with a schema and never a gateway, a cursor, a token or a `Retry-After`; every annotation it sees is a hint, and enforcement is the PEP's and the effect point's (`connector.md` §5.1, §5.3). **Seams crossed.** S-12, S-26, S-27, S-28; R-14 (an agent cannot reach a host the deployment did not bind). **Graded by.** `crm-rest` — **#1466** (`../../../reference/connectors/README.md` §1.2's agent-surface column: six verbs project as six tool descriptors, and a verb without a `[summary]` refuses `CXER6325` before an agent sees it). **§3.8. Patterns with no reference example yet** Each row is a defect of this page under COMP-1, recorded rather than hidden. | Pattern | What is ungraded | The issue that owes it | |---|---|---| | §3.1 poll–transform–sink | The poll and the sink are graded by **#1467** and **#1472**; the TRANSFORM is graded by no reference example — the `order-pipeline` document carries no `[compute …]` step, and the transform step's own conformance cases are advisory until the runner code lands (`flow.md` §4.4a, RULED: WF-42) | **#1472** — the one example whose document already orders two features' verbs, and so the one that can carry the mapping between them | | §3.3 async bulk export | The flow document's RUN over a real socket: a step's act runs under the runner's own declared effects, so the connector verb's `net` is refused `CXER0271` whatever the process grant (`flow.md` §5); `bulk-export` grades the verbs over the socket from a script and the document over their recorded answers | **cx-home/cx-platform-flow#12** | **§3.9. The scaffold** `cx xap scaffold ` emits one pattern of §3 as a DECLARATION SKELETON (RULED: COMP-1), and `cx xap scaffold ` one automation skeleton of §3.10 (RULED: AA-3). The contract is four sentences. **Input** is a name: one member of the closed set named by §3.1-§3.7, or of §3.10's closed sub-set of four skeletons, taken as a slug — and, for a skeleton only, `--answers FILE`, an `[answers [ ]…]` document, in which case the command calls `fill` (§3.10) and emits what `fill` returns: the flow document and its `[on …]` row with no slot open, or `fill`'s one refusal (RULED: AA-3; comp-001). With `--answers` the command also requires `--as ` and refuses without it; the emitted row's `as=` is that principal, as `cx flow run` names its actor (RULED: AA-8; comp-003). **Output** is the declaration documents that pattern states — the flow document, the feature and gateway declarations, and the deployment binding — plus a README carrying the pattern's need, its modules, the §2 rows it crosses, the reference example that grades it, and the list below. Every emitted document parses. **The bodies are data:** the seven patterns' bodies and the four skeletons are CX documents shipped as data in cx-platform-flow, and the scaffold, the generated primer chapter and the studio read those same documents — the command carries no copy of a body of its own (RULED: AA-1). **The TODO rule.** What the pattern STATES becomes a declaration; what it does not state becomes an authoring TODO, never a guess — the discipline `connector.md` §6.3 fixes for every skeleton the toolchain emits (RULED: 1430-g), applied here to a composition instead of to an OpenAPI document. The feature names, the thresholds, the routes, the nouns, the verbs, every idempotency claim and every deployment fact are TODOs, and each says why it is one; in a skeleton each TODO is a typed, named `[slot …]` (§3.10). The command is pure — a name and, for a skeleton, the answers file named on its command line in; a skeleton or a filled document out — with no network, no clock and nothing else read from the tree; it never writes into a deployment document (RULED: AA-3). **The refusal.** A name outside the closed set is refused with the seven patterns and the four skeletons named, never with the nearest one (RULED: AA-3; comp-002): a composition an adopter needs that is not one of the seven is a decision for the owner, not a variation to improvise, and a scaffold that answered with something near it would hide that decision rather than raise it. The skeleton does not run as generated, and says so — `cx xap init` scaffolds a project that composes unedited because a project has a working shape, and a composition pattern is a shape. **§3.10. Automation skeletons (RULED: AA-1, AA-2, AA-3, AA-6)** **The need.** A person who is not a developer says "when this happens, do that" — from the studio or from `cx xap scaffold` — and gets a flow document and its binding without writing either by hand. The engine already runs such an automation; what this section closes is the AUTHORING. **What a skeleton is.** An automation skeleton is a flow document plus one `[on …]` row (`flow.md` §4.24), in which each TODO is a typed, named slot, `[slot name= kind=act|field|role|principal|duration|value]`. It is shipped as data in cx-platform-flow, which owns the vocabulary it uses, and it is content-addressed and pinnable like any document (RULED: AA-1). The set is a closed sub-set of its own, not an eighth pattern: exactly the four below, named `-`, using only the binding kinds the XAP host serves (`flow.md` §4.24) and vocabulary already built (RULED: AA-2). | Skeleton | The trigger | The shape | Reference example | |---|---|---|---| | `on-change-act` | an `intent` row selected by `act=` (`flow.md` §4.24) | a committed act leads to `:runner` steps: assign, then notify | its reference `[answers …]` document — flow-180 | | `on-change-approve` | an `intent` row selected by `act=` | a `:principal` step with `to=role:`, `deadline=`, `[escalate]` and an optional `quorum=`, then the act | its reference `[answers …]` document — flow-181 | | `on-change-check` | an `intent` row selected by `act=` | a `:runner` step on a connector verb, a `when=` on its recorded result, then the act | its reference `[answers …]` document — flow-182 | | `on-schedule-act` | a `schedule` row | `:runner` steps | its reference `[answers …]` document — flow-183 | The value test of a change ("changed to done") is a `when=` guard over `$args` (`flow.md` §4.4), never a second attribute on the row (RULED: AA-2). **Not in the set:** `webhook` (a `path=` is a deployment fact, and §3.4 covers webhooks for adopters), `fold` (a named landing on both faces), and `map` or `until` bodies (an end user has no recorded set to fan out over) (RULED: AA-2). No skeleton carries `pivot=true`, `by=:agent`, `by=:peer`, `flow=` or `[compute …]`, so a step a non-developer may not compose is absent by construction rather than refused after the fact (RULED: AA-4; flow-185). **The `ux:automate` claim is granted by the tenant's own root `[claims-map]` document** — `ux.md` §6.5's cascade, position tenant — the same document that grants `ux:edit`, and never a per-surface override: a session's `[rule …]` either names `[grant cap="ux:automate"]` at that root or the automations plane is closed to it (RULED: NAMES-1, AA-4; case ux-134). **`fill` — one pure def, two callers.** `fill` takes a skeleton and `[answers [ ]…]` and returns the flow document and its `[on …]` row with no slot left open, or refuses, naming EVERY slot that is unanswered or answered with a value of the wrong kind in one refusal — never the first alone, never a silent partial (RULED: AA-3; flow-184). `fill` is a pure def of `cx-platform/flow`, beside the skeleton data, and that one refusal is a code in flow's band (`flow.md` §8) (RULED: AA-7; flow-184). `cx xap scaffold --answers FILE` (§3.9) calls it, and the studio's automations plane calls the same def on the host (`ux.md` P0-130), so the two cannot produce different documents from the same answers (RULED: AA-3). **Graded by.** Each skeleton's case (flow-180 … flow-183) takes its reference `[answers …]` document and expects `fill`'s output byte for byte, then a clean `validate`, then `simulate` (`flow.md` §3) over a declared result table reaching `:done` and each skipped or refused path; a real-host case, `case-host-automation`, then starts the run from a committed act on that host and expects the folded run record (`flow.md` §4.23) (RULED: AA-6). Landed with **#1498**'s code wave: flow-180 … flow-185 and `case-host-automation` all exist and grade the four skeletons above (RULED: NAMES-1, AA-6). **The path table.** Each skeleton's data document carries a fourth part alongside `[flow]` and `[on]` — `[paths [path name= [args …] [results …]]…]`, the declared result table `simulate` runs at publish, slot-marked and filled from the same answers as the flow itself; a publish is refused unless its declared path reaches `:done`, and the studio shows that simulated overlay before the commit (`ux.md` P0-130). `on-change-act`'s own copy is graded by flow-180 (RULED: NAMES-1, AA-6). ### 12.5 The four runtimes — deployment-topology.md §1 **§1. The runtimes** Four daemons serve the platform, each with its own configuration document, its own XSP profile and its own journal. `cx --help` names three of them as subcommands — `store-serve`, `fabric-serve` and `flow serve`; the fourth is the XAP host a deployment boots through `[$xap:serve]` (`../xap/xap.md` §9, §25.2). | Runtime | Daemon | XSP profile | Binding fields | What it journals | |---|---|---|---|---| | **store** | `cx store-serve --config ` | the **STORE** profile — the store's network API IS the XSP store profile, and `cx-store://host:port/store-name/` dials the daemon's `[xsp]` listener over TLS (`cx-store+xsp://` is the cleartext sibling) | the daemon's `[xsp]` section: the listener, `[grants [grant did= caps= over=]…]`, `[limits …]`, `[revocations journal=]`, `[peers [peer url=]]`; the client side carries no userinfo — identity rides the open-opts `xsp-did` + `xsp-seed-env`, and the seed always names an env var | the fixed `store:log` lineage (#708): the docs plane plus one stream per named wire ref — the one log the local porcelain and the wire subscription read | | **fabric** | `cx fabric-serve --config ` | the **XAP** profile — fabric's verbs are that profile's vocabulary, spoken as raw XSP frames over tcp/tls; a client dials `[$fabric:open "xsp://…"]` and attaches under XSP-AUTH | the attr-exact-validated `fabric.service.cx`: `[bind]`, optional `[health]`, optional `[tls]`, `[identity did= seed-env=]`, `[policy]` (mutual \| floor), `[limits pending-window= liveness-ms= request-timeout-ms=]`, `[fabrics]` (mounts keyed by tenant), `[principals]`/`[anonymous]` carrying the grants | nothing of its own below itself: the durable plane IS the journal point served, and consumer groups and cumulative offsets are store-persisted | | **the flow runner** | `cx flow serve RUNNER.cx` | the **XAP** profile plus the `flow` semantic-feature token — a delegated intent crosses as the initiator's runner emitting the counterparty's act through its own authenticated XSP session, answered by a signed `step-ack` | the `[runner]` document: `[journal url=]`, `[docs url=]` (a Tier-1 `start=` address is fetched and verified here), `[env …]` (the resolver's module tree), `[store url=]`, `[ingress bind=]` (never a bare port), the `[on kind= …]` rows of §4.24's one vocabulary, and `[courier every=]` | one home stream per run: transitions as entries, the run record as fold plus snapshots, locator triples on effects | | **the XAP host** | `[$xap:serve URL {runtime: …}]`, bootstrapped onto `[?http-service]` / `[$http:serve]` — xap opens no socket of its own | the **XAP** profile over the v1 **web binding**: SSE server→client, POST client→server; raw TCP/TLS is buildable and the WebSocket binding is deferred | the deployment document's `[on …]` rows for the kinds this face serves, the runtime's registered components and surfaces, and the session/attribution posture a bound runtime refuses to boot without | the deployment's fold — the journal fold IS the state, which is why resume is application-anchored and transport-stateless | **Cited:** `store.md` §6.4 and its `wire` axis row; `../xap/xsp_store_profile.md` §6.1 (the `[xsp [grants …]]` section); `../xap/fabric.md` §13, §13.1, §19.6, and §2/§6/§9 for the durable plane; `flow.md` §4.23, §4.24 and §4.12b; `../xap/xsp.md` §4, §4.1, §5.3 and §6's profile registry; `../xap/xap.md` §9, §24 and §25.2. **Inside the XAP host, and inside it only.** Features run in the host, and the connector engine and the sync contract run inside a feature: the host calls a feature's `readout` / `apply` / `simulate` with the deployment context `$host`, `apply` delegates to the kit, and a `[capture]` is a child of the feature's `[gateway]` (`composition.md` S-24, S-25, S-27). Neither the engine nor the sync contract is a daemon, and neither is reachable across a runtime boundary except as the feature's verbs. **The fifth process that is not a runtime.** `cx flow run` from a checkout is a runner with no liveness guarantee and no bindings — the invocation IS the `start` (`flow.md` §4.24's runner table). It is named here so that "the flow runner" in this page always means the daemon. ### 12.6 The two shapes, and a worked binding — deployment-topology.md §2 **§2. The two shapes, as equals** A deployment runs the four runtimes in one process or in four, and the declarations are IDENTICAL in both: the same feature documents, the same flow document, the same seam rows. **Only the deployment binding changes.** | | same-process shape | distributed shape | |---|---|---| | store | opened in-process at a `mem://` or `file://` URL — the `embedded` deployment axis, where the substrate is named in the URI | `cx store-serve`, dialed at `cx-store://host:port/store-name/` — the `service` deployment axis, where the substrate hides inside the daemon | | fabric | opened in-process: durable streams over the process's own journal/store handles, transient streams process-local — no daemon, no network | `cx fabric-serve` alongside store-serve, dialed at `xsp://host:port`, MAY point at the same store its journal rides | | the flow runner | `cx flow run` over the checkout's journal, or the runner embedded in the XAP host | `cx flow serve RUNNER.cx`, its own journal and ingress | | the XAP host | the same `[$xap:serve]` process that holds everything above | its own process, reaching the other three as a client | **Cited:** `store.md`'s `deployment` axis (`embedded` · `service`); `../xap/fabric.md` §13's embedded and served tiers; `flow.md` §4.24 — "a flow moves between them without an edit, and what changes is where the runner runs, not what the flow says". **§2.1. A worked binding — `order-pipeline` in both shapes** `../../../reference/connectors/README.md` §2.3 states the example's two bindings in words: the graded scenario boots `crm-rest`'s mock and `orders-db`'s sqlite file plus a `kind=bus` gateway bound to an **embedded fabric over a `mem://` journal**, and the production deployment binds the NATS bridge instead. Written in the binding block that document proposes (`[deployment [runtime [connectors [connector feature= [gateway …] [tenant …]]]]]`, §2.1 there), the same-process shape is: ```cx [deployment name=order-pipeline-scenario [runtime [journal url='mem://order-pipeline/acts' stream=acts] [store url='mem://order-pipeline/store'] [connectors [connector feature=crm-rest [gateway name=crm-api base-url='http://127.0.0.1:8801' [retry max=4 delay=250ms backoff=exponential jitter=full] [timeout per-attempt=10s]] [tenant name=tenant-north [credential gateway=crm-api handle='handle:crm/sandbox/north-client']]] [connector feature=orders-db [gateway name=order-store backend='sql-db:orders-sandbox'] [tenant name=tenant-north [credential gateway=order-store handle='handle:orders/sandbox/north']]] [connector feature=order-events [gateway name=order-bus fabric='mem://order-pipeline/bus'] [tenant name=tenant-north [credential gateway=order-bus handle='handle:bus/sandbox/north']]]]]] ``` and the distributed shape differs in exactly the deployment facts `connector.md` §4.6 names — a different WHERE per gateway, a different handle per tenant — plus the three carriers this page adds: ```cx [deployment name=order-pipeline-production [runtime [journal url='cx-store://store.example.test:8443/order-pipeline/' stream=acts] [store url='cx-store://store.example.test:8443/order-pipeline/' xsp-did='did:key:z6Mk…' xsp-seed-env=ORDER_PIPELINE_SEED] [connectors [connector feature=crm-rest [gateway name=crm-api base-url='https://crm.example.test' [retry max=6 delay=500ms backoff=exponential jitter=decorrelated] [timeout per-attempt=20s]] [tenant name=tenant-north [credential gateway=crm-api handle='handle:crm/production/north-client']]] [connector feature=orders-db [gateway name=order-store backend='sql-db:orders-production'] [tenant name=tenant-north [credential gateway=order-store handle='handle:orders/production/north']]] [connector feature=order-events [gateway name=order-bus fabric='xsp://fabric.example.test:8450'] [tenant name=tenant-north [credential gateway=order-bus handle='handle:bus/production/north']]]]]] ``` The flow document, the three feature documents and every `[gateway]` DECLARATION are byte-identical between the two; what moved is a URL, a handle and a carrier. `xsp-did=` and `xsp-seed-env=` are the store client's open-opts under their own names (`store.md` §6.4), written where a deployment writes its other facts: the seed names an env var rather than a value, as §6.4 requires and as `connector.md` §4.6's "a handle, never a value" requires of every credential. **The spelling is the reference document's proposal, not a specification's.** `../../../reference/connectors/README.md` §8 item 1 records that `connector.md` §4.6 says WHAT a binding carries and no specification says how it is spelled; the blocks above inherit that proposal unchanged. The per-kind WHERE is likewise the ADAPTER's field and not this page's — §4.6 states `base-url=` for `kind=http` and `kind=soap`, a backend handle for `kind=db` and, for an operator's own kind, whatever its adapter declared — so `backend=` on the `kind=db` gateway and the fabric URL on the `kind=bus` gateway are written above under that rule, at the names the adapter owns. `connector.md` §4.6 has since fixed the credential spelling (RULED: 1466-a): a credential's `handle=` attribute carries a value in the `handle:/` grammar (`spec/03-approved/platform/connector.md` §4.6), and the blocks above cite that ruling rather than proposing their own. ### 12.7 The carrier rule — deployment-topology.md §3 **§3. The carrier rule** **Every cross-runtime seam between CX components is an XSP session under the callee component's profile, declared in the deployment binding, never discovered.** One carrier, stated once: 1. **The profile is the callee's**, from `../xap/xsp.md` §6's registry — the STORE profile toward a store, the XAP profile toward fabric, the flow runner and the XAP host. The generic frame layer never grows semantics; growth is only a token. 2. **The session is authenticated at attach.** XSP-AUTH is mutual, channel-bound and transcript-signed (`../xap/xap_identity_model.md` §4), and reconnect re-proves identity — resume never bypasses it. 3. **The address is bound, never discovered.** A URL, a DID and a seed env var are deployment data written in the binding (§2.1); no runtime learns a peer's address from a registry, a broadcast or another runtime's journal. 4. **In-process is the other carrier, and it is the same seam.** When both sides share a process the seam is an ordinary call across a `[?lib]` surface and no frame is encoded. The seam row does not change; its `Carrier` column does (§4 of `composition.md`, the `Carrier` column of its §2.1 and §2.2). **Integration edges, named as such.** Two wires cross a runtime boundary and are NOT this rule's subject, because the party on the far side is not a CX component: | Edge | What it is | Cited | |---|---|---| | `grpc` (store) | the gRPC edge adapter — `cx-store+grpc(s)://`, re-based onto the profile pipeline with per-call XSP-AUTH — for gRPC-speaking environments. The CX-to-CX wire is `cx-store://` | `store.md` §6.4 and its `wire` axis row | | the NATS bridge (fabric) | an ordinary edge client on fabric's remote tier (`tooling/cxfabric/nats-bridge.cx`), the legacy-migration seam — a bridge to a non-CX broker, not a seam between two CX runtimes | `../xap/fabric.md` §14, §18 (#547) | A third wire is an edge of the same kind in the other direction: the webhook adapter's HTTP/SSE reach (`../xap/fabric.md` §13's first adapter). An edge carries no CX-to-CX seam and never appears in a seam row's `Carrier` column. ### 12.8 What differs across a runtime boundary — deployment-topology.md §4 **§4. What differs from in-process — three rules a pattern may rely on** A pattern composed in one process and deployed across four is the same pattern; these three rules are everything that changes, and a pattern may rely on them. **§4.1. Identity crosses as signed claims, never as an in-memory `$host`** In one process a feature call carries the deployment context `$host` (`composition.md` S-27). Across a runtime boundary there is no `$host` to carry: the session principal proved key control in the XSP-AUTH handshake, and authority reaches the session as verifiable credentials presented on the control stream — a presented VC's subject MUST be the session principal, and there are no bearer credentials in this model (`../xap/xap_identity_model.md` §4, §5.1). The daemon's own local grants are the `[xsp [grants [grant did= caps= over=]…]]` section, and each matching grant compiles at attach into an ordinary `[delegation …]` in the session's authority basis, decided by the one decision function (`../xap/xsp_store_profile.md` §6.1; `../xap/xap_identity_model.md` §5.3). A configuration WITH a grants section is deny-by-default. **What this means for a pattern.** A cross-runtime step's authority is the callee's compiled basis — local grants plus that session's verified credentials, intersected with the envelopes — and never the caller's memory. A pattern that would pass an in-memory context across a seam has no distributed form; a pattern that names a principal and a capability has the same form in both shapes. **§4.2. A session can drop mid-act, so a cross-runtime step is at-least-once** `../xap/xsp.md` §5 is normative and applies to every profile unchanged: `ping`/`pong` liveness with the server's advertised window (§5.1); receiver-driven credit windows, where the sender at zero credit MUST stop pushing (§5.2); and reconnect-resume that is application-anchored and transport-stateless — the transport buffers nothing across connections, the durable log is the resume source, and the resume token is the durable event's journal sequence (§5.3). Two consequences a pattern may rely on: - **A cross-runtime step is at-least-once and completes on an acknowledgment.** A delegated intent is answered by a signed `step-ack` naming the counterparty's effect locator and the initiator's locator it acted on (`flow.md` §4.12b); the run id in it is a correlation key and never an authority input. - **Every act a cross-runtime step emits is idempotent, or is recorded as not.** `order-pipeline`'s bounded re-attempt (`attempts=`/`every=`) is admitted only because the consume verb declares `[idempotent]`, and the one step that cannot be retried carries `pivot=true` (`../../../reference/connectors/README.md` §2.3). A step that reaches no module's state has nothing to retry: a transform step performs no act, opens no connection, reads no store and holds no capability — only the run record (`flow.md` §4.4a, RULED: WF-42, CK-4b's recorded-intent shape). **§4.3. Each runtime keeps its own journal, and no seam reads another's** A runtime's journal is its own: the store's `store:log` lineage, fabric's served durable plane, the runner's home stream per run, the host's fold (§1). Across a boundary, each party's journal holds its half — "there is no shared record and no shared engine", and disputes settle by comparing the two chains at the locators the acknowledgments name (`flow.md` §4.12b). **A cross-runtime seam exchanges records — acts, acknowledgments, effects — and never shared state.** A pattern that reads a peer's journal to learn what happened has no distributed form; a pattern that reads an acknowledgment does. # Reference: the CX data language — v0.18.0-pre.1 > **GENERATED.** Source: `docs-src/llm/reference-data-language.md.tmpl` + the > conformance corpus. Every output was re-recorded from the `cx` v0.18.0-pre.1 > binary. Read `primer.md` first. This is Ring 0: the reading, the canonical form, and the conversions. No evaluator exists in this ring, so nothing here can execute. ## Elements A document is a sequence of elements. An element is `[name …]` with optional attributes and children; children may be elements, text, or collections. `input.cx` ```cx [a href=https://example.com/path Click here] ``` ```console $ cx --from=cx --to=cx input.cx [a href=https://example.com/path Click here] ``` Attribute order is not significant to equality; the canonical form fixes one order so that hashing and diffing are stable. ## Scalars and auto-typing Bare tokens are typed on read. `true`/`false` are booleans, integers are ints, fractions are **decimals**, `null` is the explicit null, and anything else is an atom. Quoting suppresses all of it and gives you a string. `input.cx` ```cx [active true] ``` ```console $ cx --from=cx --to=cx input.cx [active true] ``` ### Decimal and float are two kinds, and the literal decides A bare fraction is a **decimal**: exact, base-10, arbitrary precision. A float arises only from an `e`-exponent literal (`1.5e0`), a `::float` annotation, `[cast … :float]`, a division of ints whose quotient is not whole (`[/ 7 2]` — `[/ 6 2]` is the int `3`, and a decimal or bigint operand divides exactly into a decimal, code.md §6.5, CO-17), `[$avg …]` of ints or floats, and the float-returning `math` functions; `[$avg …]` of decimals keeps the exact decimal (`[$avg [0.5, 1.5]]` is `1.00`). ``` 1.5 decimal 1.5e0 float 19.99 decimal [/ 7 2] float [* 2 1.5] decimal [$avg [2, 4]] float [$avg [0.5, 1.5]] decimal ``` They do not mix in arithmetic — `[cast]` is the only bridge — and they are distinct to identity: the two spell different canonical bytes, hash differently, and `[$eq]` says `false`. `primer.md` §2 carries the worked refusal. **The float image is exponent form.** A float's canonical image is Ryū exponent notation (`3.5e0`, `1.024e3`) in every position, attribute and body alike; a decimal's image is the fraction as written (`1.5` stays `1.5`, `25.00` stays `25.00`); and a `date` / `datetime` **attribute** value renders bare. That asymmetry is deliberate and load-bearing — it is what makes the canonical form *bijective*, so a fraction written without an exponent reads back as a decimal and never as a float. > Images in this pack are re-recorded from the binary that generated the > page, and the canonical-image repair (`RULED: CO-12`) is landing across the > emitters. If a float example here still shows a bare image, that binary > predates the repair — `cx canonical` on the binary you are running is > always the authority, and this page is regenerated against it. A value that must survive as text — a version number, an id with dots, a URL — has to be quoted, and the canonical form keeps the quotes: `input.cx` ```cx [m {k: 'a b', j: 'a.b', u: 'https://a.com', ok: 'x', p: 'a/b'} [note a b]] ``` ```console $ cx --from=cx --to=cx input.cx [m {k: 'a b', j: 'a.b', u: 'https://a.com', ok: x, p: a/b} [note a b] ] ``` ## Text, mixed content, and raw text Text children and element children can interleave. `input.cx` ```cx [p text [b bold] more] ``` ```console $ cx --from=cx --to=cx input.cx [p 'text ' [b bold] ' more'] ``` Raw text is written `[# … #]` and is **opaque**: no entity expansion, no escaping, no reinterpretation. It is how CX carries a payload in another language — SQL, a shell script, a regex, prose with brackets in it — without the reader touching a byte. `[$text]` cannot see inside a raw block; the serialization is its defined byte-exact projection, which is why tooling that needs the payload (including the generator that produced this page) reads it by serializing and stripping the delimiters. The corresponding fixture is `012-rawtext` in `conformance/core.cxd`; it cannot be *shown* here, because a raw block containing raw-block delimiters is exactly the case that defeats that extraction — and this layer refuses to print a fixture it cannot reproduce byte-exactly. ## Namespaces A `ns:name` head or attribute is namespaced. Namespaces are part of identity: two documents differing only in prefix binding are not equal. `input.cx` ```cx [doc xmlns=urn:doc xmlns:xl=http://www.w3.org/1999/xlink [xl:a href=https://example.com Link] ] ``` ```console $ cx --from=cx --to=cx input.cx [doc xmlns=urn:doc xmlns:xl=http://www.w3.org/1999/xlink [xl:a href=https://example.com Link] ] ``` ## Operator-headed elements A **delimited** operator glyph in head position names an element. The head set is the evaluator's, and it is closed at eighteen names, twelve of them glyphs: ``` one-char + * - / % = < > ~ two-char != <= >= ``` "Delimited" means the glyph is followed by whitespace or `]`. So `[% 7 3]` is an element named `%`, and the data reading keeps it that way: `input.cx` ```cx [% 7 3] ``` ```console $ cx --from=cx --to=cx input.cx [% 7 3] ``` `input.cx` ```cx [!= 5 3] ``` ```console $ cx --from=cx --to=cx input.cx [!= 5 3] ``` Two guard rails matter, because both used to be soundness holes rather than formatting warts: * **Out-of-set glyphs are not heads.** `**` and `==` are absent from the ruled set, so `[== 5 3]` stays an array and canonicalizes as the string `'== 5 3'` — deliberately, and pinned. * **Glued continuations are not heads.** `[-1, 2]` is an array with a negative number, `[*n]` is an alias reference, `[<=x]` is an array item. Only the delimited glyph makes the bracket an element. Getting this wrong is not cosmetic: before the alphabet was closed, `[<= 5 3]` canonicalized into the very bytes of `['<= 5 3']` and shared its content address — two documents the evaluator reads differently at one address. ## Anchors and references (identity) `@name` declares an anchor; a reference resolves to the anchored node. Forward references resolve — the document is not read strictly top-to-bottom for identity purposes. `input.cx` ```cx [users [reviewer assigned-to=@u-1] [user #u-1 name=alice] ] ``` ```console $ cx --from=cx --to=cx input.cx [users [reviewer assigned-to=@u-1] [user #u-1 name=alice] ] ``` ## Tables `[table[…]]` is the columnar block: typed columns, rows as records. It is a first-class data form, not a convention over elements, which is what lets `cx table` project it to Parquet/Arrow without guessing. `input.cx` ```cx [users [table[name::string age::int active::bool]] alice 30 true bob 25 false ] ``` ```console $ cx --from=cx --to=cx input.cx [users [table[name::string age::int active::bool]] alice 30 true bob 25 false ] ``` ## Conversions The data reading round-trips to and from XML, JSON, YAML, TOML, CSV/TSV/PSV and Markdown. `--lossless` adds the markers that make the round trip byte-identical rather than merely equivalent. ```console cx --from=json --to=cx in.json cx --from=cx --to=xml doc.cx cx --from=cx --to=json --lossless doc.cx # re-imports byte-identically cx --xml doc.cx # projection shorthand ``` The XML projection is the one to check your mental model against, because the two models are close but not identical: `input.xml` ```xml Click ``` ```console $ cx --from=xml --to=cx input.xml [a href=https://example.com class=link Click] ``` ## Canonical form, equality, hashing, diffing Ring 0's whole value proposition is that these four agree: ```console cx canonical doc.cx # strict canonical text (presentation stripped) cx fmt doc.cx # lossless format (comments and anchors preserved) cx hash doc.cx # SHA-256 of the strict-canonical bytes cx eq a.cx b.cx # exit 0 iff canonical(a) == canonical(b) cx diff a.cx b.cx # semantic diff over the canonical forms ``` `cx fmt` and `cx canonical` are different tools on purpose: `fmt` is what you run on source, `canonical` is what identity is defined over. ### CX has two equality notions, and they are not the same notion This trips up anyone who assumes a language has one `==`. Both of these are deliberate, and neither may drift into the other: | Notion | Reached by | What it does | |---|---|---| | **Value equality** | `[$eq a b]`, `[= a b]`, `[?match]`, `[$distinct]` | Compares under the atomization policy: an element with single scalar content compares as that value, and a scalar compares against the string of its image | | **Identity** | `cx canonical`, `cx hash`, `cx eq`, store handles | Type-faithful over the strict-canonical bytes: it distinguishes exactly the pairs value equality merges | So a decimal attribute and the string of its image compare *equal* as values, by design: `prog.cx` ```cx [probe dec-str=[$eq [u a=1.5] [u a='1.5']] atom-str=[$eq [u a=:ops] [u a='ops']]] ``` ```console $ cx prog.cx [probe dec-str=true atom-str=true] ``` …while `cx hash` mints them distinct addresses. Pick the notion you meant. If you are asking "is this the same document" — for caching, deduplication, or a content address — you want identity, not `[$eq]`. Canonical serialization is **bijective**: every scalar kind has a type-faithful image in every position, so `parse(canonical(v))` returns the same typed value. That is what lets a content address stand for a value rather than merely for its text. ## Schemas `.cxs` files describe documents; `cx validate` checks one against a schema, `cx schema infer` derives a schema from a corpus, and `cx schema export` projects it to JSON Schema. ```console cx validate doc.cx --schema=shape.cxs cx schema infer corpus/*.cx ``` # Reference: the CX code language — v0.18.0-pre.1 > **GENERATED.** Source: `docs-src/llm/reference-code-language.md.tmpl` + the > conformance corpus. Every output was re-recorded from the `cx` v0.18.0-pre.1 > binary. Read `primer.md` first. Ring 1. A program is a document whose elements include directives. Directives are spelled `[?name …]`; the normative registry is `spec/03-approved/core/code.md` §4.1, and every directive in it is exercised by `conformance/code.cxd`. ## Binding `[?let]` takes any number of `[= $name value]` bindings and one body. The bindings are sequential: each sees the ones before it. There is no `let*`, because there is nothing for it to do. `prog.cx` ```cx [?let [= $x 9] [?let [= $x 1] [= $y $x] [pair x=$x y=$y]]] ``` ```console $ cx prog.cx [pair x=1 y=1] ``` `[?with-scope]` merges a map into scope for a body — the form to reach for when the names come from data rather than from source. `prog.cx` ```cx [?with-scope {a: 1, b: 2} [?test-current-scope]] ``` ```console $ cx prog.cx 'a=1,b=2' ``` ## Control flow `[?if COND [then …] [else …]]`. The clause names are mandatory: `[?if c a b]` is a clause-shape refusal (`cx-err:CXER0111`), not a positional shorthand. `prog.cx` ```cx [?if true 'a' 'b'] ``` ```console $ cx prog.cx error: cx-err:CXER0111: [?if] expects [then …] / [else …] clause children after the condition — got a bare positional branch expression; write [?if cond [then thenExpr] [else elseExpr]?] at line 1:11 ``` `[else]` may be omitted, and a false condition then yields empty: `prog.cx` ```cx [out [ok [?if false [then 'a']]]] ``` ```console $ cx prog.cx [out [ok]] ``` Truthiness is defined per type, not by coercion to boolean: zero and empty string are falsy, an empty *element* is truthy (it exists), and an empty match set is falsy — which is what makes the existence idiom work: `input.cx` ```cx [app [flag]] ``` `prog.cx` ```cx [?if //flag [then "on"] [else "off"]] ``` ```console $ cx --data=input.cx prog.cx on ``` ## Pattern matching `[?match VALUE [case PATTERN result] … [else result]]`. Arms are tried in order. `[when …]` adds a predicate arm, `[where …]` guards a case. `prog.cx` ```cx [?let [= $u [user age=20 [name Alice]]] [?match $u [case [user$u] [where [>= $u@age 18]] :adult] [case [user$u] :minor]]] ``` ```console $ cx prog.cx :adult ``` A match with one `[case]` arm and no `[else]` that does not match RAISES rather than returning empty — matching is a claim, and a failed claim is an error: `prog.cx` ```cx [?match [foo "x"] [bar $b] [yield $b]] ``` ```console $ cx prog.cx error: cx-err:CXER0100: [?match] no match for value (single-arm form) at line 1:1 ``` Dropping the arm keywords entirely gives the **single-arm** form — `[?match SUBJECT PATTERN BODY]` — which takes the full pattern grammar and is the destructure-or-refuse bind: `prog.cx` ```cx [?def apply-op ($stack $op) [?match $stack ($b, $a, *$rest) [yield [$op $a $b]]]] [$apply-op (3, 4, 9) [?fn ($x $y) [+ $x $y]]] ``` ```console $ cx prog.cx 7 ``` ## Comprehensions `[?for]` is the one iteration directive: `[in $x SOURCE]` clauses (more than one nests), `[where]` filters, `[group-by]` partitions and binds `$count` and `$group`, `[order-by]` sorts, `[yield]` emits. The bind position takes the **full pattern grammar** — sequence, array, map, element, typed binds, rest `*$name` — so key/value iteration needs no index arithmetic: `prog.cx` ```cx [?for-map [in ($k, $v) (("a", 1), ("b", 2))] [yield-map $k [* $v 10]]] ``` ```console $ cx prog.cx {a: 10, b: 20} ``` A bind-form pattern that an item does not fit **refuses**, naming the item's ordinal — it is never a silent skip, because a skip is silent data loss. (Element patterns keep their search semantics and still skip: they are a claim about which nodes to visit, not about every item's shape.) `prog.cx` ```cx [?for [in ($k, $v) (("a", 1), ("b"))] [yield $k]] ``` ```console $ cx prog.cx error: cx-err:CXER0100: [?for] generator pattern does not match item 2 (NO_MATCH): the item `(b)` does not fit the pattern of the [in …] clause at line 1:7. `[in PATTERN SRC]` is a destructuring BIND — every item of SRC must fit the pattern. To keep only the items that fit, filter by shape with [where] or a [?match] in the yield body (code.md §7.5) ``` `input.cx` ```cx [users [user active=true [name Alice] [email a@x.com]] [user active=false [name Bob] [email b@x.com]] [user active=true [name Carol] [email c@x.com]]] ``` `prog.cx` ```cx [?for [user @active=true [email $e]] [yield $e]] ``` ```console $ cx --data=input.cx prog.cx 'a@x.com' 'c@x.com' ``` `prog.cx` ```cx [?let [= $orders [orders [order region=east [line qty=2 price=10] [line qty=1 price=5]] [order region=west [line qty=3 price=4]] [order region=east [line qty=1 price=100]]]] [?for [in $o $orders/order] [in $l $o/line] [= $r [* $l@qty $l@price]] [group-by $o@region] [yield [row region=$key revenue=[$sum $group/r] n=$count]]]] ``` ```console $ cx prog.cx [row region=east revenue=125 n=3] [row region=west revenue=12 n=1] ``` ## CXPath Paths are values: `$doc/child`, `/rooted`, `//descendant`, with predicates. They work as a `[?for]` source, as an `[?if]` condition (truthy iff the match set is non-empty), and anywhere a value is expected. `input.cx` ```cx [users [user [name Alice] [email a@x.com]] [user [name Bob] [email b@x.com]]] ``` `prog.cx` ```cx //user ``` ```console $ cx --data=input.cx prog.cx [user [name Alice] [email a@x.com]] [user [name Bob] [email b@x.com]] ``` `input.cx` ```cx [users [user active=true [name Alice]] [user active=false [name Bob]] [user active=true [name Carol]]] ``` `prog.cx` ```cx //user[= $_@active true] ``` ```console $ cx --data=input.cx prog.cx [user active=true [name Alice]] [user active=true [name Carol]] ``` `input.cx` ```cx [users [user active=true [name Alice]] [user active=false [name Bob]] [user active=true [name Carol]]] ``` `prog.cx` ```cx [$count //user[= $_@active true]] ``` ```console $ cx --data=input.cx prog.cx 2 ``` ## Functions `[?def name [attrs] (params) body]` defines; `[?fn (params) body]` is the anonymous form. Definitions are order-independent and mutually recursive. `scope=public` exports. `pure`/`impure` is checked. `[returns T]` and `::T` annotations document by default and are enforced under `--strict`. `prog.cx` ```cx [?def greet scope=public [returns string] ($name::string) [$concat "hello, " $name]] [$greet "world"] ``` ```console $ cx prog.cx 'hello, world' ``` `prog.cx` ```cx [?def even-p ($n) [?if [= $n 0] [then true] [else [$odd-p [- $n 1]]]]] [?def odd-p ($n) [?if [= $n 0] [then false] [else [$even-p [- $n 1]]]]] [$even-p 10] ``` ```console $ cx prog.cx true ``` Defs are first-class values, so a def name is usable wherever a function is: `prog.cx` ```cx [?def dbl ($x) [* $x 2]] [?map (1, 2, 3) [using $dbl]] ``` ```console $ cx prog.cx (2, 4, 6) ``` Nesting a `[?def]` inside another is refused (`cx-err:CXER0204`) — definitions are module-level: `prog.cx` ```cx [?let [= $f [?fn () [?def inner ($x) [* $x 2]]]] [$f]] ``` ```console $ cx prog.cx error: cx-err:CXER0204: cx-err:CXER0204 E_NESTED_DEF: `[?def inner]` is not permitted inside a function body (definitions are top/module-level only) ``` ## Pipes `[?pipe]` threads a value through stages. There is a canonical form and infix sugar; both parse to the same thing. `prog.cx` ```cx [?pipe (1, 2, 3, 4) [?fn $xs [?for [in $x $xs] [where [> $x 2]] [yield $x]]] $count] ``` ```console $ cx prog.cx 2 ``` `prog.cx` ```cx [?pipe (1, 2, 3, 4) [?fn $xs [?for [in $x $xs] [where [> $x 2]] [yield $x]]] $count] ``` ```console $ cx prog.cx 2 ``` ## Iterators and reduction The iterator family is lazy: `[?map]`, `[?filter]`, `[?take]`, `[?drop]`, `[?zip]`, `[?enumerate]`, `[?chunks]` build a pipeline, and a consumer forces it. `[?reduce]` folds; `[par]` marks an associative fold as parallelisable. `prog.cx` ```cx [?to-sequence [?take 3 [?map (10, 20, 30, 40, 50) [using [?fn $i [+ $i 1]]]]]] ``` ```console $ cx prog.cx (11, 21, 31) ``` `prog.cx` ```cx [?reduce (1, 2, 3, 4) [using [?fn ($a $b) [- $a $b]]] [init 10]] ``` ```console $ cx prog.cx 0 ``` An `[?reduce]` declared `ordered` where the operation is not is refused rather than silently reordered: `prog.cx` ```cx [?reduce (1, 2, 3) [using [?fn ($a $b) [+ $a $b]]] [init 0] [ordered]] ``` ```console $ cx prog.cx error: cx-err:CXER0100: [?reduce] takes a single positional source slot at line 1:1 ``` ## Transformation `[?modify]` rewrites a document by matching positions and replacing them. It is the transform half of the language, and the reason CX does not need a separate template dialect. `prog.cx` ```cx [?let [= $d [teams [team name="alpha" [member id=1]] [team name="beta" [member id=2]]]] [?let [= $flag "alpha"] [?modify $d //team[= $_@name $flag]/member [set-attr flagged true]]]] ``` ```console $ cx prog.cx [teams [team name=alpha [member id=1 flagged=true]] [team name=beta [member id=2]]] ``` ## Construction — what actually becomes a child This is the rule most likely to break a program written from priors, because every templating language you have seen splices a list into a slot silently. CX does not. **A non-empty sequence value never becomes element content.** Writing `[violations $vs]` where `$vs` is a sequence is refused loudly, and the diagnostic names the idiom you wanted: `prog.cx` ```cx [?let [= $vs ([violation kind=a], [violation kind=b])] [violations $vs]] ``` ```console $ cx prog.cx error: cx-err:CXER0100: a sequence value (2 members) cannot be element content — adopt its members with [?splice EXPR] (in [violations …]; first member [violation …]; code.md §6.4.1, #847-1a) ``` `[?splice]` is that idiom. It grafts the members in as siblings, and the child axis then sees them: `prog.cx` ```cx [?let [= $vs ([violation kind=a], [violation kind=b])] [?let [= $bag [violations [?splice $vs]]] [$count $bag/violation]]] ``` ```console $ cx prog.cx 2 ``` Auto-splicing was considered and rejected: it silently rewrites what the author wrote, and it makes `/*` depend on where a value came from. One loud, early refusal is the answer instead. ### The four rules that follow from it **1. Directives contribute; values are adopted or refused.** `[?splice]` and `[?for]` in a multi-sibling slot are *multi-sibling contributors* — `[?for]` contributes one sibling per `[yield]`, which is its defined contribution, not a value being auto-spliced: `prog.cx` ```cx [?let [= $t [c [?for [in $x ('A', 'B')] [yield [x $x]]]]] [$count $t/x]] ``` ```console $ cx prog.cx 2 ``` Everything else — a binding read, a call result, an `[?if]` / `[?let]` / `[?match]` result — contributes exactly *one* child, so one of those yielding a non-empty sequence refuses the same way. Conditional multi-contribution is spelled `[?splice [?if …]]`: `prog.cx` ```cx [?let [= $vs ([v k=1], [v k=2])] [c [?if [= 1 1] [then $vs]]]] ``` ```console $ cx prog.cx error: cx-err:CXER0100: a sequence value (2 members) cannot be element content — adopt its members with [?splice EXPR] (in [c …]; first member [v …]; code.md §6.4.1, #847-1a) ``` **2. Dispatch consumes arguments; only plain construction refuses.** A bracketed form that *dispatches* — a `$`-head call, an operator head (`[= …]`, `[+ …]`), a closure call — takes an operand-produced sequence as an ARGUMENT, which is legal everywhere. An argument is not content: `input.cx` ```cx [ignored] ``` `prog.cx` ```cx [?let [= $doc [order [line qty=2 price=10] [line qty=1 price=20] [line qty=3 price=5]]] [$sum $doc/line/@price]] ``` ```console $ cx --data=input.cx prog.cx 35 ``` The refusal applies only to a form that *is* plain element construction: a bare data head, a bare named-builtin head (named builtins are reached only as `[$name …]`), and always the computed-name `[?element …]` form. **3. Absence contributes nothing.** An item that evaluates to the empty sequence — an absent optional read, an empty comprehension — contributes no child at all. It is not adopted as a visible empty child: `prog.cx` ```cx [?let [= $u [user [tag 'a']]] [$count [c $u/nothing]/*]] ``` ```console $ cx prog.cx 0 ``` **4. The loop carriers are exempt.** `[break …]` and `[continue …]` are the `[?loop]` protocol's *positional value carriers*, not documents. Every expression there contributes exactly one value: an absence rides as the empty sequence and a sequence rides whole — because the loop rebinds its declared bindings positionally, and a dropped or spliced value would silently shift every binding after it. `prog.cx` ```cx [?let [= $u [user [tag 'a']]] [?loop [= $none $u/nothing] [= $seq ('x', 'y')] [= $n 0] [?if [= $n 0] [then [continue $u/nothing ('x', 'y') [+ $n 1]]] [else [break [list [$count $none] [$count $seq] $n]]]]]] ``` ```console $ cx prog.cx [list 0 2 1] ``` ### Position, not value The discriminator is where a thing is written, not what it looks like — the same principle as the literal-`[err]` rule below. A **source-literal** paren sequence in element content is data, and keeps the data reading's navigation (`/name` opaque, `//` through, `/*` sees the sequence node): `prog.cx` ```cx [?let [= $t [pair ([v k=1], [v k=2])]] [list [$count $t/v] [$count $t//v] [$count $t/*]]] ``` ```console $ cx prog.cx [list 0 2 1] ``` ## Errors An error is the value `[err code=… message=… …]`. It is produced, returned, bound, and inspected like any other value; it propagates out of the call that made it and through anything constructed from it. `prog.cx` ```cx [?let [= $seq [?for [in $x ("a")] [yield $x]]] [section [div [$concat $seq "-x"]]]] ``` ```console $ cx prog.cx [err code=cx-err:CXER0100 message='concat: an argument does not satisfy the builtin signature (scalar kind/type — code.md §6.5)' at='2:17'] ``` A literal `[err …]` written in *data* stays data — the propagation rule is about computed positions, not about the head name: `prog.cx` ```cx [foo [err code='x' message='y']] ``` ```console $ cx prog.cx [foo [err code=x message=y]] ``` `[?else VALUE FALLBACK]` is the recovery idiom. There is no `try`/`catch` directive; `[?try]` and `[?chain]` were retired from the surface and now appear only in negative fixtures. ## Capabilities Every effect names the capability it needs, and a denial is an ordinary error value carrying the flag that would have granted it: `prog.cx` ```cx [?lib 'cx-stdlib/io'] [$io:read-file "/var/log/app.log"] ``` ```console $ cx prog.cx [err code=cx-err:CXER0271 message='E_CAP_DENIED: read capability required for io-read-file; none granted (grant via --allow-read)' at='2:1'] ``` The nine capabilities are `read write net env clock random subprocess eval secret-reveal`. `--allow-common` is all but `secret-reveal`; `--allow-all` includes it. # Reference: the CX standard library — v0.18.0-pre.1 > **GENERATED.** Source: `docs-src/llm/reference-stdlib.md.tmpl` + the module > sources and the conformance corpus. The catalogs below are projected from > each module's own `[module-doc]`, so a bundled module cannot be missing > from this page. Read `primer.md` first. ## Importing `[?lib 'cx-stdlib/strings']` binds the module under its own name. `[?lib 'cx-stdlib/strings' as=str]` renames it. Two import prefixes: * **`cx-stdlib/…`** — the frozen standard set. Stable surface. * **`cx-platform/…`** — the platform modules: the ones that serve, or reach a store or a protocol (`reference-platform.md`). The XAP orchestrator is one of them (`[?lib 'cx-platform/xap' as=xap]`), and so is fabric (`[?lib 'cx-platform/fabric']`); both had names of their own, `cx-xap` and `cx-fabric`, which are retired and refuse naming the new spelling. So are the eleven modules that were the `cx-x/` experimental tier (`run`, `llm`, `mcp`, `mcp-server`, `a2a`, `a2a-xap`, `adjudicate`, `tools`, `ux`, `ux-web`, `ux-tui`): each imports as `cx-platform/`, and the old spelling refuses naming the new one. A call is `[$module:fn args…]`. The `$` is not optional stylistically — it is the assertion that this is a call rather than an element you are building (see the anti-patterns in `primer.md`). `prog.cx` ```cx [?lib 'cx-stdlib/math' as=m] [$m:abs -7] ``` ```console $ cx prog.cx 7 ``` ## Standard tier — `cx-stdlib/` | Module | Scope | |---|---| | `authz` | The XAP authority model as data plus a single decision function. | | `bus` | In-process publish/subscribe that delivers each published message to its matching subscribers synchronously and in a defined order. | | `bytes` | Byte-level operations on the CX bytes scalar kind. | | `crypto` | Operations involving a key, a secret, or authentication. | | `csv` | Parse and emit CSV/TSV following RFC 4180 with Excel-pragmatic extensions. | | `diagram` | The §10.1.2 reference diagram renderer as a pure CX program (RULED #758, DR-1…DR-11). | | `did` | Decentralized identifiers (DIDs): the decentralized identity source that identifies a principal, counterpart to crypto's centralized JWT/JWKS. | | `email` | Parse and build RFC 5322 + MIME multipart email messages. | | `env` | Expose process-level metadata to CX code: environment variables, command-line arguments, and process identity. | | `format` | Emit CX values back to CX text in four forms: canonical, pretty, compact, and diff-friendly. | | `fp` | Functional composition over the four value channels. | | `ft` | In-program fulltext search with structured ranking and snippet generation. | | `geo` | Coordinate primitives without a new scalar kind — geometries are ordinary CXDM elements. | | `graphql` | The GraphQL client codec: text and values in, text and values out. | | `hash` | Content-addressable hashing — fixed-length digests of arbitrary byte payloads. | | `pdf` | Text extraction from a PDF byte string, pure and with no I/O. | | `image` | Image derivatives from an uploaded byte string, pure and with no I/O: decode JPEG, PNG, GIF (first frame), BMP and WebP (lossy, lossless, alpha, the first frame of an animation); turn the frame upright by its EXIF orientation; crop; resize with a stated filter (fill, contain or cover a box); encode PNG, JPEG or WebP with every metadata block dropped. | | `html` | HTML as a first-class document format: parse, sanitize, serialize, and extract text. | | `http-client` | HTTP/1.1 request/response semantics from the CLIENT side, plus the message codec both halves of HTTP share: method verbs, headers, materialized bodies, redirects, content decoding, connection pooling, and the Server-Sent Events read side. | | `i18n` | The message and translation layer of CX's internationalization surface. | | `io` | File and stream I/O — whole-file reads and writes, streaming handles, line iteration, filesystem queries, directory operations, globbing, tempfiles, and advisory file locking. | | `json` | Parse JSON (RFC 8259) into CXDM values and emit CXDM values back to JSON. | | `locale` | The primitives layer of CX's internationalization surface. | | `log` | Structured logging: leveled emit with arbitrary structured fields. | | `math` | Numeric utilities over CX int and float scalar kinds. | | `mime` | MIME data-shape handling: a built-in extension-to-type registry, Content-Type and Content-Disposition parsing, multipart boundary generation, type classification, and Accept-header content negotiation. | | `oidc` | The OpenID Connect relying party: the login half of enterprise SSO. | | `path` | Filesystem path manipulation — splitting, joining, normalizing, and comparing OS paths. | | `process` | Run child processes: spawn them, capture their output, stream their stdio, connect them into pipelines, deliver signals, manage process groups, and drive pseudo-terminals. | | `prof` | In-program profiling, callable directly from CX code. | | `random` | Two distinct randomness facilities, kept cleanly separated so PRNG output can never accidentally stand in for crypto. | | `re` | Regular expressions backed by the RE2 engine, guaranteeing linear-time matching against any input with no catastrophic backtracking. | | `saml` | The SAML 2.0 service-provider verify core: XML-DSig verification and assertion validation as a pure codec, the enterprise counterpart to cx-stdlib/oidc. | | `sasl` | The SASL mechanism registry and framing codec, shared by cx-platform/smtp and cx-platform/imap. | | `sched` | Scheduled events and timers on the event loop. | | `scim` | SCIM 2.0 provisioning semantics — the PROVISIONING half of enterprise SSO, counterpart to cx-stdlib/oidc's login half. | | `similar` | Graded comparison as a generalization of exact equality: where = returns a boolean, the core ~ operator returns a score in [0,1] plus evidence, and a decision policy maps the score to :match / :review / :no-match bands. | | `soap` | The SOAP codec: octets and values in, octets and values out. | | `strings` | String inspection, search, and transformation for general text work. | | `supervise` | Restart policies over monitored workers: run a set of named children (each an arity-0 callable spawned as a worker) under a declared policy — strategy (:one-for-one, :one-for-all, :rest-for-one), restart intensity (max-restarts within a window), and per-child exponential backoff — restarting them when they die according to each child's restart type (:permanent, :transient, :temporary). | | `tar` | Tar archive codec over the CX bytes scalar kind. | | `term` | Native raw-mode terminal input for interactive TUIs (#30; Ring 1 since RS-5, #1591 item 7). | | `test` | Authoring primitives for unit-test-style programs written in CX: assertions, fixtures, lifecycle hooks, and structured reporting. | | `time` | Dates, datetimes, durations, and instants, with wall-clock and monotonic time sources. | | `url` | RFC 3986 and WHATWG-URL-aligned URL parsing, building, and component encoding. | | `uuid` | Generate, parse, format, and validate Universally Unique Identifiers. | | `validate` | Validate a CX data record against a record-schema at runtime, in the JSON-Schema / pydantic style. | | `vc` | Verifiable credentials: portable, signed, attenuating delegations that carry authority between DIDs and verify offline (the §22.2 delegation transport, counterpart to `did` for identity). | | `ws` | The RFC 6455 WebSocket frame codec: octets and values in, octets and values out. | | `xsp` | The XAP Stream Protocol frame codec — a self-describing, self-delimiting frame [version · type · stream-id · principal-DID · flags · len · payload] that carries XAP over any transport. | | `zip` | Zip archive codec over the CX bytes scalar kind. | ## The packs that need a capability Pure modules (`strings`, `math`, `map`, `array`, `fp`, `json`, `re`, `path`, `format`, `bytes`, `url`, `mime`, …) need no grant. The effectful ones name their capability at the point of the effect, so you discover the requirement by running rather than by reading: | Pack | Capability | Grant | |---|---|---| | `io` (files, dirs) | read / write | `--allow-read`, `--allow-write` | | `net`, `http` | net | `--allow-net[=host[:port]]` | | `env` (`var`, `vars`) | env | `--allow-env` | | `time` (now, clocks) | clock | `--allow-clock` | | `random`, `uuid` | random | `--allow-random` | | `process` | subprocess | `--allow-subprocess` | | `cx` (`[?eval]`) | eval | `--allow-eval` | | secret declassification | secret-reveal | `--allow-all` | `env:argv` and `env:parse-args` are **ungated** — reading your own arguments is not an effect. `prog.cx` ```cx [?lib 'cx-stdlib/env'] [$env:var "CX_DEFINITELY_UNSET_VAR_XYZ_001"] ``` ```console $ cx prog.cx [err code=cx-err:CXER0271 message='E_CAP_DENIED: env capability required for env-var; none granted (grant via --allow-env)' at='2:1'] ``` ## The modules worth knowing first ### `strings` — UTF-8, codepoint-positioned `prog.cx` ```cx [?lib 'cx-stdlib/strings'] [$strings:length "héllo"] ``` ```console $ cx prog.cx 5 ``` Indexing, length, and slicing count codepoints, and case folding follows the Unicode tables — so this is not the answer a byte-oriented library gives: `prog.cx` ```cx [?lib 'cx-stdlib/strings'] [$strings:upper "straße"] ``` ```console $ cx prog.cx STRASSE ``` For byte-level work use `bytes`; for patterns use `re`. ### `re` — RE2, and what that excludes `re` is RE2-backed: linear time, no backtracking, and therefore **no lookaround and no backreferences**. Those are refused at compile time, not silently mis-executed — which matters when you are porting a PCRE pattern. `prog.cx` ```cx [?lib 'cx-stdlib/re'] [$re:compile "(.)\\1"] ``` ```console $ cx prog.cx [err code=cx-err:CXER3200 message='E_RE_FEATURE_UNSUPPORTED: CXER3200:E_RE_FEATURE_UNSUPPORTED: (.)\1' at='2:1'] ``` `prog.cx` ```cx [?lib 'cx-stdlib/re'] [$re:matches [$re:compile "a+"] "aaa"] ``` ```console $ cx prog.cx true ``` ### `map` and `array` — absence is a value, not a crash A missing key reads as empty rather than raising; a strict read is a separate call. This is the same absence discipline as the rest of the language. `prog.cx` ```cx [?lib 'cx-stdlib/map'] [$map:get {a: 1} 'zz'] ``` ```console $ cx prog.cx () ``` `prog.cx` ```cx [?lib 'cx-stdlib/array'] [$array:get [1, 2] 5] ``` ```console $ cx prog.cx [err code=cx-err:CXER0100 message='[$array:get] position 5 is out of range for an array of 2 item(s) — positions are 1-based (E_ARRAY_INDEX_OUT_OF_RANGE)' at='2:1'] ``` ### `fp` — the functional layer `map`, `filter`, `fold`, `flat-map` over sequences, and the absence-aware variants that make a pipeline total rather than partial. `prog.cx` ```cx [?lib 'cx-stdlib/fp'] [$fp:map (1, 2, 3) [+ _ 1]] ``` ```console $ cx prog.cx (2, 3, 4) ``` ### `json` — parse and emit `prog.cx` ```cx [?lib 'cx-stdlib/json'] [$json:parse "42"] ``` ```console $ cx prog.cx 42 ``` ### `format` — four renderings, all of them faithful `canonical`, `compact`, `pretty` and `diff-friendly` are four *renderings* of one value, and every one of them round-trips: parsing the output gives back a structurally equal value. `prog.cx` ```cx [?lib 'cx-stdlib/format'] [?let [= $w [user active=true age=41 name=alice role="ops lead" score=1.5 tier=:gold [s "text"] [i 7] [d 2.50] [b false] [a :ok]]] [$eq [$format:canonical [$cx:parse [$format:pretty $w]]] [$format:canonical $w]]] ``` ```console $ cx prog.cx true ``` That is a guarantee about **types**, not just text. `pretty` never quotes a non-string scalar — a decimal stays a decimal, an atom keeps its `:` sigil, a glued type annotation survives: `prog.cx` ```cx [?lib 'cx-stdlib/format'] [$format:pretty [u tier=:gold]] ``` ```console $ cx prog.cx '[u tier=:gold]' ``` `prog.cx` ```cx [?lib 'cx-stdlib/format'] [$format:pretty [u port::u16=8080 t=100ms]] ``` ```console $ cx prog.cx '[u port::u16=8080 t::duration=100ms]' ``` There is exactly one deliberate divergence between the forms: `pretty` always quotes a string, while `canonical` leaves it bare whenever the bare image re-parses as that same string. Both round-trip. Which one to reach for: | Form | Use it for | |---|---| | `canonical` | identity — hashing, addressing, equality. Never for display | | `compact` | the same bytes, minimised | | `pretty` | reading and debugging. **Not version-stable — never snapshot-test it** | | `diff-friendly` | review diffs; sorts attributes | ### `cx` — CX reflecting on CX `[$cx:parse]`, `[$cx:serialize]`, `[$cx:ast]`, `[$cx:from-format]`. This is how tooling written in CX reads CX — including the generator that produced this file. `prog.cx` ```cx [?let [= $t [$cx:parse "[?if true [then 1] [else 2]]"]] [$count $t//then]] ``` ```console $ cx prog.cx 1 ``` `[$cx:carries-err TEXT]` answers the one question about a run that only the runtime can answer: does this RENDERED output carry an err value anywhere in it? Three positions count — a line beginning `[err `, a sequence whose first item errs, and a sequence whose err sits anywhere else — so a CX tool that spawns `cx` and reads its stdout back never has to guess at a prefix. It is a **tolerance** test: an err in the output does not make a nonzero exit code required, because a literal err in a data document is data echoing itself. `prog.cx` ```cx [$cx:carries-err '(1, 4, 9, 16, [err code=shard-offline shard=5], 36)'] ``` ```console $ cx prog.cx true ``` ### `log` — structured context, not string prefixes Log context is a **scope**, entered with `[?with-scope]` and read back as a map. Nesting overrides key by key, and the scope is restored on exit — which is what makes a request id ride an entire call tree without being threaded through every signature: `prog.cx` ```cx [?lib 'cx-stdlib/log'] [?with-scope {request-id: "r1"} [$log:current-scope]] ``` ```console $ cx prog.cx {request-id: r1} ``` `prog.cx` ```cx [?lib 'cx-stdlib/log'] [?with-scope {request-id: "outer"} [?with-scope {request-id: "inner"} [$log:current-scope]]] ``` ```console $ cx prog.cx {request-id: inner} ``` `prog.cx` ```cx [?lib 'cx-stdlib/log'] [$log:current-scope] ``` ```console $ cx prog.cx {} ``` ### `time` — typed instants, refused nonsense `prog.cx` ```cx [?lib 'cx-stdlib/time'] [$time:date 2026 2 31] ``` ```console $ cx prog.cx [err code=cx-err:CXER3300 message='E_TIME_INVALID_COMPONENT: 2026-2-31' at='2:1'] ``` ## Finding the rest Per-function documentation is co-located with the implementation as `[fn-doc]` blocks in `stdlib/.cx`, gated for presence, purity, and example-backing by `make guide-check`. The rendered form is the "Standard library" section of the guide. Each module's full conformance matrix is `conformance/stdlib/.cxd` — the honest answer to "what does this function do at the edges". # Reference: the `cx` command line — v0.18.0-pre.1 > **GENERATED.** Source: `docs-src/llm/reference-cli.md.tmpl`. The help text > below is the v0.18.0-pre.1 binary's own `--help`, captured at generation > time — not a transcription. Read `primer.md` first. ## The one rule ``` cx [cx-flags] FILE [program-args...] ``` Flags bind **before** the resource. Everything after the resource is the program's `argv`. This is the interpreter convention (and shebang-identical), and getting it backwards is not a usage error — it produces a program that never got its capability. Since #1424 it is not silent either: an argument after the file spelled like a cx flag draws one line on STDERR (unless the program's `[argspec]` declares it), while argv and the exit status stay as they were: #### cx flags placed after the file The run surface is `cx [cx-flags] FILE [program-args...]` — the interpreter convention. Everything AFTER the file is the PROGRAM's argv (#926), so `--allow-env` there is a string handed to the program, not a grant handed to `cx`. The capability is never granted and the program dies at its first effect. Since #1424 (RULED: OL-10) one line on STDERR says so as it happens — `cx: '--allow-env' after FILE is program argv, not a cx flag — flags bind before FILE (cx [flags] FILE [args])` — unless the program's own `[argspec]` declares that argument. The line is a diagnostic: argv, the rendered result below and the exit status are exactly what they were. **Do not write this:** `prog.cx` ```cx [?lib 'cx-stdlib/env' as=env] [probe args=[$count [$env:argv]] home=[$env:var-or-default 'CX_PRIMER_ABSENT' 'unset']] ``` ```console $ cx prog.cx --allow-env [err code=cx-err:CXER0271 message='E_CAP_DENIED: env capability required for env-var-or-default; none granted (grant via --allow-env)' at='2:39'] ``` **Write this:** cx flags placed before the file `prog.cx` ```cx [?lib 'cx-stdlib/env' as=env] [probe args=[$count [$env:argv]] home=[$env:var-or-default 'CX_PRIMER_ABSENT' 'unset']] ``` ```console $ cx --allow-env prog.cx [probe args=1 home=unset] ``` Fixtures: `ap-flags-after-file-wrong` / `ap-flags-after-file-right` in `conformance/llm/antipatterns.cxd` An inline one-liner uses `-e`, and `cx eval` — a legacy alias of the default action — still takes a **file**: #### `cx eval 'PROGRAM'` — the subcommand takes a FILE `cx eval` is a legacy ALIAS of the default run action and, like it, takes a FILE. An inline program needs `-e`. The habit of writing `cx eval ''` (from other interpreters) makes cx try to OPEN the program text as a path. Two rules avoid the whole class: run files with `cx FILE`, and reach for `cx -e 'PROGRAM'` only for a one-liner. **Do not write this:** `prog.cx` ```cx [+ 1 2] ``` ```console $ cx eval '[+ 1 2]' cx eval: error reading program "[+ 1 2]": failed to open file "[+ 1 2]"; code: 2 ``` **Write this:** `cx -e 'PROGRAM'` for a one-liner `prog.cx` ```cx [+ 1 2] ``` ```console $ cx -e '[+ 1 2]' 3 ``` Fixtures: `ap-cx-eval-inline-wrong` / `ap-cx-eval-inline-right` in `conformance/llm/antipatterns.cxd` ## Reading program arguments `env:argv` is ungated (reading your own arguments is not an effect) and `argv[0]` is the file. `env:parse-args` takes a declarative flag spec. `prog.cx` ```cx [?lib 'cx-stdlib/env' as=env] [probe args=[$count [$env:argv]] home=[$env:var-or-default 'CX_PRIMER_ABSENT' 'unset']] ``` ```console $ cx --allow-env prog.cx [probe args=1 home=unset] ``` ## `cx --help` ```console $ cx --help Usage: cx [flags] FILE.cx [args...] Run a CX resource (the default action): parse and evaluate. A pure-data document evaluates to itself. Everything after FILE is the PROGRAM's argv ($env:argv, argv[0]=FILE) — cx flags go BEFORE the file, program args after (the interpreter convention; shebang-identical). cx [flags] - [args...] Run a program from stdin (a pipe into bare `cx` with no FILE also evaluates stdin). cx [flags] -e 'PROGRAM' [args...] Run an inline program (also --expression). cx [args] One of the subcommands below. cx -v | --version Version / build info. cx -h | --help This help. Run flags (the default action; flags bind BEFORE the resource): --cx (default) | --xml | --json | --yaml | --csv | --tsv render the RESULT --data=FILE|- separate data input: loaded via the data reading and bound as $doc/$input before evaluation; the caller input WINS over the program's data roots. --strict enforce declared ::T / [returns T] annotations at call boundaries + [?pipe] stage flow (code.md §12.7; default OFF erases annotations — documentation only). --manual-clock run sched under the deterministic :manual clock so [$sched:test-clock-advance] works (stdlib_sched.md §3.3). Default is the production :wall clock, where armed timers fire at blocking points instead. Unknown flags BEFORE FILE are hard usage errors (exit 2) — nothing is ignored; AFTER FILE a cx-flag spelling is the PROGRAM's argv and warns once on stderr, unless the program's [argspec] declares it. Capabilities are deny-by-default (spec/03-approved/core/security.md); grant explicitly: --allow-read --allow-write --allow-net --allow-env --allow-clock --allow-random --allow-subprocess --allow-eval --allow-secret-reveal --allow-keychain --allow-common --allow-all (three grants take an enforced scope: --allow-net=host[:port], --allow-read=PATH and --allow-write=PATH. A PATH is a directory-component subtree root — --allow-read=/data admits /data/x and refuses /database; the flag repeats and the roots union; a bare grant is the whole filesystem. A resource suffix on any OTHER capability is a usage error, not a narrowing: that scoping is unimplemented, #1059.) --probe is the preset for looking around without changing anything: it expands to --allow-read=. --allow-clock, so a probe that writes or reaches the network refuses with the ordinary capability error. --allow-common is the common working set WITHOUT secret-reveal and keychain; --allow-all additionally grants secret-reveal, which declassifies secrets, and keychain, which reads the OS keychain (env.md §3.6). Convert flags (the data reading; an explicit --from selects it): cx --from=FMT [--to=FMT] [--compact] [--lossless] [--include-root=DIR] [FILE] --from: cx|xml|json|yaml|toml|md|csv|tsv|psv|html|url (.xml/.json/.yaml/.toml/.md files auto-detect their format) --to: cx|xml|json|yaml|toml|md|csv|tsv|psv|html|url Projection shorthands: --ast --cx --xml --json --yaml --toml --md --csv --tsv --psv --cxcol --compact minimised output --lossless emit round-trip-preserving output (conversions.md 0.2/2.2.1): xml carries cx: markers; json/yaml carry the $tag structure envelope plus a `cx:type` sidecar / `!!cx:T` tags — element docs re-import byte-identically (cx/xml/json/yaml targets; other lanes reject the flag) --include-root=DIR resolve [?cx include=...] against DIR first Subcommands (`cx --help` for details): fmt Lossless canonical formatter (preserves comments/anchors). canonical Strict canonical text (strips presentation; data-equivalent). corpus Grade module corpus files (.cxd `[case …]` with [in-code …]) with this binary. schema Schema verb family — infer a .cxs from a corpus; convert an XSD; export to JSON Schema; classify compatibility. stdlib Standard-library inventory — enumerate registered modules and print a module's registered source. tools Agent-tool verb family — project command defs to MCP tool descriptors. address Content-address stability drill — re-derive stored pins against a tree. hash SHA-256 hex of the strict-canonical bytes. eq Exit 0 iff strict-canonical(A) == strict-canonical(B). diff Semantic diff (walks the strict-canonical forms). lint Style + correctness warnings. imports List a file's imports against its calls; --fix adds, removes and sorts its [?lib] lines. validate Validate a document against a CX schema (.cxs). eval Evaluate a CX program (alias of the default run action; prefer `cx FILE`). primer Print the LLM onboarding primer for THIS binary (#938). version Version / build info (same output as -v / --version). select CXPath query over a document (matches in canonical CX). diagram Render a CX program as a diagram (mermaid/svg/png). test Run the test-* functions of *_test.cx files (test.md §2.3). code-diagram Mermaid diagram of a CX source (flowchart / erDiagram). code-tree Tree View JSON of a CX source. table Table API over [table[...]] blocks (info / dump / load). build One executable that runs a program (cx build PROG.cx -o OUT). scaffold Typed, commented skeleton on stdout (config/data/doc/log/table). xap XAP project tooling — scaffold a project (`init`), a composition pattern (`scaffold`), and check (`check-surface`). flow cx-platform/flow: the local profile (run / validate / simulate / status / diagram) and the standalone runner (serve). secrets cx-platform/secrets: the keystore client verbs (put / rotate / list) — a value is never printed, never read from argv. deps The repository dependency pins: sync deps// to each deps.cxd sha, or --check it — a stale pin or a drifted checkout is refused, never warned. demo Self-contained showcase (no file I/O, no network, < 1s). lock Generate / verify cx.lock from [?lib] directives. lsp Language server (LSP) on stdio. store-serve Run the CX store service daemon from a config. fabric-serve Run the CX fabric eventing daemon from a config. store-health Store readiness probe (exit 0 iff accepting). store-rotate-kek Rotate a store key-encryption key (re-wrap envelopes). store-mint-principal Mint an XSP-AUTH principal offline (seed file + [grant …] stanza). ``` ## The verbs, by what you are trying to do The `--help` capture above is the complete list. This is the same set sorted by intent. **Run and inspect** | Command | Use it for | |---|---| | `cx FILE.cx` | run a program **interpreted** (the evaluator), or print a document's canonical form | | `cx -e 'PROG'` | a one-liner | | `cx demo` | a self-contained showcase, no I/O, under a second | | `cx primer` | this documentation set's primer, for the installed binary | | `cx version` | version and build info (same as `-v` / `--version`) | **Identity, shape, and correctness** | Command | Use it for | |---|---| | `cx fmt FILE` | lossless format (keeps comments and anchors) | | `cx fmt --code FILE.cx` | format a program keeping its layout — your line breaks, indentation by form, closers glued, lines past 80 broken | | `cx canonical FILE` | strict canonical text — what identity is defined over | | `cx hash FILE` | SHA-256 of the strict-canonical bytes | | `cx eq A B` / `cx diff A B` | semantic equality / semantic diff | | `cx lint FILE` | style and correctness findings | | `cx validate FILE --schema=S.cxs` | schema check | | `cx corpus FILE.cxd` | grade a module conformance corpus with this binary — the grader a repository runs with no toolchain; `--times` adds each case's in-process time | | `cx schema infer` / `export` / … | derive a `.cxs`, project it to JSON Schema, classify compatibility | | `cx select 'PATH' FILE` | one CXPath query, no program | | `cx --from=json --to=cx f.json` | the convert surface | | `cx table info` / `dump` / `load` | the `[table[…]]` API | | `cx scaffold KIND` | a typed, commented skeleton on stdout | **Understand a program** | Command | Use it for | |---|---| | `cx code-diagram FILE` | Mermaid of a program (`--view=effects` for its capability graph) | | `cx code-tree FILE` | Tree View JSON of a source | | `cx diagram FILE` | render a program as mermaid / svg / png | | `cx tools export MODULE.cx` | project command defs to MCP tool descriptors | | `cx lsp` | the language server, on stdio | **Build and ship a feature** | Command | Use it for | |---|---| | `cx xap init NAME` | scaffold a feature | | `cx xap check-surface DIR` | the surface derivation check | | `cx lock` | generate / verify `cx.lock` from `[?lib]` imports | | `cx build [RUN-FLAGS] PROG.cx -o OUT` | **compiled**: one executable that runs the program as `cx [RUN-FLAGS] PROG.cx` does, with no cx install where it runs (same OS and architecture); `OUT --version` names the program and the cx commit that built it | | `cx build --native [RUN-FLAGS] PROG.cx -o OUT [-- ARG...]` | **compiled**, the same, with the hot pure defs compiled to machine code from a training run (`-- ARG...` are its arguments, for a program that reads `$env:argv`; OUT takes its own when it runs; a C compiler where OUT is built; the link kit beside cx, `make cli-kit`) | | `cx imports FILE.cx` | list the modules a file imports and calls (used / unused / missing); `--fix -w` adds the missing `[?lib]` lines, removes the unused and sorts them | | `cx flow run FLOW.cx --env ENV.cx` | run a flow document over a journal — build, release and authoring flows from a checkout, no engine to start | | `cx flow validate` / `simulate` / `status` | check a flow document, run the runner law over a supplied result table, read a run record back | **Operate a platform** (the platform profile) | Command | Use it for | |---|---| | `cx store-serve config.cx` | the store service daemon | | `cx fabric-serve config.cx` | the fabric eventing daemon | | `cx store-health URL` | readiness probe — exit 0 iff accepting | | `cx store-rotate-kek …` | rotate a key-encryption key (re-wrap envelopes) | | `cx store-mint-principal …` | mint an XSP-AUTH principal offline — the clean-state bootstrap | `reference-platform.md` has the bootstrap walkthrough; `playbook-xap.md` has the whole arc from feature grammar to hosted surface. ## What the version string tells you Release-ness is **derived**, never hand-maintained. `cx --version` prints `cx vX.Y.Z` only when the binary was built from a clean tree at exactly the annotated release tag matching the repo's `VERSION`. Anything else prints `cx vX.Y.Z-dev+` — semantically a pre-release of `X.Y.Z`, which is what unreleased source is. If you are reporting a bug, the `-dev+` suffix is the part that matters. ## A retired verb tells you what replaced it A verb that once existed and no longer does answers with **its own retirement**, never with "unknown subcommand". `cx store-token` is the current example — it says the bearer/RBAC plane is gone, that store credentials are now XSP-AUTH principals granted in the daemon config, and which verb mints one. Retirement entries are kept indefinitely. So if a verb you remember is missing from `--help`, **run it** rather than guessing at a replacement. The tool knows what happened to it. ## Capability grants ``` --allow-read[=PATH] --allow-write[=PATH] --allow-net[=host[:port]] --allow-env --allow-clock --allow-random --allow-subprocess --allow-eval --allow-secret-reveal --probe # the preset: --allow-read=. --allow-clock --allow-common # every capability EXCEPT secret-reveal --allow-all # including secret-reveal (declassifies secrets) ``` Deny-by-default, and a denial names the flag that would have allowed it: `prog.cx` ```cx [?lib 'cx-stdlib/io'] [$io:write-file "/tmp/out.txt" "hello"] ``` ```console $ cx prog.cx [err code=cx-err:CXER0271 message='E_CAP_DENIED: write capability required for io-write-file; none granted (grant via --allow-write)' at='2:1'] ``` Grant the narrowest thing that works. **Three grants take a scope** — `--allow-net=host[:port]`, `--allow-read=PATH` and `--allow-write=PATH` — and the rest are all-or-nothing, which is a reason to prefer a scoped `--allow-read=./data` over `--allow-common` in anything automated. A `PATH` scope is a **directory-component subtree root**: `--allow-read=/data` admits `/data` and `/data/x` and refuses `/database`. The flag repeats and the roots union; a bare `--allow-read` grants the whole filesystem, as it always did. Both the path as written and the path after symlinks must fall under a root, so a symlink out of the granted tree — and a symlink into it from outside — refuses. A path outside the roots is the ordinary capability refusal (`cx-err:CXER0271`), naming the path, the roots in force, and the flag to add. `--probe` is the preset for a read-only look around: it expands to `--allow-read=. --allow-clock`, and a probe that writes or reaches the network gets that same refusal. A resource suffix on any OTHER capability — `--allow-env=HOME`, `--allow-subprocess=/bin/ls` — is a **usage error** (exit 2, before evaluation) naming the flag, the suffix it would have ignored, and the bare spelling that is accepted. Earlier versions took such a suffix, discarded it, and granted the blanket capability, so the narrower-looking spelling silently bought wider authority; per-name and per-executable scoping is unimplemented, and the surface says so rather than pretending. ## Exit codes | Code | Meaning | |---|---| | 0 | success | | 1 | the program's result carries a COMPUTED `[err]` at any depth, or a check found findings | | 2 | usage error — an unknown flag, a missing file, a bad invocation | Exit 1 is about the program's result (R5.13), and the discriminator is PROVENANCE, not position (RULED: 1099-a, #1099): - A **computed** `[err]` — one a refusal produced at run time — anywhere in the result makes the run exit 1: as the whole result, as a member of a sequence / array / map (#1058 T1.1: `[?for [in $n (1)] [yield [/ $n 0]]]`), or resting inside an **element** at any depth — `[out [doc [?splice $rows]]]` with a refusal among `$rows`, `[doc [?for … [yield [/ $n 0]]]]`. Before 1099-a the walk stopped at a named element, so the last shape printed its refusals and exited 0. - A **written** `[err …]` — parsed from source, or built by the program's own `[err …]` literal — is data echoing itself and exits 0 wherever it sits: `[err code=x]` as the top-level form, `{a: [err code=c]}`, `[wrapper [err …]]`, `[doc [?splice ([err code=c], 2)]]`. - Resting is still ruled (CO-2, #975): a contributed member is not propagated out of the construction — the document is built around it and printed — and the REFUSAL guard sits at the effect boundary (`[$store:put-doc]` on a document carrying any err, written or computed, refuses with `CXER0275`). The exit status is not a refusal; it is the process saying what it printed. On exit 1 the run also names the first err on STDERR; STDOUT is unchanged, because the rendered err is still the program's answer. Do not read the exit status as a refusal contract: refusal at a boundary is `CXER0275` (store / http), raised at the boundary, independent of how the process exits. Unknown flags are hard errors. Nothing is ignored: a typo'd grant *before* the file exits 2, and a real cx flag *after* the file is the program's argument — which still grants nothing, and now says so in one STDERR line unless the program's `[argspec]` declares it. ## Scripting `cx` from CX Tooling in this repo is written in CX and run with `cx ` — never `cx eval`, and never a shell wrapper where CX would do. The pattern: ```console cx --allow-read --allow-write --allow-subprocess --allow-env script.cx --flag value ``` Flags for `cx` first, the script, then the script's own arguments. # Playbook: building a production XAP — v0.18.0-pre.1 > **GENERATED.** Source: `docs-src/llm/playbook-xap.md.tmpl` + the conformance > corpus. Every code block is a fixture and every output was re-recorded from > the `cx` v0.18.0-pre.1 binary at generation time. Where a claim is backed by a > file in the repository rather than a fixture, the file and line are cited and > **the citation is the evidence** — nothing here is invented. Read `primer.md` > first; this document assumes the language. A **XAP** is CX's unit of packaged functionality. It is a *feature*, never an "app". A production deployment is several features composed into one runtime, with an authority model, a durable plane, and a projected surface — and the striking thing about it is how little of that is code. Most of it is data you declare, gated by rules that refuse rather than guess. This playbook walks the whole arc. Each chapter names what is ruled, what it forbids, and where the evidence lives. ## 0. The map Six artefacts, in the order you author them. | # | Artefact | What it is | Chapter | |---|---|---|---| | 1 | `.feature.cxd` | one feature's grammar: nouns, verbs, requirements | §1 | | 2 | *(a composite feature)* | a feature that `[uses]` others and declares derived nouns | §3 | | 3 | `.xap.cxd` | the **wiring layer**: which features, who has authority, what the durable plane is | §5 | | 4 | a deriver module | the one producer of a derived noun | §3 | | 5 | `.surface.cxd` | the route table and media binding | §7 | | 6 | a client | a separate project; the deployment does not own it | §7 | The dividing line to hold onto: **a feature document declares grammar; the deployment document declares nothing about grammar.** It enables features, names principals, and binds the runtime. Per-verb grants live in the feature, because that is where the verb lives. Worked instances to read alongside this document: * `reference/shop/` — the composition, authority, and distribution reference. Eleven files; `README.md` is the guided tour. * `spec/03-approved/xap/demos/oriel/` — the projection flagship: the full ux vocabulary, both renderers, and the headless drive-step harness. ## 1. Author a feature grammar A feature is a document. Its head is `[feature name=…]`, and it carries `[nouns]`, `[verbs]`, and `[requirements]` — all three, always. A verb states its `effect` class, the `[intent]` that reaches it, and what it `[reads]` or `[writes]`: `prog.cx` ```cx [?lib 'cx-platform/xap' as=xap] [?let [= $chart [feature name=chart [frames [use frame=geo via=center]] [nouns [noun name=viewport singular=true [field name=center type=geo-point]]] [verbs [verb name=highlight effect=arrange [intent [do :highlight]] [reads viewport]] [verb name=set-waypoint effect=act scope=shared consequence=reversible [intent [do :set-waypoint]] [writes viewport]]] [requirements [requirement kind=functional as=user traces=highlight [want 'to mark a spot'] [so 'I can find it']]]]] [?let [= $g [$xap:compose $chart]] $g//verb[= $_@name 'chart/highlight']/@effect]] ``` ```console $ cx prog.cx [effect arrange] ``` Read the output: after composition the verb is `chart/highlight`, not `highlight`. **Names qualify on the way in.** That is what lets two features each own a `highlight` without either of them knowing about the other. Requirements are not decoration. They carry `kind`, an RFC-2119 `level`, and `[acceptance]` rows — see `reference/shop/shop.xap.cxd:43-54` for three MUSTs with their acceptance criteria written as checkable sentences. Per-verb grants live here too, in the feature's own `[governance]`: `reference/shop/orders.feature.cxd:56-58` grants `place-order` and `cancel-order` to the `customer` role. The deployment document's `[governance]` block is the layer *above* this one, not a replacement for it. ## 2. Compose them `[$xap:compose]` takes n feature grammars and returns one grammar — or a conflict value naming every violation. It is an **algebra with laws**, not a merge convention. **Commutative** — order of arguments cannot change the result, and the composed grammar's Tier-1 hash is the equality oracle that proves it: `prog.cx` ```cx [?lib 'cx-platform/xap' as=xap] [?let [= $chart [feature name=chart [nouns [noun name=viewport singular=true [field name=center type=geo-point]]] [verbs [verb name=highlight effect=arrange [intent [do :highlight]] [reads viewport]]] [requirements [requirement kind=functional as=user traces=highlight [want 'to mark a spot'] [so 'I can find it']]]]] [= $strikes [feature name=strikes [nouns [noun name=strike [field name=pos type=geo-point] [field name=at type=instant]]] [verbs [verb name=list-strikes effect=observe [intent [do :list-strikes]] [reads strike]]] [requirements [requirement kind=functional as=user traces=list-strikes [want 'to see strikes'] [so 'I avoid them']]]]] [= [$xap:grammar-hash [$xap:compose $chart $strikes]] [$xap:grammar-hash [$xap:compose $strikes $chart]]]] ``` ```console $ cx prog.cx true ``` **Associative / order-independent at any arity:** `prog.cx` ```cx [?lib 'cx-platform/xap' as=xap] [?let [= $a [feature name=a [nouns [noun name=an [field name=x type=int]]] [verbs [verb name=va effect=observe [intent [do :va]] [reads an]]] [requirements [requirement kind=functional as=user traces=va [want 'a'] [so 'a']]]]] [= $b [feature name=b [nouns [noun name=bn [field name=x type=int]]] [verbs [verb name=vb effect=observe [intent [do :vb]] [reads bn]]] [requirements [requirement kind=functional as=user traces=vb [want 'b'] [so 'b']]]]] [= $c [feature name=c [nouns [noun name=cn [field name=x type=int]]] [verbs [verb name=vc effect=observe [intent [do :vc]] [reads cn]]] [requirements [requirement kind=functional as=user traces=vc [want 'c'] [so 'c']]]]] [= [$xap:grammar-hash [$xap:compose $a $b $c]] [$xap:grammar-hash [$xap:compose $c $a $b]]]] ``` ```console $ cx prog.cx true ``` **Idempotent** — composing a feature twice is composing it once: `prog.cx` ```cx [?lib 'cx-platform/xap' as=xap] [?let [= $chart [feature name=chart [nouns [noun name=viewport singular=true [field name=center type=geo-point]]] [verbs [verb name=highlight effect=arrange [intent [do :highlight]] [reads viewport]]] [requirements [requirement kind=functional as=user traces=highlight [want 'to mark a spot'] [so 'I can find it']]]]] [= [$xap:grammar-hash [$xap:compose $chart $chart]] [$xap:grammar-hash [$xap:compose $chart]]]] ``` ```console $ cx prog.cx true ``` **Closed under name collision** — two features may each own a verb of the same name, because qualification already separated them: `prog.cx` ```cx [?lib 'cx-platform/xap' as=xap] [?let [= $chart [feature name=chart [nouns [noun name=viewport singular=true [field name=center type=geo-point]]] [verbs [verb name=highlight effect=arrange [intent [do :highlight]] [reads viewport]] [verb name=set-waypoint effect=act scope=shared consequence=reversible [intent [do :set-waypoint]] [writes viewport]]] [requirements [requirement kind=functional as=user traces=highlight [want 'to mark a spot'] [so 'I can find it']]]]] [= $strikes [feature name=strikes [nouns [noun name=strike [field name=pos type=geo-point] [field name=at type=instant]]] [verbs [verb name=highlight effect=arrange [intent [do :highlight]] [reads strike]] [verb name=list-strikes effect=observe [intent [do :list-strikes]] [reads strike]]] [requirements [requirement kind=functional as=user traces=highlight [want 'to see strikes'] [so 'I avoid them']]]]] [= $g [$xap:compose $chart $strikes]] [$count $g//verb]] ``` ```console $ cx prog.cx 4 ``` ### The W-gates Composition runs a well-formedness gate. Each violation has its own code, and the gate reports **all** of them rather than stopping at the first: `prog.cx` ```cx [?lib 'cx-platform/xap' as=xap] [?let [= $chart [feature name=chart [nouns [noun name=viewport singular=true [field name=center type=geo-point]]] [verbs [verb name=highlight effect=arrange [intent [do :highlight]] [reads viewport]]] [requirements [requirement kind=functional as=user traces=highlight [want 'to mark a spot'] [so 'I can find it']]]]] [= $chart-b [feature name=chart [nouns [noun name=marks [field name=n type=int]]] [verbs [verb name=mark effect=act [intent [do :mark]] [writes marks]]] [requirements [requirement kind=functional as=user traces=mark [want 'to mark'] [so 'marked']]]]] [$xap:compose $chart $chart-b]] ``` ```console $ cx prog.cx [err code=cx-err:CXER4870 message='E_XAP_COMPOSE_CONFLICT: composition rejected with 1 conflict(s)' [conflict code=':w1' at=chart detail='2 distinct features named "chart" — feature names must be unique in a XAP']] ``` `prog.cx` ```cx [?lib 'cx-platform/xap' as=xap] [?let [= $chart [feature name=chart [nouns [noun name=viewport singular=true [field name=center type=geo-point]]] [verbs [verb name=highlight effect=arrange [intent [do :highlight]] [reads viewport]]] [requirements [requirement kind=functional as=user traces=highlight [want 'to mark a spot'] [so 'I can find it']]]]] [= $ghost-user [feature name=ghost-user kind=composite [uses features='chart ghost'] [nouns [noun name=shadow derived=true [field name=n type=int] [from 'chart/viewport']]] [verbs [verb name=peek effect=observe [intent [do :peek]] [reads shadow]]] [requirements [requirement kind=functional as=user traces=peek [want 'to peek'] [so 'peeked']]]]] [$xap:compose $chart $ghost-user]] ``` ```console $ cx prog.cx [err code=cx-err:CXER4870 message='E_XAP_COMPOSE_CONFLICT: composition rejected with 1 conflict(s)' [conflict code=':w5' at=ghost-user detail='composite "ghost-user" uses "ghost", which is not in the composed feature set']] ``` `prog.cx` ```cx [?lib 'cx-platform/xap' as=xap] [?let [= $chart [feature name=chart [nouns [noun name=viewport singular=true [field name=center type=geo-point]]] [verbs [verb name=highlight effect=arrange [intent [do :highlight]] [reads viewport]]] [requirements [requirement kind=functional as=user traces=highlight [want 'to mark a spot'] [so 'I can find it']]]]] [= $chart-b [feature name=chart [nouns [noun name=marks [field name=n type=int]]] [verbs [verb name=mark effect=act [intent [do :mark]] [writes marks]]] [requirements [requirement kind=functional as=user traces=mark [want 'to mark'] [so 'marked']]]]] [= $labels [feature name=labels [frames [use frame=geo via=title]] [nouns [noun name=tag [field name=title type=text]]] [verbs [verb name=label effect=arrange [intent [do :label]] [reads tag]]] [requirements [requirement kind=functional as=user traces=label [want 'to label'] [so 'labeled']]]]] [= $r [$xap:compose-report $chart $chart-b $labels]] [$count $r//conflict]] ``` ```console $ cx prog.cx 2 ``` Two faces, one predicate. `[$xap:compose]` raises; `[$xap:compose-report]` is the never-raising tooling face, and the law is that **compose raises if and only if compose-report says `ok=false`**: `prog.cx` ```cx [?lib 'cx-platform/xap' as=xap] [?let [= $chart [feature name=chart [nouns [noun name=viewport singular=true [field name=center type=geo-point]]] [verbs [verb name=highlight effect=arrange [intent [do :highlight]] [reads viewport]]] [requirements [requirement kind=functional as=user traces=highlight [want 'to mark a spot'] [so 'I can find it']]]]] [= $strikes [feature name=strikes [nouns [noun name=strike [field name=pos type=geo-point] [field name=at type=instant]]] [verbs [verb name=list-strikes effect=observe [intent [do :list-strikes]] [reads strike]]] [requirements [requirement kind=functional as=user traces=list-strikes [want 'to see strikes'] [so 'I avoid them']]]]] [?let [= $r [$xap:compose-report $chart $strikes]] $r/@ok]] ``` ```console $ cx prog.cx true ``` ### Composing nothing is a refusal This is worth its own heading because it is the failure mode that makes a gate worse than no gate. A gate over the empty set has verified the empty set, and reports a pass: `prog.cx` ```cx [?lib 'cx-platform/xap' as=xap] [$xap:compose] ``` ```console $ cx prog.cx [err code=cx-err:CXER4874 message='E_XAP_COMPOSE_EMPTY: composition has no features — a gate over the empty set verifies nothing (§3.1 identity)'] ``` The tooling face agrees, at the same vacuous input, so the two can never diverge into "the check passes but the build fails": `prog.cx` ```cx [?lib 'cx-platform/xap' as=xap] [$xap:compose-report] ``` ```console $ cx prog.cx [compose-report ok=false [conflict code=':empty' at='(no features)' detail='composition has no features — a gate over the empty set verifies nothing (§3.1 identity)']] ``` **If your feature files were renamed, moved, or not yet written, your composition refuses.** It does not congratulate you. ### Bare terms resolve, or say they cannot A bare `[do highlight]` is resolved by ρ against the composed grammar. With one owner it resolves: `prog.cx` ```cx [?lib 'cx-platform/xap' as=xap] [?let [= $chart [feature name=chart [nouns [noun name=viewport singular=true [field name=center type=geo-point]]] [verbs [verb name=highlight effect=arrange [intent [do :highlight]] [reads viewport]]] [requirements [requirement kind=functional as=user traces=highlight [want 'to mark a spot'] [so 'I can find it']]]]] [= $ais [feature name=ais [types [type 'mmsi::text' [pattern '^[0-9]{9}$']]] [keys [key name=mmsi via=mmsi]] [nouns [noun name=vessel [field name=mmsi type=mmsi] [field name=pos type=geo-point]]] [verbs [verb name=track effect=observe [intent [do :track]] [reads vessel]]] [requirements [requirement kind=functional as=user traces=track [want 'to track vessels'] [so 'I know traffic']]]]] [= $g [$xap:compose $chart $ais]] [$xap:resolve $g "track"]] ``` ```console $ cx prog.cx ais/track ``` A qualified term bypasses resolution entirely: `prog.cx` ```cx [?lib 'cx-platform/xap' as=xap] [?let [= $chart [feature name=chart [nouns [noun name=viewport singular=true [field name=center type=geo-point]]] [verbs [verb name=highlight effect=arrange [intent [do :highlight]] [reads viewport]]] [requirements [requirement kind=functional as=user traces=highlight [want 'to mark a spot'] [so 'I can find it']]]]] [= $strikes [feature name=strikes [nouns [noun name=strike [field name=pos type=geo-point] [field name=at type=instant]]] [verbs [verb name=highlight effect=arrange [intent [do :highlight]] [reads strike]]] [requirements [requirement kind=functional as=user traces=highlight [want 'to see strikes'] [so 'I avoid them']]]]] [= $g [$xap:compose $chart $strikes]] [$xap:resolve $g "chart/highlight"]] ``` ```console $ cx prog.cx chart/highlight ``` And ambiguity is **a value listing the candidates — never a guess**: `prog.cx` ```cx [?lib 'cx-platform/xap' as=xap] [?let [= $chart [feature name=chart [nouns [noun name=viewport singular=true [field name=center type=geo-point]]] [verbs [verb name=highlight effect=arrange [intent [do :highlight]] [reads viewport]]] [requirements [requirement kind=functional as=user traces=highlight [want 'to mark a spot'] [so 'I can find it']]]]] [= $strikes [feature name=strikes [nouns [noun name=strike [field name=pos type=geo-point] [field name=at type=instant]]] [verbs [verb name=highlight effect=arrange [intent [do :highlight]] [reads strike]]] [requirements [requirement kind=functional as=user traces=highlight [want 'to see strikes'] [so 'I avoid them']]]]] [= $g [$xap:compose $chart $strikes]] [$xap:resolve $g "highlight"]] ``` ```console $ cx prog.cx [err code=cx-err:CXER4871 message='E_XAP_VERB_AMBIGUOUS: "highlight" has 2 candidates — disambiguate, never auto-pick' term=highlight candidates='chart/highlight strikes/highlight'] ``` `prog.cx` ```cx [?lib 'cx-platform/xap' as=xap] [?let [= $chart [feature name=chart [nouns [noun name=viewport singular=true [field name=center type=geo-point]]] [verbs [verb name=highlight effect=arrange [intent [do :highlight]] [reads viewport]]] [requirements [requirement kind=functional as=user traces=highlight [want 'to mark a spot'] [so 'I can find it']]]]] [= $g [$xap:compose $chart]] [$xap:resolve $g "warp"]] ``` ```console $ cx prog.cx [err code=cx-err:CXER4872 message='E_XAP_VERB_UNKNOWN: no feature of the composed grammar defines "warp"' term=warp] ``` That property has a name (N-COMPOSE-1) and it is the reason enabling one more feature cannot silently change what an existing utterance means. See `reference/shop/shop.xap.cxd:51-54`, where a requirement pins exactly this. ## 3. Derived nouns and deriver principals A **composite** feature `[uses]` other features and declares nouns that are `derived=true`, each naming the nouns it is derived `[from …]`: `prog.cx` ```cx [?lib 'cx-platform/xap' as=xap] [?let [= $own-ship [feature name=own-ship [types [type 'deg::decimal' [range 0 360]] [type 'knots::decimal' [min 0]]] [frames [use frame=geo via=position] [use frame=time via=observed-at]] [nouns [noun name=own-ship singular=true [field name=position type=geo-point] [field name=cog type=deg] [field name=sog type=knots] [field name=observed-at type=instant]]] [verbs [verb name=read-instruments effect=observe [intent [do :read-instruments]] [reads own-ship]]] [requirements [requirement kind=functional as=skipper traces=read-instruments [want 'to read my instruments'] [so 'I know my state']]]]] [= $traffic [feature name=traffic [types [type 'mmsi::text' [pattern '^[0-9]{9}$']] [type 'deg::decimal' [range 0 360]] [type 'knots::decimal' [min 0]]] [frames [use frame=geo via=position] [use frame=time via=observed-at]] [keys [key name=mmsi via=mmsi]] [nouns [noun name=vessel [field name=mmsi type=mmsi] [field name=position type=geo-point] [field name=cog type=deg] [field name=sog type=knots] [field name=observed-at type=instant]]] [verbs [verb name=list-targets effect=observe [intent [do :list-targets]] [reads vessel]]] [requirements [requirement kind=functional as=skipper traces=list-targets [want 'to see nearby traffic'] [so 'I avoid it']]]]] [= $collision-cpa [feature name=collision-cpa kind=composite [uses features='own-ship traffic'] [types [type 'mmsi::text' [pattern '^[0-9]{9}$']] [type 'nm::decimal' [min 0]] [type 'minutes::decimal' [min 0]]] [frames [use frame=geo] [use frame=time]] [nouns [noun name=cpa derived=true [field name=vessel type=mmsi] [field name=cpa-dist type=nm] [field name=tcpa type=minutes] [from 'own-ship/own-ship' 'traffic/vessel']]] [verbs [verb name=cpa-threats effect=observe [intent [do :cpa-threats]] [reads cpa]]] [requirements [requirement kind=functional as=skipper traces=cpa-threats [want 'closing vessels flagged'] [so 'I can avoid collision']]]]] [?let [= $g [$xap:compose $own-ship $traffic $collision-cpa]] $g//noun[= $_@name 'collision-cpa/cpa']/@derived]] ``` ```console $ cx prog.cx [derived true] ``` Four rules govern derived nouns, and each of them is a gate. **A derived noun must declare `[from …]`.** Without it the composition refuses: `prog.cx` ```cx [?lib 'cx-platform/xap' as=xap] [?let [= $chart [feature name=chart [nouns [noun name=viewport singular=true [field name=center type=geo-point]]] [verbs [verb name=highlight effect=arrange [intent [do :highlight]] [reads viewport]]] [requirements [requirement kind=functional as=user traces=highlight [want 'to mark a spot'] [so 'I can find it']]]]] [= $sourceless [feature name=sourceless kind=composite [uses features='chart'] [nouns [noun name=shadow derived=true [field name=n type=int]]] [verbs [verb name=peek effect=observe [intent [do :peek]] [reads shadow]]] [requirements [requirement kind=functional as=user traces=peek [want 'to peek'] [so 'peeked']]]]] [= $r [$xap:compose-report $chart $sourceless]] [$first [?for [in $c $r//conflict] [yield $c@detail]]]] ``` ```console $ cx prog.cx 'derived noun "sourceless/shadow" declares no [from …] source list' ``` **Every reference inside `[from …]` must resolve.** W5 covers each one: `prog.cx` ```cx [?lib 'cx-platform/xap' as=xap] [?let [= $chart [feature name=chart [nouns [noun name=viewport singular=true [field name=center type=geo-point]]] [verbs [verb name=highlight effect=arrange [intent [do :highlight]] [reads viewport]]] [requirements [requirement kind=functional as=user traces=highlight [want 'to mark a spot'] [so 'I can find it']]]]] [= $dangling [feature name=dangling kind=composite [uses features='chart'] [nouns [noun name=shadow derived=true [field name=n type=int] [from 'chart/viewport' 'nosuchfeature/nothing']]] [verbs [verb name=peek effect=observe [intent [do :peek]] [reads shadow]]] [requirements [requirement kind=functional as=user traces=peek [want 'to peek'] [so 'peeked']]]]] [= $r [$xap:compose-report $chart $dangling]] [$first [?for [in $c $r//conflict] [yield $c@detail]]]] ``` ```console $ cx prog.cx '[from …] reference "nosuchfeature/nothing" is not a noun of the composed grammar' ``` `prog.cx` ```cx [?lib 'cx-platform/xap' as=xap] [?let [= $chart [feature name=chart [nouns [noun name=viewport singular=true [field name=center type=geo-point]]] [verbs [verb name=highlight effect=arrange [intent [do :highlight]] [reads viewport]]] [requirements [requirement kind=functional as=user traces=highlight [want 'to mark a spot'] [so 'I can find it']]]]] [= $shade [feature name=shade kind=composite [uses features='chart'] [nouns [noun name=shadow derived=true [field name=n type=int] [from 'chart/viewport']]] [verbs [verb name=peek effect=observe [intent [do :peek]] [reads shadow]]] [requirements [requirement kind=functional as=user traces=peek [want 'to peek'] [so 'peeked']]]]] [= $r [$xap:compose-report $chart $shade]] $r@ok] ``` ```console $ cx prog.cx true ``` **Derived nouns are deriver-reserved.** No grammar verb may `[writes]` one — the only producer is the declared deriver: `prog.cx` ```cx [?lib 'cx-platform/xap' as=xap] [?let [= $chart [feature name=chart [nouns [noun name=viewport singular=true [field name=center type=geo-point]]] [verbs [verb name=highlight effect=arrange [intent [do :highlight]] [reads viewport]]] [requirements [requirement kind=functional as=user traces=highlight [want 'to mark a spot'] [so 'I can find it']]]]] [= $shade [feature name=shade kind=composite [uses features='chart'] [nouns [noun name=shadow derived=true [field name=n type=int] [from 'chart/viewport']]] [verbs [verb name=assert-shadow effect=act [intent [do :assert-shadow [n]]] [writes shadow]] [verb name=peek effect=observe [intent [do :peek]] [reads shadow]]] [requirements [requirement kind=functional as=user traces=peek [want 'to peek'] [so 'peeked']]]]] [= $r [$xap:compose-report $chart $shade]] [probe [ok $r@ok] [w7 [$count [?for [in $c $r//conflict] [where [= $c@code ':w7']] [yield $c]]]]]] ``` ```console $ cx prog.cx [probe [ok false] [w7 1]] ``` **A derived verb's signature is the floor of its constituents.** It cannot be weaker than what it composes: `prog.cx` ```cx [?lib 'cx-platform/xap' as=xap] [?let [= $chart [feature name=chart [nouns [noun name=viewport singular=true [field name=center type=geo-point]]] [verbs [verb name=set-waypoint effect=act scope=shared consequence=reversible [intent [do :set-waypoint]] [writes viewport]]] [requirements [requirement kind=functional as=user traces=set-waypoint [want 'to set a waypoint'] [so 'I can navigate']]]]] [= $strikes [feature name=strikes [nouns [noun name=strike [field name=pos type=geo-point] [field name=at type=instant]]] [verbs [verb name=highlight effect=arrange [intent [do :highlight]] [reads strike]]] [requirements [requirement kind=functional as=user traces=highlight [want 'to see strikes'] [so 'I avoid them']]]]] [= $cpa-guard [feature name=cpa-guard kind=composite [uses features='chart strikes'] [nouns [noun name=threat derived=true [field name=level type=int] [from 'chart/viewport' 'strikes/strike']]] [verbs [verb name=mark-threat effect=act scope=shared consequence=reversible [intent [do :mark-threat]] [constituents 'chart/set-waypoint strikes/highlight'] [reads threat]]] [requirements [requirement kind=functional as=user traces=mark-threat [want 'to mark a threat'] [so 'the crew sees it']]]]] [?let [= $g [$xap:compose $chart $strikes $cpa-guard]] $g//verb[= $_@name 'cpa-guard/mark-threat']/@derived]] ``` ```console $ cx prog.cx [derived true] ``` `prog.cx` ```cx [?lib 'cx-platform/xap' as=xap] [?let [= $chart [feature name=chart [nouns [noun name=viewport singular=true [field name=center type=geo-point]]] [verbs [verb name=set-waypoint effect=act scope=shared consequence=reversible [intent [do :set-waypoint]] [writes viewport]]] [requirements [requirement kind=functional as=user traces=set-waypoint [want 'to set a waypoint'] [so 'I can navigate']]]]] [= $weak-guard [feature name=weak-guard kind=composite [uses features=chart] [nouns [noun name=threat derived=true [field name=level type=int] [from 'chart/viewport']]] [verbs [verb name=mark-threat effect=observe [intent [do :mark-threat]] [constituents 'chart/set-waypoint'] [reads threat]]] [requirements [requirement kind=functional as=user traces=mark-threat [want 'to mark a threat'] [so 'the crew sees it']]]]] [$xap:compose $chart $weak-guard]] ``` ```console $ cx prog.cx [err code=cx-err:CXER4870 message='E_XAP_COMPOSE_CONFLICT: composition rejected with 1 conflict(s)' [conflict code=':w5' at='weak-guard/mark-threat' detail='declared effect="observe" weakens the derived floor "act"']] ``` ### The deriver is a principal This is the design move that makes the rest cohere. A deriver is not a callback or a background worker; it is an **actor**, declared beside the roles and the agents, so run assembly reads *one* block to know every actor in the system: ```cx [principals [role name=customer authority=own-orders features='orders shipments'] [role name=fulfilment authority=operations features='*'] [agent name=expediter] [deriver name=detect produces='delayed-shipment/delay-alert' package='./detect.cx' reads='orders/order shipments/shipment']] ``` `reference/shop/shop.xap.cxd:18-27`. Note `reads=`: it is the deriver's read **authority envelope**, and it must resolve inside the `[from …]` envelope of the noun it produces. Run assembly checks that. The deriver module records its production explicitly — `reference/shop/detect.cx:21-25` calls `[$xap:derive $rt {deriver: "detect", noun: "delayed-shipment/delay-alert", record: …}]`, and the entry commits as `actor: deriver:detect`. The join itself is ordinary CX (`reference/shop/detect.cx:32-46`) — there is **no join algebra**. The specification declares production, not semantics. **Precedence you must know:** when the deployment document carries `[deriver]` rows, they *supersede* an OPTS `derivers:` key **entirely — they are not merged**, and the superseded key is announced at boot. With no `[deriver]` rows the OPTS key forwards unchanged. See `spec/03-approved/xap/xap_feature_distribution_market.md:634-643`. ## 4. Authority: the PEP, and what a grant does not convey The policy enforcement point sits between an intent and its effect. Two properties carry most of the model. **Authority is rooted in a principal, delegated, and attenuating.** A permit names the chain it travelled: `prog.cx` ```cx [?lib 'cx-stdlib/authz'] [?lib 'cx-platform/authz-store'] [?let [= $az [$authz-store:store {tenant: 'acme'}]] [= $d [$authz-store:delegate $az [delegation d-1 [tenant acme] [from [principal dana]] [to [agent ops-1]] [capabilities [refund-duplicate]] [over '/orders'] [assurance :t1] [signature sig-dana]]]] [$authz:check $az [authz-request [actor [agent ops-1]] [capability refund-duplicate] [slice '/orders/9'] [tenant acme]]]] ``` ```console $ cx prog.cx [permit rooted-principal=dana [delegation d-1] [via d-1] [tier :t1] [capability refund-duplicate]] ``` **A denial is a value carrying its reason.** There is no implicit grant, and the reasons stay distinguishable rather than collapsing into one "forbidden" — no grant at all, a grant that does not reach this slice, a grant that has expired: `prog.cx` ```cx [?lib 'cx-stdlib/authz'] [?let [= $az [$authz:open {tenant: 'acme'}]] [$authz:check $az [authz-request [actor [agent ghost]] [capability refund-duplicate] [slice '/orders/9'] [tenant acme]]]] ``` ```console $ cx prog.cx [deny actor=ghost [code cx-err:CXER4700] [reason :no-grant] [capability refund-duplicate] [slice /orders/9] [tenant id=acme]] ``` `prog.cx` ```cx [?lib 'cx-stdlib/authz'] [?let [= $az [$authz:open {tenant: 'acme', grants: [delegation d-1 [tenant acme] [from [principal dana]] [to [agent ops-1]] [capabilities [refund-duplicate]] [over '/orders'] [assurance :t1] [signature s]]}]] [$authz:check $az [authz-request [actor [agent ops-1]] [capability refund-duplicate] [slice '/payments/9'] [tenant acme]]]] ``` ```console $ cx prog.cx [deny actor=ops-1 [code cx-err:CXER4700] [reason :out-of-slice] [capability refund-duplicate] [slice /payments/9] [tenant id=acme]] ``` `prog.cx` ```cx [?lib 'cx-stdlib/authz'] [?let [= $az [$authz:open {tenant: 'acme', grants: [delegation d-1 [tenant acme] [from [principal dana]] [to [agent ops-1]] [capabilities [refund]] [over '/orders'] [until 1700000000] [assurance :t1] [signature s]]}]] [$authz:check $az [authz-request [actor [agent ops-1]] [capability refund] [slice '/orders/9'] [tenant acme] [as-of 1800000000]]]] ``` ```console $ cx prog.cx [deny actor=ops-1 [code cx-err:CXER4700] [reason :expired] [capability refund] [slice /orders/9] [tenant id=acme]] ``` The distinction matters operationally: "you were never granted this" and "your grant does not cover this resource" send an operator to two different places. ### A composite verb evaluates its transitive leaves This is the rule that stops a composite from laundering authority. Emitting a derived verb requires the grants of every **leaf constituent**, transitively: `prog.cx` ```cx [?lib 'cx-platform/xap' as=xap] [?let [= $door [feature name=door [nouns [noun name=door singular=true [field name=locked type=int]]] [verbs [verb name=unlock effect=act scope=shared consequence=irreversible [intent [do :unlock]] [writes door]] [verb name=chime effect=observe [intent [do :chime]] [reads door]]] [requirements [requirement kind=functional as=resident traces=unlock [want 'to unlock the door'] [so 'guests can enter']] [requirement kind=functional as=resident traces=chime [want 'to sound the chime'] [so 'arrival is announced']]]]] [= $porch [feature name=porch kind=composite [uses features=door] [nouns [noun name=visit derived=true [field name=count type=int] [from 'door/door']]] [verbs [verb name=welcome effect=act scope=shared consequence=irreversible [intent [do :welcome]] [constituents 'door/unlock door/chime'] [reads visit]]] [requirements [requirement kind=functional as=resident traces=welcome [want 'to welcome a guest'] [so 'arrival is one gesture']]]]] [= $g [$xap:compose $door $porch]] [= $rt [$xap:run {tenant: "demo" grammar: $g derivers: ({name: "counter" produces: "porch/visit"})}]] [= $d [$xap:dial $rt [from id="principal:dana"] [to id="agent:porter-1"] [scope 'door/chime']]] [$xap:emit $rt [do :welcome] {actor: "agent:porter-1"}]] ``` ```console $ cx prog.cx [err code=cx-err:CXER4850 message='E_XAP_UNAUTHORIZED: actor "agent:porter-1" is not granted "door/unlock" over ""' at='24:2'] ``` A grant on the wrapper's own name conveys **nothing** — the wrapper name is never consulted: `prog.cx` ```cx [?lib 'cx-platform/xap' as=xap] [?let [= $door [feature name=door [nouns [noun name=door singular=true [field name=locked type=int]]] [verbs [verb name=unlock effect=act scope=shared consequence=irreversible [intent [do :unlock]] [writes door]] [verb name=chime effect=observe [intent [do :chime]] [reads door]]] [requirements [requirement kind=functional as=resident traces=unlock [want 'to unlock the door'] [so 'guests can enter']] [requirement kind=functional as=resident traces=chime [want 'to sound the chime'] [so 'arrival is announced']]]]] [= $porch [feature name=porch kind=composite [uses features=door] [nouns [noun name=visit derived=true [field name=count type=int] [from 'door/door']]] [verbs [verb name=welcome effect=act scope=shared consequence=irreversible [intent [do :welcome]] [constituents 'door/unlock door/chime'] [reads visit]]] [requirements [requirement kind=functional as=resident traces=welcome [want 'to welcome a guest'] [so 'arrival is one gesture']]]]] [= $g [$xap:compose $door $porch]] [= $rt [$xap:run {tenant: "demo" grammar: $g derivers: ({name: "counter" produces: "porch/visit"})}]] [= $d [$xap:dial $rt [from id="principal:dana"] [to id="agent:porter-1"] [scope 'porch/welcome']]] [$xap:emit $rt [do :welcome] {actor: "agent:porter-1"}]] ``` ```console $ cx prog.cx [err code=cx-err:CXER4850 message='E_XAP_UNAUTHORIZED: actor "agent:porter-1" is not granted "door/chime" over ""' at='24:2'] ``` Dial every leaf and the same emit is admitted: `prog.cx` ```cx [?lib 'cx-platform/xap' as=xap] [?let [= $door [feature name=door [nouns [noun name=door singular=true [field name=locked type=int]]] [verbs [verb name=unlock effect=act scope=shared consequence=irreversible [intent [do :unlock]] [writes door]] [verb name=chime effect=observe [intent [do :chime]] [reads door]]] [requirements [requirement kind=functional as=resident traces=unlock [want 'to unlock the door'] [so 'guests can enter']] [requirement kind=functional as=resident traces=chime [want 'to sound the chime'] [so 'arrival is announced']]]]] [= $porch [feature name=porch kind=composite [uses features=door] [nouns [noun name=visit derived=true [field name=count type=int] [from 'door/door']]] [verbs [verb name=welcome effect=act scope=shared consequence=irreversible [intent [do :welcome]] [constituents 'door/unlock door/chime'] [reads visit]]] [requirements [requirement kind=functional as=resident traces=welcome [want 'to welcome a guest'] [so 'arrival is one gesture']]]]] [= $g [$xap:compose $door $porch]] [= $rt [$xap:run {tenant: "demo" grammar: $g derivers: ({name: "counter" produces: "porch/visit"})}]] [= $d1 [$xap:dial $rt [from id="principal:dana"] [to id="agent:porter-1"] [scope 'door/unlock']]] [= $d2 [$xap:dial $rt [from id="principal:dana"] [to id="agent:porter-1"] [scope 'door/chime']]] [$xap:emit $rt [do :welcome] {actor: "agent:porter-1"}]] ``` ```console $ cx prog.cx [event actor=agent:porter-1 [do porch/welcome]] ``` Nesting does not dilute it — an *intermediate* grant is still insufficient: `prog.cx` ```cx [?lib 'cx-platform/xap' as=xap] [?let [= $door [feature name=door [nouns [noun name=door singular=true [field name=locked type=int]]] [verbs [verb name=unlock effect=act scope=shared consequence=irreversible [intent [do :unlock]] [writes door]] [verb name=chime effect=observe [intent [do :chime]] [reads door]]] [requirements [requirement kind=functional as=resident traces=unlock [want 'to unlock the door'] [so 'guests can enter']] [requirement kind=functional as=resident traces=chime [want 'to sound the chime'] [so 'arrival is announced']]]]] [= $porch [feature name=porch kind=composite [uses features=door] [nouns [noun name=visit derived=true [field name=count type=int] [from 'door/door']]] [verbs [verb name=welcome effect=act scope=shared consequence=irreversible [intent [do :welcome]] [constituents 'door/unlock door/chime'] [reads visit]]] [requirements [requirement kind=functional as=resident traces=welcome [want 'to welcome a guest'] [so 'arrival is one gesture']]]]] [= $estate [feature name=estate kind=composite [uses features=porch] [nouns [noun name=open-day derived=true [field name=visits type=int] [from 'porch/visit']]] [verbs [verb name=open-house effect=act scope=shared consequence=irreversible [intent [do :open-house]] [constituents 'porch/welcome'] [reads open-day]]] [requirements [requirement kind=functional as=resident traces=open-house [want 'to run an open house'] [so 'visitors flow in']]]]] [= $g [$xap:compose $door $porch $estate]] [= $rt [$xap:run {tenant: "demo" grammar: $g derivers: ({name: "counter" produces: "porch/visit"}, {name: "planner" produces: "estate/open-day"})}]] [= $d [$xap:dial $rt [from id="principal:dana"] [to id="agent:porter-1"] [scope 'porch/welcome']]] [$xap:emit $rt [do :open-house] {actor: "agent:porter-1"}]] ``` ```console $ cx prog.cx [err code=cx-err:CXER4850 message='E_XAP_UNAUTHORIZED: actor "agent:porter-1" is not granted "door/chime" over ""' at='34:2'] ``` `prog.cx` ```cx [?lib 'cx-platform/xap' as=xap] [?let [= $door [feature name=door [nouns [noun name=door singular=true [field name=locked type=int]]] [verbs [verb name=unlock effect=act scope=shared consequence=irreversible [intent [do :unlock]] [writes door]] [verb name=chime effect=observe [intent [do :chime]] [reads door]]] [requirements [requirement kind=functional as=resident traces=unlock [want 'to unlock the door'] [so 'guests can enter']] [requirement kind=functional as=resident traces=chime [want 'to sound the chime'] [so 'arrival is announced']]]]] [= $porch [feature name=porch kind=composite [uses features=door] [nouns [noun name=visit derived=true [field name=count type=int] [from 'door/door']]] [verbs [verb name=welcome effect=act scope=shared consequence=irreversible [intent [do :welcome]] [constituents 'door/unlock door/chime'] [reads visit]]] [requirements [requirement kind=functional as=resident traces=welcome [want 'to welcome a guest'] [so 'arrival is one gesture']]]]] [= $estate [feature name=estate kind=composite [uses features=porch] [nouns [noun name=open-day derived=true [field name=visits type=int] [from 'porch/visit']]] [verbs [verb name=open-house effect=act scope=shared consequence=irreversible [intent [do :open-house]] [constituents 'porch/welcome'] [reads open-day]]] [requirements [requirement kind=functional as=resident traces=open-house [want 'to run an open house'] [so 'visitors flow in']]]]] [= $g [$xap:compose $door $porch $estate]] [= $rt [$xap:run {tenant: "demo" grammar: $g derivers: ({name: "counter" produces: "porch/visit"}, {name: "planner" produces: "estate/open-day"})}]] [= $d1 [$xap:dial $rt [from id="principal:dana"] [to id="agent:porter-1"] [scope 'door/unlock']]] [= $d2 [$xap:dial $rt [from id="principal:dana"] [to id="agent:porter-1"] [scope 'door/chime']]] [$xap:emit $rt [do :open-house] {actor: "agent:porter-1"}]] ``` ```console $ cx prog.cx [event actor=agent:porter-1 [do estate/open-house]] ``` ### The decision explains itself `[$xap:why-allowed]` answers over exactly the same constituent set the PEP used, so the explanation cannot drift from the decision: `prog.cx` ```cx [?lib 'cx-platform/xap' as=xap] [?let [= $door [feature name=door [nouns [noun name=door singular=true [field name=locked type=int]]] [verbs [verb name=unlock effect=act scope=shared consequence=irreversible [intent [do :unlock]] [writes door]] [verb name=chime effect=observe [intent [do :chime]] [reads door]]] [requirements [requirement kind=functional as=resident traces=unlock [want 'to unlock the door'] [so 'guests can enter']] [requirement kind=functional as=resident traces=chime [want 'to sound the chime'] [so 'arrival is announced']]]]] [= $porch [feature name=porch kind=composite [uses features=door] [nouns [noun name=visit derived=true [field name=count type=int] [from 'door/door']]] [verbs [verb name=welcome effect=act scope=shared consequence=irreversible [intent [do :welcome]] [constituents 'door/unlock door/chime'] [reads visit]]] [requirements [requirement kind=functional as=resident traces=welcome [want 'to welcome a guest'] [so 'arrival is one gesture']]]]] [= $g [$xap:compose $door $porch]] [= $rt [$xap:run {tenant: "demo" grammar: $g derivers: ({name: "counter" produces: "porch/visit"})}]] [= $d [$xap:dial $rt [from id="principal:dana"] [to id="agent:porter-1"] [scope 'porch/welcome']]] [?let [= $w [$xap:why-allowed $rt [do :welcome] {actor: "agent:porter-1"}]] $w/@allowed]] ``` ```console $ cx prog.cx 'false' ``` `prog.cx` ```cx [?lib 'cx-platform/xap' as=xap] [?let [= $door [feature name=door [nouns [noun name=door singular=true [field name=locked type=int]]] [verbs [verb name=unlock effect=act scope=shared consequence=irreversible [intent [do :unlock]] [writes door]] [verb name=chime effect=observe [intent [do :chime]] [reads door]]] [requirements [requirement kind=functional as=resident traces=unlock [want 'to unlock the door'] [so 'guests can enter']] [requirement kind=functional as=resident traces=chime [want 'to sound the chime'] [so 'arrival is announced']]]]] [= $porch [feature name=porch kind=composite [uses features=door] [nouns [noun name=visit derived=true [field name=count type=int] [from 'door/door']]] [verbs [verb name=welcome effect=act scope=shared consequence=irreversible [intent [do :welcome]] [constituents 'door/unlock door/chime'] [reads visit]]] [requirements [requirement kind=functional as=resident traces=welcome [want 'to welcome a guest'] [so 'arrival is one gesture']]]]] [= $g [$xap:compose $door $porch]] [= $rt [$xap:run {tenant: "demo" grammar: $g derivers: ({name: "counter" produces: "porch/visit"})}]] [= $d1 [$xap:dial $rt [from id="principal:dana"] [to id="agent:porter-1"] [scope 'door/unlock']]] [= $d2 [$xap:dial $rt [from id="principal:dana"] [to id="agent:porter-1"] [scope 'door/chime']]] [?let [= $w [$xap:why-allowed $rt [do :welcome] {actor: "agent:porter-1"}]] $w/@allowed]] ``` ```console $ cx prog.cx 'true' ``` The authz layer does the same for delegated authority — naming the **first failing link** rather than reporting a bare denial: `prog.cx` ```cx [?lib 'cx-stdlib/authz'] [?let [= $az [$authz:open {tenant: 'acme', grants: [delegation d-1 [tenant acme] [from [principal dana]] [to [agent ops-1]] [capabilities [refund]] [over '/orders'] [assurance :t1] [signature s]]}]] [= $dec [$authz:check $az [authz-request [actor [agent ops-1]] [capability refund] [slice '/orders/9'] [tenant acme]]]] [$authz:explain $dec]] ``` ```console $ cx prog.cx [explanation outcome=permit [accountable dana] [authority-chain [step d-1]]] ``` `prog.cx` ```cx [?lib 'cx-stdlib/authz'] [?let [= $az [$authz:open {tenant: 'acme'}]] [= $dec [$authz:check $az [authz-request [actor [agent x]] [capability y] [tenant acme]]]] [$authz:explain $dec]] ``` ```console $ cx prog.cx [explanation outcome=deny [first-failing-link :no-grant] [code cx-err:CXER4700]] ``` An enterprise deployment needs this more than it needs the enforcement: an authority chain nobody can read is one nobody can review. ### The dial moves *who drives*, never *what is permitted* An agent's autonomy is a dial, and at its floor an agent may observe and propose but never act. `reference/shop/shop.xap.cxd:40-41` sets `[agent-default name=expediter dial=floor]` and says why: an irreversible verb is "out of its reach **by construction, not by convention**". Raising the dial is the single explicit act that widens what the agent may do. ## 5. The deployment document (`*.xap.cxd`) The wiring layer. Its complete child set is fixed by `spec/03-approved/xap/xap_schemas/xap.cxs:14-24`: ``` [xap name= version= [summary …] [features …] [principals …] [runtime …] [deployment …] [surfacing …] [governance …] [requirements …]] ``` `name=` is required and `[features]` is required exactly once (`xap.cxs:15`, `:18`). Everything else is `0..1`. That cardinality is the schema saying the same thing the vacuity refusal of §2 says: a deployment that enables nothing is not a deployment. **There is no `[transport]` block, and you must not invent one.** The transport is the store — `spec/03-approved/xap/xap_feature_distribution_market.md:225`, and "no new transport" at `:710`. Remote endpoints are declared as `[deployment [remote name= uri= priority=]]` (`spec/03-approved/xap/xap_schemas/xap.cxs:125-133`). The reference instance is `reference/shop/shop.xap.cxd` in full — features `:13-16`, principals `:18-27`, deployment `:29-31`, surfacing `:33-35`, governance `:37-41`, requirements `:43-54`. Read it; it is 55 lines and it is the whole shape. ### `[features]` — pinned, and the hash is the truth ```cx [features [feature name=orders package='./orders.feature.cxd'] [feature name=shipments package='./shipments.feature.cxd'] [feature name=delayed-shipment package='./delayed-shipment.feature.cxd']] ``` `version=` on a row is **informative; the hash is the truth** (`spec/03-approved/xap/xap_schemas/xap.cxs:56`). A pin/content mismatch refuses to boot. ### `[runtime]` — the durable plane, as data Four run options — `journal`, `sources`, `resolver`, `log-reduce` — ride a `[runtime]` block, **one child per option, each named for its option key verbatim**, so the document and the opts map share one vocabulary. The host compiles the document's data into exactly the values `[$xap:run]` accepts: there is **one validator, never two**. ```cx [runtime [journal url='file:///var/acme/acts' stream=acts checkpoint='file:///var/acme/ckpt' checkpoint-every=512] [sources [source fabric='xsp://127.0.0.1:8447' stream=evidence verb=record group=acme-xap actor='role:operator']] [resolver [affinity component='orders/order-list' when='context[deadline]' class=:orders.urgent rank=1]] [log-reduce window=500 fn='orders:compact']] ``` `spec/03-approved/xap/xap_feature_distribution_market.md:600-609`. Per child: * **`[journal url= …]`** — `url=` is required. Without a journal block a hosted XAP is in-process demo mode; with one, the bound stream **is** the runtime's journal and restart is a re-fold. * **`[sources [source …]]`** — one `[source]` row each. **A `[sources]` block with no row is a refusal, never a silent no-op.** * **`[resolver …]`** — only the kinds that *are* data: `kind=scripted` and `[affinity …]` rule rows. A **closure** resolver is code, not deployment data, and stays on the direct `[$xap:run]` path. * **`[log-reduce window= fn=]`** — `fn=` names a public def of a *pinned feature's* contract module as `:`. Because a binding may name a contract def, the host's load step runs **before** its attach step. The four `[runtime]` options have **no OPTS spelling on a hosted XAP**: naming one in `[$xap:host]` opts refuses at boot with `E_XAP_HOST_BINDING_MISPLACED` — rather than being accepted twice or dropped once. Every refusal here is named and loud at boot: an unknown `[runtime]` child, a `[journal]` without `url=`, an empty `[sources]`, an `fn=` naming no loaded contract def. `spec/…market.md:640-649`. **Boot ordering.** A `[sources]` subscription **opens** during run assembly — so a bad binding refuses at boot rather than at first delivery — but **consumes** nothing until the dials and the host-auth map are wired. Opening and consuming are two moments, with the deployment's authority between them. A boot that refuses after run assembly consumed nothing, therefore acked nothing, so every entry stays redeliverable to the next boot. `spec/…market.md:651-667`. > **Honest gap.** No `[runtime]` block is instantiated anywhere in this > repository today — the schema (`xap.cxs:90-118`) and the spec example above > are the only spellings, and `reference/shop/shop.xap.cxd` carries none. > Yours will be the first. Treat the schema as normative and the example as > the shape. ## 6. Bootstrap identity A production deployment is deny-by-default at every layer, which means a fresh one has nobody who may talk to it. Bootstrapping is deliberate. ### Mint the principals offline ```console cx store-mint-principal --id fleet-ops \ --seed-file ./secrets/fleet-ops.seed --caps "read write" cx store-mint-principal --id shop-host \ --seed-file ./secrets/shop-host.seed --for identity ``` Nothing transits a wire and no store is opened. The verb generates an Ed25519 seed, derives its `did:key`, writes the seed at mode 0600 (never to stdout), and prints the config rows: `--for grant` (the default) prints the `[xsp [grants [grant …]]]` row authorizing that principal; `--for identity` prints the daemon's own `[xsp [identity did= seed-env=]]` responder row. `--caps` is **required** for a grant row — the authority a grant carries is an explicit choice at mint time, with no default. The seed environment variable is *derived* from `--id` (`CX_XSP_SEED_`), so two ids that would derive the same variable are refused by name rather than silently sharing one seed. **The minted principal is inert until an operator splices the grant into the config. Config remains the sole authority.** There is no trust-on-first-use step and no bearer token anywhere in this path. The arc: **mint → splice the grant into the config → start the deny-by-default daemon → the client presents its DID.** ### Bind the host's channel policy `[$xap:host]` reads one block of deployment data for its channel authentication: ```cx [host-auth [identity did="did:key:z6Mk…" seed-env="CX_XAP_HOST_SEED"] [policy mode="mutual"] [principals [principal did="did:key:z6Mk…" role="operator"]] [public [route "/"] [route "/static/"]]] ``` `spec/03-approved/xap/xap_identity_model.md:635-641`. Four things to know: * **Absent means today's behavior, byte-for-byte** — the localhost dev floor. Going from floor to production is *adding this block* and nothing else. `:652-657`. * **The block is `[host-auth]`, not `[auth]`** — a deployment may already carry an `[auth]` element for its own login configuration, a distinct concern. Squatting on `[auth]` silently broke that invariant once already. `:644-650`. * **Boot is fail-closed on `[identity]`** — a missing seed variable, a malformed seed, or a seed whose public key does not re-derive `did=` refuses to boot. Never a latent runtime denial. `:658-663`. * **An authenticated DID with no `[principal]` row attaches but holds no dials** — the session is real and every intent is PEP-denied. That is default-deny working, not a bug. `:670-674`. `[policy mode="floor"]` attaches an anonymous peer as the fixed principal `floor:`; the `floor:` prefix is **constructed by the host** so a floor principal can never collide with an inherent-authority `principal:` id. `POST /attach` is always open — it *is* the handshake. ### Sessions bind a principal, immutably Below the host, `cx-platform/session` binds a `(principal, tenant)` pair for the session's whole life. An anonymous floor is a real principal: `prog.cx` ```cx [?lib 'cx-platform/session'] [?let [= $pair [$session:attach-guest [request scheme="https"] {anonymous-floor: "web-public" tenant: "shop"}]] [= $s [$first $pair]] [?let [= $p [$session:principal $s]] $p@id]] ``` ```console $ cx prog.cx web-public ``` …and a deployment that declares no floor refuses cleanly, naming the policy: `prog.cx` ```cx [?lib 'cx-platform/session'] [$session:attach-guest [request scheme="https"] {tenant: "shop"}] ``` ```console $ cx prog.cx [err code=cx-err:CXER4812 message='E_SESSION_ANONYMOUS_REFUSED: the deployment attach policy does not admit an anonymous floor — no `anonymous-floor` principal in cfg (xap_identity_model §4.7; refusing is the production default)' at='2:1'] ``` The mutual-authentication path is the same story with a handshake in front of it. `[$session:attach-xsp]` binds the **proven** DID — the principal the peer demonstrated possession of, never a claimed one — and an anonymous peer with no declared floor is refused with `CXER4803`. A presented verifiable credential compiles into an ordinary authz delegation, so the *unchanged* PEP decides: there is one evaluator, not a credential path running beside the authority path, and a credential past its time of use denies at check. Those four cases carry full RFC key vectors and are long, so they are cited rather than printed. Read them in `conformance/platform/session.cxd`: `session-045-attach-xsp-binds-proven-principal`, `session-047-attach-xsp-anonymous-no-floor-rejected`, `session-050-present-vc-compiles-to-permit`, `session-052-present-vc-time-of-use-expiry`. The shape they pin is the whole lesson: **proof, then principal, then delegation, then the same check everything else goes through.** Session ids are opaque and are **never** the raw token: `prog.cx` ```cx [?lib 'cx-platform/session'] [?lib 'cx-stdlib/crypto'] [?lib 'cx-stdlib/time'] [?lib 'cx-stdlib/strings'] [?let [= $pair [$session:attach-cookie [request scheme="https" [headers [header name="Authorization" value="Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6InJzYS0xIiwidHlwIjoiSldUIn0.eyJpc3MiOiJodHRwczovL2lkcC5leGFtcGxlIiwic3ViIjoidXNlci0xIiwiYXVkIjoibXktYXBpIiwiZXhwIjoxNzAwMDAzNjAwLCJuYmYiOjE2OTk5OTk5OTAsImlhdCI6MTY5OTk5OTk5MH0.e4bn1A_a5Q0_4IZxKWipmmrUCCdmNkiTUs071mBMtMVXb68RGPtV2FTR1nRWvM3Zw2XTMuBsk_Y1HPinat_2JQkm3s90lltrErpYOky6Nwm6ha57BxQ0Sg5kWxuQF5KhWUqHzePIVDTfkr0WeN591nDTDn6VuajLaHn2pjXBsaJ0reT6mh6v40UQcBW-yy-XOfrsMeGVj_qZd1UmlE81XN957rjPENcPpH2E1SHfVhu3fQP3QZapxtdMADgWmryw_z5O2lDIIWb7miT0siXRCDjVEhDzKH6Hsf1aUPcYx9x44o1B5OBpX6z1uM1SP0qaOZu6x26H0AbHj84nIISE1A"]]] {jwks: [$crypto:jwks-parse "{\"keys\":[{\"kid\":\"rsa-1\",\"kty\":\"RSA\",\"alg\":\"RS256\",\"n\":\"jdwwBcaMZQLoSNYGNEm3l03HIQqpRIv0eqLUNUCkyv7ysVw4i6vZgdYxcdU0D3kSvUCIjH-icqk4PCDx5AwkeNp55Nqt6wKXDv9TH5pr-Wc3BWmZ1sEOEwyN8QlI8_5EpY3i1w5tysDdeFuiR7BOjpkD49RZzF0YajmocsB4_jXoZcldVNCOChXAbEfOw-BtjFJRN0N3EksymF4azkey8Q3rX07sXkRHavRfAOnowH119RL1V-Xmk_DUX8wSR-2zdlp8FxitJWPgbMmnszsTgEQrThTwsuMrndqzJ_nQ7HTnuK2LXEFmFwXw9NADspq2ZnQmZiN7CXnEYG8WCApzxw\",\"e\":\"AQAB\"}]}"] tenant-claim: "aud" now: [$time:datetime 2023 11 14 22 13 20]}]] [= $s [$first $pair]] [$strings:starts-with $s@id "s-"]] ``` ```console $ cx prog.cx true ``` `prog.cx` ```cx [?lib 'cx-platform/session'] [?lib 'cx-stdlib/crypto'] [?lib 'cx-stdlib/time'] [?let [= $pair [$session:attach-cookie [request scheme="https" [headers [header name="Authorization" value="Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6InJzYS0xIiwidHlwIjoiSldUIn0.eyJpc3MiOiJodHRwczovL2lkcC5leGFtcGxlIiwic3ViIjoidXNlci0xIiwiYXVkIjoibXktYXBpIiwiZXhwIjoxNzAwMDAzNjAwLCJuYmYiOjE2OTk5OTk5OTAsImlhdCI6MTY5OTk5OTk5MH0.e4bn1A_a5Q0_4IZxKWipmmrUCCdmNkiTUs071mBMtMVXb68RGPtV2FTR1nRWvM3Zw2XTMuBsk_Y1HPinat_2JQkm3s90lltrErpYOky6Nwm6ha57BxQ0Sg5kWxuQF5KhWUqHzePIVDTfkr0WeN591nDTDn6VuajLaHn2pjXBsaJ0reT6mh6v40UQcBW-yy-XOfrsMeGVj_qZd1UmlE81XN957rjPENcPpH2E1SHfVhu3fQP3QZapxtdMADgWmryw_z5O2lDIIWb7miT0siXRCDjVEhDzKH6Hsf1aUPcYx9x44o1B5OBpX6z1uM1SP0qaOZu6x26H0AbHj84nIISE1A"]]] {jwks: [$crypto:jwks-parse "{\"keys\":[{\"kid\":\"rsa-1\",\"kty\":\"RSA\",\"alg\":\"RS256\",\"n\":\"jdwwBcaMZQLoSNYGNEm3l03HIQqpRIv0eqLUNUCkyv7ysVw4i6vZgdYxcdU0D3kSvUCIjH-icqk4PCDx5AwkeNp55Nqt6wKXDv9TH5pr-Wc3BWmZ1sEOEwyN8QlI8_5EpY3i1w5tysDdeFuiR7BOjpkD49RZzF0YajmocsB4_jXoZcldVNCOChXAbEfOw-BtjFJRN0N3EksymF4azkey8Q3rX07sXkRHavRfAOnowH119RL1V-Xmk_DUX8wSR-2zdlp8FxitJWPgbMmnszsTgEQrThTwsuMrndqzJ_nQ7HTnuK2LXEFmFwXw9NADspq2ZnQmZiN7CXnEYG8WCApzxw\",\"e\":\"AQAB\"}]}"] tenant-claim: "aud" now: [$time:datetime 2023 11 14 22 13 20]}]] [= $s [$first $pair]] [= $r [$session:rotate $s]] [= $s@id $r@id]] ``` ```console $ cx prog.cx false ``` Re-minting the session id on a privilege transition is the fixation defense. `spec/03-approved/xap/demos/oriel/serve.cx:2523-2528` does exactly that on sign-in, and carries the basket forward by an **explicit journaled adoption** — "never a silent rewrite, never a silent loss". > **A discipline worth copying verbatim.** Oriel never writes a session id > into its journal. It writes a *derived, non-invertible* stream key — > `sha2-256(secret ‖ session-id)`, truncated > (`spec/03-approved/xap/demos/oriel/session.cx:70`, rationale at `:72-82`). > A journal is durable, exportable, and frequently handed to an analyst; > writing the session id there would put **live bearer material into the > permanent audit record**, where it survives logout and outlives the session > it authenticates. ## 7. Host it, and project a surface ### The host is six steps `[$xap:host XAP-DOC OPTS]` boots a complete XAP from its deployment document (`spec/03-approved/xap/xap_feature_distribution_market.md:539-577`): 1. **acquire** — open the store, fetch every pinned `[feature]` by pin, verify. A pin/content mismatch refuses to boot. 2. **compose** — `[$xap:compose]` over the sealed spec layers; the composed grammar is stored and attached via `[$xap:run {grammar: …}]`, together with the `[runtime]` bindings of §5. 3. **govern** — roles, `[governance]` grants, and agent envelopes become runtime dials. Actor resolution is an OPTS hook with a default of `role:`. 4. **load** — resolve each feature's `pkg:` reference and register its contract surface, keyed by the composed grammar's qualified verbs. 5. **serve** — the standard surface: `GET /grammar`, `GET /features`, `GET /surface` + `GET /surface/`, `POST /intent`, `GET /stream`. 6. **extend** — OPTS registers what is genuinely deployment-specific: extra routes, ingest workers, sim cadence. **Adapters register onto the host; they never fork it.** Adapter routes are consulted *before* the standard surface, so a deployment may enrich a standard route without forking. **Two layers of refusal, both visible in the ack.** The PEP admits the *verb*; the feature's contract `apply` may still refuse the *values* by returning `[refused reason=…]`, and the host acks `ok=false` with that reason. `spec/…market.md:574-577`. A XAP with no custom transport is therefore **zero server code**: deployment document + published packages + a client. Deploying one more feature is publish, add a pinned row, re-pin, restart. > **Honest gap.** `[$xap:host]` has no live call site in this repository — > every occurrence is specification or schema. The nearest working programs > use the direct `compose` + `run` path and say so: > `reference/shop-web-client/serve.cx:36`, with `[$xap:compose]` at `:131` and > `[$xap:run]` at `:136`. The guide's Operations section, *The XAP host*, is the six-step > summary and carries the `[$xap:host …]!` postfix rule. ### The surface document Routes are composition data. A `[surface]` names its XAP, binds media, and declares which act verbs each route **offers**: * `spec/03-approved/xap/demos/oriel/surface.cx:11-12` — the surface head. * `:23-31` — `[offer verb=…]`, the route's declaration of what is reachable from it. "A route with no `[offer]` is a reading page and asserts nothing." * `:32-37` — a route with its feature instance and fragment path. * `reference/shop/shop.surface.cxd:26-43` — panels, layout regions, and `[materialize]` rows; `:61-67` — `[clients]`, where a human client and an agent client are declared as peers. The comment there is the design in one sentence: the agent "is not a side channel with its own privileges — what it may do is decided by the dial", not by giving it another door. The coverage claim is **mechanical, and it refuses at compose time**. An act verb no route offers is a refusal, not a lint warning: `prog.cx` ```cx [?lib 'cx-platform/ux' as=ux] [?let [= $f [feature name=shop version="1" [nouns [noun name=order [field name=id type=text]]] [verbs [verb name=sell effect=act [intent [do :sell [id]]] [writes order]] [verb name=refund effect=act [intent [do :refund [id]]] [writes order]] [verb name=audit effect=act [intent [do :audit [id]]] [writes order]] [verb name=browse effect=observe [intent [do :browse]] [reads order]]]]] [= $ok [surface a [route path="/o" [feature xap=shop] [offer verb=sell]] [not-offered verb=refund why="No payments rail on this surface."] [not-offered verb=audit why="The audit trail is the journal itself."]]] [= $missing [surface a [route path="/o" [feature xap=shop] [offer verb=sell]] [not-offered verb=refund why="No payments rail on this surface."]]] [= $bare [surface a [route path="/o" [feature xap=shop] [offer verb=sell]] [not-offered verb=refund why="No payments rail on this surface."] [not-offered verb=audit]]] ([$ux:check-surface $ok ("/o") ($f)], [$ux:check-surface $missing ("/o") ($f)], [$ux:check-surface $bare ("/o") ($f)])] ``` ```console $ cx prog.cx (true, [err code=ux-refused [ux-refusal code=ux-verb-unaccounted verb=audit]], [err code=ux-refused [ux-refusal code=ux-verb-unaccounted verb=audit note='declared not-offered without a why']]) ``` `prog.cx` ```cx [?lib 'cx-platform/ux' as=ux] [?let [= $ctx {route: "/orders", feature: "orders", path: "/"}] [= $ok [ux:region feature=orders path="/live" [ux:filter name=q affects="/live/open/rows"] [ux:table id=open [ux:rows id=rows [ux:row key=r1 [ux:cell "a"]]]]]] [= $bad [ux:region feature=orders path="/live" [ux:filter name=q affects="/live/typo/rows"] [ux:table id=open [ux:rows id=rows [ux:row key=r1 [ux:cell "a"]]]]]] [; R-A1 migration: envelope → spliced children (2026-08-25) ] [list [?splice [$ux:dangling-affects $ok $ctx]] [?splice [$ux:dangling-affects $bad $ctx]]]] ``` ```console $ cx prog.cx [list [ux-refusal code=ux-dangling-affects element=ux:filter affects='/live/typo/rows']] ``` Oriel runs that gate at boot (`serve.cx:3940-3948`) and wraps it in a `[?match]` on purpose — the refusal is an **err value**, and as a bare call operand it would propagate and the log line would never run, which is a silent gate. The comment at `serve.cx:3949-3952` says exactly that. This is the err-boundary rule of `primer.md` §6 met in real code. > Several fixtures below carry a `[; R-A1 migration: envelope → spliced > children …]` comment. That is not noise you should imitate — it is the > corpus marking where a call returning a sequence stopped being wrapped in an > envelope element and became `[?splice …]` in element content, per the rule > in `primer.md` §5. Read those lines as worked examples of the migration you > would have to make in your own code. ### The semantic vocabulary is closed You do not write HTML. You write a **semantic tree** over a closed vocabulary, and a renderer lowers it. Three closed sets govern what you may write, and the module reports all three — the element vocabulary first (regions and grouping, text and values, collections, navigation, queries, writes, flows, depiction), then the **renderer-private** attribute names a semantic tree may never carry, then the notice tones: `prog.cx` ```cx [?lib 'cx-platform/ux' as=ux] [; R-A1 migration: envelope → spliced children (2026-08-25) ] [list [?splice [$ux:vocabulary]] [?splice [$ux:renderer-private-attrs]] [?splice [$ux:tones]]] ``` ```console $ cx prog.cx [list 'ux:region' 'ux:card' 'ux:heading' 'ux:text' 'ux:field' 'ux:badge' 'ux:list' 'ux:item' 'ux:table' 'ux:columns' 'ux:column' 'ux:rows' 'ux:row' 'ux:cell' 'ux:nav' 'ux:nav-item' 'ux:nav-group' 'ux:link' 'ux:action' 'ux:form' 'ux:input' 'ux:hidden' 'ux:submit' 'ux:error' 'ux:notice' 'ux:filter' 'ux:select' 'ux:option' 'ux:group' 'ux:media' 'ux:grid' 'ux:param' 'ux:steps' 'ux:step' 'ux:quantity' 'ux:breadcrumb' 'ux:crumb' 'ux:pager' 'ux:page-link' 'ux:facet' 'ux:facet-value' 'ux:sort' 'ux:sort-option' 'ux:aside' 'ux:status' 'ux:panel' 'oob' 'target' 'swap' 'ext' 'subscribe' 'on' 'include' 'ok' 'warn' 'danger' 'info' 'neutral'] ``` An element outside the vocabulary is refused, and so is an attribute outside the grant table of the member it sits on — one mistake, one refusal, naming both the element and the attribute: `prog.cx` ```cx [?lib 'cx-platform/ux' as=ux] [?let [= $bad ([ux:action verb=v label=x disabled=true reason="out"], [ux:aside label=Basket name=basket], [ux:row id=r1 [ux:cell "x"]], [ux:filter name=q affects="/rows" over="id status"])] [?for [in $e $bad] [yield [$ux:refusals-of $e]]]] ``` ```console $ cx prog.cx [ux-refusal code=ux-unknown-attr element=ux:action attr=disabled] [ux-refusal code=ux-unknown-attr element=ux:aside attr=name] [ux-refusal code=ux-unknown-attr element=ux:row attr=id] [ux-refusal code=ux-unknown-attr element=ux:filter attr=over] ``` The second set is the sharper one. `target=`, `swap=`, `oob=`, `ext=`, `subscribe=`, `on=`, `include=` are **renderer-private**: they are how the web renderer thinks, and a semantic tree that names one has stopped being renderer-neutral. That is not a hypothetical — an earlier arrangement of this module let exactly those concepts climb into the vocabulary, and the neutrality claim was false while they were there. The refusal has its own code: `prog.cx` ```cx [?lib 'cx-platform/ux' as=ux] [?let [= $r [$ux:refusals-of [ux:action verb=v target="#x"]]] [= $f [$first $r]] [list [$count $r] [$string $f@code]]] ``` ```console $ cx prog.cx [list 1 'ux-renderer-private-attr'] ``` Retired members refuse **by name, with the replacement stated** — there are no aliases, per the standing no-dual-accept rule. ### Forms project from definitions You do not hand-author a form. A command — a `[?def]` carrying an `[effects]` clause — *projects* onto one: `prog.cx` ```cx [?lib 'cx-platform/ux' as=ux] [?let [= $src """ [?def place-order scope=public impure [effects [write]] [returns element] ($customer::string $qty::int $note::string="") [ordered customer=$customer]] [fn-doc name=place-order scope=public purity=impure [summary '''Place a customer order.'''] [param-doc name=customer '''Who the order is for.''']] """] [$ux:form $src "place-order" {affects: "/live/new"}]] ``` ```console $ cx prog.cx [ux:form verb=place-order affects='/live/new' [ux:heading level=2 Place a customer order.] [ux:input name=customer kind=string required='true' help='Who the order is for.'] [ux:input name=qty kind=int required='true'] [ux:input name=note kind=string required='false'] [ux:submit label='Place order']] ``` An act verb of a feature grammar projects the same way, which is the seam between the XAP layer and the UX layer: `prog.cx` ```cx [?lib 'cx-platform/ux' as=ux] [?let [= $f [feature name=commerce [types [type 'did::text'] [type 'money::decimal' [min 0]]] [nouns [noun name=order [field name=id type=text] [field name=package type=text] [field name=buyer type=did doc="who is buying"]] [noun name=price [field name=amount type=money]]] [verbs [verb name=place-order effect=act [summary "Place an order"] [writes order]] [verb name=quote effect=observe [reads price]]]]] [$ux:form $f "place-order" {affects: "/live/new"}]] ``` ```console $ cx prog.cx [ux:form verb=place-order affects='/live/new' [ux:heading level=2 Place an order] [ux:input name=id label=Id kind=text value=''] [ux:input name=package label=Package kind=text value=''] [ux:input name=buyer label=Buyer kind=did value='' help='who is buying'] [ux:submit label='Place order']] ``` `prog.cx` ```cx [?lib 'cx-platform/ux' as=ux] [?let [= $f [feature name=commerce [types [type 'did::text'] [type 'money::decimal' [min 0]]] [nouns [noun name=order [field name=id type=text] [field name=package type=text] [field name=buyer type=did doc="who is buying"]] [noun name=price [field name=amount type=money]]] [verbs [verb name=place-order effect=act [summary "Place an order"] [writes order]] [verb name=quote effect=observe [reads price]]]]] [; R-A1 migration: envelope → spliced children (2026-08-25) ] [$ux:form $f "cancel-order" {}]] ``` ```console $ cx prog.cx [err code=ux-refused [ux-refusal code=ux-no-such-verb verb=cancel-order]] ``` The point is that **there is no materialized UI manifest to drift**. The form is derived at render time from the definition, so a parameter that changed cannot leave a stale control behind. Richer shapes come from the same derivation — a reference-typed field projects a chooser, a repeating field projects a group, and a refusal keeps what the user typed: `prog.cx` ```cx [?lib 'cx-platform/ux' as=ux] [?let [= $feature [$cx:parse "[feature name=shop [nouns [noun name=product [field name=sku type=text] [field name=name type=text]] [noun name=line [field name=sku type=product label='Product' doc='chosen, never typed'] [field name=qty type=int label='Qty']] [noun name=order [field name=customer type=text label='Customer'] [field name=lines type=line repeats=true add-label='Add a line']]] [verbs [verb name=place-order [summary 'Place an order.'] [intent [do :place-order [customer] [lines]]] [writes order]]]]"]] [= $opts {options: ([options for=product [option value=ESC-18 label="Escapement"] [option value=BAL-9 label="Balance wheel"]]), affects: "/f/entry", group-endpoint: true, group-affects: "/f/entry/place-order/lines"}] [= $f [$ux:form $feature "place-order" $opts]] [$cx:canonical [$first $f//ux:select]]] ``` ```console $ cx prog.cx "[ux:select name=lines.0.sku label=Product required='true' value=''\n [ux:option value=ESC-18 Escapement]\n [ux:option value=BAL-9 Balance wheel]\n]\n" ``` `prog.cx` ```cx [?lib 'cx-platform/ux' as=ux] [?let [= $feature [$cx:parse "[feature name=shop [nouns [noun name=product [field name=sku type=text] [field name=name type=text]] [noun name=line [field name=sku type=product label='Product' doc='chosen, never typed'] [field name=qty type=int label='Qty']] [noun name=order [field name=customer type=text label='Customer'] [field name=lines type=line repeats=true add-label='Add a line']]] [verbs [verb name=place-order [summary 'Place an order.'] [intent [do :place-order [customer] [lines]]] [writes order]]]]"]] [= $opts {options: ([options for=product [option value=ESC-18 label="Escapement"] [option value=BAL-9 label="Balance wheel"]]), affects: "/f/entry", group-endpoint: true, group-affects: "/f/entry/place-order/lines"}] [= $f [$ux:form $feature "place-order" $opts]] [$cx:canonical [$first $f//ux:group]]] ``` ```console $ cx prog.cx "[ux:group name=lines repeat='true' affects=/f/entry/place-order/lines add-label='Add a line'\n [ux:card\n [ux:select name=lines.0.sku label=Product required='true' value=''\n [ux:option value=ESC-18 Escapement]\n [ux:option value=BAL-9 Balance wheel]\n ]\n [ux:input name=lines.0.qty label=Qty kind=int value='']\n ]\n]\n" ``` `prog.cx` ```cx [?lib 'cx-platform/ux' as=ux] [?let [= $feature [$cx:parse "[feature name=shop [nouns [noun name=product [field name=sku type=text] [field name=name type=text]] [noun name=line [field name=sku type=product label='Product' doc='chosen, never typed'] [field name=qty type=int label='Qty']] [noun name=order [field name=customer type=text label='Customer'] [field name=lines type=line repeats=true add-label='Add a line']]] [verbs [verb name=place-order [summary 'Place an order.'] [intent [do :place-order [customer] [lines]]] [writes order]]]]"]] [= $opts {options: ([options for=product [option value=ESC-18 label="Escapement"] [option value=BAL-9 label="Balance wheel"]]), affects: "/f/entry", group-endpoint: true, group-affects: "/f/entry/place-order/lines"}] [= $vals ([val name=customer text=""], [val name="lines.0.sku" text="BAL-9"], [val name="lines.0.qty" text="2"])] [= $errs ([field-error name=customer message="who is this order for?"])] [= $f [$ux:form $feature "place-order" [$cx:merge $opts {values: $vals, errors: $errs}]]] [$cx:canonical [$first $f//ux:input]]] ``` ```console $ cx prog.cx "[ux:input name=customer label=Customer kind=text value='' error='who is this order for?']\n" ``` ### Identity is content-addressed A fragment's identity is exactly the triple `(route, feature, element path)` — theme, locale, actor, session and data are *content*, never identity: `prog.cx` ```cx [?lib 'cx-platform/ux' as=ux] [?let [= $f [$ux:frag "/orders/o-1041" "orders" "/open-orders/status"]] [list [$ux:frag-address $f] [$ux:frag-id $f]]] ``` ```console $ cx prog.cx [list 'sha2-256:e1029934ea3a0804f9d5aab65a3933242fb692cbf15eeac4b7bb83c6899025d6' 'cx-e1029934ea3a0804'] ``` `prog.cx` ```cx [?lib 'cx-platform/ux' as=ux] [?let [= $a [$ux:frag "/orders" "orders" "/open"]] [= $b [$ux:frag "/orders" "orders" "/open"]] [= $c [$ux:frag "/orders" "orders-b" "/open"]] [list [= [$ux:frag-id $a] [$ux:frag-id $b]] [= [$ux:frag-id $a] [$ux:frag-id $c]]]] ``` ```console $ cx prog.cx [list true false] ``` The DOM id is a *derived rendering* of that address, not a second identity, and a reused address is a refusal rather than a silent disambiguation: `prog.cx` ```cx [?lib 'cx-platform/ux' as=ux] [?let [= $t [ux:region feature=orders path="/live" [ux:card id=open [ux:heading level=2 "Open"]] [ux:form verb=place-order [ux:submit label="Go"]]]] [?for [in $p [$ux:address-pairs $t {route: "/orders", feature: "orders", path: "/"}]] [yield [$string $p@id]]]] ``` ```console $ cx prog.cx cx-b9aa0e4902fe6a43 cx-70c02252bf05598f cx-25ff65bbcc985af2 ``` `prog.cx` ```cx [?lib 'cx-platform/ux' as=ux] [?let [= $ctx {route: "/o", feature: "f", path: "/"}] [= $dup [ux:region feature=f path="/r" [ux:card id=same [ux:text "a"]] [ux:card id=same [ux:text "b"]]]] [= $ok [ux:region feature=f path="/r" [ux:card id=one [ux:text "a"]] [ux:card id=two [ux:text "b"]]]] [; R-A1 migration: envelope → spliced children (2026-08-25) ] [list [?splice [$ux:address-collisions $dup $ctx]] [?splice [$ux:address-collisions $ok $ctx]]]] ``` ```console $ cx prog.cx [list [ux-refusal code=ux-address-reused address=same id=cx-bfb773fba6d60c77]] ``` Rows key by their **declared key** or get no address at all. Positional row identity is never minted: `prog.cx` ```cx [?lib 'cx-platform/ux' as=ux] [?let [= $rows ([order id=o-1 customer="Ada"], [order id=o-2 customer="Lin"])] [$ux:table $rows {id: "open"}]] ``` ```console $ cx prog.cx [ux:table id=open [ux:columns [ux:column Customer] [ux:column Id]] [ux:rows id=rows [ux:row [ux:cell Ada] [ux:cell o-1]] [ux:row [ux:cell Lin] [ux:cell o-2]]]] ``` ### The web renderer, and what it is forbidden to author `cx-x/ux-web` lowers the semantic tree to HTML plus a **pinned htmx subset**. It is the sole author of those attributes, and the list is closed: `prog.cx` ```cx [?lib 'cx-platform/ux' as=ux] [?lib 'cx-platform/ux-web' as=web] [; R-A1 migration: envelope → spliced children (2026-08-25) ] [list [?splice [$web:htmx-subset]] [$web:htmx-allowed "hx-post"] [$web:htmx-allowed "hx-on"] [$web:htmx-allowed "hx-boost"]] ``` ```console $ cx prog.cx [list 'hx-get' 'hx-post' 'hx-target' 'hx-swap' 'hx-swap-oob' 'hx-trigger' 'hx-include' 'hx-indicator' 'hx-ext' 'sse-connect' 'sse-swap' true false false] ``` `hx-on` is excluded **permanently** — it is what would force `unsafe-inline` into `script-src`. Enforcement is two-sided: a name gate at authorship, and a re-scan of the *serialized bytes* so a path that bypassed the constructor still fails: `prog.cx` ```cx [?lib 'cx-platform/ux' as=ux] [?lib 'cx-platform/ux-web' as=web] [; R-A1 migration: envelope → spliced children (2026-08-25) ] [list [?splice [$web:off-subset-attrs "
"]] [?splice [$web:off-subset-attrs "
"]]] ``` ```console $ cx prog.cx [list [ux-refusal code=ux-attr-off-subset attr=hx-on] [ux-refusal code=ux-attr-off-subset attr=hx-boost]] ``` Escaping is **structural**. Fragments compose as trees and serialize once, so there is no interpolation surface to inject into: `prog.cx` ```cx [?lib 'cx-platform/ux' as=ux] [?lib 'cx-platform/ux-web' as=web] [$web:render [ux:card [ux:text "R & D \"quoted\""] [ux:field label="A & B" ""]] {route: "/o", feature: "f", path: "/"}] ``` ```console $ cx prog.cx '

R & D <team> "quoted"

A & B<script>alert(1)</script>
' ``` `prog.cx` ```cx [?lib 'cx-platform/ux' as=ux] [?lib 'cx-platform/ux-web' as=web] [; R-A1 migration: envelope → spliced children (2026-08-25) ] [list [?splice [$web:csp-violations "
"]] [?splice [$web:csp-violations ""]] [?splice [$web:csp-violations "
"]] [?splice [$web:csp-violations ""]]] ``` ```console $ cx prog.cx [list [ux-refusal code=ux-inline-style] [ux-refusal code=ux-inline-script] [ux-refusal code=ux-inline-script] [ux-refusal code=ux-event-handler]] ``` Here is a complete rendered fragment — a region containing a card, a heading, and an action. Read the `hx-target`: it is **derived** from the semantic `affects=` path through the same fragment id that minted the target's own id, so it cannot drift from what the emitter actually produced. Nothing in the semantic tree named a selector: `prog.cx` ```cx [?lib 'cx-platform/ux' as=ux] [?lib 'cx-platform/ux-web' as=web] [?let [= $ctx {route: "/orders", feature: "orders", path: "/", bind: [ux:render-ctx [swap default=outerHTML]]}] [$web:render [ux:region feature=orders path="/open-orders" [ux:card id=summary [ux:heading level=2 "Open orders"] [ux:action verb=place-order label="Place order" affects="/open-orders"]]] $ctx]] ``` ```console $ cx prog.cx '

Open orders

' ``` `prog.cx` ```cx [?lib 'cx-platform/ux' as=ux] [?lib 'cx-platform/ux-web' as=web] [$web:render [ux:table id=grid [ux:rows [ux:row key=r1 [ux:cell "a"]] [ux:row [ux:cell "b"]]]] {route: "/o", feature: "orders", path: "/"}] ``` ```console $ cx prog.cx '
a
b
' ``` Liveness comes from the render context's binding for a logical feed name, never from the tree: `prog.cx` ```cx [?lib 'cx-platform/ux' as=ux] [?lib 'cx-platform/ux-web' as=web] [?let [= $rctx [ux:render-ctx [feed name=orders endpoint="/stream" event="orders-changed"] [fragment affects="/live/open/rows" endpoint="/orders/rows"] [swap default=outerHTML]]] [= $ctx {route: "/orders", feature: "orders", path: "/", bind: $rctx}] [= $r [$web:render [$ux:feed "orders" "/open" "orders" [ux:text "live"]] $ctx]] [list $r [$web:off-subset-attrs $r] [$web:csp-violations $r]]] ``` ```console $ cx prog.cx [list '

live

'] ``` `prog.cx` ```cx [?lib 'cx-platform/ux' as=ux] [?lib 'cx-platform/ux-web' as=web] [?let [= $rctx [ux:render-ctx [feed name=layout endpoint="/stream" event="layout-changed" region-refetch="/home/body"] [feed name=basket endpoint="/stream" event="basket-changed"] [swap default=outerHTML]]] [= $c0 {route: "/", feature: "home", path: "/", bind: $rctx}] [= $listener [$web:render [ux:region feature=home path="/home" live=layout [ux:text "x"]] $c0]] [= $owner [$web:render [ux:region feature=shell path="/shell" live=basket [ux:text "x"]] $c0]] [list $listener [$contains [$string $owner] "sse-connect"] [$contains [$string $listener] "sse-connect"]]] ``` ```console $ cx prog.cx [list '

x

' true false] ``` ### Refusals are part of the surface A projection failure never reaches a browser as content: `prog.cx` ```cx [?lib 'cx-platform/ux' as=ux] [?lib 'cx-platform/ux-web' as=web] [; R-A1 migration: envelope → spliced children (2026-08-25) ] ([$web:render [ux:action verb=v target="#x"] {route: "/o", feature: "f", path: "/"}], [$web:render [ux:region feature=f path="/r" [ux:card id=d] [ux:card id=d]] {route: "/o", feature: "f", path: "/"}]) ``` ```console $ cx prog.cx ([err code=ux-refused [ux-refusal code=ux-renderer-private-attr element=ux:action attr=target]], [err code=ux-refused [ux-refusal code=ux-address-reused address=d id=cx-fa5c41a015045bf8]]) ``` A field-level refusal lands **on its own control**, with the message beside it and both reaching a screen reader: `prog.cx` ```cx [?lib 'cx-platform/ux' as=ux] [?lib 'cx-platform/ux-web' as=web] [?let [= $t [ux:form verb=place-order [ux:input name=customer label="Customer" error="who is this order for?"] [ux:submit label="Place order"]]] [$web:render $t {route: "/o", feature: "f", path: "/"}]] ``` ```console $ cx prog.cx '
who is this order for?
' ``` And removing a line is an empty response with a renderer-minted control — no script involved anywhere: `prog.cx` ```cx [?lib 'cx-platform/ux' as=ux] [?lib 'cx-platform/ux-web' as=web] [?let [= $rctx [ux:render-ctx [feed name=orders endpoint="/stream" event="orders-changed"] [fragment affects="/live/open/rows" endpoint="/orders/rows"] [swap default=outerHTML]]] [; R-A1 migration: envelope → spliced children (2026-08-25) ] [= $t [ux:group name=lines label="Lines" affects="/f/entry/place-order/lines" add-label="Add a line" [ux:card [ux:input name="lines.0.qty" label="Qty"]]]] [$web:render $t {route: "/o", feature: "f", path: "/entry/place-order", fragment: true, bind: $rctx}]] ``` ```console $ cx prog.cx [err code=ux-refused [ux-refusal code=ux-dangling-affects element=ux:group affects='/f/entry/place-order/lines']] ``` ### One tree, two serializations This is the property that makes an agent a first-class client rather than a scraper. The handler builds **one** semantic tree and the request chooses the serialization: ```cx [body [?if [$wants-cx $request] [then [$cx:canonical [$view $rctx2 $c0 $body]]] [else [$web:page-html "ORIEL" $assets $body $c0]]]] ``` `spec/03-approved/xap/demos/oriel/serve.cx:4000-4002`. `$view` wraps the tree in the `[ux:view [route …] [feature …] [path …] $rctx [ux:tree …]]` envelope (`serve.cx:52-54`) — that five-child element is the agent-parity wire format. Equivalence between the two faces is not asserted, it is **computed**. The content normal form strips presentation, and the renderers are checked against it: `prog.cx` ```cx [?lib 'cx-platform/ux' as=ux] [?let [= $t [ux:card [ux:heading level=2 "Open orders"] [ux:field label="Customer" "Ada"] [ux:field label="Status" [ux:badge tone=warn "late"]] [ux:input name=qty kind=int required=true] [ux:action verb=place-order label="Place order"]]] [$ux:content-repr $t]] ``` ```console $ cx prog.cx "[c:card\n [c:heading level='2' Open orders]\n [c:field label=Customer Ada]\n [c:field label=Status\n [c:badge tone=warn late]\n ]\n [c:input name=qty label=Qty required='true' error='' '']\n [c:action verb=place-order label='Place order']\n]\n" ``` `prog.cx` ```cx [?lib 'cx-platform/ux' as=ux] [?lib 'cx-platform/ux-web' as=web] [?let [= $t [ux:card [ux:heading level=2 "Open"] [ux:field label="Customer" "Ada"] [ux:badge tone=warn "late"] [ux:action verb=place-order label="Go"]]] [= $ctx {route: "/o", feature: "f", path: "/"}] [list [= [$ux:content-repr $t] [$web:content-repr-html $t $ctx]] [$web:content-repr-html $t $ctx]]] ``` ```console $ cx prog.cx [list true "[c:card\n [c:heading level='2' Open]\n [c:field label=Customer Ada]\n [c:badge tone=warn late]\n [c:action verb=place-order label=Go]\n]\n"] ``` ### Act from inside a collection The single most common enterprise-UI mistake CX's vocabulary is designed to prevent: an `[ux:action]` may sit inside an `[ux:item]` or `[ux:row]` and carry its parameters from that item. N items produce N addressable actions. Parameters are **server-derived at projection time and never read back out of the rendered document**. An item whose price changed between render and click posts the price it was rendered with, and the command layer refuses it — which is the correct outcome, and the one a DOM read cannot produce. A *missing* parameter is a projection refusal, because the alternative produces a command refusal that blames the user for the projection's bug. `spec/03-approved/xap/ux.md:1023-1042`. ## 8. Test it: the drive-step pattern Parity between faces is a ruled requirement, and it has broken more than once — so it is checked by a program rather than asserted in a note. `spec/03-approved/xap/demos/oriel/drive.cx` is the reference harness. Four pieces: 1. **Wire readers.** `get-view` requests `Accept: application/cx` and parses the answer (`drive.cx:34-38`); `get-html` fetches the other face (`:40-42`); `vtree` / `vctx` unpack the `[ux:view]` envelope (`:54-64`). 2. **A driver that uses the shipped wire.** `post-raw` posts to `/intent/` with `follow-redirects: false` (`:70-72`), and `post-headers` toggles `HX-Request: true` for the fragment responses (`:74-83`). Nothing supplies a scope — the server derives it from the cookie it set. 3. **Steps are values.** `[?def check]` returns `[step name= ok="PASS"|"FAIL" detail=]` (`:132-133`), so the harness output is a document you can query rather than a log you must read. 4. **Assertions that catch both directions.** `equivalence` (`:147-162`) takes the content-normal-form of a **live route** three ways — semantic, web, terminal — "because it catches a renderer that drops content **and** one that invents it". `paints` (`:167-182`) renders through the terminal face and measures the focus ring, because a face with no pointer cannot hide an unreachable control, so the ring's size *is* the keyboard-reachability measurement. It is headless on purpose: "a proof that needs a human at a keyboard is not a proof anyone can re-run" (`drive.cx:9-10`). **One trap the header calls out** (`drive.cx:12-15`): a bare `--allow-net` denies a loopback literal, and every step then silently reports `offline`. Scope the grant. ## 9. What the rulings forbid The prohibitions are as teachable as the requirements, because each one names a failure mode that looks like success. **No dual-accept.** When a surface changes, the old spelling is *removed*, not kept alongside the new one. Retired UX members refuse by name with the replacement stated; retired CLI verbs name their own retirement. There is no compatibility alias anywhere, deliberately — two spellings for one meaning is two things to keep true. **No silent partials.** A gate over the empty set refuses rather than reporting a pass (`xap-compose-051`). A `[sources]` block with no rows refuses rather than binding nothing. A composition reports *every* W-violation rather than the first (`xap-compose-014`). The pattern: a check that cannot do its work says so. **No error-as-silent-data.** An externalizing effect refuses to carry an `[err]` out of the program unless you wrote `errs=:permit`. This closes the `written=0 errors=25`, exit 0 class and the render-`[err]`-into-HTML class at the boundary, which is the only honest place. See `primer.md` §6. `prog.cx` ```cx [?lib 'cx-platform/store'] [?let [= $s [$store:open "mem://"]] [= $rows ([row [v 2.5]], [err code=cx-err:CXER0100 message="a refusal at rest"], [row [v 4.5]])] [$store:put-doc $s [report [count [$count $rows]] [?splice $rows]]]] ``` ```console $ cx prog.cx [err code=cx-err:CXER0275 message='E_ERR_AT_BOUNDARY: refusing the store document write — the document contains cx-err:CXER0100 at /report/err; a refusal must not leave the program as silent data (pass errs=:permit on the effect to externalize it deliberately)' err-path='/report/err' err-code=cx-err:CXER0100] ``` **No guessing at ambiguity.** ρ returns a value listing the candidates. A composite never resolves to "probably this one". **No auto-splicing.** A sequence value in element-content position refuses and names `[?splice]`. Nothing silently rewrites what you wrote. **No laundering authority through a wrapper.** A grant on a composite verb's own name conveys nothing; the transitive leaf set is what the PEP evaluates. **No selectors in a semantic tree.** Every htmx spelling is derived from a semantic declaration plus render context. A hand-written selector could drift from what the emitter minted — and once did. **No interpolation.** Fragments compose as trees and serialize once. If you find yourself concatenating markup, you have left the supported path. **No bearer material in a journal.** Write a derived, non-invertible key. **No enforcement in the browser.** The PEP rechecks at the wire; a refusal is plain and does not disclose capability structure. Hiding a control is a courtesy, never a control. **No inventing a module, a directive, or a block.** If it is not in the directive registry (`primer.md` §4), the module catalog (`reference-stdlib.md`), or the `[xap]` child set (§5 above), it does not exist. This is the most expensive mistake to make with a young language, because the result is plausible and wrong. ## 10. Before you ship 1. Every feature document has `[nouns]`, `[verbs]`, **and** `[requirements]`. 2. `[$xap:compose-report]` says `ok=true` over the real feature set — and the set is non-empty. 3. Every derived noun declares `[from …]`, every reference in it resolves, and exactly one declared deriver produces it. 4. No grammar verb `[writes]` a derived noun. 5. The deployment document's children are all in the ruled set. You did not invent `[transport]`. 6. `[runtime]` names each option key verbatim; `[sources]` has at least one row; `[log-reduce] fn=` names a def of a pinned feature. 7. Deriver rows and an OPTS `derivers:` key are not both present — the document wins, and the drop is announced. 8. `[host-auth]` is present for anything not on a localhost dev floor, with `[identity]` fail-closed and a `[principal]` row for every DID that needs a dial. 9. Principals were minted offline and their grants spliced into config by an operator. 10. Every act verb is offered by some route; every `affects=` resolves. 11. The surface tree is semantic — no selector, no htmx attribute, no inline style, no concatenated markup. 12. Both faces agree under the content normal form, and you ran the check. 13. Every externalizing effect either carries no `[err]` or says `errs=:permit`. 14. You ran it. `cx ` is a second, and a plausible-looking XAP that does not compose costs a reviewer much more than that. ## Where the evidence lives | Topic | Normative source | |---|---| | Composition algebra, W-gates, derived nouns, derivers | `spec/03-approved/xap/xap_grammar_composition.md` | | Distribution, the deployment host, `[runtime]` bindings | `spec/03-approved/xap/xap_feature_distribution_market.md` | | `[xap]` document schema | `spec/03-approved/xap/xap_schemas/xap.cxs` | | Host channel auth, principals, attach | `spec/03-approved/xap/xap_identity_model.md` | | The UX projection and the emitter contract | `spec/03-approved/xap/ux.md` | | The paradigm and its frozen invariants | `spec/03-approved/xap/xap.md` | | The effect boundary | `spec/03-approved/core/commands_effects.md` | | Composition + authority + distribution, worked | `reference/shop/` | | The projection, worked | `spec/03-approved/xap/demos/oriel/` | Every example above is a case in `conformance/`; the release gate fails if any of them stops holding. # The customization model: what one tenant may change — v0.18.0-pre.1 > **GENERATED.** Source: `docs-src/llm/model-customization.md.tmpl` + the > conformance corpus. Every code block is a fixture and every output was > re-recorded from the `cx` v0.18.0-pre.1 binary at generation time. Where a > claim rests on a file in the repository rather than on a fixture, the file > and line are cited and **the citation is the evidence**. Where a mechanism > does not exist yet, this document says so in the same voice — §6 is a list > of gaps, not a list of features. Read `primer.md` first for the language and `playbook-xap.md` for how a deployment is assembled. This document answers a different question, and it is the one an adopter asks first: > **What may be changed for one tenant, what must change for everyone, and > why?** ## 0. The answer in one sentence **A tenant customizes its surface and its contract; it never customizes behaviour — so one release reaches every tenant, and the toolchain can say in advance which tenants it breaks.** Every refusal you will meet is that sentence being enforced. This matters because the refusals are the product. Met one at a time — `CXER4877`, `CXER4878`, `CXER4879`, `:unservable-verb` — they read as friction, and the natural response is to route around them. Read as one model, they are the reason a fleet upgrade is a replay instead of a merge. The comparison an adopter will reach for is the metadata-driven platform that held this same line **by policy** and eventually opened a side door under customer pressure. CX holds it in the toolchain, which is stronger — but only if you know it is the line before your first client asks for a custom verb. Two planes, and they behave differently. Keep them apart: | | **Surface plane** | **Grammar plane** | |---|---|---| | What a tenant edits | arrangement, presentation, look and feel | a **contract**: renames, additions, narrowings, withdrawals | | The unit | journaled commands over a base document | an `[instance …]` binding document | | How a vendor change arrives | adopt a new base, **replay** the tenant's commands | move the `of=` pin, **re-instantiate** | | Granularity of failure | per command: clean / drifted / refused | **all-or-nothing** per instance | | Authority | `ux.md` §18–§19 | `xap_grammar_composition.md` §4.3 | ## 1. What you build once, and what a tenant may change ### 1.1 The surface plane — exactly four planes, each a document `ux.md:1861` is normative and worth quoting exactly: > **[P0-114] Everything a studio edits is one of exactly four planes, and > each plane is a document — never code, never markup.** | Plane | The document | The commands a tenant issues | |---|---|---| | **Arrangement** | a `[ux:layout]` base document + its journaled command stream | `ux:move` · `ux:wrap` · `ux:place` · `ux:remove` | | **Presentation & content** | the same layout document (hint and param children) | `ux:set-hint` · `ux:set-param` | | **Look and feel** | the theme token document | the theme write (`ux.md:521`, `[P0-112]`) | | **Automations** | the tenant's automation stream — a skeleton, its answers and the filled flow document by address | `ux:automate` · `ux:retire-automation` (`ux.md` §18.8, `[P0-130]`) | What is deliberately **not** a plane: *"markup, CSS, and component internals"* (`ux.md:1870`). And nothing is stored flattened — `ux.md:1878` — *"The rendered arrangement is `fold(base, commands)` — recomputed, never a saved copy."* That is not an implementation detail; it is the entire reason an upgrade can exist. A forked document cannot be replayed onto a new base. A command stream can. A move touches what it addresses and nothing else, and a command addressing an element that is not there refuses rather than guessing: `prog.cx` ```cx [?lib 'cx-platform/ux' as=ux] [?let [= $comps ([component name=hero], [component name=departments], [component name=promo], [component name=group container=true])] [= $l0 [ux:layout [placed id=hero component=hero] [placed id=promo component=promo] [placed id=depts component=departments]]] [= $l1 [$ux:layout-apply $l0 [ux:move id=depts parent="" before=hero] $comps]] [list [$cx:canonical $l1] [$ux:layout-hint $l1 promo tone]]] ``` ```console $ cx prog.cx [list '[ux:layout\n [placed id=depts component=departments]\n [placed id=hero component=hero]\n [placed id=promo component=promo]\n]\n' ''] ``` `prog.cx` ```cx [?lib 'cx-platform/ux' as=ux] [?let [; R-A1 migration: envelope → spliced children (2026-08-25) ] [= $comps ([component name=hero])] [= $l0 [ux:layout [placed id=hero component=hero]]] [= $r [$ux:layout-apply $l0 [ux:move id=ghost parent="" before=""] $comps]] [$cx:canonical ($r)]] ``` ```console $ cx prog.cx '([err code=ux-refused\n [ux-refusal code=ux-layout-address-miss id=ghost]\n]\n)\n' ``` Every command has an inverse, and the round trip is byte-identical — which is what makes an edit stream auditable and reversible rather than a diff: `prog.cx` ```cx [?lib 'cx-platform/ux' as=ux] [?let [= $comps ([component name=hero], [component name=featured], [component name=promo], [component name=group container=true])] [= $l0 [ux:layout [placed id=hero component=hero] [placed id=featured component=featured]]] [= $rt [?fn ($cmd) [?let [= $inv [$ux:layout-inverse $l0 $cmd]] [= [$cx:canonical [$ux:layout-apply-batch [$ux:layout-apply $l0 $cmd $comps] $inv $comps]] [$cx:canonical $l0]]]]] [list [$rt [ux:move id=featured parent="" before=hero]] [$rt [ux:set-hint id=hero hint=tone value=info]] [$rt [ux:place id=promo component=promo parent="" before=""]] [$rt [ux:wrap id=hero wrapper=g1 component=group]] [$rt [ux:remove id=hero]]]] ``` ```console $ cx prog.cx [list true true true true true] ``` A batch is atomic, so a tenant never lands half an edit: `prog.cx` ```cx [?lib 'cx-platform/ux' as=ux] [?let [= $comps ([component name=hero])] [= $l0 [ux:layout [placed id=hero component=hero]]] [; R-A1 migration: envelope → spliced children (2026-08-25) ] [= $r [$ux:layout-apply-batch $l0 ([ux:set-hint id=hero hint=tone value=info], [ux:remove id=ghost]) $comps]] [$cx:canonical ($r)]] ``` ```console $ cx prog.cx '([err code=ux-refused\n [ux-refusal code=ux-layout-address-miss id=ghost]\n]\n)\n' ``` Content and presentation are the same closed discipline — an undeclared param name refuses: `prog.cx` ```cx [?lib 'cx-platform/ux' as=ux] [?let [; R-A1 migration: envelope → spliced children (2026-08-25) ] [= $comps ([component name=hero [param name=heading default="Everyday things." carrier=ux-heading]], [component name=promo [param name=text default="A note." carrier=ux-text]])] [= $l0 [ux:layout [placed id=hero component=hero] [placed id=promo component=promo]]] [= $c1 [ux:set-param id=hero param=heading value="Made properly."]] [= $l1 [$ux:layout-apply $l0 $c1 $comps]] [= $r1 [$ux:layout-apply $l0 [ux:set-param id=hero param=nonesuch value=x] $comps]] [= $inv [$ux:layout-inverse $l0 $c1]] [= $l2 [$ux:layout-apply-batch $l1 $inv $comps]] [list [$cx:canonical $l1] [$ux:layout-param $l1 hero heading] [$cx:canonical ($r1)] [= [$cx:canonical $l2] [$cx:canonical $l0]] [$cx:canonical [x [?splice [$ux:layout-inverse $l1 [ux:remove id=hero]]]]]]] ``` ```console $ cx prog.cx [list "[ux:layout\n [placed id=hero component=hero\n [param name=heading value='Made properly.']\n ]\n [placed id=promo component=promo]\n]\n" 'Made properly.' '([err code=ux-refused\n [ux-refusal code=ux-layout-unknown-param id=hero param=nonesuch]\n]\n)\n' true "[x\n [ux:place id=hero component=hero parent='' before=promo]\n [ux:set-param id=hero param=heading value='Made properly.']\n]\n"] ``` And look-and-feel is a token document compiled to a stylesheet, where an unsafe value refuses **the whole sheet** rather than emitting a partial one: `prog.cx` ```cx [?lib 'cx-platform/ux' as=ux] [?lib 'cx-platform/ux-web' as=web] [?let [; R-A1 migration: envelope → spliced children (2026-08-25) ] [= $ok [ux:theme [token name=bg value="#111"] [token name=fg value="white"]]] [= $bad [ux:theme [token name=bg value="red;} body{display:none"]]] ([$web:token-value-safe "#111"], [$web:token-value-safe "red;}"], [$web:theme-refusals $bad], [$web:theme-css $bad])] ``` ```console $ cx prog.cx (true, false, [ux-refusal code=ux-bad-token-value token=bg], [err code=ux-refused [ux-refusal code=ux-bad-token-value token=bg]]) ``` The closed command set is doing load-bearing work here. `ux.md:2154` states why: *"A closed set is analyzable, which is what makes [P0-120]'s fleet preflight possible at all… Free CSS is not analyzable, which is why theme-fork platforms make upgrades the client's problem."* ### 1.2 The grammar plane — four refinements, and removal cannot be spelled A tenant's variant of someone else's feature is an `[instance …]` binding document. `xap_grammar_composition.md:330` calls it *"The refinement contract — four verbs admitted, everything else structurally impossible"*: | Refinement | What it may do | |---|---| | `[rename]` | `label` / `doc` / `summary` of a named noun, field or verb. **Names of record are not renameable** — the vocabulary has no slot for it | | `[add]` | new fields on existing nouns, new verbs, new rules. Adding is always a narrowing in this rule model; an added name **must not collide** with an inherited one | | `[tighten]` | move **up** the rank lattice only — scope may narrow, consequence may rise, effect may strengthen | | `[select]` | the subsetting vocabulary (`offer` / `not-offered`-with-`why`). Selection is a narrowing and **binds every consumption point** | The schema is the same list, and it is deliberately the whole vocabulary (`spec/03-approved/xap/xap_schemas/instance.cxs:18-26`). Note what is absent: *"Removal is not refused because it cannot be spelled: the binding vocabulary has no remove"* (`xap_grammar_composition.md:359`). All four at once, with the pin computed in-case — which is also the demonstration that `of=` is derivable rather than magic: `prog.cx` ```cx [?lib 'cx-platform/xap' as=xap] [?lib 'cx-stdlib/hash' as=hash] [?lib 'cx-stdlib/bytes' as=bytes] [?let [= $arch [feature name=stamp version="1" [nouns [noun name=mark [field name=id type=text] [field name=note type=text label='Note']]] [verbs [verb name=press effect=act scope=shared consequence=reversible [intent [do :press [id]]] [writes mark]] [verb name=see effect=observe [intent [do :see]] [reads mark]]] [rules [rule name=see-after-press kind=ordering verb='stamp/see' after='stamp/press' [statement 'You can only see a mark that was pressed.']]] [requirements [requirement kind=functional as=user traces=press [want 'to press'] [so 'pressed']]]]] [= $pin [$concat "sha2-256:" [$bytes:to-hex [$hash:sha256-string [$cx:canonical $arch]]]]] [= $eff [$xap:instantiate $arch [instance name=wax-seal of=$pin [rename field='mark/note' label='Inscription'] [add on=mark [field name=colour type=text]] [tighten verb=press consequence=irreversible] [select [offer verb=press] [not-offered verb=see why='Seals are inspected by the registrar.']]]]] [= $r [$xap:compose-report $eff]] [probe [name [$string $eff@name]] [cq [?for [in $v $eff//verb] [where [= [$string $v@name] "press"]] [yield [$string $v@consequence]]]] [rule [?for [in $ru $eff//rule] [yield [$string $ru@verb]]]] [fields [$count $eff//field]] [sel [$count $eff//selection]] [prov [$count $eff//instantiated-from]] [composes [$string $r@ok]]]] ``` ```console $ cx prog.cx [probe [name wax-seal] [cq irreversible] [rule wax-seal/see] [fields 3] [sel 1] [prov 1] [composes 'true']] ``` An instance may also add views onto a noun it inherited: `prog.cx` ```cx [?lib 'cx-platform/xap' as=xap] [?lib 'cx-stdlib/hash' as=hash] [?lib 'cx-stdlib/bytes' as=bytes] [?let [= $arch [feature name=stamp version="1" [nouns [noun name=mark [field name=id type=text] [field name=note type=text]]] [verbs [verb name=see effect=observe [intent [do :see]] [reads mark]]] [requirements [requirement kind=functional as=user traces=see [want 'to see'] [so 'seen']]]]] [= $pin [$concat "sha2-256:" [$bytes:to-hex [$hash:sha256-string [$cx:canonical $arch]]]]] [= $eff [$xap:instantiate $arch [instance name=wax-seal of=$pin [add on=mark [views [sort field=id] [hidden field=note]]]]]] [= $r [$xap:compose-report $eff]] ([$string $eff//views/sort/@field], [$string $eff//views/hidden/@field], [$string $r@ok])] ``` ```console $ cx prog.cx (id, note, 'true') ``` ### 1.3 What is refused, and the one-sentence reason for each | Refusal | Raised when | The reason, in one sentence | |---|---|---| | `CXER4877` `E_XAP_ARCHETYPE_REPURPOSE` | an `[add]` collides with an inherited name, or a refinement names a member the archetype does not declare | a tenant may extend the vocabulary, never redefine a word already in it | | `CXER4878` `E_XAP_ARCHETYPE_LOOSEN` | a `[tighten]` widens any axis | substitutability — an instance must be usable everywhere its archetype is | | `CXER4879` `E_XAP_INSTANCE_INVALID` | `of=` does not match the archetype document presented | an instance derives from an **exact** base, never from "whatever the vendor ships today" | | `CXER4864` `E_XAP_VERB_NOT_OFFERED` | a withdrawn verb is uttered | a withdrawal is a real narrowing, not a hidden verb — and it is deliberately distinct from "no such verb" | Repurposing an inherited name: `prog.cx` ```cx [?lib 'cx-platform/xap' as=xap] [?lib 'cx-stdlib/hash' as=hash] [?lib 'cx-stdlib/bytes' as=bytes] [?let [= $arch [feature name=stamp version="1" [nouns [noun name=mark [field name=id type=text]]] [verbs [verb name=press effect=act [intent [do :press [id]]] [writes mark]]] [requirements [requirement kind=functional as=user traces=press [want 'to press'] [so 'pressed']]]]] [= $pin [$concat "sha2-256:" [$bytes:to-hex [$hash:sha256-string [$cx:canonical $arch]]]]] [$xap:instantiate $arch [instance name=x of=$pin [add [verb name=press effect=observe [intent [do :press]]]]]]] ``` ```console $ cx prog.cx [err code=cx-err:CXER4877 message='E_XAP_ARCHETYPE_REPURPOSE: verb "press" is inherited — an [add] may not repurpose it (§4.3)' at='10:2'] ``` Widening what was inherited. `xap_grammar_composition.md:358` is blunt about the absence of an escape hatch: *"There is no escape hatch; the honest release valve is authoring your own feature."* `prog.cx` ```cx [?lib 'cx-platform/xap' as=xap] [?lib 'cx-stdlib/hash' as=hash] [?lib 'cx-stdlib/bytes' as=bytes] [?let [= $arch [feature name=stamp version="1" [nouns [noun name=mark [field name=id type=text]]] [verbs [verb name=press effect=act scope=shared consequence=irreversible [intent [do :press [id]]] [writes mark]]] [requirements [requirement kind=functional as=user traces=press [want 'to press'] [so 'pressed']]]]] [= $pin [$concat "sha2-256:" [$bytes:to-hex [$hash:sha256-string [$cx:canonical $arch]]]]] [$xap:instantiate $arch [instance name=x of=$pin [tighten verb=press consequence=reversible]]]] ``` ```console $ cx prog.cx [err code=cx-err:CXER4878 message='E_XAP_ARCHETYPE_LOOSEN: verb "press" consequence irreversible → reversible widens (§4.3)' at='10:2'] ``` A refinement naming a field the archetype does not declare — the typo class, caught at instantiation rather than at first render: `prog.cx` ```cx [?lib 'cx-platform/xap' as=xap] [?lib 'cx-stdlib/hash' as=hash] [?lib 'cx-stdlib/bytes' as=bytes] [?let [= $arch [feature name=stamp version="1" [nouns [noun name=mark [field name=id type=text]]] [verbs [verb name=see effect=observe [intent [do :see]] [reads mark]]] [requirements [requirement kind=functional as=user traces=see [want 'to see'] [so 'seen']]]]] [= $pin [$concat "sha2-256:" [$bytes:to-hex [$hash:sha256-string [$cx:canonical $arch]]]]] [$xap:instantiate $arch [instance name=wax-seal of=$pin [add on=mark [views [sort field=colour]]]]]] ``` ```console $ cx prog.cx [err code=cx-err:CXER4877 message='E_XAP_ARCHETYPE_REPURPOSE: [add [views [sort field=colour]]] on "mark" names a field the archetype does not declare (§4.3/§4.5)' at='10:2'] ``` A withdrawn verb refuses at resolution, **before** the policy check, and with its own code: `prog.cx` ```cx [?lib 'cx-platform/xap' as=xap] [?lib 'cx-stdlib/hash' as=hash] [?lib 'cx-stdlib/bytes' as=bytes] [?let [= $arch [feature name=stamp version="1" [nouns [noun name=mark [field name=id type=text]]] [verbs [verb name=press effect=act scope=shared consequence=reversible [intent [do :press [id]]] [writes mark]] [verb name=purge effect=act scope=shared consequence=irreversible [intent [do :purge [id]]] [writes mark]]] [requirements [requirement kind=functional as=user traces=press [want 'to press'] [so 'pressed']]]]] [= $pin [$concat "sha2-256:" [$bytes:to-hex [$hash:sha256-string [$cx:canonical $arch]]]]] [= $eff [$xap:instantiate $arch [instance name=stamp of=$pin [select [not-offered verb=purge why='This deployment does not permit purging.']]]]] [= $g [$xap:compose $eff]] [= $q [$xap:resolve $g "stamp/purge"]] [= $b [$xap:resolve $g "purge"]] [= $ok [$xap:resolve $g "press"]] [probe [qualified [code $q@code] [verb $q@verb] [why $q@why]] [bare [code $b@code] [verb $b@verb]] [offered-verb $ok]]] ``` ```console $ cx prog.cx [probe [qualified [code cx-err:CXER4864] [verb stamp/purge] [why This deployment does not permit purging.]] [bare [code cx-err:CXER4864] [verb stamp/purge]] [offered-verb stamp/press]] ``` A withdrawal removes a *verb*, not a *word* — a bare term still resolves through another feature that offers one: `prog.cx` ```cx [?lib 'cx-platform/xap' as=xap] [?lib 'cx-stdlib/hash' as=hash] [?lib 'cx-stdlib/bytes' as=bytes] [?let [= $arch [feature name=stamp version="1" [nouns [noun name=mark [field name=id type=text]]] [verbs [verb name=press effect=act scope=shared consequence=reversible [intent [do :press [id]]] [writes mark]] [verb name=purge effect=act scope=shared consequence=irreversible [intent [do :purge [id]]] [writes mark]]] [requirements [requirement kind=functional as=user traces=press [want 'to press'] [so 'pressed']]]]] [= $vault [feature name=vault version="1" [nouns [noun name=slot [field name=id type=text]]] [verbs [verb name=purge effect=act scope=shared consequence=irreversible [intent [do :purge [id]]] [writes slot]]] [requirements [requirement kind=functional as=ops traces=purge [want 'to purge'] [so 'purged']]]]] [= $pin [$concat "sha2-256:" [$bytes:to-hex [$hash:sha256-string [$cx:canonical $arch]]]]] [= $eff [$xap:instantiate $arch [instance name=stamp of=$pin [select [not-offered verb=purge why='This deployment does not permit purging.']]]]] [= $g [$xap:compose $eff $vault]] [probe [purge [$xap:resolve $g "purge"]] [bare [?for [in $t $g//term] [yield [$string $t@name]]]]]] ``` ```console $ cx prog.cx [probe [purge vault/purge] [bare 'press' 'purge']] ``` ## 2. Why behaviour is not tenant-customizable State this as the promise, not the limitation. `xap_grammar_composition.md` §4.3 now says it normatively (**RULED: AD-10**, #1162): *instantiation is contract-level by design.* A binding refines a contract — it selects, narrows and renames what the archetype **already implements**. It does not carry code, and the vocabulary has no slot for code: `[rename]`, `[add]`, `[tighten]` and `[select]` are declarations about a grammar, not implementations of one. Behaviour travels with a feature as its `.cx` module, and dispatch is by feature name, so an instance is served by the **archetype's** module — the one its `of=` pin resolves to. The consequence you will actually meet: **an `[add]`ed verb that no implementation can serve is admitted at instantiation and refused at the surface.** `[add]` is one of the four refinements, so the instance may still *declare* the verb. What may not happen is a surface **offering** it, because that is where a declaration becomes a route a caller can reach. `cx xap check-surface` reports it as `:unservable-verb` — *"a route offers a verb no `.cx` module can serve"* (`vcx/cmd/xap_check_surface.v:49`; exit 0 clean, 1 any problem). The rationale is carried in the generated checker program itself, `vcx/cmd/xap_check_surface.v:213-216`: > At the SURFACE, not at instantiate: §4.3 admits ADD of new verbs as one of > its four refinement operations, so an instance may still DECLARE one. What > may not happen is a surface OFFERING it, because that is where the promise > becomes a route a caller can reach. What this buys, stated as the value and not the mechanism: **one code path per feature, one release for every tenant, and conflicts known before shipping.** The ruling record is explicit that this was chosen as a *refusal* rather than as a new capability — `ledger/rulings_2026_09_01_adoption_campaign.md:446`: *"a DECLARATION THAT BINDS NOTHING becomes a refusal, not a new capability."* So when a tenant genuinely needs new behaviour, the answer is §4 below: author your own feature and compose it beside the shared one. That is a supported path with a checked seam — not a workaround. ## 3. How change reaches a fleet — the grammar plane Nothing propagates. A vendor publishing v2 does not move any instance. The vendor ships v2 of a feature; a tenant's binding still pins v1; presenting it the new archetype **refuses**, naming both addresses: `prog.cx` ```cx [?lib 'cx-platform/xap' as=xap] [?lib 'cx-stdlib/hash' as=hash] [?lib 'cx-stdlib/bytes' as=bytes] [?let [= $v1 [feature name=thing version="1" [nouns [noun name=thing [field name=id type=text] [field name=label type=text]]] [verbs [verb name=create effect=act scope=shared consequence=reversible [intent [do :create]] [writes thing]]] [requirements [requirement kind=functional as=operator traces=create [want 'to create a thing'] [so 'it exists in the record']]]]] [= $v2 [feature name=thing version="2" [nouns [noun name=thing [field name=id type=text] [field name=label type=text] [field name=retired-at type=instant]]] [verbs [verb name=create effect=act scope=shared consequence=irreversible [intent [do :create]] [writes thing]]] [requirements [requirement kind=functional as=operator traces=create [want 'to create a thing'] [so 'it exists in the record']]]]] [= $pin1 [$concat "sha2-256:" [$bytes:to-hex [$hash:sha256-string [$cx:canonical $v1]]]]] [$xap:instantiate $v2 [instance name=asset of=$pin1 [rename noun=thing label='Asset']]]] ``` ```console $ cx prog.cx [err code=cx-err:CXER4879 message='E_XAP_INSTANCE_INVALID: of= pins sha2-256:999a55929a571983e0909d5fa312c401857b3319c35f83918b6fd5ad817473bf but the archetype presented is sha2-256:0416a467110492c86d98f8597bfc2d51e0376ce75394fc0af33ad9aef93caf21 — an instance derives from an EXACT base (§4.3); re-bless by moving the pin deliberately' at='20:2'] ``` The upgrade is one deliberate act — *"Re-bless, never silent propagation"* (`xap_grammar_composition.md:372`): the binding's `of=` moves to the new address and the instance re-instantiates, **with the whole contract re-checked against the new base.** The tenant keeps its refinements and inherits the vendor's additions: `prog.cx` ```cx [?lib 'cx-platform/xap' as=xap] [?lib 'cx-stdlib/hash' as=hash] [?lib 'cx-stdlib/bytes' as=bytes] [?let [= $v2 [feature name=thing version="2" [nouns [noun name=thing [field name=id type=text] [field name=label type=text] [field name=retired-at type=instant]]] [verbs [verb name=create effect=act scope=shared consequence=irreversible [intent [do :create]] [writes thing]]] [requirements [requirement kind=functional as=operator traces=create [want 'to create a thing'] [so 'it exists in the record']]]]] [= $pin2 [$concat "sha2-256:" [$bytes:to-hex [$hash:sha256-string [$cx:canonical $v2]]]]] [= $reblessed [$xap:instantiate $v2 [instance name=asset of=$pin2 [rename noun=thing label='Asset'] [add on=thing [field name=serial type=text]] [tighten verb=create consequence=irreversible]]]] [probe [name [$string $reblessed@name]] [fields [$count $reblessed//field]] [cq [?for [in $v $reblessed//verb] [where [= [$string $v@name] "create"]] [yield [$string $v@consequence]]]] [rebased [= [$string $reblessed//instantiated-from@archetype] $pin2]]]] ``` ```console $ cx prog.cx [probe [name asset] [fields 4] [cq irreversible] [rebased true]] ``` And *"the gate holds at every generation"* is not decoration. A refinement that was legal against v1 can become a widening against v2, and re-bless refuses rather than lowering the vendor's new floor for one tenant: `prog.cx` ```cx [?lib 'cx-platform/xap' as=xap] [?lib 'cx-stdlib/hash' as=hash] [?lib 'cx-stdlib/bytes' as=bytes] [?let [= $v2 [feature name=thing version="2" [nouns [noun name=thing [field name=id type=text] [field name=label type=text]]] [verbs [verb name=create effect=act scope=shared consequence=irreversible [intent [do :create]] [writes thing]]] [requirements [requirement kind=functional as=operator traces=create [want 'to create a thing'] [so 'it exists in the record']]]]] [= $pin2 [$concat "sha2-256:" [$bytes:to-hex [$hash:sha256-string [$cx:canonical $v2]]]]] [$xap:instantiate $v2 [instance name=asset of=$pin2 [tighten verb=create consequence=reversible]]]] ``` ```console $ cx prog.cx [err code=cx-err:CXER4878 message='E_XAP_ARCHETYPE_LOOSEN: verb "create" consequence irreversible → reversible widens (§4.3)' at='12:2'] ``` The control proves the refusal is about the *base*, not the binding — the same refinement against v1 is fine: `prog.cx` ```cx [?lib 'cx-platform/xap' as=xap] [?lib 'cx-stdlib/hash' as=hash] [?lib 'cx-stdlib/bytes' as=bytes] [?let [= $v1 [feature name=thing version="1" [nouns [noun name=thing [field name=id type=text] [field name=label type=text]]] [verbs [verb name=create effect=act scope=shared consequence=reversible [intent [do :create]] [writes thing]]] [requirements [requirement kind=functional as=operator traces=create [want 'to create a thing'] [so 'it exists in the record']]]]] [= $pin1 [$concat "sha2-256:" [$bytes:to-hex [$hash:sha256-string [$cx:canonical $v1]]]]] [= $ok [$xap:instantiate $v1 [instance name=asset of=$pin1 [tighten verb=create consequence=reversible]]]] [probe [legal-on-v1 [$string $ok@name]] [cq [?for [in $v $ok//verb] [where [= [$string $v@name] "create"]] [yield [$string $v@consequence]]]]]] ``` ```console $ cx prog.cx [probe [legal-on-v1 asset] [cq reversible]] ``` Two rejected alternatives are on the record (`xap_grammar_composition.md:381`): *"copy-with-provenance (a fork with a birth certificate); mutable `extends=` (silent propagation)."* Both would have made the fleet story easier and the guarantee empty. Underneath, distribution supplies the immutability the pin relies on. `xap_feature_distribution_market.md:480` — *"Released aliases are immutable (re-pointing is a market-rule violation; consumers pin hashes anyway)"*. So a new artifact is a new version, and a pin rollback is hash identity: "go back" is exact rather than approximate. (Both are pinned by `conformance/xap/xap-dist.cxd` cases `xap-dist-014-publish-released-alias-immutable` and `xap-dist-020-pin-rollback-hash-identity`; they are cited by id rather than inlined here because they mint an ed25519 keypair, and this document's code blocks are replayed with no capability grants.) Publishing itself also diffs the new version against the previous release — `:482`, *"Schema lineage rides the publish (RULED: SEA-1)"*: publish compares the two content trees' schema entries and classifies each change, so a **derivable** change derives its lineage claim and its upcaster mechanically while a **reinterpreting** one (an `int` that becomes a `string`) **refuses the publish** with `CXER4890` (`:488`). That is the nearest thing the grammar plane has to a preflight — but note what it is not: it runs **at publish, against schema history**, not per tenant against tenant bindings. See §6.2. ## 4. How change reaches a fleet — the surface plane Here the mechanism is a replay, and `ux.md:1934` gives the reason it can be one: *"improvements and customizations live at different cascade levels and in different documents, so 'upgrade' is a replay, never an overwrite."* `[P0-119]` (`ux.md:1938-1947`) names **three deliberately separate deploy channels**: 1. **Code** — *"Improves every client on deploy with no per-tenant state, because no client owns markup."* 2. **Vendor documents** — content-addressed; *"each tenant is pinned to a version and moves only by an explicit act."* 3. **The tenant's own streams** — *"Never rewritten by an upgrade."* Adopting a new base is itself a journaled act with a preflight, and `[P0-120]` (`ux.md:1954`) classifies every replayed command: | Class | Meaning | |---|---| | **clean** | applies | | **drifted** | applies, but its neighborhood changed — reported | | **refused** | its target is gone | A dry run produces that classification without committing. Two sentences carry the value: *"the same replay runs across the whole fleet before release — the vendor learns which tenants break, and on which command, before shipping"* (`ux.md:1962`), and *"A refused customization is presented to its owner as a decision (re-place it, or drop it); no upgrade path may silently discard one"* (`ux.md:1965`). Breaking vendor changes ship **migration commands in the same act**; unmigrated, they refuse rather than degrading (`ux.md:1968`). The layering an adopter is operating in is §19.3's: platform (CX) → **vendor (you)** → tenant (your client) → surface (one deployment). *"An adopter's release is therefore a versioned vendor bundle, each client pinned to a version"* (`ux.md:2245`). The ceiling is itself a document: *"The allow document at the vendor level is how the adopter chooses their client's ceiling: which elements may move, which axes may be set, which params may be written"* (`ux.md:2247`). **Read §6 before building against this section.** The surface-plane fleet story is spec prose plus one demo, with no conformance fixture, and the spec's own status ladder scopes it. ## 5. When to compose beside, instead of refining Refine when you are narrowing a contract someone else implements. **Compose a new feature beside the shared one** when you need behaviour — that is the supported answer to §2's refusal, and the seam is checked. The compose gate reports **every** violation, not the first (`xap_grammar_composition.md:145`). The gates that guard a seam between two independently authored features: | Gate | What it guarantees | |---|---| | **W1** | feature names distinct, each a single segment | | **W2** | **key compatibility** — every registration onto one key name carries the same value type/shape | | **W3** | frame registration: `via` names a real field, typed to the family's coordinate type | | **W4** | rule consistency — no mandate/exclusion contradiction, no ordering cycle, dependency targets exist | | **W5** | every `uses` entry, every qualified reference in a derived noun's `[from …]`, and every derived verb's constituent resolves | | **W6** | resolution is total and deterministic — every bare term yields one qualified verb or a well-formed ambiguity, never zero-or-crash | | **W7** | a derived noun is **deriver-reserved** — no verb writes onto it | W2 is the one an adopter meets first, because the shared key is the join: `prog.cx` ```cx [?lib 'cx-platform/xap' as=xap] [?let [= $ais [feature name=ais [types [type 'mmsi::text' [pattern '^[0-9]{9}$']]] [keys [key name=mmsi via=mmsi]] [nouns [noun name=vessel [field name=mmsi type=mmsi] [field name=pos type=geo-point]]] [verbs [verb name=track effect=observe [intent [do :track]] [reads vessel]]] [requirements [requirement kind=functional as=user traces=track [want 'to track vessels'] [so 'I know traffic']]]]] [= $harbor [feature name=harbor [keys [key name=mmsi via=id]] [nouns [noun name=berth [field name=id type=int]]] [verbs [verb name=list-berths effect=observe [intent [do :list-berths]] [reads berth]]] [requirements [requirement kind=functional as=user traces=list-berths [want 'to list berths'] [so 'I can dock']]]]] [$xap:compose $ais $harbor]] ``` ```console $ cx prog.cx [err code=cx-err:CXER4870 message='E_XAP_COMPOSE_CONFLICT: composition rejected with 1 conflict(s)' [conflict code=':w2' at=mmsi detail='key "mmsi" registered with conflicting value types: int vs mmsi']] ``` Compose is commutative and idempotent, and the same feature *set* yields a byte-identical canonical form — which is what makes a grammar hash an equality oracle rather than a checksum: `prog.cx` ```cx [?lib 'cx-platform/xap' as=xap] [?let [= $chart [feature name=chart [nouns [noun name=viewport singular=true [field name=center type=geo-point]]] [verbs [verb name=highlight effect=arrange [intent [do :highlight]] [reads viewport]]] [requirements [requirement kind=functional as=user traces=highlight [want 'to mark a spot'] [so 'I can find it']]]]] [= $strikes [feature name=strikes [nouns [noun name=strike [field name=pos type=geo-point] [field name=at type=instant]]] [verbs [verb name=list-strikes effect=observe [intent [do :list-strikes]] [reads strike]]] [requirements [requirement kind=functional as=user traces=list-strikes [want 'to see strikes'] [so 'I avoid them']]]]] [= [$xap:grammar-hash [$xap:compose $chart $strikes]] [$xap:grammar-hash [$xap:compose $strikes $chart]]]] ``` ```console $ cx prog.cx true ``` `prog.cx` ```cx [?lib 'cx-platform/xap' as=xap] [?let [= $chart [feature name=chart [nouns [noun name=viewport singular=true [field name=center type=geo-point]]] [verbs [verb name=highlight effect=arrange [intent [do :highlight]] [reads viewport]]] [requirements [requirement kind=functional as=user traces=highlight [want 'to mark a spot'] [so 'I can find it']]]]] [= [$xap:grammar-hash [$xap:compose $chart $chart]] [$xap:grammar-hash [$xap:compose $chart]]]] ``` ```console $ cx prog.cx true ``` ### 5.1 The trap: a shared key that moves does NOT refuse This is the most important paragraph in this section, because the intuition is wrong. **W2 checks type agreement only, never spelling** (`xap_grammar_composition.md:1104`): *"W2 checks exactly one property of them: that every registration onto one name agrees in value type. Nothing checks that the NAME is the one the author meant."* So if one feature says `order-id` and the other says `order_id`, there is **no W2 conflict**. Both become solitary, composition passes, `ok=true`, and the first evidence is an empty readout at runtime. What catches it is a **note**, and `ok=` is untouched: `prog.cx` ```cx [?lib 'cx-platform/xap' as=xap] [?let [= $alpha [feature name=alpha version='1' [summary 'A.'] [keys [key name=order-id via=oid]] [nouns [noun name=a [field name=oid type=text]]] [verbs [verb name=see effect=observe [intent [do :see]] [reads a]]] [requirements [requirement kind=functional as=user traces=see [want 'x'] [so 'y']]]]] [= $beta [feature name=beta version='1' [summary 'B.'] [keys [key name=order_id via=oid]] [nouns [noun name=b [field name=oid type=text]]] [verbs [verb name=look effect=observe [intent [do :look]] [reads b]]] [requirements [requirement kind=functional as=user traces=look [want 'x'] [so 'y']]]]] [$xap:compose-report $alpha $beta]] ``` ```console $ cx prog.cx [compose-report ok=true [note code=':solitary-key' at='alpha/a#oid' key=order-id detail='one registration — a key name meant to JOIN needs at least two, so this is either a misspelling of another key or a private field wearing a global name'] [note code=':solitary-key' at='beta/b#oid' key=order_id detail='one registration — a key name meant to JOIN needs at least two, so this is either a misspelling of another key or a private field wearing a global name']] ``` Spelled correctly, the same pair joins and the report is silent — so the note carries information rather than noise: `prog.cx` ```cx [?lib 'cx-platform/xap' as=xap] [?let [= $alpha [feature name=alpha version='1' [summary 'A.'] [keys [key name=order-id via=oid]] [nouns [noun name=a [field name=oid type=text]]] [verbs [verb name=see effect=observe [intent [do :see]] [reads a]]] [requirements [requirement kind=functional as=user traces=see [want 'x'] [so 'y']]]]] [= $beta [feature name=beta version='1' [summary 'B.'] [keys [key name=order-id via=oid]] [nouns [noun name=b [field name=oid type=text]]] [verbs [verb name=look effect=observe [intent [do :look]] [reads b]]] [requirements [requirement kind=functional as=user traces=look [want 'x'] [so 'y']]]]] [$xap:compose-report $alpha $beta]] ``` ```console $ cx prog.cx [compose-report ok=true] ``` Know its limit, stated at `xap_grammar_composition.md:1123`: it catches the **typo** class exactly, and says nothing about two features meaning different things by one correctly-spelled name. ### 5.2 Archetype status is earned, not declared Before you publish a feature as an archetype for others to instantiate, `xap_grammar_composition.md:383`: > **Graduation (R8.10).** Archetype status is EARNED: the cohesion gate passing, > plus TWO genuinely different instantiations named and recorded as evidence — > different composing surface/tenant, non-overlapping `uses` neighborhoods, > **not two skins of one deployment**. The same bar governs whether a feature is ready for a marketplace at all (`:497`): *"Designed-for-one-XAP bias cannot be seen by inspection, only by a second composition failing to fit."* The cohesion instrument ships report-first — a healthy feature is one component: `prog.cx` ```cx [?lib 'cx-platform/xap' as=xap] [?let [= $f [feature name=till version="1" [nouns [noun name=sale [field name=id type=text]] [noun name=receipt [field name=sale-id type=text]]] [verbs [verb name=ring-up effect=act [intent [do :ring-up [id]]] [writes sale]] [verb name=print effect=act [intent [do :print [id]]] [writes receipt]]] [rules [rule name=receipt-cites-sale kind=validity nouns='receipt sale' [statement 'A receipt MUST cite the sale it records.']]] [requirements [requirement kind=functional as=clerk traces=ring-up [want 'to ring up'] [so 'rung']]]]] [= $r [$xap:cohesion $f]] [probe [n [$string $r@components]] [members [$count $r//member]] [edges [$count $r//edge]]]] ``` ```console $ cx prog.cx [probe [n '1'] [members 4] [edges 3]] ``` `prog.cx` ```cx [?lib 'cx-platform/xap' as=xap] [?let [= $f [feature name=till-and-piano version="1" [nouns [noun name=sale [field name=id type=text]] [noun name=piano [field name=pitch type=text]]] [verbs [verb name=ring-up effect=act [intent [do :ring-up [id]]] [writes sale]] [verb name=tune effect=act [intent [do :tune]] [writes piano]]] [requirements [requirement kind=functional as=clerk traces=ring-up [want 'to ring up'] [so 'rung']]]]] [= $r [$xap:cohesion $f]] [probe [n [$string $r@components]] [split [?for [in $c $r//component] [yield [part [?splice [?for [in $m $c/*] [yield [x [$string $m@name]]]]]]]]]]] ``` ```console $ cx prog.cx [probe [n '2'] [split [part [x piano] [x tune]] [part [x sale] [x ring-up]]]] ``` ## 6. The worked arc, and what is missing ### 6.1 Two tenants, one archetype — runnable This is the whole model in one fixture, on the feature `cx xap init` actually generates (`vcx/cmd/xap_init_templates.v:100-131`, copied verbatim). Two tenants derive **different** variants of **one** base: `acme` renames, adds a field and tightens `create` to irreversible; `borough` renames and withdraws `list` from its offer. Neither carries code. Both name the same base pin. `prog.cx` ```cx [?lib 'cx-platform/xap' as=xap] [?lib 'cx-stdlib/hash' as=hash] [?lib 'cx-stdlib/bytes' as=bytes] [?let [= $thing [feature name=thing version="1" [summary 'The things this XAP is about. Rename it to your own noun.'] [frames [use frame=time via=created-at]] [keys [key name=owner-id via=owner]] [nouns [noun name=thing [field name=id type=text] [field name=owner type=text doc='the shared key — same name and type in owner.feature.cxd'] [field name=created-at type=instant doc='the time-frame coordinate'] [field name=label type=text]]] [verbs [verb name=create effect=act scope=shared consequence=reversible [intent [do :create]] [writes thing]] [verb name=list effect=observe [intent [do :list]] [reads thing]]] [rules [rule name=label-required kind=validity [statement 'A thing MUST carry a label.']]] [governance [grant verb=create to=operator]] [requirements [requirement kind=functional as=operator traces=create [want 'to create a thing'] [so 'it exists in the record'] [acceptance 'a created thing appears under the /thing state route']]]]] [= $pin [$concat "sha2-256:" [$bytes:to-hex [$hash:sha256-string [$cx:canonical $thing]]]]] [= $acme [$xap:instantiate $thing [instance name=asset of=$pin [rename noun=thing label='Asset'] [add on=thing [field name=serial type=text]] [tighten verb=create consequence=irreversible]]]] [= $borough [$xap:instantiate $thing [instance name=permit of=$pin [rename noun=thing label='Permit'] [select [offer verb=create] [not-offered verb=list why='Permits are listed by the registry, not the tenant.']]]]] [probe [acme-name [$string $acme@name]] [acme-cq [?for [in $v $acme//verb] [where [= [$string $v@name] "create"]] [yield [$string $v@consequence]]]] [acme-fields [$count $acme//field]] [borough-name [$string $borough@name]] [borough-cq [?for [in $v $borough//verb] [where [= [$string $v@name] "create"]] [yield [$string $v@consequence]]]] [borough-fields [$count $borough//field]] [acme-base-is-pin [= [$string $acme//instantiated-from@archetype] $pin]] [borough-base-is-pin [= [$string $borough//instantiated-from@archetype] $pin]]]] ``` ```console $ cx prog.cx [probe [acme-name asset] [acme-cq irreversible] [acme-fields 5] [borough-name permit] [borough-cq reversible] [borough-fields 4] [acme-base-is-pin true] [borough-base-is-pin true]] ``` Read the output: two contracts, one implementation. That is §0's sentence, executed. ### 6.2 The grammar-plane preflight, and what still does not exist **The grammar plane has a per-refinement preflight** (RULED: 1255-a). `[$xap:instantiate-preflight ARCHETYPE BINDING]` is the same gate `[$xap:instantiate]` enforces, read rather than applied: it answers `[instantiate-preflight ok= …]` with one `[refinement status=… at=… detail=…]` per refinement of the binding, in binding order, where `status=` is `clean`, `refused` — carrying that refinement's own code, `CXER4877`/`4878`/ `4879` — or `redundant`, meaning the archetype already holds that floor, so a re-bless may drop the line. It never refuses what the gate decides, and **`ok=false` exactly when `instantiate` would raise**: both faces run the one validation pass, so the agreement law holds by construction rather than by two edits kept in step — the same law `compose-report` and `compose` hold on the composition side. A stale `of=` pin is itself reported as a refusal, and the refinements are classified **anyway**. That combination is the point: a candidate v2 presents a stale pin to *every* tenant binding, so a preflight that refused there would lose the fleet at the first tenant. Instead a vendor asks which of a fleet's bindings the candidate would break, and on which refinement, and gets an answer per tenant: `prog.cx` ```cx [?lib 'cx-platform/xap' as=xap] [?lib 'cx-stdlib/hash' as=hash] [?lib 'cx-stdlib/bytes' as=bytes] [?let [= $v1 [feature name=thing version="1" [nouns [noun name=thing [field name=id type=text] [field name=label type=text]]] [verbs [verb name=create effect=act scope=shared consequence=reversible [intent [do :create]] [writes thing]]] [requirements [requirement kind=functional as=operator traces=create [want 'to create a thing'] [so 'it exists in the record']]]]] [= $v2 [feature name=thing version="2" [nouns [noun name=thing [field name=id type=text] [field name=label type=text]]] [verbs [verb name=create effect=act scope=shared consequence=irreversible [intent [do :create]] [writes thing]]] [requirements [requirement kind=functional as=operator traces=create [want 'to create a thing'] [so 'it exists in the record']]]]] [= $pin1 [$concat "sha2-256:" [$bytes:to-hex [$hash:sha256-string [$cx:canonical $v1]]]]] [= $r [$xap:instantiate-preflight $v2 [instance name=asset of=$pin1 [tighten verb=create consequence=reversible]]]] [probe [ok [$string $r@ok]] [codes [?for [in $x $r//refinement] [yield [$string $x@code]]]]]] ``` ```console $ cx prog.cx [probe [ok 'false'] [codes 'cx-err:CXER4879' 'cx-err:CXER4878']] ``` Two refusals from one binding is also what totality means here: `instantiate` raises the first refusal's code and sentence — unchanged, which is why no existing fixture moved — and now names the rest instead of dropping them (`xap-compose-137`). The three statuses against the same v1/v2 pair are `xap-compose-133` (refused, with its code), `134` (redundant — the tighten the vendor made the floor) and `135` (clean, both faces agreeing): `prog.cx` ```cx [?lib 'cx-platform/xap' as=xap] [?lib 'cx-stdlib/hash' as=hash] [?lib 'cx-stdlib/bytes' as=bytes] [?let [= $v1 [feature name=thing version="1" [nouns [noun name=thing [field name=id type=text] [field name=label type=text]]] [verbs [verb name=create effect=act scope=shared consequence=reversible [intent [do :create]] [writes thing]]] [requirements [requirement kind=functional as=operator traces=create [want 'to create a thing'] [so 'it exists in the record']]]]] [= $pin1 [$concat "sha2-256:" [$bytes:to-hex [$hash:sha256-string [$cx:canonical $v1]]]]] [= $r [$xap:instantiate-preflight $v1 [instance name=asset of=$pin1 [tighten verb=create consequence=reversible]]]] [probe [ok [$string $r@ok]] [st [?for [in $x $r//refinement] [yield [$string $x@status]]]]]] ``` ```console $ cx prog.cx [probe [ok 'true'] [st redundant]] ``` The fleet-level command that walks a tenant list and calls this per tenant (`cx xap preflight --to `) is its consumer and is **not built yet**; this verb is what it will be built on. **The order in which an adopter then moves their tenants is deliberately unprescribed** (RULED: 1255-b, `ux.md` §19.3). CX supplies the facts — this per-tenant classification, and pins that move independently — and the adopter supplies the policy. A canary-first fleet, a smallest-blast-radius-first fleet and an all-at-once fleet are all expressible with the same facts; prescribing one would make a release-management opinion normative for everyone. What still does not exist, stated plainly, because a document that implies a mechanism is worse than one that admits a gap: - **The surface-plane fleet mechanics have no conformance fixture.** `adopt-base`, the `fold(base, commands)` replay, tenant pins and the allow document are spec prose plus the ORIEL demo (`spec/03-approved/xap/demos/oriel/serve.cx:5165-5263`, routes at `surface.cx:66-67`, gate-enforced assertions at `drive.cx:1016-1025` and `:1362-1381` — which is a real-socket step, `make test-oriel`). The spec's own status ladder scopes the claim (`ux.md:2114`): built *"for a candidate bundle and per-tenant adoption"*, with migration commands for breaking changes **unbuilt**. - **Two spellings in §18.5 are not what the implementation does.** `ux.md:1960` describes adoption committing a `[base-adopted from=… to=…]` entry; the implementation journals `[pinned tenant= version= clean= refused=]` (`serve.cx:5253-5258`). And of `[P0-120]`'s three classes, the layout replay emits only `clean` and `refused` (`serve.cx:3523-3531`); `drifted` comes from the catalog comparison arm (`serve.cx:3647`), i.e. tenant-owned content, not a replayed vendor-document command. Trust the code over the prose here until one of them moves. ## 7. Where to go next | File | Load it when | |---|---| | `playbook-xap.md` | you are authoring the features and the deployment this model applies to | | `reference-platform.md` | you are persisting, serving or distributing | Normative sources for everything above: `spec/03-approved/xap/ux.md` §18–§19 (the surface plane), `spec/03-approved/xap/xap_grammar_composition.md` §4.3–§4.4 and §4.13 (the grammar plane), `spec/03-approved/xap/xap_feature_distribution_market.md` §1 (packages and pins), and `spec/03-approved/xap/xap_schemas/instance.cxs` (the binding vocabulary). The rulings behind the refusals: AD-10 (#1162) and 1161-Q1a in `ledger/`; the preflight and the unprescribed rollout order are 1255-a and 1255-b (`ledger/rulings_2026_09_08_grammar_plane_preflight_1255.md`). # CX contributor front door — the rules, and what to load > **GENERATED FILE.** Source: `docs-src/llm/contributor.md.tmpl`, the ledger's > `RS-n` statements and two conformance fixtures, assembled by > `scripts/gen_docs/primer_build.cx`. `make docs-check` refuses this file when > it drifts from them. Edit the template, never this file. > > Read `cx primer` first — it teaches the language. This file teaches the > PROJECT: how CX is built, changed and graded, so a change you make is the > one a maintainer would have made. - CX is built so that a claim can be checked: the spec states it, a corpus case pins it, a gate refuses the tree that breaks it. Contribute in that order and nothing you write has to be taken on trust. - Two frozen rings (data, code) and product groups above them, each product its own repository, pinned by sha. You change the smallest ring that owns the behaviour and nothing above it moves. - Every rule below cites the decision (`RULED: `) that made it; the ledger (`ledger/README.md` indexes every id) is where to read the reason. - Load ONE area file for the work at hand. The table in §1 is the whole routing. ## 1. The reading order — load the least that answers the question | The work | Load, in order | Never load | |---|---|---| | Write or fix a CX program | `cx primer` → the one `reference-*.md` the primer's §11 names | the spec whole; `llms-full.txt` | | Add or move a module / surface | this file → `contributor-design.md` → the module's row in `registry/modules.cxd` → its spec's named section | `registry/repos.cxd`'s `why=` prose; other modules' specs | | Change V code (evaluator, a product) | this file → `contributor-implementation.md` → the one V module directory | `third_party/v/`; `deps/*/` trees other than the pin you change | | Know where something lives / who pins what | `contributor-architecture.md` | the repositories themselves, one by one | | Choose and run the tests for a change | `contributor-test.md` → `scripts/test_changed.sh --dry-run` | `make test` (the post-merge union); the Makefile whole | | Cite or check a decision | `ledger/README.md` (the index) → the one page and heading it names | the ledger directory whole | | Write documentation | `contributor.md` §3 rule 17 → `scripts/gen_docs/README.md` | hand-copied examples from anywhere | | Read a failure | the run log's `STEP-START`/`STEP-END` lines and the failing step's own block | the whole interleaved log top to bottom | Size discipline: the language corpus (`deps/cx-core-code/conformance/code.cxd`) is the largest corpus file — find a case by id (`grep -n 'id='`), never read it. Find a step in the Makefile by `grep -n '^:'`. A spec page is read by the section its decision names, never whole. ## 2. What CX guarantees, pinned by the corpus Two properties every contribution preserves. Both are corpus cases, replayed against the binary each time this file is generated. **Effects are deny-by-default** — reading a file with no grant is an ordinary error value naming the flag that would allow it: Generated from the corpus case `io-001-read-file-cap-denied` (`cx-core-code`, `conformance/stdlib/io.cxd`), replayed by `make docs` — do not edit here. `prog.cx` ```cx [?lib 'cx-stdlib/io'] [$io:read-file "/var/log/app.log"] ``` ```console $ cx prog.cx [err code=cx-err:CXER0271 message='E_CAP_DENIED: read capability required for io-read-file; none granted (grant via --allow-read)' at='2:1'] ``` **Errors are values** — a captured `[err]` propagates out of any element built from it, with no `try` and no exception: Generated from the corpus case `program-construction-err-004-captured-err-propagates` (`cx-core-code`, `conformance/code.cxd`), replayed by `make docs` — do not edit here. `prog.cx` ```cx [?let [= $e [err code='c' message='m']] [report $e]] ``` ```console $ cx prog.cx [err code=c message=m] ``` A change that makes either of these false is refused by the corpus, whatever else it fixes. ## 3. The rules for the best long-term change Each is a rule, the decision behind it, and the step that refuses a tree that breaks it. Where the ledger has the rule's own words, §4 prints them. | # | Rule | Decision | Held by | |---|---|---|---| | 1 | **The spec is the only truth.** Normative text is `spec/03-approved/`; no design note overrides it; never edit a spec to match an implementation shortfall. | AGENTS.md rule 1 (RULED: CFG-1) | `spec-freeze-gate`, `check-no-adr-citations` | | 2 | **A spec edit and its impl edit carry a recorded decision.** The `RULED:` id is on the ledger BEFORE a commit cites it. | the spec-freeze register (RULED: R4.1) | `spec-freeze-gate`, `ledger-index-check` | | 3 | **The corpus is the executable truth; the fixture comes before the fix.** A behaviour change lands its failing case first, then the code that passes it. | AGENTS.md rule 2 (RULED: CFG-1) | the fixture graders, `check-code-fixtures` | | 4 | **No stubs, no partial implementations, no silent scope cuts.** A seam with no live consumer is a partial implementation; what cannot be finished is said and stopped. | AGENTS.md rule 3 (RULED: CFG-1) | `check-no-stub-impl` | | 5 | **Two rings, then groups.** Ring 0 cannot execute; Ring 1 cannot reach a socket, a store or a protocol; everything else is a group with a declared, acyclic import graph. | RULED: RS-1 | `ring-import-gate`, `placement-gate` | | 6 | **A module lives in the ring of its highest verb**; a pure half a Ring-1 consumer needs becomes its own Ring-1 module. | RULED: OL-14 | `placement-gate` | | 7 | **Placement before code.** A new surface states its ring, group and directories in its decision, then its `registry/modules.cxd` row, then spec, corpus, code. | RULED: OL-15, 1427-f | `placement-gate`, `stdlib-catalog-gate` | | 8 | **One repository per product**, split where a person could own, release and explain it in one line. | RULED: RS-2, RS-3 | `repos-allocation-gate` | | 9 | **Consumers pull, producers never push.** Every repository pins what it builds on by sha in `deps.cxd`; `cx` is a migration path, never a gate. | RULED: RS-7 | `test-deps-pins`, `make deps-sync` | | 10 | **The mover fixes what it breaks.** A lower repository keeps its specified surface compatible; an intentional break is a decision naming its consumers. | RULED: RS-8 | the consumer's own gate on the bump | | 11 | **A V product imports the rings and its pins, nothing else**; one V module per product, explicit `import`s, a `pub` surface per caller. | RULED: RS-24 | `product-import-gate` | | 12 | **Products register themselves.** A product adds its builtins and its CLI verbs from its own `init()`; the core knows no product by name. | RULED: D71a, D73a | the build of a profile without the product | | 13 | **The ring decides where code lives, not its only consumer.** A pure Ring-1 V half stays in the core even when one product calls it. | RULED: D76c, D84a | `repos-allocation-gate` | | 14 | **Tooling is written in CX.** Scripts, probes and bisects are CX programs; another language needs a filed `cx-gap` issue first. | RULED: CXF-1 | review; `reader-parity` holds every reader of the corpus files to one answer (RULED: CXF-5) | | 15 | **A CX surprise is filed the same hour, with its reproduction.** A small defect in a module you already touch is fixed in your branch, fixture first. | RULED: CXF-8, FIX-1 | the issue tracker | | 16 | **One vocabulary.** release/epic/issue/decision/spec; branch/worktree/integration branch/merge; step/pipeline/run/runner. | RULED: DG-1 | `spec/03-approved/process/delivery-grammar.md` | | 17 | **Docs follow the module and are generated.** A reference is projected from its corpus at release; an example no fixture backs is a liability. | RULED: RS-9, RS-28 | `docs-check`, `verify-doc-blocks` | | 18 | **CX's own voice.** Lead with why to reach for CX; state its case, never referee; a runnable example or a measured number beside every claim. | RULED: RS-30 | `docs-check` (the examples replay) | | 19 | **The version derives from `VERSION`.** No second copy of a version string anywhere. | AGENTS.md rule 5 (RULED: CFG-1) | `check-version-consistency` | | 20 | **No AI attribution, anywhere, ever** — no co-author trailer, no generated-by line, in any repository. | RULED: RS-33 | `check-no-ai-attribution` | | 21 | **No consumer's name in the tree.** Every artifact speaks CX-generic workload terms (users, tracked entities, events, deployments); the tree is published. | the owner's standing decision of 2026-07-24, under RULED: RS-11 | `check-no-consumer-terms` | | 22 | **Flags bind before the resource.** `cx [cx-flags] FILE [program-args]`. | AGENTS.md rule 4 (RULED: CFG-1) | the antipattern corpus | When two sources disagree: the owner's live word, then `AGENTS.md`, then the process specs, then the ledger (RULED: CFG-1). A harness template never overrides any of them. ## 4. The standing decisions, in the ledger's own words The `RS-n` series is the repository-split decision set every contributor works inside. Each statement below is the ledger's bold lead or heading, verbatim; read the page for the reason and the options refused. Generated by `scripts/gen_docs/contributor_facts.cx` from the ledger pages' `RS-n` decision rows and headings, verbatim (37 rows) — do not edit here: change the source, run `make docs`. | id | the ledger's statement | page | |---|---|---| | `RS-1` | Two rings, then groups. | `ledger/rulings_2026_09_21_repo_split_1589.md` | | `RS-2` | Split by product, not by dependency graph. | `ledger/rulings_2026_09_21_repo_split_1589.md` | | `RS-3` | The shape: `cx` (front door and distribution), `cx-core-data` (Ring 0), `cx-core-code` (Ring 1, the stdlib inside it), `cx-platform-{net, mail, identity, store, db, fabric, xap}` (V, compiled into the full `cx`, release with it), `cx-platform-{flow, connector, sso, ux, agent}` (pure CX, registry packages, their own cadence), `cx-binding-{python, go, rust, v}` (active) and `cx-binding-{typescript, java, kotlin, csharp, swift, ruby}` (archived repositories at the ABI they were frozen with), `cx-registry`, `cx-decisions`, `cx-tooling`. | `ledger/rulings_2026_09_21_repo_split_1589.md` | | `RS-4` | Naming: `cx--`; the bare `cx` is where a visitor starts. | `ledger/rulings_2026_09_21_repo_split_1589.md` | | `RS-5` | Stdlib membership moves, by the membership test (cx_partition.md §10) and by consumer. | `ledger/rulings_2026_09_21_repo_split_1589.md` | | `RS-6` | The store server authenticates through the Ring-1 trust primitives (did/vc verify, the authz decision) and its own `[grants]`, not through session tables. | `ledger/rulings_2026_09_21_repo_split_1589.md` | | `RS-7` | Consumers pull, producers never push. | `ledger/rulings_2026_09_21_repo_split_1589.md` | | `RS-8` | Break policy (owner, "1a"): within a release train a lower repository keeps its specified surface compatible; the repository that moved fixes what it breaks or ships the compatibility; an intentional break is a decision naming its consumers. | `ledger/rulings_2026_09_21_repo_split_1589.md` | | `RS-9` | Docs follow the module. | `ledger/rulings_2026_09_21_repo_split_1589.md` | | `RS-10` | Daemons stay subcommands of one `cx` (`store-serve`, `fabric-serve`, `flow serve`, the XAP host). | `ledger/rulings_2026_09_21_repo_split_1589.md` | | `RS-11` | Public or private is a setting per repository. | `ledger/rulings_2026_09_21_repo_split_1589.md` | | `RS-12` | Sequencing: the split starts now, during the suspension, and v0.18.0 ships from `cx` pinning the new repositories. | `ledger/rulings_2026_09_21_repo_split_1589.md` | | `RS-13` | `authz` splits along the store-auth design line (owner: D14a) | `ledger/rulings_2026_09_22_repo_split_followups.md` | | `RS-14` | did/vc's impure remainder gets modules named for what leaves (owner: D15b) | `ledger/rulings_2026_09_22_repo_split_followups.md` | | `RS-15` | the xsp `auth-*` defs become `cx-platform/xsp-auth` (owner: D16a) | `ledger/rulings_2026_09_22_repo_split_followups.md` | | `RS-16` | the `cx` binary grades a corpus file (owner: D13a) | `ledger/rulings_2026_09_22_repo_split_followups.md` | | `RS-17` | the `-gc e` per-case growth is fixed at the root (owner: D3b) | `ledger/rulings_2026_09_22_repo_split_followups.md` | | `RS-18` | builtin registration splits per family, before the db extracts (owner: D20a) | `ledger/rulings_2026_09_22_repo_split_followups.md` | | `RS-19` | mail claims its smtp/imap pure halves, as `sasl` already is (owner: D19a) | `ledger/rulings_2026_09_22_repo_split_followups.md` | | `RS-20` | flow's CLI verbs stay in the front door, and the act seam gets a row (owner: D21a) | `ledger/rulings_2026_09_22_repo_split_followups.md` | | `RS-21` | a decision's design item may ADD spec text where the edit map is silent (owner: D22b) | `ledger/rulings_2026_09_22_repo_split_followups.md` | | `RS-22` | `authz-store` and `vc-revocation` stay in `cx-platform-identity` (owner: D24a) | `ledger/rulings_2026_09_22_repo_split_followups.md` | | `RS-23` | the decision half gets a pure `open` over an in-memory trust store (owner: D25b) | `ledger/rulings_2026_09_22_repo_split_followups.md` | | `RS-24` | `vcx/platform` splits by product, one V module per V product (owner: D28a) | `ledger/rulings_2026_09_22_repo_split_followups.md` | | `RS-25` | the mock adapter is declared by the kit's own CX, and the compile-time gate retires (owner: D30d) | `ledger/rulings_2026_09_22_repo_split_followups.md` | | `RS-26` | RS-26…RS-31 — five spec-sentence authorizations, the afternoon letters, documentation and CI/CD, the documentation voice and the evening letters (owner, 2026-09-23, in session on dev2) | `ledger/rulings_2026_09_23_spec_sentence_letters.md` | | `RS-27` | the afternoon letters of 2026-09-23 (owner: D49a, D50a, D51d, D52a, D53a) | `ledger/rulings_2026_09_23_spec_sentence_letters.md` | | `RS-28` | one site, thin repositories, and cx created by the recipe (owner: D57a, D58a, D59a) | `ledger/rulings_2026_09_23_spec_sentence_letters.md` | | `RS-29` | CI/CD is cx flow, documentation included (owner: D60a) | `ledger/rulings_2026_09_23_spec_sentence_letters.md` | | `RS-30` | the documentation voice (owner, 2026-09-23 ~16:4xZ) | `ledger/rulings_2026_09_23_spec_sentence_letters.md` | | `RS-31` | the evening letters of 2026-09-23 (owner: D54c, D55c, D56a, D62a, D63a, D64c, D65d, D66a, D67a, D68a, D69b, D70a1, D71a, D72a, D73a, D74c, D75a, D76c, D77d, D78a) | `ledger/rulings_2026_09_23_spec_sentence_letters.md` | | `RS-32` | the front-door letters of 2026-09-24 (owner: D79a, D80a, D81a, D82a, D83a, D84a) | `ledger/rulings_2026_09_24_front_door_letters.md` | | `RS-33` | zero AI attribution anywhere in any repository, ever (owner: D87, D88b) | `ledger/rulings_2026_09_24_no_ai_attribution_rs33.md` | | `RS-34` | the letters of 2026-09-25 (owner: D89a, D90b) | `ledger/rulings_2026_09_25_llm_front_door_k11a.md` | | `RS-35` | the V fork under the zero-attribution rule: strip the fork's own range only (owner: Letter 2 = (a), 2026-09-25) | `ledger/rulings_2026_09_25_v_fork_rs35.md` | | `RS-36` | how a wave in a component repository lands after the split (owner, 2026-09-25, Letter 4 (a)) | `ledger/rulings_2026_09_25_component_waves_rs36.md` | | `RS-38` | a flow-ladder wave may state, inside its ruled section, the exact semantics its fixtures grade (owner: Letter 18 = (a), 2026-09-26) | `ledger/rulings_2026_09_26_fixture_backed_sentences_rs38.md` | ## 5. The area files | File | Load it when | Generated parts | |---|---|---| | `contributor-architecture.md` | you need the repository map, the pins, the rings and groups, or the registration hooks | the repository map (`registry/repos.cxd`), the pins (`deps.cxd`) | | `contributor-design.md` | a surface needs a ring, a group, a row, a spec and a corpus before code | the module census and the row's columns (`registry/modules.cxd`) | | `contributor-implementation.md` | you are writing V: a module, an import, a registration, the front door's build | — (rules only; each cites its decision) | | `contributor-test.md` | you are choosing, running or reading steps | the step roster (the Makefile's `TEST_TARGETS`), the umbrella files (the pins) | # CX contributor front door — architecture > **GENERATED FILE.** Source: `docs-src/llm/contributor-architecture.md.tmpl`, > `registry/repos.cxd` and `deps.cxd`, assembled by > `scripts/gen_docs/primer_build.cx`. The two tables are projections; the > prose is the rules, each citing its decision. Edit the template or the > registry, never this file. - Two rings, frozen: **Ring 0** the data format (`cx-core-data`), **Ring 1** the language and its standard library (`cx-core-code`). Everything else is a **group** — platform, binding, ecosystem, dist — importing the rings plus what its row declares, acyclic (RULED: RS-1). - One repository per product; `cx` is the front door that pins every one by sha and builds the binaries from the pins (RULED: RS-2, RS-3, RS-7). - The core names no product: products register their builtins and CLI verbs from their own `init()` (RULED: D71a, D73a). - Where a thing lives is DATA: `registry/repos.cxd` allots every tracked path to one repository; `registry/modules.cxd` declares every surface's ring and group. Read the row, not a directory listing. ## 1. Rings and groups — what each import contract buys | Layer | What it may import | What that buys | Decision | |---|---|---|---| | Ring 0 — data | nothing above itself | a document cannot execute: parsing a file is always safe | RULED: RS-1 | | Ring 1 — code | Ring 0 | a program cannot reach a socket, a store or a protocol without a platform module | RULED: RS-1 | | platform group (`ring=1 group=platform`) | the rings + the products its row pins | state that outlives the program: store, net, mail, identity, fabric, xsp, xap | RULED: RS-1, RS-24 | | binding / ecosystem / dist groups | the C ABI and the released binary | other languages, editors, the registry, the site | RULED: RS-1, RS-3 | A module lives in the ring of its **highest verb**: pure or purely local is Ring 1; serving, or reaching a store or a protocol, is the platform group (RULED: OL-14). The daemons (`store-serve`, `fabric-serve`, `flow serve`, the XAP host) stay subcommands of one `cx` (RULED: RS-10). ## 2. The repository map `status=extracted` means the repository exists, owns its paths, and none of them is tracked in the front door any more; a blank status is a row that still allots paths here. `pins` is the import graph a V product may use (the `product-import-gate` holds it); `V module` names the one `vcx//` directory the product compiles from (RULED: RS-24). Generated by `scripts/gen_docs/contributor_facts.cx` from `registry/repos.cxd`'s `[repo]` rows; `at=` (the pinned sha) and `why=` stay in the file (32 rows) — do not edit here: change the source, run `make docs`. | repository | group | ring | ships | V module (`vcx//`) | pins | status | what it is | |---|---|---|---|---|---|---|---| | `cx` | dist | none | binary | — | — | — | the front door: pins every repo, builds the four cx builds and libcx-core/libcx, holds the site, the release, the installer | | `cx-private` | dist | none | none | — | — | — | the private orchestration repository: boards, evidence, runner configuration, briefs | | `cx-core-data` | core | 0 | binary | — | — | extracted | the data format: reader, writer, canonical form, schema, codecs, libcx-core, the data-only cx | | `cx-core-code` | core | 1 | binary | — | `cx-core-data` | extracted | the language: evaluator, stdlib, LSP, libcx, the cli and embed builds | | `cx-platform-net` | platform | none | binary | `cxnet` | `cx-core-code` | extracted | sockets, TLS, the HTTP server, the XSP transport bindings, ftp, sftp, file_surface | | `cx-platform-mail` | platform | none | binary | `mail` | `cx-platform-net` | extracted | SMTP and IMAP, server and client cores | | `cx-platform-identity` | platform | none | binary | `identity` | `cx-platform-store` `cx-platform-net` `cx-platform-xsp` | extracted | sessions, did:web, authz-store, vc-revocation | | `cx-platform-store` | platform | none | binary | `store` | `cx-core-code` `cx-platform-net` `cx-platform-xsp` | extracted | the storage engine, its substrates, journal/live/audit, and the store server | | `cx-platform-db` | platform | none | binary | `cxdb` | `cx-core-code` | extracted | external database access: $sql-*, $redis-*, the drivers | | `cx-platform-fabric` | platform | none | binary | `fabric` | `cx-platform-store` `cx-platform-identity` `cx-platform-net` `cx-platform-xsp` | extracted | messaging and fabric-serve | | `cx-platform-xsp` | platform | none | binary | `xsp` | `cx-core-code` | extracted | the XAP Stream Protocol: the XSP-AUTH handshake, the protocol pages | | `cx-platform-xap` | platform | none | binary | `xap` | `cx-platform-net` `cx-platform-mail` `cx-platform-identity` `cx-platform-store` `cx-platform-db` `cx-platform-fabric` `cx-platform-xsp` | extracted | the application host, compose, dist, schema, market, the reference deployments | | `cx-platform-flow` | platform | none | package | — | `cx-core-code` `cx-platform-store` | extracted | workflow; its CLI verbs stay in the binary | | `cx-platform-connector` | platform | none | package | — | `cx-core-code` `cx-platform-mail` `cx-platform-store` | extracted | the connector kit and sync | | `cx-platform-secrets` | platform | none | package | — | `cx-core-code` `cx-platform-connector` `cx-platform-store` | extracted | the minimal keystore: one handle grammar, one internal resolve seam, the PEP and the audit record, custody that rises by a provider row | | `cx-platform-sso` | platform | none | package | — | `cx-core-code` `cx-platform-identity` `cx-platform-net` `cx-platform-store` | extracted | enterprise sign-on and its interop step | | `cx-platform-ux` | platform | none | package | — | `cx-core-code` `cx-platform-flow` | extracted | the web and terminal surfaces | | `cx-platform-workbench` | platform | none | package | — | `cx-core-code` | created | the document workbench — open, browse, comment on and safely modify a CX document | | `cx-platform-agent` | platform | none | package | — | `cx-core-code` `cx-platform-store` `cx-platform-xap` | extracted | tools, mcp, mcp-server, a2a, a2a-xap, llm, run, adjudicate | | `cx-binding-python` | binding | none | pypi | — | — | extracted | cxlib for Python | | `cx-binding-go` | binding | none | go-module | — | — | extracted | cxlib for Go | | `cx-binding-rust` | binding | none | crate | — | — | extracted | cxlib for Rust | | `cx-binding-v` | binding | none | v-module | — | — | extracted | the native reference binding | | `cx-binding-typescript` | binding | none | archived | — | — | — | archived at the ABI it was frozen with; lang/_archived/README.md names the version | | `cx-binding-java` | binding | none | archived | — | — | — | archived at the ABI it was frozen with; lang/_archived/README.md names the version | | `cx-binding-kotlin` | binding | none | archived | — | — | — | archived at the ABI it was frozen with; lang/_archived/README.md names the version | | `cx-binding-csharp` | binding | none | archived | — | — | — | archived at the ABI it was frozen with; lang/_archived/README.md names the version | | `cx-binding-swift` | binding | none | archived | — | — | — | archived at the ABI it was frozen with; lang/_archived/README.md names the version | | `cx-binding-ruby` | binding | none | archived | — | — | — | archived at the ABI it was frozen with; lang/_archived/README.md names the version | | `cx-registry` | ecosystem | none | none | — | — | extracted | the package index: the store, publisher keys, the publish program | | `cx-decisions` | ecosystem | none | none | — | — | extracted | the ledger, the process specs, the standing rules, design texts | | `cx-tooling` | ecosystem | none | editor | — | — | extracted | vscode, neovim, tree-sitter, completions, syntax, the installer | ## 3. The pins — how the front door is assembled Generated by `scripts/gen_docs/contributor_facts.cx` from `deps.cxd`'s `[dep]` rows; each row's `sha` and `v-fork` sha stay in the file (24 rows) — do not edit here: change the source, run `make docs`. | pinned repository | modules | V repository (carries `v-fork`) | the modules the pin provides | |---|---|---|---| | `cx-platform-agent` | 8 | no | `cx-platform/tools` `cx-platform/mcp` `cx-platform/mcp-server` `cx-platform/a2a` `cx-platform/a2a-xap` `cx-platform/llm` `cx-platform/run` `cx-platform/adjudicate` | | `cx-platform-ux` | 3 | no | `cx-platform/ux` `cx-platform/ux-web` `cx-platform/ux-tui` | | `cx-platform-workbench` | 1 | no | `cx-platform/workbench` | | `cx-platform-sso` | 1 | no | `cx-platform/sso` | | `cx-platform-flow` | 1 | no | `cx-platform/flow` | | `cx-core-data` | 1 | yes | `cx-core-data` | | `cx-core-code` | 57 | yes | `cx-stdlib/array` `cx-stdlib/authz` `cx-stdlib/bus` `cx-stdlib/bytes` `cx-stdlib/crypto` `cx-stdlib/csv` `cx-stdlib/cx` `cx-stdlib/diagram` `cx-stdlib/did` `cx-stdlib/email` `cx-stdlib/env` `cx-stdlib/format` `cx-stdlib/fp` `cx-stdlib/ft` `cx-stdlib/geo` `cx-stdlib/graphql` `cx-stdlib/hash` `cx-stdlib/html` `cx-stdlib/http-client` `cx-stdlib/i18n` `cx-stdlib/io` `cx-stdlib/json` `cx-stdlib/jsonrpc` `cx-stdlib/jsonschema` `cx-stdlib/locale` `cx-stdlib/log` `cx-stdlib/map` `cx-stdlib/math` `cx-stdlib/mime` `cx-stdlib/oidc` `cx-stdlib/path` `cx-stdlib/process` `cx-stdlib/prof` `cx-stdlib/random` `cx-stdlib/re` `cx-stdlib/saml` `cx-stdlib/sasl` `cx-stdlib/sched` `cx-stdlib/scim` `cx-stdlib/set` `cx-stdlib/similar` `cx-stdlib/soap` `cx-stdlib/strings` `cx-stdlib/supervise` `cx-stdlib/tar` `cx-stdlib/term` `cx-stdlib/test` `cx-stdlib/time` `cx-stdlib/url` `cx-stdlib/uuid` `cx-stdlib/validate` `cx-stdlib/vc` `cx-stdlib/ws` `cx-stdlib/xml` `cx-stdlib/xsp` `cx-stdlib/zip` `term` | | `cx-platform-db` | 3 | yes | `db` `sql` `redis` | | `cx-platform-net` | 4 | yes | `cx-platform/net` `cx-platform/http` `cx-platform/ftp` `cx-platform/sftp` | | `cx-platform-mail` | 2 | yes | `cx-platform/imap` `cx-platform/smtp` | | `cx-platform-store` | 4 | yes | `cx-platform/store` `cx-platform/journal` `cx-platform/live` `cx-platform/audit` | | `cx-platform-identity` | 4 | yes | `cx-platform/session` `cx-platform/authz-store` `cx-platform/did-web` `cx-platform/vc-revocation` | | `cx-platform-connector` | 2 | no | `cx-platform/connector` `cx-platform/sync` | | `cx-platform-secrets` | 1 | no | `cx-platform/secrets` | | `cx-platform-fabric` | 1 | yes | `cx-platform/fabric` | | `cx-platform-xsp` | 1 | yes | `cx-platform/xsp-auth` | | `cx-tooling` | 1 | no | `cx-tooling` | | `cx-platform-xap` | 1 | yes | `cx-platform/xap` | | `cx-registry` | 1 | no | `cx-registry` | | `cx-decisions` | 1 | no | `cx-decisions` | | `cx-binding-python` | 1 | no | `cx-binding-python` | | `cx-binding-go` | 1 | no | `cx-binding-go` | | `cx-binding-rust` | 1 | no | `cx-binding-rust` | | `cx-binding-v` | 1 | no | `cx-binding-v` | - `deps.cxd` is the ONE place a pin lives: one `[dep]` row per repository, its sha, the modules it provides, and the V-fork sha for a V repository (RULED: RS-7; the format is `spec/03-approved/process/repository-dependency-pins.md`). - `cx deps sync` — the binary's verb, which `make deps-sync` calls — fetches each pin shallowly into the ignored `deps//` and **fails** on a stale pin or a drifted checkout — it never warns; `cx deps sync --check` fetches nothing and refuses a checkout that is not at its pin. The program is `scripts/deps_sync.cx`, embedded in the binary (RULED: DEPSV-1). - The build compiles every V module from its pinned checkout through `-path`: `make build-vcx` runs the Ring-1 repository's own `vcx/` build (`deps/cx-core-code/vcx`) with the front door's `deps.cxd` and `deps/` root, so the binary is exactly the pinned set. - `cx` is a migration path, never a gate: a repository that releases is tried against the newest set that passes together; a pass advances its pin, a fail leaves it and files a finding on the repository that moved (RULED: RS-7, RS-8). ## 4. The registration hooks — the core names no product | Hook | Where | A product does | Decision | |---|---|---|---| | builtin dispatch | `vcx/code/stdlib_builtin_registry.v` (`register_stdlib_builtin(family, f)`) | registers its `_stdlib_builtin` from its own `init()`; a family registered twice panics naming both | RULED: D71a, RS-18 | | CLI verb table | `vcx/code/cli_verb_registry.v` (`register_cli_verb(v)`) | registers its verbs (`store-serve`, …) from its own `init()` | RULED: D73a | | the platform packs | `vcx/code/ring_registry.v` | registers its pack's dispatch entries from a `*_register.v` file of its own (`stdlib_session_register.v`, `sql_register.v`, …) | RULED: RS-18 | What the hooks buy: a binary built WITHOUT a product answers that product's names with the by-name refusal `CXER0136 E_NO_CALLABLE`, never a crash and never another family's answer — which is what the profile gate probes. ## 5. Two-repository modules — the ring decides, not the consumer A module whose pure V half is Ring 1 keeps that half in `cx-core-code` even when its only consumer is one product; its CX surface, spec and corpus live with the product (RULED: D76c, D84a, D78a): | Module | V half (Ring 1, `cx-core-code`) | CX surface, spec, corpus | |---|---|---| | `sasl` | `vcx/code/stdlib_sasl.v` | `cx-platform-mail` | | `ft` | `vcx/code/stdlib_ft.v` (the tokenizer `similar` calls) | `cx-platform-store` | | `diagram` | the V diagram half | `cx-tooling` | ## 6. Documentation follows the module Each repository generates its own reference fragment from its own corpus at release; `cx`'s build embeds the docs of exactly the module versions it compiles, and the one site in `cx` indexes every published fragment (`docs/llm/manifest-fragment.cxd` per repository; RULED: RS-9, RS-28). The component repositories stay thin: a README, CONTRIBUTING, the generated fragment (RULED: D58a). ## 7. The public front door and the private working tree The public `cx` is filtered from the private orchestration repository's passing heads by the extraction recipe: every `repo=cx` path except the orchestration set (runner evidence, per-tool scaffolding, the agent entry points), with the ledger and the process specs public because `cx`'s own gate reads them (RULED: D59a, D82a, D83a). A path's destination is its first-matching `[path]` rule in `registry/repos.cxd`; `repos-allocation-gate` refuses a tracked file no rule claims and a rule that claims nothing. # CX contributor front door — design: a surface before its code > **GENERATED FILE.** Source: `docs-src/llm/contributor-design.md.tmpl` and > `registry/modules.cxd`, assembled by `scripts/gen_docs/primer_build.cx`. > The two tables are projections; the procedure is the rules, each citing its > decision. Edit the template or the registry, never this file. - A surface is DECLARED before it is written: its decision states its ring, its group and its directories; its `registry/modules.cxd` row follows; then the spec, the corpus, the bundled source, the V code — in that order (RULED: OL-15, 1427-f). - The row is the single declaration. `make placement-gate` refuses a tree where the spec directory, the corpus directory and header, the code directory or the `[?lib]` namespace disagrees with it. - A structural change (tree, namespace, gates, ownership) is preceded by the full option table across code, spec, conformance, gates, docs, lockfile and tooling, on its issue, for the owner's decision — never executed one dimension at a time (RULED: OL-15). - Placement mistakes are the most expensive CX has made; the procedure below exists because each step was once skipped and cost a restructure. ## 1. The procedure — six artifacts, one order | Step | Artifact | Where (Ring 1 / platform group) | Refused by | |---|---|---|---| | 1 | the DECISION: ring, group, directories, the option table | the issue, then a `ledger/` page citing `RULED: ` | `spec-freeze-gate` (a later spec+impl commit needs the id) | | 2 | the ROW | `registry/modules.cxd` — `status=planned` until every artifact exists | `placement-gate` | | 3 | the SPEC | `spec/03-approved/stdlib/.md` / `spec/03-approved/platform/.md`, in the owning repository | `spec-freeze-gate`, `check-code-spec-consistency` | | 4 | the CORPUS — fixtures first | `conformance/stdlib/.cxd` / `conformance/platform/.cxd`, its header's `ring=` choosing the grader that runs it | the fixture graders; `check-fixture-shard-manifest` (a new file in no shard) | | 5 | the BUNDLED SOURCE with its `[module-doc]` | `stdlib/.cx`, namespace `cx-stdlib/` / `cx-platform/` | `guide-check` (fn-doc examples match the corpus), `stdlib-catalog-gate` | | 6 | the V CODE, only where CX cannot express it | `vcx/code/` (the rings) / the product's `vcx//` | `ring-import-gate`, `product-import-gate`, `check-no-stub-impl` | Then the catalog: `spec/03-approved/stdlib/README.md` or `spec/03-approved/platform/README.md` lists it, and the primer's module table picks it up from the row at the next `make docs` (RULED: 1427-f). ## 2. Deciding the ring and the group | Question about the module | Answer | Decision | |---|---|---| | Is it pure, or purely local (a file, the clock, a subprocess)? | Ring 1, no group, `cx-stdlib/` | RULED: RS-1, OL-14 | | Does it serve, or reach a store or a protocol? | `ring=1 group=platform`, `cx-platform/` | RULED: RS-1 | | Is it a data-format surface synthesized by the codec registry? | Ring 0, `kind=codec` | RULED: RS-1 | | Does a platform module have a pure codec or client a Ring-1 consumer needs? | declare it in the row's `half=`; at its first Ring-1 consumer it becomes its own Ring-1 module (as `http-client` split from `http`) | RULED: 1427-d, 1427-e | | Is its only consumer one product? | its CX surface moves to that product; a pure V half stays in the core | RULED: RS-5, D76c | | Is it a shared codec with callers outside one product? | it stays in the language (saml, scim, oidc, soap, graphql, ws, email, http-client, similar) | RULED: RS-5 | The rule under all of it: **a module lives in the ring of its highest verb** (RULED: OL-14). The rings are frozen and change only by decision; a `ring=2` row is refused by name (RULED: RS-1). ## 3. The row — `registry/modules.cxd` Generated by `scripts/gen_docs/contributor_facts.cx` from the attribute names `registry/modules.cxd`'s `[module]` rows carry, in first-use order (13 rows) — do not edit here: change the source, run `make docs`. | column | rows carrying it | its values (six or fewer), or how many | |---|---|---| | `name=` | 103 | 102 distinct values | | `ring=` | 103 | `1` `0` | | `ns=` | 103 | `cx-stdlib` `cx-platform` `none` | | `kind=` | 103 | `module` `natives` `backend` `design` `suite` `codec` | | `repo=` | 103 | 15 distinct values | | `spec=` | 103 | 101 distinct values | | `corpus=` | 103 | 99 distinct values | | `source=` | 103 | 92 distinct values | | `code=` | 103 | 77 distinct values | | `half=` | 103 | `none` `deps/cx-core-code/vcx/code/stdlib_time.v deps/cx-core-code/vcx/code/datetime_core.v` | | `status=` | 103 | `current` | | `group=` | 42 | `platform` | | `suite-ring=` | 5 | `1` | What the columns mean (the file's own `[doc]` block is the full text): - `ring` is the DECISION, not a location — it never takes a `-to`; what moves is the artifact. `group=platform` lets a module import the platform graph. - `repo=` names the repository that owns the artifacts when it is not the front door; its `spec`/`corpus`/`source`/`code` then name the pinned checkout `deps//…` (RULED: RS-7, RS-12). - `status=planned` is NOT WRITTEN YET: the check holds the row to its ring and directories but not to existence, and refuses it once every artifact is on disk — so the declaration cannot outlive the writing. - A `-to` is NOT MOVED YET: it names where a move puts the artifact and exists only while the move is owed. - `code` on a product's row is reached through the builtin-dispatch registry the product's own `init()` fills — never by name from `vcx/code` (RULED: D71a). ## 4. Where the rows live today Generated by `scripts/gen_docs/contributor_facts.cx` from `registry/modules.cxd`'s `[module]` rows, grouped by `repo=` (103 rows) — do not edit here: change the source, run `make docs`. | repository | rows | ring 0 | ring 1 | `group=platform` | `status=planned` | the rows (`name=`) | |---|---|---|---|---|---|---| | `cx-core-code` | 61 | 1 | 60 | 0 | 0 | authz, array, bus, bytes, crypto, csv, cx, decision, diagram, did, email, env, format, fp, ft, geo, graphql, hash, heap, pdf, image, html, http-client, i18n, io, json, jsonrpc, jsonschema, locale, log, map, math, mime, oidc, path, process, prof, random, re, saml, sasl, sched, scim, set, similar, soap, strings, supervise, tar, term, test, time, url, uuid, validate, vc, ws, xsp, zip, term, xml | | `cx-platform-store` | 6 | 0 | 0 | 6 | 0 | audit, journal, live, store, distributed_store, erasure_compliance | | `cx-platform-identity` | 4 | 0 | 0 | 4 | 0 | authz-store, did-web, session, vc-revocation | | `cx-platform-connector` | 2 | 0 | 0 | 2 | 0 | connector, sync | | `cx-platform-flow` | 1 | 0 | 0 | 1 | 0 | flow | | `cx-platform-fabric` | 1 | 0 | 0 | 1 | 0 | fabric | | `cx-platform-net` | 4 | 0 | 0 | 4 | 0 | ftp, http, net, sftp | | `cx-platform-mail` | 2 | 0 | 0 | 2 | 0 | imap, smtp | | `cx-platform-secrets` | 1 | 0 | 0 | 1 | 0 | secrets | | `cx-platform-sso` | 1 | 0 | 0 | 1 | 0 | sso | | `cx-platform-xap` | 5 | 0 | 0 | 5 | 0 | xap, xap_dist, xap-compose, xap-schema, xap-on | | `cx-platform-xsp` | 1 | 0 | 0 | 1 | 0 | xsp-auth | | `cx-platform-agent` | 8 | 0 | 0 | 8 | 0 | a2a, a2a-xap, adjudicate, llm, mcp, mcp-server, run, tools | | `cx-platform-ux` | 3 | 0 | 0 | 3 | 0 | ux, ux-tui, ux-web | | `cx-platform-db` | 3 | 0 | 0 | 3 | 0 | redis, sql, db | ## 5. Writing the spec - The spec is the only truth; there are no ADRs and no design notes that override it (AGENTS.md rule 1, RULED: CFG-1; `check-no-adr-citations`). - A decision's design item may ADD spec text where its edit map is silent; it may never move or delete a sentence the map does not name (RULED: RS-21). - Specs stay loosely coupled: a generated document carries no `§` numbers into another page's prose (the primer drops them from the directive registry for this reason; the owner's 1a of 2026-08-19, `ledger/rulings_2026_08_19_787_guide_and_packaging.md`). - An effect is deny-by-default and named by the capability that grants it; an error is a value with a registered `CXER` code (`cxer-registry-gate` holds the registry to `spec/03-approved/process/governance.md`). Both are pinned by corpus cases in `contributor.md` §2. A surface that throws, or that reaches an effect with no capability, is a design defect before it is a code defect. - A normative sentence has a corpus case beside it before any code moves: the case is how a later reader knows the sentence still holds. ## 6. The option table — what a structural decision shows Every structural change is posed on its issue as the full option table, for the owner's decision, before anything moves (RULED: OL-15); each option states its long-term consequence for CX. The table covers every dimension at once: | Dimension | What the option must say | |---|---| | code | which V module and directory; the imports it adds; the registration hook it uses | | spec | which page, which section, added or changed sentences | | conformance | which corpus file, which grader (`ring=`), which shard | | gates | which step refuses the wrong tree, and whether one is added (`check-selection-manifest` then) | | docs | which generated document moves (`make docs` in the same commit) | | lockfile / pins | which `deps.cxd` row or `[module]` row changes | | tooling | editors, completions, the migration tool (`cx --migrate-namespace`) | # CX contributor front door — implementation > **GENERATED FILE.** Source: `docs-src/llm/contributor-implementation.md.tmpl`, > assembled by `scripts/gen_docs/primer_build.cx`. This file is rules only; > each cites its decision and the step that refuses the tree that breaks it. > The facts it relies on are projected in `contributor-architecture.md`. > Edit the template, never this file. - Write CX first. The implementation language of the evaluator and the products is V; everything else — scripts, gates, generators, probes — is a CX program run with `cx FILE` (RULED: CXF-1). - **A V module is one directory**, and V compiles it as one unit: a file move across directories is an interface change, never a rename (RULED: RS-24). - **Imports point down only**: Ring 0 imports nothing above it; Ring 1 imports Ring 0; a product imports the rings and exactly the products its row pins (RULED: RS-1, RS-24). - **`pub` is per caller**: a symbol is exported when a caller in another module needs it, and the product's `pub` surface IS its interface (RULED: RS-24). - The core names no product: a product plugs in through the registration hooks from its own `init()` (RULED: D71a, D73a). ## 1. Where V code goes | The code is | Directory (in its repository) | `module` | It may import | |---|---|---|---| | Ring 0 — the reader, writer, canonical form, codecs | `vcx/cx/` (`cx-core-data`) | `cx` | vlib only | | Ring 1 — the evaluator, the stdlib natives, a platform module's declared pure half | `vcx/code/` (`cx-core-code`) | `code` | `cx`, vlib | | the program half of the corpus grading core (`cx corpus`, the fixture shards) | `vcx/corpus/` (`cx-core-code`) | `corpus` | `cx`, `code`, `fixtures`, vlib | | a V product (store, net, mail, identity, db, fabric, xsp, xap) | `vcx//` (its product repository) | `` | `cx`, `code`, vlib, and the V modules of the repositories its row pins | | the front door's own verbs | `vcx/cmd/` (`cx`) | `main` | whatever the assembled build provides | The `vmodule=` and `pins=` of each product are in the repository map (`contributor-architecture.md` §2). `product-import-gate` refuses an import a product's pins do not name; `ring-import-gate` refuses a ring importing upward (RULED: RS-24, VC-22). ## 2. The rules for a V change | # | Rule | Decision | Held by | |---|---|---|---| | 1 | A behaviour change lands its failing corpus case FIRST, in its own commit; the code that passes it follows. | AGENTS.md rule 2 (RULED: CFG-1) | the fixture graders | | 2 | No stub, no placeholder success, no seam with no live consumer. An effectful primitive that cannot be finished refuses; it never returns a synthetic shape. | AGENTS.md rule 3 (RULED: CFG-1) | `check-no-stub-impl` | | 3 | Errors travel as `[err code=cx-err:CXERnnnn …]` values with a registered code; an effect is refused with the capability that would allow it. | the CXER registry, `spec/03-approved/process/governance.md`; pinned by the corpus cases in `contributor.md` §2 | `cxer-registry-gate`, the corpus | | 4 | Move a file across modules only with its imports and its `pub` surface; a bare same-module call that crosses a repository line cannot compile alone. | RULED: RS-24 | `product-import-gate`, each product's own build | | 5 | A product registers its builtins with `register_stdlib_builtin(family, f)` and its CLI verbs with `register_cli_verb(v)` from its own `init()`. A family registered twice panics naming both. | RULED: D71a, D73a | the profile builds (a binary without the product refuses its names `CXER0136`) | | 6 | A pure Ring-1 V half stays in `vcx/code` even when one product is its only caller; its row names it in `half=` or re-allocates it to the core. | RULED: D76c, D84a | `placement-gate`, `repos-allocation-gate` | | 7 | A long-lived evaluator (a grader, a server loop) closes a finished env before the next program: per-case cost stays flat under `-gc e`. | RULED: RS-17 | review; the step timings `check-verification-budget` judges | | 8 | The V compiler is a pinned fork; a fork change lives in the fork's own repository and gets a register row. Never edit `third_party/`. | RULED: VC-1 | `check-v-fork` | | 9 | No version literal in code, a comment or a spec sentence: derive it from `VERSION`. | AGENTS.md rule 5 (RULED: CFG-1) | `check-version-consistency` | | 10 | A mover fixes every reader it missed — path scans, doc links, selection examples, baselines — in the same branch. | RULED: RS-8 | the post-merge union | Two operational rules from `CONTRIBUTING.md` (practice, not decisions): a new standalone `*_test.v` links the whole module graph on every run, so add test functions to the umbrella that owns the area (`contributor-test.md` §3); and the dev build accepts shapes the `-prod` checker refuses, so `check-prod-build` runs on every change set. ## 3. The front door's build, from the pins The front door compiles nothing of its own but its verbs and grader; the binaries are assembled from the pinned checkouts: ```console $ git submodule update --init --recursive # the V fork and re2, at their gitlinks $ make -C third_party/v # the fork builds itself $ make deps-sync CX_BIN= # every deps.cxd pin into deps//, refusing a stale or drifted one $ make build-vcx # deps/cx-core-code/vcx's build over the front door's deps.cxd and deps/ $ deps/cx-core-code/vcx/target/cx --version # names the commit and the V fork it was built from $ deps/cx-core-code/vcx/target/cx deps sync --check # every checkout at its pin, nothing fetched ``` - `make deps-sync` is a thin caller of the binary's verb `cx deps sync [--check] [--verbose]`, which runs `scripts/deps_sync.cx` embedded in it; a `cx` older than the verb runs the checkout's file instead and says so, and a box with no `cx` at all seeds `deps/` with `scripts/deps_bootstrap.sh` first (RULED: DEPSV-1). - The build passes the pinned checkouts to V through `-path`; a direct `v test` of anything importing `cx` needs the same flags, printed by `make -s -C deps/cx-core-code/vcx print-VFLAGS`. - Four builds share one source: `data`, `embed`, `cli`, `platform` (`make build-vcx` is the platform build). A `ring=1` corpus case is graded in every profile, so a Ring-1 case may not lean on a platform pack (RULED: RS-1; RS-23 is the precedent). - A `deps//` checkout is a build INPUT, never a place to edit: a change a product needs is made in that product's repository and reaches the front door as a pin bump; a component repository's change is its own branch there, pinned by sha in the front door's branch, and the union grades both (RULED: RS-7, RS-36). ## 4. Tooling in CX - A new script is a CX program (`scripts/.cx`) run with `cx FILE`, imported by path (`[?lib './scripts/.cx' as=x]`) when another program needs it. Keep the logic PURE — text in, verdict out — and put the reads, writes and exit in the program that calls it (`scripts/deps_pins.cx` is the shape) (RULED: CXF-1). - Another language for any work needs a filed `cx-gap` issue — the work, the CX attempt, the gap by kind — BEFORE it runs, for a probe as much as for a checked-in tool (RULED: CXF-1). - A CX surprise — a silent wrong answer, a lint that passes what the run refuses, a diagnostic that points elsewhere — is filed the same hour with its reproduction (RULED: CXF-8). - A script that reads or writes the corpus is graded by `reader-parity` against the other readers of the same files (RULED: CXF-5). - A check that derives its population from the tree refuses to vouch when the derivation comes up empty or short — a check that runs over the empty set passes forever (`check-selection-manifest` and this layer's generators hold a row floor for that reason; RULED: 1350-b). # CX contributor front door — test > **GENERATED FILE.** Source: `docs-src/llm/contributor-test.md.tmpl`, the > Makefile's `TEST_TARGETS` and `DOC_TARGETS`, and the umbrella test files of > the pinned checkouts, assembled by `scripts/gen_docs/primer_build.cx`. The > two tables are projections; the procedure is the rules, each citing its > decision. Edit the template or the Makefile, never this file. - The corpus is the executable truth: a behaviour is a `[case …]` in a `.cxd` file, graded against the binary; a fix lands its failing case first (AGENTS.md rule 2, RULED: CFG-1). - `cx corpus FILE.cxd` grades any corpus file with the binary alone — the same PASS/FAIL lines and summary as the in-tree graders (RULED: RS-16). - Run what your change selects, not everything: `scripts/test_changed.sh --dry-run` prints the steps whose inputs your diff touches; the full `make test` union is the post-merge run's (RULED: RUN-1, INT-5). - A step that reads a file the tree derives it from refuses to vouch for an empty derivation; a check over the empty set would pass forever (RULED: 1350-b). ## 1. Grading the corpus ```console $ cx corpus conformance/llm/antipatterns.cxd # one file, this binary, its PASS/FAIL lines and summary $ cx corpus --quiet deps/cx-core-code/conformance/stdlib/json.cxd $ make fixtures # every module corpus, sharded, one census line $ make fixtures FIXTURE_FILES=" …" # just the files your branch touched ``` - A case is judged on `[out-text]`, `[out-multiset]` or `[out-err]` under its own `grant=`, `argv=`, `tol=`; a case naming a pack this profile does not carry is SKIPPED by name, never graded (`cx corpus --help`). - A case id in `conformance/code.cxd` uses a prefix registered for its `level=` in `scripts/check_code_fixtures.cx`; `check-code-fixtures` grades that file alone. - A new `.cxd` belongs to a shard (`fixture_shards.cxd`) or `check-fixture-shard-manifest` refuses it. - A `ring=1` case is graded at EVERY profile, including the ones without the platform packs; read a response's status as `$r@status`. - A changed fixture that a generated document cites carries its `make docs` in the same commit — `docs-check` replays every cited case. ## 2. Choosing the steps — the computed selection | Situation | What runs pre-merge | Decision | |---|---|---| | the diff touches ordinary inputs | `scripts/test_changed.sh --dry-run` → the steps it lists, and the `test-vcx-suite` files it names | RULED: RUN-1 | | the diff touches the Makefile, `vcx/Makefile`, `scripts/`, `VERSION` or `devbox*` (ESCALATED) | the COMPUTED selection: the diff MINUS those paths as a `--changed-files` list, each selected step as its own row; the escalated union is the post-merge run's, never queued pre-merge | RULED: RUN-4, INT-5 | | the diff adds or edits a `scripts/*.cx` | `make reader-parity` as an own step (the computed selection strips `scripts/` before it is asked) | RULED: CXF-5 | | the diff adds a `TEST_TARGETS` step | `make check-selection-manifest`: every step has an input row in `scripts/test_changed.sh` | RULED: RUN-1 | | the diff touches only documentation | the doc pipeline (`make test-docs`, the steps marked below) is the post-merge run's; your own steps are unchanged | RULED: INT-10 | | a step fails at the same commit with no code change | a flaky test: a known one is retried once; a new one gets a flaky-test issue — never a loosened bound or a skipped case | RULED: DG-1 | A failed post-merge run names EVERY failed step (`make test` runs its storm with `-k`), so the fix for a failed head carries every class the log names (RULED: RUN-2). ## 3. The umbrellas — where a test function goes Test files, not functions, are the compile cost: every `*_test.v` links the whole module graph. A new test is a function in the umbrella that owns the area; the roster is `scripts/consolidation/.files` and `scripts/consolidate_tests.sh absorb ` folds a stray file in. Generated by `scripts/gen_docs/contributor_facts.cx` from the `*_umbrella_test.v` files in the front door and its pinned checkouts (16 rows) — do not edit here: change the source, run `make docs`. | umbrella (path in its repository) | repository | |---|---| | `vcx/tests/async_conc_umbrella_test.v` | `cx-core-code` | | `vcx/tests/canonical_umbrella_test.v` | `cx-core-code` | | `vcx/tests/cli_umbrella_test.v` | `cx-core-code` | | `vcx/tests/code_units_umbrella_test.v` | `cx-core-code` | | `vcx/tests/diagram_umbrella_test.v` | `cx-core-code` | | `vcx/tests/lint_lsp_umbrella_test.v` | `cx-core-code` | | `vcx/tests/parser_units_umbrella_test.v` | `cx-core-code` | | `vcx/tests/table_stream_umbrella_test.v` | `cx-core-code` | | `vcx/code/code_module_umbrella_test.v` | `cx-core-code` | | `vcx/tests/http_umbrella_test.v` | `cx-platform-net` | | `vcx/tests/net_umbrella_test.v` | `cx-platform-net` | | `vcx/tests/real_services_umbrella_test.v` | `cx-platform-net` | | `vcx/tests/store_core_umbrella_test.v` | `cx-platform-store` | | `vcx/tests/store_remote_umbrella_test.v` | `cx-platform-store` | | `vcx/tests/fabric_umbrella_test.v` | `cx-platform-fabric` | | `vcx/tests/xap_umbrella_test.v` | `cx-platform-xap` | A Ring-0 change (lexer, parser, evaluator, emitter, loader) runs, as its own steps, every umbrella that reads a module it touched (`grep -l '' deps/*/vcx/tests/*_umbrella_test.v`) and those modules' `vcx/cx/*_test.v` (RULED: VERIFY-2); a code change runs its umbrellas the same way, with `make check-effect-alignment`. ## 4. The census trio — a corpus file that gains or loses cases A branch that adds or removes cases in a top-level `conformance/*.cxd` moves three baselines together, deliberately, naming each id: | Baseline | Step | What moves it | |---|---|---| | the parser's full-corpus census | `vcx/tests/parser_units_umbrella_test.v` (its `cxparse_full_corpus_diff` section) | every new `in-cx` input — bump its pinned count, naming the ids | | the headline rebless scope | `vcx/tests/cxparse_headline_rebless_scan_test.v` | regenerate with `CX_REBLESS_SCAN_WRITE=1`, commit the scope file | | the Tier-2 address baseline | `make address-baseline-gate` | an extraction or a case removal: `make address-baseline-capture`, the ids named in the commit | The ids are named because a count that moves silently hides a case that was lost with one that was added (RULED: RS-12). ## 5. The two runners | Runner | Runs | Holds | |---|---|---| | pre-merge — the branch's own | its builds, lints, graders, umbrellas and docs checks, under its own `CX_BUILD_SLOT` through `scripts/build_slot.cx`; load-sensitive steps (memory gauges, bench, real-socket tests) one at a time on the shared slot | the branch's own tree: every own step passed once on the final tree before merge (RULED: INT-8, OL-3) | | post-merge — the loop on each new head | `make test-changed BASE=`, escalating to the full union on an infra change; the full union daily on an idle runner | the integration branch: a failed run gets a fix, never a revert of the gate (RULED: RUN-1, RUN-3) | Read a status directly, never through an inner shell that `devbox run` wraps — that shape answers 0 for a program that failed; `check-exit-status-probe` holds the line (AGENTS.md, "Exit statuses"; RULED: CFG-1). ## 6. The step roster The union `make test` runs, in order. A step marked in the doc pipeline also runs in `make test-docs` (RULED: INT-10); the third column is where to start reading what the step checks. Generated by `scripts/gen_docs/contributor_facts.cx` from the Makefile's `TEST_TARGETS` (in order), `DOC_TARGETS`, the targets `docs-src/flow/docs.flow.cx` names, and each step's own rule (136 rows) — do not edit here: change the source, run `make docs`. | # | step | in the doc pipeline | the first program its recipe names | |---|---|---|---| | 1 | `docs-voice-check` | yes, in the docs flow | `scripts/gen_site/docs_voice_check.cx` | | 2 | `docs-vocabulary-check` | | `scripts/gen_site/docs_vocabulary_check.cx` | | 3 | `nav-shape-check` | | `scripts/gen_guide/nav_shape_check.cx` | | 4 | `ring-svg-check` | yes, in the docs flow | `scripts/gen_site/ring_svg.cx` | | 5 | `check-no-ai-attribution` | | `scripts/check_no_ai_attribution.cx` | | 6 | `check-public-history-replace` | | `scripts/public_history_replace_selftest.cx` | | 7 | `check-vcache-soundness` | | `scripts/vcache_soundness_memo.cx` | | 8 | `check-vcache-soundness-selftest` | | `scripts/vcache_soundness_memo_selftest.cx` | | 9 | `check-build-failure-classifier` | | `scripts/classify_v_build_failure.cx` | | 10 | `test-vcx-timing` | | `v test deps/cx-core-code/vcx/timing/` | | 11 | `check-conformance-coverage` | | `scripts/check_conformance_coverage.cx` | | 12 | `check-shim-archives` | | `scripts/check_archive_alignment.cx` | | 13 | `abi-c-test` | | — | | 14 | `check-v-fork` | | `scripts/check_v_fork_patches.cx` | | 15 | `check-portable-links` | | `scripts/check_portable_links.cx` | | 16 | `check-release-asset-links` | | `scripts/release_asset_links_gate.cx` | | 17 | `check-serial-retry-rosters` | | — | | 18 | `check-fixture-shard-manifest` | | `scripts/check_fixture_shard_manifest.cx` | | 19 | `check-consolidation-manifests` | | `scripts/consolidate_tests.sh` | | 20 | `check-bare-head-calls` | | `scripts/bare_head_calls_selftest.cx` | | 21 | `test-vcx-suite` | | `scripts/fixtures_census.cx` | | 22 | `test-vcx-code` | | — | | 23 | `test-vcx-cmd` | | `v test vcx/cmd/` | | 24 | `test-vcx-cx` | | `v test deps/cx-core-data/vcx/fixtures/` | | 25 | `test-vcx-conform` | | `make -C deps/cx-core-code/vcx conform-all` | | 26 | `test-vcx-columnar` | | `make -C deps/cx-core-code/vcx arrow-shim` | | 27 | `test-vcx-sqlite` | | `v test deps/cx-platform-store/vcx/store/store_sqlite_test.v` | | 28 | `check-prod-build` | | `make -C deps/cx-core-code/vcx check-prod` | | 29 | `check-no-legacy-try` | | `scripts/check_no_legacy_try.cx` | | 30 | `check-pipefail-pipes` | | `scripts/pipefail_pipe_gate.cx` | | 31 | `check-exec-redirect` | | `scripts/exec_redirect_gate.cx` | | 32 | `check-exit-status-probe` | | `scripts/exit_status_probe_gate.cx` | | 33 | `check-build-slot` | | `scripts/build_slot_selftest.cx` | | 34 | `check-bench-isolation` | | `scripts/bench_isolation_selftest.cx` | | 35 | `check-no-infix-range` | | `scripts/check_no_infix_range.cx` | | 36 | `check-no-cxl-token` | | `scripts/check_no_cxl_token.cx` | | 37 | `listener-bind-gate` | | `scripts/listener_bind_gate.cx` | | 38 | `check-no-consumer-terms` | | `scripts/check_no_consumer_terms.cx` | | 39 | `secrets-scan` | | `scripts/secrets_scan.cx` | | 40 | `check-version-consistency` | yes | `scripts/check_version_consistency.cx` | | 41 | `check-effect-alignment` | | `v test deps/cx-core-code/vcx/tests/code_units_umbrella_test.v` | | 42 | `check-null-absence-conflation` | | `scripts/check_null_absence_conflation.cx` | | 43 | `check-docs-tier1-guardrail` | | `scripts/check_docs_tier1_guardrail.cx` | | 44 | `check-no-adr-citations` | | `scripts/check_no_adr_citations.cx` | | 45 | `check-no-stub-impl` | | `scripts/check_no_stub_impl.cx` | | 46 | `check-completions-drift` | | `scripts/check_completions_drift.cx` | | 47 | `check-editor-surface-parity` | | `scripts/check_editor_surface_parity.cx` | | 48 | `guide-check` | yes, in the docs flow | `scripts/gen_guide/stdlib_docs_check.cx` | | 49 | `guide-snippets-check` | | `scripts/gen_guide/snippet_check.cx` | | 50 | `guide-render-gate` | | `scripts/gen_guide/theme_check.cx` | | 51 | `site-check` | yes, in the docs flow | `scripts/gen_site/site_assemble.cx` | | 52 | `directive-docs-check` | | `scripts/gen_guide/directive_docs_check.cx` | | 53 | `verify-doc-blocks` | yes, in the docs flow | `tools/verify-doc-blocks.sh` | | 54 | `verify-doc-links` | yes, in the docs flow | `tools/verify-doc-links.sh` | | 55 | `verify-examples` | | `tools/verify-examples.sh` | | 56 | `verify-playground-examples` | | `scripts/gen_guide/playground/gen_examples.cx` | | 57 | `docs-check` | yes, in the docs flow | `scripts/gen_docs/primer_build.cx` | | 58 | `primer-platform-check` | yes, in the docs flow | `scripts/gen_docs/primer_platform.cx` | | 59 | `ring-import-gate` | | `scripts/ring_import_gate.cx` | | 60 | `gates-manifest-gate` | | `scripts/gates_manifest_gate.cx` | | 61 | `advisory-audit` | | `scripts/advisory_audit_check.cx` | | 62 | `property-gate` | | `scripts/property_gate.cx` | | 63 | `spec-invariant-gate` | | `scripts/spec_invariants.cx` | | 64 | `ring-tag-gate` | | `scripts/ring_query.cx` | | 65 | `cxer-registry-gate` | | `scripts/cxer_registry_report.cx` | | 66 | `diagnostics-census-check` | | `scripts/diagnostics_census_check.cx` | | 67 | `spec-freeze-gate` | yes | `scripts/spec_freeze_gate.cx` | | 68 | `test-extraction-gate` | | — | | 69 | `abi-gc-gate` | | — | | 70 | `libcx-abi-gate` | | `tools/libcx-abi-gate.sh` | | 71 | `test-profile-gate` | | `make -C deps/cx-core-code/vcx test-profile-gate` | | 72 | `check-code-spec-consistency` | | `scripts/check_code_spec_consistency.cx` | | 73 | `check-code-fixtures` | | `scripts/check_code_fixtures.cx` | | 74 | `reader-parity` | | `v test vcx/tests/reader_parity_test.v` | | 75 | `stdlib-catalog-gate` | | `scripts/stdlib_catalog_gate.cx` | | 76 | `placement-gate` | | `scripts/placement_gate.cx` | | 77 | `repos-allocation-gate` | | `scripts/repos_allocation_gate.cx` | | 78 | `product-import-gate` | | `scripts/product_import_gate.cx` | | 79 | `test-deps-pins` | | `scripts/check_deps_pins_fixtures.cx` | | 80 | `check-deps-bootstrap-token` | | `scripts/deps_bootstrap.sh` | | 81 | `test-bundle-sources` | | `scripts/check_bundle_sources_fixtures.cx` | | 82 | `test-docs-fragment` | | `scripts/check_docs_fragment_fixtures.cx` | | 83 | `test-migrate-namespace` | | `scripts/check_migrate_namespace_fixtures.cx` | | 84 | `store-session-dep-gate` | | `scripts/store_session_dep_gate.cx` | | 85 | `flow-vocabulary-gate` | | `deps/cx-platform-flow/scripts/flow_vocabulary_gate.cx` | | 86 | `flow-dogfood-gate` | | `deps/cx-platform-flow/scripts/flow_dogfood_gate.cx` | | 87 | `docs-flow-gate` | yes | `scripts/ci_flow_gate.cx` | | 88 | `release-flow-gate` | | `scripts/ci_flow_gate.cx` | | 89 | `test-flow-umbrella` | | — | | 90 | `address-baseline-gate` | | `v run deps/cx-core-code/vcx/tests/runners/address_baseline/address_baseline.v` | | 91 | `tools-export-gate` | | — | | 92 | `test-code-diagram` | | `scripts/check_code_diagram_fixtures.cx` | | 93 | `test-playground-mermaid` | | `scripts/test_playground_mermaid.mjs` | | 94 | `test-playground-nav` | | `scripts/test_playground_nav.mjs` | | 95 | `test-playground-search` | | `scripts/test_playground_search.mjs` | | 96 | `test-playground-readings` | | `scripts/gen_guide/playground/readings_check.cx` | | 97 | `test-oriel` | | `deps/cx-platform-ux/scripts/oriel_real.sh` | | 98 | `test-agent-real` | | — | | 99 | `test-connector-real` | | — | | 100 | `test-xap-umbrella` | | — | | 101 | `test-fabric-umbrella` | | — | | 102 | `test-mail-real` | | — | | 103 | `test-net-real` | | — | | 104 | `test-web-component-offline` | | `scripts/test_web_component_offline.cx` | | 105 | `bench-code-pattern-compile` | | — | | 106 | `test-db-real` | | — | | 107 | `test-sso-interop` | | `deps/cx-platform-sso/scripts/sso_interop.sh` | | 108 | `test-xpath-parity-cx` | | `scripts/check_xpath_parity_fixtures.cx` | | 109 | `corpus-audit` | | `scripts/corpus_audit.cx` | | 110 | `repr-guard` | | `bench/repr/run.cx` | | 111 | `repr-guard-selftest` | | `scripts/repr_guard_selftest.cx` | | 112 | `check-inmodule-test-roster` | | — | | 113 | `check-build-input-roster` | | `make -C deps/cx-core-code/vcx check-build-input-roster` | | 114 | `check-sync-cmd-split` | | `scripts/sync_cmd_split_selftest.cx` | | 115 | `check-gitconfig-home` | | `scripts/gitconfig_home_selftest.cx` | | 116 | `check-selection-manifest` | | `scripts/check_selection_manifest.cx` | | 117 | `fmt-sweep-gate` | | `scripts/fmt_corpus_sweep.cx` | | 118 | `test-playground-wasm-traps` | | `scripts/test_playground_wasm_traps.mjs` | | 119 | `test-site-phone-width` | | `scripts/test_site_phone_width.mjs` | | 120 | `test-playground-primer` | | `scripts/test_playground_primer.mjs` | | 121 | `site-live-check` | | `scripts/site_live_check.cx` | | 122 | `site-links-selftest` | | `scripts/gen_site/check_external_links.cx` | | 123 | `issue-label-gate` | | `scripts/issue_label_gate.cx` | | 124 | `conformance-suites` | | `scripts/conformance_suites.cx` | | 125 | `ledger-index-check` | yes | `scripts/ledger_index.cx` | | 126 | `ledger-edit-map-check` | yes | `scripts/ledger_edit_map_check.cx` | | 127 | `check-profile-gate-selection` | | `scripts/profile_gate_selection_selftest.cx` | | 128 | `check-verification-budget` | | `scripts/check_verification_budget.cx` | | 129 | `check-verification-budget-selftest` | | `scripts/verification_budget_selftest.cx` | | 130 | `check-perf-settle-selftest` | | `scripts/perf_settle_selftest.cx` | | 131 | `premerge-record` | | `scripts/premerge_record.cx` | | 132 | `check-storm-keep-going` | | `scripts/storm_keep_going_selftest.cx` | | 133 | `check-verification-timings` | | `scripts/verification_timings_selftest.cx` | | 134 | `check-bench-runners-compile` | | `bench/probe` | | 135 | `check-flow-doc-lines` | | `scripts/check_flow_doc_lines_selftest.cx` | | 136 | `perf-pair` | | `scripts/compare_bench.cx` | # Reference: the platform, bindings and ecosystem groups — v0.18.0-pre.1 > **GENERATED.** Source: `docs-src/llm/reference-platform.md.tmpl` + the > conformance corpus. Every output was re-recorded from the `cx` v0.18.0-pre.1 > binary. Read `primer.md` first. ## The platform group The platform group is where data outlives a process and where more than one party is involved: the store and its journal, the session layer, the HTTP surface, the XAP host, the fabric, database drivers. **The platform group is reached over a network, not embedded.** The store engine imports Ring 0 only and contains no evaluator, so running a platform does not drag a server into every program that merely reads data. That is the property that keeps Rings 0 and 1 auditable, and it is enforced by the build, not by convention. ### The store — content-addressed documents `open` takes a URL. `mem://` is the in-process substrate and needs no capability at all, which makes the whole functional matrix runnable in a plain `cx` invocation: `prog.cx` ```cx [?lib 'cx-platform/store'] [?let [= $s [$store:open "mem://"]] [= $h [$store:put-doc $s [doc [item "hello"]]]] [$store:get-doc $s $h]] ``` ```console $ cx prog.cx [doc [item hello]] ``` A handle is derived from the document's canonical bytes, so putting the same document twice yields the same handle — identity is a property of content, not of insertion order: `prog.cx` ```cx [?lib 'cx-platform/store'] [?let [= $s [$store:open "mem://"]] [= $h1 [$store:put-doc $s [doc [item "hello"]]]] [= $h2 [$store:put-doc $s [doc [item "hello"]]]] [= $h1 $h2]] ``` ```console $ cx prog.cx true ``` Two different documents cannot collide into one handle: `prog.cx` ```cx [?lib 'cx-platform/store'] [?let [= $s [$store:open "mem://"]] [= $h1 [$store:put-doc $s [doc [item "a"]]]] [= $h2 [$store:put-doc $s [doc [item "b"]]]] [= $h1 $h2]] ``` ```console $ cx prog.cx false ``` Absence and refusal are values, not exceptions. A missing handle, a malformed handle, and a handle naming an unknown hash algorithm are three distinct answers: `prog.cx` ```cx [?lib 'cx-platform/store'] [?let [= $s [$store:open "mem://"]] [$store:get-doc $s "sha2-256:0000000000000000000000000000000000000000000000000000000000000000"]] ``` ```console $ cx prog.cx [err code=cx-err:CXER1121 message='E_STORE_NOT_FOUND: sha2-256:0000000000000000000000000000000000000000000000000000000000000000' at='2:37'] ``` `prog.cx` ```cx [?lib 'cx-platform/store'] [?let [= $s [$store:open "mem://"]] [$store:get-doc $s "deadbeef"]] ``` ```console $ cx prog.cx [err code=cx-err:CXER0130 message='E_STORE_ADDRESS_INVALID: bare hex is not an address — tagged form `:` required since I1 (e.g. `sha2-256:…`) (cx-err:CXER0130)' at='2:37'] ``` Aliases give a mutable name over immutable content: `prog.cx` ```cx [?lib 'cx-platform/store'] [?let [= $s [$store:open "mem://"]] [= $h [$store:put-doc $s [doc [item "hello"]]]] [= $a [$store:set-alias $s "latest" $h]] [$store:get-alias $s "latest"]] ``` ```console $ cx prog.cx 'sha2-256:6d8460dd10c5fb09c542c81d617efef1eba054a71755f4e1c96a6af7830ddeeb' ``` A read-only store refuses writes rather than accepting and dropping them: `prog.cx` ```cx [?lib 'cx-platform/store'] [?let [= $s [$store:open-opts "mem://" [map read-only="true"]]] [$store:put-doc $s [doc [item "x"]]]] ``` ```console $ cx prog.cx [err code=cx-err:CXER1110 message='E_STORE_READ_ONLY: mem://' at='2:65'] ``` ### Writes are an effect boundary — errors do not leave silently A store *document* write externalizes. It refuses to carry a document containing an `[err]` at any depth out of the program: `prog.cx` ```cx [?lib 'cx-platform/store'] [?let [= $s [$store:open "mem://"]] [= $rows ([row [v 2.5]], [err code=cx-err:CXER0100 message="a refusal at rest"], [row [v 4.5]])] [$store:put-doc $s [report [count [$count $rows]] [?splice $rows]]]] ``` ```console $ cx prog.cx [err code=cx-err:CXER0275 message='E_ERR_AT_BOUNDARY: refusing the store document write — the document contains cx-err:CXER0100 at /report/err; a refusal must not leave the program as silent data (pass errs=:permit on the effect to externalize it deliberately)' err-path='/report/err' err-code=cx-err:CXER0100] ``` The guarded verbs are `put-doc`, `put-doc-stream`, `put-doc-text` (it stores a *parsed* document) and `modify-doc` (its action payload is the injection vehicle). Blob and byte writes are exempt by construction — bytes carry no `[err]` to find. Storing a refusal *deliberately* is legal and explicit: `prog.cx` ```cx [?lib 'cx-platform/store'] [?let [= $s [$store:open "mem://"]] [= $h [$store:put-doc $s [report [err code=cx-err:CXER0100 message="stored deliberately"]] {errs: :permit}]] [$store:get-doc $s $h]] ``` ```console $ cx prog.cx [report [err code=cx-err:CXER0100 message='stored deliberately']] ``` `prog.cx` ```cx [?lib 'cx-platform/store'] [?let [= $s [$store:open "mem://"]] [= $h [$store:put-doc $s [doc [item "x"]]]] [$store:modify-doc $s $h [append [err code=cx-err:CXER0100 message="injected"]]]] ``` ```console $ cx prog.cx [err code=cx-err:CXER0275 message='E_ERR_AT_BOUNDARY: refusing the store document write — the document contains cx-err:CXER0100 at /append/err; a refusal must not leave the program as silent data (pass errs=:permit on the effect to externalize it deliberately)' err-path='/append/err' err-code=cx-err:CXER0100] ``` The same rule governs HTTP response emission, with the refusal as a loud 500. `$journal:append` and `$fabric:emit` are deliberately **not** guarded: a journal is exactly where error events belong, and fabric emit is channel-shaped fan-out. ### The journal — history you can replay The journal is an append-only chain: each entry links the previous one, so the history is verifiable rather than merely stored. `prog.cx` ```cx [?lib 'cx-platform/journal'] [?let [= $j [$journal:open "mem://acme" "acme"]] [$journal:append $j [do :refund] {actor: "ops" authority: "d-77"}]] ``` ```console $ cx prog.cx [entry seq=1 tenant=acme actor=ops authority=d-77 ts='1970-01-01T00:00:01Z' prev-hash='genesis:' payload='sha2-256:1c28ab1fd422c356d61afc4be98fe2ff836d21b306487a4d67a62b40fc762ec5' hash='sha2-256:902dfe8cf83616e030e70ec30429c632601d4bd29308b757049706a3afb218a4' [event [do :refund]]] ``` `prog.cx` ```cx [?lib 'cx-platform/journal'] [?let [= $j [$journal:open "mem://t" "acme"]] [= $e1 [$journal:append $j [a 1] {actor: "u" authority: "g"}]] [= $e2 [$journal:append $j [b 2] {actor: "u" authority: "g"}]] [$journal:read $j 2]] ``` ```console $ cx prog.cx [entry seq=2 tenant=acme actor=u authority=g ts='1970-01-01T00:00:02Z' prev-hash='sha2-256:69c69761d531660401aa7275810f13828b1db1430511243efbaddc053637311d' payload=sha2-256:e74783796d0d83dfce07552c3773cc9ab4f270fbeb19ada5438a363d79b08e18 hash='sha2-256:1d4f5830f65cd9c6b37aa82309376cb3933b6ff91b7d0c7ef877d4403c9dabb0' [event [b 2]]] ``` Reading past the end is absence, not an error: `prog.cx` ```cx [?lib 'cx-platform/journal'] [?let [= $j [$journal:open "mem://t" "acme"]] [$journal:read $j 5]] ``` ```console $ cx prog.cx () ``` ### HTTP Status codes are values you can ask questions about, which is what makes an HTTP result composable with the rest of the language instead of needing a separate error channel: `prog.cx` ```cx [?lib 'cx-stdlib/http-client' as=http] [$http:status [response status=404]] ``` ```console $ cx prog.cx 404 ``` `prog.cx` ```cx [?lib 'cx-stdlib/http-client' as=http] [$http:ok [response status=404]] ``` ```console $ cx prog.cx false ``` Serving is `[?http-service]` with `[block true]`; the guide's own static server (`scripts/gen_guide/guide_serve.cx`) is the worked example in-repo. Anything that dials or listens needs `--allow-net`, optionally scoped: `--allow-net=example.com:443`. ### Identity and authority Two layers sit between a request and an effect, and they answer different questions. **Session** answers *who is this*. **Authz** answers *may they*. A session binds a `(principal, tenant)` pair for its whole life — the binding is immutable, and rebinding is a refusal, not an update. A deployment that admits an anonymous floor says so in its attach policy, and the floor is a *real* principal, not the absence of one: `prog.cx` ```cx [?lib 'cx-platform/session'] [?let [= $pair [$session:attach-guest [request scheme="https"] {anonymous-floor: "web-public" tenant: "shop"}]] [= $s [$first $pair]] [?let [= $p [$session:principal $s]] $p@id]] ``` ```console $ cx prog.cx web-public ``` Refusal is the common case. Without a declared floor, a guest attach is a clean typed refusal naming the policy — deny-by-default all the way down: `prog.cx` ```cx [?lib 'cx-platform/session'] [$session:attach-guest [request scheme="https"] {tenant: "shop"}] ``` ```console $ cx prog.cx [err code=cx-err:CXER4812 message='E_SESSION_ANONYMOUS_REFUSED: the deployment attach policy does not admit an anonymous floor — no `anonymous-floor` principal in cfg (xap_identity_model §4.7; refusing is the production default)' at='2:1'] ``` Authority is **delegated, attenuating, and rooted in a principal**. A permit names the chain it came through: `prog.cx` ```cx [?lib 'cx-stdlib/authz'] [?lib 'cx-platform/authz-store'] [?let [= $az [$authz-store:store {tenant: 'acme'}]] [= $d [$authz-store:delegate $az [delegation d-1 [tenant acme] [from [principal dana]] [to [agent ops-1]] [capabilities [refund-duplicate]] [over '/orders'] [assurance :t1] [signature sig-dana]]]] [$authz:check $az [authz-request [actor [agent ops-1]] [capability refund-duplicate] [slice '/orders/9'] [tenant acme]]]] ``` ```console $ cx prog.cx [permit rooted-principal=dana [delegation d-1] [via d-1] [tier :t1] [capability refund-duplicate]] ``` A denial is a *value* carrying its reason, not an exception. There is no implicit grant anywhere: `prog.cx` ```cx [?lib 'cx-stdlib/authz'] [?let [= $az [$authz:open {tenant: 'acme'}]] [$authz:check $az [authz-request [actor [agent ghost]] [capability refund-duplicate] [slice '/orders/9'] [tenant acme]]]] ``` ```console $ cx prog.cx [deny actor=ghost [code cx-err:CXER4700] [reason :no-grant] [capability refund-duplicate] [slice /orders/9] [tenant id=acme]] ``` `prog.cx` ```cx [?lib 'cx-stdlib/authz'] [?let [= $az [$authz:open {tenant: 'acme', grants: [delegation d-1 [tenant acme] [from [principal dana]] [to [agent ops-1]] [capabilities [refund-duplicate]] [over '/orders'] [assurance :t1] [signature s]]}]] [$authz:check $az [authz-request [actor [agent ops-1]] [capability refund-duplicate] [slice '/payments/9'] [tenant acme]]]] ``` ```console $ cx prog.cx [deny actor=ops-1 [code cx-err:CXER4700] [reason :out-of-slice] [capability refund-duplicate] [slice /payments/9] [tenant id=acme]] ``` And the decision explains itself — the first failing link, by name, which is what makes an authority chain auditable rather than merely enforced: `prog.cx` ```cx [?lib 'cx-stdlib/authz'] [?let [= $az [$authz:open {tenant: 'acme', grants: [delegation d-1 [tenant acme] [from [principal dana]] [to [agent ops-1]] [capabilities [refund]] [over '/orders'] [assurance :t1] [signature s]]}]] [= $dec [$authz:check $az [authz-request [actor [agent ops-1]] [capability refund] [slice '/orders/9'] [tenant acme]]]] [$authz:explain $dec]] ``` ```console $ cx prog.cx [explanation outcome=permit [accountable dana] [authority-chain [step d-1]]] ``` `prog.cx` ```cx [?lib 'cx-stdlib/authz'] [?let [= $az [$authz:open {tenant: 'acme'}]] [= $dec [$authz:check $az [authz-request [actor [agent x]] [capability y] [tenant acme]]]] [$authz:explain $dec]] ``` ```console $ cx prog.cx [explanation outcome=deny [first-failing-link :no-grant] [code cx-err:CXER4700]] ``` ### Operating a platform ```console cx store-serve config.cx # the store service daemon cx fabric-serve config.cx # the fabric eventing daemon cx store-health URL # readiness probe (exit 0 iff accepting) cx store-rotate-kek … # rotate a key-encryption key cx store-mint-principal … # mint an XSP-AUTH principal, offline ``` All take a CX configuration document or an explicit target. There is deliberately no supervisor integration in-repo: lifecycle is the operator's, explicitly. #### The clean-state bootstrap A daemon is deny-by-default, so a fresh deployment has nobody who may talk to it. The bootstrap is an **offline identity mint** — nothing transits a wire, no store is opened, and there is no trust-on-first-use step: ```console cx store-mint-principal --id fleet-ops \ --seed-file ./secrets/fleet-ops.seed --caps "read write" ``` That generates an Ed25519 seed, derives its `did:key`, writes the seed at mode 0600 (never to stdout), and prints two things: the `[grant …]` row for the daemon config's `[xsp [grants …]]` table, and the client's `xsp-did` / `xsp-seed-env` open-opts. The minted principal is **inert** until an operator splices that grant into the config — **config remains the sole authority**. `--for identity` prints the daemon's *own* `[xsp [identity did= seed-env=]]` responder row instead. `--caps` is required: the authority a grant carries is an explicit choice at mint time, with no default. The seed environment variable is *derived* from `--id` (`CX_XSP_SEED_`, upper-cased with `-` folded to `_`), so an `--id` whose derivation would collide with another is refused by name rather than silently sharing one seed. The full arc is: **mint → splice the grant into the config → start the deny-by-default daemon → the client presents its DID.** #### Retired verbs answer for themselves `cx store-token` is retired — the bearer/RBAC plane is gone. It does not answer "unknown subcommand"; it names its own retirement and points at the replacement. If you recall a store verb that is not in `cx --help`, run it: the tool will tell you what replaced it. ## The bindings and ecosystem groups The bindings and ecosystem groups are everything *around* CX: feature distribution, the registry, the language bindings, and agent interop. They are the least settled groups; what follows is what ships. ### Agent interop — MCP and A2A CX command definitions (an `[?def]` carrying an `[effects …]` clause) project to tool descriptors, and the protocol shapes are ordinary CX values emitted to their wire form. Nothing here is a bespoke serializer: `prog.cx` ```cx [?lib 'cx-platform/mcp' as=mcp] [?lib 'cx-stdlib/json' as=json] [$json:emit [$mcp:list-tools-request 2]] ``` ```console $ cx prog.cx '{"id":2,"jsonrpc":"2.0","method":"tools/list"}' ``` `prog.cx` ```cx [?lib 'cx-platform/mcp' as=mcp] [?lib 'cx-stdlib/json' as=json] [$mcp:validate-args [$json:parse "{\"name\":\"w\",\"inputSchema\":{\"type\":\"object\",\"required\":[\"location\"],\"properties\":{\"location\":{\"type\":\"string\"}}}}"] [$json:parse "{\"location\":\"NYC\"}"]] ``` ```console $ cx prog.cx [ok] ``` Argument validation refuses rather than defaulting, so a malformed tool call fails at the boundary: `prog.cx` ```cx [?lib 'cx-platform/mcp' as=mcp] [?lib 'cx-stdlib/jsonschema' as=js] [?lib 'cx-stdlib/json' as=json] [$js:violation-paths [$mcp:validate-args [$json:parse "{\"name\":\"w\",\"inputSchema\":{\"type\":\"object\",\"required\":[\"location\"]}}"] [$json:parse "{}"]]] ``` ```console $ cx prog.cx ('/location') ``` The A2A shapes are the same story on the agent-to-agent side: `prog.cx` ```cx [?lib 'cx-platform/a2a' as=a2a] [?lib 'cx-stdlib/json' as=json] [$json:emit [$a2a:agent-card "greeter" "says hi" "http://x/" "0.1" ()]] ``` ```console $ cx prog.cx '{"capabilities":{"streaming":false},"description":"says hi","name":"greeter","skills":[],"url":"http://x/","version":"0.1"}' ``` The projection itself is offline and inspectable: ```console cx tools export MODULE.cx # command defs -> tool descriptors ``` Enforcement stays at the CX effect point (`cx-err:CXER0271`) regardless of what any client claims — descriptor annotations are hints, and hints are lossy downward. ### Features (XAPs) and distribution A XAP is the unit of packaged CX functionality — a *feature*, never an "app". Scaffolding and surface checking are CLI verbs: ```console cx xap init NAME # scaffold a feature cx xap check-surface DIR # the surface derivation check cx lock # generate / verify cx.lock from [?lib] imports ``` Feature grammars **compose**, and composition is an algebra with laws rather than a merge convention. Composing is commutative, associative, and idempotent, and the composed grammar has a Tier-1 hash that is the equality oracle: `prog.cx` ```cx [?lib 'cx-platform/xap' as=xap] [?let [= $chart [feature name=chart [nouns [noun name=viewport singular=true [field name=center type=geo-point]]] [verbs [verb name=highlight effect=arrange [intent [do :highlight]] [reads viewport]]] [requirements [requirement kind=functional as=user traces=highlight [want 'to mark a spot'] [so 'I can find it']]]]] [= $strikes [feature name=strikes [nouns [noun name=strike [field name=pos type=geo-point] [field name=at type=instant]]] [verbs [verb name=list-strikes effect=observe [intent [do :list-strikes]] [reads strike]]] [requirements [requirement kind=functional as=user traces=list-strikes [want 'to see strikes'] [so 'I avoid them']]]]] [= [$xap:grammar-hash [$xap:compose $chart $strikes]] [$xap:grammar-hash [$xap:compose $strikes $chart]]]] ``` ```console $ cx prog.cx true ``` Names qualify on the way in, so two features may each own a `highlight` without collision: `prog.cx` ```cx [?lib 'cx-platform/xap' as=xap] [?let [= $chart [feature name=chart [frames [use frame=geo via=center]] [nouns [noun name=viewport singular=true [field name=center type=geo-point]]] [verbs [verb name=highlight effect=arrange [intent [do :highlight]] [reads viewport]] [verb name=set-waypoint effect=act scope=shared consequence=reversible [intent [do :set-waypoint]] [writes viewport]]] [requirements [requirement kind=functional as=user traces=highlight [want 'to mark a spot'] [so 'I can find it']]]]] [?let [= $g [$xap:compose $chart]] $g//verb[= $_@name 'chart/highlight']/@effect]] ``` ```console $ cx prog.cx [effect arrange] ``` Composing *nothing* is a refusal, not a vacuous pass — a gate over the empty set has verified the empty set, which is the one failure mode that makes a gate worse than no gate: `prog.cx` ```cx [?lib 'cx-platform/xap' as=xap] [$xap:compose] ``` ```console $ cx prog.cx [err code=cx-err:CXER4874 message='E_XAP_COMPOSE_EMPTY: composition has no features — a gate over the empty set verifies nothing (§3.1 identity)'] ``` Distribution is seal → sign → publish → install, and **the transport is the store**. There is no separate package wire and no new protocol. A package tree is content-addressed, so entry order cannot change its identity: `prog.cx` ```cx [?lib 'cx-platform/xap' as=xap] [?lib 'cx-platform/store' as=store] [?let [= $s [$store:open "mem://"]] [= $t1 [$xap:pkg-tree ([entry path='b.cx' 'bee'], [entry path='a.cx' 'ay'])]] [= $t2 [$xap:pkg-tree ([entry path='a.cx' 'ay'], [entry path='b.cx' 'bee'])]] [= [$store:put-doc $s $t1] [$store:put-doc $s $t2]]] ``` ```console $ cx prog.cx true ``` …and every gate fails closed rather than sanitising: `prog.cx` ```cx [?lib 'cx-platform/xap' as=xap] [$xap:pkg-tree ([entry path='../evil.cx' 'x'])] ``` ```console $ cx prog.cx [err code=cx-err:CXER4880 message='E_XAP_PKG_INVALID: entry path "../evil.cx" must be relative and traversal-free' path='../evil.cx'] ``` `prog.cx` ```cx [?lib 'cx-platform/xap' as=xap] [$xap:pkg-tree ([entry path='a.cx' 'one'], [entry path='a.cx' 'two'])] ``` ```console $ cx prog.cx [err code=cx-err:CXER4880 message='E_XAP_PKG_INVALID: duplicate entry path "a.cx"' path='a.cx'] ``` **Building a production XAP end to end — composing features, authoring the deployment document, bootstrapping identity, hosting, and projecting a ux-web surface — is `playbook-xap.md`.** Read it before you write one. ### Language bindings CX is reachable from four host languages through the C ABI (`include/cx.h`), each binding a repository of its own — `cx-binding-python`, `cx-binding-go`, `cx-binding-rust`, `cx-binding-v` — parity-tested against one shared conformance family (`conformance/binding_api.cxd`), so "it works" in one binding and "it works" in another mean the same bytes came back. ## Choosing a ring, in one line each * **Ring 0** if the answer is already in the document. * **Ring 1** if the answer has to be computed. * **The platform group** if the answer has to outlive the process or be shared. * **The bindings and ecosystem groups** if the answer has to cross a language or a protocol boundary. Take the smallest one. Moving outward later costs nothing, because a lower ring is never changed by a higher one. # `cx-platform/connector` The connector engine every connector feature runs on: the capability check at the effect point, the open handle registry, the token cache, the spend ledger and handle resolution. A pure-CX package (RULED: RS-25) — no V code of its own. Source: [`stdlib/connector.cx`](https://github.com/cx-home/cx-platform-connector/blob/main/stdlib/connector.cx). Corpus: [`conformance/platform/connector.cxd`](https://github.com/cx-home/cx-platform-connector/blob/main/conformance/platform/connector.cxd). Spec: [`spec/03-approved/platform/connector.md`](https://github.com/cx-home/cx-platform-connector/blob/main/spec/03-approved/platform/connector.md). # `cx-platform/sync` Incremental sync: the cursor/watermark contract, and the three ways a source is captured. Reaches `store`, `journal`, `sched` and the connector engine. Source: [`stdlib/sync.cx`](https://github.com/cx-home/cx-platform-connector/blob/main/stdlib/sync.cx). Corpus: [`conformance/platform/sync.cxd`](https://github.com/cx-home/cx-platform-connector/blob/main/conformance/platform/sync.cxd). Spec: [`spec/03-approved/platform/sync.md`](https://github.com/cx-home/cx-platform-connector/blob/main/spec/03-approved/platform/sync.md).