@fitlab-ai/agent-infra 0.11.1 → 0.11.3

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 (131) hide show
  1. package/dist/bin/internal-cli.js +7 -2
  2. package/dist/lib/agent-clients/adapter.js +3 -0
  3. package/dist/lib/agent-clients/adapters/codex-lifecycle/recovery.js +234 -0
  4. package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +8 -35
  5. package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +1 -34
  6. package/dist/lib/agent-clients/adapters/codex.js +10 -0
  7. package/dist/lib/internal/cli-route-inventory.js +1 -1
  8. package/dist/lib/internal/manual-validation.js +22 -27
  9. package/dist/lib/internal/platform-comment.js +59 -0
  10. package/dist/lib/internal/sandbox-control.js +2 -171
  11. package/dist/lib/internal/task-artifact.js +1 -1
  12. package/dist/lib/internal/task-lifecycle.js +1 -1
  13. package/dist/lib/internal/task-operation-registry.js +7 -2
  14. package/dist/lib/internal/task-review.js +5 -5
  15. package/dist/lib/internal/task-validate.js +0 -6
  16. package/dist/lib/platform/comment-safety.js +7 -1
  17. package/dist/lib/platform/github-provider.js +13 -3
  18. package/dist/lib/platform/issue-comments.js +93 -15
  19. package/dist/lib/platform/pr-summary.js +7 -2
  20. package/dist/lib/platform/provider-contract.d.ts +2 -0
  21. package/dist/lib/platform/provider-validation.js +7 -2
  22. package/dist/lib/platform/verification-sync.js +19 -2
  23. package/dist/lib/sandbox/control/client.js +3 -44
  24. package/dist/lib/sandbox/control/executor.js +14 -117
  25. package/dist/lib/sandbox/control/finalization-response.js +8 -2
  26. package/dist/lib/sandbox/control/protocol.js +5 -64
  27. package/dist/lib/sandbox/control/server.js +29 -206
  28. package/dist/lib/sandbox/control/state.js +1 -7
  29. package/dist/lib/sandbox/control/task-view.js +2 -1
  30. package/dist/lib/task/artifact-command.js +6 -6
  31. package/dist/lib/task/codex-orchestration.js +10 -208
  32. package/dist/lib/task/commit-operation.js +3 -18
  33. package/dist/lib/task/control-authority.js +35 -44
  34. package/dist/lib/task/control-recovery.js +3 -42
  35. package/dist/lib/task/delegation-receipts.js +16 -78
  36. package/dist/lib/task/events.js +89 -237
  37. package/dist/lib/task/finalization-artifacts.js +1 -3
  38. package/dist/lib/task/finalization-state.js +2 -1
  39. package/dist/lib/task/finalization.js +208 -57
  40. package/dist/lib/task/lifecycle-execution.js +2 -11
  41. package/dist/lib/task/lifecycle.js +1 -1
  42. package/dist/lib/task/local-artifact-finalization.js +25 -242
  43. package/dist/lib/task/manual-validation-receipt.js +8 -3
  44. package/dist/lib/task/orchestration.js +12 -24
  45. package/dist/lib/task/review-command.js +0 -1
  46. package/dist/lib/task/review-finalization.js +46 -211
  47. package/dist/lifecycle-build-manifest.json +3 -3
  48. package/lib/agent-clients/adapter.ts +46 -0
  49. package/lib/agent-clients/adapters/codex-lifecycle/recovery.ts +297 -0
  50. package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +13 -33
  51. package/lib/agent-clients/adapters/codex-lifecycle/store.ts +1 -44
  52. package/lib/agent-clients/adapters/codex.ts +10 -0
  53. package/lib/internal/cli-route-inventory.ts +1 -1
  54. package/lib/internal/manual-validation.ts +22 -26
  55. package/lib/internal/platform-comment.ts +38 -0
  56. package/lib/internal/sandbox-control.ts +1 -185
  57. package/lib/internal/task-artifact.ts +1 -1
  58. package/lib/internal/task-lifecycle.ts +2 -2
  59. package/lib/internal/task-operation-registry.ts +8 -4
  60. package/lib/internal/task-review.ts +5 -5
  61. package/lib/internal/task-validate.ts +0 -5
  62. package/lib/platform/comment-safety.ts +8 -0
  63. package/lib/platform/github-provider.ts +11 -2
  64. package/lib/platform/issue-comments.ts +102 -17
  65. package/lib/platform/pr-summary.ts +10 -2
  66. package/lib/platform/provider-contract.ts +2 -0
  67. package/lib/platform/provider-validation.ts +6 -2
  68. package/lib/platform/verification-sync.ts +19 -2
  69. package/lib/sandbox/control/client.ts +5 -68
  70. package/lib/sandbox/control/executor.ts +14 -125
  71. package/lib/sandbox/control/finalization-response.ts +8 -2
  72. package/lib/sandbox/control/protocol.ts +7 -83
  73. package/lib/sandbox/control/server.ts +30 -220
  74. package/lib/sandbox/control/state.ts +1 -9
  75. package/lib/sandbox/control/task-view.ts +2 -1
  76. package/lib/task/artifact-command.ts +6 -7
  77. package/lib/task/codex-orchestration.ts +8 -241
  78. package/lib/task/commit-operation.ts +3 -23
  79. package/lib/task/control-authority.ts +39 -55
  80. package/lib/task/control-recovery.ts +3 -43
  81. package/lib/task/delegation-receipts.ts +14 -81
  82. package/lib/task/events.ts +106 -255
  83. package/lib/task/finalization-artifacts.ts +1 -3
  84. package/lib/task/finalization-state.ts +2 -1
  85. package/lib/task/finalization.ts +221 -65
  86. package/lib/task/lifecycle-execution.ts +10 -21
  87. package/lib/task/lifecycle.ts +2 -2
  88. package/lib/task/local-artifact-finalization.ts +23 -281
  89. package/lib/task/manual-validation-receipt.ts +8 -2
  90. package/lib/task/orchestration.ts +30 -53
  91. package/lib/task/review-command.ts +0 -1
  92. package/lib/task/review-finalization.ts +45 -193
  93. package/package.json +2 -2
  94. package/templates/.agents/rules/lifecycle-orchestration.en.md +23 -9
  95. package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +23 -9
  96. package/templates/.agents/rules/local-artifact-repair.en.md +14 -60
  97. package/templates/.agents/rules/local-artifact-repair.zh-CN.md +13 -60
  98. package/templates/.agents/skills/analyze-task/SKILL.en.md +3 -3
  99. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +3 -3
  100. package/templates/.agents/skills/code-task/SKILL.en.md +3 -3
  101. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +3 -3
  102. package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +2 -2
  103. package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +2 -2
  104. package/templates/.agents/skills/complete-task/SKILL.en.md +7 -8
  105. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +7 -8
  106. package/templates/.agents/skills/plan-task/SKILL.en.md +3 -3
  107. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +3 -3
  108. package/templates/.agents/skills/review-analysis/SKILL.en.md +2 -2
  109. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +2 -2
  110. package/templates/.agents/skills/review-code/SKILL.en.md +3 -3
  111. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +3 -3
  112. package/templates/.agents/skills/review-plan/SKILL.en.md +2 -2
  113. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +2 -2
  114. package/templates/.agents/skills/run-manual-validation/SKILL.en.md +2 -2
  115. package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +2 -2
  116. package/templates/.agents/skills/run-task/SKILL.en.md +10 -9
  117. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +10 -9
  118. package/templates/.agents/skills/run-task/reference/host-validation.en.md +2 -2
  119. package/templates/.agents/skills/run-task/reference/host-validation.zh-CN.md +2 -2
  120. package/dist/lib/sandbox/control/task-workflow.js +0 -74
  121. package/dist/lib/sandbox/control/workflow-executor.js +0 -157
  122. package/dist/lib/task/lifecycle-recovery.js +0 -549
  123. package/dist/lib/task/recovery-warning.js +0 -19
  124. package/dist/lib/task/workflow-command.js +0 -18
  125. package/dist/lib/task/workflow-dispatch.js +0 -161
  126. package/lib/sandbox/control/task-workflow.ts +0 -95
  127. package/lib/sandbox/control/workflow-executor.ts +0 -183
  128. package/lib/task/lifecycle-recovery.ts +0 -691
  129. package/lib/task/recovery-warning.ts +0 -21
  130. package/lib/task/workflow-command.ts +0 -19
  131. package/lib/task/workflow-dispatch.ts +0 -171
