@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,525 @@
1
+ import { lookup } from "node:dns/promises";
2
+ import net from "node:net";
3
+
4
+ const TERMINAL_HTTP_STATUSES = new Set([401, 403, 404, 410]);
5
+ const RANGE_FALLBACK_STATUSES = new Set([403, 404, 405, 501]);
6
+ const REDIRECT_HTTP_STATUSES = new Set([301, 302, 303, 307, 308]);
7
+ const USER_AGENT = "CiteArk-Reproduction-Admission/1.0";
8
+ const DEFAULT_MAX_ATTEMPTS = 3;
9
+ const DEFAULT_RETRY_BASE_DELAY_MS = 500;
10
+ const DEFAULT_RETRY_MAX_DELAY_MS = 60_000;
11
+ const RETRYABLE_HTTP_STATUSES = new Set([408, 425, 429]);
12
+ const SENSITIVE_QUERY_KEY = /(auth|credential|key|secret|signature|token|x-amz)/i;
13
+ const MAX_RECORDED_HUGGING_FACE_FILES = 20_000;
14
+
15
+ class UnsafeAssetHostError extends Error {}
16
+
17
+ /** Probe only public, unauthenticated transport feasibility. */
18
+ export async function probePublicAssetUrl(value, {
19
+ fetchImpl = fetch,
20
+ timeoutMs = 12_000,
21
+ resolveHost = lookup,
22
+ maxAttempts = DEFAULT_MAX_ATTEMPTS,
23
+ retryBaseDelayMs = DEFAULT_RETRY_BASE_DELAY_MS,
24
+ retryMaxDelayMs = DEFAULT_RETRY_MAX_DELAY_MS,
25
+ sleepImpl = (delayMs) => new Promise((resolve) => setTimeout(resolve, delayMs)),
26
+ randomImpl = Math.random,
27
+ observe,
28
+ now = () => new Date(),
29
+ } = {}) {
30
+ const boundedAttempts = positiveInteger(maxAttempts, DEFAULT_MAX_ATTEMPTS, 1, 8);
31
+ const attempts = [];
32
+ let result;
33
+ for (let attempt = 1; attempt <= boundedAttempts; attempt += 1) {
34
+ const startedAt = now();
35
+ const operations = [];
36
+ result = await probePublicAssetUrlOnce(value, {
37
+ fetchImpl,
38
+ timeoutMs,
39
+ resolveHost,
40
+ operations,
41
+ now,
42
+ });
43
+ const finishedAt = now();
44
+ const attemptRecord = {
45
+ attempt,
46
+ maxAttempts: boundedAttempts,
47
+ startedAt: startedAt.toISOString(),
48
+ finishedAt: finishedAt.toISOString(),
49
+ durationMs: Math.max(0, finishedAt.getTime() - startedAt.getTime()),
50
+ status: result.status,
51
+ ...(result.httpStatus === undefined ? {} : { httpStatus: result.httpStatus }),
52
+ ...(result.reasonCode ? { reasonCode: result.reasonCode } : {}),
53
+ ...(result.retryAfterMs === undefined ? {} : { retryAfterMs: result.retryAfterMs }),
54
+ ...(result.detail ? { detail: result.detail } : {}),
55
+ operations,
56
+ };
57
+ attempts.push(attemptRecord);
58
+ emitProbeObservation(observe, result, attemptRecord);
59
+ if (result.status !== "retryable" || attempt === boundedAttempts) break;
60
+ await sleepImpl(retryDelayMs({
61
+ result,
62
+ attempt,
63
+ retryBaseDelayMs,
64
+ retryMaxDelayMs,
65
+ randomImpl,
66
+ }));
67
+ }
68
+ return {
69
+ ...result,
70
+ observedAt: now().toISOString(),
71
+ attemptCount: attempts.length,
72
+ attempts,
73
+ };
74
+ }
75
+
76
+ async function probePublicAssetUrlOnce(value, {
77
+ fetchImpl,
78
+ timeoutMs,
79
+ resolveHost,
80
+ operations,
81
+ now,
82
+ }) {
83
+ let current;
84
+ try {
85
+ current = safePublicUrl(value);
86
+ } catch (error) {
87
+ return terminal(value, "admission.asset_url_unsafe", error);
88
+ }
89
+
90
+ const huggingFace = huggingFaceApiUrl(current);
91
+ if (huggingFace) {
92
+ return await probeHuggingFaceRepository(huggingFace, {
93
+ originalUrl: current,
94
+ fetchImpl,
95
+ timeoutMs,
96
+ resolveHost,
97
+ operations,
98
+ now,
99
+ });
100
+ }
101
+
102
+ for (let redirect = 0; redirect <= 5; redirect += 1) {
103
+ let response;
104
+ try {
105
+ await assertPublicResolution(current, resolveHost, { operations, now });
106
+ response = await tracedFetch(current, {
107
+ method: "HEAD",
108
+ redirect: "manual",
109
+ signal: AbortSignal.timeout(timeoutMs),
110
+ headers: { "user-agent": USER_AGENT },
111
+ }, { fetchImpl, operations, now });
112
+ } catch (error) {
113
+ return networkOrSafetyFailure(current, error);
114
+ }
115
+ if (REDIRECT_HTTP_STATUSES.has(response.status)) {
116
+ const redirected = safeRedirect(current, response);
117
+ if (redirected.observation) return redirected.observation;
118
+ current = redirected.url;
119
+ continue;
120
+ }
121
+ if (response.ok) return reachable(current, response);
122
+ if (RANGE_FALLBACK_STATUSES.has(response.status)) {
123
+ return await rangeProbe(current, { fetchImpl, timeoutMs, operations, now });
124
+ }
125
+ return httpFailure(current, response);
126
+ }
127
+ return terminal(current, "admission.asset_redirect_limit");
128
+ }
129
+
130
+ async function probeHuggingFaceRepository(apiUrl, {
131
+ originalUrl,
132
+ fetchImpl,
133
+ timeoutMs,
134
+ resolveHost,
135
+ operations,
136
+ now,
137
+ }) {
138
+ let response;
139
+ try {
140
+ await assertPublicResolution(apiUrl, resolveHost, { operations, now });
141
+ response = await tracedFetch(apiUrl, {
142
+ method: "GET",
143
+ redirect: "manual",
144
+ signal: AbortSignal.timeout(timeoutMs),
145
+ headers: { accept: "application/json", "user-agent": USER_AGENT },
146
+ }, { fetchImpl, operations, now });
147
+ } catch (error) {
148
+ return networkOrSafetyFailure(originalUrl, error);
149
+ }
150
+ if (!response.ok) {
151
+ if (response?.body) await response.body.cancel().catch(() => {});
152
+ return httpFailure(originalUrl, response);
153
+ }
154
+ let metadata;
155
+ try {
156
+ metadata = await response.json();
157
+ } catch (error) {
158
+ return {
159
+ url: loggableUrl(originalUrl),
160
+ status: "retryable",
161
+ reasonCode: "admission.asset_metadata_invalid",
162
+ detail: error instanceof Error ? error.message : String(error),
163
+ };
164
+ }
165
+ if (metadata?.private === true || Boolean(metadata?.gated)) {
166
+ return {
167
+ url: loggableUrl(originalUrl),
168
+ probeUrl: loggableUrl(apiUrl),
169
+ status: "terminal",
170
+ httpStatus: response.status,
171
+ reasonCode: "admission.asset_requires_auth_or_acceptance",
172
+ };
173
+ }
174
+ const siblings = Array.isArray(metadata?.siblings) ? metadata.siblings : [];
175
+ const inventory = siblings.map((item) => ({
176
+ path: String(item?.rfilename ?? ""),
177
+ bytes: nonNegativeInteger(item?.size ?? item?.lfs?.size),
178
+ ...(typeof item?.lfs?.sha256 === "string" ? { sha256: item.lfs.sha256 } : {}),
179
+ }));
180
+ const unknownSizeCount = inventory.filter((item) => item.bytes === null).length;
181
+ return {
182
+ url: loggableUrl(originalUrl),
183
+ probeUrl: loggableUrl(apiUrl),
184
+ status: "reachable",
185
+ httpStatus: response.status,
186
+ contentType: response.headers.get("content-type"),
187
+ inventory: {
188
+ provider: "hugging-face",
189
+ revision: typeof metadata?.sha === "string" ? metadata.sha : null,
190
+ fileCount: inventory.length,
191
+ totalBytes: unknownSizeCount === 0
192
+ ? inventory.reduce((sum, item) => sum + item.bytes, 0)
193
+ : null,
194
+ unknownSizeCount,
195
+ truncated: inventory.length > MAX_RECORDED_HUGGING_FACE_FILES,
196
+ files: inventory.slice(0, MAX_RECORDED_HUGGING_FACE_FILES),
197
+ },
198
+ };
199
+ }
200
+
201
+ function huggingFaceApiUrl(url) {
202
+ if (url.hostname.toLowerCase() !== "huggingface.co") return null;
203
+ const parts = url.pathname.split("/").filter(Boolean);
204
+ if (parts[0] === "api") return null;
205
+ const dataset = parts[0] === "datasets";
206
+ const offset = dataset ? 1 : 0;
207
+ if (!parts[offset] || !parts[offset + 1]) return null;
208
+ const kind = dataset ? "datasets" : "models";
209
+ const api = new URL(`/api/${kind}/${encodeURIComponent(parts[offset])}/${encodeURIComponent(parts[offset + 1])}`, url.origin);
210
+ api.searchParams.set("blobs", "true");
211
+ return api;
212
+ }
213
+
214
+ async function rangeProbe(url, { fetchImpl, timeoutMs, operations, now }) {
215
+ let response;
216
+ try {
217
+ response = await tracedFetch(url, {
218
+ method: "GET",
219
+ redirect: "manual",
220
+ signal: AbortSignal.timeout(timeoutMs),
221
+ headers: { range: "bytes=0-0", "user-agent": USER_AGENT },
222
+ }, { fetchImpl, operations, now });
223
+ } catch (error) {
224
+ return networkOrSafetyFailure(url, error);
225
+ }
226
+ if (response?.body) await response.body.cancel().catch(() => {});
227
+ if (response.ok || response.status === 206) return reachable(url, response);
228
+ return httpFailure(url, response);
229
+ }
230
+
231
+ async function assertPublicResolution(url, resolveHost, { operations, now }) {
232
+ if (net.isIP(url.hostname)) return;
233
+ const startedAt = now();
234
+ let addresses;
235
+ try {
236
+ addresses = await resolveHost(url.hostname, { all: true, verbatim: true });
237
+ } catch (error) {
238
+ const finishedAt = now();
239
+ operations.push({
240
+ kind: "dns",
241
+ hostname: url.hostname,
242
+ status: "failed",
243
+ startedAt: startedAt.toISOString(),
244
+ finishedAt: finishedAt.toISOString(),
245
+ durationMs: Math.max(0, finishedAt.getTime() - startedAt.getTime()),
246
+ detail: error instanceof Error ? error.message : String(error),
247
+ });
248
+ throw error;
249
+ }
250
+ const finishedAt = now();
251
+ operations.push({
252
+ kind: "dns",
253
+ hostname: url.hostname,
254
+ status: "succeeded",
255
+ addressCount: Array.isArray(addresses) ? addresses.length : 0,
256
+ addresses: Array.isArray(addresses)
257
+ ? addresses.map((item) => ({ address: item?.address, family: item?.family }))
258
+ : [],
259
+ startedAt: startedAt.toISOString(),
260
+ finishedAt: finishedAt.toISOString(),
261
+ durationMs: Math.max(0, finishedAt.getTime() - startedAt.getTime()),
262
+ });
263
+ if (!Array.isArray(addresses) || addresses.length === 0) {
264
+ throw new Error("资产主机没有可解析地址");
265
+ }
266
+ if (addresses.some((item) => privateIp(item?.address))) {
267
+ throw new UnsafeAssetHostError("资产主机解析到私有或保留地址");
268
+ }
269
+ }
270
+
271
+ async function tracedFetch(url, init, { fetchImpl, operations, now }) {
272
+ const startedAt = now();
273
+ try {
274
+ const response = await fetchImpl(url, init);
275
+ const finishedAt = now();
276
+ operations.push({
277
+ kind: "http",
278
+ method: init?.method ?? "GET",
279
+ url: loggableUrl(url),
280
+ status: "completed",
281
+ httpStatus: response.status,
282
+ ...retryAfter(response),
283
+ startedAt: startedAt.toISOString(),
284
+ finishedAt: finishedAt.toISOString(),
285
+ durationMs: Math.max(0, finishedAt.getTime() - startedAt.getTime()),
286
+ });
287
+ return response;
288
+ } catch (error) {
289
+ const finishedAt = now();
290
+ operations.push({
291
+ kind: "http",
292
+ method: init?.method ?? "GET",
293
+ url: loggableUrl(url),
294
+ status: "failed",
295
+ detail: error instanceof Error ? error.message : String(error),
296
+ startedAt: startedAt.toISOString(),
297
+ finishedAt: finishedAt.toISOString(),
298
+ durationMs: Math.max(0, finishedAt.getTime() - startedAt.getTime()),
299
+ });
300
+ throw error;
301
+ }
302
+ }
303
+
304
+ function safeRedirect(current, response) {
305
+ const location = response.headers.get("location");
306
+ if (!location) {
307
+ return { observation: terminal(current, "admission.asset_redirect_missing_location", null, response.status) };
308
+ }
309
+ try {
310
+ return { url: safePublicUrl(new URL(location, current).toString()) };
311
+ } catch (error) {
312
+ return { observation: terminal(current, "admission.asset_redirect_unsafe", error, response.status) };
313
+ }
314
+ }
315
+
316
+ function safePublicUrl(value) {
317
+ const url = new URL(value);
318
+ if (!new Set(["http:", "https:"]).has(url.protocol)) throw new Error("资产 URL 必须使用 HTTP(S)");
319
+ if (url.username || url.password) throw new Error("资产 URL 不能内嵌凭据");
320
+ if (url.port && !new Set(["80", "443"]).has(url.port)) throw new Error("资产 URL 端口不在允许范围");
321
+ const hostname = url.hostname.toLowerCase().replace(/\.$/, "");
322
+ if (!hostname || hostname === "localhost" || hostname.endsWith(".local") || hostname.endsWith(".internal")) {
323
+ throw new Error("资产 URL 不能指向本地或内部主机");
324
+ }
325
+ if (net.isIP(hostname) && privateIp(hostname)) throw new Error("资产 URL 不能指向私有地址");
326
+ return url;
327
+ }
328
+
329
+ function privateIp(hostname) {
330
+ if (net.isIPv4(hostname)) {
331
+ const [a, b] = hostname.split(".").map(Number);
332
+ return a === 0 || a === 10 || a === 127 || (a === 169 && b === 254)
333
+ || (a === 172 && b >= 16 && b <= 31) || (a === 192 && b === 168)
334
+ || a >= 224;
335
+ }
336
+ const normalized = String(hostname ?? "").toLowerCase();
337
+ return normalized === "::" || normalized === "::1" || normalized.startsWith("fc")
338
+ || normalized.startsWith("fd") || normalized.startsWith("fe8")
339
+ || normalized.startsWith("fe9") || normalized.startsWith("fea")
340
+ || normalized.startsWith("feb");
341
+ }
342
+
343
+ function reachable(url, response) {
344
+ return {
345
+ url: loggableUrl(url),
346
+ status: "reachable",
347
+ httpStatus: response.status,
348
+ contentLength: nonNegativeHeader(response.headers.get("content-length")),
349
+ contentType: response.headers.get("content-type"),
350
+ };
351
+ }
352
+
353
+ function httpFailure(url, response) {
354
+ const status = Number(response?.status);
355
+ const isRetryable = RETRYABLE_HTTP_STATUSES.has(status) || status >= 500;
356
+ const isTerminal = TERMINAL_HTTP_STATUSES.has(status)
357
+ || (status >= 400 && status < 500 && !isRetryable)
358
+ || (status >= 300 && status < 400);
359
+ return {
360
+ url: loggableUrl(url),
361
+ status: isTerminal ? "terminal" : "retryable",
362
+ httpStatus: status,
363
+ reasonCode: isTerminal
364
+ ? "admission.asset_transport_unavailable"
365
+ : "admission.asset_transport_transient",
366
+ ...retryAfter(response),
367
+ };
368
+ }
369
+
370
+ function networkOrSafetyFailure(url, error) {
371
+ const unsafe = error instanceof UnsafeAssetHostError;
372
+ return {
373
+ url: loggableUrl(url),
374
+ status: unsafe ? "terminal" : "retryable",
375
+ reasonCode: unsafe ? "admission.asset_url_unsafe" : "admission.asset_probe_network_error",
376
+ detail: error instanceof Error ? error.message : String(error),
377
+ };
378
+ }
379
+
380
+ function terminal(url, reasonCode, error, httpStatus) {
381
+ return {
382
+ url: loggableUrl(url),
383
+ status: "terminal",
384
+ ...(httpStatus === undefined ? {} : { httpStatus }),
385
+ reasonCode,
386
+ ...(error ? { detail: error instanceof Error ? error.message : String(error) } : {}),
387
+ };
388
+ }
389
+
390
+ function retryAfter(response) {
391
+ const value = response?.headers?.get?.("retry-after");
392
+ if (!value) return {};
393
+ const seconds = Number(value);
394
+ if (Number.isFinite(seconds) && seconds >= 0) {
395
+ return { retryAfterMs: Math.round(seconds * 1000) };
396
+ }
397
+ const timestamp = Date.parse(value);
398
+ if (!Number.isFinite(timestamp)) return {};
399
+ return { retryAfterMs: Math.max(0, timestamp - Date.now()) };
400
+ }
401
+
402
+ function retryDelayMs({
403
+ result,
404
+ attempt,
405
+ retryBaseDelayMs,
406
+ retryMaxDelayMs,
407
+ randomImpl,
408
+ }) {
409
+ const base = Math.max(0, Number(retryBaseDelayMs) || 0) * 2 ** Math.max(0, attempt - 1);
410
+ const jitter = base * (0.75 + Math.max(0, Math.min(1, Number(randomImpl()) || 0)) * 0.5);
411
+ const requested = Math.max(jitter, Number(result?.retryAfterMs) || 0);
412
+ return Math.round(Math.min(Math.max(0, Number(retryMaxDelayMs) || 0), requested));
413
+ }
414
+
415
+ function emitProbeObservation(observe, result, attempt) {
416
+ if (typeof observe !== "function") return;
417
+ try {
418
+ for (const operation of attempt.operations) observe({
419
+ schemaVersion: "1.0",
420
+ kind: "external_request",
421
+ component: "public_asset_probe",
422
+ operation: operation.kind === "dns" ? "resolve_asset_host" : "request_public_asset",
423
+ service: operation.kind === "dns"
424
+ ? operation.hostname
425
+ : serviceName(operation.url),
426
+ request: operation.kind === "dns" ? {
427
+ attempt: attempt.attempt,
428
+ maxAttempts: attempt.maxAttempts,
429
+ method: "DNS_LOOKUP",
430
+ hostname: operation.hostname,
431
+ } : {
432
+ attempt: attempt.attempt,
433
+ maxAttempts: attempt.maxAttempts,
434
+ method: operation.method,
435
+ url: operation.url,
436
+ },
437
+ response: {
438
+ ...(operation.httpStatus === undefined ? {} : { status: operation.httpStatus }),
439
+ ...(operation.retryAfterMs === undefined ? {} : { retryAfterMs: operation.retryAfterMs }),
440
+ ...(operation.addressCount === undefined ? {} : { addressCount: operation.addressCount }),
441
+ },
442
+ outcome: {
443
+ status: operation.status === "failed" ? "failed" : "succeeded",
444
+ retryable: operation.status === "failed" && attempt.status === "retryable",
445
+ ...(operation.status === "failed" && attempt.reasonCode
446
+ ? { reasonCode: attempt.reasonCode }
447
+ : {}),
448
+ },
449
+ timing: {
450
+ startedAt: operation.startedAt,
451
+ finishedAt: operation.finishedAt,
452
+ durationMs: operation.durationMs,
453
+ },
454
+ details: {
455
+ finalProbeStatus: attempt.status,
456
+ ...(operation.addresses ? { addresses: operation.addresses } : {}),
457
+ ...(operation.detail ? { error: operation.detail } : {}),
458
+ },
459
+ });
460
+ observe({
461
+ schemaVersion: "1.0",
462
+ kind: "decision",
463
+ component: "public_asset_probe",
464
+ operation: "classify_probe_attempt",
465
+ service: serviceName(result?.probeUrl ?? result?.url),
466
+ outcome: {
467
+ status: attempt.status === "reachable" ? "succeeded" : "failed",
468
+ retryable: attempt.status === "retryable",
469
+ ...(attempt.reasonCode ? { reasonCode: attempt.reasonCode } : {}),
470
+ },
471
+ timing: {
472
+ startedAt: attempt.startedAt,
473
+ finishedAt: attempt.finishedAt,
474
+ durationMs: attempt.durationMs,
475
+ },
476
+ details: {
477
+ attempt: attempt.attempt,
478
+ maxAttempts: attempt.maxAttempts,
479
+ url: result?.url,
480
+ operationCount: attempt.operations.length,
481
+ ...(attempt.httpStatus === undefined ? {} : { httpStatus: attempt.httpStatus }),
482
+ ...(attempt.retryAfterMs === undefined ? {} : { retryAfterMs: attempt.retryAfterMs }),
483
+ ...(attempt.detail ? { error: attempt.detail } : {}),
484
+ },
485
+ });
486
+ } catch {
487
+ // Observability must never turn a successful transport probe into a failure.
488
+ }
489
+ }
490
+
491
+ function serviceName(value) {
492
+ try {
493
+ return new URL(String(value)).hostname;
494
+ } catch {
495
+ return "unknown";
496
+ }
497
+ }
498
+
499
+ function loggableUrl(value) {
500
+ try {
501
+ const url = value instanceof URL ? new URL(value) : new URL(String(value));
502
+ for (const key of [...url.searchParams.keys()]) {
503
+ if (SENSITIVE_QUERY_KEY.test(key)) url.searchParams.set(key, "[REDACTED]");
504
+ }
505
+ return url.toString();
506
+ } catch {
507
+ return String(value);
508
+ }
509
+ }
510
+
511
+ function positiveInteger(value, fallback, minimum, maximum) {
512
+ const number = Number(value);
513
+ if (!Number.isInteger(number)) return fallback;
514
+ return Math.max(minimum, Math.min(maximum, number));
515
+ }
516
+
517
+ function nonNegativeHeader(value) {
518
+ const number = Number(value);
519
+ return Number.isFinite(number) && number >= 0 ? number : null;
520
+ }
521
+
522
+ function nonNegativeInteger(value) {
523
+ const number = Number(value);
524
+ return Number.isSafeInteger(number) && number >= 0 ? number : null;
525
+ }
@@ -0,0 +1,119 @@
1
+ import path from "node:path";
2
+ import { fileURLToPath } from "node:url";
3
+
4
+ import {
5
+ assessAutomaticReproductionAdmission,
6
+ assertNoTransientAdmissionFailures,
7
+ } from "./reproduction-admission.mjs";
8
+ import { loadResearchStructure } from "../research/structure.mjs";
9
+ import { CiteArkError, readJson, writeJson } from "../util.mjs";
10
+ import { validateAssetPreparationManifest } from "./manifest.mjs";
11
+ import { assetPreparationPlanFailures, createAssetPreparationPlan } from "./plan.mjs";
12
+
13
+ const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
14
+
15
+ export const ASSET_QUALIFICATION_BOUNDARY = "asset_preparation";
16
+
17
+ export async function loadAssetQualificationContext({
18
+ researchPath,
19
+ claimId,
20
+ experimentId,
21
+ datasetRegistryPath,
22
+ }) {
23
+ const [research, datasetRegistry] = await Promise.all([
24
+ loadResearchStructure(researchPath),
25
+ readJson(datasetRegistryPath ?? path.join(PACKAGE_ROOT, "data", "dataset-source-registry.v1.json")),
26
+ ]);
27
+ const { claim, experiment } = selectAssetQualificationTarget({
28
+ research,
29
+ claimId,
30
+ experimentId,
31
+ });
32
+ return { research, datasetRegistry, claim, experiment };
33
+ }
34
+
35
+ export function selectAssetQualificationTarget({ research, claimId, experimentId }) {
36
+ const claims = Array.isArray(research?.claims) ? research.claims : [];
37
+ const experiments = Array.isArray(research?.experiments) ? research.experiments : [];
38
+ const experiment = experimentId
39
+ ? experiments.find((item) => item.id === experimentId)
40
+ : experiments.find((item) => Array.isArray(item.claimIds) && item.claimIds.some((id) => (
41
+ !claimId || id === claimId
42
+ )));
43
+ if (!experiment) throw new CiteArkError(`找不到可做资产验收的 experiment:${experimentId ?? "未指定"}`);
44
+ const claim = claimId
45
+ ? claims.find((item) => item.id === claimId)
46
+ : claims.find((item) => experiment.claimIds?.includes(item.id));
47
+ if (!claim) throw new CiteArkError(`实验 ${experiment.id} 没有可验收的 claim`);
48
+ if (!experiment.claimIds?.includes(claim.id)) {
49
+ throw new CiteArkError(`claim ${claim.id} 与 experiment ${experiment.id} 没有绑定`);
50
+ }
51
+ return { claim, experiment };
52
+ }
53
+
54
+ export function planAssetQualification({ experiment, datasetRegistry, limits, now }) {
55
+ const plan = createAssetPreparationPlan({ experiment, datasetRegistry, limits, now });
56
+ return {
57
+ plan,
58
+ failures: assetPreparationPlanFailures(plan),
59
+ };
60
+ }
61
+
62
+ export async function probeAssetQualification({
63
+ research,
64
+ claim,
65
+ experiment,
66
+ datasetRegistry,
67
+ outputPath,
68
+ ...probeOptions
69
+ }) {
70
+ const admission = await assessAutomaticReproductionAdmission({
71
+ research,
72
+ targets: [{ claimId: claim.id, experimentId: experiment.id }],
73
+ datasetRegistry,
74
+ ...probeOptions,
75
+ });
76
+ if (outputPath) await writeJson(outputPath, admission);
77
+ return admission;
78
+ }
79
+
80
+ export function assertAssetQualificationAdmission(admission) {
81
+ assertNoTransientAdmissionFailures(admission);
82
+ if (admission?.blockedTargets?.length) {
83
+ const reasonCodes = admission.blockedTargets
84
+ .map((target) => target.reasonCode)
85
+ .filter(Boolean);
86
+ throw new CiteArkError(
87
+ `${admission.blockedTargets.length} 个目标未通过公开资产准入,未启动资产 Agent`,
88
+ {
89
+ exitCode: 2,
90
+ retryable: false,
91
+ failureCode: reasonCodes[0] ?? "admission.asset_qualification_blocked",
92
+ failureCategory: "admission",
93
+ },
94
+ );
95
+ }
96
+ if (admission?.admittedTargets?.length !== admission?.summary?.targetCount) {
97
+ throw new CiteArkError("资产准入结果不完整,未启动资产 Agent", {
98
+ exitCode: 2,
99
+ retryable: false,
100
+ failureCode: "admission.asset_qualification_incomplete",
101
+ failureCategory: "admission",
102
+ });
103
+ }
104
+ return admission;
105
+ }
106
+
107
+ export async function verifyAssetQualification({ planPath, manifestPath }) {
108
+ const [plan, manifest] = await Promise.all([
109
+ readJson(planPath),
110
+ readJson(manifestPath),
111
+ ]);
112
+ const issues = validateAssetPreparationManifest(plan, manifest);
113
+ return {
114
+ valid: issues.length === 0,
115
+ issues,
116
+ plan,
117
+ manifest,
118
+ };
119
+ }