Skip to content

ADR-0033: The kernel wire route surface — /sys/<verb>/<name>

ADR-0033: The kernel wire route surface — /sys/<verb>/<name>

Status

Accepted. Supersedes the routing and doorway decisions of ADR-0028, and amends the error type-URI spelling of ADR-0014 (/sys/errors/{CODE}/sys/meta/errors/{CODE}). 0028’s QuerySpec-is-the-wire-surface thesis, condition tree, envelope, delivery matrix, and conformance discipline stand unchanged; so do 0014’s error-model decisions apart from that URI spelling.

Date

2026-07-04

Context

ADR-0028 ratified that QuerySpec is the wire surface and the QueryIR never serializes, and named provisional doorway routes: POST /v0/query, /v0/aggregate, /v0/series, /v0/subscribe. The implementation then diverged from that — query-surface.md and the server (crates/services/vinxi-kernel-server/src/routes.rs) grew a different, PascalCase /sys/<verb>/<Name> surface (/sys/query/Entities, /sys/act/RegisterResourceVersion) and treated ADR-0028’s /v0/* as shorthand for it. That /sys/* spelling was never itself a governed decision, and the surface it grew is inconsistent:

  • PascalCase in paths (Entities, RegisterResourceVersion) — unusual, and since HTTP paths are case-sensitive, an invitation to /Entities vs /entities 404s.
  • The third path segment means two different things: for Act it is an open, reality-declared operation name (RegisterResourceVersion); for Query a closed answer-kind (Entities).
  • /sys/query/Subscribe collides with the fact that Subscribe is one of the six verbs (ADR-0002), not a Query answer-kind. The implementation folded it under the query pipeline; the six-verb model disagrees.
  • /sys/errors is a resource-style observability outlier squatting under the same /sys/ prefix as the RPC verbs, muddying what /sys/ means.

The verb-first paradigm is not in question. The seam is a syscall surface (the-seam §V, “the syscall table”); ADR-0002 makes the six verbs the only interaction surface; ADR-0028 explicitly rejected resource-REST and GraphQL for the read seam (per-field resolvers invite the post-hoc policy DP-4 bans). What was missing is one governed spelling rule.

Decision

One rule: POST /sys/<verb>/<name>. Lowercase-kebab. The path is operation identity, the query string is modifiers (?explain=…, ?dryRun=…), the body is payload (the QuerySpec, the Act input, ReadOpts). The name is always in the path — never a query param, never a body discriminator — so a gateway, audit log, per-operation metric, rate-limit, or capability grant can key off the URL without parsing the body (the transport-legibility DP-4 wants).

No version segment. QuerySpec is v0 — one grammar that evolves compatibly (extensions + NOT_YET_SERVING staging + the conformance mirror), so a path version would be dead weight. The “v2/v3” labels were parallel-workstream churn and are retired. A header/content-type version is reserved as the break-glass for a genuinely-breaking future ABI change; it is a floor, never a licence to skip compatible evolution.

The six verbs organize the surface (ᴿ = ratified shape, HTTP execution reserved; only /sys/query/entities and /sys/act/register-resource-version are served today):

Query (one-shot pull) Subscribe (standing push)
/sys/query/entities /sys/subscribe/view ᴿ
/sys/query/aggregate ᴿ /sys/subscribe/events ᴿ (undesigned)
/sys/query/series ᴿ
Act /sys/act/<action> served: register-resource-version ; reserved base:
create-entity · install-schema ; reality-declared:
open-ticket · raise-alarm · … (logic in userland — ADR-0031)
Assert /sys/assert/<fact> ᴿ series · link · state
Call /sys/call/<driver-op> ᴿ
Schedule /sys/schedule/<name> ᴿ
  • Subscribe is its own verb, not a Query doorway. <name> says what you subscribe toview (a QuerySpec, membership diffs enter|update|leave) or events (a governed, seam-filtered event stream — reserved, distinct from the raw-CDC 0028 banned). Query = pull answer-kinds; Subscribe = push subscription-kinds. Pull-vs-push is the verb boundary.
  • Act <name> may be domain vocabulary — an Action is a declared, kernel-visible, SDK-projected operation-type (the-seam §VIII), so the reality extends the Act namespace with its declared actions.
  • Assert <name> is a domain-neutral fact-kindseries and link exist as kernel syscalls (assert_series, assert_link); state is reserved — entity state is written today via the Act create-entity path, not a dedicated Assert. Assert declares facts, not named operations; there is no “assertion type” to name (ADR-0031). No /sys/assert/* HTTP doorway is mounted yet.

/sys/meta/<topic> (GET, read-only) is the kernel’s self-description, not a verb: the error catalog (/sys/meta/errors, /sys/meta/errors/{code} — was /sys/errors, amending ADR-0014’s type-URI), version, capabilities (the SERVING vs NOT_YET_SERVING staging), and reality/schema/ontology introspection — the installed kinds/traits/links/declared-actions/paths that sdkgen, the GraphQL projection, and MCP manifests all project from. Two flavors: static (no context) and scoped reality introspection (carries tenant/universe/branch). Reading the schema is meta; changing it is an Act (/sys/act/install-schema) — introspection is read-only, mutation is always a verb. /healthz and /metrics stay at root (infra convention; not the seam).

Alternatives Considered

  • Name-in-body (POST /sys/act with {action, input}, one route per verb): rejected — six stable routes, but every operation becomes invisible to the transport, forcing body-parsing policy (the DP-4 smell); the “unbounded Act paths” worry is moot since the path set is exactly the reality’s declared actions, which sdkgen enumerates.
  • Resource-REST (/entities, /entities/{id}): already rejected by ADR-0028; the seam is verbs, not CRUD resources.
  • Path versioning (/sys/v0/…): rejected now — the grammar evolves compatibly by construction; header versioning is reserved instead.
  • PascalCase paths: rejected — preserves the SDK symbol on the wire (a codegen convenience) at the cost of the URL idiom and case-sensitivity bugs; the kebab→symbol transform is deterministic and owned by sdkgen.

Consequences

  • The live route /sys/query/Entities renames to /sys/query/entities; /sys/errors[/…] moves to /sys/meta/errors[/…], and the RFC-7807 error type URIs move with it. Small, mechanical, and it is the “fix the lame URL” the workstream set out to do.
  • ADR-0014’s error-model contract stands, but its type-URI spelling is amended here (/sys/errors/{CODE}/sys/meta/errors/{CODE}); the index carries the forward pointer since 0014 is immutable.
  • ADR-0028’s grammar, condition tree, envelope, delivery matrix, and conformance discipline are untouched; query-surface.md is updated to the new routes and to retire the “v3” label (→ v0).
  • Subscribe leaving the Query doorways is a doc change now and a code change when Subscribe’s execution rung lands (it is NOT_YET_SERVING today).
  • sdkgen, the GraphQL projection, and MCP tool manifests project from stable verb + name paths and the /sys/meta introspection surface.
  • /sys/ is blessed as the kernel’s own namespace: the six-verb governed seam (POST /sys/<verb>/<name>, context + body) plus kernel-meta introspection (GET /sys/meta/<topic>, no verb).