ADR-0030: Playbooks are packaged interpreted scripts against the Vinxi SDK
ADR-0030: Playbooks are packaged interpreted scripts against the Vinxi SDK
Status
Accepted. Refines ADR-0029 §3 (domain language as packaged data) — it does not supersede it; the grammar of 0029 stands.
Date
2026-07-04
Context
ADR-0029 §3 reserved one fixed slot for domain vocabulary — vinxi run <reality>.<playbook> — with playbooks “declared as data” in
realities/<product>/playbooks/, marked future. Phases 1–3 shipped; this is
the Phase-4 tail.
The question deferred by 0029 was what a playbook is. Real domain flows carry
logic: seed-fiber-pon loops over four output legs; a future inject-cut
raises an alarm on every ONT downstream of a cut. Encoding logic as a JSON
manifest means inventing control flow (loops, parameters, then query-driven
steps) inside JSON — a configuration format creeping toward an ad-hoc, badly
specified scripting language (Greenspun’s tenth rule). Meanwhile the demo
fixtures under vinxi dev already hand-roll an SDK: the create/link closures
in dev_cmd.rs. There was a real SDK hiding in the harness.
Decision
A playbook is an interpreted JavaScript ES module, shipped in the reality package, run against a formalized Vinxi SDK.
- Engine — QuickJS via
rquickjs. Real ES2020 with async/promises (SDK verbs areawaited, no block-on wrinkle), ~1 MB, sandboxed. The dependency is confined tovinxi-cli— never referenced from another crate, never from a wasm build graph (there is no Rust wasm target today; the CLI is a leaf binary). - The Vinxi SDK is the seam. A scope-bound surface exposing the fixed kernel
verbs (
create/linktoday) that maps 1:1 to the syscalls. It is injected into the script as thevinxiobject over a JSON-string boundary, so no JS handle is held across anawait. Its shape is the seam for the future CLI-as-client-of-vinxi-kernel-server: an HTTP-client implementation swaps in behind the same verbs. - Module contract. A playbook exports
meta(read forrun --list/validation without executing the body) and a defaultasync function (vinxi, args). Its return value becomes the one stdoutReport.json; op-counts form the human summary.run --listdiscovers playbooks by scanningrealities/*/playbooks/*.js. - Sandbox. The script can touch only
vinxi.*andconsole.log→stderr — no filesystem, network, or clock. Blast radius equals the kernel verbs the operator already holds. A thrown JS error becomes a typedVinxiError; an SDK error is preserved with its code across the boundary.
This does not breach ADR-0029’s invariant. A .js file in the reality
package, discovered and interpreted at runtime, is data on disk — not a
compiled subcommand; it shares the trust and lifecycle model of the ontology
.json documents beside it. The grammar stays closed (vinxi run), vocabulary
stays open, nothing domain-specific is baked into the binary. 0029’s letter
(“declared as data”) stretches to “packaged interpreted script”; its spirit
holds exactly.
Alternatives Considered
- A JSON manifest DSL (linear steps → args → loops → query-driven steps): the status-quo reading of “data.” Rejected — each increment reinvents a scripting primitive; the terminal form is a JSON workflow language nobody wants to author or maintain.
- Rhai (pure-Rust embedded scripting): featherweight and sandboxed, but a bespoke language (not JS) and synchronous, forcing a block-on bridge to the async kernel.
- Embedded Deno/V8: real JS/TS and async-native, but a ~20–50 MB binary embed pulling hundreds of crates — disproportionate for demo/seed macros.
- Boa (pure-Rust JS): JS syntax at Rhai weight, but less mature with weak async support.
- Compiled per-product subcommands / plugin binaries: the anti-pattern 0029 exists to prevent.
Consequences
seed-fiber-ponis nowrealities/networkaccess/playbooks/seed-fiber-pon.js(loops intact);vinxi dev seed-fiber-ponis retired. Theinject-cut/correlate/prove/seed-trafficurefixtures stay invinxi dev(they call kernel-core internals that are not verbs).- The SDK is write-only (
create/link) for this slice — all the seed uses. - Deferred and ledgered in
TODO.md§ “CLI — agent-first grammar”: SDK reads (find/get/trace) → unlocks a dynamicinject-cutplaybook; a JSON declarative front (desugaring to the same SDK calls); extracting the SDK to avinxi-sdkcrate + trait (embedded vs. HTTP-client) for CLI-as-client-of-server;import/multi-file playbooks; typedparamsschema validation;vinxi explain <playbook>. rquickjsadds a small vendored C dependency (built with the system C compiler, no extra toolchain). It is confined tovinxi-cli, so it never enters a wasm build and never bloats another crate.