Dashboard native Arrow response contract
This document defines the native-v1 correctness oracle for a future native
Arrow response from dashboard table data queries. It does not activate that
response, change the current dashboard endpoint, or authorize a production
migration. FAI-543 and later experiments must satisfy this contract before
their performance results can be considered.
The existing unversioned dashboard Arrow response is a compatibility boundary:
it projects every cell to UTF-8, represents a source null as an empty string,
and returns its next cursor as an initial response header. A native-v1
response instead preserves governed Arrow values and uses a completion trailer.
Those behaviors are not wire-compatible. The current stream must not silently
change; a native response requires explicit client opt-in and the response must
identify native-v1 in both its header and schema metadata.
Scope
The first eligible implementation is an ordinary detail/table query. It must use the same authorization, governance, admission, audit, result-budget, sorting, and pagination inputs as the current direct dashboard query. Matrix, pivot, calculations, multi-block shaping, Datastar SSE, retained-result cache, and warm-cache delivery are outside this contract's first implementation.
Schema contract
The IPC stream schema is the post-governance projected schema, not the source table schema. In particular:
- Fields appear in governed projection order. Field names are the requested aliases where aliases exist; physical source names must not replace aliases.
- Arrow physical types are preserved. Boolean and signed/unsigned integer widths, floating-point widths, dates, timestamps, UTF-8 strings, binary values, decimals, and dictionary encodings must not be projected to strings.
- The field's nullable declaration is the final governed effective nullability
described below. Each array's physical validity bitmap is preserved. A null
must remain null; it must not become zero,
false, an empty string, or an empty byte slice. - A decimal preserves its precision, scale, sign, and exact unscaled value.
- A timestamp preserves its Arrow unit and timezone. A timestamp with timezone
UTCrepresents the same UTC instant; a timezone-neutral timestamp remains timezone-neutral rather than acquiring a server-local zone. - Binary values preserve their exact bytes, including zero bytes and empty non-null values.
- A dictionary field preserves its index type, value type, ordering flag, dictionary values, indices, and validity bitmap. Expanding a dictionary to strings is a contract change.
- Only response-safe governed metadata from the closed allowlists below is emitted. It must not expose generated SQL, physical connection or source identifiers, policy expressions, credentials, principals, or other internal state.
Nullability authority
native-v1 uses governed effective nullability with conservative fallback.
The server-owned output schema descriptor for the final post-governance
projection is authoritative; neither a source-column declaration nor the
DuckDB Arrow field flag is independently authoritative.
An emitted Arrow field has Nullable=false only when the final projected
expression is proven incapable of producing SQL NULL after every governed
transformation. A nullable, unknown, incomplete, or conflicting derivation is
emitted as Nullable=true. In particular:
- a source
NOT NULLdeclaration does not by itself prove that the final projection is non-null; - the nullable side of a left relationship traversal is nullable;
- a column mask that can produce
NULLmakes the output nullable; - a calculation or derived expression is nullable unless its complete operator and input semantics prove otherwise;
- metric nullability includes its governed empty-set policy; and
- filters or the values observed in one page, including a page with zero nulls, never narrow the declared nullability.
Unknown always maps to nullable. The rule is stable for empty results: an empty stream uses the same derived output schema descriptor as a non-empty execution of the same governed query. It must not infer nullability from the absence of record batches.
Runtime validity bitmaps remain the authoritative physical statement about which values are null. The stream forwards them without null-to-value conversion. If a record contains a null for a field declared non-null, the execution has violated the governed output contract; Arrow encoder acceptance does not make that stream valid.
Implementation boundary
The schema foundation represents each projected field with an internal
query.OutputFieldDescriptor containing:
- the final alias;
- the public governed logical type;
- effective nullability represented internally as proven non-null, nullable, or unknown; and
- internal derivation provenance sufficient to explain the decision without exposing source, policy, SQL, or connection details on the wire.
query.Planner.DescribeOutputSchema derives the descriptor before a response
schema could be emitted from validated final PlanIR, including relationship
traversal, column masks, metric empty-set policy, calculations, and derived
expressions. Unknown derivations remain nullable. The descriptor is matched to
the governed alias and projection order; it is not reconstructed from observed
batches or client-authored metadata.
arrowquery.OutputSchemaSink is the un-routed enforcement boundary. It
reconciles only the Arrow field nullability declaration required by the
descriptor, preserves physical Arrow types and validity buffers, respects
borrowed-batch callback lifetimes, and validates every emitted record batch.
Its internal provenance is opaque and is not serialized as response metadata.
No production route currently constructs this sink; this foundation does not
authorize a handler change or native streaming migration.
An empty result is a valid Arrow IPC stream containing the full governed schema and zero record batches. It is not a schema-less stream and it is not a JSON empty array.
Reserved LeapView metadata
The leapview.* namespace is server-owned. Upstream/source metadata cannot set
or override it. The allowlists are closed: unknown keys are rejected whether
they use the leapview.* namespace, another namespace, or no namespace. Adding
a response metadata key requires a contract revision and corresponding oracle
coverage.
| Location | Key | Authority and required value |
|---|---|---|
| Schema | leapview.arrow_contract |
native-v1 |
| Schema | leapview.query_id |
Same value as X-Query-ID |
| Schema | leapview.serving_snapshot |
Same value as X-Serving-Snapshot |
| Schema | leapview.visualization_schema_version |
Server-controlled visualization schema version |
| Schema | leapview.visualization_spec_revision |
Server-controlled visualization specification revision |
| Schema | leapview.visualization_data_revision |
Server-controlled visualization data revision |
| Field | leapview.logical_type |
Public governed logical type, when available |
| Field | display.label |
Approved producer metadata for the governed display label |
All schema keys and leapview.logical_type are authoritative server values;
producer values cannot override them. display.label is the only producer
metadata currently approved for forwarding. SQL, engine, connection, physical
source, and other producer metadata are rejected. In particular, a cursor is
never placed in schema metadata: it is disclosed only through the response
trailer after successful completion.
Response protocol
A successful native response is an Arrow IPC stream and has these required headers before the body is committed:
| Header | Value |
|---|---|
Content-Type |
application/vnd.apache.arrow.stream |
Cache-Control |
no-store |
X-Query-ID |
Non-empty request/query correlation ID |
X-Serving-Snapshot |
Non-empty serving snapshot bound to the query |
X-LeapView-Arrow-Contract |
native-v1 |
Trailer |
Declares X-Next-Cursor |
Version negotiation
The legacy and native contracts share the dashboard visual query route but are separate, explicitly negotiated representations. A native request supplies both:
Accept: application/vnd.apache.arrow.stream
X-LeapView-Arrow-Contract: native-v1
Legacy behavior remains unchanged: an unversioned Arrow request receives the
all-string schema, the existing initial X-Next-Cursor header semantics, and
the existing exact-or-capped AvailableRows behavior.
A successful native response echoes
X-LeapView-Arrow-Contract: native-v1. An Arrow request without the contract
marker remains on the legacy all-string representation. A marker without the
Arrow media type, or any unknown contract version, fails closed with 406 Not Acceptable; the server must never silently downgrade it. This foundation does
not activate the native representation in the production dashboard handler.
Activation remains a separate adoption change.
Native-v1 page model
The default page limit is 100 rows. The accepted range is 1 through 1,000 rows,
inclusive. Native interactive pagination emits at most 10,000 cumulative rows
for one cursor chain. The server executes a limit + 1 probe under the same
governed query while there is capacity below that cumulative cap, emits at most
limit rows, and uses only the probe row to decide whether continuation exists.
It does not calculate or promise an exact total and does not expose
AvailableRows. Clients that require exact or capped totals remain on the
legacy contract.
The first request has offset zero. A middle page resumes from the offset in a
validated native cursor. If the probe row exists and the IPC writer closes
successfully, the server writes an opaque X-Next-Cursor trailer. The final
page, an empty result, and a page that reaches the 10,000-row cap have no trailer
value. Exactly limit physical rows is a final page; limit + 1 physical rows
is the first condition that permits a cursor. At offsets where a full requested
page would cross the cumulative cap, the emitted/query limit is reduced to the
remaining allowance and no continuation probe is executed beyond the cap.
The trailer name is declared before commitment on every successful native response so clients can consume final and continuing pages uniformly. Initial headers and Arrow schema metadata never contain the native cursor. Probe rows count toward physical execution and result budgets even though they are not emitted. The empty response remains a complete native schema with zero rows and no cursor.
Dashboard-native cursor domain
Native-v1 uses the signed d3 cursor domain. Legacy dashboard d1 cursors and
semantic-query q1 cursors are not interchangeable with d3; every decoder
rejects cursors from the other domains. A native cursor expires after 15 minutes
and binds:
- the
native-v1contract version; - dashboard, page, and visual identity;
- server-computed canonical digests of normalized filters, selections, and effective sorting;
- the requested page limit;
- the final governed effective-policy identity;
- the serving snapshot;
- the next offset, cumulative rows consumed, and 10,000-row cap; and
- its expiry.
The signed payload contains a digest of the request/governance scope rather
than raw filters, selections, sort definitions, policies, principals, SQL,
source identifiers, or physical connection details. Governance and
authorization are recomputed for each page before the cursor binding is
accepted. A changed contract, identity, normalized request state, limit, or
policy identity is an invalid cursor (400). A different serving snapshot is a
cursor conflict (409). Clients treat the entire cursor as opaque.
Error and commit boundary
Before the Arrow response is committed, failures use the API's JSON problem
shape with Content-Type: application/problem+json and no Arrow contract
headers. Existing resource-concealment rules remain authoritative.
| Failure | Required result before commit |
|---|---|
| Authentication failure | 401 problem response |
| Authorization failure | 403, or 404 where the route conceals inaccessible resources |
| Malformed or wrong-scope cursor | 400 problem response |
| Expired, wrong-contract, changed-filter, changed-selection, changed-sort, changed-limit, or changed-policy cursor | 400 problem response |
| Cursor serving-snapshot mismatch | 409 problem response |
| Row or byte budget failure | 422 problem response |
| Admission rejection or resource exhaustion | 503 problem response, including Retry-After where applicable |
| Admission queue timeout or execution timeout | 504 problem response |
| Internal failure | 500 problem response |
Request cancellation stops query work and releases borrowed batches and other leases. If the connection remains writable and no bytes have been committed, the failure follows the normal problem mapping; a disconnected client may observe only connection termination.
A runtime null in a field declared non-null is a native contract failure. If
detected before response commitment, the server returns the normal structured
JSON problem response and does not start an Arrow stream. If detected after
commitment, the server terminates the Arrow stream, does not append a JSON
fallback, and does not publish a successful X-Next-Cursor trailer value.
After the Arrow response is committed, the server cannot switch formats. A
query, IPC, cancellation, or transport failure terminates the stream. There is
no JSON suffix or fallback, no successful completion signal, and no
X-Next-Cursor trailer value. Consumers must treat an unreadable or incomplete
IPC stream as failed even if the HTTP status was already 200.
Cursor publication is ordered after successful IPC close. Cancellation, timeout, row/byte budget failure, admission failure, partial write, IPC write failure, or IPC close failure therefore cannot publish a successful cursor.
Security and governance invariants
The native transport changes representation only. Before any schema or batch is written, the request must pass the same boundaries as the current governed direct query:
- Resolve the authenticated principal and active serving snapshot.
- Authorize the dashboard/model dependencies and preserve any resource concealment behavior.
- Apply row policies and column masks and compute the effective policy fingerprint. The emitted schema is the masked projection; it cannot reveal denied source columns or pre-mask physical types/metadata.
- Acquire workload admission using the same class, identity, operation, and memory estimate as the control query.
- Apply row and byte budgets to the schema, emitted rows, and pagination probe. A transport encoder cannot bypass budget accounting.
- Record the same audit actor, credential/effective subject, operation, resource target, start, success, and failure outcomes as the control query.
FAI-543's borrowed DuckDB batches may be observed only synchronously inside the sink callback. The sink must finish reading or encoding a batch before the callback returns, must not retain borrowed buffers, and must not populate or read the retained dashboard result cache.
Correctness qualification
A candidate is rejected before performance comparison if it differs from the
control in column order, aliases, physical types, values, null positions,
metadata, sorting, offset/limit behavior, cursor behavior, cancellation,
partial-write handling, authorization, row policies, masks, admission,
budgets, or audit identity. The executable fixtures in
internal/dashboard/http/arrow_contract_test.go lock the native value and wire
semantics. Existing authorization, admission, budget, and audit tests lock the
shared governed execution boundaries.
Only after those gates pass may an experiment compare the candidate with the
current api_direct dashboard path using the same query and physical query
behavior. Warm-cache measurements are a guardrail, not a comparable lane.
Nullability qualification must cover, at minimum:
- base
NOT NULLand nullable fields; - aliases and projection ordering;
- empty results;
- fields on both sides of a left relationship traversal;
- masks that preserve, replace, or introduce nulls;
- metrics with null and zero empty-set policies;
- calculations and derived expressions with proven and unknown semantics;
- multiple record batches; and
- non-null declaration mismatches detected both before and after response commitment.
Client compatibility and activation
No first-party browser component currently decodes this API Arrow stream, but API and generated-client consumers may rely on the current string/null projection. Therefore:
- native delivery requires the two-header
native-v1opt-in above; merely sending the Arrow media type continues to select the existing legacy stream; - the response must return
X-LeapView-Arrow-Contract: native-v1, and clients must reject unknown contract versions; - clients must support native Arrow types, validity bitmaps, dictionaries, and HTTP trailers before opting in;
- generated clients continue to prefer JSON unless native Arrow support is explicitly selected.
The request marker must be added to TypeSpec/OpenAPI when production activation is proposed. This contract foundation intentionally remains unrouted so the generated and live endpoint cannot claim support before native serving, resource policy, and client qualification are complete.