@fitlab-ai/agent-infra 0.11.1 → 0.11.2
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/lib/internal/cli-route-inventory.js +1 -1
- package/dist/lib/internal/platform-comment.js +59 -0
- package/dist/lib/internal/sandbox-control.js +4 -131
- package/dist/lib/internal/task-artifact.js +1 -1
- package/dist/lib/internal/task-operation-registry.js +1 -0
- 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 +120 -13
- 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/executor.js +0 -38
- package/dist/lib/sandbox/control/finalization-response.js +10 -2
- package/dist/lib/sandbox/control/task-view.js +3 -1
- package/dist/lib/sandbox/control/workflow-executor.js +28 -43
- 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 +1 -8
- package/dist/lib/task/delegation-receipts.js +14 -76
- package/dist/lib/task/events.js +89 -237
- package/dist/lib/task/finalization-state.js +2 -1
- package/dist/lib/task/finalization.js +273 -49
- package/dist/lib/task/lifecycle-execution.js +2 -11
- package/dist/lib/task/local-artifact-finalization.js +25 -242
- package/dist/lib/task/orchestration.js +0 -18
- package/dist/lib/task/review-command.js +0 -1
- package/dist/lib/task/review-finalization.js +46 -211
- package/dist/lib/task/workflow-command.js +4 -1
- package/dist/lib/task/workflow-dispatch.js +55 -3
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/internal/cli-route-inventory.ts +1 -1
- package/lib/internal/platform-comment.ts +38 -0
- package/lib/internal/sandbox-control.ts +3 -145
- package/lib/internal/task-artifact.ts +1 -1
- package/lib/internal/task-operation-registry.ts +1 -0
- 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 +132 -13
- 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/executor.ts +0 -44
- package/lib/sandbox/control/finalization-response.ts +10 -2
- package/lib/sandbox/control/task-view.ts +3 -1
- package/lib/sandbox/control/workflow-executor.ts +23 -50
- 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 +1 -8
- package/lib/task/delegation-receipts.ts +12 -79
- package/lib/task/events.ts +106 -255
- package/lib/task/finalization-state.ts +2 -1
- package/lib/task/finalization.ts +277 -57
- package/lib/task/lifecycle-execution.ts +10 -21
- package/lib/task/local-artifact-finalization.ts +23 -281
- package/lib/task/orchestration.ts +0 -34
- package/lib/task/review-command.ts +0 -1
- package/lib/task/review-finalization.ts +45 -193
- package/lib/task/workflow-command.ts +4 -1
- package/lib/task/workflow-dispatch.ts +46 -3
- package/package.json +2 -2
- package/templates/.agents/rules/lifecycle-orchestration.en.md +7 -7
- package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +7 -7
- 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-task/SKILL.en.md +3 -3
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +3 -3
- 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-task/SKILL.en.md +6 -6
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +6 -6
- 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
|
@@ -1,37 +1,16 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
2
3
|
|
|
3
4
|
import { inspectDecisionDetailDuplicates } from './decision-details.ts';
|
|
4
5
|
import { scanVisibleMarkdown } from './markdown.ts';
|
|
5
6
|
import { parseArtifactName } from './artifact-name.ts';
|
|
6
7
|
import {
|
|
7
8
|
hasOpenArtifactRound,
|
|
8
|
-
validateArtifactPublication,
|
|
9
|
-
validateCompletedArtifact
|
|
10
9
|
} from './artifact-lifecycle.ts';
|
|
11
10
|
import { resolveTaskRef } from './resolve-ref.ts';
|
|
12
11
|
import { expectedQualificationRelations, validateQualificationAudit } from './qualification-audit.ts';
|
|
13
12
|
import { canonicalSemanticDigest, inspectArtifactPatterns, inspectArtifactStructure, sha256Content } from './artifact-operations.ts';
|
|
14
13
|
import { getArtifactSchema } from './artifact-schema.ts';
|
|
15
|
-
import { readArtifactRecoveryIntent } from './artifact-repair-intent.ts';
|
|
16
|
-
import type { ArtifactRecoveryIntent } from './artifact-repair-intent.ts';
|
|
17
|
-
import {
|
|
18
|
-
beginArtifactRecovery,
|
|
19
|
-
commitArtifactRecovery,
|
|
20
|
-
consumeArtifactRecovery,
|
|
21
|
-
prepareArtifactRecoveryFinal,
|
|
22
|
-
prepareArtifactRecoveryCommit,
|
|
23
|
-
recordArtifactRecoveryPassed,
|
|
24
|
-
reconcileArtifactRecovery,
|
|
25
|
-
recoveryContextFromIntent,
|
|
26
|
-
stageArtifactCandidate
|
|
27
|
-
} from './artifact-recovery.ts';
|
|
28
|
-
import type { ArtifactRecoveryContext } from './artifact-recovery.ts';
|
|
29
|
-
import {
|
|
30
|
-
consumeLifecycleRecoveryAttestation,
|
|
31
|
-
lifecycleRecoveryAttestationDigest,
|
|
32
|
-
validateLifecycleRecoveryAttestation
|
|
33
|
-
} from './control-authority.ts';
|
|
34
|
-
import type { LifecycleRecoveryAttestationV1 } from './control-authority.ts';
|
|
35
14
|
|
|
36
15
|
type LocalArtifactFamily = 'analysis' | 'plan' | 'code';
|
|
37
16
|
|
|
@@ -50,8 +29,6 @@ type LocalArtifactDiagnosticCode =
|
|
|
50
29
|
| 'LOCAL_REQUIRED_PATTERN_MISSING'
|
|
51
30
|
| 'LOCAL_STRUCTURAL_INVALID'
|
|
52
31
|
| 'LOCAL_SECTION_HEADING_TRAILING_PUNCTUATION'
|
|
53
|
-
| 'LOCAL_RECOVERY_PROVENANCE_CONFLICT'
|
|
54
|
-
| 'LOCAL_RECOVERY_BASELINE_MISMATCH'
|
|
55
32
|
| 'LOCAL_QUALIFICATION_AUDIT_INVALID';
|
|
56
33
|
|
|
57
34
|
type LocalArtifactDiagnostic = {
|
|
@@ -80,17 +57,9 @@ type LocalArtifactFinalizationRequest = {
|
|
|
80
57
|
artifact: string;
|
|
81
58
|
repoRoot?: string;
|
|
82
59
|
requiredSections?: readonly string[];
|
|
83
|
-
recoveryId?: string;
|
|
84
60
|
lockAlreadyHeld?: boolean;
|
|
85
61
|
};
|
|
86
62
|
|
|
87
|
-
type LocalArtifactRecoveryInfo = Readonly<{
|
|
88
|
-
recoveryId: string;
|
|
89
|
-
candidatePath: string;
|
|
90
|
-
baselineSha256: string;
|
|
91
|
-
baselineSemanticDigest: string;
|
|
92
|
-
}>;
|
|
93
|
-
|
|
94
63
|
type LocalArtifactFinalizationResult = {
|
|
95
64
|
status: 'passed' | 'failed';
|
|
96
65
|
changed: boolean;
|
|
@@ -101,54 +70,9 @@ type LocalArtifactFinalizationResult = {
|
|
|
101
70
|
artifactSha256: string | null;
|
|
102
71
|
semanticDigest: string | null;
|
|
103
72
|
diagnostics: readonly LocalArtifactDiagnostic[];
|
|
104
|
-
recovery?: LocalArtifactRecoveryInfo;
|
|
105
73
|
error: { code: string; message: string } | null;
|
|
106
74
|
};
|
|
107
75
|
|
|
108
|
-
type LocalArtifactFinalizationIntent = ArtifactRecoveryIntent;
|
|
109
|
-
|
|
110
|
-
function authorityDigest(authority: LifecycleRecoveryAttestationV1): string {
|
|
111
|
-
return lifecycleRecoveryAttestationDigest(authority);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
function validFinalizerAuthority(
|
|
115
|
-
authority: LifecycleRecoveryAttestationV1 | undefined,
|
|
116
|
-
request: LocalArtifactFinalizationRequest,
|
|
117
|
-
taskId: string
|
|
118
|
-
): boolean {
|
|
119
|
-
if (!authority) return false;
|
|
120
|
-
try { validateLifecycleRecoveryAttestation(authority); }
|
|
121
|
-
catch { return false; }
|
|
122
|
-
return authority.version === 1
|
|
123
|
-
&& authority.phase === 'artifact.finalize-local'
|
|
124
|
-
&& authority.taskId === taskId
|
|
125
|
-
&& authority.family === request.family
|
|
126
|
-
&& authority.artifact === request.artifact
|
|
127
|
-
&& authority.round === (parseArtifactName(request.artifact)?.round ?? 0)
|
|
128
|
-
&& authority.requestId.length > 0
|
|
129
|
-
&& authority.operationId.length > 0;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
function recoveryInfo(context: ArtifactRecoveryContext): LocalArtifactRecoveryInfo {
|
|
133
|
-
return {
|
|
134
|
-
recoveryId: context.recoveryId,
|
|
135
|
-
candidatePath: context.stagingPath,
|
|
136
|
-
baselineSha256: context.baselineSha256,
|
|
137
|
-
baselineSemanticDigest: context.baselineSemanticDigest
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
function consumeLocalArtifactFinalizationIntent(
|
|
142
|
-
repoRoot: string,
|
|
143
|
-
intent: LocalArtifactFinalizationIntent,
|
|
144
|
-
options: Readonly<{ lockAlreadyHeld?: boolean }> = {}
|
|
145
|
-
): LocalArtifactFinalizationIntent {
|
|
146
|
-
if (intent.state === 'consumed') return intent;
|
|
147
|
-
const resolved = resolveTaskRef(intent.taskId, { repoRoot });
|
|
148
|
-
if (!resolved.ok) throw new Error(resolved.message);
|
|
149
|
-
return consumeArtifactRecovery(recoveryContextFromIntent(repoRoot, resolved.taskDir, intent), options);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
76
|
function localDiagnosticCode(code: string): LocalArtifactDiagnosticCode {
|
|
153
77
|
const map: Record<string, LocalArtifactDiagnosticCode> = {
|
|
154
78
|
ARTIFACT_EMPTY: 'LOCAL_ARTIFACT_EMPTY',
|
|
@@ -245,32 +169,11 @@ type LocalArtifactPreparation = Readonly<{
|
|
|
245
169
|
content: string;
|
|
246
170
|
repoRoot?: string;
|
|
247
171
|
lockAlreadyHeld?: boolean;
|
|
248
|
-
recovery?: ArtifactRecoveryContext;
|
|
249
|
-
authority?: LifecycleRecoveryAttestationV1;
|
|
250
172
|
}>;
|
|
251
173
|
|
|
252
|
-
function tupleFor(
|
|
253
|
-
request: LocalArtifactFinalizationRequest,
|
|
254
|
-
taskId: string,
|
|
255
|
-
authority?: LifecycleRecoveryAttestationV1
|
|
256
|
-
) {
|
|
257
|
-
const round = parseArtifactName(request.artifact)?.round ?? 0;
|
|
258
|
-
return {
|
|
259
|
-
taskId,
|
|
260
|
-
family: request.family,
|
|
261
|
-
artifact: request.artifact,
|
|
262
|
-
round,
|
|
263
|
-
requestId: authority?.lifecycleRequestId ?? `local-finalize:${taskId}:${request.family}:${round}`,
|
|
264
|
-
phase: authority?.phase ?? 'artifact.finalize-local' as const,
|
|
265
|
-
authorityDigest: authority ? authorityDigest(authority) : null
|
|
266
|
-
};
|
|
267
|
-
}
|
|
268
|
-
|
|
269
174
|
function prepareLocalArtifact(
|
|
270
175
|
request: LocalArtifactFinalizationRequest,
|
|
271
|
-
candidate?: string
|
|
272
|
-
authority?: LifecycleRecoveryAttestationV1,
|
|
273
|
-
preflightOnly = false
|
|
176
|
+
candidate?: string
|
|
274
177
|
): LocalArtifactPreparation {
|
|
275
178
|
const failed = (code: string, message: string, extra: Partial<LocalArtifactFinalizationResult> = {}): LocalArtifactPreparation => ({
|
|
276
179
|
result: failedFinalization(request, { code, message }, extra), content: candidate ?? ''
|
|
@@ -279,34 +182,17 @@ function prepareLocalArtifact(
|
|
|
279
182
|
if (!resolved.ok) return failed(resolved.code, resolved.message);
|
|
280
183
|
const parsed = parseArtifactName(request.artifact);
|
|
281
184
|
if (!parsed || parsed.family !== request.family) return failed('ARTIFACT_IDENTITY_INVALID', `artifact '${request.artifact}' does not match ${request.family}`);
|
|
282
|
-
if (authority && !validFinalizerAuthority(authority, request, resolved.taskId)) {
|
|
283
|
-
return failed('LOCAL_EXECUTION_AUTHORITY_INVALID', 'finalizer authority does not match the artifact tuple');
|
|
284
|
-
}
|
|
285
|
-
|
|
286
185
|
let taskContent: string;
|
|
287
186
|
let content: string;
|
|
288
|
-
let recovery: ArtifactRecoveryContext | undefined;
|
|
289
|
-
let recoveryState: ArtifactRecoveryIntent['state'] | undefined;
|
|
290
187
|
try {
|
|
291
188
|
taskContent = fs.readFileSync(resolved.taskMdPath, 'utf8');
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
if (!['awaiting-preflight-recovery', 'preflight-ready', 'preflight-commit-started', 'preflight-passed', 'full-finalizer-ready', 'commit-started'].includes(intent.state)) return failed('LOCAL_RECOVERY_PROVENANCE_CONFLICT', `recovery journal is in '${intent.state}' state`);
|
|
296
|
-
recoveryState = intent.state;
|
|
297
|
-
recovery = recoveryContextFromIntent(resolved.repoRoot, resolved.taskDir, intent);
|
|
298
|
-
content = fs.existsSync(recovery.stagingPath)
|
|
299
|
-
? fs.readFileSync(recovery.stagingPath, 'utf8')
|
|
300
|
-
: fs.readFileSync(recovery.formalPath, 'utf8');
|
|
301
|
-
} else {
|
|
302
|
-
const validated = validateCompletedArtifact(resolved.taskDir, request.family, request.artifact, parsed.round);
|
|
303
|
-
if (!validated.ok) return failed(validated.error.code, validated.error.message);
|
|
304
|
-
content = candidate ?? fs.readFileSync(validated.artifact.path, 'utf8');
|
|
305
|
-
}
|
|
189
|
+
const artifactPath = path.join(resolved.taskDir, request.artifact);
|
|
190
|
+
if (!fs.statSync(artifactPath).isFile()) return failed('ARTIFACT_NOT_REGULAR', `artifact '${request.artifact}' is not a regular file`);
|
|
191
|
+
content = candidate ?? fs.readFileSync(artifactPath, 'utf8');
|
|
306
192
|
} catch (error) { return failed('ARTIFACT_NOT_READABLE', String(error)); }
|
|
307
193
|
|
|
308
|
-
if (resolved.state !== 'active'
|
|
309
|
-
return failed('ARTIFACT_IDENTITY_INVALID', '
|
|
194
|
+
if (resolved.state !== 'active') {
|
|
195
|
+
return failed('ARTIFACT_IDENTITY_INVALID', 'artifact finalization requires an active task');
|
|
310
196
|
}
|
|
311
197
|
const validation = validateLocalArtifact(content, {
|
|
312
198
|
family: request.family, requiredSections: request.requiredSections, taskContent, artifact: request.artifact
|
|
@@ -320,186 +206,44 @@ function prepareLocalArtifact(
|
|
|
320
206
|
semanticDigest: validation.semanticDigest, diagnostics: validation.diagnostics
|
|
321
207
|
});
|
|
322
208
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
};
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
const existing = readArtifactRecoveryIntent(resolved.repoRoot, resolved.taskId, request.family, request.artifact);
|
|
336
|
-
if (existing?.state === 'passed' || existing?.state === 'consumed') {
|
|
337
|
-
if (existing.state === 'consumed' && (existing.finalArtifactSha256 !== artifactSha256 || existing.finalSemanticDigest !== validation.semanticDigest)) {
|
|
338
|
-
return { ...failed('LOCAL_RECOVERY_PROVENANCE_CONFLICT', 'formal artifact does not match its completed recovery journal'), content, repoRoot: resolved.repoRoot, authority, lockAlreadyHeld: request.lockAlreadyHeld };
|
|
339
|
-
}
|
|
340
|
-
if (existing.finalArtifactSha256 === artifactSha256 && existing.finalSemanticDigest === validation.semanticDigest) {
|
|
341
|
-
const context = recoveryContextFromIntent(resolved.repoRoot, resolved.taskDir, existing);
|
|
342
|
-
return {
|
|
343
|
-
result: { ...result, status: 'passed', error: null, recovery: recoveryInfo(context) },
|
|
344
|
-
content, repoRoot: resolved.repoRoot, recovery: context, authority, lockAlreadyHeld: request.lockAlreadyHeld
|
|
345
|
-
};
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
if (existing && ['preflight-ready', 'preflight-commit-started', 'preflight-passed', 'full-finalizer-ready'].includes(existing.state)) {
|
|
350
|
-
const context = recoveryContextFromIntent(resolved.repoRoot, resolved.taskDir, existing);
|
|
351
|
-
return {
|
|
352
|
-
result: { ...result, status: 'passed', changed: false, error: null, recovery: recoveryInfo(context) },
|
|
353
|
-
content, repoRoot: resolved.repoRoot, recovery: context, authority, lockAlreadyHeld: request.lockAlreadyHeld
|
|
354
|
-
};
|
|
355
|
-
}
|
|
356
|
-
if (existing?.state === 'commit-started') {
|
|
357
|
-
if (existing.finalArtifactSha256 !== artifactSha256 || existing.finalSemanticDigest !== validation.semanticDigest) {
|
|
358
|
-
return { ...failed('LOCAL_RECOVERY_PROVENANCE_CONFLICT', 'formal artifact does not match the interrupted recovery journal'), content, repoRoot: resolved.repoRoot, authority, lockAlreadyHeld: request.lockAlreadyHeld };
|
|
359
|
-
}
|
|
360
|
-
const context = recoveryContextFromIntent(resolved.repoRoot, resolved.taskDir, existing);
|
|
361
|
-
const reconciled = reconcileArtifactRecovery(context, {
|
|
362
|
-
lockAlreadyHeld: request.lockAlreadyHeld,
|
|
363
|
-
validateFinal: () => validation.semanticDigest === existing.finalSemanticDigest
|
|
364
|
-
});
|
|
365
|
-
if (reconciled.status !== 'passed') {
|
|
366
|
-
return { ...failed('LOCAL_RECOVERY_PROVENANCE_CONFLICT', `interrupted recovery could not be reconciled: ${reconciled.status}`), content, repoRoot: resolved.repoRoot, recovery: context, authority, lockAlreadyHeld: request.lockAlreadyHeld };
|
|
367
|
-
}
|
|
368
|
-
return {
|
|
369
|
-
result: { ...result, status: 'passed', changed: false, error: null, recovery: recoveryInfo(context) },
|
|
370
|
-
content, repoRoot: resolved.repoRoot, recovery: context, authority, lockAlreadyHeld: request.lockAlreadyHeld
|
|
371
|
-
};
|
|
372
|
-
}
|
|
209
|
+
// Trusted local operators edit the canonical artifact directly. A
|
|
210
|
+
// finalization result is a fresh observation of those bytes, never a
|
|
211
|
+
// capability minted by an earlier recovery attempt or generation.
|
|
212
|
+
return {
|
|
213
|
+
result: validation.ok ? { ...result, status: 'passed', error: null } : result,
|
|
214
|
+
content,
|
|
215
|
+
repoRoot: resolved.repoRoot,
|
|
216
|
+
lockAlreadyHeld: request.lockAlreadyHeld
|
|
217
|
+
};
|
|
373
218
|
|
|
374
|
-
try {
|
|
375
|
-
if (validation.ok) {
|
|
376
|
-
if (preflightOnly) {
|
|
377
|
-
const context = beginArtifactRecovery(tupleFor(request, resolved.taskId, authority), Buffer.from(content, 'utf8'), {
|
|
378
|
-
repoRoot: resolved.repoRoot, taskDir: resolved.taskDir,
|
|
379
|
-
...(authority ? { recoveryId: authority.operationId } : {}), lockAlreadyHeld: request.lockAlreadyHeld
|
|
380
|
-
});
|
|
381
|
-
const staged = stageArtifactCandidate(context, Buffer.from(content, 'utf8'), { lockAlreadyHeld: request.lockAlreadyHeld });
|
|
382
|
-
prepareArtifactRecoveryCommit(context, staged.candidateSha256, staged.semanticDigest, { lockAlreadyHeld: request.lockAlreadyHeld });
|
|
383
|
-
return { result: { ...result, status: 'passed', error: null, recovery: recoveryInfo(context) }, content, repoRoot: resolved.repoRoot, recovery: context, authority, lockAlreadyHeld: request.lockAlreadyHeld };
|
|
384
|
-
}
|
|
385
|
-
const context = recordArtifactRecoveryPassed(tupleFor(request, resolved.taskId, authority), {
|
|
386
|
-
repoRoot: resolved.repoRoot,
|
|
387
|
-
taskDir: resolved.taskDir,
|
|
388
|
-
expectedFinalSha256: artifactSha256,
|
|
389
|
-
expectedFinalSemanticDigest: validation.semanticDigest,
|
|
390
|
-
...(authority ? { recoveryId: authority.operationId } : {}),
|
|
391
|
-
lockAlreadyHeld: request.lockAlreadyHeld
|
|
392
|
-
});
|
|
393
|
-
recovery = context;
|
|
394
|
-
return { result: { ...result, status: 'passed', changed: false, error: null, recovery: recoveryInfo(context) }, content, repoRoot: resolved.repoRoot, recovery, authority, lockAlreadyHeld: request.lockAlreadyHeld };
|
|
395
|
-
}
|
|
396
|
-
const context = beginArtifactRecovery(tupleFor(request, resolved.taskId, authority), Buffer.from(content, 'utf8'), {
|
|
397
|
-
repoRoot: resolved.repoRoot,
|
|
398
|
-
taskDir: resolved.taskDir,
|
|
399
|
-
...(authority ? { recoveryId: authority.operationId } : {}),
|
|
400
|
-
lockAlreadyHeld: request.lockAlreadyHeld
|
|
401
|
-
});
|
|
402
|
-
recovery = context;
|
|
403
|
-
return {
|
|
404
|
-
result: { ...result, recovery: recoveryInfo(context) },
|
|
405
|
-
content, repoRoot: resolved.repoRoot, recovery, authority, lockAlreadyHeld: request.lockAlreadyHeld
|
|
406
|
-
};
|
|
407
|
-
} catch (error) {
|
|
408
|
-
return { ...failed('LOCAL_RECOVERY_PROVENANCE_CONFLICT', String(error)), content, repoRoot: resolved.repoRoot, authority, lockAlreadyHeld: request.lockAlreadyHeld };
|
|
409
|
-
}
|
|
410
219
|
}
|
|
411
220
|
|
|
412
221
|
function commitLocalArtifactProvenance(
|
|
413
|
-
prepared: LocalArtifactPreparation
|
|
414
|
-
options: Readonly<{ afterPublish?: () => void }> = {}
|
|
222
|
+
prepared: LocalArtifactPreparation
|
|
415
223
|
): LocalArtifactFinalizationResult {
|
|
416
|
-
|
|
417
|
-
if (prepared.result.status === 'failed') return prepared.result;
|
|
418
|
-
try {
|
|
419
|
-
const intent = readArtifactRecoveryIntent(prepared.repoRoot, prepared.recovery.taskId, prepared.recovery.family, prepared.recovery.artifact);
|
|
420
|
-
if (!intent) throw new Error('ARTIFACT_RECOVERY_INTENT_MISSING');
|
|
421
|
-
let committed;
|
|
422
|
-
if (intent.state === 'passed' || intent.state === 'consumed') {
|
|
423
|
-
return {
|
|
424
|
-
...prepared.result,
|
|
425
|
-
changed: false,
|
|
426
|
-
artifactSha256: intent.finalArtifactSha256,
|
|
427
|
-
semanticDigest: intent.finalSemanticDigest,
|
|
428
|
-
error: null
|
|
429
|
-
};
|
|
430
|
-
} else if (intent.state === 'commit-started') {
|
|
431
|
-
const reconciled = reconcileArtifactRecovery(prepared.recovery, {
|
|
432
|
-
lockAlreadyHeld: prepared.lockAlreadyHeld,
|
|
433
|
-
afterPublish: options.afterPublish
|
|
434
|
-
});
|
|
435
|
-
committed = reconciled.intent;
|
|
436
|
-
} else if (intent.state === 'awaiting-preflight-recovery') {
|
|
437
|
-
const staged = stageArtifactCandidate(prepared.recovery, Buffer.from(prepared.content, 'utf8'), { lockAlreadyHeld: prepared.lockAlreadyHeld });
|
|
438
|
-
prepareArtifactRecoveryCommit(prepared.recovery, staged.candidateSha256, staged.semanticDigest, { lockAlreadyHeld: prepared.lockAlreadyHeld });
|
|
439
|
-
committed = commitArtifactRecovery(prepared.recovery, { lockAlreadyHeld: prepared.lockAlreadyHeld });
|
|
440
|
-
}
|
|
441
|
-
if (intent.state === 'preflight-ready' || intent.state === 'preflight-commit-started') {
|
|
442
|
-
committed = commitArtifactRecovery(prepared.recovery, { lockAlreadyHeld: prepared.lockAlreadyHeld });
|
|
443
|
-
}
|
|
444
|
-
if (committed?.state === 'preflight-passed' || intent.state === 'preflight-passed') {
|
|
445
|
-
prepareArtifactRecoveryFinal(prepared.recovery, Buffer.from(prepared.content, 'utf8'), { lockAlreadyHeld: prepared.lockAlreadyHeld });
|
|
446
|
-
committed = commitArtifactRecovery(prepared.recovery, { lockAlreadyHeld: prepared.lockAlreadyHeld, afterPublish: options.afterPublish });
|
|
447
|
-
} else if (intent.state === 'full-finalizer-ready') {
|
|
448
|
-
committed = commitArtifactRecovery(prepared.recovery, { lockAlreadyHeld: prepared.lockAlreadyHeld, afterPublish: options.afterPublish });
|
|
449
|
-
}
|
|
450
|
-
if (!committed || committed.state !== 'passed') throw new Error(`ARTIFACT_RECOVERY_STATE_INVALID: commit ended in '${committed?.state ?? intent.state}'`);
|
|
451
|
-
return {
|
|
452
|
-
...prepared.result,
|
|
453
|
-
status: 'passed',
|
|
454
|
-
changed: false,
|
|
455
|
-
artifactSha256: committed.finalArtifactSha256,
|
|
456
|
-
semanticDigest: committed.finalSemanticDigest,
|
|
457
|
-
error: null
|
|
458
|
-
};
|
|
459
|
-
} catch (error) {
|
|
460
|
-
if (error instanceof Error && error.message.startsWith('TASK_WORKFLOW_FAULT_INJECTED:')) throw error;
|
|
461
|
-
return { ...prepared.result, status: 'failed', error: {
|
|
462
|
-
code: 'LOCAL_RECOVERY_COMMIT_FAILED', message: String(error)
|
|
463
|
-
} };
|
|
464
|
-
}
|
|
224
|
+
return prepared.result;
|
|
465
225
|
}
|
|
466
226
|
|
|
467
227
|
function finalizeLocalArtifact(
|
|
468
|
-
request: LocalArtifactFinalizationRequest
|
|
469
|
-
authority?: LifecycleRecoveryAttestationV1,
|
|
470
|
-
options: Readonly<{ deferLifecycleRecoveryConsumption?: boolean }> = {}
|
|
228
|
+
request: LocalArtifactFinalizationRequest
|
|
471
229
|
): LocalArtifactFinalizationResult {
|
|
472
|
-
|
|
473
|
-
const result = commitLocalArtifactProvenance(prepared);
|
|
474
|
-
if (authority && !options.deferLifecycleRecoveryConsumption && result.status === 'passed') {
|
|
475
|
-
try { consumeLifecycleRecoveryAttestation(authority); }
|
|
476
|
-
catch (error) {
|
|
477
|
-
return { ...result, status: 'failed', error: {
|
|
478
|
-
code: 'LOCAL_EXECUTION_AUTHORITY_CONSUME_FAILED',
|
|
479
|
-
message: error instanceof Error ? error.message : String(error)
|
|
480
|
-
} };
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
return result;
|
|
230
|
+
return prepareLocalArtifact(request).result;
|
|
484
231
|
}
|
|
485
232
|
|
|
486
233
|
/** Validate and seal an immutable preflight generation without publishing the formal artifact. */
|
|
487
234
|
function preflightLocalArtifact(
|
|
488
|
-
request: LocalArtifactFinalizationRequest
|
|
489
|
-
authority?: LifecycleRecoveryAttestationV1
|
|
235
|
+
request: LocalArtifactFinalizationRequest
|
|
490
236
|
): LocalArtifactFinalizationResult {
|
|
491
|
-
const prepared = prepareLocalArtifact(request
|
|
237
|
+
const prepared = prepareLocalArtifact(request);
|
|
492
238
|
return prepared.result;
|
|
493
239
|
}
|
|
494
240
|
|
|
495
241
|
export {
|
|
496
242
|
LOCAL_ARTIFACT_REQUIRED_SECTIONS,
|
|
497
|
-
consumeLocalArtifactFinalizationIntent,
|
|
498
243
|
finalizeLocalArtifact,
|
|
499
244
|
preflightLocalArtifact,
|
|
500
245
|
prepareLocalArtifact,
|
|
501
246
|
commitLocalArtifactProvenance,
|
|
502
|
-
readArtifactRecoveryIntent as readLocalArtifactFinalizationIntent,
|
|
503
247
|
canonicalSemanticDigest as semanticDigest,
|
|
504
248
|
sha256Content,
|
|
505
249
|
validateLocalArtifact
|
|
@@ -510,9 +254,7 @@ export type {
|
|
|
510
254
|
LocalArtifactFamily,
|
|
511
255
|
LocalArtifactFinalizationRequest,
|
|
512
256
|
LocalArtifactFinalizationResult,
|
|
513
|
-
LocalArtifactFinalizationIntent,
|
|
514
257
|
LocalArtifactPreparation,
|
|
515
258
|
LocalArtifactValidationOptions,
|
|
516
|
-
LocalArtifactValidationResult
|
|
517
|
-
LocalArtifactRecoveryInfo
|
|
259
|
+
LocalArtifactValidationResult
|
|
518
260
|
};
|
|
@@ -41,7 +41,6 @@ import type {
|
|
|
41
41
|
OrchestrationModelPolicy,
|
|
42
42
|
OrchestrationRolePolicy
|
|
43
43
|
} from '../agent-clients/types.ts';
|
|
44
|
-
import type { LifecycleRecoveryAttestationV1 } from './control-authority.ts';
|
|
45
44
|
import {
|
|
46
45
|
captureRepositorySnapshot,
|
|
47
46
|
captureWorkspaceSnapshot,
|
|
@@ -174,9 +173,6 @@ type OrchestrationOptions = {
|
|
|
174
173
|
}>;
|
|
175
174
|
diffWorkspace?: (repoRoot: string, before: string, after: string) => string[];
|
|
176
175
|
supportsLifecycleDelegation?: (client: AgentClientId) => boolean;
|
|
177
|
-
validateLifecycleCapability?: () => Exclude<OrchestrationResult['error'], null> | null;
|
|
178
|
-
consumeLifecycleCapability?: () => Exclude<OrchestrationResult['error'], null> | null;
|
|
179
|
-
lifecycleRecoveryAttestation?: LifecycleRecoveryAttestationV1 | null;
|
|
180
176
|
token?: () => string;
|
|
181
177
|
monotonicNow?: () => number;
|
|
182
178
|
sleep?: (milliseconds: number) => Promise<void>;
|
|
@@ -819,7 +815,6 @@ function prepareOrchestrationDelegationUnlocked(
|
|
|
819
815
|
requestedModel?: string;
|
|
820
816
|
requestedReasoningEffort?: string;
|
|
821
817
|
lifecycleProvenance?: DelegationLifecycleProvenance;
|
|
822
|
-
lifecycleRecoveryAttestation?: LifecycleRecoveryAttestationV1 | null;
|
|
823
818
|
}>,
|
|
824
819
|
options: OrchestrationOptions = {}
|
|
825
820
|
): OrchestrationResult {
|
|
@@ -861,16 +856,6 @@ function prepareOrchestrationDelegationUnlocked(
|
|
|
861
856
|
if (!validModel(input.requestedReasoningEffort)) {
|
|
862
857
|
return failed('ORCHESTRATION_REQUESTED_REASONING_EFFORT_REQUIRED', 'prepare requires the exact requested reasoning effort', resolved.taskId);
|
|
863
858
|
}
|
|
864
|
-
if (input.client === 'codex' && !input.lifecycleProvenance) {
|
|
865
|
-
return failed('ORCHESTRATION_CODEX_PROVENANCE_REQUIRED', 'Codex prepare requires lifecycle provenance', resolved.taskId);
|
|
866
|
-
}
|
|
867
|
-
if (input.lifecycleRecoveryAttestation
|
|
868
|
-
&& (input.lifecycleRecoveryAttestation.phase !== 'orchestration.prepare'
|
|
869
|
-
|| input.lifecycleRecoveryAttestation.taskId !== resolved.taskId
|
|
870
|
-
|| input.lifecycleRecoveryAttestation.artifact !== next.artifact
|
|
871
|
-
|| input.lifecycleRecoveryAttestation.round !== next.round)) {
|
|
872
|
-
return failed('LIFECYCLE_AUTHORITY_TUPLE_MISMATCH', 'orchestration authority does not match the routed stage', resolved.taskId);
|
|
873
|
-
}
|
|
874
859
|
const expectedPolicy = run.modelPolicy[next.role];
|
|
875
860
|
if (input.requestedModel !== expectedPolicy.model) {
|
|
876
861
|
return failed('ORCHESTRATION_REQUESTED_MODEL_MISMATCH', `requested model does not match the persisted ${next.role} model`, resolved.taskId);
|
|
@@ -878,15 +863,6 @@ function prepareOrchestrationDelegationUnlocked(
|
|
|
878
863
|
if (input.requestedReasoningEffort !== expectedPolicy.reasoningEffort) {
|
|
879
864
|
return failed('ORCHESTRATION_REQUESTED_REASONING_EFFORT_MISMATCH', `requested reasoning effort does not match the persisted ${next.role} policy`, resolved.taskId);
|
|
880
865
|
}
|
|
881
|
-
const capabilityValidationError = options.validateLifecycleCapability?.();
|
|
882
|
-
if (capabilityValidationError) {
|
|
883
|
-
return failed(
|
|
884
|
-
capabilityValidationError.code,
|
|
885
|
-
capabilityValidationError.message,
|
|
886
|
-
resolved.taskId,
|
|
887
|
-
capabilityValidationError.detail ? { detail: capabilityValidationError.detail } : {}
|
|
888
|
-
);
|
|
889
|
-
}
|
|
890
866
|
let beforeFingerprint: string;
|
|
891
867
|
try {
|
|
892
868
|
beforeFingerprint = (options.captureWorkspace ?? captureWorkspaceSnapshot)({
|
|
@@ -911,15 +887,6 @@ function prepareOrchestrationDelegationUnlocked(
|
|
|
911
887
|
lifecycleProvenance: input.lifecycleProvenance ?? null,
|
|
912
888
|
beforeFingerprint
|
|
913
889
|
}, { id: options.id, now: options.now, monotonicNow: options.monotonicNow });
|
|
914
|
-
const capabilityError = options.consumeLifecycleCapability?.();
|
|
915
|
-
if (capabilityError) {
|
|
916
|
-
return failed(
|
|
917
|
-
capabilityError.code,
|
|
918
|
-
capabilityError.message,
|
|
919
|
-
resolved.taskId,
|
|
920
|
-
capabilityError.detail ? { detail: capabilityError.detail } : {}
|
|
921
|
-
);
|
|
922
|
-
}
|
|
923
890
|
const updated = withUpdatedRun(run, {
|
|
924
891
|
nextStage: next.stage,
|
|
925
892
|
pendingDelegation: receipt
|
|
@@ -935,7 +902,6 @@ function prepareOrchestrationDelegation(
|
|
|
935
902
|
requestedModel?: string;
|
|
936
903
|
requestedReasoningEffort?: string;
|
|
937
904
|
lifecycleProvenance?: DelegationLifecycleProvenance;
|
|
938
|
-
lifecycleRecoveryAttestation?: LifecycleRecoveryAttestationV1 | null;
|
|
939
905
|
}>,
|
|
940
906
|
options: OrchestrationOptions = {}
|
|
941
907
|
): OrchestrationResult {
|
|
@@ -7,7 +7,6 @@ export function parseReviewCommand(args: readonly string[]): ReviewFinalizationR
|
|
|
7
7
|
const values: Record<string, string | boolean> = {};
|
|
8
8
|
const flags = {
|
|
9
9
|
'--stage': 'stage', '--artifact': 'artifact', '--orchestrated': 'orchestrated', '--dry-run': 'dryRun',
|
|
10
|
-
'--recovery-id': 'recoveryId',
|
|
11
10
|
'--override-ticket': 'overrideTicket', '--override-target': 'overrideTarget', '--override-scope': 'overrideScope'
|
|
12
11
|
} as const;
|
|
13
12
|
for (let index = 2; index < args.length; index += 1) {
|