From fa0c6bccde1df65c747dbf853a455abb29be0191 Mon Sep 17 00:00:00 2001 From: rabii-chaarani Date: Tue, 27 Oct 2026 15:21:46 +0930 Subject: [PATCH] fix(ci): archive historical Python evidence helpers Preserve the four helper inputs byte-for-byte in a deterministic archive and run the unchanged repository policy gate before packaging in CI. Refs: COD-99; Scryer chg-cxz5az. Historical archival approved by the user; release replacement and publication remain separate. --- .github/workflows/ci.yml | 2 + crates/xtask/src/main.rs | 31 ++ docs/release.md | 1 + .../graph-disk-cleanup-evidence/README.md | 58 +++ .../graph-disk-cleanup-evidence/audit.py | 124 ----- .../historical-python-helpers.json | 48 ++ .../historical-python-helpers.tar.gz | Bin 0 -> 15810 bytes .../qualification.py | 449 ------------------ .../graph-disk-cleanup-evidence/test_audit.py | 133 ------ .../test_qualification.py | 405 ---------------- .../release-gate-40453997235/local-checks.log | 95 ++++ .../release-gate-40453997235/nextest.toml | 2 + .../policy-failure-probes.log | 8 + .../release-gate-40453997235/review.md | 54 +++ .../verification.json | 116 +++++ .../release-gate-40453997235/xtask.junit.xml | 91 ++++ 16 files changed, 506 insertions(+), 1111 deletions(-) create mode 100644 docs/testing/graph-disk-cleanup-evidence/README.md delete mode 100644 docs/testing/graph-disk-cleanup-evidence/audit.py create mode 100644 docs/testing/graph-disk-cleanup-evidence/historical-python-helpers.json create mode 100644 docs/testing/graph-disk-cleanup-evidence/historical-python-helpers.tar.gz delete mode 100644 docs/testing/graph-disk-cleanup-evidence/qualification.py delete mode 100644 docs/testing/graph-disk-cleanup-evidence/test_audit.py delete mode 100644 docs/testing/graph-disk-cleanup-evidence/test_qualification.py create mode 100644 docs/testing/release-gate-40453997235/local-checks.log create mode 100644 docs/testing/release-gate-40453997235/nextest.toml create mode 100644 docs/testing/release-gate-40453997235/policy-failure-probes.log create mode 100644 docs/testing/release-gate-40453997235/review.md create mode 100644 docs/testing/release-gate-40453997235/verification.json create mode 100644 docs/testing/release-gate-40453997235/xtask.junit.xml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dac53fa4..9a244bd7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -75,6 +75,8 @@ jobs: steps: - name: Check out repository uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + - name: Check repository release policy + run: cargo run -p xtask --locked -- release-gate - name: Verify crates.io package run: cargo publish --dry-run --locked - name: Enforce crates.io package size limit diff --git a/crates/xtask/src/main.rs b/crates/xtask/src/main.rs index 330237b3..a633567e 100644 --- a/crates/xtask/src/main.rs +++ b/crates/xtask/src/main.rs @@ -1173,6 +1173,12 @@ fn check_workflow_policy( ); } let ci_publish = yaml_path(ci, &["jobs", "publish-dry-run"]).unwrap_or(&YamlValue::Null); + if !yaml_contains_string(ci_publish, "cargo run -p xtask --locked -- release-gate") { + issues.push( + "FAIL: repository-release-gate-missing: CI must check repository release policy." + .to_string(), + ); + } if !yaml_contains_string(ci_publish, "cargo package --locked --no-verify") || !yaml_contains_string(ci_publish, "verify-crate-size") { @@ -2837,6 +2843,31 @@ mod tests { ); } + #[test] + fn workflow_policy_rejects_missing_repository_release_gate() { + let mut ci = valid_ci_workflow(); + let steps = ci["jobs"]["publish-dry-run"]["steps"] + .as_sequence_mut() + .unwrap(); + steps.retain(|step| { + step.get("run").and_then(YamlValue::as_str) + != Some("cargo run -p xtask --locked -- release-gate") + }); + let mut issues = Vec::new(); + check_workflow_policy( + &ci, + &valid_native_workflow(), + &yaml_serde::from_str(&valid_release_workflow_text()).unwrap(), + &mut issues, + ); + assert!( + issues + .iter() + .any(|issue| issue.contains("repository-release-gate-missing")), + "{issues:?}" + ); + } + #[test] fn workflow_policy_rejects_missing_crate_size_gates() { let broken_release = valid_release_workflow_text() diff --git a/docs/release.md b/docs/release.md index 9bd55258..6e7d1008 100644 --- a/docs/release.md +++ b/docs/release.md @@ -37,6 +37,7 @@ Pull requests targeting `main` and pushes to `main` run: - platform-aligned workspace tests on macOS ARM64 and Windows x86_64 - `cargo clippy --workspace --all-targets --all-features --locked -- -D warnings` - Rust advisory scanning with `cargo audit` +- `cargo run -p xtask --locked -- release-gate`, before the package dry-run, to reject repository-policy violations before release. - `cargo publish --dry-run --locked` - An exact-byte check that the generated `.crate` does not exceed crates.io's 10 MiB upload limit. - `cargo package -p k-wiki --locked --no-verify` and the isolated Knowledge Wiki smoke. The diff --git a/docs/testing/graph-disk-cleanup-evidence/README.md b/docs/testing/graph-disk-cleanup-evidence/README.md new file mode 100644 index 00000000..f2a49ec0 --- /dev/null +++ b/docs/testing/graph-disk-cleanup-evidence/README.md @@ -0,0 +1,58 @@ +# Historical Python evidence helpers + +[COD-99](https://linear.app/codebasegraph/issue/COD-99) archives the four Python +helpers from commit `7038e66ff8271ffddc511f879cf8d28353a09b2d`. The user approved +archiving them on 2026-10-27. They are retained historical evidence, not maintained +implementation or supported verification tooling. The Rust-only repository policy +is unchanged. + +`historical-python-helpers.tar.gz` preserves each file's original bytes and repository-relative +path. [The manifest](./historical-python-helpers.json) records the source commit, +Git blob IDs, byte lengths, SHA-256 digests and exact archive creation command. +The archive SHA-256 is `032ac4ef9cfddf40dc0540a913f71c34de4901a4a7d61d035141cb96832e7519`. Two independent `git archive` +invocations produced identical bytes; each regular member matched its original +file byte-for-byte before removal. + +Historical reports, source manifests, test outputs and their hashes are unchanged. +Their `.py` paths still identify their original inputs, now stored in this archive. +The archive does not turn earlier partial evidence into current qualification. +The canonical DC-S1–DC-S5 contract remains +`knowledge:architecture/graph-disk-cleanup-contract`. + +## Reconstruct historical inputs + +Use a separate temporary checkout of the revision named by the report you are +reproducing. Do not extract into the current checkout: loose Python source would +correctly fail the repository gate again. The archive contains only the four +helpers, not their historical environment or every report prerequisite. + +From this checkout, inspect and verify the archive before extracting: + +```bash +shasum -a 256 docs/testing/graph-disk-cleanup-evidence/historical-python-helpers.tar.gz +tar -tzf docs/testing/graph-disk-cleanup-evidence/historical-python-helpers.tar.gz +``` + +For an unused temporary directory, reconstruct the archive's source revision: + +```bash +git worktree add --detach /private/tmp/codebasegraph-historical-7038e66 \ + 7038e66ff8271ffddc511f879cf8d28353a09b2d +tar -xzf docs/testing/graph-disk-cleanup-evidence/historical-python-helpers.tar.gz \ + -C /private/tmp/codebasegraph-historical-7038e66 +``` + +All file members are under `docs/testing/graph-disk-cleanup-evidence/`, so sibling +imports and original relative paths are preserved. Verify their hashes against +the manifest. Follow the selected historical report's commands and prerequisites; +this archival change does not claim that historical helper tests were rerun or +that all inputs can be reproduced from the archive alone. + +## Release limitation + +The original [failed job](https://github.com/rabii-chaarani/codebaseGraph/actions/runs/40453997235/job/122023180345) +checks immutable source `7038e66`. A fix on a later commit cannot make that source +pass by rerunning it. Corrected publication needs a trusted replacement release +PR and its own successful exact-SHA CI under [the release process](../../release.md). +Version selection, replacement of PR #156, tag/label changes and publication are +not approved by the archival decision. diff --git a/docs/testing/graph-disk-cleanup-evidence/audit.py b/docs/testing/graph-disk-cleanup-evidence/audit.py deleted file mode 100644 index 18a90517..00000000 --- a/docs/testing/graph-disk-cleanup-evidence/audit.py +++ /dev/null @@ -1,124 +0,0 @@ -"""Audit archived executions without treating them as aggregate acceptance. - -Run with Python 3.8+: python3 docs/testing/graph-disk-cleanup-evidence/audit.py -This checks evidence integrity, not implementation correctness or platform gates. -""" - -import hashlib -import gzip -import json -from pathlib import Path -import xml.etree.ElementTree as ET - - -def sha256(path): - return hashlib.sha256(path.read_bytes()).hexdigest() - - -def passed(case): - return not any(case.find(tag) is not None for tag in ("failure", "error", "skipped")) - - -def audit(root=None): - root = Path(root) if root is not None else Path(__file__).resolve().parent - index = json.loads((root.parent / "graph-disk-cleanup-evidence.json").read_text()) - reports = json.loads((root / "foundation-reports.json").read_text())["reports"] - report_ids = set() - executions = 0 - cases_by_report = {} - for report in reports: - report_id = report["id"] - assert report_id not in report_ids, report_id - report_ids.add(report_id) - path = root / report["report"] - assert sha256(path) == report["sha256"], path - xml = ET.parse(path).getroot() - cases = xml.findall(".//testcase") - cases_by_report[report_id] = cases - actual = { - "cases": len(cases), - "failed": sum(bool(c.findall("failure") + c.findall("error")) for c in cases), - "skipped": sum(c.find("skipped") is not None for c in cases), - } - assert actual == report["execution_counts"], (report_id, actual) - names = [ - {"suite": suite.get("name"), "name": case.get("name"), "classname": case.get("classname")} - for suite in xml.findall(".//testsuite") - for case in suite.findall("testcase") - ] - assert names == report["case_names"], report_id - if "source_manifest" in report: - assert sha256(root / report["source_manifest"]) == report["source_manifest_sha256"] - executions += actual["cases"] - - ids = [row["id"] for row in index["traceability"]] - assert len(ids) == len(set(ids)), "duplicate work identity" - expected = { - "DC-S%d-%s%d" % (step, kind, number) - for step, requirements, acceptance in [(1, 8, 4), (2, 8, 4), (3, 8, 5), (4, 8, 4), (5, 9, 4)] - for kind, count in [("R", requirements), ("A", acceptance)] - for number in range(1, count + 1) - } - assert set(ids) == expected, (expected - set(ids), set(ids) - expected) - by_id = {report["id"]: report for report in reports} - for row in index["traceability"]: - for evidence in row["executed_evidence"]: - report = by_id[evidence["report_id"]] - names = {case["name"] for case in report["case_names"]} - assert evidence["tests"] and set(evidence["tests"]) <= names, row["id"] - if evidence["result"] == "passed": - for name in evidence["tests"]: - referenced = [case for case in cases_by_report[evidence["report_id"]] if case.get("name") == name] - assert referenced and all(passed(case) for case in referenced), (row["id"], name) - msrv = json.loads((root / "msrv-readiness.json").read_text()) - for attempt in msrv["attempts"]: - assert sha256(root / attempt["stderr"]) == attempt["stderr_sha256"] - assert attempt["actual_exit_code"] == 101 - assert msrv["baseline_manifest_lock_diff"] == "", "dependency baseline changed" - reviews = json.loads((root / "review-outcomes.json").read_text()) - for artifact in reviews["s3_expanded_partial"]["executed_failure_artifacts"]: - assert sha256(root / artifact["path"]) == artifact["sha256"] - if artifact.get("encoding") == "gzip": - decoded = gzip.decompress((root / artifact["path"]).read_bytes()) - assert hashlib.sha256(decoded).hexdigest() == artifact["decoded_sha256"] - else: - assert artifact.get("encoding") is None, "unsupported evidence encoding" - scheduler = reviews["s4_scheduler_primitive"]["actual_junit"] - assert sha256(root / scheduler["path"]) == scheduler["sha256"] - external = json.loads((root.parent / index["s5_external_execution"]).read_text()) - assert sha256(root / external["junit"]) == external["junit_sha256"] - external_xml = ET.parse(root / external["junit"]).getroot() - external_cases = external_xml.findall(".//testcase") - assert external_xml.get("uuid") == external["run_id"], "external run identity mismatch" - actual_names = sorted((case.get("name"), case.get("classname")) for case in external_cases) - expected_names = sorted((case["name"], case["classname"]) for case in external["expected_test_cases"]) - assert expected_names == sorted((name, "codebase-graph::storage_maintenance") for name in ( - "health_preserves_legacy_fields_and_marks_unobserved_maintenance_unknown", - "nonboolean_quiescence_confirmation_is_rejected_before_repository_effects", - )), "unexpected external test contract" - assert actual_names == expected_names, "external case identity mismatch" - actual_results = { - "passed": sum(passed(case) for case in external_cases), - "failed": sum(bool(case.findall("failure") + case.findall("error")) for case in external_cases), - "skipped": sum(case.find("skipped") is not None for case in external_cases), - } - assert actual_results == external["actual_result"], "external result mismatch" - assert actual_results == {"passed": 2, "failed": 0, "skipped": 0} - assert "src/api/normalization.rs" in external["source_sha256"] - for path, expected_hash in external["capture_files_sha256"].items(): - assert sha256(root / path) == expected_hash, path - capture = root / "s5-fresh-capture" - source_before = json.loads((capture / "source-before.json").read_text()) - source_after_build = json.loads((capture / "source-after-build.json").read_text()) - source_after_execution = json.loads((capture / "source-after-execution.json").read_text()) - assert source_before == source_after_build == source_after_execution == external["source_sha256"] - binary_before = json.loads((capture / "binary-before.json").read_text()) - binary_after = json.loads((capture / "binary-after.json").read_text()) - assert binary_before == binary_after == external["binary_sha256"] - print("Evidence integrity passed: %d traceability rows, %d reports, %d executions." % (len(ids), len(reports), executions)) - print("Execution counts are not unique scenarios. This audit does not establish aggregate acceptance.") - print("Separate narrow S5 health/request report: 2 passed; final integration remains pending.") - - -if __name__ == "__main__": - audit() diff --git a/docs/testing/graph-disk-cleanup-evidence/historical-python-helpers.json b/docs/testing/graph-disk-cleanup-evidence/historical-python-helpers.json new file mode 100644 index 00000000..0f7717a1 --- /dev/null +++ b/docs/testing/graph-disk-cleanup-evidence/historical-python-helpers.json @@ -0,0 +1,48 @@ +{ + "schema_version": 1, + "work_item": "COD-99", + "classification": "historical evidence only; not maintained implementation or supported tooling", + "source_commit": "7038e66ff8271ffddc511f879cf8d28353a09b2d", + "approval": { + "date": "2026-10-27", + "user_decision": "i approve archiving the python helpers", + "scope": "Lossless historical archival of the four Python helpers; no policy exemption or release publication approval." + }, + "archive": { + "path": "docs/testing/graph-disk-cleanup-evidence/historical-python-helpers.tar.gz", + "sha256": "032ac4ef9cfddf40dc0540a913f71c34de4901a4a7d61d035141cb96832e7519", + "size_bytes": 15810, + "member_paths_relative_to": "repository root" + }, + "creation_command": "git archive --format=tar.gz --output=docs/testing/graph-disk-cleanup-evidence/historical-python-helpers.tar.gz 7038e66ff8271ffddc511f879cf8d28353a09b2d -- docs/testing/graph-disk-cleanup-evidence/audit.py docs/testing/graph-disk-cleanup-evidence/qualification.py docs/testing/graph-disk-cleanup-evidence/test_audit.py docs/testing/graph-disk-cleanup-evidence/test_qualification.py", + "files": [ + { + "path": "docs/testing/graph-disk-cleanup-evidence/audit.py", + "git_blob_sha1": "18a90517c8f2954dea0d57df412faf119186f266", + "sha256": "316630c38e1638370b8c1873174d4311cc32ae4d0d9bcbbf0045e1083ccfb518", + "size_bytes": 6707, + "git_mode": "100644" + }, + { + "path": "docs/testing/graph-disk-cleanup-evidence/qualification.py", + "git_blob_sha1": "1384a3298cedeb0f3d0022938e885b50875cce04", + "sha256": "264753ae10581691d0dc1c7591acab9601b29ff7c9f95df8d04ed56b56509e3b", + "size_bytes": 26627, + "git_mode": "100644" + }, + { + "path": "docs/testing/graph-disk-cleanup-evidence/test_audit.py", + "git_blob_sha1": "f934f32d3a7f5d583c038bf4c431a48a674573e9", + "sha256": "b317ac2a5f4ab7ec3c52e98169e37090d65c9a7a92d397bd898962516f6f545c", + "size_bytes": 5632, + "git_mode": "100644" + }, + { + "path": "docs/testing/graph-disk-cleanup-evidence/test_qualification.py", + "git_blob_sha1": "4e20628898f3e019e7e45bb956d1669b960a1e53", + "sha256": "4fa5adbdff51ec53085ae307444ca4800a557726b95e18cacfc42091c0c57ef3", + "size_bytes": 24274, + "git_mode": "100644" + } + ] +} diff --git a/docs/testing/graph-disk-cleanup-evidence/historical-python-helpers.tar.gz b/docs/testing/graph-disk-cleanup-evidence/historical-python-helpers.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..05ec406ef509a1bfd58673f67333a434f92c1ecc GIT binary patch literal 15810 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%0DVD z7Nj8PU8e(35pqtf9+?Ab`gX5B(F=R`8Ztn zJi3l1v$M|{zOin!+qfz$`pNyv&fDSj`A? zG`mj4Vf&_Vib(3!Kn_M}=*wzIQym*r9l#J2Lr&axt-q=K(X^oe0(OGp^yyC)ML%bzw zK9b-5k8tsUg!fbR6E5U!Edc*qs9?IGx%fp5aMD`y!N`jw_zCQK(d2SE@=zRaPPiCZ zP={6+jy>YrQCWr>SslK3`r?S51FP|$`HoZ@v;R5X(WTD6QwC%>3{j=7X%1@{4wZpt_?Uq;92VJS2jKEqM~(?-=E>22A0>YM zkM4kN6dhDuFeMM~+Fnnvw~i1U((U;G7yz^y*_x4I_T3$JWN_oQkxf2N|FFSE14FJj z=EA>DldhI9F**&Tu}Kcw-Zf5^y^VajXXfV8m+nj;oN?@kf(NO z3;~B+XrL1U{UN+0id|gKCVW-}5eGy-4M;M}Zc>5Tr`B^;v7w8W4Y5Jf0x;cYYmArT zdbEzcjd5P0H;flsL!{{5`JHZ@-*jty+kNpKjPUq%`0Cq&?j)po9o|*gX3DHcggUbF zd0K$Pr)Dp7Yj$W7w z2Tid7DSLB`7WUmB6yu$H3YKpPb;N53DJcBZtgnm^;VV@fCVMiVV&%30h;o~RoN8n0QWR_;8| zP6J?zDc}S84u0SoC9?ue33y6cAG5kUThGHpFW>HpFW>HpFW>HpFW>H|GJ<5 M5AA@3asU_s0J5+sqW}N^ literal 0 HcmV?d00001 diff --git a/docs/testing/graph-disk-cleanup-evidence/qualification.py b/docs/testing/graph-disk-cleanup-evidence/qualification.py deleted file mode 100644 index 1384a329..00000000 --- a/docs/testing/graph-disk-cleanup-evidence/qualification.py +++ /dev/null @@ -1,449 +0,0 @@ -"""Fresh DC-S5 suite execution capture; never promotes aggregate/platform gates. - -Requires Python 3.9+. Run `python3 qualification.py capture --repository REPO --output NEW_DIRECTORY` -only while holding the coordinator's Cargo target lease. `verify DIRECTORY` -checks the archived capture without rebuilding or reading the original checkout. -""" - -import argparse -import hashlib -import json -import os -from pathlib import Path -import platform -import subprocess -import xml.etree.ElementTree as ET - -SCHEMA = 3 -BINARY = "codebase-graph::storage_maintenance" -EXPECTED = { - "health_preserves_legacy_fields_and_marks_unobserved_maintenance_unknown", - "nonboolean_quiescence_confirmation_is_rejected_before_repository_effects", - "repeated_health_entries_preserve_source_and_foreign_sentinels", -} -LEGACY_LIMITATIONS = [ - "Narrow external storage_maintenance execution only; no collector qualification.", - "Native Windows/Linux, MSRV, full suite and aggregate closure remain pending.", - "Source identity covers repository build inputs; dependency provenance uses Cargo.lock and metadata.", -] -LIMITATIONS = [ - "Captured storage_maintenance suite execution only; acceptance remains criterion-specific.", - "Native Windows/Linux, MSRV, workspace full suite and aggregate closure remain pending.", - "Source identity covers repository build inputs; dependency provenance uses Cargo.lock and metadata.", -] -WORKLOAD_CASES = { - "twenty_public_builds_match_clean_control_and_preserve_unchanged_artifact", - "fixture::workload::tests::schedule_covers_twenty_updates_and_each_required_interruption_boundary", - "fixture::workload::tests::independent_control_matches_twenty_source_updates_without_rewriting_stable_input", - "fixture::workload::tests::search_oracle_omits_only_incarnation_ids_and_keeps_rank_spans_and_content", - "fixture::workload::tests::oracle_rejects_missing_response_fields_and_preserves_context_multiplicity", -} -PROCESS_CASE = "native_phase_owner_death_preserves_prior_graph_and_retries_without_source_edit" - - - -def require(condition, message): - if not condition: - raise ValueError(message) - - -def sha256(path): - digest = hashlib.sha256() - with Path(path).open("rb") as stream: - for block in iter(lambda: stream.read(1024 * 1024), b""): - digest.update(block) - return digest.hexdigest() - - -def write_json(path, value): - Path(path).write_text(json.dumps(value, sort_keys=True, indent=2) + "\n") - - -def read_json(path): - return json.loads(Path(path).read_text()) - - -def source_manifest(repository): - repository = Path(repository).resolve() - # Include untracked new inputs and detect additions/deletions between phases. - names = subprocess.check_output([ - "git", "ls-files", "-z", "--cached", "--others", "--exclude-standard", - ], cwd=repository).decode().split("\0") - roots = {"src", "tests", "crates", "vendor", "assets", ".cargo", ".config"} - singles = {"Cargo.toml", "Cargo.lock", "build.rs", "rust-toolchain", "rust-toolchain.toml"} - result = {} - for name in sorted(set(names) - {""}): - if name.split("/")[0] not in roots and name not in singles: - continue - path = repository / name - require(not path.is_symlink() and path.resolve().is_relative_to(repository), - "build input is a symlink or escapes repository: " + name) - result[name] = sha256(path) # Missing tracked input fails closed. - require({"Cargo.toml", "Cargo.lock", "build.rs"} <= result.keys(), "missing core build inputs") - return result - - -def run(command, repository, output, label, env=None): - with (output / (label + ".stdout")).open("wb") as stdout: - with (output / (label + ".stderr")).open("wb") as stderr: - result = subprocess.run(command, cwd=repository, stdout=stdout, stderr=stderr, check=False, env=env) - return {"argv": command, "exit_code": result.returncode} - - -def binary_paths(metadata): - binaries = metadata["rust-binaries"] - require(set(binaries) == {BINARY}, "capture must select exactly the storage_maintenance binary") - paths = {str(Path(row["binary-path"]).resolve()) for row in binaries.values()} - # nextest can launch non-test helper executables too: freeze those as inputs. - build = metadata["rust-build-meta"] - for rows in build.get("non-test-binaries", {}).values(): - for row in rows: - paths.add(str((Path(build["target-directory"]) / row["path"]).resolve())) - return sorted(paths) - - -def junit_result(path, case_names=None): - xml = ET.parse(path).getroot() - cases = xml.findall(".//testcase") - identities = [(case.get("classname"), case.get("name")) for case in cases] - expected = {(BINARY, name) for name in (EXPECTED if case_names is None else case_names)} - require(len(identities) == len(expected) and set(identities) == expected, - "unexpected, missing or duplicate external testcase") - require(all(not any(case.find(tag) is not None for tag in ("failure", "error", "skipped", "rerunFailure", "rerunError")) - for case in cases), "external testcase did not pass on its first execution") - return {"run_id": xml.get("uuid"), "passed": len(cases), "failed": 0, "skipped": 0} - - -def verify(output): - output = Path(output) - record = read_json(output / "capture.json") - schema = record["schema"] - require(schema in (1, 2, SCHEMA), "unsupported capture schema") - require(record["scope"] == ("storage_maintenance_suite" if schema == SCHEMA else "narrow_external_contract"), "capture scope cannot claim qualification") - require(record["qualification"] == {"aggregate": "pending", "windows": "pending", "linux": "pending"}, - "narrow capture cannot promote qualification gates") - for name, digest in record["artifacts"].items(): - path = Path(name) - require(not path.is_absolute() and ".." not in path.parts, "capture path escapes archive") - require(not (output / path).is_symlink() and (output / path).resolve().is_relative_to(output.resolve()), - "capture artifact escapes archive") - require(sha256(output / path) == digest, "artifact changed: " + name) - required = {"source-before.json", "source-after-build.json", "source-after-execution.json", - "binary-before.json", "binary-after.json", "binary-list.stdout", "binary-list.stderr", - "cargo-metadata.stdout", "cargo-metadata.stderr", "execution.stdout", "execution.stderr", - "junit.xml", "nextest.toml", "capture-driver.py", "rustc.stdout", "rustc.stderr", "cargo.stdout", "cargo.stderr"} - if schema >= 2: - required.update({"root-build.stdout", "root-build.stderr", "wrapper-build.stdout", "wrapper-build.stderr", "root-wrapper.rs", "root-wrapper.exe", "compile-proof.json", "build-environment.json", "root-rustc.stdout", "root-rustc.stderr"}) - if schema == SCHEMA: - required.update({"inventory.stdout", "inventory.stderr"}) - require(set(record["artifacts"]) == required, "incomplete capture artifact set") - sources = [read_json(output / name) for name in ( - "source-before.json", "source-after-build.json", "source-after-execution.json")] - require(sources[0] and sources[0] == sources[1] == sources[2], "source changed during capture") - require({"Cargo.toml", "Cargo.lock", "build.rs"} <= sources[0].keys(), "missing core build inputs") - before, after = [read_json(output / name) for name in ("binary-before.json", "binary-after.json")] - metadata = read_json(output / "binary-list.stdout") - require(before and before == after and set(before) == set(binary_paths(metadata)), "binary identity mismatch") - require(metadata["rust-build-meta"]["base-output-directories"] == ["debug"], "capture did not use debug build") - commands = record["commands"] - expected_commands = {"rustc", "cargo", "cargo-metadata", "binary-list", "execution"} - if schema >= 2: - expected_commands.update({"wrapper-build", "root-build", "root-rustc"}) - if schema == SCHEMA: - expected_commands.add("inventory") - require(set(commands) == expected_commands, "missing command provenance") - require(all(command["exit_code"] == 0 for command in commands.values()), "captured command failed") - for label, argv in [("rustc", ["rustc", "-vV"]), ("cargo", ["cargo", "-V"]), - ("cargo-metadata", ["cargo", "metadata", "--offline", "--locked", "--format-version", "1"])]: - require(commands[label]["argv"] == argv, "unexpected " + label + " command") - require(commands["binary-list"]["argv"] == ["cargo", "nextest", "list", "--offline", "--locked", "--test", "storage_maintenance", "--list-type", "binaries-only", "--message-format", "json"], "unexpected compile command") - require(commands["execution"]["argv"] == execution_command(Path(record["output"])), "unexpected execution command") - if schema >= 2: - validate_compile_proof(record, output) - if schema == SCHEMA: - require(commands["inventory"]["argv"] == inventory_command(Path(record["output"])), "unexpected inventory command") - names = inventory_cases(read_json(output / "inventory.stdout"), metadata, sources[0]) - require(record["expected_case_names"] == sorted(names), "recorded inventory identity mismatch") - else: - names = EXPECTED - result = junit_result(output / "junit.xml", names) - require(result["run_id"] and result == record["result"], "JUnit result/run identity mismatch") - require(record["limitations"] == (LIMITATIONS if schema == SCHEMA else LEGACY_LIMITATIONS), "qualification limitations changed") - return record - - -def execution_command(output): - return ["cargo", "nextest", "run", "--binaries-metadata", str(output / "binary-list.stdout"), - "--cargo-metadata", str(output / "cargo-metadata.stdout"), "--config-file", str(output / "nextest.toml"), - "--profile", "cleanup-s5-capture", "--test-threads", "1"] - - - -def inventory_command(output): - return ["cargo", "nextest", "list", "--binaries-metadata", str(output / "binary-list.stdout"), - "--cargo-metadata", str(output / "cargo-metadata.stdout"), "--config-file", str(output / "nextest.toml"), - "--profile", "cleanup-s5-capture", "--message-format", "json"] - - -def inventory_cases(inventory, binaries, sources): - require(inventory["rust-build-meta"] == binaries["rust-build-meta"], "inventory build identity mismatch") - suites = inventory["rust-suites"] - require(set(suites) == {BINARY}, "inventory selected unexpected test suites") - suite = suites[BINARY] - binary = binaries["rust-binaries"][BINARY] - require(suite["binary-path"] == binary["binary-path"] and suite["package-id"] == binary["package-id"], "inventory binary/package identity mismatch") - cases = suite["testcases"] - require(inventory["test-count"] == len(cases), "inventory count does not match named cases") - require(all(case["ignored"] is False and case["filter-match"]["status"] == "matches" for case in cases.values()), "ignored or filtered required test in inventory") - required = set(EXPECTED) - if "tests/support/storage_maintenance/workload.rs" in sources: - required.update(WORKLOAD_CASES) - if "tests/support/storage_maintenance/process.rs" in sources: - required.add(PROCESS_CASE) - require(required <= cases.keys(), "required fixture test cases were not registered") - return set(cases) - - -def capture(repository, output): - repository, output = Path(repository).resolve(), Path(output).resolve() - require(not output.is_relative_to(repository), "capture output must be outside the repository") - output.mkdir(parents=False, exist_ok=False) # Never overwrite a historical execution. - (output / "capture-driver.py").write_bytes(Path(__file__).read_bytes()) - head = subprocess.check_output(["git", "rev-parse", "HEAD"], cwd=repository).decode().strip() - write_json(output / "source-before.json", source_manifest(repository)) - # An absolute path avoids shared target directories overwriting other JUnit runs. - (output / "nextest.toml").write_text('[profile.cleanup-s5-capture]\nfail-fast = false\nretries = 0\n' - '[profile.cleanup-s5-capture.junit]\npath = ' + json.dumps(str(output / "junit.xml")) + '\n') - commands = {} - environment = build_environment(output) - write_json(output / "build-environment.json", {name: environment[name] for name in BUILD_ENV}) - (output / "root-wrapper.rs").write_text(WRAPPER_SOURCE) - (output / "rustc-events").mkdir() - commands["wrapper-build"] = run(["rustc", "--edition=2021", str(output / "root-wrapper.rs"), "-o", str(output / "root-wrapper.exe")], repository, output, "wrapper-build", environment) - require(commands["wrapper-build"]["exit_code"] == 0, "wrapper compilation failed") - for label, argv in [ - ("rustc", ["rustc", "-vV"]), ("cargo", ["cargo", "-V"]), - ("cargo-metadata", ["cargo", "metadata", "--offline", "--locked", "--format-version", "1"]), - ]: - commands[label] = run(argv, repository, output, label, environment) - require(commands[label]["exit_code"] == 0, label + " failed; raw output retained") - commands["root-build"] = run(root_build_command(), repository, output, "root-build", environment) - require(commands["root-build"]["exit_code"] == 0, "root build failed; raw output retained") - for label, argv in [ - ("binary-list", ["cargo", "nextest", "list", "--offline", "--locked", "--test", "storage_maintenance", "--list-type", "binaries-only", "--message-format", "json"]), - ]: - commands[label] = run(argv, repository, output, label, environment) - require(commands[label]["exit_code"] == 0, label + " failed; raw output retained") - write_json(output / "source-after-build.json", source_manifest(repository)) - require(read_json(output / "source-before.json") == read_json(output / "source-after-build.json"), "source changed during build") - paths = binary_paths(read_json(output / "binary-list.stdout")) - proof = compile_proof(repository, output) - write_json(output / "compile-proof.json", proof) - compilers = {event["compiler"] for event in proof["events"] - if event["manifest"] == str(repository) and "--crate-name" in event["args"]} - require(len(compilers) == 1, "root package used ambiguous compilers") - commands["root-rustc"] = run([next(iter(compilers)), "-vV"], repository, output, "root-rustc", environment) - require(commands["root-rustc"]["exit_code"] == 0, "actual root rustc identity failed") - write_json(output / "binary-before.json", {path: sha256(path) for path in paths}) - commands["inventory"] = run(inventory_command(output), repository, output, "inventory", environment) - require(commands["inventory"]["exit_code"] == 0, "test inventory failed; raw output retained") - names = inventory_cases(read_json(output / "inventory.stdout"), read_json(output / "binary-list.stdout"), read_json(output / "source-before.json")) - commands["execution"] = run(execution_command(output), repository, output, "execution", environment) - write_json(output / "binary-after.json", {path: sha256(path) for path in paths}) - write_json(output / "source-after-execution.json", source_manifest(repository)) - require(commands["execution"]["exit_code"] == 0, "execution failed; raw output retained") - record = { - "schema": SCHEMA, "scope": "storage_maintenance_suite", "head": head, - "repository": str(repository), "output": str(output), "host": platform.platform(), - "qualification": {"aggregate": "pending", "windows": "pending", "linux": "pending"}, - "commands": commands, "compile_proof": proof, "expected_case_names": sorted(names), "result": junit_result(output / "junit.xml", names), "limitations": LIMITATIONS, - "artifacts": {path.name: sha256(path) for path in sorted(output.iterdir()) if path.is_file()}, - } - write_json(output / "capture.json", record) - verify(output) - return record - - -# A unique workspace-wrapper path is part of Cargo's crate hash. Each capture -# therefore recompiles workspace code, retaining non-workspace dependency caches. -# The wrapper records actual compiler execution and applies assertions LAST, -# after flags supplied by Cargo configuration or profiles. -WRAPPER_SOURCE = r''' -use std::{env, ffi::OsString, fs, process::{Command, exit}}; -fn hex(value: &str) -> String { - value.as_bytes().iter().map(|b| format!("{b:02x}")).collect() -} -fn main() { - let mut args: Vec = env::args_os().skip(1).collect(); - let compiler = args.remove(0); - if args.iter().any(|arg| arg == "--crate-name") { - args.push("-C".into()); - args.push("debug-assertions=on".into()); - } - let mut evidence = format!("compiler={}\nmanifest={}\ncwd={}\n", - hex(compiler.to_str().expect("non-UTF8 compiler path")), - hex(&env::var("CARGO_MANIFEST_DIR").unwrap_or_default()), - hex(env::current_dir().unwrap().to_str().expect("non-UTF8 cwd"))); - for arg in &args { evidence.push_str(&format!("arg={}\n", hex(arg.to_str().expect("non-UTF8 compiler arg")))); } - let status = Command::new(&compiler).args(&args).status().expect("launch rustc"); - let code = status.code().unwrap_or(1); - evidence.push_str(&format!("exit={code}\n")); - let dir = env::var_os("CGRAPH_QUALIFICATION_EVENTS").expect("event directory"); - fs::write(std::path::PathBuf::from(dir).join(format!("{}.txt", std::process::id())), evidence).unwrap(); - exit(code); -} -''' -BUILD_ENV = ("RUSTC_WORKSPACE_WRAPPER", "CGRAPH_QUALIFICATION_EVENTS", - "CARGO_PROFILE_TEST_DEBUG_ASSERTIONS", "CARGO_PROFILE_DEV_DEBUG_ASSERTIONS") - - -def build_environment(output): - environment = os.environ.copy() - for name in ("RUSTC_WRAPPER", "RUSTC_WORKSPACE_WRAPPER", "RUSTFLAGS", "CARGO_ENCODED_RUSTFLAGS", "RUSTC"): - require(not environment.get(name), "unsupported ambient compiler override: " + name) - environment.update({ - "RUSTC_WORKSPACE_WRAPPER": str(output / "root-wrapper.exe"), - "CGRAPH_QUALIFICATION_EVENTS": str(output / "rustc-events"), - "CARGO_PROFILE_TEST_DEBUG_ASSERTIONS": "true", - "CARGO_PROFILE_DEV_DEBUG_ASSERTIONS": "true", - }) - return environment - - -def root_build_command(): - return ["cargo", "test", "--offline", "--locked", "--package", "codebase-graph", - "--test", "storage_maintenance", "--no-run", "--message-format=json", "-vv"] - - -def compile_proof(repository, output): - events = [] - for path in sorted((output / "rustc-events").glob("*.txt")): - event = {"args": []} - for line in path.read_text().splitlines(): - key, value = line.split("=", 1) - if key == "exit": - event[key] = int(value) - elif key == "arg": - event["args"].append(bytes.fromhex(value).decode()) - else: - event[key] = bytes.fromhex(value).decode() - events.append(event) - artifacts = [json.loads(line) for line in (output / "root-build.stdout").read_text().splitlines() - if line.startswith("{")] - outputs = {} - for artifact in artifacts: - if artifact.get("reason") != "compiler-artifact" or "custom-build" in artifact["target"]["kind"]: - continue - name = artifact["target"]["name"].replace("-", "_") - for event in events: - args = event["args"] - if event["manifest"] != str(repository) or "--crate-name" not in args or args[args.index("--crate-name") + 1] != name: - continue - actual_sources = [str(Path(event["cwd"]) / arg) if not Path(arg).is_absolute() else arg - for arg in args if arg.endswith(".rs")] - if artifact["target"]["src_path"] not in actual_sources: - continue - for filename in artifact["filenames"]: - emitted = compiler_output(event, artifact, filename) - require(sha256(emitted) == sha256(filename), "Cargo artifact differs from actual compiler output") - outputs[filename] = {"compiler_output": str(emitted), "sha256": sha256(emitted)} - return {"events": events, "cargo_messages": artifacts, "outputs": outputs} - - -def compiler_output(event, artifact, filename): - args = event["args"] - out_dir = Path(args[args.index("--out-dir") + 1]) - path = Path(filename) - if path.parent == out_dir: - return path - # Cargo copies normal bin targets from deps to the profile directory. - require("bin" in artifact["target"]["kind"] and path.parent == out_dir.parent, - "root output path mismatch") - extra = [arg.split("=", 1)[1] for arg in args if arg.startswith("extra-filename=")] - require(len(extra) == 1, "missing compiler output suffix") - return out_dir / (artifact["target"]["name"].replace("-", "_") + extra[0] + (".exe" if path.suffix == ".exe" else "")) - - -def validate_compile_proof(record, output): - repository = Path(record["repository"]) - original_output = Path(record["output"]) - require(record["commands"]["root-build"]["argv"] == root_build_command(), "unexpected root build command") - require(record["commands"]["wrapper-build"]["argv"] == ["rustc", "--edition=2021", str(original_output / "root-wrapper.rs"), "-o", str(original_output / "root-wrapper.exe")], "unexpected wrapper build command") - environment = read_json(output / "build-environment.json") - require(environment == { - "RUSTC_WORKSPACE_WRAPPER": str(original_output / "root-wrapper.exe"), - "CGRAPH_QUALIFICATION_EVENTS": str(original_output / "rustc-events"), - "CARGO_PROFILE_TEST_DEBUG_ASSERTIONS": "true", "CARGO_PROFILE_DEV_DEBUG_ASSERTIONS": "true", - }, "unsupported build environment") - require((output / "root-wrapper.rs").read_text() == WRAPPER_SOURCE, "compiler wrapper changed") - metadata = read_json(output / "cargo-metadata.stdout") - require(metadata["workspace_root"] == str(repository), "wrong Cargo workspace") - packages = [p for p in metadata["packages"] if p["manifest_path"] == str(repository / "Cargo.toml")] - require(len(packages) == 1 and packages[0]["name"] == "codebase-graph", "wrong root Cargo package") - package = packages[0] - require(package["id"] in metadata["workspace_members"], "root package is not a workspace member") - proof = read_json(output / "compile-proof.json") - require(proof == record["compile_proof"], "compile proof mismatch") - raw_messages = [json.loads(line) for line in (output / "root-build.stdout").read_text().splitlines() if line.startswith("{")] - require(proof["cargo_messages"] == raw_messages, "Cargo compile messages mismatch") - artifacts = [message for message in raw_messages if message.get("reason") == "compiler-artifact" - and message["package_id"] == package["id"] and "custom-build" not in message["target"]["kind"]] - require(artifacts, "missing root compile artifacts") - selected = read_json(output / "binary-list.stdout") - require(all(row["package-id"] == package["id"] for row in selected["rust-binaries"].values()), "nextest selected wrong Cargo package") - require(set(selected["rust-build-meta"].get("non-test-binaries", {})) <= {package["id"]}, "wrong helper package") - compilers = {event["compiler"] for event in proof["events"] - if event["manifest"] == str(repository) and "--crate-name" in event["args"]} - require(len(compilers) == 1 and record["commands"]["root-rustc"]["argv"] == [next(iter(compilers)), "-vV"], "actual root compiler identity mismatch") - compiled_paths = set() - saw_library = False - sources = read_json(output / "source-before.json") - for artifact in artifacts: - require(artifact["fresh"] is False, "root artifact reused from cache") - require(artifact["profile"]["debug_assertions"] is True, "root profile disables debug assertions") - source = Path(artifact["target"]["src_path"]) - require(source.is_relative_to(repository) and str(source.relative_to(repository)) in sources, "unrecorded root source") - name = artifact["target"]["name"].replace("-", "_") - matching = [] - for event in proof["events"]: - args = event["args"] - if event["manifest"] != str(repository) or event["exit"] != 0 or "--crate-name" not in args: - continue - if args[args.index("--crate-name") + 1] != name: - continue - actual_sources = [str(Path(event["cwd"]) / arg) if not Path(arg).is_absolute() else arg - for arg in args if arg.endswith(".rs")] - if str(source) in actual_sources: - matching.append(event) - require(matching, "no actual rustc invocation for root artifact") - for event in matching: - require(event["args"][-2:] == ["-C", "debug-assertions=on"], "actual root compiler assertions are not enabled") - for path in artifact["filenames"]: - emitted = compiler_output(event, artifact, path) - require(proof["outputs"][path]["compiler_output"] == str(emitted), "compiler output identity mismatch") - if path in read_json(output / "binary-before.json"): - require(proof["outputs"][path]["sha256"] == read_json(output / "binary-before.json")[path], "selected binary differs from compiler output") - compiled_paths.update(artifact["filenames"]) - saw_library |= "lib" in artifact["target"]["kind"] - require(saw_library, "root library was not freshly compiled") - require(set(binary_paths(selected)) <= compiled_paths, "selected binary/helper did not come from root compilation") - - -def main(): - parser = argparse.ArgumentParser(description=__doc__) - commands = parser.add_subparsers(dest="action", required=True) - command = commands.add_parser("capture") - command.add_argument("--repository", required=True) - command.add_argument("--output", required=True) - command = commands.add_parser("verify") - command.add_argument("output") - args = parser.parse_args() - record = capture(args.repository, args.output) if args.action == "capture" else verify(args.output) - if record["schema"] < SCHEMA: - print("Historical %d-case capture integrity verified for its recorded scope; no new suite qualification." % record["result"]["passed"]) - else: - print("Verified all %d registered storage_maintenance cases; aggregate/platform gates remain pending." % record["result"]["passed"]) - - -if __name__ == "__main__": - main() diff --git a/docs/testing/graph-disk-cleanup-evidence/test_audit.py b/docs/testing/graph-disk-cleanup-evidence/test_audit.py deleted file mode 100644 index f934f32d..00000000 --- a/docs/testing/graph-disk-cleanup-evidence/test_audit.py +++ /dev/null @@ -1,133 +0,0 @@ -"""Mutation regressions use isolated copies; original execution reports stay intact.""" - -import contextlib -import hashlib -import gzip -import importlib.util -import io -import json -from pathlib import Path -import shutil -import tempfile -import unittest -import xml.etree.ElementTree as ET - -ROOT = Path(__file__).resolve().parent -SPEC = importlib.util.spec_from_file_location("evidence_audit", ROOT / "audit.py") -AUDIT = importlib.util.module_from_spec(SPEC) -SPEC.loader.exec_module(AUDIT) - - -class EvidenceMutationTests(unittest.TestCase): - def setUp(self): - self.temporary = tempfile.TemporaryDirectory(prefix="cgraph-s5-audit-regression-") - self.addCleanup(self.temporary.cleanup) - base = Path(self.temporary.name) - self.root = base / ROOT.name - shutil.copytree(ROOT, self.root, ignore=shutil.ignore_patterns("__pycache__")) - shutil.copy2(ROOT.parent / "graph-disk-cleanup-evidence.json", base) - - def read(self, name): - return json.loads((self.root / name).read_text()) - - def write(self, name, data): - (self.root / name).write_text(json.dumps(data)) - - def rewrite_report(self, record, mutate, path_key="junit", hash_key="junit_sha256"): - path = self.root / record[path_key] - tree = ET.parse(path) - mutate(tree.getroot()) - tree.write(path, encoding="utf-8", xml_declaration=True) - record[hash_key] = hashlib.sha256(path.read_bytes()).hexdigest() - - def external(self): - index = json.loads((self.root.parent / "graph-disk-cleanup-evidence.json").read_text()) - return Path(index["s5_external_execution"]).name - - def rejected(self): - with contextlib.redirect_stdout(io.StringIO()), self.assertRaises(AssertionError): - AUDIT.audit(self.root) - - def test_original_actual_reports_pass(self): - with contextlib.redirect_stdout(io.StringIO()): - AUDIT.audit(self.root) - - def test_compressed_artifact_matches_historical_decoded_bytes(self): - data = self.read("review-outcomes.json") - artifact = next(a for a in data["s3_expanded_partial"]["executed_failure_artifacts"] if a.get("encoding") == "gzip") - decoded = gzip.decompress((self.root / artifact["path"]).read_bytes()) - historical = self.read("s5-narrow-initial-checkpoint.json") - historical_hash = historical["owned_files_sha256"][artifact["historical_repository_path"]] - self.assertEqual(hashlib.sha256(decoded).hexdigest(), historical_hash) - self.assertEqual(artifact["decoded_sha256"], historical_hash) - self.assertEqual(artifact["historical_commit"], "9eaa8dedf4f626ebf0974f327f81d0a3c0315e57") - - def test_compressed_payload_tamper_rejected_even_with_matching_encoded_hash(self): - data = self.read("review-outcomes.json") - artifact = next(a for a in data["s3_expanded_partial"]["executed_failure_artifacts"] if a.get("encoding") == "gzip") - encoded = gzip.compress(b"changed observation output\n", mtime=0) - (self.root / artifact["path"]).write_bytes(encoded) - artifact["sha256"] = hashlib.sha256(encoded).hexdigest() - self.write("review-outcomes.json", data) - self.rejected() - - def test_named_foundation_skip_rejected_even_with_matching_hash_and_counts(self): - data = self.read("foundation-reports.json") - record = next(r for r in data["reports"] if r["id"] == "s1-post-main") - def skip(xml): - case = next(c for c in xml.findall(".//testcase") if "dc_s1_a1_" in c.get("name", "")) - ET.SubElement(case, "skipped") - self.rewrite_report(record, skip, "report", "sha256") - record["execution_counts"]["skipped"] += 1 - self.write("foundation-reports.json", data) - self.rejected() - - def test_external_skip_rejected_with_original_claimed_counts(self): - name = self.external() - data = self.read(name) - self.rewrite_report(data, lambda xml: ET.SubElement(xml.findall(".//testcase")[0], "skipped")) - self.write(name, data) - self.rejected() - - def test_external_skip_rejected_even_with_matching_manifest_counts(self): - name = self.external() - data = self.read(name) - self.rewrite_report(data, lambda xml: ET.SubElement(xml.findall(".//testcase")[0], "skipped")) - data["actual_result"] = {"passed": 1, "failed": 0, "skipped": 1} - self.write(name, data) - self.rejected() - - def test_external_claimed_failure_count_mismatch_rejected(self): - name = self.external() - data = self.read(name) - data["actual_result"]["failed"] = 1 - self.write(name, data) - self.rejected() - - def test_external_case_name_mismatch_rejected_despite_valid_hash(self): - name = self.external() - data = self.read(name) - self.rewrite_report(data, lambda xml: xml.findall(".//testcase")[0].set("name", "different_case")) - self.write(name, data) - self.rejected() - - def test_external_run_identity_mismatch_rejected_despite_valid_hash(self): - name = self.external() - data = self.read(name) - self.rewrite_report(data, lambda xml: xml.set("uuid", "different-run")) - self.write(name, data) - self.rejected() - - def test_external_missing_case_rejected_despite_valid_hash(self): - name = self.external() - data = self.read(name) - def remove(xml): - suite = xml.find("testsuite") - suite.remove(suite.find("testcase")) - self.rewrite_report(data, remove) - self.write(name, data) - self.rejected() - - -if __name__ == "__main__": - unittest.main() diff --git a/docs/testing/graph-disk-cleanup-evidence/test_qualification.py b/docs/testing/graph-disk-cleanup-evidence/test_qualification.py deleted file mode 100644 index 4e206288..00000000 --- a/docs/testing/graph-disk-cleanup-evidence/test_qualification.py +++ /dev/null @@ -1,405 +0,0 @@ -"""Protocol regressions. Synthetic archives here never qualify production behavior.""" - -import importlib.util -import json -import os -import subprocess -from pathlib import Path -import sys -import tempfile -import unittest -from unittest.mock import patch - -ROOT = Path(__file__).resolve().parent -SPEC = importlib.util.spec_from_file_location("qualification", ROOT / "qualification.py") -Q = importlib.util.module_from_spec(SPEC) -SPEC.loader.exec_module(Q) - - -class QualificationProtocolTests(unittest.TestCase): - def setUp(self): - self.temporary = tempfile.TemporaryDirectory(prefix="cgraph-s5-qualification-protocol-") - self.addCleanup(self.temporary.cleanup) - self.output = Path(self.temporary.name) - source = {name: "a" * 64 for name in ["Cargo.toml", "Cargo.lock", "build.rs", "src/lib.rs", "src/main.rs", "tests/storage_maintenance.rs"]} - for name in ["source-before.json", "source-after-build.json", "source-after-execution.json"]: - Q.write_json(self.output / name, source) - binaries = {"/synthetic/target/debug/deps/storage_maintenance-test": "b" * 64, - "/synthetic/target/debug/codebase-graph": "c" * 64} - for name in ["binary-before.json", "binary-after.json"]: - Q.write_json(self.output / name, binaries) - metadata = {"rust-build-meta": { - "target-directory": "/synthetic/target", "base-output-directories": ["debug"], - "non-test-binaries": {"synthetic-root": [{"path": "debug/codebase-graph"}]}, - }, "rust-binaries": {Q.BINARY: {"package-id": "synthetic-root", "binary-path": "/synthetic/target/debug/deps/storage_maintenance-test"}}} - Q.write_json(self.output / "binary-list.stdout", metadata) - self.xml = '' + ''.join( - '' % (Q.BINARY, name) for name in sorted(Q.EXPECTED) - ) + '' - (self.output / "junit.xml").write_text(self.xml) - for name in ["binary-list.stderr", "cargo-metadata.stdout", "cargo-metadata.stderr", - "execution.stdout", "execution.stderr", "nextest.toml", "capture-driver.py", "rustc.stdout", - "rustc.stderr", "cargo.stdout", "cargo.stderr"]: - (self.output / name).write_text("synthetic protocol fixture\n") - repository = Path("/synthetic/repo") - package = {"id": "synthetic-root", "name": "codebase-graph", "manifest_path": str(repository / "Cargo.toml")} - Q.write_json(self.output / "cargo-metadata.stdout", {"workspace_root": str(repository), "workspace_members": [package["id"]], "packages": [package]}) - messages, events = [], [] - for name, kind, source, filename in [ - ("codebase_graph", "lib", "src/lib.rs", "/synthetic/target/debug/deps/libcodebase_graph.rlib"), - ("codebase-graph", "bin", "src/main.rs", "/synthetic/target/debug/codebase-graph"), - ("storage_maintenance", "test", "tests/storage_maintenance.rs", "/synthetic/target/debug/deps/storage_maintenance-test"), - ]: - messages.append({"reason": "compiler-artifact", "package_id": package["id"], "fresh": False, - "profile": {"debug_assertions": True}, "target": {"name": name, "kind": [kind], "src_path": str(repository / source)}, "filenames": [filename]}) - events.append({"compiler": "/synthetic/rustc", "manifest": str(repository), "cwd": str(repository), "exit": 0, - "args": ["--crate-name", name.replace("-", "_"), source, "--out-dir", str(Path(filename).parent), "-C", "debug-assertions=on"]}) - (self.output / "root-build.stdout").write_text("".join(json.dumps(m) + "\n" for m in messages)) - for name in ["root-build.stderr", "wrapper-build.stdout", "wrapper-build.stderr", "root-wrapper.exe", "root-rustc.stdout", "root-rustc.stderr"]: - (self.output / name).write_text("synthetic protocol fixture\n") - (self.output / "root-wrapper.rs").write_text(Q.WRAPPER_SOURCE) - outputs = {m["filenames"][0]: {"compiler_output": m["filenames"][0], "sha256": binaries.get(m["filenames"][0], "d" * 64)} for m in messages} - Q.write_json(self.output / "compile-proof.json", {"events": events, "cargo_messages": messages, "outputs": outputs}) - Q.write_json(self.output / "build-environment.json", { - "RUSTC_WORKSPACE_WRAPPER": str(self.output / "root-wrapper.exe"), "CGRAPH_QUALIFICATION_EVENTS": str(self.output / "rustc-events"), - "CARGO_PROFILE_TEST_DEBUG_ASSERTIONS": "true", "CARGO_PROFILE_DEV_DEBUG_ASSERTIONS": "true"}) - Q.write_json(self.output / "inventory.stdout", { - "rust-build-meta": metadata["rust-build-meta"], "test-count": len(Q.EXPECTED), - "rust-suites": {Q.BINARY: {"binary-path": "/synthetic/target/debug/deps/storage_maintenance-test", "package-id": "synthetic-root", - "testcases": {name: {"ignored": False, "filter-match": {"status": "matches"}} for name in Q.EXPECTED}}}}) - (self.output / "inventory.stderr").write_text("synthetic protocol inventory\n") - self.record = { - "schema": Q.SCHEMA, "scope": "storage_maintenance_suite", "output": str(self.output), "repository": str(repository), - "compile_proof": {"events": events, "cargo_messages": messages, "outputs": outputs}, - "expected_case_names": sorted(Q.EXPECTED), - "qualification": {"aggregate": "pending", "windows": "pending", "linux": "pending"}, - "limitations": Q.LIMITATIONS, - "commands": {label: {"argv": argv, "exit_code": 0} for label, argv in [ - ("rustc", ["rustc", "-vV"]), ("cargo", ["cargo", "-V"]), - ("cargo-metadata", ["cargo", "metadata", "--offline", "--locked", "--format-version", "1"]), - ("binary-list", ["cargo", "nextest", "list", "--offline", "--locked", "--test", "storage_maintenance", "--list-type", "binaries-only", "--message-format", "json"]), - ("wrapper-build", ["rustc", "--edition=2021", str(self.output / "root-wrapper.rs"), "-o", str(self.output / "root-wrapper.exe")]), - ("root-build", Q.root_build_command()), - ("root-rustc", ["/synthetic/rustc", "-vV"]), - ("inventory", Q.inventory_command(self.output)), - ("execution", Q.execution_command(self.output)), - ]}, - "result": Q.junit_result(self.output / "junit.xml"), - } - self.seal() - - def seal(self): - self.record["artifacts"] = {p.name: Q.sha256(p) for p in self.output.iterdir() if p.name != "capture.json"} - Q.write_json(self.output / "capture.json", self.record) - - def rejected(self, reason): - with self.assertRaisesRegex(ValueError, reason): - Q.verify(self.output) - - def test_protocol_fixture_is_valid_without_production_qualification(self): - self.assertEqual(Q.verify(self.output)["qualification"]["aggregate"], "pending") - - def test_missing_and_changed_artifacts_rejected(self): - (self.output / "execution.stderr").write_text("changed") - self.rejected("artifact changed") - - def test_path_traversal_rejected(self): - self.record["artifacts"]["../outside"] = "a" * 64 - Q.write_json(self.output / "capture.json", self.record) - self.rejected("escapes archive") - - def test_source_change_even_with_rehashed_artifact_rejected(self): - Q.write_json(self.output / "source-after-build.json", {"Cargo.toml": "d" * 64}) - self.seal() - self.rejected("source changed") - - def test_source_addition_and_deletion_invalidate_snapshot(self): - for name in ["Cargo.toml", "Cargo.lock", "build.rs"]: - (self.output / name).write_text("fixture") - with patch.object(Q.subprocess, "check_output", return_value=b"Cargo.toml\0Cargo.lock\0build.rs\0"): - original = Q.source_manifest(self.output) - (self.output / "new.rs").write_text("new") - with patch.object(Q.subprocess, "check_output", return_value=b"Cargo.toml\0Cargo.lock\0build.rs\0src/new.rs\0"): - (self.output / "src").mkdir() - (self.output / "src/new.rs").write_text("new") - self.assertNotEqual(Q.source_manifest(self.output), original) - (self.output / "Cargo.lock").unlink() - with patch.object(Q.subprocess, "check_output", return_value=b"Cargo.toml\0Cargo.lock\0build.rs\0"): - with self.assertRaises(FileNotFoundError): - Q.source_manifest(self.output) - - def test_changed_binary_rejected_even_with_rehashed_artifact(self): - Q.write_json(self.output / "binary-after.json", {"/synthetic/other": "d" * 64}) - self.seal() - self.rejected("binary identity") - - def test_unrecorded_helper_binary_rejected(self): - for name in ["binary-before.json", "binary-after.json"]: - data = Q.read_json(self.output / name) - del data["/synthetic/target/debug/codebase-graph"] - Q.write_json(self.output / name, data) - self.seal() - self.rejected("binary identity") - - def test_release_build_cannot_qualify_debug_capture(self): - metadata = Q.read_json(self.output / "binary-list.stdout") - metadata["rust-build-meta"]["base-output-directories"] = ["release"] - Q.write_json(self.output / "binary-list.stdout", metadata) - self.seal() - self.rejected("debug build") - - def test_failed_build_or_execution_rejected(self): - for label in ["binary-list", "execution"]: - self.record["commands"][label]["exit_code"] = 101 - self.seal() - self.rejected("command failed") - self.record["commands"][label]["exit_code"] = 0 - - def test_filtered_or_rebuilt_execution_rejected(self): - self.record["commands"]["execution"]["argv"] += ["-E", "test(nothing)"] - self.seal() - self.rejected("execution command") - - def test_skip_failure_error_and_retry_are_not_passes(self): - for tag in ["skipped", "failure", "error", "rerunFailure", "rerunError"]: - (self.output / "junit.xml").write_text(self.xml.replace('/>', '><%s/>' % tag, 1)) - self.seal() - self.rejected("first execution") - - def test_missing_extra_and_duplicate_cases_rejected(self): - for change in [self.xml.replace('name="health_preserves', 'name="different_health_preserves'), - self.xml.replace('', ''), - self.xml.replace('', '' % (Q.BINARY, sorted(Q.EXPECTED)[0]))]: - (self.output / "junit.xml").write_text(change) - self.seal() - self.rejected("testcase") - - def test_result_and_run_identity_mismatch_rejected(self): - self.record["result"]["run_id"] = "another-run" - self.seal() - self.rejected("identity mismatch") - - def test_platform_or_aggregate_promotion_rejected(self): - for gate in ["aggregate", "windows", "linux"]: - self.record["qualification"][gate] = "passed" - self.seal() - self.rejected("qualification gates") - self.record["qualification"][gate] = "pending" - - def test_capture_refuses_existing_output_before_any_build(self): - with self.assertRaises(FileExistsError): - Q.capture(ROOT, self.output) - - def mutate_proof(self, mutate): - proof = Q.read_json(self.output / "compile-proof.json") - mutate(proof) - Q.write_json(self.output / "compile-proof.json", proof) - self.record["compile_proof"] = proof - (self.output / "root-build.stdout").write_text("".join(json.dumps(m) + "\n" for m in proof["cargo_messages"])) - self.seal() - - def test_cached_root_artifact_is_rejected(self): - self.mutate_proof(lambda proof: proof["cargo_messages"][0].update(fresh=True)) - self.rejected("reused from cache") - - def test_assertion_disabled_root_profile_is_rejected(self): - self.mutate_proof(lambda proof: proof["cargo_messages"][0]["profile"].update(debug_assertions=False)) - self.rejected("profile disables") - - def test_missing_actual_root_compiler_invocation_is_rejected(self): - self.mutate_proof(lambda proof: proof.update(events=[])) - self.rejected("actual root compiler identity|actual rustc invocation") - - def test_assertion_disabled_actual_compiler_is_rejected(self): - self.mutate_proof(lambda proof: proof["events"][0]["args"].__setitem__(-1, "debug-assertions=off")) - self.rejected("compiler assertions") - - def test_wrong_workspace_and_package_are_rejected(self): - data = Q.read_json(self.output / "cargo-metadata.stdout") - data["workspace_root"] = "/another/repository" - Q.write_json(self.output / "cargo-metadata.stdout", data) - self.seal() - self.rejected("wrong Cargo workspace") - - def test_ambient_rustflags_are_rejected_before_compilation(self): - with patch.dict(os.environ, {"RUSTFLAGS": "-C debug-assertions=off"}): - with self.assertRaisesRegex(ValueError, "unsupported ambient compiler override"): - Q.build_environment(self.output) - - def test_current_suite_inventory_cannot_hide_filtered_or_ignored_cases(self): - original = Q.read_json(self.output / "inventory.stdout") - for field in ["ignored", "filter-match"]: - inventory = json.loads(json.dumps(original)) - case = next(iter(inventory["rust-suites"][Q.BINARY]["testcases"].values())) - case[field] = True if field == "ignored" else {"status": "mismatch"} - Q.write_json(self.output / "inventory.stdout", inventory) - self.seal() - self.rejected("ignored or filtered") - - def test_added_registered_case_must_execute_in_junit(self): - inventory = Q.read_json(self.output / "inventory.stdout") - inventory["test-count"] += 1 - inventory["rust-suites"][Q.BINARY]["testcases"]["new_real_case"] = {"ignored": False, "filter-match": {"status": "matches"}} - Q.write_json(self.output / "inventory.stdout", inventory) - self.record["expected_case_names"].append("new_real_case") - self.record["expected_case_names"].sort() - self.seal() - self.rejected("testcase") - - def test_source_workload_cannot_pass_only_the_three_historical_cases(self): - for name in ["source-before.json", "source-after-build.json", "source-after-execution.json"]: - source = Q.read_json(self.output / name) - source["tests/support/storage_maintenance/workload.rs"] = "e" * 64 - Q.write_json(self.output / name, source) - self.seal() - self.rejected("required fixture test cases") - - def test_inventory_binary_package_and_named_identity_must_match(self): - inventory = Q.read_json(self.output / "inventory.stdout") - inventory["rust-suites"][Q.BINARY]["package-id"] = "foreign" - Q.write_json(self.output / "inventory.stdout", inventory) - self.seal() - self.rejected("binary/package identity") - - def test_source_bound_eight_and_nine_case_inventories_are_accepted_exactly(self): - names = set(Q.EXPECTED) | Q.WORKLOAD_CASES - for include_process in [False, True]: - if include_process: - names.add(Q.PROCESS_CASE) - for name in ["source-before.json", "source-after-build.json", "source-after-execution.json"]: - source = Q.read_json(self.output / name) - source["tests/support/storage_maintenance/workload.rs"] = "e" * 64 - if include_process: - source["tests/support/storage_maintenance/process.rs"] = "f" * 64 - Q.write_json(self.output / name, source) - inventory = Q.read_json(self.output / "inventory.stdout") - inventory["test-count"] = len(names) - inventory["rust-suites"][Q.BINARY]["testcases"] = {name: {"ignored": False, "filter-match": {"status": "matches"}} for name in names} - Q.write_json(self.output / "inventory.stdout", inventory) - xml = '' + ''.join('' % (Q.BINARY, name) for name in sorted(names)) + '' - (self.output / "junit.xml").write_text(xml) - self.record["expected_case_names"] = sorted(names) - self.record["result"] = Q.junit_result(self.output / "junit.xml", names) - self.seal() - self.assertEqual(Q.verify(self.output)["result"]["passed"], 9 if include_process else 8) - - def test_historical_schema_two_keeps_its_exact_three_case_scope(self): - self.record["schema"] = 2 - self.record["scope"] = "narrow_external_contract" - self.record["limitations"] = Q.LEGACY_LIMITATIONS - del self.record["commands"]["inventory"] - for name in ["inventory.stdout", "inventory.stderr"]: - (self.output / name).unlink() - self.seal() - historical = Q.verify(self.output) - self.assertEqual(historical["result"]["passed"], 3) - self.assertEqual(historical["schema"], 2) - self.assertEqual(historical["scope"], "narrow_external_contract") - - def test_real_subprocess_exit_and_output_are_captured(self): - result = Q.run([sys.executable, "-c", "import sys; print('actual output'); sys.stderr.write('actual error'); sys.exit(7)"], self.output, self.output, "probe") - self.assertEqual(result["exit_code"], 7) - self.assertEqual((self.output / "probe.stdout").read_text(), "actual output\n") - self.assertEqual((self.output / "probe.stderr").read_text(), "actual error") - - -class QualificationRuntimeTests(unittest.TestCase): - """Real Cargo/nextest counterexamples, using only a tiny fixture-owned target. - - These run normally with this module. --protocol-only is available while the - coordinator's Cargo lease belongs to another slice; it is not qualification. - """ - def setUp(self): - evidence_root = os.environ.get("CGRAPH_QUALIFICATION_RUNTIME_OUTPUT") - if evidence_root: - self.base = Path(tempfile.mkdtemp(prefix=self._testMethodName + "-", dir=evidence_root)).resolve() - print("Real provenance regression archive: " + str(self.base), file=sys.stderr) - else: - self.temporary = tempfile.TemporaryDirectory(prefix="cgraph-s5-real-provenance-") - self.addCleanup(self.temporary.cleanup) - self.base = Path(self.temporary.name).resolve() - self.repository = self.base / "repo" - self.repository.mkdir() - (self.repository / "src").mkdir() - (self.repository / "tests").mkdir() - (self.repository / "Cargo.toml").write_text('[package]\nname="codebase-graph"\nversion="0.0.0"\nedition="2021"\n') - (self.repository / "build.rs").write_text("fn main() {}\n") - (self.repository / "src/lib.rs").write_text("pub fn assertions_enabled() -> bool { cfg!(debug_assertions) }\n") - (self.repository / "src/main.rs").write_text("fn main() { assert!(cfg!(debug_assertions)); }\n") - self.test_source = self.repository / "tests/storage_maintenance.rs" - self.write_assertions("assert!(true)") - environment = os.environ.copy() - environment["CARGO_TARGET_DIR"] = str(self.base / "target") - for name in ["RUSTFLAGS", "CARGO_ENCODED_RUSTFLAGS", "RUSTC", "RUSTC_WRAPPER", "RUSTC_WORKSPACE_WRAPPER"]: - environment.pop(name, None) - self.environment = environment - for command in [ - ["git", "init", "--quiet"], ["cargo", "generate-lockfile", "--offline"], - ["git", "add", "."], ["git", "-c", "user.name=Qualification fixture", "-c", "user.email=fixture@example.invalid", "commit", "--quiet", "-m", "test: isolated fixture"], - ]: - subprocess.run(command, cwd=self.repository, env=environment, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - - def write_assertions(self, assertion): - self.test_source.write_text("".join( - '#[test]\nfn %s() { %s; assert!(codebase_graph::assertions_enabled()); assert!(std::process::Command::new(env!("CARGO_BIN_EXE_codebase-graph")).status().unwrap().success()); }\n' % (name, assertion) - for name in sorted(Q.EXPECTED))) - - def capture(self, name): - with patch.dict(os.environ, self.environment, clear=True): - return Q.capture(self.repository, self.base / name) - - def test_mtime_preserved_failing_source_is_recompiled_and_fails(self): - original = self.capture("before") - old_stat = self.test_source.stat() - self.write_assertions("assert!(false)") - os.utime(self.test_source, ns=(old_stat.st_atime_ns, old_stat.st_mtime_ns)) - failing_hash = Q.sha256(self.test_source) - with self.assertRaisesRegex(ValueError, "execution failed"): - self.capture("after") - self.assertEqual(Q.sha256(self.test_source), failing_hash) - self.assertEqual(self.test_source.stat().st_mtime_ns, old_stat.st_mtime_ns) - proof = Q.read_json(self.base / "after/compile-proof.json") - root = [m for m in proof["cargo_messages"] if m.get("reason") == "compiler-artifact" and "custom-build" not in m["target"]["kind"]] - self.assertTrue(root and all(m["fresh"] is False for m in root)) - self.assertNotEqual(Q.read_json(self.base / "before/source-before.json"), Q.read_json(self.base / "after/source-before.json")) - self.assertEqual(original["result"]["passed"], 3) - Q.write_json(self.base / "runtime-result.json", { - "scenario": "mtime-preserved changed assertion", "baseline_result": original["result"], - "failing_source_sha256": failing_hash, "mtime_before_ns": old_stat.st_mtime_ns, - "mtime_after_ns": self.test_source.stat().st_mtime_ns, - "changed_source_was_recompiled": all(m["fresh"] is False for m in root), - "changed_source_capture_rejected": True, - "actual_failed_junit_sha256": Q.sha256(self.base / "after/junit.xml"), - }) - - def test_assertion_disabled_environment_and_config_cannot_pass_disabled_tests(self): - self.environment["CARGO_PROFILE_TEST_DEBUG_ASSERTIONS"] = "false" - self.environment["CARGO_PROFILE_DEV_DEBUG_ASSERTIONS"] = "false" - config = self.repository / ".cargo" - config.mkdir() - (config / "config.toml").write_text('[build]\nrustflags = ["-C", "debug-assertions=off"]\n') - # Enabled-sensitive library, helper and all tests pass despite ambient off. - enabled = self.capture("enabled") - self.assertEqual(enabled["result"]["passed"], 3) - self.write_assertions("assert!(!cfg!(debug_assertions))") - with self.assertRaisesRegex(ValueError, "execution failed"): - self.capture("disabled-counterexample") - proof = Q.read_json(self.base / "enabled/compile-proof.json") - self.assertTrue(any("debug-assertions=off" in e["args"] for e in proof["events"])) - root_events = [e for e in proof["events"] if "--crate-name" in e["args"]] - self.assertTrue(root_events and all(e["args"][-2:] == ["-C", "debug-assertions=on"] for e in root_events)) - Q.write_json(self.base / "runtime-result.json", { - "scenario": "assertion-disabled environment and Cargo config", "enabled_result": enabled["result"], - "ambient_test_debug_assertions": "false", "ambient_dev_debug_assertions": "false", - "config_rustflags": ["-C", "debug-assertions=off"], "actual_root_compilers_end_assertions_on": True, - "assertion_disabled_test_capture_rejected": True, - "actual_failed_junit_sha256": Q.sha256(self.base / "disabled-counterexample/junit.xml"), - }) - - -if __name__ == "__main__": - if "--protocol-only" in sys.argv: - sys.argv.remove("--protocol-only") - suite = unittest.defaultTestLoader.loadTestsFromTestCase(QualificationProtocolTests) - sys.exit(not unittest.TextTestRunner(verbosity=2).run(suite).wasSuccessful()) - unittest.main() diff --git a/docs/testing/release-gate-40453997235/local-checks.log b/docs/testing/release-gate-40453997235/local-checks.log new file mode 100644 index 00000000..e27772fc --- /dev/null +++ b/docs/testing/release-gate-40453997235/local-checks.log @@ -0,0 +1,95 @@ + Compiling xtask v4.0.0 (/private/tmp/codebasegraph-fix-release-python-40453997235/crates/xtask) + Finished `test` profile [unoptimized + debuginfo] target(s) in 0.87s + Running unittests src/main.rs (target/debug/deps/xtask-3ae50130c6c7ac02) + +running 43 tests +test native_tests::shards::tests::empty_shard_never_runs_an_unfiltered_suite ... ok +test native_tests::shards::tests::selected_exact_filters_execute_the_expected_real_harness_cases ... ok +test native_tests::shards::tests::shards_cover_every_listed_case_once_and_bound_windows_arguments ... ok +test native_tests::tests::captured_cli_survives_cargo_replacing_its_output ... ok +test native_tests::tests::partitions_every_test_target_and_keeps_new_integration_targets ... ok +test native_tests::tests::windows_regressions_precede_the_complete_unfiltered_suite ... ok +test release::tests::metadata_rejects_inconsistent_versions ... ok +test release::tests::notes_only_include_the_exact_release ... ok +test release::tests::notes_reject_missing_duplicate_and_empty_sections ... ok +test release::workflow_tests::ci_run_recovery_dry_run_validates_without_authorizing_publication ... ok +test release::workflow_tests::ordinary_commit_cannot_authorize_release_and_tagged_retry_still_can ... ok +test release::workflow_tests::proposal_reports_pending_release_then_proceeds_after_finalization ... ok +test release::workflow_tests::publisher_rejects_conflicting_tags_and_does_not_regress_latest ... ok +test release::workflow_tests::publisher_targets_original_sha_and_is_idempotent ... ok +test release::workflow_tests::recovery_requires_one_target_and_forbids_ci_run_rebuilds ... ok +test release::workflow_tests::verified_release_rejects_failed_ci_and_untrusted_run_identity ... ok +test release::workflow_tests::verified_release_rejects_untrusted_prs_and_removed_history ... ok +test release::workflow_tests::verified_release_survives_main_advancing_and_ignores_other_pending_releases ... ok +test tests::archive_contract_rejects_unexpected_entries ... ok +test tests::complete_artifact_set_rejects_mixed_provenance ... ok +test tests::crate_size_validation_enforces_the_registry_limit ... ok +test tests::installer_rejects_removed_hosts_before_reading_archives_or_writing_destinations ... ok +test tests::native_capture_requires_unique_source_bound_normal_cli_artifact ... ok +test tests::native_capture_reuses_the_exact_test_profile_and_features ... ok +test tests::native_target_contract_maps_names_and_binaries ... ok +test tests::native_test_failure_cannot_authorize_artifact_production ... ok +test tests::release_identity_validation_is_strict ... ok +test tests::windows_native_tests_forward_bundled_extensions ... ok +test tests::workflow_policy_rejects_automatic_rebuild_recovery ... ok +test tests::workflow_policy_rejects_direct_release_push_trigger ... ok +test tests::workflow_policy_rejects_github_sha_for_release_identity ... ok +test tests::workflow_policy_rejects_missing_ci_success_guard ... ok +test tests::workflow_policy_rejects_missing_crate_publish_retry ... ok +test tests::workflow_policy_rejects_missing_crate_size_gates ... ok +test tests::workflow_policy_rejects_missing_repository_release_gate ... ok +test tests::workflow_policy_rejects_missing_triggering_run_binding ... ok +test tests::workflow_policy_rejects_release_queue_replacement ... ok +test tests::workflow_policy_rejects_unconditional_release_publication ... ok +test tests::workflow_policy_rejects_unvalidated_publishers ... ok +test tests::workflow_policy_requires_ci_completion_release_trigger ... ok +test tests::workflow_policy_requires_publication_revalidation ... ok +test tests::workflow_yaml_validation_rejects_unindented_heredoc_body ... ok +test tests::workflows_reject_removed_platform_targets ... ok + +test result: ok. 43 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 7.02s + + Checking typenum v1.20.1 + Checking cfg-if v1.0.4 + Checking libc v0.2.189 + Checking bitflags v2.13.0 + Checking serde_core v1.0.228 + Checking itoa v1.0.18 + Checking equivalent v1.0.2 + Checking hashbrown v0.15.5 + Checking adler2 v2.0.1 + Checking simd-adler32 v0.3.10 + Checking crc32fast v1.5.0 + Checking zmij v1.0.21 + Checking libyaml-rs v0.3.0 + Checking ryu v1.0.23 + Checking memchr v2.8.2 + Checking miniz_oxide v0.9.1 + Checking flate2 v1.1.10 + Checking errno v0.3.14 + Checking filetime v0.2.29 + Checking cpufeatures v0.2.17 + Checking indexmap v2.7.1 + Checking rustix v1.1.4 + Checking generic-array v0.14.7 + Checking block-buffer v0.10.4 + Checking crypto-common v0.1.7 + Checking digest v0.10.7 + Checking sha2 v0.10.9 + Checking xattr v1.6.1 + Checking tar v0.4.46 + Checking serde v1.0.228 + Checking serde_json v1.0.150 + Checking yaml_serde v0.10.7 + Checking xtask v4.0.0 (/private/tmp/codebasegraph-fix-release-python-40453997235/crates/xtask) + Finished `dev` profile [unoptimized + debuginfo] target(s) in 2.50s + Compiling xtask v4.0.0 (/private/tmp/codebasegraph-fix-release-python-40453997235/crates/xtask) + Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.68s + Running `target/debug/xtask check-workflows` +workflow policy passed + Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.05s + Running `target/debug/xtask release-gate` +release gate passed + Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.06s + Running `target/debug/xtask release-gate --production --confirm release-environment --confirm private-vulnerability-reporting` +release gate passed diff --git a/docs/testing/release-gate-40453997235/nextest.toml b/docs/testing/release-gate-40453997235/nextest.toml new file mode 100644 index 00000000..dfda1d6a --- /dev/null +++ b/docs/testing/release-gate-40453997235/nextest.toml @@ -0,0 +1,2 @@ +[profile.cod99] +junit.path = "cod99-junit.xml" diff --git a/docs/testing/release-gate-40453997235/policy-failure-probes.log b/docs/testing/release-gate-40453997235/policy-failure-probes.log new file mode 100644 index 00000000..b4d7c08a --- /dev/null +++ b/docs/testing/release-gate-40453997235/policy-failure-probes.log @@ -0,0 +1,8 @@ +Python source probe (expected exit 1): +FAIL: python-file-present: docs/testing/graph-disk-cleanup-evidence/cod99-policy-probe.py must not be maintained source. +release gate failed + +Missing production confirmations (expected exit 1): +FAIL: external-confirmation-missing: production release requires --confirm release-environment. +FAIL: external-confirmation-missing: production release requires --confirm private-vulnerability-reporting. +release gate failed diff --git a/docs/testing/release-gate-40453997235/review.md b/docs/testing/release-gate-40453997235/review.md new file mode 100644 index 00000000..64f69583 --- /dev/null +++ b/docs/testing/release-gate-40453997235/review.md @@ -0,0 +1,54 @@ +# COD-99 read-only review + +**Result: no confirmed defects in the reviewed staged change.** + +- Reviewer: `openai-codex/gpt-6.1-sol`, `thinking="max"` (parent-confirmed actual spawn settings); fresh child `archival-ci-review`, `sub-4f7bb63f`. +- Ordinary small-CI/evidence-layout review; not a claimed large-implementation or adversarial-skill gate. +- Worktree: `/private/tmp/codebasegraph-fix-release-python-40453997235`, branch `fix/release-python-evidence-gate`. +- Baseline/HEAD: `7038e66ff8271ffddc511f879cf8d28353a09b2d`. +- Reviewed all 14 staged paths. No unstaged changes or reviewer implementation edits. No commits, integration mutations, external posts, dispatches or publication actions. + +## Confirmed scope and behavior + +1. The archive contains exactly four regular helper files plus their three parent directories. Paths are relative and confined to the original documentation subtree; no traversal, absolute paths, links, duplicate members or special files. Each member is byte-for-byte identical to a fresh `git archive` of the baseline and matches the manifest's original Git blob ID, SHA-256 and size. Historical Python bodies were not read or executed. +2. `historical-python-helpers.tar.gz` is staged despite the generic ignore rule: mode `100644`, blob `05ec406ef509a1bfd58673f67333a434f92c1ecc`, 15,810 bytes. All ten staged nondeleted file contents match their working files and the hashes below. Four loose `.py` files are deleted as intended. +3. `check_rust_only_files` is unchanged from the baseline. The new CI step runs the existing non-production gate before `cargo publish --dry-run --locked`, remains mandatory through the existing `required` dependency, and needs no production confirmation. The policy check and removal regression detect a missing gate command. Runtime, package manifests, release workflow, version, labels and release identity contracts are unchanged. +4. Existing historical reports, source manifests, test results/hashes and cleanup qualification claims are unchanged. The README correctly describes archival only, retains the canonical DC-S1–DC-S5 contract, and does not upgrade partial historical evidence to present qualification. +5. The original Release job remains bound to immutable source `7038e66`; rerunning it cannot apply this later fix. Replacement release/version selection, PR #156 replacement, tags/labels and publication remain outside the archival approval. + +## Executed and inspected checks + +Independently executed, all successful unless noted: + +- `cargo fmt --check`. +- `cargo test -p xtask --locked`: **43 passed, 0 failed, 0 ignored**. +- `cargo clippy -p xtask --all-targets --locked -- -D warnings`: passed without warnings. +- `cargo run -p xtask --locked -- check-workflows`. +- `cargo run -p xtask --locked -- release-gate`. +- `target/debug/xtask release-gate --production`: expected exit 1, rejecting both missing production confirmations. +- `git diff --cached --check`; index-vs-working-content verification; binary archive-vs-Git comparison and member safety checks. + +Inspected coordinator evidence under `docs/testing/release-gate-40453997235/`: both positive gate modes and Python reintroduction rejection, plus nextest JUnit run `caa02f03-1b42-4b8f-94c0-6b9fb63dedcf`. Parsed JUnit confirms **43 test cases, 0 failures/errors/skips**, and its digest matches `verification.json`. The baseline failure and source reintroduction probe were not independently reenacted by this read-only reviewer. + +## Exact reviewed SHA-256 snapshot + +Core files match `/private/tmp/codebasegraph-cod99-verification/review-snapshot.json`: + +- `.github/workflows/ci.yml`: `a2cc1e8eb18dee65eaa72f9fdfbfd7a9df23f3a071a4faa935e124a888c747bb` +- `crates/xtask/src/main.rs`: `70507a624361a97a7bcdb28412b31112258fcb023fa8e80dccd943b113b335ad` +- `docs/release.md`: `8c813f005f68e62e8dfeca0d4cdf8054a3802efcf9311ca865f9bdbbab508ecf` +- `docs/testing/graph-disk-cleanup-evidence/README.md`: `1c7212d00b8793b00a7672482bb77fee508fac8bbe80f0ee305eec12f4c3a7bc` +- `docs/testing/graph-disk-cleanup-evidence/historical-python-helpers.json`: `b9b750c6f8500064b9942d9d4fa413c949f4ca8075bb0e1564fd9aab2ecdfcbb` +- `docs/testing/graph-disk-cleanup-evidence/historical-python-helpers.tar.gz`: `032ac4ef9cfddf40dc0540a913f71c34de4901a4a7d61d035141cb96832e7519` +- `docs/testing/release-gate-40453997235/local-checks.log`: `a214c81efa0a33b1afa8cc75198b36ef9f7e2614b74209768b00e2566210105d` +- `docs/testing/release-gate-40453997235/policy-failure-probes.log`: `da54c62abec905f698741e1f82fed59a8106a0ed18ee24e47b4cc5782af4159e` +- `docs/testing/release-gate-40453997235/verification.json`: `aeb56c95afa98dd77c744cb285938bab6fecda0a7cb1d6a637c8d95985744889` +- `docs/testing/release-gate-40453997235/xtask.junit.xml`: `551ec04ad88fcf4885fa2b375a7a2c4eab8eba74dab28032b579dcab68ec4c18` + +## Limits and remaining gates + +- Local Darwin/aarch64 xtask scope only. Full product/native-platform matrix and package dry-run were not run locally; PR CI remains pending. Historical helper tests were not rerun, and the archive does not contain every historical environment prerequisite. +- Production flags reproduce the failed job's arguments; they do not independently verify owner-controlled external environment settings or authorize publishing. +- Graph and Scryer were consulted read-only against the older original checkout for orientation. The shared Scryer model is not configured for this implementation worktree. No JUnit ingestion, source/model drift reconciliation, seeding or copying was performed against mismatched source. Existing `resp-14`/`resp-15` responsibilities remain unchanged; model evidence synchronization is not claimed. +- Approval assumption: the parent's quoted user decision, “i approve archiving the python helpers”, governs this lossless archive; the requested CI failure repair covers the preventive existing-gate invocation. No broader release action is inferred. +- No constants changed. No new reusable memory candidate: this review confirms existing immutable-release guidance and directly rediscoverable snapshot facts, not a new durable procedure. diff --git a/docs/testing/release-gate-40453997235/verification.json b/docs/testing/release-gate-40453997235/verification.json new file mode 100644 index 00000000..054bd6e8 --- /dev/null +++ b/docs/testing/release-gate-40453997235/verification.json @@ -0,0 +1,116 @@ +{ + "work_item": "COD-99", + "baseline_commit": "7038e66ff8271ffddc511f879cf8d28353a09b2d", + "approval": { + "date": "2026-10-27", + "user_decision": "i approve archiving the python helpers", + "scope": "Archive four historical Python helpers losslessly. No release replacement or publication approval." + }, + "scope": "Historical evidence archival and required CI repository policy prevention; no architecture, source-policy exemption, runtime or package-payload changes.", + "environment": { + "platform": "Darwin aarch64", + "rustc": "1.96.0 (ac68faa20 2026-05-25)", + "cargo": "1.96.0 (30a34c682 2026-05-25)", + "cargo_nextest": "0.9.140" + }, + "snapshot_sha256": { + ".github/workflows/ci.yml": "a2cc1e8eb18dee65eaa72f9fdfbfd7a9df23f3a071a4faa935e124a888c747bb", + "crates/xtask/src/main.rs": "70507a624361a97a7bcdb28412b31112258fcb023fa8e80dccd943b113b335ad", + "docs/release.md": "8c813f005f68e62e8dfeca0d4cdf8054a3802efcf9311ca865f9bdbbab508ecf", + "docs/testing/graph-disk-cleanup-evidence/README.md": "1c7212d00b8793b00a7672482bb77fee508fac8bbe80f0ee305eec12f4c3a7bc", + "docs/testing/graph-disk-cleanup-evidence/historical-python-helpers.json": "b9b750c6f8500064b9942d9d4fa413c949f4ca8075bb0e1564fd9aab2ecdfcbb", + "docs/testing/graph-disk-cleanup-evidence/historical-python-helpers.tar.gz": "032ac4ef9cfddf40dc0540a913f71c34de4901a4a7d61d035141cb96832e7519" + }, + "archive_integrity": { + "sha256": "032ac4ef9cfddf40dc0540a913f71c34de4901a4a7d61d035141cb96832e7519", + "repeated_archive_byte_identical": true, + "files_checked": 4, + "source_bytes_checked_before_removal": true, + "no_extra_regular_members": true, + "safe_paths_and_types": true, + "extracted_hashes": [ + { + "path": "docs/testing/graph-disk-cleanup-evidence/audit.py", + "sha256": "316630c38e1638370b8c1873174d4311cc32ae4d0d9bcbbf0045e1083ccfb518", + "matched": true + }, + { + "path": "docs/testing/graph-disk-cleanup-evidence/qualification.py", + "sha256": "264753ae10581691d0dc1c7591acab9601b29ff7c9f95df8d04ed56b56509e3b", + "matched": true + }, + { + "path": "docs/testing/graph-disk-cleanup-evidence/test_audit.py", + "sha256": "b317ac2a5f4ab7ec3c52e98169e37090d65c9a7a92d397bd898962516f6f545c", + "matched": true + }, + { + "path": "docs/testing/graph-disk-cleanup-evidence/test_qualification.py", + "sha256": "4fa5adbdff51ec53085ae307444ca4800a557726b95e18cacfc42091c0c57ef3", + "matched": true + } + ] + }, + "checks": [ + { + "command": "cargo fmt --check", + "result": "passed" + }, + { + "command": "cargo test -p xtask --locked", + "result": "passed", + "tests": 43, + "failed": 0, + "ignored": 0 + }, + { + "command": "cargo clippy -p xtask --all-targets --locked -- -D warnings", + "result": "passed" + }, + { + "command": "cargo run -p xtask --locked -- check-workflows", + "result": "passed" + }, + { + "command": "cargo run -p xtask --locked -- release-gate", + "result": "passed" + }, + { + "command": "cargo run -p xtask --locked -- release-gate --production --confirm release-environment --confirm private-vulnerability-reporting", + "result": "passed", + "note": "Reproduces failed-job confirmation arguments; not new owner-controlled environment verification." + }, + { + "command": "cargo nextest run -p xtask --locked --config-file /private/tmp/codebasegraph-cod99-verification/nextest.toml --profile cod99", + "result": "passed", + "run_id": "caa02f03-1b42-4b8f-94c0-6b9fb63dedcf", + "tests": 43, + "failed": 0, + "skipped": 0, + "junit_path": "xtask.junit.xml", + "junit_sha256": "551ec04ad88fcf4885fa2b375a7a2c4eab8eba74dab28032b579dcab68ec4c18" + }, + { + "method": "Run target/debug/xtask release-gate with an empty loose Python source probe in the same evidence directory, then remove the probe", + "result": "expected failure: python-file-present", + "exit_code": 1 + }, + { + "command": "target/debug/xtask release-gate --production", + "result": "expected failure: both production confirmations missing", + "exit_code": 1 + } + ], + "limits": [ + "Historical helper tests were not rerun; exact bytes and extraction verified.", + "Full product/native platform matrix and package dry-run not executed locally; pending PR CI.", + "Existing failed job remains bound to immutable 7038e66. Corrected release replacement/version/publication require a separate decision.", + "Shared Scryer model is configured for the unrelated original worktree; no whole-project drift reconciliation or verdict activation against mismatched source." + ], + "scryer_change": "chg-cxz5az", + "scryer_existing_claims": [ + "resp-14", + "resp-15" + ], + "architecture_claims_changed": false +} diff --git a/docs/testing/release-gate-40453997235/xtask.junit.xml b/docs/testing/release-gate-40453997235/xtask.junit.xml new file mode 100644 index 00000000..ade658cf --- /dev/null +++ b/docs/testing/release-gate-40453997235/xtask.junit.xml @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +