diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index acd189f4d..3afc400f4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,7 +30,7 @@ jobs: run: npm --prefix showcase/angular install --no-audit --no-fund - name: Validate extension (manifest + JS syntax) run: npm run validate:extension - - name: Run extension + bridge contract tests + - name: Phase 65 Codex contract (sole Linux root invocation) run: npm test mcp-smoke: @@ -47,9 +47,80 @@ jobs: run: npm --prefix mcp install --no-audit --no-fund - name: Build MCP server (TypeScript) run: npm --prefix mcp run build + - name: Phase 62 adapter drift smoke + run: node tests/mcp-agent-drift-smoke.test.js - name: MCP lifecycle + tools smoke run: npm run test:mcp-smoke + native-host-windows: + name: native host (Windows PE + pack) + runs-on: windows-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: '20' + - name: Install MCP dependencies + run: npm --prefix mcp ci --no-audit --no-fund + - name: Build x64 native-host bootstrap + shell: cmd + run: | + call "%ProgramFiles%\Microsoft Visual Studio\2022\Enterprise\Common7\Tools\VsDevCmd.bat" -arch=x64 -host_arch=x64 + if errorlevel 1 exit /b %errorlevel% + node scripts/build-native-host-windows.mjs --arch x64 + - name: Build arm64 native-host bootstrap + shell: cmd + run: | + call "%ProgramFiles%\Microsoft Visual Studio\2022\Enterprise\Common7\Tools\VsDevCmd.bat" -arch=arm64 -host_arch=x64 + if errorlevel 1 exit /b %errorlevel% + node scripts/build-native-host-windows.mjs --arch arm64 + - name: Verify both PE artifacts and write package metadata + shell: cmd + run: | + call "%ProgramFiles%\Microsoft Visual Studio\2022\Enterprise\Common7\Tools\VsDevCmd.bat" -arch=x64 -host_arch=x64 + if errorlevel 1 exit /b %errorlevel% + node scripts/build-native-host-windows.mjs --arch all --verify-only --metadata-out mcp/native-host/windows-artifacts.json + - name: Execute the x64 bootstrap and safe registry-helper harnesses + run: node tests/mcp-native-host-packaging.test.js --section windows-bootstrap + - name: Inspect the staged npm package + working-directory: mcp + run: npm pack --dry-run --ignore-scripts --json + - name: Read package version + id: package-version + shell: pwsh + run: | + node -e "require('fs').appendFileSync(process.env.GITHUB_OUTPUT, 'version=' + require('./mcp/package.json').version + String.fromCharCode(10))" + - name: Upload version-bound Windows bootstrap artifacts + uses: actions/upload-artifact@v4 + with: + name: fsb-native-host-${{ steps.package-version.outputs.version }} + if-no-files-found: error + path: | + mcp/native-host/bin/win32-x64/fsb-native-host.exe + mcp/native-host/bin/win32-x64/fsb-native-host-registry.exe + mcp/native-host/bin/win32-arm64/fsb-native-host.exe + mcp/native-host/bin/win32-arm64/fsb-native-host-registry.exe + mcp/native-host/windows-artifacts.json + + runtime-payload: + name: native runtime payload (${{ matrix.os }}) + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: '20' + - name: Install MCP dependencies from the committed lock + run: npm --prefix mcp ci --no-audit --no-fund + - name: Build the package payload + run: npm --prefix mcp run build + - name: Prove complete offline payload and negative cases + run: node tests/mcp-native-host-packaging.test.js --section workflow-and-pack + website: name: showcase (build + crawler smoke) runs-on: ubuntu-latest @@ -88,7 +159,7 @@ jobs: all-green: name: all-green - needs: [extension, mcp-smoke, website] + needs: [extension, mcp-smoke, website, native-host-windows, runtime-payload] runs-on: ubuntu-latest steps: - run: echo "All required CI checks passed." diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml index a3bff5e79..275411628 100644 --- a/.github/workflows/npm-publish.yml +++ b/.github/workflows/npm-publish.yml @@ -4,20 +4,65 @@ on: push: tags: # Only mcp release tags trigger npm publish. Repo-wide milestone - # tags (v0.9.x) are markers, not publishable artifacts; including them - # caused every milestone tag to fail npm-publish (re-publishing an - # existing mcp version). + # tags are markers, not publishable artifacts. - 'mcp-v*' workflow_dispatch: jobs: + windows-bootstrap: + name: build native-host Windows artifacts + runs-on: windows-latest + outputs: + version: ${{ steps.package-version.outputs.version }} + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: '20' + - name: Install MCP dependencies + run: npm --prefix mcp ci --no-audit --no-fund + - name: Build x64 native-host bootstrap + shell: cmd + run: | + call "%ProgramFiles%\Microsoft Visual Studio\2022\Enterprise\Common7\Tools\VsDevCmd.bat" -arch=x64 -host_arch=x64 + if errorlevel 1 exit /b %errorlevel% + node scripts/build-native-host-windows.mjs --arch x64 + - name: Build arm64 native-host bootstrap + shell: cmd + run: | + call "%ProgramFiles%\Microsoft Visual Studio\2022\Enterprise\Common7\Tools\VsDevCmd.bat" -arch=arm64 -host_arch=x64 + if errorlevel 1 exit /b %errorlevel% + node scripts/build-native-host-windows.mjs --arch arm64 + - name: Verify PE architecture, embedded version, and checksums + shell: cmd + run: | + call "%ProgramFiles%\Microsoft Visual Studio\2022\Enterprise\Common7\Tools\VsDevCmd.bat" -arch=x64 -host_arch=x64 + if errorlevel 1 exit /b %errorlevel% + node scripts/build-native-host-windows.mjs --arch all --verify-only --metadata-out mcp/native-host/windows-artifacts.json + - name: Execute x64 bootstrap and safe registry-helper contract harnesses + run: node tests/mcp-native-host-packaging.test.js --section windows-bootstrap + - name: Read package version + id: package-version + shell: pwsh + run: | + node -e "require('fs').appendFileSync(process.env.GITHUB_OUTPUT, 'version=' + require('./mcp/package.json').version + String.fromCharCode(10))" + - name: Upload version-bound bootstrap payload + uses: actions/upload-artifact@v4 + with: + name: fsb-native-host-${{ steps.package-version.outputs.version }} + if-no-files-found: error + path: | + mcp/native-host/bin/win32-x64/fsb-native-host.exe + mcp/native-host/bin/win32-x64/fsb-native-host-registry.exe + mcp/native-host/bin/win32-arm64/fsb-native-host.exe + mcp/native-host/bin/win32-arm64/fsb-native-host-registry.exe + mcp/native-host/windows-artifacts.json + publish: + needs: windows-bootstrap runs-on: ubuntu-latest permissions: contents: write - defaults: - run: - working-directory: mcp steps: - uses: actions/checkout@v4 @@ -26,36 +71,107 @@ jobs: node-version: '20' registry-url: 'https://registry.npmjs.org' - - run: npm ci - - run: npm run build - - - name: Publish to npm - run: npm publish --access public + - name: Install exact MCP dependency tree + run: npm --prefix mcp ci --no-audit --no-fund + - name: Build MCP package + run: npm --prefix mcp run build + - name: Download version-bound Windows bootstrap payload + uses: actions/download-artifact@v4 + with: + name: fsb-native-host-${{ needs.windows-bootstrap.outputs.version }} + path: mcp/native-host + - name: Verify release tag and package version binding + if: github.event_name == 'push' + run: test "$GITHUB_REF_NAME" = "mcp-v${{ needs.windows-bootstrap.outputs.version }}" + - name: Pack the exact verified payload once + id: pack + working-directory: mcp + run: | + mkdir -p "$RUNNER_TEMP/fsb-npm-pack" + npm pack . --ignore-scripts --json --pack-destination "$RUNNER_TEMP/fsb-npm-pack" > "$RUNNER_TEMP/fsb-pack-receipt.json" + node -e "const fs=require('fs');const path=require('path');const r=require(process.env.RUNNER_TEMP+'/fsb-pack-receipt.json')[0];fs.appendFileSync(process.env.GITHUB_OUTPUT,'tarball='+path.resolve(process.env.RUNNER_TEMP,'fsb-npm-pack',r.filename)+String.fromCharCode(10))" + - name: Verify final tarball PE files, receipt, complete bundle, and offline negatives + run: node tests/mcp-native-host-packaging.test.js --section workflow-and-pack + env: + FSB_REQUIRE_REAL_WINDOWS_ARTIFACTS: '1' + FSB_PACKED_TARBALL: ${{ steps.pack.outputs.tarball }} + - name: Bind tarball, lock, receipt, and PE checksums + id: release-metadata + env: + TARBALL: ${{ steps.pack.outputs.tarball }} + run: | + node <<'NODE' + const { createHash } = require('crypto'); + const { mkdirSync, readFileSync, writeFileSync, appendFileSync } = require('fs'); + const { basename, join } = require('path'); + const hash = (algorithm, bytes) => createHash(algorithm).update(bytes).digest('hex'); + const packageManifest = require('./mcp/package.json'); + const lockBytes = readFileSync('./mcp/package-lock.json'); + const receiptBytes = readFileSync('./mcp/native-host/runtime-integrity.json'); + const receipt = JSON.parse(receiptBytes); + const windows = require('./mcp/native-host/windows-artifacts.json'); + if (windows.version !== packageManifest.version || windows.package !== packageManifest.name) { + throw new Error('Windows metadata package/version mismatch'); + } + const lockSha256 = hash('sha256', lockBytes); + if (receipt.lockSha256 !== lockSha256) throw new Error('runtime receipt lock mismatch'); + const tarballBytes = readFileSync(process.env.TARBALL); + const metadata = { + schema: 1, + packageName: packageManifest.name, + packageVersion: packageManifest.version, + tarball: basename(process.env.TARBALL), + tarballBytes: tarballBytes.length, + tarballSha512: hash('sha512', tarballBytes), + lockSha256, + integrityReceiptSha256: hash('sha256', receiptBytes), + peArtifacts: windows.artifacts.map(({ architecture, role, path, bytes, peMachine, sha256, packageVersion, roleMarker }) => ({ + architecture, + role, + path, + bytes, + peMachine, + sha256, + packageVersion, + roleMarker, + })), + }; + const directory = join(process.env.RUNNER_TEMP, 'fsb-release-metadata'); + const pathname = join(directory, `fsb-mcp-server-${packageManifest.version}-release-metadata.json`); + mkdirSync(directory, { recursive: true }); + writeFileSync(pathname, `${JSON.stringify(metadata, null, 2)}\n`); + appendFileSync(process.env.GITHUB_OUTPUT, `metadata=${pathname}\n`); + NODE + - name: Upload version-keyed release integrity metadata + uses: actions/upload-artifact@v4 + with: + name: fsb-mcp-release-metadata-${{ needs.windows-bootstrap.outputs.version }} + if-no-files-found: error + path: ${{ steps.release-metadata.outputs.metadata }} + - name: Publish the already-verified tarball to npm + run: npm publish "${{ steps.pack.outputs.tarball }}" --access public env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - - - name: Extract version - id: version - run: echo "version=$(node -p 'require("./package.json").version')" >> $GITHUB_OUTPUT - - name: Create GitHub Release uses: softprops/action-gh-release@v2 with: - name: "fsb-mcp-server v${{ steps.version.outputs.version }}" + name: "fsb-mcp-server v${{ needs.windows-bootstrap.outputs.version }}" body: | ## Install / Update ```bash - npx -y fsb-mcp-server@${{ steps.version.outputs.version }} + npx -y fsb-mcp-server@${{ needs.windows-bootstrap.outputs.version }} ``` Or install globally: ```bash - npm i -g fsb-mcp-server@${{ steps.version.outputs.version }} + npm i -g fsb-mcp-server@${{ needs.windows-bootstrap.outputs.version }} ``` Install into MCP clients: ```bash npx fsb-mcp-server install --all ``` + files: | + ${{ steps.release-metadata.outputs.metadata }} generate_release_notes: true diff --git a/.planning/PROJECT.md b/.planning/PROJECT.md index eb8ac53ee..472d7270d 100644 --- a/.planning/PROJECT.md +++ b/.planning/PROJECT.md @@ -10,6 +10,8 @@ FSB is an AI-powered browser automation Chrome extension that executes tasks thr ## Current State +**Active milestone progress:** Phases 57-64 are complete (48/51 requirements). Phase 64 verified 4/4 roadmap success criteria, 66/66 plan truths, 36/36 key links, MULTI-01..03, and all ten registered HIGH/CRITICAL threats; its guarded 25-command matrix and repository test process are green after a clean code-review fix loop. Three genuine OpenCode account/process/browser/accessibility scenarios join the prior 42 in the user-directed milestone-end UAT sweep. Phase 65 (Codex Adapter) is the final implementation phase in v0.9.91; backlog Phase 999.1 remains outside this milestone. + **Last completed:** v1.2.0 Showcase i18n Completeness — Phases 52-56 shipped 2026-07-09. Full-page translation audit, 5-id resync + stats-274 retirement + hero/CTA transcreation, stats lint gate flip, permanent `verify-translation-drift` CI gate, and WARNING-02 locale-cookie redirect fix. VISUAL-01 browser UAT remains human_needed (`53-VISUAL-QA.md`). > **Post-v1.2 supersession (2026-07-15):** The showcase localization follow-up @@ -23,7 +25,34 @@ FSB is an AI-powered browser automation Chrome extension that executes tasks thr - v1.0.0 Full App Catalog (OpenTabs Parity) -- archived 2026-06-29; T1 expansion debt carried into v1.1.0 -## Current Milestone: v1.2.0 Showcase i18n Completeness +## Current Milestone: v0.9.91 MCP Clients as Providers + +**Goal:** Make installed agent CLIs (Claude Code first) first-class side-panel providers -- FSB captures which MCP clients the user installs/connects, presents them as key-less providers in a renamed Providers panel, and delegates side-panel tasks to a spawned agent CLI that drives the browser back through FSB's own MCP tools. + +**Target features:** +- **Agent identity capture (Phase 57 complete)** -- onboarding copy intent, MCP initialize `clientInfo`, and the 21-client installed inventory now converge into durable clicked/connected/installed evidence and one guarded `getMcpClients` view. The wire additions remain optional and legacy `agent:register` payloads stay byte-compatible. +- **Providers panel (Phase 58 complete)** -- "API Configuration" is now "Providers"; explicit `api` vs `agent` kinds preserve BYOK settings; agent details report evidence and conditional billing truth; one advisory recommendation follows live > installed > copy-clicked > xAI. Live visual/interaction UAT is deferred to the milestone-end sweep. +- **Reverse-request security foundation (Phase 59 complete)** -- additive `ext:*` frames now cross the existing loopback bridge behind exact Host/Origin/session authority, deterministic capable-relay routing, secret redaction, topology exact-once cleanup, and a permanent forbidden-flag prebuild gate. Four live pairing/lifecycle/accessibility checks remain pending for the milestone-end sweep. +- **Adapter contract + Claude Code MVP (Phase 60 complete)** -- the serve daemon owns the five-method adapter contract and safe spawn supervisor; Claude Code runs through the pinned task-mode profile, stdin-only prompt, recorded stream fixture, exact process-tree cleanup, and restart recovery. Seven genuine CLI/OS/browser checks remain in the milestone-end sweep. +- **Delegation UX + persistence (Phase 61 complete)** -- the side panel has consent, live streaming progress, background-tab ownership, Take Control, Stop/reclaim, honest usage, 20-second liveness, and service-worker-eviction recovery. Eight genuine consent/theme/handoff/stream/endurance/POSIX/restart checks remain pending. +- **Drift gate + compatibility doctor (Phase 62 complete)** -- one daemon-owned compatibility matrix feeds offline production-parser CI, doctor text/JSON, authenticated durable browser projection, protocol-drift diagnostics, and non-mutating Providers compatibility states. Three genuine installed/rendered/accessibility checks remain pending. +- **Native wake path (Phase 63 complete)** -- the optional native-messaging host wakes or attaches only to `serve` through exact one-shot framing, exact-owned cross-platform registration, read-only diagnostics, and a background-authoritative wake UI with doctor fallback; it never spawns agent CLIs, and the Phase 59 channel-gate files are byte-identical to the phase base. Eight genuine install/Chrome/accessibility checks remain pending. +- **OpenCode adapter (Phase 64 complete)** -- the unchanged five-method `AgentProviderAdapter` now drives both cold `opencode run` tasks and verified attaches to an FSB-owned `opencode serve`; exact 1.14.25 private policy, schema-derived JSONL fixture, first-commit drift coverage, transient server authentication, role-aware crash recovery, provider-neutral browser persistence, and honest unknown billing are all verified. Three genuine live scenarios remain pending. +- **Codex adapter (Phase 65 next)** -- complete the stable adapter roster with hermetic `codex exec --json`, source-pinned fixture/drift coverage, and honest ChatGPT OAuth versus API-key versus unauthenticated disclosure. Gemini remains explicitly deferred until a live help capture and fixture pin exist. Task-mode only for v0.9.91. + +**Key context:** +- The spawn channel is security-critical (RCE-adjacent): extension-origin gating + shared secret + explicit consent tiers required. Bridge already rejects untrusted browser origins (`tests/mcp-bridge-topology.test.js`). +- Daemon lifecycle constraint through Phase 62: the extension has NO nativeMessaging permission and cannot wake any process. Phase 63 adds one optional wake host that starts or attaches only to `serve`; all agent spawn authority stays behind the existing daemon channel gates. +- INV-01 carries forward: MCP wire contracts stay byte-stable -- all bridge message types and tools are additive. +- Test suite has source-pin tripwires on extension files (token counts/substrings); extension-side wiring must run the suite from the first commit. +- Delegation becomes the fifth `EXECUTION_MODES` entry in `extension/ai/engine-config.js` (autopilot, mcp-manual, mcp-agent, dashboard-remote, + delegated). +- This milestone completes the v0.9.45rc1 arc (background agents retired in favor of external agent runtimes) and the v0.9.36 deferred item "derive trusted MCP client identity from connection/handshake metadata". +- Phases continue from 57. +- Per user instruction, all live/human UAT checklists accumulate without fabricated passes and are executed as one milestone-end gate; each phase still requires green automated/source verification and clean review before advancing. + +## Last Milestone: v1.2.0 Showcase i18n Completeness + +**Status:** Archived 2026-07-09. Phases 52-56 shipped; audit passed. Archive files under `.planning/milestones/v1.2.0-*`. VISUAL-01 browser UAT remains human_needed (`53-VISUAL-QA.md`). **Goal:** Close the translation gap that reopened after v0.9.63 shipped -- full, drift-free coverage across all six supported locales (en, es, de, ja, zh-CN, zh-TW) for every showcase marketing page plus the stats page, the long-deferred locale-cookie redirect bug, and a CI gate that catches future drift automatically. @@ -416,7 +445,33 @@ Carry-forward backlog candidates: ### Active -(Milestone v1.2.0 Showcase i18n Completeness -- requirements and roadmap to be defined; phases continue from v1.1.0's Phase 51 -> start at Phase 52. v1.1.0 T1 App Execution Expansion is archived; this milestone returns to the showcase-site i18n surface last touched in v0.9.63.) +(Milestone v0.9.91 MCP Clients as Providers -- Phases 57-63 are verified complete. NATIVE-01..04 validated in Phase 63: Native-Messaging Host. Remaining active requirements are MULTI-01..06; 45/51 requirements are complete.) + +### Validated (v0.9.91) + +- [x] IDENT-01: Onboarding copy actions persist durable, aggregated per-client intent without changing clipboard feedback -- Phase 57. +- [x] IDENT-02: Stdio and streamable-HTTP MCP initialization identity crosses the existing bridge through optional additive registration evidence -- Phase 57. +- [x] IDENT-03: Sanitized client identity persists on live AgentRecords and canonical durable connected rows, with reconnects updating in place -- Phase 57. +- [x] IDENT-04: The daemon reports the full platform registry inventory with fixed, shell-free Claude Code version probing and dual tolerant delivery -- Phase 57. +- [x] IDENT-05: A guarded fresh-on-read `getMcpClients` action returns the durable clicked/installed/connected plus live evidence union -- Phase 57. +- [x] PROV-01: Providers is the canonical route and legacy `#api-config` normalizes without breaking source-pin contracts -- Phase 58. +- [x] PROV-02: Seven API and three agent providers use explicit closed kind/id domains -- Phase 58. +- [x] PROV-03: Agent mode removes API controls and presents truthful install, connection, account, setup, usage, and credential-boundary details -- Phase 58. +- [x] PROV-04: Agent intent remains isolated from API-only `modelProvider`; latent BYOK state survives switching, refresh, delayed work, and cancellation races -- Phase 58. +- [x] PROV-05: Exactly one non-selecting recommendation follows live > installed > clicked > xAI with raw identities excluded -- Phase 58. +- [x] PROV-06: Usage and billing avoid fabricated currency or subscription claims; unknown auth reports Billing not reported and links fixed official destinations -- Phase 58. +- [x] CHAN-01: A separate strict `ext:request` / `ext:event` / `ext:response` family crosses the existing bridge while historical MCP and default relay bytes remain frozen -- Phase 59. +- [x] CHAN-02: Optional `agent-spawn` capabilities route reverse requests local-first, then to the first capable relay, with a typed offline fallback and no default production advertisement -- Phase 59. +- [x] CHAN-03: Loopback bind, exact Host, exact extension Origin, pre-registration classification, and per-frame current-session checks enforce the browser-to-daemon trust boundary -- Phase 59. +- [x] CHAN-04: Explicit session-only pairing uses a 32-byte daemon-session secret, durable exact Origin binding, reset/rebind, secret-free authorization probe, and rotation revocation -- Phase 59. +- [x] CHAN-05: Caller and diagnostic-sink redaction remove bridge-secret tokens and raw/interior-token tests remain green -- Phase 59. +- [x] CHAN-06: Hub/relay/stale-socket churn settles reverse work exactly once, clears ownership maps, and never replays automatically -- Phase 59. +- [x] CHAN-07: Every MCP build recursively rejects forbidden agent auto-approval flags before TypeScript compilation -- Phase 59. +- [x] ADAPT-01..05: The serve-owned five-method adapter contract, shell-free supervisor, stdin-only prompt path, exact process-tree cancellation, and orphan recovery are enforced -- Phase 60. +- [x] CLAUDE-01..04: Claude Code uses the pinned hermetic task-mode profile, production stream parser/fixture, closed compatibility classifier, and no session persistence -- Phase 60. +- [x] UX-01..06: Consent, delegated routing, live feed, background ownership, Take Control/Stop, and honest usage/offline recovery are implemented without weakening BYOK behavior -- Phase 61. +- [x] LIFE-01..04: Session-backed event persistence, exact recovery, active-only heartbeat, hold/resume, and daemon-restart classification survive MV3 worker eviction -- Phase 61. +- [x] DRIFT-01..04: Registry-driven offline drift CI, safe doctor text/JSON, sanitized rate-limited runtime diagnostics, and one canonical browser-consumed compatibility matrix are enforced -- Phase 62. ### Validated (v0.9.99) @@ -681,4 +736,4 @@ This document evolves at phase transitions and milestone boundaries. 4. Update Context with current state --- -*Last updated: 2026-07-08 -- Phase 52 (Full-Page Translation Completeness Audit) complete. Next: Phase 53 (Trans-Unit Resync, Stats Translation & Transcreation Review).* +*Last updated: 2026-07-21 -- Phase 64 OpenCode Adapter deterministically complete; 45 live scenarios remain deferred to the milestone-end sweep. Next: Phase 65 Codex Adapter.* diff --git a/.planning/REQUIREMENTS.md b/.planning/REQUIREMENTS.md index 495c2aa20..a9bf7c9d0 100644 --- a/.planning/REQUIREMENTS.md +++ b/.planning/REQUIREMENTS.md @@ -1,59 +1,111 @@ # Requirements: FSB (Full Self-Browsing) -> **Historical-scope note (2026-07-15):** CI-05 and the dashboard entry under -> Out of Scope record the completed v1.2.0 milestone decision. A later showcase -> localization follow-up localized the dashboard and removed its lint exclusion; -> `showcase/angular/src/locale/I18N-BOUNDARIES.md` is the current policy. - -**Defined:** 2026-07-07 +**Defined:** 2026-07-11 **Core Value:** Reliable single-attempt execution -- the AI decides correctly, the mechanics execute precisely. -**Milestone:** v1.2.0 Showcase i18n Completeness +**Milestone:** v0.9.91 MCP Clients as Providers ## v1 Requirements Requirements for this milestone. Each maps to roadmap phases. -### Translation Audit +### IDENT -- Agent Identity Capture + +- [x] **IDENT-01**: When the user clicks a copy-to-clipboard button on the onboarding MCP-install screen for a specific client (`claude-code`, `cursor`, `vscode`, `windsurf`, `codex`, `opencode`, `openclaw`, `claude-desktop`, `all`), FSB records that client id (with timestamp, deduplicated, all-clients aggregated for multi-select cases) into a durable `fsbAgentProviders.clicked` list in `chrome.storage.local`. +- [x] **IDENT-02**: When an MCP client completes its `initialize` handshake with `fsb-mcp-server` (over any transport: stdio, streamable-HTTP, or the ws://7225 bridge), FSB captures the caller's `clientInfo.name` and `clientInfo.version` and threads them to the extension via an additive field on the existing `agent:register` bridge payload without breaking any existing consumer of that payload (INV-01). +- [x] **IDENT-03**: The extension's agent registry stamps captured `clientInfo` onto each live `AgentRecord` and rolls the identity up into a durable `fsbAgentProviders.connected` entry that survives service-worker eviction and Chrome restart, keyed so re-connections update rather than duplicate. +- [x] **IDENT-04**: `fsb-mcp-server` can enumerate installed MCP-capable clients on the current machine by inspecting the paths already known to `platforms.ts` (per-OS `configPath` for file-mode clients; ` --version` probe for cli-mode `claude-code`) and report each as `installed` / `not-installed` with any parseable version. +- [x] **IDENT-05**: A `getMcpClients` extension runtime message returns a merged view (`clicked` ∪ `installed` ∪ `connected`) with per-client status, so UI surfaces read one consistent structure instead of assembling it themselves. + +### PROV -- Providers Panel + +- [x] **PROV-01**: The control panel section formerly labeled "API Configuration" is labeled "Providers" (heading, nav label, and any anchor `#api-config` continues to work as a redirect to the new `#providers` anchor for existing bookmarks). +- [x] **PROV-02**: Each provider has an explicit `providerKind` value of either `api` (the existing 7 BYOK LLM providers) or `agent` (a locally installed agent CLI); the kind determines which fields render. +- [x] **PROV-03**: When an `agent`-kind provider is selected, the API-key input, key-URL hint, and per-model key-format hint are hidden; the panel shows instead the provider's install status, auth status (from the CLI's own login state where surfaceable), connection status, and a short "uses your subscription -- no API key needed" caption. +- [x] **PROV-04**: When the user has both an active `agent` provider selection and a valid BYOK key for an `api` provider, `universal-provider.js` continues to see only `api`-kind provider values; the two selections do not collide, and switching between them preserves the other's configuration (INV-03 provider parity for the BYOK side). +- [x] **PROV-05**: The panel visually marks exactly one provider as "Recommended" per session, chosen by a ground-truth cascade: highest-priority = a provider whose CLI is currently connected via MCP `initialize`, next = a provider whose CLI is installed on disk, next = a provider whose copy button the user clicked during onboarding, fallback = the current xAI-default recommendation. The panel never auto-switches the user's selection; the badge is advisory only. +- [x] **PROV-06**: Cost/usage rows for `agent`-kind providers never display fabricated dollar amounts; they display token count, turn count, and duration and label the run as "included in your subscription", with a link to the vendor's current billing page (copy must not promise "free" or "unlimited"). + +### CHAN -- Delegation Channel & Security Foundation + +- [x] **CHAN-01**: A new bridge message-type family (`ext:request` / `ext:response` / `ext:event`) transports extension→daemon reverse requests over the existing ws://localhost:7225 bridge without changing the byte-shape of any existing `MCPMessageType` value (INV-01 additive proof). +- [x] **CHAN-02**: A relay process signals its ability to fulfill spawn requests by advertising `capabilities: ['agent-spawn']` in its `relay:hello`; the hub routes each `ext:request` locally (if itself the daemon) or to the first relay advertising the required capability. +- [x] **CHAN-03**: The bridge rejects every incoming `ext:*` frame whose WebSocket upgrade did not carry an `Origin` header matching a durable per-install FSB-extension-id allowlist and whose `Host` header is not exactly `127.0.0.1` (or `localhost`) at the loopback port. +- [x] **CHAN-04**: A per-install >=32-byte shared secret is provisioned once between the extension and the daemon, transported only in the `Sec-WebSocket-Protocol` upgrade header (never in URL, never in payloads, never in logs), rotated on daemon restart, and required on every `ext:*` frame. +- [x] **CHAN-05**: `redactForLog` and diagnostic ring-buffer writes strip any string matching the shared-secret token pattern; the drift gate fails the build if a raw secret substring appears in any tracked log fixture. +- [x] **CHAN-06**: The bridge topology test suite covers hub-exit-mid-delegation and relay-mid-`ext:*`-frame scenarios; existing hub-exit-promotion tests still pass byte-for-byte. +- [x] **CHAN-07**: A permanent CI grep gate fails the build if the strings `--dangerously-skip-permissions`, `--yolo`, or `--auto` appear anywhere in `mcp/src/agent-providers/**`, so those flags can never enter the spawn path in any future patch. + +### ADAPT -- Adapter Contract & Spawn Supervisor -- [x] **AUDIT-01**: A full-page audit produces a per-page, per-locale, per-trans-unit verdict distinguishing "coverage" (i18n-marked + translated target exists) from "currency" (target still matches the current English source) across every current showcase route. -- [x] **AUDIT-02**: The audit traces the orphaned `translations.stats-274.*.json` artifacts end-to-end into (or explicitly out of) the live `messages..xlf` files the build consumes. +- [x] **ADAPT-01**: An `AgentProviderAdapter` TypeScript interface in `mcp/src/agent-providers/` defines exactly five methods: `detect() -> {installed, version, authState, binary}`, `buildSpawn(task, ctx) -> SpawnSpec`, `parseEvents(stream) -> AsyncIterable`, `kill(child, {grace}) -> Promise`, and `caps() -> AdapterCapabilities`. +- [x] **ADAPT-02**: A `SpawnSupervisor` module living in the `fsb-mcp-server serve` daemon accepts a validated spawn request, looks up the requested adapter, constructs argv from adapter output plus a daemon-controlled flag allowlist (unknown payload keys rejected), and spawns the child with `{ detached: true, windowsHide: true, stdio: ['pipe','pipe','pipe'] }` and an environment with `ANTHROPIC_API_KEY`/`OPENAI_API_KEY`/`GEMINI_API_KEY` scrubbed. +- [x] **ADAPT-03**: The supervisor never invokes a shell; the user's prompt travels only via `child.stdin` (never argv, never `sh -c`) so shell metacharacters and Windows `.cmd`-shim EINVAL (Node CVE-2024-27980) cannot execute. +- [x] **ADAPT-04**: A `stop`/cancel request triggers SIGTERM at the process-group level (POSIX `process.kill(-child.pid, 'SIGTERM')` after `spawn({detached:true})`, Windows `taskkill /pid /T /F`), escalates to SIGKILL after a grace window, and blocks resolving the delegation until either an exit-signal is observed or the daemon confirms no descendant matches remain. +- [x] **ADAPT-05**: On daemon startup, the supervisor scans for orphaned children matching prior adapter fingerprints and kills them before accepting new spawn requests (recovery from crash). -### Translation Resync +### CLAUDE -- Claude Code MVP -- [x] **RESYNC-01**: Every trans-unit the audit identifies as drifted (English source changed without a matching translation update) is resynced across all 5 translated locales (es, de, ja, zh-CN, zh-TW), preserving `DO-NOT-TRANSLATE.md` brand/term rules and `` placeholder alignment. -- [x] **RESYNC-02**: The stats page reaches full translation coverage across all 5 locales. -- [x] **RESYNC-03**: Hero headlines and primary CTAs (~10-20 strings) receive a transcreation-lens review rather than literal translation. +- [x] **CLAUDE-01**: The Claude Code adapter spawns `claude -p --verbose --output-format stream-json --include-partial-messages --strict-mcp-config --mcp-config --agents --agent fsb --permission-mode dontAsk --allowedTools "mcp__fsb" --disallowedTools "Bash,Edit,Write,NotebookEdit,WebFetch,WebSearch" --max-turns 40 --no-session-persistence`, or the version-appropriate equivalent selected by `detect()` output. +- [x] **CLAUDE-02**: The user's task prompt is sent to the spawned CLI via stdin only; the adapter constructs no argv fragment containing user-supplied text. +- [x] **CLAUDE-03**: The adapter's `parseEvents` translates the CLI's stream-json events (`system/init`, `assistant`, `user`, tool-use events, `system/api_retry`, `result`) into a normalized `AgentEvent` schema (`type`, `sessionId`, `payload`), fails loud on unknown event types (surfaced as `agent_protocol_drift` diagnostic), and is covered by a recorded JSONL fixture under `tests/fixtures/agent-streams/claude-code-2.1.177/` so CI runs without a live CLI. +- [x] **CLAUDE-04**: The Claude Code adapter's `detect()` fingerprints the binary via `claude --version`, compares against a minimum supported version, and reports `installed=false` with a doctor-diagnostic message rather than spawning if the version predates the verified stream-json contract. -### CI Enforcement +### UX -- Delegation UX -- [x] **CI-01**: The `lint:i18n` ignore-pattern for `src/app/pages/stats/**` is removed only after RESYNC-02 is verified complete. -- [x] **CI-02**: A new `verify-translation-drift.mjs` CI gate fails the build when any trans-unit's English `` text changes without a matching update in one of the 5 translated locale files, diffing per trans-unit `id` (not whole-file/line-count). -- [x] **CI-03**: The new drift gate is back-tested against this repo's own git history (known-clean churn commits plus commit `6d3ad363`) before being wired hard-fail into CI, so it doesn't chronically false-positive on routine `ng extract-i18n` re-extraction churn. -- [x] **CI-04**: The drift gate's target-locale list is derived dynamically from the existing locale registry, not hardcoded. -- [x] **CI-05**: The dashboard page's `lint:i18n` exclusion is documented as a permanent, intentional architectural boundary (authenticated app surface, not marketing content) rather than left as open deferred debt. +- [x] **UX-01**: A fifth entry `delegated` in `EXECUTION_MODES` (`extension/ai/engine-config.js`) defines: `uiFeedbackChannel: 'popup-sidepanel'`, `animatedHighlights: true`, `safetyLimits: { wallClockMs, eventSilenceMs }` (no iteration cap -- the loop runs in the spawned CLI, not `runAgentLoop`), and is selected when the active provider is `agent`-kind. +- [x] **UX-02**: The side panel renders a live per-run streaming feed with distinct card types for init (client, model, session id, allowed tools), tool-call (name, args summary, tab id), retry (typed error class), and result (usage summary), driven by the normalized `AgentEvent` stream. +- [x] **UX-03**: Before FSB spawns any agent CLI for the first time, the user sees an explicit consent card that names the CLI, what it will be permitted to do (drive the FSB MCP tools on the user's live browser), and what it will not be permitted to do (edit files, run shell, fetch arbitrary URLs). A per-run confirm-to-continue toggle is on by default and can be disabled per provider only via an explicit "trust this agent" setting. +- [x] **UX-04**: A prominent Stop button in the side panel triggers `stopDelegatedTask`, which routes to the supervisor's kill and, on confirmed exit, releases every tab that was owned by the spawned agent (per v0.9.60 ownership) and reports "Agent stopped, N tab(s) released" in the feed. +- [x] **UX-05**: A delegated run opens by default in a new background tab; when the user activates the tab that the agent is driving, a persistent "Take control" affordance appears; clicking it pauses the agent (v0.9.60 ownership release + supervisor grace hold), lets the user interact, and offers "Resume with agent" to give ownership back. +- [x] **UX-06**: A post-run summary card displays tokens (in/out/total), turn count, wall-clock duration, cost bucket (`included in your subscription` for agent kind; real USD for api kind), and a per-tool-call breakdown, expandable to the full tool-call log for the run. -### Locale Routing +### LIFE -- Lifecycle & Persistence -- [x] **ROUTE-01**: A picker-set `fsb-locale` cookie naming a valid, non-default supported locale redirects the bare-`/` request to that locale's subpath instead of short-circuiting to the EN prerender (closes WARNING-02). -- [x] **ROUTE-02**: The default-locale case (cookie value = `en`) still falls through correctly without redirecting to a 404ing `/en/` path. +- [x] **LIFE-01**: Every progress event received from the supervisor is written to `chrome.storage.session` under a per-delegation key before it fans out to UI subscribers, so a MV3 service worker eviction mid-run reloads exactly the delivered feed on re-open. +- [x] **LIFE-02**: While a delegation is active, the extension pings the bridge every 20 s over the existing WS heartbeat channel to keep the Chrome 116+ SW-lifetime extension applied; if 3 heartbeats are missed the extension shows a `daemon:disconnected` fallback that offers a doctor-relaunch button but does not attempt an in-extension restart. +- [x] **LIFE-03**: If `fsb-mcp-server serve` is not running when a delegated send is attempted, the side panel shows an "Agent offline" state with a deep-link to `fsb-mcp-server doctor` output and does not enqueue or optimistically show the message. +- [x] **LIFE-04**: On daemon restart while a delegation was mid-flight, the supervisor does not re-adopt any surviving spawned CLI; it kills it (LIFE-04 restart-is-clean) and reports `daemon_restart_lost_run` in the side panel so the user knows the run ended. -### Visual QA +### DRIFT -- CI Drift-Smoke Gate & Doctor Extensions -- [ ] **VISUAL-01**: German and zh-CN/zh-TW copy on the highest-copy-density routes receives a targeted manual visual spot-check for text-expansion/line-wrap issues. +- [x] **DRIFT-01**: A CI job runs each shipped adapter against a canned prompt fixture, asserts a known event-type sequence and the presence of required fields on `system/init` and `result`, and fails the build on unknown event types, missing fields, or a `--version` outside the compatibility matrix. +- [x] **DRIFT-02**: `fsb-mcp-server doctor` gains a per-adapter section reporting: binary path, version, auth state (parseable where the CLI exposes it), shared-secret presence, and the current spawn-secret rotation age. +- [x] **DRIFT-03**: The diagnostics ring buffer classifies drift events as `agent_protocol_drift` (with adapter id, expected vs observed) and rate-limits duplicate entries at the existing 1-per-10s bucket. +- [x] **DRIFT-04**: The `doctor` output includes a machine-readable adapter compatibility matrix that both CI and the extension can read to render "supported / degraded / unsupported" states without hardcoding versions in extension code. + +### NATIVE -- Native-Messaging Host + +- [x] **NATIVE-01**: `fsb-mcp-server install --native-host` writes the platform-appropriate native-messaging host manifest (macOS `~/Library/Application Support/Google/Chrome/NativeMessagingHosts/`, Linux `~/.config/google-chrome/NativeMessagingHosts/`, Windows registry `HKCU\Software\Google\Chrome\NativeMessagingHosts\`), allowing the FSB extension id as the sole caller, and installs a host binary that wakes the daemon on demand. +- [x] **NATIVE-02**: The extension's manifest gains a `nativeMessaging` permission entry (additive, no other permission changes), and the extension detects native-host presence at boot; when present, an "Agent offline" state auto-attempts a wake before showing the doctor deep-link. +- [x] **NATIVE-03**: The native host itself does not spawn agents; it only starts `fsb-mcp-server serve` (or attaches to a running one) and exits after handoff. All spawn authority remains inside the `serve` daemon behind the CHAN gates. +- [x] **NATIVE-04**: `fsb-mcp-server uninstall --native-host` removes the manifest, and `doctor` reports native-host install state including manifest path and any Chrome allowlist mismatch. + +### MULTI -- Additional Adapters + +- [x] **MULTI-01**: An OpenCode adapter (`mcp/src/agent-providers/opencode.ts`) implements the `AgentProviderAdapter` contract with `caps.serverMode=true`; the supervisor either spawns `opencode run` cold or attaches to a running `opencode serve` per the adapter's `buildSpawn` output (contract-stresser: the ADAPT contract must accommodate both spawn and attach without hardcoding). +- [x] **MULTI-02**: The OpenCode adapter ships a pinned agent definition (equivalent to Claude Code's `--agents fsb`) using OpenCode's `agent create` / `agents` config surface, keyed to a version pinned during phase spike. +- [x] **MULTI-03**: A recorded OpenCode JSONL fixture under `tests/fixtures/agent-streams/opencode-1.14.25/` (or the latest pinned version) proves the adapter's event schema in CI without a live CLI. +- [x] **MULTI-04**: A Codex adapter (`mcp/src/agent-providers/codex.ts`) implements the `AgentProviderAdapter` contract, invoking `codex exec --json` with the current-verified flag set (v0.142.5 as baseline: use `--ephemeral` + `--ignore-user-config` for hermeticity; do not use the deprecated `--full-auto`). +- [x] **MULTI-05**: The Codex adapter's `detect()` correctly identifies auth via ChatGPT OAuth vs API key vs unauthenticated and surfaces the state in the provider panel so the user knows which billing bucket a run will hit. +- [x] **MULTI-06**: A schema-derived Codex JSONL contract fixture pins the event schema in CI with `liveCapturePending: true` until genuine UAT capture, and the adapter's `caps()` correctly reports `chatMode: false` for v0.9.91 (task-mode only across all adapters). ## v2 Requirements Deferred to future release. Tracked but not in current roadmap. -### Translation Quality +### Chat-Mode Continuity + +- **CHAT-FUTURE-01**: Adapters expose `caps.chatMode: true` and the side panel maps a chat thread to `--resume ` (Claude Code) / `codex resume` / `gemini --resume` / `opencode --continue` per adapter. +- **CHAT-FUTURE-02**: The daemon pins per-thread working directory so `claude --resume` finds its history. + +### Gemini CLI Adapter -- **QA-01**: Native-speaker/bilingual-fluent QA pass across all resynced trans-units and the stats page (this milestone accepts AI-translation quality; a full re-review is a future quality bar, not reapplication of an existing one). +- **GEMINI-FUTURE-01**: Gemini CLI adapter after a live `--help` capture and JSONL schema pinning (v0.9.91 lacked a local binary to verify against). -### Tooling +### Broader Agent Ecosystem -- **I18N-FUTURE-01**: Migrate the stats page off its ad hoc `translations.stats-274.*.json` mechanism into the main XLIFF pipeline. -- **I18N-FUTURE-02**: Full automated per-locale visual regression/screenshot-diffing pipeline. -- **I18N-FUTURE-03**: Translation-freshness / "last synced" reporting surface. +- **ACP-FUTURE-01**: ACP-based adapter unification (`@zed-industries/agent-client-protocol`) once ≥2 non-Claude adapters have shipped and proven the contract shape. +- **REMOTE-FUTURE-01**: Remote/mobile delegation surfaces (Happy-style approval flows) -- explicitly localhost-only in v0.9.91. ## Out of Scope @@ -61,36 +113,91 @@ Explicitly excluded. Documented to prevent scope creep. | Feature | Reason | |---------|--------| -| Dashboard page translation | Authenticated app surface, not marketing content -- explicit permanent boundary (see CI-05), not deferred debt | -| Commercial TMS adoption (Lokalise, Crowdin, Smartling, Phrase, doloc.io) | Site scale (6 locales, ~942 trans-units, ~7 routes) doesn't warrant one; milestone brief requires a no-paid-SaaS solution | -| `ng-extract-i18n-merge` adoption | Legitimate but separate future decision; rewrites `angular.json`'s `extract-i18n` builder, not required to satisfy this milestone's CI-gate requirement | -| Re-deriving or changing the supported-locale list | Fixed at en/es/de/ja/zh-CN/zh-TW per v0.9.63's `LocaleService` + locale-constants module -- not up for debate | +| Embedding `@anthropic-ai/claude-agent-sdk` in FSB | Anthropic policy prohibits third-party products from using consumer subscription auth via the SDK (enforcement Apr 4 2026); shelling to the user's installed `claude` binary is the only compliant "no API key" path. | +| Proxying / spoofing subscription OAuth tokens | Banned outright by Anthropic Apr 4 2026; would be product-killing regardless of technical feasibility. | +| Bundling / silent-installing agent CLIs | Wrong shape for a Chrome extension + npm daemon; would violate Chrome Web Store distribution rules and add attack surface. | +| PTY / TUI screen-scraping of agent CLIs | Structured headless interfaces exist for all four target CLIs; scraping is a maintenance sinkhole. | +| Any `--dangerously-skip-permissions` / `--yolo` / `--auto` flag in the spawn path | 1-click RCE for any prompt-injection incident; CHAN-07 grep gate makes this a permanent invariant. | +| Fabricated dollar costs on subscription-backed runs | Cline established the $0.00 convention; PROV-06 codifies it. | +| Auto-switching the user's provider when a "better" agent appears | Advisory badge only (PROV-05); user consent is the load-bearing property. | +| Forcing users away from BYOK when an agent is available | INV-03 provider parity carries forward; BYOK stays first-class. | +| `Firefox` support | Deferred at project level; MV3 nativeMessaging in NATIVE requirements is Chrome-specific for v0.9.91. | +| Native host that itself spawns agent CLIs | NATIVE-03 explicitly forbids this; all spawn authority lives inside the serve daemon behind CHAN gates. | ## Traceability -Which phases cover which requirements. Updated during roadmap creation. +Which phases cover which requirements. Populated during roadmap creation. | Requirement | Phase | Status | |-------------|-------|--------| -| AUDIT-01 | Phase 52 | Complete | -| AUDIT-02 | Phase 52 | Complete | -| RESYNC-01 | Phase 53 | Complete | -| RESYNC-02 | Phase 53 | Complete | -| RESYNC-03 | Phase 53 | Complete | -| CI-01 | Phase 54 | Complete | -| CI-02 | Phase 55 | Complete | -| CI-03 | Phase 55 | Complete | -| CI-04 | Phase 55 | Complete | -| CI-05 | Phase 54 | Complete | -| ROUTE-01 | Phase 56 | Complete | -| ROUTE-02 | Phase 56 | Complete | -| VISUAL-01 | Phase 53 | human_needed | +| IDENT-01 | Phase 57 | Complete | +| IDENT-02 | Phase 57 | Complete | +| IDENT-03 | Phase 57 | Complete | +| IDENT-04 | Phase 57 | Complete | +| IDENT-05 | Phase 57 | Complete | +| PROV-01 | Phase 58 | Complete | +| PROV-02 | Phase 58 | Complete | +| PROV-03 | Phase 58 | Complete | +| PROV-04 | Phase 58 | Complete | +| PROV-05 | Phase 58 | Complete | +| PROV-06 | Phase 58 | Complete | +| CHAN-01 | Phase 59 | Complete | +| CHAN-02 | Phase 59 | Complete | +| CHAN-03 | Phase 59 | Complete | +| CHAN-04 | Phase 59 | Complete | +| CHAN-05 | Phase 59 | Complete | +| CHAN-06 | Phase 59 | Complete | +| CHAN-07 | Phase 59 | Complete | +| ADAPT-01 | Phase 60 | Complete | +| ADAPT-02 | Phase 60 | Complete | +| ADAPT-03 | Phase 60 | Complete | +| ADAPT-04 | Phase 60 | Complete | +| ADAPT-05 | Phase 60 | Complete | +| CLAUDE-01 | Phase 60 | Complete | +| CLAUDE-02 | Phase 60 | Complete | +| CLAUDE-03 | Phase 60 | Complete | +| CLAUDE-04 | Phase 60 | Complete | +| UX-01 | Phase 61 | Complete | +| UX-02 | Phase 61 | Complete | +| UX-03 | Phase 61 | Complete | +| UX-04 | Phase 61 | Complete | +| UX-05 | Phase 61 | Complete | +| UX-06 | Phase 61 | Complete | +| LIFE-01 | Phase 61 | Complete | +| LIFE-02 | Phase 61 | Complete | +| LIFE-03 | Phase 61 | Complete | +| LIFE-04 | Phase 61 | Complete | +| DRIFT-01 | Phase 62 | Complete | +| DRIFT-02 | Phase 62 | Complete | +| DRIFT-03 | Phase 62 | Complete | +| DRIFT-04 | Phase 62 | Complete | +| NATIVE-01 | Phase 63 | Complete | +| NATIVE-02 | Phase 63 | Complete | +| NATIVE-03 | Phase 63 | Complete | +| NATIVE-04 | Phase 63 | Complete | +| MULTI-01 | Phase 64 | Complete | +| MULTI-02 | Phase 64 | Complete | +| MULTI-03 | Phase 64 | Complete | +| MULTI-04 | Phase 65 | Complete | +| MULTI-05 | Phase 65 | Complete | +| MULTI-06 | Phase 65 | Complete | **Coverage:** -- v1 requirements: 13 total -- Mapped to phases: 13 -- Unmapped: 0 ✓ +- v1 requirements: 51 total +- Mapped to phases: 51 / 51 (100%) +- Unmapped: 0 + +**Phase distribution:** +- Phase 57 (IDENT): 5 requirements +- Phase 58 (PROV): 6 requirements +- Phase 59 (CHAN): 7 requirements +- Phase 60 (ADAPT + CLAUDE): 9 requirements +- Phase 61 (UX + LIFE): 10 requirements +- Phase 62 (DRIFT): 4 requirements +- Phase 63 (NATIVE): 4 requirements +- Phase 64 (MULTI-OpenCode): 3 requirements +- Phase 65 (MULTI-Codex): 3 requirements --- -*Requirements defined: 2026-07-07* -*Last updated: 2026-07-07 after roadmap creation (Phases 52-56)* +*Requirements defined: 2026-07-11* +*Last updated: 2026-07-16 after Phase 62 completion (41/51 requirements complete; all live checks deferred to milestone end)* diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index 6b4170901..ec3d07ae3 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -8,98 +8,298 @@ ## Milestones +- 🚧 **v0.9.91 MCP Clients as Providers** — Phases 57-65, planning 2026-07-11. Extends the Chrome MV3 extension + `fsb-mcp-server` so installed agent CLIs (Claude Code first, then OpenCode + Codex) become first-class side-panel providers: FSB captures which MCP clients the user copies/installs/connects, presents them as key-less providers in a renamed Providers panel, and delegates side-panel tasks to a spawned agent CLI that drives the browser back through FSB's own MCP tools — with a security-first reverse-request channel, an `AgentProviderAdapter` contract, task-mode only (chat-mode deferred), CI drift-smoke gate, native-messaging wake-host, and INV-01 (byte-stable existing MCP wire) preserved end-to-end. - ✅ **v1.2.0 Showcase i18n Completeness** — Phases 52-56, shipped 2026-07-09. Archive: `.planning/milestones/v1.2.0-ROADMAP.md`. Closes the translation gap that reopened after v0.9.63 shipped: a full-page audit establishes the true drift/missing scope, resync + stats-page translation + transcreation review close it, a CI drift-detection gate lands on the clean baseline, and the long-deferred WARNING-02 locale-cookie redirect bug is fixed. - ✅ **v1.1.0 T1 App Execution Expansion** — Phases 44-51, shipped 2026-06-30; refreshed 2026-07-01 after post-closeout T1 ports. Expanded proven T1/guarded coverage from 26 baseline descriptors to 1,267 executable T1-ready descriptors plus 556 guarded fail-closed rows, and closed the remaining catalog tail with explicit terminal-state accounting. Archive: `.planning/milestones/v1.1.0-ROADMAP.md`. - ✅ **v1.0.0 Full App Catalog (OpenTabs Parity)** — Phases 35-43, shipped 2026-06-29. Full OpenTabs-derived catalog/search/discovery surface: 2,314 descriptors across 128 app stems / 129 services; 26 T1/T1b descriptors today; 2,288 descriptors intentionally remain DOM/discovery-tail. Archive: `.planning/milestones/v1.0.0-ROADMAP.md`. -## Current Milestone: _(none — v1.2.0 complete; run /gsd-new-milestone for next)_ +## Current Milestone: v0.9.91 MCP Clients as Providers -**Milestone Goal:** Close the translation gap that reopened after v0.9.63 shipped -- full, drift-free coverage across all six supported locales (en, es, de, ja, zh-CN, zh-TW) for every showcase marketing page plus the stats page, the long-deferred locale-cookie redirect bug, and a CI gate that catches future drift automatically. +**Milestone Goal:** Make installed agent CLIs (Claude Code first) first-class side-panel providers — FSB captures which MCP clients the user installs/connects, presents them as key-less providers in a renamed Providers panel, and delegates side-panel tasks to a spawned agent CLI that drives the browser back through FSB's own MCP tools. The user's own words: "when I'm sending a message from the side panel, the FSB extension sends a request to the MCP server, which spawns a Claude/Codex/OpenCode agent with the prompt, and that agent uses FSB MCP tools to perform the task — technically driving the local coding agent from the side panel." -**Key context:** Supported locales are fixed (en source + es/de/ja/zh-CN/zh-TW) -- carried over from v0.9.63's `LocaleService` + locale-constants module, not up for debate. This is the second attempt at closing this exact gap: v0.9.63 left dashboard + WARNING-02 as accepted debt that then sat untouched through 6+ subsequent milestones. Builds on existing tooling: `lint:i18n` eslint check, `verify-locale-sync.mjs`, `ng extract-i18n` -- no new runtime dependency, no XLIFF-format migration. +**Key context (non-negotiable):** -**Phase ordering rationale (from research, all 4 researchers converged independently):** audit first (establish true scope) -> resync + stats-page translation + visual QA (close the known + newly-found gap) -> stats-page lint-gate flip + dashboard boundary documentation (gated on verified resync completion) -> new CI drift-detection gate (must land on a clean, drift-free baseline) -> WARNING-02 cookie-redirect fix (fully independent, zero shared surface with the rest, sequenced last for narrative completeness only). +- **Security-first hard rule.** The reverse-request channel is RCE-adjacent by construction: a browser click becomes `execve(claude, -p, )` inside a localhost daemon. The security foundation phase (Phase 59) — reverse-request channel + Origin/Host/secret/consent + CHAN-07 grep gate — MUST land before ANY spawn code exists. It cannot be deferred to a "hardening" phase. All CHAN-01..CHAN-07 requirements land in Phase 59 together. +- **INV-01 discipline.** Every wire addition is additive: new frame types (`ext:request`/`ext:response`/`ext:event`) + optional payload fields only. Existing `MCPMessageType` values and tool schemas stay byte-stable across the entire milestone; a byte-freeze regression test proves this in every phase that touches the wire. +- **Task-mode only.** Chat-mode continuity via `--resume` / `codex resume` / `opencode --continue` is v2 scope (CHAT-FUTURE-01/02); every adapter's `caps()` reports `chatMode: false` for v0.9.91. +- **Adapter breadth: OpenCode + Codex; skip Gemini.** GEMINI-FUTURE-01 defers Gemini until a live `--help` capture and JSONL schema pinning are done. NATIVE and DRIFT are IN scope. +- **No `--dangerously-skip-permissions` / `--yolo` / `--auto`.** Anywhere. Ever. Not even in an "advanced mode." A permanent CI grep gate (CHAN-07) fails the build if those strings appear under `mcp/src/agent-providers/**`. +- **No `@anthropic-ai/claude-agent-sdk` embedded in FSB.** Anthropic banned third-party products from using consumer subscription auth via the SDK (enforcement Apr 4 2026). Shelling to the user's installed `claude` binary is the only compliant "no API key" path and is standard ecosystem practice (Cline, Roo, Zed, Conductor). +- **Extension has no `nativeMessaging` permission in Phases 57-62.** Delegation therefore requires a running `fsb-mcp-server serve` daemon; MVP ships an honest "agent offline → doctor" state. Phase 63 (NATIVE) adds the optional wake-host that closes this UX cliff. +- **Source-pin tripwire discipline.** FSB's test suite pins exact token counts and substrings on extension source (even comments). Every extension-touching commit updates paired tripwires; the full suite runs from commit 1 of every phase. -**Research correction carried into scope:** the milestone brief's original "247 trans-units changed" framing (from commit `6d3ad363`) overstates the resync scope. An id-keyed ``-text diff shows only 5 of the 247 touched trans-unit blocks have real source drift (`agents.meta.description`, `agents.schema.software.description`, `home.meta.description`, `support.faq.q.tools.a`, `support.schema.faq.tools.a`); the other 242 are harmless `` churn. Phase 52's audit is the authority on the true, final drift count -- do not fix a resync scope number ahead of that audit completing. +**Phase ordering rationale (from research; dependency chain non-negotiable):** identity data (Phase 57) → provider selection UI reads it (Phase 58) → security foundation must exist before any spawn code (Phase 59) → adapter contract needs the channel (Phase 60) → UX/lifecycle needs the adapter (Phase 61) → drift gate and doctor need something to check (Phase 62) → native-host is additive and can only close the offline cliff after the offline state itself exists (Phase 63) → contract must be stable before adapter breadth (Phases 64-65). ## Phases **Phase Numbering:** -- Integer phases (52, 53, 54, 55, 56): Planned milestone work, continuing from v1.1.0's Phase 51 -- Decimal phases (52.1, 52.2): Urgent insertions (marked with INSERTED) - -- [x] **Phase 52: Full-Page Translation Completeness Audit** - Establish the true per-page, per-locale, per-trans-unit coverage/currency verdict and trace the orphaned stats artifacts -- [x] **Phase 53: Trans-Unit Resync, Stats Translation & Transcreation Review** - Close every audit-identified drift, bring the stats page to full coverage, apply a transcreation lens to hero copy, and spot-check DE/CJK rendering -- [x] **Phase 54: Stats Lint Gate Flip & Dashboard Boundary Documentation** - Remove the stats-page `lint:i18n` exclusion now that coverage is verified, and document the dashboard exclusion as permanent -- [x] **Phase 55: CI Drift-Detection Gate** - Add a permanent, back-tested CI gate that fails the build on future undetected translation drift -- [x] **Phase 56: Locale-Cookie Redirect Fix (WARNING-02)** - Fix the picker-set locale cookie so it correctly redirects returning visitors instead of short-circuiting to EN +- Integer phases (57, 58, 59, 60, 61, 62, 63, 64, 65): Planned milestone work, continuing from v1.2.0's Phase 56 +- Decimal phases (57.1, 57.2): Urgent insertions (marked with INSERTED) + +- [x] **Phase 57: Agent Identity Capture** - Persist copy-clicks, capture MCP `initialize` `clientInfo`, thread it through `agent:register`, add disk-scan detection, expose a unified `getMcpClients` view — additive on both sides of the wire (INV-01 safe), unblocks everything downstream (completed 2026-07-12) +- [x] **Phase 58: Providers Panel** - Rename "API Configuration" → "Providers", introduce `api` vs `agent` provider kinds, hide the key input for agent kind, badge exactly one "Recommended" provider via the connected > installed > copy-clicked cascade, keep `universal-provider.js` unaware of agent values (INV-03 BYOK parity) (completed 2026-07-12; live UAT deferred to milestone-end gate) +- [x] **Phase 59: Reverse-Request Channel & Security Foundation** - **SECURITY-CRITICAL, load-bearing**. Additive `ext:*` frames on ws://localhost:7225, strict Origin allowlist + Host loopback + per-install rotating shared secret in `Sec-WebSocket-Protocol`, log redaction, hub-exit-mid-delegation topology tests, permanent CI grep gate against `--dangerously-skip-permissions` / `--yolo` / `--auto` — completed 2026-07-14; automated/source verification passed and four live checks are deferred to the milestone-end UAT gate +- [x] **Phase 60: Adapter Contract & Claude Code MVP** - `AgentProviderAdapter` interface, `SpawnSupervisor` in the `serve` daemon with argv-only spawn / scrubbed env / SIGTERM-at-process-group / Windows `taskkill /T /F` / orphan scan on startup, Claude Code adapter with verified 2.1.177 flag set + shipped `fsb` agent definition + recorded stream-json JSONL fixture — completed 2026-07-14; automated/source verification passed and seven live checks are deferred to the milestone-end UAT gate +- [x] **Phase 61: Delegation UX & SW-Eviction Persistence** - Fifth `EXECUTION_MODES` entry `delegated`, explicit first-use consent card, live per-tool-call streaming feed, default-background-tab + "Take control" affordance, kill switch that reclaims owned tabs, post-run usage summary, `chrome.storage.session` per-event persistence, 20 s WS heartbeat, "agent offline → `doctor`" deep-link, restart-is-clean semantics (completed 2026-07-15; automated/source verification passed and eight live checks are deferred to the milestone-end UAT gate) +- [x] **Phase 62: CI Drift-Smoke Gate & Doctor Extensions** - Per-adapter CI drift-smoke against canned fixtures (fail-loud on unknown event types / missing fields / version outside compat matrix), `fsb-mcp-server doctor` per-adapter section (binary path, version, auth, secret rotation age), machine-readable adapter compatibility matrix consumed by the extension (completed 2026-07-16; automated/source/full-suite verification passed and three live checks are deferred to the milestone-end UAT gate) +- [ ] **Phase 63: Native-Messaging Host** - `install --native-host` writes the platform-appropriate manifest (mac/Linux/Windows), extension gains additive `nativeMessaging` permission, "Agent offline" state auto-attempts wake before the doctor deep-link; the native host only launches (or attaches to) `serve` — it NEVER spawns agent CLIs directly (all spawn authority stays inside the daemon behind Phase 59 CHAN gates) +- [ ] **Phase 64: OpenCode Adapter** - Second adapter proves the contract accommodates server-mode + attach on top of cold spawn without any Phase 60 rewrite; pinned OpenCode agent definition + recorded JSONL fixture + drift-smoke coverage +- [ ] **Phase 65: Codex Adapter** - Third adapter with `codex exec --json` on the verified 0.142.5 hermetic flag set (`--ephemeral` + `--ignore-user-config`, never the deprecated `--full-auto`), `detect()` surfacing ChatGPT OAuth / API key / unauthenticated so the Providers panel discloses which billing bucket a run will hit; `caps.chatMode: false` across all adapters (automated/source verification passed 2026-07-22; exactly three live UAT scenarios remain pending) ## Phase Details -### Phase 52: Full-Page Translation Completeness Audit -**Goal**: Every current showcase route has an authoritative, per-locale, per-trans-unit verdict that distinguishes "coverage" (marked + target exists) from "currency" (target still matches the current English source) -- and the orphaned `translations.stats-274.*.json` artifacts are traced end-to-end into (or explicitly out of) the live XLIFF files the build consumes. -**Depends on**: Nothing (first phase of this milestone) -**Requirements**: AUDIT-01, AUDIT-02 +### Phase 57: Agent Identity Capture +**Goal**: FSB knows which MCP-capable agent CLIs the user has installed on disk, has expressed intent to install (via onboarding copy clicks), and has actually connected via MCP `initialize` — surfaced as a single ground-truth view that unblocks every downstream provider-selection decision. +**Depends on**: Nothing (first phase of this milestone; pure additive data layer on both sides of the wire) +**Requirements**: IDENT-01, IDENT-02, IDENT-03, IDENT-04, IDENT-05 +**Success Criteria** (what must be TRUE): + 1. When the user clicks any copy-to-clipboard button on the onboarding MCP-install screen (for `claude-code`, `cursor`, `vscode`, `windsurf`, `codex`, `opencode`, `openclaw`, `claude-desktop`, or `all`), FSB records that client id (with timestamp, deduplicated, all-clients aggregated for multi-select cases) into a durable `fsbAgentProviders.clicked` list in `chrome.storage.local` that survives service-worker eviction and Chrome restart. + 2. When any MCP client completes its `initialize` handshake with `fsb-mcp-server` (over stdio, streamable-HTTP, or the ws://7225 bridge), FSB captures the caller's `clientInfo.name` and `clientInfo.version`, threads them through an additive field on the existing `agent:register` bridge payload, stamps them onto the live `AgentRecord` in the registry, and rolls the identity up into a durable `fsbAgentProviders.connected` entry keyed so re-connections update rather than duplicate. + 3. `fsb-mcp-server` can enumerate installed MCP-capable clients on the current machine by inspecting the paths already known to `platforms.ts` (per-OS `configPath` for file-mode clients; `claude --version` binary probe for cli-mode `claude-code`) and report each as `installed` / `not-installed` with any parseable version. + 4. A single `getMcpClients` extension runtime message returns a merged `clicked ∪ installed ∪ connected` view with per-client status, so UI surfaces read one consistent structure instead of assembling it themselves — and the merged view survives MV3 service-worker eviction. + 5. INV-01 holds: no existing `MCPMessageType` value, no existing tool schema, and no existing consumer of the `agent:register` payload breaks — the additive `clientInfo` field is optional and existing `payload: {}` handlers keep working byte-for-byte. +**Plans**: 3/3 complete +- [x] 57-01: Capture lazy MCP client identity and daemon-side installed-client inventory. +- [x] 57-02: Persist durable clicked, connected, and installed evidence in the extension. +- [x] 57-03: Expose the canonical merged MCP-client evidence view with compatibility locks. + +### Phase 58: Providers Panel +**Goal**: The control panel's "API Configuration" section becomes the "Providers" panel — distinguishing BYOK API providers from installed agent CLIs, badging exactly one provider "Recommended" from ground truth (never auto-switching selection), and shipping honest no-fabrication cost/usage copy for agent-kind providers. +**Depends on**: Phase 57 (needs real `fsbAgentProviders` data to render agent rows and to drive the "Recommended" cascade) +**Requirements**: PROV-01, PROV-02, PROV-03, PROV-04, PROV-05, PROV-06 +**Success Criteria** (what must be TRUE): + 1. The control panel section formerly labeled "API Configuration" is labeled "Providers" (heading, nav label, and the legacy `#api-config` anchor continues to work as a redirect to the new `#providers` anchor for existing bookmarks); the source-pin tripwire suite stays green from commit 1 of this phase. + 2. Every provider row has an explicit `providerKind` of either `api` (the existing 7 BYOK LLM providers) or `agent` (a locally installed agent CLI); the kind determines which fields render — an `agent`-kind selection hides the API-key input, key-URL hint, and per-model key-format hint, and shows install status, auth status (where the CLI exposes it), connection status, and a "uses your subscription — no API key needed" caption instead. + 3. Exactly one provider is badged "Recommended" per session, chosen by the strict ground-truth cascade (highest = a CLI currently connected via MCP `initialize`, next = a CLI installed on disk, next = a CLI whose copy button was clicked during onboarding, fallback = the existing xAI-default recommendation); the badge is advisory only — the user's selection is never auto-switched. + 4. `universal-provider.js` (the existing BYOK request builder) never observes an agent value: switching between an active agent provider and a BYOK api provider preserves the other's configuration, and INV-03 provider parity for the 7 BYOK providers holds unchanged. + 5. Cost/usage rows for `agent`-kind providers display token count, turn count, and duration alongside the label "included in your subscription", with a link to the vendor's current billing page — never a fabricated dollar amount and never the words "free" or "unlimited". +**Plans**: 3/3 complete +**UI hint**: yes + +### Phase 59: Reverse-Request Channel & Security Foundation +**Goal**: Extension → daemon reverse requests transit the existing `ws://localhost:7225` bridge with every documented 2025-2026 CVE-class defense enforced from commit one — Origin allowlist, Host loopback, per-install rotating shared secret, log redaction, additive-only wire evolution, and a permanent grep gate against the three "one-click RCE" flags. This phase is SECURITY-CRITICAL and load-bearing: it ships BEFORE any spawn code exists in Phase 60. +**Depends on**: Nothing (channel design is orthogonal to Phase 57/58 data + UI; can develop in parallel but MUST be code-green before Phase 60 starts) +**Requirements**: CHAN-01, CHAN-02, CHAN-03, CHAN-04, CHAN-05, CHAN-06, CHAN-07 +**Success Criteria** (what must be TRUE): + 1. A new `ext:request` / `ext:response` / `ext:event` bridge message-type family transports extension → daemon reverse requests over the existing ws://localhost:7225 bridge, and a byte-freeze regression test proves every existing `MCPMessageType` value, tool schema, and bridge payload stays byte-identical (INV-01 additive proof). + 2. A relay process advertising `capabilities: ['agent-spawn']` on `relay:hello` becomes the routing target for `ext:*` frames: the hub routes each `ext:request` locally when it is itself the daemon, forwards to the first `agent-spawn`-advertising relay when it is not, and replies `agent_provider_offline` when no supervisor is present — verified by extending `tests/mcp-bridge-topology.test.js` with new hub-exit-mid-delegation and relay-mid-`ext:*`-frame cases without breaking any existing hub-exit-promotion test. + 3. A fixture crafted with `Origin: https://evil.com` on the WebSocket upgrade is rejected before any handler runs; a second fixture with `Host: evil.com:7225` (even when the target actually resolves to 127.0.0.1) is rejected as DNS-rebind defense — the bridge accepts `ext:*` frames only when the WS upgrade carried an `Origin` matching a durable per-install `chrome-extension://` allowlist and a `Host` exactly `127.0.0.1` or `localhost` at the loopback port, and the daemon never binds `0.0.0.0`. + 4. A per-install ≥32-byte shared secret is provisioned once between the extension and the daemon, transported only in the `Sec-WebSocket-Protocol` upgrade header (never in URL, never in payloads, never in logs), rotated on daemon restart, and required on every `ext:*` frame — and `redactForLog` plus every tracked diagnostic ring-buffer sink strips any string matching the shared-secret token pattern, with a build-time drift gate that fails if a raw secret substring appears in any tracked log fixture. + 5. A permanent CI grep gate fails the build if the strings `--dangerously-skip-permissions`, `--yolo`, or `--auto` appear anywhere in `mcp/src/agent-providers/**`, so those "one-click RCE" flags can never enter the spawn path in any future patch. +**Plans**: 4/4 complete + +### Phase 60: Adapter Contract & Claude Code MVP +**Goal**: A shell-free, tree-killable `SpawnSupervisor` in the `serve` daemon spawns the user's installed `claude` CLI via a formal five-method `AgentProviderAdapter` contract — with a shipped `fsb` agent definition (never prompt-stuffing), `--strict-mcp-config` hermeticity, and a recorded stream-json JSONL fixture — so a user with Claude Code installed can send a side-panel message and observe the agent drive their live browser back through FSB's MCP tools with visible per-tool-call feedback. This phase is the integration payoff; the contract is designed for adapter breadth so OpenCode/Codex slot in without rework. +**Depends on**: Phase 58 (provider selection reads the Providers panel), Phase 59 (channel + all CHAN security foundations must be code-green before any spawn code exists) +**Requirements**: ADAPT-01, ADAPT-02, ADAPT-03, ADAPT-04, ADAPT-05, CLAUDE-01, CLAUDE-02, CLAUDE-03, CLAUDE-04 +**Success Criteria** (what must be TRUE): + 1. The `AgentProviderAdapter` TypeScript interface (`mcp/src/agent-providers/adapter.ts`) exposes exactly five methods (`detect()` → `{installed, version, authState, binary}`, `buildSpawn(task, ctx)` → `SpawnSpec`, `parseEvents(stream)` → `AsyncIterable`, `kill(child, {grace})` → `Promise`, `caps()` → `AdapterCapabilities`), and the Claude Code adapter (`mcp/src/agent-providers/claude-code.ts`) implements all five and is registered against the `INSTALL_CLIENTS` / `PLATFORMS` client id `claude-code`. + 2. A user with Claude Code installed can select "Claude Code" as their provider in the side panel, type a task, and observe the spawned CLI drive their live browser back through FSB's MCP tools — with visible per-tool-call feedback — while every existing FSB MCP tool call remains byte-identical on the wire (INV-01 preserved end-to-end through the delegation round-trip; the byte-freeze regression test still passes). + 3. The `SpawnSupervisor` accepts a validated spawn request over Phase 59's `ext:request` channel, looks up the requested adapter, constructs argv from adapter output plus a daemon-controlled flag allowlist (unknown payload keys rejected with a typed error), and spawns the child with `{ detached: true, windowsHide: true, stdio: ['pipe','pipe','pipe'] }` and an environment with `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, and `GEMINI_API_KEY` scrubbed; the supervisor never invokes a shell, and the user's prompt reaches the CLI only via `child.stdin` (never argv, never `sh -c`) so shell metacharacters and Windows `.cmd`-shim EINVAL (Node CVE-2024-27980) cannot execute. + 4. A `stop` / cancel request triggers SIGTERM at the process-group level on POSIX (`process.kill(-child.pid, 'SIGTERM')` after `spawn({detached:true})`), `taskkill /pid /T /F` on Windows, escalates to SIGKILL after a grace window, and does not resolve the delegation until either an exit-signal is observed or the daemon confirms no descendant matches remain — grandchildren, MCP sub-servers, and shell tools spawned by the CLI are all terminated. + 5. On daemon startup, the supervisor scans for orphaned children matching prior adapter fingerprints (env-var tag + argv pattern) and kills them before accepting new spawn requests, and reports the scan result in structured diagnostics — so a daemon crash mid-run cannot leave a ghost CLI controlling the browser. + 6. The Claude Code adapter spawns `claude -p --verbose --output-format stream-json --include-partial-messages --strict-mcp-config --mcp-config --agents --agent fsb --permission-mode dontAsk --allowedTools "mcp__fsb" --disallowedTools "Bash,Edit,Write,NotebookEdit,WebFetch,WebSearch" --max-turns 40 --no-session-persistence` (or the version-appropriate equivalent selected by `detect()`); `parseEvents` translates the CLI's stream-json events into a normalized `AgentEvent` schema, fails loud on unknown event types (surfaced as `agent_protocol_drift` diagnostic), and is covered by a recorded JSONL fixture under `tests/fixtures/agent-streams/claude-code-2.1.177/` so CI runs without a live CLI; `detect()` fingerprints via `claude --version`, compares against a minimum supported version, and reports `installed=false` with a doctor-diagnostic message rather than spawning if the version predates the verified stream-json contract. +**Plans**: 4/4 complete +- [x] 60-01: Freeze the exact adapter contract, Claude detection/profile, and shipped FSB agent policy. +- [x] 60-02: Normalize bounded Claude JSONL through the concrete adapter with truthful offline fixture provenance. +- [x] 60-03: Build private runtime state plus verified POSIX/Windows termination and orphan recovery. +- [x] 60-04: Wire the serve-only supervisor, exact-once reverse delegation, full regression gates, and deferred UAT ledger. + +### Phase 61: Delegation UX & SW-Eviction Persistence +**Goal**: The side panel becomes a first-class delegation surface — visible first-use consent gate, live per-tool-call streaming feed, default-background-tab with a "Take control" affordance, kill switch that reclaims owned tabs, honest post-run usage summary, and MV3 SW-eviction-safe persistence so a 45-minute run resumes exactly the feed the user last saw. The extension has no `nativeMessaging` permission at this point — this phase ships the honest "Agent offline → `doctor`" state that Phase 63 later augments with an optional wake path. +**Depends on**: Phase 60 (adapter contract + Claude Code MVP producing the `AgentEvent` stream + tab ownership on spawn); Phase 59 heartbeat plumbing +**Requirements**: UX-01, UX-02, UX-03, UX-04, UX-05, UX-06, LIFE-01, LIFE-02, LIFE-03, LIFE-04 **Success Criteria** (what must be TRUE): - 1. A generated audit report lists, for every current showcase route (lattice, phantom-stream, prometheus, home, mobile nav, stats, and the original v0.9.63 routes) and every one of the 5 non-English locales, a per-trans-unit verdict of coverage AND currency as two distinct checks -- not a single pass/fail signal. - 2. The audit's true drift count is authoritative and supersedes the milestone brief's original "247 trans-units" estimate; the 5 confirmed `6d3ad363`-drifted units are validated as a subset of (not a substitute for) the full audit findings. - 3. The orphaned `translations.stats-274.*.json` artifacts are explicitly resolved: either shown to already be merged into the live `messages..xlf` files, or shown to still be outstanding work with no ambiguity left for Phase 53 to inherit. -**Plans**: 1/1 complete - -### Phase 53: Trans-Unit Resync, Stats Translation & Transcreation Review -**Goal**: Every trans-unit the Phase 52 audit flagged as drifted is resynced across all 5 translated locales, the stats page reaches full translation coverage, hero/CTA copy gets a transcreation-quality pass instead of literal translation, and German/CJK rendering is confirmed clean on the highest-copy-density routes. -**Depends on**: Phase 52 -**Requirements**: RESYNC-01, RESYNC-02, RESYNC-03, VISUAL-01 + 1. A fifth `EXECUTION_MODES` entry `delegated` in `extension/ai/engine-config.js` (with `uiFeedbackChannel: 'popup-sidepanel'`, `animatedHighlights: true`, wall-clock + event-silence watchdogs, and no iteration cap — the reasoning loop runs in the spawned CLI, not `runAgentLoop`) is selected automatically whenever the active provider is agent-kind, and the extension's own `runAgentLoop` is never entered for delegated tasks. + 2. Before FSB spawns any agent CLI for the first time, the user sees an explicit consent card that names the CLI, states what it will be permitted to do (drive FSB MCP tools on the user's live browser), and states what it will not (edit files, run shell, fetch arbitrary URLs); a per-run confirm-to-continue toggle is on by default and can be disabled per-provider only via an explicit "trust this agent" setting — the copy never uses "faster mode" language. + 3. The side panel renders a live per-run streaming feed with distinct card types for `init` (client, model, session id, allowed tools), `tool-call` (name, args summary, tab id), `retry` (typed error class), and `result` (usage summary) driven by the normalized `AgentEvent` stream; a delegated run opens by default in a new background tab, and when the user activates the tab the agent is driving, a persistent "Take control" affordance appears that pauses the agent (v0.9.60 ownership release + supervisor grace hold), lets the user interact, and offers "Resume with agent" to give ownership back. + 4. A prominent Stop button in the side panel triggers `stopDelegatedTask`, routes to the supervisor's kill, and — on confirmed exit — releases every tab that was owned by the spawned agent (per v0.9.60 ownership) and reports "Agent stopped, N tab(s) released" in the feed; a post-run summary card displays tokens (in/out/total), turn count, wall-clock duration, cost bucket (`included in your subscription` for agent kind, real USD for api kind), and an expandable per-tool-call breakdown. + 5. Every progress event received from the supervisor is written to `chrome.storage.session` under a per-delegation key BEFORE it fans out to UI subscribers, so a mid-run MV3 service worker eviction reloads exactly the delivered feed on re-open — no ghost state, no fabricated events, no dropped events; the fifth `EXECUTION_MODES` entry works end-to-end across a forced SW eviction in a test fixture. + 6. While a delegation is active, the extension pings the bridge every 20 s over the existing WS heartbeat channel to keep the Chrome 116+ SW-lifetime extension applied; if 3 heartbeats are missed the side panel switches to a `daemon:disconnected` fallback that offers a doctor-relaunch button but does not attempt an in-extension restart; if `fsb-mcp-server serve` is not running when a delegated send is attempted, the side panel shows an "Agent offline" state with a deep-link to `fsb-mcp-server doctor` output and does not enqueue or optimistically show the message; if the daemon restarts while a delegation was mid-flight, the supervisor kills any surviving spawned CLI (never re-adopts) and reports `daemon_restart_lost_run` in the side panel so the user knows the run ended. +**Plans**: 8/8 complete; automated/source verification passed; eight live UAT checks deferred to the milestone-end gate +- [x] 61-01: Freeze exact agent routing, delegated mode, read-only preflight, one-use consent, and provider-local trust. +- [x] 61-02: Build the bounded write-before-fanout event ledger and sole delegated lifecycle controller. +- [x] 61-03: Serialize async bridge observers, add acknowledged active-delegation heartbeat, and pin Chrome 116/no-native authority. +- [x] 61-04: Correlate server delegation ids with extension agents and add sealed complete mapped-tab hold leases. +- [x] 61-05: Add supervisor hold/resume/status plus generation-backed restart-loss evidence. +- [x] 61-06: Integrate start, handoff, Stop, hydration, heartbeat, and recovery in the service worker. +- [x] 61-07: Implement the safe, exact-copy, accessible delegated side-panel feed and lifecycle UI. +- [x] 61-08: Install complete regression/evidence gates and defer genuine live UAT to the milestone-end ledger. +**UI hint**: yes + +### Phase 62: CI Drift-Smoke Gate & Doctor Extensions +**Goal**: An `fsb-mcp-server doctor` operator and CI both catch adapter drift the moment an agent CLI ships a new flag or event-shape — with a machine-readable adapter compatibility matrix the extension consumes to render "supported / degraded / unsupported" states without hardcoding versions anywhere in extension source. +**Depends on**: Phase 60 (needs at least one adapter to run drift-smoke against; Phase 61 UX consumes the compatibility matrix for rendering degraded states) +**Requirements**: DRIFT-01, DRIFT-02, DRIFT-03, DRIFT-04 **Success Criteria** (what must be TRUE): - 1. Every trans-unit flagged as drifted by the Phase 52 audit (the 5 confirmed `6d3ad363` units plus any additional units the audit surfaced) has an updated `` in es, de, ja, zh-CN, and zh-TW that matches the current English ``, with `` placeholder alignment preserved and `DO-NOT-TRANSLATE.md` brand/term rules re-verified for each resynced string. - 2. The stats page has a `` for every trans-unit across all 5 non-English locales -- full coverage, not partial. - 3. The ~10-20 hero headline and primary CTA strings read as natural, locale-appropriate marketing copy rather than literal word-for-word translation, reviewed explicitly through a transcreation lens. - 4. A targeted manual visual spot-check of German (text-expansion risk) and zh-CN/zh-TW (CJK line-wrap risk) on the highest-copy-density routes shows no broken layout, truncation, or overflow -- performed only after the resynced/transcreated copy above has landed, since it needs final translated text to check against. -**Plans**: 53-01 ✅, 53-02 ✅, 53-03 ✅ - -### Phase 54: Stats Lint Gate Flip & Dashboard Boundary Documentation -**Goal**: The stats page is held to the same CI translation-completeness bar as every other marketing page, and the dashboard's permanent exclusion from that bar is written down as an explicit architectural decision rather than left as unstated deferred debt. -**Depends on**: Phase 53 (must follow verified stats-page translation completion, not precede it) -**Requirements**: CI-01, CI-05 + 1. A CI job runs each shipped adapter against a canned prompt fixture, asserts the known event-type sequence and the presence of required fields on `system/init` and `result`, and fails the build on unknown event types, missing required fields, or a `--version` outside the compatibility matrix — with no live CLI required (the recorded JSONL fixtures from Phases 60/64/65 suffice), so contributors without every CLI installed can still land safe changes. + 2. `fsb-mcp-server doctor` gains a per-adapter section reporting: binary path, version, auth state (parseable where the CLI exposes it), shared-secret presence, and the current spawn-secret rotation age — so an operator with only `doctor` output can identify which adapter failed and why. + 3. The diagnostics ring buffer classifies drift events as `agent_protocol_drift` with adapter id, expected vs observed fields, and rate-limits duplicate entries at the existing 1-per-10s bucket so a chatty drift does not blow the buffer. + 4. `doctor` emits a machine-readable adapter compatibility matrix that both the CI drift-smoke job and the extension can read; the extension consumes the matrix at boot (and on doctor refresh) to render `supported` / `degraded` / `unsupported` badges in the Providers panel — and never hardcodes CLI versions in extension source. +**Plans**: 6 plans +- [x] 62-01: Establish the canonical adapter compatibility matrix/classifier and generalized offline drift-smoke CI gate. +- [x] 62-02: Extend `doctor` with offline adapter diagnostics, safe spawn-secret metadata, and text/JSON parity. +- [x] 62-03: Carry the authenticated safe compatibility projection into background-owned durable, freshness-aware provider state. +- [x] 62-04: Project sanitized protocol-drift detail and report it exactly once through a true per-adapter pre-throttle. +- [x] 62-05: Render exact, accessible, non-mutating compatibility badges and selected-provider details. +- [x] 62-06: Wire root/source/security gates and preserve all genuine live evidence in the pending milestone-end UAT ledger. + +### Phase 63: Native-Messaging Host +**Goal**: When the user has installed the optional native-messaging host, the "Agent offline" state auto-attempts to wake `fsb-mcp-server serve` before falling back to the Phase 61 doctor deep-link — closing the UX cliff introduced by the extension having no `nativeMessaging` permission through Phases 57-62. All spawn authority stays inside the serve daemon behind Phase 59's CHAN gates; the native host itself never spawns agent CLIs. +**Depends on**: Phase 61 ("Agent offline → doctor" state must already exist for the wake to have a fallback), Phase 62 (doctor extensions already report daemon state) +**Requirements**: NATIVE-01, NATIVE-02, NATIVE-03, NATIVE-04 **Success Criteria** (what must be TRUE): - 1. The `lint:i18n` ignore-pattern for `src/app/pages/stats/**` in `showcase/angular/package.json` is removed, and `npm run lint:i18n` passes clean against the stats page's own templates. - 2. The dashboard page's `lint:i18n` exclusion is documented in-repo as a permanent, intentional architectural boundary (authenticated app surface, not marketing content) with an explicit rationale, not a bare ignore-pattern with no explanation. -**Plans**: 54-01 ✅ - -### Phase 55: CI Drift-Detection Gate -**Goal**: A new, permanent CI gate automatically fails the build the moment `messages.xlf`'s English source content changes without a matching update in one of the 5 translated locale files -- landing only once the tree is verified drift-free, so it passes clean on day one instead of immediately failing on residual debt. -**Depends on**: Phase 54 (must land on the clean baseline established by Phases 52-54) -**Requirements**: CI-02, CI-03, CI-04 + 1. `fsb-mcp-server install --native-host` writes the platform-appropriate native-messaging host manifest — macOS `~/Library/Application Support/Google/Chrome/NativeMessagingHosts/`, Linux `~/.config/google-chrome/NativeMessagingHosts/`, Windows registry `HKCU\Software\Google\Chrome\NativeMessagingHosts\` — allowing the FSB extension id as the sole caller, and installs a host binary that can wake the daemon on demand. + 2. The extension's manifest gains a single additive `nativeMessaging` permission entry (no other permission changes); at boot the extension detects native-host presence, and when the host is installed, an "Agent offline" state auto-attempts a wake before showing the doctor deep-link — a user with the host installed sees the delegation come alive on demand instead of the manual `serve` prompt. + 3. The native host itself does NOT spawn agents; it only starts `fsb-mcp-server serve` (or attaches to a running one) and exits after handoff — all spawn authority remains inside the serve daemon behind the Phase 59 CHAN gates (Origin allowlist, shared secret, flag allowlist, argv-only) and no CHAN requirement is relaxed to accommodate the wake path. + 4. `fsb-mcp-server uninstall --native-host` cleanly removes the manifest, and `doctor` reports native-host install state including manifest path, allowlist mismatches, and host-binary reachability so an operator can debug wake failures without shell-level inspection. +**Plans**: 12 plans + +**Wave 1** +- [x] 63-01: Establish the workspace-safe build guard, release-built Windows bootstrap, and durable bundled runtime contract. + +**Wave 2** *(blocked on Wave 1 completion)* +- [x] 63-02: Fix pre-bind secret rotation and make product-specific serve readiness authoritative. + +**Wave 3** *(blocked on Wave 2 completion)* +- [x] 63-03: Implement the closed native-messaging framing, validation, one-shot entry, and authority boundary. + +**Wave 4** *(blocked on Wave 3 completion)* +- [x] 63-04: Implement exact readiness probing, tokened wake coalescing, and one shell-free serve handoff. + +**Wave 5** *(blocked on Wave 4 completion)* +- [x] 63-05: Implement exact-owned atomic registration, runtime materialization, install, and uninstall across three OS families. + +**Wave 6** *(blocked on Wave 5 completion)* +- [x] 63-06: Add explicit native-host CLI install/uninstall routes without widening existing installer behavior. + +**Wave 7** *(blocked on Wave 6 completion)* +- [x] 63-07: Add bounded read-only native-host diagnostics with safe local and browser projections. + +**Wave 8** *(blocked on Wave 7 completion)* +- [x] 63-08: Add the single manifest permission and background-owned probe/offline-wake controller. + +**Wave 9** *(blocked on Wave 8 completion)* +- [x] 63-09: Render the approved intent-fenced checking state in the existing delegation card. + +**Wave 10** *(blocked on Wave 9 completion)* +- [x] 63-10: Wire deterministic focused/root/CI contracts and create the still-pending milestone-end UAT ledger. + +**Wave 11** *(blocked on Wave 10 completion)* +- [x] 63-11: Run independent blocking code, security, and UI source-review gates. + +**Wave 12** *(blocked on Wave 11 completion)* +- [x] 63-12: Run the reviewed focused matrix and workspace-preserving repository-wide regression gate. + +### Phase 64: OpenCode Adapter +**Goal**: The `AgentProviderAdapter` contract proves it accommodates a second CLI family — one that supports both cold spawn AND attach-to-running-server (`opencode serve` + `opencode run --attach`) — without any Phase 60 rewrite; OpenCode joins the Providers panel with a pinned agent definition and CI-covered event schema so the drift gate covers it from day one. +**Depends on**: Phase 60 (contract), Phase 62 (drift-smoke gate must accept the new adapter fixture) +**Requirements**: MULTI-01, MULTI-02, MULTI-03 **Success Criteria** (what must be TRUE): - 1. `verify-translation-drift.mjs` exists, follows the zero-dependency style of the existing `verify-locale-sync.mjs`/`verify-hreflang.mjs` scripts, and fails the build when any trans-unit's English `` text changes without a corresponding update in one of the 5 translated locale files -- diffing per trans-unit `id`, never whole-file or line-count. - 2. The gate is back-tested against this repo's own git history (known-clean pure-churn commits plus commit `6d3ad363` itself) and demonstrably stays silent on clean churn while firing on `6d3ad363`-shaped drift, before being wired hard-fail into CI. - 3. The gate's target-locale list is read dynamically from the existing locale registry at runtime, not hardcoded as a literal list in the script. -**Plans**: 55-01 ✅ - -### Phase 56: Locale-Cookie Redirect Fix (WARNING-02) -**Goal**: A returning visitor whose picker-set `fsb-locale` cookie names a valid, non-default supported locale is correctly redirected to that locale's subpath from the bare-`/` route, instead of the cookie being ignored in favor of the EN prerender. -**Depends on**: Nothing (fully independent of Phases 52-55; zero shared surface, could be parallelized) -**Requirements**: ROUTE-01, ROUTE-02 + 1. An OpenCode adapter (`mcp/src/agent-providers/opencode.ts`) implements the `AgentProviderAdapter` contract with `caps.serverMode = true`; the supervisor either spawns `opencode run` cold OR attaches to a running `opencode serve` per the adapter's `buildSpawn` output — and the Phase 60 contract handles both without any hardcoded spawn-vs-attach branch outside the adapter. + 2. The OpenCode adapter ships a pinned agent definition (equivalent to Claude Code's `--agents fsb`) using OpenCode's `agent create` / `agents` config surface, keyed to a version pinned during phase spike, so tool boundaries and system-prompt intent are identical across the two adapters. + 3. A recorded OpenCode JSONL fixture under `tests/fixtures/agent-streams/opencode-/` proves the adapter's event schema in CI without a live CLI; the Phase 62 drift-smoke job includes OpenCode from the first commit of this phase, and unknown event types raise `agent_protocol_drift` with the adapter id `opencode`. Planning-document commits do not count as implementation commits: Plan 64-01 has exactly one implementation task/commit containing the complete parser, fixture, native negative corpus, and generalized drift gate; every later plan depends on it transitively, and Plan 64-05 exposes adapter/registry/matrix/bijection atomically. + 4. A user with OpenCode installed can pick "OpenCode" as their provider in the side panel and observe the delegation UX (streaming feed, kill switch that reclaims tabs, post-run summary, SW-eviction survival) work identically to Claude Code — with no adapter-specific side-panel branches. +**Plans**: 13 plans + +**Wave 1** +- [x] 64-01-PLAN.md — Land the complete strict parser, honest 1.14.25 fixture, native negative corpus, and generalized adapter-native Phase 62 CI drift gate as the sole first implementation commit. + +**Wave 2** *(blocked on Wave 1 completion)* +- [x] 64-02-PLAN.md — Freeze the exact five-method provider-neutral topology/attestation seam and the public fixed-env versus transient secret-binding contract. + +**Wave 3** *(blocked on Wave 2 completion)* +- [x] 64-03-PLAN.md — Version the exact delegation/provider-server runtime journal and private-artifact boundary with legacy recovery and structural secret non-retention preserved. + +**Wave 4** *(blocked on Wave 3 completion)* +- [x] 64-04-PLAN.md — Build exact OpenCode 1.14.25 retained detection, private policy, effective-attestation declarations, and exact server/attach secret-binding descriptors. + +**Wave 5** *(blocked on Wave 4 completion)* +- [x] 64-05-PLAN.md — Atomically compose and expose the production adapter, registry row, compatibility row, and exact parser/fixture/native-drift bijection. + +**Wave 6** *(parallel; blocked on Wave 5, with the server lifecycle also directly bound to Wave 3 runtime state)* +- [x] 64-06-PLAN.md — Project the exact roster into bounded inventory/doctor evidence and the browser-safe Claude/OpenCode compatibility plane. +- [x] 64-07-PLAN.md — Implement the generic cold-first, FSB-owned-server lease, health, fallback, idle, and recovery lifecycle. + +**Wave 7** *(parallel; each blocked on its Wave 6 dependency)* +- [x] 64-08-PLAN.md — Interpret closed attestation descriptors through the shared verifier, enforce no replay, and gate results on clean exit/tree settlement. +- [x] 64-09-PLAN.md — Generalize canonical extension preflight, consent, trust, and background-authoritative provider routing. + +**Wave 8** *(parallel; each blocked on its Wave 7 dependency)* +- [x] 64-10-PLAN.md — Generalize durable event/controller hydration and safe per-adapter drift diagnostics. +- [x] 64-11-PLAN.md — Promote the existing OpenCode Providers row from safe evidence with honest auth/billing and no visible layout change. + +**Wave 9** *(blocked on Waves 7-8 completion)* +- [x] 64-12-PLAN.md — Project OpenCode through the existing delegated consent/side-panel/feed/summary UI with no provider-specific branch. + +**Wave 10** *(blocked on Wave 9 completion)* +- [x] 64-13-PLAN.md — Wire exact validation/root/CI/security/full-suite gates and the still-pending three-row human-UAT ledger. + +### Phase 65: Codex Adapter +**Goal**: Task-mode delegation coverage extends to OpenAI Codex with correct auth-state disclosure (ChatGPT OAuth vs API key vs unauthenticated) so users know which billing bucket a run will hit before starting; `caps.chatMode: false` across all v0.9.91 adapters confirms task-mode-only scope for the milestone. +**Depends on**: Phase 60 (contract), Phase 62 (drift-smoke gate) +**Requirements**: MULTI-04, MULTI-05, MULTI-06 **Success Criteria** (what must be TRUE): - 1. A request to `/` carrying an `fsb-locale` cookie set to a non-default supported locale (es, de, ja, zh-CN, or zh-TW) redirects to that locale's subpath rather than serving the EN prerender. - 2. A request to `/` carrying an `fsb-locale` cookie set to `en` (the default) still falls through correctly to the EN prerender without redirecting to a 404ing `/en/` path. -**Plans**: 56-01 ✅ + 1. A Codex adapter (`mcp/src/agent-providers/codex.ts`) implements the `AgentProviderAdapter` contract, invoking `codex exec --json` with the verified 0.142.5 flag set (`--ephemeral` + `--ignore-user-config` for hermeticity; the deprecated `--full-auto` is NEVER referenced in adapter source, protected by the CHAN-07 grep gate against `--auto`). + 2. The Codex adapter's `detect()` correctly identifies auth via ChatGPT OAuth, API key, or unauthenticated and surfaces the state in the Providers panel so the user knows which billing bucket a run will hit — with the accepted copy **Included with your ChatGPT plan**, **Billed to the API key stored by Codex; dollar amount not reported.**, or the exact sign-in/status-refresh recovery copy. + 3. A schema-derived Codex JSONL contract fixture pins the event schema in CI under `tests/fixtures/agent-streams/codex-0.142.5/`, honestly retains `liveCapturePending: true`, and awaits genuine capture in UAT65-02; the Phase 62 drift-smoke job includes Codex from the first implementation commit, and the adapter's `caps()` correctly reports `chatMode: false` — matching the milestone-wide task-mode-only posture. + 4. A user with Codex installed can pick "Codex" as their provider and observe the same delegation UX as Claude Code and OpenCode with the correct per-auth-state cost copy, and the Providers panel's `agent`-kind cost row shows tokens / turns / duration + the auth-state-appropriate subscription caption rather than a fabricated dollar amount. +**Plans**: 8/8 complete + +- [x] 65-01-PLAN.md — Establish canonical accepted identity, capability vocabulary, and durable provider-neutral persistence before Codex exposure. +- [x] 65-02-PLAN.md — Bind background-owned preflight, consent, trust, and replay rejection to the exact five-field identity. +- [x] 65-03-PLAN.md — Enforce immediate supervisor re-attestation and exact start echo before any runtime or browser-visible effect. +- [x] 65-04-PLAN.md — Add bounded byte probes, sanitized environments, serve-owned authority descriptors, and empty direct scratch cleanup. +- [x] 65-05-PLAN.md — Atomically expose the complete Codex adapter, profile, parser, fixture, roster, drift, and negative corpus. +- [x] 65-06-PLAN.md — Project safe auth evidence and exact billing copy through the existing three-provider Providers surface. +- [x] 65-07-PLAN.md — Preserve immutable accepted identity through feed/hydration and close shared 44px delegated controls. +- [x] 65-08-PLAN.md — Lock exact validation/UAT contracts and the preservation-safe focused, root, and CI closure gate. ## Progress **Execution Order:** -Phases execute in numeric order: 52 → 53 → 54 → 55 → 56 +Phases execute in numeric order: 57 → 58 → 59 → 60 → 61 → 62 → 63 → 64 → 65 +Security-first hard rule: Phase 59 is code-green before Phase 60 spawn code lands, regardless of Phase 57/58 progress. | Phase | Plans Complete | Status | Completed | |-------|-----------------|--------|-----------| -| 52. Full-Page Translation Completeness Audit | 1/1 | Complete | 2026-07-08 | -| 53. Trans-Unit Resync, Stats Translation & Transcreation Review | 3/3 | Complete | 2026-07-09 | -| 54. Stats Lint Gate Flip & Dashboard Boundary Documentation | 1/1 | Complete | 2026-07-09 | -| 55. CI Drift-Detection Gate | 1/1 | Complete | 2026-07-09 | -| 56. Locale-Cookie Redirect Fix (WARNING-02) | 1/1 | Complete | 2026-07-09 | +| 57. Agent Identity Capture | 3/3 | Complete | 2026-07-12 | +| 58. Providers Panel | 3/3 | Complete | 2026-07-12 | +| 59. Reverse-Request Channel & Security Foundation | 4/4 | Complete | 2026-07-14 | +| 60. Adapter Contract & Claude Code MVP | 4/4 | Complete | 2026-07-14 | +| 61. Delegation UX & SW-Eviction Persistence | 8/8 | Complete (UAT deferred) | 2026-07-15 | +| 62. CI Drift-Smoke Gate & Doctor Extensions | 6/6 | Complete (UAT deferred) | 2026-07-16 | +| 63. Native-Messaging Host | 12/12 | Complete | 2026-07-20 | +| 64. OpenCode Adapter | 13/13 | Complete | 2026-07-21 | +| 65. Codex Adapter | 8/8 | Complete | 2026-07-22 | ## Completed Milestones +
+v1.2.0 Showcase i18n Completeness — Phases 52-56, SHIPPED 2026-07-09 + +**Milestone Goal:** Close the translation gap that reopened after v0.9.63 shipped -- full, drift-free coverage across all six supported locales (en, es, de, ja, zh-CN, zh-TW) for every showcase marketing page plus the stats page, the long-deferred locale-cookie redirect bug, and a CI gate that catches future drift automatically. + +**Phase summary:** + +| Phase | Name | Plans | Status | +|-------|------|-------|--------| +| 52 | Full-Page Translation Completeness Audit | 1/1 | Complete | +| 53 | Trans-Unit Resync, Stats Translation & Transcreation Review | 3/3 | Complete; VISUAL-01 human_needed | +| 54 | Stats Lint Gate Flip & Dashboard Boundary Documentation | 1/1 | Complete | +| 55 | CI Drift-Detection Gate | 1/1 | Complete | +| 56 | Locale-Cookie Redirect Fix (WARNING-02) | 1/1 | Complete | + +Archive files: + +- `.planning/milestones/v1.2.0-ROADMAP.md` +- `.planning/milestones/v1.2.0-REQUIREMENTS.md` +- `.planning/v1.2.0-MILESTONE-AUDIT.md` + +Outcome: 13/13 v1.2.0 requirements satisfied; audit passed. Phase 52's audit established the true 5-drifted/54-orphaned baseline, superseding the milestone brief's original "247 trans-units" estimate. Phase 53 resynced the 5 drifted units + retired the stats-274 JSON artifacts + transcreated the 19 hero/CTA strings across 5 non-en locales. Phase 54 flipped `lint:i18n` to cover stats and documented the dashboard exclusion as permanent. Phase 55 landed `verify-translation-drift.mjs` as a hard-fail CI gate on a clean drift-free baseline. Phase 56 fixed WARNING-02's picker-cookie short-circuit on the bare-`/` Accept-Language redirect. VISUAL-01 browser UAT remains `human_needed` (`53-VISUAL-QA.md`). + +
+
v1.1.0 T1 App Execution Expansion — Phases 44-51, SHIPPED 2026-06-30 @@ -303,10 +503,11 @@ Known deferred closeout evidence: 11 human-gated Chrome MV3/UAT verification ite ## Carry-Forward Candidates -- **Consolidated Chrome MV3 UAT debt:** Run and capture archived v0.10/v0.11/v0.12 + v0.9.99 (UAT-27/29/30/31/32-01) browser evidence if release policy requires post-close proof. Does NOT block v1.2.0. +- **v0.9.91 v2 deferred (see REQUIREMENTS.md):** CHAT-FUTURE-01/02 (chat-mode continuity via `--resume` / `codex resume` / `opencode --continue` + per-thread cwd pinning); GEMINI-FUTURE-01 (Gemini CLI adapter after live `--help` capture + JSONL schema pinning); ACP-FUTURE-01 (`@zed-industries/agent-client-protocol` unification once ≥2 non-Claude adapters have shipped); REMOTE-FUTURE-01 (remote/mobile delegation surfaces — v0.9.91 is explicitly localhost-only). +- **Consolidated Chrome MV3 UAT debt:** Run and capture archived v0.10/v0.11/v0.12 + v0.9.99 (UAT-27/29/30/31/32-01) browser evidence if release policy requires post-close proof. Does NOT block v0.9.91. +- **v1.2.0 v2 deferred:** QA-01 (native-speaker/bilingual QA pass), I18N-FUTURE-01 (migrate stats page off ad hoc JSON mechanism into main XLIFF pipeline), I18N-FUTURE-02 (full automated per-locale visual regression pipeline), I18N-FUTURE-03 (translation-freshness/"last synced" reporting surface). - **v2 deferred capability families (acknowledged, out of v1.0.0/v1.1.0):** GAPI-01 (gapi-bridge handler family for Google Workspace); CLOUD-01 (cloud-console Pattern-D ports); UATX-01 (per-app live guarded-write UAT closeout). -- **v1.2.0 v2 deferred (see REQUIREMENTS.md):** QA-01 (native-speaker/bilingual QA pass), I18N-FUTURE-01 (migrate stats page off ad hoc JSON mechanism into main XLIFF pipeline), I18N-FUTURE-02 (full automated per-locale visual regression pipeline), I18N-FUTURE-03 (translation-freshness/"last synced" reporting surface). -- **Delegation primitive:** Parked from v0.10.0; re-scope as either a Lattice-owned primitive or an FSB-only consumer of Lattice receipt + tripwire surfaces. +- **Delegation primitive (Lattice-owned):** Parked from v0.10.0; re-scope as either a Lattice-owned primitive or an FSB-only consumer of Lattice receipt + tripwire surfaces after v0.9.91 stabilizes the adapter contract. ## Backlog diff --git a/.planning/STATE.md b/.planning/STATE.md index bd0cc7542..d1bca01e5 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -1,20 +1,20 @@ --- gsd_state_version: 1.0 -milestone: v1.2.0 -milestone_name: Showcase i18n Completeness -status: milestone_complete -stopped_at: v1.2.0 archived -- ready for /gsd-new-milestone -last_updated: "2026-07-09T19:18:40.000Z" -last_activity: 2026-07-09 +milestone: v0.9.91 +milestone_name: MCP Clients as Providers +status: human_needed +stopped_at: Phase 65 automated verification passed; human UAT65-01 through UAT65-03 pending +last_updated: "2026-07-22T18:45:13.807Z" +last_activity: 2026-07-22 progress: - total_phases: 5 - completed_phases: 5 - total_plans: 4 - completed_plans: 4 - percent: 100 + total_phases: 18 + completed_phases: 9 + total_plans: 61 + completed_plans: 63 + percent: 50 --- -*Note: the `total_phases`/`completed_phases` counts above are scoped to the active v1.2.0 milestone (Phases 52-56) only. Some GSD tooling (`roadmap.analyze`, `phase.complete`) reports a noisy 14-phase/9-complete count and misidentifies "999.1" (an unrelated Backlog item) as the next phase -- both are artifacts of that tooling scanning the whole ROADMAP.md file, including the collapsed `## Completed Milestones` archive and `## Backlog` sections, rather than just the active milestone's phases. Phases 44-51 are archived v1.1.0 work; 999.1 is unrelated backlog. Treat this file's own numbers as authoritative for v1.2.0 progress.* +*Note: the `total_phases`/`completed_phases` counts above are scoped to the active v0.9.91 milestone (Phases 57-65) only. Some GSD tooling (`roadmap.analyze`, `phase.complete`) reports a noisy multi-phase count including collapsed `## Completed Milestones` archive entries and `## Backlog` sections — treat this file's own numbers as authoritative for v0.9.91 progress.* # Project State @@ -26,76 +26,385 @@ progress: ## Project Reference -See: .planning/PROJECT.md (v1.2.0 Showcase i18n Completeness framing) -See: .planning/ROADMAP.md (v1.2.0 active, Phases 52-56; v1.1.0/v1.0.0/v0.9.99/etc. archived and collapsed) -See: .planning/REQUIREMENTS.md (13 v1 requirements, mapped to Phases 52-56, 0/13 complete) -See: .planning/research/SUMMARY.md (phase sequencing rationale, confirmed 5-drifted/54-orphaned baseline) -See: .planning/milestones/v1.1.0-ROADMAP.md, .planning/milestones/v1.1.0-REQUIREMENTS.md, .planning/milestones/v1.1.0-MILESTONE-AUDIT.md (archived T1 App Execution Expansion milestone) +See: .planning/PROJECT.md (v0.9.91 MCP Clients as Providers — Current Milestone section, Key context bullets) +See: .planning/ROADMAP.md (v0.9.91 active, Phases 57-65; v1.2.0 / v1.1.0 / v1.0.0 / v0.9.99 / etc. archived and collapsed) +See: .planning/REQUIREMENTS.md (51 v1 requirements across 10 categories: IDENT, PROV, CHAN, ADAPT, CLAUDE, UX, LIFE, DRIFT, NATIVE, MULTI — all mapped to Phases 57-65, 48/51 complete) +See: .planning/research/SUMMARY.md (converged research summary; suggested phase structure; HIGH confidence) +See: .planning/research/PITFALLS.md (16 pitfalls with phase assignments; security section verified against 2025-2026 CVE class incidents) +See: .planning/research/ARCHITECTURE.md (file:line integration seams; brownfield mapping onto existing FSB architecture) +See: .planning/milestones/v1.2.0-ROADMAP.md, .planning/milestones/v1.2.0-REQUIREMENTS.md, .planning/v1.2.0-MILESTONE-AUDIT.md (archived Showcase i18n Completeness milestone) -**Core value:** Reliable single-attempt execution -- the AI decides correctly, the mechanics execute precisely. v1.2.0 does not touch the automation/T1 catalog surface; it closes a reopened i18n completeness gap on the showcase marketing site. -**Current focus:** v1.2.0 milestone complete — ready for audit/complete +**Core value:** Reliable single-attempt execution — the AI decides correctly, the mechanics execute precisely. v0.9.91 does not touch the DOM/automation single-attempt property; it extends the surface so installed agent CLIs (Claude Code first, then OpenCode + Codex) become first-class side-panel providers that drive the same live browser through FSB's own MCP tools. +**Current focus:** Milestone-end human UAT sweep; Phase 65 automated verification is complete ## Current Position -Phase: 54 of 56 (Stats Lint Gate Flip & Dashboard Boundary Documentation) -Plan: Not yet planned -Status: Ready to plan -- Phase 53 complete (resync + stats reconciliation + transcreation); visual UAT deferred human_needed -Last activity: 2026-07-09 +Phase: 65 (codex-adapter) — EXECUTING +Plan: 8 of 8 +Status: human_needed +Last activity: 2026-07-22 -Progress: [██████████] 100% (5/5 phases in v1.2.0) - -## Roadmap At A Glance (v1.2.0, Phases 52-56) +## Roadmap At A Glance (v0.9.91, Phases 57-65) | Phase | Name | Requirements | Status | |-------|------|--------------|--------| -| 52 | Full-Page Translation Completeness Audit | AUDIT-01, AUDIT-02 | Complete (2026-07-08) | -| 53 | Trans-Unit Resync, Stats Translation & Transcreation Review | RESYNC-01, RESYNC-02, RESYNC-03, VISUAL-01 | Complete (2026-07-09; VISUAL-01 human_needed deferred) | -| 54 | Stats Lint Gate Flip & Dashboard Boundary Documentation | CI-01, CI-05 | Complete (2026-07-09) | -| 55 | CI Drift-Detection Gate | CI-02, CI-03, CI-04 | Complete (2026-07-09) | -| 56 | Locale-Cookie Redirect Fix (WARNING-02) | ROUTE-01, ROUTE-02 | Complete (2026-07-09) | - -Coverage: 13/13 v1.2.0 requirements mapped, 0 orphaned. Dependency chain: 52 -> 53 -> 54 -> 55 (hard sequential dependency: each gate must land on the clean baseline the prior phase verified). Phase 56 is fully independent of 52-55 and could run in parallel if desired, but executes last in numeric order. - -## Hard Invariants (v1.2.0) - -- Supported locale list stays fixed at en (source) + es/de/ja/zh-CN/zh-TW -- not up for debate this milestone. -- No commercial TMS adoption (Lokalise, Crowdin, Smartling, Phrase, doloc.io) -- explicit no-paid-SaaS constraint. -- No `ng-extract-i18n-merge` adoption this milestone -- legitimate future option, not required to satisfy the CI-gate requirement. -- Dashboard page translation stays explicitly out of scope (authenticated app surface, not marketing content) -- Phase 54 documents this as permanent, not deferred. -- The new drift gate (Phase 55) must diff `` text per trans-unit `id`, never whole-file/line-count, to avoid the false-positive/bypass-habit failure mode that let WARNING-02 (Phase 56) sit unaddressed for 6+ milestones. -- The drift gate must land only after Phases 52-54 verify a clean, drift-free baseline -- wiring it earlier guarantees either an immediately-red CI or a gate built loose enough to miss real drift. +| 57 | Agent Identity Capture | IDENT-01, IDENT-02, IDENT-03, IDENT-04, IDENT-05 | Complete (2026-07-12) | +| 58 | Providers Panel | PROV-01, PROV-02, PROV-03, PROV-04, PROV-05, PROV-06 | Complete (2026-07-12; UAT deferred to milestone end) | +| 59 | Reverse-Request Channel & Security Foundation | CHAN-01, CHAN-02, CHAN-03, CHAN-04, CHAN-05, CHAN-06, CHAN-07 | Complete (2026-07-14; UAT deferred to milestone end) | +| 60 | Adapter Contract & Claude Code MVP | ADAPT-01..05, CLAUDE-01..04 | Complete (2026-07-14; UAT deferred to milestone end) | +| 61 | Delegation UX & SW-Eviction Persistence | UX-01..06, LIFE-01..04 | Complete (2026-07-15; UAT deferred to milestone end) | +| 62 | CI Drift-Smoke Gate & Doctor Extensions | DRIFT-01, DRIFT-02, DRIFT-03, DRIFT-04 | Complete (2026-07-16; UAT deferred to milestone end) | +| 63 | Native-Messaging Host | NATIVE-01, NATIVE-02, NATIVE-03, NATIVE-04 | Complete (2026-07-20; UAT deferred to milestone end) | +| 64 | OpenCode Adapter | MULTI-01, MULTI-02, MULTI-03 | In Progress (9/13) | +| 65 | Codex Adapter | MULTI-04, MULTI-05, MULTI-06 | Not started | + +Coverage: 51/51 v0.9.91 requirements mapped and automated/source complete; milestone closure awaits the accumulated human UAT ledgers + +## Hard Invariants (v0.9.91) + +- **Security-first hard rule.** All CHAN-01..CHAN-07 land in Phase 59 together, BEFORE any spawn code exists in Phase 60. Not deferrable to a "hardening" phase. +- **INV-01 (byte-stable MCP wire).** Every wire addition is additive: new frame types (`ext:request` / `ext:response` / `ext:event`) + optional payload fields only. No existing `MCPMessageType` value or tool schema changes across the entire milestone; a byte-freeze regression test proves this in every wire-touching phase. +- **INV-03 (BYOK provider parity).** `universal-provider.js` continues to see only the existing 7 `api`-kind provider values across the whole milestone; `agent`-kind values never leak into request-builders. +- **INV-04 (MV3 SW-survivability).** The `setTimeout`-chained iterator pattern is preserved; delegation lifecycle persists to `chrome.storage.session` per progress event and heartbeats WS every 20 s. +- **INV-05 (no resurrection of sunset modules).** `extension/agents/*` background-agents surface (retired in v0.9.45rc1) stays frozen; the new provider surface is MCP-client identity, a different concept. +- **Task-mode only for v0.9.91.** Every adapter's `caps.chatMode: false`. Chat-mode continuity via `--resume` / `codex resume` / `opencode --continue` is v2 scope (CHAT-FUTURE-01/02). +- **No `--dangerously-skip-permissions` / `--yolo` / `--auto` in the spawn path.** Anywhere. Ever. CHAN-07 makes this a permanent invariant enforced by a CI grep gate; the Codex adapter (Phase 65) explicitly must NOT reference `--full-auto`. +- **No `@anthropic-ai/claude-agent-sdk` embedded in FSB.** Anthropic banned third-party products from using consumer subscription auth via the SDK (enforcement Apr 4 2026). Shell to the user's installed `claude` binary only. +- **No native-messaging permission through Phases 57-62.** The extension has NO way to wake the daemon in those phases; delegation UX must ship an honest "Agent offline → `doctor`" state (LIFE-03). Phase 63 (NATIVE) adds the optional wake-host later without relaxing any CHAN rule — the host itself never spawns agent CLIs (NATIVE-03). +- **Source-pin tripwire discipline.** Test suite pins exact token counts + substrings on extension source. Every extension-touching commit updates paired tripwires in the same commit; the full suite runs green from commit 1 of every phase. +- **Same-context dispatch in the MV3 SW.** Delegation dispatcher integrates through `globalThis.fsbDispatchInternalMessage` / the Phase 225-01 bus, NOT `chrome.runtime.sendMessage` (auto-memory: sendMessage never loops back in-SW). +- **Adapter breadth: Claude Code + OpenCode + Codex; NO Gemini.** GEMINI-FUTURE-01 defers Gemini until a live `--help` capture + JSONL schema pinning are done. ## Accumulated Context ### Decisions -Full decision log for prior milestones (v0.9.99 Phase 26-34, v1.0.0, v1.1.0 T1/Wall/consent decisions) lives in PROJECT.md and the archived `.planning/milestones/v1.0.0-*` / `.planning/milestones/v1.1.0-*` files. Those decisions concern the T1 capability-catalog surface and have zero shared surface with v1.2.0's showcase-i18n work; not repeated here to keep this file a digest. - -v1.2.0-specific decisions so far: - -- [Roadmap]: Phase ordering follows the audit -> resync -> gate-flip -> drift-gate -> cookie-fix chain all 4 independent researchers converged on. The drift gate (Phase 55) is sequenced after Phases 52-54, not before, so it lands clean rather than immediately red. -- [Roadmap]: VISUAL-01 (DE/CJK visual spot-check) is folded into Phase 53's success criteria rather than given its own phase, since it has a hard content dependency on RESYNC-01/02 landing first (needs final translated copy to visually check) and is small enough not to warrant a standalone phase. -- [Roadmap]: The milestone brief's original "247 trans-units changed" framing is explicitly corrected in ROADMAP.md -- research confirmed only 5 of 247 touched blocks have real `` drift; the true count is whatever Phase 52's audit finds, not a number fixed in advance. -- [Phase 52]: Corrected the plan's stated 6 non-shellless routes to the verified-correct 8 (sitemaps and legal also render the shared shell); derived dynamically per-route from ROUTE_TABLE.shellless rather than a hardcoded route-name list -- [Phase 52]: traceStats274's idDriftFromTemplate implemented exactly as specified (produces 13/locale); reported as explicitly unreconciled against 52-RESEARCH.md Open Questions #3's disputed 7/9/13 candidates rather than silently picking one +Full decision log for prior milestones (v0.9.99 Phase 26-34, v1.0.0, v1.1.0, v1.2.0) lives in PROJECT.md, ROADMAP.md's `## Completed Milestones` section, and the archived `.planning/milestones/*` files. Those decisions concern the T1 capability-catalog surface and the showcase-i18n surface; they have zero shared surface with v0.9.91's provider/delegation work and are not repeated here to keep this file a digest. + +v0.9.91-specific decisions so far: + +- [Roadmap]: Phase structure follows the research SUMMARY.md's dependency chain (identity → provider UI → security → adapter contract + Claude MVP → UX/lifecycle → drift → native → OpenCode → Codex). ADAPT and CLAUDE combined in one phase (60) because the Claude Code MVP is the contract's first real implementation; UX and LIFE combined in one phase (61) because the UX affordances (kill switch reclaims tabs, offline state, take-control) are inseparable from the persistence semantics that make them SW-eviction-safe. +- [Roadmap]: MULTI split across two phases (64 = OpenCode, 65 = Codex) rather than combined into one, because each adapter has its own version-pinning spike, fixture capture, and auth-detection story — the incremental phase cost is small versus the risk of one adapter's drift blocking the other's ship. +- [Roadmap]: NATIVE placed at Phase 63 (after DRIFT) rather than immediately after UX, so the doctor extensions from DRIFT (compatibility matrix, per-adapter section) exist before the native host reports its own state; NATIVE cannot ship BEFORE UX because it augments UX's "Agent offline" state. +- [Roadmap]: Security-first hard rule is enforced by phase ordering AND by the CHAN-07 CI grep gate landing in Phase 59; the gate protects future patches (including Phase 65's Codex adapter, which must NEVER reference `--full-auto`). +- [Roadmap]: DRIFT is INCLUDED in v0.9.91 (Phase 62) per user confirmation; the drift gate is scoped to Claude Code from its first commit and extended per-adapter as OpenCode and Codex land in Phases 64/65. +- [Roadmap]: NATIVE is INCLUDED in v0.9.91 (Phase 63) per user confirmation; the additive `nativeMessaging` permission is the only permission change in the milestone. +- [Roadmap]: Every `agent`-kind cost row displays "included in your subscription" + real token/turn/duration counts — never a fabricated dollar amount (PROV-06). The Codex adapter's per-auth-state copy (Phase 65) is the strictest test of this discipline. +- [Roadmap]: The reverse-request channel reuses the existing `ws://localhost:7225` bridge; no new port is added (SUMMARY.md architecture decision; a separate supervisor port is a documented fallback if hub-forwarding proves fragile during Phase 59 implementation). +- [Phase 57]: Read MCP client identity and inventory lazily through feature-detected AgentScope suppliers so bare scopes and structural mocks remain compatible. +- [Phase 57]: Reuse PLATFORMS and resolvePlatformTarget for installed-client detection, with fixed shell-free Claude Code version probes. +- [Phase 57]: Deliver one memoized client inventory through both system:client-inventory and agent:register payload.platforms. +- [Phase 57]: Keep MCP clientInfo observational and bounded — Only sanitized name/version evidence is persisted; authority, cap, ownership, and routing logic remain unchanged. +- [Phase 57]: Converge both installed-inventory delivery paths on replaceInstalled — Registration piggybacks and system frames replace only installed evidence while preserving clicked, connected, and unknown siblings. +- [Phase 57]: Persist onboarding intent without awaiting storage — Resolve current at click time and serialize same-page writes so feedback timing is unchanged and rapid clicks retain counts. +- [Phase 57]: Join known MCP identities only through a frozen exact alias vocabulary — Unknown names remain visible as raw, non-authoritative entries; no fuzzy matching or Gemini aliasing. +- [Phase 57]: Re-read durable provider evidence for every merged query — Clicked, installed, connected, and live evidence stay separate and no recommendation is derived. +- [Phase 57]: Guard getMcpClients with the existing own-extension sender check and direct registry access — This preserves same-context service-worker dispatch without self-send. +- [Phase 57]: Expose getMergedClients as a non-enumerable additive helper — Direct consumers gain the method while the locked enumerable API remains compatible. +- [Phase 58]: Keep provider settings and recommendation evidence separate — modelProvider remains closed to seven API ids, agent intent stays in agentProviderId, and fixed live/installed/clicked evidence changes only one advisory recommendation. +- [Phase 58]: Keep the radio roster as an in-form projection — API and agent selections use the existing Save/Discard boundary, inactive-kind values survive switching, and agent ids never enter modelProvider or discovery maps. +- [Phase 58]: Preserve the last successful evidence snapshot on refresh failure and label it stale — provider evidence and the single recommendation remain advisory and never mutate selection, API inputs, Save state, or storage. +- [Phase 58]: Keep account and billing truth independent from client evidence — absent auth renders Billing not reported; only explicit future subscription/API/credit/Zen/provider modes can change that label. +- [Phase 58]: Announce background provider refreshes through polite status and reserve one assertive alert for an explicit manual failure; never turn the full details region into an alert. +- [Phase 60]: Keep spawn authority serve-only and recovery-gated — bind HTTP first, finish exact journal recovery second, then advertise `agent-spawn`; stdio and incidental bridges never own a supervisor. +- [Phase 60]: Use one closed Claude Code 2.1.177 profile with a retained native path, immutable MCP-only argv, a shipped static FSB agent, task text through stdin only, scrubbed provider-key environment, and truthful schema-derived fixture provenance. +- [Phase 60]: Treat process-tree or runtime cleanup uncertainty as daemon-wide degradation — stop accepting starts, preserve exact evidence, and drive one orderly nonzero shutdown rather than risk overlapping browser authority. +- [Phase 60]: Bind delegation lifetime to its authenticated reverse route and join setup, stdin, cleanup, and cancellation so every topology/exit/error race settles exactly once without replay. +- [Phase 61 planning]: Keep provider selection background-authoritative and exact; pure preflight plus challenge-bound trust enable precede all visible mutation, while Providers gets a separate authority-reducing confirmation reset. +- [Phase 61 planning]: Persist one bounded redacted typed entry per normalized event before fanout; per-correlation bridge tails, closed snapshots/terminal codes, and UI-only exact sequence suppression prevent final-over-write and recovery ambiguity. +- [Phase 61 planning]: The controller owns id-keyed run records and active-tab eligibility; confirmed hold moves every exact mapped tab into one sealed lease, complete restore precedes resume, and Stop waits tree settlement before exact release/count. +- [Phase 61 planning]: Active delegation liveness uses one ref-counted 20-second exact-nonce heartbeat with daemon echo and three-miss disconnect; restart loss additionally requires generation plus bounded recovery disposition. +- [Phase 61 Plan 01]: Keep API-kind agent ids storage-compatible but inactive at pure preflight; only exact `agent` + `claude-code` enters delegated routing, with closed unsupported/offline/unpaired results. +- [Phase 61 Plan 01]: Persist a challenge's one-use trust-write slot before local trust enable, so replay and local-storage failure cannot reopen authority; exact provider-local clear remains the sole false path. +- [Phase 61 Plan 01]: Keep provider trust outside broad Config authority, and require a freshly minted, atomically consumed internal challenge for trusted and untrusted starts alike. +- [Phase 61 Plan 02]: Treat the bounded redacted session ledger as the visibility commit point; only a successfully persisted canonical entry may change controller state or fan out. +- [Phase 61 Plan 02]: Reject persisted duplicate, conflicting, gapped, reversed, or identity-mismatched sequences as corruption; delivery deduplication remains UI-only. +- [Phase 61 Plan 02]: Isolate timers and settlement per server delegation id, seal every mapped tab into one hold lease, and restore the complete lease before daemon resume. +- [Phase 61 Plan 03]: Serialize the global observer roster on each pending correlation's private tail; matching final settlement waits for that tail and observer failure rejects only that request. +- [Phase 61 Plan 03]: Replace ordinary keepalive with one Set-refcounted 20-second exact-nonce loop only while delegation owners exist; three misses classify disconnected without restart or replay inference. +- [Phase 61 Plan 03]: Pin exact Chrome 116 while leaving permissions, worker scripts, and bridge authority unchanged; doctor/setup remain data-only future UI behavior with no native, shell, process, or daemon-restart path. +- [Phase 61 Plan 04]: Carry the daemon-minted delegation id only on the initial bounded registration sidecar; the sole controller gate binds it once to a fresh extension-minted agent and every denial rolls the ordinary agent back. +- [Phase 61 Plan 04]: Persist cloned candidate registry state before adopting delegation binding, hold seal, restore, or exact release, so storage rejection and index drift leave the prior complete authority unchanged. +- [Phase 61 Plan 04]: Treat the five-minute hold deadline as cancellation-required rather than ownership release; complete tab/token reservations remain unclaimable through expiry and worker reload. +- [Phase 61 Plan 04]: Refuse generic/grace release for controller-mapped agents; only exact delegation-plus-agent cleanup may remove their distinct active and held tab union and report its count. +- [Phase 61 Plan 05]: Keep hold, resume, cancel, settlement, and status inside the one serve-owned supervisor; the five-method adapter contract remains unchanged. +- [Phase 61 Plan 05]: A POSIX lifecycle transition is acknowledged only after the retained group and exact tree are confirmed before and after its signal; unsupported or unverifiable paths converge to confirmed cancellation. +- [Phase 61 Plan 05]: Restart loss requires a prior daemon generation plus an owner-only persisted post-cleanup disposition; same-generation records, transport disconnect, and failed cleanup never qualify. +- [Phase 61 Plan 06]: Keep background as the sole delegation composition root; authoritative provider reload, preflight, challenge consumption, daemon acceptance, and controller/UI allocation remain strictly ordered before any legacy mutation. +- [Phase 61 Plan 06]: Treat same-generation supervisor status as observation only; generation change without matching explicit restart disposition remains disconnected and classification-pending without adopting the new generation. +- [Phase 61 Plan 06]: Retain one heartbeat owner for every hydrated nonterminal id and release it only after terminal persistence and exact tab cleanup; all owners continue to share the bridge's single interval. +- [Phase 61 Plan 06]: Recover held state only from the registry's complete exact sealed lease; a missing or inconsistent lease cancels rather than recreating ownership. +- [Phase 61 Plan 07]: Render only closed canonical snapshot fields through text-only DOM construction; presentation strings, provider-native payloads, URLs, and style metadata never become UI authority or executable markup. +- [Phase 61 Plan 07]: Bind one exact server delegation id to one selected conversation in bounded session storage, hydrate silently before subscribing, and announce only strictly newer matching sequences. +- [Phase 61 Plan 07]: Change consent, held/running, stopping/stopped, disconnect, and restart-loss presentation only after authoritative background snapshots; pending controls never invent lifecycle state. +- [Phase 61 Plan 07]: Limit recovery actions to copying the literal doctor command or opening the local Providers surface; no side-panel action executes, restarts, or invokes native code. +- [Phase 61 Plan 08]: Keep one eight-scenario milestone-end live ledger with every result `human_needed`, pending, and empty; deterministic evidence proves the ledger is honest but never substitutes for performing it. +- [Phase 61 Plan 08]: Close the phase contract mechanically across all 23 task rows, UX/LIFE requirements, D-01..D-28, T61-01..14, typed schemas, architecture links, UI rules, and forbidden authority patterns. +- [Phase 61 Plan 08]: Restore only exact paths that were already unstaged before a full-suite run; never restore, stage, or otherwise mutate the Git index, and continue failing closed on newly dirty clean paths or untracked entries. +- [Phase 61 Plan 08]: Preserve the first Wave-5 wrapper preservation red separately from the explicitly authorized corrected green; no failed evidence is erased or relabeled. +- [Phase 62 Plan 01]: Keep one exact deeply frozen daemon matrix as the only version/profile/fixture authority; only the inclusive fixture-tested range is supported, while newer same-major evidence is degraded but remains start-eligible. +- [Phase 62 Plan 01]: Drive drift CI from the production registry and each registered adapter's production parser, preserving the Claude fixture's schema-derived provenance and pending live-capture truth exactly. +- [Phase 62 Plan 01]: Retain only detector-approved binary/version evidence on unsupported local detections so later doctor output is useful without granting start/profile authority. +- [Phase 62 Plan 02]: Build local doctor rows only from the production-registry/canonical-matrix intersection; malformed or unavailable detectors fail closed without suppressing offline diagnostics. +- [Phase 62 Plan 02]: Keep Claude auth exactly `unknown` / `Not reported` and project bridge auth immediately to secret presence, validated rotation timestamp, and non-negative age only. +- [Phase 62 Plan 02]: Render human and JSON doctor modes from one collected snapshot while preserving historical diagnostic-layer precedence and healthy/unhealthy exit semantics. +- [Phase 62 Plan 03]: Route compatibility through a separate authenticated exact-empty-payload read while leaving delegation and process authority unchanged. +- [Phase 62 Plan 03]: Validate and durably replace one closed safe compatibility snapshot on the existing serialized provider mutation chain, with a one-way fifteen-minute freshness downgrade. +- [Phase 62 Plan 03]: Coalesce paired compatibility refreshes behind one five-second request and persist before fan-out; failures expose only refreshed, stale, or unavailable cached outcomes. +- [Phase 62 Plan 04]: Project every typed provider-protocol drift into one exact three-label terminal detail and collapse untyped drift to fixed adapter-contract labels. +- [Phase 62 Plan 04]: Enforce the per-adapter ten-second admission window before invoking the ring-writing diagnostics sink, including exact-boundary and clock-rollback behavior. +- [Phase 62 Plan 04]: Report only validated authoritative finals once per delegation through a private bounded FIFO, independently of controller settlement and cleanup. +- [Phase 62 Plan 05]: Map only background-projected compatibility into one constant-owned Supported, Degraded, or Unsupported display model; invalid or unshipped evidence fails closed. +- [Phase 62 Plan 05]: Keep compatibility observational and distinct from availability, recommendation, selection, auth, billing, and start authority across DOM, announcements, and refresh. +- [Phase 62 Plan 05]: Apply semantic compatibility styling through existing tokens and fixed responsive ranges without recoloring or reordering existing provider controls. +- [Phase 62 Plan 06]: Root and CI execute the exact generalized drift harness while one 763-assertion contract pins all 17 tasks, four requirements, eight threats, safe projections, and preserved Phase 59–61 interfaces. +- [Phase 62 Plan 06]: Keep exactly three unchecked Phase 62 UAT scenarios pending and evidence-empty until the user-directed milestone-end sweep; automation verifies ledger integrity but never promotes it. +- [Phase 62 closeout]: Diagnose guarded-suite failures as stale test seams, preserve production authority, and require an explicit exit-0 workspace-preserving full-suite result before advancing. +- [Milestone]: Defer every live/human UAT checklist to one milestone-end sweep; automated/source verification and clean review remain mandatory per phase, and no deferred item is silently marked passed. +- [Phase 63]: Use only a version-bound Windows PE bootstrap; never select a batch, command-shell, PowerShell, SEA, or historical relay fallback. — Chromium directly launches executable hosts, and a minimal reviewed CreateProcessW boundary avoids command-interpreter authority. +- [Phase 63]: Keep the stable runtime below the fixed per-user FSB root and separate from the invoking npm cache, checkout, or package root. — Registration must remain durable after npx cache eviction and must have one closed ownership boundary for later atomic install and uninstall. +- [Phase 63]: Exact-pin and bundle all six direct production dependencies, with every lock-reachable production package bound into a deterministic integrity receipt. — The installed host runtime must materialize from a new empty cache without registry access or silent online fallback. +- [Phase 63]: Run npm pack from the exact absolute package cwd with a constant dot argument. — npm 11 omits bundled dependencies when the same relocated package is passed as a directory spec, while the exact cwd form preserves the complete bundled tree. +- [Phase 63]: Pack once for publication, verify that exact tarball offline, and bind its SHA-512 with the lock, receipt, version, and both PE checksums. — The release dependency graph must not publish a tarball different from the artifact that passed the complete closure and platform gates. +- [Phase 63]: Use exact service identity, numeric native-host protocol 1, and false-by-default serve readiness. — A generic or partially initialized loopback listener must never count as the ready FSB daemon. +- [Phase 63]: Prepare bridge auth only after successful bind and recovery, before bridge connect. — A losing concurrent serve process must be unable to invalidate the active daemon session. +- [Phase 63]: Publish serve readiness only after initial inventory push. — Reachability becomes true only after the complete startup barrier has settled. +- [Phase 63]: Treat zero-byte EOF as a silent boot-presence probe before invocation validation; it produces no handler call or stdout frame. — Chrome must be able to probe native-host installation without manufacturing a wake request or causing daemon work. +- [Phase 63]: Permit only the exact entry/constants/protocol native leaf graph plus node:os at this wave, with source and compiled graphs judged separately. — The positive graph prevents authority drift now while allowing Plan 04 to extend the verifier deliberately when its frozen runtime leaves exist. +- [Phase 63]: Reconstruct every native response through the closed outcome/reason table and await at most one framed stdout write. — Closed reconstruction and one-shot settlement keep stdout protocol-pure and prevent handler objects or raw errors from widening the native boundary. +- [Phase 63]: Let only exact ready FSB v1 health bypass or complete wake authority; incompatible responders are closed unavailable facts. — A generic, malformed, oversized, partially ready, or protocol-incompatible loopback listener must never be overwritten or mistaken for the owned daemon. +- [Phase 63]: Coalesce native wake with an atomic token-only directory lock, health-rechecked stale quarantine, and exact-token release. — Concurrent hosts may create at most one serve child, and no process identifier or signaling authority is needed for recovery or timeout settlement. +- [Phase 63]: Keep the stable native-host index as a one-shot composition root over only constants, protocol, runtime-layout, platform, daemon, and entry leaves. — The exact positive graph and unique absolute-Node serve edge prevent installer, router, agent, task, browser, auth, shell, or extra child-process authority from entering the host. +- [Phase 63]: Treat HKCU user/32 as the sole canonical Windows registration view and every user/64 value as a non-mutable shadow mismatch. — Chrome's first-read view must never be silently shadowed or repaired by broad registry authority. +- [Phase 63]: Publish only an exact package runtime whose lock-bound bundled production closure survives empty-cache offline reinstallation and full artifact revalidation. — Native registration must never point at the invoking checkout, npm cache, incomplete tarball, or an online fallback. +- [Phase 63]: Install only from both-absent state or accept both-exact-current as zero-write idempotence; refuse split, older, foreign, invalid, symlinked, unavailable, or shadowed state. — Persistent code-execution registration cannot be implicitly repaired or upgraded. +- [Phase 63]: Remove registration first and then only an internally exact marker-proved runtime, including an intact older owned version. — Uninstall authority follows proven ownership rather than the current invoker version and never reaches adjacent hosts or broad Chrome roots. +- [Phase 63]: Route native CLI work by exact own-key presence before list, all, client, or Claude expansion, then validate the complete flag record. — A malformed or mixed explicit target must fail before any native or legacy mutation. +- [Phase 63]: Reconstruct terminal receipts from exactly five ordinary own data fields with closed reasons, origins, and bounded control-free locations. — Prototype data, raw errors, and receipt extras must never become optimistic or sensitive output. +- [Phase 63]: Keep unresolved production dependency composition outside the CLI router and fail closed as unavailable. — Plan 05 has no safe production filesystem/process/registry composer or persisted receipt layer, and Plan 06 must not invent that architecture while claiming live operability. +- [Phase 63]: Keep Chrome native APIs inside one background helper with memory-only advisory presence and cooldown. — UI and persisted state cannot acquire native lifecycle authority. +- [Phase 63]: Allow actual wake only after the existing pure preflight returns exact agent_offline. — Native reachability cannot override provider, pairing, consent, or start authority. +- [Phase 63]: Treat exact native success as reachability only, then wait for the ordinary bridge and rerun pure preflight directly once. — Every failure preserves the original offline result and no user intent is replayed. +- [Phase 63]: Protect the sole nativeMessaging manifest addition with an exact prior-byte hash and extension-wide authority scan. — No other permission or extension module silently gains native authority. +- [Phase 63]: Fence native wake UI by safe Send intent, exact composer bytes, and monotonic edit revision through consent lookup. — Any input event or stale settlement must become a no-op, including edit-then-revert and a response racing a newer explicit Send. +- [Phase 63]: Render checking only in the existing delegation card and announcer with fixed info-tone copy. — Transient native reachability is presentation-only and cannot create actions, alerts, focus moves, feed/session/tab state, or native authority. +- [Phase 63]: Converge every native wake settlement through the existing preflight, consent, start, unpaired, and offline branches. — Native success remains reachability only; provider, pairing, consent, and start authority stay in their established controllers. +- [Phase 63]: Keep all eight genuine OS, Chrome, visual, keyboard, and screen-reader scenarios in one milestone-end ledger — Automated evidence may validate ledger integrity but cannot populate or promote live UAT +- [Phase 63]: Run compiled Phase 63 seams in one workspace-preserving build lifecycle — The complete prior build graph, unrelated dirty bytes, and raw Git index must survive every automated gate exactly +- [Phase 63]: Derive all 30 validation mappings directly from the twelve PLAN files — A mechanical bijection fails on any missing, duplicate, or altered task mapping while future reviews remain honestly pending +- [Phase 63]: Bind independent code, security, and UI reviews to one canonical patch, ordered manifest, implementation index, and worktree fingerprint — Review prose and verifier infrastructure cannot silently alter the implementation identity being approved +- [Phase 63]: Preserve resolved blocking findings in the final review record and rerun each affected review after RED/GREEN remediation — Any open or accepted HIGH/CRITICAL row remains a hard stop, while historical resolved rows stay auditable +- [Phase 63]: Use one separately packaged fixed Win32 Registry API helper instead of `reg.exe`, inherited `SystemRoot`, or `PATH` executable authority — Locale-dependent output and attacker-influenced executable selection cannot authorize an absent fact or registry mutation +- [Phase 63 closeout]: Supersede stale gate receipts by full task restart at a fresh commit fingerprint — Prior green evidence is retained in history but never represented as current when the reviewed tree has advanced +- [Phase 63 closeout]: Neutralize external lock-enabled Git stat-refresh interference with reversible per-entry assume-unchanged flags on content-clean generated files, reverted after the gates — Frozen wrappers, verifiers, and tests stay untouched while the raw-index byte invariant holds +- [Phase 64]: Treat only tool-calls and unknown as continuation finishes. — The pinned OpenCode 1.14.25 prompt loop continues only for those two reasons; every other bounded source-valid finish remains a candidate until EOF. +- [Phase 64]: Keep OpenCode fixture-gated but absent from production registry and compatibility exposure until Plan 05. — Parser and fixture drift must block CI before production selection, while the first-commit boundary must not smuggle in start authority. +- [Phase 64]: Project fixed tool and provider-error fields above the OpenCode parser. — Call identity, tool name, error status, and a closed diagnostic are sufficient; raw input, output, metadata, and error bodies remain confidential below the parser boundary. +- [Phase 64]: Reconstruct and recursively freeze the closed direct/owned-server topology before the supervisor reads adapter output. — Roles, streams, runtime references, bindings, lifecycle policy, and attestations remain provider-neutral data rather than callbacks or adapter-id branches. +- [Phase 64]: Keep fixed environment data serializable and spawn secrets opaque. — The sole Basic-password binding names a supervisor-owned reference and is legal only on the owned server and attach task; raw secret values have no contract field. +- [Phase 64]: Interpret policy evidence through one bounded own-data verifier and a closed assertion grammar. — Provider-native JSON, arbitrary prefixes, accessors, inherited records, cycles, and unbounded input cannot cross the attestation boundary. +- [Phase 64]: Use delegation and provider_server as the only durable runtime roles. — Task work and daemon infrastructure share one exact journal while only confirmed stale delegations create lost-run dispositions. +- [Phase 64]: Normalize exact version-1 Claude journals in memory without rewriting them on read. — Legacy recovery remains byte-compatible while every new mutation emits the closed version-2 role-aware schema. +- [Phase 64]: Derive private paths from four closed logical artifact kinds inside the minted run directory. — Callers cannot persist arbitrary cleanup paths, and the full contained graph is mode/symlink/foreign-node validated before mutation or removal. +- [Phase 64]: Persist fixed public environment and proven process identity only. — Secret bindings, resolved spawn environments, raw credentials, Authorization headers, and credential-shaped values remain structurally outside durable runtime state. +- [Phase 64]: Accept only OpenCode 1.14.25 after a shell-free bounded version probe and retained-realpath recheck. — Detection deliberately leaves account state unknown and never promotes partial evidence to readiness. +- [Phase 64]: Isolate OpenCode under private XDG config, test-home, and managed-config roots without overriding its native default model. — Project config, external skills, inherited Claude prompts, auto-update, and LSP downloads remain disabled. +- [Phase 64]: Carry task text only on task-child stdin and bind the sole opaque owned-server password reference only to server and attach roles. — Profile data cannot serialize task or secret values. +- [Phase 64]: Describe OpenCode policy with four closed generic attestations interpreted by the shared verifier. — No native checker, callback, extra adapter method, supervisor hook, reducer, or selector is introduced. +- [Phase 64]: Expose OpenCode only as an exact five-method frozen composition with task/server-only capabilities. — Detector, profile, parser, and supervisor-owned tree kill remain the only dependencies; the adapter gains no process, filesystem, network, secret, or timer authority. +- [Phase 64]: Promote Claude Code and OpenCode as one exact ordered registry/matrix/parser/manifest/fixture/adapter bijection. — The seven production and drift paths land in one atomic implementation commit, so registration cannot outrun its compatibility and native-drift evidence. +- [Phase 64]: Keep OpenCode spawn eligibility exact at 1.14.25 while Codex remains absent. — Newer same-major versions may be observed as degraded but cannot become spawn-authorizing evidence. +- [Phase 64]: Keep local provider evidence and browser evidence asymmetric. — Local doctor may expose bounded executable/version facts, while browser inventory and durable compatibility storage receive only closed availability/status facts. +- [Phase 64]: Require exact Claude Code/OpenCode registry and matrix membership before collecting or projecting evidence. — Roster corruption fails closed to canonical unsupported rows and cannot inject, omit, reorder, or case-vary provider authority. +- [Phase 64]: Ship OpenCode compatibility as observational data only while Codex remains unshipped. — Compatibility cannot select or recommend a provider, mutate settings, mark state dirty, grant spawn, or retain auth/billing/topology/native evidence. +- [Phase 64]: Keep owned-topology execution cold first and attach only after exact lease re-verification. — An already-selected task never changes process after warming, while topology, generation, process identity, secret presence, and authenticated health must all authorize a later attach. +- [Phase 64]: Retain only random owned-server secret bytes and materialize password/header strings transiently. — Only the reviewed server and selected attach spawn env plus bounded health request require the raw value; every mutable call object is scrubbed afterward. +- [Phase 64]: Release each attach lease only after task tree and runtime cleanup, with token-fenced idle teardown. — Reference counts protect active task sessions, stale idle callbacks cannot stop renewed work, and task cleanup always precedes server reclamation. +- [Phase 64]: Coalesce health, exit, idle, and daemon-close cleanup through one exact-once server stop. — Retiring leases become non-attachable immediately and no replacement server can warm until the proven old tree and volatile secret settle. +- [Phase 64]: Require generic effective-policy attestation before task authority — Process and owned-server evidence use only closed descriptors and the shared verifier; raw documents, credentials, headers, model values, and stderr remain transient. +- [Phase 64]: Make selected task-child spawn the permanent replay boundary — One role-checked writer sends the exact task plus EOF once; every later failure cleans up and settles without cold fallback. +- [Phase 64]: Publish a normalized result only after independent cleanup truth — Parser EOF, clean stderr, code 0/no signal, task-tree settlement, and runtime removal must all corroborate the private candidate before it becomes observable. +- [Phase 64]: Canonicalize only the exact shipped Claude Code and OpenCode records; keep Codex dormant and outside authorization. — One frozen exact provider table prevents browser authorization, compatibility, identity, and billing metadata from drifting apart. +- [Phase 64]: Reread background-owned selection before trust mutation and challenge consumption; keep side-panel start requests provider-free. — Only saved settings may select the adapter, so stale or attacker-supplied client intent cannot acquire provider authority. +- [Phase 64]: Bind a frozen canonical provider context only after request-bound started identity matches and controller start persists. — Events and final settlement retain accepted identity and honest billing even when settings change later. +- [Phase 64]: Reconstruct evicted-run provider context only from validated persisted provider and init metadata. — Current settings and daemon presentation fields cannot relabel or expand an already accepted run. +- [Phase 64]: Accept only exact canonical Claude Code or OpenCode own-data pairs at durable boundaries — Reconstruct frozen copies and derive later identity solely from persisted init metadata. +- [Phase 64]: Keep every normalized result in running state until an explicit authoritative terminal — Candidate results cannot hydrate or settle as fabricated success or failure. +- [Phase 64]: Mirror adapter-specific drift vocabularies in a bounded six-field browser projection — Throttle each shipped adapter independently and report only after terminal persistence succeeds. +- [Phase 64]: Upgrade compact MCP drift tuples at the background boundary with immutable accepted-run context — Preserve the wire contract while preventing provider relabeling or duplicate diagnostics. +- [Phase 64]: Use the canonical delegation-provider roster for exactly Claude Code and OpenCode compatibility — Avoid an OpenCode renderer branch and keep unshipped Codex fail-closed. +- [Phase 64]: Compute UI expiry from the earliest valid shipped-row timestamp — Missing or malformed shipped evidence fails closed to unknown. +- [Phase 64]: Keep the visible Providers section and CSS byte-identical — The sole HTML delta is the canonical helper script before the two existing consumers. +- [Phase 64]: Load the canonical provider helper before both side-panel consumers as the sole HTML change — Add no visible markup or CSS while enabling validated provider labels. +- [Phase 64]: Bind consent and trust to an exact canonical provider identity while keeping start intent provider-free — Background-owned settings remain the only adapter selection authority. +- [Phase 64]: Hide result and summary rows for every non-completed outcome — Only controller-authoritative completed terminal truth may present a persisted result candidate as success. +- [Phase 64]: Keep exactly three genuine OpenCode account, process, browser, and accessibility scenarios pending — Deterministic fixtures and DOM tests cannot promote human evidence. +- [Phase 64]: Use one explicit adapter aggregate section while retaining the separate first-commit drift gate — Root and focused execution cover every parser, fixture, and drift edge exactly once. +- [Phase 64]: Keep the existing direct drift-smoke CI job and one renamed Phase 64 root invocation — Preserve CI ordering and avoid duplicate or skipped adapter evidence. +- [Phase 64]: Mark all 28 implementation rows green only after every focused, historical, security, root, and preservation gate passes — Automated closure remains distinct from the pending live UAT boundary. +- [Phase 64]: Defer exactly UAT64-01 through UAT64-03 to the milestone-end live sweep — Formal verification found zero automated gaps; these cases require genuine account, process, browser, and accessibility evidence per the existing user instruction. +- [Phase 64]: Carry two non-blocking UI audit warnings forward — UI audit scored 21/24 with no blockers; 44px delegated targets and profile-version presentation coverage remain advisory findings recorded in 64-UI-REVIEW.md. +- [Phase 65]: Keep Phase 65 and milestone closure human-pending after zero-gap automated verification — UAT65-01 through UAT65-03 require genuine Codex accounts, live browser/process behavior, and assistive-technology evidence; automation cannot promote them. ### Pending Todos -None yet. +None. Phases 57-63 are automated/source complete; Phase 63 automated validation is closed (all 30 per-task rows green) and awaits phase verification plus the milestone-end human UAT sweep. ### Blockers/Concerns -None yet. Two open judgment calls flagged by research to resolve during Phase 52/55 planning (not blockers, but decisions to make explicitly rather than let default-implicitly): - -- Whether "orphaned" trans-unit IDs (present in a locale file but absent from current `messages.xlf`) should be hard-fail or warning-only in the Phase 55 drift gate. -- Which canonical staleness-tracking mechanism to use (source-hash sidecar vs. XLIFF `state=` attribute vs. the drift gate's own commit-to-commit diff) -- research recommends deciding this explicitly during Phase 55, not implicitly. +No active blocker. + +- **Milestone-end UAT gate:** Phase 58's 12 live Providers checks, Phase 59's 4 live pairing/lifecycle/accessibility checks, Phase 60's 7 authenticated CLI/OS/browser checks, Phase 61's 8 consolidated consent/theme/handoff/stream/worker/endurance/POSIX/restart scenarios, Phase 62's 3 doctor/stream/layout/accessibility checks, and Phase 63's eight genuine OS/Chrome/visual/accessibility scenarios remain pending. Per user instruction, all live evidence is accumulated and audited at milestone end; automated/source and independent review evidence is green through Phase 63 Plan 12 (automated validation closed), and no live pass is inferred. + +- **Phase 59 pairing decision resolved:** Use explicit `fsb-mcp-server pair`, a durable exact extension-Origin binding, a per-daemon 32-byte session credential, `pair --reset` for deliberate rebind, per-frame sessionId revalidation, and a secret-free `bridge.auth-status` acknowledgement. Silent TOFU is rejected. + +Phase 60 resolved the static inline-agent/profile and Windows shell-free resolution questions in source and deterministic tests. Their genuine installed-CLI corroboration remains in the deferred Phase 60 UAT ledger. Phase 64 Plan 07 resolved the remaining OpenCode server-shape item with cold-first execution plus an FSB-owned, authenticated `opencode serve` lease for later exact-identity attaches. + +- Milestone-end UAT gate: Phase 65 adds UAT65-01 through UAT65-03 (genuine auth matrix, live Codex/browser lifecycle, and accessibility/responsive rendering); all remain human_needed, pending, and evidence-empty. + +### Quick Tasks Completed + +| # | Description | Date | Commit | Status | Directory | +|---|-------------|------|--------|--------|-----------| +| 260720-jb5 | Patch Sheets session redaction and multi-tab visual-session token finalization | 2026-07-20 | 2f8359b2 | Verified | [260720-jb5-patch-sheets-session-redaction-and-multi](./quick/260720-jb5-patch-sheets-session-redaction-and-multi/) | +| 260715-hs1 | Replace Google Sheets OAuth with a zero-extra-auth signed-in tab session | 2026-07-15 | 9ab3d40d | Needs Review | [260715-hs1-replace-google-sheets-oauth-with-a-signe](./quick/260715-hs1-replace-google-sheets-oauth-with-a-signe/) | +| 260715-8wh | Implement production-safe Google Sheets API MVP with Chrome OAuth, typed read/write capabilities, and spreadsheet-data recording redaction | 2026-07-15 | a83d21b8 | | [260715-8wh-implement-production-safe-google-sheets-](./quick/260715-8wh-implement-production-safe-google-sheets-/) | +| 260707-7id | Record MCP agent sessions into logs, history, replay, and memory like autopilot runs | 2026-07-07 | 721e2826 | | [260707-7id-record-mcp-agent-sessions-into-logs-hist](./quick/260707-7id-record-mcp-agent-sessions-into-logs-hist/) | +| 260701-e6c | Implement Supabase to be T1 ready | 2026-07-01 | 2619d949 | | [260701-e6c-implement-this-supabase-to-be-t1-ready-u](./quick/260701-e6c-implement-this-supabase-to-be-t1-ready-u/) | +| 260701-e6d | Implement Microsoft Teams to be T1 ready | 2026-07-01 | working-tree | | [260701-e6d-implement-this-microsoft-teams-to-be-t1-](./quick/260701-e6d-implement-this-microsoft-teams-to-be-t1-/) | +| 260701-e69 | Implement Steam T1 readiness as blocked-policy terminal coverage | 2026-07-01 | blocked-working-tree | | [260701-e69-implement-steam-to-be-t1-ready-using-gsd](./quick/260701-e69-implement-steam-to-be-t1-ready-using-gsd/) | +| 260701-iz0 | Implement this Steam to be T1 ready | 2026-07-01 | 8d64b761 | | [260701-iz0-implement-this-steam-to-be-t1-ready](./quick/260701-iz0-implement-this-steam-to-be-t1-ready/) | +| 260701-e5j | Implement ClickUp to be T1 ready | 2026-07-01 | 999f07b0 | | [260701-e5j-implement-this-clickup-to-be-t1-ready-wo](./quick/260701-e5j-implement-this-clickup-to-be-t1-ready-wo/) | +| 260701-e5y | Implement Glama to be T1 ready | 2026-07-01 | working-tree | | [260701-e5y-implement-glama-to-be-t1-ready](./quick/260701-e5y-implement-glama-to-be-t1-ready/) | +| 260701-e60 | Implement LinkedIn to be T1 ready | 2026-07-01 | working-tree | | [260701-e60-implement-this-linkedin-to-be-t1-ready](./quick/260701-e60-implement-this-linkedin-to-be-t1-ready/) | +| 260701-e5v | Implement this Google Maps to be T1 ready | 2026-07-01 | working-tree | | [260701-e5v-implement-this-google-maps-to-be-t1-read](./quick/260701-e5v-implement-this-google-maps-to-be-t1-read/) | +| 260701-e5i | Implement Confluence to be T1 ready | 2026-07-01 | b8cd262a | | [260701-e5i-implement-this-confluence-to-be-t1-ready](./quick/260701-e5i-implement-this-confluence-to-be-t1-ready/) | +| 260701-e5j | Implement Fiverr to be T1 ready | 2026-07-01 | 41d5befd | | [260701-e5j-implement-this-fiverr-to-be-t1-ready](./quick/260701-e5j-implement-this-fiverr-to-be-t1-ready/) | +| 260701-e5y | Implement Microsoft Word to be T1 ready | 2026-07-01 | working-tree | | [260701-e5y-implement-this-microsoft-word-to-be-t1-r](./quick/260701-e5y-implement-this-microsoft-word-to-be-t1-r/) | +| 260701-e5m | Implement Fidelity to be T1 ready | 2026-07-01 | 72423b87 | | [260701-e5m-implement-fidelity-to-be-t1-ready](./quick/260701-e5m-implement-fidelity-to-be-t1-ready/) | +| 260701-e76 | Implement Uber rideshare to be T1 ready | 2026-07-01 | working-tree | | [260701-e76-implement-uber-rideshare-to-be-t1-ready](./quick/260701-e76-implement-uber-rideshare-to-be-t1-ready/) | +| 260701-e5v | Implement this Lyft to be T1 ready. | 2026-07-01 | working-tree | | [260701-e5v-implement-this-lyft-to-be-t1-ready](./quick/260701-e5v-implement-this-lyft-to-be-t1-ready/) | +| 260701-e6l | Implement Robinhood to be T1 ready | 2026-07-01 | working-tree | | [260701-e6l-implement-robinhood-to-be-t1-ready](./quick/260701-e6l-implement-robinhood-to-be-t1-ready/) | +| 260701-e74 | Fourth-agent T1 readiness integration pass for Uber, YouTube, and YouTube Music | 2026-07-01 | working-tree | | [260701-e74-fourth-agent-t1-readiness-integration-pa](./quick/260701-e74-fourth-agent-t1-readiness-integration-pa/) | +| 260701-e61 | Implement Netflix T1 readiness as blocked-policy terminal coverage | 2026-07-01 | working-tree | | [260701-e61-implement-this-netflix-to-be-t1-ready-ow](./quick/260701-e61-implement-this-netflix-to-be-t1-ready-ow/) | +| 260701-2m5 | Make Linear T1 ready | 2026-07-01 | working-tree | | [260701-2m5-make-linear-t1-ready](./quick/260701-2m5-make-linear-t1-ready/) | +| 260701-2m4 | Implement Sentry to be T1 ready | 2026-07-01 | working-tree | | [260701-2m4-implement-sentry-to-be-t1-ready](./quick/260701-2m4-implement-sentry-to-be-t1-ready/) | +| 260701-2mh | Make Uber Eats implementation T1 ready | 2026-07-01 | d4ab8789 | | [260701-2mh-make-uber-eats-implementation-t1-ready](./quick/260701-2mh-make-uber-eats-implementation-t1-ready/) | +| 260701-2sl | Implement PostHog to be T1 ready | 2026-07-01 | working-tree | | [260701-2sl-implement-posthog-to-be-t1-ready](./quick/260701-2sl-implement-posthog-to-be-t1-ready/) | +| 260701-2o4 | Implement Tinder to be T1 ready | 2026-07-01 | working-tree | | [260701-2o4-implement-tinder-to-be-t1-ready](./quick/260701-2o4-implement-tinder-to-be-t1-ready/) | +| 260701-2nw | Implement TikTok T1 readiness | 2026-07-01 | working-tree | | [260701-2nw-implement-this-tiktok-to-be-t1-ready](./quick/260701-2nw-implement-this-tiktok-to-be-t1-ready/) | +| 260701-2ln | Implement Eventbrite to be T1 ready | 2026-07-01 | working-tree | | [260701-2ln-implement-eventbrite-to-be-t1-ready](./quick/260701-2ln-implement-eventbrite-to-be-t1-ready/) | +| 260701-2lr | Implement Datadog to be T1 ready | 2026-07-01 | working-tree | | [260701-2lr-implement-datadog-to-be-t1-ready](./quick/260701-2lr-implement-datadog-to-be-t1-ready/) | +| 260701-2m8 | Make Kayak T1 ready | 2026-07-01 | working-tree | | [260701-2m8-make-kayak-t1-ready](./quick/260701-2m8-make-kayak-t1-ready/) | +| 260701-2mc | Implement Zendesk to be T1 ready | 2026-07-01 | working-tree | | [260701-2mc-implement-zendesk-to-be-t1-ready](./quick/260701-2mc-implement-zendesk-to-be-t1-ready/) | +| 260701-2ki | Implement AWS T1 readiness | 2026-07-01 | working-tree | | [260701-2ki-implement-aws-to-be-t1-ready](./quick/260701-2ki-implement-aws-to-be-t1-ready/) | +| 260701-2m5 | Implement Threads T1 readiness | 2026-07-01 | working-tree | | [260701-2m5-implement-this-threads-to-be-t1-ready](./quick/260701-2m5-implement-this-threads-to-be-t1-ready/) | +| 260701-2km | Implement Airtable to be T1 ready | 2026-07-01 | working-tree | | [260701-2km-implement-airtable-to-be-t1-ready-using-](./quick/260701-2km-implement-airtable-to-be-t1-ready-using-/) | +| 260701-2lo | Make OneNote T1-ready | 2026-07-01 | working-tree | | [260701-2lo-make-onenote-t1-ready](./quick/260701-2lo-make-onenote-t1-ready/) | +| 260701-2m6 | Implement Telegram to be T1 ready | 2026-07-01 | working-tree | | [260701-2m6-implement-telegram-to-be-t1-ready](./quick/260701-2m6-implement-telegram-to-be-t1-ready/) | +| 260701-2lx | Make Google Calendar T1 ready | 2026-07-01 | working-tree | | [260701-2lx-make-google-calendar-t1-ready](./quick/260701-2lx-make-google-calendar-t1-ready/) | +| 260701-2lw | Make this app Etsy T1-ready | 2026-07-01 | working-tree | | [260701-2lw-make-this-app-etsy-t1-ready](./quick/260701-2lw-make-this-app-etsy-t1-ready/) | +| 260701-2q3 | Implement OpenTable T1 readiness | 2026-07-01 | working-tree | | [260701-2q3-implement-opentable-to-be-t1-ready](./quick/260701-2q3-implement-opentable-to-be-t1-ready/) | +| 260701-2ll | Implement eBay to be T1 ready | 2026-07-01 | working-tree | | [260701-2ll-implement-ebay-to-be-t1-ready](./quick/260701-2ll-implement-ebay-to-be-t1-ready/) | +| 260701-2md | Make Mastodon T1-ready | 2026-07-01 | working-tree | | [260701-2md-make-mastodon-t1-ready](./quick/260701-2md-make-mastodon-t1-ready/) | +| 260701-2ku | Implement Claude T1 readiness | 2026-07-01 | working-tree | | [260701-2ku-implement-claude-to-be-t1-ready-using-gs](./quick/260701-2ku-implement-claude-to-be-t1-ready-using-gs/) | +| 260701-2lo | Implement Craigslist to be T1 ready | 2026-07-01 | working-tree | | [260701-2lo-implement-craigslist-to-be-t1-ready](./quick/260701-2lo-implement-craigslist-to-be-t1-ready/) | +| 260701-2lm | Make DoorDash T1-ready | 2026-07-01 | working-tree | | [260701-2lm-make-doordash-t1-ready](./quick/260701-2lm-make-doordash-t1-ready/) | +| 260701-2kj | Implement Amazon T1 readiness | 2026-07-01 | working-tree | | [260701-2kj-implement-amazon-to-be-t1-ready](./quick/260701-2kj-implement-amazon-to-be-t1-ready/) | +| 260701-2lr | Implement MiniMax T1 readiness | 2026-07-01 | working-tree | | [260701-2lr-implement-this-minimax-to-be-t1-ready](./quick/260701-2lr-implement-this-minimax-to-be-t1-ready/) | +| 260701-2mc | Implement Robinhood to be T1 ready | 2026-07-01 | 1dcb49a | | [260701-2mc-implement-robinhood-to-be-t1-ready](./quick/260701-2mc-implement-robinhood-to-be-t1-ready/) | +| 260701-2nu | Make this app Jira T1-ready | 2026-07-01 | working-tree | | [260701-2nu-make-this-app-jira-t1-ready](./quick/260701-2nu-make-this-app-jira-t1-ready/) | +| 260701-2km | Implement Carta to be T1 ready | 2026-07-01 | working-tree | | [260701-2km-implement-carta-to-be-t1-ready](./quick/260701-2km-implement-carta-to-be-t1-ready/) | +| 260701-2m2 | Implement Temporal to be T1 ready | 2026-07-01 | working-tree | | [260701-2m2-implement-this-temporal-to-be-t1-ready](./quick/260701-2m2-implement-this-temporal-to-be-t1-ready/) | +| 260701-2ml | Implement YouTube Music to be T1 ready | 2026-07-01 | 51666789 | | [260701-2ml-implement-youtube-music-to-be-t1-ready](./quick/260701-2ml-implement-youtube-music-to-be-t1-ready/) | +| 260701-e78 | Repair YouTube Music blocked-policy T1 readiness | 2026-07-01 | ce60e8cb | | [260701-e78-implement-youtube-music-to-be-t1-ready](./quick/260701-e78-implement-youtube-music-to-be-t1-ready/) | +| 260701-2no | Make YouTube T1-ready | 2026-07-01 | working-tree | | [260701-2no-make-youtube-t1-ready](./quick/260701-2no-make-youtube-t1-ready/) | +| 260701-j08 | Implement this YouTube to be T1 ready | 2026-07-01 | ce10b54d | | [260701-j08-implement-this-youtube-to-be-t1-ready](./quick/260701-j08-implement-this-youtube-to-be-t1-ready/) | +| 260701-2lz | Make this app OnlyFans T1 ready | 2026-07-01 | working-tree | | [260701-2lz-make-this-app-onlyfans-t1-ready](./quick/260701-2lz-make-this-app-onlyfans-t1-ready/) | +| 260701-2du | Fix CDP keyboard attach degradation that silently drops trusted keystrokes into cross-origin iframes (Stripe CVC) | 2026-07-01 | 293162d9 | | [260701-2du-fix-cdp-keyboard-attach-degradation-that](./quick/260701-2du-fix-cdp-keyboard-attach-degradation-that/) | +| 260701-2lu | Implement Netflix to be T1 ready | 2026-07-01 | working-tree | | [260701-2lu-implement-netflix-to-be-t1-ready](./quick/260701-2lu-implement-netflix-to-be-t1-ready/) | +| 260701-1nd | Make this app Calendly T1-ready | 2026-07-01 | working-tree | | [260701-1nd-make-this-app-calendly-t1-ready](./quick/260701-1nd-make-this-app-calendly-t1-ready/) | +| 260701-1kg | Make this app Walmart T1-ready | 2026-07-01 | working-tree | | [260701-1kg-make-this-app-walmart-t1-ready](./quick/260701-1kg-make-this-app-walmart-t1-ready/) | +| 260701-1kv | Make this app DockerHub T1-ready | 2026-07-01 | working-tree | | [260701-1kv-make-this-app-dockerhub-t1-ready](./quick/260701-1kv-make-this-app-dockerhub-t1-ready/) | +| 260701-1ka | Make this app Figma T1-ready | 2026-07-01 | working-tree | | [260701-1ka-make-this-app-figma-t1-ready](./quick/260701-1ka-make-this-app-figma-t1-ready/) | +| 260701-1kj | Make this app Instacart T1-ready | 2026-07-01 | working-tree | | [260701-1kj-make-this-app-instacart-t1-ready](./quick/260701-1kj-make-this-app-instacart-t1-ready/) | +| 260701-1tu | Make this app ClickHouse T1-ready | 2026-07-01 | working-tree | | [260701-1tu-make-this-app-clickhouse-t1-ready](./quick/260701-1tu-make-this-app-clickhouse-t1-ready/) | +| 260701-1lj | Make this app Slack T1-ready | 2026-07-01 | working-tree | | [260701-1lj-make-this-app-slack-t1-ready](./quick/260701-1lj-make-this-app-slack-t1-ready/) | +| 260630-vog | Make this app Panda Express T1-ready | 2026-07-01 | working-tree | | [260630-vog-make-this-app-pandaexpress-t1-ready](./quick/260630-vog-make-this-app-pandaexpress-t1-ready/) | +| 260630-vnj | Make this app Facebook T1-ready | 2026-07-01 | working-tree | | [260630-vnj-make-this-app-facebook-t1-ready](./quick/260630-vnj-make-this-app-facebook-t1-ready/) | +| 260630-vnw | Make this app New Relic T1-ready | 2026-07-01 | working-tree | | [260630-vnw-make-this-app-newrelic-t1-ready](./quick/260630-vnw-make-this-app-newrelic-t1-ready/) | +| 260630-vop | Make this app Redfin T1-ready | 2026-07-01 | working-tree | | [260630-vop-make-this-app-redfin-t1-ready](./quick/260630-vop-make-this-app-redfin-t1-ready/) | +| 260630-vq5 | Make this app Coinbase T1-ready | 2026-07-01 | working-tree | | [260630-vq5-make-this-app-coinbase-t1-ready](./quick/260630-vq5-make-this-app-coinbase-t1-ready/) | +| 260630-vns | Make this app Booking T1-ready | 2026-07-01 | working-tree | | [260630-vns-make-this-app-booking-t1-ready](./quick/260630-vns-make-this-app-booking-t1-ready/) | +| 260630-vo5 | Make this app CircleCI T1-ready | 2026-07-01 | working-tree | | [260630-vo5-make-this-app-circleci-t1-ready](./quick/260630-vo5-make-this-app-circleci-t1-ready/) | +| 260630-vpd | Make this app GitLab T1-ready | 2026-07-01 | working-tree | | [260630-vpd-make-this-app-gitlab-t1-ready](./quick/260630-vpd-make-this-app-gitlab-t1-ready/) | +| 260630-u7d | Make this app Airbnb T1-ready | 2026-07-01 | working-tree | | [260630-u7d-make-this-app-airbnb-t1-ready](./quick/260630-u7d-make-this-app-airbnb-t1-ready/) | +| 260630-u64 | Make this app Outlook T1-ready | 2026-07-01 | working-tree | | [260630-u64-make-this-app-outlook-t1-ready](./quick/260630-u64-make-this-app-outlook-t1-ready/) | +| 260630-u5z | Make this app Retool T1-ready | 2026-07-01 | working-tree | | [260630-u5z-make-this-app-retool-t1-ready](./quick/260630-u5z-make-this-app-retool-t1-ready/) | +| 260630-u70 | Make this app Excel T1-ready | 2026-07-01 | working-tree | | [260630-u70-make-this-app-excel-t1-ready](./quick/260630-u70-make-this-app-excel-t1-ready/) | +| 260630-u62 | Make this app Costco T1-ready | 2026-07-01 | working-tree | | [260630-u62-make-this-app-costco-t1-ready](./quick/260630-u62-make-this-app-costco-t1-ready/) | +| 260630-u6d | Make this app YNAB T1-ready | 2026-07-01 | working-tree | | [260630-u6d-make-this-app-ynab-t1-ready](./quick/260630-u6d-make-this-app-ynab-t1-ready/) | +| 260630-u7s | Make this app Todoist T1-ready | 2026-07-01 | working-tree | | [260630-u7s-make-this-app-todoist-t1-ready](./quick/260630-u7s-make-this-app-todoist-t1-ready/) | +| 260630-u7q | Make this app Expedia T1-ready | 2026-07-01 | working-tree | | [260630-u7q-make-this-app-expedia-t1-ready](./quick/260630-u7q-make-this-app-expedia-t1-ready/) | +| 260630-u6s | Make this app Webflow T1-ready | 2026-07-01 | working-tree | | [260630-u6s-make-this-app-webflow-t1-ready](./quick/260630-u6s-make-this-app-webflow-t1-ready/) | +| 260630-qjb | Make this app PowerPoint T1-ready | 2026-07-01 | working-tree | | [260630-qjb-make-this-app-powerpoint-t1-ready](./quick/260630-qjb-make-this-app-powerpoint-t1-ready/) | +| 260630-qjb | Make this app Lucid T1-ready | 2026-07-01 | working-tree | | [260630-qjb-make-this-app-lucid-t1-ready](./quick/260630-qjb-make-this-app-lucid-t1-ready/) | +| 260630-qj8 | Make this app Target T1-ready | 2026-07-01 | working-tree | | [260630-qj8-make-this-app-target-t1-ready](./quick/260630-qj8-make-this-app-target-t1-ready/) | +| 260630-qjd | Make Discord app T1-ready | 2026-07-01 | working-tree | | [260630-qjd-make-discord-app-t1-ready](./quick/260630-qjd-make-discord-app-t1-ready/) | +| 260630-qj0 | Make this app Snowflake T1-ready | 2026-07-01 | working-tree | | [260630-qj0-make-this-app-snowflake-t1-ready](./quick/260630-qj0-make-this-app-snowflake-t1-ready/) | +| 260630-ql3 | Make this app ChatGPT T1-ready | 2026-07-01 | working-tree | | [260630-ql3-make-this-app-chatgpt-t1-ready](./quick/260630-ql3-make-this-app-chatgpt-t1-ready/) | +| 260630-qj4 | Make this app Hack2Hire T1-ready | 2026-07-01 | working-tree | | [260630-qj4-make-this-app-hack2hire-t1-ready](./quick/260630-qj4-make-this-app-hack2hire-t1-ready/) | +| 260630-qiu | Make this app CockroachDB T1-ready | 2026-07-01 | working-tree | | [260630-qiu-make-this-app-cockroachdb-t1-ready](./quick/260630-qiu-make-this-app-cockroachdb-t1-ready/) | +| 260630-qjh | Make this app MSWord T1-ready | 2026-06-30 | working-tree | | [260630-qjh-make-this-app-msword-t1-ready](./quick/260630-qjh-make-this-app-msword-t1-ready/) | +| 260630-nh1 | Make this app Chipotle T1-ready | 2026-06-30 | working-tree | | [260630-nh1-make-this-app-chipotle-t1-ready](./quick/260630-nh1-make-this-app-chipotle-t1-ready/) | +| 260630-nhs | Make this app amplitude T1-ready | 2026-06-30 | working-tree | | [260630-nhs-make-this-app-amplitude-t1-ready](./quick/260630-nhs-make-this-app-amplitude-t1-ready/) | +| 260630-nh1 | Make this app dominos T1-ready | 2026-06-30 | working-tree | | [260630-nh1-make-this-app-dominos-t1-ready](./quick/260630-nh1-make-this-app-dominos-t1-ready/) | +| 260630-njd | Make this app WhatsApp T1-ready | 2026-06-30 | working-tree | | [260630-njd-make-this-app-whatsapp-t1-ready](./quick/260630-njd-make-this-app-whatsapp-t1-ready/) | +| 260630-nk0 | Make Medium T1-ready | 2026-06-30 | working-tree | | [260630-nk0-make-medium-t1-ready](./quick/260630-nk0-make-medium-t1-ready/) | +| 260630-njd | Make this app Starbucks T1-ready | 2026-06-30 | working-tree | | [260630-njd-make-this-app-starbucks-t1-ready](./quick/260630-njd-make-this-app-starbucks-t1-ready/) | +| 260630-nh3 | Make this app Bitbucket T1-ready | 2026-06-30 | working-tree | | [260630-nh3-make-this-app-bitbucket-t1-ready](./quick/260630-nh3-make-this-app-bitbucket-t1-ready/) | +| 260630-mh2 | Make this app instagram T1-ready | 2026-06-30 | working-tree | | [260630-mh2-make-this-app-instagram-t1-ready](./quick/260630-mh2-make-this-app-instagram-t1-ready/) | +| 260630-mj0 | Make Pinterest T1-ready | 2026-06-30 | working-tree | | [260630-mj0-make-this-app-pinterest-t1-ready](./quick/260630-mj0-make-this-app-pinterest-t1-ready/) | +| 260630-mga | Make Instagram T1-ready | 2026-06-30 | working-tree | | [260630-mga-make-instagram-t1-ready](./quick/260630-mga-make-instagram-t1-ready/) | +| 260630-mjs | Make MongoDB Atlas T1-ready | 2026-06-30 | working-tree | | [260630-mjs-make-this-app-mongodb-t1-ready](./quick/260630-mjs-make-this-app-mongodb-t1-ready/) | +| 260630-mgp | Make Stack Overflow T1-ready | 2026-06-30 | working-tree | | [260630-mgp-make-stack-overflow-t1-ready](./quick/260630-mgp-make-stack-overflow-t1-ready/) | +| 260630-moa | Make this app Netlify T1-ready | 2026-06-30 | working-tree | | [260630-moa-make-this-app-netlify-t1-ready](./quick/260630-moa-make-this-app-netlify-t1-ready/) | +| 260630-mjd | Make Priceline T1-ready | 2026-06-30 | working-tree | | [260630-mjd-make-this-app-priceline-t1-ready](./quick/260630-mjd-make-this-app-priceline-t1-ready/) | +| 260630-mgf | Make this app Reddit T1-ready | 2026-06-30 | working-tree | | [260630-mgf-make-this-app-reddit-t1-ready](./quick/260630-mgf-make-this-app-reddit-t1-ready/) | +| 260630-mgf | Make Tumblr T1-ready | 2026-06-30 | working-tree | | [260630-mgf-make-this-app-tumblr-t1-ready](./quick/260630-mgf-make-this-app-tumblr-t1-ready/) | +| 260630-m4c | Promote bsky public AppView reads to T1-ready | 2026-06-30 | working-tree | | [260630-m4c-promote-bsky-public-appview-reads-to-t1-](./quick/260630-m4c-promote-bsky-public-appview-reads-to-t1-/) | +| 260630-m1q | Make this app Stripe T1-ready | 2026-06-30 | working-tree | | [260630-m1q-make-this-app-stripe-t1-ready](./quick/260630-m1q-make-this-app-stripe-t1-ready/) | +| 260630-m0u | Make Meticulous same-origin GraphQL reads T1-ready | 2026-06-30 | working-tree | | [260630-m0u-make-this-app-meticulous-t1-ready](./quick/260630-m0u-make-this-app-meticulous-t1-ready/) | +| 260630-m35 | Make this app twilio T1-ready | 2026-06-30 | working-tree | | [260630-m35-make-this-app-twilio-t1-ready](./quick/260630-m35-make-this-app-twilio-t1-ready/) | +| 260630-m1a | Make Cloudflare T1-ready with same-origin dashboard read handlers | 2026-06-30 | working-tree | | [260630-m1a-make-cloudflare-app-t1-ready-by-adding-s](./quick/260630-m1a-make-cloudflare-app-t1-ready-by-adding-s/) | +| 260630-m2u | Promote X public same-origin profile and tweet reads to T1-ready | 2026-06-30 | working-tree | | [260630-m2u-promote-x-public-same-origin-profile-and](./quick/260630-m2u-promote-x-public-same-origin-profile-and/) | +| 260630-m16 | Make Terraform Cloud T1-ready | 2026-06-30 | working-tree | | [260630-m16-make-terraform-cloud-t1-ready](./quick/260630-m16-make-terraform-cloud-t1-ready/) | +| 260630-lhy | Promote Zillow public same-origin search reads to T1-ready | 2026-06-30 | working-tree | | [260630-lhy-promote-another-safe-same-origin-public-](./quick/260630-lhy-promote-another-safe-same-origin-public-/) | +| 260630-l0o | Promote TripAdvisor public same-origin reads to T1-ready | 2026-06-30 | working-tree | | [260630-l0o-promote-another-safe-same-origin-public-](./quick/260630-l0o-promote-another-safe-same-origin-public-/) | +| 260630-kqt | Promote Yelp public same-origin reads to T1-ready | 2026-06-30 | working-tree | | [260630-kqt-promote-one-more-safe-same-origin-public](./quick/260630-kqt-promote-one-more-safe-same-origin-public/) | +| 260630-kg0 | Promote npm public same-origin Spiferack reads to T1-ready | 2026-06-30 | working-tree | | [260630-kg0-promote-npm-public-same-origin-read-hand](./quick/260630-kg0-promote-npm-public-same-origin-read-hand/) | +| 260630-hct | Anonymous aggregate state-level region telemetry (IP-derived at ingest, k≥5 floor, self-hosted DB-IP, graceful "unknown" until dataset present) | 2026-06-30 | e2a1b67a..8edf3525 | | [260630-hct-anon-region-telemetry](./quick/260630-hct-anon-region-telemetry/) | +| 260630-k18 | Promote Hacker News public same-origin HTML reads to T1-ready | 2026-06-30 | working-tree | | [260630-k18-promote-hacker-news-public-same-origin-h](./quick/260630-k18-promote-hacker-news-public-same-origin-h/) | +| 260630-hgl | Promote Wikipedia public same-origin reads to T1-ready | 2026-06-30 | working-tree | | [260630-hgl-promote-wikipedia-public-same-origin-rea](./quick/260630-hgl-promote-wikipedia-public-same-origin-rea/) | +| 260630-ha5 | Promote LeetCode query-only same-origin reads to T1-ready | 2026-06-30 | working-tree | | [260630-ha5-promote-leetcode-query-only-same-origin-](./quick/260630-ha5-promote-leetcode-query-only-same-origin-/) | +| 260630-gry | Promote Shortcut no-param same-origin reads to app-specific T1a handler proof | 2026-06-30 | working-tree | | [260630-gry-promote-next-safe-same-origin-read-recip](./quick/260630-gry-promote-next-safe-same-origin-read-recip/) | +| 260629-ksj | Support i18n internationalization for latest updated showcase content | 2026-06-29 | working-tree | | [260629-ksj-support-i18n-internationalization-for-la](./quick/260629-ksj-support-i18n-internationalization-for-la/) | + +- ~~39-03 cold-start circuit-breaker FLAG (the eval smoke index reached 93.3KB / 96KB after the retail/marketplace sub-batch)~~ **RESOLVED by 39-04** per the orchestrator decision: the smoke byte ceiling was widened 96KB->512KB (the 96KB ceiling was sized for a tiny corpus; 39-04 reached 108.6KB at a flat 570 bytes/descriptor -- legitimate linear corpus growth, NO params-leak/layout regression). The tight <10ms load-time assert (the real cold-start latency concern) is KEPT, and a NEW per-descriptor footprint flatness check (<700 bytes/descriptor -- the real params-leak regression signal) was ADDED. 512KB is well within the SCALE-01 ~1-2MB full-corpus target; the authoritative full-corpus SCALE-01 cold-start gate (size + load-time) remains Phase 43, kept separate. 39-05/06 have ample headroom under 512KB. ## Deferred Items -Items acknowledged and carried forward from previous milestone closes (Chrome MV3/manual UAT evidence gaps, not fabricated passes; procedures archived under `.planning/milestones/*/` and `.planning/phases/*/`). None of this debt blocks v1.2.0, which does not touch the automation/T1 surface. +Items acknowledged and carried forward from previous milestone closes (Chrome MV3/manual UAT evidence gaps, not fabricated passes; procedures archived under `.planning/milestones/*/` and `.planning/phases/*/`). None of this debt blocks v0.9.91. | Category | Item | Status | Deferred At | |----------|------|--------|-------------| +| uat_gap | Phase 63 Plans 05-06 (real POSIX ownership/modes, Windows HKCU/WOW64/PE/bootstrap behavior, Chrome discovery, production CLI composition, installed launcher/native execution) | deferred to v0.9.91 milestone end; human_needed | Phase 63 Plan 06 | +| uat_gap | Phase 62 / 62-HUMAN-UAT.md (installed doctor/genuine stream, compatibility layout, keyboard/accessibility/live refresh) | deferred to v0.9.91 milestone end; 3 scenarios | Phase 62 Plan 06 | +| uat_gap | Phase 61 / 61-HUMAN-UAT.md (consent/theme/handoff, authenticated stream, worker eviction, 45-minute endurance, POSIX lifecycle, daemon restart classification) | deferred to v0.9.91 milestone end; 8 scenarios | Phase 61 Plan 08 | +| uat_gap | Phase 60 / 60-HUMAN-UAT.md (authenticated isolation, genuine JSONL provenance, live MCP read, POSIX/Windows tree kill, crash recovery, browser ownership/vault/consent) | deferred to v0.9.91 milestone end; 7 scenarios | Phase 60 Plan 04 | +| uat_gap | Phase 59 / 59-HUMAN-UAT.md (live pair, daemon restart invalidation, Chrome session clearing, accessibility/theme) | deferred to v0.9.91 milestone end; 4 scenarios | Phase 59 Plan 04 | +| uat_gap | Phase 58 / 58-HUMAN-UAT.md + 58-VISUAL-QA.md (live Providers theme/responsive/keyboard/motion and extension-state checks) | deferred to v0.9.91 milestone end; 12 scenarios | Phase 58 Plan 03 | +| uat_gap | v1.2.0 Phase 53 / 53-VISUAL-QA.md (VISUAL-01 live DE/CJK browser visual spot-check) | human_needed | v1.2.0 Phase 53 | | uat_gap | Phase 27 / 27-HUMAN-UAT.md (live FETCH-05 logged-in-shape UAT-27-01 + contrast + origin-pin) | human_needed; 3 scenarios | v0.9.99 Phase 27 | | uat_gap | Phase 29 / 29-HUMAN-UAT.md ([ASSUMED] internal-endpoint live capture) | human_needed | v0.9.99 Phase 29 | | uat_gap | Phase 30 / 30-HUMAN-UAT.md (UAT-30-01 live render/Grant/badge smoke) | human_needed | v0.9.99 Phase 30 | @@ -106,17 +415,54 @@ Items acknowledged and carried forward from previous milestone closes (Chrome MV | uat_gap | Phases 01/16/20/25 (v0.10/v0.11/v0.12 live-browser) | human_needed/partial | prior closes | | i18n_debt | WARNING-02 picker-cookie short-circuits bare-`/` Accept-Language redirect | closed in v1.2.0 Phase 56 (ROUTE-01/02) | v0.9.63, carried 6+ milestones | -Carry-forward publish/tag gates (pre-existing, user-gated): `npm publish fsb-mcp-server@0.9.0`; `npm publish fsb-mcp-server@0.10.0`; branch + tag pushes for v0.9.62 / v0.9.63 / v0.9.69 / v0.10.0 / v0.11.0 / v0.12.0; `clawhub publish "skills/FSB Skill"`; public package publication. None of this blocks v1.2.0. +Carry-forward publish/tag gates (pre-existing, user-gated): `npm publish fsb-mcp-server@0.9.0`; `npm publish fsb-mcp-server@0.10.0`; branch + tag pushes for v0.9.62 / v0.9.63 / v0.9.69 / v0.10.0 / v0.11.0 / v0.12.0 / v1.2.0; `clawhub publish "skills/FSB Skill"`; public package publication. None of this blocks v0.9.91. -v2 deferred (see REQUIREMENTS.md v1.2.0 v2 section): QA-01 (native-speaker/bilingual QA pass), I18N-FUTURE-01 (migrate stats page off ad hoc JSON mechanism), I18N-FUTURE-02 (automated visual regression pipeline), I18N-FUTURE-03 (translation-freshness reporting). +v2 deferred (see REQUIREMENTS.md v0.9.91 v2 section): CHAT-FUTURE-01/02 (chat-mode continuity via `--resume` / `codex resume` / `opencode --continue` + per-thread cwd pinning); GEMINI-FUTURE-01 (Gemini CLI adapter after live `--help` capture + JSONL schema pinning); ACP-FUTURE-01 (`@zed-industries/agent-client-protocol` unification once ≥2 non-Claude adapters have shipped); REMOTE-FUTURE-01 (remote/mobile delegation surfaces — v0.9.91 is explicitly localhost-only). ## Session Continuity -Last session: 2026-07-09 -Stopped at: Phase 53 complete — 5 drifted units resynced, stats-274 JSON retired, 19 hero/CTA strings transcreated; VISUAL-01 browser UAT deferred as human_needed. Continuing autonomously into Phase 54. +Last session: 2026-07-22T18:44:34.205Z +Stopped at: Phase 65 automated verification passed; human UAT65-01 through UAT65-03 pending Resume file: None - ## Next Actions -v1.2.0 complete and archived. Optional: finish VISUAL-01 human UAT. Start next milestone with `/gsd-new-milestone`. +Execute 64-10-PLAN.md next. Generalize durable event/controller hydration and safe per-adapter drift diagnostics while preserving the canonical accepted-run provider context established in Plan 09. + +## Performance Metrics + +| Phase | Plan | Duration | Notes | +|-------|------|----------|-------| +| Phase 63 P01 | 58 min | 3 tasks | 13 files | +| Phase 63 P02 | 18 min | 2 tasks | 5 files | +| Phase 63 P03 | 16 min | 3 tasks | 6 files | +| Phase 63 P04 | 27 min | 2 tasks | 10 files | +| Phase 63 P05 | 36 min | 3 tasks | 6 files | +| Phase 63 P06 | 24 min | 2 tasks | 4 files | +| Phase 63 P07 | 14 min | 2 tasks | 4 files | +| Phase 63 P08 | 28 min | 3 tasks | 5 files | +| Phase 63 P09 | 17 min | 2 tasks | 3 files | +| Phase 63 P10 | 25 min | 3 tasks | 7 files | +| Phase 63 P11 | 5h 26m | 3 review tasks | 38 files | +| Phase 63 P12 | 34min | 2 tasks | 1 files | +| Phase 64 P01 | 30 min | 1 task | 9 files | +| Phase 64 P02 | 27 min | 1 task | 7 files | +| Phase 64 P03 | 32 min | 1 task | 3 files | +| Phase 64 P04 | 36 min | 3 tasks | 4 files | +| Phase 64 P05 | 41 min | 1 task | 9 files | +| Phase 64 P06 | 38 min | 2 tasks | 13 files | +| Phase 64 P07 | 48 min | 3 tasks | 2 files | +| Phase 64 P08 | 49 min | 3 tasks | 6 files | +| Phase 64 P09 | 42 min | 3 tasks | 14 files | +| Phase 64 P10 | 31 min | 3 tasks | 9 files | +| Phase 64 P11 | 13 min | 2 tasks | 6 files | +| Phase 64 P12 | 14 min | 2 tasks | 7 files | +| Phase 64 P13 | 30 min | 3 tasks | 9 files | +| Phase 65 P01 | 22 min | 2 tasks | 6 files | +| Phase 65 P02 | 15 min | 2 tasks | 4 files | +| Phase 65 P03 | 20 min | 2 tasks | 6 files | +| Phase 65 P04 | 46 min | 3 tasks | 11 files | +| Phase 65 P05 | 1h 4m | 1 tasks | 32 files | +| Phase 65 P06 | 40 min | 2 tasks | 16 files | +| Phase 65 P07 | 21m | 2 tasks | 8 files | +| Phase 65 P08 | 55m | 2 tasks | 22 files | diff --git a/.planning/debug/knowledge-base.md b/.planning/debug/knowledge-base.md index ccefb5fe9..c9c24512f 100644 --- a/.planning/debug/knowledge-base.md +++ b/.planning/debug/knowledge-base.md @@ -1,6 +1,6 @@ --- status: complete -updated: 2026-06-15 +updated: 2026-07-16 --- # GSD Debug Knowledge Base @@ -16,3 +16,11 @@ Resolved debug sessions. Used by `gsd-debugger` to surface known-pattern hypothe - **Fix:** (a) Declare the missing module-scope thread state vars + add no-op persistSidepanelThreadState stub. (b) Add hydrateChatFromConversationId(convId) helper that reads fsbSessionLogs + fsbSessionIndex, filters by conversationId, sorts ascending by startTime, replays session.commands[] as user messages and session.completionMessage as ai completion. (c) Wire into DOMContentLoaded after initTabConversationStore -- welcome suppressed when hydrate count > 0. (d) Wire into swapToTabConversation for tabs with bound convId. (e) Update RESOLVED #1 + #3 in 11-RESEARCH.md inline. - **Files changed:** extension/ui/sidepanel.js, .planning/phases/11-tab-aware-side-panel-surface/11-RESEARCH.md --- + +## phase62-merged-view-suite — Extracted VM seams and byte-count pins drifted from current runtime contracts +- **Date:** 2026-07-16 +- **Error patterns:** mcp_client_inventory_unavailable in merged-view or identity integration, fsbReadCachedMcpClients is not defined, legacy importScripts count off by one after adding a worker dependency +- **Root cause:** Two extracted-handler VM tests still injected the obsolete live-refresh helper and expected the pre-Phase-62 envelope, while production had moved `getMcpClients` to the cache-only reader with `compatibilityExpiresAt`. After that correction, a legacy Lattice smoke test still pinned service-worker import counts to Phase 61 totals and did not account for the intentional Phase 62 drift-diagnostics import. +- **Fix:** Align extracted VM dependencies and response envelopes with production, then advance only the two documented service-worker import-count pins from 315/311 to 316/312. +- **Files changed:** tests/mcp-client-merged-view.test.js, tests/mcp-client-identity-integration.test.js, tests/lattice-provider-bridge-smoke.test.js +--- diff --git a/.planning/debug/resolved/phase62-merged-view-suite.md b/.planning/debug/resolved/phase62-merged-view-suite.md new file mode 100644 index 000000000..165a72527 --- /dev/null +++ b/.planning/debug/resolved/phase62-merged-view-suite.md @@ -0,0 +1,105 @@ +--- +status: resolved +trigger: "Phase 62 guarded full suite fails in tests/mcp-client-merged-view.test.js at line 443" +created: 2026-07-16T22:07:00Z +updated: 2026-07-16T22:27:41Z +--- + +## Current Focus + +hypothesis: Resolved. Two Phase 57 VM harnesses and one legacy service-worker import-count pin had drifted from intentional Phase 62 contracts; production behavior was correct. +test: The guarded `node scripts/run-phase60-full-tests.mjs` wrapper completed with exit 0 after both test-only alignments. +expecting: No further action. The serial suite preserves all protected workspace artifacts and human UAT remains deferred. +next_action: Archive this session and continue Phase 62 closeout. + +reasoning_checkpoint: + hypothesis: "The extracted handler fails because its VM contexts omit the exact `fsbReadCachedMcpClients` global it invokes; the catch converts that ReferenceError into `mcp_client_inventory_unavailable`." + confirming_evidence: + - "An in-memory probe exposed `ReferenceError: fsbReadCachedMcpClients is not defined` at the handler." + - "Both merged-view and immediately following identity-integration fail through that extracted route while production-focused cache/contract suites pass." + falsification_test: "If injecting only `fsbReadCachedMcpClients` with the current envelope does not make both focused suites pass, or production-focused suites fail afterward, this diagnosis is wrong." + fix_rationale: "Aligning the two test VMs with the production cache-only dependency and response field removes the stale seam while preserving the implementation under test and its fail-closed behavior." + blind_spots: "The guarded full suite is intentionally reserved for the root agent; this investigation verifies only deterministic focused and adjacent production suites." + +## Symptoms + +expected: The guarded root suite should pass; the own-extension cross-context request in `mcp-client-merged-view.test.js` should receive `{ success: true, refreshOutcome: 'unavailable', clients: { cursor: ... } }`. +actual: The request receives `{ success: false, error: 'mcp_client_inventory_unavailable' }`. +errors: "AssertionError [ERR_ASSERTION]: own-extension cross-context request receives the exact successful envelope at tests/mcp-client-merged-view.test.js:443:10" +reproduction: Run `node scripts/run-phase60-full-tests.mjs`; the suite reaches `node tests/mcp-client-merged-view.test.js` and fails at line 443. First isolate with `node tests/mcp-client-merged-view.test.js`. +started: First observed in the single post-`ba572f94` guarded Phase 62 full-suite run on 2026-07-16; the preceding focused Phase 62 suites were green. + +## Eliminated + +- hypothesis: The Phase 62 production cache-only inventory route regressed. + evidence: Production-focused background dispatch passes its cache-only/no-live-refresh assertions, and the Phase 62 contract suite passes 763/0; only VMs omitting the current reader fail. + timestamp: 2026-07-16T22:16:58Z + +## Evidence + +- timestamp: 2026-07-16T22:27:41Z + checked: Final guarded full-suite retry and protected workspace hashes + found: The wrapper exited 0 with `[phase60-full-tests] PASS: full suite passed and workspace state was preserved`; MCP build and all three showcase artifacts retain their exact protected SHA-256 values. + implication: Both stale test seams are closed, the entire serial suite is green, and the user-owned dirty workspace remains byte-for-byte preserved. + +- timestamp: 2026-07-16T22:25:42Z + checked: Legacy `lattice-provider-bridge-smoke.test.js` failure after the first harness correction + found: Production intentionally added the Phase 62 drift-diagnostics `importScripts()` call, but the legacy Phase 6 byte-count assertions still expected Phase 61 totals 315/311 instead of 316/312. Updating only the two expectations produced 110 passed/0 failed, and the Phase 61–62 contract remained 763/0. + implication: The second guarded-suite stop was another stale test pin, not a production regression. + +- timestamp: 2026-07-16T22:19:32Z + checked: Final identity rerun, diff check, and protected hashes after assertion-label cleanup + found: Identity-integration exits 0; `git diff --check` exits 0; MCP build and showcase SHA-256 values still match the recorded baseline exactly. + implication: The finalized patch is deterministic, formatting-clean, and preserves every protected artifact byte-for-byte. + +- timestamp: 2026-07-16T22:18:41Z + checked: Post-fix production regressions, focused diff, and protected artifacts + found: Background dispatch exits 0 with 293 passed/0 failed; Phase 62 contract exits 0 with 763 passed/0 failed; diff check is clean. MCP build and all three showcase SHA-256 hashes exactly match the pre-fix baseline. + implication: The test-only fix leaves production contracts and protected user artifacts byte-exactly unchanged. + +- timestamp: 2026-07-16T22:17:54Z + checked: Exact post-fix reproductions for merged-view and identity-integration + found: Both commands exit 0. Merged-view prints PASS after exercising its own-extension success, external rejection, same-context success, storage rejection, and registry rejection cases; identity-integration also prints PASS. + implication: The minimal cache-reader/envelope alignment fixes both deterministic stale seams and preserves the bounded negative behavior. + +- timestamp: 2026-07-16T22:16:58Z + checked: Adjacent `node tests/mcp-client-identity-integration.test.js` + found: It exits 1 at its first same-context `getMcpClients` success assertion and injects the same obsolete `fsbRefreshMcpCompatibility` symbol. + implication: The guarded chain would immediately encounter the same stale seam after merged-view; the minimal justified fix covers both adjacent VM harnesses. + +- timestamp: 2026-07-16T22:16:14Z + checked: Exact in-memory diagnostic probe of the extracted runtime handler + found: Revealing the swallowed exception produced `ReferenceError: fsbReadCachedMcpClients is not defined` at the `getMcpClients` handler. + implication: The missing symbol—not provider data, storage, registry, timing, or merge logic—is the direct cause of the observed bounded error. + +- timestamp: 2026-07-16T22:16:14Z + checked: Production-focused `mcp-bridge-background-dispatch.test.js` and Phase 62 `delegation-phase-contract.test.js` + found: Both exit 0; the background suite proves cache-only inventory, zero live refreshes, and the `compatibilityExpiresAt` envelope, while the Phase 62 contract reports 763 passed and 0 failed. + implication: The production route and current contract are green independently of the stale merged-view VM, eliminating a production regression. + +- timestamp: 2026-07-16T22:14:32Z + checked: Complete merged-view harness and production compatibility/router path + found: Production `case 'getMcpClients'` awaits `fsbReadCachedMcpClients()`; the VM never defines or extracts that function and instead defines unused `fsbRefreshMcpCompatibility`. The caught ReferenceError is therefore converted to the observed bounded error. Production success also emits `compatibilityExpiresAt`, absent from this test's expected envelope. + implication: The exact failure mechanism is an obsolete test dependency and response contract. Production-focused tests must still pass before classifying it as fixture-only. + +- timestamp: 2026-07-16T22:12:56Z + checked: Isolated reproduction with `node tests/mcp-client-merged-view.test.js` + found: The test exits 1 at line 443; actual is exactly `{ success: false, error: 'mcp_client_inventory_unavailable' }` instead of the expected successful cursor envelope. + implication: The guarded-suite symptom is deterministic and isolated; execution reaches the production fail-closed inventory branch before the assertion. + +- timestamp: 2026-07-16T22:12:14Z + checked: Debug knowledge base and project skill discovery + found: The knowledge base contains only the unrelated Phase 11 sidepanel hydration issue, and neither `.codex/skills/` nor `.agents/skills/` exists in this repository. + implication: There is no prior matching diagnosis or project-local rule to shortcut or constrain this investigation. + +- timestamp: 2026-07-16T22:07:00Z + checked: Guarded full-suite output + found: The suite passed Phase 62 compatibility, drift, doctor, storage, background, and contract gates before failing at merged-view line 443 with `mcp_client_inventory_unavailable`. + implication: The failure is localized to the merged-view request fixture or a dependency unique to that dispatch path, not the final Providers UI ordering correction. + +## Resolution + +root_cause: The Phase 57 VM integration harnesses drifted from the Phase 62 cache-only runtime contract: both inject `fsbRefreshMcpCompatibility`, but extracted production `getMcpClients` calls `fsbReadCachedMcpClients`; merged-view also expects the pre-Phase-62 envelope without `compatibilityExpiresAt`. The missing global throws a ReferenceError that production correctly catches and bounds. +fix: Updated only the two extracted-handler VM harnesses to inject `fsbReadCachedMcpClients` instead of the obsolete live-refresh symbol, return `compatibilityExpiresAt: null`, and assert the current success envelope. +verification: `node tests/mcp-client-merged-view.test.js` PASS; `node tests/mcp-client-identity-integration.test.js` PASS; `node tests/mcp-bridge-background-dispatch.test.js` 293 passed/0 failed; `node tests/delegation-phase-contract.test.js` 763 passed/0 failed; `node tests/lattice-provider-bridge-smoke.test.js` 110 passed/0 failed; guarded `node scripts/run-phase60-full-tests.mjs` exited 0 and reported workspace preservation; protected artifact SHA-256 values unchanged. +files_changed: [tests/mcp-client-merged-view.test.js, tests/mcp-client-identity-integration.test.js, tests/lattice-provider-bridge-smoke.test.js, .planning/debug/resolved/phase62-merged-view-suite.md] diff --git a/.planning/phases/26-recipe-schema-bundled-interpreter-mv3-ci-guard/26-01-PLAN.md b/.planning/phases/26-recipe-schema-bundled-interpreter-mv3-ci-guard/26-01-PLAN.md deleted file mode 100644 index ce5a0b514..000000000 --- a/.planning/phases/26-recipe-schema-bundled-interpreter-mv3-ci-guard/26-01-PLAN.md +++ /dev/null @@ -1,272 +0,0 @@ ---- -phase: 26-recipe-schema-bundled-interpreter-mv3-ci-guard -plan: 01 -type: execute -wave: 1 -depends_on: [] -files_modified: - - extension/lib/cfworker-json-schema.min.js - - extension/lib/minisearch.min.js - - extension/lib/jmespath.min.js - - extension/utils/capability-recipe-schema.js - - extension/background.js - - catalog/recipes/_fixtures/valid-recipe.json - - catalog/recipes/_fixtures/reject-field-script.json - - catalog/recipes/_fixtures/reject-field-expr.json - - catalog/recipes/_fixtures/reject-field-transform.json - - catalog/recipes/_fixtures/reject-field-code.json - - catalog/recipes/_fixtures/reject-field-fn.json - - catalog/recipes/_fixtures/reject-field-js.json - - catalog/recipes/_fixtures/reject-unknown-field.json - - catalog/recipes/_fixtures/reject-bad-method.json - - catalog/recipes/_fixtures/reject-bad-authstrategy.json - - tests/capability-recipe-schema.test.js - - package.json -autonomous: true -requirements: [CAP-01, CAP-05] -user_setup: [] - -must_haves: - truths: - - "The three vendored libraries (cfworker-json-schema, minisearch, jmespath) exist in extension/lib/ and pass node --check as classic scripts" - - "The service worker loads the three libs and the recipe-schema module via additive importScripts lines" - - "A valid recipe passes schema validation" - - "A recipe carrying any of the six forbidden fields (script/expr/transform/code/fn/js) is rejected with a typed RECIPE_UNKNOWN_FIELD" - - "A recipe with an unknown top-level field is rejected" - - "A recipe with a bad method or authStrategy enum value is rejected" - - "No manifest or permission change is introduced" - - "Decision coverage (26-CONTEXT.md): implements D-01, D-02, D-03, D-05, D-06, D-07, D-08, D-09, D-10, D-19" - artifacts: - - path: "extension/lib/cfworker-json-schema.min.js" - provides: "Eval-free JSON Schema validator as a browser-loadable IIFE (global CfworkerJsonSchema)" - contains: "var CfworkerJsonSchema" - - path: "extension/lib/minisearch.min.js" - provides: "Vendored minisearch UMD (global MiniSearch) — shipped per CAP-05, wired Phase 28" - min_lines: 1 - - path: "extension/lib/jmespath.min.js" - provides: "Vendored jmespath UMD (global lowercase jmespath) for read-only extract" - min_lines: 1 - - path: "extension/utils/capability-recipe-schema.js" - provides: "RECIPE_SCHEMA closed-vocabulary JSON Schema + FSB_RECIPE_SCHEMA_VERSION + validateRecipe returning typed RECIPE_* codes" - exports: ["RECIPE_SCHEMA", "FSB_RECIPE_SCHEMA_VERSION", "validateRecipe"] - - path: "catalog/recipes/_fixtures/valid-recipe.json" - provides: "Canonical accept fixture shared by the schema test and the CI guard" - contains: "schemaVersion" - - path: "tests/capability-recipe-schema.test.js" - provides: "Accept/reject fixture suite proving CAP-01" - min_lines: 30 - key_links: - - from: "extension/background.js" - to: "extension/lib/cfworker-json-schema.min.js" - via: "importScripts in the SW boot chain" - pattern: "importScripts\\('lib/cfworker-json-schema.min.js'\\)" - - from: "extension/utils/capability-recipe-schema.js" - to: "CfworkerJsonSchema.Validator" - via: "typeof-guarded global access (getFSBRecipeValidator)" - pattern: "CfworkerJsonSchema" - - from: "tests/capability-recipe-schema.test.js" - to: "catalog/recipes/_fixtures" - via: "fixture file reads" - pattern: "_fixtures" ---- - -## Decision Coverage - -Traceability to `26-CONTEXT.md` locked decisions implemented by this plan's tasks: - -- **D-01:** PATH A — the 3 libs vendored into `extension/lib/` and loaded via `importScripts` (Task 1) -- **D-02:** `@cfworker/json-schema` IIFE-bundled via the esbuild one-off; `minisearch`/`jmespath` vendored as-is (Task 1) -- **D-03:** all three libraries ship this phase (Task 1) -- **D-05:** no manifest/permission change; the `background.js` edit is additive `importScripts` only (Task 1) -- **D-06:** versioned closed-vocabulary recipe JSON Schema (Task 2) -- **D-07:** the six forbidden script-like field names are rejected (Tasks 2-3) -- **D-08:** `authStrategy` enum locked at the four v1 members (Task 2) -- **D-09:** pagination out of v1 (Task 2 — absent by construction) -- **D-10:** `schemaVersion` envelope via `FSB_RECIPE_SCHEMA_VERSION` (Task 2) -- **D-19:** schema accept/reject test suite — suite (a) (Task 3) - - -Establish the Wall-1 data spine: the three vendored libraries inside the extension package and the closed-vocabulary recipe JSON Schema that rejects every forbidden/unknown field with a typed error. This plan delivers CAP-05 (the three libs ship, no manifest/permission change) and CAP-01 (the versioned recipe-as-data schema rejects out-of-vocabulary fields), plus the shared accept/reject fixture set that both this plan's schema test and Plan 03's CI guard consume. - -Purpose: Nothing downstream can validate, bind, or guard a recipe until the validator global exists as a browser-loadable IIFE and the schema actually rejects forbidden fields. This is the foundational constraint of the phase — the entire "no code fetched as data" line is mechanically enforced here. - -Output: `extension/lib/{cfworker-json-schema,minisearch,jmespath}.min.js`, `extension/utils/capability-recipe-schema.js`, additive `importScripts` lines in `extension/background.js`, the `catalog/recipes/_fixtures/*.json` fixture set, `tests/capability-recipe-schema.test.js`, and a `package.json` `build:cfworker` script + one `scripts.test` entry. - - - -@$HOME/.claude/get-shit-done/workflows/execute-plan.md -@$HOME/.claude/get-shit-done/templates/summary.md - - - -@.planning/STATE.md -@.planning/ROADMAP.md -@.planning/REQUIREMENTS.md -@.planning/phases/26-recipe-schema-bundled-interpreter-mv3-ci-guard/26-CONTEXT.md -@.planning/phases/26-recipe-schema-bundled-interpreter-mv3-ci-guard/26-RESEARCH.md -@.planning/phases/26-recipe-schema-bundled-interpreter-mv3-ci-guard/26-PATTERNS.md -@.planning/phases/26-recipe-schema-bundled-interpreter-mv3-ci-guard/26-VALIDATION.md - - - - -Vendored-lib globals (verified from package headers; PATCH the schema module and tests against these EXACT names): - - @cfworker/json-schema IIFE -> globalThis.CfworkerJsonSchema (header: `var CfworkerJsonSchema = (() => { ... })();`); exposes CfworkerJsonSchema.Validator - - minisearch UMD -> globalThis.MiniSearch - - jmespath UMD -> globalThis.jmespath (LOWERCASE — header footer `this.jmespath = {}`) - -CfworkerJsonSchema validator API (verified @4.1.1): - new CfworkerJsonSchema.Validator(schema, draft /* '2020-12' */, shortCircuit /* false => emit all errors */) - validator.validate(value) -> { valid: boolean, errors: OutputUnit[] } - OutputUnit = { keyword, keywordLocation, instanceLocation, error } - NB: cfworker ASSERTS format:'uri' by default in 2020-12 (verified). Use format:'uri' on `origin` ONLY; validate `endpoint` with a `pattern` instead (Pitfall 4). - -Dual-export IIFE shell to clone (from extension/utils/trigger-store.js tail + extension/utils/value-extractor.js tail): - (function(global) { - 'use strict'; - // module body ... - var exportsObj = { /* public API */ }; - global.FsbCapabilityRecipeSchema = exportsObj; // SW importScripts consumer reads this global - if (typeof module !== 'undefined' && module.exports) { module.exports = exportsObj; } // node tests require() this - })(typeof globalThis !== 'undefined' ? globalThis : this); - -Version-const idiom to mirror (extension/utils/trigger-store.js:58-59): - var FSB_TRIGGER_REGISTRY_PAYLOAD_VERSION = 1; -> var FSB_RECIPE_SCHEMA_VERSION = 1; (also the schemaVersion `const` in RECIPE_SCHEMA) - -importScripts idiom + lz-string precedent (extension/background.js:97): - try { importScripts('lib/lz-string.min.js'); } catch (e) { console.error('[FSB] Failed to load lz-string.min.js:', e.message); } - -Test harness (verified): tests/fixtures/run-task-harness.js exports installChromeMock — NOT needed for pure schema validation (no chrome.* touched here). -cfworker IIFE test-load: a plain require() will NOT populate module.exports (it assigns `var CfworkerJsonSchema`). Test-load via vm.runInThisContext(readFileSync(path,'utf8')) then read globalThis.CfworkerJsonSchema (test-only; the CI guard scans the recipe path, not tests). - - - - - - - Task 1: Vendor the three libraries into extension/lib/ and wire importScripts - extension/lib/cfworker-json-schema.min.js, extension/lib/minisearch.min.js, extension/lib/jmespath.min.js, extension/background.js, package.json - - - extension/lib/lz-string.min.js (header — the `var X=...()` classic-script IIFE shape every lib/*.min.js follows) - - extension/background.js:7-118 (the importScripts boot chain; line 97 is the lz-string precedent; lines 47-50 show the value-extractor→trigger-store→... load-order comment style to mirror) - - scripts/validate-extension.mjs:79 (EXT_DIRS includes 'lib' → every lib/*.js is node --check'd as a classic script) and the walk at :82-103 - - esbuild.config.js:67-78 (the offscreen-stt ENTRY — the per-entry platform:browser / target:chrome120 / format:iife / legalComments:none precedent for the one-off cfworker build) - - node_modules/@cfworker/json-schema/dist/esm/index.js (the esbuild input — exists, zero deps verified) - - node_modules/minisearch/dist/umd/index.js (vendor as-is) and node_modules/jmespath/jmespath.js (vendor as-is) - - .planning/phases/26-recipe-schema-bundled-interpreter-mv3-ci-guard/26-RESEARCH.md:120-137 (the exact verified esbuild command + the two integration options) - - .planning/phases/26-recipe-schema-bundled-interpreter-mv3-ci-guard/26-PATTERNS.md:84-116, 370-393 (vendoring + background.js MODIFY anchors) - - - Produce `extension/lib/cfworker-json-schema.min.js` by running the repo-pinned esbuild one-off (NOT npx --yes, NOT an esbuild.config.js ENTRIES push — ENTRIES emit to extension/dist/, this emits to extension/lib/): `node_modules/.bin/esbuild node_modules/@cfworker/json-schema/dist/esm/index.js --bundle --format=iife --global-name=CfworkerJsonSchema --platform=browser --target=chrome120 --legal-comments=none --outfile=extension/lib/cfworker-json-schema.min.js`. Copy `node_modules/minisearch/dist/umd/index.js` to `extension/lib/minisearch.min.js` and `node_modules/jmespath/jmespath.js` to `extension/lib/jmespath.min.js` AS-IS (both are already UMD classic scripts). Add a `package.json` script `"build:cfworker": "node_modules/.bin/esbuild node_modules/@cfworker/json-schema/dist/esm/index.js --bundle --format=iife --global-name=CfworkerJsonSchema --platform=browser --target=chrome120 --legal-comments=none --outfile=extension/lib/cfworker-json-schema.min.js"` documenting regeneration (the built file is committed, matching every existing lib/*.min.js). Add additive importScripts lines to `extension/background.js` as a contiguous commented block (mirror the value-extractor→trigger-store comment style at :47-50), load-order: the three libs FIRST (so their globals exist) then the recipe-schema module, each wrapped `try { importScripts('lib/...') } catch (e) { console.error('[FSB] Failed to load ...:', e.message); }` — order: `lib/jmespath.min.js`, `lib/minisearch.min.js`, `lib/cfworker-json-schema.min.js`, `utils/capability-recipe-schema.js`. Do NOT touch `extension/manifest.json` (CAP-05 / D-05 — manifest and permissions unchanged; background.js is byte-frozen as an esbuild input, only additive importScripts lines are permitted). The durable reason cfworker MUST be IIFE-bundled (not vendored raw): a top-level import/export is a SyntaxError when loaded via importScripts in a classic service worker (the "node --check fails on Node 20" rationale in D-02 is version-fragile — it passes on Node 25; cite the importScripts runtime reason). - - - node --check extension/lib/cfworker-json-schema.min.js && node --check extension/lib/minisearch.min.js && node --check extension/lib/jmespath.min.js && grep -q "var CfworkerJsonSchema" extension/lib/cfworker-json-schema.min.js && grep -c "importScripts('lib/cfworker-json-schema.min.js')" extension/background.js && node -e "const fs=require('fs');const s=fs.readFileSync('extension/lib/cfworker-json-schema.min.js','utf8');if(/\beval\s*\(|\bnew\s+Function\b|\bimport\s*\(/.test(s)){console.error('cfworker bundle contains forbidden construct');process.exit(1)}console.log('cfworker bundle clean')" - - - - All three `extension/lib/*.min.js` files exist and pass `node --check` (classic-script parse) on the repo Node. - - `extension/lib/cfworker-json-schema.min.js` contains the literal `var CfworkerJsonSchema` and contains ZERO `eval(`, `new Function`, or `import(` substrings. - - `extension/background.js` contains four new additive `importScripts(...)` lines (`lib/jmespath.min.js`, `lib/minisearch.min.js`, `lib/cfworker-json-schema.min.js`, `utils/capability-recipe-schema.js`), each in the `try { ... } catch (e) { console.error(...) }` form, ordered libs-before-module. - - `git diff extension/manifest.json` is empty (no manifest/permission change — CAP-05). - - `package.json` has a `scripts.build:cfworker` entry equal to the one-off esbuild command above. - - Three libs vendored + node --check clean + globals present; importScripts wired additively; manifest untouched; build:cfworker script documents regeneration. - - - - Task 2: Author the closed-vocabulary recipe JSON Schema module - extension/utils/capability-recipe-schema.js - - - extension/utils/trigger-store.js:1-2 and :183-200 (the dual-export IIFE shell to clone) and :58-59 (the FSB_*_PAYLOAD_VERSION version-const idiom for FSB_RECIPE_SCHEMA_VERSION) - - extension/utils/value-extractor.js (the pure, chrome-free dual-export module posture — no _getChrome resolver; same posture the schema module follows) - - extension/ws/ws-client.js:98-99 (getFSBLZStringCodec — the typeof-guarded global accessor pattern for getFSBRecipeValidator) - - extension/ws/mcp-tool-dispatcher.js:190-198 (createMcpOwnershipError — the typed {success:false, code, errorCode, error, ...context} RETURN shape, never throw) - - .planning/phases/26-recipe-schema-bundled-interpreter-mv3-ci-guard/26-RESEARCH.md:331-350 (validateRecipe + CfworkerJsonSchema error→typed-code mapping), :407-409 (the optional csrf object resolution), :402-405 (authStrategy locked at the four D-08 members) - - .planning/phases/26-recipe-schema-bundled-interpreter-mv3-ci-guard/26-CONTEXT.md D-06/D-07/D-08/D-10/D-13/D-15 (closed vocab, forbidden names, enum, version, eval-free, typed-error) - - - - validateRecipe(validRecipe) -> { success: true } - - validateRecipe(recipe with a `script` field) -> { success: false, code: 'RECIPE_UNKNOWN_FIELD', field: 'script' } (and the same for expr/transform/code/fn/js) - - validateRecipe(recipe with an unknown top-level field e.g. `foo`) -> { success: false, code: 'RECIPE_UNKNOWN_FIELD', field: 'foo' } - - validateRecipe(recipe with method:'CONNECT') -> { success: false, code: 'RECIPE_OPCODE_INVALID' } - - validateRecipe(recipe with authStrategy:'persisted-query-hash') -> { success: false, code: 'RECIPE_OPCODE_INVALID' } - - validateRecipe(recipe missing schemaVersion or with wrong schemaVersion const) -> { success: false, code: 'RECIPE_SCHEMA_INVALID' } - - When CfworkerJsonSchema global is absent -> { success: false, code: 'RECIPE_SCHEMA_INVALID', error: 'validator unavailable' } - - - Create `extension/utils/capability-recipe-schema.js` as a dual-export IIFE (clone the trigger-store.js shell; global name `FsbCapabilityRecipeSchema`; also `module.exports`). Define `var FSB_RECIPE_SCHEMA_VERSION = 1;`. Export `RECIPE_SCHEMA` — a JSON Schema object with `additionalProperties:false` at EVERY object level and the closed top-level vocabulary per D-06: `schemaVersion` (a JSON-Schema `const` equal to FSB_RECIPE_SCHEMA_VERSION), `id` (the recipe identity field — lock to the name `id` to match the `valid-recipe.json` accept fixture; do NOT use `slug`), `origin` (string, `format:'uri'`), `endpoint` (string, `pattern` requiring a leading `/` — NOT format:'uri', per Pitfall 4 because cfworker asserts uri format), `method` (enum exactly ["GET","POST","PUT","PATCH","DELETE"]), `authStrategy` (enum exactly ["same-origin-cookie","csrf-header-scrape","bearer-from-storage","none"] — locked at four members; persisted-query-hash and split-token are DEFERRED to Phase 29 bundled handlers, not added here), `params` (a nested JSON-Schema object validated against invoke args downstream), `request` (a static param→placement map object with `additionalProperties:false`; placements query/header/body), `extract` (a single string — the read-only JMESPath), and an OPTIONAL `csrf` object `{ from: enum["meta","cookie","response"], selector?: string, header: string }` with `additionalProperties:false`, required-when via JSON-Schema `if/then` only when `authStrategy === "csrf-header-scrape"`. Add a defense-in-depth forbidden-name guard at the top level (a `not`/`propertyNames` clause or — preferred for a precise field name in context — an interpreter-style pre-scan inside `validateRecipe`) covering the six names `script`,`expr`,`transform`,`code`,`fn`,`js` (Pitfall 2: additionalProperties:false alone yields a generic location, so the pre-scan reports WHICH forbidden field). Add `getFSBRecipeValidator(schema, draft)` (typeof-guarded `CfworkerJsonSchema` accessor, `new CfworkerJsonSchema.Validator(schema, draft||'2020-12', false)`), and `validateRecipe(recipe)` that RETURNS (never throws) the typed shape: pre-scan keys for forbidden names → `{success:false, code:'RECIPE_UNKNOWN_FIELD', field}`; then validate; on `valid:true` → `{success:true}`; map an additionalProperties failure → `RECIPE_UNKNOWN_FIELD` (report offending key from `instanceLocation`); map a method/authStrategy enum failure → `RECIPE_OPCODE_INVALID`; otherwise → `RECIPE_SCHEMA_INVALID`; carry `errors` in context. Set BOTH `code` and `errorCode` on every error object (so errors.ts resolveErrorKey picks it up either way). No `eval`/`new Function`/`import()` anywhere (this file is on the Plan 03 CI-guard allowlist). - - - node --check extension/utils/capability-recipe-schema.js && node -e "const fs=require('fs');const s=fs.readFileSync('extension/utils/capability-recipe-schema.js','utf8');if(/\beval\s*\(|\bnew\s+Function\b|\bimport\s*\(/.test(s)){console.error('recipe-schema module contains forbidden construct');process.exit(1)}if(!/FSB_RECIPE_SCHEMA_VERSION/.test(s)){console.error('missing version const');process.exit(1)}console.log('schema module clean')" - - - - `extension/utils/capability-recipe-schema.js` passes `node --check` and exports `RECIPE_SCHEMA`, `FSB_RECIPE_SCHEMA_VERSION`, and `validateRecipe` via both `globalThis.FsbCapabilityRecipeSchema` and `module.exports`. - - The file contains ZERO `eval(`, `new Function`, or `import(` substrings (it is on the Plan 03 allowlist). - - `RECIPE_SCHEMA.additionalProperties === false` at the top level; `authStrategy` enum is exactly the four D-08 members; `method` enum is exactly the five verbs; `schemaVersion` is a `const`. - - `format:'uri'` appears only on `origin`; `endpoint` uses a `pattern` (not `format:'uri'`). - - `validateRecipe` RETURNS typed objects (verified by Task 3's suite); it does not `throw` on invalid input. - - The closed recipe schema exists, is eval-free, rejects forbidden/unknown/bad-enum fields with precise typed RECIPE_* codes, and exposes RECIPE_SCHEMA + version + validateRecipe. - - - - Task 3: Create fixtures + the schema accept/reject test suite (CAP-01) - catalog/recipes/_fixtures/*.json (valid-recipe + reject-field-{script,expr,transform,code,fn,js} + reject-unknown-field + reject-bad-method + reject-bad-authstrategy), tests/capability-recipe-schema.test.js, package.json - - - tests/trigger-store.test.js:22-46 (freshRequireStore + module-load pattern), :28-39 (passed/failed counters + runTest runner), and the tail (process.exit(failed>0?1:0)) - - tests/ownership-error-codes.test.js:31-39 (the synchronous check(cond,msg) variant for typed-{code} assertions) - - tests/fixtures/run-task-harness.js (installChromeMock — NOT needed here; schema validation touches no chrome.*) - - .planning/phases/26-recipe-schema-bundled-interpreter-mv3-ci-guard/26-PATTERNS.md:288-336 (the cfworker IIFE test-load via vm.runInThisContext then read globalThis.CfworkerJsonSchema), :362-364 (fixture pattern: one accept + one reject per forbidden name + unknown-field + bad-enum) - - extension/utils/capability-recipe-schema.js (from Task 2 — the module under test) - - - Create the shared fixture set under `catalog/recipes/_fixtures/` (repo-root catalog/, chosen over extension/catalog/ because validate-extension.mjs only walks dirs under extension/ — verified EXT_ROOT=join(ROOT,'extension') — so repo-root fixtures are not node --check'd, and these are TEST FIXTURES, not shipped runtime recipes; declarative shipped recipes are Phase 29): `valid-recipe.json` (a complete valid recipe: schemaVersion 1, id, origin "https://example.com", endpoint "/api/{id}", method "GET", authStrategy "same-origin-cookie", params object, request map, extract JMESPath string); plus one reject fixture per forbidden name — `reject-field-script.json`, `reject-field-expr.json`, `reject-field-transform.json`, `reject-field-code.json`, `reject-field-fn.json`, `reject-field-js.json` (each = the valid recipe plus that one forbidden field); `reject-unknown-field.json` (valid recipe + a `foo` field); `reject-bad-method.json` (method "CONNECT"); `reject-bad-authstrategy.json` (authStrategy "persisted-query-hash"). Create `tests/capability-recipe-schema.test.js` cloning the trigger-store.test.js zero-framework structure (passed/failed counters, `check(cond,msg)`, `process.exit(failed>0?1:0)`). Test-load the cfworker IIFE FIRST via `vm.runInThisContext(fs.readFileSync(path.join(__dirname,'..','extension','lib','cfworker-json-schema.min.js'),'utf8'))` then assert `globalThis.CfworkerJsonSchema` is present (this populates the global the schema module reads). Then require `extension/utils/capability-recipe-schema.js` and assert, reading each fixture JSON: valid-recipe → `validateRecipe` returns `{success:true}`; each forbidden-name fixture → `{success:false, code:'RECIPE_UNKNOWN_FIELD'}` with the offending field name in context; unknown-field → `RECIPE_UNKNOWN_FIELD`; bad-method → `RECIPE_OPCODE_INVALID`; bad-authstrategy → `RECIPE_OPCODE_INVALID`; a recipe with wrong/missing schemaVersion → `RECIPE_SCHEMA_INVALID`. Append `&& node tests/capability-recipe-schema.test.js` to the END of the `package.json` `scripts.test` `&&`-chain (current tail is `... && node tests/trigger-blocking-reporting.test.js`). - - - node tests/capability-recipe-schema.test.js - - - - `node tests/capability-recipe-schema.test.js` exits 0 with non-zero passed and zero failed. - - The suite asserts: valid recipe accepted; each of script/expr/transform/code/fn/js rejected as RECIPE_UNKNOWN_FIELD; unknown field rejected; bad method and bad authStrategy each rejected as RECIPE_OPCODE_INVALID; bad schemaVersion rejected as RECIPE_SCHEMA_INVALID. - - `catalog/recipes/_fixtures/` contains the 10 fixture files listed in files_modified, each valid JSON. - - The suite test-loads the cfworker IIFE via vm.runInThisContext (not bare require) and confirms globalThis.CfworkerJsonSchema before validating. - - `package.json` `scripts.test` ends with `&& node tests/capability-recipe-schema.test.js`. - - CAP-01 is proven by an automated suite: the closed schema accepts valid recipes and rejects all six forbidden names, unknown fields, and bad enums with the correct typed codes; the suite is wired into npm test. - - - - - -## Trust Boundaries - -| Boundary | Description | -|----------|-------------| -| server/catalog → SW interpreter | A recipe is untrusted DATA crossing into the service worker; it must be validated before any use. This plan builds the validator + schema that guard this boundary. | -| vendored lib → SW runtime | Third-party library code is bundled (not remotely fetched); legitimacy was audited (slopcheck OK, zero deps) and the cfworker bundle is asserted eval-free. | - -## STRIDE Threat Register - -| Threat ID | Category | Component | Disposition | Mitigation Plan | -|-----------|----------|-----------|-------------|-----------------| -| T-26-01 | Tampering / Elevation | `capability-recipe-schema.js` RECIPE_SCHEMA + fixtures | mitigate | Closed top-level vocabulary with `additionalProperties:false` at every level + a defense-in-depth forbidden-name pre-scan (script/expr/transform/code/fn/js) → typed RECIPE_UNKNOWN_FIELD; per-forbidden-name reject fixtures prove each is rejected (Task 2/3). Locks CAP-01 / CAP-03. | -| T-26-02 | Elevation | `extension/lib/cfworker-json-schema.min.js` (the validator on the recipe path) | mitigate | The validator is eval-free `@cfworker/json-schema` (README + verified 0 `eval`/`new Function`/`import(` in the 45.3 kB IIFE — Task 1 verify asserts this); it is IIFE-bundled so it loads as a classic script under importScripts (no module/eval pathway). The schema module itself is eval-free (Task 2 verify). | -| T-26-01b | Tampering | param injection into the URL (downstream) | accept (this plan) | The `{var}` templater that encodeURIComponent-escapes params is built in Plan 02; this plan only defines the schema fields. No injection surface exists yet (no templating, no fetch). | -| T-26-SC | Tampering | npm package vendoring (minisearch/jmespath/@cfworker) | mitigate | Package Legitimacy Audit in 26-RESEARCH.md:139-152 — all three slopcheck [OK], zero dependencies, version-pinned, vendored (no runtime code fetch). No [ASSUMED]/[SUS] packages → no blocking human checkpoint required. CAP-05's "no remotely-hosted code" is satisfied by bundling. | - - - -- `node --check` passes for all three `extension/lib/*.min.js` and `extension/utils/capability-recipe-schema.js`. -- `node tests/capability-recipe-schema.test.js` green. -- `git diff extension/manifest.json` empty (CAP-05). -- The cfworker bundle and the schema module both contain zero `eval`/`new Function`/`import(` (pre-stages the Plan 03 allowlist). - - - -- CAP-05: the three libraries exist in `extension/lib/`, pass `node --check`, expose their globals, are loaded via additive importScripts, with no manifest/permission change. -- CAP-01: a versioned closed-vocabulary recipe schema exists and rejects every forbidden field (script/expr/transform/code/fn/js), unknown fields, and bad method/authStrategy enums with a typed RECIPE_* error — proven by `tests/capability-recipe-schema.test.js`. -- The shared `catalog/recipes/_fixtures/` set exists for reuse by Plan 03's CI guard. - - - -Create `.planning/phases/26-recipe-schema-bundled-interpreter-mv3-ci-guard/26-01-SUMMARY.md` when done. - diff --git a/.planning/phases/26-recipe-schema-bundled-interpreter-mv3-ci-guard/26-01-SUMMARY.md b/.planning/phases/26-recipe-schema-bundled-interpreter-mv3-ci-guard/26-01-SUMMARY.md deleted file mode 100644 index 527f3855b..000000000 --- a/.planning/phases/26-recipe-schema-bundled-interpreter-mv3-ci-guard/26-01-SUMMARY.md +++ /dev/null @@ -1,168 +0,0 @@ ---- -phase: 26-recipe-schema-bundled-interpreter-mv3-ci-guard -plan: 01 -subsystem: api -tags: [json-schema, cfworker, jmespath, minisearch, mv3, service-worker, recipe-as-data, esbuild, iife] - -# Dependency graph -requires: - - phase: (none) - provides: greenfield-additive; first plan of the v0.9.99 Native Capability Catalog milestone -provides: - - "extension/lib/cfworker-json-schema.min.js — eval-free JSON Schema validator IIFE (global CfworkerJsonSchema)" - - "extension/lib/minisearch.min.js — vendored UMD (global MiniSearch), shipped per CAP-05, wired Phase 28" - - "extension/lib/jmespath.min.js — vendored UMD (lowercase global jmespath) for read-only extract" - - "extension/utils/capability-recipe-schema.js — RECIPE_SCHEMA + FSB_RECIPE_SCHEMA_VERSION + validateRecipe (typed RECIPE_* returns)" - - "catalog/recipes/_fixtures/*.json — shared accept/reject fixture set (single source of truth for the Plan 03 CI guard)" - - "additive importScripts boot chain for the three libs + the recipe-schema module" -affects: [phase-27-authenticated-fetch, phase-28-mcp-search, phase-29-catalog-router, plan-02-interpreter, plan-03-ci-guard, errors.ts-RECIPE-family] - -# Tech tracking -tech-stack: - added: ["@cfworker/json-schema@4.1.1 (IIFE-bundled, vendored)", "jmespath@0.16.0 (vendored UMD)", "minisearch@7.2.0 (vendored UMD)"] - patterns: - - "Build-time esbuild one-off IIFE bundle emitting to extension/lib/ (NOT the esbuild.config.js ENTRIES array, which emits to extension/dist/)" - - "Dual-export IIFE SW module (global + module.exports) cloned from trigger-store.js/value-extractor.js" - - "typeof-guarded vendored-global accessor (getFSBRecipeValidator) cloned from ws-client.js getFSBLZStringCodec" - - "Closed-vocabulary JSON Schema (additionalProperties:false at every structural level) + defense-in-depth forbidden-name pre-scan" - - "Typed RECIPE_* RETURN shape (code + errorCode both set) cloned from createMcpOwnershipError" - -key-files: - created: - - extension/lib/cfworker-json-schema.min.js - - extension/lib/minisearch.min.js - - extension/lib/jmespath.min.js - - extension/utils/capability-recipe-schema.js - - catalog/recipes/_fixtures/valid-recipe.json - - catalog/recipes/_fixtures/reject-field-script.json - - catalog/recipes/_fixtures/reject-field-expr.json - - catalog/recipes/_fixtures/reject-field-transform.json - - catalog/recipes/_fixtures/reject-field-code.json - - catalog/recipes/_fixtures/reject-field-fn.json - - catalog/recipes/_fixtures/reject-field-js.json - - catalog/recipes/_fixtures/reject-unknown-field.json - - catalog/recipes/_fixtures/reject-bad-method.json - - catalog/recipes/_fixtures/reject-bad-authstrategy.json - - tests/capability-recipe-schema.test.js - modified: - - extension/background.js - - package.json - -key-decisions: - - "cfworker IIFE-bundled (not vendored raw) because a top-level import/export is a SyntaxError under importScripts in a classic service worker — the durable runtime reason, independent of the Node-version-fragile node --check rationale" - - "Error-mapping order classifies schemaVersion const and method/authStrategy enum failures BEFORE the generic additionalProperties check, because cfworker emits a root additionalProperties error alongside enum/const failures (verified live)" - - "Forbidden script-like names rejected by a top-level pre-scan that names the offending field (additionalProperties:false alone yields a generic location — Pitfall 2)" - - "authStrategy enum locked at four members (D-08); persisted-query-hash / split-token deferred to the Phase 29 bundled-handler head" - - "format:'uri' on origin ONLY; endpoint uses a leading-slash pattern (Pitfall 4 — cfworker asserts uri format in 2020-12)" - - "Fixtures live at repo-root catalog/recipes/_fixtures/ (validate-extension only walks dirs under extension/, so they are not node --check'd; they are test data, not shipped runtime recipes)" - -patterns-established: - - "esbuild one-off to extension/lib/ documented as package.json scripts.build:cfworker; the built file is committed like every other lib/*.min.js" - - "Recipe-path source files are kept free of dynamic-code substrings even in comments, pre-satisfying the Plan 03 CI-guard allowlist scan" - -requirements-completed: [CAP-01, CAP-05] - -# Metrics -duration: 8min -completed: 2026-06-20 ---- - -# Phase 26 Plan 01: Recipe Schema + Vendored Libraries Summary - -**Closed-vocabulary recipe JSON Schema (@cfworker/json-schema, eval-free) that rejects all six forbidden script-like fields, unknown fields, and bad method/authStrategy enums with typed RECIPE_* codes, plus the three capability libraries vendored into the extension package with no manifest/permission change.** - -## Performance - -- **Duration:** 8 min -- **Started:** 2026-06-20T04:31:36Z -- **Completed:** 2026-06-20T04:40:13Z -- **Tasks:** 3 -- **Files modified:** 17 (15 created, 2 modified) - -## Accomplishments -- Vendored the three capability libraries into `extension/lib/`: `@cfworker/json-schema` IIFE-bundled (global `CfworkerJsonSchema`, eval-free), `minisearch` and `jmespath` UMD as-is — all pass `node --check` as classic scripts and expose their globals (CAP-05). -- Authored the versioned, closed-vocabulary `RECIPE_SCHEMA` and a `validateRecipe` that RETURNS (never throws) precise typed `RECIPE_UNKNOWN_FIELD` / `RECIPE_OPCODE_INVALID` / `RECIPE_SCHEMA_INVALID` codes (CAP-01). -- Wired the four additive `importScripts` lines into the service-worker boot chain (libs-before-module order) with no manifest/permission change (D-05). -- Delivered the shared `catalog/recipes/_fixtures/` accept/reject set and a 25-assertion zero-framework test suite proving CAP-01, wired into `npm test`. - -## Task Commits - -Each task was committed atomically: - -1. **Task 1: Vendor the three libraries + wire importScripts** - `9c6ad4fc` (feat) -2. **Task 2: Author the closed-vocabulary recipe JSON Schema module** - `00ae33b8` (feat) -3. **Task 3: Create fixtures + the schema accept/reject test suite (CAP-01)** - `03aef84f` (test) - -**Plan metadata:** committed separately (docs: complete plan) - -_Note: Task 2 carried `tdd="true"`; per the plan's task structure the proving suite lives in Task 3 (test commit), so Task 2 was authored against the documented `` contract and verified by an inline behavioral smoke (28/28) before Task 3's fixture-driven suite (25/25) locked it in._ - -## Files Created/Modified -- `extension/lib/cfworker-json-schema.min.js` - Eval-free JSON Schema validator, IIFE-bundled from `node_modules/@cfworker/json-schema/dist/esm/index.js` via the repo-pinned esbuild (global `CfworkerJsonSchema`, exposes `.Validator`). -- `extension/lib/minisearch.min.js` - Vendored UMD (global `MiniSearch`); ships per CAP-05, not wired until Phase 28. -- `extension/lib/jmespath.min.js` - Vendored UMD (lowercase global `jmespath`) for the read-only `extract` field; the live read runs in Phase 27. -- `extension/utils/capability-recipe-schema.js` - Dual-export IIFE; `RECIPE_SCHEMA`, `FSB_RECIPE_SCHEMA_VERSION = 1`, `getFSBRecipeValidator`, `validateRecipe` (typed RECIPE_* returns, forbidden-name pre-scan). -- `catalog/recipes/_fixtures/valid-recipe.json` - Canonical accept fixture (`id`, schemaVersion 1, origin, endpoint, GET, same-origin-cookie, params, request, extract). -- `catalog/recipes/_fixtures/reject-field-{script,expr,transform,code,fn,js}.json` - One reject fixture per forbidden script-like name. -- `catalog/recipes/_fixtures/reject-unknown-field.json` - Valid recipe + a `foo` out-of-vocabulary field. -- `catalog/recipes/_fixtures/reject-bad-method.json` - method `CONNECT` (out of the five-verb enum). -- `catalog/recipes/_fixtures/reject-bad-authstrategy.json` - authStrategy `persisted-query-hash` (out of the four-member enum). -- `tests/capability-recipe-schema.test.js` - Zero-framework accept/reject suite; vm-loads the cfworker IIFE before requiring the module; 25 assertions. -- `extension/background.js` - Additive `importScripts` block: `jmespath` -> `minisearch` -> `cfworker-json-schema` -> `capability-recipe-schema` (each try/catch wrapped). -- `package.json` - Added `scripts.build:cfworker`; appended the suite to the end of `scripts.test`; recorded the three libs in `dependencies` (already installed in node_modules). - -## Decisions Made -- **cfworker MUST be IIFE-bundled, not vendored raw** — a top-level `import`/`export` is a SyntaxError under `importScripts` in a classic service worker. This is the durable runtime reason (the "node --check fails on Node 20" rationale in D-02 is version-fragile and passes on the repo's Node 25). Cited in the background.js comment and the commit. -- **Error-mapping ordering** — `schemaVersion` const and `method`/`authStrategy` enum failures are classified BEFORE the generic `additionalProperties` check (see Deviations Rule 1). -- **`params` is the one intentionally-open object** — it holds a user-authored JSON-Schema sub-document validated against invoke args downstream, so its internal shape is not locked; every other structural object (`request`, `csrf`, top level) is `additionalProperties:false`. -- **`csrf` required-when-`csrf-header-scrape`** expressed via JSON-Schema `if/then` (verified live against cfworker). - -## Deviations from Plan - -### Auto-fixed Issues - -**1. [Rule 1 - Bug] Corrected the validateRecipe error-mapping order vs the RESEARCH example** -- **Found during:** Task 2 (Author the closed-vocabulary recipe JSON Schema module) -- **Issue:** The RESEARCH.md code example (lines 336-348) checks for an `additionalProperties` failure FIRST, then enum failures. A live probe of `@cfworker/json-schema@4.1.1` showed that when a KNOWN field fails its `enum`/`const` (e.g. `method:'CONNECT'`, `authStrategy:'persisted-query-hash'`, `schemaVersion:2`), cfworker emits a root `#/additionalProperties` error ALONGSIDE the enum/const error. Following the RESEARCH order would mis-classify a bad method/authStrategy/schemaVersion as `RECIPE_UNKNOWN_FIELD` instead of the `RECIPE_OPCODE_INVALID` / `RECIPE_SCHEMA_INVALID` the plan's `` requires. -- **Fix:** Ordered the mapping as: forbidden-name pre-scan -> schemaVersion const (`RECIPE_SCHEMA_INVALID`) -> method/authStrategy enum (`RECIPE_OPCODE_INVALID`) -> additionalProperties (`RECIPE_UNKNOWN_FIELD`) -> fallback (`RECIPE_SCHEMA_INVALID`). -- **Files modified:** extension/utils/capability-recipe-schema.js -- **Verification:** 28/28 inline behavioral assertions and 25/25 fixture-suite assertions green; `reject-bad-method.json` and `reject-bad-authstrategy.json` correctly return `RECIPE_OPCODE_INVALID`. -- **Committed in:** `00ae33b8` (Task 2 commit) - -**2. [Rule 2 - Missing Critical] Removed literal dynamic-code substrings from the schema module's comments** -- **Found during:** Task 2 (Author the closed-vocabulary recipe JSON Schema module) -- **Issue:** The schema module's docblock originally described the eval-free constraint using the literal phrase "ZERO eval / new Function / import()". The Plan 03 CI guard (D-16/D-17) scans this exact file's text — including comments and strings (RESEARCH Pitfall 3) — for `eval(`, `new Function`, `import(`, and would fail the build on those comment substrings. The plan's own Task 2 verify command also flagged it. -- **Fix:** Reworded the comment to describe the constraint without the literal trigger patterns ("dynamic-code-free … no run-string-as-code, no function-from-string, no dynamic module loader"). Confirmed the file (and all four recipe-path files) contain zero matches of the guard regex even in comments. -- **Files modified:** extension/utils/capability-recipe-schema.js -- **Verification:** `grep -E '\beval\s*\(|\bnew\s+Function\b|\bimport\s*\('` returns empty for the module and all three vendored libs; the plan's Task 2 verify command now prints "schema module clean". -- **Committed in:** `00ae33b8` (Task 2 commit) - ---- - -**Total deviations:** 2 auto-fixed (1 bug, 1 missing critical) -**Impact on plan:** Both auto-fixes were necessary for correctness — the mapping-order fix makes `validateRecipe` return the typed codes the plan's `` and acceptance criteria demand; the comment fix pre-satisfies the Plan 03 CI-guard allowlist that this plan explicitly stages for. No scope creep; no architectural change. - -## Issues Encountered -- The conductor workspace is a git worktree (`.git` is a file) on branch `automation-worktree`, despite the prompt's note that worktrees are disabled. Confirmed `automation-worktree` is not a protected ref (not main/master/develop/trunk/release/*) and is the branch the agent was spawned on, so normal atomic commits are correct and safe. -- The working tree carried pre-existing uncommitted changes unrelated to this plan (showcase files, ws-client.js, dist, STATE.md, package-lock.json, several tests). All commits staged ONLY this plan's task-specific files; the unrelated changes were left untouched. -- `package.json` already carried the three libs in `dependencies` as an uncommitted working-tree change (pre-staged for this phase). They were committed in Task 1 alongside `build:cfworker` as part of Task 1's "vendor the three libraries" scope. - -## Known Stubs -None. `minisearch` is vendored but deliberately not yet wired into any runtime path — this is an explicit CAP-05 requirement ("the three libs ship"), with wiring scheduled for Phase 28 (search). It is a planned vendor-now/wire-later artifact, not a data stub: no UI renders empty data and no consumer receives placeholder values. - -## User Setup Required -None - no external service configuration required. The three libraries were already installed in `node_modules`; no `npm install` was run. - -## Next Phase Readiness -- The validator global (`CfworkerJsonSchema`) and the typed-return schema module are in place — Plan 02 (the bundled interpreter: validate -> bind -> emit boundRequestSpec) can build on `RECIPE_SCHEMA` + `validateRecipe` directly. -- The `catalog/recipes/_fixtures/` set is the single source of truth ready for Plan 03's CI guard (allowlist grep + accept/reject fixture run); all four recipe-path files are already eval-free even in comments. -- `mcp/src/errors.ts` does NOT yet carry the `RECIPE_*` family — adding `RECIPE_.+` to the verbatim-passthrough regex is a downstream task (RESEARCH names errors.ts:122 as the one-line copy-target; not in this plan's scope). -- No blockers. - -## Self-Check: PASSED - -All 15 created files verified present on disk (3 vendored libs, the schema module, 10 fixtures, the test suite) and all 3 task commits (`9c6ad4fc`, `00ae33b8`, `03aef84f`) verified in git history. - ---- -*Phase: 26-recipe-schema-bundled-interpreter-mv3-ci-guard* -*Completed: 2026-06-20* diff --git a/.planning/phases/26-recipe-schema-bundled-interpreter-mv3-ci-guard/26-02-PLAN.md b/.planning/phases/26-recipe-schema-bundled-interpreter-mv3-ci-guard/26-02-PLAN.md deleted file mode 100644 index 2720d17b3..000000000 --- a/.planning/phases/26-recipe-schema-bundled-interpreter-mv3-ci-guard/26-02-PLAN.md +++ /dev/null @@ -1,247 +0,0 @@ ---- -phase: 26-recipe-schema-bundled-interpreter-mv3-ci-guard -plan: 02 -type: execute -wave: 2 -depends_on: ["26-01"] -files_modified: - - extension/utils/capability-auth-strategies.js - - extension/utils/capability-interpreter.js - - extension/background.js - - mcp/src/errors.ts - - tests/capability-interpreter.test.js - - package.json -autonomous: true -requirements: [CAP-02, CAP-03] -user_setup: [] - -must_haves: - truths: - - "A valid recipe + valid invoke args produce a bound request spec {url, method, headers, body, authStrategy, csrfSource?, origin, extract}" - - "Each of the four auth strategies (none, same-origin-cookie, bearer-from-storage, csrf-header-scrape) shapes the spec correctly" - - "Invoke args that violate the recipe's params sub-schema are rejected with RECIPE_SCHEMA_INVALID" - - "An unknown authStrategy is rejected with RECIPE_OPCODE_INVALID (defense-in-depth beyond the schema enum)" - - "The interpreter performs NO network call — chrome.scripting.executeScript and fetch are never invoked" - - "A SW result {success:false, code:'RECIPE_SCHEMA_INVALID'} surfaces the code verbatim through mcp/src/errors.ts (not collapsed to action_rejected)" - - "Decision coverage (26-CONTEXT.md): implements D-04, D-11, D-12, D-13, D-14, D-15, D-19" - artifacts: - - path: "extension/utils/capability-auth-strategies.js" - provides: "Frozen AUTH_HANDLERS registry of four spec-shaping stubs keyed by the authStrategy enum" - exports: ["AUTH_HANDLERS", "bindAuthStrategy"] - - path: "extension/utils/capability-interpreter.js" - provides: "validate-bind-emit-spec interpreter that STOPS before the network; returns typed RECIPE_* errors" - exports: ["interpretRecipe"] - - path: "tests/capability-interpreter.test.js" - provides: "Binding + no-network + invoke-param-validation + errors.ts passthrough suite (CAP-02/CAP-03)" - min_lines: 40 - - path: "mcp/src/errors.ts" - provides: "RECIPE_.+ added to the verbatim-passthrough regex" - contains: "RECIPE_" - key_links: - - from: "extension/utils/capability-interpreter.js" - to: "extension/utils/capability-recipe-schema.js" - via: "validateRecipe + RECIPE_SCHEMA reuse" - pattern: "FsbCapabilityRecipeSchema|validateRecipe" - - from: "extension/utils/capability-interpreter.js" - to: "extension/utils/capability-auth-strategies.js" - via: "bindAuthStrategy / AUTH_HANDLERS dispatch" - pattern: "FsbCapabilityAuthStrategies|bindAuthStrategy" - - from: "mcp/src/errors.ts" - to: "RECIPE_ error family" - via: "verbatim-passthrough regex in resolveErrorKey" - pattern: "RECIPE_" ---- - -## Decision Coverage - -Traceability to `26-CONTEXT.md` locked decisions implemented by this plan's tasks: - -- **D-04:** hand-rolled `{var}` endpoint templater; `url-template` not used (Task 2) -- **D-11:** interpreter validates + binds → bound request spec and performs NO network call (Tasks 2-3) -- **D-12:** auth-strategy handlers are header/spec-shaping stubs only (Task 1) -- **D-13:** eval-free validation via `@cfworker/json-schema` only — no `eval`/`new Function`/`import()` (Task 2) -- **D-14:** `extract` (JMESPath) defined/validated; helper unit-tested against a static fixture (Tasks 2-3) -- **D-15:** typed `RECIPE_*` errors returned (not thrown) and surfaced via `mcp/src/errors.ts` (Tasks 2-3) -- **D-19:** interpreter binding test suite — suite (b) (Task 3) - - -Build the fixed, eval-free interpreter that turns a validated recipe + invoke args into a bound, ready-to-execute request spec — and STOPS there, performing no network call. This plan delivers CAP-02 (the interpreter binds recipe data to a closed enum of bundled auth-strategy handlers, never via eval/new Function/import) and CAP-03 (recipe + invoke params validated in the SW by the eval-free validator, invalid/unknown-opcode rejected with a typed error). It also adds the RECIPE_* family to the MCP error passthrough so the typed codes surface verbatim. - -Purpose: This is the Wall-1/Wall-2 seam. The interpreter is the config-driven binder, not a command runner — and Phase 26's hard boundary is that it emits the spec and stops; the authenticated MAIN-world fetch, live CSRF scrape, and origin-pin enforcement are Phase 27. The no-network assertion is the load-bearing proof that this boundary holds. - -Output: extension/utils/capability-auth-strategies.js, extension/utils/capability-interpreter.js, additive importScripts lines, the RECIPE_ regex extension in mcp/src/errors.ts, tests/capability-interpreter.test.js, and one scripts.test entry. - - - -@$HOME/.claude/get-shit-done/workflows/execute-plan.md -@$HOME/.claude/get-shit-done/templates/summary.md - - - -@.planning/STATE.md -@.planning/ROADMAP.md -@.planning/phases/26-recipe-schema-bundled-interpreter-mv3-ci-guard/26-CONTEXT.md -@.planning/phases/26-recipe-schema-bundled-interpreter-mv3-ci-guard/26-RESEARCH.md -@.planning/phases/26-recipe-schema-bundled-interpreter-mv3-ci-guard/26-PATTERNS.md -@.planning/phases/26-recipe-schema-bundled-interpreter-mv3-ci-guard/26-VALIDATION.md -@.planning/phases/26-recipe-schema-bundled-interpreter-mv3-ci-guard/26-01-SUMMARY.md - - - - -From extension/utils/capability-recipe-schema.js (Plan 01): - globalThis.FsbCapabilityRecipeSchema = { RECIPE_SCHEMA, FSB_RECIPE_SCHEMA_VERSION, validateRecipe } - validateRecipe(recipe) -> { success:true } | { success:false, code:'RECIPE_UNKNOWN_FIELD'|'RECIPE_OPCODE_INVALID'|'RECIPE_SCHEMA_INVALID', errorCode, field?, errors? } - -CfworkerJsonSchema (vendored Plan 01, global): new CfworkerJsonSchema.Validator(schema, '2020-12', false).validate(x) -> { valid, errors[] } - (Used here to validate invoke args against recipe.params.) -jmespath (vendored Plan 01, LOWERCASE global): defined this phase; the live extract RUN is Phase 27. Do NOT execute extraction against a live response here. - -Typed-error RETURN shape to clone (extension/ws/mcp-tool-dispatcher.js:190-198, createMcpOwnershipError): - return { success: false, code, errorCode: code, error: code, ...context }; // BOTH code and errorCode set - -Dual-export IIFE shell (extension/utils/value-extractor.js tail — pure, no chrome.* resolver): - (function(global){ 'use strict'; /* body */ var exportsObj={...}; global.FsbCapabilityInterpreter=exportsObj; if(typeof module!=='undefined'&&module.exports){module.exports=exportsObj;} })(typeof globalThis!=='undefined'?globalThis:this); - -Auth-strategy registry shape (frozen; spec-shaping STUBS only — declare needs, no I/O): - AUTH_HANDLERS = Object.freeze({ - 'none': { shape(spec){ return spec; } }, - 'same-origin-cookie': { shape(spec){ return Object.assign({}, spec, { credentials:'include' }); } }, // Phase 27 consumes credentials - 'bearer-from-storage': { shape(spec){ return Object.assign({}, spec, { _authNeed:{ kind:'bearer', source:'storage' } }); } }, - 'csrf-header-scrape': { shape(spec, recipe){ return Object.assign({}, spec, { csrfSource: recipe.csrf || { from:'meta', selector:'meta[name=csrf-token]', header:'X-CSRF-Token' } }); } }, - }); - -errors.ts copy-target (mcp/src/errors.ts:122, VERIFIED current line): - if (explicitCode && /^(TRIGGER_.+|INVALID_TRIGGER_ID|INVALID_TAB_ID|LIFECYCLE_UNAVAILABLE|REFRESH_POLL_INTERVAL_TOO_LOW)$/.test(explicitCode)) { return explicitCode; } - resolveErrorKey reads errorCode then code (errors.ts:104-107), so setting both carries the code. - -Hand-rolled {var} templater (D-04; FSB-authored; RESEARCH:353-363): replace(/\{([a-zA-Z0-9_]+)\}/g, ...) -> encodeURIComponent each param; reject unfilled/unknown placeholders. NO url-template, NO eval. - -Chrome mock recorder (tests/fixtures/run-task-harness.js installChromeMock provides runtime/storage/tabs but NOT scripting — extend inline in the test to add a chrome.scripting.executeScript recorder and assert it is never called; test-only, not on the recipe-path allowlist). - - - - - - - Task 1: Author the closed auth-strategy handler registry (spec-shaping stubs) - extension/utils/capability-auth-strategies.js - - - extension/utils/value-extractor.js (the pure dual-export IIFE shell to clone; global name FsbCapabilityAuthStrategies) - - extension/ws/mcp-tool-dispatcher.js:190-198 (createMcpOwnershipError — typed RETURN shape for the unknown-strategy rejection) - - .planning/phases/26-recipe-schema-bundled-interpreter-mv3-ci-guard/26-RESEARCH.md:253-267 (Pattern 3: the frozen enum-to-bundled-behavior registry; the four stubs) and :34-36 (D-12: stubs declare needs, perform NO I/O) - - .planning/phases/26-recipe-schema-bundled-interpreter-mv3-ci-guard/26-PATTERNS.md:207-223 (the registry shape + bindAuthStrategy) - - .planning/phases/26-recipe-schema-bundled-interpreter-mv3-ci-guard/26-CONTEXT.md D-08 (the four enum members) / D-12 (stubs only) - - - Create extension/utils/capability-auth-strategies.js as a dual-export IIFE (clone value-extractor.js shell; global FsbCapabilityAuthStrategies; also module.exports). Define a frozen registry via Object.freeze keyed EXACTLY by the four authStrategy enum members none, same-origin-cookie, bearer-from-storage, csrf-header-scrape, each value an object with a shape(spec, recipe) method that RETURNS a new spec (do not mutate the input; use Object.assign or spread). none returns spec unchanged. same-origin-cookie adds credentials:'include' (a DECLARATION Phase 27's fetch will consume). bearer-from-storage adds _authNeed:{ kind:'bearer', source:'storage' }. csrf-header-scrape adds csrfSource from recipe.csrf (the optional schema field) falling back to { from:'meta', selector:'meta[name=csrf-token]', header:'X-CSRF-Token' }. These are SPEC-SHAPING STUBS ONLY: they declare what Phase 27 will need; they perform NO fetch, NO chrome.* call, NO DOM read, NO I/O of any kind (D-12). Export bindAuthStrategy(strategy, spec, recipe) that looks up the handler; if absent RETURN { success:false, code:'RECIPE_OPCODE_INVALID', errorCode:'RECIPE_OPCODE_INVALID', error:'RECIPE_OPCODE_INVALID', field:'authStrategy', value:strategy } (defense-in-depth beyond the schema enum); otherwise return handler.shape(spec, recipe). No eval, new Function, or import() (this file is on the Plan 03 allowlist). - - - node --check extension/utils/capability-auth-strategies.js && node -e "const fs=require('fs');const s=fs.readFileSync('extension/utils/capability-auth-strategies.js','utf8');if(/\beval\s*\(|\bnew\s+Function\b|\bimport\s*\(/.test(s)){console.error('forbidden construct');process.exit(1)}const m=require('./extension/utils/capability-auth-strategies.js');const keys=Object.keys(m.AUTH_HANDLERS).sort().join(',');if(keys!=='bearer-from-storage,csrf-header-scrape,none,same-origin-cookie'){console.error('wrong keys: '+keys);process.exit(1)}if(!Object.isFrozen(m.AUTH_HANDLERS)){console.error('not frozen');process.exit(1)}if(m.bindAuthStrategy('nope',{},{}).code!=='RECIPE_OPCODE_INVALID'){console.error('unknown strategy not rejected');process.exit(1)}console.log('auth-strategies ok')" - - - - extension/utils/capability-auth-strategies.js passes node --check, exports AUTH_HANDLERS (frozen, exactly the four keys) and bindAuthStrategy via both global and module.exports. - - The file contains ZERO eval(, new Function, import(, fetch, or chrome. substrings (pure spec-shaping; on the Plan 03 allowlist). - - bindAuthStrategy('not-a-strategy', {}, {}) returns { success:false, code:'RECIPE_OPCODE_INVALID', ... }. - - Each handler's shape returns a NEW object (input spec not mutated). - - The frozen four-member auth-strategy registry exists as spec-shaping stubs with a typed unknown-strategy rejection and zero I/O. - - - - Task 2: Author the interpreter (validate-bind-emit-spec, stops before the network) + wire importScripts - extension/utils/capability-interpreter.js, extension/background.js - - - extension/utils/value-extractor.js (pure dual-export shell; the interpreter follows the same chrome-free posture — D-11, no fetch) - - extension/ws/ws-client.js:98-99 (getFSBLZStringCodec — the typeof-guarded global accessor pattern for getFSBRecipeValidator/getFSBJmespath) - - extension/utils/capability-recipe-schema.js (Plan 01 — validateRecipe + RECIPE_SCHEMA; reuse, do not re-implement) - - extension/utils/capability-auth-strategies.js (Task 1 — bindAuthStrategy/AUTH_HANDLERS) - - extension/background.js:47-50, :97 (the importScripts try/catch idiom + load-order comment style; Plan 01 already added the lib + schema lines — append the auth-strategies then interpreter lines AFTER them) - - .planning/phases/26-recipe-schema-bundled-interpreter-mv3-ci-guard/26-RESEARCH.md:154-189 (the validate-bind-emit pipeline diagram), :331-363 (validateRecipe mapping + the {var} templater), :34-36 (D-11 boundary: emit spec, no fetch) - - .planning/phases/26-recipe-schema-bundled-interpreter-mv3-ci-guard/26-CONTEXT.md D-11/D-13/D-14/D-15 (validate+bind, eval-free, extract defined-not-run, typed-error return) - - - - interpretRecipe(validRecipe, validArgs) -> { success:true, spec:{ url, method, headers, body, authStrategy, csrfSource?, origin, extract } } where url has {var} placeholders substituted from validArgs (encodeURIComponent'd) - - interpretRecipe with each of the four authStrategy values -> spec carries the correct shaping (credentials / _authNeed / csrfSource / unchanged) - - interpretRecipe(recipeFailingSchema, args) -> { success:false, code:'RECIPE_UNKNOWN_FIELD'|'RECIPE_OPCODE_INVALID'|'RECIPE_SCHEMA_INVALID' } (delegated to validateRecipe) - - interpretRecipe(validRecipe, argsViolatingParamsSchema) -> { success:false, code:'RECIPE_SCHEMA_INVALID' } - - interpretRecipe(validRecipe, argsMissingAPlaceholderVar) -> { success:false, code:'RECIPE_SCHEMA_INVALID' } (templater rejects unfilled {var}) - - interpretRecipe never calls fetch or chrome.scripting.executeScript (Phase 26 stops before the network — Task 3 asserts this) - - - Create extension/utils/capability-interpreter.js as a dual-export IIFE (clone value-extractor.js shell; global FsbCapabilityInterpreter; also module.exports). Add typeof-guarded accessors getFSBRecipeSchema() (reads FsbCapabilityRecipeSchema), getFSBAuthStrategies() (reads FsbCapabilityAuthStrategies), getFSBJmespath() (reads the LOWERCASE jmespath global) — each returns null when its global is absent. Implement a hand-rolled templateEndpoint(template, params) per D-04: a String.replace over the regex matching {name} tokens that, for each token, throws a typed object when the param is missing and otherwise returns encodeURIComponent(String(params[name])); catch that throw and convert it to a typed return (the public API never throws). Implement interpretRecipe(recipe, args) doing, in order: (1) validateRecipe(recipe) via the schema module — on failure return its typed result verbatim; (2) validate args against recipe.params using a fresh CfworkerJsonSchema.Validator(recipe.params, '2020-12', false) — on invalid return { success:false, code:'RECIPE_SCHEMA_INVALID', errorCode:'RECIPE_SCHEMA_INVALID', errors }; (3) url = templateEndpoint(recipe.endpoint, args) (typed-return on unfilled placeholder); (4) build { query, headers, body } from the STATIC recipe.request placement map applied to validated args (no arbitrary header/body construction from server strings); (5) assemble base spec { url, method: recipe.method, headers, body, origin: recipe.origin, extract: recipe.extract }; (6) bindAuthStrategy(recipe.authStrategy, spec, recipe) — if it returns a {success:false} shape, return that; otherwise the returned shaped spec becomes spec, then return { success:true, spec }. The interpreter MUST NOT call fetch, chrome.scripting.executeScript, or run jmespath against any live response — recipe.extract is carried in the spec for Phase 27 to execute (D-14: extract is defined + schema-validated here, RUN in Phase 27). Set BOTH code and errorCode on every error return. No eval, new Function, or import() (on the Plan 03 allowlist). Then add additive importScripts lines to extension/background.js AFTER the Plan 01 block (load order: schema and libs already loaded, so utils/capability-auth-strategies.js then utils/capability-interpreter.js), each wrapped try { importScripts('utils/...') } catch (e) { console.error('[FSB] Failed to load ...:', e.message); }. - - - node --check extension/utils/capability-interpreter.js && node -e "const fs=require('fs');const s=fs.readFileSync('extension/utils/capability-interpreter.js','utf8');if(/\beval\s*\(|\bnew\s+Function\b|\bimport\s*\(/.test(s)){console.error('forbidden construct');process.exit(1)}if(/\bfetch\s*\(/.test(s)){console.error('interpreter contains a fetch call');process.exit(1)}if(/chrome\.scripting/.test(s)){console.error('interpreter references chrome.scripting');process.exit(1)}console.log('interpreter clean')" && grep -c "importScripts('utils/capability-interpreter.js')" extension/background.js - - - - extension/utils/capability-interpreter.js passes node --check and exports interpretRecipe via both global and module.exports. - - The file contains ZERO eval(, new Function, import(, fetch(, or chrome.scripting substrings (the no-network boundary is enforced statically here and behaviorally in Task 3). - - extension/background.js has two new additive importScripts lines (utils/capability-auth-strategies.js then utils/capability-interpreter.js), each in try/catch form, loaded AFTER the Plan 01 lib + schema block. - - interpretRecipe RETURNS typed results (never throws), reusing validateRecipe from Plan 01 and bindAuthStrategy from Task 1 (no re-implementation). - - The interpreter validates recipe + invoke params, binds via the auth-strategy registry, templates the endpoint with escaped params, and emits a bound spec — stopping before any network call. - - - - Task 3: Interpreter test (binding + no-network proof) + errors.ts RECIPE_ passthrough + wire - tests/capability-interpreter.test.js, mcp/src/errors.ts, package.json - - - tests/trigger-store.test.js:22-46 (freshRequire + module-load), :28-39 (passed/failed counters), tail (process.exit) - - tests/ownership-error-codes.test.js:31-39 (synchronous check(cond,msg) for typed-{code} asserts) - - tests/mcp-recovery-messaging.test.js:28-31 and :99-142 (import mcp/build/errors.js then mapFSBError({success:false, code/errorCode}) asserting the code is surfaced verbatim — the pattern for the errors.ts passthrough assertion) - - tests/fixtures/run-task-harness.js:131-165 (installChromeMock — provides runtime/storage/tabs; extend inline to add a chrome.scripting.executeScript recorder) - - mcp/src/errors.ts:104-124 (resolveErrorKey + the TRIGGER_ regex at :122 — the one-line copy-target), :54-68 (CODE_ONLY_ERROR_KEYS — the alternative, not used) - - extension/utils/capability-interpreter.js (Task 2) and extension/utils/capability-recipe-schema.js (Plan 01) - - .planning/phases/26-recipe-schema-bundled-interpreter-mv3-ci-guard/26-PATTERNS.md:405-428 (the errors.ts MODIFY anchor + how the code is read), :326-336 (chrome mock recorder + cfworker test-load) - - .planning/phases/26-recipe-schema-bundled-interpreter-mv3-ci-guard/26-VALIDATION.md:45-49 (CAP-02/CAP-03 + errors.ts rows; the no-fetch/executeScript assertion is mandatory) - - - Edit mcp/src/errors.ts:122: add RECIPE_.+ to the verbatim-passthrough alternation so the regex becomes /^(TRIGGER_.+|RECIPE_.+|INVALID_TRIGGER_ID|INVALID_TAB_ID|LIFECYCLE_UNAVAILABLE|REFRESH_POLL_INTERVAL_TOO_LOW)$/ (one-line change; this is the ONLY errors.ts edit this phase — no new FSB_ERROR_MESSAGES or LAYER_LABELS entry, the RECIPE_ codes fall through to buildLayeredDetail's default arm which is acceptable for Phase 26; the dispatcher route carrying it is Phase 28). Create tests/capability-interpreter.test.js cloning the trigger-store.test.js zero-framework structure (passed/failed counters, check(cond,msg), process.exit(failed>0?1:0)). Test-load the cfworker IIFE via vm.runInThisContext(readFileSync('extension/lib/cfworker-json-schema.min.js')) so globalThis.CfworkerJsonSchema is present; require the schema module and the auth-strategies module so their globals are set; require extension/utils/capability-interpreter.js. Install a chrome mock (extend installChromeMock from run-task-harness.js) whose chrome.scripting = { executeScript: (...a) => { recorderCalls.push(a); } } and leave a globalThis.fetch recorder; assert across the whole suite that recorderCalls.length === 0 AND the fetch recorder was never called (CAP-02 no-network proof — this is the load-bearing Phase 26/27 boundary assertion). Then assert: (a) a valid recipe + valid args -> { success:true } with spec.url having the {var} substituted+escaped, spec.method/origin/extract carried, and the four authStrategy variants each shaping spec correctly (credentials:'include' / _authNeed / csrfSource / unchanged); (b) invalid invoke args (violating recipe.params) -> { success:false, code:'RECIPE_SCHEMA_INVALID' }; (c) a recipe whose authStrategy bypasses the schema (call bindAuthStrategy directly with an unknown strategy, or a hand-built spec path) -> RECIPE_OPCODE_INVALID; (d) args missing a {var} placeholder -> RECIPE_SCHEMA_INVALID. Add an errors.ts passthrough assertion (mirror mcp-recovery-messaging.test.js): import the built mcp errors module and assert mapFSBError({success:false, code:'RECIPE_SCHEMA_INVALID'}) surfaces RECIPE_SCHEMA_INVALID verbatim (NOT action_rejected) — note this requires npm --prefix mcp run build to have run, which the scripts.test chain already does before the capability suites. Append && node tests/capability-interpreter.test.js to the END of package.json scripts.test (after the Plan 01 capability-recipe-schema entry). - - - npm --prefix mcp run build && node tests/capability-interpreter.test.js && grep -q "RECIPE_" mcp/src/errors.ts - - - - node tests/capability-interpreter.test.js exits 0 with zero failed, AND its output confirms the chrome.scripting.executeScript recorder and the fetch recorder were each called 0 times (CAP-02 no-network proof). - - The suite asserts a valid recipe+args yields a bound spec with substituted+escaped url and correct shaping for all four auth strategies; invalid invoke args -> RECIPE_SCHEMA_INVALID; unknown authStrategy -> RECIPE_OPCODE_INVALID; missing {var} -> RECIPE_SCHEMA_INVALID. - - mcp/src/errors.ts:122 regex contains RECIPE_.+ and the built mcp module surfaces a RECIPE_SCHEMA_INVALID code verbatim (not collapsed to action_rejected), proven by an in-suite assertion. - - package.json scripts.test ends with the two capability suites in order (recipe-schema then interpreter). - - CAP-02 and CAP-03 are proven: the interpreter binds to a bound spec for all four strategies, validates invoke params, rejects unknown opcodes, performs no network call, and the RECIPE_ codes surface verbatim through errors.ts. - - - - - -## Trust Boundaries - -| Boundary | Description | -|----------|-------------| -| recipe data -> interpreter binding | The interpreter consumes validated recipe DATA and selects a bundled handler by enum id; the recipe never carries handler code. This boundary is where "code as data" drift would occur. | -| interpreter output -> Phase 27 fetch | The bound spec is the hand-off contract to the (future) MAIN-world fetch. Phase 26 must STOP at the spec; crossing into a network call here would be a Wall-2 (extension-origin) anti-pattern. | - -## STRIDE Threat Register - -| Threat ID | Category | Component | Disposition | Mitigation Plan | -|-----------|----------|-----------|-------------|-----------------| -| T-26-02 | Elevation | capability-interpreter.js (the validator + binder is coaxed into eval/new Function/import of recipe data, or into running a network call) | mitigate | The interpreter uses ONLY the eval-free CfworkerJsonSchema validator and a frozen enum-keyed handler registry (no dynamic dispatch from recipe strings to code); it emits a bound DATA spec, never a thunk. Task 2 verify asserts zero eval/new Function/import/fetch/chrome.scripting; Task 3 asserts at runtime that chrome.scripting.executeScript and fetch are NEVER called (the no-network proof). Locks CAP-02. | -| T-26-01 | Tampering | invoke args + recipe (unknown opcode / param injection) | mitigate | Invoke args validated against recipe.params via the eval-free validator before binding (CAP-03); bindAuthStrategy rejects any authStrategy not in the four-member frozen registry with RECIPE_OPCODE_INVALID (defense-in-depth beyond the schema enum); the {var} templater encodeURIComponent-escapes every substituted param and rejects unfilled placeholders (no template injection into the URL — ASVS V5.2). Task 1 + Task 3 cover. | -| T-26-03 | Tampering | future drift reintroducing dynamic code on this file | transfer (to Plan 03) | The interpreter and auth-strategies files are placed on the Plan 03 CI-guard allowlist; the standing guard (built in Plan 03) fails the build if eval/new Function/import is ever added here. This plan's static verifies are the per-task gate; the durable enforcement is Plan 03. | - - - -- node --check passes for both new utils modules. -- node tests/capability-interpreter.test.js green, with the no-network recorder assertions at 0 calls. -- npm --prefix mcp run build passes (type-checks the errors.ts change). -- The interpreter and auth-strategies files contain zero eval/new Function/import/fetch/chrome.scripting (pre-stages the Plan 03 allowlist). - - - -- CAP-02: the interpreter binds a validated recipe to the closed four-member auth-strategy handler registry and emits a bound spec without eval/new Function/import and without any network call (proven by the no-network recorder assertion). -- CAP-03: recipe and invoke params are validated in the SW by the eval-free validator before binding; invalid args -> RECIPE_SCHEMA_INVALID and unknown opcode -> RECIPE_OPCODE_INVALID; the RECIPE_ family surfaces verbatim through mcp/src/errors.ts. - - - -Create `.planning/phases/26-recipe-schema-bundled-interpreter-mv3-ci-guard/26-02-SUMMARY.md` when done. - diff --git a/.planning/phases/26-recipe-schema-bundled-interpreter-mv3-ci-guard/26-02-SUMMARY.md b/.planning/phases/26-recipe-schema-bundled-interpreter-mv3-ci-guard/26-02-SUMMARY.md deleted file mode 100644 index 59b7629e9..000000000 --- a/.planning/phases/26-recipe-schema-bundled-interpreter-mv3-ci-guard/26-02-SUMMARY.md +++ /dev/null @@ -1,130 +0,0 @@ ---- -phase: 26-recipe-schema-bundled-interpreter-mv3-ci-guard -plan: 02 -subsystem: api -tags: [recipe-as-data, interpreter, auth-strategy, cfworker, json-schema, jmespath, mv3, service-worker, iife, errors-passthrough] - -# Dependency graph -requires: - - phase: 26-01 - provides: "extension/utils/capability-recipe-schema.js (validateRecipe + RECIPE_SCHEMA), the three vendored libs (CfworkerJsonSchema/jmespath/MiniSearch), and the catalog/recipes/_fixtures/ accept fixture" -provides: - - "extension/utils/capability-auth-strategies.js -- frozen AUTH_HANDLERS registry (four spec-shaping stubs) + bindAuthStrategy (typed RECIPE_OPCODE_INVALID for unknown strategy)" - - "extension/utils/capability-interpreter.js -- interpretRecipe: validate-bind-emit-spec that STOPS before the network; emits { url, method, headers, body, query, authStrategy, csrfSource?/_authNeed?/credentials?, origin, extract }" - - "mcp/src/errors.ts -- RECIPE_.+ added to the verbatim-passthrough regex (codes surface, not collapsed to action_rejected)" - - "additive importScripts wiring in background.js (auth-strategies then interpreter, after the Plan 01 lib+schema block)" - - "tests/capability-interpreter.test.js -- 26-assertion binding + no-network-proof + invoke-param + errors.ts passthrough suite" -affects: [phase-27-authenticated-fetch, phase-28-mcp-search, phase-29-catalog-router, plan-03-ci-guard] - -# Tech tracking -tech-stack: - added: [] - patterns: - - "Frozen enum->bundled-stub auth-strategy registry (Object.freeze keyed by the authStrategy enum); the recipe selects a handler by id, never carries handler logic (Wall-1 code-vs-data line)" - - "Spec-shaping stub: shape(spec, recipe) RETURNS a new spec (Object.assign, no input mutation) declaring Phase-27 needs (credentials / _authNeed / csrfSource); performs NO I/O (D-12)" - - "Hand-rolled {var} endpoint templater (D-04): String.replace over /\\{([a-zA-Z0-9_]+)\\}/g, encodeURIComponent each param, internal typed-throw on unfilled placeholder caught and converted to a typed RETURN (public API never throws)" - - "Static param->placement map (request.query/header/body) filled from validated args; query values URL-encoded, header/body raw; no arbitrary header/body construction from server strings (ASVS V5.2)" - - "typeof-guarded sibling-module + vendored-global accessors (getFSBRecipeSchema/getFSBAuthStrategies/getFSBRecipeValidator/getFSBJmespath) so the module degrades to a typed RECIPE_SCHEMA_INVALID when a dependency is absent" - -key-files: - created: - - extension/utils/capability-auth-strategies.js - - extension/utils/capability-interpreter.js - - tests/capability-interpreter.test.js - modified: - - extension/background.js - - mcp/src/errors.ts - - package.json - -key-decisions: - - "Reused Plan 01's validateRecipe verbatim as the recipe-schema gate (no re-implementation); the interpreter delegates step 1 and returns the typed RECIPE_* result as-is, so a bad method/authStrategy enum surfaces RECIPE_OPCODE_INVALID and an unknown/forbidden field surfaces RECIPE_UNKNOWN_FIELD without duplicating the cfworker error-mapping logic" - - "Invoke args validated against recipe.params only when recipe.params is present (it is an optional, intentionally-open JSON-Schema sub-document); a fresh CfworkerJsonSchema.Validator(recipe.params, '2020-12', false) is constructed per call so invalid args return RECIPE_SCHEMA_INVALID before any binding" - - "bindAuthStrategy is exercised by the interpreter for EVERY recipe (the enum->bundled-stub dispatch is the binding step); its unknown-strategy rejection is defense-in-depth beyond the schema enum and is asserted directly in the suite because a recipe carrying an unknown strategy is already rejected upstream by validateRecipe" - - "The bound spec carries recipe.extract UNEVALUATED (string or null); jmespath is reached only through getFSBJmespath() and never run against a live response in Phase 26 (D-14) -- the extract RUN is Phase 27" - - "errors.ts edit is the single one-line regex extension (RECIPE_.+ joined to the TRIGGER_.+ alternation at the verbatim-passthrough); no new FSB_ERROR_MESSAGES/LAYER_LABELS entry -- RECIPE_* codes fall through buildLayeredDetail's default arm ('Tool returned error code: RECIPE_*'), acceptable for Phase 26 (the dispatcher route is Phase 28). INV-01 honored: no MCP tool schema or TOOL_REGISTRY touched" - -patterns-established: - - "No-network boundary proof: the interpreter test installs a chrome.scripting.executeScript recorder AND a globalThis.fetch recorder and asserts BOTH are called 0 times across the whole suite -- the load-bearing Phase 26/27 Wall-2 assertion" - - "Recipe-path source files kept free of dynamic-code AND network/browser-API substrings (eval(/new Function/import(/fetch/chrome.scripting) even in comments, pre-satisfying the Plan 03 CI-guard allowlist scan" - -requirements-completed: [CAP-02, CAP-03] - -# Metrics -duration: 7min -completed: 2026-06-20 ---- - -# Phase 26 Plan 02: Bundled Interpreter (validate-bind-emit-spec) Summary - -**Eval-free service-worker interpreter that validates a recipe + invoke args, binds them to a frozen four-member auth-strategy registry, templates the endpoint with encodeURIComponent-escaped params, and emits a bound request spec -- then STOPS before any network call (proven by a recorder asserting chrome.scripting.executeScript and fetch are each invoked 0 times), plus the RECIPE_* family added to the MCP error passthrough so the typed codes surface verbatim.** - -## Performance - -- **Duration:** 7 min -- **Started:** 2026-06-20T04:45:57Z -- **Completed:** 2026-06-20T04:52:58Z -- **Tasks:** 3 -- **Files modified:** 6 (3 created, 3 modified) - -## Accomplishments -- Authored the closed, frozen `AUTH_HANDLERS` registry of four spec-shaping stubs (`none`, `same-origin-cookie`, `bearer-from-storage`, `csrf-header-scrape`) plus `bindAuthStrategy`, which returns a typed `RECIPE_OPCODE_INVALID` for any strategy outside the enum (defense-in-depth) and performs zero I/O (CAP-02, D-08/D-12). -- Authored `interpretRecipe` (CAP-02/CAP-03): reuses Plan 01's `validateRecipe`, validates invoke args against `recipe.params` via the eval-free cfworker validator, templates the endpoint with a hand-rolled `{var}` replacer (D-04), applies the static `request` placement map, assembles the bound spec, and binds the auth strategy -- emitting `{ url, method, headers, body, query, authStrategy, csrfSource?/_authNeed?/credentials?, origin, extract }` and stopping before the network (D-11). `extract` is carried unevaluated for Phase 27 (D-14). -- Proved the Phase 26/27 boundary holds with the load-bearing no-network assertion: a chrome.scripting.executeScript recorder AND a globalThis.fetch recorder are each called 0 times across the 26-assertion suite. -- Added the `RECIPE_.+` family to the `mcp/src/errors.ts` verbatim-passthrough regex (D-15) and proved via the built mcp module that `RECIPE_SCHEMA_INVALID`/`RECIPE_OPCODE_INVALID` surface verbatim, not collapsed to `action_rejected`. INV-01 preserved (no tool schema touched). - -## Task Commits - -Each task was committed atomically: - -1. **Task 1: Author the closed auth-strategy handler registry (spec-shaping stubs)** - `0c06fe69` (feat) -2. **Task 2: Author the interpreter (validate-bind-emit-spec, stops before the network) + wire importScripts** - `34b28efe` (feat) -3. **Task 3: Interpreter test (binding + no-network proof) + errors.ts RECIPE_ passthrough + wire** - `81d751eb` (test) - -**Plan metadata:** committed separately (docs: complete plan) - -_Note: Task 2 carried `tdd="true"`; per this plan's task structure the proving suite lives in Task 3 (the `test` commit), exactly as Plan 01 handled its `tdd` Task 2. Task 2 was authored against the documented `` contract and verified by an inline behavioral smoke (valid case + all four auth variants + bad-enum + bad-args + missing-placeholder) before Task 3's 26-assertion suite locked it in._ - -## Files Created/Modified -- `extension/utils/capability-auth-strategies.js` - Dual-export IIFE (global `FsbCapabilityAuthStrategies` + `module.exports`). Frozen `AUTH_HANDLERS` keyed exactly by the four `authStrategy` enum members, each value a `shape(spec, recipe)` stub returning a new spec; `bindAuthStrategy(strategy, spec, recipe)` returns the typed `RECIPE_OPCODE_INVALID` shape for an unknown strategy. Zero `eval(`/`new Function`/`import(`/`fetch`/`chrome.` substrings. -- `extension/utils/capability-interpreter.js` - Dual-export IIFE (global `FsbCapabilityInterpreter` + `module.exports`). `interpretRecipe(recipe, args)` (validate -> bind -> emit spec, never the network), the hand-rolled `templateEndpoint` `{var}` replacer, the static `buildRequest` placement-map filler, and the typeof-guarded accessors. Zero `eval(`/`new Function`/`import(`/`fetch(`/`chrome.scripting` substrings. -- `tests/capability-interpreter.test.js` - Zero-framework 26-assertion suite. vm-loads the cfworker IIFE, requires the schema + auth-strategies + interpreter modules, installs the executeScript + fetch recorders, and asserts the binding behaviors, the no-network proof (both recorders at 0), and the errors.ts verbatim passthrough (via the built `mcp/build/errors.js`). -- `extension/background.js` - Two additive `importScripts` lines (`utils/capability-auth-strategies.js` then `utils/capability-interpreter.js`), each try/catch-wrapped, loaded AFTER the Plan 01 lib + schema block (D-05; additive only, byte-freeze respected). -- `mcp/src/errors.ts` - One-line regex extension: `RECIPE_.+` joined to the `TRIGGER_.+` verbatim-passthrough alternation in `resolveErrorKey` (errors.ts:122), with an explanatory comment. No other change. -- `package.json` - Appended `&& node tests/capability-interpreter.test.js` to the end of `scripts.test` (after the Plan 01 `capability-recipe-schema.test.js` entry). - -## Decisions Made -- **Delegated the recipe gate to Plan 01's `validateRecipe`** rather than re-mapping cfworker errors in the interpreter -- the typed `RECIPE_*` codes (including the Plan 01 mapping-order fix for bad enums) are inherited for free, keeping the interpreter focused on bind+emit. -- **`recipe.params` validation is conditional on its presence** -- it is the one intentionally-open JSON-Schema sub-document; a fresh `CfworkerJsonSchema.Validator(recipe.params, '2020-12', false)` per call validates invoke args and yields `RECIPE_SCHEMA_INVALID` before binding. -- **The bound spec also carries a `query` field** (the filled `request.query` placement map) alongside `headers`/`body` -- Phase 27 needs the resolved query map to assemble the final URL; this is a superset of the plan's listed spec keys, not a deviation (the plan lists the auth-relevant keys; the static placement output is part of "build { query, headers, body }" in the action). -- **The unknown-opcode test calls `bindAuthStrategy` directly** because a recipe carrying an unknown `authStrategy` is already rejected by `validateRecipe`'s enum (returning `RECIPE_OPCODE_INVALID`); the direct call proves the interpreter's defense-in-depth layer independently, exactly as the plan's Task 3 action (c) specifies. - -## Deviations from Plan - -None - plan executed exactly as written. - -The only non-mechanical authoring choice was wording the new modules' comments to avoid the literal substrings `fetch` and `chrome.` (and the dynamic-code patterns), which the task acceptance criteria require to be ZERO in these files and which the Plan 03 CI-guard allowlist scans even in comments. This is a faithful implementation of the stated acceptance criteria (and matches the precedent Plan 01 set for its schema module), not a change to plan scope or behavior. - -## Issues Encountered -- The conductor workspace is a git worktree (`.git` is a file) on branch `automation-worktree`, as the prompt's `` block describes (worktrees disabled for this project; sequential executor on the working tree). Confirmed `automation-worktree` is not a protected ref (not main/master/develop/trunk/release/*); normal atomic commits with hooks were used (no `--no-verify`), matching how Plan 01 committed. -- The working tree carried pre-existing uncommitted changes unrelated to this plan (showcase, extension/dist, ws-client.js, package-lock.json, several tests). Every commit staged ONLY this plan's task-specific files individually; the unrelated changes were left untouched. -- `mcp/build/errors.js` is a gitignored build artifact regenerated by the `npm --prefix mcp run build` step (which runs both standalone here and mid-chain in `npm test`); only the source `mcp/src/errors.ts` was committed. - -## Known Stubs -The four auth-strategy handlers are intentional spec-shaping STUBS per D-12: they DECLARE what the Phase 27 authenticated MAIN-world request will need (`credentials:'include'`, `_authNeed:{kind:'bearer',source:'storage'}`, `csrfSource:{from,selector,header}`) but perform no I/O. This is the deliberate Phase 26/27 boundary, documented in 26-CONTEXT.md (D-11/D-12) and 26-PLAN.md's scope, not a data stub -- no UI renders empty data and no consumer receives placeholder values in Phase 26. The cookie-carrying request, the live CSRF scrape, the origin-pin enforcement, and the `extract` RUN are scheduled for Phase 27 (FETCH-01..05). - -## User Setup Required -None - no external service configuration required. No `npm install` was run (the three libs were already vendored by Plan 01; no new dependency was added). - -## Next Phase Readiness -- Plan 03 (the CI guard) can add `extension/utils/capability-interpreter.js` and `extension/utils/capability-auth-strategies.js` to its recipe-path allowlist immediately -- both are already free of `eval`/`new Function`/`import(`/`fetch`/`chrome.scripting` even in comments, and `validate:extension` parses all 266 extension JS files clean. -- Phase 27 can consume the bound spec contract directly: `interpretRecipe` emits `{ url, method, headers, body, query, authStrategy, csrfSource?, _authNeed?, credentials?, origin, extract }`; the authenticated MAIN-world request, live CSRF scrape, origin-pin, and the `extract` (jmespath) RUN are its scope. -- `mcp/src/errors.ts` now surfaces the `RECIPE_*` family verbatim; the dispatcher route that carries a `RECIPE_*` result to a tool response is Phase 28. -- No blockers. - -## Self-Check: PASSED - -All 3 created files verified present on disk and all 3 task commits (`0c06fe69`, `34b28efe`, `81d751eb`) verified in git history (see Self-Check command output appended below). - ---- -*Phase: 26-recipe-schema-bundled-interpreter-mv3-ci-guard* -*Completed: 2026-06-20* diff --git a/.planning/phases/26-recipe-schema-bundled-interpreter-mv3-ci-guard/26-03-PLAN.md b/.planning/phases/26-recipe-schema-bundled-interpreter-mv3-ci-guard/26-03-PLAN.md deleted file mode 100644 index 810f648a5..000000000 --- a/.planning/phases/26-recipe-schema-bundled-interpreter-mv3-ci-guard/26-03-PLAN.md +++ /dev/null @@ -1,202 +0,0 @@ ---- -phase: 26-recipe-schema-bundled-interpreter-mv3-ci-guard -plan: 03 -type: execute -wave: 3 -depends_on: ["26-01", "26-02"] -files_modified: - - scripts/verify-recipe-path-guard.mjs - - tests/recipe-path-guard.test.js - - package.json -autonomous: true -requirements: [CAP-04] -user_setup: [] - -must_haves: - truths: - - "The guard scans an explicit hardcoded recipe-path file allowlist (the 3 capability modules + the 3 vendored libs) for eval/new Function/import( and exits non-zero on any hit" - - "The guard runs the recipe schema against the accept/reject fixtures and exits non-zero if any accept fixture is rejected or any reject fixture is accepted" - - "The guard does NOT scan the three sanctioned execute_js sites (tool-executor.js, mcp-bridge-client.js, lattice-runtime-adapter.js) and asserts they are not on the allowlist" - - "A planted-eval file on the allowlist makes the guard exit non-zero (proven by the spawn test)" - - "The guard is chained into npm run validate:extension so it runs in the CI extension job before npm test and feeds ci / all-green" - - "Decision coverage (26-CONTEXT.md): implements D-16, D-17, D-18, D-19" - artifacts: - - path: "scripts/verify-recipe-path-guard.mjs" - provides: "Node static-analysis CI guard: allowlist grep + fixture run + sanctioned-site negative self-assertion" - min_lines: 60 - - path: "tests/recipe-path-guard.test.js" - provides: "Spawns the guard: asserts exit 0 on clean tree, non-zero on a planted-eval fixture" - min_lines: 25 - - path: "package.json" - provides: "scripts.validate:extension chains the new guard; scripts.test runs the spawn test" - contains: "verify-recipe-path-guard" - key_links: - - from: "package.json scripts.validate:extension" - to: "scripts/verify-recipe-path-guard.mjs" - via: "&&-chained invocation" - pattern: "verify-recipe-path-guard.mjs" - - from: "scripts/verify-recipe-path-guard.mjs" - to: "catalog/recipes/_fixtures" - via: "accept/reject fixture validation against RECIPE_SCHEMA" - pattern: "_fixtures" - - from: "scripts/verify-recipe-path-guard.mjs" - to: "extension/utils/capability-interpreter.js" - via: "recipe-path allowlist membership" - pattern: "capability-interpreter" ---- - -## Decision Coverage - -Traceability to `26-CONTEXT.md` locked decisions implemented by this plan's tasks: - -- **D-16:** new Node static-analysis recipe-path guard (allowlist grep for `eval`/`new Function`/`import(` + fixture run) (Task 1) -- **D-17:** explicit hardcoded file allowlist (not whole-tree) with a negative self-assertion that the sanctioned `execute_js` sites are NOT flagged (Task 1) -- **D-18:** guard chained into `npm run validate:extension` → `ci / all-green` (Tasks 1-2) -- **D-19:** eval-free guard self-test suite — suite (c) (Task 2) - - -Make the Wall-1 "no code fetched as data" line unbreakable going forward: a Node static-analysis CI guard that fails the build on any eval / new Function / import( reachable from an explicit recipe-path file allowlist, and that proves the closed schema rejects out-of-vocabulary recipes by running the accept/reject fixtures. This plan delivers CAP-04. - -Purpose: The schema (Plan 01) and interpreter (Plan 02) enforce the line at runtime; this guard enforces it at build time forever — so future drift that reintroduces dynamic code on the recipe path turns CI red. The guard MUST use an explicit allowlist (not a whole-extension grep) because FSB legitimately uses eval/new Function in the MAIN-world execute_js primitive (three verified sanctioned sites) — a broad grep would false-positive and break the build on sanctioned code. - -Output: scripts/verify-recipe-path-guard.mjs, tests/recipe-path-guard.test.js, and package.json wiring (chain the guard into validate:extension + add the spawn test to scripts.test). - - - -@$HOME/.claude/get-shit-done/workflows/execute-plan.md -@$HOME/.claude/get-shit-done/templates/summary.md - - - -@.planning/STATE.md -@.planning/ROADMAP.md -@.planning/phases/26-recipe-schema-bundled-interpreter-mv3-ci-guard/26-CONTEXT.md -@.planning/phases/26-recipe-schema-bundled-interpreter-mv3-ci-guard/26-RESEARCH.md -@.planning/phases/26-recipe-schema-bundled-interpreter-mv3-ci-guard/26-PATTERNS.md -@.planning/phases/26-recipe-schema-bundled-interpreter-mv3-ci-guard/26-VALIDATION.md -@.planning/phases/26-recipe-schema-bundled-interpreter-mv3-ci-guard/26-01-SUMMARY.md -@.planning/phases/26-recipe-schema-bundled-interpreter-mv3-ci-guard/26-02-SUMMARY.md - - - - -The recipe-path file allowlist (the EXACT six files — hardcoded, NOT a glob): - extension/utils/capability-recipe-schema.js - extension/utils/capability-interpreter.js - extension/utils/capability-auth-strategies.js - extension/lib/cfworker-json-schema.min.js - extension/lib/jmespath.min.js - extension/lib/minisearch.min.js - -The three SANCTIONED sites that MUST NOT be on the allowlist (verified whole-tree grep this session — exactly these three): - extension/ai/lattice-runtime-adapter.js:66 -> `import('lattice')` inside a comment - extension/ai/tool-executor.js:387 -> eval(jsCode) - extension/ws/mcp-bridge-client.js:922 -> new Function(userCode) - -Forbidden patterns to scan for: /\beval\s*\(/ , /\bnew\s+Function\b/ , /\bimport\s*\(/ - -Fixtures (created in Plan 01, catalog/recipes/_fixtures/): valid-recipe.json (accept) + reject-field-{script,expr,transform,code,fn,js}.json + reject-unknown-field.json + reject-bad-method.json + reject-bad-authstrategy.json (reject). The guard validates each against RECIPE_SCHEMA from extension/utils/capability-recipe-schema.js (require it; it reads globalThis.CfworkerJsonSchema, so vm.runInThisContext-load the cfworker IIFE first — same loader the Plan 01 test uses). - -Static-gate analog to clone (scripts/verify-store-listing.mjs, VERIFIED): - - header: #!/usr/bin/env node ; 'use strict' ; import { readFileSync, statSync } from 'node:fs' ; import { resolve, dirname } from 'node:path' ; import { fileURLToPath } from 'node:url' ; const __dirname = dirname(fileURLToPath(import.meta.url)) ; const ROOT = resolve(__dirname, '..') ; const failures = [] - - safeRead(path,label) try/catch pushes a failure; returns null - - tail: if (failures.length>0){ console.error('verify-recipe-path-guard: FAIL'); for(const f of failures) console.error(' - '+f); process.exit(1) } console.log('verify-recipe-path-guard: PASS'); process.exit(0) - -Spawn-test analog to clone (tests/verify-store-listing.test.js, VERIFIED): - const { spawnSync } = require('child_process'); const result = spawnSync('node', ['scripts/verify-recipe-path-guard.mjs'], { cwd: ROOT, stdio:'pipe', env: process.env }); check exit + stdout/stderr. - -package.json wiring targets (VERIFIED current values): - scripts.validate:extension == "node scripts/validate-extension.mjs" -> becomes "node scripts/validate-extension.mjs && node scripts/verify-recipe-path-guard.mjs" - scripts.ci runs "npm run validate:extension && npm test && ..." -> chaining into validate:extension covers BOTH ci AND local; .github/workflows/ci.yml needs NO edit (the extension job runs validate:extension before npm test and feeds all-green). - scripts.test tail currently ends with the Plan 01/02 capability suites -> append && node tests/recipe-path-guard.test.js - - - - - - - Task 1: Author the recipe-path CI guard (allowlist grep + fixtures + negative self-assertion) - scripts/verify-recipe-path-guard.mjs - - - scripts/verify-store-listing.mjs:24-47 (header + node-builtins imports + failures[] + safeRead) and :122-131 (the FAIL/PASS exit convention) — the near-exact structural clone - - extension/utils/capability-recipe-schema.js (Plan 01 — RECIPE_SCHEMA + validateRecipe the guard reuses for the fixture check) - - catalog/recipes/_fixtures/*.json (Plan 01 — the accept/reject fixtures) - - .planning/phases/26-recipe-schema-bundled-interpreter-mv3-ci-guard/26-PATTERNS.md:227-285 (the guard analog + the three guard responsibilities + the verified sanctioned-site list) - - .planning/phases/26-recipe-schema-bundled-interpreter-mv3-ci-guard/26-RESEARCH.md:269-285 (Pitfall 3: substring grep matches comments — desired strictness on the allowlist; the negative self-test), :64 (the cfworker bundle is clean — 0 forbidden constructs) - - .planning/phases/26-recipe-schema-bundled-interpreter-mv3-ci-guard/26-CONTEXT.md D-16/D-17 (allowlist not whole-tree; the three responsibilities) - - - Create scripts/verify-recipe-path-guard.mjs cloning the verify-store-listing.mjs structure (node-builtins-only; failures[] accumulator; safeRead; FAIL prints each failure and process.exit(1); PASS prints a green line and process.exit(0)). Implement THREE checks. (1) ALLOWLIST GREP: define a hardcoded const array RECIPE_PATH_ALLOWLIST of the EXACT six file paths (the three extension/utils/capability-*.js + the three extension/lib/*.min.js) — NOT a glob, NOT a directory walk; for each file, read its text and test it against the three forbidden patterns /\beval\s*\(/, /\bnew\s+Function\b/, /\bimport\s*\(/; push a failure naming the file + the matched pattern on any hit. (2) FIXTURE RUN: vm.runInThisContext-load extension/lib/cfworker-json-schema.min.js so globalThis.CfworkerJsonSchema is set, require extension/utils/capability-recipe-schema.js, then for every JSON file in catalog/recipes/_fixtures/ classify by filename (valid-* is an accept fixture, reject-* is a reject fixture) and assert validateRecipe(JSON.parse(file)).success === true for accepts and === false for rejects; push a failure on any mismatch. (3) NEGATIVE SELF-ASSERTION: define the three known SANCTIONED paths (extension/ai/tool-executor.js, extension/ws/mcp-bridge-client.js, extension/ai/lattice-runtime-adapter.js) and assert NONE of them appears in RECIPE_PATH_ALLOWLIST (push a failure if allowlist drift ever adds one) — this proves the guard does not false-positive on sanctioned execute_js. Add a documented overridable seam so Task 2's spawn test can point the guard at a planted-eval fixture: read an env var (e.g. FSB_RECIPE_GUARD_EXTRA_ALLOWLIST, a comma-separated path list) that, when set, is appended to the allowlist before the grep — this lets the test add a temp planted-eval file and assert the guard flips non-zero (document this seam in a header comment as test-only). The guard itself is build/CI tooling (NOT shipped to the browser) so it MAY use vm/eval for the fixture loader — it is NOT on the recipe-path allowlist it scans. - - - node scripts/verify-recipe-path-guard.mjs && echo "GUARD_EXIT=$?" - - - - node scripts/verify-recipe-path-guard.mjs exits 0 on the current clean tree and prints a PASS line. - - RECIPE_PATH_ALLOWLIST is a hardcoded array of exactly the six recipe-path files (assertable by grep for each path literal in the script). - - The script contains the three sanctioned paths in its negative self-assertion and asserts none are on the allowlist. - - The script honors FSB_RECIPE_GUARD_EXTRA_ALLOWLIST (or the chosen env var) to append paths for the Task 2 planted-eval test. - - All accept fixtures validate true and all reject fixtures validate false under RECIPE_SCHEMA (the guard exits non-zero if not). - - The CI guard scans the explicit recipe-path allowlist for forbidden constructs, runs the schema fixtures, asserts the sanctioned sites are excluded, and exposes a test seam — exiting 0 on the clean tree. - - - - Task 2: Spawn test (clean PASS + planted-eval FAIL) + wire the guard into validate:extension and scripts.test - tests/recipe-path-guard.test.js, package.json - - - tests/verify-store-listing.test.js:13-49 (spawnSync('node', [script]) + exit-code/stdout assertions — the near-exact clone) - - tests/trigger-store.test.js:28-39 (passed/failed counters + check) and tail (process.exit) - - scripts/verify-recipe-path-guard.mjs (Task 1 — the guard under test + its FSB_RECIPE_GUARD_EXTRA_ALLOWLIST seam) - - package.json (scripts.validate:extension currently "node scripts/validate-extension.mjs"; scripts.ci runs validate:extension then test; scripts.test tail ends with the Plan 01/02 capability suites) - - .planning/phases/26-recipe-schema-bundled-interpreter-mv3-ci-guard/26-PATTERNS.md:340-358 (spawn-a-guard pattern + the planted-eval flip), :446-462 (the validate:extension chaining = Option 1, recommended; ci.yml needs no edit) - - .planning/phases/26-recipe-schema-bundled-interpreter-mv3-ci-guard/26-CONTEXT.md D-18 (chain into validate:extension which runs in the CI extension job before npm test and feeds all-green) - - - Create tests/recipe-path-guard.test.js cloning the verify-store-listing.test.js spawn pattern (require child_process spawnSync; passed/failed counters; process.exit(failed>0?1:0)). Assertion A (clean PASS): spawnSync('node', ['scripts/verify-recipe-path-guard.mjs'], { cwd: ROOT, stdio:'pipe', env: process.env }) and assert result.status === 0 (CAP-04 happy path; the recipe path is clean). Assertion B (planted-eval FAIL): write a temporary file under a temp dir (use node:fs + node:os tmpdir, or a fixtures/tmp path the test cleans up) containing a line with eval( (a planted forbidden construct), spawn the guard with env { ...process.env, FSB_RECIPE_GUARD_EXTRA_ALLOWLIST: }, and assert result.status !== 0 AND stderr/stdout mentions the planted file — proving the guard actually flips red when dynamic code appears on the (extended) recipe path (CAP-04 negative case). Clean up the temp file in a finally. Wire the guard into the gate: change package.json scripts.validate:extension from "node scripts/validate-extension.mjs" to "node scripts/validate-extension.mjs && node scripts/verify-recipe-path-guard.mjs" (Option 1 — this runs the guard inside the existing CI extension job before npm test and feeds ci / all-green, AND runs locally via npm run validate:extension; .github/workflows/ci.yml needs NO edit per D-18). Append && node tests/recipe-path-guard.test.js to the END of package.json scripts.test (after the Plan 02 capability-interpreter entry). - - - node tests/recipe-path-guard.test.js && npm run validate:extension && node -e "const p=require('./package.json');if(!/verify-recipe-path-guard\.mjs/.test(p.scripts['validate:extension'])){console.error('guard not chained into validate:extension');process.exit(1)}if(!/recipe-path-guard\.test\.js/.test(p.scripts.test)){console.error('spawn test not in scripts.test');process.exit(1)}console.log('wiring ok')" - - - - node tests/recipe-path-guard.test.js exits 0: Assertion A (clean tree -> guard exit 0) and Assertion B (planted-eval on the extended allowlist -> guard exit non-zero) both pass; the temp planted file is cleaned up. - - npm run validate:extension exits 0 and runs BOTH validate-extension.mjs AND verify-recipe-path-guard.mjs (the guard is chained in). - - package.json scripts.validate:extension contains verify-recipe-path-guard.mjs and scripts.test ends with node tests/recipe-path-guard.test.js. - - .github/workflows/ci.yml is unchanged (the guard runs via the existing validate:extension step — git diff on ci.yml is empty). - - CAP-04 is proven and wired: the guard exits 0 on a clean recipe path, flips non-zero on a planted-eval, and runs inside npm run validate:extension (hence the CI extension job feeding all-green) with no ci.yml edit. - - - - - -## Trust Boundaries - -| Boundary | Description | -|----------|-------------| -| developer commit -> CI gate | Future code changes on the recipe path are untrusted until the guard re-verifies them; the guard is the standing build-time enforcement of Wall-1. | -| guard allowlist scope -> sanctioned execute_js | The guard must scan ONLY the recipe path; the sanctioned MAIN-world eval/new Function sites are a different trust class and must be excluded, or the build breaks on legitimate code. | - -## STRIDE Threat Register - -| Threat ID | Category | Component | Disposition | Mitigation Plan | -|-----------|----------|-----------|-------------|-----------------| -| T-26-03 | Tampering | scripts/verify-recipe-path-guard.mjs (future drift reintroduces dynamic code on the recipe path) | mitigate | The guard greps the explicit six-file recipe-path allowlist for eval/new Function/import( and fails the build (process.exit(1)) on any hit; chained into validate:extension so it runs in the CI extension job before npm test and feeds ci / all-green. Task 2 proves it flips non-zero on a planted-eval. Locks CAP-04. | -| T-26-03b | Availability (false-positive breaking the build) | the allowlist scope vs the three sanctioned execute_js sites | mitigate | The allowlist is hardcoded (not a glob/whole-tree walk); a negative self-assertion proves none of the three sanctioned sites (tool-executor.js:387, mcp-bridge-client.js:922, lattice-runtime-adapter.js:66) is on the allowlist, so the guard does NOT false-positive on sanctioned MAIN-world code (verified: whole-tree grep finds exactly those three). | -| T-26-01 | Tampering | catalog/recipes/_fixtures + RECIPE_SCHEMA | mitigate | The guard re-runs the schema against every accept/reject fixture and fails if any forbidden-field/unknown-field/bad-enum fixture is accepted — a build-time proof that the closed-vocabulary rejection (CAP-01/CAP-04 "any recipe field outside the closed vocabulary") holds, independent of the runtime test. | - - - -- node scripts/verify-recipe-path-guard.mjs exits 0 on the clean tree. -- node tests/recipe-path-guard.test.js green (clean PASS + planted-eval FAIL). -- npm run validate:extension green and runs the guard. -- git diff .github/workflows/ci.yml empty (wired via validate:extension per D-18). - - - -- CAP-04: the build fails on any eval / new Function / import( reachable from the explicit recipe-path allowlist (proven by the planted-eval spawn test) AND on any recipe field outside the closed vocabulary (proven by the fixture run); the guard does not false-positive on the three sanctioned execute_js sites; it is chained into npm run validate:extension so the CI extension job enforces it on every build feeding ci / all-green. - - - -Create `.planning/phases/26-recipe-schema-bundled-interpreter-mv3-ci-guard/26-03-SUMMARY.md` when done. - diff --git a/.planning/phases/26-recipe-schema-bundled-interpreter-mv3-ci-guard/26-03-SUMMARY.md b/.planning/phases/26-recipe-schema-bundled-interpreter-mv3-ci-guard/26-03-SUMMARY.md deleted file mode 100644 index 44a70766a..000000000 --- a/.planning/phases/26-recipe-schema-bundled-interpreter-mv3-ci-guard/26-03-SUMMARY.md +++ /dev/null @@ -1,128 +0,0 @@ ---- -phase: 26-recipe-schema-bundled-interpreter-mv3-ci-guard -plan: 03 -subsystem: ci -tags: [ci-guard, static-analysis, wall-1, recipe-as-data, allowlist, eval-ban, json-schema, cfworker, fixtures, mv3] - -# Dependency graph -requires: - - phase: 26-01 - provides: "extension/utils/capability-recipe-schema.js (validateRecipe + RECIPE_SCHEMA), the three vendored libs (cfworker-json-schema/jmespath/minisearch), and catalog/recipes/_fixtures/ accept+reject set" - - phase: 26-02 - provides: "extension/utils/capability-interpreter.js + extension/utils/capability-auth-strategies.js on the recipe path (both already eval-free even in comments)" -provides: - - "scripts/verify-recipe-path-guard.mjs -- Node static-analysis CI guard: hardcoded six-file allowlist grep for eval/new Function/import( + accept/reject fixture run + negative self-assertion that the three sanctioned execute_js sites are NOT on the allowlist; honors a test-only FSB_RECIPE_GUARD_EXTRA_ALLOWLIST seam" - - "tests/recipe-path-guard.test.js -- spawn test: guard exit 0 on the clean tree + exit non-zero on a planted-eval temp file (named in the FAIL output, cleaned up in finally)" - - "package.json scripts.validate:extension chains the guard (runs in the CI extension job before npm test, feeds ci/all-green); scripts.test appends the spawn test" -affects: [phase-27-authenticated-fetch, phase-28-mcp-search, phase-29-catalog-router, phase-31-recipe-synthesis, phase-32-self-heal] - -# Tech tracking -tech-stack: - added: [] - patterns: - - "Node-builtins-only static-analysis CI gate (failures[] accumulator + safeRead + process.exit(1)-on-fail) cloned from scripts/verify-store-listing.mjs, but .mjs ESM using createRequire(import.meta.url) + node:vm to load the CJS schema module and the cfworker IIFE for the fixture check" - - "Explicit HARDCODED recipe-path file allowlist (NOT a glob, NOT a directory walk) so the guard scans ONLY the recipe path and never the three sanctioned MAIN-world execute_js sites (D-17 trust-class separation)" - - "Precise word-boundary forbidden patterns (/\\beval\\s*\\(/, /\\bnew\\s+Function\\b/, /\\bimport\\s*\\(/) so minified vendored libs do not false-positive on innocent substrings (retrieval/evaluate/important)" - - "Negative self-assertion: the guard asserts the three sanctioned sites are NOT on its own allowlist (defends against allowlist drift turning the build red on sanctioned code)" - - "Build-time closed-vocabulary proof: the guard re-runs RECIPE_SCHEMA over catalog/recipes/_fixtures (valid-* accepted, reject-* rejected), independent of the runtime test suite" - - "Test-only override seam (FSB_RECIPE_GUARD_EXTRA_ALLOWLIST, comma-separated, appended before the grep) so the spawn test can plant an eval file and prove the guard flips non-zero; documented as test-only in a header comment, never set in CI" - - "spawn-a-guard test (spawnSync('node',[script]) + exit-code/stdout assertions; passed/failed counters) cloned from tests/verify-store-listing.test.js; the planted forbidden construct is assembled from string fragments so the test's own source carries no literal forbidden token" - -key-files: - created: - - scripts/verify-recipe-path-guard.mjs - - tests/recipe-path-guard.test.js - modified: - - package.json - -key-decisions: - - "The allowlist is the EXACT six recipe-path files hardcoded (D-17), NOT a whole-extension grep -- a broad grep would false-positive on the three verified sanctioned execute_js sites (tool-executor.js:387 eval(jsCode), mcp-bridge-client.js:922 new Function(userCode), lattice-runtime-adapter.js:66 import('lattice') in a comment), which legitimately run dynamic code in MAIN world (a different trust class)" - - "Forbidden patterns use precise word boundaries so the minified jmespath/minisearch/cfworker bundles do not trip on innocent substrings; verified all six allowlisted files have ZERO matches on the clean tree" - - "The guard is an .mjs (matching the verify-store-listing.mjs analog) but needs CommonJS interop for the fixture check: createRequire(import.meta.url) to require the CJS schema module and node:vm runInThisContext to load the cfworker IIFE global -- the guard is CI tooling NOT on the recipe-path allowlist, so this dynamic load does not (and must not) trip the guard itself" - - "Wired via Option 1 (chain into validate:extension) per D-18: the existing .github/workflows/ci.yml extension job already runs `npm run validate:extension` before `npm test` and all-green needs [extension, mcp-smoke, website], so chaining covers BOTH CI and local with NO ci.yml edit (verified empty diff)" - - "Fixtures are classified by filename (valid-* = accept, reject-* = reject) and asserted against validateRecipe(...).success (the schema module returns .success, not .valid); the guard fails if any accept is rejected or any reject is accepted, and fails if zero accepts or zero rejects were exercised (defends against an empty/renamed fixture dir silently passing)" - -patterns-established: - - "A standing build-time Wall-1 enforcement: any future commit that reintroduces eval/new Function/import( on the six recipe-path files turns CI red; proven to flip by the planted-eval spawn test via the documented test-only env seam" - - "Guard + spawn-test source files are themselves kept OFF the allowlist they scan (they contain the literal forbidden patterns as regex/string fragments by necessity), and the test plants its forbidden construct from assembled fragments to keep its own source token-clean" - -requirements-completed: [CAP-04] - -# Metrics -duration: 3min -completed: 2026-06-20 ---- - -# Phase 26 Plan 03: Recipe-Path CI Guard (CAP-04) Summary - -**A Node static-analysis CI guard that makes the Wall-1 "no code fetched as data" line unbreakable at build time forever: it greps an explicit hardcoded six-file recipe-path allowlist for `eval`/`new Function`/`import(` (failing the build on any hit), re-runs the closed-vocabulary RECIPE_SCHEMA over the accept/reject fixtures (failing if any out-of-vocabulary recipe is accepted), and asserts the three sanctioned MAIN-world `execute_js` sites are NOT on its allowlist so it never false-positives on sanctioned code -- chained into `npm run validate:extension` so the CI extension job enforces it on every build feeding `ci / all-green`, with no `ci.yml` edit.** - -## Performance - -- **Duration:** 3 min -- **Started:** 2026-06-20T04:58:05Z -- **Completed:** 2026-06-20T05:00:52Z -- **Tasks:** 2 -- **Files modified:** 3 (2 created, 1 modified) - -## Accomplishments -- Authored `scripts/verify-recipe-path-guard.mjs` (CAP-04, D-16/D-17): three checks in one Node-builtins gate -- (1) an explicit hardcoded six-file recipe-path allowlist grepped for the three forbidden dynamic-code constructs with precise word-boundary patterns; (2) a fixture run that loads the cfworker IIFE, requires the recipe-schema module, and asserts every `valid-*` fixture is accepted and every `reject-*` fixture is rejected; (3) a negative self-assertion that none of the three sanctioned `execute_js` sites is on the allowlist. -- Exposed a documented test-only `FSB_RECIPE_GUARD_EXTRA_ALLOWLIST` seam so the build-time guard can be pointed at a planted-eval fixture to prove it flips red. -- Authored `tests/recipe-path-guard.test.js` (D-19 suite c): spawns the guard and asserts exit 0 + a PASS line on the clean tree, then writes a temp planted-eval file, points the guard at it via the env seam, and asserts the guard exits non-zero AND names the planted file -- cleaning the temp file up in a `finally`. -- Wired the guard into the gate (D-18): `scripts.validate:extension` now runs `validate-extension.mjs && verify-recipe-path-guard.mjs`, so the existing CI `extension` job runs it before `npm test` and feeds `ci / all-green`; `scripts.test` appends the spawn test. No `.github/workflows/ci.yml` edit was needed (verified empty diff). - -## Task Commits - -Each task was committed atomically (hooks ran; no `--no-verify`): - -1. **Task 1: Author the recipe-path CI guard (allowlist grep + fixtures + negative self-assertion)** - `b42356f0` (feat) -2. **Task 2: Spawn test (clean PASS + planted-eval FAIL) + wire the guard into validate:extension and scripts.test** - `84c20c59` (test) - -**Plan metadata:** committed separately (docs: complete plan). - -## Files Created/Modified -- `scripts/verify-recipe-path-guard.mjs` - The CI guard. Node-builtins-only (`node:fs`, `node:path`, `node:url`, `node:module` `createRequire`, `node:vm`). `RECIPE_PATH_ALLOWLIST` is the exact six recipe-path files; `SANCTIONED_SITES` is the three excluded MAIN-world sites; `FORBIDDEN` is the three word-boundary patterns. Check 1 greps each allowlisted (+ any env-seam) file; Check 2 vm-loads the cfworker IIFE, requires the schema module, and runs `validateRecipe` over `catalog/recipes/_fixtures/*.json` classified by filename; Check 3 asserts no sanctioned site is on the allowlist. `process.exit(1)` with named failures on any miss, `process.exit(0)` + a PASS line otherwise. -- `tests/recipe-path-guard.test.js` - Zero-framework spawn test cloning `tests/verify-store-listing.test.js`. Assertion A: clean-tree guard exit 0 + PASS in stdout. Assertion B: a temp planted-eval file (forbidden construct assembled from string fragments so this source stays token-clean) on the env-seam allowlist makes the guard exit non-zero and name the file; temp file removed in `finally`. 5 assertions, all green. -- `package.json` - Two surgical edits: `scripts.validate:extension` changed from `node scripts/validate-extension.mjs` to `node scripts/validate-extension.mjs && node scripts/verify-recipe-path-guard.mjs`; `scripts.test` appends `&& node tests/recipe-path-guard.test.js` after the Plan 02 `capability-interpreter.test.js` entry. No other change. - -## Decisions Made -- **Hardcoded allowlist, not a whole-tree grep (D-17)** - the guard scans only the six recipe-path files. The three sanctioned sites (`tool-executor.js:387` `eval(jsCode)`, `mcp-bridge-client.js:922` `new Function(userCode)`, `lattice-runtime-adapter.js:66` `import('lattice')` in a comment) legitimately run dynamic code in MAIN world (a different trust class) and are deliberately excluded; a negative self-assertion proves they stay off the allowlist even under future drift. -- **Precise word-boundary forbidden patterns** - `/\beval\s*\(/`, `/\bnew\s+Function\b/`, `/\bimport\s*\(/` so the minified vendored libs do not false-positive on innocent substrings (`retrieval`, `evaluate`, `important`). Verified all six allowlisted files have zero matches on the clean tree. -- **`.mjs` guard with CommonJS interop for the fixture check** - matches the `verify-store-listing.mjs` analog but uses `createRequire(import.meta.url)` to require the CJS schema module and `node:vm` `runInThisContext` to populate `globalThis.CfworkerJsonSchema` (the same loader the Plan 01 test uses). The guard is CI tooling that is NOT on the allowlist it scans, so this dynamic load is allowed and does not self-flag. -- **Wired via Option 1 (chain into `validate:extension`), no `ci.yml` edit (D-18)** - the CI `extension` job already runs `npm run validate:extension` before `npm test`, and `all-green` needs `[extension, mcp-smoke, website]`, so chaining covers both CI and local runs. `git diff .github/workflows/ci.yml` is empty. -- **Fixtures classified by filename and asserted on `.success`** - `valid-*` must return `success === true`, `reject-*` must return `success === false`. The guard also fails if zero accepts or zero rejects were exercised, so a renamed/emptied fixture dir cannot silently pass. - -## Deviations from Plan - -None - plan executed exactly as written. - -The plan's `` block described the fixture check as `validateRecipe(...).valid` / `valid:true`, but the actual Plan 01 schema module returns `{ success: true }` / `{ success: false, code, ... }` (confirmed by reading `extension/utils/capability-recipe-schema.js` and `tests/capability-recipe-schema.test.js`). The guard therefore asserts `.success`, exactly matching the live contract and the Task 1 action text ("assert validateRecipe(JSON.parse(file)).success === true"). This is faithful implementation against the real interface, not a scope change. - -## Issues Encountered -- The conductor workspace is a git worktree (`.git` is a file) on branch `automation-worktree`, as the `` block notes (sequential executor on the working tree). Confirmed `automation-worktree` is not a protected ref (not main/master/develop/trunk/release/*); normal atomic commits with hooks were used (no `--no-verify`), matching Plans 01 and 02. -- The working tree carried pre-existing uncommitted changes unrelated to this plan (showcase files, extension/dist, several tests). Every commit staged ONLY this plan's task-specific files individually (`scripts/verify-recipe-path-guard.mjs`, `tests/recipe-path-guard.test.js`, `package.json`); the unrelated changes were left untouched. -- The `gsd-sdk query` state handlers in this environment expect NAMED flags (`--phase`, `--plan`, `--duration`, `--summary`) rather than positional args; the metric and decision updates were re-issued with flags and succeeded. - -## Known Stubs -None. The guard is a complete, exercised build-time gate: it exits 0 on the clean tree and is proven to flip non-zero on a planted-eval via the spawn test. `minisearch` remains a vendored-but-unwired lib (a Plan 01 CAP-05 artifact, scheduled for Phase 28) but it IS on this guard's allowlist and IS scanned, so it is covered by Wall-1 enforcement now even though it is not yet wired into a runtime path. - -## Threat Flags -None. This plan adds CI tooling and a test only; it introduces no new network endpoint, auth path, file-access pattern, or trust-boundary schema change beyond the recipe path the threat model already covers. The guard itself is the mitigation for T-26-03 / T-26-03b / T-26-01 in the plan's threat register. - -## User Setup Required -None - no external service configuration required. No `npm install` was run (no new dependency; the guard uses only Node built-ins, and the cfworker bundle + schema module + fixtures were vendored by Plans 01/02). - -## Next Phase Readiness -- CAP-04 is locked: the Wall-1 "no code fetched as data" line is now enforced at build time. Any future commit that reintroduces `eval`/`new Function`/`import(` on the six recipe-path files, or that loosens the closed vocabulary so an out-of-vocabulary recipe is accepted, turns `ci / all-green` red. -- Phase 26 is complete (CAP-01..05 all delivered: schema + libs in Plan 01, interpreter + auth stubs + RECIPE_ passthrough in Plan 02, the CI guard here). -- Phase 27 (authenticated MAIN-world fetch, FETCH-01..05) can build on the bound-spec contract from Plan 02 with the guard standing watch: when Phase 27 adds the live fetch + CSRF scrape + extract RUN, those land in MAIN-world `execute_js` / new files OUTSIDE the recipe-path allowlist (the sanctioned trust class), so the guard will not flag them -- but if any of it were ever placed on a recipe-path file it would correctly fail. -- If a future plan adds a new recipe-path file (e.g. a bundled-handler module in Phase 29), it must be ADDED to `RECIPE_PATH_ALLOWLIST` in `scripts/verify-recipe-path-guard.mjs` to bring it under Wall-1 enforcement. -- No blockers. - -## Self-Check: PASSED - -Both created files verified present on disk (`scripts/verify-recipe-path-guard.mjs`, `tests/recipe-path-guard.test.js`) and both task commits (`b42356f0`, `84c20c59`) verified in git history. The committed `package.json` carries the guard reference in `scripts.validate:extension` and the spawn-test reference in `scripts.test`. The full plan verification block is green: `node scripts/verify-recipe-path-guard.mjs` exits 0, `node tests/recipe-path-guard.test.js` exits 0, `npm run validate:extension` exits 0 (running both `validate-extension.mjs` and the guard), and `git diff .github/workflows/ci.yml` is empty. - ---- -*Phase: 26-recipe-schema-bundled-interpreter-mv3-ci-guard* -*Completed: 2026-06-20* diff --git a/.planning/phases/26-recipe-schema-bundled-interpreter-mv3-ci-guard/26-CONTEXT.md b/.planning/phases/26-recipe-schema-bundled-interpreter-mv3-ci-guard/26-CONTEXT.md deleted file mode 100644 index e1e3e29c2..000000000 --- a/.planning/phases/26-recipe-schema-bundled-interpreter-mv3-ci-guard/26-CONTEXT.md +++ /dev/null @@ -1,128 +0,0 @@ -# Phase 26: Recipe Schema + Bundled Interpreter + MV3 CI Guard - Context - -**Gathered:** 2026-06-19 (assumptions mode) -**Status:** Ready for planning - - -## Phase Boundary - -Establish the **closed-vocabulary recipe-as-data format** and the **fixed, eval-free bundled interpreter**, plus a **CI guard** that makes the Wall-1 "no code fetched as data" line unbreakable *before any recipe is ever interpreted*. This is the foundational Wall-1 day-one invariant of the v0.9.99 Native Capability Catalog milestone. - -**In scope (CAP-01..05):** versioned recipe JSON Schema; the bundled interpreter that validates + binds recipe data to a closed enum of auth-strategy handlers (producing a ready-to-execute request spec); eval-free in-SW validation via `@cfworker/json-schema`; the CI guard; vendoring the three new libs into the extension package with no manifest/permission change. - -**Explicitly NOT in this phase:** the authenticated MAIN-world `fetch`, live CSRF scrape, origin-pin enforcement against a live request, and SW-eviction resume — those are **Phase 27 (FETCH-01..05)**. The `search_capabilities`/`invoke_capability` MCP tools are **Phase 28**. Tiering/router/bundled-head/autopilot are **Phase 29**. Consent/signing/audit are **Phase 30**. - - - -## Implementation Decisions - -### Library Integration & Packaging -- **D-01:** Use **PATH A** — vendor `minisearch`, `jmespath`, and `@cfworker/json-schema` into `extension/lib/` as global UMD/IIFE files loaded via `importScripts(...)` in the service worker, mirroring `extension/lib/lz-string.min.js`. Access them in the SW via `typeof !== 'undefined'` checks (the `getFSBLZStringCodec()` / `ws-client.js:98-99` pattern). -- **D-02:** `minisearch` (ships UMD `dist/umd`) and `jmespath` (single UMD `jmespath.js`) vendor **as-is**. `@cfworker/json-schema` is ESM/CJS-only and **must** be converted by a one-off build-time `esbuild --bundle --format=iife --global-name=...` of the local `node_modules` copy into `extension/lib/cfworker-json-schema.min.js`. (Decisive reason: a raw-ESM file dropped in `lib/` fails the existing `scripts/validate-extension.mjs` `node --check` gate — empirically confirmed — and would break CI.) -- **D-03:** All **three** libraries ship in Phase 26 to satisfy CAP-05 ("interpreter + the three new libraries ship inside the extension package"), even though only `@cfworker/json-schema` (validation) and `jmespath` (extract) are *exercised* here. `minisearch` is vendored now but not wired until Phase 28 (search). -- **D-04:** `url-template` is **OUT for v1** — endpoint templating uses a hand-rolled `{var}` replacer. -- **D-05:** **No manifest/permission change.** The only edit to the service worker entry is additive `importScripts('lib/...')` line(s) — consistent with the `lz-string` precedent and with the esbuild SW byte-freeze invariant (`esbuild.config.js`; `background.js` is not an esbuild input). - -### Recipe Schema (Closed Vocabulary) -- **D-06:** A recipe is a **versioned JSON object** with a **closed** top-level vocabulary: `schemaVersion`, `id`/`slug`, `origin`, `endpoint` (URI template), `method` (enum: GET/POST/PUT/PATCH/DELETE), `authStrategy` (closed enum, see D-08), `params` (nested JSON-Schema validated against invoke args), `request` (static param→placement map: query/header/body), and `extract` (a single read-only JMESPath string). -- **D-07:** **No executable/script fields, ever.** Forbidden field names that must be actively rejected and CI-guarded: `script`, `expr`, `transform`, `code`, `fn`, `js`. Any field outside the closed vocabulary → typed rejection (D-15). -- **D-08:** `authStrategy` v1 enum members: `same-origin-cookie`, `csrf-header-scrape`, `bearer-from-storage`, `none`. **(OPEN — resolve in the plan-time schema spike:** whether `persisted-query-hash` and/or a Slack-style split-token strategy join v1 now, or defer to the bundled-handler head in Phase 29. Default = defer.) -- **D-09:** **Pagination is OUT of the v1 schema** (no requirement behind it; adding cursor loops edges toward the Wall-1 "interpreter" line). -- **D-10:** Schema versioning via the `schemaVersion` envelope field, mirroring the existing `FSB_TRIGGER_REGISTRY_PAYLOAD_VERSION` idiom in `extension/utils/trigger-store.js`. - -### Interpreter Scope (Phase 26 ↔ Phase 27 boundary) -- **D-11:** The Phase 26 interpreter **validates** (recipe + invocation params, in the SW, via `@cfworker/json-schema`) and **binds** recipe data to the selected auth-strategy handler, producing a **bound, ready-to-execute request spec** `{ url, method, headers, body, authStrategy, csrfSource? }`. It **does NOT perform the network call.** -- **D-12:** Auth-strategy handlers in Phase 26 are **header/spec-shaping stubs** — they declare what header/CSRF source each strategy needs and shape the spec. The actual cookie-carrying MAIN-world fetch + live CSRF scrape are **Phase 27 (FETCH-01/02)**. -- **D-13:** Validation is **eval-free**: `@cfworker/json-schema` only — never Ajv default codegen (`new Function` under MV3 CSP), never `eval`/`new Function`/`import()` on a recipe field. -- **D-14:** The `extract` (JMESPath) field is **defined and schema-validated** in Phase 26; the `jmespath` lib is vendored and the extract helper may be unit-tested against a **static JSON fixture**. Extraction against a *live* response runs in Phase 27 (after the fetch exists). - -### CI Guard & Typed Errors -- **D-15:** Typed-error shape for schema/opcode rejection: the interpreter **returns** (does not throw) `{ success: false, code: 'RECIPE_SCHEMA_INVALID' | 'RECIPE_UNKNOWN_FIELD' | 'RECIPE_OPCODE_INVALID', ...context }`, surfaced by adding the `RECIPE_*` family to `mcp/src/errors.ts` (the `CODE_ONLY_ERROR_KEYS` set / verbatim-passthrough regex, mirroring the `TRIGGER_*` extension point). -- **D-16:** A **new Node static-analysis guard** (e.g. `scripts/verify-recipe-path-guard.mjs`) that (1) grep/regex-scans a **recipe-path file allowlist** for the literal patterns `eval`, `new Function`, `import(` and fails non-zero on any hit; and (2) runs the recipe JSON Schema against **accept/reject fixtures** to prove unknown fields/opcodes are rejected. -- **D-17:** "The recipe path" is delimited by an **explicit hardcoded file allowlist** (interpreter, schema module, auth-strategy handler module, vendored runtime libs) — **NOT** a whole-`extension/` grep — to avoid false-positives on FSB's **sanctioned** `execute_js` primitive (`extension/ai/tool-executor.js:382`, `extension/ws/mcp-bridge-client.js:915` intentionally use `new Function` in MAIN world; different trust class). -- **D-18:** The guard hooks into the existing gate: added to / chained after `npm run validate:extension`, which runs in `.github/workflows/ci.yml`'s `extension` job **before** `npm test` and feeds the `ci / all-green` status check. - -### Tests -- **D-19:** Plain CommonJS `node tests/*.test.js` files appended to the root `package.json` `scripts.test` `&&`-chain — **no test framework** — mirroring `tests/trigger-store.test.js` (absolute-path module load + `chrome.*`/global shims). Three suites: (a) schema accept/reject fixtures (valid passes; `script`/unknown-field recipes rejected with the typed `RECIPE_*` code), (b) interpreter binding (valid recipe → expected bound spec, asserting it **stops before the network**), (c) eval-free guard self-test (a planted-`eval` fixture is flagged). - -### Claude's Discretion -- The standalone-script vs extend-`validate-extension.mjs`-in-place sub-choice for the CI guard (D-16/D-18) — either is acceptable; planner picks the lower-friction option. -- Exact new-file names/locations within established conventions (interpreter/schema/handlers expected under `extension/utils/`, alongside `trigger-store.js`; declarative recipe data under a `catalog/recipes/*.json`-style path if any sample recipes are shipped for fixtures). -- esbuild flags for the one-off `@cfworker` IIFE bundle (`platform: browser`, `target: chrome120`, matching existing entries). - -### Folded Todos -None — no pending todos matched Phase 26. - - - -## Canonical References - -**Downstream agents MUST read these before planning or implementing.** - -Research (authoritative, dated 2026-06-19; NB: research uses pre-final phase numbers — "Phase 25" in research == final **Phase 26**): -- `.planning/research/STACK.md` — library decisions, PATH A vs PATH B, the five-question answers, Integration Points table (file:line anchors), What-NOT-to-Use (Ajv codegen, JSONata-in-recipes, zod@4, SW `fetch`). -- `.planning/research/PITFALLS.md` — Wall-1 code-vs-data table, Pitfall 1 (recipe-as-code ban + CI-guard guidance + forbidden field names), the "looks done but isn't" checklist. -- `.planning/research/ARCHITECTURE.md` — component layout (`utils/capability-*.js`, `catalog/recipes/*.json`), Decision B (interpreter-in-SW / fetch-in-MAIN split), Anti-Pattern 3 (the `eval` trust-class distinction the CI guard must respect). -- `.planning/research/SUMMARY.md` — decision-ready synthesis; Research Flags (schema-design + RHC-line spike); Gaps to Address (PATH A vs B left to this phase). - -Roadmap / requirements: -- `.planning/ROADMAP.md` — Phase 26 + Phase 27 details (the scope boundary), INV-01..04, the two architectural Walls. -- `.planning/REQUIREMENTS.md` — CAP-01..05 (Phase 26), FETCH-01..05 (Phase 27, for the boundary). - -Source anchors (real tree under `extension/`; the `.planning/codebase/*.md` maps are stale, dated 2026-02-03): -- `extension/background.js:97` (`importScripts('lib/lz-string.min.js')` precedent) + the importScripts block. -- `extension/ws/ws-client.js:98-99` (global-access pattern `typeof LZString !== 'undefined'`). -- `scripts/validate-extension.mjs` (static CI gate; `EXT_DIRS` ~line 79 — `lib/` included, `dist/` excluded; per-file `node --check`). -- `.github/workflows/ci.yml` (`extension` job runs `validate:extension` → `npm test`; `all-green` gate `needs:[extension, mcp-smoke, website]`). -- `mcp/src/errors.ts:54-68` (`CODE_ONLY_ERROR_KEYS`) and `:104-124` (`resolveErrorKey` verbatim passthrough + `TRIGGER_*` regex extension point). -- `extension/ws/mcp-tool-dispatcher.js:1063` (typed `{ code:'TAB_NOT_OWNED', ... }` return shape) + `tests/ownership-error-codes.test.js`. -- `tests/trigger-store.test.js` (the `node tests/*.test.js` convention to clone) + root `package.json` `scripts.test`/`scripts.ci`. -- `esbuild.config.js` (D-17 SW byte-freeze header; per-entry `platform:browser`/`target:chrome120`/`format:iife` precedent for the PATH-A one-off `@cfworker` bundle). -- `extension/manifest.json` (no explicit `content_security_policy` → MV3 default `script-src 'self'` → the eval ban that mandates `@cfworker/json-schema` over Ajv). - - - -## Existing Code Insights - -### Reusable Assets -- **Vendored-lib precedent:** `extension/lib/{lz-string,chart,marked,purify,mermaid,gpt-tokenizer}.min.js` loaded via `importScripts` and read as globals — the exact pattern for the three new libs. -- **Static CI gate:** `scripts/validate-extension.mjs` already greps + `node --check`s `lib/` and source dirs and exits non-zero on first failure — the model (and likely host) for the new recipe-path guard. -- **Typed-error convention:** dispatcher handlers return `{ success:false, code, ...context }`; `mcp/src/errors.ts` surfaces `code`/`errorCode` verbatim (the `TRIGGER_*` regex at errors.ts:122 is the copy-target for `RECIPE_*`). -- **Test harness:** `tests/trigger-store.test.js` / `tests/ownership-error-codes.test.js` show the zero-framework `node tests/*.test.js` + global-shim + typed-`{code}` convention. -- **esbuild per-entry browser IIFE** config exists for the one-off `@cfworker` bundle. -- **`jsonSchemaToZod` bridge** (`mcp/src/tools/schema-bridge.ts`) — awareness only; used when the MCP tools land in Phase 28, not Phase 26. - -### Established Patterns -- The SW loads vendored libs **only from `lib/`** (and source dirs) via `importScripts` — it imports **nothing from `extension/dist/`** today (dist is content/offscreen/sidepanel bundles consumed via `` (or inline into `options.js` per planner) AFTER `options.js` in `control_panel.html`, alongside the existing `install-identity-ui.js` tail (`control_panel.html:1613-1627`). Any new store module reached from the UI is added as a `` at the bottom of `control_panel.html` immediately before ``. Do not move, remove, or duplicate any other script and do not add inline JavaScript under MV3 CSP. + +Extend `tests/providers-panel-logic.test.js` with a source-order assertion that both script tokens occur exactly once and `providers-panel.js` precedes `options.js`. Add `node tests/providers-panel-logic.test.js` to root `package.json`'s serial `test` command immediately after `node tests/mcp-client-identity-integration.test.js` and before the existing turn/provider tests. Preserve every existing command and its relative order otherwise. + +Run the new test plus existing options/model/sunset contracts and then `npm test`. This task must not add provider rows, settings keys, rendering, CSS, storage calls, or change the current visible API Configuration surface; Plan 58-02 owns visible behavior. + + +- `control_panel.html` loads `providers-panel.js` exactly once and before `options.js`, with no inline script added. +- Root `npm test` contains exactly one `node tests/providers-panel-logic.test.js` and drops no pre-existing test command. +- The current seven-value `#modelProvider` and model combobox markup remain present and unchanged in behavior. +- `node tests/providers-panel-logic.test.js`, `node tests/model-discovery-ui.test.js`, `node tests/model-combobox-ui.test.js`, and `node tests/agent-sunset-control-panel.test.js` all exit 0. +- `npm test` exits 0 at this extension-touching task gate. + + + node tests/providers-panel-logic.test.js && node tests/model-discovery-ui.test.js && node tests/model-combobox-ui.test.js && node tests/agent-sunset-control-panel.test.js && npm test + + The pure helper is available before options boot and permanently enforced without yet altering visible provider behavior. + + + + + +## Trust Boundaries + +| Boundary | Description | +|----------|-------------| +| Phase 57 merged client map → provider helper | Durable/live/raw evidence is observational and may be malformed or attacker-named. | +| Persisted settings → provider helper | Legacy or manually edited storage may contain unsupported provider values. | +| Static provider definitions → external vendor pages | Billing text and destinations affect user financial understanding. | + +## STRIDE Threat Register + +| Threat ID | Category | Component | Disposition | Mitigation Plan | +|-----------|----------|-----------|-------------|-----------------| +| T-58-01 | Spoofing | recommendation helper | mitigate | Scan only closed ids in fixed order; raw/unknown/`__proto__` keys never become candidates; Task 1 table-tests all cases. | +| T-58-02 | Tampering | recommendation vs selection | mitigate | Pure recommendation has no settings input/output and returns a fresh object; non-mutation tests freeze inputs. | +| T-58-03 | Elevation of Privilege | settings normalization | mitigate | Closed API/agent allowlists and invalid-agent fail-closed migration keep agent ids out of `modelProvider`. | +| T-58-04 | Information Integrity | billing definitions | mitigate | Exact provider-specific qualified copy; subscription label is not encoded as unconditional state. | +| T-58-05 | Spoofing | external destinations | mitigate | Frozen exact HTTPS official URLs asserted by tests; no runtime URL construction. | + + + +1. `node tests/providers-panel-logic.test.js` +2. `node tests/model-discovery-ui.test.js` +3. `node tests/model-combobox-ui.test.js` +4. `node tests/agent-sunset-control-panel.test.js` +5. Confirm `git diff --check` and that only the four declared files changed for this plan. + + + +- Pure provider logic is fully testable without DOM or Chrome APIs. +- Recommendation and selected settings have no mutation path between them. +- The API/agent id domains are closed and preserved separately. +- Billing destinations/copy are frozen to the approved honest contract. +- The helper loads under MV3 CSP and the root suite runs its contract permanently. + + + +After completion, create `.planning/phases/58-providers-panel/58-01-SUMMARY.md`. + diff --git a/.planning/phases/58-providers-panel/58-01-SUMMARY.md b/.planning/phases/58-providers-panel/58-01-SUMMARY.md new file mode 100644 index 000000000..8751e6d55 --- /dev/null +++ b/.planning/phases/58-providers-panel/58-01-SUMMARY.md @@ -0,0 +1,125 @@ +--- +phase: 58-providers-panel +plan: "01" +subsystem: ui +tags: [chrome-mv3, vanilla-js, provider-selection, recommendation, regression-tests] + +requires: + - phase: 57-agent-identity-capture + provides: Durable clicked, installed, connected, and live MCP-client evidence +provides: + - Closed API and agent provider domains with fail-closed settings normalization + - Deterministic live, installed, clicked, then xAI recommendation contract + - Honest agent status and frozen official billing definitions + - CSP-safe provider helper load order and permanent root-suite coverage +affects: [58-02, 58-03, providers-panel, options-page, delegated-providers] + +tech-stack: + added: [] + patterns: [pure classic-script IIFE, frozen closed allowlists, advisory evidence isolation] + +key-files: + created: + - extension/ui/providers-panel.js + - tests/providers-panel-logic.test.js + modified: + - extension/ui/control_panel.html + - package.json + +key-decisions: + - "Keep modelProvider closed to the existing seven API ids while storing agent intent separately." + - "Scan recommendation evidence only in fixed Claude Code, OpenCode, Codex order and ignore historical connected evidence." + - "Treat subscription inclusion as conditional future metadata and freeze provider-specific billing guidance instead." + +patterns-established: + - "Provider-domain purity: deterministic settings, recommendation, status, and definitions remain free of DOM and Chrome APIs." + - "Evidence isolation: recommendation returns fresh advisory objects and never accepts or mutates settings." + +requirements-completed: [PROV-02, PROV-04, PROV-05, PROV-06] + +duration: 14 min +completed: 2026-07-12 +--- + +# Phase 58 Plan 01: Pure Provider Domain Contract Summary + +**Frozen API/agent provider domains with deterministic evidence-based recommendation, honest billing metadata, and permanent MV3 load-order regression coverage** + +## Performance + +- **Duration:** 14 min +- **Started:** 2026-07-12T20:27:45Z +- **Completed:** 2026-07-12T20:42:39Z +- **Tasks:** 2 +- **Files modified:** 4 + +## Accomplishments + +- Added a frozen classic-script/CommonJS provider helper that keeps seven BYOK API ids and three agent ids structurally separate. +- Locked recommendation priority to live, installed, clicked, then xAI with fixed agent tie order, prototype-safe reads, fresh results, and no selection mutation path. +- Added status derivation that distinguishes current connection from historical evidence and exposes only finite installed check times. +- Frozen four approved HTTPS billing destinations and qualified provider-specific copy without unconditional subscription, zero-cost, or unlimited claims. +- Loaded the helper immediately before `options.js` and made its exhaustive Node contract a permanent serial root-suite gate. + +## Task Commits + +Each task was committed atomically: + +1. **Task 1: Implement the closed provider settings, recommendation, status, and billing contracts** - `67bd8d30` (feat) +2. **Task 2: Load and permanently gate the provider helper without changing runtime behavior** - `00c6ab70` (chore) + +## Files Created/Modified + +- `extension/ui/providers-panel.js` - Pure frozen provider definitions, normalization, recommendation, and status API. +- `tests/providers-panel-logic.test.js` - Table-driven contract, source-purity, script-order, legacy-markup, and root-suite assertions. +- `extension/ui/control_panel.html` - Loads the helper exactly once immediately before options boot. +- `package.json` - Runs the provider contract after MCP client identity integration without changing any other command order. + +## Decisions Made + +- Valid latent agent selection is preserved while API kind is active, but an invalid active agent fails closed to API kind. +- Only a non-null `live` record means connected now; durable `connected` evidence remains display-only as Seen before. +- Agent definitions describe the account/provider that may incur usage and charges; they do not assert a subscription billing mode before adapter metadata exists. + +## Deviations from Plan + +None - plan executed exactly as written. + +## Issues Encountered + +- The pre-existing deletion of `.planning/phases/39-breadth-c-commerce-travel-misc-most-sensitive/39-06-REMAINING-APPS.md` caused the first raw root-suite run to stop in `coverage-report.test.js`. Each required root-suite gate used a temporary symlink to the identical archived v1.0.0 milestone copy; the link and temporary directory were removed after each run, preserving the user's deletion exactly. + +## Verification + +- `node tests/providers-panel-logic.test.js` - PASS +- `node tests/model-discovery-ui.test.js` - PASS (79/79) +- `node tests/model-combobox-ui.test.js` - PASS (30/30) +- `node tests/agent-sunset-control-panel.test.js` - PASS +- Task 1 `npm test` - PASS, exit 0 +- Task 2 `npm test` with the provider contract in the root command - PASS, exit 0 +- `git diff --check 67bd8d30^..00c6ab70` - PASS +- Declared-file audit - PASS; only `control_panel.html`, `providers-panel.js`, `package.json`, and `providers-panel-logic.test.js` changed across task commits. + +## Known Stubs + +None. Phase 58-01 intentionally ships pure domain contracts and load order; visible provider rows and wiring remain owned by Plan 58-02. + +## User Setup Required + +None - no external service configuration required. + +## Next Phase Readiness + +- Ready for 58-02 to build the accessible provider roster and kind-aware settings UI on the closed helper contract. +- `extension/ai/universal-provider.js` and retired `extension/agents/*` remain untouched. + +## Self-Check: PASSED + +- Created files exist on disk. +- Task commits `67bd8d30` and `00c6ab70` exist in git history. +- Focused and full-suite claims above were rechecked against command results. +- The temporary Phase 39 test fixture is absent, preserving the pre-existing deletion. + +--- +*Phase: 58-providers-panel* +*Completed: 2026-07-12* diff --git a/.planning/phases/58-providers-panel/58-02-PLAN.md b/.planning/phases/58-providers-panel/58-02-PLAN.md new file mode 100644 index 000000000..578d57a16 --- /dev/null +++ b/.planning/phases/58-providers-panel/58-02-PLAN.md @@ -0,0 +1,234 @@ +--- +phase: 58-providers-panel +plan: "02" +type: execute +wave: 2 +depends_on: + - "01" +files_modified: + - extension/ui/control_panel.html + - extension/ui/options.css + - extension/ui/options.js + - tests/providers-panel-ui.test.js + - tests/model-discovery-ui.test.js + - tests/lattice-provider-bridge-smoke.test.js + - package.json +autonomous: true +requirements: + - PROV-01 + - PROV-02 + - PROV-03 + - PROV-04 +must_haves: + truths: + - "The control panel has one canonical Providers route while legacy #api-config bookmarks normalize to #providers." + - "Ten native radio rows express explicit api/agent kinds; selected state is user intent and participates in the existing Save/Discard flow." + - "Agent selection removes model/key/URL/format controls from rendering and keyboard flow, while API selection restores the untouched BYOK configuration." + - "Loading, saving, discarding, and switching kinds preserve modelProvider as one of the seven API ids and preserve the latent agent selection separately." + artifacts: + - path: "extension/ui/control_panel.html" + provides: "Canonical Providers route, accessible grouped provider radio roster, and separate API/agent detail containers" + contains: "id=\"providers\"" + - path: "extension/ui/options.css" + provides: "Theme-aware selected/recommended/status/detail/responsive provider styles" + contains: ".provider-row" + - path: "extension/ui/options.js" + provides: "Hash normalization and kind-aware in-form/load/save/discard behavior" + contains: "renderProviderKind" + - path: "tests/providers-panel-ui.test.js" + provides: "Static and VM contracts for markup, CSS, hashes, selection, visibility, and settings parity" + key_links: + - from: "extension/ui/options.js" + to: "extension/ui/providers-panel.js" + via: "normalizeSettings validates storage before any radio/model control is updated" + pattern: "FSBProvidersPanel.*normalizeSettings" + - from: "provider API radio" + to: "#modelProvider" + via: "API-only selection mirrors the id and reuses the existing change/discovery path" + pattern: "modelProvider" + - from: "saveSettings" + to: "extension/ai/universal-provider.js" + via: "saved modelProvider remains API-only while providerKind/agentProviderId are sibling keys" + pattern: "providerKind.*agentProviderId" +--- + + +Turn the API Configuration surface into an accessible Providers chooser with kind-aware settings that preserve BYOK behavior. + +Purpose: Land the visible route, roster, selection semantics, conditional panels, and storage migration before wiring asynchronous Phase 57 evidence. +Output: Providers markup/CSS, canonical+legacy routing, radio/load/save/discard behavior, and focused UI/compatibility tests. + + + +@$HOME/.codex/get-shit-done/workflows/execute-plan.md +@$HOME/.codex/get-shit-done/templates/summary.md + + + +@.planning/PROJECT.md +@.planning/ROADMAP.md +@.planning/STATE.md +@.planning/phases/58-providers-panel/58-CONTEXT.md +@.planning/phases/58-providers-panel/58-RESEARCH.md +@.planning/phases/58-providers-panel/58-VALIDATION.md +@.planning/phases/58-providers-panel/58-UI-SPEC.md +@.planning/phases/58-providers-panel/58-01-SUMMARY.md + + +The hidden compatibility select keeps exactly these values: + +```html + +``` + +In-form provider state remains separate from recommendation: + +```javascript +providerPanelState = { + providerKind: 'api', + agentProviderId: '', + recommendation: { providerKind: 'api', providerId: 'xai', reason: 'fallback' }, + clients: Object.create(null), + evidenceStatus: 'idle' +}; +``` + + + + + + + Task 1: Build the canonical Providers route and accessible static roster using existing design tokens + extension/ui/control_panel.html, extension/ui/options.css, extension/ui/options.js, tests/providers-panel-ui.test.js + +- .planning/phases/58-providers-panel/58-UI-SPEC.md +- extension/shared/fsb-ui-core.css +- extension/ui/control_panel.html +- extension/ui/options.css +- extension/ui/options.js +- tests/model-discovery-ui.test.js +- tests/agent-cap-ui.test.js +- tests/agent-sunset-control-panel.test.js + + +Rename the sidebar target/label and content section to canonical `providers` / `Providers`. Use the section description exactly from UI-SPEC: `Choose how FSB runs AI tasks. API providers use keys stored locally; agent CLIs use their existing local sign-in.` Replace the visible provider select area with one named `
` and visible legend `Choose a provider`. Add a secondary `
+ + + +
+
+
+

API provider settings

+ -
Select your preferred AI provider
-
- +
@@ -358,6 +517,88 @@

CAPTCHA Solver

+ + +
@@ -573,6 +814,43 @@

Action Change Reports

+
+
+
+ +
+
+

MCP Session Replay

+

Local, exact-fidelity history for MCP agent actions

+
+
+
+
+ +
+
+
+ MCP replay retention (days) +
+ + + +
+
+
Default 30. Range 1 to 365 days. This automatically deletes expired MCP replay sessions only; Autopilot history is never pruned by this setting.
+
+
+
+
@@ -1023,7 +1301,7 @@

- +
@@ -1796,7 +2074,7 @@

Community

- ▽0.9.90 + ▽0.9.91
@@ -1921,6 +2199,8 @@

Community

+ + +
-