Skip to content

feat(aios-protocol): tier-specific shell execution policy (BRO-216)#3

Merged
broomva merged 4 commits intomainfrom
feat/bro-216-shell-gating
Mar 25, 2026
Merged

feat(aios-protocol): tier-specific shell execution policy (BRO-216)#3
broomva merged 4 commits intomainfrom
feat/bro-216-shell-gating

Conversation

@broomva
Copy link
Owner

@broomva broomva commented Mar 25, 2026

Summary

  • anonymous tier: Remove exec:cmd:* from gate_capabilities so the StaticPolicyEngine immediately denies bash/shell tool calls rather than queuing them for human approval (anonymous sessions have no approver)
  • free tier: Replace the exec:cmd:* wildcard gate with 11 specific whitelisted exec commands in allow_capabilities (cat, ls, echo, grep, jq, python3, find, head, tail, sort, wc); unlisted commands fall through to denied
  • pro/enterprise: Unchanged — wildcard "*" in allow_capabilities already covers all capabilities

Enforcement model

Capability evaluation in StaticPolicyEngine:

  1. If token is in allow_capabilitiesallowed
  2. If token is in gate_capabilitiesrequires_approval (human queue)
  3. Otherwise → denied (immediate block, no ticket)

Previously, exec:cmd:* in gate_capabilities for anonymous/free tiers caused the engine to queue bash calls for human approval — an approval that could never arrive. This PR removes the gate so the engine denies immediately.

Test plan

  • policy_set_anonymous: gate has 3 caps (exec removed), exec:cmd:* NOT in gate
  • policy_set_free: allow has 13 caps (session-read + net-egress + 11 exec), gate has 2 (fs:write + secrets), exec:cmd:* NOT in gate
  • All 90 existing tests still pass: cargo test -p aios-protocol

Closes BRO-216

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added queue lifecycle event tracking with new event types for queued, steered, and drained states.
    • Introduced steering mode options for queue management (Collect, Steer, Followup, Interrupt).
    • Added preset policy configurations for different service tiers (anonymous, free, pro, enterprise) with preconfigured capability sets and rate limits.

broomva and others added 3 commits March 24, 2026 19:02
…ous/free/pro/enterprise)

Adds anonymous(), free(), pro(), and enterprise() constructors to PolicySet
for BRO-211. Each tier sets appropriate allow/gate capability lists and
per-turn event/runtime limits. Includes unit tests for all four tiers.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- anonymous(): remove exec:cmd:* from gate_capabilities so StaticPolicyEngine
  immediately denies bash/shell tool calls without creating an approval ticket
- free(): add 11 whitelisted exec commands to allow_capabilities (cat, ls, echo,
  grep, jq, python3, find, head, tail, sort, wc); remove exec:cmd:* wildcard from
  gate so unlisted commands are denied outright
- pro()/enterprise(): unchanged — wildcard '*' in allow_capabilities covers all exec
- Update tests: anonymous gate has 3 caps (exec removed), free allow has 13 caps

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@linear
Copy link

linear bot commented Mar 25, 2026

@coderabbitai
Copy link

coderabbitai bot commented Mar 25, 2026

📝 Walkthrough

Walkthrough

Three new EventKind variants (Queued, Steered, QueueDrained) with a supporting SteeringMode enum were added to the event protocol layer. Four preset constructors (anonymous(), free(), pro(), enterprise()) were added to PolicySet for configurable capability and rate-limit presets. The new SteeringMode enum was re-exported at the crate root.

Changes

Cohort / File(s) Summary
Queue Lifecycle & Steering Events
crates/aios-protocol/src/event.rs, crates/aios-protocol/src/lib.rs
Added three new EventKind variants (Queued, Steered, QueueDrained) with supporting SteeringMode enum (Collect, Steer, Followup, Interrupt); updated internal EventKindKnown enum and deserialization conversion path; re-exported SteeringMode at crate root.
Policy Presets
crates/aios-protocol/src/policy.rs
Added four public PolicySet constructors (anonymous(), free(), pro(), enterprise()) with preset capability allowlists, gatelist configurations, and rate/runtime limits; includes unit tests validating capability set contents and limit correctness.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

Poem