@@ -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, 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',
@@ -127,19 +88,7 @@ function failedFinalization(request, error, extra = {}) {
127
88
  diagnostics: [], error, ...extra
128
89
  };
129
90
  }
130
- function tupleFor(request, taskId, authority) {
131
- const round = parseArtifactName(request.artifact)?.round ?? 0;
132
- return {
133
- taskId,
134
- family: request.family,
135
- artifact: request.artifact,
136
- round,
137
- requestId: authority?.lifecycleRequestId ?? `local-finalize:${taskId}:${request.family}:${round}`,
138
- phase: authority?.phase ?? 'artifact.finalize-local',
139
- authorityDigest: authority ? authorityDigest(authority) : null
140
- };
141
- }
142
- function prepareLocalArtifact(request, candidate, authority, preflightOnly = false) {
91
+ function prepareLocalArtifact(request, candidate) {
143
92
  const failed = (code, message, extra = {}) => ({
144
93
  result: failedFinalization(request, { code, message }, extra), content: candidate ?? ''
145
94
  });
@@ -149,39 +98,20 @@ function prepareLocalArtifact(request, candidate, authority, preflightOnly = fal
149
98
  const parsed = parseArtifactName(request.artifact);
150
99
  if (!parsed || parsed.family !== request.family)
151
100
  return failed('ARTIFACT_IDENTITY_INVALID', `artifact '${request.artifact}' does not match ${request.family}`);
152
- if (authority && !validFinalizerAuthority(authority, request, resolved.taskId)) {
153
- return failed('LOCAL_EXECUTION_AUTHORITY_INVALID', 'finalizer authority does not match the artifact tuple');
154
- }
155
101
  let taskContent;
156
102
  let content;
157
- let recovery;
158
- let recoveryState;
159
103
  try {
160
104
  taskContent = fs.readFileSync(resolved.taskMdPath, 'utf8');
161
- if (request.recoveryId) {
162
- const intent = readArtifactRecoveryIntent(resolved.repoRoot, resolved.taskId, request.family, request.artifact);
163
- if (!intent || intent.recoveryOperationId !== request.recoveryId)
164
- return failed('LOCAL_RECOVERY_PROVENANCE_CONFLICT', 'recovery id does not match the artifact journal');
165
- if (!['awaiting-preflight-recovery', 'preflight-ready', 'preflight-commit-started', 'preflight-passed', 'full-finalizer-ready', 'commit-started'].includes(intent.state))
166
- return failed('LOCAL_RECOVERY_PROVENANCE_CONFLICT', `recovery journal is in '${intent.state}' state`);
167
- recoveryState = intent.state;
168
- recovery = recoveryContextFromIntent(resolved.repoRoot, resolved.taskDir, intent);
169
- content = fs.existsSync(recovery.stagingPath)
170
- ? fs.readFileSync(recovery.stagingPath, 'utf8')
171
- : fs.readFileSync(recovery.formalPath, 'utf8');
172
- }
173
- else {
174
- const validated = validateCompletedArtifact(resolved.taskDir, request.family, request.artifact, parsed.round);
175
- if (!validated.ok)
176
- return failed(validated.error.code, validated.error.message);
177
- content = candidate ?? fs.readFileSync(validated.artifact.path, 'utf8');
178
- }
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');
179
109
  }
180
110
  catch (error) {
181
111
  return failed('ARTIFACT_NOT_READABLE', String(error));
182
112
  }
183
- if (resolved.state !== 'active' || !hasOpenArtifactRound(taskContent, request.family, parsed.round)) {
184
- 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');
185
115
  }
186
116
  const validation = validateLocalArtifact(content, {
187
117
  family: request.family, requiredSections: request.requiredSections, taskContent, artifact: request.artifact
@@ -194,173 +124,26 @@ function prepareLocalArtifact(request, candidate, authority, preflightOnly = fal
194
124
  taskId: resolved.taskId, taskDir: resolved.taskDir, artifactSha256,
195
125
  semanticDigest: validation.semanticDigest, diagnostics: validation.diagnostics
196
126
  });
197
- if (request.recoveryId) {
198
- if (!validation.ok)
199
- return { result, content, repoRoot: resolved.repoRoot, recovery, authority, lockAlreadyHeld: request.lockAlreadyHeld };
200
- if (preflightOnly && recovery && recoveryState === 'awaiting-preflight-recovery') {
201
- const staged = stageArtifactCandidate(recovery, Buffer.from(content, 'utf8'), { lockAlreadyHeld: request.lockAlreadyHeld });
202
- prepareArtifactRecoveryCommit(recovery, staged.candidateSha256, staged.semanticDigest, { lockAlreadyHeld: request.lockAlreadyHeld });
203
- }
204
- return {
205
- result: { ...result, status: 'passed', changed: false, error: null, recovery: recoveryInfo(recovery) },
206
- content, repoRoot: resolved.repoRoot, recovery, authority, lockAlreadyHeld: request.lockAlreadyHeld
207
- };
208
- }
209
- const existing = readArtifactRecoveryIntent(resolved.repoRoot, resolved.taskId, request.family, request.artifact);
210
- if (existing?.state === 'passed' || existing?.state === 'consumed') {
211
- if (existing.state === 'consumed' && (existing.finalArtifactSha256 !== artifactSha256 || existing.finalSemanticDigest !== validation.semanticDigest)) {
212
- return { ...failed('LOCAL_RECOVERY_PROVENANCE_CONFLICT', 'formal artifact does not match its completed recovery journal'), content, repoRoot: resolved.repoRoot, authority, lockAlreadyHeld: request.lockAlreadyHeld };
213
- }
214
- if (existing.finalArtifactSha256 === artifactSha256 && existing.finalSemanticDigest === validation.semanticDigest) {
215
- const context = recoveryContextFromIntent(resolved.repoRoot, resolved.taskDir, existing);
216
- return {
217
- result: { ...result, status: 'passed', error: null, recovery: recoveryInfo(context) },
218
- content, repoRoot: resolved.repoRoot, recovery: context, authority, lockAlreadyHeld: request.lockAlreadyHeld
219
- };
220
- }
221
- }
222
- if (existing && ['preflight-ready', 'preflight-commit-started', 'preflight-passed', 'full-finalizer-ready'].includes(existing.state)) {
223
- const context = recoveryContextFromIntent(resolved.repoRoot, resolved.taskDir, existing);
224
- return {
225
- result: { ...result, status: 'passed', changed: false, error: null, recovery: recoveryInfo(context) },
226
- content, repoRoot: resolved.repoRoot, recovery: context, authority, lockAlreadyHeld: request.lockAlreadyHeld
227
- };
228
- }
229
- if (existing?.state === 'commit-started') {
230
- if (existing.finalArtifactSha256 !== artifactSha256 || existing.finalSemanticDigest !== validation.semanticDigest) {
231
- return { ...failed('LOCAL_RECOVERY_PROVENANCE_CONFLICT', 'formal artifact does not match the interrupted recovery journal'), content, repoRoot: resolved.repoRoot, authority, lockAlreadyHeld: request.lockAlreadyHeld };
232
- }
233
- const context = recoveryContextFromIntent(resolved.repoRoot, resolved.taskDir, existing);
234
- const reconciled = reconcileArtifactRecovery(context, {
235
- lockAlreadyHeld: request.lockAlreadyHeld,
236
- validateFinal: () => validation.semanticDigest === existing.finalSemanticDigest
237
- });
238
- if (reconciled.status !== 'passed') {
239
- return { ...failed('LOCAL_RECOVERY_PROVENANCE_CONFLICT', `interrupted recovery could not be reconciled: ${reconciled.status}`), content, repoRoot: resolved.repoRoot, recovery: context, authority, lockAlreadyHeld: request.lockAlreadyHeld };
240
- }
241
- return {
242
- result: { ...result, status: 'passed', changed: false, error: null, recovery: recoveryInfo(context) },
243
- content, repoRoot: resolved.repoRoot, recovery: context, authority, lockAlreadyHeld: request.lockAlreadyHeld
244
- };
245
- }
246
- try {
247
- if (validation.ok) {
248
- if (preflightOnly) {
249
- const context = beginArtifactRecovery(tupleFor(request, resolved.taskId, authority), Buffer.from(content, 'utf8'), {
250
- repoRoot: resolved.repoRoot, taskDir: resolved.taskDir,
251
- ...(authority ? { recoveryId: authority.operationId } : {}), lockAlreadyHeld: request.lockAlreadyHeld
252
- });
253
- const staged = stageArtifactCandidate(context, Buffer.from(content, 'utf8'), { lockAlreadyHeld: request.lockAlreadyHeld });
254
- prepareArtifactRecoveryCommit(context, staged.candidateSha256, staged.semanticDigest, { lockAlreadyHeld: request.lockAlreadyHeld });
255
- return { result: { ...result, status: 'passed', error: null, recovery: recoveryInfo(context) }, content, repoRoot: resolved.repoRoot, recovery: context, authority, lockAlreadyHeld: request.lockAlreadyHeld };
256
- }
257
- const context = recordArtifactRecoveryPassed(tupleFor(request, resolved.taskId, authority), {
258
- repoRoot: resolved.repoRoot,
259
- taskDir: resolved.taskDir,
260
- expectedFinalSha256: artifactSha256,
261
- expectedFinalSemanticDigest: validation.semanticDigest,
262
- ...(authority ? { recoveryId: authority.operationId } : {}),
263
- lockAlreadyHeld: request.lockAlreadyHeld
264
- });
265
- recovery = context;
266
- return { result: { ...result, status: 'passed', changed: false, error: null, recovery: recoveryInfo(context) }, content, repoRoot: resolved.repoRoot, recovery, authority, lockAlreadyHeld: request.lockAlreadyHeld };
267
- }
268
- const context = beginArtifactRecovery(tupleFor(request, resolved.taskId, authority), Buffer.from(content, 'utf8'), {
269
- repoRoot: resolved.repoRoot,
270
- taskDir: resolved.taskDir,
271
- ...(authority ? { recoveryId: authority.operationId } : {}),
272
- lockAlreadyHeld: request.lockAlreadyHeld
273
- });
274
- recovery = context;
275
- return {
276
- result: { ...result, recovery: recoveryInfo(context) },
277
- content, repoRoot: resolved.repoRoot, recovery, authority, lockAlreadyHeld: request.lockAlreadyHeld
278
- };
279
- }
280
- catch (error) {
281
- return { ...failed('LOCAL_RECOVERY_PROVENANCE_CONFLICT', String(error)), content, repoRoot: resolved.repoRoot, authority, lockAlreadyHeld: request.lockAlreadyHeld };
282
- }
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
+ };
283
136
  }
284
- function commitLocalArtifactProvenance(prepared, options = {}) {
285
- if (!prepared.recovery || !prepared.repoRoot || prepared.result.status === 'failed' && !prepared.result.recovery)
286
- return prepared.result;
287
- if (prepared.result.status === 'failed')
288
- return prepared.result;
289
- try {
290
- const intent = readArtifactRecoveryIntent(prepared.repoRoot, prepared.recovery.taskId, prepared.recovery.family, prepared.recovery.artifact);
291
- if (!intent)
292
- throw new Error('ARTIFACT_RECOVERY_INTENT_MISSING');
293
- let committed;
294
- if (intent.state === 'passed' || intent.state === 'consumed') {
295
- return {
296
- ...prepared.result,
297
- changed: false,
298
- artifactSha256: intent.finalArtifactSha256,
299
- semanticDigest: intent.finalSemanticDigest,
300
- error: null
301
- };
302
- }
303
- else if (intent.state === 'commit-started') {
304
- const reconciled = reconcileArtifactRecovery(prepared.recovery, {
305
- lockAlreadyHeld: prepared.lockAlreadyHeld,
306
- afterPublish: options.afterPublish
307
- });
308
- committed = reconciled.intent;
309
- }
310
- else if (intent.state === 'awaiting-preflight-recovery') {
311
- const staged = stageArtifactCandidate(prepared.recovery, Buffer.from(prepared.content, 'utf8'), { lockAlreadyHeld: prepared.lockAlreadyHeld });
312
- prepareArtifactRecoveryCommit(prepared.recovery, staged.candidateSha256, staged.semanticDigest, { lockAlreadyHeld: prepared.lockAlreadyHeld });
313
- committed = commitArtifactRecovery(prepared.recovery, { lockAlreadyHeld: prepared.lockAlreadyHeld });
314
- }
315
- if (intent.state === 'preflight-ready' || intent.state === 'preflight-commit-started') {
316
- committed = commitArtifactRecovery(prepared.recovery, { lockAlreadyHeld: prepared.lockAlreadyHeld });
317
- }
318
- if (committed?.state === 'preflight-passed' || intent.state === 'preflight-passed') {
319
- prepareArtifactRecoveryFinal(prepared.recovery, Buffer.from(prepared.content, 'utf8'), { lockAlreadyHeld: prepared.lockAlreadyHeld });
320
- committed = commitArtifactRecovery(prepared.recovery, { lockAlreadyHeld: prepared.lockAlreadyHeld, afterPublish: options.afterPublish });
321
- }
322
- else if (intent.state === 'full-finalizer-ready') {
323
- committed = commitArtifactRecovery(prepared.recovery, { lockAlreadyHeld: prepared.lockAlreadyHeld, afterPublish: options.afterPublish });
324
- }
325
- if (!committed || committed.state !== 'passed')
326
- throw new Error(`ARTIFACT_RECOVERY_STATE_INVALID: commit ended in '${committed?.state ?? intent.state}'`);
327
- return {
328
- ...prepared.result,
329
- status: 'passed',
330
- changed: false,
331
- artifactSha256: committed.finalArtifactSha256,
332
- semanticDigest: committed.finalSemanticDigest,
333
- error: null
334
- };
335
- }
336
- catch (error) {
337
- if (error instanceof Error && error.message.startsWith('TASK_WORKFLOW_FAULT_INJECTED:'))
338
- throw error;
339
- return { ...prepared.result, status: 'failed', error: {
340
- code: 'LOCAL_RECOVERY_COMMIT_FAILED', message: String(error)
341
- } };
342
- }
137
+ function commitLocalArtifactProvenance(prepared) {
138
+ return prepared.result;
343
139
  }
344
- function finalizeLocalArtifact(request, authority, options = {}) {
345
- const prepared = prepareLocalArtifact(request, undefined, authority);
346
- const result = commitLocalArtifactProvenance(prepared);
347
- if (authority && !options.deferLifecycleRecoveryConsumption && result.status === 'passed') {
348
- try {
349
- consumeLifecycleRecoveryAttestation(authority);
350
- }
351
- catch (error) {
352
- return { ...result, status: 'failed', error: {
353
- code: 'LOCAL_EXECUTION_AUTHORITY_CONSUME_FAILED',
354
- message: error instanceof Error ? error.message : String(error)
355
- } };
356
- }
357
- }
358
- return result;
140
+ function finalizeLocalArtifact(request) {
141
+ return prepareLocalArtifact(request).result;
359
142
  }
360
143
  /** Validate and seal an immutable preflight generation without publishing the formal artifact. */
361
- function preflightLocalArtifact(request, authority) {
362
- const prepared = prepareLocalArtifact(request, undefined, authority, true);
144
+ function preflightLocalArtifact(request) {
145
+ const prepared = prepareLocalArtifact(request);
363
146
  return prepared.result;
364
147
  }
365
- 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 };
366
149
  //# sourceMappingURL=local-artifact-finalization.js.map
@@ -4,6 +4,7 @@ import { createHash } from 'node:crypto';
4
4
  import { MANUAL_VALIDATION_ARTIFACT as ARTIFACT, MANUAL_VALIDATION_SHA40 as SHA40, MANUAL_VALIDATION_SHA64 as SHA64, MANUAL_VALIDATION_TASK_ID as TASK_ID, exactKeys, isRecord, validTimestamp, writeJsonAtomic } from './manual-validation-shared.js';
5
5
  const MANUAL_VALIDATION_RECEIPT_SCHEMA = 'agent-infra/manual-validation-receipt';
6
6
  const MANUAL_VALIDATION_RECEIPT_VERSION = 1;
7
+ const MANUAL_VALIDATION_RECEIPT_PLACEHOLDER = '[[manual-validation-receipt]]';
7
8
  function invalid(message) {
8
9
  return { ok: false, error: { code: 'MANUAL_VALIDATION_RECEIPT_INVALID', message } };
9
10
  }
@@ -33,12 +34,16 @@ function manualValidationFinalSummaryDigest(body) {
33
34
  const projection = heading
34
35
  ? body.slice(heading.index, heading.index + heading[0].length + (nextSection?.index ?? afterHeading.length)).replace(/\s+$/u, '')
35
36
  : body;
36
- const preimage = projection.replace(/receipt=(?:[a-f0-9]{64}|<receipt>)/gu, 'receipt=<receipt>');
37
+ const preimage = projection
38
+ .replace(/\n*<!--\s*canonical-pr-change-report\s*-->\s*/gu, '')
39
+ .replace(/<!--\s*manual-validation-receipt:\s*[\s\S]*?-->/gu, MANUAL_VALIDATION_RECEIPT_PLACEHOLDER)
40
+ .replace(/receipt=(?:[a-f0-9]{64}|<receipt>)/gu, 'receipt=<receipt>');
37
41
  return createHash('sha256').update(preimage, 'utf8').digest('hex');
38
42
  }
39
43
  function manualValidationFinalSummaryProjectionMatches(body, receipt) {
40
44
  const identity = `transaction=${receipt.transactionId}; receipt=${receipt.receiptDigest}; evidence=${receipt.evidenceDigest}; head=${receipt.prHeadSha}`;
41
- return body.includes(identity) && manualValidationFinalSummaryDigest(body) === receipt.finalSummaryDigest;
45
+ return (body.includes(identity) || body.includes(MANUAL_VALIDATION_RECEIPT_PLACEHOLDER))
46
+ && manualValidationFinalSummaryDigest(body) === receipt.finalSummaryDigest;
42
47
  }
43
48
  function validateManualValidationReceipt(value, expected) {
44
49
  if (!isRecord(value))
@@ -101,5 +106,5 @@ function readManualValidationReceipt(taskDir, expected) {
101
106
  }
102
107
  return validateManualValidationReceipt(value, expected);
103
108
  }
104
- export { MANUAL_VALIDATION_RECEIPT_SCHEMA, MANUAL_VALIDATION_RECEIPT_VERSION, createManualValidationReceipt, manualValidationFinalSummaryDigest, manualValidationFinalSummaryProjectionMatches, manualValidationReceiptDigest, manualValidationReceiptPath, readManualValidationReceipt, validateManualValidationReceipt, writeManualValidationReceiptAtomic };
109
+ export { MANUAL_VALIDATION_RECEIPT_SCHEMA, MANUAL_VALIDATION_RECEIPT_VERSION, MANUAL_VALIDATION_RECEIPT_PLACEHOLDER, createManualValidationReceipt, manualValidationFinalSummaryDigest, manualValidationFinalSummaryProjectionMatches, manualValidationReceiptDigest, manualValidationReceiptPath, readManualValidationReceipt, validateManualValidationReceipt, writeManualValidationReceiptAtomic };
105
110
  //# sourceMappingURL=manual-validation-receipt.js.map
@@ -682,16 +682,6 @@ function prepareOrchestrationDelegationUnlocked(taskRef, input, options = {}) {
682
682
  if (!validModel(input.requestedReasoningEffort)) {
683
683
  return failed('ORCHESTRATION_REQUESTED_REASONING_EFFORT_REQUIRED', 'prepare requires the exact requested reasoning effort', resolved.taskId);
684
684
  }
685
- if (input.client === 'codex' && !input.lifecycleProvenance) {
686
- return failed('ORCHESTRATION_CODEX_PROVENANCE_REQUIRED', 'Codex prepare requires lifecycle provenance', resolved.taskId);
687
- }
688
- if (input.lifecycleRecoveryAttestation
689
- && (input.lifecycleRecoveryAttestation.phase !== 'orchestration.prepare'
690
- || input.lifecycleRecoveryAttestation.taskId !== resolved.taskId
691
- || input.lifecycleRecoveryAttestation.artifact !== next.artifact
692
- || input.lifecycleRecoveryAttestation.round !== next.round)) {
693
- return failed('LIFECYCLE_AUTHORITY_TUPLE_MISMATCH', 'orchestration authority does not match the routed stage', resolved.taskId);
694
- }
695
685
  const expectedPolicy = run.modelPolicy[next.role];
696
686
  if (input.requestedModel !== expectedPolicy.model) {
697
687
  return failed('ORCHESTRATION_REQUESTED_MODEL_MISMATCH', `requested model does not match the persisted ${next.role} model`, resolved.taskId);
@@ -699,10 +689,6 @@ function prepareOrchestrationDelegationUnlocked(taskRef, input, options = {}) {
699
689
  if (input.requestedReasoningEffort !== expectedPolicy.reasoningEffort) {
700
690
  return failed('ORCHESTRATION_REQUESTED_REASONING_EFFORT_MISMATCH', `requested reasoning effort does not match the persisted ${next.role} policy`, resolved.taskId);
701
691
  }
702
- const capabilityValidationError = options.validateLifecycleCapability?.();
703
- if (capabilityValidationError) {
704
- return failed(capabilityValidationError.code, capabilityValidationError.message, resolved.taskId, capabilityValidationError.detail ? { detail: capabilityValidationError.detail } : {});
705
- }
706
692
  let beforeFingerprint;
707
693
  try {
708
694
  beforeFingerprint = (options.captureWorkspace ?? captureWorkspaceSnapshot)({
@@ -728,10 +714,6 @@ function prepareOrchestrationDelegationUnlocked(taskRef, input, options = {}) {
728
714
  lifecycleProvenance: input.lifecycleProvenance ?? null,
729
715
  beforeFingerprint
730
716
  }, { id: options.id, now: options.now, monotonicNow: options.monotonicNow });
731
- const capabilityError = options.consumeLifecycleCapability?.();
732
- if (capabilityError) {
733
- return failed(capabilityError.code, capabilityError.message, resolved.taskId, capabilityError.detail ? { detail: capabilityError.detail } : {});
734
- }
735
717
  const updated = withUpdatedRun(run, {
736
718
  nextStage: next.stage,
737
719
  pendingDelegation: receipt
@@ -1093,6 +1075,14 @@ function recoverPreparedOrchestrationDelegation(taskRef, options = {}) {
1093
1075
  return failed('ORCHESTRATION_PREPARED_RECOVERY_FAILED', error instanceof Error ? error.message : String(error), resolved.taskId);
1094
1076
  }
1095
1077
  }
1078
+ function recoveryReceiptMatches(receipt, taskId, event) {
1079
+ return receipt.taskId === taskId
1080
+ && receipt.stage === event.stage
1081
+ && receipt.round === event.round
1082
+ && receipt.artifact === event.artifact
1083
+ && receipt.childId === event.childId
1084
+ && normalizeAgentToken(event.startedAgent) === normalizeAgentToken(receipt.client);
1085
+ }
1096
1086
  function recoverActivatedOrchestrationDelegationUnderLock(taskRef, event, options = {}) {
1097
1087
  const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
1098
1088
  if (!resolved.ok)
@@ -1107,12 +1097,7 @@ function recoverActivatedOrchestrationDelegationUnderLock(taskRef, event, option
1107
1097
  const receipt = matches.find((candidate) => candidate.id === event.receiptId);
1108
1098
  if (!receipt)
1109
1099
  return failed('ORCHESTRATION_DELEGATION_MISSING', 'recovery receipt does not exist', resolved.taskId);
1110
- if (receipt.taskId !== resolved.taskId
1111
- || receipt.stage !== event.stage
1112
- || receipt.round !== event.round
1113
- || receipt.artifact !== event.artifact
1114
- || receipt.childId !== event.childId
1115
- || normalizeAgentToken(event.startedAgent) !== normalizeAgentToken(receipt.client)) {
1100
+ if (!recoveryReceiptMatches(receipt, resolved.taskId, event)) {
1116
1101
  return failed('ORCHESTRATION_PROVENANCE_MISMATCH', 'recovery selector does not match the delegation receipt', resolved.taskId);
1117
1102
  }
1118
1103
  if (receipt.status === 'aborted' && run.pendingDelegation === null) {
@@ -1124,6 +1109,9 @@ function recoverActivatedOrchestrationDelegationUnderLock(taskRef, event, option
1124
1109
  if (run.receipts.some((candidate) => candidate.id === receipt.id)) {
1125
1110
  return failed('ORCHESTRATION_RECEIPT_DUPLICATE', 'recovery receipt already exists in the completed receipt history', resolved.taskId);
1126
1111
  }
1112
+ if (run.status !== 'running' || run.pause !== null) {
1113
+ return failed('ORCHESTRATION_RECOVERY_UNSAFE', 'activated recovery cannot replace an unrelated orchestration pause', resolved.taskId);
1114
+ }
1127
1115
  const aborted = abortActivatedDelegation(receipt, {
1128
1116
  childId: event.childId,
1129
1117
  stopRevision: event.stopRevision,
@@ -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) {