--- phase: 01-stabilize-video-pipeline plan: 01 type: execute wave: 0 depends_on: [] files_modified: - .planning/PROJECT.md - .planning/REQUIREMENTS.md - .planning/ROADMAP.md - .planning/intel/decisions.md - .planning/intel/constraints.md - manifest.json autonomous: true scope_exception: "doc-text-only edits with no TypeScript compilation; cognitive load substantially lower than code plans of equal task count" requirements: - REQ-video-ring-buffer requirements_addressed: - REQ-video-ring-buffer must_haves: truths: - "PROJECT.md DEC-003, DEC-010 reflect the getDisplayMedia + port-keepalive amendments" - "REQUIREMENTS.md REQ-video-ring-buffer no longer says 'active-tab' and binds to getDisplayMedia" - "ROADMAP.md Phase 1 Success Criterion #2 no longer references tab re-attach" - "intel/decisions.md DEC-003 and DEC-010 carry an Amendment block" - "intel/constraints.md CON-tab-capture-binding and CON-service-worker-keepalive are RETIRED with a CON-display-capture-binding added" - "manifest.json permissions list contains desktopCapture (not tabCapture) and drops the now-unused alarms entry" - "Every code-touching plan (02-07) sees a consistent doc baseline before it runs" artifacts: - path: ".planning/PROJECT.md" provides: "Amended DEC-003 / DEC-010 rows and amended Constraints section" contains: "getDisplayMedia" - path: ".planning/REQUIREMENTS.md" provides: "REQ-video-ring-buffer wording without 'active-tab' and bound to getDisplayMedia" contains: "getDisplayMedia" - path: ".planning/ROADMAP.md" provides: "Phase 1 Success Criterion #2 with tab-reattach clause removed" contains: "MediaRecorder" - path: ".planning/intel/decisions.md" provides: "DEC-003 and DEC-010 with Amendment blocks" contains: "## Amendment" - path: ".planning/intel/constraints.md" provides: "RETIRED markers + new CON-display-capture-binding" contains: "CON-display-capture-binding" - path: "manifest.json" provides: "Permission swap" contains: "desktopCapture" key_links: - from: "manifest.json" to: "intel/constraints.md" via: "lockstep permission set" pattern: "desktopCapture" - from: "intel/decisions.md" to: "PROJECT.md" via: "Amendment block referenced from Key Decisions table" pattern: "AMENDED" --- Doc-cascade for D-A1..D-A6 + manifest permission swap (D-05/D-A6). This is Wave 0 work that MUST precede every code-touching plan in this phase so downstream agents see a consistent baseline (DEC-003 amended to `getDisplayMedia`, DEC-010 amended to port keepalive, manifest permissions swapped to `desktopCapture`). Plan 01 makes zero code-runtime changes — only text edits to planning docs and a single permissions edit in `manifest.json`. Purpose: Phase 2 and later phases will read PROJECT.md / REQUIREMENTS.md / ROADMAP.md to understand what was decided. Leaving them stale would carry the old `chrome.tabCapture` contract forward and cause downstream agents to silently re-introduce active-tab assumptions. Output: Six amended doc files + the manifest permissions block in its final Phase-1 shape. @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md @.planning/PROJECT.md @.planning/REQUIREMENTS.md @.planning/ROADMAP.md @.planning/intel/decisions.md @.planning/intel/constraints.md @.planning/phases/01-stabilize-video-pipeline/01-CONTEXT.md @manifest.json ## Trust Boundaries | Boundary | Description | |----------|-------------| | extension manifest → Chrome runtime | Permissions block declares the API surface Chrome will grant; minimising this is the policy boundary | ## STRIDE Threat Register | Threat ID | Category | Component | Disposition | Mitigation Plan | |-----------|----------|-----------|-------------|-----------------| | T-1-02 | Information Disclosure / over-privileged | `manifest.json` permissions array | mitigate | Drop `tabCapture` and `alarms` (no longer used); add `desktopCapture` (matches CONTEXT.md D-05 / D-A6). `host_permissions: [""]` and `activeTab` retained — already justified by Phase 3 screenshot path. Grep gate: `grep -v '^#' manifest.json \| grep -c '"tabCapture"' == 0 && grep -v '^#' manifest.json \| grep -c '"desktopCapture"' == 1` | (T-1-01 codec downgrade and T-1-04 port-hijack are addressed in Plans 03 and 04. T-1-03 stream-content-leakage is an accepted residual risk per CONTEXT.md D-04 — recorded in Plan 03's threat model.) Task 1: Amend intel/decisions.md DEC-003 and DEC-010 (D-A1) .planning/intel/decisions.md - .planning/intel/decisions.md (lines 48-60 DEC-003, lines 145-153 DEC-010) - .planning/phases/01-stabilize-video-pipeline/01-CONTEXT.md §"Doc Amendments (precede code)" (D-A1) Append an Amendment block to DEC-003 immediately AFTER line 59 (the `Confirming source` line). The amendment block reads VERBATIM: ```markdown ## Amendment (Phase 01-stabilize-video-pipeline, 2026-05-15) - AMENDED-BY: Phase 01 CONTEXT.md D-01..D-05 - Replace `chrome.tabCapture.capture()` with `navigator.mediaDevices.getDisplayMedia()` called from the offscreen document. - Offscreen document is created with `chrome.offscreen.Reason.DISPLAY_MEDIA` (replaces `USER_MEDIA`). - Codec/bitrate/timeslice binding unchanged: `video/webm; codecs=vp9` @ 400 000 bps, MediaRecorder timeslice 2000 ms. - Trade-off accepted: SPEC §1 "silent operation" is given up — Chrome's permanent "Sharing your screen" indicator is shown while recording. Phase 1 accepts this in exchange for broader capture coverage and elimination of `tabCapture` user-gesture juggling. - Tab-switch re-attachment clause is REMOVED — `getDisplayMedia` captures a screen/window, not a tab. There is nothing to re-attach. - Manifest permission `tabCapture` is REPLACED with `desktopCapture` (the latter is harmless: `getDisplayMedia` is a web standard API and does NOT actually require `desktopCapture`, but we declare it for clarity per CONTEXT.md D-05). ``` Then append an Amendment block to DEC-010 immediately AFTER line 152 (the `chrome.alarms` decision line). The amendment block reads VERBATIM: ```markdown ## Amendment (Phase 01-stabilize-video-pipeline, 2026-05-15) - AMENDED-BY: Phase 01 CONTEXT.md D-17..D-18 - Replace `chrome.alarms`-driven 20 s keepalive with a long-lived `chrome.runtime.connect` port opened from the offscreen document to the Service Worker. The port emits a `PING` message every 25 s; both directions of traffic reset the SW's 30 s idle timer per Chrome 110+ semantics (developer.chrome.com/blog/longer-esw-lifetimes). - The `alarms` permission is removed from `manifest.json` (it is no longer used by Phase 1; Phase 2 / 3 may re-add if needed). - Port lifetime cap (~5 minutes per Chromium-extensions community gist sunnyguan/f94058f66fab89e59e75b1ac1bf1a06e) is mitigated by reconnecting on `onDisconnect` and pre-emptively at ~290 s. - See `.planning/phases/01-stabilize-video-pipeline/01-RESEARCH.md` Pattern 5 for the canonical implementation. ``` Do not modify any other text in decisions.md. grep -c "AMENDED-BY: Phase 01" .planning/intel/decisions.md - `grep -c "AMENDED-BY: Phase 01" .planning/intel/decisions.md` returns 2 - `grep -c "getDisplayMedia" .planning/intel/decisions.md` returns at least 1 - `grep -c "port" .planning/intel/decisions.md` returns at least 1 - DEC-003 still has its original text intact (the amendment is APPENDED, not replacing) - DEC-010 still has its original text intact (the amendment is APPENDED, not replacing) Both decisions carry an Amendment block; downstream phases that grep DEC-003 / DEC-010 now find the new contract alongside the original. Task 2: Amend intel/constraints.md — retire two, add one (D-A2) .planning/intel/constraints.md - .planning/intel/constraints.md (lines 95-111 CON-tab-capture-binding and CON-service-worker-keepalive) - .planning/phases/01-stabilize-video-pipeline/01-CONTEXT.md §"Doc Amendments (precede code)" (D-A2) Two edits, both in `.planning/intel/constraints.md`: (1) Append a RETIRED block immediately AFTER line 102 (the end of CON-tab-capture-binding). VERBATIM text: ```markdown ### RETIRED (Phase 01-stabilize-video-pipeline, 2026-05-15) - RETIRED-BY: Phase 01 CONTEXT.md D-01 / D-A2 - Reason: This phase replaces `chrome.tabCapture` with `navigator.mediaDevices.getDisplayMedia()`. The new API is not active-tab-bound; the recorder captures a screen / window selected once via Chrome's native picker and continues across tab switches. - Replacement: CON-display-capture-binding (below). ``` (2) Append a RETIRED block immediately AFTER line 111 (the end of CON-service-worker-keepalive). VERBATIM text: ```markdown ### RETIRED (Phase 01-stabilize-video-pipeline, 2026-05-15) - RETIRED-BY: Phase 01 CONTEXT.md D-17 / D-A2 - Reason: This phase replaces alarms-driven keepalive with a long-lived `chrome.runtime.connect` port between offscreen and Service Worker. Port-message traffic resets the SW idle timer per Chrome 110+ semantics. - Replacement: CON-display-capture-binding (binds the port-keepalive expectations alongside the new capture API). ``` (3) Append a brand-new constraint immediately AFTER the existing `CON-buffer-storage` block (after line 196). VERBATIM text: ```markdown --- ## CON-display-capture-binding - Source: Phase 01 CONTEXT.md D-01..D-17, RESEARCH.md Patterns 1 & 5 - Type: api-contract - Constraint: Video capture uses `navigator.mediaDevices.getDisplayMedia()` invoked once per session from the offscreen document with `chrome.offscreen.Reason.DISPLAY_MEDIA`. The Service Worker is kept alive by a long-lived `chrome.runtime.connect({ name: 'video-keepalive' })` port opened by the offscreen, with traffic in both directions at a minimum cadence of 25 s and pre-emptive reconnect at 290 s. - Replaces: CON-tab-capture-binding (RETIRED), CON-service-worker-keepalive (RETIRED). - UX trade-off: Chrome's permanent "Sharing your screen" indicator is shown while recording. SPEC §1 silent-operation property is intentionally relaxed. ``` Do not modify any other text in constraints.md. grep -c "RETIRED-BY: Phase 01" .planning/intel/constraints.md - `grep -c "RETIRED-BY: Phase 01" .planning/intel/constraints.md` returns 2 - `grep -c "## CON-display-capture-binding" .planning/intel/constraints.md` returns 1 - The original `## CON-tab-capture-binding` and `## CON-service-worker-keepalive` headings still exist (RETIRED is APPENDED below them, not replacing them) - `grep -c "video-keepalive" .planning/intel/constraints.md` returns at least 1 Two retired constraints carry RETIRED-BY markers; CON-display-capture-binding exists as the consolidated replacement constraint. Task 3: Amend PROJECT.md Key Decisions table and Constraints section (D-A3) .planning/PROJECT.md - .planning/PROJECT.md (lines 79-110 Constraints section, lines 113-134 Key Decisions table) - .planning/phases/01-stabilize-video-pipeline/01-CONTEXT.md §"Doc Amendments (precede code)" (D-A3) Three edits in `.planning/PROJECT.md`: (1) DEC-003 row in the Key Decisions table — find line 124 starting `| **DEC-003**:` and replace the entire row with VERBATIM: ```markdown | **DEC-003**: Active video via `getDisplayMedia()` (vp9 / 400 kbps / 2000 ms) | AMENDED by Phase 01: SPEC §2/§4.1/§7 originally specified `chrome.tabCapture`; Phase 01 swaps to `getDisplayMedia` invoked in the offscreen document with `chrome.offscreen.Reason.DISPLAY_MEDIA`. Codec/bitrate/timeslice binding unchanged. See `.planning/intel/decisions.md` DEC-003 Amendment. | — Pending | locked (Phase 1, post-Amendment) | ``` (2) DEC-010 row — find line 131 starting `| **DEC-010**:` and replace the entire row with VERBATIM: ```markdown | **DEC-010**: Service Worker keepalive via long-lived port | AMENDED by Phase 01: SPEC §8 originally specified `chrome.alarms` at 20 s; Phase 01 swaps to a `chrome.runtime.connect` port between offscreen and SW with 25 s ping cadence and 290 s pre-emptive reconnect. See `.planning/intel/decisions.md` DEC-010 Amendment. | — Pending | locked (Phase 1, post-Amendment) | ``` (3) Constraints section — find line 100-101 (the `chrome.alarms` keepalive bullet) and replace with VERBATIM: ```markdown - **Service Worker lifecycle**: MV3 SW unloads after ~30 s idle; a long-lived `chrome.runtime.connect` port from offscreen to SW emits a PING every 25 s to keep the SW alive (CON-display-capture-binding, AMENDED from CON-service-worker-keepalive). - **Tab capture binding**: REMOVED (CON-tab-capture-binding RETIRED). The new `getDisplayMedia` binding (CON-display-capture-binding) is screen/window- scoped, not tab-scoped, and survives tab switches without re-attach. ``` (this replaces the two bullets currently at lines 97-102 — the existing SW-keepalive bullet AND the existing tab-capture-binding bullet — with the two replacement bullets above. The bullet ordering and the rest of the Constraints section are unchanged.) Do not modify any other text in PROJECT.md. grep -c "AMENDED by Phase 01" .planning/PROJECT.md - `grep -c "AMENDED by Phase 01" .planning/PROJECT.md` returns 2 (DEC-003 + DEC-010 rows) - `grep -c "getDisplayMedia" .planning/PROJECT.md` returns at least 1 - `grep -c "long-lived port" .planning/PROJECT.md` returns at least 1 - `grep -c "RETIRED" .planning/PROJECT.md` returns at least 1 - The `## Key Decisions` heading and the `## Constraints` heading still exist - All other rows of the Key Decisions table are untouched PROJECT.md's Key Decisions table and Constraints section reflect the new contract; downstream readers of PROJECT.md see the amendments without needing to drill into intel/. Task 4: Amend REQUIREMENTS.md REQ-video-ring-buffer (D-A4) .planning/REQUIREMENTS.md - .planning/REQUIREMENTS.md (lines 19-27 REQ-video-ring-buffer block) - .planning/phases/01-stabilize-video-pipeline/01-CONTEXT.md §"Doc Amendments (precede code)" (D-A4) Replace the entire REQ-video-ring-buffer entry (currently lines 19-27 starting `- [ ] **REQ-video-ring-buffer**`) with VERBATIM: ```markdown - [ ] **REQ-video-ring-buffer**: The extension maintains an in-memory ring buffer containing the most recent 30 seconds of captured video. AMENDED in Phase 01: video is acquired via `navigator.mediaDevices.getDisplayMedia()` invoked from the offscreen document (with `chrome.offscreen.Reason.DISPLAY_MEDIA`), NOT `chrome.tabCapture` as originally specified. The captured stream is screen-or-window-scoped per the operator's one-time selection in Chrome's native picker, and continues unchanged across tab switches. Encoding is unchanged: `video/webm; codecs=vp9` @ 400 000 bps with a `MediaRecorder` timeslice of 2000 ms; a single continuous recorder runs for the whole session. The first emitted chunk (WebM header) is retained indefinitely; subsequent chunks rotate out by the 30-second TTL rule. Bindings: DEC-003 (AMENDED), DEC-009, CON-video-window, CON-video-codec, CON-webm-header-retention, CON-display-capture-binding (replaces RETIRED CON-tab-capture-binding). - SPEC §10 acceptance criteria: #2, #3, #7. ``` Do not modify any other text in REQUIREMENTS.md. grep -c "AMENDED in" .planning/REQUIREMENTS.md - `grep -c "AMENDED in" .planning/REQUIREMENTS.md` returns at least 1 - `grep -c "getDisplayMedia" .planning/REQUIREMENTS.md` returns at least 1 - `grep -c "active-tab video" .planning/REQUIREMENTS.md` returns 0 (the old "active-tab" wording is removed) - `grep -c "REQ-video-ring-buffer" .planning/REQUIREMENTS.md` returns at least 2 (the bullet + the Traceability table row) - The Traceability table row for REQ-video-ring-buffer remains intact REQ-video-ring-buffer reads correctly for Phase 1 and binds the new constraint set. Task 5: Amend ROADMAP.md Phase 1 description + Success Criterion #2 (D-A5) .planning/ROADMAP.md - .planning/ROADMAP.md (lines 25, 33-66 Phase 1 block) - .planning/phases/01-stabilize-video-pipeline/01-CONTEXT.md §"Doc Amendments (precede code)" (D-A5) Two edits in `.planning/ROADMAP.md`: (1) Replace line 25 VERBATIM with: ```markdown - [ ] **Phase 1: Stabilize video pipeline** — Collapse offscreen duality, fix MediaRecorder shadow, fix WebM ring buffer playability, replace `chrome.tabCapture` with offscreen `getDisplayMedia` (AMENDED from original DEC-003) ``` (2) Replace Success Criterion #2 (currently lines 58-61 in the `**Success Criteria** (what must be TRUE):` block — "With the extension loaded and a tab open, a single continuous `MediaRecorder` ... without losing the WebM container header.") with VERBATIM: ```markdown 2. With the extension loaded and an operator session active, a single continuous `MediaRecorder` is running on the operator-selected screen/window source with timeslice 2000 ms; the recorder continues unchanged across tab switches (no tab re-attach logic; AMENDED from the original wording). The WebM container header is retained in the ring buffer indefinitely. ``` Do not modify any other text in ROADMAP.md. grep -c "AMENDED" .planning/ROADMAP.md - `grep -c "AMENDED" .planning/ROADMAP.md` returns at least 2 - `grep -c "tab re-attach" .planning/ROADMAP.md` returns 0 (the old phrase is gone) - `grep -c "getDisplayMedia" .planning/ROADMAP.md` returns at least 1 - Phases 2-5 sections remain untouched ROADMAP.md Phase 1 description and Success Criterion #2 reflect the new contract; readers of ROADMAP.md see the amendment without needing to drill into CONTEXT.md. Task 6: Manifest permission swap — tabCapture → desktopCapture, drop alarms (D-A6 / D-05) manifest.json - manifest.json (lines 6-14 permissions block) - .planning/phases/01-stabilize-video-pipeline/01-CONTEXT.md §"Doc Amendments (precede code)" (D-A6) - .planning/phases/01-stabilize-video-pipeline/01-PATTERNS.md §`manifest.json` (lines 444-476) Replace the `"permissions": [...]` array (currently lines 6-14) VERBATIM with: ```json "permissions": [ "desktopCapture", "activeTab", "downloads", "scripting", "storage", "offscreen" ], ``` Specifically: - Replace `"tabCapture"` → `"desktopCapture"` (per D-A6). - Remove `"alarms"` (the alarms keepalive is deleted in Plan 05; declaring an unused permission expands attack surface, mitigating T-1-02). - Keep `"activeTab"` (needed for `chrome.tabs.captureVisibleTab` in Phase 3 screenshot path — current code uses it at src/background/index.ts:190). - Keep `"downloads"` (used at src/background/index.ts:305). - Keep `"scripting"` (content script injection). - Keep `"storage"` (currently unused by code but retained per SPEC §7; do not remove in this phase — that is Phase 5 hardening territory). - Keep `"offscreen"` (required to call `chrome.offscreen.createDocument`). Do not modify any other field in `manifest.json` (manifest_version, name, version, description, host_permissions, background, content_scripts, action, icons all remain untouched). node -e "const m=require('./manifest.json'); const p=m.permissions; if(p.includes('tabCapture')||p.includes('alarms')){process.exit(1)}; if(!p.includes('desktopCapture')||!p.includes('offscreen')){process.exit(2)}; console.log('ok')" - `grep -c '"tabCapture"' manifest.json` returns 0 - `grep -c '"desktopCapture"' manifest.json` returns 1 - `grep -c '"alarms"' manifest.json` returns 0 - `grep -c '"offscreen"' manifest.json` returns 1 - `grep -c '"activeTab"' manifest.json` returns 1 - `node -e "require('./manifest.json')"` exits 0 (JSON valid) - `host_permissions` array still contains `""` (unchanged) manifest.json carries the final Phase-1 permissions set; downstream code-touching plans (02-07) operate against a manifest that matches the new API contract. After all six tasks land: 1. `grep -c "AMENDED" .planning/PROJECT.md .planning/REQUIREMENTS.md .planning/ROADMAP.md` returns at least 4 lines (each file has at least one amendment marker; the precise totals are checked in each task's acceptance_criteria). 2. `grep -c "RETIRED-BY: Phase 01" .planning/intel/constraints.md` returns 2. 3. `grep -c "AMENDED-BY: Phase 01" .planning/intel/decisions.md` returns 2. 4. `grep -c '"tabCapture"' manifest.json` returns 0 and `grep -c '"desktopCapture"' manifest.json` returns 1. 5. `node -e "require('./manifest.json')"` exits 0 (JSON valid). 6. `node -e "require('./.planning/config.json')"` exits 0 (we did not touch config.json). 7. Every code-touching plan in Waves 0..2 can grep the doc baseline (manifest.json, PROJECT.md, REQUIREMENTS.md, ROADMAP.md, decisions.md, constraints.md) and find the amendments — no plan needs to back-patch a doc. Commit cadence: ONE git commit per task (six atomic commits). Each commit message follows `docs(01-01): amend {file} per D-{ID}` style. The orchestrator handles commit creation. - All six tasks complete and verified. - No file in the dependency closure carries the pre-amendment wording. - manifest.json `permissions` array exactly matches the list specified in Task 6's action block. - Plans 02..07 can run against a self-consistent doc baseline (no late doc patches required mid-phase). After completion, create `.planning/phases/01-stabilize-video-pipeline/01-01-SUMMARY.md` with: - Which 6 files were amended and at which line ranges - The exact text of the new CON-display-capture-binding block (so downstream plans can quote it) - Confirmation of all six acceptance grep checks - Six commit SHAs (one per task)