@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
@@ -1,29 +1,47 @@
1
1
  import fs from 'node:fs';
2
- import { inspectDecisionDetailDuplicates, scanVisibleMarkdown } from './decision-details.js';
3
- import { parseArtifactName, validateCompletedArtifact } from './artifact-lifecycle.js';
2
+ import { inspectDecisionDetailDuplicates } from './decision-details.js';
3
+ import { scanVisibleMarkdown } from './markdown.js';
4
+ import { parseArtifactName } from './artifact-name.js';
5
+ import { hasOpenArtifactRound, validateArtifactPublication, validateCompletedArtifact } from './artifact-lifecycle.js';
4
6
  import { resolveTaskRef } from './resolve-ref.js';
5
7
  import { expectedQualificationRelations, validateQualificationAudit } from './qualification-audit.js';
6
8
  import { canonicalSemanticDigest, inspectArtifactPatterns, inspectArtifactStructure, sha256Content } from './artifact-operations.js';
7
9
  import { getArtifactSchema } from './artifact-schema.js';
8
10
  import { readArtifactRepairIntent, writeArtifactRepairIntent } from './artifact-repair-intent.js';
11
+ import { consumeLifecycleRecoveryAttestation, lifecycleRecoveryAttestationDigest, validateLifecycleRecoveryAttestation } from './control-authority.js';
9
12
  const LOCAL_ARTIFACT_REQUIRED_SECTIONS = {
10
13
  analysis: getArtifactSchema('analysis').sections.map((section) => section.headings.zh),
11
14
  plan: getArtifactSchema('plan').sections.map((section) => section.headings.zh),
12
15
  code: getArtifactSchema('code').sections.map((section) => section.headings.zh)
13
16
  };
