@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
@@ -6,6 +6,7 @@ import { LEDGER_COLUMNS, LEDGER_HEADINGS, parseLedger } from './ledger.js';
6
6
  import { resolveTaskContext } from './resolve-ref.js';
7
7
  import { extractSection, extractSubSection, findSectionHeading } from './sections.js';
8
8
  import { captureTaskWriteMetadata, writeTask } from './write.js';
9
+ import { allowsManualOverride } from './guard-override.js';
9
10
  const DECISION_ALIASES = ['人工裁决', 'Human Rulings', 'Human Decisions', 'Human Decision'];
10
11
  const ACTIVITY_ALIASES = ['活动日志', 'Activity Log'];
11
12
  function failed(code, message, taskId = null, ledgerId = null) {
@@ -56,7 +57,8 @@ function applyHumanDecision(request, options = {}) {
56
57
  const resolved = resolveTaskContext(request.taskRef, { repoRoot: options.repoRoot });
57
58
  if (!resolved.ok)
58
59
  return failed(resolved.code, resolved.message, resolved.taskId);
59
- if (resolved.state !== 'active')
60
+ const stateOverride = allowsManualOverride(options.manualOverride, 'decision-intent', 'TASK_STATE_MISMATCH');
61
+ if (resolved.state !== 'active' && !stateOverride)
60
62
  return failed('TASK_STATE_MISMATCH', `task ${resolved.taskId} is ${resolved.state}, expected active`, resolved.taskId);
61
63
  let content;
62
64
  let rows;
@@ -155,7 +157,7 @@ function applyHumanDecision(request, options = {}) {
155
157
  kind: 'section', aliases: ACTIVITY_ALIASES, heading: activity.heading,
156
158
  body: appendActivityEntry(activity, { time: metadata.timestamp, step: 'Human Decision', agent: 'human', note: `${row.id} decided → ${recordId}` })
157
159
  });
158
- const writeResult = writeTask({ taskRef: resolved.taskId, expectedState: 'active', mutations, dryRun: request.dryRun }, {
160
+ const writeResult = writeTask({ taskRef: resolved.taskId, expectedState: stateOverride ? resolved.state : 'active', mutations, dryRun: request.dryRun }, {
159
161
  ...options,
160
162
  taskLocation: { repoRoot: resolved.repoRoot, taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, state: resolved.state },
161
163
  metadataProvider: () => metadata
@@ -10,7 +10,12 @@ function managedDelegationRole(nativeAgent) {
10
10
  function fail(code, message) {
11
11
  return { ok: false, code, message };
12
12
  }
13
+ // 空白串折叠为 null,保持"缺失即 null"的单一表示(Start/Stop 两处共用,避免任一入口把 " " 当成已观察到的值入账)
14
+ function foldBlankToNull(value) {
15
+ return value?.trim() ? value : null;
16
+ }
13
17
  function prepareDelegation(input, options = {}) {
18
+ const monotonic = (options.monotonicNow ?? (() => Number(process.hrtime.bigint() / 1000000n)))();
14
19
  return Object.freeze({
15
20
  ...input,
16
21
  id: (options.id ?? randomUUID)(),
@@ -27,43 +32,97 @@ function prepareDelegation(input, options = {}) {
27
32
  afterFingerprint: null,
28
33
  changedPaths: Object.freeze([]),
29
34
  createdAt: (options.now ?? (() => new Date().toISOString()))(),
35
+ preparedMonotonicMs: monotonic,
36
+ spawnDispatchMonotonicMs: null,
37
+ activationDeadlineMonotonicMs: null,
38
+ spawnDispatchedAt: null,
39
+ activationDeadlineAt: null,
40
+ startEvidenceMonotonicMs: null,
41
+ activatedMonotonicMs: null,
30
42
  activatedAt: null,
31
43
  sealedAt: null,
32
44
  consumedAt: null
33
45
  });
34
46
  }
47
+ function dispatchDelegation(receipt, options = {}) {
48
+ if (receipt.status !== 'prepared') {
49
+ return fail('DELEGATION_STATE_INVALID', `delegation ${receipt.id} is ${receipt.status}, expected prepared`);
50
+ }
51
+ if (receipt.spawnDispatchMonotonicMs != null || receipt.spawnDispatchedAt != null) {
52
+ return fail('DELEGATION_DISPATCH_REPLAY', `delegation ${receipt.id} was already dispatched`);
53
+ }
54
+ const monotonic = (options.monotonicNow ?? (() => Number(process.hrtime.bigint() / 1000000n)))();
55
+ const dispatchedAt = (options.now ?? (() => new Date().toISOString()))();
56
+ const window = options.activationWindowMs ?? 15_000;
57
+ return { ok: true, receipt: Object.freeze({
58
+ ...receipt,
59
+ spawnDispatchMonotonicMs: monotonic,
60
+ activationDeadlineMonotonicMs: monotonic + window,
61
+ spawnDispatchedAt: dispatchedAt,
62
+ activationDeadlineAt: new Date(Date.parse(dispatchedAt) + window).toISOString()
63
+ }) };
64
+ }
35
65
  function activateDelegation(receipt, event, options = {}) {
36
66
  const managedRole = managedDelegationRole(event.nativeAgent);
37
67
  if (!managedRole)
38
68
  return fail('DELEGATION_IGNORED', `subagent '${event.nativeAgent}' is not lifecycle-managed`);
39
69
  if (receipt.status !== 'prepared')
40
70
  return fail('DELEGATION_STATE_INVALID', `delegation ${receipt.id} is ${receipt.status}, expected prepared`);
71
+ if (receipt.spawnDispatchMonotonicMs == null
72
+ || receipt.spawnDispatchedAt == null
73
+ || receipt.activationDeadlineAt == null
74
+ || receipt.activationDeadlineMonotonicMs == null
75
+ || !Number.isFinite(Date.parse(receipt.spawnDispatchedAt))
76
+ || !Number.isFinite(Date.parse(receipt.activationDeadlineAt))) {
77
+ return fail('DELEGATION_NOT_DISPATCHED', `delegation ${receipt.id} has not reached the native spawn dispatch boundary`);
78
+ }
79
+ const monotonic = (options.monotonicNow ?? (() => Number(process.hrtime.bigint() / 1000000n)))();
80
+ const wallNow = Date.parse((options.now ?? (() => new Date().toISOString()))());
81
+ if (wallNow > Date.parse(receipt.activationDeadlineAt)) {
82
+ return fail('DELEGATION_ACTIVATION_TIMEOUT', `delegation ${receipt.id} activation evidence arrived after its deadline`);
83
+ }
41
84
  if (managedRole !== receipt.role)
42
85
  return fail('DELEGATION_ROLE_MISMATCH', `managed role ${managedRole} does not match ${receipt.role}`);
43
86
  if (!event.parentId || (receipt.parentId !== null && event.parentId !== receipt.parentId) || !event.childId || event.childId === event.parentId) {
44
87
  return fail('DELEGATION_IDENTITY_INVALID', 'native parent/child identity does not match the prepared delegation');
45
88
  }
46
- if (event.spawnMode !== 'fresh')
89
+ // spawnMode 的必需性按 client 判断:claude-code 宿主结构上不提供该字段,跳过;其余 client 维持硬校验
90
+ if (receipt.client !== 'claude-code' && event.spawnMode !== 'fresh') {
47
91
  return fail('DELEGATION_FORK_FORBIDDEN', `spawn mode '${event.spawnMode}' is not fresh`);
48
- const actualModel = event.actualModel;
49
- if (!actualModel || actualModel.trim() !== actualModel) {
50
- return fail('DELEGATION_MODEL_IDENTITY_MISSING', 'native start event must provide a non-empty actual model identity');
51
92
  }
52
- if (actualModel !== receipt.requestedModel && (!event.modelFallbackReason || event.modelFallbackReason.trim() === '')) {
53
- return fail('DELEGATION_MODEL_FALLBACK_UNRECORDED', 'actual model differs from requested model without a fallback reason');
93
+ // model/effort 维度:claude-code 如实记录、不做门禁;其余 client 维持现状硬校验(HDR-2)
94
+ const actualModel = foldBlankToNull(event.actualModel);
95
+ const actualReasoningEffort = foldBlankToNull(event.actualReasoningEffort);
96
+ if (receipt.client !== 'claude-code') {
97
+ if (!actualModel || actualModel.trim() !== actualModel) {
98
+ return fail('DELEGATION_MODEL_IDENTITY_MISSING', 'native start event must provide a non-empty actual model identity');
99
+ }
54
100
  }
55
- const actualReasoningEffort = event.actualReasoningEffort;
56
- if (!actualReasoningEffort || actualReasoningEffort.trim() !== actualReasoningEffort) {
57
- return fail('DELEGATION_REASONING_EFFORT_MISSING', 'native start event must provide a non-empty actual reasoning effort');
101
+ if (actualModel !== null && actualModel !== receipt.requestedModel) {
102
+ if (receipt.client !== 'claude-code' && (!event.modelFallbackReason || event.modelFallbackReason.trim() === '')) {
103
+ return fail('DELEGATION_MODEL_FALLBACK_UNRECORDED', 'actual model differs from requested model without a fallback reason');
104
+ }
58
105
  }
59
- if (actualReasoningEffort !== receipt.requestedReasoningEffort
60
- && (!event.reasoningEffortFallbackReason || event.reasoningEffortFallbackReason.trim() === '')) {
61
- return fail('DELEGATION_REASONING_EFFORT_FALLBACK_UNRECORDED', 'actual reasoning effort differs from requested effort without a fallback reason');
106
+ if (receipt.client !== 'claude-code') {
107
+ if (!actualReasoningEffort || actualReasoningEffort.trim() !== actualReasoningEffort) {
108
+ return fail('DELEGATION_REASONING_EFFORT_MISSING', 'native start event must provide a non-empty actual reasoning effort');
109
+ }
62
110
  }
63
- if (actualModel === receipt.requestedModel && event.modelFallbackReason) {
111
+ if (receipt.client === 'codex'
112
+ && event.hostEvidence?.kind !== 'codex-lifecycle-v2')
113
+ return fail('DELEGATION_HOST_EVIDENCE_REQUIRED', 'Codex activation requires trusted lifecycle-v2 host evidence');
114
+ if (actualReasoningEffort !== null && actualReasoningEffort !== receipt.requestedReasoningEffort) {
115
+ if (receipt.client !== 'claude-code' && (!event.reasoningEffortFallbackReason || event.reasoningEffortFallbackReason.trim() === '')) {
116
+ return fail('DELEGATION_REASONING_EFFORT_FALLBACK_UNRECORDED', 'actual reasoning effort differs from requested effort without a fallback reason');
117
+ }
118
+ }
119
+ // "无关 fallback 理由"检查对所有 client 保持不变——防止编造理由的红线,不属于 HDR-2 放宽范围
120
+ // actualModel/actualReasoningEffort 为 null(未观察到)时同样没有"实际不同"这回事,理由一样是无关的
121
+ if ((actualModel === null || actualModel === receipt.requestedModel) && event.modelFallbackReason) {
64
122
  return fail('DELEGATION_MODEL_FALLBACK_INVALID', 'model fallback reason is only valid when actual model differs');
65
123
  }
66
- if (actualReasoningEffort === receipt.requestedReasoningEffort && event.reasoningEffortFallbackReason) {
124
+ if ((actualReasoningEffort === null || actualReasoningEffort === receipt.requestedReasoningEffort)
125
+ && event.reasoningEffortFallbackReason) {
67
126
  return fail('DELEGATION_REASONING_EFFORT_FALLBACK_INVALID', 'reasoning-effort fallback reason is only valid when actual effort differs');
68
127
  }
69
128
  if (event.hostEvidence
@@ -72,12 +131,38 @@ function activateDelegation(receipt, event, options = {}) {
72
131
  || !Number.isSafeInteger(event.hostEvidence.startRevision)
73
132
  || event.hostEvidence.startRevision < 1))
74
133
  return fail('DELEGATION_HOST_EVIDENCE_INVALID', 'Codex start evidence reference is invalid');
134
+ if (event.hostEvidence?.kind === 'codex-lifecycle-v2') {
135
+ const expected = receipt.lifecycleProvenance;
136
+ const spawnObservedAt = Date.parse(event.hostEvidence.spawnObservedAt ?? '');
137
+ const spawnDispatchedAt = Date.parse(receipt.spawnDispatchedAt);
138
+ const activationDeadlineAt = Date.parse(receipt.activationDeadlineAt);
139
+ if (!expected
140
+ || event.hostEvidence.protocolVersion !== expected.protocolVersion
141
+ || event.hostEvidence.packageVersion !== expected.packageVersion
142
+ || event.hostEvidence.internalExecutableBuildHash !== expected.internalExecutableBuildHash
143
+ || event.hostEvidence.lifecycleContractHash !== expected.lifecycleContractHash
144
+ || event.hostEvidence.hookDefinitionHash !== expected.hookDefinitionHash
145
+ || event.hostEvidence.hookSource !== expected.hookSource
146
+ || event.hostEvidence.hookSourcePathDigest !== expected.hookSourcePathDigest
147
+ || event.hostEvidence.hookSourceHash !== expected.hookSourceHash
148
+ || event.hostEvidence.capabilitySessionId !== expected.capabilitySessionId
149
+ || event.hostEvidence.capabilityTurnId !== expected.capabilityTurnId
150
+ || !event.hostEvidence.spawnToolUseId?.trim()
151
+ || event.hostEvidence.spawnToolUseId === expected.capabilityToolUseId
152
+ || !Number.isFinite(spawnObservedAt)
153
+ || spawnObservedAt < spawnDispatchedAt
154
+ || spawnObservedAt > activationDeadlineAt
155
+ || event.parentId !== expected.capabilitySessionId
156
+ || (event.hostEvidence.controllerInstanceDigest ?? null) !== expected.controllerInstanceDigest
157
+ || (event.hostEvidence.controlGeneration ?? null) !== expected.controlGeneration)
158
+ return fail('DELEGATION_HOST_EVIDENCE_INVALID', 'Codex lifecycle provenance does not match the prepared receipt');
159
+ }
75
160
  return { ok: true, receipt: Object.freeze({
76
161
  ...receipt,
77
162
  status: 'activated',
78
163
  parentId: event.parentId,
79
164
  childId: event.childId,
80
- spawnMode: event.spawnMode,
165
+ spawnMode: event.spawnMode ?? null,
81
166
  actualModel,
82
167
  actualReasoningEffort,
83
168
  modelFallbackReason: event.modelFallbackReason ?? null,
@@ -88,9 +173,20 @@ function activateDelegation(receipt, event, options = {}) {
88
173
  consumer: null,
89
174
  consumedAt: null
90
175
  }) : receipt.hostEvidence ?? null,
91
- activatedAt: (options.now ?? (() => new Date().toISOString()))()
176
+ startEvidenceMonotonicMs: monotonic,
177
+ activatedMonotonicMs: (options.monotonicNow ?? (() => Number(process.hrtime.bigint() / 1000000n)))(),
178
+ activatedAt: new Date(wallNow).toISOString()
92
179
  }) };
93
180
  }
181
+ function abortPreparedDelegation(receipt) {
182
+ if (receipt.status !== 'prepared') {
183
+ return fail('DELEGATION_STATE_INVALID', `delegation ${receipt.id} is ${receipt.status}, expected prepared`);
184
+ }
185
+ return {
186
+ ok: true,
187
+ receipt: Object.freeze({ ...receipt, status: 'aborted' })
188
+ };
189
+ }
94
190
  function completeDelegationStage(receipt, event) {
95
191
  if (receipt.status !== 'activated')
96
192
  return fail('DELEGATION_STATE_INVALID', `delegation ${receipt.id} is ${receipt.status}, expected activated`);
@@ -105,6 +201,9 @@ function completeDelegationStage(receipt, event) {
105
201
  function sealDelegation(receipt, event, options = {}) {
106
202
  if (receipt.status !== 'stage-completed')
107
203
  return fail('DELEGATION_STATE_INVALID', `delegation ${receipt.id} is ${receipt.status}, expected stage-completed`);
204
+ if (receipt.client === 'codex' && options.requireHostEvidence !== false && !event.hostEvidence) {
205
+ return fail('DELEGATION_HOST_EVIDENCE_REQUIRED', 'Codex sealing requires consumed lifecycle-v2 host evidence');
206
+ }
108
207
  if (event.childId !== receipt.childId || event.exitCode !== 0)
109
208
  return fail('DELEGATION_STOP_INVALID', 'native stop identity or exit status is invalid');
110
209
  if (receipt.role === 'reviewer') {
@@ -131,6 +230,10 @@ function sealDelegation(receipt, event, options = {}) {
131
230
  status: 'sealed',
132
231
  afterFingerprint: event.afterFingerprint,
133
232
  changedPaths: Object.freeze([...event.changedPaths]),
233
+ actualModel: receipt.actualModel ?? foldBlankToNull(event.actualModel),
234
+ actualReasoningEffort: receipt.actualReasoningEffort ?? foldBlankToNull(event.actualReasoningEffort),
235
+ modelFallbackReason: receipt.modelFallbackReason ?? event.modelFallbackReason ?? null,
236
+ reasoningEffortFallbackReason: receipt.reasoningEffortFallbackReason ?? event.reasoningEffortFallbackReason ?? null,
134
237
  hostEvidence: event.hostEvidence && receipt.hostEvidence ? Object.freeze({
135
238
  ...receipt.hostEvidence,
136
239
  ...event.hostEvidence
@@ -149,5 +252,5 @@ function consumeDelegation(receipt, options = {}) {
149
252
  consumedAt: (options.now ?? (() => new Date().toISOString()))()
150
253
  }) };
151
254
  }
152
- export { activateDelegation, completeDelegationStage, consumeDelegation, managedDelegationRole, prepareDelegation, sealDelegation };
255
+ export { activateDelegation, abortPreparedDelegation, completeDelegationStage, consumeDelegation, dispatchDelegation, foldBlankToNull, managedDelegationRole, prepareDelegation, sealDelegation };
153
256
  //# sourceMappingURL=delegation-receipts.js.map
@@ -1,6 +1,7 @@
1
1
  import fs from 'node:fs';
2
2
  import { appendActivityEntry, locateActivityLog, pairEntries } from './activity-log.js';
3
- import { buildArtifactLinkSection, artifactName, parseArtifactName, resolveArtifactContext, validateCompletedArtifact } from './artifact-lifecycle.js';
3
+ import { buildArtifactLinkSection, artifactName, inspectArtifactDirectory, parseReviewedInputReference, parseCodePlanInputReference, parseArtifactName, resolveArtifactContext, validateCompletedArtifact } from './artifact-lifecycle.js';
4
+ import { ArtifactReceiptError, sha256File, upsertArtifactReceipt } from './artifact-receipts.js';
4
5
  import { parseTypedTaskFrontmatter } from './frontmatter.js';
5
6
  import { consumeImplementationInput, IMPLEMENTATION_INPUT_ALIASES, parseImplementationInputs, renderImplementationInputs } from './implementation-inputs.js';
6
7
  import { parseLedger, summarizeLedgerStage, validateLedgerRows } from './ledger.js';
@@ -11,6 +12,7 @@ import { validateLifecycleExecution } from './lifecycle-execution.js';
11
12
  import { commitOrchestrationStageCompletion } from './orchestration.js';
12
13
  import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.js';
13
14
  import { captureTaskWriteMetadata, writeTask } from './write.js';
15
+ import { allowsManualOverride } from './guard-override.js';
14
16
  const eventCatalog = [
15
17
  'analyze.started', 'analyze.awaiting-input', 'analyze.completed',
16
18
  'review-analysis.started', 'review-analysis.completed',
@@ -21,7 +23,7 @@ const eventCatalog = [
21
23
  'manual-validation.started', 'manual-validation.completed',
22
24
  'validation-run.started', 'validation-run.completed'
23
25
  ];
24
- const BASE_FIELDS = new Set(['taskRef', 'event', 'agent', 'dryRun']);
26
+ const BASE_FIELDS = new Set(['taskRef', 'event', 'agent', 'dryRun', 'overrideTicket', 'overrideTarget', 'overrideScope']);
25
27
  const SCHEMAS = {
26
28
  'analyze.started': { optional: ['round'] },
27
29
  'analyze.awaiting-input': { required: ['question'] },
@@ -237,6 +239,92 @@ function openStartedIdentity(rows, family) {
237
239
  });
238
240
  return matches.length === 1 ? matches[0] : matches.length > 1 ? { conflict: true } : null;
239
241
  }
242
+ function reviewInputFamily(family) {
243
+ return family === 'review-analysis' ? 'analysis' : family === 'review-plan' ? 'plan' : 'code';
244
+ }
245
+ function buildCompletionReceipt(taskDir, family, artifact, completedAt, frontmatter) {
246
+ if (family === 'code') {
247
+ let content;
248
+ try {
249
+ content = fs.readFileSync(artifact.path, 'utf8');
250
+ }
251
+ catch (error) {
252
+ return { ok: false, message: `cannot read ${artifact.name}: ${String(error)}` };
253
+ }
254
+ const input = parseCodePlanInputReference(content);
255
+ if (!input)
256
+ return { ok: false, message: `${artifact.name} does not reference a canonical plan artifact` };
257
+ const startedInput = typeof frontmatter.code_input_artifact === 'string' ? frontmatter.code_input_artifact : '';
258
+ const startedSha256 = typeof frontmatter.code_input_sha256 === 'string' ? frontmatter.code_input_sha256 : '';
259
+ if (!startedInput || !startedSha256)
260
+ return { ok: false, message: 'code.started plan input context is missing' };
261
+ if (input.name !== startedInput)
262
+ return { ok: false, message: `${artifact.name} plan input '${input.name}' does not match code.started input '${startedInput}'` };
263
+ const plan = inspectArtifactDirectory(taskDir, 'plan');
264
+ if (plan.status !== 'ready' || !plan.latest || plan.latest.name !== input.name) {
265
+ return { ok: false, message: `code input '${input.name}' is not the latest plan artifact` };
266
+ }
267
+ try {
268
+ const inputSha256 = sha256File(plan.latest.path);
269
+ if (inputSha256 !== startedSha256)
270
+ return { ok: false, message: `code input ${input.name} changed after code.started` };
271
+ return {
272
+ ok: true,
273
+ receipt: {
274
+ event: 'code.completed', output: artifact.name, input: input.name,
275
+ inputSha256, completedAt
276
+ }
277
+ };
278
+ }
279
+ catch (error) {
280
+ return { ok: false, message: `cannot hash code input plan: ${error instanceof Error ? error.message : String(error)}` };
281
+ }
282
+ }
283
+ if (!family.startsWith('review-'))
284
+ return null;
285
+ const expectedFamily = reviewInputFamily(family);
286
+ let content;
287
+ try {
288
+ content = fs.readFileSync(artifact.path, 'utf8');
289
+ }
290
+ catch (error) {
291
+ return { ok: false, message: `cannot read ${artifact.name}: ${String(error)}` };
292
+ }
293
+ const input = parseReviewedInputReference(content, expectedFamily);
294
+ if (!input)
295
+ return { ok: false, message: `${artifact.name} does not reference a canonical ${expectedFamily} artifact` };
296
+ const startedInput = typeof frontmatter.review_input_artifact === 'string' ? frontmatter.review_input_artifact : '';
297
+ const startedSha256 = typeof frontmatter.review_input_sha256 === 'string' ? frontmatter.review_input_sha256 : '';
298
+ if (!startedInput || !startedSha256)
299
+ return { ok: false, message: 'review started input context is missing' };
300
+ if (input.name !== startedInput)
301
+ return { ok: false, message: `${artifact.name} input '${input.name}' does not match review.started input '${startedInput}'` };
302
+ const current = inspectArtifactDirectory(taskDir, expectedFamily);
303
+ if (current.status !== 'ready' || !current.latest || current.latest.name !== input.name) {
304
+ return { ok: false, message: `review input '${input.name}' is not the latest ${expectedFamily} artifact` };
305
+ }
306
+ const event = family === 'review-analysis'
307
+ ? 'review-analysis.completed'
308
+ : family === 'review-plan' ? 'review-plan.completed' : 'review-code.completed';
309
+ try {
310
+ const stat = fs.lstatSync(current.latest.path);
311
+ if (!stat.isFile() || stat.isSymbolicLink())
312
+ throw new Error('review input is not a regular file');
313
+ const inputSha256 = sha256File(current.latest.path);
314
+ if (inputSha256 !== startedSha256)
315
+ return { ok: false, message: `review input ${input.name} changed after review.started` };
316
+ return {
317
+ ok: true,
318
+ receipt: {
319
+ event, output: artifact.name, input: input.name,
320
+ inputSha256, completedAt
321
+ }
322
+ };
323
+ }
324
+ catch (error) {
325
+ return { ok: false, message: `cannot hash review input ${input.name}: ${error instanceof Error ? error.message : String(error)}` };
326
+ }
327
+ }
240
328
  function applyTaskEventUnlocked(request, options = {}) {
241
329
  const invalid = validateTaskEventRequest(request);
242
330
  if (invalid)
@@ -244,7 +332,8 @@ function applyTaskEventUnlocked(request, options = {}) {
244
332
  const resolved = resolveTaskRef(request.taskRef, { repoRoot: options.repoRoot });
245
333
  if (!resolved.ok)
246
334
  return failed(request, { code: resolved.code, message: resolved.message }, { taskId: resolved.taskId });
247
- if (resolved.state !== 'active')
335
+ const stateOverride = allowsManualOverride(options.manualOverride, 'task-event', 'TASK_STATE_MISMATCH');
336
+ if (resolved.state !== 'active' && !stateOverride)
248
337
  return failed(request, { code: 'TASK_STATE_MISMATCH', message: `task ${resolved.taskId} is ${resolved.state}, expected active` }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath });
249
338
  let content;
250
339
  try {
@@ -316,7 +405,7 @@ function applyTaskEventUnlocked(request, options = {}) {
316
405
  return successNoOp(normalized, resolved.taskId, resolved.taskMdPath, currentStep, eventIdentity, done.done, frontmatter, artifactContext);
317
406
  if (eventIdentity.phase === 'completed' && completedRows.length > 0 && openRows.length === 0)
318
407
  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 });
319
- if (eventIdentity.phase === 'completed' && !row?.started)
408
+ if (eventIdentity.phase === 'completed' && !row?.started && !allowsManualOverride(options.manualOverride, 'task-event', 'EVENT_START_MISSING'))
320
409
  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 });
321
410
  if (eventIdentity.phase === 'completed') {
322
411
  const validated = validateCompletedArtifact(resolved.taskDir, FAMILY[eventIdentity.family].artifact, normalized.artifact, normalized.round);
@@ -329,7 +418,7 @@ function applyTaskEventUnlocked(request, options = {}) {
329
418
  return successNoOp(normalized, resolved.taskId, resolved.taskMdPath, currentStep, eventIdentity, existing.time, frontmatter, artifactContext);
330
419
  }
331
420
  const allowed = FAMILY[eventIdentity.family][eventIdentity.phase === 'started' ? 'started' : 'completed'];
332
- if (!allowed.includes(currentStep))
421
+ if (!allowed.includes(currentStep) && !allowsManualOverride(options.manualOverride, 'task-event', 'EVENT_TRANSITION_INVALID'))
333
422
  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 });
334
423
  const findingCountError = validateReviewFindingCounts(normalized, content, eventIdentity.family, completedArtifact?.path ?? null);
335
424
  if (findingCountError)
@@ -363,19 +452,69 @@ function applyTaskEventUnlocked(request, options = {}) {
363
452
  catch (error) {
364
453
  return failed(normalized, { code: 'METADATA_CAPTURE_FAILED', message: String(error) }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath });
365
454
  }
455
+ let completionReceipt = null;
456
+ if (eventIdentity.phase === 'completed' && completedArtifact) {
457
+ const receipt = buildCompletionReceipt(resolved.taskDir, eventIdentity.family, completedArtifact, metadata.timestamp, frontmatter);
458
+ if (receipt && !receipt.ok) {
459
+ 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 });
460
+ }
461
+ completionReceipt = receipt?.receipt ?? null;
462
+ }
366
463
  const step = eventIdentity.phase === 'started' || eventIdentity.target === null ? currentStep : eventIdentity.target;
367
464
  const logStep = eventIdentity.phase === 'started' ? `${eventIdentity.action} [started]` : eventIdentity.action;
368
465
  const body = appendActivityEntry(section, { time: metadata.timestamp, step: logStep, agent: normalized.agent, note: eventIdentity.note });
369
466
  const frontmatterSet = { current_step: step, assigned_to: normalized.agent };
467
+ let frontmatterRemove;
468
+ if (eventIdentity.phase === 'started' && eventIdentity.family === 'code') {
469
+ const planInput = artifactContext?.inputs.find((input) => input.family === 'plan');
470
+ if (!planInput)
471
+ 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 });
472
+ try {
473
+ frontmatterSet.code_input_artifact = planInput.name;
474
+ frontmatterSet.code_input_sha256 = sha256File(planInput.path);
475
+ }
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
+ }
480
+ else if (eventIdentity.phase === 'completed' && eventIdentity.family === 'code') {
481
+ frontmatterRemove = ['code_input_artifact', 'code_input_sha256'];
482
+ }
483
+ else if (eventIdentity.phase === 'started' && eventIdentity.family.startsWith('review-')) {
484
+ const expectedFamily = reviewInputFamily(eventIdentity.family);
485
+ const input = artifactContext?.inputs.find((candidate) => candidate.family === expectedFamily);
486
+ if (!input)
487
+ 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 });
488
+ try {
489
+ frontmatterSet.review_input_artifact = input.name;
490
+ frontmatterSet.review_input_sha256 = sha256File(input.path);
491
+ }
492
+ catch (error) {
493
+ 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 });
494
+ }
495
+ }
496
+ else if (eventIdentity.phase === 'completed' && eventIdentity.family.startsWith('review-')) {
497
+ frontmatterRemove = ['review_input_artifact', 'review_input_sha256'];
498
+ }
370
499
  if (eventIdentity.phase === 'started' && normalized.implementationInput)
371
500
  frontmatterSet.last_reviewed_commit = '';
372
501
  const mutations = [
373
- { kind: 'frontmatter', set: frontmatterSet }
502
+ { kind: 'frontmatter', set: frontmatterSet, remove: frontmatterRemove }
374
503
  ];
375
504
  if (completedArtifact) {
376
505
  const link = buildArtifactLinkSection(content, completedArtifact);
377
506
  mutations.push({ kind: 'section', aliases: link.aliases, heading: link.heading, body: link.body });
378
507
  }
508
+ if (completionReceipt) {
509
+ try {
510
+ const receiptSection = upsertArtifactReceipt(content, completionReceipt);
511
+ mutations.push({ kind: 'section', aliases: receiptSection.aliases, heading: receiptSection.heading, body: receiptSection.body });
512
+ }
513
+ catch (error) {
514
+ const message = error instanceof ArtifactReceiptError ? error.message : String(error);
515
+ return failed(normalized, { code: 'EVENT_ARTIFACT_CONFLICT', message }, { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, fromStep: currentStep, toStep: currentStep, action: eventIdentity.action, phase: eventIdentity.phase });
516
+ }
517
+ }
379
518
  if (eventIdentity.phase === 'completed' && normalized.implementationInput) {
380
519
  let implementationRows;
381
520
  try {
@@ -391,7 +530,7 @@ function applyTaskEventUnlocked(request, options = {}) {
391
530
  });
392
531
  }
393
532
  mutations.push({ kind: 'section', aliases: ['活动日志', 'Activity Log'], heading: section.heading, body });
394
- const result = writeTask({ taskRef: normalized.taskRef, expectedState: 'active', dryRun: normalized.dryRun, mutations }, { ...options, metadataProvider: () => metadata });
533
+ const result = writeTask({ taskRef: normalized.taskRef, expectedState: stateOverride ? resolved.state : 'active', dryRun: normalized.dryRun, mutations }, { ...options, metadataProvider: () => metadata });
395
534
  if (result.status === 'failed')
396
535
  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 });
397
536
  if (!normalized.dryRun && orchestrationCompletion) {
@@ -431,6 +570,8 @@ function applyTaskEvent(request, options = {}) {
431
570
  if (invalid)
432
571
  return failed(request, invalid);
433
572
  const parts = eventParts(request.event);
573
+ if (options.lockAlreadyHeld)
574
+ return applyTaskEventUnlocked(request, options);
434
575
  if (request.dryRun || parts.phase !== 'completed' || parts.family === 'manual-validation') {
435
576
  return applyTaskEventUnlocked(request, options);
436
577
  }
@@ -0,0 +1,31 @@
1
+ const guardProducerCatalog = [
2
+ { producerId: 'task.resolve', guardId: 'G-01', code: 'TASK_NOT_FOUND' },
3
+ { producerId: 'task.resolve', guardId: 'G-01', code: 'TASK_IDENTITY_CONFLICT' },
4
+ { producerId: 'task.write', guardId: 'G-02', code: 'TASK_STATE_MISMATCH' },
5
+ { producerId: 'task-event', guardId: 'G-04', code: 'TASK_STATE_MISMATCH' },
6
+ { producerId: 'task-event', guardId: 'G-04', code: 'EVENT_TRANSITION_INVALID' },
7
+ { producerId: 'task-event', guardId: 'G-04', code: 'EVENT_START_MISSING' },
8
+ { producerId: 'task-verify', guardId: 'G-05', code: 'VERIFY_TASK_STATE_MISMATCH' },
9
+ { producerId: 'task-verify', guardId: 'G-05', code: 'VERIFY_ARTIFACT_INVALID' },
10
+ { producerId: 'verification-engine', guardId: 'G-06', code: 'CHECK_FAILED' },
11
+ { producerId: 'verification-engine', guardId: 'G-06', code: 'CHECK_BLOCKED' },
12
+ { producerId: 'activity-intent', guardId: 'G-07', code: 'TASK_STATE_MISMATCH' },
13
+ { producerId: 'ledger-intent', guardId: 'G-07', code: 'TASK_STATE_MISMATCH' },
14
+ { producerId: 'decision-intent', guardId: 'G-07', code: 'TASK_STATE_MISMATCH' },
15
+ { producerId: 'workflow-warning', guardId: 'G-07', code: 'TASK_STATE_MISMATCH' },
16
+ { producerId: 'review-finalization', guardId: 'G-07', code: 'TASK_STATE_MISMATCH' },
17
+ { producerId: 'ledger-intent', guardId: 'G-08', code: 'LEDGER_TRANSITION_INVALID' },
18
+ { producerId: 'lifecycle-execution', guardId: 'G-09', code: 'ORCHESTRATION_STANDALONE_BUSY' },
19
+ { producerId: 'lifecycle-execution', guardId: 'G-09', code: 'ORCHESTRATION_PROVENANCE_MISMATCH' },
20
+ { producerId: 'lifecycle-execution', guardId: 'G-09', code: 'ORCHESTRATION_STATE_INVALID' },
21
+ { producerId: 'platform.issue', guardId: 'G-10', code: 'PLATFORM_BIND_FAILED' },
22
+ { producerId: 'platform.pull-request', guardId: 'G-10', code: 'PLATFORM_BIND_FAILED' }
23
+ ];
24
+ function guardFailureId(producerId, code) {
25
+ return `${producerId}:${code}`;
26
+ }
27
+ function allowsManualOverride(capability, producerId, code) {
28
+ return capability?.failureId === guardFailureId(producerId, code);
29
+ }
30
+ export { allowsManualOverride, guardFailureId, guardProducerCatalog };
31
+ //# sourceMappingURL=guard-override.js.map