🐰 Events queued in order, steering modes align,
Policies preset for each tier design,
Queued, Steered, and Drained now play,
The protocol hops along a brighter way!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(aios-protocol): tier-specific shell execution policy (BRO-216)' accurately reflects the main changes—adding tier-specific PolicySet constructors that enforce different shell execution policies (removing exec:cmd:* from anonymous, whitelisting 11 commands for free tier).
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/bro-216-shell-gating
⚔️ Resolve merge conflicts
  • Resolve merge conflict in branch feat/bro-216-shell-gating

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@crates/aios-protocol/src/event.rs`:
- Around line 590-604: The new enum variants Queued, Steered, QueueDrained and
the SteeringMode type were added but lack serde roundtrip tests; add unit tests
that serialize each variant (Queued { queue_id, mode, message }, Steered {
queue_id, preempted_at }, QueueDrained { queue_id, processed }) and deserialize
back asserting equality and that the serialized JSON contains the expected
variant tag and fields (and add tests for SteeringMode serialization); put these
tests alongside existing event serde tests (or in the crate tests module) and
ensure assertions cover both structure and content so future serde tag/field
regressions are caught.
- Around line 590-604: The event_kind_name function is missing explicit match
arms for the new enum variants Queued, Steered, and QueueDrained, causing them
to fall through to the generic "custom" label; update the match in
event_kind_name (crates/aios-runtime/src/lib.rs) to add arms matching
Event::Queued { .. } => "queued", Event::Steered { .. } => "steered", and
Event::QueueDrained { .. } => "queue_drained" (use wildcard field patterns to
ignore inner fields) so tracing emits the correct event names for these
variants.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e36cbbbf-ebf3-4d8c-87b3-dffb2e839217

📥 Commits

Reviewing files that changed from the base of the PR and between 25a2613 and e980ee4.

📒 Files selected for processing (3)
  • crates/aios-protocol/src/event.rs
  • crates/aios-protocol/src/lib.rs
  • crates/aios-protocol/src/policy.rs

Comment on lines +590 to +604
// ── Queue & steering (Phase 2.5) ──
Queued {
queue_id: String,
mode: SteeringMode,
message: String,
},
Steered {
queue_id: String,
/// Tool boundary where preemption occurred (e.g. "tool:read_file:call-3").
preempted_at: String,
},
QueueDrained {
queue_id: String,
processed: usize,
},
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add coverage for new queue/steering event behavior.

Queued, Steered, QueueDrained, and SteeringMode were introduced, but there are no dedicated roundtrip/serialization assertions for them. Please add tests so regressions in serde tags/fields are caught.

Proposed test additions
 #[cfg(test)]
 mod tests {
     use super::*;
@@
     fn voice_events_roundtrip() {
         let kind = EventKind::VoiceSessionStarted {
@@
         let back: EventKind = serde_json::from_str(&json).unwrap();
         assert!(matches!(back, EventKind::VoiceSessionStarted { .. }));
     }
+
+    #[test]
+    fn queue_events_roundtrip() {
+        let queued = EventKind::Queued {
+            queue_id: "q1".into(),
+            mode: SteeringMode::Followup,
+            message: "next task".into(),
+        };
+        let steered = EventKind::Steered {
+            queue_id: "q1".into(),
+            preempted_at: "tool:read_file:call-3".into(),
+        };
+        let drained = EventKind::QueueDrained {
+            queue_id: "q1".into(),
+            processed: 2,
+        };
+
+        for kind in [queued, steered, drained] {
+            let json = serde_json::to_string(&kind).unwrap();
+            let back: EventKind = serde_json::from_str(&json).unwrap();
+            match (kind, back) {
+                (EventKind::Queued { .. }, EventKind::Queued { .. }) => {}
+                (EventKind::Steered { .. }, EventKind::Steered { .. }) => {}
+                (EventKind::QueueDrained { .. }, EventKind::QueueDrained { .. }) => {}
+                _ => panic!("roundtrip mismatch"),
+            }
+        }
+    }
+
+    #[test]
+    fn steering_mode_serializes_snake_case() {
+        let kind = EventKind::Queued {
+            queue_id: "q2".into(),
+            mode: SteeringMode::Interrupt,
+            message: "urgent".into(),
+        };
+        let json = serde_json::to_string(&kind).unwrap();
+        assert!(json.contains("\"mode\":\"interrupt\""));
+    }
 }

As per coding guidelines: "Add or extend tests with every behavioral change; do not silently widen capabilities or safety boundaries".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/aios-protocol/src/event.rs` around lines 590 - 604, The new enum
variants Queued, Steered, QueueDrained and the SteeringMode type were added but
lack serde roundtrip tests; add unit tests that serialize each variant (Queued {
queue_id, mode, message }, Steered { queue_id, preempted_at }, QueueDrained {
queue_id, processed }) and deserialize back asserting equality and that the
serialized JSON contains the expected variant tag and fields (and add tests for
SteeringMode serialization); put these tests alongside existing event serde
tests (or in the crate tests module) and ensure assertions cover both structure
and content so future serde tag/field regressions are caught.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Verify whether runtime event kind naming has explicit arms for new variants.
runtime_file=$(fd '^lib\.rs$' crates/aios-runtime/src | head -n1)

rg -n -C4 'fn event_kind_name|EventKind::Queued|EventKind::Steered|EventKind::QueueDrained|_ => "custom"' "$runtime_file"

Repository: broomva/aiOS

Length of output: 781


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Get the complete event_kind_name function
runtime_file=$(fd '^lib\.rs$' crates/aios-runtime/src | head -n1)

# Extract lines from function start to the closing brace
sed -n '1640,1666p' "$runtime_file"

Repository: broomva/aiOS

Length of output: 1606


Add explicit match arms for Queued, Steered, and QueueDrained in event_kind_name.

The three new first-class event variants are not handled in the event_kind_name function (crates/aios-runtime/src/lib.rs:1640), so they fall through to _ => "custom". Add explicit arms to emit proper event names ("queued", "steered", "queue_drained") instead of generic labels, enabling accurate runtime observability via tracing.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/aios-protocol/src/event.rs` around lines 590 - 604, The
event_kind_name function is missing explicit match arms for the new enum
variants Queued, Steered, and QueueDrained, causing them to fall through to the
generic "custom" label; update the match in event_kind_name
(crates/aios-runtime/src/lib.rs) to add arms matching Event::Queued { .. } =>
"queued", Event::Steered { .. } => "steered", and Event::QueueDrained { .. } =>
"queue_drained" (use wildcard field patterns to ignore inner fields) so tracing
emits the correct event names for these variants.

…anges

Main had exec:cmd:* in gate_capabilities for anonymous/free tiers (old behavior).
BRO-216 removes exec:cmd:* from gate so StaticPolicyEngine denies shell calls
immediately without creating an approval ticket. Keeping BRO-216 semantics.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@broomva broomva merged commit 220635f into main Mar 25, 2026
3 checks passed
@broomva broomva deleted the feat/bro-216-shell-gating branch March 25, 2026 02:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant