# Test plan: aite Produced with the qa-manager skill (`.claude/skills/qa-manager`), 2026-10-09. Advisory input to the plan; see `adversarial-review.md` for the independent critique. Inputs read: `docs/PLAN.md` (overview), `docs/TASKS.md` (33 tasks with acceptance criteria), `docs/decisions.md`, `docs/journeys.md`, `docs/design/{script-format,protocol,data-formats,locator-repair}.md`. These together are the "plan with a task list". Inputs not available: no `research.md` (the analysis of Google's BITE archive is inside PLAN.md section 2, and was done by the same author); no code (nothing is built yet, so no existing tests or CI to inspect); no CI history or git history of bugs; Chrome behaviors cited in the plan (MV3 service worker keep-alive, `captureVisibleTab`, Playwright loading extensions) were not tested here. Assumptions I made: - [ASSUMED] Consumers of the output are Alex and Qian only. If aite's reports are shown to others, the report (UIF) risk rises to High. - [ASSUMED] Irreversible or high-consequence actions are: a public post on Bluesky from a test account, a GitLab issue created automatically, a git history rewrite (D8), and any test that moves testnet funds. Testnet funds are low value; the posts and issues are public/visible. - [ASSUMED] Review time available is about two hours per phase gate. If less, drop the Medium manual call-outs first (M-04, M-06). - [ASSUMED] A false pass (the tool says green when the app is broken) costs far more than a false fail. The tiering is built on that. - [ASSUMED] Chrome on `balrog` auto-updates. If it is pinned, the Chrome-drift risk (section 0) drops. Tier summary: **8 High / 19 Medium / 6 Low** (33 tasks) as first written; **after the review: 16 High / 26 Medium / 8 Low (50 tasks), see section 6.2.** | Tier | Tasks | |---|---| | High | P1-2, P2-2, P2-3, P3-2, P4-3, P6-2, P7-2, P7-3 | | Medium | P0-1, P0-5, P1-1, P1-3, P2-1, P2-4, P3-1, P3-3, P4-1, P4-4, P5-1, P5-2, P5-3, P6-1, P6-3, P6-4, P6-5, P7-1, P6-8 | | Low | P0-2, P0-3, P0-4, P1-4, P4-2, P5-4 | --- ## 0. Cross-cutting risks and what this plan cannot detect **The central risk of a test tool is the false pass.** A tool that reports green when the app is broken is worse than no tool, because people stop looking. Most High-tier tasks are High for this reason (P2-2, P2-3, P3-2, P7-2). Every High task therefore gets a seeded-failure check. **Classes with no layer able to detect them here** - **REQ (requirement misread):** the claims "a human can tell in under a minute whether the app or the script is wrong" and "the report is clearer than a written test log" are only checkable by an independent reader (M-01, M-04). Unit and functional tests cannot reach them. If the cold readers are Alex or Qian after they have seen the design, the check is weak; use someone who has not (see question Q2). - **SCH (schedule/scope):** each phase has an estimate ("2 days", "3 to 4 days") but the plan states no capacity, and the largest unknown (MV3 behavior, P1-2) is in Phase 1. Only REV can catch this; the adversarial review lens 2 covers it. - **UIF (the report and the confirm page):** judgment only (M-01, M-04). - **NDT (real services):** Bluesky OAuth and posting, Monad testnet RPC, GitLab availability, Chrome auto-updates. Tests can mock them; only the first real runs (M-02, M-03) and scheduled runs (P7-3) show their behavior. The plan has no check that detects a Chrome version change breaking the extension other than a failing run; I recommend recording the Chrome version in `run.json` (already in `data-formats.md`) and alerting when it changes (added as a case in P7-3). **Where tests and code share an author or reading of the plan** - The same agent will write the parser, the parser's tests, the fixture pages, and the scripts that test mallbucks. A misread of `script-format.md` (for example what `verifyNot` means, or whether `type` appends or replaces) would be in both code and test. Mitigations: known-answer vectors written from the spec's own examples by a human-readable table (P2-1), the seeded-failure checks, and the fixture pages being plain HTML a person can read. - The expected values in the mallbucks scripts come from the QA plan and logs written by the same agent family; M-04 (Qian) is the independent check. **Treated as done with no verification step** - P5-1 "Works after a reboot of balrog (documented)": documented does not mean tested; add a reboot test or drop the claim. - P0-2 "README links both": no check; trivial, but the LICENSE text itself is not verified against the original (add a checksum comparison). - PLAN.md section 4.2 thresholds (0.85, 0.60, 0.15) are starting values with no data; P3-3 tests the mechanism, not the thresholds. Real-data tuning is an unowned task (new task T-N1 proposed in section 5). - "Every real script starts with it (`needs session NAME`)" (P5-3) has no enforcement: nothing fails a script that omits it (new case in P5-3). **Dependencies that could not be verified** - That an open websocket with a message every 15 s keeps an MV3 service worker alive in the Chrome version on balrog (P1-2 tests it; I did not). - That `captureVisibleTab` works when the test tab is not the focused window of a headed profile (the pay pop-up case, P6-3). - That Playwright can load the unpacked MV3 extension for the CI self-check (P2-4). It requires a persistent context and the new headless mode; if not, the self-check job cannot run and the plan has no fallback (new case in P2-4). - That Git LFS is enabled on this GitLab (P0-1). - That balrog can run a headed Chrome (display or Xvfb) (P5-1). --- ## 1. Risk matrix (class x layer), by task group Convention: only cells that are plausible per the reach table in `risk-and-layers.md` are listed; each listed cell has exactly one state. Layers not listed for a class are outside that layer's reach. `PLAN(id)` links to a case in section 2. `U` = `UNREACHABLE`. ### Group P0 (housekeeping: P0-1 to P0-5) | Class | Cells | |---|---| | LOG | STA=PLAN(P0-4.a) UNI=NOT WORTH IT(no logic yet; Low tier) | | CON | STA=COVERED(P0-4) INT=PLAN(P0-5.a) | | REQ | REV=PLAN(P0-1.a) MAN=NOT WORTH IT(decisions are Alex's, recorded in writing) | | ENV | STA=PLAN(P0-4.b) INT=PLAN(P0-5.a, P0-5.b) E2E=COVERED(P0-5) | | DAT | INT=PLAN(P0-1.b) (clone size and history) | | UIF | N/A(no UI) | | CNC | N/A(no concurrency) | | PRF | N/A(clone size is measured in P0-1.b) | | SEC | REV=PLAN(P0-2.a) (license/NOTICE completeness) | | NDT | N/A(deterministic) | | SCH | REV=COVERED(adversarial review) | ### Group P1 (walking skeleton: P1-1 to P1-4) | Class | Cells | |---|---| | LOG | UNI=PLAN(P1-2.c) PRP=PLAN(P1-2.d) FUN=PLAN(P1-3.a) | | CON | INT=PLAN(P1-2.a, P1-2.b) FUN=PLAN(P1-3.b) E2E=PLAN(P1-3.a) | | REQ | REV=COVERED(adversarial review lens 3) MAN=UNREACHABLE(that an extension is the right driver can only be judged by building P1 and by the review; see D2) | | ENV | INT=PLAN(P1-1.a) E2E=PLAN(P1-2.e) (idle soak on the real Chrome) PRD=NOT WORTH IT(no production yet) | | DAT | N/A(no persistent data) | | UIF | MAN=PLAN(P1-1.b) (badge reads correctly) | | CNC | PRP=PLAN(P1-2.d) INT=PLAN(P1-2.b) E2E=PLAN(P1-2.e) | | PRF | E2E=PLAN(P1-4.a) (screenshot size/time) | | SEC | PRP=PLAN(P1-2.f) REV=COVERED(adversarial review lens 5) | | NDT | E2E=PLAN(P1-2.e) (Chrome worker lifetime is nondeterministic; measure over N=5 idle runs) | | SCH | REV=COVERED(adversarial review lens 2) | ### Group P2 (script language: P2-1 to P2-4) | Class | Cells | |---|---| | LOG | STA=PLAN(P2-1.a) UNI=PLAN(P2-1.b, P2-2.a) PRP=PLAN(P2-1.c) FUN=PLAN(P2-3.a to P2-3.h) | | CON | INT=PLAN(P2-3.i) FUN=PLAN(P2-3.a) E2E=PLAN(P2-4.a) | | REQ | REV=PLAN(P2-1.d) MAN=UNREACHABLE(by tests: whether `type` and `verifyNot` mean what Alex intends needs his confirmation, Q1) | | ENV | STA=COVERED(P0-4) E2E=PLAN(P2-4.b) | | DAT | PRP=PLAN(P2-1.c) (round-trip: parse then print then parse) | | UIF | N/A(no UI; the error messages are text and are covered by P2-1.e) | | CNC | PRP=PLAN(P2-2.c) INT=PLAN(P2-3.j) (pop-up and slow-element fixtures) | | PRF | NOT WORTH IT(Low impact; timeouts are the bound) | | SEC | PRP=PLAN(P2-1.c) (hostile script text), REV=PLAN(P2-3.k) (`call` runs code: only from `scripts/checks/`) | | NDT | E2E=PLAN(P2-4.c) (flaky fixture timing, 20 repeats) | | SCH | REV=COVERED(adversarial review) | ### Group P3 (descriptors and repair: P3-1 to P3-3) | Class | Cells | |---|---| | LOG | UNI=PLAN(P3-2.a to P3-2.f) PRP=PLAN(P3-2.g) | | CON | INT=PLAN(P3-1.a) FUN=PLAN(P3-1.b) | | REQ | REV=PLAN(P3-2.h) (are the thresholds the intended behavior?), MAN=PLAN(M-04 judges repaired steps in real reports) | | ENV | N/A(pure logic plus a page) | | DAT | PRP=PLAN(P3-1.c) (lock file stable, no churn) | | UIF | N/A | | CNC | N/A | | PRF | NOT WORTH IT(scoring over visible elements; cap candidates at 2000 and test it, P3-2.i) | | SEC | N/A(no new trust boundary) | | NDT | N/A(deterministic given a page) | | SCH | REV=COVERED(adversarial review) | ### Group P4 (reporting and verdicts: P4-1 to P4-4) | Class | Cells | |---|---| | LOG | UNI=PLAN(P4-1.a, P4-3.d) FUN=PLAN(P4-3.a to P4-3.c) | | CON | INT=PLAN(P4-3.e) (GitLab API shape) | | REQ | MAN=PLAN(M-01) REV=COVERED(adversarial review lens 1) | | ENV | INT=PLAN(P4-3.f) (token only from env, never logged) | | DAT | INT=PLAN(P4-1.b) (run.json schema validation) | | UIF | MAN=PLAN(M-01) | | CNC | INT=PLAN(P4-3.g) (double click on a verdict creates one issue) | | PRF | NOT WORTH IT(small files) | | SEC | PRP=PLAN(P4-1.c) (HTML escaping of page text in the report) REV=PLAN(P4-3.h) MAN=PLAN(M-06) | | NDT | N/A | | SCH | REV=COVERED | ### Group P5 (real session and accounts: P5-1 to P5-4) | Class | Cells | |---|---| | LOG | UNI=PLAN(P5-3.a) | | CON | E2E=PLAN(P5-3.b) | | REQ | MAN=PLAN(M-05) | | ENV | E2E=PLAN(P5-1.a, P5-1.b) PRD=PLAN(P5-3.c) (session-check as a guard) | | DAT | N/A | | UIF | N/A | | CNC | NOT WORTH IT(one run at a time; enforced by a lock file, P5-1.c) | | PRF | N/A | | SEC | MAN=PLAN(M-05) REV=PLAN(P5-2.a) STA=PLAN(P5-2.b) (no secrets in git: scan) | | NDT | PRD=PLAN(P5-3.c) (sessions expire) MAN=PLAN(M-02) | | SCH | REV=COVERED | ### Group P6 (mallbucks journeys: P6-1 to P6-5) | Class | Cells | |---|---| | LOG | N/A(logic is in the tool, covered earlier) | | CON | E2E=PLAN(P6-1.a, P6-2.a, P6-3.a) | | REQ | MAN=PLAN(M-04) REV=PLAN(P6-1.b) (expected texts come from the spec, not the page) | | ENV | E2E=PLAN(P6-3.b) (pop-up window) PRD=COVERED(P5-3) | | DAT | E2E=PLAN(P6-3.c) (downloaded file name and size) | | UIF | MAN=PLAN(M-04) | | CNC | E2E=PLAN(P6-3.b) | | PRF | NOT WORTH IT | | SEC | MAN=PLAN(M-03) (public post) | | NDT | E2E=PLAN(P6-4.a) (negative runs prove it can fail) PRD=PLAN(P7-3) | | SCH | REV=COVERED | ### Group P7 (authoring and scheduling: P7-1 to P6-8) | Class | Cells | |---|---| | LOG | UNI=PLAN(P7-1.a, P6-8.a) FUN=PLAN(P6-8.b) | | CON | INT=PLAN(P7-2.a) | | REQ | MAN=PLAN(M-07) REV=PLAN(P7-2.b) | | ENV | PRD=PLAN(P7-3.a to P7-3.d) | | DAT | INT=PLAN(P7-3.c) (one issue per script) | | UIF | N/A | | CNC | PRP=PLAN(P7-3.e) (overlapping scheduled runs) | | PRF | PRD=PLAN(P7-3.f) (run time budget) | | SEC | REV=PLAN(P7-2.c) MAN=PLAN(M-03) | | NDT | PRP=PLAN(P7-2.d) (LLM authoring: assert on the script's validity and on gaming, not on text) | | SCH | REV=COVERED | --- ## 2. Per-task plans Case IDs are `.`. "Cost" is run time or attention; "flake" is the expected instability. ### T-P0-1 Decide the large-file question Tier: M (why: impact low, but a history rewrite breaks existing clones and is hard to undo) Intent: "Decide: Git LFS ..., rewrite history to remove them ..., or leave them. Measure a fresh clone size before and after." Classes: REQ, DAT. Layers: REV, INT. Not chosen: unit/E2E (no code). | ID | Layer | Case | Oracle | Falsifiable by | Cost / flake | |---|---|---|---|---|---| | P0-1.a | REV | The decision row D8 states the choice, the date, and the consequence for existing clones | PLAN.md D8 text | row left "open" | 2 min | | P0-1.b | INT | `git clone` of the repo into a fresh dir; `du -sh` recorded before and after the change | measured sizes | size unchanged after "reduce" | 1 min | Requirement-misread guard: Alex decides; nothing else. Exit: D8 closed in `decisions.md`; both sizes recorded; if history was rewritten, both clones replaced. ### T-P0-2 LICENSE and NOTICE Tier: L (why: reversible, no runtime effect) Cases: P0-2.a REV, compare `LICENSE` byte-for-byte with BITE's `COPYING` (a checksum), oracle = the original file; falsified by an edited license. Exit: files present, checksum equal, NOTICE names BITE. ### T-P0-3 Curate reference/ Tier: L (why: copies of files; no runtime effect) Cases: P0-3.a STA, a script lists `reference/` files and checks size under 1 MB and that each has an Apache header and a source-path comment; oracle = the task text. Exit: check passes. Not chosen: anything else. ### T-P0-4 TypeScript skeleton Tier: L (why: standard tooling, reversible) | ID | Layer | Case | Oracle | Falsifiable by | Cost | |---|---|---|---|---|---| | P0-4.a | STA | `tsc --noEmit` on a file with a deliberate type error exits non-zero | exit code | a config that ignores errors | seconds | | P0-4.b | ENV | `npm ci && npm test && npm run build` on a clean checkout (new clone) | exit codes | a missing lockfile or global dependency | 1 min | Exit: both pass on a fresh clone. ### T-P0-5 CI Tier: M (why: a CI that cannot fail gives false confidence; the stores repo is the precedent) | ID | Layer | Case | Oracle | Falsifiable by | Cost / flake | |---|---|---|---|---|---| | P0-5.a | INT | Pipeline runs on push and is green on the skeleton | GitLab pipeline status | job skipped by `rules` | 2 min | | P0-5.b | INT | Push a commit with a failing test: pipeline red; revert: green | GitLab pipeline status | `allow_failure: true` or tests not invoked | 4 min | Seeded-failure: P0-5.b is the seeded failure for the whole project's tests. Exit: both cases observed and recorded in the issue. ### T-P1-1 MV3 extension shell Tier: M (why: Chrome's loader is strict; failure here blocks the project but is cheap to see) | ID | Layer | Case | Oracle | Falsifiable by | Cost | |---|---|---|---|---|---| | P1-1.a | INT | Load unpacked in `chrome://extensions` on balrog and on a laptop: zero errors; manifest has no permissions beyond the listed ones | the page's error list and a manifest permission allowlist test | an extra `` permission | 5 min, manual-ish | | P1-1.b | MAN | Badge shows disconnected then connected when a bridge is started and stopped | a person looking | badge stuck | 2 min | Exit: loads on two machines; permission allowlist test passes in CI. ### T-P1-2 Bridge, handshake, keep-alive, reconnect Tier: H (why: impact = the architecture; likelihood high, MV3 worker lifetime is subtle; cheap to learn early but expensive if found late) Intent: "This is the biggest technical risk, so prove it before more is built." Classes: CON, CNC, ENV, NDT, SEC, LOG. Layers chosen: INT, UNI, PRP, E2E. Not chosen: MAN (automatable). | ID | Layer | Case (input -> expected) | Oracle | Falsifiable by | Cost / flake | |---|---|---|---|---|---| | P1-2.a | INT | Wrong token -> connection closed with code, extension shows error | protocol.md "closes any connection with a wrong token" | a bridge that accepts any token | seconds | | P1-2.b | INT | Kill the bridge, restart it -> extension reconnects within 10 s and sends `hello` with `resume` | protocol.md backoff list; timer | no reconnect, or reconnect after 60 s | 20 s, low flake | | P1-2.c | UNI | `act` request without a response after reconnect -> step fails `connection-lost`; request never re-sent (spy counts sends = 1) | protocol.md delivery rules | a bridge that re-sends the click | seconds | | P1-2.d | PRP | Random interleavings of ping, request, disconnect: no request gets two responses, none is lost without `connection-lost` | invariant: exactly one response or one `connection-lost` per id | duplicate or silent loss | 1 min | | P1-2.e | E2E | Real Chrome, idle connection 5 minutes, then a command -> still connected, no reconnect logged. Repeat 5 times. | the extension's own reconnect counter = 0 | worker stopped (counter > 0) | 30 min total, medium flake (Chrome timing) | | P1-2.f | PRP | Messages with oversize, malformed JSON, unknown `cmd`, a web page posting to the extension: ignored or rejected, no crash | protocol.md security section | an exception that stops the worker | 1 min | Seeded-failure: P1-2.e fails if `ping` is disabled and an alarm removed (prove it, then restore). P1-2.b fails if backoff is removed. Requirement-misread guard: the Chrome keep-alive claim is second-hand; P1-2.e is the independent check. UNREACHABLE for a different Chrome version than the one tested: record the Chrome version with the result. MANUAL QA: none triggered (automatable). Exit: a, b, c, d, f pass in CI; e passes 5 of 5 on balrog's Chrome; Chrome version recorded. ### T-P1-3 click and verify text, end to end Tier: M (why: first end-to-end path; wrong here is cheap to find) | ID | Layer | Case | Oracle | Falsifiable by | Cost | |---|---|---|---|---|---| | P1-3.a | E2E | `hello.aite` clicks the button, verifies "Hello, world" | fixture HTML (human readable) | a click that does nothing but verify reads old text | 10 s | | P1-3.b | FUN | Wrong expected text -> failure `mismatch` with the found text | script-format.md verify rules | a verify that passes on a substring | 10 s | Exit: a and b green; running a broken fixture proves b's failure path. ### T-P1-4 Screenshot over the bridge Tier: L (why: additive, reversible) Cases: P1-4.a E2E, run produces a non-empty PNG that decodes and has the fixture's expected dimensions; falsified by a blank/black image (assert a pixel count of non-background colors above a threshold). Exit: PNG exists and decodes. ### T-P2-1 Parser Tier: M (why: pure logic; a misread spec is shared-author risk) | ID | Layer | Case | Oracle | Falsifiable by | Cost | |---|---|---|---|---|---| | P2-1.a | STA | Strict types on the AST | tsc | `any` creeping in | free | | P2-1.b | UNI | 30+ table cases: every valid example in script-format.md parses to the documented tree | the spec's own examples (quoted) | a parser that accepts the example differently | seconds | | P2-1.c | PRP | Random generated scripts: parse -> print -> parse is identical; random garbage never throws, only returns E-codes | round trip and totality | an uncaught exception | 1 min | | P2-1.d | REV | A person other than the author reads the spec's `verifyNot` and `type` semantics and says what they expect | their statement vs the table | a different expectation | 10 min (Q1) | | P2-1.e | UNI | Each of E001 to E005 appears with the exact message shape and the right line number | the table in script-format.md | wrong line (off by one) | seconds | Exit: all green; P2-1.d answered. ### T-P2-2 Step engine and run.json writer Tier: H (why: it decides pass/fail; a false pass here silently invalidates every later result) Intent: "A failed command marks the rest of its test skipped"; "Retries are counted". Classes: LOG, CNC, REQ. Layers: UNI, PRP, E2E. | ID | Layer | Case | Oracle | Falsifiable by | Cost | |---|---|---|---|---|---| | P2-2.a | UNI | Command fails -> later steps `skipped`, test `failed`; first command passes after one retry -> step `passed-after-retry`, retries=1 | the TASKS.md acceptance text | a step marked `passed` after a retry | seconds | | P2-2.b | UNI | A step with zero commands, or a test with zero `verify` commands -> warning "no assertion" in run.json | principle 5 (never fake a pass) | a green test that checks nothing | seconds | | P2-2.c | PRP | Random sequences of command outcomes: the test status is `passed` iff every command passed (possibly after retry) | invariant | any other combination | 1 min | | P2-2.d | UNI | `${var}` used before set -> E007 before any command runs | script-format.md E007 | the run starts then fails halfway | seconds | | P2-2.f | FUN | `needs session` naming a profile missing from the config -> E006 with the exact shape and line, no command runs | script-format.md E006 | the run starts, or wrong line | second | | P2-2.e | UNI | `sleep` produces a warning; the summary counts it | script-format.md timing rules | silent sleep | seconds | Seeded-failure: break the engine to mark a failed command `passed` (one line); P2-2.a and c must fail. Record that they did. Requirement-misread guard: Q1 (what counts as a pass when a test has no verify). Exit: green plus the seeded failure shown to fail. ### T-P2-3 Remaining commands in the extension Tier: H (why: `verify`/`wait` semantics and clicking the wrong element are the false-pass/false-act sources) Classes: LOG, CON, CNC, SEC. Layers: FUN against fixtures, INT, PRP. | ID | Layer | Case (fixture page) | Oracle | Falsifiable by | Cost / flake | |---|---|---|---|---|---| | P2-3.a | FUN | `click` on a hidden / disabled / off-screen button -> `not-visible` / `not-enabled`, nothing clicked | script-format.md click rules | click fires anyway | seconds | | P2-3.b | FUN | `type` replaces (not appends) and fires `input` and `change` (page counter increments) | spec table; page JS counter | no events | seconds | | P2-3.c | FUN | `verify text` passes only on equal text (trimmed, case-sensitive); "1 MALLBUCKS" does not match "11 MALLBUCKS" | spec | substring match | seconds | | P2-3.d | FUN | `verify` times out -> `timeout`, never passes by default; `verifyNot` passes only if the condition is absent for the whole timeout (an element that appears at 80 percent of the timeout fails it) | spec timing rules | pass on first miss | 10 s | | P2-3.e | FUN | Two matching elements without `index` -> `ambiguous` with candidates | spec targets | picks the first | seconds | | P2-3.f | FUN | `wait ... gone`, `text-changes`, `url`, `network-idle` each pass and each time out correctly | spec wait forms | immediate pass | 30 s | | P2-3.g | FUN | `select`, `check`/`uncheck`, `press`, `drag`, `doubleClick` perform the documented effect on the fixture | fixture state | no effect | 30 s | | P2-3.h | FUN | `capture` then `verify number:changed-by from:NAME equals:-1` on a counter that goes 5 -> 4 passes and 5 -> 5 fails | spec | pass when unchanged | seconds | | P2-3.i | INT | Every failure `reason` in protocol.md is producible by some fixture; none is `internal` in normal use | protocol.md | an unmapped error | 1 min | | P2-3.j | INT | `switchTab`/`closeTab` with a pop-up fixture; commands after `closeTab` act on the previous tab | spec | acts on the closed tab | 20 s, medium flake | | P2-3.k | REV | `call` loads only `scripts/checks/NAME.ts`; a path with `..` or an absolute path is refused | spec; unit test | path traversal | seconds | Seeded-failure: for each of c, d, h, break the implementation in the obvious wrong way (substring match; pass on first miss; skip the delta check) and show the case fails. Requirement-misread guard: Q1. Exit: all green; the three seeded failures demonstrated. ### T-P2-4 Fixture pages and CI self-check Tier: M (why: this is the net under everything; it must itself be able to fail) | ID | Layer | Case | Oracle | Falsifiable by | Cost / flake | |---|---|---|---|---|---| | P2-4.a | E2E | Self-check job loads the extension (Playwright persistent context) and runs every fixture script -> all pass | job status | extension not actually loaded (assert the service worker URL is present) | 3 min | | P2-4.b | ENV | Deliberately break the extension (remove a handler) -> job fails | job status | job still green | 3 min | | P2-4.c | NDT | Run each fixture script 20 times -> pass rate 100 percent; any flake is a bug in the tool | counted results | one flaky pass/fail | 5 min | | P2-4.d | INT | If Playwright cannot load the extension on the runner, the job fails loudly (not skipped) and the plan's fallback is a manual run, recorded | job log | silent skip | n/a | Exit: a to c green; d decided. ### T-P3-1 Descriptor capture and lock file Tier: M | ID | Layer | Case | Oracle | Falsifiable by | |---|---|---|---|---| | P3-1.a | INT | First run creates the lock file with the schema in data-formats.md; a JSON schema validates it | schema | missing field | | P3-1.b | FUN | Second run reads it; a script without a lock file still runs | task text | crash | | P3-1.c | PRP | Two runs on an unchanged page produce a byte-identical lock file | invariant | churn (timestamps, ordering) | Exit: green. ### T-P3-2 Resolution order and repair scoring Tier: H (why: a wrong repair makes a script click the wrong thing and pass; this is the silent-false-pass risk in its purest form) Classes: LOG, REQ, NDT. Layers: UNI, PRP, REV, MAN. | ID | Layer | Case | Oracle | Falsifiable by | Cost | |---|---|---|---|---|---| | P3-2.a | UNI | Exact target found -> `found`, no repair | locator-repair.md | scoring runs anyway | s | | P3-2.b | UNI | Text changed from "Send" to "Send now", same role/testid -> `repaired`, score at least 0.85, runner-up at least 0.15 behind | the scoring table | outcome `found` | s | | P3-2.c | UNI | Button replaced by an unrelated element at the same position -> not repaired (position alone cannot reach 0.85) | locator-repair.md "own words dominate" | repaired | s | | P3-2.d | UNI | Score between 0.60 and 0.85 -> `drifted`, step status `failed` | outcomes table | a pass | s | | P3-2.e | UNI | Two candidates within 0.15 -> `ambiguous` | outcomes table | picks one | s | | P3-2.f | UNI | Hidden or disabled candidates are never chosen for actions | rules | a click on a hidden node | s | | P3-2.g | PRP | Random mutations of a page (rename, reorder, wrap, restyle): a repaired element is the intended one in at least the cases a human judges equivalent; any ambiguity or low score fails | property over a labelled corpus | repairs a wrong element | 2 min | | P3-2.h | REV | A person reviews 10 real repair decisions on real mallbucks pages and marks each right/wrong; any wrong repair at or above 0.85 means the thresholds are wrong | human judgment (this is the threshold check P3-3 does not do) | one wrong repair | 20 min | | P3-2.i | UNI | Page with 5000 elements: candidates capped at 2000, time under 1 s, result `drifted` or `not-found` with a note | locator-repair.md note (to add) | hang | s | Seeded-failure: lower the repair threshold to 0.3 and show P3-2.c and d fail. Requirement-misread guard: P3-2.h. Exit: green; P3-2.h done on real data (after P5/P6 give real pages, so this exit is deferred to Phase 6; note it on the issue). ### T-P3-3 drift.html fixture Tier: M Cases: P3-3.a small rename -> `repaired` shown in the summary; P3-3.b large change -> `drifted`, candidate outlined; P3-3.c both are logged with score. Oracle = locator-repair.md; falsified by a pass on the large change. Exit: green. Note: this tests the mechanism, not the thresholds (see P3-2.h). ### T-P4-1 run.json and report.html Tier: M | ID | Layer | Case | Oracle | Falsifiable by | |---|---|---|---|---| | P4-1.a | UNI | Report generated from a fixture run.json contains the intent, wanted, found, screenshot for a failed step, failed steps first | data-formats.md | missing field | | P4-1.b | INT | run.json validates against a JSON schema; unknown fields rejected | schema | drift between writer and report | | P4-1.c | PRP | Page text containing `