Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions draft/progress.md
Original file line number Diff line number Diff line change
Expand Up @@ -197,3 +197,9 @@
- Committed exact 41-file scope as `1f5db1500aeeccdd6b730edb48ce0fe2f034fc52`, pushed non-force branch, and independently verified remote SHA plus workflow/runner/test blobs 3/3. PR #11: https://github.com/OmniBioAI/omnibioai-tool-images/pull/11. Remote CI run `37229412691` passed.
- Before native dispatch, added a success-only Actions artifact handoff retaining the validated SIF and its bound entry record. The former evidence-only retention excluded SIF payloads; without a handoff, future publication would lose the exact validated bytes when runners terminate. No registry write or release step was added.
- Revalidated 69 adapter tests, actionlint and Ruff. Latest change must pass remote CI before merge. No native workflow dispatch, build or publication yet; original worktree preserved.

### Remote merge and pinned-package execution gate — 2026-10-04

- PR #11 latest head `fac440b4d7b2c97a1c9234ff1b7d162e03390a4d` passed remote CI `37229585080`, then merged normally as `affc86625734bc7825439faef94e69b669b52c76`. New workflow is registered active on GitHub (workflow ID `374835660`). Before first dispatch, run listing is empty.
- Completed the execution source-identity gate: read actual installed Conda metadata independently in OCI and SIF; require exact pinned package name, scientific version, build, artifact filename, subdir and SHA256/MD5. Bind normalized package-artifact identity into existing schema-v1 build inputs. Missing/mismatched metadata blocks; no version, package pin or schema change was made.
- Added 84 package-pin negative regressions plus durable reconciliation-failure summary coverage. New adapter suite **154 tests**; full repository **8,392 PASS / 13 SKIP / 0 FAIL**. Ruff/actionlint/diff checks PASS. This patch needs fresh remote CI before the first native dispatch. No actual build/publication has occurred.
48 changes: 43 additions & 5 deletions scripts/contract_native_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@

TOOLS = ("3ddna_extra", "abricate", "accelerate", "agat_extra", "aicsimageio_extra", "airr_extra", "alevin_fry")
RUNNERS = {"amd64": "ubuntu-24.04", "arm64": "ubuntu-24.04-arm"}
GATES = ("runner", "oci_identity", "oci_architecture", "oci_executable", "oci_version", "oci_smoke",
"sif_structure", "sif_architecture", "sif_executable", "sif_version", "sif_smoke", "oci_sif_binding", "identity", "provenance")
GATES = ("runner", "oci_identity", "oci_architecture", "oci_executable", "oci_package", "oci_version", "oci_smoke",
"sif_structure", "sif_architecture", "sif_executable", "sif_package", "sif_version", "sif_smoke", "oci_sif_binding", "identity", "provenance")


class ValidationError(ValueError):
Expand All @@ -52,6 +52,30 @@ def fixture_hashes(contract: dict, repo: Path) -> dict:
return result


def package_pin(contract: dict, arch: str) -> dict:
pins = contract["pinning"]["package_artifacts"]
declaration = pins.get(arch, pins.get("noarch", ""))
match = re.fullmatch(r"([a-zA-Z0-9_.-]+\.(?:conda|tar\.bz2));(sha256|md5):([0-9a-f]+)", declaration)
if not match or len(match[3]) != {"sha256": 64, "md5": 32}[match[2]]:
raise ValidationError("missing immutable package artifact pin")
filename, algorithm, checksum = match.groups()
name, version = contract["pinning"]["package_name"], contract["scientific_version"]
stem = filename.removesuffix(".conda").removesuffix(".tar.bz2")
prefix = f"{name}-{version}-"
if not stem.startswith(prefix) or not stem[len(prefix):]:
raise ValidationError("package pin name/version mismatch")
return {"name": name, "version": version, "build": stem[len(prefix):], "fn": filename,
"subdir": "noarch" if arch not in pins else {"amd64": "linux-64", "arm64": "linux-aarch64"}[arch],
algorithm: checksum}


def verify_package(metadata: dict, contract: dict, arch: str) -> dict:
expected = package_pin(contract, arch)
if not isinstance(metadata, dict) or any(metadata.get(key) != value for key, value in expected.items()):
raise ValidationError("installed package differs from pinned name/version/build/subdir/checksum")
return expected


def load_contract(repo: Path, tool: str) -> dict:
if tool not in TOOLS:
raise ValidationError("tool outside exact approved canary")
Expand All @@ -72,6 +96,8 @@ def load_contract(repo: Path, tool: str) -> dict:
raise ValidationError("commands must be nonempty argument arrays")
if value.get("version_output_source") not in ("stdout", "stderr", "either") or not value.get("version_observation_parser"):
raise ValidationError("missing version stream contract")
for arch in RUNNERS:
package_pin(value, arch)
fixture_hashes(value, repo)
return value

