myc

Self-custodial remote signer for Radroots apps
git clone https://radroots.dev/git/myc.git
Log | Files | Refs | README | LICENSE

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:
Acontracts/services_hardening/control_plane_wave_090_a.v1.json | 51+++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/control_plane_wave_090_a.rs | 278+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/lib.rs | 2++
Mtests/package_boundary.rs | 55+++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtests/services_hardening_legacy_removal.rs | 17+++++++++++++----
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")); } }