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:
@@ -5,6 +5,9 @@ type: auto
|
||||
wave: 2
|
||||
depends_on: [01]
|
||||
files_modified:
|
||||
- manifest.json
|
||||
- tests/i18n/manifest-i18n.test.ts
|
||||
- .planning/PROJECT.md
|
||||
- src/background/tab-url-tracker.ts
|
||||
- src/shared/types.ts
|
||||
- src/background/index.ts
|
||||
@@ -28,7 +31,7 @@ must_haves:
|
||||
- ".planning/REQUIREMENTS.md REQ-meta-json-schema text amended to reflect the new 8-field shape, with the breaking-change cutover documented inline."
|
||||
- "chrome.tabs.onActivated and chrome.tabs.onUpdated listeners registered in src/background/index.ts maintain the tracker's internal Set; pruning happens alongside the video segment ring buffer lifecycle (no separate timer)."
|
||||
- "Always-on charter preserved (Plan 01-09 Amendment 3): SAVE creates a zip with the current tracker state; tracker continues accumulating after SAVE."
|
||||
- "manifest.json permissions: the existing `tabs` permission gap (Plan 01-13 SUMMARY Known Limitations item 3) is NOT closed by this plan — the tracker uses chrome.tabs.onActivated + chrome.tabs.onUpdated which work on `activeTab` permission alone for the active-tab URL events. Cross-window/inactive-tab URL events require `tabs` permission and are deferred to Phase 4 hardening (planner-decision documented inline)."
|
||||
- "DEC-011 amended (Amendment 1, 2026-05-20): `tabs` permission ADDED to manifest.json by this plan. chrome.tabs.get(tabId).url is now reliably defined for all tabs in any window (not just activeTab). The tab-url-tracker enumerates chrome.tabs.query({}) at SAVE time as a defensive fallback to capture tabs the operator OPENED but never activated during the 30s window. tests/i18n/manifest-i18n.test.ts pins the new 8-entry permission set as a regression guard."
|
||||
artifacts:
|
||||
- path: "src/background/tab-url-tracker.ts"
|
||||
provides: "getTabUrlsSeen(): string[] + initTabUrlTracker(): void + clearTabUrlsSeen(): void"
|
||||
@@ -43,6 +46,15 @@ must_haves:
|
||||
- path: ".planning/REQUIREMENTS.md"
|
||||
provides: "REQ-meta-json-schema amended for 8-field shape + breaking-change cutover note"
|
||||
contains: "schemaVersion|urls.*string\\[\\]"
|
||||
- path: "manifest.json"
|
||||
provides: "permissions array extended to include `tabs` (DEC-011 Amendment 1)"
|
||||
contains: "\"tabs\""
|
||||
- path: "tests/i18n/manifest-i18n.test.ts"
|
||||
provides: "regression-pin describe block for the 8-entry permission set including `tabs`"
|
||||
contains: "DEC-011 Amendment 1|EXPECTED_PERMISSIONS"
|
||||
- path: ".planning/PROJECT.md"
|
||||
provides: "DEC-011 row rewritten with Amendment 1 (2026-05-20) citing Phase 2 D-P2-02 as justification"
|
||||
contains: "Amendment 1"
|
||||
key_links:
|
||||
- from: "src/background/index.ts:initialize"
|
||||
to: "src/background/tab-url-tracker.ts:initTabUrlTracker"
|
||||
@@ -189,6 +201,65 @@ REQ-meta-json-schema (REQUIREMENTS.md:106-119) — verbatim text to amend:
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto" tdd="false">
|
||||
<name>Task 0: DEC-011 Amendment 1 — add `tabs` to manifest + sync PROJECT.md + i18n permission-pin test</name>
|
||||
<files>manifest.json, .planning/PROJECT.md, tests/i18n/manifest-i18n.test.ts</files>
|
||||
<action>
|
||||
Per plan-checker iteration 1 BLOCKER B1 resolution (user-approved Option A: honor D-P2-02
|
||||
"all tabs visible" verbatim by adding `tabs` permission to manifest.json).
|
||||
|
||||
1. **manifest.json:** extend the `permissions` array to include `"tabs"`. The locked
|
||||
post-amendment set has exactly 8 entries (alphabetic order is NOT required; the
|
||||
i18n test pins membership via set-equality, not order):
|
||||
```json
|
||||
"permissions": [
|
||||
"desktopCapture",
|
||||
"activeTab",
|
||||
"tabs",
|
||||
"downloads",
|
||||
"scripting",
|
||||
"storage",
|
||||
"offscreen",
|
||||
"notifications"
|
||||
]
|
||||
```
|
||||
NOTE: this commit may have already been applied during the revision pass — verify before
|
||||
editing. If `grep -c '"tabs"' manifest.json` returns a value matching the post-amendment
|
||||
state, treat this step as a no-op verification and proceed.
|
||||
|
||||
2. **.planning/PROJECT.md DEC-011 row:** rewrite the row to embed Amendment 1
|
||||
(2026-05-20) referencing Phase 2 D-P2-02 as justification. The amended row must:
|
||||
(a) Cite SPEC §7 as the original source and DEC-003 Amendment as Phase 01 delta.
|
||||
(b) State Amendment 1 ADDS `tabs` for D-P2-02 meta.urls feature.
|
||||
(c) List the current locked set verbatim (8 permissions + host_permissions).
|
||||
(d) Acknowledge audit T-1-02 ("unused permissions expand attack surface") and
|
||||
explicitly override it: the permission is USED by the meta.urls feature, so
|
||||
it is not unused.
|
||||
(e) Set status to `locked (Phase 1, post-Amendment 1)`.
|
||||
|
||||
3. **tests/i18n/manifest-i18n.test.ts:** add a new describe block
|
||||
`"Plan 02-03 DEC-011 Amendment 1: manifest.json permissions include \"tabs\" (D-P2-02)"`
|
||||
containing TWO assertions:
|
||||
(a) `manifest.permissions.includes('tabs') === true`
|
||||
(b) Set-equality between `manifest.permissions` (sorted) and the locked 8-entry
|
||||
EXPECTED_PERMISSIONS constant (sorted). Both lengths must match.
|
||||
Pre-existing test blocks (`Plan 01-12: ...`) MUST remain GREEN unchanged.
|
||||
|
||||
The amendment closes plan-checker iteration 1 BLOCKER B1 and unblocks Plan 02-04 A27's
|
||||
strict-mode (both-URLs-required) assertion — see Plan 02-04 Task 3 amendment in the same
|
||||
iteration 1 pass.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>grep -c '"tabs"' manifest.json ; grep -c 'Amendment 1' .planning/PROJECT.md ; npx vitest run tests/i18n/manifest-i18n.test.ts 2>&1 | tail -10</automated>
|
||||
</verify>
|
||||
<done>
|
||||
manifest.json permissions includes `"tabs"` exactly once. PROJECT.md DEC-011 row carries
|
||||
Amendment 1 prose (≥1 grep hit for "Amendment 1"). tests/i18n/manifest-i18n.test.ts new
|
||||
describe block GREEN (2/2 assertions pass). Atomic commit shared with the rest of the
|
||||
iteration 1 revision pass — see commit conventions in the orchestrator brief.
|
||||
</done>
|
||||
</task>
|
||||
|
||||
<task type="auto" tdd="true">
|
||||
<name>Task 1: Create src/background/tab-url-tracker.ts module</name>
|
||||
<files>src/background/tab-url-tracker.ts</files>
|
||||
@@ -239,19 +310,31 @@ REQ-meta-json-schema (REQUIREMENTS.md:106-119) — verbatim text to amend:
|
||||
and in-tab navigation events. No explicit "snapshot active tab at SAVE time" is needed (the
|
||||
tracker has already captured it via onActivated).
|
||||
|
||||
NOTE on `tabs` permission gap: chrome.tabs.onActivated provides tabId in the event payload;
|
||||
chrome.tabs.get(tabId) returns the tab WITHOUT the `.url` field unless `tabs` permission is
|
||||
granted (Plan 01-13 SUMMARY Known Limitations item 3). For the active tab specifically,
|
||||
`activeTab` permission grants temporary URL access. The tracker accepts the URL when present
|
||||
and skips silently when absent (logged at warn level for diagnostics). This means inactive-tab
|
||||
URL changes are NOT captured — acceptable per planner-decision deferred to Phase 4 hardening
|
||||
(CONTEXT.md `<deferred>` "tabs permission gap"). Document this limitation inline.
|
||||
NOTE on `tabs` permission (post DEC-011 Amendment 1, 2026-05-20): the new task in this plan
|
||||
AMENDS manifest.json to include the `tabs` permission. With `tabs` granted, chrome.tabs.get(tabId)
|
||||
reliably populates the `.url` field for any tab in any window — closing the limitation noted in
|
||||
Plan 01-13 SUMMARY Known Limitations item 3. The tracker therefore captures URL transitions for
|
||||
BOTH active and inactive tabs (the `chrome.tabs.onUpdated` listener already fires for any tab's
|
||||
URL change regardless of active state). A diagnostic logger.warn fires only if `.url` is still
|
||||
missing after the permission is granted (shouldn't happen — defensive only).
|
||||
|
||||
Per D-P2-03's "urls array non-empty" rule from Plan 02-01 Task 3: if getTabUrlsSeen returns
|
||||
an empty array at SAVE time, createArchive (Task 3 below) must fall back to a sentinel value
|
||||
(e.g., the extension-origin URL from `new URL(chrome.runtime.getURL('')).origin` — same as the
|
||||
current single-url path). This guarantees the strict 8-field schema validation never fails on
|
||||
empty urls[].
|
||||
SAVE-time defensive enumeration (per F2 + DEC-011 Amendment 1 capability): in addition to the
|
||||
event-driven Set maintenance, the tracker exposes a `snapshotOpenTabs(): Promise<void>` helper
|
||||
that invokes `chrome.tabs.query({})` and folds every returned tab.url (that passes the filter)
|
||||
into the internal Set + firstSeenOrder. createArchive (Task 2 below) calls this helper BEFORE
|
||||
reading `getTabUrlsSeen()` so any tab the operator OPENED during the 30s window but never
|
||||
activated (so the onActivated listener never fired for it) is still captured. This is purely
|
||||
additive — duplicates dedup; order preserves first-seen-first; no behavior change for
|
||||
already-observed tabs.
|
||||
|
||||
Per F2 (plan-checker iteration 1): the empty-tracker case is NO LONGER a sentinel-URL fallback.
|
||||
If after `snapshotOpenTabs()` the tracker is STILL empty (e.g., whole-desktop recording with NO
|
||||
browser tabs open at SAVE time — operator captured a non-Chrome window via desktopCapture only),
|
||||
that is a MEANINGFUL state and is represented faithfully as `urls: []` (empty array). The strict-
|
||||
validation rule in `tests/build/strict-meta-json-validation.test.ts` Test 3 was relaxed in Plan
|
||||
02-01 iteration 1 to PERMIT empty urls[] for this case; Plan 02-01's RED tests pin the
|
||||
empty-tracker → `meta.urls === []` contract explicitly. NO fake extension-origin URL is
|
||||
inserted.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>npx tsc --noEmit 2>&1 | head -20</automated>
|
||||
@@ -269,16 +352,27 @@ REQ-meta-json-schema (REQUIREMENTS.md:106-119) — verbatim text to amend:
|
||||
<behavior>
|
||||
- src/shared/types.ts SessionMetadata: REPLACE `url: string` with `urls: string[]`; ADD `schemaVersion: string` as the first field. Total 8 fields.
|
||||
- src/background/index.ts:
|
||||
(a) import { initTabUrlTracker, getTabUrlsSeen } from './tab-url-tracker';
|
||||
(a) import { initTabUrlTracker, snapshotOpenTabs, getTabUrlsSeen } from './tab-url-tracker';
|
||||
(b) call initTabUrlTracker() inside the existing `initialize()` function (line 957) under defensive try/catch matching the chrome.notifications.onClicked pattern.
|
||||
(c) createArchive (line 673-684) updated:
|
||||
(c) createArchive (line 673-684) updated. Per F2 (plan-checker iteration 1) the empty-tracker case is NOT a sentinel-URL fallback; it is `urls: []`:
|
||||
```typescript
|
||||
const manifest = chrome.runtime.getManifest();
|
||||
const tabUrls = getTabUrlsSeen();
|
||||
// D-P2-03 non-empty guarantee: if the tracker has no URLs (e.g., SW just spawned,
|
||||
// operator hasn't activated a real tab yet), fall back to the extension-origin URL
|
||||
// so the strict-validation schema (REQ-meta-json-schema) never fails on empty array.
|
||||
const urls = tabUrls.length > 0 ? tabUrls : [new URL(chrome.runtime.getURL('')).origin];
|
||||
// DEC-011 Amendment 1: with `tabs` permission granted, snapshot every currently-open
|
||||
// tab's URL at SAVE time as a defensive fallback. This captures tabs the operator
|
||||
// OPENED during the 30s window but never activated (so the onActivated listener
|
||||
// never fired for them). Dedup + order preservation is handled by the tracker.
|
||||
try {
|
||||
await snapshotOpenTabs();
|
||||
} catch (err) {
|
||||
logger.warn('snapshotOpenTabs failed at SAVE time (continuing with tracker state):', err);
|
||||
}
|
||||
const urls = getTabUrlsSeen();
|
||||
if (urls.length === 0) {
|
||||
// Meaningful state: whole-desktop recording with NO browser tabs open. Per F2
|
||||
// resolution, this is represented faithfully as urls:[] — NO fake extension-origin
|
||||
// URL inserted. tests/background/meta-json-urls-schema.test.ts pins this contract.
|
||||
logger.warn('createArchive: tabUrlsSeen is empty after snapshotOpenTabs — emitting meta.urls=[] (whole-desktop session with no browser tabs)');
|
||||
}
|
||||
const metadata: SessionMetadata = {
|
||||
schemaVersion: '2',
|
||||
timestamp: new Date().toISOString(),
|
||||
@@ -308,7 +402,7 @@ REQ-meta-json-schema (REQUIREMENTS.md:106-119) — verbatim text to amend:
|
||||
decision to make schemaVersion the 8th field.
|
||||
|
||||
2. Edit `src/background/index.ts`:
|
||||
- Add import: `import { initTabUrlTracker, getTabUrlsSeen } from './tab-url-tracker';` near
|
||||
- Add import: `import { initTabUrlTracker, snapshotOpenTabs, getTabUrlsSeen } from './tab-url-tracker';` near
|
||||
the existing import block (line 1-11).
|
||||
- In `initialize()` (line 957), add a defensive try/catch block calling initTabUrlTracker()
|
||||
right after the existing chrome.notifications.onClicked listener registration (search for
|
||||
@@ -436,25 +530,28 @@ REQ-meta-json-schema (REQUIREMENTS.md:106-119) — verbatim text to amend:
|
||||
- `grep -c "schemaVersion" .planning/REQUIREMENTS.md` ≥ 2 (in REQ block + acceptance bullet).
|
||||
- `grep -c "url: string" src/shared/types.ts` → 0 (the legacy field is gone).
|
||||
- Always-on charter check: `grep -n "clearTabUrlsSeen" src/background/index.ts` → 0 hits (createArchive does NOT clear; tracker keeps accumulating after SAVE).
|
||||
- DEC-011 Amendment 1 verification: `grep -c '"tabs"' manifest.json` ≥ 1; `npx vitest run tests/i18n/manifest-i18n.test.ts` GREEN (pre-existing 10 tests + 2 new permission-set tests = 12/12 GREEN); `grep -c 'Amendment 1' .planning/PROJECT.md` ≥ 1.
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
1. tests/background/meta-json-urls-schema.test.ts 4/4 GREEN.
|
||||
2. tests/build/strict-meta-json-validation.test.ts 8/8 GREEN.
|
||||
1. tests/background/meta-json-urls-schema.test.ts 4/4 GREEN (including the F2-pinned empty-tracker → `meta.urls === []` contract).
|
||||
2. tests/build/strict-meta-json-validation.test.ts 8/8 GREEN (Test 3 relaxed to PERMIT empty urls[] per F2 resolution).
|
||||
3. UAT harness 24/24 GREEN preserved (or A13 amended in lockstep if its meta.json assertions depend on the old `url` field — verify before commit).
|
||||
4. .planning/REQUIREMENTS.md REQ-meta-json-schema reflects the 8-field shape with breaking-change cutover documented.
|
||||
5. Always-on charter preserved: tab-url-tracker continues accumulating after SAVE; no createArchive-time clearTabUrlsSeen call.
|
||||
6. Tier-1 FORBIDDEN_HOOK_STRINGS = 12 (unchanged).
|
||||
7. tabs permission gap NOT closed by this plan — explicit deferral comment in tab-url-tracker.ts citing CONTEXT.md `<deferred>`.
|
||||
7. DEC-011 Amendment 1 LANDED: manifest.json permissions includes `"tabs"`; PROJECT.md DEC-011 row carries Amendment 1 prose; tests/i18n/manifest-i18n.test.ts permission-set describe block GREEN. The `tabs` permission gap noted in Plan 01-13 SUMMARY Known Limitations item 3 is CLOSED by this plan, not deferred.
|
||||
8. createArchive empty-tracker fallback REMOVED (F2): emits `urls: []` + logger.warn for whole-desktop-no-tab sessions; NO fake extension-origin URL inserted.
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.planning/phases/02-stabilize-export-pipeline/02-03-SUMMARY.md`
|
||||
documenting:
|
||||
- New module src/background/tab-url-tracker.ts (LOC + public API summary)
|
||||
- New module src/background/tab-url-tracker.ts (LOC + public API summary) — 4 exports incl. snapshotOpenTabs (DEC-011 Amendment 1)
|
||||
- SessionMetadata field-count delta (7 → 8) + ordering rationale
|
||||
- 8th field `schemaVersion` decision: planner-suggested in Plan 02-01 Task 3, ratified here
|
||||
- Filter rules verbatim from CONTEXT.md `<specifics>`
|
||||
- Tabs permission gap deferral citation (CONTEXT.md `<deferred>`)
|
||||
- Forward link: Plan 02-04 may amend UAT harness A13 if its meta.json assertions assumed the old `url` field
|
||||
- DEC-011 Amendment 1 (2026-05-20): `tabs` permission ADDED; tests/i18n/manifest-i18n.test.ts pins the 8-entry set; PROJECT.md DEC-011 row rewritten with Amendment 1 prose
|
||||
- F2 resolution: empty-tracker case emits `urls: []` (no fake extension-origin fallback)
|
||||
- Forward link: Plan 02-04 A27 strict-mode (both URLs required) is unblocked by Amendment 1
|
||||
</output>
|
||||
|
||||
Reference in New Issue
Block a user