Milestone v1 (v2.0.0): Mokosh — Session Capture #1

Merged
strategy155 merged 297 commits from gsd/phase-04-harden-clean-up-optional into main 2026-05-31 15:34:17 +00:00
2 changed files with 82 additions and 3 deletions
Showing only changes of commit 78031e7782 - Show all commits

View File

@@ -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

View File

@@ -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;