---
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 (D-05, D-A6)"
    - "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"
---

<objective>
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.
</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/REQUIREMENTS.md
@.planning/ROADMAP.md
@.planning/intel/decisions.md
@.planning/intel/constraints.md
@.planning/phases/01-stabilize-video-pipeline/01-CONTEXT.md
@manifest.json
</context>

<threat_model>
## 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: ["<all_urls>"]` 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.)
</threat_model>

<tasks>

<task type="auto">
  <name>Task 1: Amend intel/decisions.md DEC-003 and DEC-010 (D-A1)</name>
  <files>.planning/intel/decisions.md</files>
  <read_first>
    - .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)
  </read_first>
  <action>
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.
  </action>
  <verify>
    <automated>grep -c "AMENDED-BY: Phase 01" .planning/intel/decisions.md</automated>
  </verify>
  <acceptance_criteria>
    - `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)
  </acceptance_criteria>
  <done>Both decisions carry an Amendment block; downstream phases that grep DEC-003 / DEC-010 now find the new contract alongside the original.</done>
</task>

<task type="auto">
  <name>Task 2: Amend intel/constraints.md — retire two, add one (D-A2)</name>
  <files>.planning/intel/constraints.md</files>
  <read_first>
    - .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)
  </read_first>
  <action>
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.
  </action>
  <verify>
    <automated>grep -c "RETIRED-BY: Phase 01" .planning/intel/constraints.md</automated>
  </verify>
  <acceptance_criteria>
    - `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
  </acceptance_criteria>
  <done>Two retired constraints carry RETIRED-BY markers; CON-display-capture-binding exists as the consolidated replacement constraint.</done>
</task>

<task type="auto">
  <name>Task 3: Amend PROJECT.md Key Decisions table and Constraints section (D-A3)</name>
  <files>.planning/PROJECT.md</files>
  <read_first>
    - .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)
  </read_first>
  <action>
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.
  </action>
  <verify>
    <automated>grep -c "AMENDED by Phase 01" .planning/PROJECT.md</automated>
  </verify>
  <acceptance_criteria>
    - `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
  </acceptance_criteria>
  <done>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/.</done>
</task>

<task type="auto">
  <name>Task 4: Amend REQUIREMENTS.md REQ-video-ring-buffer (D-A4)</name>
  <files>.planning/REQUIREMENTS.md</files>
  <read_first>
    - .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)
  </read_first>
  <action>
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.
  </action>
  <verify>
    <automated>grep -c "AMENDED in" .planning/REQUIREMENTS.md</automated>
  </verify>
  <acceptance_criteria>
    - `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
  </acceptance_criteria>
  <done>REQ-video-ring-buffer reads correctly for Phase 1 and binds the new constraint set.</done>
</task>

<task type="auto">
  <name>Task 5: Amend ROADMAP.md Phase 1 description + Success Criterion #2 (D-A5)</name>
  <files>.planning/ROADMAP.md</files>
  <read_first>
    - .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)
  </read_first>
  <action>
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.
  </action>
  <verify>
    <automated>grep -c "AMENDED" .planning/ROADMAP.md</automated>
  </verify>
  <acceptance_criteria>
    - `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
  </acceptance_criteria>
  <done>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.</done>
</task>

<task type="auto">
  <name>Task 6: Manifest permission swap — tabCapture → desktopCapture, drop alarms (D-A6 / D-05)</name>
  <files>manifest.json</files>
  <read_first>
    - 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)
  </read_first>
  <action>
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).
  </action>
  <verify>
    <automated>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')"</automated>
  </verify>
  <acceptance_criteria>
    - `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 `"<all_urls>"` (unchanged)
  </acceptance_criteria>
  <done>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.</done>
</task>

</tasks>

<verification>
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.
</verification>

<success_criteria>
- 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).
</success_criteria>

<output>
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)
</output>
