commit 18222d3a4cfc2ed21d93db7e2d5d7e5377918aaf
parent fb88b899b93a652e06a27719e51d1761870195de
Author: triesap <tyson@radroots.org>
Date: Mon, 24 Aug 2026 16:24:21 +0000
refactor(rhi): bind domain Unix admin routes
- activate the exact thirteen domain routes through the sealed RHI adapter
- enforce bounded queries, canonical cursors, and typed trade identifiers
- retain handler-owned replay, cursor authentication, and durable commit semantics
- freeze the cumulative contract, tests, documentation, and public API baseline
Diffstat:
9 files changed, 417 insertions(+), 107 deletions(-)
diff --git a/AGENTS.md b/AGENTS.md
@@ -404,6 +404,11 @@
owns sensitive routes, and Step 209 alone may claim the complete 22-route
surface. Never expose the shared raw router, listener, JSON handler, or a
caller-selected socket path.
+- Through Step 207, the active set is exactly seven common plus thirteen
+ domain routes. Pagination is bounded to 200 items, query fields are closed,
+ authenticated cursors remain bound by the handler to route/filter/snapshot,
+ and decoded trade parameters use the exact lowercase-hex trade-ID type. The
+ two identity-sensitive mutations remain unregistered until Step 208.
- 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
@@ -550,9 +550,9 @@ database-path, worker, environment-file, or arbitrary path-leaf flag. Identity
rekey and replacement are not commands; rotation is a create-new offline
artifact plus governed configuration apply.
-## Common Unix-admin boundary
+## Unix-admin boundary
-Step 206 binds the seven common RHI routes for detailed status, redacted
+Step 206 bound the seven common RHI routes for detailed status, redacted
effective configuration, service-identity status and public export, state
status, online backup, and a bounded metrics snapshot to the shared
`radroots_service_host` HTTP/1.1-over-Unix server. The public RHI route and
@@ -566,11 +566,23 @@ shared writer authority before binding, and uses the shared server's system
entropy for absent correlation IDs. Construction does no I/O; binding does not
spawn; serving remains a later supervised runtime responsibility. Raw shared
routers, listeners, JSON values, and caller-selected socket paths never cross
-the public RHI boundary. Domain and sensitive routes remain deliberately
-unregistered until Steps 207 and 208, before Step 209 closes the complete
-22-route/36-model inventory. The partial machine contract is
+the public RHI boundary. Its historical partial machine contract is
[`admin_common.v1.json`](contracts/services_hardening/admin_common.v1.json).
+Step 207 cumulatively activates the thirteen reconciliation, job, source,
+trade projection/report, publication target/backlog, retry, and presence
+domain routes. Page sizes stop at 200; query names and duplicates are closed;
+cursors use canonical base64url without padding and remain authenticated and
+bound by the handler to the same route, filters, and snapshot. Every decoded
+`{trade_id}` is exactly 32 lowercase hexadecimal characters. Mutations retain
+stable operation-ID exact replay and conflicting-reuse rejection, and success
+still means the handler's contract-defined local effect is durably committed,
+not relay delivery. The adapter performs no SQLite or relay I/O itself. The
+two identity-sensitive mutations remain unregistered for Step 208, before
+Step 209 qualifies the complete 22-route/36-model inventory. The cumulative
+domain contract is
+[`admin_domain.v1.json`](contracts/services_hardening/admin_domain.v1.json).
+
## Sealed service-instance paths
One validated CLI invocation resolves through `RhiRuntimeContext`, which owns
diff --git a/contracts/api_baselines/rhi.txt b/contracts/api_baselines/rhi.txt
@@ -60,8 +60,10 @@ pub rhi::RhiAdminRoute::TradeProjection
pub rhi::RhiAdminRoute::TradeReportCurrent
pub rhi::RhiAdminRoute::TradeReports
impl rhi::RhiAdminRoute
+pub const rhi::RhiAdminRoute::ACTIVE: [Self; 20]
pub const rhi::RhiAdminRoute::ALL: [Self; 22]
pub const rhi::RhiAdminRoute::COMMON: [Self; 7]
+pub const rhi::RhiAdminRoute::DOMAIN: [Self; 13]
pub const fn rhi::RhiAdminRoute::is_mutation(self) -> bool
pub const fn rhi::RhiAdminRoute::method(self) -> rhi::RhiAdminMethod
pub const fn rhi::RhiAdminRoute::operation_id(self) -> &'static str
@@ -697,10 +699,19 @@ pub fn rhi::RhiAdminResponseDocument::from_canonical_bytes(rhi::RhiAdminRoute, &
pub const fn rhi::RhiAdminResponseDocument::route(&self) -> rhi::RhiAdminRoute
impl core::fmt::Debug for rhi::RhiAdminResponseDocument
pub fn rhi::RhiAdminResponseDocument::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result
+pub struct rhi::RhiAdminRouter
+impl core::fmt::Debug for rhi::RhiAdminRouter
+pub fn rhi::RhiAdminRouter::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result
pub struct rhi::RhiAdminRouterError
impl core::error::Error for rhi::RhiAdminRouterError
impl core::fmt::Display for rhi::RhiAdminRouterError
pub fn rhi::RhiAdminRouterError::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result
+pub struct rhi::RhiAdminServer
+impl rhi::RhiAdminServer
+pub async fn rhi::RhiAdminServer::bind(self, &rhi::RhiRuntimeContext) -> core::result::Result<rhi::RhiBoundAdminServer, rhi::RhiAdminServerError>
+pub fn rhi::RhiAdminServer::new<H>(&rhi::RhiConfigDocumentV1, alloc::sync::Arc<H>) -> core::result::Result<Self, rhi::RhiAdminServerError> where H: rhi::RhiAdminHandler
+impl core::fmt::Debug for rhi::RhiAdminServer
+pub fn rhi::RhiAdminServer::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result
pub struct rhi::RhiAdminServerError
impl rhi::RhiAdminServerError
pub const fn rhi::RhiAdminServerError::code(self) -> &'static str
@@ -721,11 +732,11 @@ pub const fn rhi::RhiAdmittedTradeMutationEvent::observed_at_unix_seconds(&self)
pub fn rhi::RhiAdmittedTradeMutationEvent::original_bytes(&self) -> &[u8]
impl core::fmt::Debug for rhi::RhiAdmittedTradeMutationEvent
pub fn rhi::RhiAdmittedTradeMutationEvent::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result
-pub struct rhi::RhiBoundCommonAdminServer
-impl rhi::RhiBoundCommonAdminServer
-pub async fn rhi::RhiBoundCommonAdminServer::serve(self, rhi::RhiAdminCancellationToken) -> core::result::Result<(), rhi::RhiAdminServerError>
-impl core::fmt::Debug for rhi::RhiBoundCommonAdminServer
-pub fn rhi::RhiBoundCommonAdminServer::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result
+pub struct rhi::RhiBoundAdminServer
+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::RhiCliInvocationV1
impl rhi::RhiCliInvocationV1
pub const fn rhi::RhiCliInvocationV1::command(&self) -> rhi::RhiCommandV1
@@ -752,15 +763,6 @@ pub const fn rhi::RhiCommittedPublication::exact_signed_event_bytes(&self) -> &[
pub const fn rhi::RhiCommittedPublication::outbox_id(&self) -> rhi::RhiPublicationOutboxId
impl core::fmt::Debug for rhi::RhiCommittedPublication
pub fn rhi::RhiCommittedPublication::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result
-pub struct rhi::RhiCommonAdminRouter
-impl core::fmt::Debug for rhi::RhiCommonAdminRouter
-pub fn rhi::RhiCommonAdminRouter::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result
-pub struct rhi::RhiCommonAdminServer
-impl rhi::RhiCommonAdminServer
-pub async fn rhi::RhiCommonAdminServer::bind(self, &rhi::RhiRuntimeContext) -> core::result::Result<rhi::RhiBoundCommonAdminServer, rhi::RhiAdminServerError>
-pub fn rhi::RhiCommonAdminServer::new<H>(&rhi::RhiConfigDocumentV1, alloc::sync::Arc<H>) -> core::result::Result<Self, rhi::RhiAdminServerError> where H: rhi::RhiAdminHandler
-impl core::fmt::Debug for rhi::RhiCommonAdminServer
-pub fn rhi::RhiCommonAdminServer::fmt(&self, &mut core::fmt::Formatter<'_>) -> core::fmt::Result
pub struct rhi::RhiConfigApplyError
impl rhi::RhiConfigApplyError
pub const fn rhi::RhiConfigApplyError::code(self) -> &'static str
@@ -2009,7 +2011,7 @@ pub fn rhi::CanonicalRhiIdentityAccess::open_existing(&self, &rhi::RhiIdentityEn
pub fn rhi::admit_rhi_trade_mutation_event(rhi::RhiTradeMutationAdmissionLimits, &[u8], rhi::RhiTradeMutationObservedAtUnixSeconds, rhi::RhiTradeMutationAuthoredTimePolicy) -> core::result::Result<rhi::RhiAdmittedTradeMutationEvent, rhi::RhiTradeMutationAdmissionError>
pub async fn rhi::apply_rhi_configuration(&rhi::RhiRuntimeContext, &rhi::RhiConfigDocumentV1, &rhi::RhiConfigDocumentV1, radroots_service_sqlite::migration::MigrationAppliedAtUnixSeconds, &radroots_service_sqlite::migration::MigrationBuildIdentity) -> core::result::Result<rhi::RhiConfigApplyOutcome, rhi::RhiConfigApplyError>
pub fn rhi::attest_projection_claim(&radroots_trade::trade_contract_v1::RadrootsTradeProjectionV1, &radroots_event::id::MutationId, &rhi::TradeAgreementAttestationPolicy) -> core::result::Result<rhi::TradeAgreementAttestationReportV1, rhi::TradeAgreementAttestationError>
-pub fn rhi::build_rhi_common_admin_router<H>(alloc::sync::Arc<H>) -> core::result::Result<rhi::RhiCommonAdminRouter, rhi::RhiAdminRouterError> where H: rhi::RhiAdminHandler
+pub fn rhi::build_rhi_admin_router<H>(alloc::sync::Arc<H>) -> core::result::Result<rhi::RhiAdminRouter, rhi::RhiAdminRouterError> where H: rhi::RhiAdminHandler
pub fn rhi::build_rhi_signed_evidence_attestation(rhi::RhiReconciliationFinalizationFence, &rhi::RhiDecryptedIdentity, radroots_service_host::time::UnixTimeSeconds, &dyn radroots_service_host::entropy::EntropySource, core::option::Option<rhi::RhiEvidenceAttestationSupersession>) -> core::result::Result<rhi::RhiSignedEvidenceAttestation, rhi::RhiReconciliationAttestationError>
pub fn rhi::build_rhi_signed_presence_documents(rhi::RhiPresenceDesiredCommitOutcome, &rhi::RhiPresenceDesiredAuthority, &rhi::RhiDecryptedIdentity, radroots_service_host::time::UnixTimeSeconds, &dyn radroots_service_host::entropy::EntropySource) -> core::result::Result<rhi::RhiSignedPresenceDocuments, rhi::RhiPresencePublicationError>
pub fn rhi::evaluate_rhi_reconciliation_claim(rhi::RhiReconciliationProjection, radroots_event::id::MutationId) -> rhi::RhiReconciliationEvaluation
diff --git a/contracts/services_hardening/admin_domain.v1.json b/contracts/services_hardening/admin_domain.v1.json
@@ -0,0 +1,63 @@
+{
+ "schema": "radroots.rhi.admin-domain.v1",
+ "contract_version": 1,
+ "service": "rhi",
+ "shared_transport": "radroots_service_host",
+ "final_inventory": {
+ "route_count": 22,
+ "model_count": 36,
+ "source": "operator_contract.v1.json"
+ },
+ "active_route_count": 20,
+ "newly_registered_routes": [
+ "radroots.rhi.reconciliation.status.get.v1",
+ "radroots.rhi.reconciliation.jobs.list.v1",
+ "radroots.rhi.reconciliation.refresh.v1",
+ "radroots.rhi.sources.list.v1",
+ "radroots.rhi.trade.projection.get.v1",
+ "radroots.rhi.trade.report.current.get.v1",
+ "radroots.rhi.trade.reports.list.v1",
+ "radroots.rhi.publication.backlog.list.v1",
+ "radroots.rhi.publication.targets.list.v1",
+ "radroots.rhi.publication.retry.v1",
+ "radroots.rhi.presence.desired.get.v1",
+ "radroots.rhi.presence.render.v1",
+ "radroots.rhi.presence.refresh.v1"
+ ],
+ "deferred_routes": [
+ "radroots.rhi.identity.rekey.v1",
+ "radroots.rhi.identity.replace.v1"
+ ],
+ "pagination": {
+ "maximum_page_items": 200,
+ "cursor_encoding": "canonical_base64url_no_padding",
+ "cursor_integrity": "server_authenticated",
+ "cursor_binding": ["route", "filters", "snapshot"],
+ "duplicate_query_items": "reject",
+ "unknown_query_items": "reject"
+ },
+ "path_parameters": {
+ "trade_id": {
+ "utf8_bytes": 32,
+ "encoding": "lowercase_hex"
+ }
+ },
+ "authority": {
+ "handler_required": true,
+ "mutation_success_after_authoritative_commit": true,
+ "operation_id_replay_exact": true,
+ "operation_id_conflicting_reuse": "reject",
+ "invalid_cursor_error": "invalid_cursor",
+ "raw_shared_router_public": false,
+ "raw_json_handler_public": false
+ },
+ "effects": {
+ "adapter_performs_sqlite": false,
+ "adapter_performs_relay_io": false,
+ "adapter_spawns_tasks": false,
+ "tcp_admin": false,
+ "identity_mutation": false,
+ "nix": false,
+ "oci": false
+ }
+}
diff --git a/src/admin_v1.rs b/src/admin_v1.rs
@@ -10,7 +10,8 @@ use std::{
use radroots_service_host::{
AdminCorrelationId, AdminError, AdminErrorCode, AdminErrorMessage, AdminHttpMethod,
AdminMutationRequest, AdminOperationId, AdminRequest, AdminRouteFailure,
- AdminRouteFailureStatus, AdminRouteOutcome, AdminRouter, AdminServer, AdminServerError,
+ AdminRouteFailureStatus, AdminRouteOutcome, AdminRouter as SharedAdminRouter,
+ AdminServer as SharedAdminServer, AdminServerError as SharedAdminServerError,
AdminTransportLimitValues, AdminTransportLimits, CancellationToken, UnixAdminSocketBinding,
UnixAdminSocketWriterAuthority,
};
@@ -96,6 +97,47 @@ impl RhiAdminRoute {
Self::MetricsSnapshot,
];
+ /// Domain routes admitted by the Step 207 control surface.
+ pub const DOMAIN: [Self; 13] = [
+ Self::ReconciliationStatus,
+ Self::ReconciliationJobs,
+ Self::ReconciliationRefresh,
+ Self::Sources,
+ Self::TradeProjection,
+ Self::TradeReportCurrent,
+ Self::TradeReports,
+ Self::PublicationBacklog,
+ Self::PublicationTargets,
+ Self::PublicationRetry,
+ Self::PresenceDesired,
+ Self::PresenceRender,
+ Self::PresenceRefresh,
+ ];
+
+ /// Routes admitted through Step 207, in final machine-contract order.
+ pub const ACTIVE: [Self; 20] = [
+ Self::Status,
+ Self::EffectiveConfig,
+ Self::IdentityStatus,
+ Self::IdentityPublic,
+ Self::StateStatus,
+ Self::StateBackup,
+ Self::MetricsSnapshot,
+ Self::ReconciliationStatus,
+ Self::ReconciliationJobs,
+ Self::ReconciliationRefresh,
+ Self::Sources,
+ Self::TradeProjection,
+ Self::TradeReportCurrent,
+ Self::TradeReports,
+ Self::PublicationBacklog,
+ Self::PublicationTargets,
+ Self::PublicationRetry,
+ Self::PresenceDesired,
+ Self::PresenceRender,
+ Self::PresenceRefresh,
+ ];
+
#[must_use]
pub const fn method(self) -> RhiAdminMethod {
match self {
@@ -490,31 +532,31 @@ impl fmt::Display for RhiAdminRouterError {
impl Error for RhiAdminRouterError {}
-/// Opaque Step 206 common-route RHI v1 router capability.
+/// Opaque RHI v1 router capability through Step 207.
///
/// The underlying shared-host router remains an implementation detail. The
/// later runtime-composition checkpoint consumes this capability without
/// exposing raw listener or transport authority.
///
/// ```compile_fail
-/// use rhi::RhiCommonAdminRouter;
+/// use rhi::RhiAdminRouter;
///
-/// let _ = RhiCommonAdminRouter { inner: todo!() };
+/// let _ = RhiAdminRouter { inner: todo!() };
/// ```
-pub struct RhiCommonAdminRouter {
- inner: AdminRouter,
+pub struct RhiAdminRouter {
+ inner: SharedAdminRouter,
}
-impl fmt::Debug for RhiCommonAdminRouter {
+impl fmt::Debug for RhiAdminRouter {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
let Self { inner } = self;
let _ = inner;
- formatter.write_str("RhiCommonAdminRouter")
+ formatter.write_str("RhiAdminRouter")
}
}
-impl RhiCommonAdminRouter {
- fn into_inner(self) -> AdminRouter {
+impl RhiAdminRouter {
+ fn into_inner(self) -> SharedAdminRouter {
self.inner
}
}
@@ -619,17 +661,17 @@ impl fmt::Display for RhiAdminServerError {
impl Error for RhiAdminServerError {}
-/// Unbound Step 206 common-route RHI Unix-admin server.
+/// Unbound RHI Unix-admin server through Step 207.
///
/// Construction projects only the already-admitted Rhi 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 RhiCommonAdminServer {
- inner: AdminServer,
+pub struct RhiAdminServer {
+ inner: SharedAdminServer,
}
-impl RhiCommonAdminServer {
+impl RhiAdminServer {
pub fn new<H>(
configuration: &crate::RhiConfigDocumentV1,
handler: Arc<H>,
@@ -638,9 +680,9 @@ impl RhiCommonAdminServer {
H: RhiAdminHandler,
{
let limits = admin_transport_limits(configuration)?;
- let router = build_rhi_common_admin_router(handler)
+ let router = build_rhi_admin_router(handler)
.map_err(|_| RhiAdminServerError::new(RhiAdminServerErrorKind::Router))?;
- let inner = AdminServer::with_system_entropy(router.into_inner(), limits)
+ let inner = SharedAdminServer::with_system_entropy(router.into_inner(), limits)
.map_err(|_| RhiAdminServerError::new(RhiAdminServerErrorKind::ServerConfiguration))?;
Ok(Self { inner })
}
@@ -652,32 +694,32 @@ impl RhiCommonAdminServer {
pub async fn bind(
self,
runtime: &crate::RhiRuntimeContext,
- ) -> Result<RhiBoundCommonAdminServer, RhiAdminServerError> {
+ ) -> Result<RhiBoundAdminServer, RhiAdminServerError> {
let authority = UnixAdminSocketWriterAuthority::acquire(runtime.context().paths().run())
.map_err(|_| RhiAdminServerError::new(RhiAdminServerErrorKind::WriterAuthority))?;
let binding = UnixAdminSocketBinding::bind(authority, runtime.artifacts().admin_socket())
.await
.map_err(|_| RhiAdminServerError::new(RhiAdminServerErrorKind::Bind))?;
- Ok(RhiBoundCommonAdminServer {
+ Ok(RhiBoundAdminServer {
inner: self.inner,
binding,
})
}
}
-impl fmt::Debug for RhiCommonAdminServer {
+impl fmt::Debug for RhiAdminServer {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
- formatter.write_str("RhiCommonAdminServer([sealed])")
+ formatter.write_str("RhiAdminServer([sealed])")
}
}
-/// Bound Step 206 common-route RHI Unix-admin server.
-pub struct RhiBoundCommonAdminServer {
- inner: AdminServer,
+/// Bound RHI Unix-admin server through Step 207.
+pub struct RhiBoundAdminServer {
+ inner: SharedAdminServer,
binding: UnixAdminSocketBinding,
}
-impl RhiBoundCommonAdminServer {
+impl RhiBoundAdminServer {
/// Serves until supervisor cancellation and then drains bounded connection work.
pub async fn serve(
self,
@@ -690,26 +732,24 @@ impl RhiBoundCommonAdminServer {
}
}
-impl fmt::Debug for RhiBoundCommonAdminServer {
+impl fmt::Debug for RhiBoundAdminServer {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
- formatter.write_str("RhiBoundCommonAdminServer([sealed])")
+ formatter.write_str("RhiBoundAdminServer([sealed])")
}
}
-/// Registers only the seven common Step 206 routes on the hardened Lib router.
+/// Registers the seven common and thirteen domain routes owned through Step 207.
///
-/// Domain and sensitive routes remain unregistered until their ordered owners.
-pub fn build_rhi_common_admin_router<H>(
- handler: Arc<H>,
-) -> Result<RhiCommonAdminRouter, RhiAdminRouterError>
+/// The two sensitive identity mutations remain unregistered until Step 208.
+pub fn build_rhi_admin_router<H>(handler: Arc<H>) -> Result<RhiAdminRouter, RhiAdminRouterError>
where
H: RhiAdminHandler,
{
if !operator_route_inventory_is_exact() {
return Err(RhiAdminRouterError);
}
- let mut router = AdminRouter::new();
- for route in RhiAdminRoute::COMMON {
+ let mut router = SharedAdminRouter::new();
+ for route in RhiAdminRoute::ACTIVE {
let handler = Arc::clone(&handler);
router
.route(route.host_method(), route.path(), move |request| {
@@ -718,7 +758,7 @@ where
})
.map_err(|_| RhiAdminRouterError)?;
}
- Ok(RhiCommonAdminRouter { inner: router })
+ Ok(RhiAdminRouter { inner: router })
}
pub(crate) fn admin_transport_limits(
@@ -756,13 +796,14 @@ const fn invalid_admin_configuration() -> RhiAdminServerError {
RhiAdminServerError::new(RhiAdminServerErrorKind::InvalidConfiguration)
}
-const fn map_admin_server_error(error: AdminServerError) -> RhiAdminServerError {
+const fn map_admin_server_error(error: SharedAdminServerError) -> RhiAdminServerError {
let kind = match error {
- AdminServerError::ListenerClone { .. } | AdminServerError::ListenerRegistration { .. } => {
- RhiAdminServerErrorKind::Listener
+ SharedAdminServerError::ListenerClone { .. }
+ | SharedAdminServerError::ListenerRegistration { .. } => RhiAdminServerErrorKind::Listener,
+ SharedAdminServerError::Accept { .. } => RhiAdminServerErrorKind::Accept,
+ SharedAdminServerError::ConnectionTaskPanicked => {
+ RhiAdminServerErrorKind::ConnectionTaskPanicked
}
- AdminServerError::Accept { .. } => RhiAdminServerErrorKind::Accept,
- AdminServerError::ConnectionTaskPanicked => RhiAdminServerErrorKind::ConnectionTaskPanicked,
};
RhiAdminServerError::new(kind)
}
@@ -1732,6 +1773,16 @@ mod tests {
assert!(operator_route_inventory_is_exact());
assert_eq!(RhiAdminRoute::ALL.len(), 22);
assert_eq!(RhiAdminRoute::COMMON.len(), 7);
+ assert_eq!(RhiAdminRoute::DOMAIN.len(), 13);
+ assert_eq!(RhiAdminRoute::ACTIVE.len(), 20);
+ assert_eq!(
+ RhiAdminRoute::ACTIVE,
+ RhiAdminRoute::COMMON
+ .into_iter()
+ .chain(RhiAdminRoute::DOMAIN)
+ .collect::<Vec<_>>()
+ .as_slice()
+ );
let referenced = RhiAdminRoute::ALL
.into_iter()
.flat_map(|route| [route.request_model(), route.response_model()])
@@ -1913,7 +1964,7 @@ mod tests {
const CONFIG: &str = include_str!("../contracts/services_hardening/config.v1.example.toml");
- type FixtureCall = (RhiAdminRoute, Option<String>, Box<[u8]>);
+ type FixtureCall = (RhiAdminRoute, Option<String>, Option<String>, Box<[u8]>);
struct FixtureHandler {
calls: Mutex<Vec<FixtureCall>>,
@@ -1935,9 +1986,19 @@ mod tests {
RhiAdminHandlerErrorKind::OperationIdConflict,
));
}
+ if request
+ .model_bytes()
+ .windows(15)
+ .any(|bytes| bytes == b"\"cursor\":\"AAAA\"")
+ {
+ return Err(RhiAdminHandlerError::new(
+ RhiAdminHandlerErrorKind::InvalidCursor,
+ ));
+ }
self.calls.lock().expect("calls").push((
request.route(),
request.operation_id().map(str::to_owned),
+ request.parameter("trade_id").map(str::to_owned),
request.model_bytes().into(),
));
Ok(response_document(request.route()))
@@ -2020,11 +2081,11 @@ mod tests {
}
#[tokio::test]
- async fn seven_common_routes_round_trip_over_the_hardened_unix_boundary() {
+ async fn twenty_active_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 server = RhiCommonAdminServer::new(&configuration, Arc::clone(&handler))
+ let server = RhiAdminServer::new(&configuration, Arc::clone(&handler))
.expect("production admin server")
.bind(&runtime)
.await
@@ -2040,7 +2101,7 @@ mod tests {
let client =
AdminClient::new(&socket, AdminTransportLimits::DEFAULT).expect("admin client");
- for (index, route) in RhiAdminRoute::COMMON.into_iter().enumerate() {
+ for (index, route) in RhiAdminRoute::ACTIVE.into_iter().enumerate() {
let request = sample_model(route.request_model());
let target = target_for(route, &request);
let response = match route.method() {
@@ -2087,10 +2148,31 @@ mod tests {
assert!(response.starts_with("HTTP/1.1 400 "), "{response}");
}
- let domain_target =
- AdminClientTarget::new("/v1/reconciliation/status").expect("deferred domain route");
+ let domain_target = AdminClientTarget::new("/v1/reconciliation/jobs?limit=201")
+ .expect("bounded domain route");
assert!(client.get::<Value>(&domain_target).await.is_err());
+ let invalid_cursor_target =
+ AdminClientTarget::new("/v1/reconciliation/jobs?cursor=AAAA&limit=1")
+ .expect("authenticated cursor route");
+ let invalid_cursor = client
+ .get::<Value>(&invalid_cursor_target)
+ .await
+ .expect_err("domain handler rejects unbound cursor");
+ assert_eq!(
+ invalid_cursor
+ .failure()
+ .expect("failure envelope")
+ .error()
+ .code()
+ .as_str(),
+ "invalid_cursor"
+ );
+
+ let invalid_trade = AdminClientTarget::new("/v1/trades/not-hex/projection")
+ .expect("trade route target");
+ assert!(client.get::<Value>(&invalid_trade).await.is_err());
+
let sensitive_target =
AdminClientTarget::new("/v1/identity/rekey").expect("deferred sensitive route");
assert!(
@@ -2107,10 +2189,19 @@ mod tests {
{
let calls = handler.calls.lock().expect("calls");
- assert_eq!(calls.len(), 7);
- for (index, (route, operation_id, request)) in calls.iter().enumerate() {
- assert_eq!(*route, RhiAdminRoute::COMMON[index]);
+ assert_eq!(calls.len(), 20);
+ for (index, (route, operation_id, parameter, request)) in calls.iter().enumerate() {
+ assert_eq!(*route, RhiAdminRoute::ACTIVE[index]);
assert_eq!(operation_id.is_some(), route.is_mutation());
+ assert_eq!(
+ parameter.is_some(),
+ matches!(
+ route,
+ RhiAdminRoute::TradeProjection
+ | RhiAdminRoute::TradeReportCurrent
+ | RhiAdminRoute::TradeReports
+ )
+ );
validate_model(
route.request_model(),
&serde_json::from_slice(request).expect("retained request model"),
diff --git a/src/lib.rs b/src/lib.rs
@@ -41,9 +41,9 @@ pub use adapters::nostr::event::NostrEventAdapter;
pub use admin_v1::{
RhiAdminCancellationToken, RhiAdminDocumentError, RhiAdminDocumentErrorKind, RhiAdminFuture,
RhiAdminHandler, RhiAdminHandlerError, RhiAdminHandlerErrorKind, RhiAdminMethod,
- RhiAdminRequestDocument, RhiAdminResponseDocument, RhiAdminRoute, RhiAdminRouterError,
- RhiAdminServerError, RhiAdminServerErrorKind, RhiBoundCommonAdminServer, RhiCommonAdminRouter,
- RhiCommonAdminServer, build_rhi_common_admin_router,
+ RhiAdminRequestDocument, RhiAdminResponseDocument, RhiAdminRoute, RhiAdminRouter,
+ RhiAdminRouterError, RhiAdminServer, RhiAdminServerError, RhiAdminServerErrorKind,
+ RhiBoundAdminServer, build_rhi_admin_router,
};
pub use cli_v1::{
RhiBootstrapProfileV1, RhiCliInvocationV1, RhiCliOutputModeV1, RhiCliV1Error,
diff --git a/tests/package_boundary.rs b/tests/package_boundary.rs
@@ -234,9 +234,10 @@ fn state_catalog_module_is_private_and_root_api_is_curated() {
"RhiAdminRequestDocument",
"RhiAdminResponseDocument",
"RhiAdminHandler",
- "RhiCommonAdminServer",
- "RhiBoundCommonAdminServer",
- "build_rhi_common_admin_router",
+ "RhiAdminRouter",
+ "RhiAdminServer",
+ "RhiBoundAdminServer",
+ "build_rhi_admin_router",
"RhiPublicationErrorKind",
"RhiPublicationMode",
"RhiPublicationRetryPolicy",
@@ -363,13 +364,15 @@ fn state_catalog_module_is_private_and_root_api_is_curated() {
}
#[test]
-fn common_admin_boundary_hides_shared_transport_authority() {
+fn active_admin_boundary_hides_shared_transport_authority() {
for required in [
- "pub struct RhiCommonAdminRouter",
- "pub struct RhiCommonAdminServer",
- "pub struct RhiBoundCommonAdminServer",
+ "pub struct RhiAdminRouter",
+ "pub struct RhiAdminServer",
+ "pub struct RhiBoundAdminServer",
"pub trait RhiAdminHandler",
"pub const COMMON: [Self; 7]",
+ "pub const DOMAIN: [Self; 13]",
+ "pub const ACTIVE: [Self; 20]",
] {
assert!(
ADMIN.contains(required),
@@ -1487,11 +1490,14 @@ fn readme_freezes_the_root_only_boundary_and_exact_baseline() {
"[`runtime_foundation.v1.json`](contracts/services_hardening/runtime_foundation.v1.json)",
"at most 1,024 consecutive generations",
"never stores raw TOML, paths, relay URLs, credential",
- "## Common Unix-admin boundary",
+ "## Unix-admin boundary",
"seven common RHI routes",
- "Domain and sensitive routes remain deliberately",
+ "thirteen reconciliation, job, source",
+ "cursors use canonical base64url without padding",
+ "two identity-sensitive mutations remain unregistered",
"22-route/36-model inventory",
"[`admin_common.v1.json`](contracts/services_hardening/admin_common.v1.json)",
+ "[`admin_domain.v1.json`](contracts/services_hardening/admin_domain.v1.json)",
] {
assert!(README.contains(required), "README is missing {required}");
}
diff --git a/tests/services_hardening_admin_common.rs b/tests/services_hardening_admin_common.rs
@@ -7,7 +7,6 @@ use serde_json::Value;
const COMMON_CONTRACT: &str = include_str!("../contracts/services_hardening/admin_common.v1.json");
const OPERATOR_CONTRACT: &str =
include_str!("../contracts/services_hardening/operator_contract.v1.json");
-const ADMIN_SOURCE: &str = include_str!("../src/admin_v1.rs");
#[test]
fn common_route_inventory_is_an_exact_ordered_subset() {
@@ -51,7 +50,7 @@ fn common_route_inventory_is_an_exact_ordered_subset() {
}
#[test]
-fn common_adapter_is_sealed_bounded_and_partial_by_construction() {
+fn common_checkpoint_contract_remains_sealed_bounded_and_partial() {
let common: Value = serde_json::from_str(COMMON_CONTRACT).expect("common admin contract");
assert_eq!(common["shared_transport"], "radroots_service_host");
assert_eq!(common["authority"]["raw_shared_router_public"], false);
@@ -64,29 +63,11 @@ fn common_adapter_is_sealed_bounded_and_partial_by_construction() {
assert_eq!(common["effects"]["router_construction_performs_io"], false);
assert_eq!(common["effects"]["bind_spawns_task"], false);
assert_eq!(common["effects"]["tcp_admin"], false);
-
- for required in [
- "pub const COMMON: [Self; 7]",
- "for route in RhiAdminRoute::COMMON",
- "AdminServer::with_system_entropy(router.into_inner(), limits)",
- "UnixAdminSocketWriterAuthority::acquire(runtime.context().paths().run())",
- "seven_common_routes_round_trip_over_the_hardened_unix_boundary",
- ] {
- assert!(
- ADMIN_SOURCE.contains(required),
- "missing boundary `{required}`"
- );
- }
- for forbidden in [
- "for route in RhiAdminRoute::ALL",
- "pub fn into_inner",
- "pub fn router",
- "TcpListener",
- "Cors",
- ] {
- assert!(
- !ADMIN_SOURCE.contains(forbidden),
- "forbidden boundary `{forbidden}`"
- );
- }
+ assert_eq!(
+ common["deferred_route_groups"],
+ serde_json::json!([
+ "domain_queries_and_mutations_step_207",
+ "identity_and_sensitive_mutations_step_208"
+ ])
+ );
}
diff --git a/tests/services_hardening_admin_domain.rs b/tests/services_hardening_admin_domain.rs
@@ -0,0 +1,150 @@
+#![forbid(unsafe_code)]
+
+use std::collections::BTreeSet;
+
+use rhi::RhiAdminRoute;
+use serde_json::Value;
+
+const COMMON_CONTRACT: &str = include_str!("../contracts/services_hardening/admin_common.v1.json");
+const DOMAIN_CONTRACT: &str = include_str!("../contracts/services_hardening/admin_domain.v1.json");
+const OPERATOR_CONTRACT: &str =
+ include_str!("../contracts/services_hardening/operator_contract.v1.json");
+const ADMIN_SOURCE: &str = include_str!("../src/admin_v1.rs");
+
+#[test]
+fn domain_inventory_is_exact_ordered_disjoint_and_cumulative() {
+ let common: Value = serde_json::from_str(COMMON_CONTRACT).expect("common admin contract");
+ let domain: Value = serde_json::from_str(DOMAIN_CONTRACT).expect("domain admin contract");
+ let operator: Value = serde_json::from_str(OPERATOR_CONTRACT).expect("operator contract");
+ assert_eq!(domain["schema"], "radroots.rhi.admin-domain.v1");
+ assert_eq!(domain["contract_version"], 1);
+ assert_eq!(domain["active_route_count"], 20);
+
+ let common = common["registered_routes"]
+ .as_array()
+ .expect("common routes")
+ .iter()
+ .map(|value| value.as_str().expect("operation ID"))
+ .collect::<Vec<_>>();
+ let newly_registered = domain["newly_registered_routes"]
+ .as_array()
+ .expect("domain routes")
+ .iter()
+ .map(|value| value.as_str().expect("operation ID"))
+ .collect::<Vec<_>>();
+ assert_eq!(common.len(), 7);
+ assert_eq!(newly_registered.len(), 13);
+ assert!(common.iter().all(|route| !newly_registered.contains(route)));
+
+ let active = common
+ .iter()
+ .chain(&newly_registered)
+ .copied()
+ .collect::<Vec<_>>();
+ let governed = operator["admin"]["routes"]
+ .as_array()
+ .expect("operator routes")
+ .iter()
+ .map(|route| route["operation_id"].as_str().expect("operation ID"))
+ .collect::<Vec<_>>();
+ let deferred = [
+ "radroots.rhi.identity.rekey.v1",
+ "radroots.rhi.identity.replace.v1",
+ ];
+ let expected_active = governed
+ .iter()
+ .copied()
+ .filter(|route| !deferred.contains(route))
+ .collect::<Vec<_>>();
+ assert_eq!(active, expected_active);
+ assert_eq!(
+ RhiAdminRoute::COMMON
+ .into_iter()
+ .map(RhiAdminRoute::operation_id)
+ .collect::<Vec<_>>(),
+ common
+ );
+ assert_eq!(
+ RhiAdminRoute::DOMAIN
+ .into_iter()
+ .map(RhiAdminRoute::operation_id)
+ .collect::<Vec<_>>(),
+ newly_registered
+ );
+ assert_eq!(
+ RhiAdminRoute::ACTIVE
+ .into_iter()
+ .map(RhiAdminRoute::operation_id)
+ .collect::<Vec<_>>(),
+ expected_active
+ );
+ assert_eq!(active.iter().copied().collect::<BTreeSet<_>>().len(), 20);
+ assert_eq!(
+ domain["deferred_routes"],
+ serde_json::json!([
+ "radroots.rhi.identity.rekey.v1",
+ "radroots.rhi.identity.replace.v1"
+ ])
+ );
+ assert_eq!(
+ governed
+ .iter()
+ .copied()
+ .filter(|route| deferred.contains(route))
+ .collect::<Vec<_>>(),
+ deferred
+ );
+}
+
+#[test]
+fn domain_adapter_is_bounded_handler_owned_and_sensitive_route_free() {
+ let domain: Value = serde_json::from_str(DOMAIN_CONTRACT).expect("domain admin contract");
+ assert_eq!(domain["pagination"]["maximum_page_items"], 200);
+ assert_eq!(
+ domain["pagination"]["cursor_encoding"],
+ "canonical_base64url_no_padding"
+ );
+ assert_eq!(
+ domain["pagination"]["cursor_integrity"],
+ "server_authenticated"
+ );
+ assert_eq!(
+ domain["pagination"]["cursor_binding"],
+ serde_json::json!(["route", "filters", "snapshot"])
+ );
+ assert_eq!(domain["pagination"]["duplicate_query_items"], "reject");
+ assert_eq!(domain["path_parameters"]["trade_id"]["utf8_bytes"], 32);
+ assert_eq!(
+ domain["authority"]["operation_id_conflicting_reuse"],
+ "reject"
+ );
+ assert_eq!(domain["authority"]["raw_shared_router_public"], false);
+ assert_eq!(domain["effects"]["adapter_performs_sqlite"], false);
+ assert_eq!(domain["effects"]["adapter_performs_relay_io"], false);
+ assert_eq!(domain["effects"]["identity_mutation"], false);
+
+ for required in [
+ "pub const DOMAIN: [Self; 13]",
+ "pub const ACTIVE: [Self; 20]",
+ "for route in RhiAdminRoute::ACTIVE",
+ "Some((\"trade_id\", \"trade_id\"))",
+ "RhiAdminHandlerErrorKind::InvalidCursor",
+ "twenty_active_routes_round_trip_over_the_hardened_unix_boundary",
+ ] {
+ assert!(
+ ADMIN_SOURCE.contains(required),
+ "missing boundary `{required}`"
+ );
+ }
+ for forbidden in [
+ "for route in RhiAdminRoute::ALL",
+ "pub fn into_inner",
+ "TcpListener",
+ "Cors",
+ ] {
+ assert!(
+ !ADMIN_SOURCE.contains(forbidden),
+ "forbidden boundary `{forbidden}`"
+ );
+ }
+}