@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,642 @@
1
+ import { validateGraphRecords } from '../../graph/validate.mjs';
2
+ import { CAP_RECORD_ROLE } from '../../graph/model.mjs';
3
+ import { publicContractPolicyIssues } from "../../contracts/public-contract.mjs";
4
+ import { lstat, readFile, readdir } from "node:fs/promises";
5
+ import path from "node:path";
6
+
7
+ import {
8
+ assertSha256Digest,
9
+ parseCanonicalJson,
10
+ sha256Bytes,
11
+ } from "./canonical-json.mjs";
12
+ import {
13
+ CAP_MANIFEST,
14
+ CAP_MANIFEST_MEDIA_TYPE,
15
+ CAP_PROFILE,
16
+ CAP_RECORD_TYPE,
17
+ CAP_SPEC_VERSION,
18
+ CAP_READABLE_SPEC_VERSIONS,
19
+ capPolicyCommitment,
20
+ safeCapPath,
21
+ } from "./core.mjs";
22
+ import { verifyCapAttestation } from "./attestation.mjs";
23
+
24
+ const KNOWN_PROFILES = new Set(Object.values(CAP_PROFILE));
25
+ const DIGEST_PATTERN = /^sha256:[0-9a-f]{64}$/;
26
+
27
+ export async function verifyCapDirectory(directory, options = {}) {
28
+ const root = path.resolve(directory);
29
+ const issues = [];
30
+ const warnings = [];
31
+ const entries = await listRegularEntries(root, issues);
32
+ const manifestPath = path.join(root, CAP_MANIFEST);
33
+ let manifest;
34
+ let manifestBytes;
35
+ try {
36
+ manifestBytes = await readFile(manifestPath);
37
+ manifest = parseCanonicalJson(manifestBytes, CAP_MANIFEST);
38
+ } catch (error) {
39
+ issues.push(error instanceof Error ? error.message : String(error));
40
+ return emptyReport();
41
+ }
42
+ const artifactDigest = sha256Bytes(manifestBytes);
43
+ validateManifestEnvelope(manifest, issues, warnings);
44
+
45
+ const recordBodies = new Map();
46
+ const recordDescriptors = validateRecordDescriptors(manifest.records, issues);
47
+ const blobDescriptors = validateBlobDescriptors(manifest.blobs, issues);
48
+ const declaredArtifactPaths = new Set([CAP_MANIFEST]);
49
+
50
+ for (const descriptor of recordDescriptors.values()) {
51
+ declaredArtifactPaths.add(descriptor.path);
52
+ const target = path.join(root, descriptor.path);
53
+ try {
54
+ const bytes = await readFile(target);
55
+ if (bytes.byteLength !== descriptor.size) issues.push(`Record size mismatch: ${descriptor.id}`);
56
+ if (sha256Bytes(bytes) !== descriptor.digest) issues.push(`Record digest mismatch: ${descriptor.id}`);
57
+ const body = parseCanonicalJson(bytes, `Record ${descriptor.id}`);
58
+ if (body?.id !== descriptor.id) issues.push(`Record body id does not match descriptor: ${descriptor.id}`);
59
+ if (body?.type !== descriptor.recordType) issues.push(`Record body type does not match descriptor: ${descriptor.id}`);
60
+ validateRecordBody(body, descriptor, issues);
61
+ recordBodies.set(descriptor.id, body);
62
+ } catch (error) {
63
+ issues.push(error instanceof Error ? error.message : String(error));
64
+ }
65
+ }
66
+
67
+ let embedded = 0;
68
+ let external = 0;
69
+ let withheld = 0;
70
+ for (const descriptor of blobDescriptors.values()) {
71
+ if (descriptor.availability === "embedded") {
72
+ embedded += 1;
73
+ declaredArtifactPaths.add(descriptor.path);
74
+ try {
75
+ const bytes = await readFile(path.join(root, descriptor.path));
76
+ if (bytes.byteLength !== descriptor.size) issues.push(`Blob size mismatch: ${descriptor.id}`);
77
+ if (sha256Bytes(bytes) !== descriptor.digest) issues.push(`Blob digest mismatch: ${descriptor.id}`);
78
+ } catch (error) {
79
+ issues.push(`Cannot read embedded Blob ${descriptor.id}: ${error instanceof Error ? error.message : String(error)}`);
80
+ }
81
+ } else if (descriptor.availability === "external") {
82
+ external += 1;
83
+ } else if (descriptor.availability === "withheld") {
84
+ withheld += 1;
85
+ }
86
+ }
87
+
88
+ validateDeclaredPaths(entries, declaredArtifactPaths, issues);
89
+ validateRootsAndCreator(manifest, recordDescriptors, recordBodies, issues);
90
+ validateRecordReferences(recordBodies, recordDescriptors, artifactDigest, issues);
91
+ validateGraphRecords(recordBodies, recordDescriptors, issues);
92
+ for (const body of recordBodies.values()) {
93
+ if (body.blobs != null && !Array.isArray(body.blobs)) issues.push(`Record ${body.id}: blobs must be an array`);
94
+ for (const digest of Array.isArray(body.blobs) ? body.blobs : []) {
95
+ if (!blobDescriptors.has(digest)) issues.push(`Record ${body.id} references an unknown Blob digest: ${digest}`);
96
+ }
97
+ }
98
+ validateScientificBindings(recordBodies, blobDescriptors, issues, manifest.profiles ?? []);
99
+ await validateLocations(root, entries, blobDescriptors, issues, warnings);
100
+
101
+ const attestations = await verifyAttestations(root, entries, artifactDigest, options, warnings);
102
+ for (const attestation of attestations) issues.push(...attestation.issues.map((item) => `Attestation: ${item}`));
103
+ await validateProfiles({
104
+ root,
105
+ manifest,
106
+ entries,
107
+ recordBodies,
108
+ blobDescriptors,
109
+ attestations,
110
+ issues,
111
+ warnings,
112
+ });
113
+
114
+ return {
115
+ valid: issues.length === 0,
116
+ protocolVersion: CAP_SPEC_VERSION,
117
+ artifactDigest,
118
+ manifest,
119
+ issues,
120
+ warnings,
121
+ materialization: { embedded, external, withheld },
122
+ attestations,
123
+ };
124
+
125
+ function emptyReport() {
126
+ return {
127
+ valid: false,
128
+ protocolVersion: CAP_SPEC_VERSION,
129
+ artifactDigest: null,
130
+ manifest: manifest ?? null,
131
+ issues,
132
+ warnings,
133
+ materialization: { embedded: 0, external: 0, withheld: 0 },
134
+ attestations: [],
135
+ };
136
+ }
137
+ }
138
+
139
+ function validateManifestEnvelope(manifest, issues, warnings) {
140
+ if (!manifest || typeof manifest !== "object" || Array.isArray(manifest)) {
141
+ issues.push("CAP manifest must be a JSON object");
142
+ return;
143
+ }
144
+ if (!CAP_READABLE_SPEC_VERSIONS.has(manifest.specVersion)) issues.push(`Unsupported CAP specVersion: ${manifest.specVersion ?? "<missing>"}`);
145
+ if (manifest.mediaType !== CAP_MANIFEST_MEDIA_TYPE) issues.push("CAP manifest mediaType is invalid");
146
+ if (!manifest.artifact || typeof manifest.artifact !== "object") issues.push("CAP manifest requires artifact metadata");
147
+ if (!Array.isArray(manifest.profiles) || !manifest.profiles.includes(CAP_PROFILE.core)) {
148
+ issues.push("CAP manifest must declare the Core Profile");
149
+ } else {
150
+ for (const profile of manifest.profiles) {
151
+ if (!KNOWN_PROFILES.has(profile)) warnings.push(`Unknown optional CAP Profile: ${profile}`);
152
+ }
153
+ }
154
+ if (!Array.isArray(manifest.roots) || manifest.roots.length === 0) issues.push("CAP manifest requires at least one root Record");
155
+ if (!Array.isArray(manifest.records) || manifest.records.length === 0) issues.push("CAP manifest requires Record descriptors");
156
+ if (!Array.isArray(manifest.blobs)) issues.push("CAP manifest blobs must be an array");
157
+ if (!Array.isArray(manifest.relations)) issues.push("CAP manifest relations must be an array");
158
+ for (const [index, relation] of (manifest.relations ?? []).entries()) {
159
+ if (typeof relation?.relationship !== "string" || !relation.relationship) issues.push(`relations[${index}] requires relationship`);
160
+ if (!DIGEST_PATTERN.test(relation?.artifactDigest)) issues.push(`relations[${index}] requires artifactDigest`);
161
+ if (relation?.recordDigest != null && !DIGEST_PATTERN.test(relation.recordDigest)) issues.push(`relations[${index}] recordDigest is invalid`);
162
+ }
163
+ }
164
+
165
+ function validateRecordDescriptors(value, issues) {
166
+ const output = new Map();
167
+ const paths = new Set();
168
+ const digests = new Set();
169
+ for (const [index, descriptor] of (Array.isArray(value) ? value : []).entries()) {
170
+ if (!descriptor || typeof descriptor !== "object") {
171
+ issues.push(`records[${index}] must be an object`);
172
+ continue;
173
+ }
174
+ if (typeof descriptor.id !== "string" || !descriptor.id) issues.push(`records[${index}] requires id`);
175
+ if (typeof descriptor.role !== "string" || !descriptor.role) issues.push(`records[${index}] requires role`);
176
+ if (typeof descriptor.recordType !== "string" || !descriptor.recordType) issues.push(`records[${index}] requires recordType`);
177
+ if (typeof descriptor.mediaType !== "string" || !descriptor.mediaType) issues.push(`records[${index}] requires mediaType`);
178
+ if (typeof descriptor.schema !== "string" || !descriptor.schema) issues.push(`records[${index}] requires schema`);
179
+ if (!DIGEST_PATTERN.test(descriptor.digest)) issues.push(`records[${index}] digest is invalid`);
180
+ if (!Number.isSafeInteger(descriptor.size) || descriptor.size < 0) issues.push(`records[${index}] size is invalid`);
181
+ if (!safeCapPath(descriptor.path) || !descriptor.path.startsWith("records/")) { issues.push(`records[${index}] path is invalid`); continue; }
182
+ if (output.has(descriptor.id)) issues.push(`Duplicate Record id: ${descriptor.id}`);
183
+ if (paths.has(descriptor.path)) issues.push(`Duplicate Record path: ${descriptor.path}`);
184
+ if (digests.has(descriptor.digest)) issues.push(`Duplicate Record digest: ${descriptor.digest}`);
185
+ output.set(descriptor.id, descriptor);
186
+ paths.add(descriptor.path);
187
+ digests.add(descriptor.digest);
188
+ }
189
+ return output;
190
+ }
191
+
192
+ function validateBlobDescriptors(value, issues) {
193
+ const output = new Map();
194
+ const ids = new Set();
195
+ const paths = new Set();
196
+ for (const [index, descriptor] of (Array.isArray(value) ? value : []).entries()) {
197
+ if (!descriptor || typeof descriptor !== "object") {
198
+ issues.push(`blobs[${index}] must be an object`);
199
+ continue;
200
+ }
201
+ if (typeof descriptor.id !== "string" || !descriptor.id) issues.push(`blobs[${index}] requires id`);
202
+ if (!Array.isArray(descriptor.roles)) issues.push(`blobs[${index}] roles must be an array`);
203
+ if (typeof descriptor.mediaType !== "string" || !descriptor.mediaType) issues.push(`blobs[${index}] requires mediaType`);
204
+ if (!DIGEST_PATTERN.test(descriptor.digest)) issues.push(`blobs[${index}] digest is invalid`);
205
+ if (!Number.isSafeInteger(descriptor.size) || descriptor.size < 0) issues.push(`blobs[${index}] size is invalid`);
206
+ if (!new Set(["embedded", "external", "withheld"]).has(descriptor.availability)) issues.push(`blobs[${index}] availability is invalid`);
207
+ if (descriptor.availability === "embedded") {
208
+ if (!safeCapPath(descriptor.path) || !descriptor.path.startsWith("blobs/")) { issues.push(`blobs[${index}] embedded path is invalid`); continue; }
209
+ const expected = casBlobPath(descriptor.digest);
210
+ if (descriptor.path !== expected) issues.push(`Embedded Blob path is not content-addressed: ${descriptor.id}`);
211
+ if (paths.has(descriptor.path)) issues.push(`Duplicate Blob path: ${descriptor.path}`);
212
+ paths.add(descriptor.path);
213
+ } else if (descriptor.path != null) {
214
+ issues.push(`${descriptor.availability} Blob must not have a bundle path: ${descriptor.id}`);
215
+ }
216
+ if (ids.has(descriptor.id)) issues.push(`Duplicate Blob id: ${descriptor.id}`);
217
+ if (output.has(descriptor.digest)) issues.push(`Duplicate Blob digest: ${descriptor.digest}`);
218
+ ids.add(descriptor.id);
219
+ output.set(descriptor.digest, descriptor);
220
+ }
221
+ return output;
222
+ }
223
+
224
+ function validateRecordBody(body, descriptor, issues) {
225
+ const requireText = (value, field) => {
226
+ if (typeof value !== "string" || !value) issues.push(`Record ${descriptor.id} requires ${field}`);
227
+ };
228
+ if (!body || typeof body !== "object" || Array.isArray(body)) return;
229
+ if (body.$schema != null && body.$schema !== descriptor.schema) issues.push(`Record ${descriptor.id} $schema does not match its descriptor`);
230
+ switch (body.role) {
231
+ case CAP_RECORD_ROLE.actor:
232
+ requireText(body.kind, "kind");
233
+ requireText(body.name, "name");
234
+ break;
235
+ case CAP_RECORD_ROLE.sourceWork:
236
+ requireText(body.title, "title");
237
+ if (!Array.isArray(body.sources) || body.sources.length === 0) issues.push(`SourceWork ${body.id} requires sources`);
238
+ break;
239
+ case CAP_RECORD_ROLE.claim:
240
+ if (typeof body.statement?.value !== "string" || !body.statement.value) issues.push(`Claim ${body.id} requires statement.value`);
241
+
242
+ break;
243
+ case CAP_RECORD_ROLE.experiment:
244
+
245
+
246
+ break;
247
+ case CAP_RECORD_ROLE.execution:
248
+
249
+ requireText(body.status, "status");
250
+
251
+ break;
252
+ case CAP_RECORD_ROLE.evidence:
253
+ requireText(body.kind, "kind");
254
+ if (!new Set(["observed", "declared", "inferred", "attested"]).has(body.basis)) issues.push(`Evidence ${body.id} basis is invalid`);
255
+
256
+ break;
257
+ case CAP_RECORD_ROLE.assessment:
258
+
259
+ if (!Array.isArray(body.evidence)) issues.push(`Assessment ${body.id} evidence must be an array`);
260
+ if (!new Set(["supports", "challenges", "contradicts", "inconclusive"]).has(body.conclusion)) issues.push(`Assessment ${body.id} conclusion is invalid`);
261
+ if (!body.method || typeof body.method !== "object") issues.push(`Assessment ${body.id} requires method`);
262
+ break;
263
+ default:
264
+ break;
265
+ }
266
+ }
267
+
268
+ function validateDeclaredPaths(entries, declared, issues) {
269
+ for (const expected of declared) {
270
+ if (!entries.files.has(expected)) issues.push(`Declared Artifact file is missing: ${expected}`);
271
+ }
272
+ for (const filename of entries.files) {
273
+ if (declared.has(filename) || allowedBundleAttachment(filename)) continue;
274
+ issues.push(`Bundle contains an undeclared Artifact file: ${filename}`);
275
+ }
276
+ }
277
+
278
+ function validateRootsAndCreator(manifest, records, bodies, issues) {
279
+ validateReference(manifest.artifact?.createdBy, records, "artifact.createdBy", issues);
280
+ const creator = records.get(manifest.artifact?.createdBy?.ref);
281
+ if (creator && creator.recordType !== CAP_RECORD_TYPE.agent) issues.push("artifact.createdBy must reference an Actor Record");
282
+ for (const [index, root] of (manifest.roots ?? []).entries()) {
283
+ if (typeof root?.role !== "string" || !root.role) issues.push(`roots[${index}] requires role`);
284
+ validateReference(root, records, `roots[${index}]`, issues);
285
+ }
286
+ if (manifest.artifact?.createdAt && !Number.isFinite(Date.parse(manifest.artifact.createdAt))) issues.push("artifact.createdAt is not a date-time");
287
+ if (bodies.size !== records.size) issues.push("Not every Record descriptor resolved to a valid Record body");
288
+ }
289
+
290
+ function validateRecordReferences(bodies, descriptors, currentArtifactDigest, issues) {
291
+ for (const [recordId, body] of bodies) walk(body, `$record:${recordId}`);
292
+
293
+ function walk(value, pointer) {
294
+ if (Array.isArray(value)) {
295
+ value.forEach((item, index) => walk(item, `${pointer}/${index}`));
296
+ return;
297
+ }
298
+ if (!value || typeof value !== "object") return;
299
+ if (typeof value.ref === "string") {
300
+ if (value.artifactDigest) {
301
+ if (!DIGEST_PATTERN.test(value.artifactDigest)) issues.push(`${pointer}.artifactDigest is invalid`);
302
+ if (!DIGEST_PATTERN.test(value.digest)) issues.push(`${pointer}.digest is required for an external Record reference`);
303
+ if (typeof value.recordType !== "string" || !value.recordType) issues.push(`${pointer}.recordType is required for an external Record reference`);
304
+ if (value.artifactDigest === currentArtifactDigest) validateReference(value, descriptors, pointer, issues);
305
+ } else {
306
+ validateReference(value, descriptors, pointer, issues, false);
307
+ }
308
+ return;
309
+ }
310
+ for (const [key, child] of Object.entries(value)) {
311
+ if (key === "$schema") continue;
312
+ walk(child, `${pointer}/${key}`);
313
+ }
314
+ }
315
+ }
316
+
317
+ function validateScientificBindings(bodies, blobs, issues, profiles) {
318
+ const reproduction = profiles.includes(CAP_PROFILE.reproduction);
319
+ const typeOf = (reference) => reference?.artifactDigest
320
+ ? reference.role
321
+ : bodies.get(reference?.ref)?.role;
322
+ const domainProfile = profiles.some(profile => [CAP_PROFILE.reproduction, CAP_PROFILE.researchCompilation, CAP_PROFILE.researchPlan, CAP_PROFILE.computationalRun].includes(profile));
323
+ for (const body of bodies.values()) {
324
+ // Core also accepts relation-only and paper-free scientific records.
325
+ if (!domainProfile) continue;
326
+ if (body.role === "claim") {
327
+ if (body.source?.work && typeOf(body.source?.work) !== CAP_RECORD_ROLE.sourceWork) issues.push(`Claim ${body.id} source.work must reference a SourceWork Record`);
328
+ if (body.origin?.actor && typeOf(body.origin?.actor) !== CAP_RECORD_ROLE.actor) issues.push(`Claim ${body.id} origin.actor must reference an Actor Record`);
329
+ } else if (body.role === "procedure") {
330
+ for (const reference of body.tests ?? []) {
331
+ if (typeOf(reference) !== CAP_RECORD_ROLE.claim) issues.push(`Experiment ${body.id} tests must reference Claim Records`);
332
+ }
333
+ for (const issue of publicContractPolicyIssues(body.publicContract ?? {})) {
334
+ issues.push(`Experiment ${body.id} publicContract: ${issue}`);
335
+ }
336
+ } else if (body.role === "execution") {
337
+ if (body.experiment && typeOf(body.experiment) !== CAP_RECORD_ROLE.experiment) issues.push(`Execution ${body.id} experiment must reference an Experiment Record`);
338
+ for (const item of body.actors ?? []) {
339
+ if (typeOf(item.actor) !== CAP_RECORD_ROLE.actor) issues.push(`Execution ${body.id} actors must reference Actor Records`);
340
+ }
341
+ if (body.reproduced != null || body.verificationStatus != null) {
342
+ issues.push(`Execution ${body.id} must not contain a scientific reproduction verdict`);
343
+ }
344
+ if (body.trace?.blobDigest) {
345
+ const trace = blobs.get(body.trace.blobDigest);
346
+ if (!trace || !trace.roles.includes("execution-trace")) issues.push(`Execution ${body.id} trace must reference an execution-trace Blob`);
347
+ }
348
+ } else if (body.role === "observation") {
349
+ if (body.generatedBy && typeOf(body.generatedBy) !== CAP_RECORD_ROLE.execution) issues.push(`Evidence ${body.id} generatedBy must reference an Execution Record`);
350
+ for (const digest of [...(body.parser?.inputs ?? []), ...(body.blobs ?? [])]) {
351
+ if (!blobs.has(digest)) issues.push(`Evidence ${body.id} references an unknown Blob digest: ${digest}`);
352
+ }
353
+ } else if (body.role === "assessment") {
354
+ if (body.claim && typeOf(body.claim) !== CAP_RECORD_ROLE.claim) issues.push(`Assessment ${body.id} claim must reference a Claim Record`);
355
+ if (body.experiment && typeOf(body.experiment) !== CAP_RECORD_ROLE.experiment) issues.push(`Assessment ${body.id} experiment must reference an Experiment Record`);
356
+ if ((reproduction || body.execution) && typeOf(body.execution) !== CAP_RECORD_ROLE.execution) issues.push(`Assessment ${body.id} execution must reference an Execution Record`);
357
+ if (typeOf(body.performedBy) !== CAP_RECORD_ROLE.actor) issues.push(`Assessment ${body.id} performedBy must reference an Actor Record`);
358
+ for (const reference of body.evidence ?? []) {
359
+ if (typeOf(reference) !== CAP_RECORD_ROLE.evidence) issues.push(`Assessment ${body.id} evidence must reference Evidence Records`);
360
+ }
361
+ const experiment = bodies.get(body.experiment?.ref);
362
+ const execution = bodies.get(body.execution?.ref);
363
+ if (experiment && !(experiment.tests ?? []).some((item) => sameReference(item, body.claim))) issues.push(`Assessment ${body.id} Claim is not tested by its Experiment`);
364
+ if (reproduction && experiment && !DIGEST_PATTERN.test(experiment.publicContract?.verificationPolicyCommitment)) {
365
+ issues.push(`Assessment ${body.id} requires its Experiment to bind a verification policy commitment`);
366
+ }
367
+ if (execution?.experiment && !sameReference(execution.experiment, body.experiment)) issues.push(`Assessment ${body.id} Execution does not instantiate its Experiment`);
368
+ for (const reference of body.evidence ?? []) {
369
+ const evidence = bodies.get(reference.ref);
370
+ if (evidence?.generatedBy && !sameReference(evidence.generatedBy, body.execution)) issues.push(`Assessment ${body.id} Evidence was generated by a different Execution`);
371
+ }
372
+ const reveal = body.method?.policyReveal;
373
+ if (reproduction && !reveal) {
374
+ issues.push(`Assessment ${body.id} requires a verification policy reveal`);
375
+ } else if (reveal) {
376
+ try {
377
+ const actual = capPolicyCommitment({ nonce: reveal.nonce, policy: reveal.policy });
378
+ if (reveal.algorithm !== "citeark-policy-commitment-v1") issues.push(`Assessment ${body.id} policy commitment algorithm is unsupported`);
379
+ if (actual !== body.method?.policyCommitment) issues.push(`Assessment ${body.id} policy reveal does not match method.policyCommitment`);
380
+ if (experiment && actual !== experiment.publicContract?.verificationPolicyCommitment) issues.push(`Assessment ${body.id} policy reveal does not match the Experiment pre-execution commitment`);
381
+ } catch (error) {
382
+ issues.push(`Assessment ${body.id} policy reveal is invalid: ${error instanceof Error ? error.message : String(error)}`);
383
+ }
384
+ }
385
+ }
386
+ }
387
+ }
388
+
389
+ function validateReference(reference, records, label, issues, requireDigest = true) {
390
+ if (!reference || typeof reference.ref !== "string" || !reference.ref) {
391
+ issues.push(`${label} is not a CAP Record reference`);
392
+ return;
393
+ }
394
+ const descriptor = records.get(reference.ref);
395
+ if (!descriptor) {
396
+ issues.push(`${label} references an unknown Record: ${reference.ref}`);
397
+ return;
398
+ }
399
+ if (requireDigest && reference.digest !== descriptor.digest) issues.push(`${label} does not bind the current Record digest`);
400
+ if (!requireDigest && reference.digest != null && reference.digest !== descriptor.digest) issues.push(`${label} Record digest is inconsistent`);
401
+ }
402
+
403
+ async function validateLocations(root, entries, blobs, issues, warnings) {
404
+ if (!entries.files.has("cap-locations.json")) return;
405
+ try {
406
+ const locations = JSON.parse(await readFile(path.join(root, "cap-locations.json"), "utf8"));
407
+ if (!Array.isArray(locations?.objects)) throw new Error("cap-locations.json requires objects[]");
408
+ for (const [index, object] of locations.objects.entries()) {
409
+ if (!DIGEST_PATTERN.test(object?.digest)) issues.push(`cap-locations objects[${index}] digest is invalid`);
410
+ else if (!blobs.has(object.digest)) warnings.push(`cap-locations references an unknown Blob digest: ${object.digest}`);
411
+ if (!Array.isArray(object?.locations)) issues.push(`cap-locations objects[${index}] locations must be an array`);
412
+ }
413
+ } catch (error) {
414
+ issues.push(`Invalid cap-locations.json: ${error instanceof Error ? error.message : String(error)}`);
415
+ }
416
+ }
417
+
418
+ async function verifyAttestations(root, entries, artifactDigest, options, warnings) {
419
+ const output = [];
420
+ for (const filename of [...entries.files].filter((item) => item.startsWith("attestations/") && item.endsWith(".json")).sort()) {
421
+ try {
422
+ const value = JSON.parse(await readFile(path.join(root, filename), "utf8"));
423
+ const report = verifyCapAttestation(value, {
424
+ artifactDigest,
425
+ trustedKeyDigests: options.trustedKeyDigests ?? [],
426
+ });
427
+ if (!report.recognized) {
428
+ warnings.push(`Unsupported attestation attachment ignored: ${filename}`);
429
+ continue;
430
+ }
431
+ output.push({ path: filename, ...report });
432
+ warnings.push(...report.warnings.map((item) => `${filename}: ${item}`));
433
+ } catch (error) {
434
+ output.push({
435
+ path: filename,
436
+ recognized: true,
437
+ signatureValid: false,
438
+ subjectValid: false,
439
+ trusted: false,
440
+ issues: [error instanceof Error ? error.message : String(error)],
441
+ warnings: [],
442
+ });
443
+ }
444
+ }
445
+ return output;
446
+ }
447
+
448
+ async function validateProfiles({ root, manifest, entries, recordBodies, blobDescriptors, attestations, issues }) {
449
+ const types = new Set([...recordBodies.values()].map((item) => item.role));
450
+ const requireTypes = (profile, required) => {
451
+ if (!manifest.profiles.includes(profile)) return;
452
+ for (const type of required) {
453
+ if (!types.has(type)) issues.push(`Profile ${profile} requires Record type ${type}`);
454
+ }
455
+ };
456
+ requireTypes(CAP_PROFILE.computationalRun, [
457
+ CAP_RECORD_ROLE.actor,
458
+ CAP_RECORD_ROLE.experiment,
459
+ CAP_RECORD_ROLE.execution,
460
+ CAP_RECORD_ROLE.evidence,
461
+ ]);
462
+ requireTypes(CAP_PROFILE.researchCompilation, [
463
+ CAP_RECORD_ROLE.actor,
464
+ CAP_RECORD_ROLE.sourceWork,
465
+ ]);
466
+ if (manifest.profiles.includes(CAP_PROFILE.researchCompilation)) {
467
+ if (!(manifest.roots ?? []).some((root) => root.role === "primarySourceWork")) {
468
+ issues.push("Research Compilation Profile requires root role primarySourceWork");
469
+ }
470
+ const claimIds = new Set(
471
+ [...recordBodies.values()]
472
+ .filter((record) => record.role === "claim")
473
+ .map((record) => record.id),
474
+ );
475
+ const compiledClaimIds = new Set(
476
+ (manifest.roots ?? [])
477
+ .filter((root) => root.role === "compiledClaim")
478
+ .map((root) => root.ref),
479
+ );
480
+ if (
481
+ claimIds.size !== compiledClaimIds.size
482
+ || [...claimIds].some((id) => !compiledClaimIds.has(id))
483
+ ) {
484
+ issues.push("Research Compilation Profile must root every Claim as compiledClaim");
485
+ }
486
+ const compilerOutput = [...blobDescriptors.values()].find((item) =>
487
+ item.roles.includes("compiler-output"),
488
+ );
489
+ if (!compilerOutput) {
490
+ issues.push("Research Compilation Profile requires a compiler-output Blob");
491
+ }
492
+ const researchInventory = [...blobDescriptors.values()].find((item) =>
493
+ item.roles.includes("research-inventory"),
494
+ );
495
+ if (!researchInventory) {
496
+ issues.push("Research Compilation Profile requires a research-inventory Blob");
497
+ } else if (
498
+ researchInventory.availability !== "embedded"
499
+ || researchInventory.mediaType !== "application/json"
500
+ ) {
501
+ issues.push("Research Compilation research-inventory must be embedded JSON");
502
+ }
503
+ if ([...recordBodies.values()].some(body => (body.type === CAP_RECORD_TYPE.activity && !(body.role === 'research-activity'
504
+ && body.basis === 'declared' && body.origin?.basis === 'declared' && body.status === 'unknown'
505
+ && body.prospective !== true && body.sourceContext?.length > 0))
506
+ || ['procedure', 'procedure-step', 'assessment', 'measurement-assessment'].includes(body.role)
507
+ || (body.role === 'observation' && (body.basis !== 'declared' || body.generatedBy)))) {
508
+ issues.push("Research Compilation Profile permits only source-declared observations, never planned experiments, actual executions or assessments");
509
+ }
510
+ }
511
+ requireTypes(CAP_PROFILE.researchPlan, [
512
+ CAP_RECORD_ROLE.actor,
513
+ CAP_RECORD_ROLE.sourceWork,
514
+ CAP_RECORD_ROLE.claim,
515
+ ]);
516
+ if (manifest.profiles.includes(CAP_PROFILE.researchPlan)) {
517
+ for (const role of ["primarySourceWork", "plannedClaim"]) {
518
+ if (!(manifest.roots ?? []).some((root) => root.role === role)) {
519
+ issues.push(`Research Plan Profile requires root role ${role}`);
520
+ }
521
+ }
522
+ if (types.has(CAP_RECORD_ROLE.execution) || types.has(CAP_RECORD_ROLE.assessment)) {
523
+ issues.push("Research Plan Profile must not contain Execution or Assessment Records");
524
+ }
525
+ }
526
+ requireTypes(CAP_PROFILE.reproduction, [
527
+ CAP_RECORD_ROLE.sourceWork,
528
+ CAP_RECORD_ROLE.claim,
529
+ CAP_RECORD_ROLE.experiment,
530
+ CAP_RECORD_ROLE.execution,
531
+ CAP_RECORD_ROLE.evidence,
532
+ CAP_RECORD_ROLE.assessment,
533
+ ]);
534
+ if (manifest.profiles.includes(CAP_PROFILE.reproduction)) {
535
+ for (const role of ["primaryClaim", "primaryExecution", "primaryAssessment"]) {
536
+ if (!(manifest.roots ?? []).some((root) => root.role === role)) issues.push(`Reproduction Profile requires root role ${role}`);
537
+ }
538
+ }
539
+ if (manifest.profiles.includes(CAP_PROFILE.agentTrace)) {
540
+ const trace = [...blobDescriptors.values()].find((item) => item.roles.includes("execution-trace"));
541
+ if (!trace) issues.push("Agent Trace Profile requires an execution-trace Blob");
542
+ else if (trace.availability === "embedded") await validateTrace(path.join(root, trace.path), issues);
543
+ }
544
+ if (manifest.profiles.includes(CAP_PROFILE.restrictedEvidence)) {
545
+ const restricted = [...blobDescriptors.values()].filter((item) => item.availability !== "embedded");
546
+ if (restricted.length === 0) issues.push("Restricted Evidence Profile requires an external or withheld Blob");
547
+ for (const blob of restricted) {
548
+ if (!blob.accessPolicy || typeof blob.accessPolicy !== "object") issues.push(`Restricted Blob requires accessPolicy: ${blob.id}`);
549
+ }
550
+ }
551
+ if (manifest.profiles.includes(CAP_PROFILE.publicBundle)) {
552
+ if (!entries.files.has("ro-crate-metadata.json")) issues.push("Public Bundle Profile requires ro-crate-metadata.json");
553
+ else {
554
+ try {
555
+ const crate = JSON.parse(await readFile(path.join(root, "ro-crate-metadata.json"), "utf8"));
556
+ if (!crate?.["@context"] || !Array.isArray(crate?.["@graph"])) issues.push("ro-crate-metadata.json is not a usable RO-Crate projection");
557
+ } catch (error) {
558
+ issues.push(`ro-crate-metadata.json is invalid JSON: ${error instanceof Error ? error.message : String(error)}`);
559
+ }
560
+ }
561
+ for (const blob of blobDescriptors.values()) {
562
+ if (!blob.rights || typeof blob.rights !== "object") issues.push(`Public Blob requires resource-specific rights: ${blob.id}`);
563
+ }
564
+ for (const sourceWork of [...recordBodies.values()].filter((item) => item.role === "source-work")) {
565
+ for (const [index, source] of (sourceWork.sources ?? []).entries()) {
566
+ if (!source.rights || typeof source.rights !== "object") issues.push(`Public SourceWork ${sourceWork.id} source[${index}] requires rights`);
567
+ }
568
+ }
569
+ const validSignature = attestations.some((item) => item.signatureValid && item.subjectValid);
570
+ if (!validSignature) issues.push("Public Bundle Profile requires a valid root Artifact attestation");
571
+ }
572
+ }
573
+
574
+ function sameReference(left, right) {
575
+ if (!left || !right || left.ref !== right.ref) return false;
576
+ if (left.artifactDigest || right.artifactDigest) return left.artifactDigest === right.artifactDigest && left.digest === right.digest;
577
+ return true;
578
+ }
579
+
580
+ async function validateTrace(filename, issues) {
581
+ try {
582
+ const text = await readFile(filename, "utf8");
583
+ let previous = 0;
584
+ for (const [index, line] of text.split(/\r?\n/).filter(Boolean).entries()) {
585
+ const event = JSON.parse(line);
586
+ if (!Number.isSafeInteger(event.sequence) || event.sequence <= previous) issues.push(`Trace line ${index + 1} has a non-monotonic sequence`);
587
+ if (typeof event.eventType !== "string" || !event.eventType) issues.push(`Trace line ${index + 1} requires eventType`);
588
+ if (typeof event.occurredAt !== "string" || !Number.isFinite(Date.parse(event.occurredAt))) issues.push(`Trace line ${index + 1} requires occurredAt`);
589
+ if (typeof event.observedAt !== "string" || !Number.isFinite(Date.parse(event.observedAt))) issues.push(`Trace line ${index + 1} requires observedAt`);
590
+ previous = event.sequence;
591
+ }
592
+ } catch (error) {
593
+ issues.push(`Execution trace is invalid: ${error instanceof Error ? error.message : String(error)}`);
594
+ }
595
+ }
596
+
597
+ async function listRegularEntries(root, issues) {
598
+ const files = new Set();
599
+ async function visit(directory, prefix = "") {
600
+ let children;
601
+ try {
602
+ children = await readdir(directory, { withFileTypes: true });
603
+ } catch (error) {
604
+ issues.push(`Cannot read CAP directory: ${error instanceof Error ? error.message : String(error)}`);
605
+ return;
606
+ }
607
+ for (const child of children) {
608
+ const relative = prefix ? `${prefix}/${child.name}` : child.name;
609
+ if (!safeCapPath(relative)) {
610
+ issues.push(`Unsafe CAP path: ${relative}`);
611
+ continue;
612
+ }
613
+ const target = path.join(directory, child.name);
614
+ const stat = await lstat(target);
615
+ if (stat.isSymbolicLink()) {
616
+ issues.push(`CAP contains a symbolic link: ${relative}`);
617
+ } else if (stat.isDirectory()) {
618
+ await visit(target, relative);
619
+ } else if (stat.isFile()) {
620
+ files.add(relative);
621
+ } else {
622
+ issues.push(`CAP contains a non-regular entry: ${relative}`);
623
+ }
624
+ }
625
+ }
626
+ await visit(root);
627
+ return { files };
628
+ }
629
+
630
+ function allowedBundleAttachment(filename) {
631
+ return filename === "cap-locations.json"
632
+ || filename === "ro-crate-metadata.json"
633
+ || filename.startsWith("attestations/")
634
+ || filename.startsWith("preview/")
635
+ || filename.startsWith("projections/");
636
+ }
637
+
638
+ function casBlobPath(digest) {
639
+ if (!DIGEST_PATTERN.test(digest)) return "<invalid>";
640
+ const hex = digest.slice(7);
641
+ return `blobs/sha256/${hex.slice(0, 2)}/${hex}`;
642
+ }