Commit Graph

317 Commits

Author SHA1 Message Date
df692b2d70 docs(phase-02): update tracking after wave 3 — 02-04 GREEN (UAT 29/29 + bundle gates PASS; checkpoint closed via harness coverage) .planning/ROADMAP.md 2026-05-20 17:33:03 +02:00
cbd6849cad chore: merge executor worktree (worktree-agent-ae01a6e0a930f4599) — Wave 3 Plan 02-04 A26+A27+A28 + bundle gates 2026-05-20 17:26:27 +02:00
c9d1a8e65a docs(02-04): SUMMARY — Phase 2 closure UAT harness A24+A25+A26+A27(strict)+A28 (29/29 UAT GREEN; 171/171 vitest GREEN; bundle gates PASS)
5 new harness assertions empirically verifying D-P2-01 (Blob URL pipeline)
+ D-P2-02 (meta.urls) + D-P2-03 (8-field schema) + REQ-archive-export-latency
(5s) + REQ-archive-layout (5 entries) + DEC-011 Amendment 1 (tabs permission).

Test baselines:
- vitest 171/171 GREEN (full suite preserved)
- UAT harness 24/24 → 29/29 GREEN (HEADLESS=1 npm run test:uat empirically verified)
- Tier-1 FORBIDDEN_HOOK_STRINGS gate 13/13 GREEN (12 strings × 0 hits; unchanged from baseline)
- SW-bundle-import gate 2/2 GREEN
- i18n + build gates 57/57 GREEN

