ADR-0028: QuerySpec is the wire surface; IR-2 stays kernel-internal
ADR-0028: QuerySpec is the wire surface; IR-2 stays kernel-internal
Status: accepted (2026-07-03)
Number note: 0016–0020 were taken by parallel workstreams that merged to main
while this one was in flight (main runs through 0027); this ADR takes 0028 to avoid the ADR-number collision
the Book keeps hitting. (In code the type name is QueryPlan / SecuredQueryPlan;
“IR-2” is the LLD taxonomy label the title preserves.)
The data plane locked one Query IR, many surfaces (data-plane §9, DP-1) and the
surface workstream ratified a concrete client grammar — QuerySpec v2 with a closed
nine-operator where list — under an explicit renegotiation clause: “when kernel
IR-2 lands, the ref shape is renegotiated against what the SDK actually emits”
(surface-system D6/D14). Building the kernel query pipeline forces the renegotiation:
what serializes on the wire, how the grammar grows to cover the full IR-2 node set
(graph, spatial, text, vector, temporal, aggregate, series, standing), and how
“every implementer executes exactly this” stays true once there are two implementers.
QuerySpec is the wire surface; IR-2 never serializes publicly. The seam’s endpoints accept QuerySpec — the shape the surface runtime already speaks — and stage 1 of the pipeline (BIND) is the QuerySpec→IR-2 compiler. The IR remains a closed, kernel-owned plan language free to evolve until its own graduation events (data-plane §10.1); clients never construct plans. Rejected: serialized IR as the wire format (freezes the IR before OPTIMIZE/EXECUTE have exercised it; puts a compiler in every client); GraphQL as the core API (its resolver execution model invites per-field resolution and middleware policy — the post-hoc enforcement DP-4 bans; its open composition is the opposite of a closed install-validated grammar; it remains a later generated projection, per §4.1); dual raw-IR endpoints (drift).
The where grammar is a recursive condition tree with a closed leaf set.
Cond = {all: Cond[]} | {any: Cond[]} | {not: Cond} | Leaf, depth-capped at BIND.
Leaves: trait-operator (the ratified nine ops; within remains the range operator),
hasTrait, hasLink, settledness (the Trust Machine is filterable, per canon
“queryable, never hidden”), geo (within | intersects | near, GeoJSON), text
(match | phrase | prefix), similar (named embedding index, k, minScore — policy
filters the universe before retrieval). Every leaf validates against the trait’s
parameterized kind at BIND, mirroring install-time validation. QuerySpec v2’s flat
where array remains valid forever as {all: […]} sugar. Grammar precedent:
Foundry’s ontology filter trees; the leaf-type (not op-overload) encoding is ours,
because parameterized kinds give leaves better validation targets.
Four typed doorways, one pipeline. POST /v0/query, /v0/aggregate,
/v0/series, /v0/subscribe — split by answer kind (entity set · grouped rows ·
time points · diff stream), which is tier semantics, barrier coherence, and budget
class made visible (PR-7’s doctrine at the wire). All four enter the same
BIND → SECURE → ROUTE → OPTIMIZE → EXECUTE → ACCOUNT pipeline; the condition tree is
the shared organ (aggregate.over and series subject-selection embed full QuerySpecs).
Recurring history×entity composites are declared derivations/materializations
(DP-10, ADR-0015), never a fifth endpoint. With /v0/series absorbing read_series
and graph absorbing the trace syscall and CLI graph find, no read path outside
the seam survives — PR-2 becomes literally true in code.
Delivery is a content-negotiated matrix over one streaming execution.
application/json (bounded page; keyset cursor = opaque sort-position + spec-shape
hash, never OFFSET) · application/x-ndjson (row frames flowing
Postgres→envelope→socket at O(row) memory; an in-band terminal summary frame
carries count, watermark, truncation, and spend — a stream without its summary is
detectably incomplete) · application/vnd.apache.arrow.stream (columnar; Arrow
schemas derived from the ontology’s parameterized kinds, not sampled) · export
mode (ACCOUNT converts the plan to a worker job streaming NDJSON/Parquet to the blob
port; output is a registry artifact with an attestation) · SSE for subscribe
(snapshot-then-ordered-view-diffs: enter | update | leave stamped with OrderTokens,
resume tokens over a bounded ring buffer, resync when unresumable, heartbeats
carrying visibleThrough; POST-SSE at Lean, WebSocket is the P26 Scale graduation).
Diffs are view-membership deltas, never raw entity CDC for clients to re-filter.
The strict mirror is enforced by a shared conformance suite, not doctrine. One
canonical fixture realm; one language-neutral directory of
(queryspec.json, expected-result.json) golden pairs covering every executable
grammar block and every BIND rejection class; the kernel runs them against seeded
Postgres, the TS FixtureKernelClient runs the identical files — drift fails the
laggard’s CI. Ratifying a grammar extension means adding its golden pairs.
The grammar is ratified once; execution arrives in stages. Blocks the pipeline
does not yet execute are rejected at BIND with a typed NOT_YET_SERVING error naming
the gate — never silently ignored, never approximated. v0 executes: from
(kind | kinds | interface), the tree with trait/hasTrait/hasLink/settledness leaves,
select (resolved-only; evidence deny-all until the capability story), time.latest,
graph.traverse/graph.trace (named ontology paths, until, frontier honesty),
sort/limit/cursor, afterToken barriers end-to-end, aggregate (count/sum/avg/min/max,
groupBy, bucket), series windows, subscribe (enter/update/leave + debounce). Deferred
behind named gates: geo (spatial read-model decision), text, similar,
time.asOf|window|branch on the query door, percentiles/having/countDistinct,
Compare, the §24.1 pattern grammar beyond debounce.
Consequences: the surface team’s D6/D14 renegotiation clause is resolved — their
grammar and this contract are now the same document set (grammar reference:
architecture/query-surface.md; pipeline semantics: architecture/data-plane.md);
sdkgen, GraphQL, and MCP tool manifests all project from this wire contract; every
future grammar change is a QuerySpec extension ratified against this ADR’s staging
discipline plus conformance pairs; and the kernel accepts the maintenance cost of two
closed vocabularies (wire grammar above, leaf-plan port vocabulary below) as the
price of adapter-side translation with no SQL in the kernel.