@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,220 @@
1
+ import { createReadStream, createWriteStream } from "node:fs";
2
+ import { mkdir, stat } from "node:fs/promises";
3
+ import { Readable } from "node:stream";
4
+ import { pipeline } from "node:stream/promises";
5
+ import path from "node:path";
6
+
7
+ import { googleApiError } from "./rest.mjs";
8
+ import { CiteArkError } from "../util.mjs";
9
+ import { executeObservedExternalRequest } from "../observability/event-contract.mjs";
10
+
11
+ export class GcsStagingClient {
12
+ constructor({ auth, fetchImpl = fetch, observe, correlation } = {}) {
13
+ this.auth = auth;
14
+ this.fetchImpl = fetchImpl;
15
+ this.observe = observe;
16
+ this.correlation = correlation;
17
+ }
18
+
19
+ async uploadFile({ bucket, object, filePath, contentType = "application/gzip" }) {
20
+ const file = await stat(filePath);
21
+ const url = new URL(`https://storage.googleapis.com/upload/storage/v1/b/${encodeURIComponent(bucket)}/o`);
22
+ url.searchParams.set("uploadType", "resumable");
23
+ url.searchParams.set("name", object);
24
+ const authorization = await this.auth.headers();
25
+ const session = await this.#request(url, {
26
+ method: "POST",
27
+ headers: {
28
+ ...authorization,
29
+ "Content-Type": "application/json; charset=UTF-8",
30
+ "X-Upload-Content-Type": contentType,
31
+ "X-Upload-Content-Length": String(file.size),
32
+ },
33
+ body: JSON.stringify({ name: object }),
34
+ }, "gcs_upload_session", { bucket, object });
35
+ if (!session.ok) throw await googleApiError(session, "POST", url);
36
+ const sessionUrl = session.headers.get("location");
37
+ if (!sessionUrl) throw new CiteArkError("Cloud Storage resumable upload 没有返回 session URL");
38
+ const response = await this.#request(sessionUrl, {
39
+ method: "PUT",
40
+ headers: {
41
+ ...authorization,
42
+ "Content-Type": contentType,
43
+ "Content-Length": String(file.size),
44
+ },
45
+ body: createReadStream(filePath),
46
+ duplex: "half",
47
+ }, "gcs_upload_object", { bucket, object });
48
+ if (!response.ok) throw await googleApiError(response, "PUT", sessionUrl);
49
+ return await response.json();
50
+ }
51
+
52
+ async downloadFile({ bucket, object, filePath }) {
53
+ const url = new URL(`https://storage.googleapis.com/storage/v1/b/${encodeURIComponent(bucket)}/o/${encodeURIComponent(object)}`);
54
+ url.searchParams.set("alt", "media");
55
+ const response = await this.#request(
56
+ url,
57
+ { headers: await this.auth.headers() },
58
+ "gcs_download_object",
59
+ { bucket, object },
60
+ );
61
+ if (!response.ok) throw await googleApiError(response, "GET", url);
62
+ await mkdir(path.dirname(filePath), { recursive: true });
63
+ await pipeline(Readable.fromWeb(response.body), createWriteStream(filePath));
64
+ return filePath;
65
+ }
66
+
67
+ async readObjectBytes({ bucket, object, offset = 0 }) {
68
+ const url = new URL(`https://storage.googleapis.com/storage/v1/b/${encodeURIComponent(bucket)}/o/${encodeURIComponent(object)}`);
69
+ url.searchParams.set('alt', 'media');
70
+ const response = await this.#request(url, {
71
+ headers: { ...await this.auth.headers(), Range: `bytes=${offset}-` },
72
+ }, 'gcs_read_live_trace', { bucket, object });
73
+ if (response.status === 404 || response.status === 416) return Buffer.alloc(0);
74
+ if (!response.ok) throw await googleApiError(response, 'GET', url);
75
+ const bytes = Buffer.from(await response.arrayBuffer());
76
+ return response.status === 206 ? bytes : bytes.subarray(offset);
77
+ }
78
+
79
+ async readJsonObject({ bucket, object, missing = undefined }) {
80
+ const url = new URL(`https://storage.googleapis.com/storage/v1/b/${encodeURIComponent(bucket)}/o/${encodeURIComponent(object)}`);
81
+ url.searchParams.set("alt", "media");
82
+ const response = await this.#request(
83
+ url,
84
+ { headers: await this.auth.headers() },
85
+ "gcs_read_json_object",
86
+ { bucket, object },
87
+ );
88
+ if (response.status === 404 && missing !== undefined) return missing;
89
+ if (!response.ok) throw await googleApiError(response, "GET", url);
90
+ return JSON.parse(await response.text());
91
+ }
92
+
93
+ async writeJsonObject({ bucket, object, value, ifGenerationMatch }) {
94
+ const url = new URL(`https://storage.googleapis.com/upload/storage/v1/b/${encodeURIComponent(bucket)}/o`);
95
+ url.searchParams.set("uploadType", "media");
96
+ url.searchParams.set("name", object);
97
+ if (ifGenerationMatch !== undefined) {
98
+ url.searchParams.set("ifGenerationMatch", String(ifGenerationMatch));
99
+ }
100
+ const response = await this.#request(url, {
101
+ method: "POST",
102
+ headers: {
103
+ ...await this.auth.headers(),
104
+ "Content-Type": "application/json; charset=UTF-8",
105
+ },
106
+ body: `${JSON.stringify(value, null, 2)}\n`,
107
+ }, "gcs_write_json_object", { bucket, object });
108
+ if (!response.ok) throw await googleApiError(response, "POST", url);
109
+ return await response.json();
110
+ }
111
+
112
+ async createJsonObjectIfAbsent({ bucket, object, value }) {
113
+ const url = new URL(`https://storage.googleapis.com/upload/storage/v1/b/${encodeURIComponent(bucket)}/o`);
114
+ url.searchParams.set("uploadType", "media");
115
+ url.searchParams.set("name", object);
116
+ url.searchParams.set("ifGenerationMatch", "0");
117
+ const response = await this.#request(url, {
118
+ method: "POST",
119
+ headers: {
120
+ ...await this.auth.headers(),
121
+ "Content-Type": "application/json; charset=UTF-8",
122
+ },
123
+ body: `${JSON.stringify(value, null, 2)}\n`,
124
+ }, "gcs_create_json_object", { bucket, object });
125
+ if (response.status === 412) return null;
126
+ if (!response.ok) throw await googleApiError(response, "POST", url);
127
+ return await response.json();
128
+ }
129
+
130
+ async readJsonObjectVersion({ bucket, object, missing = undefined }) {
131
+ const metadataUrl = new URL(`https://storage.googleapis.com/storage/v1/b/${encodeURIComponent(bucket)}/o/${encodeURIComponent(object)}`);
132
+ const metadataResponse = await this.#request(
133
+ metadataUrl,
134
+ { headers: await this.auth.headers() },
135
+ "gcs_read_object_metadata",
136
+ { bucket, object },
137
+ );
138
+ if (metadataResponse.status === 404 && missing !== undefined) return missing;
139
+ if (!metadataResponse.ok) throw await googleApiError(metadataResponse, "GET", metadataUrl);
140
+ const metadata = await metadataResponse.json();
141
+ const generation = String(metadata.generation ?? "");
142
+ if (!/^\d+$/.test(generation)) {
143
+ throw new CiteArkError("Cloud Storage 对象缺少 generation,无法安全读取");
144
+ }
145
+ const mediaUrl = new URL(metadataUrl);
146
+ mediaUrl.searchParams.set("alt", "media");
147
+ mediaUrl.searchParams.set("ifGenerationMatch", generation);
148
+ const mediaResponse = await this.#request(
149
+ mediaUrl,
150
+ { headers: await this.auth.headers() },
151
+ "gcs_read_versioned_json_object",
152
+ { bucket, object },
153
+ );
154
+ if (!mediaResponse.ok) throw await googleApiError(mediaResponse, "GET", mediaUrl);
155
+ return { generation, value: JSON.parse(await mediaResponse.text()) };
156
+ }
157
+
158
+ async copyObject({ sourceBucket, sourceObject, destinationBucket: bucket, destinationObject: object }) {
159
+ if (![sourceBucket, sourceObject, bucket, object].every((value) => typeof value === "string" && value.length > 0))
160
+ throw new CiteArkError("Cloud copy requires source and destination bucket/object");
161
+ const base = `https://storage.googleapis.com/storage/v1/b/${encodeURIComponent(sourceBucket)}/o/${encodeURIComponent(sourceObject)}/rewriteTo/b/${encodeURIComponent(bucket)}/o/${encodeURIComponent(object)}`;
162
+ let token;
163
+ do {
164
+ const url = new URL(base);
165
+ if (token) url.searchParams.set("rewriteToken", token);
166
+ const response = await this.#request(url, { method: "POST", headers: { ...await this.auth.headers(), "Content-Type": "application/json" }, body: "{}" }, "gcs_copy_recovery_object", { bucket, object });
167
+ if (!response.ok) throw await googleApiError(response, "POST", url);
168
+ const result = await response.json();
169
+ if (result.done) return result.resource;
170
+ token = result.rewriteToken;
171
+ if (!token) throw new CiteArkError("Cloud recovery copy did not provide a continuation token");
172
+ } while (token);
173
+ }
174
+
175
+ async listObjects({ bucket, prefix }) {
176
+ const names = [];
177
+ let pageToken = null;
178
+ do {
179
+ const url = new URL(`https://storage.googleapis.com/storage/v1/b/${encodeURIComponent(bucket)}/o`);
180
+ url.searchParams.set("prefix", prefix);
181
+ if (pageToken) url.searchParams.set("pageToken", pageToken);
182
+ const response = await this.#request(
183
+ url,
184
+ { headers: await this.auth.headers() },
185
+ "gcs_list_objects",
186
+ { bucket, prefix },
187
+ );
188
+ if (!response.ok) throw await googleApiError(response, "GET", url);
189
+ const page = await response.json();
190
+ names.push(...(page.items ?? []).map((item) => item.name).filter(Boolean));
191
+ pageToken = page.nextPageToken ?? null;
192
+ } while (pageToken);
193
+ return names;
194
+ }
195
+
196
+ async deleteObject({ bucket, object, ifGenerationMatch }) {
197
+ const url = new URL(`https://storage.googleapis.com/storage/v1/b/${encodeURIComponent(bucket)}/o/${encodeURIComponent(object)}`);
198
+ if (ifGenerationMatch !== undefined) {
199
+ url.searchParams.set("ifGenerationMatch", String(ifGenerationMatch));
200
+ }
201
+ const response = await this.#request(url, {
202
+ method: "DELETE",
203
+ headers: await this.auth.headers(),
204
+ }, "gcs_delete_object", { bucket, object });
205
+ if (!response.ok && response.status !== 404) throw await googleApiError(response, "DELETE", url);
206
+ }
207
+
208
+ async #request(url, init, operation, correlation) {
209
+ return executeObservedExternalRequest({
210
+ request: async () => await this.fetchImpl(url, init),
211
+ observe: this.observe,
212
+ component: "gcp_client",
213
+ service: "storage.googleapis.com",
214
+ operation,
215
+ method: init.method ?? "GET",
216
+ url,
217
+ correlation: { ...this.correlation, ...correlation },
218
+ });
219
+ }
220
+ }
@@ -0,0 +1,41 @@
1
+ import { buildArkGraphPresentation } from './presentation.mjs';
2
+ import { mkdtemp, readFile, rm } from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import os from 'node:os';
5
+ import { readCapArchive } from '../cap/v2/read.mjs';
6
+ import { createArkGraph, queryArkGraph, getArkGraphRecord, deriveArkRoute, compareArkRoutes, traceArkGraphProvenance } from './query.mjs';
7
+
8
+ export async function graphCommand(args, parseOptions) {
9
+ const options = parseOptions(args, { '--cap': { type: 'string' }, '--server': { type: 'string' },
10
+ '--query': { type: 'string' }, '--operation': { type: 'string', default: 'subgraph' } });
11
+ const query = options['--query'] ? JSON.parse(await readFile(path.resolve(options['--query']), 'utf8')) : {};
12
+ const operation = options['--operation'];
13
+ if (options['--server']) {
14
+ if (operation === 'presentation') throw Error('presentation 当前使用本地 --cap;网站读模型尚未切换');
15
+ const base = new URL(options['--server']);
16
+ if (base.protocol !== 'https:' && !['localhost', '127.0.0.1', '[::1]'].includes(base.hostname)) throw Error('远程查询必须使用 HTTPS');
17
+ const token = process.env.CITEARK_API_KEY;
18
+ const response = await fetch(new URL('/api/v1/graph', base), { method: 'POST',
19
+ headers: { 'content-type': 'application/json', ...(token ? { authorization: `Bearer ${token}` } : {}) },
20
+ body: JSON.stringify({ ...query, operation }) });
21
+ const value = await response.json();
22
+ if (!response.ok) throw Error(value.error ?? `查询失败:${response.status}`);
23
+ console.log(JSON.stringify(value, null, 2)); return;
24
+ }
25
+ if (!options['--cap']) throw Error('graph 需要 --cap(多个文件用逗号分隔)或 --server');
26
+ const temporary = await mkdtemp(path.join(os.tmpdir(), 'citeark-graph-'));
27
+ try {
28
+ const artifacts = [];
29
+ for (const [index, filename] of options['--cap'].split(',').entries()) artifacts.push(await readCapArchive(path.resolve(filename), path.join(temporary, String(index))));
30
+ const graph = createArkGraph(artifacts);
31
+ let value;
32
+ if (operation === 'subgraph') value = queryArkGraph(graph, query);
33
+ else if (operation === 'presentation') value = buildArkGraphPresentation(graph, query);
34
+ else if (operation === 'record') value = getArkGraphRecord(graph, query.reference);
35
+ else if (operation === 'route') value = deriveArkRoute(graph, query);
36
+ else if (operation === 'provenance') value = traceArkGraphProvenance(graph, query.reference, query);
37
+ else if (operation === 'compare') value = compareArkRoutes(deriveArkRoute(graph, query.left), deriveArkRoute(graph, query.right), { targets: query.targets });
38
+ else throw Error('未知 graph 操作');
39
+ console.log(JSON.stringify(value, null, 2));
40
+ } finally { await rm(temporary, { recursive: true, force: true }); }
41
+ }
@@ -0,0 +1,97 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { CAP_RECORD_TYPE as T } from './model.mjs';
4
+ import { canonicalJsonBytes, sha256Bytes } from '../cap/v2/canonical-json.mjs';
5
+ import { redactSensitive, redactString } from '../cap/redaction.mjs';
6
+
7
+ const id = (kind, identity) => `urn:citeark:${kind}:${sha256Bytes(Buffer.from(identity)).slice(7)}`;
8
+ const ref = id => ({ ref: id });
9
+ const entity = (id, role, content) => ({ $schema: 'https://citeark.com/schemas/cap/v2/entity.schema.json', id, type: T.entity, role, ...content });
10
+
11
+ /** Materialize captured inputs, process steps and trace provenance. No model calls or experiments. */
12
+ export async function enrichExecutionGraph({ runDirectory, physicalRunId, ids, actualContract,
13
+ executionRecord, experimentRecord, assessmentRecord, runnerAudit, evidenceContext, rawEvidenceRecords,
14
+ blobByDigest, addBytesBlob, objectGraph }) {
15
+ const records = [];
16
+ const edge = (predicate, subject, object, basis = 'declared', qualifiers = {}, sources = []) => {
17
+ records.push({ $schema: 'https://citeark.com/schemas/cap/v2/relation.schema.json',
18
+ id: id('relation', JSON.stringify([predicate, subject, object, qualifiers])), type: T.relation, role: 'provenance',
19
+ predicate, subject: ref(subject), object: ref(object), basis, attributedTo: ref(ids.assembler),
20
+ sources: sources.map(ref), qualifiers });
21
+ };
22
+ edge('follows', ids.execution, ids.experiment, 'declared', {}, [ids.experiment]);
23
+ const repository = actualContract.repository ?? {};
24
+ const observedRevision = executionRecord.codeState.observedRevision;
25
+ const codeId = id('code', `${repository.url}@${observedRevision ?? repository.commit}`);
26
+ const code = objectGraph?.records.find(record => record.id === codeId);
27
+ if (!code) records.push(entity(codeId, 'code', { title: repository.url, identity: { uri: repository.url, revision: observedRevision ?? repository.commit }, basis: observedRevision ? 'observed' : 'declared' }));
28
+ if (code) { code.observedRevision = executionRecord.codeState.observedRevision; code.basis = executionRecord.codeState.observedRevision ? 'observed' : 'declared'; }
29
+ edge('used', ids.execution, codeId, executionRecord.codeState.observedRevision ? 'observed' : 'declared');
30
+ // These small retained files describe externally observable inputs and changes.
31
+ for (const [filename, role, relationship] of [
32
+ ['input/agent-instructions.md', 'prompt', 'used'],
33
+ ['execution/workspace-changes.json', 'code-patch', 'generated'],
34
+ ]) {
35
+ const bytes = await readFile(path.join(runDirectory, filename)).catch(error => { if (error.code === 'ENOENT') return null; throw error; });
36
+ if (!bytes) continue;
37
+ const cleaned = role === 'code-patch' ? canonicalJsonBytes(redactSensitive(JSON.parse(bytes.toString('utf8')))) : Buffer.from(redactString(bytes.toString('utf8')));
38
+ const blob = addBytesBlob({ id: id('blob', `${physicalRunId}:${role}`), roles: [role], mediaType: role === 'prompt' ? 'text/markdown' : 'application/json', bytes: cleaned });
39
+ const materialId = id(role, `${physicalRunId}:${filename}`);
40
+ records.push(entity(materialId, role, { title: filename, blobs: [blob.digest], basis: 'observed',
41
+ disclosure: { redacted: !bytes.equals(cleaned), originalDigest: sha256Bytes(bytes), captured: true } }));
42
+ edge(relationship, ids.execution, materialId, 'observed');
43
+ }
44
+ for (const blob of blobByDigest.values()) {
45
+ if (!blob.roles.some(role => ['execution-trace', 'runner-command-records', 'assessment-evidence-context'].includes(role))) continue;
46
+ const materialId = id('trace-material', blob.digest);
47
+ records.push(entity(materialId, blob.roles.includes('assessment-evidence-context') ? 'context' : 'trace', {
48
+ title: blob.roles.join(', '), blobs: [blob.digest], basis: 'observed',
49
+ disclosure: { level: blob.roles.includes('execution-trace') ? 'minimal' : 'audit', hiddenReasoning: false, availability: blob.availability },
50
+ }));
51
+ edge('describes', materialId, ids.execution, 'observed');
52
+ if (blob.roles.includes('assessment-evidence-context')) edge('basedOn', assessmentRecord.id, materialId, 'declared', { role: 'assessment-context' });
53
+ }
54
+ const assessor = assessmentRecord.citeark?.assessor;
55
+ if (assessor?.promptDigest) {
56
+ const promptId = id('prompt', assessor.promptDigest);
57
+ records.push(entity(promptId, 'prompt', { title: 'Assessment prompt and tool context', identity: { digest: assessor.promptDigest },
58
+ basis: 'declared', disclosure: { availability: 'digest-only', templateVersion: assessor.rubricVersion ?? null },
59
+ }));
60
+ edge('basedOn', assessmentRecord.id, promptId, 'declared', { role: 'prompt-context' });
61
+ }
62
+ // The helper records real invocations and plan snapshots, but its workspace is Agent-writable.
63
+ // Preserve that weaker basis instead of upgrading it to independent runner observation.
64
+ const eventText = await readFile(path.join(runDirectory, 'output/experiment-records.jsonl'), 'utf8').catch(error => { if (error.code === 'ENOENT') return ''; throw error; });
65
+ const events = new Map();
66
+ for (const line of eventText.split(/\r?\n/).filter(Boolean)) {
67
+ let event; try { event = JSON.parse(line); } catch { continue; }
68
+ if (!event.executionId || !event.startedAt || !Array.isArray(event.command)) continue;
69
+ events.set(event.executionId, { ...events.get(event.executionId), ...event });
70
+ }
71
+ const evidenceByPath = new Map(rawEvidenceRecords.map(record => [record.citeark.sourcePath, record.id]));
72
+ for (const event of events.values()) {
73
+ const stepId = id('activity', `${physicalRunId}:${event.executionId}`);
74
+ records.push({ $schema: 'https://citeark.com/schemas/cap/v2/activity.schema.json', id: stepId, type: T.activity, role: 'execution-step',
75
+ status: event.event === 'experiment_finished' ? event.exitCode === 0 ? 'succeeded' : 'failed' : 'unknown',
76
+ startedAt: event.startedAt, ...(event.finishedAt ? { endedAt: event.finishedAt } : {}),
77
+ ...(event.scientificStepId ? { scientificStepId: event.scientificStepId } : {}),
78
+ command: redactSensitive(event.command), planDigest: event.planDigest ?? null, basis: 'declared',
79
+ capture: { source: 'experiment-records.jsonl', boundary: 'agent-writable-helper-log' },
80
+ });
81
+ edge('partOf', stepId, ids.execution, 'declared');
82
+ const plannedStep = actualContract.researchGraph?.steps?.find(step => step.id === event.scientificStepId);
83
+ if (plannedStep) {
84
+ const procedureId = id('research-step', `${actualContract.research.workId}:${plannedStep.id}`);
85
+ if (objectGraph?.records.some(record => record.id === procedureId)) edge('follows', stepId, procedureId, 'declared', { capture: 'agent-writable-helper-log' });
86
+ for (const input of plannedStep.inputIds ?? []) {
87
+ const inputId = objectGraph?.objectIds.get(input);
88
+ if (inputId) edge('used', stepId, inputId, 'declared', { binding: 'agent-declared-scientific-step' });
89
+ }
90
+ }
91
+ for (const filename of [event.stdoutPath, event.stderrPath]) if (evidenceByPath.has(filename)) edge('generated', stepId, evidenceByPath.get(filename), 'declared');
92
+ }
93
+ // Missing detail is retained explicitly; commands are never classified into guessed training phases.
94
+ executionRecord.citeark.capture = { granularity: 'runner-process', stepLogAvailable: events.size > 0,
95
+ fineGrainedScientificPhases: 'not-classified', commandRecordCount: runnerAudit?.commandRecords?.length ?? 0 };
96
+ return records;
97
+ }
@@ -0,0 +1,37 @@
1
+ /** CAP 2 scientific primitives. Roles refine a primitive; they never replace it. */
2
+ export const CAP_RECORD_TYPE = Object.freeze(Object.fromEntries(
3
+ ['entity', 'activity', 'assertion', 'agent', 'relation'].map(name =>
4
+ [name, `https://citeark.com/cap/record-types/${name}/2.0`]),
5
+ ));
6
+ export const CAP_RECORD_ROLE = Object.freeze({
7
+ actor: 'agent', sourceWork: 'source-work', claim: 'claim', experiment: 'procedure',
8
+ execution: 'execution', evidence: 'observation', assessment: 'assessment',
9
+ });
10
+ export const CAP_ROLE_TYPE = Object.freeze({
11
+ agent: 'agent', 'source-work': 'entity', claim: 'assertion', hypothesis: 'assertion',
12
+ question: 'entity', concept: 'entity', premise: 'assertion', argument: 'entity',
13
+ 'research-plan': 'entity', 'research-activity': 'activity', 'source-assessment': 'assertion',
14
+ person: 'agent', organization: 'agent', instrument: 'entity',
15
+ 'measurement-assessment': 'assertion', 'procedure-step': 'entity', procedure: 'entity', execution: 'activity', observation: 'entity', assessment: 'assertion',
16
+ });
17
+ /** Direction is subject -> object. Null permits any primitive except Relation. */
18
+ export const CAP_PREDICATES = Object.freeze({
19
+ expects: ['entity', 'entity'], hasStep: ['entity', 'entity'], about: [null, 'entity'],
20
+ requires: ['entity', 'entity'], partOf: ['activity', 'activity'], used: ['activity', 'entity'], generated: ['activity', 'entity'],
21
+ follows: ['activity', 'entity'], associatedWith: ['activity', 'agent'],
22
+ derivedFrom: ['entity', 'entity'], assertedIn: ['assertion', 'entity'],
23
+ attributedTo: [null, 'agent'], plannedFor: ['entity', 'assertion'],
24
+ evidenceFor: ['entity', 'assertion'], assesses: ['assertion', 'assertion'],
25
+ basedOn: ['assertion', null], describes: ['entity', null],
26
+ revises: [null, null], supersedes: [null, null],
27
+ });
28
+ export const CAP_BASES = new Set(['observed', 'declared', 'inferred', 'attested']);
29
+ export const CAP_ACTIVITY_STATUSES = new Set(['running', 'succeeded', 'failed', 'cancelled', 'partial', 'timedOut', 'unknown']);
30
+ export function primitiveOf(record) {
31
+ return Object.entries(CAP_RECORD_TYPE).find(([, type]) => type === record?.type)?.[0] ?? null;
32
+ }
33
+ export function scientificProjections(records) {
34
+ return Object.fromEntries(Object.entries(CAP_RECORD_ROLE).map(([name, role]) =>
35
+ [name, records.filter(record => record.role === role)]));
36
+ }
37
+ export function referenceKey(reference) { return `${reference?.ref ?? ''}@${reference?.digest ?? ''}`; }
@@ -0,0 +1,110 @@
1
+ import { CAP_RECORD_TYPE, referenceKey } from './model.mjs';
2
+ import {sourcePage} from './source-page.mjs';
3
+
4
+ export const RESEARCH_READING_TYPES = Object.freeze({
5
+ question: '研究问题', concept: '概念与定义', hypothesis: '假说与前提', claim: '主张',
6
+ method: '方法', plan: '研究方案', activity: '研究活动', result: '观测与结果',
7
+ argument: '论证与证明', assessment: '评估', resource: '对象与资源', agent: '责任主体',
8
+ });
9
+ export function readingCategory(node) {
10
+ const role = node.role;
11
+ if (node.recordType === CAP_RECORD_TYPE.agent) return 'agent';
12
+ if (node.recordType === CAP_RECORD_TYPE.activity) return 'activity';
13
+ return ({question:'question', concept:'concept', hypothesis:'hypothesis', premise:'hypothesis',
14
+ claim:'claim', method:'method', procedure:'plan', 'procedure-step':'plan', 'research-plan':'plan',
15
+ observation:'result', argument:'argument', assessment:'assessment', 'measurement-assessment':'assessment',
16
+ 'source-assessment':'assessment'})[role] ?? 'resource';
17
+ }
18
+ const titleOf = n => n.record.title ?? n.record.statement?.value ?? n.record.name ?? n.record.evaluationPlan?.title ?? n.role;
19
+
20
+ /** A complete presentation of one authorized, verified snapshot. Never infer science
21
+ * from labels, discard hidden objects, or claim that a visual edge authorizes execution.
22
+ */
23
+ export function buildArkGraphPresentation(graph, { artifactDigest } = {}) {
24
+ if (graph.truncated) throw Error('ArkGraph presentation requires a complete snapshot');
25
+ const digest = artifactDigest ?? (graph.artifacts.length === 1 ? graph.artifacts[0] : null);
26
+ if (!digest || !graph.artifacts.includes(digest)) throw Error('Choose one exact ArkGraph snapshot');
27
+ const records = graph.nodes.filter(n => n.artifacts.includes(digest));
28
+ const byRef = new Map(records.map(n => [n.ref, n])), byKey = new Map(records.map(n => [n.key, n]));
29
+ const source = records.find(n => n.role === 'source-work' && n.record.citeark?.reading)
30
+ ?? records.find(n => n.role === 'source-work');
31
+ const view = source?.record.citeark?.reading;
32
+ const resolve = reference => {
33
+ const node = reference?.digest ? byKey.get(referenceKey(reference)) : byRef.get(reference?.ref);
34
+ if (!node) throw Error(`Reading reference missing from snapshot: ${reference?.ref}`);
35
+ return node.key;
36
+ };
37
+ const labels = new Map((view?.labels ?? []).map(n => [resolve(n), n]));
38
+ const nodes = records.map(n => {
39
+ const r = n.record, category = readingCategory(n), label = labels.get(n.key);
40
+ const locations = r.citeark?.sourceLocators ?? r.sourceContext?.map(s => ({locator:s.selector?.label}))
41
+ ?? (r.citeark?.sourceLocator ? [r.citeark.sourceLocator] : []);
42
+ const measurements = (r.measurements ?? r.citeark?.reportedMeasurements ?? (r.measurement ? [r.measurement] : [])).map(m => ({
43
+ ...m, id: m.measurementId ?? m.id, value: m.value?.decimal ?? m.value,
44
+ dimensions: m.scope ?? m.dimensions ?? {}, conditions: m.conditions ?? {},
45
+ }));
46
+ return { key:n.key, local:r.citeark?.objectId ?? r.citeark?.localId ?? n.ref, ref:n.ref,
47
+ digest:n.digest, recordType:n.recordType, role:n.role, category,
48
+ subtype:label?.subtype ?? RESEARCH_READING_TYPES[category], label:label?.title ?? titleOf(n), brief:r.description ?? r.statement?.value ?? r.evaluationPlan?.instructions ?? r.abstract ?? '',
49
+ source:locations.map(s => s.locator).filter(Boolean).join(' · '),
50
+ sourceLocators: locations, sourcePage:sourcePage(locations) ?? undefined, measurements, conditions:r.conditions ?? r.citeark?.conditions ?? r.scope ?? {},
51
+ limitations:[...(r.citeark?.limitations ?? r.limitations ?? []), ...(r.citeark?.ambiguities ?? [])],
52
+ prospective:r.prospective === true || n.role === 'procedure' || n.role === 'procedure-step',
53
+ basis:r.basis ?? r.origin?.basis ?? null, raw:r,
54
+ };
55
+ });
56
+ const edges = graph.relations.filter(e => e.artifacts.includes(digest)).map(e => ({
57
+ id:e.key, ref:e.ref, digest:e.digest, from:referenceKey(e.subject), to:referenceKey(e.object),
58
+ predicate:e.predicate, basis:e.record.basis, raw:e.record,
59
+ available:byKey.has(referenceKey(e.subject)) && byKey.has(referenceKey(e.object)),
60
+ source:(e.record.qualifiers?.sourceLocators ?? []).map(s => s.locator).join(' · '),
61
+ qualification:e.record.qualifiers?.note ?? '',
62
+ }));
63
+ const spine = view?.mainline.map(resolve) ?? records.filter(n => n.role === 'claim').map(n => n.key);
64
+ if (!spine.length && nodes.length) spine.push(nodes[0].key);
65
+ const parentMap = new Map((view?.branches ?? []).flatMap(b => b.children.map(c => [resolve(c), resolve(b.parent)])));
66
+ const rootOf = key => { const seen = new Set(); while (parentMap.has(key)) { if (seen.has(key)) throw Error('Cyclic reading branches'); seen.add(key); key = parentMap.get(key); } return key; };
67
+ const groups = spine.map(parent => ({parent, label:labels.get(parent)?.title ?? titleOf(byKey.get(parent)),
68
+ members:nodes.filter(n => parentMap.get(n.key) === parent
69
+ && !['result', 'agent'].includes(n.category) && n.role !== 'source-work').map(n => n.key)}));
70
+ const primary = [...new Set([...spine, ...groups.flatMap(g => g.members)])];
71
+ const exact = node => ({ref:node.ref,digest:node.digest,recordType:node.recordType});
72
+ const experiments = records.filter(n => n.role === 'procedure').map((n, index) => {
73
+ const outgoing = (key, predicate) => edges.filter(e => e.available && e.from === key && e.predicate === predicate).map(e => e.to);
74
+ const targets = outgoing(n.key, 'plannedFor'), inputs = outgoing(n.key, 'requires');
75
+ const stepKeys = new Set(), visiting = new Set(), visit = key => { if (stepKeys.has(key)) return; if(visiting.has(key))throw Error('Cyclic scientific step dependencies'); visiting.add(key); for (const dependency of outgoing(key,'requires')) if (byKey.get(dependency)?.role === 'procedure-step') visit(dependency); visiting.delete(key); stepKeys.add(key); };
76
+ const declaredSteps = outgoing(n.key,'hasStep');
77
+ for (const reference of n.record.steps ?? []) { const key=resolve(reference); if(declaredSteps.includes(key))visit(key); }
78
+ for (const step of declaredSteps) visit(step);
79
+ const intent = n.record.citeark?.researchIntent, from = intent ? resolve(intent.from) : inputs.find(k => byKey.get(k).role === 'method') ?? inputs[0] ?? n.key;
80
+ const to = intent ? resolve(intent.to) : targets[0];
81
+ if (!to || !targets.includes(to)) throw Error(`Experiment ${n.ref} lacks its declared target`);
82
+ const context = [...new Set([from,to,...targets,...inputs,...(intent?.context ?? []).map(resolve)])];
83
+ return { id:n.key, experimentId:n.record.citeark?.localId, procedure:exact(n), index,
84
+ source:from, target:to, question:intent?.question ?? titleOf(n), title:titleOf(n),
85
+ summary:intent?.comparison ?? n.record.citeark?.protocol?.objective ?? '',
86
+ criterion:intent?.criterion ?? '',
87
+ limitations:[...new Set([...(n.record.citeark?.observationTargets ?? []).map(t=>t.limitation?.evidence),...targets.map(k=>byKey.get(k).record.citeark?.reproduction).filter(r=>r?.status==='blocked').map(r=>r.reason)].filter(Boolean))],
88
+ contextNodeIds:context, targetKeys:targets, inputKeys:inputs,
89
+ stepKeys:[...stepKeys], requiredProcedureKeys:inputs.filter(k => byKey.get(k).role === 'procedure'),
90
+ unresolvedInputs:edges.filter(e => e.from === n.key && e.predicate === 'requires' && !e.available).map(e => e.raw.object),
91
+ };
92
+ });
93
+ experiments.sort((a,b)=>spine.indexOf(rootOf(a.target))-spine.indexOf(rootOf(b.target)));
94
+ experiments.forEach((experiment,index)=>{experiment.index=index;});
95
+ // Visible candidate anchors are existing scientific objects; they are never new procedures.
96
+ for (const e of experiments) for (const id of [e.source,e.target]) if (!primary.includes(id)) {
97
+ primary.push(id); const group=groups.find(g => g.parent === rootOf(id)) ?? groups.find(g => g.parent === e.target) ?? groups[0];
98
+ if (group && id !== group.parent && !spine.includes(id) && !group.members.includes(id)) group.members.push(id);
99
+ }
100
+ const paperUri=source?.record.sources?.find(s=>s.kind==='paper')?.uri;
101
+ const sourceDocument=typeof paperUri==='string'&&/^https?:\/\//i.test(paperUri)?paperUri:undefined;
102
+ return { schemaVersion:'arkgraph-presentation/1', digest, sourceDocument, paperTitle:source?.record.title ?? 'ArkGraph',
103
+ titleZh:source?.record.title ?? 'ArkGraph', question:view?.overview ?? source?.record.abstract ?? '',
104
+ provenance:'Agent 生成 · 来源声明', nodes, edges, experiments,
105
+ presentation:{spine,primary,groups,contextGroups:[...new Set(parentMap.values())].map(parent=>({parent,children:nodes.filter(n=>parentMap.get(n.key)===parent&&!primary.includes(n.key)).map(n=>n.key)})),shortLabels:Object.fromEntries(nodes.map(n => [n.key,n.label]))},
106
+ classification:{assignments:Object.fromEntries(nodes.map(n => [n.key,{category:n.category,
107
+ subtype:labels.get(n.key)?.subtype ?? RESEARCH_READING_TYPES[n.category], reason:'', contextualRoles:[]}]))},
108
+ completeness:{nodes:records.length,relations:edges.length,unavailableRelations:edges.filter(e=>!e.available).length},
109
+ };
110
+ }