@fitlab-ai/agent-infra 0.9.14 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -0
- package/README.zh-CN.md +10 -0
- package/bin/internal-cli.sh +45 -0
- package/dist/bin/internal-cli.js +93 -30
- package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +165 -27
- package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +9 -4
- package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +43 -3
- package/dist/lib/defaults.json +103 -103
- package/dist/lib/fs/durable-write.js +39 -0
- package/dist/lib/fs/file-lock.js +111 -0
- package/dist/lib/host-control/audit.js +19 -0
- package/dist/lib/host-control/client.js +142 -0
- package/dist/lib/host-control/command.js +49 -0
- package/dist/lib/host-control/path.js +160 -0
- package/dist/lib/host-control/secure-fs.js +140 -0
- package/dist/lib/host-control/server.js +159 -0
- package/dist/lib/host-control/service.js +112 -0
- package/dist/lib/init.js +11 -2
- package/dist/lib/internal/cli-route-inventory.js +4 -2
- package/dist/lib/internal/codex-lifecycle.js +6 -6
- package/dist/lib/internal/demo-promotion.js +322 -0
- package/dist/lib/internal/demo-transcript.js +454 -0
- package/dist/lib/internal/host-control.js +28 -0
- package/dist/lib/internal/manual-validation.js +331 -0
- package/dist/lib/internal/platform-pr.js +18 -4
- package/dist/lib/internal/release-workflow.js +63 -84
- package/dist/lib/internal/sandbox-control.js +149 -2
- package/dist/lib/internal/task-artifact.js +16 -263
- package/dist/lib/internal/task-create.js +6 -5
- package/dist/lib/internal/task-event.js +37 -37
- package/dist/lib/internal/task-lifecycle.js +2 -2
- package/dist/lib/internal/task-operation-registry.js +101 -4
- package/dist/lib/internal/task-review.js +11 -56
- package/dist/lib/internal/task-validate.js +30 -10
- package/dist/lib/owned-path.js +33 -0
- package/dist/lib/platform/check-status.js +11 -0
- package/dist/lib/platform/comment-safety.js +4 -1
- package/dist/lib/platform/completion-backfill.js +1 -1
- package/dist/lib/platform/github-data.js +241 -0
- package/dist/lib/platform/github-provider.js +15 -20
- package/dist/lib/platform/issue-comments.js +18 -12
- package/dist/lib/platform/issues.js +2 -180
- package/dist/lib/platform/pr-change-report.js +5 -4
- package/dist/lib/platform/pr-checks.js +12 -65
- package/dist/lib/platform/pr-summary.js +117 -33
- package/dist/lib/platform/pull-requests.js +5 -108
- package/dist/lib/platform/snapshots.js +2 -0
- package/dist/lib/platform/task-identities.js +2 -9
- package/dist/lib/platform/verification-sync.js +132 -143
- package/dist/lib/pr-review/evidence-grading.js +4 -8
- package/dist/lib/release/workflow.js +1 -1
- package/dist/lib/sandbox/commands/create.js +7 -2
- package/dist/lib/sandbox/commands/rm.js +19 -1511
- package/dist/lib/sandbox/control/audit.js +203 -0
- package/dist/lib/sandbox/control/client.js +93 -54
- package/dist/lib/sandbox/control/executor.js +129 -65
- package/dist/lib/sandbox/control/finalization-response.js +27 -0
- package/dist/lib/sandbox/control/identity-sentinel.js +107 -0
- package/dist/lib/sandbox/control/lifecycle.js +36 -2
- package/dist/lib/sandbox/control/native-file-lock.js +6 -98
- package/dist/lib/sandbox/control/protocol.js +55 -4
- package/dist/lib/sandbox/control/server.js +518 -127
- package/dist/lib/sandbox/control/state.js +64 -0
- package/dist/lib/sandbox/control/task-workflow.js +74 -0
- package/dist/lib/sandbox/control/workflow-executor.js +89 -0
- package/dist/lib/sandbox/index.js +1 -2
- package/dist/lib/sandbox/recovery.js +28 -4
- package/dist/lib/sandbox/removal.js +822 -0
- package/dist/lib/sandbox/task-cleanup.js +217 -0
- package/dist/lib/sandbox/workspace-identity.js +17 -1
- package/dist/lib/sandbox/workspace-view.js +13 -14
- package/dist/lib/task/activity-intent.js +2 -1
- package/dist/lib/task/activity-log.js +30 -24
- package/dist/lib/task/artifact-command.js +79 -0
- package/dist/lib/task/artifact-lifecycle.js +30 -28
- package/dist/lib/task/artifact-name.js +36 -0
- package/dist/lib/task/artifact-operations.js +29 -289
- package/dist/lib/task/artifact-receipts.js +3 -15
- package/dist/lib/task/artifact-recovery.js +430 -0
- package/dist/lib/task/artifact-repair-intent.js +134 -29
- package/dist/lib/task/artifact-schema.js +6 -14
- package/dist/lib/task/capabilities.js +12 -19
- package/dist/lib/task/codex-orchestration.js +71 -25
- package/dist/lib/task/commit-identity.js +28 -0
- package/dist/lib/task/commit-operation.js +33 -20
- package/dist/lib/task/control-authority.js +403 -6
- package/dist/lib/task/control-recovery.js +140 -0
- package/dist/lib/task/create-service.js +1 -8
- package/dist/lib/task/create.js +62 -9
- package/dist/lib/task/decision-details.js +2 -46
- package/dist/lib/task/delegation-receipts.js +64 -3
- package/dist/lib/task/events.js +275 -22
- package/dist/lib/task/finalization-state.js +9 -0
- package/dist/lib/task/human-override.js +8 -14
- package/dist/lib/task/implementation-inputs.js +3 -3
- package/dist/lib/task/intermediate-cleanup.js +441 -0
- package/dist/lib/task/invalidation.js +18 -65
- package/dist/lib/task/ledger-intents.js +7 -22
- package/dist/lib/task/ledger.js +1 -3
- package/dist/lib/task/lifecycle-recovery.js +549 -0
- package/dist/lib/task/lifecycle.js +31 -4
- package/dist/lib/task/local-artifact-finalization.js +197 -197
- package/dist/lib/task/manual-validation-completion.js +26 -0
- package/dist/lib/task/manual-validation-evidence.js +130 -0
- package/dist/lib/task/manual-validation-receipt.js +105 -0
- package/dist/lib/task/manual-validation-shared.js +40 -0
- package/dist/lib/task/manual-validation-transaction.js +243 -0
- package/dist/lib/task/markdown.js +47 -0
- package/dist/lib/task/orchestration.js +66 -19
- package/dist/lib/task/pr-delivery-fact.js +12 -56
- package/dist/lib/task/qualification-audit.js +20 -34
- package/dist/lib/task/recovery-warning.js +19 -0
- package/dist/lib/task/review-artifacts.js +2 -18
- package/dist/lib/task/review-command.js +26 -0
- package/dist/lib/task/review-finalization.js +175 -216
- package/dist/lib/task/review-fingerprint.js +2 -2
- package/dist/lib/task/sections.js +34 -56
- package/dist/lib/task/short-id.js +15 -7
- package/dist/lib/task/task-execution-lock.js +1 -1
- package/dist/lib/task/verification-engine.js +119 -337
- package/dist/lib/task/verification.js +1 -1
- package/dist/lib/task/workflow-command.js +16 -0
- package/dist/lib/task/workflow-warning-intents.js +0 -16
- package/dist/lib/task/workflow-warnings.js +28 -2
- package/dist/lib/update.js +2 -0
- package/dist/lib/version.js +1 -11
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +223 -33
- package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +10 -4
- package/lib/agent-clients/adapters/codex-lifecycle/store.ts +55 -3
- package/lib/fs/durable-write.ts +30 -0
- package/lib/fs/file-lock.ts +113 -0
- package/lib/host-control/audit.ts +22 -0
- package/lib/host-control/client.ts +172 -0
- package/lib/host-control/command.ts +60 -0
- package/lib/host-control/path.ts +167 -0
- package/lib/host-control/secure-fs.ts +135 -0
- package/lib/host-control/server.ts +180 -0
- package/lib/host-control/service.ts +133 -0
- package/lib/init.ts +15 -2
- package/lib/internal/cli-route-inventory.ts +4 -2
- package/lib/internal/codex-lifecycle.ts +6 -6
- package/lib/internal/demo-promotion.ts +354 -0
- package/lib/internal/demo-transcript.ts +419 -0
- package/lib/internal/host-control.ts +28 -0
- package/lib/internal/manual-validation.ts +315 -0
- package/lib/internal/platform-pr.ts +15 -4
- package/lib/internal/release-workflow.ts +74 -82
- package/lib/internal/sandbox-control.ts +162 -1
- package/lib/internal/task-artifact.ts +14 -217
- package/lib/internal/task-create.ts +5 -5
- package/lib/internal/task-event.ts +20 -11
- package/lib/internal/task-lifecycle.ts +4 -3
- package/lib/internal/task-operation-registry.ts +111 -4
- package/lib/internal/task-review.ts +9 -54
- package/lib/internal/task-validate.ts +26 -10
- package/lib/owned-path.ts +26 -0
- package/lib/platform/check-status.ts +9 -0
- package/lib/platform/comment-safety.ts +5 -0
- package/lib/platform/completion-backfill.ts +1 -1
- package/lib/platform/github-data.ts +322 -0
- package/lib/platform/github-provider.ts +15 -20
- package/lib/platform/issue-comments.ts +21 -14
- package/lib/platform/issues.ts +5 -246
- package/lib/platform/pr-change-report.ts +7 -5
- package/lib/platform/pr-checks.ts +13 -77
- package/lib/platform/pr-summary.ts +123 -35
- package/lib/platform/pull-requests.ts +6 -173
- package/lib/platform/snapshots.ts +53 -0
- package/lib/platform/task-identities.ts +3 -14
- package/lib/platform/verification-sync.ts +150 -156
- package/lib/pr-review/evidence-grading.ts +4 -9
- package/lib/release/workflow.ts +3 -3
- package/lib/sandbox/commands/create.ts +7 -6
- package/lib/sandbox/commands/rm.ts +21 -1895
- package/lib/sandbox/control/audit.ts +253 -0
- package/lib/sandbox/control/client.ts +109 -66
- package/lib/sandbox/control/executor.ts +134 -74
- package/lib/sandbox/control/finalization-response.ts +29 -0
- package/lib/sandbox/control/identity-sentinel.ts +137 -0
- package/lib/sandbox/control/lifecycle.ts +45 -3
- package/lib/sandbox/control/native-file-lock.ts +6 -107
- package/lib/sandbox/control/protocol.ts +80 -6
- package/lib/sandbox/control/server.ts +567 -132
- package/lib/sandbox/control/state.ts +86 -0
- package/lib/sandbox/control/task-workflow.ts +95 -0
- package/lib/sandbox/control/workflow-executor.ts +92 -0
- package/lib/sandbox/index.ts +1 -2
- package/lib/sandbox/recovery.ts +28 -5
- package/lib/sandbox/removal.ts +1142 -0
- package/lib/sandbox/task-cleanup.ts +209 -0
- package/lib/sandbox/workspace-identity.ts +19 -3
- package/lib/sandbox/workspace-view.ts +17 -15
- package/lib/task/activity-intent.ts +2 -2
- package/lib/task/activity-log.ts +27 -19
- package/lib/task/artifact-command.ts +85 -0
- package/lib/task/artifact-lifecycle.ts +31 -26
- package/lib/task/artifact-name.ts +55 -0
- package/lib/task/artifact-operations.ts +34 -310
- package/lib/task/artifact-receipts.ts +3 -14
- package/lib/task/artifact-recovery.ts +519 -0
- package/lib/task/artifact-repair-intent.ts +162 -33
- package/lib/task/artifact-schema.ts +5 -34
- package/lib/task/capabilities.ts +13 -22
- package/lib/task/codex-orchestration.ts +85 -24
- package/lib/task/commit-identity.ts +31 -0
- package/lib/task/commit-operation.ts +37 -20
- package/lib/task/control-authority.ts +540 -10
- package/lib/task/control-recovery.ts +186 -0
- package/lib/task/create-service.ts +1 -10
- package/lib/task/create.ts +63 -8
- package/lib/task/decision-details.ts +3 -71
- package/lib/task/delegation-receipts.ts +73 -2
- package/lib/task/events.ts +309 -24
- package/lib/task/finalization-state.ts +11 -0
- package/lib/task/human-override.ts +8 -16
- package/lib/task/implementation-inputs.ts +3 -3
- package/lib/task/intermediate-cleanup.ts +493 -0
- package/lib/task/invalidation.ts +18 -62
- package/lib/task/ledger-intents.ts +7 -24
- package/lib/task/ledger.ts +1 -3
- package/lib/task/lifecycle-recovery.ts +691 -0
- package/lib/task/lifecycle.ts +30 -5
- package/lib/task/local-artifact-finalization.ts +244 -234
- package/lib/task/manual-validation-completion.ts +40 -0
- package/lib/task/manual-validation-evidence.ts +209 -0
- package/lib/task/manual-validation-receipt.ts +156 -0
- package/lib/task/manual-validation-shared.ts +50 -0
- package/lib/task/manual-validation-transaction.ts +304 -0
- package/lib/task/markdown.ts +70 -0
- package/lib/task/orchestration.ts +84 -19
- package/lib/task/pr-delivery-fact.ts +15 -62
- package/lib/task/qualification-audit.ts +22 -33
- package/lib/task/recovery-warning.ts +21 -0
- package/lib/task/review-artifacts.ts +1 -20
- package/lib/task/review-command.ts +24 -0
- package/lib/task/review-finalization.ts +183 -292
- package/lib/task/review-fingerprint.ts +2 -2
- package/lib/task/sections.ts +33 -54
- package/lib/task/short-id.ts +27 -5
- package/lib/task/task-execution-lock.ts +1 -1
- package/lib/task/verification-engine.ts +113 -383
- package/lib/task/verification-types.ts +15 -0
- package/lib/task/verification.ts +1 -1
- package/lib/task/workflow-command.ts +17 -0
- package/lib/task/workflow-warning-intents.ts +0 -13
- package/lib/task/workflow-warnings.ts +21 -2
- package/lib/update.ts +6 -0
- package/lib/version.ts +1 -15
- package/package.json +6 -6
- package/templates/.agents/hooks/lifecycle-delegation.js +6 -6
- package/templates/.agents/rules/create-issue.en.md +1 -1
- package/templates/.agents/rules/create-issue.zh-CN.md +1 -1
- package/templates/.agents/rules/issue-sync.en.md +4 -2
- package/templates/.agents/rules/issue-sync.zh-CN.md +4 -3
- package/templates/.agents/rules/local-artifact-repair.en.md +46 -39
- package/templates/.agents/rules/local-artifact-repair.zh-CN.md +43 -36
- package/templates/.agents/rules/testing-discipline.en.md +2 -0
- package/templates/.agents/rules/testing-discipline.zh-CN.md +2 -0
- package/templates/.agents/scripts/package.json +3 -0
- package/templates/.agents/skills/analyze-task/SKILL.en.md +6 -6
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +6 -6
- package/templates/.agents/skills/analyze-task/config/verify.en.json +1 -1
- package/templates/.agents/skills/analyze-task/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/block-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/block-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/block-task/config/verify.json +1 -1
- package/templates/.agents/skills/cancel-task/SKILL.en.md +3 -3
- package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/cancel-task/config/verify.json +1 -1
- package/templates/.agents/skills/code-task/SKILL.en.md +7 -7
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +7 -7
- package/templates/.agents/skills/code-task/config/verify.en.json +1 -1
- package/templates/.agents/skills/code-task/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/commit/reference/issue-metadata-sync.en.md +1 -1
- package/templates/.agents/skills/commit/reference/issue-metadata-sync.zh-CN.md +1 -1
- package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +33 -21
- package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +33 -21
- package/templates/.agents/skills/complete-manual-validation/config/verify.en.json +1 -1
- package/templates/.agents/skills/complete-manual-validation/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/complete-manual-validation/reference/report-template.en.md +2 -0
- package/templates/.agents/skills/complete-manual-validation/reference/report-template.zh-CN.md +2 -0
- package/templates/.agents/skills/complete-manual-validation/reference/summary-update.en.md +16 -7
- package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +16 -7
- package/templates/.agents/skills/complete-task/SKILL.en.md +3 -3
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/complete-task/config/verify.en.json +2 -2
- package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +2 -2
- package/templates/.agents/skills/complete-task/reference/external-delivery.en.md +1 -1
- package/templates/.agents/skills/complete-task/reference/external-delivery.zh-CN.md +1 -1
- package/templates/.agents/skills/create-pr/SKILL.en.md +2 -2
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/create-pr/config/verify.json +1 -1
- package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +2 -2
- package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +2 -2
- package/templates/.agents/skills/create-task/SKILL.en.md +5 -5
- package/templates/.agents/skills/create-task/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/create-task/config/verify.json +1 -1
- package/templates/.agents/skills/import-issue/SKILL.en.md +8 -8
- package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +8 -8
- package/templates/.agents/skills/import-issue/config/verify.json +2 -2
- package/templates/.agents/skills/plan-task/SKILL.en.md +4 -4
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/plan-task/config/verify.en.json +1 -1
- package/templates/.agents/skills/plan-task/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/release/SKILL.en.md +7 -3
- package/templates/.agents/skills/release/SKILL.zh-CN.md +7 -3
- package/templates/.agents/skills/restore-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/restore-task/config/verify.json +1 -1
- package/templates/.agents/skills/review-analysis/SKILL.en.md +2 -2
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/review-analysis/config/verify.en.json +1 -1
- package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/review-analysis/reference/report-template.en.md +7 -7
- package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +6 -6
- package/templates/.agents/skills/review-code/SKILL.en.md +2 -2
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/review-code/config/verify.en.json +1 -1
- package/templates/.agents/skills/review-code/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/review-code/reference/report-template.en.md +12 -12
- package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +12 -12
- package/templates/.agents/skills/review-plan/SKILL.en.md +2 -2
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/review-plan/config/verify.en.json +1 -1
- package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/review-plan/reference/report-template.en.md +7 -7
- package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +7 -7
- package/templates/.agents/skills/review-pr/config/verify.en.json +1 -1
- package/templates/.agents/skills/review-pr/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/review-pr/reference/evidence-grading.en.md +3 -3
- package/templates/.agents/skills/review-pr/reference/evidence-grading.zh-CN.md +3 -3
- package/templates/.agents/skills/run-manual-validation/SKILL.en.md +20 -3
- package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +20 -3
- package/templates/.agents/skills/run-manual-validation/config/verify.en.json +1 -1
- package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +1 -1
- package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.en.md +4 -1
- package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.zh-CN.md +4 -1
- package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +2 -0
- package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +2 -0
- package/templates/.agents/skills/run-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/test/SKILL.en.md +17 -6
- package/templates/.agents/skills/test/SKILL.zh-CN.md +17 -6
- package/templates/.agents/templates/task.en.md +1 -1
- package/templates/.agents/templates/task.zh-CN.md +1 -1
- package/templates/.claude/commands/import-issue.en.md +1 -1
- package/templates/.claude/commands/import-issue.zh-CN.md +1 -1
- package/templates/.claude/commands/restore-task.en.md +1 -1
- package/templates/.claude/commands/restore-task.zh-CN.md +1 -1
- package/templates/.claude/commands/run-manual-validation.en.md +1 -1
- package/templates/.claude/commands/run-manual-validation.zh-CN.md +1 -1
- package/dist/lib/platform/adapters.js +0 -56
- package/lib/platform/adapters.ts +0 -197
|
@@ -1,71 +1,200 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import { isDeepStrictEqual } from 'node:util';
|
|
3
4
|
|
|
4
5
|
import type { ArtifactSchemaFamily } from './artifact-schema.ts';
|
|
6
|
+
import { parseArtifactName } from './artifact-name.ts';
|
|
5
7
|
|
|
6
|
-
type
|
|
7
|
-
|
|
8
|
+
export type ArtifactRecoveryPhase =
|
|
9
|
+
| 'orchestration.prepare'
|
|
10
|
+
| 'artifact.finalize-local'
|
|
11
|
+
| 'task-event.completed';
|
|
12
|
+
export type ArtifactRecoveryState =
|
|
13
|
+
| 'awaiting-recovery'
|
|
14
|
+
| 'finalize-ready'
|
|
15
|
+
| 'commit-started'
|
|
16
|
+
| 'passed'
|
|
17
|
+
| 'consumed'
|
|
18
|
+
| 'aborted';
|
|
19
|
+
|
|
20
|
+
export type ArtifactRecoveryIntent = Readonly<{
|
|
21
|
+
version: 3;
|
|
8
22
|
taskId: string;
|
|
9
23
|
family: ArtifactSchemaFamily;
|
|
10
24
|
artifact: string;
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
25
|
+
round: number;
|
|
26
|
+
state: ArtifactRecoveryState;
|
|
27
|
+
baselineSha256: string;
|
|
28
|
+
baselineSemanticDigest: string;
|
|
29
|
+
stagingId: string;
|
|
30
|
+
candidateSha256: string | null;
|
|
31
|
+
finalArtifactSha256: string | null;
|
|
32
|
+
finalSemanticDigest: string | null;
|
|
33
|
+
recoveryOperationId: string;
|
|
34
|
+
phase: ArtifactRecoveryPhase | null;
|
|
35
|
+
authorityDigest: string | null;
|
|
36
|
+
requestId: string;
|
|
37
|
+
errorCode: string | null;
|
|
38
|
+
errorMessage: string | null;
|
|
39
|
+
createdAt: number;
|
|
40
|
+
updatedAt: number;
|
|
15
41
|
}>;
|
|
16
42
|
|
|
17
|
-
function
|
|
43
|
+
export function artifactRecoveryIntentRoot(repoRoot: string): string {
|
|
18
44
|
return path.join(repoRoot, '.agents', 'workspace', '.local-artifact-finalization-intents');
|
|
19
45
|
}
|
|
20
46
|
|
|
47
|
+
export function artifactRecoveryRoot(taskDir: string, stagingId: string): string {
|
|
48
|
+
return path.join(taskDir, '.local-artifact-recovery', stagingId);
|
|
49
|
+
}
|
|
50
|
+
|
|
21
51
|
function intentPath(repoRoot: string, taskId: string, family: ArtifactSchemaFamily, artifact: string): string {
|
|
22
|
-
return path.join(
|
|
52
|
+
return path.join(artifactRecoveryIntentRoot(repoRoot), `${taskId}-${family}-${artifact}.json`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function validDigest(value: unknown): value is string {
|
|
56
|
+
return typeof value === 'string' && /^[a-f0-9]{64}$/u.test(value);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function validTaskId(value: unknown): value is string {
|
|
60
|
+
return typeof value === 'string' && /^TASK-\d{8}-\d{6}$/u.test(value);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function validToken(value: unknown): value is string {
|
|
64
|
+
return typeof value === 'string' && value.length > 0 && !/[\r\n]/u.test(value);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function validRecoveryId(value: unknown): value is string {
|
|
68
|
+
return typeof value === 'string' && /^[a-f0-9-]{16,64}$/u.test(value);
|
|
23
69
|
}
|
|
24
70
|
|
|
25
|
-
function
|
|
71
|
+
function validArtifact(value: unknown, family: unknown, round: unknown): value is string {
|
|
72
|
+
if (typeof value !== 'string' || path.basename(value) !== value || !value.endsWith('.md')) return false;
|
|
73
|
+
const parsed = parseArtifactName(value);
|
|
74
|
+
return Boolean(parsed && parsed.family === family && parsed.round === round);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function exactKeys(value: Record<string, unknown>, keys: readonly string[]): boolean {
|
|
78
|
+
return Object.keys(value).sort().join(',') === [...keys].sort().join(',');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function isArtifactRecoveryIntent(value: unknown): value is ArtifactRecoveryIntent {
|
|
26
82
|
if (!value || typeof value !== 'object' || Array.isArray(value)) return false;
|
|
27
83
|
const intent = value as Record<string, unknown>;
|
|
28
|
-
return intent
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
&&
|
|
35
|
-
&&
|
|
36
|
-
&&
|
|
84
|
+
return exactKeys(intent, [
|
|
85
|
+
'artifact', 'authorityDigest', 'baselineSemanticDigest', 'baselineSha256', 'candidateSha256',
|
|
86
|
+
'createdAt', 'errorCode', 'errorMessage', 'family', 'finalArtifactSha256', 'finalSemanticDigest',
|
|
87
|
+
'phase', 'recoveryOperationId', 'requestId', 'round', 'stagingId', 'state', 'taskId', 'updatedAt',
|
|
88
|
+
'version'
|
|
89
|
+
])
|
|
90
|
+
&& intent.version === 3
|
|
91
|
+
&& validTaskId(intent.taskId)
|
|
92
|
+
&& typeof intent.family === 'string'
|
|
93
|
+
&& ['analysis', 'review-analysis', 'plan', 'review-plan', 'code', 'review-code'].includes(intent.family)
|
|
94
|
+
&& Number.isSafeInteger(intent.round) && (intent.round as number) > 0
|
|
95
|
+
&& validArtifact(intent.artifact, intent.family, intent.round)
|
|
96
|
+
&& ['awaiting-recovery', 'finalize-ready', 'commit-started', 'passed', 'consumed', 'aborted'].includes(String(intent.state))
|
|
97
|
+
&& validDigest(intent.baselineSha256)
|
|
98
|
+
&& validDigest(intent.baselineSemanticDigest)
|
|
99
|
+
&& validRecoveryId(intent.stagingId)
|
|
100
|
+
&& (intent.candidateSha256 === null || validDigest(intent.candidateSha256))
|
|
101
|
+
&& (intent.finalArtifactSha256 === null || validDigest(intent.finalArtifactSha256))
|
|
102
|
+
&& (intent.finalSemanticDigest === null || validDigest(intent.finalSemanticDigest))
|
|
103
|
+
&& validRecoveryId(intent.recoveryOperationId)
|
|
104
|
+
&& intent.stagingId === intent.recoveryOperationId
|
|
105
|
+
&& (intent.phase === null || ['orchestration.prepare', 'artifact.finalize-local', 'task-event.completed'].includes(String(intent.phase)))
|
|
106
|
+
&& (intent.authorityDigest === null || validDigest(intent.authorityDigest))
|
|
107
|
+
&& validToken(intent.requestId)
|
|
108
|
+
&& (intent.errorCode === null || validToken(intent.errorCode))
|
|
109
|
+
&& (intent.errorMessage === null || validToken(intent.errorMessage))
|
|
110
|
+
&& Number.isSafeInteger(intent.createdAt)
|
|
111
|
+
&& Number.isSafeInteger(intent.updatedAt)
|
|
112
|
+
&& (intent.state === 'awaiting-recovery'
|
|
113
|
+
? intent.finalArtifactSha256 === null && intent.finalSemanticDigest === null
|
|
114
|
+
: true)
|
|
115
|
+
&& (intent.state === 'finalize-ready' || intent.state === 'commit-started' || intent.state === 'passed' || intent.state === 'consumed'
|
|
116
|
+
? validDigest(intent.candidateSha256) && validDigest(intent.finalArtifactSha256) && validDigest(intent.finalSemanticDigest)
|
|
117
|
+
: true)
|
|
118
|
+
&& (intent.state === 'aborted' ? intent.errorCode !== null && intent.errorMessage !== null : true);
|
|
37
119
|
}
|
|
38
120
|
|
|
39
|
-
function
|
|
121
|
+
export function readArtifactRecoveryIntent(
|
|
40
122
|
repoRoot: string,
|
|
41
123
|
taskId: string,
|
|
42
124
|
family: ArtifactSchemaFamily,
|
|
43
125
|
artifact: string
|
|
44
|
-
):
|
|
126
|
+
): ArtifactRecoveryIntent | null {
|
|
127
|
+
if (!validTaskId(taskId) || !validArtifact(artifact, family, parseArtifactName(artifact)?.round)) {
|
|
128
|
+
throw new Error('ARTIFACT_RECOVERY_INTENT_INVALID: recovery journal identity is invalid');
|
|
129
|
+
}
|
|
45
130
|
const target = intentPath(repoRoot, taskId, family, artifact);
|
|
46
131
|
if (!fs.existsSync(target)) return null;
|
|
47
132
|
let value: unknown;
|
|
48
133
|
try { value = JSON.parse(fs.readFileSync(target, 'utf8')) as unknown; }
|
|
49
|
-
catch (error) { throw new Error(`
|
|
50
|
-
if (!
|
|
51
|
-
throw new Error('
|
|
134
|
+
catch (error) { throw new Error(`ARTIFACT_RECOVERY_INTENT_INVALID: ${String(error)}`); }
|
|
135
|
+
if (!isArtifactRecoveryIntent(value) || value.taskId !== taskId || value.family !== family || value.artifact !== artifact) {
|
|
136
|
+
throw new Error('ARTIFACT_RECOVERY_INTENT_INVALID: recovery journal schema is invalid');
|
|
52
137
|
}
|
|
53
138
|
return value;
|
|
54
139
|
}
|
|
55
140
|
|
|
56
|
-
function
|
|
57
|
-
|
|
58
|
-
|
|
141
|
+
export function writeArtifactRecoveryIntent(
|
|
142
|
+
repoRoot: string,
|
|
143
|
+
value: unknown,
|
|
144
|
+
options: Readonly<{ expected?: ArtifactRecoveryIntent | null }> = {}
|
|
145
|
+
): void {
|
|
146
|
+
if (!isArtifactRecoveryIntent(value)) throw new Error('ARTIFACT_RECOVERY_INTENT_INVALID: recovery journal schema is invalid');
|
|
147
|
+
const intent = value;
|
|
148
|
+
const directory = artifactRecoveryIntentRoot(repoRoot);
|
|
59
149
|
fs.mkdirSync(directory, { recursive: true, mode: 0o700 });
|
|
60
|
-
const target = intentPath(repoRoot,
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
try { fs.
|
|
150
|
+
const target = intentPath(repoRoot, intent.taskId, intent.family, intent.artifact);
|
|
151
|
+
const lock = `${target}.lock`;
|
|
152
|
+
let descriptor: number;
|
|
153
|
+
try { descriptor = fs.openSync(lock, 'wx', 0o600); }
|
|
64
154
|
catch (error) {
|
|
65
|
-
|
|
155
|
+
if ((error as NodeJS.ErrnoException).code === 'EEXIST') {
|
|
156
|
+
throw new Error('ARTIFACT_RECOVERY_INTENT_CONFLICT: another journal writer owns the intent');
|
|
157
|
+
}
|
|
66
158
|
throw error;
|
|
67
159
|
}
|
|
160
|
+
try {
|
|
161
|
+
const current = fs.existsSync(target)
|
|
162
|
+
? readArtifactRecoveryIntent(repoRoot, intent.taskId, intent.family, intent.artifact)
|
|
163
|
+
: null;
|
|
164
|
+
if (!isDeepStrictEqual(current, options.expected ?? null)) {
|
|
165
|
+
throw new Error('ARTIFACT_RECOVERY_INTENT_CONFLICT: recovery journal changed during finalization');
|
|
166
|
+
}
|
|
167
|
+
const temporary = `${target}.${process.pid}.${Date.now()}.tmp`;
|
|
168
|
+
fs.writeFileSync(temporary, `${JSON.stringify(intent)}\n`, { encoding: 'utf8', mode: 0o600, flag: 'wx' });
|
|
169
|
+
try { fs.renameSync(temporary, target); }
|
|
170
|
+
catch (error) {
|
|
171
|
+
try { fs.unlinkSync(temporary); } catch { /* preserve the primary error */ }
|
|
172
|
+
throw error;
|
|
173
|
+
}
|
|
174
|
+
} finally {
|
|
175
|
+
fs.closeSync(descriptor);
|
|
176
|
+
try { fs.unlinkSync(lock); } catch { /* preserve the primary result */ }
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export function findArtifactRecoveryIntentsByOperation(
|
|
181
|
+
repoRoot: string,
|
|
182
|
+
operationId: string
|
|
183
|
+
): readonly ArtifactRecoveryIntent[] {
|
|
184
|
+
if (!validToken(operationId)) throw new Error('ARTIFACT_RECOVERY_OPERATION_INVALID: recovery operation id is invalid');
|
|
185
|
+
const directory = artifactRecoveryIntentRoot(repoRoot);
|
|
186
|
+
if (!fs.existsSync(directory)) return Object.freeze([]);
|
|
187
|
+
return Object.freeze(fs.readdirSync(directory)
|
|
188
|
+
.filter((name) => name.endsWith('.json'))
|
|
189
|
+
.map((name) => {
|
|
190
|
+
const target = path.join(directory, name);
|
|
191
|
+
let value: unknown;
|
|
192
|
+
try { value = JSON.parse(fs.readFileSync(target, 'utf8')) as unknown; }
|
|
193
|
+
catch (error) { throw new Error(`ARTIFACT_RECOVERY_INTENT_INVALID: ${String(error)}`); }
|
|
194
|
+
if (!isArtifactRecoveryIntent(value)) throw new Error('ARTIFACT_RECOVERY_INTENT_INVALID: recovery journal schema is invalid');
|
|
195
|
+
return value;
|
|
196
|
+
})
|
|
197
|
+
.filter((value) => value.recoveryOperationId === operationId));
|
|
68
198
|
}
|
|
69
199
|
|
|
70
|
-
export {
|
|
71
|
-
export type { ArtifactRepairIntent };
|
|
200
|
+
export { intentPath as artifactRecoveryIntentPath };
|
|
@@ -1,13 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
| 'review-analysis'
|
|
4
|
-
| 'plan'
|
|
5
|
-
| 'review-plan'
|
|
6
|
-
| 'code'
|
|
7
|
-
| 'review-code'
|
|
8
|
-
| 'manual-validation'
|
|
9
|
-
| 'validation-run'
|
|
10
|
-
| 'pr-review';
|
|
1
|
+
import { parseArtifactName } from './artifact-name.ts';
|
|
2
|
+
import type { ArtifactFamily } from './artifact-name.ts';
|
|
11
3
|
|
|
12
4
|
type ArtifactSchemaFamily = Exclude<ArtifactFamily, 'manual-validation' | 'validation-run' | 'pr-review'>;
|
|
13
5
|
type ArtifactLocale = 'zh-CN' | 'en';
|
|
@@ -30,25 +22,6 @@ type ArtifactSkeletonInput = Readonly<{
|
|
|
30
22
|
locale?: ArtifactLocale;
|
|
31
23
|
}>;
|
|
32
24
|
|
|
33
|
-
type ArtifactFamilySpec = Readonly<{
|
|
34
|
-
family: ArtifactFamily;
|
|
35
|
-
sectionAliases: readonly [string, string];
|
|
36
|
-
heading: string;
|
|
37
|
-
labels: readonly [string, string];
|
|
38
|
-
}>;
|
|
39
|
-
|
|
40
|
-
const ARTIFACT_FAMILY_CATALOG = [
|
|
41
|
-
{ family: 'analysis', sectionAliases: ['分析', 'Analysis'], heading: '分析', labels: ['需求分析报告', 'Requirements Analysis'] },
|
|
42
|
-
{ family: 'review-analysis', sectionAliases: ['审查反馈', 'Review Feedback'], heading: '审查反馈', labels: ['需求分析审查', 'Analysis Review'] },
|
|
43
|
-
{ family: 'plan', sectionAliases: ['设计', 'Design'], heading: '设计', labels: ['技术方案', 'Technical Plan'] },
|
|
44
|
-
{ family: 'review-plan', sectionAliases: ['审查反馈', 'Review Feedback'], heading: '审查反馈', labels: ['技术方案审查', 'Plan Review'] },
|
|
45
|
-
{ family: 'code', sectionAliases: ['实现备注', 'Implementation Notes'], heading: '实现备注', labels: ['实现报告', 'Implementation Report'] },
|
|
46
|
-
{ family: 'review-code', sectionAliases: ['审查反馈', 'Review Feedback'], heading: '审查反馈', labels: ['代码审查', 'Code Review'] },
|
|
47
|
-
{ family: 'manual-validation', sectionAliases: ['实现备注', 'Implementation Notes'], heading: '实现备注', labels: ['人工验证', 'Manual Validation'] },
|
|
48
|
-
{ family: 'validation-run', sectionAliases: ['实现备注', 'Implementation Notes'], heading: '实现备注', labels: ['验证运行证据', 'Validation Run Evidence'] },
|
|
49
|
-
{ family: 'pr-review', sectionAliases: ['审查反馈', 'Review Feedback'], heading: '审查反馈', labels: ['PR 审查报告', 'PR Review Report'] }
|
|
50
|
-
] as const satisfies readonly ArtifactFamilySpec[];
|
|
51
|
-
|
|
52
25
|
const LOCAL_SECTIONS = {
|
|
53
26
|
analysis: [
|
|
54
27
|
['source', '需求来源', 'Requirements Source'],
|
|
@@ -157,8 +130,9 @@ function renderArtifactSkeleton(input: ArtifactSkeletonInput): string {
|
|
|
157
130
|
const schema = getArtifactSchema(input.family);
|
|
158
131
|
if (!schema) throw new Error(`unknown artifact schema family '${input.family}'`);
|
|
159
132
|
const locale = input.locale ?? 'zh-CN';
|
|
160
|
-
const
|
|
161
|
-
|
|
133
|
+
const identity = parseArtifactName(input.artifact);
|
|
134
|
+
if (identity?.family !== input.family) throw new Error(`invalid artifact identity '${input.artifact}' for '${input.family}'`);
|
|
135
|
+
const round = identity.round;
|
|
162
136
|
const title = schema.title[locale === 'en' ? 'en' : 'zh'];
|
|
163
137
|
const headings = locale === 'en' ? 'en' : 'zh';
|
|
164
138
|
const lines = [
|
|
@@ -176,14 +150,11 @@ function renderArtifactSkeleton(input: ArtifactSkeletonInput): string {
|
|
|
176
150
|
}
|
|
177
151
|
|
|
178
152
|
export {
|
|
179
|
-
ARTIFACT_FAMILY_CATALOG,
|
|
180
153
|
ARTIFACT_SCHEMAS,
|
|
181
154
|
getArtifactSchema,
|
|
182
155
|
renderArtifactSkeleton
|
|
183
156
|
};
|
|
184
157
|
export type {
|
|
185
|
-
ArtifactFamily,
|
|
186
|
-
ArtifactFamilySpec,
|
|
187
158
|
ArtifactLocale,
|
|
188
159
|
ArtifactSchema,
|
|
189
160
|
ArtifactSchemaFamily,
|
package/lib/task/capabilities.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
|
|
4
|
+
import { parseArtifactName } from './artifact-name.ts';
|
|
4
5
|
import type { InvalidationDocument } from './invalidation.ts';
|
|
5
6
|
import { invalidationBlocks, isArtifactInvalidated, parseInvalidationDocument } from './invalidation.ts';
|
|
6
7
|
import { receiptForOutput } from './artifact-receipts.ts';
|
|
@@ -141,13 +142,9 @@ function canStart(action: LifecycleAction, facts: LifecycleFacts, trigger: Expli
|
|
|
141
142
|
return deny('CAPABILITY_ACTION_UNKNOWN');
|
|
142
143
|
}
|
|
143
144
|
|
|
144
|
-
function artifactRound(name: string): number {
|
|
145
|
-
const match = /-r(\d+)\.md$/.exec(name);
|
|
146
|
-
return match ? Number(match[1]) : 1;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
145
|
function latestArtifact(names: readonly string[]): string | null {
|
|
150
|
-
return
|
|
146
|
+
return names.map(parseArtifactName).filter((identity) => identity !== null)
|
|
147
|
+
.sort((left, right) => right.round - left.round || left.name.localeCompare(right.name))[0]?.name ?? null;
|
|
151
148
|
}
|
|
152
149
|
|
|
153
150
|
function reviewedInputName(content: string, expectedFamily: 'analysis' | 'plan' | 'code'): string | null {
|
|
@@ -242,17 +239,11 @@ function buildLifecycleFacts(taskDir: string, content: string, taskState = 'acti
|
|
|
242
239
|
const stat = fs.lstatSync(path.join(taskDir, name));
|
|
243
240
|
return stat.isFile() && !stat.isSymbolicLink();
|
|
244
241
|
});
|
|
245
|
-
const artifactFamilies =
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
code: /^code(?:-r\d+)?\.md$/,
|
|
251
|
-
'review-code': /^review-code(?:-r\d+)?\.md$/
|
|
252
|
-
} as const;
|
|
253
|
-
const familyFor = (name: string): keyof typeof artifactFamilies | null =>
|
|
254
|
-
(Object.keys(artifactFamilies) as Array<keyof typeof artifactFamilies>)
|
|
255
|
-
.find((family) => artifactFamilies[family].test(name)) ?? null;
|
|
242
|
+
const artifactFamilies = ['analysis', 'review-analysis', 'plan', 'review-plan', 'code', 'review-code'] as const;
|
|
243
|
+
const familyFor = (name: string) => {
|
|
244
|
+
const family = parseArtifactName(name)?.family;
|
|
245
|
+
return artifactFamilies.find((candidate) => candidate === family) ?? null;
|
|
246
|
+
};
|
|
256
247
|
const activeFiles = files.filter((name) => {
|
|
257
248
|
const family = familyFor(name);
|
|
258
249
|
return !family || !isArtifactInvalidated(invalidation.document, family, name);
|
|
@@ -260,13 +251,13 @@ function buildLifecycleFacts(taskDir: string, content: string, taskState = 'acti
|
|
|
260
251
|
const artifactHashes: Record<string, string> = {};
|
|
261
252
|
for (const name of activeFiles) artifactHashes[name] = sha256File(path.join(taskDir, name));
|
|
262
253
|
const artifacts: Partial<Record<LifecycleAction, readonly string[]>> = Object.fromEntries(
|
|
263
|
-
|
|
264
|
-
family, activeFiles.filter((name) =>
|
|
254
|
+
artifactFamilies.map((family) => [
|
|
255
|
+
family, activeFiles.filter((name) => familyFor(name) === family)
|
|
265
256
|
])
|
|
266
257
|
) as Partial<Record<LifecycleAction, readonly string[]>>;
|
|
267
258
|
const staleArtifacts: Partial<Record<LifecycleAction, readonly string[]>> = Object.fromEntries(
|
|
268
|
-
|
|
269
|
-
family, files.filter((name) =>
|
|
259
|
+
artifactFamilies.map((family) => [
|
|
260
|
+
family, files.filter((name) => familyFor(name) === family && isArtifactInvalidated(invalidation.document, family, name))
|
|
270
261
|
])
|
|
271
262
|
) as Partial<Record<LifecycleAction, readonly string[]>>;
|
|
272
263
|
const qualification = parseTaskQualification(content);
|
|
@@ -274,7 +265,7 @@ function buildLifecycleFacts(taskDir: string, content: string, taskState = 'acti
|
|
|
274
265
|
const qualificationStaleArtifacts: string[] = [];
|
|
275
266
|
if (qualification.qualification.present) {
|
|
276
267
|
const constraints = new Map(qualification.qualification.constraints.map((row) => [row.constraintId, row.digest]));
|
|
277
|
-
for (const family of
|
|
268
|
+
for (const family of artifactFamilies) {
|
|
278
269
|
if (!ARTIFACT_AUDIT_FAMILIES.has(family)) continue;
|
|
279
270
|
const name = latestArtifact(artifacts[family] ?? []);
|
|
280
271
|
if (!name) continue;
|
|
@@ -29,10 +29,18 @@ import {
|
|
|
29
29
|
OrchestrationStateError,
|
|
30
30
|
pauseMatchingOrchestrationDelegation,
|
|
31
31
|
prepareOrchestrationDelegation,
|
|
32
|
+
routeOrchestration,
|
|
32
33
|
reconcileMatchingOrchestrationDelegation,
|
|
33
34
|
sealMatchingOrchestrationDelegationWithHostEvidence
|
|
34
35
|
} from './orchestration.ts';
|
|
36
|
+
import { randomUUID, createHash } from 'node:crypto';
|
|
35
37
|
import type { OrchestrationOptions, OrchestrationResult } from './orchestration.ts';
|
|
38
|
+
import {
|
|
39
|
+
consumeLifecycleRecoveryAttestation,
|
|
40
|
+
issueLifecycleRecoveryAttestation,
|
|
41
|
+
validateLifecycleRecoveryAttestation,
|
|
42
|
+
type LifecycleRecoveryAttestationV1
|
|
43
|
+
} from './control-authority.ts';
|
|
36
44
|
|
|
37
45
|
type LifecycleStore = ReturnType<typeof createCodexLifecycleStore>;
|
|
38
46
|
type CapabilityStore = ReturnType<typeof createCodexCapabilityStore>;
|
|
@@ -46,6 +54,7 @@ type CodexBridgeOptions = Readonly<{
|
|
|
46
54
|
buildIdentity?: LifecycleBuildIdentity;
|
|
47
55
|
orchestrationOptions?: OrchestrationOptions;
|
|
48
56
|
controllerBinding?: Readonly<{ instanceDigest: string; controlGeneration: string }>;
|
|
57
|
+
deferLifecycleRecoveryConsumption?: boolean;
|
|
49
58
|
}>;
|
|
50
59
|
|
|
51
60
|
type CodexSpawnIdentity = Readonly<{
|
|
@@ -59,6 +68,10 @@ type CodexSpawnIdentity = Readonly<{
|
|
|
59
68
|
requestedReasoningEffort?: string;
|
|
60
69
|
}>;
|
|
61
70
|
|
|
71
|
+
function buildIdentityDigest(value: LifecycleBuildIdentity): string {
|
|
72
|
+
return createHash('sha256').update(JSON.stringify(value), 'utf8').digest('hex');
|
|
73
|
+
}
|
|
74
|
+
|
|
62
75
|
function coreOptions(options: CodexBridgeOptions): OrchestrationOptions {
|
|
63
76
|
return { ...options.orchestrationOptions, repoRoot: options.repoRoot ?? options.orchestrationOptions?.repoRoot };
|
|
64
77
|
}
|
|
@@ -126,7 +139,8 @@ async function prepareCodexOrchestrationDelegation(
|
|
|
126
139
|
client: AgentClientId;
|
|
127
140
|
requestedModel?: string;
|
|
128
141
|
requestedReasoningEffort?: string;
|
|
129
|
-
|
|
142
|
+
capabilityRef?: string;
|
|
143
|
+
lifecycleRecoveryAttestation?: LifecycleRecoveryAttestationV1 | null;
|
|
130
144
|
}>,
|
|
131
145
|
options: CodexBridgeOptions = {}
|
|
132
146
|
): Promise<OrchestrationResult> {
|
|
@@ -136,10 +150,11 @@ async function prepareCodexOrchestrationDelegation(
|
|
|
136
150
|
const resolved = resolveTaskRef(taskRef, { repoRoot });
|
|
137
151
|
if (!resolved.ok) return bridgeFailure(resolved.code, resolved.message);
|
|
138
152
|
const preflight = await (options.preflight ?? preflightCodexLifecycleEvidence)(repoRoot);
|
|
139
|
-
|
|
153
|
+
const capabilityRef = input.capabilityRef;
|
|
154
|
+
if (!capabilityRef) {
|
|
140
155
|
return bridgeFailure(
|
|
141
156
|
'ORCHESTRATION_CODEX_CAPABILITY_REQUIRED',
|
|
142
|
-
'Codex prepare requires a current-session capability
|
|
157
|
+
'Codex prepare requires a current-session capability reference'
|
|
143
158
|
);
|
|
144
159
|
}
|
|
145
160
|
const buildIdentity = options.buildIdentity ?? computeLifecycleBuildIdentity(repoRoot);
|
|
@@ -168,7 +183,7 @@ async function prepareCodexOrchestrationDelegation(
|
|
|
168
183
|
controlGeneration: localController.controlGeneration
|
|
169
184
|
} : null);
|
|
170
185
|
const capabilityStore = options.capabilityStore ?? createCodexCapabilityStore();
|
|
171
|
-
const attested = capabilityStore.
|
|
186
|
+
const attested = capabilityStore.inspectReference(capabilityRef);
|
|
172
187
|
const capabilityIdentity = verifyLifecycleBuildIdentity(attested.buildIdentity, buildIdentity);
|
|
173
188
|
if (!capabilityIdentity.ok) {
|
|
174
189
|
return bridgeFailure(capabilityIdentity.code!, capabilityIdentity.message!);
|
|
@@ -193,14 +208,60 @@ async function prepareCodexOrchestrationDelegation(
|
|
|
193
208
|
controlGeneration: controller.controlGeneration
|
|
194
209
|
} } : {})
|
|
195
210
|
};
|
|
211
|
+
if (input.lifecycleRecoveryAttestation) {
|
|
212
|
+
try { validateLifecycleRecoveryAttestation(input.lifecycleRecoveryAttestation); }
|
|
213
|
+
catch (error) {
|
|
214
|
+
return bridgeFailure(
|
|
215
|
+
'LIFECYCLE_AUTHORITY_ATTESTATION_INVALID',
|
|
216
|
+
error instanceof Error ? error.message : String(error)
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
let lifecycleRecoveryAttestation: LifecycleRecoveryAttestationV1 | null = input.lifecycleRecoveryAttestation ?? null;
|
|
221
|
+
const routed = input.capabilityRef && !lifecycleRecoveryAttestation
|
|
222
|
+
? routeOrchestration(taskRef, coreOptions(options))
|
|
223
|
+
: null;
|
|
224
|
+
if (routed && !routed.next) return routed;
|
|
225
|
+
if (routed?.next && ['analysis', 'plan', 'code'].includes(routed.next.stage)) {
|
|
226
|
+
const recoveryRequest = {
|
|
227
|
+
version: 1 as const,
|
|
228
|
+
requestId: randomUUID(),
|
|
229
|
+
operationId: randomUUID(),
|
|
230
|
+
phase: 'orchestration.prepare' as const,
|
|
231
|
+
taskId: resolved.taskId,
|
|
232
|
+
family: routed.next.stage === 'analysis' ? 'analysis' as const
|
|
233
|
+
: routed.next.stage === 'plan' ? 'plan' as const : 'code' as const,
|
|
234
|
+
artifact: routed.next.artifact,
|
|
235
|
+
round: routed.next.round,
|
|
236
|
+
lifecycleRequestId: randomUUID(),
|
|
237
|
+
authorityRef: capabilityRef,
|
|
238
|
+
expectedControlGeneration: controller?.controlGeneration ?? '',
|
|
239
|
+
expectedControllerInstanceDigest: controller?.instanceDigest ?? '0'.repeat(64),
|
|
240
|
+
expectedBuildIdentityDigest: buildIdentityDigest(buildIdentity),
|
|
241
|
+
expectedHookDefinitionHash: preflight.hookDefinitionHash
|
|
242
|
+
};
|
|
243
|
+
const authority = issueLifecycleRecoveryAttestation(recoveryRequest, {
|
|
244
|
+
capabilityStore,
|
|
245
|
+
...(controller ? { controllerBinding: controller } : {}),
|
|
246
|
+
buildIdentity
|
|
247
|
+
});
|
|
248
|
+
if (authority.status === 'rejected' || !authority.attestation) {
|
|
249
|
+
return bridgeFailure(
|
|
250
|
+
authority.error?.code ?? 'LIFECYCLE_AUTHORITY_REJECTED',
|
|
251
|
+
authority.error?.message ?? 'lifecycle authority was rejected'
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
lifecycleRecoveryAttestation = authority.attestation;
|
|
255
|
+
}
|
|
196
256
|
const prepared = prepareOrchestrationDelegation(taskRef, {
|
|
197
257
|
...input,
|
|
198
|
-
lifecycleProvenance
|
|
258
|
+
lifecycleProvenance,
|
|
259
|
+
lifecycleRecoveryAttestation
|
|
199
260
|
}, {
|
|
200
261
|
...coreOptions(options),
|
|
201
262
|
validateLifecycleCapability: () => {
|
|
202
263
|
try {
|
|
203
|
-
const validated = capabilityStore.
|
|
264
|
+
const validated = capabilityStore.validateReference(capabilityRef, capabilityExpected);
|
|
204
265
|
if (validated.sessionId !== attested.sessionId
|
|
205
266
|
|| validated.turnId !== attested.turnId
|
|
206
267
|
|| validated.toolUseId !== attested.toolUseId) {
|
|
@@ -214,23 +275,18 @@ async function prepareCodexOrchestrationDelegation(
|
|
|
214
275
|
return capabilityFailure(error);
|
|
215
276
|
}
|
|
216
277
|
},
|
|
217
|
-
consumeLifecycleCapability: () =>
|
|
218
|
-
try {
|
|
219
|
-
const consumed = capabilityStore.consume(input.capabilityToken!, capabilityExpected);
|
|
220
|
-
if (consumed.sessionId !== attested.sessionId
|
|
221
|
-
|| consumed.turnId !== attested.turnId
|
|
222
|
-
|| consumed.toolUseId !== attested.toolUseId) {
|
|
223
|
-
return {
|
|
224
|
-
code: 'CODEX_CAPABILITY_IDENTITY_CHANGED',
|
|
225
|
-
message: 'capability identity changed during consumption'
|
|
226
|
-
};
|
|
227
|
-
}
|
|
228
|
-
return null;
|
|
229
|
-
} catch (error) {
|
|
230
|
-
return capabilityFailure(error);
|
|
231
|
-
}
|
|
232
|
-
}
|
|
278
|
+
consumeLifecycleCapability: () => null
|
|
233
279
|
});
|
|
280
|
+
if (lifecycleRecoveryAttestation && !options.deferLifecycleRecoveryConsumption && prepared.status !== 'failed') {
|
|
281
|
+
try {
|
|
282
|
+
consumeLifecycleRecoveryAttestation(lifecycleRecoveryAttestation);
|
|
283
|
+
} catch (error) {
|
|
284
|
+
return bridgeFailure(
|
|
285
|
+
error instanceof Error ? error.name : 'LIFECYCLE_AUTHORITY_CONSUME_FAILED',
|
|
286
|
+
error instanceof Error ? error.message : String(error)
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
234
290
|
return identityWarnings.length > 0 && prepared.status !== 'failed'
|
|
235
291
|
? { ...prepared, warnings: Object.freeze(identityWarnings) }
|
|
236
292
|
: prepared;
|
|
@@ -283,7 +339,12 @@ async function activateCodexOrchestrationDelegation(
|
|
|
283
339
|
const contextVerification = contextPath
|
|
284
340
|
? verifySandboxControllerWithWarnings(contextPath, { repoRoot })
|
|
285
341
|
: null;
|
|
286
|
-
const controller =
|
|
342
|
+
const controller = options.controllerBinding
|
|
343
|
+
?? brokerControllerBinding()
|
|
344
|
+
?? (contextVerification?.context ? {
|
|
345
|
+
instanceDigest: contextVerification.context.controllerInstanceDigest,
|
|
346
|
+
controlGeneration: contextVerification.context.controlGeneration
|
|
347
|
+
} : null);
|
|
287
348
|
const activated = activateMatchingOrchestrationDelegation('codex', {
|
|
288
349
|
nativeAgent: evidence.nativeAgent,
|
|
289
350
|
childId: evidence.childThreadId,
|
|
@@ -305,7 +366,7 @@ async function activateCodexOrchestrationDelegation(
|
|
|
305
366
|
capabilityTurnId: evidence.parentTurnId,
|
|
306
367
|
spawnToolUseId: evidence.spawnToolUseId,
|
|
307
368
|
spawnObservedAt: record.spawnObservedAt ?? undefined,
|
|
308
|
-
controllerInstanceDigest: controller?.
|
|
369
|
+
controllerInstanceDigest: controller?.instanceDigest ?? null,
|
|
309
370
|
controlGeneration: controller?.controlGeneration ?? null
|
|
310
371
|
}
|
|
311
372
|
}, coreOptions(options));
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process';
|
|
2
|
+
|
|
3
|
+
import type { CheckpointIntent } from './commit-intent.ts';
|
|
4
|
+
|
|
5
|
+
function gitText(repoRoot: string, args: readonly string[]): string {
|
|
6
|
+
return execFileSync('git', [...args], {
|
|
7
|
+
cwd: repoRoot,
|
|
8
|
+
encoding: 'utf8',
|
|
9
|
+
stdio: ['ignore', 'pipe', 'pipe']
|
|
10
|
+
}).trim();
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function checkpointCommitMatches(repoRoot: string, intent: CheckpointIntent, head: string): boolean {
|
|
14
|
+
try {
|
|
15
|
+
const parent = gitText(repoRoot, ['rev-parse', `${head}^`]);
|
|
16
|
+
const tree = gitText(repoRoot, ['rev-parse', `${head}^{tree}`]);
|
|
17
|
+
const message = gitText(repoRoot, ['show', '-s', '--format=%s', head]);
|
|
18
|
+
const changed = gitText(repoRoot, ['diff-tree', '--no-commit-id', '--name-only', '-r', head])
|
|
19
|
+
.split('\n')
|
|
20
|
+
.filter(Boolean)
|
|
21
|
+
.sort();
|
|
22
|
+
return parent === intent.expectedHead
|
|
23
|
+
&& tree === intent.expectedTree
|
|
24
|
+
&& message === intent.message
|
|
25
|
+
&& JSON.stringify(changed) === JSON.stringify([...intent.paths].sort());
|
|
26
|
+
} catch {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export { checkpointCommitMatches };
|