diff --git a/scripts/research_baseline_sparse.py b/scripts/research_baseline_sparse.py index 627ca334f2..4e4c649798 100644 --- a/scripts/research_baseline_sparse.py +++ b/scripts/research_baseline_sparse.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 -"""Name the selected inputs a sparse checkout omits before research_baseline.py. +"""Name the selected inputs a sparse checkout omits before its baseline report. -In a sparse checkout, ``scripts/research_baseline.py`` stops at the first absent +In a sparse checkout, the selected baseline stops at the first absent selected input with ``Invalid selected file: ...`` and does not say which directories it needs. This read-only preflight lists every input that check reads and prints the ``git sparse-checkout add`` command that materializes them. @@ -10,7 +10,10 @@ contract (``choose_research_task.check_catalog_files``): a stage-zero regular skip-worktree entry whose blob matches HEAD. An ordinary deletion, staged drift or unknown path raises the same ``Invalid selected file`` error. Byte drift in -present files is left to ``research_baseline.py``. Nothing is verified here. +present files is left to the baseline. Nothing is verified here. + +The certificate schema selects ``scripts/research_selected_baseline.py`` for +productive-helper claims; historical certificates keep ``research_baseline.py``. This is a separate script so that the baseline and its tests, which research packages pin as live inputs, keep their bytes. @@ -27,24 +30,30 @@ import sys from choose_research_task import check_catalog_files +from research_selected_baseline import SCHEMA as PRODUCTIVE_SCHEMA ROOT = Path(__file__).resolve().parents[1] SELECTED = "certificates/selected-result.json" +def selected_json(name, root): + """Read present metadata, or its HEAD blob after validating a sparse omission.""" + check_catalog_files(root, [(name, "Invalid selected file: " + name)]) + path = root / name + if path.is_file(): + raw = path.read_bytes() + else: # a sparse omission whose index blob matches HEAD (checked above) + raw = subprocess.run(["git", "cat-file", "blob", "HEAD:" + name], + cwd=root, check=True, capture_output=True).stdout + return json.loads(raw) + + def selected_inputs(record, root=ROOT): """Repository paths the selected-record check reads, before it reads them.""" names = [record["certificate"], record["source_manifest"], record["proof"], record["review"], record["validation"], *record["proof_supplements"]] - check_catalog_files(root, [(names[1], "Invalid selected file: " + names[1])]) - manifest = root / record["source_manifest"] - if manifest.is_file(): - raw = manifest.read_bytes() - else: # a sparse omission whose index blob matches HEAD (checked above) - raw = subprocess.run(["git", "cat-file", "blob", "HEAD:" + record["source_manifest"]], - cwd=root, check=True, capture_output=True).stdout - inventory = json.loads(raw) - if inventory.get("format") == "repository-packages-sha256-v1": + inventory = selected_json(record["source_manifest"], root) + if inventory.get("format") in {"repository-packages-sha256-v1", "repository-files-sha256-v1"}: names += inventory["files"] else: parent = Path(record["source_manifest"]).parent @@ -72,18 +81,23 @@ def materialize_command(omitted): def report(record, root=ROOT): omitted = sparse_omissions(record, root) + certificate = selected_json(record["certificate"], root) + baseline = ("scripts/research_selected_baseline.py" if certificate.get("schema") == PRODUCTIVE_SCHEMA + else "scripts/research_baseline.py") + next_command = ["python3", "-B", baseline] if not omitted: return 0, { "status": "NO_SPARSE_SELECTED_OMISSIONS", - "next": ["python3", "-B", "scripts/research_baseline.py"], + "next": next_command, "scope": "Every selected input is present. Nothing was verified; run the baseline next.", } return 2, { "status": "SPARSE_SELECTED_INPUTS_ABSENT", "omitted_files": len(omitted), "first_omitted": omitted[:5], "materialize": materialize_command(omitted), + "next": next_command, "scope": "This sparse checkout omits selected inputs (skip-worktree entries matching HEAD). " - "Run the materialize command, then scripts/research_baseline.py; nothing was verified.", + "Run the materialize command, then " + baseline + "; nothing was verified.", } diff --git a/tests/test_research_baseline_sparse.py b/tests/test_research_baseline_sparse.py index 0ed25d56e7..7c5e5df9ad 100644 --- a/tests/test_research_baseline_sparse.py +++ b/tests/test_research_baseline_sparse.py @@ -59,10 +59,61 @@ def run_main(self): code = sparse.main(["--root", str(self.root)]) return code, json.loads(out.getvalue()) + def productive_certificate(self): + (self.root / self.record["certificate"]).write_text(json.dumps({ + "schema": "proposed-conditional-integer-multiplication-claim/1", + }), encoding="utf-8") + + def explicit_manifest(self): + self.record = {**RECORD, "source_manifest": "certificates/source.json"} + (self.root / sparse.SELECTED).write_text(json.dumps(self.record), encoding="utf-8") + (self.root / "LICENSE").write_text("fixture license\n", encoding="utf-8") + (self.root / self.record["source_manifest"]).write_text(json.dumps({ + "format": "repository-files-sha256-v1", + "files": {"LICENSE": "0", "research/pkg-a/data/x.json": "0"}, + }), encoding="utf-8") + self.productive_certificate() + self.git("add", ".") + self.commit("explicit repository inventory") + def test_full_checkout_reports_nothing(self): self.assertEqual(sparse.sparse_omissions(self.record, self.root), []) code, result = self.run_main() self.assertEqual((code, result["status"]), (0, "NO_SPARSE_SELECTED_OMISSIONS")) + self.assertEqual(result["next"], ["python3", "-B", "scripts/research_baseline.py"]) + + def test_full_explicit_inventory_keeps_repository_relative_paths(self): + self.explicit_manifest() + code, result = self.run_main() + self.assertEqual((code, result["status"]), (0, "NO_SPARSE_SELECTED_OMISSIONS")) + self.assertEqual(result["next"], ["python3", "-B", "scripts/research_selected_baseline.py"]) + + def test_sparse_explicit_inventory_and_certificate_read_from_head(self): + self.explicit_manifest() + self.git("sparse-checkout", "set", "--no-cone", "/*", "!/*/", "/scripts/", "/docs/", + "/certificates/selected-result.json") + self.assertFalse((self.root / self.record["source_manifest"]).exists()) + self.assertFalse((self.root / self.record["certificate"]).exists()) + self.assertTrue((self.root / "LICENSE").is_file()) + omitted = sparse.sparse_omissions(self.record, self.root) + self.assertEqual(omitted, ["certificates/source.json", "research/audit/ASSEMBLY.json", + "research/pkg-a/PROOF.md", "research/pkg-a/data/x.json", + "research/pkg-b/NOTE.md"]) + code, result = self.run_main() + self.assertEqual((code, result["status"], result["omitted_files"]), + (2, "SPARSE_SELECTED_INPUTS_ABSENT", 5)) + self.assertEqual(result["next"], ["python3", "-B", "scripts/research_selected_baseline.py"]) + self.git(*result["materialize"][1:]) + self.assertEqual(sparse.sparse_omissions(self.record, self.root), []) + self.assertEqual(self.run_main()[0], 0) + + def test_productive_schema_routes_package_inventory_to_selected_baseline(self): + self.productive_certificate() + self.commit("productive certificate", "-a") + self.git("sparse-checkout", "set", "--cone", "scripts", "docs", "certificates") + code, result = self.run_main() + self.assertEqual(code, 2) + self.assertEqual(result["next"], ["python3", "-B", "scripts/research_selected_baseline.py"]) def test_cone_sparse_names_packages_and_reads_manifest_from_head(self): self.git("sparse-checkout", "set", "--cone", "scripts", "docs", "certificates")