contract read · 20-read.md · 21-memory.md · guide · card
The read face
Implementers use this page for SQL queries, ranked retrieval and read responses. Each connection registers relations with the caller’s access restrictions. Reads write no rows and hold no leases. Queries use existing index sidecars and build no indexes.
Query components and their connections to storage, access controls and memory:
register
Registered relations, query tools, templates, the embedded SQL engine and HTTP transport.
connection-viewstestEach session connection creates or replaces one view per manifest table, scanningexplicit-file-list. Statements reuse these views undersession-pool. No view directory exists on disk.declaration-settestThe face registers the tables ofdeclaration-set, so aprimary_keya pipeline file declares reachesdedup-viewon the operator verb and the tool server alike.enginetestEvery profile serving reads links an embedded columnar SQL engine that reads Parquet with standard SQL. External processes read the same files without that embedded engine.quiet-tabletestA quiet table registers asempty-run. Reading that table returns an empty result rather than a missing-relation fault.Why
P4bare-nametestIn caller statements, templates, ranking queries and file previews, a bare table name resolves to the caller’s registered relation. That relation carries the caller’s restriction.Why
P5tool-settestThe face exposescontext.describe,context.query,context.reference,context.lineage,context.execute_queryfor templates,context.files,context.file,context.subscribe,corpus.retrieve,memory.recall,memory.traverseandmemory.explainas its closed tool set.bound-argumentstestExceptmemory.recall,memory.traverse,memory.explain,context.subscribeandcontext.lineage, read tools, including templates, acceptas_ofandvalid_as_ofand echoecho.valid-as-ofapplies only to tables the read touches.Why
because a session registers every granted table, and wrapping one declaring no pair refuses reads that never name itbound-listingtestcontext.filesand acontext.describenaming no table select underas_ofalone; each ignoresvalid_as_ofand echoes only itsas_ofpart.Why
because valid time narrows rows, and a listing returns files and table names, never rowsavailable-bound-argumenttestTools underbound-argumentsalso acceptavailable_as_ofand echobound-echo.available-as-ofapplies only to tables the read touches.Why
because a session registers every granted table, and wrapping one declaring no availability time refuses reads that never name itavailable-bound-listingtestA listingbound-listingnames ignoresavailable_as_ofand echoes noavailable_as_ofpart.Why
because availability time narrows rows, and a listing returns files and table names, never rowsdescribe-payloadtestcontext.describereturns row count,size-estimate, schema fingerprint, description,column-hints, declared indexes, partition scheme,limits.max_rows, zone label, lexicon and example queries.size-estimatetestcontext.describereportsbytes, the serialized JSON length of the caller’s restricted rows, andestimated_tokens, one per 4 B of that length, rounded up.Why
because a client choosing between a full-context read and a ranked one compares the corpus against its context window before reading itjoin-routetestCross-table joins use onecontext.querystatement over registered relations or a published model listed by describe. No tool constructs joins for callers.Why
because a statement already composes each relation’s restriction, and a join tool restates that composition outside the guardcolumn-hintstestA table’scolumn_hintsmap supplies optional per-column hints;context.describeincludes each hint only with a column the session registers.Why
because a hint for an absent column describes a relation the registered schema cannot querydescribe-zonetestcontext.describereports the session zone assession_zone, and per table, listed or described,zone_admitted: whether the table’s effective allow-set admits that zone.advertised-is-enforcedtestA table’s publishedlimitsblock lists a bound exactly when the engine applies it.Why
because a published number and a delivered guarantee cannot disagree when one derives from the otherbudget-argumentstestEvery read tool butcontext.subscribe, whichoverrunbounds, accepts optionalmax_duration_msandmax_response_bytesrequest fields, each with no default.duration-no-statementtestA read tool running no SQL statement acceptsmax_duration_mswithout triggeringduration-ceiling.Why
because no connection executes a statement for its deadline to interruptbudget-advertisementtestcontext.describelists a table’slimits.max_duration_msandlimits.max_response_bytesunderadvertised-is-enforcedand omits each undeclared limit.template-projectiontestEach manifest template registers a tool named by the template’s identifier. The tool’s typed schema requires every declared positional parameter.file-listingtestcontext.filesreturns store-root-relative paths for the tables the caller reads, and a table outside that set contributes no path.Why
P5file-preview-targettestcontext.fileresolves a path to its(table, run_id)and reads it through that table’s registered relation. A snapshot part, a traversal, an absolute path or a ledger file raisesFilePreviewNotATable.ledger-relationtestEach table’s per-run request ledger registers as the child relation<table>__requests, holding identifiers, connector, method, host, status and timing of mediated outbound calls.scoped-ledgertestThe child relation registers on the owner read alone: a credential carrying no tenant scope, over a table carrying no row policy. Naming a closed ledger raisesLedgerNotTenantScoped, stating what closed the relation.no-tenant-ledgertestNo tenant-scoped projection of a request ledger registers;scoped-ledgeris the ledger’s one read path.Why
because ledger rows carry no tenant column, and a projection filtering on a dimension its rows lack discloses every tenant’s callslexicon-surfacetestThe describe payload carries the store’s numeric-identifier and badge vocabulary. Aliases, time-window phrases and distillation examples stay on the serving side.reserved-relationtestThe engine’s reserved table namespaces register like any other relation, with no privileged path underneath.Why
P5
HTTP transport
contextful serve --http admits each request, bounds its connections and drains on stop.
network-transporttestcontextful serve --http <addr> --audience <aud> --max-in-flight <n>serves MCP Streamable HTTP atPOST /mcp. The stdio tool server answers each request’s single JSON-RPC message asapplication/json.concurrent-statementstestRequests on any number of connections run concurrently. Each statement uses its own engine connection underpool-connectionsand delays no other request.serve-declarationtestA serve with no--audience, or no positive--max-in-flight, raisesServeDeclarationMissingnaming the flag and binds no listener; neither ships a default.Why
P3past-ceilingtestA request arriving while the ceiling’s count of requests is in flight answers503with aRetry-Afterof 1 s and admits nothing.connection-ceilingtestAn accepted connection holds a slot until the server writes its response. With all slots held, the server answerspast-ceilingbefore parsing the request and starts no thread.Why
because a request head arrives before admission, and a slot taken only after reading it lets an unadmitted caller hold one thread per connectionper-request-admissiontestEach request carriesAuthorization: Bearer <credential>, orDPoP <credential>with aDPoPproof header. The listener admits each credential underpossession-bindingandnetwork-bearerand restricts reads to that credential’s grants.credential-missingtestA request carrying noAuthorizationcredential raisesHttpCredentialMissingwith401and reads no row.admission-refusedtestA request its admission refuses answers401carrying the refusal’s identifier, except a full nonce cache, which answers503undernonce-cache.per-request-revocationtestThe server re-reads--denylistfor each request and rejects a revoked credential on the next request without restarting. An unreadable denylist returns503and admits nothing.stateless-sessiontestA notification answers202with no body. The face holds no protocol session and opens no server stream: aGETorDELETEon/mcpanswers405, any other path404.request-bodytestFor a request body over 1 MiB, the server returns413and stops reading the body.Why
because the listener reads the body before admission, and an unbounded body lets an unadmitted caller hold memoryhealthtestGET /healthanswers200with the build identity ofbuild-identity, admitting no credential and reading no row.Why
because a container runtime probes readiness before any caller holds a credentialhealth-hydratedtestGET /health?ready=hydratedanswers aspast-ceilinguntil snapshot-set hydration completes, then ashealth.Why
because a load balancer probing plain/healthroutes reads to a container still hydrating, which answers each one503stop-draintestOnSIGTERMorSIGINT,contextful serve --httpadmits no new request, answers every connection it accepted before the signal, and exits 0 once none remains, at most 20 s after the signal.Why
because a container platform kills a stopping process after its grace period, and a process exiting at the signal resets every request it holdsdrain-unfinishedtestA connection still unanswered at the bound ofstop-draincloses unanswered; the server names their count on stderr and exits non-zero.Why
because an operator reading a clean exit as a complete drain misses the requests the bound cut offdraining-healthtestFrom the stop signal on, every new connection,GET /healthincluded, answers aspast-ceilingbefore its request is parsed.Why
because a load balancer reading503from/healthstops routing to a target whose admitted requests are still finishing
guard
SQL validation, allowed relations, operator statements and template arguments.
single-read-only-statementtestAdmitted text parses to exactly one read-only SELECT. Attach, copy, install, load, pragma, set, any schema change, any data modification and a piggybacked second statement raiseStatementNotReadOnly.engine-own-parsetestThe guard checks the syntax tree serialized by the SQL engine. The tree determines whether the statement is read-only; no keyword blocklist determines admission.Why
because guard and executor cannot disagree about what a text means when they share one parserelation-allowlisttestEvery base relation names a view registered for this caller or a common table expression the statement declares. A bare file path is a base relation and falls under this rule.whole-tree-walktestThe guard checks the entire tree, including select-list subqueries, union arms and pivot sources. Common-table-expression names resolve only within their declaring scope and nested scopes.unregistered-relationtestThe guard rejects an unregistered base relation underungranted-table. The error echoes the requested name and no relation belonging to another caller.table-functiontestA table function anywhere in the tree, and a schema-qualified reach into a system catalog, raiseTableFunctionRefused.statement-provenancetestLocal operator statements, template bodies and engine-composed reads execute without caller-SQL validation. The guard validates capability-token callers’ SQL. No network interface exposes raw operator execution.quoted-identifierstestEvery schema- or manifest-derived identifier is double-quoted where rendered, admitting any UTF-8 vendor field name as an identifier and never as an expression.template-declarationtestA template declares an identifier, one SQL statement, positional parameters writtenname:typeover integer, float, string, timestamp and boolean, and an optional row ceiling.template-reserved-parametertestManifest validation rejects template parameters namedas_of,valid_as_of,zoneorpin. Every template tool reserves those names for read bounds, zone and build pins.Why
because a parameter sharing a read argument’s name hides that bound from the template toolavailable-reserved-parametertestManifest validation rejectsavailable_as_oftemplate parameters undertemplate-reserved-parameter. Every template tool reserves that name for the availability bound.Why
because a parameter sharing a read argument’s name hides that bound from the template tooltemplate-relation-shapetestManifest validation and server startup raiseTemplateNamesForeignRelationfor template SQL naming anything except store tables as plain identifiers. Template identifiers starting withcontext.,corpus.ormemory.also raise that error.template-bindingtestA missing, unknown or type-mismatched argument raisesTemplateArgumentRejectedahead of execution, with no coercion. Placeholders are$1…$nor?in declaration order, or exactly the declared names.query-bindingtestcontext.querybinds$nameor contiguous$1…$nfromparameterstyped undertemplate-declaration, without coercion. Positional?, missing, unused, untyped or mismatched parameters raiseQueryParameterRejectedbefore execution. Bound values passscope-guard.Why
because a value bound to a placeholder reaches no parser, and the tenant guard decides a bound value as it decides a literalstartup-time-checktestTemplate checks are caller-independent and run once at startup; a request pays nothing for them.
respond
Response fields, cell encoding, row and byte limits, truncation and diagnostic metadata.
one-projectiontestCLI JSON output, HTTP and the tool protocol serialize the same response:columns,rows,truncated, and optional restriction, resolved-build, provenance, retrieval and time-bound blocks.Why
because a guarantee landing on one transport and missing on another is worse than a guarantee on nonerow-ceilingtestThe executor applies each table’slimits.max_rowsto delivered rows. Truncation detection uses an extra-row probe (1 rows). This ceiling limits no work performed.face-ceilingtestEvery read interface, includingcorpus.retrieve, delivers at most 10000 rows. This ceiling contributes torow-ceilingwithout requiring a declaration and bounds the candidate window.Why
because an undeclared ceiling otherwise streams a whole table into one response, and a caller learns of the cut fromtruncatedrather than from memory exhaustionengine-composed-ceilingtestEngine-composed table reads, derive sources and memory reads apply the least declared grant or table row ceiling, excluding the interface ceiling. Without declared ceilings, these reads deliver every row.Why
because a derive or memory pass over a silently cut table records partial input as completetruncation-is-exacttestFor a row ceiling, the serializer setstruncatedexactly when the extra probe row exists. Comparing returned counts with requested limits does not determine truncation.Why
P4cell-encodingtestSQL NULL is JSONnulland nothing else is. Non-finite floats are"NaN","inf","-inf"; temporal values are ISO-8601 strings, intervals ISO-8601 durations; an enum is its label; a union is its text form.bytes-and-vectorstestThe serializer encodes binary as padded base64. Fixed-size float arrays, including vector columns, become JSON arrays whose elements use float-cell encoding.nested-valuestestLists encode as JSON arrays. Structs and maps encode as JSON objects keyed by field name or key text. Each item, field or value uses cell encoding.wide-number-shapetestSQL type alone determines JSON encoding. Integers up to 32 bits, finite floats and decimals up to 15 digits encode as numbers. Wider integers and decimals encode as exact decimal strings.Why
because a wire type that varies with the values on one page breaks a typed client on the nexttype-is-the-celltestThe response carries no separate type list. Each cell’s JSON type declares its type.one-response-shapetestEvery read answers with one complete result set in one response; no read tool streams partial rows.Why
P4zero-rows-is-successtestFewer rows than the requested limit, zero included, is a success.Why
P2match-counttestMatch counts report the number of matching rows scored by the ranker in that call. These counts require no diagnostic metadata.coverage-is-a-counttestA claim of missing corpus coverage requires a table count after the full-scan fallback, without recency truncation. The highest ranked score supplies no coverage conclusion.Why
because the calibrated lexical leg scores the best row 1.0 and IDF lifts one incidental rare-term mentioninternals-opt-intestWithinternals: true, every read tool and HTTP response includes a separate object containing executed SQL, engine name, applied limit, row count and elapsed milliseconds. Otherwise, the response omits that object.query-internals-parameterstestUnderinternals: true,query-bindingincludes the validated typed parameter map beside executed SQL in the internals object.duration-ceilingtestUnderduration-ceiling, the engine interrupts the statement’s connection at its deadline.ReadDurationExceedednames the ceiling, source and elapsed milliseconds. The response contains no rows; other statements continue running.byte-ceilingtestUnderbyte-ceiling, the serialized response includes only whole rows within the selected ceiling. An envelope or first row exceeding that ceiling raisesReadResponseTooLargeand returns no rows.truncation-causetestA truncated response carriescontextful.truncationwithby,ceilingandsource:rowsadmits grant, table, request, template or face;bytesadmits grant, table or request.truncation-tietestIf both ceilings cut the same next row, bytes takes precedence. Equal ceilings within one dimension choose the first eligible source: grant, table, request, template, face.Why
because one cut needs one deterministic cause for a client to branch onoperator-metadatatestOperator table metadata includes column count and backing files. A row count comes from a count query rather than a stored field.Why
P3restriction-blocktestThe restriction block includes session zone, incognito flag and an entry per touched relation excluded or column-masked by zone:table,excluded,rows_dropped,columns_masked. Reads withholding no touched relation omit the block.in-band-errortestOn the tool protocol a refusal arrives in-band, as a protocol error object or a result flagged as an error under transport success.Why
P2paths-stay-insidetestAn ordinary read’s result carries no store path. Provenance arrives as columns naming table, run, connector version, ingestion instant and authoring subject.
query
The operator’s raw statement verb on the command line: its arguments, the relations it registers, its row bound and its output.
operator-verbtestcontextful query --json [--project <name>] [--declaration <path>] [--limit <n>] [--incognito] <sql>runs one raw operator statement understatement-provenance. The command printsone-projectionas one JSON line on standard output.one-statementtestText the engine’s parser splits into other than exactly one statement raisesQueryNotOneStatementbefore any statement runs.Why
because preparing multi-statement text executes every statement but the last and answers only the last, dropping the earlier results and side effects unseenproject-relationstestWithin a project, outsideincognito, all stored or manifest-declared tables register under bare names as unrestricted base relations at latest committed state. Quiet tables followquiet-table.Why
because operator text is the store owner’s own, and a restriction bounds a capability-token callerincognitotestWith--incognitoor[project.zone]settingincognito_default = true, each relation passes the zone step atlocal:device. Excluded tables return no rows; excluded columns return null.restriction-blockreports both.Why
because incognito pins the uncredentialed local owner’s reads to the fail-closed pair, and this verb is that owner’s readdiscovered-projecttestWithout--project, the projectdiscoveryfinds is the verb’s project. When discovery finds none, or the found project’s store is absent, no relation registers and the statement runs over no store.Why
because every other verb reads the nearestcontextful.toml, and a statement over files or literals needs no storeproject-storetestA--projectwhose.contextful/context/<project>/store is absent raisesQueryProjectAbsent.Why
because every declared table of an absent store registers as quiet, and an operator reads its zero rows as an answerdeclaration-defaulttest--declaration <path>names the manifest whose declared tables register; without it, thedefault-declarationfile does when present, and no manifest otherwise. With no project,--declarationrefuses byundiscovered.limit-truncatestest--limit <n>bounds delivered rows using the over-fetch ofrow-ceilingand setstruncatedundertruncation-is-exact. Without this flag, the query returns every rowraw-row-ceilingadmits.raw-row-ceilingtestAn operator’s raw read delivers at most the leastlimits.max_rowsamong the relations its parsed statement names, and at most--limitbeside it.Why
because a published bound the owner’s own verb ignores advertises a guarantee one face breaksjson-onlytestThe verb prints the JSON projection alone; it carries no text rendering, and a call omitting--jsonis a usage error.Why
because a second rendering is a second projection a script must reconcile with the firstengine-faulttestA statement the engine rejects exits non-zero with the engine’s message on standard error and nothing on standard output.Why
because a caller parsing standard output never mistakes a partial or empty envelope for an answer
reference
Resolution of one stored citation through the caller’s registered relation, including the authenticated distinction between an erased source and an absent version.
calltestcontext.referenceaccepts one table, run identifier and row sequence.contextful context referenceuses the same resolver under Read admission, returningone-projectionwithout selecting a filesystem path.admissiontestResolution appliesunregistered-relation, the caller’s registered row and column restrictions, andduration-ceiling,byte-ceilingandread-entrybefore publishing its verdict.erased-verdicttestAn admitted reference in the independently verified cumulative erasure index returnsavailable: falseandreason: erased. An absent reference outside that index returnsreason: missingwithout source payload.index-visibilitytestA tenant-scoped, row-policy-restricted, zone-excluded or column-masked caller receives an unreadable verdict before erasure-index access; only the owner metadata eligibility ofscoped-ledgeradmits an erased verdict.frontiertestBefore resolution and response publication, the resolver validates the same signed store frontier. The frontier’s erasure index binds store identity and cumulative reference versions throughstore-binding.
lineage
The applied dataflow graph narrowed to the relations one caller’s session registers, whole or walked from one table.
calltestcontext.lineageaccepts an optionaltable,directionanddepth, and returnsnodes,edges,truncatedandwithheldfrom the latest applied graph ofdataflow-edges.Why
because lineage states which relations feed one another, and the caller’s session decides which of them it nameswithheld-counttestA table or model the session does not register leaves the graph, one joined to a returned node counts inwithheld, and a step joined to no registered relation leaves uncounted.Why
because a lineage with its withheld tables removed uncounted reads as a complete one, and naming one discloses itseed-refusaltestAtablethe session does not register refuses asunregistered-relationdoes.Why
because a graph answering for an ungranted seed discloses that the table existsdepth-boundtestOne walk follows at most 16 edges each way and setstruncatedwhere an edge remains.node-pagetestOne response carries at most 1000 nodes and setstruncatedpast them.operator-verbtestcontextful context lineageservescallunder Read admission and prints the same answer.
Scenarios
read.lineage.withheld-count: WHEN a session reads one table of two a job joins, THEN the graph names that table and its step and reportswithheldas 1.read.lineage.depth-bound: WHEN a walk stops atdepthwith an edge remaining, THENtruncatedis true.
retrieve
Candidate rows for ranked retrieval: query tokens, relevance thresholds, per-table queries, deduplication and snippets.
ranked-calltestcorpus.retrieve({prefix, tables?, query, query_embedding?, filter?, kinds?, limit?, since?, min_score?, as_of?, valid_as_of?})returns ranked rows across item and artifact genres underprefixandtables, each with a snippet and provenance, composing the query surface and memory recall, storing nothing.table-listtestRetrieval queries session relations matching bothtablesandprefix. Names outside the session raiseRetrieveTableUnavailable, listing every unavailable name in input order. Absent and ungranted tables receive identical errors underdefault-deny.Why
because one identical refusal for an absent and an ungranted table confirms neither exists, and a silently skipped typo answers a different questionrelevance-floortestA row reaches a caller when its lexical score is null, at least the floor, or its vector score is positive. The floor is 2 tokens for queries of 3 tokens or more, else 1 tokens; a caller minimum overrides.content-tokenstestThe tokenizer lowercases query text and splits at non-alphanumeric characters. The tokenizer removes stop tokens, keeps non-ASCII runs of two or more characters, and deduplicates tokens in order.token-length-floortestAn ASCII run shorter than 2 chars leaves the content-token set.token-captestThe content-token set holds at most 12 tokens. An empty set omits the relevance predicate.script-split-matchingtestAn all-lowercase-alphanumeric token matches on an ASCII word boundary with an optional plural suffix; a token with any non-ASCII character matches by containment.Why
because no ASCII word boundary occurs beside an ideographplural-suffix-floortestA token under 4 chars takes no optional plural suffix.scored-texttestUndertable-block,retrieve.textoptionally lists columns for lexical scoring andrelevance-floor, excluding session-classed or masked columns. Withoutretrieve.text, scoring uses the table’s snippet columns.Why
because a snippet is chosen for display, so a match in a long body misses the floor and a categorical label in the snippet matches every rowempty-scored-texttestIf declaredretrieve.textleaves no column, retrieval skips that table’s full-text probe. For queries containing a content token, only positive vector scores admit its rows.Why
because a null lexical score passes the floor, so a text-only query otherwise returns such a table’s newest rows whatever it askstext-free-table-scores-nulltestA table without scored text columns has a null lexical score rather than zero. Only vector scores admit that table’s rows.Why
P4candidate-windowtestThe candidate window is 8 times the requested limit or 200 rows, whichever is larger. A filled window reports a recency-ordered slice. Where a vector sidecar answers, the window bounds no semantic recall:sidecar-generates-candidatesadds candidates beyond it.Why
P4reserved-columns-project-nulltestReserved projected columns — modality, language, prompt hash, kind — render null for a table lacking them, and its rows stay in the union.unsatisfiable-arm-dropstestInfilter, a scalar tests column equality; a value list tests membership. Rows match every condition. A table lacking a named column contributes no rows, including unfiltered rows.Why
P4filter-valuestestFilter values are strings, numbers or booleans; membership lists contain at least one value. Nulls, nested objects, nested lists and empty lists are malformed conditions.kindstestkindslists artifact kind strings as one membership condition onkind. A table without that column contributes no rows underunsatisfiable-arm-drops.engine-resolved-datetestThe engine resolves each table’s publication column itself, outside the caller’s filter.sincetestsinceis the inclusive lower bound of the question’s timeframe over the publication instantengine-resolved-dateresolves; the anchor isas_of, else the read’s clock. A date-only value means midnight UTC, and omittingsinceleaves every row in window.filter-budgettestA filter holds at most 32 conditions, each membership list at most 256 entries, and its serialized JSON,kindsincluded, at most 16 KiB.filter-budget-refusaltestBefore building per-table queries, retrieval checks the entire filter’s budget. An oversized or malformed condition raisesFilterBudgetExceededfor the whole read.row-key-deduptestRanked reads keep only the newest ingestion per(table, row key). If that version misses the relevance floor, no older copy substitutes. The key uses the declared content-hash column, otherwise null; projected values supply no digest.row-key-under-masktestFor content-hash columns masked bydrop,truncate,bucket,rangeor any combine, every row key is null. Masking byhashortokenizealone uses the digest as key.Why
because a mask merging distinct hashes lets a reader’s grants decide which rows a table holdsrow-key-stays-internaltestThe row key is absent from the outer projection.dedup-is-gatedtestThe deduplicating window function runs under the same condition as the relevance floor, and a browse-shaped read skips it.excluded-armtestAn arm whose table the session’s zone excludes contributes no candidate, and the ranked response names that table inrestriction-block.snippettestA snippet concatenates up to three text columns, usingretrieve.textwhen declared. Label-priority columns, such as title, summary, description and thesis, come first; prose-worthy columns follow in schema order.identifiers-never-snippettestSnippets exclude content hashes, URLs, identifiers and instant-valued columns.
index sidecars
Vector and full-text sidecars add candidates that rejoin through the caller’s relation.
sidecar-generates-candidatestestVector probes use query embeddings; full-text probes use content tokens. Each probe adds its top results to the recency window and rejoins throughvector-arm. Per-row scores equal exact-scan scores.Why
P5fulltext-probetestFull-text probes rank the entire snapshot by BM25 with one should-clause per content token. Tokens split by the sidecar tokenizer match resulting terms at consecutive positions. ASCII word tokens match plurals underscript-split-matching.Why
because a term outside the recency window is found only by a probe that reads every row’s postingssidecar-oversamplingtestOne probe requests 4 times the limit or 64 rows, whichever is larger, and a full-text probe 4 times that where the request carries restriction context.filtered-traversaltesttesttestUnder a caller filter or restriction context, a vector probe admits only identifiers the reader’s relation yields under the filter; traversal expands refused nodes’ neighbours and returns the probe size in admitted rows where that many exist.filtered-exact-thresholdtestA pre-scan of the arm’s relation under the filter counts the matching identifiers; under 4096 rows, the filtered probe ranks them by exact distance instead of traversal.filter-matches-nothingtestA filtered probe whose pre-scan matches no identifier adds no candidate and raises no error.adaptive-over-fetchtestA full-text probe, and a vector probe carrying neither filter nor restriction context, runs at most 4 rounds in total, doubling its size while readable rows fall below the requested limit. The retrieval block reports any remaining shortfall.sidecar-size-captestA vector sidecar exceeding 64 MiB resident memory stays unloaded; retrieval uses an exact scan. Resident memory includes each graph node’s 4 B probe mark and, for sealed sidecars, the graph file.fulltext-sealed-captestA sealed full-text sidecar file larger than 256 MiB stays unopened, and its arm adds no candidates.sidecar-falls-backtestRetrieval uses an exact scan for missing snapshots, missing matching sidecars, masked or zone-withheld identifiers or indexed columns, classed text columns, dimension or manifest mismatches, or unreadable dumps.Why
P4gain-never-losstestFor each reader, accelerated retrieval returns a superset of the exact scan’s rows. Different readers querying one snapshot may receive different rows.
Scenarios
read.retrieve.since: WHENcorpus.retrievecarriessince: "2030-01-01"and a matching row’s publication column holds2029-12-31, THEN the row still returns, its in-window flag false, ordered below every in-window row.
unsettled: Does the row id identity-columns injects serve as the row key of a table declaring no content-hash column? owner: read-path affects: read.retrieve
unsettled: Does restriction get the statistics treatment rank-statistics gives the horizon, so BM25 document frequency on fulltext-probe excludes restricted rows? owner: authority affects: read.retrieve
rank
Candidate ordering by vector and lexical scores, timeframe and reported confidence.
three-legstestRanking runs exact cosine similarity over the candidate set, BM25 over a full-text index, and a weighted fusion of the two.fusiontestFusion computesw_vec · clamp(cosine, 0, 1) + w_lex · rrf(bm25). Weights are 60 percent and 40 percent.rrfmaps BM25 rankrto61 / (60 + r). Absent documents score zero per leg; identifiers break ties.lexical-leg-matches-onlytestBM25 ranks documents matching at least one should-clause. Documents matching no term are absent. An empty query, candidate set or result produces an empty ranking and uses recency order.flat-window-full-credittestA lexical window with no score spread awards every present document full credit.question-window-is-a-tiertestA question’s timeframe projects a per-row in-window flag against the resolved publication column, and the flag leads the ordering. An out-of-window row sorts down and stays.Why
P4window-anchor-tolerancetestA publication instant more than 24 h past the question’s anchor sets the in-window flag false. A null or uncastable value sets it false too, and a basis field names which case applied.ordering-casts-firsttestOrdering compares publication values asTIMESTAMPTZinstants, casting a text value before any comparison.Why
because a text comparison of two date spellings orders strings, not instantsretrieval-blocktestThecontextful.retrievalblock reports window, candidates_prefloor, candidates, matched, returned, in_window, deduped, padded, floor and since. Each row carries_score, the count of content tokens itsscored-textmatches, the in-window flag and the basis label.delivered-countstestThe retrieval block’s returned count equals delivered rows; its in_window count equals delivered rows with a true in-window flag, after byte truncation.Why
because aggregate counts describe the rows the caller receivesinternal-score-stays-internaltestResponses omit the lexical engine’s internal floating-point score.Why
because a corpus-relative number drifts under a consumer’s threshold as the corpus changesabsent-blocktestNon-ranked statements and builds without a ranker omit the retrieval block. An absent block differs from a block reporting zero matches.Why
P2degradation-not-errortestWithout the lexical backend linked, ranking substitutes a token fallback scoring each query token over thescored-text, recency breaking ties. The result differs in order and still answers.fallback-counts-tokenstestUnder the fallback every matching token contributes, ranking hyphenated, spaced and possessive phrasings equivalently.caller-embeddingtestA suppliedquery_embeddingadds per-row cosine fused with the lexical leg; omitting one leaves every projected column as the lexical-only path. Without the vector backend it scores only rows the recency window recalled.lexical-index-cachetestThe opened full-text sidecar cache holds at most 64 entries in FIFO order, keyed by table, snapshot, path and key-version fingerprint. Repeated reads reuse sidecars; a different snapshot requires opening another.widened-window-statisticstestLexical term statistics use the widened candidate window. Accelerated retrieval may therefore order rows differently from the exact scan.calibration-gatetestA lexical-leg calibration replaces the onefusionnames only when the native evaluation scores it at or above that one on every tracked measure.
unsettled: Does a convex combination over min-max-normalized scores, its weight fitted per domain on settled judgments, replace fusion? owner: read-path affects: read.rank
unsettled: Does the read face admit a pointwise reranker, and does one whose every checkpoint postdates the horizon refuse to score under it? owner: read-path affects: read.rank
cache
Locality of the bytes a read touches, reuse of a resolved session and its connections, and reuse of a result across requests.
hot-local-parquettestThe retrieval container syncs the current snapshot set to local disk and memory-maps it; object storage stays out of the per-query path except aslazy-hydrationadmits.cold-starttestA cold retrieval container meetscontainer-readiness; a container holding no snapshot adds a whole-store pull before its first answer.warming-readtestA request other thanGET /healthreaching a retrieval container whose snapshot-set hydration is incomplete answers aspast-ceiling;/healthnames the hydration state.Why
because a container ready before hydration completes otherwise answers a read from a partial snapshot setmiss-waittestUnderlazy-parts, each absent part has a 30 s acquisition deadline covering manifest lookup, GET attempts and backoff sleeps. Expiry raisesReadPartUnavailable, naming the part.lazy-hydrationtestUnderlazy-parts, hydration completes once the pull returns and the parts on disk are mapped, and a query’s first read of an absent part takes the one fetchmiss-waitbounds.result-keytestResult keys include token id, revocation epoch, grants, subject, session zone and incognito state. Keys also include touched relations and files, bounds, statement, parameter values and applied row ceiling.cache-is-opt-intestResults enter the cache only if every touched table declaresresult_cachewith a time to live and none declaresprivate = true. Entries expire at the shortest declared time to live.volatile-bypassestestStatements execute uncached if they call functions the engine marks inconsistent across queries, includingnow(),current_timestampandrandom(). Macros calling these functions also bypass caching.Why
because such a call answers differently under an unchanged key, and a hit returns its first answer frozen until the entry expiresfrontier-invalidatestestCommits, folds and request-ledger appends change a touched table’s read frontier: its snapshot and runs. Earlier cache entries stop matching. Statements executing during a fold read undernon-blocking.Why
P4budgettestThe result cache stays within its process’s declared byte budget and evicts the least recently used entry first. Without a declared budget, the server caches nothing.hit-identicaltestCache hits return responses byte-identical to the original miss, including the restriction block. Withinternals: true, diagnostic metadata reportscacheashitormiss.Why
because a reused answer differing from a computed one is a second projection a caller must reconcilesession-pooltestFacereuses sessions and connections while the key matches: admitted authority, token id, revocation epoch, request zone, bounds, pin map and table set. Each granted table contributes schema digest, pointer, run set and ledger files.change-missestestCommitted runs, snapshots, schema edits, new tables, request-ledger files and tokens with different ids or epochs cause pool misses. The next statement reads the changed state through a new connection.pool-entriestestThe pool holds at most 16 entries, oldest evicted first; a miss evicts the same principal’s entries under an older store state.Why
because connections hold memory per authority, and an entry under a superseded store state is never hit againpool-connectionstestOne pool entry keeps at most 4 connections idle; a concurrent statement past them opens its own and closes it afterwards.Why
because concurrency on one session is bursty, and an idle connection holds its footer cache in memory
resolve-pin
Resolution of a named published-model build at read time, and the state each response echoes.
pin-parametertestEvery read tool admitspin, mapping a table name to a build identifier, and resolves that table to the files its build’s committed manifest names. An unnamed table resolves to the latest published state.pinned-schematestA pinned table registers the columns stored in that build’s parts. Later builds neither add, remove nor retype columns in the pinned read or description.null-pintestApinentry with valuenullresolves as an unnamed table. Pin maps differing only in null entries are equivalent.unknown-buildtesttestAn unknown or collected build identifier, onecollected-snapshotcounts included, raisesPinnedBuildUnavailable, naming the oldest identifier still pinnable. A pin never widens to the latest state.Why
because a substituted build applied to every remaining query passes the consumer’s cross-query comparison and stitches two statesunregistered-pintestPins on absent or ungranted tables raiseunknown-buildas for a table publishing no build. Both cases return identical text.Why
because an answer differing between an absent and an ungranted table discloses the ungranted table’s existenceearlier-bound-winstestA pin and the store’s transaction-time bound are upper bounds on one clock; a table named by both resolves to the earlier.resolved-echotestResponses touching published models includecontextful.resolved, mapping each model table to{build_id, watermark}. This includes unpinned and empty results. Watermarks followwatermark.absent-watermarktestThe watermark is null for a materialization carrying none, distinct from a watermark of zero.Why
P4consumer-comparisontestA consumer compares resolved build identifiers across every query of one derivation and fails the derivation where two differ.Why
P4
embed
Embedding implementations, stored model identifiers, query-model checks, build identity and missing-backend errors.
default-embeddertestThe default embedding implementation deterministically hashes token term frequencies into an L2-normalized vector. This implementation performs no I/O and requires no download, key or network call.default-embedder-reachtestThe default is a lexical-vector baseline: a paraphrase is orthogonal under it and cross-lingual recall is undefined. Semantic reach comes from the learned in-process model or a caller-supplied query embedding.model-identifiertestEvery stored vector uses abinary-and-vectorcolumn besideembedding_model, which names the producing model. Rows alone determine index identity and provenance.build-identitytestThe handshake reports linked retrieval backends (duckdb,fts,hnsw), connector families (m365,s3,wasm) and interfaces (http,eval,otlp). These names derive from the same feature declarations as version output;otlpnamesspan-encoding‘s protobuf exporter.Why
P3required-facetestBefore the first read, the server rejects a client’srequire: [...]withRequiredFaceAbsentfor any unreported name. An engine reporting no set satisfies no requirement.absent-read-backendtestWithout the embedded SQL engine linked, every read tool raisesReadBackendAbsentrather than answering from a narrower path.
as-of-join
Point-in-time ASOF JOIN statements and templates that match rows by their availability timestamps.
per-row-boundtestPer equality key,ASOF JOINselects the right row with latest declared availability at or before the driving instant named by the inequality. If given,max_agebounds that row’s valid-timefrom.horizon-caps-rowtestUnderavailable_as_of, each driving row’s instant is the earlier of the instant its inequality names andavailable_as_of, so no right row past the bound joins.join-undeclaredtestAnASOF JOINwhose right relation declares noavailable_timerefuses asundeclareddoes, naming the table, whether or not the read carriesavailable_as_of.Why
because joining on valid time picks a restatement published after the event
Scenarios
read.as-of-join.per-row-bound: WHEN a resource directory maps stable idresource-1to labelalphaavailable2012-05-18and tobetaavailable2022-06-09, THEN an event forresource-1on2021-03-01joinsalphaand one on2022-07-01joinsbeta.
unsettled: Does an as-of join’s max_age measure valid time or availability time? owner: read-path affects: read.as-of-join
horizon
Reads under horizon: default time bounds, rejected arguments, hidden metadata, network restrictions and selection of indexes, embedding models and builds.
inherited-boundtestUnder a horizon, read tools default absentavailable_as_ofto that horizon, caphorizon-caps-rowand echobound-echo.context.subscribeinstead raiseshorizon-refusal.Why
because a per-call bound is a bound the agent can omitexceeds-horizontestA call supplyingavailable_as_oforsincelater than its credential’s horizon raisesReadBoundExceedsHorizon, naming the argument;as_of,valid_as_ofandobserved_atpass unbounded by it.Why
because a cutoff the agent can widen is advisory, while a valid-time question about a known future effective date stays legitimaterecall-horizontestUnder a horizon,keyeddefaults absentobserved_atto that horizon and reads claims at the sameavailable_as_of. A table without declared availability time raisesundeclared.Why
because a claim lands at its commit instant, and one concluded after the horizon otherwise answers inside itengine-stampstestUnder a horizon, responses omit_ingested_atand_commit_seqbut retain citation fields_run_idand_row_seq.context.describecomputes row count, size, lexicon and example values from the horizon-bounded relation.Why
because load stamps date a row’s arrival and reveal the present under a past horizon, while_run_idand_row_seqcite a row without ordering rows in timelate-columnstestA column’s first merge setsschema.json’sfirst_landed_atonce to the batch’s earliest availability instant, otherwise commit instant. Columns withfirst_landed_atafter the horizon are absent from registered schemas and describe payloads.egress-hooktestUnder a horizon, a pre-send hook rejects requests to every host except the configured model endpoint. Rejection followshook-refusalwith reasonhorizon.Why
because one attachment point already sees every egress, so a per-tool list restates itlocal-ranktestUnder a horizon,corpus.retrieveranks stored rows through local indexes; search sources read their pinnedcapturecorpus. Live search reaches no host throughegress-hook.Why
because a live result order postdates the snapshot, and filtering it afterwards keeps the orderrank-statisticstestUnder a horizon, sidecar probes return only entries available at or before that horizon. Full-text scoring derives document count, average length and document frequency solely from those entries.embedder-routetestUnder a horizon, an embedder declaring a checkpoint family selects its newest checkpoint whosecutoffspost-training cutoff precedes that horizon. The response names the selected checkpoint.Why
because an embedder trained after the horizon places documents by later eventsbuild-resolutiontestUnder a horizon, model tables select the newest build whosebuild-horizonis at or before the credential’s horizon. If none qualifies, resolution raisesReadNoBuildWithinHorizon, naming the table.Why
because a build over current inputs carries later rows into every horizon
Scenarios
read.horizon.exceeds-horizon: WHEN a credential carries horizon2014-05-01andcorpus.retrievesuppliessince: "2015-01-01", THEN the call raisesReadBoundExceedsHorizonnamingsince, and a call supplyingvalid_as_of: "2015-01-01"answers.read.horizon.rank-statistics: WHEN a full-text sidecar indexes stories published2014-11-01and2015-06-01andcorpus.retrieveruns under horizon2015-01-01, THEN the 2014 story ranks with the score a sidecar over it alone gives, and the 2015 story is absent.
unsettled: Where does the engine record the instant a column first landed in a table, for a schema that predates the record? owner: read-path affects: read.horizon
unsettled: Does a vintage table’s sidecar count only each key’s newest vintage available by the horizon, so its statistics and candidates match the bounded relation’s one vintage per key? owner: read-path affects: read.horizon
subscribe
Committed-run notifications, durable replay, resume positions, queue limits and the subscription CLI.
durable-sourcetestSubscriptions read on-disk run manifests and commit logs rather than theemission-never-blockschannel. Subscribers receive runs committed by other processes.Why
because an in-process hub ties every reader to the landing process and loses its state on restartafter-committestSubscriptions notify only runs committed underuncommitted-run. Runs without manifests and logged runs absent from their node’s commit log produce no notification.Why
P4notificationtestA notification isNotification; itsfrontieris the pair{as_of, commit_seq}: the run’s commit instant and the_commit_seqvalue its rows carry.frontier-covers-runtestA read of the table boundedas_ofa notification’s frontier covers the notified run’s files.Why
P4commit-ordertestNotifications of one table arrive in ascendingcommit_seq, the ordercommit-ordermakes runs readable, and each committed run notifies exactly once per subscription.Why
because a subscriber applying notifications in order rebuilds the table’s commit sequenceno-contenttestA notification carries no row content and no row count.Why
because a count over a table under row policy or tenant scope discloses rows the reader cannot seeresumetestaftermaps tables to<run-id>/<node-id>positions. Subscriptions replay each table’s later committed runs once, then continue live. An intervening fold leaves the replay unchanged.Why
because a fold leaves run manifests in place until retention collects themoverruntestA subscription holding more than 1000 notifications undelivered sends 1000, then a terminaloverrunmessage naming each table’s last delivered position, and ends.Why
because a bounded queue never drops a notification silently, and the terminal position resumes the streamoperator-verbtestcontextful context subscribe <table>... [--after <table>=<run-id>/<node-id>] [--follow]prints one JSON notification per line. Without--follow, the command ends after sending the replay.tool-polltestcontext.subscribeacceptstablesand optionalafter. One response page includesmessagesunderresumeandoverrun, plusafterwith each table’s last delivered position for the next call.Why
because the tool protocol’s request and response hold no stream, and a returned position resumes a poll exactly as it resumes a streamungranted-tabletestBefore resolvingafter, the tool rejects subscriptions naming tables outside the session’s relations underungranted-table, whether the table exists or not.Why
because a position refusal or a notification on an ungranted table discloses that the table exists and commitshorizon-refusaltestOn the tool protocol, a subscription under a credential carrying a horizon raisesSubscriptionUnderHorizonbefore any table orafterposition resolves.Why
because a notification carries the run’s commit instant and sequence, whichengine-stampswithholds, and runs committed after the horizon
Scenarios
read.subscribe.resume: WHEN runsr1,r2andr3commit toeventsand a subscription namesafterevents=r1/ingest-a, THEN it sendsr2thenr3, and a fold before the call changes neither.read.subscribe.after-commit: WHEN a leased pipeline’s run writes its manifest and its commit-log entry is absent, THEN no subscription notifies it.read.subscribe.commit-order: WHEN a relational run commitseventsatcommit_seq5 before its group marker lands and a plain run commits at 6, THEN a subscription sends neither until the marker lands, then 5 before 6.
unsettled: Does a subscription under a credential horizon withhold runs committed after it and their stamps rather than refuse? owner: read-path affects: read.subscribe
unsettled: Does the networked transport carry context.subscribe as server notifications over a held stream? owner: read-path affects: read.subscribe
Shapes
The registered relation for one table:
CREATE OR REPLACE VIEW orders AS
SELECT * FROM (
SELECT *, ROW_NUMBER() OVER (
PARTITION BY order_id
ORDER BY updated_at DESC, _ingested_at DESC, _run_id DESC, _row_seq DESC
) AS _rn
FROM read_parquet(
[ 'tables/orders/data/snapshots/snapshot-01773100800000000000/part-00000.parquet',
'tables/orders/data/runs/run-0192/node-9f2c/part-00000.parquet' ],
union_by_name = true
)
) WHERE _rn = 1;
The response projection, with total_cents a BIGINT column:
{
"columns": ["order_id", "total_cents", "placed_at"],
"rows": [["A-8812", "1299", "2026-02-14T09:31:00Z"], ["A-8813", "9007199254740993", "2026-02-14T09:44:12Z"]],
"truncated": true,
"contextful.bounds": { "as_of": "2026-03-01T00:00:00Z", "inclusive": false },
"contextful.resolved": {
"daily_orders": {
"build_id": "snapshot-01773100800000000000",
"watermark": { "at": "2026-02-14T10:00:00Z", "inputs": { "orders": { "snapshot_id": null, "through": 7, "omitted": { "count": 1, "digest": "9f2c…" } } } }
}
},
"contextful.policy.applied": { "rows_dropped": 12, "columns_masked": ["email"], "overfetch_rounds": 1 },
"contextful.retrieval": {
"window": 200, "candidates_prefloor": 200, "candidates": 61,
"matched": 61, "returned": 20, "in_window": 14, "deduped": 3,
"padded": 0, "floor": 2, "since": "2026-02-07"
}
}
Per-row ranking fields: { "_score": 2, "_vscore": 0.71, "_in_window": true, "_date_basis": "published_at" }.
A template, and the tool it projects as:
[[query_templates]]
id = "orders_by_region"
sql = "SELECT region, sum(total_cents) FROM orders WHERE placed_at >= ? AND region = ? GROUP BY region"
parameters = ["since:timestamp", "region:string"]
max_rows = 500
{ "name": "orders_by_region",
"inputSchema": { "type": "object",
"properties": { "since": { "type": "string", "format": "date-time" }, "region": { "type": "string" } },
"required": ["since", "region"] },
"limits": { "max_rows": 500 } }