Verification Rules
A task is not done because the code was written. It is done when the check for its type has passed.
The checks below catch crashes. They do not catch the common case. Across 3,127 fix commits in three production repos, runtime crashes were a small minority; the bulk was code that ran fine and was wrong — a handler nested where it never fires, four surfaces disagreeing about one number, a cache key missing the account dimension, a locale holding a translation of the previous sentence. Work
rule-ramificationsbefore claiming any of these passed. Evidence:docs/failure-evidence.md.
Scope boundary
- audit owns: security, a11y, performance, type safety,
console.log, hardcoded colors, missing states, test gaps. - brainstorm owns: new features, dead code removal, file splitting, unused deps, competitor research, UX flow ideas.
No overlap. If brainstorm turns up a bug, note it and suggest audit.
Verification by task type
| Task | Required before done |
|---|---|
| Edge Function / API | curl with real params, verify 200 + response shape |
| UI (public) | Browser check: page reads correctly and the console is clean |
| UI (admin) | typecheck + build only |
| Refactor | typecheck + build + existing tests pass |
| Bulk change | grep for the old pattern to confirm full elimination |
| Auth / Billing / RLS | tests + manual verification of deny-by-default behavior |
For the UI rows, use whichever browser driver the browser skill selects — the
built-in browser tools; chrome-devtools emulate for mobile device gates.
Cross-cutting verification (all task types)
These seven apply to every task regardless of type:
- No unsafe casts —
as unknown as Typeon external data must be validated with Zod. - No fire-and-forget fetch — every
fetch()checksres.okand has try/catch. - Fail-closed auth — protected routes deny by default, not allow by default.
- Design tokens — no hardcoded colors; semantic tokens only, with the gradient-surface exception.
- Form a11y — labels on inputs, correct
type/inputmode, don't block paste. - Error handling — no empty catch blocks, no missing error states, no unhandled promise rejections.
- Something must REACH it — name what routes a user or caller to the thing you built, and check that path exists. Not "the page renders" — what links to it? Not "the helper is correct" — do its callers call it?
The reachability check, because one day produced four instances
The artifact getting built while its wiring doesn't is the most repeated failure class on record here, and every instance passed its own verification:
- a pricing page shipped reachable only through the sitemap — Google could find it, a person browsing the site could not. "The page renders" was true.
- a copy guard was wired into one writer of a field that had three; the unguarded two kept emitting exactly what the guard strips. "The guard works" was true.
- a data-loss fix landed in the shared library while the skill that performs the operation kept its hand-rolled version. The library's tests passed.
- a gate sat unlanded on a branch for eleven days. Its suite was green the whole time, on a base 39 suites behind.
The shared shape: verification asked "is the artifact correct?" when the failing question was "does anything reach it?" A page nothing links to, a helper nothing calls, a guard only one of N writers passes through, a fix on a branch nothing merged — each is indistinguishable from not built for everyone except its author.
So before passes: true, answer in one sentence: by what path does a user,
caller, or runner arrive at this change? If the sentence names an entry point —
a nav link, a call site, a merged ref, a registered hook — check that it exists.
If the sentence cannot be written, the task is not done; it is half of a task
whose other half is the wiring.
For enumerable surfaces, enumerate: a guard's writers, a token's consumers, a nav's pages. "The N sites are covered" requires stating N and how it was counted — by a mechanical rule, not recall. One repo's count went from two writers to three the day a rule replaced memory, and the third was on the most public path.
Closing a task: the claim must be checkable, and it must be true
Marking passes: true writes a claim into a file other people and other
sessions act on. Two rules, both earned the hard way.
1. Name the change, so a reader can falsify it. "Fixed" is not a record.
nudgetext moved below the authCheck call in coach.js is — anyone can open the
file and disagree.
2. Do not close a story until the change is somewhere a reader can reach it. Not "the fix is written", not "the fix is on my branch and I am about to push". Committed and pushed, or the story stays open.
Both rules are sound. The story originally told here to justify them was not, and correcting it is the more useful lesson.
I reported that two P0 stories were marked passes: true while the fix existed
nowhere — not on the default branch, not on 25 remote branches, not in 8 live
worktrees. Stated forcefully, twice, including in a handoff document.
It was false. The fixes had landed, in a commit two minutes before my own
duplicate. passes: true was accurate the whole time.
How a confident false negative gets manufactured
I searched for two shapes I expected the fix to take:
"is the handler now below authCheck?" → no
grep sanitis|sanitiz|generic.*fallback|strip.*PII → no hits
∴ "the fix exists nowhere"
The real implementation was a third shape neither pattern matched: split the
copy into text (personal, rides in the encrypted push) and pubText (generic,
written to the public file). Better than either thing I looked for — the one I
eventually recommended myself, already shipped.
An absence search is only as good as its enumeration of what would count as presence. Two misses became "nowhere". The rule:
Before reporting that something is missing, write down what you would accept as evidence that it exists. If that list has two entries, you are about to report a false negative. Search for the effect — is the leak closed? — not for the fix you had in mind.
The same discipline this framework already applies to counts (read every finding before reporting it) applies to zeroes. A zero is a finding too, and it needs the same reading.
On the rules themselves
They still hold, on their own merits rather than on that anecdote. A story that says "fixed" without naming the change cannot be checked by the next reader, and one closed before the change is pushed is a claim about a file nobody else can see. Neither needs a scandal to justify it.
There is no cheap detector for this. Two were measured and dropped.
Recorded so they are not rebuilt:
| Signal | Result |
|---|---|
| "no commit message references the story id" | 100% of done stories, in all three repos. None of them put ids in commit messages, so this is the normal state, not a finding |
| "the story cites file paths that no longer exist" | 4 hits across 371 done stories — 0 real. Three were path-prefix artifacts (dashboard/page.tsx for src/app/dashboard/page.tsx), one a file the story's own fix deliberately deleted |
Before closing a story that claims a code change, open the file and confirm the change is there — and before claiming someone else's story is falsely closed, do the same, harder.
What auto handles without being asked
Sprint transitions (archive done, carry deferred, bump number), deploys of changed edge functions, the verification above, and a conventional commit every three tasks.