Expand Down Expand Up @@ -280,6 +306,8 @@ def validate_entry(record: dict, entry: dict, plan: dict, contract: dict, repo:
inspect_sif(record["sif_inspect"], binding_labels(contract, plan, entry, identity["base_image_identity"]), entry["arch"])
for phase in ("oci", "sif"):
evidence = record[phase]
if verify_package(evidence["package_metadata"], contract, entry["arch"]) != identity["build_inputs"]["package_artifact"]:
raise ValidationError("installed package identity differs from candidate")
if evidence["runtime_architecture"] != {"amd64": "x86_64", "arm64": "aarch64"}[entry["arch"]]:
raise ValidationError("phase runtime architecture mismatch")
if validate_executable({"stdout": evidence["executable"]["output"]}, entry["arch"]) != evidence["executable"]:
Expand Down Expand Up @@ -387,13 +415,17 @@ def invoke(argv):
if machine != {"amd64": "x86_64", "arm64": "aarch64"}[arch]:
raise ValidationError("artifact runtime architecture mismatch")
executable = validate_executable(invoke(["/bin/sh", "-c", EXECUTABLE_PROBE, "probe", contract["expected_executable"]]), arch)
pin = package_pin(contract, arch)
metadata_path = f"/opt/conda/conda-meta/{pin['name']}-{pin['version']}-{pin['build']}.json"
package_metadata = json.loads(invoke(["/bin/cat", metadata_path])["stdout"])
verify_package(package_metadata, contract, arch)
version_raw = invoke(contract["version_command"])
version = validate_version_evidence(contract, repo, stdout=version_raw["stdout"], stderr=version_raw["stderr"], returncode=version_raw["returncode"])
smoke_raw = invoke(contract["smoke_command"])
smoke = smoke_result(contract, repo, work, smoke_raw)
record[phase] = {"runtime_architecture": machine, "executable": executable, "version": version, "version_raw": version_raw,
record[phase] = {"runtime_architecture": machine, "executable": executable, "package_metadata": package_metadata, "version": version, "version_raw": version_raw,
"smoke": smoke, "smoke_raw": smoke_raw}
for gate in ("architecture", "executable", "version", "smoke"):
for gate in ("architecture", "executable", "package", "version", "smoke"):
record["gates"][f"{phase}_{gate}"] = "PASS"
if sha256_file(archive) != oci["oci_archive_sha256"] or record["oci"]["version"]["observed_version"] != record["sif"]["version"]["observed_version"]:
raise ValidationError("OCI/SIF binding mismatch")
Expand All @@ -406,7 +438,7 @@ def invoke(argv):
"base_image_identity": base_identity, "oci_source_identity": oci["oci_identity"], "sif_sha256": record["sif_sha256"],
"expected_executable": contract["expected_executable"], "version_evidence": record["oci"]["version"]["observed_version"],
"smoke_status": "PASS", "provenance_status": "PASS", "build_inputs": {"validation_contract_sha256": entry["contract_sha256"],
"fixture_sha256": entry["fixture_sha256"], "source_immutable_identity": contract["source_immutable_identity"]}}
"fixture_sha256": entry["fixture_sha256"], "source_immutable_identity": contract["source_immutable_identity"], "package_artifact": package_pin(contract, arch)}}
record["candidate"] = identity_metadata(candidate)
record["gates"]["identity"] = "PASS"
record["gates"]["provenance"] = "PASS"
Expand Down Expand Up @@ -435,6 +467,7 @@ def main(argv=None) -> int:
parser.add_argument("--authorize-native-execution", action="store_true")
parser.add_argument("--github-output", type=Path)
args = parser.parse_args(argv)
records = None
try:
if args.command == "plan":
plan = make_plan(args.repo, args.source_commit or "")
Expand All @@ -457,6 +490,11 @@ def main(argv=None) -> int:
write_json(args.output, reconcile(plan, args.repo, args.plan_sha256, records, roots))
return 0
except (ValueError, OSError, TypeError, KeyError) as error:
if args.command == "reconcile" and args.output:
write_json(args.output, {"status": "FAIL", "expected_entries": 14,
"observed_records": len(records) if records is not None else None,
"error": f"{type(error).__name__}: {error}", "release_complete": False,
"publication_authorized": False})
print(f"FAIL: {error}")
return 1

Expand Down
28 changes: 28 additions & 0 deletions tests/test_contract_native_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,8 @@ def __call__(self, argv, *, timeout=60):
text = "x86_64\n" if self.arch == "amd64" else "aarch64\n"
elif command[:2] == ["/bin/sh", "-c"]:
text = "executable=/opt/conda/bin/tool\ninterpreter=/opt/conda/bin/python\nmachine=" + ("62 0\n" if self.arch == "amd64" else "183 0\n")
elif command[0] == "/bin/cat":
text = json.dumps(native.package_pin(self.contract, self.arch))
elif command == self.contract["version_command"]:
text = BANNERS[self.tool]
elif command == self.contract["smoke_command"]:
Expand Down Expand Up @@ -323,3 +325,29 @@ def popen(argv, **kwargs):
record = json.loads((tmp_path / "commands/001.json").read_text())
assert record["timed_out"] == (state == "timeout")
assert bool(killed) == (state == "timeout")


@pytest.mark.parametrize("tool", native.TOOLS)
@pytest.mark.parametrize("arch", native.RUNNERS)
@pytest.mark.parametrize("field", ["name", "version", "build", "fn", "subdir", "checksum"])
def test_installed_package_pin_fail_closed(tool, arch, field):
contract = native.load_contract(REPO, tool)
pin = native.package_pin(contract, arch)
assert native.verify_package(pin, contract, arch) == pin
key = field if field != "checksum" else ("sha256" if "sha256" in pin else "md5")
pin[key] = "wrong"
with pytest.raises(native.ValidationError):
native.verify_package(pin, contract, arch)


def test_reconciliation_failure_summary_is_durable(plan, tmp_path):
path = tmp_path / "plan.json"
native.write_json(path, plan)
directory = tmp_path / "records"
directory.mkdir()
output = tmp_path / "summary.json"
assert native.main(["reconcile", "--repo", str(REPO), "--plan", str(path), "--plan-sha256", plan["plan_sha256"],
"--records", str(directory), "--output", str(output)]) == 1
result = json.loads(output.read_text())
assert result["status"] == "FAIL" and result["observed_records"] == 0
assert result["release_complete"] is False and result["publication_authorized"] is False
Loading