Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions data-code-hosting-manifest-guard/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"name": "data-code-hosting-manifest-guard",
"version": "1.0.0",
"private": true,
"type": "module",
"scripts": {
"check": "node --check src/index.js && node --check scripts/demo.js && node --check test/index.test.js",
"test": "node --test test/index.test.js",
"demo": "node scripts/demo.js"
}
}
26 changes: 26 additions & 0 deletions data-code-hosting-manifest-guard/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Data and Code Hosting Manifest Guard

This module is a focused implementation slice for SCIBASE issue #14, Scientific/Engineering Data & Code Hosting.

It evaluates a scientific project repository manifest before a data/code release is published. The guard checks:

- FAIR-style metadata presence, including DOI, license, authors, keywords, and schema version.
- Dataset and executable artifact registration.
- SHA-256 artifact hash locks.
- Dataset metadata schemas and code runtime declarations.
- Visibility, embargo release dates, and access policy shape.
- Executable environment definitions.
- Reproducibility commands and output digests.
- Versioning strategy, release tag, and diffable review paths.

The implementation is dependency-free and uses synthetic manifests only. It does not call external services, read private projects, mutate repositories, issue DOIs, upload files, or contact storage providers.

## Commands

```bash
npm run check
npm test
npm run demo
```

`npm run demo` writes a JSON packet, Markdown report, and SVG summary under `reports/`.
123 changes: 123 additions & 0 deletions data-code-hosting-manifest-guard/reports/manifest-guard-packet.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
{
"generatedAt": "2026-06-04T00:00:00.000Z",
"readyResult": {
"status": "ready_for_repository_release",
"summary": {
"artifactCount": 2,
"datasetCount": 1,
"codeCount": 1,
"blockerCount": 0,
"warningCount": 0
},
"blockers": [],
"warnings": [],
"auditDigest": "b00f635cbe73f436a63d6338a7b6fe87dad6a923c75943df5d0e2b33c704e44a"
},
"blockedResult": {
"status": "block_publication",
"summary": {
"artifactCount": 1,
"datasetCount": 1,
"codeCount": 0,
"blockerCount": 10,
"warningCount": 5
},
"blockers": [
{
"code": "missing_metadata",
"message": "Required metadata field 'authors' is missing.",
"evidence": "authors"
},
{
"code": "missing_metadata",
"message": "Required metadata field 'license' is missing.",
"evidence": "license"
},
{
"code": "missing_metadata",
"message": "Required metadata field 'doi' is missing.",
"evidence": "doi"
},
{
"code": "missing_metadata",
"message": "Required metadata field 'keywords' is missing.",
"evidence": "keywords"
},
{
"code": "missing_metadata",
"message": "Required metadata field 'schemaVersion' is missing.",
"evidence": "schemaVersion"
},
{
"code": "invalid_artifact_hash",
"message": "Artifact 'unhashed-export' does not contain a valid SHA-256 hash lock.",
"evidence": "missing"
},
{
"code": "embargo_without_release_date",
"message": "Embargoed repositories need an explicit release date.",
"evidence": {
"visibility": "embargoed"
}
},
{
"code": "missing_execution_environment",
"message": "Executable repository hosting needs a Dockerfile, environment.yml, notebook kernel, or equivalent runtime definition.",
"evidence": {
"kind": "docker",
"definition": ""
}
},
{
"code": "missing_reproducibility_commands",
"message": "Repository needs at least one command that reproduces or verifies the hosted outputs.",
"evidence": {
"commands": []
}
},
{
"code": "missing_versioning_policy",
"message": "Repository needs explicit versioning strategy and current tag metadata.",
"evidence": {
"strategy": "",
"currentTag": ""
}
}
],
"warnings": [
{
"code": "missing_code_artifact",
"message": "No executable code, notebook, package, or model artifact is registered.",
"evidence": [
"dataset"
]
},
{
"code": "dataset_schema_missing",
"message": "Dataset 'unhashed-export' does not declare a metadata schema.",
"evidence": "unhashed-export"
},
{
"code": "docker_definition_unclear",
"message": "Docker runtime is declared without a Dockerfile-style definition pointer.",
"evidence": ""
},
{
"code": "missing_output_digest",
"message": "Reproducibility command has no expected output digest.",
"evidence": {
"commands": []
}
},
{
"code": "missing_diffable_paths",
"message": "No diffable paths are declared for dataset/code review.",
"evidence": {
"strategy": "",
"currentTag": ""
}
}
],
"auditDigest": "777f3022d220fed8225ca1f9586d09274e35b149e2cd764a753769c72c8dd951"
}
}
33 changes: 33 additions & 0 deletions data-code-hosting-manifest-guard/reports/manifest-guard-report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Data and Code Hosting Manifest Guard

Status: block_publication
Audit digest: 777f3022d220fed8225ca1f9586d09274e35b149e2cd764a753769c72c8dd951

## Summary

- Artifacts: 1
- Dataset artifacts: 1
- Code artifacts: 0
- Blockers: 10
- Warnings: 5

## Blockers

