The Validator That Can't Lie
A validator built from a second, hand-maintained copy of the rules will eventually be wrong — not from carelessness, but because two independently declared copies of the same fact almost never stay in sync. The fix isn't stricter discipline. It's removing the second copy: derive every check from the exact code path the real system runs, on every check, and never persist the intermediate. How Terraform, Rails, tsc — and RECALL's brief auditor — all arrive at the same architecture.
A Validator With Its Own Opinion
Somewhere in most systems there is a rule that gets declared twice. Once where it is enforced — a field width, a valid range, a required shape. And once where it is checked — a schema doc, a test fixture, a comment block some other tool reads before anything runs for real.
The two copies start in sync, because whoever wrote the second one was looking straight at the first. Then someone changes the real rule — widens a field, adds a case, loosens a constraint — and has no reason to remember the second copy exists. The checker keeps checking. It just checks against a rule that stopped being true.
This isn't a bug in any specific tool. It's the default failure mode of validation itself, the moment validation is implemented as a second opinion rather than a second look.
The Sharper Form of DRY
“Don't repeat yourself” usually gets taught as a warning against copy-pasted code. The more consequential version of the rule isn't about code at all — it's about knowledge. If two different functions both need to know how wide a field is allowed to be, and each one is independently, correctly told the answer, you have already lost. Not today. Today they agree. You've lost the day someone updates one and has no reason to think about the other.
The fix people reach for first is discipline — a checklist, a code review rule, a comment that says “update both places.” Discipline is the thing you need precisely because the architecture didn't rule the problem out. The sturdier fix is structural: there should be exactly one function that knows the fact, called from two different places — a real run, and a dry run — rather than two functions that each independently claim to know it.
A trustworthy check isn't the strict one. It's the one with nothing of its own to be strict about.
If the validator has no independently-declared rule to enforce — if it can only ever ask “what would the real system actually do here” — it can be wrong about the world, but it can never be wrong about itself.
It's worth being precise about where this differs from snapshot testing, because on the surface they look like the same idea. A snapshot test also derives its expectation from the system's own output rather than a hand-written assertion — run the function, capture what it produced, compare future runs against that capture. That's real progress over a hand-maintained expectation. But the capture gets approved once and then persisted. It becomes a file. And a persisted file is exactly the second copy this pattern exists to remove — it just took one extra step to become stale, because someone has to notice it's wrong and choose to re-approve it, and in practice almost nobody does.
The sharper version doesn't persist the intermediate at all. It re-derives it, from the real code path, on every single check. Nothing is captured. Nothing is approved. There is no artifact old enough to go stale.
Where This Already Runs
None of this is exotic. Once you know to look for it, the same architecture shows up anywhere a system has both a “do it” path and a “check it first” path.
Terraform's plan step
plan doesn't consult a separately-authored description of what should change. It builds the exact resource graph apply would build, then diffs that graph against live state. The preview is not a summary of the rules — it is one honest execution of them, stopped short of applying.
Rails' schema.rb, Prisma's shadow database
The "current schema" document isn't hand-authored and kept in sync with migrations by convention. It's regenerated by replaying the actual migrations. Schema drift isn't managed — it's structurally impossible, because the document is an output, not a second input.
tsc as its own linter
You don't maintain a separate type-contract doc that some other tool checks your code against. The type checker gating your build is the same one that would supposedly be documenting the types elsewhere. There is nowhere for a second opinion to live.
None of these tools were built to demonstrate a principle. They arrived at the same shape independently, because it's the shape a validator takes once you refuse to let it remember anything.
How RECALL Does It
RECALL compiles a structured JSON brief — content an AI writes, one declared field at a time — into a self-contained HTML page, through an intermediate source written in RECALL's own COBOL-inspired grammar. Every field in that intermediate source is declared with a fixed width: a PIC X(30) verdict field, a PIC X(1000) narrative field. Content longer than its declared width gets cut — silently, unless something is watching for it.
The naive version of “something watching for it” is exactly the failure mode above: a separate list of field limits, hand-maintained somewhere, that an audit script checks the brief against before anything compiles. That list and the real generator would agree on day one. They would not agree forever.
What actually runs instead is one function, called twice. Every field write in the real generator passes through a single helper that knows the field's name and its limit, and nowhere else in the codebase does that number appear:
function val(s: string | number, max: number, field = ''): string {
const str = esc(String(s ?? ''))
if (field && str.length > max) {
_truncations.push({ field, actual: str.length, max, preview: str.slice(max - 14, max) })
}
return `"${str.slice(0, max)}"`
}
// called once per field, in the real generator:
CASE-VERDICT PIC X(30) VALUE ${val(brief['CASE-VERDICT'], 30, 'CASE-VERDICT')}.The audit tool doesn't open a separate schema. It calls the exact same function that produces a real compile — buildRcl(brief) — as a dry run, then reads a log those same call sites just wrote to:
const rcl = buildRcl(brief) // the exact function the real compile calls
for (const t of getTruncations()) { // the log val() just populated, nothing more
findings.push({
level: 'error', code: 'TRUNCATION',
msg: `${t.field} — ${t.actual} chars > PIC X(${t.max}); truncates at “…${t.preview}”`,
})
}If a field's width ever changes in the real generator, the audit's idea of the limit changes with it, automatically, because it was never told the number — it reads it off the same call that would have used it for real. There is nothing to keep in sync, because there was only ever one thing.
Closing the Loop
A principle that's architecturally sound still has to survive a human forgetting to invoke it. The audit above existed for a while as a step you ran by hand, before generating a page — a good validator, still gated on someone remembering it was there. That's not a flaw in the pattern. It's the one place discipline hadn't been designed out yet.
So the deploy step was changed to run it itself — a hard gate, immediately before anything uploads, refusing to proceed on any error:
if [ -f "$BRIEF_JSON" ]; then
node "$AUDIT_CLI" "$CASE_NUM" || exit 1
fi
# ... deploy only reached if the audit above passedTested both directions before trusting it: a deliberately broken brief — one field padded past its limit — got the deploy refused, with the exact field and character count named in the output. Restored, the same brief deployed clean. There is now no path from “this brief has a truncation error” to “this page is live” that doesn't pass through the one function that would have caught it.
The General Shape
None of this is really about field widths, or RECALL, or HTML generation. It's about where a system chooses to put its opinion. A validator can hold its own copy of the rules and hope someone keeps that copy honest. Or it can hold no opinion at all, and simply run the real thing one more time, quietly, before the real thing runs for real.
The second version costs more to build. It also can't drift, can't go stale, and can't be argued with — because it isn't asserting a rule. It's just watching the same rule the real system was already about to follow, one step early.
Related
The Schema Is the Editorial Policy
The direct predecessor: what the schema polices, before this piece asks how the policing stays honest.
Live Schema, Strict Mode
The general RECALL architecture this mechanism is built on top of.
When the Methodology Becomes the Infrastructure
The same instinct, one level up — compiling a discipline into something that can’t be skipped.