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:
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())
+}