@fitlab-ai/agent-infra 0.11.2 → 0.11.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/dist/bin/internal-cli.js +7 -2
  2. package/dist/lib/agent-clients/adapter.js +3 -0
  3. package/dist/lib/agent-clients/adapters/codex-lifecycle/recovery.js +234 -0
  4. package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +8 -35
  5. package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +1 -34
  6. package/dist/lib/agent-clients/adapters/codex.js +10 -0
  7. package/dist/lib/internal/manual-validation.js +22 -27
  8. package/dist/lib/internal/sandbox-control.js +1 -43
  9. package/dist/lib/internal/task-lifecycle.js +1 -1
  10. package/dist/lib/internal/task-operation-registry.js +6 -2
  11. package/dist/lib/platform/issue-comments.js +2 -31
  12. package/dist/lib/platform/pr-summary.js +7 -2
  13. package/dist/lib/sandbox/control/client.js +3 -44
  14. package/dist/lib/sandbox/control/executor.js +14 -79
  15. package/dist/lib/sandbox/control/finalization-response.js +3 -5
  16. package/dist/lib/sandbox/control/protocol.js +5 -64
  17. package/dist/lib/sandbox/control/server.js +29 -206
  18. package/dist/lib/sandbox/control/state.js +1 -7
  19. package/dist/lib/sandbox/control/task-view.js +1 -2
  20. package/dist/lib/task/control-authority.js +34 -36
  21. package/dist/lib/task/control-recovery.js +3 -42
  22. package/dist/lib/task/delegation-receipts.js +2 -2
  23. package/dist/lib/task/finalization-artifacts.js +1 -3
  24. package/dist/lib/task/finalization-state.js +1 -1
  25. package/dist/lib/task/finalization.js +53 -126
  26. package/dist/lib/task/lifecycle.js +1 -1
  27. package/dist/lib/task/manual-validation-receipt.js +8 -3
  28. package/dist/lib/task/orchestration.js +12 -6
  29. package/dist/lifecycle-build-manifest.json +3 -3
  30. package/lib/agent-clients/adapter.ts +46 -0
  31. package/lib/agent-clients/adapters/codex-lifecycle/recovery.ts +297 -0
  32. package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +13 -33
  33. package/lib/agent-clients/adapters/codex-lifecycle/store.ts +1 -44
  34. package/lib/agent-clients/adapters/codex.ts +10 -0
  35. package/lib/internal/manual-validation.ts +22 -26
  36. package/lib/internal/sandbox-control.ts +0 -42
  37. package/lib/internal/task-lifecycle.ts +2 -2
  38. package/lib/internal/task-operation-registry.ts +7 -4
  39. package/lib/platform/issue-comments.ts +1 -35
  40. package/lib/platform/pr-summary.ts +10 -2
  41. package/lib/sandbox/control/client.ts +5 -68
  42. package/lib/sandbox/control/executor.ts +14 -81
  43. package/lib/sandbox/control/finalization-response.ts +3 -5
  44. package/lib/sandbox/control/protocol.ts +7 -83
  45. package/lib/sandbox/control/server.ts +30 -220
  46. package/lib/sandbox/control/state.ts +1 -9
  47. package/lib/sandbox/control/task-view.ts +1 -2
  48. package/lib/task/control-authority.ts +38 -47
  49. package/lib/task/control-recovery.ts +3 -43
  50. package/lib/task/delegation-receipts.ts +2 -2
  51. package/lib/task/finalization-artifacts.ts +1 -3
  52. package/lib/task/finalization-state.ts +1 -1
  53. package/lib/task/finalization.ts +57 -121
  54. package/lib/task/lifecycle.ts +2 -2
  55. package/lib/task/manual-validation-receipt.ts +8 -2
  56. package/lib/task/orchestration.ts +30 -19
  57. package/package.json +1 -1
  58. package/templates/.agents/rules/lifecycle-orchestration.en.md +19 -5
  59. package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +19 -5
  60. package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +2 -2
  61. package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +2 -2
  62. package/templates/.agents/skills/complete-task/SKILL.en.md +6 -7
  63. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +6 -7
  64. package/templates/.agents/skills/run-manual-validation/SKILL.en.md +2 -2
  65. package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +2 -2
  66. package/templates/.agents/skills/run-task/SKILL.en.md +9 -8
  67. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +9 -8
  68. package/dist/lib/sandbox/control/task-workflow.js +0 -74
  69. package/dist/lib/sandbox/control/workflow-executor.js +0 -142
  70. package/dist/lib/task/lifecycle-recovery.js +0 -549
  71. package/dist/lib/task/recovery-warning.js +0 -19
  72. package/dist/lib/task/workflow-command.js +0 -21
  73. package/dist/lib/task/workflow-dispatch.js +0 -213
  74. package/lib/sandbox/control/task-workflow.ts +0 -95
  75. package/lib/sandbox/control/workflow-executor.ts +0 -156
  76. package/lib/task/lifecycle-recovery.ts +0 -691
  77. package/lib/task/recovery-warning.ts +0 -21
  78. package/lib/task/workflow-command.ts +0 -22
  79. package/lib/task/workflow-dispatch.ts +0 -214
@@ -5,12 +5,6 @@ import type { ProcessIdentity } from '../../server/process-state.ts';
5
5
  import type { CodexControllerLeaseProofV1 } from './controller-registration.ts';
6
6
  import type { SandboxAuthorityEvidenceV1 } from '../engines/authority.ts';
7
7
  import type { SandboxTaskView } from './task-view.ts';
8
- import { validateTaskWorkflowRequest, type TaskWorkflowRequest } from './task-workflow.ts';
9
- import {
10
- validateLifecycleAuthorityRequest,
11
- type LifecycleAuthorityRequestV1,
12
- type LifecycleRecoveryAttestationV1
13
- } from '../../task/control-authority.ts';
14
8
 
