refactor: clarify resource control boundaries

This commit is contained in:
Eric Wendland 2026-07-05 23:48:21 +02:00
commit d443b82f1a
5 changed files with 275 additions and 64 deletions

View file

@ -3,6 +3,7 @@ mod daemon;
pub mod doctor; pub mod doctor;
mod local_control; mod local_control;
mod peer_client; mod peer_client;
mod resource_contracts;
mod runtime; mod runtime;
pub mod service; pub mod service;
mod sync; mod sync;
@ -5588,6 +5589,7 @@ pub fn handle_request(
node: &LocalNode, node: &LocalNode,
request: ControlRequest, request: ControlRequest,
) -> Result<ControlResponse, NodeError> { ) -> Result<ControlResponse, NodeError> {
debug_assert!(!resource_contracts::contracts().is_empty());
let store = Store::open(&node.paths.metadata_db())?; let store = Store::open(&node.paths.metadata_db())?;
match request { match request {
ControlRequest::Status => { ControlRequest::Status => {

View file

@ -15,7 +15,55 @@ pub async fn handle_request_async(
stream = trace.stream.as_deref().unwrap_or(""), stream = trace.stream.as_deref().unwrap_or(""),
"control request started" "control request started"
); );
let result = match request { let result = route_request_async(node, request).await;
match &result {
Ok(_) => tracing::info!(
command = trace.command,
peer_node = trace.peer_node.as_deref().unwrap_or(""),
resource = trace.resource.as_deref().unwrap_or(""),
capability = trace.capability.as_deref().unwrap_or(""),
stream = trace.stream.as_deref().unwrap_or(""),
"control request completed"
),
Err(error) => tracing::warn!(
command = trace.command,
peer_node = trace.peer_node.as_deref().unwrap_or(""),
resource = trace.resource.as_deref().unwrap_or(""),
capability = trace.capability.as_deref().unwrap_or(""),
stream = trace.stream.as_deref().unwrap_or(""),
error_code = node_error_code(error),
%error,
"control request failed"
),
}
result
}
enum AsyncRoute {
Handled(Box<ControlResponse>),
Continue(ControlRequest),
}
async fn route_request_async(
node: &LocalNode,
request: ControlRequest,
) -> Result<ControlResponse, NodeError> {
let request = match route_peer_family(node, request).await? {
AsyncRoute::Handled(response) => return Ok(*response),
AsyncRoute::Continue(request) => request,
};
let request = match route_resource_sync_family(node, request).await? {
AsyncRoute::Handled(response) => return Ok(*response),
AsyncRoute::Continue(request) => request,
};
route_local_with_native_mirrors(node, request).await
}
async fn route_peer_family(
node: &LocalNode,
request: ControlRequest,
) -> Result<AsyncRoute, NodeError> {
let response = match request {
ControlRequest::PeerCardExport { out } => export_peer_card(node, out, true).await, ControlRequest::PeerCardExport { out } => export_peer_card(node, out, true).await,
ControlRequest::PeerPing { node: peer_node } => peer_ping(node, &peer_node).await, ControlRequest::PeerPing { node: peer_node } => peer_ping(node, &peer_node).await,
ControlRequest::PeerAuthCheck { ControlRequest::PeerAuthCheck {
@ -36,6 +84,16 @@ pub async fn handle_request_async(
ControlRequest::NodeEnrollSync { owner_node } => { ControlRequest::NodeEnrollSync { owner_node } => {
node_enrollment_sync_from_owner(node, &owner_node).await node_enrollment_sync_from_owner(node, &owner_node).await
} }
other => return Ok(AsyncRoute::Continue(other)),
}?;
Ok(AsyncRoute::Handled(Box::new(response)))
}
async fn route_resource_sync_family(
node: &LocalNode,
request: ControlRequest,
) -> Result<AsyncRoute, NodeError> {
let response = match request {
ControlRequest::CasFetch { ControlRequest::CasFetch {
node: peer_node, node: peer_node,
hash, hash,
@ -124,8 +182,16 @@ pub async fn handle_request_async(
mtu, mtu,
} => overlay_runtime_up(node, name, bearer_secret, mtu).await, } => overlay_runtime_up(node, name, bearer_secret, mtu).await,
ControlRequest::OverlayDown { name } => overlay_runtime_down(node, &name), ControlRequest::OverlayDown { name } => overlay_runtime_down(node, &name),
other => { other => return Ok(AsyncRoute::Continue(other)),
let response = handle_request(node, other)?; }?;
Ok(AsyncRoute::Handled(Box::new(response)))
}
async fn route_local_with_native_mirrors(
node: &LocalNode,
request: ControlRequest,
) -> Result<ControlResponse, NodeError> {
let response = handle_request(node, request)?;
match &response { match &response {
ControlResponse::CasAdded { hash, .. } => { ControlResponse::CasAdded { hash, .. } => {
mirror_local_blob_to_iroh_blobs(node, hash).await?; mirror_local_blob_to_iroh_blobs(node, hash).await?;
@ -156,29 +222,6 @@ pub async fn handle_request_async(
_ => {} _ => {}
} }
Ok(response) Ok(response)
}
};
match &result {
Ok(_) => tracing::info!(
command = trace.command,
peer_node = trace.peer_node.as_deref().unwrap_or(""),
resource = trace.resource.as_deref().unwrap_or(""),
capability = trace.capability.as_deref().unwrap_or(""),
stream = trace.stream.as_deref().unwrap_or(""),
"control request completed"
),
Err(error) => tracing::warn!(
command = trace.command,
peer_node = trace.peer_node.as_deref().unwrap_or(""),
resource = trace.resource.as_deref().unwrap_or(""),
capability = trace.capability.as_deref().unwrap_or(""),
stream = trace.stream.as_deref().unwrap_or(""),
error_code = node_error_code(error),
%error,
"control request failed"
),
}
result
} }
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]

View file

@ -0,0 +1,163 @@
//! Reviewable resource-family contracts for daemon handlers.
//!
//! These contracts are intentionally small and static. They make every
//! resource family name its resource ID pattern, remote capabilities, and
//! mutation or host-access points in one place while the implementation
//! continues to live in the focused handler functions in `lib.rs` and
//! `local_control.rs`.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) struct ResourceModuleContract {
pub(crate) family: &'static str,
pub(crate) resource_ids: &'static [&'static str],
pub(crate) capabilities: &'static [&'static str],
pub(crate) mutation_points: &'static [&'static str],
}
pub(crate) const RESOURCE_MODULE_CONTRACTS: &[ResourceModuleContract] = &[
ResourceModuleContract {
family: "cas-file-root",
resource_ids: &["resource:cas:local", "resource:cas-tree:<name>"],
capabilities: &["cas.fetch"],
mutation_points: &[
"local CAS blob writes",
"local file-root registration and scans",
"remote file-root imports stored as peer-qualified metadata",
"file-root apply writes only missing paths and records conflicts",
],
},
ResourceModuleContract {
family: "kv",
resource_ids: &["resource:kv:<name>"],
capabilities: &["kv.read", "kv.write_prefix:<prefix>", "kv.write_key:<key>"],
mutation_points: &[
"local SQLite-backed KV create/set",
"authorized sync imports newer or equal timestamp entries idempotently",
"Iroh Documents mirror when native docs are enabled",
],
},
ResourceModuleContract {
family: "db",
resource_ids: &["resource:db:<name>"],
capabilities: &["db.sync"],
mutation_points: &[
"local DB resource registration",
"read-only schema and crsql_changes inspection",
"authorized crsql_changes apply into compatible local databases",
],
},
ResourceModuleContract {
family: "document",
resource_ids: &["resource:document:<name>"],
capabilities: &["document.read"],
mutation_points: &[
"local JSON document create/set",
"authorized sync imports last-writer-wins or Automerge-compatible state",
],
},
ResourceModuleContract {
family: "pubsub",
resource_ids: &["resource:pubsub:<topic>"],
capabilities: &["pubsub.publish", "pubsub.subscribe"],
mutation_points: &[
"daemon-lifetime local topic ring buffer",
"authorized remote publish appends to the serving peer snapshot",
"Iroh gossip broadcast when native gossip is enabled",
],
},
ResourceModuleContract {
family: "pipe",
resource_ids: &[
"resource:pipe:<name>",
"resource:pipe-tcp:<target>",
"resource:pipe-unix:<target>",
],
capabilities: &["pipe.connect", "pipe.listen", "pipe.forward"],
mutation_points: &[
"daemon-lifetime pipe listener registry",
"daemon-lifetime message ring buffer",
"authorized loopback TCP or absolute Unix socket connection opening",
],
},
ResourceModuleContract {
family: "ssh",
resource_ids: &[
"resource:ssh:certs",
"resource:ssh:revocations",
"resource:ssh-proxy:local",
],
capabilities: &[
"ssh_cert.sync",
"ssh_revocation.sync",
"ssh_proxy.connect",
"ssh_proxy.admin_shell",
],
mutation_points: &[
"local SSH certificate request/certificate metadata",
"local SSH revocation metadata",
"authorized SSH proxy connects only to 127.0.0.1:22",
"restricted admin shell executes only built-in geth commands",
],
},
ResourceModuleContract {
family: "overlay",
resource_ids: &["resource:overlay:<name>"],
capabilities: &["overlay.route"],
mutation_points: &[
"local overlay network membership metadata",
"daemon-lifetime packet queue and optional TUN/Wintun injection",
"authorized packet route over the overlay ALPN",
],
},
];
pub(crate) fn contracts() -> &'static [ResourceModuleContract] {
RESOURCE_MODULE_CONTRACTS
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn resource_module_contracts_cover_review_families() {
let families = RESOURCE_MODULE_CONTRACTS
.iter()
.map(|contract| contract.family)
.collect::<std::collections::BTreeSet<_>>();
for expected in [
"cas-file-root",
"kv",
"db",
"document",
"pubsub",
"pipe",
"ssh",
"overlay",
] {
assert!(
families.contains(expected),
"missing resource contract for {expected}"
);
}
for contract in RESOURCE_MODULE_CONTRACTS {
assert!(
!contract.resource_ids.is_empty(),
"{} must document resource IDs",
contract.family
);
assert!(
!contract.capabilities.is_empty(),
"{} must document capabilities",
contract.family
);
assert!(
!contract.mutation_points.is_empty(),
"{} must document mutation points",
contract.family
);
}
}
}

View file

@ -10,10 +10,13 @@ Within `geth-node`, daemon lifecycle code is separated from feature handlers:
`daemon.rs` owns `geth daemon run` startup, local socket binding, shutdown `daemon.rs` owns `geth daemon run` startup, local socket binding, shutdown
signal handling, Iroh endpoint startup, the Iroh accept loop, and background signal handling, Iroh endpoint startup, the Iroh accept loop, and background
live-sync task spawning. `local_control.rs` owns async local `ControlRequest` live-sync task spawning. `local_control.rs` owns async local `ControlRequest`
routing and safe trace-field classification before delegating to feature routing, safe trace-field classification, and named peer/resource/local handler
handlers. Runtime registries for pubsub, pipes, and overlays live behind narrow families before delegating to feature implementations. `resource_contracts.rs`
mutex-protected structs in `runtime.rs`. Command-family handler modules and records the review boundary for each resource family: resource ID patterns,
protected peer-control feature dispatch remain separate refactor targets. capabilities, and mutation or host-access points. Runtime registries for
pubsub, pipes, and overlays live behind narrow mutex-protected structs in
`runtime.rs`. Protected peer-control ALPN dispatch remains a separate refactor
target.
The local metadata store is SQLite product state. `geth-store` tracks a numeric The local metadata store is SQLite product state. `geth-store` tracks a numeric
`schema_version` in the `meta` table and applies ordered migrations up to the `schema_version` in the `meta` table and applies ordered migrations up to the

View file

@ -45,11 +45,11 @@ behavior.
ownership and locking rules. ownership and locking rules.
- `[x]` Existing daemon startup and status tests pass unchanged. - `[x]` Existing daemon startup and status tests pass unchanged.
- `[~]` Extract local control routing. - `[x]` Extract local control routing.
Acceptance criteria: Acceptance criteria:
- `[x]` Local `ControlRequest` dispatch is a routing layer, not the home of - `[x]` Local `ControlRequest` dispatch is a routing layer, not the home of
every feature implementation. every feature implementation.
- `[ ]` Each command family has a small handler module or function group. - `[x]` Each command family has a small handler module or function group.
- `[x]` Local-only behavior remains covered by existing integration tests. - `[x]` Local-only behavior remains covered by existing integration tests.
- `[~]` Extract protected peer-control routing. - `[~]` Extract protected peer-control routing.
@ -64,13 +64,13 @@ behavior.
repeating peer-card boilerplate. repeating peer-card boilerplate.
- `[x]` Remote request tests still prove discovery alone grants no access. - `[x]` Remote request tests still prove discovery alone grants no access.
- `[ ]` Extract resource module handlers. - `[x]` Extract resource module handlers.
Acceptance criteria: Acceptance criteria:
- `[ ]` CAS/file-root, KV, DB, document, pubsub, pipe, SSH, and overlay - `[x]` CAS/file-root, KV, DB, document, pubsub, pipe, SSH, and overlay
handlers are separated enough that each can be reviewed independently. handlers are separated enough that each can be reviewed independently.
- `[ ]` Each module documents its resource IDs, capabilities, and mutation - `[x]` Each module documents its resource IDs, capabilities, and mutation
points. points.
- `[ ]` No generic `geth-common` crate is introduced. - `[x]` No generic `geth-common` crate is introduced.
- `[x]` Extract live-sync engine. - `[x]` Extract live-sync engine.
Acceptance criteria: Acceptance criteria: