@fitlab-ai/agent-infra 0.9.14 → 0.9.15

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 (250) hide show
  1. package/README.md +10 -0
  2. package/README.zh-CN.md +10 -0
  3. package/bin/internal-cli.sh +45 -0
  4. package/dist/bin/internal-cli.js +93 -30
  5. package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +165 -27
  6. package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +3 -2
  7. package/dist/lib/defaults.json +103 -103
  8. package/dist/lib/fs/durable-write.js +37 -0
  9. package/dist/lib/fs/file-lock.js +111 -0
  10. package/dist/lib/host-control/audit.js +19 -0
  11. package/dist/lib/host-control/client.js +142 -0
  12. package/dist/lib/host-control/command.js +49 -0
  13. package/dist/lib/host-control/path.js +160 -0
  14. package/dist/lib/host-control/secure-fs.js +140 -0
  15. package/dist/lib/host-control/server.js +159 -0
  16. package/dist/lib/host-control/service.js +112 -0
  17. package/dist/lib/init.js +11 -2
  18. package/dist/lib/internal/cli-route-inventory.js +3 -1
  19. package/dist/lib/internal/codex-lifecycle.js +6 -6
  20. package/dist/lib/internal/demo-promotion.js +322 -0
  21. package/dist/lib/internal/demo-transcript.js +454 -0
  22. package/dist/lib/internal/host-control.js +28 -0
  23. package/dist/lib/internal/manual-validation.js +322 -0
  24. package/dist/lib/internal/platform-pr.js +18 -4
  25. package/dist/lib/internal/release-workflow.js +63 -84
  26. package/dist/lib/internal/sandbox-control.js +149 -2
  27. package/dist/lib/internal/task-artifact.js +15 -262
  28. package/dist/lib/internal/task-create.js +6 -5
  29. package/dist/lib/internal/task-event.js +37 -37
  30. package/dist/lib/internal/task-operation-registry.js +101 -3
  31. package/dist/lib/internal/task-review.js +8 -53
  32. package/dist/lib/internal/task-validate.js +30 -10
  33. package/dist/lib/owned-path.js +33 -0
  34. package/dist/lib/platform/check-status.js +11 -0
  35. package/dist/lib/platform/comment-safety.js +4 -1
  36. package/dist/lib/platform/completion-backfill.js +1 -1
  37. package/dist/lib/platform/github-data.js +241 -0
  38. package/dist/lib/platform/github-provider.js +15 -20
  39. package/dist/lib/platform/issue-comments.js +2 -2
  40. package/dist/lib/platform/issues.js +1 -179
  41. package/dist/lib/platform/pr-checks.js +12 -65
  42. package/dist/lib/platform/pr-summary.js +55 -3
  43. package/dist/lib/platform/pull-requests.js +2 -105
  44. package/dist/lib/platform/snapshots.js +2 -0
  45. package/dist/lib/platform/verification-sync.js +127 -142
  46. package/dist/lib/release/workflow.js +1 -1
  47. package/dist/lib/sandbox/commands/create.js +7 -2
  48. package/dist/lib/sandbox/commands/rm.js +3 -1514
  49. package/dist/lib/sandbox/control/audit.js +203 -0
  50. package/dist/lib/sandbox/control/client.js +93 -54
  51. package/dist/lib/sandbox/control/executor.js +129 -65
  52. package/dist/lib/sandbox/control/finalization-response.js +27 -0
  53. package/dist/lib/sandbox/control/identity-sentinel.js +107 -0
  54. package/dist/lib/sandbox/control/lifecycle.js +36 -2
  55. package/dist/lib/sandbox/control/native-file-lock.js +6 -98
  56. package/dist/lib/sandbox/control/protocol.js +55 -4
  57. package/dist/lib/sandbox/control/server.js +491 -120
  58. package/dist/lib/sandbox/control/state.js +58 -0
  59. package/dist/lib/sandbox/control/task-workflow.js +74 -0
  60. package/dist/lib/sandbox/control/workflow-executor.js +99 -0
  61. package/dist/lib/sandbox/index.js +2 -2
  62. package/dist/lib/sandbox/recovery.js +28 -4
  63. package/dist/lib/sandbox/removal.js +1717 -0
  64. package/dist/lib/sandbox/task-cleanup.js +217 -0
  65. package/dist/lib/sandbox/workspace-view.js +13 -14
  66. package/dist/lib/task/activity-intent.js +2 -1
  67. package/dist/lib/task/activity-log.js +30 -24
  68. package/dist/lib/task/artifact-command.js +101 -0
  69. package/dist/lib/task/artifact-lifecycle.js +30 -28
  70. package/dist/lib/task/artifact-name.js +36 -0
  71. package/dist/lib/task/artifact-operations.js +23 -69
  72. package/dist/lib/task/artifact-receipts.js +3 -15
  73. package/dist/lib/task/artifact-repair-intent.js +87 -16
  74. package/dist/lib/task/artifact-schema.js +6 -14
  75. package/dist/lib/task/capabilities.js +12 -19
  76. package/dist/lib/task/codex-orchestration.js +71 -25
  77. package/dist/lib/task/commit-identity.js +28 -0
  78. package/dist/lib/task/commit-operation.js +2 -16
  79. package/dist/lib/task/control-authority.js +371 -5
  80. package/dist/lib/task/control-recovery.js +128 -0
  81. package/dist/lib/task/create.js +62 -9
  82. package/dist/lib/task/decision-details.js +2 -46
  83. package/dist/lib/task/events.js +120 -9
  84. package/dist/lib/task/finalization-state.js +9 -0
  85. package/dist/lib/task/human-override.js +3 -6
  86. package/dist/lib/task/implementation-inputs.js +3 -3
  87. package/dist/lib/task/intermediate-cleanup.js +383 -0
  88. package/dist/lib/task/invalidation.js +18 -65
  89. package/dist/lib/task/ledger-intents.js +7 -22
  90. package/dist/lib/task/ledger.js +1 -3
  91. package/dist/lib/task/lifecycle.js +19 -1
  92. package/dist/lib/task/local-artifact-finalization.js +132 -157
  93. package/dist/lib/task/manual-validation-completion.js +26 -0
  94. package/dist/lib/task/manual-validation-evidence.js +130 -0
  95. package/dist/lib/task/manual-validation-receipt.js +101 -0
  96. package/dist/lib/task/manual-validation-shared.js +40 -0
  97. package/dist/lib/task/manual-validation-transaction.js +243 -0
  98. package/dist/lib/task/markdown.js +47 -0
  99. package/dist/lib/task/orchestration.js +14 -17
  100. package/dist/lib/task/qualification-audit.js +20 -34
  101. package/dist/lib/task/review-artifacts.js +2 -18
  102. package/dist/lib/task/review-command.js +25 -0
  103. package/dist/lib/task/review-finalization.js +125 -193
  104. package/dist/lib/task/review-fingerprint.js +2 -2
  105. package/dist/lib/task/sections.js +34 -56
  106. package/dist/lib/task/verification-engine.js +116 -323
  107. package/dist/lib/task/verification.js +1 -1
  108. package/dist/lib/task/workflow-command.js +17 -0
  109. package/dist/lib/task/workflow-warning-intents.js +0 -16
  110. package/dist/lib/task/workflow-warnings.js +28 -2
  111. package/dist/lib/update.js +2 -0
  112. package/dist/lifecycle-build-manifest.json +3 -3
  113. package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +223 -33
  114. package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +3 -2
  115. package/lib/fs/durable-write.ts +29 -0
  116. package/lib/fs/file-lock.ts +113 -0
  117. package/lib/host-control/audit.ts +22 -0
  118. package/lib/host-control/client.ts +172 -0
  119. package/lib/host-control/command.ts +60 -0
  120. package/lib/host-control/path.ts +167 -0
  121. package/lib/host-control/secure-fs.ts +135 -0
  122. package/lib/host-control/server.ts +180 -0
  123. package/lib/host-control/service.ts +133 -0
  124. package/lib/init.ts +15 -2
  125. package/lib/internal/cli-route-inventory.ts +3 -1
  126. package/lib/internal/codex-lifecycle.ts +6 -6
  127. package/lib/internal/demo-promotion.ts +354 -0
  128. package/lib/internal/demo-transcript.ts +419 -0
  129. package/lib/internal/host-control.ts +28 -0
  130. package/lib/internal/manual-validation.ts +306 -0
  131. package/lib/internal/platform-pr.ts +15 -4
  132. package/lib/internal/release-workflow.ts +74 -82
  133. package/lib/internal/sandbox-control.ts +162 -1
  134. package/lib/internal/task-artifact.ts +13 -216
  135. package/lib/internal/task-create.ts +5 -5
  136. package/lib/internal/task-event.ts +20 -11
  137. package/lib/internal/task-operation-registry.ts +111 -3
  138. package/lib/internal/task-review.ts +6 -51
  139. package/lib/internal/task-validate.ts +26 -10
  140. package/lib/owned-path.ts +26 -0
  141. package/lib/platform/check-status.ts +9 -0
  142. package/lib/platform/comment-safety.ts +5 -0
  143. package/lib/platform/completion-backfill.ts +1 -1
  144. package/lib/platform/github-data.ts +322 -0
  145. package/lib/platform/github-provider.ts +15 -20
  146. package/lib/platform/issue-comments.ts +3 -4
  147. package/lib/platform/issues.ts +4 -245
  148. package/lib/platform/pr-checks.ts +13 -77
  149. package/lib/platform/pr-summary.ts +61 -5
  150. package/lib/platform/pull-requests.ts +3 -170
  151. package/lib/platform/snapshots.ts +53 -0
  152. package/lib/platform/verification-sync.ts +145 -155
  153. package/lib/release/workflow.ts +3 -3
  154. package/lib/sandbox/commands/create.ts +7 -6
  155. package/lib/sandbox/commands/rm.ts +3 -1909
  156. package/lib/sandbox/control/audit.ts +253 -0
  157. package/lib/sandbox/control/client.ts +109 -66
  158. package/lib/sandbox/control/executor.ts +134 -74
  159. package/lib/sandbox/control/finalization-response.ts +29 -0
  160. package/lib/sandbox/control/identity-sentinel.ts +137 -0
  161. package/lib/sandbox/control/lifecycle.ts +45 -3
  162. package/lib/sandbox/control/native-file-lock.ts +6 -107
  163. package/lib/sandbox/control/protocol.ts +75 -6
  164. package/lib/sandbox/control/server.ts +538 -123
  165. package/lib/sandbox/control/state.ts +78 -0
  166. package/lib/sandbox/control/task-workflow.ts +95 -0
  167. package/lib/sandbox/control/workflow-executor.ts +102 -0
  168. package/lib/sandbox/index.ts +2 -2
  169. package/lib/sandbox/recovery.ts +28 -5
  170. package/lib/sandbox/removal.ts +2144 -0
  171. package/lib/sandbox/task-cleanup.ts +209 -0
  172. package/lib/sandbox/workspace-view.ts +17 -15
  173. package/lib/task/activity-intent.ts +2 -2
  174. package/lib/task/activity-log.ts +27 -19
  175. package/lib/task/artifact-command.ts +104 -0
  176. package/lib/task/artifact-lifecycle.ts +31 -26
  177. package/lib/task/artifact-name.ts +55 -0
  178. package/lib/task/artifact-operations.ts +22 -68
  179. package/lib/task/artifact-receipts.ts +3 -14
  180. package/lib/task/artifact-repair-intent.ts +99 -18
  181. package/lib/task/artifact-schema.ts +5 -34
  182. package/lib/task/capabilities.ts +13 -22
  183. package/lib/task/codex-orchestration.ts +85 -24
  184. package/lib/task/commit-identity.ts +31 -0
  185. package/lib/task/commit-operation.ts +2 -16
  186. package/lib/task/control-authority.ts +502 -5
  187. package/lib/task/control-recovery.ts +174 -0
  188. package/lib/task/create.ts +63 -8
  189. package/lib/task/decision-details.ts +3 -71
  190. package/lib/task/events.ts +131 -10
  191. package/lib/task/finalization-state.ts +11 -0
  192. package/lib/task/human-override.ts +3 -8
  193. package/lib/task/implementation-inputs.ts +3 -3
  194. package/lib/task/intermediate-cleanup.ts +455 -0
  195. package/lib/task/invalidation.ts +18 -62
  196. package/lib/task/ledger-intents.ts +7 -24
  197. package/lib/task/ledger.ts +1 -3
  198. package/lib/task/lifecycle.ts +17 -1
  199. package/lib/task/local-artifact-finalization.ts +148 -182
  200. package/lib/task/manual-validation-completion.ts +40 -0
  201. package/lib/task/manual-validation-evidence.ts +209 -0
  202. package/lib/task/manual-validation-receipt.ts +152 -0
  203. package/lib/task/manual-validation-shared.ts +50 -0
  204. package/lib/task/manual-validation-transaction.ts +304 -0
  205. package/lib/task/markdown.ts +70 -0
  206. package/lib/task/orchestration.ts +19 -19
  207. package/lib/task/qualification-audit.ts +22 -33
  208. package/lib/task/review-artifacts.ts +1 -20
  209. package/lib/task/review-command.ts +23 -0
  210. package/lib/task/review-finalization.ts +131 -259
  211. package/lib/task/review-fingerprint.ts +2 -2
  212. package/lib/task/sections.ts +33 -54
  213. package/lib/task/verification-engine.ts +110 -366
  214. package/lib/task/verification-types.ts +17 -0
  215. package/lib/task/verification.ts +1 -1
  216. package/lib/task/workflow-command.ts +18 -0
  217. package/lib/task/workflow-warning-intents.ts +0 -13
  218. package/lib/task/workflow-warnings.ts +21 -2
  219. package/lib/update.ts +6 -0
  220. package/package.json +6 -6
  221. package/templates/.agents/hooks/lifecycle-delegation.js +6 -6
  222. package/templates/.agents/rules/testing-discipline.en.md +2 -0
  223. package/templates/.agents/rules/testing-discipline.zh-CN.md +2 -0
  224. package/templates/.agents/scripts/package.json +3 -0
  225. package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +32 -20
  226. package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +32 -20
  227. package/templates/.agents/skills/complete-manual-validation/config/verify.en.json +1 -1
  228. package/templates/.agents/skills/complete-manual-validation/config/verify.zh-CN.json +1 -1
  229. package/templates/.agents/skills/complete-manual-validation/reference/report-template.en.md +2 -0
  230. package/templates/.agents/skills/complete-manual-validation/reference/report-template.zh-CN.md +2 -0
  231. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.en.md +16 -7
  232. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +16 -7
  233. package/templates/.agents/skills/complete-task/SKILL.en.md +1 -1
  234. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +1 -1
  235. package/templates/.agents/skills/release/SKILL.en.md +7 -3
  236. package/templates/.agents/skills/release/SKILL.zh-CN.md +7 -3
  237. package/templates/.agents/skills/run-manual-validation/SKILL.en.md +20 -3
  238. package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +20 -3
  239. package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.en.md +4 -1
  240. package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.zh-CN.md +4 -1
  241. package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +2 -0
  242. package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +2 -0
  243. package/templates/.agents/skills/run-task/SKILL.en.md +1 -1
  244. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +1 -1
  245. package/templates/.agents/skills/test/SKILL.en.md +17 -6
  246. package/templates/.agents/skills/test/SKILL.zh-CN.md +17 -6
  247. package/templates/.claude/commands/run-manual-validation.en.md +1 -1
  248. package/templates/.claude/commands/run-manual-validation.zh-CN.md +1 -1
  249. package/dist/lib/platform/adapters.js +0 -56
  250. package/lib/platform/adapters.ts +0 -197
