@fitlab-ai/agent-infra 0.9.14 → 0.10.0
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.
- package/README.md +10 -0
- package/README.zh-CN.md +10 -0
- package/bin/internal-cli.sh +45 -0
- package/dist/bin/internal-cli.js +93 -30
- package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +165 -27
- package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +9 -4
- package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +43 -3
- package/dist/lib/defaults.json +103 -103
- package/dist/lib/fs/durable-write.js +39 -0
- package/dist/lib/fs/file-lock.js +111 -0
- package/dist/lib/host-control/audit.js +19 -0
- package/dist/lib/host-control/client.js +142 -0
- package/dist/lib/host-control/command.js +49 -0
- package/dist/lib/host-control/path.js +160 -0
- package/dist/lib/host-control/secure-fs.js +140 -0
- package/dist/lib/host-control/server.js +159 -0
- package/dist/lib/host-control/service.js +112 -0
- package/dist/lib/init.js +11 -2
- package/dist/lib/internal/cli-route-inventory.js +4 -2
- package/dist/lib/internal/codex-lifecycle.js +6 -6
- package/dist/lib/internal/demo-promotion.js +322 -0
- package/dist/lib/internal/demo-transcript.js +454 -0
- package/dist/lib/internal/host-control.js +28 -0
- package/dist/lib/internal/manual-validation.js +331 -0
- package/dist/lib/internal/platform-pr.js +18 -4
- package/dist/lib/internal/release-workflow.js +63 -84
- package/dist/lib/internal/sandbox-control.js +149 -2
- package/dist/lib/internal/task-artifact.js +16 -263
- package/dist/lib/internal/task-create.js +6 -5
- package/dist/lib/internal/task-event.js +37 -37
- package/dist/lib/internal/task-lifecycle.js +2 -2
- package/dist/lib/internal/task-operation-registry.js +101 -4
- package/dist/lib/internal/task-review.js +11 -56
- package/dist/lib/internal/task-validate.js +30 -10
- package/dist/lib/owned-path.js +33 -0
- package/dist/lib/platform/check-status.js +11 -0
- package/dist/lib/platform/comment-safety.js +4 -1
- package/dist/lib/platform/completion-backfill.js +1 -1
- package/dist/lib/platform/github-data.js +241 -0
- package/dist/lib/platform/github-provider.js +15 -20
- package/dist/lib/platform/issue-comments.js +18 -12
- package/dist/lib/platform/issues.js +2 -180
- package/dist/lib/platform/pr-change-report.js +5 -4
- package/dist/lib/platform/pr-checks.js +12 -65
- package/dist/lib/platform/pr-summary.js +117 -33
- package/dist/lib/platform/pull-requests.js +5 -108
- package/dist/lib/platform/snapshots.js +2 -0
- package/dist/lib/platform/task-identities.js +2 -9
- package/dist/lib/platform/verification-sync.js +132 -143
- package/dist/lib/pr-review/evidence-grading.js +4 -8
- package/dist/lib/release/workflow.js +1 -1
- package/dist/lib/sandbox/commands/create.js +7 -2
- package/dist/lib/sandbox/commands/rm.js +19 -1511
- package/dist/lib/sandbox/control/audit.js +203 -0
- package/dist/lib/sandbox/control/client.js +93 -54
- package/dist/lib/sandbox/control/executor.js +129 -65
- package/dist/lib/sandbox/control/finalization-response.js +27 -0
- package/dist/lib/sandbox/control/identity-sentinel.js +107 -0
- package/dist/lib/sandbox/control/lifecycle.js +36 -2
- package/dist/lib/sandbox/control/native-file-lock.js +6 -98
- package/dist/lib/sandbox/control/protocol.js +55 -4
- package/dist/lib/sandbox/control/server.js +518 -127
- package/dist/lib/sandbox/control/state.js +64 -0
- package/dist/lib/sandbox/control/task-workflow.js +74 -0
- package/dist/lib/sandbox/control/workflow-executor.js +89 -0
- package/dist/lib/sandbox/index.js +1 -2
- package/dist/lib/sandbox/recovery.js +28 -4
- package/dist/lib/sandbox/removal.js +822 -0
- package/dist/lib/sandbox/task-cleanup.js +217 -0
- package/dist/lib/sandbox/workspace-identity.js +17 -1
- package/dist/lib/sandbox/workspace-view.js +13 -14
- package/dist/lib/task/activity-intent.js +2 -1
- package/dist/lib/task/activity-log.js +30 -24
- package/dist/lib/task/artifact-command.js +79 -0
- package/dist/lib/task/artifact-lifecycle.js +30 -28
- package/dist/lib/task/artifact-name.js +36 -0
- package/dist/lib/task/artifact-operations.js +29 -289
- package/dist/lib/task/artifact-receipts.js +3 -15
- package/dist/lib/task/artifact-recovery.js +430 -0
- package/dist/lib/task/artifact-repair-intent.js +134 -29
- package/dist/lib/task/artifact-schema.js +6 -14
- package/dist/lib/task/capabilities.js +12 -19
- package/dist/lib/task/codex-orchestration.js +71 -25
- package/dist/lib/task/commit-identity.js +28 -0
- package/dist/lib/task/commit-operation.js +33 -20
- package/dist/lib/task/control-authority.js +403 -6
- package/dist/lib/task/control-recovery.js +140 -0
- package/dist/lib/task/create-service.js +1 -8
- package/dist/lib/task/create.js +62 -9
- package/dist/lib/task/decision-details.js +2 -46
- package/dist/lib/task/delegation-receipts.js +64 -3
- package/dist/lib/task/events.js +275 -22
- package/dist/lib/task/finalization-state.js +9 -0
- package/dist/lib/task/human-override.js +8 -14
- package/dist/lib/task/implementation-inputs.js +3 -3
- package/dist/lib/task/intermediate-cleanup.js +441 -0
- package/dist/lib/task/invalidation.js +18 -65
- package/dist/lib/task/ledger-intents.js +7 -22
- package/dist/lib/task/ledger.js +1 -3
- package/dist/lib/task/lifecycle-recovery.js +549 -0
- package/dist/lib/task/lifecycle.js +31 -4
- package/dist/lib/task/local-artifact-finalization.js +197 -197
- package/dist/lib/task/manual-validation-completion.js +26 -0
- package/dist/lib/task/manual-validation-evidence.js +130 -0
- package/dist/lib/task/manual-validation-receipt.js +105 -0
- package/dist/lib/task/manual-validation-shared.js +40 -0
- package/dist/lib/task/manual-validation-transaction.js +243 -0
- package/dist/lib/task/markdown.js +47 -0
- package/dist/lib/task/orchestration.js +66 -19
- package/dist/lib/task/pr-delivery-fact.js +12 -56
- package/dist/lib/task/qualification-audit.js +20 -34
- package/dist/lib/task/recovery-warning.js +19 -0
- package/dist/lib/task/review-artifacts.js +2 -18
- package/dist/lib/task/review-command.js +26 -0
- package/dist/lib/task/review-finalization.js +175 -216
- package/dist/lib/task/review-fingerprint.js +2 -2
- package/dist/lib/task/sections.js +34 -56
- package/dist/lib/task/short-id.js +15 -7
- package/dist/lib/task/task-execution-lock.js +1 -1
- package/dist/lib/task/verification-engine.js +119 -337
- package/dist/lib/task/verification.js +1 -1
- package/dist/lib/task/workflow-command.js +16 -0
- package/dist/lib/task/workflow-warning-intents.js +0 -16
- package/dist/lib/task/workflow-warnings.js +28 -2
- package/dist/lib/update.js +2 -0
- package/dist/lib/version.js +1 -11
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +223 -33
- package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +10 -4
- package/lib/agent-clients/adapters/codex-lifecycle/store.ts +55 -3
- package/lib/fs/durable-write.ts +30 -0
- package/lib/fs/file-lock.ts +113 -0
- package/lib/host-control/audit.ts +22 -0
- package/lib/host-control/client.ts +172 -0
- package/lib/host-control/command.ts +60 -0
- package/lib/host-control/path.ts +167 -0
- package/lib/host-control/secure-fs.ts +135 -0
- package/lib/host-control/server.ts +180 -0
- package/lib/host-control/service.ts +133 -0
- package/lib/init.ts +15 -2
- package/lib/internal/cli-route-inventory.ts +4 -2
- package/lib/internal/codex-lifecycle.ts +6 -6
- package/lib/internal/demo-promotion.ts +354 -0
- package/lib/internal/demo-transcript.ts +419 -0
- package/lib/internal/host-control.ts +28 -0
- package/lib/internal/manual-validation.ts +315 -0
- package/lib/internal/platform-pr.ts +15 -4
- package/lib/internal/release-workflow.ts +74 -82
- package/lib/internal/sandbox-control.ts +162 -1
- package/lib/internal/task-artifact.ts +14 -217
- package/lib/internal/task-create.ts +5 -5
- package/lib/internal/task-event.ts +20 -11
- package/lib/internal/task-lifecycle.ts +4 -3
- package/lib/internal/task-operation-registry.ts +111 -4
- package/lib/internal/task-review.ts +9 -54
- package/lib/internal/task-validate.ts +26 -10
- package/lib/owned-path.ts +26 -0
- package/lib/platform/check-status.ts +9 -0
- package/lib/platform/comment-safety.ts +5 -0
- package/lib/platform/completion-backfill.ts +1 -1
- package/lib/platform/github-data.ts +322 -0
- package/lib/platform/github-provider.ts +15 -20
- package/lib/platform/issue-comments.ts +21 -14
- package/lib/platform/issues.ts +5 -246
- package/lib/platform/pr-change-report.ts +7 -5
- package/lib/platform/pr-checks.ts +13 -77
- package/lib/platform/pr-summary.ts +123 -35
- package/lib/platform/pull-requests.ts +6 -173
- package/lib/platform/snapshots.ts +53 -0
- package/lib/platform/task-identities.ts +3 -14
- package/lib/platform/verification-sync.ts +150 -156
- package/lib/pr-review/evidence-grading.ts +4 -9
- package/lib/release/workflow.ts +3 -3
- package/lib/sandbox/commands/create.ts +7 -6
- package/lib/sandbox/commands/rm.ts +21 -1895
- package/lib/sandbox/control/audit.ts +253 -0
- package/lib/sandbox/control/client.ts +109 -66
- package/lib/sandbox/control/executor.ts +134 -74
- package/lib/sandbox/control/finalization-response.ts +29 -0
- package/lib/sandbox/control/identity-sentinel.ts +137 -0
- package/lib/sandbox/control/lifecycle.ts +45 -3
- package/lib/sandbox/control/native-file-lock.ts +6 -107
- package/lib/sandbox/control/protocol.ts +80 -6
- package/lib/sandbox/control/server.ts +567 -132
- package/lib/sandbox/control/state.ts +86 -0
- package/lib/sandbox/control/task-workflow.ts +95 -0
- package/lib/sandbox/control/workflow-executor.ts +92 -0
- package/lib/sandbox/index.ts +1 -2
- package/lib/sandbox/recovery.ts +28 -5
- package/lib/sandbox/removal.ts +1142 -0
- package/lib/sandbox/task-cleanup.ts +209 -0
- package/lib/sandbox/workspace-identity.ts +19 -3
- package/lib/sandbox/workspace-view.ts +17 -15
- package/lib/task/activity-intent.ts +2 -2
- package/lib/task/activity-log.ts +27 -19
- package/lib/task/artifact-command.ts +85 -0
- package/lib/task/artifact-lifecycle.ts +31 -26
- package/lib/task/artifact-name.ts +55 -0
- package/lib/task/artifact-operations.ts +34 -310
- package/lib/task/artifact-receipts.ts +3 -14
- package/lib/task/artifact-recovery.ts +519 -0
- package/lib/task/artifact-repair-intent.ts +162 -33
- package/lib/task/artifact-schema.ts +5 -34
- package/lib/task/capabilities.ts +13 -22
- package/lib/task/codex-orchestration.ts +85 -24
- package/lib/task/commit-identity.ts +31 -0
- package/lib/task/commit-operation.ts +37 -20
- package/lib/task/control-authority.ts +540 -10
- package/lib/task/control-recovery.ts +186 -0
- package/lib/task/create-service.ts +1 -10
- package/lib/task/create.ts +63 -8
- package/lib/task/decision-details.ts +3 -71
- package/lib/task/delegation-receipts.ts +73 -2
- package/lib/task/events.ts +309 -24
- package/lib/task/finalization-state.ts +11 -0
- package/lib/task/human-override.ts +8 -16
- package/lib/task/implementation-inputs.ts +3 -3
- package/lib/task/intermediate-cleanup.ts +493 -0
- package/lib/task/invalidation.ts +18 -62
- package/lib/task/ledger-intents.ts +7 -24
- package/lib/task/ledger.ts +1 -3
- package/lib/task/lifecycle-recovery.ts +691 -0
- package/lib/task/lifecycle.ts +30 -5
- package/lib/task/local-artifact-finalization.ts +244 -234
- package/lib/task/manual-validation-completion.ts +40 -0
- package/lib/task/manual-validation-evidence.ts +209 -0
- package/lib/task/manual-validation-receipt.ts +156 -0
- package/lib/task/manual-validation-shared.ts +50 -0
- package/lib/task/manual-validation-transaction.ts +304 -0
- package/lib/task/markdown.ts +70 -0
- package/lib/task/orchestration.ts +84 -19
- package/lib/task/pr-delivery-fact.ts +15 -62
- package/lib/task/qualification-audit.ts +22 -33
- package/lib/task/recovery-warning.ts +21 -0
- package/lib/task/review-artifacts.ts +1 -20
- package/lib/task/review-command.ts +24 -0
- package/lib/task/review-finalization.ts +183 -292
- package/lib/task/review-fingerprint.ts +2 -2
- package/lib/task/sections.ts +33 -54
- package/lib/task/short-id.ts +27 -5
- package/lib/task/task-execution-lock.ts +1 -1
- package/lib/task/verification-engine.ts +113 -383
- package/lib/task/verification-types.ts +15 -0
- package/lib/task/verification.ts +1 -1
- package/lib/task/workflow-command.ts +17 -0
- package/lib/task/workflow-warning-intents.ts +0 -13
- package/lib/task/workflow-warnings.ts +21 -2
- package/lib/update.ts +6 -0
- package/lib/version.ts +1 -15
- package/package.json +6 -6
- package/templates/.agents/hooks/lifecycle-delegation.js +6 -6
- package/templates/.agents/rules/create-issue.en.md +1 -1
- package/templates/.agents/rules/create-issue.zh-CN.md +1 -1
- package/templates/.agents/rules/issue-sync.en.md +4 -2
- package/templates/.agents/rules/issue-sync.zh-CN.md +4 -3
- package/templates/.agents/rules/local-artifact-repair.en.md +46 -39
- package/templates/.agents/rules/local-artifact-repair.zh-CN.md +43 -36
- package/templates/.agents/rules/testing-discipline.en.md +2 -0
- package/templates/.agents/rules/testing-discipline.zh-CN.md +2 -0
- package/templates/.agents/scripts/package.json +3 -0
- package/templates/.agents/skills/analyze-task/SKILL.en.md +6 -6
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +6 -6
- package/templates/.agents/skills/analyze-task/config/verify.en.json +1 -1
- package/templates/.agents/skills/analyze-task/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/block-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/block-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/block-task/config/verify.json +1 -1
- package/templates/.agents/skills/cancel-task/SKILL.en.md +3 -3
- package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/cancel-task/config/verify.json +1 -1
- package/templates/.agents/skills/code-task/SKILL.en.md +7 -7
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +7 -7
- package/templates/.agents/skills/code-task/config/verify.en.json +1 -1
- package/templates/.agents/skills/code-task/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/commit/reference/issue-metadata-sync.en.md +1 -1
- package/templates/.agents/skills/commit/reference/issue-metadata-sync.zh-CN.md +1 -1
- package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +33 -21
- package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +33 -21
- package/templates/.agents/skills/complete-manual-validation/config/verify.en.json +1 -1
- package/templates/.agents/skills/complete-manual-validation/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/complete-manual-validation/reference/report-template.en.md +2 -0
- package/templates/.agents/skills/complete-manual-validation/reference/report-template.zh-CN.md +2 -0
- package/templates/.agents/skills/complete-manual-validation/reference/summary-update.en.md +16 -7
- package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +16 -7
- package/templates/.agents/skills/complete-task/SKILL.en.md +3 -3
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/complete-task/config/verify.en.json +2 -2
- package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +2 -2
- package/templates/.agents/skills/complete-task/reference/external-delivery.en.md +1 -1
- package/templates/.agents/skills/complete-task/reference/external-delivery.zh-CN.md +1 -1
- package/templates/.agents/skills/create-pr/SKILL.en.md +2 -2
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/create-pr/config/verify.json +1 -1
- package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +2 -2
- package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +2 -2
- package/templates/.agents/skills/create-task/SKILL.en.md +5 -5
- package/templates/.agents/skills/create-task/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/create-task/config/verify.json +1 -1
- package/templates/.agents/skills/import-issue/SKILL.en.md +8 -8
- package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +8 -8
- package/templates/.agents/skills/import-issue/config/verify.json +2 -2
- package/templates/.agents/skills/plan-task/SKILL.en.md +4 -4
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/plan-task/config/verify.en.json +1 -1
- package/templates/.agents/skills/plan-task/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/release/SKILL.en.md +7 -3
- package/templates/.agents/skills/release/SKILL.zh-CN.md +7 -3
- package/templates/.agents/skills/restore-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/restore-task/config/verify.json +1 -1
- package/templates/.agents/skills/review-analysis/SKILL.en.md +2 -2
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/review-analysis/config/verify.en.json +1 -1
- package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/review-analysis/reference/report-template.en.md +7 -7
- package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +6 -6
- package/templates/.agents/skills/review-code/SKILL.en.md +2 -2
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/review-code/config/verify.en.json +1 -1
- package/templates/.agents/skills/review-code/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/review-code/reference/report-template.en.md +12 -12
- package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +12 -12
- package/templates/.agents/skills/review-plan/SKILL.en.md +2 -2
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/review-plan/config/verify.en.json +1 -1
- package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/review-plan/reference/report-template.en.md +7 -7
- package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +7 -7
- package/templates/.agents/skills/review-pr/config/verify.en.json +1 -1
- package/templates/.agents/skills/review-pr/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/review-pr/reference/evidence-grading.en.md +3 -3
- package/templates/.agents/skills/review-pr/reference/evidence-grading.zh-CN.md +3 -3
- package/templates/.agents/skills/run-manual-validation/SKILL.en.md +20 -3
- package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +20 -3
- package/templates/.agents/skills/run-manual-validation/config/verify.en.json +1 -1
- package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.en.md +4 -1
- package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.zh-CN.md +4 -1
- package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +2 -0
- package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +2 -0
- package/templates/.agents/skills/run-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/test/SKILL.en.md +17 -6
- package/templates/.agents/skills/test/SKILL.zh-CN.md +17 -6
- package/templates/.agents/templates/task.en.md +1 -1
- package/templates/.agents/templates/task.zh-CN.md +1 -1
- package/templates/.claude/commands/import-issue.en.md +1 -1
- package/templates/.claude/commands/import-issue.zh-CN.md +1 -1
- package/templates/.claude/commands/restore-task.en.md +1 -1
- package/templates/.claude/commands/restore-task.zh-CN.md +1 -1
- package/templates/.claude/commands/run-manual-validation.en.md +1 -1
- package/templates/.claude/commands/run-manual-validation.zh-CN.md +1 -1
- package/dist/lib/platform/adapters.js +0 -56
- package/lib/platform/adapters.ts +0 -197
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { finalizationTerminalResponse } from './finalization-response.js';
|
|
2
|
+
import { isDeepStrictEqual } from 'node:util';
|
|
1
3
|
import { completedReentryView } from './completed-reentry.js';
|
|
2
4
|
import fs from 'node:fs';
|
|
3
5
|
import path from 'node:path';
|
|
@@ -6,65 +8,126 @@ import { getProcessStartTime } from '../../server/process-state.js';
|
|
|
6
8
|
import { assertGitWorktreeBinding } from '../../git/worktree-identity.js';
|
|
7
9
|
import { controlError, DEFAULT_SANDBOX_CONTROL_TIMING, SANDBOX_CONTROL_MAX_RESPONSE_BYTES, SANDBOX_CONTROL_MAX_TERMINAL_RECORD_BYTES } from './protocol.js';
|
|
8
10
|
import { prepareSandboxControlExecution } from './executor.js';
|
|
9
|
-
import {
|
|
11
|
+
import { atomicWriteJsonNoReplace, createSandboxControlPayload, cleanupStaleSandboxControlLease, executionPath, readJsonFile, readActiveLease, readExecution, readSandboxControlPayload, readSandboxControlResultEvidence, readSandboxControlTerminalResult, createSandboxControlTerminalResult, sandboxControlEncodedJsonBytes, sandboxControlGenerationUsage, sanitizeSandboxControlOutput, sanitizeSandboxControlResult, payloadPath, writeSandboxControlPayload, writeSandboxControlReservation, writeSandboxControlResultEvidence, writeSandboxControlTerminalResult, appendSandboxControlAudit, terminateSandboxControlExecution, writeSandboxControlStatus, readSandboxControlStatus } from './state.js';
|
|
10
12
|
import { validateSandboxControlRequest } from './protocol.js';
|
|
13
|
+
import { requestAuditFields, resultAuditFields, appendCriticalAudit, appendDiagnosticAudit, createSandboxControlAuditContext, writeSandboxControlTransition, readSandboxControlTransition } from './audit.js';
|
|
14
|
+
import { parseTaskControlOperation } from '../../task/control-authority.js';
|
|
15
|
+
import { parseControlOutput, classifySandboxControlRecovery, findSandboxControlRecoveryOperation, operationRecoveryBinding, SANDBOX_CONTROL_REQUIRED_COMPLETION_PHASES } from '../../task/control-recovery.js';
|
|
16
|
+
import { readRun } from '../../task/orchestration.js';
|
|
17
|
+
import { readLifecycleRecoveryDomainEvidence } from '../../task/lifecycle-recovery.js';
|
|
18
|
+
import { captureRepositorySnapshot } from '../../task/workspace-snapshot.js';
|
|
19
|
+
import { parseTypedTaskFrontmatter } from '../../task/frontmatter.js';
|
|
20
|
+
import { readLifecycleJournalEvidence } from '../../task/lifecycle.js';
|
|
21
|
+
import { resolveTaskRef } from '../../task/resolve-ref.js';
|
|
22
|
+
import { loadShortIdByTaskId } from '../../task/short-id.js';
|
|
11
23
|
import { inspectSandboxControlContainer } from './container-identity.js';
|
|
12
24
|
import { acquireSandboxControlBrokerStartup, garbageCollectSandboxControlRoot, isSandboxControlRootQuiescing, readSandboxControlManifest } from './lifecycle.js';
|
|
13
25
|
import { nextSandboxControlBackoff } from './timing.js';
|
|
14
26
|
import { readTaskFinalizationReceipt } from '../../task/finalization.js';
|
|
15
|
-
import {
|
|
27
|
+
import { readCodexControllerRegistration } from './controller-registration.js';
|
|
28
|
+
import { validateSandboxControlIdentity } from './identity-sentinel.js';
|
|
16
29
|
import { mergeSandboxTaskView, taskViewAfterFinalization, taskViewForManifest } from './task-view.js';
|
|
17
30
|
import { taskCreateOutputUnavailableResult } from '../../task/create-service.js';
|
|
18
|
-
|
|
31
|
+
import { consumeLifecycleRecoveryAttestation, issueLifecycleRecoveryAttestation, recoverLifecycleRecoveryOperation } from '../../task/control-authority.js';
|
|
32
|
+
import { createCodexCapabilityStore } from '../../agent-clients/adapters/codex-lifecycle/capability-store.js';
|
|
33
|
+
import { computeLifecycleBuildIdentity } from '../../agent-clients/adapters/codex-lifecycle/build-identity.js';
|
|
34
|
+
function consumeLifecycleAuthorityForResult(manifest, execution) {
|
|
35
|
+
const authority = execution.lifecycleAuthority?.attestation;
|
|
36
|
+
if (!authority || execution.result?.exitCode !== 0)
|
|
37
|
+
return true;
|
|
38
|
+
try {
|
|
39
|
+
consumeLifecycleRecoveryAttestation(authority);
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
appendBrokerAudit(manifest, 'lifecycle-authority-consume-retry', {
|
|
44
|
+
...requestAuditFields(manifest, execution.request),
|
|
45
|
+
errorCode: error instanceof Error ? error.name : 'LIFECYCLE_AUTHORITY_CONSUME_FAILED'
|
|
46
|
+
});
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function lifecycleAuthorityForRequest(manifest, manifestPath, request) {
|
|
51
|
+
const selector = request.family === 'task-workflow' || request.family === 'task-orchestration'
|
|
52
|
+
? request.authority
|
|
53
|
+
: undefined;
|
|
54
|
+
if (!selector)
|
|
55
|
+
return undefined;
|
|
56
|
+
let controllerBinding = null;
|
|
19
57
|
try {
|
|
20
|
-
|
|
58
|
+
const registration = readCodexControllerRegistration(manifestPath);
|
|
59
|
+
controllerBinding = {
|
|
60
|
+
instanceDigest: registration.controllerInstanceDigest,
|
|
61
|
+
controlGeneration: registration.controlGeneration
|
|
62
|
+
};
|
|
21
63
|
}
|
|
22
64
|
catch {
|
|
23
|
-
|
|
65
|
+
// The issuer reports the stable unavailable result; the broker never trusts
|
|
66
|
+
// a controller binding supplied by the request.
|
|
24
67
|
}
|
|
68
|
+
return issueLifecycleRecoveryAttestation(selector, {
|
|
69
|
+
controllerBinding,
|
|
70
|
+
capabilityStore: createCodexCapabilityStore(),
|
|
71
|
+
buildIdentity: computeLifecycleBuildIdentity(manifest.repoRoot)
|
|
72
|
+
});
|
|
25
73
|
}
|
|
26
74
|
function appendBrokerAudit(manifest, event, fields = {}) {
|
|
75
|
+
appendDiagnosticAudit(manifest, event, { source: 'broker', ...fields });
|
|
76
|
+
}
|
|
77
|
+
function operationKey(request, output) {
|
|
78
|
+
if (request.family === 'task-finalization')
|
|
79
|
+
return request.operation;
|
|
80
|
+
if (request.family === 'task-create')
|
|
81
|
+
return 'create';
|
|
82
|
+
if (request.family === 'codex-controller')
|
|
83
|
+
return request.command;
|
|
84
|
+
if (request.family === 'task-workflow')
|
|
85
|
+
return request.workflow.operation;
|
|
86
|
+
if (request.family !== 'task-lifecycle' && request.family !== 'task-orchestration')
|
|
87
|
+
return null;
|
|
88
|
+
if (request.family === 'task-orchestration' && request.args[1] === 'route') {
|
|
89
|
+
if (!output)
|
|
90
|
+
return 'route';
|
|
91
|
+
const result = parseControlOutput(output);
|
|
92
|
+
return result?.changed === true && result.status === 'completed' ? 'route.clean-completion' : 'route.read';
|
|
93
|
+
}
|
|
27
94
|
try {
|
|
28
|
-
|
|
95
|
+
const operation = parseTaskControlOperation(request.family, request.args);
|
|
96
|
+
return operation.family === 'task-orchestration' ? operation.intent : operation.request.intent;
|
|
29
97
|
}
|
|
30
98
|
catch {
|
|
31
|
-
|
|
99
|
+
return null;
|
|
32
100
|
}
|
|
33
101
|
}
|
|
34
|
-
function
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
requestArgsSha256: createHash('sha256').update(encodedArgs, 'utf8').digest('hex'),
|
|
46
|
-
requestTaskRef: args[0] ?? null,
|
|
47
|
-
requestCommand: args[1] ?? null,
|
|
48
|
-
controllerProofPresent: request.controllerProof !== null,
|
|
49
|
-
hostCwd: process.cwd(),
|
|
50
|
-
manifestPath,
|
|
51
|
-
manifestPathRealpath: safeRealpath(manifestPath),
|
|
52
|
-
repoRoot: manifest.repoRoot,
|
|
53
|
-
repoRootRealpath: safeRealpath(manifest.repoRoot),
|
|
54
|
-
worktreeRoot: manifest.worktreeRoot,
|
|
55
|
-
worktreeRootRealpath: safeRealpath(manifest.worktreeRoot),
|
|
56
|
-
runtimeDir: manifest.runtimeDir,
|
|
57
|
-
runtimeDirRealpath: safeRealpath(manifest.runtimeDir)
|
|
58
|
-
};
|
|
102
|
+
function recoveryOperationKey(request, terminalResult, output) {
|
|
103
|
+
const operation = operationKey(request, output);
|
|
104
|
+
if (operation !== 'route')
|
|
105
|
+
return operation;
|
|
106
|
+
const digest = terminalResult?.intentDigest;
|
|
107
|
+
for (const candidate of ['route.read', 'route.clean-completion']) {
|
|
108
|
+
const expected = operationRecoveryBinding(request.id, request.generation, null, request.family, candidate).intentDigest;
|
|
109
|
+
if (digest === expected)
|
|
110
|
+
return candidate;
|
|
111
|
+
}
|
|
112
|
+
return null;
|
|
59
113
|
}
|
|
60
|
-
function
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
114
|
+
function terminalResultMatchesRequest(request, terminalResult) {
|
|
115
|
+
const operation = operationKey(request);
|
|
116
|
+
const candidates = operation === 'route'
|
|
117
|
+
? ['route.read', 'route.clean-completion']
|
|
118
|
+
: operation ? [operation] : [];
|
|
119
|
+
return candidates.some((candidate) => createHash('sha256').update(`${request.family}\0${candidate}`, 'utf8').digest('hex') === terminalResult.intentDigest);
|
|
120
|
+
}
|
|
121
|
+
function criticalRequestPhase(manifest, request, phase, outcome, reference) {
|
|
122
|
+
const context = createSandboxControlAuditContext(manifest, {
|
|
123
|
+
requestId: request.id,
|
|
124
|
+
family: request.family,
|
|
125
|
+
operation: operationKey(request),
|
|
126
|
+
phase,
|
|
127
|
+
outcome
|
|
128
|
+
});
|
|
129
|
+
appendCriticalAudit(manifest, context, { reference: reference ?? null });
|
|
130
|
+
writeSandboxControlTransition(manifest, { requestId: request.id, phase, reference });
|
|
68
131
|
}
|
|
69
132
|
function assertRealDirectory(directory, parent) {
|
|
70
133
|
const stat = fs.lstatSync(directory);
|
|
@@ -146,33 +209,7 @@ function finalizationRecoveryResponse(manifest, requestId, exitCode) {
|
|
|
146
209
|
catch {
|
|
147
210
|
return { status: 'deferred' };
|
|
148
211
|
}
|
|
149
|
-
|
|
150
|
-
receipt.taskComment === 'pending' ? 'task-comment' : null,
|
|
151
|
-
receipt.verification === 'pending' ? 'verification' : null
|
|
152
|
-
].filter((step) => step !== null);
|
|
153
|
-
const completedSteps = ['lifecycle', receipt.taskComment === 'pending' ? null : 'task-comment', receipt.verification === 'pending' ? null : 'verification']
|
|
154
|
-
.filter((step) => step !== null);
|
|
155
|
-
const warnings = receipt.warnings
|
|
156
|
-
.filter((warning) => warning.status === 'open')
|
|
157
|
-
.map(({ status: _status, resolvedAt: _resolvedAt, ...warning }) => warning);
|
|
158
|
-
const result = {
|
|
159
|
-
status: 'completed', changed: false, taskId: manifest.taskId,
|
|
160
|
-
lifecycle: { status: 'no-op', changed: false, error: null },
|
|
161
|
-
taskComment: receipt.taskComment === 'pending' ? null : { status: 'no-op', changed: false, error: null },
|
|
162
|
-
verification: receipt.verification === 'pending' ? null : { status: 'no-op', changed: false, error: null },
|
|
163
|
-
completedSteps, pendingSteps,
|
|
164
|
-
result: pendingSteps.length > 0 || receipt.warningProjection === 'pending' || warnings.length > 0
|
|
165
|
-
? 'completed_with_warnings' : 'completed',
|
|
166
|
-
warnings, error: null
|
|
167
|
-
};
|
|
168
|
-
return {
|
|
169
|
-
status: 'matched',
|
|
170
|
-
response: {
|
|
171
|
-
version: 2, id: requestId, phase: 'completed', exitCode: 0,
|
|
172
|
-
stdout: `${JSON.stringify({ version: 1, status: 'completed', changed: false, accepted: true, result, error: null })}\n`,
|
|
173
|
-
stderr: '', error: null
|
|
174
|
-
}
|
|
175
|
-
};
|
|
212
|
+
return { status: 'matched', response: finalizationTerminalResponse(manifest.taskId, requestId, receipt) };
|
|
176
213
|
}
|
|
177
214
|
function publishFinalizationTaskView(manifest, broker, requestId, state, reasonCode, activeRequestId) {
|
|
178
215
|
let view;
|
|
@@ -220,7 +257,7 @@ function outputMatchesEvidence(output, bytes, sha256) {
|
|
|
220
257
|
return Buffer.byteLength(output, 'utf8') === bytes
|
|
221
258
|
&& createHash('sha256').update(output, 'utf8').digest('hex') === sha256;
|
|
222
259
|
}
|
|
223
|
-
function genericRecoveryResponse(request, exitCode, payload, cause = 'recovery') {
|
|
260
|
+
export function genericRecoveryResponse(request, exitCode, payload, cause = 'recovery', warning = null) {
|
|
224
261
|
const outputUnavailable = request.family === 'task-create' && !payload;
|
|
225
262
|
return {
|
|
226
263
|
version: 2,
|
|
@@ -228,24 +265,271 @@ function genericRecoveryResponse(request, exitCode, payload, cause = 'recovery')
|
|
|
228
265
|
phase: 'completed',
|
|
229
266
|
exitCode,
|
|
230
267
|
stdout: outputUnavailable ? `${JSON.stringify(taskCreateOutputUnavailableResult(request.id))}\n` : '',
|
|
231
|
-
stderr: payload ? '' :
|
|
232
|
-
?
|
|
233
|
-
:
|
|
268
|
+
stderr: payload ? '' : warning
|
|
269
|
+
? `${warning.code}: ${warning.message}\nAction: ${warning.action}\n`
|
|
270
|
+
: cause === 'publish'
|
|
271
|
+
? 'SANDBOX_CONTROL_OUTPUT_UNAVAILABLE: output payload was not retained\n'
|
|
272
|
+
: 'SANDBOX_CONTROL_OUTPUT_UNAVAILABLE: broker restarted after executor completion\n',
|
|
234
273
|
error: null,
|
|
235
274
|
outputState: payload ? 'available' : 'unavailable',
|
|
236
275
|
payload: payload ? payloadReference(payload) : null
|
|
237
276
|
};
|
|
238
277
|
}
|
|
239
|
-
function
|
|
278
|
+
function taskCreateDomainEvidence(manifest, output) {
|
|
279
|
+
if (!output || !['applied', 'no-op', 'degraded'].includes(String(output.status))) {
|
|
280
|
+
return { consistent: false };
|
|
281
|
+
}
|
|
282
|
+
const task = output.task && typeof output.task === 'object' && !Array.isArray(output.task)
|
|
283
|
+
? output.task : null;
|
|
284
|
+
const taskId = typeof task?.id === 'string' ? task.id : null;
|
|
285
|
+
const shortId = typeof task?.shortId === 'string' ? task.shortId : null;
|
|
286
|
+
if (!taskId || !shortId)
|
|
287
|
+
return { consistent: false };
|
|
288
|
+
try {
|
|
289
|
+
const resolved = resolveTaskRef(taskId, { repoRoot: manifest.repoRoot });
|
|
290
|
+
const shortIds = loadShortIdByTaskId(manifest.repoRoot);
|
|
291
|
+
return {
|
|
292
|
+
consistent: resolved.ok && resolved.taskId === taskId && resolved.state === 'active' && shortIds.get(taskId) === shortId,
|
|
293
|
+
taskId,
|
|
294
|
+
shortId
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
catch {
|
|
298
|
+
return { consistent: false };
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
function controllerDomainEvidence(manifest, manifestPath, request, output) {
|
|
302
|
+
if (!output || output.error !== null || typeof output.status !== 'string')
|
|
303
|
+
return { consistent: false };
|
|
304
|
+
try {
|
|
305
|
+
const registration = readCodexControllerRegistration(manifestPath);
|
|
306
|
+
if (request.family !== 'codex-controller')
|
|
307
|
+
return { consistent: false };
|
|
308
|
+
if (request.command === 'open') {
|
|
309
|
+
const lease = output.lease && typeof output.lease === 'object' && !Array.isArray(output.lease)
|
|
310
|
+
? output.lease : null;
|
|
311
|
+
return {
|
|
312
|
+
consistent: output.status === 'opened' && output.changed === true
|
|
313
|
+
&& lease?.taskId === registration.taskId
|
|
314
|
+
&& lease.controlGeneration === registration.controlGeneration
|
|
315
|
+
&& lease.controllerInstanceDigest === registration.controllerInstanceDigest
|
|
316
|
+
&& registration.taskId === manifest.taskId
|
|
317
|
+
&& registration.controlGeneration === manifest.generation
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
if (request.command === 'verify') {
|
|
321
|
+
const binding = output.binding && typeof output.binding === 'object' && !Array.isArray(output.binding)
|
|
322
|
+
? output.binding : null;
|
|
323
|
+
return {
|
|
324
|
+
consistent: output.status === 'verified' && output.changed === false
|
|
325
|
+
&& binding?.taskId === registration.taskId
|
|
326
|
+
&& binding.controlGeneration === registration.controlGeneration
|
|
327
|
+
&& binding.controllerInstanceDigest === registration.controllerInstanceDigest
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
return { consistent: false };
|
|
331
|
+
}
|
|
332
|
+
catch (error) {
|
|
333
|
+
return { consistent: request.family === 'codex-controller' && request.command === 'close'
|
|
334
|
+
&& error.code === 'CODEX_SANDBOX_CONTROLLER_REGISTRATION_MISSING'
|
|
335
|
+
&& output?.status === 'closed' && typeof output.changed === 'boolean' };
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
function orchestrationDomainEvidence(operation, terminalResult, output, run) {
|
|
339
|
+
if (!operation || !output)
|
|
340
|
+
return { consistent: false };
|
|
341
|
+
const outputRun = output.run && typeof output.run === 'object' && !Array.isArray(output.run)
|
|
342
|
+
? output.run : null;
|
|
343
|
+
const runMatches = outputRun === null ? run === null : run !== null && JSON.stringify(run) === JSON.stringify(outputRun);
|
|
344
|
+
if (operation.class === 'read-only') {
|
|
345
|
+
return {
|
|
346
|
+
consistent: output.status === terminalResult.status && output.changed === terminalResult.changed && runMatches,
|
|
347
|
+
snapshotValid: true,
|
|
348
|
+
status: run?.status ?? terminalResult.status,
|
|
349
|
+
pendingDelegation: run?.pendingDelegation ?? null
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
if (!run || !outputRun)
|
|
353
|
+
return { consistent: false };
|
|
354
|
+
return {
|
|
355
|
+
consistent: output.status === terminalResult.status && output.changed === terminalResult.changed && runMatches,
|
|
356
|
+
status: run.status,
|
|
357
|
+
pendingDelegation: run.pendingDelegation
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
function readRecoveryDomain(manifest, manifestPath, request, operation, terminalResult, payloadOutput) {
|
|
361
|
+
if (!operation)
|
|
362
|
+
return { domain: null };
|
|
363
|
+
const taskRef = request.family === 'task-finalization' || request.family === 'task-workflow'
|
|
364
|
+
? manifest.taskId
|
|
365
|
+
: 'args' in request ? request.args[0] ?? null : manifest.taskId;
|
|
366
|
+
const output = parseControlOutput(payloadOutput);
|
|
367
|
+
if (!taskRef && operation.family !== 'task-create' && operation.family !== 'codex-controller') {
|
|
368
|
+
return { domain: null };
|
|
369
|
+
}
|
|
370
|
+
if (operation.family === 'task-lifecycle') {
|
|
371
|
+
if (operation.intent === 'recover-started') {
|
|
372
|
+
try {
|
|
373
|
+
if (!('args' in request))
|
|
374
|
+
return { domain: null };
|
|
375
|
+
const parsed = parseTaskControlOperation('task-lifecycle', request.args);
|
|
376
|
+
if (parsed.family !== 'task-lifecycle' || parsed.request.intent !== 'recover-started') {
|
|
377
|
+
return { domain: null };
|
|
378
|
+
}
|
|
379
|
+
return {
|
|
380
|
+
domain: readLifecycleRecoveryDomainEvidence(manifest.repoRoot, parsed.request, terminalResult),
|
|
381
|
+
journal: readLifecycleJournalEvidence(manifest.repoRoot, taskRef)
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
catch {
|
|
385
|
+
return { domain: null };
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
if (!taskRef || !terminalResult.targetState)
|
|
389
|
+
return { domain: null };
|
|
390
|
+
const journal = readLifecycleJournalEvidence(manifest.repoRoot, taskRef);
|
|
391
|
+
try {
|
|
392
|
+
const resolved = resolveTaskRef(taskRef, { repoRoot: manifest.repoRoot });
|
|
393
|
+
const shortIds = loadShortIdByTaskId(manifest.repoRoot);
|
|
394
|
+
const shortIdMatches = terminalResult.targetState === 'active'
|
|
395
|
+
? shortIds.has(resolved.ok ? resolved.taskId : taskRef)
|
|
396
|
+
: !shortIds.has(resolved.ok ? resolved.taskId : taskRef);
|
|
397
|
+
return {
|
|
398
|
+
domain: { consistent: resolved.ok && resolved.state === terminalResult.targetState && shortIdMatches },
|
|
399
|
+
journal
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
catch {
|
|
403
|
+
return { domain: { consistent: false }, journal };
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
if (operation.family === 'task-orchestration') {
|
|
407
|
+
if (!taskRef)
|
|
408
|
+
return { domain: null };
|
|
409
|
+
try {
|
|
410
|
+
const resolved = resolveTaskRef(taskRef, { repoRoot: manifest.repoRoot });
|
|
411
|
+
if (!resolved.ok)
|
|
412
|
+
return { domain: { consistent: false } };
|
|
413
|
+
const run = readRun(resolved.taskDir);
|
|
414
|
+
if (!run && operation.class !== 'read-only')
|
|
415
|
+
return { domain: { consistent: false } };
|
|
416
|
+
if (operation.class === 'route.clean-completion') {
|
|
417
|
+
const snapshot = captureRepositorySnapshot(manifest.repoRoot);
|
|
418
|
+
const metadata = parseTypedTaskFrontmatter(fs.readFileSync(resolved.taskMdPath, 'utf8'));
|
|
419
|
+
const completion = run?.completionEvidence ?? null;
|
|
420
|
+
const consistent = run !== null && completion !== null
|
|
421
|
+
&& terminalResult.completionEvidence !== null
|
|
422
|
+
&& isDeepStrictEqual(completion, terminalResult.completionEvidence);
|
|
423
|
+
return {
|
|
424
|
+
domain: {
|
|
425
|
+
consistent,
|
|
426
|
+
status: run?.status ?? null,
|
|
427
|
+
pendingDelegation: run?.pendingDelegation ?? null,
|
|
428
|
+
completionEvidence: completion,
|
|
429
|
+
snapshot,
|
|
430
|
+
lastReviewedCommit: metadata.last_reviewed_commit
|
|
431
|
+
}
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
return { domain: orchestrationDomainEvidence(operation, terminalResult, output, run) };
|
|
435
|
+
}
|
|
436
|
+
catch {
|
|
437
|
+
return { domain: { consistent: false } };
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
if (operation.family === 'task-create') {
|
|
441
|
+
return { domain: taskCreateDomainEvidence(manifest, output) };
|
|
442
|
+
}
|
|
443
|
+
if (operation.family === 'codex-controller') {
|
|
444
|
+
return { domain: controllerDomainEvidence(manifest, manifestPath, request, output) };
|
|
445
|
+
}
|
|
446
|
+
if (operation.family === 'task-workflow') {
|
|
447
|
+
return {
|
|
448
|
+
domain: { consistent: output?.status === terminalResult.status && output.changed === terminalResult.changed, snapshotValid: true }
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
return { domain: null };
|
|
452
|
+
}
|
|
453
|
+
function readCommittedCriticalPhases(manifest, requestId) {
|
|
454
|
+
return SANDBOX_CONTROL_REQUIRED_COMPLETION_PHASES.filter((phase) => {
|
|
455
|
+
try {
|
|
456
|
+
readSandboxControlTransition(manifest, requestId, phase);
|
|
457
|
+
return true;
|
|
458
|
+
}
|
|
459
|
+
catch {
|
|
460
|
+
return false;
|
|
461
|
+
}
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
export function recoveryResponse(manifest, manifestPath, request, evidence, payload, terminalResult) {
|
|
465
|
+
const operationName = recoveryOperationKey(request, terminalResult, payload?.stdout);
|
|
466
|
+
const operation = operationName ? findSandboxControlRecoveryOperation(request.family, operationName) : null;
|
|
467
|
+
if (!operation)
|
|
468
|
+
return null;
|
|
469
|
+
if (payload) {
|
|
470
|
+
const payloadTerminal = createSandboxControlTerminalResult(manifest, {
|
|
471
|
+
id: request.id, family: request.family, operation: operationName
|
|
472
|
+
}, payload.stdout);
|
|
473
|
+
if (JSON.stringify(payloadTerminal) !== JSON.stringify(terminalResult))
|
|
474
|
+
return unknown(request.id);
|
|
475
|
+
}
|
|
476
|
+
const finalization = request.family === 'task-finalization' ? finalizationRecoveryResponse(manifest, request.id, 0) : null;
|
|
477
|
+
if (evidence.exitCode === 0 && finalization?.status === 'deferred')
|
|
478
|
+
return null;
|
|
479
|
+
if (evidence.exitCode === 0 && request.family === 'task-workflow' && request.authority?.phase === 'task-event.completed') {
|
|
480
|
+
try {
|
|
481
|
+
const registration = readCodexControllerRegistration(manifestPath);
|
|
482
|
+
const recovered = recoverLifecycleRecoveryOperation(request.authority, {
|
|
483
|
+
repoRoot: manifest.repoRoot,
|
|
484
|
+
capabilityStore: createCodexCapabilityStore(),
|
|
485
|
+
buildIdentity: computeLifecycleBuildIdentity(manifest.repoRoot),
|
|
486
|
+
controllerBinding: {
|
|
487
|
+
instanceDigest: registration.controllerInstanceDigest,
|
|
488
|
+
controlGeneration: registration.controlGeneration
|
|
489
|
+
}
|
|
490
|
+
});
|
|
491
|
+
if (recovered.status !== 'committed')
|
|
492
|
+
return null;
|
|
493
|
+
}
|
|
494
|
+
catch (error) {
|
|
495
|
+
appendBrokerAudit(manifest, 'lifecycle-authority-recovery-failed', {
|
|
496
|
+
...requestAuditFields(manifest, request),
|
|
497
|
+
errorCode: error instanceof Error ? error.name : 'LIFECYCLE_AUTHORITY_RECOVERY_FAILED'
|
|
498
|
+
});
|
|
499
|
+
return null;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
const recovery = finalization
|
|
503
|
+
? { domain: { consistent: finalization.status === 'matched' } }
|
|
504
|
+
: readRecoveryDomain(manifest, manifestPath, request, operation, terminalResult, payload?.stdout ?? null);
|
|
505
|
+
const binding = operationRecoveryBinding(request.id, manifest.generation, manifest.taskId, request.family, operationName);
|
|
506
|
+
const decision = classifySandboxControlRecovery({
|
|
507
|
+
operation,
|
|
508
|
+
binding,
|
|
509
|
+
startedCommitted: true,
|
|
510
|
+
terminalResult,
|
|
511
|
+
criticalPhases: readCommittedCriticalPhases(manifest, request.id),
|
|
512
|
+
domain: recovery.domain,
|
|
513
|
+
journal: recovery.journal
|
|
514
|
+
});
|
|
515
|
+
if (decision.outcome === 'unknown' || decision.outcome === 'in-progress')
|
|
516
|
+
return unknown(request.id);
|
|
517
|
+
if (decision.outcome === 'not-executed')
|
|
518
|
+
return notExecuted(request.id);
|
|
519
|
+
if (decision.outcome !== 'success' && decision.outcome !== 'failure')
|
|
520
|
+
return null;
|
|
240
521
|
if (request.family === 'task-finalization' && evidence.exitCode === 0) {
|
|
241
|
-
|
|
242
|
-
|
|
522
|
+
if (finalization?.status !== 'matched')
|
|
523
|
+
return null;
|
|
524
|
+
return finalization.response ?? null;
|
|
243
525
|
}
|
|
244
|
-
return genericRecoveryResponse(request, evidence.exitCode, payload);
|
|
526
|
+
return genericRecoveryResponse(request, evidence.exitCode, payload, 'recovery', terminalResult.warning ?? null);
|
|
245
527
|
}
|
|
246
|
-
function terminalMatchesEvidence(manifest, request, response, evidence, payload, payloadInvalid) {
|
|
528
|
+
function terminalMatchesEvidence(manifest, manifestPath, request, response, evidence, payload, payloadInvalid, terminalResult) {
|
|
247
529
|
if (request.family === 'task-finalization' && evidence.exitCode === 0) {
|
|
248
|
-
|
|
530
|
+
if (!terminalResult)
|
|
531
|
+
return { valid: false, payloadReferenced: false };
|
|
532
|
+
const expected = recoveryResponse(manifest, manifestPath, request, evidence, payload, terminalResult);
|
|
249
533
|
return {
|
|
250
534
|
valid: expected !== null && JSON.stringify(response) === JSON.stringify(expected),
|
|
251
535
|
payloadReferenced: false
|
|
@@ -274,7 +558,7 @@ function terminalMatchesEvidence(manifest, request, response, evidence, payload,
|
|
|
274
558
|
if (response.outputState === 'unavailable') {
|
|
275
559
|
const causes = ['recovery', 'publish'];
|
|
276
560
|
const valid = causes.some((cause) => JSON.stringify(response)
|
|
277
|
-
=== JSON.stringify(genericRecoveryResponse(request, evidence.exitCode, null, cause)));
|
|
561
|
+
=== JSON.stringify(genericRecoveryResponse(request, evidence.exitCode, null, cause, terminalResult?.warning ?? null)));
|
|
278
562
|
return { valid, payloadReferenced: false };
|
|
279
563
|
}
|
|
280
564
|
return {
|
|
@@ -286,6 +570,16 @@ function terminalMatchesEvidence(manifest, request, response, evidence, payload,
|
|
|
286
570
|
}
|
|
287
571
|
function publishExecutionResult(manifest, request, result, broker, brokerOwns) {
|
|
288
572
|
const normalized = sanitizeSandboxControlResult(manifest, result);
|
|
573
|
+
const terminalResult = writeSandboxControlTerminalResult(manifest, {
|
|
574
|
+
id: request.id,
|
|
575
|
+
family: request.family,
|
|
576
|
+
operation: operationKey(request, normalized.stdout)
|
|
577
|
+
}, normalized.stdout);
|
|
578
|
+
if (fs.existsSync(path.join(manifest.processingDir, request.id, 'transitions'))) {
|
|
579
|
+
criticalRequestPhase(manifest, request, 'completed', normalized.exitCode === 0 ? 'success' : 'failure');
|
|
580
|
+
criticalRequestPhase(manifest, request, 'evidence-written', normalized.exitCode === 0 ? 'success' : 'failure');
|
|
581
|
+
criticalRequestPhase(manifest, request, 'publish-authorized', normalized.exitCode === 0 ? 'success' : 'failure');
|
|
582
|
+
}
|
|
289
583
|
let terminal = null;
|
|
290
584
|
if (request.family === 'task-finalization') {
|
|
291
585
|
const finalization = finalizationRecoveryResponse(manifest, request.id, normalized.exitCode);
|
|
@@ -316,7 +610,7 @@ function publishExecutionResult(manifest, request, result, broker, brokerOwns) {
|
|
|
316
610
|
catch {
|
|
317
611
|
payload = null;
|
|
318
612
|
}
|
|
319
|
-
terminal = genericRecoveryResponse(request, normalized.exitCode, payload, 'publish');
|
|
613
|
+
terminal = genericRecoveryResponse(request, normalized.exitCode, payload, 'publish', terminalResult.warning ?? null);
|
|
320
614
|
}
|
|
321
615
|
}
|
|
322
616
|
if (!brokerOwns())
|
|
@@ -326,7 +620,11 @@ function publishExecutionResult(manifest, request, result, broker, brokerOwns) {
|
|
|
326
620
|
if (view.state === 'unknown')
|
|
327
621
|
return false;
|
|
328
622
|
}
|
|
329
|
-
|
|
623
|
+
const committed = writeSandboxControlResponse(manifest, terminal);
|
|
624
|
+
if (committed && fs.existsSync(path.join(manifest.processingDir, request.id, 'transitions'))) {
|
|
625
|
+
criticalRequestPhase(manifest, request, 'published-committed', normalized.exitCode === 0 ? 'success' : 'failure');
|
|
626
|
+
}
|
|
627
|
+
return committed;
|
|
330
628
|
}
|
|
331
629
|
function writeAcceptedResponse(manifest, response) {
|
|
332
630
|
atomicWriteJsonNoReplace(acceptedResponsePath(manifest, response.id), response);
|
|
@@ -386,7 +684,20 @@ function bindingReason(manifest) {
|
|
|
386
684
|
return 'SANDBOX_WORKTREE_BINDING_LOST';
|
|
387
685
|
}
|
|
388
686
|
}
|
|
389
|
-
function
|
|
687
|
+
function assertCurrentSandboxControlIdentity(manifest, manifestPath) {
|
|
688
|
+
const result = validateSandboxControlIdentity({
|
|
689
|
+
publicStatusDir: manifest.publicStatusDir,
|
|
690
|
+
root: path.dirname(path.resolve(manifestPath)),
|
|
691
|
+
mode: manifest.mode,
|
|
692
|
+
taskId: manifest.taskId,
|
|
693
|
+
generation: manifest.generation,
|
|
694
|
+
controlRootId: manifest.controlRootId
|
|
695
|
+
});
|
|
696
|
+
if (result.state !== 'valid') {
|
|
697
|
+
throw new Error(`SANDBOX_CONTROL_IDENTITY_${result.state.replaceAll('-', '_').toUpperCase()}`);
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
function recoverProcessing(manifest, manifestPath, broker, brokerOwns) {
|
|
390
701
|
for (const entry of fs.readdirSync(manifest.processingDir, { withFileTypes: true })) {
|
|
391
702
|
if (!entry.isDirectory() || !/^[a-f0-9-]{16,64}$/.test(entry.name))
|
|
392
703
|
continue;
|
|
@@ -395,14 +706,19 @@ function recoverProcessing(manifest, broker, brokerOwns) {
|
|
|
395
706
|
const descriptor = executionPath(manifest, entry.name);
|
|
396
707
|
const existingResponse = responsePath(manifest, entry.name);
|
|
397
708
|
const processingDirectory = path.join(manifest.processingDir, entry.name);
|
|
709
|
+
const transitionsDirectory = path.join(processingDirectory, 'transitions');
|
|
710
|
+
const transitionProtocolActive = fs.existsSync(transitionsDirectory);
|
|
711
|
+
const startedCommitted = fs.existsSync(path.join(transitionsDirectory, 'started-committed.json'));
|
|
398
712
|
let payloadReferenced = false;
|
|
399
713
|
if (fs.existsSync(descriptor)) {
|
|
400
714
|
const execution = readExecution(descriptor);
|
|
401
715
|
let resultEvidence = null;
|
|
716
|
+
let terminalResult = null;
|
|
402
717
|
let payload = null;
|
|
403
718
|
let payloadInvalid = false;
|
|
404
719
|
let request = null;
|
|
405
720
|
let existingTerminalResponse = null;
|
|
721
|
+
let terminalReconciled = false;
|
|
406
722
|
try {
|
|
407
723
|
const rawRequest = JSON.parse(fs.readFileSync(path.join(processingDirectory, 'request.json'), 'utf8'));
|
|
408
724
|
request = validateSandboxControlRequest(rawRequest, manifest, {
|
|
@@ -424,6 +740,19 @@ function recoverProcessing(manifest, broker, brokerOwns) {
|
|
|
424
740
|
resultEvidence = null;
|
|
425
741
|
}
|
|
426
742
|
}
|
|
743
|
+
const terminalResultPathValue = path.join(processingDirectory, 'terminal-result.json');
|
|
744
|
+
if (fs.existsSync(terminalResultPathValue)) {
|
|
745
|
+
try {
|
|
746
|
+
terminalResult = readSandboxControlTerminalResult(terminalResultPathValue);
|
|
747
|
+
if (terminalResult.requestId !== entry.name || terminalResult.generation !== manifest.generation
|
|
748
|
+
|| !request || !terminalResultMatchesRequest(request, terminalResult)) {
|
|
749
|
+
terminalResult = null;
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
catch {
|
|
753
|
+
terminalResult = null;
|
|
754
|
+
}
|
|
755
|
+
}
|
|
427
756
|
const publishedPayloadPath = payloadPath(manifest, entry.name);
|
|
428
757
|
if (fs.existsSync(publishedPayloadPath)) {
|
|
429
758
|
try {
|
|
@@ -437,12 +766,24 @@ function recoverProcessing(manifest, broker, brokerOwns) {
|
|
|
437
766
|
payloadInvalid = true;
|
|
438
767
|
}
|
|
439
768
|
}
|
|
769
|
+
if (transitionProtocolActive && startedCommitted && !terminalResult) {
|
|
770
|
+
if (!brokerOwns())
|
|
771
|
+
return false;
|
|
772
|
+
if (!terminateSandboxControlExecution(execution)) {
|
|
773
|
+
throw new Error(`SANDBOX_CONTROL_EXECUTION_STILL_RUNNING: ${entry.name}`);
|
|
774
|
+
}
|
|
775
|
+
appendDiagnosticAudit(manifest, 'orphan-tree-terminated', { requestId: entry.name });
|
|
776
|
+
writeSandboxControlResponse(manifest, unknown(entry.name));
|
|
777
|
+
continue;
|
|
778
|
+
}
|
|
779
|
+
if (transitionProtocolActive && !startedCommitted && terminalResult)
|
|
780
|
+
continue;
|
|
440
781
|
if (!terminateSandboxControlExecution(execution)) {
|
|
441
782
|
throw new Error(`SANDBOX_CONTROL_EXECUTION_STILL_RUNNING: ${entry.name}`);
|
|
442
783
|
}
|
|
443
784
|
if (!brokerOwns())
|
|
444
785
|
return false;
|
|
445
|
-
|
|
786
|
+
appendDiagnosticAudit(manifest, 'orphan-tree-terminated', { requestId: entry.name });
|
|
446
787
|
let terminal = false;
|
|
447
788
|
if (fs.existsSync(existingResponse)) {
|
|
448
789
|
try {
|
|
@@ -476,9 +817,12 @@ function recoverProcessing(manifest, broker, brokerOwns) {
|
|
|
476
817
|
if (terminal && (!request || !resultEvidence))
|
|
477
818
|
continue;
|
|
478
819
|
if (terminal && request && resultEvidence) {
|
|
479
|
-
|
|
820
|
+
if (!terminalResult && request.family !== 'task-create')
|
|
821
|
+
continue;
|
|
822
|
+
const reconciliation = terminalMatchesEvidence(manifest, manifestPath, request, existingTerminalResponse, resultEvidence, payload, payloadInvalid, terminalResult);
|
|
480
823
|
if (!reconciliation.valid)
|
|
481
824
|
continue;
|
|
825
|
+
terminalReconciled = true;
|
|
482
826
|
payloadReferenced = reconciliation.payloadReferenced;
|
|
483
827
|
if (request.family === 'task-finalization' && resultEvidence.exitCode === 0) {
|
|
484
828
|
const view = publishFinalizationTaskView(manifest, broker, request.id, 'starting', null, null);
|
|
@@ -486,12 +830,29 @@ function recoverProcessing(manifest, broker, brokerOwns) {
|
|
|
486
830
|
continue;
|
|
487
831
|
}
|
|
488
832
|
}
|
|
833
|
+
if (!terminalResult && !terminalReconciled) {
|
|
834
|
+
if (terminal)
|
|
835
|
+
continue;
|
|
836
|
+
if (!brokerOwns())
|
|
837
|
+
return false;
|
|
838
|
+
if (!resultEvidence || !request || payloadInvalid)
|
|
839
|
+
continue;
|
|
840
|
+
if (request.family === 'task-create' && resultEvidence.exitCode === 0 && payload === null) {
|
|
841
|
+
writeSandboxControlResponse(manifest, genericRecoveryResponse(request, resultEvidence.exitCode, null));
|
|
842
|
+
terminalReconciled = true;
|
|
843
|
+
terminal = true;
|
|
844
|
+
}
|
|
845
|
+
else {
|
|
846
|
+
writeSandboxControlResponse(manifest, unknown(entry.name));
|
|
847
|
+
continue;
|
|
848
|
+
}
|
|
849
|
+
}
|
|
489
850
|
if (!terminal) {
|
|
490
851
|
if (!brokerOwns())
|
|
491
852
|
return false;
|
|
492
853
|
if (!resultEvidence || !request || payloadInvalid)
|
|
493
854
|
continue;
|
|
494
|
-
const recovered = recoveryResponse(manifest, request, resultEvidence, payload);
|
|
855
|
+
const recovered = recoveryResponse(manifest, manifestPath, request, resultEvidence, payload, terminalResult);
|
|
495
856
|
if (!recovered)
|
|
496
857
|
continue;
|
|
497
858
|
if (request.family === 'task-finalization' && resultEvidence.exitCode === 0) {
|
|
@@ -500,12 +861,22 @@ function recoverProcessing(manifest, broker, brokerOwns) {
|
|
|
500
861
|
continue;
|
|
501
862
|
}
|
|
502
863
|
writeSandboxControlResponse(manifest, recovered);
|
|
864
|
+
const preserveRecoveryEvidence = recovered.error?.code === 'SANDBOX_CONTROL_RESULT_UNKNOWN';
|
|
865
|
+
if (transitionProtocolActive && !preserveRecoveryEvidence) {
|
|
866
|
+
writeSandboxControlTransition(manifest, { requestId: entry.name, phase: 'recovered' });
|
|
867
|
+
}
|
|
868
|
+
if (preserveRecoveryEvidence)
|
|
869
|
+
continue;
|
|
503
870
|
payloadReferenced = Boolean(payload) && request.family !== 'task-finalization';
|
|
504
871
|
}
|
|
505
872
|
}
|
|
506
873
|
else {
|
|
507
874
|
if (!brokerOwns())
|
|
508
875
|
return false;
|
|
876
|
+
if (transitionProtocolActive && startedCommitted) {
|
|
877
|
+
writeSandboxControlResponse(manifest, unknown(entry.name));
|
|
878
|
+
continue;
|
|
879
|
+
}
|
|
509
880
|
writeSandboxControlResponse(manifest, notExecuted(entry.name));
|
|
510
881
|
}
|
|
511
882
|
if (!brokerOwns())
|
|
@@ -536,6 +907,7 @@ export async function serveSandboxControl(manifestPath, signal = new AbortContro
|
|
|
536
907
|
const prepareExecution = options.prepareExecution ?? prepareSandboxControlExecution;
|
|
537
908
|
const manifest = readSandboxControlManifest(manifestPath);
|
|
538
909
|
const root = path.dirname(manifestPath);
|
|
910
|
+
assertCurrentSandboxControlIdentity(manifest, manifestPath);
|
|
539
911
|
const startTime = getProcessStartTime(process.pid);
|
|
540
912
|
if (!startTime)
|
|
541
913
|
throw new Error('SANDBOX_CONTROL_BROKER_IDENTITY_UNAVAILABLE');
|
|
@@ -634,22 +1006,9 @@ export async function serveSandboxControl(manifestPath, signal = new AbortContro
|
|
|
634
1006
|
return;
|
|
635
1007
|
appendBrokerAudit(manifest, 'broker-start', {
|
|
636
1008
|
pid: broker.pid,
|
|
637
|
-
brokerId: broker.brokerId
|
|
638
|
-
hostCwd: process.cwd(),
|
|
639
|
-
manifestPath,
|
|
640
|
-
manifestPathRealpath: safeRealpath(manifestPath),
|
|
641
|
-
repoRoot: manifest.repoRoot,
|
|
642
|
-
repoRootRealpath: safeRealpath(manifest.repoRoot),
|
|
643
|
-
worktreeRoot: manifest.worktreeRoot,
|
|
644
|
-
worktreeRootRealpath: safeRealpath(manifest.worktreeRoot),
|
|
645
|
-
runtimeDir: manifest.runtimeDir,
|
|
646
|
-
runtimeDirRealpath: safeRealpath(manifest.runtimeDir),
|
|
647
|
-
channelDir: manifest.channelDir,
|
|
648
|
-
publicStatusDir: manifest.publicStatusDir,
|
|
649
|
-
processingDir: manifest.processingDir,
|
|
650
|
-
internalCliPath: options.internalCliPath ?? process.argv[1] ?? null
|
|
1009
|
+
brokerId: broker.brokerId
|
|
651
1010
|
});
|
|
652
|
-
if (!recoverProcessing(manifest, broker, brokerOwns))
|
|
1011
|
+
if (!recoverProcessing(manifest, manifestPath, broker, brokerOwns))
|
|
653
1012
|
return;
|
|
654
1013
|
try {
|
|
655
1014
|
const published = readSandboxControlStatus(manifest.publicStatusDir);
|
|
@@ -666,6 +1025,7 @@ export async function serveSandboxControl(manifestPath, signal = new AbortContro
|
|
|
666
1025
|
let current;
|
|
667
1026
|
try {
|
|
668
1027
|
current = readSandboxControlManifest(manifestPath);
|
|
1028
|
+
assertCurrentSandboxControlIdentity(current, manifestPath);
|
|
669
1029
|
}
|
|
670
1030
|
catch {
|
|
671
1031
|
break;
|
|
@@ -702,7 +1062,7 @@ export async function serveSandboxControl(manifestPath, signal = new AbortContro
|
|
|
702
1062
|
}
|
|
703
1063
|
else if (observation.state === 'unknown') {
|
|
704
1064
|
containerReasonCode = 'SANDBOX_CONTROL_CONTAINER_UNKNOWN';
|
|
705
|
-
|
|
1065
|
+
appendDiagnosticAudit(manifest, 'container-heartbeat-unknown', { reason: observation.reason });
|
|
706
1066
|
const backoff = nextSandboxControlBackoff(containerBackoffMs, timing.slowCheckMs);
|
|
707
1067
|
nextContainerHeartbeatAt = Date.now() + backoff.delayMs;
|
|
708
1068
|
containerBackoffMs = backoff.nextDelayMs;
|
|
@@ -721,7 +1081,7 @@ export async function serveSandboxControl(manifestPath, signal = new AbortContro
|
|
|
721
1081
|
if (!brokerOwns())
|
|
722
1082
|
break;
|
|
723
1083
|
containerReasonCode = 'SANDBOX_CONTROL_CONTAINER_ABSENT_GC_FAILED';
|
|
724
|
-
|
|
1084
|
+
appendDiagnosticAudit(manifest, 'container-gc-failed', { reason: controlError(error).code });
|
|
725
1085
|
const backoff = nextSandboxControlBackoff(containerBackoffMs, timing.slowCheckMs);
|
|
726
1086
|
nextContainerHeartbeatAt = Date.now() + backoff.delayMs;
|
|
727
1087
|
containerBackoffMs = backoff.nextDelayMs;
|
|
@@ -790,6 +1150,10 @@ export async function serveSandboxControl(manifestPath, signal = new AbortContro
|
|
|
790
1150
|
break;
|
|
791
1151
|
let terminalCommitted = false;
|
|
792
1152
|
if (settledExecution.result && settledExecution.resultEvidenceWritten) {
|
|
1153
|
+
if (!consumeLifecycleAuthorityForResult(manifest, settledExecution)) {
|
|
1154
|
+
active = settledExecution;
|
|
1155
|
+
continue;
|
|
1156
|
+
}
|
|
793
1157
|
terminalCommitted = publishExecutionResult(manifest, settledExecution.request, settledExecution.result, broker, brokerOwns);
|
|
794
1158
|
if (terminalCommitted && settledExecution.request.family === 'task-finalization') {
|
|
795
1159
|
try {
|
|
@@ -808,7 +1172,7 @@ export async function serveSandboxControl(manifestPath, signal = new AbortContro
|
|
|
808
1172
|
if (!terminalCommitted)
|
|
809
1173
|
return;
|
|
810
1174
|
appendBrokerAudit(manifest, 'executor-result-published', {
|
|
811
|
-
...requestAuditFields(manifest,
|
|
1175
|
+
...requestAuditFields(manifest, settledExecution.request),
|
|
812
1176
|
resultAvailable: settledExecution.result !== null,
|
|
813
1177
|
resultEvidenceWritten: settledExecution.resultEvidenceWritten,
|
|
814
1178
|
...(settledExecution.result ? resultAuditFields(settledExecution.result) : {})
|
|
@@ -826,6 +1190,7 @@ export async function serveSandboxControl(manifestPath, signal = new AbortContro
|
|
|
826
1190
|
const source = path.join(requestsDir, name);
|
|
827
1191
|
let claimed = null;
|
|
828
1192
|
let validatedRequest = null;
|
|
1193
|
+
let prepared = null;
|
|
829
1194
|
try {
|
|
830
1195
|
if (!brokerOwns()) {
|
|
831
1196
|
retiring = true;
|
|
@@ -853,63 +1218,76 @@ export async function serveSandboxControl(manifestPath, signal = new AbortContro
|
|
|
853
1218
|
throw new Error('SANDBOX_CONTROL_BUSY');
|
|
854
1219
|
const request = validateSandboxControlRequest(JSON.parse(fs.readFileSync(claimed, 'utf8')), manifest);
|
|
855
1220
|
validatedRequest = request;
|
|
1221
|
+
criticalRequestPhase(manifest, request, 'validated', 'in-progress');
|
|
856
1222
|
appendBrokerAudit(manifest, 'request-validated', {
|
|
857
|
-
...requestAuditFields(manifest,
|
|
1223
|
+
...requestAuditFields(manifest, request),
|
|
858
1224
|
requestPath: claimed
|
|
859
1225
|
});
|
|
860
1226
|
if (bindingCheck(manifest))
|
|
861
1227
|
throw new Error('SANDBOX_WORKTREE_BINDING_LOST');
|
|
862
1228
|
if (readActiveLease(manifest))
|
|
863
1229
|
throw new Error('SANDBOX_CONTROL_HANDOFF_ACTIVE');
|
|
1230
|
+
criticalRequestPhase(manifest, request, 'gated', 'in-progress');
|
|
864
1231
|
appendBrokerAudit(manifest, 'request-gates-passed', {
|
|
865
|
-
...requestAuditFields(manifest,
|
|
1232
|
+
...requestAuditFields(manifest, request),
|
|
866
1233
|
requestPath: claimed,
|
|
867
1234
|
bindingChecked: true,
|
|
868
1235
|
handoffLeaseChecked: true
|
|
869
1236
|
});
|
|
1237
|
+
criticalRequestPhase(manifest, request, 'reserved', 'in-progress');
|
|
870
1238
|
writeSandboxControlReservation(manifest, request.id, sandboxControlGenerationUsage(manifest));
|
|
871
1239
|
appendBrokerAudit(manifest, 'executor-reservation-written', {
|
|
872
|
-
...requestAuditFields(manifest,
|
|
1240
|
+
...requestAuditFields(manifest, request),
|
|
873
1241
|
requestPath: claimed,
|
|
874
1242
|
executionPath: executionPath(manifest, request.id)
|
|
875
1243
|
});
|
|
876
1244
|
appendBrokerAudit(manifest, 'executor-prepare-start', {
|
|
877
|
-
...requestAuditFields(manifest,
|
|
1245
|
+
...requestAuditFields(manifest, request),
|
|
878
1246
|
requestPath: claimed,
|
|
879
1247
|
executorCwd: manifest.repoRoot,
|
|
880
1248
|
executorEntry: options.internalCliPath ?? process.argv[1] ?? null
|
|
881
1249
|
});
|
|
882
|
-
const
|
|
1250
|
+
const lifecycleAuthority = lifecycleAuthorityForRequest(manifest, manifestPath, request);
|
|
1251
|
+
if (lifecycleAuthority?.status === 'rejected') {
|
|
1252
|
+
throw Object.assign(new Error(lifecycleAuthority.error?.message ?? 'lifecycle authority was rejected'), { code: lifecycleAuthority.error?.code ?? 'LIFECYCLE_AUTHORITY_REJECTED' });
|
|
1253
|
+
}
|
|
1254
|
+
prepared = await prepareExecution({
|
|
883
1255
|
manifest, manifestPath, request, requestPath: claimed,
|
|
884
|
-
internalCliPath: options.internalCliPath ?? process.argv[1]
|
|
1256
|
+
internalCliPath: options.internalCliPath ?? process.argv[1],
|
|
1257
|
+
...(lifecycleAuthority ? { lifecycleAuthority } : {})
|
|
885
1258
|
});
|
|
1259
|
+
const preparedExecution = prepared;
|
|
1260
|
+
if (!preparedExecution)
|
|
1261
|
+
throw new Error('SANDBOX_CONTROL_EXECUTION_PREPARE_INVALID');
|
|
1262
|
+
criticalRequestPhase(manifest, request, 'prepared', 'in-progress', `${preparedExecution.execution.child.pid}`);
|
|
886
1263
|
appendBrokerAudit(manifest, 'executor-prepared', {
|
|
887
|
-
...requestAuditFields(manifest,
|
|
1264
|
+
...requestAuditFields(manifest, request),
|
|
888
1265
|
requestPath: claimed,
|
|
889
1266
|
executorCwd: manifest.repoRoot,
|
|
890
|
-
childPid:
|
|
891
|
-
childStartTime:
|
|
892
|
-
childProcessGroupId:
|
|
1267
|
+
childPid: preparedExecution.execution.child.pid,
|
|
1268
|
+
childStartTime: preparedExecution.execution.child.startTime,
|
|
1269
|
+
childProcessGroupId: preparedExecution.execution.child.processGroupId,
|
|
893
1270
|
executionPath: executionPath(manifest, request.id)
|
|
894
1271
|
});
|
|
895
1272
|
const execution = {
|
|
896
|
-
request, prepared
|
|
1273
|
+
request, prepared: preparedExecution, lifecycleAuthority, result: null,
|
|
1274
|
+
resultEvidenceWritten: false, failure: null, settled: false
|
|
897
1275
|
};
|
|
898
1276
|
active = execution;
|
|
899
|
-
|
|
1277
|
+
preparedExecution.completion.then((result) => {
|
|
900
1278
|
execution.result = sanitizeSandboxControlResult(manifest, result);
|
|
901
1279
|
appendBrokerAudit(manifest, 'executor-completed', {
|
|
902
|
-
...requestAuditFields(manifest,
|
|
1280
|
+
...requestAuditFields(manifest, request),
|
|
903
1281
|
...resultAuditFields(execution.result),
|
|
904
|
-
childPid:
|
|
905
|
-
childStartTime:
|
|
1282
|
+
childPid: preparedExecution.execution.child.pid,
|
|
1283
|
+
childStartTime: preparedExecution.execution.child.startTime
|
|
906
1284
|
});
|
|
907
1285
|
try {
|
|
908
1286
|
writeSandboxControlResultEvidence(manifest, request.id, execution.result);
|
|
909
1287
|
execution.resultEvidenceWritten = true;
|
|
910
1288
|
execution.settled = true;
|
|
911
1289
|
appendBrokerAudit(manifest, 'executor-result-evidence-written', {
|
|
912
|
-
...requestAuditFields(manifest,
|
|
1290
|
+
...requestAuditFields(manifest, request),
|
|
913
1291
|
resultPath: path.join(manifest.processingDir, request.id, 'result.json'),
|
|
914
1292
|
...resultAuditFields(execution.result)
|
|
915
1293
|
});
|
|
@@ -917,7 +1295,7 @@ export async function serveSandboxControl(manifestPath, signal = new AbortContro
|
|
|
917
1295
|
catch (error) {
|
|
918
1296
|
execution.failure = error;
|
|
919
1297
|
appendBrokerAudit(manifest, 'executor-result-evidence-failed', {
|
|
920
|
-
...requestAuditFields(manifest,
|
|
1298
|
+
...requestAuditFields(manifest, request),
|
|
921
1299
|
errorType: error instanceof Error ? error.name : typeof error,
|
|
922
1300
|
});
|
|
923
1301
|
}
|
|
@@ -925,9 +1303,9 @@ export async function serveSandboxControl(manifestPath, signal = new AbortContro
|
|
|
925
1303
|
execution.failure = error;
|
|
926
1304
|
execution.settled = true;
|
|
927
1305
|
appendBrokerAudit(manifest, 'executor-failed', {
|
|
928
|
-
...requestAuditFields(manifest,
|
|
929
|
-
childPid:
|
|
930
|
-
childStartTime:
|
|
1306
|
+
...requestAuditFields(manifest, request),
|
|
1307
|
+
childPid: preparedExecution.execution.child.pid,
|
|
1308
|
+
childStartTime: preparedExecution.execution.child.startTime,
|
|
931
1309
|
errorType: error instanceof Error ? error.name : typeof error,
|
|
932
1310
|
});
|
|
933
1311
|
});
|
|
@@ -943,11 +1321,13 @@ export async function serveSandboxControl(manifestPath, signal = new AbortContro
|
|
|
943
1321
|
retiring = true;
|
|
944
1322
|
continue;
|
|
945
1323
|
}
|
|
1324
|
+
criticalRequestPhase(manifest, request, 'accepted-authorized', 'in-progress');
|
|
946
1325
|
writeAcceptedResponse(manifest, {
|
|
947
1326
|
version: 2, id, phase: 'accepted', exitCode: null, stdout: '', stderr: '', error: null
|
|
948
1327
|
});
|
|
1328
|
+
writeSandboxControlTransition(manifest, { requestId: request.id, phase: 'accepted-committed' });
|
|
949
1329
|
appendBrokerAudit(manifest, 'request-accepted', {
|
|
950
|
-
...requestAuditFields(manifest,
|
|
1330
|
+
...requestAuditFields(manifest, request),
|
|
951
1331
|
acceptedPath: acceptedResponsePath(manifest, id),
|
|
952
1332
|
executionPhase: 'prepared'
|
|
953
1333
|
});
|
|
@@ -958,8 +1338,9 @@ export async function serveSandboxControl(manifestPath, signal = new AbortContro
|
|
|
958
1338
|
break;
|
|
959
1339
|
}
|
|
960
1340
|
try {
|
|
1341
|
+
criticalRequestPhase(manifest, request, 'start-authorized', 'in-progress', `${prepared.execution.child.pid}`);
|
|
961
1342
|
appendBrokerAudit(manifest, 'executor-start', {
|
|
962
|
-
...requestAuditFields(manifest,
|
|
1343
|
+
...requestAuditFields(manifest, request),
|
|
963
1344
|
childPid: prepared.execution.child.pid,
|
|
964
1345
|
childStartTime: prepared.execution.child.startTime,
|
|
965
1346
|
executionPath: executionPath(manifest, request.id)
|
|
@@ -968,7 +1349,7 @@ export async function serveSandboxControl(manifestPath, signal = new AbortContro
|
|
|
968
1349
|
}
|
|
969
1350
|
catch (error) {
|
|
970
1351
|
appendBrokerAudit(manifest, 'executor-start-failed', {
|
|
971
|
-
...requestAuditFields(manifest,
|
|
1352
|
+
...requestAuditFields(manifest, request),
|
|
972
1353
|
errorType: error instanceof Error ? error.name : typeof error,
|
|
973
1354
|
});
|
|
974
1355
|
const owned = brokerOwns();
|
|
@@ -997,6 +1378,14 @@ export async function serveSandboxControl(manifestPath, signal = new AbortContro
|
|
|
997
1378
|
}
|
|
998
1379
|
}
|
|
999
1380
|
catch (error) {
|
|
1381
|
+
if (prepared) {
|
|
1382
|
+
try {
|
|
1383
|
+
prepared.terminate(brokerOwns());
|
|
1384
|
+
}
|
|
1385
|
+
catch { /* cleanup is best effort after a failed admission */ }
|
|
1386
|
+
prepared = null;
|
|
1387
|
+
active = null;
|
|
1388
|
+
}
|
|
1000
1389
|
if (!brokerOwns()) {
|
|
1001
1390
|
retiring = true;
|
|
1002
1391
|
break;
|
|
@@ -1016,6 +1405,7 @@ export async function serveSandboxControl(manifestPath, signal = new AbortContro
|
|
|
1016
1405
|
retiring = true;
|
|
1017
1406
|
break;
|
|
1018
1407
|
}
|
|
1408
|
+
removeAcceptedResponse(manifest, id);
|
|
1019
1409
|
if (claimed)
|
|
1020
1410
|
fs.rmSync(path.dirname(claimed), { recursive: true, force: true });
|
|
1021
1411
|
}
|
|
@@ -1041,7 +1431,8 @@ export async function serveSandboxControl(manifestPath, signal = new AbortContro
|
|
|
1041
1431
|
}
|
|
1042
1432
|
}
|
|
1043
1433
|
if (owned && active.result && active.resultEvidenceWritten) {
|
|
1044
|
-
if (
|
|
1434
|
+
if (consumeLifecycleAuthorityForResult(manifest, active)
|
|
1435
|
+
&& publishExecutionResult(manifest, active.request, active.result, broker, brokerOwns)) {
|
|
1045
1436
|
if (brokerOwns()) {
|
|
1046
1437
|
removeAcceptedResponse(manifest, active.request.id);
|
|
1047
1438
|
fs.rmSync(path.join(manifest.processingDir, active.request.id), { recursive: true, force: true });
|
|
@@ -1071,7 +1462,7 @@ export async function serveSandboxControl(manifestPath, signal = new AbortContro
|
|
|
1071
1462
|
}
|
|
1072
1463
|
}
|
|
1073
1464
|
if (brokerOwns()) {
|
|
1074
|
-
|
|
1465
|
+
appendDiagnosticAudit(manifest, 'broker-stop', { pid: broker.pid, brokerId: broker.brokerId });
|
|
1075
1466
|
try {
|
|
1076
1467
|
if (fs.readFileSync(brokerPath, 'utf8') === brokerRecord)
|
|
1077
1468
|
fs.unlinkSync(brokerPath);
|