@fitlab-ai/agent-infra 0.9.12 → 0.9.14
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 +11 -11
- package/README.zh-CN.md +11 -11
- package/bin/cli.ts +26 -9
- package/dist/bin/cli.js +173 -157
- package/dist/bin/internal-cli.js +66 -40
- package/dist/lib/agent-client.js +5 -0
- package/dist/lib/agent-clients/next-steps.js +6 -1
- package/dist/lib/decide.js +3 -3
- package/dist/lib/internal/agent-client.js +5 -2
- package/dist/lib/internal/cli-route-inventory.js +152 -0
- package/dist/lib/internal/codex-lifecycle.js +9 -6
- package/dist/lib/internal/codex-sandbox-controller.js +8 -2
- package/dist/lib/internal/git-workflow.js +10 -6
- package/dist/lib/internal/platform-checks.js +19 -7
- package/dist/lib/internal/platform-comment.js +25 -13
- package/dist/lib/internal/platform-context.js +6 -3
- package/dist/lib/internal/platform-issue.js +20 -13
- package/dist/lib/internal/platform-metadata.js +88 -0
- package/dist/lib/internal/platform-pr-review.js +26 -13
- package/dist/lib/internal/platform-pr.js +84 -31
- package/dist/lib/internal/platform-release-notes.js +9 -6
- package/dist/lib/internal/platform-security.js +93 -0
- package/dist/lib/internal/pr-review-grade.js +25 -12
- package/dist/lib/internal/release-workflow.js +23 -9
- package/dist/lib/internal/sandbox-control.js +50 -7
- package/dist/lib/internal/task-activity.js +16 -7
- package/dist/lib/internal/task-artifact.js +235 -24
- package/dist/lib/internal/task-context.js +4 -1
- package/dist/lib/internal/task-create.js +50 -17
- package/dist/lib/internal/task-delivery.js +4 -1
- package/dist/lib/internal/task-event.js +10 -4
- package/dist/lib/internal/task-finalization.js +6 -3
- package/dist/lib/internal/task-invalidation.js +63 -0
- package/dist/lib/internal/task-ledger.js +20 -6
- package/dist/lib/internal/task-lifecycle.js +6 -3
- package/dist/lib/internal/task-operation-registry.js +421 -0
- package/dist/lib/internal/task-orchestration.js +12 -0
- package/dist/lib/internal/task-override.js +14 -7
- package/dist/lib/internal/task-qualification.js +90 -0
- package/dist/lib/internal/task-review.js +7 -4
- 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 +7 -2
- package/dist/lib/internal/task-verify.js +11 -7
- package/dist/lib/internal/task-warning.js +11 -4
- package/dist/lib/platform/adapters.js +1 -3
- package/dist/lib/platform/change-request-git-evidence.js +27 -7
- package/dist/lib/platform/comment-safety.js +226 -0
- package/dist/lib/platform/completion-backfill.js +6 -5
- package/dist/lib/platform/context.js +70 -130
- package/dist/lib/platform/github-client.js +1 -1
- package/dist/lib/platform/github-provider.js +1130 -0
- package/dist/lib/platform/in-label-sync.js +143 -0
- package/dist/lib/platform/issue-comments.js +237 -72
- package/dist/lib/platform/issues.js +335 -189
- package/dist/lib/platform/metadata-labels.js +6 -2
- package/dist/lib/platform/none-provider.js +28 -0
- package/dist/lib/platform/pr-change-report.js +545 -0
- package/dist/lib/platform/pr-checks.js +119 -104
- package/dist/lib/platform/pr-review.js +88 -72
- package/dist/lib/platform/pr-summary.js +541 -50
- package/dist/lib/platform/provider-bridge.js +45 -0
- package/dist/lib/platform/provider-contract.d.ts +553 -0
- package/dist/lib/platform/provider-contract.js +99 -0
- package/dist/lib/platform/provider-loader.js +251 -0
- package/dist/lib/platform/provider-validation.js +577 -0
- package/dist/lib/platform/pull-request-metadata.js +8 -6
- package/dist/lib/platform/pull-requests.js +734 -543
- package/dist/lib/platform/release-notes.js +78 -55
- package/dist/lib/platform/releases.js +79 -50
- package/dist/lib/platform/repository-metadata.js +377 -0
- package/dist/lib/platform/resource-identity.d.ts +26 -0
- package/dist/lib/platform/resource-identity.js +92 -0
- package/dist/lib/platform/security-alerts.js +155 -0
- package/dist/lib/platform/task-identities.js +31 -0
- package/dist/lib/platform/verification-required.js +11 -12
- package/dist/lib/platform/verification-sync.js +129 -248
- package/dist/lib/pr-review/evidence-grading.js +19 -6
- package/dist/lib/process-data/index.js +7 -0
- package/dist/lib/run/index.js +68 -23
- package/dist/lib/sandbox/commands/control-status.js +22 -0
- package/dist/lib/sandbox/commands/create.js +32 -6
- package/dist/lib/sandbox/commands/ls.js +5 -0
- package/dist/lib/sandbox/commands/rm.js +827 -120
- package/dist/lib/sandbox/commands/show.js +16 -0
- package/dist/lib/sandbox/control/client.js +174 -19
- 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 +151 -16
- package/dist/lib/sandbox/control/lifecycle.js +621 -127
- package/dist/lib/sandbox/control/native-file-lock.js +177 -0
- package/dist/lib/sandbox/control/protocol.js +77 -0
- package/dist/lib/sandbox/control/server.js +677 -34
- package/dist/lib/sandbox/control/state.js +209 -4
- package/dist/lib/sandbox/control/task-view.js +183 -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 +5 -0
- package/dist/lib/sandbox/recovery.js +21 -1
- package/dist/lib/sandbox/workspace-view.js +2 -1
- package/dist/lib/sandbox/worktree-safety.js +4 -1
- package/dist/lib/server/index.js +8 -0
- package/dist/lib/server/protocol.js +38 -1
- package/dist/lib/task/activity-log.js +8 -1
- package/dist/lib/task/artifact-lifecycle.js +75 -18
- package/dist/lib/task/artifact-operations.js +520 -0
- package/dist/lib/task/artifact-repair-intent.js +59 -0
- package/dist/lib/task/artifact-schema.js +135 -0
- package/dist/lib/task/capabilities.js +310 -0
- package/dist/lib/task/commands/status.js +32 -0
- package/dist/lib/task/commit-operation.js +12 -1
- package/dist/lib/task/control-authority.js +12 -5
- package/dist/lib/task/create-service.js +111 -12
- package/dist/lib/task/create.js +25 -0
- package/dist/lib/task/current-contract.js +5 -1
- package/dist/lib/task/decision-details.js +1 -1
- package/dist/lib/task/events.js +416 -17
- package/dist/lib/task/finalization.js +37 -13
- package/dist/lib/task/human-override.js +58 -36
- package/dist/lib/task/index.js +11 -3
- package/dist/lib/task/invalidation-command.js +86 -0
- package/dist/lib/task/invalidation.js +260 -0
- package/dist/lib/task/ledger-intents.js +37 -0
- package/dist/lib/task/local-artifact-finalization.js +299 -0
- package/dist/lib/task/orchestration.js +240 -144
- package/dist/lib/task/pr-delivery-fact.js +102 -101
- package/dist/lib/task/qualification-audit.js +490 -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 +22 -23
- package/dist/lib/task/review-finalization.js +121 -4
- package/dist/lib/task/review-fingerprint.js +24 -2
- package/dist/lib/task/rework-intent.js +138 -0
- package/dist/lib/task/sections.js +6 -4
- package/dist/lib/task/skill-scope.js +23 -0
- package/dist/lib/task/task-execution-lock.js +27 -17
- package/dist/lib/task/verification-config.js +11 -0
- package/dist/lib/task/verification-engine.js +93 -16
- package/dist/lib/task/verification.js +3 -3
- package/dist/lib/task/workflow-warning-intents.js +1 -1
- package/dist/lib/task/write.js +11 -0
- package/dist/lib/version.js +11 -1
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-client.ts +9 -0
- package/lib/agent-clients/next-steps.ts +6 -1
- package/lib/decide.ts +3 -3
- package/lib/init.ts +1 -1
- package/lib/internal/agent-client.ts +4 -2
- package/lib/internal/cli-route-inventory.ts +140 -0
- package/lib/internal/codex-lifecycle.ts +8 -6
- package/lib/internal/codex-sandbox-controller.ts +7 -2
- package/lib/internal/git-workflow.ts +9 -6
- package/lib/internal/platform-checks.ts +14 -6
- package/lib/internal/platform-comment.ts +20 -12
- package/lib/internal/platform-context.ts +5 -3
- package/lib/internal/platform-issue.ts +17 -11
- package/lib/internal/platform-metadata.ts +70 -0
- package/lib/internal/platform-pr-review.ts +23 -12
- package/lib/internal/platform-pr.ts +65 -27
- package/lib/internal/platform-release-notes.ts +8 -6
- package/lib/internal/platform-security.ts +83 -0
- package/lib/internal/pr-review-grade.ts +20 -11
- package/lib/internal/release-workflow.ts +21 -9
- package/lib/internal/sandbox-control.ts +44 -6
- package/lib/internal/task-activity.ts +15 -7
- package/lib/internal/task-artifact.ts +204 -22
- package/lib/internal/task-context.ts +3 -1
- package/lib/internal/task-create.ts +78 -17
- package/lib/internal/task-delivery.ts +3 -1
- package/lib/internal/task-event.ts +9 -4
- package/lib/internal/task-finalization.ts +6 -4
- package/lib/internal/task-invalidation.ts +41 -0
- package/lib/internal/task-ledger.ts +21 -8
- package/lib/internal/task-lifecycle.ts +5 -3
- package/lib/internal/task-operation-registry.ts +482 -0
- package/lib/internal/task-orchestration.ts +12 -0
- package/lib/internal/task-override.ts +15 -9
- package/lib/internal/task-qualification.ts +61 -0
- package/lib/internal/task-review.ts +6 -4
- package/lib/internal/task-short-id.ts +10 -1
- package/lib/internal/task-snapshot.ts +3 -1
- package/lib/internal/task-validate.ts +6 -2
- package/lib/internal/task-verify.ts +10 -7
- package/lib/internal/task-warning.ts +10 -4
- package/lib/platform/adapters.ts +3 -2
- package/lib/platform/change-request-git-evidence.ts +36 -9
- package/lib/platform/comment-safety.ts +250 -0
- package/lib/platform/completion-backfill.ts +9 -8
- package/lib/platform/context.ts +97 -134
- package/lib/platform/github-client.ts +2 -2
- package/lib/platform/github-provider.ts +1132 -0
- package/lib/platform/in-label-sync.ts +208 -0
- package/lib/platform/issue-comments.ts +255 -80
- package/lib/platform/issues.ts +344 -174
- package/lib/platform/metadata-labels.ts +5 -2
- package/lib/platform/none-provider.ts +35 -0
- package/lib/platform/pr-change-report.ts +639 -0
- package/lib/platform/pr-checks.ts +126 -106
- package/lib/platform/pr-review.ts +88 -85
- package/lib/platform/pr-summary.ts +581 -53
- package/lib/platform/provider-bridge.ts +82 -0
- package/lib/platform/provider-contract.ts +611 -0
- package/lib/platform/provider-loader.ts +307 -0
- package/lib/platform/provider-validation.ts +611 -0
- package/lib/platform/pull-request-metadata.ts +10 -7
- package/lib/platform/pull-requests.ts +781 -550
- package/lib/platform/release-notes.ts +76 -59
- package/lib/platform/releases.ts +77 -43
- package/lib/platform/repository-metadata.ts +453 -0
- package/lib/platform/resource-identity.ts +121 -0
- package/lib/platform/security-alerts.ts +191 -0
- package/lib/platform/task-identities.ts +40 -0
- package/lib/platform/types.ts +4 -2
- package/lib/platform/verification-required.ts +11 -13
- package/lib/platform/verification-sync.ts +128 -304
- package/lib/pr-review/evidence-grading.ts +25 -8
- package/lib/process-data/index.ts +7 -0
- package/lib/run/index.ts +62 -22
- package/lib/sandbox/commands/control-status.ts +26 -0
- package/lib/sandbox/commands/create.ts +46 -6
- package/lib/sandbox/commands/ls.ts +7 -0
- package/lib/sandbox/commands/rm.ts +1000 -87
- package/lib/sandbox/commands/show.ts +16 -0
- package/lib/sandbox/control/client.ts +203 -19
- package/lib/sandbox/control/completed-reentry.ts +98 -0
- package/lib/sandbox/control/container-identity.ts +27 -43
- package/lib/sandbox/control/executor.ts +180 -15
- package/lib/sandbox/control/lifecycle.ts +629 -17
- package/lib/sandbox/control/native-file-lock.ts +209 -0
- package/lib/sandbox/control/protocol.ts +118 -2
- package/lib/sandbox/control/server.ts +724 -34
- package/lib/sandbox/control/state.ts +257 -4
- package/lib/sandbox/control/task-view.ts +240 -0
- package/lib/sandbox/engines/authority.ts +365 -0
- package/lib/sandbox/engines/docker-exact-discovery.ts +132 -0
- package/lib/sandbox/index.ts +7 -0
- package/lib/sandbox/recovery.ts +21 -1
- package/lib/sandbox/workspace-view.ts +9 -3
- package/lib/sandbox/worktree-safety.ts +4 -1
- package/lib/server/index.ts +10 -0
- package/lib/server/protocol.ts +36 -1
- package/lib/task/activity-log.ts +8 -0
- package/lib/task/artifact-lifecycle.ts +70 -22
- package/lib/task/artifact-operations.ts +626 -0
- package/lib/task/artifact-repair-intent.ts +71 -0
- package/lib/task/artifact-schema.ts +192 -0
- package/lib/task/capabilities.ts +331 -0
- package/lib/task/commands/status.ts +38 -0
- package/lib/task/commit-operation.ts +12 -1
- package/lib/task/control-authority.ts +22 -9
- package/lib/task/create-service.ts +139 -14
- package/lib/task/create.ts +27 -0
- package/lib/task/current-contract.ts +6 -1
- package/lib/task/decision-details.ts +6 -2
- package/lib/task/events.ts +392 -16
- package/lib/task/finalization.ts +50 -14
- package/lib/task/human-override.ts +70 -46
- package/lib/task/index.ts +16 -3
- package/lib/task/invalidation-command.ts +86 -0
- package/lib/task/invalidation.ts +332 -0
- package/lib/task/ledger-intents.ts +35 -1
- package/lib/task/local-artifact-finalization.ts +432 -0
- package/lib/task/orchestration.ts +223 -133
- package/lib/task/pr-delivery-fact.ts +131 -148
- package/lib/task/qualification-audit.ts +599 -0
- package/lib/task/qualification-confirmation.ts +117 -0
- package/lib/task/qualification-intents.ts +150 -0
- package/lib/task/review-artifacts.ts +21 -22
- package/lib/task/review-finalization.ts +133 -4
- package/lib/task/review-fingerprint.ts +23 -2
- package/lib/task/rework-intent.ts +165 -0
- package/lib/task/sections.ts +6 -4
- package/lib/task/skill-scope.ts +24 -0
- package/lib/task/task-execution-lock.ts +23 -14
- package/lib/task/verification-config.ts +12 -0
- package/lib/task/verification-engine.ts +103 -17
- package/lib/task/verification.ts +4 -4
- package/lib/task/workflow-warning-intents.ts +1 -1
- package/lib/task/write.ts +19 -0
- package/lib/update.ts +1 -1
- package/lib/version.ts +15 -1
- package/package.json +12 -1
- package/templates/.agents/rules/README.en.md +3 -1
- package/templates/.agents/rules/README.zh-CN.md +3 -1
- 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/issue-pr-commands.en.md +1 -1
- package/templates/.agents/rules/issue-pr-commands.zh-CN.md +1 -1
- package/templates/.agents/rules/issue-sync.en.md +2 -0
- package/templates/.agents/rules/issue-sync.zh-CN.md +2 -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 +13 -2
- package/templates/.agents/rules/local-artifact-repair.zh-CN.md +13 -2
- package/templates/.agents/rules/pr-sync.en.md +10 -4
- package/templates/.agents/rules/pr-sync.zh-CN.md +17 -4
- 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/task-short-id.en.md +1 -4
- package/templates/.agents/rules/task-short-id.zh-CN.md +1 -3
- package/templates/.agents/skills/analyze-task/SKILL.en.md +28 -5
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +29 -5
- 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/check-task/SKILL.en.md +16 -37
- package/templates/.agents/skills/check-task/SKILL.zh-CN.md +16 -37
- 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 +41 -9
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +43 -11
- package/templates/.agents/skills/code-task/config/verify.en.json +6 -14
- package/templates/.agents/skills/code-task/config/verify.zh-CN.json +6 -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/commit/reference/issue-metadata-sync.en.md +2 -0
- package/templates/.agents/skills/commit/reference/issue-metadata-sync.zh-CN.md +2 -0
- package/templates/.agents/skills/commit/reference/pr-summary-sync.en.md +3 -3
- package/templates/.agents/skills/commit/reference/pr-summary-sync.zh-CN.md +3 -3
- package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +10 -4
- package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +10 -4
- 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 +4 -3
- package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +4 -3
- package/templates/.agents/skills/complete-task/SKILL.en.md +26 -21
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +25 -21
- package/templates/.agents/skills/create-pr/SKILL.en.md +7 -6
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +7 -7
- package/templates/.agents/skills/create-pr/config/verify.json +1 -0
- package/templates/.agents/skills/create-pr/reference/change-report.en.md +16 -4
- package/templates/.agents/skills/create-pr/reference/change-report.zh-CN.md +16 -4
- package/templates/.agents/skills/create-pr/reference/comment-publish.en.md +5 -3
- package/templates/.agents/skills/create-pr/reference/comment-publish.zh-CN.md +5 -3
- package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +1 -1
- package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +1 -1
- package/templates/.agents/skills/create-pr/scripts/change-report.mjs +3 -0
- 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 +27 -4
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +26 -4
- 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/review-analysis/SKILL.en.md +17 -4
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +17 -4
- 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 +18 -5
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +18 -5
- 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 +17 -4
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +17 -4
- 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 +9 -3
- package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +9 -3
- package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +3 -1
- package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +3 -1
- package/templates/.agents/skills/test/SKILL.en.md +13 -2
- package/templates/.agents/skills/test/SKILL.zh-CN.md +13 -2
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +14 -0
- package/templates/.agents/skills/watch-pr/SKILL.en.md +2 -2
- package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +6 -0
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +6 -0
- package/templates/.agents/templates/task.en.md +24 -1
- package/templates/.agents/templates/task.zh-CN.md +24 -1
- package/templates/.github/workflows/pr-label.yml +7 -26
- 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,7 +1,8 @@
|
|
|
1
1
|
import { execFileSync } from 'node:child_process';
|
|
2
2
|
import fs from 'node:fs';
|
|
3
|
+
import os from 'node:os';
|
|
3
4
|
import path from 'node:path';
|
|
4
|
-
import { randomUUID } from 'node:crypto';
|
|
5
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
5
6
|
import { buildProcessTreeStopCommand } from '../../server/process-control.ts';
|
|
6
7
|
import {
|
|
7
8
|
getProcessIdentityState,
|
|
@@ -16,6 +17,12 @@ import type {
|
|
|
16
17
|
} from './protocol.ts';
|
|
17
18
|
import { DEFAULT_SANDBOX_CONTROL_TIMING } from './protocol.ts';
|
|
18
19
|
import { inspectSandboxControlContainer, type ContainerObservation } from './container-identity.ts';
|
|
20
|
+
import {
|
|
21
|
+
acquireSandboxResourceLock,
|
|
22
|
+
resolveSandboxLockNamespace,
|
|
23
|
+
type SandboxResourceLock
|
|
24
|
+
} from './native-file-lock.ts';
|
|
25
|
+
import { isSandboxAuthorityEvidence } from '../engines/authority.ts';
|
|
19
26
|
import {
|
|
20
27
|
parseSandboxControlStatus,
|
|
21
28
|
readExecution,
|
|
@@ -30,6 +37,528 @@ const QUIESCING_FILE = 'quiescing.json';
|
|
|
30
37
|
const BROKER_STARTING_FILE = 'broker-starting.json';
|
|
31
38
|
const REPLACEMENT_FILE = 'replacement.json';
|
|
32
39
|
const REPLACEMENT_STATE_SUFFIX = '.replacement-state.json';
|
|
40
|
+
const REMOVAL_JOURNAL_ROOT = path.join('.agent-infra', 'sandbox-removal-journal');
|
|
41
|
+
|
|
42
|
+
export const SANDBOX_REMOVAL_JOURNAL_PHASES = [
|
|
43
|
+
'prepared', 'target-committed', 'container-removal', 'container-absent',
|
|
44
|
+
'carrier-finalizing', 'carrier-removed', 'workspace-finalizing', 'workspace-removed',
|
|
45
|
+
'branch-finalizing', 'branch-removed', 'tool-finalizing', 'tool-removed',
|
|
46
|
+
'shell-finalizing', 'shell-removed', 'share-finalizing', 'share-removed', 'completed'
|
|
47
|
+
] as const;
|
|
48
|
+
export type SandboxRemovalJournalPhase = typeof SANDBOX_REMOVAL_JOURNAL_PHASES[number];
|
|
49
|
+
export type SandboxRemovalTargetCommit = Readonly<{
|
|
50
|
+
branch: string;
|
|
51
|
+
project: string;
|
|
52
|
+
controlRoot: string;
|
|
53
|
+
targetDigest: string;
|
|
54
|
+
permitDigest: string;
|
|
55
|
+
removeWorktree: boolean;
|
|
56
|
+
removeBranch: boolean;
|
|
57
|
+
removeShare: boolean;
|
|
58
|
+
worktreePaths: readonly string[];
|
|
59
|
+
workspaceViewPaths: readonly string[];
|
|
60
|
+
toolPaths: readonly string[];
|
|
61
|
+
shellPaths: readonly string[];
|
|
62
|
+
sharePath: string;
|
|
63
|
+
permits: readonly Readonly<{
|
|
64
|
+
path: string;
|
|
65
|
+
mode: 'clean' | 'discard';
|
|
66
|
+
snapshot: Readonly<{
|
|
67
|
+
worktree: string;
|
|
68
|
+
branch: string;
|
|
69
|
+
head: string;
|
|
70
|
+
changes: readonly Readonly<{
|
|
71
|
+
indexStatus: string;
|
|
72
|
+
worktreeStatus: string;
|
|
73
|
+
path: string;
|
|
74
|
+
originalPath?: string;
|
|
75
|
+
}>[];
|
|
76
|
+
identity: string;
|
|
77
|
+
source?: 'registered' | 'recovered';
|
|
78
|
+
recovery?: Readonly<{
|
|
79
|
+
repoRoot: string;
|
|
80
|
+
worktreeBase: string;
|
|
81
|
+
branch: string;
|
|
82
|
+
identitySource: 'branch-only' | 'task-bound';
|
|
83
|
+
taskId: string | null;
|
|
84
|
+
}>;
|
|
85
|
+
}>;
|
|
86
|
+
}>[];
|
|
87
|
+
}>;
|
|
88
|
+
export type SandboxRemovalJournal = Readonly<{
|
|
89
|
+
version: 2;
|
|
90
|
+
operation: 'sandbox-rm';
|
|
91
|
+
oldOperation: 'sandbox-rm';
|
|
92
|
+
newOperation: 'sandbox-rm';
|
|
93
|
+
handoffId: string;
|
|
94
|
+
transitionId: string;
|
|
95
|
+
engine: string;
|
|
96
|
+
containerId: string;
|
|
97
|
+
generation: string;
|
|
98
|
+
authorityFingerprint: string;
|
|
99
|
+
carrierIdentityDigest: string;
|
|
100
|
+
lockDomain: string;
|
|
101
|
+
owner: ProcessIdentity & Readonly<{ leaseNonce: string }>;
|
|
102
|
+
phase: SandboxRemovalJournalPhase;
|
|
103
|
+
expectedOldJournalRevision: number | null;
|
|
104
|
+
revision: number;
|
|
105
|
+
target: SandboxRemovalTargetCommit;
|
|
106
|
+
recordedAt: number;
|
|
107
|
+
}>;
|
|
108
|
+
|
|
109
|
+
function removalCarrierDigest(engine: string, containerId: string, generation: string): string {
|
|
110
|
+
return createHash('sha256').update(`${engine}\0${containerId}\0${generation}`).digest('hex');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function assertPrivateDirectory(directory: string): void {
|
|
114
|
+
const home = path.resolve(os.homedir());
|
|
115
|
+
const resolved = path.resolve(directory);
|
|
116
|
+
const relative = path.relative(home, resolved);
|
|
117
|
+
if (!relative || relative.startsWith('..') || path.isAbsolute(relative)) {
|
|
118
|
+
throw new Error('SANDBOX_CONTROL_REMOVAL_JOURNAL_INVALID');
|
|
119
|
+
}
|
|
120
|
+
let current = home;
|
|
121
|
+
for (const segment of relative.split(path.sep).filter(Boolean)) {
|
|
122
|
+
current = path.join(current, segment);
|
|
123
|
+
const stat = fs.lstatSync(current);
|
|
124
|
+
if (stat.isSymbolicLink() || !stat.isDirectory()) {
|
|
125
|
+
throw new Error('SANDBOX_CONTROL_REMOVAL_JOURNAL_INVALID');
|
|
126
|
+
}
|
|
127
|
+
if (process.platform !== 'win32' && typeof process.getuid === 'function' && stat.uid !== process.getuid()) {
|
|
128
|
+
throw new Error('SANDBOX_CONTROL_REMOVAL_JOURNAL_INVALID');
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function removalJournalDomain(lockDomain: string, create = true): string {
|
|
134
|
+
const namespace = resolveSandboxLockNamespace('sandbox-removal-journal', { lockDomain });
|
|
135
|
+
const root = path.join(path.dirname(namespace.lockRoot), REMOVAL_JOURNAL_ROOT.split(path.sep).at(-1)!);
|
|
136
|
+
if (create) fs.mkdirSync(root, { recursive: true, mode: 0o700 });
|
|
137
|
+
assertPrivateDirectory(root);
|
|
138
|
+
const domain = path.join(root, lockDomain);
|
|
139
|
+
if (create) fs.mkdirSync(domain, { recursive: true, mode: 0o700 });
|
|
140
|
+
assertPrivateDirectory(domain);
|
|
141
|
+
return domain;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function removalJournalPath(manifest: SandboxControlManifest): string {
|
|
145
|
+
return path.join(
|
|
146
|
+
removalJournalDomain(manifest.authorityEvidence.lockDomain),
|
|
147
|
+
`${removalCarrierDigest(manifest.engine, manifest.containerIdentity.id, manifest.generation)}.json`
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function removalJournalPathForRecord(record: SandboxRemovalJournal): string {
|
|
152
|
+
return path.join(
|
|
153
|
+
removalJournalDomain(record.lockDomain, false),
|
|
154
|
+
`${record.carrierIdentityDigest}.json`
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
159
|
+
return Boolean(value && typeof value === 'object' && !Array.isArray(value));
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function hasExactKeys(value: Record<string, unknown>, keys: readonly string[]): boolean {
|
|
163
|
+
const actual = Object.keys(value).sort();
|
|
164
|
+
return actual.length === keys.length && actual.every((key, index) => key === keys[index]);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function isRemovalPermitCommit(value: unknown): boolean {
|
|
168
|
+
if (!isRecord(value) || typeof value.path !== 'string' || !path.isAbsolute(value.path)
|
|
169
|
+
|| (value.mode !== 'clean' && value.mode !== 'discard') || !isRecord(value.snapshot)
|
|
170
|
+
|| typeof value.snapshot.worktree !== 'string' || !path.isAbsolute(value.snapshot.worktree)
|
|
171
|
+
|| typeof value.snapshot.branch !== 'string' || typeof value.snapshot.head !== 'string'
|
|
172
|
+
|| typeof value.snapshot.identity !== 'string' || !Array.isArray(value.snapshot.changes)
|
|
173
|
+
|| value.snapshot.changes.some((change) => !isRecord(change)
|
|
174
|
+
|| typeof change.indexStatus !== 'string' || typeof change.worktreeStatus !== 'string'
|
|
175
|
+
|| typeof change.path !== 'string'
|
|
176
|
+
|| (change.originalPath !== undefined && typeof change.originalPath !== 'string'))
|
|
177
|
+
|| (value.snapshot.source !== undefined
|
|
178
|
+
&& value.snapshot.source !== 'registered' && value.snapshot.source !== 'recovered')) return false;
|
|
179
|
+
if (value.snapshot.recovery === undefined) return true;
|
|
180
|
+
const recovery = value.snapshot.recovery;
|
|
181
|
+
return isRecord(recovery)
|
|
182
|
+
&& typeof recovery.repoRoot === 'string' && path.isAbsolute(recovery.repoRoot)
|
|
183
|
+
&& typeof recovery.worktreeBase === 'string' && path.isAbsolute(recovery.worktreeBase)
|
|
184
|
+
&& typeof recovery.branch === 'string'
|
|
185
|
+
&& (recovery.identitySource === 'branch-only' || recovery.identitySource === 'task-bound')
|
|
186
|
+
&& (recovery.taskId === null || typeof recovery.taskId === 'string');
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function parseRemovalJournal(raw: string): SandboxRemovalJournal {
|
|
190
|
+
let value: unknown;
|
|
191
|
+
try {
|
|
192
|
+
value = JSON.parse(raw);
|
|
193
|
+
} catch {
|
|
194
|
+
throw new Error('SANDBOX_CONTROL_REMOVAL_JOURNAL_INVALID');
|
|
195
|
+
}
|
|
196
|
+
if (!isRecord(value) || !hasExactKeys(value, [
|
|
197
|
+
'authorityFingerprint', 'carrierIdentityDigest', 'containerId', 'engine', 'expectedOldJournalRevision',
|
|
198
|
+
'generation', 'handoffId', 'lockDomain', 'newOperation', 'oldOperation', 'operation', 'owner', 'phase',
|
|
199
|
+
'recordedAt', 'revision', 'target', 'transitionId', 'version'
|
|
200
|
+
]) || !isRecord(value.owner) || !hasExactKeys(value.owner, ['leaseNonce', 'pid', 'startTime'])
|
|
201
|
+
|| !isRecord(value.target) || !hasExactKeys(value.target, [
|
|
202
|
+
'branch', 'controlRoot', 'permitDigest', 'permits', 'project', 'removeBranch', 'removeShare',
|
|
203
|
+
'removeWorktree', 'sharePath', 'shellPaths', 'targetDigest', 'toolPaths', 'workspaceViewPaths',
|
|
204
|
+
'worktreePaths'
|
|
205
|
+
]) || value.version !== 2 || value.operation !== 'sandbox-rm'
|
|
206
|
+
|| value.oldOperation !== 'sandbox-rm' || value.newOperation !== 'sandbox-rm'
|
|
207
|
+
|| typeof value.handoffId !== 'string' || value.handoffId.length === 0
|
|
208
|
+
|| typeof value.transitionId !== 'string' || value.transitionId.length === 0
|
|
209
|
+
|| typeof value.engine !== 'string' || value.engine.length === 0
|
|
210
|
+
|| typeof value.containerId !== 'string' || value.containerId.length === 0
|
|
211
|
+
|| typeof value.generation !== 'string' || value.generation.length === 0
|
|
212
|
+
|| typeof value.authorityFingerprint !== 'string' || !/^[a-f0-9]{64}$/u.test(value.authorityFingerprint)
|
|
213
|
+
|| typeof value.carrierIdentityDigest !== 'string' || !/^[a-f0-9]{64}$/u.test(value.carrierIdentityDigest)
|
|
214
|
+
|| typeof value.lockDomain !== 'string' || !/^[a-f0-9]{64}$/u.test(value.lockDomain)
|
|
215
|
+
|| !Number.isSafeInteger(value.owner.pid) || (value.owner.pid as number) <= 0
|
|
216
|
+
|| !Number.isSafeInteger(value.owner.startTime) || typeof value.owner.leaseNonce !== 'string'
|
|
217
|
+
|| value.owner.leaseNonce.length === 0
|
|
218
|
+
|| !SANDBOX_REMOVAL_JOURNAL_PHASES.includes(value.phase as SandboxRemovalJournalPhase)
|
|
219
|
+
|| !(value.expectedOldJournalRevision === null
|
|
220
|
+
|| (Number.isSafeInteger(value.expectedOldJournalRevision) && (value.expectedOldJournalRevision as number) >= 0))
|
|
221
|
+
|| !Number.isSafeInteger(value.revision) || (value.revision as number) <= 0
|
|
222
|
+
|| typeof value.target.branch !== 'string' || value.target.branch.length === 0
|
|
223
|
+
|| typeof value.target.project !== 'string' || value.target.project.length === 0
|
|
224
|
+
|| typeof value.target.controlRoot !== 'string' || path.isAbsolute(value.target.controlRoot) === false
|
|
225
|
+
|| typeof value.target.targetDigest !== 'string' || !/^[a-f0-9]{64}$/u.test(value.target.targetDigest)
|
|
226
|
+
|| typeof value.target.permitDigest !== 'string' || !/^[a-f0-9]{64}$/u.test(value.target.permitDigest)
|
|
227
|
+
|| typeof value.target.removeWorktree !== 'boolean'
|
|
228
|
+
|| typeof value.target.removeBranch !== 'boolean'
|
|
229
|
+
|| typeof value.target.removeShare !== 'boolean'
|
|
230
|
+
|| typeof value.target.sharePath !== 'string' || !path.isAbsolute(value.target.sharePath)
|
|
231
|
+
|| !Array.isArray(value.target.worktreePaths) || value.target.worktreePaths.some((entry) => typeof entry !== 'string' || !path.isAbsolute(entry))
|
|
232
|
+
|| !Array.isArray(value.target.workspaceViewPaths) || value.target.workspaceViewPaths.some((entry) => typeof entry !== 'string' || !path.isAbsolute(entry))
|
|
233
|
+
|| !Array.isArray(value.target.toolPaths) || value.target.toolPaths.some((entry) => typeof entry !== 'string' || !path.isAbsolute(entry))
|
|
234
|
+
|| !Array.isArray(value.target.shellPaths) || value.target.shellPaths.some((entry) => typeof entry !== 'string' || !path.isAbsolute(entry))
|
|
235
|
+
|| !Array.isArray(value.target.permits)
|
|
236
|
+
|| value.target.permits.some((permit) => !isRemovalPermitCommit(permit))
|
|
237
|
+
|| !Number.isSafeInteger(value.recordedAt)) {
|
|
238
|
+
throw new Error('SANDBOX_CONTROL_REMOVAL_JOURNAL_INVALID');
|
|
239
|
+
}
|
|
240
|
+
const expected = value.expectedOldJournalRevision as number | null;
|
|
241
|
+
const revision = value.revision as number;
|
|
242
|
+
if ((expected === null && revision !== 1) || (expected !== null && revision !== expected + 1)) {
|
|
243
|
+
throw new Error('SANDBOX_CONTROL_REMOVAL_JOURNAL_INVALID');
|
|
244
|
+
}
|
|
245
|
+
return {
|
|
246
|
+
version: 2,
|
|
247
|
+
operation: 'sandbox-rm',
|
|
248
|
+
oldOperation: 'sandbox-rm',
|
|
249
|
+
newOperation: 'sandbox-rm',
|
|
250
|
+
handoffId: value.handoffId as string,
|
|
251
|
+
transitionId: value.transitionId as string,
|
|
252
|
+
engine: value.engine as string,
|
|
253
|
+
containerId: value.containerId as string,
|
|
254
|
+
generation: value.generation as string,
|
|
255
|
+
authorityFingerprint: value.authorityFingerprint as string,
|
|
256
|
+
carrierIdentityDigest: value.carrierIdentityDigest as string,
|
|
257
|
+
lockDomain: value.lockDomain as string,
|
|
258
|
+
owner: {
|
|
259
|
+
pid: value.owner.pid as number,
|
|
260
|
+
startTime: value.owner.startTime as number,
|
|
261
|
+
leaseNonce: value.owner.leaseNonce as string
|
|
262
|
+
},
|
|
263
|
+
phase: value.phase as SandboxRemovalJournalPhase,
|
|
264
|
+
expectedOldJournalRevision: expected,
|
|
265
|
+
revision,
|
|
266
|
+
target: {
|
|
267
|
+
branch: value.target.branch as string,
|
|
268
|
+
project: value.target.project as string,
|
|
269
|
+
controlRoot: value.target.controlRoot as string,
|
|
270
|
+
targetDigest: value.target.targetDigest as string,
|
|
271
|
+
permitDigest: value.target.permitDigest as string,
|
|
272
|
+
removeWorktree: value.target.removeWorktree as boolean,
|
|
273
|
+
removeBranch: value.target.removeBranch as boolean,
|
|
274
|
+
removeShare: value.target.removeShare as boolean,
|
|
275
|
+
worktreePaths: value.target.worktreePaths as string[],
|
|
276
|
+
workspaceViewPaths: value.target.workspaceViewPaths as string[],
|
|
277
|
+
toolPaths: value.target.toolPaths as string[],
|
|
278
|
+
shellPaths: value.target.shellPaths as string[],
|
|
279
|
+
sharePath: value.target.sharePath as string,
|
|
280
|
+
permits: value.target.permits as SandboxRemovalTargetCommit['permits']
|
|
281
|
+
},
|
|
282
|
+
recordedAt: value.recordedAt as number
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function readRemovalJournalAt(target: string): SandboxRemovalJournal | null {
|
|
287
|
+
if (!fs.existsSync(target)) return null;
|
|
288
|
+
const stat = fs.lstatSync(target);
|
|
289
|
+
if (!stat.isFile() || stat.isSymbolicLink()) throw new Error('SANDBOX_CONTROL_REMOVAL_JOURNAL_INVALID');
|
|
290
|
+
return parseRemovalJournal(fs.readFileSync(target, 'utf8'));
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
export function readSandboxRemovalJournal(manifest: SandboxControlManifest): SandboxRemovalJournal | null {
|
|
294
|
+
return readRemovalJournalAt(removalJournalPath(manifest));
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
export function listSandboxRemovalJournals(
|
|
298
|
+
filter: Readonly<{ branch?: string; project?: string; targetDigest?: string }> = {}
|
|
299
|
+
): SandboxRemovalJournal[] {
|
|
300
|
+
const namespace = resolveSandboxLockNamespace('sandbox-removal-journal');
|
|
301
|
+
const root = path.join(path.dirname(namespace.lockRoot), REMOVAL_JOURNAL_ROOT.split(path.sep).at(-1)!);
|
|
302
|
+
if (!fs.existsSync(root)) return [];
|
|
303
|
+
assertPrivateDirectory(root);
|
|
304
|
+
const journals: SandboxRemovalJournal[] = [];
|
|
305
|
+
for (const domainEntry of fs.readdirSync(root, { withFileTypes: true })) {
|
|
306
|
+
if (!domainEntry.isDirectory() || domainEntry.isSymbolicLink()) {
|
|
307
|
+
throw new Error('SANDBOX_CONTROL_REMOVAL_JOURNAL_INVALID');
|
|
308
|
+
}
|
|
309
|
+
const domain = path.join(root, domainEntry.name);
|
|
310
|
+
assertPrivateDirectory(domain);
|
|
311
|
+
for (const entry of fs.readdirSync(domain, { withFileTypes: true })) {
|
|
312
|
+
if (!entry.isFile() || entry.isSymbolicLink() || !entry.name.endsWith('.json')) {
|
|
313
|
+
throw new Error('SANDBOX_CONTROL_REMOVAL_JOURNAL_INVALID');
|
|
314
|
+
}
|
|
315
|
+
const record = readRemovalJournalAt(path.join(domain, entry.name));
|
|
316
|
+
if (!record || record.lockDomain !== domainEntry.name
|
|
317
|
+
|| `${record.carrierIdentityDigest}.json` !== entry.name) {
|
|
318
|
+
throw new Error('SANDBOX_CONTROL_REMOVAL_JOURNAL_INVALID');
|
|
319
|
+
}
|
|
320
|
+
if ((!filter.branch || record.target.branch === filter.branch)
|
|
321
|
+
&& (!filter.project || record.target.project === filter.project)
|
|
322
|
+
&& (!filter.targetDigest || record.target.targetDigest === filter.targetDigest)) {
|
|
323
|
+
journals.push(record);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
return journals;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
export function clearSandboxRemovalJournalRecord(record: SandboxRemovalJournal): void {
|
|
331
|
+
const target = removalJournalPathForRecord(record);
|
|
332
|
+
if (fs.existsSync(target)) {
|
|
333
|
+
const current = readRemovalJournalAt(target);
|
|
334
|
+
if (!current || current.revision !== record.revision || current.handoffId !== record.handoffId) {
|
|
335
|
+
throw new Error('SANDBOX_CONTROL_REMOVAL_JOURNAL_REVISION_MISMATCH');
|
|
336
|
+
}
|
|
337
|
+
fs.rmSync(target, { force: true });
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
export function clearSandboxRemovalJournal(manifest: SandboxControlManifest): void {
|
|
342
|
+
const target = removalJournalPath(manifest);
|
|
343
|
+
if (fs.existsSync(target)) fs.rmSync(target, { force: true });
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
type RemovalJournalCursor = Readonly<{
|
|
347
|
+
record: SandboxRemovalJournal;
|
|
348
|
+
write(phase: SandboxRemovalJournalPhase): SandboxRemovalJournal;
|
|
349
|
+
}>;
|
|
350
|
+
|
|
351
|
+
function defaultRemovalTarget(manifest: SandboxControlManifest): SandboxRemovalTargetCommit {
|
|
352
|
+
const targetDigest = createHash('sha256')
|
|
353
|
+
.update(`${manifest.project}\0${manifest.branch}\0${manifest.container}\0${manifest.channelDir}`)
|
|
354
|
+
.digest('hex');
|
|
355
|
+
return {
|
|
356
|
+
branch: manifest.branch,
|
|
357
|
+
project: manifest.project,
|
|
358
|
+
controlRoot: path.dirname(manifest.channelDir),
|
|
359
|
+
targetDigest,
|
|
360
|
+
permitDigest: createHash('sha256').update('none').digest('hex'),
|
|
361
|
+
removeWorktree: false,
|
|
362
|
+
removeBranch: false,
|
|
363
|
+
removeShare: false,
|
|
364
|
+
worktreePaths: [],
|
|
365
|
+
workspaceViewPaths: [],
|
|
366
|
+
toolPaths: [],
|
|
367
|
+
shellPaths: [],
|
|
368
|
+
sharePath: path.join(path.dirname(manifest.channelDir), 'share'),
|
|
369
|
+
permits: []
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function sameRemovalTarget(left: SandboxRemovalTargetCommit, right: SandboxRemovalTargetCommit): boolean {
|
|
374
|
+
return JSON.stringify(left) === JSON.stringify(right);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function phaseIndex(phase: SandboxRemovalJournalPhase): number {
|
|
378
|
+
return SANDBOX_REMOVAL_JOURNAL_PHASES.indexOf(phase);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
export function sandboxRemovalPhaseIndex(phase: SandboxRemovalJournalPhase): number {
|
|
382
|
+
return phaseIndex(phase);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function assertNextRemovalPhase(current: SandboxRemovalJournalPhase, next: SandboxRemovalJournalPhase): boolean {
|
|
386
|
+
const currentIndex = phaseIndex(current);
|
|
387
|
+
const nextIndex = phaseIndex(next);
|
|
388
|
+
if (nextIndex === currentIndex) return false;
|
|
389
|
+
if (current === 'target-committed' && next === 'container-absent') return true;
|
|
390
|
+
if (nextIndex !== currentIndex + 1) {
|
|
391
|
+
throw new Error('SANDBOX_CONTROL_REMOVAL_PHASE_TRANSITION_INVALID');
|
|
392
|
+
}
|
|
393
|
+
return true;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
function assertRemovalJournalLock(
|
|
397
|
+
record: SandboxRemovalJournal,
|
|
398
|
+
resourceLock: SandboxResourceLock
|
|
399
|
+
): void {
|
|
400
|
+
const namespace = resolveSandboxLockNamespace(`${record.engine}:${record.containerId}`, {
|
|
401
|
+
lockDomain: record.lockDomain
|
|
402
|
+
});
|
|
403
|
+
if (resourceLock.lockDomain !== record.lockDomain || resourceLock.path !== namespace.lockPath) {
|
|
404
|
+
throw new Error('SANDBOX_CONTROL_REMOVAL_LOCK_MISMATCH');
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function writeRemovalJournalAt(
|
|
409
|
+
target: string,
|
|
410
|
+
base: Omit<SandboxRemovalJournal, 'phase' | 'expectedOldJournalRevision' | 'revision' | 'recordedAt'>,
|
|
411
|
+
phase: SandboxRemovalJournalPhase,
|
|
412
|
+
expectedOldJournalRevision: number | null
|
|
413
|
+
): SandboxRemovalJournal {
|
|
414
|
+
const existing = readRemovalJournalAt(target);
|
|
415
|
+
if (expectedOldJournalRevision === null
|
|
416
|
+
? existing !== null
|
|
417
|
+
: existing?.revision !== expectedOldJournalRevision) {
|
|
418
|
+
throw new Error('SANDBOX_CONTROL_REMOVAL_JOURNAL_REVISION_MISMATCH');
|
|
419
|
+
}
|
|
420
|
+
const record: SandboxRemovalJournal = {
|
|
421
|
+
...base,
|
|
422
|
+
phase,
|
|
423
|
+
expectedOldJournalRevision,
|
|
424
|
+
revision: expectedOldJournalRevision === null ? 1 : expectedOldJournalRevision + 1,
|
|
425
|
+
recordedAt: Date.now()
|
|
426
|
+
};
|
|
427
|
+
const temporary = `${target}.${process.pid}.${randomUUID()}.tmp`;
|
|
428
|
+
try {
|
|
429
|
+
fs.writeFileSync(temporary, `${JSON.stringify(record)}\n`, { mode: 0o600, flag: 'wx' });
|
|
430
|
+
if (expectedOldJournalRevision === null && fs.existsSync(target)) {
|
|
431
|
+
throw new Error('SANDBOX_CONTROL_REMOVAL_JOURNAL_REVISION_MISMATCH');
|
|
432
|
+
}
|
|
433
|
+
fs.renameSync(temporary, target);
|
|
434
|
+
} finally {
|
|
435
|
+
fs.rmSync(temporary, { force: true });
|
|
436
|
+
}
|
|
437
|
+
return record;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function writeRemovalJournal(
|
|
441
|
+
manifest: SandboxControlManifest,
|
|
442
|
+
base: Omit<SandboxRemovalJournal, 'phase' | 'expectedOldJournalRevision' | 'revision' | 'recordedAt'>,
|
|
443
|
+
phase: SandboxRemovalJournalPhase,
|
|
444
|
+
expectedOldJournalRevision: number | null
|
|
445
|
+
): SandboxRemovalJournal {
|
|
446
|
+
return writeRemovalJournalAt(removalJournalPath(manifest), base, phase, expectedOldJournalRevision);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
function startRemovalJournal(
|
|
450
|
+
manifest: SandboxControlManifest,
|
|
451
|
+
options: Readonly<{
|
|
452
|
+
target?: SandboxRemovalTargetCommit;
|
|
453
|
+
identityProbe: ProcessIdentityProbe;
|
|
454
|
+
}>
|
|
455
|
+
): RemovalJournalCursor {
|
|
456
|
+
const target = removalJournalPath(manifest);
|
|
457
|
+
const existing = readRemovalJournalAt(target);
|
|
458
|
+
if (existing && (existing.engine !== manifest.engine || existing.containerId !== manifest.containerIdentity.id
|
|
459
|
+
|| existing.generation !== manifest.generation
|
|
460
|
+
|| existing.authorityFingerprint !== manifest.authorityEvidence.authorityFingerprint
|
|
461
|
+
|| existing.lockDomain !== manifest.authorityEvidence.lockDomain)) {
|
|
462
|
+
throw new Error('SANDBOX_CONTROL_REMOVAL_JOURNAL_IDENTITY_MISMATCH');
|
|
463
|
+
}
|
|
464
|
+
const targetCommit = options.target ?? existing?.target ?? defaultRemovalTarget(manifest);
|
|
465
|
+
if (existing && options.target && !sameRemovalTarget(existing.target, options.target)) {
|
|
466
|
+
throw new Error('SANDBOX_CONTROL_REMOVAL_TARGET_MISMATCH');
|
|
467
|
+
}
|
|
468
|
+
if (existing) {
|
|
469
|
+
const state = options.identityProbe(existing.owner);
|
|
470
|
+
if (state === 'alive') throw new Error('SANDBOX_CONTROL_REMOVE_RETRY_IN_PROGRESS');
|
|
471
|
+
if (state === 'unknown') throw new Error('SANDBOX_CONTROL_REMOVE_OWNER_UNKNOWN');
|
|
472
|
+
}
|
|
473
|
+
const startTime = getProcessStartTime(process.pid);
|
|
474
|
+
if (startTime === null) throw new Error('SANDBOX_CONTROL_REMOVE_OWNER_UNAVAILABLE');
|
|
475
|
+
const base = {
|
|
476
|
+
version: 2 as const,
|
|
477
|
+
operation: 'sandbox-rm' as const,
|
|
478
|
+
oldOperation: 'sandbox-rm' as const,
|
|
479
|
+
newOperation: 'sandbox-rm' as const,
|
|
480
|
+
handoffId: randomUUID(),
|
|
481
|
+
transitionId: randomUUID(),
|
|
482
|
+
engine: manifest.engine,
|
|
483
|
+
containerId: manifest.containerIdentity.id,
|
|
484
|
+
generation: manifest.generation,
|
|
485
|
+
authorityFingerprint: manifest.authorityEvidence.authorityFingerprint,
|
|
486
|
+
carrierIdentityDigest: removalCarrierDigest(manifest.engine, manifest.containerIdentity.id, manifest.generation),
|
|
487
|
+
lockDomain: manifest.authorityEvidence.lockDomain,
|
|
488
|
+
owner: { pid: process.pid, startTime, leaseNonce: randomUUID() },
|
|
489
|
+
target: targetCommit
|
|
490
|
+
};
|
|
491
|
+
const initialPhase = existing?.phase ?? 'prepared';
|
|
492
|
+
let record = writeRemovalJournal(manifest, base, initialPhase, existing?.revision ?? null);
|
|
493
|
+
return {
|
|
494
|
+
get record() { return record; },
|
|
495
|
+
write(phase) {
|
|
496
|
+
if (!assertNextRemovalPhase(record.phase, phase)) return record;
|
|
497
|
+
record = writeRemovalJournal(manifest, base, phase, record.revision);
|
|
498
|
+
return record;
|
|
499
|
+
}
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
export function claimSandboxRemovalJournal(
|
|
504
|
+
journal: SandboxRemovalJournal,
|
|
505
|
+
options: Readonly<{ identityProbe?: ProcessIdentityProbe; resourceLock?: SandboxResourceLock }> = {}
|
|
506
|
+
): SandboxRemovalJournal {
|
|
507
|
+
const target = removalJournalPathForRecord(journal);
|
|
508
|
+
const resourceLock = options.resourceLock
|
|
509
|
+
?? acquireSandboxResourceLock(`${journal.engine}:${journal.containerId}`, { lockDomain: journal.lockDomain });
|
|
510
|
+
const ownedLock = options.resourceLock ? null : resourceLock;
|
|
511
|
+
try {
|
|
512
|
+
assertRemovalJournalLock(journal, resourceLock);
|
|
513
|
+
const current = readRemovalJournalAt(target);
|
|
514
|
+
if (!current || current.revision !== journal.revision || current.handoffId !== journal.handoffId) {
|
|
515
|
+
throw new Error('SANDBOX_CONTROL_REMOVAL_JOURNAL_REVISION_MISMATCH');
|
|
516
|
+
}
|
|
517
|
+
const state = (options.identityProbe ?? getProcessIdentityState)(current.owner);
|
|
518
|
+
if (state === 'alive') throw new Error('SANDBOX_CONTROL_REMOVE_RETRY_IN_PROGRESS');
|
|
519
|
+
if (state === 'unknown') throw new Error('SANDBOX_CONTROL_REMOVE_OWNER_UNKNOWN');
|
|
520
|
+
const startTime = getProcessStartTime(process.pid);
|
|
521
|
+
if (startTime === null) throw new Error('SANDBOX_CONTROL_REMOVE_OWNER_UNAVAILABLE');
|
|
522
|
+
const base = {
|
|
523
|
+
...current,
|
|
524
|
+
handoffId: randomUUID(),
|
|
525
|
+
transitionId: randomUUID(),
|
|
526
|
+
owner: { pid: process.pid, startTime, leaseNonce: randomUUID() }
|
|
527
|
+
};
|
|
528
|
+
return writeRemovalJournalAt(target, base, current.phase, current.revision);
|
|
529
|
+
} finally {
|
|
530
|
+
ownedLock?.release();
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
export function advanceSandboxRemovalJournalPhase(
|
|
535
|
+
journal: SandboxRemovalJournal,
|
|
536
|
+
phase: SandboxRemovalJournalPhase,
|
|
537
|
+
options: Readonly<{ resourceLock?: SandboxResourceLock }> = {}
|
|
538
|
+
): SandboxRemovalJournal {
|
|
539
|
+
const target = removalJournalPathForRecord(journal);
|
|
540
|
+
const resourceLock = options.resourceLock
|
|
541
|
+
?? acquireSandboxResourceLock(`${journal.engine}:${journal.containerId}`, { lockDomain: journal.lockDomain });
|
|
542
|
+
const ownedLock = options.resourceLock ? null : resourceLock;
|
|
543
|
+
try {
|
|
544
|
+
assertRemovalJournalLock(journal, resourceLock);
|
|
545
|
+
const current = readRemovalJournalAt(target);
|
|
546
|
+
if (!current || current.revision !== journal.revision || current.handoffId !== journal.handoffId) {
|
|
547
|
+
throw new Error('SANDBOX_CONTROL_REMOVAL_JOURNAL_REVISION_MISMATCH');
|
|
548
|
+
}
|
|
549
|
+
if (!assertNextRemovalPhase(current.phase, phase)) return current;
|
|
550
|
+
const {
|
|
551
|
+
phase: _phase,
|
|
552
|
+
expectedOldJournalRevision: _expected,
|
|
553
|
+
revision: _revision,
|
|
554
|
+
recordedAt: _recordedAt,
|
|
555
|
+
...base
|
|
556
|
+
} = current;
|
|
557
|
+
return writeRemovalJournalAt(target, base, phase, current.revision);
|
|
558
|
+
} finally {
|
|
559
|
+
ownedLock?.release();
|
|
560
|
+
}
|
|
561
|
+
}
|
|
33
562
|
|
|
34
563
|
async function awaitWithDeadline<T>(
|
|
35
564
|
operation: () => Promise<T>,
|
|
@@ -82,6 +611,30 @@ function markSandboxControlRootQuiescing(root: string): void {
|
|
|
82
611
|
}
|
|
83
612
|
}
|
|
84
613
|
|
|
614
|
+
function recordSandboxRemovalPending(
|
|
615
|
+
root: string,
|
|
616
|
+
manifest: SandboxControlManifest,
|
|
617
|
+
phase: 'container-removal' | 'container-verification',
|
|
618
|
+
journal: RemovalJournalCursor
|
|
619
|
+
): void {
|
|
620
|
+
const pendingPath = path.join(root, 'removal-pending.json');
|
|
621
|
+
const temporary = `${pendingPath}.${process.pid}.${randomUUID()}.tmp`;
|
|
622
|
+
try {
|
|
623
|
+
fs.writeFileSync(temporary, `${JSON.stringify({
|
|
624
|
+
version: 1,
|
|
625
|
+
phase,
|
|
626
|
+
containerId: manifest.containerIdentity.id,
|
|
627
|
+
generation: manifest.generation,
|
|
628
|
+
authorityFingerprint: manifest.authorityEvidence.authorityFingerprint,
|
|
629
|
+
recordedAt: Date.now()
|
|
630
|
+
})}\n`, { mode: 0o600, flag: 'wx' });
|
|
631
|
+
fs.renameSync(temporary, pendingPath);
|
|
632
|
+
} finally {
|
|
633
|
+
fs.rmSync(temporary, { force: true });
|
|
634
|
+
}
|
|
635
|
+
journal.write(phase === 'container-verification' ? 'container-absent' : 'container-removal');
|
|
636
|
+
}
|
|
637
|
+
|
|
85
638
|
function readStartupOwner(filePath: string): { raw: string; owner: OwnerIdentity } | null {
|
|
86
639
|
if (!fs.existsSync(filePath)) return null;
|
|
87
640
|
if (!regularFile(filePath)) throw new Error('SANDBOX_CONTROL_BROKER_START_TRANSITION');
|
|
@@ -498,9 +1051,10 @@ function readSandboxControlManifestValue(manifestPath: string): SandboxControlMa
|
|
|
498
1051
|
const expectedKeys = [
|
|
499
1052
|
'branch', 'channelDir', 'container', 'containerIdentity', 'engine', 'generation',
|
|
500
1053
|
'mode', 'processingDir', 'project', 'publicStatusDir', 'repoRoot', 'runtimeDir',
|
|
501
|
-
'taskId', 'token', 'worktreeRoot'
|
|
1054
|
+
'taskId', 'token', 'worktreeRoot', 'authorityEvidence'
|
|
502
1055
|
];
|
|
503
|
-
|
|
1056
|
+
const actualKeys = Object.keys(manifest).sort().join(',');
|
|
1057
|
+
if (actualKeys !== expectedKeys.sort().join(',')) {
|
|
504
1058
|
throw new Error('SANDBOX_CONTROL_MANIFEST_INVALID');
|
|
505
1059
|
}
|
|
506
1060
|
if (typeof candidate.containerIdentity !== 'object' || candidate.containerIdentity === null
|
|
@@ -522,7 +1076,8 @@ function readSandboxControlManifestValue(manifestPath: string): SandboxControlMa
|
|
|
522
1076
|
|| typeof candidate.token !== 'string'
|
|
523
1077
|
|| typeof candidate.generation !== 'string'
|
|
524
1078
|
|| (candidate.mode === 'task-bound' && (typeof candidate.taskId !== 'string' || candidate.taskId.length === 0))
|
|
525
|
-
|| (candidate.mode === 'branch-only' && candidate.taskId !== null)
|
|
1079
|
+
|| (candidate.mode === 'branch-only' && candidate.taskId !== null)
|
|
1080
|
+
|| !isSandboxAuthorityEvidence(candidate.authorityEvidence)) throw new Error('SANDBOX_CONTROL_MANIFEST_INVALID');
|
|
526
1081
|
const root = path.dirname(path.resolve(manifestPath));
|
|
527
1082
|
if (path.resolve(candidate.channelDir) !== path.join(root, 'channel')
|
|
528
1083
|
|| path.resolve(candidate.publicStatusDir) !== path.join(root, 'public')
|
|
@@ -542,6 +1097,7 @@ function readSandboxControlManifestValue(manifestPath: string): SandboxControlMa
|
|
|
542
1097
|
id: containerIdentity.id as string,
|
|
543
1098
|
labels: { ...(containerIdentity.labels as Record<string, string>) }
|
|
544
1099
|
},
|
|
1100
|
+
authorityEvidence: candidate.authorityEvidence,
|
|
545
1101
|
branch: candidate.branch,
|
|
546
1102
|
mode: candidate.mode,
|
|
547
1103
|
taskId: candidate.taskId,
|
|
@@ -816,6 +1372,9 @@ export type RemoveSandboxControlOptions = Readonly<{
|
|
|
816
1372
|
removeContainer: (timeoutMs: number) => Promise<void>;
|
|
817
1373
|
requireAbsent?: boolean;
|
|
818
1374
|
selfOwner?: BrokerOwner;
|
|
1375
|
+
resourceLock?: SandboxResourceLock;
|
|
1376
|
+
retainRemovalJournal?: boolean;
|
|
1377
|
+
removalTarget?: SandboxRemovalTargetCommit;
|
|
819
1378
|
}>;
|
|
820
1379
|
|
|
821
1380
|
export async function removeSandboxControlRoot(
|
|
@@ -827,7 +1386,16 @@ export async function removeSandboxControlRoot(
|
|
|
827
1386
|
const stat = fs.lstatSync(resolvedRoot);
|
|
828
1387
|
if (!stat.isDirectory() || stat.isSymbolicLink()) throw new Error('SANDBOX_CONTROL_CHANNEL_INVALID');
|
|
829
1388
|
const manifestPath = path.join(resolvedRoot, 'manifest.json');
|
|
1389
|
+
const manifestRawBeforeLock = fs.readFileSync(manifestPath, 'utf8');
|
|
830
1390
|
const manifest = readSandboxControlManifest(manifestPath);
|
|
1391
|
+
const resourceLock = options.resourceLock ?? acquireSandboxResourceLock(
|
|
1392
|
+
`${manifest.engine}:${manifest.containerIdentity.id}`,
|
|
1393
|
+
{ lockDomain: manifest.authorityEvidence.lockDomain }
|
|
1394
|
+
);
|
|
1395
|
+
try {
|
|
1396
|
+
if (fs.readFileSync(manifestPath, 'utf8') !== manifestRawBeforeLock) {
|
|
1397
|
+
throw new Error('SANDBOX_CONTROL_MANIFEST_CHANGED');
|
|
1398
|
+
}
|
|
831
1399
|
const inspectContainer = options.inspectContainer
|
|
832
1400
|
?? ((timeoutMs) => inspectSandboxControlContainer(manifest, { timeoutMs }));
|
|
833
1401
|
const timeoutMs = options.timeoutMs ?? options.timing?.quiesceDeadlineMs ?? DEFAULT_QUIESCE_TIMEOUT_MS;
|
|
@@ -835,10 +1403,30 @@ export async function removeSandboxControlRoot(
|
|
|
835
1403
|
const deadlineAt = Date.now() + timeoutMs;
|
|
836
1404
|
const forceAt = forceDeadline(deadlineAt, timeoutMs);
|
|
837
1405
|
const remaining = (): number => Math.max(0, deadlineAt - Date.now());
|
|
1406
|
+
const journal = startRemovalJournal(manifest, {
|
|
1407
|
+
target: options.removalTarget,
|
|
1408
|
+
identityProbe
|
|
1409
|
+
});
|
|
1410
|
+
if (journal.record.phase === 'carrier-finalizing') {
|
|
1411
|
+
if (fs.readFileSync(manifestPath, 'utf8') !== manifestRawBeforeLock
|
|
1412
|
+
|| !isSandboxControlRootQuiescing(resolvedRoot)) {
|
|
1413
|
+
throw new Error('SANDBOX_CONTROL_MANIFEST_CHANGED');
|
|
1414
|
+
}
|
|
1415
|
+
fs.rmSync(resolvedRoot, { recursive: true, force: true });
|
|
1416
|
+
if (fs.existsSync(resolvedRoot)) throw new Error('SANDBOX_CONTROL_TARGET_MISMATCH');
|
|
1417
|
+
journal.write('carrier-removed');
|
|
1418
|
+
if (!options.retainRemovalJournal) clearSandboxRemovalJournal(manifest);
|
|
1419
|
+
return;
|
|
1420
|
+
}
|
|
1421
|
+
if (sandboxRemovalPhaseIndex(journal.record.phase) >= sandboxRemovalPhaseIndex('carrier-removed')) {
|
|
1422
|
+
throw new Error('SANDBOX_CONTROL_TARGET_MISMATCH');
|
|
1423
|
+
}
|
|
1424
|
+
if (journal.record.phase === 'prepared') journal.write('target-committed');
|
|
838
1425
|
const observation = await awaitWithDeadline(
|
|
839
1426
|
() => inspectContainer(remaining()), deadlineAt, 'SANDBOX_CONTROL_REMOVE_DEADLINE_EXCEEDED'
|
|
840
1427
|
);
|
|
841
1428
|
if (observation.state === 'unknown') throw new Error(`SANDBOX_CONTROL_CONTAINER_UNKNOWN: ${observation.reason}`);
|
|
1429
|
+
journal.write(observation.state === 'absent' ? 'container-absent' : 'container-removal');
|
|
842
1430
|
if (options.requireAbsent && observation.state !== 'absent') {
|
|
843
1431
|
throw new Error('SANDBOX_CONTROL_CONTAINER_REAPPEARED');
|
|
844
1432
|
}
|
|
@@ -880,28 +1468,46 @@ export async function removeSandboxControlRoot(
|
|
|
880
1468
|
}
|
|
881
1469
|
|
|
882
1470
|
if (observation.state === 'found') {
|
|
883
|
-
if (Date.now() >= deadlineAt)
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
1471
|
+
if (Date.now() >= deadlineAt) {
|
|
1472
|
+
recordSandboxRemovalPending(resolvedRoot, manifest, 'container-removal', journal);
|
|
1473
|
+
throw new Error('SANDBOX_CONTROL_REMOVE_PENDING');
|
|
1474
|
+
}
|
|
1475
|
+
let afterRemoval: ContainerObservation;
|
|
1476
|
+
try {
|
|
1477
|
+
await awaitWithDeadline(
|
|
1478
|
+
() => options.removeContainer(remaining()),
|
|
1479
|
+
deadlineAt,
|
|
1480
|
+
'SANDBOX_CONTROL_REMOVE_DEADLINE_EXCEEDED'
|
|
1481
|
+
);
|
|
1482
|
+
afterRemoval = await awaitWithDeadline(
|
|
1483
|
+
() => inspectContainer(remaining()),
|
|
1484
|
+
deadlineAt,
|
|
1485
|
+
'SANDBOX_CONTROL_REMOVE_DEADLINE_EXCEEDED'
|
|
1486
|
+
);
|
|
1487
|
+
} catch (error) {
|
|
1488
|
+
if (error instanceof Error && error.message === 'SANDBOX_CONTROL_REMOVE_DEADLINE_EXCEEDED') {
|
|
1489
|
+
recordSandboxRemovalPending(resolvedRoot, manifest, 'container-removal', journal);
|
|
1490
|
+
throw new Error('SANDBOX_CONTROL_REMOVE_PENDING');
|
|
1491
|
+
}
|
|
1492
|
+
throw error;
|
|
1493
|
+
}
|
|
894
1494
|
if (afterRemoval.state !== 'absent') {
|
|
895
1495
|
throw new Error(afterRemoval.state === 'unknown'
|
|
896
1496
|
? `SANDBOX_CONTROL_CONTAINER_UNKNOWN: ${afterRemoval.reason}`
|
|
897
1497
|
: 'SANDBOX_CONTROL_CONTAINER_STILL_EXISTS');
|
|
898
1498
|
}
|
|
1499
|
+
journal.write('container-absent');
|
|
899
1500
|
}
|
|
900
1501
|
|
|
1502
|
+
const currentManifestRaw = fs.readFileSync(manifestPath, 'utf8');
|
|
901
1503
|
const currentManifest = readSandboxControlManifest(manifestPath);
|
|
902
|
-
if (
|
|
1504
|
+
if (currentManifestRaw !== manifestRawBeforeLock
|
|
1505
|
+
|| currentManifest.engine !== manifest.engine
|
|
1506
|
+
|| currentManifest.containerIdentity.id !== manifest.containerIdentity.id
|
|
1507
|
+
|| currentManifest.authorityEvidence.lockDomain !== manifest.authorityEvidence.lockDomain
|
|
1508
|
+
|| currentManifest.token !== manifest.token || currentManifest.generation !== manifest.generation
|
|
903
1509
|
|| !isSandboxControlRootQuiescing(resolvedRoot)) {
|
|
904
|
-
throw new Error('
|
|
1510
|
+
throw new Error('SANDBOX_CONTROL_MANIFEST_CHANGED');
|
|
905
1511
|
}
|
|
906
1512
|
const currentBroker = readBrokerOwner(brokerPath);
|
|
907
1513
|
if (selfOwned && (!currentBroker || !selfOwner || !sameOwner(selfOwner, currentBroker)
|
|
@@ -970,7 +1576,13 @@ export async function removeSandboxControlRoot(
|
|
|
970
1576
|
if (fs.existsSync(brokerPath) || fs.existsSync(statusPath)) {
|
|
971
1577
|
throw new Error('SANDBOX_CONTROL_OWNER_EVIDENCE_REMAINS');
|
|
972
1578
|
}
|
|
1579
|
+
journal.write('carrier-finalizing');
|
|
973
1580
|
fs.rmSync(resolvedRoot, { recursive: true, force: true });
|
|
1581
|
+
journal.write('carrier-removed');
|
|
1582
|
+
if (!options.retainRemovalJournal) clearSandboxRemovalJournal(manifest);
|
|
1583
|
+
} finally {
|
|
1584
|
+
if (!options.resourceLock) resourceLock.release();
|
|
1585
|
+
}
|
|
974
1586
|
}
|
|
975
1587
|
|
|
976
1588
|
export type GarbageCollectSandboxControlOptions = Omit<RemoveSandboxControlOptions, 'removeContainer' | 'requireAbsent'>;
|