Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
138 changes: 124 additions & 14 deletions scripts/ci/run_mv3_compatibility.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -17,6 +17,7 @@
import json
import os
import pathlib
import shutil
import socket
import string
import subprocess
Expand All @@ -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
Expand All @@ -43,6 +46,8 @@
"workerReply",
"workerState",
"workerStartCount",
"extensionVersion",
"storageMigration",
"dnr",
"tabs",
"windows",
Expand All @@ -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(
{
Expand All @@ -72,6 +88,9 @@
"download-not-evaluated",
}
)
EXTENSION_VERSION_EVIDENCE_VALUES = frozenset(
{INITIAL_EXTENSION_VERSION, UPDATED_EXTENSION_VERSION, "missing"}
)


class CompatibilitySurfaceError(RuntimeError):
Expand Down Expand Up @@ -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"


Expand Down Expand Up @@ -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",
Expand All @@ -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",
Expand All @@ -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",
Expand Down Expand Up @@ -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"],
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -411,13 +476,17 @@ 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"])
return {
"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",
Expand Down Expand Up @@ -458,38 +527,68 @@ 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(
chrome_bin,
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()):
Expand All @@ -498,26 +597,37 @@ 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),
}


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", ""))
Expand Down
4 changes: 4 additions & 0 deletions tests/fixtures/mv3_basic/content_script.js
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
Loading
Loading