@fitlab-ai/agent-infra 0.9.15 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (224) hide show
  1. package/README.md +2 -6
  2. package/README.zh-CN.md +2 -6
  3. package/bin/internal-cli.sh +0 -13
  4. package/dist/bin/internal-cli.js +11 -49
  5. package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +6 -2
  6. package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +43 -3
  7. package/dist/lib/fs/durable-write.js +2 -0
  8. package/dist/lib/internal/cli-route-inventory.js +3 -4
  9. package/dist/lib/internal/manual-validation.js +17 -8
  10. package/dist/lib/internal/platform-comment.js +60 -2
  11. package/dist/lib/internal/task-artifact.js +1 -1
  12. package/dist/lib/internal/task-finalization.js +2 -2
  13. package/dist/lib/internal/task-lifecycle.js +4 -4
  14. package/dist/lib/internal/task-operation-registry.js +22 -13
  15. package/dist/lib/internal/task-orchestration.js +2 -2
  16. package/dist/lib/internal/task-review.js +12 -7
  17. package/dist/lib/platform/activity-recovery.js +159 -0
  18. package/dist/lib/platform/issue-comments.js +89 -31
  19. package/dist/lib/platform/issues.js +1 -1
  20. package/dist/lib/platform/pr-change-report.js +5 -4
  21. package/dist/lib/platform/pr-summary.js +64 -32
  22. package/dist/lib/platform/pull-requests.js +3 -3
  23. package/dist/lib/platform/task-comment-projection.js +70 -0
  24. package/dist/lib/platform/task-identities.js +2 -9
  25. package/dist/lib/platform/task-recovery.js +55 -0
  26. package/dist/lib/platform/verification-sync.js +45 -7
  27. package/dist/lib/pr-review/evidence-grading.js +4 -8
  28. package/dist/lib/sandbox/commands/rm.js +21 -2
  29. package/dist/lib/sandbox/control/executor.js +4 -2
  30. package/dist/lib/sandbox/control/server.js +29 -9
  31. package/dist/lib/sandbox/control/state.js +7 -1
  32. package/dist/lib/sandbox/control/task-workflow.js +1 -1
  33. package/dist/lib/sandbox/control/workflow-executor.js +87 -35
  34. package/dist/lib/sandbox/index.js +1 -2
  35. package/dist/lib/sandbox/removal.js +116 -1011
  36. package/dist/lib/sandbox/workspace-identity.js +17 -1
  37. package/dist/lib/server/process-state.js +17 -5
  38. package/dist/lib/task/artifact-command.js +11 -30
  39. package/dist/lib/task/artifact-operations.js +17 -231
  40. package/dist/lib/task/artifact-recovery.js +476 -0
  41. package/dist/lib/task/artifact-repair-intent.js +90 -39
  42. package/dist/lib/task/commit-operation.js +31 -4
  43. package/dist/lib/task/control-authority.js +39 -8
  44. package/dist/lib/task/control-recovery.js +14 -2
  45. package/dist/lib/task/create-service.js +1 -8
  46. package/dist/lib/task/delegation-receipts.js +64 -3
  47. package/dist/lib/task/events.js +165 -23
  48. package/dist/lib/task/human-override.js +5 -8
  49. package/dist/lib/task/intermediate-cleanup.js +91 -8
  50. package/dist/lib/task/lifecycle-recovery.js +549 -0
  51. package/dist/lib/task/lifecycle.js +12 -3
  52. package/dist/lib/task/local-artifact-finalization.js +212 -123
  53. package/dist/lib/task/manual-validation-receipt.js +5 -1
  54. package/dist/lib/task/orchestration.js +52 -2
  55. package/dist/lib/task/pr-delivery-fact.js +12 -56
  56. package/dist/lib/task/recovery-warning.js +19 -0
  57. package/dist/lib/task/review-command.js +3 -2
  58. package/dist/lib/task/review-finalization.js +294 -74
  59. package/dist/lib/{host-control → task}/secure-fs.js +8 -19
  60. package/dist/lib/task/short-id.js +15 -7
  61. package/dist/lib/task/task-execution-lock.js +1 -1
  62. package/dist/lib/task/verification-engine.js +3 -14
  63. package/dist/lib/task/workflow-command.js +2 -1
  64. package/dist/lib/task/workflow-dispatch.js +161 -0
  65. package/dist/lib/version.js +1 -11
  66. package/dist/lifecycle-build-manifest.json +3 -3
  67. package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +7 -2
  68. package/lib/agent-clients/adapters/codex-lifecycle/store.ts +55 -3
  69. package/lib/fs/durable-write.ts +1 -0
  70. package/lib/internal/cli-route-inventory.ts +3 -4
  71. package/lib/internal/manual-validation.ts +17 -8
  72. package/lib/internal/platform-comment.ts +53 -2
  73. package/lib/internal/task-artifact.ts +1 -1
  74. package/lib/internal/task-finalization.ts +1 -1
  75. package/lib/internal/task-lifecycle.ts +5 -4
  76. package/lib/internal/task-operation-registry.ts +22 -13
  77. package/lib/internal/task-orchestration.ts +1 -1
  78. package/lib/internal/task-review.ts +11 -7
  79. package/lib/platform/activity-recovery.ts +179 -0
  80. package/lib/platform/issue-comments.ts +105 -30
  81. package/lib/platform/issues.ts +1 -1
  82. package/lib/platform/pr-change-report.ts +7 -5
  83. package/lib/platform/pr-summary.ts +64 -32
  84. package/lib/platform/pull-requests.ts +3 -3
  85. package/lib/platform/task-comment-projection.ts +79 -0
  86. package/lib/platform/task-identities.ts +3 -14
  87. package/lib/platform/task-recovery.ts +54 -0
  88. package/lib/platform/verification-sync.ts +48 -11
  89. package/lib/pr-review/evidence-grading.ts +4 -9
  90. package/lib/sandbox/commands/rm.ts +34 -2
  91. package/lib/sandbox/control/executor.ts +4 -2
  92. package/lib/sandbox/control/protocol.ts +6 -1
  93. package/lib/sandbox/control/server.ts +30 -10
  94. package/lib/sandbox/control/state.ts +9 -1
  95. package/lib/sandbox/control/task-workflow.ts +1 -1
  96. package/lib/sandbox/control/workflow-executor.ts +106 -35
  97. package/lib/sandbox/index.ts +1 -2
  98. package/lib/sandbox/removal.ts +126 -1128
  99. package/lib/sandbox/workspace-identity.ts +19 -3
  100. package/lib/server/process-state.ts +26 -5
  101. package/lib/task/artifact-command.ts +12 -29
  102. package/lib/task/artifact-operations.ts +22 -252
  103. package/lib/task/artifact-recovery.ts +572 -0
  104. package/lib/task/artifact-repair-intent.ts +126 -51
  105. package/lib/task/commit-operation.ts +35 -4
  106. package/lib/task/control-authority.ts +48 -15
  107. package/lib/task/control-recovery.ts +13 -1
  108. package/lib/task/create-service.ts +1 -10
  109. package/lib/task/delegation-receipts.ts +73 -2
  110. package/lib/task/events.ts +188 -24
  111. package/lib/task/human-override.ts +5 -8
  112. package/lib/task/intermediate-cleanup.ts +66 -12
  113. package/lib/task/lifecycle-recovery.ts +691 -0
  114. package/lib/task/lifecycle.ts +13 -4
  115. package/lib/task/local-artifact-finalization.ts +249 -131
  116. package/lib/task/manual-validation-receipt.ts +5 -1
  117. package/lib/task/orchestration.ts +65 -0
  118. package/lib/task/pr-delivery-fact.ts +15 -62
  119. package/lib/task/recovery-warning.ts +21 -0
  120. package/lib/task/review-command.ts +2 -1
  121. package/lib/task/review-finalization.ts +298 -84
  122. package/lib/{host-control → task}/secure-fs.ts +10 -32
  123. package/lib/task/short-id.ts +27 -5
  124. package/lib/task/task-execution-lock.ts +1 -1
  125. package/lib/task/verification-engine.ts +3 -17
  126. package/lib/task/verification-types.ts +0 -2
  127. package/lib/task/workflow-command.ts +2 -1
  128. package/lib/task/workflow-dispatch.ts +171 -0
  129. package/lib/version.ts +1 -15
  130. package/package.json +1 -1
  131. package/templates/.agents/rules/create-issue.en.md +1 -1
  132. package/templates/.agents/rules/create-issue.zh-CN.md +1 -1
  133. package/templates/.agents/rules/issue-sync.en.md +12 -2
  134. package/templates/.agents/rules/issue-sync.zh-CN.md +13 -5
  135. package/templates/.agents/rules/local-artifact-repair.en.md +47 -39
  136. package/templates/.agents/rules/local-artifact-repair.zh-CN.md +45 -36
  137. package/templates/.agents/skills/analyze-task/SKILL.en.md +6 -6
  138. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +6 -6
  139. package/templates/.agents/skills/analyze-task/config/verify.en.json +1 -1
  140. package/templates/.agents/skills/analyze-task/config/verify.zh-CN.json +1 -1
  141. package/templates/.agents/skills/block-task/SKILL.en.md +2 -2
  142. package/templates/.agents/skills/block-task/SKILL.zh-CN.md +2 -2
  143. package/templates/.agents/skills/block-task/config/verify.json +1 -1
  144. package/templates/.agents/skills/cancel-task/SKILL.en.md +3 -3
  145. package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +3 -3
  146. package/templates/.agents/skills/cancel-task/config/verify.json +1 -1
  147. package/templates/.agents/skills/code-task/SKILL.en.md +7 -7
  148. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +8 -8
  149. package/templates/.agents/skills/code-task/config/verify.en.json +1 -1
  150. package/templates/.agents/skills/code-task/config/verify.zh-CN.json +1 -1
  151. package/templates/.agents/skills/commit/reference/issue-metadata-sync.en.md +1 -1
  152. package/templates/.agents/skills/commit/reference/issue-metadata-sync.zh-CN.md +1 -1
  153. package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +1 -1
  154. package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +1 -1
  155. package/templates/.agents/skills/complete-task/SKILL.en.md +2 -2
  156. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +2 -2
  157. package/templates/.agents/skills/complete-task/config/verify.en.json +2 -2
  158. package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +2 -2
  159. package/templates/.agents/skills/complete-task/reference/external-delivery.en.md +1 -1
  160. package/templates/.agents/skills/complete-task/reference/external-delivery.zh-CN.md +1 -1
  161. package/templates/.agents/skills/create-pr/SKILL.en.md +2 -2
  162. package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +2 -2
  163. package/templates/.agents/skills/create-pr/config/verify.json +1 -1
  164. package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +2 -2
  165. package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +2 -2
  166. package/templates/.agents/skills/create-task/SKILL.en.md +5 -5
  167. package/templates/.agents/skills/create-task/SKILL.zh-CN.md +5 -5
  168. package/templates/.agents/skills/create-task/config/verify.json +1 -1
  169. package/templates/.agents/skills/import-issue/SKILL.en.md +8 -8
  170. package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +8 -8
  171. package/templates/.agents/skills/import-issue/config/verify.json +2 -2
  172. package/templates/.agents/skills/plan-task/SKILL.en.md +4 -4
  173. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +4 -4
  174. package/templates/.agents/skills/plan-task/config/verify.en.json +1 -1
  175. package/templates/.agents/skills/plan-task/config/verify.zh-CN.json +1 -1
  176. package/templates/.agents/skills/restore-task/SKILL.en.md +11 -6
  177. package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +11 -6
  178. package/templates/.agents/skills/restore-task/config/verify.json +1 -1
  179. package/templates/.agents/skills/review-analysis/SKILL.en.md +3 -3
  180. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +3 -3
  181. package/templates/.agents/skills/review-analysis/config/verify.en.json +1 -1
  182. package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +1 -1
  183. package/templates/.agents/skills/review-analysis/reference/report-template.en.md +7 -7
  184. package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +6 -6
  185. package/templates/.agents/skills/review-code/SKILL.en.md +5 -4
  186. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +5 -4
  187. package/templates/.agents/skills/review-code/config/verify.en.json +1 -1
  188. package/templates/.agents/skills/review-code/config/verify.zh-CN.json +1 -1
  189. package/templates/.agents/skills/review-code/reference/report-template.en.md +12 -12
  190. package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +12 -12
  191. package/templates/.agents/skills/review-plan/SKILL.en.md +3 -3
  192. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +3 -3
  193. package/templates/.agents/skills/review-plan/config/verify.en.json +1 -1
  194. package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +1 -1
  195. package/templates/.agents/skills/review-plan/reference/report-template.en.md +7 -7
  196. package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +7 -7
  197. package/templates/.agents/skills/review-pr/config/verify.en.json +1 -1
  198. package/templates/.agents/skills/review-pr/config/verify.zh-CN.json +1 -1
  199. package/templates/.agents/skills/review-pr/reference/evidence-grading.en.md +3 -3
  200. package/templates/.agents/skills/review-pr/reference/evidence-grading.zh-CN.md +3 -3
  201. package/templates/.agents/skills/run-manual-validation/config/verify.en.json +1 -1
  202. package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +1 -1
  203. package/templates/.agents/skills/run-task/SKILL.en.md +1 -1
  204. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +1 -1
  205. package/templates/.agents/templates/task.en.md +1 -1
  206. package/templates/.agents/templates/task.zh-CN.md +1 -1
  207. package/templates/.claude/commands/import-issue.en.md +1 -1
  208. package/templates/.claude/commands/import-issue.zh-CN.md +1 -1
  209. package/templates/.claude/commands/restore-task.en.md +1 -1
  210. package/templates/.claude/commands/restore-task.zh-CN.md +1 -1
  211. package/dist/lib/host-control/audit.js +0 -19
  212. package/dist/lib/host-control/client.js +0 -142
  213. package/dist/lib/host-control/command.js +0 -49
  214. package/dist/lib/host-control/path.js +0 -160
  215. package/dist/lib/host-control/server.js +0 -159
  216. package/dist/lib/host-control/service.js +0 -112
  217. package/dist/lib/internal/host-control.js +0 -28
  218. package/lib/host-control/audit.ts +0 -22
  219. package/lib/host-control/client.ts +0 -172
  220. package/lib/host-control/command.ts +0 -60
  221. package/lib/host-control/path.ts +0 -167
  222. package/lib/host-control/server.ts +0 -180
  223. package/lib/host-control/service.ts +0 -133
  224. package/lib/internal/host-control.ts +0 -28
