@fitlab-ai/agent-infra 0.9.13 → 0.9.15
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/cli.ts +26 -9
- package/bin/internal-cli.sh +45 -0
- package/dist/bin/cli.js +173 -157
- package/dist/bin/internal-cli.js +126 -42
- package/dist/lib/agent-client.js +5 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +165 -27
- package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +3 -2
- package/dist/lib/defaults.json +103 -103
- package/dist/lib/fs/durable-write.js +37 -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/agent-client.js +5 -2
- package/dist/lib/internal/cli-route-inventory.js +154 -0
- package/dist/lib/internal/codex-lifecycle.js +15 -12
- package/dist/lib/internal/codex-sandbox-controller.js +8 -2
- package/dist/lib/internal/demo-promotion.js +322 -0
- package/dist/lib/internal/demo-transcript.js +454 -0
- package/dist/lib/internal/git-workflow.js +10 -6
- package/dist/lib/internal/host-control.js +28 -0
- package/dist/lib/internal/manual-validation.js +322 -0
- package/dist/lib/internal/platform-checks.js +16 -4
- package/dist/lib/internal/platform-comment.js +16 -4
- package/dist/lib/internal/platform-context.js +4 -1
- package/dist/lib/internal/platform-issue.js +12 -4
- package/dist/lib/internal/platform-metadata.js +88 -0
- package/dist/lib/internal/platform-pr-review.js +16 -3
- package/dist/lib/internal/platform-pr.js +42 -14
- package/dist/lib/internal/platform-release-notes.js +6 -3
- package/dist/lib/internal/platform-security.js +93 -0
- package/dist/lib/internal/pr-review-grade.js +14 -3
- package/dist/lib/internal/release-workflow.js +82 -89
- package/dist/lib/internal/sandbox-control.js +171 -7
- package/dist/lib/internal/task-activity.js +13 -4
- package/dist/lib/internal/task-artifact.js +19 -107
- package/dist/lib/internal/task-context.js +4 -1
- package/dist/lib/internal/task-create.js +48 -14
- package/dist/lib/internal/task-delivery.js +4 -1
- package/dist/lib/internal/task-event.js +38 -35
- package/dist/lib/internal/task-finalization.js +4 -1
- package/dist/lib/internal/task-invalidation.js +4 -1
- package/dist/lib/internal/task-ledger.js +12 -1
- package/dist/lib/internal/task-lifecycle.js +4 -1
- package/dist/lib/internal/task-operation-registry.js +519 -0
- package/dist/lib/internal/task-orchestration.js +12 -0
- package/dist/lib/internal/task-override.js +10 -3
- package/dist/lib/internal/task-qualification.js +90 -0
- package/dist/lib/internal/task-review.js +10 -52
- package/dist/lib/internal/task-short-id.js +11 -1
- package/dist/lib/internal/task-snapshot.js +4 -1
- package/dist/lib/internal/task-validate.js +37 -12
- package/dist/lib/internal/task-verify.js +5 -1
- package/dist/lib/internal/task-warning.js +8 -1
- package/dist/lib/owned-path.js +33 -0
- package/dist/lib/platform/check-status.js +11 -0
- package/dist/lib/platform/comment-safety.js +229 -0
- 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 +248 -26
- package/dist/lib/platform/issue-comments.js +146 -34
- package/dist/lib/platform/issues.js +1 -179
- package/dist/lib/platform/pr-change-report.js +6 -12
- package/dist/lib/platform/pr-checks.js +12 -65
- package/dist/lib/platform/pr-summary.js +83 -10
- package/dist/lib/platform/provider-contract.d.ts +67 -1
- package/dist/lib/platform/provider-contract.js +6 -2
- package/dist/lib/platform/provider-validation.js +76 -3
- package/dist/lib/platform/pull-requests.js +117 -275
- package/dist/lib/platform/repository-metadata.js +377 -0
- package/dist/lib/platform/security-alerts.js +155 -0
- package/dist/lib/platform/snapshots.js +2 -0
- package/dist/lib/platform/verification-sync.js +154 -162
- package/dist/lib/process-data/index.js +7 -0
- package/dist/lib/release/workflow.js +1 -1
- package/dist/lib/run/index.js +5 -0
- package/dist/lib/sandbox/commands/control-status.js +22 -0
- package/dist/lib/sandbox/commands/create.js +39 -8
- package/dist/lib/sandbox/commands/ls.js +5 -0
- package/dist/lib/sandbox/commands/rm.js +3 -807
- package/dist/lib/sandbox/commands/show.js +16 -0
- package/dist/lib/sandbox/control/audit.js +203 -0
- package/dist/lib/sandbox/control/client.js +125 -12
- package/dist/lib/sandbox/control/completed-reentry.js +85 -0
- package/dist/lib/sandbox/control/container-identity.js +28 -31
- package/dist/lib/sandbox/control/executor.js +233 -34
- 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 +656 -128
- package/dist/lib/sandbox/control/native-file-lock.js +85 -0
- package/dist/lib/sandbox/control/protocol.js +55 -4
- package/dist/lib/sandbox/control/server.js +720 -79
- package/dist/lib/sandbox/control/state.js +63 -3
- package/dist/lib/sandbox/control/task-view.js +183 -0
- package/dist/lib/sandbox/control/task-workflow.js +74 -0
- package/dist/lib/sandbox/control/workflow-executor.js +99 -0
- package/dist/lib/sandbox/engines/authority.js +289 -0
- package/dist/lib/sandbox/engines/docker-exact-discovery.js +97 -0
- package/dist/lib/sandbox/index.js +7 -2
- package/dist/lib/sandbox/recovery.js +49 -5
- package/dist/lib/sandbox/removal.js +1717 -0
- package/dist/lib/sandbox/task-cleanup.js +217 -0
- package/dist/lib/sandbox/workspace-view.js +15 -15
- package/dist/lib/sandbox/worktree-safety.js +4 -1
- package/dist/lib/server/index.js +8 -0
- 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 +101 -0
- package/dist/lib/task/artifact-lifecycle.js +71 -40
- package/dist/lib/task/artifact-name.js +36 -0
- package/dist/lib/task/artifact-operations.js +474 -0
- package/dist/lib/task/artifact-receipts.js +3 -15
- package/dist/lib/task/artifact-repair-intent.js +130 -0
- package/dist/lib/task/artifact-schema.js +127 -0
- package/dist/lib/task/capabilities.js +83 -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 +2 -16
- package/dist/lib/task/control-authority.js +375 -5
- package/dist/lib/task/control-recovery.js +128 -0
- package/dist/lib/task/create-service.js +103 -4
- package/dist/lib/task/create.js +80 -2
- package/dist/lib/task/decision-details.js +2 -46
- package/dist/lib/task/events.js +306 -76
- package/dist/lib/task/finalization-state.js +9 -0
- package/dist/lib/task/human-override.js +3 -6
- package/dist/lib/task/implementation-inputs.js +3 -3
- package/dist/lib/task/index.js +11 -3
- package/dist/lib/task/intermediate-cleanup.js +383 -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.js +19 -1
- package/dist/lib/task/local-artifact-finalization.js +190 -290
- 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 +101 -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 +206 -69
- package/dist/lib/task/qualification-audit.js +476 -0
- package/dist/lib/task/qualification-confirmation.js +109 -0
- package/dist/lib/task/qualification-intents.js +148 -0
- package/dist/lib/task/review-artifacts.js +23 -40
- package/dist/lib/task/review-command.js +25 -0
- package/dist/lib/task/review-finalization.js +146 -97
- package/dist/lib/task/review-fingerprint.js +2 -2
- package/dist/lib/task/sections.js +34 -54
- package/dist/lib/task/verification-config.js +11 -0
- package/dist/lib/task/verification-engine.js +170 -327
- package/dist/lib/task/verification.js +1 -1
- package/dist/lib/task/workflow-command.js +17 -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/lifecycle-build-manifest.json +3 -3
- package/lib/agent-client.ts +9 -0
- package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +223 -33
- package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +3 -2
- package/lib/fs/durable-write.ts +29 -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/agent-client.ts +4 -2
- package/lib/internal/cli-route-inventory.ts +142 -0
- package/lib/internal/codex-lifecycle.ts +14 -12
- package/lib/internal/codex-sandbox-controller.ts +7 -2
- package/lib/internal/demo-promotion.ts +354 -0
- package/lib/internal/demo-transcript.ts +419 -0
- package/lib/internal/git-workflow.ts +9 -6
- package/lib/internal/host-control.ts +28 -0
- package/lib/internal/manual-validation.ts +306 -0
- package/lib/internal/platform-checks.ts +12 -4
- package/lib/internal/platform-comment.ts +12 -4
- package/lib/internal/platform-context.ts +3 -1
- package/lib/internal/platform-issue.ts +11 -4
- package/lib/internal/platform-metadata.ts +70 -0
- package/lib/internal/platform-pr-review.ts +14 -3
- package/lib/internal/platform-pr.ts +38 -14
- package/lib/internal/platform-release-notes.ts +5 -3
- package/lib/internal/platform-security.ts +83 -0
- package/lib/internal/pr-review-grade.ts +10 -3
- package/lib/internal/release-workflow.ts +91 -87
- package/lib/internal/sandbox-control.ts +181 -6
- package/lib/internal/task-activity.ts +12 -4
- package/lib/internal/task-artifact.ts +15 -79
- package/lib/internal/task-context.ts +3 -1
- package/lib/internal/task-create.ts +76 -15
- package/lib/internal/task-delivery.ts +3 -1
- package/lib/internal/task-event.ts +21 -10
- package/lib/internal/task-finalization.ts +3 -1
- package/lib/internal/task-invalidation.ts +3 -1
- package/lib/internal/task-ledger.ts +11 -1
- package/lib/internal/task-lifecycle.ts +3 -1
- package/lib/internal/task-operation-registry.ts +590 -0
- package/lib/internal/task-orchestration.ts +12 -0
- package/lib/internal/task-override.ts +9 -3
- package/lib/internal/task-qualification.ts +61 -0
- package/lib/internal/task-review.ts +7 -50
- package/lib/internal/task-short-id.ts +10 -1
- package/lib/internal/task-snapshot.ts +3 -1
- package/lib/internal/task-validate.ts +32 -12
- package/lib/internal/task-verify.ts +4 -1
- package/lib/internal/task-warning.ts +7 -1
- package/lib/owned-path.ts +26 -0
- package/lib/platform/check-status.ts +9 -0
- package/lib/platform/comment-safety.ts +255 -0
- package/lib/platform/completion-backfill.ts +1 -1
- package/lib/platform/github-data.ts +322 -0
- package/lib/platform/github-provider.ts +244 -27
- package/lib/platform/issue-comments.ts +159 -35
- package/lib/platform/issues.ts +4 -245
- package/lib/platform/pr-change-report.ts +6 -12
- package/lib/platform/pr-checks.ts +13 -77
- package/lib/platform/pr-summary.ts +88 -11
- package/lib/platform/provider-contract.ts +78 -3
- package/lib/platform/provider-validation.ts +85 -3
- package/lib/platform/pull-requests.ts +129 -348
- package/lib/platform/repository-metadata.ts +453 -0
- package/lib/platform/security-alerts.ts +191 -0
- package/lib/platform/snapshots.ts +53 -0
- package/lib/platform/verification-sync.ts +171 -177
- package/lib/process-data/index.ts +7 -0
- package/lib/release/workflow.ts +3 -3
- package/lib/run/index.ts +5 -0
- package/lib/sandbox/commands/control-status.ts +26 -0
- package/lib/sandbox/commands/create.ts +53 -12
- package/lib/sandbox/commands/ls.ts +7 -0
- package/lib/sandbox/commands/rm.ts +3 -996
- package/lib/sandbox/commands/show.ts +16 -0
- package/lib/sandbox/control/audit.ts +253 -0
- package/lib/sandbox/control/client.ts +150 -13
- package/lib/sandbox/control/completed-reentry.ts +98 -0
- package/lib/sandbox/control/container-identity.ts +27 -43
- package/lib/sandbox/control/executor.ts +262 -37
- package/lib/sandbox/control/finalization-response.ts +29 -0
- package/lib/sandbox/control/identity-sentinel.ts +137 -0
- package/lib/sandbox/control/lifecycle.ts +672 -18
- package/lib/sandbox/control/native-file-lock.ts +108 -0
- package/lib/sandbox/control/protocol.ts +80 -8
- package/lib/sandbox/control/server.ts +779 -82
- package/lib/sandbox/control/state.ts +84 -3
- package/lib/sandbox/control/task-view.ts +240 -0
- package/lib/sandbox/control/task-workflow.ts +95 -0
- package/lib/sandbox/control/workflow-executor.ts +102 -0
- package/lib/sandbox/engines/authority.ts +365 -0
- package/lib/sandbox/engines/docker-exact-discovery.ts +132 -0
- package/lib/sandbox/index.ts +9 -2
- package/lib/sandbox/recovery.ts +49 -6
- package/lib/sandbox/removal.ts +2144 -0
- package/lib/sandbox/task-cleanup.ts +209 -0
- package/lib/sandbox/workspace-view.ts +26 -18
- package/lib/sandbox/worktree-safety.ts +4 -1
- package/lib/server/index.ts +10 -0
- package/lib/task/activity-intent.ts +2 -2
- package/lib/task/activity-log.ts +27 -19
- package/lib/task/artifact-command.ts +104 -0
- package/lib/task/artifact-lifecycle.ts +66 -39
- package/lib/task/artifact-name.ts +55 -0
- package/lib/task/artifact-operations.ts +580 -0
- package/lib/task/artifact-receipts.ts +3 -14
- package/lib/task/artifact-repair-intent.ts +152 -0
- package/lib/task/artifact-schema.ts +163 -0
- package/lib/task/capabilities.ts +82 -22
- package/lib/task/codex-orchestration.ts +85 -24
- package/lib/task/commit-identity.ts +31 -0
- package/lib/task/commit-operation.ts +2 -16
- package/lib/task/control-authority.ts +513 -6
- package/lib/task/control-recovery.ts +174 -0
- package/lib/task/create-service.ts +131 -6
- package/lib/task/create.ts +83 -1
- package/lib/task/decision-details.ts +3 -71
- package/lib/task/events.ts +292 -75
- package/lib/task/finalization-state.ts +11 -0
- package/lib/task/human-override.ts +3 -8
- package/lib/task/implementation-inputs.ts +3 -3
- package/lib/task/index.ts +16 -3
- package/lib/task/intermediate-cleanup.ts +455 -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.ts +17 -1
- package/lib/task/local-artifact-finalization.ts +223 -388
- 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 +152 -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 +191 -68
- package/lib/task/qualification-audit.ts +588 -0
- package/lib/task/qualification-confirmation.ts +117 -0
- package/lib/task/qualification-intents.ts +150 -0
- package/lib/task/review-artifacts.ts +21 -41
- package/lib/task/review-command.ts +23 -0
- package/lib/task/review-finalization.ts +154 -153
- package/lib/task/review-fingerprint.ts +2 -2
- package/lib/task/sections.ts +33 -52
- package/lib/task/verification-config.ts +12 -0
- package/lib/task/verification-engine.ts +165 -370
- package/lib/task/verification-types.ts +17 -0
- package/lib/task/verification.ts +1 -1
- package/lib/task/workflow-command.ts +18 -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/package.json +7 -2
- package/templates/.agents/hooks/lifecycle-delegation.js +6 -6
- package/templates/.agents/rules/README.en.md +2 -0
- package/templates/.agents/rules/README.zh-CN.md +2 -0
- package/templates/.agents/rules/decision-qualification.en.md +22 -0
- package/templates/.agents/rules/decision-qualification.zh-CN.md +22 -0
- package/templates/.agents/rules/evidence-reporting.en.md +28 -0
- package/templates/.agents/rules/evidence-reporting.zh-CN.md +28 -0
- package/templates/.agents/rules/label-milestone-setup.en.md +7 -7
- package/templates/.agents/rules/label-milestone-setup.zh-CN.md +7 -7
- package/templates/.agents/rules/local-artifact-repair.en.md +5 -1
- package/templates/.agents/rules/local-artifact-repair.zh-CN.md +5 -1
- package/templates/.agents/rules/security-alerts.en.md +8 -8
- package/templates/.agents/rules/security-alerts.zh-CN.md +8 -8
- 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 +15 -2
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +19 -2
- package/templates/.agents/skills/analyze-task/config/verify.en.json +1 -12
- package/templates/.agents/skills/analyze-task/config/verify.zh-CN.json +1 -12
- package/templates/.agents/skills/close-codescan/SKILL.en.md +2 -2
- package/templates/.agents/skills/close-codescan/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/close-dependabot/SKILL.en.md +2 -2
- package/templates/.agents/skills/close-dependabot/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/code-task/SKILL.en.md +14 -1
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +14 -1
- package/templates/.agents/skills/code-task/config/verify.en.json +5 -14
- package/templates/.agents/skills/code-task/config/verify.zh-CN.json +5 -14
- package/templates/.agents/skills/code-task/reference/code-rules.en.md +5 -4
- package/templates/.agents/skills/code-task/reference/code-rules.zh-CN.md +5 -4
- package/templates/.agents/skills/code-task/reference/dual-mode.en.md +1 -1
- package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +1 -1
- package/templates/.agents/skills/code-task/reference/fix-mode.en.md +3 -1
- package/templates/.agents/skills/code-task/reference/fix-mode.zh-CN.md +3 -1
- package/templates/.agents/skills/code-task/reference/report-template.en.md +42 -15
- package/templates/.agents/skills/code-task/reference/report-template.zh-CN.md +39 -12
- package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +37 -21
- package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +37 -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 +4 -0
- package/templates/.agents/skills/complete-manual-validation/reference/report-template.zh-CN.md +4 -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 +10 -23
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +10 -23
- package/templates/.agents/skills/create-task/SKILL.en.md +8 -0
- package/templates/.agents/skills/create-task/SKILL.zh-CN.md +7 -0
- package/templates/.agents/skills/import-codescan/SKILL.en.md +1 -1
- package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/import-dependabot/SKILL.en.md +1 -1
- package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/init-labels/SKILL.en.md +8 -8
- package/templates/.agents/skills/init-labels/SKILL.zh-CN.md +8 -8
- package/templates/.agents/skills/init-milestones/SKILL.en.md +9 -11
- package/templates/.agents/skills/init-milestones/SKILL.zh-CN.md +9 -11
- package/templates/.agents/skills/plan-task/SKILL.en.md +14 -1
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +16 -1
- package/templates/.agents/skills/plan-task/config/verify.en.json +4 -14
- package/templates/.agents/skills/plan-task/config/verify.zh-CN.json +4 -14
- 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/review-analysis/SKILL.en.md +14 -1
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +14 -1
- package/templates/.agents/skills/review-analysis/config/verify.en.json +1 -18
- package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +1 -18
- package/templates/.agents/skills/review-analysis/reference/report-template.en.md +50 -23
- package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +45 -18
- package/templates/.agents/skills/review-code/SKILL.en.md +14 -1
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +14 -1
- package/templates/.agents/skills/review-code/config/verify.en.json +1 -23
- package/templates/.agents/skills/review-code/config/verify.zh-CN.json +1 -23
- package/templates/.agents/skills/review-code/reference/report-template.en.md +49 -22
- package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +45 -18
- package/templates/.agents/skills/review-plan/SKILL.en.md +14 -1
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +14 -1
- package/templates/.agents/skills/review-plan/config/verify.en.json +1 -18
- package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +1 -18
- package/templates/.agents/skills/review-plan/reference/report-template.en.md +49 -22
- package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +45 -18
- package/templates/.agents/skills/review-pr/SKILL.en.md +5 -1
- package/templates/.agents/skills/review-pr/SKILL.zh-CN.md +5 -1
- package/templates/.agents/skills/review-pr/reference/report-template.en.md +4 -3
- package/templates/.agents/skills/review-pr/reference/report-template.zh-CN.md +4 -3
- package/templates/.agents/skills/run-manual-validation/SKILL.en.md +25 -4
- package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +25 -4
- 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 +5 -1
- package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +5 -1
- 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 +29 -7
- package/templates/.agents/skills/test/SKILL.zh-CN.md +29 -7
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +14 -0
- package/templates/.agents/templates/task.en.md +6 -0
- package/templates/.agents/templates/task.zh-CN.md +6 -0
- 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
- package/templates/.agents/scripts/security-alerts.github.sh +0 -151
- package/templates/.agents/scripts/security-alerts.sh +0 -31
- package/templates/.agents/skills/init-labels/scripts/init-labels.github.sh +0 -146
- package/templates/.agents/skills/init-labels/scripts/init-labels.sh +0 -32
- package/templates/.agents/skills/init-milestones/scripts/init-milestones.github.sh +0 -294
- package/templates/.agents/skills/init-milestones/scripts/init-milestones.sh +0 -6
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { finalizationTerminalResponse } from './finalization-response.ts';
|
|
2
|
+
import { isDeepStrictEqual } from 'node:util';
|
|
3
|
+
import { completedReentryView } from './completed-reentry.ts';
|
|
1
4
|
import fs from 'node:fs';
|
|
2
5
|
import path from 'node:path';
|
|
3
6
|
import { createHash, randomUUID } from 'node:crypto';
|
|
@@ -15,7 +18,6 @@ import {
|
|
|
15
18
|
} from './protocol.ts';
|
|
16
19
|
import { prepareSandboxControlExecution, type PreparedSandboxControlExecution, type SandboxControlExecutionResult } from './executor.ts';
|
|
17
20
|
import {
|
|
18
|
-
appendSandboxControlAudit,
|
|
19
21
|
atomicWriteJsonNoReplace,
|
|
20
22
|
createSandboxControlPayload,
|
|
21
23
|
cleanupStaleSandboxControlLease,
|
|
@@ -25,6 +27,8 @@ import {
|
|
|
25
27
|
readExecution,
|
|
26
28
|
readSandboxControlPayload,
|
|
27
29
|
readSandboxControlResultEvidence,
|
|
30
|
+
readSandboxControlTerminalResult,
|
|
31
|
+
createSandboxControlTerminalResult,
|
|
28
32
|
sandboxControlEncodedJsonBytes,
|
|
29
33
|
sandboxControlGenerationUsage,
|
|
30
34
|
sanitizeSandboxControlOutput,
|
|
@@ -33,10 +37,36 @@ import {
|
|
|
33
37
|
writeSandboxControlPayload,
|
|
34
38
|
writeSandboxControlReservation,
|
|
35
39
|
writeSandboxControlResultEvidence,
|
|
40
|
+
writeSandboxControlTerminalResult,
|
|
41
|
+
appendSandboxControlAudit,
|
|
36
42
|
terminateSandboxControlExecution,
|
|
37
|
-
writeSandboxControlStatus
|
|
43
|
+
writeSandboxControlStatus,
|
|
44
|
+
readSandboxControlStatus
|
|
38
45
|
} from './state.ts';
|
|
39
46
|
import { validateSandboxControlRequest } from './protocol.ts';
|
|
47
|
+
import {
|
|
48
|
+
requestAuditFields,
|
|
49
|
+
resultAuditFields,
|
|
50
|
+
appendCriticalAudit,
|
|
51
|
+
appendDiagnosticAudit,
|
|
52
|
+
createSandboxControlAuditContext,
|
|
53
|
+
writeSandboxControlTransition,
|
|
54
|
+
readSandboxControlTransition
|
|
55
|
+
} from './audit.ts';
|
|
56
|
+
import { parseTaskControlOperation } from '../../task/control-authority.ts';
|
|
57
|
+
import {
|
|
58
|
+
parseControlOutput,
|
|
59
|
+
classifySandboxControlRecovery,
|
|
60
|
+
findSandboxControlRecoveryOperation,
|
|
61
|
+
operationRecoveryBinding,
|
|
62
|
+
SANDBOX_CONTROL_REQUIRED_COMPLETION_PHASES
|
|
63
|
+
} from '../../task/control-recovery.ts';
|
|
64
|
+
import { readRun } from '../../task/orchestration.ts';
|
|
65
|
+
import { captureRepositorySnapshot } from '../../task/workspace-snapshot.ts';
|
|
66
|
+
import { parseTypedTaskFrontmatter } from '../../task/frontmatter.ts';
|
|
67
|
+
import { readLifecycleJournalEvidence } from '../../task/lifecycle.ts';
|
|
68
|
+
import { resolveTaskRef } from '../../task/resolve-ref.ts';
|
|
69
|
+
import { loadShortIdByTaskId } from '../../task/short-id.ts';
|
|
40
70
|
import { inspectSandboxControlContainer, type ContainerObservation } from './container-identity.ts';
|
|
41
71
|
import {
|
|
42
72
|
acquireSandboxControlBrokerStartup,
|
|
@@ -47,17 +77,151 @@ import {
|
|
|
47
77
|
import type { BrokerOwner } from './lifecycle.ts';
|
|
48
78
|
import { nextSandboxControlBackoff } from './timing.ts';
|
|
49
79
|
import { readTaskFinalizationReceipt } from '../../task/finalization.ts';
|
|
50
|
-
import {
|
|
80
|
+
import { readCodexControllerRegistration } from './controller-registration.ts';
|
|
81
|
+
import { validateSandboxControlIdentity } from './identity-sentinel.ts';
|
|
82
|
+
import {
|
|
83
|
+
mergeSandboxTaskView,
|
|
84
|
+
taskViewAfterFinalization,
|
|
85
|
+
taskViewForManifest,
|
|
86
|
+
type SandboxTaskView
|
|
87
|
+
} from './task-view.ts';
|
|
88
|
+
import { taskCreateOutputUnavailableResult } from '../../task/create-service.ts';
|
|
89
|
+
import {
|
|
90
|
+
consumeLifecycleRecoveryAttestation,
|
|
91
|
+
issueLifecycleRecoveryAttestation,
|
|
92
|
+
recoverLifecycleRecoveryOperation,
|
|
93
|
+
type LifecycleAuthorityResponseV1
|
|
94
|
+
} from '../../task/control-authority.ts';
|
|
95
|
+
import { createCodexCapabilityStore } from '../../agent-clients/adapters/codex-lifecycle/capability-store.ts';
|
|
96
|
+
import { computeLifecycleBuildIdentity } from '../../agent-clients/adapters/codex-lifecycle/build-identity.ts';
|
|
51
97
|
|
|
52
98
|
type ActiveExecution = {
|
|
53
99
|
request: SandboxControlRequest;
|
|
54
100
|
prepared: PreparedSandboxControlExecution;
|
|
101
|
+
lifecycleAuthority: LifecycleAuthorityResponseV1 | undefined;
|
|
55
102
|
result: SandboxControlExecutionResult | null;
|
|
56
103
|
resultEvidenceWritten: boolean;
|
|
57
104
|
failure: unknown;
|
|
58
105
|
settled: boolean;
|
|
59
106
|
};
|
|
60
107
|
|
|
108
|
+
function consumeLifecycleAuthorityForResult(
|
|
109
|
+
manifest: SandboxControlManifest,
|
|
110
|
+
execution: ActiveExecution
|
|
111
|
+
): boolean {
|
|
112
|
+
const authority = execution.lifecycleAuthority?.attestation;
|
|
113
|
+
if (!authority || execution.result?.exitCode !== 0) return true;
|
|
114
|
+
try {
|
|
115
|
+
consumeLifecycleRecoveryAttestation(authority);
|
|
116
|
+
return true;
|
|
117
|
+
} catch (error) {
|
|
118
|
+
appendBrokerAudit(manifest, 'lifecycle-authority-consume-retry', {
|
|
119
|
+
...requestAuditFields(manifest, execution.request),
|
|
120
|
+
errorCode: error instanceof Error ? error.name : 'LIFECYCLE_AUTHORITY_CONSUME_FAILED'
|
|
121
|
+
});
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function lifecycleAuthorityForRequest(
|
|
127
|
+
manifest: SandboxControlManifest,
|
|
128
|
+
manifestPath: string,
|
|
129
|
+
request: SandboxControlRequest
|
|
130
|
+
): LifecycleAuthorityResponseV1 | undefined {
|
|
131
|
+
const selector = request.family === 'task-workflow' || request.family === 'task-orchestration'
|
|
132
|
+
? request.authority
|
|
133
|
+
: undefined;
|
|
134
|
+
if (!selector) return undefined;
|
|
135
|
+
let controllerBinding: { instanceDigest: string; controlGeneration: string } | null = null;
|
|
136
|
+
try {
|
|
137
|
+
const registration = readCodexControllerRegistration(manifestPath);
|
|
138
|
+
controllerBinding = {
|
|
139
|
+
instanceDigest: registration.controllerInstanceDigest,
|
|
140
|
+
controlGeneration: registration.controlGeneration
|
|
141
|
+
};
|
|
142
|
+
} catch {
|
|
143
|
+
// The issuer reports the stable unavailable result; the broker never trusts
|
|
144
|
+
// a controller binding supplied by the request.
|
|
145
|
+
}
|
|
146
|
+
return issueLifecycleRecoveryAttestation(selector, {
|
|
147
|
+
controllerBinding,
|
|
148
|
+
capabilityStore: createCodexCapabilityStore(),
|
|
149
|
+
buildIdentity: computeLifecycleBuildIdentity(manifest.repoRoot)
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function appendBrokerAudit(
|
|
154
|
+
manifest: SandboxControlManifest,
|
|
155
|
+
event: string,
|
|
156
|
+
fields: Record<string, string | number | boolean | null> = {}
|
|
157
|
+
): void {
|
|
158
|
+
appendDiagnosticAudit(manifest, event, { source: 'broker', ...fields });
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function operationKey(request: SandboxControlRequest, output?: string): string | null {
|
|
162
|
+
if (request.family === 'task-finalization') return request.operation;
|
|
163
|
+
if (request.family === 'task-create') return 'create';
|
|
164
|
+
if (request.family === 'codex-controller') return request.command;
|
|
165
|
+
if (request.family === 'task-workflow') return request.workflow.operation;
|
|
166
|
+
if (request.family !== 'task-lifecycle' && request.family !== 'task-orchestration') return null;
|
|
167
|
+
if (request.family === 'task-orchestration' && request.args[1] === 'route') {
|
|
168
|
+
if (!output) return 'route';
|
|
169
|
+
const result = parseControlOutput(output);
|
|
170
|
+
return result?.changed === true && result.status === 'completed' ? 'route.clean-completion' : 'route.read';
|
|
171
|
+
}
|
|
172
|
+
try {
|
|
173
|
+
const operation = parseTaskControlOperation(request.family, request.args);
|
|
174
|
+
return operation.family === 'task-orchestration' ? operation.intent : operation.request.intent;
|
|
175
|
+
} catch {
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function recoveryOperationKey(
|
|
181
|
+
request: SandboxControlRequest,
|
|
182
|
+
terminalResult: ReturnType<typeof readSandboxControlTerminalResult> | null,
|
|
183
|
+
output: string | undefined
|
|
184
|
+
): string | null {
|
|
185
|
+
const operation = operationKey(request, output);
|
|
186
|
+
if (operation !== 'route') return operation;
|
|
187
|
+
const digest = terminalResult?.intentDigest;
|
|
188
|
+
for (const candidate of ['route.read', 'route.clean-completion'] as const) {
|
|
189
|
+
const expected = operationRecoveryBinding(request.id, request.generation, null, request.family, candidate).intentDigest;
|
|
190
|
+
if (digest === expected) return candidate;
|
|
191
|
+
}
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function terminalResultMatchesRequest(
|
|
196
|
+
request: SandboxControlRequest,
|
|
197
|
+
terminalResult: ReturnType<typeof readSandboxControlTerminalResult>
|
|
198
|
+
): boolean {
|
|
199
|
+
const operation = operationKey(request);
|
|
200
|
+
const candidates = operation === 'route'
|
|
201
|
+
? ['route.read', 'route.clean-completion']
|
|
202
|
+
: operation ? [operation] : [];
|
|
203
|
+
return candidates.some((candidate) => createHash('sha256').update(`${request.family}\0${candidate}`, 'utf8').digest('hex') === terminalResult.intentDigest);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function criticalRequestPhase(
|
|
207
|
+
manifest: SandboxControlManifest,
|
|
208
|
+
request: SandboxControlRequest,
|
|
209
|
+
phase: Parameters<typeof createSandboxControlAuditContext>[1]['phase'],
|
|
210
|
+
outcome: Parameters<typeof createSandboxControlAuditContext>[1]['outcome'],
|
|
211
|
+
reference?: string | null
|
|
212
|
+
): void {
|
|
213
|
+
const context = createSandboxControlAuditContext(manifest, {
|
|
214
|
+
requestId: request.id,
|
|
215
|
+
family: request.family,
|
|
216
|
+
operation: operationKey(request),
|
|
217
|
+
phase,
|
|
218
|
+
outcome
|
|
219
|
+
});
|
|
220
|
+
appendCriticalAudit(manifest, context, { reference: reference ?? null });
|
|
221
|
+
writeSandboxControlTransition(manifest, { requestId: request.id, phase, reference });
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
|
|
61
225
|
function assertRealDirectory(directory: string, parent?: string): void {
|
|
62
226
|
const stat = fs.lstatSync(directory);
|
|
63
227
|
if (!stat.isDirectory() || stat.isSymbolicLink()) throw new Error('SANDBOX_CONTROL_CHANNEL_INVALID');
|
|
@@ -142,33 +306,37 @@ function finalizationRecoveryResponse(
|
|
|
142
306
|
} catch {
|
|
143
307
|
return { status: 'deferred' };
|
|
144
308
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
?
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
309
|
+
return { status: 'matched', response: finalizationTerminalResponse(manifest.taskId, requestId, receipt) };
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function publishFinalizationTaskView(
|
|
313
|
+
manifest: SandboxControlManifest,
|
|
314
|
+
broker: BrokerOwner,
|
|
315
|
+
requestId: string,
|
|
316
|
+
state: 'starting' | 'healthy' | 'busy' | 'parked',
|
|
317
|
+
reasonCode: string | null,
|
|
318
|
+
activeRequestId: string | null
|
|
319
|
+
): SandboxTaskView {
|
|
320
|
+
let view: SandboxTaskView;
|
|
321
|
+
try {
|
|
322
|
+
const resolved = resolveTaskRef(manifest.taskId ?? '', { repoRoot: manifest.repoRoot });
|
|
323
|
+
const receipt = readTaskFinalizationReceipt(manifest.repoRoot, manifest.taskId ?? '');
|
|
324
|
+
view = resolved.ok && resolved.state === 'completed'
|
|
325
|
+
? taskViewAfterFinalization({
|
|
326
|
+
taskId: manifest.taskId ?? '', generation: manifest.generation, requestId, receipt
|
|
327
|
+
})
|
|
328
|
+
: {
|
|
329
|
+
state: 'unknown', taskId: manifest.taskId, observedSource: 'unknown', receipt: null,
|
|
330
|
+
reasonCode: 'SANDBOX_TASK_VIEW_SOURCE_UNCONFIRMED'
|
|
331
|
+
};
|
|
332
|
+
} catch {
|
|
333
|
+
view = {
|
|
334
|
+
state: 'unknown', taskId: manifest.taskId, observedSource: 'unknown', receipt: null,
|
|
335
|
+
reasonCode: 'SANDBOX_TASK_VIEW_RECEIPT_INVALID'
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
writeSandboxControlStatus(manifest, broker, state, reasonCode, activeRequestId, Date.now(), view);
|
|
339
|
+
return view;
|
|
172
340
|
}
|
|
173
341
|
|
|
174
342
|
function payloadReference(payload: ReturnType<typeof createSandboxControlPayload>) {
|
|
@@ -201,46 +369,290 @@ function outputMatchesEvidence(output: string, bytes: number, sha256: string): b
|
|
|
201
369
|
}
|
|
202
370
|
|
|
203
371
|
function genericRecoveryResponse(
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
payload: ReturnType<typeof readSandboxControlPayload> | null
|
|
372
|
+
request: SandboxControlRequest,
|
|
373
|
+
exitCode: number,
|
|
374
|
+
payload: ReturnType<typeof readSandboxControlPayload> | null,
|
|
375
|
+
cause: 'publish' | 'recovery' = 'recovery'
|
|
207
376
|
): SandboxControlResponse {
|
|
377
|
+
const outputUnavailable = request.family === 'task-create' && !payload;
|
|
208
378
|
return {
|
|
209
379
|
version: 2,
|
|
210
|
-
id:
|
|
380
|
+
id: request.id,
|
|
211
381
|
phase: 'completed',
|
|
212
|
-
exitCode
|
|
213
|
-
stdout: '',
|
|
214
|
-
stderr: payload ? '' :
|
|
382
|
+
exitCode,
|
|
383
|
+
stdout: outputUnavailable ? `${JSON.stringify(taskCreateOutputUnavailableResult(request.id))}\n` : '',
|
|
384
|
+
stderr: payload ? '' : cause === 'publish'
|
|
385
|
+
? 'SANDBOX_CONTROL_OUTPUT_UNAVAILABLE: output payload was not retained\n'
|
|
386
|
+
: 'SANDBOX_CONTROL_OUTPUT_UNAVAILABLE: broker restarted after executor completion\n',
|
|
215
387
|
error: null,
|
|
216
388
|
outputState: payload ? 'available' : 'unavailable',
|
|
217
389
|
payload: payload ? payloadReference(payload) : null
|
|
218
390
|
};
|
|
219
391
|
}
|
|
220
392
|
|
|
393
|
+
type RecoveryDomainEvidence = Readonly<{
|
|
394
|
+
domain: Readonly<Record<string, unknown>> | null;
|
|
395
|
+
journal?: ReturnType<typeof readLifecycleJournalEvidence>;
|
|
396
|
+
}>;
|
|
397
|
+
|
|
398
|
+
function taskCreateDomainEvidence(
|
|
399
|
+
manifest: SandboxControlManifest,
|
|
400
|
+
output: Record<string, unknown> | null
|
|
401
|
+
): Readonly<Record<string, unknown>> {
|
|
402
|
+
if (!output || !['applied', 'no-op', 'degraded'].includes(String(output.status))) {
|
|
403
|
+
return { consistent: false };
|
|
404
|
+
}
|
|
405
|
+
const task = output.task && typeof output.task === 'object' && !Array.isArray(output.task)
|
|
406
|
+
? output.task as Record<string, unknown> : null;
|
|
407
|
+
const taskId = typeof task?.id === 'string' ? task.id : null;
|
|
408
|
+
const shortId = typeof task?.shortId === 'string' ? task.shortId : null;
|
|
409
|
+
if (!taskId || !shortId) return { consistent: false };
|
|
410
|
+
try {
|
|
411
|
+
const resolved = resolveTaskRef(taskId, { repoRoot: manifest.repoRoot });
|
|
412
|
+
const shortIds = loadShortIdByTaskId(manifest.repoRoot);
|
|
413
|
+
return {
|
|
414
|
+
consistent: resolved.ok && resolved.taskId === taskId && resolved.state === 'active' && shortIds.get(taskId) === shortId,
|
|
415
|
+
taskId,
|
|
416
|
+
shortId
|
|
417
|
+
};
|
|
418
|
+
} catch {
|
|
419
|
+
return { consistent: false };
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
function controllerDomainEvidence(
|
|
424
|
+
manifest: SandboxControlManifest,
|
|
425
|
+
manifestPath: string,
|
|
426
|
+
request: SandboxControlRequest,
|
|
427
|
+
output: Record<string, unknown> | null
|
|
428
|
+
): Readonly<Record<string, unknown>> {
|
|
429
|
+
if (!output || output.error !== null || typeof output.status !== 'string') return { consistent: false };
|
|
430
|
+
try {
|
|
431
|
+
const registration = readCodexControllerRegistration(manifestPath);
|
|
432
|
+
if (request.family !== 'codex-controller') return { consistent: false };
|
|
433
|
+
if (request.command === 'open') {
|
|
434
|
+
const lease = output.lease && typeof output.lease === 'object' && !Array.isArray(output.lease)
|
|
435
|
+
? output.lease as Record<string, unknown> : null;
|
|
436
|
+
return {
|
|
437
|
+
consistent: output.status === 'opened' && output.changed === true
|
|
438
|
+
&& lease?.taskId === registration.taskId
|
|
439
|
+
&& lease.controlGeneration === registration.controlGeneration
|
|
440
|
+
&& lease.controllerInstanceDigest === registration.controllerInstanceDigest
|
|
441
|
+
&& registration.taskId === manifest.taskId
|
|
442
|
+
&& registration.controlGeneration === manifest.generation
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
if (request.command === 'verify') {
|
|
446
|
+
const binding = output.binding && typeof output.binding === 'object' && !Array.isArray(output.binding)
|
|
447
|
+
? output.binding as Record<string, unknown> : null;
|
|
448
|
+
return {
|
|
449
|
+
consistent: output.status === 'verified' && output.changed === false
|
|
450
|
+
&& binding?.taskId === registration.taskId
|
|
451
|
+
&& binding.controlGeneration === registration.controlGeneration
|
|
452
|
+
&& binding.controllerInstanceDigest === registration.controllerInstanceDigest
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
return { consistent: false };
|
|
456
|
+
} catch (error) {
|
|
457
|
+
return { consistent: request.family === 'codex-controller' && request.command === 'close'
|
|
458
|
+
&& (error as { code?: string }).code === 'CODEX_SANDBOX_CONTROLLER_REGISTRATION_MISSING'
|
|
459
|
+
&& output?.status === 'closed' && typeof output.changed === 'boolean' };
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
function orchestrationDomainEvidence(
|
|
464
|
+
operation: ReturnType<typeof findSandboxControlRecoveryOperation>,
|
|
465
|
+
terminalResult: ReturnType<typeof readSandboxControlTerminalResult>,
|
|
466
|
+
output: Record<string, unknown> | null,
|
|
467
|
+
run: ReturnType<typeof readRun> | null
|
|
468
|
+
): Readonly<Record<string, unknown>> {
|
|
469
|
+
if (!operation || !output) return { consistent: false };
|
|
470
|
+
const outputRun = output.run && typeof output.run === 'object' && !Array.isArray(output.run)
|
|
471
|
+
? output.run as Record<string, unknown> : null;
|
|
472
|
+
const runMatches = outputRun === null ? run === null : run !== null && JSON.stringify(run) === JSON.stringify(outputRun);
|
|
473
|
+
if (operation.class === 'read-only') {
|
|
474
|
+
return {
|
|
475
|
+
consistent: output.status === terminalResult.status && output.changed === terminalResult.changed && runMatches,
|
|
476
|
+
snapshotValid: true,
|
|
477
|
+
status: run?.status ?? terminalResult.status,
|
|
478
|
+
pendingDelegation: run?.pendingDelegation ?? null
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
if (!run || !outputRun) return { consistent: false };
|
|
482
|
+
return {
|
|
483
|
+
consistent: output.status === terminalResult.status && output.changed === terminalResult.changed && runMatches,
|
|
484
|
+
status: run.status,
|
|
485
|
+
pendingDelegation: run.pendingDelegation
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
function readRecoveryDomain(
|
|
490
|
+
manifest: SandboxControlManifest,
|
|
491
|
+
manifestPath: string,
|
|
492
|
+
request: SandboxControlRequest,
|
|
493
|
+
operation: ReturnType<typeof findSandboxControlRecoveryOperation>,
|
|
494
|
+
terminalResult: ReturnType<typeof readSandboxControlTerminalResult>,
|
|
495
|
+
payloadOutput: string | null
|
|
496
|
+
): RecoveryDomainEvidence {
|
|
497
|
+
if (!operation) return { domain: null };
|
|
498
|
+
const taskRef = request.family === 'task-finalization' || request.family === 'task-workflow'
|
|
499
|
+
? manifest.taskId
|
|
500
|
+
: 'args' in request ? request.args[0] ?? null : manifest.taskId;
|
|
501
|
+
const output = parseControlOutput(payloadOutput);
|
|
502
|
+
if (!taskRef && operation.family !== 'task-create' && operation.family !== 'codex-controller') {
|
|
503
|
+
return { domain: null };
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
if (operation.family === 'task-lifecycle') {
|
|
507
|
+
if (!taskRef || !terminalResult.targetState) return { domain: null };
|
|
508
|
+
const journal = readLifecycleJournalEvidence(manifest.repoRoot, taskRef);
|
|
509
|
+
try {
|
|
510
|
+
const resolved = resolveTaskRef(taskRef, { repoRoot: manifest.repoRoot });
|
|
511
|
+
const shortIds = loadShortIdByTaskId(manifest.repoRoot);
|
|
512
|
+
const shortIdMatches = terminalResult.targetState === 'active'
|
|
513
|
+
? shortIds.has(resolved.ok ? resolved.taskId : taskRef)
|
|
514
|
+
: !shortIds.has(resolved.ok ? resolved.taskId : taskRef);
|
|
515
|
+
return {
|
|
516
|
+
domain: { consistent: resolved.ok && resolved.state === terminalResult.targetState && shortIdMatches },
|
|
517
|
+
journal
|
|
518
|
+
};
|
|
519
|
+
} catch {
|
|
520
|
+
return { domain: { consistent: false }, journal };
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
if (operation.family === 'task-orchestration') {
|
|
525
|
+
if (!taskRef) return { domain: null };
|
|
526
|
+
try {
|
|
527
|
+
const resolved = resolveTaskRef(taskRef, { repoRoot: manifest.repoRoot });
|
|
528
|
+
if (!resolved.ok) return { domain: { consistent: false } };
|
|
529
|
+
const run = readRun(resolved.taskDir);
|
|
530
|
+
if (!run && operation.class !== 'read-only') return { domain: { consistent: false } };
|
|
531
|
+
if (operation.class === 'route.clean-completion') {
|
|
532
|
+
const snapshot = captureRepositorySnapshot(manifest.repoRoot);
|
|
533
|
+
const metadata = parseTypedTaskFrontmatter(fs.readFileSync(resolved.taskMdPath, 'utf8'));
|
|
534
|
+
const completion = run?.completionEvidence ?? null;
|
|
535
|
+
const consistent = run !== null && completion !== null
|
|
536
|
+
&& terminalResult.completionEvidence !== null
|
|
537
|
+
&& isDeepStrictEqual(completion, terminalResult.completionEvidence);
|
|
538
|
+
return {
|
|
539
|
+
domain: {
|
|
540
|
+
consistent,
|
|
541
|
+
status: run?.status ?? null,
|
|
542
|
+
pendingDelegation: run?.pendingDelegation ?? null,
|
|
543
|
+
completionEvidence: completion,
|
|
544
|
+
snapshot,
|
|
545
|
+
lastReviewedCommit: metadata.last_reviewed_commit
|
|
546
|
+
}
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
return { domain: orchestrationDomainEvidence(operation, terminalResult, output, run) };
|
|
550
|
+
} catch {
|
|
551
|
+
return { domain: { consistent: false } };
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
if (operation.family === 'task-create') {
|
|
556
|
+
return { domain: taskCreateDomainEvidence(manifest, output) };
|
|
557
|
+
}
|
|
558
|
+
if (operation.family === 'codex-controller') {
|
|
559
|
+
return { domain: controllerDomainEvidence(manifest, manifestPath, request, output) };
|
|
560
|
+
}
|
|
561
|
+
if (operation.family === 'task-workflow') {
|
|
562
|
+
return {
|
|
563
|
+
domain: { consistent: output?.status === terminalResult.status && output.changed === terminalResult.changed, snapshotValid: true }
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
return { domain: null };
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
function readCommittedCriticalPhases(manifest: SandboxControlManifest, requestId: string): readonly string[] {
|
|
570
|
+
return SANDBOX_CONTROL_REQUIRED_COMPLETION_PHASES.filter((phase) => {
|
|
571
|
+
try {
|
|
572
|
+
readSandboxControlTransition(manifest, requestId, phase);
|
|
573
|
+
return true;
|
|
574
|
+
} catch {
|
|
575
|
+
return false;
|
|
576
|
+
}
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
|
|
221
580
|
function recoveryResponse(
|
|
222
581
|
manifest: SandboxControlManifest,
|
|
582
|
+
manifestPath: string,
|
|
223
583
|
request: SandboxControlRequest,
|
|
224
584
|
evidence: ReturnType<typeof readSandboxControlResultEvidence>,
|
|
225
|
-
payload: ReturnType<typeof readSandboxControlPayload> | null
|
|
585
|
+
payload: ReturnType<typeof readSandboxControlPayload> | null,
|
|
586
|
+
terminalResult: ReturnType<typeof readSandboxControlTerminalResult>
|
|
226
587
|
): SandboxControlResponse | null {
|
|
588
|
+
const operationName = recoveryOperationKey(request, terminalResult, payload?.stdout);
|
|
589
|
+
const operation = operationName ? findSandboxControlRecoveryOperation(request.family, operationName) : null;
|
|
590
|
+
if (!operation) return null;
|
|
591
|
+
if (payload) {
|
|
592
|
+
const payloadTerminal = createSandboxControlTerminalResult(manifest, {
|
|
593
|
+
id: request.id, family: request.family, operation: operationName
|
|
594
|
+
}, payload.stdout);
|
|
595
|
+
if (JSON.stringify(payloadTerminal) !== JSON.stringify(terminalResult)) return unknown(request.id);
|
|
596
|
+
}
|
|
597
|
+
const finalization = request.family === 'task-finalization' ? finalizationRecoveryResponse(manifest, request.id, 0) : null;
|
|
598
|
+
if (evidence.exitCode === 0 && finalization?.status === 'deferred') return null;
|
|
599
|
+
if (evidence.exitCode === 0 && request.family === 'task-workflow' && request.authority?.phase === 'task-event.completed') {
|
|
600
|
+
try {
|
|
601
|
+
const registration = readCodexControllerRegistration(manifestPath);
|
|
602
|
+
const recovered = recoverLifecycleRecoveryOperation(request.authority, {
|
|
603
|
+
repoRoot: manifest.repoRoot,
|
|
604
|
+
capabilityStore: createCodexCapabilityStore(),
|
|
605
|
+
buildIdentity: computeLifecycleBuildIdentity(manifest.repoRoot),
|
|
606
|
+
controllerBinding: {
|
|
607
|
+
instanceDigest: registration.controllerInstanceDigest,
|
|
608
|
+
controlGeneration: registration.controlGeneration
|
|
609
|
+
}
|
|
610
|
+
});
|
|
611
|
+
if (recovered.status !== 'committed') return null;
|
|
612
|
+
} catch (error) {
|
|
613
|
+
appendBrokerAudit(manifest, 'lifecycle-authority-recovery-failed', {
|
|
614
|
+
...requestAuditFields(manifest, request),
|
|
615
|
+
errorCode: error instanceof Error ? error.name : 'LIFECYCLE_AUTHORITY_RECOVERY_FAILED'
|
|
616
|
+
});
|
|
617
|
+
return null;
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
const recovery: RecoveryDomainEvidence = finalization
|
|
621
|
+
? { domain: { consistent: finalization.status === 'matched' } }
|
|
622
|
+
: readRecoveryDomain(manifest, manifestPath, request, operation, terminalResult, payload?.stdout ?? null);
|
|
623
|
+
const binding = operationRecoveryBinding(request.id, manifest.generation, manifest.taskId, request.family, operationName!);
|
|
624
|
+
const decision = classifySandboxControlRecovery({
|
|
625
|
+
operation,
|
|
626
|
+
binding,
|
|
627
|
+
startedCommitted: true,
|
|
628
|
+
terminalResult,
|
|
629
|
+
criticalPhases: readCommittedCriticalPhases(manifest, request.id),
|
|
630
|
+
domain: recovery.domain,
|
|
631
|
+
journal: recovery.journal
|
|
632
|
+
});
|
|
633
|
+
if (decision.outcome === 'unknown' || decision.outcome === 'in-progress') return unknown(request.id);
|
|
634
|
+
if (decision.outcome === 'not-executed') return notExecuted(request.id);
|
|
635
|
+
if (decision.outcome !== 'success' && decision.outcome !== 'failure') return null;
|
|
227
636
|
if (request.family === 'task-finalization' && evidence.exitCode === 0) {
|
|
228
|
-
|
|
229
|
-
return finalization.
|
|
637
|
+
if (finalization?.status !== 'matched') return null;
|
|
638
|
+
return finalization.response ?? null;
|
|
230
639
|
}
|
|
231
|
-
return genericRecoveryResponse(request
|
|
640
|
+
return genericRecoveryResponse(request, evidence.exitCode, payload);
|
|
232
641
|
}
|
|
233
642
|
|
|
234
643
|
function terminalMatchesEvidence(
|
|
235
644
|
manifest: SandboxControlManifest,
|
|
645
|
+
manifestPath: string,
|
|
236
646
|
request: SandboxControlRequest,
|
|
237
647
|
response: SandboxControlResponse,
|
|
238
648
|
evidence: ReturnType<typeof readSandboxControlResultEvidence>,
|
|
239
649
|
payload: ReturnType<typeof readSandboxControlPayload> | null,
|
|
240
|
-
payloadInvalid: boolean
|
|
650
|
+
payloadInvalid: boolean,
|
|
651
|
+
terminalResult: ReturnType<typeof readSandboxControlTerminalResult> | null
|
|
241
652
|
): { valid: boolean; payloadReferenced: boolean } {
|
|
242
653
|
if (request.family === 'task-finalization' && evidence.exitCode === 0) {
|
|
243
|
-
|
|
654
|
+
if (!terminalResult) return { valid: false, payloadReferenced: false };
|
|
655
|
+
const expected = recoveryResponse(manifest, manifestPath, request, evidence, payload, terminalResult);
|
|
244
656
|
return {
|
|
245
657
|
valid: expected !== null && JSON.stringify(response) === JSON.stringify(expected),
|
|
246
658
|
payloadReferenced: false
|
|
@@ -267,8 +679,10 @@ function terminalMatchesEvidence(
|
|
|
267
679
|
};
|
|
268
680
|
}
|
|
269
681
|
if (response.outputState === 'unavailable') {
|
|
270
|
-
const
|
|
271
|
-
|
|
682
|
+
const causes = ['recovery', 'publish'] as const;
|
|
683
|
+
const valid = causes.some((cause) => JSON.stringify(response)
|
|
684
|
+
=== JSON.stringify(genericRecoveryResponse(request, evidence.exitCode, null, cause)));
|
|
685
|
+
return { valid, payloadReferenced: false };
|
|
272
686
|
}
|
|
273
687
|
return {
|
|
274
688
|
valid: response.payload === undefined
|
|
@@ -282,9 +696,20 @@ function publishExecutionResult(
|
|
|
282
696
|
manifest: SandboxControlManifest,
|
|
283
697
|
request: SandboxControlRequest,
|
|
284
698
|
result: SandboxControlExecutionResult,
|
|
699
|
+
broker: BrokerOwner,
|
|
285
700
|
brokerOwns: () => boolean
|
|
286
701
|
): boolean {
|
|
287
702
|
const normalized = sanitizeSandboxControlResult(manifest, result);
|
|
703
|
+
writeSandboxControlTerminalResult(manifest, {
|
|
704
|
+
id: request.id,
|
|
705
|
+
family: request.family,
|
|
706
|
+
operation: operationKey(request, normalized.stdout)
|
|
707
|
+
}, normalized.stdout);
|
|
708
|
+
if (fs.existsSync(path.join(manifest.processingDir, request.id, 'transitions'))) {
|
|
709
|
+
criticalRequestPhase(manifest, request, 'completed', normalized.exitCode === 0 ? 'success' : 'failure');
|
|
710
|
+
criticalRequestPhase(manifest, request, 'evidence-written', normalized.exitCode === 0 ? 'success' : 'failure');
|
|
711
|
+
criticalRequestPhase(manifest, request, 'publish-authorized', normalized.exitCode === 0 ? 'success' : 'failure');
|
|
712
|
+
}
|
|
288
713
|
let terminal: SandboxControlResponse | null = null;
|
|
289
714
|
if (request.family === 'task-finalization') {
|
|
290
715
|
const finalization = finalizationRecoveryResponse(manifest, request.id, normalized.exitCode);
|
|
@@ -299,7 +724,7 @@ function publishExecutionResult(
|
|
|
299
724
|
if (sandboxControlEncodedJsonBytes(inline) <= SANDBOX_CONTROL_MAX_TERMINAL_RECORD_BYTES) {
|
|
300
725
|
terminal = inline;
|
|
301
726
|
}
|
|
302
|
-
let payload = null;
|
|
727
|
+
let payload: ReturnType<typeof createSandboxControlPayload> | null = null;
|
|
303
728
|
if (!terminal) {
|
|
304
729
|
try {
|
|
305
730
|
payload = createSandboxControlPayload(manifest, request.id, normalized);
|
|
@@ -312,21 +737,19 @@ function publishExecutionResult(
|
|
|
312
737
|
} catch {
|
|
313
738
|
payload = null;
|
|
314
739
|
}
|
|
315
|
-
terminal =
|
|
316
|
-
version: 2,
|
|
317
|
-
id: request.id,
|
|
318
|
-
phase: 'completed',
|
|
319
|
-
exitCode: normalized.exitCode,
|
|
320
|
-
stdout: '',
|
|
321
|
-
stderr: payload ? '' : 'SANDBOX_CONTROL_OUTPUT_UNAVAILABLE: output payload was not retained\n',
|
|
322
|
-
error: null,
|
|
323
|
-
outputState: payload ? 'available' : 'unavailable',
|
|
324
|
-
payload: payload ? payloadReference(payload) : null
|
|
325
|
-
};
|
|
740
|
+
terminal = genericRecoveryResponse(request, normalized.exitCode, payload, 'publish');
|
|
326
741
|
}
|
|
327
742
|
}
|
|
328
743
|
if (!brokerOwns()) return false;
|
|
329
|
-
|
|
744
|
+
if (request.family === 'task-finalization' && normalized.exitCode === 0) {
|
|
745
|
+
const view = publishFinalizationTaskView(manifest, broker, request.id, 'healthy', null, null);
|
|
746
|
+
if (view.state === 'unknown') return false;
|
|
747
|
+
}
|
|
748
|
+
const committed = writeSandboxControlResponse(manifest, terminal);
|
|
749
|
+
if (committed && fs.existsSync(path.join(manifest.processingDir, request.id, 'transitions'))) {
|
|
750
|
+
criticalRequestPhase(manifest, request, 'published-committed', normalized.exitCode === 0 ? 'success' : 'failure');
|
|
751
|
+
}
|
|
752
|
+
return committed;
|
|
330
753
|
}
|
|
331
754
|
|
|
332
755
|
function writeAcceptedResponse(manifest: SandboxControlManifest, response: SandboxControlResponse): void {
|
|
@@ -391,21 +814,40 @@ function bindingReason(manifest: SandboxControlManifest): string | null {
|
|
|
391
814
|
}
|
|
392
815
|
}
|
|
393
816
|
|
|
394
|
-
function
|
|
817
|
+
function assertCurrentSandboxControlIdentity(manifest: SandboxControlManifest, manifestPath: string): void {
|
|
818
|
+
const result = validateSandboxControlIdentity({
|
|
819
|
+
publicStatusDir: manifest.publicStatusDir,
|
|
820
|
+
root: path.dirname(path.resolve(manifestPath)),
|
|
821
|
+
mode: manifest.mode,
|
|
822
|
+
taskId: manifest.taskId,
|
|
823
|
+
generation: manifest.generation,
|
|
824
|
+
controlRootId: manifest.controlRootId
|
|
825
|
+
});
|
|
826
|
+
if (result.state !== 'valid') {
|
|
827
|
+
throw new Error(`SANDBOX_CONTROL_IDENTITY_${result.state.replaceAll('-', '_').toUpperCase()}`);
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
function recoverProcessing(manifest: SandboxControlManifest, manifestPath: string, broker: BrokerOwner, brokerOwns: () => boolean): boolean {
|
|
395
832
|
for (const entry of fs.readdirSync(manifest.processingDir, { withFileTypes: true })) {
|
|
396
833
|
if (!entry.isDirectory() || !/^[a-f0-9-]{16,64}$/.test(entry.name)) continue;
|
|
397
834
|
if (!brokerOwns()) return false;
|
|
398
835
|
const descriptor = executionPath(manifest, entry.name);
|
|
399
836
|
const existingResponse = responsePath(manifest, entry.name);
|
|
400
837
|
const processingDirectory = path.join(manifest.processingDir, entry.name);
|
|
838
|
+
const transitionsDirectory = path.join(processingDirectory, 'transitions');
|
|
839
|
+
const transitionProtocolActive = fs.existsSync(transitionsDirectory);
|
|
840
|
+
const startedCommitted = fs.existsSync(path.join(transitionsDirectory, 'started-committed.json'));
|
|
401
841
|
let payloadReferenced = false;
|
|
402
842
|
if (fs.existsSync(descriptor)) {
|
|
403
843
|
const execution = readExecution(descriptor);
|
|
404
844
|
let resultEvidence: ReturnType<typeof readSandboxControlResultEvidence> | null = null;
|
|
845
|
+
let terminalResult: ReturnType<typeof readSandboxControlTerminalResult> | null = null;
|
|
405
846
|
let payload: ReturnType<typeof readSandboxControlPayload> | null = null;
|
|
406
847
|
let payloadInvalid = false;
|
|
407
848
|
let request: SandboxControlRequest | null = null;
|
|
408
849
|
let existingTerminalResponse: SandboxControlResponse | null = null;
|
|
850
|
+
let terminalReconciled = false;
|
|
409
851
|
try {
|
|
410
852
|
const rawRequest = JSON.parse(fs.readFileSync(path.join(processingDirectory, 'request.json'), 'utf8')) as Record<string, unknown>;
|
|
411
853
|
request = validateSandboxControlRequest(rawRequest, manifest, {
|
|
@@ -425,6 +867,18 @@ function recoverProcessing(manifest: SandboxControlManifest, brokerOwns: () => b
|
|
|
425
867
|
resultEvidence = null;
|
|
426
868
|
}
|
|
427
869
|
}
|
|
870
|
+
const terminalResultPathValue = path.join(processingDirectory, 'terminal-result.json');
|
|
871
|
+
if (fs.existsSync(terminalResultPathValue)) {
|
|
872
|
+
try {
|
|
873
|
+
terminalResult = readSandboxControlTerminalResult(terminalResultPathValue);
|
|
874
|
+
if (terminalResult.requestId !== entry.name || terminalResult.generation !== manifest.generation
|
|
875
|
+
|| !request || !terminalResultMatchesRequest(request, terminalResult)) {
|
|
876
|
+
terminalResult = null;
|
|
877
|
+
}
|
|
878
|
+
} catch {
|
|
879
|
+
terminalResult = null;
|
|
880
|
+
}
|
|
881
|
+
}
|
|
428
882
|
const publishedPayloadPath = payloadPath(manifest, entry.name);
|
|
429
883
|
if (fs.existsSync(publishedPayloadPath)) {
|
|
430
884
|
try {
|
|
@@ -437,11 +891,21 @@ function recoverProcessing(manifest: SandboxControlManifest, brokerOwns: () => b
|
|
|
437
891
|
payloadInvalid = true;
|
|
438
892
|
}
|
|
439
893
|
}
|
|
894
|
+
if (transitionProtocolActive && startedCommitted && !terminalResult) {
|
|
895
|
+
if (!brokerOwns()) return false;
|
|
896
|
+
if (!terminateSandboxControlExecution(execution)) {
|
|
897
|
+
throw new Error(`SANDBOX_CONTROL_EXECUTION_STILL_RUNNING: ${entry.name}`);
|
|
898
|
+
}
|
|
899
|
+
appendDiagnosticAudit(manifest, 'orphan-tree-terminated', { requestId: entry.name });
|
|
900
|
+
writeSandboxControlResponse(manifest, unknown(entry.name));
|
|
901
|
+
continue;
|
|
902
|
+
}
|
|
903
|
+
if (transitionProtocolActive && !startedCommitted && terminalResult) continue;
|
|
440
904
|
if (!terminateSandboxControlExecution(execution)) {
|
|
441
905
|
throw new Error(`SANDBOX_CONTROL_EXECUTION_STILL_RUNNING: ${entry.name}`);
|
|
442
906
|
}
|
|
443
907
|
if (!brokerOwns()) return false;
|
|
444
|
-
|
|
908
|
+
appendDiagnosticAudit(manifest, 'orphan-tree-terminated', { requestId: entry.name });
|
|
445
909
|
let terminal = false;
|
|
446
910
|
if (fs.existsSync(existingResponse)) {
|
|
447
911
|
try {
|
|
@@ -459,7 +923,7 @@ function recoverProcessing(manifest: SandboxControlManifest, brokerOwns: () => b
|
|
|
459
923
|
|| response.payload.stdoutSha256 !== payload.stdoutSha256
|
|
460
924
|
|| response.payload.stderrSha256 !== payload.stderrSha256) throw new Error('payload missing or mismatched');
|
|
461
925
|
payloadReferenced = true;
|
|
462
|
-
} else if (terminal && (response.outputState !== undefined
|
|
926
|
+
} else if (terminal && ((response.outputState !== undefined && response.outputState !== 'unavailable')
|
|
463
927
|
|| response.payload !== undefined && response.payload !== null)) {
|
|
464
928
|
throw new Error('payload state invalid');
|
|
465
929
|
}
|
|
@@ -470,20 +934,61 @@ function recoverProcessing(manifest: SandboxControlManifest, brokerOwns: () => b
|
|
|
470
934
|
}
|
|
471
935
|
if (terminal && (!request || !resultEvidence)) continue;
|
|
472
936
|
if (terminal && request && resultEvidence) {
|
|
473
|
-
|
|
937
|
+
if (!terminalResult && request.family !== 'task-create') continue;
|
|
938
|
+
const reconciliation = terminalMatchesEvidence(
|
|
939
|
+
manifest,
|
|
940
|
+
manifestPath,
|
|
941
|
+
request,
|
|
942
|
+
existingTerminalResponse!,
|
|
943
|
+
resultEvidence,
|
|
944
|
+
payload,
|
|
945
|
+
payloadInvalid,
|
|
946
|
+
terminalResult
|
|
947
|
+
);
|
|
474
948
|
if (!reconciliation.valid) continue;
|
|
949
|
+
terminalReconciled = true;
|
|
475
950
|
payloadReferenced = reconciliation.payloadReferenced;
|
|
951
|
+
if (request.family === 'task-finalization' && resultEvidence.exitCode === 0) {
|
|
952
|
+
const view = publishFinalizationTaskView(manifest, broker, request.id, 'starting', null, null);
|
|
953
|
+
if (view.state === 'unknown') continue;
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
if (!terminalResult && !terminalReconciled) {
|
|
957
|
+
if (terminal) continue;
|
|
958
|
+
if (!brokerOwns()) return false;
|
|
959
|
+
if (!resultEvidence || !request || payloadInvalid) continue;
|
|
960
|
+
if (request.family === 'task-create' && resultEvidence.exitCode === 0 && payload === null) {
|
|
961
|
+
writeSandboxControlResponse(manifest, genericRecoveryResponse(request, resultEvidence.exitCode, null));
|
|
962
|
+
terminalReconciled = true;
|
|
963
|
+
terminal = true;
|
|
964
|
+
} else {
|
|
965
|
+
writeSandboxControlResponse(manifest, unknown(entry.name));
|
|
966
|
+
continue;
|
|
967
|
+
}
|
|
476
968
|
}
|
|
477
969
|
if (!terminal) {
|
|
478
970
|
if (!brokerOwns()) return false;
|
|
479
971
|
if (!resultEvidence || !request || payloadInvalid) continue;
|
|
480
|
-
const recovered = recoveryResponse(manifest, request, resultEvidence, payload);
|
|
972
|
+
const recovered = recoveryResponse(manifest, manifestPath, request, resultEvidence, payload, terminalResult!);
|
|
481
973
|
if (!recovered) continue;
|
|
974
|
+
if (request.family === 'task-finalization' && resultEvidence.exitCode === 0) {
|
|
975
|
+
const view = publishFinalizationTaskView(manifest, broker, request.id, 'starting', null, null);
|
|
976
|
+
if (view.state === 'unknown') continue;
|
|
977
|
+
}
|
|
482
978
|
writeSandboxControlResponse(manifest, recovered);
|
|
979
|
+
const preserveRecoveryEvidence = recovered.error?.code === 'SANDBOX_CONTROL_RESULT_UNKNOWN';
|
|
980
|
+
if (transitionProtocolActive && !preserveRecoveryEvidence) {
|
|
981
|
+
writeSandboxControlTransition(manifest, { requestId: entry.name, phase: 'recovered' });
|
|
982
|
+
}
|
|
983
|
+
if (preserveRecoveryEvidence) continue;
|
|
483
984
|
payloadReferenced = Boolean(payload) && request.family !== 'task-finalization';
|
|
484
985
|
}
|
|
485
986
|
} else {
|
|
486
987
|
if (!brokerOwns()) return false;
|
|
988
|
+
if (transitionProtocolActive && startedCommitted) {
|
|
989
|
+
writeSandboxControlResponse(manifest, unknown(entry.name));
|
|
990
|
+
continue;
|
|
991
|
+
}
|
|
487
992
|
writeSandboxControlResponse(manifest, notExecuted(entry.name));
|
|
488
993
|
}
|
|
489
994
|
if (!brokerOwns()) return false;
|
|
@@ -499,7 +1004,13 @@ function delay(ms: number): Promise<void> {
|
|
|
499
1004
|
}
|
|
500
1005
|
|
|
501
1006
|
export function sandboxControlSafeEnv(env: NodeJS.ProcessEnv = process.env): NodeJS.ProcessEnv {
|
|
502
|
-
return Object.fromEntries(Object.entries(env).filter(([key]) =>
|
|
1007
|
+
return Object.fromEntries(Object.entries(env).filter(([key]) => {
|
|
1008
|
+
const normalized = key.toUpperCase();
|
|
1009
|
+
return !normalized.startsWith('AGENT_INFRA_CONTROL_')
|
|
1010
|
+
&& normalized !== 'AGENT_INFRA_TASK_ID'
|
|
1011
|
+
&& normalized !== 'AGENT_INFRA_RUNTIME_DIR'
|
|
1012
|
+
&& normalized !== 'AGENT_INFRA_EXECUTOR_MANIFEST';
|
|
1013
|
+
}));
|
|
503
1014
|
}
|
|
504
1015
|
|
|
505
1016
|
export async function serveSandboxControl(
|
|
@@ -519,6 +1030,7 @@ export async function serveSandboxControl(
|
|
|
519
1030
|
const prepareExecution = options.prepareExecution ?? prepareSandboxControlExecution;
|
|
520
1031
|
const manifest = readSandboxControlManifest(manifestPath);
|
|
521
1032
|
const root = path.dirname(manifestPath);
|
|
1033
|
+
assertCurrentSandboxControlIdentity(manifest, manifestPath);
|
|
522
1034
|
const startTime = getProcessStartTime(process.pid);
|
|
523
1035
|
if (!startTime) throw new Error('SANDBOX_CONTROL_BROKER_IDENTITY_UNAVAILABLE');
|
|
524
1036
|
const brokerId = randomUUID();
|
|
@@ -562,6 +1074,25 @@ export async function serveSandboxControl(
|
|
|
562
1074
|
releaseStartup();
|
|
563
1075
|
}
|
|
564
1076
|
let active: ActiveExecution | null = null;
|
|
1077
|
+
let startupReceipt: unknown = null;
|
|
1078
|
+
if (manifest.taskId) {
|
|
1079
|
+
try {
|
|
1080
|
+
startupReceipt = readTaskFinalizationReceipt(manifest.repoRoot, manifest.taskId);
|
|
1081
|
+
} catch {
|
|
1082
|
+
// Preserve malformed receipt evidence as unknown rather than treating it as no receipt.
|
|
1083
|
+
startupReceipt = {};
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
let taskView = taskViewForManifest({
|
|
1087
|
+
...manifest,
|
|
1088
|
+
receipt: startupReceipt
|
|
1089
|
+
});
|
|
1090
|
+
try {
|
|
1091
|
+
const previous = readSandboxControlStatus(manifest.publicStatusDir);
|
|
1092
|
+
if (previous.generation === manifest.generation) taskView = mergeSandboxTaskView(taskView, previous.taskView);
|
|
1093
|
+
} catch {
|
|
1094
|
+
// A fresh control root has no previous task-view projection.
|
|
1095
|
+
}
|
|
565
1096
|
let lastState = '';
|
|
566
1097
|
let lastStatusAt = 0;
|
|
567
1098
|
let nextBindingCheckAt = 0;
|
|
@@ -572,16 +1103,37 @@ export async function serveSandboxControl(
|
|
|
572
1103
|
let bindingBackoffMs: number = timing.parkedBindingInitialMs;
|
|
573
1104
|
try {
|
|
574
1105
|
if (!brokerOwns()) return;
|
|
575
|
-
|
|
1106
|
+
if (taskView.state === 'current' && taskView.observedSource === 'completed') {
|
|
1107
|
+
// Recheck durable re-entry evidence before publishing a trusted startup view.
|
|
1108
|
+
const unverified = { ...taskView, state: 'unknown' as const,
|
|
1109
|
+
reasonCode: 'SANDBOX_COMPLETED_REENTRY_EVIDENCE_INVALID' };
|
|
1110
|
+
try {
|
|
1111
|
+
taskView = await completedReentryView(manifest, inspectContainer) ?? unverified;
|
|
1112
|
+
} catch {
|
|
1113
|
+
taskView = unverified;
|
|
1114
|
+
}
|
|
1115
|
+
if (!brokerOwns()) return;
|
|
1116
|
+
}
|
|
1117
|
+
writeSandboxControlStatus(manifest, broker, 'starting', null, null, Date.now(), taskView);
|
|
576
1118
|
if (!brokerOwns()) return;
|
|
577
|
-
|
|
578
|
-
|
|
1119
|
+
appendBrokerAudit(manifest, 'broker-start', {
|
|
1120
|
+
pid: broker.pid,
|
|
1121
|
+
brokerId: broker.brokerId
|
|
1122
|
+
});
|
|
1123
|
+
if (!recoverProcessing(manifest, manifestPath, broker, brokerOwns)) return;
|
|
1124
|
+
try {
|
|
1125
|
+
const published = readSandboxControlStatus(manifest.publicStatusDir);
|
|
1126
|
+
if (published.generation === manifest.generation) taskView = published.taskView;
|
|
1127
|
+
} catch {
|
|
1128
|
+
// The regular heartbeat will publish an invalid or missing projection as unknown.
|
|
1129
|
+
}
|
|
579
1130
|
while (!signal.aborted) {
|
|
580
1131
|
let settledExecution: ActiveExecution | null = null;
|
|
581
1132
|
if (!brokerOwns()) break;
|
|
582
1133
|
let current: SandboxControlManifest;
|
|
583
1134
|
try {
|
|
584
1135
|
current = readSandboxControlManifest(manifestPath);
|
|
1136
|
+
assertCurrentSandboxControlIdentity(current, manifestPath);
|
|
585
1137
|
} catch {
|
|
586
1138
|
break;
|
|
587
1139
|
}
|
|
@@ -612,7 +1164,7 @@ export async function serveSandboxControl(
|
|
|
612
1164
|
nextContainerHeartbeatAt = Date.now() + timing.containerHeartbeatMs;
|
|
613
1165
|
} else if (observation.state === 'unknown') {
|
|
614
1166
|
containerReasonCode = 'SANDBOX_CONTROL_CONTAINER_UNKNOWN';
|
|
615
|
-
|
|
1167
|
+
appendDiagnosticAudit(manifest, 'container-heartbeat-unknown', { reason: observation.reason });
|
|
616
1168
|
const backoff = nextSandboxControlBackoff(containerBackoffMs, timing.slowCheckMs);
|
|
617
1169
|
nextContainerHeartbeatAt = Date.now() + backoff.delayMs;
|
|
618
1170
|
containerBackoffMs = backoff.nextDelayMs;
|
|
@@ -628,7 +1180,7 @@ export async function serveSandboxControl(
|
|
|
628
1180
|
} catch (error) {
|
|
629
1181
|
if (!brokerOwns()) break;
|
|
630
1182
|
containerReasonCode = 'SANDBOX_CONTROL_CONTAINER_ABSENT_GC_FAILED';
|
|
631
|
-
|
|
1183
|
+
appendDiagnosticAudit(manifest, 'container-gc-failed', { reason: controlError(error).code });
|
|
632
1184
|
const backoff = nextSandboxControlBackoff(containerBackoffMs, timing.slowCheckMs);
|
|
633
1185
|
nextContainerHeartbeatAt = Date.now() + backoff.delayMs;
|
|
634
1186
|
containerBackoffMs = backoff.nextDelayMs;
|
|
@@ -647,7 +1199,7 @@ export async function serveSandboxControl(
|
|
|
647
1199
|
if (!brokerOwns()) break;
|
|
648
1200
|
if (cleanupStaleSandboxControlLease(manifest)) {
|
|
649
1201
|
if (!brokerOwns()) break;
|
|
650
|
-
|
|
1202
|
+
appendBrokerAudit(manifest, 'lease-stale-cleanup');
|
|
651
1203
|
}
|
|
652
1204
|
if (readActiveLease(manifest)) reasonCode = 'SANDBOX_CONTROL_HANDOFF_ACTIVE';
|
|
653
1205
|
} catch {
|
|
@@ -667,25 +1219,53 @@ export async function serveSandboxControl(
|
|
|
667
1219
|
}
|
|
668
1220
|
reasonCode ??= containerReasonCode;
|
|
669
1221
|
reasonCode ??= bindingReasonCode;
|
|
1222
|
+
if (!reasonCode && !active && taskView.state !== 'current') {
|
|
1223
|
+
try {
|
|
1224
|
+
const reentered = await completedReentryView(manifest, inspectContainer);
|
|
1225
|
+
if (reentered && brokerOwns() && !readActiveLease(manifest) && !bindingCheck(manifest)) {
|
|
1226
|
+
taskView = reentered;
|
|
1227
|
+
}
|
|
1228
|
+
} catch {
|
|
1229
|
+
// Invalid or changed re-entry evidence never clears the existing task view.
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
670
1232
|
const state = reasonCode ? 'parked' : active ? 'busy' : 'healthy';
|
|
671
1233
|
const stateKey = `${state}:${reasonCode ?? ''}:${active?.request.id ?? ''}`;
|
|
672
1234
|
if (brokerOwns() && (stateKey !== lastState || now - lastStatusAt >= timing.controlTickMs)) {
|
|
673
|
-
writeSandboxControlStatus(manifest, broker, state, reasonCode, active?.request.id ?? null, now);
|
|
1235
|
+
writeSandboxControlStatus(manifest, broker, state, reasonCode, active?.request.id ?? null, now, taskView);
|
|
674
1236
|
lastStatusAt = now;
|
|
675
1237
|
}
|
|
676
1238
|
if (brokerOwns() && stateKey !== lastState) {
|
|
677
|
-
|
|
1239
|
+
appendBrokerAudit(manifest, 'broker-state', { state, reasonCode, requestId: active?.request.id ?? null });
|
|
678
1240
|
lastState = stateKey;
|
|
679
1241
|
}
|
|
680
1242
|
if (settledExecution) {
|
|
681
1243
|
if (!brokerOwns()) break;
|
|
682
1244
|
let terminalCommitted = false;
|
|
683
1245
|
if (settledExecution.result && settledExecution.resultEvidenceWritten) {
|
|
684
|
-
|
|
1246
|
+
if (!consumeLifecycleAuthorityForResult(manifest, settledExecution)) {
|
|
1247
|
+
active = settledExecution;
|
|
1248
|
+
continue;
|
|
1249
|
+
}
|
|
1250
|
+
terminalCommitted = publishExecutionResult(manifest, settledExecution.request, settledExecution.result, broker, brokerOwns);
|
|
1251
|
+
if (terminalCommitted && settledExecution.request.family === 'task-finalization') {
|
|
1252
|
+
try {
|
|
1253
|
+
const published = readSandboxControlStatus(manifest.publicStatusDir);
|
|
1254
|
+
if (published.generation === manifest.generation) taskView = published.taskView;
|
|
1255
|
+
} catch {
|
|
1256
|
+
taskView = { state: 'unknown', taskId: manifest.taskId, observedSource: 'unknown', receipt: null, reasonCode: 'SANDBOX_TASK_VIEW_STATUS_INVALID' };
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
685
1259
|
} else {
|
|
686
1260
|
terminalCommitted = writeSandboxControlResponse(manifest, unknown(settledExecution.request.id));
|
|
687
1261
|
}
|
|
688
1262
|
if (!terminalCommitted) return;
|
|
1263
|
+
appendBrokerAudit(manifest, 'executor-result-published', {
|
|
1264
|
+
...requestAuditFields(manifest, settledExecution.request),
|
|
1265
|
+
resultAvailable: settledExecution.result !== null,
|
|
1266
|
+
resultEvidenceWritten: settledExecution.resultEvidenceWritten,
|
|
1267
|
+
...(settledExecution.result ? resultAuditFields(settledExecution.result) : {})
|
|
1268
|
+
});
|
|
689
1269
|
if (!brokerOwns()) break;
|
|
690
1270
|
removeAcceptedResponse(manifest, settledExecution.request.id);
|
|
691
1271
|
fs.rmSync(path.join(manifest.processingDir, settledExecution.request.id), { recursive: true, force: true });
|
|
@@ -697,12 +1277,20 @@ export async function serveSandboxControl(
|
|
|
697
1277
|
const id = name.slice(0, -5);
|
|
698
1278
|
const source = path.join(requestsDir, name);
|
|
699
1279
|
let claimed: string | null = null;
|
|
1280
|
+
let validatedRequest: SandboxControlRequest | null = null;
|
|
1281
|
+
let prepared: PreparedSandboxControlExecution | null = null;
|
|
700
1282
|
try {
|
|
701
1283
|
if (!brokerOwns()) {
|
|
702
1284
|
retiring = true;
|
|
703
1285
|
break;
|
|
704
1286
|
}
|
|
705
1287
|
claimed = claimRequest(manifest, source, id);
|
|
1288
|
+
appendBrokerAudit(manifest, 'request-claimed', {
|
|
1289
|
+
requestId: id,
|
|
1290
|
+
requestSourcePath: source,
|
|
1291
|
+
requestClaimedPath: claimed,
|
|
1292
|
+
requestProcessingDir: path.dirname(claimed)
|
|
1293
|
+
});
|
|
706
1294
|
if (!brokerOwns()) {
|
|
707
1295
|
retiring = true;
|
|
708
1296
|
break;
|
|
@@ -715,29 +1303,99 @@ export async function serveSandboxControl(
|
|
|
715
1303
|
if (reasonCode) throw new Error(reasonCode);
|
|
716
1304
|
if (active) throw new Error('SANDBOX_CONTROL_BUSY');
|
|
717
1305
|
const request = validateSandboxControlRequest(JSON.parse(fs.readFileSync(claimed, 'utf8')), manifest);
|
|
1306
|
+
validatedRequest = request;
|
|
1307
|
+
criticalRequestPhase(manifest, request, 'validated', 'in-progress');
|
|
1308
|
+
appendBrokerAudit(manifest, 'request-validated', {
|
|
1309
|
+
...requestAuditFields(manifest, request),
|
|
1310
|
+
requestPath: claimed
|
|
1311
|
+
});
|
|
718
1312
|
if (bindingCheck(manifest)) throw new Error('SANDBOX_WORKTREE_BINDING_LOST');
|
|
719
1313
|
if (readActiveLease(manifest)) throw new Error('SANDBOX_CONTROL_HANDOFF_ACTIVE');
|
|
1314
|
+
criticalRequestPhase(manifest, request, 'gated', 'in-progress');
|
|
1315
|
+
appendBrokerAudit(manifest, 'request-gates-passed', {
|
|
1316
|
+
...requestAuditFields(manifest, request),
|
|
1317
|
+
requestPath: claimed,
|
|
1318
|
+
bindingChecked: true,
|
|
1319
|
+
handoffLeaseChecked: true
|
|
1320
|
+
});
|
|
1321
|
+
criticalRequestPhase(manifest, request, 'reserved', 'in-progress');
|
|
720
1322
|
writeSandboxControlReservation(manifest, request.id, sandboxControlGenerationUsage(manifest));
|
|
721
|
-
|
|
1323
|
+
appendBrokerAudit(manifest, 'executor-reservation-written', {
|
|
1324
|
+
...requestAuditFields(manifest, request),
|
|
1325
|
+
requestPath: claimed,
|
|
1326
|
+
executionPath: executionPath(manifest, request.id)
|
|
1327
|
+
});
|
|
1328
|
+
appendBrokerAudit(manifest, 'executor-prepare-start', {
|
|
1329
|
+
...requestAuditFields(manifest, request),
|
|
1330
|
+
requestPath: claimed,
|
|
1331
|
+
executorCwd: manifest.repoRoot,
|
|
1332
|
+
executorEntry: options.internalCliPath ?? process.argv[1] ?? null
|
|
1333
|
+
});
|
|
1334
|
+
const lifecycleAuthority = lifecycleAuthorityForRequest(manifest, manifestPath, request);
|
|
1335
|
+
if (lifecycleAuthority?.status === 'rejected') {
|
|
1336
|
+
throw Object.assign(
|
|
1337
|
+
new Error(lifecycleAuthority.error?.message ?? 'lifecycle authority was rejected'),
|
|
1338
|
+
{ code: lifecycleAuthority.error?.code ?? 'LIFECYCLE_AUTHORITY_REJECTED' }
|
|
1339
|
+
);
|
|
1340
|
+
}
|
|
1341
|
+
prepared = await prepareExecution({
|
|
722
1342
|
manifest, manifestPath, request, requestPath: claimed,
|
|
723
|
-
internalCliPath: options.internalCliPath ?? process.argv[1]
|
|
1343
|
+
internalCliPath: options.internalCliPath ?? process.argv[1]!,
|
|
1344
|
+
...(lifecycleAuthority ? { lifecycleAuthority } : {})
|
|
1345
|
+
});
|
|
1346
|
+
const preparedExecution = prepared;
|
|
1347
|
+
if (!preparedExecution) throw new Error('SANDBOX_CONTROL_EXECUTION_PREPARE_INVALID');
|
|
1348
|
+
criticalRequestPhase(manifest, request, 'prepared', 'in-progress', `${preparedExecution.execution.child.pid}`);
|
|
1349
|
+
appendBrokerAudit(manifest, 'executor-prepared', {
|
|
1350
|
+
...requestAuditFields(manifest, request),
|
|
1351
|
+
requestPath: claimed,
|
|
1352
|
+
executorCwd: manifest.repoRoot,
|
|
1353
|
+
childPid: preparedExecution.execution.child.pid,
|
|
1354
|
+
childStartTime: preparedExecution.execution.child.startTime,
|
|
1355
|
+
childProcessGroupId: preparedExecution.execution.child.processGroupId,
|
|
1356
|
+
executionPath: executionPath(manifest, request.id)
|
|
724
1357
|
});
|
|
725
1358
|
const execution: ActiveExecution = {
|
|
726
|
-
request, prepared
|
|
1359
|
+
request, prepared: preparedExecution, lifecycleAuthority, result: null,
|
|
1360
|
+
resultEvidenceWritten: false, failure: null, settled: false
|
|
727
1361
|
};
|
|
728
1362
|
active = execution;
|
|
729
|
-
|
|
1363
|
+
preparedExecution.completion.then(
|
|
730
1364
|
(result) => {
|
|
731
1365
|
execution.result = sanitizeSandboxControlResult(manifest, result);
|
|
1366
|
+
appendBrokerAudit(manifest, 'executor-completed', {
|
|
1367
|
+
...requestAuditFields(manifest, request),
|
|
1368
|
+
...resultAuditFields(execution.result),
|
|
1369
|
+
childPid: preparedExecution.execution.child.pid,
|
|
1370
|
+
childStartTime: preparedExecution.execution.child.startTime
|
|
1371
|
+
});
|
|
732
1372
|
try {
|
|
733
1373
|
writeSandboxControlResultEvidence(manifest, request.id, execution.result);
|
|
734
1374
|
execution.resultEvidenceWritten = true;
|
|
735
1375
|
execution.settled = true;
|
|
1376
|
+
appendBrokerAudit(manifest, 'executor-result-evidence-written', {
|
|
1377
|
+
...requestAuditFields(manifest, request),
|
|
1378
|
+
resultPath: path.join(manifest.processingDir, request.id, 'result.json'),
|
|
1379
|
+
...resultAuditFields(execution.result)
|
|
1380
|
+
});
|
|
736
1381
|
} catch (error) {
|
|
737
1382
|
execution.failure = error;
|
|
1383
|
+
appendBrokerAudit(manifest, 'executor-result-evidence-failed', {
|
|
1384
|
+
...requestAuditFields(manifest, request),
|
|
1385
|
+
errorType: error instanceof Error ? error.name : typeof error,
|
|
1386
|
+
});
|
|
738
1387
|
}
|
|
739
1388
|
},
|
|
740
|
-
(error) => {
|
|
1389
|
+
(error) => {
|
|
1390
|
+
execution.failure = error;
|
|
1391
|
+
execution.settled = true;
|
|
1392
|
+
appendBrokerAudit(manifest, 'executor-failed', {
|
|
1393
|
+
...requestAuditFields(manifest, request),
|
|
1394
|
+
childPid: preparedExecution.execution.child.pid,
|
|
1395
|
+
childStartTime: preparedExecution.execution.child.startTime,
|
|
1396
|
+
errorType: error instanceof Error ? error.name : typeof error,
|
|
1397
|
+
});
|
|
1398
|
+
}
|
|
741
1399
|
);
|
|
742
1400
|
if (!brokerOwns()) {
|
|
743
1401
|
prepared.terminate(false);
|
|
@@ -751,9 +1409,16 @@ export async function serveSandboxControl(
|
|
|
751
1409
|
retiring = true;
|
|
752
1410
|
continue;
|
|
753
1411
|
}
|
|
1412
|
+
criticalRequestPhase(manifest, request, 'accepted-authorized', 'in-progress');
|
|
754
1413
|
writeAcceptedResponse(manifest, {
|
|
755
1414
|
version: 2, id, phase: 'accepted', exitCode: null, stdout: '', stderr: '', error: null
|
|
756
1415
|
});
|
|
1416
|
+
writeSandboxControlTransition(manifest, { requestId: request.id, phase: 'accepted-committed' });
|
|
1417
|
+
appendBrokerAudit(manifest, 'request-accepted', {
|
|
1418
|
+
...requestAuditFields(manifest, request),
|
|
1419
|
+
acceptedPath: acceptedResponsePath(manifest, id),
|
|
1420
|
+
executionPhase: 'prepared'
|
|
1421
|
+
});
|
|
757
1422
|
if (!brokerOwns()) {
|
|
758
1423
|
prepared.terminate(false);
|
|
759
1424
|
active = null;
|
|
@@ -761,8 +1426,19 @@ export async function serveSandboxControl(
|
|
|
761
1426
|
break;
|
|
762
1427
|
}
|
|
763
1428
|
try {
|
|
1429
|
+
criticalRequestPhase(manifest, request, 'start-authorized', 'in-progress', `${prepared.execution.child.pid}`);
|
|
1430
|
+
appendBrokerAudit(manifest, 'executor-start', {
|
|
1431
|
+
...requestAuditFields(manifest, request),
|
|
1432
|
+
childPid: prepared.execution.child.pid,
|
|
1433
|
+
childStartTime: prepared.execution.child.startTime,
|
|
1434
|
+
executionPath: executionPath(manifest, request.id)
|
|
1435
|
+
});
|
|
764
1436
|
prepared.start(brokerOwns);
|
|
765
1437
|
} catch (error) {
|
|
1438
|
+
appendBrokerAudit(manifest, 'executor-start-failed', {
|
|
1439
|
+
...requestAuditFields(manifest, request),
|
|
1440
|
+
errorType: error instanceof Error ? error.name : typeof error,
|
|
1441
|
+
});
|
|
766
1442
|
const owned = brokerOwns();
|
|
767
1443
|
prepared.terminate(owned);
|
|
768
1444
|
if (!owned || !brokerOwns()) {
|
|
@@ -784,19 +1460,35 @@ export async function serveSandboxControl(
|
|
|
784
1460
|
retiring = true;
|
|
785
1461
|
break;
|
|
786
1462
|
}
|
|
787
|
-
|
|
1463
|
+
appendBrokerAudit(manifest, 'executor-gate-failed', { requestId: id });
|
|
788
1464
|
continue;
|
|
789
1465
|
}
|
|
790
1466
|
} catch (error) {
|
|
1467
|
+
if (prepared) {
|
|
1468
|
+
try { prepared.terminate(brokerOwns()); } catch { /* cleanup is best effort after a failed admission */ }
|
|
1469
|
+
prepared = null;
|
|
1470
|
+
active = null;
|
|
1471
|
+
}
|
|
791
1472
|
if (!brokerOwns()) {
|
|
792
1473
|
retiring = true;
|
|
793
1474
|
break;
|
|
794
1475
|
}
|
|
1476
|
+
const detail = controlError(error);
|
|
1477
|
+
appendBrokerAudit(manifest, 'request-rejected', {
|
|
1478
|
+
requestId: id,
|
|
1479
|
+
requestFamily: validatedRequest?.family ?? null,
|
|
1480
|
+
sandboxTaskId: manifest.taskId,
|
|
1481
|
+
requestPath: claimed,
|
|
1482
|
+
errorCode: detail.code,
|
|
1483
|
+
errorRetryable: detail.retryable,
|
|
1484
|
+
errorType: error instanceof Error ? error.name : typeof error,
|
|
1485
|
+
});
|
|
795
1486
|
writeSandboxControlResponse(manifest, rejected(id, error));
|
|
796
1487
|
if (!brokerOwns()) {
|
|
797
1488
|
retiring = true;
|
|
798
1489
|
break;
|
|
799
1490
|
}
|
|
1491
|
+
removeAcceptedResponse(manifest, id);
|
|
800
1492
|
if (claimed) fs.rmSync(path.dirname(claimed), { recursive: true, force: true });
|
|
801
1493
|
}
|
|
802
1494
|
}
|
|
@@ -818,7 +1510,8 @@ export async function serveSandboxControl(
|
|
|
818
1510
|
}
|
|
819
1511
|
}
|
|
820
1512
|
if (owned && active.result && active.resultEvidenceWritten) {
|
|
821
|
-
if (
|
|
1513
|
+
if (consumeLifecycleAuthorityForResult(manifest, active)
|
|
1514
|
+
&& publishExecutionResult(manifest, active.request, active.result, broker, brokerOwns)) {
|
|
822
1515
|
if (brokerOwns()) {
|
|
823
1516
|
removeAcceptedResponse(manifest, active.request.id);
|
|
824
1517
|
fs.rmSync(path.join(manifest.processingDir, active.request.id), { recursive: true, force: true });
|
|
@@ -830,7 +1523,11 @@ export async function serveSandboxControl(
|
|
|
830
1523
|
const terminationConfirmed = active.prepared.terminate(owned);
|
|
831
1524
|
if (owned && brokerOwns() && active.request.family === 'task-finalization') {
|
|
832
1525
|
const recovered = finalizationRecoveryResponse(manifest, active.request.id, 0);
|
|
833
|
-
|
|
1526
|
+
const view = recovered.status === 'matched'
|
|
1527
|
+
? publishFinalizationTaskView(manifest, broker, active.request.id, 'healthy', null, null)
|
|
1528
|
+
: null;
|
|
1529
|
+
if (view?.state !== 'unknown' && recovered.status === 'matched' && recovered.response
|
|
1530
|
+
&& writeSandboxControlResponse(manifest, recovered.response)) {
|
|
834
1531
|
if (terminationConfirmed && brokerOwns()) {
|
|
835
1532
|
removeAcceptedResponse(manifest, active.request.id);
|
|
836
1533
|
fs.rmSync(path.join(manifest.processingDir, active.request.id), { recursive: true, force: true });
|
|
@@ -844,7 +1541,7 @@ export async function serveSandboxControl(
|
|
|
844
1541
|
}
|
|
845
1542
|
}
|
|
846
1543
|
if (brokerOwns()) {
|
|
847
|
-
|
|
1544
|
+
appendDiagnosticAudit(manifest, 'broker-stop', { pid: broker.pid, brokerId: broker.brokerId });
|
|
848
1545
|
try {
|
|
849
1546
|
if (fs.readFileSync(brokerPath, 'utf8') === brokerRecord) fs.unlinkSync(brokerPath);
|
|
850
1547
|
} catch {
|