Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
a60875f
test(mv3): require update migration evidence
seonghobae Aug 10, 2026
39285d3
feat(mv3): expose versioned storage migration state
seonghobae Aug 10, 2026
371194f
feat(mv3): surface update migration evidence
seonghobae Aug 10, 2026
722de05
feat(mv3): prove trial-local update migration
seonghobae Aug 10, 2026
e696e19
docs(changelog): record update migration compatibility
seonghobae Aug 10, 2026
cac87d4
merge: align MV3 update migration with current prerequisite
seonghobae Aug 15, 2026
56a1507
merge: align MV3 update migration with current history prerequisite
seonghobae Aug 16, 2026
8edeef6
merge: realign MV3 update migration with cleanup-safe history prerequ…
seonghobae Aug 16, 2026
7b6a4d4
test(mv3): preserve cleanup import normalization in update lane
seonghobae Aug 16, 2026
b70dd19
chore(mv3): align update migration with live history prerequisite
seonghobae Aug 16, 2026
fd8f733
merge: align MV3 update migration with hardened history prerequisite
seonghobae Aug 16, 2026
0365553
merge(mv3): align update migration with current history prerequisite
seonghobae Aug 16, 2026
f4fce9b
fix(mv3): restore current prerequisite docs and bookmark contracts
seonghobae Aug 16, 2026
ce3dbe0
fix(mv3): preserve prerequisite bookmark diagnostics in changelog
seonghobae Aug 16, 2026
63bd2cc
fix(mv3): preserve bookmark diagnostics through update content script
seonghobae Aug 16, 2026
8a4b3f3
fix(mv3): preserve bookmark diagnostic contract through update migration
seonghobae Aug 16, 2026
538e550
fix(mv3): preserve bookmark diagnostics in update-migration runner
seonghobae Aug 16, 2026
2516f90
chore(mv3): realign update migration with current history lane
seonghobae Aug 17, 2026
bb5b216
chore(mv3): realign update migration with current history lane
seonghobae Aug 17, 2026
f897e76
docs(doctoring): preserve bookmark compatibility evidence
seonghobae Aug 17, 2026
883d624
test(mv3): bind browser diagnostic to update expectations
seonghobae Aug 17, 2026
a97d230
test(mv3): bind cleanup regression to update expectations
seonghobae Aug 17, 2026
935a1b7
test(mv3): preserve cleanup semantics with update evidence
seonghobae Aug 17, 2026
7868a0e
test(mv3): use path profile in browser diagnostic contract
seonghobae Aug 17, 2026
f153f61
test(mv3): use path profile in cleanup contract
seonghobae Aug 17, 2026
11b3f28
test(mv3): use path profile in session cleanup contract
seonghobae Aug 17, 2026
2c1ec3a
fix(stack): preserve driver authority across update migration
seonghobae Aug 19, 2026
a39e1c1
chore(mv3): realign update migration with live history lifecycle
seonghobae Aug 20, 2026
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 @@ -31,6 +31,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 os
import pathlib
import queue
import shutil
import string
import subprocess
import tempfile
Expand All @@ -34,6 +35,8 @@
PINNED_CHROMEDRIVER_RELATIVE_PATH = pathlib.PurePosixPath(
".mv3-browser/chromedriver-linux64/chromedriver"
)
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 @@ -50,6 +53,8 @@
"workerReply",
"workerState",
"workerStartCount",
"extensionVersion",
"storageMigration",
"dnr",
"tabs",
"windows",
Expand All @@ -64,7 +69,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 Down Expand Up @@ -93,6 +109,9 @@
"bookmark-not-evaluated",
}
)
EXTENSION_VERSION_EVIDENCE_VALUES = frozenset(
{INITIAL_EXTENSION_VERSION, UPDATED_EXTENSION_VERSION, "missing"}
)


class CompatibilitySurfaceError(RuntimeError):
Expand Down Expand Up @@ -127,6 +146,8 @@ def _safe_surface_value(key: str, value: str) -> str:
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"


Expand Down Expand Up @@ -275,11 +296,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 @@ -290,6 +320,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 @@ -313,6 +347,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 @@ -384,6 +420,32 @@ 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 _teardown_driver_process(driver: subprocess.Popen[bytes]) -> Exception | None:
"""Best-effort reap ChromeDriver while preserving unrecovered process failures."""

Expand Down Expand Up @@ -518,13 +580,16 @@ 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."""

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, driver_port = _start_chromedriver(chromedriver_bin)
primary_error: BaseException | None = None
Expand All @@ -549,8 +614,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 @@ -587,13 +652,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 @@ -660,38 +729,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 @@ -700,18 +799,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),
Expand Down Expand Up @@ -766,7 +876,7 @@ def _pinned_workspace_binary(


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 = _pinned_workspace_binary(
"CHROME_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