@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,6 +1,8 @@
1
1
  import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { randomUUID } from 'node:crypto';
2
4
  import { parseArtifactName } from './artifact-name.js';
3
- import { validateCompletedArtifact, hasOpenArtifactRound, validateArtifactPublication } from './artifact-lifecycle.js';
5
+ import { validateCompletedArtifact, validateArtifactPublication } from './artifact-lifecycle.js';
4
6
  import { LEDGER_SECTION_MISSING_CODE, LEDGER_SECTION_MISSING_MESSAGE, parseLedgerDocument, summarizeLedgerStage, validateLedgerRows } from './ledger.js';
5
7
  import { finalizeReviewSummaryContent } from './review-artifacts.js';
6
8
  import { inspectDecisionDetailDuplicates } from './decision-details.js';
@@ -11,8 +13,6 @@ import { allowsManualOverride } from './guard-override.js';
11
13
  import { getArtifactSchema } from './artifact-schema.js';
12
14
  import { canonicalSemanticDigest, inspectArtifactContract, sha256Content } from './artifact-operations.js';
13
15
  import { expectedQualificationRelations, validateQualificationAudit } from './qualification-audit.js';
14
- import { readArtifactRecoveryIntent } from './artifact-repair-intent.js';
15
- import { beginArtifactRecovery, commitArtifactRecovery, prepareArtifactRecoveryFinal, prepareArtifactRecoveryCommit, reconcileArtifactRecovery, recoveryContextFromIntent, stageArtifactCandidate } from './artifact-recovery.js';
16
16
  const STAGES = {
17
17
  analysis: { family: 'review-analysis' },
18
18
  plan: { family: 'review-plan' },
@@ -47,7 +47,7 @@ function preflightFailed(request, code, message, taskId = null, extra = {}) {
47
47
  stageStatus: null
48
48
  };
49
49
  }
