@fitlab-ai/agent-infra 0.9.14 → 0.9.15

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 (250) hide show
  1. package/README.md +10 -0
  2. package/README.zh-CN.md +10 -0
  3. package/bin/internal-cli.sh +45 -0
  4. package/dist/bin/internal-cli.js +93 -30
  5. package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +165 -27
  6. package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +3 -2
  7. package/dist/lib/defaults.json +103 -103
  8. package/dist/lib/fs/durable-write.js +37 -0
  9. package/dist/lib/fs/file-lock.js +111 -0
  10. package/dist/lib/host-control/audit.js +19 -0
  11. package/dist/lib/host-control/client.js +142 -0
  12. package/dist/lib/host-control/command.js +49 -0
  13. package/dist/lib/host-control/path.js +160 -0
  14. package/dist/lib/host-control/secure-fs.js +140 -0
  15. package/dist/lib/host-control/server.js +159 -0
  16. package/dist/lib/host-control/service.js +112 -0
  17. package/dist/lib/init.js +11 -2
  18. package/dist/lib/internal/cli-route-inventory.js +3 -1
  19. package/dist/lib/internal/codex-lifecycle.js +6 -6
  20. package/dist/lib/internal/demo-promotion.js +322 -0
  21. package/dist/lib/internal/demo-transcript.js +454 -0
  22. package/dist/lib/internal/host-control.js +28 -0
  23. package/dist/lib/internal/manual-validation.js +322 -0
  24. package/dist/lib/internal/platform-pr.js +18 -4
  25. package/dist/lib/internal/release-workflow.js +63 -84
  26. package/dist/lib/internal/sandbox-control.js +149 -2
  27. package/dist/lib/internal/task-artifact.js +15 -262
  28. package/dist/lib/internal/task-create.js +6 -5
  29. package/dist/lib/internal/task-event.js +37 -37
  30. package/dist/lib/internal/task-operation-registry.js +101 -3
  31. package/dist/lib/internal/task-review.js +8 -53
  32. package/dist/lib/internal/task-validate.js +30 -10
  33. package/dist/lib/owned-path.js +33 -0
  34. package/dist/lib/platform/check-status.js +11 -0
  35. package/dist/lib/platform/comment-safety.js +4 -1
  36. package/dist/lib/platform/completion-backfill.js +1 -1
  37. package/dist/lib/platform/github-data.js +241 -0
  38. package/dist/lib/platform/github-provider.js +15 -20
  39. package/dist/lib/platform/issue-comments.js +2 -2
  40. package/dist/lib/platform/issues.js +1 -179
  41. package/dist/lib/platform/pr-checks.js +12 -65
  42. package/dist/lib/platform/pr-summary.js +55 -3
  43. package/dist/lib/platform/pull-requests.js +2 -105
  44. package/dist/lib/platform/snapshots.js +2 -0
  45. package/dist/lib/platform/verification-sync.js +127 -142
  46. package/dist/lib/release/workflow.js +1 -1
  47. package/dist/lib/sandbox/commands/create.js +7 -2
  48. package/dist/lib/sandbox/commands/rm.js +3 -1514
  49. package/dist/lib/sandbox/control/audit.js +203 -0
  50. package/dist/lib/sandbox/control/client.js +93 -54
  51. package/dist/lib/sandbox/control/executor.js +129 -65
  52. package/dist/lib/sandbox/control/finalization-response.js +27 -0
  53. package/dist/lib/sandbox/control/identity-sentinel.js +107 -0
  54. package/dist/lib/sandbox/control/lifecycle.js +36 -2
  55. package/dist/lib/sandbox/control/native-file-lock.js +6 -98
  56. package/dist/lib/sandbox/control/protocol.js +55 -4
  57. package/dist/lib/sandbox/control/server.js +491 -120
  58. package/dist/lib/sandbox/control/state.js +58 -0
  59. package/dist/lib/sandbox/control/task-workflow.js +74 -0
  60. package/dist/lib/sandbox/control/workflow-executor.js +99 -0
  61. package/dist/lib/sandbox/index.js +2 -2
  62. package/dist/lib/sandbox/recovery.js +28 -4
  63. package/dist/lib/sandbox/removal.js +1717 -0
  64. package/dist/lib/sandbox/task-cleanup.js +217 -0
  65. package/dist/lib/sandbox/workspace-view.js +13 -14
  66. package/dist/lib/task/activity-intent.js +2 -1
  67. package/dist/lib/task/activity-log.js +30 -24
  68. package/dist/lib/task/artifact-command.js +101 -0
  69. package/dist/lib/task/artifact-lifecycle.js +30 -28
  70. package/dist/lib/task/artifact-name.js +36 -0
  71. package/dist/lib/task/artifact-operations.js +23 -69
  72. package/dist/lib/task/artifact-receipts.js +3 -15
  73. package/dist/lib/task/artifact-repair-intent.js +87 -16
  74. package/dist/lib/task/artifact-schema.js +6 -14
  75. package/dist/lib/task/capabilities.js +12 -19
  76. package/dist/lib/task/codex-orchestration.js +71 -25
  77. package/dist/lib/task/commit-identity.js +28 -0
  78. package/dist/lib/task/commit-operation.js +2 -16
  79. package/dist/lib/task/control-authority.js +371 -5
  80. package/dist/lib/task/control-recovery.js +128 -0
  81. package/dist/lib/task/create.js +62 -9
  82. package/dist/lib/task/decision-details.js +2 -46
  83. package/dist/lib/task/events.js +120 -9
  84. package/dist/lib/task/finalization-state.js +9 -0
  85. package/dist/lib/task/human-override.js +3 -6
  86. package/dist/lib/task/implementation-inputs.js +3 -3
  87. package/dist/lib/task/intermediate-cleanup.js +383 -0
  88. package/dist/lib/task/invalidation.js +18 -65
  89. package/dist/lib/task/ledger-intents.js +7 -22
  90. package/dist/lib/task/ledger.js +1 -3
  91. package/dist/lib/task/lifecycle.js +19 -1
  92. package/dist/lib/task/local-artifact-finalization.js +132 -157
  93. package/dist/lib/task/manual-validation-completion.js +26 -0
  94. package/dist/lib/task/manual-validation-evidence.js +130 -0
  95. package/dist/lib/task/manual-validation-receipt.js +101 -0
  96. package/dist/lib/task/manual-validation-shared.js +40 -0
  97. package/dist/lib/task/manual-validation-transaction.js +243 -0
  98. package/dist/lib/task/markdown.js +47 -0
  99. package/dist/lib/task/orchestration.js +14 -17
  100. package/dist/lib/task/qualification-audit.js +20 -34
  101. package/dist/lib/task/review-artifacts.js +2 -18
  102. package/dist/lib/task/review-command.js +25 -0
  103. package/dist/lib/task/review-finalization.js +125 -193
  104. package/dist/lib/task/review-fingerprint.js +2 -2
  105. package/dist/lib/task/sections.js +34 -56
  106. package/dist/lib/task/verification-engine.js +116 -323
  107. package/dist/lib/task/verification.js +1 -1
  108. package/dist/lib/task/workflow-command.js +17 -0
  109. package/dist/lib/task/workflow-warning-intents.js +0 -16
  110. package/dist/lib/task/workflow-warnings.js +28 -2
  111. package/dist/lib/update.js +2 -0
  112. package/dist/lifecycle-build-manifest.json +3 -3
  113. package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +223 -33
  114. package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +3 -2
  115. package/lib/fs/durable-write.ts +29 -0
  116. package/lib/fs/file-lock.ts +113 -0
  117. package/lib/host-control/audit.ts +22 -0
  118. package/lib/host-control/client.ts +172 -0
  119. package/lib/host-control/command.ts +60 -0
  120. package/lib/host-control/path.ts +167 -0
  121. package/lib/host-control/secure-fs.ts +135 -0
  122. package/lib/host-control/server.ts +180 -0
  123. package/lib/host-control/service.ts +133 -0
  124. package/lib/init.ts +15 -2
  125. package/lib/internal/cli-route-inventory.ts +3 -1
  126. package/lib/internal/codex-lifecycle.ts +6 -6
  127. package/lib/internal/demo-promotion.ts +354 -0
  128. package/lib/internal/demo-transcript.ts +419 -0
  129. package/lib/internal/host-control.ts +28 -0
  130. package/lib/internal/manual-validation.ts +306 -0
  131. package/lib/internal/platform-pr.ts +15 -4
  132. package/lib/internal/release-workflow.ts +74 -82
  133. package/lib/internal/sandbox-control.ts +162 -1
  134. package/lib/internal/task-artifact.ts +13 -216
  135. package/lib/internal/task-create.ts +5 -5
  136. package/lib/internal/task-event.ts +20 -11
  137. package/lib/internal/task-operation-registry.ts +111 -3
  138. package/lib/internal/task-review.ts +6 -51
  139. package/lib/internal/task-validate.ts +26 -10
  140. package/lib/owned-path.ts +26 -0
  141. package/lib/platform/check-status.ts +9 -0
  142. package/lib/platform/comment-safety.ts +5 -0
  143. package/lib/platform/completion-backfill.ts +1 -1
  144. package/lib/platform/github-data.ts +322 -0
  145. package/lib/platform/github-provider.ts +15 -20
  146. package/lib/platform/issue-comments.ts +3 -4
  147. package/lib/platform/issues.ts +4 -245
  148. package/lib/platform/pr-checks.ts +13 -77
  149. package/lib/platform/pr-summary.ts +61 -5
  150. package/lib/platform/pull-requests.ts +3 -170
  151. package/lib/platform/snapshots.ts +53 -0
  152. package/lib/platform/verification-sync.ts +145 -155
  153. package/lib/release/workflow.ts +3 -3
  154. package/lib/sandbox/commands/create.ts +7 -6
  155. package/lib/sandbox/commands/rm.ts +3 -1909
  156. package/lib/sandbox/control/audit.ts +253 -0
  157. package/lib/sandbox/control/client.ts +109 -66
  158. package/lib/sandbox/control/executor.ts +134 -74
  159. package/lib/sandbox/control/finalization-response.ts +29 -0
  160. package/lib/sandbox/control/identity-sentinel.ts +137 -0
  161. package/lib/sandbox/control/lifecycle.ts +45 -3
  162. package/lib/sandbox/control/native-file-lock.ts +6 -107
  163. package/lib/sandbox/control/protocol.ts +75 -6
  164. package/lib/sandbox/control/server.ts +538 -123
  165. package/lib/sandbox/control/state.ts +78 -0
  166. package/lib/sandbox/control/task-workflow.ts +95 -0
  167. package/lib/sandbox/control/workflow-executor.ts +102 -0
  168. package/lib/sandbox/index.ts +2 -2
  169. package/lib/sandbox/recovery.ts +28 -5
  170. package/lib/sandbox/removal.ts +2144 -0
  171. package/lib/sandbox/task-cleanup.ts +209 -0
  172. package/lib/sandbox/workspace-view.ts +17 -15
  173. package/lib/task/activity-intent.ts +2 -2
  174. package/lib/task/activity-log.ts +27 -19
  175. package/lib/task/artifact-command.ts +104 -0
  176. package/lib/task/artifact-lifecycle.ts +31 -26
  177. package/lib/task/artifact-name.ts +55 -0
  178. package/lib/task/artifact-operations.ts +22 -68
  179. package/lib/task/artifact-receipts.ts +3 -14
  180. package/lib/task/artifact-repair-intent.ts +99 -18
  181. package/lib/task/artifact-schema.ts +5 -34
  182. package/lib/task/capabilities.ts +13 -22
  183. package/lib/task/codex-orchestration.ts +85 -24
  184. package/lib/task/commit-identity.ts +31 -0
  185. package/lib/task/commit-operation.ts +2 -16
  186. package/lib/task/control-authority.ts +502 -5
  187. package/lib/task/control-recovery.ts +174 -0
  188. package/lib/task/create.ts +63 -8
  189. package/lib/task/decision-details.ts +3 -71
  190. package/lib/task/events.ts +131 -10
  191. package/lib/task/finalization-state.ts +11 -0
  192. package/lib/task/human-override.ts +3 -8
  193. package/lib/task/implementation-inputs.ts +3 -3
  194. package/lib/task/intermediate-cleanup.ts +455 -0
  195. package/lib/task/invalidation.ts +18 -62
  196. package/lib/task/ledger-intents.ts +7 -24
  197. package/lib/task/ledger.ts +1 -3
  198. package/lib/task/lifecycle.ts +17 -1
  199. package/lib/task/local-artifact-finalization.ts +148 -182
  200. package/lib/task/manual-validation-completion.ts +40 -0
  201. package/lib/task/manual-validation-evidence.ts +209 -0
  202. package/lib/task/manual-validation-receipt.ts +152 -0
  203. package/lib/task/manual-validation-shared.ts +50 -0
  204. package/lib/task/manual-validation-transaction.ts +304 -0
  205. package/lib/task/markdown.ts +70 -0
  206. package/lib/task/orchestration.ts +19 -19
  207. package/lib/task/qualification-audit.ts +22 -33
  208. package/lib/task/review-artifacts.ts +1 -20
  209. package/lib/task/review-command.ts +23 -0
  210. package/lib/task/review-finalization.ts +131 -259
  211. package/lib/task/review-fingerprint.ts +2 -2
  212. package/lib/task/sections.ts +33 -54
  213. package/lib/task/verification-engine.ts +110 -366
  214. package/lib/task/verification-types.ts +17 -0
  215. package/lib/task/verification.ts +1 -1
  216. package/lib/task/workflow-command.ts +18 -0
  217. package/lib/task/workflow-warning-intents.ts +0 -13
  218. package/lib/task/workflow-warnings.ts +21 -2
  219. package/lib/update.ts +6 -0
  220. package/package.json +6 -6
  221. package/templates/.agents/hooks/lifecycle-delegation.js +6 -6
  222. package/templates/.agents/rules/testing-discipline.en.md +2 -0
  223. package/templates/.agents/rules/testing-discipline.zh-CN.md +2 -0
  224. package/templates/.agents/scripts/package.json +3 -0
  225. package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +32 -20
  226. package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +32 -20
  227. package/templates/.agents/skills/complete-manual-validation/config/verify.en.json +1 -1
  228. package/templates/.agents/skills/complete-manual-validation/config/verify.zh-CN.json +1 -1
  229. package/templates/.agents/skills/complete-manual-validation/reference/report-template.en.md +2 -0
  230. package/templates/.agents/skills/complete-manual-validation/reference/report-template.zh-CN.md +2 -0
  231. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.en.md +16 -7
  232. package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +16 -7
  233. package/templates/.agents/skills/complete-task/SKILL.en.md +1 -1
  234. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +1 -1
  235. package/templates/.agents/skills/release/SKILL.en.md +7 -3
  236. package/templates/.agents/skills/release/SKILL.zh-CN.md +7 -3
  237. package/templates/.agents/skills/run-manual-validation/SKILL.en.md +20 -3
  238. package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +20 -3
  239. package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.en.md +4 -1
  240. package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.zh-CN.md +4 -1
  241. package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +2 -0
  242. package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +2 -0
  243. package/templates/.agents/skills/run-task/SKILL.en.md +1 -1
  244. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +1 -1
  245. package/templates/.agents/skills/test/SKILL.en.md +17 -6
  246. package/templates/.agents/skills/test/SKILL.zh-CN.md +17 -6
  247. package/templates/.claude/commands/run-manual-validation.en.md +1 -1
  248. package/templates/.claude/commands/run-manual-validation.zh-CN.md +1 -1
  249. package/dist/lib/platform/adapters.js +0 -56
  250. package/lib/platform/adapters.ts +0 -197
