mirror of
https://github.com/instructkr/claude-code.git
synced 2026-05-17 11:26:44 +00:00
omx(team): merge worker-2
This commit is contained in:
@@ -4,6 +4,338 @@ use serde_json::Value;
|
|||||||
|
|
||||||
use crate::config::RuntimePermissionRuleConfig;
|
use crate::config::RuntimePermissionRuleConfig;
|
||||||
|
|
||||||
|
|
||||||
|
/// Machine-readable policy exception scope that an approval token may override.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct ApprovalScope {
|
||||||
|
pub policy: String,
|
||||||
|
pub action: String,
|
||||||
|
pub repository: Option<String>,
|
||||||
|
pub branch: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ApprovalScope {
|
||||||
|
#[must_use]
|
||||||
|
pub fn new(policy: impl Into<String>, action: impl Into<String>) -> Self {
|
||||||
|
Self {
|
||||||
|
policy: policy.into(),
|
||||||
|
action: action.into(),
|
||||||
|
repository: None,
|
||||||
|
branch: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn with_repository(mut self, repository: impl Into<String>) -> Self {
|
||||||
|
self.repository = Some(repository.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn with_branch(mut self, branch: impl Into<String>) -> Self {
|
||||||
|
self.branch = Some(branch.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Actor/session hop recorded when an approval is delegated or consumed.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct ApprovalDelegationHop {
|
||||||
|
pub actor: String,
|
||||||
|
pub session_id: Option<String>,
|
||||||
|
pub reason: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ApprovalDelegationHop {
|
||||||
|
#[must_use]
|
||||||
|
pub fn new(actor: impl Into<String>, reason: impl Into<String>) -> Self {
|
||||||
|
Self {
|
||||||
|
actor: actor.into(),
|
||||||
|
session_id: None,
|
||||||
|
reason: reason.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn with_session_id(mut self, session_id: impl Into<String>) -> Self {
|
||||||
|
self.session_id = Some(session_id.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Current lifecycle state for a policy-exception approval token.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum ApprovalTokenStatus {
|
||||||
|
Pending,
|
||||||
|
Granted,
|
||||||
|
Consumed,
|
||||||
|
Expired,
|
||||||
|
Revoked,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ApprovalTokenStatus {
|
||||||
|
#[must_use]
|
||||||
|
pub fn as_str(self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Self::Pending => "approval_pending",
|
||||||
|
Self::Granted => "approval_granted",
|
||||||
|
Self::Consumed => "approval_consumed",
|
||||||
|
Self::Expired => "approval_expired",
|
||||||
|
Self::Revoked => "approval_revoked",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Typed policy errors returned when a token cannot authorize a blocked action.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub enum ApprovalTokenError {
|
||||||
|
NoApproval,
|
||||||
|
ApprovalPending,
|
||||||
|
ApprovalExpired,
|
||||||
|
ApprovalRevoked,
|
||||||
|
ApprovalAlreadyConsumed,
|
||||||
|
ScopeMismatch { expected: ApprovalScope, actual: ApprovalScope },
|
||||||
|
UnauthorizedDelegate { expected: String, actual: String },
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ApprovalTokenError {
|
||||||
|
#[must_use]
|
||||||
|
pub fn as_str(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Self::NoApproval => "no_approval",
|
||||||
|
Self::ApprovalPending => "approval_pending",
|
||||||
|
Self::ApprovalExpired => "approval_expired",
|
||||||
|
Self::ApprovalRevoked => "approval_revoked",
|
||||||
|
Self::ApprovalAlreadyConsumed => "approval_already_consumed",
|
||||||
|
Self::ScopeMismatch { .. } => "approval_scope_mismatch",
|
||||||
|
Self::UnauthorizedDelegate { .. } => "approval_unauthorized_delegate",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Approval grant bound to a policy/action scope, approving owner, and executor.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct ApprovalTokenGrant {
|
||||||
|
pub token: String,
|
||||||
|
pub scope: ApprovalScope,
|
||||||
|
pub approving_actor: String,
|
||||||
|
pub approved_executor: String,
|
||||||
|
pub status: ApprovalTokenStatus,
|
||||||
|
pub expires_at_epoch_seconds: Option<u64>,
|
||||||
|
pub max_uses: u32,
|
||||||
|
pub uses: u32,
|
||||||
|
delegation_chain: Vec<ApprovalDelegationHop>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ApprovalTokenGrant {
|
||||||
|
#[must_use]
|
||||||
|
pub fn pending(
|
||||||
|
token: impl Into<String>,
|
||||||
|
scope: ApprovalScope,
|
||||||
|
approving_actor: impl Into<String>,
|
||||||
|
approved_executor: impl Into<String>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
token: token.into(),
|
||||||
|
scope,
|
||||||
|
approving_actor: approving_actor.into(),
|
||||||
|
approved_executor: approved_executor.into(),
|
||||||
|
status: ApprovalTokenStatus::Pending,
|
||||||
|
expires_at_epoch_seconds: None,
|
||||||
|
max_uses: 1,
|
||||||
|
uses: 0,
|
||||||
|
delegation_chain: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn granted(
|
||||||
|
token: impl Into<String>,
|
||||||
|
scope: ApprovalScope,
|
||||||
|
approving_actor: impl Into<String>,
|
||||||
|
approved_executor: impl Into<String>,
|
||||||
|
) -> Self {
|
||||||
|
Self::pending(token, scope, approving_actor, approved_executor).approve()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn approve(mut self) -> Self {
|
||||||
|
self.status = ApprovalTokenStatus::Granted;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn expires_at(mut self, epoch_seconds: u64) -> Self {
|
||||||
|
self.expires_at_epoch_seconds = Some(epoch_seconds);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn with_max_uses(mut self, max_uses: u32) -> Self {
|
||||||
|
self.max_uses = max_uses.max(1);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn with_delegation_hop(mut self, hop: ApprovalDelegationHop) -> Self {
|
||||||
|
self.delegation_chain.push(hop);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn delegation_chain(&self) -> &[ApprovalDelegationHop] {
|
||||||
|
&self.delegation_chain
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Auditable result of verifying or consuming an approval token.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct ApprovalTokenAudit {
|
||||||
|
pub token: String,
|
||||||
|
pub scope: ApprovalScope,
|
||||||
|
pub approving_actor: String,
|
||||||
|
pub executing_actor: String,
|
||||||
|
pub status: ApprovalTokenStatus,
|
||||||
|
pub delegated_execution: bool,
|
||||||
|
pub delegation_chain: Vec<ApprovalDelegationHop>,
|
||||||
|
pub uses: u32,
|
||||||
|
pub max_uses: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// In-memory approval-token ledger with one-time-use and replay protection.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Default)]
|
||||||
|
pub struct ApprovalTokenLedger {
|
||||||
|
grants: BTreeMap<String, ApprovalTokenGrant>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ApprovalTokenLedger {
|
||||||
|
#[must_use]
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn insert(&mut self, grant: ApprovalTokenGrant) {
|
||||||
|
self.grants.insert(grant.token.clone(), grant);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn get(&self, token: &str) -> Option<&ApprovalTokenGrant> {
|
||||||
|
self.grants.get(token)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn revoke(&mut self, token: &str) -> Result<ApprovalTokenAudit, ApprovalTokenError> {
|
||||||
|
let grant = self
|
||||||
|
.grants
|
||||||
|
.get_mut(token)
|
||||||
|
.ok_or(ApprovalTokenError::NoApproval)?;
|
||||||
|
grant.status = ApprovalTokenStatus::Revoked;
|
||||||
|
Ok(Self::audit_for(grant, &grant.approved_executor))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn verify(
|
||||||
|
&self,
|
||||||
|
token: &str,
|
||||||
|
scope: &ApprovalScope,
|
||||||
|
executing_actor: &str,
|
||||||
|
now_epoch_seconds: u64,
|
||||||
|
) -> Result<ApprovalTokenAudit, ApprovalTokenError> {
|
||||||
|
let grant = self.grants.get(token).ok_or(ApprovalTokenError::NoApproval)?;
|
||||||
|
Self::validate_grant(grant, scope, executing_actor, now_epoch_seconds)?;
|
||||||
|
Ok(Self::audit_for(grant, executing_actor))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn consume(
|
||||||
|
&mut self,
|
||||||
|
token: &str,
|
||||||
|
scope: &ApprovalScope,
|
||||||
|
executing_actor: &str,
|
||||||
|
now_epoch_seconds: u64,
|
||||||
|
) -> Result<ApprovalTokenAudit, 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;
|
||||||
|
}
|
||||||
|
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
|
||||||
|
.expires_at_epoch_seconds
|
||||||
|
.is_some_and(|expires_at| now_epoch_seconds > expires_at)
|
||||||
|
{
|
||||||
|
return Err(ApprovalTokenError::ApprovalExpired);
|
||||||
|
}
|
||||||
|
|
||||||
|
if grant.uses >= grant.max_uses {
|
||||||
|
return Err(ApprovalTokenError::ApprovalAlreadyConsumed);
|
||||||
|
}
|
||||||
|
|
||||||
|
if grant.scope != *scope {
|
||||||
|
return Err(ApprovalTokenError::ScopeMismatch {
|
||||||
|
expected: grant.scope.clone(),
|
||||||
|
actual: scope.clone(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if grant.approved_executor != executing_actor {
|
||||||
|
return Err(ApprovalTokenError::UnauthorizedDelegate {
|
||||||
|
expected: grant.approved_executor.clone(),
|
||||||
|
actual: executing_actor.to_string(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn audit_for(grant: &ApprovalTokenGrant, executing_actor: &str) -> ApprovalTokenAudit {
|
||||||
|
let mut delegation_chain = grant.delegation_chain.clone();
|
||||||
|
if delegation_chain.is_empty() {
|
||||||
|
delegation_chain.push(ApprovalDelegationHop::new(
|
||||||
|
grant.approving_actor.clone(),
|
||||||
|
"approval granted",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if grant.approving_actor != executing_actor
|
||||||
|
&& !delegation_chain.iter().any(|hop| hop.actor == executing_actor)
|
||||||
|
{
|
||||||
|
delegation_chain.push(ApprovalDelegationHop::new(
|
||||||
|
executing_actor.to_string(),
|
||||||
|
"delegated execution",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
ApprovalTokenAudit {
|
||||||
|
token: grant.token.clone(),
|
||||||
|
scope: grant.scope.clone(),
|
||||||
|
approving_actor: grant.approving_actor.clone(),
|
||||||
|
executing_actor: executing_actor.to_string(),
|
||||||
|
status: grant.status,
|
||||||
|
delegated_execution: grant.approving_actor != executing_actor,
|
||||||
|
delegation_chain,
|
||||||
|
uses: grant.uses,
|
||||||
|
max_uses: grant.max_uses,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Permission level assigned to a tool invocation or runtime session.
|
/// Permission level assigned to a tool invocation or runtime session.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub enum PermissionMode {
|
pub enum PermissionMode {
|
||||||
|
|||||||
Reference in New Issue
Block a user