Field note · pipeline anatomy
The leak gate
The agent-vs-human scorecard on this site reads from my real task manager. Every row in that database has a title, notes, categories, and people attached — the kind of table you are told never to point a public page at. The page reads it live anyway, and publishes none of it. This is the anatomy of the layer in between: two dbt models, 27 tests, and one rule that does most of the work.
Redaction is a denylist. Denylists lose.
The instinctive way to publish sensitive data is to redact it — list the columns that must not ship and strip them. That fails the way every denylist fails. Someone adds a column next quarter, the strip list doesn’t know about it, and the new column walks straight through. The failure mode of redaction is silent publication.
So the layer inverts it. A source column is invisible to the public until a model explicitly selects it and reduces it — to a count, a percentage, a median. The failure mode of an allow-list is a missing number on a chart. I will take a missing number over a leaked note every time.
In practice that means the two published views emit exactly one text value between them: the handler tag, agent or human. Everything else is arithmetic over a rolling 90-day window — instances, minutes, outcome rate, a derived dollars-per-instance. No title survives aggregation. No note has a column to ride in.
The shape of the pipe
dbt reads the ref() calls, infers this graph, and runs it in order — models first, then every test against the freshly built views.dbt build fails the run if any test returns a row. That failure is the whole security model: the site’s build never reads a view the gates haven’t passed, so a red test doesn’t page anyone or file a ticket. It simply makes the publish impossible.
Three checks, tightest first
The leak gate itself is one SQL test, assert_no_pii_leak, that unions every text value the published views emit and flags anything suspect:
case
when p.value is null or not (p.value = any(p.allowed)) then 'not-in-allow-list'
when p.value ~* '[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}' then 'email-pattern'
when p.value ~ '(\+?\d[\s.-]?){7,}' then 'phone-pattern'
when p.value ~ '\d{3}-\d{2}-\d{4}' then 'ssn-pattern'
when lower(trim(p.value)) in (select pii from oracle) then 'matches-contact'
end as reasonThe first check is the real backstop: every text value must be exactly one of the expected enum tokens. A future model that accidentally selects a title trips it immediately, even if the title matches no known pattern. The pattern checks catch anything shaped like an email, phone number, or SSN. The last check is an oracle — it joins against the private contacts table and flags any published value that matches a real name, email, phone, or address I actually know. Before the layer shipped I injected a contact name, an email, and a raw category into a staging copy. The gate caught all three.
Around the leak gate sit the boring-but-load-bearing ones: range tests on every numeric column, a derivation test asserting the published dollars-per-instance actually equalsround(avg_minutes × rate, 2) so the methodology note can never drift from the number shown, and source-freshness checks so a stale scorecard is a loud failure instead of a quiet one.
The week the chart collapsed
The gates earn their keep on the failures you didn’t design for. In July the scorecard’s trend chart quietly lost its trend — 313 of 314 agent tasks landed in a single week bucket, and a 90-day series had two points left to plot.
The cause was a timestamp choice. The weekly buckets keyed on updated_at, and updated_at is mutable. A bulk touch on the task rows — a routine migration in the private app — restamped 90 days of history into the week the touch ran. Nothing errored. Every existing test passed, because the window test only caught buckets outside the window, and the collapse happened entirely inside it.
The fix keyed the buckets on completed_at — “the work finished then” — which no later touch can rewrite. Against live data that restored 14 week buckets from 2. And because a gate that passed during a real failure is a gate with a hole in it, the incident became a test: assert_scorecard_week_spread now fails the build if the bucket count ever falls below half the window’s worth of weeks. The regression is remembered by the pipeline, not by me.
The receipt
Every dbt build distills its run results into a committed status file the site reads at build time — the same never-hand-typed rule the rest ofAutopilot follows. The current receipt:27/27 gates green · leak gate PASS · Jul 22, 2026. When a gate goes red, that line goes red with it, here and on the ledger.
The pattern generalizes past task data. Any team sitting on a sensitive system — health metrics, revenue, support tickets — can publish real numbers this way: allow-list models, aggregate-only outputs, a leak gate with an oracle built from the private data itself, and a build that refuses to ship a red result.
Built with dbt 1.11.11 over the private mission-control database. The models, tests, and macros are public in the site repo — the data they protect is the only part you can’t see.