@@ -0,0 +1,23 @@
1
+ import type { ReviewFinalizationRequest } from './review-finalization.ts';
2
+
3
+ export function parseReviewCommand(args: readonly string[]): ReviewFinalizationRequest & {
4
+ overrideTicket?: string; overrideTarget?: string; overrideScope?: string;
5
+ } {
6
+ if (!args[0] || args[1] !== 'finalize-summary') throw new Error('task ref and finalize-summary are required');
7
+ const values: Record<string, string | boolean> = {};
8
+ const flags = {
9
+ '--stage': 'stage', '--artifact': 'artifact', '--orchestrated': 'orchestrated', '--dry-run': 'dryRun',
10
+ '--override-ticket': 'overrideTicket', '--override-target': 'overrideTarget', '--override-scope': 'overrideScope'
11
+ } as const;
12
+ for (let index = 2; index < args.length; index += 1) {
13
+ const flag = args[index]!;
14
+ if (!Object.hasOwn(flags, flag)) throw new Error(`unknown option '${flag}'`);
15
+ const key = flags[flag as keyof typeof flags];
16
+ if (Object.hasOwn(values, key)) throw new Error(`duplicate option '${flag}'`);
17
+ const value = flag === '--orchestrated' || flag === '--dry-run' ? true : args[++index];
18
+ if (!value || typeof value === 'string' && value.startsWith('--')) throw new Error(`option '${flag}' requires a value`);
19
+ values[key] = value;
20
+ }
21
+ if (!values.stage || !values.artifact) throw new Error("options '--stage' and '--artifact' are required");
22
+ return { taskRef: args[0], ...values } as ReturnType<typeof parseReviewCommand>;
23
+ }
@@ -2,8 +2,8 @@ import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { randomUUID } from 'node:crypto';
4
4
 
