@fitlab-ai/agent-infra 0.11.0 → 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/manual-validation.js +6 -2
- 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/issues.js +3 -4
- 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 +161 -156
- 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 +32 -41
- package/dist/lib/task/artifact-command.js +6 -6
- package/dist/lib/task/artifact-lifecycle.js +15 -52
- package/dist/lib/task/artifact-operations.js +27 -1
- package/dist/lib/task/capabilities.js +0 -2
- 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 +147 -278
- package/dist/lib/task/finalization-state.js +2 -1
- package/dist/lib/task/finalization.js +273 -49
- package/dist/lib/task/gate-policy.js +94 -0
- package/dist/lib/task/lifecycle-execution.js +2 -11
- package/dist/lib/task/local-artifact-finalization.js +31 -245
- package/dist/lib/task/orchestration.js +8 -24
- package/dist/lib/task/review-artifacts.js +12 -6
- package/dist/lib/task/review-command.js +0 -1
- package/dist/lib/task/review-finalization.js +47 -218
- package/dist/lib/task/verification-engine.js +28 -17
- package/dist/lib/task/verification.js +25 -9
- 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/manual-validation.ts +6 -2
- 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/issues.ts +3 -4
- package/lib/platform/provider-contract.ts +2 -0
- package/lib/platform/provider-validation.ts +6 -2
- package/lib/platform/verification-sync.ts +178 -179
- 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 +31 -48
- package/lib/task/artifact-command.ts +6 -7
- package/lib/task/artifact-lifecycle.ts +12 -49
- package/lib/task/artifact-operations.ts +27 -1
- package/lib/task/capabilities.ts +0 -1
- 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 +155 -287
- package/lib/task/finalization-state.ts +2 -1
- package/lib/task/finalization.ts +277 -57
- package/lib/task/gate-policy.ts +133 -0
- package/lib/task/lifecycle-execution.ts +10 -21
- package/lib/task/local-artifact-finalization.ts +29 -285
- package/lib/task/orchestration.ts +12 -41
- package/lib/task/review-artifacts.ts +8 -2
- package/lib/task/review-command.ts +0 -1
- package/lib/task/review-finalization.ts +46 -200
- package/lib/task/verification-engine.ts +24 -14
- package/lib/task/verification-types.ts +7 -0
- package/lib/task/verification.ts +25 -9
- 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/issue-sync.en.md +2 -0
- package/templates/.agents/rules/issue-sync.zh-CN.md +5 -8
- 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 -52
- package/templates/.agents/rules/local-artifact-repair.zh-CN.md +13 -52
- 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/analyze-task/config/verify.en.json +1 -10
- package/templates/.agents/skills/analyze-task/config/verify.zh-CN.json +1 -10
- package/templates/.agents/skills/block-task/config/verify.json +1 -3
- package/templates/.agents/skills/cancel-task/config/verify.json +1 -4
- 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/code-task/config/verify.en.json +1 -12
- package/templates/.agents/skills/code-task/config/verify.zh-CN.json +1 -12
- package/templates/.agents/skills/code-task/reference/dual-mode.en.md +1 -1
- package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +1 -1
- package/templates/.agents/skills/code-task/reference/report-template.en.md +11 -0
- package/templates/.agents/skills/code-task/reference/report-template.zh-CN.md +11 -0
- package/templates/.agents/skills/commit/config/verify.json +1 -5
- package/templates/.agents/skills/complete-manual-validation/config/verify.en.json +6 -6
- package/templates/.agents/skills/complete-manual-validation/config/verify.zh-CN.json +6 -6
- 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/complete-task/config/verify.en.json +3 -14
- package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +3 -14
- package/templates/.agents/skills/create-pr/config/verify.json +1 -10
- package/templates/.agents/skills/create-task/config/verify.json +1 -4
- package/templates/.agents/skills/import-issue/config/verify.json +1 -2
- 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/plan-task/config/verify.en.json +1 -10
- package/templates/.agents/skills/plan-task/config/verify.zh-CN.json +1 -10
- package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +4 -4
- 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-analysis/config/verify.en.json +1 -10
- package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +1 -10
- package/templates/.agents/skills/review-analysis/reference/report-template.en.md +2 -0
- package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +2 -0
- 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-code/config/verify.en.json +1 -10
- package/templates/.agents/skills/review-code/config/verify.zh-CN.json +1 -10
- package/templates/.agents/skills/review-code/reference/report-template.en.md +2 -0
- package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +2 -0
- 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/review-plan/config/verify.en.json +1 -10
- package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +1 -10
- package/templates/.agents/skills/review-plan/reference/report-template.en.md +2 -0
- package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +2 -0
- package/templates/.agents/skills/review-pr/config/verify.en.json +1 -8
- package/templates/.agents/skills/review-pr/config/verify.zh-CN.json +1 -8
- package/templates/.agents/skills/run-manual-validation/config/verify.en.json +38 -13
- package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +38 -13
- 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,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',
|
|
@@ -97,13 +58,16 @@ function validateLocalArtifact(content, options) {
|
|
|
97
58
|
if (!decisionDetails.ok)
|
|
98
59
|
diagnostics.push({ code: 'LOCAL_DECISION_DETAIL_DUPLICATE', message: decisionDetails.message, line: null });
|
|
99
60
|
if (options.taskContent !== undefined) {
|
|
100
|
-
const
|
|
101
|
-
|
|
61
|
+
const identity = options.artifact ? parseArtifactName(options.artifact) : null;
|
|
62
|
+
const expected = identity?.family === options.family && hasOpenArtifactRound(options.taskContent, options.family, identity.round)
|
|
63
|
+
? expectedQualificationRelations(options.taskContent, options.family)
|
|
64
|
+
: undefined;
|
|
65
|
+
if (expected && !expected.ok)
|
|
102
66
|
diagnostics.push({ code: 'LOCAL_QUALIFICATION_AUDIT_INVALID', message: `${expected.code}: ${expected.message}`, line: null });
|
|
103
67
|
const qualification = validateQualificationAudit(options.taskContent, content, {
|
|
104
68
|
family: options.family,
|
|
105
69
|
artifact: options.artifact,
|
|
106
|
-
expectedUpstreamRelations: expected
|
|
70
|
+
expectedUpstreamRelations: expected?.ok ? expected.relations : undefined
|
|
107
71
|
});
|
|
108
72
|
if (!qualification.ok)
|
|
109
73
|
diagnostics.push({ code: 'LOCAL_QUALIFICATION_AUDIT_INVALID', message: `${qualification.code}: ${qualification.message}`, line: null });
|
|
@@ -124,19 +88,7 @@ function failedFinalization(request, error, extra = {}) {
|
|
|
124
88
|
diagnostics: [], error, ...extra
|
|
125
89
|
};
|
|
126
90
|
}
|
|
127
|
-
function
|
|
128
|
-
const round = parseArtifactName(request.artifact)?.round ?? 0;
|
|
129
|
-
return {
|
|
130
|
-
taskId,
|
|
131
|
-
family: request.family,
|
|
132
|
-
artifact: request.artifact,
|
|
133
|
-
round,
|
|
134
|
-
requestId: authority?.lifecycleRequestId ?? `local-finalize:${taskId}:${request.family}:${round}`,
|
|
135
|
-
phase: authority?.phase ?? 'artifact.finalize-local',
|
|
136
|
-
authorityDigest: authority ? authorityDigest(authority) : null
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
function prepareLocalArtifact(request, candidate, authority, preflightOnly = false) {
|
|
91
|
+
function prepareLocalArtifact(request, candidate) {
|
|
140
92
|
const failed = (code, message, extra = {}) => ({
|
|
141
93
|
result: failedFinalization(request, { code, message }, extra), content: candidate ?? ''
|
|
142
94
|
});
|
|
@@ -146,39 +98,20 @@ function prepareLocalArtifact(request, candidate, authority, preflightOnly = fal
|
|
|
146
98
|
const parsed = parseArtifactName(request.artifact);
|
|
147
99
|
if (!parsed || parsed.family !== request.family)
|
|
148
100
|
return failed('ARTIFACT_IDENTITY_INVALID', `artifact '${request.artifact}' does not match ${request.family}`);
|
|
149
|
-
if (authority && !validFinalizerAuthority(authority, request, resolved.taskId)) {
|
|
150
|
-
return failed('LOCAL_EXECUTION_AUTHORITY_INVALID', 'finalizer authority does not match the artifact tuple');
|
|
151
|
-
}
|
|
152
101
|
let taskContent;
|
|
153
102
|
let content;
|
|
154
|
-
let recovery;
|
|
155
|
-
let recoveryState;
|
|
156
103
|
try {
|
|
157
104
|
taskContent = fs.readFileSync(resolved.taskMdPath, 'utf8');
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
if (!['awaiting-preflight-recovery', 'preflight-ready', 'preflight-commit-started', 'preflight-passed', 'full-finalizer-ready', 'commit-started'].includes(intent.state))
|
|
163
|
-
return failed('LOCAL_RECOVERY_PROVENANCE_CONFLICT', `recovery journal is in '${intent.state}' state`);
|
|
164
|
-
recoveryState = intent.state;
|
|
165
|
-
recovery = recoveryContextFromIntent(resolved.repoRoot, resolved.taskDir, intent);
|
|
166
|
-
content = fs.existsSync(recovery.stagingPath)
|
|
167
|
-
? fs.readFileSync(recovery.stagingPath, 'utf8')
|
|
168
|
-
: fs.readFileSync(recovery.formalPath, 'utf8');
|
|
169
|
-
}
|
|
170
|
-
else {
|
|
171
|
-
const validated = validateCompletedArtifact(resolved.taskDir, request.family, request.artifact, parsed.round);
|
|
172
|
-
if (!validated.ok)
|
|
173
|
-
return failed(validated.error.code, validated.error.message);
|
|
174
|
-
content = candidate ?? fs.readFileSync(validated.artifact.path, 'utf8');
|
|
175
|
-
}
|
|
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');
|
|
176
109
|
}
|
|
177
110
|
catch (error) {
|
|
178
111
|
return failed('ARTIFACT_NOT_READABLE', String(error));
|
|
179
112
|
}
|
|
180
|
-
if (resolved.state !== 'active'
|
|
181
|
-
return failed('ARTIFACT_IDENTITY_INVALID', '
|
|
113
|
+
if (resolved.state !== 'active') {
|
|
114
|
+
return failed('ARTIFACT_IDENTITY_INVALID', 'artifact finalization requires an active task');
|
|
182
115
|
}
|
|
183
116
|
const validation = validateLocalArtifact(content, {
|
|
184
117
|
family: request.family, requiredSections: request.requiredSections, taskContent, artifact: request.artifact
|
|
@@ -191,173 +124,26 @@ function prepareLocalArtifact(request, candidate, authority, preflightOnly = fal
|
|
|
191
124
|
taskId: resolved.taskId, taskDir: resolved.taskDir, artifactSha256,
|
|
192
125
|
semanticDigest: validation.semanticDigest, diagnostics: validation.diagnostics
|
|
193
126
|
});
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
content, repoRoot: resolved.repoRoot, recovery, authority, lockAlreadyHeld: request.lockAlreadyHeld
|
|
204
|
-
};
|
|
205
|
-
}
|
|
206
|
-
const existing = readArtifactRecoveryIntent(resolved.repoRoot, resolved.taskId, request.family, request.artifact);
|
|
207
|
-
if (existing?.state === 'passed' || existing?.state === 'consumed') {
|
|
208
|
-
if (existing.state === 'consumed' && (existing.finalArtifactSha256 !== artifactSha256 || existing.finalSemanticDigest !== validation.semanticDigest)) {
|
|
209
|
-
return { ...failed('LOCAL_RECOVERY_PROVENANCE_CONFLICT', 'formal artifact does not match its completed recovery journal'), content, repoRoot: resolved.repoRoot, authority, lockAlreadyHeld: request.lockAlreadyHeld };
|
|
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
|
-
}
|
|
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
|
-
}
|
|
238
|
-
return {
|
|
239
|
-
result: { ...result, status: 'passed', changed: false, error: null, recovery: recoveryInfo(context) },
|
|
240
|
-
content, repoRoot: resolved.repoRoot, recovery: context, authority, lockAlreadyHeld: request.lockAlreadyHeld
|
|
241
|
-
};
|
|
242
|
-
}
|
|
243
|
-
try {
|
|
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
|
-
}
|
|
254
|
-
const context = recordArtifactRecoveryPassed(tupleFor(request, resolved.taskId, authority), {
|
|
255
|
-
repoRoot: resolved.repoRoot,
|
|
256
|
-
taskDir: resolved.taskDir,
|
|
257
|
-
expectedFinalSha256: artifactSha256,
|
|
258
|
-
expectedFinalSemanticDigest: validation.semanticDigest,
|
|
259
|
-
...(authority ? { recoveryId: authority.operationId } : {}),
|
|
260
|
-
lockAlreadyHeld: request.lockAlreadyHeld
|
|
261
|
-
});
|
|
262
|
-
recovery = context;
|
|
263
|
-
return { result: { ...result, status: 'passed', changed: false, error: null, recovery: recoveryInfo(context) }, content, repoRoot: resolved.repoRoot, recovery, authority, lockAlreadyHeld: request.lockAlreadyHeld };
|
|
264
|
-
}
|
|
265
|
-
const context = beginArtifactRecovery(tupleFor(request, resolved.taskId, authority), Buffer.from(content, 'utf8'), {
|
|
266
|
-
repoRoot: resolved.repoRoot,
|
|
267
|
-
taskDir: resolved.taskDir,
|
|
268
|
-
...(authority ? { recoveryId: authority.operationId } : {}),
|
|
269
|
-
lockAlreadyHeld: request.lockAlreadyHeld
|
|
270
|
-
});
|
|
271
|
-
recovery = context;
|
|
272
|
-
return {
|
|
273
|
-
result: { ...result, recovery: recoveryInfo(context) },
|
|
274
|
-
content, repoRoot: resolved.repoRoot, recovery, authority, lockAlreadyHeld: request.lockAlreadyHeld
|
|
275
|
-
};
|
|
276
|
-
}
|
|
277
|
-
catch (error) {
|
|
278
|
-
return { ...failed('LOCAL_RECOVERY_PROVENANCE_CONFLICT', String(error)), content, repoRoot: resolved.repoRoot, authority, lockAlreadyHeld: request.lockAlreadyHeld };
|
|
279
|
-
}
|
|
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
|
+
};
|
|
280
136
|
}
|
|
281
|
-
function commitLocalArtifactProvenance(prepared
|
|
282
|
-
|
|
283
|
-
return prepared.result;
|
|
284
|
-
if (prepared.result.status === 'failed')
|
|
285
|
-
return prepared.result;
|
|
286
|
-
try {
|
|
287
|
-
const intent = readArtifactRecoveryIntent(prepared.repoRoot, prepared.recovery.taskId, prepared.recovery.family, prepared.recovery.artifact);
|
|
288
|
-
if (!intent)
|
|
289
|
-
throw new Error('ARTIFACT_RECOVERY_INTENT_MISSING');
|
|
290
|
-
let committed;
|
|
291
|
-
if (intent.state === 'passed' || intent.state === 'consumed') {
|
|
292
|
-
return {
|
|
293
|
-
...prepared.result,
|
|
294
|
-
changed: false,
|
|
295
|
-
artifactSha256: intent.finalArtifactSha256,
|
|
296
|
-
semanticDigest: intent.finalSemanticDigest,
|
|
297
|
-
error: null
|
|
298
|
-
};
|
|
299
|
-
}
|
|
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;
|
|
306
|
-
}
|
|
307
|
-
else if (intent.state === 'awaiting-preflight-recovery') {
|
|
308
|
-
const staged = stageArtifactCandidate(prepared.recovery, Buffer.from(prepared.content, 'utf8'), { lockAlreadyHeld: prepared.lockAlreadyHeld });
|
|
309
|
-
prepareArtifactRecoveryCommit(prepared.recovery, staged.candidateSha256, staged.semanticDigest, { lockAlreadyHeld: prepared.lockAlreadyHeld });
|
|
310
|
-
committed = commitArtifactRecovery(prepared.recovery, { lockAlreadyHeld: prepared.lockAlreadyHeld });
|
|
311
|
-
}
|
|
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}'`);
|
|
324
|
-
return {
|
|
325
|
-
...prepared.result,
|
|
326
|
-
status: 'passed',
|
|
327
|
-
changed: false,
|
|
328
|
-
artifactSha256: committed.finalArtifactSha256,
|
|
329
|
-
semanticDigest: committed.finalSemanticDigest,
|
|
330
|
-
error: null
|
|
331
|
-
};
|
|
332
|
-
}
|
|
333
|
-
catch (error) {
|
|
334
|
-
if (error instanceof Error && error.message.startsWith('TASK_WORKFLOW_FAULT_INJECTED:'))
|
|
335
|
-
throw error;
|
|
336
|
-
return { ...prepared.result, status: 'failed', error: {
|
|
337
|
-
code: 'LOCAL_RECOVERY_COMMIT_FAILED', message: String(error)
|
|
338
|
-
} };
|
|
339
|
-
}
|
|
137
|
+
function commitLocalArtifactProvenance(prepared) {
|
|
138
|
+
return prepared.result;
|
|
340
139
|
}
|
|
341
|
-
function finalizeLocalArtifact(request
|
|
342
|
-
|
|
343
|
-
const result = commitLocalArtifactProvenance(prepared);
|
|
344
|
-
if (authority && !options.deferLifecycleRecoveryConsumption && result.status === 'passed') {
|
|
345
|
-
try {
|
|
346
|
-
consumeLifecycleRecoveryAttestation(authority);
|
|
347
|
-
}
|
|
348
|
-
catch (error) {
|
|
349
|
-
return { ...result, status: 'failed', error: {
|
|
350
|
-
code: 'LOCAL_EXECUTION_AUTHORITY_CONSUME_FAILED',
|
|
351
|
-
message: error instanceof Error ? error.message : String(error)
|
|
352
|
-
} };
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
return result;
|
|
140
|
+
function finalizeLocalArtifact(request) {
|
|
141
|
+
return prepareLocalArtifact(request).result;
|
|
356
142
|
}
|
|
357
143
|
/** Validate and seal an immutable preflight generation without publishing the formal artifact. */
|
|
358
|
-
function preflightLocalArtifact(request
|
|
359
|
-
const prepared = prepareLocalArtifact(request
|
|
144
|
+
function preflightLocalArtifact(request) {
|
|
145
|
+
const prepared = prepareLocalArtifact(request);
|
|
360
146
|
return prepared.result;
|
|
361
147
|
}
|
|
362
|
-
export { LOCAL_ARTIFACT_REQUIRED_SECTIONS,
|
|
148
|
+
export { LOCAL_ARTIFACT_REQUIRED_SECTIONS, finalizeLocalArtifact, preflightLocalArtifact, prepareLocalArtifact, commitLocalArtifactProvenance, canonicalSemanticDigest as semanticDigest, sha256Content, validateLocalArtifact };
|
|
363
149
|
//# sourceMappingURL=local-artifact-finalization.js.map
|
|
@@ -20,7 +20,7 @@ import { extractReviewBaseline, extractReviewDiffBase, extractReviewTargetHead,
|
|
|
20
20
|
import { resolveDeliveryTarget, resolveDiffBase, resolveTargetHead } from './delivery-target.js';
|
|
21
21
|
import { buildLifecycleFacts, canStart, recommendNext } from './capabilities.js';
|
|
22
22
|
import { hasOpenLifecycleExecution } from './activity-log.js';
|
|
23
|
-
import {
|
|
23
|
+
import { reconcileTaskInvalidation } from './invalidation-command.js';
|
|
24
24
|
import { normalizeAgentToken } from '../agent-clients/tokens.js';
|
|
25
25
|
function supportsLifecycleDelegation(client) {
|
|
26
26
|
return getAgentClientCapability(client, 'subagents').level !== 'unsupported'
|
|
@@ -511,14 +511,12 @@ function routeOrchestration(taskRef, options = {}) {
|
|
|
511
511
|
}
|
|
512
512
|
let metadata;
|
|
513
513
|
let currentLedger = null;
|
|
514
|
-
let currentInvalidation = { operations: [], targets: [] };
|
|
515
514
|
if (resolved.state === 'active') {
|
|
516
515
|
const contract = validateCurrentTaskContract(content);
|
|
517
516
|
if (!contract.ok)
|
|
518
517
|
return failed('ORCHESTRATION_CURRENT_CONTRACT_INVALID', contract.message, resolved.taskId);
|
|
519
518
|
metadata = contract.metadata;
|
|
520
519
|
currentLedger = contract.ledger;
|
|
521
|
-
currentInvalidation = contract.invalidation;
|
|
522
520
|
}
|
|
523
521
|
else {
|
|
524
522
|
try {
|
|
@@ -528,9 +526,6 @@ function routeOrchestration(taskRef, options = {}) {
|
|
|
528
526
|
return failed('ORCHESTRATION_TASK_INVALID', error instanceof Error ? error.message : String(error), resolved.taskId);
|
|
529
527
|
}
|
|
530
528
|
}
|
|
531
|
-
if (invalidationBlocks(currentInvalidation)) {
|
|
532
|
-
return failed('ORCHESTRATION_INVALIDATION_INCOMPLETE', 'artifact invalidation is incomplete; reconcile task.md before routing downstream work', resolved.taskId);
|
|
533
|
-
}
|
|
534
529
|
let run;
|
|
535
530
|
try {
|
|
536
531
|
run = readRun(resolved.taskDir, options);
|
|
@@ -545,6 +540,9 @@ function routeOrchestration(taskRef, options = {}) {
|
|
|
545
540
|
if (!routed)
|
|
546
541
|
return failed('ORCHESTRATION_ROUTE_UNKNOWN', 'cannot determine a unique lifecycle action', resolved.taskId);
|
|
547
542
|
if ('completion' in routed) {
|
|
543
|
+
if (facts.facts.executionBusy && !run?.pendingDelegation) {
|
|
544
|
+
return failed('ORCHESTRATION_EXECUTION_BUSY', 'clean completion requires no open lifecycle execution', resolved.taskId);
|
|
545
|
+
}
|
|
548
546
|
const reviewRound = maxArtifactRound(fs.readdirSync(resolved.taskDir), 'review-code');
|
|
549
547
|
const review = parseReviewSummary(fs.readFileSync(path.join(resolved.taskDir, artifactName('review-code', reviewRound)), 'utf8'));
|
|
550
548
|
if (!review.ok || review.summary.manualValidation === null) {
|
|
@@ -670,6 +668,10 @@ function prepareOrchestrationDelegationUnlocked(taskRef, input, options = {}) {
|
|
|
670
668
|
}
|
|
671
669
|
if (run.stepCount >= run.maxSteps)
|
|
672
670
|
return pauseOrchestration(taskRef, 'ORCHESTRATION_MAX_STEPS', 'maximum orchestration steps reached', true, options);
|
|
671
|
+
const invalidation = reconcileTaskInvalidation(taskRef, { repoRoot: resolved.repoRoot });
|
|
672
|
+
if (invalidation.status === 'failed') {
|
|
673
|
+
return failed('ORCHESTRATION_INVALIDATION_RECONCILE_FAILED', `${invalidation.error?.code ?? 'UNKNOWN'}: ${invalidation.error?.message ?? ''}`, resolved.taskId);
|
|
674
|
+
}
|
|
673
675
|
const routed = routeOrchestration(taskRef, options);
|
|
674
676
|
if (!routed.next)
|
|
675
677
|
return routed;
|
|
@@ -680,16 +682,6 @@ function prepareOrchestrationDelegationUnlocked(taskRef, input, options = {}) {
|
|
|
680
682
|
if (!validModel(input.requestedReasoningEffort)) {
|
|
681
683
|
return failed('ORCHESTRATION_REQUESTED_REASONING_EFFORT_REQUIRED', 'prepare requires the exact requested reasoning effort', resolved.taskId);
|
|
682
684
|
}
|
|
683
|
-
if (input.client === 'codex' && !input.lifecycleProvenance) {
|
|
684
|
-
return failed('ORCHESTRATION_CODEX_PROVENANCE_REQUIRED', 'Codex prepare requires lifecycle provenance', resolved.taskId);
|
|
685
|
-
}
|
|
686
|
-
if (input.lifecycleRecoveryAttestation
|
|
687
|
-
&& (input.lifecycleRecoveryAttestation.phase !== 'orchestration.prepare'
|
|
688
|
-
|| input.lifecycleRecoveryAttestation.taskId !== resolved.taskId
|
|
689
|
-
|| input.lifecycleRecoveryAttestation.artifact !== next.artifact
|
|
690
|
-
|| input.lifecycleRecoveryAttestation.round !== next.round)) {
|
|
691
|
-
return failed('LIFECYCLE_AUTHORITY_TUPLE_MISMATCH', 'orchestration authority does not match the routed stage', resolved.taskId);
|
|
692
|
-
}
|
|
693
685
|
const expectedPolicy = run.modelPolicy[next.role];
|
|
694
686
|
if (input.requestedModel !== expectedPolicy.model) {
|
|
695
687
|
return failed('ORCHESTRATION_REQUESTED_MODEL_MISMATCH', `requested model does not match the persisted ${next.role} model`, resolved.taskId);
|
|
@@ -697,10 +689,6 @@ function prepareOrchestrationDelegationUnlocked(taskRef, input, options = {}) {
|
|
|
697
689
|
if (input.requestedReasoningEffort !== expectedPolicy.reasoningEffort) {
|
|
698
690
|
return failed('ORCHESTRATION_REQUESTED_REASONING_EFFORT_MISMATCH', `requested reasoning effort does not match the persisted ${next.role} policy`, resolved.taskId);
|
|
699
691
|
}
|
|
700
|
-
const capabilityValidationError = options.validateLifecycleCapability?.();
|
|
701
|
-
if (capabilityValidationError) {
|
|
702
|
-
return failed(capabilityValidationError.code, capabilityValidationError.message, resolved.taskId, capabilityValidationError.detail ? { detail: capabilityValidationError.detail } : {});
|
|
703
|
-
}
|
|
704
692
|
let beforeFingerprint;
|
|
705
693
|
try {
|
|
706
694
|
beforeFingerprint = (options.captureWorkspace ?? captureWorkspaceSnapshot)({
|
|
@@ -726,10 +714,6 @@ function prepareOrchestrationDelegationUnlocked(taskRef, input, options = {}) {
|
|
|
726
714
|
lifecycleProvenance: input.lifecycleProvenance ?? null,
|
|
727
715
|
beforeFingerprint
|
|
728
716
|
}, { id: options.id, now: options.now, monotonicNow: options.monotonicNow });
|
|
729
|
-
const capabilityError = options.consumeLifecycleCapability?.();
|
|
730
|
-
if (capabilityError) {
|
|
731
|
-
return failed(capabilityError.code, capabilityError.message, resolved.taskId, capabilityError.detail ? { detail: capabilityError.detail } : {});
|
|
732
|
-
}
|
|
733
717
|
const updated = withUpdatedRun(run, {
|
|
734
718
|
nextStage: next.stage,
|
|
735
719
|
pendingDelegation: receipt
|
|
@@ -118,20 +118,26 @@ function parseReviewSummary(content) {
|
|
|
118
118
|
function equalCounts(left, right) {
|
|
119
119
|
return left.blocker === right.blocker && left.major === right.major && left.minor === right.minor;
|
|
120
120
|
}
|
|
121
|
-
function finalizeReviewSummaryContent(content, counts) {
|
|
121
|
+
function finalizeReviewSummaryContent(content, counts, options = {}) {
|
|
122
122
|
const parsed = parseReviewSummary(content);
|
|
123
123
|
if (!parsed.ok)
|
|
124
124
|
return parsed;
|
|
125
125
|
if (parsed.summary.countState === 'numeric') {
|
|
126
126
|
if (equalCounts(parsed.summary.counts, counts))
|
|
127
127
|
return { ok: true, changed: false, content };
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
128
|
+
if (!options.refreshNumericCounts)
|
|
129
|
+
return {
|
|
130
|
+
ok: false,
|
|
131
|
+
code: 'REVIEW_SUMMARY_COUNT_MISMATCH',
|
|
132
|
+
message: 'review summary finding counts do not match the ledger snapshot'
|
|
133
|
+
};
|
|
133
134
|
}
|
|
134
135
|
let line = content.slice(parsed.findingsStart, parsed.findingsEnd);
|
|
136
|
+
if (parsed.summary.countState === 'numeric') {
|
|
137
|
+
const values = [counts.blocker, counts.major, counts.minor];
|
|
138
|
+
let ordinal = 0;
|
|
139
|
+
line = line.replace(/\d+(?=\s*(?:阻塞项|主要|次要|blockers?\b|majors?\b|minors?\b))/gi, () => String(values[ordinal++]));
|
|
140
|
+
}
|
|
135
141
|
line = line
|
|
136
142
|
.replace(PLACEHOLDERS[0], String(counts.blocker))
|
|
137
143
|
.replace(PLACEHOLDERS[1], String(counts.major))
|
|
@@ -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) {
|