Skip to content

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 cost NULL, 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_log row 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