@fitlab-ai/agent-infra 0.10.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -6
- package/README.zh-CN.md +2 -6
- package/bin/internal-cli.sh +0 -13
- package/dist/bin/internal-cli.js +11 -49
- package/dist/lib/internal/cli-route-inventory.js +3 -4
- package/dist/lib/internal/platform-comment.js +60 -2
- package/dist/lib/internal/task-artifact.js +1 -1
- package/dist/lib/internal/task-finalization.js +2 -2
- package/dist/lib/internal/task-lifecycle.js +2 -2
- package/dist/lib/internal/task-operation-registry.js +22 -12
- package/dist/lib/internal/task-orchestration.js +2 -2
- package/dist/lib/internal/task-review.js +10 -5
- package/dist/lib/platform/activity-recovery.js +159 -0
- package/dist/lib/platform/issue-comments.js +84 -32
- package/dist/lib/platform/task-comment-projection.js +70 -0
- package/dist/lib/platform/task-recovery.js +55 -0
- package/dist/lib/platform/verification-sync.js +43 -9
- package/dist/lib/sandbox/control/executor.js +4 -2
- package/dist/lib/sandbox/control/task-workflow.js +1 -1
- package/dist/lib/sandbox/control/workflow-executor.js +83 -21
- package/dist/lib/server/process-state.js +17 -5
- package/dist/lib/task/artifact-command.js +6 -3
- package/dist/lib/task/artifact-operations.js +1 -1
- package/dist/lib/task/artifact-recovery.js +114 -68
- package/dist/lib/task/artifact-repair-intent.js +25 -8
- package/dist/lib/task/control-authority.js +1 -1
- package/dist/lib/task/events.js +6 -6
- package/dist/lib/task/intermediate-cleanup.js +28 -3
- package/dist/lib/task/local-artifact-finalization.js +78 -14
- package/dist/lib/task/review-command.js +2 -2
- package/dist/lib/task/review-finalization.js +203 -10
- package/dist/lib/{host-control → task}/secure-fs.js +8 -19
- package/dist/lib/task/workflow-command.js +2 -0
- package/dist/lib/task/workflow-dispatch.js +161 -0
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/internal/cli-route-inventory.ts +3 -4
- package/lib/internal/platform-comment.ts +53 -2
- package/lib/internal/task-artifact.ts +1 -1
- package/lib/internal/task-finalization.ts +1 -1
- package/lib/internal/task-lifecycle.ts +1 -1
- package/lib/internal/task-operation-registry.ts +22 -12
- package/lib/internal/task-orchestration.ts +1 -1
- package/lib/internal/task-review.ts +9 -5
- package/lib/platform/activity-recovery.ts +179 -0
- package/lib/platform/issue-comments.ts +98 -31
- package/lib/platform/task-comment-projection.ts +79 -0
- package/lib/platform/task-recovery.ts +54 -0
- package/lib/platform/verification-sync.ts +46 -13
- package/lib/sandbox/control/executor.ts +4 -2
- package/lib/sandbox/control/protocol.ts +1 -1
- package/lib/sandbox/control/task-workflow.ts +1 -1
- package/lib/sandbox/control/workflow-executor.ts +102 -21
- package/lib/server/process-state.ts +26 -5
- package/lib/task/artifact-command.ts +6 -4
- package/lib/task/artifact-operations.ts +1 -1
- package/lib/task/artifact-recovery.ts +120 -67
- package/lib/task/artifact-repair-intent.ts +38 -11
- package/lib/task/control-authority.ts +1 -1
- package/lib/task/events.ts +6 -6
- package/lib/task/intermediate-cleanup.ts +18 -2
- package/lib/task/local-artifact-finalization.ts +85 -11
- package/lib/task/review-command.ts +1 -1
- package/lib/task/review-finalization.ts +203 -8
- package/lib/{host-control → task}/secure-fs.ts +10 -32
- package/lib/task/workflow-command.ts +2 -0
- package/lib/task/workflow-dispatch.ts +171 -0
- package/package.json +1 -1
- package/templates/.agents/rules/issue-sync.en.md +9 -1
- package/templates/.agents/rules/issue-sync.zh-CN.md +10 -3
- package/templates/.agents/rules/local-artifact-repair.en.md +7 -6
- package/templates/.agents/rules/local-artifact-repair.zh-CN.md +10 -8
- package/templates/.agents/skills/code-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/restore-task/SKILL.en.md +9 -4
- package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +9 -4
- package/templates/.agents/skills/review-analysis/SKILL.en.md +1 -1
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/review-code/SKILL.en.md +3 -2
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +3 -2
- package/templates/.agents/skills/review-plan/SKILL.en.md +1 -1
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +1 -1
- package/dist/lib/host-control/audit.js +0 -19
- package/dist/lib/host-control/client.js +0 -142
- package/dist/lib/host-control/command.js +0 -49
- package/dist/lib/host-control/path.js +0 -160
- package/dist/lib/host-control/server.js +0 -159
- package/dist/lib/host-control/service.js +0 -112
- package/dist/lib/internal/host-control.js +0 -28
- package/lib/host-control/audit.ts +0 -22
- package/lib/host-control/client.ts +0 -172
- package/lib/host-control/command.ts +0 -60
- package/lib/host-control/path.ts +0 -167
- package/lib/host-control/server.ts +0 -180
- package/lib/host-control/service.ts +0 -133
- package/lib/internal/host-control.ts +0 -28
|
@@ -37,22 +37,28 @@ export function isArtifactRecoveryIntent(value) {
|
|
|
37
37
|
return false;
|
|
38
38
|
const intent = value;
|
|
39
39
|
return exactKeys(intent, [
|
|
40
|
-
'artifact', 'authorityDigest', 'baselineSemanticDigest', 'baselineSha256', 'candidateSha256',
|
|
40
|
+
'activeGenerationSemanticDigest', 'activeGenerationSha256', 'artifact', 'authorityDigest', 'baselineSemanticDigest', 'baselineSha256', 'candidateSha256',
|
|
41
41
|
'createdAt', 'errorCode', 'errorMessage', 'family', 'finalArtifactSha256', 'finalSemanticDigest',
|
|
42
|
-
'phase', 'recoveryOperationId', 'requestId', 'round', 'stagingId', 'state', 'taskId', 'updatedAt',
|
|
42
|
+
'pendingGenerationSemanticDigest', 'pendingGenerationSha256', 'phase', 'preflightArtifactSha256', 'preflightSemanticDigest', 'recoveryOperationId', 'requestId', 'round', 'stagingId', 'state', 'taskId', 'updatedAt',
|
|
43
43
|
'version'
|
|
44
44
|
])
|
|
45
|
-
&& intent.version ===
|
|
45
|
+
&& intent.version === 4
|
|
46
46
|
&& validTaskId(intent.taskId)
|
|
47
47
|
&& typeof intent.family === 'string'
|
|
48
48
|
&& ['analysis', 'review-analysis', 'plan', 'review-plan', 'code', 'review-code'].includes(intent.family)
|
|
49
49
|
&& Number.isSafeInteger(intent.round) && intent.round > 0
|
|
50
50
|
&& validArtifact(intent.artifact, intent.family, intent.round)
|
|
51
|
-
&& ['awaiting-recovery', '
|
|
51
|
+
&& ['awaiting-preflight-recovery', 'preflight-ready', 'preflight-commit-started', 'preflight-passed', 'full-finalizer-ready', 'commit-started', 'passed', 'consumption-started', 'consumed', 'aborted'].includes(String(intent.state))
|
|
52
52
|
&& validDigest(intent.baselineSha256)
|
|
53
53
|
&& validDigest(intent.baselineSemanticDigest)
|
|
54
54
|
&& validRecoveryId(intent.stagingId)
|
|
55
55
|
&& (intent.candidateSha256 === null || validDigest(intent.candidateSha256))
|
|
56
|
+
&& (intent.preflightArtifactSha256 === null || validDigest(intent.preflightArtifactSha256))
|
|
57
|
+
&& (intent.preflightSemanticDigest === null || validDigest(intent.preflightSemanticDigest))
|
|
58
|
+
&& (intent.activeGenerationSha256 === null || validDigest(intent.activeGenerationSha256))
|
|
59
|
+
&& (intent.activeGenerationSemanticDigest === null || validDigest(intent.activeGenerationSemanticDigest))
|
|
60
|
+
&& (intent.pendingGenerationSha256 === null || validDigest(intent.pendingGenerationSha256))
|
|
61
|
+
&& (intent.pendingGenerationSemanticDigest === null || validDigest(intent.pendingGenerationSemanticDigest))
|
|
56
62
|
&& (intent.finalArtifactSha256 === null || validDigest(intent.finalArtifactSha256))
|
|
57
63
|
&& (intent.finalSemanticDigest === null || validDigest(intent.finalSemanticDigest))
|
|
58
64
|
&& validRecoveryId(intent.recoveryOperationId)
|
|
@@ -64,11 +70,22 @@ export function isArtifactRecoveryIntent(value) {
|
|
|
64
70
|
&& (intent.errorMessage === null || validToken(intent.errorMessage))
|
|
65
71
|
&& Number.isSafeInteger(intent.createdAt)
|
|
66
72
|
&& Number.isSafeInteger(intent.updatedAt)
|
|
67
|
-
&& (intent.state === 'awaiting-recovery'
|
|
68
|
-
? intent.
|
|
73
|
+
&& (intent.state === 'awaiting-preflight-recovery'
|
|
74
|
+
? intent.preflightArtifactSha256 === null && intent.preflightSemanticDigest === null
|
|
75
|
+
&& intent.activeGenerationSha256 === null && intent.activeGenerationSemanticDigest === null
|
|
76
|
+
&& intent.pendingGenerationSha256 === null && intent.pendingGenerationSemanticDigest === null
|
|
77
|
+
&& intent.finalArtifactSha256 === null && intent.finalSemanticDigest === null
|
|
69
78
|
: true)
|
|
70
|
-
&& (intent.state === '
|
|
71
|
-
? validDigest(intent.candidateSha256) && validDigest(intent.
|
|
79
|
+
&& (intent.state === 'preflight-ready' || intent.state === 'preflight-commit-started' || intent.state === 'preflight-passed' || intent.state === 'full-finalizer-ready' || intent.state === 'commit-started' || intent.state === 'passed' || intent.state === 'consumed'
|
|
80
|
+
? validDigest(intent.candidateSha256) && validDigest(intent.preflightArtifactSha256) && validDigest(intent.preflightSemanticDigest)
|
|
81
|
+
&& validDigest(intent.activeGenerationSha256) && validDigest(intent.activeGenerationSemanticDigest)
|
|
82
|
+
: true)
|
|
83
|
+
&& (intent.state === 'full-finalizer-ready' || intent.state === 'commit-started'
|
|
84
|
+
? validDigest(intent.pendingGenerationSha256) && validDigest(intent.pendingGenerationSemanticDigest)
|
|
85
|
+
&& validDigest(intent.finalArtifactSha256) && validDigest(intent.finalSemanticDigest)
|
|
86
|
+
: true)
|
|
87
|
+
&& (intent.state === 'passed' || intent.state === 'consumption-started' || intent.state === 'consumed'
|
|
88
|
+
? validDigest(intent.finalArtifactSha256) && validDigest(intent.finalSemanticDigest)
|
|
72
89
|
: true)
|
|
73
90
|
&& (intent.state === 'aborted' ? intent.errorCode !== null && intent.errorMessage !== null : true);
|
|
74
91
|
}
|
|
@@ -266,7 +266,7 @@ function lifecycleRecoveryIntentMatches(intent, selector) {
|
|
|
266
266
|
return intent.taskId === selector.taskId
|
|
267
267
|
&& intent.family === selector.family
|
|
268
268
|
&& intent.artifact === selector.artifact
|
|
269
|
-
&& (intent.state === 'commit-started' || intent.state === 'consumed');
|
|
269
|
+
&& (intent.state === 'commit-started' || intent.state === 'consumption-started' || intent.state === 'consumed');
|
|
270
270
|
}
|
|
271
271
|
/**
|
|
272
272
|
* Completes only the durable tail of a committed task event after a process
|
package/dist/lib/task/events.js
CHANGED
|
@@ -334,7 +334,7 @@ function reconcileReviewCompletionReplay(repoRoot, taskDir, taskId, family, arti
|
|
|
334
334
|
}
|
|
335
335
|
intent = reconciled.intent;
|
|
336
336
|
}
|
|
337
|
-
if (!['passed', 'consumed'].includes(intent.state)) {
|
|
337
|
+
if (!['passed', 'consumption-started', 'consumed'].includes(intent.state)) {
|
|
338
338
|
return {
|
|
339
339
|
code: 'EVENT_ARTIFACT_CONFLICT',
|
|
340
340
|
message: `review finalizer provenance is incomplete for ${artifact}`
|
|
@@ -355,7 +355,7 @@ function reconcileReviewCompletionReplay(repoRoot, taskDir, taskId, family, arti
|
|
|
355
355
|
message: `review finalizer provenance does not match ${artifact}`
|
|
356
356
|
};
|
|
357
357
|
}
|
|
358
|
-
if (intent.state === 'passed') {
|
|
358
|
+
if (intent.state === 'passed' || intent.state === 'consumption-started') {
|
|
359
359
|
const consumed = { ...intent, state: 'consumed', updatedAt: Date.now() };
|
|
360
360
|
writeArtifactRecoveryIntent(repoRoot, consumed, { expected: intent });
|
|
361
361
|
}
|
|
@@ -935,7 +935,7 @@ function applyTaskEventUnlocked(request, options = {}) {
|
|
|
935
935
|
if (reconciled.status === 'passed' || reconciled.status === 'consumed')
|
|
936
936
|
reviewFinalizationIntent = reconciled.intent;
|
|
937
937
|
}
|
|
938
|
-
if (!reviewFinalizationIntent || !['passed', 'consumed'].includes(reviewFinalizationIntent.state)) {
|
|
938
|
+
if (!reviewFinalizationIntent || !['passed', 'consumption-started', 'consumed'].includes(reviewFinalizationIntent.state)) {
|
|
939
939
|
return failed(normalized, {
|
|
940
940
|
code: 'EVENT_ARTIFACT_CONFLICT',
|
|
941
941
|
message: `review finalizer provenance is missing or incomplete for ${completedArtifact.name}`
|
|
@@ -1106,7 +1106,7 @@ function applyTaskEventUnlocked(request, options = {}) {
|
|
|
1106
1106
|
const expectedIntent = localFinalizationIntent;
|
|
1107
1107
|
localFinalizationIntent = {
|
|
1108
1108
|
...localFinalizationIntent,
|
|
1109
|
-
state: '
|
|
1109
|
+
state: 'consumption-started',
|
|
1110
1110
|
phase: 'task-event.completed',
|
|
1111
1111
|
authorityDigest: lifecycleRecoveryAttestationDigest(lifecycleAuthority),
|
|
1112
1112
|
requestId: lifecycleAuthority.lifecycleRequestId,
|
|
@@ -1138,7 +1138,7 @@ function applyTaskEventUnlocked(request, options = {}) {
|
|
|
1138
1138
|
const expectedIntent = reviewFinalizationIntent;
|
|
1139
1139
|
reviewFinalizationIntent = {
|
|
1140
1140
|
...reviewFinalizationIntent,
|
|
1141
|
-
state: '
|
|
1141
|
+
state: 'consumption-started',
|
|
1142
1142
|
phase: 'task-event.completed',
|
|
1143
1143
|
updatedAt: Date.now()
|
|
1144
1144
|
};
|
|
@@ -1179,7 +1179,7 @@ function applyTaskEventUnlocked(request, options = {}) {
|
|
|
1179
1179
|
}
|
|
1180
1180
|
if (!normalized.dryRun && reviewFinalizationIntent && completedArtifact) {
|
|
1181
1181
|
try {
|
|
1182
|
-
if (reviewFinalizationIntent.state === '
|
|
1182
|
+
if (reviewFinalizationIntent.state === 'consumption-started') {
|
|
1183
1183
|
const consumed = { ...reviewFinalizationIntent, state: 'consumed', updatedAt: Date.now() };
|
|
1184
1184
|
writeArtifactRecoveryIntent(resolved.repoRoot, consumed, { expected: reviewFinalizationIntent });
|
|
1185
1185
|
}
|
|
@@ -292,12 +292,37 @@ function cleanupConsumedRecoveryFiles(repoRoot, task, candidate) {
|
|
|
292
292
|
return false;
|
|
293
293
|
}
|
|
294
294
|
for (const entry of entries) {
|
|
295
|
-
if (entry.isSymbolicLink()
|
|
295
|
+
if (entry.isSymbolicLink())
|
|
296
|
+
return false;
|
|
297
|
+
if (entry.isFile()) {
|
|
298
|
+
if (!['baseline.md', 'candidate.md', 'final.md', 'publish.md'].includes(entry.name))
|
|
299
|
+
return false;
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
if (!entry.isDirectory() || entry.name !== 'generations')
|
|
303
|
+
return false;
|
|
304
|
+
let generations;
|
|
305
|
+
try {
|
|
306
|
+
generations = fs.readdirSync(path.join(recovery, entry.name), { withFileTypes: true });
|
|
307
|
+
}
|
|
308
|
+
catch {
|
|
309
|
+
return false;
|
|
310
|
+
}
|
|
311
|
+
if (generations.some((generation) => generation.isSymbolicLink()
|
|
312
|
+
|| !generation.isFile() || !/^[a-f0-9]{64}\.md$/u.test(generation.name)))
|
|
296
313
|
return false;
|
|
297
314
|
}
|
|
298
315
|
try {
|
|
299
|
-
for (const entry of entries)
|
|
300
|
-
|
|
316
|
+
for (const entry of entries) {
|
|
317
|
+
const target = path.join(recovery, entry.name);
|
|
318
|
+
if (entry.isFile())
|
|
319
|
+
fs.unlinkSync(target);
|
|
320
|
+
else {
|
|
321
|
+
for (const generation of fs.readdirSync(target, { withFileTypes: true }))
|
|
322
|
+
fs.unlinkSync(path.join(target, generation.name));
|
|
323
|
+
fs.rmdirSync(target);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
301
326
|
fs.rmdirSync(recovery);
|
|
302
327
|
const recoveryRoot = path.dirname(recovery);
|
|
303
328
|
if (fs.readdirSync(recoveryRoot).length === 0)
|
|
@@ -8,7 +8,7 @@ import { expectedQualificationRelations, validateQualificationAudit } from './qu
|
|
|
8
8
|
import { canonicalSemanticDigest, inspectArtifactPatterns, inspectArtifactStructure, sha256Content } from './artifact-operations.js';
|
|
9
9
|
import { getArtifactSchema } from './artifact-schema.js';
|
|
10
10
|
import { readArtifactRecoveryIntent } from './artifact-repair-intent.js';
|
|
11
|
-
import { beginArtifactRecovery, commitArtifactRecovery, consumeArtifactRecovery, prepareArtifactRecoveryCommit, recordArtifactRecoveryPassed, recoveryContextFromIntent, stageArtifactCandidate } from './artifact-recovery.js';
|
|
11
|
+
import { beginArtifactRecovery, commitArtifactRecovery, consumeArtifactRecovery, prepareArtifactRecoveryFinal, prepareArtifactRecoveryCommit, recordArtifactRecoveryPassed, reconcileArtifactRecovery, recoveryContextFromIntent, stageArtifactCandidate } from './artifact-recovery.js';
|
|
12
12
|
import { consumeLifecycleRecoveryAttestation, lifecycleRecoveryAttestationDigest, validateLifecycleRecoveryAttestation } from './control-authority.js';
|
|
13
13
|
const LOCAL_ARTIFACT_REQUIRED_SECTIONS = {
|
|
14
14
|
analysis: getArtifactSchema('analysis').sections.map((section) => section.headings.zh),
|
|
@@ -136,7 +136,7 @@ function tupleFor(request, taskId, authority) {
|
|
|
136
136
|
authorityDigest: authority ? authorityDigest(authority) : null
|
|
137
137
|
};
|
|
138
138
|
}
|
|
139
|
-
function prepareLocalArtifact(request, candidate, authority) {
|
|
139
|
+
function prepareLocalArtifact(request, candidate, authority, preflightOnly = false) {
|
|
140
140
|
const failed = (code, message, extra = {}) => ({
|
|
141
141
|
result: failedFinalization(request, { code, message }, extra), content: candidate ?? ''
|
|
142
142
|
});
|
|
@@ -152,14 +152,16 @@ function prepareLocalArtifact(request, candidate, authority) {
|
|
|
152
152
|
let taskContent;
|
|
153
153
|
let content;
|
|
154
154
|
let recovery;
|
|
155
|
+
let recoveryState;
|
|
155
156
|
try {
|
|
156
157
|
taskContent = fs.readFileSync(resolved.taskMdPath, 'utf8');
|
|
157
158
|
if (request.recoveryId) {
|
|
158
159
|
const intent = readArtifactRecoveryIntent(resolved.repoRoot, resolved.taskId, request.family, request.artifact);
|
|
159
160
|
if (!intent || intent.recoveryOperationId !== request.recoveryId)
|
|
160
161
|
return failed('LOCAL_RECOVERY_PROVENANCE_CONFLICT', 'recovery id does not match the artifact journal');
|
|
161
|
-
if (!['awaiting-recovery', '
|
|
162
|
+
if (!['awaiting-preflight-recovery', 'preflight-ready', 'preflight-commit-started', 'preflight-passed', 'full-finalizer-ready', 'commit-started'].includes(intent.state))
|
|
162
163
|
return failed('LOCAL_RECOVERY_PROVENANCE_CONFLICT', `recovery journal is in '${intent.state}' state`);
|
|
164
|
+
recoveryState = intent.state;
|
|
163
165
|
recovery = recoveryContextFromIntent(resolved.repoRoot, resolved.taskDir, intent);
|
|
164
166
|
content = fs.existsSync(recovery.stagingPath)
|
|
165
167
|
? fs.readFileSync(recovery.stagingPath, 'utf8')
|
|
@@ -192,24 +194,63 @@ function prepareLocalArtifact(request, candidate, authority) {
|
|
|
192
194
|
if (request.recoveryId) {
|
|
193
195
|
if (!validation.ok)
|
|
194
196
|
return { result, content, repoRoot: resolved.repoRoot, recovery, authority, lockAlreadyHeld: request.lockAlreadyHeld };
|
|
197
|
+
if (preflightOnly && recovery && recoveryState === 'awaiting-preflight-recovery') {
|
|
198
|
+
const staged = stageArtifactCandidate(recovery, Buffer.from(content, 'utf8'), { lockAlreadyHeld: request.lockAlreadyHeld });
|
|
199
|
+
prepareArtifactRecoveryCommit(recovery, staged.candidateSha256, staged.semanticDigest, { lockAlreadyHeld: request.lockAlreadyHeld });
|
|
200
|
+
}
|
|
195
201
|
return {
|
|
196
|
-
result: { ...result, status: 'passed', error: null, recovery: recoveryInfo(recovery) },
|
|
202
|
+
result: { ...result, status: 'passed', changed: false, error: null, recovery: recoveryInfo(recovery) },
|
|
197
203
|
content, repoRoot: resolved.repoRoot, recovery, authority, lockAlreadyHeld: request.lockAlreadyHeld
|
|
198
204
|
};
|
|
199
205
|
}
|
|
200
206
|
const existing = readArtifactRecoveryIntent(resolved.repoRoot, resolved.taskId, request.family, request.artifact);
|
|
201
207
|
if (existing?.state === 'passed' || existing?.state === 'consumed') {
|
|
202
|
-
if (existing.finalArtifactSha256 !== artifactSha256 || existing.finalSemanticDigest !== validation.semanticDigest) {
|
|
208
|
+
if (existing.state === 'consumed' && (existing.finalArtifactSha256 !== artifactSha256 || existing.finalSemanticDigest !== validation.semanticDigest)) {
|
|
203
209
|
return { ...failed('LOCAL_RECOVERY_PROVENANCE_CONFLICT', 'formal artifact does not match its completed recovery journal'), content, repoRoot: resolved.repoRoot, authority, lockAlreadyHeld: request.lockAlreadyHeld };
|
|
204
210
|
}
|
|
211
|
+
if (existing.finalArtifactSha256 === artifactSha256 && existing.finalSemanticDigest === validation.semanticDigest) {
|
|
212
|
+
const context = recoveryContextFromIntent(resolved.repoRoot, resolved.taskDir, existing);
|
|
213
|
+
return {
|
|
214
|
+
result: { ...result, status: 'passed', error: null, recovery: recoveryInfo(context) },
|
|
215
|
+
content, repoRoot: resolved.repoRoot, recovery: context, authority, lockAlreadyHeld: request.lockAlreadyHeld
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
if (existing && ['preflight-ready', 'preflight-commit-started', 'preflight-passed', 'full-finalizer-ready'].includes(existing.state)) {
|
|
220
|
+
const context = recoveryContextFromIntent(resolved.repoRoot, resolved.taskDir, existing);
|
|
221
|
+
return {
|
|
222
|
+
result: { ...result, status: 'passed', changed: false, error: null, recovery: recoveryInfo(context) },
|
|
223
|
+
content, repoRoot: resolved.repoRoot, recovery: context, authority, lockAlreadyHeld: request.lockAlreadyHeld
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
if (existing?.state === 'commit-started') {
|
|
227
|
+
if (existing.finalArtifactSha256 !== artifactSha256 || existing.finalSemanticDigest !== validation.semanticDigest) {
|
|
228
|
+
return { ...failed('LOCAL_RECOVERY_PROVENANCE_CONFLICT', 'formal artifact does not match the interrupted recovery journal'), content, repoRoot: resolved.repoRoot, authority, lockAlreadyHeld: request.lockAlreadyHeld };
|
|
229
|
+
}
|
|
205
230
|
const context = recoveryContextFromIntent(resolved.repoRoot, resolved.taskDir, existing);
|
|
231
|
+
const reconciled = reconcileArtifactRecovery(context, {
|
|
232
|
+
lockAlreadyHeld: request.lockAlreadyHeld,
|
|
233
|
+
validateFinal: () => validation.semanticDigest === existing.finalSemanticDigest
|
|
234
|
+
});
|
|
235
|
+
if (reconciled.status !== 'passed') {
|
|
236
|
+
return { ...failed('LOCAL_RECOVERY_PROVENANCE_CONFLICT', `interrupted recovery could not be reconciled: ${reconciled.status}`), content, repoRoot: resolved.repoRoot, recovery: context, authority, lockAlreadyHeld: request.lockAlreadyHeld };
|
|
237
|
+
}
|
|
206
238
|
return {
|
|
207
|
-
result: { ...result, status: 'passed', error: null, recovery: recoveryInfo(context) },
|
|
239
|
+
result: { ...result, status: 'passed', changed: false, error: null, recovery: recoveryInfo(context) },
|
|
208
240
|
content, repoRoot: resolved.repoRoot, recovery: context, authority, lockAlreadyHeld: request.lockAlreadyHeld
|
|
209
241
|
};
|
|
210
242
|
}
|
|
211
243
|
try {
|
|
212
244
|
if (validation.ok) {
|
|
245
|
+
if (preflightOnly) {
|
|
246
|
+
const context = beginArtifactRecovery(tupleFor(request, resolved.taskId, authority), Buffer.from(content, 'utf8'), {
|
|
247
|
+
repoRoot: resolved.repoRoot, taskDir: resolved.taskDir,
|
|
248
|
+
...(authority ? { recoveryId: authority.operationId } : {}), lockAlreadyHeld: request.lockAlreadyHeld
|
|
249
|
+
});
|
|
250
|
+
const staged = stageArtifactCandidate(context, Buffer.from(content, 'utf8'), { lockAlreadyHeld: request.lockAlreadyHeld });
|
|
251
|
+
prepareArtifactRecoveryCommit(context, staged.candidateSha256, staged.semanticDigest, { lockAlreadyHeld: request.lockAlreadyHeld });
|
|
252
|
+
return { result: { ...result, status: 'passed', error: null, recovery: recoveryInfo(context) }, content, repoRoot: resolved.repoRoot, recovery: context, authority, lockAlreadyHeld: request.lockAlreadyHeld };
|
|
253
|
+
}
|
|
213
254
|
const context = recordArtifactRecoveryPassed(tupleFor(request, resolved.taskId, authority), {
|
|
214
255
|
repoRoot: resolved.repoRoot,
|
|
215
256
|
taskDir: resolved.taskDir,
|
|
@@ -219,7 +260,7 @@ function prepareLocalArtifact(request, candidate, authority) {
|
|
|
219
260
|
lockAlreadyHeld: request.lockAlreadyHeld
|
|
220
261
|
});
|
|
221
262
|
recovery = context;
|
|
222
|
-
return { result: { ...result, status: 'passed', error: null, recovery: recoveryInfo(context) }, content, repoRoot: resolved.repoRoot, recovery, authority, lockAlreadyHeld: request.lockAlreadyHeld };
|
|
263
|
+
return { result: { ...result, status: 'passed', changed: false, error: null, recovery: recoveryInfo(context) }, content, repoRoot: resolved.repoRoot, recovery, authority, lockAlreadyHeld: request.lockAlreadyHeld };
|
|
223
264
|
}
|
|
224
265
|
const context = beginArtifactRecovery(tupleFor(request, resolved.taskId, authority), Buffer.from(content, 'utf8'), {
|
|
225
266
|
repoRoot: resolved.repoRoot,
|
|
@@ -237,7 +278,7 @@ function prepareLocalArtifact(request, candidate, authority) {
|
|
|
237
278
|
return { ...failed('LOCAL_RECOVERY_PROVENANCE_CONFLICT', String(error)), content, repoRoot: resolved.repoRoot, authority, lockAlreadyHeld: request.lockAlreadyHeld };
|
|
238
279
|
}
|
|
239
280
|
}
|
|
240
|
-
function commitLocalArtifactProvenance(prepared) {
|
|
281
|
+
function commitLocalArtifactProvenance(prepared, options = {}) {
|
|
241
282
|
if (!prepared.recovery || !prepared.repoRoot || prepared.result.status === 'failed' && !prepared.result.recovery)
|
|
242
283
|
return prepared.result;
|
|
243
284
|
if (prepared.result.status === 'failed')
|
|
@@ -250,30 +291,48 @@ function commitLocalArtifactProvenance(prepared) {
|
|
|
250
291
|
if (intent.state === 'passed' || intent.state === 'consumed') {
|
|
251
292
|
return {
|
|
252
293
|
...prepared.result,
|
|
294
|
+
changed: false,
|
|
253
295
|
artifactSha256: intent.finalArtifactSha256,
|
|
254
296
|
semanticDigest: intent.finalSemanticDigest,
|
|
255
297
|
error: null
|
|
256
298
|
};
|
|
257
299
|
}
|
|
258
|
-
else if (intent.state === '
|
|
259
|
-
|
|
300
|
+
else if (intent.state === 'commit-started') {
|
|
301
|
+
const reconciled = reconcileArtifactRecovery(prepared.recovery, {
|
|
302
|
+
lockAlreadyHeld: prepared.lockAlreadyHeld,
|
|
303
|
+
afterPublish: options.afterPublish
|
|
304
|
+
});
|
|
305
|
+
committed = reconciled.intent;
|
|
260
306
|
}
|
|
261
|
-
else {
|
|
307
|
+
else if (intent.state === 'awaiting-preflight-recovery') {
|
|
262
308
|
const staged = stageArtifactCandidate(prepared.recovery, Buffer.from(prepared.content, 'utf8'), { lockAlreadyHeld: prepared.lockAlreadyHeld });
|
|
263
309
|
prepareArtifactRecoveryCommit(prepared.recovery, staged.candidateSha256, staged.semanticDigest, { lockAlreadyHeld: prepared.lockAlreadyHeld });
|
|
264
310
|
committed = commitArtifactRecovery(prepared.recovery, { lockAlreadyHeld: prepared.lockAlreadyHeld });
|
|
265
311
|
}
|
|
266
|
-
if (
|
|
267
|
-
|
|
312
|
+
if (intent.state === 'preflight-ready' || intent.state === 'preflight-commit-started') {
|
|
313
|
+
committed = commitArtifactRecovery(prepared.recovery, { lockAlreadyHeld: prepared.lockAlreadyHeld });
|
|
314
|
+
}
|
|
315
|
+
if (committed?.state === 'preflight-passed' || intent.state === 'preflight-passed') {
|
|
316
|
+
prepareArtifactRecoveryFinal(prepared.recovery, Buffer.from(prepared.content, 'utf8'), { lockAlreadyHeld: prepared.lockAlreadyHeld });
|
|
317
|
+
committed = commitArtifactRecovery(prepared.recovery, { lockAlreadyHeld: prepared.lockAlreadyHeld, afterPublish: options.afterPublish });
|
|
318
|
+
}
|
|
319
|
+
else if (intent.state === 'full-finalizer-ready') {
|
|
320
|
+
committed = commitArtifactRecovery(prepared.recovery, { lockAlreadyHeld: prepared.lockAlreadyHeld, afterPublish: options.afterPublish });
|
|
321
|
+
}
|
|
322
|
+
if (!committed || committed.state !== 'passed')
|
|
323
|
+
throw new Error(`ARTIFACT_RECOVERY_STATE_INVALID: commit ended in '${committed?.state ?? intent.state}'`);
|
|
268
324
|
return {
|
|
269
325
|
...prepared.result,
|
|
270
326
|
status: 'passed',
|
|
327
|
+
changed: false,
|
|
271
328
|
artifactSha256: committed.finalArtifactSha256,
|
|
272
329
|
semanticDigest: committed.finalSemanticDigest,
|
|
273
330
|
error: null
|
|
274
331
|
};
|
|
275
332
|
}
|
|
276
333
|
catch (error) {
|
|
334
|
+
if (error instanceof Error && error.message.startsWith('TASK_WORKFLOW_FAULT_INJECTED:'))
|
|
335
|
+
throw error;
|
|
277
336
|
return { ...prepared.result, status: 'failed', error: {
|
|
278
337
|
code: 'LOCAL_RECOVERY_COMMIT_FAILED', message: String(error)
|
|
279
338
|
} };
|
|
@@ -295,5 +354,10 @@ function finalizeLocalArtifact(request, authority, options = {}) {
|
|
|
295
354
|
}
|
|
296
355
|
return result;
|
|
297
356
|
}
|
|
298
|
-
|
|
357
|
+
/** Validate and seal an immutable preflight generation without publishing the formal artifact. */
|
|
358
|
+
function preflightLocalArtifact(request, authority) {
|
|
359
|
+
const prepared = prepareLocalArtifact(request, undefined, authority, true);
|
|
360
|
+
return prepared.result;
|
|
361
|
+
}
|
|
362
|
+
export { LOCAL_ARTIFACT_REQUIRED_SECTIONS, consumeLocalArtifactFinalizationIntent, finalizeLocalArtifact, preflightLocalArtifact, prepareLocalArtifact, commitLocalArtifactProvenance, readArtifactRecoveryIntent as readLocalArtifactFinalizationIntent, canonicalSemanticDigest as semanticDigest, sha256Content, validateLocalArtifact };
|
|
299
363
|
//# sourceMappingURL=local-artifact-finalization.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export function parseReviewCommand(args) {
|
|
2
|
-
if (!args[0] || args[1]
|
|
3
|
-
throw new Error('task ref and
|
|
2
|
+
if (!args[0] || !['preflight', 'finalize-summary'].includes(args[1] ?? ''))
|
|
3
|
+
throw new Error('task ref and a supported review operation are required');
|
|
4
4
|
const values = {};
|
|
5
5
|
const flags = {
|
|
6
6
|
'--stage': 'stage', '--artifact': 'artifact', '--orchestrated': 'orchestrated', '--dry-run': 'dryRun',
|