refactor(runtime): add approval tokens and session compaction pipeline#3231
refactor(runtime): add approval tokens and session compaction pipeline#3231Gaurav-x111 wants to merge 1 commit into
Conversation
- Implement an approval token system to handle controlled execution permissions via the policy engine, including support for delegated approvals. - Add a session compaction pipeline (compact_pipeline) to cut down on runtime memory usage and message overhead. - Connect both the approval and compaction systems into the core runtime execution path (specifically updating the Trident engine). - Update the CLI and test contracts to match these changes. Note: This is a major structural change affecting the policy, execution, and session lifecycle layers.
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
This PR modernizes Rust code patterns (Option handling, derives, small refactors), introduces a new “compact_pipeline” module in runtime, and significantly rewrites the approval-token system.
Changes:
- Replace
map_orpatterns withis_some_and, simplify control flow, and apply clippy/allow attributes in a few places. - Add
runtime::compact_pipelineand re-export its types/functions fromruntime::lib. - Rewrite
runtime::approval_tokenswith a new ledger/audit model and token lifecycle APIs.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 10 comments.
Show a summary per file
| File | Description |
|---|---|
| rust/crates/rusty-claude-cli/tests/output_format_contract.rs | Refactors Option assertions; adjusts path comparison in agent listing test. |
| rust/crates/rusty-claude-cli/src/main.rs | Minor refactors (string creation, loops, help metadata alias) and clippy allows. |
| rust/crates/runtime/src/trident.rs | Small refactors/optimizations and derive-based Default for stats. |
| rust/crates/runtime/src/policy_engine.rs | Adjust clippy allow list in a long test. |
| rust/crates/runtime/src/lib.rs | Exposes new compact_pipeline module and re-exports its public API. |
| rust/crates/runtime/src/config.rs | Adds #[allow(dead_code)] to settings helpers. |
| rust/crates/runtime/src/compact_pipeline.rs | New compaction pipeline module + unit tests. |
| rust/crates/runtime/src/approval_tokens.rs | Major redesign of approval tokens, ledger, and auditing. |
| rust/crates/api/src/lib.rs | Adds crate-level clippy allow for large error results. |
| rust/.gitignore | Ignores entire .claw/ directory. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| pub fn verify(&self, token: &str) -> Result<&ApprovalTokenGrant, ApprovalTokenError> { | ||
| let grant = self | ||
| .grants | ||
| .get_mut(token) | ||
| .ok_or(ApprovalTokenError::NoApproval)?; | ||
| Self::validate_grant(grant, scope, executing_actor, now_epoch_seconds)?; | ||
| grant.uses += 1; | ||
| if grant.uses >= grant.max_uses { | ||
| grant.status = ApprovalTokenStatus::Consumed; | ||
| .tokens | ||
| .get(token) | ||
| .ok_or(ApprovalTokenError::TokenNotFound)?; | ||
| if grant.status == ApprovalTokenStatus::Expired { | ||
| return Err(ApprovalTokenError::TokenExpired); | ||
| } | ||
| Ok(Self::audit_for(grant, executing_actor)) | ||
| } | ||
|
|
||
| fn validate_grant( | ||
| grant: &ApprovalTokenGrant, | ||
| scope: &ApprovalScope, | ||
| executing_actor: &str, | ||
| now_epoch_seconds: u64, | ||
| ) -> Result<(), ApprovalTokenError> { | ||
| match grant.status { | ||
| ApprovalTokenStatus::Pending => return Err(ApprovalTokenError::ApprovalPending), | ||
| ApprovalTokenStatus::Consumed => { | ||
| return Err(ApprovalTokenError::ApprovalAlreadyConsumed) | ||
| } | ||
| ApprovalTokenStatus::Expired => return Err(ApprovalTokenError::ApprovalExpired), | ||
| ApprovalTokenStatus::Revoked => return Err(ApprovalTokenError::ApprovalRevoked), | ||
| ApprovalTokenStatus::Granted => {} | ||
| if grant.status == ApprovalTokenStatus::Exhausted { | ||
| return Err(ApprovalTokenError::TokenExhausted); | ||
| } | ||
|
|
||
| if grant | ||
| .expires_at_epoch_seconds | ||
| .is_some_and(|expires_at| now_epoch_seconds > expires_at) | ||
| { | ||
| return Err(ApprovalTokenError::ApprovalExpired); | ||
| if grant.status == ApprovalTokenStatus::Revoked { | ||
| return Err(ApprovalTokenError::TokenRevoked); | ||
| } | ||
|
|
||
| if grant.uses >= grant.max_uses { | ||
| return Err(ApprovalTokenError::ApprovalAlreadyConsumed); | ||
| } | ||
|
|
||
| if grant.scope != *scope { | ||
| return Err(ApprovalTokenError::ScopeMismatch { | ||
| expected: Box::new(grant.scope.clone()), | ||
| actual: Box::new(scope.clone()), | ||
| }); | ||
| if grant.status != ApprovalTokenStatus::Granted { | ||
| return Err(ApprovalTokenError::InvalidToken); | ||
| } | ||
| Ok(grant) | ||
| } |
| pub fn consume(&mut self, token: &str) -> Result<(), ApprovalTokenError> { | ||
| let grant = self | ||
| .tokens | ||
| .get_mut(token) | ||
| .ok_or(ApprovalTokenError::TokenNotFound)?; | ||
| if let Err(_e) = grant.consume_use() { | ||
| return Err(ApprovalTokenError::InvalidToken); | ||
| } | ||
|
|
||
| self.audit_log.push(ApprovalTokenAudit { | ||
| token: token.to_string(), | ||
| action: TokenAuditAction::Consumed, | ||
| actor: "system".to_string(), | ||
| timestamp: current_timestamp(), | ||
| details: HashMap::new(), | ||
| }); | ||
| Ok(()) | ||
| } |
| fn generate_token() -> String { | ||
| let ts = SystemTime::now() | ||
| .duration_since(UNIX_EPOCH) | ||
| .unwrap() | ||
| .as_nanos(); | ||
| format!("approval_{:x}", ts) | ||
| } |
| pub fn revoke( | ||
| &mut self, | ||
| token: &str, | ||
| scope: &ApprovalScope, | ||
| executing_actor: &str, | ||
| now_epoch_seconds: u64, | ||
| actor: &str, | ||
| ) -> Result<ApprovalTokenAudit, ApprovalTokenError> { |
| while j < messages.len() && chain.len() < threshold { | ||
| let next = &messages[j]; | ||
| if next.summary.len() < 20 && next.tool_calls.is_empty() { | ||
| chain.push(next.clone()); | ||
| j += 1; | ||
| } else { | ||
| break; | ||
| } | ||
| } | ||
|
|
||
| if chain.len() >= 2 { | ||
| collapsed += chain.len(); | ||
| result.push(CompactMessage { | ||
| id: chain.first().unwrap().id, | ||
| role: "assistant".to_string(), | ||
| summary: format!("[{} chatty messages collapsed]", chain.len()), | ||
| tool_calls: vec![], | ||
| file_ops: vec![], | ||
| }); | ||
| i = j; | ||
| continue; | ||
| } |
| for &idx in &indices[1..] { | ||
| result[idx].summary = "[MERGED]".to_string(); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // Remove merged messages | ||
| result.retain(|msg| msg.summary != "[MERGED]"); |
| @@ -1,3 +1,5 @@ | |||
| #![allow(unused_variables)] | |||
| && fs::canonicalize(&agent_path) | ||
| .map(|p| p.to_string_lossy().to_string()) | ||
| .expect("canonical listed agent path") | ||
| == agent["path"].as_str().expect("listed agent path") |
| @@ -1,3 +1,5 @@ | |||
| #![allow(clippy::result_large_err)] | |||
| .claw/settings.local.json | ||
| .claw/sessions/ | ||
| .clawhip/ | ||
| .claw/ |
|
Maintainer NO-GO: closing this PR because it is not safe to merge in its current form. Specific blockers still present in the current diff:
Please do not re-open this exact PR. A replacement should restore the security invariants first, include focused tests for the approval-token failure modes above, and prove any compaction change preserves enough state for continuation before being exported as runtime API. |
Note: This is a major structural change affecting the policy, execution, and session lifecycle layers.
Summary
Anti-slop triage
Verification
git diff --checkpasses.Resolution gate