@fitlab-ai/agent-infra 0.9.15 → 0.11.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 +2 -6
- package/README.zh-CN.md +2 -6
- package/bin/internal-cli.sh +0 -13
- package/dist/bin/internal-cli.js +11 -49
- package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +6 -2
- package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +43 -3
- package/dist/lib/fs/durable-write.js +2 -0
- package/dist/lib/internal/cli-route-inventory.js +3 -4
- package/dist/lib/internal/manual-validation.js +17 -8
- package/dist/lib/internal/platform-comment.js +60 -2
- package/dist/lib/internal/task-artifact.js +1 -1
- package/dist/lib/internal/task-finalization.js +2 -2
- package/dist/lib/internal/task-lifecycle.js +4 -4
- package/dist/lib/internal/task-operation-registry.js +22 -13
- package/dist/lib/internal/task-orchestration.js +2 -2
- package/dist/lib/internal/task-review.js +12 -7
- package/dist/lib/platform/activity-recovery.js +159 -0
- package/dist/lib/platform/issue-comments.js +89 -31
- package/dist/lib/platform/issues.js +1 -1
- package/dist/lib/platform/pr-change-report.js +5 -4
- package/dist/lib/platform/pr-summary.js +64 -32
- package/dist/lib/platform/pull-requests.js +3 -3
- package/dist/lib/platform/task-comment-projection.js +70 -0
- package/dist/lib/platform/task-identities.js +2 -9
- package/dist/lib/platform/task-recovery.js +55 -0
- package/dist/lib/platform/verification-sync.js +45 -7
- package/dist/lib/pr-review/evidence-grading.js +4 -8
- package/dist/lib/sandbox/commands/rm.js +21 -2
- package/dist/lib/sandbox/control/executor.js +4 -2
- package/dist/lib/sandbox/control/server.js +29 -9
- package/dist/lib/sandbox/control/state.js +7 -1
- package/dist/lib/sandbox/control/task-workflow.js +1 -1
- package/dist/lib/sandbox/control/workflow-executor.js +87 -35
- package/dist/lib/sandbox/index.js +1 -2
- package/dist/lib/sandbox/removal.js +116 -1011
- package/dist/lib/sandbox/workspace-identity.js +17 -1
- package/dist/lib/server/process-state.js +17 -5
- package/dist/lib/task/artifact-command.js +11 -30
- package/dist/lib/task/artifact-operations.js +17 -231
- package/dist/lib/task/artifact-recovery.js +476 -0
- package/dist/lib/task/artifact-repair-intent.js +90 -39
- package/dist/lib/task/commit-operation.js +31 -4
- package/dist/lib/task/control-authority.js +39 -8
- package/dist/lib/task/control-recovery.js +14 -2
- package/dist/lib/task/create-service.js +1 -8
- package/dist/lib/task/delegation-receipts.js +64 -3
- package/dist/lib/task/events.js +165 -23
- package/dist/lib/task/human-override.js +5 -8
- package/dist/lib/task/intermediate-cleanup.js +91 -8
- package/dist/lib/task/lifecycle-recovery.js +549 -0
- package/dist/lib/task/lifecycle.js +12 -3
- package/dist/lib/task/local-artifact-finalization.js +212 -123
- package/dist/lib/task/manual-validation-receipt.js +5 -1
- package/dist/lib/task/orchestration.js +52 -2
- package/dist/lib/task/pr-delivery-fact.js +12 -56
- package/dist/lib/task/recovery-warning.js +19 -0
- package/dist/lib/task/review-command.js +3 -2
- package/dist/lib/task/review-finalization.js +294 -74
- package/dist/lib/{host-control → task}/secure-fs.js +8 -19
- 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 +3 -14
- package/dist/lib/task/workflow-command.js +2 -1
- package/dist/lib/task/workflow-dispatch.js +161 -0
- package/dist/lib/version.js +1 -11
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +7 -2
- package/lib/agent-clients/adapters/codex-lifecycle/store.ts +55 -3
- package/lib/fs/durable-write.ts +1 -0
- package/lib/internal/cli-route-inventory.ts +3 -4
- package/lib/internal/manual-validation.ts +17 -8
- package/lib/internal/platform-comment.ts +53 -2
- package/lib/internal/task-artifact.ts +1 -1
- package/lib/internal/task-finalization.ts +1 -1
- package/lib/internal/task-lifecycle.ts +5 -4
- package/lib/internal/task-operation-registry.ts +22 -13
- package/lib/internal/task-orchestration.ts +1 -1
- package/lib/internal/task-review.ts +11 -7
- package/lib/platform/activity-recovery.ts +179 -0
- package/lib/platform/issue-comments.ts +105 -30
- package/lib/platform/issues.ts +1 -1
- package/lib/platform/pr-change-report.ts +7 -5
- package/lib/platform/pr-summary.ts +64 -32
- package/lib/platform/pull-requests.ts +3 -3
- package/lib/platform/task-comment-projection.ts +79 -0
- package/lib/platform/task-identities.ts +3 -14
- package/lib/platform/task-recovery.ts +54 -0
- package/lib/platform/verification-sync.ts +48 -11
- package/lib/pr-review/evidence-grading.ts +4 -9
- package/lib/sandbox/commands/rm.ts +34 -2
- package/lib/sandbox/control/executor.ts +4 -2
- package/lib/sandbox/control/protocol.ts +6 -1
- package/lib/sandbox/control/server.ts +30 -10
- package/lib/sandbox/control/state.ts +9 -1
- package/lib/sandbox/control/task-workflow.ts +1 -1
- package/lib/sandbox/control/workflow-executor.ts +106 -35
- package/lib/sandbox/index.ts +1 -2
- package/lib/sandbox/removal.ts +126 -1128
- package/lib/sandbox/workspace-identity.ts +19 -3
- package/lib/server/process-state.ts +26 -5
- package/lib/task/artifact-command.ts +12 -29
- package/lib/task/artifact-operations.ts +22 -252
- package/lib/task/artifact-recovery.ts +572 -0
- package/lib/task/artifact-repair-intent.ts +126 -51
- package/lib/task/commit-operation.ts +35 -4
- package/lib/task/control-authority.ts +48 -15
- package/lib/task/control-recovery.ts +13 -1
- package/lib/task/create-service.ts +1 -10
- package/lib/task/delegation-receipts.ts +73 -2
- package/lib/task/events.ts +188 -24
- package/lib/task/human-override.ts +5 -8
- package/lib/task/intermediate-cleanup.ts +66 -12
- package/lib/task/lifecycle-recovery.ts +691 -0
- package/lib/task/lifecycle.ts +13 -4
- package/lib/task/local-artifact-finalization.ts +249 -131
- package/lib/task/manual-validation-receipt.ts +5 -1
- package/lib/task/orchestration.ts +65 -0
- package/lib/task/pr-delivery-fact.ts +15 -62
- package/lib/task/recovery-warning.ts +21 -0
- package/lib/task/review-command.ts +2 -1
- package/lib/task/review-finalization.ts +298 -84
- package/lib/{host-control → task}/secure-fs.ts +10 -32
- package/lib/task/short-id.ts +27 -5
- package/lib/task/task-execution-lock.ts +1 -1
- package/lib/task/verification-engine.ts +3 -17
- package/lib/task/verification-types.ts +0 -2
- package/lib/task/workflow-command.ts +2 -1
- package/lib/task/workflow-dispatch.ts +171 -0
- package/lib/version.ts +1 -15
- package/package.json +1 -1
- 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 +12 -2
- package/templates/.agents/rules/issue-sync.zh-CN.md +13 -5
- package/templates/.agents/rules/local-artifact-repair.en.md +47 -39
- package/templates/.agents/rules/local-artifact-repair.zh-CN.md +45 -36
- 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 +8 -8
- 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 +1 -1
- package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/complete-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +2 -2
- 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/restore-task/SKILL.en.md +11 -6
- package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +11 -6
- package/templates/.agents/skills/restore-task/config/verify.json +1 -1
- package/templates/.agents/skills/review-analysis/SKILL.en.md +3 -3
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +3 -3
- 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 +5 -4
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +5 -4
- 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 +3 -3
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +3 -3
- 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/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-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +1 -1
- 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/dist/lib/host-control/audit.js +0 -19
- package/dist/lib/host-control/client.js +0 -142
- package/dist/lib/host-control/command.js +0 -49
- package/dist/lib/host-control/path.js +0 -160
- package/dist/lib/host-control/server.js +0 -159
- package/dist/lib/host-control/service.js +0 -112
- package/dist/lib/internal/host-control.js +0 -28
- package/lib/host-control/audit.ts +0 -22
- package/lib/host-control/client.ts +0 -172
- package/lib/host-control/command.ts +0 -60
- package/lib/host-control/path.ts +0 -167
- package/lib/host-control/server.ts +0 -180
- package/lib/host-control/service.ts +0 -133
- package/lib/internal/host-control.ts +0 -28
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { isDeepStrictEqual } from 'node:util';
|
|
4
|
-
|
|
4
|
+
import { parseArtifactName } from './artifact-name.js';
|
|
5
|
+
export function artifactRecoveryIntentRoot(repoRoot) {
|
|
5
6
|
return path.join(repoRoot, '.agents', 'workspace', '.local-artifact-finalization-intents');
|
|
6
7
|
}
|
|
8
|
+
export function artifactRecoveryRoot(taskDir, stagingId) {
|
|
9
|
+
return path.join(taskDir, '.local-artifact-recovery', stagingId);
|
|
10
|
+
}
|
|
7
11
|
function intentPath(repoRoot, taskId, family, artifact) {
|
|
8
|
-
return path.join(
|
|
12
|
+
return path.join(artifactRecoveryIntentRoot(repoRoot), `${taskId}-${family}-${artifact}.json`);
|
|
9
13
|
}
|
|
10
14
|
function validDigest(value) {
|
|
11
15
|
return typeof value === 'string' && /^[a-f0-9]{64}$/u.test(value);
|
|
@@ -13,35 +17,82 @@ function validDigest(value) {
|
|
|
13
17
|
function validTaskId(value) {
|
|
14
18
|
return typeof value === 'string' && /^TASK-\d{8}-\d{6}$/u.test(value);
|
|
15
19
|
}
|
|
20
|
+
function validToken(value) {
|
|
21
|
+
return typeof value === 'string' && value.length > 0 && !/[\r\n]/u.test(value);
|
|
22
|
+
}
|
|
23
|
+
function validRecoveryId(value) {
|
|
24
|
+
return typeof value === 'string' && /^[a-f0-9-]{16,64}$/u.test(value);
|
|
25
|
+
}
|
|
26
|
+
function validArtifact(value, family, round) {
|
|
27
|
+
if (typeof value !== 'string' || path.basename(value) !== value || !value.endsWith('.md'))
|
|
28
|
+
return false;
|
|
29
|
+
const parsed = parseArtifactName(value);
|
|
30
|
+
return Boolean(parsed && parsed.family === family && parsed.round === round);
|
|
31
|
+
}
|
|
16
32
|
function exactKeys(value, keys) {
|
|
17
33
|
return Object.keys(value).sort().join(',') === [...keys].sort().join(',');
|
|
18
34
|
}
|
|
19
|
-
export function
|
|
35
|
+
export function isArtifactRecoveryIntent(value) {
|
|
20
36
|
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
21
37
|
return false;
|
|
22
38
|
const intent = value;
|
|
23
39
|
return exactKeys(intent, [
|
|
24
|
-
'
|
|
25
|
-
'
|
|
26
|
-
'
|
|
40
|
+
'activeGenerationSemanticDigest', 'activeGenerationSha256', 'artifact', 'authorityDigest', 'baselineSemanticDigest', 'baselineSha256', 'candidateSha256',
|
|
41
|
+
'createdAt', 'errorCode', 'errorMessage', 'family', 'finalArtifactSha256', 'finalSemanticDigest',
|
|
42
|
+
'pendingGenerationSemanticDigest', 'pendingGenerationSha256', 'phase', 'preflightArtifactSha256', 'preflightSemanticDigest', 'recoveryOperationId', 'requestId', 'round', 'stagingId', 'state', 'taskId', 'updatedAt',
|
|
43
|
+
'version'
|
|
27
44
|
])
|
|
28
|
-
&& intent.version ===
|
|
45
|
+
&& intent.version === 4
|
|
29
46
|
&& validTaskId(intent.taskId)
|
|
30
47
|
&& typeof intent.family === 'string'
|
|
31
48
|
&& ['analysis', 'review-analysis', 'plan', 'review-plan', 'code', 'review-code'].includes(intent.family)
|
|
32
|
-
&&
|
|
33
|
-
&&
|
|
34
|
-
&&
|
|
35
|
-
&& validDigest(intent.
|
|
36
|
-
&& validDigest(intent.
|
|
37
|
-
&& (intent.
|
|
49
|
+
&& Number.isSafeInteger(intent.round) && intent.round > 0
|
|
50
|
+
&& validArtifact(intent.artifact, intent.family, intent.round)
|
|
51
|
+
&& ['awaiting-preflight-recovery', 'preflight-ready', 'preflight-commit-started', 'preflight-passed', 'full-finalizer-ready', 'commit-started', 'passed', 'consumption-started', 'consumed', 'aborted'].includes(String(intent.state))
|
|
52
|
+
&& validDigest(intent.baselineSha256)
|
|
53
|
+
&& validDigest(intent.baselineSemanticDigest)
|
|
54
|
+
&& validRecoveryId(intent.stagingId)
|
|
55
|
+
&& (intent.candidateSha256 === null || validDigest(intent.candidateSha256))
|
|
56
|
+
&& (intent.preflightArtifactSha256 === null || validDigest(intent.preflightArtifactSha256))
|
|
57
|
+
&& (intent.preflightSemanticDigest === null || validDigest(intent.preflightSemanticDigest))
|
|
58
|
+
&& (intent.activeGenerationSha256 === null || validDigest(intent.activeGenerationSha256))
|
|
59
|
+
&& (intent.activeGenerationSemanticDigest === null || validDigest(intent.activeGenerationSemanticDigest))
|
|
60
|
+
&& (intent.pendingGenerationSha256 === null || validDigest(intent.pendingGenerationSha256))
|
|
61
|
+
&& (intent.pendingGenerationSemanticDigest === null || validDigest(intent.pendingGenerationSemanticDigest))
|
|
62
|
+
&& (intent.finalArtifactSha256 === null || validDigest(intent.finalArtifactSha256))
|
|
63
|
+
&& (intent.finalSemanticDigest === null || validDigest(intent.finalSemanticDigest))
|
|
64
|
+
&& validRecoveryId(intent.recoveryOperationId)
|
|
65
|
+
&& intent.stagingId === intent.recoveryOperationId
|
|
38
66
|
&& (intent.phase === null || ['orchestration.prepare', 'artifact.finalize-local', 'task-event.completed'].includes(String(intent.phase)))
|
|
39
67
|
&& (intent.authorityDigest === null || validDigest(intent.authorityDigest))
|
|
40
|
-
&&
|
|
41
|
-
&&
|
|
42
|
-
&& (intent.
|
|
68
|
+
&& validToken(intent.requestId)
|
|
69
|
+
&& (intent.errorCode === null || validToken(intent.errorCode))
|
|
70
|
+
&& (intent.errorMessage === null || validToken(intent.errorMessage))
|
|
71
|
+
&& Number.isSafeInteger(intent.createdAt)
|
|
72
|
+
&& Number.isSafeInteger(intent.updatedAt)
|
|
73
|
+
&& (intent.state === 'awaiting-preflight-recovery'
|
|
74
|
+
? intent.preflightArtifactSha256 === null && intent.preflightSemanticDigest === null
|
|
75
|
+
&& intent.activeGenerationSha256 === null && intent.activeGenerationSemanticDigest === null
|
|
76
|
+
&& intent.pendingGenerationSha256 === null && intent.pendingGenerationSemanticDigest === null
|
|
77
|
+
&& intent.finalArtifactSha256 === null && intent.finalSemanticDigest === null
|
|
78
|
+
: true)
|
|
79
|
+
&& (intent.state === 'preflight-ready' || intent.state === 'preflight-commit-started' || intent.state === 'preflight-passed' || intent.state === 'full-finalizer-ready' || intent.state === 'commit-started' || intent.state === 'passed' || intent.state === 'consumed'
|
|
80
|
+
? validDigest(intent.candidateSha256) && validDigest(intent.preflightArtifactSha256) && validDigest(intent.preflightSemanticDigest)
|
|
81
|
+
&& validDigest(intent.activeGenerationSha256) && validDigest(intent.activeGenerationSemanticDigest)
|
|
82
|
+
: true)
|
|
83
|
+
&& (intent.state === 'full-finalizer-ready' || intent.state === 'commit-started'
|
|
84
|
+
? validDigest(intent.pendingGenerationSha256) && validDigest(intent.pendingGenerationSemanticDigest)
|
|
85
|
+
&& validDigest(intent.finalArtifactSha256) && validDigest(intent.finalSemanticDigest)
|
|
86
|
+
: true)
|
|
87
|
+
&& (intent.state === 'passed' || intent.state === 'consumption-started' || intent.state === 'consumed'
|
|
88
|
+
? validDigest(intent.finalArtifactSha256) && validDigest(intent.finalSemanticDigest)
|
|
89
|
+
: true)
|
|
90
|
+
&& (intent.state === 'aborted' ? intent.errorCode !== null && intent.errorMessage !== null : true);
|
|
43
91
|
}
|
|
44
|
-
function
|
|
92
|
+
export function readArtifactRecoveryIntent(repoRoot, taskId, family, artifact) {
|
|
93
|
+
if (!validTaskId(taskId) || !validArtifact(artifact, family, parseArtifactName(artifact)?.round)) {
|
|
94
|
+
throw new Error('ARTIFACT_RECOVERY_INTENT_INVALID: recovery journal identity is invalid');
|
|
95
|
+
}
|
|
45
96
|
const target = intentPath(repoRoot, taskId, family, artifact);
|
|
46
97
|
if (!fs.existsSync(target))
|
|
47
98
|
return null;
|
|
@@ -50,19 +101,20 @@ function readArtifactRepairIntent(repoRoot, taskId, family, artifact) {
|
|
|
50
101
|
value = JSON.parse(fs.readFileSync(target, 'utf8'));
|
|
51
102
|
}
|
|
52
103
|
catch (error) {
|
|
53
|
-
throw new Error(`
|
|
104
|
+
throw new Error(`ARTIFACT_RECOVERY_INTENT_INVALID: ${String(error)}`);
|
|
54
105
|
}
|
|
55
|
-
if (!
|
|
56
|
-
throw new Error('
|
|
106
|
+
if (!isArtifactRecoveryIntent(value) || value.taskId !== taskId || value.family !== family || value.artifact !== artifact) {
|
|
107
|
+
throw new Error('ARTIFACT_RECOVERY_INTENT_INVALID: recovery journal schema is invalid');
|
|
57
108
|
}
|
|
58
109
|
return value;
|
|
59
110
|
}
|
|
60
|
-
export function
|
|
61
|
-
if (!
|
|
62
|
-
throw new Error('
|
|
63
|
-
const
|
|
111
|
+
export function writeArtifactRecoveryIntent(repoRoot, value, options = {}) {
|
|
112
|
+
if (!isArtifactRecoveryIntent(value))
|
|
113
|
+
throw new Error('ARTIFACT_RECOVERY_INTENT_INVALID: recovery journal schema is invalid');
|
|
114
|
+
const intent = value;
|
|
115
|
+
const directory = artifactRecoveryIntentRoot(repoRoot);
|
|
64
116
|
fs.mkdirSync(directory, { recursive: true, mode: 0o700 });
|
|
65
|
-
const target = intentPath(repoRoot,
|
|
117
|
+
const target = intentPath(repoRoot, intent.taskId, intent.family, intent.artifact);
|
|
66
118
|
const lock = `${target}.lock`;
|
|
67
119
|
let descriptor;
|
|
68
120
|
try {
|
|
@@ -70,19 +122,19 @@ export function writeArtifactRepairIntent(repoRoot, value, options = {}) {
|
|
|
70
122
|
}
|
|
71
123
|
catch (error) {
|
|
72
124
|
if (error.code === 'EEXIST') {
|
|
73
|
-
throw new Error('
|
|
125
|
+
throw new Error('ARTIFACT_RECOVERY_INTENT_CONFLICT: another journal writer owns the intent');
|
|
74
126
|
}
|
|
75
127
|
throw error;
|
|
76
128
|
}
|
|
77
129
|
try {
|
|
78
130
|
const current = fs.existsSync(target)
|
|
79
|
-
?
|
|
131
|
+
? readArtifactRecoveryIntent(repoRoot, intent.taskId, intent.family, intent.artifact)
|
|
80
132
|
: null;
|
|
81
133
|
if (!isDeepStrictEqual(current, options.expected ?? null)) {
|
|
82
|
-
throw new Error('
|
|
134
|
+
throw new Error('ARTIFACT_RECOVERY_INTENT_CONFLICT: recovery journal changed during finalization');
|
|
83
135
|
}
|
|
84
136
|
const temporary = `${target}.${process.pid}.${Date.now()}.tmp`;
|
|
85
|
-
fs.writeFileSync(temporary, `${JSON.stringify(
|
|
137
|
+
fs.writeFileSync(temporary, `${JSON.stringify(intent)}\n`, { encoding: 'utf8', mode: 0o600, flag: 'wx' });
|
|
86
138
|
try {
|
|
87
139
|
fs.renameSync(temporary, target);
|
|
88
140
|
}
|
|
@@ -90,7 +142,7 @@ export function writeArtifactRepairIntent(repoRoot, value, options = {}) {
|
|
|
90
142
|
try {
|
|
91
143
|
fs.unlinkSync(temporary);
|
|
92
144
|
}
|
|
93
|
-
catch { /* preserve primary error */ }
|
|
145
|
+
catch { /* preserve the primary error */ }
|
|
94
146
|
throw error;
|
|
95
147
|
}
|
|
96
148
|
}
|
|
@@ -102,11 +154,10 @@ export function writeArtifactRepairIntent(repoRoot, value, options = {}) {
|
|
|
102
154
|
catch { /* preserve the primary result */ }
|
|
103
155
|
}
|
|
104
156
|
}
|
|
105
|
-
export function
|
|
106
|
-
if (!
|
|
107
|
-
throw new Error('
|
|
108
|
-
|
|
109
|
-
const directory = intentRoot(repoRoot);
|
|
157
|
+
export function findArtifactRecoveryIntentsByOperation(repoRoot, operationId) {
|
|
158
|
+
if (!validToken(operationId))
|
|
159
|
+
throw new Error('ARTIFACT_RECOVERY_OPERATION_INVALID: recovery operation id is invalid');
|
|
160
|
+
const directory = artifactRecoveryIntentRoot(repoRoot);
|
|
110
161
|
if (!fs.existsSync(directory))
|
|
111
162
|
return Object.freeze([]);
|
|
112
163
|
return Object.freeze(fs.readdirSync(directory)
|
|
@@ -118,13 +169,13 @@ export function findArtifactRepairIntentsByOperation(repoRoot, operationId) {
|
|
|
118
169
|
value = JSON.parse(fs.readFileSync(target, 'utf8'));
|
|
119
170
|
}
|
|
120
171
|
catch (error) {
|
|
121
|
-
throw new Error(`
|
|
172
|
+
throw new Error(`ARTIFACT_RECOVERY_INTENT_INVALID: ${String(error)}`);
|
|
122
173
|
}
|
|
123
|
-
if (!
|
|
124
|
-
throw new Error('
|
|
174
|
+
if (!isArtifactRecoveryIntent(value))
|
|
175
|
+
throw new Error('ARTIFACT_RECOVERY_INTENT_INVALID: recovery journal schema is invalid');
|
|
125
176
|
return value;
|
|
126
177
|
})
|
|
127
178
|
.filter((value) => value.recoveryOperationId === operationId));
|
|
128
179
|
}
|
|
129
|
-
export {
|
|
180
|
+
export { intentPath as artifactRecoveryIntentPath };
|
|
130
181
|
//# sourceMappingURL=artifact-repair-intent.js.map
|
|
@@ -181,6 +181,16 @@ function checkpointIdentity(input, task, delivery) {
|
|
|
181
181
|
function checkpointTimestamp() {
|
|
182
182
|
return new Date().toISOString();
|
|
183
183
|
}
|
|
184
|
+
function samePreparedCheckpointIdentity(pending, identity) {
|
|
185
|
+
return pending.state === 'prepared'
|
|
186
|
+
&& pending.taskId === identity.taskId
|
|
187
|
+
&& pending.branch === identity.branch
|
|
188
|
+
&& pending.mode === identity.mode
|
|
189
|
+
&& pending.expectedHead === identity.expectedHead
|
|
190
|
+
&& pending.message === identity.message
|
|
191
|
+
&& pending.round === identity.round
|
|
192
|
+
&& JSON.stringify(pending.paths) === JSON.stringify(identity.paths);
|
|
193
|
+
}
|
|
184
194
|
function executeUnlocked(input, task, mode) {
|
|
185
195
|
const inspected = inspectGitWorkflow(input.cwd);
|
|
186
196
|
if (!inspected.snapshot)
|
|
@@ -214,11 +224,28 @@ function executeUnlocked(input, task, mode) {
|
|
|
214
224
|
error: { code: 'COMMIT_INTENT_INVALID', message: error instanceof Error ? error.message : String(error) }
|
|
215
225
|
};
|
|
216
226
|
}
|
|
217
|
-
if (pendingIntent && !sameCheckpointIntent(pendingIntent, identity))
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
227
|
+
if (pendingIntent && !sameCheckpointIntent(pendingIntent, identity)) {
|
|
228
|
+
if (!input.recoverPreparedIntent || !samePreparedCheckpointIdentity(pendingIntent, identity))
|
|
229
|
+
return {
|
|
230
|
+
status: 'failed', changed: false, snapshot: inspected.snapshot, operations: [],
|
|
231
|
+
error: { code: 'COMMIT_INTENT_CONFLICT', message: 'A different checkpoint intent is still pending for this task' }
|
|
232
|
+
};
|
|
233
|
+
pendingIntent = {
|
|
234
|
+
...pendingIntent,
|
|
235
|
+
expectedTree: identity.expectedTree,
|
|
236
|
+
digest: checkpointIntentDigest(identity),
|
|
237
|
+
updatedAt: checkpointTimestamp()
|
|
221
238
|
};
|
|
239
|
+
try {
|
|
240
|
+
writeCheckpointIntent(task.repoRoot, pendingIntent);
|
|
241
|
+
}
|
|
242
|
+
catch (error) {
|
|
243
|
+
return {
|
|
244
|
+
status: 'failed', changed: false, snapshot: inspected.snapshot, operations: [],
|
|
245
|
+
error: { code: 'COMMIT_INTENT_WRITE_FAILED', message: error instanceof Error ? error.message : String(error) }
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
}
|
|
222
249
|
if (!pendingIntent) {
|
|
223
250
|
const timestamp = checkpointTimestamp();
|
|
224
251
|
pendingIntent = {
|
|
@@ -8,12 +8,13 @@ import { activateMatchingOrchestrationDelegation, activateOrchestrationDelegatio
|
|
|
8
8
|
import { prepareCodexOrchestrationDelegation } from './codex-orchestration.js';
|
|
9
9
|
import { applyTaskFinalization } from './finalization.js';
|
|
10
10
|
import { applyTaskLifecycle, taskLifecycleFailure } from './lifecycle.js';
|
|
11
|
+
import { recoverStartedLifecycleUnderLock, recoveryFailure } from './lifecycle-recovery.js';
|
|
11
12
|
import { locateActivityLog } from './activity-log.js';
|
|
12
13
|
import { resolveTaskRef, TASK_ID_RE } from './resolve-ref.js';
|
|
13
14
|
import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.js';
|
|
14
15
|
import { verifyTaskEvent } from './verification.js';
|
|
15
16
|
import { createCodexCapabilityStore } from '../agent-clients/adapters/codex-lifecycle/capability-store.js';
|
|
16
|
-
import {
|
|
17
|
+
import { findArtifactRecoveryIntentsByOperation, writeArtifactRecoveryIntent } from './artifact-repair-intent.js';
|
|
17
18
|
import { computeLifecycleBuildIdentity, verifyLifecycleBuildIdentity } from '../agent-clients/adapters/codex-lifecycle/build-identity.js';
|
|
18
19
|
const lifecycleAuthorityPhases = [
|
|
19
20
|
'orchestration.prepare', 'artifact.finalize-local', 'task-event.completed'
|
|
@@ -184,7 +185,7 @@ export function queryLifecycleRecoveryOperation(operationId, options = {}) {
|
|
|
184
185
|
const store = options.capabilityStore ?? createCodexCapabilityStore();
|
|
185
186
|
const registry = authorityOperationMap.get(store);
|
|
186
187
|
const known = [...(registry?.values() ?? [])].filter((value) => value.operationId === operationId);
|
|
187
|
-
const intents = options.repoRoot ?
|
|
188
|
+
const intents = options.repoRoot ? findArtifactRecoveryIntentsByOperation(options.repoRoot, operationId) : [];
|
|
188
189
|
const capabilities = store.findByRecoveryOperation(operationId);
|
|
189
190
|
const phaseMap = new Map();
|
|
190
191
|
for (const capability of capabilities) {
|
|
@@ -265,7 +266,7 @@ function lifecycleRecoveryIntentMatches(intent, selector) {
|
|
|
265
266
|
return intent.taskId === selector.taskId
|
|
266
267
|
&& intent.family === selector.family
|
|
267
268
|
&& intent.artifact === selector.artifact
|
|
268
|
-
&& (intent.state === 'commit-started' || intent.state === 'consumed');
|
|
269
|
+
&& (intent.state === 'commit-started' || intent.state === 'consumption-started' || intent.state === 'consumed');
|
|
269
270
|
}
|
|
270
271
|
/**
|
|
271
272
|
* Completes only the durable tail of a committed task event after a process
|
|
@@ -312,19 +313,19 @@ export function recoverLifecycleRecoveryOperation(input, options) {
|
|
|
312
313
|
if (!phase) {
|
|
313
314
|
throw lifecycleRecoveryCompensationError('LIFECYCLE_RECOVERY_PHASE_MISSING', 'durable task completion phase is missing');
|
|
314
315
|
}
|
|
315
|
-
const intents =
|
|
316
|
+
const intents = findArtifactRecoveryIntentsByOperation(options.repoRoot, selector.operationId)
|
|
316
317
|
.filter((intent) => intent.taskId === selector.taskId
|
|
317
318
|
&& intent.family === selector.family
|
|
318
319
|
&& intent.artifact === selector.artifact);
|
|
319
320
|
if (intents.length !== 1) {
|
|
320
|
-
throw lifecycleRecoveryCompensationError('LIFECYCLE_RECOVERY_INTENT_INVALID', 'durable artifact
|
|
321
|
+
throw lifecycleRecoveryCompensationError('LIFECYCLE_RECOVERY_INTENT_INVALID', 'durable artifact recovery intent does not identify this task event');
|
|
321
322
|
}
|
|
322
323
|
const intent = intents[0];
|
|
323
324
|
if (!lifecycleRecoveryIntentMatches(intent, selector)
|
|
324
325
|
|| intent.phase !== selector.phase
|
|
325
326
|
|| intent.requestId !== selector.lifecycleRequestId
|
|
326
327
|
|| intent.authorityDigest === null) {
|
|
327
|
-
throw lifecycleRecoveryCompensationError('LIFECYCLE_RECOVERY_INTENT_INVALID', 'durable artifact
|
|
328
|
+
throw lifecycleRecoveryCompensationError('LIFECYCLE_RECOVERY_INTENT_INVALID', 'durable artifact recovery intent does not match the recovery request');
|
|
328
329
|
}
|
|
329
330
|
if (!lifecycleRecoveryEventCommitted(selector, options.repoRoot)) {
|
|
330
331
|
throw lifecycleRecoveryCompensationError('LIFECYCLE_RECOVERY_COMMIT_UNCONFIRMED', 'task completion is not durably present; compensation is not allowed');
|
|
@@ -339,7 +340,7 @@ export function recoverLifecycleRecoveryOperation(input, options) {
|
|
|
339
340
|
throw lifecycleRecoveryCompensationError('LIFECYCLE_RECOVERY_STATE_INVALID', 'consumed capability has an unconsumed completion phase');
|
|
340
341
|
}
|
|
341
342
|
if (intent.state !== 'consumed') {
|
|
342
|
-
|
|
343
|
+
writeArtifactRecoveryIntent(options.repoRoot, {
|
|
343
344
|
...intent,
|
|
344
345
|
state: 'consumed',
|
|
345
346
|
updatedAt: (options.now ?? Date.now)()
|
|
@@ -473,6 +474,9 @@ async function applyLifecycleWithAuthority(context, request) {
|
|
|
473
474
|
return taskLifecycleFailure(request, { code: 'LIFECYCLE_PAYLOAD_INVALID', message: conflict.message });
|
|
474
475
|
}
|
|
475
476
|
const execute = async () => {
|
|
477
|
+
if (request.intent === 'recover-started') {
|
|
478
|
+
return recoverStartedLifecycleUnderLock(request, { repoRoot: context.repoRoot });
|
|
479
|
+
}
|
|
476
480
|
const lifecycleResult = applyTaskLifecycle(request, { repoRoot: context.repoRoot });
|
|
477
481
|
if (lifecycleResult.status !== 'failed' || !request.overrideTicket)
|
|
478
482
|
return lifecycleResult;
|
|
@@ -506,6 +510,9 @@ async function applyLifecycleWithAuthority(context, request) {
|
|
|
506
510
|
catch (error) {
|
|
507
511
|
if (!(error instanceof TaskExecutionLockError))
|
|
508
512
|
throw error;
|
|
513
|
+
if (request.intent === 'recover-started') {
|
|
514
|
+
return recoveryFailure(request, 'owner-unknown', error.code, error.message);
|
|
515
|
+
}
|
|
509
516
|
return taskLifecycleFailure(request, { code: error.code, message: error.message }, taskId);
|
|
510
517
|
}
|
|
511
518
|
}
|
|
@@ -616,7 +623,7 @@ function operationInvalid(message) {
|
|
|
616
623
|
}
|
|
617
624
|
const LIFECYCLE_FLAGS = new Set([
|
|
618
625
|
'--agent', '--reason', '--unblock-condition', '--note', '--alert-number', '--staging-dir', '--issue-number',
|
|
619
|
-
'--override-ticket', '--override-target', '--override-scope', '--dry-run'
|
|
626
|
+
'--stage', '--round', '--artifact', '--override-ticket', '--override-target', '--override-scope', '--dry-run'
|
|
620
627
|
]);
|
|
621
628
|
const FINALIZATION_FLAGS = new Set(['--agent']);
|
|
622
629
|
const ORCHESTRATION_FLAGS = new Set([
|
|
@@ -692,8 +699,32 @@ export function parseTaskControlOperation(family, args) {
|
|
|
692
699
|
...(value(values, '--override-scope') ? { overrideScope: value(values, '--override-scope') } : {}),
|
|
693
700
|
...(value(values, '--alert-number') ? { alertNumber: Number(value(values, '--alert-number')) } : {}),
|
|
694
701
|
...(value(values, '--issue-number') ? { issueNumber: Number(value(values, '--issue-number')) } : {}),
|
|
702
|
+
...(value(values, '--stage') ? { stage: value(values, '--stage') } : {}),
|
|
703
|
+
...(value(values, '--round') ? { round: Number(value(values, '--round')) } : {}),
|
|
704
|
+
...(value(values, '--artifact') ? { artifact: value(values, '--artifact') } : {}),
|
|
695
705
|
...(values['--dry-run'] === true ? { dryRun: true } : {})
|
|
696
706
|
};
|
|
707
|
+
if (intent === 'recover-started') {
|
|
708
|
+
const recoveryFlags = new Set(['--agent', '--stage', '--round', '--artifact', '--reason']);
|
|
709
|
+
for (const flag of Object.keys(values)) {
|
|
710
|
+
if (!recoveryFlags.has(flag))
|
|
711
|
+
operationInvalid(`option '${flag}' is not supported for recover-started`);
|
|
712
|
+
}
|
|
713
|
+
required(values, ['--stage', '--round', '--artifact', '--reason']);
|
|
714
|
+
const stage = value(values, '--stage');
|
|
715
|
+
if (!['analysis', 'review-analysis', 'plan', 'review-plan', 'code', 'review-code'].includes(stage ?? '')) {
|
|
716
|
+
operationInvalid('--stage must be a supported lifecycle stage');
|
|
717
|
+
}
|
|
718
|
+
const round = Number(value(values, '--round'));
|
|
719
|
+
if (!Number.isSafeInteger(round) || round < 1)
|
|
720
|
+
operationInvalid('--round must be a positive integer');
|
|
721
|
+
if (value(values, '--reason').includes('\n') || value(values, '--reason').includes('\r')) {
|
|
722
|
+
operationInvalid('--reason must be a single line');
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
else if (values['--stage'] !== undefined || values['--round'] !== undefined || values['--artifact'] !== undefined) {
|
|
726
|
+
operationInvalid('--stage, --round, and --artifact are only supported for recover-started');
|
|
727
|
+
}
|
|
697
728
|
return { family, request: input };
|
|
698
729
|
}
|
|
699
730
|
const values = parseValues(args, 2, ORCHESTRATION_FLAGS);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
2
|
import { isCompletionEvidence } from './orchestration.js';
|
|
3
3
|
import { TASK_WORKFLOW_OPERATIONS } from './workflow-command.js';
|
|
4
|
+
import { sameRecoveryWarning } from './recovery-warning.js';
|
|
4
5
|
/** Normalize the command result once at the control transport boundary. */
|
|
5
6
|
export function parseControlOutput(output) {
|
|
6
7
|
if (output === null)
|
|
@@ -18,7 +19,7 @@ export function parseControlOutput(output) {
|
|
|
18
19
|
export const SANDBOX_CONTROL_REQUIRED_COMPLETION_PHASES = Object.freeze([
|
|
19
20
|
'completed', 'evidence-written', 'publish-authorized'
|
|
20
21
|
]);
|
|
21
|
-
const LIFECYCLE_INTENTS = ['block', 'activate', 'cancel', 'complete', 'close-codescan', 'close-dependabot', 'restore'];
|
|
22
|
+
const LIFECYCLE_INTENTS = ['block', 'activate', 'cancel', 'complete', 'close-codescan', 'close-dependabot', 'restore', 'recover-started'];
|
|
22
23
|
const ORCHESTRATION_INTENTS = [
|
|
23
24
|
'begin-or-resume', 'route.read', 'route.clean-completion', 'status', 'prepare', 'dispatch',
|
|
24
25
|
'await-activation', 'recover-prepared', 'hook-start', 'hook-stop', 'advance', 'pause'
|
|
@@ -65,6 +66,15 @@ function domainEvidenceMatches(operation, result, domain) {
|
|
|
65
66
|
&& snapshot.worktreeTree === completion.worktreeTree
|
|
66
67
|
&& lastReviewedCommit === completion.lastReviewedCommit;
|
|
67
68
|
}
|
|
69
|
+
if (operation.family === 'task-lifecycle' && operation.intent === 'recover-started') {
|
|
70
|
+
const retryRequired = domain.recoveryState === 'retry-required'
|
|
71
|
+
&& sameRecoveryWarning(domain.warning, result.warning);
|
|
72
|
+
return result.targetState === 'active'
|
|
73
|
+
&& ['applied', 'no-op'].includes(String(result.status))
|
|
74
|
+
&& domain.recovery === true
|
|
75
|
+
&& domain.targetState === 'active'
|
|
76
|
+
&& (domain.recoveryState === 'released' || retryRequired);
|
|
77
|
+
}
|
|
68
78
|
if (operation.class === 'read-only')
|
|
69
79
|
return result.changed === false && domain.snapshotValid === true;
|
|
70
80
|
return true;
|
|
@@ -119,7 +129,9 @@ export function classifySandboxControlRecovery(input) {
|
|
|
119
129
|
responseReconstructable: true,
|
|
120
130
|
reasonCode: input.operation.class === 'route.clean-completion' && result.changed === false
|
|
121
131
|
? 'RECOVERY_ROUTE_COMPLETION_NOOP'
|
|
122
|
-
: '
|
|
132
|
+
: input.operation.intent === 'recover-started' && input.domain?.recoveryState === 'retry-required'
|
|
133
|
+
? 'RECOVERY_RELEASE_RETRY_REQUIRED'
|
|
134
|
+
: 'RECOVERY_TERMINAL_AND_DOMAIN_MATCH'
|
|
123
135
|
};
|
|
124
136
|
}
|
|
125
137
|
export function operationRecoveryBinding(requestId, generation, taskId, family, intent) {
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
1
|
import { syncPlatformComment } from '../platform/issue-comments.js';
|
|
4
2
|
import { createPlatformIssue, syncPlatformIssue } from '../platform/issues.js';
|
|
5
3
|
import { VERSION } from '../version.js';
|
|
@@ -139,11 +137,6 @@ function platformOperations(prefix, operations) {
|
|
|
139
137
|
reasonCode: operation.reasonCode ?? null
|
|
140
138
|
}));
|
|
141
139
|
}
|
|
142
|
-
function issueFromTask(repoRoot, taskId) {
|
|
143
|
-
const content = fs.readFileSync(path.join(repoRoot, '.agents', 'workspace', 'active', taskId, 'task.md'), 'utf8');
|
|
144
|
-
const match = /^issue_number:\s*(\d+)\s*$/m.exec(content);
|
|
145
|
-
return match ? { number: Number(match[1]), url: '' } : null;
|
|
146
|
-
}
|
|
147
140
|
async function createTask(value, options) {
|
|
148
141
|
const dependencies = { ...DEFAULT_DEPENDENCIES, ...options.dependencies };
|
|
149
142
|
let candidate;
|
|
@@ -163,7 +156,7 @@ async function createTask(value, options) {
|
|
|
163
156
|
const operations = [{
|
|
164
157
|
name: 'task:local', status: local.status, reasonCode: null
|
|
165
158
|
}];
|
|
166
|
-
let issue =
|
|
159
|
+
let issue = null;
|
|
167
160
|
const created = await dependencies.createIssue(local.task.id, { cwd: options.repoRoot, agent: candidate.agent });
|
|
168
161
|
operations.push(...platformOperations('platform-create', created.operations));
|
|
169
162
|
if (created.issue)
|
|
@@ -92,7 +92,8 @@ function hasCurrentCodexEvidence(receipt) {
|
|
|
92
92
|
const provenance = receipt.lifecycleProvenance;
|
|
93
93
|
if (!provenance)
|
|
94
94
|
return false;
|
|
95
|
-
const activated = ['activated', 'stage-completed', 'sealed', 'consumed'].includes(receipt.status)
|
|
95
|
+
const activated = ['activated', 'stage-completed', 'sealed', 'consumed'].includes(receipt.status)
|
|
96
|
+
|| (receipt.status === 'aborted' && receipt.activatedAt !== null);
|
|
96
97
|
if (!activated)
|
|
97
98
|
return receipt.hostEvidence === null;
|
|
98
99
|
const host = receipt.hostEvidence;
|
|
@@ -124,6 +125,12 @@ function hasCurrentCodexEvidence(receipt) {
|
|
|
124
125
|
&& host.consumer === receipt.id
|
|
125
126
|
&& exactText(host.consumedAt);
|
|
126
127
|
}
|
|
128
|
+
if (receipt.status === 'aborted') {
|
|
129
|
+
return Number.isSafeInteger(host.stopRevision)
|
|
130
|
+
&& host.stopRevision > host.startRevision
|
|
131
|
+
&& host.consumer === `lifecycle-recovery:${receipt.taskId}:${receipt.id}`
|
|
132
|
+
&& exactText(host.consumedAt);
|
|
133
|
+
}
|
|
127
134
|
return host.stopRevision === null && host.consumer === null && host.consumedAt === null;
|
|
128
135
|
}
|
|
129
136
|
const RECEIPT_KEYS = [
|
|
@@ -147,7 +154,8 @@ function hasStatusBoundEvidence(receipt) {
|
|
|
147
154
|
const dispatchComplete = dispatchFields.every((field) => field !== null);
|
|
148
155
|
if (!dispatchEmpty && !dispatchComplete)
|
|
149
156
|
return false;
|
|
150
|
-
const beforeActivation = ['prepared', '
|
|
157
|
+
const beforeActivation = ['prepared', 'expired'].includes(receipt.status)
|
|
158
|
+
|| (receipt.status === 'aborted' && receipt.activatedAt === null);
|
|
151
159
|
if (beforeActivation) {
|
|
152
160
|
return receipt.parentId === null
|
|
153
161
|
&& receipt.childId === null
|
|
@@ -181,6 +189,20 @@ function hasStatusBoundEvidence(receipt) {
|
|
|
181
189
|
&& receipt.sealedAt === null
|
|
182
190
|
&& receipt.consumedAt === null;
|
|
183
191
|
}
|
|
192
|
+
if (receipt.status === 'aborted') {
|
|
193
|
+
const host = receipt.hostEvidence;
|
|
194
|
+
return receipt.agent === null
|
|
195
|
+
&& receipt.afterFingerprint === null
|
|
196
|
+
&& receipt.changedPaths.length === 0
|
|
197
|
+
&& receipt.sealedAt === null
|
|
198
|
+
&& receipt.consumedAt === null
|
|
199
|
+
&& host !== null
|
|
200
|
+
&& host !== undefined
|
|
201
|
+
&& Number.isSafeInteger(host.stopRevision)
|
|
202
|
+
&& host.stopRevision > host.startRevision
|
|
203
|
+
&& host.consumer === `lifecycle-recovery:${receipt.taskId}:${receipt.id}`
|
|
204
|
+
&& exactText(host.consumedAt);
|
|
205
|
+
}
|
|
184
206
|
if (!exactText(receipt.agent)
|
|
185
207
|
|| normalizeAgentToken(receipt.agent) !== normalizeAgentToken(receipt.client))
|
|
186
208
|
return false;
|
|
@@ -429,6 +451,45 @@ function abortPreparedDelegation(receipt) {
|
|
|
429
451
|
receipt: Object.freeze({ ...receipt, status: 'aborted' })
|
|
430
452
|
};
|
|
431
453
|
}
|
|
454
|
+
function abortActivatedDelegation(receipt, event) {
|
|
455
|
+
if (receipt.status !== 'activated') {
|
|
456
|
+
return fail('DELEGATION_STATE_INVALID', `delegation ${receipt.id} is ${receipt.status}, expected activated`);
|
|
457
|
+
}
|
|
458
|
+
if (receipt.client !== 'codex'
|
|
459
|
+
|| !receipt.lifecycleProvenance
|
|
460
|
+
|| receipt.hostEvidence?.kind !== 'codex-lifecycle-v2'
|
|
461
|
+
|| typeof receipt.lifecycleProvenance.controllerInstanceDigest !== 'string'
|
|
462
|
+
|| typeof receipt.lifecycleProvenance.controlGeneration !== 'string'
|
|
463
|
+
|| typeof receipt.hostEvidence.controllerInstanceDigest !== 'string'
|
|
464
|
+
|| typeof receipt.hostEvidence.controlGeneration !== 'string') {
|
|
465
|
+
return fail('DELEGATION_RECOVERY_EVIDENCE_INVALID', 'activated delegation lacks the current Codex controller binding');
|
|
466
|
+
}
|
|
467
|
+
if (event.childId !== receipt.childId
|
|
468
|
+
|| !Number.isSafeInteger(event.stopRevision)
|
|
469
|
+
|| event.stopRevision <= receipt.hostEvidence.startRevision
|
|
470
|
+
|| event.consumer !== `lifecycle-recovery:${receipt.taskId}:${receipt.id}`
|
|
471
|
+
|| !exactText(event.consumedAt)) {
|
|
472
|
+
return fail('DELEGATION_RECOVERY_EVIDENCE_INVALID', 'recovery stop evidence does not match the activated delegation');
|
|
473
|
+
}
|
|
474
|
+
return {
|
|
475
|
+
ok: true,
|
|
476
|
+
receipt: Object.freeze({
|
|
477
|
+
...receipt,
|
|
478
|
+
status: 'aborted',
|
|
479
|
+
agent: null,
|
|
480
|
+
afterFingerprint: null,
|
|
481
|
+
changedPaths: Object.freeze([]),
|
|
482
|
+
sealedAt: null,
|
|
483
|
+
consumedAt: null,
|
|
484
|
+
hostEvidence: Object.freeze({
|
|
485
|
+
...receipt.hostEvidence,
|
|
486
|
+
stopRevision: event.stopRevision,
|
|
487
|
+
consumer: event.consumer,
|
|
488
|
+
consumedAt: event.consumedAt
|
|
489
|
+
})
|
|
490
|
+
})
|
|
491
|
+
};
|
|
492
|
+
}
|
|
432
493
|
function completeDelegationStage(receipt, event) {
|
|
433
494
|
if (receipt.status !== 'activated')
|
|
434
495
|
return fail('DELEGATION_STATE_INVALID', `delegation ${receipt.id} is ${receipt.status}, expected activated`);
|
|
@@ -494,5 +555,5 @@ function consumeDelegation(receipt, options = {}) {
|
|
|
494
555
|
consumedAt: (options.now ?? (() => new Date().toISOString()))()
|
|
495
556
|
}) };
|
|
496
557
|
}
|
|
497
|
-
export { activateDelegation, abortPreparedDelegation, completeDelegationStage, consumeDelegation, dispatchDelegation, foldBlankToNull, isDelegationReceipt, managedDelegationRole, prepareDelegation, sealDelegation };
|
|
558
|
+
export { activateDelegation, abortActivatedDelegation, abortPreparedDelegation, completeDelegationStage, consumeDelegation, dispatchDelegation, foldBlankToNull, isDelegationReceipt, managedDelegationRole, prepareDelegation, sealDelegation };
|
|
498
559
|
//# sourceMappingURL=delegation-receipts.js.map
|