Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion crates/subc-control/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "subc-control"
version = "0.1.1"
version = "0.1.2"
edition = "2021"
publish = true
description = "Client-facing subc control-plane wire shapes."
Expand Down
61 changes: 61 additions & 0 deletions crates/subc-control/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ pub mod ops {
pub const SUPERVISOR_HEALTH_PROBE: &str = "supervisor.health_probe";
pub const SUPERVISOR_HEALTH: &str = "supervisor.health";
pub const SUPERVISOR_STDERR_TAIL: &str = "supervisor.stderr_tail";
pub const SUPERVISOR_ROUTES: &str = "supervisor.routes";
}

/// Client-originated channel-0 control RPC body.
Expand Down Expand Up @@ -135,6 +136,23 @@ pub enum ClientControlRequest {
SupervisorHealthProbe { module_id: String },
#[serde(rename = "supervisor.health")]
SupervisorHealth {},
/// Enumerate the routes currently served by one supervised module, or every
/// module when omitted.
///
/// This privileged census is control-plane-only. It is deliberately not an
/// MCP facade or agent-tool operation: callers holding the daemon control
/// connection may inspect live route ownership, while agent-facing modules
/// must not be able to address that surface at all.
///
/// The daemon answers from its forwarding table under a read lock and never
/// consults a module. That makes the read safe during a drain, when a module
/// cannot be queried without recreating the hang/restart hazard that route
/// status reads avoid.
#[serde(rename = "supervisor.routes")]
SupervisorRoutes {
#[serde(default, skip_serializing_if = "Option::is_none")]
module_id: Option<String>,
},
/// Retained stderr for one module.
///
/// A separate op rather than a field on `supervisor.list`: the tail is
Expand Down Expand Up @@ -224,6 +242,8 @@ pub enum ClientControlResponse {
generation: u64,
modules: Vec<SupervisorHealthEntry>,
},
#[serde(rename = "supervisor.routes")]
SupervisorRoutes { modules: Vec<SupervisorRouteModule> },
#[serde(rename = "supervisor.stderr_tail")]
SupervisorStderrTail {
module_id: String,
Expand All @@ -249,6 +269,36 @@ pub struct StderrTail {
pub dropped_lines: u64,
}

/// Live routes served by one module.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct SupervisorRouteModule {
pub module_id: String,
pub routes: Vec<SupervisorRoute>,
}

/// One live consumer route in a [`SupervisorRouteModule`].
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct SupervisorRoute {
pub consumer: SupervisorRouteConsumer,
/// Milliseconds since the daemon bound this route.
pub age_ms: u64,
/// True once the endpoint began draining. Draining routes remain visible so
/// a census does not misreport an already-closing route as live.
pub draining: bool,
}

/// The identity tier the daemon can honestly report for a route consumer.
///
/// A caller that proved a live daemon-issued launch nonce is named `reserved`.
/// A direct key-holder has no such attestation, so it is reported as `direct`
/// with its connection counter instead of an invented module name.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum SupervisorRouteConsumer {
Reserved { module_id: String },
Direct { connection_id: u64 },
}

/// Whether stderr is being captured for a module, and if not, why not.
///
/// A typed state rather than an empty-tail convention. "The module printed
Expand Down Expand Up @@ -554,4 +604,15 @@ mod tests {
assert_eq!(consumer_capabilities, None);
assert_eq!(admission_facts, None);
}

#[test]
fn supervisor_routes_is_a_control_plane_request() {
let body = serde_json::json!({
"op": "supervisor.routes",
"module_id": "aft"
});

let request: ClientControlRequest = serde_json::from_value(body.clone()).unwrap();
assert_eq!(serde_json::to_value(request).unwrap(), body);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@
"supervisor.set_enabled",
"supervisor.health_probe",
"supervisor.health",
"supervisor.stderr_tail"
"supervisor.stderr_tail",
"supervisor.routes"
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"supervisor.set_enabled",
"supervisor.health_probe",
"supervisor.health",
"supervisor.stderr_tail"
"supervisor.stderr_tail",
"supervisor.routes"
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"supervisor.set_enabled",
"supervisor.health_probe",
"supervisor.health",
"supervisor.stderr_tail"
"supervisor.stderr_tail",
"supervisor.routes"
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"modules": [
{
"module_id": "target",
"routes": [
{
"age_ms": 0,
"consumer": {
"connection_id": 102,
"kind": "direct"
},
"draining": true
},
{
"age_ms": 0,
"consumer": {
"kind": "reserved",
"module_id": "fed"
},
"draining": true
}
]
}
],
"op": "supervisor.routes"
}
26 changes: 25 additions & 1 deletion crates/subc-control/tests/golden_json.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ use serde_json::Value;
use subc_control::{
CatalogEntry, ClientControlRequest, ClientControlResponse, ConsumerIdentity, PollKind,
StderrCaptureState, StderrTail, StderrTailEntry, SupervisorEntry, SupervisorHealthEntry,
SupervisorHealthStatus, SupervisorRescanResult,
SupervisorHealthStatus, SupervisorRescanResult, SupervisorRoute, SupervisorRouteConsumer,
SupervisorRouteModule,
};
use subc_protocol::{
manifest::{
Expand Down Expand Up @@ -276,6 +277,28 @@ fn client_control_responses() -> Vec<(&'static str, ClientControlResponse)> {
modules: vec![supervisor_health_entry()],
},
),
(
"client_control_response_supervisor_routes",
ClientControlResponse::SupervisorRoutes {
modules: vec![SupervisorRouteModule {
module_id: "target".to_string(),
routes: vec![
SupervisorRoute {
consumer: SupervisorRouteConsumer::Direct { connection_id: 102 },
age_ms: 0,
draining: true,
},
SupervisorRoute {
consumer: SupervisorRouteConsumer::Reserved {
module_id: "fed".to_string(),
},
age_ms: 0,
draining: true,
},
],
}],
},
),
(
"client_control_response_supervisor_stderr_tail",
ClientControlResponse::SupervisorStderrTail {
Expand Down Expand Up @@ -346,6 +369,7 @@ fn thin_core_ops() -> Vec<String> {
"supervisor.health_probe".to_string(),
"supervisor.health".to_string(),
"supervisor.stderr_tail".to_string(),
"supervisor.routes".to_string(),
]
}

Expand Down
2 changes: 1 addition & 1 deletion crates/subc-core/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "subc-core"
version = "0.3.1"
version = "0.3.2"
edition = "2021"
publish = false
description = "subc daemon core: loopback TCP transport and opaque-byte splice router."
Expand Down
3 changes: 2 additions & 1 deletion crates/subc-core/src/bench_harness.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use subc_protocol::{
Bindings, Concurrency, ExecutionMode, IdentityBinding, IdentityScope, ModuleManifest,
ProviderRole, StorageBinding, StorageKind, StorageScope, Tool, TrustTier,
},
Flags, FrameType, Priority, PROTOCOL_VERSION,
Flags, FrameType, Principal, Priority, PROTOCOL_VERSION,
};
use tokio::sync::mpsc;

Expand Down Expand Up @@ -177,6 +177,7 @@ pub async fn build_bench_forwarding_setup(
PROTOCOL_VERSION,
route_index as u64 + 1,
&module_id,
Principal::Direct,
tokio::time::Instant::now() + std::time::Duration::from_secs(30),
)
.await
Expand Down
Loading