@@ -2,12 +2,13 @@ import fs from 'node:fs';
2
2
  import { inspectDecisionDetailDuplicates } from './decision-details.js';
3
3
  import { scanVisibleMarkdown } from './markdown.js';
4
4
  import { parseArtifactName } from './artifact-name.js';
5
- import { hasOpenArtifactRound, validateArtifactPublication, validateCompletedArtifact } from './artifact-lifecycle.js';
5
+ import { hasOpenArtifactRound, parseCodePlanInputReference, validateArtifactPublication, validateCompletedArtifact } from './artifact-lifecycle.js';
6
6
  import { resolveTaskRef } from './resolve-ref.js';
7
7
  import { expectedQualificationRelations, validateQualificationAudit } from './qualification-audit.js';
8
8
  import { canonicalSemanticDigest, inspectArtifactPatterns, inspectArtifactStructure, sha256Content } from './artifact-operations.js';
9
9
  import { getArtifactSchema } from './artifact-schema.js';
10
- import { readArtifactRepairIntent, writeArtifactRepairIntent } from './artifact-repair-intent.js';
10
+ import { readArtifactRecoveryIntent } from './artifact-repair-intent.js';
11
+ import { beginArtifactRecovery, commitArtifactRecovery, consumeArtifactRecovery, prepareArtifactRecoveryFinal, prepareArtifactRecoveryCommit, recordArtifactRecoveryPassed, reconcileArtifactRecovery, recoveryContextFromIntent, stageArtifactCandidate } from './artifact-recovery.js';
11
12
  import { consumeLifecycleRecoveryAttestation, lifecycleRecoveryAttestationDigest, validateLifecycleRecoveryAttestation } from './control-authority.js';
