//! Negative production-process evidence for Stage 3c2. //! //! This module is nested under `phase11_gate`, so every case uses the accepted //! bounded harness and the actual `CARGO_BIN_EXE_hexwork` binary. #![cfg(any(target_os = "linux", target_os = "macos"))] use super::{field, support::Harness}; use hexwork_save::{read_file_bounded, MAX_COMPRESSED_BYTES, MAX_VAULT_BYTES}; use std::fs::{self, File, OpenOptions}; use std::io::Write; use std::path::{Path, PathBuf}; use std::process::Output; const SCRIPT_CAP: u64 = 8 * 1024 * 1024; const REPLAY_CAP: u64 = 64 * 1024 * 1024; const REJECTED_SCRIPT: &str = "observe state\ninput wait\nsave\n"; // Historical v1 shape retained verbatim from hexwork-save's // `real_v1_vault_rejects_before_v2_body_and_missing_is_explicit` fixture. const OLD_V1_VAULT: &[u8] = br#"{"version":1,"mapped_regions":[],"recipes":[],"translated_hymns":[],"ghosts":[]}"#; // These exact files were emitted and decoded by the accepted v3 encoder at // 97edbd8dc95e805bfe4f9167ea09420d2853baa2 in an isolated git archive. Keep // that provenance alongside every process-level reuse of the historical bytes. const GENUINE_V3_RUN: &[u8] = include_bytes!("../../../hexwork-save/testfixtures/run-v3-97edbd8.hexrun"); const GENUINE_V3_REPLAY: &[u8] = include_bytes!("../../../hexwork-save/testfixtures/replay-v3-97edbd8.hexdemo"); struct Baseline { run_id: String, run_path: PathBuf, vault_path: PathBuf, run_bytes: Vec, vault_bytes: Vec, run_entries: Vec, } fn bounded_bytes(path: &Path, cap: usize) -> Vec { read_file_bounded(path, cap) .unwrap_or_else(|error| panic!("bounded snapshot {}: {error}", path.display())) } fn use_private_content(harness: &mut Harness) { let canonical = harness.content.clone(); let private = harness.root.join("content"); for relative in hexwork_content::manifest() { let source = canonical.join(relative); let destination = private.join(relative); fs::create_dir_all(destination.parent().expect("manifest parent")) .expect("create private content parent"); fs::copy(&source, &destination).unwrap_or_else(|error| { panic!( "copy canonical content {} to {}: {error}", source.display(), destination.display() ) }); } hexwork_content::RuntimePack::load(&private).expect("private canonical pack remains valid"); harness.content = private; } fn run_entries(harness: &Harness) -> Vec { let mut entries = fs::read_dir(harness.data.join("runs")) .expect("managed runs directory") .map(|entry| { entry .expect("managed run entry") .file_name() .into_string() .expect("managed run name is UTF-8") }) .collect::>(); entries.sort(); entries } fn initialize_baseline(harness: &Harness, seed: u64) -> Baseline { let script = harness.script("baseline.commands", "save\nquit\n"); let mut command = harness.managed(&script); command.arg("--seed").arg(seed.to_string()); let output = Harness::run(command); assert!( output.status.success(), "baseline stderr: {}", String::from_utf8_lossy(&output.stderr) ); let records = Harness::records(&output); let run_id = records .iter() .find(|record| field(record, "code") == "allocation-durable") .and_then(|record| field(record, "run_id").as_str()) .expect("baseline allocation run ID") .to_owned(); let run_path = harness.data.join("runs").join(&run_id).join("run.save"); let vault_path = harness.data.join("lineage-vault.json"); Baseline { run_id, run_bytes: bounded_bytes(&run_path, MAX_COMPRESSED_BYTES), vault_bytes: bounded_bytes(&vault_path, MAX_VAULT_BYTES), run_entries: run_entries(harness), run_path, vault_path, } } fn assert_exit(output: &Output, expected: i32, code: &str) { assert_eq!( output.status.code(), Some(expected), "expected exit {expected} ({code}); stdout={} stderr={}", String::from_utf8_lossy(&output.stdout), String::from_utf8_lossy(&output.stderr) ); assert!( String::from_utf8_lossy(&output.stderr).contains(code), "missing error code {code}: {}", String::from_utf8_lossy(&output.stderr) ); } fn assert_no_play(output: &Output) { let records = Harness::records(output); assert!( records.iter().all(|record| { field(record, "record_kind") != "observation" && field(record, "record_kind") != "command_ack" && field(record, "code") != "allocation-durable" }), "failure exposed play, executed a command, or allocated a run: {records:?}" ); } fn assert_baseline_preserved(harness: &Harness, baseline: &Baseline) { assert_eq!( bounded_bytes(&baseline.run_path, MAX_COMPRESSED_BYTES), baseline.run_bytes, "managed run evidence changed" ); assert_eq!( bounded_bytes(&baseline.vault_path, MAX_VAULT_BYTES), baseline.vault_bytes, "vault evidence changed" ); assert_eq!(run_entries(harness), baseline.run_entries, "run allocated"); } fn rejected_load(harness: &Harness, script: &Path, run_id: &str) -> std::process::Command { let mut command = harness.managed(script); command.arg("--load").arg(run_id); command } #[test] fn missing_script_and_replay_are_io_class_before_artifact_root_write() { let mut harness = Harness::new("negative-missing-inputs"); use_private_content(&mut harness); let missing_script = harness.root.join("missing-script.commands"); let output = Harness::run(harness.managed(&missing_script)); assert_exit(&output, 4, "script-read"); assert_no_play(&output); assert!( !harness.data.exists(), "missing script opened artifact root" ); let output = Harness::run({ let mut command = harness.command(); command .arg("--content-root") .arg(&harness.content) .arg("--replay") .arg(harness.root.join("missing-replay.hexdemo")); command }); assert_exit(&output, 4, "replay-read"); assert_no_play(&output); assert!( !harness.data.exists(), "missing replay opened artifact root" ); } #[test] fn sparse_cap_plus_one_script_and_replay_are_limit_class_before_root_write() { let mut harness = Harness::new("negative-sparse-limits"); use_private_content(&mut harness); let script = harness.root.join("script-cap-plus-one.commands"); File::create(&script) .expect("create sparse script") .set_len(SCRIPT_CAP + 1) .expect("size sparse script"); let output = Harness::run(harness.managed(&script)); assert_exit(&output, 5, "script-read"); assert_no_play(&output); assert!( !harness.data.exists(), "oversize script opened artifact root" ); let replay = harness.root.join("replay-cap-plus-one.hexdemo"); File::create(&replay) .expect("create sparse replay") .set_len(REPLAY_CAP + 1) .expect("size sparse replay"); let output = Harness::run({ let mut command = harness.command(); command .arg("--content-root") .arg(&harness.content) .arg("--replay") .arg(&replay); command }); assert_exit(&output, 5, "replay-read"); assert_no_play(&output); assert!( !harness.data.exists(), "oversize replay opened artifact root" ); } #[test] fn malformed_and_path_shaped_load_ids_are_usage_class_without_play() { let mut harness = Harness::new("negative-load-id"); use_private_content(&mut harness); let script = harness.script("must-not-run.commands", REJECTED_SCRIPT); for value in [ "run-1", "../run-00000000000000000001", "run-00000000000000000001/../../escape", ] { let output = Harness::run(rejected_load(&harness, &script, value)); assert_exit(&output, 2, "invalid --load RUN_ID"); assert_no_play(&output); assert!( !harness.data.exists(), "invalid load ID {value:?} opened artifact root" ); } } #[test] fn generated_run_seed_and_mode_mismatch_preserve_evidence_and_allocation() { let mut harness = Harness::new("negative-load-constraints"); use_private_content(&mut harness); let baseline = initialize_baseline(&harness, 4_101); let script = harness.script("constraint-must-not-run.commands", REJECTED_SCRIPT); let mut seed = rejected_load(&harness, &script, &baseline.run_id); seed.arg("--seed").arg("4102"); let output = Harness::run(seed); assert_exit(&output, 3, "seed-mismatch"); assert_no_play(&output); assert_baseline_preserved(&harness, &baseline); let mut mode = rejected_load(&harness, &script, &baseline.run_id); mode.arg("--story"); let output = Harness::run(mode); assert_exit(&output, 3, "mode-mismatch"); assert_no_play(&output); assert_baseline_preserved(&harness, &baseline); } #[test] fn corrupt_managed_run_and_vault_are_retained_without_play() { let mut harness = Harness::new("negative-corrupt-managed"); use_private_content(&mut harness); let baseline = initialize_baseline(&harness, 4_201); let script = harness.script("corrupt-must-not-run.commands", REJECTED_SCRIPT); let corrupt_run = b"phase11 deliberately corrupt run evidence\n"; fs::write(&baseline.run_path, corrupt_run).expect("install corrupt run"); let output = Harness::run(rejected_load(&harness, &script, &baseline.run_id)); assert_exit(&output, 3, "artifact-run"); assert_no_play(&output); assert_eq!( bounded_bytes(&baseline.run_path, MAX_COMPRESSED_BYTES), corrupt_run, "failed load rewrote corrupt run evidence" ); assert_eq!( bounded_bytes(&baseline.vault_path, MAX_VAULT_BYTES), baseline.vault_bytes, "corrupt run load changed vault" ); assert_eq!(run_entries(&harness), baseline.run_entries, "run allocated"); fs::write(&baseline.run_path, &baseline.run_bytes).expect("restore original run fixture"); assert_baseline_preserved(&harness, &baseline); let corrupt_vault = b"{deliberately-not-json}\n"; fs::write(&baseline.vault_path, corrupt_vault).expect("install corrupt vault"); let output = Harness::run(rejected_load(&harness, &script, &baseline.run_id)); assert_exit(&output, 3, "artifact-schema"); assert_no_play(&output); assert_eq!( bounded_bytes(&baseline.vault_path, MAX_VAULT_BYTES), corrupt_vault, "failed load rewrote corrupt vault evidence" ); assert_eq!( bounded_bytes(&baseline.run_path, MAX_COMPRESSED_BYTES), baseline.run_bytes, "corrupt vault load changed run" ); assert_eq!(run_entries(&harness), baseline.run_entries, "run allocated"); fs::write(&baseline.vault_path, &baseline.vault_bytes).expect("restore original vault fixture"); assert_baseline_preserved(&harness, &baseline); } #[test] fn genuine_v3_run_replay_and_v1_vault_reject_as_old_without_play() { let mut harness = Harness::new("negative-old-production-formats"); use_private_content(&mut harness); let baseline = initialize_baseline(&harness, 4_301); let script = harness.script("old-must-not-run.commands", REJECTED_SCRIPT); fs::write(&baseline.run_path, GENUINE_V3_RUN).expect("install genuine v3 run"); let output = Harness::run(rejected_load(&harness, &script, &baseline.run_id)); assert_exit(&output, 3, "artifact-run"); assert!( String::from_utf8_lossy(&output.stderr).contains(&format!( "older than supported version {}", hexwork_save::RUN_FORMAT_VERSION )), "v3 run rejection was not explicit: {}", String::from_utf8_lossy(&output.stderr) ); assert_no_play(&output); assert_eq!( bounded_bytes(&baseline.run_path, MAX_COMPRESSED_BYTES), GENUINE_V3_RUN, "failed load rewrote genuine v3 run" ); assert_eq!(run_entries(&harness), baseline.run_entries, "run allocated"); fs::write(&baseline.run_path, &baseline.run_bytes).expect("restore current run fixture"); assert_baseline_preserved(&harness, &baseline); let replay_path = harness.root.join("genuine-v3.hexdemo"); fs::write(&replay_path, GENUINE_V3_REPLAY).expect("install genuine v3 replay"); let output = Harness::run({ let mut command = harness.command(); command .arg("--content-root") .arg(&harness.content) .arg("--replay") .arg(&replay_path); command }); assert_exit(&output, 3, "replay-decode"); assert!( String::from_utf8_lossy(&output.stderr).contains(&format!( "older than supported version {}", hexwork_save::RUN_FORMAT_VERSION )), "v3 replay rejection was not explicit: {}", String::from_utf8_lossy(&output.stderr) ); assert_no_play(&output); assert_eq!( bounded_bytes(&replay_path, MAX_COMPRESSED_BYTES), GENUINE_V3_REPLAY, "failed replay rewrote genuine v3 evidence" ); assert_baseline_preserved(&harness, &baseline); fs::write(&baseline.vault_path, OLD_V1_VAULT).expect("install historical v1 vault"); let output = Harness::run(rejected_load(&harness, &script, &baseline.run_id)); assert_exit(&output, 3, "artifact-schema"); assert!( String::from_utf8_lossy(&output.stderr).contains("older than supported version 2"), "v1 vault rejection was not explicit: {}", String::from_utf8_lossy(&output.stderr) ); assert_no_play(&output); assert_eq!( bounded_bytes(&baseline.vault_path, MAX_VAULT_BYTES), OLD_V1_VAULT, "failed load rewrote historical v1 vault" ); assert_eq!(run_entries(&harness), baseline.run_entries, "run allocated"); fs::write(&baseline.vault_path, &baseline.vault_bytes).expect("restore current vault fixture"); assert_baseline_preserved(&harness, &baseline); } #[test] fn valid_changed_content_hash_rejects_generated_run_and_preserves_sources() { let mut harness = Harness::new("negative-content-hash"); use_private_content(&mut harness); let baseline = initialize_baseline(&harness, 4_401); let script = harness.script("content-must-not-run.commands", REJECTED_SCRIPT); let changed_root = harness.root.join("changed-content"); let manifest = hexwork_content::manifest(); let source_items = harness.content.join(manifest[0]); let source_items_before = bounded_bytes(&source_items, MAX_VAULT_BYTES); for relative in &manifest { let source = harness.content.join(relative); let destination = changed_root.join(relative); fs::create_dir_all(destination.parent().expect("manifest parent")) .expect("create copied content parent"); fs::copy(&source, &destination).unwrap_or_else(|error| { panic!( "copy canonical content {} to {}: {error}", source.display(), destination.display() ) }); } let changed_items = changed_root.join(manifest[0]); OpenOptions::new() .append(true) .open(&changed_items) .expect("open copied items") .write_all(b"\n# phase11 valid content-hash change\n") .expect("append valid TOML comment"); hexwork_content::RuntimePack::load(&changed_root).expect("changed copied pack remains valid"); let mut command = rejected_load(&harness, &script, &baseline.run_id); command.args(["--content-root"]).arg(&changed_root); let output = Harness::run(command); assert_exit(&output, 3, "artifact-run"); assert!( String::from_utf8_lossy(&output.stderr).contains("content hash mismatch"), "changed pack did not reach hash rejection: {}", String::from_utf8_lossy(&output.stderr) ); assert_no_play(&output); assert_baseline_preserved(&harness, &baseline); assert_eq!( bounded_bytes(&source_items, MAX_VAULT_BYTES), source_items_before, "canonical source content changed" ); }