Receipts¶
Every answer ships with its receipt. The response carries the answer, the rows, the
SQL that produced them, citations, a confidence grade with the per-check verification
report behind it, certification and its provenance (who vouched, when), data_as_of,
and a request_id (services/contracts/response.py). Provenance is not a log you go
dig up afterwards — it is part of the output.
What one receipt contains¶
- Who asked. The caller identity, resolved from the API key and written into the
trace. A caller is the person behind the agent — an agent is an extension of
the person it acts for, never a caller on its own account. Doctrine: one key per
person, never one per tool (
services/cli/init.py:459); however many agents someone drives, they all ask under that person's key, so every answer traces back to somebody who can be asked about it. An unattended app is no exception — its key belongs to the person accountable for it. - Which lens answered, under which allow-list decision — both allow and deny
outcomes are written to
audit_log(services/governance/audit.py:22). - What it cost, on both meters: AI tokens and USD cost, warehouse bytes and cost,
plus latency, all on the per-request trace (
services/contracts/trace.py:17). One rule with teeth: an unpriced model makes the whole trace's costNULL, never a default — a partial sum shown as the total would be a silent lie (services/observability/cost.py:5). - How much to trust it. The verification grade (
verified | partial | unverified) is derived from deterministic checks — every number in the answer traceable to the rows, definition applied, intent alignment, result not truncated (services/runtime/verification.py:232). - The SQL. Always attached to a served answer. A rejected response never includes
the SQL it refused to run — the trace keeps it for review
(
services/runtime/pipeline.py:146).
data_as_of and certified provenance are response-only trust signals: the model never
sees them, so it cannot fake them.
The signed block¶
A number pasted into a slide, a ticket, or another agent's context used to carry
nothing a skeptic could check. So every data answer ships a small portable receipt
block — request_id, lens, served_at, certification, cert_id, confidence,
sql_sha256, data_as_of, and a digest — that anyone in the org can send back
(POST /v1/verify-receipt, or the verify_receipt MCP tool) to learn whether these
exact claims were really served, by this server: the signature is recomputed, and
every field is cross-checked against the logged trace
(services/runtime/receipt.py).
- Signing is HMAC-SHA256 over canonical JSON (sorted keys, digest field
excluded), keyed by
DST_SECRET_KEY— the same key list and rotation contract as stored-secret encryption: the first key signs, every key verifies. - The block carries the SQL's hash, not the SQL — receipts travel further than SQL should, and the hash still pins the receipt to the exact query.
- Verification is stateless: nothing new is persisted; it recomputes the
signature and reads the
request_logrow that serving already wrote. - No key configured → the receipt ships with
digest: null, and verification reports unsigned out loud. Fabricating a digest, or refusing to serve for lack of one, would both be worse. A receipt that carries a digest this server has no key to check reports unkeyed — a config gap named as itself, never as forgery. - Refusals and clarifications carry no receipt — they make no data claim to attest.
The freshness contract¶
Two facts ride the answer, one measured and one declared. data_as_of is
measured — read from the stored table profiles, never asserted
(services/lenses/profile_enrich.py:148). stale_after_days is what you
declare on the lens (services/contracts/lens_config.py:261): how old is too
old for this use case, which nobody downstream can infer. It sits on the lens rather
than on a table because tolerance is a property of the question, not the data — the
same orders table is fresh enough for a monthly close and too stale for an ops
board, and only the use case knows which.
Past the contract, the freshness check fails, the grade is demoted, and the answer
says so — a certified serve past the contract is still stale
(services/runtime/verification.py:555). Undeclared, the check reports skip,
never a vacuous pass.
Know how data_as_of is measured before you set the contract: it is the oldest
last-update across every entity in the lens's scope — not the tables the answer
actually touched — and it reads profiles from the lens's first declared connection
only (services/runtime/assembly.py:389). Two consequences. A single rarely-updated
table in scope — a country lookup that legitimately has not changed in months —
drags data_as_of down and can mark every answer from that lens stale, including
questions that never read it. And on a multi-connection lens, freshness on the
second connection is not measured at all. So set stale_after_days against the
slowest thing in scope, not against the table you have in mind — or split the
slow-moving dimension into its own lens. Leaving it unset is a reasonable default
until you have checked what data_as_of actually reports for that lens.
The warehouse sees receipts too¶
Every statement dst executes — serving, profiling, audits, eval runs — carries a
structured leading comment that survives in warehouse query history
(services/connectors/tagging.py:49):
/* dst: {"app": "dst", "purpose": "serve", "lens": "sales_comp", ...} */
SELECT ...
Warehouse admins get attribution without instrumenting anything, and history mining can mechanically exclude dst's own traffic — the scaffolded history-bootstrap skill's SQL filters the tag out before reading anything.
The receipt is the platform engineer's audit log¶
Traces persist to request_log, org-scoped and RLS-enforced
(services/observability/logger.py); the Observe views are read-side aggregates over
that same table (services/observability/observe.py) — there is no separate,
prettier record that could drift from the evidence. Per-caller cost roll-ups,
wrong-answer investigations, and access reviews all read from the same rows the
requests wrote.
Bet
Attribution is the product: an unattributed answer is an unaccountable one, so
the receipt — caller, lens, cost, grade, SQL — is a first-class output, not an
ops afterthought.
source: doctrine; services/cli/init.py:459