@fitlab-ai/agent-infra 0.9.7 → 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 (96) hide show
  1. package/dist/bin/internal-cli.js +5 -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 +2 -1
  4. package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +2 -1
  5. package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +30 -2
  6. package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +2 -1
  7. package/dist/lib/decide.js +68 -14
  8. package/dist/lib/init.js +1 -1
  9. package/dist/lib/internal/release-workflow.js +1 -3
  10. package/dist/lib/internal/task-activity.js +57 -7
  11. package/dist/lib/internal/task-event.js +57 -4
  12. package/dist/lib/internal/task-ledger.js +59 -5
  13. package/dist/lib/internal/task-lifecycle.js +59 -8
  14. package/dist/lib/internal/task-orchestration.js +11 -3
  15. package/dist/lib/internal/task-override.js +131 -0
  16. package/dist/lib/internal/task-review.js +64 -6
  17. package/dist/lib/internal/task-verify.js +65 -3
  18. package/dist/lib/internal/task-warning.js +57 -4
  19. package/dist/lib/platform/pr-summary.js +5 -3
  20. package/dist/lib/runtime/agent-runtime.js +56 -0
  21. package/dist/lib/sandbox/commands/create.js +389 -311
  22. package/dist/lib/sandbox/control/container-identity.js +1 -1
  23. package/dist/lib/sandbox/control/executor.js +10 -2
  24. package/dist/lib/sandbox/control/lifecycle.js +392 -72
  25. package/dist/lib/sandbox/control/state.js +27 -11
  26. package/dist/lib/sandbox/engine.js +14 -7
  27. package/dist/lib/sandbox/mounts.js +20 -5
  28. package/dist/lib/sandbox/recovery.js +78 -10
  29. package/dist/lib/sandbox/workspace-view.js +62 -40
  30. package/dist/lib/server/process-state.js +51 -1
  31. package/dist/lib/task/activity-intent.js +4 -1
  32. package/dist/lib/task/decision-intents.js +4 -2
  33. package/dist/lib/task/delegation-receipts.js +37 -16
  34. package/dist/lib/task/events.js +9 -5
  35. package/dist/lib/task/guard-override.js +31 -0
  36. package/dist/lib/task/human-override.js +1240 -0
  37. package/dist/lib/task/ledger-intents.js +6 -3
  38. package/dist/lib/task/lifecycle.js +69 -14
  39. package/dist/lib/task/orchestration.js +60 -6
  40. package/dist/lib/task/review-finalization.js +3 -2
  41. package/dist/lib/task/verification-engine.js +40 -14
  42. package/dist/lib/task/verification.js +13 -1
  43. package/dist/lib/task/workflow-warning-intents.js +4 -2
  44. package/dist/lib/task/write.js +2 -1
  45. package/dist/lifecycle-build-manifest.json +3 -3
  46. package/lib/agent-clients/adapters/claude-code.ts +3 -3
  47. package/lib/agent-clients/adapters/codex-lifecycle/app-server.ts +2 -1
  48. package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +2 -1
  49. package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +29 -2
  50. package/lib/agent-clients/adapters/codex-lifecycle/store.ts +2 -1
  51. package/lib/decide.ts +59 -14
  52. package/lib/init.ts +1 -1
  53. package/lib/internal/release-workflow.ts +1 -3
  54. package/lib/internal/task-activity.ts +46 -6
  55. package/lib/internal/task-event.ts +53 -4
  56. package/lib/internal/task-ledger.ts +48 -4
  57. package/lib/internal/task-lifecycle.ts +56 -12
  58. package/lib/internal/task-orchestration.ts +12 -4
  59. package/lib/internal/task-override.ts +138 -0
  60. package/lib/internal/task-review.ts +51 -6
  61. package/lib/internal/task-verify.ts +52 -3
  62. package/lib/internal/task-warning.ts +44 -3
  63. package/lib/platform/pr-summary.ts +10 -3
  64. package/lib/runtime/agent-runtime.ts +76 -0
  65. package/lib/sandbox/commands/create.ts +88 -4
  66. package/lib/sandbox/config.ts +2 -1
  67. package/lib/sandbox/control/container-identity.ts +3 -3
  68. package/lib/sandbox/control/executor.ts +10 -2
  69. package/lib/sandbox/control/lifecycle.ts +463 -76
  70. package/lib/sandbox/control/protocol.ts +5 -2
  71. package/lib/sandbox/control/state.ts +35 -18
  72. package/lib/sandbox/engine.ts +20 -9
  73. package/lib/sandbox/mounts.ts +23 -5
  74. package/lib/sandbox/recovery.ts +90 -9
  75. package/lib/sandbox/workspace-view.ts +70 -40
  76. package/lib/server/process-state.ts +48 -1
  77. package/lib/task/activity-intent.ts +4 -2
  78. package/lib/task/decision-intents.ts +4 -2
  79. package/lib/task/delegation-receipts.ts +46 -18
  80. package/lib/task/events.ts +10 -5
  81. package/lib/task/guard-override.ts +50 -0
  82. package/lib/task/human-override.ts +1503 -0
  83. package/lib/task/ledger-intents.ts +6 -3
  84. package/lib/task/lifecycle.ts +71 -14
  85. package/lib/task/orchestration.ts +89 -7
  86. package/lib/task/review-finalization.ts +6 -2
  87. package/lib/task/verification-engine.ts +42 -16
  88. package/lib/task/verification.ts +15 -1
  89. package/lib/task/workflow-warning-intents.ts +4 -2
  90. package/lib/task/write.ts +4 -1
  91. package/package.json +1 -1
  92. package/templates/.agents/hooks/lifecycle-delegation.js +8 -3
  93. package/templates/.agents/rules/lifecycle-orchestration.en.md +3 -2
  94. package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +3 -2
  95. package/templates/.agents/skills/run-task/reference/host-validation.en.md +3 -1
  96. package/templates/.agents/skills/run-task/reference/host-validation.zh-CN.md +3 -1
