diff --git a/repository-license-compatibility-guard/README.md b/repository-license-compatibility-guard/README.md
new file mode 100644
index 00000000..9b3bc5e1
--- /dev/null
+++ b/repository-license-compatibility-guard/README.md
@@ -0,0 +1,33 @@
+# Repository License Compatibility Guard
+
+This module provides a focused Project Repository & Version Control slice for
+SCIBASE issue #10. It evaluates a synthetic repository release/export manifest
+before DOI publication or partner export, checking code package licenses, dataset
+licenses, model weight terms, generated figure reuse, fork attribution, SPDX
+normalization, missing notices, and release-blocking conflicts.
+
+## What It Covers
+
+- License compatibility for tagged repository releases.
+- Dataset and model export holds for proprietary, missing, or unsupported terms.
+- Non-commercial license conflicts for commercial/partner export intents.
+- Copyleft source-bundle and fork-license evidence checks.
+- Attribution and notice completion tasks before publication.
+- Deterministic reviewer packets and audit digests.
+
+## Run
+
+```bash
+npm run check
+npm test
+npm run demo
+```
+
+Generated artifacts are written to `reports/`:
+
+- `summary.json`
+- `reviewer-packet.md`
+- `summary.svg`
+- `demo.mp4`
+
+The data is synthetic and does not scan a live repository or legal system.
diff --git a/repository-license-compatibility-guard/acceptance-notes.md b/repository-license-compatibility-guard/acceptance-notes.md
new file mode 100644
index 00000000..4e2367e2
--- /dev/null
+++ b/repository-license-compatibility-guard/acceptance-notes.md
@@ -0,0 +1,19 @@
+# Acceptance Notes
+
+The guard classifies release assets as:
+
+- `publishable`: compatible with the requested release/export intent.
+- `notice_required`: export can proceed after attribution or notice completion.
+- `conflict`: license terms conflict with the export intent.
+- `hold`: release-blocking missing, proprietary, unsupported, or incomplete evidence.
+
+Validation commands:
+
+```bash
+npm run check
+npm test
+npm run demo
+ffprobe -v error -show_entries format=duration,size -show_entries stream=codec_name,width,height,pix_fmt -of default=noprint_wrappers=1 reports/demo.mp4
+git diff --check
+git diff --cached --check
+```
diff --git a/repository-license-compatibility-guard/demo.js b/repository-license-compatibility-guard/demo.js
new file mode 100644
index 00000000..5586cda4
--- /dev/null
+++ b/repository-license-compatibility-guard/demo.js
@@ -0,0 +1,31 @@
+const fs = require("node:fs");
+const path = require("node:path");
+const {
+ analyzeRepositoryLicenses,
+ renderMarkdownReport,
+ renderSvgSummary,
+} = require("./index");
+const { sampleRepositoryLicensePacket } = require("./sample-data");
+
+const reportsDir = path.join(__dirname, "reports");
+fs.mkdirSync(reportsDir, { recursive: true });
+
+const result = analyzeRepositoryLicenses(sampleRepositoryLicensePacket, {
+ asOf: "2026-05-22T12:00:00.000Z",
+});
+
+fs.writeFileSync(
+ path.join(reportsDir, "summary.json"),
+ `${JSON.stringify(result, null, 2)}\n`
+);
+fs.writeFileSync(
+ path.join(reportsDir, "reviewer-packet.md"),
+ renderMarkdownReport(result)
+);
+fs.writeFileSync(
+ path.join(reportsDir, "summary.svg"),
+ renderSvgSummary(result)
+);
+
+console.log("repository license compatibility guard demo artifacts written");
+console.log(`audit digest: ${result.auditDigest}`);
diff --git a/repository-license-compatibility-guard/index.js b/repository-license-compatibility-guard/index.js
new file mode 100644
index 00000000..4203d17c
--- /dev/null
+++ b/repository-license-compatibility-guard/index.js
@@ -0,0 +1,326 @@
+const crypto = require("node:crypto");
+
+const PUBLISHABLE = "publishable";
+const NOTICE_REQUIRED = "notice_required";
+const CONFLICT = "conflict";
+const HOLD = "hold";
+
+const PERMISSIVE_LICENSES = new Set(["MIT", "Apache-2.0", "BSD-2-Clause", "BSD-3-Clause"]);
+const OPEN_DATA_LICENSES = new Set(["CC-BY-4.0", "CC0-1.0", "ODC-BY-1.0"]);
+const COPYLEFT_LICENSES = new Set(["GPL-3.0", "AGPL-3.0", "LGPL-3.0"]);
+const NON_COMMERCIAL_LICENSES = new Set(["CC-BY-NC-4.0", "CC-BY-NC-SA-4.0"]);
+
+function requireFields(value, fields, label) {
+ for (const field of fields) {
+ if (value[field] === undefined || value[field] === null || value[field] === "") {
+ throw new Error(`missing required ${label} field: ${field}`);
+ }
+ }
+}
+
+function normalizeLicense(value) {
+ return String(value || "").trim();
+}
+
+function stableStringify(value) {
+ if (Array.isArray(value)) {
+ return `[${value.map(stableStringify).join(",")}]`;
+ }
+
+ if (value && typeof value === "object") {
+ return `{${Object.keys(value)
+ .sort()
+ .map((key) => `${JSON.stringify(key)}:${stableStringify(value[key])}`)
+ .join(",")}}`;
+ }
+
+ return JSON.stringify(value);
+}
+
+function createAuditDigest(result) {
+ const payload = {
+ asOf: result.asOf,
+ repositoryId: result.repositoryId,
+ versionTag: result.versionTag,
+ totals: result.totals,
+ decisions: result.decisions.map((decision) => ({
+ id: decision.id,
+ license: decision.license,
+ status: decision.status,
+ reasons: decision.reasons,
+ })),
+ };
+
+ return crypto.createHash("sha256").update(stableStringify(payload)).digest("hex");
+}
+
+function requiresNotice(license) {
+ return (
+ PERMISSIVE_LICENSES.has(license) ||
+ OPEN_DATA_LICENSES.has(license) ||
+ COPYLEFT_LICENSES.has(license) ||
+ NON_COMMERCIAL_LICENSES.has(license)
+ );
+}
+
+function isCommercialExport(intent) {
+ return /commercial|partner|enterprise/i.test(intent);
+}
+
+function evaluateAsset(asset, release) {
+ requireFields(asset, ["id", "componentType", "path", "exportIncluded"], "asset");
+
+ const license = normalizeLicense(asset.license);
+ const reasons = [];
+ const actions = [];
+ const base = {
+ id: asset.id,
+ componentType: asset.componentType,
+ path: asset.path,
+ license,
+ exportIncluded: Boolean(asset.exportIncluded),
+ status: PUBLISHABLE,
+ releaseBlocking: false,
+ reasons,
+ actions,
+ };
+
+ if (!asset.exportIncluded) {
+ return {
+ ...base,
+ status: PUBLISHABLE,
+ reasons: ["asset is not included in the release export"],
+ actions: ["No release action required"],
+ };
+ }
+
+ if (!license) {
+ return {
+ ...base,
+ status: HOLD,
+ releaseBlocking: true,
+ reasons: ["asset has no normalized SPDX or data license"],
+ actions: [
+ "Hold tagged release and export bundle",
+ "Attach license metadata or remove asset from export",
+ ],
+ };
+ }
+
+ if (/proprietary|restricted|unknown/i.test(license)) {
+ return {
+ ...base,
+ status: HOLD,
+ releaseBlocking: true,
+ reasons: ["asset license is proprietary, restricted, or unknown"],
+ actions: [
+ "Exclude from public DOI/export package",
+ "Request rights review and replacement artifact",
+ ],
+ };
+ }
+
+ if (NON_COMMERCIAL_LICENSES.has(license) && isCommercialExport(release.exportIntent)) {
+ return {
+ ...base,
+ status: CONFLICT,
+ releaseBlocking: true,
+ reasons: ["non-commercial license conflicts with commercial partner export intent"],
+ actions: [
+ "Block commercial export until asset is relicensed or omitted",
+ "Generate public-only export variant if permitted",
+ ],
+ };
+ }
+
+ if (COPYLEFT_LICENSES.has(license)) {
+ const sourceOkay = release.sourceBundleIncluded && asset.sourceAvailable;
+ const forkOkay = !asset.derivedFromFork || asset.forkLicense === license;
+ if (!sourceOkay || !forkOkay) {
+ return {
+ ...base,
+ status: HOLD,
+ releaseBlocking: true,
+ reasons: [
+ !sourceOkay
+ ? "copyleft asset requires source bundle availability"
+ : "fork attribution license does not match derived asset",
+ ],
+ actions: [
+ "Hold release until source and fork license evidence are complete",
+ "Attach copy of license and source bundle manifest",
+ ],
+ };
+ }
+ }
+
+ if (requiresNotice(license) && (!asset.noticeIncluded || !asset.attribution)) {
+ return {
+ ...base,
+ status: NOTICE_REQUIRED,
+ releaseBlocking: false,
+ reasons: ["license permits export but attribution or notice is incomplete"],
+ actions: [
+ "Add license notice to export manifest",
+ "Include attribution in citation and repository notice files",
+ ],
+ };
+ }
+
+ if (
+ !PERMISSIVE_LICENSES.has(license) &&
+ !OPEN_DATA_LICENSES.has(license) &&
+ !COPYLEFT_LICENSES.has(license) &&
+ !NON_COMMERCIAL_LICENSES.has(license)
+ ) {
+ return {
+ ...base,
+ status: HOLD,
+ releaseBlocking: true,
+ reasons: ["license is not in the supported compatibility matrix"],
+ actions: [
+ "Route to legal/repository steward review before release",
+ "Map license to compatibility policy",
+ ],
+ };
+ }
+
+ return {
+ ...base,
+ status: PUBLISHABLE,
+ releaseBlocking: false,
+ reasons: ["license is compatible with the requested release/export intent"],
+ actions: [
+ "Allow asset in DOI/export package",
+ "Preserve license metadata in manifest",
+ ],
+ };
+}
+
+function analyzeRepositoryLicenses(packet, options = {}) {
+ requireFields(packet, ["release", "assets"], "repository license packet");
+ requireFields(packet.release, ["repositoryId", "versionTag", "exportIntent"], "release");
+
+ if (!Array.isArray(packet.assets)) {
+ throw new Error("assets must be an array");
+ }
+
+ const asOf = options.asOf || packet.asOf || new Date().toISOString();
+ const decisions = packet.assets.map((asset) => evaluateAsset(asset, packet.release));
+ const totals = decisions.reduce(
+ (acc, decision) => {
+ acc.totalAssets += 1;
+ acc.releaseBlockingAssets += decision.releaseBlocking ? 1 : 0;
+ acc.byStatus[decision.status] = (acc.byStatus[decision.status] || 0) + 1;
+ return acc;
+ },
+ {
+ totalAssets: 0,
+ releaseBlockingAssets: 0,
+ byStatus: {},
+ }
+ );
+ const result = {
+ asOf,
+ repositoryId: packet.release.repositoryId,
+ versionTag: packet.release.versionTag,
+ exportIntent: packet.release.exportIntent,
+ totals,
+ decisions,
+ };
+
+ return {
+ ...result,
+ auditDigest: createAuditDigest(result),
+ };
+}
+
+function renderMarkdownReport(result) {
+ const lines = [
+ "# Repository License Compatibility Guard",
+ "",
+ `Repository: ${result.repositoryId}`,
+ `Version: ${result.versionTag}`,
+ `Export intent: ${result.exportIntent}`,
+ `Audit digest: \`${result.auditDigest}\``,
+ "",
+ "## Totals",
+ "",
+ `- Assets evaluated: ${result.totals.totalAssets}`,
+ `- Release-blocking assets: ${result.totals.releaseBlockingAssets}`,
+ ];
+
+ for (const [status, count] of Object.entries(result.totals.byStatus).sort()) {
+ lines.push(`- ${status}: ${count}`);
+ }
+
+ lines.push("", "## Asset Decisions", "");
+ for (const decision of result.decisions) {
+ lines.push(
+ `### ${decision.id}`,
+ "",
+ `- Path: ${decision.path}`,
+ `- Type: ${decision.componentType}`,
+ `- License: ${decision.license || "(missing)"}`,
+ `- Status: ${decision.status}`,
+ `- Release blocking: ${decision.releaseBlocking}`,
+ `- Reasons: ${decision.reasons.join("; ")}`,
+ `- Actions: ${decision.actions.join("; ")}`,
+ ""
+ );
+ }
+
+ return `${lines.join("\n").trimEnd()}\n`;
+}
+
+function escapeXml(value) {
+ return String(value)
+ .replace(/&/g, "&")
+ .replace(//g, ">")
+ .replace(/"/g, """);
+}
+
+function renderSvgSummary(result) {
+ const rows = Object.entries(result.totals.byStatus)
+ .sort()
+ .map(([status, count], index) => {
+ const y = 154 + index * 46;
+ const width = Math.max(44, count * 94);
+ return `${escapeXml(status)}
+
+ ${count}`;
+ })
+ .join("\n ");
+
+ return `
+`;
+}
+
+module.exports = {
+ PUBLISHABLE,
+ NOTICE_REQUIRED,
+ CONFLICT,
+ HOLD,
+ analyzeRepositoryLicenses,
+ createAuditDigest,
+ renderMarkdownReport,
+ renderSvgSummary,
+};
diff --git a/repository-license-compatibility-guard/package.json b/repository-license-compatibility-guard/package.json
new file mode 100644
index 00000000..14ef6b50
--- /dev/null
+++ b/repository-license-compatibility-guard/package.json
@@ -0,0 +1,13 @@
+{
+ "name": "repository-license-compatibility-guard",
+ "version": "1.0.0",
+ "private": true,
+ "description": "Synthetic repository dependency license compatibility guard for SCIBASE issue #10.",
+ "main": "index.js",
+ "scripts": {
+ "check": "node --check index.js && node --check sample-data.js && node --check demo.js && node --check test.js",
+ "test": "node test.js",
+ "demo": "node demo.js"
+ },
+ "license": "MIT"
+}
diff --git a/repository-license-compatibility-guard/reports/demo.mp4 b/repository-license-compatibility-guard/reports/demo.mp4
new file mode 100644
index 00000000..a8678b2e
Binary files /dev/null and b/repository-license-compatibility-guard/reports/demo.mp4 differ
diff --git a/repository-license-compatibility-guard/reports/reviewer-packet.md b/repository-license-compatibility-guard/reports/reviewer-packet.md
new file mode 100644
index 00000000..d6792416
--- /dev/null
+++ b/repository-license-compatibility-guard/reports/reviewer-packet.md
@@ -0,0 +1,87 @@
+# Repository License Compatibility Guard
+
+Repository: repo-neuro-open-atlas
+Version: v1.4.0
+Export intent: public_doi_and_commercial_partner_export
+Audit digest: `734ff57751d2a28105e23bf2507f2da485edf49f470f9048cb1e19b332b9d71b`
+
+## Totals
+
+- Assets evaluated: 7
+- Release-blocking assets: 3
+- conflict: 1
+- hold: 2
+- notice_required: 2
+- publishable: 2
+
+## Asset Decisions
+
+### asset-code-core
+
+- Path: code/atlas_pipeline.py
+- Type: code
+- License: MIT
+- Status: publishable
+- Release blocking: false
+- Reasons: license is compatible with the requested release/export intent
+- Actions: Allow asset in DOI/export package; Preserve license metadata in manifest
+
+### asset-dataset-images
+
+- Path: data/images-manifest.json
+- Type: dataset
+- License: CC-BY-4.0
+- Status: notice_required
+- Release blocking: false
+- Reasons: license permits export but attribution or notice is incomplete
+- Actions: Add license notice to export manifest; Include attribution in citation and repository notice files
+
+### asset-training-table
+
+- Path: data/training-table.csv
+- Type: dataset
+- License: CC-BY-NC-4.0
+- Status: conflict
+- Release blocking: true
+- Reasons: non-commercial license conflicts with commercial partner export intent
+- Actions: Block commercial export until asset is relicensed or omitted; Generate public-only export variant if permitted
+
+### asset-model-weights
+
+- Path: models/segmentation-weights.bin
+- Type: model
+- License: Proprietary
+- Status: hold
+- Release blocking: true
+- Reasons: asset license is proprietary, restricted, or unknown
+- Actions: Exclude from public DOI/export package; Request rights review and replacement artifact
+
+### asset-notebook
+
+- Path: notebooks/analysis.ipynb
+- Type: notebook
+- License: GPL-3.0
+- Status: notice_required
+- Release blocking: false
+- Reasons: license permits export but attribution or notice is incomplete
+- Actions: Add license notice to export manifest; Include attribution in citation and repository notice files
+
+### asset-figure
+
+- Path: results/figure-3.svg
+- Type: figure
+- License: CC0-1.0
+- Status: publishable
+- Release blocking: false
+- Reasons: license is compatible with the requested release/export intent
+- Actions: Allow asset in DOI/export package; Preserve license metadata in manifest
+
+### asset-raw-supplement
+
+- Path: data/supplement-private.tsv
+- Type: dataset
+- License: (missing)
+- Status: hold
+- Release blocking: true
+- Reasons: asset has no normalized SPDX or data license
+- Actions: Hold tagged release and export bundle; Attach license metadata or remove asset from export
diff --git a/repository-license-compatibility-guard/reports/summary.json b/repository-license-compatibility-guard/reports/summary.json
new file mode 100644
index 00000000..fc5d0acb
--- /dev/null
+++ b/repository-license-compatibility-guard/reports/summary.json
@@ -0,0 +1,131 @@
+{
+ "asOf": "2026-05-22T12:00:00.000Z",
+ "repositoryId": "repo-neuro-open-atlas",
+ "versionTag": "v1.4.0",
+ "exportIntent": "public_doi_and_commercial_partner_export",
+ "totals": {
+ "totalAssets": 7,
+ "releaseBlockingAssets": 3,
+ "byStatus": {
+ "publishable": 2,
+ "notice_required": 2,
+ "conflict": 1,
+ "hold": 2
+ }
+ },
+ "decisions": [
+ {
+ "id": "asset-code-core",
+ "componentType": "code",
+ "path": "code/atlas_pipeline.py",
+ "license": "MIT",
+ "exportIncluded": true,
+ "status": "publishable",
+ "releaseBlocking": false,
+ "reasons": [
+ "license is compatible with the requested release/export intent"
+ ],
+ "actions": [
+ "Allow asset in DOI/export package",
+ "Preserve license metadata in manifest"
+ ]
+ },
+ {
+ "id": "asset-dataset-images",
+ "componentType": "dataset",
+ "path": "data/images-manifest.json",
+ "license": "CC-BY-4.0",
+ "exportIncluded": true,
+ "status": "notice_required",
+ "releaseBlocking": false,
+ "reasons": [
+ "license permits export but attribution or notice is incomplete"
+ ],
+ "actions": [
+ "Add license notice to export manifest",
+ "Include attribution in citation and repository notice files"
+ ]
+ },
+ {
+ "id": "asset-training-table",
+ "componentType": "dataset",
+ "path": "data/training-table.csv",
+ "license": "CC-BY-NC-4.0",
+ "exportIncluded": true,
+ "status": "conflict",
+ "releaseBlocking": true,
+ "reasons": [
+ "non-commercial license conflicts with commercial partner export intent"
+ ],
+ "actions": [
+ "Block commercial export until asset is relicensed or omitted",
+ "Generate public-only export variant if permitted"
+ ]
+ },
+ {
+ "id": "asset-model-weights",
+ "componentType": "model",
+ "path": "models/segmentation-weights.bin",
+ "license": "Proprietary",
+ "exportIncluded": true,
+ "status": "hold",
+ "releaseBlocking": true,
+ "reasons": [
+ "asset license is proprietary, restricted, or unknown"
+ ],
+ "actions": [
+ "Exclude from public DOI/export package",
+ "Request rights review and replacement artifact"
+ ]
+ },
+ {
+ "id": "asset-notebook",
+ "componentType": "notebook",
+ "path": "notebooks/analysis.ipynb",
+ "license": "GPL-3.0",
+ "exportIncluded": true,
+ "status": "notice_required",
+ "releaseBlocking": false,
+ "reasons": [
+ "license permits export but attribution or notice is incomplete"
+ ],
+ "actions": [
+ "Add license notice to export manifest",
+ "Include attribution in citation and repository notice files"
+ ]
+ },
+ {
+ "id": "asset-figure",
+ "componentType": "figure",
+ "path": "results/figure-3.svg",
+ "license": "CC0-1.0",
+ "exportIncluded": true,
+ "status": "publishable",
+ "releaseBlocking": false,
+ "reasons": [
+ "license is compatible with the requested release/export intent"
+ ],
+ "actions": [
+ "Allow asset in DOI/export package",
+ "Preserve license metadata in manifest"
+ ]
+ },
+ {
+ "id": "asset-raw-supplement",
+ "componentType": "dataset",
+ "path": "data/supplement-private.tsv",
+ "license": "",
+ "exportIncluded": true,
+ "status": "hold",
+ "releaseBlocking": true,
+ "reasons": [
+ "asset has no normalized SPDX or data license"
+ ],
+ "actions": [
+ "Hold tagged release and export bundle",
+ "Attach license metadata or remove asset from export"
+ ]
+ }
+ ],
+ "auditDigest": "734ff57751d2a28105e23bf2507f2da485edf49f470f9048cb1e19b332b9d71b"
+}
diff --git a/repository-license-compatibility-guard/reports/summary.svg b/repository-license-compatibility-guard/reports/summary.svg
new file mode 100644
index 00000000..6f396553
--- /dev/null
+++ b/repository-license-compatibility-guard/reports/summary.svg
@@ -0,0 +1,29 @@
+
diff --git a/repository-license-compatibility-guard/requirements-map.md b/repository-license-compatibility-guard/requirements-map.md
new file mode 100644
index 00000000..4e639578
--- /dev/null
+++ b/repository-license-compatibility-guard/requirements-map.md
@@ -0,0 +1,19 @@
+# Requirements Map
+
+| Issue #10 area | Coverage |
+| --- | --- |
+| Repository structure and components | Evaluates code, dataset, notebook, model, and figure assets in a release manifest. |
+| File and metadata versioning | Binds decisions to a repository ID and semantic version tag. |
+| Collaboration and forking | Checks fork-derived asset license continuity and attribution. |
+| Computation-aware reproducibility | Holds model/notebook assets when source or license evidence is incomplete. |
+| Repository identifiers and citation | Blocks DOI/export publication when license and attribution evidence is unsafe. |
+| Programmatic access and export | Produces deterministic decision packets for export-bundle gating. |
+
+## Non-overlap
+
+This is not a broad repository ledger, release engine, structured diff/rollback,
+provenance attestation, release embargo, notebook replay, schema migration,
+citation impact, API/export, merge queue, environment drift, access review,
+DOI tombstone, metadata readiness, branch lineage, or sensitive-artifact commit
+slice. It focuses on dependency, dataset, model, and generated-artifact license
+compatibility before release/export.
diff --git a/repository-license-compatibility-guard/sample-data.js b/repository-license-compatibility-guard/sample-data.js
new file mode 100644
index 00000000..b18010ab
--- /dev/null
+++ b/repository-license-compatibility-guard/sample-data.js
@@ -0,0 +1,92 @@
+const sampleRepositoryLicensePacket = {
+ asOf: "2026-05-22T12:00:00.000Z",
+ release: {
+ repositoryId: "repo-neuro-open-atlas",
+ versionTag: "v1.4.0",
+ exportIntent: "public_doi_and_commercial_partner_export",
+ forkedFrom: "repo-vision-source",
+ sourceBundleIncluded: true,
+ },
+ assets: [
+ {
+ id: "asset-code-core",
+ componentType: "code",
+ path: "code/atlas_pipeline.py",
+ license: "MIT",
+ attribution: "SCIBASE Neuro Team",
+ noticeIncluded: true,
+ sourceAvailable: true,
+ derivedFromFork: false,
+ exportIncluded: true,
+ },
+ {
+ id: "asset-dataset-images",
+ componentType: "dataset",
+ path: "data/images-manifest.json",
+ license: "CC-BY-4.0",
+ attribution: "Open Imaging Consortium",
+ noticeIncluded: false,
+ sourceAvailable: true,
+ derivedFromFork: false,
+ exportIncluded: true,
+ },
+ {
+ id: "asset-training-table",
+ componentType: "dataset",
+ path: "data/training-table.csv",
+ license: "CC-BY-NC-4.0",
+ attribution: "Partner Lab",
+ noticeIncluded: true,
+ sourceAvailable: true,
+ derivedFromFork: false,
+ exportIncluded: true,
+ },
+ {
+ id: "asset-model-weights",
+ componentType: "model",
+ path: "models/segmentation-weights.bin",
+ license: "Proprietary",
+ attribution: "Private Vendor",
+ noticeIncluded: true,
+ sourceAvailable: false,
+ derivedFromFork: false,
+ exportIncluded: true,
+ },
+ {
+ id: "asset-notebook",
+ componentType: "notebook",
+ path: "notebooks/analysis.ipynb",
+ license: "GPL-3.0",
+ attribution: "Legacy Methods Group",
+ noticeIncluded: false,
+ sourceAvailable: true,
+ derivedFromFork: true,
+ forkLicense: "GPL-3.0",
+ exportIncluded: true,
+ },
+ {
+ id: "asset-figure",
+ componentType: "figure",
+ path: "results/figure-3.svg",
+ license: "CC0-1.0",
+ attribution: "Generated from synthetic data",
+ noticeIncluded: true,
+ sourceAvailable: true,
+ derivedFromFork: false,
+ exportIncluded: true,
+ },
+ {
+ id: "asset-raw-supplement",
+ componentType: "dataset",
+ path: "data/supplement-private.tsv",
+ license: "",
+ attribution: "",
+ noticeIncluded: false,
+ sourceAvailable: false,
+ derivedFromFork: false,
+ exportIncluded: true,
+ },
+ ],
+};
+
+module.exports = { sampleRepositoryLicensePacket };
diff --git a/repository-license-compatibility-guard/test.js b/repository-license-compatibility-guard/test.js
new file mode 100644
index 00000000..33066a26
--- /dev/null
+++ b/repository-license-compatibility-guard/test.js
@@ -0,0 +1,43 @@
+const assert = require("node:assert/strict");
+const {
+ CONFLICT,
+ HOLD,
+ NOTICE_REQUIRED,
+ PUBLISHABLE,
+ analyzeRepositoryLicenses,
+ createAuditDigest,
+ renderMarkdownReport,
+ renderSvgSummary,
+} = require("./index");
+const { sampleRepositoryLicensePacket } = require("./sample-data");
+
+const result = analyzeRepositoryLicenses(sampleRepositoryLicensePacket);
+
+assert.equal(result.totals.totalAssets, 7);
+assert.equal(result.totals.releaseBlockingAssets, 3);
+assert.equal(result.totals.byStatus[PUBLISHABLE], 2);
+assert.equal(result.totals.byStatus[NOTICE_REQUIRED], 2);
+assert.equal(result.totals.byStatus[CONFLICT], 1);
+assert.equal(result.totals.byStatus[HOLD], 2);
+
+const commercialConflict = result.decisions.find((decision) => decision.id === "asset-training-table");
+assert.equal(commercialConflict.status, CONFLICT);
+assert.match(commercialConflict.reasons.join(" "), /non-commercial license/);
+
+const missingLicense = result.decisions.find((decision) => decision.id === "asset-raw-supplement");
+assert.equal(missingLicense.status, HOLD);
+assert.equal(missingLicense.releaseBlocking, true);
+
+const gplNotebook = result.decisions.find((decision) => decision.id === "asset-notebook");
+assert.equal(gplNotebook.status, NOTICE_REQUIRED);
+
+assert.equal(createAuditDigest(result), result.auditDigest);
+assert.match(renderMarkdownReport(result), /Repository License Compatibility Guard/);
+assert.match(renderSvgSummary(result), /Blocking 3/);
+
+assert.throws(
+ () => analyzeRepositoryLicenses({ release: {}, assets: [] }),
+ /missing required release field: repositoryId/
+);
+
+console.log("repository license compatibility guard tests passed");