Typed Query Runtime
@opsdna/query-runtime is the heart of the runtime path. It defines a finite, closed algebra of query operations, validates typed query plans against that algebra and the current registries, executes plans through permission-gated adapters, and seals every run into an AnswerRun audit trace.
Why a closed algebra?
A planner backed by a language model will happily invent an operation called computeSuperReturn if you let it. OpsDNA doesn’t. The vocabulary in packages/query-runtime/src/algebra.ts is a const tuple of exactly fifteen operations, wrapped in a schema so nothing outside the list ever decodes:
// packages/query-runtime/src/algebra.ts
export const queryOperations = [
"resolve", "select", "filter", "join", "aggregate",
"coverageDiff", "compute", "reconcile", "resolvePersistedFact",
"rank", "retrieveEvidence", "project",
"invokePolicy", "proposeEffect", "emitGap",
] as const
export const queryIntents = [
"metric_lookup", "coverage_diff", "ranking", "reconciliation",
"document_lookup", "workflow_invocation", "action_preparation",
"policy_check",
] as constNew domain capability arrives by registering entities, metrics, and computations in the registries — never by widening this list at runtime. When a question needs something the system can’t do, the plan emits a typed gap (emitGap, packages/query-runtime/src/gaps.ts: unknown_metric, missing_extraction_schema, missing_source_data, insufficient_evidence, …) instead of hallucinating an answer.
Plan → validate → gate → execute → seal
Planning
packages/query-planner/src/planner.ts exposes createLiveQueryPlanner, whose plan(question) returns an Effect producing a PlannerOutcome. The candidate extractor (heuristic or OpenAI-backed) emits concept-resolution candidates; the deterministic resolver binds them to registry concepts before a TypedQueryPlan exists. The AI-backed extractor is opt-in only (OPSDNA_ENABLE_LIVE_QUERY_AI=1 + OPENAI_API_KEY); otherwise the route returns a typed configuration gap. packages/planner-eval is fully deterministic and owns no LLM calls.
Validation
packages/query-runtime/src/validation.ts provides validateTypedQueryPlan and the stricter validateTypedQueryPlanForExecution, and computes requiredPermissionGatesForOperation for each step — a compute step demands a computation gate decision, a retrieveEvidence step a retrieval decision, and so on.
Execution
executeTypedQueryPlan in packages/query-runtime/src/execution.ts takes a QueryExecutionContext that makes every dependency explicit:
// packages/query-runtime/src/execution.ts (abridged)
export interface QueryExecutionContext {
readonly queryText: string
readonly authorizationEnvelope: AuthorizationEnvelope
readonly computationRegistry: ComputationRegistrySnapshot
readonly permissionEngine: PermissionEngine
readonly entityAdapter: EntityAdapter
readonly evidenceAdapter: EvidenceAdapter
readonly computationInputAdapter: ComputationInputAdapter
readonly reconciliationAdapter?: ReconciliationAdapter
readonly domainPolicyAdapter?: DomainPolicyAdapter
readonly registryVersions?: {
readonly semanticRegistry?: string
readonly computationRegistry?: string
readonly permissionPolicyBundle?: string
}
readonly plannerModelId?: string
readonly plannerPromptHash?: string
readonly intentClassificationAudit?: Record<string, unknown>
}Note the registryVersions pins and planner provenance fields — an AnswerRun records which registry snapshot and which model/prompt produced it.
Sealing
packages/query-runtime/src/answer-run.ts defines the AnswerRun record: plan hash (hashTypedQueryPlan from hashing.ts, over @opsdna/canonical-json), per-step permission decision bindings and their hashes, resolved step output proofs, gap-reason hashes, projection output hashes, and an aggregate trust state derived from audit verdicts (audit.ts, audit-checkers.ts, audit-seal.ts). Branded hash types come from @opsdna/brands (PlanHash, PermissionDecisionHash, ResolvedStepOutputProofHash, …), so you cannot pass the wrong hash to the wrong slot at compile time.
A real path end to end
Run the seeded demo:
bun run demo:largest-lpIt plans “Who was my largest LP in Fund 2?”, executes resolveEntities → rank → retrieveEvidence against seeded commitments, validates evidence-graph references, and prints a cited answer. bun run demo:onerous-side-letter does the same for a rubric-scored ranking (side_letter_onerousness).
Two pitfalls. First, packages/query-execution is legacy — it predates this runtime and exists only for demo/intake and side-letter fixtures; new contracts go in query-runtime. Second, permission decisions inside a run are bound to the plan hash: if you mutate a plan after decisions were issued, AnswerRun/PolicyRun schema refinements reject the record. Re-plan and re-gate; don’t patch.
Persistence of AnswerRun/PolicyRun traces lives in packages/persistence — see the entity model for tenant scoping, and Policies & Workflows for the standing-query counterpart.