12
13
  const LOCAL_ARTIFACT_REQUIRED_SECTIONS = {
13
14
  analysis: getArtifactSchema('analysis').sections.map((section) => section.headings.zh),
@@ -35,14 +36,21 @@ function validFinalizerAuthority(authority, request, taskId) {
35
36
  && authority.requestId.length > 0
36
37
  && authority.operationId.length > 0;
37
38
  }
38
- function consumeLocalArtifactFinalizationIntent(repoRoot, intent) {
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 = {}) {
39
48
  if (intent.state === 'consumed')
40
49
  return intent;
41
- if (intent.state !== 'passed')
42
- throw new Error('LOCAL_FINALIZATION_INTENT_INVALID: only passed provenance can be consumed');
43
- const consumed = { ...intent, state: 'consumed', updatedAt: Date.now() };
44
- writeArtifactRepairIntent(repoRoot, consumed, { expected: intent });
45
- return consumed;
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);
46
54
  }
47
55
  function localDiagnosticCode(code) {
48
56
  const map = {
@@ -54,78 +62,56 @@ function localDiagnosticCode(code) {
54
62
  return map[code] ?? 'LOCAL_STRUCTURAL_INVALID';
55
63
  }
56
64
  function localDiagnostic(item) {
57
- const code = localDiagnosticCode(item.code);
58
- if (!code)
59
- return null;
60
- return {
61
- code,
62
- message: item.message,
63
- repairable: item.repairable,
64
- line: item.line,
65
- ...(item.operation?.kind === 'replace-line'
66
- ? { from: item.operation.from, to: item.operation.to, operation: 'replace-line' }
67
- : {})
68
- };
65
+ return { code: localDiagnosticCode(item.code), message: item.message, line: item.line };
69
66
  }
70
67
  function isStatusPattern(pattern) {
71
68
  return pattern === '^\\$ ';
72
69
  }
73
70
  function validateLocalArtifact(content, options) {
74
71
  const schema = getArtifactSchema(options.family);
75
- const patterns = schema.requiredPatterns;
76
72
  const diagnostics = [];
77
73
  const scanned = scanVisibleMarkdown(content);
78
74
  const structure = inspectArtifactStructure(content, schema);
79
- for (const item of structure.diagnostics) {
80
- const mapped = localDiagnostic(item);
81
- if (mapped)
82
- diagnostics.push(mapped);
83
- }
75
+ for (const item of structure.diagnostics)
76
+ diagnostics.push(localDiagnostic(item));
84
77
  const statusSection = schema.sections.find((section) => section.id === 'state-check');
85
78
  if (statusSection) {
86
79
  const statusHeading = scanned.headings.find((heading) => (heading.level === 2 && [statusSection.headings.zh, statusSection.headings.en].some((name) => (heading.text === name || heading.text === `${name}:` || heading.text === `${name}:`))));
87
80
  if (statusHeading) {
88
81
  const next = scanned.headings.find((candidate) => candidate.start > statusHeading.start && candidate.level <= 2);
89
82
  const body = content.slice(statusHeading.end, next?.start ?? content.length);
90
- for (const pattern of patterns.filter(isStatusPattern)) {
83
+ for (const pattern of schema.requiredPatterns.filter(isStatusPattern)) {
91
84
  if (!new RegExp(pattern, 'm').test(body)) {
92
- diagnostics.push({ code: 'LOCAL_STATUS_COMMAND_MISSING', message: `status section '${statusHeading.text}' is missing required command output`, repairable: false, line: content.slice(0, statusHeading.start).split('\n').length });
85
+ diagnostics.push({ code: 'LOCAL_STATUS_COMMAND_MISSING', message: `status section '${statusHeading.text}' is missing required command output`, line: content.slice(0, statusHeading.start).split('\n').length });
93
86
  }
94
87
  }
95
88
  }
96
89
  }
97
90
  const patternInspection = inspectArtifactPatterns(content, {
98
91
  ...schema,
99
- requiredPatterns: patterns.filter((item) => !isStatusPattern(item))
92
+ requiredPatterns: schema.requiredPatterns.filter((item) => !isStatusPattern(item))
100
93
  });
101
- for (const item of patternInspection.diagnostics) {
102
- diagnostics.push({ code: 'LOCAL_REQUIRED_PATTERN_MISSING', message: item.message, repairable: false, line: null });
103
- }
94
+ for (const item of patternInspection.diagnostics)
95
+ diagnostics.push({ code: 'LOCAL_REQUIRED_PATTERN_MISSING', message: item.message, line: null });
104
96
  const decisionDetails = inspectDecisionDetailDuplicates(content);
105
- if (!decisionDetails.ok) {
106
- diagnostics.push({ code: 'LOCAL_DECISION_DETAIL_DUPLICATE', message: decisionDetails.message, repairable: false, line: null });
107
- }
97
+ if (!decisionDetails.ok)
98
+ diagnostics.push({ code: 'LOCAL_DECISION_DETAIL_DUPLICATE', message: decisionDetails.message, line: null });
108
99
  if (options.taskContent !== undefined) {
109
100
  const expected = expectedQualificationRelations(options.taskContent, options.family);
110
- if (!expected.ok) {
111
- diagnostics.push({ code: 'LOCAL_QUALIFICATION_AUDIT_INVALID', message: `${expected.code}: ${expected.message}`, repairable: false, line: null });
112
- }
101
+ if (!expected.ok)
102
+ diagnostics.push({ code: 'LOCAL_QUALIFICATION_AUDIT_INVALID', message: `${expected.code}: ${expected.message}`, line: null });
113
103
  const qualification = validateQualificationAudit(options.taskContent, content, {
114
- family: options.family === 'analysis' ? 'analysis' : options.family === 'plan' ? 'plan' : 'code',
104
+ family: options.family,
115
105
  artifact: options.artifact,
116
106
  expectedUpstreamRelations: expected.ok ? expected.relations : undefined
117
107
  });
118
- if (!qualification.ok) {
119
- diagnostics.push({ code: 'LOCAL_QUALIFICATION_AUDIT_INVALID', message: `${qualification.code}: ${qualification.message}`, repairable: false, line: null });
120
- }
108
+ if (!qualification.ok)
109
+ diagnostics.push({ code: 'LOCAL_QUALIFICATION_AUDIT_INVALID', message: `${qualification.code}: ${qualification.message}`, line: null });
121
110
  }
122
- const repairable = structure.repair !== null && diagnostics.length === 1 && diagnostics[0]?.repairable === true;
123
- const semanticDigestValue = canonicalSemanticDigest(content, structure.repair);
124
111
  return {
125
112
  ok: diagnostics.length === 0,
126
113
  family: options.family,
127
- semanticDigest: semanticDigestValue,
128
- repairable,
114
+ semanticDigest: canonicalSemanticDigest(content),
129
115
  diagnostics
130
116
  };
131
117
  }
@@ -135,13 +121,24 @@ function failedFinalization(request, error, extra = {}) {
135
121
  taskId: null, taskDir: null,
136
122
  family: request.family, artifact: request.artifact,
137
123
  artifactSha256: null, semanticDigest: null,
138
- repairable: false, diagnostics: [], error, ...extra
124
+ diagnostics: [], error, ...extra
125
+ };
126
+ }
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
139
137
  };
140
138
  }
