@fitlab-ai/agent-infra 0.11.0 → 0.11.2

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 (146) hide show
  1. package/dist/lib/internal/cli-route-inventory.js +1 -1
  2. package/dist/lib/internal/manual-validation.js +6 -2
  3. package/dist/lib/internal/platform-comment.js +59 -0
  4. package/dist/lib/internal/sandbox-control.js +4 -131
  5. package/dist/lib/internal/task-artifact.js +1 -1
  6. package/dist/lib/internal/task-operation-registry.js +1 -0
  7. package/dist/lib/internal/task-review.js +5 -5
  8. package/dist/lib/internal/task-validate.js +0 -6
  9. package/dist/lib/platform/comment-safety.js +7 -1
  10. package/dist/lib/platform/github-provider.js +13 -3
  11. package/dist/lib/platform/issue-comments.js +120 -13
  12. package/dist/lib/platform/issues.js +3 -4
  13. package/dist/lib/platform/provider-contract.d.ts +2 -0
  14. package/dist/lib/platform/provider-validation.js +7 -2
  15. package/dist/lib/platform/verification-sync.js +161 -156
  16. package/dist/lib/sandbox/control/executor.js +0 -38
  17. package/dist/lib/sandbox/control/finalization-response.js +10 -2
  18. package/dist/lib/sandbox/control/task-view.js +3 -1
  19. package/dist/lib/sandbox/control/workflow-executor.js +32 -41
  20. package/dist/lib/task/artifact-command.js +6 -6
  21. package/dist/lib/task/artifact-lifecycle.js +15 -52
  22. package/dist/lib/task/artifact-operations.js +27 -1
  23. package/dist/lib/task/capabilities.js +0 -2
  24. package/dist/lib/task/codex-orchestration.js +10 -208
  25. package/dist/lib/task/commit-operation.js +3 -18
  26. package/dist/lib/task/control-authority.js +1 -8
  27. package/dist/lib/task/delegation-receipts.js +14 -76
  28. package/dist/lib/task/events.js +147 -278
  29. package/dist/lib/task/finalization-state.js +2 -1
  30. package/dist/lib/task/finalization.js +273 -49
  31. package/dist/lib/task/gate-policy.js +94 -0
  32. package/dist/lib/task/lifecycle-execution.js +2 -11
  33. package/dist/lib/task/local-artifact-finalization.js +31 -245
  34. package/dist/lib/task/orchestration.js +8 -24
  35. package/dist/lib/task/review-artifacts.js +12 -6
  36. package/dist/lib/task/review-command.js +0 -1
  37. package/dist/lib/task/review-finalization.js +47 -218
  38. package/dist/lib/task/verification-engine.js +28 -17
  39. package/dist/lib/task/verification.js +25 -9
  40. package/dist/lib/task/workflow-command.js +4 -1
  41. package/dist/lib/task/workflow-dispatch.js +55 -3
  42. package/dist/lifecycle-build-manifest.json +3 -3
  43. package/lib/internal/cli-route-inventory.ts +1 -1
  44. package/lib/internal/manual-validation.ts +6 -2
  45. package/lib/internal/platform-comment.ts +38 -0
  46. package/lib/internal/sandbox-control.ts +3 -145
  47. package/lib/internal/task-artifact.ts +1 -1
  48. package/lib/internal/task-operation-registry.ts +1 -0
  49. package/lib/internal/task-review.ts +5 -5
  50. package/lib/internal/task-validate.ts +0 -5
  51. package/lib/platform/comment-safety.ts +8 -0
  52. package/lib/platform/github-provider.ts +11 -2
  53. package/lib/platform/issue-comments.ts +132 -13
  54. package/lib/platform/issues.ts +3 -4
  55. package/lib/platform/provider-contract.ts +2 -0
  56. package/lib/platform/provider-validation.ts +6 -2
  57. package/lib/platform/verification-sync.ts +178 -179
  58. package/lib/sandbox/control/executor.ts +0 -44
  59. package/lib/sandbox/control/finalization-response.ts +10 -2
  60. package/lib/sandbox/control/task-view.ts +3 -1
  61. package/lib/sandbox/control/workflow-executor.ts +31 -48
  62. package/lib/task/artifact-command.ts +6 -7
  63. package/lib/task/artifact-lifecycle.ts +12 -49
  64. package/lib/task/artifact-operations.ts +27 -1
  65. package/lib/task/capabilities.ts +0 -1
  66. package/lib/task/codex-orchestration.ts +8 -241
  67. package/lib/task/commit-operation.ts +3 -23
  68. package/lib/task/control-authority.ts +1 -8
  69. package/lib/task/delegation-receipts.ts +12 -79
  70. package/lib/task/events.ts +155 -287
  71. package/lib/task/finalization-state.ts +2 -1
  72. package/lib/task/finalization.ts +277 -57
  73. package/lib/task/gate-policy.ts +133 -0
  74. package/lib/task/lifecycle-execution.ts +10 -21
  75. package/lib/task/local-artifact-finalization.ts +29 -285
  76. package/lib/task/orchestration.ts +12 -41
  77. package/lib/task/review-artifacts.ts +8 -2
  78. package/lib/task/review-command.ts +0 -1
  79. package/lib/task/review-finalization.ts +46 -200
  80. package/lib/task/verification-engine.ts +24 -14
  81. package/lib/task/verification-types.ts +7 -0
  82. package/lib/task/verification.ts +25 -9
  83. package/lib/task/workflow-command.ts +4 -1
  84. package/lib/task/workflow-dispatch.ts +46 -3
  85. package/package.json +2 -2
  86. package/templates/.agents/rules/issue-sync.en.md +2 -0
  87. package/templates/.agents/rules/issue-sync.zh-CN.md +5 -8
  88. package/templates/.agents/rules/lifecycle-orchestration.en.md +7 -7
  89. package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +7 -7
  90. package/templates/.agents/rules/local-artifact-repair.en.md +14 -52
  91. package/templates/.agents/rules/local-artifact-repair.zh-CN.md +13 -52
  92. package/templates/.agents/skills/analyze-task/SKILL.en.md +3 -3
  93. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +3 -3
  94. package/templates/.agents/skills/analyze-task/config/verify.en.json +1 -10
  95. package/templates/.agents/skills/analyze-task/config/verify.zh-CN.json +1 -10
  96. package/templates/.agents/skills/block-task/config/verify.json +1 -3
  97. package/templates/.agents/skills/cancel-task/config/verify.json +1 -4
  98. package/templates/.agents/skills/code-task/SKILL.en.md +3 -3
  99. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +3 -3
  100. package/templates/.agents/skills/code-task/config/verify.en.json +1 -12
  101. package/templates/.agents/skills/code-task/config/verify.zh-CN.json +1 -12
  102. package/templates/.agents/skills/code-task/reference/dual-mode.en.md +1 -1
  103. package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +1 -1
  104. package/templates/.agents/skills/code-task/reference/report-template.en.md +11 -0
  105. package/templates/.agents/skills/code-task/reference/report-template.zh-CN.md +11 -0
  106. package/templates/.agents/skills/commit/config/verify.json +1 -5
  107. package/templates/.agents/skills/complete-manual-validation/config/verify.en.json +6 -6
  108. package/templates/.agents/skills/complete-manual-validation/config/verify.zh-CN.json +6 -6
  109. package/templates/.agents/skills/complete-task/SKILL.en.md +3 -3
  110. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +3 -3
  111. package/templates/.agents/skills/complete-task/config/verify.en.json +3 -14
  112. package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +3 -14
  113. package/templates/.agents/skills/create-pr/config/verify.json +1 -10
  114. package/templates/.agents/skills/create-task/config/verify.json +1 -4
  115. package/templates/.agents/skills/import-issue/config/verify.json +1 -2
  116. package/templates/.agents/skills/plan-task/SKILL.en.md +3 -3
  117. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +3 -3
  118. package/templates/.agents/skills/plan-task/config/verify.en.json +1 -10
  119. package/templates/.agents/skills/plan-task/config/verify.zh-CN.json +1 -10
  120. package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +4 -4
  121. package/templates/.agents/skills/review-analysis/SKILL.en.md +2 -2
  122. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +2 -2
  123. package/templates/.agents/skills/review-analysis/config/verify.en.json +1 -10
  124. package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +1 -10
  125. package/templates/.agents/skills/review-analysis/reference/report-template.en.md +2 -0
  126. package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +2 -0
  127. package/templates/.agents/skills/review-code/SKILL.en.md +3 -3
  128. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +3 -3
  129. package/templates/.agents/skills/review-code/config/verify.en.json +1 -10
  130. package/templates/.agents/skills/review-code/config/verify.zh-CN.json +1 -10
  131. package/templates/.agents/skills/review-code/reference/report-template.en.md +2 -0
  132. package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +2 -0
  133. package/templates/.agents/skills/review-plan/SKILL.en.md +2 -2
  134. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +2 -2
  135. package/templates/.agents/skills/review-plan/config/verify.en.json +1 -10
  136. package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +1 -10
  137. package/templates/.agents/skills/review-plan/reference/report-template.en.md +2 -0
  138. package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +2 -0
  139. package/templates/.agents/skills/review-pr/config/verify.en.json +1 -8
  140. package/templates/.agents/skills/review-pr/config/verify.zh-CN.json +1 -8
  141. package/templates/.agents/skills/run-manual-validation/config/verify.en.json +38 -13
  142. package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +38 -13
  143. package/templates/.agents/skills/run-task/SKILL.en.md +6 -6
  144. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +6 -6
  145. package/templates/.agents/skills/run-task/reference/host-validation.en.md +2 -2
  146. package/templates/.agents/skills/run-task/reference/host-validation.zh-CN.md +2 -2
@@ -1,7 +1,10 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
- import { randomUUID } from 'node:crypto';
3
+ import { createHash, randomUUID } from 'node:crypto';
4
4
  import { syncPlatformComment } from '../platform/issue-comments.js';
5
+ import { canonicalizeSummaryBody } from '../platform/comment-safety.js';
6
+ import { taskIssueIdentity } from '../platform/task-identities.js';
7
+ import { parseTaskFrontmatter } from './frontmatter.js';
5
8
  import { applyTaskLifecycle } from './lifecycle.js';
6
9
  import { resolveTaskRef } from './resolve-ref.js';
7
10
  import { inspectShortIdRegistry } from './short-id.js';
@@ -9,8 +12,8 @@ import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-
9
12
  import { verifyTaskEvent } from './verification.js';
10
13
  import { projectFinalizationWarning } from './workflow-warning-intents.js';
11
14
  import { mergeOperationWarnings } from './operation-outcome.js';
12
- const RECEIPT_VERSION = 2;
13
- const FINALIZATION_STEPS = ['lifecycle', 'task-comment', 'verification'];
15
+ const RECEIPT_VERSION = 3;
16
+ const FINALIZATION_STEPS = ['lifecycle', 'task-comment', 'verification', 'summary', 'post-summary-verification'];
14
17
  function finalizationRoot(repoRoot) {
15
18
  return path.join(repoRoot, '.agents', 'workspace', '.task-finalization');
16
19
  }
