@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
@@ -4,14 +4,13 @@ import path from 'node:path';
4
4
  import { appendActivityEntry, locateActivityLog, pairEntries, startedBackedRows } from './activity-log.ts';
5
5
  import {
6
6
  buildArtifactLinkSection,
7
- artifactName,
8
7
  inspectArtifactDirectory,
9
8
  parseReviewedInputReference,
10
9
  parseCodePlanInputReference,
11
- parseArtifactName,
12
10
  resolveArtifactContext,
13
11
  validateCompletedArtifact
14
12
  } from './artifact-lifecycle.ts';
13
+ import { artifactName, parseArtifactName } from './artifact-name.ts';
15
14
  import type { ArtifactContextResult, ArtifactErrorCode, ArtifactFamily, ArtifactIdentity } from './artifact-lifecycle.ts';
16
15
  import { ArtifactReceiptError, parseArtifactReceipts, sha256File, upsertArtifactReceipt } from './artifact-receipts.ts';
17
16
  import type { ArtifactReceipt } from './artifact-receipts.ts';
@@ -39,6 +38,7 @@ import {
39
38
  readLocalArtifactFinalizationIntent,
40
39
  validateLocalArtifact
41
40
  } from './local-artifact-finalization.ts';
41
+ import { writeArtifactRepairIntent } from './artifact-repair-intent.ts';
42
42
  import type { LocalArtifactFamily, LocalArtifactFinalizationIntent } from './local-artifact-finalization.ts';
43
43
  import { buildLifecycleFacts, canStart } from './capabilities.ts';
44
44
  import type { ExplicitTrigger, LifecycleAction, TriggerInitiator, TriggerReason } from './capabilities.ts';