15
9
  export const SANDBOX_CONTROL_MAX_BYTES = 64 * 1024;
16
10
  export const SANDBOX_CONTROL_MAX_LOGICAL_RECORDS = 1024;
@@ -21,7 +15,7 @@ export const SANDBOX_CONTROL_ADMISSION_WINDOW_MS = 2_000;
21
15
  export const SANDBOX_CONTROL_STATUS_INTERVAL_MS = 250;
22
16
  export const SANDBOX_CONTROL_STATUS_STALE_MS = 1_500;
23
17
  export const SANDBOX_CONTROL_FUTURE_SKEW_MS = 1_000;
24
- export const SANDBOX_CONTROL_FAMILIES = ['task-lifecycle', 'task-orchestration', 'task-finalization', 'task-create', 'codex-controller', 'task-workflow'] as const;
18
+ export const SANDBOX_CONTROL_FAMILIES = ['task-lifecycle', 'task-finalization', 'task-create', 'codex-controller'] as const;
25
19
  export type SandboxControlTimingPolicy = Readonly<{
26
20
  controlTickMs: number;
27
21
  parkedBindingInitialMs: number;
@@ -61,9 +55,6 @@ export type SandboxControlExecutorGateV2 = Readonly<{
61
55
  nonce: string;
62
56
  owner: SandboxControlBrokerOwner;
63
57
  requestId: string;
64
- operationId: string;
65
- phase: 'orchestration.prepare' | 'artifact.preflight' | 'artifact.finalize-local' | 'task-event.completed';
66
- authority: LifecycleRecoveryAttestationV1 | null;
67
58
  }>;
68
59
  type RequestBase = Readonly<{
69
60
  version: 3; id: string; token: string; generation: string; issuedAt: number; expiresAt: number;
@@ -71,8 +62,7 @@ type RequestBase = Readonly<{
71
62
  controllerProof: CodexControllerLeaseProofV1 | null;
72
63
  }>;
73
64
  export type SandboxTaskCommandRequest = RequestBase & Readonly<{
74
- family: 'task-lifecycle' | 'task-orchestration'; args: string[];
75
- authority?: LifecycleAuthorityRequestV1;
65
+ family: 'task-lifecycle'; args: string[];
76
66
  }>;
77
67
  export type SandboxTaskFinalizationRequest = RequestBase & Readonly<{
78
68
  family: 'task-finalization'; operation: 'complete'; agent: string; args: [];
@@ -85,13 +75,7 @@ export type SandboxCodexControllerRequest = RequestBase & Readonly<{
85
75
  command: 'open' | 'close' | 'verify';
86
76
  args: [];
87
77
  }>;
88
- export type SandboxTaskWorkflowRequest = RequestBase & Readonly<{
89
- family: 'task-workflow';
90
- args: [];
91
- workflow: TaskWorkflowRequest;
92
- authority?: LifecycleAuthorityRequestV1;
93
- }>;
94
- export type SandboxControlRequest = SandboxTaskCommandRequest | SandboxTaskFinalizationRequest | SandboxTaskCreateRequest | SandboxCodexControllerRequest | SandboxTaskWorkflowRequest;
78
+ export type SandboxControlRequest = SandboxTaskCommandRequest | SandboxTaskFinalizationRequest | SandboxTaskCreateRequest | SandboxCodexControllerRequest;
95
79
  export type SandboxControlError = Readonly<{ code: string; message: string; retryable: boolean }>;
96
80
  export type SandboxControlResultEvidence = Readonly<{
97
81
  version: 1;
@@ -131,11 +115,6 @@ export type SandboxControlResponse = Readonly<{
131
115
  outputState?: 'available' | 'unavailable';
132
116
  payload?: SandboxControlPayloadReference | null;
133
117
  }>;
134
- export type SandboxControlRecoveryWarning = Readonly<{
135
- code: string;
136
- message: string;
137
- action: string;
138
- }>;
139
118
  export type SandboxControlStatus = Readonly<{
140
119
  version: 3; generation: string; broker: ProcessIdentity & { brokerId: string };
141
120
  state: 'starting' | 'healthy' | 'busy' | 'parked'; reasonCode: string | null;
@@ -313,25 +292,8 @@ export function validateSandboxControlRequest(
313
292
  }
314
293
  return request as SandboxTaskFinalizationRequest;
315
294
  }
316
- if (request.family === 'task-workflow') {
317
- const expected = ['args', 'authority', 'controllerProcess', 'controllerProof', 'expiresAt', 'family', 'generation', 'id', 'issuedAt', 'token', 'version', 'workflow'];
318
- const baseExpected = expected.filter((key) => key !== 'authority');
319
- if (![baseExpected, expected].some((keys) => Object.keys(request).sort().join(',') === keys.sort().join(','))
320
- || !Array.isArray(request.args) || request.args.length !== 0
321
- || request.controllerProcess !== null || request.controllerProof !== null) {
322
- fail('SANDBOX_CONTROL_REQUEST_INVALID', 'task-workflow request schema is invalid');
323
- }
324
- if (manifest.mode !== 'task-bound' || !manifest.taskId) fail('SANDBOX_CONTROL_BRANCH_ONLY', 'branch-only sandboxes cannot use task-workflow');
325
- const workflow = validateTaskWorkflowRequest(request.workflow);
326
- if (workflow.taskId !== manifest.taskId || workflow.generation !== manifest.generation || workflow.id !== request.id) {
327
- fail('SANDBOX_CONTROL_REQUEST_INVALID', 'task-workflow binding does not match the sandbox manifest');
328
- }
329
- if (request.authority !== undefined) validateAuthoritySelector(request.authority, request, manifest);
330
- return { ...request, workflow } as SandboxTaskWorkflowRequest;
331
- }
332
- const expected = ['args', 'authority', 'controllerProcess', 'controllerProof', 'expiresAt', 'family', 'generation', 'id', 'issuedAt', 'token', 'version'];
333
- const baseExpected = expected.filter((key) => key !== 'authority');
334
- if (![baseExpected, expected].some((keys) => Object.keys(request).sort().join(',') === keys.sort().join(','))
295
+ const expected = ['args', 'controllerProcess', 'controllerProof', 'expiresAt', 'family', 'generation', 'id', 'issuedAt', 'token', 'version'];
296
+ if (Object.keys(request).sort().join(',') !== expected.sort().join(',')
335
297
  || !Array.isArray(request.args) || !request.args.every((arg) => typeof arg === 'string')) {
336
298
  fail('SANDBOX_CONTROL_REQUEST_INVALID', 'request schema or authorization is invalid');
337
299
  }
@@ -341,52 +303,14 @@ export function validateSandboxControlRequest(
341
303
  "branch-only sandboxes cannot coordinate tasks; return to the host and run 'ai sandbox start --recreate <task-ref-or-correct-branch>'"
342
304
  );
343
305
  }
344
- if (request.family === 'task-orchestration'
345
- && request.args.some((arg) => arg === '--git-worktree-root' || arg.startsWith('--git-worktree-root='))) {
346
- fail('SANDBOX_CONTROL_REQUEST_INVALID', 'worktree binding is reserved for the control broker');
347
- }
348
- if (request.controllerProcess !== null
349
- || (request.controllerProof !== null && !validControllerProof(request.controllerProof))) {
306
+ if (request.controllerProcess !== null || request.controllerProof !== null) {
350
307
  fail('SANDBOX_CONTROL_REQUEST_INVALID', 'controller authority is invalid');
351
308
  }
352
- if (request.family !== 'task-orchestration' && request.controllerProof !== null) {
353
- fail('SANDBOX_CONTROL_REQUEST_INVALID', 'controller proof is not allowed for this family');
354
- }
355
- if (request.authority !== undefined) validateAuthoritySelector(request.authority, request, manifest);
356
309
  return request as SandboxTaskCommandRequest;
357
310
  }
358
311
 
359
- function validateAuthoritySelector(
360
- value: unknown,
361
- request: Record<string, unknown>,
362
- manifest: SandboxControlManifest
363
- ): asserts value is LifecycleAuthorityRequestV1 {
364
- let authority: LifecycleAuthorityRequestV1;
365
- try { authority = validateLifecycleAuthorityRequest(value); }
366
- catch { fail('SANDBOX_CONTROL_AUTHORITY_INVALID', 'lifecycle authority selector is invalid'); }
367
- if (authority.requestId !== request.id
368
- || authority.taskId !== manifest.taskId
369
- || authority.expectedControlGeneration !== manifest.generation) {
370
- fail('SANDBOX_CONTROL_AUTHORITY_INVALID', 'lifecycle authority selector is not bound to this request');
371
- }
372
- const args = Array.isArray(request.args) ? request.args : [];
373
- if (request.family === 'task-orchestration'
374
- && (args[1] !== 'prepare' || authority.phase !== 'orchestration.prepare')) {
375
- fail('SANDBOX_CONTROL_AUTHORITY_INVALID', 'orchestration authority is only valid for prepare');
376
- }
377
- const workflow = request.workflow as TaskWorkflowRequest | undefined;
378
- if (request.family === 'task-workflow') {
379
- const expectedPhase = workflow?.operation === 'artifact-finalize-local'
380
- ? 'artifact.finalize-local'
381
- : workflow?.operation === 'event' ? 'task-event.completed' : null;
382
- if (authority.phase !== expectedPhase) {
383
- fail('SANDBOX_CONTROL_AUTHORITY_INVALID', 'workflow authority phase does not match the workflow operation');
384
- }
385
- }
386
- }
387
-
388
312
  export function bindSandboxControlTask(request: SandboxControlRequest, taskId: string): string[] {
389
- if (request.family === 'task-create' || request.family === 'codex-controller' || request.family === 'task-finalization' || request.family === 'task-workflow') {
313
+ if (request.family === 'task-create' || request.family === 'codex-controller' || request.family === 'task-finalization') {
390
314
  fail('SANDBOX_CONTROL_REQUEST_INVALID', `${request.family} requests do not bind a current task`);
391
315
  }
392
316
  if (request.args.length === 0) fail('SANDBOX_CONTROL_REQUEST_INVALID', 'command arguments are required');
@@ -1,5 +1,4 @@
1
1
  import { finalizationTerminalResponse } from './finalization-response.ts';
2
- import { isDeepStrictEqual } from 'node:util';
3
2
  import { completedReentryView } from './completed-reentry.ts';
4
3
  import fs from 'node:fs';
5
4
  import path from 'node:path';
@@ -12,7 +11,6 @@ import {
12
11
  SANDBOX_CONTROL_MAX_RESPONSE_BYTES,
13
12
  SANDBOX_CONTROL_MAX_TERMINAL_RECORD_BYTES,
14
13
  type SandboxControlManifest,
15
- type SandboxControlRecoveryWarning,
16
14
  type SandboxControlRequest,
17
15
  type SandboxControlResponse,
18
16
  type SandboxControlTimingPolicy
@@ -62,10 +60,6 @@ import {
62
60
  operationRecoveryBinding,
63
61
  SANDBOX_CONTROL_REQUIRED_COMPLETION_PHASES
64
62
  } from '../../task/control-recovery.ts';
65
- import { readRun } from '../../task/orchestration.ts';
66
- import { readLifecycleRecoveryDomainEvidence } from '../../task/lifecycle-recovery.ts';
67
- import { captureRepositorySnapshot } from '../../task/workspace-snapshot.ts';
68
- import { parseTypedTaskFrontmatter } from '../../task/frontmatter.ts';
69
63
  import { readLifecycleJournalEvidence } from '../../task/lifecycle.ts';
70
64
  import { resolveTaskRef } from '../../task/resolve-ref.ts';
71
65
  import { loadShortIdByTaskId } from '../../task/short-id.ts';
@@ -88,70 +82,16 @@ import {
88
82
  type SandboxTaskView
89
83
  } from './task-view.ts';
90
84
  import { taskCreateOutputUnavailableResult } from '../../task/create-service.ts';
91
- import {
92
- consumeLifecycleRecoveryAttestation,
93
- issueLifecycleRecoveryAttestation,
94
- recoverLifecycleRecoveryOperation,
95
- type LifecycleAuthorityResponseV1
96
- } from '../../task/control-authority.ts';
97
- import { createCodexCapabilityStore } from '../../agent-clients/adapters/codex-lifecycle/capability-store.ts';
98
- import { computeLifecycleBuildIdentity } from '../../agent-clients/adapters/codex-lifecycle/build-identity.ts';
99
85
 
100
86
  type ActiveExecution = {
101
87
  request: SandboxControlRequest;
102
88
  prepared: PreparedSandboxControlExecution;
103
- lifecycleAuthority: LifecycleAuthorityResponseV1 | undefined;
104
89
  result: SandboxControlExecutionResult | null;
105
90
  resultEvidenceWritten: boolean;
106
91
  failure: unknown;
107
92
  settled: boolean;
108
93
  };
109
94
 
110
- function consumeLifecycleAuthorityForResult(
111
- manifest: SandboxControlManifest,
112
- execution: ActiveExecution
113
- ): boolean {
114
- const authority = execution.lifecycleAuthority?.attestation;
115
- if (!authority || execution.result?.exitCode !== 0) return true;
116
- try {
117
- consumeLifecycleRecoveryAttestation(authority);
118
- return true;
119
- } catch (error) {
120
- appendBrokerAudit(manifest, 'lifecycle-authority-consume-retry', {
121
- ...requestAuditFields(manifest, execution.request),
122
- errorCode: error instanceof Error ? error.name : 'LIFECYCLE_AUTHORITY_CONSUME_FAILED'
123
- });
124
- return false;
125
- }
126
- }
127
-
128
- function lifecycleAuthorityForRequest(
129
- manifest: SandboxControlManifest,
130
- manifestPath: string,
131
- request: SandboxControlRequest
132
- ): LifecycleAuthorityResponseV1 | undefined {
133
- const selector = request.family === 'task-workflow' || request.family === 'task-orchestration'
134
- ? request.authority
135
- : undefined;
136
- if (!selector) return undefined;
137
- let controllerBinding: { instanceDigest: string; controlGeneration: string } | null = null;
138
- try {
139
- const registration = readCodexControllerRegistration(manifestPath);
140
- controllerBinding = {
141
- instanceDigest: registration.controllerInstanceDigest,
142
- controlGeneration: registration.controlGeneration
143
- };
144
- } catch {
145
- // The issuer reports the stable unavailable result; the broker never trusts
146
- // a controller binding supplied by the request.
147
- }
148
- return issueLifecycleRecoveryAttestation(selector, {
149
- controllerBinding,
150
- capabilityStore: createCodexCapabilityStore(),
151
- buildIdentity: computeLifecycleBuildIdentity(manifest.repoRoot)
152
- });
153
- }
154
-
155
95
  function appendBrokerAudit(
156
96
  manifest: SandboxControlManifest,
157
97
  event: string,
@@ -160,38 +100,21 @@ function appendBrokerAudit(
160
100
  appendDiagnosticAudit(manifest, event, { source: 'broker', ...fields });
161
101
  }
162
102
 
163
- function operationKey(request: SandboxControlRequest, output?: string): string | null {
103
+ function operationKey(request: SandboxControlRequest): string | null {
164
104
  if (request.family === 'task-finalization') return request.operation;
165
105
  if (request.family === 'task-create') return 'create';
166
106
  if (request.family === 'codex-controller') return request.command;
167
- if (request.family === 'task-workflow') return request.workflow.operation;
168
- if (request.family !== 'task-lifecycle' && request.family !== 'task-orchestration') return null;
169
- if (request.family === 'task-orchestration' && request.args[1] === 'route') {
170
- if (!output) return 'route';
171
- const result = parseControlOutput(output);
172
- return result?.changed === true && result.status === 'completed' ? 'route.clean-completion' : 'route.read';
173
- }
107
+ if (request.family !== 'task-lifecycle') return null;
174
108
  try {
175
109
  const operation = parseTaskControlOperation(request.family, request.args);
176
- return operation.family === 'task-orchestration' ? operation.intent : operation.request.intent;
110
+ return operation.family === 'task-lifecycle' ? operation.request.intent : null;
177
111
  } catch {
178
112
  return null;
179
113
  }
180
114
  }
181
115
 
182
- function recoveryOperationKey(
183
- request: SandboxControlRequest,
184
- terminalResult: ReturnType<typeof readSandboxControlTerminalResult> | null,
185
- output: string | undefined
186
- ): string | null {
187
- const operation = operationKey(request, output);
188
- if (operation !== 'route') return operation;
189
- const digest = terminalResult?.intentDigest;
190
- for (const candidate of ['route.read', 'route.clean-completion'] as const) {
191
- const expected = operationRecoveryBinding(request.id, request.generation, null, request.family, candidate).intentDigest;
192
- if (digest === expected) return candidate;
193
- }
194
- return null;
116
+ function recoveryOperationKey(request: SandboxControlRequest): string | null {
117
+ return operationKey(request);
195
118
  }
196
119
 
197
120
  function terminalResultMatchesRequest(
@@ -199,9 +122,7 @@ function terminalResultMatchesRequest(
199
122
  terminalResult: ReturnType<typeof readSandboxControlTerminalResult>
200
123
  ): boolean {
201
124
  const operation = operationKey(request);
202
- const candidates = operation === 'route'
203
- ? ['route.read', 'route.clean-completion']
204
- : operation ? [operation] : [];
125
+ const candidates = operation ? [operation] : [];
205
126
  return candidates.some((candidate) => createHash('sha256').update(`${request.family}\0${candidate}`, 'utf8').digest('hex') === terminalResult.intentDigest);
206
127
  }
207
128
 
@@ -374,8 +295,7 @@ export function genericRecoveryResponse(
374
295
  request: SandboxControlRequest,
375
296
  exitCode: number,
376
297
  payload: ReturnType<typeof readSandboxControlPayload> | null,
377
- cause: 'publish' | 'recovery' = 'recovery',
378
- warning: SandboxControlRecoveryWarning | null = null
298
+ cause: 'publish' | 'recovery' = 'recovery'
379
299
  ): SandboxControlResponse {
380
300
  const outputUnavailable = request.family === 'task-create' && !payload;
381
301
  return {
@@ -384,9 +304,7 @@ export function genericRecoveryResponse(
384
304
  phase: 'completed',
385
305
  exitCode,
386
306
  stdout: outputUnavailable ? `${JSON.stringify(taskCreateOutputUnavailableResult(request.id))}\n` : '',
387
- stderr: payload ? '' : warning
388
- ? `${warning.code}: ${warning.message}\nAction: ${warning.action}\n`
389
- : cause === 'publish'
307
+ stderr: payload ? '' : cause === 'publish'
390
308
  ? 'SANDBOX_CONTROL_OUTPUT_UNAVAILABLE: output payload was not retained\n'
391
309
  : 'SANDBOX_CONTROL_OUTPUT_UNAVAILABLE: broker restarted after executor completion\n',
392
310
  error: null,
@@ -465,42 +383,16 @@ function controllerDomainEvidence(
465
383
  }
466
384
  }
467
385
 
468
- function orchestrationDomainEvidence(
469
- operation: ReturnType<typeof findSandboxControlRecoveryOperation>,
470
- terminalResult: ReturnType<typeof readSandboxControlTerminalResult>,
471
- output: Record<string, unknown> | null,
472
- run: ReturnType<typeof readRun> | null
473
- ): Readonly<Record<string, unknown>> {
474
- if (!operation || !output) return { consistent: false };
475
- const outputRun = output.run && typeof output.run === 'object' && !Array.isArray(output.run)
476
- ? output.run as Record<string, unknown> : null;
477
- const runMatches = outputRun === null ? run === null : run !== null && JSON.stringify(run) === JSON.stringify(outputRun);
478
- if (operation.class === 'read-only') {
479
- return {
480
- consistent: output.status === terminalResult.status && output.changed === terminalResult.changed && runMatches,
481
- snapshotValid: true,
482
- status: run?.status ?? terminalResult.status,
483
- pendingDelegation: run?.pendingDelegation ?? null
484
- };
485
- }
486
- if (!run || !outputRun) return { consistent: false };
487
- return {
488
- consistent: output.status === terminalResult.status && output.changed === terminalResult.changed && runMatches,
489
- status: run.status,
490
- pendingDelegation: run.pendingDelegation
491
- };
492
- }
493
-
494
- function readRecoveryDomain(
386
+ async function readRecoveryDomain(
495
387
  manifest: SandboxControlManifest,
496
388
  manifestPath: string,
497
389
  request: SandboxControlRequest,
498
390
  operation: ReturnType<typeof findSandboxControlRecoveryOperation>,
499
391
  terminalResult: ReturnType<typeof readSandboxControlTerminalResult>,
500
392
  payloadOutput: string | null
501
- ): RecoveryDomainEvidence {
393
+ ): Promise<RecoveryDomainEvidence> {
502
394
  if (!operation) return { domain: null };
503
- const taskRef = request.family === 'task-finalization' || request.family === 'task-workflow'
395
+ const taskRef = request.family === 'task-finalization'
504
396
  ? manifest.taskId
505
397
  : 'args' in request ? request.args[0] ?? null : manifest.taskId;
506
398
  const output = parseControlOutput(payloadOutput);
@@ -510,19 +402,7 @@ function readRecoveryDomain(
510
402
 
511
403
  if (operation.family === 'task-lifecycle') {
512
404
  if (operation.intent === 'recover-started') {
513
- try {
514
- if (!('args' in request)) return { domain: null };
515
- const parsed = parseTaskControlOperation('task-lifecycle', request.args);
516
- if (parsed.family !== 'task-lifecycle' || parsed.request.intent !== 'recover-started') {
517
- return { domain: null };
518
- }
519
- return {
520
- domain: readLifecycleRecoveryDomainEvidence(manifest.repoRoot, parsed.request, terminalResult),
521
- journal: readLifecycleJournalEvidence(manifest.repoRoot, taskRef!)
522
- };
523
- } catch {
524
- return { domain: null };
525
- }
405
+ return { domain: null };
526
406
  }
527
407
  if (!taskRef || !terminalResult.targetState) return { domain: null };
528
408
  const journal = readLifecycleJournalEvidence(manifest.repoRoot, taskRef);
@@ -541,48 +421,12 @@ function readRecoveryDomain(
541
421
  }
542
422
  }
543
423
 
544
- if (operation.family === 'task-orchestration') {
545
- if (!taskRef) return { domain: null };
546
- try {
547
- const resolved = resolveTaskRef(taskRef, { repoRoot: manifest.repoRoot });
548
- if (!resolved.ok) return { domain: { consistent: false } };
549
- const run = readRun(resolved.taskDir);
550
- if (!run && operation.class !== 'read-only') return { domain: { consistent: false } };
551
- if (operation.class === 'route.clean-completion') {
552
- const snapshot = captureRepositorySnapshot(manifest.repoRoot);
553
- const metadata = parseTypedTaskFrontmatter(fs.readFileSync(resolved.taskMdPath, 'utf8'));
554
- const completion = run?.completionEvidence ?? null;
555
- const consistent = run !== null && completion !== null
556
- && terminalResult.completionEvidence !== null
557
- && isDeepStrictEqual(completion, terminalResult.completionEvidence);
558
- return {
559
- domain: {
560
- consistent,
561
- status: run?.status ?? null,
562
- pendingDelegation: run?.pendingDelegation ?? null,
563
- completionEvidence: completion,
564
- snapshot,
565
- lastReviewedCommit: metadata.last_reviewed_commit
566
- }
567
- };
568
- }
569
- return { domain: orchestrationDomainEvidence(operation, terminalResult, output, run) };
570
- } catch {
571
- return { domain: { consistent: false } };
572
- }
573
- }
574
-
575
424
  if (operation.family === 'task-create') {
576
425
  return { domain: taskCreateDomainEvidence(manifest, output) };
577
426
  }
578
427
  if (operation.family === 'codex-controller') {
579
428
  return { domain: controllerDomainEvidence(manifest, manifestPath, request, output) };
580
429
  }
581
- if (operation.family === 'task-workflow') {
582
- return {
583
- domain: { consistent: output?.status === terminalResult.status && output.changed === terminalResult.changed, snapshotValid: true }
584
- };
585
- }
586
430
  return { domain: null };
587
431
  }
588
432
 
@@ -597,15 +441,15 @@ function readCommittedCriticalPhases(manifest: SandboxControlManifest, requestId
597
441
  });
598
442
  }
599
443
 
600
- export function recoveryResponse(
444
+ export async function recoveryResponse(
601
445
  manifest: SandboxControlManifest,
602
446
  manifestPath: string,
603
447
  request: SandboxControlRequest,
604
448
  evidence: ReturnType<typeof readSandboxControlResultEvidence>,
605
449
  payload: ReturnType<typeof readSandboxControlPayload> | null,
606
450
  terminalResult: ReturnType<typeof readSandboxControlTerminalResult>
607
- ): SandboxControlResponse | null {
608
- const operationName = recoveryOperationKey(request, terminalResult, payload?.stdout);
451
+ ): Promise<SandboxControlResponse | null> {
452
+ const operationName = recoveryOperationKey(request);
609
453
  const operation = operationName ? findSandboxControlRecoveryOperation(request.family, operationName) : null;
610
454
  if (!operation) return null;
611
455
  if (payload) {
@@ -616,30 +460,9 @@ export function recoveryResponse(
616
460
  }
617
461
  const finalization = request.family === 'task-finalization' ? finalizationRecoveryResponse(manifest, request.id, 0) : null;
618
462
  if (evidence.exitCode === 0 && finalization?.status === 'deferred') return null;
619
- if (evidence.exitCode === 0 && request.family === 'task-workflow' && request.authority?.phase === 'task-event.completed') {
620
- try {
621
- const registration = readCodexControllerRegistration(manifestPath);
622
- const recovered = recoverLifecycleRecoveryOperation(request.authority, {
623
- repoRoot: manifest.repoRoot,
624
- capabilityStore: createCodexCapabilityStore(),
625
- buildIdentity: computeLifecycleBuildIdentity(manifest.repoRoot),
626
- controllerBinding: {
627
- instanceDigest: registration.controllerInstanceDigest,
628
- controlGeneration: registration.controlGeneration
629
- }
630
- });
631
- if (recovered.status !== 'committed') return null;
632
- } catch (error) {
633
- appendBrokerAudit(manifest, 'lifecycle-authority-recovery-failed', {
634
- ...requestAuditFields(manifest, request),
635
- errorCode: error instanceof Error ? error.name : 'LIFECYCLE_AUTHORITY_RECOVERY_FAILED'
636
- });
637
- return null;
638
- }
639
- }
640
463
  const recovery: RecoveryDomainEvidence = finalization
641
464
  ? { domain: { consistent: finalization.status === 'matched' } }
642
- : readRecoveryDomain(manifest, manifestPath, request, operation, terminalResult, payload?.stdout ?? null);
465
+ : await readRecoveryDomain(manifest, manifestPath, request, operation, terminalResult, payload?.stdout ?? null);
643
466
  const binding = operationRecoveryBinding(request.id, manifest.generation, manifest.taskId, request.family, operationName!);
644
467
  const decision = classifySandboxControlRecovery({
645
468
  operation,
@@ -657,10 +480,10 @@ export function recoveryResponse(
657
480
  if (finalization?.status !== 'matched') return null;
658
481
  return finalization.response ?? null;
659
482
  }
660
- return genericRecoveryResponse(request, evidence.exitCode, payload, 'recovery', terminalResult.warning ?? null);
483
+ return genericRecoveryResponse(request, evidence.exitCode, payload);
661
484
  }
662
485
 
663
- function terminalMatchesEvidence(
486
+ async function terminalMatchesEvidence(
664
487
  manifest: SandboxControlManifest,
665
488
  manifestPath: string,
666
489
  request: SandboxControlRequest,
@@ -669,10 +492,10 @@ function terminalMatchesEvidence(
669
492
  payload: ReturnType<typeof readSandboxControlPayload> | null,
670
493
  payloadInvalid: boolean,
671
494
  terminalResult: ReturnType<typeof readSandboxControlTerminalResult> | null
672
- ): { valid: boolean; payloadReferenced: boolean } {
495
+ ): Promise<{ valid: boolean; payloadReferenced: boolean }> {
673
496
  if (request.family === 'task-finalization' && evidence.exitCode === 0) {
674
497
  if (!terminalResult) return { valid: false, payloadReferenced: false };
675
- const expected = recoveryResponse(manifest, manifestPath, request, evidence, payload, terminalResult);
498
+ const expected = await recoveryResponse(manifest, manifestPath, request, evidence, payload, terminalResult);
676
499
  return {
677
500
  valid: expected !== null && JSON.stringify(response) === JSON.stringify(expected),
678
501
  payloadReferenced: false
@@ -701,7 +524,7 @@ function terminalMatchesEvidence(
701
524
  if (response.outputState === 'unavailable') {
702
525
  const causes = ['recovery', 'publish'] as const;
703
526
  const valid = causes.some((cause) => JSON.stringify(response)
704
- === JSON.stringify(genericRecoveryResponse(request, evidence.exitCode, null, cause, terminalResult?.warning ?? null)));
527
+ === JSON.stringify(genericRecoveryResponse(request, evidence.exitCode, null, cause)));
705
528
  return { valid, payloadReferenced: false };
706
529
  }
707
530
  return {
@@ -723,7 +546,7 @@ function publishExecutionResult(
723
546
  const terminalResult = writeSandboxControlTerminalResult(manifest, {
724
547
  id: request.id,
725
548
  family: request.family,
726
- operation: operationKey(request, normalized.stdout)
549
+ operation: operationKey(request)
727
550
  }, normalized.stdout);
728
551
  if (fs.existsSync(path.join(manifest.processingDir, request.id, 'transitions'))) {
729
552
  criticalRequestPhase(manifest, request, 'completed', normalized.exitCode === 0 ? 'success' : 'failure');
@@ -757,7 +580,7 @@ function publishExecutionResult(
757
580
  } catch {
758
581
  payload = null;
759
582
  }
760
- terminal = genericRecoveryResponse(request, normalized.exitCode, payload, 'publish', terminalResult.warning ?? null);
583
+ terminal = genericRecoveryResponse(request, normalized.exitCode, payload, 'publish');
761
584
  }
762
585
  }
763
586
  if (!brokerOwns()) return false;
@@ -848,7 +671,7 @@ function assertCurrentSandboxControlIdentity(manifest: SandboxControlManifest, m
848
671
  }
849
672
  }
850
673
 
851
- function recoverProcessing(manifest: SandboxControlManifest, manifestPath: string, broker: BrokerOwner, brokerOwns: () => boolean): boolean {
674
+ async function recoverProcessing(manifest: SandboxControlManifest, manifestPath: string, broker: BrokerOwner, brokerOwns: () => boolean): Promise<boolean> {
852
675
  for (const entry of fs.readdirSync(manifest.processingDir, { withFileTypes: true })) {
853
676
  if (!entry.isDirectory() || !/^[a-f0-9-]{16,64}$/.test(entry.name)) continue;
854
677
  if (!brokerOwns()) return false;
@@ -955,7 +778,7 @@ function recoverProcessing(manifest: SandboxControlManifest, manifestPath: strin
955
778
  if (terminal && (!request || !resultEvidence)) continue;
956
779
  if (terminal && request && resultEvidence) {
957
780
  if (!terminalResult && request.family !== 'task-create') continue;
958
- const reconciliation = terminalMatchesEvidence(
781
+ const reconciliation = await terminalMatchesEvidence(
959
782
  manifest,
960
783
  manifestPath,
961
784
  request,
@@ -989,7 +812,7 @@ function recoverProcessing(manifest: SandboxControlManifest, manifestPath: strin
989
812
  if (!terminal) {
990
813
  if (!brokerOwns()) return false;
991
814
  if (!resultEvidence || !request || payloadInvalid) continue;
992
- const recovered = recoveryResponse(manifest, manifestPath, request, resultEvidence, payload, terminalResult!);
815
+ const recovered = await recoveryResponse(manifest, manifestPath, request, resultEvidence, payload, terminalResult!);
993
816
  if (!recovered) continue;
994
817
  if (request.family === 'task-finalization' && resultEvidence.exitCode === 0) {
995
818
  const view = publishFinalizationTaskView(manifest, broker, request.id, 'starting', null, null);
@@ -1140,7 +963,7 @@ export async function serveSandboxControl(
1140
963
  pid: broker.pid,
1141
964
  brokerId: broker.brokerId
1142
965
  });
1143
- if (!recoverProcessing(manifest, manifestPath, broker, brokerOwns)) return;
966
+ if (!await recoverProcessing(manifest, manifestPath, broker, brokerOwns)) return;
1144
967
  try {
1145
968
  const published = readSandboxControlStatus(manifest.publicStatusDir);
1146
969
  if (published.generation === manifest.generation) taskView = published.taskView;
@@ -1263,10 +1086,6 @@ export async function serveSandboxControl(
1263
1086
  if (!brokerOwns()) break;
1264
1087
  let terminalCommitted = false;
1265
1088
  if (settledExecution.result && settledExecution.resultEvidenceWritten) {
1266
- if (!consumeLifecycleAuthorityForResult(manifest, settledExecution)) {
1267
- active = settledExecution;
1268
- continue;
1269
- }
1270
1089
  terminalCommitted = publishExecutionResult(manifest, settledExecution.request, settledExecution.result, broker, brokerOwns);
1271
1090
  if (terminalCommitted && settledExecution.request.family === 'task-finalization') {
1272
1091
  try {
@@ -1351,17 +1170,9 @@ export async function serveSandboxControl(
1351
1170
  executorCwd: manifest.repoRoot,
1352
1171
  executorEntry: options.internalCliPath ?? process.argv[1] ?? null
1353
1172
  });
1354
- const lifecycleAuthority = lifecycleAuthorityForRequest(manifest, manifestPath, request);
1355
- if (lifecycleAuthority?.status === 'rejected') {
1356
- throw Object.assign(
1357
- new Error(lifecycleAuthority.error?.message ?? 'lifecycle authority was rejected'),
1358
- { code: lifecycleAuthority.error?.code ?? 'LIFECYCLE_AUTHORITY_REJECTED' }
1359
- );
1360
- }
1361
1173
  prepared = await prepareExecution({
1362
1174
  manifest, manifestPath, request, requestPath: claimed,
1363
- internalCliPath: options.internalCliPath ?? process.argv[1]!,
1364
- ...(lifecycleAuthority ? { lifecycleAuthority } : {})
1175
+ internalCliPath: options.internalCliPath ?? process.argv[1]!
1365
1176
  });
1366
1177
  const preparedExecution = prepared;
1367
1178
  if (!preparedExecution) throw new Error('SANDBOX_CONTROL_EXECUTION_PREPARE_INVALID');
@@ -1376,7 +1187,7 @@ export async function serveSandboxControl(
1376
1187
  executionPath: executionPath(manifest, request.id)
1377
1188
  });
1378
1189
  const execution: ActiveExecution = {
1379
- request, prepared: preparedExecution, lifecycleAuthority, result: null,
1190
+ request, prepared: preparedExecution, result: null,
1380
1191
  resultEvidenceWritten: false, failure: null, settled: false
1381
1192
  };
1382
1193
  active = execution;
@@ -1530,8 +1341,7 @@ export async function serveSandboxControl(
1530
1341
  }
1531
1342
  }
1532
1343
  if (owned && active.result && active.resultEvidenceWritten) {
1533
- if (consumeLifecycleAuthorityForResult(manifest, active)
1534
- && publishExecutionResult(manifest, active.request, active.result, broker, brokerOwns)) {
1344
+ if (publishExecutionResult(manifest, active.request, active.result, broker, brokerOwns)) {
1535
1345
  if (brokerOwns()) {
1536
1346
  removeAcceptedResponse(manifest, active.request.id);
1537
1347
  fs.rmSync(path.join(manifest.processingDir, active.request.id), { recursive: true, force: true });
@@ -1,6 +1,5 @@
1
1
  import { isCompletionEvidence, type CleanCompletionEvidence } from '../../task/orchestration.ts';
2
2
  import { parseControlOutput } from '../../task/control-recovery.ts';
3
- import { isRecoveryWarning } from '../../task/recovery-warning.ts';
4
3
  import { execFileSync } from 'node:child_process';
5
4
  import fs from 'node:fs';
6
5
  import path from 'node:path';
@@ -15,7 +14,6 @@ import type {
15
14
  SandboxControlPayload,
16
15
  SandboxControlReservation,
17
16
  SandboxControlResultEvidence,
18
- SandboxControlRecoveryWarning,
19
17
  SandboxControlStatus
20
18
  } from './protocol.ts';
21
19
  import {
@@ -115,7 +113,6 @@ export type SandboxControlTerminalResult = Readonly<{
115
113
  status: string;
116
114
  changed: boolean | null;
117
115
  completionEvidence: CleanCompletionEvidence | null;
118
- warning?: SandboxControlRecoveryWarning | null;
119
116
  }>;
120
117
 
121
118
  export function terminalResultPath(manifest: SandboxControlManifest, requestId: string): string {
@@ -133,7 +130,6 @@ export function createSandboxControlTerminalResult(
133
130
  ? nested.run as Record<string, unknown> : null;
134
131
  const rawCompletion = nested.completionEvidence ?? run?.completionEvidence;
135
132
  const operation = request.operation ?? (typeof nested.intent === 'string' ? nested.intent : null) ?? '';
136
- const warning = isRecoveryWarning(nested.warning) ? nested.warning : null;
137
133
  return {
138
134
  version: 1,
139
135
  requestId: request.id,
@@ -143,8 +139,7 @@ export function createSandboxControlTerminalResult(
143
139
  targetState: typeof nested.targetState === 'string' ? nested.targetState : null,
144
140
  status: typeof nested.status === 'string' ? nested.status : 'completed',
145
141
  changed: typeof nested.changed === 'boolean' ? nested.changed : null,
146
- completionEvidence: isCompletionEvidence(rawCompletion) ? rawCompletion : null,
147
- ...(warning ? { warning } : {})
142
+ completionEvidence: isCompletionEvidence(rawCompletion) ? rawCompletion : null
148
143
  };
149
144
  }
150
145
 
@@ -178,9 +173,6 @@ export function readSandboxControlTerminalResult(filePath: string): SandboxContr
178
173
  if (value.completionEvidence !== null && !isCompletionEvidence(value.completionEvidence)) {
179
174
  throw new Error('SANDBOX_CONTROL_TERMINAL_RESULT_INVALID');
180
175
  }
181
- if (value.warning !== undefined && value.warning !== null && !isRecoveryWarning(value.warning)) {
182
- throw new Error('SANDBOX_CONTROL_TERMINAL_RESULT_INVALID');
183
- }
184
176
  if (value.changed === undefined) {
185
177
  throw new Error('SANDBOX_CONTROL_TERMINAL_RESULT_INVALID');
186
178
  }