@@ -34,6 +37,8 @@ function failed(taskId, error, overrides = {}) {
34
37
  lifecycle: null,
35
38
  taskComment: null,
36
39
  verification: null,
40
+ summary: null,
41
+ postSummaryVerification: null,
37
42
  completedSteps: [],
38
43
  pendingSteps: [...FINALIZATION_STEPS],
39
44
  result: error.retryable ? 'blocked' : 'failed',
@@ -52,8 +57,11 @@ function emptyReceipt(taskId, controlBinding) {
52
57
  lifecycle: 'pending',
53
58
  taskComment: 'pending',
54
59
  verification: 'pending',
60
+ summary: 'pending',
61
+ postSummaryVerification: 'pending',
55
62
  warningProjection: 'done',
56
63
  warnings: [],
64
+ summarySha256: null,
57
65
  ...(controlBinding ? { controlBinding } : {}),
58
66
  updatedAt: now(),
59
67
  lastError: null
@@ -88,8 +96,11 @@ function validateReceipt(value, taskId) {
88
96
  || !states.includes(String(receipt.lifecycle))
89
97
  || !states.includes(String(receipt.taskComment))
90
98
  || !states.includes(String(receipt.verification))
99
+ || !states.includes(String(receipt.summary))
100
+ || !states.includes(String(receipt.postSummaryVerification))
91
101
  || !['pending', 'done'].includes(String(receipt.warningProjection))
92
102
  || !Array.isArray(receipt.warnings) || receipt.warnings.some((warning) => !validWarning(warning))
103
+ || (receipt.summarySha256 !== null && (typeof receipt.summarySha256 !== 'string' || !/^[a-f0-9]{64}$/u.test(receipt.summarySha256)))
93
104
  || typeof receipt.updatedAt !== 'string'
94
105
  || (receipt.lastError !== null && (typeof receipt.lastError !== 'object' || Array.isArray(receipt.lastError)))
95
106
  || (receipt.controlBinding !== undefined && (!controlBinding || typeof controlBinding !== 'object'
@@ -109,10 +120,6 @@ function readReceipt(repoRoot, taskId) {
109
120
  function readTaskFinalizationReceipt(repoRoot, taskId) {
110
121
  return readReceipt(path.resolve(repoRoot), taskId);
111
122
  }
112
- function controlBindingMatches(receipt, binding) {
113
- return receipt.controlBinding?.generation === binding.generation
114
- && receipt.controlBinding.requestId === binding.requestId;
115
- }
116
123
  function writeReceipt(repoRoot, receipt) {
117
124
  const directory = finalizationRoot(repoRoot);
118
125
  fs.mkdirSync(directory, { recursive: true, mode: 0o700 });
@@ -132,20 +139,24 @@ function writeReceipt(repoRoot, receipt) {
132
139
  }
133
140
  function updateReceipt(repoRoot, receipt, patch) {
134
141
  const current = readReceipt(repoRoot, receipt.taskId);
135
- if (!current || current.receiptId !== receipt.receiptId || current.revision !== receipt.revision) {
136
- const error = new Error('finalization receipt revision is stale');
137
- Object.assign(error, { code: 'FINALIZATION_CAPABILITY_STALE' });
138
- throw error;
139
- }
142
+ if (!current || current.receiptId !== receipt.receiptId)
143
+ throw capabilityError('FINALIZATION_SCOPE_INVALID', 'finalization receipt does not belong to the current task');
140
144
  const next = { ...current, ...patch, revision: current.revision + 1, updatedAt: now() };
141
145
  writeReceipt(repoRoot, next);
142
146
  return next;
143
147
  }
144
148
  function completedSteps(receipt) {
145
- return FINALIZATION_STEPS.filter((step) => receipt[step === 'task-comment' ? 'taskComment' : step] !== 'pending');
149
+ return FINALIZATION_STEPS.filter((step) => stepState(receipt, step) !== 'pending');
146
150
  }
147
151
  function pendingSteps(receipt) {
148
- return FINALIZATION_STEPS.filter((step) => receipt[step === 'task-comment' ? 'taskComment' : step] === 'pending');
152
+ return FINALIZATION_STEPS.filter((step) => stepState(receipt, step) === 'pending');
153
+ }
154
+ function stepState(receipt, step) {
155
+ if (step === 'task-comment')
156
+ return receipt.taskComment;
157
+ if (step === 'post-summary-verification')
158
+ return receipt.postSummaryVerification;
159
+ return receipt[step];
149
160
  }
150
161
  function lifecycleStep(result) {
151
162
  return {
@@ -234,9 +245,8 @@ function mutationKeys(value) {
234
245
  return Object.keys(value).sort();
235
246
  }
236
247
  function validateCapabilityMutation(current, capability, mutation, consumed) {
237
- if (capability.receiptId !== current.receiptId || capability.baseRevision !== current.revision || consumed.has(capability.nonce)) {
238
- throw capabilityError('FINALIZATION_CAPABILITY_STALE', 'finalization capability is stale or outside its scope');
239
- }
248
+ if (capability.receiptId !== current.receiptId)
249
+ throw capabilityError('FINALIZATION_SCOPE_INVALID', 'finalization mutation belongs to a different task receipt');
240
250
  if (capability.scope !== mutation.scope) {
241
251
  throw capabilityError('FINALIZATION_SCOPE_INVALID', 'finalization capability mutation scope does not match its capability');
242
252
  }
@@ -403,9 +413,89 @@ async function syncPendingTaskComment(input) {
403
413
  return { receipt, step: { status: 'blocked', changed: false, error: detail }, changed: false, error: detail };
404
414
  }
405
415
  }
416
+ function readDeliverySummary(taskDir, taskId) {
417
+ const file = path.join(taskDir, '.delivery-summary.json');
418
+ let value;
419
+ try {
420
+ value = JSON.parse(fs.readFileSync(file, 'utf8'));
421
+ }
422
+ catch {
423
+ throw Object.assign(new Error('delivery summary staging record is unavailable'), { code: 'SUMMARY_STAGING_INVALID' });
424
+ }
425
+ if (value.taskId !== taskId || typeof value.body !== 'string' || typeof value.sha256 !== 'string') {
426
+ throw Object.assign(new Error('delivery summary staging record is invalid'), { code: 'SUMMARY_STAGING_INVALID' });
427
+ }
428
+ const canonical = canonicalizeSummaryBody(value.body);
429
+ if (!canonical.ok || canonical.value !== value.body) {
430
+ throw Object.assign(new Error('delivery summary staging body is not canonical'), { code: 'SUMMARY_STAGING_INVALID' });
431
+ }
432
+ const digest = createHash('sha256').update(canonical.value).digest('hex');
433
+ if (digest !== value.sha256)
434
+ throw Object.assign(new Error('delivery summary staging digest does not match body'), { code: 'SUMMARY_STAGING_INVALID' });
435
+ return { body: canonical.value, sha256: value.sha256 };
436
+ }
437
+ async function syncPendingSummary(input) {
438
+ let { receipt } = input;
439
+ if (receipt.summary !== 'pending') {
440
+ return { receipt, step: { status: receipt.summary === 'skipped' ? 'skipped' : 'no-op', changed: false, error: null }, changed: false, error: null };
441
+ }
442
+ try {
443
+ const resolved = resolveTaskRef(input.taskId, { repoRoot: input.repoRoot });
444
+ if (!resolved.ok || resolved.state !== 'completed')
445
+ throw Object.assign(new Error('completed task is unavailable for summary sync'), { code: 'SUMMARY_STAGING_INVALID' });
446
+ if (!taskIssueIdentity(parseTaskFrontmatter(fs.readFileSync(resolved.taskMdPath, 'utf8')))) {
447
+ receipt = updateReceipt(input.repoRoot, receipt, {
448
+ summary: 'skipped', postSummaryVerification: 'skipped', summarySha256: null, lastError: null
449
+ });
450
+ return { receipt, step: { status: 'skipped', changed: false, error: null }, changed: false, error: null };
451
+ }
452
+ const summary = readDeliverySummary(resolved.taskDir, input.taskId);
453
+ const result = await input.commentSync(input.taskId, {
454
+ kind: 'summary', agent: input.agent, body: summary.body, cwd: input.repoRoot,
455
+ summaryAuthorization: { sha256: summary.sha256 }
456
+ });
457
+ const step = commentStep(result);
458
+ if (result.status === 'applied' || result.status === 'no-op') {
459
+ const skipped = result.error?.code === 'ISSUE_NOT_LINKED';
460
+ const resolvedWarning = receipt.warnings.some((warning) => warning.step === 'summary' && warning.status === 'open');
461
+ const warnings = resolveStepWarnings(receipt, 'summary');
462
+ receipt = updateReceipt(input.repoRoot, receipt, {
463
+ summary: skipped ? 'skipped' : 'done', postSummaryVerification: skipped ? 'skipped' : receipt.postSummaryVerification,
464
+ summarySha256: skipped ? null : summary.sha256,
465
+ taskComment: resolvedWarning ? 'pending' : receipt.taskComment,
466
+ warningProjection: warnings.some((warning) => warning.status === 'open') ? 'pending' : 'done',
467
+ warnings,
468
+ lastError: null
469
+ });
470
+ return { receipt, step: skipped ? { ...step, status: 'skipped' } : step, changed: result.changed, error: null };
471
+ }
472
+ const error = step.error ?? { code: 'SUMMARY_SYNC_FAILED', message: 'delivery summary synchronization failed', retryable: true };
473
+ const warning = warningFromError('summary', error);
474
+ const alreadyOpen = receipt.warnings.some((item) => item.step === warning.step && item.code === warning.code && item.target === warning.target && item.status === 'open');
475
+ const warnings = replaceWarning(receipt, warning, 'open');
476
+ receipt = updateReceipt(input.repoRoot, receipt, { summary: 'pending', taskComment: alreadyOpen ? receipt.taskComment : 'pending', warningProjection: 'pending', warnings, lastError: error });
477
+ return { receipt, step, changed: result.changed, error };
478
+ }
479
+ catch (cause) {
480
+ const error = errorOf(cause, 'SUMMARY_SYNC_FAILED', true);
481
+ try {
482
+ const warning = warningFromError('summary', error);
483
+ const alreadyOpen = receipt.warnings.some((item) => item.step === warning.step && item.code === warning.code && item.target === warning.target && item.status === 'open');
484
+ const warnings = replaceWarning(receipt, warning, 'open');
485
+ receipt = updateReceipt(input.repoRoot, receipt, { summary: 'pending', taskComment: alreadyOpen ? receipt.taskComment : 'pending', warningProjection: 'pending', warnings, lastError: error });
486
+ }
487
+ catch { /* preserve primary error */ }
488
+ return { receipt, step: { status: 'blocked', changed: false, error }, changed: false, error };
489
+ }
490
+ }
491
+ function clearDeliverySummary(taskDir) {
492
+ const file = path.join(taskDir, '.delivery-summary.json');
493
+ if (fs.existsSync(file))
494
+ fs.unlinkSync(file);
495
+ }
406
496
  function terminalResult(taskId, receipt, steps, changed, error = null) {
407
497
  const pending = pendingSteps(receipt);
408
- const blocked = [steps.lifecycle, steps.taskComment, steps.verification].some((step) => step?.status === 'blocked');
498
+ const blocked = [steps.lifecycle, steps.taskComment, steps.verification, steps.summary, steps.postSummaryVerification].some((step) => step?.status === 'blocked');
409
499
  const warnings = openWarnings(receipt);
410
500
  const postLifecyclePending = receipt.lifecycle === 'done' && (pending.some((step) => step !== 'lifecycle') || receipt.warningProjection === 'pending');
411
501
  const hardError = error?.code.startsWith('FINALIZATION_') || error?.code === 'TASK_FINALIZATION_RECEIPT_INVALID';
@@ -413,13 +503,17 @@ function terminalResult(taskId, receipt, steps, changed, error = null) {
413
503
  status: hardError ? (error?.retryable ? 'blocked' : 'failed') : pending.length === 0 ? 'completed' : postLifecyclePending ? 'completed' : blocked ? 'blocked' : 'failed',
414
504
  changed,
415
505
  taskId,
416
- ...steps,
506
+ lifecycle: steps.lifecycle ?? null,
507
+ taskComment: steps.taskComment ?? null,
508
+ verification: steps.verification ?? null,
509
+ summary: steps.summary ?? null,
510
+ postSummaryVerification: steps.postSummaryVerification ?? null,
417
511
  completedSteps: completedSteps(receipt),
418
512
  pendingSteps: pending,
419
- result: postLifecyclePending && (warnings.length > 0 || receipt.warningProjection === 'pending')
420
- ? 'completed_with_warnings'
421
- : hardError
422
- ? (error?.retryable ? 'blocked' : 'failed')
513
+ result: hardError
514
+ ? (error?.retryable ? 'blocked' : 'failed')
515
+ : postLifecyclePending && (warnings.length > 0 || receipt.warningProjection === 'pending')
516
+ ? 'completed_with_warnings'
423
517
  : pending.length === 0 || postLifecyclePending
424
518
  ? 'completed'
425
519
  : blocked
@@ -446,17 +540,9 @@ async function applyUnderLock(request, taskId, options) {
446
540
  catch (error) {
447
541
  return failed(taskId, errorOf(error, 'TASK_FINALIZATION_RECEIPT_INVALID'));
448
542
  }
449
- if (options.controlBinding && !controlBindingMatches(receipt, options.controlBinding)) {
450
- const error = {
451
- code: 'TASK_FINALIZATION_CONTROL_BINDING_CONFLICT',
452
- message: 'finalization receipt belongs to a different sandbox request or generation',
453
- retryable: false
454
- };
455
- return failed(taskId, error, {
456
- completedSteps: completedSteps(receipt),
457
- pendingSteps: pendingSteps(receipt)
458
- });
459
- }
543
+ if (options.controlBinding && (receipt.controlBinding?.generation !== options.controlBinding.generation
544
+ || receipt.controlBinding.requestId !== options.controlBinding.requestId))
545
+ receipt = updateReceipt(repoRoot, receipt, { controlBinding: options.controlBinding });
460
546
  const preflightState = resolveTaskRef(taskId, { repoRoot });
461
547
  if (options.preflight && preflightState.ok && preflightState.state === 'active') {
462
548
  try {
@@ -546,13 +632,11 @@ async function applyUnderLock(request, taskId, options) {
546
632
  let verification = null;
547
633
  try {
548
634
  receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
549
- if (receipt.verification !== 'pending') {
550
- verification = { status: 'no-op', changed: false, error: null };
551
- }
552
- else {
553
- const result = await verify({ taskRef: taskId, event: 'complete-task.completed' }, { repoRoot });
554
- verification = verificationStep(result);
555
- if (result.status === 'pass') {
635
+ const verificationPending = receipt.verification === 'pending';
636
+ const result = await verify({ taskRef: taskId, event: 'complete-task.completed' }, { repoRoot });
637
+ verification = verificationStep(result);
638
+ if (result.status === 'pass') {
639
+ if (verificationPending) {
556
640
  const capability = issueCapability(receipt, 'verification');
557
641
  receipt = applyFinalizationReceiptMutationUnderLock(repoRoot, receipt, capability, {
558
642
  scope: 'verification', operation: 'succeeded'
@@ -570,30 +654,170 @@ async function applyUnderLock(request, taskId, options) {
570
654
  }
571
655
  }
572
656
  else {
573
- const detail = verification.error ?? { code: 'VERIFY_FAILED', message: 'verification failed', retryable: true };
657
+ receipt = updateReceipt(repoRoot, receipt, { lastError: null });
658
+ }
659
+ }
660
+ else {
661
+ const detail = verification.error ?? { code: 'VERIFY_FAILED', message: 'verification failed', retryable: true };
662
+ if (verificationPending) {
574
663
  const capability = issueCapability(receipt, 'verification');
575
664
  receipt = applyFinalizationReceiptMutationUnderLock(repoRoot, receipt, capability, {
576
665
  scope: 'verification', operation: 'failed', error: detail
577
666
  }, consumedCapabilities);
578
667
  receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
579
- return terminalResult(taskId, receipt, { lifecycle: lifecycleResult, taskComment, verification }, changed, verification.error);
580
668
  }
669
+ else {
670
+ const warnings = replaceWarning(receipt, warningFromError('verification', detail), 'open');
671
+ receipt = updateReceipt(repoRoot, receipt, {
672
+ verification: 'pending', taskComment: 'pending', warningProjection: 'pending', warnings, lastError: detail
673
+ });
674
+ receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
675
+ const warningComment = await syncPendingTaskComment({ repoRoot, taskId, agent: request.agent, receipt, commentSync, consumedCapabilities });
676
+ receipt = warningComment.receipt;
677
+ if (warningComment.step.status !== 'no-op')
678
+ taskComment = warningComment.step;
679
+ changed = changed || warningComment.changed;
680
+ }
681
+ return terminalResult(taskId, receipt, { lifecycle: lifecycleResult, taskComment, verification }, changed, detail);
581
682
  }
582
683
  }
583
684
  catch (error) {
584
685
  const detail = errorOf(error, 'VERIFY_FAILED', true);
585
686
  try {
586
- const capability = issueCapability(receipt, 'verification');
587
- receipt = applyFinalizationReceiptMutationUnderLock(repoRoot, receipt, capability, {
588
- scope: 'verification', operation: 'failed', error: detail
589
- }, consumedCapabilities);
687
+ if (receipt.verification === 'pending') {
688
+ const capability = issueCapability(receipt, 'verification');
689
+ receipt = applyFinalizationReceiptMutationUnderLock(repoRoot, receipt, capability, {
690
+ scope: 'verification', operation: 'failed', error: detail
691
+ }, consumedCapabilities);
692
+ }
693
+ else {
694
+ const warnings = replaceWarning(receipt, warningFromError('verification', detail), 'open');
695
+ receipt = updateReceipt(repoRoot, receipt, {
696
+ verification: 'pending', taskComment: 'pending', warningProjection: 'pending', warnings, lastError: detail
697
+ });
698
+ }
590
699
  receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
591
700
  }
701
+ catch (writeError) {
702
+ const persistence = errorOf(writeError, 'FINALIZATION_RECEIPT_WRITE_FAILED', true);
703
+ const hardError = {
704
+ code: 'FINALIZATION_RECEIPT_WRITE_FAILED',
705
+ message: `Unable to persist verification failure (${detail.code}: ${detail.message}): ${persistence.message}`,
706
+ retryable: true
707
+ };
708
+ return terminalResult(taskId, receipt, {
709
+ lifecycle: lifecycleResult, taskComment, verification: { status: 'blocked', changed: false, error: detail }
710
+ }, changed, hardError);
711
+ }
712
+ try {
713
+ const warningComment = await syncPendingTaskComment({ repoRoot, taskId, agent: request.agent, receipt, commentSync, consumedCapabilities });
714
+ receipt = warningComment.receipt;
715
+ if (warningComment.step.status !== 'no-op')
716
+ taskComment = warningComment.step;
717
+ changed = changed || warningComment.changed;
718
+ }
592
719
  catch { /* preserve the primary error */ }
593
720
  return terminalResult(taskId, receipt, { lifecycle: lifecycleResult, taskComment, verification: { status: 'blocked', changed: false, error: detail } }, changed, detail);
594
721
  }
722
+ const summary = await syncPendingSummary({ repoRoot, taskId, agent: request.agent, receipt, commentSync });
723
+ receipt = summary.receipt;
724
+ changed = changed || summary.changed;
725
+ if (summary.error) {
726
+ receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
727
+ const warningComment = await syncPendingTaskComment({
728
+ repoRoot, taskId, agent: request.agent, receipt, commentSync, consumedCapabilities
729
+ });
730
+ receipt = warningComment.receipt;
731
+ if (warningComment.step.status !== 'no-op')
732
+ taskComment = warningComment.step;
733
+ changed = changed || warningComment.changed;
734
+ return terminalResult(taskId, receipt, { lifecycle: lifecycleResult, taskComment, verification, summary: summary.step }, changed, summary.error);
735
+ }
736
+ let postSummaryVerification = null;
737
+ if (receipt.postSummaryVerification === 'skipped') {
738
+ postSummaryVerification = { status: 'skipped', changed: false, error: null };
739
+ }
740
+ else if (receipt.postSummaryVerification !== 'pending') {
741
+ const replay = await commentSync(taskId, {
742
+ kind: 'summary', agent: request.agent, body: '', cwd: repoRoot, verifyOnly: true,
743
+ summaryAuthorization: { sha256: receipt.summarySha256 ?? '' }
744
+ });
745
+ postSummaryVerification = commentStep(replay);
746
+ if (replay.status !== 'applied' && replay.status !== 'no-op') {
747
+ const error = postSummaryVerification.error ?? { code: 'SUMMARY_VERIFICATION_FAILED', message: 'summary replay verification failed', retryable: true };
748
+ const warnings = replaceWarning(receipt, warningFromError('post-summary-verification', error), 'open');
749
+ receipt = updateReceipt(repoRoot, receipt, { postSummaryVerification: 'pending', taskComment: 'pending', warningProjection: 'pending', warnings, lastError: error });
750
+ receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
751
+ return terminalResult(taskId, receipt, { lifecycle: lifecycleResult, taskComment, verification, summary: summary.step, postSummaryVerification }, changed, error);
752
+ }
753
+ }
754
+ else {
755
+ try {
756
+ const summaryVerification = await commentSync(taskId, {
757
+ kind: 'summary', agent: request.agent, body: '', cwd: repoRoot, verifyOnly: true,
758
+ summaryAuthorization: { sha256: receipt.summarySha256 ?? '' }
759
+ });
760
+ if (summaryVerification.status !== 'applied' && summaryVerification.status !== 'no-op') {
761
+ const error = commentStep(summaryVerification).error ?? { code: 'SUMMARY_VERIFICATION_FAILED', message: 'summary verification failed', retryable: true };
762
+ const warnings = replaceWarning(receipt, warningFromError('post-summary-verification', error), 'open');
763
+ receipt = updateReceipt(repoRoot, receipt, { postSummaryVerification: 'pending', taskComment: 'pending', warningProjection: 'pending', warnings, lastError: error });
764
+ receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
765
+ const warningComment = await syncPendingTaskComment({ repoRoot, taskId, agent: request.agent, receipt, commentSync, consumedCapabilities });
766
+ receipt = warningComment.receipt;
767
+ if (warningComment.step.status !== 'no-op')
768
+ taskComment = warningComment.step;
769
+ changed = changed || warningComment.changed;
770
+ return terminalResult(taskId, receipt, { lifecycle: lifecycleResult, taskComment, verification, summary: summary.step, postSummaryVerification: commentStep(summaryVerification) }, changed, error);
771
+ }
772
+ const result = await verify({ taskRef: taskId, event: 'complete-task.completed' }, { repoRoot });
773
+ postSummaryVerification = verificationStep(result);
774
+ if (result.status === 'pass') {
775
+ const resolvedWarning = receipt.warnings.some((warning) => warning.step === 'post-summary-verification' && warning.status === 'open');
776
+ const warnings = resolveStepWarnings(receipt, 'post-summary-verification');
777
+ receipt = updateReceipt(repoRoot, receipt, {
778
+ postSummaryVerification: 'done', taskComment: resolvedWarning ? 'pending' : receipt.taskComment,
779
+ warningProjection: warnings.some((warning) => warning.status === 'open') ? 'pending' : 'done', warnings, lastError: null
780
+ });
781
+ receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
782
+ const warningComment = await syncPendingTaskComment({ repoRoot, taskId, agent: request.agent, receipt, commentSync, consumedCapabilities });
783
+ receipt = warningComment.receipt;
784
+ if (warningComment.step.status !== 'no-op')
785
+ taskComment = warningComment.step;
786
+ changed = changed || warningComment.changed;
787
+ }
788
+ else {
789
+ const error = postSummaryVerification.error ?? { code: 'VERIFY_FAILED', message: 'post-summary verification failed', retryable: true };
790
+ const warnings = replaceWarning(receipt, warningFromError('post-summary-verification', error), 'open');
791
+ receipt = updateReceipt(repoRoot, receipt, { postSummaryVerification: 'pending', taskComment: 'pending', warningProjection: 'pending', warnings, lastError: error });
792
+ receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
793
+ const warningComment = await syncPendingTaskComment({ repoRoot, taskId, agent: request.agent, receipt, commentSync, consumedCapabilities });
794
+ receipt = warningComment.receipt;
795
+ if (warningComment.step.status !== 'no-op')
796
+ taskComment = warningComment.step;
797
+ changed = changed || warningComment.changed;
798
+ return terminalResult(taskId, receipt, { lifecycle: lifecycleResult, taskComment, verification, summary: summary.step, postSummaryVerification }, changed, error);
799
+ }
800
+ }
801
+ catch (error) {
802
+ const detail = errorOf(error, 'POST_SUMMARY_VERIFY_FAILED', true);
803
+ const warnings = replaceWarning(receipt, warningFromError('post-summary-verification', detail), 'open');
804
+ receipt = updateReceipt(repoRoot, receipt, { postSummaryVerification: 'pending', taskComment: 'pending', warningProjection: 'pending', warnings, lastError: detail });
805
+ receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
806
+ const warningComment = await syncPendingTaskComment({ repoRoot, taskId, agent: request.agent, receipt, commentSync, consumedCapabilities });
807
+ receipt = warningComment.receipt;
808
+ if (warningComment.step.status !== 'no-op')
809
+ taskComment = warningComment.step;
810
+ changed = changed || warningComment.changed;
811
+ return terminalResult(taskId, receipt, { lifecycle: lifecycleResult, taskComment, verification, summary: summary.step, postSummaryVerification: { status: 'blocked', changed: false, error: detail } }, changed, detail);
812
+ }
813
+ }
595
814
  receipt = reconcileWarningProjection(repoRoot, taskId, receipt, consumedCapabilities);
596
- return terminalResult(taskId, receipt, { lifecycle: lifecycleResult, taskComment, verification }, changed);
815
+ if (receipt.summary !== 'pending' && receipt.postSummaryVerification !== 'pending' && receipt.warningProjection === 'done' && openWarnings(receipt).length === 0) {
816
+ const resolved = resolveTaskRef(taskId, { repoRoot });
817
+ if (resolved.ok && resolved.state === 'completed')
818
+ clearDeliverySummary(resolved.taskDir);
819
+ }
820
+ return terminalResult(taskId, receipt, { lifecycle: lifecycleResult, taskComment, verification, summary: summary.step, postSummaryVerification }, changed);
597
821
  }
598
822
  async function applyTaskFinalization(request, options) {
599
823
  if (request.intent !== 'complete' || !request.taskRef || !request.agent) {
@@ -613,5 +837,5 @@ async function applyTaskFinalization(request, options) {
613
837
  return failed(resolved.taskId, detail);
614
838
  }
615
839
  }
616
- export { applyFinalizationReceiptMutation, applyTaskFinalization, issueCapability as createFinalizationCapability, readTaskFinalizationReceipt };
840
+ export { applyFinalizationReceiptMutation, applyTaskFinalization, issueCapability as createFinalizationCapability, readTaskFinalizationReceipt, terminalResult };
617
841
  //# sourceMappingURL=finalization.js.map
@@ -0,0 +1,94 @@
1
+ const OPTIONAL_PLATFORM_AUDITS = new Set([
2
+ 'closed-status-labels', 'status-label', 'comment-marker', 'pr-comment-marker',
3
+ 'pr-comment-last-commit', 'pr-comment-content', 'comment-content', 'task-comment-content',
4
+ 'in-labels-computed', 'pr-type-label', 'in-labels-match-pr', 'pr-assignee',
5
+ 'requirements', 'issue-type', 'issue-fields', 'milestone'
6
+ ]);
7
+ const STATUS_LABELS = {
8
+ 'create-task': 'status: waiting-for-triage',
9
+ 'analyze-task': 'status: pending-design-work',
10
+ 'review-analysis': 'status: pending-design-work',
11
+ 'plan-task': 'status: pending-design-work',
12
+ 'review-plan': 'status: pending-design-work',
13
+ 'code-task': 'status: in-progress',
14
+ 'review-code': 'status: in-progress'
15
+ };
16
+ const ARTIFACT_COMMENT_SKILLS = new Set([
17
+ 'analyze-task', 'plan-task', 'review-analysis', 'review-plan', 'code-task',
18
+ 'review-code', 'review-pr', 'run-manual-validation'
19
+ ]);
20
+ const PR_AUDIT_SKILLS = new Set(['create-pr']);
21
+ const IN_LABEL_AUDIT_SKILLS = new Set(['code-task', 'commit', 'create-pr']);
22
+ export function platformAuditPolicy(checkId, context) {
23
+ const skillName = context.skillName || '';
24
+ const artifactComment = Boolean(context.artifactFile) && ARTIFACT_COMMENT_SKILLS.has(skillName);
25
+ const prAudit = PR_AUDIT_SKILLS.has(skillName);
26
+ const inLabelAudit = IN_LABEL_AUDIT_SKILLS.has(skillName);
27
+ switch (checkId) {
28
+ case 'status-label':
29
+ return { classification: 'soft', enabled: Boolean(STATUS_LABELS[skillName]), expectedStatusLabel: STATUS_LABELS[skillName] };
30
+ case 'comment-marker':
31
+ return {
32
+ classification: 'soft',
33
+ enabled: artifactComment || skillName === 'complete-task',
34
+ ...(artifactComment ? { expectedCommentMarkerKey: 'artifact' } : skillName === 'complete-task' ? { expectedCommentMarkerKey: 'summary' } : {})
35
+ };
36
+ case 'pr-comment-marker':
37
+ return {
38
+ classification: 'soft',
39
+ enabled: skillName === 'commit' || prAudit,
40
+ ...(skillName === 'commit' || prAudit ? { expectedPrCommentMarkerKey: 'prSummary' } : {})
41
+ };
42
+ case 'pr-comment-last-commit':
43
+ return {
44
+ classification: 'soft',
45
+ enabled: skillName === 'commit',
46
+ ...(skillName === 'commit' ? { expectedPrCommentMarkerKey: 'prSummary' } : {})
47
+ };
48
+ case 'pr-comment-content':
49
+ return {
50
+ classification: 'soft',
51
+ enabled: skillName === 'complete-manual-validation',
52
+ ...(skillName === 'complete-manual-validation' ? { expectedPrCommentMarkerKey: 'prSummary' } : {})
53
+ };
54
+ case 'comment-content':
55
+ return { classification: 'soft', enabled: artifactComment, expectedCommentMarkerKey: 'artifact' };
56
+ case 'task-comment-content':
57
+ return { classification: 'soft', enabled: true };
58
+ case 'in-labels-computed':
59
+ return { classification: 'soft', enabled: inLabelAudit };
60
+ case 'pr-type-label':
61
+ case 'in-labels-match-pr':
62
+ case 'pr-assignee':
63
+ return { classification: 'soft', enabled: prAudit };
64
+ case 'requirements':
65
+ return { classification: 'soft', enabled: skillName === 'complete-task' };
66
+ case 'milestone':
67
+ return { classification: 'soft', enabled: true, requireSpecificMilestone: skillName === 'code-task' };
68
+ case 'closed-status-labels':
69
+ case 'issue-type':
70
+ case 'issue-fields':
71
+ return { classification: 'soft', enabled: true };
72
+ }
73
+ }
74
+ export function classificationForCheck(checkId) {
75
+ return checkId.startsWith('platform.') && OPTIONAL_PLATFORM_AUDITS.has(checkId.slice('platform.'.length))
76
+ ? 'soft'
77
+ : 'hard';
78
+ }
79
+ export function effectiveStatus(status, classification) {
80
+ return classification === 'hard' ? status : 'pass';
81
+ }
82
+ export function normalizeVerificationRecord(record) {
83
+ const checkId = record.checkId ?? record.type;
84
+ const classification = record.classification ?? classificationForCheck(checkId);
85
+ return {
86
+ ...record,
87
+ checkId,
88
+ classification,
89
+ effectiveStatus: record.effectiveStatus ?? effectiveStatus(record.status, classification),
90
+ reason: record.reason ?? record.fail_type ?? (classification === 'info' ? 'NOT_APPLICABLE' : record.status === 'pass' ? 'OK' : 'CHECK_FAILED'),
91
+ action: record.action ?? (classification === 'info' || record.status === 'pass' ? 'No action required' : 'Review the check evidence and retry after correction')
92
+ };
93
+ }
94
+ //# sourceMappingURL=gate-policy.js.map
@@ -1,4 +1,4 @@
1
- import { inspectOrchestrationStage, pauseOrchestration, planOrchestrationStageCompletion, readRun } from './orchestration.js';
1
+ import { inspectOrchestrationStage, pauseOrchestration, planOrchestrationStageCompletion } from './orchestration.js';
2
2
  import { resolveTaskRef } from './resolve-ref.js';
3
3
  function failure(mode, code, message) {
4
4
  return { ok: false, mode, completionPlan: null, error: { code, message } };
@@ -8,16 +8,7 @@ function inspectLifecycleExecution(taskRef, request, options = {}) {
8
8
  if (!resolved.ok)
9
9
  return failure(request.mode, resolved.code, resolved.message);
10
10
  if (request.mode === 'standalone') {
11
- try {
12
- const run = readRun(resolved.taskDir);
13
- if (run?.pendingDelegation) {
14
- return failure(request.mode, 'ORCHESTRATION_STANDALONE_BUSY', 'standalone lifecycle execution is blocked by a pending orchestration delegation');
15
- }
16
- return { ok: true, mode: request.mode, completionPlan: null, error: null };
17
- }
18
- catch (error) {
19
- return failure(request.mode, 'ORCHESTRATION_STATE_INVALID', error instanceof Error ? error.message : String(error));
20
- }
11
+ return { ok: true, mode: request.mode, completionPlan: null, error: null };
21
12
  }
22
13
  if (request.agent) {
23
14
  const planned = planOrchestrationStageCompletion(taskRef, {