Skip to content

Commit 7efef49

Browse files
committed
Merge remote-tracking branch 'origin/reborn-integration' into reborn/fs-rework-unified
# Conflicts: # crates/ironclaw_reborn/src/planned_driver.rs
2 parents 8585184 + aae2b6b commit 7efef49

38 files changed

Lines changed: 4498 additions & 1672 deletions

Cargo.lock

Lines changed: 4 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/ironclaw_agent_loop/src/executor.rs

Lines changed: 25 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -229,8 +229,7 @@ impl CanonicalAgentLoopExecutor {
229229
CancelCheck::Exit(exit) => return Ok(exit),
230230
};
231231

232-
let context_request = planner.context().plan_context_request(&state).await;
233-
let prompt_mode = context_request.mode;
232+
let surface_filter = planner.capability().filter(&state).await;
234233
state = match self
235234
.checkpoint_and_exit_if_cancelled_after_pending_input_ack(
236235
host,
@@ -242,25 +241,14 @@ impl CanonicalAgentLoopExecutor {
242241
CancelCheck::Continue(state) => *state,
243242
CancelCheck::Exit(exit) => return Ok(exit),
244243
};
245-
let prompt_bundle = host
246-
.build_prompt_bundle(context_request)
244+
let mut surface = host
245+
.visible_capabilities(VisibleCapabilityRequest)
247246
.await
248247
.map_err(|_| AgentLoopExecutorError::HostUnavailable {
249-
stage: HostStage::Prompt,
248+
stage: HostStage::Capability,
250249
})?;
251-
self.emit_progress(
252-
host,
253-
LoopProgressEvent::PromptBundleBuilt {
254-
iteration: state.iteration,
255-
bundle_ref: prompt_bundle.bundle_ref.clone(),
256-
mode: prompt_mode,
257-
surface_version: prompt_bundle.surface_version.clone(),
258-
message_count: prompt_bundle.messages.len() as u32,
259-
identity_message_count: prompt_bundle.identity_message_count,
260-
instruction_snippet_count: prompt_bundle.instruction_snippet_count,
261-
},
262-
)
263-
.await;
250+
apply_capability_filter(&mut surface, &surface_filter);
251+
state.surface_version = Some(surface.version.clone());
264252
state = match self
265253
.checkpoint_and_exit_if_cancelled_after_pending_input_ack(
266254
host,
@@ -273,7 +261,9 @@ impl CanonicalAgentLoopExecutor {
273261
CancelCheck::Exit(exit) => return Ok(exit),
274262
};
275263

276-
let surface_filter = planner.capability().filter(&state).await;
264+
let mut context_request = planner.context().plan_context_request(&state).await;
265+
context_request.surface_version = Some(surface.version.clone());
266+
let prompt_mode = context_request.mode;
277267
state = match self
278268
.checkpoint_and_exit_if_cancelled_after_pending_input_ack(
279269
host,
@@ -285,14 +275,25 @@ impl CanonicalAgentLoopExecutor {
285275
CancelCheck::Continue(state) => *state,
286276
CancelCheck::Exit(exit) => return Ok(exit),
287277
};
288-
let mut surface = host
289-
.visible_capabilities(VisibleCapabilityRequest)
278+
let prompt_bundle = host
279+
.build_prompt_bundle(context_request)
290280
.await
291281
.map_err(|_| AgentLoopExecutorError::HostUnavailable {
292-
stage: HostStage::Capability,
282+
stage: HostStage::Prompt,
293283
})?;
294-
apply_capability_filter(&mut surface, &surface_filter);
295-
state.surface_version = Some(surface.version.clone());
284+
self.emit_progress(
285+
host,
286+
LoopProgressEvent::PromptBundleBuilt {
287+
iteration: state.iteration,
288+
bundle_ref: prompt_bundle.bundle_ref.clone(),
289+
mode: prompt_mode,
290+
surface_version: prompt_bundle.surface_version.clone(),
291+
message_count: prompt_bundle.messages.len() as u32,
292+
identity_message_count: prompt_bundle.identity_message_count,
293+
instruction_snippet_count: prompt_bundle.instruction_snippet_count,
294+
},
295+
)
296+
.await;
296297
state = match self
297298
.checkpoint_and_exit_if_cancelled_after_pending_input_ack(
298299
host,

crates/ironclaw_agent_loop/tests/safety_nets.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,8 +94,8 @@ async fn recovery_budget_exhaustion_uses_single_call_retry() {
9494
calls.as_slice(),
9595
[
9696
MockHostCall::PollInputs,
97-
MockHostCall::BuildPromptBundle,
9897
MockHostCall::VisibleCapabilities,
98+
MockHostCall::BuildPromptBundle,
9999
MockHostCall::StageCheckpointPayload(CheckpointKind::BeforeModel),
100100
MockHostCall::SaveCheckpoint(CheckpointKind::BeforeModel),
101101
MockHostCall::StreamModel,

crates/ironclaw_architecture/tests/reborn_dependency_boundaries.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -806,7 +806,8 @@ fn boundary_rules() -> Vec<BoundaryRule> {
806806
],
807807
},
808808
BoundaryRule {
809-
// Registry is a contracts-only Reborn crate. Runtime/dispatcher/engine
809+
// Registry projects ProductAdapter host-api sections from the single
810+
// Extension Manifest v2 and owns activation state. Runtime/dispatcher/engine
810811
// crates would invert ownership, secrets crates could expose raw
811812
// material instead of opaque handles, and v1 WASM/channel crates
812813
// would bypass the Reborn registry boundary.
@@ -820,7 +821,6 @@ fn boundary_rules() -> Vec<BoundaryRule> {
820821
"ironclaw_dispatcher",
821822
"ironclaw_engine",
822823
"ironclaw_events",
823-
"ironclaw_extensions",
824824
"ironclaw_filesystem",
825825
"ironclaw_gateway",
826826
"ironclaw_host_runtime",

crates/ironclaw_host_runtime/src/services.rs

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,8 @@ use ironclaw_turns::LibSqlTurnStateStore;
7373
#[cfg(feature = "postgres")]
7474
use ironclaw_turns::PostgresTurnStateStore;
7575
use ironclaw_turns::{
76-
DefaultTurnCoordinator, InMemoryTurnStateStore, NoopTurnRunWakeNotifier, TurnRunWakeNotifier,
77-
TurnStateStore, runner::TurnRunTransitionPort,
76+
DefaultTurnCoordinator, InMemoryTurnStateStore, NoopTurnRunWakeNotifier, RunProfileResolver,
77+
TurnRunWakeNotifier, TurnStateStore, runner::TurnRunTransitionPort,
7878
};
7979
use ironclaw_wasm::{
8080
DenyWasmHostHttp, EmptyWasmRuntimeCredentials, PreparedWitTool, WasmError,
@@ -169,6 +169,7 @@ pub enum ProductionWiringComponent {
169169
WasmRuntime,
170170
FirstPartyRuntime,
171171
TurnState,
172+
RunProfileResolver,
172173
TurnRunWakeNotifier,
173174
}
174175

@@ -194,6 +195,7 @@ impl ProductionWiringComponent {
194195
Self::WasmRuntime => "wasm_runtime",
195196
Self::FirstPartyRuntime => "first_party_runtime",
196197
Self::TurnState => "turn_state",
198+
Self::RunProfileResolver => "run_profile_resolver",
197199
Self::TurnRunWakeNotifier => "turn_run_wake_notifier",
198200
}
199201
}
@@ -275,6 +277,7 @@ struct ProductionComponentTypes {
275277
mcp_runtime: Option<ProductionComponentType>,
276278
first_party_runtime: Option<ProductionComponentType>,
277279
turn_state: Option<ProductionComponentType>,
280+
run_profile_resolver: Option<ProductionComponentType>,
278281
turn_run_transition_port: Option<ProductionComponentType>,
279282
turn_run_transition_port_verified: bool,
280283
turn_run_wake_notifier: Option<ProductionComponentType>,
@@ -383,6 +386,7 @@ where
383386
first_party_runtime: Option<Arc<FirstPartyCapabilityRegistry>>,
384387
wasm_runtime: Option<Arc<WasmRuntimeAdapter>>,
385388
turn_state: Option<Arc<dyn TurnStateStore>>,
389+
run_profile_resolver: Option<Arc<dyn RunProfileResolver>>,
386390
turn_run_transition_port: Option<Arc<dyn TurnRunTransitionPort>>,
387391
turn_run_wake_notifier: Option<Arc<dyn TurnRunWakeNotifier>>,
388392
component_types: ProductionComponentTypes,
@@ -438,6 +442,7 @@ where
438442
first_party_runtime: None,
439443
wasm_runtime: None,
440444
turn_state: None,
445+
run_profile_resolver: None,
441446
turn_run_transition_port: None,
442447
turn_run_wake_notifier: None,
443448
component_types: ProductionComponentTypes {
@@ -462,6 +467,7 @@ where
462467
mcp_runtime: None,
463468
first_party_runtime: None,
464469
turn_state: None,
470+
run_profile_resolver: None,
465471
turn_run_transition_port: None,
466472
turn_run_transition_port_verified: false,
467473
turn_run_wake_notifier: None,
@@ -501,6 +507,7 @@ where
501507
first_party_runtime,
502508
wasm_runtime,
503509
turn_state,
510+
run_profile_resolver,
504511
turn_run_transition_port,
505512
turn_run_wake_notifier,
506513
mut component_types,
@@ -533,6 +540,7 @@ where
533540
first_party_runtime,
534541
wasm_runtime,
535542
turn_state,
543+
run_profile_resolver,
536544
turn_run_transition_port,
537545
turn_run_wake_notifier,
538546
component_types,
@@ -587,6 +595,7 @@ where
587595
first_party_runtime,
588596
wasm_runtime,
589597
turn_state,
598+
run_profile_resolver,
590599
turn_run_transition_port,
591600
turn_run_wake_notifier,
592601
mut component_types,
@@ -629,6 +638,7 @@ where
629638
first_party_runtime,
630639
wasm_runtime,
631640
turn_state,
641+
run_profile_resolver,
632642
turn_run_transition_port,
633643
turn_run_wake_notifier,
634644
component_types,
@@ -819,6 +829,15 @@ where
819829
self
820830
}
821831

832+
pub fn with_run_profile_resolver<T>(mut self, resolver: Arc<T>) -> Self
833+
where
834+
T: RunProfileResolver + 'static,
835+
{
836+
self.component_types.run_profile_resolver = Some(ProductionComponentType::of::<T>());
837+
self.run_profile_resolver = Some(resolver);
838+
self
839+
}
840+
822841
#[cfg(feature = "libsql")]
823842
pub async fn with_libsql_turn_state_store(
824843
self,
@@ -1119,6 +1138,11 @@ where
11191138
ProductionWiringComponent::TurnState,
11201139
self.turn_state.is_some(),
11211140
);
1141+
self.push_missing(
1142+
&mut issues,
1143+
ProductionWiringComponent::RunProfileResolver,
1144+
self.run_profile_resolver.is_some(),
1145+
);
11221146
self.push_missing(
11231147
&mut issues,
11241148
ProductionWiringComponent::TurnRunWakeNotifier,
@@ -1408,6 +1432,13 @@ where
14081432
None,
14091433
));
14101434
};
1435+
let Some(run_profile_resolver) = self.run_profile_resolver.as_ref() else {
1436+
return Err(production_wiring_report(
1437+
ProductionWiringComponent::RunProfileResolver,
1438+
ProductionWiringIssueKind::Missing,
1439+
None,
1440+
));
1441+
};
14111442
let Some(notifier) = self.turn_run_wake_notifier.as_ref() else {
14121443
return Err(production_wiring_report(
14131444
ProductionWiringComponent::TurnRunWakeNotifier,
@@ -1416,6 +1447,7 @@ where
14161447
));
14171448
};
14181449
Ok(DefaultTurnCoordinator::new(Arc::clone(turn_state))
1450+
.with_run_profile_resolver(Arc::clone(run_profile_resolver))
14191451
.with_wake_notifier(Arc::clone(notifier)))
14201452
}
14211453

@@ -1485,6 +1517,11 @@ where
14851517
ProductionWiringComponent::TurnState,
14861518
self.turn_state.is_some(),
14871519
);
1520+
self.push_missing(
1521+
&mut issues,
1522+
ProductionWiringComponent::RunProfileResolver,
1523+
self.run_profile_resolver.is_some(),
1524+
);
14881525
self.push_missing(
14891526
&mut issues,
14901527
ProductionWiringComponent::TurnRunWakeNotifier,

crates/ironclaw_host_runtime/tests/host_runtime_services_contract.rs

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,9 @@ use ironclaw_trust::{
8080
use ironclaw_turns::LibSqlTurnStateStore;
8181
#[cfg(feature = "libsql")]
8282
use ironclaw_turns::{
83-
AcceptedMessageRef, IdempotencyKey, ReplyTargetBindingRef, RunProfileRequest, SourceBindingRef,
84-
SubmitTurnRequest, SubmitTurnResponse, TurnActor, TurnCoordinator, TurnScope, TurnStateStore,
83+
AcceptedMessageRef, IdempotencyKey, InMemoryRunProfileResolver, ReplyTargetBindingRef,
84+
RunProfileRequest, SourceBindingRef, SubmitTurnRequest, SubmitTurnResponse, TurnActor,
85+
TurnCoordinator, TurnScope, TurnStateStore,
8586
};
8687
use ironclaw_turns::{NoopTurnRunWakeNotifier, TurnRunWake, TurnRunWakeNotifier};
8788
use ironclaw_wasm::{
@@ -144,6 +145,13 @@ fn production_wiring_validation_rejects_missing_components_and_local_only_defaul
144145
),
145146
"missing turn-state store should be reported: {report:?}"
146147
);
148+
assert!(
149+
report.contains(
150+
ProductionWiringComponent::RunProfileResolver,
151+
ProductionWiringIssueKind::Missing
152+
),
153+
"missing run-profile resolver should be reported: {report:?}"
154+
);
147155
assert!(
148156
report.contains(
149157
ProductionWiringComponent::TurnRunWakeNotifier,
@@ -749,6 +757,7 @@ async fn production_turn_coordinator_uses_configured_store_and_notifier() {
749757
.with_libsql_turn_state_store(Arc::clone(&db))
750758
.await
751759
.unwrap()
760+
.with_run_profile_resolver(Arc::new(InMemoryRunProfileResolver::default()))
752761
.with_turn_run_wake_notifier(Arc::clone(&notifier));
753762

754763
let coordinator = services
@@ -771,6 +780,36 @@ async fn production_turn_coordinator_uses_configured_store_and_notifier() {
771780
assert_eq!(notifier.wakes()[0].run_id, run_id);
772781
}
773782

783+
#[cfg(feature = "libsql")]
784+
#[tokio::test]
785+
async fn production_turn_coordinator_requires_explicit_run_profile_resolver() {
786+
let db_dir = tempfile::tempdir().unwrap();
787+
let db_path = db_dir.path().join("turn-coordinator-missing-resolver.db");
788+
let db = Arc::new(libsql::Builder::new_local(db_path).build().await.unwrap());
789+
790+
let services = HostRuntimeServices::new(
791+
Arc::new(registry_with_manifest(SCRIPT_MANIFEST)),
792+
Arc::new(LocalFilesystem::new()),
793+
Arc::new(InMemoryResourceGovernor::new()),
794+
Arc::new(GrantAuthorizer::new()),
795+
ProcessServices::in_memory(),
796+
CapabilitySurfaceVersion::new("surface-v1").unwrap(),
797+
)
798+
.with_libsql_turn_state_store(Arc::clone(&db))
799+
.await
800+
.unwrap()
801+
.with_turn_run_wake_notifier(Arc::new(RecordingTurnRunWakeNotifier::default()));
802+
803+
let report = match services.turn_coordinator_for_production() {
804+
Ok(_) => panic!("production turn coordinator must fail closed without a resolver"),
805+
Err(report) => report,
806+
};
807+
assert!(report.contains(
808+
ProductionWiringComponent::RunProfileResolver,
809+
ProductionWiringIssueKind::Missing
810+
));
811+
}
812+
774813
#[test]
775814
fn production_wiring_validation_rejects_noop_turn_wake_notifier() {
776815
let services = HostRuntimeServices::new(

0 commit comments

Comments
 (0)