141
- /** Read and validate once; callers publish this content before committing provenance. */
142
- function prepareLocalArtifact(request, candidate, authority) {
143
- const failed = (code, message) => ({
144
- result: failedFinalization(request, { code, message }), content: candidate ?? ''
139
+ function prepareLocalArtifact(request, candidate, authority, preflightOnly = false) {
140
+ const failed = (code, message, extra = {}) => ({
141
+ result: failedFinalization(request, { code, message }, extra), content: candidate ?? ''
145
142
  });
146
143
  const resolved = resolveTaskRef(request.taskRef, { repoRoot: request.repoRoot });
147
144
  if (!resolved.ok)
@@ -149,114 +146,201 @@ function prepareLocalArtifact(request, candidate, authority) {
149
146
  const parsed = parseArtifactName(request.artifact);
150
147
  if (!parsed || parsed.family !== request.family)
151
148
  return failed('ARTIFACT_IDENTITY_INVALID', `artifact '${request.artifact}' does not match ${request.family}`);
152
- let content;
149
+ if (authority && !validFinalizerAuthority(authority, request, resolved.taskId)) {
150
+ return failed('LOCAL_EXECUTION_AUTHORITY_INVALID', 'finalizer authority does not match the artifact tuple');
151
+ }
153
152
  let taskContent;
153
+ let content;
154
+ let recovery;
155
+ let recoveryState;
154
156
  try {
155
157
  taskContent = fs.readFileSync(resolved.taskMdPath, 'utf8');
156
- if (candidate !== undefined) {
157
- const error = validateArtifactPublication(resolved.taskDir, request.family, request.artifact);
158
- if (error)
159
- return failed(error.code, error.message);
160
- if (resolved.state !== 'active' || !hasOpenArtifactRound(taskContent, request.family, parsed.round)) {
161
- return failed('ARTIFACT_IDENTITY_INVALID', 'candidate must match one open started round in an active task');
162
- }
163
- content = candidate;
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');
164
169
  }
165
170
  else {
166
171
  const validated = validateCompletedArtifact(resolved.taskDir, request.family, request.artifact, parsed.round);
167
172
  if (!validated.ok)
168
173
  return failed(validated.error.code, validated.error.message);
169
- content = fs.readFileSync(validated.artifact.path, 'utf8');
174
+ content = candidate ?? fs.readFileSync(validated.artifact.path, 'utf8');
170
175
  }
171
176
  }
172
177
  catch (error) {
173
178
  return failed('ARTIFACT_NOT_READABLE', String(error));
174
179
  }
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');
182
+ }
175
183
  const validation = validateLocalArtifact(content, {
176
184
  family: request.family, requiredSections: request.requiredSections, taskContent, artifact: request.artifact
177
185
  });
178
186
  const artifactSha256 = sha256Content(content);
179
- const { semanticDigest, repairable, diagnostics } = validation;
180
187
  const result = failedFinalization(request, {
181
- code: 'LOCAL_ARTIFACT_INVALID', message: diagnostics.map((item) => `${item.code}: ${item.message}`).join('; ')
182
- }, { taskId: resolved.taskId, taskDir: resolved.taskDir, artifactSha256, semanticDigest, repairable, diagnostics });
183
- const preparedBase = { result, content, repoRoot: resolved.repoRoot };
184
- if (authority !== undefined && !validFinalizerAuthority(authority, request, resolved.taskId)) {
188
+ code: 'LOCAL_ARTIFACT_INVALID',
189
+ message: validation.diagnostics.map((item) => `${item.code}: ${item.message}`).join('; ')
190
+ }, {
191
+ taskId: resolved.taskId, taskDir: resolved.taskDir, artifactSha256,
192
+ semanticDigest: validation.semanticDigest, diagnostics: validation.diagnostics
193
+ });
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
+ }
185
201
  return {
186
- ...preparedBase,
187
- result: { ...result, repairable: false, error: {
188
- code: 'LOCAL_EXECUTION_AUTHORITY_INVALID',
189
- message: 'finalizer authority does not match the artifact tuple'
190
- }, diagnostics: [] }
202
+ result: { ...result, status: 'passed', changed: false, error: null, recovery: recoveryInfo(recovery) },
203
+ content, repoRoot: resolved.repoRoot, recovery, authority, lockAlreadyHeld: request.lockAlreadyHeld
191
204
  };
192
205
  }
193
- const reject = (code, message) => ({
194
- ...prepared, result: { ...result, repairable: false, error: { code, message },
195
- diagnostics: [{ code, message, repairable: false, line: null }] }
196
- });
197
- let intent;
198
- try {
199
- intent = readArtifactRepairIntent(resolved.repoRoot, resolved.taskId, request.family, request.artifact);
200
- }
201
- catch (error) {
202
- return { ...preparedBase, result: { ...result, repairable: false, diagnostics: [],
203
- error: { code: 'LOCAL_FINALIZATION_INTENT_INVALID', message: String(error) } } };
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
+ }
204
218
  }
