@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,341 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { execFile } from "node:child_process";
4
+ import { promisify } from "node:util";
5
+
6
+ const execFileAsync = promisify(execFile);
7
+ const MAX_SOURCE_TEXT_BYTES = 8 * 1024 * 1024;
8
+
9
+ /**
10
+ * Mechanically bind public asset locators to the immutable sources available
11
+ * to Research Compiler. This is deliberately narrower than semantic identity:
12
+ * the preparation worker still has to verify bytes, format and the real loader.
13
+ */
14
+ export async function assetSourceGroundingIssues(research, context = {}) {
15
+ const issues = [];
16
+ let fixedPaperText;
17
+ let fixedPaperIssue;
18
+ const readFixedPaper = async () => {
19
+ if (fixedPaperText !== undefined || fixedPaperIssue !== undefined) {
20
+ return { text: fixedPaperText, issue: fixedPaperIssue };
21
+ }
22
+ try {
23
+ fixedPaperText = await fixedPaperSourceText(context);
24
+ return { text: fixedPaperText };
25
+ } catch (error) {
26
+ fixedPaperIssue = error instanceof Error ? error.message : String(error);
27
+ return { issue: fixedPaperIssue };
28
+ }
29
+ };
30
+ const experiments = Array.isArray(research?.experiments) ? research.experiments : [];
31
+ for (const experiment of experiments) {
32
+ const preflight = experiment?.protocol?.preflight;
33
+ if (!new Set(["0.2", "0.3"]).has(preflight?.schemaVersion)) continue;
34
+ for (const asset of Array.isArray(preflight.assets) ? preflight.assets : []) {
35
+ if (asset?.availability !== "public_unverified") continue;
36
+ const label = `experiment ${experiment.id} asset ${asset?.id ?? "unknown"}`;
37
+ const evidence = Array.isArray(asset?.sourceEvidence) ? asset.sourceEvidence : [];
38
+ const groundedUrls = new Set();
39
+ let registryGrounded = false;
40
+ for (const entry of evidence) {
41
+ if (entry?.source === "repository") {
42
+ const result = await validateRepositoryEvidence(
43
+ entry,
44
+ context,
45
+ Array.isArray(asset?.sourceUrls) ? asset.sourceUrls : [],
46
+ );
47
+ if (result.issue) issues.push(`${label}: ${result.issue}`);
48
+ for (const url of result.urls ?? []) groundedUrls.add(comparableUrl(url));
49
+ } else if (entry?.source === "dataset_registry") {
50
+ const result = validateRegistryEvidence(entry, asset, context.datasetRegistry);
51
+ if (result.issue) issues.push(`${label}: ${result.issue}`);
52
+ registryGrounded ||= result.grounded;
53
+ for (const url of result.urls ?? []) groundedUrls.add(comparableUrl(url));
54
+ } else if (entry?.source === "paper") {
55
+ const result = await validatePaperEvidence(entry, context, readFixedPaper);
56
+ if (result.issue) issues.push(`${label}: ${result.issue}`);
57
+ if (result.url) groundedUrls.add(comparableUrl(result.url));
58
+ }
59
+ }
60
+ for (const url of Array.isArray(asset?.sourceUrls) ? asset.sourceUrls : []) {
61
+ if (!groundedUrls.has(comparableUrl(url))) {
62
+ issues.push(`${label}: 公开链接没有绑定到固定论文、固定仓库原文或数据注册表:${url}`);
63
+ continue;
64
+ }
65
+ for (const issue of assetTransportCompatibilityIssues(asset, url)) {
66
+ issues.push(`${label}: ${issue}`);
67
+ }
68
+ }
69
+ if (asset?.registryDatasetId && !registryGrounded) {
70
+ issues.push(`${label}: registryDatasetId 没有绑定到数据注册表记录`);
71
+ }
72
+ }
73
+ }
74
+ return [...new Set(issues)];
75
+ }
76
+
77
+ async function validatePaperEvidence(entry, context, readFixedPaper) {
78
+ if (!context.paperDigest) return { issue: "paper 来源没有绑定到固定论文摘要" };
79
+ const locator = typeof entry?.locator === "string" ? entry.locator.trim() : "";
80
+ if (!/^https?:\/\//i.test(locator)) {
81
+ return { issue: "paper 来源的 locator 必须是论文中出现的完整 HTTP(S) 链接" };
82
+ }
83
+ const paper = await readFixedPaper();
84
+ if (paper.issue) return { issue: `无法核验固定论文中的链接:${paper.issue}` };
85
+ if (!containsPossiblyWrappedUrl(paper.text, locator)) {
86
+ return { issue: "固定论文快照没有逐字包含定位链接" };
87
+ }
88
+ return { url: locator };
89
+ }
90
+
91
+ async function fixedPaperSourceText(context) {
92
+ if (typeof context.paperText === "string") {
93
+ return context.paperText.slice(0, MAX_SOURCE_TEXT_BYTES);
94
+ }
95
+ if (typeof context.paperPath !== "string" || !context.paperPath) {
96
+ throw new Error("缺少固定论文文件路径");
97
+ }
98
+ if (path.extname(context.paperPath).toLowerCase() === ".pdf") {
99
+ const result = await execFileAsync(
100
+ "pdftotext",
101
+ ["-nopgbrk", context.paperPath, "-"],
102
+ { encoding: "utf8", maxBuffer: MAX_SOURCE_TEXT_BYTES },
103
+ );
104
+ return result.stdout.slice(0, MAX_SOURCE_TEXT_BYTES);
105
+ }
106
+ return (await readFile(context.paperPath, "utf8")).slice(0, MAX_SOURCE_TEXT_BYTES);
107
+ }
108
+
109
+ function containsPossiblyWrappedUrl(text, url) {
110
+ if (text.includes(url)) return true;
111
+ const pattern = [...url]
112
+ .map((character) => character.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"))
113
+ .join("\\s*");
114
+ return new RegExp(pattern, "i").test(text);
115
+ }
116
+
117
+ async function validateRepositoryEvidence(entry, context, assetSourceUrls = []) {
118
+ if (!context.repositoryIdentity?.available || !context.repositoryRoot) {
119
+ return { issue: "repository 来源不可用" };
120
+ }
121
+ if (entry.commit !== context.repositoryIdentity.commit) {
122
+ return { issue: "repository 来源没有绑定到固定 commit" };
123
+ }
124
+ if (!safeRelativePath(entry.path)) {
125
+ return { issue: "repository 来源路径无效" };
126
+ }
127
+ const root = path.resolve(context.repositoryRoot);
128
+ const absolute = path.resolve(root, entry.path);
129
+ if (!absolute.startsWith(`${root}${path.sep}`)) return { issue: "repository 来源路径逃逸" };
130
+ let contents;
131
+ try {
132
+ contents = await readFile(absolute, "utf8");
133
+ } catch {
134
+ return { issue: `repository 来源文件不存在:${entry.path}` };
135
+ }
136
+ if (contents.length > 4 * 1024 * 1024) return { issue: `repository 来源文件过大,无法作为链接证据:${entry.path}` };
137
+ const locator = typeof entry.locator === "string" ? entry.locator.trim() : "";
138
+ const resolver = entry.resolver ?? "exact_url";
139
+ if (resolver === "exact_url") {
140
+ if (/^https?:\/\//i.test(locator) && containsPossiblyWrappedUrl(contents, locator)) {
141
+ return { urls: [locator] };
142
+ }
143
+ const locatorMatchesFixedFile = fixedRepositoryFileLocator(entry, context, locator);
144
+ const candidates = assetSourceUrls.filter((url) =>
145
+ repositoryContentsGroundAssetUrl(contents, url));
146
+ if (
147
+ candidates.length
148
+ && (locatorMatchesFixedFile || candidates.some((url) => comparableUrl(url) === comparableUrl(locator)))
149
+ ) {
150
+ return { urls: candidates };
151
+ }
152
+ return { issue: `repository 来源文件无法逐字或确定性组合出资产定位链接:${entry.path}` };
153
+ }
154
+ if (resolver === "append_path") {
155
+ const suffix = typeof entry.suffix === "string" ? entry.suffix.trim() : "";
156
+ if (!/^https?:\/\//i.test(locator) || !containsPossiblyWrappedUrl(contents, locator)) {
157
+ return { issue: `repository 来源文件没有逐字包含 URL 基址:${entry.path}` };
158
+ }
159
+ if (!safeUrlPathSuffix(suffix) || !contents.includes(suffix)) {
160
+ return { issue: `repository 来源文件没有逐字包含安全的资产路径后缀:${entry.path}` };
161
+ }
162
+ return { urls: [joinPublicUrl(locator, suffix)] };
163
+ }
164
+ if (resolver === "huggingface_model" || resolver === "huggingface_dataset") {
165
+ if (!safeRepositoryIdentifier(locator) || !contents.includes(locator)) {
166
+ return { issue: `repository 来源文件没有逐字包含 Hugging Face 仓库标识:${entry.path}` };
167
+ }
168
+ const prefix = resolver === "huggingface_dataset"
169
+ ? "https://huggingface.co/datasets/"
170
+ : "https://huggingface.co/";
171
+ return { urls: [`${prefix}${locator}`] };
172
+ }
173
+ return { issue: `repository 来源 resolver 无效:${resolver}` };
174
+ }
175
+
176
+ /**
177
+ * Reject transports that are reachable but cannot represent the declared
178
+ * asset. Full byte, format, split and loader verification remains in the
179
+ * separate asset-preparation stage.
180
+ */
181
+ export function assetTransportCompatibilityIssues(asset, value) {
182
+ let url;
183
+ try {
184
+ url = new URL(String(value));
185
+ } catch {
186
+ return ["资产传输地址不是有效 URL"];
187
+ }
188
+ const kind = String(asset?.kind ?? "other");
189
+ const identity = `${asset?.id ?? ""} ${asset?.identity ?? ""}`;
190
+ const hostname = url.hostname.toLowerCase();
191
+ const parts = url.pathname.split("/").filter(Boolean);
192
+
193
+ if (hostname === "huggingface.co") {
194
+ const dataset = parts[0] === "datasets";
195
+ const offset = dataset ? 1 : 0;
196
+ const repositoryId = parts[offset] && parts[offset + 1]
197
+ ? `${parts[offset]}/${parts[offset + 1]}`
198
+ : "";
199
+ if (kind === "dataset" && !dataset) {
200
+ return ["dataset 资产必须指向 Hugging Face dataset,而不是 model 仓库"];
201
+ }
202
+ if (new Set(["checkpoint", "model"]).has(kind) && dataset) {
203
+ return [`${kind} 资产不能指向 Hugging Face dataset 仓库`];
204
+ }
205
+ if (repositoryId && !transportIdentityMatches(identity, repositoryId)) {
206
+ return [`Hugging Face 仓库 ${repositoryId} 与资产身份不匹配`];
207
+ }
208
+ }
209
+
210
+ if (hostname === "github.com" && parts.length === 2) {
211
+ const repositoryId = `${parts[0]}/${parts[1].replace(/\.git$/i, "")}`;
212
+ if (new Set(["checkpoint", "model"]).has(kind)) {
213
+ return [`${kind} 资产不能把 GitHub 源码仓库根地址当作模型字节来源`];
214
+ }
215
+ if (!transportIdentityMatches(identity, repositoryId)) {
216
+ return [`GitHub 仓库 ${repositoryId} 与资产身份不匹配`];
217
+ }
218
+ }
219
+
220
+ return [];
221
+ }
222
+
223
+ function validateRegistryEvidence(entry, asset, registry) {
224
+ const record = (Array.isArray(registry?.datasets) ? registry.datasets : [])
225
+ .find((candidate) => candidate?.id === asset?.registryDatasetId);
226
+ if (!record) return { issue: `数据注册表不存在 ${asset?.registryDatasetId ?? "对应记录"}`, grounded: false };
227
+ const urls = new Set([
228
+ record?.candidateTransport?.url,
229
+ ...(Array.isArray(record?.candidateTransport?.mirrors) ? record.candidateTransport.mirrors : []),
230
+ ].filter(Boolean));
231
+ const locator = typeof entry.locator === "string" ? entry.locator.trim() : "";
232
+ if (locator !== record.id && !urls.has(locator)) {
233
+ return { issue: "dataset_registry locator 与固定注册表记录不一致", grounded: false };
234
+ }
235
+ return {
236
+ grounded: true,
237
+ urls: locator === record.id ? [...urls] : [locator],
238
+ };
239
+ }
240
+
241
+ function repositoryContentsGroundAssetUrl(contents, value) {
242
+ if (typeof value !== "string" || !/^https?:\/\//i.test(value)) return false;
243
+ if (containsPossiblyWrappedUrl(contents, value)) return true;
244
+ let url;
245
+ try {
246
+ url = new URL(value);
247
+ } catch {
248
+ return false;
249
+ }
250
+ if (url.hostname.toLowerCase() !== "huggingface.co") return false;
251
+ const parts = url.pathname.split("/").filter(Boolean);
252
+ const offset = parts[0] === "datasets" ? 1 : 0;
253
+ const owner = parts[offset];
254
+ const name = parts[offset + 1];
255
+ if (!owner || !name) return false;
256
+ if (contents.includes(`${owner}/${name}`)) return true;
257
+ const base = parts[0] === "datasets"
258
+ ? `https://huggingface.co/datasets/${owner}`
259
+ : `https://huggingface.co/${owner}`;
260
+ return containsPossiblyWrappedUrl(contents, base) && contents.includes(name);
261
+ }
262
+
263
+ function fixedRepositoryFileLocator(entry, context, value) {
264
+ if (typeof value !== "string" || !/^https?:\/\//i.test(value)) return false;
265
+ let locator;
266
+ let repository;
267
+ try {
268
+ locator = new URL(value);
269
+ repository = new URL(context.repositoryIdentity?.url);
270
+ } catch {
271
+ return false;
272
+ }
273
+ if (locator.hostname.toLowerCase() !== "github.com" || repository.hostname.toLowerCase() !== "github.com") {
274
+ return false;
275
+ }
276
+ const repositoryParts = repository.pathname.replace(/\.git$/i, "").split("/").filter(Boolean);
277
+ const locatorParts = locator.pathname.split("/").filter(Boolean);
278
+ if (repositoryParts.length !== 2 || locatorParts.length < 5) return false;
279
+ const [owner, name] = repositoryParts;
280
+ const [locatorOwner, locatorName, marker, commit, ...fileParts] = locatorParts;
281
+ return owner === locatorOwner
282
+ && name === locatorName
283
+ && marker === "blob"
284
+ && commit === context.repositoryIdentity?.commit
285
+ && fileParts.join("/") === entry.path;
286
+ }
287
+
288
+ function safeRelativePath(value) {
289
+ return typeof value === "string"
290
+ && value.length > 0
291
+ && !path.posix.isAbsolute(value.replaceAll("\\", "/"))
292
+ && !value.replaceAll("\\", "/").split("/").includes("..")
293
+ && !/[\u0000-\u001f]/.test(value);
294
+ }
295
+
296
+ function safeRepositoryIdentifier(value) {
297
+ return typeof value === "string"
298
+ && /^[a-zA-Z0-9][a-zA-Z0-9._-]*\/[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(value);
299
+ }
300
+
301
+ function safeUrlPathSuffix(value) {
302
+ return typeof value === "string"
303
+ && value.length > 0
304
+ && value.length <= 500
305
+ && !value.startsWith("/")
306
+ && !value.split("/").includes("..")
307
+ && !/[?#\\\u0000-\u001f]/.test(value);
308
+ }
309
+
310
+ function joinPublicUrl(base, suffix) {
311
+ const normalizedBase = base.endsWith("/") ? base : `${base}/`;
312
+ return new URL(suffix, normalizedBase).toString();
313
+ }
314
+
315
+ function comparableUrl(value) {
316
+ try {
317
+ const url = new URL(String(value));
318
+ url.hash = "";
319
+ return url.toString().replace(/\/$/, "");
320
+ } catch {
321
+ return String(value);
322
+ }
323
+ }
324
+
325
+ function transportIdentityMatches(identity, repositoryId) {
326
+ const normalizedIdentity = normalizeIdentity(identity);
327
+ const [owner, name] = repositoryId.split("/");
328
+ const normalizedRepository = normalizeIdentity(repositoryId);
329
+ const normalizedName = normalizeIdentity(name);
330
+ if (normalizedRepository.length >= 5 && normalizedIdentity.includes(normalizedRepository)) return true;
331
+ if (normalizedName.length >= 4 && normalizedIdentity.includes(normalizedName)) return true;
332
+ const normalizedOwner = normalizeIdentity(owner);
333
+ return normalizedOwner.length >= 6
334
+ && normalizedName.length >= 3
335
+ && normalizedIdentity.includes(normalizedOwner)
336
+ && normalizedIdentity.includes(normalizedName);
337
+ }
338
+
339
+ function normalizeIdentity(value) {
340
+ return String(value ?? "").toLowerCase().replace(/[^a-z0-9]+/g, "");
341
+ }
@@ -0,0 +1,118 @@
1
+ const SOURCE_PRIORITIES = Object.freeze({
2
+ author_repository: 10,
3
+ paper_explicit: 20,
4
+ primary_archive: 30,
5
+ official_registry: 40,
6
+ research_plan: 50,
7
+ verified_mirror: 60,
8
+ unverified_mirror: 70,
9
+ });
10
+
11
+ export function rankedAssetLocators(asset, registry) {
12
+ const evidence = new Map((Array.isArray(asset?.sourceEvidence) ? asset.sourceEvidence : [])
13
+ .filter((entry) => typeof entry?.locator === "string")
14
+ .map((entry) => [entry.locator, entry.source]));
15
+ const candidates = [];
16
+ for (const url of Array.isArray(asset?.sourceUrls) ? asset.sourceUrls : []) {
17
+ const source = evidence.get(url);
18
+ candidates.push(locator(url, source === "repository"
19
+ ? "author_repository"
20
+ : source === "paper"
21
+ ? "paper_explicit"
22
+ : "research_plan"));
23
+ }
24
+ const primary = registry?.bridgeEvidence?.primaryUrl;
25
+ if (typeof primary === "string") {
26
+ candidates.push(locator(primary, "primary_archive", {
27
+ expectedDigest: digest(registry?.bridgeEvidence?.primaryArchiveSha256),
28
+ }));
29
+ }
30
+ const transport = registry?.candidateTransport;
31
+ if (typeof transport?.url === "string") {
32
+ candidates.push(locator(transport.url, "official_registry", {
33
+ expectedDigest: digest(transport.archiveSha256),
34
+ equivalence: registry?.confidence ?? null,
35
+ }));
36
+ }
37
+ for (const mirror of Array.isArray(transport?.mirrors) ? transport.mirrors : []) {
38
+ const value = typeof mirror === "string" ? { url: mirror } : mirror;
39
+ candidates.push(locator(value?.url, value?.sha256 ? "verified_mirror" : "unverified_mirror", {
40
+ expectedDigest: digest(value?.sha256),
41
+ fallbackRequiresDigestMatch: true,
42
+ }));
43
+ }
44
+ const deduplicated = new Map();
45
+ for (const candidate of candidates) {
46
+ if (!safeHttpUrl(candidate.url)) continue;
47
+ const previous = deduplicated.get(candidate.url);
48
+ if (
49
+ !previous
50
+ || candidate.priority < previous.priority
51
+ || (!previous.expectedDigest && candidate.expectedDigest)
52
+ ) {
53
+ deduplicated.set(candidate.url, {
54
+ ...previous,
55
+ ...candidate,
56
+ expectedDigest: candidate.expectedDigest ?? previous?.expectedDigest ?? null,
57
+ equivalence: candidate.equivalence ?? previous?.equivalence ?? null,
58
+ fallbackRequiresDigestMatch:
59
+ candidate.fallbackRequiresDigestMatch === true
60
+ || previous?.fallbackRequiresDigestMatch === true,
61
+ });
62
+ }
63
+ }
64
+ return [...deduplicated.values()]
65
+ .sort((left, right) => left.priority - right.priority)
66
+ .map((candidate, index) => ({ ...candidate, rank: index + 1 }));
67
+ }
68
+
69
+ export function validateSelectedSource(asset, acquisition) {
70
+ const issues = [];
71
+ const locators = Array.isArray(asset?.sourceLocators) ? asset.sourceLocators : [];
72
+ const selected = locators.find((candidate) => candidate.url === acquisition?.sourceLocator);
73
+ if (!selected) return issues;
74
+ if (selected.expectedDigest && acquisition?.sourceDigest !== selected.expectedDigest) {
75
+ issues.push(`资产 ${asset.id} 的来源摘要与计划不一致`);
76
+ }
77
+ if (selected.fallbackRequiresDigestMatch && !selected.expectedDigest) {
78
+ issues.push(`资产 ${asset.id} 使用镜像但计划没有可核验的等价摘要`);
79
+ }
80
+ const earlier = locators.filter((candidate) => candidate.rank < selected.rank);
81
+ if (earlier.length) {
82
+ const attempts = new Map((Array.isArray(acquisition?.attemptedSources)
83
+ ? acquisition.attemptedSources
84
+ : []).map((attempt) => [attempt?.url, attempt]));
85
+ for (const candidate of earlier) {
86
+ const attempt = attempts.get(candidate.url);
87
+ if (!attempt || attempt.status !== "failed") {
88
+ issues.push(`资产 ${asset.id} 使用后备来源前没有记录更高优先级来源失败:${candidate.url}`);
89
+ }
90
+ }
91
+ }
92
+ return issues;
93
+ }
94
+
95
+ function locator(url, sourceClass, extra = {}) {
96
+ return {
97
+ url,
98
+ source: sourceClass,
99
+ sourceClass,
100
+ priority: SOURCE_PRIORITIES[sourceClass],
101
+ ...extra,
102
+ };
103
+ }
104
+
105
+ function digest(value) {
106
+ if (typeof value !== "string" || !value) return null;
107
+ const normalized = value.startsWith("sha256:") ? value : `sha256:${value}`;
108
+ return /^sha256:[0-9a-f]{64}$/.test(normalized) ? normalized : null;
109
+ }
110
+
111
+ function safeHttpUrl(value) {
112
+ try {
113
+ const url = new URL(value);
114
+ return new Set(["http:", "https:"]).has(url.protocol) && !url.username && !url.password;
115
+ } catch {
116
+ return false;
117
+ }
118
+ }