ADR-0135: Vayu Compatibility Bible — enforceable extension contracts
- Status: Accepted
- Date: 2026-07-16
- Deciders: BDFL / Owner
- Relates to: ADR-0134 (VayuAPI fine-grained keys), ADR-0056 (plugin sandbox)
Context
VayuPress wants an ecosystem: developers and AI agents building themes,
plugins, and tools that work on the first try. What stood in the way was not
missing machinery but missing contracts. The sandbox, the theme compiler,
the registry, and the API permission model all enforce real rules — yet none
of them was declared anywhere a third party could check before shipping.
Concretely: plugin manifests existed only as Go structs constructed in host
code, never as a file an author could write; hook names were free-form strings
where a typo silently never fired; the plugin SPEC and all five example
plugins documented hook names (articles.write, article.created.v1) that
the host has never fired; the compat golden test froze a hand-written field
list containing a wire key (hook_name) that never existed (the real tag is
hook); themes failed late (a bad colour aborts compile at apply time) or
silently (bad dimensions dropped, unknown option keys ignored); and nothing
told an extension which API permissions it could declare.
Decision
- Contracts live in code, docs describe them. A new
internal/vcbpackage is the single source of truth: an enumerated hook catalogue (exactly the names the host fires:article.create,article.update,article.delete,cache.purge), a versioned on-disk plugin manifest (plugin.json, strict parsing — unknown fields are errors), a versioned theme manifest (theme.jsonwrapping the realtheme.Tokens), and the capability vocabulary re-exported frominternal/apikeys. Manifests convert 1:1 into the runtimesandbox.Manifest, so nothing validated ever differs from what runs. - Validation is the same code the host runs.
internal/vcb/validatelifts every rule from its enforcement point — the compiler's hex/dimension/ font primitives (now exported frominternal/themeand shared withCompileCSSitself), the option schema, the fixed category vocabulary, the sandbox's hash check, the registry's HTTPS+SHA-256 requirements, the API-permission enum (wildcards refused in manifests: least privilege).tools/vayu-compatis a standalone CLI over that package — the first tool module allowed to import parent internals (module pathgithub.com/johalputt/vayupress/tools/vayu-compat+replace ../..), precisely so it can never drift from the host. - Fail closed, fail early. Unknown manifest schema versions are refused outright; unknown fields are parse errors; unknown hooks, capabilities, option keys/values, and categories are validation errors. Silence is never the failure mode.
- The ecosystem docs now match reality. The golden test derives the
frozen IPC field list from the real
sandbox.Request/Responseviaencoding/json(deliberate golden update:hook_name→hook, plus the capabilities and response shapes are now frozen too); SPEC.md, the example plugins, api-contracts.md and sandbox-boundaries.md were corrected to the fired hook names and the real wire key. Built-in presets are tested against the published theme contract, so the contract and the shipped themes cannot disagree. - CI gates the contract.
scripts/preflight-ci.shgains a named VCB section:go test ./internal/vcb/...plus an explicit build+test of thetools/vayu-compatmodule (nested modules are invisible to the root./..., so the gate exercises it directly). - Deferred, documented, not promised: unifying the six event namespaces
(plugin hooks, webhook events, outbox envelope types, SSE names, two typed
buses) is a deep refactor; the schema registry's type checker stays shallow
(strings/integers/booleans) and unwired; in-process
HookFuncs keep full process privileges — third-party code must ship as a sandboxed subprocess. All three are recorded as known limitations in the Bible itself.
Consequences
- A developer or AI agent can write
plugin.json/theme.json, runvayu-compat check, and know the package will install and behave — the validator's findings name the exact enforcement that would otherwise bite. - The provisioning loop closes: a manifest's declared
api_permissionsare the boxes the operator ticks in the VayuOS key manager (ADR-0134), so an extension runs with exactly the access it declared. - The hook catalogue is now load-bearing: adding a host hook means adding it
to
internal/vcb/hooks.go(and docs) or extensions cannot subscribe to it. - The plugin IPC golden finally freezes the real wire contract; any future
change to
sandbox.Request/Responsefails the golden and demands a deliberate update per the stability matrix. - Public documentation ships at
/docs/compatibility/vcband/docs/compatibility/vayuapi(the URL the API's 403/429 errors have linked to since v3.13.38 — previously a 404, now real).