Controlled Production Reads
When this applies
Section titled “When this applies”A controlled production read is a query run against live production data to answer a question nobody can answer from the code: how many rows are in a state a repair would have to handle, what a deploy actually changed, or what an incident actually touched.
Three occasions produce one:
- Diagnosing an incident — establishing what the system did, not what it should have done.
- Sizing a repair — counting and classifying the rows a fix would have to cover, before committing to a fix.
- Verifying a deploy’s effect — confirming that a migration or release changed what it was supposed to and nothing else.
Which side of the system. Back-end data: SQL through the bastion route, or attribution reads over the API. This page is not about the front end, and it is never about running a test suite against production — a test asserts, and a read asks.
Who runs one, and on whose word. Reads are executed under the query-custody policy that owns the results, and each one is granted individually. There is no standing permission to read production: a grant covers a read, not a habit. If you are about to run a second query because the first raised a question, that is a second read and needs its own grant.
Why this page exists
Section titled “Why this page exists”A query against production ran its control, the control disagreed with the value it was checking, and the run reported that the data could not be trusted.
The data was fine. The control had silently computed a different quantity than the one it was meant to verify — so it disagreed with a correct value, and the run’s conclusion was not a wrong number but a wrong verdict about the dataset itself.
That is the failure this page exists to prevent, and it is worse than a wrong answer. A wrong number gets contradicted by the next person who looks. A false “distrust everything” discredits good data, costs a re-derivation, and is indistinguishable from a real integrity problem.
A control computes one quantity by two structurally different routes
Section titled “A control computes one quantity by two structurally different routes”The rule that came out of the correction:
Compute the same quantity two ways, compare per item, and make the two ways structurally different.
Each clause carries weight.
Two ways, not one. A query that reports a number with nothing to check it against reports a number.
Per item, not in aggregate. Totals cancel. Two errors of opposite sign in different rows sum to agreement, and the control passes while both rows are wrong. Compare row by row and a single disagreement surfaces.
Structurally different, not merely differently written. Two expressions built from the same join, the same predicate or the same assumption share their mistakes and will agree with each other while both are wrong. Routes count as independent when they reach the answer through different structure — one from the rows, another from a projection; one by counting, another by classifying.
The corrected query carries three such controls: column accounting against the live schema, terminality established by two independent methods, and business classification by two independent methods.
If any control fails, report the failure — not the answer
Section titled “If any control fails, report the failure — not the answer”The answer computed alongside a failed control is not a degraded answer. It is an answer whose reliability is unmeasured, and publishing it with a caveat invites someone to use it without one.
This is not a new principle, and this run is a second clean instance of it holding rather than its first statement.
Protect the query against the schema it will meet later
Section titled “Protect the query against the schema it will meet later”A production query is rarely run once. The schema moves between runs, and a query that hardcodes a list of columns will quietly mean something different after a migration it never saw.
The corrected query checks the live table’s columns against its own hardcoded classification list, and fails if they diverge. That is what makes it safe to run again later without a manual schema review first — the query states the corpus it assumes and refuses to run against a different one.
Results do not leave custody
Section titled “Results do not leave custody”The method is publishable. What the method found is not.
Counts, identifiers and classification results are production data. They live in the query directory’s README under its access controls, governed by the custody policy that owns them — not in this documentation site, and not in this page as an illustration.
That last point is the one to hold onto. The natural way to explain a classification scheme is to show what it classified, and doing so here would move production content into a site with different access rules for the sake of a worked example. Describe the shape of the output instead.
Say whether you measured it or argued it
Section titled “Say whether you measured it or argued it”Not every question can be answered by a read, and the honest response is not always to block until it can be.
A check before editing a set of migrations had to establish where they had already run — an applied migration’s checksum breaks on edit. The answer came in two halves, and they are different kinds of claim:
- Measured, on the partition the session could read: six queries against the per-module migration-history tables, filtered on the actual filenames. Zero rows in all six.
- Argued, on the partition it could not: unmerged-branch migrations do not reach demo or production through the normal deploy path, because
mainpins those tables’ schema without the files and branch deployments target the other partition only. The argument rests on deploy topology, not on a read.
Reporting only the conclusion — these migrations have not run anywhere — would have been true and would have concealed that half of it was never checked. Whoever inherits that conclusion cannot tell which half to re-verify when something later looks wrong.
The gate is usually access, and it is worth naming rather than implying. Admin on the development and staging partition is self-refreshable by the session that needs it. Admin on the demo and production partition is the operator’s own act every time, with no standing session — so the read means asking for a one-off grant. Once granted it is mechanically identical to the other check: read-only, no elevated risk.
So a session deciding not to spend that ask is making a judgement, not hitting a wall. Publishing an argued claim, labelled as argued, is a legitimate third option between verifying it and declining to say anything. It is the one that gets lost: the usual framing offers two, and a reader who believes those are the only two either spends an ask they judged unnecessary or quietly presents an argument as a measurement.
State which you did. Where you argued, state what the argument rests on — here, the deploy topology — so the next reader can attack the argument rather than the conclusion.
The worked example
Section titled “The worked example”The read that occasioned this page was a repair-sizing one: a set of rows had forked — two versions of what should have been one thing — and the question was how many of those forks a repair would have to resolve by hand rather than mechanically.
Answering it needs three questions per fork, and the third depends on the first two:
- Is the difference between the two branches a business difference or a mechanical one? A mechanical difference can be resolved by rule. A business difference cannot: something happened that a person meant.
- Are both branches terminal, one, or neither? A fork where both sides are finished is a historical record. One where neither is remains live and can still diverge further.
- The cross-tabulation of the two. That is the answer the repair needs — how many forks are mechanical-and-closed, how many are business-and-live, and so on. Only some cells need a domain judgment.
Each of those three was computed twice, by structurally different routes, and compared per fork rather than in aggregate: terminality from the rows themselves and again from a projection over them; the business-or-mechanical classification by two independent methods. A fourth control checked the live table’s columns against the query’s own hardcoded list, so a schema that had moved since the last run would stop the query rather than silently change its meaning.
The shape of the output, not the output. The result is a table of counts by cell — how many forks fall in each of the six combinations. Those counts are production data and live under the custody policy, not here. What generalizes is the three-question structure and the four controls, both of which apply to any “which duplicated rows need a person before they can be repaired” question, whatever the table.
See also
Section titled “See also”- What a Green Run Proves — a related rule about checks whose two sides are not independent. That page concerns instruments: a suite whose fixture is generated from the artifact under test cannot witness the two disagreeing. This page’s rule is adjacent rather than the same — here both routes are written deliberately, and the requirement is that they differ in structure rather than only in expression.
Sources
Section titled “Sources”The corrected pattern and its predecessor’s failure come from a production classification read run under operator authorization, 2026-09-09. The submission is /workbooks/notebooks/domain-ontology/streams/harry-tuttle/submission-w8-fork-classification-method.md. No counts, identifiers or results from that run appear here or anywhere on this site.
The measured-versus-argued section comes from a separate migration safety check on the same stack, /workbooks/notebooks/domain-ontology/streams/harry-tuttle/handback-p3-migration-deploy-check.md, and from the access answer that handback gave when asked what the gap actually was.
Copyright: © Arda Systems 2025-2026, All rights reserved