commit ad591fe0961202521118124fa583cd2c626ab500
parent b20797d61766228fcf51bb06b141231592d5369f
Author: triesap <tyson@radroots.org>
Date: Sat, 22 Aug 2026 06:57:53 +0000
test(myc): close control-plane first wave
Diffstat:
5 files changed, 399 insertions(+), 4 deletions(-)
diff --git a/contracts/services_hardening/control_plane_wave_090_a.v1.json b/contracts/services_hardening/control_plane_wave_090_a.v1.json
@@ -0,0 +1,51 @@
+{
+ "schema": "radroots.myc.control-plane-wave-090-a.v1",
+ "contract_version": 1,
+ "steps": [152, 153, 154, 155, 156, 157],
+ "positive_corpus": [
+ "one_parse_offline_doctor",
+ "required_doctor_checks_pass",
+ "latest_cached_status_publication",
+ "exact_passive_tcp_routes",
+ "stable_structured_process_result"
+ ],
+ "negative_corpus": [
+ "invalid_cli_is_source_free",
+ "required_doctor_failure_exits_6",
+ "unready_cache_returns_503",
+ "detailed_status_is_not_tcp_routable",
+ "unknown_method_and_query_are_rejected"
+ ],
+ "integration_order": [
+ "one_pass_cli_admission",
+ "sealed_execution_plan",
+ "typed_runtime_context",
+ "ordered_injected_doctor",
+ "immutable_status_publication",
+ "passive_operations_projection",
+ "fixed_structured_diagnostic"
+ ],
+ "authority": {
+ "cli_parse_count": 1,
+ "doctor_observation": "injected",
+ "status_observation": "prevalidated_cached_value",
+ "tcp_routes": ["/livez", "/readyz", "/metrics"],
+ "sqlite_access": "not_performed",
+ "provider_execution": "not_performed",
+ "relay_io": "not_performed",
+ "dns_or_fresh_probe": "not_performed",
+ "diagnostics_stream": "stderr"
+ },
+ "gate": {
+ "wave": "090-a",
+ "complete_after_step": 157,
+ "rcld_promotion_owner": 162
+ },
+ "nonclaims": [
+ "runtime_task_supervision",
+ "signal_handling",
+ "rcld_promotion",
+ "nix",
+ "oci"
+ ]
+}
diff --git a/src/control_plane_wave_090_a.rs b/src/control_plane_wave_090_a.rs
@@ -0,0 +1,278 @@
+//! Native test-only integration gate for RCLD-RSHR-090 wave 090-a.
+
+use std::{
+ collections::BTreeMap,
+ net::{Ipv4Addr, SocketAddrV4, TcpListener},
+};
+
+use tokio::{
+ io::{AsyncReadExt, AsyncWriteExt},
+ net::TcpStream,
+};
+
+use crate::{
+ InstanceId, MycBootstrapProfileV1, MycCliOfflineOperationV1, MycCliPrimaryAuthorityV1,
+ MycConfigProfile, MycConnectionCountsV1, MycDoctorCheckDefinition, MycDoctorCheckId,
+ MycDoctorFuture, MycDoctorObservation, MycDoctorProbe, MycIdentityHealthV1,
+ MycIntegrityStateV1, MycLogRecord, MycOperationsCancellationToken, MycOperationsServer,
+ MycOutboxStatusV1, MycPersistenceHealthV1, MycPersistenceStatusV1, MycProcessResult,
+ MycProviderStatusV1, MycRelayTransportStatusV1, MycServicePhase, MycStatusBuildInfoV1,
+ MycStatusBuildMode, MycStatusCommonV1, MycStatusConfigurationIdentityV1,
+ MycStatusConfigurationSource, MycStatusObservationV1, MycStatusReasonCodes,
+ MycTransportHealthV1, RadrootsHostEnvironment, RadrootsPathResolver, RadrootsPlatform,
+ myc_status_cache, parse_myc_cli_v1_from, parse_myc_config_v1, plan_myc_cli_v1,
+ resolve_myc_runtime_context, run_myc_doctor,
+};
+
+const CONFIG: &str = include_str!("../contracts/services_hardening/config.v1.example.toml");
+const CORPUS: &str =
+ include_str!("../contracts/services_hardening/control_plane_wave_090_a.v1.json");
+const SERVICE_REVISION: &str = "0123456789abcdef0123456789abcdef01234567";
+const LIB_REVISION: &str = "89abcdef0123456789abcdef0123456789abcdef";
+
+struct FixedProbe {
+ observations: BTreeMap<MycDoctorCheckId, MycDoctorObservation>,
+}
+
+impl FixedProbe {
+ fn all(observation: MycDoctorObservation) -> Self {
+ Self {
+ observations: crate::myc_doctor_check_definitions()
+ .iter()
+ .map(|definition| (definition.id(), observation))
+ .collect(),
+ }
+ }
+
+ fn with(mut self, id: MycDoctorCheckId, observation: MycDoctorObservation) -> Self {
+ self.observations.insert(id, observation);
+ self
+ }
+}
+
+impl MycDoctorProbe for FixedProbe {
+ fn probe(&self, definition: MycDoctorCheckDefinition) -> MycDoctorFuture<'_> {
+ let observation = self.observations[&definition.id()];
+ Box::pin(async move { observation })
+ }
+}
+
+fn enabled_config(port: u16) -> String {
+ CONFIG.replacen(
+ "[operations]\nenabled = false",
+ &format!(
+ "[operations]\nenabled = true\nlisten = \"127.0.0.1:{port}\"\nbind_policy = \"loopback_only\"\n\n[operations.limits]\nheader_count = 16\nheader_bytes = 8192\nresponse_body_utf8_bytes = 4096\nconcurrent_connections = 4\nrequest_deadline_ms = 500\nidle_timeout_ms = 500"
+ ),
+ 1,
+ )
+}
+
+fn available_port() -> u16 {
+ TcpListener::bind(SocketAddrV4::new(Ipv4Addr::LOCALHOST, 0))
+ .expect("ephemeral listener")
+ .local_addr()
+ .expect("listener address")
+ .port()
+}
+
+fn status_observation(phase: MycServicePhase, ready: bool) -> MycStatusObservationV1 {
+ let empty = MycStatusReasonCodes::empty;
+ let build = MycStatusBuildInfoV1::new(
+ MycStatusBuildMode::Release,
+ Some(env!("CARGO_PKG_VERSION")),
+ Some(SERVICE_REVISION),
+ Some(LIB_REVISION),
+ Some("1.97.1"),
+ Some("x86_64-unknown-linux-gnu"),
+ Some("service-host"),
+ )
+ .expect("build info");
+ let configuration = MycStatusConfigurationIdentityV1::new(
+ "a".repeat(64),
+ MycStatusConfigurationSource::ExplicitConfig,
+ )
+ .expect("configuration identity");
+ let persistence = MycPersistenceStatusV1::new(
+ MycPersistenceHealthV1::Ready,
+ 9,
+ 1,
+ MycIntegrityStateV1::Verified,
+ empty(),
+ )
+ .expect("persistence status");
+ let identity = || MycIdentityHealthV1::new(true, true, empty()).expect("identity health");
+ let provider = MycProviderStatusV1::new(identity(), identity(), identity(), empty())
+ .expect("provider status");
+ let transport = MycRelayTransportStatusV1::new(MycTransportHealthV1::Ready, true, 2, empty())
+ .expect("transport status");
+ MycStatusObservationV1::new(
+ MycStatusCommonV1::new(phase, ready, empty(), 1, build, configuration, persistence)
+ .expect("common status"),
+ provider,
+ transport,
+ MycConnectionCountsV1::default(),
+ MycOutboxStatusV1::default(),
+ )
+}
+
+async fn raw_request(address: std::net::SocketAddr, request: &[u8]) -> String {
+ let mut stream = TcpStream::connect(address).await.expect("connect");
+ stream.write_all(request).await.expect("request write");
+ let mut response = Vec::new();
+ stream
+ .read_to_end(&mut response)
+ .await
+ .expect("response read");
+ String::from_utf8(response).expect("response UTF-8")
+}
+
+#[test]
+fn machine_corpus_freezes_the_accumulated_wave_and_deferrals() {
+ let corpus: serde_json::Value = serde_json::from_str(CORPUS).expect("wave corpus");
+ assert_eq!(corpus["schema"], "radroots.myc.control-plane-wave-090-a.v1");
+ assert_eq!(corpus["contract_version"], 1);
+ assert_eq!(
+ corpus["steps"],
+ serde_json::json!([152, 153, 154, 155, 156, 157])
+ );
+ assert_eq!(corpus["authority"]["cli_parse_count"], 1);
+ assert_eq!(
+ corpus["authority"]["tcp_routes"],
+ serde_json::json!(["/livez", "/readyz", "/metrics"])
+ );
+ assert_eq!(corpus["gate"]["wave"], "090-a");
+ assert_eq!(corpus["gate"]["complete_after_step"], 157);
+ assert_eq!(corpus["gate"]["rcld_promotion_owner"], 162);
+ assert!(
+ corpus["nonclaims"]
+ .as_array()
+ .expect("nonclaims")
+ .iter()
+ .any(|value| value == "runtime_task_supervision")
+ );
+}
+
+#[tokio::test]
+async fn one_parse_runtime_doctor_and_diagnostics_share_the_exact_exit_contract() {
+ let root = tempfile::tempdir().expect("temporary root");
+ let invocation = parse_myc_cli_v1_from([
+ "myc",
+ "--profile",
+ "repo-local",
+ "--instance",
+ "primary",
+ "--repo-local-root",
+ root.path().to_str().expect("UTF-8 root"),
+ "doctor",
+ ])
+ .expect("doctor CLI");
+ assert_eq!(invocation.profile(), MycBootstrapProfileV1::RepoLocal);
+ let plan = plan_myc_cli_v1(&invocation);
+ assert_eq!(plan.primary_authority(), MycCliPrimaryAuthorityV1::Offline);
+ assert_eq!(
+ plan.offline_operation(),
+ Some(MycCliOfflineOperationV1::Doctor)
+ );
+ let runtime = resolve_myc_runtime_context(
+ &RadrootsPathResolver::new(RadrootsPlatform::Linux, RadrootsHostEnvironment::default()),
+ &invocation,
+ )
+ .expect("runtime context");
+
+ let pass = run_myc_doctor(&runtime, &FixedProbe::all(MycDoctorObservation::Pass))
+ .await
+ .expect("passing doctor");
+ assert_eq!(pass.exit_code(), MycProcessResult::Success.exit_code_u8());
+
+ let fail = run_myc_doctor(
+ &runtime,
+ &FixedProbe::all(MycDoctorObservation::Pass)
+ .with(MycDoctorCheckId::WriterLock, MycDoctorObservation::Fail),
+ )
+ .await
+ .expect("failing doctor report");
+ assert_eq!(
+ fail.exit_code(),
+ MycProcessResult::DoctorRequiredCheckFailed.exit_code_u8()
+ );
+ assert_eq!(
+ MycLogRecord::process_result(MycProcessResult::DoctorRequiredCheckFailed).to_string(),
+ r#"{"schema":"radroots.myc.log.v1","contract_version":1,"service":"myc","level":"error","event":"process_result","code":"doctor_required_check_failed","exit_code":6}"#
+ );
+
+ let secret = "secret-private-key-path";
+ let rejected = parse_myc_cli_v1_from(["myc", &format!("--credential={secret}")])
+ .expect_err("ungoverned argument");
+ assert!(!format!("{rejected:?} {rejected}").contains(secret));
+}
+
+#[tokio::test]
+async fn live_status_and_operations_share_only_the_latest_passive_projection() {
+ let live = parse_myc_cli_v1_from([
+ "myc",
+ "--profile",
+ "service-host",
+ "--instance",
+ "primary",
+ "status",
+ ])
+ .expect("live status CLI");
+ let plan = plan_myc_cli_v1(&live);
+ assert_eq!(
+ plan.primary_authority(),
+ MycCliPrimaryAuthorityV1::LiveUnixAdmin
+ );
+ assert_eq!(
+ plan.offline_operation(),
+ Some(MycCliOfflineOperationV1::StateReadOnly)
+ );
+
+ let config = parse_myc_config_v1(
+ enabled_config(available_port()).as_bytes(),
+ MycConfigProfile::Production,
+ )
+ .expect("operations configuration");
+ let (mut publisher, reader) = myc_status_cache(
+ InstanceId::new("primary").expect("instance"),
+ status_observation(MycServicePhase::Unready, false),
+ )
+ .expect("status cache");
+ let initial_detail = reader.snapshot().detailed_status_json().to_vec();
+ let bound = MycOperationsServer::new(&config, &reader)
+ .expect("operations server")
+ .bind()
+ .await
+ .expect("operations bind");
+ let address = bound.local_address();
+ let cancellation = MycOperationsCancellationToken::new();
+ let task = tokio::spawn(bound.serve(cancellation.clone()));
+
+ let unready = raw_request(address, b"GET /readyz HTTP/1.1\r\nhost: localhost\r\n\r\n").await;
+ let detailed = raw_request(
+ address,
+ b"GET /v1/status HTTP/1.1\r\nhost: localhost\r\n\r\n",
+ )
+ .await;
+ assert!(unready.starts_with("HTTP/1.1 503 Service Unavailable\r\n"));
+ assert!(detailed.starts_with("HTTP/1.1 404 Not Found\r\n"));
+
+ publisher
+ .publish(status_observation(MycServicePhase::Ready, true))
+ .expect("ready publication");
+ let ready = raw_request(address, b"GET /readyz HTTP/1.1\r\nhost: localhost\r\n\r\n").await;
+ let metrics = raw_request(address, b"GET /metrics HTTP/1.1\r\nhost: localhost\r\n\r\n").await;
+ let query = raw_request(
+ address,
+ b"GET /readyz?probe=1 HTTP/1.1\r\nhost: localhost\r\n\r\n",
+ )
+ .await;
+ assert!(ready.starts_with("HTTP/1.1 200 OK\r\n"));
+ assert!(ready.ends_with("ready\n"));
+ assert!(metrics.contains("radroots_myc_service_phase{phase=\"ready\"} 1\n"));
+ assert!(metrics.contains("radroots_myc_service_ready 1\n"));
+ assert!(query.starts_with("HTTP/1.1 404 Not Found\r\n"));
+ assert_ne!(reader.snapshot().detailed_status_json(), initial_detail);
+
+ cancellation.cancel();
+ assert_eq!(task.await.expect("server join"), Ok(()));
+}
diff --git a/src/lib.rs b/src/lib.rs
@@ -5,6 +5,8 @@
mod admin_v1;
mod cli_v1;
mod config_v1;
+#[cfg(all(test, any(target_os = "linux", target_os = "macos")))]
+mod control_plane_wave_090_a;
mod diagnostics_v1;
mod doctor_v1;
mod nip46_admission;
diff --git a/tests/package_boundary.rs b/tests/package_boundary.rs
@@ -15,6 +15,9 @@ const NIP46_COMPLETION: &str = include_str!("../src/state_completion.rs");
const NIP46_RESPONSE: &str = include_str!("../src/state_response.rs");
const DELIVERY_RECOVERY: &str = include_str!("../src/state_recovery.rs");
const DOCTOR_V1: &str = include_str!("../src/doctor_v1.rs");
+const CONTROL_PLANE_WAVE_090_A: &str = include_str!("../src/control_plane_wave_090_a.rs");
+const CONTROL_PLANE_WAVE_090_A_CONTRACT: &str =
+ include_str!("../contracts/services_hardening/control_plane_wave_090_a.v1.json");
const DIAGNOSTICS_V1: &str = include_str!("../src/diagnostics_v1.rs");
const DIAGNOSTICS_CONTRACT: &str =
include_str!("../contracts/services_hardening/diagnostics.v1.json");
@@ -45,6 +48,7 @@ const SOURCES: &[&str] = &[
include_str!("../src/admin_v1.rs"),
include_str!("../src/cli_v1.rs"),
include_str!("../src/config_v1.rs"),
+ include_str!("../src/control_plane_wave_090_a.rs"),
include_str!("../src/doctor_v1.rs"),
include_str!("../src/diagnostics_v1.rs"),
include_str!("../src/nip46_admission.rs"),
@@ -87,6 +91,7 @@ fn implementation_modules_are_private_and_rustdoc_uses_the_reviewed_readme() {
"admin_v1",
"cli_v1",
"config_v1",
+ "control_plane_wave_090_a",
"doctor_v1",
"diagnostics_v1",
"nip46_admission",
@@ -240,6 +245,7 @@ fn reviewed_api_is_root_only_and_exposes_no_implementation_authority() {
"admin_v1",
"cli_v1",
"config_v1",
+ "control_plane_wave_090_a",
"doctor_v1",
"diagnostics_v1",
"nip46_admission",
@@ -478,6 +484,55 @@ fn step156_diagnostics_are_closed_stderr_only_and_whole_chain_redacted() {
}
#[test]
+fn step157_control_plane_wave_is_machine_bound_native_and_test_only() {
+ let contract: serde_json::Value = serde_json::from_str(CONTROL_PLANE_WAVE_090_A_CONTRACT)
+ .expect("Step 157 control-plane wave contract");
+ assert_eq!(
+ contract["schema"],
+ "radroots.myc.control-plane-wave-090-a.v1"
+ );
+ assert_eq!(
+ contract["steps"],
+ serde_json::json!([152, 153, 154, 155, 156, 157])
+ );
+ assert_eq!(contract["gate"]["wave"], "090-a");
+ assert_eq!(contract["gate"]["complete_after_step"], 157);
+ assert_eq!(contract["gate"]["rcld_promotion_owner"], 162);
+ assert!(ROOT.contains(
+ "#[cfg(all(test, any(target_os = \"linux\", target_os = \"macos\")))]\nmod control_plane_wave_090_a;"
+ ));
+ for required in [
+ "one_parse_offline_doctor",
+ "required_doctor_failure_exits_6",
+ "latest_cached_status_publication",
+ "exact_passive_tcp_routes",
+ "detailed_status_is_not_tcp_routable",
+ "prevalidated_cached_value",
+ "runtime_task_supervision",
+ ] {
+ assert!(
+ CONTROL_PLANE_WAVE_090_A_CONTRACT.contains(required),
+ "Step 157 corpus is missing `{required}`"
+ );
+ }
+ for forbidden in [
+ "sqlx::",
+ "std::fs::",
+ "std::env::",
+ "SystemTime",
+ "reqwest::",
+ "RelayPool",
+ "provider_local_signer",
+ "process::exit",
+ ] {
+ assert!(
+ !CONTROL_PLANE_WAVE_090_A.contains(forbidden),
+ "Step 157 gate gained forbidden authority `{forbidden}`"
+ );
+ }
+}
+
+#[test]
fn step148_response_commit_is_one_atomic_exact_byte_authority() {
let contract: serde_json::Value =
serde_json::from_str(NIP46_RESPONSE_CONTRACT).expect("Step 148 contract");
diff --git a/tests/services_hardening_legacy_removal.rs b/tests/services_hardening_legacy_removal.rs
@@ -3,6 +3,8 @@
use std::path::Path;
use std::process::Command;
+use myc::{MycLogRecord, MycProcessResult};
+
const LIB_SOURCE: &str = include_str!("../src/lib.rs");
const MAIN_SOURCE: &str = include_str!("../src/main.rs");
const MANIFEST: &str = include_str!("../Cargo.toml");
@@ -22,6 +24,10 @@ const ACTIVE_STATE_SOURCES: &[&str] = &[
include_str!("../src/state_response.rs"),
];
+fn process_diagnostic(result: MycProcessResult) -> String {
+ format!("{}\n", MycLogRecord::process_result(result))
+}
+
#[test]
fn prototype_environment_and_cli_sources_are_absent() {
let root = Path::new(env!("CARGO_MANIFEST_DIR"));
@@ -231,17 +237,17 @@ fn binary_uses_only_the_hardened_parser_and_fails_closed_before_dispatch() {
assert_eq!(missing.status.code(), Some(2));
assert_eq!(
String::from_utf8(missing.stderr).expect("utf8 stderr"),
- "myc: command-line arguments are invalid\n"
+ process_diagnostic(MycProcessResult::InputOrConfiguration)
);
let admitted = Command::new(env!("CARGO_BIN_EXE_myc"))
.args(["--profile", "service-host", "--instance", "primary", "run"])
.output()
.expect("run admitted command");
- assert_eq!(admitted.status.code(), Some(1));
+ assert_eq!(admitted.status.code(), Some(3));
assert_eq!(
String::from_utf8(admitted.stderr).expect("utf8 stderr"),
- "myc: command execution is unavailable\n"
+ process_diagnostic(MycProcessResult::ServiceOrDependencyUnavailable)
);
}
@@ -259,7 +265,10 @@ fn removed_alias_and_leaf_arguments_fail_without_echoing_values() {
.expect("run forbidden command");
assert_eq!(output.status.code(), Some(2));
let stderr = String::from_utf8(output.stderr).expect("utf8 stderr");
- assert_eq!(stderr, "myc: command-line arguments are invalid\n");
+ assert_eq!(
+ stderr,
+ process_diagnostic(MycProcessResult::InputOrConfiguration)
+ );
assert!(!stderr.contains("sensitive"));
}
}