@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.ts';
|
|
2
|
+
import { isDeepStrictEqual } from 'node:util';
|
|
1
3
|
import { completedReentryView } from './completed-reentry.ts';
|
|
2
4
|
import fs from 'node:fs';
|
|
3
5
|
import path from 'node:path';
|
|
@@ -10,13 +12,13 @@ import {
|
|
|
10
12
|
SANDBOX_CONTROL_MAX_RESPONSE_BYTES,
|
|
11
13
|
SANDBOX_CONTROL_MAX_TERMINAL_RECORD_BYTES,
|
|
12
14
|
type SandboxControlManifest,
|
|
15
|
+
type SandboxControlRecoveryWarning,
|
|
13
16
|
type SandboxControlRequest,
|
|
14
17
|
type SandboxControlResponse,
|
|
15
18
|
type SandboxControlTimingPolicy
|
|
16
19
|
} from './protocol.ts';
|
|
17
20
|
import { prepareSandboxControlExecution, type PreparedSandboxControlExecution, type SandboxControlExecutionResult } from './executor.ts';
|
|
18
21
|
import {
|
|
19
|
-
appendSandboxControlAudit,
|
|
20
22
|
atomicWriteJsonNoReplace,
|
|
21
23
|
createSandboxControlPayload,
|
|
22
24
|
cleanupStaleSandboxControlLease,
|
|
@@ -26,6 +28,8 @@ import {
|
|
|
26
28
|
readExecution,
|
|
27
29
|
readSandboxControlPayload,
|
|
28
30
|
readSandboxControlResultEvidence,
|
|
31
|
+
readSandboxControlTerminalResult,
|
|
32
|
+
createSandboxControlTerminalResult,
|
|
29
33
|
sandboxControlEncodedJsonBytes,
|
|
30
34
|
sandboxControlGenerationUsage,
|
|
31
35
|
sanitizeSandboxControlOutput,
|
|
@@ -34,11 +38,37 @@ import {
|
|
|
34
38
|
writeSandboxControlPayload,
|
|
35
39
|
writeSandboxControlReservation,
|
|
36
40
|
writeSandboxControlResultEvidence,
|
|
41
|
+
writeSandboxControlTerminalResult,
|
|
42
|
+
appendSandboxControlAudit,
|
|
37
43
|
terminateSandboxControlExecution,
|
|
38
44
|
writeSandboxControlStatus,
|
|
39
45
|
readSandboxControlStatus
|
|
40
46
|
} from './state.ts';
|
|
41
47
|
import { validateSandboxControlRequest } from './protocol.ts';
|
|
48
|
+
import {
|
|
49
|
+
requestAuditFields,
|
|
50
|
+
resultAuditFields,
|
|
51
|
+
appendCriticalAudit,
|
|
52
|
+
appendDiagnosticAudit,
|
|
53
|
+
createSandboxControlAuditContext,
|
|
54
|
+
writeSandboxControlTransition,
|
|
55
|
+
readSandboxControlTransition
|
|
56
|
+
} from './audit.ts';
|
|
57
|
+
import { parseTaskControlOperation } from '../../task/control-authority.ts';
|
|
58
|
+
import {
|
|
59
|
+
parseControlOutput,
|
|
60
|
+
classifySandboxControlRecovery,
|
|
61
|
+
findSandboxControlRecoveryOperation,
|
|
62
|
+
operationRecoveryBinding,
|
|
63
|
+
SANDBOX_CONTROL_REQUIRED_COMPLETION_PHASES
|
|
64
|
+
} 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
|
+
import { readLifecycleJournalEvidence } from '../../task/lifecycle.ts';
|
|
70
|
+
import { resolveTaskRef } from '../../task/resolve-ref.ts';
|
|
71
|
+
import { loadShortIdByTaskId } from '../../task/short-id.ts';
|
|
42
72
|
import { inspectSandboxControlContainer, type ContainerObservation } from './container-identity.ts';
|
|
43
73
|
import {
|
|
44
74
|
acquireSandboxControlBrokerStartup,
|
|
@@ -49,7 +79,8 @@ import {
|
|
|
49
79
|
import type { BrokerOwner } from './lifecycle.ts';
|
|
50
80
|
import { nextSandboxControlBackoff } from './timing.ts';
|
|
51
81
|
import { readTaskFinalizationReceipt } from '../../task/finalization.ts';
|
|
52
|
-
import {
|
|
82
|
+
import { readCodexControllerRegistration } from './controller-registration.ts';
|
|
83
|
+
import { validateSandboxControlIdentity } from './identity-sentinel.ts';
|
|
53
84
|
import {
|
|
54
85
|
mergeSandboxTaskView,
|
|
55
86
|
taskViewAfterFinalization,
|
|
@@ -57,22 +88,68 @@ import {
|
|
|
57
88
|
type SandboxTaskView
|
|
58
89
|
} from './task-view.ts';
|
|
59
90
|
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';
|
|
60
99
|
|
|
61
100
|
type ActiveExecution = {
|
|
62
101
|
request: SandboxControlRequest;
|
|
63
102
|
prepared: PreparedSandboxControlExecution;
|
|
103
|
+
lifecycleAuthority: LifecycleAuthorityResponseV1 | undefined;
|
|
64
104
|
result: SandboxControlExecutionResult | null;
|
|
65
105
|
resultEvidenceWritten: boolean;
|
|
66
106
|
failure: unknown;
|
|
67
107
|
settled: boolean;
|
|
68
108
|
};
|
|
69
109
|
|
|
70
|
-
function
|
|
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;
|
|
71
138
|
try {
|
|
72
|
-
|
|
139
|
+
const registration = readCodexControllerRegistration(manifestPath);
|
|
140
|
+
controllerBinding = {
|
|
141
|
+
instanceDigest: registration.controllerInstanceDigest,
|
|
142
|
+
controlGeneration: registration.controlGeneration
|
|
143
|
+
};
|
|
73
144
|
} catch {
|
|
74
|
-
|
|
145
|
+
// The issuer reports the stable unavailable result; the broker never trusts
|
|
146
|
+
// a controller binding supplied by the request.
|
|
75
147
|
}
|
|
148
|
+
return issueLifecycleRecoveryAttestation(selector, {
|
|
149
|
+
controllerBinding,
|
|
150
|
+
capabilityStore: createCodexCapabilityStore(),
|
|
151
|
+
buildIdentity: computeLifecycleBuildIdentity(manifest.repoRoot)
|
|
152
|
+
});
|
|
76
153
|
}
|
|
77
154
|
|
|
78
155
|
function appendBrokerAudit(
|
|
@@ -80,53 +157,72 @@ function appendBrokerAudit(
|
|
|
80
157
|
event: string,
|
|
81
158
|
fields: Record<string, string | number | boolean | null> = {}
|
|
82
159
|
): void {
|
|
160
|
+
appendDiagnosticAudit(manifest, event, { source: 'broker', ...fields });
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function operationKey(request: SandboxControlRequest, output?: string): string | null {
|
|
164
|
+
if (request.family === 'task-finalization') return request.operation;
|
|
165
|
+
if (request.family === 'task-create') return 'create';
|
|
166
|
+
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
|
+
}
|
|
83
174
|
try {
|
|
84
|
-
|
|
175
|
+
const operation = parseTaskControlOperation(request.family, request.args);
|
|
176
|
+
return operation.family === 'task-orchestration' ? operation.intent : operation.request.intent;
|
|
85
177
|
} catch {
|
|
86
|
-
|
|
178
|
+
return null;
|
|
87
179
|
}
|
|
88
180
|
}
|
|
89
181
|
|
|
90
|
-
function
|
|
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;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function terminalResultMatchesRequest(
|
|
198
|
+
request: SandboxControlRequest,
|
|
199
|
+
terminalResult: ReturnType<typeof readSandboxControlTerminalResult>
|
|
200
|
+
): boolean {
|
|
201
|
+
const operation = operationKey(request);
|
|
202
|
+
const candidates = operation === 'route'
|
|
203
|
+
? ['route.read', 'route.clean-completion']
|
|
204
|
+
: operation ? [operation] : [];
|
|
205
|
+
return candidates.some((candidate) => createHash('sha256').update(`${request.family}\0${candidate}`, 'utf8').digest('hex') === terminalResult.intentDigest);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function criticalRequestPhase(
|
|
91
209
|
manifest: SandboxControlManifest,
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
210
|
+
request: SandboxControlRequest,
|
|
211
|
+
phase: Parameters<typeof createSandboxControlAuditContext>[1]['phase'],
|
|
212
|
+
outcome: Parameters<typeof createSandboxControlAuditContext>[1]['outcome'],
|
|
213
|
+
reference?: string | null
|
|
214
|
+
): void {
|
|
215
|
+
const context = createSandboxControlAuditContext(manifest, {
|
|
98
216
|
requestId: request.id,
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
requestTaskRef: args[0] ?? null,
|
|
107
|
-
requestCommand: args[1] ?? null,
|
|
108
|
-
controllerProofPresent: request.controllerProof !== null,
|
|
109
|
-
hostCwd: process.cwd(),
|
|
110
|
-
manifestPath,
|
|
111
|
-
manifestPathRealpath: safeRealpath(manifestPath),
|
|
112
|
-
repoRoot: manifest.repoRoot,
|
|
113
|
-
repoRootRealpath: safeRealpath(manifest.repoRoot),
|
|
114
|
-
worktreeRoot: manifest.worktreeRoot,
|
|
115
|
-
worktreeRootRealpath: safeRealpath(manifest.worktreeRoot),
|
|
116
|
-
runtimeDir: manifest.runtimeDir,
|
|
117
|
-
runtimeDirRealpath: safeRealpath(manifest.runtimeDir)
|
|
118
|
-
};
|
|
217
|
+
family: request.family,
|
|
218
|
+
operation: operationKey(request),
|
|
219
|
+
phase,
|
|
220
|
+
outcome
|
|
221
|
+
});
|
|
222
|
+
appendCriticalAudit(manifest, context, { reference: reference ?? null });
|
|
223
|
+
writeSandboxControlTransition(manifest, { requestId: request.id, phase, reference });
|
|
119
224
|
}
|
|
120
225
|
|
|
121
|
-
function resultAuditFields(result: SandboxControlExecutionResult): Record<string, string | number | boolean | null> {
|
|
122
|
-
return {
|
|
123
|
-
exitCode: result.exitCode,
|
|
124
|
-
stdoutBytes: Buffer.byteLength(result.stdout, 'utf8'),
|
|
125
|
-
stderrBytes: Buffer.byteLength(result.stderr, 'utf8'),
|
|
126
|
-
stdoutSha256: createHash('sha256').update(result.stdout, 'utf8').digest('hex'),
|
|
127
|
-
stderrSha256: createHash('sha256').update(result.stderr, 'utf8').digest('hex')
|
|
128
|
-
};
|
|
129
|
-
}
|
|
130
226
|
|
|
131
227
|
function assertRealDirectory(directory: string, parent?: string): void {
|
|
132
228
|
const stat = fs.lstatSync(directory);
|
|
@@ -212,33 +308,7 @@ function finalizationRecoveryResponse(
|
|
|
212
308
|
} catch {
|
|
213
309
|
return { status: 'deferred' };
|
|
214
310
|
}
|
|
215
|
-
|
|
216
|
-
receipt.taskComment === 'pending' ? 'task-comment' : null,
|
|
217
|
-
receipt.verification === 'pending' ? 'verification' : null
|
|
218
|
-
].filter((step): step is string => step !== null);
|
|
219
|
-
const completedSteps = ['lifecycle', receipt.taskComment === 'pending' ? null : 'task-comment', receipt.verification === 'pending' ? null : 'verification']
|
|
220
|
-
.filter((step): step is string => step !== null);
|
|
221
|
-
const warnings = receipt.warnings
|
|
222
|
-
.filter((warning) => warning.status === 'open')
|
|
223
|
-
.map(({ status: _status, resolvedAt: _resolvedAt, ...warning }) => warning);
|
|
224
|
-
const result = {
|
|
225
|
-
status: 'completed', changed: false, taskId: manifest.taskId,
|
|
226
|
-
lifecycle: { status: 'no-op', changed: false, error: null },
|
|
227
|
-
taskComment: receipt.taskComment === 'pending' ? null : { status: 'no-op', changed: false, error: null },
|
|
228
|
-
verification: receipt.verification === 'pending' ? null : { status: 'no-op', changed: false, error: null },
|
|
229
|
-
completedSteps, pendingSteps,
|
|
230
|
-
result: pendingSteps.length > 0 || receipt.warningProjection === 'pending' || warnings.length > 0
|
|
231
|
-
? 'completed_with_warnings' : 'completed',
|
|
232
|
-
warnings, error: null
|
|
233
|
-
};
|
|
234
|
-
return {
|
|
235
|
-
status: 'matched',
|
|
236
|
-
response: {
|
|
237
|
-
version: 2, id: requestId, phase: 'completed', exitCode: 0,
|
|
238
|
-
stdout: `${JSON.stringify({ version: 1, status: 'completed', changed: false, accepted: true, result, error: null })}\n`,
|
|
239
|
-
stderr: '', error: null
|
|
240
|
-
}
|
|
241
|
-
};
|
|
311
|
+
return { status: 'matched', response: finalizationTerminalResponse(manifest.taskId, requestId, receipt) };
|
|
242
312
|
}
|
|
243
313
|
|
|
244
314
|
function publishFinalizationTaskView(
|
|
@@ -300,11 +370,12 @@ function outputMatchesEvidence(output: string, bytes: number, sha256: string): b
|
|
|
300
370
|
&& createHash('sha256').update(output, 'utf8').digest('hex') === sha256;
|
|
301
371
|
}
|
|
302
372
|
|
|
303
|
-
function genericRecoveryResponse(
|
|
373
|
+
export function genericRecoveryResponse(
|
|
304
374
|
request: SandboxControlRequest,
|
|
305
375
|
exitCode: number,
|
|
306
376
|
payload: ReturnType<typeof readSandboxControlPayload> | null,
|
|
307
|
-
cause: 'publish' | 'recovery' = 'recovery'
|
|
377
|
+
cause: 'publish' | 'recovery' = 'recovery',
|
|
378
|
+
warning: SandboxControlRecoveryWarning | null = null
|
|
308
379
|
): SandboxControlResponse {
|
|
309
380
|
const outputUnavailable = request.family === 'task-create' && !payload;
|
|
310
381
|
return {
|
|
@@ -313,38 +384,295 @@ function genericRecoveryResponse(
|
|
|
313
384
|
phase: 'completed',
|
|
314
385
|
exitCode,
|
|
315
386
|
stdout: outputUnavailable ? `${JSON.stringify(taskCreateOutputUnavailableResult(request.id))}\n` : '',
|
|
316
|
-
stderr: payload ? '' :
|
|
317
|
-
?
|
|
318
|
-
:
|
|
387
|
+
stderr: payload ? '' : warning
|
|
388
|
+
? `${warning.code}: ${warning.message}\nAction: ${warning.action}\n`
|
|
389
|
+
: cause === 'publish'
|
|
390
|
+
? 'SANDBOX_CONTROL_OUTPUT_UNAVAILABLE: output payload was not retained\n'
|
|
391
|
+
: 'SANDBOX_CONTROL_OUTPUT_UNAVAILABLE: broker restarted after executor completion\n',
|
|
319
392
|
error: null,
|
|
320
393
|
outputState: payload ? 'available' : 'unavailable',
|
|
321
394
|
payload: payload ? payloadReference(payload) : null
|
|
322
395
|
};
|
|
323
396
|
}
|
|
324
397
|
|
|
325
|
-
|
|
398
|
+
type RecoveryDomainEvidence = Readonly<{
|
|
399
|
+
domain: Readonly<Record<string, unknown>> | null;
|
|
400
|
+
journal?: ReturnType<typeof readLifecycleJournalEvidence>;
|
|
401
|
+
}>;
|
|
402
|
+
|
|
403
|
+
function taskCreateDomainEvidence(
|
|
404
|
+
manifest: SandboxControlManifest,
|
|
405
|
+
output: Record<string, unknown> | null
|
|
406
|
+
): Readonly<Record<string, unknown>> {
|
|
407
|
+
if (!output || !['applied', 'no-op', 'degraded'].includes(String(output.status))) {
|
|
408
|
+
return { consistent: false };
|
|
409
|
+
}
|
|
410
|
+
const task = output.task && typeof output.task === 'object' && !Array.isArray(output.task)
|
|
411
|
+
? output.task as Record<string, unknown> : null;
|
|
412
|
+
const taskId = typeof task?.id === 'string' ? task.id : null;
|
|
413
|
+
const shortId = typeof task?.shortId === 'string' ? task.shortId : null;
|
|
414
|
+
if (!taskId || !shortId) return { consistent: false };
|
|
415
|
+
try {
|
|
416
|
+
const resolved = resolveTaskRef(taskId, { repoRoot: manifest.repoRoot });
|
|
417
|
+
const shortIds = loadShortIdByTaskId(manifest.repoRoot);
|
|
418
|
+
return {
|
|
419
|
+
consistent: resolved.ok && resolved.taskId === taskId && resolved.state === 'active' && shortIds.get(taskId) === shortId,
|
|
420
|
+
taskId,
|
|
421
|
+
shortId
|
|
422
|
+
};
|
|
423
|
+
} catch {
|
|
424
|
+
return { consistent: false };
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
function controllerDomainEvidence(
|
|
429
|
+
manifest: SandboxControlManifest,
|
|
430
|
+
manifestPath: string,
|
|
431
|
+
request: SandboxControlRequest,
|
|
432
|
+
output: Record<string, unknown> | null
|
|
433
|
+
): Readonly<Record<string, unknown>> {
|
|
434
|
+
if (!output || output.error !== null || typeof output.status !== 'string') return { consistent: false };
|
|
435
|
+
try {
|
|
436
|
+
const registration = readCodexControllerRegistration(manifestPath);
|
|
437
|
+
if (request.family !== 'codex-controller') return { consistent: false };
|
|
438
|
+
if (request.command === 'open') {
|
|
439
|
+
const lease = output.lease && typeof output.lease === 'object' && !Array.isArray(output.lease)
|
|
440
|
+
? output.lease as Record<string, unknown> : null;
|
|
441
|
+
return {
|
|
442
|
+
consistent: output.status === 'opened' && output.changed === true
|
|
443
|
+
&& lease?.taskId === registration.taskId
|
|
444
|
+
&& lease.controlGeneration === registration.controlGeneration
|
|
445
|
+
&& lease.controllerInstanceDigest === registration.controllerInstanceDigest
|
|
446
|
+
&& registration.taskId === manifest.taskId
|
|
447
|
+
&& registration.controlGeneration === manifest.generation
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
if (request.command === 'verify') {
|
|
451
|
+
const binding = output.binding && typeof output.binding === 'object' && !Array.isArray(output.binding)
|
|
452
|
+
? output.binding as Record<string, unknown> : null;
|
|
453
|
+
return {
|
|
454
|
+
consistent: output.status === 'verified' && output.changed === false
|
|
455
|
+
&& binding?.taskId === registration.taskId
|
|
456
|
+
&& binding.controlGeneration === registration.controlGeneration
|
|
457
|
+
&& binding.controllerInstanceDigest === registration.controllerInstanceDigest
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
return { consistent: false };
|
|
461
|
+
} catch (error) {
|
|
462
|
+
return { consistent: request.family === 'codex-controller' && request.command === 'close'
|
|
463
|
+
&& (error as { code?: string }).code === 'CODEX_SANDBOX_CONTROLLER_REGISTRATION_MISSING'
|
|
464
|
+
&& output?.status === 'closed' && typeof output.changed === 'boolean' };
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
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(
|
|
326
495
|
manifest: SandboxControlManifest,
|
|
496
|
+
manifestPath: string,
|
|
497
|
+
request: SandboxControlRequest,
|
|
498
|
+
operation: ReturnType<typeof findSandboxControlRecoveryOperation>,
|
|
499
|
+
terminalResult: ReturnType<typeof readSandboxControlTerminalResult>,
|
|
500
|
+
payloadOutput: string | null
|
|
501
|
+
): RecoveryDomainEvidence {
|
|
502
|
+
if (!operation) return { domain: null };
|
|
503
|
+
const taskRef = request.family === 'task-finalization' || request.family === 'task-workflow'
|
|
504
|
+
? manifest.taskId
|
|
505
|
+
: 'args' in request ? request.args[0] ?? null : manifest.taskId;
|
|
506
|
+
const output = parseControlOutput(payloadOutput);
|
|
507
|
+
if (!taskRef && operation.family !== 'task-create' && operation.family !== 'codex-controller') {
|
|
508
|
+
return { domain: null };
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
if (operation.family === 'task-lifecycle') {
|
|
512
|
+
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
|
+
}
|
|
526
|
+
}
|
|
527
|
+
if (!taskRef || !terminalResult.targetState) return { domain: null };
|
|
528
|
+
const journal = readLifecycleJournalEvidence(manifest.repoRoot, taskRef);
|
|
529
|
+
try {
|
|
530
|
+
const resolved = resolveTaskRef(taskRef, { repoRoot: manifest.repoRoot });
|
|
531
|
+
const shortIds = loadShortIdByTaskId(manifest.repoRoot);
|
|
532
|
+
const shortIdMatches = terminalResult.targetState === 'active'
|
|
533
|
+
? shortIds.has(resolved.ok ? resolved.taskId : taskRef)
|
|
534
|
+
: !shortIds.has(resolved.ok ? resolved.taskId : taskRef);
|
|
535
|
+
return {
|
|
536
|
+
domain: { consistent: resolved.ok && resolved.state === terminalResult.targetState && shortIdMatches },
|
|
537
|
+
journal
|
|
538
|
+
};
|
|
539
|
+
} catch {
|
|
540
|
+
return { domain: { consistent: false }, journal };
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
|
|
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
|
+
if (operation.family === 'task-create') {
|
|
576
|
+
return { domain: taskCreateDomainEvidence(manifest, output) };
|
|
577
|
+
}
|
|
578
|
+
if (operation.family === 'codex-controller') {
|
|
579
|
+
return { domain: controllerDomainEvidence(manifest, manifestPath, request, output) };
|
|
580
|
+
}
|
|
581
|
+
if (operation.family === 'task-workflow') {
|
|
582
|
+
return {
|
|
583
|
+
domain: { consistent: output?.status === terminalResult.status && output.changed === terminalResult.changed, snapshotValid: true }
|
|
584
|
+
};
|
|
585
|
+
}
|
|
586
|
+
return { domain: null };
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
function readCommittedCriticalPhases(manifest: SandboxControlManifest, requestId: string): readonly string[] {
|
|
590
|
+
return SANDBOX_CONTROL_REQUIRED_COMPLETION_PHASES.filter((phase) => {
|
|
591
|
+
try {
|
|
592
|
+
readSandboxControlTransition(manifest, requestId, phase);
|
|
593
|
+
return true;
|
|
594
|
+
} catch {
|
|
595
|
+
return false;
|
|
596
|
+
}
|
|
597
|
+
});
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
export function recoveryResponse(
|
|
601
|
+
manifest: SandboxControlManifest,
|
|
602
|
+
manifestPath: string,
|
|
327
603
|
request: SandboxControlRequest,
|
|
328
604
|
evidence: ReturnType<typeof readSandboxControlResultEvidence>,
|
|
329
|
-
payload: ReturnType<typeof readSandboxControlPayload> | null
|
|
605
|
+
payload: ReturnType<typeof readSandboxControlPayload> | null,
|
|
606
|
+
terminalResult: ReturnType<typeof readSandboxControlTerminalResult>
|
|
330
607
|
): SandboxControlResponse | null {
|
|
608
|
+
const operationName = recoveryOperationKey(request, terminalResult, payload?.stdout);
|
|
609
|
+
const operation = operationName ? findSandboxControlRecoveryOperation(request.family, operationName) : null;
|
|
610
|
+
if (!operation) return null;
|
|
611
|
+
if (payload) {
|
|
612
|
+
const payloadTerminal = createSandboxControlTerminalResult(manifest, {
|
|
613
|
+
id: request.id, family: request.family, operation: operationName
|
|
614
|
+
}, payload.stdout);
|
|
615
|
+
if (JSON.stringify(payloadTerminal) !== JSON.stringify(terminalResult)) return unknown(request.id);
|
|
616
|
+
}
|
|
617
|
+
const finalization = request.family === 'task-finalization' ? finalizationRecoveryResponse(manifest, request.id, 0) : null;
|
|
618
|
+
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
|
+
const recovery: RecoveryDomainEvidence = finalization
|
|
641
|
+
? { domain: { consistent: finalization.status === 'matched' } }
|
|
642
|
+
: readRecoveryDomain(manifest, manifestPath, request, operation, terminalResult, payload?.stdout ?? null);
|
|
643
|
+
const binding = operationRecoveryBinding(request.id, manifest.generation, manifest.taskId, request.family, operationName!);
|
|
644
|
+
const decision = classifySandboxControlRecovery({
|
|
645
|
+
operation,
|
|
646
|
+
binding,
|
|
647
|
+
startedCommitted: true,
|
|
648
|
+
terminalResult,
|
|
649
|
+
criticalPhases: readCommittedCriticalPhases(manifest, request.id),
|
|
650
|
+
domain: recovery.domain,
|
|
651
|
+
journal: recovery.journal
|
|
652
|
+
});
|
|
653
|
+
if (decision.outcome === 'unknown' || decision.outcome === 'in-progress') return unknown(request.id);
|
|
654
|
+
if (decision.outcome === 'not-executed') return notExecuted(request.id);
|
|
655
|
+
if (decision.outcome !== 'success' && decision.outcome !== 'failure') return null;
|
|
331
656
|
if (request.family === 'task-finalization' && evidence.exitCode === 0) {
|
|
332
|
-
|
|
333
|
-
return finalization.
|
|
657
|
+
if (finalization?.status !== 'matched') return null;
|
|
658
|
+
return finalization.response ?? null;
|
|
334
659
|
}
|
|
335
|
-
return genericRecoveryResponse(request, evidence.exitCode, payload);
|
|
660
|
+
return genericRecoveryResponse(request, evidence.exitCode, payload, 'recovery', terminalResult.warning ?? null);
|
|
336
661
|
}
|
|
337
662
|
|
|
338
663
|
function terminalMatchesEvidence(
|
|
339
664
|
manifest: SandboxControlManifest,
|
|
665
|
+
manifestPath: string,
|
|
340
666
|
request: SandboxControlRequest,
|
|
341
667
|
response: SandboxControlResponse,
|
|
342
668
|
evidence: ReturnType<typeof readSandboxControlResultEvidence>,
|
|
343
669
|
payload: ReturnType<typeof readSandboxControlPayload> | null,
|
|
344
|
-
payloadInvalid: boolean
|
|
670
|
+
payloadInvalid: boolean,
|
|
671
|
+
terminalResult: ReturnType<typeof readSandboxControlTerminalResult> | null
|
|
345
672
|
): { valid: boolean; payloadReferenced: boolean } {
|
|
346
673
|
if (request.family === 'task-finalization' && evidence.exitCode === 0) {
|
|
347
|
-
|
|
674
|
+
if (!terminalResult) return { valid: false, payloadReferenced: false };
|
|
675
|
+
const expected = recoveryResponse(manifest, manifestPath, request, evidence, payload, terminalResult);
|
|
348
676
|
return {
|
|
349
677
|
valid: expected !== null && JSON.stringify(response) === JSON.stringify(expected),
|
|
350
678
|
payloadReferenced: false
|
|
@@ -373,7 +701,7 @@ function terminalMatchesEvidence(
|
|
|
373
701
|
if (response.outputState === 'unavailable') {
|
|
374
702
|
const causes = ['recovery', 'publish'] as const;
|
|
375
703
|
const valid = causes.some((cause) => JSON.stringify(response)
|
|
376
|
-
=== JSON.stringify(genericRecoveryResponse(request, evidence.exitCode, null, cause)));
|
|
704
|
+
=== JSON.stringify(genericRecoveryResponse(request, evidence.exitCode, null, cause, terminalResult?.warning ?? null)));
|
|
377
705
|
return { valid, payloadReferenced: false };
|
|
378
706
|
}
|
|
379
707
|
return {
|
|
@@ -392,6 +720,16 @@ function publishExecutionResult(
|
|
|
392
720
|
brokerOwns: () => boolean
|
|
393
721
|
): boolean {
|
|
394
722
|
const normalized = sanitizeSandboxControlResult(manifest, result);
|
|
723
|
+
const terminalResult = writeSandboxControlTerminalResult(manifest, {
|
|
724
|
+
id: request.id,
|
|
725
|
+
family: request.family,
|
|
726
|
+
operation: operationKey(request, normalized.stdout)
|
|
727
|
+
}, normalized.stdout);
|
|
728
|
+
if (fs.existsSync(path.join(manifest.processingDir, request.id, 'transitions'))) {
|
|
729
|
+
criticalRequestPhase(manifest, request, 'completed', normalized.exitCode === 0 ? 'success' : 'failure');
|
|
730
|
+
criticalRequestPhase(manifest, request, 'evidence-written', normalized.exitCode === 0 ? 'success' : 'failure');
|
|
731
|
+
criticalRequestPhase(manifest, request, 'publish-authorized', normalized.exitCode === 0 ? 'success' : 'failure');
|
|
732
|
+
}
|
|
395
733
|
let terminal: SandboxControlResponse | null = null;
|
|
396
734
|
if (request.family === 'task-finalization') {
|
|
397
735
|
const finalization = finalizationRecoveryResponse(manifest, request.id, normalized.exitCode);
|
|
@@ -419,7 +757,7 @@ function publishExecutionResult(
|
|
|
419
757
|
} catch {
|
|
420
758
|
payload = null;
|
|
421
759
|
}
|
|
422
|
-
terminal = genericRecoveryResponse(request, normalized.exitCode, payload, 'publish');
|
|
760
|
+
terminal = genericRecoveryResponse(request, normalized.exitCode, payload, 'publish', terminalResult.warning ?? null);
|
|
423
761
|
}
|
|
424
762
|
}
|
|
425
763
|
if (!brokerOwns()) return false;
|
|
@@ -427,7 +765,11 @@ function publishExecutionResult(
|
|
|
427
765
|
const view = publishFinalizationTaskView(manifest, broker, request.id, 'healthy', null, null);
|
|
428
766
|
if (view.state === 'unknown') return false;
|
|
429
767
|
}
|
|
430
|
-
|
|
768
|
+
const committed = writeSandboxControlResponse(manifest, terminal);
|
|
769
|
+
if (committed && fs.existsSync(path.join(manifest.processingDir, request.id, 'transitions'))) {
|
|
770
|
+
criticalRequestPhase(manifest, request, 'published-committed', normalized.exitCode === 0 ? 'success' : 'failure');
|
|
771
|
+
}
|
|
772
|
+
return committed;
|
|
431
773
|
}
|
|
432
774
|
|
|
433
775
|
function writeAcceptedResponse(manifest: SandboxControlManifest, response: SandboxControlResponse): void {
|
|
@@ -492,21 +834,40 @@ function bindingReason(manifest: SandboxControlManifest): string | null {
|
|
|
492
834
|
}
|
|
493
835
|
}
|
|
494
836
|
|
|
495
|
-
function
|
|
837
|
+
function assertCurrentSandboxControlIdentity(manifest: SandboxControlManifest, manifestPath: string): void {
|
|
838
|
+
const result = validateSandboxControlIdentity({
|
|
839
|
+
publicStatusDir: manifest.publicStatusDir,
|
|
840
|
+
root: path.dirname(path.resolve(manifestPath)),
|
|
841
|
+
mode: manifest.mode,
|
|
842
|
+
taskId: manifest.taskId,
|
|
843
|
+
generation: manifest.generation,
|
|
844
|
+
controlRootId: manifest.controlRootId
|
|
845
|
+
});
|
|
846
|
+
if (result.state !== 'valid') {
|
|
847
|
+
throw new Error(`SANDBOX_CONTROL_IDENTITY_${result.state.replaceAll('-', '_').toUpperCase()}`);
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
function recoverProcessing(manifest: SandboxControlManifest, manifestPath: string, broker: BrokerOwner, brokerOwns: () => boolean): boolean {
|
|
496
852
|
for (const entry of fs.readdirSync(manifest.processingDir, { withFileTypes: true })) {
|
|
497
853
|
if (!entry.isDirectory() || !/^[a-f0-9-]{16,64}$/.test(entry.name)) continue;
|
|
498
854
|
if (!brokerOwns()) return false;
|
|
499
855
|
const descriptor = executionPath(manifest, entry.name);
|
|
500
856
|
const existingResponse = responsePath(manifest, entry.name);
|
|
501
857
|
const processingDirectory = path.join(manifest.processingDir, entry.name);
|
|
858
|
+
const transitionsDirectory = path.join(processingDirectory, 'transitions');
|
|
859
|
+
const transitionProtocolActive = fs.existsSync(transitionsDirectory);
|
|
860
|
+
const startedCommitted = fs.existsSync(path.join(transitionsDirectory, 'started-committed.json'));
|
|
502
861
|
let payloadReferenced = false;
|
|
503
862
|
if (fs.existsSync(descriptor)) {
|
|
504
863
|
const execution = readExecution(descriptor);
|
|
505
864
|
let resultEvidence: ReturnType<typeof readSandboxControlResultEvidence> | null = null;
|
|
865
|
+
let terminalResult: ReturnType<typeof readSandboxControlTerminalResult> | null = null;
|
|
506
866
|
let payload: ReturnType<typeof readSandboxControlPayload> | null = null;
|
|
507
867
|
let payloadInvalid = false;
|
|
508
868
|
let request: SandboxControlRequest | null = null;
|
|
509
869
|
let existingTerminalResponse: SandboxControlResponse | null = null;
|
|
870
|
+
let terminalReconciled = false;
|
|
510
871
|
try {
|
|
511
872
|
const rawRequest = JSON.parse(fs.readFileSync(path.join(processingDirectory, 'request.json'), 'utf8')) as Record<string, unknown>;
|
|
512
873
|
request = validateSandboxControlRequest(rawRequest, manifest, {
|
|
@@ -526,6 +887,18 @@ function recoverProcessing(manifest: SandboxControlManifest, broker: BrokerOwner
|
|
|
526
887
|
resultEvidence = null;
|
|
527
888
|
}
|
|
528
889
|
}
|
|
890
|
+
const terminalResultPathValue = path.join(processingDirectory, 'terminal-result.json');
|
|
891
|
+
if (fs.existsSync(terminalResultPathValue)) {
|
|
892
|
+
try {
|
|
893
|
+
terminalResult = readSandboxControlTerminalResult(terminalResultPathValue);
|
|
894
|
+
if (terminalResult.requestId !== entry.name || terminalResult.generation !== manifest.generation
|
|
895
|
+
|| !request || !terminalResultMatchesRequest(request, terminalResult)) {
|
|
896
|
+
terminalResult = null;
|
|
897
|
+
}
|
|
898
|
+
} catch {
|
|
899
|
+
terminalResult = null;
|
|
900
|
+
}
|
|
901
|
+
}
|
|
529
902
|
const publishedPayloadPath = payloadPath(manifest, entry.name);
|
|
530
903
|
if (fs.existsSync(publishedPayloadPath)) {
|
|
531
904
|
try {
|
|
@@ -538,11 +911,21 @@ function recoverProcessing(manifest: SandboxControlManifest, broker: BrokerOwner
|
|
|
538
911
|
payloadInvalid = true;
|
|
539
912
|
}
|
|
540
913
|
}
|
|
914
|
+
if (transitionProtocolActive && startedCommitted && !terminalResult) {
|
|
915
|
+
if (!brokerOwns()) return false;
|
|
916
|
+
if (!terminateSandboxControlExecution(execution)) {
|
|
917
|
+
throw new Error(`SANDBOX_CONTROL_EXECUTION_STILL_RUNNING: ${entry.name}`);
|
|
918
|
+
}
|
|
919
|
+
appendDiagnosticAudit(manifest, 'orphan-tree-terminated', { requestId: entry.name });
|
|
920
|
+
writeSandboxControlResponse(manifest, unknown(entry.name));
|
|
921
|
+
continue;
|
|
922
|
+
}
|
|
923
|
+
if (transitionProtocolActive && !startedCommitted && terminalResult) continue;
|
|
541
924
|
if (!terminateSandboxControlExecution(execution)) {
|
|
542
925
|
throw new Error(`SANDBOX_CONTROL_EXECUTION_STILL_RUNNING: ${entry.name}`);
|
|
543
926
|
}
|
|
544
927
|
if (!brokerOwns()) return false;
|
|
545
|
-
|
|
928
|
+
appendDiagnosticAudit(manifest, 'orphan-tree-terminated', { requestId: entry.name });
|
|
546
929
|
let terminal = false;
|
|
547
930
|
if (fs.existsSync(existingResponse)) {
|
|
548
931
|
try {
|
|
@@ -571,28 +954,61 @@ function recoverProcessing(manifest: SandboxControlManifest, broker: BrokerOwner
|
|
|
571
954
|
}
|
|
572
955
|
if (terminal && (!request || !resultEvidence)) continue;
|
|
573
956
|
if (terminal && request && resultEvidence) {
|
|
574
|
-
|
|
957
|
+
if (!terminalResult && request.family !== 'task-create') continue;
|
|
958
|
+
const reconciliation = terminalMatchesEvidence(
|
|
959
|
+
manifest,
|
|
960
|
+
manifestPath,
|
|
961
|
+
request,
|
|
962
|
+
existingTerminalResponse!,
|
|
963
|
+
resultEvidence,
|
|
964
|
+
payload,
|
|
965
|
+
payloadInvalid,
|
|
966
|
+
terminalResult
|
|
967
|
+
);
|
|
575
968
|
if (!reconciliation.valid) continue;
|
|
969
|
+
terminalReconciled = true;
|
|
576
970
|
payloadReferenced = reconciliation.payloadReferenced;
|
|
577
971
|
if (request.family === 'task-finalization' && resultEvidence.exitCode === 0) {
|
|
578
972
|
const view = publishFinalizationTaskView(manifest, broker, request.id, 'starting', null, null);
|
|
579
973
|
if (view.state === 'unknown') continue;
|
|
580
974
|
}
|
|
581
975
|
}
|
|
976
|
+
if (!terminalResult && !terminalReconciled) {
|
|
977
|
+
if (terminal) continue;
|
|
978
|
+
if (!brokerOwns()) return false;
|
|
979
|
+
if (!resultEvidence || !request || payloadInvalid) continue;
|
|
980
|
+
if (request.family === 'task-create' && resultEvidence.exitCode === 0 && payload === null) {
|
|
981
|
+
writeSandboxControlResponse(manifest, genericRecoveryResponse(request, resultEvidence.exitCode, null));
|
|
982
|
+
terminalReconciled = true;
|
|
983
|
+
terminal = true;
|
|
984
|
+
} else {
|
|
985
|
+
writeSandboxControlResponse(manifest, unknown(entry.name));
|
|
986
|
+
continue;
|
|
987
|
+
}
|
|
988
|
+
}
|
|
582
989
|
if (!terminal) {
|
|
583
990
|
if (!brokerOwns()) return false;
|
|
584
991
|
if (!resultEvidence || !request || payloadInvalid) continue;
|
|
585
|
-
const recovered = recoveryResponse(manifest, request, resultEvidence, payload);
|
|
992
|
+
const recovered = recoveryResponse(manifest, manifestPath, request, resultEvidence, payload, terminalResult!);
|
|
586
993
|
if (!recovered) continue;
|
|
587
994
|
if (request.family === 'task-finalization' && resultEvidence.exitCode === 0) {
|
|
588
995
|
const view = publishFinalizationTaskView(manifest, broker, request.id, 'starting', null, null);
|
|
589
996
|
if (view.state === 'unknown') continue;
|
|
590
997
|
}
|
|
591
998
|
writeSandboxControlResponse(manifest, recovered);
|
|
999
|
+
const preserveRecoveryEvidence = recovered.error?.code === 'SANDBOX_CONTROL_RESULT_UNKNOWN';
|
|
1000
|
+
if (transitionProtocolActive && !preserveRecoveryEvidence) {
|
|
1001
|
+
writeSandboxControlTransition(manifest, { requestId: entry.name, phase: 'recovered' });
|
|
1002
|
+
}
|
|
1003
|
+
if (preserveRecoveryEvidence) continue;
|
|
592
1004
|
payloadReferenced = Boolean(payload) && request.family !== 'task-finalization';
|
|
593
1005
|
}
|
|
594
1006
|
} else {
|
|
595
1007
|
if (!brokerOwns()) return false;
|
|
1008
|
+
if (transitionProtocolActive && startedCommitted) {
|
|
1009
|
+
writeSandboxControlResponse(manifest, unknown(entry.name));
|
|
1010
|
+
continue;
|
|
1011
|
+
}
|
|
596
1012
|
writeSandboxControlResponse(manifest, notExecuted(entry.name));
|
|
597
1013
|
}
|
|
598
1014
|
if (!brokerOwns()) return false;
|
|
@@ -634,6 +1050,7 @@ export async function serveSandboxControl(
|
|
|
634
1050
|
const prepareExecution = options.prepareExecution ?? prepareSandboxControlExecution;
|
|
635
1051
|
const manifest = readSandboxControlManifest(manifestPath);
|
|
636
1052
|
const root = path.dirname(manifestPath);
|
|
1053
|
+
assertCurrentSandboxControlIdentity(manifest, manifestPath);
|
|
637
1054
|
const startTime = getProcessStartTime(process.pid);
|
|
638
1055
|
if (!startTime) throw new Error('SANDBOX_CONTROL_BROKER_IDENTITY_UNAVAILABLE');
|
|
639
1056
|
const brokerId = randomUUID();
|
|
@@ -721,22 +1138,9 @@ export async function serveSandboxControl(
|
|
|
721
1138
|
if (!brokerOwns()) return;
|
|
722
1139
|
appendBrokerAudit(manifest, 'broker-start', {
|
|
723
1140
|
pid: broker.pid,
|
|
724
|
-
brokerId: broker.brokerId
|
|
725
|
-
hostCwd: process.cwd(),
|
|
726
|
-
manifestPath,
|
|
727
|
-
manifestPathRealpath: safeRealpath(manifestPath),
|
|
728
|
-
repoRoot: manifest.repoRoot,
|
|
729
|
-
repoRootRealpath: safeRealpath(manifest.repoRoot),
|
|
730
|
-
worktreeRoot: manifest.worktreeRoot,
|
|
731
|
-
worktreeRootRealpath: safeRealpath(manifest.worktreeRoot),
|
|
732
|
-
runtimeDir: manifest.runtimeDir,
|
|
733
|
-
runtimeDirRealpath: safeRealpath(manifest.runtimeDir),
|
|
734
|
-
channelDir: manifest.channelDir,
|
|
735
|
-
publicStatusDir: manifest.publicStatusDir,
|
|
736
|
-
processingDir: manifest.processingDir,
|
|
737
|
-
internalCliPath: options.internalCliPath ?? process.argv[1] ?? null
|
|
1141
|
+
brokerId: broker.brokerId
|
|
738
1142
|
});
|
|
739
|
-
if (!recoverProcessing(manifest, broker, brokerOwns)) return;
|
|
1143
|
+
if (!recoverProcessing(manifest, manifestPath, broker, brokerOwns)) return;
|
|
740
1144
|
try {
|
|
741
1145
|
const published = readSandboxControlStatus(manifest.publicStatusDir);
|
|
742
1146
|
if (published.generation === manifest.generation) taskView = published.taskView;
|
|
@@ -749,6 +1153,7 @@ export async function serveSandboxControl(
|
|
|
749
1153
|
let current: SandboxControlManifest;
|
|
750
1154
|
try {
|
|
751
1155
|
current = readSandboxControlManifest(manifestPath);
|
|
1156
|
+
assertCurrentSandboxControlIdentity(current, manifestPath);
|
|
752
1157
|
} catch {
|
|
753
1158
|
break;
|
|
754
1159
|
}
|
|
@@ -779,7 +1184,7 @@ export async function serveSandboxControl(
|
|
|
779
1184
|
nextContainerHeartbeatAt = Date.now() + timing.containerHeartbeatMs;
|
|
780
1185
|
} else if (observation.state === 'unknown') {
|
|
781
1186
|
containerReasonCode = 'SANDBOX_CONTROL_CONTAINER_UNKNOWN';
|
|
782
|
-
|
|
1187
|
+
appendDiagnosticAudit(manifest, 'container-heartbeat-unknown', { reason: observation.reason });
|
|
783
1188
|
const backoff = nextSandboxControlBackoff(containerBackoffMs, timing.slowCheckMs);
|
|
784
1189
|
nextContainerHeartbeatAt = Date.now() + backoff.delayMs;
|
|
785
1190
|
containerBackoffMs = backoff.nextDelayMs;
|
|
@@ -795,7 +1200,7 @@ export async function serveSandboxControl(
|
|
|
795
1200
|
} catch (error) {
|
|
796
1201
|
if (!brokerOwns()) break;
|
|
797
1202
|
containerReasonCode = 'SANDBOX_CONTROL_CONTAINER_ABSENT_GC_FAILED';
|
|
798
|
-
|
|
1203
|
+
appendDiagnosticAudit(manifest, 'container-gc-failed', { reason: controlError(error).code });
|
|
799
1204
|
const backoff = nextSandboxControlBackoff(containerBackoffMs, timing.slowCheckMs);
|
|
800
1205
|
nextContainerHeartbeatAt = Date.now() + backoff.delayMs;
|
|
801
1206
|
containerBackoffMs = backoff.nextDelayMs;
|
|
@@ -858,6 +1263,10 @@ export async function serveSandboxControl(
|
|
|
858
1263
|
if (!brokerOwns()) break;
|
|
859
1264
|
let terminalCommitted = false;
|
|
860
1265
|
if (settledExecution.result && settledExecution.resultEvidenceWritten) {
|
|
1266
|
+
if (!consumeLifecycleAuthorityForResult(manifest, settledExecution)) {
|
|
1267
|
+
active = settledExecution;
|
|
1268
|
+
continue;
|
|
1269
|
+
}
|
|
861
1270
|
terminalCommitted = publishExecutionResult(manifest, settledExecution.request, settledExecution.result, broker, brokerOwns);
|
|
862
1271
|
if (terminalCommitted && settledExecution.request.family === 'task-finalization') {
|
|
863
1272
|
try {
|
|
@@ -872,7 +1281,7 @@ export async function serveSandboxControl(
|
|
|
872
1281
|
}
|
|
873
1282
|
if (!terminalCommitted) return;
|
|
874
1283
|
appendBrokerAudit(manifest, 'executor-result-published', {
|
|
875
|
-
...requestAuditFields(manifest,
|
|
1284
|
+
...requestAuditFields(manifest, settledExecution.request),
|
|
876
1285
|
resultAvailable: settledExecution.result !== null,
|
|
877
1286
|
resultEvidenceWritten: settledExecution.resultEvidenceWritten,
|
|
878
1287
|
...(settledExecution.result ? resultAuditFields(settledExecution.result) : {})
|
|
@@ -889,6 +1298,7 @@ export async function serveSandboxControl(
|
|
|
889
1298
|
const source = path.join(requestsDir, name);
|
|
890
1299
|
let claimed: string | null = null;
|
|
891
1300
|
let validatedRequest: SandboxControlRequest | null = null;
|
|
1301
|
+
let prepared: PreparedSandboxControlExecution | null = null;
|
|
892
1302
|
try {
|
|
893
1303
|
if (!brokerOwns()) {
|
|
894
1304
|
retiring = true;
|
|
@@ -914,69 +1324,84 @@ export async function serveSandboxControl(
|
|
|
914
1324
|
if (active) throw new Error('SANDBOX_CONTROL_BUSY');
|
|
915
1325
|
const request = validateSandboxControlRequest(JSON.parse(fs.readFileSync(claimed, 'utf8')), manifest);
|
|
916
1326
|
validatedRequest = request;
|
|
1327
|
+
criticalRequestPhase(manifest, request, 'validated', 'in-progress');
|
|
917
1328
|
appendBrokerAudit(manifest, 'request-validated', {
|
|
918
|
-
...requestAuditFields(manifest,
|
|
1329
|
+
...requestAuditFields(manifest, request),
|
|
919
1330
|
requestPath: claimed
|
|
920
1331
|
});
|
|
921
1332
|
if (bindingCheck(manifest)) throw new Error('SANDBOX_WORKTREE_BINDING_LOST');
|
|
922
1333
|
if (readActiveLease(manifest)) throw new Error('SANDBOX_CONTROL_HANDOFF_ACTIVE');
|
|
1334
|
+
criticalRequestPhase(manifest, request, 'gated', 'in-progress');
|
|
923
1335
|
appendBrokerAudit(manifest, 'request-gates-passed', {
|
|
924
|
-
...requestAuditFields(manifest,
|
|
1336
|
+
...requestAuditFields(manifest, request),
|
|
925
1337
|
requestPath: claimed,
|
|
926
1338
|
bindingChecked: true,
|
|
927
1339
|
handoffLeaseChecked: true
|
|
928
1340
|
});
|
|
1341
|
+
criticalRequestPhase(manifest, request, 'reserved', 'in-progress');
|
|
929
1342
|
writeSandboxControlReservation(manifest, request.id, sandboxControlGenerationUsage(manifest));
|
|
930
1343
|
appendBrokerAudit(manifest, 'executor-reservation-written', {
|
|
931
|
-
...requestAuditFields(manifest,
|
|
1344
|
+
...requestAuditFields(manifest, request),
|
|
932
1345
|
requestPath: claimed,
|
|
933
1346
|
executionPath: executionPath(manifest, request.id)
|
|
934
1347
|
});
|
|
935
1348
|
appendBrokerAudit(manifest, 'executor-prepare-start', {
|
|
936
|
-
...requestAuditFields(manifest,
|
|
1349
|
+
...requestAuditFields(manifest, request),
|
|
937
1350
|
requestPath: claimed,
|
|
938
1351
|
executorCwd: manifest.repoRoot,
|
|
939
1352
|
executorEntry: options.internalCliPath ?? process.argv[1] ?? null
|
|
940
1353
|
});
|
|
941
|
-
const
|
|
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
|
+
prepared = await prepareExecution({
|
|
942
1362
|
manifest, manifestPath, request, requestPath: claimed,
|
|
943
|
-
internalCliPath: options.internalCliPath ?? process.argv[1]
|
|
1363
|
+
internalCliPath: options.internalCliPath ?? process.argv[1]!,
|
|
1364
|
+
...(lifecycleAuthority ? { lifecycleAuthority } : {})
|
|
944
1365
|
});
|
|
1366
|
+
const preparedExecution = prepared;
|
|
1367
|
+
if (!preparedExecution) throw new Error('SANDBOX_CONTROL_EXECUTION_PREPARE_INVALID');
|
|
1368
|
+
criticalRequestPhase(manifest, request, 'prepared', 'in-progress', `${preparedExecution.execution.child.pid}`);
|
|
945
1369
|
appendBrokerAudit(manifest, 'executor-prepared', {
|
|
946
|
-
...requestAuditFields(manifest,
|
|
1370
|
+
...requestAuditFields(manifest, request),
|
|
947
1371
|
requestPath: claimed,
|
|
948
1372
|
executorCwd: manifest.repoRoot,
|
|
949
|
-
childPid:
|
|
950
|
-
childStartTime:
|
|
951
|
-
childProcessGroupId:
|
|
1373
|
+
childPid: preparedExecution.execution.child.pid,
|
|
1374
|
+
childStartTime: preparedExecution.execution.child.startTime,
|
|
1375
|
+
childProcessGroupId: preparedExecution.execution.child.processGroupId,
|
|
952
1376
|
executionPath: executionPath(manifest, request.id)
|
|
953
1377
|
});
|
|
954
1378
|
const execution: ActiveExecution = {
|
|
955
|
-
request, prepared
|
|
1379
|
+
request, prepared: preparedExecution, lifecycleAuthority, result: null,
|
|
1380
|
+
resultEvidenceWritten: false, failure: null, settled: false
|
|
956
1381
|
};
|
|
957
1382
|
active = execution;
|
|
958
|
-
|
|
1383
|
+
preparedExecution.completion.then(
|
|
959
1384
|
(result) => {
|
|
960
1385
|
execution.result = sanitizeSandboxControlResult(manifest, result);
|
|
961
1386
|
appendBrokerAudit(manifest, 'executor-completed', {
|
|
962
|
-
...requestAuditFields(manifest,
|
|
1387
|
+
...requestAuditFields(manifest, request),
|
|
963
1388
|
...resultAuditFields(execution.result),
|
|
964
|
-
childPid:
|
|
965
|
-
childStartTime:
|
|
1389
|
+
childPid: preparedExecution.execution.child.pid,
|
|
1390
|
+
childStartTime: preparedExecution.execution.child.startTime
|
|
966
1391
|
});
|
|
967
1392
|
try {
|
|
968
1393
|
writeSandboxControlResultEvidence(manifest, request.id, execution.result);
|
|
969
1394
|
execution.resultEvidenceWritten = true;
|
|
970
1395
|
execution.settled = true;
|
|
971
1396
|
appendBrokerAudit(manifest, 'executor-result-evidence-written', {
|
|
972
|
-
...requestAuditFields(manifest,
|
|
1397
|
+
...requestAuditFields(manifest, request),
|
|
973
1398
|
resultPath: path.join(manifest.processingDir, request.id, 'result.json'),
|
|
974
1399
|
...resultAuditFields(execution.result)
|
|
975
1400
|
});
|
|
976
1401
|
} catch (error) {
|
|
977
1402
|
execution.failure = error;
|
|
978
1403
|
appendBrokerAudit(manifest, 'executor-result-evidence-failed', {
|
|
979
|
-
...requestAuditFields(manifest,
|
|
1404
|
+
...requestAuditFields(manifest, request),
|
|
980
1405
|
errorType: error instanceof Error ? error.name : typeof error,
|
|
981
1406
|
});
|
|
982
1407
|
}
|
|
@@ -985,9 +1410,9 @@ export async function serveSandboxControl(
|
|
|
985
1410
|
execution.failure = error;
|
|
986
1411
|
execution.settled = true;
|
|
987
1412
|
appendBrokerAudit(manifest, 'executor-failed', {
|
|
988
|
-
...requestAuditFields(manifest,
|
|
989
|
-
childPid:
|
|
990
|
-
childStartTime:
|
|
1413
|
+
...requestAuditFields(manifest, request),
|
|
1414
|
+
childPid: preparedExecution.execution.child.pid,
|
|
1415
|
+
childStartTime: preparedExecution.execution.child.startTime,
|
|
991
1416
|
errorType: error instanceof Error ? error.name : typeof error,
|
|
992
1417
|
});
|
|
993
1418
|
}
|
|
@@ -1004,11 +1429,13 @@ export async function serveSandboxControl(
|
|
|
1004
1429
|
retiring = true;
|
|
1005
1430
|
continue;
|
|
1006
1431
|
}
|
|
1432
|
+
criticalRequestPhase(manifest, request, 'accepted-authorized', 'in-progress');
|
|
1007
1433
|
writeAcceptedResponse(manifest, {
|
|
1008
1434
|
version: 2, id, phase: 'accepted', exitCode: null, stdout: '', stderr: '', error: null
|
|
1009
1435
|
});
|
|
1436
|
+
writeSandboxControlTransition(manifest, { requestId: request.id, phase: 'accepted-committed' });
|
|
1010
1437
|
appendBrokerAudit(manifest, 'request-accepted', {
|
|
1011
|
-
...requestAuditFields(manifest,
|
|
1438
|
+
...requestAuditFields(manifest, request),
|
|
1012
1439
|
acceptedPath: acceptedResponsePath(manifest, id),
|
|
1013
1440
|
executionPhase: 'prepared'
|
|
1014
1441
|
});
|
|
@@ -1019,8 +1446,9 @@ export async function serveSandboxControl(
|
|
|
1019
1446
|
break;
|
|
1020
1447
|
}
|
|
1021
1448
|
try {
|
|
1449
|
+
criticalRequestPhase(manifest, request, 'start-authorized', 'in-progress', `${prepared.execution.child.pid}`);
|
|
1022
1450
|
appendBrokerAudit(manifest, 'executor-start', {
|
|
1023
|
-
...requestAuditFields(manifest,
|
|
1451
|
+
...requestAuditFields(manifest, request),
|
|
1024
1452
|
childPid: prepared.execution.child.pid,
|
|
1025
1453
|
childStartTime: prepared.execution.child.startTime,
|
|
1026
1454
|
executionPath: executionPath(manifest, request.id)
|
|
@@ -1028,7 +1456,7 @@ export async function serveSandboxControl(
|
|
|
1028
1456
|
prepared.start(brokerOwns);
|
|
1029
1457
|
} catch (error) {
|
|
1030
1458
|
appendBrokerAudit(manifest, 'executor-start-failed', {
|
|
1031
|
-
...requestAuditFields(manifest,
|
|
1459
|
+
...requestAuditFields(manifest, request),
|
|
1032
1460
|
errorType: error instanceof Error ? error.name : typeof error,
|
|
1033
1461
|
});
|
|
1034
1462
|
const owned = brokerOwns();
|
|
@@ -1056,6 +1484,11 @@ export async function serveSandboxControl(
|
|
|
1056
1484
|
continue;
|
|
1057
1485
|
}
|
|
1058
1486
|
} catch (error) {
|
|
1487
|
+
if (prepared) {
|
|
1488
|
+
try { prepared.terminate(brokerOwns()); } catch { /* cleanup is best effort after a failed admission */ }
|
|
1489
|
+
prepared = null;
|
|
1490
|
+
active = null;
|
|
1491
|
+
}
|
|
1059
1492
|
if (!brokerOwns()) {
|
|
1060
1493
|
retiring = true;
|
|
1061
1494
|
break;
|
|
@@ -1075,6 +1508,7 @@ export async function serveSandboxControl(
|
|
|
1075
1508
|
retiring = true;
|
|
1076
1509
|
break;
|
|
1077
1510
|
}
|
|
1511
|
+
removeAcceptedResponse(manifest, id);
|
|
1078
1512
|
if (claimed) fs.rmSync(path.dirname(claimed), { recursive: true, force: true });
|
|
1079
1513
|
}
|
|
1080
1514
|
}
|
|
@@ -1096,7 +1530,8 @@ export async function serveSandboxControl(
|
|
|
1096
1530
|
}
|
|
1097
1531
|
}
|
|
1098
1532
|
if (owned && active.result && active.resultEvidenceWritten) {
|
|
1099
|
-
if (
|
|
1533
|
+
if (consumeLifecycleAuthorityForResult(manifest, active)
|
|
1534
|
+
&& publishExecutionResult(manifest, active.request, active.result, broker, brokerOwns)) {
|
|
1100
1535
|
if (brokerOwns()) {
|
|
1101
1536
|
removeAcceptedResponse(manifest, active.request.id);
|
|
1102
1537
|
fs.rmSync(path.join(manifest.processingDir, active.request.id), { recursive: true, force: true });
|
|
@@ -1126,7 +1561,7 @@ export async function serveSandboxControl(
|
|
|
1126
1561
|
}
|
|
1127
1562
|
}
|
|
1128
1563
|
if (brokerOwns()) {
|
|
1129
|
-
|
|
1564
|
+
appendDiagnosticAudit(manifest, 'broker-stop', { pid: broker.pid, brokerId: broker.brokerId });
|
|
1130
1565
|
try {
|
|
1131
1566
|
if (fs.readFileSync(brokerPath, 'utf8') === brokerRecord) fs.unlinkSync(brokerPath);
|
|
1132
1567
|
} catch {
|