automated-qa 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (187) hide show
  1. package/LICENSE +22 -0
  2. package/NOTICE +9 -0
  3. package/README.md +100 -0
  4. package/SKILL.md +65 -0
  5. package/agent/prebuilt/darwin-universal/qa-agent +0 -0
  6. package/agent/prebuilt/linux-arm64/qa-agent +0 -0
  7. package/agent/prebuilt/linux-x64/qa-agent +0 -0
  8. package/agent/release-manifest.json +31 -0
  9. package/bin/autonomous-cache.mjs +2 -0
  10. package/bin/bench-generate.mjs +232 -0
  11. package/bin/bench-graph-query.mjs +2 -0
  12. package/bin/bench-score.mjs +10 -0
  13. package/bin/classify.mjs +15 -0
  14. package/bin/coherence-playwright.mjs +1 -0
  15. package/bin/coherence-runner.mjs +6 -0
  16. package/bin/crud-runner.mjs +12 -0
  17. package/bin/experiment-executor.mjs +10 -0
  18. package/bin/experiment-runner.mjs +3 -0
  19. package/bin/inventory.mjs +17 -0
  20. package/bin/probe.js +879 -0
  21. package/bin/qa.mjs +16 -0
  22. package/bin/rules.mjs +5 -0
  23. package/bin/run-benchmarks.mjs +2 -0
  24. package/bin/runtime-packs.mjs +3 -0
  25. package/bin/semantic-explorer.mjs +3 -0
  26. package/bin/shadow-collector.mjs +3 -0
  27. package/bin/shadow-replay.mjs +6 -0
  28. package/bin/soak-marathon.mjs +15 -0
  29. package/bin/sweep.mjs +43 -0
  30. package/bin/validate-priors.mjs +3 -0
  31. package/bin/verify-artifacts.mjs +3 -0
  32. package/bin/verify-manifest.mjs +2 -0
  33. package/install-skill.sh +45 -0
  34. package/lib/qa/agent-availability.mjs +1 -0
  35. package/lib/qa/agent-result.mjs +1 -0
  36. package/lib/qa/analysis.mjs +5 -0
  37. package/lib/qa/atlas-coverage.mjs +11 -0
  38. package/lib/qa/atlas-v1.mjs +1 -0
  39. package/lib/qa/atlas.mjs +2 -0
  40. package/lib/qa/auth/coordinator.mjs +1 -0
  41. package/lib/qa/auth/launcher.mjs +3 -0
  42. package/lib/qa/auth/lease.mjs +1 -0
  43. package/lib/qa/auth/oracle.mjs +1 -0
  44. package/lib/qa/auth/personas.mjs +3 -0
  45. package/lib/qa/autonomous-cache.mjs +3 -0
  46. package/lib/qa/block-ledger.mjs +1 -0
  47. package/lib/qa/capture-report.mjs +63 -0
  48. package/lib/qa/ci.mjs +11 -0
  49. package/lib/qa/cli.mjs +538 -0
  50. package/lib/qa/coherence/analyze.mjs +1 -0
  51. package/lib/qa/coherence/cache-key.mjs +6 -0
  52. package/lib/qa/coherence/causes.mjs +1 -0
  53. package/lib/qa/coherence/concurrency.mjs +1 -0
  54. package/lib/qa/coherence/contracts.mjs +1 -0
  55. package/lib/qa/coherence/entity-aliases.mjs +1 -0
  56. package/lib/qa/coherence/enums.mjs +1 -0
  57. package/lib/qa/coherence/family-report.mjs +1 -0
  58. package/lib/qa/coherence/flow-pool.mjs +1 -0
  59. package/lib/qa/coherence/graph.mjs +1 -0
  60. package/lib/qa/coherence/identity.mjs +1 -0
  61. package/lib/qa/coherence/ir.mjs +4 -0
  62. package/lib/qa/coherence/navigation.mjs +1 -0
  63. package/lib/qa/coherence/observe.mjs +2 -0
  64. package/lib/qa/coherence/offline.mjs +1 -0
  65. package/lib/qa/coherence/plan-join.mjs +1 -0
  66. package/lib/qa/coherence/provenance.mjs +2 -0
  67. package/lib/qa/coherence/report.mjs +1 -0
  68. package/lib/qa/coherence/representations.mjs +1 -0
  69. package/lib/qa/coherence/risk.mjs +2 -0
  70. package/lib/qa/coherence/structural-js.mjs +11 -0
  71. package/lib/qa/coherence/symbols.mjs +6 -0
  72. package/lib/qa/coherence/tanstack.mjs +4 -0
  73. package/lib/qa/coherence/views.mjs +1 -0
  74. package/lib/qa/coherence-planner.mjs +1 -0
  75. package/lib/qa/command-catalog.mjs +2 -0
  76. package/lib/qa/commands/dev.mjs +1 -0
  77. package/lib/qa/commands/fix.mjs +1 -0
  78. package/lib/qa/commands/impl/analysis.mjs +1 -0
  79. package/lib/qa/commands/impl/atlas.mjs +1 -0
  80. package/lib/qa/commands/impl/coherence.mjs +1 -0
  81. package/lib/qa/commands/impl/crud.mjs +2 -0
  82. package/lib/qa/commands/impl/doctor.mjs +1 -0
  83. package/lib/qa/commands/impl/experiments.mjs +1 -0
  84. package/lib/qa/commands/impl/fp.mjs +1 -0
  85. package/lib/qa/commands/impl/graph.mjs +1 -0
  86. package/lib/qa/commands/impl/invariants.mjs +1 -0
  87. package/lib/qa/commands/impl/lanes.mjs +7 -0
  88. package/lib/qa/commands/impl/ledger.mjs +11 -0
  89. package/lib/qa/commands/impl/loop.mjs +1 -0
  90. package/lib/qa/commands/impl/marathon.mjs +1 -0
  91. package/lib/qa/commands/impl/next.mjs +38 -0
  92. package/lib/qa/commands/impl/report.mjs +4 -0
  93. package/lib/qa/commands/impl/research.mjs +4 -0
  94. package/lib/qa/commands/impl/selftest.mjs +1 -0
  95. package/lib/qa/commands/impl/serve.mjs +1 -0
  96. package/lib/qa/commands/impl/shadow.mjs +1 -0
  97. package/lib/qa/commands/impl/stability.mjs +1 -0
  98. package/lib/qa/commands/impl/triage.mjs +1 -0
  99. package/lib/qa/commands/impl/validate.mjs +4 -0
  100. package/lib/qa/commands/impl/version.mjs +1 -0
  101. package/lib/qa/commands/impl/watch.mjs +1 -0
  102. package/lib/qa/commands/init.mjs +6 -0
  103. package/lib/qa/commands/measure.mjs +1 -0
  104. package/lib/qa/commands/run.mjs +1 -0
  105. package/lib/qa/commands/show.mjs +1 -0
  106. package/lib/qa/commands/verify.mjs +6 -0
  107. package/lib/qa/deepening.mjs +1 -0
  108. package/lib/qa/denominators.mjs +1 -0
  109. package/lib/qa/depth.mjs +4 -0
  110. package/lib/qa/doctor.mjs +2 -0
  111. package/lib/qa/experiments-default.mjs +1 -0
  112. package/lib/qa/fix-lanes.mjs +3 -0
  113. package/lib/qa/forced-states.mjs +1 -0
  114. package/lib/qa/fp.mjs +2 -0
  115. package/lib/qa/fs.mjs +6 -0
  116. package/lib/qa/graph-schema.mjs +2 -0
  117. package/lib/qa/graph.mjs +3 -0
  118. package/lib/qa/harness-plan.mjs +3 -0
  119. package/lib/qa/init.mjs +5 -0
  120. package/lib/qa/integrity.mjs +1 -0
  121. package/lib/qa/invariants.mjs +84 -0
  122. package/lib/qa/jcs.mjs +1 -0
  123. package/lib/qa/lanes.mjs +1 -0
  124. package/lib/qa/loop.mjs +7 -0
  125. package/lib/qa/marathon-coherence.mjs +1 -0
  126. package/lib/qa/marathon-phases.mjs +1 -0
  127. package/lib/qa/marathon-wait.mjs +3 -0
  128. package/lib/qa/marathon.mjs +5 -0
  129. package/lib/qa/mode-coverage.mjs +1 -0
  130. package/lib/qa/mutation-policy.mjs +1 -0
  131. package/lib/qa/pattern-ledger.mjs +3 -0
  132. package/lib/qa/pending.mjs +1 -0
  133. package/lib/qa/proc.mjs +4 -0
  134. package/lib/qa/recommend.mjs +1 -0
  135. package/lib/qa/release-manifest.mjs +1 -0
  136. package/lib/qa/report-html.mjs +178 -0
  137. package/lib/qa/reports.mjs +2 -0
  138. package/lib/qa/request-evidence.mjs +1 -0
  139. package/lib/qa/research.mjs +20 -0
  140. package/lib/qa/routing.mjs +1 -0
  141. package/lib/qa/runs.mjs +1 -0
  142. package/lib/qa/schema.mjs +1 -0
  143. package/lib/qa/serve.mjs +4 -0
  144. package/lib/qa/server.mjs +1 -0
  145. package/lib/qa/source-snapshot.mjs +3 -0
  146. package/lib/qa/stability.mjs +3 -0
  147. package/lib/qa/state-evidence.mjs +1 -0
  148. package/lib/qa/sweep-incremental.mjs +1 -0
  149. package/lib/qa/triage.mjs +2 -0
  150. package/lib/qa/verdict.mjs +1 -0
  151. package/lib/qa/watch.mjs +2 -0
  152. package/package.json +62 -0
  153. package/references/ATLAS-COVERAGE.md +231 -0
  154. package/references/atlas-v1.md +318 -0
  155. package/references/atlas.md +338 -0
  156. package/references/verify.crud.example.json +1 -0
  157. package/references/verify.impact.example.json +1 -0
  158. package/references/verify.shadow.example.json +1 -0
  159. package/schemas/agent-result.schema.json +135 -0
  160. package/schemas/analysis.schema.json +15 -0
  161. package/schemas/classify.schema.json +61 -0
  162. package/schemas/coherence-ir.schema.json +1585 -0
  163. package/schemas/coherence-observed.schema.json +1171 -0
  164. package/schemas/coherence-plan.schema.json +77 -0
  165. package/schemas/coherence-risk.schema.json +46 -0
  166. package/schemas/crud-observed.schema.json +118 -0
  167. package/schemas/experiment-execution.schema.json +82 -0
  168. package/schemas/graph-edge.schema.json +22 -0
  169. package/schemas/graph-node.schema.json +81 -0
  170. package/schemas/graph.schema.json +2493 -0
  171. package/schemas/identity-oracle.schema.json +19 -0
  172. package/schemas/inventory.schema.json +2167 -0
  173. package/schemas/lanes-plan.schema.json +52 -0
  174. package/schemas/next.schema.json +54 -0
  175. package/schemas/personas.schema.json +52 -0
  176. package/schemas/research.schema.json +192 -0
  177. package/schemas/run.schema.json +160 -0
  178. package/schemas/shadow-replay.schema.json +68 -0
  179. package/schemas/stability.schema.json +92 -0
  180. package/schemas/sweep.schema.json +239 -0
  181. package/schemas/v1/agent-result.schema.json +106 -0
  182. package/schemas/v1/run.schema.json +96 -0
  183. package/schemas/verify-crud.schema.json +37 -0
  184. package/schemas/verify-read-only.schema.json +37 -0
  185. package/templates/github-workflow.yml +80 -0
  186. package/templates/pre-commit.sample +22 -0
  187. package/verify.sh +385 -0
@@ -0,0 +1,231 @@
1
+ # Atlas coverage
2
+
3
+ Generated by `qa dev atlas --write` from `references/atlas.md` and the detectors
4
+ (`bin/classify.mjs`, every JavaScript producer under `bin/` and `lib/`, and
5
+ `lib/qa/atlas.mjs`). Do not edit by hand — it will be overwritten on the next
6
+ `--write`.
7
+
8
+ 77 of 105 S/R classes fully implemented (0 partial, 3 deliberate, 25 gaps); 79 of 108 mode obligations met; 30 not-automatable (G 16, H 12, A 2)
9
+
10
+ ## L1 — Delivery: the code under test is not the code being served
11
+
12
+ 11 classes: 6 implemented, 0 partial, 0 deliberate, 4 gap, 1 not-automatable.
13
+
14
+ | id | det | status | class | detail |
15
+ |---|---|---|---|---|
16
+ | L1.1 | S | implemented | Build-time env (`NEXT_PUBLIC_*`, `VITE_*`) set at runtime — never reaches the browser | S:runtime-env-in-client-code, S:runtime-env-in-client-code |
17
+ | L1.2 | R | gap | CDN/edge cache not invalidated after deploy; old bundle still served | no detector for R |
18
+ | L1.3 | R | implemented | Two hosts serving different builds; health check green against a placeholder | R:route.unreachable |
19
+ | L1.4 | R | gap | Service worker serving a stale app shell | no detector for R |
20
+ | L1.5 | R | implemented | `ChunkLoadError` — hashed chunk gone after deploy, open tabs break | R:delivery.chunk-load |
21
+ | L1.6 | R | implemented | Wrong `basePath`/`assetPrefix` → every asset 404s | R:route.status-* |
22
+ | L1.7 | S | gap | Env var present in CI, absent in the runtime image | no detector for S |
23
+ | L1.8 | S | gap | Build succeeded with a stub/fallback module aliased in | no detector for S |
24
+ | L1.9 | H | not-automatable | Verified on preview, shipped from prod — different pipeline | human intent — the irreducible part; one question per entity, not automatable from source or the DOM alone |
25
+ | L1.10 | R | implemented | Route's first visit failed, retry succeeded — intermittent; reported as `route.flaky`, never as a silent pass | R:route.flaky |
26
+ | L1.11 | R | implemented | Route reached only through two or more intermediate paths — a redirect chain, not a direct landing (`route.indirect-landing`; auth bounces are reported as unreached, not here) | R:route.indirect-landing |
27
+
28
+ ## L2 — Contracts and boundaries: the typed line between two sides is not honoured
29
+
30
+ 3 classes: 2 implemented, 0 partial, 0 deliberate, 1 gap, 0 not-automatable.
31
+
32
+ | id | det | status | class | detail |
33
+ |---|---|---|---|---|
34
+ | L2.1 | S | implemented | Typed-boundary bypass — a raw/untyped value crosses an API or component boundary that declares a generated or validated type, skipping the check the type exists to enforce | S:unchecked-fetch-response, S:unchecked-fetch-response |
35
+ | L2.2 | S | gap | Application contract, fixture, or RBAC drift — the client's assumed shape/permission set has diverged from what the server (or its schema/fixture) actually declares | no detector for S |
36
+ | L2.3 | S | implemented | Authenticated-transport bypass — a request goes direct to the app's own API while every sibling caller goes through an authenticated client, so whatever that client attaches (session token, error contract) is absent. Source cannot see the endpoint's authentication contract, so this is a candidate for the real-persona lane to confirm with a live 401, never a confirmed defect on its own | S:unauthenticated-api-fetch |
37
+
38
+ ## L3 — Arrival: the data never gets there
39
+
40
+ 17 classes: 9 implemented, 0 partial, 1 deliberate, 5 gap, 2 not-automatable.
41
+
42
+ | id | det | status | class | detail |
43
+ |---|---|---|---|---|
44
+ | L3.10 | R | gap | Request never fired (`enabled: false`, conditional hook) | no detector for R |
45
+ | L3.11 | R | implemented | 401/403 — token not attached, or expired mid-session | R:network.http-* |
46
+ | L3.12 | G | not-automatable | HTTP 200 with an empty array — wrong tenant or filter scope | generated journey — derived from the route×entity matrix (route×role sweep or a journey), not a standing rule |
47
+ | L3.13 | G | not-automatable | Only page 1 rendered; pagination never wired | generated journey — derived from the route×entity matrix (route×role sweep or a journey), not a standing rule |
48
+ | L3.14 | S+R | deliberately-not-implemented | Race: the slower response overwrites the newer one | duplicate-in-flight-request (3.14's runtime half): needs a proven guard separating React StrictMode's double-invoke from a real race; ship only with a StrictMode fixture asserted silent |
49
+ | L3.15 | R | implemented | Waterfall — dependent sequential fetches; partial at every instant | R:network.waterfall |
50
+ | L3.16 | S | implemented | Response applied after unmount; no `AbortController` | S:effect-fetch-without-abort |
51
+ | L3.17 | S | implemented | Cache-key collision — two queries share one key | S:cache-key-collision |
52
+ | L3.18 | S | gap | Cache-key over-specificity — one entity cached under N key shapes | no detector for S |
53
+ | L3.19 | R | gap | Retry storm → rate limit → looks like "no data" | no detector for R |
54
+ | L3.20 | R | implemented | CORS preflight failure — invisible behind a dev proxy | R:network.requestfailed |
55
+ | L3.21 | R | gap | Parse failure returns `undefined`, treated as empty | no detector for R |
56
+ | L3.22 | R | implemented | Same-origin JSON array carried N>0 records; every list on the page rendered zero rows — the data arrived and was never rendered | R:data.rendered-zero-of-n |
57
+ | L3.23 | R | implemented | An endpoint TEMPLATE answered 5xx, aggregated across the cell rather than per response (`network.endpoint-5xx`) | R:network.endpoint-5xx |
58
+ | L3.24 | R | implemented | The same (method, endpoint template) fired 3+ times in one passive page load — a refetch loop or a missing cache key, with no user action to explain it (`network.duplicate-request`) | R:network.duplicate-request |
59
+ | L3.25 | R | gap | Fewer distinct endpoint templates fired than the route's declared query hooks — the "suspiciously low, not zero" case 3.10 cannot see (`network.request-count-below-declared`; only filed when the import-graph join is trusted) | no detector for R |
60
+ | L3.26 | R | implemented | A cross-origin request failed — a third-party script, font, analytics or CDN asset the page depends on (`network.requestfailed.external`) | R:network.requestfailed.external |
61
+
62
+ ## L4 — Synchronisation: data arrives, then diverges
63
+
64
+ 14 classes: 6 implemented, 0 partial, 0 deliberate, 1 gap, 7 not-automatable.
65
+
66
+ | id | det | status | class | detail |
67
+ |---|---|---|---|---|
68
+ | L4.22 | S | implemented | Server entity copied into a client store — a second source of truth the query cache cannot invalidate | S:server-state-in-client-store |
69
+ | L4.23 | S | implemented | Mutation with no invalidation → every sibling view stale | S:no-invalidation |
70
+ | L4.24 | S | implemented | Invalidation targets the wrong key — nothing the written resource's readers query under; blast radius resolved | S:partial-invalidation |
71
+ | L4.25 | G | not-automatable | Optimistic update never reconciled with server truth | generated journey — derived from the route×entity matrix (route×role sweep or a journey), not a standing rule (but a detector exists: R:coherence.write-not-durable, R:coherence.write-not-durable, R:crud.write-lost — atlas Det may be stale) |
72
+ | L4.26 | G | not-automatable | Optimistic update not rolled back on error | generated journey — derived from the route×entity matrix (route×role sweep or a journey), not a standing rule |
73
+ | L4.27 | S | gap | Two components own one mutation; only one invalidates | no detector for S |
74
+ | L4.28 | S | implemented | Derived state not recomputed — `useState(props.x)`, stale closure, dead memo | S:derived-state-in-useState, S:stale-closure |
75
+ | L4.29 | S | implemented | Tearing — external store read without `useSyncExternalStore` under concurrent rendering | S:external-store-without-sync |
76
+ | L4.30 | A | not-automatable | Cross-tab divergence (no storage/broadcast sync) | architecture-dependent — applies only if this repo chose that architecture; a configuration question, not a missing rule |
77
+ | L4.31 | A | not-automatable | Realtime subscription not re-established after reconnect | architecture-dependent — applies only if this repo chose that architecture; a configuration question, not a missing rule (but a detector exists: R:coherence.offline-replay-divergence — atlas Det may be stale) |
78
+ | L4.32 | G | not-automatable | Form draft silently overwritten by a background refetch | generated journey — derived from the route×entity matrix (route×role sweep or a journey), not a standing rule |
79
+ | L4.33 | G | not-automatable | Deleted entity still referenced by a cached list | generated journey — derived from the route×entity matrix (route×role sweep or a journey), not a standing rule (but a detector exists: R:crud.delete-ineffective — atlas Det may be stale) |
80
+ | L4.34 | G | not-automatable | Pagination cache not reset on filter change → mixed pages | generated journey — derived from the route×entity matrix (route×role sweep or a journey), not a standing rule |
81
+ | L4.35 | R | implemented | Write succeeded through the real form, then client-side navigation to a blast-radius route refetched nothing and shows the old value — 4.23/4.24 confirmed live (`--mutate`) | R:coherence.stale-after-write, R:coherence.stale-after-write, R:coherence.refetched-stale, R:coherence.refetched-stale, R:crud.update-lost |
82
+
83
+ ## L5 — Rendering/representation: the frame paints wrong, or the value in it lies
84
+
85
+ 26 classes: 19 implemented, 0 partial, 0 deliberate, 3 gap, 4 not-automatable.
86
+
87
+ | id | det | status | class | detail |
88
+ |---|---|---|---|---|
89
+ | L5.34 | S+R | implemented | Missing loading state → empty shell paints first | S:unread-query-loading, S:no-loading-boundary, R:render.empty, R:state.slow-blank |
90
+ | L5.35 | S | implemented | Missing error state → failure renders as empty, silently | S:unread-query-error |
91
+ | L5.36 | S+R | implemented | Missing empty state → an empty list and a failed fetch are indistinguishable | S:missing-empty-state, R:render.empty-list-no-state, R:state.no-empty-state, R:coherence.missing-required-summary |
92
+ | L5.37 | R | implemented | Suspense boundary converts an error into a permanent spinner | R:render.stuck-loading, R:state.stuck-spinner |
93
+ | L5.38 | S | implemented | Error boundary catches and renders nothing | S:no-error-boundary, S:no-error-boundary |
94
+ | L5.39 | R | implemented | Hydration mismatch — SSR output ≠ first client render | R:render.hydration-mismatch |
95
+ | L5.40 | S | implemented | List key collision (`key={index}`) → possible local state or focus mismatch when a list reorders | S:index-as-list-key |
96
+ | L5.41 | S | gap | Effect dependency bug → infinite render, or never runs | no detector for S |
97
+ | L5.42 | S | implemented | Conditional hook order violation | S:conditional-hook-call |
98
+ | L5.43 | R | gap | Portal/modal escapes its theme or stacking context | no detector for R |
99
+ | L5.44 | R | implemented | Virtualised list renders zero rows at certain heights | R:render.virtualized-empty |
100
+ | L5.45 | R | implemented | Layout shift on data arrival (no reserved space) | R:perf.layout-shift |
101
+ | L5.46 | R | implemented | `undefined` rendered as text † (this one reached a live API in prod) | R:value.leak |
102
+ | L5.47 | R | implemented | `NaN` rendered | R:value.leak |
103
+ | L5.48 | R | implemented | `[object Object]` rendered | R:value.leak |
104
+ | L5.49 | R | implemented | `Invalid Date` rendered | R:value.leak |
105
+ | L5.50 | R | implemented | The string `"null"` rendered | R:value.leak |
106
+ | L5.51 | R | implemented | Unresolved template variable | R:value.leak |
107
+ | L5.52 | H | not-automatable | Timezone — the date shifts by a day | human intent — the irreducible part; one question per entity, not automatable from source or the DOM alone |
108
+ | L5.53 | H | not-automatable | Currency — cents/dollars confusion, float rounding | human intent — the irreducible part; one question per entity, not automatable from source or the DOM alone |
109
+ | L5.54 | H | not-automatable | Locale/number formatting inconsistent between views | human intent — the irreducible part; one question per entity, not automatable from source or the DOM alone (but a detector exists: R:coherence.representation-divergence — atlas Det may be stale) |
110
+ | L5.55 | S | implemented | Unescaped HTML or raw markdown shown | S:unsanitized-markdown-raw-html |
111
+ | L5.56 | R | implemented | Truncation with no ellipsis or title | R:layout.truncated-no-title |
112
+ | L5.57 | R | implemented | Enum rendered raw (`in_progress`, not "In progress") | R:value.raw-enum |
113
+ | L5.58 | H | not-automatable | Precision loss on large IDs (JS number vs int64) | human intent — the irreducible part; one question per entity, not automatable from source or the DOM alone |
114
+ | L5.59 | R | gap | Fabricated success — the run reports a pass that the underlying evidence does not support. Currently unmeasured: no encoded oracle exists, and this class has no producer | no detector for R |
115
+
116
+ ## L6 — Interaction and control flow
117
+
118
+ 14 classes: 9 implemented, 0 partial, 0 deliberate, 1 gap, 4 not-automatable.
119
+
120
+ | id | det | status | class | detail |
121
+ |---|---|---|---|---|
122
+ | L6.59 | S | implemented | Double submit — no pending guard → duplicate records | S:submit-without-pending-guard |
123
+ | L6.60 | G | not-automatable | Navigation during a pending mutation → lost write | generated journey — derived from the route×entity matrix (route×role sweep or a journey), not a standing rule |
124
+ | L6.61 | G | not-automatable | Back button does not restore list state, scroll, or filters | generated journey — derived from the route×entity matrix (route×role sweep or a journey), not a standing rule |
125
+ | L6.62 | G | not-automatable | Refresh on a deep link loses required state | generated journey — derived from the route×entity matrix (route×role sweep or a journey), not a standing rule |
126
+ | L6.63 | S | implemented | Unsaved-changes guard missing | S:unsaved-changes-guard-missing |
127
+ | L6.64 | R | implemented | Interactive element covered by another element — clicks land on the occluder (`interact.click-occluded`; open dialogs and `pointer-events:none` overlays excluded) | R:interact.click-occluded |
128
+ | L6.65 | R | gap | Focus lost after an action → keyboard user stranded | no detector for R |
129
+ | L6.66 | G | not-automatable | Client validation passes; server rejects with an unhandled error shape | generated journey — derived from the route×entity matrix (route×role sweep or a journey), not a standing rule (but a detector exists: R:crud.create-failed — atlas Det may be stale) |
130
+ | L6.67 | R | implemented | Disabled state not conveyed — looks clickable, does nothing | R:interact.disabled-not-conveyed |
131
+ | L6.68 | S | implemented | Async action with no feedback → the user clicks again | S:async-click-without-feedback |
132
+ | L6.69 | R | implemented | A control the surface should render never appeared, waited for rather than raced (`interact.never-appeared`) | R:interact.never-appeared |
133
+ | L6.70 | R | implemented | A control appeared but was never clickable — a different defect from never appearing, and it needs a different fix (`interact.not-clickable`) | R:interact.not-clickable |
134
+ | L6.71 | R | implemented | Inert affordance — a control was clicked and nothing observable followed: no DOM mutation, no request, no navigation, no ARIA state change (`interact.inert-control`) | R:interact.inert-control |
135
+ | L6.72 | R | implemented | Clicking a control throws — the handler errors where a user would click (`interact.click-error`) | R:interact.click-error |
136
+
137
+ ## L7 — Access, identity, client security
138
+
139
+ 10 classes: 4 implemented, 0 partial, 0 deliberate, 2 gap, 4 not-automatable.
140
+
141
+ | id | det | status | class | detail |
142
+ |---|---|---|---|---|
143
+ | L7.69 | G | not-automatable | Role-gated route reachable by deep link | generated journey — derived from the route×entity matrix (route×role sweep or a journey), not a standing rule |
144
+ | L7.70 | G | not-automatable | UI hides an action the API allows — or shows one it denies | generated journey — derived from the route×entity matrix (route×role sweep or a journey), not a standing rule (but a detector exists: R:crud.unauthorized-allowed — atlas Det may be stale) |
145
+ | L7.71 | G | not-automatable | Tenant scope missing from a query → cross-tenant leak | generated journey — derived from the route×entity matrix (route×role sweep or a journey), not a standing rule |
146
+ | L7.72 | R | gap | Session expiry → redirect loop, or silent empty data | no detector for R |
147
+ | L7.73 | G | not-automatable | Stale permissions cached after a role change | generated journey — derived from the route×entity matrix (route×role sweep or a journey), not a standing rule (but a detector exists: R:coherence.cross-principal-divergence, R:coherence.cross-principal-divergence — atlas Det may be stale) |
148
+ | L7.74 | S | implemented | XSS sink — unsanitised HTML injection | S:unsanitized-html |
149
+ | L7.75 | S | implemented | Prototype pollution — merging untrusted payload into an object | S:prototype-pollution-shape |
150
+ | L7.76 | S | gap | DOM clobbering — a named element shadowing a global | no detector for S |
151
+ | L7.77 | R | implemented | CSP violation blocking a real resource | R:security.csp-violation |
152
+ | L7.78 | R | implemented | `SameSite=None` cookie without `Secure` — the browser rejects it; auth silently absent (`security.cookie-samesite-none-insecure`) | R:security.cookie-samesite-none-insecure |
153
+
154
+ ## L8 — Responsive and layout
155
+
156
+ 7 classes: 5 implemented, 0 partial, 0 deliberate, 2 gap, 0 not-automatable.
157
+
158
+ | id | det | status | class | detail |
159
+ |---|---|---|---|---|
160
+ | L8.79 | R | implemented | Horizontal scroll at 320/390 | R:layout.h-scroll |
161
+ | L8.80 | R | implemented | Overlap or clipping at a breakpoint | R:layout.clipped-text |
162
+ | L8.82 | R | gap | Long content (400-char name) breaks the layout | no detector for R |
163
+ | L8.83 | R | implemented | Broken image with no fallback dimension | R:render.broken-image |
164
+ | L8.84 | R | implemented | Dark-mode override missed | R:theme.tokens-not-applied, R:theme.light-hardcoded |
165
+ | L8.85 | R | gap | Font swap shift | no detector for R |
166
+ | L8.86 | R | implemented | Fixed height clips content at large text size or zoom | R:layout.fixed-height-clip |
167
+
168
+ ## L9 — Accessibility
169
+
170
+ 8 classes: 7 implemented, 0 partial, 1 deliberate, 0 gap, 0 not-automatable.
171
+
172
+ | id | det | status | class | detail |
173
+ |---|---|---|---|---|
174
+ | L9.87 | R | implemented | Contrast below threshold | R:a11y.contrast |
175
+ | L9.88 | R | implemented | Control with no accessible name | R:a11y.unlabeled-control, R:a11y.label-association-broken |
176
+ | L9.89 | R | implemented | Keyboard trap, or unreachable control | R:a11y.keyboard-trap |
177
+ | L9.90 | R | implemented | Focus order does not follow visual order | R:a11y.focus-order |
178
+ | L9.91 | R | implemented | No visible focus ring | R:a11y.no-focus-ring |
179
+ | L9.92 | S | deliberately-not-implemented | Async result with no live region | missing-live-region-for-async-status (9.92): scoping unresolved: a live region far from the toast is still a live region; the conservative gate leaves mostly misses |
180
+ | L9.93 | R | implemented | Tap target < 24px — excluding sr-only elements † (moved from L8 in v2: reachability by touch is an accessibility property) | R:a11y.tap-target |
181
+ | L9.94 | R | implemented | ARIA state attribute the element's role does not support — `aria-selected`, `aria-pressed` (the rest of the `aria-allowed-attr` table is not measured) | R:a11y.aria-attr-not-allowed |
182
+
183
+ ## L10 — Performance
184
+
185
+ 9 classes: 7 implemented, 0 partial, 1 deliberate, 1 gap, 0 not-automatable.
186
+
187
+ | id | det | status | class | detail |
188
+ |---|---|---|---|---|
189
+ | L10.93 | R | implemented | Long task > 200ms — input blocked for that window (INP) | R:perf.long-task |
190
+ | L10.94 | R | implemented | CLS > 0.1 — content moves under the cursor | R:perf.layout-shift |
191
+ | L10.95 | R | implemented | LCP > 2500ms | R:perf.lcp |
192
+ | L10.96 | R | implemented | Layout thrash — geometry read after DOM write, in a loop | R:perf.layout-thrash |
193
+ | L10.97 | R | implemented | Memory leak — heap retained after GC across navigation | R:perf.memory-leak |
194
+ | L10.98 | S | implemented | Unstable context value → re-render storm, memoisation dead | S:unstable-context-value |
195
+ | L10.99 | S | gap | Unbounded list render | no detector for S |
196
+ | L10.100 | S | deliberately-not-implemented | Bundle size regression | bundle-size-regression (10.100's runtime half): needs a per-repo build-output path; no universal baseline location in monorepos |
197
+ | L10.101 | R | implemented | Preloaded resource never used — Chrome's own diagnostic, exact-matched (`perf.unused-preload`) | R:perf.unused-preload |
198
+
199
+ ## L11 — Verification integrity
200
+
201
+ 16 classes: 3 implemented, 0 partial, 0 deliberate, 5 gap, 8 not-automatable.
202
+
203
+ | id | det | status | class | detail |
204
+ |---|---|---|---|---|
205
+ | L11.101 | H | not-automatable | Capture fires before render → the gate measures nothing † *"worse than no gate — it consumes the verification slot without occupying it"* | human intent — the irreducible part; one question per entity, not automatable from source or the DOM alone (but a detector exists: R:state.not-intercepted — atlas Det may be stale) |
206
+ | L11.102 | H | not-automatable | Baseline captured from the buggy build → locks the bug in as correct | human intent — the irreducible part; one question per entity, not automatable from source or the DOM alone |
207
+ | L11.103 | H | not-automatable | An assertion that cannot fail (empty selector, truthy on undefined) | human intent — the irreducible part; one question per entity, not automatable from source or the DOM alone |
208
+ | L11.104 | R | implemented | The check ran against the wrong host or build | R:route.not-found-shell |
209
+ | L11.105 | H | not-automatable | Test skipped or filtered, reported green | human intent — the irreducible part; one question per entity, not automatable from source or the DOM alone (but a detector exists: R:journey.load-failed — atlas Det may be stale) |
210
+ | L11.106 | H | not-automatable | Claim inflation — report says 103, reality is 1 | human intent — the irreducible part; one question per entity, not automatable from source or the DOM alone |
211
+ | L11.107 | G | not-automatable | Verified at the wrong layer — API green, UI broken | generated journey — derived from the route×entity matrix (route×role sweep or a journey), not a standing rule (but a detector exists: R:journey.failed — atlas Det may be stale) |
212
+ | L11.108 | H | not-automatable | Fixture regenerated from actual output → the test becomes a screenshot of the bug | human intent — the irreducible part; one question per entity, not automatable from source or the DOM alone |
213
+ | L11.109 | H | not-automatable | Gate returns "inconclusive" and is read as pass | human intent — the irreducible part; one question per entity, not automatable from source or the DOM alone |
214
+ | L11.110 | S | gap | Route absent from the sweep because the route list is hardcoded | no detector for S |
215
+ | L11.111 | R | implemented | The dark-mode pass could not run — the theme seeding itself threw, so "dark renders correctly" was never measured (`theme.check-failed`) | R:theme.check-failed |
216
+ | L11.112 | R | implemented | The `--mutate` replay could not complete, so nothing is claimed about the write either way (`mutate.failed`) | R:crud.cleanup-failed, R:crud.cleanup-failed, R:crud.cleanup-failed |
217
+ | L11.113 | R | gap | Import-walk vs request-log blind spot — the static import graph and the runtime request log disagree about what a route depends on, and neither is trusted as the superset of the other | no detector for R |
218
+ | L11.114 | S | gap | Wrong-layer verification (of the harness itself) — a claim scoped to one detection mode (S or R) is verified using evidence produced by the other mode, and reported as if the claimed mode had actually run | no detector for S |
219
+ | L11.115 | S | gap | Incomplete denominator — a pass/fail claim is reported without stating how many of what, or the denominator silently drops an unmeasured/unreached population instead of naming it | no detector for S |
220
+ | L11.116 | S | gap | Unmeasured promoted to pass — a route, cell, or class the run never reached is folded into the passing count instead of `report.unreached` | no detector for S |
221
+
222
+ ## Reverse drift
223
+
224
+ None today.
225
+ ## Deliberately not implemented, not linked to one class
226
+
227
+ - **render-blocking-head-script** — fires on deliberately synchronous analytics snippets — "verify this is intentional" is not a defect claim
228
+ - **indexeddb-open-error** — too narrow — apps touch IndexedDB through libraries that already handle it
229
+ - **deterministic-rendering (render twice, diff)** — the noisiest possible rule: timestamps, counters, carousels and A/B buckets are legitimately non-deterministic
230
+ - **CSRF-token-presence** — token-auth and SameSite-cookie architectures are both healthy without one; the absence proves nothing
231
+
@@ -0,0 +1,318 @@
1
+ # Frontend failure atlas
2
+
3
+ > **FROZEN — this is atlas v1.** Every archive, fid, diff and external
4
+ > consumer that predates atlas v2 resolves through this file and through
5
+ > `lib/qa/atlas-v1.mjs`. Do not add, renumber, retitle or reword a row here —
6
+ > that would reinterpret evidence that was already measured and already acted
7
+ > on. The current taxonomy is `references/atlas.md` (v2); fix it there.
8
+
9
+ Every way a frontend fails, organised by **pipeline position** — the order is
10
+ information: a defect at L1 makes every check below it meaningless, because you
11
+ are testing a build nobody is serving.
12
+
13
+ Detection column:
14
+
15
+ | | Meaning |
16
+ |---|---|
17
+ | **S** | Static. `classify.mjs` / `inventory.mjs`. Free, exhaustive, runs on save. |
18
+ | **R** | Runtime invariant. `sweep.mjs` + `probe.js`. No per-feature authoring. |
19
+ | **G** | Generated journey. Derived from the route×entity matrix. |
20
+ | **H** | Human intent. The irreducible part — one question per entity. |
21
+ | **A** | Architecture-dependent: applies only if you chose that architecture. |
22
+
23
+ `†` = observed in this user's own session corpus (86 sessions, 532 findings).
24
+
25
+ ---
26
+
27
+ ## L1 — Delivery: the code under test is not the code being served
28
+
29
+ Everything below L1 is void if L1 is wrong. Check it first, always.
30
+
31
+ | # | Class | Det |
32
+ |---|---|---|
33
+ | 1.1 | Build-time env (`NEXT_PUBLIC_*`, `VITE_*`) set at runtime — never reaches the browser † | S |
34
+ | 1.2 | CDN/edge cache not invalidated after deploy; old bundle still served † | R |
35
+ | 1.3 | Two hosts serving different builds; health check green against a placeholder † | R |
36
+ | 1.4 | Service worker serving a stale app shell | R |
37
+ | 1.5 | `ChunkLoadError` — hashed chunk gone after deploy, open tabs break | R |
38
+ | 1.6 | Wrong `basePath`/`assetPrefix` → every asset 404s | R |
39
+ | 1.7 | Env var present in CI, absent in the runtime image | S |
40
+ | 1.8 | Build succeeded with a stub/fallback module aliased in | S |
41
+ | 1.9 | Verified on preview, shipped from prod — different pipeline † | H |
42
+ | 1.10 | Route's first visit failed, retry succeeded — intermittent; reported as `route.flaky`, never as a silent pass | R |
43
+ | 1.11 | Route reached only through two or more intermediate paths — a redirect chain, not a direct landing (`route.indirect-landing`; auth bounces are reported as unreached, not here) | R |
44
+
45
+ ## L2 — Arrival: the data never gets there
46
+
47
+ | # | Class | Det |
48
+ |---|---|---|
49
+ | 2.10 | Request never fired (`enabled: false`, conditional hook) | R |
50
+ | 2.11 | 401/403 — token not attached, or expired mid-session | R |
51
+ | 2.12 | HTTP 200 with an empty array — wrong tenant or filter scope | G |
52
+ | 2.13 | Only page 1 rendered; pagination never wired | G |
53
+ | 2.14 | Race: the slower response overwrites the newer one | S+R |
54
+ | 2.15 | Waterfall — dependent sequential fetches; partial at every instant | R |
55
+ | 2.16 | Response applied after unmount; no `AbortController` | S |
56
+ | 2.17 | Cache-key collision — two queries share one key | S |
57
+ | 2.18 | Cache-key over-specificity — one entity cached under N key shapes | S |
58
+ | 2.19 | Retry storm → rate limit → looks like "no data" | R |
59
+ | 2.20 | CORS preflight failure — invisible behind a dev proxy | R |
60
+ | 2.21 | Parse failure returns `undefined`, treated as empty | R |
61
+ | 2.22 | Same-origin JSON array carried N>0 records; every list on the page rendered zero rows — the data arrived and was never rendered | R |
62
+ | 2.23 | An endpoint TEMPLATE answered 5xx, aggregated across the cell rather than per response (`network.endpoint-5xx`) | R |
63
+ | 2.24 | The same (method, endpoint template) fired 3+ times in one passive page load — a refetch loop or a missing cache key, with no user action to explain it (`network.duplicate-request`) | R |
64
+ | 2.25 | Fewer distinct endpoint templates fired than the route's declared query hooks — the "suspiciously low, not zero" case 2.10 cannot see (`network.request-count-below-declared`; only filed when the import-graph join is trusted) | R |
65
+ | 2.26 | A cross-origin request failed — a third-party script, font, analytics or CDN asset the page depends on (`network.requestfailed.external`) | R |
66
+
67
+ ## L3 — Synchronisation: data arrives, then diverges
68
+
69
+ **The cross-page sync bug lives here.** `inventory.mjs syncRisks` finds most of
70
+ it statically, with blast radius.
71
+
72
+ | # | Class | Det |
73
+ |---|---|---|
74
+ | 3.22 | **Server entity copied into a client store** — a second source of truth the query cache cannot invalidate | S |
75
+ | 3.23 | Mutation with no invalidation → every sibling view stale | S |
76
+ | 3.24 | Invalidation targets the wrong key — nothing the written resource's readers query under; blast radius resolved | S |
77
+ | 3.25 | Optimistic update never reconciled with server truth | G |
78
+ | 3.26 | Optimistic update not rolled back on error | G |
79
+ | 3.27 | Two components own one mutation; only one invalidates | S |
80
+ | 3.28 | Derived state not recomputed — `useState(props.x)`, stale closure, dead memo | S |
81
+ | 3.29 | Tearing — external store read without `useSyncExternalStore` under concurrent rendering | S |
82
+ | 3.30 | Cross-tab divergence (no storage/broadcast sync) | A |
83
+ | 3.31 | Realtime subscription not re-established after reconnect | A |
84
+ | 3.32 | Form draft silently overwritten by a background refetch | G |
85
+ | 3.33 | Deleted entity still referenced by a cached list | G |
86
+ | 3.34 | Pagination cache not reset on filter change → mixed pages | G |
87
+ | 3.35 | Write succeeded through the real form, then client-side navigation to a blast-radius route refetched nothing and shows the old value — 3.23/3.24 confirmed live (`--mutate`) | R |
88
+
89
+ ## L4 — Rendering and lifecycle
90
+
91
+ | # | Class | Det |
92
+ |---|---|---|
93
+ | 4.34 | Missing loading state → empty shell paints first | S+R |
94
+ | 4.35 | Missing error state → failure renders as empty, silently | S |
95
+ | 4.36 | **Missing empty state → an empty list and a failed fetch are indistinguishable** | S+R |
96
+ | 4.37 | Suspense boundary converts an error into a permanent spinner | R |
97
+ | 4.38 | Error boundary catches and renders nothing | S |
98
+ | 4.39 | Hydration mismatch — SSR output ≠ first client render | R |
99
+ | 4.40 | List key collision (`key={index}`) → wrong row updates on reorder | S |
100
+ | 4.41 | Effect dependency bug → infinite render, or never runs | S |
101
+ | 4.42 | Conditional hook order violation | S |
102
+ | 4.43 | Portal/modal escapes its theme or stacking context | R |
103
+ | 4.44 | Virtualised list renders zero rows at certain heights | R |
104
+ | 4.45 | Layout shift on data arrival (no reserved space) | R |
105
+
106
+ ## L5 — Value and serialisation: the leaks
107
+
108
+ Cheapest class to detect, highest hit rate, almost nobody checks it.
109
+
110
+ | # | Class | Det |
111
+ |---|---|---|
112
+ | 5.46 | `undefined` rendered as text † (this one reached a live API in prod) | R |
113
+ | 5.47 | `NaN` rendered | R |
114
+ | 5.48 | `[object Object]` rendered | R |
115
+ | 5.49 | `Invalid Date` rendered | R |
116
+ | 5.50 | The string `"null"` rendered | R |
117
+ | 5.51 | Unresolved template variable † | R |
118
+ | 5.52 | Timezone — the date shifts by a day | H |
119
+ | 5.53 | Currency — cents/dollars confusion, float rounding | H |
120
+ | 5.54 | Locale/number formatting inconsistent between views | H |
121
+ | 5.55 | Unescaped HTML or raw markdown shown | S |
122
+ | 5.56 | Truncation with no ellipsis or title | R |
123
+ | 5.57 | Enum rendered raw (`in_progress`, not "In progress") | R |
124
+ | 5.58 | Precision loss on large IDs (JS number vs int64) | H |
125
+
126
+ ## L6 — Interaction and control flow
127
+
128
+ | # | Class | Det |
129
+ |---|---|---|
130
+ | 6.59 | **Double submit** — no pending guard → duplicate records | S |
131
+ | 6.60 | Navigation during a pending mutation → lost write | G |
132
+ | 6.61 | Back button does not restore list state, scroll, or filters | G |
133
+ | 6.62 | Refresh on a deep link loses required state | G |
134
+ | 6.63 | Unsaved-changes guard missing | S |
135
+ | 6.64 | Interactive element covered by another element — clicks land on the occluder (`interact.click-occluded`; open dialogs and `pointer-events:none` overlays excluded) | R |
136
+ | 6.65 | Focus lost after an action → keyboard user stranded | R |
137
+ | 6.66 | Client validation passes; server rejects with an unhandled error shape | G |
138
+ | 6.67 | Disabled state not conveyed — looks clickable, does nothing | R |
139
+ | 6.68 | Async action with no feedback → the user clicks again | S |
140
+ | 6.69 | A control the surface should render never appeared, waited for rather than raced (`interact.never-appeared`) | R |
141
+ | 6.70 | A control appeared but was never clickable — a different defect from never appearing, and it needs a different fix (`interact.not-clickable`) | R |
142
+ | 6.71 | **Inert affordance** — a control was clicked and nothing observable followed: no DOM mutation, no request, no navigation, no ARIA state change (`interact.inert-control`) | R |
143
+ | 6.72 | Clicking a control throws — the handler errors where a user would click (`interact.click-error`) | R |
144
+
145
+ ## L7 — Access and identity
146
+
147
+ | # | Class | Det |
148
+ |---|---|---|
149
+ | 7.69 | Role-gated route reachable by deep link | G |
150
+ | 7.70 | UI hides an action the API allows — or shows one it denies | G |
151
+ | 7.71 | Tenant scope missing from a query → cross-tenant leak | G |
152
+ | 7.72 | Session expiry → redirect loop, or silent empty data | R |
153
+ | 7.73 | Stale permissions cached after a role change | G |
154
+ | 7.74 | XSS sink — unsanitised HTML injection | S |
155
+ | 7.75 | Prototype pollution — merging untrusted payload into an object | S |
156
+ | 7.76 | DOM clobbering — a named element shadowing a global | S |
157
+ | 7.77 | CSP violation blocking a real resource | R |
158
+ | 7.78 | `SameSite=None` cookie without `Secure` — the browser rejects it; auth silently absent (`security.cookie-samesite-none-insecure`) | R |
159
+
160
+ ## L8 — Responsive and layout
161
+
162
+ | # | Class | Det |
163
+ |---|---|---|
164
+ | 8.79 | Horizontal scroll at 320/390 | R |
165
+ | 8.80 | Overlap or clipping at a breakpoint | R |
166
+ | 8.81 | Tap target < 24px — **excluding sr-only elements** † | R |
167
+ | 8.82 | Long content (400-char name) breaks the layout | R |
168
+ | 8.83 | Broken image with no fallback dimension | R |
169
+ | 8.84 | Dark-mode override missed † | R |
170
+ | 8.85 | Font swap shift | R |
171
+ | 8.86 | Fixed height clips content at large text size or zoom | R |
172
+
173
+ ## L9 — Accessibility
174
+
175
+ | # | Class | Det |
176
+ |---|---|---|
177
+ | 9.87 | Contrast below threshold | R |
178
+ | 9.88 | Control with no accessible name | R |
179
+ | 9.89 | Keyboard trap, or unreachable control | R |
180
+ | 9.90 | Focus order does not follow visual order | R |
181
+ | 9.91 | No visible focus ring | R |
182
+ | 9.92 | Async result with no live region | S |
183
+
184
+ ## L10 — Performance
185
+
186
+ | # | Class | Det |
187
+ |---|---|---|
188
+ | 10.93 | Long task > 200ms — input blocked for that window (INP) | R |
189
+ | 10.94 | CLS > 0.1 — content moves under the cursor | R |
190
+ | 10.95 | LCP > 2500ms | R |
191
+ | 10.96 | Layout thrash — geometry read after DOM write, in a loop | R |
192
+ | 10.97 | Memory leak — heap retained after GC across navigation | R |
193
+ | 10.98 | Unstable context value → re-render storm, memoisation dead | S |
194
+ | 10.99 | Unbounded list render | S |
195
+ | 10.100 | Bundle size regression | S |
196
+ | 10.101 | Preloaded resource never used — Chrome's own diagnostic, exact-matched (`perf.unused-preload`) | R |
197
+
198
+ ## L11 — The check itself
199
+
200
+ **The highest-leverage layer and the one nobody writes down.** Every class here
201
+ makes the layers above report green while broken.
202
+
203
+ | # | Class | Det |
204
+ |---|---|---|
205
+ | 11.101 | Capture fires before render → the gate measures nothing † *"worse than no gate — it consumes the verification slot without occupying it"* | H |
206
+ | 11.102 | Baseline captured from the buggy build → locks the bug in as correct | H |
207
+ | 11.103 | An assertion that cannot fail (empty selector, truthy on undefined) | H |
208
+ | 11.104 | The check ran against the wrong host or build † | R |
209
+ | 11.105 | Test skipped or filtered, reported green | H |
210
+ | 11.106 | Claim inflation — report says 103, reality is 1 † | H |
211
+ | 11.107 | **Verified at the wrong layer — API green, UI broken** † | G |
212
+ | 11.108 | Fixture regenerated from actual output → the test becomes a screenshot of the bug † | H |
213
+ | 11.109 | Gate returns "inconclusive" and is read as pass | H |
214
+ | 11.110 | Route absent from the sweep because the route list is hardcoded | S |
215
+ | 11.111 | The dark-mode pass could not run — the theme seeding itself threw, so "dark renders correctly" was never measured (`theme.check-failed`) | R |
216
+ | 11.112 | The `--mutate` replay could not complete, so nothing is claimed about the write either way (`mutate.failed`) | R |
217
+
218
+ 11.107 is the mechanism behind "Claude said it was built": the API was checked,
219
+ it answered correctly, and the UI had no password field because the env var was
220
+ inlined at build time. Verification at one layer says nothing about the layer
221
+ above it.
222
+
223
+ ---
224
+
225
+ ## The advanced concept list, sorted by what you can do with it
226
+
227
+ Concepts split three ways. Only the middle column is testable — but the first
228
+ column is what tells you **where to place the probe**, so it is not wasted.
229
+
230
+ ### Mechanisms — explain why a class exists; not themselves testable
231
+
232
+ Reconciliation · Fiber · virtual DOM diffing · structural sharing · referential
233
+ equality · immutable data patterns · event loop (macro/microtasks) · scheduler
234
+ priorities · time slicing · concurrent rendering · critical rendering path ·
235
+ paint vs composite vs layout · browser compositing layers · GPU acceleration ·
236
+ subpixel rendering · tree shaking internals · garbage collection timing ·
237
+ accessibility tree · virtual DOM complexity · priority inversion
238
+
239
+ Read these to know *why* `key={index}` corrupts rows (reconciliation), why a new
240
+ object literal kills memoisation (referential equality), why a geometry read
241
+ after a write stalls (layout → paint → composite ordering).
242
+
243
+ ### Observable — these ARE the runtime probes
244
+
245
+ | Concept | Becomes | Atlas |
246
+ |---|---|---|
247
+ | Hydration / selective hydration | React #418/#423/#425 in console; click before hydrate is dropped | 4.39 |
248
+ | Tearing in concurrent UI | external store read without `useSyncExternalStore` | 3.29 |
249
+ | Stale closure problem | effect with `[]` deps reading state | 3.28 |
250
+ | Memoisation pitfalls | Provider value is a fresh literal each render | 10.98 |
251
+ | Layout thrashing | geometry reads interleaved with writes in one frame | 10.96 |
252
+ | Long tasks API / FID / INP | longest task > 200ms | 10.93 |
253
+ | CLS / LCP | PerformanceObserver, buffered | 10.94-95 |
254
+ | Detached DOM nodes / leak detection | heap retained after GC across navigation | 10.97 |
255
+ | `ResizeObserver` loop limit | its own console error — free once console is a gate | 4.41 |
256
+ | `MutationObserver` cost | forced-reflow counter | 10.96 |
257
+ | Render waterfalls | dependent request chain ≥ 3 | 2.15 |
258
+ | `AbortController` | fetch in effect without a signal | 2.16 |
259
+ | Backpressure / streaming fetch | unconsumed reader → stalled render | 2.21 |
260
+ | Task starvation | long task + no yield | 10.93 |
261
+ | Service Worker lifecycle traps | stale app shell; buildId mismatch | 1.4 |
262
+ | Cache invalidation / SWR / ETag vs Cache-Control | response headers on the sweep | 1.2 |
263
+ | Code splitting / dynamic import chunking | `ChunkLoadError` | 1.5 |
264
+ | CORS preflight | failed OPTIONS | 2.20 |
265
+ | CSP / Trusted Types | `securitypolicyviolation` | 7.77 |
266
+ | SameSite cookie modes | cookie attributes after login | 7.78 |
267
+ | XSS / CSRF / DOM clobbering / prototype pollution | static sink detection | 7.74-76 |
268
+ | Race conditions in UI state | two fetches, slower wins | 2.14 |
269
+ | Optimistic UI rollback | mutate → force error → assert revert | 3.26 |
270
+ | Idempotent UI actions | double-submit guard | 6.59 |
271
+ | Deterministic rendering | render twice, diff | 11.103 |
272
+ | Preload / prefetch / preconnect, priority hints | resource timing on the sweep | 10.95 |
273
+ | IndexedDB | quota + upgrade failure paths | A |
274
+ | ARIA live regions | async result with no announcement | 9.92 |
275
+ | Pointer events | tap target + hit testing | 8.81 |
276
+ | Speculative prerendering | double-fired effects on prerender | A |
277
+
278
+ ### Architecture — change which classes apply, not what you check
279
+
280
+ Islands · partial hydration · streaming SSR · server components · edge rendering
281
+ · micro-frontend orchestration · module federation · Shadow DOM · custom
282
+ elements · Web Components interop · Web Workers vs Service Workers ·
283
+ `SharedArrayBuffer` · transferable objects · `OffscreenCanvas` · WebAssembly ·
284
+ WebRTC · CRDTs · event sourcing · finite state modelling · offline conflict
285
+ resolution · CSS containment
286
+
287
+ Pick one and a whole column of classes switches on. Islands and partial
288
+ hydration make 4.39 and "click before hydrate" primary. Micro-frontends and
289
+ module federation make 1.3 (two hosts, different builds) a weekly event. CRDTs
290
+ and offline make 3.30-3.31 the dominant risk. **Configuration, not new code.**
291
+
292
+ ---
293
+
294
+ ## Evaluated and deliberately not implemented
295
+
296
+ Each of these was specced far enough to know why it must not ship yet. A rule
297
+ that fires on healthy apps costs more than the class it catches.
298
+
299
+ | Check | Why not |
300
+ |---|---|
301
+ | duplicate-in-flight-request (2.14's runtime half) | needs a proven guard separating React StrictMode's double-invoke from a real race; ship only with a StrictMode fixture asserted silent |
302
+ | bundle-size-regression (10.100's runtime half) | needs a per-repo build-output path; no universal baseline location in monorepos |
303
+ | render-blocking-head-script | fires on deliberately synchronous analytics snippets — "verify this is intentional" is not a defect claim |
304
+ | missing-live-region-for-async-status (9.92) | scoping unresolved: a live region far from the toast is still a live region; the conservative gate leaves mostly misses |
305
+ | indexeddb-open-error | too narrow — apps touch IndexedDB through libraries that already handle it |
306
+ | deterministic-rendering (render twice, diff) | the noisiest possible rule: timestamps, counters, carousels and A/B buckets are legitimately non-deterministic |
307
+ | CSRF-token-presence | token-auth and SameSite-cookie architectures are both healthy without one; the absence proves nothing |
308
+
309
+ ## Adding a class
310
+
311
+ 1. Reproduce it once, by hand.
312
+ 2. Add the rule to `classify.mjs` (S) or `probe.js` (R).
313
+ 3. Plant an instance in `references/fixture.html` or `selftest-static.sh`.
314
+ 4. Confirm the selftest **fails without the fix and passes with it**.
315
+ 5. Give it a number here.
316
+
317
+ Step 4 is the one people skip, and skipping it produces 11.103 — a rule that
318
+ occupies the verification slot while asserting nothing.