feat(02-03): meta.json — urls[] + schemaVersion (D-P2-02 + D-P2-03; replaces url:string)
- src/shared/types.ts SessionMetadata: REPLACE `url: string` with
`urls: string[]`; ADD `schemaVersion: string` as the first field.
Total 8 fields. Field-emission order follows source-declaration order
(TypeScript object-literal insertion order; JSON.stringify emits in
insertion order per ECMA-262). Docstring cites D-P2-02 + D-P2-03 +
Plan 02-01 Task 3 planner-resolved 8th field decision + F2 empty-array
permission.
- src/background/index.ts:
* Import { initTabUrlTracker, snapshotOpenTabs, getTabUrlsSeen } from
'./tab-url-tracker'.
* Register initTabUrlTracker() at module top-level alongside
chrome.downloads.onChanged (Plan 02-02 precedent for D-P2-* feature
registration). Defensive try/catch matches the surrounding chrome.*
listener pattern; tracker module has its own initialized flag for
idempotency.
* createArchive: snapshotOpenTabs() before reading getTabUrlsSeen()
(DEC-011 Amendment 1 capability — captures tabs opened but never
activated). Empty urls[] emitted faithfully per F2 (no fake
extension-origin sentinel; logger.warn for diagnostic visibility on
whole-desktop-no-tab sessions).
* metadata literal: schemaVersion: '2' first, urls (not url), 8 fields
total. ECMA-262 insertion-order guarantee + JSON.stringify deliver
the canonical wire shape.
- Always-on charter preserved: createArchive does NOT call
clearTabUrlsSeen() — tracker continues accumulating across saves
(Plan 01-09 Amendment 3 invariant).
Verification:
- npx tsc --noEmit → clean.
- npm run build → clean (dist/assets/index.ts-8LkXuqac.js 378.82 kB,
~+2 kB vs pre-Task-2 baseline for the new tab-url-tracker module).
- npx vitest run → 171/171 GREEN (was 163 GREEN / 8 RED; +8 GREEN net).
- Tier-1 grep gate: 13/13 GREEN unchanged.
Closes 8 RED tests:
- tests/background/meta-json-urls-schema.test.ts Tests 1+2 (Tests 3+4+5
flipped in Task 1).
- tests/build/strict-meta-json-validation.test.ts Tests 1+3+8 (Tests 2,
4, 5, 6, 7 remain GREEN regression guards).
This commit is contained in:
@@ -8,6 +8,12 @@ import type {
|
|||||||
VideoBufferResponse
|
VideoBufferResponse
|
||||||
} from '../shared/types';
|
} from '../shared/types';
|
||||||
import { remuxSegments } from './webm-remux';
|
import { remuxSegments } from './webm-remux';
|
||||||
|
// Plan 02-03 D-P2-02 — tab-url tracker for meta.urls (multi-tab context).
|
||||||
|
// initTabUrlTracker is called from initialize(); snapshotOpenTabs +
|
||||||
|
// getTabUrlsSeen are called from createArchive() at SAVE time. The
|
||||||
|
// always-on charter (Plan 01-09 Amendment 3) is preserved — no
|
||||||
|
// clearTabUrlsSeen call in the save flow.
|
||||||
|
import { initTabUrlTracker, snapshotOpenTabs, getTabUrlsSeen } from './tab-url-tracker';
|
||||||
import JSZip from 'jszip';
|
import JSZip from 'jszip';
|
||||||
|
|
||||||
// ─── Plan 01-13: NO SW-side test hook gate (Approach B) ──────────────
|
// ─── Plan 01-13: NO SW-side test hook gate (Approach B) ──────────────
|
||||||
@@ -709,10 +715,43 @@ async function createArchive(
|
|||||||
// the running version once manifest.json bumps to 1.0.1+. The Chrome
|
// the running version once manifest.json bumps to 1.0.1+. The Chrome
|
||||||
// runtime API is always available in the SW context, so no fallback
|
// runtime API is always available in the SW context, so no fallback
|
||||||
// is needed.
|
// is needed.
|
||||||
|
//
|
||||||
|
// Plan 02-03 D-P2-02 + D-P2-03 amendment (2026-05-20; closes audit P1 #10):
|
||||||
|
// - `url: string` REPLACED by `urls: string[]` sourced from the
|
||||||
|
// tab-url tracker (chrome.tabs.onActivated + onUpdated event-driven
|
||||||
|
// accumulation + snapshotOpenTabs SAVE-time defensive enumeration
|
||||||
|
// via chrome.tabs.query({})).
|
||||||
|
// - `schemaVersion: '2'` ADDED to mark the schema-breaking cutover.
|
||||||
|
// - Field-emission order follows the SessionMetadata interface line
|
||||||
|
// order — TypeScript preserves object-literal insertion order, and
|
||||||
|
// JSON.stringify emits in insertion order per ECMA-262.
|
||||||
|
//
|
||||||
|
// 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 30 s window but never
|
||||||
|
// activated (so the onActivated listener never fired for them). Dedup
|
||||||
|
// + order preservation is handled by the tracker itself.
|
||||||
|
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 per F2 (plan-checker iteration 1): whole-desktop
|
||||||
|
// recording with NO browser tabs open at SAVE time. The empty array
|
||||||
|
// IS the canonical representation — 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 manifest = chrome.runtime.getManifest();
|
const manifest = chrome.runtime.getManifest();
|
||||||
const metadata: SessionMetadata = {
|
const metadata: SessionMetadata = {
|
||||||
|
schemaVersion: '2',
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
url: new URL(chrome.runtime.getURL('')).origin,
|
urls,
|
||||||
userAgent: navigator.userAgent,
|
userAgent: navigator.userAgent,
|
||||||
extensionVersion: manifest.version,
|
extensionVersion: manifest.version,
|
||||||
videoBufferSeconds: 30,
|
videoBufferSeconds: 30,
|
||||||
@@ -1171,6 +1210,20 @@ try {
|
|||||||
logger.warn('chrome.notifications.onClicked.addListener failed:', e);
|
logger.warn('chrome.notifications.onClicked.addListener failed:', e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Plan 02-03 D-P2-02 — tab-url tracker bootstrap.
|
||||||
|
// Registers chrome.tabs.onActivated + chrome.tabs.onUpdated listeners that
|
||||||
|
// maintain an internal Set of URLs observed during the SW's lifetime.
|
||||||
|
// Feeds meta.urls in createArchive (closes audit P1 #10). Defensive
|
||||||
|
// try/catch matches the surrounding chrome.* listener registration
|
||||||
|
// pattern; idempotency is handled by the tracker module itself (an
|
||||||
|
// initialized flag prevents double-registration if some future caller
|
||||||
|
// invokes initTabUrlTracker more than once).
|
||||||
|
try {
|
||||||
|
initTabUrlTracker();
|
||||||
|
} catch (e) {
|
||||||
|
logger.warn('initTabUrlTracker failed:', e);
|
||||||
|
}
|
||||||
|
|
||||||
// chrome.downloads.onChanged: D-P2-01 (P0-6 fix) — revoke-on-terminal-state.
|
// chrome.downloads.onChanged: D-P2-01 (P0-6 fix) — revoke-on-terminal-state.
|
||||||
// Closes the URL.revokeObjectURL lifecycle by routing terminal download
|
// Closes the URL.revokeObjectURL lifecycle by routing terminal download
|
||||||
// state transitions (`complete` / `interrupted`) into a REVOKE_DOWNLOAD_URL
|
// state transitions (`complete` / `interrupted`) into a REVOKE_DOWNLOAD_URL
|
||||||
|
|||||||
@@ -130,10 +130,36 @@ export interface UserEvent {
|
|||||||
meta?: Record<string, unknown>;
|
meta?: Record<string, unknown>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Метаданные сессии
|
// Метаданные сессии.
|
||||||
|
//
|
||||||
|
// Phase 2 Plan 02-03 — D-P2-02 + D-P2-03 schema-breaking amendment
|
||||||
|
// (2026-05-20; closes audit P1 #10):
|
||||||
|
//
|
||||||
|
// - `url: string` REPLACED by `urls: string[]`. Captures the operator's
|
||||||
|
// multi-tab context during the rolling 30 s recording window — not
|
||||||
|
// just the active-at-save tab. Fed by the new
|
||||||
|
// `src/background/tab-url-tracker.ts` module via chrome.tabs.onActivated
|
||||||
|
// + chrome.tabs.onUpdated listeners + a SAVE-time
|
||||||
|
// chrome.tabs.query({}) snapshot (DEC-011 Amendment 1 grants the
|
||||||
|
// `tabs` permission). Empty array IS permitted (F2 — whole-desktop-
|
||||||
|
// no-tab session).
|
||||||
|
//
|
||||||
|
// - `schemaVersion: string` ADDED as the 8th field. Value '2' marks the
|
||||||
|
// D-P2-02 url→urls cutover; future schema bumps increment. The 8th
|
||||||
|
// field name was planner-suggested in Plan 02-01 Task 3 and ratified
|
||||||
|
// here as the lockstep for tests/build/strict-meta-json-validation.test.ts
|
||||||
|
// EXPECTED_KEYS.
|
||||||
|
//
|
||||||
|
// Total field count: 8 (per D-P2-03 strict exact-shape rule).
|
||||||
|
//
|
||||||
|
// Field-emission order follows source-declaration order: TypeScript object
|
||||||
|
// literals preserve insertion order, and JSON.stringify emits in insertion
|
||||||
|
// order per ECMA-262 §9.1.11.1 — so the meta.json output mirrors this
|
||||||
|
// interface line-by-line.
|
||||||
export interface SessionMetadata {
|
export interface SessionMetadata {
|
||||||
|
schemaVersion: string;
|
||||||
timestamp: string;
|
timestamp: string;
|
||||||
url: string;
|
urls: string[];
|
||||||
userAgent: string;
|
userAgent: string;
|
||||||
extensionVersion: string;
|
extensionVersion: string;
|
||||||
videoBufferSeconds: number;
|
videoBufferSeconds: number;
|
||||||
|
|||||||
Reference in New Issue
Block a user