From 9588c2ef1e4d5250adf57c2df1239d6d416d6b92 Mon Sep 17 00:00:00 2001 From: SSobol77 Date: Fri, 10 Jul 2026 19:26:36 +0200 Subject: [PATCH] ci: wire Linux evidence drift into release gate --- Makefile | 8 +- docs/release/artifact-contract.md | 1 + docs/release/packaging-flows.md | 7 ++ docs/release/release-checklist.md | 9 ++ .../test_f4_linter_linux_provisioning.py | 95 +++++++++++++++++++ 5 files changed, 119 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 5ea4590..4d91b76 100755 --- a/Makefile +++ b/Makefile @@ -160,6 +160,7 @@ help-full: help @echo " make validate-packaging Packaging contract suite" @echo " make validate-release-contract Release/package matrix contract checks" @echo " make validate-release-assets Exact 21 GitHub Release asset gate" + @echo " make validate-official-evidence-drift" @echo " make validate-version-consistency" @echo " make validate-runtime-imports" @echo " make validate-pypi-contract" @@ -1166,6 +1167,11 @@ validate-runtime-imports: @$(PYTHON) scripts/check_runtime_imports.py @echo "--> OK: production runtime imports" +.PHONY: validate-official-evidence-drift +validate-official-evidence-drift: + @$(UV) run python scripts/f4_linter_linux_provisioning.py --check-official-evidence-drift + @echo "--> OK: Linux official distro evidence drift gate" + .PHONY: validate-pypi-contract validate-pypi-contract: @test -f "$(PYPI_WHEEL_FILE)" || (echo "Missing $(PYPI_WHEEL_FILE)"; exit 2) @@ -1209,7 +1215,7 @@ validate-windows-contract: package-windows-assert @echo "--> OK: Windows contract" .PHONY: validate-gate2 -validate-gate2: validate-version-consistency validate-runtime-imports +validate-gate2: validate-version-consistency validate-runtime-imports validate-official-evidence-drift @if [ -f "$(PYPI_WHEEL_FILE)" ] || [ -f "$(PYPI_WHEEL_FILE).sha256" ] || [ -f "$(PYPI_SDIST_FILE)" ] || [ -f "$(PYPI_SDIST_FILE).sha256" ]; then \ $(MAKE) validate-pypi-contract; \ else \ diff --git a/docs/release/artifact-contract.md b/docs/release/artifact-contract.md index 524664e..66cb97b 100644 --- a/docs/release/artifact-contract.md +++ b/docs/release/artifact-contract.md @@ -418,6 +418,7 @@ scripts and release contract tests. | `scripts/check_log_invariant.py` | Read-only `git ls-files` log-location invariant | | `scripts/verify_artifact.py` | Structural SHA256 sidecar verifier; exit-code contract `0`-`5` preserved | | `scripts/verify_release_assets.py` | Read-only exact 21 ECLI-owned GitHub Release asset verifier; ignores `.checksums/` only when it is a directory | +| `scripts/f4_linter_linux_provisioning.py` | Read-only Linux F4 provisioning manifest verifier and official distro evidence drift audit gate; `--check-official-evidence-drift` exits `2` on release-blocking drift | | `scripts/verify_runtime.py` | Cross-artifact launcher validation; exit codes (`0`/`2`/`3`/`4`/`5`/`6`) preserved | | `scripts/provision_f4_linters.py` | Provider-neutral F4 linter provisioning planner/evidence writer; dry-run by default, no network/upstream downloads unless explicitly allowed | | `scripts/verify_f4_linter_provisioning.py` | Read-only F4 linter provisioning evidence verifier for one artifact or all 21 canonical artifact entries | diff --git a/docs/release/packaging-flows.md b/docs/release/packaging-flows.md index b442c0c..b27a831 100644 --- a/docs/release/packaging-flows.md +++ b/docs/release/packaging-flows.md @@ -70,6 +70,13 @@ package-manager or bundle installation remains the responsibility of each artifact-specific flow and must stay non-interactive for CI and unattended package-manager installs. +Linux official distro evidence is release-gated by +`make validate-official-evidence-drift`, which calls +`uv run python scripts/f4_linter_linux_provisioning.py --check-official-evidence-drift`. +The command is read-only: it does not write evidence files, download tools, or +invoke package managers. Exit `0` means the official evidence registry matches +generated distro evidence; exit `2` is release-blocking drift. + ## Mandatory GitHub Release Assets ```text diff --git a/docs/release/release-checklist.md b/docs/release/release-checklist.md index d776cb8..56679a1 100644 --- a/docs/release/release-checklist.md +++ b/docs/release/release-checklist.md @@ -39,6 +39,10 @@ See the LICENSE file in the project root for full license text. `make sysinfo` match current package surfaces and canonical Python scripts. - [ ] `make validate-gate2` passes before any publish step. +- [ ] `make validate-official-evidence-drift` passes; this invokes + `scripts/f4_linter_linux_provisioning.py --check-official-evidence-drift` + and blocks release readiness if the Linux official distro evidence + registry drifts from generated evidence. - [ ] `make validate-release-assets` passes against `releases//`. - [ ] Required packaging scripts exist and are executable. - [ ] Active shell wrappers under `scripts/` are absent; Python entrypoints under @@ -82,6 +86,11 @@ See the LICENSE file in the project root for full license text. 21 deterministic `f4-linter-provisioning-.json` evidence files, and `scripts/verify_f4_linter_provisioning.py --all-artifacts` verifies them. +- [ ] Linux official distro evidence drift audit remains clean: + `uv run python scripts/f4_linter_linux_provisioning.py --check-official-evidence-drift` + prints + `PASS: Linux official distro evidence drift audit clean` and exits `0`; + exit `2` is release-blocking drift. - [ ] F4 linter package-manager dependencies: package metadata asserts the dependency relationship and post-install executable availability for each required linter/toolchain dependency it delegates to the OS package diff --git a/tests/packaging/test_f4_linter_linux_provisioning.py b/tests/packaging/test_f4_linter_linux_provisioning.py index 7e56745..6ab1d4e 100644 --- a/tests/packaging/test_f4_linter_linux_provisioning.py +++ b/tests/packaging/test_f4_linter_linux_provisioning.py @@ -428,6 +428,101 @@ def test_official_distro_evidence_drift_check_cli_passes( assert result.stdout == "PASS: Linux official distro evidence drift audit clean\n" +def test_validate_gate2_invokes_official_evidence_drift_gate( + read_repo_text: RepoReader, +) -> None: + makefile = read_repo_text("Makefile") + target_start = makefile.index(".PHONY: validate-official-evidence-drift") + target_end = makefile.index(".PHONY: validate-pypi-contract") + target = makefile[target_start:target_end] + + assert "validate-official-evidence-drift:" in target + assert ( + "$(UV) run python scripts/f4_linter_linux_provisioning.py --check-official-evidence-drift" + in target + ) + assert "provision_f4_linters.py" not in target + assert "build/evidence" not in target + assert "releases/" not in target + assert ( + "validate-gate2: validate-version-consistency validate-runtime-imports validate-official-evidence-drift" + in makefile + ) + + +def test_make_official_evidence_drift_gate_passes_clean( + repo_root: Path, +) -> None: + result = subprocess.run( + ["make", "validate-official-evidence-drift"], + cwd=repo_root, + capture_output=True, + text=True, + check=False, + ) + + assert result.returncode == 0 + assert "PASS: Linux official distro evidence drift audit clean" in result.stdout + assert "--> OK: Linux official distro evidence drift gate" in result.stdout + + +def test_validate_gate2_propagates_official_evidence_drift_failure( + repo_root: Path, + tmp_path: Path, +) -> None: + fake_python = tmp_path / "python-ok" + fake_python.write_text( + "#!/usr/bin/env python3\nimport sys\nsys.exit(0)\n", + encoding="utf-8", + ) + fake_python.chmod(0o755) + uv_log = tmp_path / "uv-args.txt" + fake_uv = tmp_path / "uv-fail" + fake_uv.write_text( + "#!/usr/bin/env python3\n" + "import pathlib\n" + "import sys\n" + f"pathlib.Path({str(uv_log)!r}).write_text(' '.join(sys.argv[1:]), encoding='utf-8')\n" + "print('FAIL: Linux official distro evidence drift detected')\n" + "sys.exit(2)\n", + encoding="utf-8", + ) + fake_uv.chmod(0o755) + + result = subprocess.run( + [ + "make", + "validate-gate2", + f"PYTHON={fake_python}", + f"UV={fake_uv}", + ], + cwd=repo_root, + capture_output=True, + text=True, + check=False, + ) + + assert result.returncode != 0 + assert uv_log.read_text(encoding="utf-8") == ( + "run python scripts/f4_linter_linux_provisioning.py " + "--check-official-evidence-drift" + ) + assert "FAIL: Linux official distro evidence drift detected" in result.stdout + + +def test_release_docs_reference_official_evidence_drift_gate( + read_repo_text: RepoReader, +) -> None: + for path in ( + "docs/release/release-checklist.md", + "docs/release/artifact-contract.md", + "docs/release/packaging-flows.md", + ): + text = read_repo_text(path) + assert "--check-official-evidence-drift" in text + assert "release" in text.lower() + + def test_official_distro_evidence_drift_comparison_rejects_mismatched_url( linux_helper: ModuleType, ) -> None: