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.
- package/LICENSE +22 -0
- package/NOTICE +9 -0
- package/README.md +100 -0
- package/SKILL.md +65 -0
- package/agent/prebuilt/darwin-universal/qa-agent +0 -0
- package/agent/prebuilt/linux-arm64/qa-agent +0 -0
- package/agent/prebuilt/linux-x64/qa-agent +0 -0
- package/agent/release-manifest.json +31 -0
- package/bin/autonomous-cache.mjs +2 -0
- package/bin/bench-generate.mjs +232 -0
- package/bin/bench-graph-query.mjs +2 -0
- package/bin/bench-score.mjs +10 -0
- package/bin/classify.mjs +15 -0
- package/bin/coherence-playwright.mjs +1 -0
- package/bin/coherence-runner.mjs +6 -0
- package/bin/crud-runner.mjs +12 -0
- package/bin/experiment-executor.mjs +10 -0
- package/bin/experiment-runner.mjs +3 -0
- package/bin/inventory.mjs +17 -0
- package/bin/probe.js +879 -0
- package/bin/qa.mjs +16 -0
- package/bin/rules.mjs +5 -0
- package/bin/run-benchmarks.mjs +2 -0
- package/bin/runtime-packs.mjs +3 -0
- package/bin/semantic-explorer.mjs +3 -0
- package/bin/shadow-collector.mjs +3 -0
- package/bin/shadow-replay.mjs +6 -0
- package/bin/soak-marathon.mjs +15 -0
- package/bin/sweep.mjs +43 -0
- package/bin/validate-priors.mjs +3 -0
- package/bin/verify-artifacts.mjs +3 -0
- package/bin/verify-manifest.mjs +2 -0
- package/install-skill.sh +45 -0
- package/lib/qa/agent-availability.mjs +1 -0
- package/lib/qa/agent-result.mjs +1 -0
- package/lib/qa/analysis.mjs +5 -0
- package/lib/qa/atlas-coverage.mjs +11 -0
- package/lib/qa/atlas-v1.mjs +1 -0
- package/lib/qa/atlas.mjs +2 -0
- package/lib/qa/auth/coordinator.mjs +1 -0
- package/lib/qa/auth/launcher.mjs +3 -0
- package/lib/qa/auth/lease.mjs +1 -0
- package/lib/qa/auth/oracle.mjs +1 -0
- package/lib/qa/auth/personas.mjs +3 -0
- package/lib/qa/autonomous-cache.mjs +3 -0
- package/lib/qa/block-ledger.mjs +1 -0
- package/lib/qa/capture-report.mjs +63 -0
- package/lib/qa/ci.mjs +11 -0
- package/lib/qa/cli.mjs +538 -0
- package/lib/qa/coherence/analyze.mjs +1 -0
- package/lib/qa/coherence/cache-key.mjs +6 -0
- package/lib/qa/coherence/causes.mjs +1 -0
- package/lib/qa/coherence/concurrency.mjs +1 -0
- package/lib/qa/coherence/contracts.mjs +1 -0
- package/lib/qa/coherence/entity-aliases.mjs +1 -0
- package/lib/qa/coherence/enums.mjs +1 -0
- package/lib/qa/coherence/family-report.mjs +1 -0
- package/lib/qa/coherence/flow-pool.mjs +1 -0
- package/lib/qa/coherence/graph.mjs +1 -0
- package/lib/qa/coherence/identity.mjs +1 -0
- package/lib/qa/coherence/ir.mjs +4 -0
- package/lib/qa/coherence/navigation.mjs +1 -0
- package/lib/qa/coherence/observe.mjs +2 -0
- package/lib/qa/coherence/offline.mjs +1 -0
- package/lib/qa/coherence/plan-join.mjs +1 -0
- package/lib/qa/coherence/provenance.mjs +2 -0
- package/lib/qa/coherence/report.mjs +1 -0
- package/lib/qa/coherence/representations.mjs +1 -0
- package/lib/qa/coherence/risk.mjs +2 -0
- package/lib/qa/coherence/structural-js.mjs +11 -0
- package/lib/qa/coherence/symbols.mjs +6 -0
- package/lib/qa/coherence/tanstack.mjs +4 -0
- package/lib/qa/coherence/views.mjs +1 -0
- package/lib/qa/coherence-planner.mjs +1 -0
- package/lib/qa/command-catalog.mjs +2 -0
- package/lib/qa/commands/dev.mjs +1 -0
- package/lib/qa/commands/fix.mjs +1 -0
- package/lib/qa/commands/impl/analysis.mjs +1 -0
- package/lib/qa/commands/impl/atlas.mjs +1 -0
- package/lib/qa/commands/impl/coherence.mjs +1 -0
- package/lib/qa/commands/impl/crud.mjs +2 -0
- package/lib/qa/commands/impl/doctor.mjs +1 -0
- package/lib/qa/commands/impl/experiments.mjs +1 -0
- package/lib/qa/commands/impl/fp.mjs +1 -0
- package/lib/qa/commands/impl/graph.mjs +1 -0
- package/lib/qa/commands/impl/invariants.mjs +1 -0
- package/lib/qa/commands/impl/lanes.mjs +7 -0
- package/lib/qa/commands/impl/ledger.mjs +11 -0
- package/lib/qa/commands/impl/loop.mjs +1 -0
- package/lib/qa/commands/impl/marathon.mjs +1 -0
- package/lib/qa/commands/impl/next.mjs +38 -0
- package/lib/qa/commands/impl/report.mjs +4 -0
- package/lib/qa/commands/impl/research.mjs +4 -0
- package/lib/qa/commands/impl/selftest.mjs +1 -0
- package/lib/qa/commands/impl/serve.mjs +1 -0
- package/lib/qa/commands/impl/shadow.mjs +1 -0
- package/lib/qa/commands/impl/stability.mjs +1 -0
- package/lib/qa/commands/impl/triage.mjs +1 -0
- package/lib/qa/commands/impl/validate.mjs +4 -0
- package/lib/qa/commands/impl/version.mjs +1 -0
- package/lib/qa/commands/impl/watch.mjs +1 -0
- package/lib/qa/commands/init.mjs +6 -0
- package/lib/qa/commands/measure.mjs +1 -0
- package/lib/qa/commands/run.mjs +1 -0
- package/lib/qa/commands/show.mjs +1 -0
- package/lib/qa/commands/verify.mjs +6 -0
- package/lib/qa/deepening.mjs +1 -0
- package/lib/qa/denominators.mjs +1 -0
- package/lib/qa/depth.mjs +4 -0
- package/lib/qa/doctor.mjs +2 -0
- package/lib/qa/experiments-default.mjs +1 -0
- package/lib/qa/fix-lanes.mjs +3 -0
- package/lib/qa/forced-states.mjs +1 -0
- package/lib/qa/fp.mjs +2 -0
- package/lib/qa/fs.mjs +6 -0
- package/lib/qa/graph-schema.mjs +2 -0
- package/lib/qa/graph.mjs +3 -0
- package/lib/qa/harness-plan.mjs +3 -0
- package/lib/qa/init.mjs +5 -0
- package/lib/qa/integrity.mjs +1 -0
- package/lib/qa/invariants.mjs +84 -0
- package/lib/qa/jcs.mjs +1 -0
- package/lib/qa/lanes.mjs +1 -0
- package/lib/qa/loop.mjs +7 -0
- package/lib/qa/marathon-coherence.mjs +1 -0
- package/lib/qa/marathon-phases.mjs +1 -0
- package/lib/qa/marathon-wait.mjs +3 -0
- package/lib/qa/marathon.mjs +5 -0
- package/lib/qa/mode-coverage.mjs +1 -0
- package/lib/qa/mutation-policy.mjs +1 -0
- package/lib/qa/pattern-ledger.mjs +3 -0
- package/lib/qa/pending.mjs +1 -0
- package/lib/qa/proc.mjs +4 -0
- package/lib/qa/recommend.mjs +1 -0
- package/lib/qa/release-manifest.mjs +1 -0
- package/lib/qa/report-html.mjs +178 -0
- package/lib/qa/reports.mjs +2 -0
- package/lib/qa/request-evidence.mjs +1 -0
- package/lib/qa/research.mjs +20 -0
- package/lib/qa/routing.mjs +1 -0
- package/lib/qa/runs.mjs +1 -0
- package/lib/qa/schema.mjs +1 -0
- package/lib/qa/serve.mjs +4 -0
- package/lib/qa/server.mjs +1 -0
- package/lib/qa/source-snapshot.mjs +3 -0
- package/lib/qa/stability.mjs +3 -0
- package/lib/qa/state-evidence.mjs +1 -0
- package/lib/qa/sweep-incremental.mjs +1 -0
- package/lib/qa/triage.mjs +2 -0
- package/lib/qa/verdict.mjs +1 -0
- package/lib/qa/watch.mjs +2 -0
- package/package.json +62 -0
- package/references/ATLAS-COVERAGE.md +231 -0
- package/references/atlas-v1.md +318 -0
- package/references/atlas.md +338 -0
- package/references/verify.crud.example.json +1 -0
- package/references/verify.impact.example.json +1 -0
- package/references/verify.shadow.example.json +1 -0
- package/schemas/agent-result.schema.json +135 -0
- package/schemas/analysis.schema.json +15 -0
- package/schemas/classify.schema.json +61 -0
- package/schemas/coherence-ir.schema.json +1585 -0
- package/schemas/coherence-observed.schema.json +1171 -0
- package/schemas/coherence-plan.schema.json +77 -0
- package/schemas/coherence-risk.schema.json +46 -0
- package/schemas/crud-observed.schema.json +118 -0
- package/schemas/experiment-execution.schema.json +82 -0
- package/schemas/graph-edge.schema.json +22 -0
- package/schemas/graph-node.schema.json +81 -0
- package/schemas/graph.schema.json +2493 -0
- package/schemas/identity-oracle.schema.json +19 -0
- package/schemas/inventory.schema.json +2167 -0
- package/schemas/lanes-plan.schema.json +52 -0
- package/schemas/next.schema.json +54 -0
- package/schemas/personas.schema.json +52 -0
- package/schemas/research.schema.json +192 -0
- package/schemas/run.schema.json +160 -0
- package/schemas/shadow-replay.schema.json +68 -0
- package/schemas/stability.schema.json +92 -0
- package/schemas/sweep.schema.json +239 -0
- package/schemas/v1/agent-result.schema.json +106 -0
- package/schemas/v1/run.schema.json +96 -0
- package/schemas/verify-crud.schema.json +37 -0
- package/schemas/verify-read-only.schema.json +37 -0
- package/templates/github-workflow.yml +80 -0
- package/templates/pre-commit.sample +22 -0
- package/verify.sh +385 -0
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
# Frontend failure atlas
|
|
2
|
+
|
|
3
|
+
Every way a frontend fails, organised by **pipeline position** — the order is
|
|
4
|
+
information: a defect at L1 makes every check below it meaningless, because you
|
|
5
|
+
are testing a build nobody is serving.
|
|
6
|
+
|
|
7
|
+
This is **atlas v2**. The frozen v1 taxonomy — what every archive, fid, diff
|
|
8
|
+
and external consumer that predates v2 still resolves against — lives at
|
|
9
|
+
`references/atlas-v1.md` / `lib/qa/atlas-v1.mjs` and is never edited again. A
|
|
10
|
+
finding's coordinate is `(atlasVersion, atlas)`; missing `atlasVersion` on a
|
|
11
|
+
record means v1.
|
|
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 — Contracts and boundaries: the typed line between two sides is not honoured
|
|
46
|
+
|
|
47
|
+
**New in v2.** A typed boundary (an API client, a generated schema, a shared
|
|
48
|
+
type) exists precisely so the two sides of it cannot silently disagree. This
|
|
49
|
+
layer is what fires when something crosses that line unchecked. An
|
|
50
|
+
authorization disagreement caught here carries an Access impact tag rather
|
|
51
|
+
than becoming a second, duplicate L7 finding.
|
|
52
|
+
|
|
53
|
+
| # | Class | Det |
|
|
54
|
+
|---|---|---|
|
|
55
|
+
| 2.1 | 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 |
|
|
56
|
+
| 2.2 | 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 | S |
|
|
57
|
+
| 2.3 | 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 |
|
|
58
|
+
|
|
59
|
+
## L3 — Arrival: the data never gets there
|
|
60
|
+
|
|
61
|
+
| # | Class | Det |
|
|
62
|
+
|---|---|---|
|
|
63
|
+
| 3.10 | Request never fired (`enabled: false`, conditional hook) | R |
|
|
64
|
+
| 3.11 | 401/403 — token not attached, or expired mid-session | R |
|
|
65
|
+
| 3.12 | HTTP 200 with an empty array — wrong tenant or filter scope | G |
|
|
66
|
+
| 3.13 | Only page 1 rendered; pagination never wired | G |
|
|
67
|
+
| 3.14 | Race: the slower response overwrites the newer one | S+R |
|
|
68
|
+
| 3.15 | Waterfall — dependent sequential fetches; partial at every instant | R |
|
|
69
|
+
| 3.16 | Response applied after unmount; no `AbortController` | S |
|
|
70
|
+
| 3.17 | Cache-key collision — two queries share one key | S |
|
|
71
|
+
| 3.18 | Cache-key over-specificity — one entity cached under N key shapes | S |
|
|
72
|
+
| 3.19 | Retry storm → rate limit → looks like "no data" | R |
|
|
73
|
+
| 3.20 | CORS preflight failure — invisible behind a dev proxy | R |
|
|
74
|
+
| 3.21 | Parse failure returns `undefined`, treated as empty | R |
|
|
75
|
+
| 3.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 |
|
|
76
|
+
| 3.23 | An endpoint TEMPLATE answered 5xx, aggregated across the cell rather than per response (`network.endpoint-5xx`) | R |
|
|
77
|
+
| 3.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 |
|
|
78
|
+
| 3.25 | 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) | R |
|
|
79
|
+
| 3.26 | A cross-origin request failed — a third-party script, font, analytics or CDN asset the page depends on (`network.requestfailed.external`) | R |
|
|
80
|
+
|
|
81
|
+
## L4 — Synchronisation: data arrives, then diverges
|
|
82
|
+
|
|
83
|
+
**The cross-page sync bug lives here.** `inventory.mjs syncRisks` finds most of
|
|
84
|
+
it statically, with blast radius.
|
|
85
|
+
|
|
86
|
+
| # | Class | Det |
|
|
87
|
+
|---|---|---|
|
|
88
|
+
| 4.22 | **Server entity copied into a client store** — a second source of truth the query cache cannot invalidate | S |
|
|
89
|
+
| 4.23 | Mutation with no invalidation → every sibling view stale | S |
|
|
90
|
+
| 4.24 | Invalidation targets the wrong key — nothing the written resource's readers query under; blast radius resolved | S |
|
|
91
|
+
| 4.25 | Optimistic update never reconciled with server truth | G |
|
|
92
|
+
| 4.26 | Optimistic update not rolled back on error | G |
|
|
93
|
+
| 4.27 | Two components own one mutation; only one invalidates | S |
|
|
94
|
+
| 4.28 | Derived state not recomputed — `useState(props.x)`, stale closure, dead memo | S |
|
|
95
|
+
| 4.29 | Tearing — external store read without `useSyncExternalStore` under concurrent rendering | S |
|
|
96
|
+
| 4.30 | Cross-tab divergence (no storage/broadcast sync) | A |
|
|
97
|
+
| 4.31 | Realtime subscription not re-established after reconnect | A |
|
|
98
|
+
| 4.32 | Form draft silently overwritten by a background refetch | G |
|
|
99
|
+
| 4.33 | Deleted entity still referenced by a cached list | G |
|
|
100
|
+
| 4.34 | Pagination cache not reset on filter change → mixed pages | G |
|
|
101
|
+
| 4.35 | 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 |
|
|
102
|
+
|
|
103
|
+
## L5 — Rendering/representation: the frame paints wrong, or the value in it lies
|
|
104
|
+
|
|
105
|
+
Old Rendering (lifecycle) and old Value (serialisation) merge here: both are
|
|
106
|
+
"the DOM says something the data doesn't support," one at the shape level, one
|
|
107
|
+
at the value level. Cheapest class to detect, highest hit rate, almost nobody
|
|
108
|
+
checks it.
|
|
109
|
+
|
|
110
|
+
| # | Class | Det |
|
|
111
|
+
|---|---|---|
|
|
112
|
+
| 5.34 | Missing loading state → empty shell paints first | S+R |
|
|
113
|
+
| 5.35 | Missing error state → failure renders as empty, silently | S |
|
|
114
|
+
| 5.36 | **Missing empty state → an empty list and a failed fetch are indistinguishable** | S+R |
|
|
115
|
+
| 5.37 | Suspense boundary converts an error into a permanent spinner | R |
|
|
116
|
+
| 5.38 | Error boundary catches and renders nothing | S |
|
|
117
|
+
| 5.39 | Hydration mismatch — SSR output ≠ first client render | R |
|
|
118
|
+
| 5.40 | List key collision (`key={index}`) → possible local state or focus mismatch when a list reorders | S |
|
|
119
|
+
| 5.41 | Effect dependency bug → infinite render, or never runs | S |
|
|
120
|
+
| 5.42 | Conditional hook order violation | S |
|
|
121
|
+
| 5.43 | Portal/modal escapes its theme or stacking context | R |
|
|
122
|
+
| 5.44 | Virtualised list renders zero rows at certain heights | R |
|
|
123
|
+
| 5.45 | Layout shift on data arrival (no reserved space) | R |
|
|
124
|
+
| 5.46 | `undefined` rendered as text † (this one reached a live API in prod) | R |
|
|
125
|
+
| 5.47 | `NaN` rendered | R |
|
|
126
|
+
| 5.48 | `[object Object]` rendered | R |
|
|
127
|
+
| 5.49 | `Invalid Date` rendered | R |
|
|
128
|
+
| 5.50 | The string `"null"` rendered | R |
|
|
129
|
+
| 5.51 | Unresolved template variable † | R |
|
|
130
|
+
| 5.52 | Timezone — the date shifts by a day | H |
|
|
131
|
+
| 5.53 | Currency — cents/dollars confusion, float rounding | H |
|
|
132
|
+
| 5.54 | Locale/number formatting inconsistent between views | H |
|
|
133
|
+
| 5.55 | Unescaped HTML or raw markdown shown | S |
|
|
134
|
+
| 5.56 | Truncation with no ellipsis or title | R |
|
|
135
|
+
| 5.57 | Enum rendered raw (`in_progress`, not "In progress") | R |
|
|
136
|
+
| 5.58 | Precision loss on large IDs (JS number vs int64) | H |
|
|
137
|
+
| 5.59 | 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 | R |
|
|
138
|
+
|
|
139
|
+
## L6 — Interaction and control flow
|
|
140
|
+
|
|
141
|
+
| # | Class | Det |
|
|
142
|
+
|---|---|---|
|
|
143
|
+
| 6.59 | **Double submit** — no pending guard → duplicate records | S |
|
|
144
|
+
| 6.60 | Navigation during a pending mutation → lost write | G |
|
|
145
|
+
| 6.61 | Back button does not restore list state, scroll, or filters | G |
|
|
146
|
+
| 6.62 | Refresh on a deep link loses required state | G |
|
|
147
|
+
| 6.63 | Unsaved-changes guard missing | S |
|
|
148
|
+
| 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 |
|
|
149
|
+
| 6.65 | Focus lost after an action → keyboard user stranded | R |
|
|
150
|
+
| 6.66 | Client validation passes; server rejects with an unhandled error shape | G |
|
|
151
|
+
| 6.67 | Disabled state not conveyed — looks clickable, does nothing | R |
|
|
152
|
+
| 6.68 | Async action with no feedback → the user clicks again | S |
|
|
153
|
+
| 6.69 | A control the surface should render never appeared, waited for rather than raced (`interact.never-appeared`) | R |
|
|
154
|
+
| 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 |
|
|
155
|
+
| 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 |
|
|
156
|
+
| 6.72 | Clicking a control throws — the handler errors where a user would click (`interact.click-error`) | R |
|
|
157
|
+
|
|
158
|
+
## L7 — Access, identity, client security
|
|
159
|
+
|
|
160
|
+
| # | Class | Det |
|
|
161
|
+
|---|---|---|
|
|
162
|
+
| 7.69 | Role-gated route reachable by deep link | G |
|
|
163
|
+
| 7.70 | UI hides an action the API allows — or shows one it denies | G |
|
|
164
|
+
| 7.71 | Tenant scope missing from a query → cross-tenant leak | G |
|
|
165
|
+
| 7.72 | Session expiry → redirect loop, or silent empty data | R |
|
|
166
|
+
| 7.73 | Stale permissions cached after a role change | G |
|
|
167
|
+
| 7.74 | XSS sink — unsanitised HTML injection | S |
|
|
168
|
+
| 7.75 | Prototype pollution — merging untrusted payload into an object | S |
|
|
169
|
+
| 7.76 | DOM clobbering — a named element shadowing a global | S |
|
|
170
|
+
| 7.77 | CSP violation blocking a real resource | R |
|
|
171
|
+
| 7.78 | `SameSite=None` cookie without `Secure` — the browser rejects it; auth silently absent (`security.cookie-samesite-none-insecure`) | R |
|
|
172
|
+
|
|
173
|
+
## L8 — Responsive and layout
|
|
174
|
+
|
|
175
|
+
Tap target moved to L9 in v2 — it is an accessibility class (touch reachability), not a layout one.
|
|
176
|
+
|
|
177
|
+
| # | Class | Det |
|
|
178
|
+
|---|---|---|
|
|
179
|
+
| 8.79 | Horizontal scroll at 320/390 | R |
|
|
180
|
+
| 8.80 | Overlap or clipping at a breakpoint | R |
|
|
181
|
+
| 8.82 | Long content (400-char name) breaks the layout | R |
|
|
182
|
+
| 8.83 | Broken image with no fallback dimension | R |
|
|
183
|
+
| 8.84 | Dark-mode override missed † | R |
|
|
184
|
+
| 8.85 | Font swap shift | R |
|
|
185
|
+
| 8.86 | Fixed height clips content at large text size or zoom | R |
|
|
186
|
+
|
|
187
|
+
## L9 — Accessibility
|
|
188
|
+
|
|
189
|
+
| # | Class | Det |
|
|
190
|
+
|---|---|---|
|
|
191
|
+
| 9.87 | Contrast below threshold | R |
|
|
192
|
+
| 9.88 | Control with no accessible name | R |
|
|
193
|
+
| 9.89 | Keyboard trap, or unreachable control | R |
|
|
194
|
+
| 9.90 | Focus order does not follow visual order | R |
|
|
195
|
+
| 9.91 | No visible focus ring | R |
|
|
196
|
+
| 9.92 | Async result with no live region | S |
|
|
197
|
+
| 9.93 | Tap target < 24px — **excluding sr-only elements** † (moved from L8 in v2: reachability by touch is an accessibility property) | R |
|
|
198
|
+
| 9.94 | 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 |
|
|
199
|
+
|
|
200
|
+
## L10 — Performance
|
|
201
|
+
|
|
202
|
+
| # | Class | Det |
|
|
203
|
+
|---|---|---|
|
|
204
|
+
| 10.93 | Long task > 200ms — input blocked for that window (INP) | R |
|
|
205
|
+
| 10.94 | CLS > 0.1 — content moves under the cursor | R |
|
|
206
|
+
| 10.95 | LCP > 2500ms | R |
|
|
207
|
+
| 10.96 | Layout thrash — geometry read after DOM write, in a loop | R |
|
|
208
|
+
| 10.97 | Memory leak — heap retained after GC across navigation | R |
|
|
209
|
+
| 10.98 | Unstable context value → re-render storm, memoisation dead | S |
|
|
210
|
+
| 10.99 | Unbounded list render | S |
|
|
211
|
+
| 10.100 | Bundle size regression | S |
|
|
212
|
+
| 10.101 | Preloaded resource never used — Chrome's own diagnostic, exact-matched (`perf.unused-preload`) | R |
|
|
213
|
+
|
|
214
|
+
## L11 — Verification integrity
|
|
215
|
+
|
|
216
|
+
**The highest-leverage layer and the one nobody writes down.** Every class here
|
|
217
|
+
makes the layers above report green while broken.
|
|
218
|
+
|
|
219
|
+
| # | Class | Det |
|
|
220
|
+
|---|---|---|
|
|
221
|
+
| 11.101 | Capture fires before render → the gate measures nothing † *"worse than no gate — it consumes the verification slot without occupying it"* | H |
|
|
222
|
+
| 11.102 | Baseline captured from the buggy build → locks the bug in as correct | H |
|
|
223
|
+
| 11.103 | An assertion that cannot fail (empty selector, truthy on undefined) | H |
|
|
224
|
+
| 11.104 | The check ran against the wrong host or build † | R |
|
|
225
|
+
| 11.105 | Test skipped or filtered, reported green | H |
|
|
226
|
+
| 11.106 | Claim inflation — report says 103, reality is 1 † | H |
|
|
227
|
+
| 11.107 | **Verified at the wrong layer — API green, UI broken** † | G |
|
|
228
|
+
| 11.108 | Fixture regenerated from actual output → the test becomes a screenshot of the bug † | H |
|
|
229
|
+
| 11.109 | Gate returns "inconclusive" and is read as pass | H |
|
|
230
|
+
| 11.110 | Route absent from the sweep because the route list is hardcoded | S |
|
|
231
|
+
| 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 |
|
|
232
|
+
| 11.112 | The `--mutate` replay could not complete, so nothing is claimed about the write either way (`mutate.failed`) | R |
|
|
233
|
+
| 11.113 | 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 | R |
|
|
234
|
+
| 11.114 | 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 | S |
|
|
235
|
+
| 11.115 | 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 | S |
|
|
236
|
+
| 11.116 | Unmeasured promoted to pass — a route, cell, or class the run never reached is folded into the passing count instead of `report.unreached` | S |
|
|
237
|
+
|
|
238
|
+
11.107 is the mechanism behind "Claude said it was built": the API was checked,
|
|
239
|
+
it answered correctly, and the UI had no password field because the env var was
|
|
240
|
+
inlined at build time. Verification at one layer says nothing about the layer
|
|
241
|
+
above it.
|
|
242
|
+
|
|
243
|
+
---
|
|
244
|
+
|
|
245
|
+
## The advanced concept list, sorted by what you can do with it
|
|
246
|
+
|
|
247
|
+
Concepts split three ways. Only the middle column is testable — but the first
|
|
248
|
+
column is what tells you **where to place the probe**, so it is not wasted.
|
|
249
|
+
|
|
250
|
+
### Mechanisms — explain why a class exists; not themselves testable
|
|
251
|
+
|
|
252
|
+
Reconciliation · Fiber · virtual DOM diffing · structural sharing · referential
|
|
253
|
+
equality · immutable data patterns · event loop (macro/microtasks) · scheduler
|
|
254
|
+
priorities · time slicing · concurrent rendering · critical rendering path ·
|
|
255
|
+
paint vs composite vs layout · browser compositing layers · GPU acceleration ·
|
|
256
|
+
subpixel rendering · tree shaking internals · garbage collection timing ·
|
|
257
|
+
accessibility tree · virtual DOM complexity · priority inversion
|
|
258
|
+
|
|
259
|
+
Read these to know *why* `key={index}` corrupts rows (reconciliation), why a new
|
|
260
|
+
object literal kills memoisation (referential equality), why a geometry read
|
|
261
|
+
after a write stalls (layout → paint → composite ordering).
|
|
262
|
+
|
|
263
|
+
### Observable — these ARE the runtime probes
|
|
264
|
+
|
|
265
|
+
| Concept | Becomes | Atlas |
|
|
266
|
+
|---|---|---|
|
|
267
|
+
| Hydration / selective hydration | React #418/#423/#425 in console; click before hydrate is dropped | 5.39 |
|
|
268
|
+
| Tearing in concurrent UI | external store read without `useSyncExternalStore` | 4.29 |
|
|
269
|
+
| Stale closure problem | effect with `[]` deps reading state | 4.28 |
|
|
270
|
+
| Memoisation pitfalls | Provider value is a fresh literal each render | 10.98 |
|
|
271
|
+
| Layout thrashing | geometry reads interleaved with writes in one frame | 10.96 |
|
|
272
|
+
| Long tasks API / FID / INP | longest task > 200ms | 10.93 |
|
|
273
|
+
| CLS / LCP | PerformanceObserver, buffered | 10.94-95 |
|
|
274
|
+
| Detached DOM nodes / leak detection | heap retained after GC across navigation | 10.97 |
|
|
275
|
+
| `ResizeObserver` loop limit | its own console error — free once console is a gate | 5.41 |
|
|
276
|
+
| `MutationObserver` cost | forced-reflow counter | 10.96 |
|
|
277
|
+
| Render waterfalls | dependent request chain ≥ 3 | 3.15 |
|
|
278
|
+
| `AbortController` | fetch in effect without a signal | 3.16 |
|
|
279
|
+
| Backpressure / streaming fetch | unconsumed reader → stalled render | 3.21 |
|
|
280
|
+
| Task starvation | long task + no yield | 10.93 |
|
|
281
|
+
| Service Worker lifecycle traps | stale app shell; buildId mismatch | 1.4 |
|
|
282
|
+
| Cache invalidation / SWR / ETag vs Cache-Control | response headers on the sweep | 1.2 |
|
|
283
|
+
| Code splitting / dynamic import chunking | `ChunkLoadError` | 1.5 |
|
|
284
|
+
| CORS preflight | failed OPTIONS | 3.20 |
|
|
285
|
+
| CSP / Trusted Types | `securitypolicyviolation` | 7.77 |
|
|
286
|
+
| SameSite cookie modes | cookie attributes after login | 7.78 |
|
|
287
|
+
| XSS / CSRF / DOM clobbering / prototype pollution | static sink detection | 7.74-76 |
|
|
288
|
+
| Race conditions in UI state | two fetches, slower wins | 3.14 |
|
|
289
|
+
| Optimistic UI rollback | mutate → force error → assert revert | 4.26 |
|
|
290
|
+
| Idempotent UI actions | double-submit guard | 6.59 |
|
|
291
|
+
| Deterministic rendering | render twice, diff | 11.103 |
|
|
292
|
+
| Preload / prefetch / preconnect, priority hints | resource timing on the sweep | 10.95 |
|
|
293
|
+
| IndexedDB | quota + upgrade failure paths | A |
|
|
294
|
+
| ARIA live regions | async result with no announcement | 9.92 |
|
|
295
|
+
| Pointer events | tap target + hit testing | 9.93 |
|
|
296
|
+
| Speculative prerendering | double-fired effects on prerender | A |
|
|
297
|
+
|
|
298
|
+
### Architecture — change which classes apply, not what you check
|
|
299
|
+
|
|
300
|
+
Islands · partial hydration · streaming SSR · server components · edge rendering
|
|
301
|
+
· micro-frontend orchestration · module federation · Shadow DOM · custom
|
|
302
|
+
elements · Web Components interop · Web Workers vs Service Workers ·
|
|
303
|
+
`SharedArrayBuffer` · transferable objects · `OffscreenCanvas` · WebAssembly ·
|
|
304
|
+
WebRTC · CRDTs · event sourcing · finite state modelling · offline conflict
|
|
305
|
+
resolution · CSS containment
|
|
306
|
+
|
|
307
|
+
Pick one and a whole column of classes switches on. Islands and partial
|
|
308
|
+
hydration make 5.39 and "click before hydrate" primary. Micro-frontends and
|
|
309
|
+
module federation make 1.3 (two hosts, different builds) a weekly event. CRDTs
|
|
310
|
+
and offline make 4.30-4.31 the dominant risk. **Configuration, not new code.**
|
|
311
|
+
|
|
312
|
+
---
|
|
313
|
+
|
|
314
|
+
## Evaluated and deliberately not implemented
|
|
315
|
+
|
|
316
|
+
Each of these was specced far enough to know why it must not ship yet. A rule
|
|
317
|
+
that fires on healthy apps costs more than the class it catches.
|
|
318
|
+
|
|
319
|
+
| Check | Why not |
|
|
320
|
+
|---|---|
|
|
321
|
+
| 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 |
|
|
322
|
+
| bundle-size-regression (10.100's runtime half) | needs a per-repo build-output path; no universal baseline location in monorepos |
|
|
323
|
+
| render-blocking-head-script | fires on deliberately synchronous analytics snippets — "verify this is intentional" is not a defect claim |
|
|
324
|
+
| 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 |
|
|
325
|
+
| indexeddb-open-error | too narrow — apps touch IndexedDB through libraries that already handle it |
|
|
326
|
+
| deterministic-rendering (render twice, diff) | the noisiest possible rule: timestamps, counters, carousels and A/B buckets are legitimately non-deterministic |
|
|
327
|
+
| CSRF-token-presence | token-auth and SameSite-cookie architectures are both healthy without one; the absence proves nothing |
|
|
328
|
+
|
|
329
|
+
## Adding a class
|
|
330
|
+
|
|
331
|
+
1. Reproduce it once, by hand.
|
|
332
|
+
2. Add the rule to `classify.mjs` (S) or `probe.js` (R).
|
|
333
|
+
3. Plant an instance in `references/fixture.html` or `selftest-static.sh`.
|
|
334
|
+
4. Confirm the selftest **fails without the fix and passes with it**.
|
|
335
|
+
5. Give it a number here.
|
|
336
|
+
|
|
337
|
+
Step 4 is the one people skip, and skipping it produces 11.103 — a rule that
|
|
338
|
+
occupies the verification slot while asserting nothing.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"schema_version":2,"personas":["owner","reader"],"beacon_paths":["/api/telemetry"],"flows":[{"name":"example","route":"/things","declared_endpoint":"/api/things","cleanup_required":true,"production":false},{"name":"example-crosscheck","route":"/things/{fixtureId}","journey":"open-thing","declared_endpoint":"/api/things/{fixtureId}","cleanup_required":true,"production":false,"crosscheck":{"entity":"thing","writer":"owner","observers":[{"persona":"owner","expect":"visible"},{"persona":"reader","expect":"redacted"}],"authoritative_readback":"thingByFixtureId","targets":[{"id":"things-list","route":"/things","role":"collection","transition":"client-navigation","representation":{"selector":"[data-qa-thing-title]","field":"title","type":"string"}}]}}]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"schema_version":1,"defaults":{"tenant_scope":1,"data_sensitivity":1,"sla_weight":1,"regulatory_weight":0},"policy_note":"Values are repository policy inputs; the agent never infers revenue or compliance status."}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"schema_version":1,"allow_routes":["/dashboard"],"allow_endpoints":["/api/example"],"retention_days":7,"tenant_partition":"fixture","replay":{"isolated":true,"production":false}}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://frontend-verify.dev/schemas/agent-result.schema.json",
|
|
4
|
+
"title": "agent-result.json",
|
|
5
|
+
"description": "lib/qa/agent-result.mjs — the schema-validated AgentResult a compatible qa-agent binary returns, embedded verbatim under run.json's `agent` key and archived as its own canonical file so the manifest can bind its digest without hashing run.json.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": ["schemaVersion", "protocolVersion", "status", "runId", "mode", "lane", "binary", "inputs", "dimensions", "limitations"],
|
|
8
|
+
"additionalProperties": true,
|
|
9
|
+
"properties": {
|
|
10
|
+
"schemaVersion": { "const": 1 },
|
|
11
|
+
"protocolVersion": { "const": 1 },
|
|
12
|
+
"status": { "type": "string", "enum": ["measured", "unmeasured", "invalid"] },
|
|
13
|
+
"runId": { "type": "string", "minLength": 1 },
|
|
14
|
+
"mode": { "type": "string", "enum": ["static", "runtime"] },
|
|
15
|
+
"lane": { "type": "string", "enum": ["source", "public-runtime", "mock-contract", "real-integration", "mfa-integration"] },
|
|
16
|
+
"binary": {
|
|
17
|
+
"type": "object",
|
|
18
|
+
"required": ["sha256", "protocolVersion"],
|
|
19
|
+
"additionalProperties": true,
|
|
20
|
+
"properties": {
|
|
21
|
+
"sha256": { "type": "string", "pattern": "^[0-9a-f]{64}$" },
|
|
22
|
+
"protocolVersion": { "type": "integer", "minimum": 1 }
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"inputs": { "type": "array", "items": { "$ref": "#/$defs/input" } },
|
|
26
|
+
"dimensions": {
|
|
27
|
+
"type": "object",
|
|
28
|
+
"additionalProperties": { "$ref": "#/$defs/dimension" },
|
|
29
|
+
"properties": {
|
|
30
|
+
"routePriority": { "$ref": "#/$defs/routePriorityDimension" },
|
|
31
|
+
"coherenceObligations": { "$ref": "#/$defs/coherenceObligationsDimension" }
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"limitations": { "type": "array", "items": { "type": "string" } }
|
|
35
|
+
},
|
|
36
|
+
"$defs": {
|
|
37
|
+
"input": {
|
|
38
|
+
"type": "object",
|
|
39
|
+
"required": ["name", "sha256", "bytes"],
|
|
40
|
+
"additionalProperties": true,
|
|
41
|
+
"properties": {
|
|
42
|
+
"name": { "type": "string", "minLength": 1 },
|
|
43
|
+
"sha256": { "type": "string", "pattern": "^[0-9a-f]{64}$" },
|
|
44
|
+
"bytes": { "type": "integer", "minimum": 0 }
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
"typedReason": {
|
|
48
|
+
"type": "string",
|
|
49
|
+
"enum": [
|
|
50
|
+
"no-sweep-input", "no-binary", "incompatible-protocol", "unsupported-platform",
|
|
51
|
+
"not-requested", "malformed-output", "schema-invalid", "digest-mismatch",
|
|
52
|
+
"load-failed", "graph-integrity-failed", "no-coherence-input", "legacy-coherence-input"
|
|
53
|
+
]
|
|
54
|
+
},
|
|
55
|
+
"dimension": {
|
|
56
|
+
"oneOf": [
|
|
57
|
+
{ "type": "object", "required": ["status", "value"], "additionalProperties": true, "properties": { "status": { "const": "measured" } } },
|
|
58
|
+
{ "type": "object", "required": ["status", "reason"], "additionalProperties": true, "properties": { "status": { "const": "unmeasured" }, "reason": { "$ref": "#/$defs/typedReason" } } },
|
|
59
|
+
{ "type": "object", "required": ["status", "reason"], "additionalProperties": true, "properties": { "status": { "const": "invalid" }, "reason": { "$ref": "#/$defs/typedReason" } } }
|
|
60
|
+
]
|
|
61
|
+
},
|
|
62
|
+
"collection": {
|
|
63
|
+
"type": "object",
|
|
64
|
+
"required": ["items", "returned", "total", "truncated"],
|
|
65
|
+
"additionalProperties": true,
|
|
66
|
+
"properties": {
|
|
67
|
+
"items": { "type": "array" },
|
|
68
|
+
"returned": { "type": "integer", "minimum": 0 },
|
|
69
|
+
"total": { "type": "integer", "minimum": 0 },
|
|
70
|
+
"truncated": { "type": "boolean" }
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
"routePriorityItem": {
|
|
74
|
+
"type": "object",
|
|
75
|
+
"required": ["route", "confidence", "expectedInformation"],
|
|
76
|
+
"additionalProperties": true,
|
|
77
|
+
"properties": {
|
|
78
|
+
"route": { "type": "string" },
|
|
79
|
+
"confidence": { "type": "number", "minimum": 0, "maximum": 1 },
|
|
80
|
+
"expectedInformation": { "type": "number", "minimum": 0, "maximum": 1 }
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
"routePriorityDimension": {
|
|
84
|
+
"oneOf": [
|
|
85
|
+
{
|
|
86
|
+
"type": "object", "required": ["status", "value"], "additionalProperties": true,
|
|
87
|
+
"properties": {
|
|
88
|
+
"status": { "const": "measured" },
|
|
89
|
+
"value": {
|
|
90
|
+
"type": "object",
|
|
91
|
+
"required": ["items", "returned", "total", "truncated"],
|
|
92
|
+
"additionalProperties": true,
|
|
93
|
+
"properties": {
|
|
94
|
+
"items": { "type": "array", "items": { "$ref": "#/$defs/routePriorityItem" } },
|
|
95
|
+
"returned": { "type": "integer", "minimum": 0 },
|
|
96
|
+
"total": { "type": "integer", "minimum": 0 },
|
|
97
|
+
"truncated": { "type": "boolean" }
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
{ "type": "object", "required": ["status", "reason"], "additionalProperties": true, "properties": { "status": { "const": "unmeasured" }, "reason": { "$ref": "#/$defs/typedReason" } } },
|
|
103
|
+
{ "type": "object", "required": ["status", "reason"], "additionalProperties": true, "properties": { "status": { "const": "invalid" }, "reason": { "$ref": "#/$defs/typedReason" } } }
|
|
104
|
+
]
|
|
105
|
+
},
|
|
106
|
+
"coherenceObligationsDimension": {
|
|
107
|
+
"oneOf": [
|
|
108
|
+
{
|
|
109
|
+
"type": "object", "required": ["status", "value"], "additionalProperties": false,
|
|
110
|
+
"properties": {
|
|
111
|
+
"status": { "const": "measured" },
|
|
112
|
+
"value": {
|
|
113
|
+
"type": "object",
|
|
114
|
+
"required": ["schemaVersion", "coverage", "universe", "invariants", "families", "unattributed", "obligationIds", "operationIds", "sideChannelIds", "cacheKeys", "reads", "mutations", "surfaces", "operations", "sideChannels", "obligations", "perRoute", "plan"],
|
|
115
|
+
"additionalProperties": false,
|
|
116
|
+
"properties": {
|
|
117
|
+
"schemaVersion": { "const": 2 }, "coverage": { "type": "object" }, "universe": { "type": "object" },
|
|
118
|
+
"invariants": { "type": "array" }, "families": { "type": "array" }, "unattributed": { "type": "object" },
|
|
119
|
+
"obligationIds": { "type": "array", "items": { "type": "string", "pattern": "^[a-f0-9]{64}$" } },
|
|
120
|
+
"operationIds": { "type": "array", "items": { "type": "string", "pattern": "^[a-f0-9]{64}$" } },
|
|
121
|
+
"sideChannelIds": { "type": "array", "items": { "type": "string", "pattern": "^[a-f0-9]{64}:sha256:[a-f0-9]{64}$" } },
|
|
122
|
+
"cacheKeys": { "type": "array" }, "reads": { "type": "array" }, "mutations": { "type": "array" }, "surfaces": { "type": "array" },
|
|
123
|
+
"operations": { "type": "array" }, "sideChannels": { "type": "array" },
|
|
124
|
+
"obligations": { "type": "array" },
|
|
125
|
+
"perRoute": { "type": "array" }, "plan": { "type": "object" }
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
{ "type": "object", "required": ["status", "reason"], "additionalProperties": true, "properties": { "status": { "const": "unmeasured" }, "reason": { "$ref": "#/$defs/typedReason" } } },
|
|
131
|
+
{ "type": "object", "required": ["status", "reason"], "additionalProperties": true, "properties": { "status": { "const": "invalid" }, "reason": { "$ref": "#/$defs/typedReason" } } }
|
|
132
|
+
]
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://frontend-verify.dev/schemas/analysis.schema.json",
|
|
4
|
+
"title": "analysis.json",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"required": ["schemaVersion", "source", "evidence", "features", "context", "graphs", "risks", "stories", "mutationContracts", "proof", "fixQueue", "beforeAfter", "coverage"],
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"properties": {
|
|
9
|
+
"schemaVersion": {"const": 1},
|
|
10
|
+
"source": {"type": "object", "required": ["inventoryIdentity", "sourceDigest", "researchRunId"], "additionalProperties": false, "properties": {"inventoryIdentity": {"type": ["string", "null"]}, "sourceDigest": {"type": ["string", "null"]}, "researchRunId": {"type": ["string", "null"]}}},
|
|
11
|
+
"evidence": {"type": "array", "items": {"type": "object", "required": ["id", "kind", "fact", "sources", "uses", "confidence"], "additionalProperties": false, "properties": {"id": {"type": "string"}, "kind": {"type": "string"}, "fact": {"type": "string"}, "sources": {"type": "array", "items": {"type": "object", "required": ["file", "verified"], "additionalProperties": false, "properties": {"file": {"type": "string"}, "verified": {"type": "boolean"}}}}, "uses": {"type": "array", "items": {"type": "string"}}, "confidence": {"enum": ["high", "medium", "low"]}}}},
|
|
12
|
+
"features": {"type": "array", "items": {"type": "object", "required": ["id", "route", "owner", "personas", "requirements", "expectedBehavior", "evidence", "surfaces", "producers", "consumers", "consistencyRules", "rbac", "coverage"], "additionalProperties": false, "properties": {"id": {"type": "string"}, "route": {"type": "string"}, "owner": {"type": "string"}, "personas": {"type": "array", "items": {"type": "string"}}, "requirements": {"type": "array", "items": {"type": "object", "required": ["id", "status", "text"], "additionalProperties": false, "properties": {"id": {"type": "string"}, "status": {"type": "string"}, "text": {"type": "string"}}}}, "expectedBehavior": {"type": "string"}, "evidence": {"type": "array", "items": {"type": "string"}}, "surfaces": {"type": "array", "items": {"type": "string"}}, "producers": {"type": "array", "items": {"type": "object", "required": ["action", "source"], "additionalProperties": false, "properties": {"action": {"type": "string"}, "source": {"type": "string"}}}}, "consumers": {"type": "array", "items": {"type": "string"}}, "consistencyRules": {"type": "array", "items": {"type": "string"}}, "rbac": {"type": "object", "required": ["personas", "status"], "additionalProperties": false, "properties": {"personas": {"type": "array", "items": {"type": "string"}}, "status": {"type": "string"}}}, "coverage": {"type": "object", "required": ["verifiedAppearances", "inferredConnections", "unmeasuredBehavior"], "additionalProperties": false, "properties": {"verifiedAppearances": {"type": "array", "items": {"type": "string"}}, "inferredConnections": {"type": "array", "items": {"type": "string"}}, "unmeasuredBehavior": {"type": "array", "items": {"type": "string"}}}}}}}, "context": {"type": "object"}, "graphs": {"type": "object", "required": ["mermaid", "ascii"], "additionalProperties": false, "properties": {"mermaid": {"type": "string"}, "ascii": {"type": "string"}}}, "risks": {"type": "array"}, "stories": {"type": "array"}, "mutationContracts": {"type": "array"}, "proof": {"type": "array"}, "fixQueue": {"type": "array"}, "beforeAfter": {"type": "object"},
|
|
13
|
+
"coverage": {"type": "object", "required": ["scope", "declared", "executable", "attempted", "passed", "failed", "unmeasured", "invalid", "exclusions", "rule"], "additionalProperties": false, "properties": {"scope": {"type": "string"}, "declared": {"type": "integer", "minimum": 0}, "executable": {"type": "integer", "minimum": 0}, "attempted": {"type": "integer", "minimum": 0}, "passed": {"type": "integer", "minimum": 0}, "failed": {"type": "integer", "minimum": 0}, "unmeasured": {"type": "integer", "minimum": 0}, "invalid": {"type": "integer", "minimum": 0}, "exclusions": {"type": "integer", "minimum": 0}, "rule": {"type": "string"}}}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://frontend-verify.dev/schemas/classify.schema.json",
|
|
4
|
+
"title": "classify.json",
|
|
5
|
+
"description": "bin/classify.mjs — static per-file defect classifier.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": ["root", "files", "counts", "byRule", "findings", "waived", "cache"],
|
|
8
|
+
"additionalProperties": true,
|
|
9
|
+
"properties": {
|
|
10
|
+
"root": { "type": "string" },
|
|
11
|
+
"atlasVersion": { "type": "integer", "enum": [1, 2] },
|
|
12
|
+
"files": { "type": "integer", "minimum": 0 },
|
|
13
|
+
"cache": { "$ref": "#/$defs/cache" },
|
|
14
|
+
"counts": {
|
|
15
|
+
"type": "object",
|
|
16
|
+
"required": ["total", "waived"],
|
|
17
|
+
"additionalProperties": { "type": "integer", "minimum": 0 },
|
|
18
|
+
"properties": {
|
|
19
|
+
"total": { "type": "integer", "minimum": 0 },
|
|
20
|
+
"waived": { "type": "integer", "minimum": 0 },
|
|
21
|
+
"P0": { "type": "integer", "minimum": 0 },
|
|
22
|
+
"P1": { "type": "integer", "minimum": 0 },
|
|
23
|
+
"P2": { "type": "integer", "minimum": 0 },
|
|
24
|
+
"P3": { "type": "integer", "minimum": 0 }
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"byRule": { "type": "object", "additionalProperties": { "type": "integer", "minimum": 0 } },
|
|
28
|
+
"findings": { "type": "array", "items": { "$ref": "#/$defs/finding" } },
|
|
29
|
+
"waived": { "type": "array", "items": { "$ref": "#/$defs/finding" } }
|
|
30
|
+
},
|
|
31
|
+
"$defs": {
|
|
32
|
+
"cache": {
|
|
33
|
+
"type": "object",
|
|
34
|
+
"description": "the content-identity cache lib/qa/source-snapshot.mjs computes -- see CLAUDE.md 'Outputs are byte-stable'.",
|
|
35
|
+
"required": ["hit", "identity", "schemaVersion", "sourceDigest", "computedAt"],
|
|
36
|
+
"additionalProperties": true,
|
|
37
|
+
"properties": {
|
|
38
|
+
"hit": { "type": "boolean" },
|
|
39
|
+
"identity": { "type": ["string", "null"] },
|
|
40
|
+
"schemaVersion": { "type": "integer", "minimum": 1 },
|
|
41
|
+
"sourceDigest": { "type": "string" },
|
|
42
|
+
"computedAt": { "type": "string" },
|
|
43
|
+
"reusedAt": { "type": "string" }
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
"finding": {
|
|
47
|
+
"type": "object",
|
|
48
|
+
"required": ["rule", "severity", "file", "line", "detail", "atlas"],
|
|
49
|
+
"additionalProperties": true,
|
|
50
|
+
"properties": {
|
|
51
|
+
"rule": { "type": "string", "minLength": 1 },
|
|
52
|
+
"severity": { "type": "string", "enum": ["P0", "P1", "P2", "P3"] },
|
|
53
|
+
"file": { "type": "string" },
|
|
54
|
+
"line": { "type": "integer", "minimum": 1 },
|
|
55
|
+
"detail": { "type": "string" },
|
|
56
|
+
"atlas": { "type": ["string", "null"] },
|
|
57
|
+
"waived": { "type": "boolean" }
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|