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 Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 10 additions & 1 deletion crates/domain/src/approval.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,9 @@ impl ApprovalTargetTool {
pub enum ApprovalState {
Pending,
Granted,
/// Resume has been claimed. Execution may be in flight or interrupted.
/// Resume claimed; waiting for a resource. No side effect is possible yet.
Queued,
/// Resource granted. Mutation dispatch may be in flight or interrupted.
Resuming,
Denied,
Consumed,
Expand All @@ -65,6 +67,7 @@ impl ApprovalState {
match self {
Self::Pending => "pending",
Self::Granted => "granted",
Self::Queued => "queued",
Self::Resuming => "resuming",
Self::Denied => "denied",
Self::Consumed => "consumed",
Expand All @@ -75,6 +78,7 @@ impl ApprovalState {
match value {
"pending" => Ok(Self::Pending),
"granted" => Ok(Self::Granted),
"queued" => Ok(Self::Queued),
"resuming" => Ok(Self::Resuming),
"denied" => Ok(Self::Denied),
"consumed" => Ok(Self::Consumed),
Expand Down Expand Up @@ -143,6 +147,11 @@ mod tests {
serde_json::to_value(ApprovalsMode::Confirm).unwrap(),
"confirm"
);
assert_eq!(ApprovalState::Queued.as_str(), "queued");
assert_eq!(
ApprovalState::parse("queued").unwrap(),
ApprovalState::Queued
);
assert_eq!(ApprovalState::Resuming.as_str(), "resuming");
assert_eq!(
ApprovalState::parse("resuming").unwrap(),
Expand Down
9 changes: 9 additions & 0 deletions crates/domain/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ use serde::{Deserialize, Serialize};
pub enum ErrorCode {
Unauthorized,
WorkspaceNotFound,
/// Live process owns the workspace mutation lease.
WorkspaceBusy,
/// Per-resource waiter queue is at `MAX_WAITERS_PER_RESOURCE`.
ResourceQueueFull,
InvalidPatch,
InvalidCommand,
ProcessSpawnFailed,
Expand Down Expand Up @@ -39,6 +42,8 @@ pub enum ErrorCode {
ApprovalNotFound,
ApprovalConflict,
ApprovalAmbiguous,
/// Scheduler or store invariant failed. Not occupancy; do not retry as busy.
Internal,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
Expand Down Expand Up @@ -134,6 +139,10 @@ mod tests {
assert_eq!(json, "\"APPROVAL_CONFLICT\"");
let json = serde_json::to_string(&ErrorCode::ApprovalAmbiguous).unwrap();
assert_eq!(json, "\"APPROVAL_AMBIGUOUS\"");
let json = serde_json::to_string(&ErrorCode::ResourceQueueFull).unwrap();
assert_eq!(json, "\"RESOURCE_QUEUE_FULL\"");
let json = serde_json::to_string(&ErrorCode::Internal).unwrap();
assert_eq!(json, "\"INTERNAL\"");
}

#[test]
Expand Down
61 changes: 61 additions & 0 deletions crates/domain/src/execution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ use crate::files::{DEFAULT_FIND_LIMIT, DEFAULT_READ_LIMIT};
pub const PTY_INITIAL_ROWS: u16 = 24;
pub const PTY_INITIAL_COLS: u16 = 80;

/// In-memory waiter cap per resource. Excess acquires return `RESOURCE_QUEUE_FULL`.
pub const MAX_WAITERS_PER_RESOURCE: u32 = 64;

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "kebab-case")]
pub enum ClientEnvironmentKind {
Expand Down Expand Up @@ -147,14 +150,39 @@ pub struct ProcessExecutionInfo {
pub capabilities: Option<ProcessCapabilityInfo>,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "kebab-case")]
pub enum SerializationScope {
Workspace,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "kebab-case")]
pub enum RequestConflictPolicy {
WaitFifo,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "kebab-case")]
pub enum ProcessConflictPolicy {
Reject,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct WorkspaceSerializationInfo {
pub scope: SerializationScope,
pub request_conflict: RequestConflictPolicy,
pub process_conflict: ProcessConflictPolicy,
pub queue_durable: bool,
pub max_waiters_per_resource: u32,
pub live_process_holds_mutation_lease: bool,
pub parallel_exec: bool,
pub read_while_process_live: bool,
pub find_while_process_live: bool,
pub patch_while_process_live: bool,
/// Live process owns the workspace. Not queue saturation.
pub conflict_error: ErrorCode,
pub queue_full_error: ErrorCode,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
Expand Down Expand Up @@ -263,12 +291,18 @@ impl WorkspaceExecutionInfo {
files,
process,
serialization: WorkspaceSerializationInfo {
scope: SerializationScope::Workspace,
request_conflict: RequestConflictPolicy::WaitFifo,
process_conflict: ProcessConflictPolicy::Reject,
queue_durable: false,
max_waiters_per_resource: MAX_WAITERS_PER_RESOURCE,
live_process_holds_mutation_lease: true,
parallel_exec: false,
read_while_process_live: true,
find_while_process_live: true,
patch_while_process_live: false,
conflict_error: ErrorCode::WorkspaceBusy,
queue_full_error: ErrorCode::ResourceQueueFull,
},
isolation: IsolationInfo {
file_tools_workspace_scoped: true,
Expand Down Expand Up @@ -399,9 +433,36 @@ mod tests {
assert_eq!(exec.network.policy, NetworkPolicyState::Restricted);
assert_eq!(exec.network.enforcement, NetworkEnforcementState::None);
assert!(!exec.network.client_may_escalate);
assert_eq!(exec.serialization.scope, SerializationScope::Workspace);
assert_eq!(
exec.serialization.request_conflict,
RequestConflictPolicy::WaitFifo
);
assert_eq!(
exec.serialization.process_conflict,
ProcessConflictPolicy::Reject
);
assert!(!exec.serialization.queue_durable);
assert_eq!(
exec.serialization.max_waiters_per_resource,
MAX_WAITERS_PER_RESOURCE
);
assert_eq!(exec.serialization.conflict_error, ErrorCode::WorkspaceBusy);
assert_eq!(
exec.serialization.queue_full_error,
ErrorCode::ResourceQueueFull
);
let json = serde_json::to_value(&exec).unwrap();
assert_eq!(json["serialization"]["scope"], "workspace");
assert_eq!(json["serialization"]["request_conflict"], "wait-fifo");
assert_eq!(json["serialization"]["process_conflict"], "reject");
assert_eq!(json["serialization"]["queue_durable"], false);
assert_eq!(json["serialization"]["max_waiters_per_resource"], 64);
assert_eq!(json["serialization"]["conflict_error"], "WORKSPACE_BUSY");
assert_eq!(
json["serialization"]["queue_full_error"],
"RESOURCE_QUEUE_FULL"
);
assert!(json.get("environment_id").is_none());
assert_eq!(json["environment"]["kind"], "host");
assert_eq!(json["environment"]["file_read_supported"], true);
Expand Down
7 changes: 4 additions & 3 deletions crates/domain/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,10 @@ pub use execution::{
ClientEnvironmentKind, CommandSandboxState, EffectivePermissionInfo, EnvironmentExecutionInfo,
FileCapabilityInfo, FileExecutionInfo, FileOperationInfo, IsolationInfo,
NetworkEnforcementState, NetworkInfo, NetworkPolicyState, ProcessCapabilityInfo,
ProcessDisconnectAction, ProcessExecutionInfo, ProcessLifetimeInfo, ProcessLifetimeOwner,
ProcessRestartRecovery, PtyCapabilityInfo, WorkspaceExecutionInfo, WorkspaceSerializationInfo,
PTY_INITIAL_COLS, PTY_INITIAL_ROWS,
ProcessConflictPolicy, ProcessDisconnectAction, ProcessExecutionInfo, ProcessLifetimeInfo,
ProcessLifetimeOwner, ProcessRestartRecovery, PtyCapabilityInfo, RequestConflictPolicy,
SerializationScope, WorkspaceExecutionInfo, WorkspaceSerializationInfo,
MAX_WAITERS_PER_RESOURCE, PTY_INITIAL_COLS, PTY_INITIAL_ROWS,
};
pub use files::{
FindParams, FindResult, ReadParams, ReadResult, DEFAULT_FIND_LIMIT, DEFAULT_READ_LIMIT,
Expand Down
Loading
Loading