ACTION manifest
action-manifest.json (this directory) is the authoritative registry of every
XChain protocol ACTION and which repositories must wire it. It is the single
source of truth for the cross-repo “action lockstep”: today “what actions exist”
is re-encoded as an independent literal in roughly six places, and three of them
fail closed and silent if you forget one:
- forget the decoder
VALID_ACTION_NAMESand every on-chain instance is dropped at decode; - forget the indexer dispatch/registration and the action coerces to
UNKNOWNand no-ops (per-node divergence is a ledger fork); - forget the explorer
getActionDatabranch and the public page renders blank.
How it is enforced
Each repo vendors a byte-identical copy at test/fixtures/action-manifest.json
and ships an ActionManifestConformance.test.js that runs in its own unit tier
(no sibling checkout required). Each guard asserts two things:
- BEHAVIOR: the repo’s local action set equals the manifest slice for its role flag (
wireDecodedfor the decoder,indexerHandledfor the indexer dispatch,userEncodablefor the SDK Formats,explorerRenderfor the explorer,walletFormfor the wallet registry). - IDENTITY: the vendored copy is byte-identical to this canonical file (skips when
xchain-documentationis not checked out, matching theConsensusPrimitiveConformanceconvention).
So adding an action everywhere-but-one-repo fails that repo’s CI, naming the missing action and the file to edit.
Schema
{
"flags": { /* what each per-repo role flag means */ },
"categories": { /* human-readable grouping (wire-user / validator / mirror-injected / lifecycle / explorer-legacy-render) */ },
"aliases": { "TRANSFER": "SEND", ... }, // expanded to canonical before any gate
"actions": {
"SEND": { "category": "wire-user", "wireDecoded": true, "indexerHandled": true, "userEncodable": true, "explorerRender": true, "walletForm": true }
// one entry per action; only the TRUE flags are present
}
}
The per-repo sets legitimately differ by role: the SDK omits validator-only
actions (ANCHOR/ATTEST/NODEPROOF/SLASH); the indexer adds mirror-injected
(XCALL/XEXEC/CROSS_SETTLE) and lifecycle (*_MATCH/*_EXPIRE/DISPENSE)
handlers that are never decoded wire bytes; the explorer is the render superset
(including legacy order/dispenser cancel+edit views). The manifest encodes these
differences as flags rather than pretending all sets are equal.
Two things deliberately excluded: the indexer protocol_changes feature-gate
flags (VM_ACTIONS, CONTROLLER_GUARD, UNIFIED_FEES, …) which are not
actions, and the UNKNOWN catch-all sentinel.
Adding a new ACTION
- Add one entry to
action-manifest.jsonwith the role flags it should carry. - Re-vendor: run
bin/sync-action-manifest.shfrom the platform checkout. It copies this file over everytest/fixtures/action-manifest.jsonand verifies the result. - Run each repo’s conformance test. Each repo whose flag you set but did not wire fails loudly; wire it until green.
bin/sync-action-manifest.sh --check is the cross-repo byte-parity gate, run by
bin/ci-all.sh. It exists because the per-repo IDENTITY assertion below only
compares against canonical when xchain-documentation is checked out beside the
repo, which on GitHub it never is, so the monorepo run is where all six copies are
compared at once. The same pass classifies every other action-manifest.json in
the checkout: build output, xchain-node install clones and throwaway worktrees
are reported and not gated, while a tracked copy in a platform repo that is
not on the roster fails, since that is how a seventh vendoring site with its own
conformance guard would otherwise appear with nothing keeping it in step.
flowchart TD
Add["Add entry to action-manifest.json<br>with the role flags it should carry"] --> Vendor["Re-vendor: bin/sync-action-manifest.sh<br>copies the file over every repo's<br>test/fixtures/action-manifest.json"]
Vendor --> Test["Each repo runs ActionManifestConformance.test.js<br>in its own unit tier"]
Test --> Behavior{"BEHAVIOR: repo's local action set<br>equals the manifest slice for its role flag?"}
Behavior -->|"no"| Fail["That repo's CI fails,<br>naming the missing action and the file to edit"]
Behavior -->|"yes"| Identity{"IDENTITY: vendored copy<br>byte-identical to the canonical file?"}
Identity -->|"no"| Fail
Identity -->|"yes"| Green["Repo's conformance test passes"]
Fail --> Wire["Wire the action in that repo"]
Wire --> Test
Step 2 is one command as of XC-1268. The full collapse of the per-repo literals into generated code (so the per-repo edits disappear too) is still a future follow-on; today the manifest, the sync tool and the conformance guards make the fan-out safe and one-edit to vendor, not yet one-edit to wire.