fix(02): revise plans per checker (B1 + 4 flags) — add tabs permission for D-P2-02

- BLOCKER B1: add `tabs` to manifest.json permissions (DEC-011 Amendment 1
  cites Phase 2 D-P2-02 meta.urls feature as justification). Honors
  D-P2-02 "all tabs visible" wording verbatim. Updates manifest-i18n test
  expected permission list lockstep.
- F1: add A28 harness assertion for REQ-archive-layout strict zip-layout
  verification (5 entries, no extras).
- F2: createArchive empty-tracker fallback removed; logs warn + sets
  urls:[] instead of fake [extension-origin URL]. 02-01 RED test pins
  empty-tracker → urls:[].
- F3: 02-02 Task 3 prose deliberation struck; typed `blob-url-mint-failed`
  throw is the resolved-only contract.
- F4: 02-02 Task 3 verify block adds full-suite `npm test` after focused
  test runs.
- A27 strict-mode (Plan 02-04): REQUIRES both URLs in meta.urls; FAILS
  on length < 2.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-20 14:25:20 +02:00
parent 0608b22427
commit 9dcfcf0793
8 changed files with 496 additions and 121 deletions

View File

@@ -290,7 +290,7 @@ Existing blobToBase64 / base64ToBlob (src/shared/binary.ts:42-85) — REUSE AS-I
(2) Generate a requestId (use crypto.randomUUID() — SW supports it; see getVideoBufferFromOffscreen pattern).
(3) Post `{ type: 'CREATE_DOWNLOAD_URL', requestId, dataBase64, mimeType: 'application/zip' }` on the existing port (offscreenPort variable — see Plan 01-04 wiring at src/background/index.ts:onConnect host).
(4) Await the matching `DOWNLOAD_URL` response (per-request listener map pattern, mirroring REQUEST_BUFFER → BUFFER from getVideoBufferFromOffscreen).
(5) If response.url === '' or no response within timeout (5000ms), throw an EmptyVideoBufferError-equivalent "blob-url-mint-failed" or log + return (planner-decision: log warn, attempt fallback via legacy data: URL ONLY for archives <1 MBotherwise throw and surface to operator). Resolved inline: NO FALLBACK. The blob: URL is the new contract; failure must surface via a typed error so the operator gets a clear failure mode, not a silent corrupt archive. Throw `new Error('blob-url-mint-failed: offscreen unresponsive or empty url')`.
(5) If response.url === '' or no response within timeout (5000ms), throw a typed error: `new Error('blob-url-mint-failed: offscreen unresponsive or empty url')`. NO FALLBACK to legacy data: URL — the blob: URL is the contract; failure surfaces via the typed error so the operator gets a clear failure mode (routed through saveArchive's catch block to the RECORDING_ERROR channel, mirroring the EmptyVideoBufferError pattern). Silent corrupt-archive paths are forbidden.
(6) Call chrome.downloads.download({ url, filename, saveAs: false }) capturing the returned downloadId.
(7) Store downloadId → url in a module-scoped Map<number, string> (`pendingRevokes`) so chrome.downloads.onChanged can dispatch the revoke.
- chrome.downloads.onChanged listener registered at module init (after the existing chrome.runtime.onMessage listener registration around line 843):
@@ -433,7 +433,7 @@ Existing blobToBase64 / base64ToBlob (src/shared/binary.ts:42-85) — REUSE AS-I
listener for URL.revokeObjectURL lifecycle.
</action>
<verify>
<automated>npx tsc --noEmit 2>&1 | head -20 ; npm run build 2>&1 | tail -10 ; npx vitest run tests/background/blob-url-download.test.ts 2>&1 | tail -20</automated>
<automated>npx tsc --noEmit 2>&1 | head -20 ; npm run build 2>&1 | tail -10 ; npx vitest run tests/background/blob-url-download.test.ts 2>&1 | tail -20 ; npm test 2>&1 | tail -30</automated>
</verify>
<done>
tsc clean. npm run build clean. tests/background/blob-url-download.test.ts → 3/3 GREEN.