@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,819 @@
1
+ import { executionGraphContext } from '../research/execution-graph-context.mjs';
2
+ import { reproductionScopeIssues } from "../research/reproduction-scope.mjs";
3
+ import { revisedInventoryClaims } from "../research/inventory-revisions.mjs";
4
+ import { publicContractPolicyIssues, researchPublicContract } from "./public-contract.mjs";
5
+ import { randomBytes } from "node:crypto";
6
+
7
+ import { capPolicyCommitment } from "../cap/v2/core.mjs";
8
+ import { CiteArkError, readJson, safeRelativePath, sha256Value, writeJson } from "../util.mjs";
9
+ import {
10
+ loadResearchStructure,
11
+ } from "../research/structure.mjs";
12
+ import {
13
+ describeSupportedPreflightSchemaVersions,
14
+ MAX_PREFLIGHT_MINUTES,
15
+ MIN_PUBLIC_ASSET_PREFLIGHT_MINUTES,
16
+ PREFLIGHT_CHECK_KINDS,
17
+ supportsPreflightSchemaVersion,
18
+ } from "./preflight-schema.mjs";
19
+ import { resolveComputeRequirement, validateDeclaredComputeRequirement } from "../compute/requirements.mjs";
20
+ import { resolveVerificationTolerance } from "../verification/tolerance.mjs";
21
+ import { EXECUTION_MODES } from "./execution-mode.mjs";
22
+ import { executionWorkloadIssues } from "./execution-workload.mjs";
23
+ import { measurementHardwareIssues } from "../compute/measurement-hardware.mjs";
24
+ import {
25
+ executionTimeoutCapMinutes,
26
+ LEGACY_EXECUTION_TIMEOUT_FALLBACK_MINUTES,
27
+ } from "./execution-timeouts.mjs";
28
+ import {
29
+ ASSET_ACQUISITION_MODES,
30
+ ASSET_SCIENTIFIC_ROLES,
31
+ } from "../assets/plan.mjs";
32
+
33
+ export const EXECUTION_CONTRACT_VERSION = "1.0";
34
+ export const VERIFICATION_POLICY_VERSION = "1.0";
35
+
36
+ export async function createExecutionContract({
37
+ researchPath,
38
+ claimId,
39
+ experimentId,
40
+ executionClaimIds,
41
+ restrictToAssignedMeasurements = false,
42
+ outputPath,
43
+ policyPath,
44
+ tolerance,
45
+ }) {
46
+ const research = await loadResearchStructure(researchPath);
47
+ if (research.compilationStage === "inventory") throw new CiteArkError("研究清单尚未进行复现准备;请先根据固定清单生成执行计划,再创建执行契约");
48
+ const claim = research.claims.find((item) => item.id === claimId);
49
+ if (!claim) throw new CiteArkError(`找不到 claim:${claimId}`);
50
+ const experiment = experimentId
51
+ ? research.experiments.find((item) => item.id === experimentId)
52
+ : research.experiments.find((item) => item.claimIds.includes(claimId));
53
+ if (!experiment) throw new CiteArkError(`找不到绑定 claim ${claimId} 的实验`);
54
+ if (!experiment.claimIds.includes(claimId)) throw new CiteArkError("实验没有绑定所选 claim");
55
+ const bindings = resolveMeasurementBindings(research, claim, experiment);
56
+ if (!bindings.length) throw new CiteArkError("claim 的报告指标与实验测量定义不匹配");
57
+ const requestedExecutionClaimIds = [...new Set(
58
+ Array.isArray(executionClaimIds) && executionClaimIds.length
59
+ ? executionClaimIds
60
+ : [claim.id],
61
+ )];
62
+ const executionClaims = requestedExecutionClaimIds.map((id) => {
63
+ const candidate = research.claims.find((item) => item.id === id);
64
+ if (!candidate) throw new CiteArkError(`找不到共享执行 claim:${id}`);
65
+ if (!experiment.claimIds.includes(id)) {
66
+ throw new CiteArkError(`共享执行 claim ${id} 没有绑定实验 ${experiment.id}`);
67
+ }
68
+ return candidate;
69
+ });
70
+ const executionBindings = deduplicateMeasurementBindings(
71
+ executionClaims.flatMap((candidate) =>
72
+ resolveMeasurementBindings(research, candidate, experiment)),
73
+ );
74
+ if (!executionBindings.length) {
75
+ throw new CiteArkError("共享执行单元没有可执行测量定义");
76
+ }
77
+ const claimMeasurementIds = [...new Set(executionClaims.flatMap((candidate) =>
78
+ candidate.reportedMeasurements.map((measurement) => measurement.id)))];
79
+ const measurementIds = executionBindings.map(({ measurementId }) => measurementId);
80
+ const boundMeasurementIds = new Set(measurementIds);
81
+ const uncoveredMeasurementIds = claimMeasurementIds.filter((measurementId) =>
82
+ !boundMeasurementIds.has(measurementId));
83
+ const paper = research.sources.find((source) => source.kind === "paper");
84
+ const editablePlan = experiment.protocol?.executionMode === "agent_planned";
85
+ const executionGoalBindings = editablePlan && !restrictToAssignedMeasurements && research.reproductionScope === undefined
86
+ ? deduplicateMeasurementBindings(executionClaims.flatMap((candidate) =>
87
+ candidate.reportedMeasurements.map((reported) => ({
88
+ measurementId: reported.id,
89
+ reported,
90
+ measurement: executionBindings.find(
91
+ (binding) => binding.measurementId === reported.id,
92
+ )?.measurement ?? reported,
93
+ }))))
94
+ : executionBindings;
95
+ const verificationBindings = editablePlan && !restrictToAssignedMeasurements && research.reproductionScope === undefined
96
+ ? claim.reportedMeasurements.map((reported) => ({
97
+ measurementId: reported.id,
98
+ reported,
99
+ measurement: bindings.find(
100
+ (binding) => binding.measurementId === reported.id,
101
+ )?.measurement ?? reported,
102
+ }))
103
+ : bindings;
104
+ const targets = verificationBindings.map(({ measurementId, reported, measurement }) => {
105
+ const resolvedTolerance = resolveVerificationTolerance({
106
+ unit: reported.unit,
107
+ tolerance: measurement.verificationTolerance ?? reported.verificationTolerance ?? tolerance,
108
+ });
109
+ return {
110
+ measurementId,
111
+ metric: reported.metric,
112
+ unit: reported.unit,
113
+ reportedValue: reported.value,
114
+ comparator: "absolute-difference",
115
+ tolerance: resolvedTolerance.value,
116
+ toleranceSource: resolvedTolerance.source,
117
+ evidenceBasis: reported.evidenceBasis ?? "reported_numeric",
118
+ ...(reported.extractionUncertainty !== undefined
119
+ ? { extractionUncertainty: reported.extractionUncertainty }
120
+ : {}),
121
+ };
122
+ });
123
+ const committedPolicy = { claimVersionId: claim.versionId, targets };
124
+ const commitmentNonce = randomBytes(16).toString("base64url");
125
+ const verificationPolicyCommitment = capPolicyCommitment({
126
+ nonce: commitmentNonce,
127
+ policy: committedPolicy,
128
+ });
129
+ const reconstructionFidelity = experiment.reconstructionFidelity
130
+ ?? (experiment.implementationOrigin === "citeark_reconstruction" ? "approximate" : "faithful");
131
+ const executionBudget = fidelityExecutionBudget(reconstructionFidelity, experiment);
132
+
133
+ const researchGraph = executionGraphContext(research, experiment);
134
+ const contractBody = {
135
+ ...(researchGraph ? { researchGraph } : {}),
136
+ schemaVersion: EXECUTION_CONTRACT_VERSION,
137
+ kind: "citeark.execution-contract",
138
+ name: `${research.work.id}-${claim.id}-${experiment.id}`,
139
+ research: {
140
+ workId: research.work.id,
141
+ claimId: claim.id,
142
+ claimVersionId: claim.versionId,
143
+ experimentId: experiment.id,
144
+ experimentVersionId: experiment.versionId,
145
+ measurementIds,
146
+ claimMeasurementIds,
147
+ ...(restrictToAssignedMeasurements ? { assignedMeasurementsOnly: true } : {}),
148
+ ...(requestedExecutionClaimIds.length > 1
149
+ ? { executionClaimIds: requestedExecutionClaimIds }
150
+ : {}),
151
+ claimCoverage: uncoveredMeasurementIds.length ? "partial" : "complete",
152
+ ...(uncoveredMeasurementIds.length ? { uncoveredMeasurementIds } : {}),
153
+ },
154
+ paper: { title: research.work.title, url: paper?.uri },
155
+ repository: experiment.repository,
156
+ claim: {
157
+ text: research.sourceInventory
158
+ ? `Evaluate the assigned source question ${claim.id}. Source context is not execution evidence; the private numeric verification policy is withheld until independent verification.`
159
+ : `Evaluate the protocol-bound claim ${claim.id}; the paper's reported outcome is intentionally withheld until independent verification.`,
160
+ source: `${claim.sourceLocator.sourceId}:${claim.sourceLocator.locator}`,
161
+ },
162
+ measurements: executionBindings.map(({ measurementId, reported, measurement }) => ({
163
+ measurementId,
164
+ metric: measurement.metric,
165
+ ...(measurement.quantityKind ? { quantityKind: measurement.quantityKind } : {}),
166
+ unit: measurement.unit,
167
+ ...blindMeasurementScope(reported, measurement),
168
+ parser: measurement.parser,
169
+ })),
170
+ protocol: experiment.protocol,
171
+ ...(editablePlan ? { workspacePlan: {
172
+ version: 2,
173
+ ...(research.sourceInventory ? { sourceInventory: executionInventoryContext(research.sourceInventory) } : {}),
174
+ measurements: executionGoalBindings.map(({ measurementId, reported, measurement }) => ({
175
+ measurementId, metric: reported.metric, unit: reported.unit,
176
+ ...blindMeasurementScope(reported, measurement),
177
+ source: reported.sourceLocator ?? claim.sourceLocator,
178
+ })),
179
+ } } : {}),
180
+ reproductionLevel: experiment.reproductionLevel,
181
+ ...(research.reproductionScope ? { reproductionScope: research.reproductionScope, requiredReproductionScope: experiment.requiredReproductionScope } : {}),
182
+ reconstructionFidelity,
183
+ computeRequirement: resolveComputeRequirement(experiment),
184
+ verificationPolicyCommitment,
185
+ agent: {
186
+ runtime: experiment.agent?.runtime ?? null,
187
+ model: experiment.agent?.model ?? null,
188
+ subagentModel: experiment.agent?.subagentModel ?? null,
189
+ autoCompactWindow: experiment.agent?.autoCompactWindow ?? 786432,
190
+ effort: experiment.agent?.effort ?? "max",
191
+ reasoningMode: experiment.agent?.reasoningMode ?? "standard",
192
+ maxTurns: executionBudget.maxTurns,
193
+ maxBudgetUsd: executionBudget.maxBudgetUsd,
194
+ ...(executionBudget.hardCaps ? {
195
+ budgetCaps: {
196
+ maxTurns: executionBudget.maxTurns,
197
+ maxBudgetUsd: executionBudget.maxBudgetUsd,
198
+ },
199
+ } : {}),
200
+ validationRetries: experiment.agent?.validationRetries ?? 1,
201
+ },
202
+ environment: {
203
+ image: experiment.environment?.image ?? "citeark-agent/runtime:0.2.0",
204
+ timeoutMinutes: executionBudget.timeoutMinutes,
205
+ cpus: experiment.environment?.cpus ?? 8,
206
+ memoryGb: experiment.environment?.memoryGb ?? 32,
207
+ shmGb: experiment.environment?.shmGb ?? 8,
208
+ gpu: experiment.environment?.gpu ?? "none",
209
+ networkAccess: true,
210
+ },
211
+ };
212
+ const contract = { ...contractBody, contractDigest: `sha256:${sha256Value(contractBody)}` };
213
+ const policyBody = {
214
+ schemaVersion: VERIFICATION_POLICY_VERSION,
215
+ kind: "citeark.private-verification-policy",
216
+ contractDigest: contract.contractDigest,
217
+ claimVersionId: claim.versionId,
218
+ targets,
219
+ commitment: {
220
+ algorithm: "citeark-policy-commitment-v1",
221
+ nonce: commitmentNonce,
222
+ digest: verificationPolicyCommitment,
223
+ },
224
+ };
225
+ const policy = { ...policyBody, policyDigest: `sha256:${sha256Value(policyBody)}` };
226
+ // Contract construction is a deterministic boundary. Validate before either
227
+ // file is published so schema drift can never masquerade as an Agent start.
228
+ validateExecutionContract(contract);
229
+ if (outputPath) await writeJson(outputPath, contract);
230
+ if (policyPath) await writeJson(policyPath, policy);
231
+ return { research, contract, policy };
232
+ }
233
+
234
+ export function fidelityExecutionBudget(fidelity, experiment = {}) {
235
+ const requested = {
236
+ maxTurns: positiveNumber(experiment.agent?.maxTurns, 120),
237
+ maxBudgetUsd: positiveNumber(experiment.agent?.maxBudgetUsd, 10),
238
+ timeoutMinutes: positiveNumber(
239
+ experiment.environment?.timeoutMinutes,
240
+ LEGACY_EXECUTION_TIMEOUT_FALLBACK_MINUTES,
241
+ ),
242
+ };
243
+ const caps = fidelity === "proxy"
244
+ ? { maxTurns: 30, maxBudgetUsd: 2, timeoutMinutes: executionTimeoutCapMinutes("proxy") }
245
+ : fidelity === "approximate"
246
+ ? { maxTurns: 80, maxBudgetUsd: 6, timeoutMinutes: executionTimeoutCapMinutes("approximate") }
247
+ : { ...requested, timeoutMinutes: executionTimeoutCapMinutes(fidelity) };
248
+ return {
249
+ maxTurns: Math.max(1, Math.floor(Math.min(requested.maxTurns, caps.maxTurns))),
250
+ maxBudgetUsd: Math.min(requested.maxBudgetUsd, caps.maxBudgetUsd),
251
+ timeoutMinutes: Math.min(requested.timeoutMinutes, caps.timeoutMinutes),
252
+ hardCaps: fidelity === "proxy" || fidelity === "approximate",
253
+ };
254
+ }
255
+
256
+ function positiveNumber(value, fallback) {
257
+ const numeric = Number(value);
258
+ return Number.isFinite(numeric) && numeric > 0 ? numeric : fallback;
259
+ }
260
+
261
+ export async function loadExecutionContract(target) {
262
+ const contract = await readJson(target);
263
+ validateExecutionContract(contract);
264
+ return contract;
265
+ }
266
+
267
+ export async function loadVerificationPolicy(target, contract) {
268
+ const policy = await readJson(target);
269
+ const issues = [];
270
+ if (policy.schemaVersion !== VERIFICATION_POLICY_VERSION) issues.push(`policy.schemaVersion 必须是 ${VERIFICATION_POLICY_VERSION}`);
271
+ if (policy.kind !== "citeark.private-verification-policy") issues.push("policy.kind 无效");
272
+ if (policy.contractDigest !== contract.contractDigest) issues.push("policy 与 execution contract 未绑定");
273
+ if (policy.claimVersionId !== contract.research.claimVersionId) issues.push("policy 与 claim version 未绑定");
274
+ const contractMeasurements = [1, 2].includes(contract.workspacePlan?.version)
275
+ ? contract.workspacePlan.measurements : executionContractMeasurements(contract);
276
+ const targets = verificationPolicyTargets(policy);
277
+ if (!targets.length) issues.push("policy.targets 必须至少包含一个测量目标");
278
+ for (const [index, target] of targets.entries()) {
279
+ const measurement = contractMeasurements.find((item) => item.measurementId === target.measurementId);
280
+ if (!measurement || target.metric !== measurement.metric || target.unit !== measurement.unit) {
281
+ issues.push(`policy.targets[${index}] 与 contract measurement 不一致`);
282
+ }
283
+ if (typeof target.reportedValue !== "number" || !Number.isFinite(target.reportedValue)) issues.push(`policy.targets[${index}].reportedValue 无效`);
284
+ if (target.comparator !== "absolute-difference") issues.push(`policy.targets[${index}] 目前只支持 absolute-difference`);
285
+ if (typeof target.tolerance !== "number" || target.tolerance < 0) issues.push(`policy.targets[${index}].tolerance 无效`);
286
+ if (target.toleranceSource !== undefined) {
287
+ if (!target.toleranceSource || typeof target.toleranceSource !== "object") issues.push(`policy.targets[${index}].toleranceSource 无效`);
288
+ else if (!new Set(["explicit", "unit-default", "unconfigured"]).has(target.toleranceSource.kind)) issues.push(`policy.targets[${index}].toleranceSource.kind 无效`);
289
+ else {
290
+ if (target.toleranceSource.unit !== target.unit) issues.push(`policy.targets[${index}].toleranceSource.unit 与目标单位不一致`);
291
+ if (target.toleranceSource.kind === "unconfigured") {
292
+ if (target.tolerance !== 0) issues.push(`policy.targets[${index}] 未配置容差时 tolerance 必须为 0`);
293
+ if (typeof target.toleranceSource.reason !== "string" || !target.toleranceSource.reason.trim()) {
294
+ issues.push(`policy.targets[${index}] 未配置容差时必须记录原因`);
295
+ }
296
+ }
297
+ }
298
+ }
299
+ if (
300
+ target.evidenceBasis !== undefined
301
+ && !new Set(["reported_numeric", "digitized_figure", "derived_reported_relation"])
302
+ .has(target.evidenceBasis)
303
+ ) {
304
+ issues.push(`policy.targets[${index}].evidenceBasis 无效`);
305
+ }
306
+ if (
307
+ target.extractionUncertainty !== undefined
308
+ && (!Number.isFinite(target.extractionUncertainty) || target.extractionUncertainty < 0)
309
+ ) {
310
+ issues.push(`policy.targets[${index}].extractionUncertainty 无效`);
311
+ }
312
+ }
313
+ const committedPolicy = verificationPolicyCommitmentInput(policy);
314
+ const commitment = policy.commitment;
315
+ if (commitment?.algorithm !== "citeark-policy-commitment-v1") {
316
+ issues.push("policy.commitment.algorithm 无效");
317
+ } else {
318
+ try {
319
+ const observed = capPolicyCommitment({
320
+ nonce: commitment.nonce,
321
+ policy: committedPolicy,
322
+ });
323
+ if (commitment.digest !== observed) issues.push("policy.commitment.digest 与策略内容不一致");
324
+ if (contract.verificationPolicyCommitment !== observed) issues.push("policy commitment 与 execution contract 不一致");
325
+ } catch (error) {
326
+ issues.push(`policy.commitment 无效:${error instanceof Error ? error.message : String(error)}`);
327
+ }
328
+ }
329
+ const policyBody = { ...policy };
330
+ delete policyBody.policyDigest;
331
+ if (policy.policyDigest !== `sha256:${sha256Value(policyBody)}`) issues.push("policyDigest 与内容不一致");
332
+ if (issues.length) throw new CiteArkError(`私有验证策略校验失败:\n- ${issues.join("\n- ")}`);
333
+ return policy;
334
+ }
335
+
336
+ export function validateExecutionContract(contract) {
337
+ const issues = reproductionScopeIssues({ reproductionScope: contract?.reproductionScope, experiments: contract ? [contract] : [] });
338
+ if (contract?.campaign !== undefined) {
339
+ const campaign = contract.campaign;
340
+ if (campaign?.version !== 1 || !Array.isArray(campaign.members) || !campaign.members.length) {
341
+ issues.push("campaign 必须包含启动前绑定的实验目标");
342
+ } else {
343
+ const targets = new Set(), mapped = new Set();
344
+ for (const member of campaign.members) {
345
+ if (!member?.contract || member.contract.campaign) { issues.push("campaign 成员契约无效"); continue; }
346
+ try { validateExecutionContract(member.contract); } catch (error) { issues.push(error.message); }
347
+ const { experimentId, claimId } = member.contract.research ?? {};
348
+ const key = `${experimentId}:${claimId}`;
349
+ if (targets.has(key) || member.target?.experimentId !== experimentId || member.target?.claimId !== claimId) issues.push("campaign 目标绑定不一致或重复");
350
+ targets.add(key);
351
+ const ids = (member.contract.measurements ?? []).map(m => m.measurementId);
352
+ if (Object.keys(member.measurementMap ?? {}).length !== ids.length) issues.push("campaign 测量映射数量不一致");
353
+ for (const id of ids) {
354
+ const namespaced = `${experimentId}::${id}`;
355
+ if (member.measurementMap?.[id] !== namespaced) issues.push("campaign 测量必须绑定原实验身份");
356
+ mapped.add(namespaced);
357
+ }
358
+ }
359
+ const ids = (contract.measurements ?? []).map(m => m.measurementId);
360
+ if (mapped.size !== ids.length || ids.some(id => !mapped.has(id))) issues.push("campaign 的测量与成员范围不一致");
361
+ if (campaign.workloads !== undefined) {
362
+ const experiments = new Set(campaign.members.map(m => m.target?.experimentId));
363
+ const workIds = new Set();
364
+ if (!Array.isArray(campaign.workloads)) issues.push("campaign.workloads 必须是数组");
365
+ else for (const work of campaign.workloads) {
366
+ if (typeof work?.id !== "string" || !work.id || workIds.has(work.id)
367
+ || typeof work.description !== "string" || !work.description
368
+ || !Array.isArray(work.experimentIds) || !work.experimentIds.length
369
+ || work.experimentIds.some(id => !experiments.has(id))
370
+ || new Set(work.experimentIds).size !== work.experimentIds.length) {
371
+ issues.push("campaign.workloads 计算身份或使用实验无效");
372
+ }
373
+ workIds.add(work?.id);
374
+ }
375
+ }
376
+ }
377
+ }
378
+ if (contract?.schemaVersion !== EXECUTION_CONTRACT_VERSION) issues.push(`contract.schemaVersion 必须是 ${EXECUTION_CONTRACT_VERSION}`);
379
+ if (contract?.kind !== "citeark.execution-contract") issues.push("contract.kind 无效");
380
+ for (const path of [
381
+ [contract?.research?.workId, "research.workId"],
382
+ [contract?.research?.claimVersionId, "research.claimVersionId"],
383
+ [contract?.research?.experimentVersionId, "research.experimentVersionId"],
384
+ [contract?.repository?.url, "repository.url"],
385
+ [contract?.repository?.commit, "repository.commit"],
386
+ ]) {
387
+ if (typeof path[0] !== "string" || !path[0]) issues.push(`${path[1]} 必须是非空字符串`);
388
+ }
389
+ if (
390
+ contract?.repository?.implementationOrigin !== undefined
391
+ && !new Set(["official", "citeark_reconstruction"]).has(contract.repository.implementationOrigin)
392
+ ) {
393
+ issues.push("repository.implementationOrigin 无效");
394
+ }
395
+ if (
396
+ contract?.reconstructionFidelity !== undefined
397
+ && !new Set(["exact", "faithful", "approximate", "proxy"]).has(contract.reconstructionFidelity)
398
+ ) {
399
+ issues.push("reconstructionFidelity 无效");
400
+ }
401
+ validateContractPreflight(contract?.protocol?.preflight, issues);
402
+ if (
403
+ contract?.protocol?.executionMode !== undefined
404
+ && !EXECUTION_MODES.has(contract.protocol.executionMode)
405
+ ) {
406
+ issues.push("protocol.executionMode 无效");
407
+ }
408
+ if (
409
+ contract?.protocol?.executionMode === "agent_planned"
410
+ && (typeof contract.protocol.objective !== "string" || !contract.protocol.objective.trim())
411
+ ) {
412
+ issues.push("protocol.executionMode=agent_planned 时 protocol.objective 必须是非空字符串");
413
+ }
414
+ const measurements = executionContractMeasurements(contract);
415
+ if (!measurements.length) issues.push("contract.measurements 必须至少包含一个测量项");
416
+ const measurementIds = new Set();
417
+ for (const [index, measurement] of measurements.entries()) {
418
+ for (const [value, label] of [
419
+ [measurement.measurementId, "measurementId"],
420
+ [measurement.metric, "metric"],
421
+ [measurement.unit, "unit"],
422
+ [measurement.parser?.id, "parser.id"],
423
+ [measurement.parser?.version, "parser.version"],
424
+ [measurement.parser?.evidencePath, "parser.evidencePath"],
425
+ ]) {
426
+ if (typeof value !== "string" || !value) issues.push(`measurements[${index}].${label} 必须是非空字符串`);
427
+ }
428
+ if (
429
+ typeof measurement.parser?.evidencePath === "string"
430
+ && measurement.parser.evidencePath
431
+ && !safeRelativePath(measurement.parser.evidencePath)
432
+ ) {
433
+ issues.push(`measurements[${index}].parser.evidencePath 必须是安全相对路径`);
434
+ }
435
+ if (
436
+ measurement.dimensions !== undefined
437
+ && (
438
+ !measurement.dimensions
439
+ || typeof measurement.dimensions !== "object"
440
+ || Array.isArray(measurement.dimensions)
441
+ )
442
+ ) {
443
+ issues.push(`measurements[${index}].dimensions 必须是对象`);
444
+ }
445
+ if (
446
+ measurement.aggregation !== undefined
447
+ && (typeof measurement.aggregation !== "string" || !measurement.aggregation.trim())
448
+ ) {
449
+ issues.push(`measurements[${index}].aggregation 必须是非空字符串`);
450
+ }
451
+ if (measurementIds.has(measurement.measurementId)) issues.push(`contract measurementId 重复:${measurement.measurementId}`);
452
+ measurementIds.add(measurement.measurementId);
453
+ if (Object.hasOwn(measurement, "reportedValue")) issues.push("公开 contract 不得包含 reportedValue");
454
+ }
455
+ const declaredIds = Array.isArray(contract?.research?.measurementIds) ? contract.research.measurementIds : [];
456
+ if (declaredIds.length !== measurements.length || declaredIds.some((id) => !measurementIds.has(id))) {
457
+ issues.push("research.measurementIds 与 contract.measurements 不一致");
458
+ }
459
+ const claimMeasurementIds = Array.isArray(contract?.research?.claimMeasurementIds)
460
+ ? contract.research.claimMeasurementIds
461
+ : declaredIds;
462
+ const executionClaimIds = contract?.research?.executionClaimIds;
463
+ if (executionClaimIds !== undefined && (
464
+ !Array.isArray(executionClaimIds)
465
+ || executionClaimIds.length < 2
466
+ || new Set(executionClaimIds).size !== executionClaimIds.length
467
+ || executionClaimIds.some((id) => typeof id !== "string" || !id)
468
+ || !executionClaimIds.includes(contract.research.claimId)
469
+ )) {
470
+ issues.push("research.executionClaimIds 必须是包含当前 claim 的唯一非空字符串数组");
471
+ }
472
+ if (contract.workspacePlan !== undefined) {
473
+ const context = contract.workspacePlan.sourceInventory;
474
+ if (context !== undefined) {
475
+ if (context?.view !== "source-context" || !/^sha256:[0-9a-f]{64}$/.test(context?.digest ?? "")
476
+ || !Array.isArray(context.sources) || !Array.isArray(context.claims)
477
+ || !context.claims.some((claim) => claim?.id === contract.research.claimId)) {
478
+ issues.push("workspacePlan source inventory must identify the original context and assigned claim");
479
+ }
480
+ for (const claim of Array.isArray(context?.claims) ? context.claims : []) {
481
+ if (!Array.isArray(claim?.reportedMeasurements)
482
+ || claim.reportedMeasurements.some((measurement) => !measurement || typeof measurement !== "object"
483
+ || ["value", "reportedValue", "verificationTolerance"].some((key) => Object.hasOwn(measurement, key))))
484
+ issues.push("workspacePlan source context must omit structured reported targets and tolerances");
485
+ }
486
+ }
487
+ const goals = contract.workspacePlan?.measurements;
488
+ const allowedGoalIds = contract.reproductionScope || contract.research.assignedMeasurementsOnly ? declaredIds : claimMeasurementIds;
489
+ if (![1, 2].includes(contract.workspacePlan?.version) || contract.protocol?.executionMode !== "agent_planned"
490
+ || !Array.isArray(goals) || goals.length !== allowedGoalIds.length
491
+ || new Set((Array.isArray(goals) ? goals : []).map((item) => item?.measurementId)).size !== allowedGoalIds.length) {
492
+ issues.push("workspacePlan must enable version 1 or 2 for agent_planned with the allowed measurement catalog");
493
+ }
494
+ for (const goal of Array.isArray(goals) ? goals : []) {
495
+ if (!allowedGoalIds.includes(goal.measurementId) || !goal.metric || !goal.unit
496
+ || Object.hasOwn(goal, "value") || Object.hasOwn(goal, "reportedValue")) {
497
+ issues.push("workspacePlan measurements must retain claim identity and omit reported values");
498
+ }
499
+ }
500
+ }
501
+ const uncoveredMeasurementIds = Array.isArray(contract?.research?.uncoveredMeasurementIds)
502
+ ? contract.research.uncoveredMeasurementIds
503
+ : [];
504
+ if (new Set(claimMeasurementIds).size !== claimMeasurementIds.length || claimMeasurementIds.some((id) => typeof id !== "string" || !id)) {
505
+ issues.push("research.claimMeasurementIds 必须是唯一非空字符串数组");
506
+ }
507
+ if (declaredIds.some((id) => !claimMeasurementIds.includes(id))) {
508
+ issues.push("research.claimMeasurementIds 必须包含所有 research.measurementIds");
509
+ }
510
+ if (new Set(uncoveredMeasurementIds).size !== uncoveredMeasurementIds.length || uncoveredMeasurementIds.some((id) => !claimMeasurementIds.includes(id) || declaredIds.includes(id))) {
511
+ issues.push("research.uncoveredMeasurementIds 必须是 claim 中未执行的唯一 measurementId");
512
+ }
513
+ if (
514
+ contract?.research?.claimCoverage !== undefined
515
+ && !new Set(["complete", "partial"]).has(contract.research.claimCoverage)
516
+ ) {
517
+ issues.push("research.claimCoverage 无效");
518
+ }
519
+ if (contract?.research?.claimCoverage === "partial" && uncoveredMeasurementIds.length === 0) {
520
+ issues.push("research.claimCoverage=partial 时必须声明 uncoveredMeasurementIds");
521
+ }
522
+ if (contract?.research?.claimCoverage === "complete" && uncoveredMeasurementIds.length > 0) {
523
+ issues.push("research.claimCoverage=complete 时不能声明 uncoveredMeasurementIds");
524
+ }
525
+ issues.push(...publicContractPolicyIssues(researchPublicContract({
526
+ ...contract, compute: contract?.computeRequirement,
527
+ })).map((issue) => `publicContract: ${issue}`));
528
+ if (Object.hasOwn(contract ?? {}, "target")) issues.push("公开 contract 不得包含 target");
529
+ if (!/^sha256:[0-9a-f]{64}$/.test(contract?.verificationPolicyCommitment ?? "")) {
530
+ issues.push("contract.verificationPolicyCommitment 必须是有效 SHA-256 摘要");
531
+ }
532
+ if (
533
+ contract?.agent?.runtime !== null &&
534
+ contract?.agent?.runtime !== undefined &&
535
+ !new Set(["claude-code", "codex", "opencode"]).has(contract.agent.runtime)
536
+ ) {
537
+ issues.push("agent.runtime 必须是 claude-code、codex、opencode 或 null");
538
+ }
539
+ if (
540
+ contract?.agent?.reasoningMode !== undefined &&
541
+ !new Set(["standard", "pro"]).has(contract.agent.reasoningMode)
542
+ ) {
543
+ issues.push("agent.reasoningMode 必须是 standard 或 pro");
544
+ }
545
+ if (contract?.agent?.reasoningMode === "pro" && contract?.agent?.runtime === "claude-code") {
546
+ issues.push("agent.reasoningMode=pro 仅支持 Codex 和 OpenCode");
547
+ }
548
+ if (contract?.agent?.budgetCaps !== undefined) {
549
+ if (
550
+ !Number.isInteger(contract.agent.budgetCaps?.maxTurns)
551
+ || contract.agent.budgetCaps.maxTurns < 1
552
+ ) {
553
+ issues.push("agent.budgetCaps.maxTurns 必须是正整数");
554
+ }
555
+ if (!isFinitePositive(contract.agent.budgetCaps?.maxBudgetUsd)) {
556
+ issues.push("agent.budgetCaps.maxBudgetUsd 必须大于 0");
557
+ }
558
+ if (contract.agent.maxTurns > contract.agent.budgetCaps.maxTurns) {
559
+ issues.push("agent.maxTurns 不能超过 agent.budgetCaps.maxTurns");
560
+ }
561
+ if (contract.agent.maxBudgetUsd > contract.agent.budgetCaps.maxBudgetUsd) {
562
+ issues.push("agent.maxBudgetUsd 不能超过 agent.budgetCaps.maxBudgetUsd");
563
+ }
564
+ }
565
+ if (!isFinitePositive(contract?.environment?.timeoutMinutes)) issues.push("environment.timeoutMinutes 必须大于 0");
566
+ issues.push(...executionWorkloadIssues(contract?.protocol?.workload));
567
+ issues.push(...measurementHardwareIssues({ ...contract, compute: contract.computeRequirement }));
568
+ if (contract?.environment?.networkAccess !== true) issues.push("当前运行器要求 environment.networkAccess=true");
569
+ if (contract?.computeRequirement) {
570
+ issues.push(...validateDeclaredComputeRequirement(contract.computeRequirement, "computeRequirement"));
571
+ if (!new Set(["research-declared", "research-derived"]).has(contract.computeRequirement.source)) issues.push("computeRequirement.source 无效");
572
+ if (!new Set(["cpu-only", "gpu-optional", "gpu-required"]).has(contract.computeRequirement.classification)) issues.push("computeRequirement.classification 无效");
573
+ }
574
+ const body = { ...contract };
575
+ delete body.contractDigest;
576
+ const expected = `sha256:${sha256Value(body)}`;
577
+ if (contract?.contractDigest !== expected) issues.push("contractDigest 与内容不一致");
578
+ if (issues.length) throw new CiteArkError(`执行契约校验失败:\n- ${issues.join("\n- ")}`);
579
+ return contract;
580
+ }
581
+
582
+ function validateContractPreflight(value, issues) {
583
+ // Contracts from Research Plan records created before this gate remain valid.
584
+ if (value === undefined) return;
585
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
586
+ issues.push("protocol.preflight 必须是对象");
587
+ return;
588
+ }
589
+ if (!supportsPreflightSchemaVersion(value.schemaVersion)) {
590
+ issues.push(`protocol.preflight.schemaVersion 必须是 ${describeSupportedPreflightSchemaVersions()}`);
591
+ }
592
+ if (!new Set(["declared", "defaulted"]).has(value.source)) issues.push("protocol.preflight.source 无效");
593
+ if (!Number.isFinite(value.maxMinutes) || value.maxMinutes < 1 || value.maxMinutes > MAX_PREFLIGHT_MINUTES) {
594
+ issues.push(`protocol.preflight.maxMinutes 必须在 1 到 ${MAX_PREFLIGHT_MINUTES} 之间`);
595
+ }
596
+ if (!safeRelativePath(value.evidencePath)) issues.push("protocol.preflight.evidencePath 必须是安全相对路径");
597
+ if (!Array.isArray(value.checks) || value.checks.length === 0) {
598
+ issues.push("protocol.preflight.checks 必须至少包含一项");
599
+ } else {
600
+ const ids = new Set();
601
+ for (const [index, check] of value.checks.entries()) {
602
+ if (!check || typeof check !== "object" || Array.isArray(check)) {
603
+ issues.push(`protocol.preflight.checks[${index}] 必须是对象`);
604
+ continue;
605
+ }
606
+ if (typeof check.id !== "string" || !check.id.trim()) issues.push(`protocol.preflight.checks[${index}].id 无效`);
607
+ if (ids.has(check.id)) issues.push(`protocol.preflight.checks[${index}].id 重复`);
608
+ ids.add(check.id);
609
+ if (!PREFLIGHT_CHECK_KINDS.has(check.kind)) issues.push(`protocol.preflight.checks[${index}].kind 无效`);
610
+ if (typeof check.successCriterion !== "string" || !check.successCriterion.trim()) {
611
+ issues.push(`protocol.preflight.checks[${index}].successCriterion 无效`);
612
+ }
613
+ }
614
+ }
615
+ if (!Array.isArray(value.assets)) {
616
+ issues.push("protocol.preflight.assets 必须是数组");
617
+ } else {
618
+ const ids = new Set();
619
+ for (const [index, asset] of value.assets.entries()) {
620
+ if (!asset || typeof asset !== "object" || Array.isArray(asset)) {
621
+ issues.push(`protocol.preflight.assets[${index}] 必须是对象`);
622
+ continue;
623
+ }
624
+ if (typeof asset.id !== "string" || !asset.id.trim()) issues.push(`protocol.preflight.assets[${index}].id 无效`);
625
+ if (ids.has(asset.id)) issues.push(`protocol.preflight.assets[${index}].id 重复`);
626
+ ids.add(asset.id);
627
+ if (asset.scientificRole !== undefined && !ASSET_SCIENTIFIC_ROLES.has(asset.scientificRole)) {
628
+ issues.push(`protocol.preflight.assets[${index}].scientificRole 无效`);
629
+ }
630
+ validateContractAssetAcquisition(asset.acquisition, index, issues);
631
+ validateContractAssetSourceEvidence(asset.sourceEvidence, index, issues);
632
+ const sourceUrls = asset.sourceUrls === undefined ? [] : asset.sourceUrls;
633
+ if (!Array.isArray(sourceUrls)) {
634
+ issues.push(`protocol.preflight.assets[${index}].sourceUrls 必须是数组`);
635
+ } else {
636
+ for (const [sourceIndex, sourceUrl] of sourceUrls.entries()) {
637
+ if (!validPublicAssetSourceUrl(sourceUrl)) {
638
+ issues.push(`protocol.preflight.assets[${index}].sourceUrls[${sourceIndex}] 必须是无凭据的 HTTP(S) URL`);
639
+ }
640
+ }
641
+ }
642
+ if (asset.registryDatasetId !== undefined && (typeof asset.registryDatasetId !== "string" || !asset.registryDatasetId.trim())) {
643
+ issues.push(`protocol.preflight.assets[${index}].registryDatasetId 无效`);
644
+ }
645
+ if (
646
+ asset.required === true
647
+ && asset.availability === "public_unverified"
648
+ && (!Array.isArray(sourceUrls) || sourceUrls.length === 0)
649
+ && !(typeof asset.registryDatasetId === "string" && asset.registryDatasetId.trim())
650
+ ) {
651
+ issues.push(`protocol.preflight.assets[${index}] 是必需的 public_unverified 资产,必须声明 sourceUrls 或 registryDatasetId`);
652
+ }
653
+ }
654
+ if (
655
+ value.assets.some((asset) =>
656
+ asset && typeof asset === "object" && asset.required === true && asset.availability === "public_unverified",
657
+ )
658
+ && value.maxMinutes < MIN_PUBLIC_ASSET_PREFLIGHT_MINUTES
659
+ ) {
660
+ issues.push(`protocol.preflight.maxMinutes 对必需的 public_unverified 资产不得低于 ${MIN_PUBLIC_ASSET_PREFLIGHT_MINUTES}`);
661
+ }
662
+ }
663
+ }
664
+
665
+ function validateContractAssetAcquisition(value, assetIndex, issues) {
666
+ if (value === undefined) return;
667
+ const label = `protocol.preflight.assets[${assetIndex}].acquisition`;
668
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
669
+ issues.push(`${label} 必须是对象`);
670
+ return;
671
+ }
672
+ if (!ASSET_ACQUISITION_MODES.has(value.mode)) issues.push(`${label}.mode 无效`);
673
+ for (const field of ["requiredPaths", "excludedPaths"]) {
674
+ if (value[field] === undefined) continue;
675
+ if (!Array.isArray(value[field])) {
676
+ issues.push(`${label}.${field} 必须是数组`);
677
+ continue;
678
+ }
679
+ for (const [index, pattern] of value[field].entries()) {
680
+ if (!safeAssetPattern(pattern)) issues.push(`${label}.${field}[${index}] 不是安全相对路径或 glob`);
681
+ }
682
+ }
683
+ for (const field of ["expectedDownloadBytes", "expectedExpandedBytes"]) {
684
+ if (value[field] !== undefined && (!Number.isSafeInteger(value[field]) || value[field] < 0)) {
685
+ issues.push(`${label}.${field} 必须是非负安全整数`);
686
+ }
687
+ }
688
+ if (
689
+ value.maximumDownloadBytes !== undefined
690
+ && (!Number.isSafeInteger(value.maximumDownloadBytes) || value.maximumDownloadBytes < 1)
691
+ ) {
692
+ issues.push(`${label}.maximumDownloadBytes 必须是正安全整数`);
693
+ }
694
+ if (value.supportsStreaming !== undefined && typeof value.supportsStreaming !== "boolean") {
695
+ issues.push(`${label}.supportsStreaming 必须是布尔值`);
696
+ }
697
+ }
698
+
699
+ function validateContractAssetSourceEvidence(value, assetIndex, issues) {
700
+ if (value === undefined) return;
701
+ const label = `protocol.preflight.assets[${assetIndex}].sourceEvidence`;
702
+ if (!Array.isArray(value)) {
703
+ issues.push(`${label} 必须是数组`);
704
+ return;
705
+ }
706
+ for (const [index, entry] of value.entries()) {
707
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
708
+ issues.push(`${label}[${index}] 必须是对象`);
709
+ continue;
710
+ }
711
+ if (!new Set(["paper", "repository", "dataset_registry"]).has(entry.source)) {
712
+ issues.push(`${label}[${index}].source 无效`);
713
+ }
714
+ if (typeof entry.locator !== "string" || !entry.locator.trim()) {
715
+ issues.push(`${label}[${index}].locator 无效`);
716
+ }
717
+ if (entry.path !== undefined && !safeAssetPattern(entry.path)) {
718
+ issues.push(`${label}[${index}].path 无效`);
719
+ }
720
+ }
721
+ }
722
+
723
+ function safeAssetPattern(value) {
724
+ if (typeof value !== "string" || !value.trim()) return false;
725
+ const normalized = value.replaceAll("\\", "/");
726
+ return !normalized.startsWith("/")
727
+ && !normalized.split("/").includes("..")
728
+ && !/[\u0000-\u001f]/.test(normalized);
729
+ }
730
+
731
+ function validPublicAssetSourceUrl(value) {
732
+ if (typeof value !== "string" || !value.trim()) return false;
733
+ try {
734
+ const parsed = new URL(value);
735
+ return new Set(["http:", "https:"]).has(parsed.protocol)
736
+ && !parsed.username
737
+ && !parsed.password;
738
+ } catch {
739
+ return false;
740
+ }
741
+ }
742
+
743
+ export function executionContractMeasurements(contract) {
744
+ return Array.isArray(contract?.measurements) ? contract.measurements : [];
745
+ }
746
+
747
+ export function verificationPolicyTargets(policy) {
748
+ return Array.isArray(policy?.targets) ? policy.targets : [];
749
+ }
750
+
751
+ export function verificationPolicyCommitmentInput(policy) {
752
+ return {
753
+ claimVersionId: policy?.claimVersionId,
754
+ targets: verificationPolicyTargets(policy),
755
+ };
756
+ }
757
+
758
+ function resolveMeasurementBindings(research, claim, experiment) {
759
+ const reportedById = new Map(claim.reportedMeasurements.map((measurement) => [measurement.id, measurement]));
760
+ return experiment.measurements.flatMap((measurement) => {
761
+ const reported = reportedById.get(measurement.reportedMeasurementId);
762
+ return reported ? [{ measurementId: reported.id, reported, measurement }] : [];
763
+ });
764
+ }
765
+
766
+ function deduplicateMeasurementBindings(bindings) {
767
+ const output = [];
768
+ const seen = new Set();
769
+ for (const binding of bindings) {
770
+ if (seen.has(binding.measurementId)) continue;
771
+ seen.add(binding.measurementId);
772
+ output.push(binding);
773
+ }
774
+ return output;
775
+ }
776
+
777
+ /** The original inventory is retained in the research artifact. Execution gets
778
+ * source context and locators, without structured reported targets/tolerances.
779
+ * As with the original paper, source statements are not blinded observations.
780
+ */
781
+ export function executionInventoryContext(inventory) {
782
+ const pick = (record, keys) => Object.fromEntries(keys.filter((key) => record?.[key] !== undefined)
783
+ .map((key) => [key, structuredClone(record[key])]));
784
+ return {
785
+ digest: inventory.digest, view: "source-context",
786
+ sources: structuredClone(inventory.research.sources),
787
+ ...(inventory.revisions?.length ? { revisions: inventory.revisions.map(({ claimId, reason, sources }) => ({ claimId, reason, sources })) } : {}),
788
+ claims: revisedInventoryClaims(inventory.research, inventory.revisions).map((claim) => ({
789
+ ...pick(claim, ["id", "type", "statement", "description", "sourceLocator", "sourceLocators", "conditions", "relations", "ambiguities", "verificationHint"]),
790
+ reportedMeasurements: claim.reportedMeasurements.map((measurement) => pick(measurement,
791
+ ["id", "metric", "unit", "sourceLocator", "dimensions", "conditions", "aggregation", "observationId"])),
792
+ })),
793
+ };
794
+ }
795
+
796
+ function blindMeasurementScope(reported, measurement) {
797
+ const protocolDimensions = measurement?.dimensions && typeof measurement.dimensions === "object"
798
+ && !Array.isArray(measurement.dimensions)
799
+ ? measurement.dimensions
800
+ : {};
801
+ const reportedDimensions = reported?.dimensions && typeof reported.dimensions === "object"
802
+ && !Array.isArray(reported.dimensions)
803
+ ? reported.dimensions
804
+ : {};
805
+ const dimensions = { ...protocolDimensions, ...reportedDimensions };
806
+ const aggregation = typeof reported?.aggregation === "string" && reported.aggregation.trim()
807
+ ? reported.aggregation.trim()
808
+ : typeof measurement?.aggregation === "string" && measurement.aggregation.trim()
809
+ ? measurement.aggregation.trim()
810
+ : null;
811
+ return {
812
+ ...(Object.keys(dimensions).length ? { dimensions } : {}),
813
+ ...(aggregation ? { aggregation } : {}),
814
+ };
815
+ }
816
+
817
+ function isFinitePositive(value) {
818
+ return typeof value === "number" && Number.isFinite(value) && value > 0;
819
+ }