@@ -49,6 +49,13 @@ import { ARTIFACT_FAMILIES, expectedQualificationRelations, parseQualificationAu
49
49
  import type { QualificationAudit, UpstreamRelation } from './qualification-audit.ts';
50
50
  import { getArtifactSchema } from './artifact-schema.ts';
51
51
  import { inspectArtifactContract } from './artifact-operations.ts';
52
+ import {
53
+ consumeLifecycleRecoveryAttestation,
54
+ lifecycleRecoveryAttestationDigest,
55
+ validateLifecycleRecoveryAttestation,
56
+ type LifecycleRecoveryAttestationV1
57
+ } from './control-authority.ts';
58
+ import { readManualValidationCompletion } from './manual-validation-completion.ts';
52
59
 
53
60
  const eventCatalog = [
54
61
  'analyze.started', 'analyze.awaiting-input', 'analyze.completed',
@@ -69,6 +76,10 @@ type TaskEventErrorCode =
69
76
  | 'EVENT_LOG_CONFLICT' | 'EVENT_ARTIFACT_CONFLICT' | 'EVENT_FINDING_COUNT_MISMATCH'
70
77
  | 'EVENT_VERDICT_INVALID'
71
78
  | 'EVENT_ORCHESTRATION_COMMIT_FAILED'
79
+ | 'MANUAL_VALIDATION_RECEIPT_MISSING' | 'MANUAL_VALIDATION_RECEIPT_INVALID'
80
+ | 'MANUAL_VALIDATION_RECEIPT_IDENTITY_MISMATCH'
81
+ | 'MANUAL_VALIDATION_TRANSACTION_MISSING' | 'MANUAL_VALIDATION_TRANSACTION_INVALID'
82
+ | 'MANUAL_VALIDATION_TRANSACTION_IDENTITY_MISMATCH' | 'MANUAL_VALIDATION_TRANSACTION_PHASE_INVALID'
72
83
  | ArtifactErrorCode | TaskWriteErrorCode;
73
84
  type TaskEventRequest = {
74
85
  taskRef: string; event: TaskEventName | string; agent: string; dryRun?: boolean; orchestrated?: boolean;
@@ -80,11 +91,14 @@ type TaskEventRequest = {
80
91
  verdict?: Verdict; blockers?: number; major?: number; minor?: number;
81
92
  manualValidation?: number; filesModified?: number; testsPassed?: number;
82
93
  summaryResult?: string;
94
+ transactionId?: string; receiptDigest?: string; prHeadSha?: string;
83
95
  };
84
96
  type TaskEventError = { code: TaskEventErrorCode; message: string };
85
97
  type TaskEventOptions = TaskWriteOptions & {
86
98
  commitOrchestrationCompletion?: (plan: OrchestrationStageCompletion) => void;
87
99
  lockAlreadyHeld?: boolean;
100
+ lifecycleRecoveryAttestation?: LifecycleRecoveryAttestationV1 | null;
101
+ deferLifecycleRecoveryConsumption?: boolean;
88
102
  };
89
103
  type TaskEventResult = {
90
104
  status: 'planned' | 'applied' | 'no-op' | 'failed'; changed: boolean;
@@ -112,8 +126,8 @@ const SCHEMAS: Record<TaskEventName, { required?: string[]; optional?: string[]
112
126
  'code.completed': { required: ['artifact', 'artifactSha256', 'semanticDigest'], optional: ['round', 'fixFor', 'implementationInput', 'filesModified', 'testsPassed', 'blockers', 'major', 'minor', 'manualValidation', 'orchestrated'] },
113
127
  'review-code.started': { optional: ['round'] },
114
128
  'review-code.completed': { required: ['artifact', 'verdict', 'blockers', 'major', 'minor', 'manualValidation'], optional: ['round', 'orchestrated'] },
115
- 'manual-validation.started': { optional: ['round'] },
116
- 'manual-validation.completed': { required: ['artifact', 'summaryResult'], optional: ['round'] },
129
+ 'manual-validation.started': { optional: ['round', 'transactionId'] },
130
+ 'manual-validation.completed': { required: ['artifact', 'summaryResult', 'transactionId', 'receiptDigest', 'prHeadSha'], optional: ['round'] },
117
131
  'validation-run.started': { optional: ['round'] },
118
132
  'validation-run.completed': { required: ['artifact'], optional: ['round'] }
119
133
  };
@@ -146,12 +160,23 @@ function validateTaskEventRequest(request: TaskEventRequest): TaskEventError | n
146
160
  const forbidden = fix ? ['filesModified', 'testsPassed'] : ['fixFor', 'blockers', 'major', 'minor', 'manualValidation'];
147
161
  if (modeRequired.some((key) => request[key as keyof TaskEventRequest] === undefined) || forbidden.some((key) => request[key as keyof TaskEventRequest] !== undefined)) return { code: 'EVENT_PAYLOAD_INVALID', message: 'code.completed requires either initial or fix completion payload' };
148
162
  }
149
- if (request.fixFor && !/^review-code(?:-r(?:[2-9]|[1-9]\d+))?\.md$/.test(request.fixFor)) return { code: 'EVENT_PAYLOAD_INVALID', message: 'fixFor must reference a canonical review-code artifact' };
163
+ if (request.fixFor && parseArtifactName(request.fixFor)?.family !== 'review-code') return { code: 'EVENT_PAYLOAD_INVALID', message: 'fixFor must reference a canonical review-code artifact' };
150
164
  if (request.implementationInput && !/^II-[1-9]\d*$/.test(request.implementationInput)) return { code: 'EVENT_PAYLOAD_INVALID', message: 'implementationInput must be a canonical II-N id' };
151
165
  if (request.artifactSha256 !== undefined && !/^[0-9a-f]{64}$/i.test(request.artifactSha256)) return { code: 'EVENT_PAYLOAD_INVALID', message: 'artifactSha256 must be a 64-character hexadecimal digest' };
152
166
  if (request.semanticDigest !== undefined && !/^[0-9a-f]{64}$/i.test(request.semanticDigest)) return { code: 'EVENT_PAYLOAD_INVALID', message: 'semanticDigest must be a 64-character hexadecimal digest' };
153
167
  if (request.fixFor && request.implementationInput) return { code: 'EVENT_PAYLOAD_INVALID', message: 'fixFor and implementationInput are mutually exclusive' };
154
168
  if (request.summaryResult !== undefined && (!request.summaryResult.trim() || /[\r\n]/.test(request.summaryResult))) return { code: 'EVENT_PAYLOAD_INVALID', message: 'summaryResult must be a non-empty single line' };
169
+ if (request.event === 'manual-validation.completed') {
170
+ for (const [name, value, pattern] of [
171
+ ['transactionId', request.transactionId, /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u],
172
+ ['receiptDigest', request.receiptDigest, /^[a-f0-9]{64}$/u],
173
+ ['prHeadSha', request.prHeadSha, /^[a-f0-9]{40}$/u]
174
+ ] as const) if (!value || !pattern.test(value)) return { code: 'EVENT_PAYLOAD_INVALID', message: `manual-validation.completed requires a valid ${name}` };
175
+ }
176
+ if (request.event === 'manual-validation.started' && request.transactionId !== undefined
177
+ && !/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u.test(request.transactionId)) {
178
+ return { code: 'EVENT_PAYLOAD_INVALID', message: 'manual-validation.started requires a valid transactionId' };
179
+ }
155
180
  if (request.event !== 'analyze.awaiting-input' && /\.(?:started|completed)$/.test(request.event)) {
156
181
  if (!request.initiator || !request.requestId || !request.reasonCode) {
157
182
  return { code: 'EVENT_TRIGGER_REQUIRED', message: `${request.event} requires --initiator, --request-id, and --reason-code` };
@@ -254,7 +279,9 @@ function identity(request: TaskEventRequest) {
254
279
  if (family === 'manual-validation') {
255
280
  return {
256
281
  family, phase, action: spec.label,
257
- note: phase === 'started' ? 'started' : `Manual validation passed → ${request.artifact}; ${request.summaryResult}`,
282
+ note: phase === 'started'
283
+ ? `started${request.transactionId ? `; transaction=${request.transactionId}` : ''}`
284
+ : `Manual validation passed → ${request.artifact}; ${request.summaryResult}; transaction=${request.transactionId}; receipt=${request.receiptDigest}; head=${request.prHeadSha}`,
258
285
  target: null
259
286
  } as const;
260
287
  }
@@ -303,6 +330,28 @@ function normalizeStarted(request: TaskEventRequest, repoRoot: string): { reques
303
330
  return { request: { ...request, round, artifact: context.next.name, fixFor: expectedFix, implementationInput: expectedImplementation }, context };
304
331
  }
305
332
 
333
+ function validateManualValidationCompletion(
334
+ taskDir: string,
335
+ taskId: string,
336
+ request: TaskEventRequest,
337
+ artifactPath: string
338
+ ): TaskEventError | null {
339
+ const completion = readManualValidationCompletion(taskDir, {
340
+ transactionId: request.transactionId,
341
+ taskId,
342
+ prHeadSha: request.prHeadSha,
343
+ artifact: request.artifact,
344
+ receiptDigest: request.receiptDigest
345
+ });
346
+ if (!completion.ok) return completion.error;
347
+ const { receipt, transaction } = completion.value;
348
+ if (!['receipt-committed', 'final-promotion-in-progress', 'committed'].includes(transaction.phase)) {
349
+ return { code: 'MANUAL_VALIDATION_TRANSACTION_PHASE_INVALID', message: 'manual validation completion requires the receipt-backed transaction phase' };
350
+ }
351
+ if (sha256File(artifactPath) !== receipt.artifactSha256) return { code: 'MANUAL_VALIDATION_RECEIPT_INVALID', message: 'manual-validation artifact digest does not match the receipt' };
352
+ return null;
353
+ }
354
+
306
355
  function openStartedIdentity(rows: ReturnType<typeof pairEntries>, family: EventFamily) {
307
356
  const spec = FAMILY[family];
308
357
  if (family === 'manual-validation') {
@@ -626,6 +675,10 @@ function applyTaskEventUnlocked(request: TaskEventRequest, options: TaskEventOpt
626
675
  if (openIdentity && 'conflict' in openIdentity) return failed(request, { code: 'EVENT_LOG_CONFLICT', message: 'artifact family has more than one open started event' }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath });
627
676
  if (openIdentity) {
628
677
  if (openIdentity.row.agent !== request.agent) return failed(request, { code: 'EVENT_LOG_CONFLICT', message: 'open started event has a different agent' }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath });
678
+ if (initialParts.family === 'manual-validation' && request.transactionId !== undefined) {
679
+ const existingTransactionId = /(?:^|;\s*)transaction=([A-Za-z0-9][A-Za-z0-9._-]{0,127})(?:;|$)/u.exec(openIdentity.row.note)?.[1];
680
+ if (existingTransactionId !== undefined && existingTransactionId !== request.transactionId) return failed(request, { code: 'EVENT_LOG_CONFLICT', message: 'open manual-validation started event has a different transactionId' }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath });
681
+ }
629
682
  if (request.round !== undefined && request.round !== openIdentity.round) return failed(request, { code: 'EVENT_ARTIFACT_CONFLICT', message: `round ${request.round} conflicts with open round ${openIdentity.round}` }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath });
630
683
  if (request.fixFor !== undefined && request.fixFor !== openIdentity.fixFor) return failed(request, { code: 'EVENT_ARTIFACT_CONFLICT', message: `fixFor '${request.fixFor}' conflicts with open event` }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath });
631
684
  if (request.implementationInput !== openIdentity.implementationInput) return failed(request, { code: 'EVENT_ARTIFACT_CONFLICT', message: `implementationInput '${request.implementationInput ?? ''}' conflicts with open event` }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath });
@@ -641,6 +694,23 @@ function applyTaskEventUnlocked(request: TaskEventRequest, options: TaskEventOpt
641
694
  if (identity?.family === FAMILY[initialParts.family].artifact) normalized = { ...request, round: identity.round };
642
695
  }
643
696
  const eventIdentity = identity(normalized);
697
+ const lifecycleAuthority = options.lifecycleRecoveryAttestation ?? null;
698
+ if (lifecycleAuthority) {
699
+ try { validateLifecycleRecoveryAttestation(lifecycleAuthority); }
700
+ catch (error) {
701
+ return failed(normalized, { code: 'EVENT_ARTIFACT_CONFLICT', message: error instanceof Error ? error.message : String(error) }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath });
702
+ }
703
+ const expectedFamily = eventIdentity.family === 'analyze' ? 'analysis' : eventIdentity.family;
704
+ if (eventIdentity.phase !== 'completed'
705
+ || lifecycleAuthority.phase !== 'task-event.completed'
706
+ || lifecycleAuthority.taskId !== resolved.taskId
707
+ || lifecycleAuthority.family !== expectedFamily
708
+ || lifecycleAuthority.artifact !== normalized.artifact
709
+ || lifecycleAuthority.round !== normalized.round
710
+ || (normalized.requestId !== undefined && lifecycleAuthority.lifecycleRequestId !== normalized.requestId)) {
711
+ return failed(normalized, { code: 'EVENT_ARTIFACT_CONFLICT', message: 'lifecycle completion authority does not match the event tuple' }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath });
712
+ }
713
+ }
644
714
  const currentStep = typeof frontmatter.current_step === 'string' ? frontmatter.current_step : '';
645
715
  const matchingRows = rows.filter((item) => item.step === eventIdentity.action);
646
716
  const manual = eventIdentity.family === 'manual-validation';
@@ -652,6 +722,12 @@ function applyTaskEventUnlocked(request: TaskEventRequest, options: TaskEventOpt
652
722
  let localFinalizationIntent: LocalArtifactFinalizationIntent | null = null;
653
723
  if (eventIdentity.phase === 'started' && row) {
654
724
  if (row.agent !== normalized.agent) return failed(normalized, { code: 'EVENT_LOG_CONFLICT', message: 'open started event has a different agent' }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath });
725
+ if (manual && normalized.transactionId !== undefined) {
726
+ const existingTransactionId = /(?:^|;\s*)transaction=([A-Za-z0-9][A-Za-z0-9._-]{0,127})(?:;|$)/u.exec(row.note)?.[1];
727
+ if (existingTransactionId !== undefined && existingTransactionId !== normalized.transactionId) {
728
+ return failed(normalized, { code: 'EVENT_LOG_CONFLICT', message: 'open manual-validation started event has a different transactionId' }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath });
729
+ }
730
+ }
655
731
  return successNoOp(normalized, resolved.taskId, resolved.taskMdPath, currentStep, eventIdentity, row.started, frontmatter, artifactContext);
656
732
  }
657
733
  if (eventIdentity.phase === 'started' && !manual && completedRows.length > 0) return failed(normalized, { code: 'EVENT_ALREADY_COMPLETED', message: 'event identity is already completed' }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath });
@@ -663,6 +739,15 @@ function applyTaskEventUnlocked(request: TaskEventRequest, options: TaskEventOpt
663
739
  const validated = validateCompletedArtifact(resolved.taskDir, FAMILY[eventIdentity.family].artifact, normalized.artifact!, normalized.round);
664
740
  if (!validated.ok) return failed(normalized, validated.error, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
665
741
  completedArtifact = validated.artifact;
742
+ if (eventIdentity.family === 'manual-validation') {
743
+ const validationError = validateManualValidationCompletion(
744
+ resolved.taskDir,
745
+ resolved.taskId,
746
+ normalized,
747
+ completedArtifact.path
748
+ );
749
+ if (validationError) return failed(normalized, validationError, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
750
+ }
666
751
  if (eventIdentity.family.startsWith('review-')) {
667
752
  let reviewContent: string;
668
753
  try { reviewContent = fs.readFileSync(completedArtifact.path, 'utf8'); }
@@ -730,7 +815,7 @@ function applyTaskEventUnlocked(request: TaskEventRequest, options: TaskEventOpt
730
815
  message: `cannot read local finalizer provenance for ${completedArtifact.name}: ${error instanceof Error ? error.message : String(error)}`
731
816
  }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
732
817
  }
733
- if (!localFinalizationIntent || !['passed', 'consumed'].includes(localFinalizationIntent.state)) {
818
+ if (!localFinalizationIntent || !['passed', 'consumed', 'finalize-ready', 'commit-started'].includes(localFinalizationIntent.state)) {
734
819
  return failed(normalized, {
735
820
  code: 'EVENT_ARTIFACT_CONFLICT',
736
821
  message: `local finalizer provenance is missing or incomplete for ${completedArtifact.name}`
@@ -742,6 +827,18 @@ function applyTaskEventUnlocked(request: TaskEventRequest, options: TaskEventOpt
742
827
  message: `local finalizer provenance does not match ${completedArtifact.name}`
743
828
  }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
744
829
  }
830
+ if (localFinalizationIntent.recoveryOperationId !== null) {
831
+ if (!lifecycleAuthority
832
+ || localFinalizationIntent.recoveryOperationId !== lifecycleAuthority.operationId
833
+ || localFinalizationIntent.requestId !== lifecycleAuthority.lifecycleRequestId
834
+ || (localFinalizationIntent.phase !== 'artifact.finalize-local'
835
+ && localFinalizationIntent.authorityDigest !== lifecycleRecoveryAttestationDigest(lifecycleAuthority))) {
836
+ return failed(normalized, {
837
+ code: 'EVENT_ARTIFACT_CONFLICT',
838
+ message: `local finalizer provenance does not match lifecycle completion authority for ${completedArtifact.name}`
839
+ }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase, artifactContext });
840
+ }
841
+ }
745
842
  }
746
843
  }
747
844
  if (eventIdentity.phase === 'waiting' && section.entries.some((entry) => entry.step === eventIdentity.action && entry.note === eventIdentity.note)) {
@@ -887,13 +984,26 @@ function applyTaskEventUnlocked(request: TaskEventRequest, options: TaskEventOpt
887
984
  });
888
985
  }
889
986
  mutations.push({ kind: 'section', aliases: ['活动日志', 'Activity Log'], heading: section.heading, body });
890
- if (!normalized.dryRun && localFinalizationIntent && completedArtifact && localFinalizationIntent.state === 'passed') {
987
+ if (!normalized.dryRun && localFinalizationIntent && completedArtifact) {
891
988
  try {
892
- localFinalizationIntent = consumeLocalArtifactFinalizationIntent(resolved.repoRoot, localFinalizationIntent);
989
+ if (lifecycleAuthority && localFinalizationIntent.state === 'passed') {
990
+ const expectedIntent = localFinalizationIntent;
991
+ localFinalizationIntent = {
992
+ ...localFinalizationIntent,
993
+ state: 'commit-started',
994
+ phase: 'task-event.completed',
995
+ authorityDigest: lifecycleRecoveryAttestationDigest(lifecycleAuthority),
996
+ requestId: lifecycleAuthority.lifecycleRequestId,
997
+ updatedAt: Date.now()
998
+ };
999
+ writeArtifactRepairIntent(resolved.repoRoot, localFinalizationIntent, { expected: expectedIntent });
1000
+ } else if (!lifecycleAuthority && localFinalizationIntent.state === 'passed') {
1001
+ localFinalizationIntent = consumeLocalArtifactFinalizationIntent(resolved.repoRoot, localFinalizationIntent);
1002
+ }
893
1003
  } catch (error) {
894
1004
  return failed(normalized, {
895
1005
  code: 'EVENT_ARTIFACT_CONFLICT',
896
- message: `local finalizer provenance could not be consumed before task write for ${completedArtifact.name}: ${error instanceof Error ? error.message : String(error)}`
1006
+ message: `local finalizer provenance could not be advanced before task write for ${completedArtifact.name}: ${error instanceof Error ? error.message : String(error)}`
897
1007
  }, {
898
1008
  taskId: resolved.taskId,
899
1009
  taskMdPath: resolved.taskMdPath,
@@ -909,6 +1019,17 @@ function applyTaskEventUnlocked(request: TaskEventRequest, options: TaskEventOpt
909
1019
  && ['analyze', 'plan', 'code'].includes(eventIdentity.family);
910
1020
  const result = writeTask({ taskRef: normalized.taskRef, expectedState: stateOverride ? resolved.state : 'active', dryRun: normalized.dryRun, mutations }, { ...options, invalidationContext: sourceCompletion ? 'source-completion' : 'standard', metadataProvider: () => metadata });
911
1021
  if (result.status === 'failed') return failed(normalized, result.error, { taskId: result.taskId, taskMdPath: result.taskMdPath, fromStep: currentStep, toStep: step, action: eventIdentity.action, phase: eventIdentity.phase, timestamp: result.timestamp, agentInfraVersion: result.agentInfraVersion, operations: result.operations, artifactContext });
1022
+ if (!normalized.dryRun && lifecycleAuthority && localFinalizationIntent && completedArtifact) {
1023
+ try {
1024
+ if (!options.deferLifecycleRecoveryConsumption) consumeLifecycleRecoveryAttestation(lifecycleAuthority);
1025
+ writeArtifactRepairIntent(resolved.repoRoot, { ...localFinalizationIntent, state: 'consumed', updatedAt: Date.now() }, { expected: localFinalizationIntent });
1026
+ } catch (error) {
1027
+ return failed(normalized, {
1028
+ code: 'EVENT_ARTIFACT_CONFLICT',
1029
+ message: `task completion was written but lifecycle recovery consumption is incomplete: ${error instanceof Error ? error.message : String(error)}`
1030
+ }, { taskId: result.taskId, taskMdPath: result.taskMdPath, fromStep: currentStep, toStep: step, action: eventIdentity.action, phase: eventIdentity.phase, timestamp: result.timestamp, agentInfraVersion: result.agentInfraVersion, operations: result.operations, artifactContext });
1031
+ }
1032
+ }
912
1033
  if (!normalized.dryRun && orchestrationCompletion) {
913
1034
  try {
914
1035
  (options.commitOrchestrationCompletion ?? commitOrchestrationStageCompletion)(orchestrationCompletion);
@@ -0,0 +1,11 @@
1
+ import type { TaskFinalizationReceipt } from './finalization.ts';
2
+
3
+ export type TaskControlBinding = NonNullable<TaskFinalizationReceipt['controlBinding']>;
4
+ export type TaskControlBindingEvidence = (taskId: string, binding: TaskControlBinding) => 'terminal' | 'pending' | 'mismatch';
5
+
6
+ export function taskFinalizationReceiptState(receipt: TaskFinalizationReceipt): 'pending' | 'unresolved' | 'complete' {
7
+ if (receipt.lifecycle !== 'done' || receipt.taskComment === 'pending'
8
+ || receipt.verification === 'pending' || receipt.warningProjection !== 'done') return 'pending';
9
+ if (receipt.lastError !== null || receipt.warnings.some((warning) => warning.status === 'open')) return 'unresolved';
10
+ return 'complete';
11
+ }
@@ -2,6 +2,7 @@ import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { createHash, randomUUID } from 'node:crypto';
4
4
 
5
+ import { parseArtifactName } from './artifact-name.ts';
5
6
  import { locateActivityLog } from './activity-log.ts';
6
7
  import { applyPrReviewActivityIntent } from './activity-intent.ts';
7
8
  import { applyHumanDecision } from './decision-intents.ts';
@@ -814,14 +815,8 @@ async function probeVerificationFailure(taskRef: string, policy: FailurePolicy,
814
815
  function latestArtifact(taskDir: string, family: string): string | undefined {
815
816
  let names: string[];
816
817
  try { names = fs.readdirSync(taskDir); } catch { return undefined; }
817
- const candidates = names.filter((name) => (
818
- name === `${family}.md` || new RegExp(`^${family}-r\\d+\\.md$`).test(name)
819
- ));
820
- candidates.sort((a, b) => {
821
- const round = (name: string) => Number(/-r(\d+)\.md$/.exec(name)?.[1] ?? 1);
822
- return round(b) - round(a);
823
- });
824
- return candidates[0];
818
+ return names.map(parseArtifactName).filter((identity) => identity !== null)
819
+ .filter((identity) => identity.family === family).sort((left, right) => right.round - left.round)[0]?.name;
825
820
  }
826
821
 
827
822
  async function probeVerificationEngineFailure(policy: FailurePolicy, resolved: ProbeTask): Promise<ProducerFailure | OverrideError> {
@@ -1,3 +1,4 @@
1
+ import { parseArtifactName } from './artifact-name.ts';
1
2
  import { parseTable } from './sections.ts';
2
3
 
3
4
  const SECTION_ALIASES = ['实现输入', 'Implementation Inputs'] as const;
@@ -6,7 +7,6 @@ const COLUMNS = [
6
7
  'decided_at', 'status', 'consumed_by'
7
8
  ] as const;
8
9
  const TIMESTAMP_RE = /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}[+-]\d{2}:\d{2}$/;
9
- const ARTIFACT_RE = /^code(?:-r(?:[2-9]|[1-9]\d+))?\.md$/;
10
10
 
11
11
  type ImplementationInputStatus = 'declared' | 'pending' | 'not-required' | 'consumed';
12
12
  type ImplementationInput = {
@@ -36,7 +36,7 @@ function validateInput(row: ImplementationInput): void {
36
36
  (row.status === 'declared' && row.decidedAt === '' && row.consumedBy === '') ||
37
37
  (hasValidTimestamp && row.needsImplementation && row.status === 'pending' && row.consumedBy === '') ||
38
38
  (hasValidTimestamp && !row.needsImplementation && row.status === 'not-required' && row.consumedBy === '') ||
39
- (hasValidTimestamp && row.needsImplementation && row.status === 'consumed' && ARTIFACT_RE.test(row.consumedBy));
39
+ (hasValidTimestamp && row.needsImplementation && row.status === 'consumed' && parseArtifactName(row.consumedBy)?.family === 'code');
40
40
  if (!valid) throw new Error(`implementation input ${row.id} has an invalid state combination`);
41
41
  }
42
42
 
@@ -143,7 +143,7 @@ function selectPendingImplementationInput(
143
143
  function consumeImplementationInput(
144
144
  rows: readonly ImplementationInput[], id: string, artifact: string
145
145
  ): ImplementationInput[] {
146
- if (!ARTIFACT_RE.test(artifact)) throw new Error(`implementation input artifact '${artifact}' is invalid`);
146
+ if (parseArtifactName(artifact)?.family !== 'code') throw new Error(`implementation input artifact '${artifact}' is invalid`);
147
147
  let found = false;
148
148
  const next = rows.map((row) => {
149
149
  if (row.id !== id) return row;