diff --git a/CHANGELOG.md b/CHANGELOG.md index f70f24374..ddb673eb1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,8 @@ All notable changes to OriginWeave are documented in this file. The format follo - Made malformed or oversized ChromeDriver startup-port candidate records non-authoritative within the existing bounded startup wait, so later valid startup output may recover while exact pinned-build `/status` identity remains mandatory before session creation. - Treated a failed graceful ChromeDriver termination as recoverable when the bounded hard-kill fallback successfully reaps the process, while preserving unrecovered fallback failures as teardown errors. +- 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-diagnostic lane proves one controlled loopback lifecycle in pinned Chromium, not Agent bookmark authority. - 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 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. 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/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 diff --git a/docs/doctoring/mv3-compatibility.md b/docs/doctoring/mv3-compatibility.md index 18274829b..f2c34303e 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. @@ -78,6 +82,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/scripts/ci/run_mv3_compatibility.py b/scripts/ci/run_mv3_compatibility.py index 99aaaf0e9..1d68415e8 100644 --- a/scripts/ci/run_mv3_compatibility.py +++ b/scripts/ci/run_mv3_compatibility.py @@ -58,6 +58,7 @@ "commands", "sidePanel", "bookmarks", + "bookmarksDiagnostic", "history", "downloads", "downloadsDiagnostic", @@ -79,6 +80,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", + } +) class CompatibilitySurfaceError(RuntimeError): @@ -111,6 +125,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" return value if value in SURFACE_EVIDENCE_VALUES else "unexpected" @@ -283,6 +299,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: @@ -303,6 +321,7 @@ def _wait_for_extension_evidence( "commands": "ready", "sidePanel": "ready", "bookmarks": "ready", + "bookmarksDiagnostic": "bookmark-complete-ready", "history": "ready", "downloads": "ready", "downloadsDiagnostic": "download-complete-ready", diff --git a/tests/fixtures/mv3_basic/content_script.js b/tests/fixtures/mv3_basic/content_script.js index b70d1a27f..352dc6393 100644 --- a/tests/fixtures/mv3_basic/content_script.js +++ b/tests/fixtures/mv3_basic/content_script.js @@ -26,6 +26,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 = diff --git a/tests/fixtures/mv3_basic/service_worker.js b/tests/fixtures/mv3_basic/service_worker.js index a8cec9575..d20e40168 100644 --- a/tests/fixtures/mv3_basic/service_worker.js +++ b/tests/fixtures/mv3_basic/service_worker.js @@ -102,14 +102,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:" || @@ -118,40 +118,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 exerciseCoreApis(sender) { @@ -184,8 +194,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 historyItems = await chrome.history.search({ text: "", @@ -204,6 +214,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, @@ -233,6 +244,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", 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()