From a60875f70f8412db27ff1025b75d7ad4b8ddc38e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 11 Aug 2026 06:19:12 +0900 Subject: [PATCH 01/18] test(mv3): require update migration evidence --- tests/test_mv3_update_migration_contract.py | 60 +++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 tests/test_mv3_update_migration_contract.py diff --git a/tests/test_mv3_update_migration_contract.py b/tests/test_mv3_update_migration_contract.py new file mode 100644 index 000000000..94600dcb7 --- /dev/null +++ b/tests/test_mv3_update_migration_contract.py @@ -0,0 +1,60 @@ +"""Fail-first contract for pinned-Chromium extension update migration evidence.""" + +from __future__ import annotations + +import pathlib +import unittest + +ROOT = pathlib.Path(__file__).resolve().parents[1] +RUNNER = ROOT / "scripts" / "ci" / "run_mv3_compatibility.py" +FIXTURE = ROOT / "tests" / "fixtures" / "mv3_basic" + + +class ManifestV3UpdateMigrationContractTests(unittest.TestCase): + """Require a real restart across a controlled unpacked-extension version update.""" + + def test_runner_uses_trial_local_extension_copy_and_version_update(self) -> None: + """Update evidence must not rewrite the checked-in fixture or reuse global state.""" + + runner = RUNNER.read_text(encoding="utf-8") + for expected in ( + "shutil.copytree", + "extension_dir", + "INITIAL_EXTENSION_VERSION", + "UPDATED_EXTENSION_VERSION", + "_set_fixture_version", + "update-migration", + ): + with self.subTest(expected=expected): + self.assertIn(expected, runner) + + def test_service_worker_migrates_versioned_storage_state(self) -> None: + """The fixture must expose deterministic version-state migration, not update inference.""" + + worker = (FIXTURE / "service_worker.js").read_text(encoding="utf-8") + content = (FIXTURE / "content_script.js").read_text(encoding="utf-8") + for expected in ( + "chrome.runtime.getManifest().version", + "originweave_fixture_schema_version", + "storageMigration", + ): + with self.subTest(expected=expected): + self.assertIn(expected, worker) + self.assertIn("originweaveStorageMigration", content) + + def test_runner_requires_updated_version_and_migrated_state(self) -> None: + """A restart alone must not satisfy update/version-migration compatibility.""" + + runner = RUNNER.read_text(encoding="utf-8") + for expected in ( + '"extension_version"', + '"storage_migration"', + '"update-migration":', + "UPDATED_EXTENSION_VERSION", + ): + with self.subTest(expected=expected): + self.assertIn(expected, runner) + + +if __name__ == "__main__": + unittest.main() From 39285d33e40fa5f20ad431da5b1203ddcca8051d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 11 Aug 2026 06:24:20 +0900 Subject: [PATCH 02/18] feat(mv3): expose versioned storage migration state --- tests/fixtures/mv3_basic/service_worker.js | 57 ++++++++++++++++++++-- 1 file changed, 54 insertions(+), 3 deletions(-) diff --git a/tests/fixtures/mv3_basic/service_worker.js b/tests/fixtures/mv3_basic/service_worker.js index 3b61c3d5c..ecaaa1bf4 100644 --- a/tests/fixtures/mv3_basic/service_worker.js +++ b/tests/fixtures/mv3_basic/service_worker.js @@ -3,6 +3,10 @@ const DOWNLOAD_PAYLOAD = "OriginWeave deterministic MV3 download fixture.\n"; const DOWNLOAD_POLL_ATTEMPTS = 100; const DOWNLOAD_POLL_INTERVAL_MS = 50; +const INITIAL_FIXTURE_VERSION = "1.0.0"; +const UPDATED_FIXTURE_VERSION = "1.0.1"; +const INITIAL_SCHEMA_VERSION = 1; +const UPDATED_SCHEMA_VERSION = 2; const workerStartPromise = (async () => { const values = await chrome.storage.local.get("originweave_worker_start_count"); @@ -20,6 +24,39 @@ async function ensureWorkerState() { return "installed"; } +async function ensureStorageMigrationState() { + const extensionVersion = chrome.runtime.getManifest().version; + const values = await chrome.storage.local.get("originweave_fixture_schema_version"); + const currentSchemaVersion = values.originweave_fixture_schema_version; + + if (extensionVersion === INITIAL_FIXTURE_VERSION) { + if (currentSchemaVersion === undefined) { + await chrome.storage.local.set({ + originweave_fixture_schema_version: INITIAL_SCHEMA_VERSION, + }); + return { extensionVersion, storageMigration: "initialized" }; + } + if (currentSchemaVersion === INITIAL_SCHEMA_VERSION) { + return { extensionVersion, storageMigration: "current" }; + } + return { extensionVersion, storageMigration: "invalid" }; + } + + if (extensionVersion === UPDATED_FIXTURE_VERSION) { + if (currentSchemaVersion === INITIAL_SCHEMA_VERSION) { + await chrome.storage.local.set({ + originweave_fixture_schema_version: UPDATED_SCHEMA_VERSION, + }); + return { extensionVersion, storageMigration: "migrated" }; + } + if (currentSchemaVersion === UPDATED_SCHEMA_VERSION) { + return { extensionVersion, storageMigration: "migrated" }; + } + } + + return { extensionVersion, storageMigration: "invalid" }; +} + async function waitForDownload(downloadId, expectedUrl) { const expectedBytes = new TextEncoder().encode(DOWNLOAD_PAYLOAD).byteLength; let observedDownload = false; @@ -270,15 +307,29 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { if (message !== "originweave-ping") { return false; } - Promise.all([ensureWorkerState(), workerStartPromise, exerciseCoreApis(sender)]).then( - ([worker, workerStartCount, coreApis]) => { - sendResponse({ reply: "pong", worker, workerStartCount, ...coreApis }); + Promise.all([ + ensureWorkerState(), + workerStartPromise, + ensureStorageMigrationState(), + exerciseCoreApis(sender), + ]).then( + ([worker, workerStartCount, migrationState, coreApis]) => { + sendResponse({ + reply: "pong", + worker, + workerStartCount, + extensionVersion: migrationState.extensionVersion, + storageMigration: migrationState.storageMigration, + ...coreApis, + }); }, () => { sendResponse({ reply: "pong", worker: "installed", workerStartCount: 0, + extensionVersion: "missing", + storageMigration: "invalid", tabs: "missing", windows: "missing", scripting: "missing", From 371194f8ecca6c7efbbefd04944ad6f4c952e1f7 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 11 Aug 2026 06:24:36 +0900 Subject: [PATCH 03/18] feat(mv3): surface update migration evidence --- tests/fixtures/mv3_basic/content_script.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/fixtures/mv3_basic/content_script.js b/tests/fixtures/mv3_basic/content_script.js index b70d1a27f..1056d3690 100644 --- a/tests/fixtures/mv3_basic/content_script.js +++ b/tests/fixtures/mv3_basic/content_script.js @@ -20,6 +20,10 @@ document.documentElement.dataset.originweaveWorkerStartCount = String( response?.workerStartCount ?? "missing" ); + document.documentElement.dataset.originweaveExtensionVersion = + response?.extensionVersion ?? "missing"; + document.documentElement.dataset.originweaveStorageMigration = + response?.storageMigration ?? "missing"; document.documentElement.dataset.originweaveTabs = response?.tabs ?? "missing"; document.documentElement.dataset.originweaveWindows = response?.windows ?? "missing"; document.documentElement.dataset.originweaveScripting = response?.scripting ?? "missing"; From 722de054ce8295399e8b5c5f8b908467f8ae4508 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 11 Aug 2026 06:27:13 +0900 Subject: [PATCH 04/18] feat(mv3): prove trial-local update migration --- scripts/ci/run_mv3_compatibility.py | 138 +++++++++++++++++++++++++--- 1 file changed, 124 insertions(+), 14 deletions(-) diff --git a/scripts/ci/run_mv3_compatibility.py b/scripts/ci/run_mv3_compatibility.py index 4cb3c732a..4ae24fec3 100755 --- a/scripts/ci/run_mv3_compatibility.py +++ b/scripts/ci/run_mv3_compatibility.py @@ -5,8 +5,8 @@ W3C WebDriver HTTP protocol only to prove that a real Chrome for Testing build can load the controlled MV3 fixture and repeatedly exercise service-worker, content-script, storage, declarative-net-request, tabs, windows, scripting, -commands, side-panel, bookmarks, history, downloads, real browser-click, and -restart-persistence behavior. +commands, side-panel, bookmarks, history, downloads, real browser-click, +restart-persistence, and controlled extension update-migration behavior. """ from __future__ import annotations @@ -17,6 +17,7 @@ import json import os import pathlib +import shutil import socket import string import subprocess @@ -29,6 +30,8 @@ FIXTURE = ROOT / "tests" / "fixtures" / "mv3_basic" PINNED_CHROME_VERSION = "150.0.7871.129" PINNED_CHROME_REVISION = "r1639810" +INITIAL_EXTENSION_VERSION = "1.0.0" +UPDATED_EXTENSION_VERSION = "1.0.1" REPEATABILITY_TRIALS = 3 REQUEST_TIMEOUT_SECONDS = 5.0 STARTUP_TIMEOUT_SECONDS = 20.0 @@ -43,6 +46,8 @@ "workerReply", "workerState", "workerStartCount", + "extensionVersion", + "storageMigration", "dnr", "tabs", "windows", @@ -56,7 +61,18 @@ "downloadsDiagnostic", ) SURFACE_EVIDENCE_VALUES = frozenset( - {"ready", "missing", "initialized", "persisted", "pong", "installed", "blocked"} + { + "ready", + "missing", + "initialized", + "persisted", + "current", + "migrated", + "invalid", + "pong", + "installed", + "blocked", + } ) DOWNLOAD_DIAGNOSTIC_VALUES = frozenset( { @@ -72,6 +88,9 @@ "download-not-evaluated", } ) +EXTENSION_VERSION_EVIDENCE_VALUES = frozenset( + {INITIAL_EXTENSION_VERSION, UPDATED_EXTENSION_VERSION, "missing"} +) class CompatibilitySurfaceError(RuntimeError): @@ -100,6 +119,8 @@ def _safe_surface_value(key: str, value: str) -> str: return value if value.isdecimal() and len(value) <= 20 else "invalid" if key == "downloadsDiagnostic": return value if value in DOWNLOAD_DIAGNOSTIC_VALUES else "unexpected" + if key == "extensionVersion": + return value if value in EXTENSION_VERSION_EVIDENCE_VALUES else "unexpected" return value if value in SURFACE_EVIDENCE_VALUES else "unexpected" @@ -226,11 +247,20 @@ def _wait_for_extension_evidence( driver_port: int, session_id: str, expected_storage_persistence: str, + expected_extension_version: str, + expected_storage_migration: str, ) -> dict[str, str]: """Wait until every controlled MV3 fixture surface reports its expected result.""" if expected_storage_persistence not in {"initialized", "persisted"}: raise ValueError("invalid storage persistence expectation") + if expected_extension_version not in { + INITIAL_EXTENSION_VERSION, + UPDATED_EXTENSION_VERSION, + }: + raise ValueError("invalid extension version expectation") + if expected_storage_migration not in {"initialized", "current", "migrated"}: + raise ValueError("invalid storage migration expectation") script = """ return { content: document.documentElement.dataset.originweaveContentScript || "missing", @@ -241,6 +271,10 @@ def _wait_for_extension_evidence( workerState: document.documentElement.dataset.originweaveWorkerState || "missing", workerStartCount: document.documentElement.dataset.originweaveWorkerStartCount || "missing", + extensionVersion: + document.documentElement.dataset.originweaveExtensionVersion || "missing", + storageMigration: + document.documentElement.dataset.originweaveStorageMigration || "missing", dnr: document.documentElement.dataset.originweaveDnr || "missing", tabs: document.documentElement.dataset.originweaveTabs || "missing", windows: document.documentElement.dataset.originweaveWindows || "missing", @@ -262,6 +296,8 @@ def _wait_for_extension_evidence( "storagePersistence": expected_storage_persistence, "workerReply": "pong", "workerState": "installed", + "extensionVersion": expected_extension_version, + "storageMigration": expected_storage_migration, "dnr": "blocked", "tabs": "ready", "windows": "ready", @@ -332,18 +368,47 @@ def _exercise_real_click(driver_port: int, session_id: str) -> str: return str(text) +def _set_fixture_version(extension_dir: pathlib.Path, version: str) -> None: + """Set one controlled version only inside a trial-local extension copy.""" + + if version not in {INITIAL_EXTENSION_VERSION, UPDATED_EXTENSION_VERSION}: + raise ValueError("unsupported fixture extension version") + resolved_extension_dir = extension_dir.resolve() + if resolved_extension_dir == FIXTURE.resolve(): + raise RuntimeError("refusing to mutate the checked-in MV3 fixture") + manifest_path = resolved_extension_dir / "manifest.json" + manifest = json.loads(manifest_path.read_text(encoding="utf-8")) + if not isinstance(manifest, dict): + raise RuntimeError("MV3 fixture manifest must be a JSON object") + current_version = manifest.get("version") + if current_version not in {INITIAL_EXTENSION_VERSION, UPDATED_EXTENSION_VERSION}: + raise RuntimeError("MV3 fixture manifest has an unexpected version") + if version == INITIAL_EXTENSION_VERSION and current_version != INITIAL_EXTENSION_VERSION: + raise RuntimeError("cannot rewind the trial-local extension version") + if version == UPDATED_EXTENSION_VERSION and current_version != INITIAL_EXTENSION_VERSION: + raise RuntimeError("extension update must start from the initial version") + manifest["version"] = version + manifest_path.write_text( + json.dumps(manifest, indent=2, ensure_ascii=False) + "\n", + encoding="utf-8", + ) + + def _run_browser_pass( chrome_bin: pathlib.Path, chromedriver_bin: pathlib.Path, fixture_url: str, - profile_dir: str, + profile_dir: pathlib.Path, + extension_dir: pathlib.Path, expected_storage_persistence: str, + expected_extension_version: str, + expected_storage_migration: str, ) -> dict[str, Any]: """Run one fresh browser process against a shared bounded compatibility profile.""" driver_port = _free_loopback_port() session_id: str | None = None - download_dir = pathlib.Path(profile_dir) / "downloads" + download_dir = profile_dir / "downloads" download_dir.mkdir(mode=0o700, parents=True, exist_ok=True) driver = subprocess.Popen( [str(chromedriver_bin), f"--port={driver_port}", "--allowed-ips=127.0.0.1"], @@ -372,8 +437,8 @@ def _run_browser_pass( "--disable-dev-shm-usage", "--no-sandbox", f"--user-data-dir={profile_dir}", - f"--disable-extensions-except={FIXTURE}", - f"--load-extension={FIXTURE}", + f"--disable-extensions-except={extension_dir}", + f"--load-extension={extension_dir}", ], "prefs": { "download.default_directory": str(download_dir), @@ -411,6 +476,8 @@ def _run_browser_pass( driver_port, session_id, expected_storage_persistence, + expected_extension_version, + expected_storage_migration, ) click_result = _exercise_real_click(driver_port, session_id) worker_start_count = int(surfaces["workerStartCount"]) @@ -418,6 +485,8 @@ def _run_browser_pass( "browser_version": browser_version, "worker_start_count": worker_start_count, "storage_persistence": surfaces["storagePersistence"], + "extension_version": surfaces["extensionVersion"], + "storage_migration": surfaces["storageMigration"], "surfaces": { "service-worker": surfaces["workerReply"] == "pong", "content-script": surfaces["content"] == "ready", @@ -458,17 +527,26 @@ def _run_restart_trial( fixture_url: str, trial_number: int, ) -> dict[str, Any]: - """Run one independent initial/restart pair and return credential-free evidence.""" + """Run one independent initial/restart/update-migration trial.""" trial_started = time.monotonic() with tempfile.TemporaryDirectory( prefix=f"originweave-mv3-trial-{trial_number}-" - ) as profile_dir: + ) as trial_root: + trial_dir = pathlib.Path(trial_root) + profile_dir = trial_dir / "profile" + extension_dir = trial_dir / "extension" + shutil.copytree(FIXTURE, extension_dir) + _set_fixture_version(extension_dir, INITIAL_EXTENSION_VERSION) + initial = _run_browser_pass( chrome_bin, chromedriver_bin, fixture_url, profile_dir, + extension_dir, + "initialized", + INITIAL_EXTENSION_VERSION, "initialized", ) restarted = _run_browser_pass( @@ -476,20 +554,41 @@ def _run_restart_trial( chromedriver_bin, fixture_url, profile_dir, + extension_dir, "persisted", + INITIAL_EXTENSION_VERSION, + "current", + ) + + _set_fixture_version(extension_dir, UPDATED_EXTENSION_VERSION) + updated = _run_browser_pass( + chrome_bin, + chromedriver_bin, + fixture_url, + profile_dir, + extension_dir, + "persisted", + UPDATED_EXTENSION_VERSION, + "migrated", ) initial_count = int(initial["worker_start_count"]) restarted_count = int(restarted["worker_start_count"]) + updated_count = int(updated["worker_start_count"]) surfaces = { - name: bool(initial["surfaces"][name]) and bool(restarted["surfaces"][name]) + name: bool(initial["surfaces"][name]) + and bool(restarted["surfaces"][name]) + and bool(updated["surfaces"][name]) for name in initial["surfaces"] } surfaces.update( { "restart-persistence": restarted["storage_persistence"] == "persisted", - "worker-start-count": restarted_count > initial_count, - "storage-persistence": restarted["storage_persistence"] == "persisted", + "worker-start-count": restarted_count > initial_count + and updated_count > restarted_count, + "storage-persistence": updated["storage_persistence"] == "persisted", + "update-migration": updated["extension_version"] == UPDATED_EXTENSION_VERSION + and updated["storage_migration"] == "migrated", } ) if not all(surfaces.values()): @@ -498,18 +597,29 @@ def _run_restart_trial( return { "trial_number": trial_number, "passed": True, - "browser_version": restarted["browser_version"], + "browser_version": updated["browser_version"], "surfaces": surfaces, "browser_passes": [ { "phase": "initial", "worker_start_count": initial_count, "storage_persistence": initial["storage_persistence"], + "extension_version": initial["extension_version"], + "storage_migration": initial["storage_migration"], }, { "phase": "restart", "worker_start_count": restarted_count, "storage_persistence": restarted["storage_persistence"], + "extension_version": restarted["extension_version"], + "storage_migration": restarted["storage_migration"], + }, + { + "phase": "update-migration", + "worker_start_count": updated_count, + "storage_persistence": updated["storage_persistence"], + "extension_version": updated["extension_version"], + "storage_migration": updated["storage_migration"], }, ], "duration_ms": round((time.monotonic() - trial_started) * 1000), @@ -517,7 +627,7 @@ def _run_restart_trial( def main() -> int: - """Run three independent restart trials and emit bounded repeatability evidence.""" + """Run three independent restart/update trials and emit bounded repeatability evidence.""" chrome_bin = pathlib.Path(os.environ.get("CHROME_BIN", "")) chromedriver_bin = pathlib.Path(os.environ.get("CHROMEDRIVER_BIN", "")) From e696e19c9eaf3dedb104a5de4bdbd7970abf90d4 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 11 Aug 2026 06:27:50 +0900 Subject: [PATCH 05/18] docs(changelog): record update migration compatibility --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 039b5e62c..119e20f86 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ All notable changes to OriginWeave are documented in this file. The format follo - Purpose-bound data-governance and privacy baseline that rejects both blanket masking and ambient raw-value propagation, defines field-scoped just-in-time disclosure, opaque-handle/trusted-broker boundaries, model/provider/region policy, retention/deletion/residency/break-glass controls, truthful CSAP/SOC 2 readiness language, and machine-checkable documentation contracts without inventing an OriginWeave-owned production database. - Proposed product-wide target-architecture ADRs for the Rust control plane, isolated execution modes, typed actions, semantic observation/stale-node authority, prompt-injection and secret separation, resource-governor priority, provenance evidence, browser/protocol adapters, crawler policy, and hourly automation operational closure; these remain Proposed rather than shipped claims until protected review and merge. - Pinned real-Chromium Manifest V3 compatibility evidence for a controlled history add/read/delete lifecycle confined to the ephemeral loopback fixture profile, with cleanup verification and no OriginWeave Agent history-authority claim. +- Pinned real-Chromium Manifest V3 update-migration evidence using a trial-local unpacked-extension copy, a controlled `1.0.0` to `1.0.1` transition, persisted profile state, and explicit schema migration without modifying the checked-in fixture or granting Agent authority. ### Changed From 7b6a4d49c5d8fbc2436cd6612aed6cc06233877a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 16 Aug 2026 22:32:08 +0900 Subject: [PATCH 06/18] test(mv3): preserve cleanup import normalization in update lane --- tests/test_mv3_session_cleanup_exception_contract.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/test_mv3_session_cleanup_exception_contract.py b/tests/test_mv3_session_cleanup_exception_contract.py index 8f3171712..2edebaad8 100644 --- a/tests/test_mv3_session_cleanup_exception_contract.py +++ b/tests/test_mv3_session_cleanup_exception_contract.py @@ -6,7 +6,7 @@ import runpy import tempfile import unittest -from unittest import mock +import unittest.mock ROOT = pathlib.Path(__file__).resolve().parents[1] RUNNER = ROOT / "scripts" / "ci" / "run_mv3_compatibility.py" @@ -103,8 +103,10 @@ def fake_json_request( extension_dir = trial_dir / "extension" extension_dir.mkdir() with ( - mock.patch.object(globals_["subprocess"], "Popen", return_value=fake_driver), - mock.patch.dict( + unittest.mock.patch.object( + globals_["subprocess"], "Popen", return_value=fake_driver + ), + unittest.mock.patch.dict( globals_, { "_free_loopback_port": lambda: 43123, From f4fce9b11324b755dfd2d820d54e301a04234f90 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 17 Aug 2026 04:42:41 +0900 Subject: [PATCH 07/18] fix(mv3): restore current prerequisite docs and bookmark contracts Preserve the current history-lifecycle prerequisite's canonical documentation, traceability, and bookmark diagnostic regression contracts on the update-migration descendant. This removes stale child-tree deletions without transferring predecessor evidence. --- docs/DOCUMENTATION_FITNESS.md | 2 +- docs/PRD.md | 4 +- docs/TRD.md | 2 +- docs/doctoring.md | 6 + docs/doctoring/mv3-compatibility.md | 8 +- .../evidence/2026-08-10-active-pr-maturity.md | 2 +- docs/traceability/README.md | 2 +- tests/test_mv3_bookmark_mutation_contract.py | 121 ++++++++++++++++++ 8 files changed, 140 insertions(+), 7 deletions(-) diff --git a/docs/DOCUMENTATION_FITNESS.md b/docs/DOCUMENTATION_FITNESS.md index 69f603252..11b5d5dd7 100644 --- a/docs/DOCUMENTATION_FITNESS.md +++ b/docs/DOCUMENTATION_FITNESS.md @@ -55,7 +55,7 @@ Protected main already proves a pinned-Chromium baseline for service worker, con - #43: controlled downloads; - #49: per-trial ephemeral profile isolation; -- #56: bookmark create/read/delete cleanup; +- #56: bookmark create/read/delete cleanup with allow-listed stage diagnostics; - #59: history add/read/delete/absence verification; - #60: trial-local unpacked-extension `1.0.0` → `1.0.1` update with explicit schema migration; and - #61: real content-script isolated-world evidence in which the page main world retains a `page` sentinel while the content script independently retains an `extension` sentinel. diff --git a/docs/PRD.md b/docs/PRD.md index 40539a28f..4b409f2dd 100644 --- a/docs/PRD.md +++ b/docs/PRD.md @@ -183,7 +183,7 @@ public-crawl purpose | ID | Requirement | Status | Implementation evidence / note | |---|---|---|---| | PRD-COMP-001 | Chromium is the compatibility kernel; OriginWeave does not reimplement Blink or V8 | Accepted architecture | ADR 0001 | -| PRD-COMP-002 | Maintain a Manifest V3 compatibility matrix and representative extension test farm | Planned | Partial protected-main pinned-Chromium evidence covers service worker, content script, storage, DNR, tabs, windows, scripting, commands, side panel, bookmarks, history, restart and repeatability; active PR #43 adds bounded real downloads evidence; issue #27 still owns the complete matrix/release acceptance | +| PRD-COMP-002 | Maintain a Manifest V3 compatibility matrix and representative extension test farm | Planned | Partial protected-main pinned-Chromium evidence covers service worker, content script, storage, DNR, tabs, windows, scripting, commands, side panel, bookmarks, history, restart and repeatability; active PR #43 adds bounded real downloads evidence; active PR #56 adds bounded bookmark mutation with allow-listed stage diagnostics; issue #27 still owns the complete matrix/release acceptance | | PRD-COMP-003 | Chromium-specific integrations remain behind versioned adapters | Planned | Adapter strategy ADR 0107 | | PRD-COMP-004 | Headless runtime remains independently usable without the interactive browser UI | Planned | Modular architecture target | @@ -267,7 +267,7 @@ public-crawl purpose | PRD-EXT-001 | Manifest V3 remains the extension compatibility baseline | Accepted architecture | Official Chrome platform baseline; real pinned-Chromium evidence exists on protected main | | PRD-EXT-002 | Upstream extension APIs are preserved where possible | Accepted architecture | Chromium-kernel strategy; current protected-main compatibility lane exercises multiple real MV3 APIs | | PRD-EXT-003 | Extension access to agent authority requires separate signed policy grant | Planned | Protected-main extension authority foundation exists, but the complete managed-extension/native-messaging/enterprise runtime contract remains open under issue #27; Proposed ADR 0013 does not itself make this shipped | -| PRD-EXT-004 | Compatibility tests cover install/update, worker lifecycle, scripts, storage, DNR, messaging, download, side panel and isolation | Planned | Protected-main suite already covers worker/content/storage/DNR/tabs/windows/scripting/commands/side panel/bookmarks/history/restart/repeatability; active PR #43 adds downloads; install/update/native messaging/enterprise isolation and release-wide matrix remain open under issue #27 | +| PRD-EXT-004 | Compatibility tests cover install/update, worker lifecycle, scripts, storage, DNR, messaging, download, side panel and isolation | Planned | Protected-main suite already covers worker/content/storage/DNR/tabs/windows/scripting/commands/side panel/bookmarks/history/restart/repeatability; active PR #43 adds downloads; active PR #56 adds bookmark create/get/remove diagnostics; install/update/native messaging/enterprise isolation and release-wide matrix remain open under issue #27 | ### 9.10 Crawler and capture policy diff --git a/docs/TRD.md b/docs/TRD.md index 3e8030012..bb5292833 100644 --- a/docs/TRD.md +++ b/docs/TRD.md @@ -339,7 +339,7 @@ Generic network evidence retains bounded names and canonical locators while valu The complete compatibility program is **Planned** under issue #27, while partial real-browser evidence exists on protected main. OriginWeave preserves Chromium's extension implementation rather than rebuilding Chrome APIs in Rust. Agent authority remains separate from ordinary extension permissions. Proposed ADR 0013 documents this separation but is not Accepted design authority until reviewed/integrated accordingly. -Protected-main pinned-Chromium evidence currently exercises service-worker lifecycle, content scripts, storage, declarativeNetRequest, tabs, windows, scripting, commands, side panel, bookmarks, history, restart persistence and repeatability. Active PR #43 adds a bounded real `chrome.downloads` path and allowlisted download-stage failure evidence. Installation/update, native messaging, managed-extension/enterprise policy, broader isolation, Web Store and release-wide compatibility remain outside the current protected-main claim. +Protected-main pinned-Chromium evidence currently exercises service-worker lifecycle, content scripts, storage, declarativeNetRequest, tabs, windows, scripting, commands, side panel, bookmarks, history, restart persistence and repeatability. Active PR #43 adds a bounded real `chrome.downloads` path and allowlisted download-stage failure evidence. Active PR #56 adds a bounded `chrome.bookmarks` create/get/remove lifecycle with allow-listed stage diagnostics. Installation/update, native messaging, managed-extension/enterprise policy, broader isolation, Web Store and release-wide compatibility remain outside the current protected-main claim. ## 14. Prompt-injection and model boundary diff --git a/docs/doctoring.md b/docs/doctoring.md index f6b4ede10..ad91de059 100644 --- a/docs/doctoring.md +++ b/docs/doctoring.md @@ -12,6 +12,10 @@ The 1 June 2026 WebDriver BiDi Working Draft defines a bidirectional remote-cont The current Chrome Extensions Downloads API documents the `downloads` manifest permission and `chrome.downloads` methods that initiate, monitor, search, and inspect downloads. That living vendor reference is API semantics only. OriginWeave treats a successful controlled loopback download in pinned Chromium as compatibility evidence for one declared surface, not as Agent filesystem authority, general download persistence, or a claim that every Downloads method is supported. +### Manifest V3 bookmarks compatibility + +The current Chrome Extensions Bookmarks API documents the `bookmarks` manifest permission and Promise-returning `chrome.bookmarks.create`, `chrome.bookmarks.get`, and `chrome.bookmarks.remove` methods. Bookmark node identifiers are strings unique within one browser profile. That living vendor reference is API semantics only. OriginWeave treats one controlled loopback create → get → remove lifecycle plus allow-listed stage diagnostics as compatibility evidence, not as Agent bookmark capability or ambient human-profile bookmark authority. + ### Manifest V3 WebDriver transport-protocol diagnostics RFC 9112 defines the HTTP/1.1 status-line and the requirement that a message body match the announced framing. A malformed status-line or an incomplete body is a recoverable parser failure, not a trusted diagnostic payload. W3C WebDriver carries commands over that HTTP transport. The Manifest V3 compatibility runner therefore converts `http.client.HTTPException` subclasses such as `BadStatusLine` and `IncompleteRead` into the classified message `WebDriver transport protocol failure`. Raw status-line text, partial body bytes, paths, URLs, or tokens must not enter exception text, trial evidence, or logs. @@ -110,6 +114,8 @@ Autio, C., Schwartz, R., Dunietz, J., Jain, S., Stanley, M., Tabassi, E., Hall, Bonica, R., Cotton, M., Haberman, B., & Vegoda, L. (2017). *Updates to the special-purpose IP address registries* (RFC 8190). Internet Engineering Task Force. https://doi.org/10.17487/RFC8190 +Chrome for Developers. (n.d.). *chrome.bookmarks*. Google. Retrieved August 16, 2026, from https://developer.chrome.com/docs/extensions/reference/api/bookmarks + Chrome for Developers. (n.d.). *chrome.downloads*. Google. Retrieved August 16, 2026, from https://developer.chrome.com/docs/extensions/reference/api/downloads Chromium Authors. (n.d.). *Proxy support in Chrome* [Source documentation]. Chromium. https://chromium.googlesource.com/chromium/src/+/a3e71ebfa307d8760eb68b777e2998a869940092/net/docs/proxy.md diff --git a/docs/doctoring/mv3-compatibility.md b/docs/doctoring/mv3-compatibility.md index 6dc9c4d05..3a4f058ea 100644 --- a/docs/doctoring/mv3-compatibility.md +++ b/docs/doctoring/mv3-compatibility.md @@ -22,7 +22,7 @@ This matrix separates protected-main executable evidence from active, non-shippe | `declarativeNetRequest` | **PROTECTED_MAIN** | Controlled local rule blocks its fixture request in pinned Chromium. | No claim for every DNR rule/action combination. | | `tabs`, `windows`, `scripting`, `commands`, `sidePanel` | **PROTECTED_MAIN** | Each declared API is exercised in real Chromium and required by the repeatability gate. | Chrome API permission does not become Agent capability. | | Bookmarks read compatibility | **PROTECTED_MAIN** | Protected-main fixture exercises the declared bookmarks surface. | Ambient human-profile bookmark authority is not granted. | -| Bookmarks create/read/delete lifecycle | **ACTIVE_PR #56** | Controlled synthetic bookmark is created, read back, and removed in the ephemeral compatibility profile. | Compatibility only; no Agent bookmark capability. | +| Bookmarks create/read/delete lifecycle | **ACTIVE_PR #56** | Controlled synthetic bookmark is created, read back, and removed in the ephemeral compatibility profile, with allow-listed stage diagnostics. | Compatibility only; no Agent bookmark capability. | | History read compatibility | **PROTECTED_MAIN** | Protected-main fixture exercises bounded history search in the isolated profile. | No model-visible browsing-history content or default-profile access. | | History add/read/delete lifecycle | **ACTIVE_PR #59** | Controlled synthetic loopback visit is added, exactly read back, deleted in `finally`, and required to be absent afterward. | Compatibility only; no Agent history capability. | | Downloads | **ACTIVE_PR #43** | Controlled loopback payload is downloaded and validated through pinned Chromium. | No general download persistence, unsafe filename, or Agent filesystem authority claim. | @@ -34,6 +34,10 @@ This matrix separates protected-main executable evidence from active, non-shippe The release-quality capability matrix must remain coupled to executable evidence. Adding a row to documentation never creates support; declaring a new supported capability must first add a realistic regression test and pinned-Chromium proof. Conversely, if a declared protected-main capability regresses, the release gate must fail rather than silently downgrading the matrix. +## Bookmarks API primary evidence + +For bookmark compatibility specifically, the current official Chrome Extensions API documents the `bookmarks` manifest permission and Promise-returning `chrome.bookmarks.create`, `chrome.bookmarks.get`, and `chrome.bookmarks.remove` methods. Bookmark node identifiers are strings unique within one browser profile. This living vendor reference establishes API semantics only. Active PR #56 exercises one controlled loopback create → get → remove lifecycle through pinned Chromium and retains only allow-listed stage diagnostics. That proof is not Agent bookmark capability, ambient human-profile bookmark authority, or a release claim that every `chrome.bookmarks` method works. + ## History API primary evidence For history compatibility specifically, the current official Chrome Extensions API documents the `history` manifest permission and Promise-returning `chrome.history.addUrl`, `chrome.history.search`, and `chrome.history.deleteUrl` methods. This living vendor reference establishes API semantics only. OriginWeave release evidence continues to depend on the exact pinned Chromium fixture and exact-head CI result rather than inferring compatibility from documentation. @@ -72,6 +76,8 @@ Chrome for Developers. (2023, May 2). *The extension service worker lifecycle*. Chrome for Developers. (n.d.). *chrome.declarativeNetRequest*. Google. Retrieved August 9, 2026, from https://developer.chrome.com/docs/extensions/reference/api/declarativeNetRequest +Chrome for Developers. (n.d.). *chrome.bookmarks*. Google. Retrieved August 16, 2026, from https://developer.chrome.com/docs/extensions/reference/api/bookmarks + Chrome for Developers. (n.d.). *chrome.downloads*. Google. Retrieved August 16, 2026, from https://developer.chrome.com/docs/extensions/reference/api/downloads Chrome for Developers. (n.d.). *chrome.history*. Google. Retrieved August 11, 2026, from https://developer.chrome.com/docs/extensions/reference/api/history diff --git a/docs/evidence/2026-08-10-active-pr-maturity.md b/docs/evidence/2026-08-10-active-pr-maturity.md index 71353dca2..e9d3868f4 100644 --- a/docs/evidence/2026-08-10-active-pr-maturity.md +++ b/docs/evidence/2026-08-10-active-pr-maturity.md @@ -31,7 +31,7 @@ This dated appendix records volatile implementation evidence that must not be em | #53 | Authoritative in-process sensitive-handle revocation state | **IMPLEMENTED_ON_ACTIVE_PR** | Draft stacked on #46 at exact head `86ce4bc1c11c270dc532593d673c42bd6f623d74`; CI and CodeRabbit are green. It adds typed first-revocation-wins state but no durable broker, cross-process transactionality, protected-value resolution, KMS, or persistence. | | #54 | Recheck resolution freshness at socket use | **IMPLEMENTED_ON_ACTIVE_PR** | Draft stacked on #50 at exact head `ec81031c537f2b662910c1ce78c7ae0e0bfc9c1e`; CI and CodeRabbit are green. `connect_at` revalidates freshness immediately before socket I/O and the compatibility path derives elapsed monotonic time; no resolver, DNS lookup, proxy/PAC or wall-clock authority is added. | | #55 | Bind opaque sensitive-value handle use to a non-transferable audience | **IMPLEMENTED_ON_ACTIVE_PR** | Draft stacked on exact #53 head `86ce4bc1c11c270dc532593d673c42bd6f623d74`. Test-only head `95f0f1e418024f5dbe7aa613e5fd1e9d88a9417a` and CI run `31419991170` proved a real regression: audience binding had caused a revoked handle with later mismatched policy state to return `ScopeMismatch` instead of authoritative `Revoked`. Current exact head `8d3ccf0a3b99fd9789210dd9798b422431fab7d8` restores revocation precedence, retains audience binding, and adds a synchronized one-use concurrency regression. CI run `31421061134` passes repository contracts, rustfmt, locked workspace check, all workspace tests, strict Clippy, rustdoc and exact owned production function/line/region/branch coverage; CodeRabbit exact-head status is success. A future trusted broker must still derive the audience from authenticated workload/service identity. | -| #56 | Real pinned-Chromium bookmark mutation compatibility | **IMPLEMENTED_ON_ACTIVE_PR** | Draft stacked on #43. Exact head `e1099e35ac000c7bf87ea75666cfdd928a386370` aligns the fixture and repository contracts with the bounded create → get → remove bookmark lifecycle; CI run `31427219564`, Manifest V3 Compatibility run `31427220684`, and CodeRabbit exact-head status all succeed. This is compatibility evidence only: it grants no OriginWeave Agent capability and does not complete issue #27's full extension matrix. | +| #56 | Real pinned-Chromium bookmark mutation compatibility | **IMPLEMENTED_ON_ACTIVE_PR** | Draft stacked on #43. Exact head `82403ecac1d9395871b6b05df150c47a8bbbc749` aligns the fixture and repository contracts with the bounded create → get → remove bookmark lifecycle; CI run `31959457551` includes a successful pinned-Chromium MV3 fixture job. Successor work on this lane adds allow-listed bookmark stage diagnostics so raw Chrome errors cannot enter runner evidence. This is compatibility evidence only: it grants no OriginWeave Agent capability and does not complete issue #27's full extension matrix. | | #57 | Typed semantic-node query over bounded observation evidence | **IMPLEMENTED_ON_ACTIVE_PR** | Draft stacked on exact #52 head `94fd284fe41746eeba9edc05d9753903b1c41ebf`. Test-only head `d0cd133f5be62fff99612d5b08aa4cf08ce2f29f` and CI run `31429065905` intentionally proved the missing public query boundary by failing compilation on absent `SemanticNodeQuery`/`SemanticNodeQueryError`. Current exact head `b4fa49953cbbb21c879a3340e264a6e132e41634` implements bounded exact role, accessible-name and typed-action selection against already validated `SemanticNodeObservation` values, with no CSS/XPath/raw DOM selector language, arbitrary JavaScript, browser I/O or action authority. CI run `31429995885`, Manifest V3 Compatibility run `31429997851`, and CodeRabbit exact-head status succeed. The PR remains Draft because #52/#40 are active prerequisites. | | #58 | Authority-bound semantic-node action target | **IMPLEMENTED_ON_ACTIVE_PR** | Draft stacked on #57. Current exact head `efe440c7a609cac187faacfa03a4df904a99386f` accepts only an advertised `NodeActionKind`, carries the exact OriginWeave-owned node handle, and delegates immediate-use session/context/origin/document-epoch validation to the browser authority boundary. CI run `31431277478`, Manifest V3 Compatibility run `31431277521`, and CodeRabbit exact-head status succeed. This remains descriptive execution input, not policy authorization, business-risk classification, browser I/O or action success. | | #59 | Real pinned-Chromium history mutation compatibility | **IMPLEMENTED_ON_ACTIVE_PR** | Draft stacked on #56. Test-only head `4b5f393a7420541723a07243b83cdaa7e28948de` and CI run `31432051381` established the intended repository-contract RED because controlled `history.addUrl`/`deleteUrl` lifecycle support was absent. Current exact head `b0d9c905fd7a50128eb1dde643b8a3a0f9cb1dc8` adds loopback-only add → exact readback → delete → absence verification. CI run `31432338572`, Manifest V3 Compatibility run `31432338759`, and CodeRabbit exact-head status succeed, including exact owned production function/line/region/branch coverage. Compatibility evidence only; no Agent history capability. | diff --git a/docs/traceability/README.md b/docs/traceability/README.md index e30b9eda1..edb8a04aa 100644 --- a/docs/traceability/README.md +++ b/docs/traceability/README.md @@ -69,7 +69,7 @@ ADR lifecycle is separate and remains `Proposed`, `Accepted`, `Superseded`, `Dep | Human interaction outranks inference/background collection | PARTIAL | `ARCHITECTURE.md`; PRD-RES-002 | Deterministic resource mitigation/CPU-worker admission foundations exist; platform telemetry/actuation remain Planned | | Structured observation precedes raw HTML/screenshot fallback | ACCEPTED_ARCHITECTURE | PRD-OBS-003; TRD Section 7 | Active PR #52 supplies a non-shipped bounded semantic value primitive; real browser observation and fallback adapters remain Planned | | WebDriver BiDi / CDP / WebMCP / MCP are adapters, not internal authority | ACCEPTED_ARCHITECTURE | PRD Section 9.8; TRD Section 12 | Protocol adapter implementation remains Planned/active under issue #28; active PR #40 may not be called shipped | -| Manifest V3 compatibility is preserved upstream where practical | PARTIAL | ADR 0001; issue #27; Proposed ADR 0013 | Protected main has pinned real-Chromium compatibility evidence for service worker/content script/storage/DNR/tabs/windows/scripting/commands/side panel/bookmarks/history/restart/repeatability; active PR #43 adds real bounded downloads evidence; full issue #27 matrix remains incomplete | +| Manifest V3 compatibility is preserved upstream where practical | PARTIAL | ADR 0001; issue #27; Proposed ADR 0013 | Protected main has pinned real-Chromium compatibility evidence for service worker/content script/storage/DNR/tabs/windows/scripting/commands/side panel/bookmarks/history/restart/repeatability; active PR #43 adds real bounded downloads evidence; active PR #56 adds bookmark mutation with allow-listed stage diagnostics; full issue #27 matrix remains incomplete | | Extension permission does not imply OriginWeave Agent capability | PARTIAL | protected-main extension authority kernel; Proposed ADR 0013 | Core extension-to-Agent authority isolation exists on protected main; complete managed-extension/native-messaging/enterprise release policy remains incomplete | | WARC/PROV-oriented durable evidence adapters | PLANNED | ADR 0003; PRD-EVD-005 | Source/provenance kernel foundation exists; persistence/export adapters remain Planned | | Origin Map visualizes value/action provenance | PLANNED | PRD-EVD-004; this traceability record | No shipped UI claim | diff --git a/tests/test_mv3_bookmark_mutation_contract.py b/tests/test_mv3_bookmark_mutation_contract.py index 4c121b19f..c5e41f752 100644 --- a/tests/test_mv3_bookmark_mutation_contract.py +++ b/tests/test_mv3_bookmark_mutation_contract.py @@ -2,12 +2,27 @@ from __future__ import annotations +import importlib.util import json import pathlib import unittest ROOT = pathlib.Path(__file__).resolve().parents[1] FIXTURE = ROOT / "tests" / "fixtures" / "mv3_basic" +RUNNER = ROOT / "scripts" / "ci" / "run_mv3_compatibility.py" +DOCTORING = ROOT / "docs" / "doctoring" / "mv3-compatibility.md" +ROOT_DOCTORING = ROOT / "docs" / "doctoring.md" + + +def _load_runner_module(): + """Load the compatibility runner without invoking its command-line entry point.""" + + spec = importlib.util.spec_from_file_location("originweave_mv3_runner", RUNNER) + if spec is None or spec.loader is None: + raise AssertionError("unable to load the MV3 compatibility runner") + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return module class ManifestV3BookmarkMutationContractTests(unittest.TestCase): @@ -50,6 +65,112 @@ def test_bookmark_mutation_is_bound_to_controlled_fixture_url_and_cleanup(self) self.assertNotIn("_error.message", worker) self.assertNotIn("String(_error)", worker) + def test_bookmark_failures_emit_only_bounded_stage_diagnostics(self) -> None: + """Fixture diagnostics must name a reviewed stage without retaining raw browser errors.""" + + worker = (FIXTURE / "service_worker.js").read_text(encoding="utf-8") + content = (FIXTURE / "content_script.js").read_text(encoding="utf-8") + for expected in ( + "bookmark-source-rejected", + "bookmark-create-rejected", + "bookmark-get-missing", + "bookmark-id-mismatch", + "bookmark-title-mismatch", + "bookmark-url-mismatch", + "bookmark-remove-rejected", + "bookmark-complete-ready", + "bookmarksDiagnostic", + ): + with self.subTest(expected=expected): + self.assertIn(expected, worker) + self.assertIn("originweaveBookmarksDiagnostic", content) + self.assertNotIn("created.id", worker) + self.assertNotIn("nodes[0].url", worker) + self.assertNotIn("_error.message", worker) + self.assertNotIn("String(_error)", worker) + + def test_content_script_and_runner_require_bookmark_diagnostics_on_every_pass(self) -> None: + """The compatibility report must retain a classified bookmark stage on every trial.""" + + content = (FIXTURE / "content_script.js").read_text(encoding="utf-8") + runner = RUNNER.read_text(encoding="utf-8") + self.assertIn("originweaveBookmarks", content) + self.assertIn("originweaveBookmarksDiagnostic", content) + self.assertIn('"bookmarks": surfaces["bookmarks"] == "ready"', runner) + self.assertIn('"bookmarksDiagnostic": "bookmark-complete-ready"', runner) + + def test_runner_preserves_only_reviewed_bookmark_diagnostic_tokens(self) -> None: + """Runner failure evidence must retain stage tokens while rejecting raw diagnostics.""" + + runner = _load_runner_module() + approved = { + "bookmark-source-rejected", + "bookmark-create-rejected", + "bookmark-get-missing", + "bookmark-id-mismatch", + "bookmark-title-mismatch", + "bookmark-url-mismatch", + "bookmark-remove-rejected", + "bookmark-complete-ready", + "bookmark-not-evaluated", + } + self.assertIn("bookmarksDiagnostic", runner.SURFACE_EVIDENCE_KEYS) + self.assertEqual(runner.BOOKMARK_DIAGNOSTIC_VALUES, frozenset(approved)) + for token in approved: + with self.subTest(token=token): + self.assertEqual( + runner._safe_surface_value("bookmarksDiagnostic", token), token + ) + + approved_error = runner.CompatibilitySurfaceError( + { + "bookmarks": "missing", + "bookmarksDiagnostic": "bookmark-source-rejected", + } + ) + approved_evidence = runner._failure_evidence(approved_error) + self.assertEqual( + approved_evidence["observed"]["bookmarksDiagnostic"], + "bookmark-source-rejected", + ) + + raw_bookmark_title = "OriginWeave MV3 compatibility bookmark" + raw_browser_error = "Error: secret bookmark failure" + for raw in (raw_bookmark_title, raw_browser_error): + with self.subTest(raw=raw): + error = runner.CompatibilitySurfaceError( + { + "bookmarks": "missing", + "bookmarksDiagnostic": raw, + } + ) + evidence = runner._failure_evidence(error) + self.assertEqual( + evidence["observed"]["bookmarksDiagnostic"], "unexpected" + ) + self.assertNotIn(raw, repr(evidence)) + + def test_doctoring_records_bookmarks_api_primary_citation(self) -> None: + """The living Chrome Bookmarks API reference must stay distinct from Agent authority.""" + + doctoring = DOCTORING.read_text(encoding="utf-8") + root_doctoring = ROOT_DOCTORING.read_text(encoding="utf-8") + changelog = (ROOT / "CHANGELOG.md").read_text(encoding="utf-8") + for expected in ( + "chrome.bookmarks", + "https://developer.chrome.com/docs/extensions/reference/api/bookmarks", + "allow-listed stage diagnostics", + "no Agent bookmark capability", + ): + with self.subTest(expected=expected): + self.assertIn(expected, doctoring) + self.assertIn("*chrome.bookmarks*", root_doctoring) + self.assertIn( + "https://developer.chrome.com/docs/extensions/reference/api/bookmarks", + root_doctoring, + ) + self.assertIn("chrome.bookmarks", changelog) + if __name__ == "__main__": unittest.main() From ce3dbe0668c3b97a7fa1236d106e2b0b1e2d936e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 17 Aug 2026 04:44:13 +0900 Subject: [PATCH 08/18] fix(mv3): preserve prerequisite bookmark diagnostics in changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 96ab336ca..7f36b0c3d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,8 @@ All notable changes to OriginWeave are documented in this file. The format follo ### Changed - Classified a mismatched Chrome `browserVersion` capability as an expected-only diagnostic so browser-reported capability text cannot enter Manifest V3 runner exception output. +- Classified Manifest V3 bookmark create/get/remove failures as allow-listed stage tokens so raw Chrome bookmark errors and fixture titles cannot enter runner evidence. +- Recorded the current Chrome Extensions `chrome.bookmarks` primary reference in APA 7th form and stated that the active bookmark-mutation lane proves one controlled loopback lifecycle in pinned Chromium, not Agent bookmark authority. - Classified Manifest V3 WebDriver HTTP/1.1 parser failures as a fixed transport-protocol token so a malformed status-line or incomplete message body cannot enter runner exception text. - Classified Manifest V3 real-click post-condition failures as a fixed mismatch token so page-controlled WebDriver text cannot enter runner exception text. - Recorded the current Chrome Extensions `chrome.downloads` primary reference in APA 7th form and stated that the active downloads lane proves one controlled loopback payload in pinned Chromium, not Agent filesystem authority. From 63bd2cc800557aec19fbbba985b6c4c1aa689aea Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 17 Aug 2026 04:44:33 +0900 Subject: [PATCH 09/18] fix(mv3): preserve bookmark diagnostics through update content script --- tests/fixtures/mv3_basic/content_script.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/fixtures/mv3_basic/content_script.js b/tests/fixtures/mv3_basic/content_script.js index 1056d3690..1bde75ed9 100644 --- a/tests/fixtures/mv3_basic/content_script.js +++ b/tests/fixtures/mv3_basic/content_script.js @@ -30,6 +30,8 @@ document.documentElement.dataset.originweaveCommands = response?.commands ?? "missing"; document.documentElement.dataset.originweaveSidePanel = response?.sidePanel ?? "missing"; document.documentElement.dataset.originweaveBookmarks = response?.bookmarks ?? "missing"; + document.documentElement.dataset.originweaveBookmarksDiagnostic = + response?.bookmarksDiagnostic ?? "bookmark-not-evaluated"; document.documentElement.dataset.originweaveHistory = response?.history ?? "missing"; document.documentElement.dataset.originweaveDownloads = response?.downloads ?? "missing"; document.documentElement.dataset.originweaveDownloadsDiagnostic = From 8a4b3f367c4f255b007499c5fba82cf8edfe6634 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 17 Aug 2026 04:45:02 +0900 Subject: [PATCH 10/18] fix(mv3): preserve bookmark diagnostic contract through update migration --- tests/fixtures/mv3_basic/service_worker.js | 44 ++++++++++++++-------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/tests/fixtures/mv3_basic/service_worker.js b/tests/fixtures/mv3_basic/service_worker.js index fa06d0b28..995fc6b9b 100644 --- a/tests/fixtures/mv3_basic/service_worker.js +++ b/tests/fixtures/mv3_basic/service_worker.js @@ -139,14 +139,14 @@ async function exerciseDownload(sender) { async function exerciseBookmarkMutation(sender) { const sourceUrl = sender?.tab?.url; if (typeof sourceUrl !== "string") { - return false; + return { ready: false, diagnostic: "bookmark-source-rejected" }; } let parsed; try { parsed = new URL(sourceUrl); } catch (_error) { - return false; + return { ready: false, diagnostic: "bookmark-source-rejected" }; } if ( parsed.protocol !== "http:" || @@ -155,40 +155,50 @@ async function exerciseBookmarkMutation(sender) { parsed.username !== "" || parsed.password !== "" ) { - return false; + return { ready: false, diagnostic: "bookmark-source-rejected" }; } const title = "OriginWeave MV3 compatibility bookmark"; let bookmarkId; try { const created = await chrome.bookmarks.create({ title, url: sourceUrl }); - if (typeof created?.id !== "string" || created.id.length === 0) { - return false; + const createdId = created?.id; + if (typeof createdId !== "string" || createdId.length === 0) { + return { ready: false, diagnostic: "bookmark-create-rejected" }; } - bookmarkId = created.id; + bookmarkId = createdId; } catch (_error) { - return false; + return { ready: false, diagnostic: "bookmark-create-rejected" }; } + let diagnostic = "bookmark-get-missing"; let bookmarkMutationReady = false; try { const nodes = await chrome.bookmarks.get(bookmarkId); - bookmarkMutationReady = - Array.isArray(nodes) && - nodes.length === 1 && - nodes[0]?.id === bookmarkId && - nodes[0]?.title === title && - nodes[0]?.url === sourceUrl; + if (!Array.isArray(nodes) || nodes.length !== 1) { + diagnostic = "bookmark-get-missing"; + } else if (nodes[0]?.id !== bookmarkId) { + diagnostic = "bookmark-id-mismatch"; + } else if (nodes[0]?.title !== title) { + diagnostic = "bookmark-title-mismatch"; + } else if (nodes[0]?.url !== sourceUrl) { + diagnostic = "bookmark-url-mismatch"; + } else { + diagnostic = "bookmark-complete-ready"; + bookmarkMutationReady = true; + } } catch (_error) { + diagnostic = "bookmark-get-missing"; bookmarkMutationReady = false; } finally { try { await chrome.bookmarks.remove(bookmarkId); } catch (_error) { + diagnostic = "bookmark-remove-rejected"; bookmarkMutationReady = false; } } - return bookmarkMutationReady; + return { ready: bookmarkMutationReady, diagnostic }; } async function exerciseHistoryMutation(sender) { @@ -277,8 +287,8 @@ async function exerciseCoreApis(sender) { const sidePanelOptions = await chrome.sidePanel.getOptions({ tabId }); const sidePanelReady = sidePanelOptions?.path === "side_panel.html"; - const bookmarkMutationReady = await exerciseBookmarkMutation(sender); - const bookmarksReady = bookmarkMutationReady; + const bookmarkResult = await exerciseBookmarkMutation(sender); + const bookmarksReady = bookmarkResult.ready; const historyMutationReady = await exerciseHistoryMutation(sender); const historyReady = historyMutationReady; @@ -293,6 +303,7 @@ async function exerciseCoreApis(sender) { commands: commandsReady ? "ready" : "missing", sidePanel: sidePanelReady ? "ready" : "missing", bookmarks: bookmarksReady ? "ready" : "missing", + bookmarksDiagnostic: bookmarkResult.diagnostic, history: historyReady ? "ready" : "missing", downloads: downloadsReady ? "ready" : "missing", downloadsDiagnostic: downloadResult.diagnostic, @@ -336,6 +347,7 @@ chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { commands: "missing", sidePanel: "missing", bookmarks: "missing", + bookmarksDiagnostic: "bookmark-not-evaluated", history: "missing", downloads: "missing", downloadsDiagnostic: "download-not-evaluated", From 538e550378bcb572ce85ea7c489b5320cfa54ddd Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 17 Aug 2026 04:46:48 +0900 Subject: [PATCH 11/18] fix(mv3): preserve bookmark diagnostics in update-migration runner --- scripts/ci/run_mv3_compatibility.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/scripts/ci/run_mv3_compatibility.py b/scripts/ci/run_mv3_compatibility.py index 5192dc83b..8661e430f 100644 --- a/scripts/ci/run_mv3_compatibility.py +++ b/scripts/ci/run_mv3_compatibility.py @@ -61,6 +61,7 @@ "commands", "sidePanel", "bookmarks", + "bookmarksDiagnostic", "history", "downloads", "downloadsDiagnostic", @@ -93,6 +94,19 @@ "download-not-evaluated", } ) +BOOKMARK_DIAGNOSTIC_VALUES = frozenset( + { + "bookmark-source-rejected", + "bookmark-create-rejected", + "bookmark-get-missing", + "bookmark-id-mismatch", + "bookmark-title-mismatch", + "bookmark-url-mismatch", + "bookmark-remove-rejected", + "bookmark-complete-ready", + "bookmark-not-evaluated", + } +) EXTENSION_VERSION_EVIDENCE_VALUES = frozenset( {INITIAL_EXTENSION_VERSION, UPDATED_EXTENSION_VERSION, "missing"} ) @@ -128,6 +142,8 @@ def _safe_surface_value(key: str, value: str) -> str: return value if value.isdecimal() and len(value) <= 20 else "invalid" if key == "downloadsDiagnostic": return value if value in DOWNLOAD_DIAGNOSTIC_VALUES else "unexpected" + if key == "bookmarksDiagnostic": + return value if value in BOOKMARK_DIAGNOSTIC_VALUES else "unexpected" if key == "extensionVersion": return value if value in EXTENSION_VERSION_EVIDENCE_VALUES else "unexpected" return value if value in SURFACE_EVIDENCE_VALUES else "unexpected" @@ -301,6 +317,8 @@ def _wait_for_extension_evidence( commands: document.documentElement.dataset.originweaveCommands || "missing", sidePanel: document.documentElement.dataset.originweaveSidePanel || "missing", bookmarks: document.documentElement.dataset.originweaveBookmarks || "missing", + bookmarksDiagnostic: + document.documentElement.dataset.originweaveBookmarksDiagnostic || "bookmark-not-evaluated", history: document.documentElement.dataset.originweaveHistory || "missing", downloads: document.documentElement.dataset.originweaveDownloads || "missing", downloadsDiagnostic: @@ -323,6 +341,7 @@ def _wait_for_extension_evidence( "commands": "ready", "sidePanel": "ready", "bookmarks": "ready", + "bookmarksDiagnostic": "bookmark-complete-ready", "history": "ready", "downloads": "ready", "downloadsDiagnostic": "download-complete-ready", @@ -821,4 +840,4 @@ def main() -> int: if __name__ == "__main__": - raise SystemExit(main()) + raise SystemExit(main()) \ No newline at end of file From f897e763d5b3b873cb9b9fcb7ebb261a6d15fdef Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 18 Aug 2026 02:22:13 +0900 Subject: [PATCH 12/18] docs(doctoring): preserve bookmark compatibility evidence Restore the current prerequisite's Chrome bookmarks standards/evidence text and APA reference on the update-migration child. This is dependency-stack convergence only; the child keeps its update-migration semantic delta and does not transfer predecessor checks or approvals. --- docs/doctoring.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/doctoring.md b/docs/doctoring.md index 14bbca978..04ae737a4 100644 --- a/docs/doctoring.md +++ b/docs/doctoring.md @@ -12,6 +12,10 @@ The 1 June 2026 WebDriver BiDi Working Draft defines a bidirectional remote-cont The current Chrome Extensions Downloads API documents the `downloads` manifest permission and `chrome.downloads` methods that initiate, monitor, search, and inspect downloads. That living vendor reference is API semantics only. OriginWeave treats a successful controlled loopback download in pinned Chromium as compatibility evidence for one declared surface, not as Agent filesystem authority, general download persistence, or a claim that every Downloads method is supported. +### Manifest V3 bookmarks compatibility + +The current Chrome Extensions Bookmarks API documents the `bookmarks` manifest permission and Promise-returning `chrome.bookmarks.create`, `chrome.bookmarks.get`, and `chrome.bookmarks.remove` methods. Bookmark node identifiers are strings unique within one browser profile. That living vendor reference is API semantics only. OriginWeave treats one controlled loopback create → get → remove lifecycle plus allow-listed stage diagnostics as compatibility evidence, not as Agent bookmark capability or ambient human-profile bookmark authority. + ### Manifest V3 WebDriver transport-protocol diagnostics RFC 9112 defines the HTTP/1.1 status-line and the requirement that a message body match the announced framing. A malformed status-line or an incomplete body is a recoverable parser failure, not a trusted diagnostic payload. W3C WebDriver carries commands over that HTTP transport. The Manifest V3 compatibility runner therefore converts `http.client.HTTPException` subclasses such as `BadStatusLine` and `IncompleteRead` into the classified message `WebDriver transport protocol failure`. Raw status-line text, partial body bytes, paths, URLs, or tokens must not enter exception text, trial evidence, or logs. @@ -120,6 +124,8 @@ Barth, A. (2011). *The web origin concept* (RFC 6454). Internet Engineering Task Bonica, R., Cotton, M., Haberman, B., & Vegoda, L. (2017). *Updates to the special-purpose IP address registries* (RFC 8190). Internet Engineering Task Force. https://doi.org/10.17487/RFC8190 +Chrome for Developers. (n.d.). *chrome.bookmarks*. Google. Retrieved August 16, 2026, from https://developer.chrome.com/docs/extensions/reference/api/bookmarks + Chrome for Developers. (n.d.). *chrome.downloads*. Google. Retrieved August 16, 2026, from https://developer.chrome.com/docs/extensions/reference/api/downloads Chromium Authors. (n.d.). *Proxy support in Chrome* [Source documentation]. Chromium. https://chromium.googlesource.com/chromium/src/+/a3e71ebfa307d8760eb68b777e2998a869940092/net/docs/proxy.md From 883d62462fb767c728963253c291da013447197f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 18 Aug 2026 02:33:24 +0900 Subject: [PATCH 13/18] test(mv3): bind browser diagnostic to update expectations --- tests/test_mv3_browser_version_diagnostic_contract.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/test_mv3_browser_version_diagnostic_contract.py b/tests/test_mv3_browser_version_diagnostic_contract.py index c79da5ac8..08e177257 100644 --- a/tests/test_mv3_browser_version_diagnostic_contract.py +++ b/tests/test_mv3_browser_version_diagnostic_contract.py @@ -91,6 +91,9 @@ def fake_json_request( pathlib.Path("/controlled/chromedriver"), "http://127.0.0.1:8080/page.html", profile_dir, + pathlib.Path(profile_dir) / "extension", + "initialized", + namespace["INITIAL_EXTENSION_VERSION"], "initialized", ) From a97d230813c5580c2a1efd286ddb698394415779 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 18 Aug 2026 02:33:47 +0900 Subject: [PATCH 14/18] test(mv3): bind cleanup regression to update expectations --- tests/test_mv3_primary_failure_cleanup_contract.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/test_mv3_primary_failure_cleanup_contract.py b/tests/test_mv3_primary_failure_cleanup_contract.py index 133e7159f..0f6d893c9 100644 --- a/tests/test_mv3_primary_failure_cleanup_contract.py +++ b/tests/test_mv3_primary_failure_cleanup_contract.py @@ -96,6 +96,9 @@ def fake_json_request( pathlib.Path("/controlled/chromedriver"), "http://127.0.0.1:8080/page.html", profile_dir, + pathlib.Path(profile_dir) / "extension", + "initialized", + namespace["INITIAL_EXTENSION_VERSION"], "initialized", ) From 935a1b783b1a86acb61797b5371c81830ee888b8 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 18 Aug 2026 02:34:16 +0900 Subject: [PATCH 15/18] test(mv3): preserve cleanup semantics with update evidence --- tests/test_mv3_session_cleanup_exception_contract.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tests/test_mv3_session_cleanup_exception_contract.py b/tests/test_mv3_session_cleanup_exception_contract.py index 0106438d9..e4e38d461 100644 --- a/tests/test_mv3_session_cleanup_exception_contract.py +++ b/tests/test_mv3_session_cleanup_exception_contract.py @@ -69,6 +69,8 @@ def _surfaces() -> dict[str, str]: return { "workerStartCount": "1", "storagePersistence": "initialized", + "extensionVersion": "1.0.0", + "storageMigration": "initialized", "workerReply": "pong", "content": "ready", "storage": "ready", @@ -132,7 +134,9 @@ def fake_json_request( "_wait_for_driver": lambda _port: None, "_json_request": fake_json_request, "_wait_for_extension_evidence": ( - lambda _port, _session, _expected: self._surfaces() + lambda _port, _session, _persistence, _version, _migration: ( + self._surfaces() + ) ), "_exercise_real_click": lambda _port, _session: "clicked", }, @@ -144,6 +148,9 @@ def fake_json_request( pathlib.Path("/controlled/chromedriver"), "http://127.0.0.1:8080/page.html", profile_dir, + pathlib.Path(profile_dir) / "extension", + "initialized", + namespace["INITIAL_EXTENSION_VERSION"], "initialized", ) except Exception as error: # noqa: BLE001 - return exact boundary error. From 7868a0e24744470e54d15f62a087742719550907 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 18 Aug 2026 02:38:07 +0900 Subject: [PATCH 16/18] test(mv3): use path profile in browser diagnostic contract --- tests/test_mv3_browser_version_diagnostic_contract.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_mv3_browser_version_diagnostic_contract.py b/tests/test_mv3_browser_version_diagnostic_contract.py index 08e177257..71e238877 100644 --- a/tests/test_mv3_browser_version_diagnostic_contract.py +++ b/tests/test_mv3_browser_version_diagnostic_contract.py @@ -90,7 +90,7 @@ def fake_json_request( pathlib.Path("/controlled/chrome"), pathlib.Path("/controlled/chromedriver"), "http://127.0.0.1:8080/page.html", - profile_dir, + pathlib.Path(profile_dir), pathlib.Path(profile_dir) / "extension", "initialized", namespace["INITIAL_EXTENSION_VERSION"], From f153f611e98f61a09971e4285b00409d096f6028 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 18 Aug 2026 02:38:27 +0900 Subject: [PATCH 17/18] test(mv3): use path profile in cleanup contract --- tests/test_mv3_primary_failure_cleanup_contract.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_mv3_primary_failure_cleanup_contract.py b/tests/test_mv3_primary_failure_cleanup_contract.py index 0f6d893c9..04d10fb8a 100644 --- a/tests/test_mv3_primary_failure_cleanup_contract.py +++ b/tests/test_mv3_primary_failure_cleanup_contract.py @@ -95,7 +95,7 @@ def fake_json_request( pathlib.Path("/controlled/chrome"), pathlib.Path("/controlled/chromedriver"), "http://127.0.0.1:8080/page.html", - profile_dir, + pathlib.Path(profile_dir), pathlib.Path(profile_dir) / "extension", "initialized", namespace["INITIAL_EXTENSION_VERSION"], From 11b3f28fbc2c1768799f902e1c10ff1222a3aaa9 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 18 Aug 2026 02:38:57 +0900 Subject: [PATCH 18/18] test(mv3): use path profile in session cleanup contract --- tests/test_mv3_session_cleanup_exception_contract.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_mv3_session_cleanup_exception_contract.py b/tests/test_mv3_session_cleanup_exception_contract.py index e4e38d461..88bdc84f0 100644 --- a/tests/test_mv3_session_cleanup_exception_contract.py +++ b/tests/test_mv3_session_cleanup_exception_contract.py @@ -147,7 +147,7 @@ def fake_json_request( pathlib.Path("/controlled/chrome"), pathlib.Path("/controlled/chromedriver"), "http://127.0.0.1:8080/page.html", - profile_dir, + pathlib.Path(profile_dir), pathlib.Path(profile_dir) / "extension", "initialized", namespace["INITIAL_EXTENSION_VERSION"],