CX · Reference guideXAP feature distribution
PlaygroundDownloadsAboutv0.18.0-pre.1

XAP feature distribution

The headline of the 0.13 release is that XAP features became a distribution unit: sealed, signed, published, discovered, verified, installed, and entitled — end to end, on machinery the platform already had. Distribution introduces no archive format, no transport, and no trust primitive of its own: a package is a sealed subtree in a content-addressed store (store), a registry is a store, a version is an alias, and trust is the Tier-1 content hash plus a publisher-DID signature. The normative model is the feature distribution & market spec (spec/03-approved/xap/xap_feature_distribution_market.md). This section walks the staircase — the unit, authoring, publishing, consuming, entitlements, the market — then the clients that materialize a surface and the reference surfaces built the declarative way, and it is explicit about which steps are shipped and which are specified-but-not-yet-built. Running a XAP is ops-xap-host.

The unit — what a feature is

A feature is the XAP composition unit and the platform's commercial unit — the thing you author, version, sell, and install. It is a grammar (verbs, nouns, rules — compiled from requirements) plus the implementation module that travels with it; behavior ships inside the package, never in the serving process. Not everything is a feature, though — the sealing and signing machinery is content-agnostic, and the package kind decides only which check runs at install:

kind unit install gate
feature the XAP composition unit: grammar + impl + fixtures verify + compose-gate + needs consent
library plain CX code — no grammar, no governance surface verify + module-surface check (declared defs present, Tier-2 identities match)
client a medium materializer app (a separate project) verify + client-spec validation

