@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.
- package/LICENSE +202 -0
- package/README.md +128 -0
- package/data/dataset-source-registry.v1.json +300 -0
- package/dist/arkgraph/boot.js +6 -0
- package/dist/arkgraph/index.html +1 -0
- package/dist/arkgraph/viewer.css +1 -0
- package/dist/arkgraph/viewer.en.css +1 -0
- package/dist/arkgraph/viewer.en.js +49 -0
- package/dist/arkgraph/viewer.en.js.LEGAL.txt +56 -0
- package/dist/arkgraph/viewer.js +49 -0
- package/dist/arkgraph/viewer.js.LEGAL.txt +56 -0
- package/docker/claude-code/Dockerfile +97 -0
- package/docker/claude-code/codex-pro-relay.mjs +466 -0
- package/docker/claude-code/runtime-contract-check.mjs +79 -0
- package/docs/arkgraph-reading.md +79 -0
- package/docs/configuration.md +100 -0
- package/docs/integration.md +92 -0
- package/docs/maturity-plan.md +27 -0
- package/docs/npm-release.md +44 -0
- package/docs/paper-reading.md +40 -0
- package/docs/research-plan-granularity.md +27 -0
- package/docs/terminal.md +49 -0
- package/examples/toy-evaluation/compile-task.json +27 -0
- package/examples/toy-evaluation/paper.md +5 -0
- package/examples/toy-evaluation/repository/README.md +9 -0
- package/examples/toy-evaluation/repository/checkpoint.json +4 -0
- package/examples/toy-evaluation/repository/evaluate.py +17 -0
- package/examples/toy-evaluation/task.json +81 -0
- package/package.json +59 -0
- package/prompts/compile-research.md +58 -0
- package/prompts/execute-contract.md +72 -0
- package/prompts/execute-workspace-simple.md +51 -0
- package/prompts/execute-workspace.md +34 -0
- package/prompts/prepare-reproduction.md +82 -0
- package/prompts/repair-research.md +45 -0
- package/protocol/CAP.md +129 -0
- package/protocol/LICENSE +12 -0
- package/protocol/MAPPINGS.md +72 -0
- package/protocol/README.md +38 -0
- package/protocol/conformance-v2.0-alpha.1.json +36 -0
- package/protocol/examples/arkgraph/checkpoint-evaluation.json +309 -0
- package/protocol/examples/arkgraph/fixtures.mjs +49 -0
- package/protocol/examples/arkgraph/paper-free.json +291 -0
- package/protocol/examples/arkgraph/partial-failure.json +344 -0
- package/protocol/examples/arkgraph/training-evaluation.json +443 -0
- package/protocol/profiles/agent-trace.md +16 -0
- package/protocol/profiles/computational-run.md +16 -0
- package/protocol/profiles/core.md +15 -0
- package/protocol/profiles/public-bundle.md +18 -0
- package/protocol/profiles/reproduction.md +29 -0
- package/protocol/profiles/research-compilation.md +44 -0
- package/protocol/profiles/research-plan.md +39 -0
- package/protocol/profiles/restricted-evidence.md +15 -0
- package/runtime/bootstrap-autodl-runtime.sh +314 -0
- package/runtime/create-runtime-venv.sh +41 -0
- package/runtime/install-local-cpu-runtime.sh +23 -0
- package/runtime/install-scientific-runtime.sh +153 -0
- package/runtime/mineru/parse.py +62 -0
- package/runtime/mineru/requirements.txt +4 -0
- package/runtime/requirements-baseline.txt +38 -0
- package/schemas/cap/v2/activity.schema.json +47 -0
- package/schemas/cap/v2/agent.schema.json +32 -0
- package/schemas/cap/v2/assertion.schema.json +110 -0
- package/schemas/cap/v2/descriptor.schema.json +243 -0
- package/schemas/cap/v2/entity.schema.json +64 -0
- package/schemas/cap/v2/manifest.schema.json +67 -0
- package/schemas/cap/v2/relation.schema.json +82 -0
- package/schemas/compute-catalog.schema.json +63 -0
- package/schemas/compute-decision.schema.json +27 -0
- package/schemas/execution-contract.schema.json +1024 -0
- package/schemas/research-card.schema.json +30 -0
- package/schemas/research-inventory-draft.schema.json +366 -0
- package/schemas/research.schema.json +1044 -0
- package/schemas/result.schema.json +173 -0
- package/schemas/verification-policy.schema.json +47 -0
- package/schemas/verified-conclusion.schema.json +58 -0
- package/schemas/workspace-summary.schema.json +24 -0
- package/scripts/build-arkgraph-view.mjs +12 -0
- package/scripts/check-execution-feasibility.mjs +24 -0
- package/scripts/check-syntax.mjs +15 -0
- package/scripts/deterministic-asset-preparation.py +438 -0
- package/scripts/package-cap.mjs +23 -0
- package/scripts/package-local-agent.mjs +23 -0
- package/scripts/preview-arkgraph.mjs +25 -0
- package/scripts/replay-research-compiler-candidate.mjs +134 -0
- package/scripts/review-compiler-sources.mjs +44 -0
- package/scripts/run-asset-preparation.sh +17 -0
- package/scripts/run-research-plan.mjs +98 -0
- package/scripts/validate-asset-preparation.py +290 -0
- package/scripts/verify-local-runtime.mjs +57 -0
- package/scripts/verify-npm-package.mjs +57 -0
- package/src/adapters/paper2agent.mjs +107 -0
- package/src/assets/cache.mjs +159 -0
- package/src/assets/compute.mjs +98 -0
- package/src/assets/executor.mjs +145 -0
- package/src/assets/lifecycle.mjs +213 -0
- package/src/assets/manifest.mjs +242 -0
- package/src/assets/opportunistic-preparation.mjs +81 -0
- package/src/assets/plan.mjs +411 -0
- package/src/assets/prompts.mjs +29 -0
- package/src/assets/public-asset-probe.mjs +525 -0
- package/src/assets/qualification.mjs +119 -0
- package/src/assets/readiness.mjs +130 -0
- package/src/assets/reproduction-admission.mjs +355 -0
- package/src/assets/requirements.mjs +152 -0
- package/src/assets/source-grounding.mjs +341 -0
- package/src/assets/source-policy.mjs +118 -0
- package/src/autodl/client.mjs +260 -0
- package/src/autodl/ssh.mjs +380 -0
- package/src/autodl/tools.mjs +129 -0
- package/src/cap/redaction.mjs +38 -0
- package/src/cap/v2/archive.mjs +152 -0
- package/src/cap/v2/attestation.mjs +204 -0
- package/src/cap/v2/canonical-json.mjs +114 -0
- package/src/cap/v2/compilation-artifact.mjs +240 -0
- package/src/cap/v2/core.mjs +282 -0
- package/src/cap/v2/measurement-assessment-records.mjs +23 -0
- package/src/cap/v2/pipeline-artifact.mjs +922 -0
- package/src/cap/v2/read.mjs +41 -0
- package/src/cap/v2/reassessment-artifact.mjs +383 -0
- package/src/cap/v2/research-artifact.mjs +231 -0
- package/src/cap/v2/research-map-records.mjs +46 -0
- package/src/cap/v2/research-object-records.mjs +163 -0
- package/src/cap/v2/research-records.mjs +187 -0
- package/src/cap/v2/verify.mjs +642 -0
- package/src/cli.mjs +1146 -0
- package/src/compute/autodl-pro-compiler.mjs +347 -0
- package/src/compute/autodl-pro-executor.mjs +459 -0
- package/src/compute/autodl-pro-job.mjs +843 -0
- package/src/compute/autodl-pro-network.mjs +295 -0
- package/src/compute/autodl-pro-remote.mjs +810 -0
- package/src/compute/autodl-pro-staging.mjs +117 -0
- package/src/compute/campaign.mjs +110 -0
- package/src/compute/catalog.mjs +123 -0
- package/src/compute/checkpoint-protocol.mjs +154 -0
- package/src/compute/codex-account-lock.mjs +111 -0
- package/src/compute/codex-account-session.mjs +107 -0
- package/src/compute/compiler-profile.mjs +38 -0
- package/src/compute/compiler-router.mjs +23 -0
- package/src/compute/coordinator-recovery.mjs +210 -0
- package/src/compute/executor-router.mjs +29 -0
- package/src/compute/gcp-batch-compiler.mjs +685 -0
- package/src/compute/gcp-batch-executor.mjs +1215 -0
- package/src/compute/gcp-batch-failure.mjs +92 -0
- package/src/compute/gcp-batch-job.mjs +527 -0
- package/src/compute/gcp-batch-lifecycle.mjs +81 -0
- package/src/compute/gcp-checkpoint-worker.mjs +1633 -0
- package/src/compute/local-codex-compiler.mjs +52 -0
- package/src/compute/measurement-hardware.mjs +128 -0
- package/src/compute/remote-attempt.mjs +226 -0
- package/src/compute/requirements.mjs +124 -0
- package/src/compute/research-phases.mjs +48 -0
- package/src/compute/scheduler.mjs +452 -0
- package/src/compute/shared-workloads.mjs +26 -0
- package/src/compute/stage-archive.mjs +79 -0
- package/src/contracts/campaign-contract.mjs +52 -0
- package/src/contracts/execution-contract.mjs +819 -0
- package/src/contracts/execution-mode.mjs +19 -0
- package/src/contracts/execution-timeouts.mjs +45 -0
- package/src/contracts/execution-workload.mjs +68 -0
- package/src/contracts/preflight-schema.mjs +25 -0
- package/src/contracts/public-contract.mjs +63 -0
- package/src/contracts/subject-tags.mjs +31 -0
- package/src/dashboard/data.mjs +898 -0
- package/src/dashboard/server.mjs +79 -0
- package/src/dashboard/static/dashboard.css +366 -0
- package/src/dashboard/static/dashboard.js +560 -0
- package/src/dashboard/static/index.html +85 -0
- package/src/deployment/community-policy.mjs +9 -0
- package/src/deployment/environment.mjs +112 -0
- package/src/deployment/guided.mjs +98 -0
- package/src/deployment/handoff.mjs +102 -0
- package/src/deployment/local-contract.mjs +31 -0
- package/src/deployment/local.mjs +100 -0
- package/src/deployment/prepare.mjs +46 -0
- package/src/deployment/recipe.mjs +108 -0
- package/src/deployment/supplement.mjs +51 -0
- package/src/deployment/terminal.mjs +43 -0
- package/src/diagnosis/renderer.mjs +75 -0
- package/src/diagnosis/target-failure.mjs +46 -0
- package/src/evidence/parser-registry.mjs +54 -0
- package/src/evidence/parsers/fasttext-classification.mjs +82 -0
- package/src/evidence/parsers/json-scalar.mjs +96 -0
- package/src/evidence/parsers/simcse-senteval.mjs +104 -0
- package/src/evidence/parsers/starspace-classification.mjs +78 -0
- package/src/evidence/registry.mjs +147 -0
- package/src/execution/runner-audit.mjs +473 -0
- package/src/gcp/auth.mjs +106 -0
- package/src/gcp/batch-client.mjs +120 -0
- package/src/gcp/resource-discovery.mjs +177 -0
- package/src/gcp/rest.mjs +82 -0
- package/src/gcp/secret-manager.mjs +34 -0
- package/src/gcp/signed-url.mjs +133 -0
- package/src/gcp/storage.mjs +220 -0
- package/src/graph/command.mjs +41 -0
- package/src/graph/execution.mjs +97 -0
- package/src/graph/model.mjs +37 -0
- package/src/graph/presentation.mjs +110 -0
- package/src/graph/query.mjs +159 -0
- package/src/graph/research-relations.mjs +69 -0
- package/src/graph/source-page.mjs +12 -0
- package/src/graph/source-preview.mjs +34 -0
- package/src/graph/validate.mjs +76 -0
- package/src/job.mjs +496 -0
- package/src/network/autodl-routing-proxy.mjs +462 -0
- package/src/network/egress-proxy.mjs +158 -0
- package/src/observability/event-contract.mjs +230 -0
- package/src/observability/pipeline-monitor.mjs +166 -0
- package/src/pipeline/orchestrator.mjs +1281 -0
- package/src/pipeline/recovery-error.mjs +11 -0
- package/src/pipeline/replay.mjs +304 -0
- package/src/pipeline/shared-execution.mjs +115 -0
- package/src/pipeline/stage-checkpoint.mjs +86 -0
- package/src/pipeline/stage-recovery.mjs +101 -0
- package/src/pipeline/targets.mjs +110 -0
- package/src/process.mjs +143 -0
- package/src/protocol.mjs +312 -0
- package/src/provider/codex-account.mjs +44 -0
- package/src/provider/codex-completion.mjs +49 -0
- package/src/provider/completion.mjs +292 -0
- package/src/provider/model-client.mjs +44 -0
- package/src/provider/model-route.mjs +29 -0
- package/src/provider/openrouter-readiness.mjs +189 -0
- package/src/provider/reader-bridge.mjs +35 -0
- package/src/provider/relay.mjs +263 -0
- package/src/provider/runtime-auth.mjs +40 -0
- package/src/public/cap.d.mts +90 -0
- package/src/public/cap.mjs +12 -0
- package/src/public/contracts.d.mts +2 -0
- package/src/public/host.mjs +171 -0
- package/src/public/operations.d.mts +11 -0
- package/src/public/presentation.d.mts +4 -0
- package/src/records/views.mjs +26 -0
- package/src/remote/command.mjs +178 -0
- package/src/remote/ssh.mjs +59 -0
- package/src/repository-origin.mjs +81 -0
- package/src/reproduction/evidence-feedback.mjs +96 -0
- package/src/reproduction/incomplete-initialization.mjs +25 -0
- package/src/reproduction/lifecycle.mjs +253 -0
- package/src/reproduction/plan.mjs +132 -0
- package/src/reproduction/prompts.mjs +70 -0
- package/src/reproduction/runner.mjs +188 -0
- package/src/reproduction/summary.mjs +130 -0
- package/src/reproduction/workspace-mode.mjs +7 -0
- package/src/research/automatic-admission.mjs +156 -0
- package/src/research/compiler-coverage.mjs +85 -0
- package/src/research/compiler-failure.mjs +24 -0
- package/src/research/compiler-normalization-guards.mjs +112 -0
- package/src/research/compiler-repair.mjs +3 -0
- package/src/research/compiler.mjs +853 -0
- package/src/research/continuation-selection.mjs +26 -0
- package/src/research/execution-graph-context.mjs +43 -0
- package/src/research/experiment-importance.mjs +15 -0
- package/src/research/inventory-handoff.mjs +104 -0
- package/src/research/inventory-revisions.mjs +32 -0
- package/src/research/mineru-local.mjs +73 -0
- package/src/research/paper-command.mjs +19 -0
- package/src/research/paper-markdown.mjs +180 -0
- package/src/research/paper-source-map.mjs +69 -0
- package/src/research/planning-policy.mjs +88 -0
- package/src/research/reference-materials.mjs +11 -0
- package/src/research/reproduction-scope.mjs +30 -0
- package/src/research/research-map.mjs +94 -0
- package/src/research/research-objects.mjs +88 -0
- package/src/research/source-discovery.mjs +646 -0
- package/src/research/source-observations.mjs +75 -0
- package/src/research/source-review-cli-mcp.mjs +26 -0
- package/src/research/source-review-input.mjs +209 -0
- package/src/research/source-review-local-codex.mjs +36 -0
- package/src/research/source-review-model.mjs +70 -0
- package/src/research/source-review.mjs +173 -0
- package/src/research/structure.mjs +3163 -0
- package/src/research-card/renderer.mjs +277 -0
- package/src/research-card/verified-conclusion.mjs +143 -0
- package/src/results/output-registry.mjs +183 -0
- package/src/runtime/claude-code.mjs +52 -0
- package/src/runtime/codex-capacity-retry.mjs +87 -0
- package/src/runtime/codex.mjs +64 -0
- package/src/runtime/config.mjs +157 -0
- package/src/runtime/final-output.mjs +40 -0
- package/src/runtime/index.mjs +21 -0
- package/src/runtime/local-codex.mjs +74 -0
- package/src/runtime/opencode.mjs +95 -0
- package/src/runtime/prompt.mjs +13 -0
- package/src/sandbox/docker.mjs +363 -0
- package/src/settings/command.mjs +297 -0
- package/src/settings/store.mjs +119 -0
- package/src/telemetry/pricing.mjs +68 -0
- package/src/telemetry/usage.mjs +265 -0
- package/src/terminal/events.mjs +97 -0
- package/src/terminal/input.mjs +40 -0
- package/src/terminal/plain.mjs +40 -0
- package/src/terminal/remote-stream.mjs +22 -0
- package/src/terminal/screen.mjs +214 -0
- package/src/terminal/transcript.mjs +69 -0
- package/src/util.mjs +107 -0
- package/src/verification/ai-assessor.mjs +534 -0
- package/src/verification/claim-evaluator.mjs +242 -0
- package/src/verification/evidence-context.mjs +165 -0
- package/src/verification/evidence-reader.mjs +95 -0
- package/src/verification/integrity.mjs +570 -0
- package/src/verification/tolerance.mjs +32 -0
- package/src/workloads/cpu-research-preparation.mjs +56 -0
- package/src/workloads/definition.mjs +74 -0
- package/src/workloads/phase-aware-reproduction.mjs +46 -0
- package/src/workloads/reproduction.mjs +85 -0
- package/src/workspace/command.mjs +242 -0
- package/src/workspace/control.mjs +49 -0
- package/src/workspace/entry.mjs +28 -0
- package/src/workspace/input.mjs +93 -0
- package/src/workspace/interactive.mjs +94 -0
- package/src/workspace/jobs.mjs +418 -0
- package/src/workspace/session.mjs +97 -0
- package/src/workspace/worker.mjs +137 -0
- package/ui/arkgraph/ambient-motion.mjs +10 -0
- package/ui/arkgraph/app.jsx +153 -0
- package/ui/arkgraph/boot.js +6 -0
- package/ui/arkgraph/camera-motion.mjs +20 -0
- package/ui/arkgraph/context-reveal.mjs +39 -0
- package/ui/arkgraph/details.css +3 -0
- package/ui/arkgraph/entry.jsx +28 -0
- package/ui/arkgraph/experiment-curves.mjs +17 -0
- package/ui/arkgraph/experiment-selection.mjs +15 -0
- package/ui/arkgraph/experiment-style.css +26 -0
- package/ui/arkgraph/experiment-ui.jsx +32 -0
- package/ui/arkgraph/frame.html +1 -0
- package/ui/arkgraph/graph-gestures.mjs +62 -0
- package/ui/arkgraph/label-layout.mjs +57 -0
- package/ui/arkgraph/locales/en.json +229 -0
- package/ui/arkgraph/locales/source-types.json +15 -0
- package/ui/arkgraph/localization-build.mjs +27 -0
- package/ui/arkgraph/material-build.mjs +23 -0
- package/ui/arkgraph/material-colors.mjs +39 -0
- package/ui/arkgraph/material-style.css +15 -0
- package/ui/arkgraph/open-graph.jsx +326 -0
- package/ui/arkgraph/outline.jsx +49 -0
- package/ui/arkgraph/package-lock.json +888 -0
- package/ui/arkgraph/package.json +17 -0
- package/ui/arkgraph/reading-layout.mjs +130 -0
- package/ui/arkgraph/reading-presentation.mjs +73 -0
- package/ui/arkgraph/record-detail.css +51 -0
- package/ui/arkgraph/record-details.jsx +29 -0
- package/ui/arkgraph/research-types.mjs +31 -0
- package/ui/arkgraph/selection-mark.jsx +6 -0
- package/ui/arkgraph/soft-spine.mjs +26 -0
- package/ui/arkgraph/steering-style.css +187 -0
- package/ui/arkgraph/style.css +272 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { scientificProjections } from '../../graph/model.mjs';
|
|
2
|
+
import { mkdir, readFile, readdir } from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { verifyCapArchive } from './archive.mjs';
|
|
5
|
+
import { CAP_PROFILE, CAP_RECORD_TYPE } from './core.mjs';
|
|
6
|
+
import { captureProcess } from '../../process.mjs';
|
|
7
|
+
|
|
8
|
+
/** Read authenticated scientific bytes without imposing a registry's admission policy. */
|
|
9
|
+
export async function readCapArchive(archivePath, directory, options = {}) {
|
|
10
|
+
const verification = await verifyCapArchive(archivePath, options);
|
|
11
|
+
if (!verification.valid) throw Error(verification.issues.join('; '));
|
|
12
|
+
const attestation = verification.attestations.find(item => item.signatureValid && item.subjectValid);
|
|
13
|
+
if (!attestation) throw Error('CAP 缺少有效签名');
|
|
14
|
+
await mkdir(directory, { recursive: true });
|
|
15
|
+
if ((await readdir(directory)).length) throw Error('CAP 读取目录必须为空');
|
|
16
|
+
const extracted = await captureProcess('tar', ['-xzf', path.resolve(archivePath), '-C', directory]);
|
|
17
|
+
if (extracted.code) throw Error('无法解包已验证的 CAP');
|
|
18
|
+
const graphRecords = [];
|
|
19
|
+
// Large plans can contain tens of thousands of records. Keep descriptors and
|
|
20
|
+
// parsed records in manifest order without opening every file at once.
|
|
21
|
+
for (let offset = 0; offset < verification.manifest.records.length; offset += 32) {
|
|
22
|
+
const batch = verification.manifest.records.slice(offset, offset + 32);
|
|
23
|
+
const loaded = await Promise.allSettled(batch.map(descriptor =>
|
|
24
|
+
readFile(path.join(directory, descriptor.path), 'utf8').then(JSON.parse)));
|
|
25
|
+
const failed = loaded.find(result => result.status === 'rejected');
|
|
26
|
+
if (failed) throw failed.reason;
|
|
27
|
+
graphRecords.push(...loaded.map(result => result.value));
|
|
28
|
+
}
|
|
29
|
+
const records = scientificProjections(graphRecords);
|
|
30
|
+
const profiles = verification.manifest.profiles;
|
|
31
|
+
const kind = profiles.includes(CAP_PROFILE.reproduction) ? 'reproduction'
|
|
32
|
+
: profiles.includes(CAP_PROFILE.researchPlan) ? 'research-plan'
|
|
33
|
+
: profiles.includes(CAP_PROFILE.researchCompilation) ? 'research-compilation' : profiles.includes(CAP_PROFILE.computationalRun) ? 'computational-run' : 'research-graph';
|
|
34
|
+
return { ...verification, kind, records, graphRecords, signerDigest: attestation.publicKeyDigest,
|
|
35
|
+
directory, attestation };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export async function readCapBlob(artifact, role) {
|
|
39
|
+
const descriptor = artifact.manifest.blobs.find(blob => blob.roles.includes(role) && blob.availability === 'embedded');
|
|
40
|
+
return descriptor ? JSON.parse(await readFile(path.join(artifact.directory, descriptor.path), 'utf8')) : null;
|
|
41
|
+
}
|
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
import { measurementAssessmentRecords } from './measurement-assessment-records.mjs';
|
|
2
|
+
import { CAP_RECORD_ROLE } from '../../graph/model.mjs';
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
4
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
|
|
7
|
+
import { sha256Value } from "../../util.mjs";
|
|
8
|
+
import {
|
|
9
|
+
attachCapAttestation,
|
|
10
|
+
signCapArtifact,
|
|
11
|
+
} from "./attestation.mjs";
|
|
12
|
+
import {
|
|
13
|
+
CAP_PROFILE,
|
|
14
|
+
CAP_RECORD_TYPE,
|
|
15
|
+
assembleCapDirectory,
|
|
16
|
+
} from "./core.mjs";
|
|
17
|
+
import { canonicalJsonBytes } from "./canonical-json.mjs";
|
|
18
|
+
import {
|
|
19
|
+
capActorRecord,
|
|
20
|
+
capRecordId,
|
|
21
|
+
} from "./research-records.mjs";
|
|
22
|
+
import { verifyCapDirectory } from "./verify.mjs";
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Create a new signed Assessment over an existing immutable Execution and its
|
|
26
|
+
* Evidence. No experiment command is rerun and no Evidence bytes are changed.
|
|
27
|
+
*/
|
|
28
|
+
export async function assembleReassessmentCap({
|
|
29
|
+
sourceDirectory,
|
|
30
|
+
directory,
|
|
31
|
+
scientificAssessor,
|
|
32
|
+
recoverEvidenceContext,
|
|
33
|
+
signingKeyPath,
|
|
34
|
+
processingBinding,
|
|
35
|
+
assessedAt = new Date().toISOString(),
|
|
36
|
+
assessmentId: selectedAssessmentId,
|
|
37
|
+
}) {
|
|
38
|
+
if (typeof scientificAssessor !== "function") {
|
|
39
|
+
throw new Error("CAP reassessment requires an independent scientific assessor");
|
|
40
|
+
}
|
|
41
|
+
const sourceVerification = await verifyCapDirectory(sourceDirectory);
|
|
42
|
+
if (!sourceVerification.valid) {
|
|
43
|
+
throw new Error(`Source CAP failed verification:\n- ${sourceVerification.issues.join("\n- ")}`);
|
|
44
|
+
}
|
|
45
|
+
const sourceManifest = sourceVerification.manifest;
|
|
46
|
+
const descriptors = sourceManifest.records;
|
|
47
|
+
const bodies = await Promise.all(descriptors.map(async (descriptor) => ({
|
|
48
|
+
descriptor,
|
|
49
|
+
value: JSON.parse(await readFile(path.join(sourceDirectory, descriptor.path), "utf8")),
|
|
50
|
+
})));
|
|
51
|
+
const primaryIds = sourceManifest.roots.filter(root => root.role === 'primaryAssessment').map(root => root.ref);
|
|
52
|
+
const selectedId = selectedAssessmentId ?? (primaryIds.length === 1 ? primaryIds[0] : null);
|
|
53
|
+
const oldAssessmentEntry = bodies.find(entry => entry.value.id === selectedId && entry.value.role === 'assessment');
|
|
54
|
+
if (!oldAssessmentEntry) throw Error('Select an exact assessmentId when the source has multiple assessment roots');
|
|
55
|
+
const bound = (reference, role) => {
|
|
56
|
+
const entry = bodies.find(entry => entry.value.id === reference?.ref && entry.value.role === role);
|
|
57
|
+
if (!entry) throw Error(`Reassessment requires a locally materialized ${role}`);
|
|
58
|
+
return entry;
|
|
59
|
+
};
|
|
60
|
+
const claimEntry = bound(oldAssessmentEntry.value.claim, 'claim');
|
|
61
|
+
const experimentEntry = bound(oldAssessmentEntry.value.experiment, 'procedure');
|
|
62
|
+
const executionEntry = bound(oldAssessmentEntry.value.execution, 'execution');
|
|
63
|
+
const oldAssessment = oldAssessmentEntry.value;
|
|
64
|
+
const oldComparisons = Array.isArray(oldAssessment.method?.comparisons)
|
|
65
|
+
? oldAssessment.method.comparisons
|
|
66
|
+
: [];
|
|
67
|
+
if (!oldComparisons.length) throw new Error("Source Assessment has no protocol-bound measurement comparisons");
|
|
68
|
+
|
|
69
|
+
const runnerAudit = await retainedRunnerAudit(sourceDirectory, sourceManifest, executionEntry.value);
|
|
70
|
+
const previousContext = await retainedEvidenceContext(sourceDirectory, sourceManifest, oldAssessment);
|
|
71
|
+
const evidenceContext = recoverEvidenceContext
|
|
72
|
+
? await recoverEvidenceContext({ claim: claimEntry.value, experiment: experimentEntry.value,
|
|
73
|
+
execution: executionEntry.value, runnerAudit, previousContext }) ?? previousContext
|
|
74
|
+
: previousContext;
|
|
75
|
+
// Supplement immutable evidence; never replace a previously bound byte identity.
|
|
76
|
+
if (evidenceContext && previousContext && evidenceContext !== previousContext) {
|
|
77
|
+
for (const old of previousContext.files ?? []) {
|
|
78
|
+
const next = evidenceContext.files?.find(f => f.path === old.path);
|
|
79
|
+
if (next && next.digest !== old.digest) throw new Error(`Recovered evidence changed: ${old.path}`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
const contextBytes = evidenceContext ? canonicalJsonBytes(evidenceContext) : null;
|
|
83
|
+
const contextDigest = contextBytes ? `sha256:${sha256Bytes(contextBytes)}` : null;
|
|
84
|
+
const modelResult = await scientificAssessor({
|
|
85
|
+
claim: {
|
|
86
|
+
statement: claimEntry.value.statement?.value ?? "",
|
|
87
|
+
reportedMeasurements: claimEntry.value.citeark?.reportedMeasurements ?? [],
|
|
88
|
+
},
|
|
89
|
+
contract: reassessmentContract(claimEntry.value, experimentEntry.value, oldAssessment),
|
|
90
|
+
comparisons: oldComparisons.map(modelComparison),
|
|
91
|
+
runnerAudit,
|
|
92
|
+
evidenceContext,
|
|
93
|
+
result: {
|
|
94
|
+
execution: {
|
|
95
|
+
status: executionEntry.value.citeark?.outcome?.status ?? executionEntry.value.status,
|
|
96
|
+
},
|
|
97
|
+
limitations: stringArray(executionEntry.value.citeark?.limitations),
|
|
98
|
+
workloadCompletion: executionEntry.value.citeark?.workloadCompletion ?? null,
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
if (modelResult?.status !== "completed") {
|
|
102
|
+
throw new Error([
|
|
103
|
+
modelResult?.error?.message ?? "Independent scientific reassessment did not complete",
|
|
104
|
+
modelResult?.error?.detail,
|
|
105
|
+
].filter(Boolean).join(":"));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const assessment = reassessmentBody({
|
|
109
|
+
modelResult,
|
|
110
|
+
oldAssessment,
|
|
111
|
+
oldComparisons,
|
|
112
|
+
assessedAt,
|
|
113
|
+
});
|
|
114
|
+
const assessorId = capRecordId(
|
|
115
|
+
"actor",
|
|
116
|
+
`${assessment.assessor.kind}:${assessment.assessor.provider}:${assessment.assessor.name}:${assessment.assessor.rubricVersion}`,
|
|
117
|
+
);
|
|
118
|
+
const assessmentId = capRecordId(
|
|
119
|
+
"assessment",
|
|
120
|
+
`${executionEntry.value.id}:${assessment.assessmentDigest}`,
|
|
121
|
+
);
|
|
122
|
+
// The same assessor identity must retain its already materialized bytes; otherwise
|
|
123
|
+
// copied exact provenance edges would point at a rewritten participant.
|
|
124
|
+
const assessorRecord = bodies.find(({ value }) => value.id === assessorId && value.role === 'agent')?.value ?? capActorRecord(
|
|
125
|
+
assessorId,
|
|
126
|
+
assessment.assessor.name,
|
|
127
|
+
"model",
|
|
128
|
+
assessment.assessor.model ?? assessment.assessor.rubricVersion,
|
|
129
|
+
);
|
|
130
|
+
const newAssessmentRecord = {
|
|
131
|
+
...oldAssessment,
|
|
132
|
+
id: assessmentId,
|
|
133
|
+
method: {
|
|
134
|
+
...oldAssessment.method,
|
|
135
|
+
type: "QualitativeReview",
|
|
136
|
+
comparisons: assessment.measurementAssessments.map((item) => ({
|
|
137
|
+
measurementId: item.measurementId,
|
|
138
|
+
verdict: item.verdict,
|
|
139
|
+
verificationStatus: item.verificationStatus,
|
|
140
|
+
reason: item.reason,
|
|
141
|
+
...(oldComparisons.find(
|
|
142
|
+
(candidate) => candidate.measurementId === item.measurementId,
|
|
143
|
+
)?.comparison
|
|
144
|
+
? {
|
|
145
|
+
comparison: oldComparisons.find(
|
|
146
|
+
(candidate) => candidate.measurementId === item.measurementId,
|
|
147
|
+
).comparison,
|
|
148
|
+
}
|
|
149
|
+
: {}),
|
|
150
|
+
})),
|
|
151
|
+
},
|
|
152
|
+
conclusion: assessment.verdict,
|
|
153
|
+
limitations: assessment.limitations,
|
|
154
|
+
performedBy: { ref: assessorId },
|
|
155
|
+
createdAt: assessedAt,
|
|
156
|
+
citeark: {
|
|
157
|
+
verificationStatus: assessment.verificationStatus,
|
|
158
|
+
integrityStatus: oldAssessment.citeark?.integrityStatus ?? "passed",
|
|
159
|
+
reason: assessment.reason,
|
|
160
|
+
confidence: assessment.confidence,
|
|
161
|
+
protocolComparability: assessment.protocolComparability,
|
|
162
|
+
...(assessment.executionEvidence ? { executionEvidence: assessment.executionEvidence } : {}),
|
|
163
|
+
claimCoverage: oldAssessment.citeark?.claimCoverage ?? "complete",
|
|
164
|
+
uncoveredMeasurementIds: oldAssessment.citeark?.uncoveredMeasurementIds ?? [],
|
|
165
|
+
assessor: assessment.assessor,
|
|
166
|
+
...(contextDigest ? { evidenceContextBlobDigest: contextDigest } : {}),
|
|
167
|
+
evidenceReads: modelResult.evidenceReads ?? [],
|
|
168
|
+
assessmentUsage: assessment.usage,
|
|
169
|
+
measurementAssessments: assessment.measurementAssessments,
|
|
170
|
+
outputAssessments: oldAssessment.citeark?.outputAssessments ?? [],
|
|
171
|
+
diagnosis: null,
|
|
172
|
+
},
|
|
173
|
+
};
|
|
174
|
+
const evidenceByMeasurement = new Map(bodies.filter(({ value }) => value.role === 'observation'
|
|
175
|
+
&& value.basis === 'observed' && value.measurement?.measurementId).map(({ value }) => [value.measurement.measurementId, value.id]));
|
|
176
|
+
const componentAssessments = measurementAssessmentRecords({ assessment, parent: newAssessmentRecord,
|
|
177
|
+
identity: `${executionEntry.value.id}:${assessment.assessmentDigest}`, evidenceByMeasurement });
|
|
178
|
+
newAssessmentRecord.componentAssessments = componentAssessments.map(item => ({ ref: item.id }));
|
|
179
|
+
const records = [
|
|
180
|
+
...bodies
|
|
181
|
+
.filter(({value}) => value.id !== oldAssessment.id && value.id !== assessorId
|
|
182
|
+
&& (value.type !== CAP_RECORD_TYPE.relation || ![value.subject, value.object, ...(value.sources ?? [])].some(reference => reference?.ref === oldAssessment.id)))
|
|
183
|
+
.map((entry) => entry.value),
|
|
184
|
+
assessorRecord,
|
|
185
|
+
newAssessmentRecord,
|
|
186
|
+
...componentAssessments,
|
|
187
|
+
{ $schema: 'https://citeark.com/schemas/cap/v2/relation.schema.json',
|
|
188
|
+
id: capRecordId('relation', `${assessmentId}:supersedes:${oldAssessment.id}`), type: CAP_RECORD_TYPE.relation, role: 'provenance',
|
|
189
|
+
predicate: 'supersedes', subject: {ref: assessmentId},
|
|
190
|
+
object: {ref: oldAssessment.id, digest: oldAssessmentEntry.descriptor.digest, recordType: CAP_RECORD_TYPE.assertion, artifactDigest: sourceVerification.artifactDigest},
|
|
191
|
+
attributedTo: {ref: assessorId}, basis: 'declared', sources: [],
|
|
192
|
+
},
|
|
193
|
+
];
|
|
194
|
+
for (const component of componentAssessments) {
|
|
195
|
+
const previous = bodies.find(({ value }) => oldAssessment.componentAssessments?.some(ref => ref.ref === value.id)
|
|
196
|
+
&& value.citeark?.measurementId === component.citeark.measurementId);
|
|
197
|
+
if (previous) records.push({ $schema: 'https://citeark.com/schemas/cap/v2/relation.schema.json',
|
|
198
|
+
id: capRecordId('relation', `${component.id}:supersedes:${previous.value.id}`), type: CAP_RECORD_TYPE.relation, role: 'provenance',
|
|
199
|
+
predicate: 'supersedes', subject: { ref: component.id }, object: { ref: previous.value.id },
|
|
200
|
+
attributedTo: { ref: assessorId }, basis: 'declared', sources: [] });
|
|
201
|
+
}
|
|
202
|
+
const blobs = await Promise.all(sourceManifest.blobs.map(async (blob) => ({
|
|
203
|
+
id: blob.id,
|
|
204
|
+
roles: blob.roles,
|
|
205
|
+
mediaType: blob.mediaType,
|
|
206
|
+
availability: blob.availability,
|
|
207
|
+
digest: blob.digest,
|
|
208
|
+
size: blob.size,
|
|
209
|
+
...(blob.availability === "embedded"
|
|
210
|
+
? { bytes: await readFile(path.join(sourceDirectory, blob.path)) }
|
|
211
|
+
: {}),
|
|
212
|
+
...(blob.rights ? { rights: blob.rights } : {}),
|
|
213
|
+
...(blob.accessPolicy ? { accessPolicy: blob.accessPolicy } : {}),
|
|
214
|
+
})));
|
|
215
|
+
if (contextBytes && !blobs.some(b => b.digest === contextDigest)) blobs.push({
|
|
216
|
+
id: capRecordId("blob", `${executionEntry.value.id}:assessment-context:${contextDigest}`),
|
|
217
|
+
roles: ["assessment-evidence-context"], mediaType: "application/json", bytes: contextBytes,
|
|
218
|
+
rights: { statement: "Recovered original execution evidence; original source rights remain unchanged." },
|
|
219
|
+
});
|
|
220
|
+
const roots = sourceManifest.roots.map((root) => ({
|
|
221
|
+
role: root.role,
|
|
222
|
+
ref: root.ref === oldAssessment.id ? assessmentId : root.ref,
|
|
223
|
+
}));
|
|
224
|
+
const built = await assembleCapDirectory({
|
|
225
|
+
directory,
|
|
226
|
+
artifact: {
|
|
227
|
+
id: capRecordId("artifact", `${sourceVerification.artifactDigest}:${assessment.assessmentDigest}`),
|
|
228
|
+
createdAt: assessedAt,
|
|
229
|
+
createdBy: sourceManifest.artifact.createdBy.ref,
|
|
230
|
+
},
|
|
231
|
+
profiles: sourceManifest.profiles.filter((profile) => profile !== CAP_PROFILE.core),
|
|
232
|
+
roots,
|
|
233
|
+
records,
|
|
234
|
+
blobs,
|
|
235
|
+
relations: [
|
|
236
|
+
...sourceManifest.relations.filter((relation) => relation.relationship !== "supersedes"),
|
|
237
|
+
{
|
|
238
|
+
relationship: "supersedes",
|
|
239
|
+
artifactDigest: sourceVerification.artifactDigest,
|
|
240
|
+
recordDigest: oldAssessmentEntry.descriptor.digest,
|
|
241
|
+
summary: "Reassesses the same immutable Execution and Evidence with the current scientific assessment rubric.",
|
|
242
|
+
},
|
|
243
|
+
],
|
|
244
|
+
});
|
|
245
|
+
await mkdir(path.join(directory, "projections", "citeark"), { recursive: true });
|
|
246
|
+
await writeFile(
|
|
247
|
+
path.join(directory, "projections", "citeark", "assessment.json"),
|
|
248
|
+
canonicalJsonBytes(newAssessmentRecord),
|
|
249
|
+
);
|
|
250
|
+
if (sourceManifest.profiles.includes(CAP_PROFILE.publicBundle)) {
|
|
251
|
+
await writeFile(
|
|
252
|
+
path.join(directory, "ro-crate-metadata.json"),
|
|
253
|
+
`${JSON.stringify({
|
|
254
|
+
"@context": "https://w3id.org/ro/crate/1.3/context",
|
|
255
|
+
"@graph": [
|
|
256
|
+
{ "@id": "ro-crate-metadata.json", "@type": "CreativeWork", about: { "@id": "./" } },
|
|
257
|
+
{
|
|
258
|
+
"@id": "./",
|
|
259
|
+
"@type": "Dataset",
|
|
260
|
+
identifier: built.artifactDigest,
|
|
261
|
+
hasPart: built.manifest.records.map((record) => ({ "@id": record.path })),
|
|
262
|
+
},
|
|
263
|
+
],
|
|
264
|
+
}, null, 2)}\n`,
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
const attestation = await signCapArtifact({
|
|
268
|
+
artifactDigest: built.artifactDigest,
|
|
269
|
+
actor: { ref: sourceManifest.artifact.createdBy.ref },
|
|
270
|
+
role: "artifactAssembler",
|
|
271
|
+
createdAt: assessedAt,
|
|
272
|
+
signingKeyPath,
|
|
273
|
+
processingBinding,
|
|
274
|
+
});
|
|
275
|
+
const attachedAttestation = await attachCapAttestation({ directory, attestation });
|
|
276
|
+
const verification = await verifyCapDirectory(directory);
|
|
277
|
+
if (!verification.valid) {
|
|
278
|
+
throw new Error(`Reassessment CAP failed self-verification:\n- ${verification.issues.join("\n- ")}`);
|
|
279
|
+
}
|
|
280
|
+
return {
|
|
281
|
+
...built,
|
|
282
|
+
verification,
|
|
283
|
+
attestation: attachedAttestation,
|
|
284
|
+
assessment,
|
|
285
|
+
supersedes: {
|
|
286
|
+
artifactDigest: sourceVerification.artifactDigest,
|
|
287
|
+
assessmentDigest: oldAssessmentEntry.descriptor.digest,
|
|
288
|
+
},
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function reassessmentContract(claim, experiment, oldAssessment) {
|
|
293
|
+
const executionPlan = experiment.citeark?.executionPlan;
|
|
294
|
+
return {
|
|
295
|
+
...(executionPlan ? { executionPlan } : {}),
|
|
296
|
+
claim: { text: claim.statement?.value ?? "", source: claim.source ?? null },
|
|
297
|
+
...(experiment.citeark?.reproductionScope ? { reproductionScope: experiment.citeark.reproductionScope, requiredReproductionScope: experiment.citeark.requiredReproductionScope } : {}),
|
|
298
|
+
reproductionLevel: experiment.evaluationPlan?.reproductionLevel
|
|
299
|
+
?? experiment.citeark?.reproductionLevel
|
|
300
|
+
?? null,
|
|
301
|
+
reconstructionFidelity: experiment.evaluationPlan?.reconstructionFidelity
|
|
302
|
+
?? experiment.citeark?.reconstructionFidelity
|
|
303
|
+
?? "faithful",
|
|
304
|
+
repository: {
|
|
305
|
+
implementationOrigin: experiment.citeark?.implementationOrigin ?? "official",
|
|
306
|
+
},
|
|
307
|
+
protocol: executionPlan?.plan.protocol ?? experiment.citeark?.protocol ?? experiment.publicContract ?? {},
|
|
308
|
+
measurements: (executionPlan?.plan.measurements ?? experiment.citeark?.measurements ?? experiment.publicContract?.measurements ?? []).map((measurement) => ({
|
|
309
|
+
...measurement,
|
|
310
|
+
measurementId: measurement.measurementId ?? measurement.reportedMeasurementId ?? measurement.id,
|
|
311
|
+
})),
|
|
312
|
+
research: {
|
|
313
|
+
claimCoverage: oldAssessment.citeark?.claimCoverage ?? "complete",
|
|
314
|
+
measurementIds: (oldAssessment.method?.comparisons ?? []).map((item) => item.measurementId),
|
|
315
|
+
uncoveredMeasurementIds: oldAssessment.citeark?.uncoveredMeasurementIds ?? [],
|
|
316
|
+
},
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
async function retainedRunnerAudit(directory, manifest, execution) {
|
|
321
|
+
const digest = execution.citeark?.commandRecordsBlobDigest;
|
|
322
|
+
const blob = manifest.blobs.find((item) => item.digest === digest && item.availability === "embedded");
|
|
323
|
+
if (!blob) return null;
|
|
324
|
+
// The source CAP verifier already checked the content-addressed blob.
|
|
325
|
+
const commandRecords = (await readFile(path.join(directory, blob.path), "utf8"))
|
|
326
|
+
.split(/\r?\n/).filter((line) => line.trim()).map((line) => JSON.parse(line));
|
|
327
|
+
return { audit: execution.citeark?.audit ?? null, commandRecords };
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
async function retainedEvidenceContext(directory, manifest, assessment) {
|
|
331
|
+
const digest = assessment.citeark?.evidenceContextBlobDigest;
|
|
332
|
+
const blob = manifest.blobs.find((item) => item.digest === digest && item.availability === "embedded");
|
|
333
|
+
if (!blob) return undefined;
|
|
334
|
+
// Source CAP verification binds these exact bytes; legacy CAPs may lack it.
|
|
335
|
+
return JSON.parse(await readFile(path.join(directory, blob.path), "utf8"));
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function modelComparison(item) {
|
|
339
|
+
return {
|
|
340
|
+
measurementId: item.measurementId,
|
|
341
|
+
comparison: Object.fromEntries(Object.entries(item.comparison ?? {}).map(([key, value]) => [
|
|
342
|
+
key,
|
|
343
|
+
value && typeof value === "object" && !Array.isArray(value) && "decimal" in value
|
|
344
|
+
? Number(value.decimal)
|
|
345
|
+
: value,
|
|
346
|
+
])),
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function reassessmentBody({ modelResult, oldAssessment, oldComparisons, assessedAt }) {
|
|
351
|
+
const byMeasurementId = new Map(
|
|
352
|
+
modelResult.assessment.measurementAssessments.map((item) => [item.measurementId, item]),
|
|
353
|
+
);
|
|
354
|
+
const measurementAssessments = oldComparisons.map((comparison) => ({
|
|
355
|
+
...byMeasurementId.get(comparison.measurementId),
|
|
356
|
+
comparison: modelComparison(comparison).comparison,
|
|
357
|
+
}));
|
|
358
|
+
const body = {
|
|
359
|
+
schemaVersion: "1.0",
|
|
360
|
+
kind: "citeark.claim-reassessment",
|
|
361
|
+
verdict: modelResult.assessment.conclusion,
|
|
362
|
+
verificationStatus: modelResult.assessment.verificationStatus,
|
|
363
|
+
confidence: modelResult.assessment.confidence,
|
|
364
|
+
protocolComparability: modelResult.assessment.protocolComparability,
|
|
365
|
+
...(modelResult.assessment.executionEvidence ? { executionEvidence: modelResult.assessment.executionEvidence } : {}),
|
|
366
|
+
reason: modelResult.assessment.reason,
|
|
367
|
+
limitations: modelResult.assessment.limitations,
|
|
368
|
+
measurementAssessments,
|
|
369
|
+
integrityStatus: oldAssessment.citeark?.integrityStatus ?? "passed",
|
|
370
|
+
assessedAt,
|
|
371
|
+
assessor: modelResult.assessor,
|
|
372
|
+
usage: modelResult.usage,
|
|
373
|
+
};
|
|
374
|
+
return { ...body, assessmentDigest: `sha256:${sha256Value(body)}` };
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function stringArray(value) {
|
|
378
|
+
return Array.isArray(value)
|
|
379
|
+
? value.filter((item) => typeof item === "string" && item.trim()).map((item) => item.trim())
|
|
380
|
+
: [];
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
function sha256Bytes(bytes) { return createHash("sha256").update(bytes).digest("hex"); }
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { researchMapRecords, bindResearchReading, bindResearchIntent } from './research-map-records.mjs';
|
|
2
|
+
import { researchObjectRecords, reportedObservationRecords } from './research-object-records.mjs';
|
|
3
|
+
import { researchPublicContract } from "../../contracts/public-contract.mjs";
|
|
4
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
|
|
7
|
+
import { attachCapAttestation, signCapArtifact } from "./attestation.mjs";
|
|
8
|
+
import { canonicalJsonBytes } from "./canonical-json.mjs";
|
|
9
|
+
import {
|
|
10
|
+
CAP_PROFILE,
|
|
11
|
+
CAP_RECORD_TYPE,
|
|
12
|
+
assembleCapDirectory,
|
|
13
|
+
} from "./core.mjs";
|
|
14
|
+
import {
|
|
15
|
+
CAP_SCHEMA,
|
|
16
|
+
capActorRecord,
|
|
17
|
+
capClaimRecord,
|
|
18
|
+
capHypothesisRecords,
|
|
19
|
+
capDecimal,
|
|
20
|
+
capRecordId,
|
|
21
|
+
capSourceWorkRecord,
|
|
22
|
+
} from "./research-records.mjs";
|
|
23
|
+
import { verifyCapDirectory } from "./verify.mjs";
|
|
24
|
+
import { CiteArkError } from "../../util.mjs";
|
|
25
|
+
|
|
26
|
+
/** Build an immutable scientific plan from one or more compiler outputs. */
|
|
27
|
+
export async function assembleResearchPlanCap({
|
|
28
|
+
directory,
|
|
29
|
+
research,
|
|
30
|
+
compilerRunId,
|
|
31
|
+
planner,
|
|
32
|
+
plannerRunId = compilerRunId,
|
|
33
|
+
sourceCompilationDigests = [],
|
|
34
|
+
sourceArtifactDigests = [],
|
|
35
|
+
createdAt,
|
|
36
|
+
signingKeyPath,
|
|
37
|
+
processingBinding,
|
|
38
|
+
}) {
|
|
39
|
+
if (planner && (typeof planner.id !== "string" || !planner.id || typeof planner.name !== "string" || !planner.name)) {
|
|
40
|
+
throw new CiteArkError("Research Plan planner requires id and name");
|
|
41
|
+
}
|
|
42
|
+
if (typeof plannerRunId !== "string" || !plannerRunId) {
|
|
43
|
+
throw new CiteArkError("Research Plan requires plannerRunId");
|
|
44
|
+
}
|
|
45
|
+
const timestamp = isoDate(createdAt ?? research.provenance?.compiledAt);
|
|
46
|
+
const actorId = planner
|
|
47
|
+
? capRecordId("actor", `research-planner:${planner.id}:${planner.version ?? "unversioned"}`)
|
|
48
|
+
: capRecordId("actor", `research-compiler:${compilerRunId}`);
|
|
49
|
+
const sourceWorkId = capRecordId("work", research.work.id);
|
|
50
|
+
const claimIdByLocalId = new Map(
|
|
51
|
+
research.claims.map((claim) => [claim.id, capRecordId("claim", `${research.work.id}:${claim.id}`)]),
|
|
52
|
+
);
|
|
53
|
+
const experimentIdByLocalId = new Map(
|
|
54
|
+
research.experiments.map((experiment) => [
|
|
55
|
+
experiment.id,
|
|
56
|
+
capRecordId("experiment", `${research.work.id}:${experiment.id}`),
|
|
57
|
+
]),
|
|
58
|
+
);
|
|
59
|
+
const objectGraph = researchObjectRecords({ research, sourceWorkId, actorId });
|
|
60
|
+
const { records: reportedEvidence, idsByClaim: reportedEvidenceIdsByClaim } = reportedObservationRecords({ research, sourceWorkId, objectIds: objectGraph.objectIds });
|
|
61
|
+
|
|
62
|
+
const sourceWork = capSourceWorkRecord({
|
|
63
|
+
research,
|
|
64
|
+
id: sourceWorkId,
|
|
65
|
+
processingBinding,
|
|
66
|
+
});
|
|
67
|
+
const reading = bindResearchReading(research, objectGraph, sourceWorkId);
|
|
68
|
+
if (reading) sourceWork.citeark.reading = reading;
|
|
69
|
+
const claims = research.claims.map((claim) => capClaimRecord({
|
|
70
|
+
claim,
|
|
71
|
+
id: claimIdByLocalId.get(claim.id),
|
|
72
|
+
sourceWorkId,
|
|
73
|
+
actorId,
|
|
74
|
+
reportedEvidenceIds: reportedEvidenceIdsByClaim.get(claim.id),
|
|
75
|
+
objectReferences: (claim.objectIds ?? []).map(id => ({ ref: objectGraph.objectIds.get(id) })),
|
|
76
|
+
}));
|
|
77
|
+
const experiments = research.experiments.map((experiment) => ({
|
|
78
|
+
$schema: CAP_SCHEMA.experiment,
|
|
79
|
+
id: experimentIdByLocalId.get(experiment.id),
|
|
80
|
+
type: CAP_RECORD_TYPE.entity, role: "procedure",
|
|
81
|
+
...((experiment.observationTargets ?? []).length ? {
|
|
82
|
+
objects: [...new Set(experiment.observationTargets.map(target => target.observationId))]
|
|
83
|
+
.map(id => ({ ref: requiredId(objectGraph.objectIds, id, "Source observation") })),
|
|
84
|
+
} : {}),
|
|
85
|
+
inputs: (objectGraph.inputIdsByExperiment.get(experiment.id) ?? []).map(id => ({ record: { ref: id }, role: 'scientific-input', basis: 'declared' })),
|
|
86
|
+
steps: (objectGraph.stepIdsByExperiment.get(experiment.id) ?? []).map(ref => ({ ref })),
|
|
87
|
+
tests: experiment.claimIds.map((claimId) => ({ ref: requiredId(claimIdByLocalId, claimId, "Claim") })),
|
|
88
|
+
publicContract: researchPublicContract(experiment),
|
|
89
|
+
evaluationPlan: {
|
|
90
|
+
title: experiment.title,
|
|
91
|
+
reproductionLevel: experiment.reproductionLevel,
|
|
92
|
+
...(experiment.reproductionScope ? { reproductionScope: experiment.reproductionScope, requiredReproductionScope: experiment.requiredReproductionScope } : {}),
|
|
93
|
+
reconstructionFidelity: experiment.reconstructionFidelity ?? null,
|
|
94
|
+
instructions: experiment.protocol.instructions ?? null,
|
|
95
|
+
state: "planned",
|
|
96
|
+
},
|
|
97
|
+
citeark: {
|
|
98
|
+
...(experiment.researchIntent ? { researchIntent: bindResearchIntent(experiment, research, objectGraph, sourceWorkId) } : {}),
|
|
99
|
+
localId: experiment.id,
|
|
100
|
+
versionId: experiment.versionId,
|
|
101
|
+
title: experiment.title,
|
|
102
|
+
claimIds: experiment.claimIds,
|
|
103
|
+
reproductionLevel: experiment.reproductionLevel,
|
|
104
|
+
...(experiment.reproductionScope ? { reproductionScope: experiment.reproductionScope, requiredReproductionScope: experiment.requiredReproductionScope } : {}),
|
|
105
|
+
implementationOrigin: experiment.implementationOrigin
|
|
106
|
+
?? experiment.repository?.implementationOrigin
|
|
107
|
+
?? "official",
|
|
108
|
+
reconstructionFidelity: experiment.reconstructionFidelity ?? null,
|
|
109
|
+
repository: experiment.repository,
|
|
110
|
+
protocol: experiment.protocol,
|
|
111
|
+
measurements: experiment.measurements ?? [],
|
|
112
|
+
...(experiment.observationTargets !== undefined ? { observationTargets: experiment.observationTargets } : {}),
|
|
113
|
+
compute: experiment.compute ?? null,
|
|
114
|
+
environment: experiment.environment ?? null,
|
|
115
|
+
},
|
|
116
|
+
}));
|
|
117
|
+
const actor = {
|
|
118
|
+
...capActorRecord(
|
|
119
|
+
actorId,
|
|
120
|
+
planner?.name ?? "CiteArk Research Compiler",
|
|
121
|
+
planner?.kind ?? "agent",
|
|
122
|
+
planner?.version ?? research.provenance?.compiler,
|
|
123
|
+
),
|
|
124
|
+
...(planner?.identity ? { identity: structuredClone(planner.identity) } : {}),
|
|
125
|
+
...(planner ? { planner: { id: planner.id } } : {}),
|
|
126
|
+
citeark: planner ? { plannerRunId } : { compilerRunId },
|
|
127
|
+
};
|
|
128
|
+
const built = await assembleCapDirectory({
|
|
129
|
+
directory,
|
|
130
|
+
artifact: {
|
|
131
|
+
id: capRecordId("artifact", `research-plan:${plannerRunId}`),
|
|
132
|
+
createdAt: timestamp,
|
|
133
|
+
createdBy: actorId,
|
|
134
|
+
},
|
|
135
|
+
profiles: [CAP_PROFILE.researchPlan, CAP_PROFILE.publicBundle],
|
|
136
|
+
roots: [
|
|
137
|
+
{ role: "primarySourceWork", ref: sourceWorkId },
|
|
138
|
+
...claims.map((claim) => ({ role: "plannedClaim", ref: claim.id })),
|
|
139
|
+
...experiments.map((experiment) => ({ role: "plannedExperiment", ref: experiment.id })),
|
|
140
|
+
],
|
|
141
|
+
records: [...researchMapRecords({ research: research, objectGraph, sourceWorkId, actorId }), actor, sourceWork, ...objectGraph.records, ...claims, ...experiments, ...reportedEvidence,
|
|
142
|
+
...capHypothesisRecords({ research, sourceWorkId, actorId, objectIds: objectGraph.objectIds })],
|
|
143
|
+
blobs: [{ id: capRecordId('research-plan-input', plannerRunId), roles: ['research-plan-input'],
|
|
144
|
+
mediaType: 'application/json', bytes: canonicalJsonBytes(research),
|
|
145
|
+
rights: { statement: 'Research interpretation and plan; referenced materials retain their own rights.' } }],
|
|
146
|
+
relations: [...new Set([...sourceCompilationDigests, ...sourceArtifactDigests])].map((artifactDigest) => ({
|
|
147
|
+
relationship: "derivedFrom",
|
|
148
|
+
artifactDigest,
|
|
149
|
+
summary: "Research Plan informed by prior source interpretations or research artifacts.",
|
|
150
|
+
})),
|
|
151
|
+
});
|
|
152
|
+
await writeResearchAttachments({ directory, built, research });
|
|
153
|
+
const attestation = await signCapArtifact({
|
|
154
|
+
artifactDigest: built.artifactDigest,
|
|
155
|
+
actor: { ref: actorId },
|
|
156
|
+
role: "researchPlanAssembler",
|
|
157
|
+
createdAt: timestamp,
|
|
158
|
+
signingKeyPath,
|
|
159
|
+
processingBinding,
|
|
160
|
+
});
|
|
161
|
+
const attachedAttestation = await attachCapAttestation({ directory, attestation });
|
|
162
|
+
const verification = await verifyCapDirectory(directory);
|
|
163
|
+
if (!verification.valid) {
|
|
164
|
+
throw new CiteArkError(`Research Plan CAP 2.0 failed self-verification:\n- ${verification.issues.join("\n- ")}`);
|
|
165
|
+
}
|
|
166
|
+
return {
|
|
167
|
+
...built,
|
|
168
|
+
verification,
|
|
169
|
+
attestation: attachedAttestation,
|
|
170
|
+
recordIds: {
|
|
171
|
+
actor: actorId,
|
|
172
|
+
sourceWork: sourceWorkId,
|
|
173
|
+
claims: Object.fromEntries(claimIdByLocalId),
|
|
174
|
+
experiments: Object.fromEntries(experimentIdByLocalId),
|
|
175
|
+
researchObjects: Object.fromEntries(objectGraph.objectIds),
|
|
176
|
+
},
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
async function writeResearchAttachments({ directory, built, research }) {
|
|
181
|
+
await Promise.all([
|
|
182
|
+
mkdir(path.join(directory, "preview"), { recursive: true }),
|
|
183
|
+
mkdir(path.join(directory, "projections", "citeark"), { recursive: true }),
|
|
184
|
+
]);
|
|
185
|
+
const crate = {
|
|
186
|
+
"@context": "https://w3id.org/ro/crate/1.3/context",
|
|
187
|
+
"@graph": [
|
|
188
|
+
{ "@id": "ro-crate-metadata.json", "@type": "CreativeWork", about: { "@id": "./" } },
|
|
189
|
+
{
|
|
190
|
+
"@id": "./",
|
|
191
|
+
"@type": "Dataset",
|
|
192
|
+
name: research.work.title,
|
|
193
|
+
identifier: built.artifactDigest,
|
|
194
|
+
hasPart: built.manifest.records.map((record) => ({ "@id": record.path })),
|
|
195
|
+
},
|
|
196
|
+
...built.manifest.records.map((record) => ({
|
|
197
|
+
"@id": record.path,
|
|
198
|
+
"@type": "CreativeWork",
|
|
199
|
+
identifier: record.digest,
|
|
200
|
+
encodingFormat: record.mediaType,
|
|
201
|
+
})),
|
|
202
|
+
],
|
|
203
|
+
};
|
|
204
|
+
const planned = research.claims.filter((claim) => claim.reproduction?.status === "planned").length;
|
|
205
|
+
const blocked = research.claims.filter((claim) => claim.reproduction?.status === "blocked").length;
|
|
206
|
+
const contextual = research.claims.length - planned - blocked;
|
|
207
|
+
const significance = research.work.significance
|
|
208
|
+
? `## What this means\n\n${research.work.significance}\n\n> This is an AI-generated plain-language interpretation, not reproduction evidence or a scientific Assessment.\n\n`
|
|
209
|
+
: "";
|
|
210
|
+
const readme = `# ${research.work.title}\n\n${research.work.abstract}\n\n${significance}## Research plan snapshot\n\n- Claims: ${research.claims.length}\n- Planned reproductions: ${planned}\n- Blocked claims: ${blocked}\n- Contextual claims: ${contextual}\n- Planned experiments: ${research.experiments.length}\n\nThis document is a derived preview. Canonical scientific content is stored in CAP Records.\n`;
|
|
211
|
+
await Promise.all([
|
|
212
|
+
writeFile(path.join(directory, "ro-crate-metadata.json"), `${JSON.stringify(crate, null, 2)}\n`),
|
|
213
|
+
writeFile(path.join(directory, "preview", "README.md"), readme),
|
|
214
|
+
writeFile(
|
|
215
|
+
path.join(directory, "projections", "citeark", "research-plan.json"),
|
|
216
|
+
canonicalJsonBytes(research),
|
|
217
|
+
),
|
|
218
|
+
]);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function requiredId(values, key, label) {
|
|
222
|
+
const value = values.get(key);
|
|
223
|
+
if (!value) throw new CiteArkError(`${label} reference is missing from the research plan: ${key}`);
|
|
224
|
+
return value;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function isoDate(value) {
|
|
228
|
+
const date = new Date(value ?? Date.now());
|
|
229
|
+
if (!Number.isFinite(date.getTime())) throw new CiteArkError(`CAP timestamp is invalid: ${String(value)}`);
|
|
230
|
+
return date.toISOString();
|
|
231
|
+
}
|