feat(13-12): G-B3 rate limit + read-only injection loop bound and owner notices
rate_limit.rs: assistant.chat gets its own request log keyed by AUTHENTICATED SESSION (not client IP, per 13-AI-SPEC.md §6 G-B3's own spec — an operator's session can roam across IPs within one sitting), on the SAME EndpointRateLimiter struct rather than a second limiter type. check_session/record_session_request enforce a hard ceiling (60/5min); session_soft_threshold_reached (30/5min) is checked separately so the call site can raise an owner notice before the hard refusal ever fires. Wired into assistant_chat.rs's handle_assistant_chat (Rule 3 — the plan's own declared intent, "assistant.chat is rate-limited per authenticated session," has no other call site to reach the real RPC surface) and into the existing 5-minute cleanup task in api/rpc/mod.rs. loop_.rs: run_loop now tracks whether D-10-wrapped untrusted content is present in context (seeded and re-checked as new tool results arrive mid-loop), counts grant refusals split by that flag via AssistantCounters::note_grant_refusal (a burst WITH untrusted content raises a Security notice — something in shared content may be trying to trigger actions; the same burst WITHOUT it raises a Ux/config notice instead, so probing is never confused with misconfiguration, T-13-83), counts turns-per-request, and counts MAX_TURNS-reached (3+ in one session raises an owner notice) right before the loop's own bail — this is EV-13's read-only injection loop, the one case the confirm gate structurally cannot see because reads never confirm. mod.rs: ToolExecCtx gains a `counters: Arc<AssistantCounters>` field (defaulting to the process-wide global_counters(), overridable per-test via with_confirm_gate_and_counters) so loop_.rs's counting has somewhere to write and tests can assert against an isolated instance without polluting concurrently-running tests. read_only_injection_loop_terminates_and_is_counted (EV-13) and grant_refusals_with_untrusted_content_are_a_security_signal (T-13-83) both pass. Full `cargo test --package archipelago` (1211 tests) green — the existing rate-limited RPC methods are unaffected by the new session-keyed limiter. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
fde7b1572d
commit
f1e50fbfd8
@@ -375,6 +375,13 @@ pub struct ToolExecCtx {
|
||||
/// share one pending queue; tests inject a fresh gate per test via
|
||||
/// [`ToolExecCtx::with_confirm_gate`] for isolation.
|
||||
pub confirm: Arc<confirm::ConfirmGate>,
|
||||
/// 13-12 Task 2/3: grant-refusal/validation-failure/turns-used/
|
||||
/// untrusted-content/MAX_TURNS counters and owner notices. Defaults to
|
||||
/// [`global_counters`] so production call sites share one instance;
|
||||
/// tests inject an isolated one via
|
||||
/// [`ToolExecCtx::with_confirm_gate_and_counters`] to avoid
|
||||
/// cross-test threshold pollution.
|
||||
pub counters: Arc<AssistantCounters>,
|
||||
validation_failures: Mutex<HashMap<String, u32>>,
|
||||
/// 13-08 on-device UAT (T-13-50): actions the human declined this turn,
|
||||
/// keyed by the same canonical `(tool_name, validated_args)` identity
|
||||
@@ -399,12 +406,28 @@ impl ToolExecCtx {
|
||||
caller: CallerScope,
|
||||
handler: Arc<RpcHandler>,
|
||||
confirm: Arc<confirm::ConfirmGate>,
|
||||
) -> Self {
|
||||
Self::with_confirm_gate_and_counters(registry, caller, handler, confirm, global_counters())
|
||||
}
|
||||
|
||||
/// Like [`ToolExecCtx::with_confirm_gate`], but also overrides the
|
||||
/// counters instance — tests use this to get an isolated
|
||||
/// `AssistantCounters` so threshold assertions (grant-refusal bursts,
|
||||
/// MAX_TURNS-reached) can't be polluted by other tests running
|
||||
/// concurrently against the process-wide singleton.
|
||||
pub fn with_confirm_gate_and_counters(
|
||||
registry: tools::ToolRegistry,
|
||||
caller: CallerScope,
|
||||
handler: Arc<RpcHandler>,
|
||||
confirm: Arc<confirm::ConfirmGate>,
|
||||
counters: Arc<AssistantCounters>,
|
||||
) -> Self {
|
||||
Self {
|
||||
registry,
|
||||
caller,
|
||||
handler,
|
||||
confirm,
|
||||
counters,
|
||||
validation_failures: Mutex::new(HashMap::new()),
|
||||
declined_actions: Mutex::new(HashSet::new()),
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user