Two dependency planes, never blurred: on the grammar plane a feature uses a feature (composition — changes what a principal can say); on the code plane any package requires a library (implementation, hash-pinned — changes what the code calls). A library never uses a feature, and by invariant N-DIST-2 a library carries no authority: it executes under the requiring feature's grants and can neither hold nor request capabilities of its own. Enabled features compose into one grammar with conflicts caught as values at compose time, and the composed grammar hashes deterministically — two deployments with the same pins have bit-identical grammars:

      [?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']]]]]
      [= $g [$xap:compose $chart $strikes]]
  ([$count $g//verb], [$xap:resolve $g 'highlight' {}], [$xap:grammar-hash $g])]
    
      (2, 'chart/highlight', 'sha2-256:9e0e827745381eda74a7f5ad72174d6f8ab6c384f281dcd0d5d44cd802580d46')
    

The whole distribution staircase is a function surface — [$xap:compose], [$xap:pkg-tree] / pkg-seal / pkg-sign / pkg-publish / pkg-fetch / pkg-verify / pkg-install / pkg-requires-closure / pkg-catalog, and [$xap:license-issue] / license-verify — all shipped in the bundled cx-xap module (no install). A cx pkg CLI verb family (e.g. cx pkg graph) is named in the spec but not in the shipped binary yet; drive the staircase from CX programs today.

Authoring — spec plus contract module

A feature is authored as a *.feature.cxd document validated by a schema — requirements are the source, the grammar is the compiled output, and a requirement's acceptance criteria are its conformance fixtures. The skeleton (the full-size real reference is the reference/shop app's base feature package):

      [feature name=notes version='0.1.0'
  [summary 'A shared notebook']
  [nouns [noun name=note [field name=text type=text]]]
  [verbs
    [verb name=list-notes effect=observe scope=local consequence=none
      [intent [do :list-notes]] [reads note]]
    [verb name=add-note effect=act scope=shared consequence=reversible
      [intent [do :add-note text=$text]] [writes note]]]
  [governance [grant verb=add-note to=member]]
  [requirements
    [requirement kind=functional as=member traces=add-note
      [want 'to jot a note'] [so 'the crew sees it']]]]
    

Every verb carries an effect signature — effect (observe · act · arrange) × scope × consequence — which is what the runtime gates scale on: observe is ungated, act meets the authorization check, and consequence scales the gate. Validation is real tooling, run as you author (the notes feature above validates clean against the shipped schema):

      $ cx validate notes.feature.cxd --schema=spec/03-approved/xap/xap_schemas/feature.cxs
    

The behavior half is the contract module — a plain CX module exporting the conventional surface the deployment host calls: readout (always; serves the feature's surface data), apply (iff the grammar has a non-observe verb; called after the policy check admits the intent), and optionally simulate. A minimal complete module:

      [?def readout scope=public impure ($host $t)
  [readout title='Notes' tenant=$host@tenant [kv k='count' v='0']]]
[?def apply scope=public impure ($verb $intent $host)
  [?if [= $verb 'notes/add-note']
    [then $verb]
    [else [refused reason='no apply for this verb']]]]
    

$host is the deployment context — one value naming the deployment the feature runs in: [host tenant='acme' [store <handle>] [journal <handle> stream='acts']] (#1210). $host@tenant is the tenant a feature acts under (never re-stated from an environment variable), [$first $host/store] the capability-scoped working store, and [$first $host/journal] the deployment's act chain — the same journal the runtime commits to, so a feature owns its own fold instead of opening one. The journal child is ABSENT when the deployment declares no journal binding: guard with [$present $host/journal] (the presence test — $exists asks content arity).

$intent is the committed act itself — [do 'notes/add-note' [text '…']], the very value the journal appended — so a field reads as a child, $intent/text, never through a re-shaped copy (one act form at every boundary, #1260). apply may refuse on domain policy by returning [refused reason=…] — the policy layer admitted the verb, the feature refused the values; two layers, both visible. scope=public is what a module exports, and the package manifest's exports block must list exactly that contract surface — projected from the code at publish and re-verified at install, both ways.

The filename is part of the contract, not tidiness. The module MUST be at <feature-name>.cx — the name the [feature name=…] carries, exactly. Three readers resolve the entry point by that path and nothing else: pkg: module references, the install gate, and the host's own acquisition; a package may carry other .cx entries (a vendored helper), so "whichever .cx is in the tree" is not a rule anything could apply. pkg-seal, pkg-install and host boot each refuse a mismatch with CXER4884 naming the missing entry (#1195) — before that enforcement a mismatched package sealed, published, verified, composed and booted, and then served an empty readout for the life of the deployment. The exported surface is checked both ways: code with no exports block is refused, and a declared export the code does not define is refused (the distribution spec's feature runtime contract section).

export required called
readout ($host $t) always serves GET /surface/<feature>; feeds the push channel
apply ($verb $intent $host) iff the grammar has a non-observe verb after the policy check admits the emit; $verb arrives qualified; $intent is the committed act itself
simulate ($host $t $params) optional the host's simulation tick

Three authoring rules that hold a feature set together. Requirement and verb trace each other — every verb traces to a requirement and every requirement to a verb, enforced as a validation concern, and a requirement's acceptance criteria are its conformance fixtures. Frames and keys relate nouns across features: a frame is a shared continuous coordinate (compare by nearness — geo, time, value), a key a shared identifier (join by sameness); register onto them, never invent private ones for a shared concept. The source stack is a seam: gateways (kind deliberately not enumerated) plus prioritised sensors with failover feed the nouns, and the noun shapes are the stable contract — nothing above the stack changes when a vendor does. Your verbs already project a form: [$ux:form $feature 'register' {}] derives it from the verb's declared parameters, falling back to the fields of the noun it writes, and the same entry takes a composed grammar (ux-capability):

prog.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"}]]
      
        $ 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']]
      

Fixture ux-080-feature-form-projects-the-xap-grammar in deps/cx-platform-ux/conformance/xap/ux.cxd — replayed against this cx when the page was built.

A composite feature is just a feature that uses others and declares derived nouns (a [from …] join over shared frames and keys). Composition is closed under feature: the composite packages, publishes and installs like a base feature, with its uses set as grammar-plane dependencies, and its cross-feature reads are exactly what the needs block's reads consent is for (dist-entitlements). The full-size reference is the XAP repository's reference/shop: its base features, and its composite delayed-shipment feature joining two of them over geo and time into derived alert nouns (dist-reference-surfaces).

Publishing — a git repo is a registry

A registry is just a CX store holding sealed packages, manifests, and an alias index — so a git repository containing a file:// store is a complete registry, with publish-by-PR, history as audit, and repo permissions as publish permissions. The publish pipeline is author → validate → seal → sign → publish, and the whole publisher side runs against any store:

      [?lib 'cx-platform/xap' as=xap]
[?lib 'cx-platform/store' as=store]
[?lib 'cx-stdlib/crypto' as=crypto]
[?lib 'cx-stdlib/did' as=did]
[?let [= $reg [$store:open 'mem://']]
      [= $kp [$crypto:ed25519-keypair]]
      [= $pub [$did:key-create $kp@public]]
      [= $tree [$xap:pkg-tree ([entry path='geo-utils.cx' '[?def id scope=public ($x) $x]'])]]
      [= $draft [?element 'package'
                  [?attr 'name' 'geo-utils'] [?attr 'version' '0.1.0'] [?attr 'kind' 'library']
                  [?element 'publisher' [?attr 'did' $pub]]
                  [?element 'exports' [?element 'def' [?attr 'name' 'geo-utils/id']]]]]
      [= $sealed [$xap:pkg-seal $reg $tree $draft]]
      [= $signed [$xap:pkg-sign [$store:get-doc $reg $sealed@manifest] $kp@private]]
      [= $mh [$store:put-doc $reg $signed]]
      [= $pubr [$xap:pkg-publish $reg 'geo-utils' '0.1.0' $mh]]
      [= $v [$xap:pkg-verify $reg 'geo-utils@0.1.0']]
  ($pubr@alias, $v@status, $v@kind)]
    
      $ cx --allow-random publish.cx
('geo-utils@0.1.0', 'ok', 'library')
    
  • pkg-tree seals the directory as one canonical content document — entries sorted by path, byte-stable, so its store hash is the package's Tier-1 identity (traversal and duplicate paths are rejected, CXER4880).
  • pkg-seal validates the manifest draft kind-aware (a needs block on a library is rejected — libraries carry no authority) and stores the manifest beside the tree.
  • pkg-sign fills the detached ed25519 signature over the Tier-1 hash. Signing is possession; verification is trust — the key-to-DID binding is checked at verify time.
  • pkg-publish points the name@version alias at the manifest. Released aliases are immutable: re-pointing raises CXER4887; re-publishing the identical hash is idempotent.
  • pkg-verify re-runs the full fail-closed chain (re-hash, signature, required credentials); nothing is staged on failure.

Sign from day one, even internally — it costs nothing, and it makes every later growth stage a re-host, never a re-package: the live instance was the original external reference registry (stage 1, sealed+signed packages in a git repo), the same store served over the store daemon is stage 2 (consumers switch the URI scheme, nothing else), and the market of the last subsection wraps stage 2. Hashes and signatures never change across stages, and a re-hosted package keeps its original publisher's signature — trust is per-package, never per-registry. The sealed tree is canonical — entries sorted by path, byte-stable — which is what makes its hash the package's identity:

prog.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]]]
      
        $ cx prog.cx
true
      

Fixture xap-dist-001-tree-canonical-order in deps/cx-platform-xap/conformance/xap/xap-dist.cxd — replayed against this cx when the page was built.

A stage-1 registry is a directory in a git repository: registry/keys/<publisher>.cxd (the publisher identity — a did:key plus its signing seed, committed by design at stage 1, because repository permission IS publish permission), registry/store/ (the file:// CX store holding the sealed packages, the manifests and the alias index), and the three programs that drive it — publish.cx, pin.cx, rehost.cx. Consumers reach it as file:// (a clone or a vendored copy) or https:// (raw); git adds exactly what an internal registry wants — publish by pull request (moving an alias is a reviewed commit), history as audit, repository permissions as publish permissions.

Manifests: hand-written versus projected. The draft package.cxd carries only what cannot be derived — kind, publisher, requires (the code plane), compatibility, license-terms, and the exports contract surface. The grammar summary, the needs consent set and the uses dependencies are projected from the feature spec at publish and re-verified at install, never restated by hand; the consuming project's registry/publish.cx is that projection, driven by its make targets (registry-publish, registry-publish-all, registry-publish-lib, registry-rehost-<name>). Keys: a stage-1 key is an ed25519 seed, the DID a did:key — offline-verifiable, no resolver infrastructure; rotation and compromise recovery are DID-document rotation per the did module spec, with attestations re-anchoring trust for pre-rotation artifacts. The growth path, each stage a re-host of the same bytes:

stage what status
0 — in-repo directories and path references the floor
1 — an internal git registry sealed, signed packages, publish by pull request implemented and live (the original external reference instance; the in-family successor is the XAP repository's reference/shop)
2 — a served registry the same store served over the store wire by cx store-serve; consumers switch the URL scheme implemented (the wire re-host is engine-tested; pkg-catalog works locally and served)
3 — a market the market XAP wraps the registry the model specified, the entitlement machinery shipped — dist-market

Consuming — verify, install, pin

The consumer pipeline is discover → acquire → verify → gate → consent → enable, fail-closed at every step, and every check is offline-verifiable — a vendored mirror is as good as the origin. Discovery and verification against a registry store — this exact program, pointed at the repository's own registry (registry/store, a git-hosted file:// store whose alias table [$store:list-aliases] lists gtin@0.1.0), was run live for this page:

      [?lib 'cx-platform/xap' as=xap]
[?lib 'cx-platform/store' as=store]
[?let [= $reg [$store:open-opts 'file://registry/store'
                                [map read-only='true']]]
      [= $v [$xap:pkg-verify $reg 'gtin@0.1.0']]
      [= $cat [$xap:pkg-catalog $reg {term: 'gtin'}]]
  ($v@status, $v@kind, [$count $cat//package])]
    
      $ cx --allow-read discover.cx
(ok, library, 1)
    

Install takes your [xap …] deployment document and returns it with the package pinned by hash and its requires closure resolved beneath it — the deployment doc is the lockfile, spanning both dependency planes. In one in-memory program (publish a library, then install it into an empty deployment):

      [?lib 'cx-platform/xap' as=xap]
[?lib 'cx-platform/store' as=store]
[?let [= $reg [publish geo-utils@0.1.0 as in the previous subsection]]
      [= $xap0 [xap name=my-app version='0.0.1' [features]]]
      [= $done [$xap:pkg-install $xap0 $reg 'geo-utils@0.1.0']]
  [$count $done//library]]
    
      $ cx --allow-random install.cx
1
    

Loading pinned library code at runtime is the fourth [?lib] reference form — pkg: — resolved through the registry bound by the CX_REGISTRY environment variable (a store URL), with the full verification chain re-run on every load; there is no trust-the-registry mode. Verified live against the in-repo registry (run from the repository root):

      [?lib 'pkg:gtin@0.1.0' as=gtin]
[$gtin:valid '4006381333931']
    
      $ CX_REGISTRY=file://registry/store cx --allow-read --allow-env check_gtin.cx
true
    

A pkg:NAME@VERSION#MANIFEST-HASH reference pins past the alias table entirely. Each failure is a value naming its cause:

failure error
no registry bound (CX_REGISTRY unset) CXER4889 — fails closed, never a guess
name or version absent CXER4886
#hash pin does not match the resolved manifest CXER4888
tampered tree (re-hash mismatch) CXER4881

Version discipline: a version is an alias — a name for a hash; the hash is the truth. An update is a new hash and an explicit re-pin; a rollback is re-pinning the previous hash. Vendoring is first-class for offline installs: clone the registry store locally and verification proceeds identically with no network. Distinct from all of this, the older cx lock mechanism pins https:// module fetches in a cx.lock file (the lockfile spec, spec/03-approved/core/lockfile.md) — verified live: cx lock writes bundled modules as resolved="bundled:…" entries and cx lock --check exits 1 on drift, printing expected vs actual; SRI integrity for https:// entries is recorded resolved-only today (the binary says so in a warning), so treat pkg: + the pinned deployment doc as the XAP-era system and cx.lock as the module-fetch pin.

Three consequences we hold ourselves to. Consent is grants: with a runtime and a grantee named in the install options, install issues exactly the manifest's needs set as delegations — enabling IS granting, there is no installed-but-ungoverned state, a runtime request beyond needs is denied at the policy enforcement point, and a library, which has no needs, receives no grants. An update is a new hash and an explicit re-pin; one that would rebind existing utterances surfaces as an ambiguity, never a silent change, and the composer can diff two grammars — they are data — before you move; a rollback is re-pinning the previous hash, and the composed-grammar hash returns to its previous value (a downgraded feature must tolerate event kinds a newer version introduced — the open schema mode is the floor). Yank and revocation are attestations published at the market, never remote reach-in (invariant N-DIST-1): a running XAP is untouched and its composer surfaces the warning. The consuming project's registry/pin.cx installs every feature its deployment document names and writes the fully pinned document back (make registry-pin — fails loud, writes nothing on error), and a tools/vendor.cx materializes the required library packages from the registry, verified, for an offline field deployment.

Entitlements and identity

Commercial models ride the trust machinery rather than adding a DRM subsystem: a license is a verifiable credential — an attenuating delegation from the publisher to a principal — and the entitlement check is an ordinary policy check. Issue and verify, offline:

      [?lib 'cx-platform/xap' as=xap]
[?lib 'cx-stdlib/crypto' as=crypto]
[?lib 'cx-stdlib/did' as=did]
[?let [= $kp [$crypto:ed25519-keypair]]
      [= $publisher [$did:key-create $kp@public]]
      [= $vc [$xap:license-issue $publisher $kp@private 'principal:alice'
               {package: 'own-ship', versions: '0.x', kind: 'perpetual'}]]
      [= $v [$xap:license-verify $vc 'own-ship' '0.1.1' {now: '2026-07-15T00:00:00Z'}]]
  $v@status]
    
      $ cx --allow-random license.cx
'ok'
    

Every pricing shape is an attenuation of the issued credential: perpetual (no expiry, a version range — a new major is a new purchase), subscription (short-lived VCs re-issued on a cadence plus a declared grace window — the certificate-renewal pattern, no revocation polling), per-seat (an org credential delegable into at most N principal-bound sub-credentials — the delegation chain is the count), metered (the consuming XAP's hash-chained journal is the non-repudiable meter), and trial (a time-boxed gratis VC — free is not a special case). The subscription grace window, verified — a deployment mid-passage keeps working offline past expiry, inside the declared grace:

      [?lib 'cx-platform/xap' as=xap]
[?lib 'cx-stdlib/crypto' as=crypto]
[?lib 'cx-stdlib/did' as=did]
[?let [= $kp [$crypto:ed25519-keypair]]
      [= $publisher [$did:key-create $kp@public]]
      [= $vc [$xap:license-issue $publisher $kp@private 'principal:vessel-1'
               {package: 'weather', versions: '*', kind: 'subscription',
                expires: '2026-08-01T00:00:00Z', grace-until: '2026-08-15T00:00:00Z'}]]
      [= $v [$xap:license-verify $vc 'weather' '0.1.0' {now: '2026-08-07T00:00:00Z'}]]
  $v@status]
    
      $ cx --allow-random grace.cx
'grace'
    

Price is a market property, never a package property — the manifest carries license-terms only, so the same signed artifact can be paid in one market and free in another. pkg-install takes the entitlement as an option, verified fail-closed as part of the trust chain, and a consuming XAP can set require-entitlement as its install policy. Identity underneath is DID-first end to end: publishers sign as DIDs, principals hold DIDs, delegations are VCs — and since the host-auth work landed, a deployment document can carry a [host-auth] block so the deployment host itself authenticates attaching clients with the XSP mutual-auth handshake and per-request proofs (the XAP identity model spec, spec/03-approved/xap/xap_identity_model.md, and the trust-model part of the XAP spec).

The marketplace — today versus specified

The market model: there is no central market. A market is itself a XAP whose features are catalog, entitlement, and distribution; N markets compose into one discovery surface exactly as N XAPs compose into one experience. Honest status as of this release:

piece status
packaging, signing, publish, verify, install, pkg: loading, deployment host implemented
stage-1 git registry, stage-2 served registry, catalog/discovery implemented — proven by the original external reference instance; the in-family successor is reference/shop
entitlement machinery: license-issue/verify, all pricing shapes, install policy implemented (engine + conformance)
the market as a running product (hosted catalog XAP, storefront, onboarding) specified, not yet implemented
the commerce feature + payment rails (card, invoicing, app-store, crypto) specified, not yet implemented (staged phase P3 of the distribution spec)
yank / revocation attestation flow end to end attestation VC machinery shipped; composer surfacing awaits the market — specified, not yet implemented
multi-market federation on one surface specified; fixture-proven as data, no runtime surface

What that means in practice today: you can run a complete, trustworthy distribution system for one trust domain — publish sealed, signed packages to a git registry, serve it over the store daemon, discover with pkg-catalog, install by pin, and gate installs on entitlement credentials in any pricing shape (for example an internal paid tier). What you cannot do yet: point a customer at a hosted market, take a card payment, or run the yank/refund attestation flows — all specified in the feature distribution & market spec, staged behind its market and commerce phases. Revocation, when it lands, is attestations published at the market, never remote reach-in: a running XAP is untouched; its composer surfaces the warning. A catalog listing IS the feature's grammar plus its requirements, so discovery is a structured query, not a text search — design the listing as you design the feature.

Clients and surfaces — the fourth project

A XAP never embeds its renderer (invariant N-CLIENT-2, the clients section of the XAP spec). It exposes its surface as data, and every client is a separate application with its own spec that attaches over the wire and materializes that data into one medium — so the same surface drives a command line, a terminal, an agent and a web client with no change to the XAP. The surface document (surfaces/<s>.surface.cxd) binds already-declared feature verbs and views to media and layout, and never restates an intent: a panel names its feature, its region, and how it materializes on each medium present (screen, audio, haptic — the same intent, a different trigger per medium); a surface distributes, it does not render. The client document (client.cxd, validated by client.cxs) is the fourth document kind: which XAP it attaches to, what it consumes, how it projects, its windows, widgets and session model — the XAP repository's reference/shop-web-client/client.cxd is the template.

What a client consumes is the XAP's data endpoints: observe verbs serve as GET /surface/<feature> returning readouts; act and arrange verbs arrive as POST /intent — an XSP request frame wrapping the act, [do 'feature/verb' [field value]…], bare or inside [event actor= focus= …] when the client claims an actor or narrows the resolution (#1260); GET /grammar serves the composed control grammar — the client's single control vocabulary, never a hard-coded verb list; GET /stream is the push channel, named per-feature events carrying full readout snapshots, so a reconnect simply re-converges. The routes and their fragment endpoints, replayed:

prog.cx

        [?lib 'cx-platform/ux' as=ux]
[?let
  [= $s [surface admin
          [route path="/products" [feature xap=catalog as=catalog]]
          [route path="/orders" [feature xap=orders as=orders] [fragment path="/orders/rows"]]]]
  [; R-A1 migration: envelope → spliced children (2026-08-25) ]
  [list [?splice [$ux:routes-of $s]] [?splice [$ux:fragment-paths $s]]]]
      
        $ cx prog.cx
[list [ux:route [path /products] [feature catalog] [instance catalog] [template default]] [ux:route [path /orders] [feature orders] [instance orders] [template default]] '/orders/rows']
      

Fixture ux-100-surface-routes-and-fragment-endpoints in deps/cx-platform-ux/conformance/xap/ux.cxd — replayed against this cx when the page was built.

The web client is a hypermedia client. The default architecture is server-rendered HTML from surface data, the browser a generic hypermedia client on a pinned subset of HTMX, JavaScript only by explicit, named exception; the client app — not the browser — speaks CX and XSP upstream. Run the pair with make dev (the XAP on one port, the client on the next, a browser), or the client alone against a running XAP with cx --allow-net=127.0.0.1 --allow-read --allow-env serve.cx — and --allow-net=127.0.0.1 matters: a bare --allow-net binds, but the SSRF deny-set in the http module spec blocks the loopback dial to the XAP. The pinned subset:

prog.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"]]
      
        $ 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]
      

Fixture web-003-the-pinned-htmx-subset in deps/cx-platform-ux/conformance/xap/ux-web.cxd — replayed against this cx when the page was built.

Intents ride XSP upstream. The XAP-to-client link carries XSP frames (spec/03-approved/xap/xsp.md): a self-delimiting binary frame naming the principal DID, with a text or a binary payload; the v1 web binding is push down, POST up. An act rides as the frame's text payload — the CX source the host parses, byte for byte what it journals; a binary payload is object-shaped and cannot keep a child field apart from an attribute, so it is not the carrier for an act (#1269 tracks the binary carrier). The text round trip, replayed:

prog.cx

        [?lib 'cx-stdlib/xsp' as=xsp]
[?let
  [= $d [$xsp:decode [$xsp:encode [frame type=event binary=false [payload "hello xsp"]]]]]
  [seq [$text $d/@binary] [$text $d/payload]]]
      
        $ cx prog.cx
[seq 'false' 'hello xsp']
      

Fixture xsp-003-roundtrip-text-payload in deps/cx-core-code/conformance/stdlib/xsp.cxd — replayed against this cx when the page was built.

Three rules of the road. Derive the verb surface from GET /grammar and refuse unknown verbs client-side before they leave the browser. The client mirrors authority, never owns it — a dial toggle posts the intent and re-syncs from the XAP's truth, and a non-privileged attempt reverts. Agent parity is structural: an agent reads the identical surface data the human's client materializes. WebSocket and WebTransport bindings, multiplexing beyond the stream id, credit backpressure and reconnect-resume are scoped in the XSP spec and not yet implemented.

The reference surfaces — ORIEL and the studio

Two worked surfaces show the declarative way end to end, and both live with the repositories that own them rather than on this page. ORIEL is the reference storefront — built on three shipped modules, ux, ux-web and ux-tui — a catalogue of products with departments and facets, a session-scoped basket, a four-step checkout, subscriptions, returns and reviews, in a browser, in a terminal and in a serial voice-style renderer, from declarations, with zero view code. Its four documents are the domain (a feature document), the composition (a surface document), the theme (every appearance decision, and the only place one may live) and the service (folds, the command layer, page composition — no view code); what polices the arrangement is not discipline but six instruments that walk the running store and refuse loudly when a declaration and a rendering disagree. The studio is the design tool for a live XAP: a designer opens a page of a running app, selects a block, drags it, resizes it, rewrites its words, restyles the brand — and every act is a journaled command against a base document, so shipping an improvement to every deployment that customized the app is a replay, not a merge. The studio edits arrangements, and an arrangement is projected by a face; the same document renders on all three faces, which is why a studio edit cannot mean "add some CSS" — a value the terminal cannot honour is not admissible.

Both are the ux repository's — its own step runs ORIEL's drive, keys, voice, nokernel and diff instruments on every commit — and the studio's clauses are the ux capability spec's (spec/03-approved/xap/ux.md §18–§19); the in-family reference deployment beside them is the XAP repository's reference/shop with reference/shop-web-client. The pages that own them on this site: the ux repository's page (repo-cx-platform-ux.html), the XAP repository's page (repo-cx-platform-xap.html), and the ux capability's own section, ux-capability. The content normal form every face consumes, replayed:

prog.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]]
      
        $ 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"
      

Fixture ux-030-content-normal-form in deps/cx-platform-ux/conformance/xap/ux.cxd — replayed against this cx when the page was built.