@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,130 @@
1
+ export function createAssetReadiness(plan, {
2
+ state = "planned",
3
+ manifest = null,
4
+ admission = null,
5
+ preparationMode = null,
6
+ cacheHit = false,
7
+ message = null,
8
+ } = {}) {
9
+ const records = new Map((Array.isArray(manifest?.assets) ? manifest.assets : [])
10
+ .map((record) => [record?.assetId, record]));
11
+ const assets = (Array.isArray(plan?.assets) ? plan.assets : []).slice(0, 32).map((asset) => {
12
+ const record = records.get(asset.id);
13
+ return {
14
+ id: asset.id,
15
+ kind: asset.kind,
16
+ identity: String(asset.identity ?? asset.id).slice(0, 240),
17
+ required: asset.required === true,
18
+ action: asset.decision?.action ?? "unknown",
19
+ status: record?.status ?? assetStatus(state, asset),
20
+ expectedDownloadBytes: asset.expectedDownloadBytes,
21
+ preparedBytes: record?.totalBytes ?? null,
22
+ sourceClass: asset.sourceLocators?.[0]?.sourceClass ?? asset.sourceLocators?.[0]?.source ?? null,
23
+ };
24
+ });
25
+ const admissionSummary = admission?.summary ?? admission;
26
+ const admittedCount = number(
27
+ admissionSummary?.admittedCount ?? admissionSummary?.admittedTargetCount,
28
+ );
29
+ const retryableCount = number(
30
+ admissionSummary?.transportRetryableAssetCount
31
+ ?? admissionSummary?.retryableCount
32
+ ?? admissionSummary?.retryableTargetCount,
33
+ );
34
+ const unavailableCount = number(
35
+ admissionSummary?.transportUnavailableAssetCount
36
+ ?? admissionSummary?.terminalCount
37
+ ?? admissionSummary?.blockedCount
38
+ ?? admissionSummary?.blockedTargetCount,
39
+ );
40
+ const reachableCount = number(
41
+ admissionSummary?.transportReachableAssetCount ?? admittedCount,
42
+ );
43
+ const transportStatus = unavailableCount > 0
44
+ ? "unavailable"
45
+ : retryableCount > 0
46
+ ? "retrying"
47
+ : new Set(["planned", "preparing"]).has(state) && !admissionSummary
48
+ ? "checking"
49
+ : "ready";
50
+ const scientificCompleteness = plan?.scientificCompleteness ?? {
51
+ status: "undetermined",
52
+ missingKinds: [],
53
+ missingRequirements: [],
54
+ invalidBindings: [],
55
+ summary: { total: 0, required: 0, covered: 0, notRequired: 0, missing: 0 },
56
+ };
57
+ const preparationStatus = state === "prepared"
58
+ ? "prepared"
59
+ : state === "blocked"
60
+ ? "blocked"
61
+ : state === "failed"
62
+ ? "failed"
63
+ : state === "preparing"
64
+ ? "preparing"
65
+ : state === "deferred"
66
+ ? "deferred"
67
+ : "pending";
68
+ const staticBlockedCount = number(admissionSummary?.blockedTargetCount);
69
+ const handoffAllowed = admissionSummary
70
+ ? staticBlockedCount === 0 && admittedCount > 0
71
+ : true;
72
+ const cacheUsable = preparationStatus === "prepared";
73
+ return {
74
+ schemaVersion: "0.1",
75
+ kind: "citeark.asset-readiness",
76
+ state,
77
+ transport: {
78
+ status: transportStatus,
79
+ reachable: reachableCount,
80
+ retryable: retryableCount,
81
+ unavailable: unavailableCount,
82
+ // Compatibility alias for existing public projections.
83
+ blocked: unavailableCount,
84
+ },
85
+ scientificCompleteness: {
86
+ status: scientificCompleteness.status,
87
+ missingKinds: scientificCompleteness.missingKinds ?? [],
88
+ missingRequirements: scientificCompleteness.missingRequirements ?? [],
89
+ invalidBindings: scientificCompleteness.invalidBindings ?? [],
90
+ summary: scientificCompleteness.summary,
91
+ requirements: (scientificCompleteness.items ?? []).map((item) => ({
92
+ kind: item.kind,
93
+ required: item.required,
94
+ status: item.status,
95
+ reason: item.reason,
96
+ })),
97
+ },
98
+ preparation: {
99
+ status: preparationStatus,
100
+ mode: cacheHit ? "cache" : preparationMode,
101
+ cacheHit,
102
+ completed: assets.filter((asset) => new Set(["prepared", "stream_ready", "metadata_verified"]).has(asset.status)).length,
103
+ total: assets.length,
104
+ assets,
105
+ },
106
+ agentHandoff: {
107
+ allowed: handoffAllowed,
108
+ cacheUsable,
109
+ fallbackRequired: handoffAllowed && !cacheUsable,
110
+ mode: cacheUsable ? "prepared_cache" : "autonomous_agent",
111
+ },
112
+ // "Ready" means the complete reproduction Agent may be started. It does
113
+ // not claim that optional prefetching or scientific validation succeeded.
114
+ reproductionReady: handoffAllowed,
115
+ ...(message ? { message: String(message).slice(0, 500) } : {}),
116
+ updatedAt: new Date().toISOString(),
117
+ };
118
+ }
119
+
120
+ function assetStatus(state, asset) {
121
+ if (state === "preparing") return "preparing";
122
+ if (state === "deferred") return "deferred";
123
+ if (state === "blocked" || state === "failed") return state;
124
+ if (asset?.decision?.action === "blocked") return "deferred";
125
+ return "pending";
126
+ }
127
+
128
+ function number(value) {
129
+ return Number.isSafeInteger(Number(value)) && Number(value) >= 0 ? Number(value) : 0;
130
+ }
@@ -0,0 +1,355 @@
1
+ import { lookup } from "node:dns/promises";
2
+
3
+ import { assetTransportCompatibilityIssues } from "./source-grounding.mjs";
4
+ import { automaticAdmissionByExperiment } from "../research/automatic-admission.mjs";
5
+ import { probePublicAssetUrl } from "./public-asset-probe.mjs";
6
+
7
+ export { probePublicAssetUrl } from "./public-asset-probe.mjs";
8
+
9
+ const DEFAULT_OBSERVATION_TTL_MS = 30 * 60_000;
10
+ const DEFAULT_EXTERNAL_RETRIES = 2;
11
+
12
+ export class ReproductionAdmissionProbeError extends Error {
13
+ constructor(message, admission, {
14
+ externalRetryCount = 0,
15
+ maxExternalRetries = DEFAULT_EXTERNAL_RETRIES,
16
+ } = {}) {
17
+ super(message);
18
+ this.name = "ReproductionAdmissionProbeError";
19
+ this.externalRetryCount = nonNegativeInteger(externalRetryCount);
20
+ this.maxExternalRetries = nonNegativeInteger(maxExternalRetries);
21
+ this.retryable = this.externalRetryCount < this.maxExternalRetries;
22
+ this.failureCategory = "admission";
23
+ this.failureCode = "admission.public_asset_probe_transient";
24
+ this.recoveryAction = this.retryable
25
+ ? "平台将在不消耗科研目标尝试额度的情况下重新探测公开资产"
26
+ : "公开资产在多轮平台探测后仍不可达;保留完整探测记录并停止自动重试";
27
+ this.admission = admission;
28
+ }
29
+ }
30
+
31
+ /**
32
+ * Split the static automatic-run decision from public transport observation.
33
+ *
34
+ * A target is admitted when its immutable Research Plan is eligible for an
35
+ * automatic run. Link probes only describe whether the optional prefetcher can
36
+ * save work before the autonomous reproduction Agent starts. A transient,
37
+ * unavailable, gated, or structurally surprising transport is handed to that
38
+ * Agent as an observation; it never becomes a scientific or execution verdict.
39
+ */
40
+ export async function assessAutomaticReproductionAdmission({
41
+ research,
42
+ targets,
43
+ datasetRegistry,
44
+ fetchImpl = fetch,
45
+ resolveHost = lookup,
46
+ timeoutMs = 12_000,
47
+ previousAdmission,
48
+ explicitlySelectedExperimentIds = [],
49
+ observationTtlMs = DEFAULT_OBSERVATION_TTL_MS,
50
+ probeMaxAttempts = 3,
51
+ retryBaseDelayMs,
52
+ retryMaxDelayMs,
53
+ sleepImpl,
54
+ randomImpl,
55
+ observe,
56
+ now = () => new Date(),
57
+ }) {
58
+ const experiments = new Map(
59
+ (Array.isArray(research?.experiments) ? research.experiments : [])
60
+ .map((experiment) => [experiment.id, experiment]),
61
+ );
62
+ const staticAdmissions = automaticAdmissionByExperiment(research);
63
+ const explicitlySelected = new Set(explicitlySelectedExperimentIds);
64
+ const registry = datasetRegistryMap(datasetRegistry);
65
+ const observations = new Map();
66
+ const cachedObservations = reusableObservations(previousAdmission, {
67
+ now: now(),
68
+ observationTtlMs,
69
+ });
70
+ const admittedTargets = [];
71
+ const blockedTargets = [];
72
+ const transportReports = [];
73
+
74
+ for (const target of Array.isArray(targets) ? targets : []) {
75
+ const experiment = experiments.get(target.experimentId);
76
+ const staticAdmission = staticAdmissions.get(target.experimentId);
77
+ const remainingReasons = (staticAdmission?.reasons ?? []).filter(
78
+ (reason) => !(
79
+ explicitlySelected.has(target.experimentId)
80
+ && isExplicitSelectionReason(reason.code)
81
+ ),
82
+ );
83
+ if (!experiment || (!staticAdmission?.automatic && remainingReasons.length > 0)) {
84
+ blockedTargets.push({
85
+ ...target,
86
+ state: "blocked",
87
+ reasonCode: "admission.static_ineligible",
88
+ reasons: remainingReasons.length ? remainingReasons : [{
89
+ code: "admission.experiment_missing",
90
+ message: "Research Plan 缺少目标实验",
91
+ }],
92
+ });
93
+ continue;
94
+ }
95
+
96
+ const assetResults = [];
97
+ for (const asset of staticAdmission.publicAssetsToProbe) {
98
+ const urls = publicAssetUrls(asset, registry);
99
+ const probes = [];
100
+ for (const url of urls) {
101
+ const key = String(url);
102
+ let observation = observations.get(key);
103
+ if (!observation) {
104
+ const cached = cachedObservations.get(key);
105
+ if (cached) {
106
+ observation = { ...cached, reused: true };
107
+ emitCachedObservation(observe, observation);
108
+ } else {
109
+ observation = await probePublicAssetUrl(key, {
110
+ fetchImpl,
111
+ resolveHost,
112
+ timeoutMs,
113
+ maxAttempts: probeMaxAttempts,
114
+ ...(retryBaseDelayMs === undefined ? {} : { retryBaseDelayMs }),
115
+ ...(retryMaxDelayMs === undefined ? {} : { retryMaxDelayMs }),
116
+ ...(sleepImpl === undefined ? {} : { sleepImpl }),
117
+ ...(randomImpl === undefined ? {} : { randomImpl }),
118
+ observe,
119
+ now,
120
+ });
121
+ }
122
+ observations.set(key, observation);
123
+ }
124
+ const preparationIssues = observation.status === "reachable"
125
+ ? [
126
+ ...assetTransportCompatibilityIssues(asset, key),
127
+ ...assetInventoryPreparationHints(asset, observation),
128
+ ]
129
+ : [];
130
+ probes.push({
131
+ ...observation,
132
+ ...(preparationIssues.length ? { preparationIssues } : {}),
133
+ });
134
+ if (observation.status === "reachable") break;
135
+ }
136
+ const reachable = probes.some((probe) => probe.status === "reachable");
137
+ const retryable = probes.some((probe) => probe.status === "retryable");
138
+ const preparationIssues = [...new Set(
139
+ probes.flatMap((probe) => probe.preparationIssues ?? []),
140
+ )];
141
+ assetResults.push({
142
+ assetId: asset.id,
143
+ identity: asset.identity,
144
+ status: reachable ? "reachable" : retryable ? "retryable" : "unavailable",
145
+ ...(preparationIssues.length ? {
146
+ preparationIssues,
147
+ } : {}),
148
+ probes,
149
+ });
150
+ }
151
+ const transportStatus = assetResults.some((asset) => asset.status === "unavailable")
152
+ ? "unavailable"
153
+ : assetResults.some((asset) => asset.status === "retryable")
154
+ ? "retryable"
155
+ : "reachable";
156
+ transportReports.push({
157
+ ...target,
158
+ status: transportStatus,
159
+ assets: assetResults,
160
+ handoff: transportStatus === "reachable"
161
+ ? "prefetch_candidate"
162
+ : "reproduction_agent_fallback",
163
+ });
164
+ admittedTargets.push(target);
165
+ }
166
+
167
+ const observationValues = [...observations.values()];
168
+ const transportAssetResults = transportReports.flatMap((report) => report.assets);
169
+ return {
170
+ schemaVersion: "0.1",
171
+ policy: "static-eligibility-plus-transport-observation/v2",
172
+ admittedTargets,
173
+ blockedTargets,
174
+ // Backward-compatible field: transport failures no longer schedule a new
175
+ // Processing Worker, so there are no retryable admission targets.
176
+ retryableTargets: [],
177
+ transportReports,
178
+ observations: observationValues,
179
+ summary: {
180
+ targetCount: Array.isArray(targets) ? targets.length : 0,
181
+ admittedTargetCount: admittedTargets.length,
182
+ blockedTargetCount: blockedTargets.length,
183
+ retryableTargetCount: 0,
184
+ transportReachableTargetCount: transportReports.filter(
185
+ (report) => report.status === "reachable",
186
+ ).length,
187
+ transportRetryableTargetCount: transportReports.filter(
188
+ (report) => report.status === "retryable",
189
+ ).length,
190
+ transportUnavailableTargetCount: transportReports.filter(
191
+ (report) => report.status === "unavailable",
192
+ ).length,
193
+ transportReachableAssetCount: transportAssetResults.filter(
194
+ (asset) => asset.status === "reachable",
195
+ ).length,
196
+ transportRetryableAssetCount: transportAssetResults.filter(
197
+ (asset) => asset.status === "retryable",
198
+ ).length,
199
+ transportUnavailableAssetCount: transportAssetResults.filter(
200
+ (asset) => asset.status === "unavailable",
201
+ ).length,
202
+ preparationIssueCount: transportAssetResults.reduce(
203
+ (sum, asset) => sum + (asset.preparationIssues?.length ?? 0),
204
+ 0,
205
+ ),
206
+ probedUrlCount: observationValues.length,
207
+ requestAttemptCount: observationValues.reduce(
208
+ (sum, item) => sum + nonNegativeInteger(item?.attemptCount),
209
+ 0,
210
+ ),
211
+ reusedObservationCount: observationValues.filter((item) => item?.reused === true).length,
212
+ },
213
+ assessedAt: now().toISOString(),
214
+ };
215
+ }
216
+
217
+ function isExplicitSelectionReason(code) {
218
+ return code === "admission.proxy_requires_selection"
219
+ || code === "admission.cross_hardware_requires_selection";
220
+ }
221
+
222
+ export function assertNoTransientAdmissionFailures(admission, options = {}) {
223
+ // Kept as a compatibility shim for callers outside the main pipeline.
224
+ // Public transport observations are advisory and must never create another
225
+ // Worker or consume a scientific target attempt.
226
+ void options;
227
+ return admission;
228
+ }
229
+
230
+ function reusableObservations(previousAdmission, { now, observationTtlMs }) {
231
+ const cutoff = now.getTime() - Math.max(0, Number(observationTtlMs) || 0);
232
+ return new Map(
233
+ (Array.isArray(previousAdmission?.observations) ? previousAdmission.observations : [])
234
+ .filter((observation) => observation?.status === "reachable")
235
+ .filter((observation) => {
236
+ const observedAt = Date.parse(observation?.observedAt ?? "");
237
+ return Number.isFinite(observedAt) && observedAt >= cutoff;
238
+ })
239
+ .filter((observation) => typeof observation?.url === "string")
240
+ .map((observation) => [observation.url, observation]),
241
+ );
242
+ }
243
+
244
+ function emitCachedObservation(observe, observation) {
245
+ if (typeof observe !== "function") return;
246
+ try {
247
+ observe({
248
+ schemaVersion: "1.0",
249
+ kind: "decision",
250
+ component: "public_asset_probe",
251
+ operation: "reuse_recent_success",
252
+ outcome: { status: "succeeded", retryable: false },
253
+ details: {
254
+ url: observation.url,
255
+ observedAt: observation.observedAt,
256
+ cachePolicy: "same-processing-job/30m",
257
+ },
258
+ });
259
+ } catch {
260
+ // Logging failure cannot invalidate a still-fresh successful observation.
261
+ }
262
+ }
263
+
264
+ function publicAssetUrls(asset, registry) {
265
+ const urls = Array.isArray(asset?.sourceUrls) ? [...asset.sourceUrls] : [];
266
+ const registryUrl = registry.get(asset?.registryDatasetId)?.candidateTransport?.url;
267
+ if (typeof registryUrl === "string" && !urls.includes(registryUrl)) urls.push(registryUrl);
268
+ return urls;
269
+ }
270
+
271
+ export function assetInventoryPreparationHints(asset, observation) {
272
+ const inventory = observation?.inventory;
273
+ if (inventory?.provider !== "hugging-face") return [];
274
+ const issues = [];
275
+ const acquisition = asset?.acquisition ?? {};
276
+ const files = Array.isArray(inventory.files) ? inventory.files : [];
277
+ const requiredPaths = Array.isArray(acquisition.requiredPaths)
278
+ ? acquisition.requiredPaths.filter((value) => typeof value === "string" && value)
279
+ : [];
280
+ const literalPaths = requiredPaths.filter((pattern) => !/[?*\[]/.test(pattern));
281
+ const patternPaths = requiredPaths.filter((pattern) => /[?*\[]/.test(pattern));
282
+
283
+ for (const requiredPath of literalPaths) {
284
+ if (!files.some((file) => file?.path === requiredPath)) {
285
+ issues.push(`Hugging Face 文件清单缺少必需路径 ${requiredPath}`);
286
+ }
287
+ }
288
+ if (
289
+ patternPaths.length
290
+ && !files.some((file) => patternPaths.some((pattern) => globMatches(pattern, file?.path)))
291
+ ) {
292
+ issues.push(`Hugging Face 文件清单没有匹配任何必需模式:${patternPaths.join(", ")}`);
293
+ }
294
+
295
+ const declaredRevision = typeof acquisition.revision === "string"
296
+ ? acquisition.revision.trim()
297
+ : "";
298
+ if (
299
+ /^[0-9a-f]{7,64}$/i.test(declaredRevision)
300
+ && typeof inventory.revision === "string"
301
+ && !inventory.revision.toLowerCase().startsWith(declaredRevision.toLowerCase())
302
+ ) {
303
+ issues.push(`Hugging Face revision 与计划不一致:声明 ${declaredRevision},实际 ${inventory.revision}`);
304
+ }
305
+
306
+ const maximumDownloadBytes = Number(acquisition.maximumDownloadBytes);
307
+ const plannedBytes = plannedInventoryBytes(acquisition.mode, files, inventory.totalBytes, requiredPaths);
308
+ if (
309
+ Number.isSafeInteger(maximumDownloadBytes)
310
+ && maximumDownloadBytes > 0
311
+ && Number.isSafeInteger(plannedBytes)
312
+ && plannedBytes > maximumDownloadBytes
313
+ ) {
314
+ issues.push(`公开资产预计下载 ${plannedBytes} bytes,超过计划上限 ${maximumDownloadBytes} bytes`);
315
+ }
316
+ return issues;
317
+ }
318
+
319
+ // Compatibility export for older integrations. These are preparation hints,
320
+ // not automatic-admission failures.
321
+ export const assetInventoryCompatibilityIssues = assetInventoryPreparationHints;
322
+
323
+ function plannedInventoryBytes(mode, files, totalBytes, requiredPaths) {
324
+ if (mode === "metadata") return 0;
325
+ if (mode === "selective" && requiredPaths.length) {
326
+ const matched = files.filter((file) =>
327
+ requiredPaths.some((pattern) => globMatches(pattern, file?.path)));
328
+ if (matched.some((file) => !Number.isSafeInteger(file?.bytes))) return null;
329
+ return matched.reduce((sum, file) => sum + file.bytes, 0);
330
+ }
331
+ return Number.isSafeInteger(totalBytes) ? totalBytes : null;
332
+ }
333
+
334
+ function globMatches(pattern, value) {
335
+ if (typeof value !== "string") return false;
336
+ const source = [...String(pattern)].map((character) => {
337
+ if (character === "*") return ".*";
338
+ if (character === "?") return ".";
339
+ return character.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
340
+ }).join("");
341
+ return new RegExp(`^${source}$`).test(value);
342
+ }
343
+
344
+ function datasetRegistryMap(value) {
345
+ return new Map(
346
+ (Array.isArray(value?.datasets) ? value.datasets : [])
347
+ .filter((dataset) => typeof dataset?.id === "string")
348
+ .map((dataset) => [dataset.id, dataset]),
349
+ );
350
+ }
351
+
352
+ function nonNegativeInteger(value) {
353
+ const number = Number(value);
354
+ return Number.isInteger(number) && number >= 0 ? number : 0;
355
+ }
@@ -0,0 +1,152 @@
1
+ export const ASSET_REQUIREMENT_KINDS = Object.freeze([
2
+ "author_code",
3
+ "model",
4
+ "tokenizer",
5
+ "training_data",
6
+ "evaluation_data",
7
+ "evaluator",
8
+ "configuration",
9
+ "dependencies",
10
+ ]);
11
+
12
+ const REQUIREMENT_KIND_SET = new Set(ASSET_REQUIREMENT_KINDS);
13
+ const UNDETERMINED_REASON = "Research Compiler did not determine this material requirement.";
14
+
15
+ export function normalizeAssetRequirements(value, { fillMissing = false } = {}) {
16
+ const normalized = [];
17
+ const seen = new Set();
18
+ for (const candidate of Array.isArray(value) ? value : []) {
19
+ if (!candidate || typeof candidate !== "object" || Array.isArray(candidate)) continue;
20
+ if (!REQUIREMENT_KIND_SET.has(candidate.kind) || seen.has(candidate.kind)) continue;
21
+ seen.add(candidate.kind);
22
+ normalized.push({
23
+ kind: candidate.kind,
24
+ required: candidate.required === true,
25
+ reason: text(candidate.reason) ?? "Research Compiler did not record why this material is or is not required.",
26
+ assetIds: stringList(candidate.assetIds),
27
+ checkIds: stringList(candidate.checkIds),
28
+ repository: candidate.repository === true,
29
+ });
30
+ }
31
+ if (fillMissing) {
32
+ for (const kind of ASSET_REQUIREMENT_KINDS) {
33
+ if (seen.has(kind)) continue;
34
+ normalized.push({
35
+ kind,
36
+ required: true,
37
+ reason: UNDETERMINED_REASON,
38
+ assetIds: [],
39
+ checkIds: [],
40
+ repository: false,
41
+ });
42
+ }
43
+ }
44
+ return normalized.sort(
45
+ (left, right) => ASSET_REQUIREMENT_KINDS.indexOf(left.kind)
46
+ - ASSET_REQUIREMENT_KINDS.indexOf(right.kind),
47
+ );
48
+ }
49
+
50
+ export function evaluateAssetRequirementCoverage({ requirements, assets, checks }) {
51
+ const raw = Array.isArray(requirements) ? requirements : [];
52
+ const normalized = normalizeAssetRequirements(raw);
53
+ const counts = new Map();
54
+ for (const candidate of raw) {
55
+ if (!REQUIREMENT_KIND_SET.has(candidate?.kind)) continue;
56
+ counts.set(candidate.kind, (counts.get(candidate.kind) ?? 0) + 1);
57
+ }
58
+ const missingKinds = ASSET_REQUIREMENT_KINDS.filter((kind) => !counts.has(kind));
59
+ const duplicateKinds = ASSET_REQUIREMENT_KINDS.filter((kind) => (counts.get(kind) ?? 0) > 1);
60
+ const assetIds = new Set((Array.isArray(assets) ? assets : [])
61
+ .filter((asset) => asset?.required === true)
62
+ .map((asset) => asset.id));
63
+ const checkIds = new Set((Array.isArray(checks) ? checks : []).map((check) => check?.id));
64
+ const items = normalized.map((requirement) => {
65
+ // Host-filled placeholders describe unknown requirements, not missing assets.
66
+ // Keep the conservative required flag for older consumers, but do not turn
67
+ // an absent compiler declaration into evidence that a material is needed.
68
+ const undetermined = requirement.reason === UNDETERMINED_REASON
69
+ && !requirement.repository && !requirement.assetIds.length && !requirement.checkIds.length;
70
+ const matchedAssetIds = requirement.assetIds.filter((id) => assetIds.has(id));
71
+ const matchedCheckIds = requirement.checkIds.filter((id) => checkIds.has(id));
72
+ const invalidAssetIds = requirement.assetIds.filter((id) => !assetIds.has(id));
73
+ const invalidCheckIds = requirement.checkIds.filter((id) => !checkIds.has(id));
74
+ const covered = !requirement.required
75
+ || requirement.repository
76
+ || matchedAssetIds.length > 0
77
+ || matchedCheckIds.length > 0;
78
+ return {
79
+ ...requirement,
80
+ status: undetermined ? "undetermined" : requirement.required ? covered ? "covered" : "missing" : "not_required",
81
+ matchedAssetIds,
82
+ matchedCheckIds,
83
+ invalidAssetIds,
84
+ invalidCheckIds,
85
+ };
86
+ });
87
+ const missingRequirements = items
88
+ .filter((item) => item.status === "missing")
89
+ .map((item) => item.kind);
90
+ const invalidBindings = items.flatMap((item) => [
91
+ ...item.invalidAssetIds.map((id) => `${item.kind}:asset:${id}`),
92
+ ...item.invalidCheckIds.map((id) => `${item.kind}:check:${id}`),
93
+ ]);
94
+ const undeterminedKinds = items.filter((item) => item.status === "undetermined").map((item) => item.kind);
95
+ const status = missingKinds.length || duplicateKinds.length || undeterminedKinds.length
96
+ ? "undetermined"
97
+ : missingRequirements.length || invalidBindings.length
98
+ ? "incomplete"
99
+ : "complete";
100
+ return {
101
+ schemaVersion: "0.1",
102
+ status,
103
+ items,
104
+ missingKinds,
105
+ duplicateKinds,
106
+ undeterminedKinds,
107
+ missingRequirements,
108
+ invalidBindings,
109
+ summary: {
110
+ total: ASSET_REQUIREMENT_KINDS.length,
111
+ required: items.filter((item) => item.required).length,
112
+ covered: items.filter((item) => item.status === "covered").length,
113
+ notRequired: items.filter((item) => item.status === "not_required").length,
114
+ missing: missingRequirements.length,
115
+ undetermined: undeterminedKinds.length + missingKinds.length,
116
+ },
117
+ };
118
+ }
119
+
120
+ export function assetRequirementCoverageFailures(coverage) {
121
+ if (coverage?.status === "complete") return [];
122
+ if (coverage?.status === "undetermined") {
123
+ const detail = [
124
+ ...(coverage.missingKinds ?? []).map((kind) => `缺少 ${kind}`),
125
+ ...(coverage.duplicateKinds ?? []).map((kind) => `${kind} 重复`),
126
+ ...(coverage.undeterminedKinds ?? []).map((kind) => `${kind} 待工作区核实`),
127
+ ];
128
+ return [{
129
+ assetId: null,
130
+ reasonCode: "asset.requirements_undetermined",
131
+ message: `科研资料需求清单无法判定:${detail.join(";") || "缺少完整需求声明"}。`,
132
+ }];
133
+ }
134
+ return [{
135
+ assetId: null,
136
+ reasonCode: "asset.requirements_incomplete",
137
+ message: `科研资料尚未闭合:${[
138
+ ...(coverage?.missingRequirements ?? []),
139
+ ...(coverage?.invalidBindings ?? []),
140
+ ].join(";") || "存在未满足需求"}。`,
141
+ }];
142
+ }
143
+
144
+ function stringList(value) {
145
+ return [...new Set((Array.isArray(value) ? value : [])
146
+ .map(text)
147
+ .filter(Boolean))];
148
+ }
149
+
150
+ function text(value) {
151
+ return typeof value === "string" && value.trim() ? value.trim() : null;
152
+ }