feat(03-03): Task 2 — driveA31 + orchestrator wiring (A31 password-filter PARTIAL)

- Append driveA31 to tests/uat/lib/harness-page-driver.ts after driveA30:
  - Reuses UserEvent type (Plan 03-02 import already present).
  - 3-phase pattern: page.evaluate → findLatestZip → JSZip
    logs/events.json parse + filter-pipeline grep for sentinel absence
    + control-sentinel presence.
  - 3 host-side checks: A31.2 (eventsContainingSentinel.length === 0),
    A31.3 (eventsTargetingPassword.length === 0), A31.4
    (eventsContainingControl.length >= 1; defense-in-depth proves
    the listener is alive so A31.2/A31.3 absences mean the filter
    fired rather than a tautological "no events at all" pass).
  - Standard guard checks A31.0 (zip present) + A31.0a (events.json
    entry exists) + A31.0b (JSON.parse success) gate before A31.2..A31.4
    per Plan 02-04 / Plan 03-01 / Plan 03-02 driveA26/A29/A30 precedent.
  - Filter-pipeline form preserved (no `continue`) per CLAUDE.md
    Control Flow §.
- Wire orchestrator in tests/uat/harness.test.ts:
  - Add `driveA31,` to import block after `driveA30,`.
  - Add `driveA31Wrapped` const after `driveA30Wrapped`.
  - Add `{ name: 'A31', drive: driveA31Wrapped }` entry to drivers
    array after the A30 entry with explanatory banner comment
    citing the cs-injection-world precedent + the defense-in-depth
    A31.4 control check.
  - Append `, A31` to the orchestrator banner string.

Acceptance grep gates (post-commit):
- grep -c 'driveA31' tests/uat/lib/harness-page-driver.ts returns 2
- grep -c 'driveA31' tests/uat/harness.test.ts returns 6
- grep -c 'secret-do-not-log-123' tests/uat/lib/harness-page-driver.ts returns 1
- tsc --noEmit exit 0

A29 flake disclosure (per Plan 03-02 SUMMARY "Issues Encountered"):
- During Plan 03-03 empirical verification of A31, the pre-existing
  A29 flakiness documented in 03-02-SUMMARY.md surfaced: A29 chains
  off incidental zip-mtime ordering against prior assertions' zips,
  so when A29's own (empty chrome-extension:// SAVE) zip mtime ties
  with a prior real-content zip, findLatestZip non-deterministically
  returns the prior zip with rrweb events from iana.org/example.com.
- 3 base runs (HEAD=de398347, no Plan 03-03 changes): 2/3 PASS,
  1/3 FAIL — confirms PRE-EXISTING flake, NOT a Plan 03-03 regression.
- Per CLAUDE.md SCOPE BOUNDARY ("Only auto-fix issues DIRECTLY caused
  by the current task's changes") + Plan 03-02 SUMMARY's explicit
  recommendation ("Plan 03-05's VERIFICATION.md aggregator + a
  Phase 4 hardening pass can pick it up"): A29 flake is OUT OF SCOPE
  for Plan 03-03. Documented in SUMMARY as deferred item.
This commit is contained in:
2026-05-20 20:36:00 +02:00
parent 8db629f2fb
commit 34b36fb58b
2 changed files with 190 additions and 1 deletions

View File

@@ -101,6 +101,8 @@ import {
driveA29,
// Plan 03-02 — event-log verification (SPEC §10 #5 / REQ-user-event-log)
driveA30,
// Plan 03-03 — password-filter PARTIAL (SPEC §10 #8 PARTIAL per D-P3-02)
driveA31,
getManifestVersion,
} from './lib/harness-page-driver';
import {
@@ -269,7 +271,7 @@ async function assertA0_GrepGate(): Promise<{
*/
async function main(): Promise<number> {
process.stdout.write('\nMokosh Plan 01-13 + 01-14 + 02-04 — UAT harness orchestrator\n');
process.stdout.write('Architecture: A0 pre-flight + extension-internal page driver (A1..A14, A15..A17, A18..A22, A23, A24, A25, A26, A27, A28, A29, A30)\n');
process.stdout.write('Architecture: A0 pre-flight + extension-internal page driver (A1..A14, A15..A17, A18..A22, A23, A24, A25, A26, A27, A28, A29, A30, A31)\n');
process.stdout.write('='.repeat(72) + '\n');
// A0 pre-flight (no Chrome launch needed; runs against built dist/).
@@ -345,6 +347,12 @@ async function main(): Promise<number> {
// of logs/events.json from the just-produced zip.
const driveA30Wrapped: (page: import('puppeteer').Page) => Promise<AssertionRecord> =
(page) => driveA30(page, handles.downloadsDir);
// Plan 03-03 — driveA31 needs downloadsDir for host-side JSZip
// negative-assertion against logs/events.json (sentinel absence +
// password-selector-target absence) + control-sentinel presence
// (defense-in-depth A31.4).
const driveA31Wrapped: (page: import('puppeteer').Page) => Promise<AssertionRecord> =
(page) => driveA31(page, handles.downloadsDir);
const drivers: ReadonlyArray<{
readonly name: string;
@@ -454,6 +462,19 @@ async function main(): Promise<number> {
// driveA30 JSZip-parses logs/events.json and asserts presence of
// each of the 5 UserEvent.type literal values.
{ name: 'A30', drive: driveA30Wrapped },
// Plan 03-03 A31: password-filter PARTIAL (SPEC §10 #8 PARTIAL per
// D-P3-02). Negative-assertion: opens a fresh https://example.com
// probe tab (Plan 03-02 cs-injection-world precedent), injects a
// synthetic <input type="password"> + a control <input type="text">
// via chrome.scripting.executeScript ISOLATED-world, types the
// sentinels, settles, SAVEs while the probe tab is active, finally-
// cleanup. Host-side driveA31 inspects logs/events.json and asserts
// sentinel value absence + password-selector-target absence (proves
// src/content/index.ts:82 filter fired) + control-sentinel presence
// (defense-in-depth: proves the listener is alive so A31.2/A31.3
// mean the filter actually fired rather than the trivial "no
// events at all" tautology).
{ name: 'A31', drive: driveA31Wrapped },
];
const buffers = { swConsole: handles.swConsole, offConsole: handles.offConsole };