Skip to content
Merged
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
1 change: 1 addition & 0 deletions .alfonso/release-notes/next/confine-busy-db.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Tool calls no longer fail spuriously while the database opens or is briefly busy; saved session confinement remains enforced.
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"targeted_mechanism": "none",
"kind": "harness",
"slice_class": "non_ranking",
"fixtures": [
"harness-goldens"
]
}
24 changes: 24 additions & 0 deletions crates/aft/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2747,6 +2747,10 @@ struct PolicyConfinementMemo {
/// answer, so a test can save a policy in that window.
#[cfg(test)]
after_read_for_test: Option<Box<dyn FnOnce(&AppContext) + Send>>,
/// Runs after an inconclusive memory probe, before acquiring the database
/// handle, so a test can release a contended slot at that exact boundary.
#[cfg(test)]
after_memory_miss_for_test: Option<Box<dyn FnOnce() + Send>>,
}

#[derive(Default)]
Expand Down Expand Up @@ -6668,6 +6672,26 @@ impl AppContext {
}
}

#[cfg(test)]
pub(crate) fn after_next_policy_memory_miss_for_test(
&self,
hook: impl FnOnce() + Send + 'static,
) {
self.policy_confinement.lock().after_memory_miss_for_test = Some(Box::new(hook));
}

#[cfg(test)]
pub(crate) fn run_after_policy_memory_miss_hook_for_test(&self) {
let hook = self
.policy_confinement
.lock()
.after_memory_miss_for_test
.take();
if let Some(hook) = hook {
hook();
}
}

pub(crate) fn database_runtime_failed(&self) -> bool {
matches!(
self.database_runtime_state.load(Ordering::Acquire),
Expand Down
20 changes: 13 additions & 7 deletions crates/aft/src/subc/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -433,10 +433,9 @@ struct DecodedFrame {
frame: Frame,
phase_trace: PhaseTrace,
parsed_body: OnceLock<Result<Value, serde_json::Error>>,
/// The route's saved confinement, read off the frame loop by
/// `persistence::DatabaseWaits::read_confinement` when memory could not
/// answer it. `None` until then.
confinement: Option<remote_policy::ConfinementRead>,
/// The route's confinement answer from memory or an off-loop database read
/// by `persistence::DatabaseWaits::read_confinement`. `None` until checked.
confinement: Option<remote_policy::CarriedConfinement>,
}

impl DecodedFrame {
Expand Down Expand Up @@ -8300,7 +8299,7 @@ async fn handle_tool_call(
tx: &WriterSender,
frame: &Frame,
parsed_body: Option<Result<Value, serde_json::Error>>,
confinement: Option<remote_policy::ConfinementRead>,
confinement: Option<remote_policy::CarriedConfinement>,
mut phase_trace: PhaseTrace,
routes: &HashMap<RouteChannel, RouteIdentity>,
pending_binds: &HashMap<RouteChannel, PendingBind>,
Expand Down Expand Up @@ -8448,6 +8447,12 @@ async fn handle_tool_call(
.await
}
};
if let Some(error) = tool_provider::shell_capability_refusal(
&call.name,
tool_provider::admission_trusted(&identity, &call),
) {
return send_provider_error(tx, metrics, frame, error).await;
}
if let Err(error) =
admit_confined_call_name(executor, &identity, &mut call.name, confinement.as_ref())
{
Expand Down Expand Up @@ -9887,7 +9892,8 @@ async fn signal_fatal_teardown(
shutdown.notify_one();
}
/// Check a tool call against the route's saved root confinement, before any
/// admission step that looks a tool up by name, on every route kind.
/// catalog or call-mode admission, on every route kind. Policy-independent
/// capability refusals run first and never need a database lookup.
///
/// A call cannot escape a saved worker fence by claiming another preset or by
/// sending a tool that was absent from its catalog. A confined catalog offers
Expand All @@ -9901,7 +9907,7 @@ fn admit_confined_call_name(
executor: &Executor,
identity: &RouteIdentity,
name: &mut String,
confinement: Option<&remote_policy::ConfinementRead>,
confinement: Option<&remote_policy::CarriedConfinement>,
) -> Result<(), subc_protocol::ErrorBody> {
let ctx = executor.actor_context(&identity.root).ok_or_else(|| {
subc_protocol::ErrorBody::new("confine_policy_unavailable", "session context unavailable")
Expand Down
Loading
Loading