205
- const prepared = { ...preparedBase, expectedIntent: intent };
206
- if (intent && intent.recoveryOperationId !== null
207
- && (!authority || intent.recoveryOperationId !== authority.operationId)) {
208
- return reject('LOCAL_REPAIR_PROVENANCE_CONFLICT', 'local repair provenance is owned by another recovery operation');
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
+ };
209
225
  }
210
- if (repairable) {
211
- if (intent && (intent.state !== 'awaiting-repair' || intent.baselineSemanticDigest !== semanticDigest)) {
212
- return reject('LOCAL_REPAIR_PROVENANCE_CONFLICT', 'a different local repair baseline is already recorded for this artifact');
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 };
213
229
  }
214
- if (intent && !authority)
215
- return prepared;
216
- }
217
- else {
218
- if (!validation.ok)
219
- return prepared;
220
- if (intent?.state === 'awaiting-repair' && intent.baselineSemanticDigest !== semanticDigest) {
221
- return reject('LOCAL_REPAIR_BASELINE_MISMATCH', 'the repaired artifact semantic digest does not match the recorded repair baseline');
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 };
222
237
  }
223
- if ((intent?.state === 'passed' || intent?.state === 'consumed')
224
- && (intent.artifactSha256 !== artifactSha256 || intent.semanticDigest !== semanticDigest)) {
225
- return reject('LOCAL_REPAIR_PROVENANCE_CONFLICT', 'the artifact changed after its finalization provenance was recorded');
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 };
226
264
  }
