@citeark/agent 0.3.20

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 (347) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +128 -0
  3. package/data/dataset-source-registry.v1.json +300 -0
  4. package/dist/arkgraph/boot.js +6 -0
  5. package/dist/arkgraph/index.html +1 -0
  6. package/dist/arkgraph/viewer.css +1 -0
  7. package/dist/arkgraph/viewer.en.css +1 -0
  8. package/dist/arkgraph/viewer.en.js +49 -0
  9. package/dist/arkgraph/viewer.en.js.LEGAL.txt +56 -0
  10. package/dist/arkgraph/viewer.js +49 -0
  11. package/dist/arkgraph/viewer.js.LEGAL.txt +56 -0
  12. package/docker/claude-code/Dockerfile +97 -0
  13. package/docker/claude-code/codex-pro-relay.mjs +466 -0
  14. package/docker/claude-code/runtime-contract-check.mjs +79 -0
  15. package/docs/arkgraph-reading.md +79 -0
  16. package/docs/configuration.md +100 -0
  17. package/docs/integration.md +92 -0
  18. package/docs/maturity-plan.md +27 -0
  19. package/docs/npm-release.md +44 -0
  20. package/docs/paper-reading.md +40 -0
  21. package/docs/research-plan-granularity.md +27 -0
  22. package/docs/terminal.md +49 -0
  23. package/examples/toy-evaluation/compile-task.json +27 -0
  24. package/examples/toy-evaluation/paper.md +5 -0
  25. package/examples/toy-evaluation/repository/README.md +9 -0
  26. package/examples/toy-evaluation/repository/checkpoint.json +4 -0
  27. package/examples/toy-evaluation/repository/evaluate.py +17 -0
  28. package/examples/toy-evaluation/task.json +81 -0
  29. package/package.json +59 -0
  30. package/prompts/compile-research.md +58 -0
  31. package/prompts/execute-contract.md +72 -0
  32. package/prompts/execute-workspace-simple.md +51 -0
  33. package/prompts/execute-workspace.md +34 -0
  34. package/prompts/prepare-reproduction.md +82 -0
  35. package/prompts/repair-research.md +45 -0
  36. package/protocol/CAP.md +129 -0
  37. package/protocol/LICENSE +12 -0
  38. package/protocol/MAPPINGS.md +72 -0
  39. package/protocol/README.md +38 -0
  40. package/protocol/conformance-v2.0-alpha.1.json +36 -0
  41. package/protocol/examples/arkgraph/checkpoint-evaluation.json +309 -0
  42. package/protocol/examples/arkgraph/fixtures.mjs +49 -0
  43. package/protocol/examples/arkgraph/paper-free.json +291 -0
  44. package/protocol/examples/arkgraph/partial-failure.json +344 -0
  45. package/protocol/examples/arkgraph/training-evaluation.json +443 -0
  46. package/protocol/profiles/agent-trace.md +16 -0
  47. package/protocol/profiles/computational-run.md +16 -0
  48. package/protocol/profiles/core.md +15 -0
  49. package/protocol/profiles/public-bundle.md +18 -0
  50. package/protocol/profiles/reproduction.md +29 -0
  51. package/protocol/profiles/research-compilation.md +44 -0
  52. package/protocol/profiles/research-plan.md +39 -0
  53. package/protocol/profiles/restricted-evidence.md +15 -0
  54. package/runtime/bootstrap-autodl-runtime.sh +314 -0
  55. package/runtime/create-runtime-venv.sh +41 -0
  56. package/runtime/install-local-cpu-runtime.sh +23 -0
  57. package/runtime/install-scientific-runtime.sh +153 -0
  58. package/runtime/mineru/parse.py +62 -0
  59. package/runtime/mineru/requirements.txt +4 -0
  60. package/runtime/requirements-baseline.txt +38 -0
  61. package/schemas/cap/v2/activity.schema.json +47 -0
  62. package/schemas/cap/v2/agent.schema.json +32 -0
  63. package/schemas/cap/v2/assertion.schema.json +110 -0
  64. package/schemas/cap/v2/descriptor.schema.json +243 -0
  65. package/schemas/cap/v2/entity.schema.json +64 -0
  66. package/schemas/cap/v2/manifest.schema.json +67 -0
  67. package/schemas/cap/v2/relation.schema.json +82 -0
  68. package/schemas/compute-catalog.schema.json +63 -0
  69. package/schemas/compute-decision.schema.json +27 -0
  70. package/schemas/execution-contract.schema.json +1024 -0
  71. package/schemas/research-card.schema.json +30 -0
  72. package/schemas/research-inventory-draft.schema.json +366 -0
  73. package/schemas/research.schema.json +1044 -0
  74. package/schemas/result.schema.json +173 -0
  75. package/schemas/verification-policy.schema.json +47 -0
  76. package/schemas/verified-conclusion.schema.json +58 -0
  77. package/schemas/workspace-summary.schema.json +24 -0
  78. package/scripts/build-arkgraph-view.mjs +12 -0
  79. package/scripts/check-execution-feasibility.mjs +24 -0
  80. package/scripts/check-syntax.mjs +15 -0
  81. package/scripts/deterministic-asset-preparation.py +438 -0
  82. package/scripts/package-cap.mjs +23 -0
  83. package/scripts/package-local-agent.mjs +23 -0
  84. package/scripts/preview-arkgraph.mjs +25 -0
  85. package/scripts/replay-research-compiler-candidate.mjs +134 -0
  86. package/scripts/review-compiler-sources.mjs +44 -0
  87. package/scripts/run-asset-preparation.sh +17 -0
  88. package/scripts/run-research-plan.mjs +98 -0
  89. package/scripts/validate-asset-preparation.py +290 -0
  90. package/scripts/verify-local-runtime.mjs +57 -0
  91. package/scripts/verify-npm-package.mjs +57 -0
  92. package/src/adapters/paper2agent.mjs +107 -0
  93. package/src/assets/cache.mjs +159 -0
  94. package/src/assets/compute.mjs +98 -0
  95. package/src/assets/executor.mjs +145 -0
  96. package/src/assets/lifecycle.mjs +213 -0
  97. package/src/assets/manifest.mjs +242 -0
  98. package/src/assets/opportunistic-preparation.mjs +81 -0
  99. package/src/assets/plan.mjs +411 -0
  100. package/src/assets/prompts.mjs +29 -0
  101. package/src/assets/public-asset-probe.mjs +525 -0
  102. package/src/assets/qualification.mjs +119 -0
  103. package/src/assets/readiness.mjs +130 -0
  104. package/src/assets/reproduction-admission.mjs +355 -0
  105. package/src/assets/requirements.mjs +152 -0
  106. package/src/assets/source-grounding.mjs +341 -0
  107. package/src/assets/source-policy.mjs +118 -0
  108. package/src/autodl/client.mjs +260 -0
  109. package/src/autodl/ssh.mjs +380 -0
  110. package/src/autodl/tools.mjs +129 -0
  111. package/src/cap/redaction.mjs +38 -0
  112. package/src/cap/v2/archive.mjs +152 -0
  113. package/src/cap/v2/attestation.mjs +204 -0
  114. package/src/cap/v2/canonical-json.mjs +114 -0
  115. package/src/cap/v2/compilation-artifact.mjs +240 -0
  116. package/src/cap/v2/core.mjs +282 -0
  117. package/src/cap/v2/measurement-assessment-records.mjs +23 -0
  118. package/src/cap/v2/pipeline-artifact.mjs +922 -0
  119. package/src/cap/v2/read.mjs +41 -0
  120. package/src/cap/v2/reassessment-artifact.mjs +383 -0
  121. package/src/cap/v2/research-artifact.mjs +231 -0
  122. package/src/cap/v2/research-map-records.mjs +46 -0
  123. package/src/cap/v2/research-object-records.mjs +163 -0
  124. package/src/cap/v2/research-records.mjs +187 -0
  125. package/src/cap/v2/verify.mjs +642 -0
  126. package/src/cli.mjs +1146 -0
  127. package/src/compute/autodl-pro-compiler.mjs +347 -0
  128. package/src/compute/autodl-pro-executor.mjs +459 -0
  129. package/src/compute/autodl-pro-job.mjs +843 -0
  130. package/src/compute/autodl-pro-network.mjs +295 -0
  131. package/src/compute/autodl-pro-remote.mjs +810 -0
  132. package/src/compute/autodl-pro-staging.mjs +117 -0
  133. package/src/compute/campaign.mjs +110 -0
  134. package/src/compute/catalog.mjs +123 -0
  135. package/src/compute/checkpoint-protocol.mjs +154 -0
  136. package/src/compute/codex-account-lock.mjs +111 -0
  137. package/src/compute/codex-account-session.mjs +107 -0
  138. package/src/compute/compiler-profile.mjs +38 -0
  139. package/src/compute/compiler-router.mjs +23 -0
  140. package/src/compute/coordinator-recovery.mjs +210 -0
  141. package/src/compute/executor-router.mjs +29 -0
  142. package/src/compute/gcp-batch-compiler.mjs +685 -0
  143. package/src/compute/gcp-batch-executor.mjs +1215 -0
  144. package/src/compute/gcp-batch-failure.mjs +92 -0
  145. package/src/compute/gcp-batch-job.mjs +527 -0
  146. package/src/compute/gcp-batch-lifecycle.mjs +81 -0
  147. package/src/compute/gcp-checkpoint-worker.mjs +1633 -0
  148. package/src/compute/local-codex-compiler.mjs +52 -0
  149. package/src/compute/measurement-hardware.mjs +128 -0
  150. package/src/compute/remote-attempt.mjs +226 -0
  151. package/src/compute/requirements.mjs +124 -0
  152. package/src/compute/research-phases.mjs +48 -0
  153. package/src/compute/scheduler.mjs +452 -0
  154. package/src/compute/shared-workloads.mjs +26 -0
  155. package/src/compute/stage-archive.mjs +79 -0
  156. package/src/contracts/campaign-contract.mjs +52 -0
  157. package/src/contracts/execution-contract.mjs +819 -0
  158. package/src/contracts/execution-mode.mjs +19 -0
  159. package/src/contracts/execution-timeouts.mjs +45 -0
  160. package/src/contracts/execution-workload.mjs +68 -0
  161. package/src/contracts/preflight-schema.mjs +25 -0
  162. package/src/contracts/public-contract.mjs +63 -0
  163. package/src/contracts/subject-tags.mjs +31 -0
  164. package/src/dashboard/data.mjs +898 -0
  165. package/src/dashboard/server.mjs +79 -0
  166. package/src/dashboard/static/dashboard.css +366 -0
  167. package/src/dashboard/static/dashboard.js +560 -0
  168. package/src/dashboard/static/index.html +85 -0
  169. package/src/deployment/community-policy.mjs +9 -0
  170. package/src/deployment/environment.mjs +112 -0
  171. package/src/deployment/guided.mjs +98 -0
  172. package/src/deployment/handoff.mjs +102 -0
  173. package/src/deployment/local-contract.mjs +31 -0
  174. package/src/deployment/local.mjs +100 -0
  175. package/src/deployment/prepare.mjs +46 -0
  176. package/src/deployment/recipe.mjs +108 -0
  177. package/src/deployment/supplement.mjs +51 -0
  178. package/src/deployment/terminal.mjs +43 -0
  179. package/src/diagnosis/renderer.mjs +75 -0
  180. package/src/diagnosis/target-failure.mjs +46 -0
  181. package/src/evidence/parser-registry.mjs +54 -0
  182. package/src/evidence/parsers/fasttext-classification.mjs +82 -0
  183. package/src/evidence/parsers/json-scalar.mjs +96 -0
  184. package/src/evidence/parsers/simcse-senteval.mjs +104 -0
  185. package/src/evidence/parsers/starspace-classification.mjs +78 -0
  186. package/src/evidence/registry.mjs +147 -0
  187. package/src/execution/runner-audit.mjs +473 -0
  188. package/src/gcp/auth.mjs +106 -0
  189. package/src/gcp/batch-client.mjs +120 -0
  190. package/src/gcp/resource-discovery.mjs +177 -0
  191. package/src/gcp/rest.mjs +82 -0
  192. package/src/gcp/secret-manager.mjs +34 -0
  193. package/src/gcp/signed-url.mjs +133 -0
  194. package/src/gcp/storage.mjs +220 -0
  195. package/src/graph/command.mjs +41 -0
  196. package/src/graph/execution.mjs +97 -0
  197. package/src/graph/model.mjs +37 -0
  198. package/src/graph/presentation.mjs +110 -0
  199. package/src/graph/query.mjs +159 -0
  200. package/src/graph/research-relations.mjs +69 -0
  201. package/src/graph/source-page.mjs +12 -0
  202. package/src/graph/source-preview.mjs +34 -0
  203. package/src/graph/validate.mjs +76 -0
  204. package/src/job.mjs +496 -0
  205. package/src/network/autodl-routing-proxy.mjs +462 -0
  206. package/src/network/egress-proxy.mjs +158 -0
  207. package/src/observability/event-contract.mjs +230 -0
  208. package/src/observability/pipeline-monitor.mjs +166 -0
  209. package/src/pipeline/orchestrator.mjs +1281 -0
  210. package/src/pipeline/recovery-error.mjs +11 -0
  211. package/src/pipeline/replay.mjs +304 -0
  212. package/src/pipeline/shared-execution.mjs +115 -0
  213. package/src/pipeline/stage-checkpoint.mjs +86 -0
  214. package/src/pipeline/stage-recovery.mjs +101 -0
  215. package/src/pipeline/targets.mjs +110 -0
  216. package/src/process.mjs +143 -0
  217. package/src/protocol.mjs +312 -0
  218. package/src/provider/codex-account.mjs +44 -0
  219. package/src/provider/codex-completion.mjs +49 -0
  220. package/src/provider/completion.mjs +292 -0
  221. package/src/provider/model-client.mjs +44 -0
  222. package/src/provider/model-route.mjs +29 -0
  223. package/src/provider/openrouter-readiness.mjs +189 -0
  224. package/src/provider/reader-bridge.mjs +35 -0
  225. package/src/provider/relay.mjs +263 -0
  226. package/src/provider/runtime-auth.mjs +40 -0
  227. package/src/public/cap.d.mts +90 -0
  228. package/src/public/cap.mjs +12 -0
  229. package/src/public/contracts.d.mts +2 -0
  230. package/src/public/host.mjs +171 -0
  231. package/src/public/operations.d.mts +11 -0
  232. package/src/public/presentation.d.mts +4 -0
  233. package/src/records/views.mjs +26 -0
  234. package/src/remote/command.mjs +178 -0
  235. package/src/remote/ssh.mjs +59 -0
  236. package/src/repository-origin.mjs +81 -0
  237. package/src/reproduction/evidence-feedback.mjs +96 -0
  238. package/src/reproduction/incomplete-initialization.mjs +25 -0
  239. package/src/reproduction/lifecycle.mjs +253 -0
  240. package/src/reproduction/plan.mjs +132 -0
  241. package/src/reproduction/prompts.mjs +70 -0
  242. package/src/reproduction/runner.mjs +188 -0
  243. package/src/reproduction/summary.mjs +130 -0
  244. package/src/reproduction/workspace-mode.mjs +7 -0
  245. package/src/research/automatic-admission.mjs +156 -0
  246. package/src/research/compiler-coverage.mjs +85 -0
  247. package/src/research/compiler-failure.mjs +24 -0
  248. package/src/research/compiler-normalization-guards.mjs +112 -0
  249. package/src/research/compiler-repair.mjs +3 -0
  250. package/src/research/compiler.mjs +853 -0
  251. package/src/research/continuation-selection.mjs +26 -0
  252. package/src/research/execution-graph-context.mjs +43 -0
  253. package/src/research/experiment-importance.mjs +15 -0
  254. package/src/research/inventory-handoff.mjs +104 -0
  255. package/src/research/inventory-revisions.mjs +32 -0
  256. package/src/research/mineru-local.mjs +73 -0
  257. package/src/research/paper-command.mjs +19 -0
  258. package/src/research/paper-markdown.mjs +180 -0
  259. package/src/research/paper-source-map.mjs +69 -0
  260. package/src/research/planning-policy.mjs +88 -0
  261. package/src/research/reference-materials.mjs +11 -0
  262. package/src/research/reproduction-scope.mjs +30 -0
  263. package/src/research/research-map.mjs +94 -0
  264. package/src/research/research-objects.mjs +88 -0
  265. package/src/research/source-discovery.mjs +646 -0
  266. package/src/research/source-observations.mjs +75 -0
  267. package/src/research/source-review-cli-mcp.mjs +26 -0
  268. package/src/research/source-review-input.mjs +209 -0
  269. package/src/research/source-review-local-codex.mjs +36 -0
  270. package/src/research/source-review-model.mjs +70 -0
  271. package/src/research/source-review.mjs +173 -0
  272. package/src/research/structure.mjs +3163 -0
  273. package/src/research-card/renderer.mjs +277 -0
  274. package/src/research-card/verified-conclusion.mjs +143 -0
  275. package/src/results/output-registry.mjs +183 -0
  276. package/src/runtime/claude-code.mjs +52 -0
  277. package/src/runtime/codex-capacity-retry.mjs +87 -0
  278. package/src/runtime/codex.mjs +64 -0
  279. package/src/runtime/config.mjs +157 -0
  280. package/src/runtime/final-output.mjs +40 -0
  281. package/src/runtime/index.mjs +21 -0
  282. package/src/runtime/local-codex.mjs +74 -0
  283. package/src/runtime/opencode.mjs +95 -0
  284. package/src/runtime/prompt.mjs +13 -0
  285. package/src/sandbox/docker.mjs +363 -0
  286. package/src/settings/command.mjs +297 -0
  287. package/src/settings/store.mjs +119 -0
  288. package/src/telemetry/pricing.mjs +68 -0
  289. package/src/telemetry/usage.mjs +265 -0
  290. package/src/terminal/events.mjs +97 -0
  291. package/src/terminal/input.mjs +40 -0
  292. package/src/terminal/plain.mjs +40 -0
  293. package/src/terminal/remote-stream.mjs +22 -0
  294. package/src/terminal/screen.mjs +214 -0
  295. package/src/terminal/transcript.mjs +69 -0
  296. package/src/util.mjs +107 -0
  297. package/src/verification/ai-assessor.mjs +534 -0
  298. package/src/verification/claim-evaluator.mjs +242 -0
  299. package/src/verification/evidence-context.mjs +165 -0
  300. package/src/verification/evidence-reader.mjs +95 -0
  301. package/src/verification/integrity.mjs +570 -0
  302. package/src/verification/tolerance.mjs +32 -0
  303. package/src/workloads/cpu-research-preparation.mjs +56 -0
  304. package/src/workloads/definition.mjs +74 -0
  305. package/src/workloads/phase-aware-reproduction.mjs +46 -0
  306. package/src/workloads/reproduction.mjs +85 -0
  307. package/src/workspace/command.mjs +242 -0
  308. package/src/workspace/control.mjs +49 -0
  309. package/src/workspace/entry.mjs +28 -0
  310. package/src/workspace/input.mjs +93 -0
  311. package/src/workspace/interactive.mjs +94 -0
  312. package/src/workspace/jobs.mjs +418 -0
  313. package/src/workspace/session.mjs +97 -0
  314. package/src/workspace/worker.mjs +137 -0
  315. package/ui/arkgraph/ambient-motion.mjs +10 -0
  316. package/ui/arkgraph/app.jsx +153 -0
  317. package/ui/arkgraph/boot.js +6 -0
  318. package/ui/arkgraph/camera-motion.mjs +20 -0
  319. package/ui/arkgraph/context-reveal.mjs +39 -0
  320. package/ui/arkgraph/details.css +3 -0
  321. package/ui/arkgraph/entry.jsx +28 -0
  322. package/ui/arkgraph/experiment-curves.mjs +17 -0
  323. package/ui/arkgraph/experiment-selection.mjs +15 -0
  324. package/ui/arkgraph/experiment-style.css +26 -0
  325. package/ui/arkgraph/experiment-ui.jsx +32 -0
  326. package/ui/arkgraph/frame.html +1 -0
  327. package/ui/arkgraph/graph-gestures.mjs +62 -0
  328. package/ui/arkgraph/label-layout.mjs +57 -0
  329. package/ui/arkgraph/locales/en.json +229 -0
  330. package/ui/arkgraph/locales/source-types.json +15 -0
  331. package/ui/arkgraph/localization-build.mjs +27 -0
  332. package/ui/arkgraph/material-build.mjs +23 -0
  333. package/ui/arkgraph/material-colors.mjs +39 -0
  334. package/ui/arkgraph/material-style.css +15 -0
  335. package/ui/arkgraph/open-graph.jsx +326 -0
  336. package/ui/arkgraph/outline.jsx +49 -0
  337. package/ui/arkgraph/package-lock.json +888 -0
  338. package/ui/arkgraph/package.json +17 -0
  339. package/ui/arkgraph/reading-layout.mjs +130 -0
  340. package/ui/arkgraph/reading-presentation.mjs +73 -0
  341. package/ui/arkgraph/record-detail.css +51 -0
  342. package/ui/arkgraph/record-details.jsx +29 -0
  343. package/ui/arkgraph/research-types.mjs +31 -0
  344. package/ui/arkgraph/selection-mark.jsx +6 -0
  345. package/ui/arkgraph/soft-spine.mjs +26 -0
  346. package/ui/arkgraph/steering-style.css +187 -0
  347. package/ui/arkgraph/style.css +272 -0