50
- /** Validate and seal a review draft before any finding-ledger write. */
50
+ /** Validate the current review draft before any finding-ledger write. */
51
51
  function preflightReviewSummaryUnlocked(request, options = {}) {
52
52
  const stage = request.stage;
53
53
  const spec = STAGES[stage];
@@ -68,39 +68,16 @@ function preflightReviewSummaryUnlocked(request, options = {}) {
68
68
  return preflightFailed(request, 'REVIEW_ARTIFACT_IDENTITY_INVALID', publicationError.message, resolved.taskId);
69
69
  let taskContent;
70
70
  let content;
71
- let recovery;
72
- let existingIntent;
73
71
  try {
74
72
  taskContent = fs.readFileSync(resolved.taskMdPath, 'utf8');
75
- if (request.recoveryId) {
76
- const intent = readArtifactRecoveryIntent(resolved.repoRoot, resolved.taskId, spec.family, request.artifact);
77
- if (!intent || intent.recoveryOperationId !== request.recoveryId)
78
- return preflightFailed(request, 'REVIEW_PROVENANCE_INVALID', 'recovery id does not match the artifact journal', resolved.taskId);
79
- if (!['awaiting-preflight-recovery', 'preflight-ready'].includes(intent.state))
80
- return preflightFailed(request, 'REVIEW_PROVENANCE_INVALID', `recovery journal is in '${intent.state}' state`, resolved.taskId);
81
- recovery = recoveryContextFromIntent(resolved.repoRoot, resolved.taskDir, intent);
82
- content = fs.existsSync(recovery.stagingPath)
83
- ? fs.readFileSync(recovery.stagingPath, 'utf8')
84
- : fs.readFileSync(recovery.formalPath, 'utf8');
85
- }
86
- else {
87
- const validated = validateCompletedArtifact(resolved.taskDir, spec.family, request.artifact);
88
- if (!validated.ok)
89
- return preflightFailed(request, validated.error.code === 'ARTIFACT_NOT_REGULAR' ? 'REVIEW_ARTIFACT_NOT_REGULAR' : 'REVIEW_ARTIFACT_IDENTITY_INVALID', validated.error.message, resolved.taskId);
90
- content = fs.readFileSync(validated.artifact.path, 'utf8');
91
- const intent = readArtifactRecoveryIntent(resolved.repoRoot, resolved.taskId, spec.family, request.artifact);
92
- existingIntent = intent;
93
- if (intent?.state === 'preflight-ready')
94
- recovery = recoveryContextFromIntent(resolved.repoRoot, resolved.taskDir, intent);
95
- if (intent?.state === 'consumed')
96
- return preflightFailed(request, 'REVIEW_PROVENANCE_INVALID', 'review artifact was already finalized', resolved.taskId);
97
- }
73
+ const validated = validateCompletedArtifact(resolved.taskDir, spec.family, request.artifact);
74
+ if (!validated.ok)
75
+ return preflightFailed(request, validated.error.code === 'ARTIFACT_NOT_REGULAR' ? 'REVIEW_ARTIFACT_NOT_REGULAR' : 'REVIEW_ARTIFACT_IDENTITY_INVALID', validated.error.message, resolved.taskId);
76
+ content = fs.readFileSync(validated.artifact.path, 'utf8');
98
77
  }
99
78
  catch (error) {
100
79
  return preflightFailed(request, 'REVIEW_ARTIFACT_NOT_REGULAR', String(error), resolved.taskId);
101
80
  }
102
- if (!hasOpenArtifactRound(taskContent, spec.family, parsed.round))
103
- return preflightFailed(request, 'REVIEW_ARTIFACT_IDENTITY_INVALID', `${request.artifact} does not have one matching open started review event`, resolved.taskId);
104
81
  const execution = validateLifecycleExecution(request.taskRef, {
105
82
  mode: request.orchestrated ? 'orchestrated' : 'standalone',
106
83
  identity: { stage: spec.family, round: parsed.round, artifact: request.artifact, role: 'reviewer' },
@@ -122,17 +99,6 @@ function preflightReviewSummaryUnlocked(request, options = {}) {
122
99
  });
123
100
  const qualificationError = qualification.ok ? null : qualification;
124
101
  if (!detail.ok || !structure.ok || qualificationError) {
125
- if (!recovery && !request.dryRun) {
126
- try {
127
- recovery = beginArtifactRecovery({
128
- taskId: resolved.taskId, family: spec.family, artifact: request.artifact, round: parsed.round,
129
- requestId: `review-preflight:${resolved.taskId}:${stage}:${parsed.round}`
130
- }, Buffer.from(content, 'utf8'), { repoRoot: resolved.repoRoot, taskDir: resolved.taskDir, lockAlreadyHeld: options.lockAlreadyHeld });
131
- }
132
- catch (error) {
133
- return preflightFailed(request, 'REVIEW_PROVENANCE_INVALID', String(error), resolved.taskId);
134
- }
135
- }
136
102
  const diagnostics = [
137
103
  ...(!detail.ok ? [`${detail.code}: ${detail.message}`] : []),
138
104
  ...structure.diagnostics.map((item) => `${item.code}: ${item.message}`),
@@ -140,36 +106,18 @@ function preflightReviewSummaryUnlocked(request, options = {}) {
140
106
  ].join('; ');
141
107
  return preflightFailed(request, !detail.ok ? 'REVIEW_DECISION_DETAIL_INVALID' : !structure.ok ? 'REVIEW_ARTIFACT_STRUCTURE_INVALID' : 'REVIEW_ARTIFACT_QUALIFICATION_INVALID', diagnostics, resolved.taskId, {
142
108
  artifactSha256, semanticDigest,
143
- ...(recovery ? { recovery: recoveryInfo(recovery) } : {})
144
109
  });
145
110
  }
146
- if (!recovery && existingIntent?.state === 'passed') {
111
+ if (request.dryRun) {
147
112
  return {
148
113
  ...preflightFailed(request, 'REVIEW_ARTIFACT_CONFLICT', '', resolved.taskId, { artifactSha256, semanticDigest }),
149
114
  status: 'passed', changed: false, error: null
150
115
  };
151
116
  }
152
- if (request.dryRun || recovery && readArtifactRecoveryIntent(resolved.repoRoot, resolved.taskId, spec.family, request.artifact)?.state === 'preflight-ready') {
153
- return {
154
- ...preflightFailed(request, 'REVIEW_ARTIFACT_CONFLICT', '', resolved.taskId, { artifactSha256, semanticDigest, ...(recovery ? { recovery: recoveryInfo(recovery) } : {}) }),
155
- status: 'passed', changed: false, error: null
156
- };
157
- }
158
- try {
159
- recovery ??= beginArtifactRecovery({
160
- taskId: resolved.taskId, family: spec.family, artifact: request.artifact, round: parsed.round,
161
- requestId: `review-preflight:${resolved.taskId}:${stage}:${parsed.round}`
162
- }, Buffer.from(content, 'utf8'), { repoRoot: resolved.repoRoot, taskDir: resolved.taskDir, lockAlreadyHeld: options.lockAlreadyHeld });
163
- const staged = stageArtifactCandidate(recovery, Buffer.from(content, 'utf8'), { lockAlreadyHeld: options.lockAlreadyHeld });
164
- prepareArtifactRecoveryCommit(recovery, staged.candidateSha256, staged.semanticDigest, { lockAlreadyHeld: options.lockAlreadyHeld });
165
- return {
166
- ...preflightFailed(request, 'REVIEW_ARTIFACT_CONFLICT', '', resolved.taskId, { artifactSha256, semanticDigest, recovery: recoveryInfo(recovery) }),
167
- status: 'passed', changed: false, error: null
168
- };
169
- }
170
- catch (error) {
171
- return preflightFailed(request, 'REVIEW_PROVENANCE_INVALID', String(error), resolved.taskId, { artifactSha256, semanticDigest });
172
- }
117
+ return {
118
+ ...preflightFailed(request, 'REVIEW_ARTIFACT_CONFLICT', '', resolved.taskId, { artifactSha256, semanticDigest }),
119
+ status: 'passed', changed: false, error: null
120
+ };
173
121
  }
174
122
  function preflightReviewSummary(request, options = {}) {
175
123
  if (request.dryRun || options.lockAlreadyHeld)
@@ -186,14 +134,6 @@ function preflightReviewSummary(request, options = {}) {
186
134
  return preflightFailed(request, 'REVIEW_PROVENANCE_INVALID', `${error.code}: ${error.message}`, resolved.taskId);
187
135
  }
188
136
  }
189
- function recoveryInfo(recovery) {
190
- return {
191
- recoveryId: recovery.recoveryId,
192
- candidatePath: recovery.stagingPath,
193
- baselineSha256: recovery.baselineSha256,
194
- baselineSemanticDigest: recovery.baselineSemanticDigest
195
- };
196
- }
197
137
  /** Prepare domain validation, summary bytes and provenance before any publication. */
198
138
  function prepareReviewSummaryCandidate(request, artifactContent, options = {}) {
199
139
  let taskId = null;
@@ -227,45 +167,9 @@ function prepareReviewSummaryCandidate(request, artifactContent, options = {}) {
227
167
  catch (error) {
228
168
  return reject('REVIEW_ARTIFACT_NOT_REGULAR', String(error));
229
169
  }
230
- let recovery;
231
- if (request.recoveryId) {
232
- try {
233
- const intent = readArtifactRecoveryIntent(resolved.repoRoot, taskId, spec.family, request.artifact);
234
- if (!intent || intent.recoveryOperationId !== request.recoveryId)
235
- return reject('REVIEW_PROVENANCE_INVALID', 'recovery id does not match the artifact journal');
236
- if (!['awaiting-preflight-recovery', 'preflight-ready', 'preflight-commit-started', 'preflight-passed', 'full-finalizer-ready', 'commit-started'].includes(intent.state))
237
- return reject('REVIEW_PROVENANCE_INVALID', `recovery journal is in '${intent.state}' state`);
238
- recovery = recoveryContextFromIntent(resolved.repoRoot, resolved.taskDir, intent);
239
- artifactContent = fs.existsSync(recovery.stagingPath)
240
- ? fs.readFileSync(recovery.stagingPath, 'utf8')
241
- : fs.readFileSync(recovery.formalPath, 'utf8');
242
- }
243
- catch (error) {
244
- return reject('REVIEW_PROVENANCE_INVALID', String(error));
245
- }
246
- }
247
170
  const artifactSha256 = sha256Content(artifactContent);
248
171
  const semanticDigest = canonicalSemanticDigest(artifactContent);
249
172
  const digests = { artifactSha256, semanticDigest };
250
- let repairIntent;
251
- try {
252
- repairIntent = readArtifactRecoveryIntent(resolved.repoRoot, taskId, spec.family, request.artifact);
253
- }
254
- catch (error) {
255
- return reject('REVIEW_PROVENANCE_INVALID', String(error), digests);
256
- }
257
- if (repairIntent?.state === 'consumed') {
258
- if (repairIntent.finalArtifactSha256 !== artifactSha256 || repairIntent.finalSemanticDigest !== semanticDigest) {
259
- return reject('REVIEW_PROVENANCE_INVALID', 'the review artifact changed after its finalization provenance was recorded', digests);
260
- }
261
- }
262
- if (repairIntent?.state === 'passed'
263
- && (repairIntent.finalArtifactSha256 !== artifactSha256 || repairIntent.finalSemanticDigest !== semanticDigest)) {
264
- repairIntent = undefined;
265
- }
266
- if (!hasOpenArtifactRound(taskContent, spec.family, parsed.round)) {
267
- return reject('REVIEW_ARTIFACT_IDENTITY_INVALID', `${request.artifact} does not have one matching open started review event`);
268
- }
269
173
  const execution = validateLifecycleExecution(request.taskRef, {
270
174
  mode: request.orchestrated ? 'orchestrated' : 'standalone',
271
175
  identity: { stage: spec.family, round: parsed.round, artifact: request.artifact, role: 'reviewer' },
@@ -292,65 +196,16 @@ function prepareReviewSummaryCandidate(request, artifactContent, options = {}) {
292
196
  const schema = getArtifactSchema(spec.family);
293
197
  const structure = inspectArtifactContract(artifactContent, schema);
294
198
  if (!structure.ok) {
295
- if (!recovery && !request.dryRun && options.startRecovery) {
296
- try {
297
- recovery = beginArtifactRecovery({
298
- taskId: taskId, family: spec.family, artifact: request.artifact, round: parsed.round,
299
- requestId: `review-finalize:${taskId}:${stage}:${parsed.round}`
300
- }, Buffer.from(artifactContent, 'utf8'), { repoRoot: resolved.repoRoot, taskDir: resolved.taskDir, lockAlreadyHeld: options.lockAlreadyHeld });
301
- }
302
- catch (error) {
303
- return reject('REVIEW_PROVENANCE_INVALID', String(error), digests);
304
- }
305
- }
306
- return {
307
- ...reject('REVIEW_ARTIFACT_STRUCTURE_INVALID', structure.diagnostics.map((item) => `${item.code}: ${item.message}`).join('; '), {
308
- artifactSha256, semanticDigest: structure.semanticDigest,
309
- ...(recovery ? { recovery: {
310
- recoveryId: recovery.recoveryId,
311
- candidatePath: recovery.stagingPath,
312
- baselineSha256: recovery.baselineSha256,
313
- baselineSemanticDigest: recovery.baselineSemanticDigest
314
- } } : {})
315
- }),
316
- ...(recovery ? { recovery } : {})
317
- };
199
+ return reject('REVIEW_ARTIFACT_STRUCTURE_INVALID', structure.diagnostics.map((item) => `${item.code}: ${item.message}`).join('; '), {
200
+ artifactSha256, semanticDigest: structure.semanticDigest
201
+ });
318
202
  }
319
- const transformed = finalizeReviewSummaryContent(artifactContent, stageStatus.unresolvedFindingCounts);
203
+ const transformed = finalizeReviewSummaryContent(artifactContent, stageStatus.unresolvedFindingCounts, {
204
+ refreshNumericCounts: true
205
+ });
320
206
  if (!transformed.ok)
321
207
  return reject(transformed.code, transformed.message);
322
208
  const finalDigests = { artifactSha256: sha256Content(transformed.content), semanticDigest: canonicalSemanticDigest(transformed.content) };
323
- if (!recovery && repairIntent?.state === 'commit-started') {
324
- if (repairIntent.finalArtifactSha256 !== finalDigests.artifactSha256 || repairIntent.finalSemanticDigest !== finalDigests.semanticDigest) {
325
- return reject('REVIEW_PROVENANCE_INVALID', 'formal review artifact does not match the interrupted recovery journal', finalDigests);
326
- }
327
- recovery = recoveryContextFromIntent(resolved.repoRoot, resolved.taskDir, repairIntent);
328
- }
329
- if (!recovery && (repairIntent?.state === 'passed' || repairIntent?.state === 'consumed')) {
330
- if (transformed.changed)
331
- return reject('REVIEW_PROVENANCE_INVALID', 'the review artifact requires changes after its completed recovery was consumed', finalDigests);
332
- return {
333
- content: transformed.content,
334
- result: {
335
- ...failed(request, 'REVIEW_ARTIFACT_CONFLICT', '', taskId, stageStatus, finalDigests),
336
- status: 'no-op',
337
- changed: false,
338
- error: null
339
- },
340
- lockAlreadyHeld: options.lockAlreadyHeld
341
- };
342
- }
343
- if (!recovery && !request.dryRun && options.startRecovery) {
344
- try {
345
- recovery = beginArtifactRecovery({
346
- taskId: taskId, family: spec.family, artifact: request.artifact, round: parsed.round,
347
- requestId: `review-finalize:${taskId}:${stage}:${parsed.round}`
348
- }, Buffer.from(artifactContent, 'utf8'), { repoRoot: resolved.repoRoot, taskDir: resolved.taskDir, lockAlreadyHeld: options.lockAlreadyHeld });
349
- }
350
- catch (error) {
351
- return reject('REVIEW_PROVENANCE_INVALID', String(error), finalDigests);
352
- }
353
- }
354
209
  return {
355
210
  content: transformed.content,
356
211
  result: {
@@ -360,65 +215,26 @@ function prepareReviewSummaryCandidate(request, artifactContent, options = {}) {
360
215
  operations: transformed.changed ? [{ kind: 'artifact', artifact: request.artifact, operation: 'update' }] : [],
361
216
  error: null
362
217
  },
363
- ...(recovery ? { recovery } : {}),
364
218
  lockAlreadyHeld: options.lockAlreadyHeld
365
219
  };
366
220
  }
367
221
  function commitReviewSummaryProvenance(prepared, repoRoot, options = {}) {
368
- if (prepared.result.status === 'failed' || !prepared.recovery)
369
- return prepared.result;
222
+ return prepared.result;
223
+ }
224
+ function replaceCurrentArtifactAtomically(file, content) {
225
+ const temporary = path.join(path.dirname(file), `.${path.basename(file)}.${process.pid}.${randomUUID()}.tmp`);
370
226
  try {
371
- const intent = readArtifactRecoveryIntent(prepared.recovery.repoRoot, prepared.recovery.taskId, prepared.recovery.family, prepared.recovery.artifact);
372
- if (!intent)
373
- throw new Error('ARTIFACT_RECOVERY_INTENT_MISSING');
374
- let committed;
375
- if (intent.state === 'passed' || intent.state === 'consumed') {
376
- return {
377
- ...prepared.result,
378
- status: 'no-op',
379
- changed: false,
380
- artifactSha256: intent.finalArtifactSha256,
381
- semanticDigest: intent.finalSemanticDigest,
382
- error: null
383
- };
384
- }
385
- else if (intent.state === 'commit-started') {
386
- const reconciled = reconcileArtifactRecovery(prepared.recovery, {
387
- lockAlreadyHeld: prepared.lockAlreadyHeld,
388
- afterPublish: options.afterPublish
389
- });
390
- committed = reconciled.intent;
391
- }
392
- else if (intent.state === 'awaiting-preflight-recovery') {
393
- const staged = stageArtifactCandidate(prepared.recovery, Buffer.from(prepared.content, 'utf8'), { lockAlreadyHeld: prepared.lockAlreadyHeld });
394
- prepareArtifactRecoveryCommit(prepared.recovery, staged.candidateSha256, staged.semanticDigest, { lockAlreadyHeld: prepared.lockAlreadyHeld });
395
- committed = commitArtifactRecovery(prepared.recovery, { lockAlreadyHeld: prepared.lockAlreadyHeld });
396
- }
397
- else if (intent.state === 'preflight-ready' || intent.state === 'preflight-commit-started') {
398
- committed = commitArtifactRecovery(prepared.recovery, { lockAlreadyHeld: prepared.lockAlreadyHeld });
399
- }
400
- if (committed?.state === 'preflight-passed' || intent.state === 'preflight-passed') {
401
- prepareArtifactRecoveryFinal(prepared.recovery, Buffer.from(prepared.content, 'utf8'), { lockAlreadyHeld: prepared.lockAlreadyHeld });
402
- committed = commitArtifactRecovery(prepared.recovery, { lockAlreadyHeld: prepared.lockAlreadyHeld, afterPublish: options.afterPublish });
227
+ fs.writeFileSync(temporary, content, { encoding: 'utf8', flag: 'wx' });
228
+ fs.renameSync(temporary, file);
229
+ }
230
+ finally {
231
+ try {
232
+ fs.unlinkSync(temporary);
403
233
  }
404
- else if (intent.state === 'full-finalizer-ready') {
405
- committed = commitArtifactRecovery(prepared.recovery, { lockAlreadyHeld: prepared.lockAlreadyHeld, afterPublish: options.afterPublish });
234
+ catch (error) {
235
+ if (error.code !== 'ENOENT')
236
+ throw error;
406
237
  }
407
- if (!committed || committed.state !== 'passed')
408
- throw new Error(`ARTIFACT_RECOVERY_STATE_INVALID: commit ended in '${committed?.state ?? intent.state}'`);
409
- return {
410
- ...prepared.result,
411
- artifactSha256: committed.finalArtifactSha256,
412
- semanticDigest: committed.finalSemanticDigest,
413
- error: null
414
- };
415
- }
416
- catch (error) {
417
- if (error instanceof Error && error.message.startsWith('TASK_WORKFLOW_FAULT_INJECTED:'))
418
- throw error;
419
- return { ...prepared.result, status: 'failed', error: {
420
- code: 'REVIEW_RECOVERY_COMMIT_FAILED', message: `cannot publish review candidate: ${String(error)}`
421
- } };
422
238
  }
423
239
  }
424
240
  function finalizeReviewSummaryUnlocked(request, options = {}) {
@@ -443,10 +259,23 @@ function finalizeReviewSummaryUnlocked(request, options = {}) {
443
259
  const prepared = prepareReviewSummaryCandidate(request, content, options);
444
260
  if (request.dryRun)
445
261
  return prepared.result;
446
- return commitReviewSummaryProvenance(prepared, resolved.repoRoot);
262
+ if (prepared.result.status === 'failed' || !prepared.result.changed)
263
+ return prepared.result;
264
+ try {
265
+ replaceCurrentArtifactAtomically(validated.artifact.path, prepared.content);
266
+ return {
267
+ ...prepared.result,
268
+ status: 'applied',
269
+ artifactSha256: sha256Content(prepared.content),
270
+ semanticDigest: canonicalSemanticDigest(prepared.content)
271
+ };
272
+ }
273
+ catch (error) {
274
+ return failed(request, 'REVIEW_RECOVERY_COMMIT_FAILED', `cannot update current review artifact: ${error instanceof Error ? error.message : String(error)}`, resolved.taskId);
275
+ }
447
276
  }
448
277
  function finalizeReviewSummary(request, options = {}) {
449
- const effectiveOptions = { ...options, startRecovery: !request.dryRun };
278
+ const effectiveOptions = options;
450
279
  if (request.dryRun || options.lockAlreadyHeld)
451
280
  return finalizeReviewSummaryUnlocked(request, effectiveOptions);
452
281
  const resolved = resolveTaskRef(request.taskRef, { repoRoot: options.repoRoot });
@@ -29,6 +29,7 @@ import { validateLocalArtifact } from "./local-artifact-finalization.js";
29
29
  import { validateQualificationAudit } from "./qualification-audit.js";
30
30
  import { getArtifactSchema } from "./artifact-schema.js";
31
31
  import { inspectArtifactContract } from "./artifact-operations.js";
32
+ import { normalizeVerificationRecord } from './gate-policy.js';
32
33
  import { manualValidationFinalSummaryProjectionMatches } from "./manual-validation-receipt.js";
33
34
  import { readManualValidationCompletion } from "./manual-validation-completion.js";
34
35
  import { sha256File } from "./artifact-receipts.js";
@@ -1198,19 +1199,19 @@ function interpolate(template, taskDir, artifactFile) {
1198
1199
  .replace(/\{artifact-stem\}/g, artifactStem);
1199
1200
  }
1200
1201
  function summarizeGate(checks) {
1201
- if (checks.some((check) => check.status === "blocked")) {
1202
+ if (checks.some((check) => check.effectiveStatus === "blocked")) {
1202
1203
  return "blocked";
1203
1204
  }
1204
- if (checks.some((check) => check.status === "fail")) {
1205
+ if (checks.some((check) => check.effectiveStatus === "fail")) {
1205
1206
  return "fail";
1206
1207
  }
1207
1208
  return "pass";
1208
1209
  }
1209
1210
  function summarizeChecks(checks) {
1210
1211
  const counts = {
1211
- pass: checks.filter((check) => check.status === "pass").length,
1212
- fail: checks.filter((check) => check.status === "fail").length,
1213
- blocked: checks.filter((check) => check.status === "blocked").length
1212
+ pass: checks.filter((check) => check.effectiveStatus === "pass").length,
1213
+ fail: checks.filter((check) => check.effectiveStatus === "fail").length,
1214
+ blocked: checks.filter((check) => check.effectiveStatus === "blocked").length
1214
1215
  };
1215
1216
  if (counts.blocked > 0) {
1216
1217
  return `${counts.pass} passed, ${counts.fail} failed, ${counts.blocked} blocked`;
@@ -1221,7 +1222,7 @@ function buildAction(gate, checks) {
1221
1222
  if (gate === "pass") {
1222
1223
  return "All declared checks passed";
1223
1224
  }
1224
- const firstFailure = checks.find((check) => check.status !== "pass");
1225
+ const firstFailure = checks.find((check) => check.effectiveStatus !== "pass");
1225
1226
  if (!firstFailure) {
1226
1227
  return "Review validation output";
1227
1228
  }
@@ -1277,16 +1278,16 @@ async function verifyInProcess({ mode, skillName, taskDir, artifactFile, checks:
1277
1278
  for (const [type, checkConfig] of Object.entries(verifyConfig.checks || {})) {
1278
1279
  if (checkConfig === null)
1279
1280
  continue;
1280
- const result = await runCheck(type, {
1281
+ const result = normalizeVerificationRecord(await runCheck(type, {
1281
1282
  mode,
1282
1283
  skillName,
1283
1284
  taskDir: path.resolve(taskDir),
1284
1285
  artifactFile,
1285
1286
  repositoryRoot: repoRoot,
1286
1287
  config: checkConfig
1287
- }, shared);
1288
- checks.push(result);
1289
- if (result.status === "blocked")
1288
+ }, shared));
1289
+ checks.push(...(result.subchecks?.map(normalizeVerificationRecord) ?? [result]));
1290
+ if (checks.at(-1)?.effectiveStatus === "blocked")
1290
1291
  break;
1291
1292
  }
1292
1293
  const gate = summarizeGate(checks);
@@ -1298,15 +1299,25 @@ async function verifyInProcess({ mode, skillName, taskDir, artifactFile, checks:
1298
1299
  return { skill: skillName, ...failResult(type, `Unknown check type '${type}' for skill '${skillName}'.`) };
1299
1300
  if (config === null)
1300
1301
  return { skill: skillName, ...passResult(type, `Check '${type}' is disabled for skill '${skillName}'.`) };
1302
+ const result = normalizeVerificationRecord(await runCheck(type, {
1303
+ skillName,
1304
+ taskDir: path.resolve(taskDir),
1305
+ artifactFile,
1306
+ repositoryRoot: repoRoot,
1307
+ config
1308
+ }, shared));
1309
+ if (!result.subchecks?.length)
1310
+ return { skill: skillName, ...result };
1311
+ const checks = result.subchecks.map(normalizeVerificationRecord);
1312
+ const gate = summarizeGate(checks);
1301
1313
  return {
1302
1314
  skill: skillName,
1303
- ...await runCheck(type, {
1304
- skillName,
1305
- taskDir: path.resolve(taskDir),
1306
- artifactFile,
1307
- repositoryRoot: repoRoot,
1308
- config
1309
- }, shared)
1315
+ ...result,
1316
+ status: gate,
1317
+ effectiveStatus: gate,
1318
+ reason: gate === 'pass' ? 'OK' : checks.find((check) => check.effectiveStatus !== 'pass')?.reason ?? 'CHECK_FAILED',
1319
+ action: buildAction(gate, checks),
1320
+ checks
1310
1321
  };
1311
1322
  }
1312
1323
  export { verifyInProcess };
@@ -80,7 +80,7 @@ async function verifyTaskEvent(request, options = {}) {
80
80
  else {
81
81
  for (const check of spec.checks ?? []) {
82
82
  const payload = await engine({ mode: 'checks', skillName: spec.skill, taskDir: resolved.taskDir, artifactFile: request.artifact, checks: [check], repositoryRoot: resolved.repoRoot });
83
- const status = payload.status;
83
+ const status = (payload.effectiveStatus ?? payload.status);
84
84
  if (status !== 'pass' && allowsManualOverride(options.manualOverride, 'verification-engine', status === 'blocked' ? 'CHECK_BLOCKED' : 'CHECK_FAILED')) {
85
85
  invocations.push({ status: 'pass', exitCode: 0, payload: { ...payload, status: 'pass', humanOverride: 'human-approved', originalStatus: status } });
86
86
  break;
@@ -103,19 +103,35 @@ function statusLabel(status) {
103
103
  function renderPayload(payload) {
104
104
  const lines = [];
105
105
  if (Array.isArray(payload.checks)) {
106
- lines.push(`Verification: ${payload.gate} | Skill: ${payload.skill}`, '');
106
+ const gateMode = typeof payload.gate === 'string';
107
+ lines.push(gateMode
108
+ ? `Verification: ${payload.gate} | Skill: ${payload.skill}`
109
+ : `Check: ${payload.status} | Skill: ${payload.skill} | Type: ${payload.type}`, '');
107
110
  for (const check of payload.checks) {
108
- lines.push(` [${statusLabel(check.status)}] ${check.type} - ${check.message}`);
111
+ const raw = statusLabel(check.status);
112
+ const effective = statusLabel(check.effectiveStatus ?? check.status);
113
+ const reason = check.reason ?? 'CHECK_FAILED';
114
+ const message = check.message ?? reason;
115
+ lines.push(` [${effective}] ${check.checkId ?? check.type} (${check.classification ?? 'hard'}; raw ${raw}; reason ${reason}) - ${message}; ${check.action ?? 'Review validation output'}`);
109
116
  }
110
- lines.push('', `Result: ${payload.summary} - ${payload.action}`);
117
+ const summary = payload.summary ?? (() => {
118
+ const checks = payload.checks;
119
+ const statuses = checks.map((check) => check.effectiveStatus ?? check.status);
120
+ return `${statuses.filter((status) => status === 'pass').length} passed, ${statuses.filter((status) => status === 'fail').length} failed${statuses.includes('blocked') ? `, ${statuses.filter((status) => status === 'blocked').length} blocked` : ''}`;
121
+ })();
122
+ lines.push('', `Result: ${summary} - ${payload.action}`);
111
123
  }
112
124
  else {
113
125
  lines.push(`Check: ${payload.status} | Skill: ${payload.skill} | Type: ${payload.type}`, '');
114
- lines.push(` [${statusLabel(payload.status)}] ${payload.type} - ${payload.message}`);
115
- const summary = payload.status === 'pass' ? '1 passed, 0 failed'
116
- : payload.status === 'blocked' ? '0 passed, 0 failed, 1 blocked' : '0 passed, 1 failed';
117
- const action = payload.status === 'pass' ? 'Requested check passed'
118
- : payload.status === 'blocked' ? `Resolve blocked ${payload.type} check and re-run check`
126
+ const raw = statusLabel(payload.status);
127
+ const effective = statusLabel(payload.effectiveStatus ?? payload.status);
128
+ const reason = payload.reason ?? 'CHECK_FAILED';
129
+ lines.push(` [${effective}] ${payload.checkId ?? payload.type} (${payload.classification ?? 'hard'}; raw ${raw}; reason ${reason}) - ${payload.message ?? reason}; ${payload.action ?? 'Review validation output'}`);
130
+ const status = payload.effectiveStatus ?? payload.status;
131
+ const summary = status === 'pass' ? '1 passed, 0 failed'
132
+ : status === 'blocked' ? '0 passed, 0 failed, 1 blocked' : '0 passed, 1 failed';
133
+ const action = status === 'pass' ? 'Requested check passed'
134
+ : status === 'blocked' ? `Resolve blocked ${payload.type} check and re-run check`
119
135
  : `Fix ${payload.type} issues and re-run check`;
120
136
  lines.push('', `Result: ${summary} - ${action}`);
121
137
  }
@@ -9,10 +9,13 @@ export const TASK_WORKFLOW_COMMANDS = Object.freeze({
9
9
  'ledger-finding-response': ['task-ledger', 'finding-respond'],
10
10
  'ledger-finding-review': ['task-ledger', 'finding-review'],
11
11
  'ledger-finding-upsert': ['task-ledger', 'finding-upsert'],
12
+ 'ledger-stage-status': ['task-ledger', 'stage-status'],
12
13
  'decision-next-id': ['task-ledger', 'decision-next-id'],
13
14
  'decision-upsert': ['task-ledger', 'decision-upsert'],
14
15
  'invalidation-reconcile': ['task-invalidation', 'reconcile'],
15
- 'warning-add': ['task-warning', 'add']
16
+ 'warning-add': ['task-warning', 'add'],
17
+ 'warning-list': ['task-warning', 'list'],
18
+ 'warning-set-status': ['task-warning', 'set-status']
16
19
  });
17
20
  export const TASK_WORKFLOW_OPERATIONS = Object.freeze(Object.keys(TASK_WORKFLOW_COMMANDS));
18
21
  //# sourceMappingURL=workflow-command.js.map
@@ -1,5 +1,8 @@
1
+ import fs from 'node:fs';
1
2
  import { reconcileTaskInvalidation } from './invalidation-command.js';
2
3
  import { applyLedgerIntent } from './ledger-intents.js';
4
+ import { isReviewStage, parseLedgerDocument, summarizeLedgerStage, validateLedgerRows } from './ledger.js';
5
+ import { resolveTaskRef } from './resolve-ref.js';
3
6
  import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.js';
4
7
  import { applyWorkflowWarningIntent } from './workflow-warning-intents.js';
5
8
  function failed(code, message) {
@@ -102,8 +105,27 @@ function ledgerIntent(operation, args) {
102
105
  }
103
106
  function warningIntent(args) {
104
107
  const [taskRef, kind] = args;
105
- if (!taskRef || kind !== 'add')
108
+ if (!taskRef || !['add', 'list', 'set-status'].includes(kind ?? '')) {
106
109
  return failed('TASK_WORKFLOW_PAYLOAD_INVALID', 'task workflow warning request is invalid');
110
+ }
111
+ if (kind === 'list') {
112
+ const values = options(args, { '--status': 'status' });
113
+ if (isFailure(values))
114
+ return values;
115
+ return { kind, taskRef, ...(value(values, '--status') ? { status: value(values, '--status') } : {}) };
116
+ }
117
+ if (kind === 'set-status') {
118
+ const values = options(args, { '--id': 'id', '--status': 'status', '--resolution': 'resolution', '--dry-run': 'dryRun' });
119
+ if (isFailure(values))
120
+ return values;
121
+ const missing = required(values, ['--id', '--status', '--resolution']);
122
+ if (missing)
123
+ return missing;
124
+ return {
125
+ kind, taskRef, id: value(values, '--id'), status: value(values, '--status'),
126
+ resolution: value(values, '--resolution'), ...(values['--dry-run'] === true ? { dryRun: true } : {})
127
+ };
128
+ }
107
129
  const values = options(args, {
108
130
  '--step': 'step', '--severity': 'severity', '--code': 'code', '--target': 'target',
109
131
  '--message': 'message', '--action': 'action', '--dry-run': 'dryRun'
@@ -119,6 +141,32 @@ function warningIntent(args) {
119
141
  action: value(values, '--action'), ...(values['--dry-run'] === true ? { dryRun: true } : {})
120
142
  };
121
143
  }
144
+ function stageStatus(repoRoot, args) {
145
+ const [taskRef, kind] = args;
146
+ if (!taskRef || kind !== 'stage-status')
147
+ return failed('TASK_WORKFLOW_PAYLOAD_INVALID', 'task workflow ledger request is invalid');
148
+ const values = options(args, { '--stage': 'stage' });
149
+ if (isFailure(values))
150
+ return values;
151
+ const stage = value(values, '--stage');
152
+ if (!stage || !isReviewStage(stage))
153
+ return failed('TASK_WORKFLOW_PAYLOAD_INVALID', 'stage-status requires a valid review stage');
154
+ const resolved = resolveTaskRef(taskRef, { repoRoot });
155
+ if (!resolved.ok)
156
+ return failed(resolved.code, resolved.message);
157
+ try {
158
+ const ledger = parseLedgerDocument(fs.readFileSync(resolved.taskMdPath, 'utf8'));
159
+ if (!ledger.present)
160
+ return failed('LEDGER_SECTION_MISSING', 'review disagreement ledger section is missing');
161
+ const invalid = validateLedgerRows(ledger.rows);
162
+ if (invalid)
163
+ return failed(invalid.code, invalid.message);
164
+ return { status: 'ready', changed: false, taskId: resolved.taskId, stageStatus: summarizeLedgerStage(ledger.rows, stage), error: null };
165
+ }
166
+ catch (error) {
167
+ return failed('LEDGER_DOCUMENT_INVALID', error instanceof Error ? error.message : String(error));
168
+ }
169
+ }
122
170
  /**
123
171
  * The caller has already selected and authorized the execution boundary.
124
172
  * This function deliberately invokes task domains in that process: starting a
@@ -143,11 +191,15 @@ export function dispatchWorkflowCommand(repoRoot, operation, args) {
143
191
  ...(values['--dry-run'] === true ? { dryRun: true } : {})
144
192
  }));
145
193
  }
146
- if (operation === 'warning-add') {
194
+ if (operation === 'ledger-stage-status')
195
+ return stageStatus(repoRoot, args);
196
+ if (operation === 'warning-add' || operation === 'warning-list' || operation === 'warning-set-status') {
147
197
  const intent = warningIntent(args);
148
198
  if (isFailure(intent))
149
199
  return intent;
150
- return withTaskExecutionLock(repoRoot, taskRef, 'task-warning.add', () => applyWorkflowWarningIntent(intent, { repoRoot }));
200
+ if (intent.kind === 'list')
201
+ return applyWorkflowWarningIntent(intent, { repoRoot });
202
+ return withTaskExecutionLock(repoRoot, taskRef, `task-warning.${intent.kind}`, () => applyWorkflowWarningIntent(intent, { repoRoot }));
151
203
  }
152
204
  const intent = ledgerIntent(operation, args);
153
205
  if (isFailure(intent))
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "protocolVersion": 3,
3
- "packageVersion": "0.11.0",
4
- "sourceInputHash": "c67e66a46040d5246d67c751af605b796cd61913b98add01ff327829c01ce3db",
5
- "internalExecutableBuildHash": "f6b4be06df12111b3a7614d335c9e802049c3cea194c6e7c040f3251188c694f"
3
+ "packageVersion": "0.11.2",
4
+ "sourceInputHash": "0a1ffa8b90a0d157f9b2aabea6a43f946d68a622bde337befe71059f63b3812b",
5
+ "internalExecutableBuildHash": "b6742e1b5d1b997bf7e21e644bfde2bafaa608f7ede7ca308ff61c2cd19da831"
6
6
  }
@@ -46,7 +46,7 @@ export const INTERNAL_HANDLER_ROUTE_SELECTORS = Object.freeze({
46
46
  'platform-security': ['read', 'dismiss'],
47
47
  'platform-metadata': ['init-labels', 'init-milestones'],
48
48
  'platform-context': ['resolve'],
49
- 'platform-comment': ['list', 'recover', 'owner', 'backfill', 'sync'],
49
+ 'platform-comment': ['list', 'recover', 'owner', 'backfill', 'stage-summary', 'sync'],
50
50
  'platform-issue': ['inspect', 'create', 'bind', 'sync'],
51
51
  'platform-pr': ['inspect', 'summary-context', 'resolve-external', 'create', 'bind', 'skip', 'sync', 'change-report', 'summary-sync', 'sync-in-labels'],
52
52
  'platform-pr-review': ['inspect', 'list', 'publish-pr', 'publish-task'],