From 0544b90fab8b3d38cb5d14c0c271edc3235c6f8d Mon Sep 17 00:00:00 2001 From: schickling-assistant <261620128+schickling-assistant@users.noreply.github.com> Date: Tue, 10 Nov 2026 09:32:14 +0100 Subject: [PATCH 1/2] Pin Python oracle to v0.6.0 and retire resync exceptions agent-identity: agent/compoundingtech/mergemaster agent-persona: generalist agent-supervisor: unavailable agent-tool: OMP agent-tool-version: 18.12.5 agent-runtime: OMP 18.12.5 agent-model: openai-codex/gpt-6.1-sol --- README.md | 11 +++-- flake.lock | 8 +-- flake.nix | 3 +- scripts/PROVENANCE | 32 ------------ scripts/test-scripts | 9 ---- tests/oracle_scripts.py | 64 +++++------------------- tests/test-oracle-scripts.py | 94 +++++++++++------------------------- 7 files changed, 49 insertions(+), 172 deletions(-) diff --git a/README.md b/README.md index 3ee9984..627f175 100644 --- a/README.md +++ b/README.md @@ -17,16 +17,17 @@ The default development shell provides both suites' real tool dependencies and pinned source inputs. Script tests run on the host, not inside a Nix derivation or a nested bubblewrap sandbox. -Rust signer/verifier parity tests read the immutable v0.5.0 Python source pinned +Rust signer/verifier parity tests read the immutable v0.6.0 Python source pinned by the `python-oracle` flake input, not the working-tree scripts. `MERGEMASTER_PYTHON_ORACLE` names that source root and is required for those tests; the dev shell and `checks.evidence` set it automatically. The production package does not depend on the oracle or run its parity tests. -`checks.oracle-scripts` compares every remaining working-tree script's bytes and -executable bits with the pinned oracle. Deletions are allowed; additions or edits -fail. The explicit exemptions are repository-owned `PROVENANCE`, `test-scripts`, -`parity-drift`, and `parity-drift-test`, rather than copied upstream scripts. +`checks.oracle-scripts` compares copied scripts' bytes and executable bits with +the pinned oracle in both directions: additions, deletions and edits fail. +Only repository-owned `PROVENANCE` and `test-scripts` are exempt. The retired +Python parity-drift tools are absent from both trees; the scheduled drift check +continues to use the Rust CLI. For development: diff --git a/flake.lock b/flake.lock index 45f7426..50dad20 100644 --- a/flake.lock +++ b/flake.lock @@ -107,17 +107,17 @@ "python-oracle": { "flake": false, "locked": { - "lastModified": 1793953851, - "narHash": "sha256-NL/NWSn+YfpwP7h+CGZkeNmRh9hnQOH3OkF9GtyPXgM=", + "lastModified": 1794298270, + "narHash": "sha256-UeHYs08Nzqy50cvlWu1n5Z0bR2I/J2Ectj7ztd5gfNI=", "owner": "compoundingtech", "repo": "mergemaster", - "rev": "1392a516e969be46d9516d904b76a4db98c55030", + "rev": "cff667cd53bdb6c024f9d7a06259b8f9a3fbb0cb", "type": "github" }, "original": { "owner": "compoundingtech", "repo": "mergemaster", - "rev": "1392a516e969be46d9516d904b76a4db98c55030", + "rev": "cff667cd53bdb6c024f9d7a06259b8f9a3fbb0cb", "type": "github" } }, diff --git a/flake.nix b/flake.nix index 1d1d91b..4d52339 100644 --- a/flake.nix +++ b/flake.nix @@ -11,7 +11,7 @@ flake = false; }; inputs.python-oracle = { - url = "github:compoundingtech/mergemaster/1392a516e969be46d9516d904b76a4db98c55030"; + url = "github:compoundingtech/mergemaster/cff667cd53bdb6c024f9d7a06259b8f9a3fbb0cb"; flake = false; }; @@ -61,7 +61,6 @@ # Inspect every Git-source entry, including names cleanSource excludes. src = self; MERGEMASTER_PYTHON_ORACLE = "${python-oracle}"; - MERGEMASTER_FRACTAL_FIXTURE = "${fractal-fixture}"; } '' python3 -B "$src/tests/test-oracle-scripts.py" python3 -B "$src/tests/oracle_scripts.py" diff --git a/scripts/PROVENANCE b/scripts/PROVENANCE index b10409d..27565ed 100644 --- a/scripts/PROVENANCE +++ b/scripts/PROVENANCE @@ -1,38 +1,6 @@ # Source: compoundingtech/fractal # Commit: e83871ec919f95b3b3e2a9a1cd211c2ac37babda # Scripts copied as-is except the documented upstream patches below; Git blob SHA-1 hashes (not file SHA-1 hashes). -# Resync exception; exit: https://github.com/compoundingtech/mergemaster/issues/115 -# Pending until the fixture reaches this revision; no copied-script exception before activation. -# resync-rev e2721e52463428b4ff655addee129cecd2839763 -# resync-blob scripts/ci-evidence-gate 2952f89514a584465a428017b3f3e8ea043f41b2 -# resync-blob scripts/ci-evidence-gate-test 38a34e7d48ed407b428f739c0f3d38b151fced59 -# resync-blob scripts/ci-signed-tree 77a560621f8182d2ca19f622782ff24c95e7d514 -# resync-blob scripts/ci-signed-tree-test 91612975e7ae383ca5c55b94a19af20b0b1c7df2 -# resync-blob scripts/evidence-gc 1340a798f6bda78ad6319352fabd8de2050312f9 -# resync-blob scripts/evidence-gc-test 3c7482d742f104780158789154491d290ab7a693 -# resync-blob scripts/evidence-gc-timer dd5781ab377653d43b1cfda67a46e897ec0e51d8 -# resync-blob scripts/evidence-log 73e22e8252a1ceab4eba0324a0063c97f1d75b18 -# resync-blob scripts/evidence-log-test 746aa09bc2363c2d57523220c084fb6a4e34ef66 -# resync-blob scripts/evidence-log-timer 85ffab484040914c4ae14f72b86cc70c97b7e358 -# resync-blob scripts/evidence-log-watch f580ed13fe4f8130ec936dfeefb64f6c3b97b2f1 -# resync-blob scripts/evidence-log-watch-test e29deab9f1bfdfa27989e618de577e5f748871e4 -# resync-blob scripts/evidence-log.service 341638d61c71d41358e2b147d141ce76b6d6c06e -# resync-blob scripts/evidence-presign-worker d68abbd4351ee70449a517e2c9566b54239e34be -# resync-blob scripts/evidence-queue-signer 0be3140016868d1e383d9c301c1e229066c6dd22 -# resync-blob scripts/evidence-queue-signer-test e5c8a3c7cc59e4bf6343a9f5a8601e5ba45cbc43 -# resync-blob scripts/evidence-skip-report 2bdb6c3ebee8bb381c93c736cc99c22cbb82c0b3 -# resync-blob scripts/evidence-skip-report-test 2d51833fa53146709634090bd75ebf8dc4a91c50 -# resync-blob scripts/evidence-wrap b31f1737e55f187471cd15b8d0b938dd982c72a0 -# resync-blob scripts/evidence-wrap-test 603ae3a411bfcd31e70160f7fba05854ec38df36 -# resync-blob scripts/fractal-test-report 52ebcb098a323b013eba5b136457393ef88e2951 -# resync-blob scripts/fractal-test-report-test b0dc50163986760343d7eb67c701cb4b775e9945 -# resync-blob scripts/lane-definition-closure 492b3741b117092395855d9790dc96e6ce3612e6 -# resync-blob scripts/lane-definition-closure-test 635945d2a01f18e9c4716c50a2eb9803896cbb08 -# resync-blob scripts/evidence-flip-ready ce94216c0435711419e1b2bc5acc996e2ac926b9 -# resync-blob scripts/evidence-flip-ready-test 16e505355c4092c880c3f5e283baa8c0996cea4b -# resync-blob scripts/evidence-verify-test 08d11cc7ba5e8a3d5ffb59fadb9eb49238aec95f -# resync-blob scripts/st-test 087ae7d9f3d1106a38eedc1e911caeeaf46a96ae -# resync-blob scripts/fractal-test-test decb42b9d442807c9601b473c190b3743b24c4c0 # scripts/fractal-test-test: apply the fixture maintenance-isolation patch from # https://github.com/compoundingtech/fractal/pull/4398 (5f995f26e5). # The source_blob column remains the original import; mergemaster_blob includes applied patches. diff --git a/scripts/test-scripts b/scripts/test-scripts index 14c00f2..68f9bc8 100755 --- a/scripts/test-scripts +++ b/scripts/test-scripts @@ -35,15 +35,6 @@ cp -R "$root/tests/fixtures/fractal/." "$sandbox/repo/" chmod -R u+w "$sandbox/repo" mkdir -p "$sandbox/repo/scripts" cp -R "$root/scripts/." "$sandbox/repo/scripts/" -# These exempt Python parity tools moved to Rust; retain their oracle-only suite. -cp "$oracle/scripts/parity-drift" "$oracle/scripts/parity-drift-test" "$sandbox/repo/scripts/" -# The parity inventory test needs the manifests at their owning repository paths, -# not only the fixture files overlaid at the simulated Fractal root. -while IFS= read -r -d '' manifest; do - relative=${manifest#"$root/"} - mkdir -p "$sandbox/repo/$(dirname "$relative")" - cp "$manifest" "$sandbox/repo/$relative" -done < <(find "$root/tests/fixtures/fractal" -type f -name '*PROVENANCE' -print0) chmod -R u+w "$sandbox/repo" cd "$sandbox/repo" git init -q -b main diff --git a/tests/oracle_scripts.py b/tests/oracle_scripts.py index 44cd850..86e0134 100644 --- a/tests/oracle_scripts.py +++ b/tests/oracle_scripts.py @@ -1,38 +1,8 @@ -"""Byte/mode oracle guard with the single PROVENANCE resync exception.""" -import hashlib -import json +"""Require copied scripts to match the immutable Python oracle, in both directions.""" import os from pathlib import Path -import re -EXEMPT = {"PROVENANCE", "test-scripts", "parity-drift", "parity-drift-test"} -ORIGINAL = "e83871ec919f95b3b3e2a9a1cd211c2ac37babda" -TARGET = "e2721e52463428b4ff655addee129cecd2839763" - - -def blob(data): - return hashlib.sha1(b"blob " + str(len(data)).encode() + b"\0" + data).hexdigest() - - -def table(text): - revisions, original, rows = [], [], {} - for line in text.splitlines(): - if line.startswith("# Commit: "): - original.append(line.removeprefix("# Commit: ")) - elif line.startswith("# resync-rev "): - revisions.append(line.removeprefix("# resync-rev ")) - elif line.startswith("# resync-blob "): - path, oid = line.removeprefix("# resync-blob ").split() - if not re.fullmatch(r"scripts/[A-Za-z0-9._-]+", path) or path in rows: - raise ValueError("invalid/duplicate resync path") - if not re.fullmatch(r"[0-9a-f]{40}", oid) or path[8:] in EXEMPT: - raise ValueError("invalid resync blob") - rows[path] = oid - if len(revisions) != 1 or len(original) != 1 or not rows: - raise ValueError("resync requires one target, one original revision and paths") - if ORIGINAL == TARGET or original[0] != ORIGINAL or revisions[0] != TARGET: - raise ValueError("resync revision differs from approved constants") - return revisions[0], original[0], rows +EXEMPT = {"PROVENANCE", "test-scripts"} def snapshot(path): @@ -41,34 +11,22 @@ def snapshot(path): return path.read_bytes(), path.stat().st_mode & 0o111 -def check(src, oracle, fixture, fixture_rev): - _, _, rows = table((src / "scripts/PROVENANCE").read_text()) - if fixture_rev not in (ORIGINAL, TARGET): - raise ValueError("fixture revision differs from approved constants") - active = fixture_rev == TARGET - for path, oid in rows.items(): - local, old = snapshot(src / path), snapshot(oracle / path) - if blob(old[0]) == oid: - raise ValueError(f"{path}: expired resync exception; remove it (#115)") - if active: - upstream = snapshot(fixture / path) - if blob(upstream[0]) != oid or local != upstream or local[0] == old[0]: - raise ValueError(f"{path}: resync bytes/mode/blob mismatch") - elif local != old: - raise ValueError(f"{path}: pending resync must still equal oracle") +def check(src, oracle): for path in sorted((src / "scripts").rglob("*")): if path.is_dir() and not path.is_symlink(): continue relative = path.relative_to(src).as_posix() - if relative[8:] in EXEMPT or relative in rows: + if relative[8:] in EXEMPT: continue if snapshot(path) != snapshot(oracle / relative): raise ValueError(f"{relative}: differs from pinned oracle") + for path in sorted((oracle / "scripts").rglob("*")): + if path.is_dir() and not path.is_symlink(): + continue + relative = path.relative_to(oracle).as_posix() + if relative[8:] not in EXEMPT and not (src / relative).is_file(): + raise ValueError(f"{relative}: missing oracle script") if __name__ == "__main__": - src = Path(os.environ["src"]) - lock = json.loads((src / "flake.lock").read_text()) - node = lock["nodes"][lock["nodes"]["root"]["inputs"]["fractal-fixture"]] - check(src, Path(os.environ["MERGEMASTER_PYTHON_ORACLE"]), - Path(os.environ["MERGEMASTER_FRACTAL_FIXTURE"]), node["locked"]["rev"]) + check(Path(os.environ["src"]), Path(os.environ["MERGEMASTER_PYTHON_ORACLE"])) diff --git a/tests/test-oracle-scripts.py b/tests/test-oracle-scripts.py index 6d751fe..d669141 100644 --- a/tests/test-oracle-scripts.py +++ b/tests/test-oracle-scripts.py @@ -2,68 +2,49 @@ from pathlib import Path import unittest -from oracle_scripts import ORIGINAL, TARGET, blob, check +from oracle_scripts import EXEMPT, check -class ResyncGuard(unittest.TestCase): +class OracleGuard(unittest.TestCase): def setUp(self): self.temp = tempfile.TemporaryDirectory() self.addCleanup(self.temp.cleanup) - self.src, self.oracle, self.fixture = [Path(self.temp.name) / p for p in ("src", "oracle", "fixture")] - for root in (self.src, self.oracle, self.fixture): + self.src, self.oracle = [Path(self.temp.name) / p for p in ("src", "oracle")] + for root in (self.src, self.oracle): (root / "scripts").mkdir(parents=True) - self.old, self.target = ORIGINAL, TARGET - self.oid = blob(b"new") - self.write(self.src, "one", b"new") - self.write(self.oracle, "one", b"old") - self.write(self.fixture, "one", b"new") - self.manifest() + self.write(root, "one", b"old") def write(self, root, name, data, mode=0o755): path = root / "scripts" / name path.write_bytes(data) path.chmod(mode) - def manifest(self): - (self.src / "scripts/PROVENANCE").write_text( - f"# Commit: {self.old}\n# resync-rev {self.target}\n# resync-blob scripts/one {self.oid}\n") - - def run_check(self, rev=None): - check(self.src, self.oracle, self.fixture, rev or self.target) + def run_check(self): + check(self.src, self.oracle) def test_match(self): self.run_check() - def test_pending_is_oracle_only(self): - with self.assertRaises(ValueError): - self.run_check(self.old) - self.write(self.src, "one", b"old") - self.run_check(self.old) - with self.assertRaises(ValueError): - self.run_check("c" * 40) + def test_repository_owned_exemptions(self): + for name in EXEMPT: + self.write(self.src, name, b"local") + self.write(self.oracle, name, b"oracle") + self.run_check() - def test_local_mismatch_and_oracle_copy(self): - for data in (b"edited", b"old"): + def test_local_mismatch(self): + for data in (b"edited", b"new"): self.write(self.src, "one", data) with self.assertRaises(ValueError): self.run_check() - def test_fixture_and_table_must_agree(self): - self.write(self.fixture, "one", b"edited") - with self.assertRaises(ValueError): - self.run_check() - self.write(self.fixture, "one", b"new") - self.oid = blob(b"edited") - self.manifest() + def test_executable_modes(self): + self.write(self.src, "one", b"old", 0o644) with self.assertRaises(ValueError): self.run_check() - def test_modes_and_missing_paths(self): - self.write(self.src, "one", b"new", 0o644) - with self.assertRaises(ValueError): - self.run_check() + def test_oracle_script_deletion_is_refused(self): (self.src / "scripts/one").unlink() - with self.assertRaises(ValueError): + with self.assertRaisesRegex(ValueError, "missing oracle script"): self.run_check() def test_unlisted_stays_oracle(self): @@ -75,38 +56,17 @@ def test_unlisted_stays_oracle(self): self.run_check() def test_unlisted_addition_is_refused(self): - self.write(self.src, "not-in-oracle-or-table", b"new") - with self.assertRaisesRegex(ValueError, "not a regular script"): - self.run_check() - - def test_expiry_is_byte_identical_even_if_modes_differ(self): - self.write(self.oracle, "one", b"new", 0o644) - with self.assertRaisesRegex(ValueError, "expired"): - self.run_check() - - def test_manifest_cannot_retarget_approved_revisions(self): - for original, target in [("c" * 40, "c" * 40), (ORIGINAL, "c" * 40), - (TARGET, TARGET), (ORIGINAL, ORIGINAL)]: - self.old, self.target = original, target - self.manifest() - with self.assertRaisesRegex(ValueError, "approved constants"): - self.run_check(target) - - def test_duplicate_or_malformed_rows(self): - path = self.src / "scripts/PROVENANCE" - original = path.read_text() - for row in [f"# resync-blob scripts/one {self.oid}", - "# resync-blob scripts/../escape " + self.oid, - "# resync-blob scripts/two invalid", - "# resync-blob scripts/two"]: - path.write_text(original + row + "\n") - with self.assertRaises(ValueError): - self.run_check() - - def test_listed_symlink_is_not_a_script(self): + for name in ("not-in-oracle", "parity-drift", "parity-drift-test"): + with self.subTest(name=name): + self.write(self.src, name, b"new") + with self.assertRaisesRegex(ValueError, "not a regular script"): + self.run_check() + (self.src / "scripts" / name).unlink() + + def test_symlink_is_not_a_script(self): path = self.src / "scripts/one" path.unlink() - path.symlink_to(self.fixture / "scripts/one") + path.symlink_to(self.oracle / "scripts/one") with self.assertRaisesRegex(ValueError, "not a regular script"): self.run_check() From 459cf58193e0bf1d0c9c69222beb5f011f01d730 Mon Sep 17 00:00:00 2001 From: schickling-assistant <261620128+schickling-assistant@users.noreply.github.com> Date: Tue, 10 Nov 2026 09:43:31 +0100 Subject: [PATCH 2/2] Retain the retired Python parity oracle for differential tests agent-identity: agent/compoundingtech/mergemaster agent-persona: generalist agent-supervisor: unavailable agent-tool: OMP agent-tool-version: 18.12.5 agent-runtime: OMP 18.12.5 agent-model: openai-codex/gpt-6.1-sol --- README.md | 4 +++- .../tests/parity_drift.rs | 4 ++-- flake.lock | 20 ++++++++++++++++++- flake.nix | 9 ++++++++- 4 files changed, 32 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 627f175..ef29a52 100644 --- a/README.md +++ b/README.md @@ -164,7 +164,9 @@ history or a missing watermark is an error in either mode. The workflow builds the Rust binary from the checkout's locked Nix flake before fetching upstream history, retaining read-only permissions and advisory reporting. The Rust differential integration test compares the unchanged Python implementation -and assertions from `MERGEMASTER_PYTHON_ORACLE` against real local Git histories. +and assertions from `MERGEMASTER_PYTHON_PARITY_ORACLE` against real local Git histories. +This separate `python-oracle-parity` input retains v0.5.0's retired reporter and suite; +the dev shell and `checks.evidence` set it without adding a runtime dependency. The command defaults to `scripts/PROVENANCE` at the caller's Git top level; outside a checkout, pass `--provenance`. diff --git a/crates/mergemaster-evidence/tests/parity_drift.rs b/crates/mergemaster-evidence/tests/parity_drift.rs index c08dbdc..5051c40 100644 --- a/crates/mergemaster-evidence/tests/parity_drift.rs +++ b/crates/mergemaster-evidence/tests/parity_drift.rs @@ -1,7 +1,7 @@ #[test] fn python_oracle_parity() { - let root = std::env::var_os("MERGEMASTER_PYTHON_ORACLE") - .expect("MERGEMASTER_PYTHON_ORACLE must be set; run tests in nix develop"); + let root = std::env::var_os("MERGEMASTER_PYTHON_PARITY_ORACLE") + .expect("MERGEMASTER_PYTHON_PARITY_ORACLE must be set; run tests in nix develop"); let status = std::process::Command::new("python3") .args(["-I", "-B", "-c", DRIVER]) .arg(root) diff --git a/flake.lock b/flake.lock index 50dad20..8c1e900 100644 --- a/flake.lock +++ b/flake.lock @@ -121,6 +121,23 @@ "type": "github" } }, + "python-oracle-parity": { + "flake": false, + "locked": { + "lastModified": 1793953851, + "narHash": "sha256-NL/NWSn+YfpwP7h+CGZkeNmRh9hnQOH3OkF9GtyPXgM=", + "owner": "compoundingtech", + "repo": "mergemaster", + "rev": "1392a516e969be46d9516d904b76a4db98c55030", + "type": "github" + }, + "original": { + "owner": "compoundingtech", + "repo": "mergemaster", + "rev": "1392a516e969be46d9516d904b76a4db98c55030", + "type": "github" + } + }, "root": { "inputs": { "effect-utils": "effect-utils", @@ -129,7 +146,8 @@ "effect-utils", "nixpkgs" ], - "python-oracle": "python-oracle" + "python-oracle": "python-oracle", + "python-oracle-parity": "python-oracle-parity" } }, "systems": { diff --git a/flake.nix b/flake.nix index 4d52339..d7474c8 100644 --- a/flake.nix +++ b/flake.nix @@ -14,8 +14,13 @@ url = "github:compoundingtech/mergemaster/cff667cd53bdb6c024f9d7a06259b8f9a3fbb0cb"; flake = false; }; + # The Python parity reporter was retired before the main oracle's v0.6.0 pin. + inputs.python-oracle-parity = { + url = "github:compoundingtech/mergemaster/1392a516e969be46d9516d904b76a4db98c55030"; + flake = false; + }; - outputs = { self, nixpkgs, effect-utils, fractal-fixture, python-oracle }: + outputs = { self, nixpkgs, effect-utils, fractal-fixture, python-oracle, python-oracle-parity }: let systems = [ "x86_64-linux" "aarch64-linux" ]; forAllSystems = nixpkgs.lib.genAttrs systems; @@ -54,6 +59,7 @@ evidence = (packageFor system).overrideAttrs (_: { doCheck = true; MERGEMASTER_PYTHON_ORACLE = "${python-oracle}"; + MERGEMASTER_PYTHON_PARITY_ORACLE = "${python-oracle-parity}"; MERGEMASTER_FRACTAL_FIXTURE = "${fractal-fixture}"; }); oracle-scripts = pkgs.runCommand "mergemaster-oracle-scripts" { @@ -95,6 +101,7 @@ MERGEMASTER_EFFECT_UTILS = "${effect-utils}"; MERGEMASTER_FRACTAL_FIXTURE = "${fractal-fixture}"; MERGEMASTER_PYTHON_ORACLE = "${python-oracle}"; + MERGEMASTER_PYTHON_PARITY_ORACLE = "${python-oracle-parity}"; }; }); }; }