@@ -1,43 +1,17 @@
1
1
  import fs from 'node:fs';
2
+ import { parseArtifactName } from './artifact-name.js';
2
3
  import path from 'node:path';
3
4
  import { createHash } from 'node:crypto';
4
- import { scanVisibleMarkdown } from './decision-details.js';
5
- import { locateActivityLog, pairEntries, startedBackedRows } from './activity-log.js';
5
+ import { readStableFileSync } from '../host-control/secure-fs.js';
6
+ import { scanVisibleMarkdown } from './markdown.js';
7
+ import { hasOpenArtifactRound } from './artifact-lifecycle.js';
6
8
  import { readArtifactRepairIntent } from './artifact-repair-intent.js';
9
+ import { resolveTaskRef } from './resolve-ref.js';
7
10
  import { getArtifactSchema, renderArtifactSkeleton } from './artifact-schema.js';
8
11
  import { withTaskExecutionLock } from './task-execution-lock.js';
9
12
  function sha256Content(content) {
10
13
  return createHash('sha256').update(content, 'utf8').digest('hex');
11
14
  }
12
- function sourceLines(content) {
13
- const lines = [];
14
- let start = 0;
15
- while (start < content.length) {
16
- const newline = content.indexOf('\n', start);
17
- const end = newline === -1 ? content.length : newline;
18
- lines.push({ start, end, text: content.slice(start, end).replace(/\r$/, '') });
19
- start = newline === -1 ? content.length : newline + 1;
20
- }
21
- if (content.length === 0)
22
- lines.push({ start: 0, end: 0, text: '' });
23
- return lines;
24
- }
25
- function hasUnclosedFence(content) {
26
- let fence = null;
27
- for (const line of sourceLines(content)) {
28
- if (fence) {
29
- const close = line.text.match(/^ {0,3}(`{3,}|~{3,})\s*$/);
30
- if (close && close[1][0] === fence.character && close[1].length >= fence.length)
31
- fence = null;
32
- continue;
33
- }
34
- const open = line.text.match(/^ {0,3}(`{3,}|~{3,})(.*)$/);
35
- if (open && !(open[1][0] === '`' && open[2].includes('`'))) {
36
- fence = { character: open[1][0], length: open[1].length };
37
- }
38
- }
39
- return fence !== null;
40
- }
41
15
  function lineNumber(content, offset) {
42
16
  return content.slice(0, offset).split('\n').length;
43
17
  }
@@ -47,9 +21,9 @@ function markerPattern(marker) {
47
21
  function escapeRegExp(value) {
48
22
  return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
49
23
  }
50
- function markerEntries(content) {
24
+ function markerEntries(content, scanned) {
51
25
  const entries = [];
52
- for (const line of scanVisibleMarkdown(content).lines) {
26
+ for (const line of scanned.lines) {
53
27
  const match = line.text.match(/<!--\s*(artifact-section:[^\s>]+)\s*-->/g);
54
28
  if (!match)
55
29
  continue;
@@ -67,10 +41,9 @@ function markerEntries(content) {
67
41
  }
68
42
  return entries;
69
43
  }
70
- function sectionBodyBounds(content, heading) {
71
- const next = scanVisibleMarkdown(content).headings.find((candidate) => (candidate.start > heading.start && candidate.level <= 2));
72
- const headingEnd = scanVisibleMarkdown(content).headings.find((candidate) => candidate.start === heading.start)?.end ?? heading.start;
73
- return { start: headingEnd, end: next?.start ?? content.length };
44
+ function sectionBodyBounds(content, heading, scanned) {
45
+ const next = scanned.headings.find((candidate) => candidate.start > heading.start && candidate.level <= 2);
46
+ return { start: heading.end, end: next?.start ?? content.length };
74
47
  }
75
48
  function stripHtmlComments(content) {
76
49
  let result = '';
@@ -153,7 +126,7 @@ function inspectArtifactStructure(content, schema) {
153
126
  const repairCandidates = [];
154
127
  if (!content.trim())
155
128
  diagnostics.push(diagnostic('ARTIFACT_EMPTY', 'artifact is empty', null, null));
156
- if (hasUnclosedFence(content))
129
+ if (scanned.hasUnclosedFence)
157
130
  diagnostics.push(diagnostic('ARTIFACT_UNCLOSED_FENCE', 'artifact contains an unclosed Markdown fence', null, null));
158
131
  const headingsBySection = new Map();
159
132
  for (const section of schema.sections) {
@@ -172,7 +145,7 @@ function inspectArtifactStructure(content, schema) {
172
145
  diagnostics.push(diagnostic('ARTIFACT_MISSING_SECTION', `required section '${aliases[0]}' is missing`, section.id, null));
173
146
  }
174
147
  }
175
- const markers = markerEntries(content);
148
+ const markers = markerEntries(content, scanned);
176
149
  const englishHeadings = scanned.headings.filter((heading) => heading.level === 2 && schema.sections.some((section) => section.headings.en === heading.text)).length;
177
150
  const chineseHeadings = scanned.headings.filter((heading) => heading.level === 2 && schema.sections.some((section) => section.headings.zh === heading.text)).length;
178
151
  const locale = englishHeadings > chineseHeadings ? 'en' : 'zh-CN';
@@ -190,7 +163,7 @@ function inspectArtifactStructure(content, schema) {
190
163
  diagnostics.push(diagnostic('ARTIFACT_MARKER_MISMATCH', `section marker '${section.marker}' must occupy its own visible line`, section.id, marker.line));
191
164
  }
192
165
  if (marker && heading) {
193
- const bounds = sectionBodyBounds(content, heading);
166
+ const bounds = sectionBodyBounds(content, heading, scanned);
194
167
  if (marker.start < bounds.start || marker.start >= bounds.end) {
195
168
  diagnostics.push(diagnostic('ARTIFACT_MARKER_MISMATCH', `section marker '${section.marker}' is outside its section`, section.id, marker.line));
196
169
  }
@@ -302,30 +275,16 @@ function validateRepairContext(request, content) {
302
275
  return { ok: false, code: 'ARTIFACT_REPAIR_CONTEXT_INVALID', message: 'artifact context marker does not match the requested task, family, and round' };
303
276
  }
304
277
  let taskContent;
278
+ const resolved = resolveTaskRef(request.taskId, { repoRoot: request.repoRoot });
279
+ if (!resolved.ok)
280
+ return { ok: false, code: 'ARTIFACT_REPAIR_CONTEXT_INVALID', message: resolved.message };
305
281
  try {
306
- taskContent = fs.readFileSync(path.join(request.taskDir, 'task.md'), 'utf8');
282
+ taskContent = fs.readFileSync(resolved.taskMdPath, 'utf8');
307
283
  }
308
284
  catch (error) {
309
285
  return { ok: false, code: 'ARTIFACT_REPAIR_CONTEXT_INVALID', message: `cannot read task lifecycle context: ${String(error)}` };
310
286
  }
311
- const activity = locateActivityLog(taskContent);
312
- if (!activity)
313
- return { ok: false, code: 'ARTIFACT_REPAIR_CONTEXT_INVALID', message: 'task has no unique Activity Log section' };
314
- const label = {
315
- analysis: 'Analyze Task',
316
- 'review-analysis': 'Review Analysis',
317
- plan: 'Plan Task',
318
- 'review-plan': 'Review Plan',
319
- code: 'Code Task',
320
- 'review-code': 'Review Code'
321
- };
322
- const escaped = escapeRegExp(label[request.family]);
323
- const qualifier = request.family === 'code'
324
- ? '(?:, (?:fix for review-code(?:-r(?:[2-9]|[1-9]\\d+))?\\.md|decision II-[1-9]\\d+))?'
325
- : '';
326
- const expected = new RegExp(`^${escaped} \\(Round ${round}${qualifier}\\)$`);
327
- const open = startedBackedRows(pairEntries(activity.entries)).filter((row) => expected.test(row.step) && !row.done);
328
- if (open.length !== 1) {
287
+ if (!hasOpenArtifactRound(taskContent, request.family, round)) {
329
288
  return { ok: false, code: 'ARTIFACT_REPAIR_CONTEXT_INVALID', message: `artifact '${request.artifact}' does not have exactly one matching open started lifecycle event` };
330
289
  }
331
290
  let intent;
@@ -344,13 +303,8 @@ function validateRepairContext(request, content) {
344
303
  return { ok: true };
345
304
  }
346
305
  function artifactRound(family, artifact) {
347
- if (artifact === `${family}.md`)
348
- return 1;
349
- const match = artifact.match(new RegExp(`^${escapeRegExp(family)}-r([2-9]|[1-9]\\d+)\\.md$`));
350
- if (!match)
351
- return null;
352
- const round = Number(match[1]);
353
- return Number.isSafeInteger(round) ? round : null;
306
+ const identity = parseArtifactName(artifact);
307
+ return identity?.family === family ? identity.round : null;
354
308
  }
355
309
  function validateTarget(taskDir, family, artifact) {
356
310
  if (path.basename(artifact) !== artifact || artifactRound(family, artifact) === null || !artifact.endsWith('.md'))
@@ -386,7 +340,7 @@ function applyRepairUnlocked(request) {
386
340
  return target;
387
341
  let content;
388
342
  try {
389
- content = fs.readFileSync(target.path, 'utf8');
343
+ content = readStableFileSync(target.path, { maxBytes: 1024 * 1024 }).bytes.toString('utf8');
390
344
  }
391
345
  catch (error) {
392
346
  return resultFailure('ARTIFACT_REPAIR_TARGET_INVALID', String(error));
@@ -422,7 +376,7 @@ function applyRepairUnlocked(request) {
422
376
  mode = fs.statSync(target.path).mode & 0o777;
423
377
  fs.writeFileSync(tempPath, transformed, { encoding: 'utf8', mode, flag: 'wx' });
424
378
  const currentStat = fs.lstatSync(target.path);
425
- if (currentStat.isSymbolicLink() || !currentStat.isFile() || sha256Content(fs.readFileSync(target.path, 'utf8')) !== actualSha256) {
379
+ if (currentStat.isSymbolicLink() || !currentStat.isFile() || readStableFileSync(target.path, { maxBytes: 1024 * 1024 }).sha256 !== actualSha256) {
426
380
  fs.unlinkSync(tempPath);
427
381
  return resultFailure('ARTIFACT_REPAIR_CONFLICT', 'artifact changed or was replaced during repair');
428
382
  }
@@ -469,7 +423,7 @@ function initializeArtifactSkeleton(request) {
469
423
  const existing = fs.lstatSync(target);
470
424
  if (existing.isSymbolicLink() || !existing.isFile())
471
425
  return resultFailure('ARTIFACT_INIT_TARGET_INVALID', 'artifact target is not a regular file');
472
- return resultNoOp(fs.readFileSync(target, 'utf8'));
426
+ return resultNoOp(readStableFileSync(target, { maxBytes: 1024 * 1024 }).bytes.toString('utf8'));
473
427
  }
474
428
  catch (error) {
475
429
  if (error.code !== 'ENOENT')
@@ -1,5 +1,6 @@
1
1
  import fs from 'node:fs';
2
2
  import { createHash } from 'node:crypto';
3
+ import { parseArtifactName } from './artifact-name.js';
3
4
  import { extractSection, findSectionHeading, parseTable } from './sections.js';
4
5
  const RECEIPT_SECTION_ALIASES = ['产物生命周期收据', 'Artifact Lifecycle Receipts'];
5
6
  const RECEIPT_COLUMNS = ['event', 'output', 'input', 'input_sha256', 'completed_at'];
@@ -10,7 +11,6 @@ const RECEIPT_EVENTS = new Set([
10
11
  'code.completed'
11
12
  ]);
12
13
  const SHA256_RE = /^[a-f0-9]{64}$/;
13
- const ARTIFACT_NAME_RE = /^(analysis|review-analysis|plan|review-plan|code|review-code|manual-validation|validation-run|pr-review)(?:-r([2-9]|[1-9]\d+))?\.md$/;
14
14
  const RECEIPT_SHAPES = {
15
15
  'review-analysis.completed': { output: 'review-analysis', input: 'analysis' },
16
16
  'review-plan.completed': { output: 'review-plan', input: 'plan' },
@@ -30,21 +30,9 @@ function sha256Bytes(value) {
30
30
  function sha256File(filePath) {
31
31
  return sha256Bytes(fs.readFileSync(filePath));
32
32
  }
33
- function parseReceiptArtifactName(name) {
34
- const match = ARTIFACT_NAME_RE.exec(name);
35
- if (!match)
36
- return null;
37
- const roundText = match[2];
38
- if (roundText !== undefined) {
39
- const round = Number(roundText);
40
- if (!Number.isSafeInteger(round) || String(round) !== roundText)
41
- return null;
42
- }
43
- return { family: match[1] };
44
- }
45
33
  function validateReceiptShape(event, output, input) {
46
- const outputIdentity = parseReceiptArtifactName(output);
47
- const inputIdentity = parseReceiptArtifactName(input);
34
+ const outputIdentity = parseArtifactName(output);
35
+ const inputIdentity = parseArtifactName(input);
48
36
  if (!outputIdentity || !inputIdentity)
49
37
  throw new ArtifactReceiptError(`receipt artifact identity is invalid: ${output} -> ${input}`);
50
38
  const shape = RECEIPT_SHAPES[event];
@@ -1,23 +1,44 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
+ import { isDeepStrictEqual } from 'node:util';
3
4
  function intentRoot(repoRoot) {
4
5
  return path.join(repoRoot, '.agents', 'workspace', '.local-artifact-finalization-intents');
5
6
  }
6
7
  function intentPath(repoRoot, taskId, family, artifact) {
7
8
  return path.join(intentRoot(repoRoot), `${taskId}-${family}-${artifact}.json`);
8
9
  }
9
- function isArtifactRepairIntent(value) {
10
+ function validDigest(value) {
11
+ return typeof value === 'string' && /^[a-f0-9]{64}$/u.test(value);
12
+ }
13
+ function validTaskId(value) {
14
+ return typeof value === 'string' && /^TASK-\d{8}-\d{6}$/u.test(value);
15
+ }
16
+ function exactKeys(value, keys) {
17
+ return Object.keys(value).sort().join(',') === [...keys].sort().join(',');
18
+ }
19
+ export function isArtifactRepairIntent(value) {
10
20
  if (!value || typeof value !== 'object' || Array.isArray(value))
11
21
  return false;
12
22
  const intent = value;
13
- return intent.version === 1
14
- && typeof intent.taskId === 'string' && /^TASK-\d{8}-\d{6}$/.test(intent.taskId)
15
- && typeof intent.family === 'string' && ['analysis', 'review-analysis', 'plan', 'review-plan', 'code', 'review-code'].includes(intent.family)
23
+ return exactKeys(intent, [
24
+ 'artifact', 'artifactSha256', 'authorityDigest', 'baselineSemanticDigest', 'createdAt',
25
+ 'family', 'phase', 'recoveryOperationId', 'requestId', 'semanticDigest', 'state',
26
+ 'taskId', 'updatedAt', 'version'
27
+ ])
28
+ && intent.version === 2
29
+ && validTaskId(intent.taskId)
30
+ && typeof intent.family === 'string'
31
+ && ['analysis', 'review-analysis', 'plan', 'review-plan', 'code', 'review-code'].includes(intent.family)
16
32
  && typeof intent.artifact === 'string' && intent.artifact.length > 0
17
- && ['awaiting-repair', 'passed', 'consumed'].includes(String(intent.state))
18
- && (intent.baselineSemanticDigest === null || (typeof intent.baselineSemanticDigest === 'string' && /^[a-f0-9]{64}$/.test(intent.baselineSemanticDigest)))
19
- && typeof intent.artifactSha256 === 'string' && /^[a-f0-9]{64}$/.test(intent.artifactSha256)
20
- && typeof intent.semanticDigest === 'string' && /^[a-f0-9]{64}$/.test(intent.semanticDigest)
33
+ && ['awaiting-repair', 'finalize-ready', 'commit-started', 'passed', 'consumed'].includes(String(intent.state))
34
+ && (intent.baselineSemanticDigest === null || validDigest(intent.baselineSemanticDigest))
35
+ && validDigest(intent.artifactSha256)
36
+ && validDigest(intent.semanticDigest)
37
+ && (intent.recoveryOperationId === null || (typeof intent.recoveryOperationId === 'string' && /^[a-f0-9-]{16,64}$/u.test(intent.recoveryOperationId)))
38
+ && (intent.phase === null || ['orchestration.prepare', 'artifact.finalize-local', 'task-event.completed'].includes(String(intent.phase)))
39
+ && (intent.authorityDigest === null || validDigest(intent.authorityDigest))
40
+ && typeof intent.requestId === 'string' && intent.requestId.length > 0 && !/[\r\n]/u.test(intent.requestId)
41
+ && Number.isSafeInteger(intent.createdAt) && Number.isSafeInteger(intent.updatedAt)
21
42
  && (intent.state === 'awaiting-repair' ? intent.baselineSemanticDigest === intent.semanticDigest : true);
22
43
  }
23
44
  function readArtifactRepairIntent(repoRoot, taskId, family, artifact) {
@@ -36,24 +57,74 @@ function readArtifactRepairIntent(repoRoot, taskId, family, artifact) {
36
57
  }
37
58
  return value;
38
59
  }
39
- function writeArtifactRepairIntent(repoRoot, value) {
60
+ export function writeArtifactRepairIntent(repoRoot, value, options = {}) {
40
61
  if (!isArtifactRepairIntent(value))
41
62
  throw new Error('ARTIFACT_REPAIR_INTENT_INVALID: repair provenance schema is invalid');
42
63
  const directory = intentRoot(repoRoot);
43
64
  fs.mkdirSync(directory, { recursive: true, mode: 0o700 });
44
65
  const target = intentPath(repoRoot, value.taskId, value.family, value.artifact);
45
- const temporary = `${target}.${process.pid}.${Date.now()}.tmp`;
46
- fs.writeFileSync(temporary, `${JSON.stringify(value)}\n`, { encoding: 'utf8', mode: 0o600, flag: 'wx' });
66
+ const lock = `${target}.lock`;
67
+ let descriptor;
47
68
  try {
48
- fs.renameSync(temporary, target);
69
+ descriptor = fs.openSync(lock, 'wx', 0o600);
49
70
  }
50
71
  catch (error) {
51
- try {
52
- fs.unlinkSync(temporary);
72
+ if (error.code === 'EEXIST') {
73
+ throw new Error('ARTIFACT_REPAIR_INTENT_CONFLICT: another provenance writer owns the intent');
53
74
  }
54
- catch { /* preserve primary error */ }
55
75
  throw error;
56
76
  }
77
+ try {
78
+ const current = fs.existsSync(target)
79
+ ? readArtifactRepairIntent(repoRoot, value.taskId, value.family, value.artifact)
80
+ : null;
81
+ if (!isDeepStrictEqual(current, options.expected ?? null)) {
82
+ throw new Error('ARTIFACT_REPAIR_INTENT_CONFLICT: repair provenance changed during finalization');
83
+ }
84
+ const temporary = `${target}.${process.pid}.${Date.now()}.tmp`;
85
+ fs.writeFileSync(temporary, `${JSON.stringify(value)}\n`, { encoding: 'utf8', mode: 0o600, flag: 'wx' });
86
+ try {
87
+ fs.renameSync(temporary, target);
88
+ }
89
+ catch (error) {
90
+ try {
91
+ fs.unlinkSync(temporary);
92
+ }
93
+ catch { /* preserve primary error */ }
94
+ throw error;
95
+ }
96
+ }
97
+ finally {
98
+ fs.closeSync(descriptor);
99
+ try {
100
+ fs.unlinkSync(lock);
101
+ }
102
+ catch { /* preserve the primary result */ }
103
+ }
104
+ }
105
+ export function findArtifactRepairIntentsByOperation(repoRoot, operationId) {
106
+ if (!operationId || /[\r\n]/u.test(operationId)) {
107
+ throw new Error('ARTIFACT_REPAIR_OPERATION_INVALID: recovery operation id is invalid');
108
+ }
109
+ const directory = intentRoot(repoRoot);
110
+ if (!fs.existsSync(directory))
111
+ return Object.freeze([]);
112
+ return Object.freeze(fs.readdirSync(directory)
113
+ .filter((name) => name.endsWith('.json'))
114
+ .map((name) => {
115
+ const target = path.join(directory, name);
116
+ let value;
117
+ try {
118
+ value = JSON.parse(fs.readFileSync(target, 'utf8'));
119
+ }
120
+ catch (error) {
121
+ throw new Error(`ARTIFACT_REPAIR_INTENT_INVALID: ${String(error)}`);
122
+ }
123
+ if (!isArtifactRepairIntent(value))
124
+ throw new Error('ARTIFACT_REPAIR_INTENT_INVALID: repair provenance schema is invalid');
125
+ return value;
126
+ })
127
+ .filter((value) => value.recoveryOperationId === operationId));
57
128
  }
58
- export { readArtifactRepairIntent, writeArtifactRepairIntent };
129
+ export { readArtifactRepairIntent };
59
130
  //# sourceMappingURL=artifact-repair-intent.js.map
@@ -1,14 +1,4 @@
1
- const ARTIFACT_FAMILY_CATALOG = [
2
- { family: 'analysis', sectionAliases: ['分析', 'Analysis'], heading: '分析', labels: ['需求分析报告', 'Requirements Analysis'] },
3
- { family: 'review-analysis', sectionAliases: ['审查反馈', 'Review Feedback'], heading: '审查反馈', labels: ['需求分析审查', 'Analysis Review'] },
4
- { family: 'plan', sectionAliases: ['设计', 'Design'], heading: '设计', labels: ['技术方案', 'Technical Plan'] },
5
- { family: 'review-plan', sectionAliases: ['审查反馈', 'Review Feedback'], heading: '审查反馈', labels: ['技术方案审查', 'Plan Review'] },
6
- { family: 'code', sectionAliases: ['实现备注', 'Implementation Notes'], heading: '实现备注', labels: ['实现报告', 'Implementation Report'] },
7
- { family: 'review-code', sectionAliases: ['审查反馈', 'Review Feedback'], heading: '审查反馈', labels: ['代码审查', 'Code Review'] },
8
- { family: 'manual-validation', sectionAliases: ['实现备注', 'Implementation Notes'], heading: '实现备注', labels: ['人工验证', 'Manual Validation'] },
9
- { family: 'validation-run', sectionAliases: ['实现备注', 'Implementation Notes'], heading: '实现备注', labels: ['验证运行证据', 'Validation Run Evidence'] },
10
- { family: 'pr-review', sectionAliases: ['审查反馈', 'Review Feedback'], heading: '审查反馈', labels: ['PR 审查报告', 'PR Review Report'] }
11
- ];
1
+ import { parseArtifactName } from './artifact-name.js';
12
2
  const LOCAL_SECTIONS = {
13
3
  analysis: [
14
4
  ['source', '需求来源', 'Requirements Source'],
@@ -114,8 +104,10 @@ function renderArtifactSkeleton(input) {
114
104
  if (!schema)
115
105
  throw new Error(`unknown artifact schema family '${input.family}'`);
116
106
  const locale = input.locale ?? 'zh-CN';
117
- const roundMatch = input.artifact.match(/-r(\d+)\.md$/);
118
- const round = roundMatch ? Number(roundMatch[1]) : 1;
107
+ const identity = parseArtifactName(input.artifact);
108
+ if (identity?.family !== input.family)
109
+ throw new Error(`invalid artifact identity '${input.artifact}' for '${input.family}'`);
110
+ const round = identity.round;
119
111
  const title = schema.title[locale === 'en' ? 'en' : 'zh'];
120
112
  const headings = locale === 'en' ? 'en' : 'zh';
121
113
  const lines = [
@@ -131,5 +123,5 @@ function renderArtifactSkeleton(input) {
131
123
  }
132
124
  return `${lines.join('\n').trimEnd()}\n`;
133
125
  }
134
- export { ARTIFACT_FAMILY_CATALOG, ARTIFACT_SCHEMAS, getArtifactSchema, renderArtifactSkeleton };
126
+ export { ARTIFACT_SCHEMAS, getArtifactSchema, renderArtifactSkeleton };
135
127
  //# sourceMappingURL=artifact-schema.js.map
@@ -1,5 +1,6 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
+ import { parseArtifactName } from './artifact-name.js';
3
4
  import { invalidationBlocks, isArtifactInvalidated, parseInvalidationDocument } from './invalidation.js';
4
5
  import { receiptForOutput } from './artifact-receipts.js';
5
6
  import { parseTypedTaskFrontmatter } from './frontmatter.js';
@@ -108,12 +109,9 @@ function canStart(action, facts, trigger) {
108
109
  }
109
110
  return deny('CAPABILITY_ACTION_UNKNOWN');
110
111
  }
111
- function artifactRound(name) {
112
- const match = /-r(\d+)\.md$/.exec(name);
113
- return match ? Number(match[1]) : 1;
114
- }
115
112
  function latestArtifact(names) {
116
- return [...names].sort((left, right) => artifactRound(right) - artifactRound(left) || left.localeCompare(right))[0] ?? null;
113
+ return names.map(parseArtifactName).filter((identity) => identity !== null)
114
+ .sort((left, right) => right.round - left.round || left.name.localeCompare(right.name))[0]?.name ?? null;
117
115
  }
118
116
  function reviewedInputName(content, expectedFamily) {
119
117
  const header = content.split(/\r?\n/).findIndex((line) => /\*\*(?:审查输入|Review Input)\*\*[::]/.test(line));
@@ -208,16 +206,11 @@ function buildLifecycleFacts(taskDir, content, taskState = 'active', executionBu
208
206
  const stat = fs.lstatSync(path.join(taskDir, name));
209
207
  return stat.isFile() && !stat.isSymbolicLink();
210
208
  });
211
- const artifactFamilies = {
212
- analysis: /^analysis(?:-r\d+)?\.md$/,
213
- 'review-analysis': /^review-analysis(?:-r\d+)?\.md$/,
214
- plan: /^plan(?:-r\d+)?\.md$/,
215
- 'review-plan': /^review-plan(?:-r\d+)?\.md$/,
216
- code: /^code(?:-r\d+)?\.md$/,
217
- 'review-code': /^review-code(?:-r\d+)?\.md$/
209
+ const artifactFamilies = ['analysis', 'review-analysis', 'plan', 'review-plan', 'code', 'review-code'];
210
+ const familyFor = (name) => {
211
+ const family = parseArtifactName(name)?.family;
212
+ return artifactFamilies.find((candidate) => candidate === family) ?? null;
218
213
  };
219
- const familyFor = (name) => Object.keys(artifactFamilies)
220
- .find((family) => artifactFamilies[family].test(name)) ?? null;
221
214
  const activeFiles = files.filter((name) => {
222
215
  const family = familyFor(name);
223
216
  return !family || !isArtifactInvalidated(invalidation.document, family, name);
@@ -225,11 +218,11 @@ function buildLifecycleFacts(taskDir, content, taskState = 'active', executionBu
225
218
  const artifactHashes = {};
226
219
  for (const name of activeFiles)
227
220
  artifactHashes[name] = sha256File(path.join(taskDir, name));
228
- const artifacts = Object.fromEntries(Object.keys(artifactFamilies).map((family) => [
229
- family, activeFiles.filter((name) => artifactFamilies[family].test(name))
221
+ const artifacts = Object.fromEntries(artifactFamilies.map((family) => [
222
+ family, activeFiles.filter((name) => familyFor(name) === family)
230
223
  ]));
231
- const staleArtifacts = Object.fromEntries(Object.keys(artifactFamilies).map((family) => [
232
- family, files.filter((name) => artifactFamilies[family].test(name) && isArtifactInvalidated(invalidation.document, family, name))
224
+ const staleArtifacts = Object.fromEntries(artifactFamilies.map((family) => [
225
+ family, files.filter((name) => familyFor(name) === family && isArtifactInvalidated(invalidation.document, family, name))
233
226
  ]));
234
227
  const qualification = parseTaskQualification(content);
235
228
  if (!qualification.ok)
@@ -237,7 +230,7 @@ function buildLifecycleFacts(taskDir, content, taskState = 'active', executionBu
237
230
  const qualificationStaleArtifacts = [];
238
231
  if (qualification.qualification.present) {
239
232
  const constraints = new Map(qualification.qualification.constraints.map((row) => [row.constraintId, row.digest]));
240
- for (const family of Object.keys(artifactFamilies)) {
233
+ for (const family of artifactFamilies) {
241
234
  if (!ARTIFACT_AUDIT_FAMILIES.has(family))
242
235
  continue;
243
236
  const name = latestArtifact(artifacts[family] ?? []);
@@ -5,7 +5,12 @@ import { computeLifecycleBuildIdentity, verifyLifecycleBuildIdentity } from '../
5
5
  import { verifyCodexSandboxControllerContextWithWarnings as verifySandboxControllerWithWarnings } from '../agent-clients/adapters/codex-lifecycle/sandbox-controller.js';
6
6
  import { verifyCodexSandboxControllerContextWithWarnings as verifyControllerContextFileWithWarnings } from '../agent-clients/adapters/codex-lifecycle/controller-context.js';
7
7
  import { resolveTaskRef } from './resolve-ref.js';
8
- import { activateMatchingOrchestrationDelegation, hasActivatableOrchestrationDelegation, hasSealableOrchestrationDelegation, OrchestrationStateError, pauseMatchingOrchestrationDelegation, prepareOrchestrationDelegation, reconcileMatchingOrchestrationDelegation, sealMatchingOrchestrationDelegationWithHostEvidence } from './orchestration.js';
8
+ import { activateMatchingOrchestrationDelegation, hasActivatableOrchestrationDelegation, hasSealableOrchestrationDelegation, OrchestrationStateError, pauseMatchingOrchestrationDelegation, prepareOrchestrationDelegation, routeOrchestration, reconcileMatchingOrchestrationDelegation, sealMatchingOrchestrationDelegationWithHostEvidence } from './orchestration.js';
9
+ import { randomUUID, createHash } from 'node:crypto';
10
+ import { consumeLifecycleRecoveryAttestation, issueLifecycleRecoveryAttestation, validateLifecycleRecoveryAttestation } from './control-authority.js';
11
+ function buildIdentityDigest(value) {
12
+ return createHash('sha256').update(JSON.stringify(value), 'utf8').digest('hex');
13
+ }
9
14
  function coreOptions(options) {
10
15
  return { ...options.orchestrationOptions, repoRoot: options.repoRoot ?? options.orchestrationOptions?.repoRoot };
11
16
  }
@@ -73,8 +78,9 @@ async function prepareCodexOrchestrationDelegation(taskRef, input, options = {})
73
78
  if (!resolved.ok)
74
79
  return bridgeFailure(resolved.code, resolved.message);
75
80
  const preflight = await (options.preflight ?? preflightCodexLifecycleEvidence)(repoRoot);
76
- if (!input.capabilityToken) {
77
- return bridgeFailure('ORCHESTRATION_CODEX_CAPABILITY_REQUIRED', 'Codex prepare requires a current-session capability token');
81
+ const capabilityRef = input.capabilityRef;
82
+ if (!capabilityRef) {
83
+ return bridgeFailure('ORCHESTRATION_CODEX_CAPABILITY_REQUIRED', 'Codex prepare requires a current-session capability reference');
78
84
  }
79
85
  const buildIdentity = options.buildIdentity ?? computeLifecycleBuildIdentity(repoRoot);
80
86
  const brokerController = options.controllerBinding ?? brokerControllerBinding();
@@ -99,7 +105,7 @@ async function prepareCodexOrchestrationDelegation(taskRef, input, options = {})
99
105
  controlGeneration: localController.controlGeneration
100
106
  } : null);
101
107
  const capabilityStore = options.capabilityStore ?? createCodexCapabilityStore();
102
- const attested = capabilityStore.inspect(input.capabilityToken);
108
+ const attested = capabilityStore.inspectReference(capabilityRef);
103
109
  const capabilityIdentity = verifyLifecycleBuildIdentity(attested.buildIdentity, buildIdentity);
104
110
  if (!capabilityIdentity.ok) {
105
111
  return bridgeFailure(capabilityIdentity.code, capabilityIdentity.message);
@@ -124,14 +130,57 @@ async function prepareCodexOrchestrationDelegation(taskRef, input, options = {})
124
130
  controlGeneration: controller.controlGeneration
125
131
  } } : {})
126
132
  };
133
+ if (input.lifecycleRecoveryAttestation) {
134
+ try {
135
+ validateLifecycleRecoveryAttestation(input.lifecycleRecoveryAttestation);
136
+ }
137
+ catch (error) {
138
+ return bridgeFailure('LIFECYCLE_AUTHORITY_ATTESTATION_INVALID', error instanceof Error ? error.message : String(error));
139
+ }
140
+ }
141
+ let lifecycleRecoveryAttestation = input.lifecycleRecoveryAttestation ?? null;
142
+ const routed = input.capabilityRef && !lifecycleRecoveryAttestation
143
+ ? routeOrchestration(taskRef, coreOptions(options))
144
+ : null;
145
+ if (routed && !routed.next)
146
+ return routed;
147
+ if (routed?.next && ['analysis', 'plan', 'code'].includes(routed.next.stage)) {
148
+ const recoveryRequest = {
149
+ version: 1,
150
+ requestId: randomUUID(),
151
+ operationId: randomUUID(),
152
+ phase: 'orchestration.prepare',
153
+ taskId: resolved.taskId,
154
+ family: routed.next.stage === 'analysis' ? 'analysis'
155
+ : routed.next.stage === 'plan' ? 'plan' : 'code',
156
+ artifact: routed.next.artifact,
157
+ round: routed.next.round,
158
+ lifecycleRequestId: randomUUID(),
159
+ authorityRef: capabilityRef,
160
+ expectedControlGeneration: controller?.controlGeneration ?? '',
161
+ expectedControllerInstanceDigest: controller?.instanceDigest ?? '0'.repeat(64),
162
+ expectedBuildIdentityDigest: buildIdentityDigest(buildIdentity),
163
+ expectedHookDefinitionHash: preflight.hookDefinitionHash
164
+ };
165
+ const authority = issueLifecycleRecoveryAttestation(recoveryRequest, {
166
+ capabilityStore,
167
+ ...(controller ? { controllerBinding: controller } : {}),
168
+ buildIdentity
169
+ });
170
+ if (authority.status === 'rejected' || !authority.attestation) {
171
+ return bridgeFailure(authority.error?.code ?? 'LIFECYCLE_AUTHORITY_REJECTED', authority.error?.message ?? 'lifecycle authority was rejected');
172
+ }
173
+ lifecycleRecoveryAttestation = authority.attestation;
174
+ }
127
175
  const prepared = prepareOrchestrationDelegation(taskRef, {
128
176
  ...input,
129
- lifecycleProvenance
177
+ lifecycleProvenance,
178
+ lifecycleRecoveryAttestation
130
179
  }, {
131
180
  ...coreOptions(options),
132
181
  validateLifecycleCapability: () => {
133
182
  try {
134
- const validated = capabilityStore.validate(input.capabilityToken, capabilityExpected);
183
+ const validated = capabilityStore.validateReference(capabilityRef, capabilityExpected);
135
184
  if (validated.sessionId !== attested.sessionId
136
185
  || validated.turnId !== attested.turnId
137
186
  || validated.toolUseId !== attested.toolUseId) {
@@ -146,24 +195,16 @@ async function prepareCodexOrchestrationDelegation(taskRef, input, options = {})
146
195
  return capabilityFailure(error);
147
196
  }
148
197
  },
149
- consumeLifecycleCapability: () => {
150
- try {
151
- const consumed = capabilityStore.consume(input.capabilityToken, capabilityExpected);
152
- if (consumed.sessionId !== attested.sessionId
153
- || consumed.turnId !== attested.turnId
154
- || consumed.toolUseId !== attested.toolUseId) {
155
- return {
156
- code: 'CODEX_CAPABILITY_IDENTITY_CHANGED',
157
- message: 'capability identity changed during consumption'
158
- };
159
- }
160
- return null;
161
- }
162
- catch (error) {
163
- return capabilityFailure(error);
164
- }
165
- }
198
+ consumeLifecycleCapability: () => null
166
199
  });
200
+ if (lifecycleRecoveryAttestation && !options.deferLifecycleRecoveryConsumption && prepared.status !== 'failed') {
201
+ try {
202
+ consumeLifecycleRecoveryAttestation(lifecycleRecoveryAttestation);
203
+ }
204
+ catch (error) {
205
+ return bridgeFailure(error instanceof Error ? error.name : 'LIFECYCLE_AUTHORITY_CONSUME_FAILED', error instanceof Error ? error.message : String(error));
206
+ }
207
+ }
167
208
  return identityWarnings.length > 0 && prepared.status !== 'failed'
168
209
  ? { ...prepared, warnings: Object.freeze(identityWarnings) }
169
210
  : prepared;
@@ -211,7 +252,12 @@ async function activateCodexOrchestrationDelegation(childThreadId, options = {})
211
252
  const contextVerification = contextPath
212
253
  ? verifySandboxControllerWithWarnings(contextPath, { repoRoot })
213
254
  : null;
214
- const controller = contextVerification?.context ?? null;
255
+ const controller = options.controllerBinding
256
+ ?? brokerControllerBinding()
257
+ ?? (contextVerification?.context ? {
258
+ instanceDigest: contextVerification.context.controllerInstanceDigest,
259
+ controlGeneration: contextVerification.context.controlGeneration
260
+ } : null);
215
261
  const activated = activateMatchingOrchestrationDelegation('codex', {
216
262
  nativeAgent: evidence.nativeAgent,
217
263
  childId: evidence.childThreadId,
@@ -233,7 +279,7 @@ async function activateCodexOrchestrationDelegation(childThreadId, options = {})
233
279
  capabilityTurnId: evidence.parentTurnId,
234
280
  spawnToolUseId: evidence.spawnToolUseId,
235
281
  spawnObservedAt: record.spawnObservedAt ?? undefined,
236
- controllerInstanceDigest: controller?.controllerInstanceDigest ?? null,
282
+ controllerInstanceDigest: controller?.instanceDigest ?? null,
237
283
  controlGeneration: controller?.controlGeneration ?? null
238
284
  }
239
285
  }, coreOptions(options));
@@ -0,0 +1,28 @@
1
+ import { execFileSync } from 'node:child_process';
2
+ function gitText(repoRoot, args) {
3
+ return execFileSync('git', [...args], {
4
+ cwd: repoRoot,
5
+ encoding: 'utf8',
6
+ stdio: ['ignore', 'pipe', 'pipe']
7
+ }).trim();
8
+ }
9
+ function checkpointCommitMatches(repoRoot, intent, head) {
10
+ try {
11
+ const parent = gitText(repoRoot, ['rev-parse', `${head}^`]);
12
+ const tree = gitText(repoRoot, ['rev-parse', `${head}^{tree}`]);
13
+ const message = gitText(repoRoot, ['show', '-s', '--format=%s', head]);
14
+ const changed = gitText(repoRoot, ['diff-tree', '--no-commit-id', '--name-only', '-r', head])
15
+ .split('\n')
16
+ .filter(Boolean)
17
+ .sort();
18
+ return parent === intent.expectedHead
19
+ && tree === intent.expectedTree
20
+ && message === intent.message
21
+ && JSON.stringify(changed) === JSON.stringify([...intent.paths].sort());
22
+ }
23
+ catch {
24
+ return false;
25
+ }
26
+ }
27
+ export { checkpointCommitMatches };
28
+ //# sourceMappingURL=commit-identity.js.map