- missing_metadata: Required metadata field 'authors' is missing.
- missing_metadata: Required metadata field 'license' is missing.
- missing_metadata: Required metadata field 'doi' is missing.
- missing_metadata: Required metadata field 'keywords' is missing.
- missing_metadata: Required metadata field 'schemaVersion' is missing.
- invalid_artifact_hash: Artifact 'unhashed-export' does not contain a valid SHA-256 hash lock.
- embargo_without_release_date: Embargoed repositories need an explicit release date.
- missing_execution_environment: Executable repository hosting needs a Dockerfile, environment.yml, notebook kernel, or equivalent runtime definition.
- missing_reproducibility_commands: Repository needs at least one command that reproduces or verifies the hosted outputs.
- missing_versioning_policy: Repository needs explicit versioning strategy and current tag metadata.

## Warnings

- missing_code_artifact: No executable code, notebook, package, or model artifact is registered.
- dataset_schema_missing: Dataset 'unhashed-export' does not declare a metadata schema.
- docker_definition_unclear: Docker runtime is declared without a Dockerfile-style definition pointer.
- missing_output_digest: Reproducibility command has no expected output digest.
- missing_diffable_paths: No diffable paths are declared for dataset/code review.
11 changes: 11 additions & 0 deletions data-code-hosting-manifest-guard/reports/summary.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
92 changes: 92 additions & 0 deletions data-code-hosting-manifest-guard/scripts/demo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { mkdir, writeFile } from "node:fs/promises";
import { createSampleManifest, evaluateRepositoryManifest } from "../src/index.js";

const reportDir = new URL("../reports/", import.meta.url);

function escapeXml(value) {
return String(value)
.replaceAll("&", "&")
.replaceAll("<", "&lt;")
.replaceAll(">", "&gt;")
.replaceAll('"', "&quot;");
}

function renderMarkdown(result) {
const lines = [
"# Data and Code Hosting Manifest Guard",
"",
`Status: ${result.status}`,
`Audit digest: ${result.auditDigest}`,
"",
"## Summary",
"",
`- Artifacts: ${result.summary.artifactCount}`,
`- Dataset artifacts: ${result.summary.datasetCount}`,
`- Code artifacts: ${result.summary.codeCount}`,
`- Blockers: ${result.summary.blockerCount}`,
`- Warnings: ${result.summary.warningCount}`,
"",
"## Blockers",
""
];

lines.push(...(result.blockers.length ? result.blockers.map((finding) => `- ${finding.code}: ${finding.message}`) : ["- None"]));
lines.push("", "## Warnings", "");
lines.push(...(result.warnings.length ? result.warnings.map((finding) => `- ${finding.code}: ${finding.message}`) : ["- None"]));
lines.push("");
return lines.join("\n");
}

function renderSvg(result) {
const statusColor = result.status === "ready_for_repository_release" ? "#15803d" : result.status === "needs_metadata_review" ? "#a16207" : "#b91c1c";
return `<svg xmlns="http://www.w3.org/2000/svg" width="760" height="300" viewBox="0 0 760 300" role="img" aria-label="Data code hosting manifest guard summary">
<rect width="760" height="300" fill="#f8fafc"/>
<rect x="28" y="28" width="704" height="244" rx="12" fill="#ffffff" stroke="#cbd5e1"/>
<text x="56" y="72" font-family="Arial, sans-serif" font-size="24" font-weight="700" fill="#0f172a">Data and Code Hosting Manifest Guard</text>
<text x="56" y="110" font-family="Arial, sans-serif" font-size="16" fill="#334155">Status</text>
<rect x="56" y="124" width="320" height="42" rx="6" fill="${statusColor}"/>
<text x="72" y="151" font-family="Arial, sans-serif" font-size="17" font-weight="700" fill="#ffffff">${escapeXml(result.status)}</text>
<text x="56" y="202" font-family="Arial, sans-serif" font-size="16" fill="#334155">Artifacts ${result.summary.artifactCount} | Datasets ${result.summary.datasetCount} | Code ${result.summary.codeCount}</text>
<text x="56" y="232" font-family="Arial, sans-serif" font-size="16" fill="#334155">Blockers ${result.summary.blockerCount} | Warnings ${result.summary.warningCount}</text>
<text x="56" y="258" font-family="Arial, sans-serif" font-size="12" fill="#64748b">Audit ${escapeXml(result.auditDigest.slice(0, 24))}...</text>
</svg>
`;
}

await mkdir(reportDir, { recursive: true });

const readyResult = evaluateRepositoryManifest(createSampleManifest());
const blockedResult = evaluateRepositoryManifest({
metadata: { title: "Private lab dump" },
artifacts: [
{
id: "unhashed-export",
type: "dataset",
format: "xlsx",
sha256: "missing",
version: "draft",
license: "custom-lab-license"
}
],
access: { visibility: "embargoed" },
environment: { kind: "docker", definition: "" },
reproducibility: { commands: [] },
versioning: { strategy: "", currentTag: "" }
});

const packet = {
generatedAt: new Date("2026-06-04T00:00:00.000Z").toISOString(),
readyResult,
blockedResult
};

await writeFile(new URL("manifest-guard-packet.json", reportDir), `${JSON.stringify(packet, null, 2)}\n`);
await writeFile(new URL("manifest-guard-report.md", reportDir), renderMarkdown(blockedResult));
await writeFile(new URL("summary.svg", reportDir), renderSvg(blockedResult));

console.log(JSON.stringify({
status: blockedResult.status,
blockers: blockedResult.summary.blockerCount,
warnings: blockedResult.summary.warningCount,
auditDigest: blockedResult.auditDigest
}, null, 2));
Loading