@fitlab-ai/agent-infra 0.9.14 → 0.9.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -0
- package/README.zh-CN.md +10 -0
- package/bin/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 +3 -2
- package/dist/lib/defaults.json +103 -103
- package/dist/lib/fs/durable-write.js +37 -0
- package/dist/lib/fs/file-lock.js +111 -0
- package/dist/lib/host-control/audit.js +19 -0
- package/dist/lib/host-control/client.js +142 -0
- package/dist/lib/host-control/command.js +49 -0
- package/dist/lib/host-control/path.js +160 -0
- package/dist/lib/host-control/secure-fs.js +140 -0
- package/dist/lib/host-control/server.js +159 -0
- package/dist/lib/host-control/service.js +112 -0
- package/dist/lib/init.js +11 -2
- package/dist/lib/internal/cli-route-inventory.js +3 -1
- 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 +322 -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 +15 -262
- package/dist/lib/internal/task-create.js +6 -5
- package/dist/lib/internal/task-event.js +37 -37
- package/dist/lib/internal/task-operation-registry.js +101 -3
- package/dist/lib/internal/task-review.js +8 -53
- 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 +2 -2
- package/dist/lib/platform/issues.js +1 -179
- package/dist/lib/platform/pr-checks.js +12 -65
- package/dist/lib/platform/pr-summary.js +55 -3
- package/dist/lib/platform/pull-requests.js +2 -105
- package/dist/lib/platform/snapshots.js +2 -0
- package/dist/lib/platform/verification-sync.js +127 -142
- package/dist/lib/release/workflow.js +1 -1
- package/dist/lib/sandbox/commands/create.js +7 -2
- package/dist/lib/sandbox/commands/rm.js +3 -1514
- 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 +491 -120
- package/dist/lib/sandbox/control/state.js +58 -0
- package/dist/lib/sandbox/control/task-workflow.js +74 -0
- package/dist/lib/sandbox/control/workflow-executor.js +99 -0
- package/dist/lib/sandbox/index.js +2 -2
- package/dist/lib/sandbox/recovery.js +28 -4
- package/dist/lib/sandbox/removal.js +1717 -0
- package/dist/lib/sandbox/task-cleanup.js +217 -0
- 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 +101 -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 +23 -69
- package/dist/lib/task/artifact-receipts.js +3 -15
- package/dist/lib/task/artifact-repair-intent.js +87 -16
- 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 +2 -16
- package/dist/lib/task/control-authority.js +371 -5
- package/dist/lib/task/control-recovery.js +128 -0
- package/dist/lib/task/create.js +62 -9
- package/dist/lib/task/decision-details.js +2 -46
- package/dist/lib/task/events.js +120 -9
- package/dist/lib/task/finalization-state.js +9 -0
- package/dist/lib/task/human-override.js +3 -6
- package/dist/lib/task/implementation-inputs.js +3 -3
- package/dist/lib/task/intermediate-cleanup.js +383 -0
- package/dist/lib/task/invalidation.js +18 -65
- package/dist/lib/task/ledger-intents.js +7 -22
- package/dist/lib/task/ledger.js +1 -3
- package/dist/lib/task/lifecycle.js +19 -1
- package/dist/lib/task/local-artifact-finalization.js +132 -157
- package/dist/lib/task/manual-validation-completion.js +26 -0
- package/dist/lib/task/manual-validation-evidence.js +130 -0
- package/dist/lib/task/manual-validation-receipt.js +101 -0
- package/dist/lib/task/manual-validation-shared.js +40 -0
- package/dist/lib/task/manual-validation-transaction.js +243 -0
- package/dist/lib/task/markdown.js +47 -0
- package/dist/lib/task/orchestration.js +14 -17
- package/dist/lib/task/qualification-audit.js +20 -34
- package/dist/lib/task/review-artifacts.js +2 -18
- package/dist/lib/task/review-command.js +25 -0
- package/dist/lib/task/review-finalization.js +125 -193
- package/dist/lib/task/review-fingerprint.js +2 -2
- package/dist/lib/task/sections.js +34 -56
- package/dist/lib/task/verification-engine.js +116 -323
- package/dist/lib/task/verification.js +1 -1
- package/dist/lib/task/workflow-command.js +17 -0
- package/dist/lib/task/workflow-warning-intents.js +0 -16
- package/dist/lib/task/workflow-warnings.js +28 -2
- package/dist/lib/update.js +2 -0
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +223 -33
- package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +3 -2
- package/lib/fs/durable-write.ts +29 -0
- package/lib/fs/file-lock.ts +113 -0
- package/lib/host-control/audit.ts +22 -0
- package/lib/host-control/client.ts +172 -0
- package/lib/host-control/command.ts +60 -0
- package/lib/host-control/path.ts +167 -0
- package/lib/host-control/secure-fs.ts +135 -0
- package/lib/host-control/server.ts +180 -0
- package/lib/host-control/service.ts +133 -0
- package/lib/init.ts +15 -2
- package/lib/internal/cli-route-inventory.ts +3 -1
- 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 +306 -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 +13 -216
- package/lib/internal/task-create.ts +5 -5
- package/lib/internal/task-event.ts +20 -11
- package/lib/internal/task-operation-registry.ts +111 -3
- package/lib/internal/task-review.ts +6 -51
- 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 +3 -4
- package/lib/platform/issues.ts +4 -245
- package/lib/platform/pr-checks.ts +13 -77
- package/lib/platform/pr-summary.ts +61 -5
- package/lib/platform/pull-requests.ts +3 -170
- package/lib/platform/snapshots.ts +53 -0
- package/lib/platform/verification-sync.ts +145 -155
- package/lib/release/workflow.ts +3 -3
- package/lib/sandbox/commands/create.ts +7 -6
- package/lib/sandbox/commands/rm.ts +3 -1909
- 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 +75 -6
- package/lib/sandbox/control/server.ts +538 -123
- package/lib/sandbox/control/state.ts +78 -0
- package/lib/sandbox/control/task-workflow.ts +95 -0
- package/lib/sandbox/control/workflow-executor.ts +102 -0
- package/lib/sandbox/index.ts +2 -2
- package/lib/sandbox/recovery.ts +28 -5
- package/lib/sandbox/removal.ts +2144 -0
- package/lib/sandbox/task-cleanup.ts +209 -0
- 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 +104 -0
- package/lib/task/artifact-lifecycle.ts +31 -26
- package/lib/task/artifact-name.ts +55 -0
- package/lib/task/artifact-operations.ts +22 -68
- package/lib/task/artifact-receipts.ts +3 -14
- package/lib/task/artifact-repair-intent.ts +99 -18
- 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 +2 -16
- package/lib/task/control-authority.ts +502 -5
- package/lib/task/control-recovery.ts +174 -0
- package/lib/task/create.ts +63 -8
- package/lib/task/decision-details.ts +3 -71
- package/lib/task/events.ts +131 -10
- package/lib/task/finalization-state.ts +11 -0
- package/lib/task/human-override.ts +3 -8
- package/lib/task/implementation-inputs.ts +3 -3
- package/lib/task/intermediate-cleanup.ts +455 -0
- package/lib/task/invalidation.ts +18 -62
- package/lib/task/ledger-intents.ts +7 -24
- package/lib/task/ledger.ts +1 -3
- package/lib/task/lifecycle.ts +17 -1
- package/lib/task/local-artifact-finalization.ts +148 -182
- package/lib/task/manual-validation-completion.ts +40 -0
- package/lib/task/manual-validation-evidence.ts +209 -0
- package/lib/task/manual-validation-receipt.ts +152 -0
- package/lib/task/manual-validation-shared.ts +50 -0
- package/lib/task/manual-validation-transaction.ts +304 -0
- package/lib/task/markdown.ts +70 -0
- package/lib/task/orchestration.ts +19 -19
- package/lib/task/qualification-audit.ts +22 -33
- package/lib/task/review-artifacts.ts +1 -20
- package/lib/task/review-command.ts +23 -0
- package/lib/task/review-finalization.ts +131 -259
- package/lib/task/review-fingerprint.ts +2 -2
- package/lib/task/sections.ts +33 -54
- package/lib/task/verification-engine.ts +110 -366
- package/lib/task/verification-types.ts +17 -0
- package/lib/task/verification.ts +1 -1
- package/lib/task/workflow-command.ts +18 -0
- package/lib/task/workflow-warning-intents.ts +0 -13
- package/lib/task/workflow-warnings.ts +21 -2
- package/lib/update.ts +6 -0
- package/package.json +6 -6
- package/templates/.agents/hooks/lifecycle-delegation.js +6 -6
- 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/complete-manual-validation/SKILL.en.md +32 -20
- package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +32 -20
- 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 +1 -1
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +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/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/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/.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,4 +1,6 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
+
import crypto from 'node:crypto';
|
|
3
|
+
import fs from 'node:fs';
|
|
2
4
|
|
|
3
5
|
import { normalizeAgentToken } from '../agent-clients/tokens.ts';
|
|
4
6
|
import { isAgentClientId } from '../agent-clients/types.ts';
|
|
@@ -37,9 +39,478 @@ import {
|
|
|
37
39
|
type TaskLifecycleRequest,
|
|
38
40
|
type TaskLifecycleResult
|
|
39
41
|
} from './lifecycle.ts';
|
|
42
|
+
import { locateActivityLog } from './activity-log.ts';
|
|
40
43
|
import { resolveTaskRef, TASK_ID_RE } from './resolve-ref.ts';
|
|
41
44
|
import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.ts';
|
|
42
45
|
import { verifyTaskEvent } from './verification.ts';
|
|
46
|
+
import { createCodexCapabilityStore } from '../agent-clients/adapters/codex-lifecycle/capability-store.ts';
|
|
47
|
+
import {
|
|
48
|
+
findArtifactRepairIntentsByOperation,
|
|
49
|
+
writeArtifactRepairIntent,
|
|
50
|
+
type ArtifactRepairIntent
|
|
51
|
+
} from './artifact-repair-intent.ts';
|
|
52
|
+
import {
|
|
53
|
+
computeLifecycleBuildIdentity,
|
|
54
|
+
verifyLifecycleBuildIdentity,
|
|
55
|
+
type LifecycleBuildIdentity
|
|
56
|
+
} from '../agent-clients/adapters/codex-lifecycle/build-identity.ts';
|
|
57
|
+
|
|
58
|
+
export type LifecycleAuthorityPhase =
|
|
59
|
+
| 'orchestration.prepare'
|
|
60
|
+
| 'artifact.finalize-local'
|
|
61
|
+
| 'task-event.completed';
|
|
62
|
+
|
|
63
|
+
export type LifecycleAuthorityRequestV1 = Readonly<{
|
|
64
|
+
version: 1;
|
|
65
|
+
requestId: string;
|
|
66
|
+
operationId: string;
|
|
67
|
+
phase: LifecycleAuthorityPhase;
|
|
68
|
+
taskId: string;
|
|
69
|
+
family: 'analysis' | 'plan' | 'code';
|
|
70
|
+
artifact: string;
|
|
71
|
+
round: number;
|
|
72
|
+
lifecycleRequestId: string;
|
|
73
|
+
authorityRef: string;
|
|
74
|
+
expectedControlGeneration: string;
|
|
75
|
+
expectedControllerInstanceDigest: string;
|
|
76
|
+
expectedBuildIdentityDigest: string;
|
|
77
|
+
expectedHookDefinitionHash: string;
|
|
78
|
+
}>;
|
|
79
|
+
|
|
80
|
+
export type LifecycleRecoveryAttestationV1 = Readonly<{
|
|
81
|
+
version: 1;
|
|
82
|
+
attestationId: string;
|
|
83
|
+
authorityRefDigest: string;
|
|
84
|
+
operationId: string;
|
|
85
|
+
phase: LifecycleAuthorityPhase;
|
|
86
|
+
requestId: string;
|
|
87
|
+
lifecycleRequestId: string;
|
|
88
|
+
taskId: string;
|
|
89
|
+
family: 'analysis' | 'plan' | 'code';
|
|
90
|
+
artifact: string;
|
|
91
|
+
round: number;
|
|
92
|
+
controlGeneration: string;
|
|
93
|
+
controllerInstanceDigest: string;
|
|
94
|
+
sessionId: string;
|
|
95
|
+
turnId: string;
|
|
96
|
+
toolUseId: string;
|
|
97
|
+
buildIdentityDigest: string;
|
|
98
|
+
hookDefinitionHash: string;
|
|
99
|
+
issuedAt: number;
|
|
100
|
+
expiresAt: number;
|
|
101
|
+
}>;
|
|
102
|
+
|
|
103
|
+
export type LifecycleAuthorityResponseV1 = Readonly<{
|
|
104
|
+
version: 1;
|
|
105
|
+
requestId: string;
|
|
106
|
+
operationId: string;
|
|
107
|
+
phase: LifecycleAuthorityPhase;
|
|
108
|
+
status: 'issued' | 'already-completed' | 'rejected';
|
|
109
|
+
attestation: LifecycleRecoveryAttestationV1 | null;
|
|
110
|
+
error: { code: string; message: string } | null;
|
|
111
|
+
}>;
|
|
112
|
+
|
|
113
|
+
type LifecycleAuthorityIssuerOptions = Readonly<{
|
|
114
|
+
controllerBinding?: TaskControlControllerBinding | null;
|
|
115
|
+
capabilityStore?: ReturnType<typeof createCodexCapabilityStore>;
|
|
116
|
+
buildIdentity?: LifecycleBuildIdentity;
|
|
117
|
+
now?: () => number;
|
|
118
|
+
operationAttestations?: Map<string, LifecycleRecoveryAttestationV1>;
|
|
119
|
+
}>;
|
|
120
|
+
|
|
121
|
+
export type LifecycleRecoveryOperationQueryV1 = Readonly<{
|
|
122
|
+
version: 1;
|
|
123
|
+
operationId: string;
|
|
124
|
+
status: 'unknown' | 'in-progress' | 'committed';
|
|
125
|
+
capabilityState: 'unknown' | 'reserved' | 'consumed';
|
|
126
|
+
phases: readonly Readonly<{
|
|
127
|
+
phase: LifecycleAuthorityPhase;
|
|
128
|
+
requestId: string;
|
|
129
|
+
attestationId: string | null;
|
|
130
|
+
state: 'issued' | 'consumed' | 'observed';
|
|
131
|
+
}>[];
|
|
132
|
+
}>;
|
|
133
|
+
|
|
134
|
+
const lifecycleAuthorityPhases: readonly LifecycleAuthorityPhase[] = [
|
|
135
|
+
'orchestration.prepare', 'artifact.finalize-local', 'task-event.completed'
|
|
136
|
+
];
|
|
137
|
+
const lifecycleAuthorityFamilies = ['analysis', 'plan', 'code'] as const;
|
|
138
|
+
const lifecycleAuthorityArtifact = /^(?:analysis|plan|code)(?:-r[1-9]\d*)?\.md$/u;
|
|
139
|
+
const lifecycleAuthorityDigest = (value: string): string => crypto.createHash('sha256').update(value, 'utf8').digest('hex');
|
|
140
|
+
const lifecycleAuthorityBuildDigest = (value: LifecycleBuildIdentity): string => lifecycleAuthorityDigest(JSON.stringify(value));
|
|
141
|
+
|
|
142
|
+
export function lifecycleRecoveryAttestationDigest(value: LifecycleRecoveryAttestationV1): string {
|
|
143
|
+
return lifecycleAuthorityDigest(JSON.stringify(value));
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function authorityExactKeys(value: Record<string, unknown>, keys: readonly string[]): boolean {
|
|
147
|
+
return Object.keys(value).sort().join(',') === [...keys].sort().join(',');
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function validateLifecycleAuthorityRequest(value: unknown): LifecycleAuthorityRequestV1 {
|
|
151
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error('LIFECYCLE_AUTHORITY_REQUEST_INVALID');
|
|
152
|
+
const request = value as Record<string, unknown>;
|
|
153
|
+
if (!authorityExactKeys(request, [
|
|
154
|
+
'artifact', 'authorityRef', 'expectedBuildIdentityDigest', 'expectedControlGeneration',
|
|
155
|
+
'expectedControllerInstanceDigest', 'expectedHookDefinitionHash', 'family', 'lifecycleRequestId',
|
|
156
|
+
'operationId', 'phase', 'requestId', 'round', 'taskId', 'version'
|
|
157
|
+
])
|
|
158
|
+
|| request.version !== 1
|
|
159
|
+
|| typeof request.requestId !== 'string' || !/^[a-f0-9-]{16,64}$/u.test(request.requestId)
|
|
160
|
+
|| typeof request.operationId !== 'string' || !/^[a-f0-9-]{16,64}$/u.test(request.operationId)
|
|
161
|
+
|| !lifecycleAuthorityPhases.includes(request.phase as LifecycleAuthorityPhase)
|
|
162
|
+
|| typeof request.taskId !== 'string' || !/^TASK-\d{8}-\d{6}$/u.test(request.taskId)
|
|
163
|
+
|| !lifecycleAuthorityFamilies.includes(request.family as typeof lifecycleAuthorityFamilies[number])
|
|
164
|
+
|| typeof request.artifact !== 'string' || !lifecycleAuthorityArtifact.test(request.artifact)
|
|
165
|
+
|| !Number.isSafeInteger(request.round) || (request.round as number) < 1
|
|
166
|
+
|| typeof request.lifecycleRequestId !== 'string' || !request.lifecycleRequestId || /[\r\n]/u.test(request.lifecycleRequestId)
|
|
167
|
+
|| typeof request.authorityRef !== 'string' || !request.authorityRef || /[\r\n]/u.test(request.authorityRef)
|
|
168
|
+
|| typeof request.expectedControlGeneration !== 'string' || !request.expectedControlGeneration
|
|
169
|
+
|| typeof request.expectedControllerInstanceDigest !== 'string' || !/^[a-f0-9]{64}$/u.test(request.expectedControllerInstanceDigest)
|
|
170
|
+
|| typeof request.expectedBuildIdentityDigest !== 'string' || !/^[a-f0-9]{64}$/u.test(request.expectedBuildIdentityDigest)
|
|
171
|
+
|| typeof request.expectedHookDefinitionHash !== 'string' || !/^[a-f0-9]{64}$/u.test(request.expectedHookDefinitionHash)) {
|
|
172
|
+
throw new Error('LIFECYCLE_AUTHORITY_REQUEST_INVALID');
|
|
173
|
+
}
|
|
174
|
+
const expectedArtifactFamily = request.artifact as string;
|
|
175
|
+
if (!expectedArtifactFamily.startsWith(request.family as string)) throw new Error('LIFECYCLE_AUTHORITY_REQUEST_INVALID');
|
|
176
|
+
return request as LifecycleAuthorityRequestV1;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export function validateLifecycleRecoveryAttestation(
|
|
180
|
+
value: unknown,
|
|
181
|
+
now = Date.now()
|
|
182
|
+
): LifecycleRecoveryAttestationV1 {
|
|
183
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error('LIFECYCLE_AUTHORITY_ATTESTATION_INVALID');
|
|
184
|
+
const attestation = value as Record<string, unknown>;
|
|
185
|
+
if (!authorityExactKeys(attestation, [
|
|
186
|
+
'artifact', 'attestationId', 'authorityRefDigest', 'buildIdentityDigest', 'controlGeneration',
|
|
187
|
+
'controllerInstanceDigest', 'expiresAt', 'family', 'hookDefinitionHash', 'issuedAt',
|
|
188
|
+
'lifecycleRequestId', 'operationId', 'phase', 'requestId', 'round', 'sessionId',
|
|
189
|
+
'taskId', 'toolUseId', 'turnId', 'version'
|
|
190
|
+
])
|
|
191
|
+
|| attestation.version !== 1
|
|
192
|
+
|| typeof attestation.attestationId !== 'string' || !/^[a-f0-9-]{16,64}$/u.test(attestation.attestationId)
|
|
193
|
+
|| typeof attestation.authorityRefDigest !== 'string' || !/^[a-f0-9]{64}$/u.test(attestation.authorityRefDigest)
|
|
194
|
+
|| typeof attestation.operationId !== 'string' || !/^[a-f0-9-]{16,64}$/u.test(attestation.operationId)
|
|
195
|
+
|| !lifecycleAuthorityPhases.includes(attestation.phase as LifecycleAuthorityPhase)
|
|
196
|
+
|| typeof attestation.requestId !== 'string' || !/^[a-f0-9-]{16,64}$/u.test(attestation.requestId)
|
|
197
|
+
|| typeof attestation.lifecycleRequestId !== 'string' || !attestation.lifecycleRequestId
|
|
198
|
+
|| typeof attestation.taskId !== 'string' || !/^TASK-\d{8}-\d{6}$/u.test(attestation.taskId)
|
|
199
|
+
|| !lifecycleAuthorityFamilies.includes(attestation.family as typeof lifecycleAuthorityFamilies[number])
|
|
200
|
+
|| typeof attestation.artifact !== 'string' || !lifecycleAuthorityArtifact.test(attestation.artifact)
|
|
201
|
+
|| !Number.isSafeInteger(attestation.round) || (attestation.round as number) < 1
|
|
202
|
+
|| typeof attestation.controlGeneration !== 'string' || !attestation.controlGeneration
|
|
203
|
+
|| typeof attestation.controllerInstanceDigest !== 'string' || !/^[a-f0-9]{64}$/u.test(attestation.controllerInstanceDigest)
|
|
204
|
+
|| typeof attestation.sessionId !== 'string' || !attestation.sessionId
|
|
205
|
+
|| typeof attestation.turnId !== 'string' || !attestation.turnId
|
|
206
|
+
|| typeof attestation.toolUseId !== 'string' || !attestation.toolUseId
|
|
207
|
+
|| typeof attestation.buildIdentityDigest !== 'string' || !/^[a-f0-9]{64}$/u.test(attestation.buildIdentityDigest)
|
|
208
|
+
|| typeof attestation.hookDefinitionHash !== 'string' || !/^[a-f0-9]{64}$/u.test(attestation.hookDefinitionHash)
|
|
209
|
+
|| !Number.isSafeInteger(attestation.issuedAt) || !Number.isSafeInteger(attestation.expiresAt)
|
|
210
|
+
|| (attestation.expiresAt as number) <= (attestation.issuedAt as number)
|
|
211
|
+
|| (attestation.expiresAt as number) <= now) {
|
|
212
|
+
throw new Error('LIFECYCLE_AUTHORITY_ATTESTATION_INVALID');
|
|
213
|
+
}
|
|
214
|
+
return attestation as LifecycleRecoveryAttestationV1;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const authorityOperationMap = new WeakMap<object, Map<string, LifecycleRecoveryAttestationV1>>();
|
|
218
|
+
const authorityRecords = new Map<string, Readonly<{
|
|
219
|
+
store: ReturnType<typeof createCodexCapabilityStore>;
|
|
220
|
+
capabilityRef: string;
|
|
221
|
+
expected: Readonly<{
|
|
222
|
+
taskId: string;
|
|
223
|
+
hookDefinitionHash: string;
|
|
224
|
+
buildIdentity: LifecycleBuildIdentity;
|
|
225
|
+
controller: TaskControlControllerBinding;
|
|
226
|
+
}>;
|
|
227
|
+
}>>();
|
|
228
|
+
const consumedAuthorityPhases = new Set<string>();
|
|
229
|
+
|
|
230
|
+
export function issueLifecycleRecoveryAttestation(
|
|
231
|
+
input: LifecycleAuthorityRequestV1,
|
|
232
|
+
options: LifecycleAuthorityIssuerOptions = {}
|
|
233
|
+
): LifecycleAuthorityResponseV1 {
|
|
234
|
+
let request: LifecycleAuthorityRequestV1;
|
|
235
|
+
try { request = validateLifecycleAuthorityRequest(input); }
|
|
236
|
+
catch (error) {
|
|
237
|
+
return { version: 1, requestId: String((input as { requestId?: unknown })?.requestId ?? ''), operationId: String((input as { operationId?: unknown })?.operationId ?? ''), phase: (input as { phase?: LifecycleAuthorityPhase })?.phase ?? 'artifact.finalize-local', status: 'rejected', attestation: null, error: { code: 'LIFECYCLE_AUTHORITY_REQUEST_INVALID', message: error instanceof Error ? error.message : String(error) } };
|
|
238
|
+
}
|
|
239
|
+
const binding = options.controllerBinding ?? null;
|
|
240
|
+
if (!binding) return authorityRejected(request, 'LOCAL_EXECUTION_AUTHORITY_UNAVAILABLE', 'current controller authority is unavailable');
|
|
241
|
+
if (binding.controlGeneration !== request.expectedControlGeneration
|
|
242
|
+
|| binding.instanceDigest !== request.expectedControllerInstanceDigest) {
|
|
243
|
+
return authorityRejected(request, 'LIFECYCLE_AUTHORITY_CONTROLLER_MISMATCH', 'current controller binding does not match the request');
|
|
244
|
+
}
|
|
245
|
+
const buildIdentity = options.buildIdentity ?? computeLifecycleBuildIdentity(process.cwd());
|
|
246
|
+
if (lifecycleAuthorityBuildDigest(buildIdentity) !== request.expectedBuildIdentityDigest) {
|
|
247
|
+
return authorityRejected(request, 'LIFECYCLE_AUTHORITY_BUILD_MISMATCH', 'current lifecycle build does not match the request');
|
|
248
|
+
}
|
|
249
|
+
const store = options.capabilityStore ?? createCodexCapabilityStore();
|
|
250
|
+
const registry = options.operationAttestations ?? authorityOperationMap.get(store) ?? new Map<string, LifecycleRecoveryAttestationV1>();
|
|
251
|
+
authorityOperationMap.set(store, registry);
|
|
252
|
+
const key = `${request.requestId}\0${request.operationId}\0${request.phase}`;
|
|
253
|
+
const prior = registry.get(key);
|
|
254
|
+
if (prior) return { version: 1, requestId: request.requestId, operationId: request.operationId, phase: request.phase, status: 'already-completed', attestation: prior, error: null };
|
|
255
|
+
try {
|
|
256
|
+
const expected = {
|
|
257
|
+
taskId: request.taskId,
|
|
258
|
+
hookDefinitionHash: request.expectedHookDefinitionHash,
|
|
259
|
+
buildIdentity,
|
|
260
|
+
controller: binding
|
|
261
|
+
};
|
|
262
|
+
const record = store.reserveReference(request.authorityRef, request.operationId, expected);
|
|
263
|
+
if (!record.sessionId || !record.turnId || !record.toolUseId) throw new Error('CODEX_CAPABILITY_IDENTITY_INVALID: capability hook attestation is incomplete');
|
|
264
|
+
const now = (options.now ?? Date.now)();
|
|
265
|
+
const attestation = validateLifecycleRecoveryAttestation({
|
|
266
|
+
version: 1,
|
|
267
|
+
attestationId: crypto.randomUUID(),
|
|
268
|
+
authorityRefDigest: lifecycleAuthorityDigest(request.authorityRef),
|
|
269
|
+
operationId: request.operationId,
|
|
270
|
+
phase: request.phase,
|
|
271
|
+
requestId: request.requestId,
|
|
272
|
+
lifecycleRequestId: request.lifecycleRequestId,
|
|
273
|
+
taskId: request.taskId,
|
|
274
|
+
family: request.family,
|
|
275
|
+
artifact: request.artifact,
|
|
276
|
+
round: request.round,
|
|
277
|
+
controlGeneration: binding.controlGeneration,
|
|
278
|
+
controllerInstanceDigest: binding.instanceDigest,
|
|
279
|
+
sessionId: record.sessionId,
|
|
280
|
+
turnId: record.turnId,
|
|
281
|
+
toolUseId: record.toolUseId,
|
|
282
|
+
buildIdentityDigest: lifecycleAuthorityBuildDigest(buildIdentity),
|
|
283
|
+
hookDefinitionHash: request.expectedHookDefinitionHash,
|
|
284
|
+
issuedAt: now,
|
|
285
|
+
expiresAt: Math.min(record.expiresAt, now + 30_000)
|
|
286
|
+
}, now);
|
|
287
|
+
store.reserveRecoveryPhase(
|
|
288
|
+
request.authorityRef,
|
|
289
|
+
request.operationId,
|
|
290
|
+
request.phase,
|
|
291
|
+
request.requestId,
|
|
292
|
+
expected
|
|
293
|
+
);
|
|
294
|
+
registry.set(key, attestation);
|
|
295
|
+
authorityRecords.set(attestation.attestationId, { store, capabilityRef: request.authorityRef, expected });
|
|
296
|
+
return { version: 1, requestId: request.requestId, operationId: request.operationId, phase: request.phase, status: 'issued', attestation, error: null };
|
|
297
|
+
} catch (error) {
|
|
298
|
+
return authorityRejected(request, error instanceof Error && error.name.startsWith('CODEX_') ? error.name : 'LIFECYCLE_AUTHORITY_REJECTED', error instanceof Error ? error.message : String(error));
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
export function consumeLifecycleRecoveryAttestation(
|
|
303
|
+
value: LifecycleRecoveryAttestationV1,
|
|
304
|
+
now = Date.now()
|
|
305
|
+
): void {
|
|
306
|
+
const attestation = validateLifecycleRecoveryAttestation(value, now);
|
|
307
|
+
if (consumedAuthorityPhases.has(attestation.attestationId)) return;
|
|
308
|
+
const record = authorityRecords.get(attestation.attestationId);
|
|
309
|
+
if (!record) throw new Error('LIFECYCLE_AUTHORITY_ATTESTATION_UNKNOWN');
|
|
310
|
+
record.store.consumeRecoveryPhase(
|
|
311
|
+
record.capabilityRef,
|
|
312
|
+
attestation.operationId,
|
|
313
|
+
attestation.phase,
|
|
314
|
+
attestation.requestId,
|
|
315
|
+
record.expected
|
|
316
|
+
);
|
|
317
|
+
if (attestation.phase === 'task-event.completed') {
|
|
318
|
+
record.store.consumeReference(record.capabilityRef, attestation.operationId, record.expected);
|
|
319
|
+
}
|
|
320
|
+
consumedAuthorityPhases.add(attestation.attestationId);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
export function queryLifecycleRecoveryOperation(
|
|
324
|
+
operationId: string,
|
|
325
|
+
options: Readonly<{
|
|
326
|
+
repoRoot?: string;
|
|
327
|
+
capabilityStore?: ReturnType<typeof createCodexCapabilityStore>;
|
|
328
|
+
}> = {}
|
|
329
|
+
): LifecycleRecoveryOperationQueryV1 {
|
|
330
|
+
if (!/^[a-f0-9-]{16,64}$/u.test(operationId)) {
|
|
331
|
+
throw new Error('LIFECYCLE_RECOVERY_OPERATION_INVALID');
|
|
332
|
+
}
|
|
333
|
+
const store = options.capabilityStore ?? createCodexCapabilityStore();
|
|
334
|
+
const registry = authorityOperationMap.get(store);
|
|
335
|
+
const known = [...(registry?.values() ?? [])].filter((value) => value.operationId === operationId);
|
|
336
|
+
const intents = options.repoRoot ? findArtifactRepairIntentsByOperation(options.repoRoot, operationId) : [];
|
|
337
|
+
const capabilities = store.findByRecoveryOperation(operationId);
|
|
338
|
+
const phaseMap = new Map<string, LifecycleRecoveryOperationQueryV1['phases'][number]>();
|
|
339
|
+
for (const capability of capabilities) {
|
|
340
|
+
for (const phase of capability.recoveryPhases) {
|
|
341
|
+
phaseMap.set(`${phase.phase}\0${phase.requestId}`, {
|
|
342
|
+
phase: phase.phase,
|
|
343
|
+
requestId: phase.requestId,
|
|
344
|
+
attestationId: null,
|
|
345
|
+
state: phase.state === 'consumed' ? 'consumed' : 'observed'
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
for (const attestation of known) {
|
|
350
|
+
const key = `${attestation.phase}\0${attestation.requestId}`;
|
|
351
|
+
const durable = phaseMap.get(key);
|
|
352
|
+
phaseMap.set(key, {
|
|
353
|
+
phase: attestation.phase,
|
|
354
|
+
requestId: attestation.requestId,
|
|
355
|
+
attestationId: attestation.attestationId,
|
|
356
|
+
state: durable?.state === 'consumed' || consumedAuthorityPhases.has(attestation.attestationId)
|
|
357
|
+
? 'consumed' : 'issued'
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
for (const intent of intents) {
|
|
361
|
+
if (!intent.phase) continue;
|
|
362
|
+
const key = `${intent.phase}\0${intent.requestId}`;
|
|
363
|
+
if (phaseMap.has(key)) continue;
|
|
364
|
+
phaseMap.set(key, {
|
|
365
|
+
phase: intent.phase,
|
|
366
|
+
requestId: intent.requestId,
|
|
367
|
+
attestationId: null,
|
|
368
|
+
state: intent.state === 'consumed' ? 'consumed' : 'observed'
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
const capabilityState = capabilities.some((record) => record.recoveryState === 'consumed')
|
|
372
|
+
? 'consumed'
|
|
373
|
+
: capabilities.some((record) => record.recoveryState === 'reserved') ? 'reserved' : 'unknown';
|
|
374
|
+
const committed = capabilityState === 'consumed' || intents.some((intent) => intent.state === 'consumed');
|
|
375
|
+
return {
|
|
376
|
+
version: 1,
|
|
377
|
+
operationId,
|
|
378
|
+
status: committed ? 'committed' : capabilityState === 'reserved' || phaseMap.size > 0 ? 'in-progress' : 'unknown',
|
|
379
|
+
capabilityState,
|
|
380
|
+
phases: Object.freeze([...phaseMap.values()])
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
type LifecycleRecoveryCompensationOptions = Readonly<{
|
|
385
|
+
repoRoot: string;
|
|
386
|
+
controllerBinding: TaskControlControllerBinding;
|
|
387
|
+
capabilityStore?: ReturnType<typeof createCodexCapabilityStore>;
|
|
388
|
+
buildIdentity?: LifecycleBuildIdentity;
|
|
389
|
+
now?: () => number;
|
|
390
|
+
}>;
|
|
391
|
+
|
|
392
|
+
function lifecycleRecoveryCompensationError(code: string, message: string): Error {
|
|
393
|
+
const error = new Error(`${code}: ${message}`);
|
|
394
|
+
error.name = code;
|
|
395
|
+
return error;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function lifecycleRecoveryEventCommitted(
|
|
399
|
+
selector: LifecycleAuthorityRequestV1,
|
|
400
|
+
repoRoot: string
|
|
401
|
+
): boolean {
|
|
402
|
+
const resolved = resolveTaskRef(selector.taskId, { repoRoot });
|
|
403
|
+
if (!resolved.ok || resolved.taskId !== selector.taskId) return false;
|
|
404
|
+
let content: string;
|
|
405
|
+
try { content = fs.readFileSync(resolved.taskMdPath, 'utf8'); }
|
|
406
|
+
catch { return false; }
|
|
407
|
+
const section = locateActivityLog(content);
|
|
408
|
+
if (!section) return false;
|
|
409
|
+
const labels = { analysis: 'Analyze Task', plan: 'Plan Task', code: 'Code Task' } as const;
|
|
410
|
+
const prefix = `${labels[selector.family]} (Round ${selector.round}`;
|
|
411
|
+
return section.entries.some((entry) => {
|
|
412
|
+
if (entry.step.endsWith(' [started]') || !entry.step.startsWith(prefix)) return false;
|
|
413
|
+
const delimiter = entry.step[prefix.length];
|
|
414
|
+
return (delimiter === ',' || delimiter === ')') && entry.note.endsWith(`→ ${selector.artifact}`);
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
function lifecycleRecoveryIntentMatches(
|
|
419
|
+
intent: ArtifactRepairIntent,
|
|
420
|
+
selector: LifecycleAuthorityRequestV1
|
|
421
|
+
): boolean {
|
|
422
|
+
return intent.taskId === selector.taskId
|
|
423
|
+
&& intent.family === selector.family
|
|
424
|
+
&& intent.artifact === selector.artifact
|
|
425
|
+
&& (intent.state === 'commit-started' || intent.state === 'consumed');
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Completes only the durable tail of a committed task event after a process
|
|
430
|
+
* restart. The task event itself is the commit boundary and is never replayed.
|
|
431
|
+
*/
|
|
432
|
+
export function recoverLifecycleRecoveryOperation(
|
|
433
|
+
input: LifecycleAuthorityRequestV1,
|
|
434
|
+
options: LifecycleRecoveryCompensationOptions
|
|
435
|
+
): LifecycleRecoveryOperationQueryV1 {
|
|
436
|
+
const selector = validateLifecycleAuthorityRequest(input);
|
|
437
|
+
if (selector.phase !== 'task-event.completed') {
|
|
438
|
+
throw lifecycleRecoveryCompensationError('LIFECYCLE_RECOVERY_PHASE_UNSUPPORTED', 'only task-event.completed supports durable compensation');
|
|
439
|
+
}
|
|
440
|
+
const buildIdentity = options.buildIdentity ?? computeLifecycleBuildIdentity(options.repoRoot);
|
|
441
|
+
if (selector.expectedBuildIdentityDigest !== lifecycleAuthorityBuildDigest(buildIdentity)) {
|
|
442
|
+
throw lifecycleRecoveryCompensationError('LIFECYCLE_AUTHORITY_BUILD_MISMATCH', 'current lifecycle build does not match the recovery request');
|
|
443
|
+
}
|
|
444
|
+
if (selector.expectedControlGeneration !== options.controllerBinding.controlGeneration
|
|
445
|
+
|| selector.expectedControllerInstanceDigest !== options.controllerBinding.instanceDigest) {
|
|
446
|
+
throw lifecycleRecoveryCompensationError('LIFECYCLE_AUTHORITY_CONTROLLER_MISMATCH', 'current controller binding does not match the recovery request');
|
|
447
|
+
}
|
|
448
|
+
const store = options.capabilityStore ?? createCodexCapabilityStore();
|
|
449
|
+
const expected = {
|
|
450
|
+
taskId: selector.taskId,
|
|
451
|
+
hookDefinitionHash: selector.expectedHookDefinitionHash,
|
|
452
|
+
buildIdentity,
|
|
453
|
+
controller: options.controllerBinding
|
|
454
|
+
};
|
|
455
|
+
const capabilities = store.findByRecoveryOperation(selector.operationId);
|
|
456
|
+
if (capabilities.length !== 1 || capabilities[0]!.capabilityRefDigest !== lifecycleAuthorityDigest(selector.authorityRef)) {
|
|
457
|
+
throw lifecycleRecoveryCompensationError('LIFECYCLE_RECOVERY_PROVENANCE_INVALID', 'durable capability provenance does not identify this operation');
|
|
458
|
+
}
|
|
459
|
+
const capability = capabilities[0]!;
|
|
460
|
+
const build = verifyLifecycleBuildIdentity(capability.buildIdentity, buildIdentity);
|
|
461
|
+
const expiredReserved = capability.status === 'expired' && capability.recoveryState === 'reserved';
|
|
462
|
+
if (!build.ok
|
|
463
|
+
|| capability.taskId !== selector.taskId
|
|
464
|
+
|| capability.hookDefinitionHash !== selector.expectedHookDefinitionHash
|
|
465
|
+
|| capability.controller?.controlGeneration !== options.controllerBinding.controlGeneration
|
|
466
|
+
|| capability.controller?.instanceDigest !== options.controllerBinding.instanceDigest
|
|
467
|
+
|| capability.recoveryState === 'unreserved'
|
|
468
|
+
|| capability.recoveryOperationId !== selector.operationId
|
|
469
|
+
|| (!['attested', 'consumed'].includes(capability.status) && !expiredReserved)) {
|
|
470
|
+
throw lifecycleRecoveryCompensationError('LIFECYCLE_RECOVERY_PROVENANCE_INVALID', 'durable capability provenance does not match the recovery request');
|
|
471
|
+
}
|
|
472
|
+
const phase = capability.recoveryPhases.find((entry) => entry.phase === selector.phase);
|
|
473
|
+
if (!phase) {
|
|
474
|
+
throw lifecycleRecoveryCompensationError('LIFECYCLE_RECOVERY_PHASE_MISSING', 'durable task completion phase is missing');
|
|
475
|
+
}
|
|
476
|
+
const intents = findArtifactRepairIntentsByOperation(options.repoRoot, selector.operationId)
|
|
477
|
+
.filter((intent) => intent.taskId === selector.taskId
|
|
478
|
+
&& intent.family === selector.family
|
|
479
|
+
&& intent.artifact === selector.artifact);
|
|
480
|
+
if (intents.length !== 1) {
|
|
481
|
+
throw lifecycleRecoveryCompensationError('LIFECYCLE_RECOVERY_INTENT_INVALID', 'durable artifact repair intent does not identify this task event');
|
|
482
|
+
}
|
|
483
|
+
const intent = intents[0]!;
|
|
484
|
+
if (!lifecycleRecoveryIntentMatches(intent, selector)
|
|
485
|
+
|| intent.phase !== selector.phase
|
|
486
|
+
|| intent.requestId !== selector.lifecycleRequestId
|
|
487
|
+
|| intent.authorityDigest === null) {
|
|
488
|
+
throw lifecycleRecoveryCompensationError('LIFECYCLE_RECOVERY_INTENT_INVALID', 'durable artifact repair intent does not match the recovery request');
|
|
489
|
+
}
|
|
490
|
+
if (!lifecycleRecoveryEventCommitted(selector, options.repoRoot)) {
|
|
491
|
+
throw lifecycleRecoveryCompensationError('LIFECYCLE_RECOVERY_COMMIT_UNCONFIRMED', 'task completion is not durably present; compensation is not allowed');
|
|
492
|
+
}
|
|
493
|
+
if (capability.recoveryState === 'reserved') {
|
|
494
|
+
if (phase.state === 'issued') {
|
|
495
|
+
store.consumeRecoveryPhase(selector.authorityRef, selector.operationId, selector.phase, phase.requestId, expected, { allowExpiredReserved: true });
|
|
496
|
+
}
|
|
497
|
+
store.consumeReference(selector.authorityRef, selector.operationId, expected, { allowExpiredReserved: true });
|
|
498
|
+
} else if (phase.state !== 'consumed') {
|
|
499
|
+
throw lifecycleRecoveryCompensationError('LIFECYCLE_RECOVERY_STATE_INVALID', 'consumed capability has an unconsumed completion phase');
|
|
500
|
+
}
|
|
501
|
+
if (intent.state !== 'consumed') {
|
|
502
|
+
writeArtifactRepairIntent(options.repoRoot, {
|
|
503
|
+
...intent,
|
|
504
|
+
state: 'consumed',
|
|
505
|
+
updatedAt: (options.now ?? Date.now)()
|
|
506
|
+
}, { expected: intent });
|
|
507
|
+
}
|
|
508
|
+
return queryLifecycleRecoveryOperation(selector.operationId, { repoRoot: options.repoRoot, capabilityStore: store });
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
function authorityRejected(request: LifecycleAuthorityRequestV1, code: string, message: string): LifecycleAuthorityResponseV1 {
|
|
512
|
+
return { version: 1, requestId: request.requestId, operationId: request.operationId, phase: request.phase, status: 'rejected', attestation: null, error: { code, message } };
|
|
513
|
+
}
|
|
43
514
|
|
|
44
515
|
export type TaskControlControllerBinding = Readonly<{
|
|
45
516
|
instanceDigest: string;
|
|
@@ -53,6 +524,7 @@ export type TaskControlExecutionContext =
|
|
|
53
524
|
repoRoot: string;
|
|
54
525
|
runtimeDir?: string;
|
|
55
526
|
controllerBinding?: TaskControlControllerBinding | null;
|
|
527
|
+
lifecycleRecoveryAttestation?: LifecycleRecoveryAttestationV1 | null;
|
|
56
528
|
}>
|
|
57
529
|
| Readonly<{
|
|
58
530
|
source: 'sandbox-executor';
|
|
@@ -66,6 +538,7 @@ export type TaskControlExecutionContext =
|
|
|
66
538
|
requestId: string;
|
|
67
539
|
diagnosticLog?: OrchestrationDiagnosticLogger;
|
|
68
540
|
controllerBinding?: TaskControlControllerBinding | null;
|
|
541
|
+
lifecycleRecoveryAttestation?: LifecycleRecoveryAttestationV1 | null;
|
|
69
542
|
}>;
|
|
70
543
|
|
|
71
544
|
export type TaskControlOrchestrationIntent =
|
|
@@ -94,6 +567,14 @@ export type TaskControlOperation =
|
|
|
94
567
|
|
|
95
568
|
export type TaskControlDispatchResult = TaskLifecycleResult | TaskFinalizationResult | OrchestrationResult;
|
|
96
569
|
|
|
570
|
+
export function taskControlOperationKey(operation: TaskControlOperation): string {
|
|
571
|
+
if (operation.family === 'task-finalization') return 'complete';
|
|
572
|
+
if (operation.family === 'task-lifecycle') return operation.request.intent;
|
|
573
|
+
return operation.intent === 'route'
|
|
574
|
+
? operation.input && operation.input.completed === true ? 'route.clean-completion' : 'route.read'
|
|
575
|
+
: operation.intent;
|
|
576
|
+
}
|
|
577
|
+
|
|
97
578
|
export type TaskLifecycleControlRequest = TaskLifecycleRequest & Readonly<{
|
|
98
579
|
overrideTicket?: string;
|
|
99
580
|
overrideTarget?: string;
|
|
@@ -121,13 +602,18 @@ export function createDirectHostExecutionContext(params: Readonly<{
|
|
|
121
602
|
repoRoot: string;
|
|
122
603
|
runtimeDir?: string;
|
|
123
604
|
controllerBinding?: TaskControlControllerBinding | null;
|
|
605
|
+
lifecycleRecoveryAttestation?: LifecycleRecoveryAttestationV1 | null;
|
|
124
606
|
}>): TaskControlExecutionContext {
|
|
125
607
|
return {
|
|
126
608
|
source: 'direct-host',
|
|
127
609
|
mode: 'direct-host',
|
|
128
610
|
repoRoot: absolute('repoRoot', params.repoRoot),
|
|
129
611
|
...(params.runtimeDir === undefined ? {} : { runtimeDir: absolute('runtimeDir', params.runtimeDir) }),
|
|
130
|
-
...(params.controllerBinding === undefined ? {} : { controllerBinding: binding(params.controllerBinding) })
|
|
612
|
+
...(params.controllerBinding === undefined ? {} : { controllerBinding: binding(params.controllerBinding) }),
|
|
613
|
+
...(params.lifecycleRecoveryAttestation === undefined ? {} : {
|
|
614
|
+
lifecycleRecoveryAttestation: params.lifecycleRecoveryAttestation === null
|
|
615
|
+
? null : validateLifecycleRecoveryAttestation(params.lifecycleRecoveryAttestation)
|
|
616
|
+
})
|
|
131
617
|
};
|
|
132
618
|
}
|
|
133
619
|
|
|
@@ -141,6 +627,7 @@ export function createSandboxExecutorExecutionContext(params: Readonly<{
|
|
|
141
627
|
requestId: string;
|
|
142
628
|
diagnosticLog?: OrchestrationDiagnosticLogger;
|
|
143
629
|
controllerBinding?: TaskControlControllerBinding | null;
|
|
630
|
+
lifecycleRecoveryAttestation?: LifecycleRecoveryAttestationV1 | null;
|
|
144
631
|
}>): TaskControlExecutionContext {
|
|
145
632
|
if (!/^TASK-\d{8}-\d{6}$/u.test(params.taskId) || !params.generation || !params.requestId) {
|
|
146
633
|
invalidContext('sandbox binding is incomplete');
|
|
@@ -160,7 +647,11 @@ export function createSandboxExecutorExecutionContext(params: Readonly<{
|
|
|
160
647
|
manifestPath: absolute('manifestPath', params.manifestPath),
|
|
161
648
|
requestId: params.requestId,
|
|
162
649
|
...(params.diagnosticLog === undefined ? {} : { diagnosticLog: params.diagnosticLog }),
|
|
163
|
-
...(params.controllerBinding === undefined ? {} : { controllerBinding: binding(params.controllerBinding) })
|
|
650
|
+
...(params.controllerBinding === undefined ? {} : { controllerBinding: binding(params.controllerBinding) }),
|
|
651
|
+
...(params.lifecycleRecoveryAttestation === undefined ? {} : {
|
|
652
|
+
lifecycleRecoveryAttestation: params.lifecycleRecoveryAttestation === null
|
|
653
|
+
? null : validateLifecycleRecoveryAttestation(params.lifecycleRecoveryAttestation)
|
|
654
|
+
})
|
|
164
655
|
};
|
|
165
656
|
}
|
|
166
657
|
|
|
@@ -170,6 +661,7 @@ export function assertTaskControlExecutionContext(context: TaskControlExecutionC
|
|
|
170
661
|
absolute('repoRoot', context.repoRoot);
|
|
171
662
|
if (context.runtimeDir !== undefined) absolute('runtimeDir', context.runtimeDir);
|
|
172
663
|
binding(context.controllerBinding);
|
|
664
|
+
if (context.lifecycleRecoveryAttestation) validateLifecycleRecoveryAttestation(context.lifecycleRecoveryAttestation);
|
|
173
665
|
return;
|
|
174
666
|
}
|
|
175
667
|
if (context.mode !== 'task-bound-sandbox' || !/^TASK-\d{8}-\d{6}$/u.test(context.taskId)
|
|
@@ -184,6 +676,7 @@ export function assertTaskControlExecutionContext(context: TaskControlExecutionC
|
|
|
184
676
|
invalidContext('runtimeDir is not bound to the manifest control root');
|
|
185
677
|
}
|
|
186
678
|
binding(context.controllerBinding);
|
|
679
|
+
if (context.lifecycleRecoveryAttestation) validateLifecycleRecoveryAttestation(context.lifecycleRecoveryAttestation);
|
|
187
680
|
}
|
|
188
681
|
|
|
189
682
|
function operationTaskId(context: TaskControlExecutionContext, taskRef: string): void {
|
|
@@ -284,12 +777,16 @@ function orchestration(
|
|
|
284
777
|
client: input.client as AgentClientId,
|
|
285
778
|
requestedModel: input.requestedModel as string | undefined,
|
|
286
779
|
requestedReasoningEffort: input.requestedReasoningEffort as string | undefined,
|
|
287
|
-
...(input.
|
|
780
|
+
...(input.capabilityRef === undefined ? {} : { capabilityRef: input.capabilityRef as string }),
|
|
781
|
+
...(context.lifecycleRecoveryAttestation === undefined
|
|
782
|
+
? {}
|
|
783
|
+
: { lifecycleRecoveryAttestation: context.lifecycleRecoveryAttestation })
|
|
288
784
|
};
|
|
289
785
|
if (prepareInput.client === 'codex') {
|
|
290
786
|
return prepareCodexOrchestrationDelegation(operation.taskRef, prepareInput, {
|
|
291
787
|
repoRoot: context.repoRoot,
|
|
292
788
|
orchestrationOptions: options,
|
|
789
|
+
deferLifecycleRecoveryConsumption: context.source === 'sandbox-executor',
|
|
293
790
|
...(context.controllerBinding ? { controllerBinding: context.controllerBinding } : {})
|
|
294
791
|
});
|
|
295
792
|
}
|
|
@@ -403,7 +900,7 @@ const FINALIZATION_FLAGS = new Set(['--agent']);
|
|
|
403
900
|
const ORCHESTRATION_FLAGS = new Set([
|
|
404
901
|
'--agent', '--max-steps', '--executor-model', '--executor-reasoning-effort', '--reviewer-model',
|
|
405
902
|
'--reviewer-reasoning-effort', '--client', '--requested-model', '--requested-reasoning-effort',
|
|
406
|
-
'--capability-
|
|
903
|
+
'--capability-ref', '--parent-id', '--before-fingerprint', '--stage', '--round', '--artifact', '--role',
|
|
407
904
|
'--native-agent', '--child-id', '--spawn-mode', '--actual-model', '--actual-reasoning-effort',
|
|
408
905
|
'--model-fallback-reason', '--reasoning-effort-fallback-reason', '--exit-code', '--after-fingerprint',
|
|
409
906
|
'--changed-paths', '--code', '--message', '--recoverable', '--git-worktree-root'
|
|
@@ -519,7 +1016,7 @@ export function parseTaskControlOperation(
|
|
|
519
1016
|
if (parsedIntent === 'prepare') {
|
|
520
1017
|
input.requestedModel = value(values, '--requested-model');
|
|
521
1018
|
input.requestedReasoningEffort = value(values, '--requested-reasoning-effort');
|
|
522
|
-
input.
|
|
1019
|
+
input.capabilityRef = value(values, '--capability-ref');
|
|
523
1020
|
}
|
|
524
1021
|
if (parsedIntent === 'await-activation') {
|
|
525
1022
|
required(values, ['--stage', '--round', '--artifact', '--role']);
|