@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.
Files changed (146) hide show
  1. package/dist/lib/internal/cli-route-inventory.js +1 -1
  2. package/dist/lib/internal/manual-validation.js +6 -2
  3. package/dist/lib/internal/platform-comment.js +59 -0
  4. package/dist/lib/internal/sandbox-control.js +4 -131
  5. package/dist/lib/internal/task-artifact.js +1 -1
  6. package/dist/lib/internal/task-operation-registry.js +1 -0
  7. package/dist/lib/internal/task-review.js +5 -5
  8. package/dist/lib/internal/task-validate.js +0 -6
  9. package/dist/lib/platform/comment-safety.js +7 -1
  10. package/dist/lib/platform/github-provider.js +13 -3
  11. package/dist/lib/platform/issue-comments.js +120 -13
  12. package/dist/lib/platform/issues.js +3 -4
  13. package/dist/lib/platform/provider-contract.d.ts +2 -0
  14. package/dist/lib/platform/provider-validation.js +7 -2
  15. package/dist/lib/platform/verification-sync.js +161 -156
  16. package/dist/lib/sandbox/control/executor.js +0 -38
  17. package/dist/lib/sandbox/control/finalization-response.js +10 -2
  18. package/dist/lib/sandbox/control/task-view.js +3 -1
  19. package/dist/lib/sandbox/control/workflow-executor.js +32 -41
  20. package/dist/lib/task/artifact-command.js +6 -6
  21. package/dist/lib/task/artifact-lifecycle.js +15 -52
  22. package/dist/lib/task/artifact-operations.js +27 -1
  23. package/dist/lib/task/capabilities.js +0 -2
  24. package/dist/lib/task/codex-orchestration.js +10 -208
  25. package/dist/lib/task/commit-operation.js +3 -18
  26. package/dist/lib/task/control-authority.js +1 -8
  27. package/dist/lib/task/delegation-receipts.js +14 -76
  28. package/dist/lib/task/events.js +147 -278
  29. package/dist/lib/task/finalization-state.js +2 -1
  30. package/dist/lib/task/finalization.js +273 -49
  31. package/dist/lib/task/gate-policy.js +94 -0
  32. package/dist/lib/task/lifecycle-execution.js +2 -11
  33. package/dist/lib/task/local-artifact-finalization.js +31 -245
  34. package/dist/lib/task/orchestration.js +8 -24
  35. package/dist/lib/task/review-artifacts.js +12 -6
  36. package/dist/lib/task/review-command.js +0 -1
  37. package/dist/lib/task/review-finalization.js +47 -218
  38. package/dist/lib/task/verification-engine.js +28 -17
  39. package/dist/lib/task/verification.js +25 -9
  40. package/dist/lib/task/workflow-command.js +4 -1
  41. package/dist/lib/task/workflow-dispatch.js +55 -3
  42. package/dist/lifecycle-build-manifest.json +3 -3
  43. package/lib/internal/cli-route-inventory.ts +1 -1
  44. package/lib/internal/manual-validation.ts +6 -2
  45. package/lib/internal/platform-comment.ts +38 -0
  46. package/lib/internal/sandbox-control.ts +3 -145
  47. package/lib/internal/task-artifact.ts +1 -1
  48. package/lib/internal/task-operation-registry.ts +1 -0
  49. package/lib/internal/task-review.ts +5 -5
  50. package/lib/internal/task-validate.ts +0 -5
  51. package/lib/platform/comment-safety.ts +8 -0
  52. package/lib/platform/github-provider.ts +11 -2
  53. package/lib/platform/issue-comments.ts +132 -13
  54. package/lib/platform/issues.ts +3 -4
  55. package/lib/platform/provider-contract.ts +2 -0
  56. package/lib/platform/provider-validation.ts +6 -2
  57. package/lib/platform/verification-sync.ts +178 -179
  58. package/lib/sandbox/control/executor.ts +0 -44
  59. package/lib/sandbox/control/finalization-response.ts +10 -2
  60. package/lib/sandbox/control/task-view.ts +3 -1
  61. package/lib/sandbox/control/workflow-executor.ts +31 -48
  62. package/lib/task/artifact-command.ts +6 -7
  63. package/lib/task/artifact-lifecycle.ts +12 -49
  64. package/lib/task/artifact-operations.ts +27 -1
  65. package/lib/task/capabilities.ts +0 -1
  66. package/lib/task/codex-orchestration.ts +8 -241
  67. package/lib/task/commit-operation.ts +3 -23
  68. package/lib/task/control-authority.ts +1 -8
  69. package/lib/task/delegation-receipts.ts +12 -79
  70. package/lib/task/events.ts +155 -287
  71. package/lib/task/finalization-state.ts +2 -1
  72. package/lib/task/finalization.ts +277 -57
  73. package/lib/task/gate-policy.ts +133 -0
  74. package/lib/task/lifecycle-execution.ts +10 -21
  75. package/lib/task/local-artifact-finalization.ts +29 -285
  76. package/lib/task/orchestration.ts +12 -41
  77. package/lib/task/review-artifacts.ts +8 -2
  78. package/lib/task/review-command.ts +0 -1
  79. package/lib/task/review-finalization.ts +46 -200
  80. package/lib/task/verification-engine.ts +24 -14
  81. package/lib/task/verification-types.ts +7 -0
  82. package/lib/task/verification.ts +25 -9
  83. package/lib/task/workflow-command.ts +4 -1
  84. package/lib/task/workflow-dispatch.ts +46 -3
  85. package/package.json +2 -2
  86. package/templates/.agents/rules/issue-sync.en.md +2 -0
  87. package/templates/.agents/rules/issue-sync.zh-CN.md +5 -8
  88. package/templates/.agents/rules/lifecycle-orchestration.en.md +7 -7
  89. package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +7 -7
  90. package/templates/.agents/rules/local-artifact-repair.en.md +14 -52
  91. package/templates/.agents/rules/local-artifact-repair.zh-CN.md +13 -52
  92. package/templates/.agents/skills/analyze-task/SKILL.en.md +3 -3
  93. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +3 -3
  94. package/templates/.agents/skills/analyze-task/config/verify.en.json +1 -10
  95. package/templates/.agents/skills/analyze-task/config/verify.zh-CN.json +1 -10
  96. package/templates/.agents/skills/block-task/config/verify.json +1 -3
  97. package/templates/.agents/skills/cancel-task/config/verify.json +1 -4
  98. package/templates/.agents/skills/code-task/SKILL.en.md +3 -3
  99. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +3 -3
  100. package/templates/.agents/skills/code-task/config/verify.en.json +1 -12
  101. package/templates/.agents/skills/code-task/config/verify.zh-CN.json +1 -12
  102. package/templates/.agents/skills/code-task/reference/dual-mode.en.md +1 -1
  103. package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +1 -1
  104. package/templates/.agents/skills/code-task/reference/report-template.en.md +11 -0
  105. package/templates/.agents/skills/code-task/reference/report-template.zh-CN.md +11 -0
  106. package/templates/.agents/skills/commit/config/verify.json +1 -5
  107. package/templates/.agents/skills/complete-manual-validation/config/verify.en.json +6 -6
  108. package/templates/.agents/skills/complete-manual-validation/config/verify.zh-CN.json +6 -6
  109. package/templates/.agents/skills/complete-task/SKILL.en.md +3 -3
  110. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +3 -3
  111. package/templates/.agents/skills/complete-task/config/verify.en.json +3 -14
  112. package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +3 -14
  113. package/templates/.agents/skills/create-pr/config/verify.json +1 -10
  114. package/templates/.agents/skills/create-task/config/verify.json +1 -4
  115. package/templates/.agents/skills/import-issue/config/verify.json +1 -2
  116. package/templates/.agents/skills/plan-task/SKILL.en.md +3 -3
  117. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +3 -3
  118. package/templates/.agents/skills/plan-task/config/verify.en.json +1 -10
  119. package/templates/.agents/skills/plan-task/config/verify.zh-CN.json +1 -10
  120. package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +4 -4
  121. package/templates/.agents/skills/review-analysis/SKILL.en.md +2 -2
  122. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +2 -2
  123. package/templates/.agents/skills/review-analysis/config/verify.en.json +1 -10
  124. package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +1 -10
  125. package/templates/.agents/skills/review-analysis/reference/report-template.en.md +2 -0
  126. package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +2 -0
  127. package/templates/.agents/skills/review-code/SKILL.en.md +3 -3
  128. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +3 -3
  129. package/templates/.agents/skills/review-code/config/verify.en.json +1 -10
  130. package/templates/.agents/skills/review-code/config/verify.zh-CN.json +1 -10
  131. package/templates/.agents/skills/review-code/reference/report-template.en.md +2 -0
  132. package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +2 -0
  133. package/templates/.agents/skills/review-plan/SKILL.en.md +2 -2
  134. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +2 -2
  135. package/templates/.agents/skills/review-plan/config/verify.en.json +1 -10
  136. package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +1 -10
  137. package/templates/.agents/skills/review-plan/reference/report-template.en.md +2 -0
  138. package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +2 -0
  139. package/templates/.agents/skills/review-pr/config/verify.en.json +1 -8
  140. package/templates/.agents/skills/review-pr/config/verify.zh-CN.json +1 -8
  141. package/templates/.agents/skills/run-manual-validation/config/verify.en.json +38 -13
  142. package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +38 -13
  143. package/templates/.agents/skills/run-task/SKILL.en.md +6 -6
  144. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +6 -6
  145. package/templates/.agents/skills/run-task/reference/host-validation.en.md +2 -2
  146. 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, parseCodePlanInputReference, validateArtifactPublication, validateCompletedArtifact } from './artifact-lifecycle.js';
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 expected = expectedQualificationRelations(options.taskContent, options.family);
101
- if (!expected.ok)
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.ok ? expected.relations : undefined
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 tupleFor(request, taskId, authority) {
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
- if (request.recoveryId) {
159
- const intent = readArtifactRecoveryIntent(resolved.repoRoot, resolved.taskId, request.family, request.artifact);
160
- if (!intent || intent.recoveryOperationId !== request.recoveryId)
161
- return failed('LOCAL_RECOVERY_PROVENANCE_CONFLICT', 'recovery id does not match the artifact journal');
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' || !hasOpenArtifactRound(taskContent, request.family, parsed.round)) {
181
- return failed('ARTIFACT_IDENTITY_INVALID', 'candidate must match one open started round in an active task');
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
- if (request.recoveryId) {
195
- if (!validation.ok)
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
- }
201
- return {
202
- result: { ...result, status: 'passed', changed: false, error: null, recovery: recoveryInfo(recovery) },
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, options = {}) {
282
- if (!prepared.recovery || !prepared.repoRoot || prepared.result.status === 'failed' && !prepared.result.recovery)
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, authority, options = {}) {
342
- const prepared = prepareLocalArtifact(request, undefined, authority);
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, authority) {
359
- const prepared = prepareLocalArtifact(request, undefined, authority, true);
144
+ function preflightLocalArtifact(request) {
145
+ const prepared = prepareLocalArtifact(request);
360
146
  return prepared.result;
361
147
  }
362
- export { LOCAL_ARTIFACT_REQUIRED_SECTIONS, consumeLocalArtifactFinalizationIntent, finalizeLocalArtifact, preflightLocalArtifact, prepareLocalArtifact, commitLocalArtifactProvenance, readArtifactRecoveryIntent as readLocalArtifactFinalizationIntent, canonicalSemanticDigest as semanticDigest, sha256Content, validateLocalArtifact };
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 { invalidationBlocks } from './invalidation.js';
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
- return {
129
- ok: false,
130
- code: 'REVIEW_SUMMARY_COUNT_MISMATCH',
131
- message: 'review summary finding counts do not match the ledger snapshot'
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) {