diff --git a/README.md b/README.md
index d338cf68..ab1e61a9 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,4 @@
# deepevents.ai
deepevents.ai main codebase
+
+- [Repository compute sandbox policy guard](repository-compute-sandbox-policy-guard/README.md)
diff --git a/repository-compute-sandbox-policy-guard/README.md b/repository-compute-sandbox-policy-guard/README.md
new file mode 100644
index 00000000..3d0c4f46
--- /dev/null
+++ b/repository-compute-sandbox-policy-guard/README.md
@@ -0,0 +1,49 @@
+# Repository Compute Sandbox Policy Guard
+
+This module is a self-contained Project Repository & Version Control slice for SCIBASE.AI issue #10. It evaluates whether a tagged scientific repository release/export candidate is safe to execute in a reproducibility sandbox before merge, DOI publication, or export bundle distribution.
+
+The guard uses synthetic data only. It does not scan real repositories, call external services, use credentials, or process patient/research participant data.
+
+## Scope
+
+- Require digest-pinned sandbox container images.
+- Block open or unreviewed network egress during reproducibility replay.
+- Enforce CPU, memory, runtime, GPU, and deterministic-seed policy.
+- Restrict writable mounts to controlled scratch paths with size limits.
+- Require sha256 checkpoints for input manifests, lockfiles, expected artifacts, component manifests, and export bundles.
+- Emit deterministic remediation actions for protected merge, export, and DOI publication gates.
+
+## Requirement Mapping
+
+| Issue #10 area | Implementation |
+| --- | --- |
+| Computation-aware reproducibility | CPU, memory, runtime, GPU, deterministic seed, and container replay checks |
+| Container support | Digest-pinned image validation for each pipeline |
+| Execution sandboxes | Network egress and writable mount policy checks |
+| Hash-based integrity | Component, input, lockfile, artifact, and export bundle sha256 checkpoints |
+| Programmatic access & export | Release actions block unsafe export bundles and DOI publication |
+
+## Files
+
+- `index.js` - dependency-free evaluator, report renderer, Markdown renderer, and SVG renderer.
+- `sample-data.js` - synthetic ready, blocked, and needs-review repository candidates.
+- `test.js` - Node assertion coverage for the gate decisions and report renderers.
+- `demo.js` - writes deterministic JSON, Markdown, and SVG reviewer artifacts.
+- `scripts/render-demo-video.js` - optional ffmpeg-based MP4 renderer for the reviewer packet.
+- `reports/` - generated reviewer artifacts.
+
+## Validation
+
+```bash
+npm run check
+npm test
+npm run demo
+```
+
+Optional video render when ffmpeg is available:
+
+```bash
+npm run demo:video
+```
+
+The included demo artifacts are deterministic and based only on the synthetic fixtures in `sample-data.js`.
diff --git a/repository-compute-sandbox-policy-guard/demo.js b/repository-compute-sandbox-policy-guard/demo.js
new file mode 100644
index 00000000..ae65f076
--- /dev/null
+++ b/repository-compute-sandbox-policy-guard/demo.js
@@ -0,0 +1,27 @@
+const fs = require("node:fs");
+const path = require("node:path");
+const {
+ createPolicyReport,
+ renderMarkdown,
+ renderSvg,
+} = require("./index");
+const { sampleCandidates } = require("./sample-data");
+
+const reportsDir = path.join(__dirname, "reports");
+fs.mkdirSync(reportsDir, { recursive: true });
+
+const report = createPolicyReport(sampleCandidates);
+const jsonPath = path.join(reportsDir, "demo.json");
+const markdownPath = path.join(reportsDir, "demo.md");
+const svgPath = path.join(reportsDir, "demo.svg");
+
+fs.writeFileSync(jsonPath, `${JSON.stringify(report, null, 2)}\n`);
+fs.writeFileSync(markdownPath, renderMarkdown(report));
+fs.writeFileSync(svgPath, renderSvg(report));
+
+console.log(`Wrote ${path.relative(process.cwd(), jsonPath)}`);
+console.log(`Wrote ${path.relative(process.cwd(), markdownPath)}`);
+console.log(`Wrote ${path.relative(process.cwd(), svgPath)}`);
+console.log(
+ `Ready=${report.totals.ready} NeedsReview=${report.totals.needsReview} Blocked=${report.totals.blocked}`,
+);
diff --git a/repository-compute-sandbox-policy-guard/index.js b/repository-compute-sandbox-policy-guard/index.js
new file mode 100644
index 00000000..d6b73f81
--- /dev/null
+++ b/repository-compute-sandbox-policy-guard/index.js
@@ -0,0 +1,495 @@
+const DEFAULT_POLICY = Object.freeze({
+ generatedAt: "2026-05-22T00:00:00.000Z",
+ maxCpuCores: 16,
+ maxMemoryGb: 64,
+ maxRuntimeMinutes: 240,
+ maxWritableGb: 2,
+ allowedEgressModes: ["none", "doi-resolution-only"],
+ doiResolverAllowlist: ["api.datacite.org", "doi.org"],
+ permittedWritablePrefixes: ["/tmp/scibase-run/"],
+ requiredHashPrefix: "sha256:",
+});
+
+const REQUIREMENT_MAP = Object.freeze({
+ pinned_image: "Computation-aware reproducibility: container support",
+ network_policy: "Execution sandboxes: secure runtime validation",
+ compute_budget: "Computation-aware reproducibility: controlled execution",
+ writable_mounts: "Execution sandboxes: limited mutable state",
+ artifact_hashes: "File and metadata versioning: hash-based integrity",
+ export_gate: "Programmatic access and export: release bundle guard",
+});
+
+function evaluateRepositoryComputePolicy(candidate, policy = DEFAULT_POLICY) {
+ assertCandidate(candidate);
+
+ const checks = [];
+ const issues = [];
+ const addCheck = (pipelineId, code, passed, detail) => {
+ checks.push({ pipelineId, code, passed, detail });
+ };
+ const addIssue = (
+ severity,
+ pipelineId,
+ code,
+ message,
+ requirement,
+ remediation,
+ ) => {
+ issues.push({
+ severity,
+ pipelineId,
+ code,
+ message,
+ requirement,
+ remediation,
+ });
+ };
+
+ if (!isHash(candidate.componentManifestHash, policy)) {
+ addIssue(
+ "error",
+ "repository",
+ "COMPONENT_MANIFEST_HASH_MISSING",
+ "The repository component manifest is missing a deterministic hash.",
+ REQUIREMENT_MAP.artifact_hashes,
+ "Generate a sha256 component manifest before release or export.",
+ );
+ }
+
+ if (!isHash(candidate.exportBundleHash, policy)) {
+ addIssue(
+ "error",
+ "repository",
+ "EXPORT_BUNDLE_HASH_MISSING",
+ "The export bundle is missing a deterministic hash.",
+ REQUIREMENT_MAP.export_gate,
+ "Build the release bundle and attach its sha256 hash before DOI publication.",
+ );
+ }
+
+ const pipelines = [...candidate.pipelines].sort((a, b) =>
+ a.id.localeCompare(b.id),
+ );
+
+ for (const pipeline of pipelines) {
+ const sandbox = pipeline.sandbox || {};
+ const compute = pipeline.compute || {};
+
+ checkPinnedImage(pipeline, sandbox, policy, addCheck, addIssue);
+ checkNetworkPolicy(pipeline, sandbox, policy, addCheck, addIssue);
+ checkComputeBudget(pipeline, compute, policy, addCheck, addIssue);
+ checkWritableMounts(pipeline, sandbox, policy, addCheck, addIssue);
+ checkPipelineHashes(pipeline, policy, addCheck, addIssue);
+ }
+
+ const blockingIssues = issues.filter((issue) => issue.severity === "error");
+ const warnings = issues.filter((issue) => issue.severity === "warning");
+ const status =
+ blockingIssues.length > 0
+ ? "blocked"
+ : warnings.length > 0
+ ? "needs_review"
+ : "ready";
+
+ return {
+ generatedAt: policy.generatedAt,
+ repositoryId: candidate.repositoryId,
+ tag: candidate.tag,
+ doiDraft: candidate.doiDraft,
+ status,
+ summary: {
+ pipelines: pipelines.length,
+ checks: checks.length,
+ passedChecks: checks.filter((check) => check.passed).length,
+ blockingIssues: blockingIssues.length,
+ warnings: warnings.length,
+ },
+ checks,
+ issues: issues.sort(compareIssues),
+ releaseActions: buildReleaseActions(status, issues),
+ requirementCoverage: REQUIREMENT_MAP,
+ };
+}
+
+function createPolicyReport(candidates, policy = DEFAULT_POLICY) {
+ const evaluations = candidates
+ .map((candidate) => evaluateRepositoryComputePolicy(candidate, policy))
+ .sort((a, b) => a.repositoryId.localeCompare(b.repositoryId));
+
+ return {
+ generatedAt: policy.generatedAt,
+ guard: "repository-compute-sandbox-policy-guard",
+ issue: "SCIBASE-AI/SCIBASE.AI#10",
+ totals: {
+ candidates: evaluations.length,
+ ready: evaluations.filter((item) => item.status === "ready").length,
+ needsReview: evaluations.filter((item) => item.status === "needs_review")
+ .length,
+ blocked: evaluations.filter((item) => item.status === "blocked").length,
+ blockingIssues: evaluations.reduce(
+ (sum, item) => sum + item.summary.blockingIssues,
+ 0,
+ ),
+ warnings: evaluations.reduce((sum, item) => sum + item.summary.warnings, 0),
+ },
+ evaluations,
+ };
+}
+
+function renderMarkdown(report) {
+ const rows = report.evaluations
+ .map(
+ (item) =>
+ `| ${item.repositoryId} | ${item.tag} | ${item.status} | ${item.summary.blockingIssues} | ${item.summary.warnings} |`,
+ )
+ .join("\n");
+
+ const issueList = report.evaluations
+ .flatMap((item) =>
+ item.issues.map(
+ (issue) =>
+ `- ${item.repositoryId} ${issue.pipelineId} ${issue.code}: ${issue.remediation}`,
+ ),
+ )
+ .join("\n");
+
+ return `# Repository Compute Sandbox Policy Guard
+
+Synthetic reviewer packet for ${report.issue}.
+
+## Summary
+
+| Candidate | Tag | Status | Blocking issues | Warnings |
+| --- | --- | --- | ---: | ---: |
+${rows}
+
+## Gate Totals
+
+- Ready candidates: ${report.totals.ready}
+- Needs review: ${report.totals.needsReview}
+- Blocked candidates: ${report.totals.blocked}
+- Blocking issues: ${report.totals.blockingIssues}
+- Warnings: ${report.totals.warnings}
+
+## Deterministic Remediation Queue
+
+${issueList || "- No remediation required."}
+
+## Requirement Mapping
+
+- Pinned sandbox images: ${REQUIREMENT_MAP.pinned_image}
+- Network egress policy: ${REQUIREMENT_MAP.network_policy}
+- CPU, memory, runtime, and GPU budgets: ${REQUIREMENT_MAP.compute_budget}
+- Limited writable mounts: ${REQUIREMENT_MAP.writable_mounts}
+- Artifact hash checkpoints: ${REQUIREMENT_MAP.artifact_hashes}
+- DOI and export blocking actions: ${REQUIREMENT_MAP.export_gate}
+`;
+}
+
+function renderSvg(report) {
+ const width = 960;
+ const height = 540;
+ const cardWidth = 280;
+ const colors = {
+ ready: "#157f57",
+ needs_review: "#b7791f",
+ blocked: "#b42318",
+ };
+ const cards = report.evaluations
+ .map((item, index) => {
+ const x = 40 + index * (cardWidth + 20);
+ const color = colors[item.status];
+ return `
+
+
+ ${escapeXml(item.repositoryId)}
+ ${escapeXml(item.tag)}
+ ${escapeXml(item.status)}
+ Checks: ${item.summary.passedChecks}/${item.summary.checks}
+ Blocks: ${item.summary.blockingIssues}
+`;
+ })
+ .join("\n");
+
+ return `
+`;
+}
+
+function checkPinnedImage(pipeline, sandbox, policy, addCheck, addIssue) {
+ const image = sandbox.image || "";
+ const pinned = image.includes("@sha256:") && !image.endsWith(":latest");
+ addCheck(pipeline.id, "PINNED_SANDBOX_IMAGE", pinned, image || "missing image");
+
+ if (!pinned) {
+ addIssue(
+ "error",
+ pipeline.id,
+ "SANDBOX_IMAGE_NOT_PINNED",
+ "Sandbox image must be digest-pinned and must not use latest tags.",
+ REQUIREMENT_MAP.pinned_image,
+ "Pin the execution image with an immutable sha256 digest.",
+ );
+ }
+}
+
+function checkNetworkPolicy(pipeline, sandbox, policy, addCheck, addIssue) {
+ const mode = sandbox.networkEgress || "unspecified";
+ const allowedMode = policy.allowedEgressModes.includes(mode);
+ const allowlist = [...(sandbox.egressAllowlist || [])].sort();
+ const doiOnly =
+ mode !== "doi-resolution-only" ||
+ (allowlist.length > 0 &&
+ allowlist.every((host) => policy.doiResolverAllowlist.includes(host)));
+ const passed = allowedMode && doiOnly;
+ addCheck(
+ pipeline.id,
+ "NETWORK_EGRESS_POLICY",
+ passed,
+ `mode=${mode}; allowlist=${allowlist.join(",") || "empty"}`,
+ );
+
+ if (!allowedMode || !doiOnly) {
+ addIssue(
+ "error",
+ pipeline.id,
+ "NETWORK_EGRESS_TOO_BROAD",
+ "Reproducibility execution cannot use open or unreviewed network egress.",
+ REQUIREMENT_MAP.network_policy,
+ "Set egress to none, or restrict DOI lookup to the approved resolver allowlist.",
+ );
+ }
+}
+
+function checkComputeBudget(pipeline, compute, policy, addCheck, addIssue) {
+ const cpuOk = numberAtMost(compute.cpuCores, policy.maxCpuCores);
+ const memoryOk = numberAtMost(compute.memoryGb, policy.maxMemoryGb);
+ const runtimeOk = numberAtMost(
+ compute.runtimeMinutes,
+ policy.maxRuntimeMinutes,
+ );
+ const seeded = Boolean(compute.deterministicSeed);
+
+ addCheck(
+ pipeline.id,
+ "CPU_MEMORY_RUNTIME_BUDGET",
+ cpuOk && memoryOk && runtimeOk,
+ `cpu=${compute.cpuCores}; memoryGb=${compute.memoryGb}; runtimeMinutes=${compute.runtimeMinutes}`,
+ );
+ addCheck(pipeline.id, "DETERMINISTIC_SEED", seeded, compute.deterministicSeed);
+
+ if (!cpuOk || !memoryOk || !runtimeOk) {
+ addIssue(
+ "error",
+ pipeline.id,
+ "COMPUTE_BUDGET_EXCEEDED",
+ "The pipeline exceeds the approved CPU, memory, or runtime budget.",
+ REQUIREMENT_MAP.compute_budget,
+ `Keep CPU <= ${policy.maxCpuCores}, memory <= ${policy.maxMemoryGb} GB, and runtime <= ${policy.maxRuntimeMinutes} minutes.`,
+ );
+ }
+
+ if (!seeded) {
+ addIssue(
+ "error",
+ pipeline.id,
+ "DETERMINISTIC_SEED_MISSING",
+ "The pipeline does not declare a deterministic seed.",
+ REQUIREMENT_MAP.compute_budget,
+ "Declare the seed used for replay so results can be regenerated byte-for-byte.",
+ );
+ }
+
+ if ((compute.gpuCount || 0) > 0) {
+ const justified = Boolean(compute.gpuJustification) && seeded;
+ addCheck(
+ pipeline.id,
+ "GPU_REVIEW_PACKET",
+ justified,
+ compute.gpuJustification || "missing GPU justification",
+ );
+ addIssue(
+ justified ? "warning" : "error",
+ pipeline.id,
+ "GPU_REVIEW_REQUIRED",
+ "GPU execution requires reviewer signoff because hardware kernels can affect determinism.",
+ REQUIREMENT_MAP.compute_budget,
+ "Attach deterministic kernel notes and reviewer signoff before final publication.",
+ );
+ }
+}
+
+function checkWritableMounts(pipeline, sandbox, policy, addCheck, addIssue) {
+ const mounts = sandbox.writableMounts || [];
+ const inputsReadOnly = sandbox.readOnlyInputs === true;
+ addCheck(
+ pipeline.id,
+ "READ_ONLY_INPUTS",
+ inputsReadOnly,
+ inputsReadOnly ? "inputs are read-only" : "inputs may be mutable",
+ );
+
+ if (!inputsReadOnly) {
+ addIssue(
+ "error",
+ pipeline.id,
+ "INPUTS_NOT_READ_ONLY",
+ "Repository inputs must be mounted read-only during replay.",
+ REQUIREMENT_MAP.writable_mounts,
+ "Mount repository inputs read-only and write outputs only to the controlled scratch path.",
+ );
+ }
+
+ for (const mount of mounts) {
+ const allowedPath = policy.permittedWritablePrefixes.some((prefix) =>
+ String(mount.path || "").startsWith(prefix),
+ );
+ const sizeOk = numberAtMost(mount.maxGb, policy.maxWritableGb);
+ addCheck(
+ pipeline.id,
+ "WRITABLE_MOUNT",
+ allowedPath && sizeOk,
+ `path=${mount.path}; maxGb=${mount.maxGb}`,
+ );
+
+ if (!allowedPath || !sizeOk) {
+ addIssue(
+ "error",
+ pipeline.id,
+ "WRITABLE_MOUNT_NOT_CONSTRAINED",
+ "Writable mount is outside the approved scratch prefix or exceeds size limits.",
+ REQUIREMENT_MAP.writable_mounts,
+ `Use ${policy.permittedWritablePrefixes.join(", ")} with max ${policy.maxWritableGb} GB.`,
+ );
+ }
+ }
+}
+
+function checkPipelineHashes(pipeline, policy, addCheck, addIssue) {
+ const hashFields = [
+ ["INPUT_MANIFEST_HASH", pipeline.inputManifestHash],
+ ["LOCKFILE_HASH", pipeline.lockfileHash],
+ ];
+ for (const [code, value] of hashFields) {
+ const passed = isHash(value, policy);
+ addCheck(pipeline.id, code, passed, value || "missing");
+ if (!passed) {
+ addIssue(
+ "error",
+ pipeline.id,
+ `${code}_MISSING`,
+ `${code} is missing a sha256 checkpoint.`,
+ REQUIREMENT_MAP.artifact_hashes,
+ `Generate ${code.toLowerCase()} before accepting this release candidate.`,
+ );
+ }
+ }
+
+ const artifacts = pipeline.expectedArtifacts || [];
+ for (const artifact of artifacts) {
+ const passed = isHash(artifact.hash, policy);
+ addCheck(
+ pipeline.id,
+ `ARTIFACT_HASH_${artifact.name}`,
+ passed,
+ artifact.hash || "missing",
+ );
+ if (!passed) {
+ addIssue(
+ "error",
+ pipeline.id,
+ "EXPECTED_ARTIFACT_HASH_MISSING",
+ `Expected artifact ${artifact.name} is missing a sha256 checkpoint.`,
+ REQUIREMENT_MAP.artifact_hashes,
+ "Run the pipeline once in the sandbox and record the artifact hash before export.",
+ );
+ }
+ }
+}
+
+function buildReleaseActions(status, issues) {
+ const blocking = issues.filter((issue) => issue.severity === "error");
+ const warnings = issues.filter((issue) => issue.severity === "warning");
+ return {
+ allowProtectedMerge: blocking.length === 0,
+ allowExportBundle: blocking.length === 0,
+ allowDoiPublication: blocking.length === 0 && warnings.length === 0,
+ requiresReviewerSignoff: warnings.length > 0,
+ remediationChecklist: issues.map((issue) => ({
+ code: issue.code,
+ pipelineId: issue.pipelineId,
+ action: issue.remediation,
+ })),
+ status,
+ };
+}
+
+function assertCandidate(candidate) {
+ if (!candidate || typeof candidate !== "object") {
+ throw new TypeError("candidate must be an object");
+ }
+ for (const field of ["repositoryId", "tag", "pipelines"]) {
+ if (!candidate[field]) {
+ throw new TypeError(`candidate.${field} is required`);
+ }
+ }
+ if (!Array.isArray(candidate.pipelines) || candidate.pipelines.length === 0) {
+ throw new TypeError("candidate.pipelines must contain at least one pipeline");
+ }
+}
+
+function numberAtMost(value, max) {
+ return typeof value === "number" && Number.isFinite(value) && value <= max;
+}
+
+function isHash(value, policy) {
+ return (
+ typeof value === "string" &&
+ value.startsWith(policy.requiredHashPrefix) &&
+ value.length >= policy.requiredHashPrefix.length + 16
+ );
+}
+
+function compareIssues(a, b) {
+ return (
+ severityRank(a.severity) - severityRank(b.severity) ||
+ a.pipelineId.localeCompare(b.pipelineId) ||
+ a.code.localeCompare(b.code)
+ );
+}
+
+function severityRank(severity) {
+ return severity === "error" ? 0 : 1;
+}
+
+function escapeXml(value) {
+ return String(value)
+ .replaceAll("&", "&")
+ .replaceAll("<", "<")
+ .replaceAll(">", ">")
+ .replaceAll('"', """);
+}
+
+module.exports = {
+ DEFAULT_POLICY,
+ REQUIREMENT_MAP,
+ createPolicyReport,
+ evaluateRepositoryComputePolicy,
+ renderMarkdown,
+ renderSvg,
+};
diff --git a/repository-compute-sandbox-policy-guard/package.json b/repository-compute-sandbox-policy-guard/package.json
new file mode 100644
index 00000000..e2a904c1
--- /dev/null
+++ b/repository-compute-sandbox-policy-guard/package.json
@@ -0,0 +1,13 @@
+{
+ "name": "repository-compute-sandbox-policy-guard",
+ "version": "1.0.0",
+ "private": true,
+ "type": "commonjs",
+ "description": "Synthetic compute sandbox policy guard for SCIBASE project repository releases.",
+ "scripts": {
+ "check": "node --check index.js && node --check sample-data.js && node --check demo.js && node --check test.js && node --check scripts/render-demo-video.js",
+ "test": "node test.js",
+ "demo": "node demo.js",
+ "demo:video": "node scripts/render-demo-video.js"
+ }
+}
diff --git a/repository-compute-sandbox-policy-guard/reports/demo.json b/repository-compute-sandbox-policy-guard/reports/demo.json
new file mode 100644
index 00000000..be1e72f9
--- /dev/null
+++ b/repository-compute-sandbox-policy-guard/reports/demo.json
@@ -0,0 +1,454 @@
+{
+ "generatedAt": "2026-05-22T00:00:00.000Z",
+ "guard": "repository-compute-sandbox-policy-guard",
+ "issue": "SCIBASE-AI/SCIBASE.AI#10",
+ "totals": {
+ "candidates": 3,
+ "ready": 1,
+ "needsReview": 1,
+ "blocked": 1,
+ "blockingIssues": 11,
+ "warnings": 1
+ },
+ "evaluations": [
+ {
+ "generatedAt": "2026-05-22T00:00:00.000Z",
+ "repositoryId": "climate-lab/regional-ensemble",
+ "tag": "preprint-v1.4",
+ "doiDraft": "10.5555/scibase.regional-ensemble.v1",
+ "status": "needs_review",
+ "summary": {
+ "pipelines": 1,
+ "checks": 10,
+ "passedChecks": 10,
+ "blockingIssues": 0,
+ "warnings": 1
+ },
+ "checks": [
+ {
+ "pipelineId": "gpu-ensemble-replay",
+ "code": "PINNED_SANDBOX_IMAGE",
+ "passed": true,
+ "detail": "ghcr.io/scibase/repro-cuda@sha256:2222222222222222222222222222222222222222222222222222222222222222"
+ },
+ {
+ "pipelineId": "gpu-ensemble-replay",
+ "code": "NETWORK_EGRESS_POLICY",
+ "passed": true,
+ "detail": "mode=doi-resolution-only; allowlist=api.datacite.org,doi.org"
+ },
+ {
+ "pipelineId": "gpu-ensemble-replay",
+ "code": "CPU_MEMORY_RUNTIME_BUDGET",
+ "passed": true,
+ "detail": "cpu=12; memoryGb=48; runtimeMinutes=210"
+ },
+ {
+ "pipelineId": "gpu-ensemble-replay",
+ "code": "DETERMINISTIC_SEED",
+ "passed": true,
+ "detail": "regional-ensemble-v1.4"
+ },
+ {
+ "pipelineId": "gpu-ensemble-replay",
+ "code": "GPU_REVIEW_PACKET",
+ "passed": true,
+ "detail": "Replay published CUDA ensemble with deterministic kernels and fixed seeds."
+ },
+ {
+ "pipelineId": "gpu-ensemble-replay",
+ "code": "READ_ONLY_INPUTS",
+ "passed": true,
+ "detail": "inputs are read-only"
+ },
+ {
+ "pipelineId": "gpu-ensemble-replay",
+ "code": "WRITABLE_MOUNT",
+ "passed": true,
+ "detail": "path=/tmp/scibase-run/gpu-ensemble-replay; maxGb=2"
+ },
+ {
+ "pipelineId": "gpu-ensemble-replay",
+ "code": "INPUT_MANIFEST_HASH",
+ "passed": true,
+ "detail": "sha256:eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"
+ },
+ {
+ "pipelineId": "gpu-ensemble-replay",
+ "code": "LOCKFILE_HASH",
+ "passed": true,
+ "detail": "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
+ },
+ {
+ "pipelineId": "gpu-ensemble-replay",
+ "code": "ARTIFACT_HASH_results/ensemble/replay-summary.json",
+ "passed": true,
+ "detail": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+ }
+ ],
+ "issues": [
+ {
+ "severity": "warning",
+ "pipelineId": "gpu-ensemble-replay",
+ "code": "GPU_REVIEW_REQUIRED",
+ "message": "GPU execution requires reviewer signoff because hardware kernels can affect determinism.",
+ "requirement": "Computation-aware reproducibility: controlled execution",
+ "remediation": "Attach deterministic kernel notes and reviewer signoff before final publication."
+ }
+ ],
+ "releaseActions": {
+ "allowProtectedMerge": true,
+ "allowExportBundle": true,
+ "allowDoiPublication": false,
+ "requiresReviewerSignoff": true,
+ "remediationChecklist": [
+ {
+ "code": "GPU_REVIEW_REQUIRED",
+ "pipelineId": "gpu-ensemble-replay",
+ "action": "Attach deterministic kernel notes and reviewer signoff before final publication."
+ }
+ ],
+ "status": "needs_review"
+ },
+ "requirementCoverage": {
+ "pinned_image": "Computation-aware reproducibility: container support",
+ "network_policy": "Execution sandboxes: secure runtime validation",
+ "compute_budget": "Computation-aware reproducibility: controlled execution",
+ "writable_mounts": "Execution sandboxes: limited mutable state",
+ "artifact_hashes": "File and metadata versioning: hash-based integrity",
+ "export_gate": "Programmatic access and export: release bundle guard"
+ }
+ },
+ {
+ "generatedAt": "2026-05-22T00:00:00.000Z",
+ "repositoryId": "neuro-lab/sleep-spindle-atlas",
+ "tag": "preprint-v2.1",
+ "doiDraft": "10.5555/scibase.sleep-spindle-atlas.v2",
+ "status": "ready",
+ "summary": {
+ "pipelines": 1,
+ "checks": 10,
+ "passedChecks": 10,
+ "blockingIssues": 0,
+ "warnings": 0
+ },
+ "checks": [
+ {
+ "pipelineId": "analysis-primary",
+ "code": "PINNED_SANDBOX_IMAGE",
+ "passed": true,
+ "detail": "ghcr.io/scibase/repro-python@sha256:1111111111111111111111111111111111111111111111111111111111111111"
+ },
+ {
+ "pipelineId": "analysis-primary",
+ "code": "NETWORK_EGRESS_POLICY",
+ "passed": true,
+ "detail": "mode=none; allowlist=empty"
+ },
+ {
+ "pipelineId": "analysis-primary",
+ "code": "CPU_MEMORY_RUNTIME_BUDGET",
+ "passed": true,
+ "detail": "cpu=8; memoryGb=32; runtimeMinutes=115"
+ },
+ {
+ "pipelineId": "analysis-primary",
+ "code": "DETERMINISTIC_SEED",
+ "passed": true,
+ "detail": "sleep-spindle-atlas-v2"
+ },
+ {
+ "pipelineId": "analysis-primary",
+ "code": "READ_ONLY_INPUTS",
+ "passed": true,
+ "detail": "inputs are read-only"
+ },
+ {
+ "pipelineId": "analysis-primary",
+ "code": "WRITABLE_MOUNT",
+ "passed": true,
+ "detail": "path=/tmp/scibase-run/analysis-primary; maxGb=1.5"
+ },
+ {
+ "pipelineId": "analysis-primary",
+ "code": "INPUT_MANIFEST_HASH",
+ "passed": true,
+ "detail": "sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"
+ },
+ {
+ "pipelineId": "analysis-primary",
+ "code": "LOCKFILE_HASH",
+ "passed": true,
+ "detail": "sha256:dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"
+ },
+ {
+ "pipelineId": "analysis-primary",
+ "code": "ARTIFACT_HASH_results/tables/spindle-summary.csv",
+ "passed": true,
+ "detail": "sha256:eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"
+ },
+ {
+ "pipelineId": "analysis-primary",
+ "code": "ARTIFACT_HASH_results/figures/cohort-comparison.svg",
+ "passed": true,
+ "detail": "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
+ }
+ ],
+ "issues": [],
+ "releaseActions": {
+ "allowProtectedMerge": true,
+ "allowExportBundle": true,
+ "allowDoiPublication": true,
+ "requiresReviewerSignoff": false,
+ "remediationChecklist": [],
+ "status": "ready"
+ },
+ "requirementCoverage": {
+ "pinned_image": "Computation-aware reproducibility: container support",
+ "network_policy": "Execution sandboxes: secure runtime validation",
+ "compute_budget": "Computation-aware reproducibility: controlled execution",
+ "writable_mounts": "Execution sandboxes: limited mutable state",
+ "artifact_hashes": "File and metadata versioning: hash-based integrity",
+ "export_gate": "Programmatic access and export: release bundle guard"
+ }
+ },
+ {
+ "generatedAt": "2026-05-22T00:00:00.000Z",
+ "repositoryId": "oncology-lab/cell-response-map",
+ "tag": "release-unsafe-candidate",
+ "doiDraft": "10.5555/scibase.cell-response-map.unsafe",
+ "status": "blocked",
+ "summary": {
+ "pipelines": 1,
+ "checks": 10,
+ "passedChecks": 0,
+ "blockingIssues": 11,
+ "warnings": 0
+ },
+ "checks": [
+ {
+ "pipelineId": "training-open-network",
+ "code": "PINNED_SANDBOX_IMAGE",
+ "passed": false,
+ "detail": "python:latest"
+ },
+ {
+ "pipelineId": "training-open-network",
+ "code": "NETWORK_EGRESS_POLICY",
+ "passed": false,
+ "detail": "mode=open-internet; allowlist=example-data.invalid,pypi.org"
+ },
+ {
+ "pipelineId": "training-open-network",
+ "code": "CPU_MEMORY_RUNTIME_BUDGET",
+ "passed": false,
+ "detail": "cpu=24; memoryGb=96; runtimeMinutes=360"
+ },
+ {
+ "pipelineId": "training-open-network",
+ "code": "DETERMINISTIC_SEED",
+ "passed": false,
+ "detail": ""
+ },
+ {
+ "pipelineId": "training-open-network",
+ "code": "GPU_REVIEW_PACKET",
+ "passed": false,
+ "detail": "missing GPU justification"
+ },
+ {
+ "pipelineId": "training-open-network",
+ "code": "READ_ONLY_INPUTS",
+ "passed": false,
+ "detail": "inputs may be mutable"
+ },
+ {
+ "pipelineId": "training-open-network",
+ "code": "WRITABLE_MOUNT",
+ "passed": false,
+ "detail": "path=/workspace; maxGb=12"
+ },
+ {
+ "pipelineId": "training-open-network",
+ "code": "INPUT_MANIFEST_HASH",
+ "passed": false,
+ "detail": "missing"
+ },
+ {
+ "pipelineId": "training-open-network",
+ "code": "LOCKFILE_HASH",
+ "passed": false,
+ "detail": "missing"
+ },
+ {
+ "pipelineId": "training-open-network",
+ "code": "ARTIFACT_HASH_results/models/cell-response.pt",
+ "passed": false,
+ "detail": "pending"
+ }
+ ],
+ "issues": [
+ {
+ "severity": "error",
+ "pipelineId": "repository",
+ "code": "EXPORT_BUNDLE_HASH_MISSING",
+ "message": "The export bundle is missing a deterministic hash.",
+ "requirement": "Programmatic access and export: release bundle guard",
+ "remediation": "Build the release bundle and attach its sha256 hash before DOI publication."
+ },
+ {
+ "severity": "error",
+ "pipelineId": "training-open-network",
+ "code": "COMPUTE_BUDGET_EXCEEDED",
+ "message": "The pipeline exceeds the approved CPU, memory, or runtime budget.",
+ "requirement": "Computation-aware reproducibility: controlled execution",
+ "remediation": "Keep CPU <= 16, memory <= 64 GB, and runtime <= 240 minutes."
+ },
+ {
+ "severity": "error",
+ "pipelineId": "training-open-network",
+ "code": "DETERMINISTIC_SEED_MISSING",
+ "message": "The pipeline does not declare a deterministic seed.",
+ "requirement": "Computation-aware reproducibility: controlled execution",
+ "remediation": "Declare the seed used for replay so results can be regenerated byte-for-byte."
+ },
+ {
+ "severity": "error",
+ "pipelineId": "training-open-network",
+ "code": "EXPECTED_ARTIFACT_HASH_MISSING",
+ "message": "Expected artifact results/models/cell-response.pt is missing a sha256 checkpoint.",
+ "requirement": "File and metadata versioning: hash-based integrity",
+ "remediation": "Run the pipeline once in the sandbox and record the artifact hash before export."
+ },
+ {
+ "severity": "error",
+ "pipelineId": "training-open-network",
+ "code": "GPU_REVIEW_REQUIRED",
+ "message": "GPU execution requires reviewer signoff because hardware kernels can affect determinism.",
+ "requirement": "Computation-aware reproducibility: controlled execution",
+ "remediation": "Attach deterministic kernel notes and reviewer signoff before final publication."
+ },
+ {
+ "severity": "error",
+ "pipelineId": "training-open-network",
+ "code": "INPUT_MANIFEST_HASH_MISSING",
+ "message": "INPUT_MANIFEST_HASH is missing a sha256 checkpoint.",
+ "requirement": "File and metadata versioning: hash-based integrity",
+ "remediation": "Generate input_manifest_hash before accepting this release candidate."
+ },
+ {
+ "severity": "error",
+ "pipelineId": "training-open-network",
+ "code": "INPUTS_NOT_READ_ONLY",
+ "message": "Repository inputs must be mounted read-only during replay.",
+ "requirement": "Execution sandboxes: limited mutable state",
+ "remediation": "Mount repository inputs read-only and write outputs only to the controlled scratch path."
+ },
+ {
+ "severity": "error",
+ "pipelineId": "training-open-network",
+ "code": "LOCKFILE_HASH_MISSING",
+ "message": "LOCKFILE_HASH is missing a sha256 checkpoint.",
+ "requirement": "File and metadata versioning: hash-based integrity",
+ "remediation": "Generate lockfile_hash before accepting this release candidate."
+ },
+ {
+ "severity": "error",
+ "pipelineId": "training-open-network",
+ "code": "NETWORK_EGRESS_TOO_BROAD",
+ "message": "Reproducibility execution cannot use open or unreviewed network egress.",
+ "requirement": "Execution sandboxes: secure runtime validation",
+ "remediation": "Set egress to none, or restrict DOI lookup to the approved resolver allowlist."
+ },
+ {
+ "severity": "error",
+ "pipelineId": "training-open-network",
+ "code": "SANDBOX_IMAGE_NOT_PINNED",
+ "message": "Sandbox image must be digest-pinned and must not use latest tags.",
+ "requirement": "Computation-aware reproducibility: container support",
+ "remediation": "Pin the execution image with an immutable sha256 digest."
+ },
+ {
+ "severity": "error",
+ "pipelineId": "training-open-network",
+ "code": "WRITABLE_MOUNT_NOT_CONSTRAINED",
+ "message": "Writable mount is outside the approved scratch prefix or exceeds size limits.",
+ "requirement": "Execution sandboxes: limited mutable state",
+ "remediation": "Use /tmp/scibase-run/ with max 2 GB."
+ }
+ ],
+ "releaseActions": {
+ "allowProtectedMerge": false,
+ "allowExportBundle": false,
+ "allowDoiPublication": false,
+ "requiresReviewerSignoff": false,
+ "remediationChecklist": [
+ {
+ "code": "EXPORT_BUNDLE_HASH_MISSING",
+ "pipelineId": "repository",
+ "action": "Build the release bundle and attach its sha256 hash before DOI publication."
+ },
+ {
+ "code": "COMPUTE_BUDGET_EXCEEDED",
+ "pipelineId": "training-open-network",
+ "action": "Keep CPU <= 16, memory <= 64 GB, and runtime <= 240 minutes."
+ },
+ {
+ "code": "DETERMINISTIC_SEED_MISSING",
+ "pipelineId": "training-open-network",
+ "action": "Declare the seed used for replay so results can be regenerated byte-for-byte."
+ },
+ {
+ "code": "EXPECTED_ARTIFACT_HASH_MISSING",
+ "pipelineId": "training-open-network",
+ "action": "Run the pipeline once in the sandbox and record the artifact hash before export."
+ },
+ {
+ "code": "GPU_REVIEW_REQUIRED",
+ "pipelineId": "training-open-network",
+ "action": "Attach deterministic kernel notes and reviewer signoff before final publication."
+ },
+ {
+ "code": "INPUT_MANIFEST_HASH_MISSING",
+ "pipelineId": "training-open-network",
+ "action": "Generate input_manifest_hash before accepting this release candidate."
+ },
+ {
+ "code": "INPUTS_NOT_READ_ONLY",
+ "pipelineId": "training-open-network",
+ "action": "Mount repository inputs read-only and write outputs only to the controlled scratch path."
+ },
+ {
+ "code": "LOCKFILE_HASH_MISSING",
+ "pipelineId": "training-open-network",
+ "action": "Generate lockfile_hash before accepting this release candidate."
+ },
+ {
+ "code": "NETWORK_EGRESS_TOO_BROAD",
+ "pipelineId": "training-open-network",
+ "action": "Set egress to none, or restrict DOI lookup to the approved resolver allowlist."
+ },
+ {
+ "code": "SANDBOX_IMAGE_NOT_PINNED",
+ "pipelineId": "training-open-network",
+ "action": "Pin the execution image with an immutable sha256 digest."
+ },
+ {
+ "code": "WRITABLE_MOUNT_NOT_CONSTRAINED",
+ "pipelineId": "training-open-network",
+ "action": "Use /tmp/scibase-run/ with max 2 GB."
+ }
+ ],
+ "status": "blocked"
+ },
+ "requirementCoverage": {
+ "pinned_image": "Computation-aware reproducibility: container support",
+ "network_policy": "Execution sandboxes: secure runtime validation",
+ "compute_budget": "Computation-aware reproducibility: controlled execution",
+ "writable_mounts": "Execution sandboxes: limited mutable state",
+ "artifact_hashes": "File and metadata versioning: hash-based integrity",
+ "export_gate": "Programmatic access and export: release bundle guard"
+ }
+ }
+ ]
+}
diff --git a/repository-compute-sandbox-policy-guard/reports/demo.md b/repository-compute-sandbox-policy-guard/reports/demo.md
new file mode 100644
index 00000000..085d95f1
--- /dev/null
+++ b/repository-compute-sandbox-policy-guard/reports/demo.md
@@ -0,0 +1,43 @@
+# Repository Compute Sandbox Policy Guard
+
+Synthetic reviewer packet for SCIBASE-AI/SCIBASE.AI#10.
+
+## Summary
+
+| Candidate | Tag | Status | Blocking issues | Warnings |
+| --- | --- | --- | ---: | ---: |
+| climate-lab/regional-ensemble | preprint-v1.4 | needs_review | 0 | 1 |
+| neuro-lab/sleep-spindle-atlas | preprint-v2.1 | ready | 0 | 0 |
+| oncology-lab/cell-response-map | release-unsafe-candidate | blocked | 11 | 0 |
+
+## Gate Totals
+
+- Ready candidates: 1
+- Needs review: 1
+- Blocked candidates: 1
+- Blocking issues: 11
+- Warnings: 1
+
+## Deterministic Remediation Queue
+
+- climate-lab/regional-ensemble gpu-ensemble-replay GPU_REVIEW_REQUIRED: Attach deterministic kernel notes and reviewer signoff before final publication.
+- oncology-lab/cell-response-map repository EXPORT_BUNDLE_HASH_MISSING: Build the release bundle and attach its sha256 hash before DOI publication.
+- oncology-lab/cell-response-map training-open-network COMPUTE_BUDGET_EXCEEDED: Keep CPU <= 16, memory <= 64 GB, and runtime <= 240 minutes.
+- oncology-lab/cell-response-map training-open-network DETERMINISTIC_SEED_MISSING: Declare the seed used for replay so results can be regenerated byte-for-byte.
+- oncology-lab/cell-response-map training-open-network EXPECTED_ARTIFACT_HASH_MISSING: Run the pipeline once in the sandbox and record the artifact hash before export.
+- oncology-lab/cell-response-map training-open-network GPU_REVIEW_REQUIRED: Attach deterministic kernel notes and reviewer signoff before final publication.
+- oncology-lab/cell-response-map training-open-network INPUT_MANIFEST_HASH_MISSING: Generate input_manifest_hash before accepting this release candidate.
+- oncology-lab/cell-response-map training-open-network INPUTS_NOT_READ_ONLY: Mount repository inputs read-only and write outputs only to the controlled scratch path.
+- oncology-lab/cell-response-map training-open-network LOCKFILE_HASH_MISSING: Generate lockfile_hash before accepting this release candidate.
+- oncology-lab/cell-response-map training-open-network NETWORK_EGRESS_TOO_BROAD: Set egress to none, or restrict DOI lookup to the approved resolver allowlist.
+- oncology-lab/cell-response-map training-open-network SANDBOX_IMAGE_NOT_PINNED: Pin the execution image with an immutable sha256 digest.
+- oncology-lab/cell-response-map training-open-network WRITABLE_MOUNT_NOT_CONSTRAINED: Use /tmp/scibase-run/ with max 2 GB.
+
+## Requirement Mapping
+
+- Pinned sandbox images: Computation-aware reproducibility: container support
+- Network egress policy: Execution sandboxes: secure runtime validation
+- CPU, memory, runtime, and GPU budgets: Computation-aware reproducibility: controlled execution
+- Limited writable mounts: Execution sandboxes: limited mutable state
+- Artifact hash checkpoints: File and metadata versioning: hash-based integrity
+- DOI and export blocking actions: Programmatic access and export: release bundle guard
diff --git a/repository-compute-sandbox-policy-guard/reports/demo.mp4 b/repository-compute-sandbox-policy-guard/reports/demo.mp4
new file mode 100644
index 00000000..e89249b2
Binary files /dev/null and b/repository-compute-sandbox-policy-guard/reports/demo.mp4 differ
diff --git a/repository-compute-sandbox-policy-guard/reports/demo.svg b/repository-compute-sandbox-policy-guard/reports/demo.svg
new file mode 100644
index 00000000..db154a9a
--- /dev/null
+++ b/repository-compute-sandbox-policy-guard/reports/demo.svg
@@ -0,0 +1,43 @@
+
diff --git a/repository-compute-sandbox-policy-guard/sample-data.js b/repository-compute-sandbox-policy-guard/sample-data.js
new file mode 100644
index 00000000..4619dcab
--- /dev/null
+++ b/repository-compute-sandbox-policy-guard/sample-data.js
@@ -0,0 +1,151 @@
+const HASH_A =
+ "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
+const HASH_B =
+ "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";
+const HASH_C =
+ "sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc";
+const HASH_D =
+ "sha256:dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd";
+const HASH_E =
+ "sha256:eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee";
+const HASH_F =
+ "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff";
+
+const sampleCandidates = [
+ {
+ repositoryId: "neuro-lab/sleep-spindle-atlas",
+ tag: "preprint-v2.1",
+ doiDraft: "10.5555/scibase.sleep-spindle-atlas.v2",
+ componentManifestHash: HASH_A,
+ exportBundleHash: HASH_B,
+ reviewers: ["orcid:0000-0002-1825-0097", "orcid:0000-0003-1555-4212"],
+ pipelines: [
+ {
+ id: "analysis-primary",
+ component: "code/run_analysis.ipynb",
+ inputManifestHash: HASH_C,
+ lockfileHash: HASH_D,
+ sandbox: {
+ image:
+ "ghcr.io/scibase/repro-python@sha256:1111111111111111111111111111111111111111111111111111111111111111",
+ networkEgress: "none",
+ egressAllowlist: [],
+ readOnlyInputs: true,
+ writableMounts: [
+ {
+ path: "/tmp/scibase-run/analysis-primary",
+ maxGb: 1.5,
+ },
+ ],
+ },
+ compute: {
+ cpuCores: 8,
+ memoryGb: 32,
+ runtimeMinutes: 115,
+ gpuCount: 0,
+ deterministicSeed: "sleep-spindle-atlas-v2",
+ },
+ expectedArtifacts: [
+ {
+ name: "results/tables/spindle-summary.csv",
+ hash: HASH_E,
+ },
+ {
+ name: "results/figures/cohort-comparison.svg",
+ hash: HASH_F,
+ },
+ ],
+ },
+ ],
+ },
+ {
+ repositoryId: "oncology-lab/cell-response-map",
+ tag: "release-unsafe-candidate",
+ doiDraft: "10.5555/scibase.cell-response-map.unsafe",
+ componentManifestHash: HASH_B,
+ exportBundleHash: "",
+ reviewers: ["orcid:0000-0001-0000-0000"],
+ pipelines: [
+ {
+ id: "training-open-network",
+ component: "notebooks/train-response-model.ipynb",
+ inputManifestHash: "",
+ lockfileHash: "",
+ sandbox: {
+ image: "python:latest",
+ networkEgress: "open-internet",
+ egressAllowlist: ["pypi.org", "example-data.invalid"],
+ readOnlyInputs: false,
+ writableMounts: [
+ {
+ path: "/workspace",
+ maxGb: 12,
+ },
+ ],
+ },
+ compute: {
+ cpuCores: 24,
+ memoryGb: 96,
+ runtimeMinutes: 360,
+ gpuCount: 1,
+ deterministicSeed: "",
+ },
+ expectedArtifacts: [
+ {
+ name: "results/models/cell-response.pt",
+ hash: "pending",
+ },
+ ],
+ },
+ ],
+ },
+ {
+ repositoryId: "climate-lab/regional-ensemble",
+ tag: "preprint-v1.4",
+ doiDraft: "10.5555/scibase.regional-ensemble.v1",
+ componentManifestHash: HASH_C,
+ exportBundleHash: HASH_D,
+ reviewers: ["orcid:0000-0002-4444-8888"],
+ pipelines: [
+ {
+ id: "gpu-ensemble-replay",
+ component: "code/replay_ensemble.py",
+ inputManifestHash: HASH_E,
+ lockfileHash: HASH_F,
+ sandbox: {
+ image:
+ "ghcr.io/scibase/repro-cuda@sha256:2222222222222222222222222222222222222222222222222222222222222222",
+ networkEgress: "doi-resolution-only",
+ egressAllowlist: ["doi.org", "api.datacite.org"],
+ egressJustification: "Resolve DOI metadata during citation export only.",
+ readOnlyInputs: true,
+ writableMounts: [
+ {
+ path: "/tmp/scibase-run/gpu-ensemble-replay",
+ maxGb: 2,
+ },
+ ],
+ },
+ compute: {
+ cpuCores: 12,
+ memoryGb: 48,
+ runtimeMinutes: 210,
+ gpuCount: 1,
+ gpuJustification:
+ "Replay published CUDA ensemble with deterministic kernels and fixed seeds.",
+ deterministicSeed: "regional-ensemble-v1.4",
+ },
+ expectedArtifacts: [
+ {
+ name: "results/ensemble/replay-summary.json",
+ hash: HASH_A,
+ },
+ ],
+ },
+ ],
+ },
+];
+
+module.exports = {
+ sampleCandidates,
+};
diff --git a/repository-compute-sandbox-policy-guard/scripts/render-demo-video.js b/repository-compute-sandbox-policy-guard/scripts/render-demo-video.js
new file mode 100644
index 00000000..d9c8265f
--- /dev/null
+++ b/repository-compute-sandbox-policy-guard/scripts/render-demo-video.js
@@ -0,0 +1,85 @@
+const fs = require("node:fs");
+const path = require("node:path");
+const { spawnSync } = require("node:child_process");
+
+const reportsDir = path.join(__dirname, "..", "reports");
+const reportPath = path.join(reportsDir, "demo.json");
+const outputPath = path.join(reportsDir, "demo.mp4");
+
+if (!fs.existsSync(reportPath)) {
+ throw new Error("Run npm run demo before rendering the video.");
+}
+
+const report = JSON.parse(fs.readFileSync(reportPath, "utf8"));
+const ffmpeg = process.env.FFMPEG_PATH || "ffmpeg";
+const title = "SCIBASE compute sandbox policy guard";
+const subtitle = `ready ${report.totals.ready} review ${report.totals.needsReview} blocked ${report.totals.blocked}`;
+const detail = `blocking issues ${report.totals.blockingIssues} warnings ${report.totals.warnings}`;
+const fontArg = getFontArg();
+const filters = [
+ "drawbox=x=0:y=0:w=iw:h=ih:color=0x0b1020@1:t=fill",
+ "drawbox=x=72:y=86:w=1136:h=548:color=0xffffff@0.94:t=fill",
+ "drawbox=x=72:y=86:w=1136:h=18:color=0x157f57@1:t=fill",
+ drawText(title, 112, 160, 42, "0x111827", fontArg),
+ drawText("Project Repository and Version Control", 112, 220, 24, "0x374151", fontArg),
+ drawText(subtitle, 112, 305, 34, "0x111827", fontArg),
+ drawText(detail, 112, 365, 26, "0xb42318", fontArg),
+ drawText(
+ "Pins container images, constrains network egress, budgets compute, and blocks unsafe export.",
+ 112,
+ 450,
+ 22,
+ "0x374151",
+ fontArg,
+ ),
+ drawText("Synthetic data only. No external services or private repositories.", 112, 500, 22, "0x374151", fontArg),
+].join(",");
+
+const result = spawnSync(
+ ffmpeg,
+ [
+ "-y",
+ "-f",
+ "lavfi",
+ "-i",
+ "color=c=0x0b1020:s=1280x720:d=4:r=25",
+ "-vf",
+ filters,
+ "-pix_fmt",
+ "yuv420p",
+ outputPath,
+ ],
+ { stdio: "inherit" },
+);
+
+if (result.status !== 0) {
+ throw new Error(`ffmpeg exited with status ${result.status}`);
+}
+
+console.log(`Wrote ${path.relative(process.cwd(), outputPath)}`);
+
+function drawText(text, x, y, size, color, fontArg) {
+ return `drawtext=${fontArg}:text='${escapeDrawtext(text)}':x=${x}:y=${y}:fontsize=${size}:fontcolor=${color}`;
+}
+
+function escapeDrawtext(value) {
+ return String(value)
+ .replaceAll("\\", "\\\\")
+ .replaceAll(":", "\\:")
+ .replaceAll("'", "\\'");
+}
+
+function getFontArg() {
+ const candidates = [
+ "C:/Windows/Fonts/arial.ttf",
+ "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
+ "/System/Library/Fonts/Supplemental/Arial.ttf",
+ ];
+ const font = candidates.find((candidate) => fs.existsSync(candidate));
+ if (!font) {
+ throw new Error(
+ `No supported TrueType font file found. Checked: ${candidates.join(", ")}`,
+ );
+ }
+ return `fontfile='${font.replaceAll(":", "\\:")}'`;
+}
diff --git a/repository-compute-sandbox-policy-guard/test.js b/repository-compute-sandbox-policy-guard/test.js
new file mode 100644
index 00000000..0080ca78
--- /dev/null
+++ b/repository-compute-sandbox-policy-guard/test.js
@@ -0,0 +1,101 @@
+const assert = require("node:assert/strict");
+const {
+ createPolicyReport,
+ evaluateRepositoryComputePolicy,
+ renderMarkdown,
+ renderSvg,
+} = require("./index");
+const { sampleCandidates } = require("./sample-data");
+
+const ready = evaluateRepositoryComputePolicy(sampleCandidates[0]);
+assert.equal(ready.status, "ready");
+assert.equal(ready.releaseActions.allowProtectedMerge, true);
+assert.equal(ready.releaseActions.allowDoiPublication, true);
+assert.equal(ready.summary.blockingIssues, 0);
+
+const blocked = evaluateRepositoryComputePolicy(sampleCandidates[1]);
+assert.equal(blocked.status, "blocked");
+assert.equal(blocked.releaseActions.allowExportBundle, false);
+assert.ok(
+ blocked.issues.some((issue) => issue.code === "SANDBOX_IMAGE_NOT_PINNED"),
+);
+assert.ok(
+ blocked.issues.some((issue) => issue.code === "NETWORK_EGRESS_TOO_BROAD"),
+);
+assert.ok(
+ blocked.issues.some((issue) => issue.code === "COMPUTE_BUDGET_EXCEEDED"),
+);
+assert.ok(
+ blocked.issues.some(
+ (issue) => issue.code === "WRITABLE_MOUNT_NOT_CONSTRAINED",
+ ),
+);
+assert.ok(
+ blocked.issues.some(
+ (issue) => issue.code === "EXPECTED_ARTIFACT_HASH_MISSING",
+ ),
+);
+assert.ok(
+ blocked.checks
+ .filter((check) => check.code === "WRITABLE_MOUNT")
+ .every((check) => check.detail.includes("path=")),
+);
+assert.ok(
+ !blocked.checks.some((check) => check.code.includes("/workspace")),
+);
+
+const missingDoiAllowlist = evaluateRepositoryComputePolicy({
+ ...sampleCandidates[0],
+ pipelines: [
+ {
+ ...sampleCandidates[0].pipelines[0],
+ sandbox: {
+ ...sampleCandidates[0].pipelines[0].sandbox,
+ networkEgress: "doi-resolution-only",
+ egressAllowlist: [],
+ },
+ },
+ ],
+});
+assert.equal(missingDoiAllowlist.status, "blocked");
+assert.ok(
+ missingDoiAllowlist.issues.some(
+ (issue) => issue.code === "NETWORK_EGRESS_TOO_BROAD",
+ ),
+);
+
+const needsReview = evaluateRepositoryComputePolicy(sampleCandidates[2]);
+assert.equal(needsReview.status, "needs_review");
+assert.equal(needsReview.releaseActions.allowProtectedMerge, true);
+assert.equal(needsReview.releaseActions.allowDoiPublication, false);
+assert.equal(needsReview.releaseActions.requiresReviewerSignoff, true);
+assert.ok(
+ needsReview.issues.some((issue) => issue.code === "GPU_REVIEW_REQUIRED"),
+);
+
+const report = createPolicyReport(sampleCandidates);
+assert.deepEqual(report.totals, {
+ candidates: 3,
+ ready: 1,
+ needsReview: 1,
+ blocked: 1,
+ blockingIssues: 11,
+ warnings: 1,
+});
+
+const markdown = renderMarkdown(report);
+assert.match(markdown, /Repository Compute Sandbox Policy Guard/);
+assert.match(markdown, /NETWORK_EGRESS_TOO_BROAD/);
+assert.match(markdown, /DOI and export blocking actions/);
+
+const svg = renderSvg(report);
+assert.match(svg, /