@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,8 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
inspectOrchestrationStage,
|
|
3
3
|
pauseOrchestration,
|
|
4
|
-
planOrchestrationStageCompletion
|
|
5
|
-
readRun
|
|
4
|
+
planOrchestrationStageCompletion
|
|
6
5
|
} from './orchestration.ts';
|
|
7
6
|
import type {
|
|
8
7
|
OrchestrationStageCompletion,
|
|
@@ -31,28 +30,15 @@ function failure(mode: LifecycleExecutionMode, code: string, message: string): L
|
|
|
31
30
|
function inspectLifecycleExecution(
|
|
32
31
|
taskRef: string,
|
|
33
32
|
request: LifecycleExecutionRequest,
|
|
34
|
-
options: Readonly<{
|
|
33
|
+
options: Readonly<{
|
|
34
|
+
repoRoot?: string;
|
|
35
|
+
now?: () => string;
|
|
36
|
+
}> = {}
|
|
35
37
|
): LifecycleExecutionResult {
|
|
36
38
|
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
37
39
|
if (!resolved.ok) return failure(request.mode, resolved.code, resolved.message);
|
|
38
40
|
if (request.mode === 'standalone') {
|
|
39
|
-
|
|
40
|
-
const run = readRun(resolved.taskDir);
|
|
41
|
-
if (run?.pendingDelegation) {
|
|
42
|
-
return failure(
|
|
43
|
-
request.mode,
|
|
44
|
-
'ORCHESTRATION_STANDALONE_BUSY',
|
|
45
|
-
'standalone lifecycle execution is blocked by a pending orchestration delegation'
|
|
46
|
-
);
|
|
47
|
-
}
|
|
48
|
-
return { ok: true, mode: request.mode, completionPlan: null, error: null };
|
|
49
|
-
} catch (error) {
|
|
50
|
-
return failure(
|
|
51
|
-
request.mode,
|
|
52
|
-
'ORCHESTRATION_STATE_INVALID',
|
|
53
|
-
error instanceof Error ? error.message : String(error)
|
|
54
|
-
);
|
|
55
|
-
}
|
|
41
|
+
return { ok: true, mode: request.mode, completionPlan: null, error: null };
|
|
56
42
|
}
|
|
57
43
|
|
|
58
44
|
if (request.agent) {
|
|
@@ -84,7 +70,10 @@ function inspectLifecycleExecution(
|
|
|
84
70
|
function validateLifecycleExecution(
|
|
85
71
|
taskRef: string,
|
|
86
72
|
request: LifecycleExecutionRequest,
|
|
87
|
-
options: Readonly<{
|
|
73
|
+
options: Readonly<{
|
|
74
|
+
repoRoot?: string;
|
|
75
|
+
now?: () => string;
|
|
76
|
+
}> = {}
|
|
88
77
|
): LifecycleExecutionResult {
|
|
89
78
|
const inspected = inspectLifecycleExecution(taskRef, request, options);
|
|
90
79
|
if (inspected.ok || request.mode === 'standalone' || request.dryRun) return inspected;
|
|
@@ -1,38 +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
|
-
parseCodePlanInputReference,
|
|
9
|
-
validateArtifactPublication,
|
|
10
|
-
validateCompletedArtifact
|
|
11
9
|
} from './artifact-lifecycle.ts';
|
|
12
10
|
import { resolveTaskRef } from './resolve-ref.ts';
|
|
13
11
|
import { expectedQualificationRelations, validateQualificationAudit } from './qualification-audit.ts';
|
|
14
12
|
import { canonicalSemanticDigest, inspectArtifactPatterns, inspectArtifactStructure, sha256Content } from './artifact-operations.ts';
|
|
15
13
|
import { getArtifactSchema } from './artifact-schema.ts';
|
|
16
|
-
import { readArtifactRecoveryIntent } from './artifact-repair-intent.ts';
|
|
17
|
-
import type { ArtifactRecoveryIntent } from './artifact-repair-intent.ts';
|
|
18
|
-
import {
|
|
19
|
-
beginArtifactRecovery,
|
|
20
|
-
commitArtifactRecovery,
|
|
21
|
-
consumeArtifactRecovery,
|
|
22
|
-
prepareArtifactRecoveryFinal,
|
|
23
|
-
prepareArtifactRecoveryCommit,
|
|
24
|
-
recordArtifactRecoveryPassed,
|
|
25
|
-
reconcileArtifactRecovery,
|
|
26
|
-
recoveryContextFromIntent,
|
|
27
|
-
stageArtifactCandidate
|
|
28
|
-
} from './artifact-recovery.ts';
|
|
29
|
-
import type { ArtifactRecoveryContext } from './artifact-recovery.ts';
|
|
30
|
-
import {
|
|
31
|
-
consumeLifecycleRecoveryAttestation,
|
|
32
|
-
lifecycleRecoveryAttestationDigest,
|
|
33
|
-
validateLifecycleRecoveryAttestation
|
|
34
|
-
} from './control-authority.ts';
|
|
35
|
-
import type { LifecycleRecoveryAttestationV1 } from './control-authority.ts';
|
|
36
14
|
|
|
37
15
|
type LocalArtifactFamily = 'analysis' | 'plan' | 'code';
|
|
38
16
|
|
|
@@ -51,8 +29,6 @@ type LocalArtifactDiagnosticCode =
|
|
|
51
29
|
| 'LOCAL_REQUIRED_PATTERN_MISSING'
|
|
52
30
|
| 'LOCAL_STRUCTURAL_INVALID'
|
|
53
31
|
| 'LOCAL_SECTION_HEADING_TRAILING_PUNCTUATION'
|
|
54
|
-
| 'LOCAL_RECOVERY_PROVENANCE_CONFLICT'
|
|
55
|
-
| 'LOCAL_RECOVERY_BASELINE_MISMATCH'
|
|
56
32
|
| 'LOCAL_QUALIFICATION_AUDIT_INVALID';
|
|
57
33
|
|
|
58
34
|
type LocalArtifactDiagnostic = {
|
|
@@ -81,17 +57,9 @@ type LocalArtifactFinalizationRequest = {
|
|
|
81
57
|
artifact: string;
|
|
82
58
|
repoRoot?: string;
|
|
83
59
|
requiredSections?: readonly string[];
|
|
84
|
-
recoveryId?: string;
|
|
85
60
|
lockAlreadyHeld?: boolean;
|
|
86
61
|
};
|
|
87
62
|
|
|
88
|
-
type LocalArtifactRecoveryInfo = Readonly<{
|
|
89
|
-
recoveryId: string;
|
|
90
|
-
candidatePath: string;
|
|
91
|
-
baselineSha256: string;
|
|
92
|
-
baselineSemanticDigest: string;
|
|
93
|
-
}>;
|
|
94
|
-
|
|
95
63
|
type LocalArtifactFinalizationResult = {
|
|
96
64
|
status: 'passed' | 'failed';
|
|
97
65
|
changed: boolean;
|
|
@@ -102,54 +70,9 @@ type LocalArtifactFinalizationResult = {
|
|
|
102
70
|
artifactSha256: string | null;
|
|
103
71
|
semanticDigest: string | null;
|
|
104
72
|
diagnostics: readonly LocalArtifactDiagnostic[];
|
|
105
|
-
recovery?: LocalArtifactRecoveryInfo;
|
|
106
73
|
error: { code: string; message: string } | null;
|
|
107
74
|
};
|
|
108
75
|
|
|
109
|
-
type LocalArtifactFinalizationIntent = ArtifactRecoveryIntent;
|
|
110
|
-
|
|
111
|
-
function authorityDigest(authority: LifecycleRecoveryAttestationV1): string {
|
|
112
|
-
return lifecycleRecoveryAttestationDigest(authority);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
function validFinalizerAuthority(
|
|
116
|
-
authority: LifecycleRecoveryAttestationV1 | undefined,
|
|
117
|
-
request: LocalArtifactFinalizationRequest,
|
|
118
|
-
taskId: string
|
|
119
|
-
): boolean {
|
|
120
|
-
if (!authority) return false;
|
|
121
|
-
try { validateLifecycleRecoveryAttestation(authority); }
|
|
122
|
-
catch { return false; }
|
|
123
|
-
return authority.version === 1
|
|
124
|
-
&& authority.phase === 'artifact.finalize-local'
|
|
125
|
-
&& authority.taskId === taskId
|
|
126
|
-
&& authority.family === request.family
|
|
127
|
-
&& authority.artifact === request.artifact
|
|
128
|
-
&& authority.round === (parseArtifactName(request.artifact)?.round ?? 0)
|
|
129
|
-
&& authority.requestId.length > 0
|
|
130
|
-
&& authority.operationId.length > 0;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
function recoveryInfo(context: ArtifactRecoveryContext): LocalArtifactRecoveryInfo {
|
|
134
|
-
return {
|
|
135
|
-
recoveryId: context.recoveryId,
|
|
136
|
-
candidatePath: context.stagingPath,
|
|
137
|
-
baselineSha256: context.baselineSha256,
|
|
138
|
-
baselineSemanticDigest: context.baselineSemanticDigest
|
|
139
|
-
};
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
function consumeLocalArtifactFinalizationIntent(
|
|
143
|
-
repoRoot: string,
|
|
144
|
-
intent: LocalArtifactFinalizationIntent,
|
|
145
|
-
options: Readonly<{ lockAlreadyHeld?: boolean }> = {}
|
|
146
|
-
): LocalArtifactFinalizationIntent {
|
|
147
|
-
if (intent.state === 'consumed') return intent;
|
|
148
|
-
const resolved = resolveTaskRef(intent.taskId, { repoRoot });
|
|
149
|
-
if (!resolved.ok) throw new Error(resolved.message);
|
|
150
|
-
return consumeArtifactRecovery(recoveryContextFromIntent(repoRoot, resolved.taskDir, intent), options);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
76
|
function localDiagnosticCode(code: string): LocalArtifactDiagnosticCode {
|
|
154
77
|
const map: Record<string, LocalArtifactDiagnosticCode> = {
|
|
155
78
|
ARTIFACT_EMPTY: 'LOCAL_ARTIFACT_EMPTY',
|
|
@@ -206,12 +129,15 @@ function validateLocalArtifact(
|
|
|
206
129
|
if (!decisionDetails.ok) diagnostics.push({ code: 'LOCAL_DECISION_DETAIL_DUPLICATE', message: decisionDetails.message, line: null });
|
|
207
130
|
|
|
208
131
|
if (options.taskContent !== undefined) {
|
|
209
|
-
const
|
|
210
|
-
|
|
132
|
+
const identity = options.artifact ? parseArtifactName(options.artifact) : null;
|
|
133
|
+
const expected = identity?.family === options.family && hasOpenArtifactRound(options.taskContent, options.family, identity.round)
|
|
134
|
+
? expectedQualificationRelations(options.taskContent, options.family)
|
|
135
|
+
: undefined;
|
|
136
|
+
if (expected && !expected.ok) diagnostics.push({ code: 'LOCAL_QUALIFICATION_AUDIT_INVALID', message: `${expected.code}: ${expected.message}`, line: null });
|
|
211
137
|
const qualification = validateQualificationAudit(options.taskContent, content, {
|
|
212
138
|
family: options.family,
|
|
213
139
|
artifact: options.artifact,
|
|
214
|
-
expectedUpstreamRelations: expected
|
|
140
|
+
expectedUpstreamRelations: expected?.ok ? expected.relations : undefined
|
|
215
141
|
});
|
|
216
142
|
if (!qualification.ok) diagnostics.push({ code: 'LOCAL_QUALIFICATION_AUDIT_INVALID', message: `${qualification.code}: ${qualification.message}`, line: null });
|
|
217
143
|
}
|
|
@@ -243,32 +169,11 @@ type LocalArtifactPreparation = Readonly<{
|
|
|
243
169
|
content: string;
|
|
244
170
|
repoRoot?: string;
|
|
245
171
|
lockAlreadyHeld?: boolean;
|
|
246
|
-
recovery?: ArtifactRecoveryContext;
|
|
247
|
-
authority?: LifecycleRecoveryAttestationV1;
|
|
248
172
|
}>;
|
|
249
173
|
|
|
250
|
-
function tupleFor(
|
|
251
|
-
request: LocalArtifactFinalizationRequest,
|
|
252
|
-
taskId: string,
|
|
253
|
-
authority?: LifecycleRecoveryAttestationV1
|
|
254
|
-
) {
|
|
255
|
-
const round = parseArtifactName(request.artifact)?.round ?? 0;
|
|
256
|
-
return {
|
|
257
|
-
taskId,
|
|
258
|
-
family: request.family,
|
|
259
|
-
artifact: request.artifact,
|
|
260
|
-
round,
|
|
261
|
-
requestId: authority?.lifecycleRequestId ?? `local-finalize:${taskId}:${request.family}:${round}`,
|
|
262
|
-
phase: authority?.phase ?? 'artifact.finalize-local' as const,
|
|
263
|
-
authorityDigest: authority ? authorityDigest(authority) : null
|
|
264
|
-
};
|
|
265
|
-
}
|
|
266
|
-
|
|
267
174
|
function prepareLocalArtifact(
|
|
268
175
|
request: LocalArtifactFinalizationRequest,
|
|
269
|
-
candidate?: string
|
|
270
|
-
authority?: LifecycleRecoveryAttestationV1,
|
|
271
|
-
preflightOnly = false
|
|
176
|
+
candidate?: string
|
|
272
177
|
): LocalArtifactPreparation {
|
|
273
178
|
const failed = (code: string, message: string, extra: Partial<LocalArtifactFinalizationResult> = {}): LocalArtifactPreparation => ({
|
|
274
179
|
result: failedFinalization(request, { code, message }, extra), content: candidate ?? ''
|
|
@@ -277,34 +182,17 @@ function prepareLocalArtifact(
|
|
|
277
182
|
if (!resolved.ok) return failed(resolved.code, resolved.message);
|
|
278
183
|
const parsed = parseArtifactName(request.artifact);
|
|
279
184
|
if (!parsed || parsed.family !== request.family) return failed('ARTIFACT_IDENTITY_INVALID', `artifact '${request.artifact}' does not match ${request.family}`);
|
|
280
|
-
if (authority && !validFinalizerAuthority(authority, request, resolved.taskId)) {
|
|
281
|
-
return failed('LOCAL_EXECUTION_AUTHORITY_INVALID', 'finalizer authority does not match the artifact tuple');
|
|
282
|
-
}
|
|
283
|
-
|
|
284
185
|
let taskContent: string;
|
|
285
186
|
let content: string;
|
|
286
|
-
let recovery: ArtifactRecoveryContext | undefined;
|
|
287
|
-
let recoveryState: ArtifactRecoveryIntent['state'] | undefined;
|
|
288
187
|
try {
|
|
289
188
|
taskContent = fs.readFileSync(resolved.taskMdPath, 'utf8');
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
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`);
|
|
294
|
-
recoveryState = intent.state;
|
|
295
|
-
recovery = recoveryContextFromIntent(resolved.repoRoot, resolved.taskDir, intent);
|
|
296
|
-
content = fs.existsSync(recovery.stagingPath)
|
|
297
|
-
? fs.readFileSync(recovery.stagingPath, 'utf8')
|
|
298
|
-
: fs.readFileSync(recovery.formalPath, 'utf8');
|
|
299
|
-
} else {
|
|
300
|
-
const validated = validateCompletedArtifact(resolved.taskDir, request.family, request.artifact, parsed.round);
|
|
301
|
-
if (!validated.ok) return failed(validated.error.code, validated.error.message);
|
|
302
|
-
content = candidate ?? fs.readFileSync(validated.artifact.path, 'utf8');
|
|
303
|
-
}
|
|
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');
|
|
304
192
|
} catch (error) { return failed('ARTIFACT_NOT_READABLE', String(error)); }
|
|
305
193
|
|
|
306
|
-
if (resolved.state !== 'active'
|
|
307
|
-
return failed('ARTIFACT_IDENTITY_INVALID', '
|
|
194
|
+
if (resolved.state !== 'active') {
|
|
195
|
+
return failed('ARTIFACT_IDENTITY_INVALID', 'artifact finalization requires an active task');
|
|
308
196
|
}
|
|
309
197
|
const validation = validateLocalArtifact(content, {
|
|
310
198
|
family: request.family, requiredSections: request.requiredSections, taskContent, artifact: request.artifact
|
|
@@ -318,186 +206,44 @@ function prepareLocalArtifact(
|
|
|
318
206
|
semanticDigest: validation.semanticDigest, diagnostics: validation.diagnostics
|
|
319
207
|
});
|
|
320
208
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
};
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
const existing = readArtifactRecoveryIntent(resolved.repoRoot, resolved.taskId, request.family, request.artifact);
|
|
334
|
-
if (existing?.state === 'passed' || existing?.state === 'consumed') {
|
|
335
|
-
if (existing.state === 'consumed' && (existing.finalArtifactSha256 !== artifactSha256 || existing.finalSemanticDigest !== validation.semanticDigest)) {
|
|
336
|
-
return { ...failed('LOCAL_RECOVERY_PROVENANCE_CONFLICT', 'formal artifact does not match its completed recovery journal'), content, repoRoot: resolved.repoRoot, authority, lockAlreadyHeld: request.lockAlreadyHeld };
|
|
337
|
-
}
|
|
338
|
-
if (existing.finalArtifactSha256 === artifactSha256 && existing.finalSemanticDigest === validation.semanticDigest) {
|
|
339
|
-
const context = recoveryContextFromIntent(resolved.repoRoot, resolved.taskDir, existing);
|
|
340
|
-
return {
|
|
341
|
-
result: { ...result, status: 'passed', error: null, recovery: recoveryInfo(context) },
|
|
342
|
-
content, repoRoot: resolved.repoRoot, recovery: context, authority, lockAlreadyHeld: request.lockAlreadyHeld
|
|
343
|
-
};
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
if (existing && ['preflight-ready', 'preflight-commit-started', 'preflight-passed', 'full-finalizer-ready'].includes(existing.state)) {
|
|
348
|
-
const context = recoveryContextFromIntent(resolved.repoRoot, resolved.taskDir, existing);
|
|
349
|
-
return {
|
|
350
|
-
result: { ...result, status: 'passed', changed: false, error: null, recovery: recoveryInfo(context) },
|
|
351
|
-
content, repoRoot: resolved.repoRoot, recovery: context, authority, lockAlreadyHeld: request.lockAlreadyHeld
|
|
352
|
-
};
|
|
353
|
-
}
|
|
354
|
-
if (existing?.state === 'commit-started') {
|
|
355
|
-
if (existing.finalArtifactSha256 !== artifactSha256 || existing.finalSemanticDigest !== validation.semanticDigest) {
|
|
356
|
-
return { ...failed('LOCAL_RECOVERY_PROVENANCE_CONFLICT', 'formal artifact does not match the interrupted recovery journal'), content, repoRoot: resolved.repoRoot, authority, lockAlreadyHeld: request.lockAlreadyHeld };
|
|
357
|
-
}
|
|
358
|
-
const context = recoveryContextFromIntent(resolved.repoRoot, resolved.taskDir, existing);
|
|
359
|
-
const reconciled = reconcileArtifactRecovery(context, {
|
|
360
|
-
lockAlreadyHeld: request.lockAlreadyHeld,
|
|
361
|
-
validateFinal: () => validation.semanticDigest === existing.finalSemanticDigest
|
|
362
|
-
});
|
|
363
|
-
if (reconciled.status !== 'passed') {
|
|
364
|
-
return { ...failed('LOCAL_RECOVERY_PROVENANCE_CONFLICT', `interrupted recovery could not be reconciled: ${reconciled.status}`), content, repoRoot: resolved.repoRoot, recovery: context, authority, lockAlreadyHeld: request.lockAlreadyHeld };
|
|
365
|
-
}
|
|
366
|
-
return {
|
|
367
|
-
result: { ...result, status: 'passed', changed: false, error: null, recovery: recoveryInfo(context) },
|
|
368
|
-
content, repoRoot: resolved.repoRoot, recovery: context, authority, lockAlreadyHeld: request.lockAlreadyHeld
|
|
369
|
-
};
|
|
370
|
-
}
|
|
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
|
+
};
|
|
371
218
|
|
|
372
|
-
try {
|
|
373
|
-
if (validation.ok) {
|
|
374
|
-
if (preflightOnly) {
|
|
375
|
-
const context = beginArtifactRecovery(tupleFor(request, resolved.taskId, authority), Buffer.from(content, 'utf8'), {
|
|
376
|
-
repoRoot: resolved.repoRoot, taskDir: resolved.taskDir,
|
|
377
|
-
...(authority ? { recoveryId: authority.operationId } : {}), lockAlreadyHeld: request.lockAlreadyHeld
|
|
378
|
-
});
|
|
379
|
-
const staged = stageArtifactCandidate(context, Buffer.from(content, 'utf8'), { lockAlreadyHeld: request.lockAlreadyHeld });
|
|
380
|
-
prepareArtifactRecoveryCommit(context, staged.candidateSha256, staged.semanticDigest, { lockAlreadyHeld: request.lockAlreadyHeld });
|
|
381
|
-
return { result: { ...result, status: 'passed', error: null, recovery: recoveryInfo(context) }, content, repoRoot: resolved.repoRoot, recovery: context, authority, lockAlreadyHeld: request.lockAlreadyHeld };
|
|
382
|
-
}
|
|
383
|
-
const context = recordArtifactRecoveryPassed(tupleFor(request, resolved.taskId, authority), {
|
|
384
|
-
repoRoot: resolved.repoRoot,
|
|
385
|
-
taskDir: resolved.taskDir,
|
|
386
|
-
expectedFinalSha256: artifactSha256,
|
|
387
|
-
expectedFinalSemanticDigest: validation.semanticDigest,
|
|
388
|
-
...(authority ? { recoveryId: authority.operationId } : {}),
|
|
389
|
-
lockAlreadyHeld: request.lockAlreadyHeld
|
|
390
|
-
});
|
|
391
|
-
recovery = context;
|
|
392
|
-
return { result: { ...result, status: 'passed', changed: false, error: null, recovery: recoveryInfo(context) }, content, repoRoot: resolved.repoRoot, recovery, authority, lockAlreadyHeld: request.lockAlreadyHeld };
|
|
393
|
-
}
|
|
394
|
-
const context = beginArtifactRecovery(tupleFor(request, resolved.taskId, authority), Buffer.from(content, 'utf8'), {
|
|
395
|
-
repoRoot: resolved.repoRoot,
|
|
396
|
-
taskDir: resolved.taskDir,
|
|
397
|
-
...(authority ? { recoveryId: authority.operationId } : {}),
|
|
398
|
-
lockAlreadyHeld: request.lockAlreadyHeld
|
|
399
|
-
});
|
|
400
|
-
recovery = context;
|
|
401
|
-
return {
|
|
402
|
-
result: { ...result, recovery: recoveryInfo(context) },
|
|
403
|
-
content, repoRoot: resolved.repoRoot, recovery, authority, lockAlreadyHeld: request.lockAlreadyHeld
|
|
404
|
-
};
|
|
405
|
-
} catch (error) {
|
|
406
|
-
return { ...failed('LOCAL_RECOVERY_PROVENANCE_CONFLICT', String(error)), content, repoRoot: resolved.repoRoot, authority, lockAlreadyHeld: request.lockAlreadyHeld };
|
|
407
|
-
}
|
|
408
219
|
}
|
|
409
220
|
|
|
410
221
|
function commitLocalArtifactProvenance(
|
|
411
|
-
prepared: LocalArtifactPreparation
|
|
412
|
-
options: Readonly<{ afterPublish?: () => void }> = {}
|
|
222
|
+
prepared: LocalArtifactPreparation
|
|
413
223
|
): LocalArtifactFinalizationResult {
|
|
414
|
-
|
|
415
|
-
if (prepared.result.status === 'failed') return prepared.result;
|
|
416
|
-
try {
|
|
417
|
-
const intent = readArtifactRecoveryIntent(prepared.repoRoot, prepared.recovery.taskId, prepared.recovery.family, prepared.recovery.artifact);
|
|
418
|
-
if (!intent) throw new Error('ARTIFACT_RECOVERY_INTENT_MISSING');
|
|
419
|
-
let committed;
|
|
420
|
-
if (intent.state === 'passed' || intent.state === 'consumed') {
|
|
421
|
-
return {
|
|
422
|
-
...prepared.result,
|
|
423
|
-
changed: false,
|
|
424
|
-
artifactSha256: intent.finalArtifactSha256,
|
|
425
|
-
semanticDigest: intent.finalSemanticDigest,
|
|
426
|
-
error: null
|
|
427
|
-
};
|
|
428
|
-
} else if (intent.state === 'commit-started') {
|
|
429
|
-
const reconciled = reconcileArtifactRecovery(prepared.recovery, {
|
|
430
|
-
lockAlreadyHeld: prepared.lockAlreadyHeld,
|
|
431
|
-
afterPublish: options.afterPublish
|
|
432
|
-
});
|
|
433
|
-
committed = reconciled.intent;
|
|
434
|
-
} else if (intent.state === 'awaiting-preflight-recovery') {
|
|
435
|
-
const staged = stageArtifactCandidate(prepared.recovery, Buffer.from(prepared.content, 'utf8'), { lockAlreadyHeld: prepared.lockAlreadyHeld });
|
|
436
|
-
prepareArtifactRecoveryCommit(prepared.recovery, staged.candidateSha256, staged.semanticDigest, { lockAlreadyHeld: prepared.lockAlreadyHeld });
|
|
437
|
-
committed = commitArtifactRecovery(prepared.recovery, { lockAlreadyHeld: prepared.lockAlreadyHeld });
|
|
438
|
-
}
|
|
439
|
-
if (intent.state === 'preflight-ready' || intent.state === 'preflight-commit-started') {
|
|
440
|
-
committed = commitArtifactRecovery(prepared.recovery, { lockAlreadyHeld: prepared.lockAlreadyHeld });
|
|
441
|
-
}
|
|
442
|
-
if (committed?.state === 'preflight-passed' || intent.state === 'preflight-passed') {
|
|
443
|
-
prepareArtifactRecoveryFinal(prepared.recovery, Buffer.from(prepared.content, 'utf8'), { lockAlreadyHeld: prepared.lockAlreadyHeld });
|
|
444
|
-
committed = commitArtifactRecovery(prepared.recovery, { lockAlreadyHeld: prepared.lockAlreadyHeld, afterPublish: options.afterPublish });
|
|
445
|
-
} else if (intent.state === 'full-finalizer-ready') {
|
|
446
|
-
committed = commitArtifactRecovery(prepared.recovery, { lockAlreadyHeld: prepared.lockAlreadyHeld, afterPublish: options.afterPublish });
|
|
447
|
-
}
|
|
448
|
-
if (!committed || committed.state !== 'passed') throw new Error(`ARTIFACT_RECOVERY_STATE_INVALID: commit ended in '${committed?.state ?? intent.state}'`);
|
|
449
|
-
return {
|
|
450
|
-
...prepared.result,
|
|
451
|
-
status: 'passed',
|
|
452
|
-
changed: false,
|
|
453
|
-
artifactSha256: committed.finalArtifactSha256,
|
|
454
|
-
semanticDigest: committed.finalSemanticDigest,
|
|
455
|
-
error: null
|
|
456
|
-
};
|
|
457
|
-
} catch (error) {
|
|
458
|
-
if (error instanceof Error && error.message.startsWith('TASK_WORKFLOW_FAULT_INJECTED:')) throw error;
|
|
459
|
-
return { ...prepared.result, status: 'failed', error: {
|
|
460
|
-
code: 'LOCAL_RECOVERY_COMMIT_FAILED', message: String(error)
|
|
461
|
-
} };
|
|
462
|
-
}
|
|
224
|
+
return prepared.result;
|
|
463
225
|
}
|
|
464
226
|
|
|
465
227
|
function finalizeLocalArtifact(
|
|
466
|
-
request: LocalArtifactFinalizationRequest
|
|
467
|
-
authority?: LifecycleRecoveryAttestationV1,
|
|
468
|
-
options: Readonly<{ deferLifecycleRecoveryConsumption?: boolean }> = {}
|
|
228
|
+
request: LocalArtifactFinalizationRequest
|
|
469
229
|
): LocalArtifactFinalizationResult {
|
|
470
|
-
|
|
471
|
-
const result = commitLocalArtifactProvenance(prepared);
|
|
472
|
-
if (authority && !options.deferLifecycleRecoveryConsumption && result.status === 'passed') {
|
|
473
|
-
try { consumeLifecycleRecoveryAttestation(authority); }
|
|
474
|
-
catch (error) {
|
|
475
|
-
return { ...result, status: 'failed', error: {
|
|
476
|
-
code: 'LOCAL_EXECUTION_AUTHORITY_CONSUME_FAILED',
|
|
477
|
-
message: error instanceof Error ? error.message : String(error)
|
|
478
|
-
} };
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
return result;
|
|
230
|
+
return prepareLocalArtifact(request).result;
|
|
482
231
|
}
|
|
483
232
|
|
|
484
233
|
/** Validate and seal an immutable preflight generation without publishing the formal artifact. */
|
|
485
234
|
function preflightLocalArtifact(
|
|
486
|
-
request: LocalArtifactFinalizationRequest
|
|
487
|
-
authority?: LifecycleRecoveryAttestationV1
|
|
235
|
+
request: LocalArtifactFinalizationRequest
|
|
488
236
|
): LocalArtifactFinalizationResult {
|
|
489
|
-
const prepared = prepareLocalArtifact(request
|
|
237
|
+
const prepared = prepareLocalArtifact(request);
|
|
490
238
|
return prepared.result;
|
|
491
239
|
}
|
|
492
240
|
|
|
493
241
|
export {
|
|
494
242
|
LOCAL_ARTIFACT_REQUIRED_SECTIONS,
|
|
495
|
-
consumeLocalArtifactFinalizationIntent,
|
|
496
243
|
finalizeLocalArtifact,
|
|
497
244
|
preflightLocalArtifact,
|
|
498
245
|
prepareLocalArtifact,
|
|
499
246
|
commitLocalArtifactProvenance,
|
|
500
|
-
readArtifactRecoveryIntent as readLocalArtifactFinalizationIntent,
|
|
501
247
|
canonicalSemanticDigest as semanticDigest,
|
|
502
248
|
sha256Content,
|
|
503
249
|
validateLocalArtifact
|
|
@@ -508,9 +254,7 @@ export type {
|
|
|
508
254
|
LocalArtifactFamily,
|
|
509
255
|
LocalArtifactFinalizationRequest,
|
|
510
256
|
LocalArtifactFinalizationResult,
|
|
511
|
-
LocalArtifactFinalizationIntent,
|
|
512
257
|
LocalArtifactPreparation,
|
|
513
258
|
LocalArtifactValidationOptions,
|
|
514
|
-
LocalArtifactValidationResult
|
|
515
|
-
LocalArtifactRecoveryInfo
|
|
259
|
+
LocalArtifactValidationResult
|
|
516
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,
|
|
@@ -57,8 +56,7 @@ import { extractReviewBaseline, extractReviewDiffBase, extractReviewTargetHead,
|
|
|
57
56
|
import { resolveDeliveryTarget, resolveDiffBase, resolveTargetHead } from './delivery-target.ts';
|
|
58
57
|
import { buildLifecycleFacts, canStart, recommendNext } from './capabilities.ts';
|
|
59
58
|
import { hasOpenLifecycleExecution } from './activity-log.ts';
|
|
60
|
-
import {
|
|
61
|
-
import type { InvalidationDocument } from './invalidation.ts';
|
|
59
|
+
import { reconcileTaskInvalidation } from './invalidation-command.ts';
|
|
62
60
|
import type { LifecycleAction, LifecycleFacts } from './capabilities.ts';
|
|
63
61
|
import { normalizeAgentToken } from '../agent-clients/tokens.ts';
|
|
64
62
|
|
|
@@ -175,9 +173,6 @@ type OrchestrationOptions = {
|
|
|
175
173
|
}>;
|
|
176
174
|
diffWorkspace?: (repoRoot: string, before: string, after: string) => string[];
|
|
177
175
|
supportsLifecycleDelegation?: (client: AgentClientId) => boolean;
|
|
178
|
-
validateLifecycleCapability?: () => Exclude<OrchestrationResult['error'], null> | null;
|
|
179
|
-
consumeLifecycleCapability?: () => Exclude<OrchestrationResult['error'], null> | null;
|
|
180
|
-
lifecycleRecoveryAttestation?: LifecycleRecoveryAttestationV1 | null;
|
|
181
176
|
token?: () => string;
|
|
182
177
|
monotonicNow?: () => number;
|
|
183
178
|
sleep?: (milliseconds: number) => Promise<void>;
|
|
@@ -678,13 +673,11 @@ function routeOrchestration(taskRef: string, options: OrchestrationOptions = {})
|
|
|
678
673
|
}
|
|
679
674
|
let metadata: ReturnType<typeof parseTypedTaskFrontmatter>;
|
|
680
675
|
let currentLedger: LedgerDocument | null = null;
|
|
681
|
-
let currentInvalidation: InvalidationDocument = { operations: [], targets: [] };
|
|
682
676
|
if (resolved.state === 'active') {
|
|
683
677
|
const contract = validateCurrentTaskContract(content);
|
|
684
678
|
if (!contract.ok) return failed('ORCHESTRATION_CURRENT_CONTRACT_INVALID', contract.message, resolved.taskId);
|
|
685
679
|
metadata = contract.metadata;
|
|
686
680
|
currentLedger = contract.ledger;
|
|
687
|
-
currentInvalidation = contract.invalidation;
|
|
688
681
|
} else {
|
|
689
682
|
try {
|
|
690
683
|
metadata = parseTypedTaskFrontmatter(content);
|
|
@@ -692,9 +685,6 @@ function routeOrchestration(taskRef: string, options: OrchestrationOptions = {})
|
|
|
692
685
|
return failed('ORCHESTRATION_TASK_INVALID', error instanceof Error ? error.message : String(error), resolved.taskId);
|
|
693
686
|
}
|
|
694
687
|
}
|
|
695
|
-
if (invalidationBlocks(currentInvalidation)) {
|
|
696
|
-
return failed('ORCHESTRATION_INVALIDATION_INCOMPLETE', 'artifact invalidation is incomplete; reconcile task.md before routing downstream work', resolved.taskId);
|
|
697
|
-
}
|
|
698
688
|
let run: OrchestrationRun | null;
|
|
699
689
|
try {
|
|
700
690
|
run = readRun(resolved.taskDir, options);
|
|
@@ -711,6 +701,9 @@ function routeOrchestration(taskRef: string, options: OrchestrationOptions = {})
|
|
|
711
701
|
const routed = routeFromFacts(facts.facts);
|
|
712
702
|
if (!routed) return failed('ORCHESTRATION_ROUTE_UNKNOWN', 'cannot determine a unique lifecycle action', resolved.taskId);
|
|
713
703
|
if ('completion' in routed) {
|
|
704
|
+
if (facts.facts.executionBusy && !run?.pendingDelegation) {
|
|
705
|
+
return failed('ORCHESTRATION_EXECUTION_BUSY', 'clean completion requires no open lifecycle execution', resolved.taskId);
|
|
706
|
+
}
|
|
714
707
|
const reviewRound = maxArtifactRound(fs.readdirSync(resolved.taskDir), 'review-code');
|
|
715
708
|
const review = parseReviewSummary(fs.readFileSync(
|
|
716
709
|
path.join(resolved.taskDir, artifactName('review-code', reviewRound)),
|
|
@@ -822,7 +815,6 @@ function prepareOrchestrationDelegationUnlocked(
|
|
|
822
815
|
requestedModel?: string;
|
|
823
816
|
requestedReasoningEffort?: string;
|
|
824
817
|
lifecycleProvenance?: DelegationLifecycleProvenance;
|
|
825
|
-
lifecycleRecoveryAttestation?: LifecycleRecoveryAttestationV1 | null;
|
|
826
818
|
}>,
|
|
827
819
|
options: OrchestrationOptions = {}
|
|
828
820
|
): OrchestrationResult {
|
|
@@ -847,6 +839,14 @@ function prepareOrchestrationDelegationUnlocked(
|
|
|
847
839
|
return failed('ORCHESTRATION_DELEGATION_BUSY', 'the repository already has a pending lifecycle delegation', resolved.taskId);
|
|
848
840
|
}
|
|
849
841
|
if (run.stepCount >= run.maxSteps) return pauseOrchestration(taskRef, 'ORCHESTRATION_MAX_STEPS', 'maximum orchestration steps reached', true, options);
|
|
842
|
+
const invalidation = reconcileTaskInvalidation(taskRef, { repoRoot: resolved.repoRoot });
|
|
843
|
+
if (invalidation.status === 'failed') {
|
|
844
|
+
return failed(
|
|
845
|
+
'ORCHESTRATION_INVALIDATION_RECONCILE_FAILED',
|
|
846
|
+
`${invalidation.error?.code ?? 'UNKNOWN'}: ${invalidation.error?.message ?? ''}`,
|
|
847
|
+
resolved.taskId
|
|
848
|
+
);
|
|
849
|
+
}
|
|
850
850
|
const routed = routeOrchestration(taskRef, options);
|
|
851
851
|
if (!routed.next) return routed;
|
|
852
852
|
const next = routed.next;
|
|
@@ -856,16 +856,6 @@ function prepareOrchestrationDelegationUnlocked(
|
|
|
856
856
|
if (!validModel(input.requestedReasoningEffort)) {
|
|
857
857
|
return failed('ORCHESTRATION_REQUESTED_REASONING_EFFORT_REQUIRED', 'prepare requires the exact requested reasoning effort', resolved.taskId);
|
|
858
858
|
}
|
|
859
|
-
if (input.client === 'codex' && !input.lifecycleProvenance) {
|
|
860
|
-
return failed('ORCHESTRATION_CODEX_PROVENANCE_REQUIRED', 'Codex prepare requires lifecycle provenance', resolved.taskId);
|
|
861
|
-
}
|
|
862
|
-
if (input.lifecycleRecoveryAttestation
|
|
863
|
-
&& (input.lifecycleRecoveryAttestation.phase !== 'orchestration.prepare'
|
|
864
|
-
|| input.lifecycleRecoveryAttestation.taskId !== resolved.taskId
|
|
865
|
-
|| input.lifecycleRecoveryAttestation.artifact !== next.artifact
|
|
866
|
-
|| input.lifecycleRecoveryAttestation.round !== next.round)) {
|
|
867
|
-
return failed('LIFECYCLE_AUTHORITY_TUPLE_MISMATCH', 'orchestration authority does not match the routed stage', resolved.taskId);
|
|
868
|
-
}
|
|
869
859
|
const expectedPolicy = run.modelPolicy[next.role];
|
|
870
860
|
if (input.requestedModel !== expectedPolicy.model) {
|
|
871
861
|
return failed('ORCHESTRATION_REQUESTED_MODEL_MISMATCH', `requested model does not match the persisted ${next.role} model`, resolved.taskId);
|
|
@@ -873,15 +863,6 @@ function prepareOrchestrationDelegationUnlocked(
|
|
|
873
863
|
if (input.requestedReasoningEffort !== expectedPolicy.reasoningEffort) {
|
|
874
864
|
return failed('ORCHESTRATION_REQUESTED_REASONING_EFFORT_MISMATCH', `requested reasoning effort does not match the persisted ${next.role} policy`, resolved.taskId);
|
|
875
865
|
}
|
|
876
|
-
const capabilityValidationError = options.validateLifecycleCapability?.();
|
|
877
|
-
if (capabilityValidationError) {
|
|
878
|
-
return failed(
|
|
879
|
-
capabilityValidationError.code,
|
|
880
|
-
capabilityValidationError.message,
|
|
881
|
-
resolved.taskId,
|
|
882
|
-
capabilityValidationError.detail ? { detail: capabilityValidationError.detail } : {}
|
|
883
|
-
);
|
|
884
|
-
}
|
|
885
866
|
let beforeFingerprint: string;
|
|
886
867
|
try {
|
|
887
868
|
beforeFingerprint = (options.captureWorkspace ?? captureWorkspaceSnapshot)({
|
|
@@ -906,15 +887,6 @@ function prepareOrchestrationDelegationUnlocked(
|
|
|
906
887
|
lifecycleProvenance: input.lifecycleProvenance ?? null,
|
|
907
888
|
beforeFingerprint
|
|
908
889
|
}, { id: options.id, now: options.now, monotonicNow: options.monotonicNow });
|
|
909
|
-
const capabilityError = options.consumeLifecycleCapability?.();
|
|
910
|
-
if (capabilityError) {
|
|
911
|
-
return failed(
|
|
912
|
-
capabilityError.code,
|
|
913
|
-
capabilityError.message,
|
|
914
|
-
resolved.taskId,
|
|
915
|
-
capabilityError.detail ? { detail: capabilityError.detail } : {}
|
|
916
|
-
);
|
|
917
|
-
}
|
|
918
890
|
const updated = withUpdatedRun(run, {
|
|
919
891
|
nextStage: next.stage,
|
|
920
892
|
pendingDelegation: receipt
|
|
@@ -930,7 +902,6 @@ function prepareOrchestrationDelegation(
|
|
|
930
902
|
requestedModel?: string;
|
|
931
903
|
requestedReasoningEffort?: string;
|
|
932
904
|
lifecycleProvenance?: DelegationLifecycleProvenance;
|
|
933
|
-
lifecycleRecoveryAttestation?: LifecycleRecoveryAttestationV1 | null;
|
|
934
905
|
}>,
|
|
935
906
|
options: OrchestrationOptions = {}
|
|
936
907
|
): OrchestrationResult {
|
|
@@ -176,19 +176,25 @@ function equalCounts(left: ReviewFindingCounts, right: ReviewFindingCounts): boo
|
|
|
176
176
|
|
|
177
177
|
function finalizeReviewSummaryContent(
|
|
178
178
|
content: string,
|
|
179
|
-
counts: ReviewFindingCounts
|
|
179
|
+
counts: ReviewFindingCounts,
|
|
180
|
+
options: { refreshNumericCounts?: boolean } = {}
|
|
180
181
|
): ReviewSummaryFinalizeResult {
|
|
181
182
|
const parsed = parseReviewSummary(content);
|
|
182
183
|
if (!parsed.ok) return parsed;
|
|
183
184
|
if (parsed.summary.countState === 'numeric') {
|
|
184
185
|
if (equalCounts(parsed.summary.counts!, counts)) return { ok: true, changed: false, content };
|
|
185
|
-
return {
|
|
186
|
+
if (!options.refreshNumericCounts) return {
|
|
186
187
|
ok: false,
|
|
187
188
|
code: 'REVIEW_SUMMARY_COUNT_MISMATCH',
|
|
188
189
|
message: 'review summary finding counts do not match the ledger snapshot'
|
|
189
190
|
};
|
|
190
191
|
}
|
|
191
192
|
let line = content.slice(parsed.findingsStart, parsed.findingsEnd);
|
|
193
|
+
if (parsed.summary.countState === 'numeric') {
|
|
194
|
+
const values = [counts.blocker, counts.major, counts.minor];
|
|
195
|
+
let ordinal = 0;
|
|
196
|
+
line = line.replace(/\d+(?=\s*(?:阻塞项|主要|次要|blockers?\b|majors?\b|minors?\b))/gi, () => String(values[ordinal++]));
|
|
197
|
+
}
|
|
192
198
|
line = line
|
|
193
199
|
.replace(PLACEHOLDERS[0], String(counts.blocker))
|
|
194
200
|
.replace(PLACEHOLDERS[1], String(counts.major))
|
|
@@ -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) {
|