lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README

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.