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 @@ -28,6 +28,7 @@ All notable changes to OriginWeave are documented in this file. The format follo
- Authoritative product documentation graph spanning PRD, TRD, ADR lifecycle/index, product-wide UML, conceptual ERD, requirement/decision traceability, threat modeling, product-wide test strategy, operability, API/protocol, release/rollback, and current primary-source standards doctoring, with machine-checkable repository contracts that keep conversation-derived future work distinct from protected-main implementation claims.
- 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.

### Changed

Expand Down
64 changes: 58 additions & 6 deletions tests/fixtures/mv3_basic/service_worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,62 @@ async function exerciseBookmarkMutation(sender) {
return bookmarkMutationReady;
}

async function exerciseHistoryMutation(sender) {
const sourceUrl = sender?.tab?.url;
if (typeof sourceUrl !== "string") {
return false;
}

let parsed;
try {
parsed = new URL(sourceUrl);
} catch (_error) {
return false;
}
if (
parsed.protocol !== "http:" ||
parsed.hostname !== "127.0.0.1" ||
parsed.pathname !== "/page.html" ||
parsed.username !== "" ||
parsed.password !== ""
) {
return false;
}

const historyUrl = new URL("history-entry.html", sourceUrl).href;
let historyMutationReady = false;
try {
await chrome.history.addUrl({ url: historyUrl });
const items = await chrome.history.search({
text: historyUrl,
startTime: 0,
maxResults: 10,
});
historyMutationReady =
Array.isArray(items) && items.some((item) => item?.url === historyUrl);
} catch (_error) {
historyMutationReady = false;
} finally {
try {
await chrome.history.deleteUrl({ url: historyUrl });
const remainingItems = await chrome.history.search({
text: historyUrl,
startTime: 0,
maxResults: 10,
});
if (
!Array.isArray(remainingItems) ||
remainingItems.some((item) => item?.url === historyUrl)
) {
historyMutationReady = false;
}
} catch (_error) {
historyMutationReady = false;
}
}
return historyMutationReady;
}

async function exerciseCoreApis(sender) {
const tabId = sender?.tab?.id;
if (!Number.isInteger(tabId)) {
Expand Down Expand Up @@ -187,12 +243,8 @@ async function exerciseCoreApis(sender) {
const bookmarkMutationReady = await exerciseBookmarkMutation(sender);
const bookmarksReady = bookmarkMutationReady;

const historyItems = await chrome.history.search({
text: "",
startTime: 0,
maxResults: 10,
});
const historyReady = Array.isArray(historyItems);
const historyMutationReady = await exerciseHistoryMutation(sender);
const historyReady = historyMutationReady;

const downloadResult = await exerciseDownload(sender);
const downloadsReady = downloadResult.ready;
Expand Down
54 changes: 54 additions & 0 deletions tests/test_mv3_history_mutation_contract.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
"""Fail-first contract for bounded Manifest V3 history mutation compatibility."""

from __future__ import annotations

import json
import pathlib
import unittest

ROOT = pathlib.Path(__file__).resolve().parents[1]
FIXTURE = ROOT / "tests" / "fixtures" / "mv3_basic"


class ManifestV3HistoryMutationContractTests(unittest.TestCase):
"""Require one controlled add/read/delete history lifecycle in real Chromium."""

def test_fixture_declares_history_permission(self) -> None:
"""The controlled extension must explicitly request history authority."""

manifest = json.loads((FIXTURE / "manifest.json").read_text(encoding="utf-8"))
self.assertIn("history", manifest["permissions"])

def test_service_worker_executes_bounded_history_mutation_lifecycle(self) -> None:
"""Compatibility evidence must require add/read/delete, not search alone."""

worker = (FIXTURE / "service_worker.js").read_text(encoding="utf-8")
for expected in (
"exerciseHistoryMutation",
"chrome.history.addUrl",
"chrome.history.search",
"chrome.history.deleteUrl",
"historyMutationReady",
):
with self.subTest(expected=expected):
self.assertIn(expected, worker)

def test_history_mutation_is_bound_to_controlled_fixture_url_and_cleanup(self) -> None:
"""The fixture must not mutate arbitrary history and must remove test state."""

worker = (FIXTURE / "service_worker.js").read_text(encoding="utf-8")
for expected in (
'parsed.protocol !== "http:"',
'parsed.hostname !== "127.0.0.1"',
'parsed.pathname !== "/page.html"',
"finally",
"chrome.history.deleteUrl",
):
with self.subTest(expected=expected):
self.assertIn(expected, worker)
self.assertNotIn("_error.message", worker)
self.assertNotIn("String(_error)", worker)


if __name__ == "__main__":
unittest.main()
Loading