From 64776fbf39ee785bfe075ad29be6553db9bd2efe Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 21 Aug 2026 07:24:03 +0000 Subject: [PATCH] docs(qa): define the 8 traps RUNNER.md's table was missing, derived from the items that use them MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Claude-Session: https://claude.ai/code/session_01DdCnBGcHeufjrq7drTD3wt --- docs/qa/platform-checklist/RUNNER.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/qa/platform-checklist/RUNNER.md b/docs/qa/platform-checklist/RUNNER.md index c0e1e82b94..62e3c61a5c 100644 --- a/docs/qa/platform-checklist/RUNNER.md +++ b/docs/qa/platform-checklist/RUNNER.md @@ -338,6 +338,14 @@ contradicts it, and correct it here when it does. | `wrong-panel` | feature looks missing on a sibling surface | item's `steps` name the exact surface; check it | | `wrong-persona` | admin privileges mask a guard | run guard checks as the non-privileged persona | | `absence-inference` | a missing flag/key/script read as a missing capability | follow the forwarding chain to where the default is actually decided, before writing the finding down. A scaffold's bare `objectstack dev` still serves the console: `dev` forwards `--ui` only when set and never forwards a negation, and the default is decided one hop downstream by `serve`'s own `ui` flag (`default: true, allowNo: true`) — so absence means ON. ⛔ The off switch `--no-ui` is `serve`'s and `start`'s; `dev` has no such flag and rejects it (environment facts above) | +| `auth-state-leak` | a per-persona matrix scored against ONE identity — the previous persona's credentials survived the switch, so a gate reads as holding (or as fallen open) for a caller that never made the request | prove the identity server-side before each cell (the session row, or `GET /auth/get-session`), never from the gesture that was supposed to switch it; give each persona its own session. ⚠️ Both halves have to go: the console's `localStorage` bearer token survives a `clearCookies()` gesture and a form sign-in ALSO sets `better-auth.session_token` (environment facts above), so an "anonymous" or "non-admin" cell can silently still be the admin's | +| `cache-staleness` | a read answered from a cache instead of re-resolved: the row this run just planted or edited reads back unchanged, or one persona's body is replayed for the next | before scoring, confirm the response carries something this run set (the planted spelling, the new `audience`); re-fetch with a client of its own per persona rather than reusing a response. ⚠️ Two independent read doors disagreeing may be one door's cache rather than the drift the item is hunting | +| `eventual-consistency` | a deferred write read before it settles — an effect that has not landed yet reads as a refusal, and a state the reconciler is about to fill reads as durable | prove the state from its own record (the session row / session payload), never from the symptom you are about to test; prefer the durable producer over the racy one and record which the verdict rests on; re-read after a settle window before writing an absence down | +| `clock-skew` | a relative window resolved against a different instant than the expectation was computed from — a run that spans a rollover, or a server clock that is not the runner's | record the wall clock BEFORE the first request and derive every expectation from that one instant; reconcile each preset against a literal-window query computed from the same clock, and re-run any window whose boundary the run crossed | +| `timezone-boundary` | rows near midnight or a period edge landing on the other side of a calendar window, because the window is anchored in a different zone than the runner assumed | record the zone alongside the clock and compute the expected window in the zone the door resolves in; score against the literal-window query, never against an intuition about "today" | +| `silent-coercion` | a 200 that reads as the contract holding while the input was converted across types — or the predicate dropped entirely, so the request succeeds meaning something other than what was authored | never score on status alone: compare what came back against what was sent, and the returned row count against the UNFILTERED count — a dropped predicate answers over the full table, which is the dangerous direction | +| `destructive-in-place` | a clean re-run — the first run already rewrote the input it is judged against, so the second observation describes a different tree | run against a scratch copy / a database you own; checksum (or snapshot) the input before and after and cite the pair; check which arm actually writes before replaying — a preview arm is read-only only until `--apply` | +| `first-boot-cold-start` | a 404 / empty body / white screen from a first boot still doing its one-time work, read as the first-run-only defect the item exists to hunt | read the boot log and wait for the server's own serving line before the first probe: a never-built tree compiles on the way up (`dev` spawns `os compile` when no `dist/objectstack.json` exists) and seeds admin on a zero-user DB, neither of which a warm re-boot repeats. ⛔ Do not write a first-run defect down before re-probing a warm boot | ## Run records — the GitHub issue is the report