@@ -0,0 +1,1281 @@
1
+ import { controlledExecutors, researchBoundary } from '../workspace/control.mjs';
2
+ import { mkdir, rm } from "node:fs/promises";
3
+ import { createSourceReviewCompletion } from '../research/source-review-model.mjs';
4
+ import { runtimeProviderConfiguration } from '../provider/runtime-auth.mjs';
5
+ import path from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+
8
+ import {
9
+ createAssetPreparationComputeDecision,
10
+ selectAssetPreparationProfile,
11
+ } from "../assets/compute.mjs";
12
+ import { executeGcpAssetPreparation } from "../assets/executor.mjs";
13
+ import { executeGcpBatchReproduction } from "../compute/gcp-batch-executor.mjs";
14
+ import { createCpuResearchPreparationWorkload } from "../workloads/cpu-research-preparation.mjs";
15
+ import {
16
+ assetPreparationPlanFailures,
17
+ createAssetPreparationPlan,
18
+ } from "../assets/plan.mjs";
19
+ import { createAssetReadiness } from "../assets/readiness.mjs";
20
+ import {
21
+ assetPreparationFallback,
22
+ decideOpportunisticAssetPreparation,
23
+ mustAbortAfterAssetPreparationFailure,
24
+ } from "../assets/opportunistic-preparation.mjs";
25
+ import {
26
+ ASSET_QUALIFICATION_BOUNDARY,
27
+ assertAssetQualificationAdmission,
28
+ probeAssetQualification,
29
+ } from "../assets/qualification.mjs";
30
+ import { packCap, capDirectoryContentDigest } from "../cap/v2/archive.mjs";
31
+ import { loadResearchCompilationCap } from "../cap/v2/compilation-artifact.mjs";
32
+ import { assembleResearchPlanCap } from "../cap/v2/research-artifact.mjs";
33
+ import { loadComputeCatalog, loadComputePolicy } from "../compute/catalog.mjs";
34
+ import {
35
+ executeScheduledCompilation,
36
+ listCompilerExecutors,
37
+ } from "../compute/compiler-router.mjs";
38
+ import { executeScheduledReproduction } from "../compute/executor-router.mjs";
39
+ import { scheduleCompute } from "../compute/scheduler.mjs";
40
+ import {
41
+ createExecutionContract,
42
+ loadExecutionContract,
43
+ loadVerificationPolicy,
44
+ } from "../contracts/execution-contract.mjs";
45
+ import { prepareInventoryForExecution, writePreparationTaskFromInventory } from "../research/inventory-handoff.mjs";
46
+ import { runResearchCompiler } from "../research/compiler.mjs";
47
+ import { loadResearchStructure } from "../research/structure.mjs";
48
+ import { mergeAgentUsage } from "../telemetry/usage.mjs";
49
+ import {
50
+ CiteArkError,
51
+ createRunId,
52
+ pathExists,
53
+ readJson,
54
+ sha256File,
55
+ writeJson,
56
+ } from "../util.mjs";
57
+ import { createPipelineMonitor } from "../observability/pipeline-monitor.mjs";
58
+ import { createStageCheckpoint } from "./stage-checkpoint.mjs";
59
+ import { replayRunToCap } from "./replay.mjs";
60
+ import { isCiteArkReconstructionRepository } from "../repository-origin.mjs";
61
+ import { createPipelineRecoveryEnvelope } from "./stage-recovery.mjs";
62
+ import { createScientificAssessor } from "../verification/ai-assessor.mjs";
63
+ import { usesWorkspaceSummary } from "../reproduction/workspace-mode.mjs";
64
+ import { createCampaignContract, campaignTargetDirectory } from "../contracts/campaign-contract.mjs";
65
+ import { reproductionCampaign } from "../compute/campaign.mjs";
66
+
67
+ export const PIPELINE_STAGES = Object.freeze(
68
+ [
69
+ ["research_compilation", "研究结构化"],
70
+ ["research_plan", "研究计划快照"],
71
+ ["contract_creation", "执行契约"],
72
+ ["compute_scheduling", "计算调度"],
73
+ ["asset_preparation", "资产与依赖准备"],
74
+ ["reproduction", "自主复现"],
75
+ ["evidence_parsing", "证据解析"],
76
+ ["verification", "完整性检查与 AI 科学评估"],
77
+ ["research_card", "验证结论与卡片"],
78
+ ["execution_handoff", "整理再次执行的交付说明"],
79
+ ["packaging", "产物封装"],
80
+ ["archive", "单文件封装"],
81
+ ].map((stage) => Object.freeze(stage)),
82
+ );
83
+
84
+ const PACKAGE_ROOT = path.resolve(
85
+ path.dirname(fileURLToPath(import.meta.url)),
86
+ "../..",
87
+ );
88
+
89
+ export async function executeFullPipeline({
90
+ compileTaskPath,
91
+ researchPath,
92
+ compilationArtifactPath,
93
+ sourceArtifactDigests = [],
94
+ sourceCompilationDigests: inheritedCompilationDigests = [],
95
+ referenceMaterialsPath,
96
+ paperPath,
97
+ paperReading,
98
+ claimId,
99
+ experimentId,
100
+ executionClaimIds,
101
+ executionTargets,
102
+ tolerance,
103
+ workDirectory,
104
+ signingKeyPath,
105
+ agentRuntime,
106
+ computeCatalogPath,
107
+ computePolicyPath,
108
+ resourceLimits,
109
+ computeProfile,
110
+ computeSelection,
111
+ datasetRegistryPath,
112
+ executorOptions,
113
+ processingBinding,
114
+ researchArtifactDigest,
115
+ repositoryPath,
116
+ upstreamCompilation,
117
+ upstreamAssetAdmission,
118
+ scientificAssessor,
119
+ stopAfter,
120
+ dryRun = false,
121
+ progress = () => {},
122
+ observe = () => {},
123
+ onPhaseChange = async () => {},
124
+ onCheckpoint = async () => {},
125
+ resume = false,
126
+ retryScientificExecution = false,
127
+ postProcessingOnly = false,
128
+ }) {
129
+ executorOptions = controlledExecutors(executorOptions);
130
+ const inputs = [compileTaskPath, researchPath, compilationArtifactPath].filter(Boolean);
131
+ if (inputs.length !== 1) {
132
+ throw new CiteArkError(
133
+ "pipeline execute 必须且只能提供 --compile-task、--research 或 --compilation 之一",
134
+ );
135
+ }
136
+ if (stopAfter !== undefined && !new Set([ASSET_QUALIFICATION_BOUNDARY, "research_plan"]).has(stopAfter)) {
137
+ throw new CiteArkError(`不支持的流水线停止边界:${stopAfter}`);
138
+ }
139
+ const root = path.resolve(workDirectory);
140
+ const existing = await pathExists(path.join(root, "pipeline.json"));
141
+ if (existing && !resume)
142
+ throw new CiteArkError(`流水线目录已经被使用:${root}`);
143
+ const restoredState = existing
144
+ ? await readJson(path.join(root, "pipeline.json"))
145
+ : null;
146
+ if (postProcessingOnly && !(await pathExists(path.join(root, "stage-results", "reproduction.json")))) {
147
+ throw new CiteArkError("后处理恢复缺少已保存的实验结果;保留现场,不启动新的实验", {
148
+ retryable: false, failureCategory: "publication", failureCode: "recovery.completed_result_missing",
149
+ });
150
+ }
151
+ const pipelineId =
152
+ restoredState?.pipelineId ?? createRunId().replace(/^car_/, "pipeline_");
153
+ if (
154
+ restoredState && retryScientificExecution &&
155
+ (await pathExists(path.join(root, "stage-results", "reproduction.json")))
156
+ ) {
157
+ const cached = await readJson(
158
+ path.join(root, "stage-results", "reproduction.json"),
159
+ );
160
+ // Re-running scientific work requires an explicit execution-retry decision.
161
+ // A partial/failed result is still a completed input to assessment/publication.
162
+ if (cached.value?.validation?.result?.execution?.status !== "succeeded"
163
+ && restoredState.phase === "completed") {
164
+ for (const [id] of PIPELINE_STAGES.slice(5)) {
165
+ await rm(path.join(root, "stage-results", `${id}.json`), {
166
+ force: true,
167
+ });
168
+ const stage = restoredState.stages.find((item) => item.id === id);
169
+ if (stage) stage.status = "pending";
170
+ }
171
+ const oldRunId = cached.value?.bundle?.runId;
172
+ if (oldRunId) {
173
+ const runPath = path.join(root, "runs", oldRunId, "run.json");
174
+ const run = await readJson(runPath);
175
+ delete run.recoveryRemoteResult;
176
+ run.recoveryContinueAgent = true;
177
+ await writeJson(runPath, run);
178
+ }
179
+ await rm(path.join(root, "cap"), { recursive: true, force: true });
180
+ }
181
+ }
182
+ if (restoredState && computeSelection)
183
+ await rm(path.join(root, "stage-results", "compute_scheduling.json"), {
184
+ force: true,
185
+ });
186
+
187
+ const directories = {
188
+ root,
189
+ compilations: path.join(root, "compilations"),
190
+ protocol: path.join(root, "protocol"),
191
+ private: path.join(root, "private"),
192
+ runs: path.join(root, "runs"),
193
+ researchPlanCap: path.join(root, "research-plan-cap"),
194
+ cap: path.join(root, "cap"),
195
+ };
196
+ const researchPlanArchivePath = path.join(
197
+ root,
198
+ `${pipelineId}-research-plan.cap`,
199
+ );
200
+
201
+ await Promise.all(
202
+ Object.values(directories).map((directory) =>
203
+ mkdir(directory, { recursive: true }),
204
+ ),
205
+ );
206
+ const createdAt = new Date().toISOString();
207
+ const state = restoredState ?? {
208
+ schemaVersion: "1.0",
209
+ pipelineId,
210
+ status: "running",
211
+ phase: compileTaskPath ? "research_compilation" : "research_plan",
212
+ createdAt,
213
+ updatedAt: createdAt,
214
+ stages: PIPELINE_STAGES.map(([id, label]) => {
215
+ const importedCompilation = id === "research_compilation"
216
+ && Boolean(upstreamCompilation || compilationArtifactPath);
217
+ const startedAt = importedCompilation
218
+ ? (upstreamCompilation?.startedAt ?? null)
219
+ : null;
220
+ const finishedAt = importedCompilation
221
+ ? (upstreamCompilation?.finishedAt ?? null)
222
+ : null;
223
+ return {
224
+ id,
225
+ label,
226
+ status: importedCompilation
227
+ ? "completed"
228
+ : !compileTaskPath && id === "research_compilation"
229
+ ? "skipped"
230
+ : "pending",
231
+ startedAt,
232
+ finishedAt,
233
+ durationMs:
234
+ startedAt && finishedAt
235
+ ? Math.max(0, Date.parse(finishedAt) - Date.parse(startedAt))
236
+ : null,
237
+ };
238
+ }),
239
+ };
240
+ const stageCheckpoint = createStageCheckpoint({
241
+ directory: root,
242
+ enabled: !dryRun,
243
+ identity: {
244
+ sourceDigest: processingBinding?.sourceDigest ?? null,
245
+ inputDigest: processingBinding
246
+ ? null
247
+ : await sha256File(compileTaskPath ?? researchPath ?? compilationArtifactPath),
248
+ tolerance: tolerance ?? null,
249
+ researchArtifactDigest: researchArtifactDigest ?? null,
250
+ claimId: claimId ?? null,
251
+ experimentId: experimentId ?? null,
252
+ executionClaimIds: Array.isArray(executionClaimIds)
253
+ ? [...executionClaimIds].sort()
254
+ : null,
255
+ executionTargets: executionTargets ?? null,
256
+ sourceArtifactDigests,
257
+ referenceMaterialsDigest: referenceMaterialsPath ? await sha256File(referenceMaterialsPath) : null,
258
+ },
259
+ persist: dryRun ? async () => {} : onCheckpoint,
260
+ progress,
261
+ });
262
+ const monitor = createPipelineMonitor({
263
+ directory: root,
264
+ pipelineId,
265
+ correlation: {
266
+ jobId: processingBinding?.jobId,
267
+ repositoryId: processingBinding?.repositoryId,
268
+ sourceDigest: processingBinding?.sourceDigest,
269
+ claimId,
270
+ experimentId,
271
+ },
272
+ onEvent: observe,
273
+ });
274
+ await monitor.start({ stage: state.phase });
275
+ const reportProgress = (message, details) => {
276
+ progress(message, details);
277
+ monitor.record(message, { stage: state.phase });
278
+ };
279
+ const save = async (patch) => {
280
+ Object.assign(state, patch, { updatedAt: new Date().toISOString() });
281
+ await writeJson(path.join(root, "pipeline.json"), state);
282
+ };
283
+ const updateStage = async (id, status, details = {}) => {
284
+ if (status === "running") await researchBoundary(id);
285
+ const stage = state.stages.find((item) => item.id === id);
286
+ if (!stage) throw new CiteArkError(`未知流水线阶段:${id}`);
287
+ const now = new Date().toISOString();
288
+ if (status === "running") {
289
+ stage.startedAt ??= now;
290
+ stage.finishedAt = null;
291
+ stage.durationMs = null;
292
+ } else if (
293
+ status === "completed" ||
294
+ status === "failed" ||
295
+ status === "deferred" ||
296
+ status === "skipped"
297
+ ) {
298
+ stage.startedAt ??= now;
299
+ stage.finishedAt = now;
300
+ stage.durationMs = Math.max(
301
+ 0,
302
+ Date.parse(now) - Date.parse(stage.startedAt),
303
+ );
304
+ } else if (status === "prepared") {
305
+ stage.startedAt ??= now;
306
+ }
307
+ stage.status = status;
308
+ await save({ phase: id });
309
+ if (status === "running" && !dryRun)
310
+ await onCheckpoint({ stage: id, status });
311
+ await onPhaseChange({
312
+ id,
313
+ status,
314
+ stage: structuredClone(stage),
315
+ ...details,
316
+ });
317
+ monitor.stage(id, status, `${stage.label}:${stageStatusLabel(status)}`, {
318
+ startedAt: stage.startedAt,
319
+ finishedAt: stage.finishedAt,
320
+ durationMs: stage.durationMs,
321
+ });
322
+ };
323
+ await save({});
324
+ for (const stage of state.stages.filter(
325
+ (item) => item.status === "skipped",
326
+ )) {
327
+ monitor.stage(
328
+ stage.id,
329
+ "skipped",
330
+ `${stage.label}:${stageStatusLabel("skipped")}`,
331
+ );
332
+ }
333
+
334
+ let currentAssetReadiness = null;
335
+ let assetAdmission = upstreamAssetAdmission ?? null;
336
+ let reproduction = null;
337
+ try {
338
+ let loadedComputeCatalog = null;
339
+ let normalizedResearchPath = researchPath
340
+ ? path.resolve(researchPath)
341
+ : null;
342
+ let compilation = null;
343
+ let compilationArtifact = null;
344
+ const sourceCompilationDigests = [...inheritedCompilationDigests];
345
+ if (compilationArtifactPath) {
346
+ reportProgress("读取并验证 Research Compilation CAP");
347
+ compilationArtifact = await loadResearchCompilationCap(compilationArtifactPath);
348
+ normalizedResearchPath = path.join(root, "research-compilation-input.json");
349
+ await writeJson(normalizedResearchPath, compilationArtifact.inventory);
350
+ sourceCompilationDigests.push(compilationArtifact.artifactDigest);
351
+ await save({
352
+ compilationArtifact: {
353
+ source: path.resolve(compilationArtifactPath),
354
+ artifactDigest: compilationArtifact.artifactDigest,
355
+ },
356
+ });
357
+ }
358
+ if (compileTaskPath) {
359
+ reportProgress("阶段 1/12:Research Compiler 正在结构化论文与仓库");
360
+ await updateStage("research_compilation", "running");
361
+ if (computeCatalogPath) {
362
+ loadedComputeCatalog = await loadComputeCatalog(computeCatalogPath);
363
+ }
364
+ const compilerProfile = loadedComputeCatalog
365
+ ? (loadedComputeCatalog.profiles.find(
366
+ (profile) => profile.id === computeProfile,
367
+ ) ?? loadedComputeCatalog.profiles.find(profile => profile.available !== false && profile.accelerator === "cpu")
368
+ ?? loadedComputeCatalog.profiles.find(profile => profile.available !== false))
369
+ : null;
370
+ compilation = await stageCheckpoint("research_compilation", async () =>
371
+ compilerProfile &&
372
+ listCompilerExecutors().includes(compilerProfile.executor)
373
+ ? await executeScheduledCompilation({
374
+ taskPath: compileTaskPath,
375
+ runsDirectory: directories.compilations,
376
+ runId: `${pipelineId}-compile`,
377
+ agentRuntime,
378
+ computeProfile: compilerProfile,
379
+ executorOptions,
380
+ dryRun,
381
+ progress: reportProgress,
382
+ })
383
+ : await runResearchCompiler({
384
+ taskPath: compileTaskPath,
385
+ runsDirectory: directories.compilations,
386
+ runId: `${pipelineId}-compile`,
387
+ agentRuntime,
388
+ dryRun,
389
+ }),
390
+ );
391
+ if (dryRun) {
392
+ await updateStage("research_compilation", "prepared");
393
+ await save({
394
+ status: "prepared",
395
+ phase: "research_compilation",
396
+ compilationRunId: compilation.bundle.runId,
397
+ });
398
+ return { dryRun: true, state, directories, compilation, compilationArtifact };
399
+ }
400
+ await updateStage("research_compilation", "completed");
401
+ compilation = await stageCheckpoint("reproduction_preparation", () => prepareInventoryForExecution({
402
+ compilation, sourceTaskPath: compileTaskPath, outputDirectory: path.join(directories.compilations, "preparation-input"),
403
+ progress: reportProgress,
404
+ execute: (overrides) => compilerProfile && listCompilerExecutors().includes(compilerProfile.executor)
405
+ ? executeScheduledCompilation({ ...overrides, runsDirectory: directories.compilations, agentRuntime,
406
+ computeProfile: compilerProfile, executorOptions, dryRun })
407
+ : runResearchCompiler({ ...overrides, runsDirectory: directories.compilations, agentRuntime, dryRun }),
408
+ }));
409
+ normalizedResearchPath = path.join(
410
+ compilation.bundle.directories.output,
411
+ "research.normalized.json",
412
+ );
413
+ }
414
+
415
+ let research = await loadResearchStructure(normalizedResearchPath);
416
+ if (research.compilationStage === "inventory") {
417
+ if (computeCatalogPath && !loadedComputeCatalog) loadedComputeCatalog = await loadComputeCatalog(computeCatalogPath);
418
+ const preparationProfile = loadedComputeCatalog
419
+ ? (loadedComputeCatalog.profiles.find((profile) => profile.id === computeProfile) ?? loadedComputeCatalog.profiles.find(profile => profile.available !== false && profile.accelerator === "cpu")
420
+ ?? loadedComputeCatalog.profiles.find(profile => profile.available !== false)) : null;
421
+ const taskPath = await writePreparationTaskFromInventory({ inventoryPath: normalizedResearchPath, paperPath,
422
+ repositoryPath, outputPath: path.join(directories.compilations, "preparation-input", "task.json") });
423
+ compilation = await stageCheckpoint("reproduction_preparation", async () => {
424
+ reportProgress("根据已审核研究清单与固定来源进行复现准备");
425
+ const options = { taskPath, runsDirectory: directories.compilations, runId: `${pipelineId}-prepare`, agentRuntime, dryRun };
426
+ return preparationProfile && listCompilerExecutors().includes(preparationProfile.executor)
427
+ ? executeScheduledCompilation({ ...options, computeProfile: preparationProfile, executorOptions })
428
+ : runResearchCompiler(options);
429
+ });
430
+ if (dryRun) {
431
+ await updateStage("research_plan", "prepared");
432
+ await save({ status: "prepared", phase: "research_plan", compilationRunId: compilation.bundle.runId });
433
+ return { dryRun: true, state, directories, compilation, compilationArtifact };
434
+ }
435
+ research = compilation.research;
436
+ }
437
+ normalizedResearchPath = path.join(root, "research.normalized.json");
438
+ await writeJson(normalizedResearchPath, research);
439
+
440
+ const {
441
+ effectiveResearchArtifactDigest,
442
+ researchPlan,
443
+ researchPlanArchive,
444
+ } = await stageCheckpoint("research_plan", async () => {
445
+ reportProgress("阶段 2/12:固定并验证不可变 Research Plan CAP");
446
+ await updateStage("research_plan", "running");
447
+ let effectiveResearchArtifactDigest;
448
+ let researchPlan;
449
+ let researchPlanArchive = null;
450
+ if (researchArtifactDigest !== undefined) {
451
+ if (!processingBinding || !validSha256Digest(researchArtifactDigest)) {
452
+ throw new CiteArkError(
453
+ "外部 Research Plan 摘要只接受平台已验证并绑定处理任务的 CAP",
454
+ );
455
+ }
456
+ effectiveResearchArtifactDigest = researchArtifactDigest;
457
+ researchPlan = {
458
+ source: "platform-verified",
459
+ artifactDigest: researchArtifactDigest,
460
+ };
461
+ } else {
462
+ const assembled = await assembleResearchPlanCap({
463
+ directory: directories.researchPlanCap,
464
+ research,
465
+ compilerRunId:
466
+ compilation?.bundle?.runId ?? `${pipelineId}-research-plan`,
467
+ ...(sourceCompilationDigests.length
468
+ ? {
469
+ planner: {
470
+ id: "citeark/planner",
471
+ name: "CiteArk Research Planner",
472
+ version: "reproduction-preparation-v1",
473
+ },
474
+ plannerRunId: compilation?.bundle?.runId ?? `${pipelineId}-planner`,
475
+ sourceCompilationDigests,
476
+ }
477
+ : {}),
478
+ sourceArtifactDigests,
479
+ signingKeyPath,
480
+ processingBinding,
481
+ });
482
+ researchPlanArchive = await packCap({
483
+ directory: directories.researchPlanCap,
484
+ outputPath: researchPlanArchivePath,
485
+ });
486
+ effectiveResearchArtifactDigest = assembled.artifactDigest;
487
+ researchPlan = {
488
+ source: "locally-assembled",
489
+ artifactDigest: assembled.artifactDigest,
490
+ ...(sourceCompilationDigests.length ? { sourceCompilationDigests } : {}),
491
+ capDirectory: directories.researchPlanCap,
492
+ archive: researchPlanArchive,
493
+ };
494
+ }
495
+ await save({ researchPlan });
496
+ await updateStage("research_plan", "completed");
497
+ return {
498
+ effectiveResearchArtifactDigest,
499
+ researchPlan,
500
+ researchPlanArchive,
501
+ };
502
+ });
503
+ if (stopAfter === 'research_plan') {
504
+ await save({ status: 'prepared', phase: 'research_plan', researchPath: normalizedResearchPath });
505
+ return { state, directories, research, researchPlan, researchPlanArchive, compilation, compilationArtifact };
506
+ }
507
+ const selectedClaim = claimId
508
+ ? research.claims.find((item) => item.id === claimId)
509
+ : research.claims.find((item) =>
510
+ research.experiments.some((experiment) =>
511
+ experiment.claimIds.includes(item.id),
512
+ ),
513
+ );
514
+ if (!selectedClaim)
515
+ throw new CiteArkError("Research Compiler 没有产出可执行 claim");
516
+ const originalSelectedExperiment = experimentId
517
+ ? research.experiments.find((item) => item.id === experimentId)
518
+ : research.experiments.find((item) =>
519
+ item.claimIds.includes(selectedClaim.id),
520
+ );
521
+ if (!originalSelectedExperiment)
522
+ throw new CiteArkError(`claim ${selectedClaim.id} 没有可执行 experiment`);
523
+ const campaign = executionTargets?.length ? reproductionCampaign(research, executionTargets) : null;
524
+ const selectedExperiment = campaign?.experiment ?? originalSelectedExperiment;
525
+ const datasetRegistry = await readJson(
526
+ datasetRegistryPath ??
527
+ path.join(PACKAGE_ROOT, "data", "dataset-source-registry.v1.json"),
528
+ );
529
+
530
+ const contractPath = path.join(
531
+ directories.protocol,
532
+ "execution-contract.json",
533
+ );
534
+ const policyPath = campaign ? path.join(campaignTargetDirectory(directories.protocol, campaign.targets[0]), "policy.json") : path.join(
535
+ directories.private,
536
+ "verification-policy.json",
537
+ );
538
+ await stageCheckpoint("contract_creation", async () => {
539
+ reportProgress("阶段 3/12:生成盲化 Execution Contract 与私有验证策略");
540
+ await updateStage("contract_creation", "running");
541
+ await save({
542
+ researchPath: normalizedResearchPath,
543
+ claimId: selectedClaim.id,
544
+ experimentId: selectedExperiment.id,
545
+ });
546
+ if (campaign) {
547
+ const { contract } = await createCampaignContract({ research, researchPath: normalizedResearchPath,
548
+ targets: campaign.targets, directory: directories.protocol, outputPath: contractPath, tolerance });
549
+ await updateStage("contract_creation", "completed");
550
+ return { contractDigest: contract.contractDigest, campaignId: campaign.id };
551
+ }
552
+ const { contract, policy } = await createExecutionContract({
553
+ researchPath: normalizedResearchPath,
554
+ claimId: selectedClaim.id,
555
+ experimentId: selectedExperiment.id,
556
+ executionClaimIds,
557
+ outputPath: contractPath,
558
+ policyPath,
559
+ tolerance,
560
+ });
561
+ await updateStage("contract_creation", "completed");
562
+
563
+ return {
564
+ contractDigest: contract.contractDigest,
565
+ policyDigest: policy.policyDigest,
566
+ };
567
+ });
568
+ const contract = await loadExecutionContract(contractPath);
569
+ const assessmentContractPath = campaign
570
+ ? path.join(campaignTargetDirectory(directories.protocol, campaign.targets[0]), "contract.json") : contractPath;
571
+ const policy = await loadVerificationPolicy(policyPath, campaign ? await loadExecutionContract(assessmentContractPath) : contract);
572
+ const computeCatalog =
573
+ loadedComputeCatalog ?? (await loadComputeCatalog(computeCatalogPath));
574
+ const { assetPlan, computeDecision } = await stageCheckpoint(
575
+ "compute_scheduling",
576
+ async () => {
577
+ reportProgress(
578
+ stopAfter === ASSET_QUALIFICATION_BOUNDARY
579
+ ? "阶段 4/12:为资产验收选择独立的低成本 CPU,不调度科研算力"
580
+ : "阶段 4/12:按实验需求、资源目录与预算生成可审计计算决策",
581
+ );
582
+ await updateStage("compute_scheduling", "running");
583
+ const assetPlan = createAssetPreparationPlan({
584
+ experiment: selectedExperiment,
585
+ datasetRegistry,
586
+ });
587
+ currentAssetReadiness = createAssetReadiness(assetPlan, {
588
+ state: "planned",
589
+ admission: assetAdmission,
590
+ });
591
+ await writeJson(
592
+ path.join(directories.protocol, "asset-preparation-plan.json"),
593
+ assetPlan,
594
+ );
595
+ await writeJson(
596
+ path.join(directories.protocol, "asset-readiness.json"),
597
+ currentAssetReadiness,
598
+ );
599
+ await save({ assetReadiness: currentAssetReadiness });
600
+ const assetPlanFailures = assetPreparationPlanFailures(assetPlan);
601
+ if (assetPlanFailures.length) {
602
+ currentAssetReadiness = createAssetReadiness(assetPlan, {
603
+ state: "planned",
604
+ admission: assetAdmission,
605
+ message: `${assetPlanFailures.map((failure) => failure.message).join(";")}这些问题只影响预下载缓存,不会阻止最终复现 Agent。`,
606
+ });
607
+ await writeJson(
608
+ path.join(directories.protocol, "asset-readiness.json"),
609
+ currentAssetReadiness,
610
+ );
611
+ await save({ assetReadiness: currentAssetReadiness });
612
+ }
613
+ let computeDecision;
614
+ if (stopAfter === ASSET_QUALIFICATION_BOUNDARY) {
615
+ const assetProfile = selectAssetPreparationProfile(
616
+ computeCatalog,
617
+ null,
618
+ {
619
+ profileOverride: computeProfile,
620
+ },
621
+ );
622
+ computeDecision = createAssetPreparationComputeDecision({
623
+ profile: assetProfile,
624
+ contract,
625
+ experiment: selectedExperiment,
626
+ plan: assetPlan,
627
+ catalog: computeCatalog,
628
+ });
629
+ await writeJson(
630
+ path.join(
631
+ directories.protocol,
632
+ "asset-preparation-compute-decision.json",
633
+ ),
634
+ computeDecision,
635
+ );
636
+ } else {
637
+ const computePolicy = await loadComputePolicy(computePolicyPath);
638
+ computeDecision = await scheduleCompute({
639
+ experiment: selectedExperiment,
640
+ contract,
641
+ catalog: computeCatalog,
642
+ policy: computePolicy,
643
+ resourceLimits,
644
+ profileOverride: computeProfile,
645
+ selectionConstraints: computeSelection,
646
+ outputPath: path.join(
647
+ directories.protocol,
648
+ "compute-decision.json",
649
+ ),
650
+ });
651
+ }
652
+ await save({
653
+ computeDecision: {
654
+ decisionDigest: computeDecision.decisionDigest,
655
+ classification: computeDecision.requirement.classification,
656
+ profileId: computeDecision.selectedProfile.id,
657
+ executor: computeDecision.selectedProfile.executor,
658
+ accelerator: computeDecision.selectedProfile.accelerator,
659
+ estimatedCostUsd: computeDecision.estimate.costUsd,
660
+ workload: computeDecision.workload ?? "reproduction",
661
+ },
662
+ });
663
+ await updateStage("compute_scheduling", "completed");
664
+
665
+ return { assetPlan, computeDecision };
666
+ },
667
+ );
668
+ const repositoryOverride = resolvePipelineRepositoryOverride({
669
+ compilation,
670
+ repositoryPath,
671
+ experiment: selectedExperiment,
672
+ });
673
+ if (!paperReading && compilation?.bundle?.runtimeTask?.paper?.reading?.status === "ready") {
674
+ const bundle = compilation.bundle;
675
+ paperReading = { ...bundle.runtimeTask.paper.reading, directory: path.join(bundle.directories.input, "paper-reading") };
676
+ paperPath ??= path.join(bundle.directories.input, bundle.runtimeTask.paper.path.slice("/job/input/".length));
677
+ }
678
+ const paperOverride = paperPath
679
+ ? { path: paperPath, digest: processingBinding?.sourceDigest, ...(paperReading ? { preparedReading: paperReading } : {}) }
680
+ : undefined;
681
+ const { assetPreparation, effectivePreparationDecision } =
682
+ await stageCheckpoint("asset_preparation", async () => {
683
+ const preparationDecision = resolvePipelineAssetPreparationDecision({
684
+ decision: decideOpportunisticAssetPreparation({
685
+ plan: assetPlan,
686
+ admission: assetAdmission,
687
+ executor: computeDecision.selectedProfile.executor,
688
+ }),
689
+ reuseCompletedResultOnRestore:
690
+ executorOptions?.gcpBatch?.reuseCompletedResultOnRestore === true,
691
+ singleWorkspace: usesWorkspaceSummary(contract) && stopAfter !== ASSET_QUALIFICATION_BOUNDARY,
692
+ cpuPreparation: usesWorkspaceSummary(contract) && computeDecision.selectedProfile.executor === "gcp-batch" && computeDecision.environment.gpu === "all",
693
+ });
694
+ currentAssetReadiness = createAssetReadiness(assetPlan, {
695
+ state:
696
+ preparationDecision.action === "attempt" ? "preparing" : "deferred",
697
+ admission: assetAdmission,
698
+ preparationMode:
699
+ preparationDecision.action === "attempt"
700
+ ? null
701
+ : "reproduction-agent-fallback",
702
+ message: preparationDecision.message,
703
+ });
704
+ await writeJson(
705
+ path.join(directories.protocol, "asset-readiness.json"),
706
+ currentAssetReadiness,
707
+ );
708
+ await save({
709
+ assetReadiness: currentAssetReadiness,
710
+ preparationDecision,
711
+ });
712
+ if (stopAfter === ASSET_QUALIFICATION_BOUNDARY) {
713
+ reportProgress(
714
+ "资料检查:只观察公开链接能否下载;该结果不判断实验能否复现",
715
+ );
716
+ assetAdmission = await probeAssetQualification({
717
+ research,
718
+ claim: selectedClaim,
719
+ experiment: selectedExperiment,
720
+ datasetRegistry,
721
+ outputPath: path.join(directories.protocol, "asset-admission.json"),
722
+ observe,
723
+ });
724
+ currentAssetReadiness = createAssetReadiness(assetPlan, {
725
+ state: "planned",
726
+ admission: assetAdmission,
727
+ });
728
+ await writeJson(
729
+ path.join(directories.protocol, "asset-readiness.json"),
730
+ currentAssetReadiness,
731
+ );
732
+ await save({
733
+ assetAdmission: assetAdmission.summary,
734
+ assetReadiness: currentAssetReadiness,
735
+ });
736
+ assertAssetQualificationAdmission(assetAdmission);
737
+ }
738
+ let assetPreparation = null;
739
+ let effectivePreparationDecision =
740
+ resolvePipelineAssetPreparationDecision({
741
+ decision: decideOpportunisticAssetPreparation({
742
+ plan: assetPlan,
743
+ admission: assetAdmission,
744
+ executor: computeDecision.selectedProfile.executor,
745
+ }),
746
+ reuseCompletedResultOnRestore:
747
+ executorOptions?.gcpBatch?.reuseCompletedResultOnRestore === true,
748
+ singleWorkspace: usesWorkspaceSummary(contract) && stopAfter !== ASSET_QUALIFICATION_BOUNDARY,
749
+ cpuPreparation: usesWorkspaceSummary(contract) && computeDecision.selectedProfile.executor === "gcp-batch" && computeDecision.environment.gpu === "all",
750
+ });
751
+ if (effectivePreparationDecision.action === "attempt") {
752
+ currentAssetReadiness = createAssetReadiness(assetPlan, {
753
+ state: "preparing",
754
+ admission: assetAdmission,
755
+ message: effectivePreparationDecision.message,
756
+ });
757
+ await writeJson(
758
+ path.join(directories.protocol, "asset-readiness.json"),
759
+ currentAssetReadiness,
760
+ );
761
+ await save({
762
+ assetReadiness: currentAssetReadiness,
763
+ preparationDecision: effectivePreparationDecision,
764
+ });
765
+ reportProgress(
766
+ "阶段 5/12:尝试在低成本 CPU 上预先准备可复用资料缓存",
767
+ { assetReadiness: currentAssetReadiness },
768
+ );
769
+ await updateStage("asset_preparation", "running", {
770
+ assetReadiness: currentAssetReadiness,
771
+ });
772
+ const researchPreparation = usesWorkspaceSummary(contract) && stopAfter !== ASSET_QUALIFICATION_BOUNDARY;
773
+ const assetComputeDecision =
774
+ computeDecision.workload === "asset-preparation"
775
+ ? computeDecision
776
+ : createAssetPreparationComputeDecision({
777
+ profile: selectAssetPreparationProfile(
778
+ computeCatalog,
779
+ computeDecision,
780
+ ),
781
+ contract,
782
+ experiment: selectedExperiment,
783
+ plan: assetPlan,
784
+ catalog: computeCatalog,
785
+ ...(researchPreparation ? { researchRuntimeMinutes: computeDecision.environment.timeoutMinutes } : {}),
786
+ });
787
+ if (computeDecision.workload !== "asset-preparation") {
788
+ await writeJson(
789
+ path.join(
790
+ directories.protocol,
791
+ "asset-preparation-compute-decision.json",
792
+ ),
793
+ assetComputeDecision,
794
+ );
795
+ }
796
+ try {
797
+ const prepare = researchPreparation ? executeGcpBatchReproduction : executeGcpAssetPreparation;
798
+ assetPreparation = await prepare({
799
+ taskPath: contractPath,
800
+ runsDirectory: directories.runs,
801
+ runId: `${pipelineId}-assets`,
802
+ repositoryOverride,
803
+ paperOverride,
804
+ computeDecision: assetComputeDecision,
805
+ assetPlan,
806
+ agentRuntime,
807
+ executorOptions: researchPreparation ? { ...executorOptions, gcpBatch: {
808
+ ...executorOptions?.gcpBatch,
809
+ restoreCheckpointPrefix: executorOptions?.gcpBatch?.assetPreparationRestoreCheckpointPrefix,
810
+ restoreCheckpointBucket: executorOptions?.gcpBatch?.assetPreparationRestoreCheckpointBucket,
811
+ clients: executorOptions?.gcpBatch?.assetPreparationClients ?? executorOptions?.gcpBatch?.clients,
812
+ keepStaging: true,
813
+ } } : executorOptions,
814
+ ...(researchPreparation ? { workloadAdapter: createCpuResearchPreparationWorkload({ runtimeBudgetMinutes: computeDecision.environment.timeoutMinutes }) } : {}),
815
+ dryRun,
816
+ progress: reportProgress,
817
+ });
818
+ } catch (error) {
819
+ // A failed CPU handoff is not permission to rent the GPU and repeat preparation.
820
+ if (usesWorkspaceSummary(contract) && stopAfter !== ASSET_QUALIFICATION_BOUNDARY) throw error;
821
+ if (mustAbortAfterAssetPreparationFailure(error)) throw error;
822
+ const fallback = assetPreparationFallback(error);
823
+ effectivePreparationDecision = {
824
+ action: "defer",
825
+ reasonCode: fallback.reasonCode,
826
+ message: fallback.message,
827
+ constraints: [],
828
+ };
829
+ currentAssetReadiness = createAssetReadiness(assetPlan, {
830
+ state: fallback.state,
831
+ admission: assetAdmission,
832
+ preparationMode: fallback.preparationMode,
833
+ message: fallback.message,
834
+ });
835
+ await writeJson(
836
+ path.join(directories.protocol, "asset-readiness.json"),
837
+ currentAssetReadiness,
838
+ );
839
+ await save({
840
+ assetPreparation: {
841
+ planDigest: assetPlan.planDigest,
842
+ mode: fallback.preparationMode,
843
+ reasonCode: fallback.reasonCode,
844
+ reason: fallback.message,
845
+ },
846
+ assetReadiness: currentAssetReadiness,
847
+ preparationDecision: effectivePreparationDecision,
848
+ });
849
+ reportProgress(fallback.message, {
850
+ assetReadiness: currentAssetReadiness,
851
+ });
852
+ await updateStage("asset_preparation", "deferred", {
853
+ assetReadiness: currentAssetReadiness,
854
+ });
855
+ }
856
+ if (assetPreparation) {
857
+ if (assetPreparation.preparation?.status === "blocked") throw new CiteArkError(
858
+ `CPU 准备确认当前实验无法继续:${assetPreparation.preparation.summary}`,
859
+ { failureCode: "research_preparation.no_viable_execution", retryable: false });
860
+ await save({
861
+ assetPreparation: {
862
+ planDigest: assetPlan.planDigest,
863
+ manifestDigest:
864
+ assetPreparation.manifest?.manifestDigest ?? null,
865
+ cacheHit: assetPreparation.cacheHit ?? false,
866
+ checkpointPrefix:
867
+ assetPreparation.checkpoint?.checkpointPrefix ?? null,
868
+ },
869
+ });
870
+ currentAssetReadiness = createAssetReadiness(assetPlan, {
871
+ state: dryRun ? "preparing" : "prepared",
872
+ manifest: assetPreparation.manifest,
873
+ admission: assetAdmission,
874
+ preparationMode: assetPreparation.preparationMode,
875
+ cacheHit: assetPreparation.cacheHit ?? false,
876
+ });
877
+ await writeJson(
878
+ path.join(directories.protocol, "asset-readiness.json"),
879
+ currentAssetReadiness,
880
+ );
881
+ await save({ assetReadiness: currentAssetReadiness });
882
+ await updateStage(
883
+ "asset_preparation",
884
+ dryRun ? "prepared" : "completed",
885
+ {
886
+ assetReadiness: currentAssetReadiness,
887
+ },
888
+ );
889
+ }
890
+ } else {
891
+ currentAssetReadiness = createAssetReadiness(assetPlan, {
892
+ state: "deferred",
893
+ admission: assetAdmission,
894
+ preparationMode: "reproduction-agent-fallback",
895
+ message: effectivePreparationDecision.message,
896
+ });
897
+ await writeJson(
898
+ path.join(directories.protocol, "asset-readiness.json"),
899
+ currentAssetReadiness,
900
+ );
901
+ await save({
902
+ assetPreparation: {
903
+ planDigest: assetPlan.planDigest,
904
+ mode: "reproduction-agent-fallback",
905
+ reasonCode: effectivePreparationDecision.reasonCode,
906
+ reason: effectivePreparationDecision.message,
907
+ },
908
+ assetReadiness: currentAssetReadiness,
909
+ });
910
+ reportProgress(effectivePreparationDecision.message, {
911
+ assetReadiness: currentAssetReadiness,
912
+ });
913
+ await updateStage("asset_preparation", "deferred", {
914
+ assetReadiness: currentAssetReadiness,
915
+ });
916
+ }
917
+
918
+ return { assetPreparation, effectivePreparationDecision };
919
+ });
920
+ if (stopAfter === ASSET_QUALIFICATION_BOUNDARY) {
921
+ for (const [id] of PIPELINE_STAGES.slice(
922
+ PIPELINE_STAGES.findIndex(
923
+ ([id]) => id === ASSET_QUALIFICATION_BOUNDARY,
924
+ ) + 1,
925
+ )) {
926
+ await updateStage(id, "skipped");
927
+ }
928
+ const qualification = {
929
+ schemaVersion: "0.1",
930
+ kind: "citeark.asset-qualification",
931
+ status: dryRun
932
+ ? "prepared"
933
+ : assetPreparation
934
+ ? "qualified"
935
+ : "deferred",
936
+ boundary: ASSET_QUALIFICATION_BOUNDARY,
937
+ reproductionStarted: false,
938
+ claimId: selectedClaim.id,
939
+ experimentId: selectedExperiment.id,
940
+ planDigest: assetPlan.planDigest,
941
+ manifestDigest: assetPreparation?.manifest?.manifestDigest ?? null,
942
+ checkpointPrefix:
943
+ assetPreparation?.checkpoint?.checkpointPrefix ?? null,
944
+ cacheHit: assetPreparation?.cacheHit ?? false,
945
+ ...(assetPreparation
946
+ ? {}
947
+ : {
948
+ reasonCode: effectivePreparationDecision.reasonCode,
949
+ message: effectivePreparationDecision.message,
950
+ }),
951
+ finishedAt: new Date().toISOString(),
952
+ };
953
+ await writeJson(
954
+ path.join(directories.protocol, "asset-qualification.json"),
955
+ qualification,
956
+ );
957
+ await save({
958
+ status: dryRun ? "prepared" : "completed",
959
+ phase: ASSET_QUALIFICATION_BOUNDARY,
960
+ executionBoundary: ASSET_QUALIFICATION_BOUNDARY,
961
+ contractDigest: contract.contractDigest,
962
+ policyDigest: policy.policyDigest,
963
+ usage: mergeAgentUsage(
964
+ compilation?.totalUsage ?? compilation?.bundle?.state?.usage,
965
+ assetPreparation?.bundle?.state?.usage,
966
+ assetPreparation?.usage,
967
+ ),
968
+ qualification,
969
+ finishedAt: qualification.finishedAt,
970
+ });
971
+ return {
972
+ dryRun,
973
+ state,
974
+ directories,
975
+ researchPlan,
976
+ researchPlanArchive,
977
+ compilation,
978
+ research,
979
+ contract,
980
+ policy,
981
+ computeDecision,
982
+ assetAdmission,
983
+ assetPlan,
984
+ assetPreparation,
985
+ qualification,
986
+ };
987
+ }
988
+
989
+ const existingReproductionCheckpoint =
990
+ executorOptions?.gcpBatch?.restoreCheckpointPrefix;
991
+ const reproductionExecutorOptions = assetPreparation?.checkpoint
992
+ ?.checkpointPrefix
993
+ ? {
994
+ ...executorOptions,
995
+ gcpBatch: {
996
+ ...(executorOptions?.gcpBatch ?? {}),
997
+ ...(assetPreparation.preparationMode === "research-workspace" ? {
998
+ cpuResearchComputeDecision: assetPreparation.bundle.computeDecision,
999
+ } : {}),
1000
+ restoreCheckpointPrefix:
1001
+ existingReproductionCheckpoint ??
1002
+ assetPreparation.checkpoint.checkpointPrefix,
1003
+ restoreCheckpointBucket: existingReproductionCheckpoint
1004
+ ? executorOptions?.gcpBatch?.restoreCheckpointBucket
1005
+ : assetPreparation.checkpoint.bucket,
1006
+ },
1007
+ }
1008
+ : executorOptions;
1009
+
1010
+ reproduction = await stageCheckpoint("reproduction", async () => {
1011
+ reportProgress("阶段 6/12:Agent 在隔离工作区中自主复现");
1012
+ await updateStage("reproduction", "running");
1013
+ await save({
1014
+ contractDigest: contract.contractDigest,
1015
+ policyDigest: policy.policyDigest,
1016
+ });
1017
+ const executed = await executeScheduledReproduction({
1018
+ referenceMaterialsPath,
1019
+ taskPath: contractPath,
1020
+ runsDirectory: directories.runs,
1021
+ runId: `${pipelineId}-run`,
1022
+ repositoryOverride,
1023
+ paperOverride,
1024
+ agentRuntime,
1025
+ computeDecision,
1026
+ executorOptions: reproductionExecutorOptions,
1027
+ dryRun,
1028
+ progress: reportProgress,
1029
+ });
1030
+ await save({
1031
+ reproductionRunId: executed.bundle.runId,
1032
+ agent: executed.bundle.state.agent,
1033
+ });
1034
+ if (!dryRun) await updateStage("reproduction", "completed");
1035
+ return executed;
1036
+ });
1037
+ if (dryRun) {
1038
+ await updateStage("reproduction", "prepared");
1039
+ await save({
1040
+ status: "prepared",
1041
+ phase: "reproduction",
1042
+ reproductionRunId: reproduction.bundle.runId,
1043
+ });
1044
+ return {
1045
+ dryRun: true,
1046
+ state,
1047
+ directories,
1048
+ researchPlan,
1049
+ researchPlanArchive,
1050
+ research,
1051
+ contract,
1052
+ policy,
1053
+ computeDecision,
1054
+ assetPlan,
1055
+ assetPreparation,
1056
+ reproduction,
1057
+ };
1058
+ }
1059
+ await save({
1060
+ reproductionRunId: reproduction.bundle.runId,
1061
+ agent: reproduction.bundle.state.agent,
1062
+ usage: mergeAgentUsage(
1063
+ compilation?.totalUsage ?? compilation?.bundle.state.usage,
1064
+ // The full research session continues on GPU, so its trace already
1065
+ // includes CPU preparation. Dedicated legacy cache jobs remain separate.
1066
+ assetPreparation?.preparationMode === "research-workspace" ? null : assetPreparation?.bundle?.state?.usage,
1067
+ reproduction.bundle.state.usage,
1068
+ ),
1069
+ });
1070
+ const effectiveScientificAssessor =
1071
+ typeof scientificAssessor === "function"
1072
+ ? scientificAssessor
1073
+ : createScientificAssessor({
1074
+ runtimeAgent: agentRuntime,
1075
+ providerSecret: executorOptions?.gcpBatch?.providerSecret,
1076
+ paperBudget: executorOptions?.gcpBatch?.paperBudget,
1077
+ });
1078
+ const replay = await replayRunToCap({
1079
+ researchPath: normalizedResearchPath,
1080
+ contractPath: assessmentContractPath,
1081
+ ...(campaign ? { executionContractPath: contractPath } : {}),
1082
+ policyPath,
1083
+ runDirectory: reproduction.bundle.directories.run,
1084
+ capDirectory: directories.cap,
1085
+ signingKeyPath,
1086
+ processingBinding,
1087
+ researchArtifactDigest: effectiveResearchArtifactDigest,
1088
+ scientificAssessor: effectiveScientificAssessor,
1089
+ handoffCompletion: Boolean(agentRuntime.codexAccountId || runtimeProviderConfiguration(agentRuntime).baseUrl)
1090
+ ? createSourceReviewCompletion({ runtimeAgent: agentRuntime,
1091
+ providerSecret: executorOptions?.gcpBatch?.providerSecret,
1092
+ paperBudget: executorOptions?.gcpBatch?.paperBudget, maxRequests: 80, maxToolCalls: 300 }) : undefined,
1093
+ stageCheckpoint,
1094
+ onAssessment: async (assessment) => {
1095
+ if (assessment?.usage) {
1096
+ await save({ usage: mergeAgentUsage(state.usage, assessment.usage) });
1097
+ }
1098
+ },
1099
+ getPipelineSummary: async () => ({
1100
+ schemaVersion: "1.0",
1101
+ kind: "citeark.pipeline-summary",
1102
+ pipelineId: state.pipelineId,
1103
+ status: state.status,
1104
+ phase: state.phase,
1105
+ createdAt: state.createdAt,
1106
+ updatedAt: state.updatedAt,
1107
+ stages: structuredClone(state.stages),
1108
+ usage: structuredClone(state.usage ?? mergeAgentUsage()),
1109
+ usageScope: upstreamCompilation ? "target" : "pipeline",
1110
+ agent: structuredClone(
1111
+ state.agent ?? reproduction.bundle.state.agent ?? {},
1112
+ ),
1113
+ computeDecision: structuredClone(state.computeDecision ?? {}),
1114
+ }),
1115
+ onStageChange: async ({ stage, status }) => {
1116
+ if (status === "started") {
1117
+ const messages = {
1118
+ evidence_parsing: "阶段 7/12:从原始证据独立解析指标",
1119
+ verification:
1120
+ "阶段 8/12:检查执行完整性,并由独立 AI 评估数值差异是否合理",
1121
+ research_card:
1122
+ "阶段 9/12:汇总论文级验证结论并生成研究卡片与 README 快照",
1123
+ execution_handoff: "阶段 10/12:整理已执行路线和再次运行说明",
1124
+ packaging: "阶段 11/12:生成 CAP 2.0 Records、Blobs 与签名证明",
1125
+ };
1126
+ reportProgress(messages[stage]);
1127
+ await updateStage(stage, "running");
1128
+ } else {
1129
+ await updateStage(stage, status === "skipped" ? "skipped" : "completed");
1130
+ }
1131
+ },
1132
+ });
1133
+ reportProgress("阶段 12/12:生成可上传的 .cap 单文件包并复核哈希");
1134
+ const archiveContentDigest = await capDirectoryContentDigest(directories.cap);
1135
+ const archive = await stageCheckpoint("archive", async () => {
1136
+ await updateStage("archive", "running");
1137
+ const packed = await packCap({
1138
+ directory: directories.cap,
1139
+ outputPath: path.join(root, `${pipelineId}-${archiveContentDigest.slice(7)}.cap`),
1140
+ reuseExisting: true,
1141
+ });
1142
+ await updateStage("archive", "completed");
1143
+ return packed;
1144
+ }, archiveContentDigest);
1145
+ await save({
1146
+ status: "completed",
1147
+ phase: "completed",
1148
+ verificationStatus: replay.assessment.verificationStatus,
1149
+ artifactDigest: replay.cap.artifactDigest,
1150
+ capDirectory: directories.cap,
1151
+ researchCard: {
1152
+ cardDigest: replay.researchCard.card.cardDigest,
1153
+ path: "cap/preview/README.md",
1154
+ },
1155
+ citearkArchive: archive,
1156
+ finishedAt: new Date().toISOString(),
1157
+ });
1158
+ if (!dryRun) await onCheckpoint({ stage: "publishing", status: "ready" });
1159
+ return {
1160
+ dryRun: false,
1161
+ state,
1162
+ directories,
1163
+ archive,
1164
+ researchPlan,
1165
+ researchPlanArchive,
1166
+ compilation,
1167
+ compilationArtifact,
1168
+ research,
1169
+ contract,
1170
+ policy,
1171
+ computeDecision,
1172
+ assetPlan,
1173
+ assetPreparation,
1174
+ reproduction,
1175
+ replay,
1176
+ };
1177
+ } catch (error) {
1178
+ if (error && typeof error === "object" && currentAssetReadiness) {
1179
+ error.assetReadiness ??= currentAssetReadiness;
1180
+ }
1181
+ const activeStage = state.stages.find(
1182
+ (stage) => stage.status === "running",
1183
+ );
1184
+ if (activeStage) await updateStage(activeStage.id, "failed");
1185
+ await save({
1186
+ status: "failed",
1187
+ error: error instanceof Error ? error.message : String(error),
1188
+ finishedAt: new Date().toISOString(),
1189
+ });
1190
+ if (error && typeof error === "object") {
1191
+ error.pipelineRecovery = createPipelineRecoveryEnvelope({
1192
+ state,
1193
+ reproduction,
1194
+ checkpoint: error.recoveryCheckpoint,
1195
+ });
1196
+ error.failureStage ??= error.pipelineRecovery?.resumeStage;
1197
+ }
1198
+ try {
1199
+ if (!dryRun) await onCheckpoint({ stage: state.phase, status: "failed" });
1200
+ } catch (checkpointError) {
1201
+ error.recoveryPersistenceError = checkpointError.message;
1202
+ }
1203
+ throw error;
1204
+ } finally {
1205
+ await monitor.close({
1206
+ status: state.status,
1207
+ stage: state.phase,
1208
+ message:
1209
+ state.status === "completed"
1210
+ ? state.executionBoundary === ASSET_QUALIFICATION_BOUNDARY
1211
+ ? "资产验收已完成,未启动复现 Agent"
1212
+ : "流水线已完成"
1213
+ : state.status === "prepared"
1214
+ ? "流水线已准备,尚未真实执行"
1215
+ : state.status === "failed"
1216
+ ? "流水线执行失败"
1217
+ : "流水线监控已结束",
1218
+ });
1219
+ }
1220
+ }
1221
+
1222
+ function validSha256Digest(value) {
1223
+ return typeof value === "string" && /^sha256:[0-9a-f]{64}$/.test(value);
1224
+ }
1225
+
1226
+ export function resolvePipelineRepositoryOverride({
1227
+ compilation,
1228
+ repositoryPath,
1229
+ experiment,
1230
+ }) {
1231
+ // 独立重建必须从 CiteArk 的确定性空白基座开始,不能误把编译阶段的
1232
+ // “无作者仓库”目录或调用方传入的代码当成官方实现。
1233
+ if (isCiteArkReconstructionRepository(experiment?.repository))
1234
+ return undefined;
1235
+ if (compilation) {
1236
+ return {
1237
+ path: compilation.bundle.directories.repositorySnapshot,
1238
+ digest: compilation.bundle.repositoryIdentity.snapshotDigest,
1239
+ };
1240
+ }
1241
+ return repositoryPath ? { path: path.resolve(repositoryPath) } : undefined;
1242
+ }
1243
+
1244
+ export function resolvePipelineAssetPreparationDecision({
1245
+ decision,
1246
+ reuseCompletedResultOnRestore,
1247
+ singleWorkspace = false,
1248
+ cpuPreparation = false,
1249
+ }) {
1250
+ if (singleWorkspace && cpuPreparation && !reuseCompletedResultOnRestore) return {
1251
+ action: "attempt", reasonCode: "cpu_research_preparation",
1252
+ message: "同一研究工作区先在 CPU 阅读、修订、下载与准备,再携带完整检查点进入 GPU。",
1253
+ constraints: [],
1254
+ };
1255
+ if (singleWorkspace && !reuseCompletedResultOnRestore) return {
1256
+ action: "defer", reasonCode: "single_research_workspace",
1257
+ message: "资料与依赖由当前实验工作区准备;已有缓存经核验后复用。",
1258
+ constraints: [],
1259
+ };
1260
+ if (!reuseCompletedResultOnRestore) return decision;
1261
+ return {
1262
+ action: "defer",
1263
+ reasonCode: "completed_result_reuse",
1264
+ message: "已恢复完整实验结果,不再重复准备模型与数据缓存。",
1265
+ constraints: [],
1266
+ };
1267
+ }
1268
+
1269
+ function stageStatusLabel(status) {
1270
+ return (
1271
+ {
1272
+ running: "正在执行",
1273
+ completed: "已完成",
1274
+ failed: "失败",
1275
+ prepared: "已准备",
1276
+ deferred: "交由复现 Agent",
1277
+ skipped: "已跳过",
1278
+ pending: "等待中",
1279
+ }[status] ?? status
1280
+ );
1281
+ }