5
- import { locateActivityLog, pairEntries, startedBackedRows } from './activity-log.ts';
6
- import { parseArtifactName, validateCompletedArtifact } from './artifact-lifecycle.ts';
5
+ import { parseArtifactName } from './artifact-name.ts';
6
+ import { validateCompletedArtifact, hasOpenArtifactRound, validateArtifactPublication } from './artifact-lifecycle.ts';
7
7
  import { LEDGER_SECTION_MISSING_CODE, LEDGER_SECTION_MISSING_MESSAGE, parseLedgerDocument, summarizeLedgerStage, validateLedgerRows } from './ledger.ts';
8
8
  import type { LedgerStageStatus, ReviewStage } from './ledger.ts';
9
9
  import { finalizeReviewSummaryContent } from './review-artifacts.ts';
@@ -18,6 +18,7 @@ import { getArtifactSchema } from './artifact-schema.ts';
18
18
  import { canonicalSemanticDigest, inspectArtifactContract, sha256Content } from './artifact-operations.ts';
19
19
  import { readArtifactRepairIntent, writeArtifactRepairIntent } from './artifact-repair-intent.ts';
20
20
  import type { ArtifactRepairOperation } from './artifact-operations.ts';
21
+ import type { ArtifactRepairIntent } from './artifact-repair-intent.ts';
21
22
 