227
- prepared.result = { ...result, status: 'passed', error: null };
228
- if (intent?.state === 'consumed')
229
- return prepared;
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 };
230
279
  }
231
- const timestamp = Date.now();
232
- return { ...prepared, provenance: {
233
- version: 2, taskId: resolved.taskId, family: request.family, artifact: request.artifact,
234
- state: repairable ? 'awaiting-repair' : 'passed',
235
- baselineSemanticDigest: repairable ? semanticDigest : intent?.baselineSemanticDigest ?? null,
236
- artifactSha256, semanticDigest,
237
- recoveryOperationId: authority?.operationId ?? intent?.recoveryOperationId ?? null,
238
- phase: authority?.phase ?? intent?.phase ?? null,
239
- authorityDigest: authority ? authorityDigest(authority) : intent?.authorityDigest ?? null,
240
- requestId: authority?.lifecycleRequestId ?? intent?.requestId ?? `local-finalize:${resolved.taskId}`,
241
- createdAt: intent?.createdAt ?? timestamp,
242
- updatedAt: timestamp
243
- } };
244
280
  }
245
- function commitLocalArtifactProvenance(prepared) {
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;
246
286
  try {
247
- if (prepared.provenance) {
248
- writeArtifactRepairIntent(prepared.repoRoot, prepared.provenance, { expected: prepared.expectedIntent ?? null });
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
+ };
249
299
  }
250
- return prepared.result;
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
+ };
251
332
  }
252
333
  catch (error) {
334
+ if (error instanceof Error && error.message.startsWith('TASK_WORKFLOW_FAULT_INJECTED:'))
335
+ throw error;
253
336
  return { ...prepared.result, status: 'failed', error: {
254
- code: 'LOCAL_FINALIZATION_INTENT_WRITE_FAILED', message: String(error)
337
+ code: 'LOCAL_RECOVERY_COMMIT_FAILED', message: String(error)
255
338
  } };
256
339
  }
