@fitlab-ai/agent-infra 0.9.6 → 0.9.8

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 (178) hide show
  1. package/dist/bin/internal-cli.js +10 -0
  2. package/dist/lib/agent-clients/adapters/claude-code.js +3 -3
  3. package/dist/lib/agent-clients/adapters/codex-lifecycle/app-server.js +47 -5
  4. package/dist/lib/agent-clients/adapters/codex-lifecycle/build-identity.js +162 -0
  5. package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +245 -0
  6. package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +398 -0
  7. package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +23 -6
  8. package/dist/lib/decide.js +68 -14
  9. package/dist/lib/defaults.json +10 -2
  10. package/dist/lib/git/workflow.js +30 -3
  11. package/dist/lib/init.js +1 -1
  12. package/dist/lib/internal/codex-lifecycle.js +69 -1
  13. package/dist/lib/internal/codex-sandbox-controller.js +106 -0
  14. package/dist/lib/internal/release-workflow.js +1 -3
  15. package/dist/lib/internal/task-activity.js +57 -7
  16. package/dist/lib/internal/task-event.js +57 -4
  17. package/dist/lib/internal/task-ledger.js +59 -5
  18. package/dist/lib/internal/task-lifecycle.js +59 -8
  19. package/dist/lib/internal/task-orchestration.js +48 -8
  20. package/dist/lib/internal/task-override.js +131 -0
  21. package/dist/lib/internal/task-review.js +64 -6
  22. package/dist/lib/internal/task-verify.js +65 -3
  23. package/dist/lib/internal/task-warning.js +57 -4
  24. package/dist/lib/platform/context.js +6 -4
  25. package/dist/lib/platform/github-client.js +55 -2
  26. package/dist/lib/platform/pr-summary.js +5 -3
  27. package/dist/lib/process-data/checkpoint.js +319 -0
  28. package/dist/lib/process-data/index.js +128 -60
  29. package/dist/lib/process-data/materialize.js +84 -0
  30. package/dist/lib/process-data/normalize.js +34 -1
  31. package/dist/lib/process-data/reconcile.js +55 -1
  32. package/dist/lib/process-data/sources.js +422 -1
  33. package/dist/lib/process-data/store.js +341 -11
  34. package/dist/lib/runtime/agent-runtime.js +56 -0
  35. package/dist/lib/sandbox/commands/create.js +394 -264
  36. package/dist/lib/sandbox/commands/rm.js +64 -15
  37. package/dist/lib/sandbox/commands/start.js +4 -3
  38. package/dist/lib/sandbox/control/container-identity.js +64 -0
  39. package/dist/lib/sandbox/control/executor.js +23 -9
  40. package/dist/lib/sandbox/control/lifecycle.js +590 -61
  41. package/dist/lib/sandbox/control/protocol.js +7 -0
  42. package/dist/lib/sandbox/control/server.js +202 -26
  43. package/dist/lib/sandbox/control/state.js +51 -25
  44. package/dist/lib/sandbox/control/timing.js +8 -0
  45. package/dist/lib/sandbox/engine.js +14 -7
  46. package/dist/lib/sandbox/mounts.js +7 -0
  47. package/dist/lib/sandbox/recovery.js +135 -50
  48. package/dist/lib/sandbox/workspace-view.js +59 -20
  49. package/dist/lib/server/daemon.js +1 -1
  50. package/dist/lib/server/process-control.js +11 -1
  51. package/dist/lib/server/process-state.js +107 -20
  52. package/dist/lib/task/activity-intent.js +4 -1
  53. package/dist/lib/task/artifact-lifecycle.js +82 -11
  54. package/dist/lib/task/artifact-receipts.js +128 -0
  55. package/dist/lib/task/codex-orchestration.js +81 -6
  56. package/dist/lib/task/commands/validate.js +1 -1
  57. package/dist/lib/task/create.js +10 -2
  58. package/dist/lib/task/decision-intents.js +4 -2
  59. package/dist/lib/task/delegation-receipts.js +120 -17
  60. package/dist/lib/task/events.js +148 -7
  61. package/dist/lib/task/guard-override.js +31 -0
  62. package/dist/lib/task/human-override.js +1240 -0
  63. package/dist/lib/task/ledger-intents.js +6 -3
  64. package/dist/lib/task/ledger.js +13 -1
  65. package/dist/lib/task/lifecycle.js +69 -14
  66. package/dist/lib/task/orchestration.js +293 -18
  67. package/dist/lib/task/review-finalization.js +3 -2
  68. package/dist/lib/task/sections.js +5 -1
  69. package/dist/lib/task/task-execution-lock.js +5 -4
  70. package/dist/lib/task/verification-engine.js +80 -22
  71. package/dist/lib/task/verification.js +13 -1
  72. package/dist/lib/task/workflow-warning-intents.js +4 -2
  73. package/dist/lib/task/write.js +2 -1
  74. package/dist/lifecycle-build-manifest.json +6 -0
  75. package/lib/agent-clients/adapters/claude-code.ts +3 -3
  76. package/lib/agent-clients/adapters/codex-lifecycle/app-server.ts +62 -4
  77. package/lib/agent-clients/adapters/codex-lifecycle/build-identity.ts +227 -0
  78. package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +322 -0
  79. package/lib/agent-clients/adapters/codex-lifecycle/manifest-files.json +26 -0
  80. package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +480 -0
  81. package/lib/agent-clients/adapters/codex-lifecycle/store.ts +35 -5
  82. package/lib/decide.ts +59 -14
  83. package/lib/defaults.json +10 -2
  84. package/lib/git/workflow.ts +25 -2
  85. package/lib/init.ts +1 -1
  86. package/lib/internal/codex-lifecycle.ts +68 -1
  87. package/lib/internal/codex-sandbox-controller.ts +109 -0
  88. package/lib/internal/release-workflow.ts +1 -3
  89. package/lib/internal/task-activity.ts +46 -6
  90. package/lib/internal/task-event.ts +53 -4
  91. package/lib/internal/task-ledger.ts +48 -4
  92. package/lib/internal/task-lifecycle.ts +56 -12
  93. package/lib/internal/task-orchestration.ts +43 -8
  94. package/lib/internal/task-override.ts +138 -0
  95. package/lib/internal/task-review.ts +51 -6
  96. package/lib/internal/task-verify.ts +52 -3
  97. package/lib/internal/task-warning.ts +44 -3
  98. package/lib/platform/context.ts +8 -5
  99. package/lib/platform/github-client.ts +61 -3
  100. package/lib/platform/pr-summary.ts +10 -3
  101. package/lib/process-data/checkpoint.ts +335 -0
  102. package/lib/process-data/index.ts +112 -60
  103. package/lib/process-data/materialize.ts +83 -0
  104. package/lib/process-data/normalize.ts +29 -1
  105. package/lib/process-data/reconcile.ts +64 -2
  106. package/lib/process-data/sources.ts +449 -2
  107. package/lib/process-data/store.ts +304 -11
  108. package/lib/process-data/types.ts +124 -2
  109. package/lib/runtime/agent-runtime.ts +76 -0
  110. package/lib/sandbox/commands/create.ts +139 -6
  111. package/lib/sandbox/commands/rm.ts +64 -14
  112. package/lib/sandbox/commands/start.ts +4 -3
  113. package/lib/sandbox/config.ts +2 -1
  114. package/lib/sandbox/control/container-identity.ts +93 -0
  115. package/lib/sandbox/control/executor.ts +24 -12
  116. package/lib/sandbox/control/lifecycle.ts +685 -67
  117. package/lib/sandbox/control/protocol.ts +35 -6
  118. package/lib/sandbox/control/server.ts +184 -24
  119. package/lib/sandbox/control/state.ts +67 -33
  120. package/lib/sandbox/control/timing.ts +12 -0
  121. package/lib/sandbox/engine.ts +20 -9
  122. package/lib/sandbox/mounts.ts +8 -0
  123. package/lib/sandbox/recovery.ts +159 -49
  124. package/lib/sandbox/workspace-view.ts +73 -22
  125. package/lib/server/daemon.ts +1 -1
  126. package/lib/server/process-control.ts +13 -3
  127. package/lib/server/process-state.ts +129 -27
  128. package/lib/task/activity-intent.ts +4 -2
  129. package/lib/task/artifact-lifecycle.ts +73 -10
  130. package/lib/task/artifact-receipts.ts +161 -0
  131. package/lib/task/codex-orchestration.ts +91 -5
  132. package/lib/task/commands/validate.ts +1 -1
  133. package/lib/task/create.ts +11 -2
  134. package/lib/task/decision-intents.ts +4 -2
  135. package/lib/task/delegation-receipts.ts +205 -25
  136. package/lib/task/events.ts +132 -6
  137. package/lib/task/guard-override.ts +50 -0
  138. package/lib/task/human-override.ts +1503 -0
  139. package/lib/task/ledger-intents.ts +6 -3
  140. package/lib/task/ledger.ts +16 -1
  141. package/lib/task/lifecycle.ts +71 -14
  142. package/lib/task/orchestration.ts +443 -22
  143. package/lib/task/review-finalization.ts +6 -2
  144. package/lib/task/sections.ts +6 -2
  145. package/lib/task/task-execution-lock.ts +11 -9
  146. package/lib/task/verification-engine.ts +93 -32
  147. package/lib/task/verification.ts +15 -1
  148. package/lib/task/workflow-warning-intents.ts +4 -2
  149. package/lib/task/write.ts +4 -1
  150. package/package.json +1 -1
  151. package/templates/.agents/README.en.md +7 -0
  152. package/templates/.agents/README.zh-CN.md +5 -0
  153. package/templates/.agents/hooks/lifecycle-delegation.js +33 -4
  154. package/templates/.agents/rules/lifecycle-orchestration.en.md +10 -3
  155. package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +10 -3
  156. package/templates/.agents/rules/pr-checks-commands.github.en.md +1 -1
  157. package/templates/.agents/rules/pr-checks-commands.github.zh-CN.md +1 -1
  158. package/templates/.agents/rules/review-handshake.en.md +3 -1
  159. package/templates/.agents/rules/review-handshake.zh-CN.md +3 -1
  160. package/templates/.agents/skills/code-task/reference/dual-mode.en.md +3 -1
  161. package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +3 -1
  162. package/templates/.agents/skills/complete-task/SKILL.en.md +4 -0
  163. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +4 -0
  164. package/templates/.agents/skills/run-manual-validation/SKILL.en.md +6 -6
  165. package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +6 -6
  166. package/templates/.agents/skills/run-manual-validation/config/verify.en.json +14 -3
  167. package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +14 -3
  168. package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.en.md +67 -0
  169. package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.zh-CN.md +67 -0
  170. package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +25 -0
  171. package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +25 -0
  172. package/templates/.agents/skills/run-task/SKILL.en.md +9 -7
  173. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +9 -7
  174. package/templates/.agents/skills/run-task/reference/host-validation.en.md +5 -1
  175. package/templates/.agents/skills/run-task/reference/host-validation.zh-CN.md +5 -1
  176. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +75 -3
  177. package/templates/.agents/workspace/README.en.md +0 -26
  178. package/templates/.agents/workspace/README.zh-CN.md +0 -26
