@fitlab-ai/agent-infra 0.11.1 → 0.11.3
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/dist/bin/internal-cli.js +7 -2
- package/dist/lib/agent-clients/adapter.js +3 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/recovery.js +234 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +8 -35
- package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +1 -34
- package/dist/lib/agent-clients/adapters/codex.js +10 -0
- package/dist/lib/internal/cli-route-inventory.js +1 -1
- package/dist/lib/internal/manual-validation.js +22 -27
- package/dist/lib/internal/platform-comment.js +59 -0
- package/dist/lib/internal/sandbox-control.js +2 -171
- package/dist/lib/internal/task-artifact.js +1 -1
- package/dist/lib/internal/task-lifecycle.js +1 -1
- package/dist/lib/internal/task-operation-registry.js +7 -2
- package/dist/lib/internal/task-review.js +5 -5
- package/dist/lib/internal/task-validate.js +0 -6
- package/dist/lib/platform/comment-safety.js +7 -1
- package/dist/lib/platform/github-provider.js +13 -3
- package/dist/lib/platform/issue-comments.js +93 -15
- package/dist/lib/platform/pr-summary.js +7 -2
- package/dist/lib/platform/provider-contract.d.ts +2 -0
- package/dist/lib/platform/provider-validation.js +7 -2
- package/dist/lib/platform/verification-sync.js +19 -2
- package/dist/lib/sandbox/control/client.js +3 -44
- package/dist/lib/sandbox/control/executor.js +14 -117
- package/dist/lib/sandbox/control/finalization-response.js +8 -2
- package/dist/lib/sandbox/control/protocol.js +5 -64
- package/dist/lib/sandbox/control/server.js +29 -206
- package/dist/lib/sandbox/control/state.js +1 -7
- package/dist/lib/sandbox/control/task-view.js +2 -1
- package/dist/lib/task/artifact-command.js +6 -6
- package/dist/lib/task/codex-orchestration.js +10 -208
- package/dist/lib/task/commit-operation.js +3 -18
- package/dist/lib/task/control-authority.js +35 -44
- package/dist/lib/task/control-recovery.js +3 -42
- package/dist/lib/task/delegation-receipts.js +16 -78
- package/dist/lib/task/events.js +89 -237
- package/dist/lib/task/finalization-artifacts.js +1 -3
- package/dist/lib/task/finalization-state.js +2 -1
- package/dist/lib/task/finalization.js +208 -57
- package/dist/lib/task/lifecycle-execution.js +2 -11
- package/dist/lib/task/lifecycle.js +1 -1
- package/dist/lib/task/local-artifact-finalization.js +25 -242
- package/dist/lib/task/manual-validation-receipt.js +8 -3
- package/dist/lib/task/orchestration.js +12 -24
- package/dist/lib/task/review-command.js +0 -1
- package/dist/lib/task/review-finalization.js +46 -211
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-clients/adapter.ts +46 -0
- package/lib/agent-clients/adapters/codex-lifecycle/recovery.ts +297 -0
- package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +13 -33
- package/lib/agent-clients/adapters/codex-lifecycle/store.ts +1 -44
- package/lib/agent-clients/adapters/codex.ts +10 -0
- package/lib/internal/cli-route-inventory.ts +1 -1
- package/lib/internal/manual-validation.ts +22 -26
- package/lib/internal/platform-comment.ts +38 -0
- package/lib/internal/sandbox-control.ts +1 -185
- package/lib/internal/task-artifact.ts +1 -1
- package/lib/internal/task-lifecycle.ts +2 -2
- package/lib/internal/task-operation-registry.ts +8 -4
- package/lib/internal/task-review.ts +5 -5
- package/lib/internal/task-validate.ts +0 -5
- package/lib/platform/comment-safety.ts +8 -0
- package/lib/platform/github-provider.ts +11 -2
- package/lib/platform/issue-comments.ts +102 -17
- package/lib/platform/pr-summary.ts +10 -2
- package/lib/platform/provider-contract.ts +2 -0
- package/lib/platform/provider-validation.ts +6 -2
- package/lib/platform/verification-sync.ts +19 -2
- package/lib/sandbox/control/client.ts +5 -68
- package/lib/sandbox/control/executor.ts +14 -125
- package/lib/sandbox/control/finalization-response.ts +8 -2
- package/lib/sandbox/control/protocol.ts +7 -83
- package/lib/sandbox/control/server.ts +30 -220
- package/lib/sandbox/control/state.ts +1 -9
- package/lib/sandbox/control/task-view.ts +2 -1
- package/lib/task/artifact-command.ts +6 -7
- package/lib/task/codex-orchestration.ts +8 -241
- package/lib/task/commit-operation.ts +3 -23
- package/lib/task/control-authority.ts +39 -55
- package/lib/task/control-recovery.ts +3 -43
- package/lib/task/delegation-receipts.ts +14 -81
- package/lib/task/events.ts +106 -255
- package/lib/task/finalization-artifacts.ts +1 -3
- package/lib/task/finalization-state.ts +2 -1
- package/lib/task/finalization.ts +221 -65
- package/lib/task/lifecycle-execution.ts +10 -21
- package/lib/task/lifecycle.ts +2 -2
- package/lib/task/local-artifact-finalization.ts +23 -281
- package/lib/task/manual-validation-receipt.ts +8 -2
- package/lib/task/orchestration.ts +30 -53
- package/lib/task/review-command.ts +0 -1
- package/lib/task/review-finalization.ts +45 -193
- package/package.json +2 -2
- package/templates/.agents/rules/lifecycle-orchestration.en.md +23 -9
- package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +23 -9
- package/templates/.agents/rules/local-artifact-repair.en.md +14 -60
- package/templates/.agents/rules/local-artifact-repair.zh-CN.md +13 -60
- package/templates/.agents/skills/analyze-task/SKILL.en.md +3 -3
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/code-task/SKILL.en.md +3 -3
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +2 -2
- package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/complete-task/SKILL.en.md +7 -8
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +7 -8
- package/templates/.agents/skills/plan-task/SKILL.en.md +3 -3
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/review-analysis/SKILL.en.md +2 -2
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/review-code/SKILL.en.md +3 -3
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/review-plan/SKILL.en.md +2 -2
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/run-manual-validation/SKILL.en.md +2 -2
- package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/run-task/SKILL.en.md +10 -9
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +10 -9
- package/templates/.agents/skills/run-task/reference/host-validation.en.md +2 -2
- package/templates/.agents/skills/run-task/reference/host-validation.zh-CN.md +2 -2
- package/dist/lib/sandbox/control/task-workflow.js +0 -74
- package/dist/lib/sandbox/control/workflow-executor.js +0 -157
- package/dist/lib/task/lifecycle-recovery.js +0 -549
- package/dist/lib/task/recovery-warning.js +0 -19
- package/dist/lib/task/workflow-command.js +0 -18
- package/dist/lib/task/workflow-dispatch.js +0 -161
- package/lib/sandbox/control/task-workflow.ts +0 -95
- package/lib/sandbox/control/workflow-executor.ts +0 -183
- package/lib/task/lifecycle-recovery.ts +0 -691
- package/lib/task/recovery-warning.ts +0 -21
- package/lib/task/workflow-command.ts +0 -19
- package/lib/task/workflow-dispatch.ts +0 -171
|
@@ -1,57 +1,18 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
2
3
|
import { inspectDecisionDetailDuplicates } from './decision-details.js';
|
|
3
4
|
import { scanVisibleMarkdown } from './markdown.js';
|
|
4
5
|
import { parseArtifactName } from './artifact-name.js';
|
|
5
|
-
import { hasOpenArtifactRound,
|
|
6
|
+
import { hasOpenArtifactRound, } from './artifact-lifecycle.js';
|
|
6
7
|
import { resolveTaskRef } from './resolve-ref.js';
|
|
7
8
|
import { expectedQualificationRelations, validateQualificationAudit } from './qualification-audit.js';
|
|
8
9
|
import { canonicalSemanticDigest, inspectArtifactPatterns, inspectArtifactStructure, sha256Content } from './artifact-operations.js';
|
|
9
10
|
import { getArtifactSchema } from './artifact-schema.js';
|
|
10
|
-
import { readArtifactRecoveryIntent } from './artifact-repair-intent.js';
|
|
11
|
-
import { beginArtifactRecovery, commitArtifactRecovery, consumeArtifactRecovery, prepareArtifactRecoveryFinal, prepareArtifactRecoveryCommit, recordArtifactRecoveryPassed, reconcileArtifactRecovery, recoveryContextFromIntent, stageArtifactCandidate } from './artifact-recovery.js';
|
|
12
|
-
import { consumeLifecycleRecoveryAttestation, lifecycleRecoveryAttestationDigest, validateLifecycleRecoveryAttestation } from './control-authority.js';
|
|
13
11
|
const LOCAL_ARTIFACT_REQUIRED_SECTIONS = {
|
|
14
12
|
analysis: getArtifactSchema('analysis').sections.map((section) => section.headings.zh),
|
|
15
13
|
plan: getArtifactSchema('plan').sections.map((section) => section.headings.zh),
|
|
16
14
|
code: getArtifactSchema('code').sections.map((section) => section.headings.zh)
|
|
17
15
|
};
|
|
18
|
-
function authorityDigest(authority) {
|
|
19
|
-
return lifecycleRecoveryAttestationDigest(authority);
|
|
20
|
-
}
|
|
21
|
-
function validFinalizerAuthority(authority, request, taskId) {
|
|
22
|
-
if (!authority)
|
|
23
|
-
return false;
|
|
24
|
-
try {
|
|
25
|
-
validateLifecycleRecoveryAttestation(authority);
|
|
26
|
-
}
|
|
27
|
-
catch {
|
|
28
|
-
return false;
|
|
29
|
-
}
|
|
30
|
-
return authority.version === 1
|
|
31
|
-
&& authority.phase === 'artifact.finalize-local'
|
|
32
|
-
&& authority.taskId === taskId
|
|
33
|
-
&& authority.family === request.family
|
|
34
|
-
&& authority.artifact === request.artifact
|
|
35
|
-
&& authority.round === (parseArtifactName(request.artifact)?.round ?? 0)
|
|
36
|
-
&& authority.requestId.length > 0
|
|
37
|
-
&& authority.operationId.length > 0;
|
|
38
|
-
}
|
|
39
|
-
function recoveryInfo(context) {
|
|
40
|
-
return {
|
|
41
|
-
recoveryId: context.recoveryId,
|
|
42
|
-
candidatePath: context.stagingPath,
|
|
43
|
-
baselineSha256: context.baselineSha256,
|
|
44
|
-
baselineSemanticDigest: context.baselineSemanticDigest
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
function consumeLocalArtifactFinalizationIntent(repoRoot, intent, options = {}) {
|
|
48
|
-
if (intent.state === 'consumed')
|
|
49
|
-
return intent;
|
|
50
|
-
const resolved = resolveTaskRef(intent.taskId, { repoRoot });
|
|
51
|
-
if (!resolved.ok)
|
|
52
|
-
throw new Error(resolved.message);
|
|
53
|
-
return consumeArtifactRecovery(recoveryContextFromIntent(repoRoot, resolved.taskDir, intent), options);
|
|
54
|
-
}
|
|
55
16
|
function localDiagnosticCode(code) {
|
|
56
17
|
const map = {
|
|
57
18
|
ARTIFACT_EMPTY: 'LOCAL_ARTIFACT_EMPTY',
|
|
@@ -127,19 +88,7 @@ function failedFinalization(request, error, extra = {}) {
|
|
|
127
88
|
diagnostics: [], error, ...extra
|
|
128
89
|
};
|
|
129
90
|
}
|
|
130
|
-
function
|
|
131
|
-
const round = parseArtifactName(request.artifact)?.round ?? 0;
|
|
132
|
-
return {
|
|
133
|
-
taskId,
|
|
134
|
-
family: request.family,
|
|
135
|
-
artifact: request.artifact,
|
|
136
|
-
round,
|
|
137
|
-
requestId: authority?.lifecycleRequestId ?? `local-finalize:${taskId}:${request.family}:${round}`,
|
|
138
|
-
phase: authority?.phase ?? 'artifact.finalize-local',
|
|
139
|
-
authorityDigest: authority ? authorityDigest(authority) : null
|
|
140
|
-
};
|
|
141
|
-
}
|
|
142
|
-
function prepareLocalArtifact(request, candidate, authority, preflightOnly = false) {
|
|
91
|
+
function prepareLocalArtifact(request, candidate) {
|
|
143
92
|
const failed = (code, message, extra = {}) => ({
|
|
144
93
|
result: failedFinalization(request, { code, message }, extra), content: candidate ?? ''
|
|
145
94
|
});
|
|
@@ -149,39 +98,20 @@ function prepareLocalArtifact(request, candidate, authority, preflightOnly = fal
|
|
|
149
98
|
const parsed = parseArtifactName(request.artifact);
|
|
150
99
|
if (!parsed || parsed.family !== request.family)
|
|
151
100
|
return failed('ARTIFACT_IDENTITY_INVALID', `artifact '${request.artifact}' does not match ${request.family}`);
|
|
152
|
-
if (authority && !validFinalizerAuthority(authority, request, resolved.taskId)) {
|
|
153
|
-
return failed('LOCAL_EXECUTION_AUTHORITY_INVALID', 'finalizer authority does not match the artifact tuple');
|
|
154
|
-
}
|
|
155
101
|
let taskContent;
|
|
156
102
|
let content;
|
|
157
|
-
let recovery;
|
|
158
|
-
let recoveryState;
|
|
159
103
|
try {
|
|
160
104
|
taskContent = fs.readFileSync(resolved.taskMdPath, 'utf8');
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
if (!['awaiting-preflight-recovery', 'preflight-ready', 'preflight-commit-started', 'preflight-passed', 'full-finalizer-ready', 'commit-started'].includes(intent.state))
|
|
166
|
-
return failed('LOCAL_RECOVERY_PROVENANCE_CONFLICT', `recovery journal is in '${intent.state}' state`);
|
|
167
|
-
recoveryState = intent.state;
|
|
168
|
-
recovery = recoveryContextFromIntent(resolved.repoRoot, resolved.taskDir, intent);
|
|
169
|
-
content = fs.existsSync(recovery.stagingPath)
|
|
170
|
-
? fs.readFileSync(recovery.stagingPath, 'utf8')
|
|
171
|
-
: fs.readFileSync(recovery.formalPath, 'utf8');
|
|
172
|
-
}
|
|
173
|
-
else {
|
|
174
|
-
const validated = validateCompletedArtifact(resolved.taskDir, request.family, request.artifact, parsed.round);
|
|
175
|
-
if (!validated.ok)
|
|
176
|
-
return failed(validated.error.code, validated.error.message);
|
|
177
|
-
content = candidate ?? fs.readFileSync(validated.artifact.path, 'utf8');
|
|
178
|
-
}
|
|
105
|
+
const artifactPath = path.join(resolved.taskDir, request.artifact);
|
|
106
|
+
if (!fs.statSync(artifactPath).isFile())
|
|
107
|
+
return failed('ARTIFACT_NOT_REGULAR', `artifact '${request.artifact}' is not a regular file`);
|
|
108
|
+
content = candidate ?? fs.readFileSync(artifactPath, 'utf8');
|
|
179
109
|
}
|
|
180
110
|
catch (error) {
|
|
181
111
|
return failed('ARTIFACT_NOT_READABLE', String(error));
|
|
182
112
|
}
|
|
183
|
-
if (resolved.state !== 'active'
|
|
184
|
-
return failed('ARTIFACT_IDENTITY_INVALID', '
|
|
113
|
+
if (resolved.state !== 'active') {
|
|
114
|
+
return failed('ARTIFACT_IDENTITY_INVALID', 'artifact finalization requires an active task');
|
|
185
115
|
}
|
|
186
116
|
const validation = validateLocalArtifact(content, {
|
|
187
117
|
family: request.family, requiredSections: request.requiredSections, taskContent, artifact: request.artifact
|
|
@@ -194,173 +124,26 @@ function prepareLocalArtifact(request, candidate, authority, preflightOnly = fal
|
|
|
194
124
|
taskId: resolved.taskId, taskDir: resolved.taskDir, artifactSha256,
|
|
195
125
|
semanticDigest: validation.semanticDigest, diagnostics: validation.diagnostics
|
|
196
126
|
});
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
content, repoRoot: resolved.repoRoot, recovery, authority, lockAlreadyHeld: request.lockAlreadyHeld
|
|
207
|
-
};
|
|
208
|
-
}
|
|
209
|
-
const existing = readArtifactRecoveryIntent(resolved.repoRoot, resolved.taskId, request.family, request.artifact);
|
|
210
|
-
if (existing?.state === 'passed' || existing?.state === 'consumed') {
|
|
211
|
-
if (existing.state === 'consumed' && (existing.finalArtifactSha256 !== artifactSha256 || existing.finalSemanticDigest !== validation.semanticDigest)) {
|
|
212
|
-
return { ...failed('LOCAL_RECOVERY_PROVENANCE_CONFLICT', 'formal artifact does not match its completed recovery journal'), content, repoRoot: resolved.repoRoot, authority, lockAlreadyHeld: request.lockAlreadyHeld };
|
|
213
|
-
}
|
|
214
|
-
if (existing.finalArtifactSha256 === artifactSha256 && existing.finalSemanticDigest === validation.semanticDigest) {
|
|
215
|
-
const context = recoveryContextFromIntent(resolved.repoRoot, resolved.taskDir, existing);
|
|
216
|
-
return {
|
|
217
|
-
result: { ...result, status: 'passed', error: null, recovery: recoveryInfo(context) },
|
|
218
|
-
content, repoRoot: resolved.repoRoot, recovery: context, authority, lockAlreadyHeld: request.lockAlreadyHeld
|
|
219
|
-
};
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
if (existing && ['preflight-ready', 'preflight-commit-started', 'preflight-passed', 'full-finalizer-ready'].includes(existing.state)) {
|
|
223
|
-
const context = recoveryContextFromIntent(resolved.repoRoot, resolved.taskDir, existing);
|
|
224
|
-
return {
|
|
225
|
-
result: { ...result, status: 'passed', changed: false, error: null, recovery: recoveryInfo(context) },
|
|
226
|
-
content, repoRoot: resolved.repoRoot, recovery: context, authority, lockAlreadyHeld: request.lockAlreadyHeld
|
|
227
|
-
};
|
|
228
|
-
}
|
|
229
|
-
if (existing?.state === 'commit-started') {
|
|
230
|
-
if (existing.finalArtifactSha256 !== artifactSha256 || existing.finalSemanticDigest !== validation.semanticDigest) {
|
|
231
|
-
return { ...failed('LOCAL_RECOVERY_PROVENANCE_CONFLICT', 'formal artifact does not match the interrupted recovery journal'), content, repoRoot: resolved.repoRoot, authority, lockAlreadyHeld: request.lockAlreadyHeld };
|
|
232
|
-
}
|
|
233
|
-
const context = recoveryContextFromIntent(resolved.repoRoot, resolved.taskDir, existing);
|
|
234
|
-
const reconciled = reconcileArtifactRecovery(context, {
|
|
235
|
-
lockAlreadyHeld: request.lockAlreadyHeld,
|
|
236
|
-
validateFinal: () => validation.semanticDigest === existing.finalSemanticDigest
|
|
237
|
-
});
|
|
238
|
-
if (reconciled.status !== 'passed') {
|
|
239
|
-
return { ...failed('LOCAL_RECOVERY_PROVENANCE_CONFLICT', `interrupted recovery could not be reconciled: ${reconciled.status}`), content, repoRoot: resolved.repoRoot, recovery: context, authority, lockAlreadyHeld: request.lockAlreadyHeld };
|
|
240
|
-
}
|
|
241
|
-
return {
|
|
242
|
-
result: { ...result, status: 'passed', changed: false, error: null, recovery: recoveryInfo(context) },
|
|
243
|
-
content, repoRoot: resolved.repoRoot, recovery: context, authority, lockAlreadyHeld: request.lockAlreadyHeld
|
|
244
|
-
};
|
|
245
|
-
}
|
|
246
|
-
try {
|
|
247
|
-
if (validation.ok) {
|
|
248
|
-
if (preflightOnly) {
|
|
249
|
-
const context = beginArtifactRecovery(tupleFor(request, resolved.taskId, authority), Buffer.from(content, 'utf8'), {
|
|
250
|
-
repoRoot: resolved.repoRoot, taskDir: resolved.taskDir,
|
|
251
|
-
...(authority ? { recoveryId: authority.operationId } : {}), lockAlreadyHeld: request.lockAlreadyHeld
|
|
252
|
-
});
|
|
253
|
-
const staged = stageArtifactCandidate(context, Buffer.from(content, 'utf8'), { lockAlreadyHeld: request.lockAlreadyHeld });
|
|
254
|
-
prepareArtifactRecoveryCommit(context, staged.candidateSha256, staged.semanticDigest, { lockAlreadyHeld: request.lockAlreadyHeld });
|
|
255
|
-
return { result: { ...result, status: 'passed', error: null, recovery: recoveryInfo(context) }, content, repoRoot: resolved.repoRoot, recovery: context, authority, lockAlreadyHeld: request.lockAlreadyHeld };
|
|
256
|
-
}
|
|
257
|
-
const context = recordArtifactRecoveryPassed(tupleFor(request, resolved.taskId, authority), {
|
|
258
|
-
repoRoot: resolved.repoRoot,
|
|
259
|
-
taskDir: resolved.taskDir,
|
|
260
|
-
expectedFinalSha256: artifactSha256,
|
|
261
|
-
expectedFinalSemanticDigest: validation.semanticDigest,
|
|
262
|
-
...(authority ? { recoveryId: authority.operationId } : {}),
|
|
263
|
-
lockAlreadyHeld: request.lockAlreadyHeld
|
|
264
|
-
});
|
|
265
|
-
recovery = context;
|
|
266
|
-
return { result: { ...result, status: 'passed', changed: false, error: null, recovery: recoveryInfo(context) }, content, repoRoot: resolved.repoRoot, recovery, authority, lockAlreadyHeld: request.lockAlreadyHeld };
|
|
267
|
-
}
|
|
268
|
-
const context = beginArtifactRecovery(tupleFor(request, resolved.taskId, authority), Buffer.from(content, 'utf8'), {
|
|
269
|
-
repoRoot: resolved.repoRoot,
|
|
270
|
-
taskDir: resolved.taskDir,
|
|
271
|
-
...(authority ? { recoveryId: authority.operationId } : {}),
|
|
272
|
-
lockAlreadyHeld: request.lockAlreadyHeld
|
|
273
|
-
});
|
|
274
|
-
recovery = context;
|
|
275
|
-
return {
|
|
276
|
-
result: { ...result, recovery: recoveryInfo(context) },
|
|
277
|
-
content, repoRoot: resolved.repoRoot, recovery, authority, lockAlreadyHeld: request.lockAlreadyHeld
|
|
278
|
-
};
|
|
279
|
-
}
|
|
280
|
-
catch (error) {
|
|
281
|
-
return { ...failed('LOCAL_RECOVERY_PROVENANCE_CONFLICT', String(error)), content, repoRoot: resolved.repoRoot, authority, lockAlreadyHeld: request.lockAlreadyHeld };
|
|
282
|
-
}
|
|
127
|
+
// Trusted local operators edit the canonical artifact directly. A
|
|
128
|
+
// finalization result is a fresh observation of those bytes, never a
|
|
129
|
+
// capability minted by an earlier recovery attempt or generation.
|
|
130
|
+
return {
|
|
131
|
+
result: validation.ok ? { ...result, status: 'passed', error: null } : result,
|
|
132
|
+
content,
|
|
133
|
+
repoRoot: resolved.repoRoot,
|
|
134
|
+
lockAlreadyHeld: request.lockAlreadyHeld
|
|
135
|
+
};
|
|
283
136
|
}
|
|
284
|
-
function commitLocalArtifactProvenance(prepared
|
|
285
|
-
|
|
286
|
-
return prepared.result;
|
|
287
|
-
if (prepared.result.status === 'failed')
|
|
288
|
-
return prepared.result;
|
|
289
|
-
try {
|
|
290
|
-
const intent = readArtifactRecoveryIntent(prepared.repoRoot, prepared.recovery.taskId, prepared.recovery.family, prepared.recovery.artifact);
|
|
291
|
-
if (!intent)
|
|
292
|
-
throw new Error('ARTIFACT_RECOVERY_INTENT_MISSING');
|
|
293
|
-
let committed;
|
|
294
|
-
if (intent.state === 'passed' || intent.state === 'consumed') {
|
|
295
|
-
return {
|
|
296
|
-
...prepared.result,
|
|
297
|
-
changed: false,
|
|
298
|
-
artifactSha256: intent.finalArtifactSha256,
|
|
299
|
-
semanticDigest: intent.finalSemanticDigest,
|
|
300
|
-
error: null
|
|
301
|
-
};
|
|
302
|
-
}
|
|
303
|
-
else if (intent.state === 'commit-started') {
|
|
304
|
-
const reconciled = reconcileArtifactRecovery(prepared.recovery, {
|
|
305
|
-
lockAlreadyHeld: prepared.lockAlreadyHeld,
|
|
306
|
-
afterPublish: options.afterPublish
|
|
307
|
-
});
|
|
308
|
-
committed = reconciled.intent;
|
|
309
|
-
}
|
|
310
|
-
else if (intent.state === 'awaiting-preflight-recovery') {
|
|
311
|
-
const staged = stageArtifactCandidate(prepared.recovery, Buffer.from(prepared.content, 'utf8'), { lockAlreadyHeld: prepared.lockAlreadyHeld });
|
|
312
|
-
prepareArtifactRecoveryCommit(prepared.recovery, staged.candidateSha256, staged.semanticDigest, { lockAlreadyHeld: prepared.lockAlreadyHeld });
|
|
313
|
-
committed = commitArtifactRecovery(prepared.recovery, { lockAlreadyHeld: prepared.lockAlreadyHeld });
|
|
314
|
-
}
|
|
315
|
-
if (intent.state === 'preflight-ready' || intent.state === 'preflight-commit-started') {
|
|
316
|
-
committed = commitArtifactRecovery(prepared.recovery, { lockAlreadyHeld: prepared.lockAlreadyHeld });
|
|
317
|
-
}
|
|
318
|
-
if (committed?.state === 'preflight-passed' || intent.state === 'preflight-passed') {
|
|
319
|
-
prepareArtifactRecoveryFinal(prepared.recovery, Buffer.from(prepared.content, 'utf8'), { lockAlreadyHeld: prepared.lockAlreadyHeld });
|
|
320
|
-
committed = commitArtifactRecovery(prepared.recovery, { lockAlreadyHeld: prepared.lockAlreadyHeld, afterPublish: options.afterPublish });
|
|
321
|
-
}
|
|
322
|
-
else if (intent.state === 'full-finalizer-ready') {
|
|
323
|
-
committed = commitArtifactRecovery(prepared.recovery, { lockAlreadyHeld: prepared.lockAlreadyHeld, afterPublish: options.afterPublish });
|
|
324
|
-
}
|
|
325
|
-
if (!committed || committed.state !== 'passed')
|
|
326
|
-
throw new Error(`ARTIFACT_RECOVERY_STATE_INVALID: commit ended in '${committed?.state ?? intent.state}'`);
|
|
327
|
-
return {
|
|
328
|
-
...prepared.result,
|
|
329
|
-
status: 'passed',
|
|
330
|
-
changed: false,
|
|
331
|
-
artifactSha256: committed.finalArtifactSha256,
|
|
332
|
-
semanticDigest: committed.finalSemanticDigest,
|
|
333
|
-
error: null
|
|
334
|
-
};
|
|
335
|
-
}
|
|
336
|
-
catch (error) {
|
|
337
|
-
if (error instanceof Error && error.message.startsWith('TASK_WORKFLOW_FAULT_INJECTED:'))
|
|
338
|
-
throw error;
|
|
339
|
-
return { ...prepared.result, status: 'failed', error: {
|
|
340
|
-
code: 'LOCAL_RECOVERY_COMMIT_FAILED', message: String(error)
|
|
341
|
-
} };
|
|
342
|
-
}
|
|
137
|
+
function commitLocalArtifactProvenance(prepared) {
|
|
138
|
+
return prepared.result;
|
|
343
139
|
}
|
|
344
|
-
function finalizeLocalArtifact(request
|
|
345
|
-
|
|
346
|
-
const result = commitLocalArtifactProvenance(prepared);
|
|
347
|
-
if (authority && !options.deferLifecycleRecoveryConsumption && result.status === 'passed') {
|
|
348
|
-
try {
|
|
349
|
-
consumeLifecycleRecoveryAttestation(authority);
|
|
350
|
-
}
|
|
351
|
-
catch (error) {
|
|
352
|
-
return { ...result, status: 'failed', error: {
|
|
353
|
-
code: 'LOCAL_EXECUTION_AUTHORITY_CONSUME_FAILED',
|
|
354
|
-
message: error instanceof Error ? error.message : String(error)
|
|
355
|
-
} };
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
return result;
|
|
140
|
+
function finalizeLocalArtifact(request) {
|
|
141
|
+
return prepareLocalArtifact(request).result;
|
|
359
142
|
}
|
|
360
143
|
/** Validate and seal an immutable preflight generation without publishing the formal artifact. */
|
|
361
|
-
function preflightLocalArtifact(request
|
|
362
|
-
const prepared = prepareLocalArtifact(request
|
|
144
|
+
function preflightLocalArtifact(request) {
|
|
145
|
+
const prepared = prepareLocalArtifact(request);
|
|
363
146
|
return prepared.result;
|
|
364
147
|
}
|
|
365
|
-
export { LOCAL_ARTIFACT_REQUIRED_SECTIONS,
|
|
148
|
+
export { LOCAL_ARTIFACT_REQUIRED_SECTIONS, finalizeLocalArtifact, preflightLocalArtifact, prepareLocalArtifact, commitLocalArtifactProvenance, canonicalSemanticDigest as semanticDigest, sha256Content, validateLocalArtifact };
|
|
366
149
|
//# sourceMappingURL=local-artifact-finalization.js.map
|
|
@@ -4,6 +4,7 @@ import { createHash } from 'node:crypto';
|
|
|
4
4
|
import { MANUAL_VALIDATION_ARTIFACT as ARTIFACT, MANUAL_VALIDATION_SHA40 as SHA40, MANUAL_VALIDATION_SHA64 as SHA64, MANUAL_VALIDATION_TASK_ID as TASK_ID, exactKeys, isRecord, validTimestamp, writeJsonAtomic } from './manual-validation-shared.js';
|
|
5
5
|
const MANUAL_VALIDATION_RECEIPT_SCHEMA = 'agent-infra/manual-validation-receipt';
|
|
6
6
|
const MANUAL_VALIDATION_RECEIPT_VERSION = 1;
|
|
7
|
+
const MANUAL_VALIDATION_RECEIPT_PLACEHOLDER = '[[manual-validation-receipt]]';
|
|
7
8
|
function invalid(message) {
|
|
8
9
|
return { ok: false, error: { code: 'MANUAL_VALIDATION_RECEIPT_INVALID', message } };
|
|
9
10
|
}
|
|
@@ -33,12 +34,16 @@ function manualValidationFinalSummaryDigest(body) {
|
|
|
33
34
|
const projection = heading
|
|
34
35
|
? body.slice(heading.index, heading.index + heading[0].length + (nextSection?.index ?? afterHeading.length)).replace(/\s+$/u, '')
|
|
35
36
|
: body;
|
|
36
|
-
const preimage = projection
|
|
37
|
+
const preimage = projection
|
|
38
|
+
.replace(/\n*<!--\s*canonical-pr-change-report\s*-->\s*/gu, '')
|
|
39
|
+
.replace(/<!--\s*manual-validation-receipt:\s*[\s\S]*?-->/gu, MANUAL_VALIDATION_RECEIPT_PLACEHOLDER)
|
|
40
|
+
.replace(/receipt=(?:[a-f0-9]{64}|<receipt>)/gu, 'receipt=<receipt>');
|
|
37
41
|
return createHash('sha256').update(preimage, 'utf8').digest('hex');
|
|
38
42
|
}
|
|
39
43
|
function manualValidationFinalSummaryProjectionMatches(body, receipt) {
|
|
40
44
|
const identity = `transaction=${receipt.transactionId}; receipt=${receipt.receiptDigest}; evidence=${receipt.evidenceDigest}; head=${receipt.prHeadSha}`;
|
|
41
|
-
return body.includes(identity)
|
|
45
|
+
return (body.includes(identity) || body.includes(MANUAL_VALIDATION_RECEIPT_PLACEHOLDER))
|
|
46
|
+
&& manualValidationFinalSummaryDigest(body) === receipt.finalSummaryDigest;
|
|
42
47
|
}
|
|
43
48
|
function validateManualValidationReceipt(value, expected) {
|
|
44
49
|
if (!isRecord(value))
|
|
@@ -101,5 +106,5 @@ function readManualValidationReceipt(taskDir, expected) {
|
|
|
101
106
|
}
|
|
102
107
|
return validateManualValidationReceipt(value, expected);
|
|
103
108
|
}
|
|
104
|
-
export { MANUAL_VALIDATION_RECEIPT_SCHEMA, MANUAL_VALIDATION_RECEIPT_VERSION, createManualValidationReceipt, manualValidationFinalSummaryDigest, manualValidationFinalSummaryProjectionMatches, manualValidationReceiptDigest, manualValidationReceiptPath, readManualValidationReceipt, validateManualValidationReceipt, writeManualValidationReceiptAtomic };
|
|
109
|
+
export { MANUAL_VALIDATION_RECEIPT_SCHEMA, MANUAL_VALIDATION_RECEIPT_VERSION, MANUAL_VALIDATION_RECEIPT_PLACEHOLDER, createManualValidationReceipt, manualValidationFinalSummaryDigest, manualValidationFinalSummaryProjectionMatches, manualValidationReceiptDigest, manualValidationReceiptPath, readManualValidationReceipt, validateManualValidationReceipt, writeManualValidationReceiptAtomic };
|
|
105
110
|
//# sourceMappingURL=manual-validation-receipt.js.map
|
|
@@ -682,16 +682,6 @@ function prepareOrchestrationDelegationUnlocked(taskRef, input, options = {}) {
|
|
|
682
682
|
if (!validModel(input.requestedReasoningEffort)) {
|
|
683
683
|
return failed('ORCHESTRATION_REQUESTED_REASONING_EFFORT_REQUIRED', 'prepare requires the exact requested reasoning effort', resolved.taskId);
|
|
684
684
|
}
|
|
685
|
-
if (input.client === 'codex' && !input.lifecycleProvenance) {
|
|
686
|
-
return failed('ORCHESTRATION_CODEX_PROVENANCE_REQUIRED', 'Codex prepare requires lifecycle provenance', resolved.taskId);
|
|
687
|
-
}
|
|
688
|
-
if (input.lifecycleRecoveryAttestation
|
|
689
|
-
&& (input.lifecycleRecoveryAttestation.phase !== 'orchestration.prepare'
|
|
690
|
-
|| input.lifecycleRecoveryAttestation.taskId !== resolved.taskId
|
|
691
|
-
|| input.lifecycleRecoveryAttestation.artifact !== next.artifact
|
|
692
|
-
|| input.lifecycleRecoveryAttestation.round !== next.round)) {
|
|
693
|
-
return failed('LIFECYCLE_AUTHORITY_TUPLE_MISMATCH', 'orchestration authority does not match the routed stage', resolved.taskId);
|
|
694
|
-
}
|
|
695
685
|
const expectedPolicy = run.modelPolicy[next.role];
|
|
696
686
|
if (input.requestedModel !== expectedPolicy.model) {
|
|
697
687
|
return failed('ORCHESTRATION_REQUESTED_MODEL_MISMATCH', `requested model does not match the persisted ${next.role} model`, resolved.taskId);
|
|
@@ -699,10 +689,6 @@ function prepareOrchestrationDelegationUnlocked(taskRef, input, options = {}) {
|
|
|
699
689
|
if (input.requestedReasoningEffort !== expectedPolicy.reasoningEffort) {
|
|
700
690
|
return failed('ORCHESTRATION_REQUESTED_REASONING_EFFORT_MISMATCH', `requested reasoning effort does not match the persisted ${next.role} policy`, resolved.taskId);
|
|
701
691
|
}
|
|
702
|
-
const capabilityValidationError = options.validateLifecycleCapability?.();
|
|
703
|
-
if (capabilityValidationError) {
|
|
704
|
-
return failed(capabilityValidationError.code, capabilityValidationError.message, resolved.taskId, capabilityValidationError.detail ? { detail: capabilityValidationError.detail } : {});
|
|
705
|
-
}
|
|
706
692
|
let beforeFingerprint;
|
|
707
693
|
try {
|
|
708
694
|
beforeFingerprint = (options.captureWorkspace ?? captureWorkspaceSnapshot)({
|
|
@@ -728,10 +714,6 @@ function prepareOrchestrationDelegationUnlocked(taskRef, input, options = {}) {
|
|
|
728
714
|
lifecycleProvenance: input.lifecycleProvenance ?? null,
|
|
729
715
|
beforeFingerprint
|
|
730
716
|
}, { id: options.id, now: options.now, monotonicNow: options.monotonicNow });
|
|
731
|
-
const capabilityError = options.consumeLifecycleCapability?.();
|
|
732
|
-
if (capabilityError) {
|
|
733
|
-
return failed(capabilityError.code, capabilityError.message, resolved.taskId, capabilityError.detail ? { detail: capabilityError.detail } : {});
|
|
734
|
-
}
|
|
735
717
|
const updated = withUpdatedRun(run, {
|
|
736
718
|
nextStage: next.stage,
|
|
737
719
|
pendingDelegation: receipt
|
|
@@ -1093,6 +1075,14 @@ function recoverPreparedOrchestrationDelegation(taskRef, options = {}) {
|
|
|
1093
1075
|
return failed('ORCHESTRATION_PREPARED_RECOVERY_FAILED', error instanceof Error ? error.message : String(error), resolved.taskId);
|
|
1094
1076
|
}
|
|
1095
1077
|
}
|
|
1078
|
+
function recoveryReceiptMatches(receipt, taskId, event) {
|
|
1079
|
+
return receipt.taskId === taskId
|
|
1080
|
+
&& receipt.stage === event.stage
|
|
1081
|
+
&& receipt.round === event.round
|
|
1082
|
+
&& receipt.artifact === event.artifact
|
|
1083
|
+
&& receipt.childId === event.childId
|
|
1084
|
+
&& normalizeAgentToken(event.startedAgent) === normalizeAgentToken(receipt.client);
|
|
1085
|
+
}
|
|
1096
1086
|
function recoverActivatedOrchestrationDelegationUnderLock(taskRef, event, options = {}) {
|
|
1097
1087
|
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
1098
1088
|
if (!resolved.ok)
|
|
@@ -1107,12 +1097,7 @@ function recoverActivatedOrchestrationDelegationUnderLock(taskRef, event, option
|
|
|
1107
1097
|
const receipt = matches.find((candidate) => candidate.id === event.receiptId);
|
|
1108
1098
|
if (!receipt)
|
|
1109
1099
|
return failed('ORCHESTRATION_DELEGATION_MISSING', 'recovery receipt does not exist', resolved.taskId);
|
|
1110
|
-
if (receipt.taskId
|
|
1111
|
-
|| receipt.stage !== event.stage
|
|
1112
|
-
|| receipt.round !== event.round
|
|
1113
|
-
|| receipt.artifact !== event.artifact
|
|
1114
|
-
|| receipt.childId !== event.childId
|
|
1115
|
-
|| normalizeAgentToken(event.startedAgent) !== normalizeAgentToken(receipt.client)) {
|
|
1100
|
+
if (!recoveryReceiptMatches(receipt, resolved.taskId, event)) {
|
|
1116
1101
|
return failed('ORCHESTRATION_PROVENANCE_MISMATCH', 'recovery selector does not match the delegation receipt', resolved.taskId);
|
|
1117
1102
|
}
|
|
1118
1103
|
if (receipt.status === 'aborted' && run.pendingDelegation === null) {
|
|
@@ -1124,6 +1109,9 @@ function recoverActivatedOrchestrationDelegationUnderLock(taskRef, event, option
|
|
|
1124
1109
|
if (run.receipts.some((candidate) => candidate.id === receipt.id)) {
|
|
1125
1110
|
return failed('ORCHESTRATION_RECEIPT_DUPLICATE', 'recovery receipt already exists in the completed receipt history', resolved.taskId);
|
|
1126
1111
|
}
|
|
1112
|
+
if (run.status !== 'running' || run.pause !== null) {
|
|
1113
|
+
return failed('ORCHESTRATION_RECOVERY_UNSAFE', 'activated recovery cannot replace an unrelated orchestration pause', resolved.taskId);
|
|
1114
|
+
}
|
|
1127
1115
|
const aborted = abortActivatedDelegation(receipt, {
|
|
1128
1116
|
childId: event.childId,
|
|
1129
1117
|
stopRevision: event.stopRevision,
|
|
@@ -4,7 +4,6 @@ export function parseReviewCommand(args) {
|
|
|
4
4
|
const values = {};
|
|
5
5
|
const flags = {
|
|
6
6
|
'--stage': 'stage', '--artifact': 'artifact', '--orchestrated': 'orchestrated', '--dry-run': 'dryRun',
|
|
7
|
-
'--recovery-id': 'recoveryId',
|
|
8
7
|
'--override-ticket': 'overrideTicket', '--override-target': 'overrideTarget', '--override-scope': 'overrideScope'
|
|
9
8
|
};
|
|
10
9
|
for (let index = 2; index < args.length; index += 1) {
|