257
340
  }
258
341
  function finalizeLocalArtifact(request, authority, options = {}) {
259
- const result = commitLocalArtifactProvenance(prepareLocalArtifact(request, undefined, authority));
342
+ const prepared = prepareLocalArtifact(request, undefined, authority);
343
+ const result = commitLocalArtifactProvenance(prepared);
260
344
  if (authority && !options.deferLifecycleRecoveryConsumption && result.status === 'passed') {
261
345
  try {
262
346
  consumeLifecycleRecoveryAttestation(authority);
@@ -270,5 +354,10 @@ function finalizeLocalArtifact(request, authority, options = {}) {
270
354
  }
271
355
  return result;
272
356
  }
273
- export { LOCAL_ARTIFACT_REQUIRED_SECTIONS, consumeLocalArtifactFinalizationIntent, finalizeLocalArtifact, prepareLocalArtifact, commitLocalArtifactProvenance, readArtifactRepairIntent as readLocalArtifactFinalizationIntent, canonicalSemanticDigest as semanticDigest, sha256Content, validateLocalArtifact };
357
+ /** Validate and seal an immutable preflight generation without publishing the formal artifact. */
358
+ function preflightLocalArtifact(request, authority) {
359
+ const prepared = prepareLocalArtifact(request, undefined, authority, true);
360
+ return prepared.result;
361
+ }
362
+ export { LOCAL_ARTIFACT_REQUIRED_SECTIONS, consumeLocalArtifactFinalizationIntent, finalizeLocalArtifact, preflightLocalArtifact, prepareLocalArtifact, commitLocalArtifactProvenance, readArtifactRecoveryIntent as readLocalArtifactFinalizationIntent, canonicalSemanticDigest as semanticDigest, sha256Content, validateLocalArtifact };
274
363
  //# sourceMappingURL=local-artifact-finalization.js.map
@@ -28,7 +28,11 @@ function manualValidationReceiptDigest(receipt) {
28
28
  }
29
29
  function manualValidationFinalSummaryDigest(body) {
30
30
  const heading = /^###\s+✅\s+(?:Manual Validation Passed|人工验证已通过)\s*$/mu.exec(body);
31
- const projection = heading ? body.slice(heading.index).replace(/\s+$/u, '') : body;
31
+ const afterHeading = heading ? body.slice(heading.index + heading[0].length) : '';
32
+ const nextSection = heading ? /^#{1,3}\s/mu.exec(afterHeading) : null;
33
+ const projection = heading
34
+ ? body.slice(heading.index, heading.index + heading[0].length + (nextSection?.index ?? afterHeading.length)).replace(/\s+$/u, '')
35
+ : body;
32
36
  const preimage = projection.replace(/receipt=(?:[a-f0-9]{64}|<receipt>)/gu, 'receipt=<receipt>');
33
37
  return createHash('sha256').update(preimage, 'utf8').digest('hex');
34
38
  }
@@ -7,7 +7,7 @@ import { validateCurrentTaskContract } from './current-contract.js';
7
7
  import { parseReviewSummary } from './review-artifacts.js';
8
8
  import { LEDGER_SECTION_MISSING_CODE, LEDGER_SECTION_MISSING_MESSAGE, parseLedgerDocument, summarizeLedgerStage, validateLedgerRows } from './ledger.js';
9
9
  import { resolveTaskRef } from './resolve-ref.js';
10
- import { activateDelegation, abortPreparedDelegation, completeDelegationStage, consumeDelegation, dispatchDelegation, foldBlankToNull, isDelegationReceipt, managedDelegationRole, prepareDelegation, sealDelegation } from './delegation-receipts.js';
10
+ import { activateDelegation, abortActivatedDelegation, abortPreparedDelegation, completeDelegationStage, consumeDelegation, dispatchDelegation, foldBlankToNull, isDelegationReceipt, managedDelegationRole, prepareDelegation, sealDelegation } from './delegation-receipts.js';
11
11
  import { normalizeAgentClients } from '../agent-clients/config.js';
12
12
  import { resolveAgentRuntimeStoreRoot } from '../runtime/agent-runtime.js';
13
13
  import { getAgentClientCapability, getAgentClientDelegationEvidence, getAgentClientModelSelection } from '../agent-clients/registry.js';
@@ -21,6 +21,7 @@ import { resolveDeliveryTarget, resolveDiffBase, resolveTargetHead } from './del
21
21
  import { buildLifecycleFacts, canStart, recommendNext } from './capabilities.js';
22
22
  import { hasOpenLifecycleExecution } from './activity-log.js';
23
23
  import { invalidationBlocks } from './invalidation.js';
24
+ import { normalizeAgentToken } from '../agent-clients/tokens.js';
24
25
  function supportsLifecycleDelegation(client) {
25
26
  return getAgentClientCapability(client, 'subagents').level !== 'unsupported'
26
27
  && getAgentClientCapability(client, 'orchestration').level !== 'unsupported'
@@ -1090,6 +1091,55 @@ function recoverPreparedOrchestrationDelegation(taskRef, options = {}) {
1090
1091
  return failed('ORCHESTRATION_PREPARED_RECOVERY_FAILED', error instanceof Error ? error.message : String(error), resolved.taskId);
1091
1092
  }
1092
1093
  }
1094
+ function recoverActivatedOrchestrationDelegationUnderLock(taskRef, event, options = {}) {
1095
+ const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
1096
+ if (!resolved.ok)
1097
+ return failed(resolved.code, resolved.message, resolved.taskId);
1098
+ const run = readRun(resolved.taskDir, options);
1099
+ if (!run)
1100
+ return failed('ORCHESTRATION_RUN_MISSING', 'no orchestration run exists', resolved.taskId);
1101
+ const matches = [
1102
+ ...(run.pendingDelegation ? [run.pendingDelegation] : []),
1103
+ ...run.receipts.filter((receipt) => receipt.id === event.receiptId)
1104
+ ];
1105
+ const receipt = matches.find((candidate) => candidate.id === event.receiptId);
1106
+ if (!receipt)
1107
+ return failed('ORCHESTRATION_DELEGATION_MISSING', 'recovery receipt does not exist', resolved.taskId);
1108
+ if (receipt.taskId !== resolved.taskId
1109
+ || receipt.stage !== event.stage
1110
+ || receipt.round !== event.round
1111
+ || receipt.artifact !== event.artifact
1112
+ || receipt.childId !== event.childId
1113
+ || normalizeAgentToken(event.startedAgent) !== normalizeAgentToken(receipt.client)) {
1114
+ return failed('ORCHESTRATION_PROVENANCE_MISMATCH', 'recovery selector does not match the delegation receipt', resolved.taskId);
1115
+ }
1116
+ if (receipt.status === 'aborted' && run.pendingDelegation === null) {
1117
+ return { status: run.status, changed: false, taskId: resolved.taskId, run, next: null, error: null };
1118
+ }
1119
+ if (run.pendingDelegation?.id !== receipt.id || receipt.status !== 'activated') {
1120
+ return failed('ORCHESTRATION_RECOVERY_UNSAFE', 'recovery requires one matching activated pending delegation', resolved.taskId);
1121
+ }
1122
+ if (run.receipts.some((candidate) => candidate.id === receipt.id)) {
1123
+ return failed('ORCHESTRATION_RECEIPT_DUPLICATE', 'recovery receipt already exists in the completed receipt history', resolved.taskId);
1124
+ }
1125
+ const aborted = abortActivatedDelegation(receipt, {
1126
+ childId: event.childId,
1127
+ stopRevision: event.stopRevision,
1128
+ consumer: event.consumer,
1129
+ consumedAt: event.consumedAt
1130
+ });
1131
+ if (!aborted.ok)
1132
+ return failed(aborted.code, aborted.message, resolved.taskId);
1133
+ const updated = withUpdatedRun(run, {
1134
+ status: 'running',
1135
+ pause: null,
1136
+ nextStage: null,
1137
+ pendingDelegation: null,
1138
+ receipts: Object.freeze([...run.receipts, aborted.receipt])
1139
+ }, options.now);
1140
+ saveRun(resolved.taskDir, updated);
1141
+ return { status: 'running', changed: true, taskId: resolved.taskId, run: updated, next: null, error: null };
1142
+ }
1093
1143
  function completeOrchestrationStage(taskRef, event, options = {}) {
1094
1144
  const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
1095
1145
  if (!resolved.ok)
@@ -1204,5 +1254,5 @@ function pauseOrchestration(taskRef, code, message, recoverable, options = {}) {
1204
1254
  saveRun(resolved.taskDir, updated);
1205
1255
  return { status: 'paused', changed: true, taskId: resolved.taskId, run: updated, next: null, error: null };
1206
1256
  }
1207
- export { activateMatchingOrchestrationDelegation, activateOrchestrationDelegation, awaitOrchestrationDelegationActivation, advanceOrchestration, beginOrResumeOrchestration, commitOrchestrationStageCompletion, completeOrchestrationStage, dispatchOrchestrationDelegation, inspectOrchestrationStage, hasActivatableOrchestrationDelegation, hasSealableOrchestrationDelegation, OrchestrationStateError, orchestrationPath, pauseMatchingOrchestrationDelegation, pauseOrchestration, planOrchestrationStageCompletion, prepareOrchestrationDelegation, readRun, reconcileMatchingOrchestrationDelegation, recoverPreparedOrchestrationDelegation, routeOrchestration, sealMatchingOrchestrationDelegation, sealMatchingOrchestrationDelegationWithHostEvidence, sealOrchestrationDelegation, statusOrchestration, };
1257
+ export { activateMatchingOrchestrationDelegation, activateOrchestrationDelegation, awaitOrchestrationDelegationActivation, advanceOrchestration, beginOrResumeOrchestration, commitOrchestrationStageCompletion, completeOrchestrationStage, dispatchOrchestrationDelegation, inspectOrchestrationStage, hasActivatableOrchestrationDelegation, hasSealableOrchestrationDelegation, OrchestrationStateError, orchestrationPath, pauseMatchingOrchestrationDelegation, pauseOrchestration, planOrchestrationStageCompletion, prepareOrchestrationDelegation, readRun, reconcileMatchingOrchestrationDelegation, recoverPreparedOrchestrationDelegation, recoverActivatedOrchestrationDelegationUnderLock, routeOrchestration, sealMatchingOrchestrationDelegation, sealMatchingOrchestrationDelegationWithHostEvidence, sealOrchestrationDelegation, statusOrchestration, };
1208
1258
  //# sourceMappingURL=orchestration.js.map