commit eea96175b40fd85d9a2463aec44af243f6492ca6
parent d6bc2a722f72f403bd9ba2519401d8e0a874750c
Author: triesap <tyson@radroots.org>
Date: Sun, 23 Aug 2026 11:40:04 +0000
admin: compose production control surfaces
- remove live identity rotation from final operator and CLI contracts
- bind the exact Myc handler to canonical permissioned Unix admin serving
- freeze status operations doctor and runtime ownership boundaries
- refresh API and regression evidence for the 19-route contract
Diffstat:
15 files changed, 594 insertions(+), 196 deletions(-)
diff --git a/AGENTS.md b/AGENTS.md
@@ -102,8 +102,10 @@
code may commit a completion only through the atomic exact-response method;
a retained completion without its immutable response and initial delivery
state is inconsistent evidence and must never be repaired implicitly.
-- Step 150 implements the exact 21-route and 35-model Myc Unix-admin adapter.
- Keep the shared Lib router private; reject model drift, unknown/duplicate/null
+- Step 150 introduced the historical 21-route and 35-model Myc Unix-admin
+ adapter. Step 159 unit 13 owns its final 19-route and 32-model form by
+ removing live identity rekey and replacement. Keep the shared Lib router
+ private; reject model drift, unknown/duplicate/null
fields, noncanonical response bytes, invalid path/query values, and unsafe
errors at the adapter boundary. Domain handlers must bind authenticated
cursors to route/filter/snapshot identity and must retain exact operation-ID
@@ -113,7 +115,9 @@
must select exactly daemon, offline, or permissioned Unix-admin authority;
only the explicitly contracted read-only status, backup, and public-identity
operations may fall back after proving the daemon writer lock is free.
- Rekey, replace, and every other live mutation have no direct-state fallback.
+ Identity rekey and replacement are offline create-new/config-apply
+ operations and have no live command or route. Every retained live mutation
+ has no direct-state fallback.
Do not reparse process arguments or let a live CLI plan obtain SQLite,
provider, relay, task, signal, or runtime authority.
- Step 153 freezes one ordered 13-check doctor engine. Check adapters retain
@@ -159,8 +163,17 @@
acknowledgement to UnknownAcknowledgement, and retries only the exact
committed signed bytes. Never hold a SQLite transaction across provider or
relay work, detach protected blocking work, expose the executor/client, or
- create one task per relay. Unit 13 alone wires these components into the
+ create one task per relay. Unit 15 alone wires these components into the
fixed runtime graph and startup handshake.
+- Step 159 unit 13 owns the final 19-route/32-model production Unix-admin
+ server around the exact handler boundary, the configured transport-limit
+ projection, the canonical permissioned `admin.sock` binding, and the
+ machine-bound composition facts for the existing sole status publisher,
+ passive three-route operations server, and injected 13-check doctor. Keep
+ shared routers, listeners, entropy, and cancellation private. Unit 14 owns
+ secure CLI/config bootstrap and concrete doctor probes; Unit 15 alone owns
+ server task spawning, provider/relay wiring, readiness, reconnect, and
+ shutdown.
- Treat checked-in source, tests, and prototype behavior as implementation
evidence, not permission to preserve behavior that the active requirement
removes.
diff --git a/README b/README
@@ -60,7 +60,9 @@ requires explicit `--profile <service-host|interactive|repo-local>` and
`--repo-local-root` only for `repo-local`, and accepts only an optional absolute
`--config` path. Its exact command inventory is `run`; `config
init|validate|show|schema`; `state init|status|backup|restore|verify|migrate`;
-`identity init|status|rekey|replace|export-public`; `status`; and `doctor`.
+`identity init|status|export-public`; `status`; and `doctor`. Identity rotation
+is an offline create-new/config-apply lifecycle and is intentionally absent
+from the live CLI and Unix-admin inventories.
The process binary uses only this parser. Hardened command execution remains
fail-closed until its ordered runtime-dispatch steps are complete; no prototype
command is used as a fallback.
@@ -70,8 +72,8 @@ selects daemon authority; config, exclusive state/identity provisioning, and
doctor select offline authority; state/identity/service inspection and live
mutations select the permissioned Unix-admin authority. Only explicitly
read-only status, backup, and public-identity operations may fall back when a
-later executor proves the daemon writer lock is free. Rekey and replace never
-fall back to direct state access, and no live plan carries SQLite authority.
+later executor proves the daemon writer lock is free. No live plan carries
+SQLite authority.
The active doctor boundary executes the exact 13-check operator inventory in
contract order under fixed per-check deadlines. Check implementations retain
@@ -235,8 +237,8 @@ authority. Explicit desired/current offline NIP-05 export re-reads verified
state, works through read-only inspection, and returns canonical compact
`names` plus NIP-46 discovery JSON without hosting it.
-The implemented v1 Myc administration adapter registers the exact 21-route
-inventory and validates every request and response against all 35 model
+The implemented v1 Myc administration adapter registers the exact 19-route
+inventory and validates every request and response against all 32 model
references in `contracts/services_hardening/operator_contract.v1.json`. It
reuses the hardened Lib HTTP/1.1-over-Unix server for original-wire duplicate
and null rejection, percent-decoded bounded path parameters, peer admission,
@@ -251,9 +253,17 @@ operation ID. A successful mutation means its contract-defined local effect
and operation audit are committed; it does not claim relay submission or
delivery. Identical operation-ID reuse must return the original committed
result, different-byte reuse returns `operation_id_conflict`, and pagination
-cursors remain authenticated to the same route, filters, and snapshot. This
-checkpoint supplies the typed protocol adapter, not the later daemon runtime,
-cached status/metrics producer, CLI dispatch, or TCP operations surface.
+cursors remain authenticated to the same route, filters, and snapshot. Unit 13
+seals that handler boundary inside the production `MycAdminServer`,
+projects the exact admitted admin transport limits, and binds only the
+canonical permissioned `admin.sock` through the shared host authority. The raw
+router, listener, entropy source, and cancellation token remain private. The
+existing sole status publisher feeds both detailed local status and the
+optional passive three-route TCP operations server, while doctor continues to
+accept only its exact injected 13-check probe inventory. Unit 14 owns secure
+CLI/config bootstrap and the concrete doctor probe implementations; Unit 15
+alone owns task spawning, provider/relay wiring, readiness, reconnect, and
+phase-aware shutdown.
The transport capability and admitted request remain non-forgeable outside
the crate:
diff --git a/contracts/api_baselines/myc.txt b/contracts/api_baselines/myc.txt
@@ -60,21 +60,30 @@ pub myc::MycAdminRoute::DiscoveryRefresh
pub myc::MycAdminRoute::DiscoveryRender
pub myc::MycAdminRoute::EffectiveConfig
pub myc::MycAdminRoute::IdentityPublic
-pub myc::MycAdminRoute::IdentityRekey
-pub myc::MycAdminRoute::IdentityReplace
pub myc::MycAdminRoute::IdentityStatus
pub myc::MycAdminRoute::MetricsSnapshot
pub myc::MycAdminRoute::StateBackup
pub myc::MycAdminRoute::StateStatus
pub myc::MycAdminRoute::Status
impl myc::MycAdminRoute
-pub const myc::MycAdminRoute::ALL: [Self; 21]
+pub const myc::MycAdminRoute::ALL: [Self; 19]
pub const fn myc::MycAdminRoute::is_mutation(self) -> bool
pub const fn myc::MycAdminRoute::method(self) -> myc::MycAdminMethod
pub const fn myc::MycAdminRoute::operation_id(self) -> &'static str
pub const fn myc::MycAdminRoute::path(self) -> &'static str
pub const fn myc::MycAdminRoute::request_model(self) -> &'static str
pub const fn myc::MycAdminRoute::response_model(self) -> &'static str
+pub enum myc::MycAdminServerErrorKind
+pub myc::MycAdminServerErrorKind::Accept
+pub myc::MycAdminServerErrorKind::Bind
+pub myc::MycAdminServerErrorKind::ConnectionTaskPanicked
+pub myc::MycAdminServerErrorKind::InvalidConfiguration
+pub myc::MycAdminServerErrorKind::Listener
+pub myc::MycAdminServerErrorKind::Router
+pub myc::MycAdminServerErrorKind::ServerConfiguration
+pub myc::MycAdminServerErrorKind::WriterAuthority
+impl myc::MycAdminServerErrorKind
+pub const fn myc::MycAdminServerErrorKind::code(self) -> &'static str
pub enum myc::MycAuditKind
pub myc::MycAuditKind::ChallengeAuthorization
pub myc::MycAuditKind::ChallengeCreation
@@ -124,8 +133,6 @@ pub myc::MycBootstrapProfileV1::RepoLocal
pub myc::MycBootstrapProfileV1::ServiceHost
pub enum myc::MycCliAdminOperationV1
pub myc::MycCliAdminOperationV1::IdentityPublic
-pub myc::MycCliAdminOperationV1::IdentityRekey
-pub myc::MycCliAdminOperationV1::IdentityReplace
pub myc::MycCliAdminOperationV1::IdentityStatus
pub myc::MycCliAdminOperationV1::StateBackup
pub myc::MycCliAdminOperationV1::StateStatus
@@ -404,8 +411,6 @@ pub const fn myc::MycGovernanceStateErrorKind::code(self) -> &'static str
pub enum myc::MycIdentityCommandV1
pub myc::MycIdentityCommandV1::ExportPublic
pub myc::MycIdentityCommandV1::Init
-pub myc::MycIdentityCommandV1::Rekey
-pub myc::MycIdentityCommandV1::Replace
pub myc::MycIdentityCommandV1::Status
pub enum myc::MycIntegrityStateV1
pub myc::MycIntegrityStateV1::Failed
@@ -804,6 +809,13 @@ pub enum myc::MycTransportHealthV1
pub myc::MycTransportHealthV1::Degraded
pub myc::MycTransportHealthV1::Ready
pub myc::MycTransportHealthV1::Unavailable
+pub struct myc::MycAdminCancellationToken
+impl myc::MycAdminCancellationToken
+pub fn myc::MycAdminCancellationToken::cancel(&self)
+pub fn myc::MycAdminCancellationToken::is_cancelled(&self) -> bool
+pub fn myc::MycAdminCancellationToken::new() -> Self
+impl core::fmt::Debug for myc::MycAdminCancellationToken
+pub fn myc::MycAdminCancellationToken::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result
pub struct myc::MycAdminDocumentError
impl myc::MycAdminDocumentError
pub const fn myc::MycAdminDocumentError::kind(self) -> myc::MycAdminDocumentErrorKind
@@ -858,6 +870,21 @@ pub struct myc::MycAdminRouterError
impl core::error::Error for myc::MycAdminRouterError
impl core::fmt::Display for myc::MycAdminRouterError
pub fn myc::MycAdminRouterError::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result
+pub struct myc::MycAdminServer
+impl myc::MycAdminServer
+pub async fn myc::MycAdminServer::bind(self, &myc::MycRuntimeContext) -> core::result::Result<myc::MycBoundAdminServer, myc::MycAdminServerError>
+pub fn myc::MycAdminServer::new<H>(&myc::MycConfigDocumentV1, alloc::sync::Arc<H>) -> core::result::Result<Self, myc::MycAdminServerError> where H: myc::MycAdminHandler
+impl core::fmt::Debug for myc::MycAdminServer
+pub fn myc::MycAdminServer::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result
+pub struct myc::MycAdminServerError
+impl myc::MycAdminServerError
+pub const fn myc::MycAdminServerError::code(self) -> &'static str
+pub const fn myc::MycAdminServerError::kind(self) -> myc::MycAdminServerErrorKind
+impl core::error::Error for myc::MycAdminServerError
+impl core::fmt::Debug for myc::MycAdminServerError
+pub fn myc::MycAdminServerError::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result
+impl core::fmt::Display for myc::MycAdminServerError
+pub fn myc::MycAdminServerError::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result
pub struct myc::MycAuditCorrelationId(_)
impl myc::MycAuditCorrelationId
pub const fn myc::MycAuditCorrelationId::new([u8; 32]) -> Self
@@ -918,6 +945,11 @@ pub fn myc::MycAuthorizationChallengeUrl::as_str(&self) -> &str
pub fn myc::MycAuthorizationChallengeUrl::new(&str) -> core::result::Result<Self, myc::MycConnectionStateError>
impl core::fmt::Debug for myc::MycAuthorizationChallengeUrl
pub fn myc::MycAuthorizationChallengeUrl::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result
+pub struct myc::MycBoundAdminServer
+impl myc::MycBoundAdminServer
+pub async fn myc::MycBoundAdminServer::serve(self, myc::MycAdminCancellationToken) -> core::result::Result<(), myc::MycAdminServerError>
+impl core::fmt::Debug for myc::MycBoundAdminServer
+pub fn myc::MycBoundAdminServer::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result
pub struct myc::MycBoundOperationsServer
impl myc::MycBoundOperationsServer
pub fn myc::MycBoundOperationsServer::local_address(&self) -> core::net::socket_addr::SocketAddr
diff --git a/contracts/services_hardening/control_surfaces.v1.json b/contracts/services_hardening/control_surfaces.v1.json
@@ -0,0 +1,56 @@
+{
+ "schema": "radroots.myc.control-surfaces.v1",
+ "contract_version": 1,
+ "step": 159,
+ "unit": 13,
+ "unit_id": "myc-control-surfaces",
+ "admin": {
+ "transport": "http_1_1_over_permissioned_unix_domain_socket",
+ "route_count": 19,
+ "model_count": 32,
+ "handler_boundary": "MycAdminHandler",
+ "server": "MycAdminServer",
+ "bound_server": "MycBoundAdminServer",
+ "cancellation": "MycAdminCancellationToken",
+ "limits_source": "/resource_limits/admin",
+ "system_entropy": true,
+ "canonical_runtime_directory": true,
+ "canonical_socket_artifact": "admin.sock",
+ "raw_router_or_listener_exposed": false,
+ "live_identity_mutation_routes": false,
+ "identity_rotation": "offline_create_new_then_config_apply"
+ },
+ "status": {
+ "factory": "myc_status_cache",
+ "publisher": "MycStatusPublisher",
+ "publisher_count": 1,
+ "passive_reader": "MycStatusReader",
+ "fresh_probe_on_read": false
+ },
+ "operations": {
+ "server": "MycOperationsServer",
+ "enabled_only_by_validated_configuration": true,
+ "routes": ["/livez", "/readyz", "/metrics"],
+ "reads_same_status_cache": true,
+ "active_probe": false
+ },
+ "doctor": {
+ "runner": "run_myc_doctor",
+ "check_count": 13,
+ "probe_authority_injected": true,
+ "used_as_liveness_or_readiness_probe": false
+ },
+ "security": {
+ "source_free_public_errors": true,
+ "paths_in_display_or_debug": false,
+ "raw_sqlx_or_transport_types_public": false,
+ "tcp_admin_routes": false
+ },
+ "deferred": {
+ "secure_cli_and_config_bootstrap": "unit_14",
+ "production_doctor_probe_implementations": "unit_14",
+ "authoritative_daemon_task_graph": "unit_15",
+ "provider_relay_runtime_wiring": "unit_15",
+ "readiness_reconnect_and_shutdown": "unit_15"
+ }
+}
diff --git a/contracts/services_hardening/operator_contract.v1.json b/contracts/services_hardening/operator_contract.v1.json
@@ -17,8 +17,6 @@
{ "method": "GET", "path": "/v1/status", "operation_id": "radroots.myc.status.get.v1", "request_model": "empty", "response_model": "service_status_v1", "mutation": false },
{ "method": "GET", "path": "/v1/config/effective", "operation_id": "radroots.myc.config.effective.get.v1", "request_model": "empty", "response_model": "effective_config_v1", "mutation": false },
{ "method": "GET", "path": "/v1/identity/status", "operation_id": "radroots.myc.identity.status.get.v1", "request_model": "identity_status_query_v1", "response_model": "identity_status_v1", "mutation": false },
- { "method": "POST", "path": "/v1/identity/rekey", "operation_id": "radroots.myc.identity.rekey.v1", "request_model": "identity_rekey_request_v1", "response_model": "identity_mutation_receipt_v1", "mutation": true },
- { "method": "POST", "path": "/v1/identity/replace", "operation_id": "radroots.myc.identity.replace.v1", "request_model": "identity_replace_request_v1", "response_model": "identity_mutation_receipt_v1", "mutation": true },
{ "method": "GET", "path": "/v1/identity/public", "operation_id": "radroots.myc.identity.public.get.v1", "request_model": "identity_public_query_v1", "response_model": "identity_public_v1", "mutation": false },
{ "method": "GET", "path": "/v1/state/status", "operation_id": "radroots.myc.state.status.get.v1", "request_model": "empty", "response_model": "state_status_v1", "mutation": false },
{ "method": "POST", "path": "/v1/state/backup", "operation_id": "radroots.myc.state.backup.create.v1", "request_model": "state_backup_request_v1", "response_model": "state_backup_receipt_v1", "mutation": true },
@@ -54,18 +52,9 @@
{ "id": "user", "required": true, "disabled_allowed": false, "providers": ["encrypted_file", "local_signer"] },
{ "id": "discovery", "required": false, "disabled_allowed": true, "providers": ["encrypted_file", "local_signer"] }
],
- "rekey_provider": "encrypted_file_only",
- "replace_provider_variants": {
- "discriminator": "provider",
- "encrypted_file": {
- "required_fields": ["provider", "envelope_path", "credential_reference", "expected_public_key"],
- "provider_value": "encrypted_file"
- },
- "local_signer": {
- "required_fields": ["provider", "socket_path", "request_deadline_ms", "request_max_bytes", "response_max_bytes", "concurrency", "expected_public_key"],
- "provider_value": "local_signer"
- }
- }
+ "live_mutation": false,
+ "rotation_mode": "offline_create_new_then_config_apply",
+ "in_place_overwrite": false
},
"types": {
"myc_literal": { "kind": "literal", "value": "myc" },
@@ -82,18 +71,9 @@
"sha256_hex": { "kind": "string", "utf8_bytes": 64, "pattern": "^[0-9a-f]{64}$" },
"public_key_hex": { "kind": "string", "utf8_bytes": 64, "pattern": "^[0-9a-f]{64}$" },
"absolute_path": { "kind": "string", "minimum_utf8_bytes": 1, "maximum_utf8_bytes": 4096, "pattern": "^/" },
- "credential_reference": { "kind": "string", "minimum_utf8_bytes": 1, "maximum_utf8_bytes": 256 },
"confirmation": { "kind": "literal", "value": "confirm" },
"identity_role": { "kind": "enum", "values": ["transport", "user", "discovery"] },
- "encrypted_file_provider": { "kind": "literal", "value": "encrypted_file" },
"identity_provider_kind": { "kind": "enum", "values": ["encrypted_file", "local_signer"] },
- "identity_provider_replacement": { "kind": "tagged_union", "discriminator": "provider", "variants": ["encrypted_file_replacement", "local_signer_replacement"] },
- "encrypted_file_replacement": { "kind": "closed_object", "fields": { "provider": "encrypted_file_provider", "envelope_path": "absolute_path", "credential_reference": "credential_reference", "expected_public_key": "public_key_hex" } },
- "local_signer_replacement": { "kind": "closed_object", "fields": { "provider": "local_signer_provider", "socket_path": "absolute_path", "request_deadline_ms": "deadline_ms", "request_max_bytes": "bounded_io_bytes", "response_max_bytes": "bounded_io_bytes", "concurrency": "bounded_concurrency", "expected_public_key": "public_key_hex" } },
- "local_signer_provider": { "kind": "literal", "value": "local_signer" },
- "deadline_ms": { "kind": "integer", "minimum": 1, "maximum": 30000, "unit": "milliseconds" },
- "bounded_io_bytes": { "kind": "integer", "minimum": 1024, "maximum": 1048576, "unit": "bytes" },
- "bounded_concurrency": { "kind": "integer", "minimum": 1, "maximum": 64 },
"service_phase": { "kind": "enum", "values": ["starting", "ready", "degraded", "unready", "stopping", "failed"] },
"uptime_millis": { "kind": "integer", "minimum": 0, "maximum": 18446744073709551615, "unit": "milliseconds_from_injected_monotonic_clock" },
"integrity_state": { "kind": "enum", "values": ["verified", "verification_required", "failed"] },
@@ -155,7 +135,7 @@
"optional_utc_seconds": { "kind": "optional", "representation": "absent_parent_field", "value": "utc_seconds" },
"connection_summaries": { "kind": "array", "items": "connection_summary", "maximum_items": 200, "ordered": true },
"connection_summary": { "kind": "closed_object", "fields": { "connection_id": "bounded_id", "client_public_key": "public_key_hex", "state": "connection_state", "permissions": "permission_set", "generation": "u64", "created_at_utc": "utc_seconds", "updated_at_utc": "utc_seconds" } },
- "request_identity": { "kind": "string", "minimum_utf8_bytes": 1, "maximum_utf8_bytes": 256 },
+ "request_identity": { "kind": "alias", "target": "sha256_hex" },
"audit_kind": { "kind": "string", "minimum_utf8_bytes": 1, "maximum_utf8_bytes": 64, "pattern": "^[a-z][a-z0-9_]*$" },
"audit_events": { "kind": "array", "items": "audit_event", "maximum_items": 200, "ordered": true },
"audit_event": { "kind": "closed_object", "fields": { "audit_id": "bounded_id", "occurred_at_utc": "utc_seconds", "kind": "audit_kind", "outcome": "audit_outcome", "reason_code": "reason_code", "correlation_id": "bounded_id" } },
@@ -172,9 +152,6 @@
"effective_config_v1": { "fields": { "schema": { "type": "myc_config_schema_literal", "presence": "required" }, "schema_version": { "type": "contract_version_literal", "presence": "required" }, "provenance": { "type": "config_provenance", "presence": "required" }, "redacted_config": { "type": "redacted_config", "presence": "required" }, "config_digest": { "type": "sha256_hex", "presence": "required" } } },
"identity_status_query_v1": { "fields": { "role": { "type": "identity_role", "presence": "required" } } },
"identity_status_v1": { "fields": { "role": { "type": "identity_role", "presence": "required" }, "provider": { "type": "identity_provider_kind", "presence": "optional" }, "configured": { "type": "bool", "presence": "required" }, "available": { "type": "bool", "presence": "required" }, "generation": { "type": "u64", "presence": "required" }, "reason_codes": { "type": "reason_codes", "presence": "required" }, "public_key": { "type": "public_key_hex", "presence": "optional" } } },
- "identity_rekey_request_v1": { "fields": { "role": { "type": "identity_role", "presence": "required" }, "provider": { "type": "encrypted_file_provider", "presence": "required" }, "expected_generation": { "type": "u64", "presence": "required" }, "new_credential_reference": { "type": "credential_reference", "presence": "required" }, "confirmation": { "type": "confirmation", "presence": "required" } } },
- "identity_replace_request_v1": { "fields": { "role": { "type": "identity_role", "presence": "required" }, "expected_generation": { "type": "u64", "presence": "required" }, "replacement": { "type": "identity_provider_replacement", "presence": "required" }, "confirmation": { "type": "confirmation", "presence": "required" } } },
- "identity_mutation_receipt_v1": { "fields": { "operation_id": { "type": "operation_id", "presence": "required" }, "role": { "type": "identity_role", "presence": "required" }, "previous_public_key": { "type": "public_key_hex", "presence": "required" }, "current_public_key": { "type": "public_key_hex", "presence": "required" }, "generation": { "type": "u64", "presence": "required" } } },
"identity_public_query_v1": { "fields": { "role": { "type": "identity_role", "presence": "required" } } },
"identity_public_v1": { "fields": { "role": { "type": "identity_role", "presence": "required" }, "public_key": { "type": "public_key_hex", "presence": "required" }, "generation": { "type": "u64", "presence": "required" } } },
"state_status_v1": { "fields": { "schema_version": { "type": "positive_u32", "presence": "required" }, "generation": { "type": "u64", "presence": "required" }, "integrity": { "type": "integrity_state", "presence": "required" }, "writer_lock": { "type": "writer_lock_state", "presence": "required" }, "backup_eligible": { "type": "bool", "presence": "required" }, "reason_codes": { "type": "reason_codes", "presence": "required" } } },
@@ -228,8 +205,6 @@
"success_visibility": "response_only_after_local_authoritative_commit",
"relay_submission_or_delivery_required_for_success": false,
"committed_effects": {
- "radroots.myc.identity.rekey.v1": "identity_generation_provider_binding_session_invalidation_and_operation_audit",
- "radroots.myc.identity.replace.v1": "identity_generation_provider_binding_session_invalidation_and_operation_audit",
"radroots.myc.state.backup.create.v1": "verified_snapshot_manifest_digest_and_operation_audit",
"radroots.myc.connection.approve.v1": "connection_state_permissions_generation_and_operation_audit",
"radroots.myc.connection.reject.v1": "connection_state_reason_generation_and_operation_audit",
@@ -262,8 +237,6 @@
{ "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", "offline_operation": "identity_read_only", "daemon_unavailable_offline_fallback": true },
- { "command": "identity rekey", "primary_authority": "live_unix_admin", "admin_route": "/v1/identity/rekey" },
- { "command": "identity replace", "primary_authority": "live_unix_admin", "admin_route": "/v1/identity/replace" },
{ "command": "identity export-public", "primary_authority": "live_unix_admin", "admin_route": "/v1/identity/public", "offline_operation": "identity_read_only", "daemon_unavailable_offline_fallback": true },
{ "command": "status", "primary_authority": "live_unix_admin", "admin_route": "/v1/status", "offline_operation": "state_read_only", "daemon_unavailable_offline_fallback": true },
{ "command": "doctor", "primary_authority": "offline", "offline_operation": "doctor" }
diff --git a/src/admin_v1.rs b/src/admin_v1.rs
@@ -1,6 +1,6 @@
//! Exact Myc v1 Unix-admin route and model boundary.
-use core::{fmt, future::Future, pin::Pin};
+use core::{fmt, future::Future, pin::Pin, time::Duration};
use std::{
collections::BTreeSet,
error::Error,
@@ -12,7 +12,9 @@ use base64::{Engine as _, engine::general_purpose::URL_SAFE_NO_PAD};
use radroots_service_host::{
AdminCorrelationId, AdminError, AdminErrorCode, AdminErrorMessage, AdminHttpMethod,
AdminMutationRequest, AdminOperationId, AdminRequest, AdminRouteFailure,
- AdminRouteFailureStatus, AdminRouteOutcome, AdminRouter,
+ AdminRouteFailureStatus, AdminRouteOutcome, AdminRouter, AdminServer, AdminServerError,
+ AdminTransportLimitValues, AdminTransportLimits, CancellationToken, UnixAdminSocketBinding,
+ UnixAdminSocketWriterAuthority,
};
use serde::de::{self, DeserializeSeed, MapAccess, SeqAccess, Visitor};
use serde_json::{Map, Value};
@@ -37,8 +39,6 @@ pub enum MycAdminRoute {
Status,
EffectiveConfig,
IdentityStatus,
- IdentityRekey,
- IdentityReplace,
IdentityPublic,
StateStatus,
StateBackup,
@@ -58,12 +58,10 @@ pub enum MycAdminRoute {
}
impl MycAdminRoute {
- pub const ALL: [Self; 21] = [
+ pub const ALL: [Self; 19] = [
Self::Status,
Self::EffectiveConfig,
Self::IdentityStatus,
- Self::IdentityRekey,
- Self::IdentityReplace,
Self::IdentityPublic,
Self::StateStatus,
Self::StateBackup,
@@ -95,9 +93,7 @@ impl MycAdminRoute {
| Self::AuditEvents
| Self::AuditSummary
| Self::DiscoveryDesired => MycAdminMethod::Get,
- Self::IdentityRekey
- | Self::IdentityReplace
- | Self::StateBackup
+ Self::StateBackup
| Self::ConnectionApprove
| Self::ConnectionReject
| Self::ConnectionRevoke
@@ -115,8 +111,6 @@ impl MycAdminRoute {
Self::Status => "/v1/status",
Self::EffectiveConfig => "/v1/config/effective",
Self::IdentityStatus => "/v1/identity/status",
- Self::IdentityRekey => "/v1/identity/rekey",
- Self::IdentityReplace => "/v1/identity/replace",
Self::IdentityPublic => "/v1/identity/public",
Self::StateStatus => "/v1/state/status",
Self::StateBackup => "/v1/state/backup",
@@ -142,8 +136,6 @@ impl MycAdminRoute {
Self::Status => "radroots.myc.status.get.v1",
Self::EffectiveConfig => "radroots.myc.config.effective.get.v1",
Self::IdentityStatus => "radroots.myc.identity.status.get.v1",
- Self::IdentityRekey => "radroots.myc.identity.rekey.v1",
- Self::IdentityReplace => "radroots.myc.identity.replace.v1",
Self::IdentityPublic => "radroots.myc.identity.public.get.v1",
Self::StateStatus => "radroots.myc.state.status.get.v1",
Self::StateBackup => "radroots.myc.state.backup.create.v1",
@@ -172,8 +164,6 @@ impl MycAdminRoute {
| Self::MetricsSnapshot
| Self::DiscoveryDesired => "empty",
Self::IdentityStatus => "identity_status_query_v1",
- Self::IdentityRekey => "identity_rekey_request_v1",
- Self::IdentityReplace => "identity_replace_request_v1",
Self::IdentityPublic => "identity_public_query_v1",
Self::StateBackup => "state_backup_request_v1",
Self::ConnectionsList => "connections_query_v1",
@@ -196,7 +186,6 @@ impl MycAdminRoute {
Self::Status => "service_status_v1",
Self::EffectiveConfig => "effective_config_v1",
Self::IdentityStatus => "identity_status_v1",
- Self::IdentityRekey | Self::IdentityReplace => "identity_mutation_receipt_v1",
Self::IdentityPublic => "identity_public_v1",
Self::StateStatus => "state_status_v1",
Self::StateBackup => "state_backup_receipt_v1",
@@ -499,6 +488,189 @@ impl fmt::Debug for MycAdminRouter {
}
}
+impl MycAdminRouter {
+ fn into_inner(self) -> AdminRouter {
+ self.inner
+ }
+}
+
+/// Cloneable cooperative cancellation for the Myc Unix-admin server.
+#[derive(Clone, Default)]
+pub struct MycAdminCancellationToken {
+ inner: CancellationToken,
+}
+
+impl MycAdminCancellationToken {
+ #[must_use]
+ pub fn new() -> Self {
+ Self::default()
+ }
+
+ /// Requests cancellation. Repeated requests have no additional effect.
+ pub fn cancel(&self) {
+ self.inner.cancel();
+ }
+
+ #[must_use]
+ pub fn is_cancelled(&self) -> bool {
+ self.inner.is_cancelled()
+ }
+}
+
+impl fmt::Debug for MycAdminCancellationToken {
+ fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+ formatter
+ .debug_struct("MycAdminCancellationToken")
+ .field("cancelled", &self.is_cancelled())
+ .finish()
+ }
+}
+
+/// Stable source-free Myc Unix-admin server failure classification.
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum MycAdminServerErrorKind {
+ InvalidConfiguration,
+ Router,
+ ServerConfiguration,
+ WriterAuthority,
+ Bind,
+ Listener,
+ Accept,
+ ConnectionTaskPanicked,
+}
+
+impl MycAdminServerErrorKind {
+ #[must_use]
+ pub const fn code(self) -> &'static str {
+ match self {
+ Self::InvalidConfiguration => "admin_configuration_invalid",
+ Self::Router => "admin_router_invalid",
+ Self::ServerConfiguration => "admin_server_configuration_invalid",
+ Self::WriterAuthority => "admin_writer_authority_unavailable",
+ Self::Bind => "admin_bind_failed",
+ Self::Listener => "admin_listener_failed",
+ Self::Accept => "admin_accept_failed",
+ Self::ConnectionTaskPanicked => "admin_connection_task_panicked",
+ }
+ }
+}
+
+/// One redacted source-free Myc Unix-admin server failure.
+#[derive(Clone, Copy, PartialEq, Eq)]
+pub struct MycAdminServerError {
+ kind: MycAdminServerErrorKind,
+}
+
+impl MycAdminServerError {
+ const fn new(kind: MycAdminServerErrorKind) -> Self {
+ Self { kind }
+ }
+
+ #[must_use]
+ pub const fn kind(self) -> MycAdminServerErrorKind {
+ self.kind
+ }
+
+ #[must_use]
+ pub const fn code(self) -> &'static str {
+ self.kind.code()
+ }
+}
+
+impl fmt::Debug for MycAdminServerError {
+ fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+ formatter
+ .debug_struct("MycAdminServerError")
+ .field("kind", &self.kind)
+ .finish()
+ }
+}
+
+impl fmt::Display for MycAdminServerError {
+ fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+ formatter.write_str("Myc Unix-admin server failed")
+ }
+}
+
+impl Error for MycAdminServerError {}
+
+/// Unbound production Myc Unix-admin server.
+///
+/// Construction projects only the already-admitted Myc configuration, seals
+/// the exact route inventory around the supplied domain handler, and uses the
+/// shared host's system entropy. The raw shared router and server never cross
+/// this boundary.
+pub struct MycAdminServer {
+ inner: AdminServer,
+}
+
+impl MycAdminServer {
+ pub fn new<H>(
+ configuration: &crate::MycConfigDocumentV1,
+ handler: Arc<H>,
+ ) -> Result<Self, MycAdminServerError>
+ where
+ H: MycAdminHandler,
+ {
+ let limits = admin_transport_limits(configuration)?;
+ let router = build_myc_admin_router(handler)
+ .map_err(|_| MycAdminServerError::new(MycAdminServerErrorKind::Router))?;
+ let inner = AdminServer::with_system_entropy(router.into_inner(), limits)
+ .map_err(|_| MycAdminServerError::new(MycAdminServerErrorKind::ServerConfiguration))?;
+ Ok(Self { inner })
+ }
+
+ /// Acquires the canonical runtime-directory authority and binds `admin.sock`.
+ ///
+ /// Binding does not spawn a task or begin request admission. Unit 15 owns
+ /// the final supervised server task and its shutdown phase.
+ pub async fn bind(
+ self,
+ runtime: &crate::MycRuntimeContext,
+ ) -> Result<MycBoundAdminServer, MycAdminServerError> {
+ let authority = UnixAdminSocketWriterAuthority::acquire(runtime.context().paths().run())
+ .map_err(|_| MycAdminServerError::new(MycAdminServerErrorKind::WriterAuthority))?;
+ let binding = UnixAdminSocketBinding::bind(authority, runtime.artifacts().admin_socket())
+ .await
+ .map_err(|_| MycAdminServerError::new(MycAdminServerErrorKind::Bind))?;
+ Ok(MycBoundAdminServer {
+ inner: self.inner,
+ binding,
+ })
+ }
+}
+
+impl fmt::Debug for MycAdminServer {
+ fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+ formatter.write_str("MycAdminServer([sealed])")
+ }
+}
+
+/// Bound production Myc Unix-admin server.
+pub struct MycBoundAdminServer {
+ inner: AdminServer,
+ binding: UnixAdminSocketBinding,
+}
+
+impl MycBoundAdminServer {
+ /// Serves until supervisor cancellation and then drains bounded connection work.
+ pub async fn serve(
+ self,
+ cancellation: MycAdminCancellationToken,
+ ) -> Result<(), MycAdminServerError> {
+ self.inner
+ .serve(self.binding, cancellation.inner)
+ .await
+ .map_err(map_admin_server_error)
+ }
+}
+
+impl fmt::Debug for MycBoundAdminServer {
+ fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+ formatter.write_str("MycBoundAdminServer([sealed])")
+ }
+}
+
/// Registers the complete closed Myc v1 route inventory on the hardened Lib router.
pub fn build_myc_admin_router<H>(handler: Arc<H>) -> Result<MycAdminRouter, MycAdminRouterError>
where
@@ -520,6 +692,52 @@ where
Ok(MycAdminRouter { inner: router })
}
+fn admin_transport_limits(
+ configuration: &crate::MycConfigDocumentV1,
+) -> Result<AdminTransportLimits, MycAdminServerError> {
+ let admin = configuration
+ .normalized()
+ .pointer("/resource_limits/admin")
+ .ok_or_else(invalid_admin_configuration)?;
+ let values = AdminTransportLimitValues {
+ header_count: admin_u32(admin, "/header_count")?,
+ header_bytes: admin_u32(admin, "/header_bytes")?,
+ request_body_utf8_bytes: admin_u32(admin, "/request_body_utf8_bytes")?,
+ response_body_utf8_bytes: admin_u32(admin, "/response_body_utf8_bytes")?,
+ concurrent_connections: admin_u32(admin, "/concurrent_connections")?,
+ request_deadline: Duration::from_millis(admin_u64(admin, "/request_deadline_ms")?),
+ idle_timeout: Duration::from_millis(admin_u64(admin, "/idle_timeout_ms")?),
+ query_items: admin_u32(admin, "/query_items")?,
+ };
+ AdminTransportLimits::new(values).map_err(|_| invalid_admin_configuration())
+}
+
+fn admin_u64(value: &Value, pointer: &str) -> Result<u64, MycAdminServerError> {
+ value
+ .pointer(pointer)
+ .and_then(Value::as_u64)
+ .ok_or_else(invalid_admin_configuration)
+}
+
+fn admin_u32(value: &Value, pointer: &str) -> Result<u32, MycAdminServerError> {
+ u32::try_from(admin_u64(value, pointer)?).map_err(|_| invalid_admin_configuration())
+}
+
+const fn invalid_admin_configuration() -> MycAdminServerError {
+ MycAdminServerError::new(MycAdminServerErrorKind::InvalidConfiguration)
+}
+
+const fn map_admin_server_error(error: AdminServerError) -> MycAdminServerError {
+ let kind = match error {
+ AdminServerError::ListenerClone { .. } | AdminServerError::ListenerRegistration { .. } => {
+ MycAdminServerErrorKind::Listener
+ }
+ AdminServerError::Accept { .. } => MycAdminServerErrorKind::Accept,
+ AdminServerError::ConnectionTaskPanicked => MycAdminServerErrorKind::ConnectionTaskPanicked,
+ };
+ MycAdminServerError::new(kind)
+}
+
async fn dispatch_route<H>(
route: MycAdminRoute,
handler: Arc<H>,
@@ -773,7 +991,7 @@ fn operator_route_inventory_is_exact() -> bool {
return false;
};
routes.len() == MycAdminRoute::ALL.len()
- && models.len() == 35
+ && models.len() == 32
&& admin
.pointer("/model_wire_contract/response_body_max_utf8_bytes")
.and_then(Value::as_u64)
@@ -1446,7 +1664,7 @@ mod tests {
#[test]
fn complete_route_and_model_inventory_matches_the_machine_contract() {
assert!(operator_route_inventory_is_exact());
- assert_eq!(MycAdminRoute::ALL.len(), 21);
+ assert_eq!(MycAdminRoute::ALL.len(), 19);
let referenced = MycAdminRoute::ALL
.into_iter()
.flat_map(|route| [route.request_model(), route.response_model()])
@@ -1458,7 +1676,7 @@ mod tests {
.map(String::as_str)
.collect::<BTreeSet<_>>();
assert_eq!(referenced, governed);
- assert_eq!(governed.len(), 35);
+ assert_eq!(governed.len(), 32);
for model in governed {
let value = sample_model(model);
validate_model(model, &value).expect("minimum exact model");
@@ -1573,41 +1791,36 @@ mod tests {
fn public_diagnostics_are_source_free_and_content_free() {
let document = MycAdminDocumentError::new(MycAdminDocumentErrorKind::InvalidModel);
let handler = MycAdminHandlerError::new(MycAdminHandlerErrorKind::Internal);
+ let server = MycAdminServerError::new(MycAdminServerErrorKind::Bind);
for rendered in [
format!("{document}"),
format!("{document:?}"),
format!("{handler}"),
format!("{handler:?}"),
+ format!("{server}"),
+ format!("{server:?}"),
] {
assert!(!rendered.contains("/tmp/protected"));
assert!(!rendered.contains("credential"));
}
assert!(Error::source(&document).is_none());
assert!(Error::source(&handler).is_none());
+ assert!(Error::source(&server).is_none());
+ assert_eq!(server.code(), "admin_bind_failed");
}
#[cfg(any(target_os = "linux", target_os = "macos"))]
mod native {
use core::sync::atomic::{AtomicUsize, Ordering};
+ use std::fs;
use std::sync::Mutex;
- use radroots_service_host::{
- AdminClient, AdminClientTarget, AdminServer, AdminTransportLimits, CancellationToken,
- EntropyError, EntropySource, UnixAdminSocketBinding, UnixAdminSocketWriterAuthority,
- };
+ use radroots_service_host::{AdminClient, AdminClientTarget, AdminTransportLimits};
use super::*;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
- #[derive(Clone, Copy)]
- struct FixedEntropy;
-
- impl EntropySource for FixedEntropy {
- fn fill_bytes(&self, destination: &mut [u8]) -> Result<(), EntropyError> {
- destination.fill(0x51);
- Ok(())
- }
- }
+ const CONFIG: &str = include_str!("../contracts/services_hardening/config.v1.example.toml");
type FixtureCall = (MycAdminRoute, Option<String>, Box<[u8]>);
@@ -1660,11 +1873,38 @@ mod tests {
}
}
- fn runtime_directory() -> tempfile::TempDir {
- tempfile::Builder::new()
+ fn runtime_context() -> (
+ tempfile::TempDir,
+ crate::MycRuntimeContext,
+ crate::MycConfigDocumentV1,
+ ) {
+ let root = tempfile::Builder::new()
.prefix("myc-admin-")
.tempdir_in("/tmp")
- .expect("short runtime directory")
+ .expect("short runtime root");
+ let root_path = root.path().to_str().expect("UTF-8 test root");
+ let invocation = crate::parse_myc_cli_v1_from([
+ "myc",
+ "--profile",
+ "repo-local",
+ "--instance",
+ "primary",
+ "--repo-local-root",
+ root_path,
+ "run",
+ ])
+ .expect("test CLI");
+ let resolver = crate::RadrootsPathResolver::new(
+ crate::RadrootsPlatform::Linux,
+ crate::RadrootsHostEnvironment::default(),
+ );
+ let runtime = crate::resolve_myc_runtime_context(&resolver, &invocation)
+ .expect("test runtime context");
+ fs::create_dir_all(runtime.context().paths().run()).expect("runtime directory");
+ let configuration =
+ crate::parse_myc_config_v1(CONFIG.as_bytes(), crate::MycConfigProfile::RepoLocal)
+ .expect("test configuration");
+ (root, runtime, configuration)
}
fn target_for(route: MycAdminRoute, request: &Value) -> AdminClientTarget {
@@ -1709,24 +1949,20 @@ mod tests {
}
#[tokio::test]
- async fn all_twenty_one_routes_round_trip_over_the_hardened_unix_boundary() {
- let directory = runtime_directory();
- let socket = directory.path().join("admin.sock");
- let authority = UnixAdminSocketWriterAuthority::acquire(directory.path())
- .expect("writer authority");
- let binding = UnixAdminSocketBinding::bind(authority, &socket)
- .await
- .expect("socket binding");
+ async fn all_nineteen_routes_round_trip_over_the_hardened_unix_boundary() {
+ let (_root, runtime, configuration) = runtime_context();
+ let socket = runtime.artifacts().admin_socket().to_path_buf();
let handler = Arc::new(FixtureHandler::new());
- let MycAdminRouter { inner } =
- build_myc_admin_router(Arc::clone(&handler)).expect("exact router");
- let server = AdminServer::new(inner, AdminTransportLimits::DEFAULT, FixedEntropy)
- .expect("admin server");
- let cancellation = CancellationToken::new();
+ let server = MycAdminServer::new(&configuration, Arc::clone(&handler))
+ .expect("production admin server")
+ .bind(&runtime)
+ .await
+ .expect("canonical admin binding");
+ let cancellation = MycAdminCancellationToken::new();
let server_cancellation = cancellation.clone();
let task = tokio::spawn(async move {
server
- .serve(binding, server_cancellation)
+ .serve(server_cancellation)
.await
.expect("serve Myc admin");
});
@@ -1813,7 +2049,7 @@ mod tests {
{
let calls = handler.calls.lock().expect("calls");
- assert_eq!(calls.len(), 21);
+ assert_eq!(calls.len(), 19);
for (index, (route, operation_id, request)) in calls.iter().enumerate() {
assert_eq!(*route, MycAdminRoute::ALL[index]);
assert_eq!(operation_id.is_some(), route.is_mutation());
@@ -1826,6 +2062,16 @@ mod tests {
}
cancellation.cancel();
task.await.expect("server task");
+ assert!(!socket.exists());
+ }
+
+ #[test]
+ fn production_server_projects_exact_validated_admin_limits() {
+ let (_root, _runtime, configuration) = runtime_context();
+ assert_eq!(
+ admin_transport_limits(&configuration).expect("admin limits"),
+ AdminTransportLimits::DEFAULT
+ );
}
}
}
diff --git a/src/cli_v1.rs b/src/cli_v1.rs
@@ -53,8 +53,6 @@ pub enum MycStateCommandV1 {
pub enum MycIdentityCommandV1 {
Init,
Status,
- Rekey,
- Replace,
ExportPublic,
}
@@ -84,8 +82,6 @@ pub enum MycCliAdminOperationV1 {
StateStatus,
StateBackup,
IdentityStatus,
- IdentityRekey,
- IdentityReplace,
IdentityPublic,
}
@@ -99,8 +95,6 @@ impl MycCliAdminOperationV1 {
Self::StateStatus => crate::MycAdminRoute::StateStatus,
Self::StateBackup => crate::MycAdminRoute::StateBackup,
Self::IdentityStatus => crate::MycAdminRoute::IdentityStatus,
- Self::IdentityRekey => crate::MycAdminRoute::IdentityRekey,
- Self::IdentityReplace => crate::MycAdminRoute::IdentityReplace,
Self::IdentityPublic => crate::MycAdminRoute::IdentityPublic,
}
}
@@ -362,12 +356,6 @@ pub const fn plan_myc_cli_v1(invocation: &MycCliInvocationV1) -> MycCliExecution
MycCliAdminOperationV1::IdentityPublic,
MycCliOfflineOperationV1::IdentityReadOnly,
),
- MycCommandV1::Identity(MycIdentityCommandV1::Rekey) => {
- admin_plan(MycCliAdminOperationV1::IdentityRekey)
- }
- MycCommandV1::Identity(MycIdentityCommandV1::Replace) => {
- admin_plan(MycCliAdminOperationV1::IdentityReplace)
- }
MycCommandV1::Status => read_only_admin_plan(
MycCliAdminOperationV1::Status,
MycCliOfflineOperationV1::StateReadOnly,
@@ -394,15 +382,6 @@ const fn offline_plan(operation: MycCliOfflineOperationV1) -> MycCliExecutionPla
}
}
-const fn admin_plan(operation: MycCliAdminOperationV1) -> MycCliExecutionPlanV1 {
- MycCliExecutionPlanV1 {
- primary_authority: MycCliPrimaryAuthorityV1::LiveUnixAdmin,
- offline_operation: None,
- admin_operation: Some(operation),
- daemon_unavailable_offline_fallback: false,
- }
-}
-
const fn read_only_admin_plan(
admin_operation: MycCliAdminOperationV1,
offline_operation: MycCliOfflineOperationV1,
@@ -557,8 +536,6 @@ impl From<RawStateCommand> for MycStateCommandV1 {
enum RawIdentityCommand {
Init,
Status,
- Rekey,
- Replace,
ExportPublic,
}
@@ -567,8 +544,6 @@ impl From<RawIdentityCommand> for MycIdentityCommandV1 {
match value {
RawIdentityCommand::Init => Self::Init,
RawIdentityCommand::Status => Self::Status,
- RawIdentityCommand::Rekey => Self::Rekey,
- RawIdentityCommand::Replace => Self::Replace,
RawIdentityCommand::ExportPublic => Self::ExportPublic,
}
}
@@ -637,14 +612,6 @@ mod tests {
MycCommandV1::Identity(MycIdentityCommandV1::Status),
),
(
- &["identity", "rekey"][..],
- MycCommandV1::Identity(MycIdentityCommandV1::Rekey),
- ),
- (
- &["identity", "replace"][..],
- MycCommandV1::Identity(MycIdentityCommandV1::Replace),
- ),
- (
&["identity", "export-public"][..],
MycCommandV1::Identity(MycIdentityCommandV1::ExportPublic),
),
diff --git a/src/delivery_worker.rs b/src/delivery_worker.rs
@@ -2,7 +2,7 @@
#![allow(
dead_code,
- reason = "Step 159 Unit 12 seals the worker before Unit 13 runtime graph wiring"
+ reason = "Step 159 Unit 12 seals the worker before Unit 15 runtime graph wiring"
)]
use core::fmt;
diff --git a/src/lib.rs b/src/lib.rs
@@ -50,10 +50,11 @@ mod transport_nostr_adapter;
#[cfg(any(target_os = "linux", target_os = "macos"))]
pub use admin_v1::{
- MycAdminDocumentError, MycAdminDocumentErrorKind, MycAdminFuture, MycAdminHandler,
- MycAdminHandlerError, MycAdminHandlerErrorKind, MycAdminMethod, MycAdminRequestDocument,
- MycAdminResponseDocument, MycAdminRoute, MycAdminRouter, MycAdminRouterError,
- build_myc_admin_router,
+ MycAdminCancellationToken, MycAdminDocumentError, MycAdminDocumentErrorKind, MycAdminFuture,
+ MycAdminHandler, MycAdminHandlerError, MycAdminHandlerErrorKind, MycAdminMethod,
+ MycAdminRequestDocument, MycAdminResponseDocument, MycAdminRoute, MycAdminRouter,
+ MycAdminRouterError, MycAdminServer, MycAdminServerError, MycAdminServerErrorKind,
+ MycBoundAdminServer, build_myc_admin_router,
};
pub use cli_v1::{
MycBootstrapProfileV1, MycCliAdminOperationV1, MycCliExecutionPlanV1, MycCliInvocationV1,
diff --git a/src/provider_executor.rs b/src/provider_executor.rs
@@ -2,7 +2,7 @@
#![allow(
dead_code,
- reason = "Step 159 Unit 12 seals the executor before Unit 13 runtime graph wiring"
+ reason = "Step 159 Unit 12 seals the executor before Unit 15 runtime graph wiring"
)]
use core::fmt;
diff --git a/src/transport_nostr_adapter.rs b/src/transport_nostr_adapter.rs
@@ -2,7 +2,7 @@
#![allow(
dead_code,
- reason = "Step 159 Unit 12 seals the adapter before Unit 13 runtime graph wiring"
+ reason = "Step 159 Unit 12 seals the adapter before Unit 15 runtime graph wiring"
)]
use core::{fmt, future::Future, pin::Pin};
diff --git a/tests/package_boundary.rs b/tests/package_boundary.rs
@@ -6,6 +6,8 @@ const ROOT: &str = include_str!("../src/lib.rs");
const README: &str = include_str!("../README");
const PUBLIC_API: &str = include_str!("../contracts/api_baselines/myc.txt");
const ADMIN_V1: &str = include_str!("../src/admin_v1.rs");
+const CONTROL_SURFACES_CONTRACT: &str =
+ include_str!("../contracts/services_hardening/control_surfaces.v1.json");
const NIP46_VERIFICATION: &str = include_str!("../src/nip46_verification.rs");
const NIP46_AUTHORIZATION: &str = include_str!("../src/nip46_authorization.rs");
const NIP46_REPLAY: &str = include_str!("../src/nip46_replay.rs");
@@ -211,6 +213,11 @@ fn reviewed_api_is_root_only_and_exposes_no_implementation_authority() {
"pub trait myc::MycAdminHandler",
"pub struct myc::MycAdminRouter",
"pub fn myc::build_myc_admin_router",
+ "pub struct myc::MycAdminServer",
+ "pub struct myc::MycBoundAdminServer",
+ "pub struct myc::MycAdminCancellationToken",
+ "pub struct myc::MycAdminServerError",
+ "pub enum myc::MycAdminServerErrorKind",
"pub struct myc::MycRuntimeContext",
"pub struct myc::MycRuntimeFoundation",
"pub struct myc::MycRuntimeReadiness",
@@ -885,8 +892,8 @@ fn step150_admin_adapter_is_closed_typed_and_transport_bounded() {
"#[cfg(any(target_os = \"linux\", target_os = \"macos\"))]\npub use admin_v1::{"
));
for required in [
- "pub const ALL: [Self; 21]",
- "models.len() == 35",
+ "pub const ALL: [Self; 19]",
+ "models.len() == 32",
"operator_route_inventory_is_exact",
"AdminMutationRequest<Value>",
"strict_json(bytes)",
@@ -897,7 +904,7 @@ fn step150_admin_adapter_is_closed_typed_and_transport_bounded() {
"original committed response",
"same route, filters, and snapshot",
"relay submission or delivery is not implied",
- "all_twenty_one_routes_round_trip_over_the_hardened_unix_boundary",
+ "all_nineteen_routes_round_trip_over_the_hardened_unix_boundary",
] {
assert!(
ADMIN_V1.contains(required),
@@ -923,17 +930,62 @@ fn step150_admin_adapter_is_closed_typed_and_transport_bounded() {
);
}
for required in [
- "exact 21-route",
- "all 35 model",
+ "exact 19-route",
+ "all 32 model",
"Raw shared-host routers and JSON values never cross the public",
"operation_id_conflict",
- "not the later daemon runtime",
+ "Unit 13\nseals that handler boundary inside the production `MycAdminServer`",
+ "Unit 15\nalone owns task spawning, provider/relay wiring, readiness, reconnect, and\nphase-aware shutdown",
] {
assert!(README.contains(required), "README is missing `{required}`");
}
}
#[test]
+fn step159_unit13_control_surfaces_are_sealed_and_machine_bound() {
+ let contract: serde_json::Value =
+ serde_json::from_str(CONTROL_SURFACES_CONTRACT).expect("control-surface contract");
+ assert_eq!(contract["schema"], "radroots.myc.control-surfaces.v1");
+ assert_eq!(contract["step"], 159);
+ assert_eq!(contract["unit"], 13);
+ assert_eq!(contract["admin"]["route_count"], 19);
+ assert_eq!(contract["admin"]["model_count"], 32);
+ assert_eq!(contract["status"]["publisher_count"], 1);
+ assert_eq!(contract["operations"]["active_probe"], false);
+ assert_eq!(contract["doctor"]["check_count"], 13);
+ assert_eq!(
+ contract["deferred"]["authoritative_daemon_task_graph"],
+ "unit_15"
+ );
+ for required in [
+ "AdminServer::with_system_entropy(router.into_inner(), limits)",
+ ".pointer(\"/resource_limits/admin\")",
+ "UnixAdminSocketWriterAuthority::acquire(runtime.context().paths().run())",
+ "UnixAdminSocketBinding::bind(authority, runtime.artifacts().admin_socket())",
+ "pub struct MycAdminServer",
+ "pub struct MycBoundAdminServer",
+ "pub struct MycAdminCancellationToken",
+ ] {
+ assert!(
+ ADMIN_V1.contains(required),
+ "Unit 13 is missing `{required}`"
+ );
+ }
+ for forbidden in [
+ "pub fn into_inner",
+ "pub const fn into_inner",
+ "pub fn listener",
+ "TcpListener",
+ "std::process::exit",
+ ] {
+ assert!(
+ !ADMIN_V1.contains(forbidden),
+ "Unit 13 exposes forbidden `{forbidden}`"
+ );
+ }
+}
+
+#[test]
fn step144_authorization_is_configuration_bound_and_reuses_durable_state() {
for forbidden in [
"sqlx::",
@@ -1020,10 +1072,11 @@ fn public_errors_remain_crate_owned_redacted_and_source_free() {
.lines()
.filter(|line| line.starts_with("pub struct myc::") && line.ends_with("Error"))
.count();
- assert_eq!(public_error_count, 34);
+ assert_eq!(public_error_count, 35);
assert!(PUBLIC_API.contains("pub struct myc::MycDoctorError"));
assert!(PUBLIC_API.contains("pub struct myc::MycConfigApplyError"));
assert!(PUBLIC_API.contains("pub struct myc::MycAdminOperationError"));
+ assert!(PUBLIC_API.contains("pub struct myc::MycAdminServerError"));
assert!(!PUBLIC_API.contains("pub struct myc::MycRuntimeFoundation {"));
assert!(!PUBLIC_API.contains("pub struct myc::MycStateHost {"));
}
diff --git a/tests/services_hardening_cli.rs b/tests/services_hardening_cli.rs
@@ -80,14 +80,6 @@ fn root_api_freezes_the_exact_command_inventory() {
MycCommandV1::Identity(MycIdentityCommandV1::Status),
),
(
- vec!["identity", "rekey"],
- MycCommandV1::Identity(MycIdentityCommandV1::Rekey),
- ),
- (
- vec!["identity", "replace"],
- MycCommandV1::Identity(MycIdentityCommandV1::Replace),
- ),
- (
vec!["identity", "export-public"],
MycCommandV1::Identity(MycIdentityCommandV1::ExportPublic),
),
@@ -235,22 +227,6 @@ fn every_command_has_one_exact_nonforgeable_execution_plan() {
true,
),
(
- "identity rekey",
- vec!["identity", "rekey"],
- "live_unix_admin",
- None,
- Some("/v1/identity/rekey"),
- false,
- ),
- (
- "identity replace",
- vec!["identity", "replace"],
- "live_unix_admin",
- None,
- Some("/v1/identity/replace"),
- false,
- ),
- (
"identity export-public",
vec!["identity", "export-public"],
"live_unix_admin",
@@ -403,8 +379,6 @@ fn admin_path(operation: MycCliAdminOperationV1) -> &'static str {
MycCliAdminOperationV1::StateStatus => "/v1/state/status",
MycCliAdminOperationV1::StateBackup => "/v1/state/backup",
MycCliAdminOperationV1::IdentityStatus => "/v1/identity/status",
- MycCliAdminOperationV1::IdentityRekey => "/v1/identity/rekey",
- MycCliAdminOperationV1::IdentityReplace => "/v1/identity/replace",
MycCliAdminOperationV1::IdentityPublic => "/v1/identity/public",
}
}
@@ -429,14 +403,6 @@ fn cli_admin_operations_match_the_governed_route_inventory() {
MycAdminRoute::IdentityStatus,
),
(
- MycCliAdminOperationV1::IdentityRekey,
- MycAdminRoute::IdentityRekey,
- ),
- (
- MycCliAdminOperationV1::IdentityReplace,
- MycAdminRoute::IdentityReplace,
- ),
- (
MycCliAdminOperationV1::IdentityPublic,
MycAdminRoute::IdentityPublic,
),
@@ -460,7 +426,7 @@ fn execution_plan_debug_retains_no_bootstrap_or_path_values() {
"--config",
"/secret/config.toml",
"identity",
- "rekey",
+ "export-public",
])
.expect("valid invocation");
let rendered = format!("{invocation:?} {:?}", plan_myc_cli_v1(&invocation));
@@ -480,6 +446,8 @@ fn root_api_rejects_prototype_and_arbitrary_leaf_arguments_safely() {
base(&["metrics"]),
base(&["persistence", "backup"]),
base(&["identity", "generate"]),
+ base(&["identity", "rekey"]),
+ base(&["identity", "replace"]),
base(&["run", "--relay-url", "wss://secret.example"]),
] {
let error = parse_myc_cli_v1_from(arguments).expect_err("forbidden CLI shape");
diff --git a/tests/services_hardening_contracts.rs b/tests/services_hardening_contracts.rs
@@ -93,8 +93,6 @@ fn admin_inventory_is_closed_unique_and_model_complete() {
"GET|/v1/status|radroots.myc.status.get.v1|empty|service_status_v1|false",
"GET|/v1/config/effective|radroots.myc.config.effective.get.v1|empty|effective_config_v1|false",
"GET|/v1/identity/status|radroots.myc.identity.status.get.v1|identity_status_query_v1|identity_status_v1|false",
- "POST|/v1/identity/rekey|radroots.myc.identity.rekey.v1|identity_rekey_request_v1|identity_mutation_receipt_v1|true",
- "POST|/v1/identity/replace|radroots.myc.identity.replace.v1|identity_replace_request_v1|identity_mutation_receipt_v1|true",
"GET|/v1/identity/public|radroots.myc.identity.public.get.v1|identity_public_query_v1|identity_public_v1|false",
"GET|/v1/state/status|radroots.myc.state.status.get.v1|empty|state_status_v1|false",
"POST|/v1/state/backup|radroots.myc.state.backup.create.v1|state_backup_request_v1|state_backup_receipt_v1|true",
@@ -278,17 +276,16 @@ fn admin_inventory_is_closed_unique_and_model_complete() {
]
);
assert_eq!(
- value["admin"]["identity_contract"]["replace_provider_variants"],
+ value["admin"]["identity_contract"],
serde_json::json!({
- "discriminator": "provider",
- "encrypted_file": {
- "required_fields": ["provider", "envelope_path", "credential_reference", "expected_public_key"],
- "provider_value": "encrypted_file"
- },
- "local_signer": {
- "required_fields": ["provider", "socket_path", "request_deadline_ms", "request_max_bytes", "response_max_bytes", "concurrency", "expected_public_key"],
- "provider_value": "local_signer"
- }
+ "roles": [
+ { "id": "transport", "required": true, "disabled_allowed": false, "providers": ["encrypted_file", "local_signer"] },
+ { "id": "user", "required": true, "disabled_allowed": false, "providers": ["encrypted_file", "local_signer"] },
+ { "id": "discovery", "required": false, "disabled_allowed": true, "providers": ["encrypted_file", "local_signer"] }
+ ],
+ "live_mutation": false,
+ "rotation_mode": "offline_create_new_then_config_apply",
+ "in_place_overwrite": false
})
);
assert_eq!(
@@ -328,7 +325,7 @@ fn admin_inventory_is_closed_unique_and_model_complete() {
assert_eq!(mutation_operations, committed_effects);
assert_eq!(
decision_sections_digest(&value),
- "55890c43b1ad17e897e20afe1df72941589b3d6df554bc3c81731aefe1c55d58"
+ "4967401cbb7b777aa78e19e91c75fae9d1245a307bf37ea30611d30d8ffeebbd"
);
}
diff --git a/tests/services_hardening_control_surfaces.rs b/tests/services_hardening_control_surfaces.rs
@@ -0,0 +1,82 @@
+#![forbid(unsafe_code)]
+#![cfg(any(target_os = "linux", target_os = "macos"))]
+
+use myc::{
+ MYC_DOCTOR_CHECK_COUNT, MYC_LIVEZ_PATH, MYC_METRICS_PATH, MYC_READYZ_PATH,
+ MycAdminCancellationToken, MycAdminRoute,
+};
+
+const CONTRACT: &str = include_str!("../contracts/services_hardening/control_surfaces.v1.json");
+const ADMIN_SOURCE: &str = include_str!("../src/admin_v1.rs");
+
+#[test]
+fn unit_13_contract_binds_the_complete_production_control_surface_inventory() {
+ let contract: serde_json::Value = serde_json::from_str(CONTRACT).expect("control contract");
+ assert_eq!(contract["schema"], "radroots.myc.control-surfaces.v1");
+ assert_eq!(contract["contract_version"], 1);
+ assert_eq!(contract["step"], 159);
+ assert_eq!(contract["unit"], 13);
+ assert_eq!(contract["unit_id"], "myc-control-surfaces");
+ assert_eq!(contract["admin"]["route_count"], 19);
+ assert_eq!(contract["admin"]["model_count"], 32);
+ assert_eq!(MycAdminRoute::ALL.len(), 19);
+ assert_eq!(contract["admin"]["live_identity_mutation_routes"], false);
+ assert_eq!(contract["status"]["publisher_count"], 1);
+ assert_eq!(contract["doctor"]["check_count"], MYC_DOCTOR_CHECK_COUNT);
+ assert_eq!(
+ contract["operations"]["routes"],
+ serde_json::json!([MYC_LIVEZ_PATH, MYC_READYZ_PATH, MYC_METRICS_PATH])
+ );
+ assert_eq!(
+ contract["deferred"]["authoritative_daemon_task_graph"],
+ "unit_15"
+ );
+}
+
+#[test]
+fn admin_server_is_sealed_around_canonical_paths_limits_and_system_entropy() {
+ let production = ADMIN_SOURCE
+ .split("\n#[cfg(test)]\nmod tests")
+ .next()
+ .expect("production source");
+ for required in [
+ "AdminServer::with_system_entropy(router.into_inner(), limits)",
+ ".pointer(\"/resource_limits/admin\")",
+ "UnixAdminSocketWriterAuthority::acquire(runtime.context().paths().run())",
+ "UnixAdminSocketBinding::bind(authority, runtime.artifacts().admin_socket())",
+ "pub struct MycAdminServer",
+ "pub struct MycBoundAdminServer",
+ "pub struct MycAdminCancellationToken",
+ ] {
+ assert!(
+ production.contains(required),
+ "admin source is missing `{required}`"
+ );
+ }
+ for forbidden in [
+ "pub fn into_inner",
+ "pub const fn into_inner",
+ "pub fn listener",
+ "pub fn router",
+ "TcpListener",
+ "std::process::exit",
+ "tokio::spawn",
+ ] {
+ assert!(
+ !production.contains(forbidden),
+ "admin source exposes forbidden `{forbidden}`"
+ );
+ }
+}
+
+#[test]
+fn control_surface_cancellation_is_idempotent_and_redacted() {
+ let token = MycAdminCancellationToken::new();
+ assert!(!token.is_cancelled());
+ token.cancel();
+ token.cancel();
+ assert!(token.is_cancelled());
+ let rendered = format!("{token:?}");
+ assert!(!rendered.contains("/private/control.sock"));
+ assert!(!rendered.contains("credential"));
+}