From 710b00e6c8c7744b877bc5e1075859e32a193adb Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 04:06:59 +0900 Subject: [PATCH 1/2] docs(ci): add prose-only partition canary --- docs/doctoring/ci-docs-only-partition-canary.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 docs/doctoring/ci-docs-only-partition-canary.md diff --git a/docs/doctoring/ci-docs-only-partition-canary.md b/docs/doctoring/ci-docs-only-partition-canary.md new file mode 100644 index 000000000..d6c29f9d2 --- /dev/null +++ b/docs/doctoring/ci-docs-only-partition-canary.md @@ -0,0 +1,7 @@ +# Docs-only CI partition canary + +This document is an executable canary for issue #279 and its owner repair PR #282. Its semantic delta is intentionally documentation-only so GitHub Actions can prove the exact-head partition without changing Rust, browser, policy, security, release, or workflow authority. + +Acceptance is narrow and observable. On the unchanged canary head, `CI` must execute `Classify CI scope` and `Repository and documentation contracts`. The classifier must report a documentation-only change. `Rust contracts` and `Production coverage` must not execute Rust-heavy work for this delta. Queued, skipped predecessor, runner-less, cancelled, or status-only evidence is not a successful canary result. + +The canary does not weaken the central required security/review workflows. It does not establish product release readiness or browser-runtime correctness; it only exercises the local CI trigger partition introduced for #279. From 73fd6261a2a0ea005dbaa6e7aa2a42a29c3611c9 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 4 Sep 2026 15:08:25 +0900 Subject: [PATCH 2/2] fix(ci): restore parent tree in prose canary restack --- scripts/ci/classify_ci_change_scope.py | 6 +++++- tests/test_ci_change_scope_prose_boundary.py | 12 ++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/scripts/ci/classify_ci_change_scope.py b/scripts/ci/classify_ci_change_scope.py index 4d6b6b394..75a43eda5 100644 --- a/scripts/ci/classify_ci_change_scope.py +++ b/scripts/ci/classify_ci_change_scope.py @@ -33,7 +33,11 @@ def _decode_repository_path(raw_path: bytes) -> str: if not path or path.startswith("/"): raise ValueError("changed path must be a non-empty repository-relative path") - parts = PurePosixPath(path).parts + parsed_path = PurePosixPath(path) + if path == "." or parsed_path.as_posix() != path: + raise ValueError("changed path must use a canonical repository path") + + parts = parsed_path.parts if ".." in parts: raise ValueError("changed path must not contain parent traversal") return path diff --git a/tests/test_ci_change_scope_prose_boundary.py b/tests/test_ci_change_scope_prose_boundary.py index 9be269a7e..18afae233 100644 --- a/tests/test_ci_change_scope_prose_boundary.py +++ b/tests/test_ci_change_scope_prose_boundary.py @@ -68,6 +68,18 @@ def test_markdown_under_docs_remains_lightweight(self) -> None: (True, False), ) + def test_noncanonical_docs_paths_fail_before_scope_classification(self) -> None: + """Tree-diff evidence must use Git's canonical repository-relative path spelling.""" + + for path in ( + "docs//browser-policy.md", + "docs/./browser-policy.md", + "docs/doctoring//browser-policy.md", + ): + with self.subTest(path=path): + with self.assertRaisesRegex(ValueError, "canonical repository path"): + parse_nul_raw_changes(_raw_record(path)) + if __name__ == "__main__": unittest.main()