README.md (6768B)
1 # radroots_service_host 2 3 `radroots_service_host` is the unpublished, native host-mechanism crate for 4 Radroots services. It provides narrow, reusable building blocks for validated 5 service identity, deterministic configuration loading, injected time and 6 entropy, lifecycle supervision, permissioned local administration, and cached 7 operations surfaces. 8 9 The crate owns mechanisms only. Service-specific configuration, policy, 10 database schema, domain routes, readiness decisions, process CLI parsing, 11 runtime creation, global logging, and signal installation remain with the 12 consuming service or binary boundary. Authoritative tasks may not be detached, 13 and this crate must not become a broad lifecycle framework. 14 15 All service-host modules are private. Consumers use the deliberate crate-root 16 surface, including its re-exported [`ServiceId`](crate::ServiceId) and 17 [`InstanceId`](crate::InstanceId) identity types. 18 19 The host boundary is governed by the 20 [`services_hardening_host.v1` machine decision](../../contracts/architecture/decisions/services_hardening_host.v1.json). 21 The reviewed Rust surface is recorded in the 22 [public API baseline](../../contracts/api_baselines/radroots_service_host.txt). 23 24 ## Strict configuration values 25 26 Configuration parsing is inert except for the one explicitly selected file. 27 Schema identity is checked before service-owned typed deserialization, and 28 common leaves use strict canonical units: 29 30 ```rust 31 use radroots_service_host::{ 32 BoundedCount, ByteLimit, LoggingFormat, OptionalOperationsBind, 33 PositiveDuration, 34 }; 35 36 let grace: PositiveDuration = "30s".parse()?; 37 let response_limit: ByteLimit = "1MiB".parse()?; 38 let workers = BoundedCount::<64>::new(8)?; 39 let logging: LoggingFormat = "json".parse()?; 40 let operations: OptionalOperationsBind = "127.0.0.1:9100".parse()?; 41 42 assert_eq!(grace.to_string(), "30s"); 43 assert_eq!(response_limit.bytes(), 1_048_576); 44 assert_eq!(workers.value(), 8); 45 assert_eq!(logging.to_string(), "json"); 46 assert!(operations.is_enabled()); 47 # Ok::<(), Box<dyn std::error::Error>>(()) 48 ``` 49 50 An enabled non-loopback operations address does not authorize public binding; 51 conversion to [`OperationsListenerConfig`](crate::OperationsListenerConfig) 52 still requires [`OperationsBindPolicy::Public`](crate::OperationsBindPolicy::Public). 53 Configuration errors redact selected paths and raw parser input from ordinary 54 Display, Debug, and standard error chains. 55 56 ## Cached state and explicit cancellation 57 58 Status and operations reads consume the latest validated cached value. They do 59 not perform an active probe. Cancellation is explicit, cloneable, and 60 directional from parent to child: 61 62 ```rust 63 use radroots_service_host::{ 64 CachedServiceState, CancellationToken, Readiness, ReasonCodes, 65 ServiceOperationalState, ServicePhase, cached_service_state, 66 }; 67 68 let operational = ServiceOperationalState::new( 69 ServicePhase::Ready, 70 Readiness::READY, 71 ReasonCodes::empty(), 72 )?; 73 let (_publisher, reader) = cached_service_state(CachedServiceState::new(operational, ())); 74 assert_eq!(reader.snapshot().operational().phase(), ServicePhase::Ready); 75 76 let parent = CancellationToken::new(); 77 let child = parent.child_token(); 78 parent.cancel(); 79 assert!(child.is_cancelled()); 80 # Ok::<(), radroots_service_host::StatusContractError>(()) 81 ``` 82 83 ## Local administration and operations 84 85 Detailed status and mutations use the bounded HTTP/1.1 Unix administration 86 models and server. Linux admission requires peer credentials; macOS v1 relies 87 on the exact owner-only filesystem posture. The TCP operations server exposes 88 only cached `GET /livez`, `GET /readyz`, and `GET /metrics`; it has no route 89 extension point for detailed status or mutation authority. 90 91 Request and response bodies, headers, queries, connection concurrency, 92 deadlines, task names, metric snapshots, reason collections, and shutdown 93 phases have explicit bounds. Safe Display and ordinary Debug projections 94 exclude raw paths, SQL, credentials, payloads, and provider or relay errors. 95 Where a host error retains an original cause for trusted inspection, that cause 96 is not rendered by its safe projection. 97 98 Reason-code iterators and wire arrays stop at the first item beyond their fixed 99 maximum. Administration payloads are traversed directly from the bounded input 100 or streamed directly into the capped response writer; validation does not 101 materialize an intermediate `serde_json::Value` tree. Recursive null rejection 102 and duplicate or unknown field rejection remain fail closed. 103 104 Every bounded text constructor validates borrowed UTF-8 before it creates the 105 retained string. Bounded wire strings use validating Serde visitors, so the 106 host does not create a second prevalidation copy of identifiers, safe messages, 107 reason codes, task names, routes, or metric vocabulary. 108 109 Administration operation and correlation identifiers are closed ASCII values 110 of 1 through 128 bytes. Their first byte is alphanumeric; later bytes are 111 alphanumeric or `.`, `_`, `:`, or `-`. Ordinary `Debug` is redacted and no 112 `Display` implementation exposes the retained value. Trusted protocol code 113 uses the explicit borrowed `as_str` accessor for serialization. 114 115 ## Process and runtime ownership 116 117 The crate does not parse a CLI, read configuration from environment variables, 118 install an operating-system signal handler, initialize tracing, create a Tokio 119 runtime, call `process::exit`, choose service paths, open a service database, 120 or own service-domain policy. Process binaries inject clocks and entropy, 121 normalize signals through [`ProcessSignalAdapter`], register every authoritative 122 task with [`TaskSupervisor`], and execute [`GracefulShutdown`] explicitly. 123 124 Shutdown task cancellation is phase aware. Entering a phase cancels only tasks 125 assigned to that phase and does not advance until their joins are observed; 126 bounded one-shot work drains without cancellation during `DrainOperations`, 127 while a fatal task outcome still 128 cancels and joins the complete graph. `GracefulShutdown` retains one absolute 129 deadline plus the completed handler/drain boundary across cancellation and 130 retry, so no phase or cleanup attempt receives a fresh grace period. A caller- 131 cancelled incomplete handler may be entered again and must be idempotent and 132 cancellation safe. The first phase or task failure is retained while later 133 close phases continue as long as the original deadline remains. 134 135 ## Supported targets and publication 136 137 The generic host/config/status/lifecycle surfaces support the workspace's 138 qualified Rust targets. Unix administration and its client/server are exported 139 only on Linux and macOS. Linux peer credentials are required and fail closed; 140 macOS makes no peer-credential equivalence claim. 141 142 Publication is disabled. The package is not part of the public Radroots crate 143 release closure.