From ac2328708fbfed3ee3da6581a6013b534084b4b3 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 12 Aug 2026 21:29:25 +0900 Subject: [PATCH 001/111] test(ci): require fail-closed workflow registry audit --- tests/test_workflow_registry_audit.py | 256 ++++++++++++++++++++++++++ 1 file changed, 256 insertions(+) create mode 100644 tests/test_workflow_registry_audit.py diff --git a/tests/test_workflow_registry_audit.py b/tests/test_workflow_registry_audit.py new file mode 100644 index 000000000..6d1417efd --- /dev/null +++ b/tests/test_workflow_registry_audit.py @@ -0,0 +1,256 @@ +"""Regression contracts for read-only orphaned workflow registry evidence.""" + +from __future__ import annotations + +import importlib.util +import pathlib +import unittest + +ROOT = pathlib.Path(__file__).resolve().parents[1] +SCRIPT = ROOT / "scripts/ci/audit_workflow_registry.py" +DEFAULT_SHA = "67af7c87589edc2039545af335c95064d9b8391c" +OBSERVED_AT = "2026-08-12T00:00:00Z" + + +def _load_module(): + """Load the repository utility without making scripts a Python package.""" + + spec = importlib.util.spec_from_file_location("audit_workflow_registry", SCRIPT) + if spec is None or spec.loader is None: + raise AssertionError("workflow registry audit module is not loadable") + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return module + + +def _workflow(workflow_id: int, path: str, state: str, name: str = "fixture") -> dict: + """Return one bounded GitHub Actions registry fixture record.""" + + return { + "id": workflow_id, + "name": name, + "path": path, + "state": state, + } + + +def _payload(*workflows: dict) -> dict: + """Return one complete two-page exact-protected-main audit fixture.""" + + split = max(1, len(workflows) // 2) + return { + "schema_version": 1, + "expected_default_branch_sha": DEFAULT_SHA, + "observed_default_branch_sha": DEFAULT_SHA, + "observed_at": OBSERVED_AT, + "protected_workflow_paths": [ + ".github/workflows/ci.yml", + ".github/workflows/hourly-product-development.yml", + ], + "active_pr_workflow_paths": [ + ".github/workflows/current-bounded-diagnostic.yml" + ], + "registry_pages": [ + { + "page": 1, + "status_code": 200, + "has_next": True, + "workflows": list(workflows[:split]), + }, + { + "page": 2, + "status_code": 200, + "has_next": False, + "workflows": list(workflows[split:]), + }, + ], + } + + +class WorkflowRegistryAuditTests(unittest.TestCase): + """Keep workflow-lifecycle evidence exhaustive, immutable, and non-mutating.""" + + @classmethod + def setUpClass(cls) -> None: + cls.audit = _load_module() + + def test_classifies_registry_records_by_exact_path_and_preserves_receipts(self) -> None: + """Only exact protected-tree/active-PR paths may avoid orphan classification.""" + + payload = _payload( + _workflow(1, ".github/workflows/ci.yml", "active", "CI"), + _workflow( + 2, + ".github/workflows/hourly-product-development.yml", + "active", + "Hourly Product Development", + ), + _workflow(3, ".github/workflows/format-pr1-once.yml", "active", "CI"), + _workflow(4, ".github/workflows/old-http.yml", "disabled_manually"), + _workflow(5, "dynamic/dependabot/dependabot-updates", "active"), + _workflow( + 6, + ".github/workflows/current-bounded-diagnostic.yml", + "active", + ), + ) + + evidence = self.audit.audit_workflow_registry(payload) + + self.assertEqual(evidence["schema_version"], 1) + self.assertEqual(evidence["default_branch_sha"], DEFAULT_SHA) + self.assertEqual(evidence["observed_at"], OBSERVED_AT) + self.assertFalse(evidence["mutation_performed"]) + self.assertEqual( + evidence["pagination_receipts"], + [ + { + "page": 1, + "status_code": 200, + "item_count": 3, + "has_next": True, + }, + { + "page": 2, + "status_code": 200, + "item_count": 3, + "has_next": False, + }, + ], + ) + classifications = { + record["workflow_id"]: record["classification"] + for record in evidence["workflow_records"] + } + self.assertEqual( + classifications, + { + 1: "present_repository_workflow", + 2: "present_repository_workflow", + 3: "active_orphan_repository_workflow", + 4: "disabled_orphan_repository_workflow", + 5: "github_dynamic_workflow", + 6: "active_pr_owned_workflow", + }, + ) + orphan = next( + record + for record in evidence["workflow_records"] + if record["workflow_id"] == 3 + ) + self.assertEqual(orphan["path"], ".github/workflows/format-pr1-once.yml") + self.assertEqual(orphan["state"], "active") + self.assertEqual(orphan["default_branch_sha"], DEFAULT_SHA) + self.assertEqual(orphan["observed_at"], OBSERVED_AT) + + def test_name_collision_never_protects_an_absent_workflow_path(self) -> None: + """A historical workflow named CI is not current CI without exact path ownership.""" + + evidence = self.audit.audit_workflow_registry( + _payload(_workflow(77, ".github/workflows/legacy-ci.yml", "active", "CI")) + ) + self.assertEqual( + evidence["workflow_records"][0]["classification"], + "active_orphan_repository_workflow", + ) + + def test_incomplete_or_noncontiguous_pagination_fails_closed(self) -> None: + """A truncated first page must never be treated as a complete registry inventory.""" + + for mutate in ( + lambda payload: payload["registry_pages"].pop(), + lambda payload: payload["registry_pages"][1].update(page=3), + lambda payload: payload["registry_pages"][0].update(has_next=False), + ): + with self.subTest(mutate=mutate): + payload = _payload(_workflow(1, ".github/workflows/ci.yml", "active")) + mutate(payload) + with self.assertRaises(self.audit.WorkflowAuditError): + self.audit.audit_workflow_registry(payload) + + def test_permission_and_transient_http_failures_fail_closed(self) -> None: + """403/404/5xx exports are evidence gaps, not empty successful pages.""" + + for status_code in (403, 404, 429, 500, 503): + with self.subTest(status_code=status_code): + payload = _payload(_workflow(1, ".github/workflows/ci.yml", "active")) + payload["registry_pages"][0]["status_code"] = status_code + with self.assertRaises(self.audit.WorkflowAuditError): + self.audit.audit_workflow_registry(payload) + + def test_default_branch_movement_invalidates_the_inventory(self) -> None: + """Evidence bound to a moved protected branch must be recollected before use.""" + + payload = _payload(_workflow(1, ".github/workflows/ci.yml", "active")) + payload["observed_default_branch_sha"] = "a" * 40 + with self.assertRaises(self.audit.WorkflowAuditError): + self.audit.audit_workflow_registry(payload) + + def test_malformed_case_encoded_and_traversal_paths_fail_closed(self) -> None: + """Ambiguous workflow paths must never become disable recommendations.""" + + for path in ( + ".GITHUB/workflows/ci.yml", + ".github/WORKFLOWS/ci.yml", + ".github/workflows/%2e%2e/ci.yml", + ".github/workflows/../ci.yml", + ".github\\workflows\\ci.yml", + ".github/workflows/ci.yml\u0000", + ): + with self.subTest(path=path): + payload = _payload(_workflow(1, path, "active")) + with self.assertRaises(self.audit.WorkflowAuditError): + self.audit.audit_workflow_registry(payload) + + def test_reused_or_duplicate_workflow_identifiers_fail_closed(self) -> None: + """A workflow ID reused for another path cannot be classified safely.""" + + payload = _payload( + _workflow(9, ".github/workflows/ci.yml", "active"), + _workflow(9, ".github/workflows/legacy.yml", "active"), + ) + with self.assertRaises(self.audit.WorkflowAuditError): + self.audit.audit_workflow_registry(payload) + + def test_active_pr_owned_diagnostic_is_not_reported_as_an_orphan(self) -> None: + """A bounded workflow owned by an active PR remains deferred, never disabled.""" + + evidence = self.audit.audit_workflow_registry( + _payload( + _workflow( + 12, + ".github/workflows/current-bounded-diagnostic.yml", + "active", + ) + ) + ) + record = evidence["workflow_records"][0] + self.assertEqual(record["classification"], "active_pr_owned_workflow") + self.assertFalse(record["disable_candidate"]) + + def test_only_reviewed_active_orphans_are_disable_candidates(self) -> None: + """Dynamic, disabled, present, and active-PR records remain non-candidates.""" + + evidence = self.audit.audit_workflow_registry( + _payload( + _workflow(1, ".github/workflows/ci.yml", "active"), + _workflow(2, ".github/workflows/orphan.yml", "active"), + _workflow(3, ".github/workflows/disabled.yml", "disabled_manually"), + _workflow(4, "dynamic/codeql/code-scanning", "active"), + _workflow( + 5, + ".github/workflows/current-bounded-diagnostic.yml", + "active", + ), + ) + ) + candidates = [ + record["workflow_id"] + for record in evidence["workflow_records"] + if record["disable_candidate"] + ] + self.assertEqual(candidates, [2]) + + +if __name__ == "__main__": + unittest.main() From b8ad62a784b83c5800272f338eda1de314d7e945 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 12 Aug 2026 21:33:09 +0900 Subject: [PATCH 002/111] feat(ci): classify orphaned workflow registry evidence read-only --- scripts/ci/audit_workflow_registry.py | 324 ++++++++++++++++++++++++++ 1 file changed, 324 insertions(+) create mode 100644 scripts/ci/audit_workflow_registry.py diff --git a/scripts/ci/audit_workflow_registry.py b/scripts/ci/audit_workflow_registry.py new file mode 100644 index 000000000..056ae7cd2 --- /dev/null +++ b/scripts/ci/audit_workflow_registry.py @@ -0,0 +1,324 @@ +#!/usr/bin/env python3 +"""Classify exported GitHub Actions workflow records without mutating GitHub. + +The utility consumes an operator-collected JSON document. It deliberately performs no +network request and has no workflow-disable capability. Its output binds every record +to one exact protected-branch revision, observation time, and complete pagination +receipt so a later authorized operator can review immutable workflow IDs safely. +""" + +from __future__ import annotations + +import argparse +import json +import pathlib +import re +import sys +from typing import Any + +_SCHEMA_VERSION = 1 +_MAX_INPUT_BYTES = 4 * 1024 * 1024 +_SHA_PATTERN = re.compile(r"^[0-9a-f]{40}$") +_TIMESTAMP_PATTERN = re.compile( + r"^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$" +) +_REPOSITORY_WORKFLOW_PREFIX = ".github/workflows/" +_DYNAMIC_WORKFLOW_PREFIX = "dynamic/" +_DISABLED_STATES = {"disabled_inactivity", "disabled_manually"} +_ALLOWED_STATES = {"active", *_DISABLED_STATES} + + +class WorkflowAuditError(ValueError): + """Report malformed, incomplete, stale, or ambiguous registry evidence.""" + + +def _require_mapping(value: Any, field_name: str) -> dict[str, Any]: + """Return a mapping or fail with a stable field-specific diagnostic.""" + + if not isinstance(value, dict): + raise WorkflowAuditError(f"{field_name} must be an object") + return value + + +def _require_list(value: Any, field_name: str) -> list[Any]: + """Return a list or fail with a stable field-specific diagnostic.""" + + if not isinstance(value, list): + raise WorkflowAuditError(f"{field_name} must be an array") + return value + + +def _require_nonempty_string(value: Any, field_name: str, maximum: int) -> str: + """Return one bounded nonempty string without leading or trailing whitespace.""" + + if not isinstance(value, str): + raise WorkflowAuditError(f"{field_name} must be a string") + if not value or value != value.strip() or len(value.encode("utf-8")) > maximum: + raise WorkflowAuditError(f"{field_name} is invalid") + if any(ord(character) < 0x20 or ord(character) == 0x7F for character in value): + raise WorkflowAuditError(f"{field_name} contains a control character") + return value + + +def _validate_sha(value: Any, field_name: str) -> str: + """Return one exact lowercase forty-character Git commit SHA.""" + + text = _require_nonempty_string(value, field_name, 40) + if _SHA_PATTERN.fullmatch(text) is None: + raise WorkflowAuditError(f"{field_name} must be a lowercase commit SHA") + return text + + +def _validate_observed_at(value: Any) -> str: + """Return one second-precision UTC observation timestamp.""" + + text = _require_nonempty_string(value, "observed_at", 20) + if _TIMESTAMP_PATTERN.fullmatch(text) is None: + raise WorkflowAuditError("observed_at must use YYYY-MM-DDTHH:MM:SSZ") + return text + + +def _validate_workflow_path(value: Any, field_name: str) -> str: + """Return one unambiguous GitHub workflow registry path.""" + + path = _require_nonempty_string(value, field_name, 512) + if "\\" in path or "%" in path: + raise WorkflowAuditError(f"{field_name} contains encoded or alternate separators") + segments = path.split("/") + if any(segment in {"", ".", ".."} for segment in segments): + raise WorkflowAuditError(f"{field_name} contains an ambiguous path segment") + if path.startswith(_REPOSITORY_WORKFLOW_PREFIX): + return path + if path.startswith(_DYNAMIC_WORKFLOW_PREFIX): + return path + if path.casefold().startswith(_REPOSITORY_WORKFLOW_PREFIX.casefold()): + raise WorkflowAuditError(f"{field_name} changes canonical workflow path case") + return path + + +def _validated_path_set(value: Any, field_name: str) -> set[str]: + """Return a duplicate-free set of exact repository workflow paths.""" + + paths = _require_list(value, field_name) + validated: set[str] = set() + for index, raw_path in enumerate(paths): + path = _validate_workflow_path(raw_path, f"{field_name}[{index}]") + if not path.startswith(_REPOSITORY_WORKFLOW_PREFIX): + raise WorkflowAuditError(f"{field_name}[{index}] is not a repository workflow") + if path in validated: + raise WorkflowAuditError(f"{field_name} contains a duplicate path") + validated.add(path) + return validated + + +def _validate_pages(value: Any) -> tuple[list[dict[str, Any]], list[dict[str, Any]]]: + """Return complete registry records and immutable pagination receipts.""" + + raw_pages = _require_list(value, "registry_pages") + if not raw_pages: + raise WorkflowAuditError("registry_pages must not be empty") + + workflows: list[dict[str, Any]] = [] + receipts: list[dict[str, Any]] = [] + for index, raw_page in enumerate(raw_pages): + page = _require_mapping(raw_page, f"registry_pages[{index}]") + expected_page = index + 1 + if page.get("page") != expected_page: + raise WorkflowAuditError("registry_pages must be contiguous and start at page 1") + if page.get("status_code") != 200: + raise WorkflowAuditError( + f"registry page {expected_page} did not return HTTP 200" + ) + has_next = page.get("has_next") + if not isinstance(has_next, bool): + raise WorkflowAuditError(f"registry page {expected_page} lacks has_next") + is_last = index == len(raw_pages) - 1 + if has_next == is_last: + raise WorkflowAuditError("registry pagination is truncated or contradictory") + page_workflows = _require_list( + page.get("workflows"), f"registry_pages[{index}].workflows" + ) + workflows.extend( + _require_mapping(record, f"registry_pages[{index}].workflows[{record_index}]") + for record_index, record in enumerate(page_workflows) + ) + receipts.append( + { + "page": expected_page, + "status_code": 200, + "item_count": len(page_workflows), + "has_next": has_next, + } + ) + return workflows, receipts + + +def _classify_workflow( + path: str, + state: str, + protected_paths: set[str], + active_pr_paths: set[str], +) -> str: + """Classify one registry record using exact path ownership, never display name.""" + + if path in protected_paths: + return "present_repository_workflow" + if path in active_pr_paths: + return "active_pr_owned_workflow" + if path.startswith(_REPOSITORY_WORKFLOW_PREFIX): + if state == "active": + return "active_orphan_repository_workflow" + return "disabled_orphan_repository_workflow" + if path.startswith(_DYNAMIC_WORKFLOW_PREFIX): + return "github_dynamic_workflow" + return "unresolved_workflow_record" + + +def _validate_workflow_record( + raw_record: dict[str, Any], + record_index: int, + seen_ids: set[int], + protected_paths: set[str], + active_pr_paths: set[str], + default_branch_sha: str, + observed_at: str, +) -> dict[str, Any]: + """Validate and classify one exported GitHub Actions workflow record.""" + + workflow_id = raw_record.get("id") + if isinstance(workflow_id, bool) or not isinstance(workflow_id, int): + raise WorkflowAuditError(f"workflow record {record_index} has an invalid id") + if workflow_id <= 0 or workflow_id in seen_ids: + raise WorkflowAuditError(f"workflow record {record_index} reuses an id") + seen_ids.add(workflow_id) + + name = _require_nonempty_string( + raw_record.get("name"), f"workflow record {workflow_id} name", 256 + ) + path = _validate_workflow_path( + raw_record.get("path"), f"workflow record {workflow_id} path" + ) + state = _require_nonempty_string( + raw_record.get("state"), f"workflow record {workflow_id} state", 64 + ) + if state not in _ALLOWED_STATES: + raise WorkflowAuditError(f"workflow record {workflow_id} has an unknown state") + + classification = _classify_workflow( + path, state, protected_paths, active_pr_paths + ) + return { + "workflow_id": workflow_id, + "name": name, + "path": path, + "state": state, + "classification": classification, + "disable_candidate": classification == "active_orphan_repository_workflow", + "default_branch_sha": default_branch_sha, + "observed_at": observed_at, + } + + +def audit_workflow_registry(payload: dict[str, Any]) -> dict[str, Any]: + """Return credential-free, read-only workflow lifecycle evidence. + + The expected and observed protected-branch SHAs must be identical. Registry pages + must be exhaustive and contiguous. The function never calls GitHub or mutates a + workflow; a later authorized operator must independently refetch every candidate. + """ + + document = _require_mapping(payload, "payload") + if document.get("schema_version") != _SCHEMA_VERSION: + raise WorkflowAuditError("unsupported schema_version") + + expected_sha = _validate_sha( + document.get("expected_default_branch_sha"), "expected_default_branch_sha" + ) + observed_sha = _validate_sha( + document.get("observed_default_branch_sha"), "observed_default_branch_sha" + ) + if expected_sha != observed_sha: + raise WorkflowAuditError("protected default branch moved during collection") + observed_at = _validate_observed_at(document.get("observed_at")) + + protected_paths = _validated_path_set( + document.get("protected_workflow_paths"), "protected_workflow_paths" + ) + active_pr_paths = _validated_path_set( + document.get("active_pr_workflow_paths"), "active_pr_workflow_paths" + ) + overlap = protected_paths.intersection(active_pr_paths) + if overlap: + raise WorkflowAuditError("protected and active-PR path ownership overlaps") + + raw_workflows, receipts = _validate_pages(document.get("registry_pages")) + seen_ids: set[int] = set() + records = [ + _validate_workflow_record( + raw_record, + record_index, + seen_ids, + protected_paths, + active_pr_paths, + observed_sha, + observed_at, + ) + for record_index, raw_record in enumerate(raw_workflows) + ] + records.sort(key=lambda record: record["workflow_id"]) + + return { + "schema_version": _SCHEMA_VERSION, + "default_branch_sha": observed_sha, + "observed_at": observed_at, + "mutation_performed": False, + "pagination_receipts": receipts, + "workflow_records": records, + } + + +def _read_payload(path: pathlib.Path) -> dict[str, Any]: + """Read one bounded UTF-8 JSON audit input document.""" + + if path.stat().st_size > _MAX_INPUT_BYTES: + raise WorkflowAuditError("input exceeds the four-mebibyte audit bound") + try: + parsed = json.loads(path.read_text(encoding="utf-8")) + except (OSError, UnicodeError, json.JSONDecodeError) as error: + raise WorkflowAuditError("input is not readable UTF-8 JSON") from error + return _require_mapping(parsed, "payload") + + +def main(argv: list[str] | None = None) -> int: + """Audit an exported registry document and emit canonical JSON evidence.""" + + parser = argparse.ArgumentParser( + description="Classify exported GitHub Actions workflow identities read-only." + ) + parser.add_argument("input", type=pathlib.Path, help="bounded registry export JSON") + parser.add_argument( + "--output", + type=pathlib.Path, + help="optional evidence output path; stdout is used when omitted", + ) + arguments = parser.parse_args(argv) + try: + evidence = audit_workflow_registry(_read_payload(arguments.input)) + except (OSError, WorkflowAuditError) as error: + print(f"workflow registry audit failed: {error}", file=sys.stderr) + return 1 + + serialized = json.dumps(evidence, indent=2, sort_keys=True) + "\n" + if arguments.output is None: + sys.stdout.write(serialized) + return 0 + try: + arguments.output.write_text(serialized, encoding="utf-8") + except OSError as error: + print(f"workflow registry audit failed: {error}", file=sys.stderr) + return 1 + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) From 4f3c31474eca92cfa62aaaf385a0d8e74b3b497b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 12 Aug 2026 21:37:02 +0900 Subject: [PATCH 003/111] test(ci): bind workflow inventory to reported total count --- tests/test_workflow_registry_audit.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tests/test_workflow_registry_audit.py b/tests/test_workflow_registry_audit.py index 6d1417efd..1df23d7fd 100644 --- a/tests/test_workflow_registry_audit.py +++ b/tests/test_workflow_registry_audit.py @@ -43,6 +43,7 @@ def _payload(*workflows: dict) -> dict: "expected_default_branch_sha": DEFAULT_SHA, "observed_default_branch_sha": DEFAULT_SHA, "observed_at": OBSERVED_AT, + "reported_total_count": len(workflows), "protected_workflow_paths": [ ".github/workflows/ci.yml", ".github/workflows/hourly-product-development.yml", @@ -100,6 +101,7 @@ def test_classifies_registry_records_by_exact_path_and_preserves_receipts(self) self.assertEqual(evidence["schema_version"], 1) self.assertEqual(evidence["default_branch_sha"], DEFAULT_SHA) self.assertEqual(evidence["observed_at"], OBSERVED_AT) + self.assertEqual(evidence["reported_total_count"], 6) self.assertFalse(evidence["mutation_performed"]) self.assertEqual( evidence["pagination_receipts"], @@ -168,6 +170,24 @@ def test_incomplete_or_noncontiguous_pagination_fails_closed(self) -> None: with self.assertRaises(self.audit.WorkflowAuditError): self.audit.audit_workflow_registry(payload) + def test_reported_total_count_must_match_the_complete_unique_inventory(self) -> None: + """A falsely terminated export cannot hide records behind a complete-looking page.""" + + for reported_total_count in (-1, True, 0, 2): + with self.subTest(reported_total_count=reported_total_count): + payload = _payload(_workflow(1, ".github/workflows/ci.yml", "active")) + payload["reported_total_count"] = reported_total_count + with self.assertRaises(self.audit.WorkflowAuditError): + self.audit.audit_workflow_registry(payload) + + payload = _payload( + _workflow(1, ".github/workflows/ci.yml", "active"), + _workflow(2, ".github/workflows/orphan.yml", "active"), + ) + payload["registry_pages"][1]["workflows"].clear() + with self.assertRaises(self.audit.WorkflowAuditError): + self.audit.audit_workflow_registry(payload) + def test_permission_and_transient_http_failures_fail_closed(self) -> None: """403/404/5xx exports are evidence gaps, not empty successful pages.""" From 8aa8e867c21a22fe2febf8d5e8e207e27d738513 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 12 Aug 2026 21:39:20 +0900 Subject: [PATCH 004/111] fix(ci): verify paginated workflow count against API total --- scripts/ci/audit_workflow_registry.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/scripts/ci/audit_workflow_registry.py b/scripts/ci/audit_workflow_registry.py index 056ae7cd2..bf7da976b 100644 --- a/scripts/ci/audit_workflow_registry.py +++ b/scripts/ci/audit_workflow_registry.py @@ -78,6 +78,14 @@ def _validate_observed_at(value: Any) -> str: return text +def _validate_reported_total_count(value: Any) -> int: + """Return the nonnegative total reported by the first GitHub API page.""" + + if isinstance(value, bool) or not isinstance(value, int) or value < 0: + raise WorkflowAuditError("reported_total_count must be a nonnegative integer") + return value + + def _validate_workflow_path(value: Any, field_name: str) -> str: """Return one unambiguous GitHub workflow registry path.""" @@ -240,6 +248,9 @@ def audit_workflow_registry(payload: dict[str, Any]) -> dict[str, Any]: if expected_sha != observed_sha: raise WorkflowAuditError("protected default branch moved during collection") observed_at = _validate_observed_at(document.get("observed_at")) + reported_total_count = _validate_reported_total_count( + document.get("reported_total_count") + ) protected_paths = _validated_path_set( document.get("protected_workflow_paths"), "protected_workflow_paths" @@ -252,6 +263,10 @@ def audit_workflow_registry(payload: dict[str, Any]) -> dict[str, Any]: raise WorkflowAuditError("protected and active-PR path ownership overlaps") raw_workflows, receipts = _validate_pages(document.get("registry_pages")) + if len(raw_workflows) != reported_total_count: + raise WorkflowAuditError( + "reported_total_count does not match paginated workflow records" + ) seen_ids: set[int] = set() records = [ _validate_workflow_record( @@ -271,6 +286,7 @@ def audit_workflow_registry(payload: dict[str, Any]) -> dict[str, Any]: "schema_version": _SCHEMA_VERSION, "default_branch_sha": observed_sha, "observed_at": observed_at, + "reported_total_count": reported_total_count, "mutation_performed": False, "pagination_receipts": receipts, "workflow_records": records, From d9d8b5a2125b04fe3b5299548742bdc590002364 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 12 Aug 2026 21:42:10 +0900 Subject: [PATCH 005/111] test(ci): prevent stale file-size metadata bypass --- tests/test_workflow_registry_audit.py | 38 +++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/tests/test_workflow_registry_audit.py b/tests/test_workflow_registry_audit.py index 1df23d7fd..0ec75c9b1 100644 --- a/tests/test_workflow_registry_audit.py +++ b/tests/test_workflow_registry_audit.py @@ -3,7 +3,10 @@ from __future__ import annotations import importlib.util +import io +import json import pathlib +import types import unittest ROOT = pathlib.Path(__file__).resolve().parents[1] @@ -68,6 +71,34 @@ def _payload(*workflows: dict) -> dict: } +class _GrowingAuditInput: + """Model an input that grows after a stale metadata check.""" + + def __init__(self, maximum_input_bytes: int) -> None: + self._content = json.dumps( + {"padding": "x" * maximum_input_bytes}, separators=(",", ":") + ).encode("utf-8") + + def stat(self): + """Return deliberately stale metadata claiming one byte.""" + + return types.SimpleNamespace(st_size=1) + + def read_text(self, encoding: str): + """Expose the post-check oversized content to the legacy reader.""" + + if encoding != "utf-8": + raise AssertionError("unexpected encoding") + return self._content.decode("utf-8") + + def open(self, mode: str): + """Expose the same content through a bounded binary reader.""" + + if mode != "rb": + raise AssertionError("unexpected mode") + return io.BytesIO(self._content) + + class WorkflowRegistryAuditTests(unittest.TestCase): """Keep workflow-lifecycle evidence exhaustive, immutable, and non-mutating.""" @@ -188,6 +219,13 @@ def test_reported_total_count_must_match_the_complete_unique_inventory(self) -> with self.assertRaises(self.audit.WorkflowAuditError): self.audit.audit_workflow_registry(payload) + def test_input_size_bound_applies_to_bytes_read_not_stale_metadata(self) -> None: + """A growing or replaced input cannot bypass the four-mebibyte read bound.""" + + source = _GrowingAuditInput(self.audit._MAX_INPUT_BYTES) + with self.assertRaises(self.audit.WorkflowAuditError): + self.audit._read_payload(source) + def test_permission_and_transient_http_failures_fail_closed(self) -> None: """403/404/5xx exports are evidence gaps, not empty successful pages.""" From b4608dc38e2bc15fd71e249c6387989a504cc5e7 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 12 Aug 2026 21:44:17 +0900 Subject: [PATCH 006/111] fix(ci): enforce audit input bound while reading --- scripts/ci/audit_workflow_registry.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/scripts/ci/audit_workflow_registry.py b/scripts/ci/audit_workflow_registry.py index bf7da976b..43ad36c88 100644 --- a/scripts/ci/audit_workflow_registry.py +++ b/scripts/ci/audit_workflow_registry.py @@ -294,13 +294,18 @@ def audit_workflow_registry(payload: dict[str, Any]) -> dict[str, Any]: def _read_payload(path: pathlib.Path) -> dict[str, Any]: - """Read one bounded UTF-8 JSON audit input document.""" + """Read at most four mebibytes of UTF-8 JSON without trusting stale metadata.""" - if path.stat().st_size > _MAX_INPUT_BYTES: + try: + with path.open("rb") as source: + content = source.read(_MAX_INPUT_BYTES + 1) + except OSError as error: + raise WorkflowAuditError("input is not readable UTF-8 JSON") from error + if len(content) > _MAX_INPUT_BYTES: raise WorkflowAuditError("input exceeds the four-mebibyte audit bound") try: - parsed = json.loads(path.read_text(encoding="utf-8")) - except (OSError, UnicodeError, json.JSONDecodeError) as error: + parsed = json.loads(content.decode("utf-8")) + except (UnicodeError, json.JSONDecodeError) as error: raise WorkflowAuditError("input is not readable UTF-8 JSON") from error return _require_mapping(parsed, "payload") From e462a27af6164fc31e18c98b65dbb4b981e01876 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 12 Aug 2026 23:16:51 +0900 Subject: [PATCH 007/111] test(ci): reject impossible workflow audit timestamps --- tests/test_workflow_registry_audit.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/test_workflow_registry_audit.py b/tests/test_workflow_registry_audit.py index 0ec75c9b1..fd4dd0127 100644 --- a/tests/test_workflow_registry_audit.py +++ b/tests/test_workflow_registry_audit.py @@ -244,6 +244,21 @@ def test_default_branch_movement_invalidates_the_inventory(self) -> None: with self.assertRaises(self.audit.WorkflowAuditError): self.audit.audit_workflow_registry(payload) + def test_impossible_observation_timestamps_fail_closed(self) -> None: + """Syntactically shaped but impossible UTC times are not valid audit evidence.""" + + for observed_at in ( + "2026-02-30T00:00:00Z", + "2026-08-12T24:00:00Z", + "2026-08-12T23:60:00Z", + "2026-08-12T23:59:60Z", + ): + with self.subTest(observed_at=observed_at): + payload = _payload(_workflow(1, ".github/workflows/ci.yml", "active")) + payload["observed_at"] = observed_at + with self.assertRaises(self.audit.WorkflowAuditError): + self.audit.audit_workflow_registry(payload) + def test_malformed_case_encoded_and_traversal_paths_fail_closed(self) -> None: """Ambiguous workflow paths must never become disable recommendations.""" From 65e8049cbbc51ca6b5a89199a658514e422c78d8 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 12 Aug 2026 23:22:26 +0900 Subject: [PATCH 008/111] test(ci): cover remaining workflow registry review gaps --- tests/test_workflow_registry_audit.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/test_workflow_registry_audit.py b/tests/test_workflow_registry_audit.py index fd4dd0127..62ee46228 100644 --- a/tests/test_workflow_registry_audit.py +++ b/tests/test_workflow_registry_audit.py @@ -259,6 +259,18 @@ def test_impossible_observation_timestamps_fail_closed(self) -> None: with self.assertRaises(self.audit.WorkflowAuditError): self.audit.audit_workflow_registry(payload) + def test_deleted_and_disabled_fork_states_are_inactive_non_candidates(self) -> None: + """Every documented inactive REST state remains valid but never actionable.""" + + for state in ("deleted", "disabled_fork"): + with self.subTest(state=state): + evidence = self.audit.audit_workflow_registry( + _payload(_workflow(20, ".github/workflows/legacy.yml", state)) + ) + record = evidence["workflow_records"][0] + self.assertEqual(record["classification"], "disabled_orphan_repository_workflow") + self.assertFalse(record["disable_candidate"]) + def test_malformed_case_encoded_and_traversal_paths_fail_closed(self) -> None: """Ambiguous workflow paths must never become disable recommendations.""" @@ -285,6 +297,16 @@ def test_reused_or_duplicate_workflow_identifiers_fail_closed(self) -> None: with self.assertRaises(self.audit.WorkflowAuditError): self.audit.audit_workflow_registry(payload) + def test_distinct_workflow_ids_cannot_reuse_the_same_registry_path(self) -> None: + """Duplicate paths cannot create ambiguous disable-candidate evidence.""" + + payload = _payload( + _workflow(31, ".github/workflows/orphan.yml", "active"), + _workflow(32, ".github/workflows/orphan.yml", "active"), + ) + with self.assertRaises(self.audit.WorkflowAuditError): + self.audit.audit_workflow_registry(payload) + def test_active_pr_owned_diagnostic_is_not_reported_as_an_orphan(self) -> None: """A bounded workflow owned by an active PR remains deferred, never disabled.""" From 1cd9b4fae77893c9cf2ecc95de80b69ce479911a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 13 Aug 2026 01:06:16 +0900 Subject: [PATCH 009/111] fix(ci): close workflow registry audit gaps --- scripts/ci/audit_workflow_registry.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/scripts/ci/audit_workflow_registry.py b/scripts/ci/audit_workflow_registry.py index 43ad36c88..0f0535257 100644 --- a/scripts/ci/audit_workflow_registry.py +++ b/scripts/ci/audit_workflow_registry.py @@ -10,6 +10,7 @@ from __future__ import annotations import argparse +import datetime import json import pathlib import re @@ -24,7 +25,12 @@ ) _REPOSITORY_WORKFLOW_PREFIX = ".github/workflows/" _DYNAMIC_WORKFLOW_PREFIX = "dynamic/" -_DISABLED_STATES = {"disabled_inactivity", "disabled_manually"} +_DISABLED_STATES = { + "deleted", + "disabled_fork", + "disabled_inactivity", + "disabled_manually", +} _ALLOWED_STATES = {"active", *_DISABLED_STATES} @@ -75,6 +81,12 @@ def _validate_observed_at(value: Any) -> str: text = _require_nonempty_string(value, "observed_at", 20) if _TIMESTAMP_PATTERN.fullmatch(text) is None: raise WorkflowAuditError("observed_at must use YYYY-MM-DDTHH:MM:SSZ") + try: + datetime.datetime.strptime(text, "%Y-%m-%dT%H:%M:%SZ") + except ValueError: + raise WorkflowAuditError( + "observed_at must be a valid UTC calendar timestamp" + ) from None return text @@ -186,6 +198,7 @@ def _validate_workflow_record( raw_record: dict[str, Any], record_index: int, seen_ids: set[int], + seen_paths: set[str], protected_paths: set[str], active_pr_paths: set[str], default_branch_sha: str, @@ -206,6 +219,9 @@ def _validate_workflow_record( path = _validate_workflow_path( raw_record.get("path"), f"workflow record {workflow_id} path" ) + if path in seen_paths: + raise WorkflowAuditError(f"workflow record {workflow_id} reuses a path") + seen_paths.add(path) state = _require_nonempty_string( raw_record.get("state"), f"workflow record {workflow_id} state", 64 ) @@ -268,11 +284,13 @@ def audit_workflow_registry(payload: dict[str, Any]) -> dict[str, Any]: "reported_total_count does not match paginated workflow records" ) seen_ids: set[int] = set() + seen_paths: set[str] = set() records = [ _validate_workflow_record( raw_record, record_index, seen_ids, + seen_paths, protected_paths, active_pr_paths, observed_sha, From 0c65ea948709d0cfd196047fc539a5b3ded713bd Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 13 Aug 2026 01:48:21 +0900 Subject: [PATCH 010/111] test(ci): expose disabled protected workflows --- ...flow_registry_disabled_present_contract.py | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 tests/test_workflow_registry_disabled_present_contract.py diff --git a/tests/test_workflow_registry_disabled_present_contract.py b/tests/test_workflow_registry_disabled_present_contract.py new file mode 100644 index 000000000..c8d662ce0 --- /dev/null +++ b/tests/test_workflow_registry_disabled_present_contract.py @@ -0,0 +1,76 @@ +"""Contract for surfacing disabled workflow identities that still exist on protected main.""" + +from __future__ import annotations + +import importlib.util +import pathlib +import unittest + +ROOT = pathlib.Path(__file__).resolve().parents[1] +SCRIPT = ROOT / "scripts" / "ci" / "audit_workflow_registry.py" +DEFAULT_SHA = "67af7c87589edc2039545af335c95064d9b8391c" + + +def _load_module(): + spec = importlib.util.spec_from_file_location("audit_workflow_registry", SCRIPT) + if spec is None or spec.loader is None: + raise AssertionError("workflow registry audit module is not loadable") + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return module + + +def _payload(state: str) -> dict: + return { + "schema_version": 1, + "expected_default_branch_sha": DEFAULT_SHA, + "observed_default_branch_sha": DEFAULT_SHA, + "observed_at": "2026-08-12T00:00:00Z", + "reported_total_count": 1, + "protected_workflow_paths": [".github/workflows/ci.yml"], + "active_pr_workflow_paths": [], + "registry_pages": [ + { + "page": 1, + "status_code": 200, + "has_next": False, + "workflows": [ + { + "id": 1, + "name": "CI", + "path": ".github/workflows/ci.yml", + "state": state, + } + ], + } + ], + } + + +class DisabledPresentWorkflowContractTests(unittest.TestCase): + """Require source presence and registry operational state to remain distinct evidence.""" + + @classmethod + def setUpClass(cls) -> None: + cls.audit = _load_module() + + def test_disabled_protected_workflow_is_reported_as_operational_drift(self) -> None: + """A current protected source path must not hide a disabled registry identity.""" + + for state in ( + "deleted", + "disabled_fork", + "disabled_inactivity", + "disabled_manually", + ): + with self.subTest(state=state): + evidence = self.audit.audit_workflow_registry(_payload(state)) + record = evidence["workflow_records"][0] + self.assertEqual( + record["classification"], "disabled_present_repository_workflow" + ) + self.assertFalse(record["disable_candidate"]) + + +if __name__ == "__main__": + unittest.main() From 8a5d7bae307ede5f5da54f77a963a3daafe46ffc Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 13 Aug 2026 01:52:25 +0900 Subject: [PATCH 011/111] fix(ci): surface disabled protected workflows --- scripts/ci/audit_workflow_registry.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/ci/audit_workflow_registry.py b/scripts/ci/audit_workflow_registry.py index 0f0535257..a9222560f 100644 --- a/scripts/ci/audit_workflow_registry.py +++ b/scripts/ci/audit_workflow_registry.py @@ -182,7 +182,9 @@ def _classify_workflow( """Classify one registry record using exact path ownership, never display name.""" if path in protected_paths: - return "present_repository_workflow" + if state == "active": + return "present_repository_workflow" + return "disabled_present_repository_workflow" if path in active_pr_paths: return "active_pr_owned_workflow" if path.startswith(_REPOSITORY_WORKFLOW_PREFIX): From 223e53981078e8648ecc234ead6e2c26c3f85d4d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 13 Aug 2026 05:58:51 +0900 Subject: [PATCH 012/111] test(ci): reject duplicate workflow audit JSON keys --- ...rkflow_registry_duplicate_json_contract.py | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 tests/test_workflow_registry_duplicate_json_contract.py diff --git a/tests/test_workflow_registry_duplicate_json_contract.py b/tests/test_workflow_registry_duplicate_json_contract.py new file mode 100644 index 000000000..1563add56 --- /dev/null +++ b/tests/test_workflow_registry_duplicate_json_contract.py @@ -0,0 +1,57 @@ +"""Reject ambiguous duplicate-key JSON in workflow registry audit inputs.""" + +from __future__ import annotations + +import importlib.util +import pathlib +import tempfile +import unittest + +ROOT = pathlib.Path(__file__).resolve().parents[1] +SCRIPT = ROOT / "scripts/ci/audit_workflow_registry.py" +DEFAULT_SHA = "67af7c87589edc2039545af335c95064d9b8391c" + + +def _load_module(): + """Load the read-only workflow audit utility without packaging scripts.""" + + spec = importlib.util.spec_from_file_location("audit_workflow_registry", SCRIPT) + if spec is None or spec.loader is None: + raise AssertionError("workflow registry audit module is not loadable") + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return module + + +class WorkflowRegistryDuplicateJsonContractTests(unittest.TestCase): + """Require duplicate object names to fail before semantic audit validation.""" + + @classmethod + def setUpClass(cls) -> None: + cls.audit = _load_module() + + def test_duplicate_top_level_name_fails_closed(self) -> None: + """A later duplicate JSON member must not silently replace prior evidence.""" + + document = ( + '{"schema_version":1,"schema_version":1,' + f'"expected_default_branch_sha":"{DEFAULT_SHA}",' + f'"observed_default_branch_sha":"{DEFAULT_SHA}",' + '"observed_at":"2026-08-12T00:00:00Z",' + '"reported_total_count":0,' + '"protected_workflow_paths":[],' + '"active_pr_workflow_paths":[],' + '"registry_pages":[{"page":1,"status_code":200,' + '"has_next":false,"workflows":[]}]}' + ) + with tempfile.TemporaryDirectory() as directory: + path = pathlib.Path(directory) / "registry.json" + path.write_text(document, encoding="utf-8") + with self.assertRaisesRegex( + self.audit.WorkflowAuditError, "duplicate JSON object member" + ): + self.audit._read_payload(path) + + +if __name__ == "__main__": + unittest.main() From 5f8c21949cf733362e5c763f52fa3c1309dcad6c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 13 Aug 2026 06:02:03 +0900 Subject: [PATCH 013/111] fix(ci): reject ambiguous duplicate workflow audit JSON --- scripts/ci/audit_workflow_registry.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/scripts/ci/audit_workflow_registry.py b/scripts/ci/audit_workflow_registry.py index a9222560f..45c95ec9b 100644 --- a/scripts/ci/audit_workflow_registry.py +++ b/scripts/ci/audit_workflow_registry.py @@ -313,8 +313,19 @@ def audit_workflow_registry(payload: dict[str, Any]) -> dict[str, Any]: } +def _reject_duplicate_object_members(pairs: list[tuple[str, Any]]) -> dict[str, Any]: + """Return one JSON object or fail closed when a member name is repeated.""" + + result: dict[str, Any] = {} + for name, value in pairs: + if name in result: + raise WorkflowAuditError("input contains a duplicate JSON object member") + result[name] = value + return result + + def _read_payload(path: pathlib.Path) -> dict[str, Any]: - """Read at most four mebibytes of UTF-8 JSON without trusting stale metadata.""" + """Read at most four mebibytes of unambiguous UTF-8 JSON audit evidence.""" try: with path.open("rb") as source: @@ -324,7 +335,10 @@ def _read_payload(path: pathlib.Path) -> dict[str, Any]: if len(content) > _MAX_INPUT_BYTES: raise WorkflowAuditError("input exceeds the four-mebibyte audit bound") try: - parsed = json.loads(content.decode("utf-8")) + parsed = json.loads( + content.decode("utf-8"), + object_pairs_hook=_reject_duplicate_object_members, + ) except (UnicodeError, json.JSONDecodeError) as error: raise WorkflowAuditError("input is not readable UTF-8 JSON") from error return _require_mapping(parsed, "payload") From e288a62f8b1de67f191bec235316b531d948555e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 13 Aug 2026 06:14:34 +0900 Subject: [PATCH 014/111] test(ci): reject pathological JSON nesting cleanly --- ..._workflow_registry_duplicate_json_contract.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/tests/test_workflow_registry_duplicate_json_contract.py b/tests/test_workflow_registry_duplicate_json_contract.py index 1563add56..6b171cb85 100644 --- a/tests/test_workflow_registry_duplicate_json_contract.py +++ b/tests/test_workflow_registry_duplicate_json_contract.py @@ -1,4 +1,4 @@ -"""Reject ambiguous duplicate-key JSON in workflow registry audit inputs.""" +"""Reject ambiguous or pathological JSON in workflow registry audit inputs.""" from __future__ import annotations @@ -24,7 +24,7 @@ def _load_module(): class WorkflowRegistryDuplicateJsonContractTests(unittest.TestCase): - """Require duplicate object names to fail before semantic audit validation.""" + """Require ambiguous or pathological JSON to fail as bounded audit errors.""" @classmethod def setUpClass(cls) -> None: @@ -52,6 +52,18 @@ def test_duplicate_top_level_name_fails_closed(self) -> None: ): self.audit._read_payload(path) + def test_pathological_json_nesting_fails_as_audit_error(self) -> None: + """Parser recursion exhaustion must not escape as an unbounded traceback.""" + + document = "[" * 2048 + "{}" + "]" * 2048 + with tempfile.TemporaryDirectory() as directory: + path = pathlib.Path(directory) / "registry.json" + path.write_text(document, encoding="utf-8") + with self.assertRaisesRegex( + self.audit.WorkflowAuditError, "input is not readable UTF-8 JSON" + ): + self.audit._read_payload(path) + if __name__ == "__main__": unittest.main() From 22bdcf63ab5c646c2babff2c1795f05b96cbde65 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 13 Aug 2026 07:10:12 +0900 Subject: [PATCH 015/111] test(ci): reproduce parser recursion failure deterministically --- ...rkflow_registry_duplicate_json_contract.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/tests/test_workflow_registry_duplicate_json_contract.py b/tests/test_workflow_registry_duplicate_json_contract.py index 6b171cb85..890fea2e1 100644 --- a/tests/test_workflow_registry_duplicate_json_contract.py +++ b/tests/test_workflow_registry_duplicate_json_contract.py @@ -6,9 +6,10 @@ import pathlib import tempfile import unittest +from unittest import mock ROOT = pathlib.Path(__file__).resolve().parents[1] -SCRIPT = ROOT / "scripts/ci/audit_workflow_registry.py" +SCRIPT = ROOT / "scripts" / "ci" / "audit_workflow_registry.py" DEFAULT_SHA = "67af7c87589edc2039545af335c95064d9b8391c" @@ -52,17 +53,21 @@ def test_duplicate_top_level_name_fails_closed(self) -> None: ): self.audit._read_payload(path) - def test_pathological_json_nesting_fails_as_audit_error(self) -> None: + def test_parser_recursion_exhaustion_fails_as_audit_error(self) -> None: """Parser recursion exhaustion must not escape as an unbounded traceback.""" - document = "[" * 2048 + "{}" + "]" * 2048 with tempfile.TemporaryDirectory() as directory: path = pathlib.Path(directory) / "registry.json" - path.write_text(document, encoding="utf-8") - with self.assertRaisesRegex( - self.audit.WorkflowAuditError, "input is not readable UTF-8 JSON" + path.write_text("{}", encoding="utf-8") + with mock.patch.object( + self.audit.json, + "loads", + side_effect=RecursionError("pathological JSON nesting"), ): - self.audit._read_payload(path) + with self.assertRaisesRegex( + self.audit.WorkflowAuditError, "input is not readable UTF-8 JSON" + ): + self.audit._read_payload(path) if __name__ == "__main__": From 02d9313389cef539a359430d0c32f3bb4301e283 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 13 Aug 2026 07:12:29 +0900 Subject: [PATCH 016/111] fix(ci): bound JSON parser recursion failures --- scripts/ci/audit_workflow_registry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/ci/audit_workflow_registry.py b/scripts/ci/audit_workflow_registry.py index 45c95ec9b..204d7e2e0 100644 --- a/scripts/ci/audit_workflow_registry.py +++ b/scripts/ci/audit_workflow_registry.py @@ -339,7 +339,7 @@ def _read_payload(path: pathlib.Path) -> dict[str, Any]: content.decode("utf-8"), object_pairs_hook=_reject_duplicate_object_members, ) - except (UnicodeError, json.JSONDecodeError) as error: + except (UnicodeError, json.JSONDecodeError, RecursionError) as error: raise WorkflowAuditError("input is not readable UTF-8 JSON") from error return _require_mapping(parsed, "payload") From 1fc028c89999dcde8576bdb3f40806b7328ab63d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 13 Aug 2026 07:24:02 +0900 Subject: [PATCH 017/111] test(ci): use one unittest import style --- tests/test_workflow_registry_duplicate_json_contract.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_workflow_registry_duplicate_json_contract.py b/tests/test_workflow_registry_duplicate_json_contract.py index 890fea2e1..88b88d2fc 100644 --- a/tests/test_workflow_registry_duplicate_json_contract.py +++ b/tests/test_workflow_registry_duplicate_json_contract.py @@ -6,7 +6,7 @@ import pathlib import tempfile import unittest -from unittest import mock +import unittest.mock ROOT = pathlib.Path(__file__).resolve().parents[1] SCRIPT = ROOT / "scripts" / "ci" / "audit_workflow_registry.py" @@ -59,7 +59,7 @@ def test_parser_recursion_exhaustion_fails_as_audit_error(self) -> None: with tempfile.TemporaryDirectory() as directory: path = pathlib.Path(directory) / "registry.json" path.write_text("{}", encoding="utf-8") - with mock.patch.object( + with unittest.mock.patch.object( self.audit.json, "loads", side_effect=RecursionError("pathological JSON nesting"), From 65b83ca71ca60aea70c604d65a58af7c78c54aff Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 14 Aug 2026 04:18:45 +0900 Subject: [PATCH 018/111] chore(ci): align workflow registry audit with protected main --- docs/DOCUMENTATION_FITNESS.md | 276 ++++++++++++++++ docs/PRD.md | 40 +-- docs/README.md | 41 ++- docs/TRD.md | 88 ++--- .../0013-manifest-v3-extension-authority.md | 108 +++++++ .../0014-architecture-decision-governance.md | 112 +++++++ docs/adr/README.md | 54 +++- docs/doctoring/browser-agent-protocols.md | 79 +++++ docs/doctoring/mv3-compatibility.md | 50 ++- .../evidence/2026-08-10-active-pr-maturity.md | 57 ++++ .../2026-08-11-active-pr-maturity-closure.md | 61 ++++ .../2026-08-11-active-pr-maturity-delta.md | 57 ++++ .../2026-08-12-active-pr-maturity-delta.md | 30 ++ ...-12-browser-protocol-active-pr-evidence.md | 27 ++ docs/traceability/README.md | 184 ++++++----- .../action-postcondition-evidence.md | 116 +++++++ .../extension-authority-security.md | 85 +++++ .../resolution-freshness-authority.md | 99 ++++++ .../tls-revocation-freshness-authority.md | 53 +++ docs/uml/README.md | 6 +- docs/uml/extension-authority.md | 105 ++++++ ...cumentation_active_pr_evidence_contract.py | 196 ++++++++++++ ..._documentation_discoverability_followup.py | 43 +++ tests/test_documentation_fitness_contract.py | 265 +++++++++++++++ ...tension_authority_traceability_contract.py | 49 +++ tests/test_freshness_traceability_contract.py | 50 +++ ...v3_supported_capability_matrix_contract.py | 97 ++++++ tests/test_product_documentation_contract.py | 302 +++++++++++++++--- 28 files changed, 2529 insertions(+), 201 deletions(-) create mode 100644 docs/DOCUMENTATION_FITNESS.md create mode 100644 docs/adr/0013-manifest-v3-extension-authority.md create mode 100644 docs/adr/0014-architecture-decision-governance.md create mode 100644 docs/doctoring/browser-agent-protocols.md create mode 100644 docs/evidence/2026-08-10-active-pr-maturity.md create mode 100644 docs/evidence/2026-08-11-active-pr-maturity-closure.md create mode 100644 docs/evidence/2026-08-11-active-pr-maturity-delta.md create mode 100644 docs/evidence/2026-08-12-active-pr-maturity-delta.md create mode 100644 docs/evidence/2026-08-12-browser-protocol-active-pr-evidence.md create mode 100644 docs/traceability/action-postcondition-evidence.md create mode 100644 docs/traceability/extension-authority-security.md create mode 100644 docs/traceability/resolution-freshness-authority.md create mode 100644 docs/traceability/tls-revocation-freshness-authority.md create mode 100644 docs/uml/extension-authority.md create mode 100644 tests/test_documentation_active_pr_evidence_contract.py create mode 100644 tests/test_documentation_discoverability_followup.py create mode 100644 tests/test_documentation_fitness_contract.py create mode 100644 tests/test_extension_authority_traceability_contract.py create mode 100644 tests/test_freshness_traceability_contract.py create mode 100644 tests/test_mv3_supported_capability_matrix_contract.py diff --git a/docs/DOCUMENTATION_FITNESS.md b/docs/DOCUMENTATION_FITNESS.md new file mode 100644 index 000000000..69f603252 --- /dev/null +++ b/docs/DOCUMENTATION_FITNESS.md @@ -0,0 +1,276 @@ +# OriginWeave Documentation Fitness Assessment + +- **Assessment date:** 2026-08-11 +- **Assessment scope:** protected `main`, every current OriginWeave implementation lane relevant to canonical product truth, and durable product decisions that must be reconstructable without chat history +- **Assessment type:** semantic fitness, not file-presence inventory +- **Current verdict:** **DESIGN-SUFFICIENT / PROTECTED-MAIN-PARTIAL** + +## 1. Verdict + +**DESIGN-SUFFICIENT** means the repository has a coherent product, technical, architecture, decision, diagram, data-model, security, testing, operability, protocol and release graph sufficient to implement and review OriginWeave without reconstructing product intent from chat history. + +**PROTECTED-MAIN-PARTIAL** means the design graph is sufficient, while protected `main` still lacks the canonical reconciliation and several active implementation slices. Active pull requests are implementation evidence only. Neither a green feature branch nor a Proposed ADR becomes shipped truth through documentation wording. + +File existence alone is never sufficient. An artifact can exist and still be stale, contradictory, overclaiming, underclaiming, or disconnected from executable evidence. + +## 2. Fitness matrix + +| Documentation family | Fitness | Current evidence / remaining boundary | +|---|---|---| +| PRD | **PRESENT-CURRENT on this branch / protected-main follow-up required** | Protected-main requirements remain distinct from active evidence. #37 is the bounded-HTTP replacement; #45→#46→#53→#55 narrows sensitive-handle authority without creating the trusted broker; #47→#50→#54 narrows resolution freshness through socket use; #40→#52→#57→#58 provides browser authority/semantic prerequisites; #43→#56→#59→#60→#61 plus #49 provide active MV3 compatibility evidence; #62/#63 prove extension-proposal isolation without widening Agent/secret approval authority; and #64/#65 plus #51→#66 add outcome, controlled-fixture and resource-measurement prerequisites without completing the real Chromium runtime. | +| TRD | **PRESENT-CURRENT on this branch / protected-main follow-up required** | One protected-main implementation state is kept separate from volatile active/non-shipped evidence. Value objects, fixtures, bounded Linux samplers and compatibility tests do not imply deployed services, Chromium process attribution, browser adapters or completed runtime paths. | +| Root Architecture | **PRESENT-CURRENT** | The Chromium compatibility kernel plus Rust authority-bearing control plane remains correct. #47/#50/#54 refine ADR 0004; #45/#46/#53/#55 refine ADR 0007; #40/#52/#57/#58 refine browser observation/action boundaries; #62/#63 exercise the existing extension/policy separation; #64/#65 and #51→#66 refine evidence/fixture/resource prerequisites; #43/#49/#56/#59/#60/#61 remain compatibility work under issue #27. None introduces a new trust domain, persistence owner or deployed component. | +| ADR index/lifecycle | **PRESENT-CURRENT on this branch** | Accepted ADRs remain distinct from Proposed decisions. ADR 0013 separates MV3 compatibility from Agent authority; ADR 0014 governs architecture-decision lifecycle. Their branch presence or later integration cannot silently promote them to Accepted. | +| Individual ADRs | **SUFFICIENT BY LIFECYCLE** | Existing Accepted and Proposed decisions cover current material trust boundaries. #62–#66 refine or test existing authority, evidence, fixture and resource boundaries and do not independently justify manufacturing a new ADR. | +| UML / control-flow diagrams | **PRESENT-CURRENT with one legitimate deferral** | Component, network authority, observation/action, delegated-task state, deployment, evidence, secret-fill, approval, resource-pressure/GPU fallback and hourly automation flows exist. `uml/extension-authority.md` closes the permission-vs-Agent-authority gap. Detailed real-Chromium adapter/input/post-condition/process-attribution UML remains deferred until issue #28 executable contracts stabilize. | +| Conceptual ERD/domain model | **PRESENT-CURRENT** | The ERD remains explicitly conceptual until a real persistence owner/schema exists. Current active #45–#66 value, policy, freshness, compatibility, fixture, evidence and resource slices add no OriginWeave-owned durable store. Manufacturing tables for in-memory state, value objects, browser fixtures or process samples would be false architecture. | +| Traceability | **PRESENT-CURRENT on this branch** | Uses explicit protected-main, active-PR, partial, accepted-architecture, planned, research-only, superseded and out-of-scope maturity vocabulary. Volatile exact-head evidence lives in the dated maturity appendix, now through #66. | +| Threat model / Security | **PRESENT-CURRENT with implementation follow-up** | Untrusted content, network, secret, provenance and extension risks are covered. #62/#63 prove extension proposal permission cannot replace Agent policy or R3 approval; #64 does not turn caller timestamps into trusted causality; #65's hostile page content remains a controlled untrusted fixture; #66 does not infer process ownership from caller-supplied PIDs. | +| Test strategy / quality gates | **PRESENT-CURRENT** | Exact owned production function/line/region/branch coverage, rustdoc and realistic boundary testing are explicit. Active work uses exact RED→GREEN evidence, pinned real Chromium where browser behavior is claimed, and fail-closed OS sampling contracts rather than source-text or self-reported claims alone. | +| Operability / incident response | **PRESENT-CURRENT** | Failure, readiness, quarantine, cleanup and recovery concepts exist. Current fixture/value/sampler lanes add no daemon/service or persistence owner, so new SLO/RPO/RTO claims would be fabricated. | +| API / protocol contracts | **PRESENT-CURRENT as target contracts** | #52 is an internal semantic-observation value API, #57 a bounded typed-query API, #58 an authority-bound action-target bridge, and #64 an outcome-evidence value boundary. None is a BiDi/CDP/WebMCP wire adapter, native browser input executor, trusted-clock source, business-risk classifier or post-condition observer. | +| Release / rollback / provenance | **PRESENT-CURRENT** | Release remains bound to one exact integrated protected head. Active stacks #40→#52→#57→#58, #47→#50→#54, #45→#46→#53→#55, #43→#56→#59→#60→#61 plus parallel #49, and #51→#66 preserve dependency order; #62/#63/#64/#65 are direct-main prerequisites. Predecessor-head success cannot satisfy a later head. | +| Data governance / privacy | **PRESENT-CURRENT architecture / PARTIAL runtime** | Purpose-bound policy/evidence foundations exist. #62/#63 prove that proposal authority cannot manufacture secret authority or approval, but authenticated workload identity, durable trusted-broker storage, protected-value resolution/fill, KMS, cross-process transactionality, compensation, retention and model-disclosure lifecycle remain open under issue #10. | +| Standards / doctoring | **PRESENT-CURRENT with continuous watch** | Primary browser/protocol/standards evidence and APA 7 references distinguish living/vendor/experimental material from final normative standards. Exact browser release evidence stays pinned to executable Chromium evidence rather than documentation alone. | + +## 3. Reconciliation findings + +### 3.1 HTTP lineage + +Protected-main PRD previously named historical PR #11 as active HTTP evidence. Current replacement work is PR #37, while protected main still does not ship the reconstructed bounded HTTP capability. + +**Resolution:** #37 is active/non-shipped implementation evidence, #11 is historical predecessor lineage, and integration before any of these branch repairs become protected-main truth remains mandatory. Old-head checks, reviews and mergeability never transfer. + +### 3.2 Sensitive-data authority and broker lifecycle + +Protected main contains purpose-bound sensitive-data policy/evidence governed by Accepted ADR 0007. The active dependency chain is #45 → #46 → #53 → #55: lifecycle evidence, authoritative in-process use reservation, first-revocation-wins state, then audience binding. + +The audience string accepted by the value/policy primitive is **not authentication**. A future trusted broker must derive audience from authenticated workload/service identity rather than caller-controlled input. One-process synchronization is not durable/cross-process atomicity. + +**Resolution:** these lanes may be `IMPLEMENTED_ON_ACTIVE_PR`; the complete broker remains Planned under issue #10. They do not justify a fictitious broker process, KMS path, database table, transaction manager, browser-fill adapter, new deployment topology or physical ERD entity. + +### 3.3 Manifest V3 compatibility + +Protected main already proves a pinned-Chromium baseline for service worker, content script, storage, DNR, tabs, windows, scripting, commands, side panel, bookmarks/history read behavior, restart persistence and repeatability. The active compatibility stack adds: + +- #43: controlled downloads; +- #49: per-trial ephemeral profile isolation; +- #56: bookmark create/read/delete cleanup; +- #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. + +#43/#49/#56/#59/#60/#61 are active compatibility evidence only. Chromium permission or browser compatibility success is not an OriginWeave Agent capability, policy grant, approval or protected-value authority. A successful fixture cannot become an OriginWeave Agent history grant, bookmark grant, download grant or arbitrary page-JavaScript bridge. + +The supported-capability matrix in `docs/doctoring/mv3-compatibility.md` separates `PROTECTED_MAIN`, `ACTIVE_PR`, `PLANNED`, security-gated and out-of-scope claims. Update migration is intentionally distinct from restart persistence, and isolated-world behavior is intentionally distinct from injection alone. + +**Resolution:** complete compatibility remains Planned under issue #27. Proposed ADR 0013 remains the authority separator. #59/#60/#61 are refinements of that decision, not new architecture decisions. + +### 3.4 Browser identifier authority + +Protected main contains session/context/document/node foundations under Accepted ADR 0010. Active #40 maps protocol-local identifiers into OriginWeave-owned authority and remains non-shipped. + +**Resolution:** protocol identifiers remain adapter-local, and detailed adapter sequence UML remains deferred until issue #28 stabilizes executable BiDi/CDP contracts. + +### 3.5 ADR discoverability and identifier allocation + +The earlier index omitted existing ADRs, and active #37 already reserves ADR identifiers 0011/0012. + +**Resolution:** the branch indexes every ADR by lifecycle, uses non-colliding 0013/0014 for new Proposed decisions, and treats collision-sensitive identifiers as reserved across protected main plus active work. + +### 3.6 Documentation contract parser + +The first fitness contract accepted only bare lifecycle metadata even though repository-valid ADRs can carry descriptive suffixes. + +**Resolution:** machine checks validate the leading supported lifecycle state and reject unknown states without rejecting valid suffixes. + +### 3.7 UML audit correction + +An early audit incorrectly called resource-pressure and hourly-automation views missing. + +**Resolution:** the existing resource-pressure/GPU fallback and hourly automation flows are recognized. Only the genuinely missing extension-permission-to-Agent-authority view was added. + +### 3.8 Resolution freshness authority + +Active #47 → #50 → #54 progressively binds approved resolution state to first-party network planning and rechecks freshness immediately before socket I/O under trusted monotonic time. + +**Resolution:** this refines Accepted ADR 0004 rather than introducing a resolver service, proxy/PAC authority, wall-clock authority, persistence owner or new deployed component. + +### 3.9 TLS revocation-material freshness + +Active #48 provides a bounded freshness primitive for already verified revocation material. + +**Resolution:** this is not OCSP/CRL acquisition, signature/path validation, cache operation or an unrevoked-certificate claim. No fictitious revocation-service topology is added. + +### 3.10 Browser task telemetry and process-set RSS + +Active #51 validates bounded RSS, observation-byte, action-latency and task-duration values and now samples one explicitly supplied Linux PID through strict `/proc//status` `VmRSS` parsing. Stacked #66 extends this to a bounded explicit process set: at most 256 unique nonzero caller-owned PIDs, checked aggregate addition, and fail-closed sampling when any member cannot be measured. + +**Resolution:** OS sampling is now real for caller-supplied Linux PIDs, but Chromium process discovery, same-task attribution, browser child-process/cgroup walking, GPU/VRAM, JS heap and cross-platform sampling remain unimplemented. A changing RSS value is runtime state, so correctness tests validate the sampling contract rather than assuming two sequential reads are byte-identical. + +### 3.11 Semantic observation authority + +Active #52 carries an OriginWeave-owned node handle, bounded semantic fields, typed advertised actions, provenance channels and bounded relationships. Every relationship must remain inside the same browser session, browsing context, canonical origin and document epoch. Self-parent/self-child relationships and duplicate child handles fail closed. The relationship graph remains descriptive evidence. + +**Resolution:** #52 is not a browser observation adapter. Accessibility, DOM, layout, WebMCP, structured-data and visual inputs remain untrusted observations and cannot mint capability. + +### 3.12 Typed semantic query authority + +Active #57 performs bounded exact role, accessible-name and required-typed-action matching only against already validated semantic observations. + +**Resolution:** semantic query success is descriptive selection, not CSS/XPath/raw-DOM authority, arbitrary JavaScript, browser I/O, action dispatch or policy approval. + +### 3.13 Authority-bound semantic node action target + +Active #58 accepts only an advertised `NodeActionKind`, carries the exact OriginWeave-owned node handle and revalidates session/context/origin/document epoch immediately before later use. + +**Resolution:** this remains descriptive execution input. A node advertising `Click` cannot determine business-risk classification: the same click could represent navigation, submit, purchase, delete, permission management or legal consent. Policy intent, approval, browser dispatch and verified success remain separate boundaries under issue #28. + +### 3.14 Controlled history mutation compatibility + +Active #59 creates one synthetic loopback history entry, requires exact readback, removes it in `finally` and proves its absence afterwards. + +**Resolution:** browser history compatibility is not an OriginWeave Agent history grant. No history values are exposed to a model and no human/default profile is used. + +### 3.15 Controlled extension update migration + +Active #60 copies the checked-in fixture into a trial-local directory, keeps one ephemeral profile and one extension path, transitions only `1.0.0` → `1.0.1`, observes the loaded version and requires schema state 1 → 2 migration. + +**Resolution:** this proves one deterministic unpacked-extension version transition. It does not establish Chrome Web Store updates, enterprise rollout, arbitrary downgrade or third-party migration safety. + +### 3.16 Content-script isolated-world compatibility + +Active #61 gives the page main world and the MV3 content script the same JavaScript global name with different values and requires the page to keep publishing `page` while the content script observes its own `extension` value. If the worlds collapse, the existing compatibility gate fails in real pinned Chromium. + +**Resolution:** this is a bounded compatibility proof, not a trusted page-content channel, arbitrary JavaScript bridge or Agent capability. + +### 3.17 Extension proposal authority and secret approval composition + +Active #62/#63 exercise two sides of one architectural separator. #62 first proves the exact extension/session/context `ProposeTypedAction` grant is present and then requires ordinary Agent policy to reject origin/capability/instruction/secret widening. #63 gives the Agent context its independent `FillSecret` capability and broker-handle delivery request, but still requires the ordinary high-risk result `RequireApproval(RiskClass::R3)`. + +**Resolution:** extension proposal permission can neither mint Agent capability/origin/secret authority nor manufacture approval. These are regression proofs over existing boundaries, not a secret broker, browser adapter, approval service or new trust domain. Proposed ADR 0013 already captures the relevant permission-vs-Agent-authority decision. + +### 3.18 Verified action-outcome ordering + +Active #64 makes a successful action-outcome value require existing verified provenance plus one caller-supplied monotonic dispatch timestamp and an observation timestamp that is not earlier. An earlier observation fails closed as `PostConditionPredatesDispatch`; equality is allowed for coarse monotonic clocks. + +**Resolution:** temporal ordering prevents packaging a pre-dispatch observation as later success evidence, but it does not prove trusted clock provenance, actual browser dispatch, target linkage, causal effect or that a real browser reached the declared state. #64 is not a browser dispatcher or post-condition observer. + +### 3.19 Controlled Agent Task fixture + +Active #65 supplies a deterministic synthetic local web fixture with a labelled semantic input, submit control, same-document post-condition and explicitly hidden/untrusted prompt-injection text. The fixture contains no credential collection surface and requires no live third-party site. + +**Resolution:** the fixture makes the future real Chromium vertical slice reproducible without turning a third-party site into a test dependency. It is not a browser adapter, semantic extractor, input dispatcher, policy engine, trusted clock, process-attribution source or proof of real Chromium execution. + +### 3.20 Bounded browser process-set resource evidence + +Active #51→#66 establishes two distinct layers: #51 owns single explicitly supplied Linux PID sampling and the bounded telemetry value boundary; #66 owns bounded duplicate-safe aggregation/sampling over an exact caller-owned PID set. #66's exact current contract rejects empty, zero-PID, duplicate, oversized and overflow states and fails closed if any member cannot be sampled. + +**Resolution:** aggregate resource measurement must not silently undercount a known caller-owned process set, but process membership remains an external attribution responsibility. The implementation does not discover Chromium PIDs, prove process ancestry/task ownership, walk cgroups, sample GPU/VRAM or create a durable telemetry store. + +## 4. Durable product decisions captured by the canonical graph + +1. OriginWeave is **Browse. Act. Prove.**: an enterprise agentic web runtime and provenance-native browser platform, not Selenium-style automation. +2. Chromium remains the compatibility kernel; Blink/V8 are not rewritten for differentiation. +3. Rust owns new authority-bearing control-plane semantics and remains independently reusable. +4. Human, Assist, Agent Task and Crawler modes have distinct authority/profile semantics; Agent Task does not ambiently inherit Human authority. +5. Page, extension, WebMCP and model content are untrusted observations, not goal/policy authority. +6. Structured observation precedes raw HTML or screenshot-only interpretation. +7. Typed actions and observed post-conditions replace arbitrary-script and command-return-as-success semantics. +8. Logical origin, destination, route/proxy, TCP peer, TLS identity and HTTP semantics are separate authorities. +9. Session/context/document epoch/node identity is separate from raw BiDi/CDP identifiers. +10. Manifest V3 permission is not an OriginWeave Agent capability; compatibility evidence and Agent-authority evidence are independent. +11. Raw secrets stay outside model-visible context; sensitive values use purpose-bound authority, opaque handles and trusted fill paths. +12. Browser correctness/human interaction outrank optional local-model throughput under pressure. +13. Provenance distinguishes source observation, model judgement, policy, approval, action and verified outcome. +14. WebDriver BiDi, CDP, WebMCP and MCP are versioned adapters, never the product authority model by themselves. +15. The first browser proof uses pinned stock Chromium before any broad fork. +16. High-risk actions remain approval-bound; Crawler Mode remains read-only and excludes CAPTCHA/block-evasion features. +17. Autonomous development uses OpenCode/NVIDIA NIM under deterministic gates and separate review/publication authority, never `COPILOT_GITHUB_TOKEN` as the development-model credential. +18. Documentation, checks, reviews, model judgements and operational evidence are separate evidence authorities. +19. Work-conserving maintenance continues to another safe lane rather than stopping on one merge, document, RCA, queued check or approval gap. +20. Collision-sensitive repository identifiers are reserved across protected main and active work before allocation. +21. In-memory sensitive-handle primitives may narrow replay/revocation risk without claiming the durable trusted broker exists. +22. A validated DNS answer is not sufficient socket authority indefinitely; resolution-to-socket use requires bounded trusted-monotonic freshness. +23. Revocation-material freshness, cryptographic validity, acquisition/cache operation and an unrevoked claim remain separate evidence authorities. +24. Browser telemetry values, OS process sampling and Chromium/task process attribution are separate maturity claims. +25. Semantic observation provenance and advertised node-local actions are descriptive evidence and never execution authority. +26. Semantic relationships remain bounded within exact session/context/origin/document authority. +27. Sensitive-handle audience must ultimately derive from authenticated workload/service identity. +28. Real browser compatibility fixtures may mutate and clean controlled synthetic state without creating Agent authority. +29. Semantic query success is neither selector authority nor permission to execute an advertised action. +30. Semantic action-target binding preserves exact node authority but remains separate from business-risk classification, policy approval, dispatch and observed success. +31. Update migration, restart persistence, injection and isolated-world behavior are separate compatibility claims and must retain distinct executable evidence. +32. Extension proposal authority never substitutes for Agent capability, origin/secret authority or independent high-risk approval. +33. Verified action-success evidence must not predate dispatch, while trusted clock provenance, browser dispatch, target linkage and causality remain separate authorities. +34. A controlled hostile page fixture is reproducible test infrastructure, not evidence that a real Chromium adapter exists. +35. Resource aggregation over known PIDs does not establish Chromium process discovery or task attribution. + +## 5. Architecture views legitimately deferred + +### 5.1 Extension authority — present + +`uml/extension-authority.md` captures: + +```text +Chromium MV3 permission +-> extension runtime +-> untrusted extension observation/message +-> OriginWeave extension policy/grant +-> Agent capability decision +-> typed action proposal +-> deterministic policy +``` + +Compatibility evidence cannot substitute for Agent-authority evidence, or vice versa. #62/#63 executable composition tests strengthen this existing view without changing its architecture. + +### 5.2 Network freshness sequence — reconcile after #47 → #50 → #54 integrates + +```text +resolver answer +-> destination policy + origin binding +-> fresh resolution approval +-> connection authorization at trusted monotonic use time +-> socket-use freshness recheck +-> exact socket candidate +-> observed TCP peer +-> TLS/HTTP authority layers +``` + +### 5.3 Real Chromium vertical slice — deferred until issue #28 stabilizes + +```text +isolated profile/context +-> BiDi/CDP adapter +-> OriginWeave registry +-> semantic observation +-> typed semantic query +-> authority-bound semantic action target +-> explicit business intent / deterministic policy +-> real browser input +-> observed post-condition +-> credential-safe evidence +-> teardown/recovery +``` + +#40/#52/#57/#58 make identifier/semantic/action-target authority concrete; #64 makes ordered verified outcome packaging concrete; #65 supplies a controlled hostile target application; and #51→#66 narrows resource measurement. None yet establishes the real Chromium transport/semantic extraction/native input/post-condition observer/trusted clock/process attribution chain. Freezing temporary protocol fields into authoritative UML before those executable contracts exist would create false architecture. + +### 5.4 Trusted sensitive-data broker — deferred until issue #10 establishes a real runtime boundary + +Protected-main policy/evidence plus #45→#46→#53→#55 and composition regressions #62/#63 do not justify inventing a broker process, durable database, KMS topology, authenticated service-identity mechanism or browser-fill adapter. Add physical ERD/component/transaction views only when executable ownership exists. + +## 6. Completion criteria + +The graph becomes **PROTECTED-MAIN-SUFFICIENT** only when: + +1. PRD/TRD implementation inventories agree with the exact protected-main crates/APIs/browser evidence; +2. no historical/superseded lineage is presented as current implementation evidence; +3. ADR indexes discover every protected-main ADR and match lifecycle metadata; +4. UML covers every implemented material authority flow, with planned diagrams clearly marked; +5. ERD/domain models distinguish conceptual, in-memory, persisted, adapter-owned and external state truthfully; +6. traceability maps each material requirement/Accepted decision to protected-main evidence, explicitly active-PR evidence or an open issue; +7. documentation tests catch stale status/index/link/ownership/identifier/maturity terminology; +8. security, test, operability, privacy and release docs agree on shipped-vs-planned boundaries; and +9. this documentation reconciliation itself reaches protected main through repository governance and is re-evaluated against whatever feature heads actually integrated. + +Until then, OriginWeave is **design-documented but not protected-main documentation-closed**. That finding must never be used as an excuse to stop unrelated safe implementation work. diff --git a/docs/PRD.md b/docs/PRD.md index 12e5b7f0b..40539a28f 100644 --- a/docs/PRD.md +++ b/docs/PRD.md @@ -44,7 +44,7 @@ Every requirement uses **exactly one** status from this table. Implementation ev | **Proposed** | Product direction still requiring a dedicated reviewed decision or sufficient implementation evidence. | | **Open** | A decision or acceptance criterion is intentionally unresolved. | -Only `Implemented` may describe shipped behavior. An Accepted ADR is design authority, not implementation proof. +Only `Implemented` may describe shipped behavior. An Accepted ADR is design authority, not implementation proof. Active PR implementation evidence may be named in the evidence column, but it does not change a requirement to `Implemented` until the applicable behavior reaches protected `main`. ## 4. Problem statement @@ -84,10 +84,10 @@ The status applies to the **whole named product surface**, not to every implemen |---|---|---|---| | **OriginWeave Browser** | Chromium-compatible interactive distribution with governed agent entry points | Planned | No protected-main branded browser distribution yet | | **OriginWeave Runtime** | Headless/embedded governed web-task runtime | Planned | Rust authority kernels exist; browser integration remains incomplete | -| **OriginWeave Observe** | Structured observation from tools, structured data, network, accessibility, DOM/layout and visual fallback | Planned | Session/context/node-authority foundations are on protected main; semantic browser observation adapter is incomplete | -| **OriginWeave Capture** | Schema-bound extraction, crawler controls, downloads and WARC/PROV-oriented capture | Planned | Evidence foundations exist; complete capture runtime not shipped | +| **OriginWeave Observe** | Structured observation from tools, structured data, network, accessibility, DOM/layout and visual fallback | Planned | Session/context/node-authority foundations are on protected main; active PR #52 adds a bounded authority-bound semantic-observation value primitive with explicit evidence-channel provenance, but it is not a browser observation adapter and remains non-shipped | +| **OriginWeave Capture** | Schema-bound extraction, crawler controls, downloads and WARC/PROV-oriented capture | Planned | Evidence foundations and partial real-Chromium extension compatibility evidence exist; complete capture runtime is not shipped | | **OriginWeave Governor** | CPU, RAM, GPU, VRAM, admission and model/browser priority governance | Accepted architecture | Deterministic resource-budget and CPU-worker admission foundations are implemented; platform telemetry/scheduling adapters remain incomplete | -| **OriginWeave Policy** | Capability, origin, purpose, risk, crawler, approval and sensitive-data authority | Accepted architecture | Capability/origin/purpose/risk/crawler/approval foundations are implemented; purpose-bound sensitive-data policy is active work in PR #33 and the trusted broker remains planned | +| **OriginWeave Policy** | Capability, origin, purpose, risk, crawler, approval and sensitive-data authority | Accepted architecture | Capability/origin/purpose/risk/crawler/approval and purpose-bound sensitive-data policy foundations are implemented on protected main; trusted sensitive-data broker/storage/lifecycle remain planned under issue #10 | | **OriginWeave Evidence** | Credential-free evidence, provenance and task-trail contracts | Accepted architecture | Credential-free network evidence and purpose-bound sensitive-access receipts are implemented; complete Evidence Trail, WARC/PROV adapters and durable enterprise storage remain planned | | **OriginWeave Protocol** | Stable browser-agent protocol independent of one upstream automation standard | Planned | Contract documented; implementation pending | | **OriginWeave SDK** | Typed client libraries and adapters | Planned | Not a shipped product surface | @@ -163,7 +163,7 @@ planner identifies field + purpose + destination -> disclosure receipt records metadata without protected value ``` -The full journey is target architecture until the sensitive policy, trusted broker, browser-fill path, and post-condition/evidence path are all protected-main integrated. Implemented subcomponents do not make this whole sequence shipped. +The full journey is target architecture until the trusted broker, browser-fill path, and post-condition/evidence path are all protected-main integrated. The purpose-bound sensitive-data policy foundation and access-evidence primitives are already on protected main; those implemented subcomponents do not make the complete broker journey shipped. ### 8.4 Enterprise crawler @@ -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 | Issue #27 / release-specific evidence required | +| 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-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 | @@ -191,11 +191,11 @@ public-crawl purpose | ID | Requirement | Status | Implementation evidence / note | |---|---|---|---| -| PRD-OBS-001 | Autonomous observations can carry explicit browser-session, browsing-context, canonical-origin and document-epoch authority | Implemented | `ObservedNodeHandle`, `BrowserSessionId`, `BrowsingContextId` and `DocumentEpoch` on protected main via #17; real browser adapter remains planned | -| PRD-OBS-002 | Actionable semantic-node handles are invalidated by relevant document-epoch changes at the action linearization boundary | Accepted architecture | Core exact-authority validation exists; adapter lifecycle/mutation invalidation and atomic dispatch evidence remain planned | -| PRD-OBS-003 | Observation prefers typed/structured evidence before accessibility/DOM/layout and bounded visual fallback | Accepted architecture | ADR 0103 | +| PRD-OBS-001 | Autonomous observations can carry explicit browser-session, browsing-context, canonical-origin and document-epoch authority | Implemented | `ObservedNodeHandle`, `BrowserSessionId`, `BrowsingContextId` and `DocumentEpoch` are on protected main under Accepted ADR 0010; real browser adapter remains planned | +| PRD-OBS-002 | Actionable semantic-node handles are invalidated by relevant document-epoch changes at the action linearization boundary | Accepted architecture | Core exact-authority validation exists; adapter lifecycle/mutation invalidation and atomic dispatch evidence remain planned; active PR #40 owns the bounded protocol-ID registry and remains non-shipped evidence | +| PRD-OBS-003 | Observation prefers typed/structured evidence before accessibility/DOM/layout and bounded visual fallback | Accepted architecture | ADR 0103; active PR #52 adds a bounded `SemanticNodeObservation` value contract bound to `ObservedNodeHandle`, typed node-local action descriptors and explicit non-empty evidence-channel provenance. It is not a browser observation adapter and remains active/non-shipped evidence | | PRD-OBS-004 | Observation can use bounded incremental updates rather than full repeated snapshots | Planned | Adapter-specific design needed | -| PRD-OBS-005 | Source channel and trust/provenance remain explicit | Accepted architecture | Evidence model foundations exist | +| PRD-OBS-005 | Source channel and trust/provenance remain explicit | Accepted architecture | Evidence model foundations exist; active PR #52 fails closed when a semantic observation has no contributing evidence channel, while channel identity itself grants no execution authority | ### 9.3 Typed action execution @@ -215,8 +215,8 @@ public-crawl purpose | PRD-NET-002 | Resolution snapshots are bounded, origin-bound and fail closed on unapproved expansion | Implemented | `originweave-destination` | | PRD-NET-003 | Direct transport connects only to approved canonical sockets and verifies `peer_addr` | Implemented | `originweave-network` | | PRD-NET-004 | TLS authenticates service identity over the exact governed transport with explicit roots/time | Implemented | `originweave-tls` | -| PRD-NET-005 | Proxy/PAC route authority is explicit and never ambient | Implemented | Protected-main route-authority foundation from #20; PAC evaluation, proxy transport and CONNECT remain planned | -| PRD-NET-006 | Bounded HTTP semantics operate over authenticated governed transport | Planned | Active PR #11 is not shipped evidence | +| PRD-NET-005 | Proxy/PAC route authority is explicit and never ambient | Implemented | Protected-main route-authority foundation; PAC evaluation, proxy transport and CONNECT remain planned | +| PRD-NET-006 | Bounded HTTP semantics operate over authenticated governed transport | Planned | Current implementation evidence is active replacement PR #37; it is not protected-main truth. Historical PR #11 is predecessor lineage and must not be used as current implementation evidence | | PRD-NET-007 | Real Chromium navigation proves end-to-end consumption of every shipped authority layer | Planned | Issue #28 / release acceptance requirement | ### 9.5 Secret and sensitive-data authority @@ -224,8 +224,8 @@ public-crawl purpose | ID | Requirement | Status | Implementation evidence / note | |---|---|---|---| | PRD-DATA-001 | Raw secret values never enter model-visible context | Accepted architecture | ADR 0104; trusted browser/broker runtime path not fully shipped | -| PRD-DATA-002 | Sensitive disclosure binds tenant/task/field/purpose/destination/classification | Planned | Active replacement PR #33; no active-PR evidence counts as protected-main implementation | -| PRD-DATA-003 | Trusted broker owns expiry, revocation, atomic use reservation and resolution | Planned | Broker implementation pending under issue #10 | +| PRD-DATA-002 | Sensitive disclosure binds tenant/task/field/purpose/destination/classification | Implemented | Protected-main purpose-bound sensitive-data policy kernel governed by Accepted ADR 0007; this status does not claim broker/storage/value resolution | +| PRD-DATA-003 | Trusted broker owns expiry, revocation, atomic use reservation and resolution | Planned | Broker/storage/lifecycle implementation pending under issue #10 | | PRD-DATA-004 | Privacy controls use purpose-bound authorization, encryption, retention and audit rather than blanket masking | Accepted architecture | `DATA_GOVERNANCE.md` | | PRD-DATA-005 | Model disclosure additionally binds provider/model/region/retention policy | Planned | Requires orchestrator/provider integration | @@ -238,7 +238,7 @@ public-crawl purpose | PRD-EVD-003 | Evidence Trail links source, model judgement, policy, approval, action and verified outcome as distinct authorities | Planned | Conceptual ERD/provenance ADR; complete trail is not shipped | | PRD-EVD-004 | **Origin Map** provides buyer-visible provenance exploration | Proposed | UX/product-design work still required | | PRD-EVD-005 | WARC and PROV are separate interoperability/export adapters | Accepted architecture | ADR 0106 | -| PRD-EVD-006 | Sensitive-access evidence records authority without protected value | Implemented | Protected-main purpose-bound sensitive-access receipts via #31 | +| PRD-EVD-006 | Sensitive-access evidence records authority without protected value | Implemented | Protected-main purpose-bound sensitive-access receipts | ### 9.7 Resource governance @@ -246,7 +246,7 @@ public-crawl purpose |---|---|---|---| | PRD-RES-001 | Deterministic resource budgets produce cumulative mitigations | Implemented | `originweave-resource` foundations | | PRD-RES-002 | Browser/human correctness outranks optional model throughput | Accepted architecture | ADR 0105 | -| PRD-RES-003 | CPU worker saturation participates in deterministic new-work admission | Implemented | Protected-main `ResourceSnapshot`/`ResourceGovernor` CPU-worker admission via #30; platform worker telemetry/actuation remains adapter work | +| PRD-RES-003 | CPU worker saturation participates in deterministic new-work admission | Implemented | Protected-main `ResourceSnapshot`/`ResourceGovernor` CPU-worker admission; platform worker telemetry/actuation remains adapter work | | PRD-RES-004 | Platform adapters report bounded CPU/RAM/GPU/VRAM/network/storage telemetry | Planned | Platform integration required | | PRD-RES-005 | Constrained GPU systems shrink/offload/pause model work before sacrificing governed browser correctness | Accepted architecture | ADR 0105 | @@ -264,10 +264,10 @@ public-crawl purpose | ID | Requirement | Status | Implementation evidence / note | |---|---|---|---| -| PRD-EXT-001 | Manifest V3 remains the extension compatibility baseline | Accepted architecture | Official Chrome platform baseline | -| PRD-EXT-002 | Upstream extension APIs are preserved where possible | Accepted architecture | Chromium-kernel strategy | -| PRD-EXT-003 | Extension access to agent authority requires separate signed policy grant | Planned | Issue #27 / enterprise-runtime integration | -| PRD-EXT-004 | Compatibility tests cover install/update, worker lifecycle, scripts, storage, DNR, messaging, download, side panel and isolation | Planned | Issue #27 / release-specific suite | +| 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 | ### 9.10 Crawler and capture policy diff --git a/docs/README.md b/docs/README.md index fa62037e5..03b573c54 100644 --- a/docs/README.md +++ b/docs/README.md @@ -7,9 +7,15 @@ - [Architecture](../ARCHITECTURE.md) - [Architecture Decision Record index](adr/README.md) - [UML and control-flow diagrams](uml/README.md) + - [Extension compatibility and Agent authority UML](uml/extension-authority.md) - [Conceptual ERD and durable domain model](erd/README.md) - [Data governance and privacy boundary](DATA_GOVERNANCE.md) - [Product and decision traceability](traceability/README.md) +- [Documentation fitness assessment](DOCUMENTATION_FITNESS.md) +- [Dated active-PR maturity evidence (2026-08-10)](evidence/2026-08-10-active-pr-maturity.md) +- [Active-PR maturity delta (2026-08-11)](evidence/2026-08-11-active-pr-maturity-delta.md) +- [Active-PR maturity closure (2026-08-11)](evidence/2026-08-11-active-pr-maturity-closure.md) +- [Browser protocol active-PR evidence (2026-08-12)](evidence/2026-08-12-browser-protocol-active-pr-evidence.md) - [Threat model](THREAT_MODEL.md) - [Product-wide test strategy](TEST_STRATEGY.md) - [Operability and incident-response baseline](OPERABILITY.md) @@ -17,11 +23,12 @@ - [Release and rollback contract](RELEASE_AND_ROLLBACK.md) - [Product roadmap](product-roadmap.md) - [Research and standards](doctoring.md) + - [Browser and Agent protocol standards evidence](doctoring/browser-agent-protocols.md) - [Current product-baseline standards addendum](doctoring/product-documentation-baseline.md) - [Quality gates](quality-gates.md) - [Security policy](../SECURITY.md) -The PRD/TRD/Architecture/ADR/UML/ERD/data-governance/traceability/security/operations/API/release set is the product-wide documentation graph. Feature-specific design specifications and plans below provide detailed implementation history but do not substitute for the product-wide baseline. Planned or conversation-derived capabilities must remain labelled Planned, Proposed, or Open until reviewed implementation evidence reaches protected `main`. +The PRD/TRD/Architecture/ADR/UML/ERD/data-governance/traceability/security/operations/API/release set is the product-wide documentation graph. The documentation-fitness assessment records where that graph is current, stale, partial, or intentionally proposed. Volatile exact heads, workflow results, stack state, and active-PR maturity belong in dated evidence appendices rather than timeless architecture claims. Feature-specific design specifications and plans below provide detailed implementation history but do not substitute for the product-wide baseline. Planned or conversation-derived capabilities must remain labelled Planned, Proposed, or Open until reviewed implementation evidence reaches protected `main`. ## Governance and maintenance @@ -42,7 +49,7 @@ The PRD/TRD/Architecture/ADR/UML/ERD/data-governance/traceability/security/opera - [TLS service-identity design](superpowers/specs/2026-08-06-tls-server-identity-design.md) - [TLS service-identity implementation plan](superpowers/plans/2026-08-06-tls-server-identity.md) -## Protected-main architecture decisions +## Accepted protected-main architecture decisions - [ADR 0001: Chromium compatibility kernel](adr/0001-chromium-compatibility-kernel.md) - [ADR 0002: Agent safety kernel](adr/0002-agent-safety-kernel.md) @@ -50,5 +57,33 @@ The PRD/TRD/Architecture/ADR/UML/ERD/data-governance/traceability/security/opera - [ADR 0004: Logical origin and resolved destination safety](adr/0004-resolved-destination-policy.md) - [ADR 0005: Exact direct TCP peer binding](adr/0005-direct-socket-binding.md) - [ADR 0006: TLS service identity over the verified peer](adr/0006-tls-server-identity.md) +- [ADR 0007: Purpose-bound sensitive-data authority](adr/0007-purpose-bound-sensitive-data-authority.md) +- [ADR 0008: Delegated-task TLS leaf-validity horizon](adr/0008-leaf-validity-horizon.md) +- [ADR 0010: Session/context-bound node authority](adr/0010-session-context-bound-node-authority.md) -See the [ADR index](adr/README.md) for status rules, required decision structure, and the rule that active-PR ADRs do not become Accepted merely because they exist on an unmerged branch. +## Proposed architecture decisions + +Proposed ADRs are reviewable architecture memory, not shipped behavior and not automatically Accepted because their files are present in a branch or later reach protected `main`. The provenance headings below distinguish the protected-main baseline from decisions introduced by this documentation reconciliation without changing either decision's lifecycle status. + +### Protected-main baseline proposed decisions + +- [ADR 0009: Hourly agent credential boundary](adr/0009-hourly-agent-credential-boundary.md) +- [ADR 0100: Rust control-plane boundary](adr/0100-rust-control-plane-boundary.md) +- [ADR 0101: Isolated execution/profile modes](adr/0101-isolated-execution-profile-modes.md) +- [ADR 0102: Typed actions over arbitrary JavaScript](adr/0102-typed-actions-and-arbitrary-js.md) +- [ADR 0103: Semantic observation and stale-node identity](adr/0103-semantic-observation-and-stale-node-identity.md) +- [ADR 0104: Prompt-injection and secret authority separation](adr/0104-prompt-injection-and-secret-authority.md) +- [ADR 0105: Resource governor priority](adr/0105-resource-governor-priority.md) +- [ADR 0106: Provenance evidence model](adr/0106-provenance-evidence-model.md) +- [ADR 0107: Browser protocol adapter strategy](adr/0107-browser-protocol-adapter-strategy.md) +- [ADR 0108: Crawler policy](adr/0108-crawler-policy.md) +- [ADR 0109: Hourly automation secret ordering and operational closure](adr/0109-hourly-automation-operational-closure.md) + +### Proposed decisions introduced by this documentation reconciliation + +- [ADR 0013: Manifest V3 compatibility and extension-to-Agent authority](adr/0013-manifest-v3-extension-authority.md) +- [ADR 0014: Architecture decision acceptance governance](adr/0014-architecture-decision-governance.md) + +The second group exists only on this documentation branch until the branch integrates. After integration, the heading remains useful historical provenance; it does not promote either ADR from Proposed to Accepted and it does not claim that the described runtime capability is implemented. + +See the [ADR index](adr/README.md) for status rules, required decision structure, supersession rules, and active feature ADRs. The index and each ADR's own status metadata must agree; a PR body, chat transcript, automation prompt, or stale issue reference cannot change ADR status. diff --git a/docs/TRD.md b/docs/TRD.md index 7330fec98..3e8030012 100644 --- a/docs/TRD.md +++ b/docs/TRD.md @@ -17,25 +17,28 @@ This TRD defines technical invariants for OriginWeave without describing planned - **Proposed** — a candidate design that still needs a dedicated reviewed decision or implementation proof. - **Open** — deliberately unresolved. -Pull-request code is not treated as Implemented until it reaches protected `main` and required acceptance evidence is re-established there. +Pull-request code is not treated as Implemented until it reaches protected `main` and required acceptance evidence is re-established there. Active-PR implementation may be recorded in a separate evidence note, but it never creates a composite implementation status. ## 2. Current protected-main implementation inventory -The current reusable Rust control plane is intentionally smaller than the final browser product. - -| Module | Current responsibility | Status | -|---|---|---| -| `originweave-core` | Canonical origin, typed actions, purpose/mode, capabilities, risk, secret-delivery and approval contracts. | **Implemented** | -| `originweave-policy` | Pure fail-closed action-policy evaluation. | **Implemented** | -| `originweave-destination` | Resolved-address classification, origin-bound snapshots, connection pinning, rebinding and redirect authority. | **Implemented** | -| `originweave-network` | Direct single-address TCP connection plan and exact operating-system peer verification. | **Implemented** | -| `originweave-tls` | WebPKI service identity over the already verified TCP stream. | **Implemented** | -| `originweave-resource` | Deterministic resource budgets and cumulative mitigation plans. | **Implemented** | -| `originweave-evidence` | Value-redacted network evidence and provenance foundations. | **Implemented** | -| Browser/session/observation/action adapters | Chromium/BiDi/CDP integration and node-lifetime enforcement. | **Planned / active development** | -| HTTP/proxy/PAC execution | Bounded HTTP and explicit route execution beyond pure foundations. | **Planned / active development** | -| Secret broker persistence/runtime | Atomic opaque-handle lifecycle and trusted fill. | **Planned / active development** | -| WARC/PROV persistence | Durable capture and provenance serialization. | **Planned** | +The current reusable Rust control plane is intentionally smaller than the final browser product. The status column describes protected `main` only; active PR evidence is kept in the final column. + +| Module / boundary | Current responsibility | Protected-main status | Active/non-shipped evidence | +|---|---|---|---| +| `originweave-core` | Canonical origin, typed actions, purpose/mode, capabilities, risk, secret-delivery, approval, session/context/document/node authority values. | **Implemented** | PR #40 builds a protocol-ID registry on top of these values; it is not protected-main truth | +| `originweave-policy` | Pure fail-closed action policy including purpose-bound sensitive-data authority. | **Implemented** | Trusted broker/runtime lifecycle remains separate planned work under issue #10 | +| `originweave-destination` | Resolved-address classification, origin-bound snapshots, route authority, connection pinning, rebinding and redirect authority. | **Implemented** | PAC evaluation/proxy transport/CONNECT are still Planned | +| `originweave-network` | Direct single-address TCP connection plan and exact operating-system peer verification. | **Implemented** | — | +| `originweave-tls` | WebPKI service identity over the already verified TCP stream. | **Implemented** | — | +| `originweave-resource` | Deterministic resource budgets, CPU-worker admission and cumulative mitigation plans. | **Implemented** | Platform telemetry/actuation remains Planned | +| `originweave-evidence` | Value-redacted network evidence, provenance foundations and sensitive-access evidence primitives. | **Implemented** | Complete durable Evidence Trail/WARC/PROV persistence remains Planned | +| Browser/session protocol registry | Bind raw BiDi/CDP identifiers to OriginWeave session/context/document authority. | **Planned** | Active PR #40; core lifetime value contracts are already Implemented under ADR 0010 | +| Semantic observation/action browser adapters | Chromium/BiDi/CDP observation, node lifecycle, typed input and post-condition verification. | **Planned** | Issue #28 | +| Bounded HTTP execution | HTTP/1.1 semantics over authenticated governed transport. | **Planned** | Active replacement PR #37; historical PR #11 is predecessor lineage, not current evidence | +| Proxy/PAC execution | Evaluate authorized route selection and perform governed proxy/CONNECT transport. | **Planned** | Protected-main route-authority value foundation already exists | +| Sensitive-data broker persistence/runtime | Atomic opaque-handle lifecycle, revocation/reservation, value resolution and trusted fill. | **Planned** | Protected-main policy/evidence foundations exist; issue #10 owns complete runtime lifecycle | +| Manifest V3 compatibility program | Real pinned-Chromium extension compatibility and release matrix. | **Planned** | Protected main already contains partial real-browser evidence; active PR #43 adds downloads evidence | +| WARC/PROV persistence | Durable capture and provenance serialization. | **Planned** | — | ## 3. Architectural invariants @@ -69,7 +72,7 @@ A **logical origin** is not a **resolved destination** decision. A resolved addr ### TRD-INV-003 — Untrusted page content -Browser content, rendered text, hidden text, comments, ads, WebMCP output, network bodies, downloads, and model-produced summaries are data. They cannot mutate system policy, expand capabilities, authorize destinations, reveal secrets, or redefine the user's goal. +Browser content, rendered text, hidden text, comments, ads, WebMCP output, network bodies, downloads, extension messages, and model-produced summaries are data. They cannot mutate system policy, expand capabilities, authorize destinations, reveal secrets, or redefine the user's goal. ### TRD-INV-004 — Secret separation @@ -87,23 +90,23 @@ A typed action may be attempted only after exact current authority is validated. ### Assist Mode -**Accepted architecture; Planned adapter path.** Reversible/read behavior may be automated. Irreversible or externally visible state changes re-enter the risk/approval pipeline. +**Accepted architecture.** Reversible/read behavior may be automated. Irreversible or externally visible state changes re-enter the risk/approval pipeline. The browser adapter path remains Planned. ### Agent Task Mode -**Accepted architecture; Planned adapter path.** Each delegated task receives an isolated or explicitly attached browser context, scoped capabilities, origins, secrets, policy and resource budgets. The unrestricted default human profile is not ambient task authority. +**Accepted architecture.** Each delegated task receives an isolated or explicitly attached browser context, scoped capabilities, origins, secrets, policy and resource budgets. The unrestricted default human profile is not ambient task authority. Complete browser adapter/session integration remains Planned. ### Crawler Mode -**Accepted architecture; policy foundation Implemented.** Crawler actions are read-only. Robots evidence, rate controls, purpose, privacy, retention and legal/contract policy are distinct checks. +**Accepted architecture.** The read-only crawler policy foundation is Implemented, while the complete crawler runtime is Planned. Robots evidence, rate controls, purpose, privacy, retention and legal/contract policy are distinct checks. ## 5. Identifier and lifetime contracts ### 5.1 Core identifiers -Durable identifiers introduced by adapters must be opaque and nonzero/nonempty. External browser identifiers are translated through scoped registries instead of becoming the core authority value directly. +Protected-main core contracts already define opaque browser-session, browsing-context, document-epoch and observed-node authority values governed by Accepted ADR 0010. External browser identifiers must be translated through scoped registries instead of becoming core authority directly. The protocol-ID registry is active PR #40 evidence until protected integration. -Planned browser-lifetime tuple: +Required browser-lifetime tuple: ```text browser_session_id @@ -117,7 +120,7 @@ An actionable node reference is valid only when every component matches the live ### 5.2 Document epochs -Navigation, document replacement, or another adapter-defined actionable-document lifetime change rotates `document_epoch`. A stale node reference must fail deterministically before input dispatch. +Navigation, document replacement, or another adapter-defined actionable-document lifetime change rotates `document_epoch`. A stale node reference must fail deterministically before input dispatch. Core exact-authority validation is Implemented; real browser lifecycle invalidation/linearized dispatch remains adapter work. ### 5.3 Idempotency @@ -145,7 +148,7 @@ The pure destination crate itself does no DNS lookup. ### 6.3 Route/proxy authority -**Accepted architecture; active development.** Direct routing is the default. Proxy and PAC-selected routes require explicit authority. A proxy is an intermediate authority and never replaces final-target authorization. Ambient environment proxy variables cannot silently change the governed route. +**Protected-main status: Implemented for route-authority foundations. Proxy/PAC execution: Planned.** Direct routing is the default. Proxy and PAC-selected routes require explicit authority. A proxy is an intermediate authority and never replaces final-target authorization. Ambient environment proxy variables cannot silently change the governed route. PAC evaluation, proxy transport and CONNECT require separate execution evidence before release claims. ### 6.4 Direct transport @@ -157,7 +160,9 @@ The pure destination crate itself does no DNS lookup. ### 6.6 HTTP semantics -**Accepted architecture; active development.** HTTP processing must consume an authenticated governed connection and define: +**Protected-main status: Planned.** Active replacement PR #37 implements bounded HTTP/1.1 semantics but remains non-shipped evidence until protected integration. Historical PR #11 is predecessor lineage and is not current implementation evidence. + +HTTP processing must consume an authenticated governed connection and define: - supported methods and caller-controlled fields; - syntax/framing rules; @@ -200,7 +205,7 @@ Raw HTML is not the default model payload. ## 8. Action architecture -Standard action vocabulary is **Accepted architecture / Planned runtime integration**: +The standard action vocabulary is **Accepted architecture**; complete real-browser runtime integration remains Planned: ```text navigate @@ -241,7 +246,7 @@ The adapter declares an observable post-condition contract, such as URL change, ### 9.1 Purpose-bound authority -**Active development.** Protected disclosure authority is represented as one value object/scoped record containing tenant, task, field, business purpose, canonical destination and data classification. Reclassification requires newly valid authority. +**Implemented policy foundation.** Protected disclosure authority binds tenant, task, field, business purpose, canonical destination and data classification under Accepted ADR 0007. This implementation does not imply that trusted value storage, opaque-handle resolution, revocation or browser fill are complete. ### 9.2 Opaque handle broker @@ -256,15 +261,17 @@ The adapter declares an observable post-condition contract, such as URL change, - value resolution/fill; - compensation/recovery after reserved-but-failed use. +Issue #10 owns the broader broker/storage/lifecycle completion. + ### 9.3 Evidence -Access/disclosure evidence records identifiers, scope, decision, approval reference, policy version and lifecycle times without carrying the protected value. +Protected-main evidence primitives can record purpose-bound sensitive-access authority without carrying the protected value. Complete broker-use receipts must remain aligned with the runtime lifecycle once that broker exists. ## 10. Resource-governor requirements ### 10.1 Deterministic kernel -**Implemented foundation.** `originweave-resource` validates budgets and produces a cumulative mitigation plan. It does not sample the operating system or directly schedule processes. +**Implemented.** `originweave-resource` validates budgets, includes CPU-worker admission state, and produces a cumulative mitigation plan. It does not sample the operating system or directly schedule processes. ### 10.2 Adapter telemetry @@ -276,7 +283,7 @@ Access/disclosure evidence records identifiers, scope, decision, approval refere ### 10.4 Constrained GPU -**Accepted architecture / Planned implementation.** Rendering and local model inference use phase scheduling where necessary. The mitigation ladder can shrink model batches, release inference caches, offload to CPU, pause the task and reject admission before foreground rendering is sacrificed. +**Accepted architecture.** Rendering and local model inference use phase scheduling where necessary. The implementation of platform GPU telemetry/scheduling remains Planned. The mitigation ladder can shrink model batches, release inference caches, offload to CPU, pause the task and reject admission before foreground rendering is sacrificed. ## 11. Evidence and provenance requirements @@ -310,7 +317,7 @@ Generic network evidence retains bounded names and canonical locators while valu ### WebDriver BiDi -**Planned.** WebDriver BiDi is an evolving W3C adapter contract. Its session/user-context/browsing-context identifiers are translated into OriginWeave-scoped internal identities. Protocol evolution is isolated behind versioned adapter tests. +**Planned.** WebDriver BiDi is an evolving W3C adapter contract. Its session/user-context/browsing-context identifiers are translated into OriginWeave-scoped internal identities. Core lifetime authority is already Implemented; active PR #40 is non-shipped registry implementation evidence. ### Chrome DevTools Protocol @@ -318,7 +325,7 @@ Generic network evidence retains bounded names and canonical locators while valu ### WebMCP -**Planned / experimental external dependency.** **WebMCP** can provide typed page tools. Tool schemas and outputs remain untrusted page-originated data and cannot grant OriginWeave authority. +**Planned.** **WebMCP** is an experimental external dependency that can provide typed page tools. Tool schemas and outputs remain untrusted page-originated data and cannot grant OriginWeave authority. ### Model Context Protocol @@ -330,9 +337,9 @@ Generic network evidence retains bounded names and canonical locators while valu ## 13. Manifest V3 extension requirements -**Accepted architecture / Planned compatibility program.** OriginWeave preserves Chromium's extension implementation rather than rebuilding Chrome APIs in Rust. Agent authority remains separate from ordinary extension permissions. A future signed policy registry controls which extensions may observe or propose agent actions. +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. -Compatibility acceptance includes installation/update, extension service-worker lifecycle, content scripts, storage, scripting, DNR, native messaging, downloads, side panel, restart persistence and explicit task-mode isolation. +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. ## 14. Prompt-injection and model boundary @@ -386,6 +393,7 @@ Long-running tasks and external model calls require cancellation semantics that - Node/action validation occurs immediately before execution to close stale-state races. - Sensitive-handle use becomes atomic in the trusted broker. - Migration/release/automation writer leases prevent competing repository writers. +- Repository-scoped collision-sensitive identifiers such as ADR numbers, migration IDs and protocol/schema versions are reserved across protected main plus active work before allocation. - Platform compute pools avoid avoidable oversubscription between Chromium, Rust and model runtimes. ## 17. Persistence and data naming @@ -407,7 +415,7 @@ network_exchange download_artifact ``` -The conceptual model is defined in [`erd/README.md`](erd/README.md). Adapters may use WARC/object storage/relational stores independently; cross-service application database access is not an integration contract. +The conceptual model is defined in [`erd/README.md`](erd/README.md). Adapters may use WARC/object storage/relational stores independently; cross-service application database access is not an integration contract. Conceptual ERD entities are not evidence that a physical relational schema exists. ## 18. Security and enterprise controls @@ -423,11 +431,15 @@ Product UI targets WCAG 2.2 AA / ISO/IEC 40500:2025-aligned evidence. Approval, ### Implemented kernels -Require deterministic unit/property/integration tests for canonicalization, classification, rebinding, redirects, direct peers, TLS identity, policy, resources and evidence. +Require deterministic unit/property/integration tests for canonicalization, classification, rebinding, redirects, route authority, direct peers, TLS identity, policy, session/node authority values, resources and evidence. ### Browser vertical slice -Requires real browser integration tests covering isolated contexts, stale nodes, iframes/shadow DOM where supported, origin changes, typed actions, post-conditions, crashes, cancellations and governed real network composition. +Requires real browser integration tests covering isolated contexts, protocol-ID registry binding, stale nodes, iframes/shadow DOM where supported, origin changes, typed actions, post-conditions, crashes, cancellations and governed real network composition. + +### Manifest V3 compatibility + +Maintain pinned real-Chromium evidence for every claimed extension surface, with restart/repeatability and bounded failure diagnostics. Compatibility evidence and Agent-authority evidence are independent: neither can substitute for the other. ### Security @@ -482,4 +494,4 @@ A material change to any of the following must update the authoritative document - enterprise privacy/security/tenancy contract; - release acceptance or rollback semantics. -If a decision is not implemented, the documentation must retain `Planned`, `Proposed`, or `Open` status rather than silently describe it as shipped. +If a decision is not implemented, the documentation must retain `Planned`, `Proposed`, or `Open` status rather than silently describe it as shipped. Active PR evidence remains explicitly non-shipped until protected integration and exact acceptance evidence exist. diff --git a/docs/adr/0013-manifest-v3-extension-authority.md b/docs/adr/0013-manifest-v3-extension-authority.md new file mode 100644 index 000000000..e620edf9d --- /dev/null +++ b/docs/adr/0013-manifest-v3-extension-authority.md @@ -0,0 +1,108 @@ +# ADR 0013: Manifest V3 compatibility and extension-to-Agent authority + +- **Status:** Proposed +- **Date:** 2026-08-10 +- **Supersedes:** None +- **Superseded by:** None + +## Context + +OriginWeave retains Chromium as its compatibility kernel rather than reimplementing Chrome's extension runtime. That creates two independent product questions: whether a declared Manifest V3 capability works on the pinned Chromium baseline, and whether an extension can influence an OriginWeave Agent Task only through explicit OriginWeave authority. + +Issue #27 requires both executable Manifest V3 compatibility evidence and explicit separation between Chromium extension permissions and OriginWeave Agent capabilities. Protected main contains partial pinned-Chromium compatibility evidence and extension-to-Agent authority foundations, but the full capability matrix, managed/native-messaging boundaries, release integration, and complete isolation acceptance remain open. + +This ADR makes that target architecture reviewable without claiming issue #27 is complete. Until protected-main governance accepts it, this ADR is Proposed design authority only. + +## Decision drivers + +- Preserve Chromium extension compatibility without creating a second OriginWeave plugin ecosystem. +- Prevent Chrome extension permissions from becoming ambient Agent Task authority. +- Keep Human Mode and delegated Agent Task profile semantics distinct. +- Bind compatibility claims to an exact Chromium revision and declared capability matrix. +- Keep extension-produced content and messages in the untrusted-observation domain. +- Keep protected secrets and sensitive values behind independent purpose-bound authority. +- Support managed extensions without granting arbitrary native-process or cross-origin capability. +- Allow safe rollback when a Chromium revision regresses a declared extension surface. + +## Assumptions and authority boundaries + +- Chromium owns Manifest V3 parsing, service workers, extension APIs, isolated worlds, and browser-managed extension policy. +- OriginWeave owns Agent Task isolation, extension-to-Agent grants, task/origin/action authority, secret/sensitive disclosure, approvals, evidence, and release claims. +- A Chromium extension permission authorizes the extension inside Chromium; it does not mint an OriginWeave capability. +- An OriginWeave `extension_grant` authorizes only the explicitly bound OriginWeave interaction; it does not emulate Chrome manifest permissions. +- Extension content, page mutations, messages, native-host output, and structured tool output remain untrusted observations unless independently authenticated through a separate trusted administrative channel. +- Compatibility evidence and Agent-authority-isolation evidence are separate evidence classes. Neither implies the other. + +## Options considered + +### Reimplement Chrome extensions as a Rust plugin system + +Rejected. It would create a second extension ecosystem and duplicate mature Chromium behavior. + +### Let extensions inherit Agent Task authority from Chrome permissions + +Rejected. Chrome permissions are not OriginWeave task/origin/action/approval grants and ambient inheritance creates confused-deputy, secret-disclosure, prompt-injection, and cross-origin escalation risk. + +### Disable extensions in every mode + +Rejected as a product-wide rule. Agent Task Mode defaults to no extensions or a managed allow-list, but Human Mode must retain normal compatible extension use and enterprises may require managed extensions. + +### Retain Chromium's extension plane and add explicit OriginWeave grants + +Selected. + +## Decision + +1. **Retain Chromium Manifest V3 as the compatibility plane.** OriginWeave does not create a competing Rust extension API for browser compatibility. +2. **Separate execution modes.** Human Mode may use the person's compatible extension set under browser/enterprise policy. Agent Task Mode defaults to no extensions or an explicit managed allow-list. Later attached-human-tab execution is labelled reduced-assurance when pre-existing extensions can influence page state. +3. **Require explicit OriginWeave extension authority.** Any extension-to-Agent interaction that can affect an Agent Task requires an `extension_grant` or equivalent typed decision bound at minimum to extension identity/version policy, session, applicable browsing context, capability, origin/resource scope, expiry, and task. +4. **Never translate Chrome permission into Agent capability.** `tabs`, `scripting`, `downloads`, `declarativeNetRequest`, host permissions, native messaging, or managed policy do not grant OriginWeave navigation, action, approval, secret, or sensitive-data authority. +5. **Keep extension output untrusted.** Extension messages and content enter the bounded observation/provenance path. They cannot alter the trusted goal, add tools, mint capabilities, approve high-risk actions, or weaken deterministic policy. +6. **Keep protected values brokered.** An extension does not receive raw credentials or sensitive values merely because it can inspect or modify a page. Independent secret/sensitive-data authority is rechecked immediately before trusted browser dispatch. +7. **Bound native messaging separately.** Native messaging is supported only behind an explicit host-managed allow-list, exact extension/host identity policy, process boundary, bounded I/O, and auditable lifecycle. It remains unsupported until that executable boundary exists. +8. **Publish exact compatibility evidence.** Public extension claims are bound to an exact Chromium revision/build and explicit Manifest V3 capability matrix. OriginWeave does not claim universal or `100% Chrome extension compatibility`. +9. **Separate Chrome-only services.** Web Store distribution, Google-account services, proprietary codecs/DRM, licensing, and other Chrome-only services are not implied by Manifest V3 compatibility. +10. **Gate releases by declared surfaces.** A declared supported capability that regresses blocks release or must be removed from the published matrix before release. Compatibility success never substitutes for Agent-authority-isolation evidence. + +## Consequences + +OriginWeave can preserve mature Chromium extension behavior while keeping its differentiating authority logic in reusable Rust modules. Buyers receive exact, falsifiable compatibility claims and separately reviewable security evidence. The cost is maintaining both a real-browser compatibility suite and independent authority-isolation tests, plus explicit managed-extension/native-host lifecycle work. + +## Failure and degraded behavior + +- A failed declared MV3 fixture makes that capability unsupported for the affected pinned release until fixed or removed from the published matrix. +- Invalid extension identity, grant scope, session/context binding, origin, expiry, or task fails closed. +- Attempts to widen task authority, inject a trusted instruction, resolve a secret, or synthesize approval are denied and recorded as bounded credential-free evidence. +- Missing native-host policy/process isolation keeps native messaging unsupported rather than falling back to ambient process execution. +- Attached human-tab sessions with unknown extensions are reduced-assurance and cannot inherit isolated-task release claims. + +## Security / privacy / governance impact + +The decision reduces confused-deputy and prompt-injection risk by keeping Chrome extension permissions outside OriginWeave policy. Secret and sensitive-data disclosure remain independently purpose-bound. Extension observations and compatibility diagnostics must not expose raw credentials, arbitrary local filesystem paths, unrestricted native-process output, or protected values in logs/evidence. Enterprise-managed extension policy is policy input, not a replacement for task authorization. + +## Tests and acceptance evidence + +Issue #27 acceptance requires pinned-Chromium evidence for the declared matrix and separate production authority tests, including service-worker/content-script lifecycle, declared APIs, restart/update persistence, Agent Task isolation without a grant, managed-grant success, denial of origin/action widening, untrusted-message handling, secret non-disclosure, exact build binding, repeated-run evidence, native-messaging denial until implemented, and release failure when a public capability regresses. + +Current active compatibility PRs are evidence only for their unchanged exact heads. They do not make this Proposed ADR Accepted or close issue #27. + +## Migration and rollback + +No persistent database migration is introduced. A release can roll back the Chromium baseline, disable a managed extension, revoke an `extension_grant`, or remove an unproven capability from the published matrix without widening authority. Rollback evidence must retain the exact Chromium/build/capability set that was tested. + +## Open follow-ups + +- Complete issue #27's compatibility matrix and production isolation acceptance. +- Define managed-extension identity/update semantics. +- Implement the native-messaging allow-list/process boundary before claiming support. +- Integrate the complete Agent Task browser vertical slice under issue #28. +- Reconcile PRD/TRD/traceability from protected-main evidence as compatibility slices integrate. +- Promote this ADR only through explicit protected-main governance. + +## Supersession / reversal conditions + +Supersede this ADR if Chromium adopts a materially different extension authority model, OriginWeave intentionally drops Chromium extension compatibility, or an accepted architecture provides safer equivalent compatibility without ambient Agent authority. A successor must retain explicit compatibility evidence and task-authority separation. + +## References + +Primary browser/extension/protocol evidence and APA 7 references are maintained in [`../doctoring/browser-agent-protocols.md`](../doctoring/browser-agent-protocols.md) and [`../doctoring.md`](../doctoring.md). Related decisions include ADR 0001, ADR 0002, ADR 0007, ADR 0010, ADR 0101, ADR 0104, and ADR 0107. \ No newline at end of file diff --git a/docs/adr/0014-architecture-decision-governance.md b/docs/adr/0014-architecture-decision-governance.md new file mode 100644 index 000000000..d550f8464 --- /dev/null +++ b/docs/adr/0014-architecture-decision-governance.md @@ -0,0 +1,112 @@ +# ADR 0014: Architecture decision acceptance governance + +- **Status:** Proposed +- **Date:** 2026-08-10 +- **Supersedes:** None +- **Superseded by:** None + +## Context + +OriginWeave separates protected-main source, executable checks, formal review, documentation, release evidence, and runtime policy as distinct authorities. Architecture Decision Records need the same discipline: a Markdown file, issue, chat statement, automation prompt, model verdict, or PR body can propose a decision but cannot independently make it an Accepted governing decision. + +Current contributor authority comes from protected-main `AGENTS.md`, live GitHub policy, and any explicit operationally satisfiable CWL/OriginWeave governance rule. The current contract also describes a solo-maintainer condition: an otherwise impossible independent non-author approval rule is not manufactured when fewer than two eligible independent maintainers exist, while technical/security/coverage/rustdoc/findings/live-base/branch-protection gates remain mandatory. + +The ADR index previously repeated these binding details directly. An index should discover governance rather than create it. This ADR therefore records the proposed durable acceptance model and its reversal conditions. While Proposed, it does not override `AGENTS.md` or live GitHub policy. + +## Decision drivers + +- Prevent indexes, chat, model output, or stale PR evidence from silently changing architecture authority. +- Never synthesize, impersonate, self-submit, or fabricate approval that current policy requires. +- Avoid permanent solo-maintainer deadlock when an independent reviewer route does not operationally exist and GitHub does not require one. +- Keep exact-head technical evidence mandatory regardless of review topology. +- Make reviewer-provisioning gaps explicit and reversible. +- Keep ADR status machine-checkable without turning README prose into a hidden policy engine. + +## Assumptions and authority boundaries + +- Protected-main `AGENTS.md` and live GitHub rules are authoritative for contributor actions. +- This ADR remains Proposed until a protected-main revision explicitly records an Accepted lifecycle transition in this ADR's metadata and both canonical indexes. +- Merely merging a file that still says `Proposed` does not Accept it. +- Formal review and technical checks are separate evidence classes. +- A review counts only if the governing policy recognizes that reviewer identity and review state for the relevant exact head. +- Predecessor-head approval does not transfer across a changed head unless live policy explicitly defines that behavior. + +## Options considered + +### Define ADR acceptance only in the index README + +Rejected. The index should summarize and discover decisions, not define the binding algorithm that grants its own statuses. + +### Require non-author approval unconditionally + +Rejected. In a genuine solo-maintainer topology this creates an unsatisfiable governance deadlock and pressure to invent reviewer identities or weaken the rule. + +### Let the author or automation synthesize approval + +Rejected. Self-approval, impersonation, model verdicts, reactions, status checks, or fabricated identities cannot provide independent review evidence. + +### Bind acceptance to live protected-branch governance with a narrow solo-maintainer hold + +Selected. + +## Decision + +If Accepted, OriginWeave applies these durable ADR-governance rules: + +1. **Explicit protected-main lifecycle transition defines architecture acceptance.** A branch file, issue, chat statement, prompt, PR body, check, model verdict, or merge by itself does not create a governing Accepted ADR. An ADR becomes Accepted only when a protected-main revision explicitly changes that ADR's lifecycle metadata to `Accepted` and both `docs/README.md` and `docs/adr/README.md` mirror the same status. A Proposed ADR that merely reaches protected main remains Proposed. +2. **Live policy defines mandatory review evidence.** When current GitHub rules require counted approval, acceptance requires a formal `APPROVED` review from an eligible identity recognized by that policy on the applicable unchanged head. +3. **Repository-specific review requirements must be operationally satisfiable.** A stricter CWL/OriginWeave rule may require an eligible non-author reviewer only when a legitimate reviewer route exists. +4. **No synthetic approval.** Author approval, COMMENTED reviews, reactions, model verdicts, statuses, predecessor-head approvals, impersonated identities, and fabricated accounts never substitute for required counted approval. +5. **The solo-maintainer hold is narrow.** When fewer than two eligible independent maintainers exist and live GitHub policy does not independently require counted non-author approval, an otherwise impossible repository-level independent-review requirement is held. CI, security, SAST, exact owned-code coverage, rustdoc, unresolved findings/threads, live-base, mergeability, branch protection, release, and operational evidence remain mandatory. +6. **Reviewer provisioning is a first-class state.** If live policy requires independent approval but no eligible reviewer route exists, the PR is reviewer-provisioning-blocked. The remedy is legitimate reviewer/team/App provisioning or an authorized governance change, never self-approval or gate weakening. +7. **The hold reverses automatically.** Independent-review enforcement returns when two or more eligible independent maintainers exist, live GitHub policy requires it, or an Accepted successor defines another legitimate counted-review route. +8. **Indexes discover; they do not grant status.** `docs/README.md` and `docs/adr/README.md` must mirror each ADR's explicit lifecycle metadata and protected-main location. +9. **Design authority is not implementation evidence.** Even an Accepted ADR does not prove described behavior is implemented or released; protected-main code/tests/artifacts/configuration and claim-appropriate operational evidence establish that truth. + +## Consequences + +The repository can remain review-realistic without weakening technical gates, and maintainer-topology changes have explicit re-enablement semantics. The trade-off is that reviewer eligibility and live policy must be re-evaluated when governance changes; some otherwise-green work may legitimately remain blocked on reviewer provisioning. + +## Failure and degraded behavior + +- If live review requirements cannot be determined, do not infer permission to accept or merge; treat review authority as unresolved and continue non-conflicting work. +- If a required reviewer cannot be provisioned under current authority, classify the exact PR/head as reviewer-provisioning-blocked. +- If an ADR index and file disagree, the documentation contract fails until repaired. +- If an Accepted ADR describes behavior absent from protected-main implementation evidence, product docs must label that capability partial/planned rather than shipped. + +## Security / privacy / governance impact + +This is governance hardening. It prevents automation from manufacturing social proof, preserves branch/ruleset authority, and keeps model/check output non-authoritative for approval. It introduces no new secret or personal-data path. + +## Tests and acceptance evidence + +The documentation contract must prove that every ADR file is indexed exactly once in both canonical indexes, index status matches file metadata, Accepted and Proposed entries are not silently interchanged, superseded decisions retain discoverable successors where applicable, and active-PR ADRs are not presented as protected-main implementation evidence. README prose should point to `AGENTS.md`, live GitHub policy, and this ADR instead of independently redefining the acceptance algorithm. + +Operational acceptance for an actual merge additionally requires a current-authority probe of GitHub rules and reviewer eligibility whenever counted review matters; a documentation test cannot prove runtime reviewer eligibility. + +## Migration and rollback + +No database or runtime migration is introduced. On acceptance, duplicate binding review logic should be removed from ADR-index prose and replaced by concise references to `AGENTS.md`, live GitHub policy, and this ADR. A superseding governance change must update both indexes and contributor-governance documentation coherently. + +## Open follow-ups + +- Keep the machine-checkable ADR-index/status contract aligned with lifecycle and supersession states. +- Re-evaluate reviewer topology whenever maintainers, teams, Apps, or branch rules change. +- Keep scheduler prompts subordinate to protected-main `AGENTS.md` and live GitHub policy. +- Record any future organization-wide reviewer authority and its eligibility boundary in an Accepted successor before relying on it as repository-specific governance. + +## Supersession / reversal conditions + +Supersede this ADR if GitHub governance changes to a materially different review model, the organization adopts a managed independent-review service/team with explicit eligibility semantics, or OriginWeave changes its ADR lifecycle. A successor must retain the prohibitions on synthetic approval and on treating technical/model evidence as formal review authority. + +## References + +ContextualWisdomLab. (2026). *Agent development contract* [Repository specification]. *OriginWeave*. [`../../AGENTS.md`](../../AGENTS.md) + +ContextualWisdomLab. (2026). *OriginWeave architecture decision records* [Repository specification]. *OriginWeave*. [`README.md`](README.md) + +GitHub. (n.d.). *Approving a pull request with required reviews*. GitHub Docs. Retrieved August 10, 2026, from https://docs.github.com/en/pull-requests/how-tos/review-pull-requests/approving-a-pull-request-with-required-reviews + +GitHub. (n.d.). *About protected branches*. GitHub Docs. Retrieved August 10, 2026, from https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches/about-protected-branches + +Current repository settings remain mutable runtime policy and must be probed live; these references document GitHub review/protection semantics rather than freezing the repository's current configuration into this ADR. diff --git a/docs/adr/README.md b/docs/adr/README.md index 2838a12a0..416231b1c 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -1,22 +1,22 @@ # OriginWeave Architecture Decision Index -This directory contains durable architecture decisions for OriginWeave. A pull-request body, chat transcript, roadmap bullet, or implementation plan may motivate a decision but does not replace an ADR when the decision changes a governing product or authority boundary. +This directory contains durable architecture decisions for OriginWeave. A pull-request body, chat transcript, roadmap bullet, automation prompt, issue, or implementation plan may motivate a decision but does not replace an ADR when the decision changes a governing product or authority boundary. ## Status vocabulary - **Proposed** — under review; not binding and not a shipped claim. -- **Accepted** — governing design decision on protected `main`; acceptance does not by itself prove that every described capability is implemented. +- **Accepted** — governing design decision on protected `main`; acceptance does not itself prove that every described capability is implemented. - **Superseded** — replaced by a later Accepted ADR; retained for history. - **Deprecated** — still discoverable but no longer recommended for new work. - **Rejected** — evaluated and intentionally not adopted. -An ADR becomes Accepted only through normal protected-branch review and merge. Where live repository policy or explicit CWL/OriginWeave governance requires independent review, acceptance also requires a qualifying non-author formal `APPROVED` review on the unchanged exact head. COMMENTED reviews, check/status results, model verdicts, reactions, author approval, predecessor-head approval, or dismissed reviews never substitute for that requirement. Conversation-derived ideas remain Proposed/Open in PRD/TRD/traceability until the protected process is complete. +Current contributor/review authority is defined by protected-main [`../../AGENTS.md`](../../AGENTS.md) together with live GitHub repository policy. [ADR 0014](0014-architecture-decision-governance.md) records the proposed durable ADR-acceptance model, including reviewer eligibility, the solo-maintainer hold, re-enablement conditions, and the prohibition on synthetic approval. While ADR 0014 is Proposed, it does not override those live authorities. COMMENTED reviews, check/status results, model verdicts, reactions, author approval, predecessor-head approval, or dismissed reviews never substitute for a review that current policy actually requires. -An Accepted ADR is **design authority, not implementation evidence**. Protected-main source, executable tests, built/released artifacts, migrations/configuration, and protected-main operational evidence appropriate to the claim establish current implemented behavior. An ADR may intentionally describe an accepted target that is only partially implemented; the product documents must label that implementation status separately. +An Accepted ADR is **design authority, not implementation evidence**. Protected-main source, executable tests, built/released artifacts, migrations/configuration, and protected-main operational evidence appropriate to the claim establish current implemented behavior. An ADR may intentionally describe an accepted target that is only partially implemented; product documents must label implementation status separately. -## Current protected-main decisions +## Accepted protected-main decisions -| ADR | Decision | Protected-main status | Governs | +| ADR | Decision | Status | Governs | |---|---|---|---| | [0001](0001-chromium-compatibility-kernel.md) | Retain Chromium as the compatibility kernel | Accepted | Blink/V8/graphics/extensions boundary; Rust control-plane integration | | [0002](0002-agent-safety-kernel.md) | Agent safety kernel | Accepted | mode, capability, origin, risk, crawler, secret and approval policy | @@ -24,13 +24,19 @@ An Accepted ADR is **design authority, not implementation evidence**. Protected- | [0004](0004-resolved-destination-policy.md) | Logical origin and resolved destination safety | Accepted | SSRF/rebinding/special-purpose address and redirect authority | | [0005](0005-direct-socket-binding.md) | Exact direct TCP peer binding | Accepted | explicit socket authority and operating-system peer proof | | [0006](0006-tls-server-identity.md) | TLS service identity over the verified peer | Accepted | WebPKI identity, roots, time, ALPN and stream binding | +| [0007](0007-purpose-bound-sensitive-data-authority.md) | Purpose-bound sensitive-data authority | Accepted | tenant/task/field/purpose/destination/classification disclosure authority | +| [0008](0008-leaf-validity-horizon.md) | Delegated-task TLS leaf-validity horizon | Accepted | minimum certificate-validity horizon for bounded delegated tasks | +| [0010](0010-session-context-bound-node-authority.md) | Session/context-bound node authority | Accepted | browser-session, browsing-context, origin, document-epoch and stale-node authority | + +## Proposed architecture decisions -## Proposed target-architecture decisions in this change +Proposed ADR files are reviewable target architecture without becoming Accepted or shipped behavior. The provenance subsections distinguish files already present in the protected-main baseline from decisions introduced by this documentation reconciliation. Provenance never changes lifecycle: file presence on an active branch is not protected-main truth, and later integration does not itself promote a Proposed ADR to Accepted. -The following ADRs make the product-wide target architecture reviewable without promoting it to shipped behavior. They remain **Proposed** until their exact branch is reviewed and merged under protected-main policy. Existing feature PRs may independently carry lower-numbered Proposed ADRs; the `0100` range avoids claiming or conflicting with those active decisions. +### Protected-main baseline proposed decisions | ADR | Decision | Status | Governs | |---|---|---|---| +| [0009](0009-hourly-agent-credential-boundary.md) | Hourly agent credential boundary | Proposed | deterministic gates, NVIDIA credential materialization, local broker and publication separation | | [0100](0100-rust-control-plane-boundary.md) | Rust control-plane boundary | Proposed | Rust-owned product authority versus Chromium compatibility kernel | | [0101](0101-isolated-execution-profile-modes.md) | Isolated execution/profile modes | Proposed | Human, Assist, Agent Task and Crawler session/profile isolation | | [0102](0102-typed-actions-and-arbitrary-js.md) | Typed actions over arbitrary JavaScript authority | Proposed | action API, script escape hatches, risk/policy semantics | @@ -42,7 +48,29 @@ The following ADRs make the product-wide target architecture reviewable without | [0108](0108-crawler-policy.md) | Policy-bound crawler mode | Proposed | robots, rate/resource policy, read-only collection and no-evasion behavior | | [0109](0109-hourly-automation-operational-closure.md) | Hourly automation secret ordering and operational closure | Proposed | deterministic gates, model secret boundary, retries and protected-main proof | -Active feature PRs may contain additional Proposed ADRs. Those ADRs are not described as Accepted until their exact changes merge. When an ADR becomes protected-main architecture, update this index in the same protected change or an immediately coupled documentation repair. +### Proposed decisions introduced by documentation reconciliation + +| ADR | Decision | Status | Governs | +|---|---|---|---| +| [0013](0013-manifest-v3-extension-authority.md) | Manifest V3 compatibility and extension-to-Agent authority | Proposed | Chromium extension compatibility evidence, profile separation, extension grants, native-messaging boundary and release claims | +| [0014](0014-architecture-decision-governance.md) | Architecture decision acceptance governance | Proposed | ADR lifecycle authority, reviewer eligibility, solo-maintainer hold and re-enablement conditions | + +ADR 0013 and ADR 0014 exist only on this documentation branch until it integrates. After integration, this subsection remains historical provenance rather than an active-PR claim; both decisions remain Proposed until a later policy-compliant change explicitly changes their lifecycle. + +Other active feature PRs may contain additional Proposed ADRs. Those files are not part of this canonical documentation line until integrated or deliberately reconciled here. Historical PR checks, stale branch state, or chat decisions never transfer ADR acceptance across a changed head. + +## Index completeness rule + +Every numbered ADR file in the canonical documentation tree under review must be discoverable from this index with a status that agrees with the ADR's own lifecycle metadata. The protected-main subset must remain exact, while feature ADRs outside this canonical line belong in their owning PR's traceability until integration. When an ADR is added, accepted, superseded, deprecated, or rejected, update this index in the same protected change or an immediately coupled documentation reconciliation. + +The machine-checkable documentation contract should fail when: + +- a numbered ADR file in the canonical documentation tree is absent from this index; +- this index claims `Accepted` while the ADR metadata says `Proposed`, or the reverse; +- a superseded ADR lacks a discoverable successor; +- branch provenance is presented as lifecycle status or protected-main implementation evidence; +- an active-PR ADR is presented as protected-main implementation evidence; or +- a stale PR number, SHA, run ID, automation prompt, or conversation statement is used as timeless architecture authority. ## Decisions that require a dedicated ADR @@ -59,9 +87,10 @@ A new or superseding ADR is required when a change materially alters any of the 9. resource-governor priority, telemetry or GPU/CPU fallback semantics; 10. evidence/provenance identity, retention or persistence boundaries; 11. WebDriver BiDi, CDP, WebMCP, MCP or OriginWeave Protocol authority/version boundaries; -12. Manifest V3 extension-to-agent authorization; +12. Manifest V3 extension-to-agent authorization or compatibility evidence policy; 13. tenant, privacy, residency, audit, deployment or enterprise-control ownership; -14. release acceptance, rollback/recovery or protected-main operational-proof requirements. +14. hourly automation credential, writer, continuation or protected-main operational-proof authority; or +15. release acceptance, rollback/recovery or protected-main operational-proof requirements. ## Required ADR structure @@ -100,5 +129,6 @@ Material external standards or research belong in APA 7th format in [`../doctori - [`../uml/README.md`](../uml/README.md) visualizes component, sequence, state and deployment relationships. - [`../erd/README.md`](../erd/README.md) defines the conceptual durable domain model. - [`../traceability/README.md`](../traceability/README.md) maps requirements and decisions to implementation and evidence. +- [`../DOCUMENTATION_FITNESS.md`](../DOCUMENTATION_FITNESS.md) records semantic completeness and stale/current findings across the graph. -If these artifacts disagree about what is currently implemented, protected-main source, executable tests, built/released artifacts, configuration/migrations, and protected-main operational evidence appropriate to the claim define implementation truth. Accepted ADRs explain the governing design decision and expected boundary; they do not upgrade missing behavior into shipped behavior. The disagreement is a documentation or implementation defect that must be repaired rather than silently rationalized from conversation history. +If these artifacts disagree about current implementation, protected-main source, executable tests, built/released artifacts, configuration/migrations, and protected-main operational evidence appropriate to the claim define implementation truth. Accepted ADRs explain governing design decisions; they do not upgrade missing behavior into shipped behavior. The disagreement is a documentation or implementation defect that must be repaired rather than silently rationalized from conversation history. \ No newline at end of file diff --git a/docs/doctoring/browser-agent-protocols.md b/docs/doctoring/browser-agent-protocols.md new file mode 100644 index 000000000..5173a32e6 --- /dev/null +++ b/docs/doctoring/browser-agent-protocols.md @@ -0,0 +1,79 @@ +# Browser and Agent Protocol Standards Evidence + +- **Reviewed:** 2026-08-10 +- **Purpose:** primary-source evidence for OriginWeave browser compatibility and adapter boundaries +- **Canonical research index:** [`../doctoring.md`](../doctoring.md) + +This addendum complements the main doctoring record. The main record already carries the WebDriver BiDi, WARC/ISO 28500 and W3C PROV-O evidence. This addendum records the current primary sources for Manifest V3, Chrome DevTools Protocol, WebMCP and Model Context Protocol so product documentation does not rely on uncited protocol names. + +## WebDriver BiDi + +The W3C publication reviewed for this baseline is the 1 June 2026 **Working Draft**, not a Recommendation. OriginWeave therefore treats BiDi as a versioned browser-automation adapter rather than product-internal authority. Raw BiDi session/context/node identifiers do not become durable OriginWeave identities. + +Primary source: World Wide Web Consortium, *WebDriver BiDi*. + +## Chrome Manifest V3 + +Chrome's current manifest documentation identifies Manifest V3 as the current extension manifest format and the supported `manifest_version` value. OriginWeave therefore tests its declared extension compatibility against a pinned real Chromium/Chrome-for-Testing build and publishes evidence by exact capability. This is a compatibility target, not a claim of universal Chrome/Web Store/Google-service/codec/DRM equivalence. + +A Chrome extension permission remains separate from an OriginWeave Agent capability. Passing MV3 compatibility tests does not prove Agent-authority isolation, and a correct extension-grant kernel does not prove a real Chrome extension API works. + +Primary source: Chrome for Developers, *Manifest file format* and *Manifest Version*. + +## Chrome DevTools Protocol + +The official CDP documentation states that tip-of-tree changes frequently and provides no backward-compatibility guarantee for capabilities it introduces. OriginWeave therefore pins the Chromium/protocol evidence used by a release and keeps CDP behind an adapter. CDP is useful for Chromium-specific Network, Accessibility, DOMSnapshot, tracing and diagnostic surfaces; it is not the durable OriginWeave authority model. + +Primary source: Chrome DevTools Protocol, *Chrome DevTools Protocol—Latest (tip-of-tree)*. + +## WebMCP + +Chrome's 2026 WebMCP documentation describes WebMCP as an experimental/proposed structured-tool surface and its security guidance explicitly discusses indirect prompt injection and `untrustedContentHint`. The reviewed Chrome material is associated with an origin-trial / intent-to-experiment path. OriginWeave may prefer a valid structured WebMCP tool over lower-level scraping when present, but WebMCP remains optional and adapter-bound. + +WebMCP tool definitions, extension-produced content and tool outputs are untrusted observations. They cannot mint OriginWeave capabilities, alter the trusted task goal, resolve secrets, or approve high-risk actions. + +Primary sources: Chrome for Developers, *WebMCP*; *WebMCP tool security*; *Agent security considerations for WebMCP*. + +## Model Context Protocol + +The Model Context Protocol project released specification version `2026-07-28` on 28 July 2026. That release moved the protocol core toward stateless request/response operation and removed the earlier protocol-session assumptions described by previous releases. OriginWeave therefore keeps durable browser state in explicit OriginWeave application handles and exposes MCP only as a high-level adapter to the Rust runtime. MCP clients or servers do not connect models directly to Chromium/CDP authority. + +Primary sources: Model Context Protocol, *2026-07-28 Specification* and the maintainers' official release announcement. + +## Provenance standards + +The main [`docs/doctoring.md`](../doctoring.md) records the stable W3C PROV-O Recommendation and ISO 28500:2017 WARC format. OriginWeave treats both as interoperability/persistence adapters around its typed evidence identities. A WARC record, PROV statement, model judgement, check result or action log is evidence of its own class; none becomes authorization merely because it is captured in a provenance format. + +## Product consequences + +1. Version adapter contracts independently from OriginWeave session/context/action/evidence types. +2. Pin exact Chromium/CDP compatibility evidence at release time. +3. Keep WebDriver BiDi's Working Draft status visible in compatibility claims. +4. Keep WebMCP experimental/optional and propagate untrusted-content semantics. +5. Keep MCP browser state application-level rather than equating protocol transport/session metadata with browser authority. +6. Test Manifest V3 compatibility and extension-to-Agent authority isolation as separate evidence classes. +7. Treat WARC/PROV as provenance representations, not policy or truth escalation. + +## References — APA 7th + +Chrome DevTools Protocol. (n.d.). *Chrome DevTools Protocol—Latest (tip-of-tree)*. Retrieved August 10, 2026, from https://chromedevtools.github.io/devtools-protocol/tot/ + +Google Chrome Developers. (n.d.). *Manifest file format*. Chrome for Developers. Retrieved August 10, 2026, from https://developer.chrome.com/docs/extensions/reference/manifest + +Google Chrome Developers. (n.d.). *Manifest Version*. Chrome for Developers. Retrieved August 10, 2026, from https://developer.chrome.com/docs/extensions/reference/manifest/manifest-version + +Google Chrome Developers. (2026). *WebMCP*. Chrome for Developers. https://developer.chrome.com/docs/ai/webmcp + +Pagnucco, J., & Klepper, A. (2026, June 9). *Agent security considerations for WebMCP*. Chrome for Developers. https://developer.chrome.com/docs/agents/security + +Pagnucco, J., & Klepper, A. (2026, June 9). *WebMCP tool security*. Chrome for Developers. https://developer.chrome.com/docs/ai/webmcp/secure-tools + +Soria Parra, D., & Delimarsky, D. (2026, July 28). *The 2026-07-28 specification*. Model Context Protocol. https://blog.modelcontextprotocol.io/posts/2026-07-28/ + +Model Context Protocol. (2026). *Model Context Protocol specification (2026-07-28)*. https://modelcontextprotocol.io/specification/2026-07-28 + +World Wide Web Consortium. (2013). *PROV-O: The PROV ontology*. https://www.w3.org/TR/prov-o/ + +World Wide Web Consortium. (2026, June 1). *WebDriver BiDi* (W3C Working Draft). https://www.w3.org/TR/2026/WD-webdriver-bidi-20260601/ + +International Organization for Standardization. (2017). *Information and documentation—WARC file format* (ISO Standard No. 28500:2017). https://www.iso.org/standard/68004.html diff --git a/docs/doctoring/mv3-compatibility.md b/docs/doctoring/mv3-compatibility.md index 607a2485b..571c49329 100644 --- a/docs/doctoring/mv3-compatibility.md +++ b/docs/doctoring/mv3-compatibility.md @@ -1,14 +1,54 @@ # Manifest V3 compatibility evidence baseline - **Status:** Active implementation evidence for issue #27 -- **Reviewed:** 2026-08-09 +- **Reviewed:** 2026-08-11 - **Pinned browser:** Chrome for Testing `150.0.7871.129`, Chromium revision `r1639810` -OriginWeave uses Chromium as its compatibility kernel, so browser-extension compatibility must be demonstrated with executable Chromium evidence rather than inferred from architecture alone. This first bounded lane exercises a controlled unpacked Manifest V3 extension against one exact Chrome for Testing build. It covers an extension service worker, content-script injection, `chrome.storage.local`, declarative network blocking, and one real WebDriver click/post-condition. It does **not claim 100% Chrome extension compatibility** and does not make claims about Chrome Web Store distribution, Google-only services, proprietary codecs, DRM, native messaging, enterprise policy, restart/update migration, or every Chrome extension API. +OriginWeave uses Chromium as its compatibility kernel, so browser-extension compatibility must be demonstrated with executable Chromium evidence rather than inferred from architecture alone. The protected-main lane exercises a controlled unpacked Manifest V3 extension against one exact Chrome for Testing build and proves service-worker, content-script, storage, declarative-network-request, tabs, windows, scripting, commands, side-panel, bookmarks/history read compatibility, restart persistence, repeatability, and one real WebDriver click/post-condition. Active stacked compatibility work adds downloads, bounded bookmark/history mutation, profile isolation, explicit extension update/version-migration evidence, and an exact content-script isolated-world check. OriginWeave does **not claim 100% Chrome extension compatibility**. -The checked-in fixture is intentionally local-only. Its host permission is limited to loopback HTTP used by the deterministic test server. It contains no remote code, user credential, model call, external content, native-messaging host, or production PII. Chrome permissions remain distinct from the explicit OriginWeave extension-to-Agent grant implemented in `originweave-core`. +The checked-in fixture is intentionally local-only. Its host permission is limited to loopback HTTP used by the deterministic test server. It contains no remote code, user credential, model call, external content, native-messaging host, or production PII. Chrome permissions remain distinct from the explicit OriginWeave extension-to-Agent grant implemented in `originweave-core`. Compatibility mutation tests create only controlled synthetic state inside the ephemeral test profile and must clean it up; successful API compatibility never grants the OriginWeave Agent ambient bookmarks/history/downloads authority. -The CI lane downloads the exact Chrome/ChromeDriver version from the official Chrome for Testing public bucket, records SHA-256 receipts for the downloaded archives, verifies the runtime-reported browser version, and emits bounded JSON compatibility evidence. A future release-quality compatibility matrix should additionally pin published artifact digests or equivalent immutable supply-chain identity when the upstream distribution exposes that identity in an authoritative machine-readable form. +## Supported-capability evidence matrix + +This matrix separates protected-main executable evidence from active, non-shipped evidence and from genuinely unproven surfaces. A row marked **ACTIVE_PR** is never a release claim; exact head/run provenance belongs in `docs/evidence/2026-08-10-active-pr-maturity.md` and must be refreshed when the branch changes. + +| Compatibility surface | Evidence maturity | Current evidence boundary | Known gap / non-claim | +|---|---|---|---| +| Manifest V3 unpacked extension load | **PROTECTED_MAIN** | Exact pinned Chromium fixture loads through the dedicated compatibility workflow. | No Chrome Web Store distribution or arbitrary third-party extension-install claim. | +| Service worker start/restart + event response | **PROTECTED_MAIN** | Worker startup count and message response are observed across a real browser restart. | Suspend timing and the full Chrome event catalog are not exhaustively covered. | +| Content-script injection | **PROTECTED_MAIN** | Controlled content script mutates bounded DOM evidence on loopback. | Injection alone does not prove JavaScript isolated-world semantics. | +| Content-script isolated-world separation | **ACTIVE_PR #61** | Page main-world and extension isolated-world JavaScript assign the same sentinel name to distinct values; compatibility reports ready only while the page still reads `page` and the content script reads `extension` in real pinned Chromium. | One deterministic fixture proof only; no arbitrary page-JavaScript bridge or Agent authority. | +| `chrome.storage.local` + restart persistence | **PROTECTED_MAIN** | State is initialized on the first browser pass and required to persist on restart. | No OriginWeave-owned durable application database is implied. | +| `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. | +| 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. | +| Per-trial Agent Task profile isolation | **ACTIVE_PR #49** | Compatibility trials use isolated ephemeral profiles rather than ambient human state. | Full production Agent Task browser orchestration remains issue #28 work. | +| Extension update/version migration | **ACTIVE_PR #60** | Trial-local extension copy transitions `1.0.0` → `1.0.1` on the same ephemeral profile; versioned storage state is required to migrate and real pinned-Chromium evidence reports the update-migration surface. | No Chrome Web Store updater, enterprise deployment channel, arbitrary downgrade, or protected-main release claim. | +| Managed enterprise extension policy | **PLANNED** | No protected-main executable compatibility proof yet. | Do not infer managed-policy support from Chromium ancestry alone. | +| Native messaging | **PLANNED / SECURITY-GATED** | No compatibility claim. | Future support requires an explicit host-managed allow-list and process boundary. | +| Google-only services, proprietary codecs, DRM, Web Store licensing | **OUT_OF_SCOPE FOR COMPATIBILITY CLAIM** | Deliberately excluded from the open compatibility claim. | Chromium/API compatibility must not be conflated with Google service or licensing equivalence. | + +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. + +## 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. + +## Update-migration evidence boundary + +Restart persistence and extension update migration are separate compatibility claims. A successful restart proves only that state survives a new browser process. The active update-migration lane additionally uses a trial-local copy of the checked-in fixture, preserves the same extension path and ephemeral profile across passes, changes only the controlled manifest version from `1.0.0` to `1.0.1`, observes `chrome.runtime.getManifest().version`, and requires the fixture schema marker to migrate from version 1 to version 2. The checked-in fixture is not rewritten by the test. This establishes one deterministic unpacked-extension version transition; it does not establish Chrome Web Store update behavior, enterprise rollout semantics, downgrade behavior, or arbitrary third-party extension migration safety. + +## Isolated-world evidence boundary + +Content-script injection and content-script JavaScript isolation are separate compatibility claims. Active PR #61 writes `window.originweaveWorldSentinel = "page"` in the fixture page's main world and repeatedly publishes that value through one controlled DOM attribute. The content script assigns the same global name to `"extension"` in its own execution world, waits a bounded interval, and only reports the existing compatibility surface ready when it simultaneously observes the page's published `page` value and its own `extension` value. If both scripts share one JavaScript global namespace, the page publisher changes to `extension` and real-browser compatibility fails. DOM sharing here is deliberate test evidence, not permission for arbitrary page content to become trusted instruction or Agent authority. + +## Supply-chain and repeatability evidence + +The CI lane downloads the exact Chrome/ChromeDriver version from the official Chrome for Testing public bucket, records SHA-256 receipts for the downloaded archives, verifies the runtime-reported browser version, and emits bounded JSON compatibility evidence. A future release-quality matrix should additionally pin published artifact digests or equivalent immutable supply-chain identity when the upstream distribution exposes that identity in an authoritative machine-readable form. ## Primary references — APA 7th @@ -20,6 +60,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.history*. Google. Retrieved August 11, 2026, from https://developer.chrome.com/docs/extensions/reference/api/history + Chrome for Developers. (n.d.). *Manifest file format*. Google. Retrieved August 9, 2026, from https://developer.chrome.com/docs/extensions/reference/manifest Bynens, M. (2023, June 12). *Chrome for Testing*. Chrome for Developers. https://developer.chrome.com/docs/automation-and-testing/chrome-for-testing diff --git a/docs/evidence/2026-08-10-active-pr-maturity.md b/docs/evidence/2026-08-10-active-pr-maturity.md new file mode 100644 index 000000000..71353dca2 --- /dev/null +++ b/docs/evidence/2026-08-10-active-pr-maturity.md @@ -0,0 +1,57 @@ +# Active pull-request maturity evidence series — opened 2026-08-10 + +- **Evidence series opened:** 2026-08-10 +- **Last refreshed:** 2026-08-11 +- **Filename semantics:** the date in this filename is the date this evidence series was opened; refresh provenance is recorded separately and is never backdated to match the filename. + +This dated appendix records volatile implementation evidence that must not be embedded as timeless architecture truth. Protected `main` remains the only shipped-code authority. Active pull requests are implementation evidence only until they integrate and protected-main acceptance is re-established. + +## Protected-main anchor + +- Protected `main`: `67af7c87589edc2039545af335c95064d9b8391c` +- Product status: pre-alpha +- Documentation verdict: **DESIGN-SUFFICIENT / PROTECTED-MAIN-PARTIAL** + +## Active implementation evidence + +| PR | Scope | Maturity | Dependency / evidence boundary | +|---|---|---|---| +| #37 | Bounded HTTP/1.1 over authenticated governed transport | **IMPLEMENTED_ON_ACTIVE_PR** | Exact head `9becaaf61f10d854b20ebd2e04ccd3f57dee97fe` is mergeable and passes CI `31439440664`, Security Scan `31439440691`, SAST Semgrep `31439440663`, exact owned production coverage and CodeRabbit exact-head status. Protected main still reports HTTP as Planned. Historical #11 remains predecessor lineage until protected integration. | +| #40 | Browser protocol identifier → OriginWeave authority registry | **IMPLEMENTED_ON_ACTIVE_PR** | Current exact head `9e635e80e9813a1d2a9c408155d52221b76eeed3` is gate-clean across CI, Security Scan, SAST, Manifest V3 Compatibility and CodeRabbit; the real browser adapter remains Planned under #28. | +| #43 | Real pinned-Chromium Manifest V3 downloads compatibility | **IMPLEMENTED_ON_ACTIVE_PR** | Exact head `27ce89066ed1473dcd66eb26a2f91becf9df5424` is gate-clean; this proves one declared compatibility surface, not full extension compatibility or Agent authority. | +| #44 | Canonical documentation reconciliation | **IMPLEMENTED_ON_ACTIVE_PR** | This branch owns the documentation repair itself; its content does not become protected-main truth until integration. Current-head evidence must be read from the live PR because every reconciliation commit intentionally invalidates predecessor-head exactness. | +| #45 | Credential-free sensitive-handle lifecycle evidence | **IMPLEMENTED_ON_ACTIVE_PR** | Exact head `0f07fea031090c72a448fd9501b49d4dd7568419` is gate-clean; trusted broker/storage/value resolution remain Planned under #10. | +| #46 | In-process authoritative sensitive-handle use reservation | **IMPLEMENTED_ON_ACTIVE_PR** | Exact head `5f212cdfbf3c453472069973138fd9563cf7bff8` is gate-clean; no cross-process/database transactionality or protected-value resolution is claimed. | +| #47 | Bounded resolution freshness authority | **IMPLEMENTED_ON_ACTIVE_PR** | Exact head `6b5ed4dcea281b505f67db6180bb14c3bc95b392` is gate-clean. Its first-party consumer is now implemented on stacked #50, but neither capability is protected-main truth until dependency-ordered integration. | +| #48 | TLS revocation-material freshness primitive | **IMPLEMENTED_ON_ACTIVE_PR** | Exact head `9bbe12860436027a3b7cd5786775f1dacfbc835d` is gate-clean; no OCSP/CRL acquisition, signature validation, cache, or unrevoked claim is implemented. | +| #49 | Ephemeral Agent Task profile-isolation regression | **IMPLEMENTED_ON_ACTIVE_PR** | Draft stacked on #43 at exact head `96a4e949d96b5794ef473ccf813987b8e69ea566`; CI is green but dependency-gated and not independently integrable before #43. | +| #50 | First-party network consumption of resolution freshness | **IMPLEMENTED_ON_ACTIVE_PR** | Draft stacked on exact #47 head `6b5ed4dcea281b505f67db6180bb14c3bc95b392`. Exact head `f8b43bc94444986ab23aa4ef3086e446a0b39295` structurally hides the untimed public network planner, migrates first-party TLS integration helpers through `FreshConnectionPlan`, and passes CI run `31408474576` including exact owned function/line/region/branch coverage; CodeRabbit exact-head status is success. Dependency order, not implementation incompleteness, keeps the PR Draft. | +| #51 | Browser-task runtime telemetry plus one-PID Linux RSS sampling | **IMPLEMENTED_ON_ACTIVE_PR** | Exact head `dab26e4e9652408fb67dc8eedf9fd1820e524805` validates browser/task telemetry and samples one explicitly supplied Linux PID through strict `/proc//status` `VmRSS` parsing. CI `31441792029`, production coverage job `93627900171`, Security Scan `31441792000`, SAST Semgrep `31441791982` and CodeRabbit exact-head status succeed. Chromium PID discovery, task attribution, process-set accounting, GPU/VRAM and cross-platform sampling remain separate responsibilities. | +| #52 | Bounded semantic-node observation and relationship value contract | **IMPLEMENTED_ON_ACTIVE_PR** | Draft stacked on #40. Exact head `94fd284fe41746eeba9edc05d9753903b1c41ebf` adds at most 128 ordered child relationships, optional parent linkage, exact session/context/origin/document authority matching, self/duplicate rejection and stable credential-free errors. CI run `31428454410`, Manifest V3 Compatibility run `31428454350`, and CodeRabbit exact-head status succeed, including exact owned production function/line/region/branch coverage. The value contract still performs no browser I/O or action dispatch. | +| #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. | +| #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. | +| #60 | Real pinned-Chromium extension update/version migration | **IMPLEMENTED_ON_ACTIVE_PR** | Draft stacked on #59. Test-only head `a60875f70f8412db27ff1025b75d7ad4b8ddc38e` and CI run `31433305976` established the intended RED because no trial-local extension copy, version transition, migration state, or update-migration evidence existed. Current exact head `e696e19c9eaf3dedb104a5de4bdbd7970abf90d4` uses an ephemeral extension copy and one profile across initial `1.0.0`/initialized → restart `1.0.0`/current → update `1.0.1`/migrated passes. CI run `31433968874`, Manifest V3 Compatibility run `31433968931`, and CodeRabbit exact-head status succeed; the real browser evidence reports 3/3 trials and the exact update-migration surface. This does not claim Chrome Web Store/enterprise update semantics or Agent authority. | +| #61 | Real pinned-Chromium content-script isolated-world separation | **IMPLEMENTED_ON_ACTIVE_PR** | Draft stacked on #60. Test-only head `e81cdbd9b31a62227698bd3d824fd901551061f0` and CI run `31434443638` established the intended RED because the fixture had no page-main/content-isolated sentinel contract. Current exact head `c1705ad9fd2d96e620b89bb6e7ea1235063dcb6a` requires the page to retain `window.originweaveWorldSentinel = "page"` while the content script independently retains the same-named global as `"extension"`; the existing content compatibility surface fails if the JavaScript worlds collapse. CI run `31434670642`, Manifest V3 Compatibility run `31434670629`, and CodeRabbit exact-head status succeed; real browser evidence reports 3/3 repeatability trials. Compatibility evidence only; no arbitrary page-JavaScript bridge or Agent authority. | +| #62 | Extension proposal → Agent policy isolation regression | **IMPLEMENTED_ON_ACTIVE_PR** | Ready PR based directly on protected main. Exact head `a57873b3688984711918be17aadd348ed9fb12a9` first proves the exact extension/session/context `ProposeTypedAction` grant is allowed, then proves ordinary Agent policy independently rejects an out-of-grant target origin, a missing core `Navigate` capability, `WebContent` as an untrusted instruction source, raw secret delivery and unexpected secret material. CI run `31436844685`, production coverage job `93612736291`, Security Scan run `31436844615`, SAST Semgrep run `31436844646`, and CodeRabbit exact-head status succeed. This adds no production API or real Chromium adapter and does not convert extension proposal authority into Agent action/origin authority; it also cannot manufacture secret authority. | +| #63 | Extension proposal → secret high-risk approval isolation | **IMPLEMENTED_ON_ACTIVE_PR** | Ready PR based directly on protected main at exact head `e83749acd1cf5a0b778ba38eb9d6ed5a9bd1e68f`. The exact extension grant allows `ProposeTypedAction`, while ordinary Agent policy still returns `RequireApproval(RiskClass::R3)` for broker-handle `FillSecret`. CI `31437994464`, Rust contracts job `93616406126`, production coverage job `93616406182`, Security Scan `31437994491`, SAST Semgrep `31437994454`, and CodeRabbit exact-head status succeed. This is composition evidence only: it adds no secret broker, protected value, authenticated workload identity, browser adapter or approval evidence. | +| #64 | Verified action post-condition evidence with dispatch ordering | **IMPLEMENTED_ON_ACTIVE_PR** | Ready PR based directly on protected main at exact head `2c45411ed9aa0eecca2d06c85659db9f4bb85e4d`. `VerifiedActionOutcomeEvidence` requires verified provenance and caller-supplied monotonic dispatch/observation timestamps; observations before dispatch fail as `PostConditionPredatesDispatch`. CI `31441848670`, production coverage job `93628017556`, Security Scan `31441848649`, SAST Semgrep `31441848615`, and CodeRabbit exact-head status succeed. It is not a browser dispatcher and does not prove trusted clock provenance, real browser dispatch, target linkage, causality or a reached browser condition. | +| #65 | Controlled hostile Agent Task fixture | **IMPLEMENTED_ON_ACTIVE_PR** | Ready PR based directly on protected main at exact head `0888fe3a6ef6da547a37fd075733cc73dc52b2ab`. Test-only head `d2580305f05aba93d10b5342ec1886d601c6752e` and CI `31445088008` established the intended missing-fixture RED. The current fixture provides a labelled semantic form, deterministic same-document state transition and explicitly hidden/untrusted prompt-injection text using synthetic local data only. CI `31445201739`, Rust contracts job `93637824750`, production coverage job `93637824824`, Security Scan `31445201774`, SAST Semgrep `31445201669`, and CodeRabbit exact-head status succeed. It is controlled test infrastructure, not a browser adapter or proof of real Chromium execution. | +| #66 | Bounded explicit browser process-set RSS aggregation/sampling | **IMPLEMENTED_ON_ACTIVE_PR** | Draft stacked on exact #51 head `dab26e4e9652408fb67dc8eedf9fd1820e524805`. A predecessor implementation test incorrectly required two sequential `/proc` RSS reads to be equal; current regression instead verifies the kernel sample's positive byte/unit contract without assuming RSS immutability. Exact head `986958ab8a29b3ca708c80e44df45e1ec5f9f868` accepts at most 256 unique nonzero caller-owned PIDs, rejects empty/duplicate/oversized sets and checked-add overflow, and fails closed if any sampled member is unavailable. CI `31446842334` passes repository contracts, formatting, workspace checks/tests, strict Clippy, rustdoc and exact owned production function/line/region/branch coverage; CodeRabbit exact-head status succeeds. It does not discover Chromium PIDs, prove same-task attribution, walk a process tree/cgroup, or measure GPU/VRAM. | + +## Historical lineage + +PR #11 is a historical HTTP predecessor, not current implementation authority. It may close as superseded only after #37 reaches protected main and unique-work preservation plus protected-main acceptance are revalidated. + +## Interpretation rules + +1. `IMPLEMENTED_ON_ACTIVE_PR` never means shipped. +2. A green active PR does not authorize release or change an ADR lifecycle state. +3. A Draft or stacked PR remains dependency-gated even if its own checks pass. +4. Exact heads and workflow run identifiers are volatile evidence and belong in dated appendices such as this one, not in timeless Architecture/PRD/TRD claims. +5. After an active PR integrates, canonical PRD/TRD/Architecture/UML/ERD/traceability must be re-evaluated from the new protected-main head before reclassifying the capability. +6. A formatting-only or metadata-only correction invalidates predecessor-head exactness: current-head checks must be rerun before a lane is called gate-clean. diff --git a/docs/evidence/2026-08-11-active-pr-maturity-closure.md b/docs/evidence/2026-08-11-active-pr-maturity-closure.md new file mode 100644 index 000000000..b623f847f --- /dev/null +++ b/docs/evidence/2026-08-11-active-pr-maturity-closure.md @@ -0,0 +1,61 @@ +# Active pull-request maturity evidence — 2026-08-11 closure + +- **Protected-main anchor:** `67af7c87589edc2039545af335c95064d9b8391c` +- **Canonical documentation verdict:** **DESIGN-SUFFICIENT / PROTECTED-MAIN-PARTIAL** +- **Last exact-current reconciliation:** 2026-08-12 +- **Scope:** volatile active-PR evidence for PR #73 through PR #106, preserving protected-main truth separately from branch-local implementation evidence + +Protected `main` remains the only shipped-code authority. This appendix records volatile exact-head evidence for active work and must never be read as protected-main implementation, approval, merge, or release evidence. A moved head or prerequisite invalidates the corresponding exact evidence until refetched. + +## Exact-current active lanes + +| PR | Scope | Maturity | Exact evidence / authority boundary | +|---|---|---|---| +| #73 | Bounded Chromium process-tree RSS evidence in the controlled pinned-browser fixture | **IMPLEMENTED_ON_ACTIVE_PR** | Exact head `6ba2d03345fa3153230a7d365fba8284696541a1`, stacked on exact #72 head `e0b5d43c3a869605aaefa2e4752de7b1b641ddbd`; CI `31550050786` and Manifest V3 Compatibility `31550050775` succeeded after non-destructive dependency alignment to current #72. Optional/nonresident `VmRSS` remains representable while malformed/ambiguous evidence fails closed. This is controlled Linux CI evidence, not trusted whole-task process attribution, cgroup authority, GPU/VRAM ownership, or cross-platform telemetry. | +| #74 | Separation of extension proposal-grant evaluation from ordinary Agent action policy | **IMPLEMENTED_ON_ACTIVE_PR** | Exact head `0d492564aa61c9094f1315ee4e234b46a1e63a6c`, directly based on protected main; CI `31464388199`, Security Scan `31464388200`, and SAST Semgrep `31464388210` succeeded. The branch proves independent fail-closed evaluators and does not claim a real extension-message → `ActionRequest` adapter. | +| #75 | Exact sensitive-model route admission across provider/model/region/retention/training/subprocessor/export policy | **IMPLEMENTED_ON_ACTIVE_PR** | Exact head `286f92aae9e298ab7dff1fd81c7850aabd5692ce`, stacked on #69; CI `31474904239` succeeded with exact owned production coverage. Route admission remains metadata policy only and does not disclose protected values, authenticate/invoke a provider, attest runtime region, or execute export. | +| #76 | Extension proposal authority composed with ordinary typed-action policy | **IMPLEMENTED_ON_ACTIVE_PR** | Exact head `3d2fff3daa766e5e6d7f25e7727a18e01ff52a2e`, stacked on #74; CI `31472688287` succeeded. `evaluate_extension_action_proposal` preserves ordinary instruction-source, capability, origin, secret-delivery, risk, approval, mode, purpose, and crawler policy instead of minting them from extension transport. No Chromium message adapter or browser execution is claimed. | +| #77 | Reviewed prompt/output-schema and token-budget policy after exact model-route admission | **IMPLEMENTED_ON_ACTIVE_PR** | Exact head `adb67f8de3e4828db14dfa0e2950b672b60709c5`, stacked on #75; CI `31477512549` succeeded. This is invocation metadata admission, not protected-value disclosure, provider execution, output validation, retention enforcement, fallback, or export. | +| #78 | Raw extension-message action proposals forced into untrusted content provenance | **IMPLEMENTED_ON_ACTIVE_PR** | Exact head `3fd7d563d814a895e20d04fc6bd37371e548a875`, stacked on #76; CI `31477648663` succeeded. The raw proposal exposes no instruction-source selector and is internally classified as `InstructionSource::WebContent`; no transport parsing/authentication or browser execution is claimed. | +| #79 | Exclusive freshness for reviewed model-invocation policy | **IMPLEMENTED_ON_ACTIVE_PR** | Exact head `2ad7a2162b4842fe57f74f69f08b258f4f6a9c07`, stacked on #77; CI `31481128812` succeeded. Authorization requires caller-supplied trusted time before the exclusive `valid_until` deadline; this pure policy layer does not read or attest a clock. | +| #80 | Origin-binding for node-state post-condition evidence | **IMPLEMENTED_ON_ACTIVE_PR** | Exact head `55b1421e25c5b68ca5f3b05fab37db8f4f1e22be`, stacked on #64; CI `31485218503` succeeded. `NodeStateChanged` provenance must match the governed target origin. This does not prove browser dispatch, node/frame identity, trusted clock provenance, or a real observer. | +| #81 | Fail-closed unrelated-conversation-history metadata for sensitive model invocation | **IMPLEMENTED_ON_ACTIVE_PR** | Exact head `0ec604deb1c0293008560e0fcd4af7ccb65d93ad`, stacked on #79; CI `31484982600` succeeded. Any positive `unrelated_history_items` count is denied. The trusted broker must derive this from the actual bounded outgoing message set; a supplied zero is not proof of isolation. | +| #82 | Exact extension-ID + native-messaging-host allow-list authority | **IMPLEMENTED_ON_ACTIVE_PR** | Exact head `28593cf991cc552968da54b722a887252a3695e7`, directly based on protected main; CI `31484721598`, Manifest V3 Compatibility `31484721575`, Security Scan `31484721547`, and SAST Semgrep `31484721542` succeeded. The primitive does not launch a process, parse native-host manifests/messages, communicate over stdio, expose secrets, or grant Agent actions. | +| #83 | Reduced-assurance classification for attached human tabs with known extension influence | **IMPLEMENTED_ON_ACTIVE_PR** | Exact head `204cedb1bee54a40a6d6bc0b97719afd309c3f71`, stacked on exact #82 head `28593cf991cc552968da54b722a887252a3695e7`; CI `31536812406` and Manifest V3 Compatibility `31536812367` succeeded after non-destructive dependency-topology alignment. `NoKnownExtensionInfluence` is explicitly uncertainty-safe and is not proof that extensions are absent or unable to interfere. The PR does not detect installed extensions or attach to a browser. | +| #84 | Separate model-output validation and retention-policy admission | **IMPLEMENTED_ON_ACTIVE_PR** | Exact head `62f69cadbe0b4011fec67f9e482b04c4cacf181b`, stacked on #81; CI `31487969844` succeeded with exact owned production coverage. This is deterministic metadata policy only; it does not inspect output bytes, execute schema validation, persist output, enforce deletion/retention, or attest validator identity. | +| #85 | Managed extension admission for isolated Agent Task profiles | **IMPLEMENTED_ON_ACTIVE_PR** | Exact head `e836e833be920da8764d3dd72e058e02cd9ed72b`, stacked on exact #83 head `204cedb1bee54a40a6d6bc0b97719afd309c3f71`; CI `31536909167` succeeded with exact owned production coverage after dependency-topology alignment. `AgentTaskExtensionPolicy` admits only exact canonical extension IDs and an empty policy denies all. Admission does not install/enable an extension, read enterprise policy, verify signatures/update provenance, mutate a profile, or mint any Agent capability. | +| #86 | Fail-closed sensitive-model fallback selection | **IMPLEMENTED_ON_ACTIVE_PR** | Exact head `a2c391a5e038dc9e3d6978885d9bdd943487294f`, stacked on #84; CI `31495714541` succeeded with exact owned production coverage. Primary route-policy mismatch and unknown/unreviewed fallback fail closed; only a separately reviewed exact fallback route can be selected. This does not probe provider health, invoke models, or execute retries. | +| #87 | Freshness lifetime for model-route availability evidence | **IMPLEMENTED_ON_ACTIVE_PR** | Exact head `b1273d7bc34fffee262be4bd2da24c24520d11db`, stacked on #86; CI `31500975874` succeeded with exact owned production coverage. Availability uses an exclusive validity horizon and caller-supplied trusted time; stale or invalid availability cannot drive fallback. The policy does not establish collection time or clock/provider-health provenance. | +| #88 | Exact route binding for fallback availability evidence | **IMPLEMENTED_ON_ACTIVE_PR** | Exact head `121d9d70d6c5592b9dff06d7ba09ee563958bfef`, stacked on #87; CI `31505770116` succeeded. Availability evidence retains the exact provider/model/region/retention/training/subprocessor/export route it describes; cross-route replay fails closed before freshness/state can influence fallback. Runtime route authenticity remains a trusted-adapter responsibility. | +| #89 | Full-field sensitive-model disclosure authority composition | **IMPLEMENTED_ON_ACTIVE_PR** | Exact head `f2fbcae3f07cad722f43580aa5be0b4e691d2a9c`, stacked on #88; CI `31510796392` succeeded with exact owned production coverage. Only explicit `FullFieldDisclosure` plus the same complete sensitive-data authority and independently authorized model invocation can yield metadata-level authorization. No protected bytes are carried or released by this primitive. | +| #90 | Explicit necessity gate for full-field model disclosure | **IMPLEMENTED_ON_ACTIVE_PR** | Exact head `166d68bc8e42f41fcd21965609af222e69fd3d4c`, stacked on #89; CI `31517056344` succeeded with exact owned production coverage. `LowerDisclosurePathAvailable` fails closed for handle/deterministic/local-rule/structured-tool/derived-value alternatives. A caller-supplied necessity value is not proof; a trusted broker must derive necessity immediately before protected-value resolution. | +| #91 | Credential-free sensitive-model disclosure audit metadata | **IMPLEMENTED_ON_ACTIVE_PR** | Exact head `3a33f83af7398038a2581e2e132fabf7183b17af`, stacked on #45; CI `31522457974` succeeded and CodeRabbit exact-head status is successful. Evidence records only bounded reviewed provider/model/region/retention/training/subprocessor/export identifiers linked to sensitive-data request/decision IDs. It does not authorize disclosure, prove runtime behavior, or provide durable/tamper-evident audit sequencing. | +| #92 | Fresh resolution authority required for redirect authorization | **IMPLEMENTED_ON_ACTIVE_PR** | Exact head `b796564d059f7bcbd8177617b6fd46c6edc7dda1`, stacked on #47; CI `31524574783` succeeded with exact owned production coverage. `RedirectGuard::authorize_redirect` requires `FreshResolutionSnapshot` plus caller-supplied trusted monotonic time and rejects pre-approval/expired authority before redirect-chain mutation. It performs no DNS lookup, socket I/O, HTTP redirect following, or clock attestation. | +| #93 | Exact semantic-node target bound to independently classified business action | **IMPLEMENTED_ON_ACTIVE_PR** | Exact head `c150e2daa0c890c8e2797ebb4c88a6220be13019`, stacked on #58; CI `31534945009` and Manifest V3 Compatibility `31534945000` succeeded with exact owned production coverage. `SemanticNodeActionBinding` requires the observed node origin to equal the business request source origin while leaving destination/business risk separate. It does not authorize policy or execute browser input. | +| #94 | Freshness lifetime for managed Agent Task extension admission | **IMPLEMENTED_ON_ACTIVE_PR** | Exact head `759d2f04d81dcf52bca29b88d860fb9aaeca56e8`, stacked on #85; CI `31541736861` succeeded. The policy uses one caller-attested half-open validity window and fails closed for invalid, not-yet-valid, or expired policy. It does not read enterprise policy or attest a clock/profile. | +| #95 | Deterministic policy authorization of one semantic-node/business-action binding | **IMPLEMENTED_ON_ACTIVE_PR** | Exact head `e26a2d07ae731ff35271299036fa1f43c8550039`, stacked on #93; CI `31544103569` succeeded with exact owned production coverage. Only `Decision::Allow` produces `PolicyAuthorizedSemanticNodeAction`; deny and approval-required remain typed non-authorizing outcomes. Policy allow is still not browser execution authority. | +| #96 | Same-call browser-authority revalidation immediately before adapter dispatch callback | **IMPLEMENTED_ON_ACTIVE_PR** | Exact head `c93b90a316b83a160cf80008cc25c78aa32302f9`, stacked on #95; CI `31549013124` succeeded. An earlier exact head exposed one generic-monomorphization coverage miss at `4266/4267` lines and `5569/5570` regions; the current coverage repair drives current and stale epochs through one callback call site and restores exact production function/line/region/branch coverage without changing production behavior. The callback remains a trusted-adapter integration boundary, not Chromium execution or success proof. | +| #97 | Managed Agent Task extension policy bound to one OriginWeave browser session | **IMPLEMENTED_ON_ACTIVE_PR** | Exact head `912e0909169ed2fee1b26bce126f14e9390822bd`, stacked on exact #94 head `759d2f04d81dcf52bca29b88d860fb9aaeca56e8`; CI `31546164773` succeeded with exact owned production coverage. Session mismatch fails before allow-list membership is considered, preventing cross-session policy replay/membership probing. This does not prove Chromium profile identity, enterprise-policy provenance, extension installation state, or Agent capability. | +| #99 | Exact target-node binding for node-state success evidence | **IMPLEMENTED_ON_ACTIVE_PR** | Exact head `8ec18b8701104cf3f3764334601c69c1497297b9`, stacked on exact #80 head `55b1421e25c5b68ca5f3b05fab37db8f4f1e22be`; CI `31551314274` succeeded with exact owned production coverage. Generic outcome construction now rejects `NodeStateChanged`; the node-specific constructor requires the governed and independently observed `ObservedNodeHandle` to match across session, context, canonical origin, document epoch, and node identifier. This prevents a different same-origin node from proving the target's post-condition, but does not authenticate a browser adapter or observe the condition itself. | +| #100 | Bounded semantic role/name discovery in the controlled pinned-Chromium Agent Task | **IMPLEMENTED_ON_ACTIVE_PR** | Exact head `13f49b7fc4f11d0fd851f51d816dc0cc94003b91`, stacked on exact #73 head `6ba2d03345fa3153230a7d365fba8284696541a1`. Test-only predecessor `e0e4cef2546c0564ba86b6301a3375656ed988ae` established the missing semantic-locator repository-contract RED. The current implementation enumerates at most 128 controlled WebDriver candidates, reads browser-computed role/name, requires exactly one exact match for the input and submit controls, removes direct CSS target discovery from the Agent Task action path, and adds focused exact/zero/duplicate/oversized/malformed candidate regressions. CI `31553583901` and Manifest V3 Compatibility `31553583902` succeeded on the current head with exact owned production coverage. This fixture helper is not the versioned production WebDriver BiDi adapter and does not make CSS enumeration product authority. | +| #101 | Reject known-disabled semantic-node interactive action targets | **IMPLEMENTED_ON_ACTIVE_PR** | Exact head `bd75a43ddcd0a7afa4f032ecc2b930d742c3ece5`, stacked on exact #96 head `c93b90a316b83a160cf80008cc25c78aa32302f9`; CI `31552510321` and Manifest V3 Compatibility `31552510348` succeeded. `SemanticNodeActionTarget::from_observation` now rejects a known-disabled interactive action as typed `NodeNotEnabled` while retaining `ScrollIntoView` because scrolling does not require node-enabled state. This validates only the supplied semantic observation; current enabled state immediately before dispatch remains a trusted-adapter responsibility. | +| #102 | Revalidate a semantic action target against one freshly supplied current semantic observation | **IMPLEMENTED_ON_ACTIVE_PR** | Exact head `c751865412d5642357203f917f16c6c2bbd12324`, stacked on exact #101 head `bd75a43ddcd0a7afa4f032ecc2b930d742c3ece5`; CI `31554288115` and Manifest V3 Compatibility `31554288335` succeeded. The core target rejects a different exact node, removal of the selected action, or newly disabled state for an action that requires enabled state. The trusted runtime still owns re-observation timing and provenance; this method does not observe Chromium or dispatch input. | +| #103 | Same-call current semantic-state revalidation before a policy-authorized adapter callback | **IMPLEMENTED_ON_ACTIVE_PR** | Exact head `c4c32d4305d6485a5e9f2bf202316b216d95f71f`, stacked on exact #102 head `c751865412d5642357203f917f16c6c2bbd12324`; CI `31556233043` succeeded with exact owned production coverage and exact-head CodeRabbit status success. `dispatch_if_current_observation` requires exact target-node identity, retained selected action, and required enabled state before invoking the callback. It does not obtain/authenticate the observation, execute Chromium by itself, authorize later authorities, or prove a post-condition. | +| #104 | Structured-value digest bound to exact node plus verified node/network provenance | **IMPLEMENTED_ON_ACTIVE_PR** | Exact head `b05cf7d5d45f8772fa8ea9f23cdd99d51a599f9e`, stacked on exact #99 head `8ec18b8701104cf3f3764334601c69c1497297b9`; CI `31557992269` succeeded with exact owned production coverage. `StructuredValueEvidence` binds one bounded semantic field identifier and canonical lowercase SHA-256 digest to the exact OriginWeave node plus independently verified DOM/accessibility and `NetworkResponse` provenance from the same canonical origin. It carries no raw extracted value and does not authenticate adapters or persist evidence. | +| #105 | Controlled pinned-Chromium result discovered semantically and emitted only as bounded digest evidence | **IMPLEMENTED_ON_ACTIVE_PR** | Exact head `bc1d22d6c4848a173c55fdd18054574299488067`, stacked on exact #100 head `13f49b7fc4f11d0fd851f51d816dc0cc94003b91`; CI `31559777436` and Manifest V3 Compatibility `31559777419` succeeded. The result is found by browser-computed `status` / `Task result` semantics and trial evidence retains only `task_result` plus a canonical SHA-256 digest; the raw controlled input is absent from emitted JSON evidence. This is executable compatibility evidence, not production adapter or complete source/network provenance. | +| #106 | Versioned browser-protocol adapter metadata and explicit canonical capability set | **IMPLEMENTED_ON_ACTIVE_PR** | Exact head `7a3e8f4689a8b3c0344a250f45ec06995473d21f`, stacked on exact #40 head `9e635e80e9813a1d2a9c408155d52221b76eeed3`; CI `31563580047` and Manifest V3 Compatibility `31563580032` succeeded with exact owned production coverage and exact-head CodeRabbit status success. The descriptor records explicit protocol kind plus bounded adapter/protocol/browser revisions and a non-empty duplicate-free capability set. After test-only RED head `8d2549cbad8cdabfe09a5ee61aa7a7ee1de81cc8` proved caller order changed descriptor identity, the current head normalizes capabilities into one stable rank order. Protocol kind remains descriptive and does not infer support or grant browser/Agent authority. | + +## Documentation-fitness reconciliation + +The repository-wide documentation verdict remains **DESIGN-SUFFICIENT / PROTECTED-MAIN-PARTIAL**. + +- **ADR:** no additional ADR is warranted solely by #73–#106. The browser/extension slices refine authority separation and the Proposed browser-protocol/session/extension target architecture; the sensitive-model slices make existing selective-disclosure architecture more executable without introducing a deployed broker/provider/validator/retention service, persistence owner, or new binding protocol. Proposed ADRs remain Proposed until an explicit protected-main lifecycle transition occurs; branch presence, CI, and policy helpers do not promote them. +- **PRD/TRD/Architecture:** current contracts already require resource evidence to remain distinct from trusted attribution; extension/browser permission not to mint Agent capability, origin, approval, secret, execution, or native-host authority; extension content/messages to remain untrusted; attached-tab extension influence to reduce assurance without converting absence of known evidence into high assurance; semantic-node authority to remain bound to exact browser session/context/origin/document epoch and separately classified business intent; policy authorization to remain distinct from immediate dispatch revalidation and observed success; target discovery to be semantic rather than selector authority for the controlled browser workflow; known-disabled interactive targets to fail closed; a fresh semantic observation to revalidate exact target identity/action/enabled state before use; node-state success evidence to bind the exact governed node rather than any same-origin node; structured extracted evidence to carry no raw value and bind exact node/network provenance; browser protocol kind to remain descriptive rather than implicitly granting capabilities and descriptor identity to be independent of caller capability ordering; resolution freshness immediately before network/redirect use; and AI disclosure to bind necessity, authority, exact route, prompt/schema/tokens, expiry, conversation isolation, fallback availability/route, output validation, retention, and credential-free audit metadata independently from protected-value disclosure. #73–#106 refine these boundaries without changing deployed topology. +- **UML:** existing browser/extension authority, sensitive-data/secret-fill, evidence, destination/network, and product-wide authority views remain sufficient for these policy/value primitives. A detailed production Chromium adapter → versioned protocol capability boundary → semantic observation/discovery → typed policy authorization → immediate exact semantic-state revalidation → real input → exact-node structured post-condition/provenance/recovery/resource-evidence sequence remains mandatory when that executable adapter boundary stabilizes. A trusted sensitive-data broker/provider/validator/retention sequence likewise remains future work until those deployed authorities exist. +- **ERD/data model:** none of #73–#106 introduces OriginWeave-owned durable persistence, migrations, physical ownership/cardinality changes, or rollback state. The conceptual ERD remains truthful. Physical process-sample, extension-policy, native-host, assurance, semantic-node locator/dispatch/outcome, structured-value, browser-adapter, model-route/invocation/fallback/output/audit, broker, provider, validator, retention, clock, or redirect tables would be invented architecture until an actual persistence owner is accepted and implemented. +- **Security/test/release:** exact active heads with recorded GREEN evidence remain branch-local only. Stacked Drafts remain dependency-blocked even when their exact branch checks are green. CodeRabbit success or a skipped Draft review is not independent approval, and predecessor-head success never transfers after head/base movement. +- **Traceability:** this appendix extends exact-current non-shipped evidence through #106, corrects #103 to its exact-current GREEN evidence, records #104/#105/#106 as branch-local implemented evidence, and continues to exclude closed PR #98 after fresh comparison proved its proposed explicit extension instruction-source enum duplicated the narrower active #78 raw-message boundary. Every active-PR statement remains subordinate to protected-main code/contracts and becomes historical immediately when its recorded head or prerequisite moves. + +## Truth boundary + +`IMPLEMENTED_ON_ACTIVE_PR` means only that the exact branch contains the stated behavior with the recorded branch-local evidence; it does not mean shipped. A controlled Chromium runner or role/name locator is not the product browser adapter. A sampled process tree is not trusted whole-task ownership. Extension admission, proposal permission, attached-tab assurance, or native-host grant is not Agent action authority. A browser-protocol descriptor is metadata, not authenticated browser transport or capability authority. A semantic-node binding, enabled-state check, current-observation revalidation, or deterministic policy allow is not browser execution or observed success. Exact-node and structured-value outcome evidence still depend on a trusted adapter to supply real observations and provenance. A fresh resolution value is not proof that DNS or the clock is trusted. Model route, invocation, necessity, availability, fallback, output-policy, and audit-metadata decisions are not protected-value disclosure, provider authentication/execution, runtime region attestation, output-byte validation, retention/deletion enforcement, or durable audit storage. Protected-main maturity changes only after dependency-ordered integration and fresh protected-main acceptance. \ No newline at end of file diff --git a/docs/evidence/2026-08-11-active-pr-maturity-delta.md b/docs/evidence/2026-08-11-active-pr-maturity-delta.md new file mode 100644 index 000000000..703cb6ed8 --- /dev/null +++ b/docs/evidence/2026-08-11-active-pr-maturity-delta.md @@ -0,0 +1,57 @@ +# Active pull-request maturity evidence — 2026-08-11 delta + +- **Protected-main anchor:** `67af7c87589edc2039545af335c95064d9b8391c` +- **Canonical documentation verdict:** **DESIGN-SUFFICIENT / PROTECTED-MAIN-PARTIAL** +- **Relationship to the existing series:** this file advances the dated evidence in [`2026-08-10-active-pr-maturity.md`](2026-08-10-active-pr-maturity.md) for active lanes opened after that appendix was refreshed through PR #66. It is volatile implementation evidence, not timeless architecture truth. + +Protected `main` remains the only shipped-code authority. Active pull requests, exact heads, CI runs, reviews, and coverage reports are evidence about non-shipped work until dependency-ordered integration and fresh protected-main acceptance are re-established. + +## Newly active implementation evidence + +| PR | Scope | Maturity | Exact evidence / authority boundary | +|---|---|---|---| +| #67 | Browser-task interruption and recovery evidence | **IMPLEMENTED_ON_ACTIVE_PR** | Exact head `9d9ebffee234ed4ab662dab7850bd08450ec365b` is stacked on unchanged #64 head `2c45411ed9aa0eecca2d06c85659db9f4bb85e4d`. CI run `31448465680` is successful, including exact owned production function/line/region/branch coverage. The value contract distinguishes an interruption proven before external effect from an effect that may have committed and requires browser-context closure, task-resource reclamation, and evidence finalization before `SafeToRetry`. It does **not** detect Chromium crashes, prove caller-supplied cleanup facts, reconcile external mutations, restart Chromium, dispatch a retry, persist checkpoints, or complete issue #28's real-browser vertical slice. | +| #68 | Identity-bound settlement of failed sensitive-handle reservations | **IMPLEMENTED_ON_ACTIVE_PR** | The lane is stacked on exact #55 head `8d3ccf0a3b99fd9789210dd9798b422431fab7d8`. Exact predecessor head `add3599bee784c58dfaa4275d17c477eaed781a9` passed repository contracts, formatting, workspace tests, strict Clippy and rustdoc but failed exact coverage at `branches=495/496`, `lines=3666/3667`, `regions=4575/4576`. The uncovered production `next_reservation_sequence == None` branch was synthetic/private-test-only, so the production design was replaced rather than weakening the gate. Exact head `17bc00790e75424afd97c8a73800d9b16c766300` replaced the finite sequence with an allocation-bound, non-copyable in-process reservation identity and passed CI `31451682170`. Current exact head `aa46d982b2bf786fe297744ac99f88b6c4c5f4cf` additionally proves a reservation token from one state instance cannot commit or compensate another identical-scope state. Fresh CI run `31451963178` succeeds: repository contracts, formatting, locked workspace/all-target checks, full tests, strict Clippy, rustdoc, and exact owned production function/line/region/branch coverage are green; CodeRabbit exact-head status is also success. The lane still provides no authenticated workload identity, protected-value resolution, durable/cross-process transaction, KMS, persistence, or proof that compensation is truthful. | +| #69 | Immediate pre-disclosure recheck of an exact tracked reservation | **IMPLEMENTED_ON_ACTIVE_PR** | This Draft is stacked on exact #68 head `aa46d982b2bf786fe297744ac99f88b6c4c5f4cf`. Test-only head `5a96d2931225e133768878c68d09e1a36b5ca0f6` established the intended missing-API RED. Production then exposed a real coverage defect in short-circuit recheck branches; focused malformed caller authority/audience cases were added, and two duplicate unreachable immutable-state checks were removed rather than manufacturing private-only coverage. Current exact head `de79d85e6be5131036db119efab767f0eb76a816` passes CI run `31453149013`, including exact owned production function/line/region/branch coverage, and CodeRabbit exact-head status is successful. The boundary rechecks the same still-outstanding reservation immediately before disclosure without consuming another use, but still trusts the future broker to supply authenticated workload audience, trusted time, exact current authority, transactional serialization and the protected-value disclosure boundary. | +| #70 | Controlled Agent Task execution on pinned stock Chromium | **IMPLEMENTED_ON_ACTIVE_PR** | This Draft is stacked on exact #65 head `0888fe3a6ef6da547a37fd075733cc73dc52b2ab`. Test-only head `197ce14a5e407d61ac35b38b45c0cd042dd6278c` established the intended missing-runner RED. Current exact head `f9917cdd8050c9fdf0aefa669f4d981af85479d6` passes CI run `31453647157` and pinned real-browser run `31453647201` against Chrome for Testing `150.0.7871.129` / revision `r1639810`. The controlled Agent Task completes `3/3` trials with real WebDriver clear/type/click operations, exact same-document post-condition verification, extensions disabled, and per-trial profile cleanup. This is reproducible browser execution evidence, not the product browser adapter: fixture CSS locators are test-harness locators, no semantic role/name query authority is claimed, and OriginWeave semantic observation/policy/node-handle composition remains incomplete. | +| #71 | Computed semantic role/name evidence before controlled browser action | **IMPLEMENTED_ON_ACTIVE_PR** | This Draft is stacked on unchanged exact #70 head `f9917cdd8050c9fdf0aefa669f4d981af85479d6`. Exact test-only head `977d2682dc191ca6b26b9de631a3642680abdbc0` produced the intended RED in CI run `31454219111`, Rust contracts job `93664601520`, because the runner had no `_get_element_semantics` boundary. Current exact head `5f1f972f3e9888faa44af184fd54a466d20b6ddb` adds the smallest bounded W3C WebDriver computed-role/computed-label verification before the controlled input and submit actions. CI run `31454448709` succeeds, including exact owned production function/line/region/branch coverage, and pinned real-browser run `31454448710` succeeds on Chrome for Testing `150.0.7871.129` / revision `r1639810`: all `3/3` Agent Task trials report browser-computed `textbox` / `Task text` and `button` / `Submit task` verification, exact post-condition and input echo, extensions disabled, and profile cleanup. CodeRabbit exact-head status is successful. CSS remains a controlled harness locator; this evidence does not itself implement semantic role/name search, a versioned product adapter, OriginWeave node registration/observation composition, policy dispatch, source provenance or real-site compatibility. | +| #72 | Controlled Agent Task runtime resource evidence | **IMPLEMENTED_ON_ACTIVE_PR** | This Draft is stacked on unchanged exact #71 head `5f1f972f3e9888faa44af184fd54a466d20b6ddb`. Exact test-only head `a9402a13c9ed429b8f3be2c623b994a0dfda3bb4` produced the intended RED in CI run `31454745237`, Rust contracts job `93666110420`, because strict Linux RSS parsing/sampling was absent. Current exact head `1a7186085abe926c1d0e5b22c36760965d6e237b` adds bounded `/proc//status` sampling for the ChromeDriver-issued browser PID, exact serialized semantic-observation bytes, monotonic action latency, and task duration. CI run `31454903615` succeeds, including exact owned production function/line/region/branch coverage, and pinned real-browser run `31454903620`, job `93666566904`, succeeds on Chrome for Testing `150.0.7871.129` / revision `r1639810`. All `3/3` trials pass with browser-process RSS `215326720`, `214568960`, and `213716992` bytes; semantic observation size `95` bytes each; action latency `133.282`, `130.186`, and `110.554` ms; and task duration `1144.545`, `957.991`, and `906.982` ms. Artifact ID `9087662526` has uploaded-artifact SHA-256 `a7f8ec5ae716ed723e9dd7ec84eeac3478c30fddd6eb7c9bf0d411f1a8990ee5`; CodeRabbit exact-head status is successful. The RSS metric intentionally covers only the ChromeDriver-reported browser process, not renderer/GPU/utility descendants or whole-task attribution; full trusted process-set composition remains pending the product adapter and #51/#66 contracts. | + +## Documentation-fitness reconciliation + +The addition of #67 through #72 does **not** require another ADR, a new deployed component, or a physical ERD entity at this stage. + +- **ADR:** #67 refines the existing evidence/recovery architecture. #68/#69 refine the in-process sensitive-handle lifecycle governed by Accepted ADR 0007. #70–#72 add executable compatibility/runtime evidence inside the already planned browser-adapter and resource-evidence boundaries. None changes a trust domain, persistence owner, deployment boundary, or binding protocol decision; existing ADR breadth remains sufficient. +- **PRD/TRD:** current requirements already distinguish post-condition evidence from browser dispatch, semantic observation from action authority, resource evidence from trusted process attribution, and purpose-bound sensitive policy from the future trusted broker. #67–#72 remain active/non-shipped evidence and must not be described as `Implemented` on protected main. +- **Architecture/UML:** #70 materially strengthens executable proof that stock pinned Chromium can perform the controlled task, #71 binds the controlled targets to browser-computed semantic evidence before action, and #72 adds measured resource evidence for that controlled browser execution. None creates the versioned WebDriver BiDi/CDP product adapter or composes the existing OriginWeave semantic-node/policy/evidence/resource primitives end to end. The current high-level authority diagrams remain truthful; a detailed adapter → semantic observation → typed policy/action → post-condition/recovery/resource-evidence sequence becomes mandatory when that production composition boundary stabilizes rather than while the evidence runner remains the execution owner. +- **ERD/data model:** #67 is an immutable evidence value, #68/#69 are explicitly in-process policy state, and #70–#72 are ephemeral CI/browser evidence. None creates an OriginWeave-owned durable persistence schema. The conceptual ERD remains the truthful artifact; manufacturing physical tables would overstate the implementation. +- **Security/privacy:** #67 quarantines ambiguous-effect/incomplete-cleanup states. #68/#69 preserve exact reservation identity and immediate pre-disclosure recheck without exposing protected values. #70 uses synthetic local data, disables extensions in the Agent Task profile, and proves profile cleanup. #71 is intentionally limited to bounded browser-computed role/name evidence and does not elevate page content into instruction or capability authority. #72 reads only bounded Linux process status for a ChromeDriver-issued PID and emits resource measurements without credentials or page values. +- **Test/release/traceability:** #67–#72 have fresh exact-head green evidence at this refresh. #71/#72 preserve their observed test-first RED before exact-head GREEN. No predecessor-head success transfers across any moved head, and none of these active lanes is release evidence for protected `main` yet. + +## Interpretation rules + +1. `IMPLEMENTED_ON_ACTIVE_PR` and `PARTIAL` never mean shipped. +2. Exact-head CI/coverage evidence becomes stale immediately when that head moves. +3. A stacked PR cannot be independently integrated before its exact prerequisite lineage. +4. An active implementation refinement does not manufacture a new ADR merely to mirror every PR; create or supersede an ADR only when the governing architecture decision changes. +5. In-memory identities, immutable evidence values, controlled fixtures, bounded samplers, and ephemeral compatibility/resource evidence do not justify physical ERD entities without a real durable ownership boundary. +6. A real browser test harness is not the same authority as the production browser adapter. Promote browser/runtime maturity only when the protected-main product path owns session/context/origin/document identity, semantic observation, typed policy/action dispatch, post-condition verification, recovery, and evidence composition. +7. A single ChromeDriver-reported browser PID is not equivalent to trusted Chromium process-set or task attribution. Whole-browser/task RSS claims require an adapter-owned process set and the existing bounded aggregation contracts. +8. After any of these lanes integrates, re-evaluate PRD/TRD/Architecture/UML/ERD/traceability from the new protected-main head before changing maturity claims. + +## Subsequent active lanes observed in this refresh + +| PR | Scope | Maturity | Exact evidence / authority boundary | +|---|---|---|---| +| #73 | Bounded Chromium root-plus-descendant RSS evidence in the controlled pinned-browser fixture | **PARTIAL** | This Draft remains stacked on unchanged exact #72 head `1a7186085abe926c1d0e5b22c36760965d6e237b`. Earlier exact head `cbf922fccc83782d3e114ed65afbeb6d84ef5ce6` repaired nondeterministic handling of a sampled descendant with no resident `VmRSS` and passed exact CI/coverage plus pinned-browser repeatability, but a subsequent integrity audit found a narrower fail-open ambiguity: `_snapshot_linux_process_evidence` currently catches the strict parser's `exactly one VmRSS` failure and converts it to `None`, so duplicate/ambiguous `VmRSS` records can be normalized to the same absence state as a legitimately nonresident process. Exact test-only head `015e4a5f79c0abee40c6807b481d3afce613c6c4` required a dedicated optional-RSS parser in which absent/zero `VmRSS` yields `None`, exactly one positive field yields bounded bytes, and duplicate/malformed evidence fails closed. CI run `31462156163`, Rust contracts job `93687687157`, checked out that exact test head and produced the intended RED at the missing helper boundary with `KeyError`. Current exact head `ef6f23365f225b825505a58556d6917aeef505a2` removes only the temporary RED probe so the prerequisite stack is not intentionally left failing: CI run `31462292887`, Rust contracts job `93688085184`, Production coverage job `93688085247`, and Manifest V3 Compatibility run `31462292914` are successful. Those green results do not erase the integrity finding. This lane remains **PARTIAL** and must stay Draft until the snapshot source distinguishes legitimate absent/nonresident RSS from duplicate/malformed evidence, the focused regression is restored, and the exact corrected head passes both repository and real-browser gates. It is still controlled Linux `/proc` evidence, not trusted product process ownership, cgroup/per-tab/task attribution, GPU/VRAM attribution, or cross-platform semantics. | +| #74 | Extension proposal permission cannot widen Agent mutation, execution-mode/purpose, crawler/robots, non-delegable-action, or Human-mode authority | **IMPLEMENTED_ON_ACTIVE_PR** | Exact head `ac8b27ee69229070c382ca2199eaf9ec8b1b12db` is based directly on protected main. CI run `31462551154` succeeds and SAST Semgrep run `31462551097` succeeds; Security Scan run `31462551096` is still non-terminal and is **not** counted as passing. Nine integration regressions first prove that the exact extension/session/context grant permits `ProposeTypedAction`, then prove ordinary Agent policy still denies cross-origin `Submit` as `CrossOriginMutation`, same-origin `Submit` without write authority as `OriginNotWritable`, Crawler/PublicCrawl mutation as `CrawlerMutation`, AgentTask/PublicCrawl as `ModePurposeMismatch`, crawler observations with disallowed/unknown/not-applicable robots evidence as `RobotsDisallowed`/`RobotsUnknown`/`RobotsNotApplicable`, R5 `LegalConsent` as `ForbiddenRisk`, and Human mode as `HumanModeNotAgentControlled`. GitHub reports the PR mergeable and no inline review threads are currently returned. This adds no production API or extension authority and does not claim a real Chromium extension adapter exists; exact-current security acceptance remains pending until every required gate is terminal-success. | + +## Reconciliation for #73 and #74 + +The canonical verdict remains **DESIGN-SUFFICIENT / PROTECTED-MAIN-PARTIAL**. + +- **ADR:** neither lane creates a new governing decision. #73 refines controlled evidence inside the existing resource/browser-adapter direction; #74 verifies the already documented separation between extension proposal permission and Agent action/origin/risk/control authority. No new ADR number should be allocated solely to mirror either PR. +- **PRD/TRD/Architecture:** the current contracts already require browser resource evidence to remain distinct from trusted attribution and require extension access not to imply Agent capability/origin/risk/control authority. #73 is now **PARTIAL** because exact RED evidence demonstrates an unresolved snapshot-integrity ambiguity even though the restored branch is green. #74 remains `IMPLEMENTED_ON_ACTIVE_PR` test evidence, but exact-current security acceptance is not complete until all required security gates are terminal-success. Neither is protected-main implementation. +- **UML:** the existing extension-authority view remains sufficient for #74 because no new actor, trust boundary, or execution edge is introduced. #73 remains an ephemeral CI evidence path and does not justify presenting `/proc` process lineage as a product deployment/authority relationship. +- **ERD/data model:** neither lane introduces durable OriginWeave-owned persistence, ownership, cardinality, or migration semantics. The conceptual ERD remains the truthful current artifact. +- **Security/test/release:** #73 preserves both its earlier functional RED→GREEN chain and the newer exact RED proving the evidence-integrity gap; the current green restoration is not a substitute for the source correction. #74 strengthens policy-composition regression evidence without widening authority, while its current security gate set remains incomplete. Neither lane is protected-main release evidence. diff --git a/docs/evidence/2026-08-12-active-pr-maturity-delta.md b/docs/evidence/2026-08-12-active-pr-maturity-delta.md new file mode 100644 index 000000000..af997487f --- /dev/null +++ b/docs/evidence/2026-08-12-active-pr-maturity-delta.md @@ -0,0 +1,30 @@ +# Active pull-request maturity delta — 2026-08-12 + +- **Protected-main anchor:** `67af7c87589edc2039545af335c95064d9b8391c` +- **Canonical documentation verdict:** **DESIGN-SUFFICIENT / PROTECTED-MAIN-PARTIAL** +- **Supersedes volatile evidence only:** the #103 maturity row and the #73-through-#103 scope boundary in `2026-08-11-active-pr-maturity-closure.md` +- **Extends exact-current evidence through:** PR #104 + +Protected `main` remains the only shipped-code authority. This dated delta updates only volatile active-PR evidence that changed after the prior closure appendix. It does not promote active work to protected-main truth, change an ADR status, invent a deployed service, or introduce an OriginWeave-owned physical persistence schema. A moved head or prerequisite immediately makes the corresponding exact evidence historical. + +## Changed exact-current lanes + +| PR | Scope | Maturity | Exact evidence / authority boundary | +|---|---|---|---| +| #103 | Same-call current semantic-state revalidation before a policy-authorized adapter callback | **IMPLEMENTED_ON_ACTIVE_PR** | Exact current head `c4c32d4305d6485a5e9f2bf202316b216d95f71f`, stacked on exact #102 head `c751865412d5642357203f917f16c6c2bbd12324`. Test-only head `64bc330d801dcaddad29cb907fce68a9de367afa` established the missing dispatch-composition boundary. Production head `2bf613a1d185f6d6dbdc6ede23ebad710d067103` then exposed one generic-monomorphization coverage gap. The current head routes success and semantic rejection through one shared typed helper and applies only the required canonical formatting follow-up. CI `31556233043`, Rust contracts job `93989132459`, and Production coverage job `93989132433` all succeeded with exact owned production function/line/region/branch coverage. The callback remains a trusted-adapter integration boundary: this branch does not obtain/authenticate a browser observation, execute Chromium by itself, authorize later network/secret boundaries, or prove a post-condition. | +| #104 | Structured extracted-value digest bound to one exact node plus verified node/network provenance | **IMPLEMENTED_ON_ACTIVE_PR** | Exact current head `b05cf7d5d45f8772fa8ea9f23cdd99d51a599f9e`, stacked on exact #99 head `8ec18b8701104cf3f3764334601c69c1497297b9`. Formatting-only test head `bb6b2fa09998084ee638ee715ecef6bb0b7e8163` established the valid missing-production RED in CI `31556648278`. Production head `63530aa203dccd94c7a4144186da5208353db5c6` exposed an exact coverage omission limited to the new public error-display paths; coverage artifact `9126295264` localized those misses, and head `139ab7d3ce66245b83b5c7d1245a654387431af9` restored exact coverage. A subsequent current-code audit found that punctuation-only structured field identifiers such as `---` were accepted. Test-only head `4cf977fbe5b41c76f14dfbaae2c4766331f668ef` established that data-integrity RED in CI `31557841624`, Rust contracts job `93993752193`, after repository contracts/format/workspace checks passed. Current head `b05cf7d5d45f8772fa8ea9f23cdd99d51a599f9e` requires at least one ASCII alphanumeric byte in addition to the existing alphanumeric/underscore/hyphen syntax. Exact-current CI `31557992269`, Rust contracts job `93994187786`, and Production coverage job `93994187746` all succeeded with repository contracts, formatting, locked workspace/all-target checks, tests, strict Clippy, rustdoc, and exact owned production function/line/region/branch coverage. `StructuredValueEvidence` carries a semantic bounded field identifier, lowercase SHA-256 value digest, exact OriginWeave-owned node handle, verified DOM/accessibility provenance, and verified network-response provenance; both provenance records must match the node's canonical origin. It carries no raw extracted value and does not authenticate the runtime sources or prove locator truth. | + +## Documentation-fitness reconciliation + +The repository-wide verdict remains **DESIGN-SUFFICIENT / PROTECTED-MAIN-PARTIAL**. + +- **ADR:** no new ADR is warranted solely by #103 or #104. Both refine the already documented browser-authority/evidence architecture without creating a new deployed trust domain, binding protocol, or persistence owner. Proposed ADR 0013/0014 lifecycle status is unchanged. +- **PRD/TRD/Architecture:** the existing contracts already require current semantic state to be revalidated immediately before side effect and evidence to preserve exact source authority without raw sensitive values. #104 makes the structured-value evidence boundary executable by binding an exact node to same-origin verified node/network provenance, and now rejects identifier syntax that contains no semantic alphanumeric content; the trusted runtime still owns extraction, source authentication, digest derivation, and timing. +- **UML:** existing browser/authority/evidence diagrams remain sufficient for these value and composition primitives. The still-missing production sequence is the real Chromium adapter obtaining a fresh bounded semantic observation, policy-authorizing and revalidating it, executing real input, observing the exact post-condition, extracting the structured value, deriving node/network provenance, constructing the evidence bundle, and performing cleanup/recovery. +- **ERD/data model:** #103 and #104 add no OriginWeave-owned durable state, migrations, physical cardinality, or rollback record. The conceptual/logical model remains the truthful artifact. Creating physical semantic-observation or structured-value-evidence tables now would be invented architecture. +- **Security/Test/Release:** both exact active heads are branch-local GREEN evidence only. Their success is not protected-main integration, release acceptance, or independent approval, and their stacked prerequisites remain separate authorities. +- **Traceability:** current volatile active evidence now extends through #104. Closed duplicate #98 remains excluded because #78 owns the narrower raw extension-message trust boundary. The earlier #103 `PARTIAL` row is superseded by the exact-current GREEN evidence above. + +## Truth boundary + +`IMPLEMENTED_ON_ACTIVE_PR` does not mean shipped. Current semantic-state revalidation is not browser execution. A structured-value evidence bundle is not the extraction process, source authentication, or proof that arbitrary locator text identifies the exact real node/network response. No active PR changes protected-main maturity until dependency-ordered integration and fresh protected-main acceptance establish it. \ No newline at end of file diff --git a/docs/evidence/2026-08-12-browser-protocol-active-pr-evidence.md b/docs/evidence/2026-08-12-browser-protocol-active-pr-evidence.md new file mode 100644 index 000000000..aa15e8a08 --- /dev/null +++ b/docs/evidence/2026-08-12-browser-protocol-active-pr-evidence.md @@ -0,0 +1,27 @@ +# Browser protocol active-PR evidence — 2026-08-12 + +This dated appendix extends the canonical active-PR maturity reconciliation beyond the 2026-08-11 closure. It is **volatile branch evidence**, not protected-main truth. Any recorded head, prerequisite, review, check, or base movement invalidates the affected row until it is refetched. + +The durable architecture decision remains **Proposed ADR 0107**. Nothing in this appendix promotes that ADR to Accepted, authenticates a browser adapter, or claims the first real Chromium vertical slice is complete. + +| PR | Exact active head | Exact prerequisite/base | Capability maturity | Exact-current evidence | Truth boundary | +| --- | --- | --- | --- | --- | --- | +| #107 `feat(core): fail closed on unsupported browser protocol capabilities` | `72efca6acccc66409a9c38cf57e7f4279b2d8c3a` | #106 `7a3e8f4689a8b3c0344a250f45ec06995473d21f` | `IMPLEMENTED_ON_ACTIVE_PR` | CI `31564417535` success; Manifest V3 Compatibility `31564417533` success; no formal reviews or inline review threads returned; mergeable against the exact prerequisite | `BrowserProtocolAdapterDescriptor::require_capability` fails closed when a capability is not explicitly declared. It does not prove that BiDi/CDP transport implements the declared capability, authenticate an adapter, select a fallback protocol, or grant browser/Agent authority. | +| #108 `feat(core): bind browser adapters to OriginWeave protocol version` | `ea45a91230e003babc33fff08acb9ada4b07957a` | #107 `72efca6acccc66409a9c38cf57e7f4279b2d8c3a` | `IMPLEMENTED_ON_ACTIVE_PR` | CI `31565621081` success; Rust contracts `94016710356` success; Production coverage `94016710349` exact function/line/region/branch success; Manifest V3 Compatibility `31565621018` success; CodeRabbit exact-head status success; no formal reviews or inline review threads returned; mergeable against the exact prerequisite | The descriptor carries one exact OriginWeave protocol generation and rejects version mismatch. Current pre-alpha generation is the already documented `originweave/0.1`. This does not serialize/parse an OriginWeave wire envelope, negotiate compatibility, invoke BiDi/CDP, authenticate the adapter, or grant browser/Agent authority. | +| #109 `feat(core): parse canonical OriginWeave protocol versions` | `ea17243bf3e0a332bc4a62e25207c80697fde067` | #108 `ea45a91230e003babc33fff08acb9ada4b07957a` | `IMPLEMENTED_ON_ACTIVE_PR` | Test-only head `ac780f08ff825fae08c64d993eaaab6fe6817e3b` established the missing parser RED in CI `31566958754` / Rust contracts `94020638413`; current CI `31567354585` success; Rust contracts `94021835140` success; Production coverage `94021835149` exact function/line/region/branch success; Manifest V3 Compatibility `31567354512` success; CodeRabbit exact-head status success; no formal reviews or inline review threads returned; mergeable against the exact prerequisite | `OriginWeaveProtocolVersion::from_str` now accepts only the canonical `originweave/.` rendering and rejects malformed/noncanonical serialized generations. Syntax parsing does not negotiate compatibility, decide support, authenticate transport/adapters, invoke BiDi/CDP, or grant browser/Agent authority. | +| #110 `feat(core): fail closed on browser runtime revision drift` | `f0fc8f9cfc66dd8b7664b058a8243cc2bf9d95e5` | #109 `ea17243bf3e0a332bc4a62e25207c80697fde067` | `IMPLEMENTED_ON_ACTIVE_PR` | Formatting-only test head `03f7b188b4290ed6eb748df102bbe20119e8fa6b` established the missing runtime-revision boundary RED in CI `31567827677` / Rust contracts `94023260031`; current CI `31569036376` success; Rust contracts `94026891755` success; Production coverage `94026891801` exact function/line/region/branch success; Manifest V3 Compatibility `31569036568` success; CodeRabbit exact-head status success; no formal reviews or inline review threads returned; mergeable against the exact prerequisite | `BrowserProtocolAdapterDescriptor::require_runtime_revisions` validates bounded caller-supplied runtime protocol/browser revision tokens and requires exact equality with the pinned descriptor, with deterministic protocol-before-browser mismatch precedence. It does not authenticate the caller, discover or attest the running browser/protocol revisions, negotiate compatibility, invoke BiDi/CDP, or grant browser/Agent authority. | +| #111 `feat(core): validate browser protocol use atomically` | `72c4c3359b745357ec23942efabf13cebaa0f36f` | #110 `f0fc8f9cfc66dd8b7664b058a8243cc2bf9d95e5` | `IMPLEMENTED_ON_ACTIVE_PR` | CI `31569952532` success; Manifest V3 Compatibility `31569952560` success; no formal reviews or inline review threads returned; mergeable against the exact prerequisite | `BrowserProtocolAdapterDescriptor::validate_use` composes exact OriginWeave protocol generation, caller-supplied runtime revision validation, and one explicitly declared capability into a single ordered fail-closed check before returning non-cloneable metadata-validation evidence. It does not validate the runtime protocol family, authenticate an adapter, attest runtime metadata, invoke BiDi/CDP, or grant browser/Agent authority. | +| #112 `feat(core): bind validated browser use to runtime protocol kind` | `9aed5ae21aca022f58253566c87e67be648675bd` | #111 `72c4c3359b745357ec23942efabf13cebaa0f36f` | `IMPLEMENTED_ON_ACTIVE_PR` | Test-only head `6346164b9ec272e3b2bf0333a7c780498697edd3` established the missing runtime-kind boundary in CI `31570696512` / Rust contracts `94031855124`; current CI `31571266998` success; Rust contracts `94033586878` success; Production coverage `94033586779` exact function/line/region/branch success; Manifest V3 Compatibility `31571266946` success; CodeRabbit exact-head status success; no formal reviews or inline review threads returned; mergeable against the exact prerequisite | The same atomic validation boundary now requires caller-supplied runtime protocol family to equal the descriptor's exact WebDriver BiDi/CDP kind before revision or capability checks can authorize metadata validation. This does not authenticate or attest that runtime family, derive it from a running transport, invoke browser I/O, or grant browser/Agent authority. | +| #113 `feat(evidence): record validated browser protocol metadata` | `79aeef1cdc7dffa7b11ae2a7e29867eb1881019d` | #112 `9aed5ae21aca022f58253566c87e67be648675bd` | `IMPLEMENTED_ON_ACTIVE_PR` | Formatted test-only head `f11a88b62c58f076e853ecbf8eb053ab4716f583` passed repository contracts and canonical formatting, then failed the locked workspace check in CI `31572894086` / Rust contracts `94038496074` at the deliberately absent public evidence boundary. Current CI `31573726836` success; Rust contracts `94041068752` success; Production coverage `94041068684` exact function/line/region/branch success; CodeRabbit exact-head status success; no formal reviews or inline review threads returned; mergeable against the exact prerequisite. | `BrowserProtocolValidationEvidence` copies only the validated protocol family, OriginWeave generation, adapter version, upstream protocol revision, browser revision, and capability from one already validated use. The cloneable receipt does not recreate the non-cloneable validation prerequisite, authenticate the adapter/runtime metadata, authorize browser I/O, persist an audit log, or make evidence tamper-evident. | +| #114 `feat(core): bind runtime adapter version before protocol use` | `f368a4e4326b25b8825e0b4ec1753ec23de727e3` | #113 `79aeef1cdc7dffa7b11ae2a7e29867eb1881019d` | `IMPLEMENTED_ON_ACTIVE_PR` | Test-only head `b521bbcc3c70907cdf66a189d932c6cfa8c3a526` failed CI `31576973212` with the new contract still absent; current CI `31578095843` success; Manifest V3 Compatibility `31578095834` success; no formal reviews or inline review threads returned; mergeable against the exact prerequisite | `BrowserProtocolAdapterDescriptor::validate_use` now validates bounded runtime adapter-version metadata and requires exact equality with the descriptor before runtime revision or capability checks. This does not authenticate the runtime adapter, attest where the version came from, invoke BiDi/CDP, or grant browser/Agent authority. | +| #115 `feat(core): bind browser protocol validation to dispatch call` | `9fd91db4d75dfa0db714605d1248f399d0fc6428` | #114 `f368a4e4326b25b8825e0b4ec1753ec23de727e3` | `IMPLEMENTED_ON_ACTIVE_PR` | CI `31580453003` success; Rust contracts `94062054235` success; Production coverage `94062054224` exact function/line/region/branch success; Manifest V3 Compatibility `31580452993` success; no formal reviews or inline review threads returned; mergeable against the exact prerequisite | `dispatch_if_runtime_matches` makes validated runtime metadata/capability a same-call prerequisite for one callback and transfers the non-cloneable validation value by ownership. This still does not authenticate the adapter process, bind a browser session/context/origin, serialize BiDi/CDP, perform browser I/O, or prove a post-condition. | +| #116 `feat(core): bind protocol dispatch to current browser context` | `3850bf075318b54d893ff6ae67e24ce6ea53ccc0` | #115 `9fd91db4d75dfa0db714605d1248f399d0fc6428` | `IMPLEMENTED_ON_ACTIVE_PR` | CI `31586388906` success; Rust contracts `94081156663` success; Production coverage `94081156697` exact function/line/region/branch success; Manifest V3 Compatibility `31586388924` success; no formal reviews or inline review threads returned; mergeable against the exact prerequisite | `BrowserContextDispatchTarget` groups the requested OriginWeave session/context without granting authority; `dispatch_if_context_current` then requires current registry ownership/epoch before reusing #115's exact same-call protocol validation and invoking the callback. It does not authenticate the adapter process, bind canonical origin or semantic-node authority for typed input, authorize destination/network/TLS/HTTP, perform browser I/O, or prove a post-condition. | +| #117 `feat(core): bind browser context origin before observation` | `4f5e9b0f81df48fb178eaafb7cf68a925071ce39` | #116 `3850bf075318b54d893ff6ae67e24ce6ea53ccc0` | `IMPLEMENTED_ON_ACTIVE_PR` | Test-only head `48ec0a38e0fb2eed9d1eb5ee4cbae715130a6991` established the missing pre-observation origin-binding contract. Production head `2e799eea7c2b1e98d87bd8d1c12be00fc209fd71` added the bounded method, but CI `31587528996` failed at test compilation because the controlled fixture helper attempted to convert `OriginError` into `Box` even though `OriginError` does not implement `std::error::Error`; this was a test-harness setup defect, not product rejection. Current exact head `4f5e9b0f81df48fb178eaafb7cf68a925071ce39` fixes only that fixture conversion and is exact-green: CI `31588082784` success; Rust contracts `94086457133` success; Production coverage `94086457260` exact function/line/region/branch success; Manifest V3 Compatibility `31588082643` success; no formal reviews or inline review threads returned; mergeable against the exact prerequisite. | `BrowserAuthorityRegistry::bind_context_origin` establishes one canonical origin for the exact registered session/context and current document epoch before semantic-node discovery, is idempotent for the same origin, rejects a same-epoch origin change, and relies on document advancement to clear prior origin/node bindings. It does not derive or authenticate browser origin/runtime state, authorize navigation/destination/network/TLS/HTTP, grant Agent capability, create semantic observations, perform browser I/O, or prove a post-condition. | +| #118 `feat(core): revalidate current browser context origin` | `1eae12991eb5a2f91ce2d1486e9008c9ac3663e3` | #117 `4f5e9b0f81df48fb178eaafb7cf68a925071ce39` | `IMPLEMENTED_ON_ACTIVE_PR` | CI `31589434463` success and Manifest V3 Compatibility `31589434488` success on the exact head; no predecessor result is promoted. | `BrowserAuthorityRegistry::require_context_origin` revalidates exact session/context ownership plus the canonical origin currently bound to that document and returns its current `DocumentEpoch`; unbound origin or same-epoch origin mismatch fails closed. It does not derive or attest browser URL/origin state, authenticate a protocol adapter, authorize network/browser I/O, or make the returned epoch a reusable action capability. | +| #120 `feat(core): gate protocol dispatch on current context origin` | `cf58087b87c3a54fe1665c5ed5027b07b8b913af` | #118 `1eae12991eb5a2f91ce2d1486e9008c9ac3663e3` | `IMPLEMENTED_ON_ACTIVE_PR` | Exact formatted test-only head `56e3142538bd2499a880616ba2980f256838cb45` reached the intended production-boundary RED in CI `31591479446` / Rust contracts `94097233885`: repository contracts and rustfmt passed, then the locked workspace check failed only because `dispatch_if_context_origin_current` did not exist. Current production head `cf58087b87c3a54fe1665c5ed5027b07b8b913af` adds the narrow composition; current-head CI/review/coverage evidence is pending and therefore non-passing until refetched. | The proposed same-call boundary first revalidates exact session/context/canonical-origin/current-document authority, then validates exact protocol generation/family/adapter/runtime revisions/capability before handing the non-cloneable protocol-use proof plus current epoch to one callback. It does not derive or authenticate browser origin/runtime metadata, perform browser I/O, authorize destination/network/TLS/HTTP, grant Agent capability/approval, or prove a post-condition. | + +## Reconciliation consequence + +The documentation graph remains **DESIGN-SUFFICIENT / PROTECTED-MAIN-PARTIAL**. PRs #107–#120 implement bounded pieces of the browser-protocol and browser-lifetime contracts already anticipated by Proposed ADR 0107 and the versioned API contract. None introduces a new deployed trust domain or an OriginWeave-owned durable persistence schema. The conceptual/logical ERD therefore remains the truthful data-model artifact. + +For the first real Chromium Agent Task, a later trusted adapter still has to derive the canonical current origin and runtime protocol metadata from the actual browser boundary, bind the exact OriginWeave protocol generation, runtime protocol family, runtime adapter version, pinned runtime revisions, required capability, current session/context/canonical-origin/document/node authority where the operation requires it, and authenticated browser-protocol execution immediately before use. It must then compose that execution with semantic observation/query, deterministic policy, real input, observed post-condition, recovery and resource evidence while preserving credential-safe validation metadata without turning receipts into execution authority. Until that executable composition is integrated and revalidated on protected `main`, active branch evidence must not be described as shipped behavior or release readiness. diff --git a/docs/traceability/README.md b/docs/traceability/README.md index 3d5a298e3..e30b9eda1 100644 --- a/docs/traceability/README.md +++ b/docs/traceability/README.md @@ -1,12 +1,12 @@ # OriginWeave Product and Decision Traceability - **Status:** Proposed authoritative traceability baseline -- **Scope:** Product requirements, Accepted architecture, implemented kernels, planned adapters, conversation-derived decisions, standards, and verification evidence +- **Scope:** Product requirements, Accepted architecture, protected-main implementation, active-PR implementation, planned adapters, conversation-derived decisions, standards, and verification evidence This file prevents two opposite errors: 1. an implemented safety boundary becoming undiscoverable because it exists only in code/tests; and -2. a product-design conversation or pull-request proposal being presented as if it already shipped. +2. a product-design conversation, issue, or active pull request being presented as if it already shipped. ## 1. Evidence precedence @@ -15,81 +15,111 @@ For current behavior, use this precedence order: 1. exact protected-main code and executable tests; 2. Accepted ADRs governing that code; 3. current root `ARCHITECTURE.md` and authoritative PRD/TRD aligned to protected main; -4. roadmap and issue/PR plans; -5. conversation-derived product decisions and research notes. +4. active-PR code/tests as explicitly labeled non-shipped evidence; +5. roadmap and issue plans; +6. conversation-derived product decisions and research notes. -Lower layers may define future direction but cannot override current protected implementation or an Accepted ADR. +Lower layers may define future direction but cannot override current protected implementation or an Accepted ADR. Active-PR behavior is never protected-main truth. -## 2. Status vocabulary +### 1.1 Active freshness-authority dossiers -- **Implemented** — present on protected `main` with executable evidence. -- **Accepted architecture** — governing reviewed direction, though the complete runtime path may be unfinished. -- **Proposed** — candidate product/design decision requiring reviewed adoption. -- **Open** — intentionally unresolved. +Transient implementation evidence that materially tightens an existing authority boundary is kept in explicit active-PR traceability rather than silently changing protected-main maturity: -A change can move from Proposed -> Accepted architecture -> Implemented, but never skips evidence merely because the idea is compelling. +- [`resolution-freshness-authority.md`](resolution-freshness-authority.md) — PR #47 bounds the lifetime of validated destination-resolution authority; the direct socket consumer still must require that fresh authority before the overall DNS-rebinding/TOCTOU interval can be called implemented on protected main. +- [`tls-revocation-freshness-authority.md`](tls-revocation-freshness-authority.md) — PR #48 classifies independently verified revocation material for freshness only; it does not fetch or authenticate OCSP/CRL material and does not create an unrevoked-certificate claim. + +These dossiers are evidence indexes, not substitute ADRs. A new ADR is required only when a durable architecture/trust/deployment decision changes. + +## 2. Capability maturity vocabulary + +Capability maturity uses exactly one of these values: + +- **IMPLEMENTED_ON_PROTECTED_MAIN** — present on protected `main` with executable evidence. +- **IMPLEMENTED_ON_ACTIVE_PR** — implemented and testable on an active PR, but not shipped/protected-main truth. +- **PARTIAL** — material foundations are implemented, while a named runtime, lifecycle, integration, or acceptance boundary remains incomplete. +- **ACCEPTED_ARCHITECTURE** — governing reviewed direction; implementation may be incomplete. +- **PLANNED** — accepted product backlog or target architecture without current implementation evidence. +- **RESEARCH_ONLY** — exploratory evidence that does not define a product commitment. +- **SUPERSEDED** — replaced by later implementation or architecture authority. +- **OUT_OF_SCOPE** — intentionally excluded from the current product boundary. + +ADR lifecycle is separate and remains `Proposed`, `Accepted`, `Superseded`, `Deprecated`, or `Rejected`. An Accepted ADR is design authority, not implementation proof. ## 3. Product-level decision trace -| Product decision | Origin/status | Authoritative artifact | Protected implementation/evidence | +| Product decision | Capability maturity | Authoritative artifact | Protected-main / active-PR evidence boundary | |---|---|---|---| -| Chromium remains the compatibility kernel rather than rewriting Blink/V8 | Accepted architecture | ADR 0001; `ARCHITECTURE.md`; PRD-COMP-001 | Architecture/repository contract tests; Chromium adapter itself remains Planned | -| `Browse. Act. Prove.` provenance-native product identity | Accepted product framing | `README.md`; `docs/PRD.md`; roadmap | Evidence/provenance foundation implemented; full buyer Evidence Trail Planned | -| Human / Assist / Agent Task / Crawler execution modes | Accepted architecture | `ARCHITECTURE.md`; `docs/PRD.md`; ADR 0002 | Core mode/purpose and policy foundation implemented; browser-session integration Planned | -| Page content is data, never instruction authority | Implemented foundation | ADR 0002; `ARCHITECTURE.md`; `docs/TRD.md` | `originweave-core` + `originweave-policy` tests | -| Typed actions instead of default arbitrary JavaScript | Accepted architecture | PRD-ACT-001..004; ADR 0002 | Typed core/policy foundation implemented; full browser action adapter Planned | -| logical origin != resolved destination | Implemented | ADR 0004; TRD-INV-002 | `originweave-destination`; destination governance tests | -| resolved destination != TCP peer | Implemented | ADR 0005; TRD Section 6 | `originweave-network`; loopback/peer tests | -| TCP peer != TLS service identity | Implemented | ADR 0006; TRD Section 6 | `originweave-tls`; rustls integration tests | -| Proxy/PAC route authority must be explicit | Accepted architecture / active development | PRD-NET-005; TRD Section 6.3 | Protected-main direct-only boundary exists; complete proxy execution not yet shipped | -| HTTP semantics require an authenticated governed connection and resource bounds | Accepted architecture / active development | PRD-NET-006; TRD Section 6.6 | Not yet a protected-main product capability in this baseline | -| Node handles bind session/context/origin/document lifetime | Proposed/active development | PRD-OBS-001/002; TRD Section 5 | Not treated as shipped until protected integration | -| Raw secrets never enter model context | Accepted architecture / implemented policy foundation | PRD-DATA-001; ADR 0002; TRD Section 9 | Core secret-delivery policy implemented; trusted broker runtime Planned | -| Sensitive disclosure is purpose-bound and classification-bound | Proposed/active development | PRD-DATA-002; TRD Section 9 | Do not claim complete broker/service until protected integration | -| Evidence/provenance are product outputs, not debug leftovers | Accepted / foundation implemented | ADR 0003; PRD Section 9.6 | `originweave-evidence`; evidence governance tests | -| Human interaction outranks inference/background collection | Accepted architecture / foundation implemented | `ARCHITECTURE.md`; PRD-RES-002 | Deterministic resource mitigation foundation implemented; platform telemetry Planned | -| Structured observation precedes raw HTML/screenshot fallback | Accepted architecture | PRD-OBS-003; TRD Section 7 | Observation adapter Planned | -| WebDriver BiDi / CDP / WebMCP / MCP are adapters, not internal authority | Accepted architecture | PRD Section 9.8; TRD Section 12 | Adapter implementations Planned | -| Manifest V3 compatibility is preserved upstream where practical | Accepted architecture | ADR 0001; PRD Section 9.9 | Chromium compatibility program Planned | -| WARC/PROV-oriented durable evidence adapters | Accepted architecture / Planned | ADR 0003; PRD-EVD-005 | Source/provenance kernel foundation exists; persistence adapters Planned | -| Origin Map visualizes value/action provenance | **conversation-derived Proposed** product UX | PRD-EVD-004; this traceability record | No shipped UI claim | -| Browser / Runtime / Observe / Capture / Governor / Policy / Evidence / Protocol / SDK product surfaces | **conversation-derived Proposed product taxonomy**, aligned to existing architecture | PRD Section 6 | Some foundations exist under crates; named commercial surfaces are not all shipped artifacts | -| Constrained GPU phase scheduling for browser rendering vs local inference | **conversation-derived Accepted architecture direction**, implementation Planned | PRD-RES-005; TRD Section 10 | Deterministic resource plan exists; real GPU scheduler/telemetry Planned | -| Enterprise SSO/SCIM/residency/audit/procurement package | Planned | PRD Section 9.11; roadmap Phase 5 | Not shipped in pre-alpha baseline | +| Chromium remains the compatibility kernel rather than rewriting Blink/V8 | ACCEPTED_ARCHITECTURE | ADR 0001; `ARCHITECTURE.md`; PRD-COMP-001 | Architecture/repository contracts exist; complete branded browser distribution remains Planned | +| `Browse. Act. Prove.` provenance-native product identity | ACCEPTED_ARCHITECTURE | `README.md`; `docs/PRD.md`; roadmap | Evidence/provenance foundations exist; complete buyer Evidence Trail remains Planned | +| Human / Assist / Agent Task / Crawler execution modes | PARTIAL | `ARCHITECTURE.md`; `docs/PRD.md`; ADR 0002 | Core mode/purpose/policy foundations exist; browser-session/profile integration remains incomplete | +| Page content is data, never instruction authority | IMPLEMENTED_ON_PROTECTED_MAIN | ADR 0002; `ARCHITECTURE.md`; `docs/TRD.md` | `originweave-core` + `originweave-policy` tests | +| Typed actions instead of default arbitrary JavaScript | PARTIAL | PRD-ACT-001..004; ADR 0002 | Typed core/policy foundations are on main; complete browser action adapter remains Planned | +| logical origin != resolved destination | IMPLEMENTED_ON_PROTECTED_MAIN | ADR 0004; TRD-INV-002 | `originweave-destination`; destination governance tests | +| Bounded resolution freshness is explicit before destination authority is consumed | IMPLEMENTED_ON_ACTIVE_PR | ADR 0004; [`resolution-freshness-authority.md`](resolution-freshness-authority.md) | PR #47 implements the deterministic freshness primitive; protected-main socket planning can still bypass it, so the overall resolution-to-socket TOCTOU boundary remains PARTIAL | +| resolved destination != TCP peer | IMPLEMENTED_ON_PROTECTED_MAIN | ADR 0005; TRD Section 6 | `originweave-network`; loopback/peer tests | +| TCP peer != TLS service identity | IMPLEMENTED_ON_PROTECTED_MAIN | ADR 0006; TRD Section 6 | `originweave-tls`; rustls integration tests | +| Revocation-material freshness is separate from revocation authenticity/non-revocation | IMPLEMENTED_ON_ACTIVE_PR | ADR 0006/0008 boundary; [`tls-revocation-freshness-authority.md`](tls-revocation-freshness-authority.md) | PR #48 adds a freshness classifier only; protected main still records revocation as NotConfigured and makes no unrevoked claim | +| Proxy/PAC route authority must be explicit | PARTIAL | PRD-NET-005; TRD Section 6.3 | Protected-main direct-route authority exists; PAC evaluation/proxy transport/CONNECT remain incomplete | +| Bounded HTTP semantics require an authenticated governed connection and resource bounds | IMPLEMENTED_ON_ACTIVE_PR | PRD-NET-006; issue #9; active PR #37 | `originweave-http` replacement exists on active PR #37; historical PR #11 is SUPERSEDED implementation lineage and is not current evidence; no protected-main HTTP claim yet | +| Node handles bind session/context/origin/document lifetime | PARTIAL | ADR 0010; PRD-OBS-001/002; TRD Section 5 | Core opaque session/context/document/node authority is on protected main; active PR #40 owns the protocol-ID registry and remains non-shipped evidence | +| Semantic observations retain OriginWeave node authority and explicit source-channel provenance | IMPLEMENTED_ON_ACTIVE_PR | PRD-OBS-001/003/005; ADR 0010; structured-observation architecture | Active PR #52, stacked on #40, implements a bounded `SemanticNodeObservation` value primitive that rejects missing evidence-channel provenance. It is not a browser observation adapter; channels and advertised node actions are descriptive evidence and grant no execution authority | +| Raw secrets never enter model context | PARTIAL | PRD-DATA-001; ADR 0002; TRD Section 9 | Core secret-delivery policy exists; trusted broker/runtime completion remains Planned | +| Sensitive disclosure is purpose- and classification-bound | PARTIAL | ADR 0007; PRD-DATA-002; issue #10 | Purpose-bound policy/evidence foundations are on protected main; active PR #45 adds credential-free handle-lifecycle evidence and #46 adds bounded in-process authoritative use reservation, while trusted storage/revocation/value resolution/cross-process lifecycle/model-disclosure remain open | +| Evidence/provenance are product outputs, not debug leftovers | PARTIAL | ADR 0003; PRD Section 9.6 | `originweave-evidence` foundations exist; complete durable Evidence Trail/WARC/PROV adapters remain Planned | +| 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 | +| 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 | +| Browser / Runtime / Observe / Capture / Governor / Policy / Evidence / Protocol / SDK product surfaces | PARTIAL | PRD Section 6 | Some foundations exist under crates; named commercial surfaces are not all shipped artifacts | +| Constrained GPU phase scheduling for browser rendering vs local inference | PARTIAL | PRD-RES-005; TRD Section 10 | Deterministic resource plan exists; real GPU scheduler/telemetry remains Planned | +| Enterprise SSO/SCIM/residency/audit/procurement package | PLANNED | PRD Section 9.11; roadmap Phase 5 | Not shipped in pre-alpha baseline | ## 4. Requirement-to-module trace -| Requirement family | Current module(s) | Primary tests/docs | Implementation status | +| Requirement family | Current module(s) / lane | Primary tests/docs | Capability maturity | |---|---|---|---| -| Canonical origin / action / approval | `originweave-core` | crate tests; ADR 0002 | Implemented | -| Deterministic action policy | `originweave-policy` | policy/security-review tests | Implemented | -| Destination/rebinding/redirect | `originweave-destination` | destination tests; ADR 0004 | Implemented | -| Exact direct socket/peer | `originweave-network` | real loopback + error tests; ADR 0005 | Implemented | -| TLS identity | `originweave-tls` | real rustls integration; ADR 0006 | Implemented | -| Resource budgets/mitigations | `originweave-resource` | crate tests | Implemented foundation | -| Redacted evidence/provenance | `originweave-evidence` | crate tests; ADR 0003 | Implemented foundation | -| HTTP | future/active `originweave-http` work | dedicated design/tests/PR evidence | Planned until protected merge | -| Proxy/PAC | destination foundation + future adapter | roadmap/TRD | Planned/active | -| Session/observation/action | future crates/adapters | roadmap/TRD/UML | Planned/active | -| Secret broker | future bounded service/crate | PRD/TRD | Planned/active | -| BiDi/CDP/WebMCP/MCP | adapter crates | protocol compatibility tests required | Planned | -| WARC/PROV persistence | persistence adapters | doctoring + future conformance tests | Planned | +| Canonical origin / action / approval | `originweave-core` | crate tests; ADR 0002 | IMPLEMENTED_ON_PROTECTED_MAIN | +| Deterministic action policy | `originweave-policy` | policy/security-review tests | IMPLEMENTED_ON_PROTECTED_MAIN | +| Destination/rebinding/redirect | `originweave-destination` | destination tests; ADR 0004 | IMPLEMENTED_ON_PROTECTED_MAIN | +| Resolution freshness authority | active `originweave-destination` work in PR #47 | [`resolution-freshness-authority.md`](resolution-freshness-authority.md); active exact-head tests/coverage | IMPLEMENTED_ON_ACTIVE_PR | +| Exact direct socket/peer | `originweave-network` | real loopback + error tests; ADR 0005 | IMPLEMENTED_ON_PROTECTED_MAIN | +| TLS identity | `originweave-tls` | real rustls integration; ADR 0006 | IMPLEMENTED_ON_PROTECTED_MAIN | +| TLS revocation-material freshness | active `originweave-tls` work in PR #48 | [`tls-revocation-freshness-authority.md`](tls-revocation-freshness-authority.md); active exact-head tests/coverage | IMPLEMENTED_ON_ACTIVE_PR | +| Resource budgets/mitigations | `originweave-resource` | crate tests | PARTIAL | +| Redacted evidence/provenance | `originweave-evidence` | crate tests; ADR 0003 | PARTIAL | +| Bounded HTTP/1.1 | active `originweave-http` replacement in PR #37 | issue #9; active-PR unit/integration/coverage evidence | IMPLEMENTED_ON_ACTIVE_PR | +| Proxy/PAC | destination/route foundation + future adapter | roadmap/TRD | PARTIAL | +| Session/context/document/node authority | `originweave-core` authority values; active registry work in PR #40 | ADR 0010; roadmap/TRD/UML | PARTIAL | +| Semantic observation value authority/provenance | active `originweave-core` work in PR #52, stacked on #40 | `semantic_node_observation` tests; PRD-OBS-001/003/005; issue #28 | IMPLEMENTED_ON_ACTIVE_PR | +| Manifest V3 compatibility evidence | `scripts/ci/run_mv3_compatibility.py` + controlled MV3 fixture; active downloads lane #43 | issue #27; real-browser contracts | PARTIAL | +| Extension-to-Agent authority | protected-main core authority kernel + Proposed ADR 0013 | issue #27; extension authority UML | PARTIAL | +| Purpose-bound sensitive-data policy/evidence | `originweave-policy` + evidence foundations; active lifecycle/reservation work #45/#46 | ADR 0007; issue #10 | PARTIAL | +| Trusted sensitive-data broker/storage/lifecycle | future bounded service/crate | issue #10; PRD/TRD/data governance | PLANNED | +| BiDi/CDP/WebMCP/MCP | future/versioned adapter crates; registry prerequisite active in #40 | protocol compatibility tests required | PLANNED | +| WARC/PROV persistence | persistence/export adapters | doctoring + future conformance tests | PLANNED | ## 5. Requirement-to-ADR trace -| Requirement | Governing ADR | +| Requirement | Governing ADR / current decision boundary | |---|---| -| PRD-COMP-001, PRD-COMP-003 | ADR 0001 | -| PRD-ACT-001, PRD-ACT-005, PRD-CRAWL-001, trust-source boundary | ADR 0002 | -| PRD-EVD-001, PRD-EVD-002, PRD-EVD-005 | ADR 0003 | -| PRD-NET-001, PRD-NET-002, redirect/rebinding boundary | ADR 0004 | -| PRD-NET-003 | ADR 0005 | -| PRD-NET-004 | ADR 0006 | -| Session/context/document node binding | Proposed/active decision; index only after dedicated ADR reaches protected main | -| Proxy/PAC route execution | Proposed/active decision; protected-main index updates after merge | -| HTTP semantics | Proposed/active decision; protected-main index updates after merge | -| Sensitive-data broker lifecycle | Proposed/active decision; policy/evidence slices do not equal full broker acceptance | -| Enterprise deployment/privacy | Open ADR family before production release | +| PRD-COMP-001, Chromium compatibility kernel | ADR 0001 (Accepted) | +| PRD-ACT-001, PRD-ACT-005, PRD-CRAWL-001, trust-source boundary | ADR 0002 (Accepted) | +| PRD-EVD-001, PRD-EVD-002, PRD-EVD-005 | ADR 0003 (Accepted) | +| PRD-NET-001, PRD-NET-002, redirect/rebinding/freshness boundary | ADR 0004 (Accepted); active PR #47 tightens the existing boundary without creating a new deployed component or trust owner | +| PRD-NET-003 | ADR 0005 (Accepted) | +| PRD-NET-004 | ADR 0006 (Accepted); active PR #48 adds revocation-material freshness only and does not define a complete revocation architecture | +| Purpose-bound sensitive-data authority | ADR 0007 (Accepted); trusted broker/storage/lifecycle still issue #10 | +| TLS delegated-task leaf-validity horizon | ADR 0008 (Accepted) | +| Session/context/document/node binding | ADR 0010 (Accepted); active registry implementation #40 remains non-shipped | +| Semantic observation authority/provenance | Existing session/node authority plus structured-observation architecture; active PR #52 narrows the value contract without creating a new service, trust owner, persistence boundary, or external protocol and therefore does not justify a new ADR by itself | +| Manifest V3 compatibility + extension-to-Agent authority | ADR 0013 is Proposed on documentation PR #44; protected-main extension authority code does not auto-Accept the ADR | +| Architecture-decision acceptance governance | ADR 0014 is Proposed on documentation PR #44; protected-main AGENTS + live policy remain authoritative | +| HTTP semantics | active PR #37 contains its feature ADR lineage; it is active-PR evidence until protected merge and index reconciliation | +| Proxy/PAC route execution | current protected-main route authority + future dedicated execution decision as needed | +| Enterprise deployment/privacy | open ADR family before production release | ## 6. Standards-to-decision trace @@ -100,8 +130,8 @@ The canonical APA 7th bibliography is [`../doctoring.md`](../doctoring.md). This | WHATWG URL + Chromium canonicalizer | Browser-compatible origin identity and numeric-host rejection | | IANA special-purpose registries / RFC 6890 / RFC 8190 / RFC 9637 | Destination classification and fail-closed public-web policy | | RFC 9293 | Exact TCP endpoint/peer model | -| RFC 5280 / RFC 9525 / current TLS guidance | Certificate path and HTTPS service identity | -| RFC 9110 and related HTTP specifications | Redirect and bounded HTTP semantics | +| RFC 5280 / RFC 9525 / RFC 9325 | Certificate path, HTTPS service identity, and the separation between certificate validity and any future revocation policy | +| RFC 9110 / RFC 9112 / RFC 9530 | Bounded HTTP semantics, framing, redirect evidence and digest fields | | RFC 9309 | Crawler robots evidence, explicitly not access authorization | | W3C WebDriver BiDi | Versioned browser automation adapter, not core authority | | Chrome DevTools Protocol | Chromium-specific observation/diagnostic adapter | @@ -114,15 +144,17 @@ Material claims should update `docs/doctoring.md` with current primary evidence ## 7. Diagram-to-requirement trace -| Diagram | Requirements represented | +| Diagram | Requirements represented / maturity | |---|---| | UML component/bounded-context view | Product family, Chromium/Rust ownership, adapter boundaries | -| Network authority sequence | PRD-NET-001..007; TRD-INV-002 | -| Observation/action sequence | PRD-OBS, PRD-ACT, PRD-DATA, trust separation | +| Network authority sequence | PRD-NET-001..007; TRD-INV-002; HTTP remains active-PR until #37 integrates; resolution freshness remains an active lower-layer primitive until the socket consumer requires it | +| Observation/action sequence | PRD-OBS, PRD-ACT, PRD-DATA, trust separation; active #52 makes the bounded semantic-observation value/provenance contract explicit without establishing browser I/O or action dispatch | | Delegated-task state machine | session lifecycle, approval, resource pause, cancellation/recovery, post-condition truth | | Deployment topology | renderer trust, orchestrator/model/store boundaries | -| Evidence authority flow | PRD-EVD; separation of proposal/policy/approval/execution/outcome | -| Conceptual ERD | durable session/action/network/sensitive/resource/provenance identity | +| Evidence authority flow | PRD-EVD; proposal/policy/approval/execution/outcome separation | +| Extension authority sequence | MV3 compatibility plane vs explicit OriginWeave extension grant and Agent capability separation | +| Conceptual ERD | session/action/network/sensitive/resource/provenance identity; active freshness and semantic-value primitives introduce no physical persistence | +| Real Chromium vertical-slice sequence | PLANNED until issue #28 implementation stabilizes; active #40/#51/#52 are prerequisites, not proof of the real adapter flow; do not encode temporary adapter fields as shipped architecture | ## 8. Conversation-to-repository capture rule @@ -130,23 +162,29 @@ A **conversation-derived** decision is not binding merely because it was repeate If material and absent from GitHub: -1. record it as `Proposed` or `Open` in PRD/TRD/traceability; -2. create/supersede an ADR when it changes a governing architecture decision; +1. record it with explicit capability maturity in PRD/TRD/traceability; +2. create or supersede an ADR when it changes a governing architecture decision; 3. update UML/ERD when relationships or lifecycles change; 4. add standards/research to `docs/doctoring.md` when evidence is material; -5. add executable tests before calling production behavior Implemented; +5. add executable tests before calling production behavior `IMPLEMENTED_ON_PROTECTED_MAIN`; 6. update the protected-main ADR index only after review and merge. This rule intentionally prevents chat history from becoming a shadow architecture database. ## 9. Documentation drift checks -Repository contracts should fail when the canonical PRD/TRD/ADR index/UML/ERD/traceability files disappear or when core status/authority vocabulary is removed. More semantic checks should be added when a specific drift has caused a real defect; avoid brittle tests that duplicate prose without protecting a contract. +Repository contracts should fail when canonical PRD/TRD/ADR/UML/ERD/traceability artifacts disappear, lifecycle/index status diverges, an active PR is promoted to protected-main truth, or core maturity/authority vocabulary is removed. Active freshness dossiers must remain discoverable from this index so lower-layer primitives cannot silently become over-broad shipped claims. More semantic checks should be added when a specific drift has caused a real defect; avoid brittle tests that merely duplicate prose. ## 10. Open traceability work +- **Open:** active PR #47 must reach unchanged exact-head CI/security/100% coverage, then the first-party socket consumer must require the fresh resolution authority before the resolution-to-socket TOCTOU interval can become protected-main implemented evidence. +- **Open:** active PR #48 remains freshness classification only; define and review revocation-material acquisition/authenticity/cache/failure/composition before any protected-main revocation-enforcement or unrevoked claim. +- **Open:** after #37 integrates, move bounded HTTP from `IMPLEMENTED_ON_ACTIVE_PR` into protected-main evidence and close historical PR #11 only after unique-work preservation and protected-main verification are proven. +- **Open:** after #43 integrates, move bounded MV3 downloads from `IMPLEMENTED_ON_ACTIVE_PR` into the protected-main compatibility evidence inventory while issue #27 remains open for the complete matrix. +- **Open:** after #40 stabilizes/integrates, map its registry API and tests without presenting raw BiDi/CDP identifiers as durable authority. +- **Open:** after stacked #52 stabilizes/integrates behind #40, reclassify only its bounded semantic-observation value/provenance primitive; keep real browser observation I/O, action dispatch, mutation invalidation and post-condition evidence under issue #28 until implemented. +- **Open:** after #45/#46 integrate, reclassify their narrow lifecycle/reservation primitives while keeping durable trusted-broker storage/revocation/value-resolution/model-disclosure boundaries under issue #10 until implemented. - **Open:** attach concrete release profiles and quantitative benchmark thresholds after reproducible benchmark evidence exists. - **Open:** map every future public OriginWeave Protocol operation to risk/capability/authority and conformance tests. - **Open:** map enterprise controls to exact SOC 2/CSAP-oriented control evidence without claiming certification. - **Open:** add data-retention and residency lifecycle diagrams when persistence/tenant adapters become concrete. -- **Open:** after active feature PRs merge, update this matrix from `Proposed/active development` to the exact protected implementation and Accepted ADRs. diff --git a/docs/traceability/action-postcondition-evidence.md b/docs/traceability/action-postcondition-evidence.md new file mode 100644 index 000000000..23a6e764d --- /dev/null +++ b/docs/traceability/action-postcondition-evidence.md @@ -0,0 +1,116 @@ +# Action Post-Condition Evidence Traceability + +- **Documentation status:** Active-PR evidence dossier +- **Canonical owner:** PR #44 (`docs: reconcile architecture documentation fitness`) +- **Protected-main baseline:** `67af7c87589edc2039545af335c95064d9b8391c` +- **Capability maturity:** **PARTIAL** +- **Governing decisions:** Accepted ADR 0003 plus Proposed ADR 0106 preserve provenance-native evidence and separation of action execution from verification. + +## 1. Why this dossier exists + +OriginWeave's protected-main API contract already defines a durable product rule: returning from a browser command is not equivalent to successful action completion. A state-changing action becomes successful only after the declared or derived post-condition is observed and verified. Protected main also provides generic credential-safe provenance with explicit verification state, but that design rule was not yet represented by a reusable typed action-outcome evidence object. + +This dossier records the active implementation evidence that narrows that gap. It does not promote active pull requests to protected-main shipped truth and it does not claim that a real Chromium adapter already observes the post-condition after dispatch. + +## 2. Protected-main design and implementation boundary + +Protected `main` already provides: + +- typed `ActionKind` and immutable `ActionIntentDigest` values; +- canonical `Origin` authority values; +- credential-safe `ProvenanceRecord` with explicit `VerificationResult`; +- API/TRD requirements that state-changing success waits for an observed post-condition; and +- provenance architecture that keeps observation, policy, execution, and verification as distinct authorities. + +The generic value primitives are **IMPLEMENTED_ON_PROTECTED_MAIN**. The complete action dispatch → observation → independent verification → successful outcome chain remains **PARTIAL** because protected main does not yet contain the real Chromium runtime that composes them end to end. + +## 3. Active executable evidence + +### PR #64 — verified, temporally ordered post-condition becomes typed action-outcome evidence + +**Capability maturity:** `IMPLEMENTED_ON_ACTIVE_PR` + +Exact head `2c45411ed9aa0eecca2d06c85659db9f4bb85e4d` adds `VerifiedActionOutcomeEvidence` in the existing credential-safe evidence crate. It binds: + +1. the exact typed `ActionKind`; +2. canonical target `Origin`; +3. complete immutable `ActionIntentDigest`; +4. a bounded first-slice `PostConditionKind` (`UrlChanged`, `NodeStateChanged`, `DialogStateChanged`, or `NetworkMutationObserved`); +5. caller-supplied action-dispatch and post-condition-observation timestamps that must come from one monotonic clock domain; and +6. the exact `ProvenanceRecord` used as the post-condition proof. + +Construction fails closed unless the supplied provenance has `VerificationResult::Verified`. Both `Unverified` and `Rejected` observations are rejected as `PostConditionNotVerified`. An observation timestamp earlier than dispatch is rejected as `PostConditionPredatesDispatch`; equal ticks remain valid for coarse monotonic clocks. + +On this exact head, CI run `31441848670`, Security Scan run `31441848649`, SAST Semgrep run `31441848615`, exact owned production function/line/region/branch coverage, strict Clippy, rustdoc and CodeRabbit exact-head status are successful. GitHub reports the PR mergeable and Ready for review; no formal reviews or inline review threads are currently returned. + +### PR #65 — controlled hostile local workflow fixture + +**Capability maturity:** `IMPLEMENTED_ON_ACTIVE_PR` + +Test-only head `d2580305f05aba93d10b5342ec1886d601c6752e` was based directly on the protected-main baseline and intentionally required a checked-in `tests/fixtures/agent_task_basic/index.html` before that fixture existed. CI run `31445088008`, Rust contracts job `93637443229`, checked out that exact head and failed with three `FileNotFoundError` results for the missing fixture, establishing the intended fail-first boundary. + +Exact head `0888fe3a6ef6da547a37fd075733cc73dc52b2ab` adds the smallest controlled fixture satisfying the contract: a labelled semantic field, submit control, deterministic `idle` → `submitted` observable state change carrying only synthetic text, one explicitly hidden/untrusted prompt-injection marker, and no password/OTP/API-key/secret collection surface. + +On that unchanged exact head, CI run `31445201739` succeeds; Rust contracts job `93637824750` passes repository contracts, formatting, locked workspace check, full tests, strict Clippy and rustdoc; Production coverage job `93637824824` passes exact owned production function/line/region/branch enforcement; Security Scan run `31445201774`, SAST Semgrep run `31445201669` and CodeRabbit exact-head status succeed. GitHub reports the PR mergeable and Ready for review with no formal reviews or inline review threads currently returned. + +This remains controlled test infrastructure rather than browser-execution evidence. The fixture itself does not establish WebDriver BiDi/CDP transport, Chromium semantic extraction, policy dispatch, native input, post-condition provenance, profile teardown or process attribution. + +## 4. Non-transitive success semantics + +The intended first-slice chain is: + +```text +typed action intent +-> policy-authorized dispatch +-> real browser input/event +-> observed bounded post-condition +-> independently verified provenance +-> temporally ordered VerifiedActionOutcomeEvidence +``` + +The active PR implements only the final typed evidence boundary. The following implications are explicitly invalid: + +```text +command return -/> successful action completion +protocol acknowledgement -/> successful action completion +Unverified -/> successful action completion +Rejected -/> successful action completion +caller-supplied timestamp ordering -/> proof of trusted clock provenance +VerifiedActionOutcomeEvidence type existence -/> proof of real Chromium execution +controlled fixture success -/> proof of real Chromium execution +``` + +PR #64 now rejects a caller-supplied observation timestamp that predates caller-supplied dispatch time, but the type cannot independently prove the clock source, that a real browser actually dispatched the action, that the supplied provenance belongs to the claimed browser target/node, or that the observed state was caused by that action. PR #65 supplies deterministic hostile input and a post-condition target but no browser execution. Those claims remain the responsibility of the real adapter/runtime composition under issue #28. + +## 5. Active prerequisite graph for issue #28 + +The first real Chromium vertical slice remains distributed across bounded active prerequisites rather than one shipped runtime: + +- PR #40 — protocol/browser identifiers → OriginWeave session/context/origin/document/node authority; +- PR #52 — bounded semantic node observation with explicit source-channel provenance; +- PR #57 — typed semantic-node query contract; +- PR #58 — authority-bound semantic node action target; +- PR #49 — ephemeral compatibility-profile lifecycle regression stacked on #43; +- PR #51 — bounded browser-task telemetry plus one explicitly supplied Linux PID `VmRSS` sampler; Chromium process discovery/process-set attribution remains outside that slice; +- PR #64 — verified and caller-timestamp-ordered post-condition action-outcome evidence; and +- PR #65 — controlled hostile local Agent Task workflow fixture, gate-clean and Ready for review. + +These active PRs are non-shipped evidence. They do not themselves compose WebDriver BiDi/CDP transport, trusted Chromium process attribution, policy-authorized real input dispatch, causal post-condition observation, or deterministic end-to-end teardown/recovery into one protected-main runtime. + +## 6. Remaining issue #28 boundary + +This dossier does **not** close issue #28. Material remaining work includes: + +- pinned stock Chromium exercised as one reproducible end-to-end Agent Task runtime path, not only extension compatibility fixtures; +- isolated Agent Task profile/context lifecycle and cleanup in the production vertical path; +- versioned WebDriver BiDi adapter plus explicitly bounded CDP observation fallback where needed; +- real semantic observation feeding typed query and policy-authorized typed action; +- real browser input dispatch followed by post-dispatch observation of the declared condition; +- hostile/stale/cross-session/cross-context/cross-origin/prompt-injection/secret-leak/crash/oversize regressions; +- deterministic failure/recovery evidence and task teardown; +- Chromium process discovery/process-set attribution composed into resource telemetry; and +- protected-main integration plus fresh acceptance before any active-PR capability becomes shipped truth. + +## 7. Documentation fitness consequence + +The ADR/PRD/TRD/Architecture/UML/ERD graph remains **DESIGN-SUFFICIENT / PROTECTED-MAIN-PARTIAL**. PR #64 narrows a typed evidence gap already governed by existing provenance/action-success decisions, while PR #65 supplies controlled test infrastructure for the eventual real-browser proof. Neither introduces a new trust domain, deployed component, persistence owner, database schema, or independent architecture decision, so a new ADR or physical ERD entity would overstate the implementation. Detailed real-Chromium dispatch/post-condition sequence diagrams should be reconciled when the executable adapter chain stabilizes rather than manufacturing as-built detail before that runtime exists. diff --git a/docs/traceability/extension-authority-security.md b/docs/traceability/extension-authority-security.md new file mode 100644 index 000000000..a36380a31 --- /dev/null +++ b/docs/traceability/extension-authority-security.md @@ -0,0 +1,85 @@ +# Extension-to-Agent Security Traceability + +- **Documentation status:** Active-PR evidence dossier +- **Canonical owner:** PR #44 (`docs: reconcile architecture documentation fitness`) +- **Protected-main baseline:** `67af7c87589edc2039545af335c95064d9b8391c` +- **Capability maturity:** **PARTIAL** +- **Governing decision:** Proposed ADR 0013 separates Manifest V3 compatibility from OriginWeave Agent authority. + +## 1. Why this dossier exists + +Manifest V3 compatibility and OriginWeave Agent authority are intentionally different evidence domains. A Chromium extension may possess Chrome permissions and may be explicitly granted a narrow OriginWeave extension capability without receiving Agent origin grants, Agent action capability, instruction trust, secret-delivery authority, approval, or protected-value access. + +This dossier records the current executable composition evidence for that separation. It does not promote active pull requests to protected-main shipped truth and it does not claim the trusted sensitive-data broker from issue #10 is complete. + +## 2. Protected-main authority + +Protected `main` already provides: + +- exact extension/session/context-scoped `ExtensionAgentGrant` evaluation; +- a distinction between `ObserveCurrentContext` and `ProposeTypedAction` extension capabilities; +- deterministic Agent policy evaluation for typed actions; +- fail-closed treatment of `InstructionSource::WebContent`; +- explicit Agent capability and readable/writable-origin gates; +- `FillSecret` policy that rejects raw secret delivery and requires `SecretDelivery::BrokerHandle`; and +- ordinary action-risk approval semantics that remain separate from extension permission. + +These foundations are **IMPLEMENTED_ON_PROTECTED_MAIN**. They do not by themselves prove every issue #27 cross-boundary composition case. + +## 3. Active executable evidence + +### PR #62 — proposal authority cannot widen Agent, instruction, or secret-material authority + +**Capability maturity:** `IMPLEMENTED_ON_ACTIVE_PR` + +Exact head `a57873b3688984711918be17aadd348ed9fb12a9` proves that, after an extension is genuinely allowed to `ProposeTypedAction`: + +1. a proposed navigation outside the Agent readable-origin grant is still denied; +2. proposal permission cannot supply the missing Agent `Navigate` capability; +3. extension-produced untrusted content remains rejected as instruction authority; +4. `FillSecret` with `SecretDelivery::RawValue` remains denied as `SecretBrokerRequired`; and +5. secret material attached to a non-secret action remains denied as `UnexpectedSecretMaterial`. + +The branch adds no production API and no extension runtime. It is compositional security evidence over protected-main authorities. + +### PR #63 — proposal authority cannot manufacture high-risk approval + +**Capability maturity:** `IMPLEMENTED_ON_ACTIVE_PR` + +Exact head `e83749acd1cf5a0b778ba38eb9d6ed5a9bd1e68f` deliberately keeps only the distinct approval-composition proof after duplicate regressions were removed in favor of PR #62 ownership. It proves that, after the same exact proposal grant is admitted and the Agent context independently possesses `FillSecret` plus exact readable/writable origin authority, broker-handle `FillSecret` still reaches the ordinary R3 approval boundary rather than becoming implicitly allowed. + +The exact head has successful CI, exact owned production coverage, Security Scan, SAST and CodeRabbit status and is Ready for review. It has no raw secret bytes and does not create approval evidence, a broker, browser-fill adapter, protected-value store, KMS path, authenticated workload identity, persistence owner, or release claim. + +## 4. Security interpretation + +The executable authority chain is intentionally non-transitive: + +```text +Chromium extension permission +-> explicit extension/session/context grant +-> permission to propose a typed action +-/> Agent capability +-/> Agent readable/writable origin +-/> trusted instruction source +-/> secret-delivery authority +-/> approval +-/> protected-value resolution +``` + +A future real extension adapter must preserve these separations. Chrome permissions and extension proposal grants are inputs to policy composition, never ambient authority that bypasses the deterministic Agent policy or the sensitive-data broker boundary. + +## 5. Remaining issue #27 / #10 boundary + +This dossier does **not** close issue #27 or issue #10. Remaining material work includes, among other accepted requirements: + +- real managed-extension allow-list and enterprise policy integration; +- native-messaging host boundary and process isolation; +- complete supported-capability release matrix and regression gate; +- authenticated workload/service identity for sensitive-data broker audience; +- protected-value resolution/fill outside model-visible context; +- durable transactional handle lifecycle, retention, encryption/KMS, deletion and audit-export controls; and +- protected-main integration plus fresh acceptance before any active-PR evidence becomes shipped truth. + +## 6. Documentation fitness consequence + +The existing ADR/PRD/TRD/Architecture/UML/ERD graph remains **DESIGN-SUFFICIENT / PROTECTED-MAIN-PARTIAL**. PRs #62 and #63 narrow distinct executable extension-authority evidence gaps without introducing a new trust domain, deployment component, persistence entity, database schema, or independent architecture decision. Proposed ADR 0013 remains Proposed until its own lifecycle authority changes. \ No newline at end of file diff --git a/docs/traceability/resolution-freshness-authority.md b/docs/traceability/resolution-freshness-authority.md new file mode 100644 index 000000000..edb91e4bb --- /dev/null +++ b/docs/traceability/resolution-freshness-authority.md @@ -0,0 +1,99 @@ +# Resolution Freshness Authority Trace + +- **Documentation status:** Active-PR traceability +- **Protected-main capability status:** **PARTIAL** +- **Primitive implementation lane:** PR #47, `feat/resolution-freshness-authority-main` +- **First-party planning consumer lane:** PR #50, `feat/network-consume-resolution-freshness` +- **Socket-use freshness lane:** PR #54, `fix/network-resolution-freshness-at-use` +- **Governing existing decision boundary:** ADR 0004 and the protected-main destination/rebinding authority model +- **Buyer-visible gap:** bind the interval between a validated resolution answer and actual socket use so DNS-rebinding/TOCTOU exposure is explicit and fail-closed + +## Truth boundary + +Protected `main` already classifies, approves, pins, and non-expansively revalidates resolved destination addresses. It does **not** yet require a time-bounded resolution authority through the entire first-party direct-socket path. + +PR #47 exact head `6b5ed4dcea281b505f67db6180bb14c3bc95b392` contains the reusable production `FreshResolutionSnapshot` primitive and has terminal successful CI/security/SAST/exact-coverage evidence. That primitive is therefore **IMPLEMENTED_ON_ACTIVE_PR** evidence only; it is not protected-main truth. + +PR #50 exact head `f8b43bc94444986ab23aa4ef3086e446a0b39295` implements the dependent first-party planning boundary. It keeps the untimed `ConnectionPlan` internal to `originweave-network`, exposes `FreshConnectionPlan` as the ordinary direct-socket planner, requires a `FreshResolutionSnapshot` plus caller-supplied trusted monotonic current time, rejects expired authority at plan authorization, and migrates existing TLS integration helpers through that same fresh boundary. Exact-head CI run `31408474576` passes repository contracts, formatting, workspace check/tests, strict Clippy, rustdoc and exact owned production function/line/region/branch coverage; CodeRabbit exact-head status is success. + +PR #54 exact head `ec81031c537f2b662910c1ce78c7ae0e0bfc9c1e` closes a later plan-to-connect TOCTOU discovered after #50: freshness checked only when the plan was created could expire before socket I/O. The active lane retains the exact `FreshResolutionSnapshot` in the single-use plan, exposes `connect_at(current_time)` to re-run freshness immediately before socket use under the caller's trusted monotonic clock domain, and keeps the legacy `connect()` surface fail-closed by adding process-local monotonic elapsed time to the original authorization checkpoint before delegating to `connect_at`. CI run `31418337788` passes repository contracts, formatting, workspace checks/tests, strict Clippy, rustdoc and exact owned production function/line/region/branch coverage; CodeRabbit exact-head status is successful. + +PRs #47, #50 and #54 remain **IMPLEMENTED_ON_ACTIVE_PR**, not shipped. #50 remains dependency-gated on #47 and #54 remains dependency-gated on #50. The overall protected-main resolution-to-socket interval therefore remains **PARTIAL** until dependency-ordered integration and fresh protected-main acceptance prove the same authority chain without an untimed planning or delayed-use bypass. + +## Current exact-head RCA + +### PR #47 primitive + +The first production-complete PR #47 head reached all ordinary Rust contracts and security scans, but exact coverage failed at one compiler region while functions, lines, and branches were already complete. Coverage evidence localized the missing region to the generic `FreshResolutionSnapshot::revalidate` instantiation used with a one-address resolver answer: the success path for a one-address contraction was exercised, while the same monomorphized helper's error propagation for a one-address expansion had not been executed. + +That was a realistic DNS-rebinding case rather than an impossible instrumentation artifact. The branch added a focused one-address expansion regression requiring `ResolutionSetExpanded`, retained the two-address expansion case, and exact head `6b5ed4dcea281b505f67db6180bb14c3bc95b392` subsequently passed CI including exact production function/line/region/branch coverage, Security Scan, and SAST Semgrep. + +The freshness ceiling is executable active-PR evidence rather than an aspirational requirement. `crates/originweave-destination/src/resolution.rs` owns `MAX_RESOLUTION_VALIDITY: Duration = Duration::from_secs(30)`. `FreshResolutionSnapshot::approve` rejects `Duration::ZERO` and any interval above that constant with `DestinationError::InvalidResolutionValidity`; `crates/originweave-destination/tests/resolution_freshness.rs::fresh_resolution_rejects_invalid_or_overflowing_validity` verifies both the zero and greater-than-30-second boundaries plus approval-time overflow. This evidence remains active-PR-only until PR #47 integrates. + +### PR #50 planning consumer + +PR #50 began from exact PR #47 head `6b5ed4dcea281b505f67db6180bb14c3bc95b392` with a RED consumer contract requiring fresh resolution authority plus one trusted monotonic current time before direct socket planning. + +A first production repair added a public `FreshConnectionPlan` wrapper that authorized freshness and then delegated to the existing untimed `ConnectionPlan`. That implementation made the positive/expiry path available but did not close the buyer/security gap because the original public `ConnectionPlan::new(&ResolutionSnapshot, ...)` remained callable. Canonical review therefore rejected the parallel-wrapper design as insufficient rather than weakening the acceptance boundary. + +The corrected implementation removed `ConnectionPlan` from the public crate exports while retaining it as a private implementation detail. Exact-head CI run `31407686307` then failed at the intended first-party migration boundary: `cargo check --locked --workspace --all-targets` found exactly three TLS integration tests still importing the now-private stale planner (`handshake_deadline.rs`, `handshake_integration.rs`, and `validity_horizon_integration.rs`). That compile failure was useful evidence because it enumerated remaining first-party bypass consumers instead of hiding them behind a compatibility re-export. + +Those integration helpers were migrated to deterministic `FreshResolutionSnapshot` + `FreshConnectionPlan` fixtures with one explicit trusted monotonic clock domain. A later run `31408143459` found only missing end-of-file newlines under rustfmt; that formatting-only defect was corrected without changing the authority contract. Current exact head `f8b43bc94444986ab23aa4ef3086e446a0b39295` then passed CI run `31408474576` end to end, including exact owned function/line/region/branch coverage. + +The accepted remedy is therefore realized on the active branch: ordinary first-party direct planning cannot import the untimed planner, while the private implementation remains reusable only after `FreshConnectionPlan` performs freshness authorization. This proves the active planning implementation, but not freshness at a later delayed socket-use instant. + +### PR #54 socket-use consumer + +PR #54 follows #50 because a plan authorized within the resolution window could be retained until that window expired and then connected. The first failing boundary was therefore no longer public planner construction; it was the time between plan authorization and the exact operating-system connect operation. + +The accepted active-branch remedy keeps the admitted freshness snapshot with the non-cloneable single-use plan and revalidates it at the socket-use boundary. `connect_at(current_time)` is the explicit deterministic path and rejects both expiry and an authorization-time regression using the existing destination error taxonomy. The compatibility `connect()` path does not freeze the old authorization timestamp: it anchors a process-local monotonic `Instant` at plan construction, adds actual elapsed time to the admitted authorization time, and delegates to `connect_at`, so delayed legacy callers cannot replay stale authority indefinitely. + +The regression suite proves explicit success, deadline expiry, trusted-time regression, unchanged connection-parameter validation, and expiry of the compatibility path with a deliberately short real monotonic interval. Current exact head `ec81031c537f2b662910c1ce78c7ae0e0bfc9c1e` passes CI run `31418337788`. This remains active-PR evidence and does not add DNS lookup, a wall-clock authority, proxy/PAC, or a resolver service. + +## Deterministic authority contract + +The active stack proves one continuous destination-to-socket authority chain with all of the following properties: + +1. approval time is explicit and supplied from one trusted monotonic clock domain; +2. validity is non-zero and capped by the active implementation's repository-owned `MAX_RESOLUTION_VALIDITY` safety budget (30 seconds on PR #47 exact head), with shorter caller-selected intervals permitted; +3. the usable interval is half-open: `approved_at <= now < valid_until`; +4. use before approval, use at/after expiry, arithmetic overflow, unapproved addresses, and set expansion fail closed with typed errors; +5. the ordinary first-party socket planner no longer publicly accepts an untimed `ResolutionSnapshot` as sufficient authority on PR #50 exact head; +6. a single-use plan rechecks the retained freshness authority immediately before socket I/O on PR #54 rather than assuming plan-time admission remains fresh; +7. the compatibility socket path derives a new use time from monotonic elapsed duration and therefore cannot preserve stale plan-time authority indefinitely; +8. credential-free planning evidence records approval, expiry, and authorization times without introducing credentials, resolver internals, or protected values; +9. non-expanding revalidation may renew the bounded interval only while rerunning existing destination-policy validation against the newly supplied answer; and +10. the primitive and planning/use boundaries perform no DNS lookup, wall-clock read, ambient proxy selection, TLS policy mutation, HTTP, browser control, persistence, secret, or model call. + +## Architecture and ADR assessment + +The primitive and its first-party planning/socket consumers tighten the already Accepted destination/rebinding authority governed by ADR 0004. They do not introduce a new component, persistence owner, wire protocol, browser adapter, or trust domain. Therefore a new ADR, deployment component, or physical ERD object would be false precision at this stage. + +The durable network-authority sequence is now `resolver answer -> destination/origin validation -> fresh resolution approval -> trusted monotonic plan authorization -> socket-use freshness recheck -> exact socket candidate -> observed TCP peer -> TLS/HTTP authority`. That is a sequence refinement within the existing network-authority component graph, not a new topology. A new or superseding ADR becomes appropriate only if later integration changes ownership—for example, durable cross-process freshness state, a separate resolver service, a different trusted-clock owner, or a new externally versioned protocol. + +## Evidence progression + +| Evidence state | Allowed maturity claim | +|---|---| +| Test-only primitive/consumer head with unresolved production API | intentional RED contract only; not implementation evidence | +| Active PR #47 production primitive + unchanged exact-head CI/security/100% coverage | `IMPLEMENTED_ON_ACTIVE_PR` for the primitive; overall protected-main path remains `PARTIAL` | +| Active PR #50 adds a freshness wrapper while an ordinary untimed planner remains public | implementation progress only; bypass still makes the consumer incomplete | +| Active PR #50 hides the untimed planner and exact compile evidence finds stale first-party consumers | valid structural remedy with migration still incomplete | +| Active PR #50 exact head `f8b43bc...` migrates first-party consumers and passes exact CI/coverage | `IMPLEMENTED_ON_ACTIVE_PR` for planning; delayed socket-use freshness still requires #54 | +| Active PR #54 exact head `ec81031c...` rechecks freshness immediately before socket I/O and passes exact CI/coverage | `IMPLEMENTED_ON_ACTIVE_PR` for socket-use freshness; dependency-gated and non-shipped | +| PR #47 + #50 + #54 exact heads are individually gate-clean but none are on protected main | active-PR evidence only; no shipped claim | +| Protected-main primitive/planner, but delayed socket use can outlive freshness | `PARTIAL` | +| Protected-main direct socket path requires exact fresh authority and rechecks it at use, with tests proving pre-approval/expiry/rebinding/delay behavior | `IMPLEMENTED_ON_PROTECTED_MAIN` for the bounded resolution-to-socket interval | +| Browser/network adapter proves the same clock and authority chain under real navigation | additional integration/release evidence; not implied by lower-layer primitives | + +## Required follow-through + +- keep PR #47 as active/non-shipped evidence until repository governance integrates it; +- keep PR #50 Draft and dependency-gated while #47 remains active; do not transfer its green evidence to protected main; +- keep PR #54 Draft and dependency-gated while #50 remains active; do not transfer its green evidence to #50 or protected main; +- preserve the structural invariant that ordinary first-party direct planning cannot import an untimed `ConnectionPlan`; +- preserve the socket-use invariant that a delayed call cannot reuse plan-time freshness without a new trusted monotonic use-time check; +- keep PRD/TRD/traceability from calling the DNS-rebinding/TOCTOU interval closed while any prerequisite remains active; +- reconcile the existing network-authority UML with the stable durable freshness sequence without encoding temporary branch-only identifiers as timeless architecture; +- retain the existing conceptual ERD unless a real persistence owner is introduced; and +- after all three layers integrate, rerun protected-main operational/release acceptance before promoting capability maturity. diff --git a/docs/traceability/tls-revocation-freshness-authority.md b/docs/traceability/tls-revocation-freshness-authority.md new file mode 100644 index 000000000..a5bfb98c0 --- /dev/null +++ b/docs/traceability/tls-revocation-freshness-authority.md @@ -0,0 +1,53 @@ +# TLS Revocation-Material Freshness Authority Trace + +- **Documentation status:** Active-PR traceability +- **Protected-main capability status:** **PARTIAL** +- **Active implementation lane:** PR #48, `feat/tls-revocation-freshness-main` +- **Governing existing boundary:** protected-main TLS service-identity authority, ADR 0006, ADR 0008, and the revocation-distribution/freshness roadmap gap +- **Buyer-visible gap:** prevent stale independently verified revocation material from being treated as current authority while preserving the fact that OriginWeave does not yet make an unrevoked-certificate claim + +## Truth boundary + +Protected `main` authenticates the requested HTTPS service over the already verified TCP stream, but its TLS evidence records revocation as `NotConfigured`. It does not fetch, parse, validate, cache, or enforce OCSP/CRL material and it does not claim that a certificate is unrevoked. + +PR #48 adds a reusable **freshness primitive** for revocation material only. The primitive can classify independently verified material as usable inside its signed `thisUpdate` to `nextUpdate` interval. That active-PR implementation is not protected-main truth, and passing the freshness check does not prove signature validity, path validity, responder authority, non-revocation, successful distribution, or complete TLS authentication policy. + +The complete revocation path therefore remains **PARTIAL** until a separately reviewed adapter acquires and cryptographically verifies revocation material, composes freshness into the authentication decision, defines failure/cache/recovery semantics, and proves the resulting behavior on protected main. + +## Required deterministic authority + +The bounded primitive is expected to preserve these properties: + +1. a higher-layer adapter may construct `RevocationMaterialFreshness` only after independent cryptographic verification has supplied both signed `thisUpdate` and `nextUpdate`; because RFC 6960 permits an OCSP `SingleResponse` to omit `nextUpdate`, absence must fail closed in that adapter before construction and must never be converted into an invented timestamp; +2. the active PR #48 primitive deliberately accepts mandatory `u64` `this_update_unix_seconds` and `next_update_unix_seconds`, so a missing `nextUpdate` has no representable successful state in the primitive; any future parser/adapter must expose a typed missing-`nextUpdate` error or a separately reviewed bounded fallback contract before calling freshness approved; +3. the signed interval is non-empty and ordered; +4. the usable interval is half-open: `thisUpdate <= trusted_time < nextUpdate`; +5. trusted time before `thisUpdate` and at/after `nextUpdate` fails closed with typed bounded errors; +6. the primitive performs no OCSP/CRL fetch, DNS, socket connection, TLS handshake mutation, parsing, signature verification, cache operation, browser control, persistence, or model call; and +7. no evidence or documentation converts freshness into an `unrevoked` claim. + +## Architecture and ADR assessment + +The active primitive tightens an existing TLS evidence/policy concern without introducing a new deployed component, persistence owner, wire protocol, network path, or secret boundary. A new ADR is therefore not required merely because the helper type exists. + +A new or superseding ADR becomes appropriate if OriginWeave later chooses a concrete revocation architecture that changes trust ownership—for example, stapled OCSP versus independently fetched OCSP/CRL, cache authority and freshness policy, hard-fail versus explicitly bounded degraded behavior, responder/path validation ownership, or a separate revocation service. + +No new physical ERD object is justified by this active in-memory primitive. UML should change only when the executable TLS/revocation data or control path changes materially. + +## Evidence progression + +| Evidence state | Allowed maturity claim | +|---|---| +| Protected main records `RevocationStatus::NotConfigured` | `PARTIAL`; no revocation enforcement or unrevoked claim | +| Active PR freshness primitive with exact-head tests/coverage | `IMPLEMENTED_ON_ACTIVE_PR` for freshness classification only | +| Protected-main freshness primitive without verified material acquisition/composition | `PARTIAL` | +| Protected-main adapter verifies responder/material authenticity, requires or safely bounds missing `nextUpdate`, enforces freshness, cache/failure policy, and binds the result into TLS authentication | implementation evidence for the chosen bounded revocation policy | +| Protected-main integration/recovery/operational tests prove the complete path | required additional release evidence; not implied by the helper primitive | + +## Required follow-through + +- keep PRD/TRD/TLS evidence from implying revocation enforcement while protected main remains `NotConfigured`; +- define revocation-material acquisition, authenticity, missing-`nextUpdate`, cache, freshness, failure, privacy, and recovery semantics before calling the TLS revocation boundary implemented; +- require exact 100% owned production function/line/region/branch coverage and complete rustdoc on every changed head; +- add or supersede an ADR only when the concrete revocation architecture changes a durable trust or deployment decision; and +- retain the conceptual ERD unless executable persistence ownership actually appears. diff --git a/docs/uml/README.md b/docs/uml/README.md index 1d04ab00a..1a985e254 100644 --- a/docs/uml/README.md +++ b/docs/uml/README.md @@ -6,6 +6,10 @@ These diagrams visualize governing boundaries; they do not imply that every planned adapter is already shipped. Labels use `implemented`, `active`, or `planned` where implementation status matters. +## Focused authority views + +- [Manifest V3 extension compatibility and Agent authority](extension-authority.md) + ## 1. Component and bounded-context view ```mermaid @@ -404,4 +408,4 @@ Update this pack when a protected change materially alters: - deployment boundaries; - evidence/provenance relationships. -A feature-specific ADR may include a more detailed sequence diagram, but this pack remains the product-wide view and must not require maintainers to reconstruct the complete system from scattered ADR diagrams. +A feature-specific ADR may include a more detailed sequence diagram, but this pack remains the product-wide view and must not require maintainers to reconstruct the complete system from scattered ADR diagrams. \ No newline at end of file diff --git a/docs/uml/extension-authority.md b/docs/uml/extension-authority.md new file mode 100644 index 000000000..e228e84d4 --- /dev/null +++ b/docs/uml/extension-authority.md @@ -0,0 +1,105 @@ +# Extension Compatibility and Agent Authority UML + +- **Status:** Protected-main architecture visualization with active compatibility work +- **Scope:** Chromium Manifest V3 compatibility plane versus OriginWeave Agent authority +- **Related:** [`README.md`](README.md), [`../PRD.md`](../PRD.md), [`../TRD.md`](../TRD.md), [`../THREAT_MODEL.md`](../THREAT_MODEL.md), issue #27 + +This diagram makes one security invariant visually explicit: + +> **A Chromium extension permission is not an OriginWeave Agent capability.** + +A compatible extension can use the Chromium APIs granted by its manifest and managed browser policy. It cannot thereby grant itself OriginWeave task authority, widen an Agent Task origin, resolve a protected secret, approve a high-risk action, or turn extension/page content into a trusted instruction. + +## Authority sequence + +```mermaid +sequenceDiagram + autonumber + participant Admin as Human / Enterprise Policy + participant Chrome as Chromium MV3 Runtime + participant Ext as Extension Worker / Content Script + participant Observe as OriginWeave Observation Adapter + participant Grant as OriginWeave Extension Grant Policy + participant Agent as Agent Task / Planner + participant Policy as Deterministic Action Policy + participant Broker as Secret / Sensitive Broker + participant Browser as Trusted Browser Adapter + participant Evidence as Evidence Trail + + Admin->>Chrome: install/enable extension under Chromium policy + Chrome-->>Ext: expose manifest-granted Chrome APIs + Note over Chrome,Ext: Chrome permission is compatibility authority only. + + Ext-->>Observe: extension message / page mutation / tool output + Observe-->>Agent: bounded untrusted observation + provenance + Note over Ext,Agent: Extension content cannot become trusted goal or policy. + + Admin->>Grant: issue explicit OriginWeave extension grant for bounded session/context/capability/origin + Ext->>Grant: request OriginWeave interaction + Grant->>Grant: verify extension identity, managed policy, session/context, capability, origin, expiry + + alt no valid OriginWeave grant + Grant-->>Ext: deny + Grant-->>Evidence: denial without sensitive value + else valid grant + Grant-->>Agent: bounded extension-originated proposal/evidence + Agent->>Policy: propose typed action under existing Agent Task authority + Policy->>Policy: revalidate task, action, risk, origin, approval and current browser authority + alt action requires secret/sensitive value + Policy->>Broker: authorize exact opaque handle use + Broker->>Broker: revalidate tenant/task/field/purpose/destination/expiry + Broker-->>Browser: minimum trusted value delivery + end + Policy-->>Browser: authorized typed action + Browser->>Browser: verify session/context/document epoch immediately before dispatch + Browser-->>Evidence: action result + observed post-condition + end +``` + +## Security state flow + +```mermaid +flowchart TD + manifest[Manifest V3 permissions] --> chromium[Chromium extension authority] + chromium --> extension[Extension runtime] + extension --> untrusted[Untrusted observation / message] + untrusted --> grant{Explicit OriginWeave extension grant?} + grant -- no --> deny[Deny Agent-control request] + grant -- yes --> scoped[Bind extension identity + session + context + origin + capability + expiry] + scoped --> proposal[Typed Agent action proposal] + proposal --> policy{Agent Task policy passes?} + policy -- no --> deny + policy -- yes --> approval{Risk-specific approval required?} + approval -- missing/invalid --> deny + approval -- no or valid --> execute[Trusted browser adapter executes] + execute --> verify{Observed post-condition matches?} + verify -- no --> fail[Fail / quarantine] + verify -- yes --> evidence[Credential-safe evidence] + + extension -. cannot mint .-> scoped + extension -. cannot approve .-> approval + extension -. cannot resolve .-> secret[Protected secret / sensitive value] + secret --> execute +``` + +## Compatibility evidence is separate from authority evidence + +```mermaid +flowchart LR + pinned[Pinned Chromium revision] --> fixture[Controlled MV3 fixture suite] + fixture --> compat[Compatibility evidence] + compat --> matrix[Published supported-capability matrix] + + policycode[OriginWeave extension policy] --> isolation[Agent-authority isolation evidence] + isolation --> release[Release acceptance] + matrix --> release + + compat -. does not prove .-> isolation + isolation -. does not prove .-> compat +``` + +The release claim requires both evidence classes. A passing `downloads`, `bookmarks`, `history`, storage, service-worker, DNR, or content-script compatibility test does not prove extension isolation. Conversely, a correct Rust extension-grant kernel does not prove that a real Chromium extension API works. + +## Maturity discipline + +Protected main already contains extension-to-Agent authority foundations and pinned-Chromium MV3 compatibility evidence for several surfaces. Issue #27 remains open because the complete declared capability matrix, remaining compatibility surfaces, managed/native-messaging boundaries and release integration are not yet complete. This diagram therefore represents a mixture of implemented foundations and accepted/planned product flow; it must not be read as a claim of full Chrome extension compatibility. diff --git a/tests/test_documentation_active_pr_evidence_contract.py b/tests/test_documentation_active_pr_evidence_contract.py new file mode 100644 index 000000000..d2a067e50 --- /dev/null +++ b/tests/test_documentation_active_pr_evidence_contract.py @@ -0,0 +1,196 @@ +"""Regression contracts for volatile active-PR evidence in canonical documentation.""" + +from pathlib import Path +import unittest + + +ROOT = Path(__file__).resolve().parents[1] +DOCS = ROOT / "docs" +FITNESS = DOCS / "DOCUMENTATION_FITNESS.md" +MATURITY = DOCS / "evidence" / "2026-08-10-active-pr-maturity.md" + + +def active_pr_row(text: str, pr_number: int) -> str: + """Return exactly one maturity row for an active pull request.""" + prefix = f"| #{pr_number} |" + rows = [line for line in text.splitlines() if line.startswith(prefix)] + if len(rows) != 1: + raise AssertionError( + f"expected exactly one active maturity row for PR #{pr_number}, got {len(rows)}" + ) + return rows[0] + + +class ActivePullRequestDocumentationContractTests(unittest.TestCase): + """Keep volatile implementation evidence separate from protected-main truth.""" + + @classmethod + def setUpClass(cls) -> None: + cls.fitness = FITNESS.read_text(encoding="utf-8") + cls.maturity = MATURITY.read_text(encoding="utf-8") + + def test_dependency_stacks_are_explicit_and_non_shipped(self) -> None: + """Current browser, network, sensitive and compatibility stacks stay active-only.""" + for pr_number in (52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66): + with self.subTest(pr_number=pr_number): + row = active_pr_row(self.maturity, pr_number) + self.assertIn("**IMPLEMENTED_ON_ACTIVE_PR**", row) + self.assertNotIn("IMPLEMENTED_ON_PROTECTED_MAIN", row) + + for stack in ( + "#47 → #50 → #54", + "#45 → #46 → #53 → #55", + "#40→#52→#57→#58", + "#43→#56→#59→#60→#61", + "#51→#66", + ): + with self.subTest(stack=stack): + self.assertIn(stack, self.fitness) + + self.assertIn("#49", self.fitness) + + def test_semantic_relationship_evidence_stays_bounded_and_authority_scoped(self) -> None: + """PR #52 cannot turn relationship metadata into browser or execution authority.""" + row = active_pr_row(self.maturity, 52) + for marker in ("128", "relationship", "session/context/origin/document"): + with self.subTest(marker=marker): + self.assertIn(marker, row) + + for marker in ( + "same browser session, browsing context, canonical origin and document epoch", + "Self-parent/self-child relationships and duplicate child handles fail closed", + "relationship graph remains descriptive evidence", + "not a browser observation adapter", + ): + with self.subTest(marker=marker): + self.assertIn(marker, self.fitness) + + def test_typed_semantic_query_evidence_stays_descriptive_and_bounded(self) -> None: + """PR #57 cannot turn semantic matching into selector or execution authority.""" + row = active_pr_row(self.maturity, 57) + for marker in ( + "SemanticNodeQuery", + "role", + "accessible-name", + "typed-action", + "no CSS/XPath/raw DOM selector language", + "browser I/O or action authority", + ): + with self.subTest(marker=marker): + self.assertIn(marker, row) + + self.assertIn("Draft stacked on exact #52 head", row) + self.assertIn("CI run `31429995885`", row) + self.assertIn("CodeRabbit exact-head status succeed", row) + self.assertIn("remains Draft because #52/#40 are active prerequisites", row) + + def test_sensitive_audience_evidence_does_not_claim_authentication(self) -> None: + """An internal audience field is not authenticated workload/service identity.""" + row = active_pr_row(self.maturity, 55) + self.assertIn("authenticated workload/service identity", row) + self.assertIn( + "audience string accepted by the value/policy primitive is **not authentication**", + self.fitness, + ) + self.assertIn("new deployment topology or physical ERD entity", self.fitness) + + def test_mv3_mutation_and_isolation_are_compatibility_not_agent_authority(self) -> None: + """Real MV3 evidence must remain separate from OriginWeave capability grants.""" + bookmark_row = active_pr_row(self.maturity, 56) + for marker in ("create", "get", "remove", "compatibility evidence only"): + with self.subTest(marker=marker): + self.assertIn(marker, bookmark_row) + + for pr_number in (59, 60, 61): + with self.subTest(pr_number=pr_number): + row = active_pr_row(self.maturity, pr_number) + self.assertIn("**IMPLEMENTED_ON_ACTIVE_PR**", row) + self.assertNotIn("IMPLEMENTED_ON_PROTECTED_MAIN", row) + + self.assertIn("Manifest V3 compatibility", self.fitness) + self.assertIn( + "Chromium permission or browser compatibility success is not an OriginWeave Agent capability", + self.fitness, + ) + self.assertIn( + "#43/#49/#56/#59/#60/#61 are active compatibility evidence only", + self.fitness, + ) + self.assertIn("Update migration is intentionally distinct from restart persistence", self.fitness) + self.assertIn("isolated-world behavior is intentionally distinct from injection alone", self.fitness) + + def test_extension_proposal_grant_does_not_become_agent_policy_authority(self) -> None: + """PR #62 must remain a policy-isolation regression, not a new action grant.""" + row = active_pr_row(self.maturity, 62) + for marker in ( + "ProposeTypedAction", + "out-of-grant target origin", + "missing core `Navigate` capability", + "untrusted instruction source", + "adds no production API or real Chromium adapter", + "does not convert extension proposal authority into Agent action/origin authority", + ): + with self.subTest(marker=marker): + self.assertIn(marker, row) + self.assertIn("CI run `31436844685`", row) + self.assertIn("Security Scan run `31436844615`", row) + self.assertIn("SAST Semgrep run `31436844646`", row) + self.assertNotIn("IMPLEMENTED_ON_PROTECTED_MAIN", row) + + def test_latest_agent_task_and_secret_composition_evidence_remains_partial(self) -> None: + """Newest active slices must not be promoted into a complete browser or broker runtime.""" + secret_approval = active_pr_row(self.maturity, 63) + for marker in ( + "ProposeTypedAction", + "RequireApproval(RiskClass::R3)", + "no secret broker", + ): + with self.subTest(pr_number=63, marker=marker): + self.assertIn(marker, secret_approval) + + action_outcome = active_pr_row(self.maturity, 64) + for marker in ( + "PostConditionPredatesDispatch", + "monotonic", + "not a browser dispatcher", + ): + with self.subTest(pr_number=64, marker=marker): + self.assertIn(marker, action_outcome) + + controlled_fixture = active_pr_row(self.maturity, 65) + for marker in ( + "controlled", + "prompt-injection", + "not a browser adapter", + ): + with self.subTest(pr_number=65, marker=marker): + self.assertIn(marker, controlled_fixture) + + process_set = active_pr_row(self.maturity, 66) + for marker in ( + "process-set RSS", + "duplicate", + "does not discover Chromium PIDs", + ): + with self.subTest(pr_number=66, marker=marker): + self.assertIn(marker, process_set) + + for marker in ( + "#62/#63", + "#64", + "#65", + "#51→#66", + "real Chromium", + ): + with self.subTest(fitness_marker=marker): + self.assertIn(marker, self.fitness) + + def test_erd_stays_conceptual_without_persistence_owner(self) -> None: + """Active in-memory/value primitives must not manufacture a physical data model.""" + self.assertIn("Conceptual ERD/domain model", self.fitness) + self.assertIn("add no OriginWeave-owned durable store", self.fitness) + self.assertIn("false architecture", self.fitness) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_documentation_discoverability_followup.py b/tests/test_documentation_discoverability_followup.py new file mode 100644 index 000000000..67801c1ce --- /dev/null +++ b/tests/test_documentation_discoverability_followup.py @@ -0,0 +1,43 @@ +"""Focused regression contracts for reviewed documentation discoverability gaps.""" + +from __future__ import annotations + +import pathlib +import unittest + +ROOT = pathlib.Path(__file__).resolve().parents[1] + + +class DocumentationDiscoverabilityFollowupTests(unittest.TestCase): + """Keep canonical diagrams and maturity vocabulary machine-discoverable.""" + + def test_extension_authority_view_is_indexed_mermaid(self) -> None: + """The extension authority view must exist, be indexed, and remain diagram-as-code.""" + uml_index = (ROOT / "docs" / "uml" / "README.md").read_text(encoding="utf-8") + authority_view = ROOT / "docs" / "uml" / "extension-authority.md" + + self.assertTrue(authority_view.is_file()) + self.assertIn("](extension-authority.md)", uml_index) + self.assertIn("```mermaid", authority_view.read_text(encoding="utf-8")) + + def test_traceability_keeps_complete_maturity_vocabulary(self) -> None: + """Every canonical capability maturity label must remain explicit.""" + traceability = (ROOT / "docs" / "traceability" / "README.md").read_text( + encoding="utf-8" + ) + for label in ( + "IMPLEMENTED_ON_PROTECTED_MAIN", + "IMPLEMENTED_ON_ACTIVE_PR", + "PARTIAL", + "ACCEPTED_ARCHITECTURE", + "PLANNED", + "RESEARCH_ONLY", + "SUPERSEDED", + "OUT_OF_SCOPE", + ): + with self.subTest(label=label): + self.assertIn(label, traceability) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_documentation_fitness_contract.py b/tests/test_documentation_fitness_contract.py new file mode 100644 index 000000000..33aefed22 --- /dev/null +++ b/tests/test_documentation_fitness_contract.py @@ -0,0 +1,265 @@ +"""Regression contracts for the authoritative OriginWeave documentation graph.""" + +from pathlib import Path +import re +import unittest + + +REPOSITORY_ROOT = Path(__file__).resolve().parents[1] +DOCS_ROOT = REPOSITORY_ROOT / "docs" +ADR_ROOT = DOCS_ROOT / "adr" +UML_ROOT = DOCS_ROOT / "uml" + +ADR_STATUSES = {"Proposed", "Accepted", "Superseded", "Deprecated", "Rejected"} + + +def _adr_files() -> set[str]: + """Return every numbered ADR Markdown file currently tracked by the repository.""" + return { + path.name + for path in ADR_ROOT.glob("[0-9][0-9][0-9][0-9]-*.md") + if path.is_file() + } + + +def _adr_file_status(path: Path) -> str: + """Read one ADR's explicit lifecycle status from its metadata header.""" + text = path.read_text(encoding="utf-8") + match = re.search( + r"(?im)^-\s+(?:\*\*Status:\*\*|\*\*Status\*\*:|Status:)\s*(\w+)(?:[;\s].*)?$", + text, + ) + if match is None: + raise AssertionError(f"ADR has no parseable status: {path.name}") + status = match.group(1) + if status not in ADR_STATUSES: + raise AssertionError(f"ADR has unsupported status {status!r}: {path.name}") + return status + + +def _insert_unique(mapping: dict[str, str], path: str, status: str, source: str) -> None: + """Insert one index target while rejecting duplicate or conflicting entries.""" + if path in mapping: + raise AssertionError(f"duplicate ADR index target {path!r} in {source}") + mapping[path] = status + + +def _parse_docs_index(text: str) -> dict[str, str]: + """Parse ADR links from the product documentation index by lifecycle section.""" + mapping: dict[str, str] = {} + current_status: str | None = None + for line in text.splitlines(): + if line.startswith("## "): + current_status = next( + (status for status in ADR_STATUSES if line.startswith(f"## {status}")), + None, + ) + continue + target = re.search(r"\(adr/(\d{4}[-\w]*\.md)\)", line) + if target is not None: + if current_status is None: + raise AssertionError( + f"ADR link {target.group(1)!r} is outside a lifecycle-status section" + ) + _insert_unique(mapping, target.group(1), current_status, "docs/README.md") + return mapping + + +def _parse_adr_index(text: str) -> dict[str, str]: + """Parse the dedicated ADR table into an exact target-to-status mapping.""" + mapping: dict[str, str] = {} + pattern = re.compile( + r"^\|\s*\[\d{4}\]\((\d{4}[-\w]*\.md)\)\s*\|[^|]*\|\s*" + r"(Proposed|Accepted|Superseded|Deprecated|Rejected)(?:[;\s][^|\r\n]*)?\s*\|", + re.MULTILINE, + ) + for path, status in pattern.findall(text): + _insert_unique(mapping, path, status, "docs/adr/README.md") + return mapping + + +def _active_pr_row(text: str, pr_number: int) -> str: + """Return one exact active-PR evidence row from the dated maturity appendix.""" + prefix = f"| #{pr_number} |" + rows = [line for line in text.splitlines() if line.startswith(prefix)] + if len(rows) != 1: + raise AssertionError(f"expected exactly one maturity row for PR #{pr_number}, got {len(rows)}") + return rows[0] + + +class DocumentationFitnessContractTests(unittest.TestCase): + """Keep architecture discovery and implementation-maturity metadata coherent.""" + + def test_documentation_index_links_fitness_assessment(self) -> None: + """The semantic fitness audit must remain discoverable from the docs index.""" + index = (DOCS_ROOT / "README.md").read_text(encoding="utf-8") + self.assertIn("[Documentation fitness assessment](DOCUMENTATION_FITNESS.md)", index) + self.assertTrue((DOCS_ROOT / "DOCUMENTATION_FITNESS.md").is_file()) + + def test_documentation_fitness_distinguishes_design_from_protected_main(self) -> None: + """A broad design pack must not be mislabeled as protected-main closure.""" + assessment = (DOCS_ROOT / "DOCUMENTATION_FITNESS.md").read_text(encoding="utf-8") + self.assertIn("DESIGN-SUFFICIENT", assessment) + self.assertIn("PROTECTED-MAIN-PARTIAL", assessment) + self.assertIn("File existence alone is never sufficient", assessment) + self.assertIn("HTTP lineage", assessment) + self.assertIn("Manifest V3 compatibility", assessment) + self.assertIn("Browser identifier authority", assessment) + self.assertIn("Semantic observation authority", assessment) + self.assertIn("integration before any of these branch repairs become protected-main truth", assessment) + + def test_every_adr_is_indexed_once_with_its_file_status(self) -> None: + """Both canonical indexes must exactly cover ADR files and their lifecycle status.""" + actual_files = _adr_files() + file_status = { + path: _adr_file_status(ADR_ROOT / path) + for path in sorted(actual_files) + } + docs_index = _parse_docs_index((DOCS_ROOT / "README.md").read_text(encoding="utf-8")) + adr_index = _parse_adr_index((ADR_ROOT / "README.md").read_text(encoding="utf-8")) + + self.assertEqual(set(docs_index), actual_files) + self.assertEqual(set(adr_index), actual_files) + self.assertEqual(docs_index, file_status) + self.assertEqual(adr_index, file_status) + + def test_adr_index_does_not_use_change_local_language_as_timeless_authority(self) -> None: + """The protected-main ADR index must not describe its ADRs as only `this change`.""" + adr_index = (ADR_ROOT / "README.md").read_text(encoding="utf-8") + self.assertNotIn("Proposed target-architecture decisions in this change", adr_index) + self.assertIn("Index completeness rule", adr_index) + + def test_proposed_adr_provenance_does_not_promote_branch_to_protected_main(self) -> None: + """Branch-only ADR presence must remain distinct from lifecycle and protected-main truth.""" + docs_index = (DOCS_ROOT / "README.md").read_text(encoding="utf-8") + adr_index = (ADR_ROOT / "README.md").read_text(encoding="utf-8") + + for text in (docs_index, adr_index): + with self.subTest(index="docs" if text is docs_index else "adr"): + self.assertIn("## Proposed architecture decisions", text) + self.assertIn("Protected-main baseline proposed decisions", text) + self.assertNotIn("## Proposed decisions retained on protected main", text) + + docs_branch = docs_index.split( + "### Proposed decisions introduced by this documentation reconciliation", 1 + )[1].split("\n## ", 1)[0] + adr_branch = adr_index.split( + "### Proposed decisions introduced by documentation reconciliation", 1 + )[1].split("\n## ", 1)[0] + for adr_path in ( + "0013-manifest-v3-extension-authority.md", + "0014-architecture-decision-governance.md", + ): + with self.subTest(adr=adr_path): + self.assertIn(adr_path, docs_branch) + self.assertIn(adr_path, adr_branch) + self.assertIn("exist only on this documentation branch until it integrates", adr_index) + + def test_current_replacement_lanes_are_not_promoted_to_protected_main(self) -> None: + """Each active implementation lane must carry its own exact non-shipped maturity mapping.""" + assessment = (DOCS_ROOT / "DOCUMENTATION_FITNESS.md").read_text(encoding="utf-8") + traceability = (DOCS_ROOT / "traceability" / "README.md").read_text(encoding="utf-8") + appendix = (DOCS_ROOT / "evidence" / "2026-08-10-active-pr-maturity.md").read_text( + encoding="utf-8" + ) + + for pr_number in (37, 40, 43, 52, 58, 59): + row = _active_pr_row(appendix, pr_number) + with self.subTest(pr_number=pr_number): + self.assertIn("**IMPLEMENTED_ON_ACTIVE_PR**", row) + self.assertNotIn("IMPLEMENTED_ON_PROTECTED_MAIN", row) + + for marker in ("issue #10", "issue #27", "issue #28"): + with self.subTest(marker=marker): + self.assertTrue(marker in assessment or marker in traceability) + + self.assertIn("IMPLEMENTED_ON_ACTIVE_PR", traceability) + self.assertIn("IMPLEMENTED_ON_PROTECTED_MAIN", traceability) + self.assertIn("Active-PR behavior is never protected-main truth", traceability) + + def test_semantic_observation_lane_stays_non_shipped_and_provenance_bound(self) -> None: + """The semantic observation value object must stay active-only and distinct from browser I/O.""" + appendix = (DOCS_ROOT / "evidence" / "2026-08-10-active-pr-maturity.md").read_text( + encoding="utf-8" + ) + prd = (DOCS_ROOT / "PRD.md").read_text(encoding="utf-8") + row = _active_pr_row(appendix, 52) + + self.assertIn("**IMPLEMENTED_ON_ACTIVE_PR**", row) + self.assertIn("semantic-node observation", row) + self.assertIn("no browser I/O or action dispatch", row) + self.assertNotIn("IMPLEMENTED_ON_PROTECTED_MAIN", row) + self.assertIn("active PR #52", prd) + self.assertIn("not a browser observation adapter", prd) + + def test_action_target_and_history_lanes_preserve_authority_boundaries(self) -> None: + """New active lanes must not turn descriptive or compatibility evidence into authority.""" + assessment = (DOCS_ROOT / "DOCUMENTATION_FITNESS.md").read_text(encoding="utf-8") + appendix = (DOCS_ROOT / "evidence" / "2026-08-10-active-pr-maturity.md").read_text( + encoding="utf-8" + ) + action_row = _active_pr_row(appendix, 58) + history_row = _active_pr_row(appendix, 59) + + self.assertIn("descriptive execution input, not policy authorization", action_row) + self.assertIn("no Agent history capability", history_row) + self.assertIn("business-risk classification", assessment) + self.assertIn("OriginWeave Agent history grant", assessment) + self.assertNotIn("IMPLEMENTED_ON_PROTECTED_MAIN", action_row) + self.assertNotIn("IMPLEMENTED_ON_PROTECTED_MAIN", history_row) + + def test_active_pr_maturity_appendix_tracks_current_dependency_stacks(self) -> None: + """Volatile evidence must retain the current browser/network/sensitive stacks explicitly.""" + appendix = (DOCS_ROOT / "evidence" / "2026-08-10-active-pr-maturity.md").read_text( + encoding="utf-8" + ) + for marker in ("| #52 |", "| #53 |", "| #54 |", "| #55 |", "| #58 |", "| #59 |"): + with self.subTest(marker=marker): + self.assertIn(marker, appendix) + self.assertIn("authenticated workload/service identity", appendix) + self.assertIn( + "formatting-only or metadata-only correction invalidates predecessor-head exactness", + appendix, + ) + + def test_prd_does_not_restore_superseded_active_pr_claims(self) -> None: + """Historical feature branches must not reappear as the current implementation lane.""" + prd = (DOCS_ROOT / "PRD.md").read_text(encoding="utf-8") + self.assertNotIn("Active PR #11", prd) + self.assertNotIn("Active replacement PR #33", prd) + self.assertIn("active replacement PR #37", prd) + self.assertIn("Protected-main purpose-bound sensitive-data policy kernel", prd) + self.assertIn("active PR #43 adds", prd) + + def test_trd_uses_single_status_with_separate_active_pr_evidence(self) -> None: + """Implementation status must not be collapsed with active-development annotations.""" + trd = (DOCS_ROOT / "TRD.md").read_text(encoding="utf-8") + self.assertNotIn("**Planned / active development**", trd) + self.assertNotIn("**Accepted architecture; active development.**", trd) + self.assertIn("Protected-main status", trd) + self.assertIn("Active/non-shipped evidence", trd) + self.assertIn("Active replacement PR #37", trd) + self.assertIn("purpose-bound sensitive-data authority", trd) + + def test_extension_authority_uml_separates_compatibility_from_agent_authority(self) -> None: + """A Chrome permission must never be documented as an Agent capability.""" + diagram = (UML_ROOT / "extension-authority.md").read_text(encoding="utf-8") + self.assertIn("A Chromium extension permission is not an OriginWeave Agent capability", diagram) + self.assertIn("Compatibility evidence is separate from authority evidence", diagram) + self.assertIn("sequenceDiagram", diagram) + self.assertIn("OriginWeave Extension Grant Policy", diagram) + self.assertIn("cannot approve", diagram) + self.assertIn("cannot resolve", diagram) + + def test_fitness_audit_does_not_duplicate_existing_resource_or_hourly_uml(self) -> None: + """The audit must recognize existing product-wide resource and automation diagrams.""" + assessment = (DOCS_ROOT / "DOCUMENTATION_FITNESS.md").read_text(encoding="utf-8") + uml_index = (UML_ROOT / "README.md").read_text(encoding="utf-8") + self.assertIn("resource-pressure/GPU fallback", assessment) + self.assertIn("hourly automation flows", assessment) + self.assertIn("## 9. Resource-pressure and fallback flow", uml_index) + self.assertIn("## 10. Hourly product-development gate-to-model flow", uml_index) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_extension_authority_traceability_contract.py b/tests/test_extension_authority_traceability_contract.py new file mode 100644 index 000000000..3ca5c5cdd --- /dev/null +++ b/tests/test_extension_authority_traceability_contract.py @@ -0,0 +1,49 @@ +"""Regression contract for extension-to-Agent security traceability.""" + +from pathlib import Path +import unittest + + +REPOSITORY_ROOT = Path(__file__).resolve().parents[1] +TRACEABILITY = ( + REPOSITORY_ROOT / "docs" / "traceability" / "extension-authority-security.md" +) + + +class ExtensionAuthorityTraceabilityContractTests(unittest.TestCase): + """Keep compatibility, Agent authority, and secret authority as separate claims.""" + + def test_extension_security_dossier_preserves_maturity_boundaries(self) -> None: + """Active security proofs must never be promoted to protected-main shipped truth.""" + text = TRACEABILITY.read_text(encoding="utf-8") + semantic_text = text.replace("**", "") + + self.assertIn("DESIGN-SUFFICIENT / PROTECTED-MAIN-PARTIAL", semantic_text) + self.assertIn("IMPLEMENTED_ON_PROTECTED_MAIN", text) + self.assertIn("IMPLEMENTED_ON_ACTIVE_PR", text) + self.assertIn("PR #62", text) + self.assertIn("PR #63", text) + self.assertIn("Proposed ADR 0013", text) + self.assertIn("SecretBrokerRequired", text) + self.assertIn("UnexpectedSecretMaterial", text) + self.assertIn("R3 approval", text) + self.assertIn("does not close issue #27 or issue #10", semantic_text) + + def test_extension_proposal_authority_is_explicitly_non_transitive(self) -> None: + """The dossier must forbid proposal permission from becoming broader Agent authority.""" + text = TRACEABILITY.read_text(encoding="utf-8") + + for boundary in ( + "-/> Agent capability", + "-/> Agent readable/writable origin", + "-/> trusted instruction source", + "-/> secret-delivery authority", + "-/> approval", + "-/> protected-value resolution", + ): + with self.subTest(boundary=boundary): + self.assertIn(boundary, text) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_freshness_traceability_contract.py b/tests/test_freshness_traceability_contract.py new file mode 100644 index 000000000..a9e0e4f01 --- /dev/null +++ b/tests/test_freshness_traceability_contract.py @@ -0,0 +1,50 @@ +"""Regression contracts for bounded freshness-authority documentation.""" + +from __future__ import annotations + +import pathlib +import unittest + +ROOT = pathlib.Path(__file__).resolve().parents[1] +TRACEABILITY = ROOT / "docs" / "traceability" + + +class FreshnessTraceabilityContractTests(unittest.TestCase): + """Keep active freshness primitives discoverable without promoting them to shipped truth.""" + + def test_traceability_index_discovers_each_active_freshness_authority(self) -> None: + """Resolution and TLS freshness traces must be linked from the canonical index.""" + index = (TRACEABILITY / "README.md").read_text(encoding="utf-8") + for filename in ( + "resolution-freshness-authority.md", + "tls-revocation-freshness-authority.md", + ): + with self.subTest(filename=filename): + self.assertTrue((TRACEABILITY / filename).is_file()) + self.assertIn(f"]({filename})", index) + + def test_active_freshness_traces_preserve_protected_main_maturity(self) -> None: + """Active implementation evidence must remain explicitly non-shipped and partial overall.""" + for filename in ( + "resolution-freshness-authority.md", + "tls-revocation-freshness-authority.md", + ): + text = (TRACEABILITY / filename).read_text(encoding="utf-8") + with self.subTest(filename=filename): + self.assertIn("Active-PR traceability", text) + self.assertIn("Protected-main capability status:** **PARTIAL", text) + self.assertIn("IMPLEMENTED_ON_ACTIVE_PR", text) + self.assertIn("not protected-main truth", text) + + def test_resolution_trace_requires_socket_use_freshness_not_only_plan_time(self) -> None: + """The DNS freshness trace must retain the delayed-use boundary added by PR #54.""" + text = (TRACEABILITY / "resolution-freshness-authority.md").read_text(encoding="utf-8") + self.assertIn("Socket-use freshness lane:** PR #54", text) + self.assertIn("connect_at(current_time)", text) + self.assertIn("rechecks the retained freshness authority immediately before socket I/O", text) + self.assertIn("delayed call cannot reuse plan-time freshness", text) + self.assertIn("#47 + #50 + #54", text) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_mv3_supported_capability_matrix_contract.py b/tests/test_mv3_supported_capability_matrix_contract.py new file mode 100644 index 000000000..fcc24db52 --- /dev/null +++ b/tests/test_mv3_supported_capability_matrix_contract.py @@ -0,0 +1,97 @@ +"""Regression contract for the canonical MV3 supported-capability evidence matrix.""" + +from __future__ import annotations + +import pathlib +import unittest + +ROOT = pathlib.Path(__file__).resolve().parents[1] +DOCTORING = ROOT / "docs" / "doctoring" / "mv3-compatibility.md" +MATURITY = ROOT / "docs" / "evidence" / "2026-08-10-active-pr-maturity.md" + + +class ManifestV3SupportedCapabilityMatrixContractTests(unittest.TestCase): + """Keep compatibility claims executable, maturity-scoped, and authority-safe.""" + + @classmethod + def setUpClass(cls) -> None: + cls.doctoring = DOCTORING.read_text(encoding="utf-8") + cls.maturity = MATURITY.read_text(encoding="utf-8") + + def test_matrix_separates_protected_active_planned_and_out_of_scope(self) -> None: + """The matrix must never collapse active evidence into protected-main support.""" + + for marker in ( + "## Supported-capability evidence matrix", + "**PROTECTED_MAIN**", + "**ACTIVE_PR #43**", + "**ACTIVE_PR #56**", + "**ACTIVE_PR #59**", + "**ACTIVE_PR #60**", + "**ACTIVE_PR #61**", + "**PLANNED**", + "**PLANNED / SECURITY-GATED**", + "**OUT_OF_SCOPE FOR COMPATIBILITY CLAIM**", + ): + with self.subTest(marker=marker): + self.assertIn(marker, self.doctoring) + + def test_update_migration_is_not_documented_as_restart_only(self) -> None: + """Update compatibility requires a version transition plus migrated state.""" + + for marker in ( + "Restart persistence and extension update migration are separate compatibility claims", + "`1.0.0` to `1.0.1`", + "schema marker to migrate from version 1 to version 2", + "checked-in fixture is not rewritten", + ): + with self.subTest(marker=marker): + self.assertIn(marker, self.doctoring) + + row = next( + line for line in self.maturity.splitlines() if line.startswith("| #60 |") + ) + self.assertIn("**IMPLEMENTED_ON_ACTIVE_PR**", row) + self.assertIn("e696e19c9eaf3dedb104a5de4bdbd7970abf90d4", row) + self.assertIn("CI run `31433968874`", row) + self.assertIn("Manifest V3 Compatibility run `31433968931`", row) + self.assertNotIn("IMPLEMENTED_ON_PROTECTED_MAIN", row) + + def test_isolated_world_evidence_stays_active_only(self) -> None: + """Content-script isolation proof must not be promoted into protected-main support.""" + + for marker in ( + "Content-script injection | **PROTECTED_MAIN**", + "Content-script isolated-world separation | **ACTIVE_PR #61**", + "Content-script injection and content-script JavaScript isolation are separate compatibility claims", + "page publisher changes to `extension` and real-browser compatibility fails", + ): + with self.subTest(marker=marker): + self.assertIn(marker, self.doctoring) + + row = next( + line for line in self.maturity.splitlines() if line.startswith("| #61 |") + ) + self.assertIn("**IMPLEMENTED_ON_ACTIVE_PR**", row) + self.assertIn("c1705ad9fd2d96e620b89bb6e7ea1235063dcb6a", row) + self.assertIn("CI run `31434670642`", row) + self.assertIn("Manifest V3 Compatibility run `31434670629`", row) + self.assertIn("3/3 repeatability trials", row) + self.assertNotIn("IMPLEMENTED_ON_PROTECTED_MAIN", row) + + def test_compatibility_never_grants_agent_authority(self) -> None: + """Chrome API success must remain separate from OriginWeave Agent grants.""" + + for marker in ( + "Chrome API permission does not become Agent capability", + "no Agent bookmark capability", + "no Agent history capability", + "does not claim Chrome Web Store/enterprise update semantics or Agent authority", + "no arbitrary page-JavaScript bridge or Agent authority", + ): + with self.subTest(marker=marker): + self.assertTrue(marker in self.doctoring or marker in self.maturity) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_product_documentation_contract.py b/tests/test_product_documentation_contract.py index 66a67d52f..1313189ea 100644 --- a/tests/test_product_documentation_contract.py +++ b/tests/test_product_documentation_contract.py @@ -14,9 +14,16 @@ class ProductDocumentationContractTests(unittest.TestCase): def test_authoritative_product_documentation_graph_exists(self) -> None: """Major product decisions must not require reconstructing chat or PR history.""" required_paths = { - "docs/PRD.md", "docs/TRD.md", "docs/adr/README.md", "docs/uml/README.md", - "docs/erd/README.md", "docs/traceability/README.md", "docs/THREAT_MODEL.md", - "docs/TEST_STRATEGY.md", "docs/OPERABILITY.md", "docs/API_CONTRACT.md", + "docs/PRD.md", + "docs/TRD.md", + "docs/adr/README.md", + "docs/uml/README.md", + "docs/erd/README.md", + "docs/traceability/README.md", + "docs/THREAT_MODEL.md", + "docs/TEST_STRATEGY.md", + "docs/OPERABILITY.md", + "docs/API_CONTRACT.md", "docs/RELEASE_AND_ROLLBACK.md", } missing = sorted(path for path in required_paths if not (ROOT / path).is_file()) @@ -25,110 +32,305 @@ def test_authoritative_product_documentation_graph_exists(self) -> None: def test_root_architecture_links_the_authoritative_product_graph(self) -> None: """Architecture readers must be able to reach requirements, decisions, diagrams, and data.""" architecture = (ROOT / "ARCHITECTURE.md").read_text(encoding="utf-8") - for link in ("docs/PRD.md", "docs/TRD.md", "docs/adr/README.md", "docs/uml/README.md", "docs/erd/README.md", "docs/traceability/README.md"): - with self.subTest(link=link): self.assertIn(link, architecture) + for link in ( + "docs/PRD.md", + "docs/TRD.md", + "docs/adr/README.md", + "docs/uml/README.md", + "docs/erd/README.md", + "docs/traceability/README.md", + ): + with self.subTest(link=link): + self.assertIn(link, architecture) def test_security_policy_links_the_product_threat_model(self) -> None: """Vulnerability reporters and operators must be able to find modeled trust boundaries.""" - self.assertIn("docs/THREAT_MODEL.md", (ROOT / "SECURITY.md").read_text(encoding="utf-8")) + self.assertIn( + "docs/THREAT_MODEL.md", + (ROOT / "SECURITY.md").read_text(encoding="utf-8"), + ) def test_agent_contract_is_work_conserving_instead_of_one_action_per_run(self) -> None: """Finishing one bounded slice must return maintenance to the live queue.""" contract = (ROOT / "AGENTS.md").read_text(encoding="utf-8") - for phrase in ("A completed action is an intermediate state", "one write-active slice at a time", "Mandatory exit sweep", "termination is prohibited", "blocks only that item"): - with self.subTest(phrase=phrase): self.assertIn(phrase, contract) + for phrase in ( + "A completed action is an intermediate state", + "one write-active slice at a time", + "Mandatory exit sweep", + "termination is prohibited", + "blocks only that item", + ): + with self.subTest(phrase=phrase): + self.assertIn(phrase, contract) def test_prd_covers_product_family_modes_and_buyer_acceptance(self) -> None: """The PRD must describe the actual product family rather than one kernel slice.""" prd = (ROOT / "docs/PRD.md").read_text(encoding="utf-8") - for phrase in ("Browse. Act. Prove.", "Human Mode", "Assist Mode", "Agent Task Mode", "Crawler Mode", "OriginWeave Browser", "OriginWeave Runtime", "OriginWeave Observe", "OriginWeave Capture", "OriginWeave Governor", "OriginWeave Policy", "OriginWeave Evidence", "OriginWeave Protocol", "Non-goals", "Buyer-visible acceptance"): - with self.subTest(phrase=phrase): self.assertIn(phrase, prd) + for phrase in ( + "Browse. Act. Prove.", + "Human Mode", + "Assist Mode", + "Agent Task Mode", + "Crawler Mode", + "OriginWeave Browser", + "OriginWeave Runtime", + "OriginWeave Observe", + "OriginWeave Capture", + "OriginWeave Governor", + "OriginWeave Policy", + "OriginWeave Evidence", + "OriginWeave Protocol", + "Non-goals", + "Buyer-visible acceptance", + ): + with self.subTest(phrase=phrase): + self.assertIn(phrase, prd) def test_trd_distinguishes_shipped_architecture_from_future_work(self) -> None: """Technical documentation must not silently describe planned work as shipped.""" trd = (ROOT / "docs/TRD.md").read_text(encoding="utf-8") - for phrase in ("Implemented", "Accepted architecture", "Planned", "logical origin", "resolved destination", "TCP peer", "TLS service identity", "WebDriver BiDi", "Chrome DevTools Protocol", "WebMCP", "Model Context Protocol", "NVIDIA_NIM_API_KEY", "COPILOT_GITHUB_TOKEN"): - with self.subTest(phrase=phrase): self.assertIn(phrase, trd) + for phrase in ( + "Implemented", + "Accepted architecture", + "Planned", + "logical origin", + "resolved destination", + "TCP peer", + "TLS service identity", + "WebDriver BiDi", + "Chrome DevTools Protocol", + "WebMCP", + "Model Context Protocol", + "NVIDIA_NIM_API_KEY", + "COPILOT_GITHUB_TOKEN", + ): + with self.subTest(phrase=phrase): + self.assertIn(phrase, trd) def test_target_architecture_adr_set_is_detailed(self) -> None: """Product direction must be reconstructable from durable, reviewable decisions.""" required_adrs = { - "docs/adr/0001-chromium-compatibility-kernel.md": ("Chromium", "browser-engine rewrite"), - "docs/adr/0100-rust-control-plane-boundary.md": ("Rust control plane", "Chromium compatibility kernel"), - "docs/adr/0101-isolated-execution-profile-modes.md": ("Human", "Assist", "Agent Task", "Crawler"), - "docs/adr/0102-typed-actions-and-arbitrary-js.md": ("typed action", "arbitrary JavaScript"), - "docs/adr/0103-semantic-observation-and-stale-node-identity.md": ("WebMCP", "accessibility", "document epoch", "stale"), - "docs/adr/0104-prompt-injection-and-secret-authority.md": ("prompt injection", "opaque", "secret"), - "docs/adr/0105-resource-governor-priority.md": ("resource governor", "GPU", "browser", "model"), - "docs/adr/0106-provenance-evidence-model.md": ("WARC", "PROV", "evidence"), - "docs/adr/0107-browser-protocol-adapter-strategy.md": ("WebDriver BiDi", "Chrome DevTools Protocol", "WebMCP", "Model Context Protocol"), + "docs/adr/0001-chromium-compatibility-kernel.md": ( + "Chromium", + "browser-engine rewrite", + ), + "docs/adr/0100-rust-control-plane-boundary.md": ( + "Rust control plane", + "Chromium compatibility kernel", + ), + "docs/adr/0101-isolated-execution-profile-modes.md": ( + "Human", + "Assist", + "Agent Task", + "Crawler", + ), + "docs/adr/0102-typed-actions-and-arbitrary-js.md": ( + "typed action", + "arbitrary JavaScript", + ), + "docs/adr/0103-semantic-observation-and-stale-node-identity.md": ( + "WebMCP", + "accessibility", + "document epoch", + "stale", + ), + "docs/adr/0104-prompt-injection-and-secret-authority.md": ( + "prompt injection", + "opaque", + "secret", + ), + "docs/adr/0105-resource-governor-priority.md": ( + "resource governor", + "GPU", + "browser", + "model", + ), + "docs/adr/0106-provenance-evidence-model.md": ( + "WARC", + "PROV", + "evidence", + ), + "docs/adr/0107-browser-protocol-adapter-strategy.md": ( + "WebDriver BiDi", + "Chrome DevTools Protocol", + "WebMCP", + "Model Context Protocol", + ), "docs/adr/0108-crawler-policy.md": ("robots", "rate", "CAPTCHA"), - "docs/adr/0109-hourly-automation-operational-closure.md": ("NVIDIA_NIM_API_KEY", "protected-main", "open_pull_request"), + "docs/adr/0109-hourly-automation-operational-closure.md": ( + "NVIDIA_NIM_API_KEY", + "protected-main", + "open_pull_request", + ), } - sections = ("## Context", "## Options considered", "## Decision", "## Consequences", "## Failure and degraded behavior", "## Security / privacy / governance impact", "## Tests and acceptance evidence", "## Migration and rollback", "## Supersession / reversal conditions") + sections = ( + "## Context", + "## Options considered", + "## Decision", + "## Consequences", + "## Failure and degraded behavior", + "## Security / privacy / governance impact", + "## Tests and acceptance evidence", + "## Migration and rollback", + "## Supersession / reversal conditions", + ) fields = ("- Status:", "- Date:", "- Supersedes:", "- Superseded by:") for path, phrases in required_adrs.items(): with self.subTest(path=path): text = (ROOT / path).read_text(encoding="utf-8") - for field in fields: self.assertIn(field, text) - for section in sections: self.assertIn(section, text) - for phrase in phrases: self.assertIn(phrase, text) + for field in fields: + self.assertIn(field, text) + for section in sections: + self.assertIn(section, text) + for phrase in phrases: + self.assertIn(phrase, text) def test_stale_node_adr_defines_action_linearization_race(self) -> None: """A mutation between handle validation and dispatch must never produce a stale side effect.""" - adr = (ROOT / "docs/adr/0103-semantic-observation-and-stale-node-identity.md").read_text(encoding="utf-8") - for phrase in ("action linearization point", "side effect", "competing mutation", "re-observation"): - with self.subTest(phrase=phrase): self.assertIn(phrase, adr) + adr = ( + ROOT / "docs/adr/0103-semantic-observation-and-stale-node-identity.md" + ).read_text(encoding="utf-8") + for phrase in ( + "action linearization point", + "side effect", + "competing mutation", + "re-observation", + ): + with self.subTest(phrase=phrase): + self.assertIn(phrase, adr) def test_hourly_automation_adr_requires_exit_sweep(self) -> None: """Automation closure must re-sweep all actionable lanes instead of stopping after one result.""" - adr = (ROOT / "docs/adr/0109-hourly-automation-operational-closure.md").read_text(encoding="utf-8") - for phrase in ("mandatory exit sweep", "open OriginWeave PRs and issues", "release state", "documentation", "product gaps", "safe actionable work remains"): - with self.subTest(phrase=phrase): self.assertIn(phrase, adr) + adr = ( + ROOT / "docs/adr/0109-hourly-automation-operational-closure.md" + ).read_text(encoding="utf-8") + for phrase in ( + "mandatory exit sweep", + "open OriginWeave PRs and issues", + "release state", + "documentation", + "product gaps", + "safe actionable work remains", + ): + with self.subTest(phrase=phrase): + self.assertIn(phrase, adr) def test_uml_and_erd_are_diagram_as_code(self) -> None: """Architecture flows and the conceptual domain model must be reviewable in Git.""" uml = (ROOT / "docs/uml/README.md").read_text(encoding="utf-8") + authority_view = ROOT / "docs/uml/extension-authority.md" + self.assertTrue(authority_view.is_file()) + self.assertIn("](extension-authority.md)", uml) + self.assertIn("```mermaid", authority_view.read_text(encoding="utf-8")) + erd = (ROOT / "docs/erd/README.md").read_text(encoding="utf-8") - self.assertGreaterEqual(uml.count("```mermaid"), 8); self.assertIn("sequenceDiagram", uml); self.assertIn("stateDiagram-v2", uml) - for heading in ("Secret-fill sequence", "Read/write risk approval flow", "Resource-pressure and fallback flow", "Hourly product-development gate-to-model flow"): - with self.subTest(heading=heading): self.assertIn(heading, uml) + self.assertGreaterEqual(uml.count("```mermaid"), 8) + self.assertIn("sequenceDiagram", uml) + self.assertIn("stateDiagram-v2", uml) + for heading in ( + "Secret-fill sequence", + "Read/write risk approval flow", + "Resource-pressure and fallback flow", + "Hourly product-development gate-to-model flow", + ): + with self.subTest(heading=heading): + self.assertIn(heading, uml) self.assertIn("erDiagram", erd) - for entity in ("agent_session", "browser_profile", "page_snapshot", "semantic_node", "action_event", "policy_decision", "provenance_record", "resource_budget"): - with self.subTest(entity=entity): self.assertIn(entity, erd) + for entity in ( + "agent_session", + "browser_profile", + "page_snapshot", + "semantic_node", + "action_event", + "policy_decision", + "provenance_record", + "resource_budget", + ): + with self.subTest(entity=entity): + self.assertIn(entity, erd) def test_hourly_uml_fails_closed_before_secret_or_publication(self) -> None: """Denied credentials and failed validation must terminate before secret use or publication.""" uml = (ROOT / "docs/uml/README.md").read_text(encoding="utf-8") - for phrase in ("credential denied or broker unavailable", "stop without secret materialization", "validation failed", "fail closed without publication", "validation passed"): - with self.subTest(phrase=phrase): self.assertIn(phrase, uml) + for phrase in ( + "credential denied or broker unavailable", + "stop without secret materialization", + "validation failed", + "fail closed without publication", + "validation passed", + ): + with self.subTest(phrase=phrase): + self.assertIn(phrase, uml) def test_operational_documents_preserve_fail_closed_product_boundaries(self) -> None: """Security, operations, APIs, tests, and rollback must agree on core authority boundaries.""" documents = { - "docs/THREAT_MODEL.md": ("renderer compromise", "prompt injection", "confused deputy", "cross-tenant"), - "docs/TEST_STRATEGY.md": ("true production boundary", "100%", "hostile", "protected-main"), + "docs/THREAT_MODEL.md": ( + "renderer compromise", + "prompt injection", + "confused deputy", + "cross-tenant", + ), + "docs/TEST_STRATEGY.md": ( + "true production boundary", + "100%", + "hostile", + "protected-main", + ), "docs/OPERABILITY.md": ("SLI", "SLO", "quarantine", "break-glass"), - "docs/API_CONTRACT.md": ("OriginWeave Protocol", "idempotency", "post-condition", "opaque"), - "docs/RELEASE_AND_ROLLBACK.md": ("SBOM", "provenance", "rollback", "protected main"), + "docs/API_CONTRACT.md": ( + "OriginWeave Protocol", + "idempotency", + "post-condition", + "opaque", + ), + "docs/RELEASE_AND_ROLLBACK.md": ( + "SBOM", + "provenance", + "rollback", + "protected main", + ), } for path, phrases in documents.items(): text = (ROOT / path).read_text(encoding="utf-8") for phrase in phrases: - with self.subTest(path=path, phrase=phrase): self.assertIn(phrase, text) + with self.subTest(path=path, phrase=phrase): + self.assertIn(phrase, text) def test_release_contract_never_bypasses_evidence_or_reproducibility(self) -> None: """Emergency release handling must preserve exact-head gates and reproducible artifacts.""" release = (ROOT / "docs/RELEASE_AND_ROLLBACK.md").read_text(encoding="utf-8") - for phrase in ("Emergency releases do not bypass required gates", "current-head checks", "complete coverage", "branch protection", "reproducible artifact", "nondeterministic signing"): - with self.subTest(phrase=phrase): self.assertIn(phrase, release) + for phrase in ( + "Emergency releases do not bypass required gates", + "current-head checks", + "complete coverage", + "branch protection", + "reproducible artifact", + "nondeterministic signing", + ): + with self.subTest(phrase=phrase): + self.assertIn(phrase, release) self.assertNotIn("residual unrun evidence", release) def test_traceability_labels_conversation_derived_future_work(self) -> None: - """Conversation decisions must preserve implementation status instead of becoming claims.""" + """Conversation decisions must preserve canonical maturity instead of becoming shipped claims.""" traceability = (ROOT / "docs/traceability/README.md").read_text(encoding="utf-8") - for phrase in ("Implemented", "Accepted architecture", "Proposed", "Open", "conversation-derived", "docs/doctoring.md"): - with self.subTest(phrase=phrase): self.assertIn(phrase, traceability) + for phrase in ( + "IMPLEMENTED_ON_PROTECTED_MAIN", + "IMPLEMENTED_ON_ACTIVE_PR", + "PARTIAL", + "ACCEPTED_ARCHITECTURE", + "PLANNED", + "RESEARCH_ONLY", + "SUPERSEDED", + "OUT_OF_SCOPE", + "conversation-derived", + "docs/doctoring.md", + "Active-PR behavior is never protected-main truth", + ): + with self.subTest(phrase=phrase): + self.assertIn(phrase, traceability) -if __name__ == "__main__": unittest.main() +if __name__ == "__main__": + unittest.main() From 90a504fcb93de5d0504fa564596d2ab0c047785d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 14 Aug 2026 04:22:07 +0900 Subject: [PATCH 019/111] revert: restore bounded workflow registry audit diff --- docs/DOCUMENTATION_FITNESS.md | 276 ---------------- docs/PRD.md | 40 +-- docs/README.md | 41 +-- docs/TRD.md | 88 +++-- .../0013-manifest-v3-extension-authority.md | 108 ------- .../0014-architecture-decision-governance.md | 112 ------- docs/adr/README.md | 54 +--- docs/doctoring/browser-agent-protocols.md | 79 ----- docs/doctoring/mv3-compatibility.md | 50 +-- .../evidence/2026-08-10-active-pr-maturity.md | 57 ---- .../2026-08-11-active-pr-maturity-closure.md | 61 ---- .../2026-08-11-active-pr-maturity-delta.md | 57 ---- .../2026-08-12-active-pr-maturity-delta.md | 30 -- ...-12-browser-protocol-active-pr-evidence.md | 27 -- docs/traceability/README.md | 184 +++++------ .../action-postcondition-evidence.md | 116 ------- .../extension-authority-security.md | 85 ----- .../resolution-freshness-authority.md | 99 ------ .../tls-revocation-freshness-authority.md | 53 --- docs/uml/README.md | 6 +- docs/uml/extension-authority.md | 105 ------ ...cumentation_active_pr_evidence_contract.py | 196 ------------ ..._documentation_discoverability_followup.py | 43 --- tests/test_documentation_fitness_contract.py | 265 --------------- ...tension_authority_traceability_contract.py | 49 --- tests/test_freshness_traceability_contract.py | 50 --- ...v3_supported_capability_matrix_contract.py | 97 ------ tests/test_product_documentation_contract.py | 302 +++--------------- 28 files changed, 201 insertions(+), 2529 deletions(-) delete mode 100644 docs/DOCUMENTATION_FITNESS.md delete mode 100644 docs/adr/0013-manifest-v3-extension-authority.md delete mode 100644 docs/adr/0014-architecture-decision-governance.md delete mode 100644 docs/doctoring/browser-agent-protocols.md delete mode 100644 docs/evidence/2026-08-10-active-pr-maturity.md delete mode 100644 docs/evidence/2026-08-11-active-pr-maturity-closure.md delete mode 100644 docs/evidence/2026-08-11-active-pr-maturity-delta.md delete mode 100644 docs/evidence/2026-08-12-active-pr-maturity-delta.md delete mode 100644 docs/evidence/2026-08-12-browser-protocol-active-pr-evidence.md delete mode 100644 docs/traceability/action-postcondition-evidence.md delete mode 100644 docs/traceability/extension-authority-security.md delete mode 100644 docs/traceability/resolution-freshness-authority.md delete mode 100644 docs/traceability/tls-revocation-freshness-authority.md delete mode 100644 docs/uml/extension-authority.md delete mode 100644 tests/test_documentation_active_pr_evidence_contract.py delete mode 100644 tests/test_documentation_discoverability_followup.py delete mode 100644 tests/test_documentation_fitness_contract.py delete mode 100644 tests/test_extension_authority_traceability_contract.py delete mode 100644 tests/test_freshness_traceability_contract.py delete mode 100644 tests/test_mv3_supported_capability_matrix_contract.py diff --git a/docs/DOCUMENTATION_FITNESS.md b/docs/DOCUMENTATION_FITNESS.md deleted file mode 100644 index 69f603252..000000000 --- a/docs/DOCUMENTATION_FITNESS.md +++ /dev/null @@ -1,276 +0,0 @@ -# OriginWeave Documentation Fitness Assessment - -- **Assessment date:** 2026-08-11 -- **Assessment scope:** protected `main`, every current OriginWeave implementation lane relevant to canonical product truth, and durable product decisions that must be reconstructable without chat history -- **Assessment type:** semantic fitness, not file-presence inventory -- **Current verdict:** **DESIGN-SUFFICIENT / PROTECTED-MAIN-PARTIAL** - -## 1. Verdict - -**DESIGN-SUFFICIENT** means the repository has a coherent product, technical, architecture, decision, diagram, data-model, security, testing, operability, protocol and release graph sufficient to implement and review OriginWeave without reconstructing product intent from chat history. - -**PROTECTED-MAIN-PARTIAL** means the design graph is sufficient, while protected `main` still lacks the canonical reconciliation and several active implementation slices. Active pull requests are implementation evidence only. Neither a green feature branch nor a Proposed ADR becomes shipped truth through documentation wording. - -File existence alone is never sufficient. An artifact can exist and still be stale, contradictory, overclaiming, underclaiming, or disconnected from executable evidence. - -## 2. Fitness matrix - -| Documentation family | Fitness | Current evidence / remaining boundary | -|---|---|---| -| PRD | **PRESENT-CURRENT on this branch / protected-main follow-up required** | Protected-main requirements remain distinct from active evidence. #37 is the bounded-HTTP replacement; #45→#46→#53→#55 narrows sensitive-handle authority without creating the trusted broker; #47→#50→#54 narrows resolution freshness through socket use; #40→#52→#57→#58 provides browser authority/semantic prerequisites; #43→#56→#59→#60→#61 plus #49 provide active MV3 compatibility evidence; #62/#63 prove extension-proposal isolation without widening Agent/secret approval authority; and #64/#65 plus #51→#66 add outcome, controlled-fixture and resource-measurement prerequisites without completing the real Chromium runtime. | -| TRD | **PRESENT-CURRENT on this branch / protected-main follow-up required** | One protected-main implementation state is kept separate from volatile active/non-shipped evidence. Value objects, fixtures, bounded Linux samplers and compatibility tests do not imply deployed services, Chromium process attribution, browser adapters or completed runtime paths. | -| Root Architecture | **PRESENT-CURRENT** | The Chromium compatibility kernel plus Rust authority-bearing control plane remains correct. #47/#50/#54 refine ADR 0004; #45/#46/#53/#55 refine ADR 0007; #40/#52/#57/#58 refine browser observation/action boundaries; #62/#63 exercise the existing extension/policy separation; #64/#65 and #51→#66 refine evidence/fixture/resource prerequisites; #43/#49/#56/#59/#60/#61 remain compatibility work under issue #27. None introduces a new trust domain, persistence owner or deployed component. | -| ADR index/lifecycle | **PRESENT-CURRENT on this branch** | Accepted ADRs remain distinct from Proposed decisions. ADR 0013 separates MV3 compatibility from Agent authority; ADR 0014 governs architecture-decision lifecycle. Their branch presence or later integration cannot silently promote them to Accepted. | -| Individual ADRs | **SUFFICIENT BY LIFECYCLE** | Existing Accepted and Proposed decisions cover current material trust boundaries. #62–#66 refine or test existing authority, evidence, fixture and resource boundaries and do not independently justify manufacturing a new ADR. | -| UML / control-flow diagrams | **PRESENT-CURRENT with one legitimate deferral** | Component, network authority, observation/action, delegated-task state, deployment, evidence, secret-fill, approval, resource-pressure/GPU fallback and hourly automation flows exist. `uml/extension-authority.md` closes the permission-vs-Agent-authority gap. Detailed real-Chromium adapter/input/post-condition/process-attribution UML remains deferred until issue #28 executable contracts stabilize. | -| Conceptual ERD/domain model | **PRESENT-CURRENT** | The ERD remains explicitly conceptual until a real persistence owner/schema exists. Current active #45–#66 value, policy, freshness, compatibility, fixture, evidence and resource slices add no OriginWeave-owned durable store. Manufacturing tables for in-memory state, value objects, browser fixtures or process samples would be false architecture. | -| Traceability | **PRESENT-CURRENT on this branch** | Uses explicit protected-main, active-PR, partial, accepted-architecture, planned, research-only, superseded and out-of-scope maturity vocabulary. Volatile exact-head evidence lives in the dated maturity appendix, now through #66. | -| Threat model / Security | **PRESENT-CURRENT with implementation follow-up** | Untrusted content, network, secret, provenance and extension risks are covered. #62/#63 prove extension proposal permission cannot replace Agent policy or R3 approval; #64 does not turn caller timestamps into trusted causality; #65's hostile page content remains a controlled untrusted fixture; #66 does not infer process ownership from caller-supplied PIDs. | -| Test strategy / quality gates | **PRESENT-CURRENT** | Exact owned production function/line/region/branch coverage, rustdoc and realistic boundary testing are explicit. Active work uses exact RED→GREEN evidence, pinned real Chromium where browser behavior is claimed, and fail-closed OS sampling contracts rather than source-text or self-reported claims alone. | -| Operability / incident response | **PRESENT-CURRENT** | Failure, readiness, quarantine, cleanup and recovery concepts exist. Current fixture/value/sampler lanes add no daemon/service or persistence owner, so new SLO/RPO/RTO claims would be fabricated. | -| API / protocol contracts | **PRESENT-CURRENT as target contracts** | #52 is an internal semantic-observation value API, #57 a bounded typed-query API, #58 an authority-bound action-target bridge, and #64 an outcome-evidence value boundary. None is a BiDi/CDP/WebMCP wire adapter, native browser input executor, trusted-clock source, business-risk classifier or post-condition observer. | -| Release / rollback / provenance | **PRESENT-CURRENT** | Release remains bound to one exact integrated protected head. Active stacks #40→#52→#57→#58, #47→#50→#54, #45→#46→#53→#55, #43→#56→#59→#60→#61 plus parallel #49, and #51→#66 preserve dependency order; #62/#63/#64/#65 are direct-main prerequisites. Predecessor-head success cannot satisfy a later head. | -| Data governance / privacy | **PRESENT-CURRENT architecture / PARTIAL runtime** | Purpose-bound policy/evidence foundations exist. #62/#63 prove that proposal authority cannot manufacture secret authority or approval, but authenticated workload identity, durable trusted-broker storage, protected-value resolution/fill, KMS, cross-process transactionality, compensation, retention and model-disclosure lifecycle remain open under issue #10. | -| Standards / doctoring | **PRESENT-CURRENT with continuous watch** | Primary browser/protocol/standards evidence and APA 7 references distinguish living/vendor/experimental material from final normative standards. Exact browser release evidence stays pinned to executable Chromium evidence rather than documentation alone. | - -## 3. Reconciliation findings - -### 3.1 HTTP lineage - -Protected-main PRD previously named historical PR #11 as active HTTP evidence. Current replacement work is PR #37, while protected main still does not ship the reconstructed bounded HTTP capability. - -**Resolution:** #37 is active/non-shipped implementation evidence, #11 is historical predecessor lineage, and integration before any of these branch repairs become protected-main truth remains mandatory. Old-head checks, reviews and mergeability never transfer. - -### 3.2 Sensitive-data authority and broker lifecycle - -Protected main contains purpose-bound sensitive-data policy/evidence governed by Accepted ADR 0007. The active dependency chain is #45 → #46 → #53 → #55: lifecycle evidence, authoritative in-process use reservation, first-revocation-wins state, then audience binding. - -The audience string accepted by the value/policy primitive is **not authentication**. A future trusted broker must derive audience from authenticated workload/service identity rather than caller-controlled input. One-process synchronization is not durable/cross-process atomicity. - -**Resolution:** these lanes may be `IMPLEMENTED_ON_ACTIVE_PR`; the complete broker remains Planned under issue #10. They do not justify a fictitious broker process, KMS path, database table, transaction manager, browser-fill adapter, new deployment topology or physical ERD entity. - -### 3.3 Manifest V3 compatibility - -Protected main already proves a pinned-Chromium baseline for service worker, content script, storage, DNR, tabs, windows, scripting, commands, side panel, bookmarks/history read behavior, restart persistence and repeatability. The active compatibility stack adds: - -- #43: controlled downloads; -- #49: per-trial ephemeral profile isolation; -- #56: bookmark create/read/delete cleanup; -- #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. - -#43/#49/#56/#59/#60/#61 are active compatibility evidence only. Chromium permission or browser compatibility success is not an OriginWeave Agent capability, policy grant, approval or protected-value authority. A successful fixture cannot become an OriginWeave Agent history grant, bookmark grant, download grant or arbitrary page-JavaScript bridge. - -The supported-capability matrix in `docs/doctoring/mv3-compatibility.md` separates `PROTECTED_MAIN`, `ACTIVE_PR`, `PLANNED`, security-gated and out-of-scope claims. Update migration is intentionally distinct from restart persistence, and isolated-world behavior is intentionally distinct from injection alone. - -**Resolution:** complete compatibility remains Planned under issue #27. Proposed ADR 0013 remains the authority separator. #59/#60/#61 are refinements of that decision, not new architecture decisions. - -### 3.4 Browser identifier authority - -Protected main contains session/context/document/node foundations under Accepted ADR 0010. Active #40 maps protocol-local identifiers into OriginWeave-owned authority and remains non-shipped. - -**Resolution:** protocol identifiers remain adapter-local, and detailed adapter sequence UML remains deferred until issue #28 stabilizes executable BiDi/CDP contracts. - -### 3.5 ADR discoverability and identifier allocation - -The earlier index omitted existing ADRs, and active #37 already reserves ADR identifiers 0011/0012. - -**Resolution:** the branch indexes every ADR by lifecycle, uses non-colliding 0013/0014 for new Proposed decisions, and treats collision-sensitive identifiers as reserved across protected main plus active work. - -### 3.6 Documentation contract parser - -The first fitness contract accepted only bare lifecycle metadata even though repository-valid ADRs can carry descriptive suffixes. - -**Resolution:** machine checks validate the leading supported lifecycle state and reject unknown states without rejecting valid suffixes. - -### 3.7 UML audit correction - -An early audit incorrectly called resource-pressure and hourly-automation views missing. - -**Resolution:** the existing resource-pressure/GPU fallback and hourly automation flows are recognized. Only the genuinely missing extension-permission-to-Agent-authority view was added. - -### 3.8 Resolution freshness authority - -Active #47 → #50 → #54 progressively binds approved resolution state to first-party network planning and rechecks freshness immediately before socket I/O under trusted monotonic time. - -**Resolution:** this refines Accepted ADR 0004 rather than introducing a resolver service, proxy/PAC authority, wall-clock authority, persistence owner or new deployed component. - -### 3.9 TLS revocation-material freshness - -Active #48 provides a bounded freshness primitive for already verified revocation material. - -**Resolution:** this is not OCSP/CRL acquisition, signature/path validation, cache operation or an unrevoked-certificate claim. No fictitious revocation-service topology is added. - -### 3.10 Browser task telemetry and process-set RSS - -Active #51 validates bounded RSS, observation-byte, action-latency and task-duration values and now samples one explicitly supplied Linux PID through strict `/proc//status` `VmRSS` parsing. Stacked #66 extends this to a bounded explicit process set: at most 256 unique nonzero caller-owned PIDs, checked aggregate addition, and fail-closed sampling when any member cannot be measured. - -**Resolution:** OS sampling is now real for caller-supplied Linux PIDs, but Chromium process discovery, same-task attribution, browser child-process/cgroup walking, GPU/VRAM, JS heap and cross-platform sampling remain unimplemented. A changing RSS value is runtime state, so correctness tests validate the sampling contract rather than assuming two sequential reads are byte-identical. - -### 3.11 Semantic observation authority - -Active #52 carries an OriginWeave-owned node handle, bounded semantic fields, typed advertised actions, provenance channels and bounded relationships. Every relationship must remain inside the same browser session, browsing context, canonical origin and document epoch. Self-parent/self-child relationships and duplicate child handles fail closed. The relationship graph remains descriptive evidence. - -**Resolution:** #52 is not a browser observation adapter. Accessibility, DOM, layout, WebMCP, structured-data and visual inputs remain untrusted observations and cannot mint capability. - -### 3.12 Typed semantic query authority - -Active #57 performs bounded exact role, accessible-name and required-typed-action matching only against already validated semantic observations. - -**Resolution:** semantic query success is descriptive selection, not CSS/XPath/raw-DOM authority, arbitrary JavaScript, browser I/O, action dispatch or policy approval. - -### 3.13 Authority-bound semantic node action target - -Active #58 accepts only an advertised `NodeActionKind`, carries the exact OriginWeave-owned node handle and revalidates session/context/origin/document epoch immediately before later use. - -**Resolution:** this remains descriptive execution input. A node advertising `Click` cannot determine business-risk classification: the same click could represent navigation, submit, purchase, delete, permission management or legal consent. Policy intent, approval, browser dispatch and verified success remain separate boundaries under issue #28. - -### 3.14 Controlled history mutation compatibility - -Active #59 creates one synthetic loopback history entry, requires exact readback, removes it in `finally` and proves its absence afterwards. - -**Resolution:** browser history compatibility is not an OriginWeave Agent history grant. No history values are exposed to a model and no human/default profile is used. - -### 3.15 Controlled extension update migration - -Active #60 copies the checked-in fixture into a trial-local directory, keeps one ephemeral profile and one extension path, transitions only `1.0.0` → `1.0.1`, observes the loaded version and requires schema state 1 → 2 migration. - -**Resolution:** this proves one deterministic unpacked-extension version transition. It does not establish Chrome Web Store updates, enterprise rollout, arbitrary downgrade or third-party migration safety. - -### 3.16 Content-script isolated-world compatibility - -Active #61 gives the page main world and the MV3 content script the same JavaScript global name with different values and requires the page to keep publishing `page` while the content script observes its own `extension` value. If the worlds collapse, the existing compatibility gate fails in real pinned Chromium. - -**Resolution:** this is a bounded compatibility proof, not a trusted page-content channel, arbitrary JavaScript bridge or Agent capability. - -### 3.17 Extension proposal authority and secret approval composition - -Active #62/#63 exercise two sides of one architectural separator. #62 first proves the exact extension/session/context `ProposeTypedAction` grant is present and then requires ordinary Agent policy to reject origin/capability/instruction/secret widening. #63 gives the Agent context its independent `FillSecret` capability and broker-handle delivery request, but still requires the ordinary high-risk result `RequireApproval(RiskClass::R3)`. - -**Resolution:** extension proposal permission can neither mint Agent capability/origin/secret authority nor manufacture approval. These are regression proofs over existing boundaries, not a secret broker, browser adapter, approval service or new trust domain. Proposed ADR 0013 already captures the relevant permission-vs-Agent-authority decision. - -### 3.18 Verified action-outcome ordering - -Active #64 makes a successful action-outcome value require existing verified provenance plus one caller-supplied monotonic dispatch timestamp and an observation timestamp that is not earlier. An earlier observation fails closed as `PostConditionPredatesDispatch`; equality is allowed for coarse monotonic clocks. - -**Resolution:** temporal ordering prevents packaging a pre-dispatch observation as later success evidence, but it does not prove trusted clock provenance, actual browser dispatch, target linkage, causal effect or that a real browser reached the declared state. #64 is not a browser dispatcher or post-condition observer. - -### 3.19 Controlled Agent Task fixture - -Active #65 supplies a deterministic synthetic local web fixture with a labelled semantic input, submit control, same-document post-condition and explicitly hidden/untrusted prompt-injection text. The fixture contains no credential collection surface and requires no live third-party site. - -**Resolution:** the fixture makes the future real Chromium vertical slice reproducible without turning a third-party site into a test dependency. It is not a browser adapter, semantic extractor, input dispatcher, policy engine, trusted clock, process-attribution source or proof of real Chromium execution. - -### 3.20 Bounded browser process-set resource evidence - -Active #51→#66 establishes two distinct layers: #51 owns single explicitly supplied Linux PID sampling and the bounded telemetry value boundary; #66 owns bounded duplicate-safe aggregation/sampling over an exact caller-owned PID set. #66's exact current contract rejects empty, zero-PID, duplicate, oversized and overflow states and fails closed if any member cannot be sampled. - -**Resolution:** aggregate resource measurement must not silently undercount a known caller-owned process set, but process membership remains an external attribution responsibility. The implementation does not discover Chromium PIDs, prove process ancestry/task ownership, walk cgroups, sample GPU/VRAM or create a durable telemetry store. - -## 4. Durable product decisions captured by the canonical graph - -1. OriginWeave is **Browse. Act. Prove.**: an enterprise agentic web runtime and provenance-native browser platform, not Selenium-style automation. -2. Chromium remains the compatibility kernel; Blink/V8 are not rewritten for differentiation. -3. Rust owns new authority-bearing control-plane semantics and remains independently reusable. -4. Human, Assist, Agent Task and Crawler modes have distinct authority/profile semantics; Agent Task does not ambiently inherit Human authority. -5. Page, extension, WebMCP and model content are untrusted observations, not goal/policy authority. -6. Structured observation precedes raw HTML or screenshot-only interpretation. -7. Typed actions and observed post-conditions replace arbitrary-script and command-return-as-success semantics. -8. Logical origin, destination, route/proxy, TCP peer, TLS identity and HTTP semantics are separate authorities. -9. Session/context/document epoch/node identity is separate from raw BiDi/CDP identifiers. -10. Manifest V3 permission is not an OriginWeave Agent capability; compatibility evidence and Agent-authority evidence are independent. -11. Raw secrets stay outside model-visible context; sensitive values use purpose-bound authority, opaque handles and trusted fill paths. -12. Browser correctness/human interaction outrank optional local-model throughput under pressure. -13. Provenance distinguishes source observation, model judgement, policy, approval, action and verified outcome. -14. WebDriver BiDi, CDP, WebMCP and MCP are versioned adapters, never the product authority model by themselves. -15. The first browser proof uses pinned stock Chromium before any broad fork. -16. High-risk actions remain approval-bound; Crawler Mode remains read-only and excludes CAPTCHA/block-evasion features. -17. Autonomous development uses OpenCode/NVIDIA NIM under deterministic gates and separate review/publication authority, never `COPILOT_GITHUB_TOKEN` as the development-model credential. -18. Documentation, checks, reviews, model judgements and operational evidence are separate evidence authorities. -19. Work-conserving maintenance continues to another safe lane rather than stopping on one merge, document, RCA, queued check or approval gap. -20. Collision-sensitive repository identifiers are reserved across protected main and active work before allocation. -21. In-memory sensitive-handle primitives may narrow replay/revocation risk without claiming the durable trusted broker exists. -22. A validated DNS answer is not sufficient socket authority indefinitely; resolution-to-socket use requires bounded trusted-monotonic freshness. -23. Revocation-material freshness, cryptographic validity, acquisition/cache operation and an unrevoked claim remain separate evidence authorities. -24. Browser telemetry values, OS process sampling and Chromium/task process attribution are separate maturity claims. -25. Semantic observation provenance and advertised node-local actions are descriptive evidence and never execution authority. -26. Semantic relationships remain bounded within exact session/context/origin/document authority. -27. Sensitive-handle audience must ultimately derive from authenticated workload/service identity. -28. Real browser compatibility fixtures may mutate and clean controlled synthetic state without creating Agent authority. -29. Semantic query success is neither selector authority nor permission to execute an advertised action. -30. Semantic action-target binding preserves exact node authority but remains separate from business-risk classification, policy approval, dispatch and observed success. -31. Update migration, restart persistence, injection and isolated-world behavior are separate compatibility claims and must retain distinct executable evidence. -32. Extension proposal authority never substitutes for Agent capability, origin/secret authority or independent high-risk approval. -33. Verified action-success evidence must not predate dispatch, while trusted clock provenance, browser dispatch, target linkage and causality remain separate authorities. -34. A controlled hostile page fixture is reproducible test infrastructure, not evidence that a real Chromium adapter exists. -35. Resource aggregation over known PIDs does not establish Chromium process discovery or task attribution. - -## 5. Architecture views legitimately deferred - -### 5.1 Extension authority — present - -`uml/extension-authority.md` captures: - -```text -Chromium MV3 permission --> extension runtime --> untrusted extension observation/message --> OriginWeave extension policy/grant --> Agent capability decision --> typed action proposal --> deterministic policy -``` - -Compatibility evidence cannot substitute for Agent-authority evidence, or vice versa. #62/#63 executable composition tests strengthen this existing view without changing its architecture. - -### 5.2 Network freshness sequence — reconcile after #47 → #50 → #54 integrates - -```text -resolver answer --> destination policy + origin binding --> fresh resolution approval --> connection authorization at trusted monotonic use time --> socket-use freshness recheck --> exact socket candidate --> observed TCP peer --> TLS/HTTP authority layers -``` - -### 5.3 Real Chromium vertical slice — deferred until issue #28 stabilizes - -```text -isolated profile/context --> BiDi/CDP adapter --> OriginWeave registry --> semantic observation --> typed semantic query --> authority-bound semantic action target --> explicit business intent / deterministic policy --> real browser input --> observed post-condition --> credential-safe evidence --> teardown/recovery -``` - -#40/#52/#57/#58 make identifier/semantic/action-target authority concrete; #64 makes ordered verified outcome packaging concrete; #65 supplies a controlled hostile target application; and #51→#66 narrows resource measurement. None yet establishes the real Chromium transport/semantic extraction/native input/post-condition observer/trusted clock/process attribution chain. Freezing temporary protocol fields into authoritative UML before those executable contracts exist would create false architecture. - -### 5.4 Trusted sensitive-data broker — deferred until issue #10 establishes a real runtime boundary - -Protected-main policy/evidence plus #45→#46→#53→#55 and composition regressions #62/#63 do not justify inventing a broker process, durable database, KMS topology, authenticated service-identity mechanism or browser-fill adapter. Add physical ERD/component/transaction views only when executable ownership exists. - -## 6. Completion criteria - -The graph becomes **PROTECTED-MAIN-SUFFICIENT** only when: - -1. PRD/TRD implementation inventories agree with the exact protected-main crates/APIs/browser evidence; -2. no historical/superseded lineage is presented as current implementation evidence; -3. ADR indexes discover every protected-main ADR and match lifecycle metadata; -4. UML covers every implemented material authority flow, with planned diagrams clearly marked; -5. ERD/domain models distinguish conceptual, in-memory, persisted, adapter-owned and external state truthfully; -6. traceability maps each material requirement/Accepted decision to protected-main evidence, explicitly active-PR evidence or an open issue; -7. documentation tests catch stale status/index/link/ownership/identifier/maturity terminology; -8. security, test, operability, privacy and release docs agree on shipped-vs-planned boundaries; and -9. this documentation reconciliation itself reaches protected main through repository governance and is re-evaluated against whatever feature heads actually integrated. - -Until then, OriginWeave is **design-documented but not protected-main documentation-closed**. That finding must never be used as an excuse to stop unrelated safe implementation work. diff --git a/docs/PRD.md b/docs/PRD.md index 40539a28f..12e5b7f0b 100644 --- a/docs/PRD.md +++ b/docs/PRD.md @@ -44,7 +44,7 @@ Every requirement uses **exactly one** status from this table. Implementation ev | **Proposed** | Product direction still requiring a dedicated reviewed decision or sufficient implementation evidence. | | **Open** | A decision or acceptance criterion is intentionally unresolved. | -Only `Implemented` may describe shipped behavior. An Accepted ADR is design authority, not implementation proof. Active PR implementation evidence may be named in the evidence column, but it does not change a requirement to `Implemented` until the applicable behavior reaches protected `main`. +Only `Implemented` may describe shipped behavior. An Accepted ADR is design authority, not implementation proof. ## 4. Problem statement @@ -84,10 +84,10 @@ The status applies to the **whole named product surface**, not to every implemen |---|---|---|---| | **OriginWeave Browser** | Chromium-compatible interactive distribution with governed agent entry points | Planned | No protected-main branded browser distribution yet | | **OriginWeave Runtime** | Headless/embedded governed web-task runtime | Planned | Rust authority kernels exist; browser integration remains incomplete | -| **OriginWeave Observe** | Structured observation from tools, structured data, network, accessibility, DOM/layout and visual fallback | Planned | Session/context/node-authority foundations are on protected main; active PR #52 adds a bounded authority-bound semantic-observation value primitive with explicit evidence-channel provenance, but it is not a browser observation adapter and remains non-shipped | -| **OriginWeave Capture** | Schema-bound extraction, crawler controls, downloads and WARC/PROV-oriented capture | Planned | Evidence foundations and partial real-Chromium extension compatibility evidence exist; complete capture runtime is not shipped | +| **OriginWeave Observe** | Structured observation from tools, structured data, network, accessibility, DOM/layout and visual fallback | Planned | Session/context/node-authority foundations are on protected main; semantic browser observation adapter is incomplete | +| **OriginWeave Capture** | Schema-bound extraction, crawler controls, downloads and WARC/PROV-oriented capture | Planned | Evidence foundations exist; complete capture runtime not shipped | | **OriginWeave Governor** | CPU, RAM, GPU, VRAM, admission and model/browser priority governance | Accepted architecture | Deterministic resource-budget and CPU-worker admission foundations are implemented; platform telemetry/scheduling adapters remain incomplete | -| **OriginWeave Policy** | Capability, origin, purpose, risk, crawler, approval and sensitive-data authority | Accepted architecture | Capability/origin/purpose/risk/crawler/approval and purpose-bound sensitive-data policy foundations are implemented on protected main; trusted sensitive-data broker/storage/lifecycle remain planned under issue #10 | +| **OriginWeave Policy** | Capability, origin, purpose, risk, crawler, approval and sensitive-data authority | Accepted architecture | Capability/origin/purpose/risk/crawler/approval foundations are implemented; purpose-bound sensitive-data policy is active work in PR #33 and the trusted broker remains planned | | **OriginWeave Evidence** | Credential-free evidence, provenance and task-trail contracts | Accepted architecture | Credential-free network evidence and purpose-bound sensitive-access receipts are implemented; complete Evidence Trail, WARC/PROV adapters and durable enterprise storage remain planned | | **OriginWeave Protocol** | Stable browser-agent protocol independent of one upstream automation standard | Planned | Contract documented; implementation pending | | **OriginWeave SDK** | Typed client libraries and adapters | Planned | Not a shipped product surface | @@ -163,7 +163,7 @@ planner identifies field + purpose + destination -> disclosure receipt records metadata without protected value ``` -The full journey is target architecture until the trusted broker, browser-fill path, and post-condition/evidence path are all protected-main integrated. The purpose-bound sensitive-data policy foundation and access-evidence primitives are already on protected main; those implemented subcomponents do not make the complete broker journey shipped. +The full journey is target architecture until the sensitive policy, trusted broker, browser-fill path, and post-condition/evidence path are all protected-main integrated. Implemented subcomponents do not make this whole sequence shipped. ### 8.4 Enterprise crawler @@ -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 | Issue #27 / release-specific evidence required | | 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 | @@ -191,11 +191,11 @@ public-crawl purpose | ID | Requirement | Status | Implementation evidence / note | |---|---|---|---| -| PRD-OBS-001 | Autonomous observations can carry explicit browser-session, browsing-context, canonical-origin and document-epoch authority | Implemented | `ObservedNodeHandle`, `BrowserSessionId`, `BrowsingContextId` and `DocumentEpoch` are on protected main under Accepted ADR 0010; real browser adapter remains planned | -| PRD-OBS-002 | Actionable semantic-node handles are invalidated by relevant document-epoch changes at the action linearization boundary | Accepted architecture | Core exact-authority validation exists; adapter lifecycle/mutation invalidation and atomic dispatch evidence remain planned; active PR #40 owns the bounded protocol-ID registry and remains non-shipped evidence | -| PRD-OBS-003 | Observation prefers typed/structured evidence before accessibility/DOM/layout and bounded visual fallback | Accepted architecture | ADR 0103; active PR #52 adds a bounded `SemanticNodeObservation` value contract bound to `ObservedNodeHandle`, typed node-local action descriptors and explicit non-empty evidence-channel provenance. It is not a browser observation adapter and remains active/non-shipped evidence | +| PRD-OBS-001 | Autonomous observations can carry explicit browser-session, browsing-context, canonical-origin and document-epoch authority | Implemented | `ObservedNodeHandle`, `BrowserSessionId`, `BrowsingContextId` and `DocumentEpoch` on protected main via #17; real browser adapter remains planned | +| PRD-OBS-002 | Actionable semantic-node handles are invalidated by relevant document-epoch changes at the action linearization boundary | Accepted architecture | Core exact-authority validation exists; adapter lifecycle/mutation invalidation and atomic dispatch evidence remain planned | +| PRD-OBS-003 | Observation prefers typed/structured evidence before accessibility/DOM/layout and bounded visual fallback | Accepted architecture | ADR 0103 | | PRD-OBS-004 | Observation can use bounded incremental updates rather than full repeated snapshots | Planned | Adapter-specific design needed | -| PRD-OBS-005 | Source channel and trust/provenance remain explicit | Accepted architecture | Evidence model foundations exist; active PR #52 fails closed when a semantic observation has no contributing evidence channel, while channel identity itself grants no execution authority | +| PRD-OBS-005 | Source channel and trust/provenance remain explicit | Accepted architecture | Evidence model foundations exist | ### 9.3 Typed action execution @@ -215,8 +215,8 @@ public-crawl purpose | PRD-NET-002 | Resolution snapshots are bounded, origin-bound and fail closed on unapproved expansion | Implemented | `originweave-destination` | | PRD-NET-003 | Direct transport connects only to approved canonical sockets and verifies `peer_addr` | Implemented | `originweave-network` | | PRD-NET-004 | TLS authenticates service identity over the exact governed transport with explicit roots/time | Implemented | `originweave-tls` | -| PRD-NET-005 | Proxy/PAC route authority is explicit and never ambient | Implemented | Protected-main route-authority foundation; PAC evaluation, proxy transport and CONNECT remain planned | -| PRD-NET-006 | Bounded HTTP semantics operate over authenticated governed transport | Planned | Current implementation evidence is active replacement PR #37; it is not protected-main truth. Historical PR #11 is predecessor lineage and must not be used as current implementation evidence | +| PRD-NET-005 | Proxy/PAC route authority is explicit and never ambient | Implemented | Protected-main route-authority foundation from #20; PAC evaluation, proxy transport and CONNECT remain planned | +| PRD-NET-006 | Bounded HTTP semantics operate over authenticated governed transport | Planned | Active PR #11 is not shipped evidence | | PRD-NET-007 | Real Chromium navigation proves end-to-end consumption of every shipped authority layer | Planned | Issue #28 / release acceptance requirement | ### 9.5 Secret and sensitive-data authority @@ -224,8 +224,8 @@ public-crawl purpose | ID | Requirement | Status | Implementation evidence / note | |---|---|---|---| | PRD-DATA-001 | Raw secret values never enter model-visible context | Accepted architecture | ADR 0104; trusted browser/broker runtime path not fully shipped | -| PRD-DATA-002 | Sensitive disclosure binds tenant/task/field/purpose/destination/classification | Implemented | Protected-main purpose-bound sensitive-data policy kernel governed by Accepted ADR 0007; this status does not claim broker/storage/value resolution | -| PRD-DATA-003 | Trusted broker owns expiry, revocation, atomic use reservation and resolution | Planned | Broker/storage/lifecycle implementation pending under issue #10 | +| PRD-DATA-002 | Sensitive disclosure binds tenant/task/field/purpose/destination/classification | Planned | Active replacement PR #33; no active-PR evidence counts as protected-main implementation | +| PRD-DATA-003 | Trusted broker owns expiry, revocation, atomic use reservation and resolution | Planned | Broker implementation pending under issue #10 | | PRD-DATA-004 | Privacy controls use purpose-bound authorization, encryption, retention and audit rather than blanket masking | Accepted architecture | `DATA_GOVERNANCE.md` | | PRD-DATA-005 | Model disclosure additionally binds provider/model/region/retention policy | Planned | Requires orchestrator/provider integration | @@ -238,7 +238,7 @@ public-crawl purpose | PRD-EVD-003 | Evidence Trail links source, model judgement, policy, approval, action and verified outcome as distinct authorities | Planned | Conceptual ERD/provenance ADR; complete trail is not shipped | | PRD-EVD-004 | **Origin Map** provides buyer-visible provenance exploration | Proposed | UX/product-design work still required | | PRD-EVD-005 | WARC and PROV are separate interoperability/export adapters | Accepted architecture | ADR 0106 | -| PRD-EVD-006 | Sensitive-access evidence records authority without protected value | Implemented | Protected-main purpose-bound sensitive-access receipts | +| PRD-EVD-006 | Sensitive-access evidence records authority without protected value | Implemented | Protected-main purpose-bound sensitive-access receipts via #31 | ### 9.7 Resource governance @@ -246,7 +246,7 @@ public-crawl purpose |---|---|---|---| | PRD-RES-001 | Deterministic resource budgets produce cumulative mitigations | Implemented | `originweave-resource` foundations | | PRD-RES-002 | Browser/human correctness outranks optional model throughput | Accepted architecture | ADR 0105 | -| PRD-RES-003 | CPU worker saturation participates in deterministic new-work admission | Implemented | Protected-main `ResourceSnapshot`/`ResourceGovernor` CPU-worker admission; platform worker telemetry/actuation remains adapter work | +| PRD-RES-003 | CPU worker saturation participates in deterministic new-work admission | Implemented | Protected-main `ResourceSnapshot`/`ResourceGovernor` CPU-worker admission via #30; platform worker telemetry/actuation remains adapter work | | PRD-RES-004 | Platform adapters report bounded CPU/RAM/GPU/VRAM/network/storage telemetry | Planned | Platform integration required | | PRD-RES-005 | Constrained GPU systems shrink/offload/pause model work before sacrificing governed browser correctness | Accepted architecture | ADR 0105 | @@ -264,10 +264,10 @@ public-crawl purpose | ID | Requirement | Status | Implementation evidence / note | |---|---|---|---| -| 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-001 | Manifest V3 remains the extension compatibility baseline | Accepted architecture | Official Chrome platform baseline | +| PRD-EXT-002 | Upstream extension APIs are preserved where possible | Accepted architecture | Chromium-kernel strategy | +| PRD-EXT-003 | Extension access to agent authority requires separate signed policy grant | Planned | Issue #27 / enterprise-runtime integration | +| PRD-EXT-004 | Compatibility tests cover install/update, worker lifecycle, scripts, storage, DNR, messaging, download, side panel and isolation | Planned | Issue #27 / release-specific suite | ### 9.10 Crawler and capture policy diff --git a/docs/README.md b/docs/README.md index 03b573c54..fa62037e5 100644 --- a/docs/README.md +++ b/docs/README.md @@ -7,15 +7,9 @@ - [Architecture](../ARCHITECTURE.md) - [Architecture Decision Record index](adr/README.md) - [UML and control-flow diagrams](uml/README.md) - - [Extension compatibility and Agent authority UML](uml/extension-authority.md) - [Conceptual ERD and durable domain model](erd/README.md) - [Data governance and privacy boundary](DATA_GOVERNANCE.md) - [Product and decision traceability](traceability/README.md) -- [Documentation fitness assessment](DOCUMENTATION_FITNESS.md) -- [Dated active-PR maturity evidence (2026-08-10)](evidence/2026-08-10-active-pr-maturity.md) -- [Active-PR maturity delta (2026-08-11)](evidence/2026-08-11-active-pr-maturity-delta.md) -- [Active-PR maturity closure (2026-08-11)](evidence/2026-08-11-active-pr-maturity-closure.md) -- [Browser protocol active-PR evidence (2026-08-12)](evidence/2026-08-12-browser-protocol-active-pr-evidence.md) - [Threat model](THREAT_MODEL.md) - [Product-wide test strategy](TEST_STRATEGY.md) - [Operability and incident-response baseline](OPERABILITY.md) @@ -23,12 +17,11 @@ - [Release and rollback contract](RELEASE_AND_ROLLBACK.md) - [Product roadmap](product-roadmap.md) - [Research and standards](doctoring.md) - - [Browser and Agent protocol standards evidence](doctoring/browser-agent-protocols.md) - [Current product-baseline standards addendum](doctoring/product-documentation-baseline.md) - [Quality gates](quality-gates.md) - [Security policy](../SECURITY.md) -The PRD/TRD/Architecture/ADR/UML/ERD/data-governance/traceability/security/operations/API/release set is the product-wide documentation graph. The documentation-fitness assessment records where that graph is current, stale, partial, or intentionally proposed. Volatile exact heads, workflow results, stack state, and active-PR maturity belong in dated evidence appendices rather than timeless architecture claims. Feature-specific design specifications and plans below provide detailed implementation history but do not substitute for the product-wide baseline. Planned or conversation-derived capabilities must remain labelled Planned, Proposed, or Open until reviewed implementation evidence reaches protected `main`. +The PRD/TRD/Architecture/ADR/UML/ERD/data-governance/traceability/security/operations/API/release set is the product-wide documentation graph. Feature-specific design specifications and plans below provide detailed implementation history but do not substitute for the product-wide baseline. Planned or conversation-derived capabilities must remain labelled Planned, Proposed, or Open until reviewed implementation evidence reaches protected `main`. ## Governance and maintenance @@ -49,7 +42,7 @@ The PRD/TRD/Architecture/ADR/UML/ERD/data-governance/traceability/security/opera - [TLS service-identity design](superpowers/specs/2026-08-06-tls-server-identity-design.md) - [TLS service-identity implementation plan](superpowers/plans/2026-08-06-tls-server-identity.md) -## Accepted protected-main architecture decisions +## Protected-main architecture decisions - [ADR 0001: Chromium compatibility kernel](adr/0001-chromium-compatibility-kernel.md) - [ADR 0002: Agent safety kernel](adr/0002-agent-safety-kernel.md) @@ -57,33 +50,5 @@ The PRD/TRD/Architecture/ADR/UML/ERD/data-governance/traceability/security/opera - [ADR 0004: Logical origin and resolved destination safety](adr/0004-resolved-destination-policy.md) - [ADR 0005: Exact direct TCP peer binding](adr/0005-direct-socket-binding.md) - [ADR 0006: TLS service identity over the verified peer](adr/0006-tls-server-identity.md) -- [ADR 0007: Purpose-bound sensitive-data authority](adr/0007-purpose-bound-sensitive-data-authority.md) -- [ADR 0008: Delegated-task TLS leaf-validity horizon](adr/0008-leaf-validity-horizon.md) -- [ADR 0010: Session/context-bound node authority](adr/0010-session-context-bound-node-authority.md) -## Proposed architecture decisions - -Proposed ADRs are reviewable architecture memory, not shipped behavior and not automatically Accepted because their files are present in a branch or later reach protected `main`. The provenance headings below distinguish the protected-main baseline from decisions introduced by this documentation reconciliation without changing either decision's lifecycle status. - -### Protected-main baseline proposed decisions - -- [ADR 0009: Hourly agent credential boundary](adr/0009-hourly-agent-credential-boundary.md) -- [ADR 0100: Rust control-plane boundary](adr/0100-rust-control-plane-boundary.md) -- [ADR 0101: Isolated execution/profile modes](adr/0101-isolated-execution-profile-modes.md) -- [ADR 0102: Typed actions over arbitrary JavaScript](adr/0102-typed-actions-and-arbitrary-js.md) -- [ADR 0103: Semantic observation and stale-node identity](adr/0103-semantic-observation-and-stale-node-identity.md) -- [ADR 0104: Prompt-injection and secret authority separation](adr/0104-prompt-injection-and-secret-authority.md) -- [ADR 0105: Resource governor priority](adr/0105-resource-governor-priority.md) -- [ADR 0106: Provenance evidence model](adr/0106-provenance-evidence-model.md) -- [ADR 0107: Browser protocol adapter strategy](adr/0107-browser-protocol-adapter-strategy.md) -- [ADR 0108: Crawler policy](adr/0108-crawler-policy.md) -- [ADR 0109: Hourly automation secret ordering and operational closure](adr/0109-hourly-automation-operational-closure.md) - -### Proposed decisions introduced by this documentation reconciliation - -- [ADR 0013: Manifest V3 compatibility and extension-to-Agent authority](adr/0013-manifest-v3-extension-authority.md) -- [ADR 0014: Architecture decision acceptance governance](adr/0014-architecture-decision-governance.md) - -The second group exists only on this documentation branch until the branch integrates. After integration, the heading remains useful historical provenance; it does not promote either ADR from Proposed to Accepted and it does not claim that the described runtime capability is implemented. - -See the [ADR index](adr/README.md) for status rules, required decision structure, supersession rules, and active feature ADRs. The index and each ADR's own status metadata must agree; a PR body, chat transcript, automation prompt, or stale issue reference cannot change ADR status. +See the [ADR index](adr/README.md) for status rules, required decision structure, and the rule that active-PR ADRs do not become Accepted merely because they exist on an unmerged branch. diff --git a/docs/TRD.md b/docs/TRD.md index 3e8030012..7330fec98 100644 --- a/docs/TRD.md +++ b/docs/TRD.md @@ -17,28 +17,25 @@ This TRD defines technical invariants for OriginWeave without describing planned - **Proposed** — a candidate design that still needs a dedicated reviewed decision or implementation proof. - **Open** — deliberately unresolved. -Pull-request code is not treated as Implemented until it reaches protected `main` and required acceptance evidence is re-established there. Active-PR implementation may be recorded in a separate evidence note, but it never creates a composite implementation status. +Pull-request code is not treated as Implemented until it reaches protected `main` and required acceptance evidence is re-established there. ## 2. Current protected-main implementation inventory -The current reusable Rust control plane is intentionally smaller than the final browser product. The status column describes protected `main` only; active PR evidence is kept in the final column. - -| Module / boundary | Current responsibility | Protected-main status | Active/non-shipped evidence | -|---|---|---|---| -| `originweave-core` | Canonical origin, typed actions, purpose/mode, capabilities, risk, secret-delivery, approval, session/context/document/node authority values. | **Implemented** | PR #40 builds a protocol-ID registry on top of these values; it is not protected-main truth | -| `originweave-policy` | Pure fail-closed action policy including purpose-bound sensitive-data authority. | **Implemented** | Trusted broker/runtime lifecycle remains separate planned work under issue #10 | -| `originweave-destination` | Resolved-address classification, origin-bound snapshots, route authority, connection pinning, rebinding and redirect authority. | **Implemented** | PAC evaluation/proxy transport/CONNECT are still Planned | -| `originweave-network` | Direct single-address TCP connection plan and exact operating-system peer verification. | **Implemented** | — | -| `originweave-tls` | WebPKI service identity over the already verified TCP stream. | **Implemented** | — | -| `originweave-resource` | Deterministic resource budgets, CPU-worker admission and cumulative mitigation plans. | **Implemented** | Platform telemetry/actuation remains Planned | -| `originweave-evidence` | Value-redacted network evidence, provenance foundations and sensitive-access evidence primitives. | **Implemented** | Complete durable Evidence Trail/WARC/PROV persistence remains Planned | -| Browser/session protocol registry | Bind raw BiDi/CDP identifiers to OriginWeave session/context/document authority. | **Planned** | Active PR #40; core lifetime value contracts are already Implemented under ADR 0010 | -| Semantic observation/action browser adapters | Chromium/BiDi/CDP observation, node lifecycle, typed input and post-condition verification. | **Planned** | Issue #28 | -| Bounded HTTP execution | HTTP/1.1 semantics over authenticated governed transport. | **Planned** | Active replacement PR #37; historical PR #11 is predecessor lineage, not current evidence | -| Proxy/PAC execution | Evaluate authorized route selection and perform governed proxy/CONNECT transport. | **Planned** | Protected-main route-authority value foundation already exists | -| Sensitive-data broker persistence/runtime | Atomic opaque-handle lifecycle, revocation/reservation, value resolution and trusted fill. | **Planned** | Protected-main policy/evidence foundations exist; issue #10 owns complete runtime lifecycle | -| Manifest V3 compatibility program | Real pinned-Chromium extension compatibility and release matrix. | **Planned** | Protected main already contains partial real-browser evidence; active PR #43 adds downloads evidence | -| WARC/PROV persistence | Durable capture and provenance serialization. | **Planned** | — | +The current reusable Rust control plane is intentionally smaller than the final browser product. + +| Module | Current responsibility | Status | +|---|---|---| +| `originweave-core` | Canonical origin, typed actions, purpose/mode, capabilities, risk, secret-delivery and approval contracts. | **Implemented** | +| `originweave-policy` | Pure fail-closed action-policy evaluation. | **Implemented** | +| `originweave-destination` | Resolved-address classification, origin-bound snapshots, connection pinning, rebinding and redirect authority. | **Implemented** | +| `originweave-network` | Direct single-address TCP connection plan and exact operating-system peer verification. | **Implemented** | +| `originweave-tls` | WebPKI service identity over the already verified TCP stream. | **Implemented** | +| `originweave-resource` | Deterministic resource budgets and cumulative mitigation plans. | **Implemented** | +| `originweave-evidence` | Value-redacted network evidence and provenance foundations. | **Implemented** | +| Browser/session/observation/action adapters | Chromium/BiDi/CDP integration and node-lifetime enforcement. | **Planned / active development** | +| HTTP/proxy/PAC execution | Bounded HTTP and explicit route execution beyond pure foundations. | **Planned / active development** | +| Secret broker persistence/runtime | Atomic opaque-handle lifecycle and trusted fill. | **Planned / active development** | +| WARC/PROV persistence | Durable capture and provenance serialization. | **Planned** | ## 3. Architectural invariants @@ -72,7 +69,7 @@ A **logical origin** is not a **resolved destination** decision. A resolved addr ### TRD-INV-003 — Untrusted page content -Browser content, rendered text, hidden text, comments, ads, WebMCP output, network bodies, downloads, extension messages, and model-produced summaries are data. They cannot mutate system policy, expand capabilities, authorize destinations, reveal secrets, or redefine the user's goal. +Browser content, rendered text, hidden text, comments, ads, WebMCP output, network bodies, downloads, and model-produced summaries are data. They cannot mutate system policy, expand capabilities, authorize destinations, reveal secrets, or redefine the user's goal. ### TRD-INV-004 — Secret separation @@ -90,23 +87,23 @@ A typed action may be attempted only after exact current authority is validated. ### Assist Mode -**Accepted architecture.** Reversible/read behavior may be automated. Irreversible or externally visible state changes re-enter the risk/approval pipeline. The browser adapter path remains Planned. +**Accepted architecture; Planned adapter path.** Reversible/read behavior may be automated. Irreversible or externally visible state changes re-enter the risk/approval pipeline. ### Agent Task Mode -**Accepted architecture.** Each delegated task receives an isolated or explicitly attached browser context, scoped capabilities, origins, secrets, policy and resource budgets. The unrestricted default human profile is not ambient task authority. Complete browser adapter/session integration remains Planned. +**Accepted architecture; Planned adapter path.** Each delegated task receives an isolated or explicitly attached browser context, scoped capabilities, origins, secrets, policy and resource budgets. The unrestricted default human profile is not ambient task authority. ### Crawler Mode -**Accepted architecture.** The read-only crawler policy foundation is Implemented, while the complete crawler runtime is Planned. Robots evidence, rate controls, purpose, privacy, retention and legal/contract policy are distinct checks. +**Accepted architecture; policy foundation Implemented.** Crawler actions are read-only. Robots evidence, rate controls, purpose, privacy, retention and legal/contract policy are distinct checks. ## 5. Identifier and lifetime contracts ### 5.1 Core identifiers -Protected-main core contracts already define opaque browser-session, browsing-context, document-epoch and observed-node authority values governed by Accepted ADR 0010. External browser identifiers must be translated through scoped registries instead of becoming core authority directly. The protocol-ID registry is active PR #40 evidence until protected integration. +Durable identifiers introduced by adapters must be opaque and nonzero/nonempty. External browser identifiers are translated through scoped registries instead of becoming the core authority value directly. -Required browser-lifetime tuple: +Planned browser-lifetime tuple: ```text browser_session_id @@ -120,7 +117,7 @@ An actionable node reference is valid only when every component matches the live ### 5.2 Document epochs -Navigation, document replacement, or another adapter-defined actionable-document lifetime change rotates `document_epoch`. A stale node reference must fail deterministically before input dispatch. Core exact-authority validation is Implemented; real browser lifecycle invalidation/linearized dispatch remains adapter work. +Navigation, document replacement, or another adapter-defined actionable-document lifetime change rotates `document_epoch`. A stale node reference must fail deterministically before input dispatch. ### 5.3 Idempotency @@ -148,7 +145,7 @@ The pure destination crate itself does no DNS lookup. ### 6.3 Route/proxy authority -**Protected-main status: Implemented for route-authority foundations. Proxy/PAC execution: Planned.** Direct routing is the default. Proxy and PAC-selected routes require explicit authority. A proxy is an intermediate authority and never replaces final-target authorization. Ambient environment proxy variables cannot silently change the governed route. PAC evaluation, proxy transport and CONNECT require separate execution evidence before release claims. +**Accepted architecture; active development.** Direct routing is the default. Proxy and PAC-selected routes require explicit authority. A proxy is an intermediate authority and never replaces final-target authorization. Ambient environment proxy variables cannot silently change the governed route. ### 6.4 Direct transport @@ -160,9 +157,7 @@ The pure destination crate itself does no DNS lookup. ### 6.6 HTTP semantics -**Protected-main status: Planned.** Active replacement PR #37 implements bounded HTTP/1.1 semantics but remains non-shipped evidence until protected integration. Historical PR #11 is predecessor lineage and is not current implementation evidence. - -HTTP processing must consume an authenticated governed connection and define: +**Accepted architecture; active development.** HTTP processing must consume an authenticated governed connection and define: - supported methods and caller-controlled fields; - syntax/framing rules; @@ -205,7 +200,7 @@ Raw HTML is not the default model payload. ## 8. Action architecture -The standard action vocabulary is **Accepted architecture**; complete real-browser runtime integration remains Planned: +Standard action vocabulary is **Accepted architecture / Planned runtime integration**: ```text navigate @@ -246,7 +241,7 @@ The adapter declares an observable post-condition contract, such as URL change, ### 9.1 Purpose-bound authority -**Implemented policy foundation.** Protected disclosure authority binds tenant, task, field, business purpose, canonical destination and data classification under Accepted ADR 0007. This implementation does not imply that trusted value storage, opaque-handle resolution, revocation or browser fill are complete. +**Active development.** Protected disclosure authority is represented as one value object/scoped record containing tenant, task, field, business purpose, canonical destination and data classification. Reclassification requires newly valid authority. ### 9.2 Opaque handle broker @@ -261,17 +256,15 @@ The adapter declares an observable post-condition contract, such as URL change, - value resolution/fill; - compensation/recovery after reserved-but-failed use. -Issue #10 owns the broader broker/storage/lifecycle completion. - ### 9.3 Evidence -Protected-main evidence primitives can record purpose-bound sensitive-access authority without carrying the protected value. Complete broker-use receipts must remain aligned with the runtime lifecycle once that broker exists. +Access/disclosure evidence records identifiers, scope, decision, approval reference, policy version and lifecycle times without carrying the protected value. ## 10. Resource-governor requirements ### 10.1 Deterministic kernel -**Implemented.** `originweave-resource` validates budgets, includes CPU-worker admission state, and produces a cumulative mitigation plan. It does not sample the operating system or directly schedule processes. +**Implemented foundation.** `originweave-resource` validates budgets and produces a cumulative mitigation plan. It does not sample the operating system or directly schedule processes. ### 10.2 Adapter telemetry @@ -283,7 +276,7 @@ Protected-main evidence primitives can record purpose-bound sensitive-access aut ### 10.4 Constrained GPU -**Accepted architecture.** Rendering and local model inference use phase scheduling where necessary. The implementation of platform GPU telemetry/scheduling remains Planned. The mitigation ladder can shrink model batches, release inference caches, offload to CPU, pause the task and reject admission before foreground rendering is sacrificed. +**Accepted architecture / Planned implementation.** Rendering and local model inference use phase scheduling where necessary. The mitigation ladder can shrink model batches, release inference caches, offload to CPU, pause the task and reject admission before foreground rendering is sacrificed. ## 11. Evidence and provenance requirements @@ -317,7 +310,7 @@ Generic network evidence retains bounded names and canonical locators while valu ### WebDriver BiDi -**Planned.** WebDriver BiDi is an evolving W3C adapter contract. Its session/user-context/browsing-context identifiers are translated into OriginWeave-scoped internal identities. Core lifetime authority is already Implemented; active PR #40 is non-shipped registry implementation evidence. +**Planned.** WebDriver BiDi is an evolving W3C adapter contract. Its session/user-context/browsing-context identifiers are translated into OriginWeave-scoped internal identities. Protocol evolution is isolated behind versioned adapter tests. ### Chrome DevTools Protocol @@ -325,7 +318,7 @@ Generic network evidence retains bounded names and canonical locators while valu ### WebMCP -**Planned.** **WebMCP** is an experimental external dependency that can provide typed page tools. Tool schemas and outputs remain untrusted page-originated data and cannot grant OriginWeave authority. +**Planned / experimental external dependency.** **WebMCP** can provide typed page tools. Tool schemas and outputs remain untrusted page-originated data and cannot grant OriginWeave authority. ### Model Context Protocol @@ -337,9 +330,9 @@ Generic network evidence retains bounded names and canonical locators while valu ## 13. Manifest V3 extension requirements -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. +**Accepted architecture / Planned compatibility program.** OriginWeave preserves Chromium's extension implementation rather than rebuilding Chrome APIs in Rust. Agent authority remains separate from ordinary extension permissions. A future signed policy registry controls which extensions may observe or propose agent actions. -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. +Compatibility acceptance includes installation/update, extension service-worker lifecycle, content scripts, storage, scripting, DNR, native messaging, downloads, side panel, restart persistence and explicit task-mode isolation. ## 14. Prompt-injection and model boundary @@ -393,7 +386,6 @@ Long-running tasks and external model calls require cancellation semantics that - Node/action validation occurs immediately before execution to close stale-state races. - Sensitive-handle use becomes atomic in the trusted broker. - Migration/release/automation writer leases prevent competing repository writers. -- Repository-scoped collision-sensitive identifiers such as ADR numbers, migration IDs and protocol/schema versions are reserved across protected main plus active work before allocation. - Platform compute pools avoid avoidable oversubscription between Chromium, Rust and model runtimes. ## 17. Persistence and data naming @@ -415,7 +407,7 @@ network_exchange download_artifact ``` -The conceptual model is defined in [`erd/README.md`](erd/README.md). Adapters may use WARC/object storage/relational stores independently; cross-service application database access is not an integration contract. Conceptual ERD entities are not evidence that a physical relational schema exists. +The conceptual model is defined in [`erd/README.md`](erd/README.md). Adapters may use WARC/object storage/relational stores independently; cross-service application database access is not an integration contract. ## 18. Security and enterprise controls @@ -431,15 +423,11 @@ Product UI targets WCAG 2.2 AA / ISO/IEC 40500:2025-aligned evidence. Approval, ### Implemented kernels -Require deterministic unit/property/integration tests for canonicalization, classification, rebinding, redirects, route authority, direct peers, TLS identity, policy, session/node authority values, resources and evidence. +Require deterministic unit/property/integration tests for canonicalization, classification, rebinding, redirects, direct peers, TLS identity, policy, resources and evidence. ### Browser vertical slice -Requires real browser integration tests covering isolated contexts, protocol-ID registry binding, stale nodes, iframes/shadow DOM where supported, origin changes, typed actions, post-conditions, crashes, cancellations and governed real network composition. - -### Manifest V3 compatibility - -Maintain pinned real-Chromium evidence for every claimed extension surface, with restart/repeatability and bounded failure diagnostics. Compatibility evidence and Agent-authority evidence are independent: neither can substitute for the other. +Requires real browser integration tests covering isolated contexts, stale nodes, iframes/shadow DOM where supported, origin changes, typed actions, post-conditions, crashes, cancellations and governed real network composition. ### Security @@ -494,4 +482,4 @@ A material change to any of the following must update the authoritative document - enterprise privacy/security/tenancy contract; - release acceptance or rollback semantics. -If a decision is not implemented, the documentation must retain `Planned`, `Proposed`, or `Open` status rather than silently describe it as shipped. Active PR evidence remains explicitly non-shipped until protected integration and exact acceptance evidence exist. +If a decision is not implemented, the documentation must retain `Planned`, `Proposed`, or `Open` status rather than silently describe it as shipped. diff --git a/docs/adr/0013-manifest-v3-extension-authority.md b/docs/adr/0013-manifest-v3-extension-authority.md deleted file mode 100644 index e620edf9d..000000000 --- a/docs/adr/0013-manifest-v3-extension-authority.md +++ /dev/null @@ -1,108 +0,0 @@ -# ADR 0013: Manifest V3 compatibility and extension-to-Agent authority - -- **Status:** Proposed -- **Date:** 2026-08-10 -- **Supersedes:** None -- **Superseded by:** None - -## Context - -OriginWeave retains Chromium as its compatibility kernel rather than reimplementing Chrome's extension runtime. That creates two independent product questions: whether a declared Manifest V3 capability works on the pinned Chromium baseline, and whether an extension can influence an OriginWeave Agent Task only through explicit OriginWeave authority. - -Issue #27 requires both executable Manifest V3 compatibility evidence and explicit separation between Chromium extension permissions and OriginWeave Agent capabilities. Protected main contains partial pinned-Chromium compatibility evidence and extension-to-Agent authority foundations, but the full capability matrix, managed/native-messaging boundaries, release integration, and complete isolation acceptance remain open. - -This ADR makes that target architecture reviewable without claiming issue #27 is complete. Until protected-main governance accepts it, this ADR is Proposed design authority only. - -## Decision drivers - -- Preserve Chromium extension compatibility without creating a second OriginWeave plugin ecosystem. -- Prevent Chrome extension permissions from becoming ambient Agent Task authority. -- Keep Human Mode and delegated Agent Task profile semantics distinct. -- Bind compatibility claims to an exact Chromium revision and declared capability matrix. -- Keep extension-produced content and messages in the untrusted-observation domain. -- Keep protected secrets and sensitive values behind independent purpose-bound authority. -- Support managed extensions without granting arbitrary native-process or cross-origin capability. -- Allow safe rollback when a Chromium revision regresses a declared extension surface. - -## Assumptions and authority boundaries - -- Chromium owns Manifest V3 parsing, service workers, extension APIs, isolated worlds, and browser-managed extension policy. -- OriginWeave owns Agent Task isolation, extension-to-Agent grants, task/origin/action authority, secret/sensitive disclosure, approvals, evidence, and release claims. -- A Chromium extension permission authorizes the extension inside Chromium; it does not mint an OriginWeave capability. -- An OriginWeave `extension_grant` authorizes only the explicitly bound OriginWeave interaction; it does not emulate Chrome manifest permissions. -- Extension content, page mutations, messages, native-host output, and structured tool output remain untrusted observations unless independently authenticated through a separate trusted administrative channel. -- Compatibility evidence and Agent-authority-isolation evidence are separate evidence classes. Neither implies the other. - -## Options considered - -### Reimplement Chrome extensions as a Rust plugin system - -Rejected. It would create a second extension ecosystem and duplicate mature Chromium behavior. - -### Let extensions inherit Agent Task authority from Chrome permissions - -Rejected. Chrome permissions are not OriginWeave task/origin/action/approval grants and ambient inheritance creates confused-deputy, secret-disclosure, prompt-injection, and cross-origin escalation risk. - -### Disable extensions in every mode - -Rejected as a product-wide rule. Agent Task Mode defaults to no extensions or a managed allow-list, but Human Mode must retain normal compatible extension use and enterprises may require managed extensions. - -### Retain Chromium's extension plane and add explicit OriginWeave grants - -Selected. - -## Decision - -1. **Retain Chromium Manifest V3 as the compatibility plane.** OriginWeave does not create a competing Rust extension API for browser compatibility. -2. **Separate execution modes.** Human Mode may use the person's compatible extension set under browser/enterprise policy. Agent Task Mode defaults to no extensions or an explicit managed allow-list. Later attached-human-tab execution is labelled reduced-assurance when pre-existing extensions can influence page state. -3. **Require explicit OriginWeave extension authority.** Any extension-to-Agent interaction that can affect an Agent Task requires an `extension_grant` or equivalent typed decision bound at minimum to extension identity/version policy, session, applicable browsing context, capability, origin/resource scope, expiry, and task. -4. **Never translate Chrome permission into Agent capability.** `tabs`, `scripting`, `downloads`, `declarativeNetRequest`, host permissions, native messaging, or managed policy do not grant OriginWeave navigation, action, approval, secret, or sensitive-data authority. -5. **Keep extension output untrusted.** Extension messages and content enter the bounded observation/provenance path. They cannot alter the trusted goal, add tools, mint capabilities, approve high-risk actions, or weaken deterministic policy. -6. **Keep protected values brokered.** An extension does not receive raw credentials or sensitive values merely because it can inspect or modify a page. Independent secret/sensitive-data authority is rechecked immediately before trusted browser dispatch. -7. **Bound native messaging separately.** Native messaging is supported only behind an explicit host-managed allow-list, exact extension/host identity policy, process boundary, bounded I/O, and auditable lifecycle. It remains unsupported until that executable boundary exists. -8. **Publish exact compatibility evidence.** Public extension claims are bound to an exact Chromium revision/build and explicit Manifest V3 capability matrix. OriginWeave does not claim universal or `100% Chrome extension compatibility`. -9. **Separate Chrome-only services.** Web Store distribution, Google-account services, proprietary codecs/DRM, licensing, and other Chrome-only services are not implied by Manifest V3 compatibility. -10. **Gate releases by declared surfaces.** A declared supported capability that regresses blocks release or must be removed from the published matrix before release. Compatibility success never substitutes for Agent-authority-isolation evidence. - -## Consequences - -OriginWeave can preserve mature Chromium extension behavior while keeping its differentiating authority logic in reusable Rust modules. Buyers receive exact, falsifiable compatibility claims and separately reviewable security evidence. The cost is maintaining both a real-browser compatibility suite and independent authority-isolation tests, plus explicit managed-extension/native-host lifecycle work. - -## Failure and degraded behavior - -- A failed declared MV3 fixture makes that capability unsupported for the affected pinned release until fixed or removed from the published matrix. -- Invalid extension identity, grant scope, session/context binding, origin, expiry, or task fails closed. -- Attempts to widen task authority, inject a trusted instruction, resolve a secret, or synthesize approval are denied and recorded as bounded credential-free evidence. -- Missing native-host policy/process isolation keeps native messaging unsupported rather than falling back to ambient process execution. -- Attached human-tab sessions with unknown extensions are reduced-assurance and cannot inherit isolated-task release claims. - -## Security / privacy / governance impact - -The decision reduces confused-deputy and prompt-injection risk by keeping Chrome extension permissions outside OriginWeave policy. Secret and sensitive-data disclosure remain independently purpose-bound. Extension observations and compatibility diagnostics must not expose raw credentials, arbitrary local filesystem paths, unrestricted native-process output, or protected values in logs/evidence. Enterprise-managed extension policy is policy input, not a replacement for task authorization. - -## Tests and acceptance evidence - -Issue #27 acceptance requires pinned-Chromium evidence for the declared matrix and separate production authority tests, including service-worker/content-script lifecycle, declared APIs, restart/update persistence, Agent Task isolation without a grant, managed-grant success, denial of origin/action widening, untrusted-message handling, secret non-disclosure, exact build binding, repeated-run evidence, native-messaging denial until implemented, and release failure when a public capability regresses. - -Current active compatibility PRs are evidence only for their unchanged exact heads. They do not make this Proposed ADR Accepted or close issue #27. - -## Migration and rollback - -No persistent database migration is introduced. A release can roll back the Chromium baseline, disable a managed extension, revoke an `extension_grant`, or remove an unproven capability from the published matrix without widening authority. Rollback evidence must retain the exact Chromium/build/capability set that was tested. - -## Open follow-ups - -- Complete issue #27's compatibility matrix and production isolation acceptance. -- Define managed-extension identity/update semantics. -- Implement the native-messaging allow-list/process boundary before claiming support. -- Integrate the complete Agent Task browser vertical slice under issue #28. -- Reconcile PRD/TRD/traceability from protected-main evidence as compatibility slices integrate. -- Promote this ADR only through explicit protected-main governance. - -## Supersession / reversal conditions - -Supersede this ADR if Chromium adopts a materially different extension authority model, OriginWeave intentionally drops Chromium extension compatibility, or an accepted architecture provides safer equivalent compatibility without ambient Agent authority. A successor must retain explicit compatibility evidence and task-authority separation. - -## References - -Primary browser/extension/protocol evidence and APA 7 references are maintained in [`../doctoring/browser-agent-protocols.md`](../doctoring/browser-agent-protocols.md) and [`../doctoring.md`](../doctoring.md). Related decisions include ADR 0001, ADR 0002, ADR 0007, ADR 0010, ADR 0101, ADR 0104, and ADR 0107. \ No newline at end of file diff --git a/docs/adr/0014-architecture-decision-governance.md b/docs/adr/0014-architecture-decision-governance.md deleted file mode 100644 index d550f8464..000000000 --- a/docs/adr/0014-architecture-decision-governance.md +++ /dev/null @@ -1,112 +0,0 @@ -# ADR 0014: Architecture decision acceptance governance - -- **Status:** Proposed -- **Date:** 2026-08-10 -- **Supersedes:** None -- **Superseded by:** None - -## Context - -OriginWeave separates protected-main source, executable checks, formal review, documentation, release evidence, and runtime policy as distinct authorities. Architecture Decision Records need the same discipline: a Markdown file, issue, chat statement, automation prompt, model verdict, or PR body can propose a decision but cannot independently make it an Accepted governing decision. - -Current contributor authority comes from protected-main `AGENTS.md`, live GitHub policy, and any explicit operationally satisfiable CWL/OriginWeave governance rule. The current contract also describes a solo-maintainer condition: an otherwise impossible independent non-author approval rule is not manufactured when fewer than two eligible independent maintainers exist, while technical/security/coverage/rustdoc/findings/live-base/branch-protection gates remain mandatory. - -The ADR index previously repeated these binding details directly. An index should discover governance rather than create it. This ADR therefore records the proposed durable acceptance model and its reversal conditions. While Proposed, it does not override `AGENTS.md` or live GitHub policy. - -## Decision drivers - -- Prevent indexes, chat, model output, or stale PR evidence from silently changing architecture authority. -- Never synthesize, impersonate, self-submit, or fabricate approval that current policy requires. -- Avoid permanent solo-maintainer deadlock when an independent reviewer route does not operationally exist and GitHub does not require one. -- Keep exact-head technical evidence mandatory regardless of review topology. -- Make reviewer-provisioning gaps explicit and reversible. -- Keep ADR status machine-checkable without turning README prose into a hidden policy engine. - -## Assumptions and authority boundaries - -- Protected-main `AGENTS.md` and live GitHub rules are authoritative for contributor actions. -- This ADR remains Proposed until a protected-main revision explicitly records an Accepted lifecycle transition in this ADR's metadata and both canonical indexes. -- Merely merging a file that still says `Proposed` does not Accept it. -- Formal review and technical checks are separate evidence classes. -- A review counts only if the governing policy recognizes that reviewer identity and review state for the relevant exact head. -- Predecessor-head approval does not transfer across a changed head unless live policy explicitly defines that behavior. - -## Options considered - -### Define ADR acceptance only in the index README - -Rejected. The index should summarize and discover decisions, not define the binding algorithm that grants its own statuses. - -### Require non-author approval unconditionally - -Rejected. In a genuine solo-maintainer topology this creates an unsatisfiable governance deadlock and pressure to invent reviewer identities or weaken the rule. - -### Let the author or automation synthesize approval - -Rejected. Self-approval, impersonation, model verdicts, reactions, status checks, or fabricated identities cannot provide independent review evidence. - -### Bind acceptance to live protected-branch governance with a narrow solo-maintainer hold - -Selected. - -## Decision - -If Accepted, OriginWeave applies these durable ADR-governance rules: - -1. **Explicit protected-main lifecycle transition defines architecture acceptance.** A branch file, issue, chat statement, prompt, PR body, check, model verdict, or merge by itself does not create a governing Accepted ADR. An ADR becomes Accepted only when a protected-main revision explicitly changes that ADR's lifecycle metadata to `Accepted` and both `docs/README.md` and `docs/adr/README.md` mirror the same status. A Proposed ADR that merely reaches protected main remains Proposed. -2. **Live policy defines mandatory review evidence.** When current GitHub rules require counted approval, acceptance requires a formal `APPROVED` review from an eligible identity recognized by that policy on the applicable unchanged head. -3. **Repository-specific review requirements must be operationally satisfiable.** A stricter CWL/OriginWeave rule may require an eligible non-author reviewer only when a legitimate reviewer route exists. -4. **No synthetic approval.** Author approval, COMMENTED reviews, reactions, model verdicts, statuses, predecessor-head approvals, impersonated identities, and fabricated accounts never substitute for required counted approval. -5. **The solo-maintainer hold is narrow.** When fewer than two eligible independent maintainers exist and live GitHub policy does not independently require counted non-author approval, an otherwise impossible repository-level independent-review requirement is held. CI, security, SAST, exact owned-code coverage, rustdoc, unresolved findings/threads, live-base, mergeability, branch protection, release, and operational evidence remain mandatory. -6. **Reviewer provisioning is a first-class state.** If live policy requires independent approval but no eligible reviewer route exists, the PR is reviewer-provisioning-blocked. The remedy is legitimate reviewer/team/App provisioning or an authorized governance change, never self-approval or gate weakening. -7. **The hold reverses automatically.** Independent-review enforcement returns when two or more eligible independent maintainers exist, live GitHub policy requires it, or an Accepted successor defines another legitimate counted-review route. -8. **Indexes discover; they do not grant status.** `docs/README.md` and `docs/adr/README.md` must mirror each ADR's explicit lifecycle metadata and protected-main location. -9. **Design authority is not implementation evidence.** Even an Accepted ADR does not prove described behavior is implemented or released; protected-main code/tests/artifacts/configuration and claim-appropriate operational evidence establish that truth. - -## Consequences - -The repository can remain review-realistic without weakening technical gates, and maintainer-topology changes have explicit re-enablement semantics. The trade-off is that reviewer eligibility and live policy must be re-evaluated when governance changes; some otherwise-green work may legitimately remain blocked on reviewer provisioning. - -## Failure and degraded behavior - -- If live review requirements cannot be determined, do not infer permission to accept or merge; treat review authority as unresolved and continue non-conflicting work. -- If a required reviewer cannot be provisioned under current authority, classify the exact PR/head as reviewer-provisioning-blocked. -- If an ADR index and file disagree, the documentation contract fails until repaired. -- If an Accepted ADR describes behavior absent from protected-main implementation evidence, product docs must label that capability partial/planned rather than shipped. - -## Security / privacy / governance impact - -This is governance hardening. It prevents automation from manufacturing social proof, preserves branch/ruleset authority, and keeps model/check output non-authoritative for approval. It introduces no new secret or personal-data path. - -## Tests and acceptance evidence - -The documentation contract must prove that every ADR file is indexed exactly once in both canonical indexes, index status matches file metadata, Accepted and Proposed entries are not silently interchanged, superseded decisions retain discoverable successors where applicable, and active-PR ADRs are not presented as protected-main implementation evidence. README prose should point to `AGENTS.md`, live GitHub policy, and this ADR instead of independently redefining the acceptance algorithm. - -Operational acceptance for an actual merge additionally requires a current-authority probe of GitHub rules and reviewer eligibility whenever counted review matters; a documentation test cannot prove runtime reviewer eligibility. - -## Migration and rollback - -No database or runtime migration is introduced. On acceptance, duplicate binding review logic should be removed from ADR-index prose and replaced by concise references to `AGENTS.md`, live GitHub policy, and this ADR. A superseding governance change must update both indexes and contributor-governance documentation coherently. - -## Open follow-ups - -- Keep the machine-checkable ADR-index/status contract aligned with lifecycle and supersession states. -- Re-evaluate reviewer topology whenever maintainers, teams, Apps, or branch rules change. -- Keep scheduler prompts subordinate to protected-main `AGENTS.md` and live GitHub policy. -- Record any future organization-wide reviewer authority and its eligibility boundary in an Accepted successor before relying on it as repository-specific governance. - -## Supersession / reversal conditions - -Supersede this ADR if GitHub governance changes to a materially different review model, the organization adopts a managed independent-review service/team with explicit eligibility semantics, or OriginWeave changes its ADR lifecycle. A successor must retain the prohibitions on synthetic approval and on treating technical/model evidence as formal review authority. - -## References - -ContextualWisdomLab. (2026). *Agent development contract* [Repository specification]. *OriginWeave*. [`../../AGENTS.md`](../../AGENTS.md) - -ContextualWisdomLab. (2026). *OriginWeave architecture decision records* [Repository specification]. *OriginWeave*. [`README.md`](README.md) - -GitHub. (n.d.). *Approving a pull request with required reviews*. GitHub Docs. Retrieved August 10, 2026, from https://docs.github.com/en/pull-requests/how-tos/review-pull-requests/approving-a-pull-request-with-required-reviews - -GitHub. (n.d.). *About protected branches*. GitHub Docs. Retrieved August 10, 2026, from https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches/about-protected-branches - -Current repository settings remain mutable runtime policy and must be probed live; these references document GitHub review/protection semantics rather than freezing the repository's current configuration into this ADR. diff --git a/docs/adr/README.md b/docs/adr/README.md index 416231b1c..2838a12a0 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -1,22 +1,22 @@ # OriginWeave Architecture Decision Index -This directory contains durable architecture decisions for OriginWeave. A pull-request body, chat transcript, roadmap bullet, automation prompt, issue, or implementation plan may motivate a decision but does not replace an ADR when the decision changes a governing product or authority boundary. +This directory contains durable architecture decisions for OriginWeave. A pull-request body, chat transcript, roadmap bullet, or implementation plan may motivate a decision but does not replace an ADR when the decision changes a governing product or authority boundary. ## Status vocabulary - **Proposed** — under review; not binding and not a shipped claim. -- **Accepted** — governing design decision on protected `main`; acceptance does not itself prove that every described capability is implemented. +- **Accepted** — governing design decision on protected `main`; acceptance does not by itself prove that every described capability is implemented. - **Superseded** — replaced by a later Accepted ADR; retained for history. - **Deprecated** — still discoverable but no longer recommended for new work. - **Rejected** — evaluated and intentionally not adopted. -Current contributor/review authority is defined by protected-main [`../../AGENTS.md`](../../AGENTS.md) together with live GitHub repository policy. [ADR 0014](0014-architecture-decision-governance.md) records the proposed durable ADR-acceptance model, including reviewer eligibility, the solo-maintainer hold, re-enablement conditions, and the prohibition on synthetic approval. While ADR 0014 is Proposed, it does not override those live authorities. COMMENTED reviews, check/status results, model verdicts, reactions, author approval, predecessor-head approval, or dismissed reviews never substitute for a review that current policy actually requires. +An ADR becomes Accepted only through normal protected-branch review and merge. Where live repository policy or explicit CWL/OriginWeave governance requires independent review, acceptance also requires a qualifying non-author formal `APPROVED` review on the unchanged exact head. COMMENTED reviews, check/status results, model verdicts, reactions, author approval, predecessor-head approval, or dismissed reviews never substitute for that requirement. Conversation-derived ideas remain Proposed/Open in PRD/TRD/traceability until the protected process is complete. -An Accepted ADR is **design authority, not implementation evidence**. Protected-main source, executable tests, built/released artifacts, migrations/configuration, and protected-main operational evidence appropriate to the claim establish current implemented behavior. An ADR may intentionally describe an accepted target that is only partially implemented; product documents must label implementation status separately. +An Accepted ADR is **design authority, not implementation evidence**. Protected-main source, executable tests, built/released artifacts, migrations/configuration, and protected-main operational evidence appropriate to the claim establish current implemented behavior. An ADR may intentionally describe an accepted target that is only partially implemented; the product documents must label that implementation status separately. -## Accepted protected-main decisions +## Current protected-main decisions -| ADR | Decision | Status | Governs | +| ADR | Decision | Protected-main status | Governs | |---|---|---|---| | [0001](0001-chromium-compatibility-kernel.md) | Retain Chromium as the compatibility kernel | Accepted | Blink/V8/graphics/extensions boundary; Rust control-plane integration | | [0002](0002-agent-safety-kernel.md) | Agent safety kernel | Accepted | mode, capability, origin, risk, crawler, secret and approval policy | @@ -24,19 +24,13 @@ An Accepted ADR is **design authority, not implementation evidence**. Protected- | [0004](0004-resolved-destination-policy.md) | Logical origin and resolved destination safety | Accepted | SSRF/rebinding/special-purpose address and redirect authority | | [0005](0005-direct-socket-binding.md) | Exact direct TCP peer binding | Accepted | explicit socket authority and operating-system peer proof | | [0006](0006-tls-server-identity.md) | TLS service identity over the verified peer | Accepted | WebPKI identity, roots, time, ALPN and stream binding | -| [0007](0007-purpose-bound-sensitive-data-authority.md) | Purpose-bound sensitive-data authority | Accepted | tenant/task/field/purpose/destination/classification disclosure authority | -| [0008](0008-leaf-validity-horizon.md) | Delegated-task TLS leaf-validity horizon | Accepted | minimum certificate-validity horizon for bounded delegated tasks | -| [0010](0010-session-context-bound-node-authority.md) | Session/context-bound node authority | Accepted | browser-session, browsing-context, origin, document-epoch and stale-node authority | - -## Proposed architecture decisions -Proposed ADR files are reviewable target architecture without becoming Accepted or shipped behavior. The provenance subsections distinguish files already present in the protected-main baseline from decisions introduced by this documentation reconciliation. Provenance never changes lifecycle: file presence on an active branch is not protected-main truth, and later integration does not itself promote a Proposed ADR to Accepted. +## Proposed target-architecture decisions in this change -### Protected-main baseline proposed decisions +The following ADRs make the product-wide target architecture reviewable without promoting it to shipped behavior. They remain **Proposed** until their exact branch is reviewed and merged under protected-main policy. Existing feature PRs may independently carry lower-numbered Proposed ADRs; the `0100` range avoids claiming or conflicting with those active decisions. | ADR | Decision | Status | Governs | |---|---|---|---| -| [0009](0009-hourly-agent-credential-boundary.md) | Hourly agent credential boundary | Proposed | deterministic gates, NVIDIA credential materialization, local broker and publication separation | | [0100](0100-rust-control-plane-boundary.md) | Rust control-plane boundary | Proposed | Rust-owned product authority versus Chromium compatibility kernel | | [0101](0101-isolated-execution-profile-modes.md) | Isolated execution/profile modes | Proposed | Human, Assist, Agent Task and Crawler session/profile isolation | | [0102](0102-typed-actions-and-arbitrary-js.md) | Typed actions over arbitrary JavaScript authority | Proposed | action API, script escape hatches, risk/policy semantics | @@ -48,29 +42,7 @@ Proposed ADR files are reviewable target architecture without becoming Accepted | [0108](0108-crawler-policy.md) | Policy-bound crawler mode | Proposed | robots, rate/resource policy, read-only collection and no-evasion behavior | | [0109](0109-hourly-automation-operational-closure.md) | Hourly automation secret ordering and operational closure | Proposed | deterministic gates, model secret boundary, retries and protected-main proof | -### Proposed decisions introduced by documentation reconciliation - -| ADR | Decision | Status | Governs | -|---|---|---|---| -| [0013](0013-manifest-v3-extension-authority.md) | Manifest V3 compatibility and extension-to-Agent authority | Proposed | Chromium extension compatibility evidence, profile separation, extension grants, native-messaging boundary and release claims | -| [0014](0014-architecture-decision-governance.md) | Architecture decision acceptance governance | Proposed | ADR lifecycle authority, reviewer eligibility, solo-maintainer hold and re-enablement conditions | - -ADR 0013 and ADR 0014 exist only on this documentation branch until it integrates. After integration, this subsection remains historical provenance rather than an active-PR claim; both decisions remain Proposed until a later policy-compliant change explicitly changes their lifecycle. - -Other active feature PRs may contain additional Proposed ADRs. Those files are not part of this canonical documentation line until integrated or deliberately reconciled here. Historical PR checks, stale branch state, or chat decisions never transfer ADR acceptance across a changed head. - -## Index completeness rule - -Every numbered ADR file in the canonical documentation tree under review must be discoverable from this index with a status that agrees with the ADR's own lifecycle metadata. The protected-main subset must remain exact, while feature ADRs outside this canonical line belong in their owning PR's traceability until integration. When an ADR is added, accepted, superseded, deprecated, or rejected, update this index in the same protected change or an immediately coupled documentation reconciliation. - -The machine-checkable documentation contract should fail when: - -- a numbered ADR file in the canonical documentation tree is absent from this index; -- this index claims `Accepted` while the ADR metadata says `Proposed`, or the reverse; -- a superseded ADR lacks a discoverable successor; -- branch provenance is presented as lifecycle status or protected-main implementation evidence; -- an active-PR ADR is presented as protected-main implementation evidence; or -- a stale PR number, SHA, run ID, automation prompt, or conversation statement is used as timeless architecture authority. +Active feature PRs may contain additional Proposed ADRs. Those ADRs are not described as Accepted until their exact changes merge. When an ADR becomes protected-main architecture, update this index in the same protected change or an immediately coupled documentation repair. ## Decisions that require a dedicated ADR @@ -87,10 +59,9 @@ A new or superseding ADR is required when a change materially alters any of the 9. resource-governor priority, telemetry or GPU/CPU fallback semantics; 10. evidence/provenance identity, retention or persistence boundaries; 11. WebDriver BiDi, CDP, WebMCP, MCP or OriginWeave Protocol authority/version boundaries; -12. Manifest V3 extension-to-agent authorization or compatibility evidence policy; +12. Manifest V3 extension-to-agent authorization; 13. tenant, privacy, residency, audit, deployment or enterprise-control ownership; -14. hourly automation credential, writer, continuation or protected-main operational-proof authority; or -15. release acceptance, rollback/recovery or protected-main operational-proof requirements. +14. release acceptance, rollback/recovery or protected-main operational-proof requirements. ## Required ADR structure @@ -129,6 +100,5 @@ Material external standards or research belong in APA 7th format in [`../doctori - [`../uml/README.md`](../uml/README.md) visualizes component, sequence, state and deployment relationships. - [`../erd/README.md`](../erd/README.md) defines the conceptual durable domain model. - [`../traceability/README.md`](../traceability/README.md) maps requirements and decisions to implementation and evidence. -- [`../DOCUMENTATION_FITNESS.md`](../DOCUMENTATION_FITNESS.md) records semantic completeness and stale/current findings across the graph. -If these artifacts disagree about current implementation, protected-main source, executable tests, built/released artifacts, configuration/migrations, and protected-main operational evidence appropriate to the claim define implementation truth. Accepted ADRs explain governing design decisions; they do not upgrade missing behavior into shipped behavior. The disagreement is a documentation or implementation defect that must be repaired rather than silently rationalized from conversation history. \ No newline at end of file +If these artifacts disagree about what is currently implemented, protected-main source, executable tests, built/released artifacts, configuration/migrations, and protected-main operational evidence appropriate to the claim define implementation truth. Accepted ADRs explain the governing design decision and expected boundary; they do not upgrade missing behavior into shipped behavior. The disagreement is a documentation or implementation defect that must be repaired rather than silently rationalized from conversation history. diff --git a/docs/doctoring/browser-agent-protocols.md b/docs/doctoring/browser-agent-protocols.md deleted file mode 100644 index 5173a32e6..000000000 --- a/docs/doctoring/browser-agent-protocols.md +++ /dev/null @@ -1,79 +0,0 @@ -# Browser and Agent Protocol Standards Evidence - -- **Reviewed:** 2026-08-10 -- **Purpose:** primary-source evidence for OriginWeave browser compatibility and adapter boundaries -- **Canonical research index:** [`../doctoring.md`](../doctoring.md) - -This addendum complements the main doctoring record. The main record already carries the WebDriver BiDi, WARC/ISO 28500 and W3C PROV-O evidence. This addendum records the current primary sources for Manifest V3, Chrome DevTools Protocol, WebMCP and Model Context Protocol so product documentation does not rely on uncited protocol names. - -## WebDriver BiDi - -The W3C publication reviewed for this baseline is the 1 June 2026 **Working Draft**, not a Recommendation. OriginWeave therefore treats BiDi as a versioned browser-automation adapter rather than product-internal authority. Raw BiDi session/context/node identifiers do not become durable OriginWeave identities. - -Primary source: World Wide Web Consortium, *WebDriver BiDi*. - -## Chrome Manifest V3 - -Chrome's current manifest documentation identifies Manifest V3 as the current extension manifest format and the supported `manifest_version` value. OriginWeave therefore tests its declared extension compatibility against a pinned real Chromium/Chrome-for-Testing build and publishes evidence by exact capability. This is a compatibility target, not a claim of universal Chrome/Web Store/Google-service/codec/DRM equivalence. - -A Chrome extension permission remains separate from an OriginWeave Agent capability. Passing MV3 compatibility tests does not prove Agent-authority isolation, and a correct extension-grant kernel does not prove a real Chrome extension API works. - -Primary source: Chrome for Developers, *Manifest file format* and *Manifest Version*. - -## Chrome DevTools Protocol - -The official CDP documentation states that tip-of-tree changes frequently and provides no backward-compatibility guarantee for capabilities it introduces. OriginWeave therefore pins the Chromium/protocol evidence used by a release and keeps CDP behind an adapter. CDP is useful for Chromium-specific Network, Accessibility, DOMSnapshot, tracing and diagnostic surfaces; it is not the durable OriginWeave authority model. - -Primary source: Chrome DevTools Protocol, *Chrome DevTools Protocol—Latest (tip-of-tree)*. - -## WebMCP - -Chrome's 2026 WebMCP documentation describes WebMCP as an experimental/proposed structured-tool surface and its security guidance explicitly discusses indirect prompt injection and `untrustedContentHint`. The reviewed Chrome material is associated with an origin-trial / intent-to-experiment path. OriginWeave may prefer a valid structured WebMCP tool over lower-level scraping when present, but WebMCP remains optional and adapter-bound. - -WebMCP tool definitions, extension-produced content and tool outputs are untrusted observations. They cannot mint OriginWeave capabilities, alter the trusted task goal, resolve secrets, or approve high-risk actions. - -Primary sources: Chrome for Developers, *WebMCP*; *WebMCP tool security*; *Agent security considerations for WebMCP*. - -## Model Context Protocol - -The Model Context Protocol project released specification version `2026-07-28` on 28 July 2026. That release moved the protocol core toward stateless request/response operation and removed the earlier protocol-session assumptions described by previous releases. OriginWeave therefore keeps durable browser state in explicit OriginWeave application handles and exposes MCP only as a high-level adapter to the Rust runtime. MCP clients or servers do not connect models directly to Chromium/CDP authority. - -Primary sources: Model Context Protocol, *2026-07-28 Specification* and the maintainers' official release announcement. - -## Provenance standards - -The main [`docs/doctoring.md`](../doctoring.md) records the stable W3C PROV-O Recommendation and ISO 28500:2017 WARC format. OriginWeave treats both as interoperability/persistence adapters around its typed evidence identities. A WARC record, PROV statement, model judgement, check result or action log is evidence of its own class; none becomes authorization merely because it is captured in a provenance format. - -## Product consequences - -1. Version adapter contracts independently from OriginWeave session/context/action/evidence types. -2. Pin exact Chromium/CDP compatibility evidence at release time. -3. Keep WebDriver BiDi's Working Draft status visible in compatibility claims. -4. Keep WebMCP experimental/optional and propagate untrusted-content semantics. -5. Keep MCP browser state application-level rather than equating protocol transport/session metadata with browser authority. -6. Test Manifest V3 compatibility and extension-to-Agent authority isolation as separate evidence classes. -7. Treat WARC/PROV as provenance representations, not policy or truth escalation. - -## References — APA 7th - -Chrome DevTools Protocol. (n.d.). *Chrome DevTools Protocol—Latest (tip-of-tree)*. Retrieved August 10, 2026, from https://chromedevtools.github.io/devtools-protocol/tot/ - -Google Chrome Developers. (n.d.). *Manifest file format*. Chrome for Developers. Retrieved August 10, 2026, from https://developer.chrome.com/docs/extensions/reference/manifest - -Google Chrome Developers. (n.d.). *Manifest Version*. Chrome for Developers. Retrieved August 10, 2026, from https://developer.chrome.com/docs/extensions/reference/manifest/manifest-version - -Google Chrome Developers. (2026). *WebMCP*. Chrome for Developers. https://developer.chrome.com/docs/ai/webmcp - -Pagnucco, J., & Klepper, A. (2026, June 9). *Agent security considerations for WebMCP*. Chrome for Developers. https://developer.chrome.com/docs/agents/security - -Pagnucco, J., & Klepper, A. (2026, June 9). *WebMCP tool security*. Chrome for Developers. https://developer.chrome.com/docs/ai/webmcp/secure-tools - -Soria Parra, D., & Delimarsky, D. (2026, July 28). *The 2026-07-28 specification*. Model Context Protocol. https://blog.modelcontextprotocol.io/posts/2026-07-28/ - -Model Context Protocol. (2026). *Model Context Protocol specification (2026-07-28)*. https://modelcontextprotocol.io/specification/2026-07-28 - -World Wide Web Consortium. (2013). *PROV-O: The PROV ontology*. https://www.w3.org/TR/prov-o/ - -World Wide Web Consortium. (2026, June 1). *WebDriver BiDi* (W3C Working Draft). https://www.w3.org/TR/2026/WD-webdriver-bidi-20260601/ - -International Organization for Standardization. (2017). *Information and documentation—WARC file format* (ISO Standard No. 28500:2017). https://www.iso.org/standard/68004.html diff --git a/docs/doctoring/mv3-compatibility.md b/docs/doctoring/mv3-compatibility.md index 571c49329..607a2485b 100644 --- a/docs/doctoring/mv3-compatibility.md +++ b/docs/doctoring/mv3-compatibility.md @@ -1,54 +1,14 @@ # Manifest V3 compatibility evidence baseline - **Status:** Active implementation evidence for issue #27 -- **Reviewed:** 2026-08-11 +- **Reviewed:** 2026-08-09 - **Pinned browser:** Chrome for Testing `150.0.7871.129`, Chromium revision `r1639810` -OriginWeave uses Chromium as its compatibility kernel, so browser-extension compatibility must be demonstrated with executable Chromium evidence rather than inferred from architecture alone. The protected-main lane exercises a controlled unpacked Manifest V3 extension against one exact Chrome for Testing build and proves service-worker, content-script, storage, declarative-network-request, tabs, windows, scripting, commands, side-panel, bookmarks/history read compatibility, restart persistence, repeatability, and one real WebDriver click/post-condition. Active stacked compatibility work adds downloads, bounded bookmark/history mutation, profile isolation, explicit extension update/version-migration evidence, and an exact content-script isolated-world check. OriginWeave does **not claim 100% Chrome extension compatibility**. +OriginWeave uses Chromium as its compatibility kernel, so browser-extension compatibility must be demonstrated with executable Chromium evidence rather than inferred from architecture alone. This first bounded lane exercises a controlled unpacked Manifest V3 extension against one exact Chrome for Testing build. It covers an extension service worker, content-script injection, `chrome.storage.local`, declarative network blocking, and one real WebDriver click/post-condition. It does **not claim 100% Chrome extension compatibility** and does not make claims about Chrome Web Store distribution, Google-only services, proprietary codecs, DRM, native messaging, enterprise policy, restart/update migration, or every Chrome extension API. -The checked-in fixture is intentionally local-only. Its host permission is limited to loopback HTTP used by the deterministic test server. It contains no remote code, user credential, model call, external content, native-messaging host, or production PII. Chrome permissions remain distinct from the explicit OriginWeave extension-to-Agent grant implemented in `originweave-core`. Compatibility mutation tests create only controlled synthetic state inside the ephemeral test profile and must clean it up; successful API compatibility never grants the OriginWeave Agent ambient bookmarks/history/downloads authority. +The checked-in fixture is intentionally local-only. Its host permission is limited to loopback HTTP used by the deterministic test server. It contains no remote code, user credential, model call, external content, native-messaging host, or production PII. Chrome permissions remain distinct from the explicit OriginWeave extension-to-Agent grant implemented in `originweave-core`. -## Supported-capability evidence matrix - -This matrix separates protected-main executable evidence from active, non-shipped evidence and from genuinely unproven surfaces. A row marked **ACTIVE_PR** is never a release claim; exact head/run provenance belongs in `docs/evidence/2026-08-10-active-pr-maturity.md` and must be refreshed when the branch changes. - -| Compatibility surface | Evidence maturity | Current evidence boundary | Known gap / non-claim | -|---|---|---|---| -| Manifest V3 unpacked extension load | **PROTECTED_MAIN** | Exact pinned Chromium fixture loads through the dedicated compatibility workflow. | No Chrome Web Store distribution or arbitrary third-party extension-install claim. | -| Service worker start/restart + event response | **PROTECTED_MAIN** | Worker startup count and message response are observed across a real browser restart. | Suspend timing and the full Chrome event catalog are not exhaustively covered. | -| Content-script injection | **PROTECTED_MAIN** | Controlled content script mutates bounded DOM evidence on loopback. | Injection alone does not prove JavaScript isolated-world semantics. | -| Content-script isolated-world separation | **ACTIVE_PR #61** | Page main-world and extension isolated-world JavaScript assign the same sentinel name to distinct values; compatibility reports ready only while the page still reads `page` and the content script reads `extension` in real pinned Chromium. | One deterministic fixture proof only; no arbitrary page-JavaScript bridge or Agent authority. | -| `chrome.storage.local` + restart persistence | **PROTECTED_MAIN** | State is initialized on the first browser pass and required to persist on restart. | No OriginWeave-owned durable application database is implied. | -| `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. | -| 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. | -| Per-trial Agent Task profile isolation | **ACTIVE_PR #49** | Compatibility trials use isolated ephemeral profiles rather than ambient human state. | Full production Agent Task browser orchestration remains issue #28 work. | -| Extension update/version migration | **ACTIVE_PR #60** | Trial-local extension copy transitions `1.0.0` → `1.0.1` on the same ephemeral profile; versioned storage state is required to migrate and real pinned-Chromium evidence reports the update-migration surface. | No Chrome Web Store updater, enterprise deployment channel, arbitrary downgrade, or protected-main release claim. | -| Managed enterprise extension policy | **PLANNED** | No protected-main executable compatibility proof yet. | Do not infer managed-policy support from Chromium ancestry alone. | -| Native messaging | **PLANNED / SECURITY-GATED** | No compatibility claim. | Future support requires an explicit host-managed allow-list and process boundary. | -| Google-only services, proprietary codecs, DRM, Web Store licensing | **OUT_OF_SCOPE FOR COMPATIBILITY CLAIM** | Deliberately excluded from the open compatibility claim. | Chromium/API compatibility must not be conflated with Google service or licensing equivalence. | - -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. - -## 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. - -## Update-migration evidence boundary - -Restart persistence and extension update migration are separate compatibility claims. A successful restart proves only that state survives a new browser process. The active update-migration lane additionally uses a trial-local copy of the checked-in fixture, preserves the same extension path and ephemeral profile across passes, changes only the controlled manifest version from `1.0.0` to `1.0.1`, observes `chrome.runtime.getManifest().version`, and requires the fixture schema marker to migrate from version 1 to version 2. The checked-in fixture is not rewritten by the test. This establishes one deterministic unpacked-extension version transition; it does not establish Chrome Web Store update behavior, enterprise rollout semantics, downgrade behavior, or arbitrary third-party extension migration safety. - -## Isolated-world evidence boundary - -Content-script injection and content-script JavaScript isolation are separate compatibility claims. Active PR #61 writes `window.originweaveWorldSentinel = "page"` in the fixture page's main world and repeatedly publishes that value through one controlled DOM attribute. The content script assigns the same global name to `"extension"` in its own execution world, waits a bounded interval, and only reports the existing compatibility surface ready when it simultaneously observes the page's published `page` value and its own `extension` value. If both scripts share one JavaScript global namespace, the page publisher changes to `extension` and real-browser compatibility fails. DOM sharing here is deliberate test evidence, not permission for arbitrary page content to become trusted instruction or Agent authority. - -## Supply-chain and repeatability evidence - -The CI lane downloads the exact Chrome/ChromeDriver version from the official Chrome for Testing public bucket, records SHA-256 receipts for the downloaded archives, verifies the runtime-reported browser version, and emits bounded JSON compatibility evidence. A future release-quality matrix should additionally pin published artifact digests or equivalent immutable supply-chain identity when the upstream distribution exposes that identity in an authoritative machine-readable form. +The CI lane downloads the exact Chrome/ChromeDriver version from the official Chrome for Testing public bucket, records SHA-256 receipts for the downloaded archives, verifies the runtime-reported browser version, and emits bounded JSON compatibility evidence. A future release-quality compatibility matrix should additionally pin published artifact digests or equivalent immutable supply-chain identity when the upstream distribution exposes that identity in an authoritative machine-readable form. ## Primary references — APA 7th @@ -60,8 +20,6 @@ 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.history*. Google. Retrieved August 11, 2026, from https://developer.chrome.com/docs/extensions/reference/api/history - Chrome for Developers. (n.d.). *Manifest file format*. Google. Retrieved August 9, 2026, from https://developer.chrome.com/docs/extensions/reference/manifest Bynens, M. (2023, June 12). *Chrome for Testing*. Chrome for Developers. https://developer.chrome.com/docs/automation-and-testing/chrome-for-testing diff --git a/docs/evidence/2026-08-10-active-pr-maturity.md b/docs/evidence/2026-08-10-active-pr-maturity.md deleted file mode 100644 index 71353dca2..000000000 --- a/docs/evidence/2026-08-10-active-pr-maturity.md +++ /dev/null @@ -1,57 +0,0 @@ -# Active pull-request maturity evidence series — opened 2026-08-10 - -- **Evidence series opened:** 2026-08-10 -- **Last refreshed:** 2026-08-11 -- **Filename semantics:** the date in this filename is the date this evidence series was opened; refresh provenance is recorded separately and is never backdated to match the filename. - -This dated appendix records volatile implementation evidence that must not be embedded as timeless architecture truth. Protected `main` remains the only shipped-code authority. Active pull requests are implementation evidence only until they integrate and protected-main acceptance is re-established. - -## Protected-main anchor - -- Protected `main`: `67af7c87589edc2039545af335c95064d9b8391c` -- Product status: pre-alpha -- Documentation verdict: **DESIGN-SUFFICIENT / PROTECTED-MAIN-PARTIAL** - -## Active implementation evidence - -| PR | Scope | Maturity | Dependency / evidence boundary | -|---|---|---|---| -| #37 | Bounded HTTP/1.1 over authenticated governed transport | **IMPLEMENTED_ON_ACTIVE_PR** | Exact head `9becaaf61f10d854b20ebd2e04ccd3f57dee97fe` is mergeable and passes CI `31439440664`, Security Scan `31439440691`, SAST Semgrep `31439440663`, exact owned production coverage and CodeRabbit exact-head status. Protected main still reports HTTP as Planned. Historical #11 remains predecessor lineage until protected integration. | -| #40 | Browser protocol identifier → OriginWeave authority registry | **IMPLEMENTED_ON_ACTIVE_PR** | Current exact head `9e635e80e9813a1d2a9c408155d52221b76eeed3` is gate-clean across CI, Security Scan, SAST, Manifest V3 Compatibility and CodeRabbit; the real browser adapter remains Planned under #28. | -| #43 | Real pinned-Chromium Manifest V3 downloads compatibility | **IMPLEMENTED_ON_ACTIVE_PR** | Exact head `27ce89066ed1473dcd66eb26a2f91becf9df5424` is gate-clean; this proves one declared compatibility surface, not full extension compatibility or Agent authority. | -| #44 | Canonical documentation reconciliation | **IMPLEMENTED_ON_ACTIVE_PR** | This branch owns the documentation repair itself; its content does not become protected-main truth until integration. Current-head evidence must be read from the live PR because every reconciliation commit intentionally invalidates predecessor-head exactness. | -| #45 | Credential-free sensitive-handle lifecycle evidence | **IMPLEMENTED_ON_ACTIVE_PR** | Exact head `0f07fea031090c72a448fd9501b49d4dd7568419` is gate-clean; trusted broker/storage/value resolution remain Planned under #10. | -| #46 | In-process authoritative sensitive-handle use reservation | **IMPLEMENTED_ON_ACTIVE_PR** | Exact head `5f212cdfbf3c453472069973138fd9563cf7bff8` is gate-clean; no cross-process/database transactionality or protected-value resolution is claimed. | -| #47 | Bounded resolution freshness authority | **IMPLEMENTED_ON_ACTIVE_PR** | Exact head `6b5ed4dcea281b505f67db6180bb14c3bc95b392` is gate-clean. Its first-party consumer is now implemented on stacked #50, but neither capability is protected-main truth until dependency-ordered integration. | -| #48 | TLS revocation-material freshness primitive | **IMPLEMENTED_ON_ACTIVE_PR** | Exact head `9bbe12860436027a3b7cd5786775f1dacfbc835d` is gate-clean; no OCSP/CRL acquisition, signature validation, cache, or unrevoked claim is implemented. | -| #49 | Ephemeral Agent Task profile-isolation regression | **IMPLEMENTED_ON_ACTIVE_PR** | Draft stacked on #43 at exact head `96a4e949d96b5794ef473ccf813987b8e69ea566`; CI is green but dependency-gated and not independently integrable before #43. | -| #50 | First-party network consumption of resolution freshness | **IMPLEMENTED_ON_ACTIVE_PR** | Draft stacked on exact #47 head `6b5ed4dcea281b505f67db6180bb14c3bc95b392`. Exact head `f8b43bc94444986ab23aa4ef3086e446a0b39295` structurally hides the untimed public network planner, migrates first-party TLS integration helpers through `FreshConnectionPlan`, and passes CI run `31408474576` including exact owned function/line/region/branch coverage; CodeRabbit exact-head status is success. Dependency order, not implementation incompleteness, keeps the PR Draft. | -| #51 | Browser-task runtime telemetry plus one-PID Linux RSS sampling | **IMPLEMENTED_ON_ACTIVE_PR** | Exact head `dab26e4e9652408fb67dc8eedf9fd1820e524805` validates browser/task telemetry and samples one explicitly supplied Linux PID through strict `/proc//status` `VmRSS` parsing. CI `31441792029`, production coverage job `93627900171`, Security Scan `31441792000`, SAST Semgrep `31441791982` and CodeRabbit exact-head status succeed. Chromium PID discovery, task attribution, process-set accounting, GPU/VRAM and cross-platform sampling remain separate responsibilities. | -| #52 | Bounded semantic-node observation and relationship value contract | **IMPLEMENTED_ON_ACTIVE_PR** | Draft stacked on #40. Exact head `94fd284fe41746eeba9edc05d9753903b1c41ebf` adds at most 128 ordered child relationships, optional parent linkage, exact session/context/origin/document authority matching, self/duplicate rejection and stable credential-free errors. CI run `31428454410`, Manifest V3 Compatibility run `31428454350`, and CodeRabbit exact-head status succeed, including exact owned production function/line/region/branch coverage. The value contract still performs no browser I/O or action dispatch. | -| #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. | -| #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. | -| #60 | Real pinned-Chromium extension update/version migration | **IMPLEMENTED_ON_ACTIVE_PR** | Draft stacked on #59. Test-only head `a60875f70f8412db27ff1025b75d7ad4b8ddc38e` and CI run `31433305976` established the intended RED because no trial-local extension copy, version transition, migration state, or update-migration evidence existed. Current exact head `e696e19c9eaf3dedb104a5de4bdbd7970abf90d4` uses an ephemeral extension copy and one profile across initial `1.0.0`/initialized → restart `1.0.0`/current → update `1.0.1`/migrated passes. CI run `31433968874`, Manifest V3 Compatibility run `31433968931`, and CodeRabbit exact-head status succeed; the real browser evidence reports 3/3 trials and the exact update-migration surface. This does not claim Chrome Web Store/enterprise update semantics or Agent authority. | -| #61 | Real pinned-Chromium content-script isolated-world separation | **IMPLEMENTED_ON_ACTIVE_PR** | Draft stacked on #60. Test-only head `e81cdbd9b31a62227698bd3d824fd901551061f0` and CI run `31434443638` established the intended RED because the fixture had no page-main/content-isolated sentinel contract. Current exact head `c1705ad9fd2d96e620b89bb6e7ea1235063dcb6a` requires the page to retain `window.originweaveWorldSentinel = "page"` while the content script independently retains the same-named global as `"extension"`; the existing content compatibility surface fails if the JavaScript worlds collapse. CI run `31434670642`, Manifest V3 Compatibility run `31434670629`, and CodeRabbit exact-head status succeed; real browser evidence reports 3/3 repeatability trials. Compatibility evidence only; no arbitrary page-JavaScript bridge or Agent authority. | -| #62 | Extension proposal → Agent policy isolation regression | **IMPLEMENTED_ON_ACTIVE_PR** | Ready PR based directly on protected main. Exact head `a57873b3688984711918be17aadd348ed9fb12a9` first proves the exact extension/session/context `ProposeTypedAction` grant is allowed, then proves ordinary Agent policy independently rejects an out-of-grant target origin, a missing core `Navigate` capability, `WebContent` as an untrusted instruction source, raw secret delivery and unexpected secret material. CI run `31436844685`, production coverage job `93612736291`, Security Scan run `31436844615`, SAST Semgrep run `31436844646`, and CodeRabbit exact-head status succeed. This adds no production API or real Chromium adapter and does not convert extension proposal authority into Agent action/origin authority; it also cannot manufacture secret authority. | -| #63 | Extension proposal → secret high-risk approval isolation | **IMPLEMENTED_ON_ACTIVE_PR** | Ready PR based directly on protected main at exact head `e83749acd1cf5a0b778ba38eb9d6ed5a9bd1e68f`. The exact extension grant allows `ProposeTypedAction`, while ordinary Agent policy still returns `RequireApproval(RiskClass::R3)` for broker-handle `FillSecret`. CI `31437994464`, Rust contracts job `93616406126`, production coverage job `93616406182`, Security Scan `31437994491`, SAST Semgrep `31437994454`, and CodeRabbit exact-head status succeed. This is composition evidence only: it adds no secret broker, protected value, authenticated workload identity, browser adapter or approval evidence. | -| #64 | Verified action post-condition evidence with dispatch ordering | **IMPLEMENTED_ON_ACTIVE_PR** | Ready PR based directly on protected main at exact head `2c45411ed9aa0eecca2d06c85659db9f4bb85e4d`. `VerifiedActionOutcomeEvidence` requires verified provenance and caller-supplied monotonic dispatch/observation timestamps; observations before dispatch fail as `PostConditionPredatesDispatch`. CI `31441848670`, production coverage job `93628017556`, Security Scan `31441848649`, SAST Semgrep `31441848615`, and CodeRabbit exact-head status succeed. It is not a browser dispatcher and does not prove trusted clock provenance, real browser dispatch, target linkage, causality or a reached browser condition. | -| #65 | Controlled hostile Agent Task fixture | **IMPLEMENTED_ON_ACTIVE_PR** | Ready PR based directly on protected main at exact head `0888fe3a6ef6da547a37fd075733cc73dc52b2ab`. Test-only head `d2580305f05aba93d10b5342ec1886d601c6752e` and CI `31445088008` established the intended missing-fixture RED. The current fixture provides a labelled semantic form, deterministic same-document state transition and explicitly hidden/untrusted prompt-injection text using synthetic local data only. CI `31445201739`, Rust contracts job `93637824750`, production coverage job `93637824824`, Security Scan `31445201774`, SAST Semgrep `31445201669`, and CodeRabbit exact-head status succeed. It is controlled test infrastructure, not a browser adapter or proof of real Chromium execution. | -| #66 | Bounded explicit browser process-set RSS aggregation/sampling | **IMPLEMENTED_ON_ACTIVE_PR** | Draft stacked on exact #51 head `dab26e4e9652408fb67dc8eedf9fd1820e524805`. A predecessor implementation test incorrectly required two sequential `/proc` RSS reads to be equal; current regression instead verifies the kernel sample's positive byte/unit contract without assuming RSS immutability. Exact head `986958ab8a29b3ca708c80e44df45e1ec5f9f868` accepts at most 256 unique nonzero caller-owned PIDs, rejects empty/duplicate/oversized sets and checked-add overflow, and fails closed if any sampled member is unavailable. CI `31446842334` passes repository contracts, formatting, workspace checks/tests, strict Clippy, rustdoc and exact owned production function/line/region/branch coverage; CodeRabbit exact-head status succeeds. It does not discover Chromium PIDs, prove same-task attribution, walk a process tree/cgroup, or measure GPU/VRAM. | - -## Historical lineage - -PR #11 is a historical HTTP predecessor, not current implementation authority. It may close as superseded only after #37 reaches protected main and unique-work preservation plus protected-main acceptance are revalidated. - -## Interpretation rules - -1. `IMPLEMENTED_ON_ACTIVE_PR` never means shipped. -2. A green active PR does not authorize release or change an ADR lifecycle state. -3. A Draft or stacked PR remains dependency-gated even if its own checks pass. -4. Exact heads and workflow run identifiers are volatile evidence and belong in dated appendices such as this one, not in timeless Architecture/PRD/TRD claims. -5. After an active PR integrates, canonical PRD/TRD/Architecture/UML/ERD/traceability must be re-evaluated from the new protected-main head before reclassifying the capability. -6. A formatting-only or metadata-only correction invalidates predecessor-head exactness: current-head checks must be rerun before a lane is called gate-clean. diff --git a/docs/evidence/2026-08-11-active-pr-maturity-closure.md b/docs/evidence/2026-08-11-active-pr-maturity-closure.md deleted file mode 100644 index b623f847f..000000000 --- a/docs/evidence/2026-08-11-active-pr-maturity-closure.md +++ /dev/null @@ -1,61 +0,0 @@ -# Active pull-request maturity evidence — 2026-08-11 closure - -- **Protected-main anchor:** `67af7c87589edc2039545af335c95064d9b8391c` -- **Canonical documentation verdict:** **DESIGN-SUFFICIENT / PROTECTED-MAIN-PARTIAL** -- **Last exact-current reconciliation:** 2026-08-12 -- **Scope:** volatile active-PR evidence for PR #73 through PR #106, preserving protected-main truth separately from branch-local implementation evidence - -Protected `main` remains the only shipped-code authority. This appendix records volatile exact-head evidence for active work and must never be read as protected-main implementation, approval, merge, or release evidence. A moved head or prerequisite invalidates the corresponding exact evidence until refetched. - -## Exact-current active lanes - -| PR | Scope | Maturity | Exact evidence / authority boundary | -|---|---|---|---| -| #73 | Bounded Chromium process-tree RSS evidence in the controlled pinned-browser fixture | **IMPLEMENTED_ON_ACTIVE_PR** | Exact head `6ba2d03345fa3153230a7d365fba8284696541a1`, stacked on exact #72 head `e0b5d43c3a869605aaefa2e4752de7b1b641ddbd`; CI `31550050786` and Manifest V3 Compatibility `31550050775` succeeded after non-destructive dependency alignment to current #72. Optional/nonresident `VmRSS` remains representable while malformed/ambiguous evidence fails closed. This is controlled Linux CI evidence, not trusted whole-task process attribution, cgroup authority, GPU/VRAM ownership, or cross-platform telemetry. | -| #74 | Separation of extension proposal-grant evaluation from ordinary Agent action policy | **IMPLEMENTED_ON_ACTIVE_PR** | Exact head `0d492564aa61c9094f1315ee4e234b46a1e63a6c`, directly based on protected main; CI `31464388199`, Security Scan `31464388200`, and SAST Semgrep `31464388210` succeeded. The branch proves independent fail-closed evaluators and does not claim a real extension-message → `ActionRequest` adapter. | -| #75 | Exact sensitive-model route admission across provider/model/region/retention/training/subprocessor/export policy | **IMPLEMENTED_ON_ACTIVE_PR** | Exact head `286f92aae9e298ab7dff1fd81c7850aabd5692ce`, stacked on #69; CI `31474904239` succeeded with exact owned production coverage. Route admission remains metadata policy only and does not disclose protected values, authenticate/invoke a provider, attest runtime region, or execute export. | -| #76 | Extension proposal authority composed with ordinary typed-action policy | **IMPLEMENTED_ON_ACTIVE_PR** | Exact head `3d2fff3daa766e5e6d7f25e7727a18e01ff52a2e`, stacked on #74; CI `31472688287` succeeded. `evaluate_extension_action_proposal` preserves ordinary instruction-source, capability, origin, secret-delivery, risk, approval, mode, purpose, and crawler policy instead of minting them from extension transport. No Chromium message adapter or browser execution is claimed. | -| #77 | Reviewed prompt/output-schema and token-budget policy after exact model-route admission | **IMPLEMENTED_ON_ACTIVE_PR** | Exact head `adb67f8de3e4828db14dfa0e2950b672b60709c5`, stacked on #75; CI `31477512549` succeeded. This is invocation metadata admission, not protected-value disclosure, provider execution, output validation, retention enforcement, fallback, or export. | -| #78 | Raw extension-message action proposals forced into untrusted content provenance | **IMPLEMENTED_ON_ACTIVE_PR** | Exact head `3fd7d563d814a895e20d04fc6bd37371e548a875`, stacked on #76; CI `31477648663` succeeded. The raw proposal exposes no instruction-source selector and is internally classified as `InstructionSource::WebContent`; no transport parsing/authentication or browser execution is claimed. | -| #79 | Exclusive freshness for reviewed model-invocation policy | **IMPLEMENTED_ON_ACTIVE_PR** | Exact head `2ad7a2162b4842fe57f74f69f08b258f4f6a9c07`, stacked on #77; CI `31481128812` succeeded. Authorization requires caller-supplied trusted time before the exclusive `valid_until` deadline; this pure policy layer does not read or attest a clock. | -| #80 | Origin-binding for node-state post-condition evidence | **IMPLEMENTED_ON_ACTIVE_PR** | Exact head `55b1421e25c5b68ca5f3b05fab37db8f4f1e22be`, stacked on #64; CI `31485218503` succeeded. `NodeStateChanged` provenance must match the governed target origin. This does not prove browser dispatch, node/frame identity, trusted clock provenance, or a real observer. | -| #81 | Fail-closed unrelated-conversation-history metadata for sensitive model invocation | **IMPLEMENTED_ON_ACTIVE_PR** | Exact head `0ec604deb1c0293008560e0fcd4af7ccb65d93ad`, stacked on #79; CI `31484982600` succeeded. Any positive `unrelated_history_items` count is denied. The trusted broker must derive this from the actual bounded outgoing message set; a supplied zero is not proof of isolation. | -| #82 | Exact extension-ID + native-messaging-host allow-list authority | **IMPLEMENTED_ON_ACTIVE_PR** | Exact head `28593cf991cc552968da54b722a887252a3695e7`, directly based on protected main; CI `31484721598`, Manifest V3 Compatibility `31484721575`, Security Scan `31484721547`, and SAST Semgrep `31484721542` succeeded. The primitive does not launch a process, parse native-host manifests/messages, communicate over stdio, expose secrets, or grant Agent actions. | -| #83 | Reduced-assurance classification for attached human tabs with known extension influence | **IMPLEMENTED_ON_ACTIVE_PR** | Exact head `204cedb1bee54a40a6d6bc0b97719afd309c3f71`, stacked on exact #82 head `28593cf991cc552968da54b722a887252a3695e7`; CI `31536812406` and Manifest V3 Compatibility `31536812367` succeeded after non-destructive dependency-topology alignment. `NoKnownExtensionInfluence` is explicitly uncertainty-safe and is not proof that extensions are absent or unable to interfere. The PR does not detect installed extensions or attach to a browser. | -| #84 | Separate model-output validation and retention-policy admission | **IMPLEMENTED_ON_ACTIVE_PR** | Exact head `62f69cadbe0b4011fec67f9e482b04c4cacf181b`, stacked on #81; CI `31487969844` succeeded with exact owned production coverage. This is deterministic metadata policy only; it does not inspect output bytes, execute schema validation, persist output, enforce deletion/retention, or attest validator identity. | -| #85 | Managed extension admission for isolated Agent Task profiles | **IMPLEMENTED_ON_ACTIVE_PR** | Exact head `e836e833be920da8764d3dd72e058e02cd9ed72b`, stacked on exact #83 head `204cedb1bee54a40a6d6bc0b97719afd309c3f71`; CI `31536909167` succeeded with exact owned production coverage after dependency-topology alignment. `AgentTaskExtensionPolicy` admits only exact canonical extension IDs and an empty policy denies all. Admission does not install/enable an extension, read enterprise policy, verify signatures/update provenance, mutate a profile, or mint any Agent capability. | -| #86 | Fail-closed sensitive-model fallback selection | **IMPLEMENTED_ON_ACTIVE_PR** | Exact head `a2c391a5e038dc9e3d6978885d9bdd943487294f`, stacked on #84; CI `31495714541` succeeded with exact owned production coverage. Primary route-policy mismatch and unknown/unreviewed fallback fail closed; only a separately reviewed exact fallback route can be selected. This does not probe provider health, invoke models, or execute retries. | -| #87 | Freshness lifetime for model-route availability evidence | **IMPLEMENTED_ON_ACTIVE_PR** | Exact head `b1273d7bc34fffee262be4bd2da24c24520d11db`, stacked on #86; CI `31500975874` succeeded with exact owned production coverage. Availability uses an exclusive validity horizon and caller-supplied trusted time; stale or invalid availability cannot drive fallback. The policy does not establish collection time or clock/provider-health provenance. | -| #88 | Exact route binding for fallback availability evidence | **IMPLEMENTED_ON_ACTIVE_PR** | Exact head `121d9d70d6c5592b9dff06d7ba09ee563958bfef`, stacked on #87; CI `31505770116` succeeded. Availability evidence retains the exact provider/model/region/retention/training/subprocessor/export route it describes; cross-route replay fails closed before freshness/state can influence fallback. Runtime route authenticity remains a trusted-adapter responsibility. | -| #89 | Full-field sensitive-model disclosure authority composition | **IMPLEMENTED_ON_ACTIVE_PR** | Exact head `f2fbcae3f07cad722f43580aa5be0b4e691d2a9c`, stacked on #88; CI `31510796392` succeeded with exact owned production coverage. Only explicit `FullFieldDisclosure` plus the same complete sensitive-data authority and independently authorized model invocation can yield metadata-level authorization. No protected bytes are carried or released by this primitive. | -| #90 | Explicit necessity gate for full-field model disclosure | **IMPLEMENTED_ON_ACTIVE_PR** | Exact head `166d68bc8e42f41fcd21965609af222e69fd3d4c`, stacked on #89; CI `31517056344` succeeded with exact owned production coverage. `LowerDisclosurePathAvailable` fails closed for handle/deterministic/local-rule/structured-tool/derived-value alternatives. A caller-supplied necessity value is not proof; a trusted broker must derive necessity immediately before protected-value resolution. | -| #91 | Credential-free sensitive-model disclosure audit metadata | **IMPLEMENTED_ON_ACTIVE_PR** | Exact head `3a33f83af7398038a2581e2e132fabf7183b17af`, stacked on #45; CI `31522457974` succeeded and CodeRabbit exact-head status is successful. Evidence records only bounded reviewed provider/model/region/retention/training/subprocessor/export identifiers linked to sensitive-data request/decision IDs. It does not authorize disclosure, prove runtime behavior, or provide durable/tamper-evident audit sequencing. | -| #92 | Fresh resolution authority required for redirect authorization | **IMPLEMENTED_ON_ACTIVE_PR** | Exact head `b796564d059f7bcbd8177617b6fd46c6edc7dda1`, stacked on #47; CI `31524574783` succeeded with exact owned production coverage. `RedirectGuard::authorize_redirect` requires `FreshResolutionSnapshot` plus caller-supplied trusted monotonic time and rejects pre-approval/expired authority before redirect-chain mutation. It performs no DNS lookup, socket I/O, HTTP redirect following, or clock attestation. | -| #93 | Exact semantic-node target bound to independently classified business action | **IMPLEMENTED_ON_ACTIVE_PR** | Exact head `c150e2daa0c890c8e2797ebb4c88a6220be13019`, stacked on #58; CI `31534945009` and Manifest V3 Compatibility `31534945000` succeeded with exact owned production coverage. `SemanticNodeActionBinding` requires the observed node origin to equal the business request source origin while leaving destination/business risk separate. It does not authorize policy or execute browser input. | -| #94 | Freshness lifetime for managed Agent Task extension admission | **IMPLEMENTED_ON_ACTIVE_PR** | Exact head `759d2f04d81dcf52bca29b88d860fb9aaeca56e8`, stacked on #85; CI `31541736861` succeeded. The policy uses one caller-attested half-open validity window and fails closed for invalid, not-yet-valid, or expired policy. It does not read enterprise policy or attest a clock/profile. | -| #95 | Deterministic policy authorization of one semantic-node/business-action binding | **IMPLEMENTED_ON_ACTIVE_PR** | Exact head `e26a2d07ae731ff35271299036fa1f43c8550039`, stacked on #93; CI `31544103569` succeeded with exact owned production coverage. Only `Decision::Allow` produces `PolicyAuthorizedSemanticNodeAction`; deny and approval-required remain typed non-authorizing outcomes. Policy allow is still not browser execution authority. | -| #96 | Same-call browser-authority revalidation immediately before adapter dispatch callback | **IMPLEMENTED_ON_ACTIVE_PR** | Exact head `c93b90a316b83a160cf80008cc25c78aa32302f9`, stacked on #95; CI `31549013124` succeeded. An earlier exact head exposed one generic-monomorphization coverage miss at `4266/4267` lines and `5569/5570` regions; the current coverage repair drives current and stale epochs through one callback call site and restores exact production function/line/region/branch coverage without changing production behavior. The callback remains a trusted-adapter integration boundary, not Chromium execution or success proof. | -| #97 | Managed Agent Task extension policy bound to one OriginWeave browser session | **IMPLEMENTED_ON_ACTIVE_PR** | Exact head `912e0909169ed2fee1b26bce126f14e9390822bd`, stacked on exact #94 head `759d2f04d81dcf52bca29b88d860fb9aaeca56e8`; CI `31546164773` succeeded with exact owned production coverage. Session mismatch fails before allow-list membership is considered, preventing cross-session policy replay/membership probing. This does not prove Chromium profile identity, enterprise-policy provenance, extension installation state, or Agent capability. | -| #99 | Exact target-node binding for node-state success evidence | **IMPLEMENTED_ON_ACTIVE_PR** | Exact head `8ec18b8701104cf3f3764334601c69c1497297b9`, stacked on exact #80 head `55b1421e25c5b68ca5f3b05fab37db8f4f1e22be`; CI `31551314274` succeeded with exact owned production coverage. Generic outcome construction now rejects `NodeStateChanged`; the node-specific constructor requires the governed and independently observed `ObservedNodeHandle` to match across session, context, canonical origin, document epoch, and node identifier. This prevents a different same-origin node from proving the target's post-condition, but does not authenticate a browser adapter or observe the condition itself. | -| #100 | Bounded semantic role/name discovery in the controlled pinned-Chromium Agent Task | **IMPLEMENTED_ON_ACTIVE_PR** | Exact head `13f49b7fc4f11d0fd851f51d816dc0cc94003b91`, stacked on exact #73 head `6ba2d03345fa3153230a7d365fba8284696541a1`. Test-only predecessor `e0e4cef2546c0564ba86b6301a3375656ed988ae` established the missing semantic-locator repository-contract RED. The current implementation enumerates at most 128 controlled WebDriver candidates, reads browser-computed role/name, requires exactly one exact match for the input and submit controls, removes direct CSS target discovery from the Agent Task action path, and adds focused exact/zero/duplicate/oversized/malformed candidate regressions. CI `31553583901` and Manifest V3 Compatibility `31553583902` succeeded on the current head with exact owned production coverage. This fixture helper is not the versioned production WebDriver BiDi adapter and does not make CSS enumeration product authority. | -| #101 | Reject known-disabled semantic-node interactive action targets | **IMPLEMENTED_ON_ACTIVE_PR** | Exact head `bd75a43ddcd0a7afa4f032ecc2b930d742c3ece5`, stacked on exact #96 head `c93b90a316b83a160cf80008cc25c78aa32302f9`; CI `31552510321` and Manifest V3 Compatibility `31552510348` succeeded. `SemanticNodeActionTarget::from_observation` now rejects a known-disabled interactive action as typed `NodeNotEnabled` while retaining `ScrollIntoView` because scrolling does not require node-enabled state. This validates only the supplied semantic observation; current enabled state immediately before dispatch remains a trusted-adapter responsibility. | -| #102 | Revalidate a semantic action target against one freshly supplied current semantic observation | **IMPLEMENTED_ON_ACTIVE_PR** | Exact head `c751865412d5642357203f917f16c6c2bbd12324`, stacked on exact #101 head `bd75a43ddcd0a7afa4f032ecc2b930d742c3ece5`; CI `31554288115` and Manifest V3 Compatibility `31554288335` succeeded. The core target rejects a different exact node, removal of the selected action, or newly disabled state for an action that requires enabled state. The trusted runtime still owns re-observation timing and provenance; this method does not observe Chromium or dispatch input. | -| #103 | Same-call current semantic-state revalidation before a policy-authorized adapter callback | **IMPLEMENTED_ON_ACTIVE_PR** | Exact head `c4c32d4305d6485a5e9f2bf202316b216d95f71f`, stacked on exact #102 head `c751865412d5642357203f917f16c6c2bbd12324`; CI `31556233043` succeeded with exact owned production coverage and exact-head CodeRabbit status success. `dispatch_if_current_observation` requires exact target-node identity, retained selected action, and required enabled state before invoking the callback. It does not obtain/authenticate the observation, execute Chromium by itself, authorize later authorities, or prove a post-condition. | -| #104 | Structured-value digest bound to exact node plus verified node/network provenance | **IMPLEMENTED_ON_ACTIVE_PR** | Exact head `b05cf7d5d45f8772fa8ea9f23cdd99d51a599f9e`, stacked on exact #99 head `8ec18b8701104cf3f3764334601c69c1497297b9`; CI `31557992269` succeeded with exact owned production coverage. `StructuredValueEvidence` binds one bounded semantic field identifier and canonical lowercase SHA-256 digest to the exact OriginWeave node plus independently verified DOM/accessibility and `NetworkResponse` provenance from the same canonical origin. It carries no raw extracted value and does not authenticate adapters or persist evidence. | -| #105 | Controlled pinned-Chromium result discovered semantically and emitted only as bounded digest evidence | **IMPLEMENTED_ON_ACTIVE_PR** | Exact head `bc1d22d6c4848a173c55fdd18054574299488067`, stacked on exact #100 head `13f49b7fc4f11d0fd851f51d816dc0cc94003b91`; CI `31559777436` and Manifest V3 Compatibility `31559777419` succeeded. The result is found by browser-computed `status` / `Task result` semantics and trial evidence retains only `task_result` plus a canonical SHA-256 digest; the raw controlled input is absent from emitted JSON evidence. This is executable compatibility evidence, not production adapter or complete source/network provenance. | -| #106 | Versioned browser-protocol adapter metadata and explicit canonical capability set | **IMPLEMENTED_ON_ACTIVE_PR** | Exact head `7a3e8f4689a8b3c0344a250f45ec06995473d21f`, stacked on exact #40 head `9e635e80e9813a1d2a9c408155d52221b76eeed3`; CI `31563580047` and Manifest V3 Compatibility `31563580032` succeeded with exact owned production coverage and exact-head CodeRabbit status success. The descriptor records explicit protocol kind plus bounded adapter/protocol/browser revisions and a non-empty duplicate-free capability set. After test-only RED head `8d2549cbad8cdabfe09a5ee61aa7a7ee1de81cc8` proved caller order changed descriptor identity, the current head normalizes capabilities into one stable rank order. Protocol kind remains descriptive and does not infer support or grant browser/Agent authority. | - -## Documentation-fitness reconciliation - -The repository-wide documentation verdict remains **DESIGN-SUFFICIENT / PROTECTED-MAIN-PARTIAL**. - -- **ADR:** no additional ADR is warranted solely by #73–#106. The browser/extension slices refine authority separation and the Proposed browser-protocol/session/extension target architecture; the sensitive-model slices make existing selective-disclosure architecture more executable without introducing a deployed broker/provider/validator/retention service, persistence owner, or new binding protocol. Proposed ADRs remain Proposed until an explicit protected-main lifecycle transition occurs; branch presence, CI, and policy helpers do not promote them. -- **PRD/TRD/Architecture:** current contracts already require resource evidence to remain distinct from trusted attribution; extension/browser permission not to mint Agent capability, origin, approval, secret, execution, or native-host authority; extension content/messages to remain untrusted; attached-tab extension influence to reduce assurance without converting absence of known evidence into high assurance; semantic-node authority to remain bound to exact browser session/context/origin/document epoch and separately classified business intent; policy authorization to remain distinct from immediate dispatch revalidation and observed success; target discovery to be semantic rather than selector authority for the controlled browser workflow; known-disabled interactive targets to fail closed; a fresh semantic observation to revalidate exact target identity/action/enabled state before use; node-state success evidence to bind the exact governed node rather than any same-origin node; structured extracted evidence to carry no raw value and bind exact node/network provenance; browser protocol kind to remain descriptive rather than implicitly granting capabilities and descriptor identity to be independent of caller capability ordering; resolution freshness immediately before network/redirect use; and AI disclosure to bind necessity, authority, exact route, prompt/schema/tokens, expiry, conversation isolation, fallback availability/route, output validation, retention, and credential-free audit metadata independently from protected-value disclosure. #73–#106 refine these boundaries without changing deployed topology. -- **UML:** existing browser/extension authority, sensitive-data/secret-fill, evidence, destination/network, and product-wide authority views remain sufficient for these policy/value primitives. A detailed production Chromium adapter → versioned protocol capability boundary → semantic observation/discovery → typed policy authorization → immediate exact semantic-state revalidation → real input → exact-node structured post-condition/provenance/recovery/resource-evidence sequence remains mandatory when that executable adapter boundary stabilizes. A trusted sensitive-data broker/provider/validator/retention sequence likewise remains future work until those deployed authorities exist. -- **ERD/data model:** none of #73–#106 introduces OriginWeave-owned durable persistence, migrations, physical ownership/cardinality changes, or rollback state. The conceptual ERD remains truthful. Physical process-sample, extension-policy, native-host, assurance, semantic-node locator/dispatch/outcome, structured-value, browser-adapter, model-route/invocation/fallback/output/audit, broker, provider, validator, retention, clock, or redirect tables would be invented architecture until an actual persistence owner is accepted and implemented. -- **Security/test/release:** exact active heads with recorded GREEN evidence remain branch-local only. Stacked Drafts remain dependency-blocked even when their exact branch checks are green. CodeRabbit success or a skipped Draft review is not independent approval, and predecessor-head success never transfers after head/base movement. -- **Traceability:** this appendix extends exact-current non-shipped evidence through #106, corrects #103 to its exact-current GREEN evidence, records #104/#105/#106 as branch-local implemented evidence, and continues to exclude closed PR #98 after fresh comparison proved its proposed explicit extension instruction-source enum duplicated the narrower active #78 raw-message boundary. Every active-PR statement remains subordinate to protected-main code/contracts and becomes historical immediately when its recorded head or prerequisite moves. - -## Truth boundary - -`IMPLEMENTED_ON_ACTIVE_PR` means only that the exact branch contains the stated behavior with the recorded branch-local evidence; it does not mean shipped. A controlled Chromium runner or role/name locator is not the product browser adapter. A sampled process tree is not trusted whole-task ownership. Extension admission, proposal permission, attached-tab assurance, or native-host grant is not Agent action authority. A browser-protocol descriptor is metadata, not authenticated browser transport or capability authority. A semantic-node binding, enabled-state check, current-observation revalidation, or deterministic policy allow is not browser execution or observed success. Exact-node and structured-value outcome evidence still depend on a trusted adapter to supply real observations and provenance. A fresh resolution value is not proof that DNS or the clock is trusted. Model route, invocation, necessity, availability, fallback, output-policy, and audit-metadata decisions are not protected-value disclosure, provider authentication/execution, runtime region attestation, output-byte validation, retention/deletion enforcement, or durable audit storage. Protected-main maturity changes only after dependency-ordered integration and fresh protected-main acceptance. \ No newline at end of file diff --git a/docs/evidence/2026-08-11-active-pr-maturity-delta.md b/docs/evidence/2026-08-11-active-pr-maturity-delta.md deleted file mode 100644 index 703cb6ed8..000000000 --- a/docs/evidence/2026-08-11-active-pr-maturity-delta.md +++ /dev/null @@ -1,57 +0,0 @@ -# Active pull-request maturity evidence — 2026-08-11 delta - -- **Protected-main anchor:** `67af7c87589edc2039545af335c95064d9b8391c` -- **Canonical documentation verdict:** **DESIGN-SUFFICIENT / PROTECTED-MAIN-PARTIAL** -- **Relationship to the existing series:** this file advances the dated evidence in [`2026-08-10-active-pr-maturity.md`](2026-08-10-active-pr-maturity.md) for active lanes opened after that appendix was refreshed through PR #66. It is volatile implementation evidence, not timeless architecture truth. - -Protected `main` remains the only shipped-code authority. Active pull requests, exact heads, CI runs, reviews, and coverage reports are evidence about non-shipped work until dependency-ordered integration and fresh protected-main acceptance are re-established. - -## Newly active implementation evidence - -| PR | Scope | Maturity | Exact evidence / authority boundary | -|---|---|---|---| -| #67 | Browser-task interruption and recovery evidence | **IMPLEMENTED_ON_ACTIVE_PR** | Exact head `9d9ebffee234ed4ab662dab7850bd08450ec365b` is stacked on unchanged #64 head `2c45411ed9aa0eecca2d06c85659db9f4bb85e4d`. CI run `31448465680` is successful, including exact owned production function/line/region/branch coverage. The value contract distinguishes an interruption proven before external effect from an effect that may have committed and requires browser-context closure, task-resource reclamation, and evidence finalization before `SafeToRetry`. It does **not** detect Chromium crashes, prove caller-supplied cleanup facts, reconcile external mutations, restart Chromium, dispatch a retry, persist checkpoints, or complete issue #28's real-browser vertical slice. | -| #68 | Identity-bound settlement of failed sensitive-handle reservations | **IMPLEMENTED_ON_ACTIVE_PR** | The lane is stacked on exact #55 head `8d3ccf0a3b99fd9789210dd9798b422431fab7d8`. Exact predecessor head `add3599bee784c58dfaa4275d17c477eaed781a9` passed repository contracts, formatting, workspace tests, strict Clippy and rustdoc but failed exact coverage at `branches=495/496`, `lines=3666/3667`, `regions=4575/4576`. The uncovered production `next_reservation_sequence == None` branch was synthetic/private-test-only, so the production design was replaced rather than weakening the gate. Exact head `17bc00790e75424afd97c8a73800d9b16c766300` replaced the finite sequence with an allocation-bound, non-copyable in-process reservation identity and passed CI `31451682170`. Current exact head `aa46d982b2bf786fe297744ac99f88b6c4c5f4cf` additionally proves a reservation token from one state instance cannot commit or compensate another identical-scope state. Fresh CI run `31451963178` succeeds: repository contracts, formatting, locked workspace/all-target checks, full tests, strict Clippy, rustdoc, and exact owned production function/line/region/branch coverage are green; CodeRabbit exact-head status is also success. The lane still provides no authenticated workload identity, protected-value resolution, durable/cross-process transaction, KMS, persistence, or proof that compensation is truthful. | -| #69 | Immediate pre-disclosure recheck of an exact tracked reservation | **IMPLEMENTED_ON_ACTIVE_PR** | This Draft is stacked on exact #68 head `aa46d982b2bf786fe297744ac99f88b6c4c5f4cf`. Test-only head `5a96d2931225e133768878c68d09e1a36b5ca0f6` established the intended missing-API RED. Production then exposed a real coverage defect in short-circuit recheck branches; focused malformed caller authority/audience cases were added, and two duplicate unreachable immutable-state checks were removed rather than manufacturing private-only coverage. Current exact head `de79d85e6be5131036db119efab767f0eb76a816` passes CI run `31453149013`, including exact owned production function/line/region/branch coverage, and CodeRabbit exact-head status is successful. The boundary rechecks the same still-outstanding reservation immediately before disclosure without consuming another use, but still trusts the future broker to supply authenticated workload audience, trusted time, exact current authority, transactional serialization and the protected-value disclosure boundary. | -| #70 | Controlled Agent Task execution on pinned stock Chromium | **IMPLEMENTED_ON_ACTIVE_PR** | This Draft is stacked on exact #65 head `0888fe3a6ef6da547a37fd075733cc73dc52b2ab`. Test-only head `197ce14a5e407d61ac35b38b45c0cd042dd6278c` established the intended missing-runner RED. Current exact head `f9917cdd8050c9fdf0aefa669f4d981af85479d6` passes CI run `31453647157` and pinned real-browser run `31453647201` against Chrome for Testing `150.0.7871.129` / revision `r1639810`. The controlled Agent Task completes `3/3` trials with real WebDriver clear/type/click operations, exact same-document post-condition verification, extensions disabled, and per-trial profile cleanup. This is reproducible browser execution evidence, not the product browser adapter: fixture CSS locators are test-harness locators, no semantic role/name query authority is claimed, and OriginWeave semantic observation/policy/node-handle composition remains incomplete. | -| #71 | Computed semantic role/name evidence before controlled browser action | **IMPLEMENTED_ON_ACTIVE_PR** | This Draft is stacked on unchanged exact #70 head `f9917cdd8050c9fdf0aefa669f4d981af85479d6`. Exact test-only head `977d2682dc191ca6b26b9de631a3642680abdbc0` produced the intended RED in CI run `31454219111`, Rust contracts job `93664601520`, because the runner had no `_get_element_semantics` boundary. Current exact head `5f1f972f3e9888faa44af184fd54a466d20b6ddb` adds the smallest bounded W3C WebDriver computed-role/computed-label verification before the controlled input and submit actions. CI run `31454448709` succeeds, including exact owned production function/line/region/branch coverage, and pinned real-browser run `31454448710` succeeds on Chrome for Testing `150.0.7871.129` / revision `r1639810`: all `3/3` Agent Task trials report browser-computed `textbox` / `Task text` and `button` / `Submit task` verification, exact post-condition and input echo, extensions disabled, and profile cleanup. CodeRabbit exact-head status is successful. CSS remains a controlled harness locator; this evidence does not itself implement semantic role/name search, a versioned product adapter, OriginWeave node registration/observation composition, policy dispatch, source provenance or real-site compatibility. | -| #72 | Controlled Agent Task runtime resource evidence | **IMPLEMENTED_ON_ACTIVE_PR** | This Draft is stacked on unchanged exact #71 head `5f1f972f3e9888faa44af184fd54a466d20b6ddb`. Exact test-only head `a9402a13c9ed429b8f3be2c623b994a0dfda3bb4` produced the intended RED in CI run `31454745237`, Rust contracts job `93666110420`, because strict Linux RSS parsing/sampling was absent. Current exact head `1a7186085abe926c1d0e5b22c36760965d6e237b` adds bounded `/proc//status` sampling for the ChromeDriver-issued browser PID, exact serialized semantic-observation bytes, monotonic action latency, and task duration. CI run `31454903615` succeeds, including exact owned production function/line/region/branch coverage, and pinned real-browser run `31454903620`, job `93666566904`, succeeds on Chrome for Testing `150.0.7871.129` / revision `r1639810`. All `3/3` trials pass with browser-process RSS `215326720`, `214568960`, and `213716992` bytes; semantic observation size `95` bytes each; action latency `133.282`, `130.186`, and `110.554` ms; and task duration `1144.545`, `957.991`, and `906.982` ms. Artifact ID `9087662526` has uploaded-artifact SHA-256 `a7f8ec5ae716ed723e9dd7ec84eeac3478c30fddd6eb7c9bf0d411f1a8990ee5`; CodeRabbit exact-head status is successful. The RSS metric intentionally covers only the ChromeDriver-reported browser process, not renderer/GPU/utility descendants or whole-task attribution; full trusted process-set composition remains pending the product adapter and #51/#66 contracts. | - -## Documentation-fitness reconciliation - -The addition of #67 through #72 does **not** require another ADR, a new deployed component, or a physical ERD entity at this stage. - -- **ADR:** #67 refines the existing evidence/recovery architecture. #68/#69 refine the in-process sensitive-handle lifecycle governed by Accepted ADR 0007. #70–#72 add executable compatibility/runtime evidence inside the already planned browser-adapter and resource-evidence boundaries. None changes a trust domain, persistence owner, deployment boundary, or binding protocol decision; existing ADR breadth remains sufficient. -- **PRD/TRD:** current requirements already distinguish post-condition evidence from browser dispatch, semantic observation from action authority, resource evidence from trusted process attribution, and purpose-bound sensitive policy from the future trusted broker. #67–#72 remain active/non-shipped evidence and must not be described as `Implemented` on protected main. -- **Architecture/UML:** #70 materially strengthens executable proof that stock pinned Chromium can perform the controlled task, #71 binds the controlled targets to browser-computed semantic evidence before action, and #72 adds measured resource evidence for that controlled browser execution. None creates the versioned WebDriver BiDi/CDP product adapter or composes the existing OriginWeave semantic-node/policy/evidence/resource primitives end to end. The current high-level authority diagrams remain truthful; a detailed adapter → semantic observation → typed policy/action → post-condition/recovery/resource-evidence sequence becomes mandatory when that production composition boundary stabilizes rather than while the evidence runner remains the execution owner. -- **ERD/data model:** #67 is an immutable evidence value, #68/#69 are explicitly in-process policy state, and #70–#72 are ephemeral CI/browser evidence. None creates an OriginWeave-owned durable persistence schema. The conceptual ERD remains the truthful artifact; manufacturing physical tables would overstate the implementation. -- **Security/privacy:** #67 quarantines ambiguous-effect/incomplete-cleanup states. #68/#69 preserve exact reservation identity and immediate pre-disclosure recheck without exposing protected values. #70 uses synthetic local data, disables extensions in the Agent Task profile, and proves profile cleanup. #71 is intentionally limited to bounded browser-computed role/name evidence and does not elevate page content into instruction or capability authority. #72 reads only bounded Linux process status for a ChromeDriver-issued PID and emits resource measurements without credentials or page values. -- **Test/release/traceability:** #67–#72 have fresh exact-head green evidence at this refresh. #71/#72 preserve their observed test-first RED before exact-head GREEN. No predecessor-head success transfers across any moved head, and none of these active lanes is release evidence for protected `main` yet. - -## Interpretation rules - -1. `IMPLEMENTED_ON_ACTIVE_PR` and `PARTIAL` never mean shipped. -2. Exact-head CI/coverage evidence becomes stale immediately when that head moves. -3. A stacked PR cannot be independently integrated before its exact prerequisite lineage. -4. An active implementation refinement does not manufacture a new ADR merely to mirror every PR; create or supersede an ADR only when the governing architecture decision changes. -5. In-memory identities, immutable evidence values, controlled fixtures, bounded samplers, and ephemeral compatibility/resource evidence do not justify physical ERD entities without a real durable ownership boundary. -6. A real browser test harness is not the same authority as the production browser adapter. Promote browser/runtime maturity only when the protected-main product path owns session/context/origin/document identity, semantic observation, typed policy/action dispatch, post-condition verification, recovery, and evidence composition. -7. A single ChromeDriver-reported browser PID is not equivalent to trusted Chromium process-set or task attribution. Whole-browser/task RSS claims require an adapter-owned process set and the existing bounded aggregation contracts. -8. After any of these lanes integrates, re-evaluate PRD/TRD/Architecture/UML/ERD/traceability from the new protected-main head before changing maturity claims. - -## Subsequent active lanes observed in this refresh - -| PR | Scope | Maturity | Exact evidence / authority boundary | -|---|---|---|---| -| #73 | Bounded Chromium root-plus-descendant RSS evidence in the controlled pinned-browser fixture | **PARTIAL** | This Draft remains stacked on unchanged exact #72 head `1a7186085abe926c1d0e5b22c36760965d6e237b`. Earlier exact head `cbf922fccc83782d3e114ed65afbeb6d84ef5ce6` repaired nondeterministic handling of a sampled descendant with no resident `VmRSS` and passed exact CI/coverage plus pinned-browser repeatability, but a subsequent integrity audit found a narrower fail-open ambiguity: `_snapshot_linux_process_evidence` currently catches the strict parser's `exactly one VmRSS` failure and converts it to `None`, so duplicate/ambiguous `VmRSS` records can be normalized to the same absence state as a legitimately nonresident process. Exact test-only head `015e4a5f79c0abee40c6807b481d3afce613c6c4` required a dedicated optional-RSS parser in which absent/zero `VmRSS` yields `None`, exactly one positive field yields bounded bytes, and duplicate/malformed evidence fails closed. CI run `31462156163`, Rust contracts job `93687687157`, checked out that exact test head and produced the intended RED at the missing helper boundary with `KeyError`. Current exact head `ef6f23365f225b825505a58556d6917aeef505a2` removes only the temporary RED probe so the prerequisite stack is not intentionally left failing: CI run `31462292887`, Rust contracts job `93688085184`, Production coverage job `93688085247`, and Manifest V3 Compatibility run `31462292914` are successful. Those green results do not erase the integrity finding. This lane remains **PARTIAL** and must stay Draft until the snapshot source distinguishes legitimate absent/nonresident RSS from duplicate/malformed evidence, the focused regression is restored, and the exact corrected head passes both repository and real-browser gates. It is still controlled Linux `/proc` evidence, not trusted product process ownership, cgroup/per-tab/task attribution, GPU/VRAM attribution, or cross-platform semantics. | -| #74 | Extension proposal permission cannot widen Agent mutation, execution-mode/purpose, crawler/robots, non-delegable-action, or Human-mode authority | **IMPLEMENTED_ON_ACTIVE_PR** | Exact head `ac8b27ee69229070c382ca2199eaf9ec8b1b12db` is based directly on protected main. CI run `31462551154` succeeds and SAST Semgrep run `31462551097` succeeds; Security Scan run `31462551096` is still non-terminal and is **not** counted as passing. Nine integration regressions first prove that the exact extension/session/context grant permits `ProposeTypedAction`, then prove ordinary Agent policy still denies cross-origin `Submit` as `CrossOriginMutation`, same-origin `Submit` without write authority as `OriginNotWritable`, Crawler/PublicCrawl mutation as `CrawlerMutation`, AgentTask/PublicCrawl as `ModePurposeMismatch`, crawler observations with disallowed/unknown/not-applicable robots evidence as `RobotsDisallowed`/`RobotsUnknown`/`RobotsNotApplicable`, R5 `LegalConsent` as `ForbiddenRisk`, and Human mode as `HumanModeNotAgentControlled`. GitHub reports the PR mergeable and no inline review threads are currently returned. This adds no production API or extension authority and does not claim a real Chromium extension adapter exists; exact-current security acceptance remains pending until every required gate is terminal-success. | - -## Reconciliation for #73 and #74 - -The canonical verdict remains **DESIGN-SUFFICIENT / PROTECTED-MAIN-PARTIAL**. - -- **ADR:** neither lane creates a new governing decision. #73 refines controlled evidence inside the existing resource/browser-adapter direction; #74 verifies the already documented separation between extension proposal permission and Agent action/origin/risk/control authority. No new ADR number should be allocated solely to mirror either PR. -- **PRD/TRD/Architecture:** the current contracts already require browser resource evidence to remain distinct from trusted attribution and require extension access not to imply Agent capability/origin/risk/control authority. #73 is now **PARTIAL** because exact RED evidence demonstrates an unresolved snapshot-integrity ambiguity even though the restored branch is green. #74 remains `IMPLEMENTED_ON_ACTIVE_PR` test evidence, but exact-current security acceptance is not complete until all required security gates are terminal-success. Neither is protected-main implementation. -- **UML:** the existing extension-authority view remains sufficient for #74 because no new actor, trust boundary, or execution edge is introduced. #73 remains an ephemeral CI evidence path and does not justify presenting `/proc` process lineage as a product deployment/authority relationship. -- **ERD/data model:** neither lane introduces durable OriginWeave-owned persistence, ownership, cardinality, or migration semantics. The conceptual ERD remains the truthful current artifact. -- **Security/test/release:** #73 preserves both its earlier functional RED→GREEN chain and the newer exact RED proving the evidence-integrity gap; the current green restoration is not a substitute for the source correction. #74 strengthens policy-composition regression evidence without widening authority, while its current security gate set remains incomplete. Neither lane is protected-main release evidence. diff --git a/docs/evidence/2026-08-12-active-pr-maturity-delta.md b/docs/evidence/2026-08-12-active-pr-maturity-delta.md deleted file mode 100644 index af997487f..000000000 --- a/docs/evidence/2026-08-12-active-pr-maturity-delta.md +++ /dev/null @@ -1,30 +0,0 @@ -# Active pull-request maturity delta — 2026-08-12 - -- **Protected-main anchor:** `67af7c87589edc2039545af335c95064d9b8391c` -- **Canonical documentation verdict:** **DESIGN-SUFFICIENT / PROTECTED-MAIN-PARTIAL** -- **Supersedes volatile evidence only:** the #103 maturity row and the #73-through-#103 scope boundary in `2026-08-11-active-pr-maturity-closure.md` -- **Extends exact-current evidence through:** PR #104 - -Protected `main` remains the only shipped-code authority. This dated delta updates only volatile active-PR evidence that changed after the prior closure appendix. It does not promote active work to protected-main truth, change an ADR status, invent a deployed service, or introduce an OriginWeave-owned physical persistence schema. A moved head or prerequisite immediately makes the corresponding exact evidence historical. - -## Changed exact-current lanes - -| PR | Scope | Maturity | Exact evidence / authority boundary | -|---|---|---|---| -| #103 | Same-call current semantic-state revalidation before a policy-authorized adapter callback | **IMPLEMENTED_ON_ACTIVE_PR** | Exact current head `c4c32d4305d6485a5e9f2bf202316b216d95f71f`, stacked on exact #102 head `c751865412d5642357203f917f16c6c2bbd12324`. Test-only head `64bc330d801dcaddad29cb907fce68a9de367afa` established the missing dispatch-composition boundary. Production head `2bf613a1d185f6d6dbdc6ede23ebad710d067103` then exposed one generic-monomorphization coverage gap. The current head routes success and semantic rejection through one shared typed helper and applies only the required canonical formatting follow-up. CI `31556233043`, Rust contracts job `93989132459`, and Production coverage job `93989132433` all succeeded with exact owned production function/line/region/branch coverage. The callback remains a trusted-adapter integration boundary: this branch does not obtain/authenticate a browser observation, execute Chromium by itself, authorize later network/secret boundaries, or prove a post-condition. | -| #104 | Structured extracted-value digest bound to one exact node plus verified node/network provenance | **IMPLEMENTED_ON_ACTIVE_PR** | Exact current head `b05cf7d5d45f8772fa8ea9f23cdd99d51a599f9e`, stacked on exact #99 head `8ec18b8701104cf3f3764334601c69c1497297b9`. Formatting-only test head `bb6b2fa09998084ee638ee715ecef6bb0b7e8163` established the valid missing-production RED in CI `31556648278`. Production head `63530aa203dccd94c7a4144186da5208353db5c6` exposed an exact coverage omission limited to the new public error-display paths; coverage artifact `9126295264` localized those misses, and head `139ab7d3ce66245b83b5c7d1245a654387431af9` restored exact coverage. A subsequent current-code audit found that punctuation-only structured field identifiers such as `---` were accepted. Test-only head `4cf977fbe5b41c76f14dfbaae2c4766331f668ef` established that data-integrity RED in CI `31557841624`, Rust contracts job `93993752193`, after repository contracts/format/workspace checks passed. Current head `b05cf7d5d45f8772fa8ea9f23cdd99d51a599f9e` requires at least one ASCII alphanumeric byte in addition to the existing alphanumeric/underscore/hyphen syntax. Exact-current CI `31557992269`, Rust contracts job `93994187786`, and Production coverage job `93994187746` all succeeded with repository contracts, formatting, locked workspace/all-target checks, tests, strict Clippy, rustdoc, and exact owned production function/line/region/branch coverage. `StructuredValueEvidence` carries a semantic bounded field identifier, lowercase SHA-256 value digest, exact OriginWeave-owned node handle, verified DOM/accessibility provenance, and verified network-response provenance; both provenance records must match the node's canonical origin. It carries no raw extracted value and does not authenticate the runtime sources or prove locator truth. | - -## Documentation-fitness reconciliation - -The repository-wide verdict remains **DESIGN-SUFFICIENT / PROTECTED-MAIN-PARTIAL**. - -- **ADR:** no new ADR is warranted solely by #103 or #104. Both refine the already documented browser-authority/evidence architecture without creating a new deployed trust domain, binding protocol, or persistence owner. Proposed ADR 0013/0014 lifecycle status is unchanged. -- **PRD/TRD/Architecture:** the existing contracts already require current semantic state to be revalidated immediately before side effect and evidence to preserve exact source authority without raw sensitive values. #104 makes the structured-value evidence boundary executable by binding an exact node to same-origin verified node/network provenance, and now rejects identifier syntax that contains no semantic alphanumeric content; the trusted runtime still owns extraction, source authentication, digest derivation, and timing. -- **UML:** existing browser/authority/evidence diagrams remain sufficient for these value and composition primitives. The still-missing production sequence is the real Chromium adapter obtaining a fresh bounded semantic observation, policy-authorizing and revalidating it, executing real input, observing the exact post-condition, extracting the structured value, deriving node/network provenance, constructing the evidence bundle, and performing cleanup/recovery. -- **ERD/data model:** #103 and #104 add no OriginWeave-owned durable state, migrations, physical cardinality, or rollback record. The conceptual/logical model remains the truthful artifact. Creating physical semantic-observation or structured-value-evidence tables now would be invented architecture. -- **Security/Test/Release:** both exact active heads are branch-local GREEN evidence only. Their success is not protected-main integration, release acceptance, or independent approval, and their stacked prerequisites remain separate authorities. -- **Traceability:** current volatile active evidence now extends through #104. Closed duplicate #98 remains excluded because #78 owns the narrower raw extension-message trust boundary. The earlier #103 `PARTIAL` row is superseded by the exact-current GREEN evidence above. - -## Truth boundary - -`IMPLEMENTED_ON_ACTIVE_PR` does not mean shipped. Current semantic-state revalidation is not browser execution. A structured-value evidence bundle is not the extraction process, source authentication, or proof that arbitrary locator text identifies the exact real node/network response. No active PR changes protected-main maturity until dependency-ordered integration and fresh protected-main acceptance establish it. \ No newline at end of file diff --git a/docs/evidence/2026-08-12-browser-protocol-active-pr-evidence.md b/docs/evidence/2026-08-12-browser-protocol-active-pr-evidence.md deleted file mode 100644 index aa15e8a08..000000000 --- a/docs/evidence/2026-08-12-browser-protocol-active-pr-evidence.md +++ /dev/null @@ -1,27 +0,0 @@ -# Browser protocol active-PR evidence — 2026-08-12 - -This dated appendix extends the canonical active-PR maturity reconciliation beyond the 2026-08-11 closure. It is **volatile branch evidence**, not protected-main truth. Any recorded head, prerequisite, review, check, or base movement invalidates the affected row until it is refetched. - -The durable architecture decision remains **Proposed ADR 0107**. Nothing in this appendix promotes that ADR to Accepted, authenticates a browser adapter, or claims the first real Chromium vertical slice is complete. - -| PR | Exact active head | Exact prerequisite/base | Capability maturity | Exact-current evidence | Truth boundary | -| --- | --- | --- | --- | --- | --- | -| #107 `feat(core): fail closed on unsupported browser protocol capabilities` | `72efca6acccc66409a9c38cf57e7f4279b2d8c3a` | #106 `7a3e8f4689a8b3c0344a250f45ec06995473d21f` | `IMPLEMENTED_ON_ACTIVE_PR` | CI `31564417535` success; Manifest V3 Compatibility `31564417533` success; no formal reviews or inline review threads returned; mergeable against the exact prerequisite | `BrowserProtocolAdapterDescriptor::require_capability` fails closed when a capability is not explicitly declared. It does not prove that BiDi/CDP transport implements the declared capability, authenticate an adapter, select a fallback protocol, or grant browser/Agent authority. | -| #108 `feat(core): bind browser adapters to OriginWeave protocol version` | `ea45a91230e003babc33fff08acb9ada4b07957a` | #107 `72efca6acccc66409a9c38cf57e7f4279b2d8c3a` | `IMPLEMENTED_ON_ACTIVE_PR` | CI `31565621081` success; Rust contracts `94016710356` success; Production coverage `94016710349` exact function/line/region/branch success; Manifest V3 Compatibility `31565621018` success; CodeRabbit exact-head status success; no formal reviews or inline review threads returned; mergeable against the exact prerequisite | The descriptor carries one exact OriginWeave protocol generation and rejects version mismatch. Current pre-alpha generation is the already documented `originweave/0.1`. This does not serialize/parse an OriginWeave wire envelope, negotiate compatibility, invoke BiDi/CDP, authenticate the adapter, or grant browser/Agent authority. | -| #109 `feat(core): parse canonical OriginWeave protocol versions` | `ea17243bf3e0a332bc4a62e25207c80697fde067` | #108 `ea45a91230e003babc33fff08acb9ada4b07957a` | `IMPLEMENTED_ON_ACTIVE_PR` | Test-only head `ac780f08ff825fae08c64d993eaaab6fe6817e3b` established the missing parser RED in CI `31566958754` / Rust contracts `94020638413`; current CI `31567354585` success; Rust contracts `94021835140` success; Production coverage `94021835149` exact function/line/region/branch success; Manifest V3 Compatibility `31567354512` success; CodeRabbit exact-head status success; no formal reviews or inline review threads returned; mergeable against the exact prerequisite | `OriginWeaveProtocolVersion::from_str` now accepts only the canonical `originweave/.` rendering and rejects malformed/noncanonical serialized generations. Syntax parsing does not negotiate compatibility, decide support, authenticate transport/adapters, invoke BiDi/CDP, or grant browser/Agent authority. | -| #110 `feat(core): fail closed on browser runtime revision drift` | `f0fc8f9cfc66dd8b7664b058a8243cc2bf9d95e5` | #109 `ea17243bf3e0a332bc4a62e25207c80697fde067` | `IMPLEMENTED_ON_ACTIVE_PR` | Formatting-only test head `03f7b188b4290ed6eb748df102bbe20119e8fa6b` established the missing runtime-revision boundary RED in CI `31567827677` / Rust contracts `94023260031`; current CI `31569036376` success; Rust contracts `94026891755` success; Production coverage `94026891801` exact function/line/region/branch success; Manifest V3 Compatibility `31569036568` success; CodeRabbit exact-head status success; no formal reviews or inline review threads returned; mergeable against the exact prerequisite | `BrowserProtocolAdapterDescriptor::require_runtime_revisions` validates bounded caller-supplied runtime protocol/browser revision tokens and requires exact equality with the pinned descriptor, with deterministic protocol-before-browser mismatch precedence. It does not authenticate the caller, discover or attest the running browser/protocol revisions, negotiate compatibility, invoke BiDi/CDP, or grant browser/Agent authority. | -| #111 `feat(core): validate browser protocol use atomically` | `72c4c3359b745357ec23942efabf13cebaa0f36f` | #110 `f0fc8f9cfc66dd8b7664b058a8243cc2bf9d95e5` | `IMPLEMENTED_ON_ACTIVE_PR` | CI `31569952532` success; Manifest V3 Compatibility `31569952560` success; no formal reviews or inline review threads returned; mergeable against the exact prerequisite | `BrowserProtocolAdapterDescriptor::validate_use` composes exact OriginWeave protocol generation, caller-supplied runtime revision validation, and one explicitly declared capability into a single ordered fail-closed check before returning non-cloneable metadata-validation evidence. It does not validate the runtime protocol family, authenticate an adapter, attest runtime metadata, invoke BiDi/CDP, or grant browser/Agent authority. | -| #112 `feat(core): bind validated browser use to runtime protocol kind` | `9aed5ae21aca022f58253566c87e67be648675bd` | #111 `72c4c3359b745357ec23942efabf13cebaa0f36f` | `IMPLEMENTED_ON_ACTIVE_PR` | Test-only head `6346164b9ec272e3b2bf0333a7c780498697edd3` established the missing runtime-kind boundary in CI `31570696512` / Rust contracts `94031855124`; current CI `31571266998` success; Rust contracts `94033586878` success; Production coverage `94033586779` exact function/line/region/branch success; Manifest V3 Compatibility `31571266946` success; CodeRabbit exact-head status success; no formal reviews or inline review threads returned; mergeable against the exact prerequisite | The same atomic validation boundary now requires caller-supplied runtime protocol family to equal the descriptor's exact WebDriver BiDi/CDP kind before revision or capability checks can authorize metadata validation. This does not authenticate or attest that runtime family, derive it from a running transport, invoke browser I/O, or grant browser/Agent authority. | -| #113 `feat(evidence): record validated browser protocol metadata` | `79aeef1cdc7dffa7b11ae2a7e29867eb1881019d` | #112 `9aed5ae21aca022f58253566c87e67be648675bd` | `IMPLEMENTED_ON_ACTIVE_PR` | Formatted test-only head `f11a88b62c58f076e853ecbf8eb053ab4716f583` passed repository contracts and canonical formatting, then failed the locked workspace check in CI `31572894086` / Rust contracts `94038496074` at the deliberately absent public evidence boundary. Current CI `31573726836` success; Rust contracts `94041068752` success; Production coverage `94041068684` exact function/line/region/branch success; CodeRabbit exact-head status success; no formal reviews or inline review threads returned; mergeable against the exact prerequisite. | `BrowserProtocolValidationEvidence` copies only the validated protocol family, OriginWeave generation, adapter version, upstream protocol revision, browser revision, and capability from one already validated use. The cloneable receipt does not recreate the non-cloneable validation prerequisite, authenticate the adapter/runtime metadata, authorize browser I/O, persist an audit log, or make evidence tamper-evident. | -| #114 `feat(core): bind runtime adapter version before protocol use` | `f368a4e4326b25b8825e0b4ec1753ec23de727e3` | #113 `79aeef1cdc7dffa7b11ae2a7e29867eb1881019d` | `IMPLEMENTED_ON_ACTIVE_PR` | Test-only head `b521bbcc3c70907cdf66a189d932c6cfa8c3a526` failed CI `31576973212` with the new contract still absent; current CI `31578095843` success; Manifest V3 Compatibility `31578095834` success; no formal reviews or inline review threads returned; mergeable against the exact prerequisite | `BrowserProtocolAdapterDescriptor::validate_use` now validates bounded runtime adapter-version metadata and requires exact equality with the descriptor before runtime revision or capability checks. This does not authenticate the runtime adapter, attest where the version came from, invoke BiDi/CDP, or grant browser/Agent authority. | -| #115 `feat(core): bind browser protocol validation to dispatch call` | `9fd91db4d75dfa0db714605d1248f399d0fc6428` | #114 `f368a4e4326b25b8825e0b4ec1753ec23de727e3` | `IMPLEMENTED_ON_ACTIVE_PR` | CI `31580453003` success; Rust contracts `94062054235` success; Production coverage `94062054224` exact function/line/region/branch success; Manifest V3 Compatibility `31580452993` success; no formal reviews or inline review threads returned; mergeable against the exact prerequisite | `dispatch_if_runtime_matches` makes validated runtime metadata/capability a same-call prerequisite for one callback and transfers the non-cloneable validation value by ownership. This still does not authenticate the adapter process, bind a browser session/context/origin, serialize BiDi/CDP, perform browser I/O, or prove a post-condition. | -| #116 `feat(core): bind protocol dispatch to current browser context` | `3850bf075318b54d893ff6ae67e24ce6ea53ccc0` | #115 `9fd91db4d75dfa0db714605d1248f399d0fc6428` | `IMPLEMENTED_ON_ACTIVE_PR` | CI `31586388906` success; Rust contracts `94081156663` success; Production coverage `94081156697` exact function/line/region/branch success; Manifest V3 Compatibility `31586388924` success; no formal reviews or inline review threads returned; mergeable against the exact prerequisite | `BrowserContextDispatchTarget` groups the requested OriginWeave session/context without granting authority; `dispatch_if_context_current` then requires current registry ownership/epoch before reusing #115's exact same-call protocol validation and invoking the callback. It does not authenticate the adapter process, bind canonical origin or semantic-node authority for typed input, authorize destination/network/TLS/HTTP, perform browser I/O, or prove a post-condition. | -| #117 `feat(core): bind browser context origin before observation` | `4f5e9b0f81df48fb178eaafb7cf68a925071ce39` | #116 `3850bf075318b54d893ff6ae67e24ce6ea53ccc0` | `IMPLEMENTED_ON_ACTIVE_PR` | Test-only head `48ec0a38e0fb2eed9d1eb5ee4cbae715130a6991` established the missing pre-observation origin-binding contract. Production head `2e799eea7c2b1e98d87bd8d1c12be00fc209fd71` added the bounded method, but CI `31587528996` failed at test compilation because the controlled fixture helper attempted to convert `OriginError` into `Box` even though `OriginError` does not implement `std::error::Error`; this was a test-harness setup defect, not product rejection. Current exact head `4f5e9b0f81df48fb178eaafb7cf68a925071ce39` fixes only that fixture conversion and is exact-green: CI `31588082784` success; Rust contracts `94086457133` success; Production coverage `94086457260` exact function/line/region/branch success; Manifest V3 Compatibility `31588082643` success; no formal reviews or inline review threads returned; mergeable against the exact prerequisite. | `BrowserAuthorityRegistry::bind_context_origin` establishes one canonical origin for the exact registered session/context and current document epoch before semantic-node discovery, is idempotent for the same origin, rejects a same-epoch origin change, and relies on document advancement to clear prior origin/node bindings. It does not derive or authenticate browser origin/runtime state, authorize navigation/destination/network/TLS/HTTP, grant Agent capability, create semantic observations, perform browser I/O, or prove a post-condition. | -| #118 `feat(core): revalidate current browser context origin` | `1eae12991eb5a2f91ce2d1486e9008c9ac3663e3` | #117 `4f5e9b0f81df48fb178eaafb7cf68a925071ce39` | `IMPLEMENTED_ON_ACTIVE_PR` | CI `31589434463` success and Manifest V3 Compatibility `31589434488` success on the exact head; no predecessor result is promoted. | `BrowserAuthorityRegistry::require_context_origin` revalidates exact session/context ownership plus the canonical origin currently bound to that document and returns its current `DocumentEpoch`; unbound origin or same-epoch origin mismatch fails closed. It does not derive or attest browser URL/origin state, authenticate a protocol adapter, authorize network/browser I/O, or make the returned epoch a reusable action capability. | -| #120 `feat(core): gate protocol dispatch on current context origin` | `cf58087b87c3a54fe1665c5ed5027b07b8b913af` | #118 `1eae12991eb5a2f91ce2d1486e9008c9ac3663e3` | `IMPLEMENTED_ON_ACTIVE_PR` | Exact formatted test-only head `56e3142538bd2499a880616ba2980f256838cb45` reached the intended production-boundary RED in CI `31591479446` / Rust contracts `94097233885`: repository contracts and rustfmt passed, then the locked workspace check failed only because `dispatch_if_context_origin_current` did not exist. Current production head `cf58087b87c3a54fe1665c5ed5027b07b8b913af` adds the narrow composition; current-head CI/review/coverage evidence is pending and therefore non-passing until refetched. | The proposed same-call boundary first revalidates exact session/context/canonical-origin/current-document authority, then validates exact protocol generation/family/adapter/runtime revisions/capability before handing the non-cloneable protocol-use proof plus current epoch to one callback. It does not derive or authenticate browser origin/runtime metadata, perform browser I/O, authorize destination/network/TLS/HTTP, grant Agent capability/approval, or prove a post-condition. | - -## Reconciliation consequence - -The documentation graph remains **DESIGN-SUFFICIENT / PROTECTED-MAIN-PARTIAL**. PRs #107–#120 implement bounded pieces of the browser-protocol and browser-lifetime contracts already anticipated by Proposed ADR 0107 and the versioned API contract. None introduces a new deployed trust domain or an OriginWeave-owned durable persistence schema. The conceptual/logical ERD therefore remains the truthful data-model artifact. - -For the first real Chromium Agent Task, a later trusted adapter still has to derive the canonical current origin and runtime protocol metadata from the actual browser boundary, bind the exact OriginWeave protocol generation, runtime protocol family, runtime adapter version, pinned runtime revisions, required capability, current session/context/canonical-origin/document/node authority where the operation requires it, and authenticated browser-protocol execution immediately before use. It must then compose that execution with semantic observation/query, deterministic policy, real input, observed post-condition, recovery and resource evidence while preserving credential-safe validation metadata without turning receipts into execution authority. Until that executable composition is integrated and revalidated on protected `main`, active branch evidence must not be described as shipped behavior or release readiness. diff --git a/docs/traceability/README.md b/docs/traceability/README.md index e30b9eda1..3d5a298e3 100644 --- a/docs/traceability/README.md +++ b/docs/traceability/README.md @@ -1,12 +1,12 @@ # OriginWeave Product and Decision Traceability - **Status:** Proposed authoritative traceability baseline -- **Scope:** Product requirements, Accepted architecture, protected-main implementation, active-PR implementation, planned adapters, conversation-derived decisions, standards, and verification evidence +- **Scope:** Product requirements, Accepted architecture, implemented kernels, planned adapters, conversation-derived decisions, standards, and verification evidence This file prevents two opposite errors: 1. an implemented safety boundary becoming undiscoverable because it exists only in code/tests; and -2. a product-design conversation, issue, or active pull request being presented as if it already shipped. +2. a product-design conversation or pull-request proposal being presented as if it already shipped. ## 1. Evidence precedence @@ -15,111 +15,81 @@ For current behavior, use this precedence order: 1. exact protected-main code and executable tests; 2. Accepted ADRs governing that code; 3. current root `ARCHITECTURE.md` and authoritative PRD/TRD aligned to protected main; -4. active-PR code/tests as explicitly labeled non-shipped evidence; -5. roadmap and issue plans; -6. conversation-derived product decisions and research notes. +4. roadmap and issue/PR plans; +5. conversation-derived product decisions and research notes. -Lower layers may define future direction but cannot override current protected implementation or an Accepted ADR. Active-PR behavior is never protected-main truth. +Lower layers may define future direction but cannot override current protected implementation or an Accepted ADR. -### 1.1 Active freshness-authority dossiers +## 2. Status vocabulary -Transient implementation evidence that materially tightens an existing authority boundary is kept in explicit active-PR traceability rather than silently changing protected-main maturity: +- **Implemented** — present on protected `main` with executable evidence. +- **Accepted architecture** — governing reviewed direction, though the complete runtime path may be unfinished. +- **Proposed** — candidate product/design decision requiring reviewed adoption. +- **Open** — intentionally unresolved. -- [`resolution-freshness-authority.md`](resolution-freshness-authority.md) — PR #47 bounds the lifetime of validated destination-resolution authority; the direct socket consumer still must require that fresh authority before the overall DNS-rebinding/TOCTOU interval can be called implemented on protected main. -- [`tls-revocation-freshness-authority.md`](tls-revocation-freshness-authority.md) — PR #48 classifies independently verified revocation material for freshness only; it does not fetch or authenticate OCSP/CRL material and does not create an unrevoked-certificate claim. - -These dossiers are evidence indexes, not substitute ADRs. A new ADR is required only when a durable architecture/trust/deployment decision changes. - -## 2. Capability maturity vocabulary - -Capability maturity uses exactly one of these values: - -- **IMPLEMENTED_ON_PROTECTED_MAIN** — present on protected `main` with executable evidence. -- **IMPLEMENTED_ON_ACTIVE_PR** — implemented and testable on an active PR, but not shipped/protected-main truth. -- **PARTIAL** — material foundations are implemented, while a named runtime, lifecycle, integration, or acceptance boundary remains incomplete. -- **ACCEPTED_ARCHITECTURE** — governing reviewed direction; implementation may be incomplete. -- **PLANNED** — accepted product backlog or target architecture without current implementation evidence. -- **RESEARCH_ONLY** — exploratory evidence that does not define a product commitment. -- **SUPERSEDED** — replaced by later implementation or architecture authority. -- **OUT_OF_SCOPE** — intentionally excluded from the current product boundary. - -ADR lifecycle is separate and remains `Proposed`, `Accepted`, `Superseded`, `Deprecated`, or `Rejected`. An Accepted ADR is design authority, not implementation proof. +A change can move from Proposed -> Accepted architecture -> Implemented, but never skips evidence merely because the idea is compelling. ## 3. Product-level decision trace -| Product decision | Capability maturity | Authoritative artifact | Protected-main / active-PR evidence boundary | +| Product decision | Origin/status | Authoritative artifact | Protected implementation/evidence | |---|---|---|---| -| Chromium remains the compatibility kernel rather than rewriting Blink/V8 | ACCEPTED_ARCHITECTURE | ADR 0001; `ARCHITECTURE.md`; PRD-COMP-001 | Architecture/repository contracts exist; complete branded browser distribution remains Planned | -| `Browse. Act. Prove.` provenance-native product identity | ACCEPTED_ARCHITECTURE | `README.md`; `docs/PRD.md`; roadmap | Evidence/provenance foundations exist; complete buyer Evidence Trail remains Planned | -| Human / Assist / Agent Task / Crawler execution modes | PARTIAL | `ARCHITECTURE.md`; `docs/PRD.md`; ADR 0002 | Core mode/purpose/policy foundations exist; browser-session/profile integration remains incomplete | -| Page content is data, never instruction authority | IMPLEMENTED_ON_PROTECTED_MAIN | ADR 0002; `ARCHITECTURE.md`; `docs/TRD.md` | `originweave-core` + `originweave-policy` tests | -| Typed actions instead of default arbitrary JavaScript | PARTIAL | PRD-ACT-001..004; ADR 0002 | Typed core/policy foundations are on main; complete browser action adapter remains Planned | -| logical origin != resolved destination | IMPLEMENTED_ON_PROTECTED_MAIN | ADR 0004; TRD-INV-002 | `originweave-destination`; destination governance tests | -| Bounded resolution freshness is explicit before destination authority is consumed | IMPLEMENTED_ON_ACTIVE_PR | ADR 0004; [`resolution-freshness-authority.md`](resolution-freshness-authority.md) | PR #47 implements the deterministic freshness primitive; protected-main socket planning can still bypass it, so the overall resolution-to-socket TOCTOU boundary remains PARTIAL | -| resolved destination != TCP peer | IMPLEMENTED_ON_PROTECTED_MAIN | ADR 0005; TRD Section 6 | `originweave-network`; loopback/peer tests | -| TCP peer != TLS service identity | IMPLEMENTED_ON_PROTECTED_MAIN | ADR 0006; TRD Section 6 | `originweave-tls`; rustls integration tests | -| Revocation-material freshness is separate from revocation authenticity/non-revocation | IMPLEMENTED_ON_ACTIVE_PR | ADR 0006/0008 boundary; [`tls-revocation-freshness-authority.md`](tls-revocation-freshness-authority.md) | PR #48 adds a freshness classifier only; protected main still records revocation as NotConfigured and makes no unrevoked claim | -| Proxy/PAC route authority must be explicit | PARTIAL | PRD-NET-005; TRD Section 6.3 | Protected-main direct-route authority exists; PAC evaluation/proxy transport/CONNECT remain incomplete | -| Bounded HTTP semantics require an authenticated governed connection and resource bounds | IMPLEMENTED_ON_ACTIVE_PR | PRD-NET-006; issue #9; active PR #37 | `originweave-http` replacement exists on active PR #37; historical PR #11 is SUPERSEDED implementation lineage and is not current evidence; no protected-main HTTP claim yet | -| Node handles bind session/context/origin/document lifetime | PARTIAL | ADR 0010; PRD-OBS-001/002; TRD Section 5 | Core opaque session/context/document/node authority is on protected main; active PR #40 owns the protocol-ID registry and remains non-shipped evidence | -| Semantic observations retain OriginWeave node authority and explicit source-channel provenance | IMPLEMENTED_ON_ACTIVE_PR | PRD-OBS-001/003/005; ADR 0010; structured-observation architecture | Active PR #52, stacked on #40, implements a bounded `SemanticNodeObservation` value primitive that rejects missing evidence-channel provenance. It is not a browser observation adapter; channels and advertised node actions are descriptive evidence and grant no execution authority | -| Raw secrets never enter model context | PARTIAL | PRD-DATA-001; ADR 0002; TRD Section 9 | Core secret-delivery policy exists; trusted broker/runtime completion remains Planned | -| Sensitive disclosure is purpose- and classification-bound | PARTIAL | ADR 0007; PRD-DATA-002; issue #10 | Purpose-bound policy/evidence foundations are on protected main; active PR #45 adds credential-free handle-lifecycle evidence and #46 adds bounded in-process authoritative use reservation, while trusted storage/revocation/value resolution/cross-process lifecycle/model-disclosure remain open | -| Evidence/provenance are product outputs, not debug leftovers | PARTIAL | ADR 0003; PRD Section 9.6 | `originweave-evidence` foundations exist; complete durable Evidence Trail/WARC/PROV adapters remain Planned | -| 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 | -| 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 | -| Browser / Runtime / Observe / Capture / Governor / Policy / Evidence / Protocol / SDK product surfaces | PARTIAL | PRD Section 6 | Some foundations exist under crates; named commercial surfaces are not all shipped artifacts | -| Constrained GPU phase scheduling for browser rendering vs local inference | PARTIAL | PRD-RES-005; TRD Section 10 | Deterministic resource plan exists; real GPU scheduler/telemetry remains Planned | -| Enterprise SSO/SCIM/residency/audit/procurement package | PLANNED | PRD Section 9.11; roadmap Phase 5 | Not shipped in pre-alpha baseline | +| Chromium remains the compatibility kernel rather than rewriting Blink/V8 | Accepted architecture | ADR 0001; `ARCHITECTURE.md`; PRD-COMP-001 | Architecture/repository contract tests; Chromium adapter itself remains Planned | +| `Browse. Act. Prove.` provenance-native product identity | Accepted product framing | `README.md`; `docs/PRD.md`; roadmap | Evidence/provenance foundation implemented; full buyer Evidence Trail Planned | +| Human / Assist / Agent Task / Crawler execution modes | Accepted architecture | `ARCHITECTURE.md`; `docs/PRD.md`; ADR 0002 | Core mode/purpose and policy foundation implemented; browser-session integration Planned | +| Page content is data, never instruction authority | Implemented foundation | ADR 0002; `ARCHITECTURE.md`; `docs/TRD.md` | `originweave-core` + `originweave-policy` tests | +| Typed actions instead of default arbitrary JavaScript | Accepted architecture | PRD-ACT-001..004; ADR 0002 | Typed core/policy foundation implemented; full browser action adapter Planned | +| logical origin != resolved destination | Implemented | ADR 0004; TRD-INV-002 | `originweave-destination`; destination governance tests | +| resolved destination != TCP peer | Implemented | ADR 0005; TRD Section 6 | `originweave-network`; loopback/peer tests | +| TCP peer != TLS service identity | Implemented | ADR 0006; TRD Section 6 | `originweave-tls`; rustls integration tests | +| Proxy/PAC route authority must be explicit | Accepted architecture / active development | PRD-NET-005; TRD Section 6.3 | Protected-main direct-only boundary exists; complete proxy execution not yet shipped | +| HTTP semantics require an authenticated governed connection and resource bounds | Accepted architecture / active development | PRD-NET-006; TRD Section 6.6 | Not yet a protected-main product capability in this baseline | +| Node handles bind session/context/origin/document lifetime | Proposed/active development | PRD-OBS-001/002; TRD Section 5 | Not treated as shipped until protected integration | +| Raw secrets never enter model context | Accepted architecture / implemented policy foundation | PRD-DATA-001; ADR 0002; TRD Section 9 | Core secret-delivery policy implemented; trusted broker runtime Planned | +| Sensitive disclosure is purpose-bound and classification-bound | Proposed/active development | PRD-DATA-002; TRD Section 9 | Do not claim complete broker/service until protected integration | +| Evidence/provenance are product outputs, not debug leftovers | Accepted / foundation implemented | ADR 0003; PRD Section 9.6 | `originweave-evidence`; evidence governance tests | +| Human interaction outranks inference/background collection | Accepted architecture / foundation implemented | `ARCHITECTURE.md`; PRD-RES-002 | Deterministic resource mitigation foundation implemented; platform telemetry Planned | +| Structured observation precedes raw HTML/screenshot fallback | Accepted architecture | PRD-OBS-003; TRD Section 7 | Observation adapter Planned | +| WebDriver BiDi / CDP / WebMCP / MCP are adapters, not internal authority | Accepted architecture | PRD Section 9.8; TRD Section 12 | Adapter implementations Planned | +| Manifest V3 compatibility is preserved upstream where practical | Accepted architecture | ADR 0001; PRD Section 9.9 | Chromium compatibility program Planned | +| WARC/PROV-oriented durable evidence adapters | Accepted architecture / Planned | ADR 0003; PRD-EVD-005 | Source/provenance kernel foundation exists; persistence adapters Planned | +| Origin Map visualizes value/action provenance | **conversation-derived Proposed** product UX | PRD-EVD-004; this traceability record | No shipped UI claim | +| Browser / Runtime / Observe / Capture / Governor / Policy / Evidence / Protocol / SDK product surfaces | **conversation-derived Proposed product taxonomy**, aligned to existing architecture | PRD Section 6 | Some foundations exist under crates; named commercial surfaces are not all shipped artifacts | +| Constrained GPU phase scheduling for browser rendering vs local inference | **conversation-derived Accepted architecture direction**, implementation Planned | PRD-RES-005; TRD Section 10 | Deterministic resource plan exists; real GPU scheduler/telemetry Planned | +| Enterprise SSO/SCIM/residency/audit/procurement package | Planned | PRD Section 9.11; roadmap Phase 5 | Not shipped in pre-alpha baseline | ## 4. Requirement-to-module trace -| Requirement family | Current module(s) / lane | Primary tests/docs | Capability maturity | +| Requirement family | Current module(s) | Primary tests/docs | Implementation status | |---|---|---|---| -| Canonical origin / action / approval | `originweave-core` | crate tests; ADR 0002 | IMPLEMENTED_ON_PROTECTED_MAIN | -| Deterministic action policy | `originweave-policy` | policy/security-review tests | IMPLEMENTED_ON_PROTECTED_MAIN | -| Destination/rebinding/redirect | `originweave-destination` | destination tests; ADR 0004 | IMPLEMENTED_ON_PROTECTED_MAIN | -| Resolution freshness authority | active `originweave-destination` work in PR #47 | [`resolution-freshness-authority.md`](resolution-freshness-authority.md); active exact-head tests/coverage | IMPLEMENTED_ON_ACTIVE_PR | -| Exact direct socket/peer | `originweave-network` | real loopback + error tests; ADR 0005 | IMPLEMENTED_ON_PROTECTED_MAIN | -| TLS identity | `originweave-tls` | real rustls integration; ADR 0006 | IMPLEMENTED_ON_PROTECTED_MAIN | -| TLS revocation-material freshness | active `originweave-tls` work in PR #48 | [`tls-revocation-freshness-authority.md`](tls-revocation-freshness-authority.md); active exact-head tests/coverage | IMPLEMENTED_ON_ACTIVE_PR | -| Resource budgets/mitigations | `originweave-resource` | crate tests | PARTIAL | -| Redacted evidence/provenance | `originweave-evidence` | crate tests; ADR 0003 | PARTIAL | -| Bounded HTTP/1.1 | active `originweave-http` replacement in PR #37 | issue #9; active-PR unit/integration/coverage evidence | IMPLEMENTED_ON_ACTIVE_PR | -| Proxy/PAC | destination/route foundation + future adapter | roadmap/TRD | PARTIAL | -| Session/context/document/node authority | `originweave-core` authority values; active registry work in PR #40 | ADR 0010; roadmap/TRD/UML | PARTIAL | -| Semantic observation value authority/provenance | active `originweave-core` work in PR #52, stacked on #40 | `semantic_node_observation` tests; PRD-OBS-001/003/005; issue #28 | IMPLEMENTED_ON_ACTIVE_PR | -| Manifest V3 compatibility evidence | `scripts/ci/run_mv3_compatibility.py` + controlled MV3 fixture; active downloads lane #43 | issue #27; real-browser contracts | PARTIAL | -| Extension-to-Agent authority | protected-main core authority kernel + Proposed ADR 0013 | issue #27; extension authority UML | PARTIAL | -| Purpose-bound sensitive-data policy/evidence | `originweave-policy` + evidence foundations; active lifecycle/reservation work #45/#46 | ADR 0007; issue #10 | PARTIAL | -| Trusted sensitive-data broker/storage/lifecycle | future bounded service/crate | issue #10; PRD/TRD/data governance | PLANNED | -| BiDi/CDP/WebMCP/MCP | future/versioned adapter crates; registry prerequisite active in #40 | protocol compatibility tests required | PLANNED | -| WARC/PROV persistence | persistence/export adapters | doctoring + future conformance tests | PLANNED | +| Canonical origin / action / approval | `originweave-core` | crate tests; ADR 0002 | Implemented | +| Deterministic action policy | `originweave-policy` | policy/security-review tests | Implemented | +| Destination/rebinding/redirect | `originweave-destination` | destination tests; ADR 0004 | Implemented | +| Exact direct socket/peer | `originweave-network` | real loopback + error tests; ADR 0005 | Implemented | +| TLS identity | `originweave-tls` | real rustls integration; ADR 0006 | Implemented | +| Resource budgets/mitigations | `originweave-resource` | crate tests | Implemented foundation | +| Redacted evidence/provenance | `originweave-evidence` | crate tests; ADR 0003 | Implemented foundation | +| HTTP | future/active `originweave-http` work | dedicated design/tests/PR evidence | Planned until protected merge | +| Proxy/PAC | destination foundation + future adapter | roadmap/TRD | Planned/active | +| Session/observation/action | future crates/adapters | roadmap/TRD/UML | Planned/active | +| Secret broker | future bounded service/crate | PRD/TRD | Planned/active | +| BiDi/CDP/WebMCP/MCP | adapter crates | protocol compatibility tests required | Planned | +| WARC/PROV persistence | persistence adapters | doctoring + future conformance tests | Planned | ## 5. Requirement-to-ADR trace -| Requirement | Governing ADR / current decision boundary | +| Requirement | Governing ADR | |---|---| -| PRD-COMP-001, Chromium compatibility kernel | ADR 0001 (Accepted) | -| PRD-ACT-001, PRD-ACT-005, PRD-CRAWL-001, trust-source boundary | ADR 0002 (Accepted) | -| PRD-EVD-001, PRD-EVD-002, PRD-EVD-005 | ADR 0003 (Accepted) | -| PRD-NET-001, PRD-NET-002, redirect/rebinding/freshness boundary | ADR 0004 (Accepted); active PR #47 tightens the existing boundary without creating a new deployed component or trust owner | -| PRD-NET-003 | ADR 0005 (Accepted) | -| PRD-NET-004 | ADR 0006 (Accepted); active PR #48 adds revocation-material freshness only and does not define a complete revocation architecture | -| Purpose-bound sensitive-data authority | ADR 0007 (Accepted); trusted broker/storage/lifecycle still issue #10 | -| TLS delegated-task leaf-validity horizon | ADR 0008 (Accepted) | -| Session/context/document/node binding | ADR 0010 (Accepted); active registry implementation #40 remains non-shipped | -| Semantic observation authority/provenance | Existing session/node authority plus structured-observation architecture; active PR #52 narrows the value contract without creating a new service, trust owner, persistence boundary, or external protocol and therefore does not justify a new ADR by itself | -| Manifest V3 compatibility + extension-to-Agent authority | ADR 0013 is Proposed on documentation PR #44; protected-main extension authority code does not auto-Accept the ADR | -| Architecture-decision acceptance governance | ADR 0014 is Proposed on documentation PR #44; protected-main AGENTS + live policy remain authoritative | -| HTTP semantics | active PR #37 contains its feature ADR lineage; it is active-PR evidence until protected merge and index reconciliation | -| Proxy/PAC route execution | current protected-main route authority + future dedicated execution decision as needed | -| Enterprise deployment/privacy | open ADR family before production release | +| PRD-COMP-001, PRD-COMP-003 | ADR 0001 | +| PRD-ACT-001, PRD-ACT-005, PRD-CRAWL-001, trust-source boundary | ADR 0002 | +| PRD-EVD-001, PRD-EVD-002, PRD-EVD-005 | ADR 0003 | +| PRD-NET-001, PRD-NET-002, redirect/rebinding boundary | ADR 0004 | +| PRD-NET-003 | ADR 0005 | +| PRD-NET-004 | ADR 0006 | +| Session/context/document node binding | Proposed/active decision; index only after dedicated ADR reaches protected main | +| Proxy/PAC route execution | Proposed/active decision; protected-main index updates after merge | +| HTTP semantics | Proposed/active decision; protected-main index updates after merge | +| Sensitive-data broker lifecycle | Proposed/active decision; policy/evidence slices do not equal full broker acceptance | +| Enterprise deployment/privacy | Open ADR family before production release | ## 6. Standards-to-decision trace @@ -130,8 +100,8 @@ The canonical APA 7th bibliography is [`../doctoring.md`](../doctoring.md). This | WHATWG URL + Chromium canonicalizer | Browser-compatible origin identity and numeric-host rejection | | IANA special-purpose registries / RFC 6890 / RFC 8190 / RFC 9637 | Destination classification and fail-closed public-web policy | | RFC 9293 | Exact TCP endpoint/peer model | -| RFC 5280 / RFC 9525 / RFC 9325 | Certificate path, HTTPS service identity, and the separation between certificate validity and any future revocation policy | -| RFC 9110 / RFC 9112 / RFC 9530 | Bounded HTTP semantics, framing, redirect evidence and digest fields | +| RFC 5280 / RFC 9525 / current TLS guidance | Certificate path and HTTPS service identity | +| RFC 9110 and related HTTP specifications | Redirect and bounded HTTP semantics | | RFC 9309 | Crawler robots evidence, explicitly not access authorization | | W3C WebDriver BiDi | Versioned browser automation adapter, not core authority | | Chrome DevTools Protocol | Chromium-specific observation/diagnostic adapter | @@ -144,17 +114,15 @@ Material claims should update `docs/doctoring.md` with current primary evidence ## 7. Diagram-to-requirement trace -| Diagram | Requirements represented / maturity | +| Diagram | Requirements represented | |---|---| | UML component/bounded-context view | Product family, Chromium/Rust ownership, adapter boundaries | -| Network authority sequence | PRD-NET-001..007; TRD-INV-002; HTTP remains active-PR until #37 integrates; resolution freshness remains an active lower-layer primitive until the socket consumer requires it | -| Observation/action sequence | PRD-OBS, PRD-ACT, PRD-DATA, trust separation; active #52 makes the bounded semantic-observation value/provenance contract explicit without establishing browser I/O or action dispatch | +| Network authority sequence | PRD-NET-001..007; TRD-INV-002 | +| Observation/action sequence | PRD-OBS, PRD-ACT, PRD-DATA, trust separation | | Delegated-task state machine | session lifecycle, approval, resource pause, cancellation/recovery, post-condition truth | | Deployment topology | renderer trust, orchestrator/model/store boundaries | -| Evidence authority flow | PRD-EVD; proposal/policy/approval/execution/outcome separation | -| Extension authority sequence | MV3 compatibility plane vs explicit OriginWeave extension grant and Agent capability separation | -| Conceptual ERD | session/action/network/sensitive/resource/provenance identity; active freshness and semantic-value primitives introduce no physical persistence | -| Real Chromium vertical-slice sequence | PLANNED until issue #28 implementation stabilizes; active #40/#51/#52 are prerequisites, not proof of the real adapter flow; do not encode temporary adapter fields as shipped architecture | +| Evidence authority flow | PRD-EVD; separation of proposal/policy/approval/execution/outcome | +| Conceptual ERD | durable session/action/network/sensitive/resource/provenance identity | ## 8. Conversation-to-repository capture rule @@ -162,29 +130,23 @@ A **conversation-derived** decision is not binding merely because it was repeate If material and absent from GitHub: -1. record it with explicit capability maturity in PRD/TRD/traceability; -2. create or supersede an ADR when it changes a governing architecture decision; +1. record it as `Proposed` or `Open` in PRD/TRD/traceability; +2. create/supersede an ADR when it changes a governing architecture decision; 3. update UML/ERD when relationships or lifecycles change; 4. add standards/research to `docs/doctoring.md` when evidence is material; -5. add executable tests before calling production behavior `IMPLEMENTED_ON_PROTECTED_MAIN`; +5. add executable tests before calling production behavior Implemented; 6. update the protected-main ADR index only after review and merge. This rule intentionally prevents chat history from becoming a shadow architecture database. ## 9. Documentation drift checks -Repository contracts should fail when canonical PRD/TRD/ADR/UML/ERD/traceability artifacts disappear, lifecycle/index status diverges, an active PR is promoted to protected-main truth, or core maturity/authority vocabulary is removed. Active freshness dossiers must remain discoverable from this index so lower-layer primitives cannot silently become over-broad shipped claims. More semantic checks should be added when a specific drift has caused a real defect; avoid brittle tests that merely duplicate prose. +Repository contracts should fail when the canonical PRD/TRD/ADR index/UML/ERD/traceability files disappear or when core status/authority vocabulary is removed. More semantic checks should be added when a specific drift has caused a real defect; avoid brittle tests that duplicate prose without protecting a contract. ## 10. Open traceability work -- **Open:** active PR #47 must reach unchanged exact-head CI/security/100% coverage, then the first-party socket consumer must require the fresh resolution authority before the resolution-to-socket TOCTOU interval can become protected-main implemented evidence. -- **Open:** active PR #48 remains freshness classification only; define and review revocation-material acquisition/authenticity/cache/failure/composition before any protected-main revocation-enforcement or unrevoked claim. -- **Open:** after #37 integrates, move bounded HTTP from `IMPLEMENTED_ON_ACTIVE_PR` into protected-main evidence and close historical PR #11 only after unique-work preservation and protected-main verification are proven. -- **Open:** after #43 integrates, move bounded MV3 downloads from `IMPLEMENTED_ON_ACTIVE_PR` into the protected-main compatibility evidence inventory while issue #27 remains open for the complete matrix. -- **Open:** after #40 stabilizes/integrates, map its registry API and tests without presenting raw BiDi/CDP identifiers as durable authority. -- **Open:** after stacked #52 stabilizes/integrates behind #40, reclassify only its bounded semantic-observation value/provenance primitive; keep real browser observation I/O, action dispatch, mutation invalidation and post-condition evidence under issue #28 until implemented. -- **Open:** after #45/#46 integrate, reclassify their narrow lifecycle/reservation primitives while keeping durable trusted-broker storage/revocation/value-resolution/model-disclosure boundaries under issue #10 until implemented. - **Open:** attach concrete release profiles and quantitative benchmark thresholds after reproducible benchmark evidence exists. - **Open:** map every future public OriginWeave Protocol operation to risk/capability/authority and conformance tests. - **Open:** map enterprise controls to exact SOC 2/CSAP-oriented control evidence without claiming certification. - **Open:** add data-retention and residency lifecycle diagrams when persistence/tenant adapters become concrete. +- **Open:** after active feature PRs merge, update this matrix from `Proposed/active development` to the exact protected implementation and Accepted ADRs. diff --git a/docs/traceability/action-postcondition-evidence.md b/docs/traceability/action-postcondition-evidence.md deleted file mode 100644 index 23a6e764d..000000000 --- a/docs/traceability/action-postcondition-evidence.md +++ /dev/null @@ -1,116 +0,0 @@ -# Action Post-Condition Evidence Traceability - -- **Documentation status:** Active-PR evidence dossier -- **Canonical owner:** PR #44 (`docs: reconcile architecture documentation fitness`) -- **Protected-main baseline:** `67af7c87589edc2039545af335c95064d9b8391c` -- **Capability maturity:** **PARTIAL** -- **Governing decisions:** Accepted ADR 0003 plus Proposed ADR 0106 preserve provenance-native evidence and separation of action execution from verification. - -## 1. Why this dossier exists - -OriginWeave's protected-main API contract already defines a durable product rule: returning from a browser command is not equivalent to successful action completion. A state-changing action becomes successful only after the declared or derived post-condition is observed and verified. Protected main also provides generic credential-safe provenance with explicit verification state, but that design rule was not yet represented by a reusable typed action-outcome evidence object. - -This dossier records the active implementation evidence that narrows that gap. It does not promote active pull requests to protected-main shipped truth and it does not claim that a real Chromium adapter already observes the post-condition after dispatch. - -## 2. Protected-main design and implementation boundary - -Protected `main` already provides: - -- typed `ActionKind` and immutable `ActionIntentDigest` values; -- canonical `Origin` authority values; -- credential-safe `ProvenanceRecord` with explicit `VerificationResult`; -- API/TRD requirements that state-changing success waits for an observed post-condition; and -- provenance architecture that keeps observation, policy, execution, and verification as distinct authorities. - -The generic value primitives are **IMPLEMENTED_ON_PROTECTED_MAIN**. The complete action dispatch → observation → independent verification → successful outcome chain remains **PARTIAL** because protected main does not yet contain the real Chromium runtime that composes them end to end. - -## 3. Active executable evidence - -### PR #64 — verified, temporally ordered post-condition becomes typed action-outcome evidence - -**Capability maturity:** `IMPLEMENTED_ON_ACTIVE_PR` - -Exact head `2c45411ed9aa0eecca2d06c85659db9f4bb85e4d` adds `VerifiedActionOutcomeEvidence` in the existing credential-safe evidence crate. It binds: - -1. the exact typed `ActionKind`; -2. canonical target `Origin`; -3. complete immutable `ActionIntentDigest`; -4. a bounded first-slice `PostConditionKind` (`UrlChanged`, `NodeStateChanged`, `DialogStateChanged`, or `NetworkMutationObserved`); -5. caller-supplied action-dispatch and post-condition-observation timestamps that must come from one monotonic clock domain; and -6. the exact `ProvenanceRecord` used as the post-condition proof. - -Construction fails closed unless the supplied provenance has `VerificationResult::Verified`. Both `Unverified` and `Rejected` observations are rejected as `PostConditionNotVerified`. An observation timestamp earlier than dispatch is rejected as `PostConditionPredatesDispatch`; equal ticks remain valid for coarse monotonic clocks. - -On this exact head, CI run `31441848670`, Security Scan run `31441848649`, SAST Semgrep run `31441848615`, exact owned production function/line/region/branch coverage, strict Clippy, rustdoc and CodeRabbit exact-head status are successful. GitHub reports the PR mergeable and Ready for review; no formal reviews or inline review threads are currently returned. - -### PR #65 — controlled hostile local workflow fixture - -**Capability maturity:** `IMPLEMENTED_ON_ACTIVE_PR` - -Test-only head `d2580305f05aba93d10b5342ec1886d601c6752e` was based directly on the protected-main baseline and intentionally required a checked-in `tests/fixtures/agent_task_basic/index.html` before that fixture existed. CI run `31445088008`, Rust contracts job `93637443229`, checked out that exact head and failed with three `FileNotFoundError` results for the missing fixture, establishing the intended fail-first boundary. - -Exact head `0888fe3a6ef6da547a37fd075733cc73dc52b2ab` adds the smallest controlled fixture satisfying the contract: a labelled semantic field, submit control, deterministic `idle` → `submitted` observable state change carrying only synthetic text, one explicitly hidden/untrusted prompt-injection marker, and no password/OTP/API-key/secret collection surface. - -On that unchanged exact head, CI run `31445201739` succeeds; Rust contracts job `93637824750` passes repository contracts, formatting, locked workspace check, full tests, strict Clippy and rustdoc; Production coverage job `93637824824` passes exact owned production function/line/region/branch enforcement; Security Scan run `31445201774`, SAST Semgrep run `31445201669` and CodeRabbit exact-head status succeed. GitHub reports the PR mergeable and Ready for review with no formal reviews or inline review threads currently returned. - -This remains controlled test infrastructure rather than browser-execution evidence. The fixture itself does not establish WebDriver BiDi/CDP transport, Chromium semantic extraction, policy dispatch, native input, post-condition provenance, profile teardown or process attribution. - -## 4. Non-transitive success semantics - -The intended first-slice chain is: - -```text -typed action intent --> policy-authorized dispatch --> real browser input/event --> observed bounded post-condition --> independently verified provenance --> temporally ordered VerifiedActionOutcomeEvidence -``` - -The active PR implements only the final typed evidence boundary. The following implications are explicitly invalid: - -```text -command return -/> successful action completion -protocol acknowledgement -/> successful action completion -Unverified -/> successful action completion -Rejected -/> successful action completion -caller-supplied timestamp ordering -/> proof of trusted clock provenance -VerifiedActionOutcomeEvidence type existence -/> proof of real Chromium execution -controlled fixture success -/> proof of real Chromium execution -``` - -PR #64 now rejects a caller-supplied observation timestamp that predates caller-supplied dispatch time, but the type cannot independently prove the clock source, that a real browser actually dispatched the action, that the supplied provenance belongs to the claimed browser target/node, or that the observed state was caused by that action. PR #65 supplies deterministic hostile input and a post-condition target but no browser execution. Those claims remain the responsibility of the real adapter/runtime composition under issue #28. - -## 5. Active prerequisite graph for issue #28 - -The first real Chromium vertical slice remains distributed across bounded active prerequisites rather than one shipped runtime: - -- PR #40 — protocol/browser identifiers → OriginWeave session/context/origin/document/node authority; -- PR #52 — bounded semantic node observation with explicit source-channel provenance; -- PR #57 — typed semantic-node query contract; -- PR #58 — authority-bound semantic node action target; -- PR #49 — ephemeral compatibility-profile lifecycle regression stacked on #43; -- PR #51 — bounded browser-task telemetry plus one explicitly supplied Linux PID `VmRSS` sampler; Chromium process discovery/process-set attribution remains outside that slice; -- PR #64 — verified and caller-timestamp-ordered post-condition action-outcome evidence; and -- PR #65 — controlled hostile local Agent Task workflow fixture, gate-clean and Ready for review. - -These active PRs are non-shipped evidence. They do not themselves compose WebDriver BiDi/CDP transport, trusted Chromium process attribution, policy-authorized real input dispatch, causal post-condition observation, or deterministic end-to-end teardown/recovery into one protected-main runtime. - -## 6. Remaining issue #28 boundary - -This dossier does **not** close issue #28. Material remaining work includes: - -- pinned stock Chromium exercised as one reproducible end-to-end Agent Task runtime path, not only extension compatibility fixtures; -- isolated Agent Task profile/context lifecycle and cleanup in the production vertical path; -- versioned WebDriver BiDi adapter plus explicitly bounded CDP observation fallback where needed; -- real semantic observation feeding typed query and policy-authorized typed action; -- real browser input dispatch followed by post-dispatch observation of the declared condition; -- hostile/stale/cross-session/cross-context/cross-origin/prompt-injection/secret-leak/crash/oversize regressions; -- deterministic failure/recovery evidence and task teardown; -- Chromium process discovery/process-set attribution composed into resource telemetry; and -- protected-main integration plus fresh acceptance before any active-PR capability becomes shipped truth. - -## 7. Documentation fitness consequence - -The ADR/PRD/TRD/Architecture/UML/ERD graph remains **DESIGN-SUFFICIENT / PROTECTED-MAIN-PARTIAL**. PR #64 narrows a typed evidence gap already governed by existing provenance/action-success decisions, while PR #65 supplies controlled test infrastructure for the eventual real-browser proof. Neither introduces a new trust domain, deployed component, persistence owner, database schema, or independent architecture decision, so a new ADR or physical ERD entity would overstate the implementation. Detailed real-Chromium dispatch/post-condition sequence diagrams should be reconciled when the executable adapter chain stabilizes rather than manufacturing as-built detail before that runtime exists. diff --git a/docs/traceability/extension-authority-security.md b/docs/traceability/extension-authority-security.md deleted file mode 100644 index a36380a31..000000000 --- a/docs/traceability/extension-authority-security.md +++ /dev/null @@ -1,85 +0,0 @@ -# Extension-to-Agent Security Traceability - -- **Documentation status:** Active-PR evidence dossier -- **Canonical owner:** PR #44 (`docs: reconcile architecture documentation fitness`) -- **Protected-main baseline:** `67af7c87589edc2039545af335c95064d9b8391c` -- **Capability maturity:** **PARTIAL** -- **Governing decision:** Proposed ADR 0013 separates Manifest V3 compatibility from OriginWeave Agent authority. - -## 1. Why this dossier exists - -Manifest V3 compatibility and OriginWeave Agent authority are intentionally different evidence domains. A Chromium extension may possess Chrome permissions and may be explicitly granted a narrow OriginWeave extension capability without receiving Agent origin grants, Agent action capability, instruction trust, secret-delivery authority, approval, or protected-value access. - -This dossier records the current executable composition evidence for that separation. It does not promote active pull requests to protected-main shipped truth and it does not claim the trusted sensitive-data broker from issue #10 is complete. - -## 2. Protected-main authority - -Protected `main` already provides: - -- exact extension/session/context-scoped `ExtensionAgentGrant` evaluation; -- a distinction between `ObserveCurrentContext` and `ProposeTypedAction` extension capabilities; -- deterministic Agent policy evaluation for typed actions; -- fail-closed treatment of `InstructionSource::WebContent`; -- explicit Agent capability and readable/writable-origin gates; -- `FillSecret` policy that rejects raw secret delivery and requires `SecretDelivery::BrokerHandle`; and -- ordinary action-risk approval semantics that remain separate from extension permission. - -These foundations are **IMPLEMENTED_ON_PROTECTED_MAIN**. They do not by themselves prove every issue #27 cross-boundary composition case. - -## 3. Active executable evidence - -### PR #62 — proposal authority cannot widen Agent, instruction, or secret-material authority - -**Capability maturity:** `IMPLEMENTED_ON_ACTIVE_PR` - -Exact head `a57873b3688984711918be17aadd348ed9fb12a9` proves that, after an extension is genuinely allowed to `ProposeTypedAction`: - -1. a proposed navigation outside the Agent readable-origin grant is still denied; -2. proposal permission cannot supply the missing Agent `Navigate` capability; -3. extension-produced untrusted content remains rejected as instruction authority; -4. `FillSecret` with `SecretDelivery::RawValue` remains denied as `SecretBrokerRequired`; and -5. secret material attached to a non-secret action remains denied as `UnexpectedSecretMaterial`. - -The branch adds no production API and no extension runtime. It is compositional security evidence over protected-main authorities. - -### PR #63 — proposal authority cannot manufacture high-risk approval - -**Capability maturity:** `IMPLEMENTED_ON_ACTIVE_PR` - -Exact head `e83749acd1cf5a0b778ba38eb9d6ed5a9bd1e68f` deliberately keeps only the distinct approval-composition proof after duplicate regressions were removed in favor of PR #62 ownership. It proves that, after the same exact proposal grant is admitted and the Agent context independently possesses `FillSecret` plus exact readable/writable origin authority, broker-handle `FillSecret` still reaches the ordinary R3 approval boundary rather than becoming implicitly allowed. - -The exact head has successful CI, exact owned production coverage, Security Scan, SAST and CodeRabbit status and is Ready for review. It has no raw secret bytes and does not create approval evidence, a broker, browser-fill adapter, protected-value store, KMS path, authenticated workload identity, persistence owner, or release claim. - -## 4. Security interpretation - -The executable authority chain is intentionally non-transitive: - -```text -Chromium extension permission --> explicit extension/session/context grant --> permission to propose a typed action --/> Agent capability --/> Agent readable/writable origin --/> trusted instruction source --/> secret-delivery authority --/> approval --/> protected-value resolution -``` - -A future real extension adapter must preserve these separations. Chrome permissions and extension proposal grants are inputs to policy composition, never ambient authority that bypasses the deterministic Agent policy or the sensitive-data broker boundary. - -## 5. Remaining issue #27 / #10 boundary - -This dossier does **not** close issue #27 or issue #10. Remaining material work includes, among other accepted requirements: - -- real managed-extension allow-list and enterprise policy integration; -- native-messaging host boundary and process isolation; -- complete supported-capability release matrix and regression gate; -- authenticated workload/service identity for sensitive-data broker audience; -- protected-value resolution/fill outside model-visible context; -- durable transactional handle lifecycle, retention, encryption/KMS, deletion and audit-export controls; and -- protected-main integration plus fresh acceptance before any active-PR evidence becomes shipped truth. - -## 6. Documentation fitness consequence - -The existing ADR/PRD/TRD/Architecture/UML/ERD graph remains **DESIGN-SUFFICIENT / PROTECTED-MAIN-PARTIAL**. PRs #62 and #63 narrow distinct executable extension-authority evidence gaps without introducing a new trust domain, deployment component, persistence entity, database schema, or independent architecture decision. Proposed ADR 0013 remains Proposed until its own lifecycle authority changes. \ No newline at end of file diff --git a/docs/traceability/resolution-freshness-authority.md b/docs/traceability/resolution-freshness-authority.md deleted file mode 100644 index edb91e4bb..000000000 --- a/docs/traceability/resolution-freshness-authority.md +++ /dev/null @@ -1,99 +0,0 @@ -# Resolution Freshness Authority Trace - -- **Documentation status:** Active-PR traceability -- **Protected-main capability status:** **PARTIAL** -- **Primitive implementation lane:** PR #47, `feat/resolution-freshness-authority-main` -- **First-party planning consumer lane:** PR #50, `feat/network-consume-resolution-freshness` -- **Socket-use freshness lane:** PR #54, `fix/network-resolution-freshness-at-use` -- **Governing existing decision boundary:** ADR 0004 and the protected-main destination/rebinding authority model -- **Buyer-visible gap:** bind the interval between a validated resolution answer and actual socket use so DNS-rebinding/TOCTOU exposure is explicit and fail-closed - -## Truth boundary - -Protected `main` already classifies, approves, pins, and non-expansively revalidates resolved destination addresses. It does **not** yet require a time-bounded resolution authority through the entire first-party direct-socket path. - -PR #47 exact head `6b5ed4dcea281b505f67db6180bb14c3bc95b392` contains the reusable production `FreshResolutionSnapshot` primitive and has terminal successful CI/security/SAST/exact-coverage evidence. That primitive is therefore **IMPLEMENTED_ON_ACTIVE_PR** evidence only; it is not protected-main truth. - -PR #50 exact head `f8b43bc94444986ab23aa4ef3086e446a0b39295` implements the dependent first-party planning boundary. It keeps the untimed `ConnectionPlan` internal to `originweave-network`, exposes `FreshConnectionPlan` as the ordinary direct-socket planner, requires a `FreshResolutionSnapshot` plus caller-supplied trusted monotonic current time, rejects expired authority at plan authorization, and migrates existing TLS integration helpers through that same fresh boundary. Exact-head CI run `31408474576` passes repository contracts, formatting, workspace check/tests, strict Clippy, rustdoc and exact owned production function/line/region/branch coverage; CodeRabbit exact-head status is success. - -PR #54 exact head `ec81031c537f2b662910c1ce78c7ae0e0bfc9c1e` closes a later plan-to-connect TOCTOU discovered after #50: freshness checked only when the plan was created could expire before socket I/O. The active lane retains the exact `FreshResolutionSnapshot` in the single-use plan, exposes `connect_at(current_time)` to re-run freshness immediately before socket use under the caller's trusted monotonic clock domain, and keeps the legacy `connect()` surface fail-closed by adding process-local monotonic elapsed time to the original authorization checkpoint before delegating to `connect_at`. CI run `31418337788` passes repository contracts, formatting, workspace checks/tests, strict Clippy, rustdoc and exact owned production function/line/region/branch coverage; CodeRabbit exact-head status is successful. - -PRs #47, #50 and #54 remain **IMPLEMENTED_ON_ACTIVE_PR**, not shipped. #50 remains dependency-gated on #47 and #54 remains dependency-gated on #50. The overall protected-main resolution-to-socket interval therefore remains **PARTIAL** until dependency-ordered integration and fresh protected-main acceptance prove the same authority chain without an untimed planning or delayed-use bypass. - -## Current exact-head RCA - -### PR #47 primitive - -The first production-complete PR #47 head reached all ordinary Rust contracts and security scans, but exact coverage failed at one compiler region while functions, lines, and branches were already complete. Coverage evidence localized the missing region to the generic `FreshResolutionSnapshot::revalidate` instantiation used with a one-address resolver answer: the success path for a one-address contraction was exercised, while the same monomorphized helper's error propagation for a one-address expansion had not been executed. - -That was a realistic DNS-rebinding case rather than an impossible instrumentation artifact. The branch added a focused one-address expansion regression requiring `ResolutionSetExpanded`, retained the two-address expansion case, and exact head `6b5ed4dcea281b505f67db6180bb14c3bc95b392` subsequently passed CI including exact production function/line/region/branch coverage, Security Scan, and SAST Semgrep. - -The freshness ceiling is executable active-PR evidence rather than an aspirational requirement. `crates/originweave-destination/src/resolution.rs` owns `MAX_RESOLUTION_VALIDITY: Duration = Duration::from_secs(30)`. `FreshResolutionSnapshot::approve` rejects `Duration::ZERO` and any interval above that constant with `DestinationError::InvalidResolutionValidity`; `crates/originweave-destination/tests/resolution_freshness.rs::fresh_resolution_rejects_invalid_or_overflowing_validity` verifies both the zero and greater-than-30-second boundaries plus approval-time overflow. This evidence remains active-PR-only until PR #47 integrates. - -### PR #50 planning consumer - -PR #50 began from exact PR #47 head `6b5ed4dcea281b505f67db6180bb14c3bc95b392` with a RED consumer contract requiring fresh resolution authority plus one trusted monotonic current time before direct socket planning. - -A first production repair added a public `FreshConnectionPlan` wrapper that authorized freshness and then delegated to the existing untimed `ConnectionPlan`. That implementation made the positive/expiry path available but did not close the buyer/security gap because the original public `ConnectionPlan::new(&ResolutionSnapshot, ...)` remained callable. Canonical review therefore rejected the parallel-wrapper design as insufficient rather than weakening the acceptance boundary. - -The corrected implementation removed `ConnectionPlan` from the public crate exports while retaining it as a private implementation detail. Exact-head CI run `31407686307` then failed at the intended first-party migration boundary: `cargo check --locked --workspace --all-targets` found exactly three TLS integration tests still importing the now-private stale planner (`handshake_deadline.rs`, `handshake_integration.rs`, and `validity_horizon_integration.rs`). That compile failure was useful evidence because it enumerated remaining first-party bypass consumers instead of hiding them behind a compatibility re-export. - -Those integration helpers were migrated to deterministic `FreshResolutionSnapshot` + `FreshConnectionPlan` fixtures with one explicit trusted monotonic clock domain. A later run `31408143459` found only missing end-of-file newlines under rustfmt; that formatting-only defect was corrected without changing the authority contract. Current exact head `f8b43bc94444986ab23aa4ef3086e446a0b39295` then passed CI run `31408474576` end to end, including exact owned function/line/region/branch coverage. - -The accepted remedy is therefore realized on the active branch: ordinary first-party direct planning cannot import the untimed planner, while the private implementation remains reusable only after `FreshConnectionPlan` performs freshness authorization. This proves the active planning implementation, but not freshness at a later delayed socket-use instant. - -### PR #54 socket-use consumer - -PR #54 follows #50 because a plan authorized within the resolution window could be retained until that window expired and then connected. The first failing boundary was therefore no longer public planner construction; it was the time between plan authorization and the exact operating-system connect operation. - -The accepted active-branch remedy keeps the admitted freshness snapshot with the non-cloneable single-use plan and revalidates it at the socket-use boundary. `connect_at(current_time)` is the explicit deterministic path and rejects both expiry and an authorization-time regression using the existing destination error taxonomy. The compatibility `connect()` path does not freeze the old authorization timestamp: it anchors a process-local monotonic `Instant` at plan construction, adds actual elapsed time to the admitted authorization time, and delegates to `connect_at`, so delayed legacy callers cannot replay stale authority indefinitely. - -The regression suite proves explicit success, deadline expiry, trusted-time regression, unchanged connection-parameter validation, and expiry of the compatibility path with a deliberately short real monotonic interval. Current exact head `ec81031c537f2b662910c1ce78c7ae0e0bfc9c1e` passes CI run `31418337788`. This remains active-PR evidence and does not add DNS lookup, a wall-clock authority, proxy/PAC, or a resolver service. - -## Deterministic authority contract - -The active stack proves one continuous destination-to-socket authority chain with all of the following properties: - -1. approval time is explicit and supplied from one trusted monotonic clock domain; -2. validity is non-zero and capped by the active implementation's repository-owned `MAX_RESOLUTION_VALIDITY` safety budget (30 seconds on PR #47 exact head), with shorter caller-selected intervals permitted; -3. the usable interval is half-open: `approved_at <= now < valid_until`; -4. use before approval, use at/after expiry, arithmetic overflow, unapproved addresses, and set expansion fail closed with typed errors; -5. the ordinary first-party socket planner no longer publicly accepts an untimed `ResolutionSnapshot` as sufficient authority on PR #50 exact head; -6. a single-use plan rechecks the retained freshness authority immediately before socket I/O on PR #54 rather than assuming plan-time admission remains fresh; -7. the compatibility socket path derives a new use time from monotonic elapsed duration and therefore cannot preserve stale plan-time authority indefinitely; -8. credential-free planning evidence records approval, expiry, and authorization times without introducing credentials, resolver internals, or protected values; -9. non-expanding revalidation may renew the bounded interval only while rerunning existing destination-policy validation against the newly supplied answer; and -10. the primitive and planning/use boundaries perform no DNS lookup, wall-clock read, ambient proxy selection, TLS policy mutation, HTTP, browser control, persistence, secret, or model call. - -## Architecture and ADR assessment - -The primitive and its first-party planning/socket consumers tighten the already Accepted destination/rebinding authority governed by ADR 0004. They do not introduce a new component, persistence owner, wire protocol, browser adapter, or trust domain. Therefore a new ADR, deployment component, or physical ERD object would be false precision at this stage. - -The durable network-authority sequence is now `resolver answer -> destination/origin validation -> fresh resolution approval -> trusted monotonic plan authorization -> socket-use freshness recheck -> exact socket candidate -> observed TCP peer -> TLS/HTTP authority`. That is a sequence refinement within the existing network-authority component graph, not a new topology. A new or superseding ADR becomes appropriate only if later integration changes ownership—for example, durable cross-process freshness state, a separate resolver service, a different trusted-clock owner, or a new externally versioned protocol. - -## Evidence progression - -| Evidence state | Allowed maturity claim | -|---|---| -| Test-only primitive/consumer head with unresolved production API | intentional RED contract only; not implementation evidence | -| Active PR #47 production primitive + unchanged exact-head CI/security/100% coverage | `IMPLEMENTED_ON_ACTIVE_PR` for the primitive; overall protected-main path remains `PARTIAL` | -| Active PR #50 adds a freshness wrapper while an ordinary untimed planner remains public | implementation progress only; bypass still makes the consumer incomplete | -| Active PR #50 hides the untimed planner and exact compile evidence finds stale first-party consumers | valid structural remedy with migration still incomplete | -| Active PR #50 exact head `f8b43bc...` migrates first-party consumers and passes exact CI/coverage | `IMPLEMENTED_ON_ACTIVE_PR` for planning; delayed socket-use freshness still requires #54 | -| Active PR #54 exact head `ec81031c...` rechecks freshness immediately before socket I/O and passes exact CI/coverage | `IMPLEMENTED_ON_ACTIVE_PR` for socket-use freshness; dependency-gated and non-shipped | -| PR #47 + #50 + #54 exact heads are individually gate-clean but none are on protected main | active-PR evidence only; no shipped claim | -| Protected-main primitive/planner, but delayed socket use can outlive freshness | `PARTIAL` | -| Protected-main direct socket path requires exact fresh authority and rechecks it at use, with tests proving pre-approval/expiry/rebinding/delay behavior | `IMPLEMENTED_ON_PROTECTED_MAIN` for the bounded resolution-to-socket interval | -| Browser/network adapter proves the same clock and authority chain under real navigation | additional integration/release evidence; not implied by lower-layer primitives | - -## Required follow-through - -- keep PR #47 as active/non-shipped evidence until repository governance integrates it; -- keep PR #50 Draft and dependency-gated while #47 remains active; do not transfer its green evidence to protected main; -- keep PR #54 Draft and dependency-gated while #50 remains active; do not transfer its green evidence to #50 or protected main; -- preserve the structural invariant that ordinary first-party direct planning cannot import an untimed `ConnectionPlan`; -- preserve the socket-use invariant that a delayed call cannot reuse plan-time freshness without a new trusted monotonic use-time check; -- keep PRD/TRD/traceability from calling the DNS-rebinding/TOCTOU interval closed while any prerequisite remains active; -- reconcile the existing network-authority UML with the stable durable freshness sequence without encoding temporary branch-only identifiers as timeless architecture; -- retain the existing conceptual ERD unless a real persistence owner is introduced; and -- after all three layers integrate, rerun protected-main operational/release acceptance before promoting capability maturity. diff --git a/docs/traceability/tls-revocation-freshness-authority.md b/docs/traceability/tls-revocation-freshness-authority.md deleted file mode 100644 index a5bfb98c0..000000000 --- a/docs/traceability/tls-revocation-freshness-authority.md +++ /dev/null @@ -1,53 +0,0 @@ -# TLS Revocation-Material Freshness Authority Trace - -- **Documentation status:** Active-PR traceability -- **Protected-main capability status:** **PARTIAL** -- **Active implementation lane:** PR #48, `feat/tls-revocation-freshness-main` -- **Governing existing boundary:** protected-main TLS service-identity authority, ADR 0006, ADR 0008, and the revocation-distribution/freshness roadmap gap -- **Buyer-visible gap:** prevent stale independently verified revocation material from being treated as current authority while preserving the fact that OriginWeave does not yet make an unrevoked-certificate claim - -## Truth boundary - -Protected `main` authenticates the requested HTTPS service over the already verified TCP stream, but its TLS evidence records revocation as `NotConfigured`. It does not fetch, parse, validate, cache, or enforce OCSP/CRL material and it does not claim that a certificate is unrevoked. - -PR #48 adds a reusable **freshness primitive** for revocation material only. The primitive can classify independently verified material as usable inside its signed `thisUpdate` to `nextUpdate` interval. That active-PR implementation is not protected-main truth, and passing the freshness check does not prove signature validity, path validity, responder authority, non-revocation, successful distribution, or complete TLS authentication policy. - -The complete revocation path therefore remains **PARTIAL** until a separately reviewed adapter acquires and cryptographically verifies revocation material, composes freshness into the authentication decision, defines failure/cache/recovery semantics, and proves the resulting behavior on protected main. - -## Required deterministic authority - -The bounded primitive is expected to preserve these properties: - -1. a higher-layer adapter may construct `RevocationMaterialFreshness` only after independent cryptographic verification has supplied both signed `thisUpdate` and `nextUpdate`; because RFC 6960 permits an OCSP `SingleResponse` to omit `nextUpdate`, absence must fail closed in that adapter before construction and must never be converted into an invented timestamp; -2. the active PR #48 primitive deliberately accepts mandatory `u64` `this_update_unix_seconds` and `next_update_unix_seconds`, so a missing `nextUpdate` has no representable successful state in the primitive; any future parser/adapter must expose a typed missing-`nextUpdate` error or a separately reviewed bounded fallback contract before calling freshness approved; -3. the signed interval is non-empty and ordered; -4. the usable interval is half-open: `thisUpdate <= trusted_time < nextUpdate`; -5. trusted time before `thisUpdate` and at/after `nextUpdate` fails closed with typed bounded errors; -6. the primitive performs no OCSP/CRL fetch, DNS, socket connection, TLS handshake mutation, parsing, signature verification, cache operation, browser control, persistence, or model call; and -7. no evidence or documentation converts freshness into an `unrevoked` claim. - -## Architecture and ADR assessment - -The active primitive tightens an existing TLS evidence/policy concern without introducing a new deployed component, persistence owner, wire protocol, network path, or secret boundary. A new ADR is therefore not required merely because the helper type exists. - -A new or superseding ADR becomes appropriate if OriginWeave later chooses a concrete revocation architecture that changes trust ownership—for example, stapled OCSP versus independently fetched OCSP/CRL, cache authority and freshness policy, hard-fail versus explicitly bounded degraded behavior, responder/path validation ownership, or a separate revocation service. - -No new physical ERD object is justified by this active in-memory primitive. UML should change only when the executable TLS/revocation data or control path changes materially. - -## Evidence progression - -| Evidence state | Allowed maturity claim | -|---|---| -| Protected main records `RevocationStatus::NotConfigured` | `PARTIAL`; no revocation enforcement or unrevoked claim | -| Active PR freshness primitive with exact-head tests/coverage | `IMPLEMENTED_ON_ACTIVE_PR` for freshness classification only | -| Protected-main freshness primitive without verified material acquisition/composition | `PARTIAL` | -| Protected-main adapter verifies responder/material authenticity, requires or safely bounds missing `nextUpdate`, enforces freshness, cache/failure policy, and binds the result into TLS authentication | implementation evidence for the chosen bounded revocation policy | -| Protected-main integration/recovery/operational tests prove the complete path | required additional release evidence; not implied by the helper primitive | - -## Required follow-through - -- keep PRD/TRD/TLS evidence from implying revocation enforcement while protected main remains `NotConfigured`; -- define revocation-material acquisition, authenticity, missing-`nextUpdate`, cache, freshness, failure, privacy, and recovery semantics before calling the TLS revocation boundary implemented; -- require exact 100% owned production function/line/region/branch coverage and complete rustdoc on every changed head; -- add or supersede an ADR only when the concrete revocation architecture changes a durable trust or deployment decision; and -- retain the conceptual ERD unless executable persistence ownership actually appears. diff --git a/docs/uml/README.md b/docs/uml/README.md index 1a985e254..1d04ab00a 100644 --- a/docs/uml/README.md +++ b/docs/uml/README.md @@ -6,10 +6,6 @@ These diagrams visualize governing boundaries; they do not imply that every planned adapter is already shipped. Labels use `implemented`, `active`, or `planned` where implementation status matters. -## Focused authority views - -- [Manifest V3 extension compatibility and Agent authority](extension-authority.md) - ## 1. Component and bounded-context view ```mermaid @@ -408,4 +404,4 @@ Update this pack when a protected change materially alters: - deployment boundaries; - evidence/provenance relationships. -A feature-specific ADR may include a more detailed sequence diagram, but this pack remains the product-wide view and must not require maintainers to reconstruct the complete system from scattered ADR diagrams. \ No newline at end of file +A feature-specific ADR may include a more detailed sequence diagram, but this pack remains the product-wide view and must not require maintainers to reconstruct the complete system from scattered ADR diagrams. diff --git a/docs/uml/extension-authority.md b/docs/uml/extension-authority.md deleted file mode 100644 index e228e84d4..000000000 --- a/docs/uml/extension-authority.md +++ /dev/null @@ -1,105 +0,0 @@ -# Extension Compatibility and Agent Authority UML - -- **Status:** Protected-main architecture visualization with active compatibility work -- **Scope:** Chromium Manifest V3 compatibility plane versus OriginWeave Agent authority -- **Related:** [`README.md`](README.md), [`../PRD.md`](../PRD.md), [`../TRD.md`](../TRD.md), [`../THREAT_MODEL.md`](../THREAT_MODEL.md), issue #27 - -This diagram makes one security invariant visually explicit: - -> **A Chromium extension permission is not an OriginWeave Agent capability.** - -A compatible extension can use the Chromium APIs granted by its manifest and managed browser policy. It cannot thereby grant itself OriginWeave task authority, widen an Agent Task origin, resolve a protected secret, approve a high-risk action, or turn extension/page content into a trusted instruction. - -## Authority sequence - -```mermaid -sequenceDiagram - autonumber - participant Admin as Human / Enterprise Policy - participant Chrome as Chromium MV3 Runtime - participant Ext as Extension Worker / Content Script - participant Observe as OriginWeave Observation Adapter - participant Grant as OriginWeave Extension Grant Policy - participant Agent as Agent Task / Planner - participant Policy as Deterministic Action Policy - participant Broker as Secret / Sensitive Broker - participant Browser as Trusted Browser Adapter - participant Evidence as Evidence Trail - - Admin->>Chrome: install/enable extension under Chromium policy - Chrome-->>Ext: expose manifest-granted Chrome APIs - Note over Chrome,Ext: Chrome permission is compatibility authority only. - - Ext-->>Observe: extension message / page mutation / tool output - Observe-->>Agent: bounded untrusted observation + provenance - Note over Ext,Agent: Extension content cannot become trusted goal or policy. - - Admin->>Grant: issue explicit OriginWeave extension grant for bounded session/context/capability/origin - Ext->>Grant: request OriginWeave interaction - Grant->>Grant: verify extension identity, managed policy, session/context, capability, origin, expiry - - alt no valid OriginWeave grant - Grant-->>Ext: deny - Grant-->>Evidence: denial without sensitive value - else valid grant - Grant-->>Agent: bounded extension-originated proposal/evidence - Agent->>Policy: propose typed action under existing Agent Task authority - Policy->>Policy: revalidate task, action, risk, origin, approval and current browser authority - alt action requires secret/sensitive value - Policy->>Broker: authorize exact opaque handle use - Broker->>Broker: revalidate tenant/task/field/purpose/destination/expiry - Broker-->>Browser: minimum trusted value delivery - end - Policy-->>Browser: authorized typed action - Browser->>Browser: verify session/context/document epoch immediately before dispatch - Browser-->>Evidence: action result + observed post-condition - end -``` - -## Security state flow - -```mermaid -flowchart TD - manifest[Manifest V3 permissions] --> chromium[Chromium extension authority] - chromium --> extension[Extension runtime] - extension --> untrusted[Untrusted observation / message] - untrusted --> grant{Explicit OriginWeave extension grant?} - grant -- no --> deny[Deny Agent-control request] - grant -- yes --> scoped[Bind extension identity + session + context + origin + capability + expiry] - scoped --> proposal[Typed Agent action proposal] - proposal --> policy{Agent Task policy passes?} - policy -- no --> deny - policy -- yes --> approval{Risk-specific approval required?} - approval -- missing/invalid --> deny - approval -- no or valid --> execute[Trusted browser adapter executes] - execute --> verify{Observed post-condition matches?} - verify -- no --> fail[Fail / quarantine] - verify -- yes --> evidence[Credential-safe evidence] - - extension -. cannot mint .-> scoped - extension -. cannot approve .-> approval - extension -. cannot resolve .-> secret[Protected secret / sensitive value] - secret --> execute -``` - -## Compatibility evidence is separate from authority evidence - -```mermaid -flowchart LR - pinned[Pinned Chromium revision] --> fixture[Controlled MV3 fixture suite] - fixture --> compat[Compatibility evidence] - compat --> matrix[Published supported-capability matrix] - - policycode[OriginWeave extension policy] --> isolation[Agent-authority isolation evidence] - isolation --> release[Release acceptance] - matrix --> release - - compat -. does not prove .-> isolation - isolation -. does not prove .-> compat -``` - -The release claim requires both evidence classes. A passing `downloads`, `bookmarks`, `history`, storage, service-worker, DNR, or content-script compatibility test does not prove extension isolation. Conversely, a correct Rust extension-grant kernel does not prove that a real Chromium extension API works. - -## Maturity discipline - -Protected main already contains extension-to-Agent authority foundations and pinned-Chromium MV3 compatibility evidence for several surfaces. Issue #27 remains open because the complete declared capability matrix, remaining compatibility surfaces, managed/native-messaging boundaries and release integration are not yet complete. This diagram therefore represents a mixture of implemented foundations and accepted/planned product flow; it must not be read as a claim of full Chrome extension compatibility. diff --git a/tests/test_documentation_active_pr_evidence_contract.py b/tests/test_documentation_active_pr_evidence_contract.py deleted file mode 100644 index d2a067e50..000000000 --- a/tests/test_documentation_active_pr_evidence_contract.py +++ /dev/null @@ -1,196 +0,0 @@ -"""Regression contracts for volatile active-PR evidence in canonical documentation.""" - -from pathlib import Path -import unittest - - -ROOT = Path(__file__).resolve().parents[1] -DOCS = ROOT / "docs" -FITNESS = DOCS / "DOCUMENTATION_FITNESS.md" -MATURITY = DOCS / "evidence" / "2026-08-10-active-pr-maturity.md" - - -def active_pr_row(text: str, pr_number: int) -> str: - """Return exactly one maturity row for an active pull request.""" - prefix = f"| #{pr_number} |" - rows = [line for line in text.splitlines() if line.startswith(prefix)] - if len(rows) != 1: - raise AssertionError( - f"expected exactly one active maturity row for PR #{pr_number}, got {len(rows)}" - ) - return rows[0] - - -class ActivePullRequestDocumentationContractTests(unittest.TestCase): - """Keep volatile implementation evidence separate from protected-main truth.""" - - @classmethod - def setUpClass(cls) -> None: - cls.fitness = FITNESS.read_text(encoding="utf-8") - cls.maturity = MATURITY.read_text(encoding="utf-8") - - def test_dependency_stacks_are_explicit_and_non_shipped(self) -> None: - """Current browser, network, sensitive and compatibility stacks stay active-only.""" - for pr_number in (52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66): - with self.subTest(pr_number=pr_number): - row = active_pr_row(self.maturity, pr_number) - self.assertIn("**IMPLEMENTED_ON_ACTIVE_PR**", row) - self.assertNotIn("IMPLEMENTED_ON_PROTECTED_MAIN", row) - - for stack in ( - "#47 → #50 → #54", - "#45 → #46 → #53 → #55", - "#40→#52→#57→#58", - "#43→#56→#59→#60→#61", - "#51→#66", - ): - with self.subTest(stack=stack): - self.assertIn(stack, self.fitness) - - self.assertIn("#49", self.fitness) - - def test_semantic_relationship_evidence_stays_bounded_and_authority_scoped(self) -> None: - """PR #52 cannot turn relationship metadata into browser or execution authority.""" - row = active_pr_row(self.maturity, 52) - for marker in ("128", "relationship", "session/context/origin/document"): - with self.subTest(marker=marker): - self.assertIn(marker, row) - - for marker in ( - "same browser session, browsing context, canonical origin and document epoch", - "Self-parent/self-child relationships and duplicate child handles fail closed", - "relationship graph remains descriptive evidence", - "not a browser observation adapter", - ): - with self.subTest(marker=marker): - self.assertIn(marker, self.fitness) - - def test_typed_semantic_query_evidence_stays_descriptive_and_bounded(self) -> None: - """PR #57 cannot turn semantic matching into selector or execution authority.""" - row = active_pr_row(self.maturity, 57) - for marker in ( - "SemanticNodeQuery", - "role", - "accessible-name", - "typed-action", - "no CSS/XPath/raw DOM selector language", - "browser I/O or action authority", - ): - with self.subTest(marker=marker): - self.assertIn(marker, row) - - self.assertIn("Draft stacked on exact #52 head", row) - self.assertIn("CI run `31429995885`", row) - self.assertIn("CodeRabbit exact-head status succeed", row) - self.assertIn("remains Draft because #52/#40 are active prerequisites", row) - - def test_sensitive_audience_evidence_does_not_claim_authentication(self) -> None: - """An internal audience field is not authenticated workload/service identity.""" - row = active_pr_row(self.maturity, 55) - self.assertIn("authenticated workload/service identity", row) - self.assertIn( - "audience string accepted by the value/policy primitive is **not authentication**", - self.fitness, - ) - self.assertIn("new deployment topology or physical ERD entity", self.fitness) - - def test_mv3_mutation_and_isolation_are_compatibility_not_agent_authority(self) -> None: - """Real MV3 evidence must remain separate from OriginWeave capability grants.""" - bookmark_row = active_pr_row(self.maturity, 56) - for marker in ("create", "get", "remove", "compatibility evidence only"): - with self.subTest(marker=marker): - self.assertIn(marker, bookmark_row) - - for pr_number in (59, 60, 61): - with self.subTest(pr_number=pr_number): - row = active_pr_row(self.maturity, pr_number) - self.assertIn("**IMPLEMENTED_ON_ACTIVE_PR**", row) - self.assertNotIn("IMPLEMENTED_ON_PROTECTED_MAIN", row) - - self.assertIn("Manifest V3 compatibility", self.fitness) - self.assertIn( - "Chromium permission or browser compatibility success is not an OriginWeave Agent capability", - self.fitness, - ) - self.assertIn( - "#43/#49/#56/#59/#60/#61 are active compatibility evidence only", - self.fitness, - ) - self.assertIn("Update migration is intentionally distinct from restart persistence", self.fitness) - self.assertIn("isolated-world behavior is intentionally distinct from injection alone", self.fitness) - - def test_extension_proposal_grant_does_not_become_agent_policy_authority(self) -> None: - """PR #62 must remain a policy-isolation regression, not a new action grant.""" - row = active_pr_row(self.maturity, 62) - for marker in ( - "ProposeTypedAction", - "out-of-grant target origin", - "missing core `Navigate` capability", - "untrusted instruction source", - "adds no production API or real Chromium adapter", - "does not convert extension proposal authority into Agent action/origin authority", - ): - with self.subTest(marker=marker): - self.assertIn(marker, row) - self.assertIn("CI run `31436844685`", row) - self.assertIn("Security Scan run `31436844615`", row) - self.assertIn("SAST Semgrep run `31436844646`", row) - self.assertNotIn("IMPLEMENTED_ON_PROTECTED_MAIN", row) - - def test_latest_agent_task_and_secret_composition_evidence_remains_partial(self) -> None: - """Newest active slices must not be promoted into a complete browser or broker runtime.""" - secret_approval = active_pr_row(self.maturity, 63) - for marker in ( - "ProposeTypedAction", - "RequireApproval(RiskClass::R3)", - "no secret broker", - ): - with self.subTest(pr_number=63, marker=marker): - self.assertIn(marker, secret_approval) - - action_outcome = active_pr_row(self.maturity, 64) - for marker in ( - "PostConditionPredatesDispatch", - "monotonic", - "not a browser dispatcher", - ): - with self.subTest(pr_number=64, marker=marker): - self.assertIn(marker, action_outcome) - - controlled_fixture = active_pr_row(self.maturity, 65) - for marker in ( - "controlled", - "prompt-injection", - "not a browser adapter", - ): - with self.subTest(pr_number=65, marker=marker): - self.assertIn(marker, controlled_fixture) - - process_set = active_pr_row(self.maturity, 66) - for marker in ( - "process-set RSS", - "duplicate", - "does not discover Chromium PIDs", - ): - with self.subTest(pr_number=66, marker=marker): - self.assertIn(marker, process_set) - - for marker in ( - "#62/#63", - "#64", - "#65", - "#51→#66", - "real Chromium", - ): - with self.subTest(fitness_marker=marker): - self.assertIn(marker, self.fitness) - - def test_erd_stays_conceptual_without_persistence_owner(self) -> None: - """Active in-memory/value primitives must not manufacture a physical data model.""" - self.assertIn("Conceptual ERD/domain model", self.fitness) - self.assertIn("add no OriginWeave-owned durable store", self.fitness) - self.assertIn("false architecture", self.fitness) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_documentation_discoverability_followup.py b/tests/test_documentation_discoverability_followup.py deleted file mode 100644 index 67801c1ce..000000000 --- a/tests/test_documentation_discoverability_followup.py +++ /dev/null @@ -1,43 +0,0 @@ -"""Focused regression contracts for reviewed documentation discoverability gaps.""" - -from __future__ import annotations - -import pathlib -import unittest - -ROOT = pathlib.Path(__file__).resolve().parents[1] - - -class DocumentationDiscoverabilityFollowupTests(unittest.TestCase): - """Keep canonical diagrams and maturity vocabulary machine-discoverable.""" - - def test_extension_authority_view_is_indexed_mermaid(self) -> None: - """The extension authority view must exist, be indexed, and remain diagram-as-code.""" - uml_index = (ROOT / "docs" / "uml" / "README.md").read_text(encoding="utf-8") - authority_view = ROOT / "docs" / "uml" / "extension-authority.md" - - self.assertTrue(authority_view.is_file()) - self.assertIn("](extension-authority.md)", uml_index) - self.assertIn("```mermaid", authority_view.read_text(encoding="utf-8")) - - def test_traceability_keeps_complete_maturity_vocabulary(self) -> None: - """Every canonical capability maturity label must remain explicit.""" - traceability = (ROOT / "docs" / "traceability" / "README.md").read_text( - encoding="utf-8" - ) - for label in ( - "IMPLEMENTED_ON_PROTECTED_MAIN", - "IMPLEMENTED_ON_ACTIVE_PR", - "PARTIAL", - "ACCEPTED_ARCHITECTURE", - "PLANNED", - "RESEARCH_ONLY", - "SUPERSEDED", - "OUT_OF_SCOPE", - ): - with self.subTest(label=label): - self.assertIn(label, traceability) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_documentation_fitness_contract.py b/tests/test_documentation_fitness_contract.py deleted file mode 100644 index 33aefed22..000000000 --- a/tests/test_documentation_fitness_contract.py +++ /dev/null @@ -1,265 +0,0 @@ -"""Regression contracts for the authoritative OriginWeave documentation graph.""" - -from pathlib import Path -import re -import unittest - - -REPOSITORY_ROOT = Path(__file__).resolve().parents[1] -DOCS_ROOT = REPOSITORY_ROOT / "docs" -ADR_ROOT = DOCS_ROOT / "adr" -UML_ROOT = DOCS_ROOT / "uml" - -ADR_STATUSES = {"Proposed", "Accepted", "Superseded", "Deprecated", "Rejected"} - - -def _adr_files() -> set[str]: - """Return every numbered ADR Markdown file currently tracked by the repository.""" - return { - path.name - for path in ADR_ROOT.glob("[0-9][0-9][0-9][0-9]-*.md") - if path.is_file() - } - - -def _adr_file_status(path: Path) -> str: - """Read one ADR's explicit lifecycle status from its metadata header.""" - text = path.read_text(encoding="utf-8") - match = re.search( - r"(?im)^-\s+(?:\*\*Status:\*\*|\*\*Status\*\*:|Status:)\s*(\w+)(?:[;\s].*)?$", - text, - ) - if match is None: - raise AssertionError(f"ADR has no parseable status: {path.name}") - status = match.group(1) - if status not in ADR_STATUSES: - raise AssertionError(f"ADR has unsupported status {status!r}: {path.name}") - return status - - -def _insert_unique(mapping: dict[str, str], path: str, status: str, source: str) -> None: - """Insert one index target while rejecting duplicate or conflicting entries.""" - if path in mapping: - raise AssertionError(f"duplicate ADR index target {path!r} in {source}") - mapping[path] = status - - -def _parse_docs_index(text: str) -> dict[str, str]: - """Parse ADR links from the product documentation index by lifecycle section.""" - mapping: dict[str, str] = {} - current_status: str | None = None - for line in text.splitlines(): - if line.startswith("## "): - current_status = next( - (status for status in ADR_STATUSES if line.startswith(f"## {status}")), - None, - ) - continue - target = re.search(r"\(adr/(\d{4}[-\w]*\.md)\)", line) - if target is not None: - if current_status is None: - raise AssertionError( - f"ADR link {target.group(1)!r} is outside a lifecycle-status section" - ) - _insert_unique(mapping, target.group(1), current_status, "docs/README.md") - return mapping - - -def _parse_adr_index(text: str) -> dict[str, str]: - """Parse the dedicated ADR table into an exact target-to-status mapping.""" - mapping: dict[str, str] = {} - pattern = re.compile( - r"^\|\s*\[\d{4}\]\((\d{4}[-\w]*\.md)\)\s*\|[^|]*\|\s*" - r"(Proposed|Accepted|Superseded|Deprecated|Rejected)(?:[;\s][^|\r\n]*)?\s*\|", - re.MULTILINE, - ) - for path, status in pattern.findall(text): - _insert_unique(mapping, path, status, "docs/adr/README.md") - return mapping - - -def _active_pr_row(text: str, pr_number: int) -> str: - """Return one exact active-PR evidence row from the dated maturity appendix.""" - prefix = f"| #{pr_number} |" - rows = [line for line in text.splitlines() if line.startswith(prefix)] - if len(rows) != 1: - raise AssertionError(f"expected exactly one maturity row for PR #{pr_number}, got {len(rows)}") - return rows[0] - - -class DocumentationFitnessContractTests(unittest.TestCase): - """Keep architecture discovery and implementation-maturity metadata coherent.""" - - def test_documentation_index_links_fitness_assessment(self) -> None: - """The semantic fitness audit must remain discoverable from the docs index.""" - index = (DOCS_ROOT / "README.md").read_text(encoding="utf-8") - self.assertIn("[Documentation fitness assessment](DOCUMENTATION_FITNESS.md)", index) - self.assertTrue((DOCS_ROOT / "DOCUMENTATION_FITNESS.md").is_file()) - - def test_documentation_fitness_distinguishes_design_from_protected_main(self) -> None: - """A broad design pack must not be mislabeled as protected-main closure.""" - assessment = (DOCS_ROOT / "DOCUMENTATION_FITNESS.md").read_text(encoding="utf-8") - self.assertIn("DESIGN-SUFFICIENT", assessment) - self.assertIn("PROTECTED-MAIN-PARTIAL", assessment) - self.assertIn("File existence alone is never sufficient", assessment) - self.assertIn("HTTP lineage", assessment) - self.assertIn("Manifest V3 compatibility", assessment) - self.assertIn("Browser identifier authority", assessment) - self.assertIn("Semantic observation authority", assessment) - self.assertIn("integration before any of these branch repairs become protected-main truth", assessment) - - def test_every_adr_is_indexed_once_with_its_file_status(self) -> None: - """Both canonical indexes must exactly cover ADR files and their lifecycle status.""" - actual_files = _adr_files() - file_status = { - path: _adr_file_status(ADR_ROOT / path) - for path in sorted(actual_files) - } - docs_index = _parse_docs_index((DOCS_ROOT / "README.md").read_text(encoding="utf-8")) - adr_index = _parse_adr_index((ADR_ROOT / "README.md").read_text(encoding="utf-8")) - - self.assertEqual(set(docs_index), actual_files) - self.assertEqual(set(adr_index), actual_files) - self.assertEqual(docs_index, file_status) - self.assertEqual(adr_index, file_status) - - def test_adr_index_does_not_use_change_local_language_as_timeless_authority(self) -> None: - """The protected-main ADR index must not describe its ADRs as only `this change`.""" - adr_index = (ADR_ROOT / "README.md").read_text(encoding="utf-8") - self.assertNotIn("Proposed target-architecture decisions in this change", adr_index) - self.assertIn("Index completeness rule", adr_index) - - def test_proposed_adr_provenance_does_not_promote_branch_to_protected_main(self) -> None: - """Branch-only ADR presence must remain distinct from lifecycle and protected-main truth.""" - docs_index = (DOCS_ROOT / "README.md").read_text(encoding="utf-8") - adr_index = (ADR_ROOT / "README.md").read_text(encoding="utf-8") - - for text in (docs_index, adr_index): - with self.subTest(index="docs" if text is docs_index else "adr"): - self.assertIn("## Proposed architecture decisions", text) - self.assertIn("Protected-main baseline proposed decisions", text) - self.assertNotIn("## Proposed decisions retained on protected main", text) - - docs_branch = docs_index.split( - "### Proposed decisions introduced by this documentation reconciliation", 1 - )[1].split("\n## ", 1)[0] - adr_branch = adr_index.split( - "### Proposed decisions introduced by documentation reconciliation", 1 - )[1].split("\n## ", 1)[0] - for adr_path in ( - "0013-manifest-v3-extension-authority.md", - "0014-architecture-decision-governance.md", - ): - with self.subTest(adr=adr_path): - self.assertIn(adr_path, docs_branch) - self.assertIn(adr_path, adr_branch) - self.assertIn("exist only on this documentation branch until it integrates", adr_index) - - def test_current_replacement_lanes_are_not_promoted_to_protected_main(self) -> None: - """Each active implementation lane must carry its own exact non-shipped maturity mapping.""" - assessment = (DOCS_ROOT / "DOCUMENTATION_FITNESS.md").read_text(encoding="utf-8") - traceability = (DOCS_ROOT / "traceability" / "README.md").read_text(encoding="utf-8") - appendix = (DOCS_ROOT / "evidence" / "2026-08-10-active-pr-maturity.md").read_text( - encoding="utf-8" - ) - - for pr_number in (37, 40, 43, 52, 58, 59): - row = _active_pr_row(appendix, pr_number) - with self.subTest(pr_number=pr_number): - self.assertIn("**IMPLEMENTED_ON_ACTIVE_PR**", row) - self.assertNotIn("IMPLEMENTED_ON_PROTECTED_MAIN", row) - - for marker in ("issue #10", "issue #27", "issue #28"): - with self.subTest(marker=marker): - self.assertTrue(marker in assessment or marker in traceability) - - self.assertIn("IMPLEMENTED_ON_ACTIVE_PR", traceability) - self.assertIn("IMPLEMENTED_ON_PROTECTED_MAIN", traceability) - self.assertIn("Active-PR behavior is never protected-main truth", traceability) - - def test_semantic_observation_lane_stays_non_shipped_and_provenance_bound(self) -> None: - """The semantic observation value object must stay active-only and distinct from browser I/O.""" - appendix = (DOCS_ROOT / "evidence" / "2026-08-10-active-pr-maturity.md").read_text( - encoding="utf-8" - ) - prd = (DOCS_ROOT / "PRD.md").read_text(encoding="utf-8") - row = _active_pr_row(appendix, 52) - - self.assertIn("**IMPLEMENTED_ON_ACTIVE_PR**", row) - self.assertIn("semantic-node observation", row) - self.assertIn("no browser I/O or action dispatch", row) - self.assertNotIn("IMPLEMENTED_ON_PROTECTED_MAIN", row) - self.assertIn("active PR #52", prd) - self.assertIn("not a browser observation adapter", prd) - - def test_action_target_and_history_lanes_preserve_authority_boundaries(self) -> None: - """New active lanes must not turn descriptive or compatibility evidence into authority.""" - assessment = (DOCS_ROOT / "DOCUMENTATION_FITNESS.md").read_text(encoding="utf-8") - appendix = (DOCS_ROOT / "evidence" / "2026-08-10-active-pr-maturity.md").read_text( - encoding="utf-8" - ) - action_row = _active_pr_row(appendix, 58) - history_row = _active_pr_row(appendix, 59) - - self.assertIn("descriptive execution input, not policy authorization", action_row) - self.assertIn("no Agent history capability", history_row) - self.assertIn("business-risk classification", assessment) - self.assertIn("OriginWeave Agent history grant", assessment) - self.assertNotIn("IMPLEMENTED_ON_PROTECTED_MAIN", action_row) - self.assertNotIn("IMPLEMENTED_ON_PROTECTED_MAIN", history_row) - - def test_active_pr_maturity_appendix_tracks_current_dependency_stacks(self) -> None: - """Volatile evidence must retain the current browser/network/sensitive stacks explicitly.""" - appendix = (DOCS_ROOT / "evidence" / "2026-08-10-active-pr-maturity.md").read_text( - encoding="utf-8" - ) - for marker in ("| #52 |", "| #53 |", "| #54 |", "| #55 |", "| #58 |", "| #59 |"): - with self.subTest(marker=marker): - self.assertIn(marker, appendix) - self.assertIn("authenticated workload/service identity", appendix) - self.assertIn( - "formatting-only or metadata-only correction invalidates predecessor-head exactness", - appendix, - ) - - def test_prd_does_not_restore_superseded_active_pr_claims(self) -> None: - """Historical feature branches must not reappear as the current implementation lane.""" - prd = (DOCS_ROOT / "PRD.md").read_text(encoding="utf-8") - self.assertNotIn("Active PR #11", prd) - self.assertNotIn("Active replacement PR #33", prd) - self.assertIn("active replacement PR #37", prd) - self.assertIn("Protected-main purpose-bound sensitive-data policy kernel", prd) - self.assertIn("active PR #43 adds", prd) - - def test_trd_uses_single_status_with_separate_active_pr_evidence(self) -> None: - """Implementation status must not be collapsed with active-development annotations.""" - trd = (DOCS_ROOT / "TRD.md").read_text(encoding="utf-8") - self.assertNotIn("**Planned / active development**", trd) - self.assertNotIn("**Accepted architecture; active development.**", trd) - self.assertIn("Protected-main status", trd) - self.assertIn("Active/non-shipped evidence", trd) - self.assertIn("Active replacement PR #37", trd) - self.assertIn("purpose-bound sensitive-data authority", trd) - - def test_extension_authority_uml_separates_compatibility_from_agent_authority(self) -> None: - """A Chrome permission must never be documented as an Agent capability.""" - diagram = (UML_ROOT / "extension-authority.md").read_text(encoding="utf-8") - self.assertIn("A Chromium extension permission is not an OriginWeave Agent capability", diagram) - self.assertIn("Compatibility evidence is separate from authority evidence", diagram) - self.assertIn("sequenceDiagram", diagram) - self.assertIn("OriginWeave Extension Grant Policy", diagram) - self.assertIn("cannot approve", diagram) - self.assertIn("cannot resolve", diagram) - - def test_fitness_audit_does_not_duplicate_existing_resource_or_hourly_uml(self) -> None: - """The audit must recognize existing product-wide resource and automation diagrams.""" - assessment = (DOCS_ROOT / "DOCUMENTATION_FITNESS.md").read_text(encoding="utf-8") - uml_index = (UML_ROOT / "README.md").read_text(encoding="utf-8") - self.assertIn("resource-pressure/GPU fallback", assessment) - self.assertIn("hourly automation flows", assessment) - self.assertIn("## 9. Resource-pressure and fallback flow", uml_index) - self.assertIn("## 10. Hourly product-development gate-to-model flow", uml_index) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_extension_authority_traceability_contract.py b/tests/test_extension_authority_traceability_contract.py deleted file mode 100644 index 3ca5c5cdd..000000000 --- a/tests/test_extension_authority_traceability_contract.py +++ /dev/null @@ -1,49 +0,0 @@ -"""Regression contract for extension-to-Agent security traceability.""" - -from pathlib import Path -import unittest - - -REPOSITORY_ROOT = Path(__file__).resolve().parents[1] -TRACEABILITY = ( - REPOSITORY_ROOT / "docs" / "traceability" / "extension-authority-security.md" -) - - -class ExtensionAuthorityTraceabilityContractTests(unittest.TestCase): - """Keep compatibility, Agent authority, and secret authority as separate claims.""" - - def test_extension_security_dossier_preserves_maturity_boundaries(self) -> None: - """Active security proofs must never be promoted to protected-main shipped truth.""" - text = TRACEABILITY.read_text(encoding="utf-8") - semantic_text = text.replace("**", "") - - self.assertIn("DESIGN-SUFFICIENT / PROTECTED-MAIN-PARTIAL", semantic_text) - self.assertIn("IMPLEMENTED_ON_PROTECTED_MAIN", text) - self.assertIn("IMPLEMENTED_ON_ACTIVE_PR", text) - self.assertIn("PR #62", text) - self.assertIn("PR #63", text) - self.assertIn("Proposed ADR 0013", text) - self.assertIn("SecretBrokerRequired", text) - self.assertIn("UnexpectedSecretMaterial", text) - self.assertIn("R3 approval", text) - self.assertIn("does not close issue #27 or issue #10", semantic_text) - - def test_extension_proposal_authority_is_explicitly_non_transitive(self) -> None: - """The dossier must forbid proposal permission from becoming broader Agent authority.""" - text = TRACEABILITY.read_text(encoding="utf-8") - - for boundary in ( - "-/> Agent capability", - "-/> Agent readable/writable origin", - "-/> trusted instruction source", - "-/> secret-delivery authority", - "-/> approval", - "-/> protected-value resolution", - ): - with self.subTest(boundary=boundary): - self.assertIn(boundary, text) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_freshness_traceability_contract.py b/tests/test_freshness_traceability_contract.py deleted file mode 100644 index a9e0e4f01..000000000 --- a/tests/test_freshness_traceability_contract.py +++ /dev/null @@ -1,50 +0,0 @@ -"""Regression contracts for bounded freshness-authority documentation.""" - -from __future__ import annotations - -import pathlib -import unittest - -ROOT = pathlib.Path(__file__).resolve().parents[1] -TRACEABILITY = ROOT / "docs" / "traceability" - - -class FreshnessTraceabilityContractTests(unittest.TestCase): - """Keep active freshness primitives discoverable without promoting them to shipped truth.""" - - def test_traceability_index_discovers_each_active_freshness_authority(self) -> None: - """Resolution and TLS freshness traces must be linked from the canonical index.""" - index = (TRACEABILITY / "README.md").read_text(encoding="utf-8") - for filename in ( - "resolution-freshness-authority.md", - "tls-revocation-freshness-authority.md", - ): - with self.subTest(filename=filename): - self.assertTrue((TRACEABILITY / filename).is_file()) - self.assertIn(f"]({filename})", index) - - def test_active_freshness_traces_preserve_protected_main_maturity(self) -> None: - """Active implementation evidence must remain explicitly non-shipped and partial overall.""" - for filename in ( - "resolution-freshness-authority.md", - "tls-revocation-freshness-authority.md", - ): - text = (TRACEABILITY / filename).read_text(encoding="utf-8") - with self.subTest(filename=filename): - self.assertIn("Active-PR traceability", text) - self.assertIn("Protected-main capability status:** **PARTIAL", text) - self.assertIn("IMPLEMENTED_ON_ACTIVE_PR", text) - self.assertIn("not protected-main truth", text) - - def test_resolution_trace_requires_socket_use_freshness_not_only_plan_time(self) -> None: - """The DNS freshness trace must retain the delayed-use boundary added by PR #54.""" - text = (TRACEABILITY / "resolution-freshness-authority.md").read_text(encoding="utf-8") - self.assertIn("Socket-use freshness lane:** PR #54", text) - self.assertIn("connect_at(current_time)", text) - self.assertIn("rechecks the retained freshness authority immediately before socket I/O", text) - self.assertIn("delayed call cannot reuse plan-time freshness", text) - self.assertIn("#47 + #50 + #54", text) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_mv3_supported_capability_matrix_contract.py b/tests/test_mv3_supported_capability_matrix_contract.py deleted file mode 100644 index fcc24db52..000000000 --- a/tests/test_mv3_supported_capability_matrix_contract.py +++ /dev/null @@ -1,97 +0,0 @@ -"""Regression contract for the canonical MV3 supported-capability evidence matrix.""" - -from __future__ import annotations - -import pathlib -import unittest - -ROOT = pathlib.Path(__file__).resolve().parents[1] -DOCTORING = ROOT / "docs" / "doctoring" / "mv3-compatibility.md" -MATURITY = ROOT / "docs" / "evidence" / "2026-08-10-active-pr-maturity.md" - - -class ManifestV3SupportedCapabilityMatrixContractTests(unittest.TestCase): - """Keep compatibility claims executable, maturity-scoped, and authority-safe.""" - - @classmethod - def setUpClass(cls) -> None: - cls.doctoring = DOCTORING.read_text(encoding="utf-8") - cls.maturity = MATURITY.read_text(encoding="utf-8") - - def test_matrix_separates_protected_active_planned_and_out_of_scope(self) -> None: - """The matrix must never collapse active evidence into protected-main support.""" - - for marker in ( - "## Supported-capability evidence matrix", - "**PROTECTED_MAIN**", - "**ACTIVE_PR #43**", - "**ACTIVE_PR #56**", - "**ACTIVE_PR #59**", - "**ACTIVE_PR #60**", - "**ACTIVE_PR #61**", - "**PLANNED**", - "**PLANNED / SECURITY-GATED**", - "**OUT_OF_SCOPE FOR COMPATIBILITY CLAIM**", - ): - with self.subTest(marker=marker): - self.assertIn(marker, self.doctoring) - - def test_update_migration_is_not_documented_as_restart_only(self) -> None: - """Update compatibility requires a version transition plus migrated state.""" - - for marker in ( - "Restart persistence and extension update migration are separate compatibility claims", - "`1.0.0` to `1.0.1`", - "schema marker to migrate from version 1 to version 2", - "checked-in fixture is not rewritten", - ): - with self.subTest(marker=marker): - self.assertIn(marker, self.doctoring) - - row = next( - line for line in self.maturity.splitlines() if line.startswith("| #60 |") - ) - self.assertIn("**IMPLEMENTED_ON_ACTIVE_PR**", row) - self.assertIn("e696e19c9eaf3dedb104a5de4bdbd7970abf90d4", row) - self.assertIn("CI run `31433968874`", row) - self.assertIn("Manifest V3 Compatibility run `31433968931`", row) - self.assertNotIn("IMPLEMENTED_ON_PROTECTED_MAIN", row) - - def test_isolated_world_evidence_stays_active_only(self) -> None: - """Content-script isolation proof must not be promoted into protected-main support.""" - - for marker in ( - "Content-script injection | **PROTECTED_MAIN**", - "Content-script isolated-world separation | **ACTIVE_PR #61**", - "Content-script injection and content-script JavaScript isolation are separate compatibility claims", - "page publisher changes to `extension` and real-browser compatibility fails", - ): - with self.subTest(marker=marker): - self.assertIn(marker, self.doctoring) - - row = next( - line for line in self.maturity.splitlines() if line.startswith("| #61 |") - ) - self.assertIn("**IMPLEMENTED_ON_ACTIVE_PR**", row) - self.assertIn("c1705ad9fd2d96e620b89bb6e7ea1235063dcb6a", row) - self.assertIn("CI run `31434670642`", row) - self.assertIn("Manifest V3 Compatibility run `31434670629`", row) - self.assertIn("3/3 repeatability trials", row) - self.assertNotIn("IMPLEMENTED_ON_PROTECTED_MAIN", row) - - def test_compatibility_never_grants_agent_authority(self) -> None: - """Chrome API success must remain separate from OriginWeave Agent grants.""" - - for marker in ( - "Chrome API permission does not become Agent capability", - "no Agent bookmark capability", - "no Agent history capability", - "does not claim Chrome Web Store/enterprise update semantics or Agent authority", - "no arbitrary page-JavaScript bridge or Agent authority", - ): - with self.subTest(marker=marker): - self.assertTrue(marker in self.doctoring or marker in self.maturity) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_product_documentation_contract.py b/tests/test_product_documentation_contract.py index 1313189ea..66a67d52f 100644 --- a/tests/test_product_documentation_contract.py +++ b/tests/test_product_documentation_contract.py @@ -14,16 +14,9 @@ class ProductDocumentationContractTests(unittest.TestCase): def test_authoritative_product_documentation_graph_exists(self) -> None: """Major product decisions must not require reconstructing chat or PR history.""" required_paths = { - "docs/PRD.md", - "docs/TRD.md", - "docs/adr/README.md", - "docs/uml/README.md", - "docs/erd/README.md", - "docs/traceability/README.md", - "docs/THREAT_MODEL.md", - "docs/TEST_STRATEGY.md", - "docs/OPERABILITY.md", - "docs/API_CONTRACT.md", + "docs/PRD.md", "docs/TRD.md", "docs/adr/README.md", "docs/uml/README.md", + "docs/erd/README.md", "docs/traceability/README.md", "docs/THREAT_MODEL.md", + "docs/TEST_STRATEGY.md", "docs/OPERABILITY.md", "docs/API_CONTRACT.md", "docs/RELEASE_AND_ROLLBACK.md", } missing = sorted(path for path in required_paths if not (ROOT / path).is_file()) @@ -32,305 +25,110 @@ def test_authoritative_product_documentation_graph_exists(self) -> None: def test_root_architecture_links_the_authoritative_product_graph(self) -> None: """Architecture readers must be able to reach requirements, decisions, diagrams, and data.""" architecture = (ROOT / "ARCHITECTURE.md").read_text(encoding="utf-8") - for link in ( - "docs/PRD.md", - "docs/TRD.md", - "docs/adr/README.md", - "docs/uml/README.md", - "docs/erd/README.md", - "docs/traceability/README.md", - ): - with self.subTest(link=link): - self.assertIn(link, architecture) + for link in ("docs/PRD.md", "docs/TRD.md", "docs/adr/README.md", "docs/uml/README.md", "docs/erd/README.md", "docs/traceability/README.md"): + with self.subTest(link=link): self.assertIn(link, architecture) def test_security_policy_links_the_product_threat_model(self) -> None: """Vulnerability reporters and operators must be able to find modeled trust boundaries.""" - self.assertIn( - "docs/THREAT_MODEL.md", - (ROOT / "SECURITY.md").read_text(encoding="utf-8"), - ) + self.assertIn("docs/THREAT_MODEL.md", (ROOT / "SECURITY.md").read_text(encoding="utf-8")) def test_agent_contract_is_work_conserving_instead_of_one_action_per_run(self) -> None: """Finishing one bounded slice must return maintenance to the live queue.""" contract = (ROOT / "AGENTS.md").read_text(encoding="utf-8") - for phrase in ( - "A completed action is an intermediate state", - "one write-active slice at a time", - "Mandatory exit sweep", - "termination is prohibited", - "blocks only that item", - ): - with self.subTest(phrase=phrase): - self.assertIn(phrase, contract) + for phrase in ("A completed action is an intermediate state", "one write-active slice at a time", "Mandatory exit sweep", "termination is prohibited", "blocks only that item"): + with self.subTest(phrase=phrase): self.assertIn(phrase, contract) def test_prd_covers_product_family_modes_and_buyer_acceptance(self) -> None: """The PRD must describe the actual product family rather than one kernel slice.""" prd = (ROOT / "docs/PRD.md").read_text(encoding="utf-8") - for phrase in ( - "Browse. Act. Prove.", - "Human Mode", - "Assist Mode", - "Agent Task Mode", - "Crawler Mode", - "OriginWeave Browser", - "OriginWeave Runtime", - "OriginWeave Observe", - "OriginWeave Capture", - "OriginWeave Governor", - "OriginWeave Policy", - "OriginWeave Evidence", - "OriginWeave Protocol", - "Non-goals", - "Buyer-visible acceptance", - ): - with self.subTest(phrase=phrase): - self.assertIn(phrase, prd) + for phrase in ("Browse. Act. Prove.", "Human Mode", "Assist Mode", "Agent Task Mode", "Crawler Mode", "OriginWeave Browser", "OriginWeave Runtime", "OriginWeave Observe", "OriginWeave Capture", "OriginWeave Governor", "OriginWeave Policy", "OriginWeave Evidence", "OriginWeave Protocol", "Non-goals", "Buyer-visible acceptance"): + with self.subTest(phrase=phrase): self.assertIn(phrase, prd) def test_trd_distinguishes_shipped_architecture_from_future_work(self) -> None: """Technical documentation must not silently describe planned work as shipped.""" trd = (ROOT / "docs/TRD.md").read_text(encoding="utf-8") - for phrase in ( - "Implemented", - "Accepted architecture", - "Planned", - "logical origin", - "resolved destination", - "TCP peer", - "TLS service identity", - "WebDriver BiDi", - "Chrome DevTools Protocol", - "WebMCP", - "Model Context Protocol", - "NVIDIA_NIM_API_KEY", - "COPILOT_GITHUB_TOKEN", - ): - with self.subTest(phrase=phrase): - self.assertIn(phrase, trd) + for phrase in ("Implemented", "Accepted architecture", "Planned", "logical origin", "resolved destination", "TCP peer", "TLS service identity", "WebDriver BiDi", "Chrome DevTools Protocol", "WebMCP", "Model Context Protocol", "NVIDIA_NIM_API_KEY", "COPILOT_GITHUB_TOKEN"): + with self.subTest(phrase=phrase): self.assertIn(phrase, trd) def test_target_architecture_adr_set_is_detailed(self) -> None: """Product direction must be reconstructable from durable, reviewable decisions.""" required_adrs = { - "docs/adr/0001-chromium-compatibility-kernel.md": ( - "Chromium", - "browser-engine rewrite", - ), - "docs/adr/0100-rust-control-plane-boundary.md": ( - "Rust control plane", - "Chromium compatibility kernel", - ), - "docs/adr/0101-isolated-execution-profile-modes.md": ( - "Human", - "Assist", - "Agent Task", - "Crawler", - ), - "docs/adr/0102-typed-actions-and-arbitrary-js.md": ( - "typed action", - "arbitrary JavaScript", - ), - "docs/adr/0103-semantic-observation-and-stale-node-identity.md": ( - "WebMCP", - "accessibility", - "document epoch", - "stale", - ), - "docs/adr/0104-prompt-injection-and-secret-authority.md": ( - "prompt injection", - "opaque", - "secret", - ), - "docs/adr/0105-resource-governor-priority.md": ( - "resource governor", - "GPU", - "browser", - "model", - ), - "docs/adr/0106-provenance-evidence-model.md": ( - "WARC", - "PROV", - "evidence", - ), - "docs/adr/0107-browser-protocol-adapter-strategy.md": ( - "WebDriver BiDi", - "Chrome DevTools Protocol", - "WebMCP", - "Model Context Protocol", - ), + "docs/adr/0001-chromium-compatibility-kernel.md": ("Chromium", "browser-engine rewrite"), + "docs/adr/0100-rust-control-plane-boundary.md": ("Rust control plane", "Chromium compatibility kernel"), + "docs/adr/0101-isolated-execution-profile-modes.md": ("Human", "Assist", "Agent Task", "Crawler"), + "docs/adr/0102-typed-actions-and-arbitrary-js.md": ("typed action", "arbitrary JavaScript"), + "docs/adr/0103-semantic-observation-and-stale-node-identity.md": ("WebMCP", "accessibility", "document epoch", "stale"), + "docs/adr/0104-prompt-injection-and-secret-authority.md": ("prompt injection", "opaque", "secret"), + "docs/adr/0105-resource-governor-priority.md": ("resource governor", "GPU", "browser", "model"), + "docs/adr/0106-provenance-evidence-model.md": ("WARC", "PROV", "evidence"), + "docs/adr/0107-browser-protocol-adapter-strategy.md": ("WebDriver BiDi", "Chrome DevTools Protocol", "WebMCP", "Model Context Protocol"), "docs/adr/0108-crawler-policy.md": ("robots", "rate", "CAPTCHA"), - "docs/adr/0109-hourly-automation-operational-closure.md": ( - "NVIDIA_NIM_API_KEY", - "protected-main", - "open_pull_request", - ), + "docs/adr/0109-hourly-automation-operational-closure.md": ("NVIDIA_NIM_API_KEY", "protected-main", "open_pull_request"), } - sections = ( - "## Context", - "## Options considered", - "## Decision", - "## Consequences", - "## Failure and degraded behavior", - "## Security / privacy / governance impact", - "## Tests and acceptance evidence", - "## Migration and rollback", - "## Supersession / reversal conditions", - ) + sections = ("## Context", "## Options considered", "## Decision", "## Consequences", "## Failure and degraded behavior", "## Security / privacy / governance impact", "## Tests and acceptance evidence", "## Migration and rollback", "## Supersession / reversal conditions") fields = ("- Status:", "- Date:", "- Supersedes:", "- Superseded by:") for path, phrases in required_adrs.items(): with self.subTest(path=path): text = (ROOT / path).read_text(encoding="utf-8") - for field in fields: - self.assertIn(field, text) - for section in sections: - self.assertIn(section, text) - for phrase in phrases: - self.assertIn(phrase, text) + for field in fields: self.assertIn(field, text) + for section in sections: self.assertIn(section, text) + for phrase in phrases: self.assertIn(phrase, text) def test_stale_node_adr_defines_action_linearization_race(self) -> None: """A mutation between handle validation and dispatch must never produce a stale side effect.""" - adr = ( - ROOT / "docs/adr/0103-semantic-observation-and-stale-node-identity.md" - ).read_text(encoding="utf-8") - for phrase in ( - "action linearization point", - "side effect", - "competing mutation", - "re-observation", - ): - with self.subTest(phrase=phrase): - self.assertIn(phrase, adr) + adr = (ROOT / "docs/adr/0103-semantic-observation-and-stale-node-identity.md").read_text(encoding="utf-8") + for phrase in ("action linearization point", "side effect", "competing mutation", "re-observation"): + with self.subTest(phrase=phrase): self.assertIn(phrase, adr) def test_hourly_automation_adr_requires_exit_sweep(self) -> None: """Automation closure must re-sweep all actionable lanes instead of stopping after one result.""" - adr = ( - ROOT / "docs/adr/0109-hourly-automation-operational-closure.md" - ).read_text(encoding="utf-8") - for phrase in ( - "mandatory exit sweep", - "open OriginWeave PRs and issues", - "release state", - "documentation", - "product gaps", - "safe actionable work remains", - ): - with self.subTest(phrase=phrase): - self.assertIn(phrase, adr) + adr = (ROOT / "docs/adr/0109-hourly-automation-operational-closure.md").read_text(encoding="utf-8") + for phrase in ("mandatory exit sweep", "open OriginWeave PRs and issues", "release state", "documentation", "product gaps", "safe actionable work remains"): + with self.subTest(phrase=phrase): self.assertIn(phrase, adr) def test_uml_and_erd_are_diagram_as_code(self) -> None: """Architecture flows and the conceptual domain model must be reviewable in Git.""" uml = (ROOT / "docs/uml/README.md").read_text(encoding="utf-8") - authority_view = ROOT / "docs/uml/extension-authority.md" - self.assertTrue(authority_view.is_file()) - self.assertIn("](extension-authority.md)", uml) - self.assertIn("```mermaid", authority_view.read_text(encoding="utf-8")) - erd = (ROOT / "docs/erd/README.md").read_text(encoding="utf-8") - self.assertGreaterEqual(uml.count("```mermaid"), 8) - self.assertIn("sequenceDiagram", uml) - self.assertIn("stateDiagram-v2", uml) - for heading in ( - "Secret-fill sequence", - "Read/write risk approval flow", - "Resource-pressure and fallback flow", - "Hourly product-development gate-to-model flow", - ): - with self.subTest(heading=heading): - self.assertIn(heading, uml) + self.assertGreaterEqual(uml.count("```mermaid"), 8); self.assertIn("sequenceDiagram", uml); self.assertIn("stateDiagram-v2", uml) + for heading in ("Secret-fill sequence", "Read/write risk approval flow", "Resource-pressure and fallback flow", "Hourly product-development gate-to-model flow"): + with self.subTest(heading=heading): self.assertIn(heading, uml) self.assertIn("erDiagram", erd) - for entity in ( - "agent_session", - "browser_profile", - "page_snapshot", - "semantic_node", - "action_event", - "policy_decision", - "provenance_record", - "resource_budget", - ): - with self.subTest(entity=entity): - self.assertIn(entity, erd) + for entity in ("agent_session", "browser_profile", "page_snapshot", "semantic_node", "action_event", "policy_decision", "provenance_record", "resource_budget"): + with self.subTest(entity=entity): self.assertIn(entity, erd) def test_hourly_uml_fails_closed_before_secret_or_publication(self) -> None: """Denied credentials and failed validation must terminate before secret use or publication.""" uml = (ROOT / "docs/uml/README.md").read_text(encoding="utf-8") - for phrase in ( - "credential denied or broker unavailable", - "stop without secret materialization", - "validation failed", - "fail closed without publication", - "validation passed", - ): - with self.subTest(phrase=phrase): - self.assertIn(phrase, uml) + for phrase in ("credential denied or broker unavailable", "stop without secret materialization", "validation failed", "fail closed without publication", "validation passed"): + with self.subTest(phrase=phrase): self.assertIn(phrase, uml) def test_operational_documents_preserve_fail_closed_product_boundaries(self) -> None: """Security, operations, APIs, tests, and rollback must agree on core authority boundaries.""" documents = { - "docs/THREAT_MODEL.md": ( - "renderer compromise", - "prompt injection", - "confused deputy", - "cross-tenant", - ), - "docs/TEST_STRATEGY.md": ( - "true production boundary", - "100%", - "hostile", - "protected-main", - ), + "docs/THREAT_MODEL.md": ("renderer compromise", "prompt injection", "confused deputy", "cross-tenant"), + "docs/TEST_STRATEGY.md": ("true production boundary", "100%", "hostile", "protected-main"), "docs/OPERABILITY.md": ("SLI", "SLO", "quarantine", "break-glass"), - "docs/API_CONTRACT.md": ( - "OriginWeave Protocol", - "idempotency", - "post-condition", - "opaque", - ), - "docs/RELEASE_AND_ROLLBACK.md": ( - "SBOM", - "provenance", - "rollback", - "protected main", - ), + "docs/API_CONTRACT.md": ("OriginWeave Protocol", "idempotency", "post-condition", "opaque"), + "docs/RELEASE_AND_ROLLBACK.md": ("SBOM", "provenance", "rollback", "protected main"), } for path, phrases in documents.items(): text = (ROOT / path).read_text(encoding="utf-8") for phrase in phrases: - with self.subTest(path=path, phrase=phrase): - self.assertIn(phrase, text) + with self.subTest(path=path, phrase=phrase): self.assertIn(phrase, text) def test_release_contract_never_bypasses_evidence_or_reproducibility(self) -> None: """Emergency release handling must preserve exact-head gates and reproducible artifacts.""" release = (ROOT / "docs/RELEASE_AND_ROLLBACK.md").read_text(encoding="utf-8") - for phrase in ( - "Emergency releases do not bypass required gates", - "current-head checks", - "complete coverage", - "branch protection", - "reproducible artifact", - "nondeterministic signing", - ): - with self.subTest(phrase=phrase): - self.assertIn(phrase, release) + for phrase in ("Emergency releases do not bypass required gates", "current-head checks", "complete coverage", "branch protection", "reproducible artifact", "nondeterministic signing"): + with self.subTest(phrase=phrase): self.assertIn(phrase, release) self.assertNotIn("residual unrun evidence", release) def test_traceability_labels_conversation_derived_future_work(self) -> None: - """Conversation decisions must preserve canonical maturity instead of becoming shipped claims.""" + """Conversation decisions must preserve implementation status instead of becoming claims.""" traceability = (ROOT / "docs/traceability/README.md").read_text(encoding="utf-8") - for phrase in ( - "IMPLEMENTED_ON_PROTECTED_MAIN", - "IMPLEMENTED_ON_ACTIVE_PR", - "PARTIAL", - "ACCEPTED_ARCHITECTURE", - "PLANNED", - "RESEARCH_ONLY", - "SUPERSEDED", - "OUT_OF_SCOPE", - "conversation-derived", - "docs/doctoring.md", - "Active-PR behavior is never protected-main truth", - ): - with self.subTest(phrase=phrase): - self.assertIn(phrase, traceability) + for phrase in ("Implemented", "Accepted architecture", "Proposed", "Open", "conversation-derived", "docs/doctoring.md"): + with self.subTest(phrase=phrase): self.assertIn(phrase, traceability) -if __name__ == "__main__": - unittest.main() +if __name__ == "__main__": unittest.main() From e376124553db072d3914825db1ef9049cccb5b39 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 14 Aug 2026 09:35:20 +0900 Subject: [PATCH 020/111] test(ci): reject impossible workflow source paths --- ...kflow_registry_repository_path_contract.py | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 tests/test_workflow_registry_repository_path_contract.py diff --git a/tests/test_workflow_registry_repository_path_contract.py b/tests/test_workflow_registry_repository_path_contract.py new file mode 100644 index 000000000..4147d768e --- /dev/null +++ b/tests/test_workflow_registry_repository_path_contract.py @@ -0,0 +1,81 @@ +"""Fail-closed contracts for repository-owned GitHub Actions workflow paths.""" + +from __future__ import annotations + +import importlib.util +import pathlib +import unittest + +ROOT = pathlib.Path(__file__).resolve().parents[1] +SCRIPT = ROOT / "scripts/ci/audit_workflow_registry.py" +DEFAULT_SHA = "0c376acf059be9ddddddfbde1d0189e4f39ef014" + + +def _load_module(): + """Load the read-only registry auditor without packaging scripts as modules.""" + + spec = importlib.util.spec_from_file_location("audit_workflow_registry", SCRIPT) + if spec is None or spec.loader is None: + raise AssertionError("workflow registry audit module is not loadable") + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return module + + +def _payload(path: str) -> dict: + """Build one complete registry payload containing the supplied active record.""" + + return { + "schema_version": 1, + "expected_default_branch_sha": DEFAULT_SHA, + "observed_default_branch_sha": DEFAULT_SHA, + "observed_at": "2026-08-14T00:00:00Z", + "reported_total_count": 1, + "protected_workflow_paths": [".github/workflows/ci.yml"], + "active_pr_workflow_paths": [], + "registry_pages": [ + { + "page": 1, + "status_code": 200, + "has_next": False, + "workflows": [ + {"id": 7001, "name": "candidate", "path": path, "state": "active"} + ], + } + ], + } + + +class WorkflowRegistryRepositoryPathTests(unittest.TestCase): + """Prevent impossible repository paths from becoming disable candidates.""" + + @classmethod + def setUpClass(cls) -> None: + cls.audit = _load_module() + + def test_repository_workflow_candidates_require_direct_yaml_files(self) -> None: + """Only direct .yml/.yaml files under .github/workflows are repository workflows.""" + + for invalid_path in ( + ".github/workflows/no-extension", + ".github/workflows/not-yaml.json", + ".github/workflows/nested/child.yml", + ".github/workflows/nested/child.yaml", + ): + with self.subTest(path=invalid_path): + with self.assertRaises(self.audit.WorkflowAuditError): + self.audit.audit_workflow_registry(_payload(invalid_path)) + + def test_both_supported_yaml_extensions_remain_classifiable(self) -> None: + """GitHub-supported .yml and .yaml workflow files remain valid evidence.""" + + for path in (".github/workflows/legacy.yml", ".github/workflows/legacy.yaml"): + with self.subTest(path=path): + evidence = self.audit.audit_workflow_registry(_payload(path)) + record = evidence["workflow_records"][0] + self.assertEqual(record["classification"], "active_orphan_repository_workflow") + self.assertTrue(record["disable_candidate"]) + + +if __name__ == "__main__": + unittest.main() From ef77785ff9277a71c8efee953ef9ca9eba85fd57 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 14 Aug 2026 09:38:42 +0900 Subject: [PATCH 021/111] fix(ci): validate repository workflow source shape --- scripts/ci/audit_workflow_registry.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/scripts/ci/audit_workflow_registry.py b/scripts/ci/audit_workflow_registry.py index 204d7e2e0..5f6e60791 100644 --- a/scripts/ci/audit_workflow_registry.py +++ b/scripts/ci/audit_workflow_registry.py @@ -108,6 +108,11 @@ def _validate_workflow_path(value: Any, field_name: str) -> str: if any(segment in {"", ".", ".."} for segment in segments): raise WorkflowAuditError(f"{field_name} contains an ambiguous path segment") if path.startswith(_REPOSITORY_WORKFLOW_PREFIX): + workflow_name = path[len(_REPOSITORY_WORKFLOW_PREFIX) :] + if "/" in workflow_name or not workflow_name.endswith((".yml", ".yaml")): + raise WorkflowAuditError( + f"{field_name} must name one direct .yml or .yaml workflow file" + ) return path if path.startswith(_DYNAMIC_WORKFLOW_PREFIX): return path From d7f2d2051c87c24be477b14320108dedfd4a8ca4 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 15 Aug 2026 11:08:21 +0900 Subject: [PATCH 022/111] test(ci): reject boolean integer lookalikes in workflow evidence --- ...workflow_registry_integer_type_contract.py | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 tests/test_workflow_registry_integer_type_contract.py diff --git a/tests/test_workflow_registry_integer_type_contract.py b/tests/test_workflow_registry_integer_type_contract.py new file mode 100644 index 000000000..60ba0c7de --- /dev/null +++ b/tests/test_workflow_registry_integer_type_contract.py @@ -0,0 +1,72 @@ +"""Regression contracts for strict workflow-registry integer fields.""" + +from __future__ import annotations + +import importlib.util +import pathlib +import unittest + +ROOT = pathlib.Path(__file__).resolve().parents[1] +SCRIPT = ROOT / "scripts/ci/audit_workflow_registry.py" +DEFAULT_SHA = "67af7c87589edc2039545af335c95064d9b8391c" + + +def _load_module(): + """Load the audit utility without making scripts a Python package.""" + + spec = importlib.util.spec_from_file_location("audit_workflow_registry", SCRIPT) + if spec is None or spec.loader is None: + raise AssertionError("workflow registry audit module is not loadable") + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return module + + +def _payload() -> dict: + """Return one minimal complete audit fixture.""" + + return { + "schema_version": 1, + "expected_default_branch_sha": DEFAULT_SHA, + "observed_default_branch_sha": DEFAULT_SHA, + "observed_at": "2026-08-12T00:00:00Z", + "reported_total_count": 0, + "protected_workflow_paths": [], + "active_pr_workflow_paths": [], + "registry_pages": [ + { + "page": 1, + "status_code": 200, + "has_next": False, + "workflows": [], + } + ], + } + + +class WorkflowRegistryIntegerTypeContractTests(unittest.TestCase): + """Reject Python booleans where the evidence schema requires integers.""" + + @classmethod + def setUpClass(cls) -> None: + cls.audit = _load_module() + + def test_boolean_schema_version_is_rejected(self) -> None: + """JSON true must not compare equal to schema version integer 1.""" + + payload = _payload() + payload["schema_version"] = True + with self.assertRaises(self.audit.WorkflowAuditError): + self.audit.audit_workflow_registry(payload) + + def test_boolean_page_number_is_rejected(self) -> None: + """JSON true must not compare equal to registry page integer 1.""" + + payload = _payload() + payload["registry_pages"][0]["page"] = True + with self.assertRaises(self.audit.WorkflowAuditError): + self.audit.audit_workflow_registry(payload) + + +if __name__ == "__main__": + unittest.main() From 77b63fd22207298136f9460c623a8b17f0eaba16 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 15 Aug 2026 11:10:03 +0900 Subject: [PATCH 023/111] fix(ci): reject boolean integer lookalikes in workflow evidence --- scripts/ci/audit_workflow_registry.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/scripts/ci/audit_workflow_registry.py b/scripts/ci/audit_workflow_registry.py index 5f6e60791..a82765160 100644 --- a/scripts/ci/audit_workflow_registry.py +++ b/scripts/ci/audit_workflow_registry.py @@ -148,7 +148,12 @@ def _validate_pages(value: Any) -> tuple[list[dict[str, Any]], list[dict[str, An for index, raw_page in enumerate(raw_pages): page = _require_mapping(raw_page, f"registry_pages[{index}]") expected_page = index + 1 - if page.get("page") != expected_page: + page_number = page.get("page") + if ( + isinstance(page_number, bool) + or not isinstance(page_number, int) + or page_number != expected_page + ): raise WorkflowAuditError("registry_pages must be contiguous and start at page 1") if page.get("status_code") != 200: raise WorkflowAuditError( @@ -259,7 +264,12 @@ def audit_workflow_registry(payload: dict[str, Any]) -> dict[str, Any]: """ document = _require_mapping(payload, "payload") - if document.get("schema_version") != _SCHEMA_VERSION: + schema_version = document.get("schema_version") + if ( + isinstance(schema_version, bool) + or not isinstance(schema_version, int) + or schema_version != _SCHEMA_VERSION + ): raise WorkflowAuditError("unsupported schema_version") expected_sha = _validate_sha( From c2260800bc40e157239862055230f348880e3d69 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 15 Aug 2026 11:11:59 +0900 Subject: [PATCH 024/111] docs(changelog): record workflow registry audit evidence --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e4bd39ca..29bb5a4b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ All notable changes to OriginWeave are documented in this file. The format follo ### Added - Rust workspace for independently reusable core, policy, destination, network, TLS, resource, and evidence modules. +- Read-only workflow-registry audit evidence that binds complete paginated GitHub Actions inventory to exact protected-main state and fails closed on ambiguous scalar types, workflow identities, paths, timestamps, and ownership. - Canonical HTTPS and loopback-origin boundary with case-normalized schemes and hosts, default-port normalization, IPv4/IPv6 handling, browser-special numeric-host rejection, and explicit malformed-input errors. - Typed browser actions, capabilities, risk classes, execution modes, robots decisions, secret-delivery contracts, immutable canonical action-intent digests, and intent-bound approval scopes. - Deterministic fail-closed policy evaluation for untrusted instructions, origin grants, crawler restrictions, execution-mode and purpose consistency, approvals, and brokered secrets. From 51749a9ec6912da1b3fc27071fa2eed3d22c8863 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 15 Aug 2026 12:04:42 +0900 Subject: [PATCH 025/111] test(ci): reject invisible workflow audit text --- ..._registry_unicode_presentation_contract.py | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 tests/test_workflow_registry_unicode_presentation_contract.py diff --git a/tests/test_workflow_registry_unicode_presentation_contract.py b/tests/test_workflow_registry_unicode_presentation_contract.py new file mode 100644 index 000000000..fdb6996b5 --- /dev/null +++ b/tests/test_workflow_registry_unicode_presentation_contract.py @@ -0,0 +1,78 @@ +"""Reject invisible Unicode controls from workflow audit presentation evidence.""" + +from __future__ import annotations + +import importlib.util +import pathlib +import unittest + +ROOT = pathlib.Path(__file__).resolve().parents[1] +SCRIPT = ROOT / "scripts" / "ci" / "audit_workflow_registry.py" +DEFAULT_SHA = "0c376acf059be9ddddddfbde1d0189e4f39ef014" + + +def _load_module(): + """Load the read-only registry auditor without packaging scripts as modules.""" + + spec = importlib.util.spec_from_file_location("audit_workflow_registry", SCRIPT) + if spec is None or spec.loader is None: + raise AssertionError("workflow registry audit module is not loadable") + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return module + + +def _payload(*, name: str = "candidate", path: str = ".github/workflows/orphan.yml") -> dict: + """Build one complete registry payload containing one active orphan record.""" + + return { + "schema_version": 1, + "expected_default_branch_sha": DEFAULT_SHA, + "observed_default_branch_sha": DEFAULT_SHA, + "observed_at": "2026-08-15T02:00:00Z", + "reported_total_count": 1, + "protected_workflow_paths": [".github/workflows/ci.yml"], + "active_pr_workflow_paths": [], + "registry_pages": [ + { + "page": 1, + "status_code": 200, + "has_next": False, + "workflows": [ + {"id": 9001, "name": name, "path": path, "state": "active"} + ], + } + ], + } + + +class WorkflowRegistryUnicodePresentationTests(unittest.TestCase): + """Keep operator-facing workflow evidence free of invisible format controls.""" + + @classmethod + def setUpClass(cls) -> None: + cls.audit = _load_module() + + def test_bidi_and_zero_width_controls_fail_closed_in_names_and_paths(self) -> None: + """Invisible Unicode formatting must not survive into canonical audit evidence.""" + + for field, value in ( + ("name", "legacy\u202eworkflow"), + ("name", "legacy\u200bworkflow"), + ("path", ".github/workflows/legacy\u202e.yml"), + ("path", ".github/workflows/legacy\u200b.yml"), + ): + with self.subTest(field=field, value=value): + payload = _payload(**{field: value}) + with self.assertRaises(self.audit.WorkflowAuditError): + self.audit.audit_workflow_registry(payload) + + def test_printable_unicode_workflow_name_remains_valid(self) -> None: + """Ordinary printable Unicode labels remain usable as non-authoritative display text.""" + + evidence = self.audit.audit_workflow_registry(_payload(name="릴리즈 점검 🚦")) + self.assertEqual(evidence["workflow_records"][0]["name"], "릴리즈 점검 🚦") + + +if __name__ == "__main__": + unittest.main() From 519c36ae04b2dbbbe39103b0351d2ddc4c2acd62 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 15 Aug 2026 12:07:09 +0900 Subject: [PATCH 026/111] fix(ci): reject invisible workflow audit controls --- scripts/ci/audit_workflow_registry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/ci/audit_workflow_registry.py b/scripts/ci/audit_workflow_registry.py index a82765160..a7093ade0 100644 --- a/scripts/ci/audit_workflow_registry.py +++ b/scripts/ci/audit_workflow_registry.py @@ -61,7 +61,7 @@ def _require_nonempty_string(value: Any, field_name: str, maximum: int) -> str: raise WorkflowAuditError(f"{field_name} must be a string") if not value or value != value.strip() or len(value.encode("utf-8")) > maximum: raise WorkflowAuditError(f"{field_name} is invalid") - if any(ord(character) < 0x20 or ord(character) == 0x7F for character in value): + if any(not character.isprintable() for character in value): raise WorkflowAuditError(f"{field_name} contains a control character") return value From 1fd8dae3a7ddf4f8752e6a5c718fe81aa0c25be9 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 15 Aug 2026 13:04:31 +0900 Subject: [PATCH 027/111] test(ci): reject float workflow registry status codes --- tests/test_workflow_registry_integer_type_contract.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tests/test_workflow_registry_integer_type_contract.py b/tests/test_workflow_registry_integer_type_contract.py index 60ba0c7de..738adda5a 100644 --- a/tests/test_workflow_registry_integer_type_contract.py +++ b/tests/test_workflow_registry_integer_type_contract.py @@ -45,7 +45,7 @@ def _payload() -> dict: class WorkflowRegistryIntegerTypeContractTests(unittest.TestCase): - """Reject Python booleans where the evidence schema requires integers.""" + """Reject ambiguous JSON scalar types where the evidence schema requires integers.""" @classmethod def setUpClass(cls) -> None: @@ -67,6 +67,14 @@ def test_boolean_page_number_is_rejected(self) -> None: with self.assertRaises(self.audit.WorkflowAuditError): self.audit.audit_workflow_registry(payload) + def test_float_http_status_code_is_rejected(self) -> None: + """JSON 200.0 must not masquerade as the required integer HTTP status code 200.""" + + payload = _payload() + payload["registry_pages"][0]["status_code"] = 200.0 + with self.assertRaises(self.audit.WorkflowAuditError): + self.audit.audit_workflow_registry(payload) + if __name__ == "__main__": unittest.main() From b1b5488887940925841e4eb90b6761c301d7c533 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 15 Aug 2026 13:05:50 +0900 Subject: [PATCH 028/111] fix(ci): require integer workflow registry status codes --- scripts/ci/audit_workflow_registry.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/scripts/ci/audit_workflow_registry.py b/scripts/ci/audit_workflow_registry.py index a7093ade0..dcddff8fc 100644 --- a/scripts/ci/audit_workflow_registry.py +++ b/scripts/ci/audit_workflow_registry.py @@ -155,7 +155,12 @@ def _validate_pages(value: Any) -> tuple[list[dict[str, Any]], list[dict[str, An or page_number != expected_page ): raise WorkflowAuditError("registry_pages must be contiguous and start at page 1") - if page.get("status_code") != 200: + status_code = page.get("status_code") + if ( + isinstance(status_code, bool) + or not isinstance(status_code, int) + or status_code != 200 + ): raise WorkflowAuditError( f"registry page {expected_page} did not return HTTP 200" ) From 5e2437c0336016c988090faaf84e9f4d0c66ac5e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 15 Aug 2026 13:14:46 +0900 Subject: [PATCH 029/111] test(ci): reject nonstandard JSON numeric constants --- ...rkflow_registry_duplicate_json_contract.py | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/test_workflow_registry_duplicate_json_contract.py b/tests/test_workflow_registry_duplicate_json_contract.py index 88b88d2fc..c7c8dcac9 100644 --- a/tests/test_workflow_registry_duplicate_json_contract.py +++ b/tests/test_workflow_registry_duplicate_json_contract.py @@ -53,6 +53,29 @@ def test_duplicate_top_level_name_fails_closed(self) -> None: ): self.audit._read_payload(path) + def test_nonstandard_nan_constant_fails_closed(self) -> None: + """Python's permissive NaN token must not be accepted as JSON evidence.""" + + document = ( + '{"schema_version":1,' + f'"expected_default_branch_sha":"{DEFAULT_SHA}",' + f'"observed_default_branch_sha":"{DEFAULT_SHA}",' + '"observed_at":"2026-08-12T00:00:00Z",' + '"reported_total_count":0,' + '"protected_workflow_paths":[],' + '"active_pr_workflow_paths":[],' + '"registry_pages":[{"page":1,"status_code":200,' + '"has_next":false,"workflows":[]}],' + '"unexpected":NaN}' + ) + with tempfile.TemporaryDirectory() as directory: + path = pathlib.Path(directory) / "registry.json" + path.write_text(document, encoding="utf-8") + with self.assertRaisesRegex( + self.audit.WorkflowAuditError, "input is not readable UTF-8 JSON" + ): + self.audit._read_payload(path) + def test_parser_recursion_exhaustion_fails_as_audit_error(self) -> None: """Parser recursion exhaustion must not escape as an unbounded traceback.""" From 80c2730dff6244bb08e86eaf7acccf1eee66795d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 15 Aug 2026 13:17:16 +0900 Subject: [PATCH 030/111] fix(ci): reject nonstandard JSON numeric constants --- scripts/ci/audit_workflow_registry.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/scripts/ci/audit_workflow_registry.py b/scripts/ci/audit_workflow_registry.py index dcddff8fc..e3d4fc728 100644 --- a/scripts/ci/audit_workflow_registry.py +++ b/scripts/ci/audit_workflow_registry.py @@ -344,6 +344,12 @@ def _reject_duplicate_object_members(pairs: list[tuple[str, Any]]) -> dict[str, return result +def _reject_nonstandard_json_constant(value: str) -> Any: + """Reject numeric constants that standards-conforming JSON does not define.""" + + raise json.JSONDecodeError("non-standard JSON numeric constant", value, 0) + + def _read_payload(path: pathlib.Path) -> dict[str, Any]: """Read at most four mebibytes of unambiguous UTF-8 JSON audit evidence.""" @@ -358,6 +364,7 @@ def _read_payload(path: pathlib.Path) -> dict[str, Any]: parsed = json.loads( content.decode("utf-8"), object_pairs_hook=_reject_duplicate_object_members, + parse_constant=_reject_nonstandard_json_constant, ) except (UnicodeError, json.JSONDecodeError, RecursionError) as error: raise WorkflowAuditError("input is not readable UTF-8 JSON") from error From 09f1507531f1712d124125a2e2b3fe0975b6a83f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 15 Aug 2026 13:21:42 +0900 Subject: [PATCH 031/111] test(ci): bound workflow registry JSON integers --- ...rkflow_registry_duplicate_json_contract.py | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/test_workflow_registry_duplicate_json_contract.py b/tests/test_workflow_registry_duplicate_json_contract.py index c7c8dcac9..1dd76c794 100644 --- a/tests/test_workflow_registry_duplicate_json_contract.py +++ b/tests/test_workflow_registry_duplicate_json_contract.py @@ -76,6 +76,29 @@ def test_nonstandard_nan_constant_fails_closed(self) -> None: ): self.audit._read_payload(path) + def test_oversized_integer_literal_fails_closed(self) -> None: + """Untrusted JSON integers must have a parser-level digit bound.""" + + document = ( + '{"schema_version":1,' + f'"expected_default_branch_sha":"{DEFAULT_SHA}",' + f'"observed_default_branch_sha":"{DEFAULT_SHA}",' + '"observed_at":"2026-08-12T00:00:00Z",' + '"reported_total_count":0,' + '"protected_workflow_paths":[],' + '"active_pr_workflow_paths":[],' + '"registry_pages":[{"page":1,"status_code":200,' + '"has_next":false,"workflows":[]}],' + f'"unexpected":{"9" * 21}}}' + ) + with tempfile.TemporaryDirectory() as directory: + path = pathlib.Path(directory) / "registry.json" + path.write_text(document, encoding="utf-8") + with self.assertRaisesRegex( + self.audit.WorkflowAuditError, "input is not readable UTF-8 JSON" + ): + self.audit._read_payload(path) + def test_parser_recursion_exhaustion_fails_as_audit_error(self) -> None: """Parser recursion exhaustion must not escape as an unbounded traceback.""" From 85c2b09b421d5e338e62fef6904bf9a3e9cd3fe8 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 15 Aug 2026 13:24:01 +0900 Subject: [PATCH 032/111] fix(ci): bound workflow registry JSON integers --- scripts/ci/audit_workflow_registry.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/scripts/ci/audit_workflow_registry.py b/scripts/ci/audit_workflow_registry.py index e3d4fc728..1507ac023 100644 --- a/scripts/ci/audit_workflow_registry.py +++ b/scripts/ci/audit_workflow_registry.py @@ -19,6 +19,7 @@ _SCHEMA_VERSION = 1 _MAX_INPUT_BYTES = 4 * 1024 * 1024 +_MAX_JSON_INTEGER_DIGITS = 20 _SHA_PATTERN = re.compile(r"^[0-9a-f]{40}$") _TIMESTAMP_PATTERN = re.compile( r"^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$" @@ -350,6 +351,15 @@ def _reject_nonstandard_json_constant(value: str) -> Any: raise json.JSONDecodeError("non-standard JSON numeric constant", value, 0) +def _parse_bounded_json_integer(value: str) -> int: + """Parse one decimal JSON integer within the audit evidence digit budget.""" + + digits = value[1:] if value.startswith("-") else value + if len(digits) > _MAX_JSON_INTEGER_DIGITS: + raise json.JSONDecodeError("JSON integer literal exceeds digit bound", value, 0) + return int(value) + + def _read_payload(path: pathlib.Path) -> dict[str, Any]: """Read at most four mebibytes of unambiguous UTF-8 JSON audit evidence.""" @@ -365,6 +375,7 @@ def _read_payload(path: pathlib.Path) -> dict[str, Any]: content.decode("utf-8"), object_pairs_hook=_reject_duplicate_object_members, parse_constant=_reject_nonstandard_json_constant, + parse_int=_parse_bounded_json_integer, ) except (UnicodeError, json.JSONDecodeError, RecursionError) as error: raise WorkflowAuditError("input is not readable UTF-8 JSON") from error From 441967a5b572bacd270284803c6c2e355a1315c9 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 15 Aug 2026 14:08:29 +0900 Subject: [PATCH 033/111] test(ci): reject workflow registry JSON floats --- ...rkflow_registry_duplicate_json_contract.py | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tests/test_workflow_registry_duplicate_json_contract.py b/tests/test_workflow_registry_duplicate_json_contract.py index 1dd76c794..4a454437b 100644 --- a/tests/test_workflow_registry_duplicate_json_contract.py +++ b/tests/test_workflow_registry_duplicate_json_contract.py @@ -99,6 +99,30 @@ def test_oversized_integer_literal_fails_closed(self) -> None: ): self.audit._read_payload(path) + def test_floating_point_numeric_literals_fail_closed(self) -> None: + """Schema-v1 evidence rejects every floating-point JSON number at parse time.""" + + for literal in ("1.0", "-0.25", "1e309"): + with self.subTest(literal=literal), tempfile.TemporaryDirectory() as directory: + document = ( + '{"schema_version":1,' + f'"expected_default_branch_sha":"{DEFAULT_SHA}",' + f'"observed_default_branch_sha":"{DEFAULT_SHA}",' + '"observed_at":"2026-08-12T00:00:00Z",' + '"reported_total_count":0,' + '"protected_workflow_paths":[],' + '"active_pr_workflow_paths":[],' + '"registry_pages":[{"page":1,"status_code":200,' + '"has_next":false,"workflows":[]}],' + f'"unexpected":{literal}}}' + ) + path = pathlib.Path(directory) / "registry.json" + path.write_text(document, encoding="utf-8") + with self.assertRaisesRegex( + self.audit.WorkflowAuditError, "input is not readable UTF-8 JSON" + ): + self.audit._read_payload(path) + def test_parser_recursion_exhaustion_fails_as_audit_error(self) -> None: """Parser recursion exhaustion must not escape as an unbounded traceback.""" From 5ffaa4210b52f5e4b7d68e766965b6cc2e039715 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 15 Aug 2026 14:11:48 +0900 Subject: [PATCH 034/111] fix(ci): reject workflow registry JSON floats --- scripts/ci/audit_workflow_registry.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/scripts/ci/audit_workflow_registry.py b/scripts/ci/audit_workflow_registry.py index 1507ac023..3e4f70e4f 100644 --- a/scripts/ci/audit_workflow_registry.py +++ b/scripts/ci/audit_workflow_registry.py @@ -351,6 +351,12 @@ def _reject_nonstandard_json_constant(value: str) -> Any: raise json.JSONDecodeError("non-standard JSON numeric constant", value, 0) +def _reject_json_floating_point(value: str) -> Any: + """Reject floating-point numbers outside the schema-v1 evidence grammar.""" + + raise json.JSONDecodeError("floating-point JSON numeric literal is unsupported", value, 0) + + def _parse_bounded_json_integer(value: str) -> int: """Parse one decimal JSON integer within the audit evidence digit budget.""" @@ -375,6 +381,7 @@ def _read_payload(path: pathlib.Path) -> dict[str, Any]: content.decode("utf-8"), object_pairs_hook=_reject_duplicate_object_members, parse_constant=_reject_nonstandard_json_constant, + parse_float=_reject_json_floating_point, parse_int=_parse_bounded_json_integer, ) except (UnicodeError, json.JSONDecodeError, RecursionError) as error: @@ -414,4 +421,4 @@ def main(argv: list[str] | None = None) -> int: if __name__ == "__main__": - raise SystemExit(main()) + raise SystemExit(main()) \ No newline at end of file From 527278c8bc261488765831a10a8f7001ed374889 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 15 Aug 2026 15:24:22 +0900 Subject: [PATCH 035/111] test(ci): reject workflow ids wider than u64 --- ...st_workflow_registry_integer_type_contract.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/test_workflow_registry_integer_type_contract.py b/tests/test_workflow_registry_integer_type_contract.py index 738adda5a..248100a4f 100644 --- a/tests/test_workflow_registry_integer_type_contract.py +++ b/tests/test_workflow_registry_integer_type_contract.py @@ -75,6 +75,22 @@ def test_float_http_status_code_is_rejected(self) -> None: with self.assertRaises(self.audit.WorkflowAuditError): self.audit.audit_workflow_registry(payload) + def test_workflow_id_above_unsigned_64_bit_range_is_rejected(self) -> None: + """Audit evidence must not preserve a workflow ID wider than the reviewed u64 bound.""" + + payload = _payload() + payload["reported_total_count"] = 1 + payload["registry_pages"][0]["workflows"] = [ + { + "id": 18_446_744_073_709_551_616, + "name": "oversized-id", + "path": ".github/workflows/oversized-id.yml", + "state": "active", + } + ] + with self.assertRaises(self.audit.WorkflowAuditError): + self.audit.audit_workflow_registry(payload) + if __name__ == "__main__": unittest.main() From 8d8649f290a2159427abbde2cb52ba9f76616d7d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 15 Aug 2026 15:30:15 +0900 Subject: [PATCH 036/111] fix(ci): bound workflow registry ids to u64 --- scripts/ci/audit_workflow_registry.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/scripts/ci/audit_workflow_registry.py b/scripts/ci/audit_workflow_registry.py index 3e4f70e4f..7709a59d6 100644 --- a/scripts/ci/audit_workflow_registry.py +++ b/scripts/ci/audit_workflow_registry.py @@ -20,6 +20,7 @@ _SCHEMA_VERSION = 1 _MAX_INPUT_BYTES = 4 * 1024 * 1024 _MAX_JSON_INTEGER_DIGITS = 20 +_MAX_WORKFLOW_ID = (1 << 64) - 1 _SHA_PATTERN = re.compile(r"^[0-9a-f]{40}$") _TIMESTAMP_PATTERN = re.compile( r"^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$" @@ -227,7 +228,9 @@ def _validate_workflow_record( workflow_id = raw_record.get("id") if isinstance(workflow_id, bool) or not isinstance(workflow_id, int): raise WorkflowAuditError(f"workflow record {record_index} has an invalid id") - if workflow_id <= 0 or workflow_id in seen_ids: + if workflow_id <= 0 or workflow_id > _MAX_WORKFLOW_ID: + raise WorkflowAuditError(f"workflow record {record_index} has an invalid id") + if workflow_id in seen_ids: raise WorkflowAuditError(f"workflow record {record_index} reuses an id") seen_ids.add(workflow_id) From 36b3845057b56cc72dd3fc0db79f0de0a84e1a79 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 15 Aug 2026 17:06:15 +0900 Subject: [PATCH 037/111] test(ci): reject malformed Unicode evidence safely --- .../test_workflow_registry_unicode_presentation_contract.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/test_workflow_registry_unicode_presentation_contract.py b/tests/test_workflow_registry_unicode_presentation_contract.py index fdb6996b5..893d10032 100644 --- a/tests/test_workflow_registry_unicode_presentation_contract.py +++ b/tests/test_workflow_registry_unicode_presentation_contract.py @@ -67,6 +67,12 @@ def test_bidi_and_zero_width_controls_fail_closed_in_names_and_paths(self) -> No with self.assertRaises(self.audit.WorkflowAuditError): self.audit.audit_workflow_registry(payload) + def test_unpaired_unicode_surrogate_fails_with_stable_audit_error(self) -> None: + """Malformed Unicode scalar input must fail through the public audit error contract.""" + + with self.assertRaises(self.audit.WorkflowAuditError): + self.audit.audit_workflow_registry(_payload(name="legacy\ud800workflow")) + def test_printable_unicode_workflow_name_remains_valid(self) -> None: """Ordinary printable Unicode labels remain usable as non-authoritative display text.""" From d6ddd7b0b96b423fe308af075fb45746b3616e66 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 15 Aug 2026 17:09:12 +0900 Subject: [PATCH 038/111] fix(ci): normalize malformed Unicode audit evidence --- scripts/ci/audit_workflow_registry.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/scripts/ci/audit_workflow_registry.py b/scripts/ci/audit_workflow_registry.py index 7709a59d6..8db7c7cbf 100644 --- a/scripts/ci/audit_workflow_registry.py +++ b/scripts/ci/audit_workflow_registry.py @@ -61,7 +61,13 @@ def _require_nonempty_string(value: Any, field_name: str, maximum: int) -> str: if not isinstance(value, str): raise WorkflowAuditError(f"{field_name} must be a string") - if not value or value != value.strip() or len(value.encode("utf-8")) > maximum: + if not value or value != value.strip(): + raise WorkflowAuditError(f"{field_name} is invalid") + try: + encoded_length = len(value.encode("utf-8")) + except UnicodeEncodeError: + raise WorkflowAuditError(f"{field_name} is invalid") from None + if encoded_length > maximum: raise WorkflowAuditError(f"{field_name} is invalid") if any(not character.isprintable() for character in value): raise WorkflowAuditError(f"{field_name} contains a control character") @@ -424,4 +430,4 @@ def main(argv: list[str] | None = None) -> int: if __name__ == "__main__": - raise SystemExit(main()) \ No newline at end of file + raise SystemExit(main()) From 62e8463667c02ba1b0f615a26d9128efb4ed64a9 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 15 Aug 2026 20:05:29 +0900 Subject: [PATCH 039/111] test(ci): reject ambiguous registry schema extensions --- ...workflow_registry_schema_shape_contract.py | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 tests/test_workflow_registry_schema_shape_contract.py diff --git a/tests/test_workflow_registry_schema_shape_contract.py b/tests/test_workflow_registry_schema_shape_contract.py new file mode 100644 index 000000000..25313e5c9 --- /dev/null +++ b/tests/test_workflow_registry_schema_shape_contract.py @@ -0,0 +1,84 @@ +"""Reject forward-incompatible fields in versioned workflow registry evidence.""" + +from __future__ import annotations + +import importlib.util +import pathlib +import unittest + +ROOT = pathlib.Path(__file__).resolve().parents[1] +SCRIPT = ROOT / "scripts" / "ci" / "audit_workflow_registry.py" +DEFAULT_SHA = "67af7c87589edc2039545af335c95064d9b8391c" + + +def _load_module(): + """Load the read-only workflow audit utility without packaging scripts.""" + + spec = importlib.util.spec_from_file_location("audit_workflow_registry", SCRIPT) + if spec is None or spec.loader is None: + raise AssertionError("workflow registry audit module is not loadable") + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return module + + +def _payload() -> dict: + """Return one minimal complete schema-v1 registry evidence document.""" + + return { + "schema_version": 1, + "expected_default_branch_sha": DEFAULT_SHA, + "observed_default_branch_sha": DEFAULT_SHA, + "observed_at": "2026-08-12T00:00:00Z", + "reported_total_count": 1, + "protected_workflow_paths": [".github/workflows/ci.yml"], + "active_pr_workflow_paths": [], + "registry_pages": [ + { + "page": 1, + "status_code": 200, + "has_next": False, + "workflows": [ + { + "id": 1, + "name": "CI", + "path": ".github/workflows/ci.yml", + "state": "active", + } + ], + } + ], + } + + +class WorkflowRegistrySchemaShapeContractTests(unittest.TestCase): + """Keep schema-v1 evidence closed against silent producer shape drift.""" + + @classmethod + def setUpClass(cls) -> None: + cls.audit = _load_module() + + def test_unknown_members_fail_closed_at_each_versioned_object_boundary(self) -> None: + """Unknown document, page, and workflow members cannot be silently ignored.""" + + mutations = ( + lambda payload: payload.update(unexpected_document_field="shadow"), + lambda payload: payload["registry_pages"][0].update( + unexpected_page_field="shadow" + ), + lambda payload: payload["registry_pages"][0]["workflows"][0].update( + unexpected_workflow_field="shadow" + ), + ) + for mutate in mutations: + with self.subTest(mutate=mutate): + payload = _payload() + mutate(payload) + with self.assertRaisesRegex( + self.audit.WorkflowAuditError, "unsupported field" + ): + self.audit.audit_workflow_registry(payload) + + +if __name__ == "__main__": + unittest.main() From f6a10d83c95400f287505141a58ccd4a3c423152 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sat, 15 Aug 2026 20:07:38 +0900 Subject: [PATCH 040/111] fix(ci): close workflow registry schema v1 --- scripts/ci/audit_workflow_registry.py | 39 ++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/scripts/ci/audit_workflow_registry.py b/scripts/ci/audit_workflow_registry.py index 8db7c7cbf..590e2ea30 100644 --- a/scripts/ci/audit_workflow_registry.py +++ b/scripts/ci/audit_workflow_registry.py @@ -48,6 +48,17 @@ def _require_mapping(value: Any, field_name: str) -> dict[str, Any]: return value +def _require_exact_fields( + value: Any, field_name: str, expected_fields: set[str] +) -> dict[str, Any]: + """Return a mapping whose member names exactly match this schema boundary.""" + + mapping = _require_mapping(value, field_name) + if set(mapping).difference(expected_fields): + raise WorkflowAuditError(f"{field_name} contains an unsupported field") + return mapping + + def _require_list(value: Any, field_name: str) -> list[Any]: """Return a list or fail with a stable field-specific diagnostic.""" @@ -154,7 +165,11 @@ def _validate_pages(value: Any) -> tuple[list[dict[str, Any]], list[dict[str, An workflows: list[dict[str, Any]] = [] receipts: list[dict[str, Any]] = [] for index, raw_page in enumerate(raw_pages): - page = _require_mapping(raw_page, f"registry_pages[{index}]") + page = _require_exact_fields( + raw_page, + f"registry_pages[{index}]", + {"page", "status_code", "has_next", "workflows"}, + ) expected_page = index + 1 page_number = page.get("page") if ( @@ -231,6 +246,11 @@ def _validate_workflow_record( ) -> dict[str, Any]: """Validate and classify one exported GitHub Actions workflow record.""" + raw_record = _require_exact_fields( + raw_record, + f"workflow record {record_index}", + {"id", "name", "path", "state"}, + ) workflow_id = raw_record.get("id") if isinstance(workflow_id, bool) or not isinstance(workflow_id, int): raise WorkflowAuditError(f"workflow record {record_index} has an invalid id") @@ -278,7 +298,20 @@ def audit_workflow_registry(payload: dict[str, Any]) -> dict[str, Any]: workflow; a later authorized operator must independently refetch every candidate. """ - document = _require_mapping(payload, "payload") + document = _require_exact_fields( + payload, + "payload", + { + "schema_version", + "expected_default_branch_sha", + "observed_default_branch_sha", + "observed_at", + "reported_total_count", + "protected_workflow_paths", + "active_pr_workflow_paths", + "registry_pages", + }, + ) schema_version = document.get("schema_version") if ( isinstance(schema_version, bool) @@ -430,4 +463,4 @@ def main(argv: list[str] | None = None) -> int: if __name__ == "__main__": - raise SystemExit(main()) + raise SystemExit(main()) \ No newline at end of file From a53050041d6a375c9c2a6e8d5699eef3a08eece8 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 16 Aug 2026 18:21:24 +0900 Subject: [PATCH 041/111] test(ci): reject noncanonical negative-zero evidence --- ...rkflow_registry_duplicate_json_contract.py | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/test_workflow_registry_duplicate_json_contract.py b/tests/test_workflow_registry_duplicate_json_contract.py index 4a454437b..e3ff29ef1 100644 --- a/tests/test_workflow_registry_duplicate_json_contract.py +++ b/tests/test_workflow_registry_duplicate_json_contract.py @@ -99,6 +99,28 @@ def test_oversized_integer_literal_fails_closed(self) -> None: ): self.audit._read_payload(path) + def test_negative_zero_integer_literal_fails_closed(self) -> None: + """Lexical negative zero must not collapse into canonical count zero.""" + + document = ( + '{"schema_version":1,' + f'"expected_default_branch_sha":"{DEFAULT_SHA}",' + f'"observed_default_branch_sha":"{DEFAULT_SHA}",' + '"observed_at":"2026-08-12T00:00:00Z",' + '"reported_total_count":-0,' + '"protected_workflow_paths":[],' + '"active_pr_workflow_paths":[],' + '"registry_pages":[{"page":1,"status_code":200,' + '"has_next":false,"workflows":[]}]}' + ) + with tempfile.TemporaryDirectory() as directory: + path = pathlib.Path(directory) / "registry.json" + path.write_text(document, encoding="utf-8") + with self.assertRaisesRegex( + self.audit.WorkflowAuditError, "input is not readable UTF-8 JSON" + ): + self.audit._read_payload(path) + def test_floating_point_numeric_literals_fail_closed(self) -> None: """Schema-v1 evidence rejects every floating-point JSON number at parse time.""" From 9deffec389ff3e86da872d329bda31c78e7507e3 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 16 Aug 2026 18:27:16 +0900 Subject: [PATCH 042/111] fix(ci): reject noncanonical negative-zero evidence --- scripts/ci/audit_workflow_registry.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/scripts/ci/audit_workflow_registry.py b/scripts/ci/audit_workflow_registry.py index 590e2ea30..5bee024d1 100644 --- a/scripts/ci/audit_workflow_registry.py +++ b/scripts/ci/audit_workflow_registry.py @@ -400,8 +400,10 @@ def _reject_json_floating_point(value: str) -> Any: def _parse_bounded_json_integer(value: str) -> int: - """Parse one decimal JSON integer within the audit evidence digit budget.""" + """Parse one canonical decimal JSON integer within the evidence digit budget.""" + if value == "-0": + raise json.JSONDecodeError("negative-zero JSON integer is unsupported", value, 0) digits = value[1:] if value.startswith("-") else value if len(digits) > _MAX_JSON_INTEGER_DIGITS: raise json.JSONDecodeError("JSON integer literal exceeds digit bound", value, 0) @@ -463,4 +465,4 @@ def main(argv: list[str] | None = None) -> int: if __name__ == "__main__": - raise SystemExit(main()) \ No newline at end of file + raise SystemExit(main()) From 05899bb997b53b131a49c0c456f4b6e49023d10d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 16 Aug 2026 18:32:25 +0900 Subject: [PATCH 043/111] docs(changelog): record canonical integer evidence --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 29bb5a4b1..c0968f463 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ All notable changes to OriginWeave are documented in this file. The format follo ### Added - Rust workspace for independently reusable core, policy, destination, network, TLS, resource, and evidence modules. -- Read-only workflow-registry audit evidence that binds complete paginated GitHub Actions inventory to exact protected-main state and fails closed on ambiguous scalar types, workflow identities, paths, timestamps, and ownership. +- Read-only workflow-registry audit evidence that binds complete paginated GitHub Actions inventory to exact protected-main state and fails closed on ambiguous scalar types, including noncanonical negative-zero integers, workflow identities, paths, timestamps, and ownership. - Canonical HTTPS and loopback-origin boundary with case-normalized schemes and hosts, default-port normalization, IPv4/IPv6 handling, browser-special numeric-host rejection, and explicit malformed-input errors. - Typed browser actions, capabilities, risk classes, execution modes, robots decisions, secret-delivery contracts, immutable canonical action-intent digests, and intent-bound approval scopes. - Deterministic fail-closed policy evaluation for untrusted instructions, origin grants, crawler restrictions, execution-mode and purpose consistency, approvals, and brokered secrets. From 6f7f65df741e139dfedd364debf09cf8af2deacb Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 16 Aug 2026 19:08:44 +0900 Subject: [PATCH 044/111] test(ci): cover workflow registry HTTP failures --- ..._workflow_registry_http_status_contract.py | 94 +++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 tests/test_workflow_registry_http_status_contract.py diff --git a/tests/test_workflow_registry_http_status_contract.py b/tests/test_workflow_registry_http_status_contract.py new file mode 100644 index 000000000..7938275c6 --- /dev/null +++ b/tests/test_workflow_registry_http_status_contract.py @@ -0,0 +1,94 @@ +"""Fail closed when a collected workflow-registry page is not an HTTP 200 response.""" + +from __future__ import annotations + +import importlib.util +import pathlib +import unittest + +ROOT = pathlib.Path(__file__).resolve().parents[1] +SCRIPT = ROOT / "scripts" / "ci" / "audit_workflow_registry.py" +DEFAULT_SHA = "0c376acf059be9ddddddfbde1d0189e4f39ef014" + + +def _load_module(): + """Load the read-only registry auditor without packaging scripts as modules.""" + + spec = importlib.util.spec_from_file_location("audit_workflow_registry", SCRIPT) + if spec is None or spec.loader is None: + raise AssertionError("workflow registry audit module is not loadable") + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return module + + +def _payload(status_code: int) -> dict: + """Build one complete one-page registry payload with a selected HTTP result.""" + + return { + "schema_version": 1, + "expected_default_branch_sha": DEFAULT_SHA, + "observed_default_branch_sha": DEFAULT_SHA, + "observed_at": "2026-08-16T09:40:00Z", + "reported_total_count": 1, + "protected_workflow_paths": [".github/workflows/ci.yml"], + "active_pr_workflow_paths": [], + "registry_pages": [ + { + "page": 1, + "status_code": status_code, + "has_next": False, + "workflows": [ + { + "id": 9001, + "name": "orphan candidate", + "path": ".github/workflows/orphan.yml", + "state": "active", + } + ], + } + ], + } + + +class WorkflowRegistryHttpStatusContractTests(unittest.TestCase): + """Treat permission, absence, throttling, and server failures as non-evidence.""" + + @classmethod + def setUpClass(cls) -> None: + cls.audit = _load_module() + + def test_permission_absence_rate_limit_and_server_errors_fail_closed(self) -> None: + """A non-200 page must never be interpreted as a complete registry snapshot.""" + + for status_code in (403, 404, 429, 500, 502, 503, 504): + with self.subTest(status_code=status_code): + with self.assertRaisesRegex( + self.audit.WorkflowAuditError, + "registry page 1 did not return HTTP 200", + ): + self.audit.audit_workflow_registry(_payload(status_code)) + + def test_boolean_status_is_not_accepted_as_integer_200(self) -> None: + """Python's bool-as-int relationship must not create synthetic HTTP success.""" + + payload = _payload(200) + payload["registry_pages"][0]["status_code"] = True + with self.assertRaisesRegex( + self.audit.WorkflowAuditError, + "registry page 1 did not return HTTP 200", + ): + self.audit.audit_workflow_registry(payload) + + def test_exact_http_200_still_classifies_the_active_orphan_candidate(self) -> None: + """The failure regression must preserve the reviewed successful evidence path.""" + + evidence = self.audit.audit_workflow_registry(_payload(200)) + record = evidence["workflow_records"][0] + self.assertEqual(record["classification"], "active_orphan_repository_workflow") + self.assertTrue(record["disable_candidate"]) + self.assertFalse(evidence["mutation_performed"]) + + +if __name__ == "__main__": + unittest.main() From 409b8b0a9720d5920b3e5c99e7090d521d25e071 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 17 Aug 2026 06:27:25 +0900 Subject: [PATCH 045/111] test(ci): classify retryable workflow registry failures --- ..._workflow_registry_http_status_contract.py | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/test_workflow_registry_http_status_contract.py b/tests/test_workflow_registry_http_status_contract.py index 7938275c6..0674c8af7 100644 --- a/tests/test_workflow_registry_http_status_contract.py +++ b/tests/test_workflow_registry_http_status_contract.py @@ -69,6 +69,29 @@ def test_permission_absence_rate_limit_and_server_errors_fail_closed(self) -> No ): self.audit.audit_workflow_registry(_payload(status_code)) + def test_http_failures_expose_safe_retryability_without_becoming_success(self) -> None: + """Only throttling/server failures should tell a collector that retry is safe.""" + + for status_code, retryable in ( + (403, False), + (404, False), + (429, True), + (500, True), + (502, True), + (503, True), + (504, True), + ): + with self.subTest(status_code=status_code): + with self.assertRaises(self.audit.WorkflowAuditHttpStatusError) as raised: + self.audit.audit_workflow_registry(_payload(status_code)) + error = raised.exception + self.assertEqual(error.page_number, 1) + self.assertEqual(error.status_code, status_code) + self.assertEqual(error.retryable, retryable) + self.assertEqual( + str(error), "registry page 1 did not return HTTP 200" + ) + def test_boolean_status_is_not_accepted_as_integer_200(self) -> None: """Python's bool-as-int relationship must not create synthetic HTTP success.""" From b35911f0d10b8e7c323837a93f739ec0a935dd8c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 17 Aug 2026 06:28:56 +0900 Subject: [PATCH 046/111] fix(ci): classify retryable registry collection failures --- scripts/ci/audit_workflow_registry.py | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/scripts/ci/audit_workflow_registry.py b/scripts/ci/audit_workflow_registry.py index 5bee024d1..2763bcc53 100644 --- a/scripts/ci/audit_workflow_registry.py +++ b/scripts/ci/audit_workflow_registry.py @@ -40,6 +40,23 @@ class WorkflowAuditError(ValueError): """Report malformed, incomplete, stale, or ambiguous registry evidence.""" +class WorkflowAuditHttpStatusError(WorkflowAuditError): + """Report one collected non-200 page with bounded retry guidance. + + The auditor still fails closed for every non-200 response. The ``retryable`` + flag only tells the external collector whether recollecting the page can be a + safe bounded response to throttling or a server-side failure. Permission and + absence responses remain non-retryable because this offline evidence lacks the + trusted response headers needed to reinterpret them as transient conditions. + """ + + def __init__(self, page_number: int, status_code: int) -> None: + super().__init__(f"registry page {page_number} did not return HTTP 200") + self.page_number = page_number + self.status_code = status_code + self.retryable = status_code == 429 or 500 <= status_code <= 599 + + def _require_mapping(value: Any, field_name: str) -> dict[str, Any]: """Return a mapping or fail with a stable field-specific diagnostic.""" @@ -179,14 +196,12 @@ def _validate_pages(value: Any) -> tuple[list[dict[str, Any]], list[dict[str, An ): raise WorkflowAuditError("registry_pages must be contiguous and start at page 1") status_code = page.get("status_code") - if ( - isinstance(status_code, bool) - or not isinstance(status_code, int) - or status_code != 200 - ): + if isinstance(status_code, bool) or not isinstance(status_code, int): raise WorkflowAuditError( f"registry page {expected_page} did not return HTTP 200" ) + if status_code != 200: + raise WorkflowAuditHttpStatusError(expected_page, status_code) has_next = page.get("has_next") if not isinstance(has_next, bool): raise WorkflowAuditError(f"registry page {expected_page} lacks has_next") From 78ba9b99e81da88f4f686a6d9d2decab69988129 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 17 Aug 2026 06:32:46 +0900 Subject: [PATCH 047/111] test(ci): reject retry for non-transient HTTP failures --- tests/test_workflow_registry_http_status_contract.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/test_workflow_registry_http_status_contract.py b/tests/test_workflow_registry_http_status_contract.py index 0674c8af7..38b0e43ec 100644 --- a/tests/test_workflow_registry_http_status_contract.py +++ b/tests/test_workflow_registry_http_status_contract.py @@ -61,7 +61,7 @@ def setUpClass(cls) -> None: def test_permission_absence_rate_limit_and_server_errors_fail_closed(self) -> None: """A non-200 page must never be interpreted as a complete registry snapshot.""" - for status_code in (403, 404, 429, 500, 502, 503, 504): + for status_code in (403, 404, 429, 500, 501, 502, 503, 504, 505): with self.subTest(status_code=status_code): with self.assertRaisesRegex( self.audit.WorkflowAuditError, @@ -70,16 +70,18 @@ def test_permission_absence_rate_limit_and_server_errors_fail_closed(self) -> No self.audit.audit_workflow_registry(_payload(status_code)) def test_http_failures_expose_safe_retryability_without_becoming_success(self) -> None: - """Only throttling/server failures should tell a collector that retry is safe.""" + """Only reviewed transient statuses should advertise bounded recollection.""" for status_code, retryable in ( (403, False), (404, False), (429, True), (500, True), + (501, False), (502, True), (503, True), (504, True), + (505, False), ): with self.subTest(status_code=status_code): with self.assertRaises(self.audit.WorkflowAuditHttpStatusError) as raised: From ba72cba87c2117462767a1322940e9c4c34727cc Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 17 Aug 2026 06:34:36 +0900 Subject: [PATCH 048/111] fix(ci): bound retryable registry HTTP failures --- scripts/ci/audit_workflow_registry.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/scripts/ci/audit_workflow_registry.py b/scripts/ci/audit_workflow_registry.py index 2763bcc53..ed7b1e646 100644 --- a/scripts/ci/audit_workflow_registry.py +++ b/scripts/ci/audit_workflow_registry.py @@ -34,6 +34,7 @@ "disabled_manually", } _ALLOWED_STATES = {"active", *_DISABLED_STATES} +_RETRYABLE_HTTP_STATUSES = {429, 500, 502, 503, 504} class WorkflowAuditError(ValueError): @@ -45,16 +46,17 @@ class WorkflowAuditHttpStatusError(WorkflowAuditError): The auditor still fails closed for every non-200 response. The ``retryable`` flag only tells the external collector whether recollecting the page can be a - safe bounded response to throttling or a server-side failure. Permission and - absence responses remain non-retryable because this offline evidence lacks the - trusted response headers needed to reinterpret them as transient conditions. + safe bounded response to a reviewed throttling or transient server condition. + Permission, absence, and protocol/capability failures remain non-retryable because + this offline evidence lacks trusted response headers or stronger context that + could safely reinterpret them as transient conditions. """ def __init__(self, page_number: int, status_code: int) -> None: super().__init__(f"registry page {page_number} did not return HTTP 200") self.page_number = page_number self.status_code = status_code - self.retryable = status_code == 429 or 500 <= status_code <= 599 + self.retryable = status_code in _RETRYABLE_HTTP_STATUSES def _require_mapping(value: Any, field_name: str) -> dict[str, Any]: From acb0dfe7f697ebb293187d47bc4cc2d91cd252f4 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 17 Aug 2026 06:37:43 +0900 Subject: [PATCH 049/111] docs(changelog): record bounded registry retry contract --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a4f1c6495..ea0b102c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ All notable changes to OriginWeave are documented in this file. The format follo - Bound explicit extension-to-Agent grants to exclusive trusted-time expiry in addition to extension identity, session, browsing context, and canonical origin, so a same-origin grant cannot be reused at or after the deadline. - Bound explicit extension-to-Agent grants to the exact canonical origin in addition to extension identity, session, and browsing context, so a same-session navigation or port change cannot reuse the grant. - Rust workspace for independently reusable core, policy, destination, network, TLS, resource, and evidence modules. -- Read-only workflow-registry audit evidence that binds complete paginated GitHub Actions inventory to exact protected-main state and fails closed on ambiguous scalar types, including noncanonical negative-zero integers, workflow identities, paths, timestamps, and ownership. +- Read-only workflow-registry audit evidence that binds complete paginated GitHub Actions inventory to exact protected-main state, fails closed on ambiguous scalar types including noncanonical negative-zero integers, workflow identities, paths, timestamps, and ownership, and exposes typed bounded-recollection guidance only for reviewed transient HTTP results (`429`, `500`, `502`, `503`, and `504`) while keeping permission, absence, and protocol/capability failures such as `403`, `404`, `501`, and `505` non-retryable and non-passing. - Canonical HTTPS and loopback-origin boundary with case-normalized schemes and hosts, default-port normalization, IPv4/IPv6 handling, browser-special numeric-host rejection, and explicit malformed-input errors. - Typed browser actions, capabilities, risk classes, execution modes, robots decisions, secret-delivery contracts, immutable canonical action-intent digests, and intent-bound approval scopes. - Deterministic fail-closed policy evaluation for untrusted instructions, origin grants, crawler restrictions, execution-mode and purpose consistency, approvals, and brokered secrets. From 6621603c95948be4ee77d4ddabb75656ca872f87 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 17 Aug 2026 06:51:42 +0900 Subject: [PATCH 050/111] test(ci): require bounded Retry-After registry evidence --- ..._workflow_registry_retry_after_contract.py | 103 ++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 tests/test_workflow_registry_retry_after_contract.py diff --git a/tests/test_workflow_registry_retry_after_contract.py b/tests/test_workflow_registry_retry_after_contract.py new file mode 100644 index 000000000..a7e790b3b --- /dev/null +++ b/tests/test_workflow_registry_retry_after_contract.py @@ -0,0 +1,103 @@ +"""Preserve bounded Retry-After guidance without turning failed collection into evidence.""" + +from __future__ import annotations + +import importlib.util +import pathlib +import unittest + +ROOT = pathlib.Path(__file__).resolve().parents[1] +SCRIPT = ROOT / "scripts" / "ci" / "audit_workflow_registry.py" +DEFAULT_SHA = "0841d2ab3d8b5e60a03c0a8e818cf438e2716829" + + +def _load_module(): + """Load the read-only registry auditor without packaging scripts as modules.""" + + spec = importlib.util.spec_from_file_location("audit_workflow_registry", SCRIPT) + if spec is None or spec.loader is None: + raise AssertionError("workflow registry audit module is not loadable") + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return module + + +def _payload(status_code: int, retry_after_seconds: object = None) -> dict: + """Build one failed collection page with optional bounded retry guidance.""" + + page = { + "page": 1, + "status_code": status_code, + "has_next": False, + "workflows": [], + } + if retry_after_seconds is not None: + page["retry_after_seconds"] = retry_after_seconds + return { + "schema_version": 1, + "expected_default_branch_sha": DEFAULT_SHA, + "observed_default_branch_sha": DEFAULT_SHA, + "observed_at": "2026-08-16T21:40:00Z", + "reported_total_count": 0, + "protected_workflow_paths": [".github/workflows/ci.yml"], + "active_pr_workflow_paths": [], + "registry_pages": [page], + } + + +class WorkflowRegistryRetryAfterContractTests(unittest.TestCase): + """Keep recollection guidance typed, bounded, and fail-closed.""" + + @classmethod + def setUpClass(cls) -> None: + cls.audit = _load_module() + + def test_rate_limited_403_becomes_retryable_only_with_bounded_retry_after(self) -> None: + """A collected 403 may be retried only when the collector retained Retry-After.""" + + with self.assertRaises(self.audit.WorkflowAuditHttpStatusError) as raised: + self.audit.audit_workflow_registry(_payload(403, 30)) + error = raised.exception + self.assertTrue(error.retryable) + self.assertEqual(error.retry_after_seconds, 30) + self.assertEqual(str(error), "registry page 1 did not return HTTP 200") + + with self.assertRaises(self.audit.WorkflowAuditHttpStatusError) as raised_without_hint: + self.audit.audit_workflow_registry(_payload(403)) + self.assertFalse(raised_without_hint.exception.retryable) + self.assertIsNone(raised_without_hint.exception.retry_after_seconds) + + def test_retry_after_is_preserved_for_reviewed_transient_statuses(self) -> None: + """429 and transient server failures expose the same bounded wait hint.""" + + for status_code in (429, 500, 502, 503, 504): + with self.subTest(status_code=status_code): + with self.assertRaises(self.audit.WorkflowAuditHttpStatusError) as raised: + self.audit.audit_workflow_registry(_payload(status_code, 120)) + self.assertTrue(raised.exception.retryable) + self.assertEqual(raised.exception.retry_after_seconds, 120) + + def test_retry_after_does_not_make_nontransient_statuses_retryable(self) -> None: + """Missing resources and unsupported protocol responses stay non-retryable.""" + + for status_code in (404, 501, 505): + with self.subTest(status_code=status_code): + with self.assertRaises(self.audit.WorkflowAuditHttpStatusError) as raised: + self.audit.audit_workflow_registry(_payload(status_code, 30)) + self.assertFalse(raised.exception.retryable) + self.assertEqual(raised.exception.retry_after_seconds, 30) + + def test_retry_after_rejects_ambiguous_or_unbounded_values(self) -> None: + """Retry guidance is a bounded integer, not a bool, negative, or huge wait.""" + + for value in (True, False, -1, 3601, "30", 1.5): + with self.subTest(value=value): + with self.assertRaisesRegex( + self.audit.WorkflowAuditError, + "retry_after_seconds must be an integer from 0 through 3600", + ): + self.audit.audit_workflow_registry(_payload(429, value)) + + +if __name__ == "__main__": + unittest.main() From 98db5a32b8a9522e8b20e05f7ff08a1a64491cbb Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 17 Aug 2026 06:53:10 +0900 Subject: [PATCH 051/111] fix(ci): preserve bounded Retry-After evidence --- scripts/ci/audit_workflow_registry.py | 45 ++++++++++++++++++++++----- 1 file changed, 38 insertions(+), 7 deletions(-) diff --git a/scripts/ci/audit_workflow_registry.py b/scripts/ci/audit_workflow_registry.py index ed7b1e646..5bdaf6340 100644 --- a/scripts/ci/audit_workflow_registry.py +++ b/scripts/ci/audit_workflow_registry.py @@ -21,6 +21,7 @@ _MAX_INPUT_BYTES = 4 * 1024 * 1024 _MAX_JSON_INTEGER_DIGITS = 20 _MAX_WORKFLOW_ID = (1 << 64) - 1 +_MAX_RETRY_AFTER_SECONDS = 3600 _SHA_PATTERN = re.compile(r"^[0-9a-f]{40}$") _TIMESTAMP_PATTERN = re.compile( r"^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$" @@ -47,16 +48,24 @@ class WorkflowAuditHttpStatusError(WorkflowAuditError): The auditor still fails closed for every non-200 response. The ``retryable`` flag only tells the external collector whether recollecting the page can be a safe bounded response to a reviewed throttling or transient server condition. - Permission, absence, and protocol/capability failures remain non-retryable because - this offline evidence lacks trusted response headers or stronger context that - could safely reinterpret them as transient conditions. + A collected 403 remains non-retryable unless the collector also retained one + bounded ``Retry-After`` delay; the delay is guidance for recollection only and + never converts failed registry evidence into success. """ - def __init__(self, page_number: int, status_code: int) -> None: + def __init__( + self, + page_number: int, + status_code: int, + retry_after_seconds: int | None = None, + ) -> None: super().__init__(f"registry page {page_number} did not return HTTP 200") self.page_number = page_number self.status_code = status_code - self.retryable = status_code in _RETRYABLE_HTTP_STATUSES + self.retry_after_seconds = retry_after_seconds + self.retryable = status_code in _RETRYABLE_HTTP_STATUSES or ( + status_code == 403 and retry_after_seconds is not None + ) def _require_mapping(value: Any, field_name: str) -> dict[str, Any]: @@ -136,6 +145,21 @@ def _validate_reported_total_count(value: Any) -> int: return value +def _validate_retry_after_seconds(value: Any) -> int: + """Return one bounded Retry-After delay for a failed collection page.""" + + if ( + isinstance(value, bool) + or not isinstance(value, int) + or value < 0 + or value > _MAX_RETRY_AFTER_SECONDS + ): + raise WorkflowAuditError( + "retry_after_seconds must be an integer from 0 through 3600" + ) + return value + + def _validate_workflow_path(value: Any, field_name: str) -> str: """Return one unambiguous GitHub workflow registry path.""" @@ -187,7 +211,7 @@ def _validate_pages(value: Any) -> tuple[list[dict[str, Any]], list[dict[str, An page = _require_exact_fields( raw_page, f"registry_pages[{index}]", - {"page", "status_code", "has_next", "workflows"}, + {"page", "status_code", "retry_after_seconds", "has_next", "workflows"}, ) expected_page = index + 1 page_number = page.get("page") @@ -197,13 +221,20 @@ def _validate_pages(value: Any) -> tuple[list[dict[str, Any]], list[dict[str, An or page_number != expected_page ): raise WorkflowAuditError("registry_pages must be contiguous and start at page 1") + retry_after_seconds = None + if "retry_after_seconds" in page: + retry_after_seconds = _validate_retry_after_seconds( + page.get("retry_after_seconds") + ) status_code = page.get("status_code") if isinstance(status_code, bool) or not isinstance(status_code, int): raise WorkflowAuditError( f"registry page {expected_page} did not return HTTP 200" ) if status_code != 200: - raise WorkflowAuditHttpStatusError(expected_page, status_code) + raise WorkflowAuditHttpStatusError( + expected_page, status_code, retry_after_seconds + ) has_next = page.get("has_next") if not isinstance(has_next, bool): raise WorkflowAuditError(f"registry page {expected_page} lacks has_next") From e5ae33f7c7f73a0daa4daf9e18c00b6382b68d05 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 17 Aug 2026 06:54:52 +0900 Subject: [PATCH 052/111] docs(changelog): record Retry-After audit evidence --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ea0b102c7..03a12074c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ All notable changes to OriginWeave are documented in this file. The format follo - Bound explicit extension-to-Agent grants to exclusive trusted-time expiry in addition to extension identity, session, browsing context, and canonical origin, so a same-origin grant cannot be reused at or after the deadline. - Bound explicit extension-to-Agent grants to the exact canonical origin in addition to extension identity, session, and browsing context, so a same-session navigation or port change cannot reuse the grant. - Rust workspace for independently reusable core, policy, destination, network, TLS, resource, and evidence modules. -- Read-only workflow-registry audit evidence that binds complete paginated GitHub Actions inventory to exact protected-main state, fails closed on ambiguous scalar types including noncanonical negative-zero integers, workflow identities, paths, timestamps, and ownership, and exposes typed bounded-recollection guidance only for reviewed transient HTTP results (`429`, `500`, `502`, `503`, and `504`) while keeping permission, absence, and protocol/capability failures such as `403`, `404`, `501`, and `505` non-retryable and non-passing. +- Read-only workflow-registry audit evidence that binds complete paginated GitHub Actions inventory to exact protected-main state, fails closed on ambiguous scalar types including noncanonical negative-zero integers, workflow identities, paths, timestamps, and ownership, and exposes typed bounded-recollection guidance only for reviewed transient HTTP results (`429`, `500`, `502`, `503`, and `504`), preserves bounded `Retry-After` seconds when collected, treats `403` as retryable only when that bounded hint is retained, and keeps `404`, `501`, `505`, and every other non-reviewed failure non-retryable and non-passing. - Canonical HTTPS and loopback-origin boundary with case-normalized schemes and hosts, default-port normalization, IPv4/IPv6 handling, browser-special numeric-host rejection, and explicit malformed-input errors. - Typed browser actions, capabilities, risk classes, execution modes, robots decisions, secret-delivery contracts, immutable canonical action-intent digests, and intent-bound approval scopes. - Deterministic fail-closed policy evaluation for untrusted instructions, origin grants, crawler restrictions, execution-mode and purpose consistency, approvals, and brokered secrets. From 3eace839515c0268d62bce6714ec30974b65839e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 17 Aug 2026 07:07:47 +0900 Subject: [PATCH 053/111] test(ci): reject Retry-After on successful registry evidence --- tests/test_workflow_registry_retry_after_contract.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/tests/test_workflow_registry_retry_after_contract.py b/tests/test_workflow_registry_retry_after_contract.py index a7e790b3b..924e9fd59 100644 --- a/tests/test_workflow_registry_retry_after_contract.py +++ b/tests/test_workflow_registry_retry_after_contract.py @@ -23,7 +23,7 @@ def _load_module(): def _payload(status_code: int, retry_after_seconds: object = None) -> dict: - """Build one failed collection page with optional bounded retry guidance.""" + """Build one collection page with optional bounded retry guidance.""" page = { "page": 1, @@ -98,6 +98,15 @@ def test_retry_after_rejects_ambiguous_or_unbounded_values(self) -> None: ): self.audit.audit_workflow_registry(_payload(429, value)) + def test_success_page_rejects_retry_after_guidance(self) -> None: + """Successful registry evidence must not silently retain retry-only metadata.""" + + with self.assertRaisesRegex( + self.audit.WorkflowAuditError, + "retry_after_seconds is only valid for a failed registry page", + ): + self.audit.audit_workflow_registry(_payload(200, 30)) + if __name__ == "__main__": unittest.main() From 4561282c9c0cab5181b5d07d4f072f835826efc0 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 17 Aug 2026 07:09:09 +0900 Subject: [PATCH 054/111] fix(ci): reject retry metadata on successful registry pages --- scripts/ci/audit_workflow_registry.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scripts/ci/audit_workflow_registry.py b/scripts/ci/audit_workflow_registry.py index 5bdaf6340..a0635b894 100644 --- a/scripts/ci/audit_workflow_registry.py +++ b/scripts/ci/audit_workflow_registry.py @@ -231,6 +231,10 @@ def _validate_pages(value: Any) -> tuple[list[dict[str, Any]], list[dict[str, An raise WorkflowAuditError( f"registry page {expected_page} did not return HTTP 200" ) + if status_code == 200 and retry_after_seconds is not None: + raise WorkflowAuditError( + "retry_after_seconds is only valid for a failed registry page" + ) if status_code != 200: raise WorkflowAuditHttpStatusError( expected_page, status_code, retry_after_seconds From 51e6fb195f94c073206b1fce3865ea0a7534a6a6 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 17 Aug 2026 14:32:33 +0900 Subject: [PATCH 055/111] test(ci): reject impossible workflow registry HTTP statuses --- tests/test_workflow_registry_http_status_contract.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/test_workflow_registry_http_status_contract.py b/tests/test_workflow_registry_http_status_contract.py index 38b0e43ec..963766b23 100644 --- a/tests/test_workflow_registry_http_status_contract.py +++ b/tests/test_workflow_registry_http_status_contract.py @@ -105,6 +105,17 @@ def test_boolean_status_is_not_accepted_as_integer_200(self) -> None: ): self.audit.audit_workflow_registry(payload) + def test_status_code_outside_http_range_is_malformed_evidence(self) -> None: + """Non-HTTP integers must not masquerade as real non-retryable responses.""" + + for status_code in (0, 99, 600, 999): + with self.subTest(status_code=status_code): + with self.assertRaisesRegex( + self.audit.WorkflowAuditError, + "status_code must be an integer from 100 through 599", + ): + self.audit.audit_workflow_registry(_payload(status_code)) + def test_exact_http_200_still_classifies_the_active_orphan_candidate(self) -> None: """The failure regression must preserve the reviewed successful evidence path.""" From 1a2110150d6dd618f1f267f107f16574f951eda5 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 17 Aug 2026 14:34:46 +0900 Subject: [PATCH 056/111] fix(ci): reject impossible workflow registry HTTP statuses --- scripts/ci/audit_workflow_registry.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scripts/ci/audit_workflow_registry.py b/scripts/ci/audit_workflow_registry.py index a0635b894..54aa6d2b4 100644 --- a/scripts/ci/audit_workflow_registry.py +++ b/scripts/ci/audit_workflow_registry.py @@ -231,6 +231,10 @@ def _validate_pages(value: Any) -> tuple[list[dict[str, Any]], list[dict[str, An raise WorkflowAuditError( f"registry page {expected_page} did not return HTTP 200" ) + if status_code < 100 or status_code > 599: + raise WorkflowAuditError( + "status_code must be an integer from 100 through 599" + ) if status_code == 200 and retry_after_seconds is not None: raise WorkflowAuditError( "retry_after_seconds is only valid for a failed registry page" From ac26bde1f861b09438e07c6789c172324127f71d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 17 Aug 2026 16:26:39 +0900 Subject: [PATCH 057/111] test(ci): bind active workflow ownership to exact PR heads --- ...kflow_registry_active_pr_owner_contract.py | 142 ++++++++++++++++++ 1 file changed, 142 insertions(+) create mode 100644 tests/test_workflow_registry_active_pr_owner_contract.py diff --git a/tests/test_workflow_registry_active_pr_owner_contract.py b/tests/test_workflow_registry_active_pr_owner_contract.py new file mode 100644 index 000000000..d6f768989 --- /dev/null +++ b/tests/test_workflow_registry_active_pr_owner_contract.py @@ -0,0 +1,142 @@ +"""Bind active-PR workflow deferrals to exact auditable pull-request identities.""" + +from __future__ import annotations + +import importlib.util +import pathlib +import unittest + +ROOT = pathlib.Path(__file__).resolve().parents[1] +SCRIPT = ROOT / "scripts" / "ci" / "audit_workflow_registry.py" +DEFAULT_SHA = "0841d2ab3d8b5e60a03c0a8e818cf438e2716829" +ACTIVE_PR_HEAD = "b" * 40 +ACTIVE_PATH = ".github/workflows/current-bounded-diagnostic.yml" + + +def _load_module(): + """Load the read-only registry auditor without packaging scripts as modules.""" + + spec = importlib.util.spec_from_file_location("audit_workflow_registry", SCRIPT) + if spec is None or spec.loader is None: + raise AssertionError("workflow registry audit module is not loadable") + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return module + + +def _payload() -> dict: + """Build evidence in which one workflow is deferred to an exact active PR owner.""" + + return { + "schema_version": 1, + "expected_default_branch_sha": DEFAULT_SHA, + "observed_default_branch_sha": DEFAULT_SHA, + "observed_at": "2026-08-17T07:30:00Z", + "reported_total_count": 1, + "protected_workflow_paths": [".github/workflows/ci.yml"], + "active_pr_workflow_paths": [ACTIVE_PATH], + "active_pr_workflow_owners": [ + { + "path": ACTIVE_PATH, + "pull_request_number": 321, + "head_sha": ACTIVE_PR_HEAD, + } + ], + "registry_pages": [ + { + "page": 1, + "status_code": 200, + "has_next": False, + "workflows": [ + { + "id": 17, + "name": "Current bounded diagnostic", + "path": ACTIVE_PATH, + "state": "active", + } + ], + } + ], + } + + +class WorkflowRegistryActivePrOwnerContractTests(unittest.TestCase): + """Prevent an unbound or stale-looking path assertion from hiding an orphan.""" + + @classmethod + def setUpClass(cls) -> None: + cls.audit = _load_module() + + def test_active_pr_deferral_retains_exact_owner_identity(self) -> None: + """A deferred workflow records the PR number and exact contributor head.""" + + evidence = self.audit.audit_workflow_registry(_payload()) + record = evidence["workflow_records"][0] + self.assertEqual(record["classification"], "active_pr_owned_workflow") + self.assertFalse(record["disable_candidate"]) + self.assertEqual( + record["active_pr_owner"], + { + "pull_request_number": 321, + "head_sha": ACTIVE_PR_HEAD, + }, + ) + + def test_nonempty_active_pr_paths_require_exact_owner_evidence(self) -> None: + """A path string alone cannot suppress an orphan disable candidate.""" + + payload = _payload() + payload.pop("active_pr_workflow_owners") + with self.assertRaisesRegex( + self.audit.WorkflowAuditError, + "active PR workflow ownership must be bound to exact PR heads", + ): + self.audit.audit_workflow_registry(payload) + + def test_owner_paths_must_exactly_match_deferred_paths(self) -> None: + """Missing, extra, or mismatched owner paths fail closed.""" + + for owners in ( + [], + [ + { + "path": ".github/workflows/other.yml", + "pull_request_number": 321, + "head_sha": ACTIVE_PR_HEAD, + } + ], + ): + with self.subTest(owners=owners): + payload = _payload() + payload["active_pr_workflow_owners"] = owners + with self.assertRaisesRegex( + self.audit.WorkflowAuditError, + "active PR workflow ownership must be bound to exact PR heads", + ): + self.audit.audit_workflow_registry(payload) + + def test_owner_identity_rejects_ambiguous_pr_numbers_and_heads(self) -> None: + """Only positive integer PR numbers and exact lowercase commit SHAs are accepted.""" + + invalid_identities = ( + (True, ACTIVE_PR_HEAD), + (0, ACTIVE_PR_HEAD), + (-1, ACTIVE_PR_HEAD), + (321, "B" * 40), + (321, "b" * 39), + ) + for pull_request_number, head_sha in invalid_identities: + with self.subTest( + pull_request_number=pull_request_number, head_sha=head_sha + ): + payload = _payload() + payload["active_pr_workflow_owners"][0][ + "pull_request_number" + ] = pull_request_number + payload["active_pr_workflow_owners"][0]["head_sha"] = head_sha + with self.assertRaises(self.audit.WorkflowAuditError): + self.audit.audit_workflow_registry(payload) + + +if __name__ == "__main__": + unittest.main() From c4641b82c4aeb0b7e536409185c2111c9cb974c1 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 17 Aug 2026 16:30:23 +0900 Subject: [PATCH 058/111] test(ci): bind legacy audit fixtures to exact PR owner --- tests/test_workflow_registry_audit.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/tests/test_workflow_registry_audit.py b/tests/test_workflow_registry_audit.py index 62ee46228..aa38df761 100644 --- a/tests/test_workflow_registry_audit.py +++ b/tests/test_workflow_registry_audit.py @@ -10,9 +10,10 @@ import unittest ROOT = pathlib.Path(__file__).resolve().parents[1] -SCRIPT = ROOT / "scripts/ci/audit_workflow_registry.py" +SCRIPT = ROOT / "scripts" / "ci" / "audit_workflow_registry.py" DEFAULT_SHA = "67af7c87589edc2039545af335c95064d9b8391c" OBSERVED_AT = "2026-08-12T00:00:00Z" +ACTIVE_PR_HEAD = "b" * 40 def _load_module(): @@ -41,6 +42,7 @@ def _payload(*workflows: dict) -> dict: """Return one complete two-page exact-protected-main audit fixture.""" split = max(1, len(workflows) // 2) + active_pr_path = ".github/workflows/current-bounded-diagnostic.yml" return { "schema_version": 1, "expected_default_branch_sha": DEFAULT_SHA, @@ -51,8 +53,13 @@ def _payload(*workflows: dict) -> dict: ".github/workflows/ci.yml", ".github/workflows/hourly-product-development.yml", ], - "active_pr_workflow_paths": [ - ".github/workflows/current-bounded-diagnostic.yml" + "active_pr_workflow_paths": [active_pr_path], + "active_pr_workflow_owners": [ + { + "path": active_pr_path, + "pull_request_number": 321, + "head_sha": ACTIVE_PR_HEAD, + } ], "registry_pages": [ { @@ -322,6 +329,10 @@ def test_active_pr_owned_diagnostic_is_not_reported_as_an_orphan(self) -> None: record = evidence["workflow_records"][0] self.assertEqual(record["classification"], "active_pr_owned_workflow") self.assertFalse(record["disable_candidate"]) + self.assertEqual( + record["active_pr_owner"], + {"pull_request_number": 321, "head_sha": ACTIVE_PR_HEAD}, + ) def test_only_reviewed_active_orphans_are_disable_candidates(self) -> None: """Dynamic, disabled, present, and active-PR records remain non-candidates.""" From 733094743be00ee21b15213b31c06a83ed9a6740 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 17 Aug 2026 16:31:30 +0900 Subject: [PATCH 059/111] fix(ci): bind active workflow exemptions to exact PR heads --- scripts/ci/audit_workflow_registry.py | 59 ++++++++++++++++++++++++++- 1 file changed, 57 insertions(+), 2 deletions(-) diff --git a/scripts/ci/audit_workflow_registry.py b/scripts/ci/audit_workflow_registry.py index 54aa6d2b4..df9e433b0 100644 --- a/scripts/ci/audit_workflow_registry.py +++ b/scripts/ci/audit_workflow_registry.py @@ -198,6 +198,52 @@ def _validated_path_set(value: Any, field_name: str) -> set[str]: return validated +def _validated_active_pr_owners( + value: Any, active_pr_paths: set[str] +) -> dict[str, dict[str, Any]]: + """Bind every deferred workflow path to one exact active PR number and head.""" + + ownership_error = "active PR workflow ownership must be bound to exact PR heads" + if value is None: + if active_pr_paths: + raise WorkflowAuditError(ownership_error) + return {} + + owners = _require_list(value, "active_pr_workflow_owners") + validated: dict[str, dict[str, Any]] = {} + for index, raw_owner in enumerate(owners): + owner = _require_exact_fields( + raw_owner, + f"active_pr_workflow_owners[{index}]", + {"path", "pull_request_number", "head_sha"}, + ) + path = _validate_workflow_path( + owner.get("path"), f"active_pr_workflow_owners[{index}].path" + ) + if not path.startswith(_REPOSITORY_WORKFLOW_PREFIX): + raise WorkflowAuditError(ownership_error) + pull_request_number = owner.get("pull_request_number") + if ( + isinstance(pull_request_number, bool) + or not isinstance(pull_request_number, int) + or pull_request_number <= 0 + ): + raise WorkflowAuditError(ownership_error) + head_sha = _validate_sha( + owner.get("head_sha"), f"active_pr_workflow_owners[{index}].head_sha" + ) + if path in validated: + raise WorkflowAuditError(ownership_error) + validated[path] = { + "pull_request_number": pull_request_number, + "head_sha": head_sha, + } + + if set(validated) != active_pr_paths: + raise WorkflowAuditError(ownership_error) + return validated + + def _validate_pages(value: Any) -> tuple[list[dict[str, Any]], list[dict[str, Any]]]: """Return complete registry records and immutable pagination receipts.""" @@ -297,6 +343,7 @@ def _validate_workflow_record( seen_paths: set[str], protected_paths: set[str], active_pr_paths: set[str], + active_pr_owners: dict[str, dict[str, Any]], default_branch_sha: str, observed_at: str, ) -> dict[str, Any]: @@ -334,7 +381,7 @@ def _validate_workflow_record( classification = _classify_workflow( path, state, protected_paths, active_pr_paths ) - return { + record = { "workflow_id": workflow_id, "name": name, "path": path, @@ -344,6 +391,9 @@ def _validate_workflow_record( "default_branch_sha": default_branch_sha, "observed_at": observed_at, } + if classification == "active_pr_owned_workflow": + record["active_pr_owner"] = active_pr_owners[path] + return record def audit_workflow_registry(payload: dict[str, Any]) -> dict[str, Any]: @@ -365,6 +415,7 @@ def audit_workflow_registry(payload: dict[str, Any]) -> dict[str, Any]: "reported_total_count", "protected_workflow_paths", "active_pr_workflow_paths", + "active_pr_workflow_owners", "registry_pages", }, ) @@ -395,6 +446,9 @@ def audit_workflow_registry(payload: dict[str, Any]) -> dict[str, Any]: active_pr_paths = _validated_path_set( document.get("active_pr_workflow_paths"), "active_pr_workflow_paths" ) + active_pr_owners = _validated_active_pr_owners( + document.get("active_pr_workflow_owners"), active_pr_paths + ) overlap = protected_paths.intersection(active_pr_paths) if overlap: raise WorkflowAuditError("protected and active-PR path ownership overlaps") @@ -414,6 +468,7 @@ def audit_workflow_registry(payload: dict[str, Any]) -> dict[str, Any]: seen_paths, protected_paths, active_pr_paths, + active_pr_owners, observed_sha, observed_at, ) @@ -521,4 +576,4 @@ def main(argv: list[str] | None = None) -> int: if __name__ == "__main__": - raise SystemExit(main()) + raise SystemExit(main()) \ No newline at end of file From 29c66268c7d51ec128bebdfb6945d5fbbf8f8943 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 17 Aug 2026 16:33:58 +0900 Subject: [PATCH 060/111] docs(changelog): record exact active PR workflow ownership --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 03a12074c..571e0d08b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ All notable changes to OriginWeave are documented in this file. The format follo - Bound explicit extension-to-Agent grants to the exact canonical origin in addition to extension identity, session, and browsing context, so a same-session navigation or port change cannot reuse the grant. - Rust workspace for independently reusable core, policy, destination, network, TLS, resource, and evidence modules. - Read-only workflow-registry audit evidence that binds complete paginated GitHub Actions inventory to exact protected-main state, fails closed on ambiguous scalar types including noncanonical negative-zero integers, workflow identities, paths, timestamps, and ownership, and exposes typed bounded-recollection guidance only for reviewed transient HTTP results (`429`, `500`, `502`, `503`, and `504`), preserves bounded `Retry-After` seconds when collected, treats `403` as retryable only when that bounded hint is retained, and keeps `404`, `501`, `505`, and every other non-reviewed failure non-retryable and non-passing. +- Exact active-PR workflow-owner evidence that binds every deferred registry path to one positive PR number and exact contributor-head SHA, rejects path-only or mismatched exemptions, and retains the independently refetchable owner identity on read-only audit output. - Canonical HTTPS and loopback-origin boundary with case-normalized schemes and hosts, default-port normalization, IPv4/IPv6 handling, browser-special numeric-host rejection, and explicit malformed-input errors. - Typed browser actions, capabilities, risk classes, execution modes, robots decisions, secret-delivery contracts, immutable canonical action-intent digests, and intent-bound approval scopes. - Deterministic fail-closed policy evaluation for untrusted instructions, origin grants, crawler restrictions, execution-mode and purpose consistency, approvals, and brokered secrets. From 43bd422cf7bbcb307bba914f889ae21de1de5f75 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 18 Aug 2026 05:33:54 -0700 Subject: [PATCH 061/111] test(ci): surface disabled active-PR workflow drift --- ...kflow_registry_active_pr_owner_contract.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/test_workflow_registry_active_pr_owner_contract.py b/tests/test_workflow_registry_active_pr_owner_contract.py index d6f768989..86f156e33 100644 --- a/tests/test_workflow_registry_active_pr_owner_contract.py +++ b/tests/test_workflow_registry_active_pr_owner_contract.py @@ -82,6 +82,25 @@ def test_active_pr_deferral_retains_exact_owner_identity(self) -> None: }, ) + def test_disabled_active_pr_workflow_is_reported_as_operational_drift(self) -> None: + """PR ownership must not hide that its registry identity is disabled.""" + + payload = _payload() + payload["registry_pages"][0]["workflows"][0]["state"] = "disabled_manually" + evidence = self.audit.audit_workflow_registry(payload) + record = evidence["workflow_records"][0] + self.assertEqual( + record["classification"], "disabled_active_pr_owned_workflow" + ) + self.assertFalse(record["disable_candidate"]) + self.assertEqual( + record["active_pr_owner"], + { + "pull_request_number": 321, + "head_sha": ACTIVE_PR_HEAD, + }, + ) + def test_nonempty_active_pr_paths_require_exact_owner_evidence(self) -> None: """A path string alone cannot suppress an orphan disable candidate.""" From 24922ed1ed922578041b22b1be9f01705835e7a8 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 18 Aug 2026 05:38:02 -0700 Subject: [PATCH 062/111] fix(ci): surface disabled active-PR workflow drift --- scripts/ci/audit_workflow_registry.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/scripts/ci/audit_workflow_registry.py b/scripts/ci/audit_workflow_registry.py index df9e433b0..603b8ed87 100644 --- a/scripts/ci/audit_workflow_registry.py +++ b/scripts/ci/audit_workflow_registry.py @@ -326,7 +326,9 @@ def _classify_workflow( return "present_repository_workflow" return "disabled_present_repository_workflow" if path in active_pr_paths: - return "active_pr_owned_workflow" + if state == "active": + return "active_pr_owned_workflow" + return "disabled_active_pr_owned_workflow" if path.startswith(_REPOSITORY_WORKFLOW_PREFIX): if state == "active": return "active_orphan_repository_workflow" @@ -391,7 +393,10 @@ def _validate_workflow_record( "default_branch_sha": default_branch_sha, "observed_at": observed_at, } - if classification == "active_pr_owned_workflow": + if classification in { + "active_pr_owned_workflow", + "disabled_active_pr_owned_workflow", + }: record["active_pr_owner"] = active_pr_owners[path] return record @@ -576,4 +581,4 @@ def main(argv: list[str] | None = None) -> int: if __name__ == "__main__": - raise SystemExit(main()) \ No newline at end of file + raise SystemExit(main()) From d4ae5429bce342d4aeeba42673a2b70982973fe8 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 18 Aug 2026 05:53:40 -0700 Subject: [PATCH 063/111] docs(ci): record disabled active-PR workflow drift --- CHANGELOG.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 571e0d08b..c9260e622 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ All notable changes to OriginWeave are documented in this file. The format follo - Rust workspace for independently reusable core, policy, destination, network, TLS, resource, and evidence modules. - Read-only workflow-registry audit evidence that binds complete paginated GitHub Actions inventory to exact protected-main state, fails closed on ambiguous scalar types including noncanonical negative-zero integers, workflow identities, paths, timestamps, and ownership, and exposes typed bounded-recollection guidance only for reviewed transient HTTP results (`429`, `500`, `502`, `503`, and `504`), preserves bounded `Retry-After` seconds when collected, treats `403` as retryable only when that bounded hint is retained, and keeps `404`, `501`, `505`, and every other non-reviewed failure non-retryable and non-passing. - Exact active-PR workflow-owner evidence that binds every deferred registry path to one positive PR number and exact contributor-head SHA, rejects path-only or mismatched exemptions, and retains the independently refetchable owner identity on read-only audit output. +- Disabled active-PR-owned workflow identities remain bound to their exact PR/head owner but are surfaced explicitly as operational drift instead of being mislabeled as active PR ownership. - Canonical HTTPS and loopback-origin boundary with case-normalized schemes and hosts, default-port normalization, IPv4/IPv6 handling, browser-special numeric-host rejection, and explicit malformed-input errors. - Typed browser actions, capabilities, risk classes, execution modes, robots decisions, secret-delivery contracts, immutable canonical action-intent digests, and intent-bound approval scopes. - Deterministic fail-closed policy evaluation for untrusted instructions, origin grants, crawler restrictions, execution-mode and purpose consistency, approvals, and brokered secrets. @@ -67,7 +68,7 @@ All notable changes to OriginWeave are documented in this file. The format follo - TLS accepts only an already verified direct stream, never a hostname or new socket, and requires the TLS origin to match the transport-authority origin exactly. - DNS TLS identity requires an applicable subjectAltName and never falls back to Common Name; literal IPv4 and IPv6 origins require exact IP subjectAltName entries. - TLS uses an explicit immutable trust-root bundle and fixed verification time, and permits only TLS 1.2 and TLS 1.3. -- TLS resumption, 0-RTT, secret extraction, key logging, client certificates, certificate compression, and dangerous custom verifier hooks are disabled in the first slice. +- TLS resumption, 0-RTT, secret extraction, key logging, client certificates, certificate compression, and dangerous custom verification hooks are disabled in the first slice. - The operating-system peer is rechecked before, during, and after the deadline-bound TLS handshake. - ALPN selection is restricted to the caller's bounded allow-list, while absence is either explicitly recorded or rejected by policy. - Revocation is reported as not configured; the product makes no OCSP or CRL validation claim without supplied revocation evidence. @@ -77,4 +78,4 @@ All notable changes to OriginWeave are documented in this file. The format follo - The hourly product agent has no Git metadata or repository authority. A separate post-verification publisher opens one PR and cannot approve or merge it. - The unprivileged OpenCode user is restricted to loopback egress during model execution, preventing runner-wide allow-listed endpoints from becoming direct source-exfiltration channels. -[Unreleased]: https://github.com/ContextualWisdomLab/OriginWeave/compare/main...HEAD +[Unreleased]: https://github.com/ContextualWisdomLab/OriginWeave/compare/main...HEAD \ No newline at end of file From 984bd50f08dc30e03c4064f2dd9b7b8063f18e3b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 18 Aug 2026 06:49:22 -0700 Subject: [PATCH 064/111] test(ci): reject null Git commit identities --- tests/test_workflow_registry_sha_contract.py | 95 ++++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 tests/test_workflow_registry_sha_contract.py diff --git a/tests/test_workflow_registry_sha_contract.py b/tests/test_workflow_registry_sha_contract.py new file mode 100644 index 000000000..9445fda67 --- /dev/null +++ b/tests/test_workflow_registry_sha_contract.py @@ -0,0 +1,95 @@ +"""Regression contracts for non-null Git commit identities in audit evidence.""" + +from __future__ import annotations + +import importlib.util +import pathlib +import unittest + +ROOT = pathlib.Path(__file__).resolve().parents[1] +SCRIPT = ROOT / "scripts" / "ci" / "audit_workflow_registry.py" +VALID_SHA = "1" * 40 +ZERO_SHA = "0" * 40 +OBSERVED_AT = "2026-08-18T00:00:00Z" +ACTIVE_PR_PATH = ".github/workflows/current-diagnostic.yml" + + +def _load_module(): + """Load the repository utility without turning scripts into a package.""" + + spec = importlib.util.spec_from_file_location("audit_workflow_registry", SCRIPT) + if spec is None or spec.loader is None: + raise AssertionError("workflow registry audit module is not loadable") + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return module + + +def _payload() -> dict: + """Return one complete empty registry export bound to a real-looking commit.""" + + return { + "schema_version": 1, + "expected_default_branch_sha": VALID_SHA, + "observed_default_branch_sha": VALID_SHA, + "observed_at": OBSERVED_AT, + "reported_total_count": 0, + "protected_workflow_paths": [], + "active_pr_workflow_paths": [], + "active_pr_workflow_owners": [], + "registry_pages": [ + { + "page": 1, + "status_code": 200, + "has_next": False, + "workflows": [], + } + ], + } + + +class WorkflowRegistryShaContractTests(unittest.TestCase): + """Reject Git's null object identifier wherever an exact commit is required.""" + + @classmethod + def setUpClass(cls) -> None: + cls.audit = _load_module() + + def test_null_default_branch_commit_is_rejected(self) -> None: + """A null object ID cannot bind registry evidence to protected main.""" + + payload = _payload() + payload["expected_default_branch_sha"] = ZERO_SHA + payload["observed_default_branch_sha"] = ZERO_SHA + + with self.assertRaisesRegex(self.audit.WorkflowAuditError, "nonzero"): + self.audit.audit_workflow_registry(payload) + + def test_null_active_pr_head_commit_is_rejected(self) -> None: + """A null object ID cannot defer a workflow to an independently refetchable PR.""" + + payload = _payload() + payload["reported_total_count"] = 1 + payload["active_pr_workflow_paths"] = [ACTIVE_PR_PATH] + payload["active_pr_workflow_owners"] = [ + { + "path": ACTIVE_PR_PATH, + "pull_request_number": 124, + "head_sha": ZERO_SHA, + } + ] + payload["registry_pages"][0]["workflows"] = [ + { + "id": 124, + "name": "Current diagnostic", + "path": ACTIVE_PR_PATH, + "state": "active", + } + ] + + with self.assertRaisesRegex(self.audit.WorkflowAuditError, "nonzero"): + self.audit.audit_workflow_registry(payload) + + +if __name__ == "__main__": + unittest.main() From 5080184afb818b5fde8a3eb60e21669b71fa2d63 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 18 Aug 2026 07:00:00 -0700 Subject: [PATCH 065/111] fix(ci): reject null Git commit identities --- scripts/ci/audit_workflow_registry.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/ci/audit_workflow_registry.py b/scripts/ci/audit_workflow_registry.py index 603b8ed87..2d4659375 100644 --- a/scripts/ci/audit_workflow_registry.py +++ b/scripts/ci/audit_workflow_registry.py @@ -119,6 +119,8 @@ def _validate_sha(value: Any, field_name: str) -> str: text = _require_nonempty_string(value, field_name, 40) if _SHA_PATTERN.fullmatch(text) is None: raise WorkflowAuditError(f"{field_name} must be a lowercase commit SHA") + if text == "0" * 40: + raise WorkflowAuditError(f"{field_name} must be a nonzero commit SHA") return text From 0fa781591ef7e98462df6af5ff6c2e8f95fd80ac Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 18 Aug 2026 07:05:01 -0700 Subject: [PATCH 066/111] docs(changelog): record null Git object rejection --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c9260e622..edfaf79ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ All notable changes to OriginWeave are documented in this file. The format follo - Bound explicit extension-to-Agent grants to exclusive trusted-time expiry in addition to extension identity, session, browsing context, and canonical origin, so a same-origin grant cannot be reused at or after the deadline. - Bound explicit extension-to-Agent grants to the exact canonical origin in addition to extension identity, session, and browsing context, so a same-session navigation or port change cannot reuse the grant. - Rust workspace for independently reusable core, policy, destination, network, TLS, resource, and evidence modules. -- Read-only workflow-registry audit evidence that binds complete paginated GitHub Actions inventory to exact protected-main state, fails closed on ambiguous scalar types including noncanonical negative-zero integers, workflow identities, paths, timestamps, and ownership, and exposes typed bounded-recollection guidance only for reviewed transient HTTP results (`429`, `500`, `502`, `503`, and `504`), preserves bounded `Retry-After` seconds when collected, treats `403` as retryable only when that bounded hint is retained, and keeps `404`, `501`, `505`, and every other non-reviewed failure non-retryable and non-passing. +- Read-only workflow-registry audit evidence that binds complete paginated GitHub Actions inventory to exact protected-main state, rejects Git's all-zero null object identifier anywhere a real protected-main or active-PR commit is required, fails closed on ambiguous scalar types including noncanonical negative-zero integers, workflow identities, paths, timestamps, and ownership, and exposes typed bounded-recollection guidance only for reviewed transient HTTP results (`429`, `500`, `502`, `503`, and `504`), preserves bounded `Retry-After` seconds when collected, treats `403` as retryable only when that bounded hint is retained, and keeps `404`, `501`, `505`, and every other non-reviewed failure non-retryable and non-passing. - Exact active-PR workflow-owner evidence that binds every deferred registry path to one positive PR number and exact contributor-head SHA, rejects path-only or mismatched exemptions, and retains the independently refetchable owner identity on read-only audit output. - Disabled active-PR-owned workflow identities remain bound to their exact PR/head owner but are surfaced explicitly as operational drift instead of being mislabeled as active PR ownership. - Canonical HTTPS and loopback-origin boundary with case-normalized schemes and hosts, default-port normalization, IPv4/IPv6 handling, browser-special numeric-host rejection, and explicit malformed-input errors. @@ -78,4 +78,4 @@ All notable changes to OriginWeave are documented in this file. The format follo - The hourly product agent has no Git metadata or repository authority. A separate post-verification publisher opens one PR and cannot approve or merge it. - The unprivileged OpenCode user is restricted to loopback egress during model execution, preventing runner-wide allow-listed endpoints from becoming direct source-exfiltration channels. -[Unreleased]: https://github.com/ContextualWisdomLab/OriginWeave/compare/main...HEAD \ No newline at end of file +[Unreleased]: https://github.com/ContextualWisdomLab/OriginWeave/compare/main...HEAD From 0e88fb92bd8170df9930f300ecc286b5de242b47 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 08:33:30 -0700 Subject: [PATCH 067/111] test(ci): reject moved active PR workflow owner heads --- ...test_workflow_registry_active_pr_owner_contract.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/test_workflow_registry_active_pr_owner_contract.py b/tests/test_workflow_registry_active_pr_owner_contract.py index 86f156e33..0533c4a35 100644 --- a/tests/test_workflow_registry_active_pr_owner_contract.py +++ b/tests/test_workflow_registry_active_pr_owner_contract.py @@ -156,6 +156,17 @@ def test_owner_identity_rejects_ambiguous_pr_numbers_and_heads(self) -> None: with self.assertRaises(self.audit.WorkflowAuditError): self.audit.audit_workflow_registry(payload) + def test_owner_head_movement_during_collection_fails_closed(self) -> None: + """A moved contributor head cannot keep suppressing an orphan candidate.""" + + payload = _payload() + payload["active_pr_workflow_owners"][0]["observed_head_sha"] = "c" * 40 + with self.assertRaisesRegex( + self.audit.WorkflowAuditError, + "active PR workflow head moved during collection", + ): + self.audit.audit_workflow_registry(payload) + if __name__ == "__main__": unittest.main() From ab67fcd4f7becb31ba71c05a5988f98781b3f0d1 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 08:37:44 -0700 Subject: [PATCH 068/111] test(ci): require second active PR head observation --- ...t_workflow_registry_active_pr_owner_contract.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/test_workflow_registry_active_pr_owner_contract.py b/tests/test_workflow_registry_active_pr_owner_contract.py index 0533c4a35..66b52d3a0 100644 --- a/tests/test_workflow_registry_active_pr_owner_contract.py +++ b/tests/test_workflow_registry_active_pr_owner_contract.py @@ -40,6 +40,7 @@ def _payload() -> dict: "path": ACTIVE_PATH, "pull_request_number": 321, "head_sha": ACTIVE_PR_HEAD, + "observed_head_sha": ACTIVE_PR_HEAD, } ], "registry_pages": [ @@ -122,6 +123,7 @@ def test_owner_paths_must_exactly_match_deferred_paths(self) -> None: "path": ".github/workflows/other.yml", "pull_request_number": 321, "head_sha": ACTIVE_PR_HEAD, + "observed_head_sha": ACTIVE_PR_HEAD, } ], ): @@ -156,6 +158,18 @@ def test_owner_identity_rejects_ambiguous_pr_numbers_and_heads(self) -> None: with self.assertRaises(self.audit.WorkflowAuditError): self.audit.audit_workflow_registry(payload) + def test_observed_owner_head_requires_exact_lowercase_sha(self) -> None: + """The second owner-head observation is independently syntax-validated.""" + + for observed_head_sha in ("C" * 40, "c" * 39): + with self.subTest(observed_head_sha=observed_head_sha): + payload = _payload() + payload["active_pr_workflow_owners"][0][ + "observed_head_sha" + ] = observed_head_sha + with self.assertRaises(self.audit.WorkflowAuditError): + self.audit.audit_workflow_registry(payload) + def test_owner_head_movement_during_collection_fails_closed(self) -> None: """A moved contributor head cannot keep suppressing an orphan candidate.""" From 43cd379fbfbac242100abefc1b930d3407688317 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 08:39:49 -0700 Subject: [PATCH 069/111] fix(ci): fail closed when active PR workflow owner head moves --- scripts/ci/audit_workflow_registry.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/scripts/ci/audit_workflow_registry.py b/scripts/ci/audit_workflow_registry.py index 2d4659375..c039c2897 100644 --- a/scripts/ci/audit_workflow_registry.py +++ b/scripts/ci/audit_workflow_registry.py @@ -203,7 +203,7 @@ def _validated_path_set(value: Any, field_name: str) -> set[str]: def _validated_active_pr_owners( value: Any, active_pr_paths: set[str] ) -> dict[str, dict[str, Any]]: - """Bind every deferred workflow path to one exact active PR number and head.""" + """Bind every deferred path to one PR and two equal exact head observations.""" ownership_error = "active PR workflow ownership must be bound to exact PR heads" if value is None: @@ -217,7 +217,7 @@ def _validated_active_pr_owners( owner = _require_exact_fields( raw_owner, f"active_pr_workflow_owners[{index}]", - {"path", "pull_request_number", "head_sha"}, + {"path", "pull_request_number", "head_sha", "observed_head_sha"}, ) path = _validate_workflow_path( owner.get("path"), f"active_pr_workflow_owners[{index}].path" @@ -234,6 +234,12 @@ def _validated_active_pr_owners( head_sha = _validate_sha( owner.get("head_sha"), f"active_pr_workflow_owners[{index}].head_sha" ) + observed_head_sha = _validate_sha( + owner.get("observed_head_sha"), + f"active_pr_workflow_owners[{index}].observed_head_sha", + ) + if head_sha != observed_head_sha: + raise WorkflowAuditError("active PR workflow head moved during collection") if path in validated: raise WorkflowAuditError(ownership_error) validated[path] = { @@ -583,4 +589,4 @@ def main(argv: list[str] | None = None) -> int: if __name__ == "__main__": - raise SystemExit(main()) + raise SystemExit(main()) \ No newline at end of file From 53c7c4368cd781f9b80ec79f101d5b8339f20c12 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 08:41:49 -0700 Subject: [PATCH 070/111] test(ci): refresh workflow owner fixtures at exact heads --- tests/test_workflow_registry_audit.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_workflow_registry_audit.py b/tests/test_workflow_registry_audit.py index aa38df761..8e1f03a6a 100644 --- a/tests/test_workflow_registry_audit.py +++ b/tests/test_workflow_registry_audit.py @@ -59,6 +59,7 @@ def _payload(*workflows: dict) -> dict: "path": active_pr_path, "pull_request_number": 321, "head_sha": ACTIVE_PR_HEAD, + "observed_head_sha": ACTIVE_PR_HEAD, } ], "registry_pages": [ @@ -359,4 +360,4 @@ def test_only_reviewed_active_orphans_are_disable_candidates(self) -> None: if __name__ == "__main__": - unittest.main() + unittest.main() \ No newline at end of file From 4c9e07e6946f3db2bb37a9d9633ef30e784b3a91 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 20:40:08 -0700 Subject: [PATCH 071/111] test(ci): expose bounded registry retry guidance --- ..._workflow_registry_retry_after_contract.py | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/tests/test_workflow_registry_retry_after_contract.py b/tests/test_workflow_registry_retry_after_contract.py index 924e9fd59..0932026d8 100644 --- a/tests/test_workflow_registry_retry_after_contract.py +++ b/tests/test_workflow_registry_retry_after_contract.py @@ -2,8 +2,12 @@ from __future__ import annotations +import contextlib import importlib.util +import io +import json import pathlib +import tempfile import unittest ROOT = pathlib.Path(__file__).resolve().parents[1] @@ -52,6 +56,17 @@ class WorkflowRegistryRetryAfterContractTests(unittest.TestCase): def setUpClass(cls) -> None: cls.audit = _load_module() + def _run_cli(self, payload: dict) -> tuple[int, str]: + """Run the operator CLI against one bounded local evidence document.""" + + with tempfile.TemporaryDirectory(prefix="originweave-workflow-audit-") as directory: + input_path = pathlib.Path(directory) / "registry.json" + input_path.write_text(json.dumps(payload), encoding="utf-8") + stderr = io.StringIO() + with contextlib.redirect_stderr(stderr): + exit_code = self.audit.main([str(input_path)]) + return exit_code, stderr.getvalue() + def test_rate_limited_403_becomes_retryable_only_with_bounded_retry_after(self) -> None: """A collected 403 may be retried only when the collector retained Retry-After.""" @@ -87,6 +102,26 @@ def test_retry_after_does_not_make_nontransient_statuses_retryable(self) -> None self.assertFalse(raised.exception.retryable) self.assertEqual(raised.exception.retry_after_seconds, 30) + def test_cli_preserves_retryable_recollection_guidance(self) -> None: + """Operator stderr must retain the typed bounded wait decision on failure.""" + + exit_code, diagnostic = self._run_cli(_payload(403, 30)) + + self.assertEqual(exit_code, 1) + self.assertIn("registry page 1 did not return HTTP 200", diagnostic) + self.assertIn("retryable=true", diagnostic) + self.assertIn("retry_after_seconds=30", diagnostic) + + def test_cli_preserves_nonretryable_decision_without_promoting_hint(self) -> None: + """A retained delay on a non-reviewed status must stay explicitly non-retryable.""" + + exit_code, diagnostic = self._run_cli(_payload(404, 30)) + + self.assertEqual(exit_code, 1) + self.assertIn("registry page 1 did not return HTTP 200", diagnostic) + self.assertIn("retryable=false", diagnostic) + self.assertIn("retry_after_seconds=30", diagnostic) + def test_retry_after_rejects_ambiguous_or_unbounded_values(self) -> None: """Retry guidance is a bounded integer, not a bool, negative, or huge wait.""" From 8206d9e47d02ac20805b2fa2da78d8f3f27bcdab Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 20:45:02 -0700 Subject: [PATCH 072/111] fix(ci): surface bounded registry retry guidance --- scripts/ci/audit_workflow_registry.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/scripts/ci/audit_workflow_registry.py b/scripts/ci/audit_workflow_registry.py index c039c2897..912e15a3c 100644 --- a/scripts/ci/audit_workflow_registry.py +++ b/scripts/ci/audit_workflow_registry.py @@ -572,6 +572,15 @@ def main(argv: list[str] | None = None) -> int: arguments = parser.parse_args(argv) try: evidence = audit_workflow_registry(_read_payload(arguments.input)) + except WorkflowAuditHttpStatusError as error: + message = ( + f"workflow registry audit failed: {error}; " + f"retryable={'true' if error.retryable else 'false'}" + ) + if error.retry_after_seconds is not None: + message += f"; retry_after_seconds={error.retry_after_seconds}" + print(message, file=sys.stderr) + return 1 except (OSError, WorkflowAuditError) as error: print(f"workflow registry audit failed: {error}", file=sys.stderr) return 1 From f76e7a441d0aee34843e5af7aef07c4bd6f4673d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 22:58:48 -0700 Subject: [PATCH 073/111] test(ci): require retryable workflow audit timeout --- tests/test_workflow_registry_retry_after_contract.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_workflow_registry_retry_after_contract.py b/tests/test_workflow_registry_retry_after_contract.py index 0932026d8..38ecf725c 100644 --- a/tests/test_workflow_registry_retry_after_contract.py +++ b/tests/test_workflow_registry_retry_after_contract.py @@ -83,9 +83,9 @@ def test_rate_limited_403_becomes_retryable_only_with_bounded_retry_after(self) self.assertIsNone(raised_without_hint.exception.retry_after_seconds) def test_retry_after_is_preserved_for_reviewed_transient_statuses(self) -> None: - """429 and transient server failures expose the same bounded wait hint.""" + """Request timeout, rate limiting, and transient server failures expose bounded wait hints.""" - for status_code in (429, 500, 502, 503, 504): + for status_code in (408, 429, 500, 502, 503, 504): with self.subTest(status_code=status_code): with self.assertRaises(self.audit.WorkflowAuditHttpStatusError) as raised: self.audit.audit_workflow_registry(_payload(status_code, 120)) From 6dae9529f446996cbccc504c49d66af48a80df80 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 23:01:39 -0700 Subject: [PATCH 074/111] fix(ci): classify workflow audit request timeout as retryable --- scripts/ci/audit_workflow_registry.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/ci/audit_workflow_registry.py b/scripts/ci/audit_workflow_registry.py index 912e15a3c..e50234207 100644 --- a/scripts/ci/audit_workflow_registry.py +++ b/scripts/ci/audit_workflow_registry.py @@ -35,7 +35,7 @@ "disabled_manually", } _ALLOWED_STATES = {"active", *_DISABLED_STATES} -_RETRYABLE_HTTP_STATUSES = {429, 500, 502, 503, 504} +_RETRYABLE_HTTP_STATUSES = {408, 429, 500, 502, 503, 504} class WorkflowAuditError(ValueError): @@ -598,4 +598,4 @@ def main(argv: list[str] | None = None) -> int: if __name__ == "__main__": - raise SystemExit(main()) \ No newline at end of file + raise SystemExit(main()) From f112d2d1028e50432663feaabd459339669af1f9 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 20 Aug 2026 23:05:23 -0700 Subject: [PATCH 075/111] docs(changelog): record workflow audit request-timeout retry --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index edfaf79ec..10773bc4d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ All notable changes to OriginWeave are documented in this file. The format follo - Bound explicit extension-to-Agent grants to exclusive trusted-time expiry in addition to extension identity, session, browsing context, and canonical origin, so a same-origin grant cannot be reused at or after the deadline. - Bound explicit extension-to-Agent grants to the exact canonical origin in addition to extension identity, session, and browsing context, so a same-session navigation or port change cannot reuse the grant. - Rust workspace for independently reusable core, policy, destination, network, TLS, resource, and evidence modules. -- Read-only workflow-registry audit evidence that binds complete paginated GitHub Actions inventory to exact protected-main state, rejects Git's all-zero null object identifier anywhere a real protected-main or active-PR commit is required, fails closed on ambiguous scalar types including noncanonical negative-zero integers, workflow identities, paths, timestamps, and ownership, and exposes typed bounded-recollection guidance only for reviewed transient HTTP results (`429`, `500`, `502`, `503`, and `504`), preserves bounded `Retry-After` seconds when collected, treats `403` as retryable only when that bounded hint is retained, and keeps `404`, `501`, `505`, and every other non-reviewed failure non-retryable and non-passing. +- Read-only workflow-registry audit evidence that binds complete paginated GitHub Actions inventory to exact protected-main state, rejects Git's all-zero null object identifier anywhere a real protected-main or active-PR commit is required, fails closed on ambiguous scalar types including noncanonical negative-zero integers, workflow identities, paths, timestamps, and ownership, and exposes typed bounded-recollection guidance only for reviewed transient HTTP results (`408`, `429`, `500`, `502`, `503`, and `504`), preserves bounded `Retry-After` seconds when collected, treats `403` as retryable only when that bounded hint is retained, and keeps `404`, `501`, `505`, and every other non-reviewed failure non-retryable and non-passing. - Exact active-PR workflow-owner evidence that binds every deferred registry path to one positive PR number and exact contributor-head SHA, rejects path-only or mismatched exemptions, and retains the independently refetchable owner identity on read-only audit output. - Disabled active-PR-owned workflow identities remain bound to their exact PR/head owner but are surfaced explicitly as operational drift instead of being mislabeled as active PR ownership. - Canonical HTTPS and loopback-origin boundary with case-normalized schemes and hosts, default-port normalization, IPv4/IPv6 handling, browser-special numeric-host rejection, and explicit malformed-input errors. From 766ecaae27116507e51632ba6431873b2b8f94a5 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 23 Aug 2026 15:10:18 -0700 Subject: [PATCH 076/111] test(ci): reject streaming workflow audit inputs --- ...st_workflow_registry_file_type_contract.py | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 tests/test_workflow_registry_file_type_contract.py diff --git a/tests/test_workflow_registry_file_type_contract.py b/tests/test_workflow_registry_file_type_contract.py new file mode 100644 index 000000000..40b6f00dd --- /dev/null +++ b/tests/test_workflow_registry_file_type_contract.py @@ -0,0 +1,57 @@ +"""Regression contract for bounded workflow-registry audit input file types.""" + +from __future__ import annotations + +import os +import pathlib +import runpy +import tempfile +import threading +import unittest + +ROOT = pathlib.Path(__file__).resolve().parents[1] +AUDITOR = ROOT / "scripts" / "ci" / "audit_workflow_registry.py" + + +class WorkflowRegistryFileTypeContractTests(unittest.TestCase): + """Prevent streaming OS file types from bypassing bounded audit-input semantics.""" + + def test_fifo_input_is_rejected_before_registry_bytes_are_accepted(self) -> None: + """A named pipe is not immutable operator-collected registry evidence.""" + + if not hasattr(os, "mkfifo"): + self.fail("the workflow audit file-type regression requires POSIX mkfifo support") + + namespace = runpy.run_path(str(AUDITOR), run_name="workflow_file_type_contract") + read_payload = namespace["_read_payload"] + audit_error = namespace["WorkflowAuditError"] + + with tempfile.TemporaryDirectory(prefix="originweave-workflow-fifo-") as directory: + candidate = pathlib.Path(directory) / "registry.json" + os.mkfifo(candidate) + + writer_started = threading.Event() + + def write_candidate() -> None: + writer_started.set() + with candidate.open("wb") as sink: + sink.write(b"{}") + + writer = threading.Thread(target=write_candidate, daemon=True) + writer.start() + self.assertTrue(writer_started.wait(timeout=1.0)) + + try: + with self.assertRaisesRegex(audit_error, "input must be a regular file"): + read_payload(candidate) + finally: + if writer.is_alive(): + with candidate.open("rb", buffering=0) as release_reader: + release_reader.read(2) + writer.join(timeout=1.0) + + self.assertFalse(writer.is_alive(), "FIFO writer remained blocked after rejection") + + +if __name__ == "__main__": + unittest.main() From bad20dcc30ae3891b28476187beda772e3a6c478 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 23 Aug 2026 15:12:42 -0700 Subject: [PATCH 077/111] fix(ci): reject non-regular workflow audit inputs --- scripts/ci/audit_workflow_registry.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/scripts/ci/audit_workflow_registry.py b/scripts/ci/audit_workflow_registry.py index e50234207..da6b84606 100644 --- a/scripts/ci/audit_workflow_registry.py +++ b/scripts/ci/audit_workflow_registry.py @@ -12,8 +12,10 @@ import argparse import datetime import json +import os import pathlib import re +import stat import sys from typing import Any @@ -534,11 +536,19 @@ def _parse_bounded_json_integer(value: str) -> int: return int(value) +def _nonblocking_read_opener(path: str, flags: int) -> int: + """Open audit input without allowing a FIFO/device open to wait indefinitely.""" + + return os.open(path, flags | getattr(os, "O_NONBLOCK", 0)) + + def _read_payload(path: pathlib.Path) -> dict[str, Any]: - """Read at most four mebibytes of unambiguous UTF-8 JSON audit evidence.""" + """Read at most four mebibytes from one regular UTF-8 JSON evidence file.""" try: - with path.open("rb") as source: + with open(path, "rb", opener=_nonblocking_read_opener) as source: + if not stat.S_ISREG(os.fstat(source.fileno()).st_mode): + raise WorkflowAuditError("input must be a regular file") content = source.read(_MAX_INPUT_BYTES + 1) except OSError as error: raise WorkflowAuditError("input is not readable UTF-8 JSON") from error From 40f64da9aa8afd0beb042a3178812ca9f085447b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 23 Aug 2026 15:17:20 -0700 Subject: [PATCH 078/111] test(ci): keep size bound on real audit files --- tests/test_workflow_registry_audit.py | 48 +++++++-------------------- 1 file changed, 12 insertions(+), 36 deletions(-) diff --git a/tests/test_workflow_registry_audit.py b/tests/test_workflow_registry_audit.py index 8e1f03a6a..57f35731a 100644 --- a/tests/test_workflow_registry_audit.py +++ b/tests/test_workflow_registry_audit.py @@ -3,10 +3,9 @@ from __future__ import annotations import importlib.util -import io import json import pathlib -import types +import tempfile import unittest ROOT = pathlib.Path(__file__).resolve().parents[1] @@ -79,34 +78,6 @@ def _payload(*workflows: dict) -> dict: } -class _GrowingAuditInput: - """Model an input that grows after a stale metadata check.""" - - def __init__(self, maximum_input_bytes: int) -> None: - self._content = json.dumps( - {"padding": "x" * maximum_input_bytes}, separators=(",", ":") - ).encode("utf-8") - - def stat(self): - """Return deliberately stale metadata claiming one byte.""" - - return types.SimpleNamespace(st_size=1) - - def read_text(self, encoding: str): - """Expose the post-check oversized content to the legacy reader.""" - - if encoding != "utf-8": - raise AssertionError("unexpected encoding") - return self._content.decode("utf-8") - - def open(self, mode: str): - """Expose the same content through a bounded binary reader.""" - - if mode != "rb": - raise AssertionError("unexpected mode") - return io.BytesIO(self._content) - - class WorkflowRegistryAuditTests(unittest.TestCase): """Keep workflow-lifecycle evidence exhaustive, immutable, and non-mutating.""" @@ -227,12 +198,17 @@ def test_reported_total_count_must_match_the_complete_unique_inventory(self) -> with self.assertRaises(self.audit.WorkflowAuditError): self.audit.audit_workflow_registry(payload) - def test_input_size_bound_applies_to_bytes_read_not_stale_metadata(self) -> None: - """A growing or replaced input cannot bypass the four-mebibyte read bound.""" + def test_input_size_bound_applies_to_bytes_read_from_regular_file(self) -> None: + """The four-mebibyte limit is enforced on bytes from the admitted file itself.""" - source = _GrowingAuditInput(self.audit._MAX_INPUT_BYTES) - with self.assertRaises(self.audit.WorkflowAuditError): - self.audit._read_payload(source) + with tempfile.TemporaryDirectory(prefix="originweave-workflow-size-") as directory: + source = pathlib.Path(directory) / "registry.json" + source.write_bytes(b"x" * (self.audit._MAX_INPUT_BYTES + 1)) + with self.assertRaisesRegex( + self.audit.WorkflowAuditError, + "input exceeds the four-mebibyte audit bound", + ): + self.audit._read_payload(source) def test_permission_and_transient_http_failures_fail_closed(self) -> None: """403/404/5xx exports are evidence gaps, not empty successful pages.""" @@ -360,4 +336,4 @@ def test_only_reviewed_active_orphans_are_disable_candidates(self) -> None: if __name__ == "__main__": - unittest.main() \ No newline at end of file + unittest.main() From e64f7d2ab23373fdad244fa3e49713df0dc3882a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 23 Aug 2026 15:17:36 -0700 Subject: [PATCH 079/111] test(ci): tolerate expected FIFO writer disconnect --- tests/test_workflow_registry_file_type_contract.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/test_workflow_registry_file_type_contract.py b/tests/test_workflow_registry_file_type_contract.py index 40b6f00dd..b5f3d09ec 100644 --- a/tests/test_workflow_registry_file_type_contract.py +++ b/tests/test_workflow_registry_file_type_contract.py @@ -34,8 +34,12 @@ def test_fifo_input_is_rejected_before_registry_bytes_are_accepted(self) -> None def write_candidate() -> None: writer_started.set() - with candidate.open("wb") as sink: - sink.write(b"{}") + try: + with candidate.open("wb") as sink: + sink.write(b"{}") + except BrokenPipeError: + # Expected when the reader rejects the FIFO before consuming bytes. + return writer = threading.Thread(target=write_candidate, daemon=True) writer.start() From 08f68717d8dcbe173a980d1a92a6fd213c9e625a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 23 Aug 2026 15:33:55 -0700 Subject: [PATCH 080/111] test(ci): remove stale workflow audit import --- tests/test_workflow_registry_audit.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_workflow_registry_audit.py b/tests/test_workflow_registry_audit.py index 57f35731a..a34b1ff1c 100644 --- a/tests/test_workflow_registry_audit.py +++ b/tests/test_workflow_registry_audit.py @@ -3,7 +3,6 @@ from __future__ import annotations import importlib.util -import json import pathlib import tempfile import unittest From 8c3d6d22eadd23a54acab0c736100008ba5e611f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 23 Aug 2026 20:30:38 -0700 Subject: [PATCH 081/111] test(ci): reject symlink workflow audit evidence --- ...st_workflow_registry_file_type_contract.py | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/tests/test_workflow_registry_file_type_contract.py b/tests/test_workflow_registry_file_type_contract.py index b5f3d09ec..2f03526c1 100644 --- a/tests/test_workflow_registry_file_type_contract.py +++ b/tests/test_workflow_registry_file_type_contract.py @@ -14,7 +14,7 @@ class WorkflowRegistryFileTypeContractTests(unittest.TestCase): - """Prevent streaming OS file types from bypassing bounded audit-input semantics.""" + """Prevent indirect or streaming OS file types from becoming audit evidence.""" def test_fifo_input_is_rejected_before_registry_bytes_are_accepted(self) -> None: """A named pipe is not immutable operator-collected registry evidence.""" @@ -56,6 +56,26 @@ def write_candidate() -> None: self.assertFalse(writer.is_alive(), "FIFO writer remained blocked after rejection") + def test_symbolic_link_input_is_rejected_before_target_bytes_are_accepted(self) -> None: + """Audit evidence must name the collected regular file directly, not through a symlink.""" + + namespace = runpy.run_path(str(AUDITOR), run_name="workflow_symlink_contract") + read_payload = namespace["_read_payload"] + audit_error = namespace["WorkflowAuditError"] + + with tempfile.TemporaryDirectory(prefix="originweave-workflow-symlink-") as directory: + root = pathlib.Path(directory) + target = root / "collected-registry.json" + target.write_text("{}", encoding="utf-8") + candidate = root / "registry.json" + try: + candidate.symlink_to(target) + except OSError as error: + self.skipTest(f"symbolic links are unavailable on this platform: {error}") + + with self.assertRaisesRegex(audit_error, "input must not be a symbolic link"): + read_payload(candidate) + if __name__ == "__main__": unittest.main() From 42b1edd36fbddb766fc1e335f446f1ad21799852 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 23 Aug 2026 20:35:51 -0700 Subject: [PATCH 082/111] fix(ci): reject indirect workflow audit evidence --- scripts/ci/audit_workflow_registry.py | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/scripts/ci/audit_workflow_registry.py b/scripts/ci/audit_workflow_registry.py index da6b84606..d24cb0d0d 100644 --- a/scripts/ci/audit_workflow_registry.py +++ b/scripts/ci/audit_workflow_registry.py @@ -11,6 +11,7 @@ import argparse import datetime +import errno import json import os import pathlib @@ -359,7 +360,7 @@ def _validate_workflow_record( default_branch_sha: str, observed_at: str, ) -> dict[str, Any]: - """Validate and classify one exported GitHub Actions workflow record.""" + """Validate and classify one exported GitHub Actions workflow registry record.""" raw_record = _require_exact_fields( raw_record, @@ -537,20 +538,36 @@ def _parse_bounded_json_integer(value: str) -> int: def _nonblocking_read_opener(path: str, flags: int) -> int: - """Open audit input without allowing a FIFO/device open to wait indefinitely.""" + """Open audit input without following a final symlink or blocking on a stream.""" - return os.open(path, flags | getattr(os, "O_NONBLOCK", 0)) + return os.open( + path, + flags | getattr(os, "O_NONBLOCK", 0) | getattr(os, "O_NOFOLLOW", 0), + ) def _read_payload(path: pathlib.Path) -> dict[str, Any]: - """Read at most four mebibytes from one regular UTF-8 JSON evidence file.""" + """Read at most four mebibytes from one direct regular UTF-8 JSON evidence file.""" try: + candidate_stat = path.lstat() + if stat.S_ISLNK(candidate_stat.st_mode): + raise WorkflowAuditError("input must not be a symbolic link") + if not stat.S_ISREG(candidate_stat.st_mode): + raise WorkflowAuditError("input must be a regular file") with open(path, "rb", opener=_nonblocking_read_opener) as source: - if not stat.S_ISREG(os.fstat(source.fileno()).st_mode): + opened_stat = os.fstat(source.fileno()) + if not stat.S_ISREG(opened_stat.st_mode): raise WorkflowAuditError("input must be a regular file") + if (candidate_stat.st_dev, candidate_stat.st_ino) != ( + opened_stat.st_dev, + opened_stat.st_ino, + ): + raise WorkflowAuditError("input file identity changed during read") content = source.read(_MAX_INPUT_BYTES + 1) except OSError as error: + if error.errno == errno.ELOOP: + raise WorkflowAuditError("input must not be a symbolic link") from error raise WorkflowAuditError("input is not readable UTF-8 JSON") from error if len(content) > _MAX_INPUT_BYTES: raise WorkflowAuditError("input exceeds the four-mebibyte audit bound") From 772613044d3aa64cc0149a44f3f8f73ed54f520a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 23 Aug 2026 21:15:49 -0700 Subject: [PATCH 083/111] test(ci): reject workflow audit ancestor symlink swaps --- ...st_workflow_registry_file_type_contract.py | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/tests/test_workflow_registry_file_type_contract.py b/tests/test_workflow_registry_file_type_contract.py index 2f03526c1..7c0ac83f8 100644 --- a/tests/test_workflow_registry_file_type_contract.py +++ b/tests/test_workflow_registry_file_type_contract.py @@ -76,6 +76,51 @@ def test_symbolic_link_input_is_rejected_before_target_bytes_are_accepted(self) with self.assertRaisesRegex(audit_error, "input must not be a symbolic link"): read_payload(candidate) + @unittest.skipUnless(hasattr(os, "symlink") and hasattr(os, "link"), "requires links") + def test_parent_swap_to_symlink_during_open_fails_closed(self) -> None: + """A transient ancestor symlink must not redirect the actual evidence-file open.""" + + namespace = runpy.run_path(str(AUDITOR), run_name="workflow_parent_swap_contract") + read_payload = namespace["_read_payload"] + audit_error = namespace["WorkflowAuditError"] + original_opener = namespace["_nonblocking_read_opener"] + + with tempfile.TemporaryDirectory(prefix="originweave-workflow-parent-swap-") as directory: + root = pathlib.Path(directory) + direct_directory = root / "direct" + direct_directory.mkdir() + actual_directory = root / "actual" + actual_directory.mkdir() + + actual_candidate = actual_directory / "registry.json" + actual_candidate.write_text("{}", encoding="utf-8") + direct_candidate = direct_directory / "registry.json" + try: + os.link(actual_candidate, direct_candidate) + except OSError as error: + self.skipTest(f"hard links are unavailable on this platform: {error}") + parked_directory = root / "direct-parked" + + def swapping_opener(path: str, flags: int) -> int: + direct_directory.rename(parked_directory) + try: + direct_directory.symlink_to(actual_directory.name, target_is_directory=True) + except OSError as error: + parked_directory.rename(direct_directory) + self.skipTest(f"directory symlinks are unavailable on this platform: {error}") + try: + return original_opener(path, flags) + finally: + direct_directory.unlink() + parked_directory.rename(direct_directory) + + read_payload.__globals__["_nonblocking_read_opener"] = swapping_opener + try: + with self.assertRaisesRegex(audit_error, "input must not use a symbolic-link parent"): + read_payload(direct_candidate) + finally: + read_payload.__globals__["_nonblocking_read_opener"] = original_opener + if __name__ == "__main__": unittest.main() From 49244fb80d25f262489ea85b878f5a267253fab2 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 23 Aug 2026 21:18:48 -0700 Subject: [PATCH 084/111] fix(ci): pin workflow audit path traversal to directory descriptors --- scripts/ci/audit_workflow_registry.py | 52 ++++++++++++++++++++++++--- 1 file changed, 47 insertions(+), 5 deletions(-) diff --git a/scripts/ci/audit_workflow_registry.py b/scripts/ci/audit_workflow_registry.py index d24cb0d0d..9241c7c50 100644 --- a/scripts/ci/audit_workflow_registry.py +++ b/scripts/ci/audit_workflow_registry.py @@ -538,12 +538,54 @@ def _parse_bounded_json_integer(value: str) -> int: def _nonblocking_read_opener(path: str, flags: int) -> int: - """Open audit input without following a final symlink or blocking on a stream.""" + """Open audit input through a no-follow descriptor-relative component walk.""" + + nofollow = getattr(os, "O_NOFOLLOW", 0) + directory = getattr(os, "O_DIRECTORY", 0) + cloexec = getattr(os, "O_CLOEXEC", 0) + if not nofollow or not directory or os.open not in os.supports_dir_fd: + raise WorkflowAuditError("secure direct-file open is unavailable") + + candidate = pathlib.Path(path) + components = list(candidate.parts) + if candidate.is_absolute(): + anchor = candidate.anchor + components = components[1:] + else: + anchor = "." + if not components or any(component in {"", ".", ".."} for component in components): + raise WorkflowAuditError("input path is ambiguous") + + directory_flags = os.O_RDONLY | directory | nofollow | cloexec + try: + parent_fd = os.open(anchor, directory_flags) + except OSError as error: + raise WorkflowAuditError("input is not readable UTF-8 JSON") from error - return os.open( - path, - flags | getattr(os, "O_NONBLOCK", 0) | getattr(os, "O_NOFOLLOW", 0), - ) + try: + for component in components[:-1]: + try: + next_fd = os.open(component, directory_flags, dir_fd=parent_fd) + except OSError as error: + if error.errno in {errno.ELOOP, errno.ENOTDIR}: + raise WorkflowAuditError( + "input must not use a symbolic-link parent" + ) from error + raise + os.close(parent_fd) + parent_fd = next_fd + try: + return os.open( + components[-1], + flags | getattr(os, "O_NONBLOCK", 0) | nofollow | cloexec, + dir_fd=parent_fd, + ) + except OSError as error: + if error.errno == errno.ELOOP: + raise WorkflowAuditError("input must not be a symbolic link") from error + raise + finally: + os.close(parent_fd) def _read_payload(path: pathlib.Path) -> dict[str, Any]: From 0b3d59e1eaea46c6de6720a679b0ca895bf938d3 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 23 Aug 2026 22:03:02 -0700 Subject: [PATCH 085/111] test(ci): reject symlinked workflow audit output --- ..._workflow_registry_output_file_contract.py | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 tests/test_workflow_registry_output_file_contract.py diff --git a/tests/test_workflow_registry_output_file_contract.py b/tests/test_workflow_registry_output_file_contract.py new file mode 100644 index 000000000..0cf2f2258 --- /dev/null +++ b/tests/test_workflow_registry_output_file_contract.py @@ -0,0 +1,71 @@ +"""Regression contract for workflow-registry audit output path authority.""" + +from __future__ import annotations + +import json +import pathlib +import runpy +import tempfile +import unittest + +ROOT = pathlib.Path(__file__).resolve().parents[1] +AUDITOR = ROOT / "scripts" / "ci" / "audit_workflow_registry.py" +SHA = "a" * 40 + + +def _payload() -> dict: + """Return one valid single-page read-only workflow-registry fixture.""" + + return { + "schema_version": 1, + "expected_default_branch_sha": SHA, + "observed_default_branch_sha": SHA, + "observed_at": "2026-08-24T04:00:00Z", + "reported_total_count": 1, + "protected_workflow_paths": [".github/workflows/ci.yml"], + "active_pr_workflow_paths": [], + "registry_pages": [ + { + "page": 1, + "status_code": 200, + "has_next": False, + "workflows": [ + { + "id": 1, + "name": "CI", + "path": ".github/workflows/ci.yml", + "state": "active", + } + ], + } + ], + } + + +class WorkflowRegistryOutputFileContractTests(unittest.TestCase): + """Prevent an audit output path from inheriting ambient symlink authority.""" + + def test_symbolic_link_output_cannot_overwrite_its_target(self) -> None: + """A caller-controlled symlink must never redirect canonical audit evidence output.""" + + namespace = runpy.run_path(str(AUDITOR), run_name="workflow_output_file_contract") + main = namespace["main"] + + with tempfile.TemporaryDirectory(prefix="originweave-workflow-output-") as directory: + root = pathlib.Path(directory) + source = root / "registry.json" + source.write_text(json.dumps(_payload()), encoding="utf-8") + target = root / "operator-owned.txt" + target.write_text("sentinel\n", encoding="utf-8") + output = root / "audit.json" + try: + output.symlink_to(target) + except OSError as error: + self.skipTest(f"symbolic links are unavailable on this platform: {error}") + + self.assertEqual(main([str(source), "--output", str(output)]), 1) + self.assertEqual(target.read_text(encoding="utf-8"), "sentinel\n") + + +if __name__ == "__main__": + unittest.main() From dc90cd453d14b8093243fa57021f50a206a4af25 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 23 Aug 2026 22:07:40 -0700 Subject: [PATCH 086/111] fix(ci): bind workflow audit output path authority --- scripts/ci/audit_workflow_registry.py | 102 +++++++++++++++++++++++++- 1 file changed, 100 insertions(+), 2 deletions(-) diff --git a/scripts/ci/audit_workflow_registry.py b/scripts/ci/audit_workflow_registry.py index 9241c7c50..6e36169cc 100644 --- a/scripts/ci/audit_workflow_registry.py +++ b/scripts/ci/audit_workflow_registry.py @@ -626,6 +626,104 @@ def _read_payload(path: pathlib.Path) -> dict[str, Any]: return _require_mapping(parsed, "payload") +def _open_output_descriptor(path: pathlib.Path, create_new: bool) -> int: + """Open one output leaf without following symbolic-link path components.""" + + nofollow = getattr(os, "O_NOFOLLOW", 0) + directory = getattr(os, "O_DIRECTORY", 0) + cloexec = getattr(os, "O_CLOEXEC", 0) + if not nofollow or not directory or os.open not in os.supports_dir_fd: + raise WorkflowAuditError("secure direct-file output is unavailable") + + components = list(path.parts) + if path.is_absolute(): + anchor = path.anchor + components = components[1:] + else: + anchor = "." + if not components or any(component in {"", ".", ".."} for component in components): + raise WorkflowAuditError("output path is ambiguous") + + directory_flags = os.O_RDONLY | directory | nofollow | cloexec + try: + parent_fd = os.open(anchor, directory_flags) + except OSError as error: + raise WorkflowAuditError("output path is not writable") from error + + try: + for component in components[:-1]: + try: + next_fd = os.open(component, directory_flags, dir_fd=parent_fd) + except OSError as error: + if error.errno in {errno.ELOOP, errno.ENOTDIR}: + raise WorkflowAuditError( + "output must not use a symbolic-link parent" + ) from error + raise WorkflowAuditError("output path is not writable") from error + os.close(parent_fd) + parent_fd = next_fd + + flags = os.O_WRONLY | nofollow | cloexec + if create_new: + flags |= os.O_CREAT | os.O_EXCL + try: + return os.open(components[-1], flags, 0o600, dir_fd=parent_fd) + except OSError as error: + if error.errno == errno.ELOOP: + raise WorkflowAuditError("output must not be a symbolic link") from error + if create_new and error.errno == errno.EEXIST: + raise WorkflowAuditError( + "output file identity changed before write" + ) from error + raise WorkflowAuditError("output path is not writable") from error + finally: + os.close(parent_fd) + + +def _write_output(path: pathlib.Path, serialized: str) -> None: + """Write evidence to one directly named regular file without path inheritance.""" + + candidate_stat: os.stat_result | None + try: + candidate_stat = path.lstat() + except FileNotFoundError: + candidate_stat = None + except OSError as error: + raise WorkflowAuditError("output path is not writable") from error + + if candidate_stat is not None: + if stat.S_ISLNK(candidate_stat.st_mode): + raise WorkflowAuditError("output must not be a symbolic link") + if not stat.S_ISREG(candidate_stat.st_mode): + raise WorkflowAuditError("output must be a regular file") + + descriptor: int | None = None + try: + descriptor = _open_output_descriptor(path, candidate_stat is None) + opened_stat = os.fstat(descriptor) + if not stat.S_ISREG(opened_stat.st_mode): + raise WorkflowAuditError("output must be a regular file") + if candidate_stat is not None and ( + candidate_stat.st_dev, + candidate_stat.st_ino, + ) != ( + opened_stat.st_dev, + opened_stat.st_ino, + ): + raise WorkflowAuditError("output file identity changed before write") + os.ftruncate(descriptor, 0) + with os.fdopen(descriptor, "w", encoding="utf-8") as destination: + descriptor = None + destination.write(serialized) + except OSError as error: + if error.errno == errno.ELOOP: + raise WorkflowAuditError("output must not be a symbolic link") from error + raise WorkflowAuditError("output path is not writable") from error + finally: + if descriptor is not None: + os.close(descriptor) + + def main(argv: list[str] | None = None) -> int: """Audit an exported registry document and emit canonical JSON evidence.""" @@ -659,8 +757,8 @@ def main(argv: list[str] | None = None) -> int: sys.stdout.write(serialized) return 0 try: - arguments.output.write_text(serialized, encoding="utf-8") - except OSError as error: + _write_output(arguments.output, serialized) + except WorkflowAuditError as error: print(f"workflow registry audit failed: {error}", file=sys.stderr) return 1 return 0 From e3fa6633fb22f87105dfa9dc6bae16b72f56cfc7 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 23 Aug 2026 22:14:31 -0700 Subject: [PATCH 087/111] test(ci): reject hard-linked workflow audit output --- ..._workflow_registry_output_file_contract.py | 31 ++++++++++++++++--- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/tests/test_workflow_registry_output_file_contract.py b/tests/test_workflow_registry_output_file_contract.py index 0cf2f2258..73fb3e3c2 100644 --- a/tests/test_workflow_registry_output_file_contract.py +++ b/tests/test_workflow_registry_output_file_contract.py @@ -43,14 +43,18 @@ def _payload() -> dict: class WorkflowRegistryOutputFileContractTests(unittest.TestCase): - """Prevent an audit output path from inheriting ambient symlink authority.""" + """Prevent an audit output path from inheriting ambient link authority.""" - def test_symbolic_link_output_cannot_overwrite_its_target(self) -> None: - """A caller-controlled symlink must never redirect canonical audit evidence output.""" + def _main(self): + """Load the audit CLI entrypoint without executing its process wrapper.""" namespace = runpy.run_path(str(AUDITOR), run_name="workflow_output_file_contract") - main = namespace["main"] + return namespace["main"] + + def test_symbolic_link_output_cannot_overwrite_its_target(self) -> None: + """A caller-controlled symlink must never redirect canonical audit evidence output.""" + main = self._main() with tempfile.TemporaryDirectory(prefix="originweave-workflow-output-") as directory: root = pathlib.Path(directory) source = root / "registry.json" @@ -66,6 +70,25 @@ def test_symbolic_link_output_cannot_overwrite_its_target(self) -> None: self.assertEqual(main([str(source), "--output", str(output)]), 1) self.assertEqual(target.read_text(encoding="utf-8"), "sentinel\n") + def test_hard_link_output_cannot_overwrite_its_peer(self) -> None: + """A caller-controlled hard link must not grant write authority to a peer path.""" + + main = self._main() + with tempfile.TemporaryDirectory(prefix="originweave-workflow-output-") as directory: + root = pathlib.Path(directory) + source = root / "registry.json" + source.write_text(json.dumps(_payload()), encoding="utf-8") + target = root / "operator-owned.txt" + target.write_text("sentinel\n", encoding="utf-8") + output = root / "audit.json" + try: + output.hardlink_to(target) + except OSError as error: + self.skipTest(f"hard links are unavailable on this platform: {error}") + + self.assertEqual(main([str(source), "--output", str(output)]), 1) + self.assertEqual(target.read_text(encoding="utf-8"), "sentinel\n") + if __name__ == "__main__": unittest.main() From 296dfc3a288e259e5a65091133c93daacaede922 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 23 Aug 2026 22:16:42 -0700 Subject: [PATCH 088/111] fix(ci): reject linked workflow audit outputs --- scripts/ci/audit_workflow_registry.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/scripts/ci/audit_workflow_registry.py b/scripts/ci/audit_workflow_registry.py index 6e36169cc..e43b10eb9 100644 --- a/scripts/ci/audit_workflow_registry.py +++ b/scripts/ci/audit_workflow_registry.py @@ -632,6 +632,7 @@ def _open_output_descriptor(path: pathlib.Path, create_new: bool) -> int: nofollow = getattr(os, "O_NOFOLLOW", 0) directory = getattr(os, "O_DIRECTORY", 0) cloexec = getattr(os, "O_CLOEXEC", 0) + nonblock = getattr(os, "O_NONBLOCK", 0) if not nofollow or not directory or os.open not in os.supports_dir_fd: raise WorkflowAuditError("secure direct-file output is unavailable") @@ -663,7 +664,7 @@ def _open_output_descriptor(path: pathlib.Path, create_new: bool) -> int: os.close(parent_fd) parent_fd = next_fd - flags = os.O_WRONLY | nofollow | cloexec + flags = os.O_WRONLY | nonblock | nofollow | cloexec if create_new: flags |= os.O_CREAT | os.O_EXCL try: @@ -681,7 +682,7 @@ def _open_output_descriptor(path: pathlib.Path, create_new: bool) -> int: def _write_output(path: pathlib.Path, serialized: str) -> None: - """Write evidence to one directly named regular file without path inheritance.""" + """Write evidence to one directly named, singly linked regular file.""" candidate_stat: os.stat_result | None try: @@ -696,6 +697,8 @@ def _write_output(path: pathlib.Path, serialized: str) -> None: raise WorkflowAuditError("output must not be a symbolic link") if not stat.S_ISREG(candidate_stat.st_mode): raise WorkflowAuditError("output must be a regular file") + if candidate_stat.st_nlink != 1: + raise WorkflowAuditError("output must have exactly one hard link") descriptor: int | None = None try: @@ -703,6 +706,8 @@ def _write_output(path: pathlib.Path, serialized: str) -> None: opened_stat = os.fstat(descriptor) if not stat.S_ISREG(opened_stat.st_mode): raise WorkflowAuditError("output must be a regular file") + if opened_stat.st_nlink != 1: + raise WorkflowAuditError("output must have exactly one hard link") if candidate_stat is not None and ( candidate_stat.st_dev, candidate_stat.st_ino, From e34accdec4d371a588296901b300a382e1685842 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 23 Aug 2026 23:12:45 -0700 Subject: [PATCH 089/111] test(ci): reproduce raced output hard-link alias --- ..._workflow_registry_output_file_contract.py | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/tests/test_workflow_registry_output_file_contract.py b/tests/test_workflow_registry_output_file_contract.py index 73fb3e3c2..06bf9dfcf 100644 --- a/tests/test_workflow_registry_output_file_contract.py +++ b/tests/test_workflow_registry_output_file_contract.py @@ -3,10 +3,12 @@ from __future__ import annotations import json +import os import pathlib import runpy import tempfile import unittest +import unittest.mock ROOT = pathlib.Path(__file__).resolve().parents[1] AUDITOR = ROOT / "scripts" / "ci" / "audit_workflow_registry.py" @@ -89,6 +91,39 @@ def test_hard_link_output_cannot_overwrite_its_peer(self) -> None: self.assertEqual(main([str(source), "--output", str(output)]), 1) self.assertEqual(target.read_text(encoding="utf-8"), "sentinel\n") + def test_hard_link_race_after_descriptor_check_cannot_alias_output(self) -> None: + """A hard link added after descriptor inspection must not gain write authority.""" + + main = self._main() + with tempfile.TemporaryDirectory(prefix="originweave-workflow-output-") as directory: + root = pathlib.Path(directory) + source = root / "registry.json" + source.write_text(json.dumps(_payload()), encoding="utf-8") + output = root / "audit.json" + output.write_text("sentinel\n", encoding="utf-8") + peer = root / "raced-peer.txt" + real_fstat = os.fstat + fstat_calls = 0 + + def fstat_with_hard_link_race(descriptor: int) -> os.stat_result: + nonlocal fstat_calls + result = real_fstat(descriptor) + fstat_calls += 1 + if fstat_calls == 2: + try: + peer.hardlink_to(output) + except OSError as error: + self.skipTest( + f"hard links are unavailable on this platform: {error}" + ) + return result + + with unittest.mock.patch("os.fstat", side_effect=fstat_with_hard_link_race): + self.assertEqual(main([str(source), "--output", str(output)]), 1) + + self.assertEqual(output.read_text(encoding="utf-8"), "sentinel\n") + self.assertEqual(peer.read_text(encoding="utf-8"), "sentinel\n") + if __name__ == "__main__": unittest.main() From 1756ce13165a688d8dc58e37a805f80ef0047c2b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 23 Aug 2026 23:17:46 -0700 Subject: [PATCH 090/111] fix(ci): fail closed on preexisting audit output --- scripts/ci/audit_workflow_registry.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/scripts/ci/audit_workflow_registry.py b/scripts/ci/audit_workflow_registry.py index e43b10eb9..65a4093dd 100644 --- a/scripts/ci/audit_workflow_registry.py +++ b/scripts/ci/audit_workflow_registry.py @@ -682,7 +682,7 @@ def _open_output_descriptor(path: pathlib.Path, create_new: bool) -> int: def _write_output(path: pathlib.Path, serialized: str) -> None: - """Write evidence to one directly named, singly linked regular file.""" + """Create one directly named, singly linked regular evidence output file.""" candidate_stat: os.stat_result | None try: @@ -716,7 +716,8 @@ def _write_output(path: pathlib.Path, serialized: str) -> None: opened_stat.st_ino, ): raise WorkflowAuditError("output file identity changed before write") - os.ftruncate(descriptor, 0) + if candidate_stat is not None: + raise WorkflowAuditError("output file already exists") with os.fdopen(descriptor, "w", encoding="utf-8") as destination: descriptor = None destination.write(serialized) From f2dd4b23aed5af3867912f4f7cb213084ce0c2b9 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 25 Aug 2026 05:40:05 -0700 Subject: [PATCH 091/111] docs(changelog): preserve workflow audit release notes --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 116c02fe6..3db3fc404 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,9 @@ All notable changes to OriginWeave are documented in this file. The format follo - Bound explicit extension-to-Agent grants to exclusive trusted-time expiry in addition to extension identity, session, browsing context, and canonical origin, so a same-origin grant cannot be reused at or after the deadline. - Bound explicit extension-to-Agent grants to the exact canonical origin in addition to extension identity, session, and browsing context, so a same-session navigation or port change cannot reuse the grant. - Rust workspace for independently reusable core, policy, destination, network, TLS, resource, and evidence modules. +- Read-only workflow-registry audit evidence that binds complete paginated GitHub Actions inventory to exact protected-main state, rejects Git's all-zero null object identifier anywhere a real protected-main or active-PR commit is required, fails closed on ambiguous scalar types including noncanonical negative-zero integers, workflow identities, paths, timestamps, and ownership, and exposes typed bounded-recollection guidance only for reviewed transient HTTP results (`408`, `429`, `500`, `502`, `503`, and `504`), preserves bounded `Retry-After` seconds when collected, treats `403` as retryable only when that bounded hint is retained, and keeps `404`, `501`, `505`, and every other non-reviewed failure non-retryable and non-passing. +- Exact active-PR workflow-owner evidence that binds every deferred registry path to one positive PR number and exact contributor-head SHA, rejects path-only or mismatched exemptions, and retains the independently refetchable owner identity on read-only audit output. +- Disabled active-PR-owned workflow identities remain bound to their exact PR/head owner but are surfaced explicitly as operational drift instead of being mislabeled as active PR ownership. - Canonical HTTPS and loopback-origin boundary with case-normalized schemes and hosts, default-port normalization, IPv4/IPv6 handling, browser-special numeric-host rejection, and explicit malformed-input errors. - Typed browser actions, capabilities, risk classes, execution modes, robots decisions, secret-delivery contracts, immutable canonical action-intent digests, and intent-bound approval scopes. - Active PR #168 adds deterministic MCP `2026-07-28` stateless tool-routing foundations with bounded names, a single reviewed tool-to-action registry shared by routing and discovery metadata, and fail-closed policy binding that grants no ambient authority. This is active-PR evidence only; the complete MCP adapter, transport serialization, discovery response handling, OAuth, browser I/O, and persistence remain planned until separately integrated on protected `main`. From 320f1da651450909e516318a8b0227c4f7299e35 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 25 Aug 2026 06:44:34 -0700 Subject: [PATCH 092/111] test(ci): remove partial audit output after write failure --- ..._workflow_registry_output_file_contract.py | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/tests/test_workflow_registry_output_file_contract.py b/tests/test_workflow_registry_output_file_contract.py index 06bf9dfcf..56f7f9137 100644 --- a/tests/test_workflow_registry_output_file_contract.py +++ b/tests/test_workflow_registry_output_file_contract.py @@ -124,6 +124,42 @@ def fstat_with_hard_link_race(descriptor: int) -> os.stat_result: self.assertEqual(output.read_text(encoding="utf-8"), "sentinel\n") self.assertEqual(peer.read_text(encoding="utf-8"), "sentinel\n") + def test_failed_new_output_write_is_removed_for_safe_retry(self) -> None: + """A partial create-once evidence file must not survive a failed write.""" + + main = self._main() + + class FailingDestination: + def __init__(self, descriptor: int) -> None: + self.descriptor = descriptor + + def __enter__(self): + return self + + def __exit__(self, _type, _value, _traceback) -> bool: + os.close(self.descriptor) + return False + + def write(self, _serialized: str) -> None: + os.write(self.descriptor, b"{") + raise OSError("simulated output write failure") + + with tempfile.TemporaryDirectory(prefix="originweave-workflow-output-") as directory: + root = pathlib.Path(directory) + source = root / "registry.json" + source.write_text(json.dumps(_payload()), encoding="utf-8") + output = root / "audit.json" + + with unittest.mock.patch( + "os.fdopen", + side_effect=lambda descriptor, *_args, **_kwargs: FailingDestination( + descriptor + ), + ): + self.assertEqual(main([str(source), "--output", str(output)]), 1) + + self.assertFalse(output.exists()) + if __name__ == "__main__": unittest.main() From ef7c7021be5b914b7e075a4fff97df43c8716a5c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 25 Aug 2026 06:55:38 -0700 Subject: [PATCH 093/111] fix(ci): stage audit output before atomic publish --- scripts/ci/audit_workflow_registry.py | 201 +++++++++++++++++++++++--- 1 file changed, 178 insertions(+), 23 deletions(-) diff --git a/scripts/ci/audit_workflow_registry.py b/scripts/ci/audit_workflow_registry.py index 65a4093dd..0cf72dc12 100644 --- a/scripts/ci/audit_workflow_registry.py +++ b/scripts/ci/audit_workflow_registry.py @@ -16,6 +16,7 @@ import os import pathlib import re +import secrets import stat import sys from typing import Any @@ -25,6 +26,7 @@ _MAX_JSON_INTEGER_DIGITS = 20 _MAX_WORKFLOW_ID = (1 << 64) - 1 _MAX_RETRY_AFTER_SECONDS = 3600 +_MAX_OUTPUT_STAGING_ATTEMPTS = 4 _SHA_PATTERN = re.compile(r"^[0-9a-f]{40}$") _TIMESTAMP_PATTERN = re.compile( r"^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$" @@ -626,14 +628,20 @@ def _read_payload(path: pathlib.Path) -> dict[str, Any]: return _require_mapping(parsed, "payload") -def _open_output_descriptor(path: pathlib.Path, create_new: bool) -> int: - """Open one output leaf without following symbolic-link path components.""" +def _open_output_parent(path: pathlib.Path) -> tuple[int, str]: + """Open the direct parent of one output leaf through a no-follow component walk.""" nofollow = getattr(os, "O_NOFOLLOW", 0) directory = getattr(os, "O_DIRECTORY", 0) cloexec = getattr(os, "O_CLOEXEC", 0) - nonblock = getattr(os, "O_NONBLOCK", 0) - if not nofollow or not directory or os.open not in os.supports_dir_fd: + required_dir_fd_operations = {os.open, os.stat, os.link, os.unlink} + if ( + not nofollow + or not directory + or not required_dir_fd_operations.issubset(os.supports_dir_fd) + or os.stat not in os.supports_follow_symlinks + or os.link not in os.supports_follow_symlinks + ): raise WorkflowAuditError("secure direct-file output is unavailable") components = list(path.parts) @@ -663,12 +671,25 @@ def _open_output_descriptor(path: pathlib.Path, create_new: bool) -> int: raise WorkflowAuditError("output path is not writable") from error os.close(parent_fd) parent_fd = next_fd + return parent_fd, components[-1] + except Exception: + os.close(parent_fd) + raise + +def _open_output_descriptor(path: pathlib.Path, create_new: bool) -> int: + """Open one output leaf without following symbolic-link path components.""" + + nofollow = getattr(os, "O_NOFOLLOW", 0) + cloexec = getattr(os, "O_CLOEXEC", 0) + nonblock = getattr(os, "O_NONBLOCK", 0) + parent_fd, leaf_name = _open_output_parent(path) + try: flags = os.O_WRONLY | nonblock | nofollow | cloexec if create_new: flags |= os.O_CREAT | os.O_EXCL try: - return os.open(components[-1], flags, 0o600, dir_fd=parent_fd) + return os.open(leaf_name, flags, 0o600, dir_fd=parent_fd) except OSError as error: if error.errno == errno.ELOOP: raise WorkflowAuditError("output must not be a symbolic link") from error @@ -681,8 +702,59 @@ def _open_output_descriptor(path: pathlib.Path, create_new: bool) -> int: os.close(parent_fd) +def _create_output_staging_descriptor(parent_fd: int) -> tuple[int, str]: + """Create one bounded random staging inode in the already-authorized parent.""" + + flags = ( + os.O_WRONLY + | os.O_CREAT + | os.O_EXCL + | getattr(os, "O_NOFOLLOW", 0) + | getattr(os, "O_CLOEXEC", 0) + ) + for _ in range(_MAX_OUTPUT_STAGING_ATTEMPTS): + staging_name = f".originweave-audit-{secrets.token_hex(16)}.tmp" + try: + descriptor = os.open(staging_name, flags, 0o600, dir_fd=parent_fd) + return descriptor, staging_name + except FileExistsError: + continue + except OSError as error: + raise WorkflowAuditError("output path is not writable") from error + raise WorkflowAuditError("secure output staging name allocation was exhausted") + + +def _stat_output_leaf(parent_fd: int, leaf_name: str) -> os.stat_result | None: + """Return a no-follow output-leaf stat or None when the leaf is absent.""" + + try: + return os.stat(leaf_name, dir_fd=parent_fd, follow_symlinks=False) + except FileNotFoundError: + return None + except OSError as error: + raise WorkflowAuditError("output path is not writable") from error + + +def _unlink_matching_staging( + parent_fd: int, + staging_name: str, + expected_identity: tuple[int, int], +) -> None: + """Remove only the still-matching private staging inode after failure.""" + + staging_stat = _stat_output_leaf(parent_fd, staging_name) + if staging_stat is None: + return + if (staging_stat.st_dev, staging_stat.st_ino) != expected_identity: + raise WorkflowAuditError("output staging identity changed during cleanup") + try: + os.unlink(staging_name, dir_fd=parent_fd) + except OSError as error: + raise WorkflowAuditError("output staging cleanup failed") from error + + def _write_output(path: pathlib.Path, serialized: str) -> None: - """Create one directly named, singly linked regular evidence output file.""" + """Publish complete create-once evidence without exposing a partial canonical leaf.""" candidate_stat: os.stat_result | None try: @@ -700,34 +772,117 @@ def _write_output(path: pathlib.Path, serialized: str) -> None: if candidate_stat.st_nlink != 1: raise WorkflowAuditError("output must have exactly one hard link") + descriptor: int | None = None + try: + descriptor = _open_output_descriptor(path, False) + opened_stat = os.fstat(descriptor) + if not stat.S_ISREG(opened_stat.st_mode): + raise WorkflowAuditError("output must be a regular file") + if opened_stat.st_nlink != 1: + raise WorkflowAuditError("output must have exactly one hard link") + if (candidate_stat.st_dev, candidate_stat.st_ino) != ( + opened_stat.st_dev, + opened_stat.st_ino, + ): + raise WorkflowAuditError("output file identity changed before write") + raise WorkflowAuditError("output file already exists") + finally: + if descriptor is not None: + os.close(descriptor) + + parent_fd: int | None = None descriptor: int | None = None + staging_name: str | None = None + staging_identity: tuple[int, int] | None = None try: - descriptor = _open_output_descriptor(path, candidate_stat is None) + parent_fd, leaf_name = _open_output_parent(path) + final_stat = _stat_output_leaf(parent_fd, leaf_name) + if final_stat is not None: + if stat.S_ISLNK(final_stat.st_mode): + raise WorkflowAuditError("output must not be a symbolic link") + if not stat.S_ISREG(final_stat.st_mode): + raise WorkflowAuditError("output must be a regular file") + if final_stat.st_nlink != 1: + raise WorkflowAuditError("output must have exactly one hard link") + raise WorkflowAuditError("output file identity changed before write") + + descriptor, staging_name = _create_output_staging_descriptor(parent_fd) opened_stat = os.fstat(descriptor) if not stat.S_ISREG(opened_stat.st_mode): - raise WorkflowAuditError("output must be a regular file") + raise WorkflowAuditError("output staging must be a regular file") if opened_stat.st_nlink != 1: - raise WorkflowAuditError("output must have exactly one hard link") - if candidate_stat is not None and ( - candidate_stat.st_dev, - candidate_stat.st_ino, - ) != ( - opened_stat.st_dev, - opened_stat.st_ino, + raise WorkflowAuditError("output staging must have exactly one hard link") + staging_identity = (opened_stat.st_dev, opened_stat.st_ino) + + try: + with os.fdopen(descriptor, "w", encoding="utf-8") as destination: + descriptor = None + destination.write(serialized) + destination.flush() + os.fsync(destination.fileno()) + except OSError as error: + _unlink_matching_staging(parent_fd, staging_name, staging_identity) + staging_name = None + raise WorkflowAuditError("output path is not writable") from error + + staged_stat = _stat_output_leaf(parent_fd, staging_name) + if staged_stat is None or ( + staged_stat.st_dev, + staged_stat.st_ino, + ) != staging_identity: + raise WorkflowAuditError("output staging identity changed before publish") + if not stat.S_ISREG(staged_stat.st_mode) or staged_stat.st_nlink != 1: + raise WorkflowAuditError("output staging authority changed before publish") + + try: + os.link( + staging_name, + leaf_name, + src_dir_fd=parent_fd, + dst_dir_fd=parent_fd, + follow_symlinks=False, + ) + except FileExistsError as error: + raise WorkflowAuditError("output file identity changed before publish") from error + except OSError as error: + raise WorkflowAuditError("output path is not writable") from error + + published_stat = _stat_output_leaf(parent_fd, leaf_name) + staged_stat = _stat_output_leaf(parent_fd, staging_name) + if ( + published_stat is None + or staged_stat is None + or (published_stat.st_dev, published_stat.st_ino) != staging_identity + or (staged_stat.st_dev, staged_stat.st_ino) != staging_identity + or not stat.S_ISREG(published_stat.st_mode) + or published_stat.st_nlink != 2 + or staged_stat.st_nlink != 2 ): - raise WorkflowAuditError("output file identity changed before write") - if candidate_stat is not None: - raise WorkflowAuditError("output file already exists") - with os.fdopen(descriptor, "w", encoding="utf-8") as destination: - descriptor = None - destination.write(serialized) + raise WorkflowAuditError("published output identity is ambiguous") + + os.unlink(staging_name, dir_fd=parent_fd) + staging_name = None + final_stat = _stat_output_leaf(parent_fd, leaf_name) + if ( + final_stat is None + or (final_stat.st_dev, final_stat.st_ino) != staging_identity + or not stat.S_ISREG(final_stat.st_mode) + or final_stat.st_nlink != 1 + ): + raise WorkflowAuditError("published output identity is ambiguous") except OSError as error: - if error.errno == errno.ELOOP: - raise WorkflowAuditError("output must not be a symbolic link") from error raise WorkflowAuditError("output path is not writable") from error finally: if descriptor is not None: os.close(descriptor) + if ( + parent_fd is not None + and staging_name is not None + and staging_identity is not None + ): + _unlink_matching_staging(parent_fd, staging_name, staging_identity) + if parent_fd is not None: + os.close(parent_fd) def main(argv: list[str] | None = None) -> int: From f5f0538e1c662d72d650b893605d2312f69452a5 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 25 Aug 2026 08:09:22 -0700 Subject: [PATCH 094/111] test(ci): cover post-publish cleanup failures --- ..._workflow_registry_output_file_contract.py | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/tests/test_workflow_registry_output_file_contract.py b/tests/test_workflow_registry_output_file_contract.py index 56f7f9137..2cf945c6a 100644 --- a/tests/test_workflow_registry_output_file_contract.py +++ b/tests/test_workflow_registry_output_file_contract.py @@ -2,6 +2,7 @@ from __future__ import annotations +import errno import json import os import pathlib @@ -160,6 +161,78 @@ def write(self, _serialized: str) -> None: self.assertFalse(output.exists()) + def test_interrupted_staging_cleanup_is_retried_without_false_failure(self) -> None: + """One interrupted private-link cleanup must not turn a completed publish into failure.""" + + main = self._main() + real_unlink = os.unlink + interrupted = False + + with tempfile.TemporaryDirectory(prefix="originweave-workflow-output-") as directory: + root = pathlib.Path(directory) + source = root / "registry.json" + source.write_text(json.dumps(_payload()), encoding="utf-8") + output = root / "audit.json" + + def interrupt_first_published_staging_unlink( + path: str | bytes, *args, **kwargs + ) -> None: + nonlocal interrupted + if ( + not interrupted + and output.exists() + and isinstance(path, str) + and path.startswith(".originweave-audit-") + ): + interrupted = True + raise InterruptedError(errno.EINTR, "simulated interrupted cleanup") + real_unlink(path, *args, **kwargs) + + with unittest.mock.patch( + "os.unlink", side_effect=interrupt_first_published_staging_unlink + ): + self.assertEqual(main([str(source), "--output", str(output)]), 0) + + self.assertTrue(interrupted) + self.assertTrue(output.is_file()) + self.assertEqual(output.stat().st_nlink, 1) + + def test_failed_staging_cleanup_rolls_back_published_output_for_safe_retry(self) -> None: + """A reported cleanup failure must not leave a canonical output that blocks retry.""" + + main = self._main() + real_unlink = os.unlink + failed_once = False + + with tempfile.TemporaryDirectory(prefix="originweave-workflow-output-") as directory: + root = pathlib.Path(directory) + source = root / "registry.json" + source.write_text(json.dumps(_payload()), encoding="utf-8") + output = root / "audit.json" + + def fail_first_published_staging_unlink(path: str | bytes, *args, **kwargs) -> None: + nonlocal failed_once + if ( + not failed_once + and output.exists() + and isinstance(path, str) + and path.startswith(".originweave-audit-") + ): + failed_once = True + raise OSError(errno.EIO, "simulated staging cleanup failure") + real_unlink(path, *args, **kwargs) + + with unittest.mock.patch( + "os.unlink", side_effect=fail_first_published_staging_unlink + ): + self.assertEqual(main([str(source), "--output", str(output)]), 1) + + self.assertTrue(failed_once) + self.assertFalse(output.exists()) + self.assertEqual(main([str(source), "--output", str(output)]), 0) + self.assertTrue(output.is_file()) + self.assertEqual(output.stat().st_nlink, 1) + if __name__ == "__main__": unittest.main() From 459c13e0f4297518b813b43af071c9b0ba22724e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 25 Aug 2026 08:24:17 -0700 Subject: [PATCH 095/111] fix(ci): recover audit publish cleanup --- scripts/ci/audit_workflow_registry.py | 43 +++++++++++++++++++++++---- 1 file changed, 38 insertions(+), 5 deletions(-) diff --git a/scripts/ci/audit_workflow_registry.py b/scripts/ci/audit_workflow_registry.py index 0cf72dc12..fbcbfc600 100644 --- a/scripts/ci/audit_workflow_registry.py +++ b/scripts/ci/audit_workflow_registry.py @@ -735,6 +735,22 @@ def _stat_output_leaf(parent_fd: int, leaf_name: str) -> os.stat_result | None: raise WorkflowAuditError("output path is not writable") from error +def _unlink_with_interrupted_retry(parent_fd: int, leaf_name: str) -> OSError | None: + """Unlink one descriptor-relative leaf with one bounded EINTR retry.""" + + try: + os.unlink(leaf_name, dir_fd=parent_fd) + return None + except OSError as error: + if error.errno != errno.EINTR: + return error + try: + os.unlink(leaf_name, dir_fd=parent_fd) + return None + except OSError as error: + return error + + def _unlink_matching_staging( parent_fd: int, staging_name: str, @@ -747,10 +763,9 @@ def _unlink_matching_staging( return if (staging_stat.st_dev, staging_stat.st_ino) != expected_identity: raise WorkflowAuditError("output staging identity changed during cleanup") - try: - os.unlink(staging_name, dir_fd=parent_fd) - except OSError as error: - raise WorkflowAuditError("output staging cleanup failed") from error + cleanup_error = _unlink_with_interrupted_retry(parent_fd, staging_name) + if cleanup_error is not None: + raise WorkflowAuditError("output staging cleanup failed") from cleanup_error def _write_output(path: pathlib.Path, serialized: str) -> None: @@ -860,7 +875,25 @@ def _write_output(path: pathlib.Path, serialized: str) -> None: ): raise WorkflowAuditError("published output identity is ambiguous") - os.unlink(staging_name, dir_fd=parent_fd) + cleanup_error = _unlink_with_interrupted_retry(parent_fd, staging_name) + if cleanup_error is not None: + rollback_stat = _stat_output_leaf(parent_fd, leaf_name) + if ( + rollback_stat is None + or (rollback_stat.st_dev, rollback_stat.st_ino) != staging_identity + or not stat.S_ISREG(rollback_stat.st_mode) + or rollback_stat.st_nlink != 2 + ): + raise WorkflowAuditError( + "published output identity changed during cleanup rollback" + ) from cleanup_error + rollback_error = _unlink_with_interrupted_retry(parent_fd, leaf_name) + if rollback_error is not None: + raise WorkflowAuditError( + "published output rollback failed" + ) from rollback_error + raise WorkflowAuditError("output staging cleanup failed") from cleanup_error + staging_name = None final_stat = _stat_output_leaf(parent_fd, leaf_name) if ( From 687086a4ea0b080cda3338777faa0755a0af499d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 25 Aug 2026 08:49:16 -0700 Subject: [PATCH 096/111] fix(ci): bind output capability checks to stdlib primitives --- scripts/ci/audit_workflow_registry.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/scripts/ci/audit_workflow_registry.py b/scripts/ci/audit_workflow_registry.py index fbcbfc600..61cf9bcee 100644 --- a/scripts/ci/audit_workflow_registry.py +++ b/scripts/ci/audit_workflow_registry.py @@ -21,6 +21,10 @@ import sys from typing import Any +_OS_OPEN = os.open +_OS_STAT = os.stat +_OS_LINK = os.link +_OS_UNLINK = os.unlink _SCHEMA_VERSION = 1 _MAX_INPUT_BYTES = 4 * 1024 * 1024 _MAX_JSON_INTEGER_DIGITS = 20 @@ -634,13 +638,13 @@ def _open_output_parent(path: pathlib.Path) -> tuple[int, str]: nofollow = getattr(os, "O_NOFOLLOW", 0) directory = getattr(os, "O_DIRECTORY", 0) cloexec = getattr(os, "O_CLOEXEC", 0) - required_dir_fd_operations = {os.open, os.stat, os.link, os.unlink} + required_dir_fd_operations = {_OS_OPEN, _OS_STAT, _OS_LINK, _OS_UNLINK} if ( not nofollow or not directory or not required_dir_fd_operations.issubset(os.supports_dir_fd) - or os.stat not in os.supports_follow_symlinks - or os.link not in os.supports_follow_symlinks + or _OS_STAT not in os.supports_follow_symlinks + or _OS_LINK not in os.supports_follow_symlinks ): raise WorkflowAuditError("secure direct-file output is unavailable") From 5c059a1d4fb0f70e913e7f3cad7bf7ee87583ee5 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 25 Aug 2026 10:10:01 -0700 Subject: [PATCH 097/111] test(ci): cover existing-output fstat failure --- ..._workflow_registry_output_file_contract.py | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/tests/test_workflow_registry_output_file_contract.py b/tests/test_workflow_registry_output_file_contract.py index 2cf945c6a..bf18ffa72 100644 --- a/tests/test_workflow_registry_output_file_contract.py +++ b/tests/test_workflow_registry_output_file_contract.py @@ -125,6 +125,31 @@ def fstat_with_hard_link_race(descriptor: int) -> os.stat_result: self.assertEqual(output.read_text(encoding="utf-8"), "sentinel\n") self.assertEqual(peer.read_text(encoding="utf-8"), "sentinel\n") + def test_existing_output_fstat_failure_is_reported_without_exception(self) -> None: + """A descriptor-stat failure on an existing output must fail closed at the CLI.""" + + main = self._main() + with tempfile.TemporaryDirectory(prefix="originweave-workflow-output-") as directory: + root = pathlib.Path(directory) + source = root / "registry.json" + source.write_text(json.dumps(_payload()), encoding="utf-8") + output = root / "audit.json" + output.write_text("sentinel\n", encoding="utf-8") + real_fstat = os.fstat + fstat_calls = 0 + + def fail_existing_output_fstat(descriptor: int) -> os.stat_result: + nonlocal fstat_calls + fstat_calls += 1 + if fstat_calls == 2: + raise OSError(errno.EIO, "simulated existing-output fstat failure") + return real_fstat(descriptor) + + with unittest.mock.patch("os.fstat", side_effect=fail_existing_output_fstat): + self.assertEqual(main([str(source), "--output", str(output)]), 1) + + self.assertEqual(output.read_text(encoding="utf-8"), "sentinel\n") + def test_failed_new_output_write_is_removed_for_safe_retry(self) -> None: """A partial create-once evidence file must not survive a failed write.""" From 581b551bff76ba6fd4bf0960fd10fb54e582f4d9 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 25 Aug 2026 10:17:19 -0700 Subject: [PATCH 098/111] fix(ci): normalize existing-output stat failure --- scripts/ci/audit_workflow_registry.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/scripts/ci/audit_workflow_registry.py b/scripts/ci/audit_workflow_registry.py index 61cf9bcee..32ac08138 100644 --- a/scripts/ci/audit_workflow_registry.py +++ b/scripts/ci/audit_workflow_registry.py @@ -794,7 +794,10 @@ def _write_output(path: pathlib.Path, serialized: str) -> None: descriptor: int | None = None try: descriptor = _open_output_descriptor(path, False) - opened_stat = os.fstat(descriptor) + try: + opened_stat = os.fstat(descriptor) + except OSError as error: + raise WorkflowAuditError("output could not be inspected safely") from error if not stat.S_ISREG(opened_stat.st_mode): raise WorkflowAuditError("output must be a regular file") if opened_stat.st_nlink != 1: @@ -963,4 +966,4 @@ def main(argv: list[str] | None = None) -> int: if __name__ == "__main__": - raise SystemExit(main()) + raise SystemExit(main()) \ No newline at end of file From cd764c17a95d6ad9da89a84d7c175e8084b0406e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 25 Aug 2026 10:30:54 -0700 Subject: [PATCH 099/111] test(ci): reproduce staging fstat cleanup leak --- ..._workflow_registry_output_file_contract.py | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/tests/test_workflow_registry_output_file_contract.py b/tests/test_workflow_registry_output_file_contract.py index bf18ffa72..8a3d32eb2 100644 --- a/tests/test_workflow_registry_output_file_contract.py +++ b/tests/test_workflow_registry_output_file_contract.py @@ -150,6 +150,31 @@ def fail_existing_output_fstat(descriptor: int) -> os.stat_result: self.assertEqual(output.read_text(encoding="utf-8"), "sentinel\n") + def test_staging_fstat_failure_removes_private_temp_file(self) -> None: + """A failed first staging inspection must not orphan its private temp inode.""" + + main = self._main() + with tempfile.TemporaryDirectory(prefix="originweave-workflow-output-") as directory: + root = pathlib.Path(directory) + source = root / "registry.json" + source.write_text(json.dumps(_payload()), encoding="utf-8") + output = root / "audit.json" + real_fstat = os.fstat + fstat_calls = 0 + + def fail_staging_fstat(descriptor: int) -> os.stat_result: + nonlocal fstat_calls + fstat_calls += 1 + if fstat_calls == 2: + raise OSError(errno.EIO, "simulated staging fstat failure") + return real_fstat(descriptor) + + with unittest.mock.patch("os.fstat", side_effect=fail_staging_fstat): + self.assertEqual(main([str(source), "--output", str(output)]), 1) + + self.assertFalse(output.exists()) + self.assertEqual(list(root.glob(".originweave-audit-*.tmp")), []) + def test_failed_new_output_write_is_removed_for_safe_retry(self) -> None: """A partial create-once evidence file must not survive a failed write.""" From cc98483aaec1a9b8e718334b18a632d2fd4a4457 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 25 Aug 2026 10:39:32 -0700 Subject: [PATCH 100/111] fix(ci): clean staging after inspection failure --- scripts/ci/audit_workflow_registry.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/scripts/ci/audit_workflow_registry.py b/scripts/ci/audit_workflow_registry.py index 32ac08138..64c4366a2 100644 --- a/scripts/ci/audit_workflow_registry.py +++ b/scripts/ci/audit_workflow_registry.py @@ -829,7 +829,18 @@ def _write_output(path: pathlib.Path, serialized: str) -> None: raise WorkflowAuditError("output file identity changed before write") descriptor, staging_name = _create_output_staging_descriptor(parent_fd) - opened_stat = os.fstat(descriptor) + try: + opened_stat = os.fstat(descriptor) + except OSError as error: + fallback_stat: os.stat_result | None = None + if os.stat in os.supports_fd: + try: + fallback_stat = os.stat(descriptor) + except OSError: + fallback_stat = None + if fallback_stat is not None: + staging_identity = (fallback_stat.st_dev, fallback_stat.st_ino) + raise WorkflowAuditError("output staging could not be inspected safely") from error if not stat.S_ISREG(opened_stat.st_mode): raise WorkflowAuditError("output staging must be a regular file") if opened_stat.st_nlink != 1: From ddaa47c0b06bf7d6759076e1498cd133b34e5640 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 25 Aug 2026 10:49:09 -0700 Subject: [PATCH 101/111] test(ci): reproduce cleanup error masking fd leak --- ..._workflow_registry_output_file_contract.py | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/tests/test_workflow_registry_output_file_contract.py b/tests/test_workflow_registry_output_file_contract.py index 8a3d32eb2..28ce54442 100644 --- a/tests/test_workflow_registry_output_file_contract.py +++ b/tests/test_workflow_registry_output_file_contract.py @@ -211,6 +211,70 @@ def write(self, _serialized: str) -> None: self.assertFalse(output.exists()) + def test_cleanup_failure_preserves_primary_error_and_closes_parent_descriptor(self) -> None: + """Cleanup failure must not mask the primary write error or leak the parent fd.""" + + namespace = runpy.run_path( + str(AUDITOR), run_name="workflow_output_cleanup_failure_contract" + ) + write_output = namespace["_write_output"] + workflow_audit_error = namespace["WorkflowAuditError"] + function_globals = write_output.__globals__ + original_open_parent = function_globals["_open_output_parent"] + parent_descriptor: int | None = None + + class FailingDestination: + def __init__(self, descriptor: int) -> None: + self.descriptor = descriptor + + def __enter__(self): + return self + + def __exit__(self, _type, _value, _traceback) -> bool: + os.close(self.descriptor) + return False + + def write(self, _serialized: str) -> None: + os.write(self.descriptor, b"{") + raise OSError("simulated primary write failure") + + def capture_output_parent(path: pathlib.Path) -> tuple[int, str]: + nonlocal parent_descriptor + parent_descriptor, leaf_name = original_open_parent(path) + return parent_descriptor, leaf_name + + def fail_identity_cleanup( + _parent_fd: int, _staging_name: str, _expected_identity: tuple[int, int] + ) -> None: + raise workflow_audit_error("simulated cleanup identity change") + + with tempfile.TemporaryDirectory(prefix="originweave-workflow-output-") as directory: + output = pathlib.Path(directory) / "audit.json" + with ( + unittest.mock.patch.dict( + function_globals, + { + "_open_output_parent": capture_output_parent, + "_unlink_matching_staging": fail_identity_cleanup, + }, + ), + unittest.mock.patch( + "os.fdopen", + side_effect=lambda descriptor, *_args, **_kwargs: FailingDestination( + descriptor + ), + ), + self.assertRaises(workflow_audit_error) as raised, + ): + write_output(output, "{}\n") + + self.assertEqual(str(raised.exception), "output path is not writable") + self.assertIsNotNone(parent_descriptor) + assert parent_descriptor is not None + with self.assertRaises(OSError) as closed: + os.fstat(parent_descriptor) + self.assertEqual(closed.exception.errno, errno.EBADF) + def test_interrupted_staging_cleanup_is_retried_without_false_failure(self) -> None: """One interrupted private-link cleanup must not turn a completed publish into failure.""" From 6dd2023ca2f9fd5003564d1603565c05f4d9f111 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 25 Aug 2026 11:36:47 -0700 Subject: [PATCH 102/111] fix(ci): preserve primary output failure during cleanup --- scripts/ci/audit_workflow_registry.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/scripts/ci/audit_workflow_registry.py b/scripts/ci/audit_workflow_registry.py index 64c4366a2..2e07f510e 100644 --- a/scripts/ci/audit_workflow_registry.py +++ b/scripts/ci/audit_workflow_registry.py @@ -854,9 +854,15 @@ def _write_output(path: pathlib.Path, serialized: str) -> None: destination.flush() os.fsync(destination.fileno()) except OSError as error: - _unlink_matching_staging(parent_fd, staging_name, staging_identity) + primary_error = WorkflowAuditError("output path is not writable") + try: + _unlink_matching_staging(parent_fd, staging_name, staging_identity) + except WorkflowAuditError as cleanup_error: + primary_error.add_note( + f"output staging cleanup also failed: {cleanup_error}" + ) staging_name = None - raise WorkflowAuditError("output path is not writable") from error + raise primary_error from error staged_stat = _stat_output_leaf(parent_fd, staging_name) if staged_stat is None or ( @@ -926,14 +932,12 @@ def _write_output(path: pathlib.Path, serialized: str) -> None: finally: if descriptor is not None: os.close(descriptor) - if ( - parent_fd is not None - and staging_name is not None - and staging_identity is not None - ): - _unlink_matching_staging(parent_fd, staging_name, staging_identity) if parent_fd is not None: - os.close(parent_fd) + try: + if staging_name is not None and staging_identity is not None: + _unlink_matching_staging(parent_fd, staging_name, staging_identity) + finally: + os.close(parent_fd) def main(argv: list[str] | None = None) -> int: From 30cc458bb5f47004000db45b5a2e15a71308b37d Mon Sep 17 00:00:00 2001 From: seonghobae Date: Wed, 26 Aug 2026 12:50:44 +0900 Subject: [PATCH 103/111] fix(ci): reject homoglyph workflow path confusion in registry audit Restrict audited workflow paths to a canonical ASCII alphabet so printable confusables (U+FF0F fullwidth solidus, U+2044, U+2215, homoglyph letters) fail closed during validation instead of surviving as evidence that cannot match exact protected/active-PR ownership sets. Regression tests cover the Strix vuln-0001 finding on PR #124. --- CHANGELOG.md | 1 + scripts/ci/audit_workflow_registry.py | 13 +- ...rkflow_registry_homoglyph_path_contract.py | 119 ++++++++++++++++++ 3 files changed, 131 insertions(+), 2 deletions(-) create mode 100644 tests/test_workflow_registry_homoglyph_path_contract.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 3db3fc404..ba9b81b57 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ All notable changes to OriginWeave are documented in this file. The format follo ## [Unreleased] ### Added +- Hardened the read-only workflow-registry audit against printable Unicode confusables: every audited workflow path is now restricted to a canonical ASCII alphabet, so fullwidth solidus, fraction-slash, homoglyph letter forms, and similar look-alike separators fail closed instead of passing validation while failing exact-match ownership classification. - Refreshed the product and technical gap baseline with the 2026-08-24 live inventory: 158 open pull requests (44 ready, 114 draft), refreshed exact base/head evidence for the #208–#222 release, enterprise-approval, BAP, and WARC/PROV chains, the governance issue additions #212 and #215, and a required-check provider-failure record for the fail-closed Strix re-dispatches on #208/#218/#220. - Added a dated product and technical gap baseline that separates protected-main implementation truth, active pull-request evidence, live review/check blockers, and the next buyer-visible Phase 1 acceptance work. diff --git a/scripts/ci/audit_workflow_registry.py b/scripts/ci/audit_workflow_registry.py index 2e07f510e..68deb156f 100644 --- a/scripts/ci/audit_workflow_registry.py +++ b/scripts/ci/audit_workflow_registry.py @@ -37,6 +37,13 @@ ) _REPOSITORY_WORKFLOW_PREFIX = ".github/workflows/" _DYNAMIC_WORKFLOW_PREFIX = "dynamic/" +# GitHub workflow registry paths are ASCII file paths. Restricting every audited +# path to this canonical alphabet keeps printable confusables (fullwidth solidus, +# fraction slash, homoglyph letters, and bidi-adjacent look-alikes) out of +# evidence where they could pass one validator yet fail exact-match ownership. +_WORKFLOW_PATH_ALPHABET = frozenset( + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789._-/" +) _DISABLED_STATES = { "deleted", "disabled_fork", @@ -175,8 +182,10 @@ def _validate_workflow_path(value: Any, field_name: str) -> str: """Return one unambiguous GitHub workflow registry path.""" path = _require_nonempty_string(value, field_name, 512) - if "\\" in path or "%" in path: - raise WorkflowAuditError(f"{field_name} contains encoded or alternate separators") + if any(character not in _WORKFLOW_PATH_ALPHABET for character in path): + raise WorkflowAuditError( + f"{field_name} contains a character outside the canonical path alphabet" + ) segments = path.split("/") if any(segment in {"", ".", ".."} for segment in segments): raise WorkflowAuditError(f"{field_name} contains an ambiguous path segment") diff --git a/tests/test_workflow_registry_homoglyph_path_contract.py b/tests/test_workflow_registry_homoglyph_path_contract.py new file mode 100644 index 000000000..838f95996 --- /dev/null +++ b/tests/test_workflow_registry_homoglyph_path_contract.py @@ -0,0 +1,119 @@ +"""Reject Unicode homoglyph path confusion from workflow audit evidence. + +These regression tests cover the Strix-reported finding ``vuln-0001``: printable +Unicode confusables such as FULLWIDTH SOLIDUS must never enter workflow path +evidence where they could pass validation yet fail exact-match classification. +""" + +from __future__ import annotations + +import importlib.util +import pathlib +import unittest + +ROOT = pathlib.Path(__file__).resolve().parents[1] +SCRIPT = ROOT / "scripts" / "ci" / "audit_workflow_registry.py" +DEFAULT_SHA = "0c376acf059be9ddddddfbde1d0189e4f39ef014" + + +def _load_module(): + """Load the read-only registry auditor without packaging scripts as modules.""" + + spec = importlib.util.spec_from_file_location("audit_workflow_registry", SCRIPT) + if spec is None or spec.loader is None: + raise AssertionError("workflow registry audit module is not loadable") + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return module + + +class WorkflowRegistryHomoglyphPathContractTests(unittest.TestCase): + """Keep every audited workflow path inside the canonical ASCII alphabet.""" + + @classmethod + def setUpClass(cls) -> None: + cls.audit = _load_module() + + def _record(self, path: str, workflow_id: int = 4101) -> dict: + """Return one active registry record carrying the supplied path.""" + + return { + "id": workflow_id, + "name": "twin", + "path": path, + "state": "active", + } + + def _payload(self, *records: dict) -> dict: + """Build one complete payload around the supplied registry records.""" + + return { + "schema_version": 1, + "expected_default_branch_sha": DEFAULT_SHA, + "observed_default_branch_sha": DEFAULT_SHA, + "observed_at": "2026-08-26T03:00:00Z", + "reported_total_count": len(records), + "protected_workflow_paths": [], + "active_pr_workflow_paths": [], + "registry_pages": [ + { + "page": 1, + "status_code": 200, + "has_next": False, + "workflows": list(records), + } + ], + } + + def test_homoglyph_path_separators_fail_closed(self) -> None: + """Printable slash look-alikes must be rejected before classification.""" + + for separator in ("\uff0f", "\u2044", "\u2215", "\u29f8"): + with self.subTest(separator=hex(ord(separator))): + twin = f"github{separator}workflows{separator}real.yml" + with self.assertRaises(self.audit.WorkflowAuditError): + self.audit.audit_workflow_registry( + self._payload(self._record(twin)) + ) + + def test_mixed_ascii_and_homoglyph_separators_fail_closed(self) -> None: + """Partially homoglyphic paths cannot bypass the canonical alphabet gate.""" + + for path in ( + ".github/workflows\uff0freal.yml", + ".github\uff0fworkflows/real.yml", + ".github/workflows/real.yml\uff0e", + ): + with self.subTest(path=path): + with self.assertRaises(self.audit.WorkflowAuditError): + self.audit.audit_workflow_registry(self._payload(self._record(path))) + + def test_fullwidth_and_accented_path_letters_fail_closed(self) -> None: + """Confusable letter forms outside the canonical alphabet are rejected.""" + + for path in ( + ".github/workflows/r\u0131al.yml", + ".github/workflows/\uff52eal.yml", + ): + with self.subTest(path=path): + with self.assertRaises(self.audit.WorkflowAuditError): + self.audit.audit_workflow_registry(self._payload(self._record(path))) + + def test_canonical_ascii_paths_remain_valid(self) -> None: + """The strict alphabet keeps every legitimate repository workflow usable.""" + + evidence = self.audit.audit_workflow_registry( + self._payload( + self._record(".github/workflows/ci.yml", workflow_id=4101), + self._record("dynamic/external-workflow.yml", workflow_id=4102), + ) + ) + classifications = [ + record["classification"] for record in evidence["workflow_records"] + ] + self.assertEqual(classifications[0], "active_orphan_repository_workflow") + self.assertEqual(classifications[1], "github_dynamic_workflow") + + +if __name__ == "__main__": + unittest.main() From 30f909a2fa93a67b03181876d82d293ece7f0f0a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 25 Aug 2026 23:04:18 -0700 Subject: [PATCH 104/111] test(ci): reproduce Python 3.10 cleanup compatibility failure --- ...low_registry_python310_cleanup_contract.py | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 tests/test_workflow_registry_python310_cleanup_contract.py diff --git a/tests/test_workflow_registry_python310_cleanup_contract.py b/tests/test_workflow_registry_python310_cleanup_contract.py new file mode 100644 index 000000000..8444dc620 --- /dev/null +++ b/tests/test_workflow_registry_python310_cleanup_contract.py @@ -0,0 +1,93 @@ +"""Regression contract for Python 3.10-safe workflow-audit cleanup failures.""" + +from __future__ import annotations + +import errno +import os +import pathlib +import runpy +import tempfile +import unittest +import unittest.mock + +ROOT = pathlib.Path(__file__).resolve().parents[1] +AUDITOR = ROOT / "scripts" / "ci" / "audit_workflow_registry.py" + + +class WorkflowRegistryPython310CleanupContractTests(unittest.TestCase): + """Keep secondary cleanup diagnostics from replacing the causal write failure.""" + + def test_cleanup_failure_does_not_require_exception_add_note(self) -> None: + """The recovery path must remain correct when BaseException.add_note is absent.""" + + namespace = runpy.run_path( + str(AUDITOR), run_name="workflow_python310_cleanup_contract" + ) + write_output = namespace["_write_output"] + workflow_audit_error = namespace["WorkflowAuditError"] + function_globals = write_output.__globals__ + original_open_parent = function_globals["_open_output_parent"] + parent_descriptor: int | None = None + + class FailingDestination: + def __init__(self, descriptor: int) -> None: + self.descriptor = descriptor + + def __enter__(self): + return self + + def __exit__(self, _type, _value, _traceback) -> bool: + os.close(self.descriptor) + return False + + def write(self, _serialized: str) -> None: + os.write(self.descriptor, b"{") + raise OSError("simulated primary write failure") + + def capture_output_parent(path: pathlib.Path) -> tuple[int, str]: + nonlocal parent_descriptor + parent_descriptor, leaf_name = original_open_parent(path) + return parent_descriptor, leaf_name + + def fail_identity_cleanup( + _parent_fd: int, _staging_name: str, _expected_identity: tuple[int, int] + ) -> None: + raise workflow_audit_error("simulated cleanup identity change") + + with tempfile.TemporaryDirectory(prefix="originweave-python310-cleanup-") as directory: + output = pathlib.Path(directory) / "audit.json" + with ( + unittest.mock.patch.dict( + function_globals, + { + "_open_output_parent": capture_output_parent, + "_unlink_matching_staging": fail_identity_cleanup, + }, + ), + unittest.mock.patch.object( + workflow_audit_error, + "add_note", + side_effect=AttributeError("BaseException.add_note is unavailable"), + create=True, + ), + unittest.mock.patch( + "os.fdopen", + side_effect=lambda descriptor, *_args, **_kwargs: FailingDestination( + descriptor + ), + ), + self.assertRaises(workflow_audit_error) as raised, + ): + write_output(output, "{}\n") + + self.assertEqual(str(raised.exception), "output path is not writable") + self.assertIsInstance(raised.exception.__cause__, OSError) + self.assertIsNotNone(parent_descriptor) + assert parent_descriptor is not None + with self.assertRaises(OSError) as closed: + os.fstat(parent_descriptor) + self.assertEqual(closed.exception.errno, errno.EBADF) + + +if __name__ == "__main__": + unittest.main() From 296ad25bb541023dbc869ae07ae1d853820f83a4 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 25 Aug 2026 23:10:34 -0700 Subject: [PATCH 105/111] fix(ci): preserve cleanup failures on Python 3.10 --- scripts/ci/audit_workflow_registry.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/scripts/ci/audit_workflow_registry.py b/scripts/ci/audit_workflow_registry.py index 68deb156f..a79e4d403 100644 --- a/scripts/ci/audit_workflow_registry.py +++ b/scripts/ci/audit_workflow_registry.py @@ -57,6 +57,21 @@ class WorkflowAuditError(ValueError): """Report malformed, incomplete, stale, or ambiguous registry evidence.""" + def __init__(self, message: str) -> None: + super().__init__(message) + self.secondary_diagnostics: list[str] = [] + + def record_secondary_diagnostic(self, diagnostic: str) -> None: + """Retain a secondary failure without masking the primary audit error.""" + + self.secondary_diagnostics.append(diagnostic) + try: + self.add_note(diagnostic) + except AttributeError: + # Python 3.10 lacks BaseException.add_note; the typed fallback above + # keeps the cleanup diagnostic available without replacing the cause. + return + class WorkflowAuditHttpStatusError(WorkflowAuditError): """Report one collected non-200 page with bounded retry guidance. @@ -156,7 +171,7 @@ def _validate_observed_at(value: Any) -> str: def _validate_reported_total_count(value: Any) -> int: - """Return the nonnegative total reported by the first GitHub API page.""" + """Return the nonnegative total reported for the complete GitHub API collection.""" if isinstance(value, bool) or not isinstance(value, int) or value < 0: raise WorkflowAuditError("reported_total_count must be a nonnegative integer") @@ -867,7 +882,7 @@ def _write_output(path: pathlib.Path, serialized: str) -> None: try: _unlink_matching_staging(parent_fd, staging_name, staging_identity) except WorkflowAuditError as cleanup_error: - primary_error.add_note( + primary_error.record_secondary_diagnostic( f"output staging cleanup also failed: {cleanup_error}" ) staging_name = None From ae247ff09f480718725b1c5e0d306c9fa6b01809 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 27 Aug 2026 14:50:34 -0700 Subject: [PATCH 106/111] test(ci): require durable workflow audit publication --- ..._workflow_registry_output_file_contract.py | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/tests/test_workflow_registry_output_file_contract.py b/tests/test_workflow_registry_output_file_contract.py index 28ce54442..eae9ddadf 100644 --- a/tests/test_workflow_registry_output_file_contract.py +++ b/tests/test_workflow_registry_output_file_contract.py @@ -7,6 +7,7 @@ import os import pathlib import runpy +import stat import tempfile import unittest import unittest.mock @@ -347,6 +348,60 @@ def fail_first_published_staging_unlink(path: str | bytes, *args, **kwargs) -> N self.assertTrue(output.is_file()) self.assertEqual(output.stat().st_nlink, 1) + def test_successful_output_fsyncs_parent_directory_before_reporting_success(self) -> None: + """Create-once evidence is successful only after its directory entry is durable.""" + + main = self._main() + real_fsync = os.fsync + synced_modes: list[int] = [] + + def record_fsync_target(descriptor: int) -> None: + synced_modes.append(os.fstat(descriptor).st_mode) + real_fsync(descriptor) + + with tempfile.TemporaryDirectory(prefix="originweave-workflow-output-") as directory: + root = pathlib.Path(directory) + source = root / "registry.json" + source.write_text(json.dumps(_payload()), encoding="utf-8") + output = root / "audit.json" + + with unittest.mock.patch("os.fsync", side_effect=record_fsync_target): + self.assertEqual(main([str(source), "--output", str(output)]), 0) + + self.assertGreaterEqual(len(synced_modes), 2) + self.assertTrue(stat.S_ISREG(synced_modes[0])) + self.assertTrue(stat.S_ISDIR(synced_modes[-1])) + self.assertTrue(output.is_file()) + + def test_parent_directory_fsync_failure_rolls_back_output_for_safe_retry(self) -> None: + """A publication whose directory metadata is not durable must not be reported as success.""" + + main = self._main() + real_fsync = os.fsync + directory_sync_attempted = False + + def fail_directory_fsync(descriptor: int) -> None: + nonlocal directory_sync_attempted + if stat.S_ISDIR(os.fstat(descriptor).st_mode): + directory_sync_attempted = True + raise OSError(errno.EIO, "simulated parent-directory fsync failure") + real_fsync(descriptor) + + with tempfile.TemporaryDirectory(prefix="originweave-workflow-output-") as directory: + root = pathlib.Path(directory) + source = root / "registry.json" + source.write_text(json.dumps(_payload()), encoding="utf-8") + output = root / "audit.json" + + with unittest.mock.patch("os.fsync", side_effect=fail_directory_fsync): + self.assertEqual(main([str(source), "--output", str(output)]), 1) + + self.assertTrue(directory_sync_attempted) + self.assertFalse(output.exists()) + self.assertEqual(main([str(source), "--output", str(output)]), 0) + self.assertTrue(output.is_file()) + self.assertEqual(output.stat().st_nlink, 1) + if __name__ == "__main__": unittest.main() From 151450d07c9751cbd42975c626e90be9995a2f86 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 27 Aug 2026 16:39:53 -0700 Subject: [PATCH 107/111] fix(ci): fsync workflow audit directory publication --- scripts/ci/audit_workflow_registry.py | 32 ++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/scripts/ci/audit_workflow_registry.py b/scripts/ci/audit_workflow_registry.py index a79e4d403..d23ef3767 100644 --- a/scripts/ci/audit_workflow_registry.py +++ b/scripts/ci/audit_workflow_registry.py @@ -951,6 +951,36 @@ def _write_output(path: pathlib.Path, serialized: str) -> None: or final_stat.st_nlink != 1 ): raise WorkflowAuditError("published output identity is ambiguous") + + try: + os.fsync(parent_fd) + except OSError as error: + rollback_stat = _stat_output_leaf(parent_fd, leaf_name) + if ( + rollback_stat is None + or (rollback_stat.st_dev, rollback_stat.st_ino) != staging_identity + or not stat.S_ISREG(rollback_stat.st_mode) + or rollback_stat.st_nlink != 1 + ): + raise WorkflowAuditError( + "published output identity changed during durability rollback" + ) from error + rollback_error = _unlink_with_interrupted_retry(parent_fd, leaf_name) + if rollback_error is not None: + raise WorkflowAuditError( + "published output rollback failed" + ) from rollback_error + primary_error = WorkflowAuditError( + "output parent directory could not be synchronized" + ) + try: + os.fsync(parent_fd) + except OSError as rollback_sync_error: + primary_error.record_secondary_diagnostic( + "output rollback directory sync also failed: " + f"{type(rollback_sync_error).__name__}" + ) + raise primary_error from error except OSError as error: raise WorkflowAuditError("output path is not writable") from error finally: @@ -1005,4 +1035,4 @@ def main(argv: list[str] | None = None) -> int: if __name__ == "__main__": - raise SystemExit(main()) \ No newline at end of file + raise SystemExit(main()) From 41fd850b3d80e94c52f527bc54db1a80ef1f9b65 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 27 Aug 2026 16:44:37 -0700 Subject: [PATCH 108/111] docs(ci): document workflow registry audit contract --- docs/WORKFLOW_REGISTRY_AUDIT.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 docs/WORKFLOW_REGISTRY_AUDIT.md diff --git a/docs/WORKFLOW_REGISTRY_AUDIT.md b/docs/WORKFLOW_REGISTRY_AUDIT.md new file mode 100644 index 000000000..28827a7be --- /dev/null +++ b/docs/WORKFLOW_REGISTRY_AUDIT.md @@ -0,0 +1,32 @@ +# Workflow Registry Audit + +## Status and scope + +`scripts/ci/audit_workflow_registry.py` is an operator-facing, credential-free, read-only audit utility. It classifies operator-collected GitHub Actions workflow-registry evidence; it does **not** call GitHub, disable workflows, change repository settings, or grant mutation authority. Any later workflow disablement remains a separate authorized control-plane action that must independently refetch the exact workflow identity immediately before mutation. + +## Invocation + +```bash +python3 scripts/ci/audit_workflow_registry.py INPUT.json +python3 scripts/ci/audit_workflow_registry.py INPUT.json --output evidence.json +``` + +Without `--output`, canonical JSON evidence is written to stdout. With `--output`, the target is create-once: an existing leaf is never overwritten. + +## Collection contract + +The input is bounded to four MiB and must be a directly named regular UTF-8 JSON file reached without symbolic-link path components. FIFO/device input, ambiguous parent components, file-identity movement, duplicate JSON members, floating-point schema values, non-standard JSON constants, oversized integers, and malformed input fail closed. + +Schema version 1 binds the collection to equal expected and observed protected-default-branch commit SHAs and one valid second-precision UTC observation timestamp. Registry pages must be contiguous from page 1, every accepted page must have HTTP 200, `has_next` must agree with the supplied page set, and the sum of all page records must equal GitHub's unfiltered `reported_total_count` for that collection. A non-200 page remains failed evidence. The typed retry metadata only states whether bounded recollection is appropriate for reviewed transient statuses (`408`, `429`, `500`, `502`, `503`, `504`, plus `403` when a validated bounded `Retry-After` was retained); it never converts failure into success. + +Protected-main workflow paths and active-PR workflow paths are exact, duplicate-free canonical `.github/workflows/*.yml` or `.yaml` identities. Every active-PR exemption is bound to one positive pull-request number and two independently supplied lowercase 40-character contributor-head observations that must match. Protected-main and active-PR ownership may not overlap. Registry records reject duplicate IDs, duplicate paths, ambiguous path case/encoding/traversal, unsupported state values, and malformed workflow identities. + +## Output and durability contract + +The evidence records the exact protected-head SHA, observation time, pagination receipts, immutable workflow IDs, exact paths/states, classifications, and active-PR ownership where applicable. `active_orphan_repository_workflow` may be emitted as a `disable_candidate`; that field is evidence for review, not permission to mutate GitHub. + +A file output is staged as a mode-0600 regular inode in the already-authorized parent directory, flushed and file-`fsync`ed, identity/link-count checked, linked to the absent canonical leaf, and reduced to one canonical link. The final parent directory is then `fsync`ed before success is reported. If publication, staging cleanup, or parent-directory durability fails, the utility preserves the primary typed error, performs only identity-checked bounded cleanup/rollback, and does not report successful evidence publication. Unknown identity changes fail closed rather than deleting an unproven path. + +## Operator acceptance + +Treat the generated JSON as point-in-time evidence only. Before any authorized workflow lifecycle mutation, independently refetch protected `main`, the relevant active-PR heads, the workflow registry, ruleset/branch-protection state, and the exact immutable workflow ID. If any authority, owner, head, path, or registry state changed, discard the earlier mutation decision and recollect. Scheduled OriginWeave writers remain subject to `AGENTS.md`; this utility does not expand their authority. From 028789d8bb6cc30b8e84b1ba7ed46556b26e75ea Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Thu, 27 Aug 2026 16:45:57 -0700 Subject: [PATCH 109/111] docs(ci): record workflow registry audit utility --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f747adeae..48199f9c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ All notable changes to OriginWeave are documented in this file. The format follo - Refreshed the product-gap queue to 126 open pull requests (54 ready, 72 draft) after #190, #188, #185, #192, #182, #184, #115, #181, #116, #117, #118, #183, #114, #127, #112, #109, #186, #110, #108, #111, #174, and #113 were merged into their immediate stacked prerequisites. PRs #147, #146, #145, #144, #143, #142, #141, #139, #136, #132, #129, and #128 moved to ready after exact-head checks and thread review; these are queue-consolidation results, not protected-main shipment. ### Added +- Added a documented, credential-free workflow-registry audit operator path that binds exact protected-head and active-PR ownership evidence, fails closed on incomplete or malformed registry collections, and publishes create-once JSON only after file and parent-directory durability barriers succeed. - Corrected the 2026-08-26 product-gap snapshot with current #229 presentation-identity evidence, stacked-only #205 integration evidence, current base/head pairs, the 126-PR queue count, explicit root-versus-child merge ordering, and the active GitHub counted-approval gate. - Refreshed the product and technical gap baseline onto the 2026-08-26 live inventory: 126 open pull requests (54 ready, 72 draft), protected-main promotion of #168/#194/#196/#216/#151, a verified maintenance-loop record (supersession closure of #153, conflict reconciliations on #37/#149/#152/#173/#175, issue #212 option-(b) authorization on #43, Strix vuln-0001 homoglyph remediation on #124), provider-rerun outcome evidence, an organization review-pipeline congestion record, and refreshed merge-order queue guidance. Documentation evidence contracts were aligned to the same snapshot so the baseline, its dated markers, and the pinned exact-head rows cannot silently diverge. @@ -102,4 +103,4 @@ All notable changes to OriginWeave are documented in this file. The format follo - The hourly product agent has no Git metadata or repository authority. A separate post-verification publisher opens one PR and cannot approve or merge it. - The unprivileged OpenCode user is restricted to loopback egress during model execution, preventing runner-wide allow-listed endpoints from becoming direct source-exfiltration channels. -[Unreleased]: https://github.com/ContextualWisdomLab/OriginWeave/compare/main...HEAD \ No newline at end of file +[Unreleased]: https://github.com/ContextualWisdomLab/OriginWeave/compare/main...HEAD From 89d9c5693f306d03bc9cf46246d5cc7b1685c373 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 01:08:24 -0700 Subject: [PATCH 110/111] test(ci): reproduce persistent staging cleanup masking --- ...ow_registry_persistent_cleanup_contract.py | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 tests/test_workflow_registry_persistent_cleanup_contract.py diff --git a/tests/test_workflow_registry_persistent_cleanup_contract.py b/tests/test_workflow_registry_persistent_cleanup_contract.py new file mode 100644 index 000000000..9477c10ba --- /dev/null +++ b/tests/test_workflow_registry_persistent_cleanup_contract.py @@ -0,0 +1,82 @@ +"""Regression contract for persistent workflow-audit staging cleanup failures.""" + +from __future__ import annotations + +import errno +import os +import pathlib +import runpy +import tempfile +import unittest +import unittest.mock + +ROOT = pathlib.Path(__file__).resolve().parents[1] +AUDITOR = ROOT / "scripts" / "ci" / "audit_workflow_registry.py" + + +class WorkflowRegistryPersistentCleanupContractTests(unittest.TestCase): + """Preserve the first causal cleanup failure across a bounded final cleanup attempt.""" + + def test_persistent_staging_cleanup_does_not_replace_first_failure(self) -> None: + """A second staging cleanup failure is diagnostic, not a replacement cause.""" + + namespace = runpy.run_path( + str(AUDITOR), run_name="workflow_persistent_cleanup_contract" + ) + write_output = namespace["_write_output"] + workflow_audit_error = namespace["WorkflowAuditError"] + function_globals = write_output.__globals__ + original_open_parent = function_globals["_open_output_parent"] + original_unlink_with_retry = function_globals["_unlink_with_interrupted_retry"] + parent_descriptor: int | None = None + staging_cleanup_attempts = 0 + first_cleanup_failure = OSError(errno.EIO, "initial staging cleanup failure") + repeated_cleanup_failure = OSError(errno.EBUSY, "final staging cleanup failure") + + def capture_output_parent(path: pathlib.Path) -> tuple[int, str]: + nonlocal parent_descriptor + parent_descriptor, leaf_name = original_open_parent(path) + return parent_descriptor, leaf_name + + def persistently_fail_staging_cleanup( + parent_fd: int, leaf_name: str + ) -> OSError | None: + nonlocal staging_cleanup_attempts + if leaf_name.startswith(".originweave-audit-"): + staging_cleanup_attempts += 1 + if staging_cleanup_attempts == 1: + return first_cleanup_failure + return repeated_cleanup_failure + return original_unlink_with_retry(parent_fd, leaf_name) + + with tempfile.TemporaryDirectory(prefix="originweave-workflow-output-") as directory: + output = pathlib.Path(directory) / "audit.json" + with ( + unittest.mock.patch.dict( + function_globals, + { + "_open_output_parent": capture_output_parent, + "_unlink_with_interrupted_retry": persistently_fail_staging_cleanup, + }, + ), + self.assertRaises(workflow_audit_error) as raised, + ): + write_output(output, "{}\n") + + self.assertEqual(str(raised.exception), "output staging cleanup failed") + self.assertIs(raised.exception.__cause__, first_cleanup_failure) + self.assertEqual(staging_cleanup_attempts, 2) + self.assertEqual( + raised.exception.secondary_diagnostics, + ["final staging cleanup failed: OSError"], + ) + self.assertFalse(output.exists()) + self.assertIsNotNone(parent_descriptor) + assert parent_descriptor is not None + with self.assertRaises(OSError) as closed: + os.fstat(parent_descriptor) + self.assertEqual(closed.exception.errno, errno.EBADF) + + +if __name__ == "__main__": + unittest.main() From fdb88698ca20626a6643bc2ad7944fb968835700 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 28 Aug 2026 01:16:08 -0700 Subject: [PATCH 111/111] fix(ci): preserve causal staging cleanup failure --- scripts/ci/audit_workflow_registry.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/scripts/ci/audit_workflow_registry.py b/scripts/ci/audit_workflow_registry.py index d23ef3767..6c83e97ce 100644 --- a/scripts/ci/audit_workflow_registry.py +++ b/scripts/ci/audit_workflow_registry.py @@ -940,7 +940,21 @@ def _write_output(path: pathlib.Path, serialized: str) -> None: raise WorkflowAuditError( "published output rollback failed" ) from rollback_error - raise WorkflowAuditError("output staging cleanup failed") from cleanup_error + primary_error = WorkflowAuditError("output staging cleanup failed") + try: + _unlink_matching_staging(parent_fd, staging_name, staging_identity) + except WorkflowAuditError as final_cleanup_error: + diagnostic_source = ( + final_cleanup_error.__cause__ + if final_cleanup_error.__cause__ is not None + else final_cleanup_error + ) + primary_error.record_secondary_diagnostic( + "final staging cleanup failed: " + f"{type(diagnostic_source).__name__}" + ) + staging_name = None + raise primary_error from cleanup_error staging_name = None final_stat = _stat_output_leaf(parent_fd, leaf_name) @@ -1035,4 +1049,4 @@ def main(argv: list[str] | None = None) -> int: if __name__ == "__main__": - raise SystemExit(main()) + raise SystemExit(main()) \ No newline at end of file