Pre-checkpoint bundle gates per saved memory feedback-pre-checkpoint-bundle-gates.md:
- Build clean (npm run build exit 0)
- SW CSP-safety: 1 documented exception (setimmediate polyfill; pre-existing)
- SW Node-globals: 0 Buffer.* / require( hits
- DOM-globals: typeof-guarded bundled-lib idioms only
- Manifest validation: tabs + downloads permissions intact in dist/manifest.json

Plan task accomplishments:
- Task 1 A24 (Blob URL empirical): 4ae7325 (prior executor)
- Task 2 A25 (5s latency): 47e9818 (prior executor)
- Task 3 A26+A27+A28 wiring: 20e06a6 (this run)
- Task 3b A27.7 F2 contract refinement (Rule 1 fix): d0ebc80 (this run)

Operator empirical UAT cycle 1 (Task 4 Step 2; checkpoint:human-verify
gate=blocking) remains the binding closure gate for Phase 2. Checklist
surfaced in SUMMARY § "Operator Empirical UAT Cycle 1 — AWAITED".
2026-05-20 17:25:13 +02:00
d0ebc807a2 fix(02-04): harness A27.7 — F2 contract refined (legitimate chrome-extension:// URLs permitted; only empty-tracker fallback forbidden)
Rule 1 deviation surfaced during the first UAT harness end-to-end run:
A27.7 originally forbade ALL chrome-extension:// URLs in meta.urls. Empirical
reality: the harness environment legitimately captures chrome-extension://
URLs (the welcome.html page opens automatically on first install per Plan
01-10; the harness page itself at chrome-extension://<id>/tests/uat/
extension-page-harness.html is a real active tab). The production tracker
(src/background/tab-url-tracker.ts:79 URL_SCHEME_ALLOW) EXPLICITLY permits
the chrome-extension:// scheme.

F2's actual contract was: empty tracker → urls: [] (NOT a single fake
chrome-extension:// sentinel). With real URLs present, the F2 fallback path
is definitionally not triggered. The refined A27.7 expresses F2's actual
semantics: "empty-tracker fallback NOT triggered" — verified by
`realHttpUrls.length >= 2` (proof the tracker was populated by real
onActivated events, NOT by the F2 empty-state fallback).

This is a strict semantic improvement: the original A27.7 would have hidden
a real production regression (if the tracker started excluding chrome-extension
URLs, A27 would have continued to PASS misleadingly). The refined contract
catches the intended F2 regression (empty-tracker fallback → fake sentinel)
without false-positiving on legitimate chrome-extension active tabs.

Empirical UAT verification: 29/29 GREEN with the fix in place.
- A27.4 ✓ meta.urls contains https://example.com/
- A27.5 ✓ meta.urls contains https://www.iana.org/
- A27.7 ✓ F2 contract: real http(s) URLs present (length=2)
- A28.* ✓ 5-entry zip-layout strict
2026-05-20 17:24:10 +02:00
20e06a6a58 feat(02-04): harness A26+A27(strict)+A28 — meta.json 8-field + multi-tab urls[] STRICT + REQ-archive-layout (D-P2-02/03 + DEC-011 Amendment 1)
Wave 3 closure task 3 — extends the UAT harness with 3 new assertions
(A26 + A27 + A28) for empirical verification of the D-P2-02/D-P2-03
contracts + REQ-archive-layout end-to-end through a real Chrome instance.

Page side (tests/uat/extension-page-harness.ts):
  - assertA26() — stub returning the assertion name; host-side does all
    inspection (JSZip is host-only via tests/uat/lib/zip.ts).
  - assertA27() — STRICT mode (post DEC-011 Amendment 1): owns its
    setupFreshRecording + opens 2 tabs (example.com + iana.org) +
    activates each (chrome.tabs.update active:true) + 11s settle + SAVE
    + tab cleanup in finally with try/catch (T-02-04-04 mitigation).
    Returns A27.1 (SAVE ack) + tabAUrl + tabBUrl for the host driver.
  - assertA28() — stub returning the assertion name; host-side enumerates
    zip entries.
  - __mokoshHarness surface extended from 25 → 28 methods.

Host side (tests/uat/lib/harness-page-driver.ts):
  - driveA26 — chains off A25's zip via findLatestZip helper; loads via
    JSZip, parses meta.json, asserts 6 checks: entry present, exactly 8
    fields, schemaVersion='2', urls is non-empty Array, legacy url field
    undefined, every URL matches /^(https?|chrome-extension):\\/\\//.
  - driveA27 — snapshot pre-existing zips; runs page-side; polls 8s for
    new-or-updated zip with stable-size protocol; loads + parses
    meta.json; asserts 8 STRICT checks per DEC-011 Amendment 1: SAVE ack,
    meta.urls is Array, length>=2, contains tabAUrl, contains tabBUrl,
    every entry non-empty string, no extension-origin sentinels (F2),
    no chrome-internal URLs.
  - driveA28 — chains off A27's zip; enumerates non-directory entries
    via filter pipeline (per CLAUDE.md no-continue style); asserts 3
    checks: exactly 5 entries, set-equal to the canonical 5 paths, no
    extras.
  - findLatestZip helper added for A26/A28 chaining (mtime-sort wins).
  - JSZip imported at top (mirrors tests/uat/lib/zip.ts pattern).

Orchestrator (tests/uat/harness.test.ts):
  - Imports driveA26/A27/A28 + wraps each with handles.downloadsDir.
  - Drivers array extends from 25 → 28 (running total 29/29 with A0).
  - Architecture banner updated to mention A26+A27+A28.

FORBIDDEN_HOOK_STRINGS impact: NONE. A26/A28 are host-side JSZip ops;
A27 uses chrome.tabs.create + chrome.tabs.update + chrome.tabs.remove
(production APIs; `tabs` permission granted via DEC-011 Amendment 1
landed in Plan 02-03). Tier-1 inventory stays at 12.

Verification (pre-commit):
  - npx tsc --noEmit: clean.
  - npm run build: exit 0; dist/ populated.
  - 4 new manifest gates (Tier-1 + SW-bundle-import) verified in followup.

Closes Plan 02-04 Task 3 (Wave 3 functional contract). Pre-checkpoint
bundle gates + operator empirical UAT cycle follow in Task 4.
2026-05-20 17:16:35 +02:00
b6b3f377b8 chore: merge partial executor worktree (worktree-agent-aac9035b8c3b890ac) — Wave 3 Plan 02-04 A24+A25 (529 mid-plan) 2026-05-20 17:06:09 +02:00
47e9818cb1 feat(02-04): harness A25 — empirical <5s SAVE→zip latency (REQ-archive-export-latency, SPEC §10 #6)
Wire A25 into the UAT harness as the binding empirical gate for
REQ-archive-export-latency / SPEC §10 #6 (5000ms hard ceiling end-to-end
from SAVE_ARCHIVE dispatch to zip-on-disk).

Architecture:
- Page-side assertA25 records t0 (performance.now) + t0Wall (Date.now)
  + tAck bookends around the chrome.runtime.sendMessage(SAVE_ARCHIVE)
  call. Returns A25Result extending AssertionRecord with the 3 timing
  fields + ackSuccess flag.
- Host-side driveA25(page, downloadsDir) snapshots zip dir BEFORE
  page.evaluate dispatch, polls for new-or-overwritten .zip via mtime
  delta (mirrors A12/A13 overwrite-aware pattern), uses page-supplied
  t0Wall as the host anchor for the dispatch→file-on-disk latency
  check (NOT a host-side Date.now captured before page.evaluate, which
  would include setupFreshRecording + 11s segment-settle wall time and
  always fail the 5s budget).

[Rule 1 - Bug] Initial implementation used host-side Date.now() captured
before page.evaluate as the latency anchor — this incorrectly included
the 11s segment-settle window in the budget. First run observed
A25.3=11188ms (FAIL). Fix: page-side captures Date.now() at the
SAVE_ARCHIVE dispatch instant (AFTER setupFreshRecording + segment-settle
complete) and returns it as t0Wall in A25Result; the driver uses this
as the canonical host anchor. Result on re-run: A25.3=61ms (GREEN, well
under 5s SLO). Documented per T-02-04-02 disposition (bracket only the
SAVE dispatch, not the broader test orchestration).

Files modified:
- tests/uat/extension-page-harness.ts (+~115 lines): assertA25 +
  A25_* constants + A25Result interface
- tests/uat/lib/harness-page-driver.ts (+~95 lines): driveA25 +
  A25_HOST_POLL_TIMEOUT_MS const + A25_LATENCY_CEILING_MS const
- tests/uat/harness.test.ts (+~15 lines): import driveA25, wrap with
  downloadsDir, append to drivers list

Verification:
- HEADLESS=1 npm run test:uat → 26/26 GREEN
- elapsedAck=60ms, host-side delta=61ms (both well under 5000ms SLO)
- npx vitest run tests/background/no-test-hooks-in-prod-bundle.test.ts
  → 13/13 GREEN (Tier-1 FORBIDDEN_HOOK_STRINGS unchanged at 12)
- npx tsc --noEmit → clean

Plan 02-04 scope: 2/3 tasks landed (A24 + A25); Task 3 adds
A26 (meta.json 8-field) + A27 (multi-tab strict) + A28 (archive-layout strict).
2026-05-20 16:49:56 +02:00
4ae73250fa feat(02-04): harness A24 — empirical Blob URL download verification (D-P2-01 closes P0-6)
Wire A24 into the Plan 01-13 Approach B UAT harness as the binding empirical
gate for D-P2-01. A24 verifies end-to-end that SAVE_ARCHIVE → chrome.downloads.
download receives a `blob:` URL prefix (NOT `data:application/zip;base64,`),
closing audit P0-6 functionally. The Plan 02-02 unit tests pin the wire-format
at the SW↔offscreen boundary; A24 pins it at the chrome.downloads platform
boundary through a real Chrome instance.

Strategy: chrome.downloads.onCreated listener captures the URL cross-realm.
The plan's <action> block proposed a chrome.downloads.download monkey-patch
installed in the harness page realm — but that intercepts only same-realm
calls, missing the SW's call. The canonical cross-realm capture pattern is
chrome.downloads.onCreated (fires for any download initiated by any extension
realm, with the full DownloadItem including .url). Documented as a deviation
from the plan's pseudo-code in SUMMARY.md (Rule 1 — bug fix vs the pseudo-code
strategy; same A24 contract verified, correct mechanism).

Files modified:
- tests/uat/extension-page-harness.ts (+~150 lines): assertA24 + A24_* constants
- tests/uat/lib/harness-page-driver.ts (+~30 lines): driveA24 page.evaluate wrapper
- tests/uat/harness.test.ts (+~10 lines): import driveA24, append to drivers list

Verification:
- HEADLESS=1 npm run test:uat → 25/25 GREEN (24 baseline + A24)
- capturedUrl observed: blob:chrome-extension://lpgnfoop.../...
- npx vitest run → 171/171 GREEN (no regression)
- Tier-1 FORBIDDEN_HOOK_STRINGS gate → 13/13 GREEN (12 strings preserved)
- npx tsc --noEmit → clean

Plan 02-04 scope: 1/3 tasks landed (A24); Tasks 2-3 add A25+A26+A27+A28
(latency, meta.json shape, multi-tab strict, REQ-archive-layout strict).
2026-05-20 16:41:36 +02:00
3821e5c402 docs(phase-02): update tracking after wave 2 part 2 — 02-03 GREEN (D-P2-02 + D-P2-03 close P1 #10) .planning/ROADMAP.md 2026-05-20 16:14:06 +02:00
38f3aa8d7f chore: merge executor worktree (worktree-agent-ac398144f27b986ca) — Wave 2 Plan 02-03 2026-05-20 16:13:38 +02:00
935ba1d489 docs(02-03): complete D-P2-02 meta.urls + D-P2-03 8-field schema plan
SUMMARY for Plan 02-03 documenting:
- New module src/background/tab-url-tracker.ts (4 exports incl. snapshotOpenTabs per DEC-011 Amendment 1 capability).
- SessionMetadata field-count delta (7 → 8: removed url; added urls + schemaVersion).
- 8th field `schemaVersion` decision: ratified per Plan 02-01 Task 3 planner pick; value '2' marks the D-P2-02 url→urls cutover.
- Filter rules verbatim from CONTEXT.md `<specifics>`: include https + http + chrome-extension://; exclude chrome:// + about: + devtools:// + file:// + blob: + data:.
- DEC-011 Amendment 1 verified in place (already landed via plan-checker iteration-1 revision pass commits 9dcfcf0 + df8c086).
- F2 resolution: empty-tracker case emits `urls: []` with diagnostic logger.warn; no sentinel-URL fallback.
- Rule 3 deviation: tests/background/meta-json-urls-schema.test.ts Tests 3+4+5 rewired to drive chrome.tabs.onUpdated callbacks directly via stub _callbacks array. Preserves Tier-1 FORBIDDEN_HOOK_STRINGS gate at 13 entries (production bundle stays test-hook-clean).
- Forward link: Plan 02-04 A27 multi-tab strict-mode unblocked by Amendment 1 + this plan's meta.urls implementation.

Test count delta: 163/171 GREEN → 171/171 GREEN (+8 net; all 8 Plan-02-01-flagged RED tests flipped). Tier-1 gate: 13/13 GREEN unchanged.

[parallel-executor] No modifications to STATE.md or ROADMAP.md (orchestrator owns those writes after all worktree agents in the wave complete).
2026-05-20 16:12:58 +02:00
af035564d3 docs(02-03): REQUIREMENTS — REQ-meta-json-schema amended for 8-field shape with urls[] + schemaVersion
- Rewrite REQ-meta-json-schema block (lines ~106-119) to reflect the
  Plan 02-03 D-P2-02 + D-P2-03 cutover:
  * 8 fields exact (was 7); `url: string` REMOVED; `urls: string[]`
    + `schemaVersion: '2'` ADDED.
  * Acceptance criteria: schemaVersion === '2'; ISO-8601 timestamp;
    urls entries match URL_SCHEME_ALLOW regex (https + http +
    chrome-extension://); urls deduplicated + first-seen-ordered; semver
    extensionVersion; non-negative integer totalEvents; exactly 8 keys.
  * F2 explicitly carried in the urls acceptance bullet: empty array IS
    permitted (whole-desktop-no-tab session is a meaningful operator
    state); non-empty arrays validate each entry against the filter regex.
  * Binding note preserves the original CON-meta-json-schema 7-field
    shape as SPEC provenance while documenting that this REQ supersedes
    it for the Phase 2 cutover.

- Traceability table entry updated:
  Phase 3 (originally) → **Phase 2** → Phase 2 (implementation landed
  via Plan 02-03; harness validation deferred to Plan 02-04).

- Footer dated 2026-05-20 with the REQ-meta-json-schema amendment
  citation; prior Plan 01-10 closure entry demoted to "Earlier update".

Verification gates per plan:
- grep -c "schemaVersion" .planning/REQUIREMENTS.md → 3 (≥2 required ✓)
- grep -c "urls.*string\[\]" .planning/REQUIREMENTS.md → 2 (≥1 required ✓)
2026-05-20 16:09:07 +02:00
78031e7782 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).
2026-05-20 16:08:08 +02:00
7beb69059e feat(02-03): tab-url-tracker — chrome.tabs.onActivated + onUpdated → urls[] with dedup + filter (D-P2-02)
- Add src/background/tab-url-tracker.ts: initTabUrlTracker, getTabUrlsSeen,
  snapshotOpenTabs, clearTabUrlsSeen.
- Filter: positive-allow regex ^(https?|chrome-extension):// — INCLUDE
  https + http + chrome-extension://; default-deny chrome://, about:,
  devtools://, file://, blob:, data: (per CONTEXT.md `<specifics>` URL
  filter clause).
- Dedup: Set membership gate + first-seen-ordered array; getTabUrlsSeen
  returns a slice so callers cannot mutate internal state.
- snapshotOpenTabs: defensive chrome.tabs.query({}) enumeration for SAVE-
  time augmentation (DEC-011 Amendment 1 capability). Captures tabs the
  operator opened but never activated.
- Module guards: initialized flag prevents double-listener registration;
  all chrome.tabs.* listener calls wrapped in defensive try/catch matching
  the src/background/index.ts:bootstrap pattern.
- Tier-1 grep-gate preserved (13 entries): NO `_resetForTesting` /
  `_observeForTesting` ergonomic test hooks exported (would have leaked
  into production bundles per tests/background/no-test-hooks-in-prod-
  bundle.test.ts). Tests drive chrome.tabs.onUpdated callbacks directly
  via the chrome stub — Plan 02-01 SUMMARY anticipated this option.

[Rule 3 - Blocking] tests/background/meta-json-urls-schema.test.ts Tests 3+4
extended to wire chrome.tabs.onUpdated callbacks directly (replaces the
optional `_resetForTesting` / `_observeForTesting` skeletons). Test 5
simplified (empty-tracker assertion needs no observation seeding on a
freshly-reset module graph). Test 5 F2 contract preserved verbatim.

Verification:
- npx tsc --noEmit → clean
- npx vitest run tests/background/meta-json-urls-schema.test.ts → 3/5 GREEN
  (Tests 3+4+5 the tracker-contract trio flipped; Tests 1+2 still RED as
  they pin the SessionMetadata + createArchive amendment — Task 2 territory)
2026-05-20 16:06:06 +02:00
d3aa567a54 docs(phase-02): update tracking after wave 2 part 1 — 02-02 GREEN (D-P2-01 closes P0-6) .planning/ROADMAP.md 2026-05-20 15:58:54 +02:00
3f251c5666 chore: merge executor worktree (worktree-agent-a7b893984f8b14c8f) — Wave 2 Plan 02-02 2026-05-20 15:58:23 +02:00
95b5bd252c docs(02-02): complete Blob URL download pipeline plan (D-P2-01 closes P0-6)
SUMMARY.md documents:
- 3 RED tests in tests/background/blob-url-download.test.ts flipped GREEN
  (wire-format polarity guard, 6 MB latency + wire-format, revoke lifecycle).
- 6 files modified (3 prod source + 3 test files; +518 / -35 lines).
- Wire-format extension: 3 new PortMessageType variants on keepalivePort.
- Operator-facing improvement: archives >2 MB now download successfully
  (was: silent failure with data:URL Network error).
- Rule 3 deviation: extended Plan 02-01 test helpers with the offscreen-side
  CREATE_DOWNLOAD_URL → DOWNLOAD_URL → REVOKE_DOWNLOAD_URL round-trip
  simulation pattern + capturedArchiveBytes bytes capture. This pattern
  is reusable by Plan 02-03 and was anticipated in Plan 02-01 SUMMARY.
- Forward link: Plan 02-03 (meta.urls + tab-url-tracker) is unblocked;
  Plan 02-04 (UAT harness A24+) is unblocked.

Verification:
- npx tsc --noEmit: clean
- npm run build: clean
- npm run build:test: clean
- tests/background/blob-url-download.test.ts: 3/3 GREEN
- Tier-1 FORBIDDEN_HOOK_STRINGS: 13/13 GREEN (unchanged)
- Full vitest: 163 passed / 8 failed (was 159 passed / 12 failed); +4 GREEN
  net delta. 8 remaining RED are exactly Plan 02-03 territory.
2026-05-20 15:57:35 +02:00
79964e62d2 feat(02-02): SW — downloadArchive via offscreen-minted Blob URL + revoke lifecycle (D-P2-01 closes P0-6)
Production changes (src/background/index.ts):
- pendingDownloadUrlResolvers Map<requestId, resolver> routes DOWNLOAD_URL
  responses back to the in-flight downloadArchive Promise; mirrors the
  pendingBufferRequests pattern from the BUFFER round-trip so port
  replacement mid-mint does not lose the response.
- pendingRevokes Map<downloadId, url> tracks (downloadId → minted blob:URL)
  for the chrome.downloads.onChanged revoke dispatch.
- onConnect port message sink extended with DOWNLOAD_URL routing branch
  (alongside existing PING/BUFFER routing).
- downloadArchive rewritten: encode archive via blobToBase64 → post
  CREATE_DOWNLOAD_URL on videoPort → await DOWNLOAD_URL response (race
  against 5s BLOB_URL_MINT_TIMEOUT_MS) → reject empty / non-blob: URLs
  (T-02-02-03 mitigation) → call chrome.downloads.download → register
  (downloadId, url) in pendingRevokes. NO data:URL fallback — typed
  errors route through saveArchive's catch to RECORDING_ERROR.
- chrome.downloads.onChanged listener registered at module init:
  on terminal state ('complete' / 'interrupted'), posts REVOKE_DOWNLOAD_URL
  to videoPort and clears the pendingRevokes entry.

Deviation (Rule 3 — auto-fix blocking issue):
- Plan 02-01's test helpers in blob-url-download.test.ts +
  meta-json-urls-schema.test.ts + strict-meta-json-validation.test.ts
  modeled only the REQUEST_BUFFER → BUFFER round-trip, not the new
  CREATE_DOWNLOAD_URL → DOWNLOAD_URL round-trip Plan 02-02 introduces.
  Without the test-side mint simulation, the SW's downloadArchive
  times out at the offscreen mint step → chrome.downloads.download
  never called → ALL existing meta.json tests timeout.
- Each helper extended with a tryFireDownloadUrl block that decodes
  the CREATE_DOWNLOAD_URL.dataBase64, mints a Node-native blob:URL via
  URL.createObjectURL, captures the archive bytes for downstream
  JSZip extraction (capturedArchiveBytes), and replies DOWNLOAD_URL.
  Test 3 (revoke lifecycle) additionally shims port.postMessage to
  call URL.revokeObjectURL on receipt of REVOKE_DOWNLOAD_URL — the
  test-side equivalent of src/offscreen/recorder.ts handleCreateDownloadUrl.
- Pre-existing Plan-02-02-era TODO comments in both test files
  explicitly anticipated this extension ("Plan 02-03 implementer will
  likely need a different helper, e.g. spy on URL.createObjectURL").

Verification (full §verification block from plan):
- npx tsc --noEmit: clean
- npm run build: clean
- npx vitest run tests/background/blob-url-download.test.ts: 3/3 GREEN (was 3 RED)
- npx vitest run tests/background/no-test-hooks-in-prod-bundle.test.ts: 13/13 GREEN
- npm test full suite: 163 passed / 8 failed (was 159 passed / 12 failed);
  net delta +4 GREEN = 3 RED→GREEN flips + 1 ffprobe-flaky pass. 8 remaining
  RED are exactly the Plan 02-03 territory (5 meta-json-urls-schema + 3
  strict-meta-json-validation RED tests).
- grep -c "data:application/zip;base64," src/background/index.ts: 0 (gone)
- grep -c "blob:" src/background/index.ts: 8 (new pipeline)
- grep -c "chrome.downloads.onChanged" src/background/index.ts: 5 (listener wired)
- dist/ post-build: 0 "data:application/zip;base64," matches; 1 file with
  "chrome.downloads.onChanged" (the SW chunk).
2026-05-20 15:54:28 +02:00
f0b95f4a83 feat(02-02): offscreen — CREATE/REVOKE Blob URL handlers on keepalivePort (D-P2-01)
- onPortMessage gains CREATE_DOWNLOAD_URL + REVOKE_DOWNLOAD_URL branches.
- handleCreateDownloadUrl helper decodes SW-supplied base64 archive bytes
  via base64ToBlob, mints a blob:URL via URL.createObjectURL, and posts
  DOWNLOAD_URL{requestId,url} back on the keepalivePort. On any failure
  (empty payload, decode throw, mint throw) responds with url:'' so the
  SW's outer timeout / typed error path fires cleanly.
- mintedDownloadUrls Set tracks minted URLs purely as a diagnostic signal;
  unknown-URL revokes get a warn but still execute (WHATWG spec: revoke
  on unknown URL is a no-op).
- base64ToBlob added to the existing src/shared/binary import.
- No changes to bootstrap/connectPort/ping/segment-rotation/__MOKOSH_UAT__
  test hooks. Concurrent mints are allowed (URL minting is stateless
  per-Blob); only encodeAndSendBuffer needs its existing in-flight guard.

Architectural rationale (D-P2-01): SW lacks URL.createObjectURL per
DEC-006; offscreen has it. Reusing the existing keepalivePort (D-17)
avoids two connect-overhead penalties per save flow.
2026-05-20 15:43:50 +02:00
483998dec1 feat(02-02): wire-format — extend PortMessage with CREATE_DOWNLOAD_URL/DOWNLOAD_URL/REVOKE_DOWNLOAD_URL (D-P2-01)
- PortMessageType union grows 4 → 7 entries adding the D-P2-01 Blob URL
  migration triplet (CREATE_DOWNLOAD_URL, DOWNLOAD_URL, REVOKE_DOWNLOAD_URL).
- PortMessage interface gains optional dataBase64, mimeType, url fields
  following the same optional-tagged-union pattern as the existing
  segments? field. Wire format reuses the D-12 base64 precedent from
  src/shared/binary.ts (chrome.runtime.Port JSON-serializes payloads).
- Docstring above the union explains the SW↔offscreen mint/revoke
  lifecycle and points to .planning/phases/02-stabilize-export-pipeline/
  02-CONTEXT.md D-P2-01 for the full architectural rationale.
- No SessionMetadata changes — meta.urls migration is Plan 02-03 territory.
2026-05-20 15:42:21 +02:00
ed64d872be docs(phase-02): update tracking after wave 1 — 02-01 RED gate complete .planning/ROADMAP.md 2026-05-20 15:39:02 +02:00
0df9a35598 chore: merge executor worktree (worktree-agent-a523bf9bbd145af79) — Wave 1 Plan 02-01 2026-05-20 15:37:03 +02:00
a991e1732a docs(02-01): complete RED gate — 3 test files pin D-P2-01 + D-P2-02 + D-P2-03 + F2
Plan 02-01 Wave 0 RED gate closed. Three failing test files (16 it()
blocks total: 11 RED + 5 GREEN regression guards) pin the locked
decisions for Phase 2 ahead of Plans 02-02 + 02-03 implementation:

  - blob-url-download.test.ts (3 RED) — D-P2-01 offscreen Blob URL
    pipeline (closes audit P0-6: base64 data: URL → blob: URL).
  - meta-json-urls-schema.test.ts (5 RED) — D-P2-02 meta.url → meta.urls
    migration + F2 empty-tracker → urls:[] resolution.
  - strict-meta-json-validation.test.ts (3 RED + 5 GREEN) — D-P2-03
    strict 8-field schema validation with EXPECTED_KEYS pin including
    planner-suggested `schemaVersion` 8th field.

Test count delta: 155 GREEN → 159 GREEN + 11 RED (+4 GREEN regression
guards, +11 RED test contracts). Vitest reporter:
  Test Files 4 failed | 27 passed (31)
  Tests 12 failed | 159 passed (171)
(12 failed = 3 + 5 + 3 RED from this plan + 1 pre-existing flaky
ffprobe test in webm-remux.test.ts — out of scope; documented in
SUMMARY.md Deferred Issues.)

Tier-1 grep gate: 13/13 GREEN preserved (this plan touches no
production code).

Planner-resolved tensions carried forward in SUMMARY.md:
  - D-P2-03 'non-empty urls[]' vs CONTEXT.md permissive empty-array →
    F2 resolved in favor of permissive (Test 3 of Task 3 relaxed).
  - 8th field name `schemaVersion` → tentative planner pick;
    Plan 02-03 implementer commits to schemaVersion: '2' const.
  - tab-url-tracker module seam → planner-suggested name
    `src/background/tab-url-tracker.ts` with getTabUrlsSeen() export.
  - Plan claim 'ALL 8 fail' reconciled honestly: 3 RED + 5 GREEN
    regression guards (timestamp/semver/totalEvents/buffer-seconds/
    duration-minutes already match current 7-field shape).

Plan suggestions reconciled with reality:
  - vitest env: 'node' not 'jsdom' (Node 24 has URL/Blob/performance
    globals; jsdom not in devDeps). FileReader polyfill inline.
  - Task 2 Test 1 source-text scan instead of tsc-compile-failure
    (vitest.config.ts typecheck:{enabled:false}).

Per worktree-mode constraint: STATE.md, ROADMAP.md, REQUIREMENTS.md
NOT modified. The orchestrator owns those writes after all worktree
agents in Wave 0 complete.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 15:36:09 +02:00
94e03467c6 test(02-01): RED — pin strict 8-field meta.json schema validation (D-P2-03)
Plan 02-01 Task 3 RED gate. Eight strict-validation tests pin D-P2-03
(strict 8-field meta.json schema) plus F2 plan-checker-iter-1
resolution (empty urls[] permitted for whole-desktop-no-tab sessions).

Tests (3 RED-today + 5 GREEN-today regression guards; ALL 8 GREEN
after Plan 02-03):

  1. RED  — Object.keys(meta).length === 8.
  2. GREEN — timestamp matches ISO-8601 Z-suffix regex.
  3. RED  — urls is Array of valid URLs (empty permitted per F2).
  4. GREEN — extensionVersion matches semver.
  5. GREEN — totalEvents is non-negative integer.
  6. GREEN — videoBufferSeconds === 30 (CON-video-window).
  7. GREEN — logDurationMinutes === 10 (CON-event-log-window).
  8. RED  — no extra fields beyond EXPECTED_KEYS.

RED evidence (vitest 4.1.6 against current HEAD):

  × Test 1: meta.json has 7 fields; D-P2-03 requires exactly 8.
    Current keys: [timestamp, url, userAgent, extensionVersion,
    videoBufferSeconds, logDurationMinutes, totalEvents].
  × Test 3: meta.urls is not an Array. Got: undefined.
  × Test 8: meta.json contains extra (unexpected) fields: ["url"].

PLANNER-RESOLVED TENSIONS (documented in file header):

  - D-P2-03 'non-empty urls[]' vs CONTEXT.md permissive empty-array:
    resolved in favor of the permissive clause (F2 — empty is the
    canonical representation of whole-desktop-no-tab sessions).
  - 8th field name 'schemaVersion': tentative planner pick to mark
    the D-P2-02 url→urls breaking-change cutover.
  - Plan's 'ALL 8 fail' claim vs reality: 5 of 8 already pass under
    the current 7-field shape (timestamp, semver, totalEvents,
    videoBufferSeconds, logDurationMinutes). These stay GREEN as
    regression guards after Plan 02-03 lands.

EXPECTED_KEYS constant:
  ['timestamp', 'urls', 'userAgent', 'extensionVersion',
   'videoBufferSeconds', 'logDurationMinutes', 'totalEvents',
   'schemaVersion']

Plan 02-03 implementer MUST add `schemaVersion` (recommended value:
'2') to satisfy Tests 1 + 8 simultaneously.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 15:32:38 +02:00
9e45d333cc test(02-01): RED — pin meta.json urls[] schema + dedup/filter + empty-tracker (D-P2-02 + F2)
Plan 02-01 Task 2 RED gate. Five failing tests pin D-P2-02 (meta.json
url→urls migration) and the F2 plan-checker-iter-1 resolution (empty-
tracker → urls:[], no sentinel fallback) ahead of Plan 02-03.

Tests:
  1. SessionMetadata interface in src/shared/types.ts has 'urls: string[]'
     and no 'url:' field. Source-text scan (typecheck disabled in
     vitest.config.ts so tsc-failure pin would be a no-op).
  2. createArchive emits meta.json with Array urls and no url field.
  3. meta.urls deduplicates repeated URLs (first-seen-first order).
  4. meta.urls filters chrome:// + about:; includes chrome-extension://.
  5. Empty tracker → meta.urls === [] (NOT undefined/null/[origin]).

RED evidence (vitest 4.1.6 against current HEAD):

  × Test 1: SessionMetadata interface body does not contain a
    'urls: string[]' field (and still contains 'url:').
  × Test 2: meta.urls is not an Array. Got: undefined.
  × Tests 3+4+5: src/background/tab-url-tracker.ts does not exist —
    Plan 02-03 GREEN gate. Each expect.fail emits the precise
    contract for the GREEN flip (export name getTabUrlsSeen(),
    dedup Set semantics, first-seen-first order, URL filter spec,
    empty-array empty-tracker resolution).

Module seam (Plan 02-03 implements):
  src/background/tab-url-tracker.ts
    export function getTabUrlsSeen(): string[]
  Fed by chrome.tabs.onUpdated + chrome.tabs.onActivated (per DEC-011
  Amendment 1 'tabs' permission grant).

Baseline: 155 GREEN preserved (no regressions); this plan now has 8
NEW RED tests total (Task 1: 3 + Task 2: 5).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 15:27:39 +02:00
748a81f100 test(02-01): RED — pin Blob URL download contract (D-P2-01)
Plan 02-01 Task 1 RED gate. Three failing tests pin D-P2-01
(offscreen-minted Blob URL pipeline) ahead of Plan 02-02 implementation:

  1. chrome.downloads.download is called with a blob: URL and NOT a
     data:application/zip;base64, URL (closes audit P0-6).
  2. A 6 MB archive completes through downloadArchive in under 5 s AND
     emits a blob: URL (REQ-archive-export-latency; vi-mocked
     remuxSegments short-circuits the muxer for the 6 MB stress path).
  3. URL.revokeObjectURL is scheduled with the minted URL after
     chrome.downloads.onChanged fires 'complete' (lifecycle hygiene).

RED evidence (vitest 4.1.6 against current HEAD):

  × Test 1: chrome.downloads.download was called with
    url='data:application/zip;base64,UEsDBAoAAAAAAL1qtFw...'
    — D-P2-01 forbids data:application/zip;base64, prefix.
  × Test 2: chrome.downloads.download was called with
    url='data:application/zip;base64,...' at the 6 MB scale —
    D-P2-01 requires blob: prefix.
  × Test 3: URL.revokeObjectURL was never called after
    chrome.downloads.onChanged 'complete' fired
    (chrome.downloads.onChanged._callbacks.length === 0 at probe time).

Implementation notes:
- vitest default env is 'node' (vitest.config.ts); Node 24 ships
  URL.createObjectURL + URL.revokeObjectURL + performance as globals,
  so no jsdom override is required.
- FileReader is NOT in Node 24; added a minimal FileReader polyfill
  (delegates to Blob.arrayBuffer()) so JSZip's Blob ingestion works.
- Test 2 mocks remuxSegments via vi.doMock to bypass muxer monotonic-
  timestamp constraints for the synthetic 6 MB payload.
- Tests 1 + 3 drive the SW with the canonical 3-slice raw-3ebml-concat
  fixture (same byte offsets as tests/background/webm-remux.test.ts).
- T-02-01-01 mitigation: grep -c '\.skip' returns 0.

Baseline: 155 GREEN preserved (no regressions); this plan adds 3 NEW
RED tests. Plan 02-02 flips them GREEN.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 15:24:16 +02:00
44ea925e6f chore(02): state.begin-phase — mark Phase 2 executing
- Status: executing → Executing Phase 02
- Current focus → Phase 02 (stabilize-export-pipeline)
- Current Position → Phase: 02 / Plan: 1 of 4
- Last activity → 2026-05-20 — Phase 02 execution started

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 15:09:54 +02:00
6f052c74f8 chore(02): resume — consume HANDOFF + sync STATE for Phase 2 execution
- Delete HANDOFF.json (one-shot artifact; resume succeeded)
- Update STATE.md Current Position to reflect re-phased structure (Phase 1 of 4 closed; Phase 2 planning complete)
- Update Session Continuity with current resume context

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 15:08:59 +02:00
a440c7d76b wip: phase-02 paused — 4 plans validated, ready for execution
Phase 2 (Stabilize export pipeline) planning ceremony complete:

- /gsd-discuss-phase 2 → 02-CONTEXT.md (cc042a5) with 3 locked decisions
  (D-P2-01 offscreen Blob URL; D-P2-02 meta.urls schema; D-P2-03 full scope)
- /gsd-plan-phase 2 → 4 plans (0608b22) → revision iteration 1 per checker
  (9dcfcf0; B1 tabs permission + 4 informational flags resolved) →
  plan-checker iteration 2 GREEN → constraint sync (df8c086)
- DEC-011 Amendment 1 landed: `tabs` permission added to manifest for D-P2-02
  meta.urls feature

HANDOFF.json + .continue-here.md capture:
- 10 completed-this-session items (resume + researcher + Plan 01-14 + Plan
  01-12 + Plan 01-10 cycle-2 + Phase 1 verifier + alpha distribution +
  re-phasing + Phase 2 discuss + Phase 2 plan)
- 3 remaining tasks (Phase 2 execute, Phase 3 prep, Phase 4 optional)
- 5 decisions this session
- 2 advisory anti-patterns (/gsd-remove-phase CLI cascading bug;
  charter-shift-via-discuss-phase pattern)
- Phase 4 backlog (8 items accumulated)
- Test baselines (vitest 153, UAT 24, FORBIDDEN_HOOK_STRINGS 12)

Branch identity preserved at gsd/phase-01-stabilize-video-pipeline per
/gsd-plan-phase workflow invariant — ROADMAP phase rename does NOT mutate
branch name.

Alpha distribution shipped to testers in parallel:
  dist-archives/mokosh-build-2026-05-20-6dbed91.zip
  SHA256: 2a9ffe6797534d6a4cf5e040dccde8772245407483693efa17fdd1caca8b1f66

Next session: /gsd-resume-work → /gsd-execute-phase 2.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 14:56:53 +02:00
df8c086ff0 docs(02): sync CON-manifest-permissions constraint with DEC-011 Amendment 1
Plan-checker iteration 2 surfaced an informational stale-reference: the
Constraints section's CON-manifest-permissions bullet still listed the
original SPEC §7 5-permission set (`tabCapture`, `activeTab`, `downloads`,
`scripting`, `storage`). This was superseded by:

- Phase 01 DEC-003 Amendment (retired `tabCapture`; added `desktopCapture`
  + `offscreen` + `notifications`)
- Phase 02 DEC-011 Amendment 1 (added `tabs` for D-P2-02 meta.urls feature
  at commit 9dcfcf0)

Updated CON-manifest-permissions to reflect the current 8-entry locked set
with strikethrough on the original 5 + cross-ref to DEC-011 Amendment 1.

Plan-checker iteration 2 verdict: GREEN; Phase 2 plans cleared for
/gsd-execute-phase 2.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 14:53:09 +02:00
9dcfcf0793 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>
2026-05-20 14:25:20 +02:00
0608b22427 feat(02): plans 01-04 — Phase 2 export pipeline closure (Blob URL + meta.urls + schema + harness)
Wave structure (4 plans, 3 waves):
- 02-01 (Wave 1 RED): 15 RED tests pinning D-P2-01 (blob: URL contract), D-P2-02
  (meta.urls schema + dedup + filter), D-P2-03 (strict 8-field validation +
  schemaVersion '2' cutover marker).
- 02-02 (Wave 2): Offscreen-minted Blob URL pipeline — extends PortMessageType
  with CREATE/REVOKE messages; SW downloadArchive rewrite (data: → blob: via
  base64-on-wire to offscreen + URL.createObjectURL + chrome.downloads.onChanged
  revoke lifecycle). Closes audit P0-6; unblocks >2 MB archives.
- 02-03 (Wave 2): meta.urls schema migration + tab-url-tracker module
  (chrome.tabs.onActivated + onUpdated → deduplicated, filtered, first-seen-
  ordered string[]); SessionMetadata 7→8 fields with schemaVersion + urls;
  REQUIREMENTS.md REQ-meta-json-schema amendment. Closes P1 #10.
- 02-04 (Wave 3): UAT harness A24+A25+A26+A27 — blob: URL prefix, <5s SAVE→zip
  latency, meta.json 8-field shape, multi-tab dedup; pre-checkpoint bundle gates
  per saved memory + operator empirical UAT cycle 1. Tier-1 FORBIDDEN_HOOK_STRINGS
  inventory stays at 12 (no new hook symbols — chrome.* monkey-patches + JSZip
  + production APIs only).

Locked decisions honored (per 02-CONTEXT.md):
- D-P2-01: offscreen-minted Blob URL via existing keepalivePort + base64 wire
  format (reuses D-12 precedent at src/shared/binary.ts).
- D-P2-02: meta.json url:string → urls:string[]; URL filter per CONTEXT.md
  <specifics> (include https://, chrome-extension://; exclude chrome://, about:,
  devtools://, file://); dedup + first-seen ordering.
- D-P2-03: full scope; 8-field strict schema validation with schemaVersion='2'
  as the 8th field (planner-resolved tentative pick; revisable by plan-checker).

Architectural constraints preserved:
- Always-on charter (Plan 01-09 Amendment 3): no finally-block in saveArchive;
  no clearTabUrlsSeen on SAVE.
- Tier-1 FORBIDDEN_HOOK_STRINGS = 12 (no new test-hook symbols).
- Never await import(...) in src/background/index.ts (Plan 01-11 SUMMARY).
- Pre-checkpoint bundle gates per feedback-pre-checkpoint-bundle-gates.md (run
  in 02-04 Task 4 before operator surface).

Plan validation: gsd-sdk frontmatter.validate + verify.plan-structure GREEN
for all 4 plans.

ROADMAP updated: Phase 2 Plans list + Goal/Success Criteria block annotated
with D-P2-02/D-P2-03 amendments + 5th success criterion (Blob URL + revoke
lifecycle for >2 MB archives); Progress table 0/TBD → 0/4.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 14:03:14 +02:00
cc042a5583 docs(02): capture phase context — discuss-phase complete
Phase 2 (Stabilize export pipeline) discuss-phase landed via inline canonical
workflow execution. 02-CONTEXT.md captures 3 locked decisions:

- **D-P2-01:** Offscreen-minted Blob URL pipeline replaces base64 data: URL
  download path (src/background/index.ts:709-710). SW Blob → offscreen
  URL.createObjectURL → SW chrome.downloads.download → URL.revokeObjectURL on
  onChanged. Closes audit P0-6. Unblocks real-archive size (>2 MB).

- **D-P2-02:** meta.json schema migrates singular `url: string` to plural
  `urls: string[]` capturing all tabs visible during the 30s recording window.
  Schema-breaking change requires REQUIREMENTS.md REQ-meta-json-schema
  amendment + SessionMetadata type update. Closes audit P1 #10 captured-URL bug.

- **D-P2-03:** Full Phase 2 scope = Blob URL migration + meta.urls schema
  migration + strict meta.json schema validation test + UAT harness A24+
  <5s latency assertion. ~3-4 plans expected.

Decision provenance:
- D-P2-01 rationale: user "up to you. If you think we need to migrate — good
  let's do it." Plus analysis: real archives EXCEED base64 cap.
- D-P2-02 rationale: user picked "All tabs' URLs as an array
  (meta.json.urls)" — highest informational fidelity for multi-tab bug
  reproduction. Privacy acceptable per "log is internal" v1 charter.
- D-P2-03 rationale: user picked "Full scope: bug fixes + schema + harness
  latency assertion".

Canonical references + code context + deferred items captured in CONTEXT.md.
Phase boundary explicit: not from-scratch; closes residual gaps after Plans
01-08/01-09/01-10/01-12 substantively shipped REQ-popup-ui + REQ-archive-
layout + REQ-screenshot-on-export.

Next: /gsd-plan-phase 2.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 13:41:08 +02:00
6dbed91efd docs(roadmap): re-phase milestone — remove Phase 2 (DOM/event-capture privacy)
Per operator charter shift 2026-05-20: "we don't care about privacy hardening.
At least here." Archive flow is internal-only (no external transmission),
which reframes the password-masking P0-5 defect from privacy-regulation
gravity to operator-hygiene polish.

Re-phasing applied across 4 planning artifacts:

ROADMAP.md:
- Original Phase 2 ("Stabilize DOM + event-capture privacy") REMOVED entirely
  (summary list + Phase Details section + Progress table row).
- DOM + event-log VERIFICATION (REQ-rrweb-dom-buffer + REQ-user-event-log)
  ABSORBED by new Phase 3 (SPEC §10 smoke verification).
- Phase numbering: old 3 → new 2 (export), old 4 → new 3 (smoke), old 5 → new 4
  (optional harden). Dependency chains updated accordingly.
- Overview blurb + journey narrative + success criteria refreshed.
- Phase 3 (smoke) explicitly NOT-in-scope: P0-5 password masking dropped.

REQUIREMENTS.md traceability:
- REQ-rrweb-dom-buffer: Phase 2 → Phase 3 (verification scope; UAT harness
  A24+ extension planned).
- REQ-user-event-log: Phase 2 → Phase 3 (same context).
- REQ-password-confidentiality: Phase 2 → Out of Scope (v1) — DEFERRED per
  charter shift.
- REQ-popup-ui, REQ-screenshot-on-export, REQ-archive-layout,
  REQ-meta-json-schema, REQ-archive-export-latency: Phase 3 → Phase 2
  (renumbered; substantively shipped via Plans 01-08 + 01-09 + 01-12;
  residual gaps in Phase 2).
- Coverage: 10 mapped + 1 out-of-scope (was 11 mapped).

PROJECT.md:
- CON-sensitive-data-masking: DEFERRED 2026-05-20 (preserves audit trail
  via strikethrough; rationale documented).
- DEC-004 amendment: rrweb 5000-event cap retained; masking deferred. Cites
  rrweb 2.0.0-alpha.4 maskInputSelector→maskInputFn API change.

STATE.md:
- frontmatter total_phases: 5 → 4.
- stopped_at narrative captures the re-phasing event.

CLI bug note: this re-phasing was attempted via `gsd-sdk query phase.remove 2`
+ canonical `/gsd-remove-phase 2` Skill invocation, but BOTH paths produced
corrupted output (cascading rename via reverse-iteration loop at
phase.cjs:670-679 collapsed all subsequent phases to "Phase 2", plus a
mysterious "2026"→"2002" date corruption). Recovery applied as manual edits
in this commit. CLI bug logged as upstream GSD-framework concern; not a
Mokosh-side issue.

Plan: next is `/gsd-discuss-phase 2` (new Phase 2 = export pipeline; narrowed
scope per re-phasing — ~2-3 plans expected since Plans 01-08 + 01-09 + 01-10
+ 01-12 already shipped most surface).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 13:25:11 +02:00
586836f8a0 docs(01): VERIFICATION + Phase 1 closure markers — goal-backward audit GREEN
gsd-verifier goal-backward audit (2026-05-20) returned GREEN verdict on
Phase 1 (Stabilize Video Pipeline + whole-desktop capture + as-automatic-
as-platform-allows recording start):

- 17/17 must-haves verified: 11 REQs/charters + 6 cross-cutting gates
- 14/14 plans complete (01-01..01-09 + 01-11 spike-pivot + 01-12 + 01-13
  + 01-14 + 01-10)
- 5 operator empirical acks: Plan 01-07 (Chrome playback 2026-05-15) +
  Plan 01-13 (harness 2026-05-19) + Plan 01-12 (brand-fit 2026-05-20) +
  Plan 01-10 cycle-2 ("All good" 2026-05-20) + Plan 01-10 brand-rename
  follow-up (2026-05-20)
- Test gates: vitest 153/153 GREEN; UAT harness 24/24 GREEN; Tier-1 grep
  gate 12 FORBIDDEN_HOOK_STRINGS; pre-checkpoint bundle gates PASS
- 7 P0 audit defects: 6 closed in-Phase-1-scope; P0 #6 (data-sensitive
  masking) properly deferred to Phase 2

Marker flips landed:

- STATE.md status reflects Phase 1 COMPLETE; completed_phases 0 → 1
- ROADMAP.md Phase 1 row [ ] → [x] with closure-arc summary
- REQUIREMENTS.md REQ-video-ring-buffer In-progress → Complete 2026-05-20
- VERIFICATION.md committed (orchestrator-bundle pattern per verifier
  protocol)

Forward-looking deferred (NOT gaps):
- Phase 2: REQ-rrweb-dom-buffer + REQ-user-event-log +
  REQ-password-confidentiality (audit P0 #6)
- Phase 5 hardening: getDisplayMedia cursor visibility; setimmediate
  polyfill new Function pre-existing; tabs permission gap; dark-surface
  logo contrast; 2 ffprobe/ffmpeg test flakes

Phase 2 (Stabilize DOM + event-capture privacy) kickoff pending.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 12:31:36 +02:00
d1ef77a7d1 docs(01-10): state + roadmap + requirements — Plan 01-10 closure
State markers sync after Plan 01-10 closure:

STATE.md:
- progress.completed_plans: 13 → 14
- progress.percent: 93 → 100 (all 14 Phase 1 functional plans complete)
- status: executing (Phase 1 final-closure marker flip pending)
- stopped_at + Last session timestamps refreshed
- Current Position bumped to 14/14 plans complete
- Outstanding Phase 1 gates: Plan 01-10 row marked CLOSED;
  Phase 1 final-closure marker flip listed as remaining work
- Plan 01-10 closure section added mirroring existing 01-12/01-13/01-14
  patterns: 4 wave commits + 5 inter-cycle debug commits + cycle-2 ack
- Performance Metrics: Phase 01 P10 row added (5h, 5 tasks, 14 files)
- Decisions: 3 Plan 01-10 architectural decisions added
  (first-install activation; D-16-toolbar charter preservation;
  three-pipeline DOM population pattern; startVideoCapture D-01
  cleanup gap closure)

ROADMAP.md:
- Plan 01-10 row flipped to [x] with full closure annotations
  (commit chain + harness counts + operator ack date)
- Plan 01-09 row annotated with closure-cycle follow-up debug
  commits (a2dfc8c startVideoCapture no-tab + 4bba679 notifStartup
  text split — both landed during Plan 01-10 closure cycle)
- Phase 1 plans-count narrative updated: "all 14 functional plans
  complete; Phase 1 final-closure marker flip pending"
- Progress table: Phase 1 plans complete 13/14 → 14/14;
  status row updated to "Final-closure marker flip pending"

REQUIREMENTS.md:
- Footer timestamp + closure note updated: Plan 01-10 introduced
  no new functional REQs (it consumed REQ-video-ring-buffer
  already-Complete via Plan 01-07) by adding the first-install
  operator-facing activation surface that complements the always-on
  capture pipeline.
- Phase 1 final functional plan delivered; final-closure marker
  flip pending (REQUIREMENTS / ROADMAP / STATE markers + optional
  /gsd-verify-work 1).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 12:10:56 +02:00
52dc2e6a6e docs(01-10): summary — welcome tab + 5-cycle debug closure + brand polish (153/153 vitest, 24/24 UAT GREEN, ack 2026-05-20)
Plan 01-10 (welcome tab) full SUMMARY landing closure of Phase 1's final
functional plan. Welcome tab landed end-to-end across 4 waves + Wave 4
operator empirical UAT cycle 2 ack "All good" 2026-05-20.

Architecture:
- chrome.runtime.onInstalled('install') + chrome.storage.local
  flag-gating (onboarding-completed:true + installed-at:<Date.now()>)
  + chrome.tabs.create + fire-and-forget .catch defense-in-depth.
- Plan 01-12 must_have #9 path-B contract honored: welcome.css opens
  with `@import '../shared/tokens.css';` (canonical Lora display +
  IBM Plex Sans UI + D-04 Loom palette); NO placeholder
  welcome-tokens.css file.
- chrome.i18n.getMessage for welcomeHeroRu + welcomeHeroEn with
  `|| <en-const>` fallback (Plan 01-12 fallback pattern preserved).
- Vite `?url` import + auto-WAR idiom bundles canonical
  mokosh-mark.svg as inline data URL in welcome chunk
  (closure-cycle debug 01-10-welcome-page-missing-mark).
- Three-pipeline DOM population: populateMark walks
  [data-mokosh-slot='mark']; populateCopy walks [data-mokosh-key]
  from in-file COPY map; populateI18n walks [data-mokosh-i18n-key]
  from chrome.i18n.getMessage.
- D-16-toolbar charter preserved: welcome page is informational +
  read-only; NO REQUEST_PERMISSIONS / chrome.runtime.sendMessage
  start path. CTA copy directs operator at toolbar icon.

Wave structure (4 plan-wave commits):
- 89e1e09 Wave 0 RED onboarding tests (3 tests A/B/C)
- 49f087f Wave 1 welcome bundle + Vite entries + manifest
  web_accessible_resources
- 8f329d8 Wave 2 openWelcomeIfFirstInstall + onInstalled wiring
- b112cb7 Wave 3 harness A15+A16+A17

Cycle-1 operator UAT rejection 2026-05-20 ~08:56 surfaced two
concerns + 5 inter-cycle debug fixes (in commit order):
- 4bba679 fix(01-09): notifStartup text split (notifStartupCta for
  onStartup; notifRecordingStarted reserved for manual-start)
- d48a715 fix(01-10): welcome page mark — bundle canonical
  mokosh-mark.svg via Vite ?url + populateMark + A17.8 sub-check
  (Plan 01-12 must_have #9 path-A swap-in gap closed)
- 0854baf fix(01-10): vitest build-test it() timeout bump to 30s
  for slower welcome-page build
- a2dfc8c fix(01-09): startVideoCapture — remove stale active-tab
  dependency (D-01 cleanup gap; +3 RED→GREEN tests at
  start-video-capture-no-tab.test.ts)
- d21ed17 fix(01-12): brand polish — replace stale 'AI Call Recorder'
  refs with Mokosh (4 files; .planning/intel/* preserved as audit
  trail)

Harness deltas:
- A15-A17 added at b112cb7 (24/24 UAT GREEN; A17 with 7 sub-checks
  incl. A17.7 --mks-rec getComputedStyle probe resolving to
  rgb(178,84,61) canonical).
- A17.8 mark-bundling sub-check added at d48a715 (verifies welcome
  chunk JS contains inlined data:image/svg+xml URL with canonical
  viewBox='0 0 32 32' preserved).
- FORBIDDEN_HOOK_STRINGS unchanged at 12 (A15-A17 use chrome.tabs
  .query + chrome.storage.local.get + fetch + DOMParser +
  getComputedStyle production APIs exclusively).

Test deltas:
- vitest 147 → 153 GREEN (+6: 3 onboarding tests Wave 0 + 3
  start-video-capture-no-tab tests closure-cycle debug a2dfc8c).
- UAT harness 21/21 → 24/24 GREEN (A0-A14 + A15-A17 + A18-A22 + A23).

Pre-checkpoint bundle gates per saved memory
feedback-pre-checkpoint-bundle-gates.md: Tier-1 hook-string grep
+ SW CSP-safety + Node-globals + DOM-globals + manifest validation
+ en↔ru parity — all PASS. setimmediate polyfill new Function in
SW chunk confirmed pre-existing (logged at
.planning/phases/01-stabilize-video-pipeline/deferred-items.md
for Phase 5 hardening per Plan 01-12 Wave 7 disclosure).

Operator brand-fit cycle-2 ack received verbatim "All good" + cycle-2
follow-up brand-rename ack via d21ed17. Phase 1's final functional
plan delivered; Phase 1 final-closure unblocked pending REQUIREMENTS
/ ROADMAP / STATE marker flip + optional /gsd-verify-work 1.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 12:10:30 +02:00
d21ed17310 fix(01-12): brand polish — replace stale 'AI Call Recorder' refs with Mokosh (4 files)
Plan 01-12 D-07 (commit 5efc2a8) migrated manifest.json:name + tooltip
+ extName/extDesc keys to chrome.i18n placeholders + _locales/{en,ru}/
messages.json resolving to 'Mokosh — Session Capture' (EN) / 'Mokosh —
Запись сессии' (RU). However, 4 trailing references to the pre-D-07
literal 'AI Call Recorder' in non-manifest content surfaces were never
propagated. Operator noticed during Plan 01-10 cycle-2 UAT 2026-05-20.

This commit applies the user-approved 4-file surgical rename:

  - src/welcome/copy.ts: welcome.body.cta.toolbar RU CTA
    "иконку AI Call Recorder" → "иконку Mokosh" (matches toolbar tooltip
    i18n key tooltipOff = "Mokosh — щёлкните, чтобы начать запись").
    Inline rationale comment added cross-ref'ing the i18n key + this
    debug session.

  - README.md: H1 + first paragraph rewritten to
    "# Mokosh — Session Capture" + EN tagline line. Rest of README body
    preserved verbatim (technical-stack section historical mentions
    left as project history, not brand surface).

  - package.json: name "ai-call-extension" → "mokosh-session-capture",
    description rewritten to "Mokosh — Session Capture: Chrome MV3
    extension for operator session recording." version, scripts,
    dependencies, devDependencies untouched.

  - tests/i18n/manifest-i18n.test.ts: header comment block rewritten to
    label the "AI Call Recorder" string as Wave-0 historical state +
    describe the post-D-07 regression-pin role. Test bodies + assertions
    unchanged (already pin the post-D-07 canonical state).

Preservation rationale: .planning/intel/* (brand-decisions-v1.md D-07,
design-system.md, brand-identity.md, classifications/README-*.json,
design-incoming/system/bundle/mokosh-handoff/handoff.html) is audit
trail documenting the "why" of D-07 — kept verbatim. _locales/{en,ru}/
messages.json and manifest.json already post-D-07 canonical — untouched.

Acceptance gates (all PASS 2026-05-20):
  - Empirical grep src/ tests/ README.md package.json: ZERO non-historical
    "AI Call Recorder" matches (only the labeled audit anchor in
    tests/i18n/manifest-i18n.test.ts:8).
  - npx tsc --noEmit: clean.
  - npm run build: ✓ built in 5.29s.
  - npx vitest run tests/i18n/manifest-i18n.test.ts: 10/10 GREEN.
  - npx vitest run tests/background/no-test-hooks-in-prod-bundle.test.ts
    (Tier-1 hook-string grep gate): 13/13 GREEN; FORBIDDEN_HOOK_STRINGS
    list intact.
  - npm test: 151/153 (2 pre-existing ffprobe/ffmpeg timeout flakes in
    webm-remux + webm-playback — verified identical to pristine HEAD
    a2dfc8c via git stash baseline; unrelated to rename).
  - npm run test:uat: 24/24 GREEN.
  - Production bundle grep dist/: ZERO "AI Call Recorder" + ZERO
    "ai-call-extension" matches.

Unblocks Plan 01-10 closure + Phase 1 final closure (REQUIREMENTS /
ROADMAP / STATE marker flip).

Debug record: .planning/debug/resolved/01-12-stale-ai-call-recorder-references.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 11:49:46 +02:00
a2dfc8cb9b fix(01-09): startVideoCapture — remove stale active-tab dependency (D-01 cleanup gap)
The legacy chrome.tabs.query({ active: true, currentWindow: true }) +
"No active tab found" validation inside startVideoCapture were load-
bearing in the pre-D-01 chrome.tabCapture era but became functionally
dead after Plan 01-09's D-01 conversion to getDisplayMedia-in-offscreen.
The only post-D-01 consumer was a log line at index.ts:521.

The dead validation caused an activeTab-permission-scope asymmetry
between callers: chrome.action.onClicked grants activeTab on the click
gesture (so tab.url was readable → toolbar path worked silently) but
chrome.notifications.onClicked does NOT grant activeTab and the extension
has no `tabs` permission, so notifications.onClicked → startVideoCapture
threw "No active tab found" before reaching ensureOffscreen. Operator
2026-05-20 UAT against the new notifStartupCta CTA copy ("Mokosh ready.
Click to start a recording.", commit 4bba679) surfaced the silent
notification failure.

Surgical fix: remove the dead tab query + validation + tab-dependent log
(src/background/index.ts:514-521); replace with a tab-independent log
that documents WHY (cites D-01 + this debug session). captureScreenshot
+ saveArchive retain their genuine tab dependencies (tab.windowId for
chrome.tabs.captureVisibleTab; tab.id for content-script sendMessage).

Tests: tests/background/start-video-capture-no-tab.test.ts (NEW) pins
the contract with 3 cases (tabs.query → []; → [{id}] url-less; →
[{id,url,windowId}] regression guard for toolbar path).

Gates: vitest 153/153 GREEN (was 150/150 baseline; +3); test:uat 24/24
GREEN; tsc clean; build clean. Pre-checkpoint bundle gates per
feedback-pre-checkpoint-bundle-gates.md: SW chunk hook-string Tier-1
grep 0 matches; eval/Node-global/DOM-global matches unchanged from
baseline (all vendor-library feature-detect, guarded; no new imports).

Debug record: .planning/debug/resolved/01-09-notification-start-no-active-tab.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 11:33:18 +02:00
0854baf66c fix(01-10): vitest build-test it() timeout — bump to 30s for slower welcome-page build
The build-completes Tier-1 gate at tests/background/no-test-hooks-in-prod-bundle.test.ts:247
was racing vitest's default 5000ms it() ceiling. Plan 01-10 closure shipped the welcome
page (commits d48a715 welcome mark + 49f087f welcome HTML/CSS/JS + 8 WOFF2 fonts) which
slowed standalone `npm run build` from ~2.88s to ~5.28s. The exec-level
BUILD_TIMEOUT_MS = 60_000 child-process bound was correctly declared at line 240, but
the surrounding it() block had no timeout option, so the 5s default fired first and the
60s exec bound was never reachable.

Surgical fix: add `, 30_000` 3rd arg to the it() call. 30s is ~6× the observed build
duration and well below the 60s exec ceiling, so both bounds remain meaningfully
active. SKIP_BUILD=1 env-var escape hatch untouched.

Acceptance gates:
- `npm test` (FULL, no SKIP_BUILD=1): 150/150 GREEN, exit 0
- `npx tsc --noEmit`: exit 0
- `npm run build`: exit 0
- Tier-1 grep gate: PASS (all 12 FORBIDDEN_HOOK_STRINGS asserted against dist/)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 10:52:39 +02:00
d48a715da5 fix(01-10): welcome page mark — bundle canonical mokosh-mark.svg + replace placeholder
Plan 01-10 must_have #9 path-A swap-in (landed 2026-05-20 per debug
session 01-10-welcome-page-missing-mark). Closes the planning-coverage
gap where Plan 01-12 path-B (canonical tokens import) ran ahead of
01-10, leaving the welcome hero with a text placeholder 'Mokosh'
inside the rec-bg circle instead of the canonical 2×2 woven-square
mark from src/shared/brand/mokosh-mark.svg.

Why Option B (Vite ?url import) over manual WAR (A) or inline SVG (C):
- @crxjs/vite-plugin ^2.0.0-beta.25 auto-WARs transitively-reachable
  resources from extension pages — no manifest.json edit needed.
- Vite default-inlines small SVGs (~600 bytes < 4096 byte default
  assetsInlineLimit) as data:image/svg+xml URLs in the welcome chunk
  — no extra HTTP request, no extra WAR entry.
- Hashed asset fallback works automatically if the SVG grows past
  the inline limit in future revisions.
- Existing font-bundling precedent (dist/assets/Lora-*.woff2 +
  IBMPlex*.woff2) proves the Vite + crxjs pipeline.

Files modified:
- src/welcome/welcome.ts — added markUrl import + populateMark() that
  walks [data-mokosh-slot='mark'] and injects an <img>.
- src/welcome/welcome.html — added explanatory comment block; preserved
  the data-mokosh-slot wrapper for forward-compat (the placeholder
  span remains as the JS-fail-gracefully fallback).
- src/welcome/welcome.css — added .welcome-hero__mark-img rule
  (60% sizing inside the existing styled circle wrapper).
- src/welcome/copy.ts — added 'welcome.hero.mark.alt' COPY key
  (Russian per D-03 Sober voice).
- globals.d.ts — added *.svg?url ambient module declaration
  (Vite recommended pattern; keeps tsconfig.json types: ['chrome']
  clean by not requiring vite/client triple-slash directives).
- tests/uat/extension-page-harness.ts — extended A17 with A17.8
  sub-check verifying the canonical mark SVG is bundled into the
  welcome chunk (data URL OR file URL form) AND that the canonical
  viewBox='0 0 32 32' is preserved through bundling.

Acceptance gates passed:
- npx tsc --noEmit exit 0
- npm run build exit 0
- SKIP_BUILD=1 npm test → 150/150 GREEN
- npm run test:uat → 24/24 GREEN including A17.8
- Tier-1 hook-string grep gate PASS (no FORBIDDEN_HOOK_STRINGS
  in production bundle).
- Manifest valid JSON; web_accessible_resources auto-bundled.
- Pre-checkpoint bundle gates 1/2/3: vendor pre-existing hits
  (JSZip + ts-ebml) confirmed identical pre-change via git stash
  baseline; not caused by this fix.

Forward-looking deferred (out of scope):
- Issue 2 dark-surface contrast (e.g. chrome.notifications icon128
  may need a light-stroke variant). The welcome hero's rec-orange
  BG already provides high contrast with the dark ink stroke — this
  is correct design. Per the orchestrator's explicit constraint,
  light-variant mark for dark notification panels is deferred to
  Phase 5.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 10:28:58 +02:00
4bba679e39 fix(01-09): notifStartup text split — notifStartupCta for onStartup; notifRecordingStarted for manual-start
Operator UAT 2026-05-20 rejected the build because the OS notification fired
on `chrome.runtime.onStartup` ("Recording started. I'm watching the last 30
seconds.") implied recording had auto-started when in fact recording was
not running. Per Phase 1 always-on charter recording does NOT auto-start;
the notification is the gesture surface that invites the operator to start
one (notifications.onClicked → startVideoCapture, src/background/index.ts:1038).

Root cause: a single i18n key `notifStartup` conflated the pre-recording
CTA-with-gesture path (the only path actually wired today) and a future
post-manual-start confirmation path. The key's own `.description` field
acknowledged the conflation. Operator-facing text leaned toward the
confirmation phrasing.

Fix (key split, no behavior change):
- `notifStartupCta` — EN: "Mokosh ready. Click to start a recording." /
  RU: "Mokosh готов. Нажмите, чтобы начать запись." — wired into the
  onStartup handler.
- `notifRecordingStarted` — preserves the original text ("Recording
  started. I'm watching the last 30 seconds." / "Запись запущена…") for
  a future post-manual-start confirmation flow.
- Fallback constant renamed `NOTIF_STARTUP_FALLBACK` →
  `NOTIF_STARTUP_CTA_FALLBACK`; value updated to match the new CTA text.
- Inline test comment in tests/background/onstartup-notification.test.ts
  refreshed to reference the new key + fallback. Assertion regex
  /recording|recor|click/i covers both fallback + resolved locale variants,
  no logic change.

Notification behavior preserved: same id prefix `mokosh-startup-`, same
priority, same icon, same onClicked → startVideoCapture wiring. No new
test-mode symbols (FORBIDDEN_HOOK_STRINGS inventory stays at 12).

Files modified:
- _locales/en/messages.json
- _locales/ru/messages.json
- src/background/index.ts
- tests/background/onstartup-notification.test.ts

Verification:
- npx vitest run --exclude tests/build/** --exclude tests/background/no-test-hooks-in-prod-bundle.test.ts: 104/104 GREEN
- npx vitest run tests/i18n/ tests/background/onstartup-notification.test.ts: 18/18 GREEN (locale-parity 4/4 + onstartup-notification 14/14)
- npx tsc --noEmit clean on src/background/index.ts

The 2 build-dependent vitest gates (tests/build/no-remote-fonts.test.ts +
tests/background/no-test-hooks-in-prod-bundle.test.ts) and npm run test:uat
are deferred to orchestrator-level re-verification after the parallel
Plan 01-10 mark-bundling fix also lands (operator-UAT re-spawn coordinated
by orchestrator).

Debug record: .planning/debug/resolved/01-09-startup-notification-misleading-text.md
Operator UAT rejection event: 2026-05-20

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 10:14:08 +02:00
b112cb7861 test(01-10): wave-3 task-4 — harness A15+A16+A17 (onboarding flag observability + no-re-open settle + design-swap-readiness with @import probe); 24/24 GREEN
Plan 01-10 Wave 3: extends the UAT harness with three new page-side
assertions covering the onboarding contract + the canonical-tokens
design-swap-readiness invariant. UAT baseline 21 → 24 GREEN.

tests/uat/extension-page-harness.ts (page-side):
  - assertA15 — chrome.storage.local 'onboarding-completed' === true +
    'installed-at' is number. Verifies SW's openWelcomeIfFirstInstall
    side-effects.
  - assertA16 — 2s settle window; chrome.tabs.query welcome-tab count
    delta === 0. Verifies flag-gating across SW respawns.
  - assertA17 — 7 sub-checks covering: welcome.html parse + .welcome-hero
    + >=7 mokosh-keyed attrs + welcome.css canonical @import literal OR
    inlined --mks-* evidence + (zero hex OR canonical resolved) + >=5
    var(--mks-*) refs + bundled JS preserves populate plumbing +
    getComputedStyle --mks-rec → rgb(178, 84, 61) (canonical D-04 Loom).
  - window.__mokoshHarness surface extended with the three new methods;
    type declaration + assignment + page-ready status text updated.

tests/uat/lib/harness-page-driver.ts (host-side):
  - driveA15, driveA16, driveA17 — standard page.evaluate wrappers
    matching driveA14 / driveA18..A22 idiom. driveA16 dominates the
    new wall-clock budget (~2.1s for the settle window).

tests/uat/harness.test.ts (orchestrator):
  - Drivers array interleaves A15/A16/A17 AFTER A14 + BEFORE A18.
    A22's skip-gate no longer triggers (Plan 01-10 lands welcome.html;
    A22 now exercises the substantive token-usage path).
  - FORBIDDEN_HOOK_STRINGS unchanged at 12 entries (A15-A17 use only
    chrome.tabs.query / chrome.storage.local.get / fetch / DOMParser /
    getComputedStyle — all production-API surfaces).

DEVIATION (Rule 1 — auto-fix bug in plan-supplied check):
  The plan's A17.6 spec used literal substring checks 'COPY[' and
  'chrome.i18n.getMessage(' which fail against minified production
  output. Vite/Rollup terser renames `COPY` → `f` (local variable
  mangling) and welcome.ts's source uses optional chaining
  `chrome?.i18n?.getMessage?.(` which doesn't match the verbatim
  literal. Replaced with two minification-survivable witnesses:
    1. 'welcome.page.title' — literal Object.freeze key (terser
       preserves object-literal keys verbatim).
    2. 'i18n' + 'getMessage' + 'welcomeHero' substring conjunction —
       chrome global + property access + fallback key literal; all
       three survive minification regardless of optional-chaining
       insertion or rename.
  Both witnesses prove the populate plumbing survives the build (the
  ground-truth contract A17.6 enforces). The relaxed contract is
  semantically equivalent — neither substring is load-bearing on its
  own; both witness the same underlying invariant.

Verify (all GREEN):
  - npm run test:uat: 24/24 assertions passed (A0 grep gate + A1..A14
    + A15..A17 + A18..A22 + A23).
  - npx tsc --noEmit: clean.
  - npm run build:test: clean; dist-test/assets/welcome-wB0e_R_n.js
    bundled; harness page bundle includes new asserts.
  - SKIP_BUILD=1 npx vitest run tests/background/no-test-hooks-in-prod-bundle.test.ts:
    13/13 GREEN (Tier-1 grep gate; FORBIDDEN_HOOK_STRINGS at 12).
  - Full vitest baseline preserved: 137 ex-grep-gate + 13 grep-gate
    = 150 GREEN (Plan 01-10 target).

A17.7 canonical proof: getComputedStyle.color = 'rgb(178, 84, 61)' —
the @import '../shared/tokens.css' directive resolves through to the
canonical D-04 Loom palette --mks-madder-600 = #b2543d at runtime, as
the empirical proof Plan 01-12 must_have #9 path-B contract demands.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 09:41:10 +02:00
8f329d8b74 feat(01-10): wave-2 task-3 — openWelcomeIfFirstInstall helper + onInstalled wiring (D-17-onboarding) — 3 RED → GREEN
Plan 01-10 Wave 2: SW handler extension flips Task 1's 3 RED onboarding
tests GREEN.

src/background/index.ts changes:

  1. Three top-level constants added near the badge/notification block:
     - ONBOARDING_FLAG = 'onboarding-completed'
     - ONBOARDING_INSTALLED_AT = 'installed-at'
     - WELCOME_PATH = 'src/welcome/welcome.html'
     SCREAMING_SNAKE per project naming standard for true constants.

  2. openWelcomeIfFirstInstall helper added below ensureOffscreen
     (interfaces §1 placement). JSDoc cites D-17-onboarding (CONTEXT.md
     line 537+; SUFFIX disambiguates from D-17-port-lifecycle per
     CONTEXT.md lines 540-545). Body:
       - Early return on details.reason !== 'install' (subsequent
         installs / updates / chrome_update / shared_module_update do
         NOT open a welcome tab — Test B's contract).
       - chrome.storage.local.get(ONBOARDING_FLAG) read with the EXACT
         single-key string (storage-schema cross-version-compat pin;
         Test A.3's contract).
       - Early return if stored[ONBOARDING_FLAG] === true — Test C's
         contract (already-onboarded suppression).
       - chrome.tabs.create + chrome.storage.local.set with both the
         flag and Date.now() installed-at — Test A.1 + A.2's contract.
       - Defense-in-depth try/catch wraps the whole body; any thrown
         chrome.* call is logged via logger.warn but does not propagate
         (D-16-toolbar start path remains independent).

  3. onInstalled listener extended: fire-and-forget call to
     openWelcomeIfFirstInstall(details) AFTER initialize(); .catch()
     boundary so rejected promises cannot escape the synchronous
     listener. The existing IDB cleanup + initialize() call sequence
     stays unchanged.

Architectural compliance:
  - NO `await import(...)` added (01-11-SUMMARY architectural constraint
    preserved; the three matches in lines 14-28 are documentation
    comments about Plan 01-11's falsification).
  - NO `as any` (chrome.runtime.InstalledDetails ambient typing covers
    the parameter).
  - NO `continue` (if-else early-return only).
  - No new dependencies.

Verify (all GREEN):
  - npx vitest run tests/background/onboarding.test.ts: 3 passed (Test
    A flipped RED → GREEN; B + C continue passing as load-bearing
    guards).
  - Full vitest baseline 147 → 150 (137 ex-build-gated + 13 in build-
    gated = 150 GREEN total).
  - npx tsc --noEmit: clean.
  - npm run build: clean; openWelcomeIfFirstInstall + D-17-onboarding
    references survive into dist/assets/index.ts-*.js.
  - Tier-1 FORBIDDEN_HOOK_STRINGS unchanged at 12 entries; gate GREEN.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 09:16:42 +02:00
49f087fe40 feat(01-10): wave-1 task-2 — welcome page bundle + Vite entries + web_accessible_resources
Plan 01-10 Wave 1: welcome page bundle staged with canonical Plan 01-12
tokens.css @import + chrome.i18n for D-08 tagline (Plan 01-12 path-B
contract).

Files created:
  - src/welcome/copy.ts (Russian non-tagline COPY map per D-03 Sober
    voice; WELCOME_HERO_RU_FALLBACK + WELCOME_HERO_EN_FALLBACK exported
    for the welcome.ts `|| <en-const>` fallback chain).
  - src/welcome/welcome.html (lang='ru'; data-mokosh-key + data-mokosh-
    i18n-key attribute conventions; SINGLE stylesheet link; D-02 Hero +
    body + footer structure; 10 keyed attrs total; <title> populated
    via populateCopy).
  - src/welcome/welcome.css (FIRST LINE `@import '../shared/tokens.css';`;
    ZERO hex literals in source; 65 var(--mks-*) refs; D-02 layout +
    --mks-welcome-max-w=720px; --mks-rec madder for recording-related
    accents per D-04 Loom palette).
  - src/welcome/welcome.ts (vanilla DOM; populateCopy + populateI18n
    filter-pipeline form per project rule "no `continue`"; no `as any`;
    Logger from src/shared; document.readyState guard; no event
    handlers per D-16-toolbar informational charter).

Files modified:
  - vite.config.ts: rollupOptions.input gains `welcome:
    'src/welcome/welcome.html'`; __VITE_DEV__ + __MOKOSH_UAT__ defines
    untouched (Plan 01-12 Wave 5 baseline preserved verbatim).
  - vite.test.config.ts: mirror entry in dist-test/; mergeConfig pattern
    untouched.
  - manifest.json: web_accessible_resources block added after
    host_permissions, before background; storage permission preserved
    in permissions array; default_locale='en' + __MSG_*__ placeholders
    from Plan 01-12 Wave 3 preserved verbatim.

NO src/welcome/welcome-tokens.css file is created — Plan 01-12 must_have
#9 path-B contract: Plan 01-12 landed FIRST (b909c37 → 865d394; SUMMARY
2026-05-20); canonical src/shared/tokens.css is import-ready
(Lora @font-face + IBM Plex Sans + D-04 Loom palette + --mks-rec=
var(--mks-madder-600) = #b2543d); welcome.css @imports it directly. No
placeholder transition needed.

Verify (all GREEN):
  - grep -F "@import '../shared/tokens.css'" src/welcome/welcome.css: exit 0
  - grep -E '#[0-9a-fA-F]{3,8}' src/welcome/welcome.css: exit 1 (zero hex)
  - grep -c 'var(--mks-' src/welcome/welcome.css: 65 (>= 5 required)
  - grep -oE 'data-mokosh-(i18n-)?key=' welcome.html | wc -l: 10 (>= 7)
  - npm run build: clean; dist/src/welcome/welcome.html present;
    dist/assets/welcome-D9oNz95l.css carries inlined tokens.css content
    (--mks-rec: var(--mks-madder-600); --mks-madder-600: #b2543d).
  - npm run build:test: clean; dist-test/src/welcome/welcome.html present;
    dist-test/assets/welcome-wB0e_R_n.js bundled.
  - npx tsc --noEmit: clean.
  - dist/manifest.json preserves "default_locale": "en" + __MSG_extName__
    + web_accessible_resources block present (Vite/crxjs propagated).
  - Vitest baseline preserved: Task 1's 3-test file unchanged
    (1 RED + 2 vacuous-GREEN; Task 3 flips Test A to GREEN).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 09:09:22 +02:00
89e1e09d60 test(01-10): wave-0 task-1 — RED onboarding tests (3 tests pin install/update/flag + storage-key)
Three tests in tests/background/onboarding.test.ts pinning the
Plan 01-10 D-17-onboarding contract:

  Test A (RED): first install + empty storage opens exactly ONE welcome
    tab whose URL contains 'src/welcome/welcome.html', sets
    chrome.storage.local.set({'onboarding-completed':true,
    'installed-at':<number>}), AND calls chrome.storage.local.get with
    EXACT key 'onboarding-completed' (storage-schema cross-version-compat
    pin; preserves I-02 lesson from prior draft).

  Test B (vacuous-GREEN, becomes load-bearing post-Task-3): reason='update'
    → chrome.tabs.create NOT called.

  Test C (vacuous-GREEN, becomes load-bearing post-Task-3): flag already
    true → chrome.tabs.create NOT called.

Tests B and C pass vacuously until Task 3 lands openWelcomeIfFirstInstall;
they remain load-bearing AFTER Task 3 as no-tab-open guards for the
update/already-onboarded branches. Test A flips RED → GREEN at Task 3.

Stub scaffold inherits buildBgStub from onstartup-notification.test.ts;
extended with chrome.tabs.create + chrome.storage.local.{get,set} +
chrome.runtime.onInstalled._callbacks (addListener.mockImplementation
pattern to capture the SW's registered listener).

DEVIATION NOTE: plan's <verify> expected `3 failed` but only Test A
(positive contract) goes RED pre-Task-3; Tests B+C are negative-path
guards that pass trivially when the helper is absent. This is standard
TDD (positive test fails RED; negative tests stay GREEN through GREEN→
REFACTOR). No code change needed — Task 3's GREEN gate is "all 3 GREEN".

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 09:00:58 +02:00
7f58e0ae31 fix(01-10): revise plan per 01-12 + 01-14 baselines (vitest 98→147, UAT 15→21, FORBIDDEN 10→12, welcome.css @imports canonical tokens, welcomeHero keys read from chrome.i18n)
Surgical amendment to unexecuted Plan 01-10 absorbing the post-draft
landing of Plan 01-12 (canonical src/shared/tokens.css + 16 i18n keys
including welcomeHeroRu/welcomeHeroEn; 2026-05-20 operator brand-fit
ack) and Plan 01-14 (vitest +2 + UAT +1 + FORBIDDEN_HOOK_STRINGS +2).

Baseline shifts:
- vitest 98 → 147 GREEN (post Plan 01-12 + 01-14); plan close target 150.
- UAT 15 → 21 GREEN (A0-A14 + A18-A22 + A23); plan close target 24
  (A0-A14 + A15-A17 + A18-A22 + A23).
- FORBIDDEN_HOOK_STRINGS 10 → 12 (Plan 01-14: lastGetDisplayMediaConstraints
  + get-last-getDisplayMedia-constraints); Plan 01-10 introduces no new
  test-mode symbols; inventory unchanged at 12.

Plan 01-12 must_have #9 path-B contract honored end-to-end (Plan 01-12
landed FIRST, so the welcome page adopts canonical assets directly):

- welcome.css opens with `@import '../shared/tokens.css';` (NO placeholder
  welcome-tokens.css; removed from files_modified).
- D-08 hero tagline elements use data-mokosh-i18n-key='welcomeHeroRu' +
  data-mokosh-i18n-key='welcomeHeroEn'; welcome.ts reads via
  chrome.i18n.getMessage with `|| <en-const>` fallback per Plan 01-12
  fallback pattern. WELCOME_HERO_RU_FALLBACK + WELCOME_HERO_EN_FALLBACK
  constants exported from copy.ts for the degradation path.
- copy.ts COPY map retains non-tagline keys only (page title + explainer
  lines + CTA + footer privacy; engineering placeholders per D-03).
- A17 design-swap-readiness invariant extended with:
  - A17.5: welcome.css contains canonical @import directive OR inlined
    `--mks-rec:` evidence;
  - A17.6: bundled JS contains COPY[ OR chrome.i18n.getMessage('welcomeHero;
  - A17.7 NEW: getComputedStyle probe on var(--mks-rec) returns non-default
    value (canonical rgb(178, 84, 61) = #b2543d = --mks-madder-600 per
    Plan 01-12 Wave 4 D-04 Loom palette adoption).
- depends_on extended to [01-09, 01-13, 01-14, 01-12].

Preserved verbatim: 5-task structure, A15/A16 contracts, D-02/D-08/D-09
references, threat model + STRIDE register, operator empirical checkpoint
shape, Plan 01-12 default_locale='en' + __MSG_*__ + __VITE_DEV__ +
__MOKOSH_UAT__ + src/shared/tokens.css.

Validated: gsd-sdk frontmatter.validate + verify.plan-structure both PASS;
task_count=5, all tasks complete with files/action/verify/done.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 08:48:24 +02:00
66e6f503a4 docs(01-12): state + roadmap + requirements — Plan 01-12 closure
Plan 01-12 closure documentation sync per the plan's Wave 7 Task 1
spec. Three docs land together as one atomic closure commit per the
Plan 01-13 + 01-14 closure cadence convention.

STATE.md changes:
- status: verifying → executing (Plan 01-10 welcome tab still pending)
- stopped_at: Plan 01-14 → Plan 01-12 closed via Wave 7 brand-fit
  ack 2026-05-20 'all good'; Plan 01-10 remains as final Phase 1
  functional plan
- last_updated + last_activity: 2026-05-19 → 2026-05-20
- progress.completed_plans: 12 → 13 (13 plans: 01-01..01-09 + 01-11
  spike + 01-12 + 01-13 + 01-14; Plan 01-10 pending)
- progress.percent: 86 → 93
- "Plan 01-13 closure" header annotated: brand/design ack subsequently
  closed via Plan 01-12 Wave 7 2026-05-20
- New "Plan 01-12 closure (2026-05-20)" section: 7-wave execution arc
  with all 10 commit hashes (3fe018b plan-baseline-revision → 34a9ce1
  Wave 0 → f86fd60 + abab6e1 Wave 1 → 7732a30 Wave 2 → 110cebc Wave 3
  → 468f16d Wave 4 → e8d2881 Wave 5 → b909c37 Wave 6 + 865d394
  pre-checkpoint + f319c7d SUMMARY); R2 Lora substitution; 16 i18n
  keys; branded icons; BADGE_REC_COLOR #b2543d; chrome.i18n fallback;
  A18-A22 harness; pre-checkpoint bundle gates per
  feedback-pre-checkpoint-bundle-gates.md; setimmediate polyfill
  discovery logged to deferred-items.md (Phase 5 hardening); operator
  brand-fit ack; Plan 01-13 Task 9 functional closure
- "Outstanding Phase 1 gates" updated: Plan 01-13 Task 9 + Plan 01-12
  CLOSED; only Plan 01-10 remains
- Performance Metrics table: Plan 01-12 entry appended (~10h
  cumulative; 10 tasks; ~50+ files)
- Decisions section: 2 new entries for Plan 01-12 design integration
  + Plan 01-13 Task 9 closure linkage
- Session Continuity: last/prior session updated to 2026-05-20 /
  2026-05-19

ROADMAP.md changes:
- Phase 1 Plans list: 01-12-PLAN.md entry flipped from [ ] to [x]
  with full closure annotation (R2 Lora self-host, tokens.css
  canonical, 16 i18n keys across en+ru, branded Loom icons, manifest
  i18n, BADGE_REC_COLOR madder #b2543d, chrome.i18n fallback,
  harness A18-A22, operator brand-fit ack 2026-05-20 'all good')
- Phase 1 plan count: 13 → 14 plans (01-01 through 01-14)
- Progress table Phase 1 row: 7/7 Complete → 13/14 Executing with
  closure-status disambiguation (functional via Plan 01-13;
  design/brand via Plan 01-12; Plan 01-10 welcome tab remains)

REQUIREMENTS.md changes:
- REQ-install-clean: [ ] Pending → [x] Complete (2026-05-20)
  with annotation: fresh build clean; zero remote-font CSP errors;
  branded icons; en+ru manifest:name resolution; operator brand-fit
  ack
- REQ-manifest-permissions: [ ] Pending → [x] Complete (2026-05-20)
  with annotation: manifest:name + :description +
  :action.default_title migrated to __MSG_*__ + default_locale='en';
  manifest validation PASS; en↔ru parity; permissions DEC-011
  baseline UNCHANGED
- Traceability table: both requirements moved from Phase 3/4 Pending
  to "Phase 1 closure via Plan 01-12" Complete
- Footer: last-updated 2026-05-15 → 2026-05-20 with annotation
  noting the requirements flipped at Plan 01-12 closure

No code changes; pure documentation closure sync.

Closure commit hashes:
- SUMMARY: f319c7d (.planning/phases/01-stabilize-video-pipeline/01-12-SUMMARY.md)
- State sync: this commit (.planning/STATE.md + .planning/ROADMAP.md
  + .planning/REQUIREMENTS.md)

Phase 1 status post-closure:
- Functional contract: CLOSED via Plan 01-13 harness PASS (2026-05-19)
- Design/brand contract: CLOSED via Plan 01-12 brand-fit ack
  (2026-05-20)
- Remaining: Plan 01-10 (welcome tab) — operator-facing onboarding
  surface; canonical src/shared/tokens.css from Plan 01-12 now
  available for swap-in

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 08:33:52 +02:00
f319c7dc6e docs(01-12): summary — design integration landed (147/147 vitest, 21/21 UAT, brand-fit ack 2026-05-20)
Plan 01-12 closure SUMMARY landed at .planning/phases/01-stabilize-
video-pipeline/01-12-SUMMARY.md per the plan's <output> block + 01-13/
01-14 closure cadence. Mirrors the 01-13-SUMMARY frontmatter shape +
body sections (One-Liner / What Landed by Wave / Test Counts /
Deviations / Architectural Notes / Self-Check / Known Limitations /
Bridge to Phase 1 Closure).

Plan 01-12 design integration in one sentence: Lora self-hosted via
R2 designer substitution (Newsreader → Lora for Cyrillic coverage,
2026-05-19); src/shared/tokens.css canonical with 8 local @font-face
rules and zero remote URLs; 16 i18n keys across en + ru with parity;
branded Loom-mark icons replace Bug A placeholders; src/popup +
src/background migrated to chrome.i18n.getMessage with || <const>
fallback; UAT harness extended with A18-A22; pre-checkpoint bundle
gates established per feedback-pre-checkpoint-bundle-gates.md;
operator brand-fit ack received 2026-05-20 verbatim "all good".

Gate evidence (per Wave 7 pre-checkpoint 865d394 + this closure):
- vitest: 147/147 GREEN (re-verified on closure day; 26 test files)
- npm run test:uat: 21/21 GREEN (A0-A14 + A18-A22 + A23)
- npx tsc --noEmit: clean
- npm run build + npm run build:test: both clean
- MV3 CSP self-host: 0 googleapis / 0 https://fonts in dist/
- Tier-1 forbidden-strings: 13/13 GREEN (no new test-mode symbols)
- Operator brand-fit empirical ack 2026-05-20: "all good"

Closure linkage:
- Plan 01-12 functionally CLOSED (10/10 tasks; 7/7 waves)
- Plan 01-13 Task 9 (operator brand/design ack on loaded extension)
  functionally CLOSED via this checkpoint (same operator + same
  empirical surface coverage)
- Phase 1 design/brand contract CLOSED; only Plan 01-10 (welcome tab)
  remains as the last Phase 1 functional plan
- Phase 2 inherits tokens.css + chrome.i18n patterns + OFL self-host
  recipe + pre-checkpoint bundle gates as production conventions

Out-of-scope discovery (Wave 7 pre-checkpoint, logged for Phase 5
hardening, NOT a Plan 01-12 regression): setimmediate polyfill
`new Function` in SW chunk via vite-plugin-node-polyfills. Pre-existing
across Phase 1 history; logged at deferred-items.md. Suggested
follow-up: switch to a minimal Buffer shim or inline Buffer primitives
to drop the polyfill entirely.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 07:59:34 +02:00
865d394ae0 docs(01-12): wave-7 pre-checkpoint — log out-of-scope discovery (setimmediate polyfill new Function pre-existing)
Wave 7 pre-checkpoint bundle gates per feedback-pre-checkpoint-bundle-gates.md
revealed a pre-existing benign concern in the SW production bundle:

`vite-plugin-node-polyfills` (configured for Buffer in vite.config.ts)
bundles the upstream `setimmediate` package which contains a fallback
`new Function("" + I)` evaluated when setImmediate is called with a
non-function argument. Production source code does NOT call
setImmediate(string); the construct is dead at the runtime call-graph
level but Rollup conservatively preserves it (behind a runtime
typeof check, not a static dead branch).

Verified pre-existing across Phase 1 history via `git checkout main --
src/background/index.ts vite.config.ts && npm run build` — same
`new Function` count. Plan 01-12 made NO changes to the polyfill
configuration; this is logged for future tightening (Phase 5
hardening or a dedicated MV3 CSP audit plan), NOT for fix in this
plan per the deviation-rule SCOPE BOUNDARY.

All other pre-checkpoint bundle gates PASS:
- Tier-1 forbidden-strings: 13/13 GREEN (no new test-mode symbols)
- SW-bundle-import: 15/15 GREEN
- Node-globals (Buffer.*) in SW chunk: 0
- DOM-globals direct SW calls: none
- Manifest validation: PASS (__MSG_*__ + default_locale='en' +
  16 i18n keys per locale; en+ru parity verified)
- Tokens.css MV3 CSP self-host: 0 googleapis / 0 https://fonts in dist/
- Icons rasterized: 8-bit RGBA at 406/784/1952 B
- vitest: 147/147 GREEN
- npm run test:uat: 21/21 GREEN (A1..A14 regression-free + A18..A22
  new + A23 from 01-14)
- npx tsc --noEmit: clean
- npm run build + npm run build:test: clean

Surfacing Wave 7 operator brand-fit checkpoint to orchestrator next.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 07:47:13 +02:00