22
23
  type ReviewFinalizationErrorCode =
23
24
  | ResolveTaskRefErrorCode
@@ -79,10 +80,10 @@ type ReviewFinalizationOptions = {
79
80
  lockAlreadyHeld?: boolean;
80
81
  };
81
82
 
82
- const STAGES: Record<ReviewStage, { family: 'review-analysis' | 'review-plan' | 'review-code'; action: string }> = {
83
- analysis: { family: 'review-analysis', action: 'Review Analysis' },
84
- plan: { family: 'review-plan', action: 'Review Plan' },
85
- code: { family: 'review-code', action: 'Review Code' }
83
+ const STAGES: Record<ReviewStage, { family: 'review-analysis' | 'review-plan' | 'review-code' }> = {
84
+ analysis: { family: 'review-analysis' },
85
+ plan: { family: 'review-plan' },
86
+ code: { family: 'review-code' }
86
87
  };
87
88
  const DEFAULT_FILE_SYSTEM: ReviewFileSystem = {
88
89
  readFileSync: (file) => fs.readFileSync(file, 'utf8'),
@@ -119,15 +120,6 @@ function failed(
119
120
  };
120
121
  }
121
122
 
122
- function openReviewRound(content: string, action: string, round: number): boolean {
123
- const section = locateActivityLog(content);
124
- if (!section) return false;
125
- const expected = `${action} (Round ${round})`;
126
- return startedBackedRows(pairEntries(section.entries)).filter((row) => (
127
- row.step === expected && !row.done
128
- )).length === 1;
129
- }
130
-
131
123
  function cleanupTemp(fileSystem: ReviewFileSystem, tempPath: string): void {
132
124
  try {
133
125
  fileSystem.unlinkSync(tempPath);
@@ -136,288 +128,167 @@ function cleanupTemp(fileSystem: ReviewFileSystem, tempPath: string): void {
136
128
  }
137
129
  }
138
130
 
139
- function finalizeReviewSummaryUnlocked(
131
+ type ReviewSummaryCandidatePreparation = Readonly<{
132
+ result: ReviewFinalizationResult;
133
+ content: string;
134
+ provenance?: Parameters<typeof writeArtifactRepairIntent>[1];
135
+ expectedIntent?: ArtifactRepairIntent | null;
136
+ }>;
137
+
138
+ /** Prepare domain validation, summary bytes and provenance before any publication. */
139
+ function prepareReviewSummaryCandidate(
140
140
  request: ReviewFinalizationRequest,
141
+ artifactContent: string,
141
142
  options: ReviewFinalizationOptions = {}
142
- ): ReviewFinalizationResult {
143
+ ): ReviewSummaryCandidatePreparation {
144
+ let taskId: string | null = null;
145
+ let stageStatus: LedgerStageStatus | null = null;
146
+ const reject = (code: ReviewFinalizationErrorCode, message: string, extra: Partial<ReviewFinalizationResult> = {}): ReviewSummaryCandidatePreparation => ({
147
+ result: failed(request, code, message, taskId, stageStatus, extra), content: artifactContent
148
+ });
143
149
  const stage = request.stage as ReviewStage;
144
150
  const spec = STAGES[stage];
145
- if (!spec) return failed(request, 'REVIEW_STAGE_INVALID', `unsupported review stage '${request.stage}'`);
146
- if (!request.taskRef || !request.artifact) {
147
- return failed(request, 'REVIEW_PAYLOAD_INVALID', 'taskRef, stage, and artifact are required');
148
- }
151
+ if (!spec) return reject('REVIEW_STAGE_INVALID', `unsupported review stage '${request.stage}'`);
152
+ if (!request.taskRef || !request.artifact) return reject('REVIEW_PAYLOAD_INVALID', 'taskRef, stage, and artifact are required');
149
153
  const resolved = resolveTaskRef(request.taskRef, { repoRoot: options.repoRoot });
150
- if (!resolved.ok) return failed(request, resolved.code, resolved.message, resolved.taskId);
154
+ taskId = resolved.taskId ?? null;
155
+ if (!resolved.ok) return reject(resolved.code, resolved.message);
151
156
  if (resolved.state !== 'active' && !allowsManualOverride(options.manualOverride, 'review-finalization', 'TASK_STATE_MISMATCH')) {
152
- return failed(request, 'TASK_STATE_MISMATCH', `task ${resolved.taskId} is ${resolved.state}, expected active`, resolved.taskId);
153
- }
154
- const parsedArtifact = parseArtifactName(request.artifact);
155
- if (!parsedArtifact || parsedArtifact.family !== spec.family) {
156
- return failed(
157
- request,
158
- 'REVIEW_ARTIFACT_IDENTITY_INVALID',
159
- `artifact '${request.artifact}' does not match ${spec.family}`,
160
- resolved.taskId
161
- );
157
+ return reject('TASK_STATE_MISMATCH', `task ${taskId} is ${resolved.state}, expected active`);
162
158
  }
163
- const validated = validateCompletedArtifact(
164
- resolved.taskDir,
165
- spec.family,
166
- request.artifact,
167
- parsedArtifact.round
168
- );
169
- if (!validated.ok) {
170
- const code = validated.error.code === 'ARTIFACT_NOT_REGULAR'
171
- ? 'REVIEW_ARTIFACT_NOT_REGULAR'
172
- : 'REVIEW_ARTIFACT_IDENTITY_INVALID';
173
- return failed(request, code, validated.error.message, resolved.taskId);
174
- }
175
-
176
- const fileSystem = { ...DEFAULT_FILE_SYSTEM, ...options.fileSystem };
159
+ const parsed = parseArtifactName(request.artifact);
160
+ if (!parsed || parsed.family !== spec.family) return reject('REVIEW_ARTIFACT_IDENTITY_INVALID', `artifact '${request.artifact}' does not match ${spec.family}`);
161
+ const publicationError = validateArtifactPublication(resolved.taskDir, spec.family, request.artifact);
162
+ if (publicationError) return reject('REVIEW_ARTIFACT_IDENTITY_INVALID', publicationError.message);
177
163
  let taskContent: string;
178
- let artifactContent: string;
179
- try {
180
- taskContent = fileSystem.readFileSync(resolved.taskMdPath);
181
- artifactContent = fileSystem.readFileSync(validated.artifact.path);
182
- } catch (error) {
183
- return failed(request, 'REVIEW_ARTIFACT_NOT_REGULAR', String(error), resolved.taskId);
184
- }
185
- const schema = getArtifactSchema(spec.family);
186
- const structure = schema ? inspectArtifactContract(artifactContent, schema) : null;
164
+ try { taskContent = (options.fileSystem?.readFileSync ?? DEFAULT_FILE_SYSTEM.readFileSync)(resolved.taskMdPath); }
165
+ catch (error) { return reject('REVIEW_ARTIFACT_NOT_REGULAR', String(error)); }
187
166
  const artifactSha256 = sha256Content(artifactContent);
188
- const artifactSemanticDigest = canonicalSemanticDigest(artifactContent);
167
+ const semanticDigest = canonicalSemanticDigest(artifactContent);
168
+ const digests = { artifactSha256, semanticDigest };
189
169
  let repairIntent;
190
- try {
191
- repairIntent = readArtifactRepairIntent(resolved.repoRoot, resolved.taskId, spec.family, request.artifact);
192
- } catch (error) {
193
- return failed(request, 'REVIEW_PROVENANCE_INVALID', String(error), resolved.taskId, null, {
194
- artifactSha256,
195
- semanticDigest: artifactSemanticDigest
196
- });
197
- }
198
- if (structure && !structure.ok) {
170
+ try { repairIntent = readArtifactRepairIntent(resolved.repoRoot, taskId!, spec.family, request.artifact); }
171
+ catch (error) { return reject('REVIEW_PROVENANCE_INVALID', String(error), digests); }
172
+ const schema = getArtifactSchema(spec.family)!;
173
+ const structure = inspectArtifactContract(artifactContent, schema);
174
+ if (!structure.ok) {
199
175
  const repairable = structure.repair !== null && structure.diagnostics.length === 1 && structure.diagnostics[0]?.repairable === true;
200
- if (repairable) {
201
- if (repairIntent && (
202
- repairIntent.state !== 'awaiting-repair' ||
203
- repairIntent.artifactSha256 !== artifactSha256 ||
204
- repairIntent.semanticDigest !== structure.semanticDigest
205
- )) {
206
- return failed(request, 'REVIEW_PROVENANCE_INVALID', 'a different review repair baseline is already recorded for this artifact', resolved.taskId, null, {
207
- artifactSha256,
208
- semanticDigest: structure.semanticDigest
209
- });
210
- }
211
- try {
212
- if (!repairIntent) {
213
- writeArtifactRepairIntent(resolved.repoRoot, {
214
- version: 1,
215
- taskId: resolved.taskId,
216
- family: spec.family,
217
- artifact: request.artifact,
218
- state: 'awaiting-repair',
219
- baselineSemanticDigest: structure.semanticDigest,
220
- artifactSha256,
221
- semanticDigest: structure.semanticDigest
222
- });
223
- }
224
- } catch (error) {
225
- return failed(request, 'REVIEW_PROVENANCE_INVALID', `cannot record repair provenance: ${String(error)}`, resolved.taskId, null, {
226
- artifactSha256,
227
- semanticDigest: structure.semanticDigest,
228
- operation: structure.repair
229
- });
230
- }
176
+ if (repairable && repairIntent && (repairIntent.state !== 'awaiting-repair'
177
+ || repairIntent.artifactSha256 !== artifactSha256 || repairIntent.semanticDigest !== structure.semanticDigest)) {
178
+ return reject('REVIEW_PROVENANCE_INVALID', 'a different review repair baseline is already recorded for this artifact', digests);
231
179
  }
232
- return failed(
233
- request,
234
- 'REVIEW_ARTIFACT_STRUCTURE_INVALID',
235
- structure.diagnostics.map((item) => `${item.code}: ${item.message}`).join('; '),
236
- resolved.taskId,
237
- null,
238
- {
239
- artifactSha256,
240
- semanticDigest: structure.semanticDigest,
241
- repairable,
242
- operation: structure.repair
243
- }
244
- );
180
+ return {
181
+ ...reject('REVIEW_ARTIFACT_STRUCTURE_INVALID', structure.diagnostics.map((item) => `${item.code}: ${item.message}`).join('; '), {
182
+ artifactSha256, semanticDigest: structure.semanticDigest, repairable, operation: structure.repair
183
+ }),
184
+ ...(repairable && !repairIntent ? { provenance: {
185
+ version: 2 as const, taskId: taskId!, family: spec.family, artifact: request.artifact,
186
+ state: 'awaiting-repair' as const, baselineSemanticDigest: structure.semanticDigest,
187
+ artifactSha256, semanticDigest: structure.semanticDigest,
188
+ recoveryOperationId: null, phase: null, authorityDigest: null,
189
+ requestId: `review-finalize:${taskId!}`, createdAt: Date.now(), updatedAt: Date.now()
190
+ }, expectedIntent: null } : {})
191
+ };
245
192
  }
246
- if (repairIntent?.state === 'awaiting-repair' && repairIntent.baselineSemanticDigest !== artifactSemanticDigest) {
247
- return failed(request, 'REVIEW_PROVENANCE_INVALID', 'the repaired review artifact semantic digest does not match the recorded repair baseline', resolved.taskId, null, {
248
- artifactSha256,
249
- semanticDigest: artifactSemanticDigest
250
- });
193
+ if (repairIntent?.state === 'awaiting-repair' && repairIntent.baselineSemanticDigest !== semanticDigest) {
194
+ return reject('REVIEW_PROVENANCE_INVALID', 'the repaired review artifact semantic digest does not match the recorded repair baseline', digests);
251
195
  }
252
196
  if ((repairIntent?.state === 'passed' || repairIntent?.state === 'consumed')
253
- && (repairIntent.artifactSha256 !== artifactSha256 || repairIntent.semanticDigest !== artifactSemanticDigest)) {
254
- return failed(request, 'REVIEW_PROVENANCE_INVALID', 'the review artifact changed after its finalization provenance was recorded', resolved.taskId, null, {
255
- artifactSha256,
256
- semanticDigest: artifactSemanticDigest
257
- });
197
+ && (repairIntent.artifactSha256 !== artifactSha256 || repairIntent.semanticDigest !== semanticDigest)) {
198
+ return reject('REVIEW_PROVENANCE_INVALID', 'the review artifact changed after its finalization provenance was recorded', digests);
258
199
  }
259
- if (!openReviewRound(taskContent, spec.action, parsedArtifact.round)) {
260
- return failed(
261
- request,
262
- 'REVIEW_ARTIFACT_IDENTITY_INVALID',
263
- `${request.artifact} does not have one matching open started review event`,
264
- resolved.taskId
265
- );
200
+ if (!hasOpenArtifactRound(taskContent, spec.family, parsed.round)) {
201
+ return reject('REVIEW_ARTIFACT_IDENTITY_INVALID', `${request.artifact} does not have one matching open started review event`);
266
202
  }
267
203
  const execution = validateLifecycleExecution(request.taskRef, {
268
204
  mode: request.orchestrated ? 'orchestrated' : 'standalone',
269
- identity: {
270
- stage: spec.family,
271
- round: parsedArtifact.round,
272
- artifact: request.artifact,
273
- role: 'reviewer'
274
- },
205
+ identity: { stage: spec.family, round: parsed.round, artifact: request.artifact, role: 'reviewer' },
275
206
  dryRun: request.dryRun
276
207
  }, { repoRoot: options.repoRoot });
277
208
  if (!execution.ok) {
278
- return failed(
279
- request,
280
- 'REVIEW_PROVENANCE_INVALID',
281
- `${execution.error?.code ?? 'ORCHESTRATION_PROVENANCE_MISMATCH'}: ${execution.error?.message ?? 'orchestration provenance validation failed'}`,
282
- resolved.taskId
283
- );
209
+ return reject('REVIEW_PROVENANCE_INVALID', `${execution.error?.code ?? 'ORCHESTRATION_PROVENANCE_MISMATCH'}: ${execution.error?.message ?? 'orchestration provenance validation failed'}`);
284
210
  }
285
-
286
- let rows;
287
211
  try {
288
212
  const ledger = parseLedgerDocument(taskContent);
289
- if (!ledger.present) {
290
- return failed(request, 'REVIEW_LEDGER_INVALID', `${LEDGER_SECTION_MISSING_CODE}: ${LEDGER_SECTION_MISSING_MESSAGE}`, resolved.taskId);
213
+ if (!ledger.present) return reject('REVIEW_LEDGER_INVALID', `${LEDGER_SECTION_MISSING_CODE}: ${LEDGER_SECTION_MISSING_MESSAGE}`);
214
+ const error = validateLedgerRows(ledger.rows);
215
+ if (error) return reject('REVIEW_LEDGER_INVALID', `${error.code}: ${error.message}`);
216
+ stageStatus = summarizeLedgerStage(ledger.rows, stage);
217
+ } catch (error) { return reject('REVIEW_LEDGER_INVALID', String(error)); }
218
+ const detail = inspectDecisionDetailDuplicates(artifactContent);
219
+ if (!detail.ok) return reject('REVIEW_DECISION_DETAIL_INVALID', `${detail.code}: ${detail.message}`);
220
+ const transformed = finalizeReviewSummaryContent(artifactContent, stageStatus.unresolvedFindingCounts);
221
+ if (!transformed.ok) return reject(transformed.code, transformed.message);
222
+ const finalDigests = { artifactSha256: sha256Content(transformed.content), semanticDigest: canonicalSemanticDigest(transformed.content) };
223
+ return {
224
+ content: transformed.content,
225
+ result: {
226
+ ...failed(request, 'REVIEW_ARTIFACT_CONFLICT', '', taskId, stageStatus, finalDigests),
227
+ status: transformed.changed ? request.dryRun ? 'planned' : 'applied' : 'no-op',
228
+ changed: transformed.changed,
229
+ operations: transformed.changed ? [{ kind: 'artifact', artifact: request.artifact, operation: 'update' }] : [],
230
+ error: null
231
+ },
232
+ ...(repairIntent?.state === 'awaiting-repair' ? {
233
+ provenance: { ...repairIntent, state: 'passed' as const, ...finalDigests },
234
+ expectedIntent: repairIntent
235
+ } : {})
236
+ };
237
+ }
238
+
239
+ function commitReviewSummaryProvenance(
240
+ prepared: ReviewSummaryCandidatePreparation,
241
+ repoRoot: string
242
+ ): ReviewFinalizationResult {
243
+ try {
244
+ if (prepared.provenance) {
245
+ writeArtifactRepairIntent(repoRoot, prepared.provenance, { expected: prepared.expectedIntent ?? null });
291
246
  }
292
- rows = ledger.rows;
247
+ return prepared.result;
293
248
  } catch (error) {
294
- return failed(request, 'REVIEW_LEDGER_INVALID', String(error), resolved.taskId);
295
- }
296
- const ledgerError = validateLedgerRows(rows);
297
- if (ledgerError) {
298
- return failed(
299
- request,
300
- 'REVIEW_LEDGER_INVALID',
301
- `${ledgerError.code}: ${ledgerError.message}`,
302
- resolved.taskId
303
- );
304
- }
305
- const stageStatus = summarizeLedgerStage(rows, stage);
306
- const detailInspection = inspectDecisionDetailDuplicates(artifactContent);
307
- if (!detailInspection.ok) {
308
- return failed(
309
- request,
310
- 'REVIEW_DECISION_DETAIL_INVALID',
311
- `${detailInspection.code}: ${detailInspection.message}`,
312
- resolved.taskId,
313
- stageStatus
314
- );
315
- }
316
- const transformed = finalizeReviewSummaryContent(
317
- artifactContent,
318
- stageStatus.unresolvedFindingCounts
319
- );
320
- if (!transformed.ok) {
321
- return failed(request, transformed.code, transformed.message, resolved.taskId, stageStatus);
322
- }
323
- if (!transformed.changed) {
324
- if (repairIntent?.state === 'awaiting-repair') {
325
- try {
326
- writeArtifactRepairIntent(resolved.repoRoot, {
327
- ...repairIntent,
328
- state: 'passed',
329
- artifactSha256,
330
- semanticDigest: artifactSemanticDigest
331
- });
332
- } catch (error) {
333
- return failed(request, 'REVIEW_PROVENANCE_INVALID', `cannot record repaired review provenance: ${String(error)}`, resolved.taskId, stageStatus, {
334
- artifactSha256,
335
- semanticDigest: artifactSemanticDigest
336
- });
337
- }
338
- }
339
- return {
340
- ...failed(request, 'REVIEW_ARTIFACT_CONFLICT', '', resolved.taskId, stageStatus, {
341
- artifactSha256,
342
- semanticDigest: artifactSemanticDigest
343
- }),
344
- status: 'no-op',
345
- error: null
346
- };
347
- }
348
- const operations = [{ kind: 'artifact' as const, artifact: request.artifact, operation: 'update' as const }];
349
- if (request.dryRun) {
350
- return {
351
- ...failed(request, 'REVIEW_ARTIFACT_CONFLICT', '', resolved.taskId, stageStatus, {
352
- artifactSha256: sha256Content(transformed.content),
353
- semanticDigest: canonicalSemanticDigest(transformed.content)
354
- }),
355
- status: 'planned',
356
- changed: true,
357
- operations,
358
- error: null
359
- };
249
+ return { ...prepared.result, status: 'failed', error: {
250
+ code: 'REVIEW_PROVENANCE_INVALID', message: `cannot record review provenance: ${String(error)}`
251
+ } };
360
252
  }
253
+ }
361
254
 
362
- const tempPath = path.join(
363
- resolved.taskDir,
364
- `.${request.artifact}.tmp-${process.pid}-${(options.randomSuffix ?? randomUUID)()}`
365
- );
366
- let mode: number;
255
+ function finalizeReviewSummaryUnlocked(
256
+ request: ReviewFinalizationRequest,
257
+ options: ReviewFinalizationOptions = {}
258
+ ): ReviewFinalizationResult {
259
+ const spec = STAGES[request.stage as ReviewStage];
260
+ if (!spec || !request.taskRef || !request.artifact) return prepareReviewSummaryCandidate(request, '', options).result;
261
+ const resolved = resolveTaskRef(request.taskRef, { repoRoot: options.repoRoot });
262
+ if (!resolved.ok) return failed(request, resolved.code, resolved.message, resolved.taskId);
263
+ const validated = validateCompletedArtifact(resolved.taskDir, spec.family, request.artifact);
264
+ if (!validated.ok) {
265
+ return failed(request, validated.error.code === 'ARTIFACT_NOT_REGULAR' ? 'REVIEW_ARTIFACT_NOT_REGULAR' : 'REVIEW_ARTIFACT_IDENTITY_INVALID', validated.error.message, resolved.taskId);
266
+ }
267
+ const fileSystem = { ...DEFAULT_FILE_SYSTEM, ...options.fileSystem };
268
+ let content: string;
269
+ try { content = fileSystem.readFileSync(validated.artifact.path); }
270
+ catch (error) { return failed(request, 'REVIEW_ARTIFACT_NOT_REGULAR', String(error), resolved.taskId); }
271
+ const prepared = prepareReviewSummaryCandidate(request, content, options);
272
+ if (request.dryRun) return prepared.result;
273
+ if (prepared.result.status !== 'applied') return commitReviewSummaryProvenance(prepared, resolved.repoRoot);
274
+ const tempPath = path.join(resolved.taskDir, `.${request.artifact}.tmp-${process.pid}-${(options.randomSuffix ?? randomUUID)()}`);
367
275
  try {
368
- mode = fileSystem.statModeSync(validated.artifact.path);
369
- fileSystem.writeFileSync(tempPath, transformed.content, mode);
276
+ fileSystem.writeFileSync(tempPath, prepared.content, fileSystem.statModeSync(validated.artifact.path));
370
277
  } catch (error) {
371
278
  cleanupTemp(fileSystem, tempPath);
372
- return failed(request, 'REVIEW_TEMP_WRITE_FAILED', String(error), resolved.taskId, stageStatus);
279
+ return failed(request, 'REVIEW_TEMP_WRITE_FAILED', String(error), resolved.taskId, prepared.result.stageStatus);
373
280
  }
374
281
  try {
375
- if (fileSystem.readFileSync(validated.artifact.path) !== artifactContent) {
282
+ if (fileSystem.readFileSync(validated.artifact.path) !== content) {
376
283
  cleanupTemp(fileSystem, tempPath);
377
- return failed(
378
- request,
379
- 'REVIEW_ARTIFACT_CONFLICT',
380
- `${request.artifact} changed during finalization`,
381
- resolved.taskId,
382
- stageStatus
383
- );
284
+ return failed(request, 'REVIEW_ARTIFACT_CONFLICT', `${request.artifact} changed during finalization`, resolved.taskId, prepared.result.stageStatus);
384
285
  }
385
286
  fileSystem.renameSync(tempPath, validated.artifact.path);
386
287
  } catch (error) {
387
288
  cleanupTemp(fileSystem, tempPath);
388
- return failed(request, 'REVIEW_RENAME_FAILED', String(error), resolved.taskId, stageStatus);
389
- }
390
- if (repairIntent?.state === 'awaiting-repair') {
391
- try {
392
- writeArtifactRepairIntent(resolved.repoRoot, {
393
- ...repairIntent,
394
- state: 'passed',
395
- artifactSha256: sha256Content(transformed.content),
396
- semanticDigest: canonicalSemanticDigest(transformed.content)
397
- });
398
- } catch (error) {
399
- return failed(request, 'REVIEW_PROVENANCE_INVALID', `cannot record repaired review provenance: ${String(error)}`, resolved.taskId, stageStatus, {
400
- artifactSha256: sha256Content(transformed.content),
401
- semanticDigest: canonicalSemanticDigest(transformed.content)
402
- });
403
- }
289
+ return failed(request, 'REVIEW_RENAME_FAILED', String(error), resolved.taskId, prepared.result.stageStatus);
404
290
  }
405
- return {
406
- status: 'applied',
407
- changed: true,
408
- intent: 'finalize-summary',
409
- requestRef: request.taskRef,
410
- taskId: resolved.taskId,
411
- stage,
412
- artifact: request.artifact,
413
- stageStatus,
414
- artifactSha256: sha256Content(transformed.content),
415
- semanticDigest: canonicalSemanticDigest(transformed.content),
416
- repairable: false,
417
- operation: null,
418
- operations,
419
- error: null
420
- };
291
+ return commitReviewSummaryProvenance(prepared, resolved.repoRoot);
421
292
  }
422
293
 
423
294
  function finalizeReviewSummary(
@@ -445,11 +316,12 @@ function finalizeReviewSummary(
445
316
  }
446
317
  }
447
318
 
448
- export { finalizeReviewSummary };
319
+ export { finalizeReviewSummary, prepareReviewSummaryCandidate, commitReviewSummaryProvenance };
449
320
  export type {
450
321
  ReviewFinalizationError,
451
322
  ReviewFinalizationErrorCode,
452
323
  ReviewFinalizationOptions,
453
324
  ReviewFinalizationRequest,
454
- ReviewFinalizationResult
325
+ ReviewFinalizationResult,
326
+ ReviewSummaryCandidatePreparation
455
327
  };
@@ -1,7 +1,7 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
3
 
4
- import { artifactName, maxRound } from "./review-artifacts.ts";
4
+ import { artifactName, maxArtifactRound } from "./artifact-name.ts";
5
5
  import { invalidationBlocks, isArtifactInvalidated, parseInvalidationDocument } from "./invalidation.ts";
6
6
 
7
7
  type ReviewedGitTree = string & { readonly __reviewedGitTree: unique symbol };
@@ -45,7 +45,7 @@ export function resolvePostReviewGlobs(
45
45
 
46
46
  export function findAuthoritativeReviewCodeArtifact(taskDir: string) {
47
47
  const entries = fs.existsSync(taskDir) ? fs.readdirSync(taskDir) : [];
48
- const round = maxRound(entries, "review-code");
48
+ const round = maxArtifactRound(entries, "review-code");
49
49
  if (round === 0) {
50
50
  return { ok: false, round: 0, fileName: null, path: null, error: null };
51
51
  }