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
21 changes: 19 additions & 2 deletions Cargo.lock

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

11 changes: 9 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[workspace]
members = [".", "crates/ironclaw_common", "crates/ironclaw_observability", "crates/ironclaw_host_api", "crates/ironclaw_filesystem", "crates/ironclaw_attachments", "crates/ironclaw_extractors", "crates/ironclaw_memory", "crates/ironclaw_memory_native", "crates/ironclaw_events", "crates/ironclaw_event_projections", "crates/ironclaw_event_streams", "crates/ironclaw_reborn_event_store", "crates/ironclaw_extensions", "crates/ironclaw_processes", "crates/ironclaw_dispatcher", "crates/ironclaw_scripts", "crates/ironclaw_process_sandbox", "crates/ironclaw_mcp", "crates/ironclaw_wasm", "crates/ironclaw_wasm_sandbox_core", "crates/ironclaw_wasm_limiter", "crates/ironclaw_capabilities", "crates/ironclaw_secrets", "crates/ironclaw_network", "crates/ironclaw_host_runtime", "crates/ironclaw_runtime_policy", "crates/ironclaw_authorization", "crates/ironclaw_run_state", "crates/ironclaw_approvals", "crates/ironclaw_resources", "crates/ironclaw_auth", "crates/ironclaw_trust", "crates/ironclaw_turns", "crates/ironclaw_agent_loop", "crates/ironclaw_threads", "crates/ironclaw_prompt_envelope", "crates/ironclaw_hooks", "crates/ironclaw_loop_support", "crates/ironclaw_reborn", "crates/ironclaw_reborn_config", "crates/ironclaw_reborn_composition", "crates/ironclaw_reborn_identity", "crates/ironclaw_first_party_extensions", "crates/ironclaw_reborn_cli", "crates/ironclaw_reborn_traces", "crates/ironclaw_reborn_webui_ingress", "crates/ironclaw_reborn_openai_compat", "crates/ironclaw_conversations", "crates/ironclaw_product_adapters", "crates/ironclaw_product_context", "crates/ironclaw_product_workflow", "crates/ironclaw_product_adapter_registry", "crates/ironclaw_wasm_product_adapters", "crates/ironclaw_telegram_v2_adapter", "crates/ironclaw_slack_v2_adapter", "crates/ironclaw_outbound", "crates/ironclaw_triggers", "crates/ironclaw_projects", "crates/ironclaw_architecture", "crates/ironclaw_safety", "crates/ironclaw_skills", "crates/ironclaw_oauth", "crates/ironclaw_llm", "crates/ironclaw_embeddings", "crates/ironclaw_gateway", "crates/ironclaw_tui", "crates/ironclaw_webui_v2", "crates/ironclaw_skill_learning", "crates/ironclaw_reborn_migration", "tools/ironclaw_stress"]
members = [".", "crates/ironclaw_common", "crates/ironclaw_observability", "crates/ironclaw_host_api", "crates/ironclaw_filesystem", "crates/ironclaw_attachments", "crates/ironclaw_extractors", "crates/ironclaw_memory", "crates/ironclaw_memory_native", "crates/ironclaw_events", "crates/ironclaw_event_projections", "crates/ironclaw_event_streams", "crates/ironclaw_reborn_event_store", "crates/ironclaw_extensions", "crates/ironclaw_extension_host", "crates/ironclaw_processes", "crates/ironclaw_dispatcher", "crates/ironclaw_scripts", "crates/ironclaw_process_sandbox", "crates/ironclaw_mcp", "crates/ironclaw_wasm", "crates/ironclaw_wasm_sandbox_core", "crates/ironclaw_wasm_limiter", "crates/ironclaw_capabilities", "crates/ironclaw_secrets", "crates/ironclaw_network", "crates/ironclaw_host_runtime", "crates/ironclaw_runtime_policy", "crates/ironclaw_authorization", "crates/ironclaw_run_state", "crates/ironclaw_approvals", "crates/ironclaw_resources", "crates/ironclaw_auth", "crates/ironclaw_trust", "crates/ironclaw_turns", "crates/ironclaw_agent_loop", "crates/ironclaw_threads", "crates/ironclaw_prompt_envelope", "crates/ironclaw_hooks", "crates/ironclaw_loop_support", "crates/ironclaw_reborn", "crates/ironclaw_reborn_config", "crates/ironclaw_reborn_composition", "crates/ironclaw_reborn_identity", "crates/ironclaw_first_party_extensions", "crates/ironclaw_reborn_cli", "crates/ironclaw_reborn_traces", "crates/ironclaw_reborn_webui_ingress", "crates/ironclaw_reborn_openai_compat", "crates/ironclaw_conversations", "crates/ironclaw_product_adapters", "crates/ironclaw_product_context", "crates/ironclaw_product_workflow", "crates/ironclaw_product_adapter_registry", "crates/ironclaw_wasm_product_adapters", "crates/ironclaw_telegram_v2_adapter", "crates/ironclaw_slack_v2_adapter", "crates/ironclaw_outbound", "crates/ironclaw_triggers", "crates/ironclaw_projects", "crates/ironclaw_architecture", "crates/ironclaw_safety", "crates/ironclaw_skills", "crates/ironclaw_oauth", "crates/ironclaw_llm", "crates/ironclaw_embeddings", "crates/ironclaw_gateway", "crates/ironclaw_tui", "crates/ironclaw_webui_v2", "crates/ironclaw_skill_learning", "crates/ironclaw_reborn_migration", "tools/ironclaw_stress"]
exclude = [
"channels-src/discord",
"channels-src/feishu",
Expand Down Expand Up @@ -282,7 +282,10 @@ ironclaw_approvals = { path = "crates/ironclaw_approvals", version = "0.1.0" }
ironclaw_attachments = { path = "crates/ironclaw_attachments", version = "0.1.0" }
ironclaw_authorization = { path = "crates/ironclaw_authorization", version = "0.1.0" }
ironclaw_extensions = { path = "crates/ironclaw_extensions", version = "0.1.0" }
ironclaw_filesystem = { path = "crates/ironclaw_filesystem", version = "0.1.0" }
# Extension-runtime P2: the invented-vendor fixture factory the integration
# harness registers (`NativeExtensionFactory`), plus scripted channel fakes.
ironclaw_extension_host = { path = "crates/ironclaw_extension_host", version = "0.1.0", features = ["test-support"] }
ironclaw_filesystem = { path = "crates/ironclaw_filesystem", version = "0.1.0", features = ["postgres"] }
ironclaw_mcp = { path = "crates/ironclaw_mcp", version = "0.1.0" }
ironclaw_first_party_extensions = { path = "crates/ironclaw_first_party_extensions", version = "0.1.0" }
# W6-COLD-SPOTS: `CommunicationPreferenceRecord`/`CommunicationPreferenceKey`
Expand Down Expand Up @@ -465,6 +468,10 @@ path = "tests/integration/comm_context.rs"
name = "reborn_integration_durable"
path = "tests/integration/durable.rs"

[[test]]
name = "reborn_integration_extension_runtime"
path = "tests/integration/extension_runtime.rs"

[[test]]
name = "reborn_integration_extension_visibility"
path = "tests/integration/extension_visibility.rs"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3149,13 +3149,6 @@ const LAYER_MATRIX_EXCEPTIONS: &[LayerMatrixException] = &[
removes_in: "W7",
reason: "runtime process management still depends on resource contracts currently classed with kernel behavior",
},
LayerMatrixException {
crate_name: "ironclaw_dispatcher",
dependency_name: "ironclaw_extensions",
introduced: "2026-07-09",
removes_in: "W7",
reason: "dispatcher still reaches extension routing until the kernel consolidation makes dispatcher an internal kernel module",
},
LayerMatrixException {
crate_name: "ironclaw_event_projections",
dependency_name: "ironclaw_turns",
Expand Down
22 changes: 9 additions & 13 deletions crates/ironclaw_architecture/tests/reborn_extension_specificity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -465,7 +465,14 @@ fn is_test_source_path(path: &Path) -> bool {
.components()
.map(|component| component.as_os_str().to_string_lossy().to_string());
if components.any(|component| {
component == "tests" || component == "__tests__" || component == "test-utils"
component == "tests"
|| component == "__tests__"
|| component == "test-utils"
// `test_support` modules are feature-gated fixtures/scripted
// doubles, not product code; tests may name concrete products
// (overview §8), and the fixtures they build (acme-*) legitimately
// do.
|| component == "test_support"
}) {
return true;
}
Expand All @@ -474,6 +481,7 @@ fn is_test_source_path(path: &Path) -> bool {
.map(|name| name.to_string_lossy().to_string())
.unwrap_or_default();
name == "tests.rs"
|| name == "test_support.rs"
|| name.ends_with("_tests.rs")
|| name.contains(".test.")
|| name.contains(".spec.")
Expand Down Expand Up @@ -1264,18 +1272,6 @@ const ALLOWLIST: &[(&str, &str)] = &[
"crates/ironclaw_reborn_composition/src/slack/slack_setup.rs",
"slack",
),
(
"crates/ironclaw_reborn_composition/src/test_support/mod.rs",
"google",
),
(
"crates/ironclaw_reborn_composition/src/test_support/oauth_product_auth.rs",
"google",
),
(
"crates/ironclaw_reborn_composition/src/test_support/oauth_product_auth.rs",
"oauth2.googleapis.com",
),
(
"crates/ironclaw_reborn_composition/src/web_access.rs",
"web_access",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ use ironclaw_approvals::{ApprovalResolver, LeaseApproval};
use ironclaw_authorization::*;
use ironclaw_capabilities::*;
use ironclaw_dispatcher::{
RuntimeAdapter, RuntimeAdapterRequest, RuntimeAdapterResult, RuntimeDispatcher,
BoundCapabilityAdapter, BoundCapabilityRequest, ResolvedCapability, RuntimeAdapterResult,
RuntimeDispatcher, ToolResolver,
};
use ironclaw_events::{InMemoryEventSink, RuntimeEventKind};
use ironclaw_filesystem::LocalFilesystem;

use ironclaw_host_api::*;
use ironclaw_resources::*;
use ironclaw_run_state::*;
Expand All @@ -20,10 +21,8 @@ use support::*;

#[tokio::test]
async fn capability_host_invokes_through_runtime_dispatcher_and_completes_run() {
let adapter = Arc::new(RecordingRuntimeAdapter::new(
json!({"via":"runtime-dispatcher"}),
));
let (registry, dispatcher, governor, events) = runtime_dispatcher_stack(Arc::clone(&adapter));
let (registry, dispatcher, governor, events, adapter) =
runtime_dispatcher_stack(json!({"via":"runtime-dispatcher"}));
let run_state = InMemoryRunStateStore::new();
let authorizer = GrantAuthorizer::new();
let host =
Expand Down Expand Up @@ -89,8 +88,8 @@ async fn capability_host_invokes_through_runtime_dispatcher_and_completes_run()

#[tokio::test]
async fn capability_host_blocks_then_resumes_approved_dispatch_through_runtime_dispatcher() {
let adapter = Arc::new(RecordingRuntimeAdapter::new(json!({"approved":true})));
let (registry, dispatcher, _governor, events) = runtime_dispatcher_stack(Arc::clone(&adapter));
let (registry, dispatcher, _governor, events, adapter) =
runtime_dispatcher_stack(json!({"approved":true}));
let run_state = InMemoryRunStateStore::new();
let approval_requests = InMemoryApprovalRequestStore::new();
let leases = InMemoryCapabilityLeaseStore::new();
Expand Down Expand Up @@ -200,8 +199,8 @@ async fn capability_host_blocks_then_resumes_approved_dispatch_through_runtime_d

#[tokio::test]
async fn capability_host_rejects_resume_from_wrong_user_scope_without_dispatch_or_lease_claim() {
let adapter = Arc::new(RecordingRuntimeAdapter::new(json!({"must_not":"dispatch"})));
let (registry, dispatcher, _governor, _events) = runtime_dispatcher_stack(Arc::clone(&adapter));
let (registry, dispatcher, _governor, _events, adapter) =
runtime_dispatcher_stack(json!({"must_not":"dispatch"}));
let run_state = InMemoryRunStateStore::new();
let approval_requests = InMemoryApprovalRequestStore::new();
let leases = InMemoryCapabilityLeaseStore::new();
Expand Down Expand Up @@ -282,8 +281,8 @@ async fn capability_host_rejects_resume_from_wrong_user_scope_without_dispatch_o

#[tokio::test]
async fn capability_host_rejects_expired_approval_lease_before_dispatch() {
let adapter = Arc::new(RecordingRuntimeAdapter::new(json!({"must_not":"dispatch"})));
let (registry, dispatcher, _governor, _events) = runtime_dispatcher_stack(Arc::clone(&adapter));
let (registry, dispatcher, _governor, _events, adapter) =
runtime_dispatcher_stack(json!({"must_not":"dispatch"}));
let run_state = InMemoryRunStateStore::new();
let approval_requests = InMemoryApprovalRequestStore::new();
let leases = InMemoryCapabilityLeaseStore::new();
Expand Down Expand Up @@ -366,13 +365,15 @@ struct RecordedRuntimeRequest {

struct RecordingRuntimeAdapter {
output: Value,
governor: Arc<InMemoryResourceGovernor>,
requests: Mutex<Vec<RecordedRuntimeRequest>>,
}

impl RecordingRuntimeAdapter {
fn new(output: Value) -> Self {
fn new(output: Value, governor: Arc<InMemoryResourceGovernor>) -> Self {
Self {
output,
governor,
requests: Mutex::new(Vec::new()),
}
}
Expand All @@ -387,10 +388,10 @@ impl RecordingRuntimeAdapter {
}

#[async_trait]
impl RuntimeAdapter<LocalFilesystem, InMemoryResourceGovernor> for RecordingRuntimeAdapter {
impl BoundCapabilityAdapter for RecordingRuntimeAdapter {
async fn dispatch_json(
&self,
request: RuntimeAdapterRequest<'_, LocalFilesystem, InMemoryResourceGovernor>,
request: BoundCapabilityRequest,
) -> Result<RuntimeAdapterResult, DispatchError> {
self.requests.lock().unwrap().push(RecordedRuntimeRequest {
capability_id: request.capability_id.clone(),
Expand All @@ -407,15 +408,15 @@ impl RuntimeAdapter<LocalFilesystem, InMemoryResourceGovernor> for RecordingRunt
};
let reservation = match request.resource_reservation {
Some(reservation) => reservation,
None => request
None => self
.governor
.reserve(request.scope, request.estimate)
.map_err(|_| DispatchError::Wasm {
kind: RuntimeDispatchErrorKind::Resource,
})?,
};
let output_bytes = usage.output_bytes;
let receipt = request
let receipt = self
.governor
.reconcile(reservation.id, usage.clone())
.map_err(|_| DispatchError::Wasm {
Expand All @@ -431,23 +432,41 @@ impl RuntimeAdapter<LocalFilesystem, InMemoryResourceGovernor> for RecordingRunt
}
}

struct SingleCapabilityResolver {
capability_id: CapabilityId,
resolved: ResolvedCapability,
}

impl ToolResolver for SingleCapabilityResolver {
fn resolve(&self, capability_id: &CapabilityId) -> Option<ResolvedCapability> {
(capability_id == &self.capability_id).then(|| self.resolved.clone())
}
}

fn runtime_dispatcher_stack(
adapter: Arc<RecordingRuntimeAdapter>,
output: Value,
) -> (
Arc<ironclaw_extensions::ExtensionRegistry>,
RuntimeDispatcher<'static, LocalFilesystem, InMemoryResourceGovernor>,
RuntimeDispatcher<'static, InMemoryResourceGovernor>,
Arc<InMemoryResourceGovernor>,
InMemoryEventSink,
Arc<RecordingRuntimeAdapter>,
) {
let registry = Arc::new(registry_with_echo_capability());
let filesystem = Arc::new(LocalFilesystem::new());
let governor = Arc::new(InMemoryResourceGovernor::new());
let events = InMemoryEventSink::new();
let dispatcher =
RuntimeDispatcher::from_arcs(Arc::clone(&registry), filesystem, Arc::clone(&governor))
.with_runtime_adapter_arc(RuntimeKind::Wasm, adapter)
.with_event_sink_arc(Arc::new(events.clone()));
(registry, dispatcher, governor, events)
let adapter = Arc::new(RecordingRuntimeAdapter::new(output, Arc::clone(&governor)));
let resolver: Arc<dyn ToolResolver> = Arc::new(SingleCapabilityResolver {
capability_id: capability_id(),
resolved: ResolvedCapability {
provider: ExtensionId::new("echo").unwrap(),
runtime: RuntimeKind::Wasm,
adapter: Arc::clone(&adapter) as Arc<dyn BoundCapabilityAdapter>,
},
});
let dispatcher = RuntimeDispatcher::from_arcs(resolver, Arc::clone(&governor))
.with_event_sink_arc(Arc::new(events.clone()));
(registry, dispatcher, governor, events, adapter)
}

async fn approve_dispatch(
Expand Down
2 changes: 0 additions & 2 deletions crates/ironclaw_dispatcher/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ layer = "kernel"
[dependencies]
async-trait = "0.1"
ironclaw_events = { path = "../ironclaw_events" }
ironclaw_extensions = { path = "../ironclaw_extensions" }
ironclaw_filesystem = { path = "../ironclaw_filesystem" }
ironclaw_host_api = { path = "../ironclaw_host_api" }
ironclaw_resources = { path = "../ironclaw_resources" }
serde_json = "1"
Expand Down
Loading
Loading