14
- function readLocalArtifactFinalizationIntent(repoRoot, taskId, family, artifact) {
15
- return readArtifactRepairIntent(repoRoot, taskId, family, artifact);
17
+ function authorityDigest(authority) {
18
+ return lifecycleRecoveryAttestationDigest(authority);
16
19
  }
17
- function writeLocalArtifactFinalizationIntent(repoRoot, value) {
18
- writeArtifactRepairIntent(repoRoot, value);
20
+ function validFinalizerAuthority(authority, request, taskId) {
21
+ if (!authority)
22
+ return false;
23
+ try {
24
+ validateLifecycleRecoveryAttestation(authority);
25
+ }
26
+ catch {
27
+ return false;
28
+ }
29
+ return authority.version === 1
30
+ && authority.phase === 'artifact.finalize-local'
31
+ && authority.taskId === taskId
32
+ && authority.family === request.family
33
+ && authority.artifact === request.artifact
34
+ && authority.round === (parseArtifactName(request.artifact)?.round ?? 0)
35
+ && authority.requestId.length > 0
36
+ && authority.operationId.length > 0;
19
37
  }
20
38
  function consumeLocalArtifactFinalizationIntent(repoRoot, intent) {
21
39
  if (intent.state === 'consumed')
22
40
  return intent;
23
41
  if (intent.state !== 'passed')
24
42
  throw new Error('LOCAL_FINALIZATION_INTENT_INVALID: only passed provenance can be consumed');
25
- const consumed = { ...intent, state: 'consumed' };
26
- writeLocalArtifactFinalizationIntent(repoRoot, consumed);
43
+ const consumed = { ...intent, state: 'consumed', updatedAt: Date.now() };
44
+ writeArtifactRepairIntent(repoRoot, consumed, { expected: intent });
27
45
  return consumed;
28
46
  }
29
47
  function localDiagnosticCode(code) {
@@ -120,180 +138,137 @@ function failedFinalization(request, error, extra = {}) {
120
138
  repairable: false, diagnostics: [], error, ...extra
121
139
  };
122
140
  }
123
- function provenanceFailure(request, resolved, content, artifactSha256, semanticDigestValue, code, message) {
124
- return failedFinalization(request, { code, message }, {
125
- taskId: resolved.taskId,
126
- taskDir: resolved.taskDir,
127
- artifactSha256,
128
- semanticDigest: semanticDigestValue,
129
- diagnostics: [{ code, message, repairable: false, line: null }]
141
+ /** Read and validate once; callers publish this content before committing provenance. */
142
+ function prepareLocalArtifact(request, candidate, authority) {
143
+ const failed = (code, message) => ({
144
+ result: failedFinalization(request, { code, message }), content: candidate ?? ''
130
145
  });
131
- }
132
- function finalizeLocalArtifact(request) {
133
146
  const resolved = resolveTaskRef(request.taskRef, { repoRoot: request.repoRoot });
134
147
  if (!resolved.ok)
135
- return failedFinalization(request, { code: resolved.code, message: resolved.message });
148
+ return failed(resolved.code, resolved.message);
136
149
  const parsed = parseArtifactName(request.artifact);
137
- if (!parsed || parsed.family !== request.family) {
138
- return failedFinalization(request, {
139
- code: 'ARTIFACT_IDENTITY_INVALID',
140
- message: `artifact '${request.artifact}' does not match ${request.family}`
141
- }, { taskId: resolved.taskId, taskDir: resolved.taskDir });
142
- }
143
- const validated = validateCompletedArtifact(resolved.taskDir, request.family, request.artifact, parsed.round);
144
- if (!validated.ok) {
145
- return failedFinalization(request, validated.error, { taskId: resolved.taskId, taskDir: resolved.taskDir });
146
- }
150
+ if (!parsed || parsed.family !== request.family)
151
+ return failed('ARTIFACT_IDENTITY_INVALID', `artifact '${request.artifact}' does not match ${request.family}`);
147
152
  let content;
148
- try {
149
- content = fs.readFileSync(validated.artifact.path, 'utf8');
150
- }
151
- catch (error) {
152
- return failedFinalization(request, { code: 'ARTIFACT_NOT_READABLE', message: String(error) }, {
153
- taskId: resolved.taskId, taskDir: resolved.taskDir
154
- });
155
- }
156
153
  let taskContent;
157
154
  try {
158
155
  taskContent = fs.readFileSync(resolved.taskMdPath, 'utf8');
156
+ if (candidate !== undefined) {
157
+ const error = validateArtifactPublication(resolved.taskDir, request.family, request.artifact);
158
+ if (error)
159
+ return failed(error.code, error.message);
160
+ if (resolved.state !== 'active' || !hasOpenArtifactRound(taskContent, request.family, parsed.round)) {
161
+ return failed('ARTIFACT_IDENTITY_INVALID', 'candidate must match one open started round in an active task');
162
+ }
163
+ content = candidate;
164
+ }
165
+ else {
166
+ const validated = validateCompletedArtifact(resolved.taskDir, request.family, request.artifact, parsed.round);
167
+ if (!validated.ok)
168
+ return failed(validated.error.code, validated.error.message);
169
+ content = fs.readFileSync(validated.artifact.path, 'utf8');
170
+ }
159
171
  }
160
172
  catch (error) {
161
- return failedFinalization(request, { code: 'TASK_READ_FAILED', message: error instanceof Error ? error.message : String(error) }, {
162
- taskId: resolved.taskId, taskDir: resolved.taskDir
163
- });
173
+ return failed('ARTIFACT_NOT_READABLE', String(error));
164
174
  }
165
- const result = validateLocalArtifact(content, {
166
- family: request.family,
167
- requiredSections: request.requiredSections,
168
- taskContent,
169
- artifact: request.artifact
175
+ const validation = validateLocalArtifact(content, {
176
+ family: request.family, requiredSections: request.requiredSections, taskContent, artifact: request.artifact
170
177
  });
171
178
  const artifactSha256 = sha256Content(content);
179
+ const { semanticDigest, repairable, diagnostics } = validation;
180
+ const result = failedFinalization(request, {
181
+ code: 'LOCAL_ARTIFACT_INVALID', message: diagnostics.map((item) => `${item.code}: ${item.message}`).join('; ')
182
+ }, { taskId: resolved.taskId, taskDir: resolved.taskDir, artifactSha256, semanticDigest, repairable, diagnostics });
183
+ const preparedBase = { result, content, repoRoot: resolved.repoRoot };
184
+ if (authority !== undefined && !validFinalizerAuthority(authority, request, resolved.taskId)) {
185
+ return {
186
+ ...preparedBase,
187
+ result: { ...result, repairable: false, error: {
188
+ code: 'LOCAL_EXECUTION_AUTHORITY_INVALID',
189
+ message: 'finalizer authority does not match the artifact tuple'
190
+ }, diagnostics: [] }
191
+ };
192
+ }
193
+ const reject = (code, message) => ({
194
+ ...prepared, result: { ...result, repairable: false, error: { code, message },
195
+ diagnostics: [{ code, message, repairable: false, line: null }] }
196
+ });
172
197
  let intent;
173
198
  try {
174
- intent = readLocalArtifactFinalizationIntent(resolved.repoRoot, resolved.taskId, request.family, request.artifact);
199
+ intent = readArtifactRepairIntent(resolved.repoRoot, resolved.taskId, request.family, request.artifact);
175
200
  }
176
201
  catch (error) {
177
- return failedFinalization(request, {
178
- code: 'LOCAL_FINALIZATION_INTENT_INVALID',
179
- message: error instanceof Error ? error.message : String(error)
180
- }, { taskId: resolved.taskId, taskDir: resolved.taskDir, artifactSha256, semanticDigest: result.semanticDigest });
202
+ return { ...preparedBase, result: { ...result, repairable: false, diagnostics: [],
203
+ error: { code: 'LOCAL_FINALIZATION_INTENT_INVALID', message: String(error) } } };
181
204
  }
182
- if (result.repairable) {
183
- if (intent && (intent.state !== 'awaiting-repair' || intent.baselineSemanticDigest !== result.semanticDigest)) {
184
- return provenanceFailure(request, resolved, content, artifactSha256, result.semanticDigest, 'LOCAL_REPAIR_PROVENANCE_CONFLICT', 'a different local repair baseline is already recorded for this artifact');
185
- }
186
- if (!intent) {
187
- try {
188
- writeLocalArtifactFinalizationIntent(resolved.repoRoot, {
189
- version: 1,
190
- taskId: resolved.taskId,
191
- family: request.family,
192
- artifact: request.artifact,
193
- state: 'awaiting-repair',
194
- baselineSemanticDigest: result.semanticDigest,
195
- artifactSha256,
196
- semanticDigest: result.semanticDigest
197
- });
198
- }
199
- catch (error) {
200
- return failedFinalization(request, {
201
- code: 'LOCAL_FINALIZATION_INTENT_WRITE_FAILED',
202
- message: error instanceof Error ? error.message : String(error)
203
- }, { taskId: resolved.taskId, taskDir: resolved.taskDir, artifactSha256, semanticDigest: result.semanticDigest });
204
- }
205
- }
206
- return {
207
- status: 'failed',
208
- changed: false,
209
- taskId: resolved.taskId,
210
- taskDir: resolved.taskDir,
211
- family: request.family,
212
- artifact: request.artifact,
213
- artifactSha256,
214
- semanticDigest: result.semanticDigest,
215
- repairable: true,
216
- diagnostics: result.diagnostics,
217
- error: {
218
- code: 'LOCAL_ARTIFACT_INVALID',
219
- message: result.diagnostics.map((item) => `${item.code}: ${item.message}`).join('; ')
220
- }
221
- };
222
- }
223
- if (!result.ok) {
224
- return {
225
- status: 'failed',
226
- changed: false,
227
- taskId: resolved.taskId,
228
- taskDir: resolved.taskDir,
229
- family: request.family,
230
- artifact: request.artifact,
231
- artifactSha256,
232
- semanticDigest: result.semanticDigest,
233
- repairable: false,
234
- diagnostics: result.diagnostics,
235
- error: {
236
- code: 'LOCAL_ARTIFACT_INVALID',
237
- message: result.diagnostics.map((item) => `${item.code}: ${item.message}`).join('; ')
238
- }
239
- };
240
- }
241
- if (intent?.state === 'awaiting-repair' && intent.baselineSemanticDigest !== result.semanticDigest) {
242
- return provenanceFailure(request, resolved, content, artifactSha256, result.semanticDigest, 'LOCAL_REPAIR_BASELINE_MISMATCH', 'the repaired artifact semantic digest does not match the recorded repair baseline');
205
+ const prepared = { ...preparedBase, expectedIntent: intent };
206
+ if (intent && intent.recoveryOperationId !== null
207
+ && (!authority || intent.recoveryOperationId !== authority.operationId)) {
208
+ return reject('LOCAL_REPAIR_PROVENANCE_CONFLICT', 'local repair provenance is owned by another recovery operation');
243
209
  }
244
- if ((intent?.state === 'passed' || intent?.state === 'consumed')
245
- && (intent.artifactSha256 !== artifactSha256 || intent.semanticDigest !== result.semanticDigest)) {
246
- return provenanceFailure(request, resolved, content, artifactSha256, result.semanticDigest, 'LOCAL_REPAIR_PROVENANCE_CONFLICT', 'the artifact changed after its finalization provenance was recorded');
210
+ if (repairable) {
211
+ if (intent && (intent.state !== 'awaiting-repair' || intent.baselineSemanticDigest !== semanticDigest)) {
212
+ return reject('LOCAL_REPAIR_PROVENANCE_CONFLICT', 'a different local repair baseline is already recorded for this artifact');
213
+ }
214
+ if (intent && !authority)
215
+ return prepared;
247
216
  }
248
- if (intent?.state === 'consumed') {
249
- return {
250
- status: 'passed',
251
- changed: false,
252
- taskId: resolved.taskId,
253
- taskDir: resolved.taskDir,
254
- family: request.family,
255
- artifact: request.artifact,
256
- artifactSha256,
257
- semanticDigest: result.semanticDigest,
258
- repairable: false,
259
- diagnostics: result.diagnostics,
260
- error: null
261
- };
217
+ else {
218
+ if (!validation.ok)
219
+ return prepared;
220
+ if (intent?.state === 'awaiting-repair' && intent.baselineSemanticDigest !== semanticDigest) {
221
+ return reject('LOCAL_REPAIR_BASELINE_MISMATCH', 'the repaired artifact semantic digest does not match the recorded repair baseline');
222
+ }
223
+ if ((intent?.state === 'passed' || intent?.state === 'consumed')
224
+ && (intent.artifactSha256 !== artifactSha256 || intent.semanticDigest !== semanticDigest)) {
225
+ return reject('LOCAL_REPAIR_PROVENANCE_CONFLICT', 'the artifact changed after its finalization provenance was recorded');
226
+ }
227
+ prepared.result = { ...result, status: 'passed', error: null };
228
+ if (intent?.state === 'consumed')
229
+ return prepared;
262
230
  }
231
+ const timestamp = Date.now();
232
+ return { ...prepared, provenance: {
233
+ version: 2, taskId: resolved.taskId, family: request.family, artifact: request.artifact,
234
+ state: repairable ? 'awaiting-repair' : 'passed',
235
+ baselineSemanticDigest: repairable ? semanticDigest : intent?.baselineSemanticDigest ?? null,
236
+ artifactSha256, semanticDigest,
237
+ recoveryOperationId: authority?.operationId ?? intent?.recoveryOperationId ?? null,
238
+ phase: authority?.phase ?? intent?.phase ?? null,
239
+ authorityDigest: authority ? authorityDigest(authority) : intent?.authorityDigest ?? null,
240
+ requestId: authority?.lifecycleRequestId ?? intent?.requestId ?? `local-finalize:${resolved.taskId}`,
241
+ createdAt: intent?.createdAt ?? timestamp,
242
+ updatedAt: timestamp
243
+ } };
244
+ }
245
+ function commitLocalArtifactProvenance(prepared) {
263
246
  try {
264
- writeLocalArtifactFinalizationIntent(resolved.repoRoot, {
265
- version: 1,
266
- taskId: resolved.taskId,
267
- family: request.family,
268
- artifact: request.artifact,
269
- state: 'passed',
270
- baselineSemanticDigest: intent?.baselineSemanticDigest ?? null,
271
- artifactSha256,
272
- semanticDigest: result.semanticDigest
273
- });
247
+ if (prepared.provenance) {
248
+ writeArtifactRepairIntent(prepared.repoRoot, prepared.provenance, { expected: prepared.expectedIntent ?? null });
249
+ }
250
+ return prepared.result;
274
251
  }
275
252
  catch (error) {
276
- return failedFinalization(request, {
277
- code: 'LOCAL_FINALIZATION_INTENT_WRITE_FAILED',
278
- message: error instanceof Error ? error.message : String(error)
279
- }, { taskId: resolved.taskId, taskDir: resolved.taskDir, artifactSha256, semanticDigest: result.semanticDigest });
253
+ return { ...prepared.result, status: 'failed', error: {
254
+ code: 'LOCAL_FINALIZATION_INTENT_WRITE_FAILED', message: String(error)
255
+ } };
280
256
  }
281
- return {
282
- status: result.ok ? 'passed' : 'failed',
283
- changed: false,
284
- taskId: resolved.taskId,
285
- taskDir: resolved.taskDir,
286
- family: request.family,
287
- artifact: request.artifact,
288
- artifactSha256,
289
- semanticDigest: result.semanticDigest,
290
- repairable: result.repairable,
291
- diagnostics: result.diagnostics,
292
- error: result.ok ? null : {
293
- code: 'LOCAL_ARTIFACT_INVALID',
294
- message: result.diagnostics.map((item) => `${item.code}: ${item.message}`).join('; ')
257
+ }
258
+ function finalizeLocalArtifact(request, authority, options = {}) {
259
+ const result = commitLocalArtifactProvenance(prepareLocalArtifact(request, undefined, authority));
260
+ if (authority && !options.deferLifecycleRecoveryConsumption && result.status === 'passed') {
261
+ try {
262
+ consumeLifecycleRecoveryAttestation(authority);
295
263
  }
296
- };
264
+ catch (error) {
265
+ return { ...result, status: 'failed', error: {
266
+ code: 'LOCAL_EXECUTION_AUTHORITY_CONSUME_FAILED',
267
+ message: error instanceof Error ? error.message : String(error)
268
+ } };
269
+ }
270
+ }
271
+ return result;
297
272
  }
298
- export { LOCAL_ARTIFACT_REQUIRED_SECTIONS, consumeLocalArtifactFinalizationIntent, finalizeLocalArtifact, readLocalArtifactFinalizationIntent, canonicalSemanticDigest as semanticDigest, sha256Content, validateLocalArtifact };
273
+ export { LOCAL_ARTIFACT_REQUIRED_SECTIONS, consumeLocalArtifactFinalizationIntent, finalizeLocalArtifact, prepareLocalArtifact, commitLocalArtifactProvenance, readArtifactRepairIntent as readLocalArtifactFinalizationIntent, canonicalSemanticDigest as semanticDigest, sha256Content, validateLocalArtifact };
299
274
  //# sourceMappingURL=local-artifact-finalization.js.map
@@ -0,0 +1,26 @@
1
+ import { readManualValidationReceipt } from './manual-validation-receipt.js';
2
+ import { readManualValidationTransaction } from './manual-validation-transaction.js';
3
+ function readManualValidationCompletion(taskDir, expected = {}) {
4
+ const { receiptDigest: expectedReceiptDigest, ...receiptIdentity } = expected;
5
+ const receipt = readManualValidationReceipt(taskDir, receiptIdentity);
6
+ if (!receipt.ok)
7
+ return receipt;
8
+ if (expectedReceiptDigest !== undefined && receipt.value.receiptDigest !== expectedReceiptDigest) {
9
+ return { ok: false, error: { code: 'MANUAL_VALIDATION_RECEIPT_IDENTITY_MISMATCH', message: 'receipt digest does not match the expected completion' } };
10
+ }
11
+ const transaction = readManualValidationTransaction(taskDir, {
12
+ transactionId: receipt.value.transactionId,
13
+ taskId: receipt.value.taskId,
14
+ prNumber: receipt.value.prNumber,
15
+ prHeadSha: receipt.value.prHeadSha,
16
+ artifact: receipt.value.artifact
17
+ });
18
+ if (!transaction.ok)
19
+ return transaction;
20
+ if (transaction.value.committedReceipt !== receipt.value.receiptDigest) {
21
+ return { ok: false, error: { code: 'MANUAL_VALIDATION_TRANSACTION_PHASE_INVALID', message: 'completion transaction is not bound to the canonical receipt' } };
22
+ }
23
+ return { ok: true, value: { receipt: receipt.value, transaction: transaction.value } };
24
+ }
25
+ export { readManualValidationCompletion };
26
+ //# sourceMappingURL=manual-validation-completion.js.map
@@ -0,0 +1,130 @@
1
+ import fs from 'node:fs';
2
+ import { createHash } from 'node:crypto';
3
+ import { MANUAL_VALIDATION_SHA40 as SHA40, MANUAL_VALIDATION_TASK_ID as TASK_ID, exactKeys, isRecord, validTimestamp } from './manual-validation-shared.js';
4
+ const MANUAL_VALIDATION_EVIDENCE_SCHEMA = 'agent-infra/branch-only-validation-evidence';
5
+ const MANUAL_VALIDATION_EVIDENCE_VERSION = 1;
6
+ function invalid(message) {
7
+ return { ok: false, error: { code: 'MANUAL_VALIDATION_EVIDENCE_INVALID', message } };
8
+ }
9
+ function validateShape(value) {
10
+ if (!isRecord(value))
11
+ return invalid('evidence must be a JSON object');
12
+ const topError = exactKeys(value, ['schema', 'version', 'mode', 'taskId', 'branch', 'commit', 'recoverable', 'source', 'result'], 'evidence');
13
+ if (topError)
14
+ return invalid(topError);
15
+ if (value.schema !== MANUAL_VALIDATION_EVIDENCE_SCHEMA || value.version !== MANUAL_VALIDATION_EVIDENCE_VERSION) {
16
+ return invalid('evidence schema or version is unsupported');
17
+ }
18
+ if (value.mode !== 'task-bound' && value.mode !== 'branch-only')
19
+ return invalid('mode must be task-bound or branch-only');
20
+ if (value.taskId !== null && (typeof value.taskId !== 'string' || !TASK_ID.test(value.taskId)))
21
+ return invalid('taskId must be a TASK identifier or null');
22
+ if (typeof value.branch !== 'string' || !value.branch.trim() || /[\r\n]/u.test(value.branch))
23
+ return invalid('branch must be a non-empty single line');
24
+ if (typeof value.commit !== 'string' || !SHA40.test(value.commit))
25
+ return invalid('commit must be a 40-character lowercase hexadecimal SHA');
26
+ if (typeof value.recoverable !== 'boolean')
27
+ return invalid('recoverable must be boolean');
28
+ if (value.source !== 'run-manual-validation')
29
+ return invalid('source is unsupported');
30
+ if (value.mode === 'branch-only' && (value.taskId !== null || value.recoverable !== false))
31
+ return invalid('branch-only evidence must be non-recoverable and task-free');
32
+ if (value.mode === 'task-bound' && (value.taskId === null || value.recoverable !== true))
33
+ return invalid('task-bound evidence must identify a recoverable task');
34
+ if (!isRecord(value.result))
35
+ return invalid('result must be a JSON object');
36
+ const result = value.result;
37
+ const resultError = exactKeys(result, ['scope', 'command', 'startedAt', 'completedAt', 'exitCode', 'signal', 'cleanup'], 'result');
38
+ if (resultError)
39
+ return invalid(resultError);
40
+ if (result.scope !== 'snapshot' && result.scope !== 'inplace')
41
+ return invalid('result.scope is invalid');
42
+ if (typeof result.command !== 'string' || !result.command || /[\\/\r\n]/u.test(result.command))
43
+ return invalid('result.command must be a basename');
44
+ if (!validTimestamp(result.startedAt) || !validTimestamp(result.completedAt) || Date.parse(result.completedAt) < Date.parse(result.startedAt))
45
+ return invalid('result timestamps are invalid');
46
+ const exitCode = result.exitCode;
47
+ if (!Number.isSafeInteger(exitCode) || exitCode < 0)
48
+ return invalid('result.exitCode must be a non-negative integer');
49
+ if (result.signal !== null && (typeof result.signal !== 'string' || !result.signal || /[\r\n]/u.test(result.signal)))
50
+ return invalid('result.signal must be a string or null');
51
+ if (!['completed', 'pending', 'failed', 'not-created'].includes(result.cleanup))
52
+ return invalid('result.cleanup is invalid');
53
+ return { ok: true, value: value };
54
+ }
55
+ function createManualValidationEvidence(input) {
56
+ const value = {
57
+ schema: MANUAL_VALIDATION_EVIDENCE_SCHEMA,
58
+ version: MANUAL_VALIDATION_EVIDENCE_VERSION,
59
+ mode: input.mode,
60
+ taskId: input.taskId,
61
+ branch: input.branch,
62
+ commit: input.commit,
63
+ recoverable: input.recoverable,
64
+ source: 'run-manual-validation',
65
+ result: {
66
+ scope: input.scope,
67
+ command: input.command,
68
+ startedAt: input.startedAt,
69
+ completedAt: input.completedAt,
70
+ exitCode: input.exitCode,
71
+ signal: input.signal,
72
+ cleanup: input.cleanup
73
+ }
74
+ };
75
+ const checked = validateShape(value);
76
+ if (!checked.ok)
77
+ throw new Error(`${checked.error.code}: ${checked.error.message}`);
78
+ return checked.value;
79
+ }
80
+ function manualValidationEvidenceSummary(evidence) {
81
+ return {
82
+ version: 1,
83
+ taskId: evidence.taskId,
84
+ branch: evidence.branch,
85
+ scope: evidence.result.scope,
86
+ commit: evidence.commit,
87
+ command: evidence.result.command,
88
+ startedAt: evidence.result.startedAt,
89
+ completedAt: evidence.result.completedAt,
90
+ exitCode: evidence.result.exitCode,
91
+ signal: evidence.result.signal,
92
+ cleanup: evidence.result.cleanup
93
+ };
94
+ }
95
+ function validateManualValidationEvidence(value, expected) {
96
+ const shape = validateShape(value);
97
+ if (!shape.ok)
98
+ return shape;
99
+ const evidence = shape.value;
100
+ if (evidence.result.exitCode !== 0 || evidence.result.signal !== null || evidence.result.cleanup !== 'completed') {
101
+ return { ok: false, error: { code: 'MANUAL_VALIDATION_EVIDENCE_UNSUCCESSFUL', message: 'validation must exit 0 without a signal and complete cleanup' } };
102
+ }
103
+ if (expected) {
104
+ if (evidence.taskId !== expected.taskId || evidence.branch !== expected.branch) {
105
+ return { ok: false, error: { code: 'MANUAL_VALIDATION_EVIDENCE_IDENTITY_MISMATCH', message: 'evidence task or branch does not match the expected identity' } };
106
+ }
107
+ if (expected.commit !== undefined && evidence.commit !== expected.commit) {
108
+ return { ok: false, error: { code: 'MANUAL_VALIDATION_EVIDENCE_STALE', message: 'evidence commit does not match the current head' } };
109
+ }
110
+ }
111
+ return shape;
112
+ }
113
+ function readManualValidationEvidence(file, expected) {
114
+ let parsed;
115
+ try {
116
+ parsed = JSON.parse(fs.readFileSync(file, 'utf8'));
117
+ }
118
+ catch (error) {
119
+ const code = error.code === 'ENOENT'
120
+ ? 'MANUAL_VALIDATION_EVIDENCE_MISSING'
121
+ : 'MANUAL_VALIDATION_EVIDENCE_INVALID';
122
+ return { ok: false, error: { code, message: error instanceof Error ? error.message : String(error) } };
123
+ }
124
+ return validateManualValidationEvidence(parsed, expected);
125
+ }
126
+ function manualValidationEvidenceDigest(evidence) {
127
+ return createHash('sha256').update(JSON.stringify(evidence), 'utf8').digest('hex');
128
+ }
129
+ export { MANUAL_VALIDATION_EVIDENCE_SCHEMA, MANUAL_VALIDATION_EVIDENCE_VERSION, createManualValidationEvidence, manualValidationEvidenceSummary, manualValidationEvidenceDigest, readManualValidationEvidence, validateManualValidationEvidence };
130
+ //# sourceMappingURL=manual-validation-evidence.js.map
@@ -0,0 +1,101 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { createHash } from 'node:crypto';
4
+ import { MANUAL_VALIDATION_ARTIFACT as ARTIFACT, MANUAL_VALIDATION_SHA40 as SHA40, MANUAL_VALIDATION_SHA64 as SHA64, MANUAL_VALIDATION_TASK_ID as TASK_ID, exactKeys, isRecord, validTimestamp, writeJsonAtomic } from './manual-validation-shared.js';
5
+ const MANUAL_VALIDATION_RECEIPT_SCHEMA = 'agent-infra/manual-validation-receipt';
6
+ const MANUAL_VALIDATION_RECEIPT_VERSION = 1;
7
+ function invalid(message) {
8
+ return { ok: false, error: { code: 'MANUAL_VALIDATION_RECEIPT_INVALID', message } };
9
+ }
10
+ function canonicalPayload(receipt) {
11
+ return {
12
+ schema: MANUAL_VALIDATION_RECEIPT_SCHEMA,
13
+ version: MANUAL_VALIDATION_RECEIPT_VERSION,
14
+ transactionId: receipt.transactionId,
15
+ taskId: receipt.taskId,
16
+ prNumber: receipt.prNumber,
17
+ prHeadSha: receipt.prHeadSha,
18
+ evidenceDigest: receipt.evidenceDigest,
19
+ artifact: receipt.artifact,
20
+ artifactSha256: receipt.artifactSha256,
21
+ pendingSummaryDigest: receipt.pendingSummaryDigest,
22
+ finalSummaryDigest: receipt.finalSummaryDigest,
23
+ committedAt: receipt.committedAt
24
+ };
25
+ }
26
+ function manualValidationReceiptDigest(receipt) {
27
+ return createHash('sha256').update(JSON.stringify(canonicalPayload(receipt)), 'utf8').digest('hex');
28
+ }
29
+ function manualValidationFinalSummaryDigest(body) {
30
+ const heading = /^###\s+✅\s+(?:Manual Validation Passed|人工验证已通过)\s*$/mu.exec(body);
31
+ const projection = heading ? body.slice(heading.index).replace(/\s+$/u, '') : body;
32
+ const preimage = projection.replace(/receipt=(?:[a-f0-9]{64}|<receipt>)/gu, 'receipt=<receipt>');
33
+ return createHash('sha256').update(preimage, 'utf8').digest('hex');
34
+ }
35
+ function manualValidationFinalSummaryProjectionMatches(body, receipt) {
36
+ const identity = `transaction=${receipt.transactionId}; receipt=${receipt.receiptDigest}; evidence=${receipt.evidenceDigest}; head=${receipt.prHeadSha}`;
37
+ return body.includes(identity) && manualValidationFinalSummaryDigest(body) === receipt.finalSummaryDigest;
38
+ }
39
+ function validateManualValidationReceipt(value, expected) {
40
+ if (!isRecord(value))
41
+ return invalid('receipt must be a JSON object');
42
+ const keys = ['schema', 'version', 'transactionId', 'taskId', 'prNumber', 'prHeadSha', 'evidenceDigest', 'artifact', 'artifactSha256', 'pendingSummaryDigest', 'finalSummaryDigest', 'committedAt', 'receiptDigest'];
43
+ const keyError = exactKeys(value, keys, 'receipt');
44
+ if (keyError)
45
+ return invalid(keyError);
46
+ if (value.schema !== MANUAL_VALIDATION_RECEIPT_SCHEMA || value.version !== MANUAL_VALIDATION_RECEIPT_VERSION)
47
+ return invalid('receipt schema or version is unsupported');
48
+ if (typeof value.transactionId !== 'string' || !/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u.test(value.transactionId))
49
+ return invalid('transactionId is invalid');
50
+ if (typeof value.taskId !== 'string' || !TASK_ID.test(value.taskId))
51
+ return invalid('taskId is invalid');
52
+ const prNumber = value.prNumber;
53
+ if (!Number.isSafeInteger(prNumber) || prNumber <= 0)
54
+ return invalid('prNumber is invalid');
55
+ for (const [name, pattern] of [['prHeadSha', SHA40], ['evidenceDigest', SHA64], ['artifactSha256', SHA64], ['pendingSummaryDigest', SHA64], ['finalSummaryDigest', SHA64], ['receiptDigest', SHA64]]) {
56
+ if (typeof value[name] !== 'string' || !pattern.test(value[name]))
57
+ return invalid(`${name} is invalid`);
58
+ }
59
+ if (typeof value.artifact !== 'string' || !ARTIFACT.test(value.artifact))
60
+ return invalid('artifact is not a canonical manual-validation artifact');
61
+ if (!validTimestamp(value.committedAt))
62
+ return invalid('committedAt is invalid');
63
+ const receipt = value;
64
+ if (manualValidationReceiptDigest(receipt) !== receipt.receiptDigest)
65
+ return invalid('receiptDigest does not match receipt contents');
66
+ if (expected && Object.entries(expected).some(([key, expectedValue]) => expectedValue !== undefined && receipt[key] !== expectedValue)) {
67
+ return { ok: false, error: { code: 'MANUAL_VALIDATION_RECEIPT_IDENTITY_MISMATCH', message: 'receipt identity does not match the expected transaction' } };
68
+ }
69
+ return { ok: true, value: receipt };
70
+ }
71
+ function createManualValidationReceipt(input) {
72
+ const payload = canonicalPayload(input);
73
+ const receipt = { ...payload, receiptDigest: manualValidationReceiptDigest(input) };
74
+ const checked = validateManualValidationReceipt(receipt);
75
+ if (!checked.ok)
76
+ throw new Error(`${checked.error.code}: ${checked.error.message}`);
77
+ return checked.value;
78
+ }
79
+ function manualValidationReceiptPath(taskDir) {
80
+ return path.join(taskDir, '.manual-validation', 'receipt.json');
81
+ }
82
+ function writeManualValidationReceiptAtomic(taskDir, receipt) {
83
+ const checked = validateManualValidationReceipt(receipt);
84
+ if (!checked.ok)
85
+ throw new Error(`${checked.error.code}: ${checked.error.message}`);
86
+ const target = manualValidationReceiptPath(taskDir);
87
+ return writeJsonAtomic(target, receipt);
88
+ }
89
+ function readManualValidationReceipt(taskDir, expected) {
90
+ const file = manualValidationReceiptPath(taskDir);
91
+ let value;
92
+ try {
93
+ value = JSON.parse(fs.readFileSync(file, 'utf8'));
94
+ }
95
+ catch (error) {
96
+ return { ok: false, error: { code: error.code === 'ENOENT' ? 'MANUAL_VALIDATION_RECEIPT_MISSING' : 'MANUAL_VALIDATION_RECEIPT_INVALID', message: error instanceof Error ? error.message : String(error) } };
97
+ }
98
+ return validateManualValidationReceipt(value, expected);
99
+ }
100
+ export { MANUAL_VALIDATION_RECEIPT_SCHEMA, MANUAL_VALIDATION_RECEIPT_VERSION, createManualValidationReceipt, manualValidationFinalSummaryDigest, manualValidationFinalSummaryProjectionMatches, manualValidationReceiptDigest, manualValidationReceiptPath, readManualValidationReceipt, validateManualValidationReceipt, writeManualValidationReceiptAtomic };
101
+ //# sourceMappingURL=manual-validation-receipt.js.map
@@ -0,0 +1,40 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { randomUUID } from 'node:crypto';
4
+ const MANUAL_VALIDATION_TASK_ID = /^TASK-\d{8}-\d{6}$/u;
5
+ const MANUAL_VALIDATION_SHA40 = /^[a-f0-9]{40}$/u;
6
+ const MANUAL_VALIDATION_SHA64 = /^[a-f0-9]{64}$/u;
7
+ const MANUAL_VALIDATION_ARTIFACT = /^manual-validation(?:-r[2-9]|-r[1-9]\d+)?.md$/u;
8
+ const MANUAL_VALIDATION_TIMESTAMP = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/u;
9
+ function isRecord(value) {
10
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
11
+ }
12
+ function exactKeys(value, keys, label) {
13
+ const allowed = new Set(keys);
14
+ const unknown = Object.keys(value).find((key) => !allowed.has(key));
15
+ if (unknown)
16
+ return `${label} contains unknown field '${unknown}'`;
17
+ const missing = keys.find((key) => !Object.hasOwn(value, key));
18
+ return missing ? `${label} is missing '${missing}'` : null;
19
+ }
20
+ function validTimestamp(value) {
21
+ return typeof value === 'string' && MANUAL_VALIDATION_TIMESTAMP.test(value) && Number.isFinite(Date.parse(value));
22
+ }
23
+ function writeJsonAtomic(file, value) {
24
+ const temporary = `${file}.${process.pid}.${randomUUID()}.tmp`;
25
+ fs.mkdirSync(path.dirname(file), { recursive: true });
26
+ try {
27
+ fs.writeFileSync(temporary, `${JSON.stringify(value)}\n`, { mode: 0o600 });
28
+ fs.renameSync(temporary, file);
29
+ }
30
+ catch (error) {
31
+ try {
32
+ fs.unlinkSync(temporary);
33
+ }
34
+ catch { /* best effort cleanup of our own temp file */ }
35
+ throw error;
36
+ }
37
+ return file;
38
+ }
39
+ export { MANUAL_VALIDATION_ARTIFACT, MANUAL_VALIDATION_SHA40, MANUAL_VALIDATION_SHA64, MANUAL_VALIDATION_TASK_ID, MANUAL_VALIDATION_TIMESTAMP, exactKeys, isRecord, validTimestamp, writeJsonAtomic };
40
+ //# sourceMappingURL=manual-validation-shared.js.map