commit b27c561a885b5904d270848eed5c71d8855fc7e4
parent d2ca795a81de1b4f9c6200f06b38d6723feabc58
Author: triesap <tyson@radroots.org>
Date: Mon, 24 Aug 2026 17:15:55 +0000
cli: freeze RHI one-pass execution authority
- bind all 31 commands to one sealed daemon, offline, or Unix-admin plan
- forbid live offline and direct-SQLite fallback in the machine contract
- qualify exact route mapping, API closure, and single parse/plan use
Diffstat:
9 files changed, 740 insertions(+), 9 deletions(-)
diff --git a/AGENTS.md b/AGENTS.md
@@ -413,6 +413,14 @@
use the exact lowercase-hex trade-ID type. Identity rotation remains offline
create-new plus validated configuration apply and restart; Unix peer
admission grants no direct SQLite or identity-provider mutation authority.
+- Step 210 freezes the one-parse CLI execution split. Every admitted command
+ selects exactly daemon, offline, or permissioned Unix-admin authority. All
+ twenty live commands map one-to-one to the final twenty-route inventory and
+ carry no offline or direct-SQLite fallback. Config initialization,
+ validation, schema, and apply; exclusive state maintenance; initial identity
+ provisioning; and doctor remain offline. Do not reparse process arguments or
+ let a live CLI plan obtain SQLite, identity-provider, source, relay, task,
+ signal, or runtime authority.
- Optional TCP operations expose only cached `/livez`, `/readyz`, and
`/metrics`; requests must not perform SQLite, source, relay, DNS, identity,
evidence, or credential probes.
diff --git a/README b/README
@@ -542,8 +542,13 @@ identity init, status, and public export; service status and metrics snapshot;
reconciliation status, jobs, and refresh; source listing; trade projection and
report queries; publication backlog, targets, and retry; desired-presence
inspection, render, and refresh; and doctor. The parser performs no command
-execution. Later ordered steps bind exact command inputs and each offline,
-daemon, or live Unix-admin authority.
+execution. The admitted invocation projects once into a sealed execution plan:
+`run` selects daemon authority; config initialization, validation, schema, and
+apply, exclusive state maintenance, initial identity provisioning, and doctor
+select offline authority; and the remaining twenty commands map one-to-one to
+the final twenty permissioned Unix-admin routes. No live command carries an
+offline or direct-SQLite fallback. Later ordered steps bind each command's exact
+input and execute the already-selected authority without reparsing arguments.
Ordinary `run` accepts no identity-generation, identity-path, log-path,
database-path, worker, environment-file, or arbitrary path-leaf flag. Identity
diff --git a/contracts/api_baselines/rhi.txt b/contracts/api_baselines/rhi.txt
@@ -83,9 +83,41 @@ pub enum rhi::RhiBootstrapProfileV1
pub rhi::RhiBootstrapProfileV1::Interactive
pub rhi::RhiBootstrapProfileV1::RepoLocal
pub rhi::RhiBootstrapProfileV1::ServiceHost
+pub enum rhi::RhiCliAdminOperationV1
+pub rhi::RhiCliAdminOperationV1::EffectiveConfig
+pub rhi::RhiCliAdminOperationV1::IdentityPublic
+pub rhi::RhiCliAdminOperationV1::IdentityStatus
+pub rhi::RhiCliAdminOperationV1::MetricsSnapshot
+pub rhi::RhiCliAdminOperationV1::PresenceDesired
+pub rhi::RhiCliAdminOperationV1::PresenceRefresh
+pub rhi::RhiCliAdminOperationV1::PresenceRender
+pub rhi::RhiCliAdminOperationV1::PublicationBacklog
+pub rhi::RhiCliAdminOperationV1::PublicationRetry
+pub rhi::RhiCliAdminOperationV1::PublicationTargets
+pub rhi::RhiCliAdminOperationV1::ReconciliationJobs
+pub rhi::RhiCliAdminOperationV1::ReconciliationRefresh
+pub rhi::RhiCliAdminOperationV1::ReconciliationStatus
+pub rhi::RhiCliAdminOperationV1::Sources
+pub rhi::RhiCliAdminOperationV1::StateBackup
+pub rhi::RhiCliAdminOperationV1::StateStatus
+pub rhi::RhiCliAdminOperationV1::Status
+pub rhi::RhiCliAdminOperationV1::TradeProjection
+pub rhi::RhiCliAdminOperationV1::TradeReportCurrent
+pub rhi::RhiCliAdminOperationV1::TradeReports
+impl rhi::RhiCliAdminOperationV1
+pub const fn rhi::RhiCliAdminOperationV1::route(self) -> rhi::RhiAdminRoute
+pub enum rhi::RhiCliOfflineOperationV1
+pub rhi::RhiCliOfflineOperationV1::Config
+pub rhi::RhiCliOfflineOperationV1::Doctor
+pub rhi::RhiCliOfflineOperationV1::IdentityExclusive
+pub rhi::RhiCliOfflineOperationV1::StateExclusive
pub enum rhi::RhiCliOutputModeV1
pub rhi::RhiCliOutputModeV1::Human
pub rhi::RhiCliOutputModeV1::Json
+pub enum rhi::RhiCliPrimaryAuthorityV1
+pub rhi::RhiCliPrimaryAuthorityV1::Daemon
+pub rhi::RhiCliPrimaryAuthorityV1::LiveUnixAdmin
+pub rhi::RhiCliPrimaryAuthorityV1::Offline
pub enum rhi::RhiCliV1ErrorKind
pub rhi::RhiCliV1ErrorKind::InvalidArguments
pub rhi::RhiCliV1ErrorKind::InvalidConfigPath
@@ -735,6 +767,13 @@ impl rhi::RhiBoundAdminServer
pub async fn rhi::RhiBoundAdminServer::serve(self, rhi::RhiAdminCancellationToken) -> core::result::Result<(), rhi::RhiAdminServerError>
impl core::fmt::Debug for rhi::RhiBoundAdminServer
pub fn rhi::RhiBoundAdminServer::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result
+pub struct rhi::RhiCliExecutionPlanV1
+impl rhi::RhiCliExecutionPlanV1
+pub const fn rhi::RhiCliExecutionPlanV1::admin_operation(&self) -> core::option::Option<rhi::RhiCliAdminOperationV1>
+pub const fn rhi::RhiCliExecutionPlanV1::offline_operation(&self) -> core::option::Option<rhi::RhiCliOfflineOperationV1>
+pub const fn rhi::RhiCliExecutionPlanV1::primary_authority(&self) -> rhi::RhiCliPrimaryAuthorityV1
+impl core::fmt::Debug for rhi::RhiCliExecutionPlanV1
+pub fn rhi::RhiCliExecutionPlanV1::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result
pub struct rhi::RhiCliInvocationV1
impl rhi::RhiCliInvocationV1
pub const fn rhi::RhiCliInvocationV1::command(&self) -> rhi::RhiCommandV1
@@ -2023,6 +2062,7 @@ pub async fn rhi::open_rhi_state_read_write(&rhi::RhiRuntimeContext, &rhi::RhiSt
pub async fn rhi::open_rhi_state_read_write_from_config(&rhi::RhiRuntimeContext, &rhi::RhiConfigDocumentV1, radroots_service_sqlite::migration::MigrationAppliedAtUnixSeconds, &radroots_service_sqlite::migration::MigrationBuildIdentity) -> core::result::Result<rhi::RhiStateHost, rhi::RhiStateHostError>
pub fn rhi::parse_rhi_cli_v1_from<I, T>(I) -> core::result::Result<rhi::RhiCliInvocationV1, rhi::RhiCliV1Error> where I: core::iter::traits::collect::IntoIterator<Item = T>, T: core::convert::Into<std::ffi::os_str::OsString> + core::clone::Clone
pub fn rhi::parse_rhi_config_v1(&[u8], rhi::RhiConfigProfile) -> core::result::Result<rhi::RhiConfigDocumentV1, rhi::RhiConfigV1Error>
+pub const fn rhi::plan_rhi_cli_v1(&rhi::RhiCliInvocationV1) -> rhi::RhiCliExecutionPlanV1
pub fn rhi::provision_rhi_encrypted_identity(&rhi::RhiIdentityEnvelopeBinding, &rhi::RhiWrappingCredential, rhi::RhiEncryptedIdentityProvisioningMaterial) -> core::result::Result<rhi::RhiDecryptedIdentity, rhi::RhiEncryptedIdentityEnvelopeError>
pub fn rhi::reduce_rhi_reconciliation_manifest(rhi::RhiReconciliationManifest) -> core::result::Result<rhi::RhiReconciliationProjection, rhi::RhiReconciliationReducerError>
pub fn rhi::resolve_rhi_runtime_context(&radroots_runtime_paths::roots::RadrootsPathResolver, &rhi::RhiCliInvocationV1) -> core::result::Result<rhi::RhiRuntimeContext, rhi::RhiRuntimeContextError>
diff --git a/contracts/services_hardening/operator_contract.v1.json b/contracts/services_hardening/operator_contract.v1.json
@@ -208,6 +208,45 @@
}
}
},
+ "cli_dispatch": {
+ "parse_count": 1,
+ "primary_authorities": ["daemon", "offline", "live_unix_admin"],
+ "live_direct_sqlite_access": false,
+ "live_command_offline_fallback": false,
+ "commands": [
+ { "command": "run", "primary_authority": "daemon" },
+ { "command": "config init", "primary_authority": "offline", "offline_operation": "config" },
+ { "command": "config validate", "primary_authority": "offline", "offline_operation": "config" },
+ { "command": "config show", "primary_authority": "live_unix_admin", "admin_route": "/v1/config/effective" },
+ { "command": "config schema", "primary_authority": "offline", "offline_operation": "config" },
+ { "command": "config apply", "primary_authority": "offline", "offline_operation": "config" },
+ { "command": "state init", "primary_authority": "offline", "offline_operation": "state_exclusive" },
+ { "command": "state status", "primary_authority": "live_unix_admin", "admin_route": "/v1/state/status" },
+ { "command": "state backup", "primary_authority": "live_unix_admin", "admin_route": "/v1/state/backup" },
+ { "command": "state restore", "primary_authority": "offline", "offline_operation": "state_exclusive" },
+ { "command": "state verify", "primary_authority": "offline", "offline_operation": "state_exclusive" },
+ { "command": "state migrate", "primary_authority": "offline", "offline_operation": "state_exclusive" },
+ { "command": "identity init", "primary_authority": "offline", "offline_operation": "identity_exclusive" },
+ { "command": "identity status", "primary_authority": "live_unix_admin", "admin_route": "/v1/identity/status" },
+ { "command": "identity export-public", "primary_authority": "live_unix_admin", "admin_route": "/v1/identity/public" },
+ { "command": "status", "primary_authority": "live_unix_admin", "admin_route": "/v1/status" },
+ { "command": "metrics snapshot", "primary_authority": "live_unix_admin", "admin_route": "/v1/metrics/snapshot" },
+ { "command": "reconciliation status", "primary_authority": "live_unix_admin", "admin_route": "/v1/reconciliation/status" },
+ { "command": "reconciliation jobs", "primary_authority": "live_unix_admin", "admin_route": "/v1/reconciliation/jobs" },
+ { "command": "reconciliation refresh", "primary_authority": "live_unix_admin", "admin_route": "/v1/reconciliation/refresh" },
+ { "command": "sources list", "primary_authority": "live_unix_admin", "admin_route": "/v1/sources" },
+ { "command": "trade projection", "primary_authority": "live_unix_admin", "admin_route": "/v1/trades/{trade_id}/projection" },
+ { "command": "trade report-current", "primary_authority": "live_unix_admin", "admin_route": "/v1/trades/{trade_id}/reports/current" },
+ { "command": "trade reports", "primary_authority": "live_unix_admin", "admin_route": "/v1/trades/{trade_id}/reports" },
+ { "command": "publication backlog", "primary_authority": "live_unix_admin", "admin_route": "/v1/publication/backlog" },
+ { "command": "publication targets", "primary_authority": "live_unix_admin", "admin_route": "/v1/publication/targets" },
+ { "command": "publication retry", "primary_authority": "live_unix_admin", "admin_route": "/v1/publication/retry" },
+ { "command": "presence desired", "primary_authority": "live_unix_admin", "admin_route": "/v1/presence/desired" },
+ { "command": "presence render", "primary_authority": "live_unix_admin", "admin_route": "/v1/presence/render" },
+ { "command": "presence refresh", "primary_authority": "live_unix_admin", "admin_route": "/v1/presence/refresh" },
+ { "command": "doctor", "primary_authority": "offline", "offline_operation": "doctor" }
+ ]
+ },
"doctor": {
"shared_schema": "radroots.service.doctor.v1",
"contract_version": 1,
diff --git a/src/cli_v1.rs b/src/cli_v1.rs
@@ -114,6 +114,131 @@ pub enum RhiPresenceCommandV1 {
Refresh,
}
+/// The only three process authorities selected by the hardened CLI.
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum RhiCliPrimaryAuthorityV1 {
+ Daemon,
+ Offline,
+ LiveUnixAdmin,
+}
+
+/// The closed offline operation classes selected before any state access.
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum RhiCliOfflineOperationV1 {
+ Config,
+ StateExclusive,
+ IdentityExclusive,
+ Doctor,
+}
+
+/// The closed Unix-admin operations reachable from the command inventory.
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum RhiCliAdminOperationV1 {
+ Status,
+ EffectiveConfig,
+ IdentityStatus,
+ IdentityPublic,
+ StateStatus,
+ StateBackup,
+ MetricsSnapshot,
+ ReconciliationStatus,
+ ReconciliationJobs,
+ ReconciliationRefresh,
+ Sources,
+ TradeProjection,
+ TradeReportCurrent,
+ TradeReports,
+ PublicationBacklog,
+ PublicationTargets,
+ PublicationRetry,
+ PresenceDesired,
+ PresenceRender,
+ PresenceRefresh,
+}
+
+#[cfg(any(target_os = "linux", target_os = "macos"))]
+impl RhiCliAdminOperationV1 {
+ /// Returns the exact native Unix-admin route selected by this operation.
+ #[must_use]
+ pub const fn route(self) -> crate::RhiAdminRoute {
+ match self {
+ Self::Status => crate::RhiAdminRoute::Status,
+ Self::EffectiveConfig => crate::RhiAdminRoute::EffectiveConfig,
+ Self::IdentityStatus => crate::RhiAdminRoute::IdentityStatus,
+ Self::IdentityPublic => crate::RhiAdminRoute::IdentityPublic,
+ Self::StateStatus => crate::RhiAdminRoute::StateStatus,
+ Self::StateBackup => crate::RhiAdminRoute::StateBackup,
+ Self::MetricsSnapshot => crate::RhiAdminRoute::MetricsSnapshot,
+ Self::ReconciliationStatus => crate::RhiAdminRoute::ReconciliationStatus,
+ Self::ReconciliationJobs => crate::RhiAdminRoute::ReconciliationJobs,
+ Self::ReconciliationRefresh => crate::RhiAdminRoute::ReconciliationRefresh,
+ Self::Sources => crate::RhiAdminRoute::Sources,
+ Self::TradeProjection => crate::RhiAdminRoute::TradeProjection,
+ Self::TradeReportCurrent => crate::RhiAdminRoute::TradeReportCurrent,
+ Self::TradeReports => crate::RhiAdminRoute::TradeReports,
+ Self::PublicationBacklog => crate::RhiAdminRoute::PublicationBacklog,
+ Self::PublicationTargets => crate::RhiAdminRoute::PublicationTargets,
+ Self::PublicationRetry => crate::RhiAdminRoute::PublicationRetry,
+ Self::PresenceDesired => crate::RhiAdminRoute::PresenceDesired,
+ Self::PresenceRender => crate::RhiAdminRoute::PresenceRender,
+ Self::PresenceRefresh => crate::RhiAdminRoute::PresenceRefresh,
+ }
+ }
+}
+
+/// A sealed, side-effect-free execution plan for one admitted CLI invocation.
+///
+/// Construction is owned by [`plan_rhi_cli_v1`]. Live commands carry only a
+/// governed Unix-admin operation and never receive an offline or direct-SQLite
+/// fallback.
+///
+/// ```compile_fail
+/// use rhi::{RhiCliExecutionPlanV1, RhiCliPrimaryAuthorityV1};
+///
+/// let _ = RhiCliExecutionPlanV1 {
+/// primary_authority: RhiCliPrimaryAuthorityV1::Offline,
+/// offline_operation: None,
+/// admin_operation: None,
+/// };
+/// ```
+#[derive(Clone, Copy, PartialEq, Eq)]
+pub struct RhiCliExecutionPlanV1 {
+ primary_authority: RhiCliPrimaryAuthorityV1,
+ offline_operation: Option<RhiCliOfflineOperationV1>,
+ admin_operation: Option<RhiCliAdminOperationV1>,
+}
+
+impl RhiCliExecutionPlanV1 {
+ /// Returns the sole selected process authority.
+ #[must_use]
+ pub const fn primary_authority(&self) -> RhiCliPrimaryAuthorityV1 {
+ self.primary_authority
+ }
+
+ /// Returns the bounded offline operation, when the plan admits one.
+ #[must_use]
+ pub const fn offline_operation(&self) -> Option<RhiCliOfflineOperationV1> {
+ self.offline_operation
+ }
+
+ /// Returns the bounded Unix-admin operation, when the plan admits one.
+ #[must_use]
+ pub const fn admin_operation(&self) -> Option<RhiCliAdminOperationV1> {
+ self.admin_operation
+ }
+}
+
+impl fmt::Debug for RhiCliExecutionPlanV1 {
+ fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+ formatter
+ .debug_struct("RhiCliExecutionPlanV1")
+ .field("primary_authority", &self.primary_authority)
+ .field("offline_operation", &self.offline_operation)
+ .field("admin_operation", &self.admin_operation)
+ .finish()
+ }
+}
+
/// Stable source-free classification for command-line admission failures.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum RhiCliV1ErrorKind {
@@ -278,6 +403,117 @@ where
})
}
+/// Selects the sole permitted execution authority for an admitted command.
+///
+/// This function performs no filesystem, database, socket, environment, task,
+/// or process work. Later executors consume the plan without reparsing process
+/// arguments. No live command receives direct SQLite or offline fallback
+/// authority.
+#[must_use]
+pub const fn plan_rhi_cli_v1(invocation: &RhiCliInvocationV1) -> RhiCliExecutionPlanV1 {
+ match invocation.command {
+ RhiCommandV1::Run => daemon_plan(),
+ RhiCommandV1::Config(RhiConfigCommandV1::Init)
+ | RhiCommandV1::Config(RhiConfigCommandV1::Validate)
+ | RhiCommandV1::Config(RhiConfigCommandV1::Schema)
+ | RhiCommandV1::Config(RhiConfigCommandV1::Apply) => {
+ offline_plan(RhiCliOfflineOperationV1::Config)
+ }
+ RhiCommandV1::Config(RhiConfigCommandV1::Show) => {
+ admin_plan(RhiCliAdminOperationV1::EffectiveConfig)
+ }
+ RhiCommandV1::State(RhiStateCommandV1::Init)
+ | RhiCommandV1::State(RhiStateCommandV1::Restore)
+ | RhiCommandV1::State(RhiStateCommandV1::Verify)
+ | RhiCommandV1::State(RhiStateCommandV1::Migrate) => {
+ offline_plan(RhiCliOfflineOperationV1::StateExclusive)
+ }
+ RhiCommandV1::State(RhiStateCommandV1::Status) => {
+ admin_plan(RhiCliAdminOperationV1::StateStatus)
+ }
+ RhiCommandV1::State(RhiStateCommandV1::Backup) => {
+ admin_plan(RhiCliAdminOperationV1::StateBackup)
+ }
+ RhiCommandV1::Identity(RhiIdentityCommandV1::Init) => {
+ offline_plan(RhiCliOfflineOperationV1::IdentityExclusive)
+ }
+ RhiCommandV1::Identity(RhiIdentityCommandV1::Status) => {
+ admin_plan(RhiCliAdminOperationV1::IdentityStatus)
+ }
+ RhiCommandV1::Identity(RhiIdentityCommandV1::ExportPublic) => {
+ admin_plan(RhiCliAdminOperationV1::IdentityPublic)
+ }
+ RhiCommandV1::Status => admin_plan(RhiCliAdminOperationV1::Status),
+ RhiCommandV1::Metrics(RhiMetricsCommandV1::Snapshot) => {
+ admin_plan(RhiCliAdminOperationV1::MetricsSnapshot)
+ }
+ RhiCommandV1::Reconciliation(RhiReconciliationCommandV1::Status) => {
+ admin_plan(RhiCliAdminOperationV1::ReconciliationStatus)
+ }
+ RhiCommandV1::Reconciliation(RhiReconciliationCommandV1::Jobs) => {
+ admin_plan(RhiCliAdminOperationV1::ReconciliationJobs)
+ }
+ RhiCommandV1::Reconciliation(RhiReconciliationCommandV1::Refresh) => {
+ admin_plan(RhiCliAdminOperationV1::ReconciliationRefresh)
+ }
+ RhiCommandV1::Sources(RhiSourcesCommandV1::List) => {
+ admin_plan(RhiCliAdminOperationV1::Sources)
+ }
+ RhiCommandV1::Trade(RhiTradeCommandV1::Projection) => {
+ admin_plan(RhiCliAdminOperationV1::TradeProjection)
+ }
+ RhiCommandV1::Trade(RhiTradeCommandV1::ReportCurrent) => {
+ admin_plan(RhiCliAdminOperationV1::TradeReportCurrent)
+ }
+ RhiCommandV1::Trade(RhiTradeCommandV1::Reports) => {
+ admin_plan(RhiCliAdminOperationV1::TradeReports)
+ }
+ RhiCommandV1::Publication(RhiPublicationCommandV1::Backlog) => {
+ admin_plan(RhiCliAdminOperationV1::PublicationBacklog)
+ }
+ RhiCommandV1::Publication(RhiPublicationCommandV1::Targets) => {
+ admin_plan(RhiCliAdminOperationV1::PublicationTargets)
+ }
+ RhiCommandV1::Publication(RhiPublicationCommandV1::Retry) => {
+ admin_plan(RhiCliAdminOperationV1::PublicationRetry)
+ }
+ RhiCommandV1::Presence(RhiPresenceCommandV1::Desired) => {
+ admin_plan(RhiCliAdminOperationV1::PresenceDesired)
+ }
+ RhiCommandV1::Presence(RhiPresenceCommandV1::Render) => {
+ admin_plan(RhiCliAdminOperationV1::PresenceRender)
+ }
+ RhiCommandV1::Presence(RhiPresenceCommandV1::Refresh) => {
+ admin_plan(RhiCliAdminOperationV1::PresenceRefresh)
+ }
+ RhiCommandV1::Doctor => offline_plan(RhiCliOfflineOperationV1::Doctor),
+ }
+}
+
+const fn daemon_plan() -> RhiCliExecutionPlanV1 {
+ RhiCliExecutionPlanV1 {
+ primary_authority: RhiCliPrimaryAuthorityV1::Daemon,
+ offline_operation: None,
+ admin_operation: None,
+ }
+}
+
+const fn offline_plan(operation: RhiCliOfflineOperationV1) -> RhiCliExecutionPlanV1 {
+ RhiCliExecutionPlanV1 {
+ primary_authority: RhiCliPrimaryAuthorityV1::Offline,
+ offline_operation: Some(operation),
+ admin_operation: None,
+ }
+}
+
+const fn admin_plan(operation: RhiCliAdminOperationV1) -> RhiCliExecutionPlanV1 {
+ RhiCliExecutionPlanV1 {
+ primary_authority: RhiCliPrimaryAuthorityV1::LiveUnixAdmin,
+ offline_operation: None,
+ admin_operation: Some(operation),
+ }
+}
+
fn validate_bootstrap_paths(
profile: RhiBootstrapProfileV1,
repo_local_root: Option<&Path>,
diff --git a/src/lib.rs b/src/lib.rs
@@ -46,10 +46,11 @@ pub use admin_v1::{
RhiBoundAdminServer, build_rhi_admin_router,
};
pub use cli_v1::{
- RhiBootstrapProfileV1, RhiCliInvocationV1, RhiCliOutputModeV1, RhiCliV1Error,
+ RhiBootstrapProfileV1, RhiCliAdminOperationV1, RhiCliExecutionPlanV1, RhiCliInvocationV1,
+ RhiCliOfflineOperationV1, RhiCliOutputModeV1, RhiCliPrimaryAuthorityV1, RhiCliV1Error,
RhiCliV1ErrorKind, RhiCommandV1, RhiConfigCommandV1, RhiIdentityCommandV1, RhiMetricsCommandV1,
RhiPresenceCommandV1, RhiPublicationCommandV1, RhiReconciliationCommandV1, RhiSourcesCommandV1,
- RhiStateCommandV1, RhiTradeCommandV1, parse_rhi_cli_v1_from,
+ RhiStateCommandV1, RhiTradeCommandV1, parse_rhi_cli_v1_from, plan_rhi_cli_v1,
};
pub use config_v1::{
RHI_CONFIG_DOCUMENT_MAX_UTF8_BYTES, RHI_CONFIG_EFFECTIVE_MAX_UTF8_BYTES, RHI_CONFIG_SCHEMA,
diff --git a/src/main.rs b/src/main.rs
@@ -5,7 +5,7 @@ use std::process::ExitCode;
use rhi::{
RadrootsHostEnvironment, RadrootsPathResolver, RadrootsPlatform, parse_rhi_cli_v1_from,
- resolve_rhi_runtime_context,
+ plan_rhi_cli_v1, resolve_rhi_runtime_context,
};
fn main() -> ExitCode {
@@ -27,6 +27,7 @@ fn exit_code_from_run(result: Result<(), ()>) -> ExitCode {
}
fn execute(invocation: rhi::RhiCliInvocationV1) -> Result<(), ()> {
+ let _plan = plan_rhi_cli_v1(&invocation);
let resolver = RadrootsPathResolver::new(RadrootsPlatform::current(), host_environment());
let _context = resolve_rhi_runtime_context(&resolver, &invocation).map_err(|_| ())?;
Err(())
diff --git a/tests/package_boundary.rs b/tests/package_boundary.rs
@@ -242,6 +242,11 @@ fn state_catalog_module_is_private_and_root_api_is_curated() {
"RhiAdminServer",
"RhiBoundAdminServer",
"build_rhi_admin_router",
+ "RhiCliPrimaryAuthorityV1",
+ "RhiCliOfflineOperationV1",
+ "RhiCliAdminOperationV1",
+ "RhiCliExecutionPlanV1",
+ "plan_rhi_cli_v1",
"RhiPublicationErrorKind",
"RhiPublicationMode",
"RhiPublicationRetryPolicy",
@@ -356,6 +361,10 @@ fn state_catalog_module_is_private_and_root_api_is_curated() {
assert!(PUBLIC_API.contains("pub enum rhi::RhiPresenceDesiredErrorKind"));
assert!(PUBLIC_API.contains("pub struct rhi::RhiSignedPresenceDocument"));
assert!(PUBLIC_API.contains("pub enum rhi::RhiPresencePublicationErrorKind"));
+ assert!(PUBLIC_API.contains("pub struct rhi::RhiCliExecutionPlanV1"));
+ assert!(PUBLIC_API.contains(
+ "pub const fn rhi::plan_rhi_cli_v1(&rhi::RhiCliInvocationV1) -> rhi::RhiCliExecutionPlanV1"
+ ));
assert!(!PUBLIC_API.contains("rhi::adapters::"));
assert!(!PUBLIC_API.contains("rhi::features::"));
assert!(!PUBLIC_API.contains("rhi::runtime_adapters::"));
@@ -365,6 +374,7 @@ fn state_catalog_module_is_private_and_root_api_is_curated() {
assert!(!PUBLIC_API.contains("rhi::publication_submission::"));
assert!(!PUBLIC_API.contains("rhi::presence_publication::"));
assert!(!PUBLIC_API.contains("rhi::admin_v1::"));
+ assert!(!PUBLIC_API.contains("rhi::cli_v1::"));
}
#[test]
diff --git a/tests/services_hardening_cli.rs b/tests/services_hardening_cli.rs
@@ -1,15 +1,22 @@
#![forbid(unsafe_code)]
+use std::collections::BTreeSet;
use std::error::Error;
use std::path::Path;
use rhi::{
- INSTANCE_ID_MAX_BYTES, RhiBootstrapProfileV1, RhiCliOutputModeV1, RhiCliV1ErrorKind,
- RhiCommandV1, RhiConfigCommandV1, RhiIdentityCommandV1, RhiMetricsCommandV1,
- RhiPresenceCommandV1, RhiPublicationCommandV1, RhiReconciliationCommandV1, RhiSourcesCommandV1,
- RhiStateCommandV1, RhiTradeCommandV1, parse_rhi_cli_v1_from,
+ INSTANCE_ID_MAX_BYTES, RhiBootstrapProfileV1, RhiCliAdminOperationV1, RhiCliOfflineOperationV1,
+ RhiCliOutputModeV1, RhiCliPrimaryAuthorityV1, RhiCliV1ErrorKind, RhiCommandV1,
+ RhiConfigCommandV1, RhiIdentityCommandV1, RhiMetricsCommandV1, RhiPresenceCommandV1,
+ RhiPublicationCommandV1, RhiReconciliationCommandV1, RhiSourcesCommandV1, RhiStateCommandV1,
+ RhiTradeCommandV1, parse_rhi_cli_v1_from, plan_rhi_cli_v1,
};
+const CLI_SOURCE: &str = include_str!("../src/cli_v1.rs");
+const MAIN_SOURCE: &str = include_str!("../src/main.rs");
+const OPERATOR_CONTRACT: &str =
+ include_str!("../contracts/services_hardening/operator_contract.v1.json");
+
fn parse(command: &[&str]) -> rhi::RhiCliInvocationV1 {
let mut arguments = vec!["rhi", "--profile", "service-host", "--instance", "default"];
arguments.extend_from_slice(command);
@@ -224,6 +231,390 @@ fn bootstrap_values_are_explicit_bounded_and_cross_bound() {
}
#[test]
+fn every_command_has_one_exact_nonforgeable_execution_plan() {
+ let vectors = [
+ ("run", vec!["run"], "daemon", None, None),
+ (
+ "config init",
+ vec!["config", "init"],
+ "offline",
+ Some("config"),
+ None,
+ ),
+ (
+ "config validate",
+ vec!["config", "validate"],
+ "offline",
+ Some("config"),
+ None,
+ ),
+ (
+ "config show",
+ vec!["config", "show"],
+ "live_unix_admin",
+ None,
+ Some("/v1/config/effective"),
+ ),
+ (
+ "config schema",
+ vec!["config", "schema"],
+ "offline",
+ Some("config"),
+ None,
+ ),
+ (
+ "config apply",
+ vec!["config", "apply"],
+ "offline",
+ Some("config"),
+ None,
+ ),
+ (
+ "state init",
+ vec!["state", "init"],
+ "offline",
+ Some("state_exclusive"),
+ None,
+ ),
+ (
+ "state status",
+ vec!["state", "status"],
+ "live_unix_admin",
+ None,
+ Some("/v1/state/status"),
+ ),
+ (
+ "state backup",
+ vec!["state", "backup"],
+ "live_unix_admin",
+ None,
+ Some("/v1/state/backup"),
+ ),
+ (
+ "state restore",
+ vec!["state", "restore"],
+ "offline",
+ Some("state_exclusive"),
+ None,
+ ),
+ (
+ "state verify",
+ vec!["state", "verify"],
+ "offline",
+ Some("state_exclusive"),
+ None,
+ ),
+ (
+ "state migrate",
+ vec!["state", "migrate"],
+ "offline",
+ Some("state_exclusive"),
+ None,
+ ),
+ (
+ "identity init",
+ vec!["identity", "init"],
+ "offline",
+ Some("identity_exclusive"),
+ None,
+ ),
+ (
+ "identity status",
+ vec!["identity", "status"],
+ "live_unix_admin",
+ None,
+ Some("/v1/identity/status"),
+ ),
+ (
+ "identity export-public",
+ vec!["identity", "export-public"],
+ "live_unix_admin",
+ None,
+ Some("/v1/identity/public"),
+ ),
+ (
+ "status",
+ vec!["status"],
+ "live_unix_admin",
+ None,
+ Some("/v1/status"),
+ ),
+ (
+ "metrics snapshot",
+ vec!["metrics", "snapshot"],
+ "live_unix_admin",
+ None,
+ Some("/v1/metrics/snapshot"),
+ ),
+ (
+ "reconciliation status",
+ vec!["reconciliation", "status"],
+ "live_unix_admin",
+ None,
+ Some("/v1/reconciliation/status"),
+ ),
+ (
+ "reconciliation jobs",
+ vec!["reconciliation", "jobs"],
+ "live_unix_admin",
+ None,
+ Some("/v1/reconciliation/jobs"),
+ ),
+ (
+ "reconciliation refresh",
+ vec!["reconciliation", "refresh"],
+ "live_unix_admin",
+ None,
+ Some("/v1/reconciliation/refresh"),
+ ),
+ (
+ "sources list",
+ vec!["sources", "list"],
+ "live_unix_admin",
+ None,
+ Some("/v1/sources"),
+ ),
+ (
+ "trade projection",
+ vec!["trade", "projection"],
+ "live_unix_admin",
+ None,
+ Some("/v1/trades/{trade_id}/projection"),
+ ),
+ (
+ "trade report-current",
+ vec!["trade", "report-current"],
+ "live_unix_admin",
+ None,
+ Some("/v1/trades/{trade_id}/reports/current"),
+ ),
+ (
+ "trade reports",
+ vec!["trade", "reports"],
+ "live_unix_admin",
+ None,
+ Some("/v1/trades/{trade_id}/reports"),
+ ),
+ (
+ "publication backlog",
+ vec!["publication", "backlog"],
+ "live_unix_admin",
+ None,
+ Some("/v1/publication/backlog"),
+ ),
+ (
+ "publication targets",
+ vec!["publication", "targets"],
+ "live_unix_admin",
+ None,
+ Some("/v1/publication/targets"),
+ ),
+ (
+ "publication retry",
+ vec!["publication", "retry"],
+ "live_unix_admin",
+ None,
+ Some("/v1/publication/retry"),
+ ),
+ (
+ "presence desired",
+ vec!["presence", "desired"],
+ "live_unix_admin",
+ None,
+ Some("/v1/presence/desired"),
+ ),
+ (
+ "presence render",
+ vec!["presence", "render"],
+ "live_unix_admin",
+ None,
+ Some("/v1/presence/render"),
+ ),
+ (
+ "presence refresh",
+ vec!["presence", "refresh"],
+ "live_unix_admin",
+ None,
+ Some("/v1/presence/refresh"),
+ ),
+ ("doctor", vec!["doctor"], "offline", Some("doctor"), None),
+ ];
+ let contract: serde_json::Value =
+ serde_json::from_str(OPERATOR_CONTRACT).expect("operator contract");
+ let dispatch = contract
+ .get("cli_dispatch")
+ .and_then(serde_json::Value::as_object)
+ .expect("CLI dispatch contract");
+ assert_eq!(
+ dispatch.keys().map(String::as_str).collect::<BTreeSet<_>>(),
+ BTreeSet::from([
+ "commands",
+ "live_command_offline_fallback",
+ "live_direct_sqlite_access",
+ "parse_count",
+ "primary_authorities",
+ ])
+ );
+ assert_eq!(dispatch["parse_count"], 1);
+ assert_eq!(
+ dispatch["primary_authorities"],
+ serde_json::json!(["daemon", "offline", "live_unix_admin"])
+ );
+ assert_eq!(dispatch["live_direct_sqlite_access"], false);
+ assert_eq!(dispatch["live_command_offline_fallback"], false);
+ let commands = dispatch["commands"].as_array().expect("command inventory");
+ assert_eq!(commands.len(), vectors.len());
+
+ for (index, (command, arguments, authority, offline, route)) in vectors.into_iter().enumerate()
+ {
+ let invocation = parse(&arguments);
+ let plan = plan_rhi_cli_v1(&invocation);
+ assert_eq!(authority_name(plan.primary_authority()), authority);
+ assert_eq!(plan.offline_operation().map(offline_name), offline);
+ assert_eq!(plan.admin_operation().map(admin_path), route);
+
+ let row = commands[index].as_object().expect("command row");
+ let mut expected_keys = BTreeSet::from(["command", "primary_authority"]);
+ if offline.is_some() {
+ expected_keys.insert("offline_operation");
+ }
+ if route.is_some() {
+ expected_keys.insert("admin_route");
+ }
+ assert_eq!(
+ row.keys().map(String::as_str).collect::<BTreeSet<_>>(),
+ expected_keys
+ );
+ assert_eq!(row["command"], command);
+ assert_eq!(row["primary_authority"], authority);
+ assert_eq!(
+ row.get("offline_operation")
+ .and_then(serde_json::Value::as_str),
+ offline
+ );
+ assert_eq!(
+ row.get("admin_route").and_then(serde_json::Value::as_str),
+ route
+ );
+ }
+}
+
+fn authority_name(authority: RhiCliPrimaryAuthorityV1) -> &'static str {
+ match authority {
+ RhiCliPrimaryAuthorityV1::Daemon => "daemon",
+ RhiCliPrimaryAuthorityV1::Offline => "offline",
+ RhiCliPrimaryAuthorityV1::LiveUnixAdmin => "live_unix_admin",
+ }
+}
+
+fn offline_name(operation: RhiCliOfflineOperationV1) -> &'static str {
+ match operation {
+ RhiCliOfflineOperationV1::Config => "config",
+ RhiCliOfflineOperationV1::StateExclusive => "state_exclusive",
+ RhiCliOfflineOperationV1::IdentityExclusive => "identity_exclusive",
+ RhiCliOfflineOperationV1::Doctor => "doctor",
+ }
+}
+
+fn admin_path(operation: RhiCliAdminOperationV1) -> &'static str {
+ match operation {
+ RhiCliAdminOperationV1::Status => "/v1/status",
+ RhiCliAdminOperationV1::EffectiveConfig => "/v1/config/effective",
+ RhiCliAdminOperationV1::IdentityStatus => "/v1/identity/status",
+ RhiCliAdminOperationV1::IdentityPublic => "/v1/identity/public",
+ RhiCliAdminOperationV1::StateStatus => "/v1/state/status",
+ RhiCliAdminOperationV1::StateBackup => "/v1/state/backup",
+ RhiCliAdminOperationV1::MetricsSnapshot => "/v1/metrics/snapshot",
+ RhiCliAdminOperationV1::ReconciliationStatus => "/v1/reconciliation/status",
+ RhiCliAdminOperationV1::ReconciliationJobs => "/v1/reconciliation/jobs",
+ RhiCliAdminOperationV1::ReconciliationRefresh => "/v1/reconciliation/refresh",
+ RhiCliAdminOperationV1::Sources => "/v1/sources",
+ RhiCliAdminOperationV1::TradeProjection => "/v1/trades/{trade_id}/projection",
+ RhiCliAdminOperationV1::TradeReportCurrent => "/v1/trades/{trade_id}/reports/current",
+ RhiCliAdminOperationV1::TradeReports => "/v1/trades/{trade_id}/reports",
+ RhiCliAdminOperationV1::PublicationBacklog => "/v1/publication/backlog",
+ RhiCliAdminOperationV1::PublicationTargets => "/v1/publication/targets",
+ RhiCliAdminOperationV1::PublicationRetry => "/v1/publication/retry",
+ RhiCliAdminOperationV1::PresenceDesired => "/v1/presence/desired",
+ RhiCliAdminOperationV1::PresenceRender => "/v1/presence/render",
+ RhiCliAdminOperationV1::PresenceRefresh => "/v1/presence/refresh",
+ }
+}
+
+#[cfg(any(target_os = "linux", target_os = "macos"))]
+#[test]
+fn cli_admin_operations_match_the_complete_governed_route_inventory() {
+ assert_eq!(
+ [
+ RhiCliAdminOperationV1::Status,
+ RhiCliAdminOperationV1::EffectiveConfig,
+ RhiCliAdminOperationV1::IdentityStatus,
+ RhiCliAdminOperationV1::IdentityPublic,
+ RhiCliAdminOperationV1::StateStatus,
+ RhiCliAdminOperationV1::StateBackup,
+ RhiCliAdminOperationV1::MetricsSnapshot,
+ RhiCliAdminOperationV1::ReconciliationStatus,
+ RhiCliAdminOperationV1::ReconciliationJobs,
+ RhiCliAdminOperationV1::ReconciliationRefresh,
+ RhiCliAdminOperationV1::Sources,
+ RhiCliAdminOperationV1::TradeProjection,
+ RhiCliAdminOperationV1::TradeReportCurrent,
+ RhiCliAdminOperationV1::TradeReports,
+ RhiCliAdminOperationV1::PublicationBacklog,
+ RhiCliAdminOperationV1::PublicationTargets,
+ RhiCliAdminOperationV1::PublicationRetry,
+ RhiCliAdminOperationV1::PresenceDesired,
+ RhiCliAdminOperationV1::PresenceRender,
+ RhiCliAdminOperationV1::PresenceRefresh,
+ ]
+ .map(RhiCliAdminOperationV1::route),
+ rhi::RhiAdminRoute::ALL
+ );
+}
+
+#[test]
+fn execution_plan_is_safe_and_the_binary_parses_and_plans_once() {
+ let invocation = parse_rhi_cli_v1_from([
+ "rhi",
+ "--profile",
+ "repo-local",
+ "--instance",
+ "secret-instance",
+ "--repo-local-root",
+ "/secret/repository",
+ "--config",
+ "/secret/config.toml",
+ "publication",
+ "retry",
+ ])
+ .expect("valid invocation");
+ let rendered = format!("{invocation:?} {:?}", plan_rhi_cli_v1(&invocation));
+ for forbidden in [
+ "secret-instance",
+ "/secret/repository",
+ "/secret/config.toml",
+ ] {
+ assert!(!rendered.contains(forbidden));
+ }
+
+ assert_eq!(
+ MAIN_SOURCE
+ .matches("parse_rhi_cli_v1_from(std::env::args_os())")
+ .count(),
+ 1
+ );
+ assert_eq!(
+ MAIN_SOURCE.matches("plan_rhi_cli_v1(&invocation)").count(),
+ 1
+ );
+ for source in [CLI_SOURCE, MAIN_SOURCE] {
+ for forbidden in ["sqlx::", "RhiStateHost", "open_rhi_state_"] {
+ assert!(!source.contains(forbidden), "found `{forbidden}`");
+ }
+ }
+}
+
+#[test]
fn prototype_and_unsafe_commands_are_absent_and_errors_are_safe() {
for arguments in [
vec![