Skip to content

Commit 5c2230a

Browse files
committed
fix(hosted-web): secure lifecycle socket authorization
1 parent 85ab089 commit 5c2230a

13 files changed

Lines changed: 2605 additions & 0 deletions

File tree

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
import type { CompositeRuntimePlan, LaneId } from './runtimePlan';
2+
3+
/** Private, machine-to-machine protocol. It is not an operator command surface. */
4+
export const AGENT_RUNTIME_LIFECYCLE_ACL_PROTOCOL_VERSION = 1 as const;
5+
export const AGENT_RUNTIME_LIFECYCLE_ACL_MAX_FRAME_BYTES = 1024 * 1024;
6+
7+
export const AGENT_RUNTIME_LIFECYCLE_EFFECTS = Object.freeze([
8+
'preflight',
9+
'launch',
10+
'observe',
11+
'stop',
12+
'recover',
13+
] as const);
14+
export type AgentRuntimeLifecycleEffect = (typeof AGENT_RUNTIME_LIFECYCLE_EFFECTS)[number];
15+
16+
/**
17+
* A short-lived authority issued to the one external lifecycle orchestrator for
18+
* the current application boot. The token is presented only over the private
19+
* machine channel and is never exposed through browser transports.
20+
*/
21+
export interface AgentRuntimeLifecycleCallerLease {
22+
readonly kind: 'agent-runtime-lifecycle-caller-lease/v1';
23+
readonly bootId: string;
24+
readonly leaseId: string;
25+
readonly authority: 'external_lifecycle_orchestrator';
26+
readonly callerId: string;
27+
readonly token: string;
28+
readonly issuedAtIso: string;
29+
readonly expiresAtIso: string;
30+
}
31+
32+
/** Exact external-effect claim. Mutating backends durably enforce this binding. */
33+
export interface AgentRuntimeLifecycleEffectLease {
34+
readonly token: string;
35+
readonly fence: number;
36+
readonly ownerId: string;
37+
readonly claimedAtIso: string;
38+
readonly expiresAtIso: string;
39+
}
40+
41+
export interface AgentRuntimeLifecycleReadinessReceipt {
42+
readonly backend: 'provisioning_cli' | 'opencode';
43+
readonly bindingId: string;
44+
readonly laneId: LaneId;
45+
readonly planHash: `sha256:${string}`;
46+
readonly bindingRevision: number;
47+
readonly providerRevisions: readonly {
48+
readonly providerId: 'anthropic' | 'codex' | 'gemini' | 'opencode';
49+
readonly capabilityRevision: number;
50+
}[];
51+
}
52+
53+
interface AgentRuntimeLifecycleRequestBase {
54+
readonly protocolVersion: typeof AGENT_RUNTIME_LIFECYCLE_ACL_PROTOCOL_VERSION;
55+
readonly requestId: string;
56+
readonly callerLease: AgentRuntimeLifecycleCallerLease;
57+
readonly operationId: string;
58+
readonly effectLease: AgentRuntimeLifecycleEffectLease;
59+
/** The orchestrator supplies the already accepted immutable plan. */
60+
readonly plan: CompositeRuntimePlan;
61+
readonly laneId: LaneId;
62+
}
63+
64+
export interface AgentRuntimeLifecyclePreflightRequest extends AgentRuntimeLifecycleRequestBase {
65+
readonly effect: 'preflight';
66+
}
67+
68+
export interface AgentRuntimeLifecycleLaunchRequest extends AgentRuntimeLifecycleRequestBase {
69+
readonly effect: 'launch';
70+
readonly readiness: AgentRuntimeLifecycleReadinessReceipt;
71+
}
72+
73+
export interface AgentRuntimeLifecycleObserveRequest extends AgentRuntimeLifecycleRequestBase {
74+
readonly effect: 'observe';
75+
readonly executionRef: string;
76+
}
77+
78+
export interface AgentRuntimeLifecycleStopRequest extends AgentRuntimeLifecycleRequestBase {
79+
readonly effect: 'stop';
80+
readonly executionRef: string;
81+
readonly mode: 'graceful' | 'immediate';
82+
}
83+
84+
export interface AgentRuntimeLifecycleRecoverRequest extends AgentRuntimeLifecycleRequestBase {
85+
readonly effect: 'recover';
86+
}
87+
88+
export type AgentRuntimeLifecycleRequest =
89+
| AgentRuntimeLifecyclePreflightRequest
90+
| AgentRuntimeLifecycleLaunchRequest
91+
| AgentRuntimeLifecycleObserveRequest
92+
| AgentRuntimeLifecycleStopRequest
93+
| AgentRuntimeLifecycleRecoverRequest;
94+
95+
export type AgentRuntimeLifecycleRejectionReason =
96+
| 'invalid_request'
97+
| 'unauthenticated'
98+
| 'caller_lease_expired'
99+
| 'caller_lease_boot_mismatch'
100+
| 'effect_lease_binding_mismatch'
101+
| 'invalid_plan'
102+
| 'lane_not_found'
103+
| 'backend_not_registered'
104+
| 'provider_not_owned'
105+
| 'backend_rejected'
106+
| 'unavailable';
107+
108+
export type AgentRuntimeLifecycleEffectOutcome =
109+
| { readonly status: 'ready'; readonly readiness: AgentRuntimeLifecycleReadinessReceipt }
110+
| { readonly status: 'ready' }
111+
| { readonly status: 'launched' | 'already_launched'; readonly executionRef: string }
112+
| { readonly status: 'starting' | 'degraded' | 'stopping' }
113+
| { readonly status: 'exited'; readonly outcome: 'success' | 'failure' | 'unknown' }
114+
| { readonly status: 'stopped' | 'already_stopped' | 'cancelled' }
115+
| { readonly status: 'not_started' }
116+
| { readonly status: 'recovered'; readonly executionRef: string }
117+
| { readonly status: 'operator_required' }
118+
| {
119+
readonly status: 'rejected';
120+
readonly reason:
121+
| 'cancelled'
122+
| 'invalid_plan'
123+
| 'unsupported'
124+
| 'unavailable'
125+
| 'capability_mismatch'
126+
| 'readiness_mismatch'
127+
| 'stale_plan'
128+
| 'not_owned';
129+
};
130+
131+
export type AgentRuntimeLifecycleResponse =
132+
| {
133+
readonly protocolVersion: typeof AGENT_RUNTIME_LIFECYCLE_ACL_PROTOCOL_VERSION;
134+
readonly requestId: string;
135+
readonly effect: AgentRuntimeLifecycleEffect;
136+
readonly status: 'completed';
137+
readonly outcome: AgentRuntimeLifecycleEffectOutcome;
138+
}
139+
| {
140+
readonly protocolVersion: typeof AGENT_RUNTIME_LIFECYCLE_ACL_PROTOCOL_VERSION;
141+
readonly requestId: string;
142+
readonly effect: AgentRuntimeLifecycleEffect | null;
143+
readonly status: 'rejected';
144+
readonly reason: AgentRuntimeLifecycleRejectionReason;
145+
};

0 commit comments

Comments
 (0)