@@ -0,0 +1,1503 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { createHash, randomUUID } from 'node:crypto';
4
+
5
+ import { locateActivityLog } from './activity-log.ts';
6
+ import { applyPrReviewActivityIntent } from './activity-intent.ts';
7
+ import { applyHumanDecision } from './decision-intents.ts';
8
+ import { applyTaskEvent } from './events.ts';
9
+ import { parseTypedTaskFrontmatter } from './frontmatter.ts';
10
+ import { applyTaskLifecycle, lifecycleProducerCatalog } from './lifecycle.ts';
11
+ import type { TaskLifecycleIntent, TaskLifecycleRequest } from './lifecycle.ts';
12
+ import { guardFailureId, guardProducerCatalog } from './guard-override.ts';
13
+ import type { GuardProducer, ManualOverrideCapability } from './guard-override.ts';
14
+ import { inspectLifecycleExecution } from './lifecycle-execution.ts';
15
+ import { applyLedgerIntent } from './ledger-intents.ts';
16
+ import { bindPlatformIssue } from '../platform/issues.ts';
17
+ import { bindPlatformPullRequest } from '../platform/pull-requests.ts';
18
+ import type { GitHubClient } from '../platform/github-client.ts';
19
+ import { finalizeReviewSummary } from './review-finalization.ts';
20
+ import { resolveTaskRef } from './resolve-ref.ts';
21
+ import { DocumentMutationError, parseTable } from './sections.ts';
22
+ import { applyWorkflowWarningIntent } from './workflow-warning-intents.ts';
23
+ import { verifyTaskEvent } from './verification.ts';
24
+ import { verifyInProcess } from './verification-engine.ts';
25
+ import { canonicalTimestamp, writeTask } from './write.ts';
26
+ import type { TaskFileSystem, TaskWriteMetadata, TaskWriteResult } from './write.ts';
27
+
28
+ type Eligibility = 'eligible' | 'repair-only' | 'runtime-recovery-only' | 'never-overridable';
29
+ type OutcomeEffect = 'apply-target' | 'retry-same-intent' | 'record-only' | 'no-write';
30
+ type OutcomeResult = 'human-approved' | 'safe-closed' | 'recovery-required' | 'preserve-failure';
31
+
32
+ type FailureIdentity = Readonly<{
33
+ id: string;
34
+ producerId: string;
35
+ guardId: string;
36
+ code: string;
37
+ }>;
38
+
39
+ type OutcomeContext = Readonly<{
40
+ id: string;
41
+ facts: readonly string[];
42
+ }>;
43
+
44
+ type OutcomeRule = Readonly<{
45
+ target: string;
46
+ contextId: string;
47
+ effect: OutcomeEffect;
48
+ result: OutcomeResult;
49
+ residual: string;
50
+ }>;
51
+
52
+ type OutcomeFallback = Readonly<{
53
+ effect: 'no-write' | 'record-only';
54
+ result: 'recovery-required' | 'preserve-failure';
55
+ residual: string;
56
+ }>;
57
+
58
+ type FailurePolicy = FailureIdentity & Readonly<{
59
+ eligibility: Eligibility;
60
+ capability: string;
61
+ targets: readonly string[];
62
+ contexts: readonly OutcomeContext[];
63
+ targetContexts: Readonly<Record<string, readonly string[]>>;
64
+ outcomes: readonly OutcomeRule[];
65
+ fallback: OutcomeFallback;
66
+ probeId: string;
67
+ }>;
68
+
69
+ type Outcome = Readonly<{
70
+ effect: OutcomeEffect;
71
+ result: OutcomeResult;
72
+ residual: string;
73
+ policyId: string;
74
+ target: string;
75
+ contextId: string;
76
+ }>;
77
+
78
+ type HumanOverrideIdentity = Readonly<{
79
+ source: 'local-declared';
80
+ operator: string;
81
+ verified: false;
82
+ }>;
83
+
84
+ type HumanOverrideOptions = Readonly<{
85
+ repoRoot?: string;
86
+ now?: () => string;
87
+ randomId?: () => string;
88
+ metadataProvider?: () => TaskWriteMetadata;
89
+ randomSuffix?: () => string;
90
+ taskFileSystem?: Partial<TaskFileSystem>;
91
+ probeIntent?: TaskLifecycleIntent;
92
+ probeOperator?: string;
93
+ probeReason?: string;
94
+ probeAlertNumber?: number;
95
+ probeIssueNumber?: number;
96
+ probePullRequestNumber?: number;
97
+ probeStagingDir?: string;
98
+ platformClient?: GitHubClient;
99
+ effectExecutor?: (capability: ManualOverrideCapability) => OverrideError | null;
100
+ }>;
101
+
102
+ type FailureSnapshot = Readonly<{
103
+ schema: '1';
104
+ taskId: string;
105
+ failureId: string;
106
+ producerId: string;
107
+ code: string;
108
+ target: string;
109
+ state: string;
110
+ digest: string;
111
+ observedFacts: readonly string[];
112
+ message: string;
113
+ capturedAt: string;
114
+ source: 'producer-result';
115
+ }>;
116
+
117
+ type HumanOverrideRequest = Readonly<{
118
+ taskRef: string;
119
+ failureId: string;
120
+ target: string;
121
+ operator: string;
122
+ reason: string;
123
+ scope: string;
124
+ expiresAt: string;
125
+ intent?: TaskLifecycleIntent;
126
+ alertNumber?: number;
127
+ issueNumber?: number;
128
+ pullRequestNumber?: number;
129
+ stagingDir?: string;
130
+ failureDigest?: string;
131
+ failureSnapshot?: FailureSnapshot;
132
+ identitySource?: string;
133
+ }>;
134
+
135
+ type ConsumeHumanOverrideRequest = Readonly<{
136
+ taskRef: string;
137
+ ticketId: string;
138
+ failureId: string;
139
+ target: string;
140
+ scope: string;
141
+ intent?: TaskLifecycleIntent;
142
+ alertNumber?: number;
143
+ issueNumber?: number;
144
+ pullRequestNumber?: number;
145
+ stagingDir?: string;
146
+ failureDigest?: string;
147
+ observedFacts?: readonly string[];
148
+ failureSnapshot?: FailureSnapshot;
149
+ }>;
150
+
151
+ type OverrideError = Readonly<{ code: string; message: string }>;
152
+
153
+ type HumanOverrideIssueResult =
154
+ | Readonly<{
155
+ status: 'applied' | 'no-op';
156
+ changed: boolean;
157
+ ticketId: string;
158
+ taskId: string;
159
+ identity: HumanOverrideIdentity;
160
+ error: null;
161
+ }>
162
+ | Readonly<{
163
+ status: 'failed';
164
+ changed: false;
165
+ ticketId: string | null;
166
+ taskId: string | null;
167
+ identity: HumanOverrideIdentity | null;
168
+ error: OverrideError;
169
+ }>;
170
+
171
+ type HumanOverrideConsumeResult =
172
+ | Readonly<{
173
+ status: 'applied' | 'no-op';
174
+ changed: boolean;
175
+ ticketId: string;
176
+ taskId: string;
177
+ outcome: Outcome;
178
+ manualOverride?: ManualOverrideCapability;
179
+ error: null;
180
+ }>
181
+ | Readonly<{
182
+ status: 'failed';
183
+ changed: false;
184
+ ticketId: string | null;
185
+ taskId: string | null;
186
+ outcome: Outcome | null;
187
+ error: OverrideError;
188
+ }>;
189
+
190
+ const LIFECYCLE_PRODUCER = 'lifecycle.apply';
191
+ const HUMAN_OVERRIDE_SECTION_ALIASES = ['人工豁免账本', 'Human Override Ledger'] as const;
192
+ const HUMAN_OVERRIDE_COLUMNS = [
193
+ 'id', 'schema', 'failure_id', 'producer_id', 'code', 'target', 'operator',
194
+ 'identity_source', 'verified', 'reason', 'scope', 'intent', 'failure_message',
195
+ 'failure_digest', 'context_id', 'effect', 'source_state', 'issued_at', 'expires_at', 'status',
196
+ 'result', 'consumed_at', 'residual', 'alert_number', 'issue_number', 'pull_request_number', 'staging_dir'
197
+ ] as const;
198
+ const LEGACY_HUMAN_OVERRIDE_COLUMNS = HUMAN_OVERRIDE_COLUMNS.filter((column) => column !== 'pull_request_number');
199
+ const PULL_REQUEST_NUMBER_COLUMN = 'pull_request_number';
200
+
201
+ const SHORT_ID_CONFIRMED = 'identity-confirmed-and-safe-close-proven';
202
+ const SHORT_ID_UNCONFIRMED = 'identity-unconfirmed';
203
+ const REGISTRY_RECOVERABLE = 'registry-recoverable';
204
+
205
+ function failureId(producerId: string, code: string): string {
206
+ if (!singleLine(producerId) || !singleLine(code) || producerId.includes(':') || code.includes(':')) {
207
+ throw new Error('failure identity producer and code must be non-empty and colon-free');
208
+ }
209
+ return `${producerId}:${code}`;
210
+ }
211
+
212
+ function identity(producerId: string, code: string, guardId = code): FailureIdentity {
213
+ return { id: failureId(producerId, code), producerId, guardId, code };
214
+ }
215
+
216
+ function context(id: string, ...facts: string[]): OutcomeContext {
217
+ return { id, facts };
218
+ }
219
+
220
+ function makeSimplePolicy(code: string, eligibility: Eligibility, target: string, result: OutcomeResult): FailurePolicy {
221
+ const id = failureId(LIFECYCLE_PRODUCER, code);
222
+ const contextId = 'identity-confirmed';
223
+ const effect: OutcomeEffect = result === 'preserve-failure' ? 'record-only' : 'apply-target';
224
+ return {
225
+ ...identity(LIFECYCLE_PRODUCER, code, 'G-03'),
226
+ eligibility,
227
+ capability: eligibility === 'never-overridable' ? 'audit-only' : 'local-task-repair',
228
+ targets: [target],
229
+ contexts: [context(contextId, contextId)],
230
+ targetContexts: { [target]: [contextId] },
231
+ outcomes: [{
232
+ target,
233
+ contextId,
234
+ effect,
235
+ result,
236
+ residual: result === 'preserve-failure' ? 'original failure remains authoritative' : 'local operator action was applied to the task'
237
+ }],
238
+ fallback: {
239
+ effect: 'no-write',
240
+ result: result === 'preserve-failure' ? 'preserve-failure' : 'recovery-required',
241
+ residual: 'outcome context was not confirmed; no target state was written'
242
+ },
243
+ probeId: `probe.${code.toLowerCase()}`
244
+ };
245
+ }
246
+
247
+ function simplePolicy(code: string): FailurePolicy {
248
+ if (code === 'LIFECYCLE_SOURCE_INVALID') return makeSimplePolicy(code, 'eligible', 'continue-local', 'human-approved');
249
+ if (code === 'LIFECYCLE_LOG_MISSING' || code === 'LIFECYCLE_METADATA_FAILED' || code === 'LIFECYCLE_DOCUMENT_INVALID') {
250
+ return makeSimplePolicy(code, 'repair-only', 'repair-task', 'recovery-required');
251
+ }
252
+ if (code === 'LIFECYCLE_IDENTITY_CONFLICT' || code === 'LIFECYCLE_IDENTITY_INVALID' || code === 'LIFECYCLE_PAYLOAD_INVALID' || code === 'LIFECYCLE_STAGING_IDENTITY_INVALID' || code === 'LIFECYCLE_STAGING_INVALID') {
253
+ return makeSimplePolicy(code, 'never-overridable', 'record-only', 'preserve-failure');
254
+ }
255
+ return makeSimplePolicy(code, 'runtime-recovery-only', 'retry-same-intent', 'recovery-required');
256
+ }
257
+
258
+ function genericGuardPolicy(producer: GuardProducer): FailurePolicy {
259
+ const preserve = producer.guardId === 'G-01' || producer.guardId === 'G-09' || producer.guardId === 'G-10';
260
+ const contextId = 'guard-observed';
261
+ const result: OutcomeResult = preserve ? 'preserve-failure' : 'human-approved';
262
+ const effect: OutcomeEffect = preserve ? 'record-only' : 'apply-target';
263
+ return {
264
+ ...identity(producer.producerId, producer.code, producer.guardId),
265
+ eligibility: preserve ? 'never-overridable' : 'eligible',
266
+ capability: preserve ? 'audit-only' : `local-${producer.guardId.toLowerCase()}-gate`,
267
+ targets: ['continue-local'],
268
+ contexts: [context(contextId, contextId)],
269
+ targetContexts: { 'continue-local': [contextId] },
270
+ outcomes: [{
271
+ target: 'continue-local',
272
+ contextId,
273
+ effect,
274
+ result,
275
+ residual: preserve ? 'external or identity evidence remains authoritative' : `${producer.guardId} local guard was explicitly overridden`
276
+ }],
277
+ fallback: {
278
+ effect: 'record-only',
279
+ result: 'preserve-failure',
280
+ residual: 'guard facts were not confirmed; the original failure remains authoritative'
281
+ },
282
+ probeId: `probe.${producer.producerId}.${producer.code.toLowerCase()}`
283
+ };
284
+ }
285
+
286
+ const shortIdPolicy: FailurePolicy = {
287
+ ...identity(LIFECYCLE_PRODUCER, 'SHORT_ID_CAPACITY_EXCEEDED', 'G-03'),
288
+ eligibility: 'repair-only',
289
+ capability: 'safe-close-or-registry-recovery',
290
+ targets: ['safe-close', 'recover-registry'],
291
+ contexts: [
292
+ context(SHORT_ID_CONFIRMED, SHORT_ID_CONFIRMED),
293
+ context(SHORT_ID_UNCONFIRMED, SHORT_ID_UNCONFIRMED),
294
+ context(REGISTRY_RECOVERABLE, REGISTRY_RECOVERABLE)
295
+ ],
296
+ targetContexts: {
297
+ 'safe-close': [SHORT_ID_CONFIRMED, SHORT_ID_UNCONFIRMED],
298
+ 'recover-registry': [REGISTRY_RECOVERABLE]
299
+ },
300
+ outcomes: [
301
+ {
302
+ target: 'safe-close',
303
+ contextId: SHORT_ID_CONFIRMED,
304
+ effect: 'apply-target',
305
+ result: 'safe-closed',
306
+ residual: 'task was atomically safe-closed without entering active or expanding short-id capacity'
307
+ },
308
+ {
309
+ target: 'safe-close',
310
+ contextId: SHORT_ID_UNCONFIRMED,
311
+ effect: 'no-write',
312
+ result: 'recovery-required',
313
+ residual: 'task identity could not be confirmed; no terminal state was written'
314
+ },
315
+ {
316
+ target: 'recover-registry',
317
+ contextId: REGISTRY_RECOVERABLE,
318
+ effect: 'retry-same-intent',
319
+ result: 'recovery-required',
320
+ residual: 'registry recovery must complete before a new diagnosis and ticket are issued'
321
+ }
322
+ ],
323
+ fallback: {
324
+ effect: 'no-write',
325
+ result: 'recovery-required',
326
+ residual: 'short-id failure facts did not match a registered safe outcome context; original failure remains authoritative'
327
+ },
328
+ probeId: 'probe.short-id-capacity'
329
+ };
330
+
331
+ const HUMAN_OVERRIDE_FAILURE_REGISTRY: readonly FailurePolicy[] = [
332
+ shortIdPolicy,
333
+ ...lifecycleProducerCatalog
334
+ .filter(({ code }) => code !== 'SHORT_ID_CAPACITY_EXCEEDED')
335
+ .map(({ code }) => simplePolicy(code)),
336
+ ...guardProducerCatalog.map((producer) => genericGuardPolicy(producer))
337
+ ];
338
+
339
+ const stableFailureCatalog = [
340
+ ...lifecycleProducerCatalog,
341
+ ...guardProducerCatalog
342
+ ].map(({ producerId, guardId, code }) => ({
343
+ id: guardFailureId(producerId, code),
344
+ producerId,
345
+ guardId,
346
+ code
347
+ }));
348
+
349
+ function singleLine(value: unknown): value is string {
350
+ return typeof value === 'string' && value.trim().length > 0 && !/[\r\n]/.test(value);
351
+ }
352
+
353
+ function validatePolicy(policy: FailurePolicy): void {
354
+ if (!singleLine(policy.id) || !singleLine(policy.producerId) || !singleLine(policy.guardId) || !singleLine(policy.code)) {
355
+ throw new Error('failure policy identity is invalid');
356
+ }
357
+ if (policy.id !== failureId(policy.producerId, policy.code)) throw new Error(`policy ${policy.id} has an invalid identity`);
358
+ if (!Array.isArray(policy.targets) || new Set(policy.targets).size !== policy.targets.length || policy.targets.length === 0) {
359
+ throw new Error(`policy ${policy.id} targets are invalid`);
360
+ }
361
+ if (!Array.isArray(policy.contexts) || policy.contexts.length === 0) throw new Error(`policy ${policy.id} contexts are missing`);
362
+ const contexts = new Map<string, OutcomeContext>();
363
+ for (const item of policy.contexts) {
364
+ if (!singleLine(item.id) || contexts.has(item.id) || item.facts.length === 0 || new Set(item.facts).size !== item.facts.length) {
365
+ throw new Error(`policy ${policy.id} contains invalid or duplicate contexts`);
366
+ }
367
+ contexts.set(item.id, item);
368
+ }
369
+ const contextFactsByTarget = new Map<string, Set<string>>();
370
+ for (const target of policy.targets) {
371
+ const facts = contextFactsByTarget.get(target) ?? new Set<string>();
372
+ for (const contextId of policy.targetContexts[target] ?? []) {
373
+ const item = contexts.get(contextId);
374
+ if (!item) continue;
375
+ const key = item.facts.slice().sort().join('\0');
376
+ if (facts.has(key)) throw new Error(`policy ${policy.id} has overlapping context facts for target ${target}`);
377
+ facts.add(key);
378
+ }
379
+ contextFactsByTarget.set(target, facts);
380
+ }
381
+ const outcomes = new Set<string>();
382
+ for (const target of policy.targets) {
383
+ const contextIds = policy.targetContexts[target];
384
+ if (!contextIds || contextIds.length === 0) throw new Error(`policy ${policy.id} target ${target} has no contexts`);
385
+ for (const contextId of contextIds) {
386
+ if (!contexts.has(contextId)) throw new Error(`policy ${policy.id} references unknown context ${contextId}`);
387
+ const key = `${target}\0${contextId}`;
388
+ if (outcomes.has(key)) throw new Error(`policy ${policy.id} has overlapping outcome ${key}`);
389
+ outcomes.add(key);
390
+ }
391
+ }
392
+ if (!Array.isArray(policy.outcomes) || policy.outcomes.length !== outcomes.size) {
393
+ throw new Error(`policy ${policy.id} outcome table does not cover every target/context`);
394
+ }
395
+ for (const rule of policy.outcomes) {
396
+ const key = `${rule.target}\0${rule.contextId}`;
397
+ if (!outcomes.has(key) || outcomes.delete(key) === false || !singleLine(rule.residual)) {
398
+ throw new Error(`policy ${policy.id} contains an invalid or duplicate outcome`);
399
+ }
400
+ if (rule.result === 'human-approved' && (policy.eligibility !== 'eligible' || rule.effect !== 'apply-target')) {
401
+ throw new Error(`policy ${policy.id} grants human-approved outside an eligible apply-target rule`);
402
+ }
403
+ if (rule.result === 'safe-closed' && rule.effect !== 'apply-target') {
404
+ throw new Error(`policy ${policy.id} grants safe-closed without applying a target`);
405
+ }
406
+ if (rule.effect === 'record-only' && rule.result === 'human-approved') {
407
+ throw new Error(`policy ${policy.id} treats record-only as human-approved`);
408
+ }
409
+ }
410
+ if (outcomes.size !== 0) throw new Error(`policy ${policy.id} has missing outcomes`);
411
+ if (!['no-write', 'record-only'].includes(policy.fallback.effect) || !['recovery-required', 'preserve-failure'].includes(policy.fallback.result) || !singleLine(policy.fallback.residual)) {
412
+ throw new Error(`policy ${policy.id} fallback is invalid`);
413
+ }
414
+ }
415
+
416
+ function validateRegistry(registry: readonly FailurePolicy[] = HUMAN_OVERRIDE_FAILURE_REGISTRY): void {
417
+ const ids = new Set<string>();
418
+ for (const policy of registry) {
419
+ validatePolicy(policy);
420
+ if (ids.has(policy.id)) throw new Error(`duplicate failure policy ${policy.id}`);
421
+ ids.add(policy.id);
422
+ }
423
+ const catalogIds = new Set(stableFailureCatalog.map((item) => item.id));
424
+ if (ids.size !== catalogIds.size || [...catalogIds].some((id) => !ids.has(id))) {
425
+ throw new Error('human override registry does not match the lifecycle producer catalog');
426
+ }
427
+ for (const item of stableFailureCatalog) {
428
+ const policy = registry.find((candidate) => candidate.id === item.id);
429
+ if (!policy || policy.producerId !== item.producerId || policy.guardId !== item.guardId || policy.code !== item.code) {
430
+ throw new Error(`human override policy ${item.id} does not match its producer catalog entry`);
431
+ }
432
+ }
433
+ }
434
+
435
+ validateRegistry();
436
+
437
+ function policyFor(value: string | FailurePolicy): FailurePolicy | null {
438
+ if (typeof value !== 'string') return value;
439
+ return HUMAN_OVERRIDE_FAILURE_REGISTRY.find((policy) => policy.id === value) ?? null;
440
+ }
441
+
442
+ function resolveOutcome(policyOrId: string | FailurePolicy, requestedTarget: string, observedFacts: readonly string[]): Outcome {
443
+ const policy = policyFor(policyOrId);
444
+ if (!policy || !singleLine(requestedTarget) || !Array.isArray(observedFacts) || observedFacts.some((fact) => !singleLine(fact))) {
445
+ return {
446
+ effect: 'no-write',
447
+ result: 'recovery-required',
448
+ residual: 'failure policy or observed facts were not registered; no target state was written',
449
+ policyId: typeof policyOrId === 'string' ? policyOrId : policyOrId.id,
450
+ target: requestedTarget,
451
+ contextId: 'context-unmatched'
452
+ };
453
+ }
454
+ const allowedContexts = policy.targetContexts[requestedTarget] ?? [];
455
+ const matches = policy.contexts.filter((item) => allowedContexts.includes(item.id)
456
+ && item.facts.length === observedFacts.length
457
+ && item.facts.every((fact) => observedFacts.includes(fact))
458
+ && observedFacts.every((fact) => item.facts.includes(fact)));
459
+ if (matches.length !== 1) {
460
+ return { ...policy.fallback, policyId: policy.id, target: requestedTarget, contextId: 'context-unmatched' };
461
+ }
462
+ const rule = policy.outcomes.find((item) => item.target === requestedTarget && item.contextId === matches[0]!.id);
463
+ if (!rule) return { ...policy.fallback, policyId: policy.id, target: requestedTarget, contextId: 'context-unmatched' };
464
+ return { ...rule, policyId: policy.id };
465
+ }
466
+
467
+ function error(code: string, message: string): OverrideError {
468
+ return { code, message };
469
+ }
470
+
471
+ type ParsedHumanOverrideLedger = Readonly<{
472
+ table: NonNullable<ReturnType<typeof parseTable>>;
473
+ columns: readonly string[];
474
+ }>;
475
+
476
+ function parseHumanOverrideLedger(content: string): ParsedHumanOverrideLedger | null {
477
+ try {
478
+ const table = parseTable(content, {
479
+ sectionAliases: HUMAN_OVERRIDE_SECTION_ALIASES,
480
+ columns: HUMAN_OVERRIDE_COLUMNS,
481
+ keyColumn: 'id'
482
+ });
483
+ return table ? { table, columns: HUMAN_OVERRIDE_COLUMNS } : null;
484
+ } catch (cause) {
485
+ if (!(cause instanceof DocumentMutationError) || cause.code !== 'TABLE_NOT_FOUND') throw cause;
486
+ const table = parseTable(content, {
487
+ sectionAliases: HUMAN_OVERRIDE_SECTION_ALIASES,
488
+ columns: LEGACY_HUMAN_OVERRIDE_COLUMNS,
489
+ keyColumn: 'id'
490
+ });
491
+ return table ? { table, columns: LEGACY_HUMAN_OVERRIDE_COLUMNS } : null;
492
+ }
493
+ }
494
+
495
+ function escapeTableCell(value: string): string {
496
+ return value.replace(/\\/g, '\\\\').replace(/\|/g, '\\|');
497
+ }
498
+
499
+ function migrateHumanOverrideLedger(ledger: ParsedHumanOverrideLedger) {
500
+ if (ledger.columns.includes(PULL_REQUEST_NUMBER_COLUMN)) return null;
501
+ const header = `| ${HUMAN_OVERRIDE_COLUMNS.join(' | ')} |`;
502
+ const separator = `| ${HUMAN_OVERRIDE_COLUMNS.map(() => '---').join(' | ')} |`;
503
+ const rows = ledger.table.rows.map(({ values }) => `| ${HUMAN_OVERRIDE_COLUMNS
504
+ .map((column) => escapeTableCell(values[column] ?? ''))
505
+ .join(' | ')} |`);
506
+ return {
507
+ kind: 'section' as const,
508
+ aliases: HUMAN_OVERRIDE_SECTION_ALIASES,
509
+ heading: ledger.table.heading,
510
+ body: [header, separator, ...rows].join('\n')
511
+ };
512
+ }
513
+
514
+ function validatePullRequestBinding(
515
+ policy: FailurePolicy,
516
+ row: Readonly<Record<string, string>>,
517
+ request: ConsumeHumanOverrideRequest
518
+ ): OverrideError | null {
519
+ if (policy.producerId !== 'platform.pull-request') return null;
520
+ const bound = toPositiveInteger(row[PULL_REQUEST_NUMBER_COLUMN]);
521
+ if (bound === null) {
522
+ return error('OVERRIDE_RESOURCE_BINDING_MISSING', 'override ticket has no bound pull request number; issue a new ticket');
523
+ }
524
+ if (request.pullRequestNumber !== undefined && request.pullRequestNumber !== bound) {
525
+ return error('OVERRIDE_RESOURCE_MISMATCH', `override ticket is bound to pull request #${bound}`);
526
+ }
527
+ return null;
528
+ }
529
+
530
+ function overrideDryRunConflict(values: Readonly<Record<string, unknown>>): OverrideError | null {
531
+ const hasOverride = ['overrideTicket', 'overrideTarget', 'overrideScope'].some((key) => values[key] !== undefined && values[key] !== '');
532
+ return values.dryRun && hasOverride
533
+ ? error('OVERRIDE_DRY_RUN_CONFLICT', '--dry-run cannot be combined with --override-*')
534
+ : null;
535
+ }
536
+
537
+ function resolveTask(taskRef: string, options: HumanOverrideOptions) {
538
+ return resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
539
+ }
540
+
541
+ function failedIssue(code: string, message: string, taskId: string | null = null): HumanOverrideIssueResult {
542
+ return { status: 'failed', changed: false, ticketId: null, taskId, identity: null, error: error(code, message) };
543
+ }
544
+
545
+ function failedConsume(code: string, message: string, ticketId: string | null = null, taskId: string | null = null): HumanOverrideConsumeResult {
546
+ return { status: 'failed', changed: false, ticketId, taskId, outcome: null, error: error(code, message) };
547
+ }
548
+
549
+ function readTask(taskMdPath: string): { content: string; frontmatter: ReturnType<typeof parseTypedTaskFrontmatter> } | OverrideError {
550
+ try {
551
+ const content = fs.readFileSync(taskMdPath, 'utf8');
552
+ const frontmatter = parseTypedTaskFrontmatter(content);
553
+ return { content, frontmatter };
554
+ } catch (cause) {
555
+ return error('OVERRIDE_DOCUMENT_INVALID', cause instanceof Error ? cause.message : String(cause));
556
+ }
557
+ }
558
+
559
+ function writeOptions(options: HumanOverrideOptions) {
560
+ return {
561
+ repoRoot: options.repoRoot,
562
+ metadataProvider: options.metadataProvider,
563
+ randomSuffix: options.randomSuffix,
564
+ fileSystem: options.taskFileSystem
565
+ };
566
+ }
567
+
568
+ function taskWriteError(result: TaskWriteResult): OverrideError | null {
569
+ return result.status === 'failed' ? error(result.error.code, result.error.message) : null;
570
+ }
571
+
572
+ type ProducerFailure = Readonly<{
573
+ code: string;
574
+ message: string;
575
+ observedFacts: readonly string[];
576
+ state: string;
577
+ }>;
578
+
579
+ function snapshotEvidence(
580
+ taskId: string,
581
+ failureIdValue: string,
582
+ target: string,
583
+ content: string,
584
+ frontmatter: Record<string, unknown> | null,
585
+ producer: ProducerFailure,
586
+ resource: Readonly<{ kind: 'issue' | 'pull-request'; number: number }> | null
587
+ ): string {
588
+ const stableFrontmatter = frontmatter
589
+ ? Object.fromEntries(Object.entries(frontmatter).filter(([key]) => !['updated_at', 'agent_infra_version', 'status', 'blocked_at', 'completed_at', 'cancelled_at'].includes(key)))
590
+ : { parse: 'invalid' };
591
+ return JSON.stringify({
592
+ taskId,
593
+ failureId: failureIdValue,
594
+ target,
595
+ producer: {
596
+ code: producer.code,
597
+ message: producer.message,
598
+ observedFacts: producer.observedFacts,
599
+ state: producer.state
600
+ },
601
+ resource,
602
+ stableFrontmatter,
603
+ activityLogPresent: locateActivityLog(content) !== null
604
+ });
605
+ }
606
+
607
+ function resourceForSnapshot(
608
+ policy: FailurePolicy,
609
+ options: HumanOverrideOptions,
610
+ frontmatter: Record<string, unknown> | null
611
+ ): Readonly<{ kind: 'issue' | 'pull-request'; number: number }> | null {
612
+ if (policy.producerId === 'platform.issue') {
613
+ const number = options.probeIssueNumber ?? toPositiveInteger(frontmatter?.issue_number);
614
+ return number === null ? null : { kind: 'issue', number };
615
+ }
616
+ if (policy.producerId === 'platform.pull-request') {
617
+ const number = options.probePullRequestNumber ?? toPositiveInteger(frontmatter?.pr_number);
618
+ return number === null ? null : { kind: 'pull-request', number };
619
+ }
620
+ return null;
621
+ }
622
+
623
+ function probeLifecycleFailure(
624
+ taskRef: string,
625
+ policy: FailurePolicy,
626
+ resolved: { taskId: string; state: string },
627
+ options: HumanOverrideOptions
628
+ ): ProducerFailure | OverrideError {
629
+ if (!options.probeIntent) return error('OVERRIDE_PROBE_INPUT_MISSING', 'an original lifecycle intent is required to prove the producer failure');
630
+ const request = lifecycleRequestFor(
631
+ taskRef,
632
+ options.probeIntent,
633
+ options.probeOperator ?? 'local-operator',
634
+ options.probeReason ?? 'human override producer probe',
635
+ {
636
+ alert_number: options.probeAlertNumber === undefined ? undefined : String(options.probeAlertNumber),
637
+ issue_number: options.probeIssueNumber === undefined ? undefined : String(options.probeIssueNumber),
638
+ staging_dir: options.probeStagingDir
639
+ }
640
+ );
641
+ if ('code' in request) return request;
642
+ const result = applyTaskLifecycle({ ...request, dryRun: true }, {
643
+ repoRoot: options.repoRoot,
644
+ metadataProvider: options.metadataProvider
645
+ });
646
+ if (result.status !== 'failed' || result.error?.code !== policy.code) {
647
+ return error(
648
+ 'OVERRIDE_FAILURE_NOT_PRESENT',
649
+ `producer probe returned ${result.error?.code ?? 'no failure'}; expected ${policy.code}`
650
+ );
651
+ }
652
+ const identityConfirmed = result.taskId === resolved.taskId;
653
+ const observedFacts = policy.code === 'SHORT_ID_CAPACITY_EXCEEDED'
654
+ ? identityConfirmed && ['active', 'blocked'].includes(resolved.state)
655
+ ? [SHORT_ID_CONFIRMED]
656
+ : [SHORT_ID_UNCONFIRMED]
657
+ : identityConfirmed ? ['identity-confirmed'] : [];
658
+ return {
659
+ code: result.error.code,
660
+ message: result.error.message,
661
+ observedFacts,
662
+ state: result.sourceState ?? resolved.state
663
+ };
664
+ }
665
+
666
+ type ProbeTask = Readonly<{
667
+ taskId: string;
668
+ state: string;
669
+ taskDir: string;
670
+ repoRoot: string;
671
+ }>;
672
+
673
+ function probeResult(
674
+ policy: FailurePolicy,
675
+ result: unknown,
676
+ state: string
677
+ ): ProducerFailure | null {
678
+ if (!result || typeof result !== 'object') return null;
679
+ const errorValue = (result as { error?: unknown }).error;
680
+ if (!errorValue || typeof errorValue !== 'object') return null;
681
+ const code = String((errorValue as { code?: unknown }).code ?? '');
682
+ if (code !== policy.code) return null;
683
+ return {
684
+ code,
685
+ message: String((errorValue as { message?: unknown }).message ?? code),
686
+ observedFacts: ['guard-observed'],
687
+ state
688
+ };
689
+ }
690
+
691
+ function probeNotObserved(policy: FailurePolicy): OverrideError {
692
+ return error('OVERRIDE_FAILURE_NOT_PRESENT', `producer ${policy.producerId} did not produce ${policy.code}`);
693
+ }
694
+
695
+ function probePlatformFailure(
696
+ taskRef: string,
697
+ policy: FailurePolicy,
698
+ resolved: ProbeTask,
699
+ options: HumanOverrideOptions
700
+ ): ProducerFailure | OverrideError {
701
+ let frontmatter: ReturnType<typeof parseTypedTaskFrontmatter>;
702
+ try {
703
+ frontmatter = parseTypedTaskFrontmatter(fs.readFileSync(path.join(resolved.taskDir, 'task.md'), 'utf8'));
704
+ } catch (cause) {
705
+ return error('OVERRIDE_DOCUMENT_INVALID', cause instanceof Error ? cause.message : String(cause));
706
+ }
707
+ const issueNumber = options.probeIssueNumber ?? toPositiveInteger(frontmatter.issue_number);
708
+ const pullRequestNumber = options.probePullRequestNumber ?? toPositiveInteger(frontmatter.pr_number);
709
+ const operation = policy.producerId === 'platform.issue'
710
+ ? issueNumber === null
711
+ ? null
712
+ : bindPlatformIssue(taskRef, {
713
+ issue: issueNumber,
714
+ agent: options.probeOperator ?? 'local-operator',
715
+ dryRun: true,
716
+ cwd: resolved.repoRoot,
717
+ ...(options.platformClient ? { client: options.platformClient } : {})
718
+ })
719
+ : pullRequestNumber === null
720
+ ? null
721
+ : bindPlatformPullRequest(taskRef, {
722
+ pr: pullRequestNumber,
723
+ agent: options.probeOperator ?? 'local-operator',
724
+ dryRun: true,
725
+ cwd: resolved.repoRoot,
726
+ ...(options.platformClient ? { client: options.platformClient } : {})
727
+ });
728
+ if (!operation) return error('OVERRIDE_PROBE_INPUT_MISSING', `${policy.producerId} requires a bound issue or pull request number`);
729
+ if (operation.status !== 'failed' && operation.status !== 'blocked') return probeNotObserved(policy);
730
+ if (!operation.error) return error('OVERRIDE_PROBE_INPUT_MISSING', `${policy.producerId} returned no platform error evidence`);
731
+ return {
732
+ code: policy.code,
733
+ message: `${operation.error.code}: ${operation.error.message}`,
734
+ observedFacts: ['guard-observed'],
735
+ state: resolved.state
736
+ };
737
+ }
738
+
739
+ function toPositiveInteger(value: unknown): number | null {
740
+ const number = typeof value === 'number' ? value : Number(value);
741
+ return Number.isSafeInteger(number) && number > 0 ? number : null;
742
+ }
743
+
744
+ function probeTaskEventFailure(taskRef: string, policy: FailurePolicy, resolved: ProbeTask): ProducerFailure | OverrideError {
745
+ const starts = [
746
+ 'analyze.started', 'review-analysis.started', 'plan.started', 'review-plan.started',
747
+ 'code.started', 'review-code.started', 'manual-validation.started', 'validation-run.started'
748
+ ];
749
+ const completions = [
750
+ ['analyze.completed', 'analysis.md'],
751
+ ['review-analysis.completed', 'review-analysis.md'],
752
+ ['plan.completed', 'plan.md'],
753
+ ['review-plan.completed', 'review-plan.md'],
754
+ ['code.completed', 'code.md'],
755
+ ['review-code.completed', 'review-code.md']
756
+ ] as const;
757
+ const candidates: Array<{ event: string; artifact?: string }> = policy.code === 'EVENT_START_MISSING'
758
+ ? completions.map(([event, artifact]) => ({ event, artifact }))
759
+ : starts.map((event) => ({ event }));
760
+ for (const candidate of candidates) {
761
+ const result = applyTaskEvent({
762
+ taskRef,
763
+ event: candidate.event,
764
+ agent: 'codex',
765
+ dryRun: true,
766
+ ...(candidate.artifact ? { artifact: candidate.artifact } : {})
767
+ }, { repoRoot: resolved.repoRoot, lockAlreadyHeld: true });
768
+ const matched = probeResult(policy, result, resolved.state);
769
+ if (matched) return matched;
770
+ }
771
+ return probeNotObserved(policy);
772
+ }
773
+
774
+ function probeVerificationFailure(taskRef: string, policy: FailurePolicy, resolved: ProbeTask): ProducerFailure | OverrideError {
775
+ const result = verifyTaskEvent(
776
+ {
777
+ taskRef,
778
+ event: 'review-code.completed',
779
+ artifact: policy.code === 'VERIFY_ARTIFACT_INVALID' ? 'invalid-artifact.md' : 'review-code.md'
780
+ },
781
+ { repoRoot: resolved.repoRoot }
782
+ );
783
+ const matched = probeResult(policy, result, resolved.state);
784
+ return matched ?? probeNotObserved(policy);
785
+ }
786
+
787
+ function latestArtifact(taskDir: string, family: string): string | undefined {
788
+ let names: string[];
789
+ try { names = fs.readdirSync(taskDir); } catch { return undefined; }
790
+ const candidates = names.filter((name) => (
791
+ name === `${family}.md` || new RegExp(`^${family}-r\\d+\\.md$`).test(name)
792
+ ));
793
+ candidates.sort((a, b) => {
794
+ const round = (name: string) => Number(/-r(\d+)\.md$/.exec(name)?.[1] ?? 1);
795
+ return round(b) - round(a);
796
+ });
797
+ return candidates[0];
798
+ }
799
+
800
+ function probeVerificationEngineFailure(policy: FailurePolicy, resolved: ProbeTask): ProducerFailure | OverrideError {
801
+ const candidates = [
802
+ { skillName: 'review-code', family: 'review-code' },
803
+ { skillName: 'review-plan', family: 'review-plan' },
804
+ { skillName: 'review-analysis', family: 'review-analysis' },
805
+ { skillName: 'code-task', family: 'code' },
806
+ { skillName: 'plan-task', family: 'plan' },
807
+ { skillName: 'analyze-task', family: 'analysis' },
808
+ { skillName: 'complete-task', family: null }
809
+ ];
810
+ for (const candidate of candidates) {
811
+ const artifactFile = candidate.family ? latestArtifact(resolved.taskDir, candidate.family) : undefined;
812
+ if (candidate.family && !artifactFile) continue;
813
+ try {
814
+ const payload = verifyInProcess({
815
+ mode: 'gate',
816
+ skillName: candidate.skillName,
817
+ taskDir: resolved.taskDir,
818
+ artifactFile,
819
+ checks: [],
820
+ repositoryRoot: resolved.repoRoot
821
+ }) as { gate?: unknown };
822
+ const code = payload.gate === 'blocked' ? 'CHECK_BLOCKED' : payload.gate === 'fail' ? 'CHECK_FAILED' : '';
823
+ if (code === policy.code) {
824
+ return {
825
+ code,
826
+ message: `${candidate.skillName} verification gate returned ${payload.gate}`,
827
+ observedFacts: ['guard-observed'],
828
+ state: resolved.state
829
+ };
830
+ }
831
+ } catch {
832
+ // A probe failure is not evidence that the requested verification failure occurred.
833
+ }
834
+ }
835
+ return probeNotObserved(policy);
836
+ }
837
+
838
+ function probeGenericFailure(
839
+ taskRef: string,
840
+ policy: FailurePolicy,
841
+ resolved: ProbeTask,
842
+ options: HumanOverrideOptions = {}
843
+ ): ProducerFailure | OverrideError {
844
+ switch (policy.producerId) {
845
+ case 'task.resolve':
846
+ return probeNotObserved(policy);
847
+ case 'task.write':
848
+ return probeResult(policy, writeTask({ taskRef, expectedState: 'active', mutations: [], dryRun: true }, { repoRoot: resolved.repoRoot }), resolved.state) ?? probeNotObserved(policy);
849
+ case 'task-event':
850
+ return probeTaskEventFailure(taskRef, policy, resolved);
851
+ case 'task-verify':
852
+ return probeVerificationFailure(taskRef, policy, resolved);
853
+ case 'verification-engine':
854
+ return probeVerificationEngineFailure(policy, resolved);
855
+ case 'activity-intent':
856
+ return probeResult(policy, applyPrReviewActivityIntent({
857
+ kind: 'pr-review-start', taskRef, agent: 'codex', artifact: 'pr-review.md', head: '0'.repeat(40), dryRun: true
858
+ }, { repoRoot: resolved.repoRoot, lockAlreadyHeld: true }), resolved.state) ?? probeNotObserved(policy);
859
+ case 'ledger-intent':
860
+ return probeResult(policy, applyLedgerIntent({
861
+ kind: 'finding-respond', taskRef, id: 'CD-1', round: 1, status: 'accepted', evidence: 'review-code.md#CD-1', dryRun: true
862
+ }), resolved.state) ?? probeNotObserved(policy);
863
+ case 'decision-intent':
864
+ return probeResult(policy, applyHumanDecision({ taskRef, selector: 'CD-1', decision: 'probe', dryRun: true }), resolved.state) ?? probeNotObserved(policy);
865
+ case 'workflow-warning':
866
+ return probeResult(policy, applyWorkflowWarningIntent({
867
+ kind: 'add', taskRef, step: 'probe', severity: 'IMPORTANT', code: 'PROBE', target: 'probe', message: 'probe', action: 'probe', dryRun: true
868
+ }), resolved.state) ?? probeNotObserved(policy);
869
+ case 'review-finalization':
870
+ return probeResult(policy, finalizeReviewSummary({
871
+ taskRef, stage: 'code', artifact: 'review-code.md', dryRun: true
872
+ }, { repoRoot: resolved.repoRoot, lockAlreadyHeld: true }), resolved.state) ?? probeNotObserved(policy);
873
+ case 'lifecycle-execution': {
874
+ const identities = {
875
+ stage: 'code' as const, round: 1, artifact: 'code.md', role: 'executor' as const
876
+ };
877
+ const results = [
878
+ inspectLifecycleExecution(taskRef, { mode: 'standalone', identity: identities, dryRun: true }, { repoRoot: resolved.repoRoot }),
879
+ inspectLifecycleExecution(taskRef, { mode: 'orchestrated', identity: identities, dryRun: true }, { repoRoot: resolved.repoRoot })
880
+ ];
881
+ for (const result of results) {
882
+ const matched = probeResult(policy, result, resolved.state);
883
+ if (matched) return matched;
884
+ }
885
+ return probeNotObserved(policy);
886
+ }
887
+ case 'platform.issue':
888
+ case 'platform.pull-request':
889
+ return probePlatformFailure(taskRef, policy, resolved, options);
890
+ default:
891
+ return error('OVERRIDE_PROBE_INPUT_MISSING', `no producer probe is registered for ${policy.producerId}`);
892
+ }
893
+ }
894
+
895
+ function isProducerFailure(value: ProducerFailure | OverrideError): value is ProducerFailure {
896
+ return 'observedFacts' in value && 'state' in value && Array.isArray(value.observedFacts);
897
+ }
898
+
899
+ function captureFailureSnapshot(taskRef: string, failureIdValue: string, target: string, options: HumanOverrideOptions = {}): FailureSnapshot | OverrideError {
900
+ const policy = policyFor(failureIdValue);
901
+ if (!policy) return error('OVERRIDE_FAILURE_UNKNOWN', `failure policy '${failureIdValue}' is not registered`);
902
+ if (!policy.targets.includes(target)) return error('OVERRIDE_TARGET_INVALID', `target '${target}' is not registered for ${failureIdValue}`);
903
+ const resolved = resolveTask(taskRef, options);
904
+ if (!resolved.ok) return error(resolved.code, resolved.message);
905
+ let content: string;
906
+ try {
907
+ content = fs.readFileSync(resolved.taskMdPath, 'utf8');
908
+ } catch (cause) {
909
+ return error('OVERRIDE_DOCUMENT_INVALID', cause instanceof Error ? cause.message : String(cause));
910
+ }
911
+ let frontmatter: Record<string, unknown> | null = null;
912
+ try { frontmatter = parseTypedTaskFrontmatter(content); } catch { /* probe reports an unconfirmed identity */ }
913
+ const producer: ProducerFailure | OverrideError = policy.producerId === LIFECYCLE_PRODUCER
914
+ ? probeLifecycleFailure(taskRef, policy, resolved, options)
915
+ : probeGenericFailure(taskRef, policy, resolved, options);
916
+ if (!isProducerFailure(producer)) return producer;
917
+ const digest = computeFailureDigest(snapshotEvidence(
918
+ resolved.taskId,
919
+ failureIdValue,
920
+ target,
921
+ content,
922
+ frontmatter,
923
+ producer,
924
+ resourceForSnapshot(policy, options, frontmatter)
925
+ ));
926
+ const [producerId, ...codeParts] = failureIdValue.split(':');
927
+ const code = codeParts.join(':');
928
+ return {
929
+ schema: '1',
930
+ taskId: resolved.taskId,
931
+ failureId: failureIdValue,
932
+ producerId: producerId ?? '',
933
+ code,
934
+ target,
935
+ state: producer.state,
936
+ digest,
937
+ observedFacts: producer.observedFacts,
938
+ message: producer.message,
939
+ capturedAt: (options.now ?? canonicalTimestamp)(),
940
+ source: 'producer-result'
941
+ };
942
+ }
943
+
944
+ function isOverrideError(value: FailureSnapshot | OverrideError): value is OverrideError {
945
+ return !('schema' in value);
946
+ }
947
+
948
+ function validateSnapshot(snapshot: FailureSnapshot, expected: FailureSnapshot): OverrideError | null {
949
+ if (
950
+ snapshot.schema !== '1' || snapshot.source !== 'producer-result' ||
951
+ snapshot.taskId !== expected.taskId || snapshot.failureId !== expected.failureId ||
952
+ snapshot.producerId !== expected.producerId || snapshot.code !== expected.code ||
953
+ snapshot.target !== expected.target || snapshot.digest !== expected.digest ||
954
+ snapshot.state !== expected.state || snapshot.message !== expected.message ||
955
+ snapshot.observedFacts.length !== expected.observedFacts.length ||
956
+ snapshot.observedFacts.some((fact, index) => fact !== expected.observedFacts[index])
957
+ ) return error('OVERRIDE_SNAPSHOT_MISMATCH', 'failure snapshot is not the canonical producer probe result');
958
+ return null;
959
+ }
960
+
961
+ function validateCommon(request: HumanOverrideRequest): OverrideError | null {
962
+ const fields = [request.taskRef, request.failureId, request.target, request.operator, request.reason, request.scope, request.expiresAt];
963
+ if (fields.some((value) => !singleLine(value))) return error('OVERRIDE_PAYLOAD_INVALID', 'override fields must be non-empty single-line values');
964
+ if (request.identitySource !== undefined && request.identitySource !== 'local-declared') {
965
+ return error('OVERRIDE_IDENTITY_SOURCE_INVALID', 'the local override entrypoint owns identity_source=local-declared');
966
+ }
967
+ return null;
968
+ }
969
+
970
+ function validateResourceNumbers(request: HumanOverrideRequest): OverrideError | null {
971
+ const values = [
972
+ ['alertNumber', request.alertNumber],
973
+ ['issueNumber', request.issueNumber],
974
+ ['pullRequestNumber', request.pullRequestNumber]
975
+ ] as const;
976
+ for (const [name, value] of values) {
977
+ if (value !== undefined && (!Number.isSafeInteger(value) || value <= 0)) {
978
+ return error('OVERRIDE_RESOURCE_INVALID', `${name} must be a safe positive integer`);
979
+ }
980
+ }
981
+ return null;
982
+ }
983
+
984
+ function issueHumanOverride(request: HumanOverrideRequest, options: HumanOverrideOptions = {}): HumanOverrideIssueResult {
985
+ const invalid = validateCommon(request);
986
+ if (invalid) return failedIssue(invalid.code, invalid.message);
987
+ const policy = policyFor(request.failureId);
988
+ if (!policy) return failedIssue('OVERRIDE_FAILURE_UNKNOWN', `failure policy '${request.failureId}' is not registered`);
989
+ if (!policy.targets.includes(request.target)) return failedIssue('OVERRIDE_TARGET_INVALID', `target '${request.target}' is not registered for ${request.failureId}`);
990
+ const invalidResource = validateResourceNumbers(request);
991
+ if (invalidResource) return failedIssue(invalidResource.code, invalidResource.message);
992
+ const recordOnly = policy.outcomes.some((rule) => rule.target === request.target && rule.effect === 'record-only' && rule.result === 'preserve-failure');
993
+ if (policy.eligibility === 'never-overridable' && !recordOnly) return failedIssue('OVERRIDE_NOT_ELIGIBLE', `failure '${request.failureId}' is never overridable`);
994
+ const issuedAt = (options.now ?? canonicalTimestamp)();
995
+ const issuedMs = Date.parse(issuedAt);
996
+ const expiresMs = Date.parse(request.expiresAt);
997
+ if (!Number.isFinite(issuedMs) || !Number.isFinite(expiresMs) || expiresMs <= issuedMs) {
998
+ return failedIssue('OVERRIDE_EXPIRY_INVALID', 'expiresAt must be a valid time after issuedAt');
999
+ }
1000
+ const resolved = resolveTask(request.taskRef, options);
1001
+ if (!resolved.ok) return failedIssue(resolved.code, resolved.message, resolved.taskId);
1002
+ const document = readTask(resolved.taskMdPath);
1003
+ if ('code' in document) return failedIssue(document.code, document.message, resolved.taskId);
1004
+ if (document.frontmatter.id !== resolved.taskId) return failedIssue('OVERRIDE_DOCUMENT_INVALID', 'task frontmatter id does not match resolved task', resolved.taskId);
1005
+ const snapshotResult = captureFailureSnapshot(request.taskRef, request.failureId, request.target, {
1006
+ ...options,
1007
+ probeIntent: request.intent,
1008
+ probeOperator: request.operator,
1009
+ probeReason: request.reason,
1010
+ probeAlertNumber: request.alertNumber,
1011
+ probeIssueNumber: request.issueNumber,
1012
+ probePullRequestNumber: request.pullRequestNumber,
1013
+ probeStagingDir: request.stagingDir
1014
+ });
1015
+ if (isOverrideError(snapshotResult)) return failedIssue(snapshotResult.code, snapshotResult.message, resolved.taskId);
1016
+ const snapshot = snapshotResult as FailureSnapshot;
1017
+ if (request.failureSnapshot) {
1018
+ const snapshotError = validateSnapshot(request.failureSnapshot, snapshot);
1019
+ if (snapshotError) return failedIssue(snapshotError.code, snapshotError.message, resolved.taskId);
1020
+ }
1021
+ if (request.failureDigest !== undefined && request.failureDigest !== snapshot.digest) {
1022
+ return failedIssue('OVERRIDE_DIGEST_MISMATCH', 'caller-provided failure digest does not match the producer probe', resolved.taskId);
1023
+ }
1024
+ let ledger: ParsedHumanOverrideLedger | null = null;
1025
+ try {
1026
+ ledger = parseHumanOverrideLedger(document.content);
1027
+ } catch (cause) {
1028
+ return failedIssue('OVERRIDE_DOCUMENT_INVALID', cause instanceof Error ? cause.message : String(cause), resolved.taskId);
1029
+ }
1030
+ const ticketId = (options.randomId ?? randomUUID)();
1031
+ if (!singleLine(ticketId)) return failedIssue('OVERRIDE_PAYLOAD_INVALID', 'ticket id generator returned an invalid id', resolved.taskId);
1032
+ const mutations = [] as Array<
1033
+ | { kind: 'section'; aliases: readonly string[]; heading: string; body: string }
1034
+ | { kind: 'table-row'; sectionAliases: readonly string[]; columns: readonly string[]; keyColumn: string; key: string; action: 'upsert'; values: Readonly<Record<string, string>> }
1035
+ >;
1036
+ if (!ledger) {
1037
+ const header = `| ${HUMAN_OVERRIDE_COLUMNS.join(' | ')} |`;
1038
+ const separator = `| ${HUMAN_OVERRIDE_COLUMNS.map(() => '---').join(' | ')} |`;
1039
+ mutations.push({
1040
+ kind: 'section',
1041
+ aliases: HUMAN_OVERRIDE_SECTION_ALIASES,
1042
+ heading: 'Human Override Ledger',
1043
+ body: `${header}\n${separator}`
1044
+ });
1045
+ } else {
1046
+ const migration = migrateHumanOverrideLedger(ledger);
1047
+ if (migration) mutations.push(migration);
1048
+ }
1049
+ const [producerId, ...codeParts] = request.failureId.split(':');
1050
+ const code = codeParts.join(':');
1051
+ mutations.push({
1052
+ kind: 'table-row',
1053
+ sectionAliases: HUMAN_OVERRIDE_SECTION_ALIASES,
1054
+ columns: HUMAN_OVERRIDE_COLUMNS,
1055
+ keyColumn: 'id',
1056
+ key: ticketId,
1057
+ action: 'upsert',
1058
+ values: {
1059
+ schema: '1',
1060
+ failure_id: request.failureId,
1061
+ producer_id: producerId ?? '',
1062
+ code,
1063
+ target: request.target,
1064
+ operator: request.operator,
1065
+ identity_source: 'local-declared',
1066
+ verified: 'false',
1067
+ reason: request.reason,
1068
+ scope: request.scope,
1069
+ intent: request.intent ?? '',
1070
+ failure_message: snapshot.message,
1071
+ failure_digest: snapshot.digest,
1072
+ context_id: snapshot.observedFacts[0] ?? 'context-unmatched',
1073
+ effect: '',
1074
+ source_state: resolved.state,
1075
+ issued_at: issuedAt,
1076
+ expires_at: request.expiresAt,
1077
+ status: 'issued',
1078
+ result: '',
1079
+ consumed_at: '',
1080
+ residual: '',
1081
+ alert_number: request.alertNumber === undefined ? '' : String(request.alertNumber),
1082
+ issue_number: request.issueNumber === undefined ? '' : String(request.issueNumber),
1083
+ pull_request_number: request.pullRequestNumber === undefined ? '' : String(request.pullRequestNumber),
1084
+ staging_dir: request.stagingDir ?? ''
1085
+ }
1086
+ });
1087
+ const result = writeTask({ taskRef: request.taskRef, expectedState: resolved.state, mutations }, writeOptions(options));
1088
+ const writeError = taskWriteError(result);
1089
+ if (writeError) return failedIssue(writeError.code, writeError.message, resolved.taskId);
1090
+ if (result.status === 'failed') return failedIssue(result.error.code, result.error.message, resolved.taskId);
1091
+ if (result.status === 'planned') return failedIssue('OVERRIDE_WRITE_INVALID', 'override writes cannot be planned', resolved.taskId);
1092
+ return {
1093
+ status: result.status,
1094
+ changed: result.changed,
1095
+ ticketId,
1096
+ taskId: resolved.taskId,
1097
+ identity: { source: 'local-declared', operator: request.operator, verified: false },
1098
+ error: null
1099
+ };
1100
+ }
1101
+
1102
+ function lifecycleRequestFor(
1103
+ taskRef: string,
1104
+ intent: string,
1105
+ operator: string,
1106
+ reason: string,
1107
+ values: Readonly<Record<string, string | undefined>> = {}
1108
+ ): TaskLifecycleRequest | OverrideError {
1109
+ if (!['block', 'activate', 'cancel', 'complete', 'restore', 'close-codescan', 'close-dependabot'].includes(intent)) {
1110
+ return error('OVERRIDE_EFFECT_INPUT_MISSING', 'a lifecycle intent is required to apply this override outcome');
1111
+ }
1112
+ if (intent === 'block') return { taskRef, intent: 'block', agent: operator, reason, unblockCondition: 'manual override recovery' };
1113
+ if (intent === 'activate') return { taskRef, intent: 'activate', agent: operator, note: `manual override: ${reason}` };
1114
+ if (intent === 'cancel') return { taskRef, intent: 'cancel', agent: operator, reason };
1115
+ if (intent === 'complete') return { taskRef, intent: 'complete', agent: operator };
1116
+ if (intent === 'close-codescan' || intent === 'close-dependabot') {
1117
+ const alertNumber = Number(values.alert_number);
1118
+ if (!Number.isSafeInteger(alertNumber) || alertNumber < 1) return error('OVERRIDE_EFFECT_INPUT_MISSING', `${intent} requires the original alert number`);
1119
+ return { taskRef, intent, agent: operator, alertNumber, reason };
1120
+ }
1121
+ const issueNumber = Number(values.issue_number);
1122
+ const stagingDir = values.staging_dir;
1123
+ if (!Number.isSafeInteger(issueNumber) || issueNumber < 1 || !stagingDir) return error('OVERRIDE_EFFECT_INPUT_MISSING', 'restore requires the original staging directory and issue number');
1124
+ return { taskRef, intent: 'restore', agent: operator, stagingDir, issueNumber };
1125
+ }
1126
+
1127
+ function applyOutcomeEffect(
1128
+ request: ConsumeHumanOverrideRequest,
1129
+ row: Readonly<Record<string, string>>,
1130
+ snapshot: FailureSnapshot,
1131
+ outcome: Outcome,
1132
+ options: HumanOverrideOptions
1133
+ ): OverrideError | null {
1134
+ if (outcome.effect === 'no-write' || outcome.effect === 'record-only') return null;
1135
+ const intent = outcome.target === 'safe-close' ? 'cancel' : row.intent || request.intent;
1136
+ if (!intent && outcome.target === 'continue-local') {
1137
+ if (!options.effectExecutor) return error('OVERRIDE_EFFECT_EXECUTOR_MISSING', 'the producer must supply an effect executor for a continue-local outcome');
1138
+ return options.effectExecutor({
1139
+ failureId: request.failureId,
1140
+ operator: row.operator ?? 'local-operator',
1141
+ reason: row.reason ?? snapshot.message
1142
+ });
1143
+ }
1144
+ if (!intent) return error('OVERRIDE_EFFECT_INPUT_MISSING', 'the ticket has no original lifecycle intent for its retry effect');
1145
+ const lifecycleRequest = lifecycleRequestFor(request.taskRef, intent, row.operator ?? 'local-operator', row.reason ?? snapshot.message, row);
1146
+ if ('code' in lifecycleRequest) return lifecycleRequest;
1147
+ const result = applyTaskLifecycle(lifecycleRequest, {
1148
+ repoRoot: options.repoRoot,
1149
+ metadataProvider: options.metadataProvider,
1150
+ manualOverride: { failureId: request.failureId, operator: row.operator ?? 'local-operator', reason: row.reason ?? snapshot.message }
1151
+ });
1152
+ if (result.status === 'failed') {
1153
+ return error('OVERRIDE_EFFECT_FAILED', `${result.error?.code ?? 'LIFECYCLE_FAILED'}: ${result.error?.message ?? 'manual outcome effect failed'}`);
1154
+ }
1155
+ return null;
1156
+ }
1157
+
1158
+ function ticketMutation(
1159
+ ticketId: string,
1160
+ row: Readonly<Record<string, string>>,
1161
+ outcome: Outcome,
1162
+ status: 'consuming' | 'consumed',
1163
+ timestamp: string,
1164
+ residual: string,
1165
+ columns: readonly string[]
1166
+ ) {
1167
+ const values: Record<string, string> = {
1168
+ schema: row.schema ?? '',
1169
+ failure_id: row.failure_id ?? '',
1170
+ producer_id: row.producer_id ?? '',
1171
+ code: row.code ?? '',
1172
+ target: row.target ?? '',
1173
+ operator: row.operator ?? '',
1174
+ identity_source: row.identity_source ?? '',
1175
+ verified: row.verified ?? '',
1176
+ reason: row.reason ?? '',
1177
+ scope: row.scope ?? '',
1178
+ intent: row.intent ?? '',
1179
+ failure_message: row.failure_message ?? '',
1180
+ failure_digest: row.failure_digest ?? '',
1181
+ context_id: outcome.contextId,
1182
+ effect: outcome.effect,
1183
+ source_state: row.source_state ?? '',
1184
+ issued_at: row.issued_at ?? '',
1185
+ expires_at: row.expires_at ?? '',
1186
+ status,
1187
+ result: outcome.result,
1188
+ consumed_at: status === 'consumed' ? timestamp : '',
1189
+ residual,
1190
+ alert_number: row.alert_number ?? '',
1191
+ issue_number: row.issue_number ?? '',
1192
+ staging_dir: row.staging_dir ?? ''
1193
+ };
1194
+ if (columns.includes(PULL_REQUEST_NUMBER_COLUMN)) values[PULL_REQUEST_NUMBER_COLUMN] = row[PULL_REQUEST_NUMBER_COLUMN] ?? '';
1195
+ return {
1196
+ kind: 'table-row' as const,
1197
+ sectionAliases: HUMAN_OVERRIDE_SECTION_ALIASES,
1198
+ columns,
1199
+ keyColumn: 'id',
1200
+ key: ticketId,
1201
+ action: 'upsert' as const,
1202
+ values
1203
+ };
1204
+ }
1205
+
1206
+ function outcomeFromConsumingRow(policy: FailurePolicy, row: Readonly<Record<string, string>>, target: string): Outcome | OverrideError {
1207
+ const contextId = row.context_id;
1208
+ if (!contextId || !row.effect || !row.result || !row.residual) {
1209
+ return error('OVERRIDE_RECOVERY_INVALID', 'consuming override ticket has incomplete effect state');
1210
+ }
1211
+ const outcome = resolveOutcome(policy, target, [contextId]);
1212
+ if (
1213
+ outcome.contextId !== contextId || outcome.effect !== row.effect || outcome.result !== row.result ||
1214
+ outcome.residual !== row.residual.replace(/^effect pending: /, '')
1215
+ ) {
1216
+ return error('OVERRIDE_RECOVERY_INVALID', 'consuming override ticket does not match its registered outcome');
1217
+ }
1218
+ return outcome;
1219
+ }
1220
+
1221
+ function snapshotFromTicket(taskId: string, row: Readonly<Record<string, string>>): FailureSnapshot | OverrideError {
1222
+ const [producerId, ...codeParts] = (row.failure_id ?? '').split(':');
1223
+ if (!producerId || codeParts.length === 0 || !row.failure_digest || !row.failure_message || !row.source_state) {
1224
+ return error('OVERRIDE_RECOVERY_INVALID', 'consuming override ticket has incomplete failure evidence');
1225
+ }
1226
+ return {
1227
+ schema: '1',
1228
+ taskId,
1229
+ failureId: row.failure_id,
1230
+ producerId,
1231
+ code: codeParts.join(':'),
1232
+ target: row.target,
1233
+ state: row.source_state,
1234
+ digest: row.failure_digest,
1235
+ observedFacts: row.context_id ? [row.context_id] : [],
1236
+ message: row.failure_message,
1237
+ capturedAt: row.issued_at,
1238
+ source: 'producer-result'
1239
+ };
1240
+ }
1241
+
1242
+ function consumeHumanOverride(request: ConsumeHumanOverrideRequest, options: HumanOverrideOptions = {}): HumanOverrideConsumeResult {
1243
+ const values = [request.taskRef, request.ticketId, request.failureId, request.target, request.scope];
1244
+ if (values.some((value) => !singleLine(value))) {
1245
+ return failedConsume('OVERRIDE_PAYLOAD_INVALID', 'consume fields must be non-empty single-line values', request.ticketId || null);
1246
+ }
1247
+ const policy = policyFor(request.failureId);
1248
+ if (!policy) return failedConsume('OVERRIDE_FAILURE_UNKNOWN', `failure policy '${request.failureId}' is not registered`, request.ticketId);
1249
+ if (!policy.targets.includes(request.target)) return failedConsume('OVERRIDE_TARGET_INVALID', `target '${request.target}' is not registered for ${request.failureId}`, request.ticketId);
1250
+ const resolved = resolveTask(request.taskRef, options);
1251
+ if (!resolved.ok) return failedConsume(resolved.code, resolved.message, request.ticketId, resolved.taskId);
1252
+ const document = readTask(resolved.taskMdPath);
1253
+ if ('code' in document) return failedConsume(document.code, document.message, request.ticketId, resolved.taskId);
1254
+ if (document.frontmatter.id !== resolved.taskId) return failedConsume('OVERRIDE_DOCUMENT_INVALID', 'task frontmatter id does not match resolved task', request.ticketId, resolved.taskId);
1255
+ let ledger: ParsedHumanOverrideLedger | null;
1256
+ try {
1257
+ ledger = parseHumanOverrideLedger(document.content);
1258
+ } catch (cause) {
1259
+ return failedConsume('OVERRIDE_DOCUMENT_INVALID', cause instanceof Error ? cause.message : String(cause), request.ticketId, resolved.taskId);
1260
+ }
1261
+ if (!ledger) return failedConsume('OVERRIDE_NOT_FOUND', `override ledger is missing for ticket '${request.ticketId}'`, request.ticketId, resolved.taskId);
1262
+ const row = ledger.table.rows.find((item) => item.values.id === request.ticketId);
1263
+ if (!row) return failedConsume('OVERRIDE_NOT_FOUND', `override ticket '${request.ticketId}' was not found`, request.ticketId, resolved.taskId);
1264
+ if (row.values.status === 'consumed') return failedConsume('OVERRIDE_REPLAY', `override ticket '${request.ticketId}' was already consumed`, request.ticketId, resolved.taskId);
1265
+ const recovering = row.values.status === 'consuming';
1266
+ if (row.values.status !== 'issued' && !recovering) return failedConsume('OVERRIDE_STATUS_INVALID', `override ticket '${request.ticketId}' is not issued`, request.ticketId, resolved.taskId);
1267
+ if (row.values.failure_id !== request.failureId) return failedConsume('OVERRIDE_FAILURE_MISMATCH', 'ticket failure identity does not match consume request', request.ticketId, resolved.taskId);
1268
+ if (row.values.target !== request.target) return failedConsume('OVERRIDE_TARGET_INVALID', 'ticket target does not match consume request', request.ticketId, resolved.taskId);
1269
+ if (row.values.scope !== request.scope) return failedConsume('OVERRIDE_SCOPE_MISMATCH', 'ticket scope does not match consume request', request.ticketId, resolved.taskId);
1270
+ const resourceError = validatePullRequestBinding(policy, row.values, request);
1271
+ if (resourceError) return failedConsume(resourceError.code, resourceError.message, request.ticketId, resolved.taskId);
1272
+ let snapshot: FailureSnapshot;
1273
+ if (recovering) {
1274
+ const recoverySnapshot = snapshotFromTicket(resolved.taskId, row.values);
1275
+ if (isOverrideError(recoverySnapshot)) return failedConsume(recoverySnapshot.code, recoverySnapshot.message, request.ticketId, resolved.taskId);
1276
+ snapshot = recoverySnapshot;
1277
+ } else {
1278
+ const snapshotResult = captureFailureSnapshot(request.taskRef, request.failureId, request.target, {
1279
+ ...options,
1280
+ probeIntent: row.values.intent ? row.values.intent as TaskLifecycleIntent : request.intent,
1281
+ probeOperator: row.values.operator,
1282
+ probeReason: row.values.reason,
1283
+ probeAlertNumber: row.values.alert_number ? Number(row.values.alert_number) : undefined,
1284
+ probeIssueNumber: row.values.issue_number ? Number(row.values.issue_number) : undefined,
1285
+ probePullRequestNumber: row.values.pull_request_number ? Number(row.values.pull_request_number) : undefined,
1286
+ probeStagingDir: row.values.staging_dir || undefined
1287
+ });
1288
+ if (isOverrideError(snapshotResult)) return failedConsume(snapshotResult.code, snapshotResult.message, request.ticketId, resolved.taskId);
1289
+ snapshot = snapshotResult as FailureSnapshot;
1290
+ if (request.failureSnapshot) {
1291
+ const snapshotError = validateSnapshot(request.failureSnapshot, snapshot);
1292
+ if (snapshotError) return failedConsume(snapshotError.code, snapshotError.message, request.ticketId, resolved.taskId);
1293
+ }
1294
+ if (request.failureDigest !== undefined && request.failureDigest !== snapshot.digest) return failedConsume('OVERRIDE_DIGEST_MISMATCH', 'caller-provided failure digest does not match the producer result', request.ticketId, resolved.taskId);
1295
+ if (row.values.failure_digest !== snapshot.digest) return failedConsume('OVERRIDE_DIGEST_MISMATCH', 'ticket failure digest does not match the producer result', request.ticketId, resolved.taskId);
1296
+ if (request.observedFacts && (request.observedFacts.length !== snapshot.observedFacts.length || request.observedFacts.some((fact, index) => fact !== snapshot.observedFacts[index]))) {
1297
+ return failedConsume('OVERRIDE_FACTS_MISMATCH', 'caller-provided facts do not match the producer result', request.ticketId, resolved.taskId);
1298
+ }
1299
+ if (row.values.source_state && row.values.source_state !== snapshot.state) {
1300
+ return failedConsume('OVERRIDE_SNAPSHOT_STALE', 'the task no longer has the state captured when the ticket was issued', request.ticketId, resolved.taskId);
1301
+ }
1302
+ }
1303
+ const now = (options.now ?? canonicalTimestamp)();
1304
+ const nowMs = Date.parse(now);
1305
+ const expiresMs = Date.parse(row.values.expires_at ?? '');
1306
+ if (!recovering && (!Number.isFinite(nowMs) || !Number.isFinite(expiresMs) || expiresMs <= nowMs)) return failedConsume('OVERRIDE_EXPIRED', `override ticket '${request.ticketId}' is expired`, request.ticketId, resolved.taskId);
1307
+ const resolvedOutcome = recovering ? outcomeFromConsumingRow(policy, row.values, request.target) : resolveOutcome(policy, request.target, snapshot.observedFacts);
1308
+ if ('code' in resolvedOutcome) return failedConsume(resolvedOutcome.code, resolvedOutcome.message, request.ticketId, resolved.taskId);
1309
+ const outcome = resolvedOutcome;
1310
+ let effectRow = row.values;
1311
+ if (!recovering && outcome.effect !== 'no-write' && outcome.effect !== 'record-only') {
1312
+ const staged = writeTask({
1313
+ taskRef: request.taskRef,
1314
+ expectedState: resolved.state,
1315
+ mutations: [ticketMutation(request.ticketId, row.values, outcome, 'consuming', now, `effect pending: ${outcome.residual}`, ledger.columns)]
1316
+ }, writeOptions(options));
1317
+ const stagedError = taskWriteError(staged);
1318
+ if (stagedError) return failedConsume(stagedError.code, stagedError.message, request.ticketId, resolved.taskId);
1319
+ if (staged.status === 'failed') return failedConsume(staged.error.code, staged.error.message, request.ticketId, resolved.taskId);
1320
+ if (staged.status === 'planned') return failedConsume('OVERRIDE_WRITE_INVALID', 'override writes cannot be planned', request.ticketId, resolved.taskId);
1321
+ effectRow = { ...row.values, status: 'consuming', effect: outcome.effect, result: outcome.result, context_id: outcome.contextId, residual: `effect pending: ${outcome.residual}` };
1322
+ }
1323
+ const effectError = applyOutcomeEffect(request, effectRow, snapshot, outcome, options);
1324
+ if (effectError) return failedConsume(effectError.code, effectError.message, request.ticketId, resolved.taskId);
1325
+ const after = resolveTask(request.taskRef, options);
1326
+ if (!after.ok) return failedConsume(after.code, after.message, request.ticketId, after.taskId);
1327
+ const mutation = ticketMutation(request.ticketId, effectRow, outcome, 'consumed', now, outcome.residual, ledger.columns);
1328
+ const result = writeTask({ taskRef: request.taskRef, expectedState: after.state, mutations: [mutation] }, writeOptions(options));
1329
+ const writeError = taskWriteError(result);
1330
+ if (writeError) return failedConsume(writeError.code, writeError.message, request.ticketId, resolved.taskId);
1331
+ if (result.status === 'failed') return failedConsume(result.error.code, result.error.message, request.ticketId, resolved.taskId);
1332
+ if (result.status === 'planned') return failedConsume('OVERRIDE_WRITE_INVALID', 'override writes cannot be planned', request.ticketId, resolved.taskId);
1333
+ return {
1334
+ status: result.status,
1335
+ changed: result.changed,
1336
+ ticketId: request.ticketId,
1337
+ taskId: resolved.taskId,
1338
+ outcome,
1339
+ ...(outcome.effect !== 'record-only' && outcome.effect !== 'no-write'
1340
+ ? { manualOverride: { failureId: request.failureId, operator: row.values.operator ?? 'local-operator', reason: row.values.reason ?? snapshot.message } }
1341
+ : {}),
1342
+ error: null
1343
+ };
1344
+ }
1345
+
1346
+ function diagnoseHumanOverride(failure: string, target?: string) {
1347
+ const policy = policyFor(failure);
1348
+ if (!policy) return { status: 'failed' as const, changed: false as const, error: error('OVERRIDE_FAILURE_UNKNOWN', `failure policy '${failure}' is not registered`) };
1349
+ return {
1350
+ status: 'ready' as const,
1351
+ changed: false as const,
1352
+ failure: policy,
1353
+ target: target ?? null,
1354
+ outcomes: target ? policy.outcomes.filter((rule) => rule.target === target) : policy.outcomes,
1355
+ error: null
1356
+ };
1357
+ }
1358
+
1359
+ function diagnoseHumanOverrideForTask(
1360
+ taskRef: string,
1361
+ failure?: string,
1362
+ target?: string,
1363
+ options: HumanOverrideOptions = {}
1364
+ ) {
1365
+ const resolved = resolveTask(taskRef, options);
1366
+ if (!resolved.ok) {
1367
+ return {
1368
+ status: 'failed' as const,
1369
+ changed: false as const,
1370
+ taskId: resolved.taskId,
1371
+ state: null,
1372
+ blockedBy: [],
1373
+ probes: [],
1374
+ error: error(resolved.code, resolved.message)
1375
+ };
1376
+ }
1377
+ const document = readTask(resolved.taskMdPath);
1378
+ if ('code' in document) {
1379
+ return {
1380
+ status: 'failed' as const,
1381
+ changed: false as const,
1382
+ taskId: resolved.taskId,
1383
+ state: resolved.state,
1384
+ blockedBy: [],
1385
+ probes: [],
1386
+ error: document
1387
+ };
1388
+ }
1389
+ const policies = failure
1390
+ ? [policyFor(failure)].filter((policy): policy is FailurePolicy => policy !== null)
1391
+ : HUMAN_OVERRIDE_FAILURE_REGISTRY;
1392
+ if (failure && policies.length === 0) {
1393
+ return {
1394
+ status: 'failed' as const,
1395
+ changed: false as const,
1396
+ taskId: resolved.taskId,
1397
+ state: resolved.state,
1398
+ blockedBy: [],
1399
+ probes: [],
1400
+ error: error('OVERRIDE_FAILURE_UNKNOWN', `failure policy '${failure}' is not registered`)
1401
+ };
1402
+ }
1403
+ const probes = policies
1404
+ .filter((policy) => !target || policy.targets.includes(target))
1405
+ .map((policy) => {
1406
+ const producer = policy.producerId === LIFECYCLE_PRODUCER
1407
+ ? probeLifecycleFailure(taskRef, policy, resolved, options)
1408
+ : probeGenericFailure(taskRef, policy, resolved, options);
1409
+ const observed = isProducerFailure(producer);
1410
+ return {
1411
+ failureId: policy.id,
1412
+ producerId: policy.producerId,
1413
+ guardId: policy.guardId,
1414
+ code: policy.code,
1415
+ target: target && policy.targets.includes(target) ? target : policy.targets[0]!,
1416
+ eligibility: policy.eligibility,
1417
+ status: observed ? 'observed' as const : producer.code === 'OVERRIDE_PROBE_INPUT_MISSING' ? 'unproven' as const : 'not-observed' as const,
1418
+ observedFacts: observed ? producer.observedFacts : [],
1419
+ message: observed ? producer.message : producer.message,
1420
+ errorCode: observed ? null : producer.code
1421
+ };
1422
+ });
1423
+ const blockedBy = probes.filter((probe) => probe.status === 'observed');
1424
+ return {
1425
+ status: 'ready' as const,
1426
+ changed: false as const,
1427
+ taskId: resolved.taskId,
1428
+ state: resolved.state,
1429
+ snapshot: {
1430
+ taskId: resolved.taskId,
1431
+ state: resolved.state,
1432
+ documentDigest: computeFailureDigest(document.content)
1433
+ },
1434
+ blockedBy,
1435
+ probes,
1436
+ error: null
1437
+ };
1438
+ }
1439
+
1440
+ function renderHumanOverrideAudit(content: string): string {
1441
+ let ledger: ParsedHumanOverrideLedger | null;
1442
+ try {
1443
+ ledger = parseHumanOverrideLedger(content);
1444
+ } catch {
1445
+ return '';
1446
+ }
1447
+ if (!ledger || ledger.table.rows.length === 0) return '';
1448
+ const rows = ledger.table.rows.map(({ values }) => [
1449
+ `- ${values.status || 'unknown'}: ${values.failure_id || 'unknown failure'}`,
1450
+ values.pull_request_number ? `resource=pull-request#${values.pull_request_number}` : values.issue_number ? `resource=issue#${values.issue_number}` : 'resource=local',
1451
+ `target=${values.target || 'unknown'}`,
1452
+ `context=${values.context_id || 'unknown'}`,
1453
+ `effect=${values.effect || 'unknown'}`,
1454
+ `result=${values.result || 'pending'}`,
1455
+ `operator=${values.operator || 'unknown'}`,
1456
+ `identity_source=${values.identity_source || 'unknown'}`,
1457
+ `reason=${values.reason || 'not recorded'}`,
1458
+ `residual=${values.residual || 'pending'}`
1459
+ ].join('; '));
1460
+ return `## Human Override Audit\n\n${rows.join('\n')}`;
1461
+ }
1462
+
1463
+ function computeFailureDigest(value: string): string {
1464
+ return createHash('sha256').update(value).digest('hex');
1465
+ }
1466
+
1467
+ export {
1468
+ HUMAN_OVERRIDE_COLUMNS,
1469
+ HUMAN_OVERRIDE_FAILURE_REGISTRY,
1470
+ HUMAN_OVERRIDE_SECTION_ALIASES,
1471
+ LIFECYCLE_PRODUCER,
1472
+ computeFailureDigest,
1473
+ captureFailureSnapshot,
1474
+ consumeHumanOverride,
1475
+ diagnoseHumanOverride,
1476
+ diagnoseHumanOverrideForTask,
1477
+ failureId,
1478
+ issueHumanOverride,
1479
+ overrideDryRunConflict,
1480
+ renderHumanOverrideAudit,
1481
+ resolveOutcome,
1482
+ stableFailureCatalog,
1483
+ validatePolicy,
1484
+ validateRegistry
1485
+ };
1486
+ export type {
1487
+ ConsumeHumanOverrideRequest,
1488
+ Eligibility,
1489
+ FailureIdentity,
1490
+ FailurePolicy,
1491
+ HumanOverrideConsumeResult,
1492
+ HumanOverrideIdentity,
1493
+ HumanOverrideIssueResult,
1494
+ HumanOverrideOptions,
1495
+ HumanOverrideRequest,
1496
+ FailureSnapshot,
1497
+ Outcome,
1498
+ OutcomeContext,
1499
+ OutcomeEffect,
1500
+ OutcomeFallback,
1501
+ OutcomeResult,
1502
+ OutcomeRule
1503
+ };