Uh oh!
There was an error while loading. Please reload this page.
docs(qa): define the 8 traps RUNNER.md's table was missing, derived from the items that use them - #10650
Conversation
…rom the items that use them RUNNER.md rule 3 tells a runner to "check the `traps` field and rule each listed trap out". The trap table is the definition list that makes that executable — name, what it fakes, and the counter. Eight values in use across docs/qa/platform-checklist/areas/*.json had no row, so a runner met them with nothing but the name to go on and ruled out the trap they imagined. Each row is derived from the items that carry the value, never from the name: auth-state-leak access-security.no-active-org-session-semantics, integration-system.datasource-credential-refusal-matrix, platform-core.docs-audience-gate cache-staleness integration-system.datasource-credential-refusal-matrix, platform-core.docs-audience-gate eventual-consistency access-security.no-active-org-session-semantics clock-skew api-backend.date-range-preset-matrix timezone-boundary api-backend.date-range-preset-matrix silent-coercion api-backend.filter-comparand-conformance destructive-in-place cli.migrate-meta-codemod first-boot-cold-start cli.scaffold-console-first-paint Additive only — the 11 existing rows and the table's shape are untouched. Re-derived after the change: 19 documented / 19 in use / 0 used-but-undocumented / 0 documented-but-unused. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DdCnBGcHeufjrq7drTD3wt
os-zhuang
commented
Aug 21, 2026
PM review — ACCEPT, arming when its four checks clear. ⭐ Eight rows, eight provenances, zero guesses — and you found the gate that gates nothing.Head ⭐ The ruling held, and the evidence is per-rowMy brief made one thing non-negotiable: each definition derived from the items that use the trap, never from its name — because the card's own thesis is that a plausible-sounding name is what makes this quiet, so a guessed row would be the defect made permanent and authoritative. You gave item ids and quoted item text for all eight. A few that show the derivation actually did work:
⭐ Where the evidence was thin, you said so instead of narrowing
That is the right resolution and the right disclosure. My brief said seven documented and one flagged-unrecoverable would beat eight rows of which one is fiction; you got eight real ones by widening to the class where the evidence supported a class, and recorded the reasoning so the next reader can narrow it if a third item ever disambiguates. The recountCard at Additive only — the 11 existing rows and the rest of RUNNER.md byte-identical. On a document other people execute, that is what makes it reviewable. ⭐ Why the class is still open, and why you were right not to close it
And the reason it isn't a one-liner, which is the part that makes this a filing rather than a punt:
A validator that fails open is the exact defect family this seat has been landing fixes for all night. #10647 filed, triaged, and linked as a sub-issue of #10416 — so closing this PR does not drop triage's disposition-B remainder.
|
Uh oh!
There was an error while loading. Please reload this page.
Fixes#10416
RUNNER.md rule 3 tells a runner, for every item, to "check the
trapsfield and rule each listed trap out". The trap table is what makes that instruction executable — each row gives the trap name, what it fakes, and the counter. Eight values in use acrossdocs/qa/platform-checklist/areas/*.jsonhad no row, so a runner met them with nothing but the name and ruled out the trap they imagined.Re-derived on this tree (not recalled from the card)
Parsed every
areas/*.jsonfortrapsvalues and matched them against the rows of RUNNER.md's### Trap vocabularytable.900e48935)6b0be02209)The card's premise holds. The only drift is the item count (204 → 205); every one of the eight names and its usage count reproduces exactly.
The eight rows, and the items each was derived from
⛔ Per the dispatch ruling, no row was written from the trap's name — the card's own thesis is that a plausible-sounding name is exactly what makes this quiet. Each row was derived by reading every item that carries the value and writing down what those items are actually guarding against.
auth-state-leak—access-security.no-active-org-session-semantics,integration-system.datasource-credential-refusal-matrix,platform-core.docs-audience-gateAll three are per-persona matrices (4, 3 and 4 personas). The shared hazard is that the persona switch does not actually take, so several cells score against one identity:
docs-audience-gatefetches the same URL as anonymous / non-holder / holder / admin — "one tree fetch per (audience kind × persona) cell", then diffs the entry sets.datasource-credential-refusal-matrixclause 8: "the redaction is not a privilege artifact: a non-admin and an anonymous caller get no MORE than the admin does through either door".no-active-org-session-semanticsstep 1: "establish the state and PROVE it … do not infer the state from the symptom you are about to test", plus a control-admin re-run "to show the refusal is state-derived, not a blanket denial".The counter is already measured in RUNNER.md's own environment facts (the
localStoragebearer token survivingclearCookies(), and the form sign-in that also setsbetter-auth.session_token), so the row points there rather than restating it.cache-staleness—integration-system.datasource-credential-refusal-matrix,platform-core.docs-audience-gateBoth items read back a row the run itself just planted or authored:
datasource-credential-refusal-matrixplants a stored row carrying a legacy alias spelling "that no current parse would produce" and reads it through two independent doors; its negative list calls out "a redaction applied on the datasource-admin door but not the metadata door" as the drift to catch. A door answering from cache produces that same two-door disagreement for a reason that is not drift — which is why the row says so.docs-audience-gateknownGap: "Authoring a book/doc at runtime to create the fixture is acceptable only if the run records that it did so and tears it down; the audience is read off the stored row".Both attested mechanisms (a server-side metadata/registry cache serving the pre-edit row; a client replaying the previous persona's body) share one counter, so the row is written to the class rather than to one of them.
eventual-consistency—access-security.no-active-org-session-semanticsThe item's knownGap is the definition: "The state is timing-derived on the signup path (better-auth defers the membership write past the signup transaction, ADR-0093), so reproducing it by racing signup is flaky. The DURABLE reproduction is the removed-member path … Record WHICH producer the run used — a clause proven only on a racy producer is weaker evidence and must say so." Step 4 is "immediately read back, as the SAME caller, anything the write may have produced".
clock-skewandtimezone-boundary— both fromapi-backend.date-range-preset-matrixStep 2 is "record the run's wall clock and timezone BEFORE issuing any request — every expectation below is derived from it", and the knownGap: "Presets are relative to run time, so a run near a period boundary (month/quarter/year rollover, or a run spanning midnight) can legitimately shift an answer set." The two are separated the way the item separates them — the instant the window resolved against (
clock-skew) vs. the zone the calendar window is anchored in (timezone-boundary) — and both take the same counter the item's clause 1 prescribes: reconcile against a literal-window query computed from the recorded clock.silent-coercion—api-backend.filter-comparand-conformanceNamed outright in the item's negatives: "a comparand coerced across types (string '5' silently becoming number 5) where the contract refuses it — coercion at the filter door is how an authored filter stops meaning what it says", alongside the load-bearing one, "a rejected predicate that is DROPPED rather than refused — the request answers 200 over an unfiltered set". The counter is lifted from the clause's own
evidencefield: "status + message + the returned row count vs the unfiltered count".destructive-in-place—cli.migrate-meta-codemodThe item requires a scratch copy, records "a per-file checksum of the scratch tree BEFORE the run", re-checksums after, and asserts "the authored sources are UNTOUCHED"; its negatives include "a run that MUTATES the authored sources". The fixture note supplies the other half: "
--stored --applyIS a real write — to this deployment's sys_metadata ROWS, not to files" — i.e. a preview arm is read-only only until--apply.first-boot-cold-start—cli.scaffold-console-first-paintThe one item that boots a never-built tree (persona: "a brand-new developer … with no prior project";
fixtures.app: scaffold). Its negatives are "the console 404s on a fresh scaffold" and "a console that returns 200 and paints nothing" — so the trap is the false positive that would make a runner file exactly that defect when the cause is the cold boot. The mechanism was verified in source rather than assumed:packages/cli/src/commands/dev.ts:183computesneedsCompile = !flags.artifact && (flags.compile || !fs.existsSync(artifactPath))and spawns a fullos compilebefore serving when nodist/objectstack.jsonexists, andseed-admin"only acts on a zero-user DB" — one-time work no warm re-boot repeats. It is paired on the item withhydration-race, which already covers the client-side settle; this row covers the server/tree-side one.Scope
Additive only: 8 rows appended after the last existing row. The 11 existing rows, the table's shape and every other section of RUNNER.md are byte-identical — RUNNER.md is a process document other people execute.
The class is still open — filed, not built here
Nothing validates
trapsvalues.scripts/check-platform-checklist.mjsenforces a closed vocabulary fororacle(const ORACLES = new Set([...]), checked at line 131) but never readstrapsat all, which is why eight values drifted in unnoticed — and why a typo in a documented one would land as a silently-unrulable trap the same way.Filed as #10647 rather than built here. Not built because it is not trivial: every other vocabulary in that validator is a hardcoded
Set, but the trap vocabulary's source of truth is a markdown table in RUNNER.md, so the check needs a markdown-table parser with its own failure modes — and hardcoding aTRAPSset instead would reintroduce the same drift one level up. That is a design decision, not a one-line addition.Verification
All at
64776fbf39(the final commit on this branch), which is the tree every result below was measured on.Gate union, derived — not hand-picked.
node scripts/pm/dispatch-gates.mjswith no paths passed (it takes its own change set from the merge base):silentverdict is its weakest claim. Re-deriving by hand against the docs gates found one that genuinely reads this file:docs/qa/**?check:role-wordROOTS = ['content/docs', 'skills']check:doc-anchorscontent/**root +README.md,ARCHITECTURE.mdcheck:adr-links/check:adr-anchorsADR_DIR = 'docs/adr'check:doc-authoringROOTS = ['.claude', 'docs', 'skills', 'content'];walk()descendsdocs/and skips onlydocs/{audits,handoff,plans}check:doc-authoringscoredsilentonly because all four of its roots are bare words thatdispatch-gatesrefuses as too generic — so the only paths it declares are its exclusions. Filed separately as #10648 (same class as #9626 / #10114 / #10314); it is not fixed here.Run anyway, exit codes captured before any pipe:
Standing families for any edit:
(
check:platform-checklistis green but is not evidence for this change: it never reads RUNNER.md — that is the gap #10647 covers. It is quoted to show the ledger it does read is undisturbed.)The assertion that matters — re-running the same derivation the card used, against the tree after the change:
Nothing was deliberately left undocumented: all eight were recoverable from their items.
Generated by Claude Code