docs(04): create phase plan — 7 plans for Phase 4 hardening (audit P1 polish + flake stabilization + SW persistence + visual polish + closure)
Wave structure: - W1 (parallel): 04-01 (Audit P1 polish #11/#14/#15 TDD) + 04-02 (build/CSP hygiene: setimmediate polyfill + dead-code + generate-icons.cjs) - W2: 04-03 (A29 cs-injection-world rewrite; closes flake) - W3: 04-04 (A33 SW state persistence; spike-first + CDP worker.close()) - W4: 04-05 (A34 fetch+XHR network_error; ROADMAP SC #2 + validates Plan 04-01 P1 #11 end-to-end) - W5: 04-06 (dark-logo currentColor + cursor verification + 01-07-SUMMARY back-patch; operator empirical) - W6: 04-07 (04-VERIFICATION.md aggregator + ROADMAP backfill + v1 close prep) Honors locked decisions D-P4-01..05 (full Phase 4 + all 3 P1 polish + both visual items + alpha-independent + ROADMAP backfill). Implements RESEARCH Q1 (setimmediate option a), Q2 (spike-first SW persistence), Q3 (A29 cs-injection-world), Finding 4 (cursor already shipped — verification only). UI-SPEC dark-logo currentColor strategy with inline-SVG injection landed per UI-SPEC §"Implementation amendment". Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
328
.planning/phases/04-harden-clean-up-optional/04-05-PLAN.md
Normal file
328
.planning/phases/04-harden-clean-up-optional/04-05-PLAN.md
Normal file
@@ -0,0 +1,328 @@
|
||||
---
|
||||
phase: 04
|
||||
slug: harden-clean-up-optional
|
||||
plan: 05
|
||||
type: execute
|
||||
wave: 4
|
||||
depends_on:
|
||||
- 01
|
||||
- 02
|
||||
- 03
|
||||
- 04
|
||||
files_modified:
|
||||
- tests/uat/extension-page-harness.ts
|
||||
- tests/uat/lib/harness-page-driver.ts
|
||||
- tests/uat/harness.test.ts
|
||||
autonomous: true
|
||||
requirements: []
|
||||
tags:
|
||||
- uat-harness
|
||||
- a34
|
||||
- fetch-network-error
|
||||
- xhr-network-error
|
||||
- roadmap-sc-2
|
||||
- cs-injection-world
|
||||
- charter-d-p4-01
|
||||
user_setup: []
|
||||
must_haves:
|
||||
truths:
|
||||
- "A34 harness assertion fires synthetic fetch(404-url) + XMLHttpRequest(404-url) from a probe tab (via chrome.scripting.executeScript ISOLATED) — covering BOTH paths required by ROADMAP SC #2"
|
||||
- "Host-side driveA34 JSZip-parses the archive and asserts >=2 network_error entries in logs/events.json (one for fetch, one for XHR), each with `meta.status >= 400`"
|
||||
- "fetch network_error entry's `target` field carries the actual URL (validating Plan 04-01 P1 #11 fix: Request-narrow + String narrowing works empirically end-to-end at the SAVE -> archive layer)"
|
||||
- "UAT harness count 34 -> 35 GREEN with A34 added"
|
||||
artifacts:
|
||||
- path: "tests/uat/extension-page-harness.ts"
|
||||
provides: "assertA34 page-side stub (probe tab create + cs-injection-world fetch+XHR triggers + SAVE)"
|
||||
contains: "A34_404"
|
||||
- path: "tests/uat/lib/harness-page-driver.ts"
|
||||
provides: "driveA34 host-side: JSZip parse + filter logs/events.json for 2 network_error entries (fetch + XHR variants) + meta.status >= 400 assertion"
|
||||
contains: "network_error"
|
||||
- path: "tests/uat/harness.test.ts"
|
||||
provides: "driveA34 import + wrapped driver + drivers-array push with banner"
|
||||
contains: "driveA34Wrapped"
|
||||
key_links:
|
||||
- from: "tests/uat/extension-page-harness.ts assertA34"
|
||||
to: "cs-injection-world fetch + XHR triggers on probe tab"
|
||||
via: "chrome.scripting.executeScript ISOLATED with fetch('https://example.com/404-fetch-a34') + xhr.open('GET','/404-xhr-a34')"
|
||||
pattern: "A34_404"
|
||||
- from: "tests/uat/lib/harness-page-driver.ts driveA34"
|
||||
to: "JSZip parse logs/events.json + filter network_error entries"
|
||||
via: "userEvents.filter(e => e.type === 'network_error' && e.meta?.status >= 400)"
|
||||
pattern: "network_error"
|
||||
---
|
||||
|
||||
<objective>
|
||||
Ship A34 as the empirical harness extension for ROADMAP SC #2 ("A page that issues a failing `fetch` (response code >= 400) produces a `network_error` entry in `events.json`; a failing `XMLHttpRequest` does too").
|
||||
|
||||
Phase 3's A30 already exercises the fetch path via a 404-fetch from a probe tab; A34 EXTENDS this with:
|
||||
1. An empirical end-to-end test that the Plan 04-01 P1 #11 fetch URL extraction fix (Request-narrow) works in a REAL Chrome page context (not just the unit-test JSDOM environment).
|
||||
2. A complementary XMLHttpRequest 404 path that the existing A30 does NOT cover — XHR uses a different code path in src/content/index.ts (lines ~208-237) and merits its own empirical gate.
|
||||
|
||||
The fix uses the canonical cs-injection-world pattern (Plan 03-02 / Plan 04-03 A29 precedent):
|
||||
- Open https://example.com/ probe tab.
|
||||
- Wait 1.5s for content-script attach.
|
||||
- Wait 11s for first segment rotation.
|
||||
- chrome.scripting.executeScript ISOLATED to inject TWO triggers:
|
||||
- `fetch('https://example.com/404-fetch-a34-' + Date.now())` (uniqueness guard against caching).
|
||||
- `new XMLHttpRequest(); xhr.open('GET', 'https://example.com/404-xhr-a34-' + Date.now()); xhr.send();`
|
||||
- Wait ~1s for both responses to land + content script's network-error wrapper to enqueue both UserEvents.
|
||||
- SAVE_ARCHIVE.
|
||||
- Host-side: JSZip parse logs/events.json; filter for `network_error` entries with `meta.status >= 400`; assert >=2 entries (one per protocol).
|
||||
|
||||
Purpose: Closes ROADMAP SC #2 empirically. Validates both:
|
||||
- The Plan 04-01 P1 #11 fetch URL extraction fix works end-to-end through the production bundle (the fetch network_error's `target` field carries the actual URL, not `'[object Request]'`).
|
||||
- The XHR path also produces network_error entries (was implicit before; now empirically pinned).
|
||||
|
||||
Output: 1 NEW assertion (A34; harness count 34->35); 3-file lockstep update.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
|
||||
@$HOME/.claude/get-shit-done/templates/summary.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
@.planning/PROJECT.md
|
||||
@.planning/ROADMAP.md
|
||||
@.planning/STATE.md
|
||||
@.planning/phases/04-harden-clean-up-optional/04-CONTEXT.md
|
||||
@.planning/phases/04-harden-clean-up-optional/04-RESEARCH.md
|
||||
@.planning/phases/04-harden-clean-up-optional/04-PATTERNS.md
|
||||
|
||||
# Source files — locus of the harness extension
|
||||
@tests/uat/extension-page-harness.ts
|
||||
@tests/uat/lib/harness-page-driver.ts
|
||||
@tests/uat/harness.test.ts
|
||||
@src/content/index.ts
|
||||
|
||||
# Prior plan SUMMARYs — canonical cs-injection-world precedents
|
||||
@.planning/phases/03-spec-10-smoke-verification-dom-event-log-verification/03-02-SUMMARY.md
|
||||
@.planning/phases/04-harden-clean-up-optional/04-01-SUMMARY.md
|
||||
|
||||
<interfaces>
|
||||
<!-- Key shapes the executor consumes directly. Extracted from codebase 2026-05-21. -->
|
||||
|
||||
From src/content/index.ts:163-237 (production fetch + XHR wrappers — the SUT for A34):
|
||||
```typescript
|
||||
function setupNetworkLogging() {
|
||||
// Перехват fetch (lines 165-199 post Plan 04-01)
|
||||
const originalFetch = window.fetch;
|
||||
window.fetch = function(...args) {
|
||||
return originalFetch.apply(this, args)
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
addUserEvent({
|
||||
timestamp: Date.now(),
|
||||
type: 'network_error',
|
||||
target: (args[0] instanceof Request ? args[0].url : String(args[0])) || 'unknown',
|
||||
value: `HTTP ${response.status} ${response.statusText}`,
|
||||
url: window.location.href,
|
||||
meta: { status: response.status, statusText: response.statusText, url: response.url },
|
||||
});
|
||||
}
|
||||
return response;
|
||||
})
|
||||
.catch(error => { /* catch-branch — also Plan 04-01 fixed */ });
|
||||
};
|
||||
|
||||
// Перехват XMLHttpRequest (lines 201-237)
|
||||
// xhr.open captures _method + _url; xhr.send adds loadend listener that
|
||||
// emits network_error when xhr.status >= 400 OR xhr.status === 0.
|
||||
}
|
||||
```
|
||||
|
||||
Constants to define at top of assertA34:
|
||||
```typescript
|
||||
const A34_PROBE_TAB_URL = 'https://example.com/';
|
||||
const A34_TAB_NAVIGATION_WAIT_MS = 1_500;
|
||||
const A34_SEGMENT_SETTLE_MS = 11_000;
|
||||
const A34_NETWORK_SETTLE_MS = 1_000; // both fetch + xhr settle
|
||||
const A34_SAVE_ARCHIVE_TIMEOUT_MS = 15_000;
|
||||
const A34_404_FETCH_PATH = '/404-fetch-a34';
|
||||
const A34_404_XHR_PATH = '/404-xhr-a34';
|
||||
```
|
||||
|
||||
Injection function body (inside chrome.scripting.executeScript func: arg):
|
||||
```typescript
|
||||
func: (fetchPath: string, xhrPath: string) => {
|
||||
// Uniqueness guards against intermediate caching
|
||||
const stamp = Date.now();
|
||||
// Trigger 1: failing fetch (catch swallows the network rejection)
|
||||
fetch(`https://example.com${fetchPath}-${stamp}`).catch(() => { /* expected 404 */ });
|
||||
// Trigger 2: failing XHR
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', `https://example.com${xhrPath}-${stamp}`);
|
||||
xhr.send();
|
||||
},
|
||||
args: [A34_404_FETCH_PATH, A34_404_XHR_PATH],
|
||||
```
|
||||
|
||||
Host-side filter (driveA34):
|
||||
```typescript
|
||||
const events = JSON.parse(eventsRaw) as Array<UserEvent>;
|
||||
const networkErrors = events.filter(e => e.type === 'network_error');
|
||||
const fetchEntries = networkErrors.filter(e => typeof e.target === 'string' && e.target.includes('404-fetch-a34'));
|
||||
const xhrEntries = networkErrors.filter(e => typeof e.target === 'string' && e.target.includes('404-xhr-a34'));
|
||||
// A34.1: SAVE ack (page-side); A34.2: >=1 fetch entry; A34.3: >=1 XHR entry;
|
||||
// A34.4: fetch entry's meta.status === 404 (Plan 04-01 P1 #11 end-to-end);
|
||||
// A34.5: XHR entry's meta.status === 404
|
||||
```
|
||||
</interfaces>
|
||||
</context>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: Append assertA34 (page-side) using cs-injection-world fetch + XHR injection</name>
|
||||
<files>tests/uat/extension-page-harness.ts</files>
|
||||
<read_first>tests/uat/extension-page-harness.ts:3517-3700 (assertA30 — canonical skeleton; the post-Plan-04-03 assertA29 also uses the same skeleton), tests/uat/extension-page-harness.ts:3950-4000 (__mokoshHarness global registration block), src/content/index.ts:163-237 (production SUT)</read_first>
|
||||
<action>
|
||||
1. Read tests/uat/extension-page-harness.ts in the line range 3700-4050 once (covers assertA30+A31+A32 patterns + __mokoshHarness global registration + Window interface declaration). Do not re-read.
|
||||
2. Append `async function assertA34(): Promise<AssertionResult>` AFTER the existing assertA32 (or near the end of the assertA* block per file convention; verify placement). Use the cs-injection-world skeleton from assertA30/A29 verbatim with A34-specific substitutions:
|
||||
- Constants block per the `<interfaces>` block above.
|
||||
- Step 1: `await setupFreshRecording()` (existing helper).
|
||||
- Step 2: `const probeTab = await chrome.tabs.create({url: A34_PROBE_TAB_URL, active: true});`
|
||||
- Step 3: `await new Promise(r => setTimeout(r, A34_TAB_NAVIGATION_WAIT_MS));`
|
||||
- Step 4: `await new Promise(r => setTimeout(r, A34_SEGMENT_SETTLE_MS));`
|
||||
- Step 5: `await chrome.scripting.executeScript({target: {tabId: probeTab.id!}, world: 'ISOLATED', func: ..., args: [A34_404_FETCH_PATH, A34_404_XHR_PATH]});` per the injection function body above. The `fetch(404).catch(() => {})` is required — without the catch, the unhandled rejection might surface as a separate js_error UserEvent which A34 doesn't care about.
|
||||
- Step 6: `await new Promise(r => setTimeout(r, A34_NETWORK_SETTLE_MS));` — both fetch + xhr need to settle their then/catch + loadend chains AND the content-script's setupNetworkLogging wrapper needs to push 2 UserEvents.
|
||||
- Step 7: `const ack = await sendMessageWithTimeout({type: 'SAVE_ARCHIVE'}, A34_SAVE_ARCHIVE_TIMEOUT_MS, 'SAVE_ARCHIVE (A34)');`
|
||||
- Push `result.checks` for A34.1 (SAVE ack success).
|
||||
- try/finally: silent-ignore `chrome.tabs.remove(probeTab.id)` cleanup per T-02-04-04 precedent.
|
||||
- `result.passed = result.checks.every(c => c.passed);` and return.
|
||||
3. Add `assertA34: () => Promise<AssertionResult>;` to the `__mokoshHarness` Window interface declaration block (~line 3971).
|
||||
4. Add `assertA34` entry to the `window.__mokoshHarness = { ... };` object literal (mirror existing entries).
|
||||
|
||||
Filter-pipeline form (no continue). TypeScript-strict. Inline comment cites Plan 04-01 P1 #11 (the fetch URL extraction fix that A34 empirically validates).
|
||||
|
||||
Verify: `npx tsc --noEmit` exits 0. `npm run build:test` exits 0.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>npx tsc --noEmit 2>&1 | grep -c 'error TS'; npm run build:test 2>&1 | tail -5</automated>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- `npx tsc --noEmit` exits 0.
|
||||
- `npm run build:test` exits 0.
|
||||
- `grep -c 'assertA34' tests/uat/extension-page-harness.ts` returns >=3 (function def + Window interface entry + object literal entry).
|
||||
- `grep -c 'A34_404' tests/uat/extension-page-harness.ts` returns >=4 (constants + injection args).
|
||||
- `grep -nE "world: 'ISOLATED'" tests/uat/extension-page-harness.ts | grep -v '^#'` returns >=4 lines (A29 + A30 + A31 + A34 all explicit).
|
||||
- `grep -c 'new XMLHttpRequest()' tests/uat/extension-page-harness.ts` returns >=1 (the A34 XHR injection trigger).
|
||||
</acceptance_criteria>
|
||||
<done>assertA34 + Window interface + object literal entries appended. Commit: `feat(04-05): A34 page-side — cs-injection-world fetch + XHR 404 injection`.</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: Append driveA34 (host-side) + orchestrator wiring (3-file lockstep)</name>
|
||||
<files>tests/uat/lib/harness-page-driver.ts, tests/uat/harness.test.ts</files>
|
||||
<read_first>tests/uat/lib/harness-page-driver.ts:2039-2148 (driveA30 — canonical host-side filter pattern), tests/uat/harness.test.ts:100-110 (import block), tests/uat/harness.test.ts:340-360 (wrapped-driver block), tests/uat/harness.test.ts:459-490 (drivers-array push block)</read_first>
|
||||
<action>
|
||||
File 1: tests/uat/lib/harness-page-driver.ts
|
||||
- Append `export async function driveA34(page: Page, downloadsDir: string): Promise<AssertionRecord>` after driveA33 (Plan 04-04 placement).
|
||||
- Body shape:
|
||||
- Phase 1: page-side stub call: `const pageResult = await page.evaluate(() => (window as any).__mokoshHarness.assertA34() as AssertionRecord);`
|
||||
- Phase 2: `const zipPath = findLatestZip(downloadsDir);` + null-check guard pushing A34.0 fail check.
|
||||
- Phase 3: JSZip parse + read `logs/events.json` text, JSON.parse to Array<UserEvent>.
|
||||
- Filter pipeline:
|
||||
- networkErrors = events.filter(e => e.type === 'network_error')
|
||||
- fetchEntries = networkErrors.filter(e => typeof e.target === 'string' && e.target.includes('404-fetch-a34'))
|
||||
- xhrEntries = networkErrors.filter(e => typeof e.target === 'string' && e.target.includes('404-xhr-a34'))
|
||||
- Push A34.2: `passed: fetchEntries.length >= 1` (with descriptive name "fetch 404 produced network_error entry containing '404-fetch-a34' (Plan 04-01 P1 #11 end-to-end)")
|
||||
- Push A34.3: `passed: xhrEntries.length >= 1` (with descriptive name "XHR 404 produced network_error entry containing '404-xhr-a34'")
|
||||
- Push A34.4: `passed: fetchEntries[0]?.meta?.status === 404`
|
||||
- Push A34.5: `passed: xhrEntries[0]?.meta?.status === 404`
|
||||
- Aggregate: `mergedPassed = mergedChecks.every(c => c.passed);` and return.
|
||||
- Filter-pipeline form. TypeScript-strict (no any except the cast at page.evaluate result).
|
||||
|
||||
File 2: tests/uat/harness.test.ts
|
||||
- Import block (~line 101 after `driveA33,`): add `driveA34,` to the binding list.
|
||||
- Wrapped-driver block (~line 357 after `driveA33Wrapped`):
|
||||
```typescript
|
||||
// Plan 04-05 — driveA34 needs downloadsDir for host-side JSZip parse of logs/events.json
|
||||
const driveA34Wrapped: (page: import('puppeteer').Page) => Promise<AssertionRecord> =
|
||||
(page) => driveA34(page, handles.downloadsDir);
|
||||
```
|
||||
- Drivers-array push (~line 486 after the A33 entry):
|
||||
```typescript
|
||||
// Plan 04-05 A34: fetch + XHR network_error empirical (ROADMAP SC #2).
|
||||
// Verifies both protocol paths in src/content/index.ts setupNetworkLogging
|
||||
// produce events.json entries. Empirically validates Plan 04-01 P1 #11
|
||||
// fetch URL extraction fix at the SAVE->archive layer (A34.4 + A34.5).
|
||||
{ name: 'A34', drive: driveA34Wrapped },
|
||||
```
|
||||
|
||||
Verify gates:
|
||||
- `npx tsc --noEmit` exits 0.
|
||||
- `npm run build:test` exits 0.
|
||||
- Quick UAT: `HEADLESS=1 SKIP_PROD_REBUILD=0 SKIP_LONG_UAT=1 npm run test:uat` exits 0 with 35/35 GREEN (A33 SKIPPED placeholder; A34 actually runs ~25s).
|
||||
- Full UAT: `HEADLESS=1 SKIP_PROD_REBUILD=0 npm run test:uat` exits 0 with 35/35 GREEN (~7 min total).
|
||||
- FORBIDDEN_HOOK_STRINGS count unchanged at 12.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>npx tsc --noEmit && npm run build:test && HEADLESS=1 SKIP_PROD_REBUILD=1 SKIP_LONG_UAT=1 npm run test:uat 2>&1 | tail -10 | tee /tmp/04-05-task-2.log; grep -c '35/35' /tmp/04-05-task-2.log</automated>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- `npx tsc --noEmit` exits 0.
|
||||
- `npm run build:test` exits 0.
|
||||
- UAT harness count flips 34 -> 35.
|
||||
- Skip-mode (SKIP_LONG_UAT=1): 35/35 GREEN in ~95s (A33 skipped placeholder; A34 runs).
|
||||
- Full-mode: 35/35 GREEN in ~7 min (A33 + A34 both real).
|
||||
- `grep -c 'driveA34' tests/uat/harness.test.ts` returns >=3 (import + wrapped + push entry).
|
||||
- `grep -c '404-fetch-a34\\|404-xhr-a34' tests/uat/lib/harness-page-driver.ts` returns >=4 (2 sentinel literals + 2 includes() checks).
|
||||
- ROADMAP SC #2 GREEN — A34 produces 2 network_error entries with status === 404.
|
||||
- FORBIDDEN_HOOK_STRINGS count unchanged at 12.
|
||||
</acceptance_criteria>
|
||||
<done>driveA34 + orchestrator wiring landed; UAT 34 -> 35 GREEN. Atomic commit: `feat(04-05): A34 host-side + orchestrator — fetch+XHR network_error empirical (ROADMAP SC #2 GREEN)`.</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<threat_model>
|
||||
## Trust Boundaries
|
||||
|
||||
| Boundary | Description |
|
||||
|----------|-------------|
|
||||
| chrome.scripting.executeScript ISOLATED -> page realm | injected fetch + XHR run in the content-script ISOLATED world; the same realm as the content script's `window.fetch` + `XMLHttpRequest.prototype` wrappers, so the wrappers intercept the failing requests as designed |
|
||||
| network -> 404 response | example.com/404-* responds with HTTP 404 (the example.com domain is RFC 2606 reserved AND serves 404 for arbitrary unknown paths; both fetch and XHR see status=404 in their respective callbacks) |
|
||||
| Content script -> events.json (archive) | UserEvent buffer flushed at SAVE time via chrome.runtime.sendMessage; same path A30 uses; no new boundary |
|
||||
|
||||
## STRIDE Threat Register
|
||||
|
||||
| Threat ID | Category | Component | Disposition | Mitigation Plan |
|
||||
|-----------|----------|-----------|-------------|-----------------|
|
||||
| T-04-05-01 | Repudiation | a future regression to src/content/index.ts setupNetworkLogging would silently break ROADMAP SC #2 if A34 doesn't catch both paths | mitigate | A34's 4 checks (2 presence + 2 status-code) cover both protocols + the Plan 04-01 P1 #11 URL extraction in one assertion |
|
||||
| T-04-05-02 | Tampering | uniqueness stamps (`-${Date.now()}` suffix on probe URLs) defend against any future flake where iana.org or example.com caches a hit between consecutive runs | accept | The stamps are functionally unnecessary today (the paths don't exist; 404 is always fresh) but defend against future caching behavior changes |
|
||||
| T-04-05-03 | Information Disclosure | network_error UserEvent.target field carries the actual URL (post-Plan-04-01 fix); if the URL contains query-string secrets, those land in the archive | accept | Out of scope for v1 per CONTEXT charter; REQ-password-confidentiality deferred to v2; alpha distribution archives are operator-curated |
|
||||
</threat_model>
|
||||
|
||||
<verification>
|
||||
- `npx tsc --noEmit` exits 0.
|
||||
- `npm run build:test` exits 0.
|
||||
- UAT harness count 34 -> 35.
|
||||
- Skip-mode `SKIP_LONG_UAT=1`: 35/35 GREEN ~95s.
|
||||
- Full-mode: 35/35 GREEN ~7 min.
|
||||
- ROADMAP SC #2 GREEN — A34.2 fetch presence + A34.3 XHR presence + A34.4/A34.5 status===404.
|
||||
- Plan 04-01 P1 #11 end-to-end validation: A34.4 fetch entry target contains the actual URL (NOT '[object Request]' string).
|
||||
- FORBIDDEN_HOOK_STRINGS unchanged at 12.
|
||||
- vitest baseline preserved (>=181 GREEN from Plans 04-01 + 04-02).
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- assertA34 + driveA34 + orchestrator wiring landed (Tasks 1 + 2).
|
||||
- UAT harness 34 -> 35 GREEN.
|
||||
- ROADMAP SC #2 (fetch + XHR network_error) GREEN.
|
||||
- Plan 04-01 P1 #11 fetch URL extraction validated end-to-end (A34.4 pin).
|
||||
- Pre-checkpoint bundle gates 6/6 unchanged.
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.planning/phases/04-harden-clean-up-optional/04-05-SUMMARY.md` capturing:
|
||||
- assertA34 diff (full body)
|
||||
- driveA34 diff (full body)
|
||||
- Orchestrator wiring diff (3 sites in harness.test.ts)
|
||||
- UAT before/after (34/34 -> 35/35)
|
||||
- ROADMAP SC #2 closure evidence (A34.4 fetch URL contains real path; A34.5 XHR status===404)
|
||||
- Plan 04-01 P1 #11 end-to-end empirical pin (no '[object Request]' in any network_error entry)
|
||||
- Commit refs (Task 1 + Task 2)
|
||||
</output>
|
||||
</content>
|
||||
</invoke>
|
||||
Reference in New Issue
Block a user