lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README

commit 714a918e302a5b28f6b0dbca7fe23069d6fec2c3
parent 867cd5d6c86416195785771b5ebcfb387d7ab8de
Author: triesap <tyson@radroots.org>
Date:   Sun,  6 Sep 2026 23:12:44 +0000

fix: route Step 298 through governed xtask

- Replace the provisional Python gate with a Rust xtask command.
- Use only tools present in the frozen Step 297 manifest.
- Preserve exact mutation and Nix evaluation coverage.
- Emit the required source-bound typed gate result.

Diffstat:
Mcontracts/rshr-202-step-298-gates.v1.json | 2+-
Dtools/rshr_202_step_298_gate.py | 320-------------------------------------------------------------------------------
Mtools/xtask/src/main.rs | 35+++++++++++++++++++++++++++++++++++
Atools/xtask/src/rshr_202_step_298_gate.rs | 356+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 392 insertions(+), 321 deletions(-)

diff --git a/contracts/rshr-202-step-298-gates.v1.json b/contracts/rshr-202-step-298-gates.v1.json @@ -1 +1 @@ -{"gate_command_contract":[{"argv_template":["cargo","extbuild","run","--","uv","run","--offline","--no-project","python3","-B","tools/rshr_202_step_298_gate.py","--step={step}","--check-id={check_id}","--source-revision={source_revision}","--source-tree={source_tree}","--candidate-digest={candidate_digest}","--platform=macos_aarch64","--execution-request-sha256={execution_request_sha256}"],"assertion_id":["step_298_gate_01_14c62391f40dcf5a2e166bac481c9eb8f50052ee65403ddbd63df5cc82ec6843"],"check_id":"gate-01-14c62391f40dcf5a2e166bac481c9eb8f50052ee65403ddbd63df5cc82ec6843","environment_authority":{"cache_policy_id":"rshr-200-step-287-cache-policy.v1","cache_policy_sha256":"3e81d178bce97b6c349dfbb00c68fd6f620ac00b1a1c8d37b12e9998f3c9eaaa","cadence_policy_id":"rshr-200-step-287-cadence-policy.v1","cadence_policy_sha256":"d24903df8659ee3772297c84994911efe7d21cb8b988320ddc6ddce0431892a1","isolation":"extbuild_host_constrained","network":"disabled","network_policy_id":"none","network_policy_sha256":"none","resource_policy_id":"rshr-200-step-287-resource-policy.v1","resource_policy_sha256":"05d3c7a89185d3c55678d97955193fce2ed92b1eee5af99083d77ea64c98d14e"},"environment_names":["EXT_BUILD_CONFIG","EXT_BUILD_MACHINE_CONFIG","EXT_BUILD_ROOT","HOME","PATH","RUSTUP_TOOLCHAIN","TMPDIR"],"gate_definition_sha256":"14c62391f40dcf5a2e166bac481c9eb8f50052ee65403ddbd63df5cc82ec6843","required_platforms":["macos_aarch64"],"required_tools":["uv","python3","git","perl"],"result_schema":"radroots.services-hardening.rshr-200-step-check-result.v1","schema":"radroots.services-hardening.rshr-200-step-check-command.v1","step":298,"verifier_path":"tools/rshr_202_step_298_gate.py","verifier_sha256":"828950a36acd01e995833fd24ccb5c8226d776e7d876e1890561c145598a54e2"}],"schema":"radroots.lib.rshr-202-step-298-gates.v1","step":[298]} +{"gate_command_contract":[{"argv_template":["cargo","extbuild","run","--","cargo","run","--offline","--locked","-q","-p","xtask","--","rshr-step-298-gate","--step={step}","--check-id={check_id}","--source-revision={source_revision}","--source-tree={source_tree}","--candidate-digest={candidate_digest}","--platform=macos_aarch64","--execution-request-sha256={execution_request_sha256}"],"assertion_id":["step_298_gate_01_14c62391f40dcf5a2e166bac481c9eb8f50052ee65403ddbd63df5cc82ec6843"],"check_id":"gate-01-14c62391f40dcf5a2e166bac481c9eb8f50052ee65403ddbd63df5cc82ec6843","environment_authority":{"cache_policy_id":"rshr-200-step-287-cache-policy.v1","cache_policy_sha256":"3e81d178bce97b6c349dfbb00c68fd6f620ac00b1a1c8d37b12e9998f3c9eaaa","cadence_policy_id":"rshr-200-step-287-cadence-policy.v1","cadence_policy_sha256":"d24903df8659ee3772297c84994911efe7d21cb8b988320ddc6ddce0431892a1","isolation":"extbuild_host_constrained","network":"disabled","network_policy_id":"none","network_policy_sha256":"none","resource_policy_id":"rshr-200-step-287-resource-policy.v1","resource_policy_sha256":"05d3c7a89185d3c55678d97955193fce2ed92b1eee5af99083d77ea64c98d14e"},"environment_names":["EXT_BUILD_CONFIG","EXT_BUILD_MACHINE_CONFIG","EXT_BUILD_ROOT","HOME","PATH","RUSTUP_TOOLCHAIN","TMPDIR"],"gate_definition_sha256":"14c62391f40dcf5a2e166bac481c9eb8f50052ee65403ddbd63df5cc82ec6843","required_platforms":["macos_aarch64"],"required_tools":[],"result_schema":"radroots.services-hardening.rshr-200-step-check-result.v1","schema":"radroots.services-hardening.rshr-200-step-check-command.v1","step":298,"verifier_path":"tools/xtask/src/rshr_202_step_298_gate.rs","verifier_sha256":"45a00523fd6cf73e22734af65b2023cc986e5d43de9afda44724239d0c703c38"}],"schema":"radroots.lib.rshr-202-step-298-gates.v1","step":[298]} diff --git a/tools/rshr_202_step_298_gate.py b/tools/rshr_202_step_298_gate.py @@ -1,320 +0,0 @@ -#!/usr/bin/env python3 -"""Emit the source-bound RSHR-202 gate result for Lib Step 298.""" - -from __future__ import annotations - -import argparse -import hashlib -import json -import os -import shutil -import sys -from pathlib import Path - -import rshr_201_step_gate as shared - - -ROOT = Path(__file__).resolve().parent.parent -AUTHORITY_PATH = ROOT / "contracts/rshr-202-step-298-gates.v1.json" -ORIGIN = "ssh://git@github.com/radrootslabs/lib.git" -BRANCH = "rshr/rcld-202" -STEP = 298 -GATE_DEFINITION = ( - "contract, transition, source-lock, Windows, macOS-x86_64, Linux-aarch64, " - "and undeclared-system mutation vectors" -) -GATE_DIGEST = hashlib.sha256(GATE_DEFINITION.encode("utf-8")).hexdigest() -CHECK_ID = f"gate-01-{GATE_DIGEST}" -ASSERTION_ID = f"step_{STEP:03d}_gate_01_{GATE_DIGEST}" -EXPECTED_ARGV_TEMPLATE = [ - "cargo", - "extbuild", - "run", - "--", - "uv", - "run", - "--offline", - "--no-project", - "python3", - "-B", - "tools/rshr_202_step_298_gate.py", - "--step={step}", - "--check-id={check_id}", - "--source-revision={source_revision}", - "--source-tree={source_tree}", - "--candidate-digest={candidate_digest}", - "--platform=macos_aarch64", - "--execution-request-sha256={execution_request_sha256}", -] -EXPECTED_FILES = { - "contracts/architecture/decisions/services_hardening_source_lock.v3.json": ( - "3bc32c8ca2cecb06c8f8239ab1fe1fcfba93fe3ef0d60e9b078390347d08f817" - ), - "contracts/release/lib-artifact-contract.v3.json": ( - "bc352a132dd4c0e6f1d2ae7449998833efe1fdda2ab851e512bc9241c49edbf0" - ), - "contracts/architecture/decisions/services_hardening_build_qualification.v3.json": ( - "4f1bf59e6411c28c9b202c81ed9455c3446525fc39c8e96276a48e4223de1394" - ), - "build/nix/service/systems.nix": ( - "d16e21827022a2315234f4c5e4b485017a36ecd90a5559b01d23331cdd505e46" - ), - "build/nix/service/fixture.nix": ( - "d9f4ec24762b2cadb4aed45518f81e53308d4e1bf7ff2708aec74a1485269402" - ), - "build/nix/service/oci.nix": ( - "9111ce51465bbe45944b718e5e2477b43a08f8f7d9e29f43940a67e86e75daf1" - ), - "contracts/releases/target_matrix.toml": ( - "28583b0a163e51468d9688b463902ec2cd22b59c99061630596839baf9396527" - ), - "tools/xtask/src/service_build_qualification.rs": ( - "20dbae0f446bdd95e99f84d1c27ef4dec422eae8035ead5876adba047db20a9f" - ), - "tools/xtask/src/target_qualification.rs": ( - "0e5b9506c70f5175edeae7cf9b7fb0f55a0cb6abf465a3f708d4234b2069c585" - ), -} -EXPECTED_TESTS = { - "service_build_qualification::tests": [ - "service_build_qualification::tests::checked_in_contract_and_fixture_are_exact", - "service_build_qualification::tests::contract_inventory_is_literal_and_complete", - "service_build_qualification::tests::contract_rejects_every_independent_governed_field_drift", - "service_build_qualification::tests::errors_are_fixed_and_source_free", - "service_build_qualification::tests::fixture_rejects_every_identity_and_lockfile_drift", - "service_build_qualification::tests::fixture_rejects_every_independent_metadata_drift", - ], - "target_qualification::tests": [ - "target_qualification::tests::current_contract_selects_exact_toolchains_targets_and_packages", - "target_qualification::tests::unsupported_production_targets_are_rejected", - ], -} -EXPECTED_NIX_SHA256 = ( - "a59ab70f97f6d571642d13c7506aafec0a4275520d53daee2d8451be7c495cd1" -) -EXPECTED_NIX_VERSION_SHA256 = ( - "6db806391ffaea4cdb08ade0031feac399c0cd08474b3bfde8cb33f88a36c8e1" -) - - -def run_cargo(arguments: list[str], *, label: str) -> bytes: - return shared.run( - ["cargo", "+1.97.1", *arguments], shared.gate_environment(), label=label - ) - - -def require_listed_tests(output: bytes, expected: list[str], *, label: str) -> None: - try: - observed = sorted( - line.removesuffix(": test") - for line in output.decode("utf-8", "strict").splitlines() - if line.endswith(": test") - ) - except UnicodeError as error: - raise shared.GateError(f"{label} inventory is not UTF-8") from error - if observed != sorted(expected): - raise shared.GateError(f"{label} inventory differs") - - -def require_source_state(source_revision: str, source_tree: str) -> None: - if ( - shared.git("rev-parse", "HEAD") != source_revision - or shared.git("rev-parse", "HEAD^{tree}") != source_tree - or shared.git("symbolic-ref", "--short", "HEAD") != BRANCH - or shared.git("remote", "get-url", "origin") != ORIGIN - or shared.git("rev-parse", f"refs/remotes/origin/{BRANCH}") != source_revision - or shared.git_bytes("status", "--porcelain=v1", "-z", "--untracked-files=all") - ): - raise shared.GateError("Lib source is not clean and tracking-exact") - tracked = shared.git_bytes("ls-files", "-z").split(b"\0") - if any(path == b".github" or path.startswith(b".github/") for path in tracked): - raise shared.GateError("forbidden .github surface is tracked") - if os.path.lexists(ROOT / ".github"): - raise shared.GateError("forbidden .github surface is present") - - -def require_exact_sources() -> None: - for relative, expected in EXPECTED_FILES.items(): - contents = shared.read_regular(ROOT / relative) - if shared.sha256_bytes(contents) != expected: - raise shared.GateError("Step 298 governed source bytes differ") - - -def run_test_lane(test_filter: str, expected: list[str]) -> None: - base = [ - "test", - "--offline", - "--locked", - "-p", - "xtask", - test_filter, - ] - listed = run_cargo( - [*base, "--", "--list", "--format=terse"], - label=f"Step 298 {test_filter} inventory", - ) - require_listed_tests(listed, expected, label=test_filter) - run_cargo( - [*base, "--", "--test-threads=1"], label=f"Step 298 {test_filter}" - ) - - -def run_nix_lane() -> None: - executable_name = os.environ.get("RSHR_NIX_EXECUTABLE", "nix") - selected = Path(executable_name) - if not selected.is_absolute(): - selected = Path(shutil.which(executable_name) or "") - try: - executable = selected.resolve(strict=True) - except OSError as error: - raise shared.GateError("Step 298 Nix client is unavailable") from error - if ( - not executable.is_file() - or shared.sha256_bytes(executable.read_bytes()) != EXPECTED_NIX_SHA256 - ): - raise shared.GateError("Step 298 Nix client identity differs") - environment = shared.gate_environment() - version = shared.run( - [os.fspath(executable), "--version"], environment, label="Step 298 Nix version" - ) - if shared.sha256_bytes(version) != EXPECTED_NIX_VERSION_SHA256: - raise shared.GateError("Step 298 Nix version differs") - systems = shared.run( - [ - os.fspath(executable), - "--offline", - "eval", - "--json", - "--file", - "build/nix/service/systems.nix", - ], - environment, - label="Step 298 Nix system evaluation", - ) - if systems != b'["aarch64-darwin","x86_64-linux"]\n': - raise shared.GateError("Step 298 Nix systems differ") - shared.run( - [ - os.fspath(executable), - "--offline", - "flake", - "check", - "--no-build", - "--no-write-lock-file", - ], - environment, - label="Step 298 Nix flake evaluation", - ) - - -def run_step() -> None: - require_exact_sources() - run_cargo(["fmt", "--all", "--", "--check"], label="Step 298 formatting") - for test_filter, expected in EXPECTED_TESTS.items(): - run_test_lane(test_filter, expected) - run_cargo( - ["run", "--offline", "--locked", "-q", "-p", "xtask", "--", "contract", "validate"], - label="Step 298 contract validation", - ) - run_nix_lane() - if shared.git_bytes("status", "--porcelain=v1", "-z", "--untracked-files=all"): - raise shared.GateError("verification changed the source state") - - -def parse_arguments() -> argparse.Namespace: - parser = shared.RedactedArgumentParser(allow_abbrev=False) - parser.add_argument("--step", type=int, required=True) - parser.add_argument("--check-id") - parser.add_argument("--source-revision", required=True) - parser.add_argument("--source-tree", required=True) - parser.add_argument("--candidate-digest") - parser.add_argument("--platform", required=True) - parser.add_argument("--execution-request-sha256", required=True) - return parser.parse_args() - - -def expected_contract(verifier_digest: str) -> dict[str, object]: - return { - "argv_template": EXPECTED_ARGV_TEMPLATE, - "assertion_id": [ASSERTION_ID], - "check_id": CHECK_ID, - "environment_authority": shared.EXPECTED_ENVIRONMENT_AUTHORITY, - "environment_names": shared.EXPECTED_ENVIRONMENT_NAMES, - "gate_definition_sha256": GATE_DIGEST, - "required_platforms": ["macos_aarch64"], - "required_tools": ["uv", "python3", "git", "perl"], - "result_schema": "radroots.services-hardening.rshr-200-step-check-result.v1", - "schema": "radroots.services-hardening.rshr-200-step-check-command.v1", - "step": STEP, - "verifier_path": "tools/rshr_202_step_298_gate.py", - "verifier_sha256": verifier_digest, - } - - -def main() -> int: - arguments = parse_arguments() - if arguments.step != STEP: - raise shared.GateError("step is outside the Lib gate authority") - shared.validate_digest(arguments.source_revision, "source revision", 40) - shared.validate_digest(arguments.source_tree, "source tree", 40) - shared.validate_digest(arguments.execution_request_sha256, "execution request", 64) - if arguments.check_id != CHECK_ID: - raise shared.GateError("check identity differs") - if arguments.candidate_digest != "none" or arguments.platform != "macos_aarch64": - raise shared.GateError("candidate or platform scope differs") - - authority_bytes = shared.read_regular(AUTHORITY_PATH, 256 * 1024) - try: - authority = json.loads(authority_bytes) - except (UnicodeError, json.JSONDecodeError) as error: - raise shared.GateError("gate authority is not canonical JSON") from error - if shared.canonical(authority) + b"\n" != authority_bytes: - raise shared.GateError("gate authority is not canonical JSON") - verifier_digest = shared.sha256_bytes(shared.read_regular(Path(__file__).resolve())) - contracts = authority.get("gate_command_contract") - if ( - not isinstance(authority, dict) - or set(authority) != {"schema", "step", "gate_command_contract"} - or authority.get("schema") != "radroots.lib.rshr-202-step-298-gates.v1" - or authority.get("step") != [STEP] - or not isinstance(contracts, list) - or len(contracts) != 1 - or contracts[0] != expected_contract(verifier_digest) - ): - raise shared.GateError("gate command authority differs from source bytes") - - require_source_state(arguments.source_revision, arguments.source_tree) - run_step() - contract = contracts[0] - assertions = [{"id": ASSERTION_ID, "result": "pass"}] - result = { - "schema": "radroots.services-hardening.rshr-200-step-check-result.v1", - "step": STEP, - "check_id": CHECK_ID, - "gate_definition_sha256": GATE_DIGEST, - "source_revision": arguments.source_revision, - "source_tree": arguments.source_tree, - "candidate_generation": 0, - "candidate_digest": "none", - "command_contract_sha256": shared.sha256_bytes(shared.canonical(contract)), - "verifier_sha256": verifier_digest, - "execution_request": [ - {"platform": arguments.platform, "sha256": arguments.execution_request_sha256} - ], - "assertion_inventory_sha256": shared.sha256_bytes(shared.canonical(assertions)), - "assertion": assertions, - "result": "pass", - } - sys.stdout.buffer.write(shared.canonical(result) + b"\n") - return 0 - - -if __name__ == "__main__": - try: - raise SystemExit(main()) - except shared.GateError as error: - print(f"Lib RSHR-202 Step 298 gate failed: {error}", file=sys.stderr) - raise SystemExit(1) - except Exception: - print("Lib RSHR-202 Step 298 gate failed safely", file=sys.stderr) - raise SystemExit(1) diff --git a/tools/xtask/src/main.rs b/tools/xtask/src/main.rs @@ -38,6 +38,7 @@ mod release_graph; mod release_preflight; #[cfg_attr(coverage_nightly, coverage(off))] mod release_qualification; +mod rshr_202_step_298_gate; mod safe_artifact_io; #[cfg_attr(coverage_nightly, coverage(off))] mod safety_qualification; @@ -128,6 +129,23 @@ enum XtaskCommand { #[arg(allow_hyphen_values = true)] args: Vec<String>, }, + #[command(name = "rshr-step-298-gate", hide = true)] + RshrStep298Gate { + #[arg(long)] + step: u16, + #[arg(long)] + check_id: String, + #[arg(long)] + source_revision: String, + #[arg(long)] + source_tree: String, + #[arg(long)] + candidate_digest: String, + #[arg(long)] + platform: String, + #[arg(long)] + execution_request_sha256: String, + }, SourceLock { #[arg(long)] consumer_root: PathBuf, @@ -571,6 +589,23 @@ fn run(args: &[String]) -> Result<(), String> { XtaskCommand::Generate { args } => generate::run(&args, &workspace_root()), XtaskCommand::Hygiene { args } => hygiene::run(&args, &workspace_root()), XtaskCommand::Release { args } => run_release(&args), + XtaskCommand::RshrStep298Gate { + step, + check_id, + source_revision, + source_tree, + candidate_digest, + platform, + execution_request_sha256, + } => rshr_202_step_298_gate::run(rshr_202_step_298_gate::Arguments { + step, + check_id, + source_revision, + source_tree, + candidate_digest, + platform, + execution_request_sha256, + }), XtaskCommand::SourceLock { consumer_root } => { build_control::validate_consumer(&consumer_root).map(|_| ()) } diff --git a/tools/xtask/src/rshr_202_step_298_gate.rs b/tools/xtask/src/rshr_202_step_298_gate.rs @@ -0,0 +1,356 @@ +use std::env; +use std::fs; +use std::path::{Path, PathBuf}; +use std::process::{Command, Output}; + +use serde_json::{Value, json}; +use sha2::{Digest, Sha256}; + +const STEP: u16 = 298; +const GATE_DIGEST: &str = "14c62391f40dcf5a2e166bac481c9eb8f50052ee65403ddbd63df5cc82ec6843"; +const NIX_SHA256: &str = "a59ab70f97f6d571642d13c7506aafec0a4275520d53daee2d8451be7c495cd1"; +const NIX_VERSION_SHA256: &str = "6db806391ffaea4cdb08ade0031feac399c0cd08474b3bfde8cb33f88a36c8e1"; +const MAX_OUTPUT_BYTES: usize = 32 * 1024 * 1024; + +const EXACT_SOURCES: &[(&str, &str)] = &[ + ( + "contracts/architecture/decisions/services_hardening_source_lock.v3.json", + "3bc32c8ca2cecb06c8f8239ab1fe1fcfba93fe3ef0d60e9b078390347d08f817", + ), + ( + "contracts/release/lib-artifact-contract.v3.json", + "bc352a132dd4c0e6f1d2ae7449998833efe1fdda2ab851e512bc9241c49edbf0", + ), + ( + "contracts/architecture/decisions/services_hardening_build_qualification.v3.json", + "4f1bf59e6411c28c9b202c81ed9455c3446525fc39c8e96276a48e4223de1394", + ), + ( + "build/nix/service/systems.nix", + "d16e21827022a2315234f4c5e4b485017a36ecd90a5559b01d23331cdd505e46", + ), + ( + "build/nix/service/fixture.nix", + "d9f4ec24762b2cadb4aed45518f81e53308d4e1bf7ff2708aec74a1485269402", + ), + ( + "build/nix/service/oci.nix", + "9111ce51465bbe45944b718e5e2477b43a08f8f7d9e29f43940a67e86e75daf1", + ), + ( + "contracts/releases/target_matrix.toml", + "28583b0a163e51468d9688b463902ec2cd22b59c99061630596839baf9396527", + ), + ( + "tools/xtask/src/service_build_qualification.rs", + "20dbae0f446bdd95e99f84d1c27ef4dec422eae8035ead5876adba047db20a9f", + ), + ( + "tools/xtask/src/target_qualification.rs", + "0e5b9506c70f5175edeae7cf9b7fb0f55a0cb6abf465a3f708d4234b2069c585", + ), +]; + +const BUILD_TESTS: &[&str] = &[ + "service_build_qualification::tests::checked_in_contract_and_fixture_are_exact", + "service_build_qualification::tests::contract_inventory_is_literal_and_complete", + "service_build_qualification::tests::contract_rejects_every_independent_governed_field_drift", + "service_build_qualification::tests::errors_are_fixed_and_source_free", + "service_build_qualification::tests::fixture_rejects_every_identity_and_lockfile_drift", + "service_build_qualification::tests::fixture_rejects_every_independent_metadata_drift", +]; + +const TARGET_TESTS: &[&str] = &[ + "target_qualification::tests::current_contract_selects_exact_toolchains_targets_and_packages", + "target_qualification::tests::unsupported_production_targets_are_rejected", +]; + +pub(crate) struct Arguments { + pub(crate) step: u16, + pub(crate) check_id: String, + pub(crate) source_revision: String, + pub(crate) source_tree: String, + pub(crate) candidate_digest: String, + pub(crate) platform: String, + pub(crate) execution_request_sha256: String, +} + +fn root() -> PathBuf { + Path::new(env!("CARGO_MANIFEST_DIR")) + .parent() + .and_then(Path::parent) + .expect("xtask must remain under tools/xtask") + .to_path_buf() +} + +fn sha256(bytes: &[u8]) -> String { + hex::encode(Sha256::digest(bytes)) +} + +fn canonical(value: &Value) -> Result<Vec<u8>, String> { + serde_json::to_vec(value).map_err(|_| "Step 298 JSON encoding failed".to_owned()) +} + +fn bounded(command: &mut Command, label: &str) -> Result<Output, String> { + let output = command + .current_dir(root()) + .env("CARGO_NET_OFFLINE", "true") + .env("CARGO_TERM_COLOR", "never") + .output() + .map_err(|_| format!("{label} could not start"))?; + if output.stdout.len() > MAX_OUTPUT_BYTES || output.stderr.len() > MAX_OUTPUT_BYTES { + return Err(format!("{label} exceeded its output bound")); + } + if !output.status.success() { + return Err(format!("{label} failed")); + } + Ok(output) +} + +fn cargo(arguments: &[&str], label: &str) -> Result<Output, String> { + bounded(Command::new("cargo").args(arguments), label) +} + +fn require_test_lane(filter: &str, expected: &[&str]) -> Result<(), String> { + let listed = cargo( + &[ + "+1.97.1", + "test", + "--offline", + "--locked", + "-p", + "xtask", + filter, + "--", + "--list", + "--format=terse", + ], + "Step 298 test inventory", + )?; + let text = std::str::from_utf8(&listed.stdout) + .map_err(|_| "Step 298 test inventory is not UTF-8".to_owned())?; + let mut observed = text + .lines() + .filter_map(|line| line.strip_suffix(": test")) + .collect::<Vec<_>>(); + observed.sort_unstable(); + let mut required = expected.to_vec(); + required.sort_unstable(); + if observed != required { + return Err("Step 298 test inventory differs".to_owned()); + } + cargo( + &[ + "+1.97.1", + "test", + "--offline", + "--locked", + "-p", + "xtask", + filter, + "--", + "--test-threads=1", + ], + "Step 298 mutation lane", + )?; + Ok(()) +} + +fn resolve_nix() -> Result<PathBuf, String> { + if let Some(explicit) = env::var_os("RSHR_NIX_EXECUTABLE") { + return fs::canonicalize(explicit) + .map_err(|_| "Step 298 Nix client is unavailable".to_owned()); + } + let path = env::var_os("PATH").ok_or_else(|| "Step 298 PATH is absent".to_owned())?; + env::split_paths(&path) + .map(|directory| directory.join("nix")) + .find(|candidate| candidate.is_file()) + .and_then(|candidate| fs::canonicalize(candidate).ok()) + .ok_or_else(|| "Step 298 Nix client is unavailable".to_owned()) +} + +fn require_nix() -> Result<(), String> { + let executable = resolve_nix()?; + let bytes = fs::read(&executable).map_err(|_| "Step 298 Nix client is unreadable")?; + if sha256(&bytes) != NIX_SHA256 { + return Err("Step 298 Nix client identity differs".to_owned()); + } + let version = bounded( + Command::new(&executable).arg("--version"), + "Step 298 Nix version", + )?; + if sha256(&version.stdout) != NIX_VERSION_SHA256 { + return Err("Step 298 Nix version differs".to_owned()); + } + let systems = bounded( + Command::new(&executable).args([ + "--offline", + "eval", + "--json", + "--file", + "build/nix/service/systems.nix", + ]), + "Step 298 Nix systems", + )?; + if systems.stdout != b"[\"aarch64-darwin\",\"x86_64-linux\"]\n" { + return Err("Step 298 Nix systems differ".to_owned()); + } + bounded( + Command::new(&executable).args([ + "--offline", + "flake", + "check", + "--no-build", + "--no-write-lock-file", + ]), + "Step 298 Nix flake evaluation", + )?; + Ok(()) +} + +fn expected_contract(verifier_sha256: &str) -> Value { + json!({ + "argv_template": [ + "cargo", "extbuild", "run", "--", "cargo", "run", "--offline", "--locked", + "-q", "-p", "xtask", "--", "rshr-step-298-gate", "--step={step}", + "--check-id={check_id}", "--source-revision={source_revision}", + "--source-tree={source_tree}", "--candidate-digest={candidate_digest}", + "--platform=macos_aarch64", + "--execution-request-sha256={execution_request_sha256}" + ], + "assertion_id": [format!("step_298_gate_01_{GATE_DIGEST}")], + "check_id": format!("gate-01-{GATE_DIGEST}"), + "environment_authority": { + "cache_policy_id": "rshr-200-step-287-cache-policy.v1", + "cache_policy_sha256": "3e81d178bce97b6c349dfbb00c68fd6f620ac00b1a1c8d37b12e9998f3c9eaaa", + "cadence_policy_id": "rshr-200-step-287-cadence-policy.v1", + "cadence_policy_sha256": "d24903df8659ee3772297c84994911efe7d21cb8b988320ddc6ddce0431892a1", + "isolation": "extbuild_host_constrained", + "network": "disabled", + "network_policy_id": "none", + "network_policy_sha256": "none", + "resource_policy_id": "rshr-200-step-287-resource-policy.v1", + "resource_policy_sha256": "05d3c7a89185d3c55678d97955193fce2ed92b1eee5af99083d77ea64c98d14e" + }, + "environment_names": [ + "EXT_BUILD_CONFIG", "EXT_BUILD_MACHINE_CONFIG", "EXT_BUILD_ROOT", "HOME", "PATH", + "RUSTUP_TOOLCHAIN", "TMPDIR" + ], + "gate_definition_sha256": GATE_DIGEST, + "required_platforms": ["macos_aarch64"], + "required_tools": [], + "result_schema": "radroots.services-hardening.rshr-200-step-check-result.v1", + "schema": "radroots.services-hardening.rshr-200-step-check-command.v1", + "step": STEP, + "verifier_path": "tools/xtask/src/rshr_202_step_298_gate.rs", + "verifier_sha256": verifier_sha256 + }) +} + +pub(crate) fn run(arguments: Arguments) -> Result<(), String> { + let check_id = format!("gate-01-{GATE_DIGEST}"); + if arguments.step != STEP + || arguments.check_id != check_id + || arguments.candidate_digest != "none" + || arguments.platform != "macos_aarch64" + || arguments.source_revision.len() != 40 + || arguments.source_tree.len() != 40 + || arguments.execution_request_sha256.len() != 64 + || !arguments + .source_revision + .bytes() + .chain(arguments.source_tree.bytes()) + .chain(arguments.execution_request_sha256.bytes()) + .all(|byte| byte.is_ascii_hexdigit() && !byte.is_ascii_uppercase()) + { + return Err("Step 298 gate arguments differ".to_owned()); + } + let root = root(); + if root.join(".github").exists() { + return Err("forbidden .github surface is present".to_owned()); + } + for (relative, expected) in EXACT_SOURCES { + let bytes = fs::read(root.join(relative)) + .map_err(|_| "Step 298 governed source is unreadable".to_owned())?; + if sha256(&bytes) != *expected { + return Err("Step 298 governed source bytes differ".to_owned()); + } + } + + let verifier_path = root.join("tools/xtask/src/rshr_202_step_298_gate.rs"); + let verifier_sha256 = + sha256(&fs::read(verifier_path).map_err(|_| "Step 298 verifier is unreadable".to_owned())?); + let authority_path = root.join("contracts/rshr-202-step-298-gates.v1.json"); + let authority_bytes = + fs::read(authority_path).map_err(|_| "Step 298 gate authority is unreadable".to_owned())?; + let authority: Value = serde_json::from_slice(&authority_bytes) + .map_err(|_| "Step 298 gate authority is invalid".to_owned())?; + let mut canonical_authority = canonical(&authority)?; + canonical_authority.push(b'\n'); + let contracts = authority + .get("gate_command_contract") + .and_then(Value::as_array) + .ok_or_else(|| "Step 298 gate contract is absent".to_owned())?; + if authority_bytes != canonical_authority + || authority.get("schema") + != Some(&Value::String( + "radroots.lib.rshr-202-step-298-gates.v1".to_owned(), + )) + || authority.get("step") != Some(&json!([STEP])) + || contracts.as_slice() != [expected_contract(&verifier_sha256)] + { + return Err("Step 298 gate authority differs".to_owned()); + } + + cargo( + &["+1.97.1", "fmt", "--all", "--", "--check"], + "Step 298 formatting", + )?; + require_test_lane("service_build_qualification::tests", BUILD_TESTS)?; + require_test_lane("target_qualification::tests", TARGET_TESTS)?; + cargo( + &[ + "+1.97.1", + "run", + "--offline", + "--locked", + "-q", + "-p", + "xtask", + "--", + "contract", + "validate", + ], + "Step 298 contract validation", + )?; + require_nix()?; + + let contract = &contracts[0]; + let assertion = json!([{ + "id": format!("step_298_gate_01_{GATE_DIGEST}"), + "result": "pass" + }]); + let result = json!({ + "schema": "radroots.services-hardening.rshr-200-step-check-result.v1", + "step": STEP, + "check_id": check_id, + "gate_definition_sha256": GATE_DIGEST, + "source_revision": arguments.source_revision, + "source_tree": arguments.source_tree, + "candidate_generation": 0, + "candidate_digest": "none", + "command_contract_sha256": sha256(&canonical(contract)?), + "verifier_sha256": verifier_sha256, + "execution_request": [{ + "platform": arguments.platform, + "sha256": arguments.execution_request_sha256 + }], + "assertion_inventory_sha256": sha256(&canonical(&assertion)?), + "assertion": assertion, + "result": "pass" + }); + let mut bytes = canonical(&result)?; + bytes.push(b'\n'); + std::io::Write::write_all(&mut std::io::stdout().lock(), &bytes) + .map_err(|_| "Step 298 result write failed".to_owned()) +}