@@ -4,11 +4,16 @@ import { appendActivityEntry, locateActivityLog, pairEntries } from './activity-
4
4
  import {
5
5
  buildArtifactLinkSection,
6
6
  artifactName,
7
+ inspectArtifactDirectory,
8
+ parseReviewedInputReference,
9
+ parseCodePlanInputReference,
7
10
  parseArtifactName,
8
11
  resolveArtifactContext,
9
12
  validateCompletedArtifact
10
13
  } from './artifact-lifecycle.ts';
11
14
  import type { ArtifactContextResult, ArtifactErrorCode, ArtifactFamily, ArtifactIdentity } from './artifact-lifecycle.ts';
15
+ import { ArtifactReceiptError, sha256File, upsertArtifactReceipt } from './artifact-receipts.ts';
16
+ import type { ArtifactReceipt } from './artifact-receipts.ts';
12
17
  import { parseTypedTaskFrontmatter } from './frontmatter.ts';
13
18
  import {
14
19
  consumeImplementationInput,
@@ -27,6 +32,7 @@ import type { OrchestrationStageCompletion } from './orchestration.ts';
27
32
  import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.ts';
28
33
  import { captureTaskWriteMetadata, writeTask } from './write.ts';
29
34
  import type { TaskOperationSummary, TaskWriteErrorCode, TaskWriteOptions } from './write.ts';
35
+ import { allowsManualOverride } from './guard-override.ts';
30
36
 
31
37
  const eventCatalog = [
32
38
  'analyze.started', 'analyze.awaiting-input', 'analyze.completed',
@@ -48,6 +54,7 @@ type TaskEventErrorCode =
48
54
  | ArtifactErrorCode | TaskWriteErrorCode;
49
55
  type TaskEventRequest = {
50
56
  taskRef: string; event: TaskEventName | string; agent: string; dryRun?: boolean; orchestrated?: boolean;
57
+ overrideTicket?: string; overrideTarget?: string; overrideScope?: string;
51
58
  round?: number; question?: number; artifact?: string; fixFor?: string; implementationInput?: string;
52
59
  verdict?: Verdict; blockers?: number; major?: number; minor?: number;
53
60
  manualValidation?: number; filesModified?: number; testsPassed?: number;
@@ -56,6 +63,7 @@ type TaskEventRequest = {
56
63
  type TaskEventError = { code: TaskEventErrorCode; message: string };
57
64
  type TaskEventOptions = TaskWriteOptions & {
58
65
  commitOrchestrationCompletion?: (plan: OrchestrationStageCompletion) => void;
66
+ lockAlreadyHeld?: boolean;
59
67
  };
60
68
  type TaskEventResult = {
61
69
  status: 'planned' | 'applied' | 'no-op' | 'failed'; changed: boolean;
@@ -68,7 +76,7 @@ type TaskEventResult = {
68
76
  operations: readonly TaskOperationSummary[]; error: TaskEventError | null;
69
77
  };
70
78
 
71
- const BASE_FIELDS = new Set(['taskRef', 'event', 'agent', 'dryRun']);
79
+ const BASE_FIELDS = new Set(['taskRef', 'event', 'agent', 'dryRun', 'overrideTicket', 'overrideTarget', 'overrideScope']);
72
80
  const SCHEMAS: Record<TaskEventName, { required?: string[]; optional?: string[] }> = {
73
81
  'analyze.started': { optional: ['round'] },
74
82
  'analyze.awaiting-input': { required: ['question'] },
@@ -273,12 +281,87 @@ function openStartedIdentity(rows: ReturnType<typeof pairEntries>, family: Event
273
281
  return matches.length === 1 ? matches[0] : matches.length > 1 ? { conflict: true as const } : null;
274
282
  }
275
283
 
284
+ function reviewInputFamily(family: EventFamily): ArtifactFamily {
285
+ return family === 'review-analysis' ? 'analysis' : family === 'review-plan' ? 'plan' : 'code';
286
+ }
287
+
288
+ function buildCompletionReceipt(
289
+ taskDir: string,
290
+ family: EventFamily,
291
+ artifact: ArtifactIdentity,
292
+ completedAt: string,
293
+ frontmatter: Record<string, unknown>
294
+ ): { ok: true; receipt: ArtifactReceipt } | { ok: false; message: string } | null {
295
+ if (family === 'code') {
296
+ let content: string;
297
+ try { content = fs.readFileSync(artifact.path, 'utf8'); }
298
+ catch (error) { return { ok: false, message: `cannot read ${artifact.name}: ${String(error)}` }; }
299
+ const input = parseCodePlanInputReference(content);
300
+ if (!input) return { ok: false, message: `${artifact.name} does not reference a canonical plan artifact` };
301
+ const startedInput = typeof frontmatter.code_input_artifact === 'string' ? frontmatter.code_input_artifact : '';
302
+ const startedSha256 = typeof frontmatter.code_input_sha256 === 'string' ? frontmatter.code_input_sha256 : '';
303
+ if (!startedInput || !startedSha256) return { ok: false, message: 'code.started plan input context is missing' };
304
+ if (input.name !== startedInput) return { ok: false, message: `${artifact.name} plan input '${input.name}' does not match code.started input '${startedInput}'` };
305
+ const plan = inspectArtifactDirectory(taskDir, 'plan');
306
+ if (plan.status !== 'ready' || !plan.latest || plan.latest.name !== input.name) {
307
+ return { ok: false, message: `code input '${input.name}' is not the latest plan artifact` };
308
+ }
309
+ try {
310
+ const inputSha256 = sha256File(plan.latest.path);
311
+ if (inputSha256 !== startedSha256) return { ok: false, message: `code input ${input.name} changed after code.started` };
312
+ return {
313
+ ok: true,
314
+ receipt: {
315
+ event: 'code.completed', output: artifact.name, input: input.name,
316
+ inputSha256, completedAt
317
+ }
318
+ };
319
+ } catch (error) {
320
+ return { ok: false, message: `cannot hash code input plan: ${error instanceof Error ? error.message : String(error)}` };
321
+ }
322
+ }
323
+ if (!family.startsWith('review-')) return null;
324
+ const expectedFamily = reviewInputFamily(family);
325
+ let content: string;
326
+ try { content = fs.readFileSync(artifact.path, 'utf8'); }
327
+ catch (error) { return { ok: false, message: `cannot read ${artifact.name}: ${String(error)}` }; }
328
+ const input = parseReviewedInputReference(content, expectedFamily);
329
+ if (!input) return { ok: false, message: `${artifact.name} does not reference a canonical ${expectedFamily} artifact` };
330
+ const startedInput = typeof frontmatter.review_input_artifact === 'string' ? frontmatter.review_input_artifact : '';
331
+ const startedSha256 = typeof frontmatter.review_input_sha256 === 'string' ? frontmatter.review_input_sha256 : '';
332
+ if (!startedInput || !startedSha256) return { ok: false, message: 'review started input context is missing' };
333
+ if (input.name !== startedInput) return { ok: false, message: `${artifact.name} input '${input.name}' does not match review.started input '${startedInput}'` };
334
+ const current = inspectArtifactDirectory(taskDir, expectedFamily);
335
+ if (current.status !== 'ready' || !current.latest || current.latest.name !== input.name) {
336
+ return { ok: false, message: `review input '${input.name}' is not the latest ${expectedFamily} artifact` };
337
+ }
338
+ const event = family === 'review-analysis'
339
+ ? 'review-analysis.completed' as const
340
+ : family === 'review-plan' ? 'review-plan.completed' as const : 'review-code.completed' as const;
341
+ try {
342
+ const stat = fs.lstatSync(current.latest.path);
343
+ if (!stat.isFile() || stat.isSymbolicLink()) throw new Error('review input is not a regular file');
344
+ const inputSha256 = sha256File(current.latest.path);
345
+ if (inputSha256 !== startedSha256) return { ok: false, message: `review input ${input.name} changed after review.started` };
346
+ return {
347
+ ok: true,
348
+ receipt: {
349
+ event, output: artifact.name, input: input.name,
350
+ inputSha256, completedAt
351
+ }
352
+ };
353
+ } catch (error) {
354
+ return { ok: false, message: `cannot hash review input ${input.name}: ${error instanceof Error ? error.message : String(error)}` };
355
+ }
356
+ }
357
+
276
358
  function applyTaskEventUnlocked(request: TaskEventRequest, options: TaskEventOptions = {}): TaskEventResult {
277
359
  const invalid = validateTaskEventRequest(request);
278
360
  if (invalid) return failed(request, invalid);
279
361
  const resolved = resolveTaskRef(request.taskRef, { repoRoot: options.repoRoot });
280
362
  if (!resolved.ok) return failed(request, { code: resolved.code, message: resolved.message }, { taskId: resolved.taskId });
281
- if (resolved.state !== 'active') return failed(request, { code: 'TASK_STATE_MISMATCH', message: `task ${resolved.taskId} is ${resolved.state}, expected active` }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath });
363
+ const stateOverride = allowsManualOverride(options.manualOverride, 'task-event', 'TASK_STATE_MISMATCH');
364
+ if (resolved.state !== 'active' && !stateOverride) return failed(request, { code: 'TASK_STATE_MISMATCH', message: `task ${resolved.taskId} is ${resolved.state}, expected active` }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath });
282
365
  let content: string;
283
366
  try { content = fs.readFileSync(resolved.taskMdPath, 'utf8'); }
284
367
  catch (error) { return failed(request, { code: 'TASK_READ_FAILED', message: String(error) }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath }); }
@@ -327,7 +410,7 @@ function applyTaskEventUnlocked(request: TaskEventRequest, options: TaskEventOpt
327
410
  const done = completedRows.find((item) => item.note === eventIdentity.note);
328
411
  if (eventIdentity.phase === 'completed' && done) return successNoOp(normalized, resolved.taskId, resolved.taskMdPath, currentStep, eventIdentity, done.done, frontmatter, artifactContext);
329
412
  if (eventIdentity.phase === 'completed' && completedRows.length > 0 && openRows.length === 0) return failed(normalized, { code: 'EVENT_LOG_CONFLICT', message: 'event identity is already completed with a different payload' }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
330
- if (eventIdentity.phase === 'completed' && !row?.started) return failed(normalized, { code: 'EVENT_START_MISSING', message: 'completion requires one open matching started event' }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
413
+ if (eventIdentity.phase === 'completed' && !row?.started && !allowsManualOverride(options.manualOverride, 'task-event', 'EVENT_START_MISSING')) return failed(normalized, { code: 'EVENT_START_MISSING', message: 'completion requires one open matching started event' }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
331
414
  if (eventIdentity.phase === 'completed') {
332
415
  const validated = validateCompletedArtifact(resolved.taskDir, FAMILY[eventIdentity.family].artifact, normalized.artifact!, normalized.round);
333
416
  if (!validated.ok) return failed(normalized, validated.error, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
@@ -338,7 +421,7 @@ function applyTaskEventUnlocked(request: TaskEventRequest, options: TaskEventOpt
338
421
  return successNoOp(normalized, resolved.taskId, resolved.taskMdPath, currentStep, eventIdentity, existing.time, frontmatter, artifactContext);
339
422
  }
340
423
  const allowed = FAMILY[eventIdentity.family][eventIdentity.phase === 'started' ? 'started' : 'completed'];
341
- if (!(allowed as readonly string[]).includes(currentStep)) return failed(normalized, { code: 'EVENT_TRANSITION_INVALID', message: `${normalized.event} is not allowed from '${currentStep}'` }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
424
+ if (!(allowed as readonly string[]).includes(currentStep) && !allowsManualOverride(options.manualOverride, 'task-event', 'EVENT_TRANSITION_INVALID')) return failed(normalized, { code: 'EVENT_TRANSITION_INVALID', message: `${normalized.event} is not allowed from '${currentStep}'` }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
342
425
  const findingCountError = validateReviewFindingCounts(
343
426
  normalized,
344
427
  content,
@@ -371,18 +454,60 @@ function applyTaskEventUnlocked(request: TaskEventRequest, options: TaskEventOpt
371
454
  let metadata;
372
455
  try { metadata = (options.metadataProvider ?? captureTaskWriteMetadata)(); }
373
456
  catch (error) { return failed(normalized, { code: 'METADATA_CAPTURE_FAILED', message: String(error) }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath }); }
457
+ let completionReceipt: ArtifactReceipt | null = null;
458
+ if (eventIdentity.phase === 'completed' && completedArtifact) {
459
+ const receipt = buildCompletionReceipt(resolved.taskDir, eventIdentity.family, completedArtifact, metadata.timestamp, frontmatter);
460
+ if (receipt && !receipt.ok) {
461
+ return failed(normalized, { code: 'EVENT_ARTIFACT_CONFLICT', message: receipt.message }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
462
+ }
463
+ completionReceipt = receipt?.receipt ?? null;
464
+ }
374
465
  const step = eventIdentity.phase === 'started' || eventIdentity.target === null ? currentStep : eventIdentity.target;
375
466
  const logStep = eventIdentity.phase === 'started' ? `${eventIdentity.action} [started]` : eventIdentity.action;
376
467
  const body = appendActivityEntry(section, { time: metadata.timestamp, step: logStep, agent: normalized.agent, note: eventIdentity.note });
377
468
  const frontmatterSet: Record<string, string> = { current_step: step, assigned_to: normalized.agent };
469
+ let frontmatterRemove: string[] | undefined;
470
+ if (eventIdentity.phase === 'started' && eventIdentity.family === 'code') {
471
+ const planInput = artifactContext?.inputs.find((input) => input.family === 'plan');
472
+ if (!planInput) return failed(normalized, { code: 'EVENT_ARTIFACT_CONFLICT', message: 'code.started plan input context is unavailable' }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: step, action: eventIdentity.action, phase: eventIdentity.phase, artifactContext });
473
+ try {
474
+ frontmatterSet.code_input_artifact = planInput.name;
475
+ frontmatterSet.code_input_sha256 = sha256File(planInput.path);
476
+ } catch (error) {
477
+ return failed(normalized, { code: 'EVENT_ARTIFACT_CONFLICT', message: `cannot hash code.started plan input: ${error instanceof Error ? error.message : String(error)}` }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: step, action: eventIdentity.action, phase: eventIdentity.phase, artifactContext });
478
+ }
479
+ } else if (eventIdentity.phase === 'completed' && eventIdentity.family === 'code') {
480
+ frontmatterRemove = ['code_input_artifact', 'code_input_sha256'];
481
+ } else if (eventIdentity.phase === 'started' && eventIdentity.family.startsWith('review-')) {
482
+ const expectedFamily = reviewInputFamily(eventIdentity.family);
483
+ const input = artifactContext?.inputs.find((candidate) => candidate.family === expectedFamily);
484
+ if (!input) return failed(normalized, { code: 'EVENT_ARTIFACT_CONFLICT', message: `review.started ${expectedFamily} input context is unavailable` }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: step, action: eventIdentity.action, phase: eventIdentity.phase, artifactContext });
485
+ try {
486
+ frontmatterSet.review_input_artifact = input.name;
487
+ frontmatterSet.review_input_sha256 = sha256File(input.path);
488
+ } catch (error) {
489
+ return failed(normalized, { code: 'EVENT_ARTIFACT_CONFLICT', message: `cannot hash review.started input: ${error instanceof Error ? error.message : String(error)}` }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: step, action: eventIdentity.action, phase: eventIdentity.phase, artifactContext });
490
+ }
491
+ } else if (eventIdentity.phase === 'completed' && eventIdentity.family.startsWith('review-')) {
492
+ frontmatterRemove = ['review_input_artifact', 'review_input_sha256'];
493
+ }
378
494
  if (eventIdentity.phase === 'started' && normalized.implementationInput) frontmatterSet.last_reviewed_commit = '';
379
495
  const mutations: Parameters<typeof writeTask>[0]['mutations'][number][] = [
380
- { kind: 'frontmatter', set: frontmatterSet }
496
+ { kind: 'frontmatter', set: frontmatterSet, remove: frontmatterRemove }
381
497
  ];
382
498
  if (completedArtifact) {
383
499
  const link = buildArtifactLinkSection(content, completedArtifact);
384
500
  mutations.push({ kind: 'section', aliases: link.aliases, heading: link.heading, body: link.body });
385
501
  }
502
+ if (completionReceipt) {
503
+ try {
504
+ const receiptSection = upsertArtifactReceipt(content, completionReceipt);
505
+ mutations.push({ kind: 'section', aliases: receiptSection.aliases, heading: receiptSection.heading, body: receiptSection.body });
506
+ } catch (error) {
507
+ const message = error instanceof ArtifactReceiptError ? error.message : String(error);
508
+ return failed(normalized, { code: 'EVENT_ARTIFACT_CONFLICT', message }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
509
+ }
510
+ }
386
511
  if (eventIdentity.phase === 'completed' && normalized.implementationInput) {
387
512
  let implementationRows;
388
513
  try {
@@ -401,7 +526,7 @@ function applyTaskEventUnlocked(request: TaskEventRequest, options: TaskEventOpt
401
526
  });
402
527
  }
403
528
  mutations.push({ kind: 'section', aliases: ['活动日志', 'Activity Log'], heading: section.heading, body });
404
- const result = writeTask({ taskRef: normalized.taskRef, expectedState: 'active', dryRun: normalized.dryRun, mutations }, { ...options, metadataProvider: () => metadata });
529
+ const result = writeTask({ taskRef: normalized.taskRef, expectedState: stateOverride ? resolved.state : 'active', dryRun: normalized.dryRun, mutations }, { ...options, metadataProvider: () => metadata });
405
530
  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 });
406
531
  if (!normalized.dryRun && orchestrationCompletion) {
407
532
  try {
@@ -439,6 +564,7 @@ function applyTaskEvent(request: TaskEventRequest, options: TaskEventOptions = {
439
564
  const invalid = validateTaskEventRequest(request);
440
565
  if (invalid) return failed(request, invalid);
441
566
  const parts = eventParts(request.event);
567
+ if (options.lockAlreadyHeld) return applyTaskEventUnlocked(request, options);
442
568
  if (request.dryRun || parts.phase !== 'completed' || parts.family === 'manual-validation') {
443
569
  return applyTaskEventUnlocked(request, options);
444
570
  }
@@ -0,0 +1,50 @@
1
+ type ManualOverrideCapability = Readonly<{
2
+ failureId: string;
3
+ operator: string;
4
+ reason: string;
5
+ }>;
6
+
7
+ type GuardProducer = Readonly<{
8
+ producerId: string;
9
+ guardId: `G-${string}`;
10
+ code: string;
11
+ }>;
12
+
13
+ const guardProducerCatalog: readonly GuardProducer[] = [
14
+ { producerId: 'task.resolve', guardId: 'G-01', code: 'TASK_NOT_FOUND' },
15
+ { producerId: 'task.resolve', guardId: 'G-01', code: 'TASK_IDENTITY_CONFLICT' },
16
+ { producerId: 'task.write', guardId: 'G-02', code: 'TASK_STATE_MISMATCH' },
17
+ { producerId: 'task-event', guardId: 'G-04', code: 'TASK_STATE_MISMATCH' },
18
+ { producerId: 'task-event', guardId: 'G-04', code: 'EVENT_TRANSITION_INVALID' },
19
+ { producerId: 'task-event', guardId: 'G-04', code: 'EVENT_START_MISSING' },
20
+ { producerId: 'task-verify', guardId: 'G-05', code: 'VERIFY_TASK_STATE_MISMATCH' },
21
+ { producerId: 'task-verify', guardId: 'G-05', code: 'VERIFY_ARTIFACT_INVALID' },
22
+ { producerId: 'verification-engine', guardId: 'G-06', code: 'CHECK_FAILED' },
23
+ { producerId: 'verification-engine', guardId: 'G-06', code: 'CHECK_BLOCKED' },
24
+ { producerId: 'activity-intent', guardId: 'G-07', code: 'TASK_STATE_MISMATCH' },
25
+ { producerId: 'ledger-intent', guardId: 'G-07', code: 'TASK_STATE_MISMATCH' },
26
+ { producerId: 'decision-intent', guardId: 'G-07', code: 'TASK_STATE_MISMATCH' },
27
+ { producerId: 'workflow-warning', guardId: 'G-07', code: 'TASK_STATE_MISMATCH' },
28
+ { producerId: 'review-finalization', guardId: 'G-07', code: 'TASK_STATE_MISMATCH' },
29
+ { producerId: 'ledger-intent', guardId: 'G-08', code: 'LEDGER_TRANSITION_INVALID' },
30
+ { producerId: 'lifecycle-execution', guardId: 'G-09', code: 'ORCHESTRATION_STANDALONE_BUSY' },
31
+ { producerId: 'lifecycle-execution', guardId: 'G-09', code: 'ORCHESTRATION_PROVENANCE_MISMATCH' },
32
+ { producerId: 'lifecycle-execution', guardId: 'G-09', code: 'ORCHESTRATION_STATE_INVALID' },
33
+ { producerId: 'platform.issue', guardId: 'G-10', code: 'PLATFORM_BIND_FAILED' },
34
+ { producerId: 'platform.pull-request', guardId: 'G-10', code: 'PLATFORM_BIND_FAILED' }
35
+ ];
36
+
37
+ function guardFailureId(producerId: string, code: string): string {
38
+ return `${producerId}:${code}`;
39
+ }
40
+
41
+ function allowsManualOverride(
42
+ capability: ManualOverrideCapability | undefined,
43
+ producerId: string,
44
+ code: string
45
+ ): boolean {
46
+ return capability?.failureId === guardFailureId(producerId, code);
47
+ }
48
+
49
+ export { allowsManualOverride, guardFailureId, guardProducerCatalog };
50
+ export type { GuardProducer, ManualOverrideCapability };