From 38fa65a08e1c833c0ad6e6a60d2c6f1036940340 Mon Sep 17 00:00:00 2001 From: orenodinner Date: Sun, 31 May 2026 13:40:06 +0900 Subject: [PATCH] add repository artifact prune guard --- repository-artifact-prune-guard/README.md | 27 ++++ .../artifactPruneGuard.js | 147 ++++++++++++++++++ repository-artifact-prune-guard/demo.js | 60 +++++++ .../reports/artifact-prune-packet.json | 77 +++++++++ .../reports/artifact-prune-report.md | 21 +++ .../reports/demo.ass | 17 ++ .../reports/demo.mp4 | Bin 0 -> 58352 bytes .../reports/summary.svg | 9 ++ .../sampleRepository.js | 88 +++++++++++ repository-artifact-prune-guard/test.js | 63 ++++++++ 10 files changed, 509 insertions(+) create mode 100644 repository-artifact-prune-guard/README.md create mode 100644 repository-artifact-prune-guard/artifactPruneGuard.js create mode 100644 repository-artifact-prune-guard/demo.js create mode 100644 repository-artifact-prune-guard/reports/artifact-prune-packet.json create mode 100644 repository-artifact-prune-guard/reports/artifact-prune-report.md create mode 100644 repository-artifact-prune-guard/reports/demo.ass create mode 100644 repository-artifact-prune-guard/reports/demo.mp4 create mode 100644 repository-artifact-prune-guard/reports/summary.svg create mode 100644 repository-artifact-prune-guard/sampleRepository.js create mode 100644 repository-artifact-prune-guard/test.js diff --git a/repository-artifact-prune-guard/README.md b/repository-artifact-prune-guard/README.md new file mode 100644 index 00000000..8438beb4 --- /dev/null +++ b/repository-artifact-prune-guard/README.md @@ -0,0 +1,27 @@ +# Repository Artifact Prune Guard + +This is a self-contained Project Repository & Version Control slice for issue #10. + +The guard checks whether repository artifacts can be pruned before a tagged scientific repository release or export bundle is published. It protects citation evidence, DOI version manifests, export bundle manifests, and reproducibility run packets from accidental deletion. + +## Scope + +- Synthetic data only. +- No network calls, credentials, DOI provider, storage provider, Git provider, or SCIBASE production service integration. +- Focused on artifact garbage-collection readiness, not broad repository ledgers, external reference pinning, retention legal holds, embargo release checks, component-owner approvals, branch protection, or release signatures. + +## Validation + +```sh +node repository-artifact-prune-guard/test.js +node repository-artifact-prune-guard/demo.js +``` + +The demo writes deterministic reviewer artifacts under `repository-artifact-prune-guard/reports/`. + +Reviewer artifacts: + +- `reports/artifact-prune-packet.json` +- `reports/artifact-prune-report.md` +- `reports/summary.svg` +- `reports/demo.mp4` diff --git a/repository-artifact-prune-guard/artifactPruneGuard.js b/repository-artifact-prune-guard/artifactPruneGuard.js new file mode 100644 index 00000000..10328b1f --- /dev/null +++ b/repository-artifact-prune-guard/artifactPruneGuard.js @@ -0,0 +1,147 @@ +"use strict"; + +const DAY_MS = 24 * 60 * 60 * 1000; + +function daysBetween(olderIso, newerIso) { + return Math.floor((Date.parse(newerIso) - Date.parse(olderIso)) / DAY_MS); +} + +function indexReferences(references) { + const byArtifact = new Map(); + + for (const group of references) { + for (const item of group.artifacts) { + const entries = byArtifact.get(item.id) || []; + entries.push({ + source: group.source, + sourceType: group.sourceType, + requiredHash: item.hash || null, + severity: group.severity || "blocker", + }); + byArtifact.set(item.id, entries); + } + } + + return byArtifact; +} + +function artifactLabel(artifact) { + return `${artifact.id} (${artifact.path})`; +} + +function analyzeArtifactPrunePlan(input) { + const now = input.now || new Date().toISOString(); + const retentionDays = input.policy?.minimumUnreferencedAgeDays ?? 30; + const candidateIds = new Set(input.pruneCandidates || []); + const referencesByArtifact = indexReferences(input.references || []); + + const artifactsById = new Map(input.artifacts.map((artifact) => [artifact.id, artifact])); + const missingCandidates = [...candidateIds].filter((id) => !artifactsById.has(id)); + + const approvedPrunes = []; + const blockedPrunes = []; + const warnings = []; + + for (const artifact of input.artifacts) { + if (!candidateIds.has(artifact.id)) { + continue; + } + + const ageDays = daysBetween(artifact.lastReferencedAt || artifact.createdAt, now); + const references = referencesByArtifact.get(artifact.id) || []; + const blockers = []; + + for (const reference of references) { + if (reference.requiredHash && reference.requiredHash !== artifact.hash) { + blockers.push({ + code: "REFERENCE_HASH_DRIFT", + message: `${reference.source} expects ${reference.requiredHash} but ${artifact.id} is ${artifact.hash}`, + source: reference.source, + }); + continue; + } + + blockers.push({ + code: referenceCode(reference.sourceType), + message: `${artifactLabel(artifact)} is still required by ${reference.source}`, + source: reference.source, + }); + } + + if (artifact.retentionHold) { + blockers.push({ + code: "RETENTION_HOLD", + message: `${artifactLabel(artifact)} has an active retention hold: ${artifact.retentionHold}`, + source: "repository policy", + }); + } + + if (ageDays < retentionDays) { + blockers.push({ + code: "MINIMUM_AGE_NOT_MET", + message: `${artifactLabel(artifact)} is ${ageDays} days old; policy requires ${retentionDays}`, + source: "repository policy", + }); + } + + if (blockers.length > 0) { + blockedPrunes.push({ + artifactId: artifact.id, + path: artifact.path, + blockers, + }); + } else { + approvedPrunes.push({ + artifactId: artifact.id, + path: artifact.path, + reason: `unreferenced for ${ageDays} days and no release evidence depends on it`, + }); + } + } + + for (const id of missingCandidates) { + warnings.push({ + code: "UNKNOWN_CANDIDATE", + message: `Prune candidate ${id} is not present in the repository artifact inventory`, + }); + } + + const status = blockedPrunes.length === 0 && missingCandidates.length === 0 ? "pass" : "blocked"; + + return { + status, + checkedAt: now, + policy: { + minimumUnreferencedAgeDays: retentionDays, + }, + summary: { + candidates: candidateIds.size, + approved: approvedPrunes.length, + blocked: blockedPrunes.length, + warnings: warnings.length, + }, + approvedPrunes, + blockedPrunes, + warnings, + }; +} + +function referenceCode(sourceType) { + switch (sourceType) { + case "citation": + return "CITATION_EVIDENCE_BOUND"; + case "doi": + return "DOI_VERSION_BOUND"; + case "export": + return "EXPORT_MANIFEST_BOUND"; + case "reproducibility": + return "REPRODUCIBILITY_PACKET_BOUND"; + default: + return "REPOSITORY_REFERENCE_BOUND"; + } +} + +module.exports = { + analyzeArtifactPrunePlan, + daysBetween, +}; diff --git a/repository-artifact-prune-guard/demo.js b/repository-artifact-prune-guard/demo.js new file mode 100644 index 00000000..d678f6cd --- /dev/null +++ b/repository-artifact-prune-guard/demo.js @@ -0,0 +1,60 @@ +"use strict"; + +const fs = require("fs"); +const path = require("path"); +const { analyzeArtifactPrunePlan } = require("./artifactPruneGuard"); +const { sampleRepository } = require("./sampleRepository"); + +const report = analyzeArtifactPrunePlan(sampleRepository); +const reportsDir = path.join(__dirname, "reports"); + +fs.mkdirSync(reportsDir, { recursive: true }); +fs.writeFileSync( + path.join(reportsDir, "artifact-prune-packet.json"), + `${JSON.stringify(report, null, 2)}\n`, +); +fs.writeFileSync(path.join(reportsDir, "artifact-prune-report.md"), renderMarkdown(report)); +fs.writeFileSync(path.join(reportsDir, "summary.svg"), renderSvg(report)); + +console.log(`Artifact prune guard status: ${report.status}`); +console.log(`Approved: ${report.summary.approved}; blocked: ${report.summary.blocked}`); + +function renderMarkdown(packet) { + const lines = [ + "# Artifact Prune Guard Report", + "", + `Status: ${packet.status}`, + `Checked at: ${packet.checkedAt}`, + "", + "## Approved Prunes", + "", + ...packet.approvedPrunes.map((item) => `- ${item.artifactId}: ${item.reason}`), + "", + "## Blocked Prunes", + "", + ]; + + for (const item of packet.blockedPrunes) { + lines.push(`- ${item.artifactId} (${item.path})`); + for (const blocker of item.blockers) { + lines.push(` - ${blocker.code}: ${blocker.message}`); + } + } + + return `${lines.join("\n")}\n`; +} + +function renderSvg(packet) { + const approvedWidth = 70 + packet.summary.approved * 70; + const blockedWidth = 70 + packet.summary.blocked * 70; + return ` + + Artifact Prune Guard + Status: ${packet.status} + + Approved ${packet.summary.approved} + + Blocked ${packet.summary.blocked} + +`; +} diff --git a/repository-artifact-prune-guard/reports/artifact-prune-packet.json b/repository-artifact-prune-guard/reports/artifact-prune-packet.json new file mode 100644 index 00000000..5454cef0 --- /dev/null +++ b/repository-artifact-prune-guard/reports/artifact-prune-packet.json @@ -0,0 +1,77 @@ +{ + "status": "blocked", + "checkedAt": "2026-05-31T04:45:00.000Z", + "policy": { + "minimumUnreferencedAgeDays": 30 + }, + "summary": { + "candidates": 5, + "approved": 1, + "blocked": 4, + "warnings": 0 + }, + "approvedPrunes": [ + { + "artifactId": "orphan-scratch-plot", + "path": "results/tmp/orphan-scratch-plot.png", + "reason": "unreferenced for 118 days and no release evidence depends on it" + } + ], + "blockedPrunes": [ + { + "artifactId": "fig-survival-curve", + "path": "results/figures/survival-curve.svg", + "blockers": [ + { + "code": "CITATION_EVIDENCE_BOUND", + "message": "fig-survival-curve (results/figures/survival-curve.svg) is still required by manuscript/citations.json#figure-2", + "source": "manuscript/citations.json#figure-2" + }, + { + "code": "DOI_VERSION_BOUND", + "message": "fig-survival-curve (results/figures/survival-curve.svg) is still required by metadata.json#doi:10.5555/scibase.demo.v1", + "source": "metadata.json#doi:10.5555/scibase.demo.v1" + } + ] + }, + { + "artifactId": "raw-screening-table", + "path": "data/raw/screening-table.csv", + "blockers": [ + { + "code": "EXPORT_MANIFEST_BOUND", + "message": "raw-screening-table (data/raw/screening-table.csv) is still required by exports/release-v1/manifest.json", + "source": "exports/release-v1/manifest.json" + } + ] + }, + { + "artifactId": "notebook-run-packet", + "path": "notebooks/run-2026-05-01/replay-packet.json", + "blockers": [ + { + "code": "REPRODUCIBILITY_PACKET_BOUND", + "message": "notebook-run-packet (notebooks/run-2026-05-01/replay-packet.json) is still required by results/reproducibility/run-2026-05-01.json", + "source": "results/reproducibility/run-2026-05-01.json" + }, + { + "code": "MINIMUM_AGE_NOT_MET", + "message": "notebook-run-packet (notebooks/run-2026-05-01/replay-packet.json) is 29 days old; policy requires 30", + "source": "repository policy" + } + ] + }, + { + "artifactId": "protocol-appendix", + "path": "protocols/appendix-a.md", + "blockers": [ + { + "code": "RETENTION_HOLD", + "message": "protocol-appendix (protocols/appendix-a.md) has an active retention hold: IRB audit window closes 2026-12-31", + "source": "repository policy" + } + ] + } + ], + "warnings": [] +} diff --git a/repository-artifact-prune-guard/reports/artifact-prune-report.md b/repository-artifact-prune-guard/reports/artifact-prune-report.md new file mode 100644 index 00000000..de994345 --- /dev/null +++ b/repository-artifact-prune-guard/reports/artifact-prune-report.md @@ -0,0 +1,21 @@ +# Artifact Prune Guard Report + +Status: blocked +Checked at: 2026-05-31T04:45:00.000Z + +## Approved Prunes + +- orphan-scratch-plot: unreferenced for 118 days and no release evidence depends on it + +## Blocked Prunes + +- fig-survival-curve (results/figures/survival-curve.svg) + - CITATION_EVIDENCE_BOUND: fig-survival-curve (results/figures/survival-curve.svg) is still required by manuscript/citations.json#figure-2 + - DOI_VERSION_BOUND: fig-survival-curve (results/figures/survival-curve.svg) is still required by metadata.json#doi:10.5555/scibase.demo.v1 +- raw-screening-table (data/raw/screening-table.csv) + - EXPORT_MANIFEST_BOUND: raw-screening-table (data/raw/screening-table.csv) is still required by exports/release-v1/manifest.json +- notebook-run-packet (notebooks/run-2026-05-01/replay-packet.json) + - REPRODUCIBILITY_PACKET_BOUND: notebook-run-packet (notebooks/run-2026-05-01/replay-packet.json) is still required by results/reproducibility/run-2026-05-01.json + - MINIMUM_AGE_NOT_MET: notebook-run-packet (notebooks/run-2026-05-01/replay-packet.json) is 29 days old; policy requires 30 +- protocol-appendix (protocols/appendix-a.md) + - RETENTION_HOLD: protocol-appendix (protocols/appendix-a.md) has an active retention hold: IRB audit window closes 2026-12-31 diff --git a/repository-artifact-prune-guard/reports/demo.ass b/repository-artifact-prune-guard/reports/demo.ass new file mode 100644 index 00000000..b1a23efe --- /dev/null +++ b/repository-artifact-prune-guard/reports/demo.ass @@ -0,0 +1,17 @@ +[Script Info] +Title: Artifact Prune Guard Demo +ScriptType: v4.00+ +PlayResX: 1280 +PlayResY: 720 + +[V4+ Styles] +Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding +Style: Title,Arial,54,&H00FFFFFF,&H000000FF,&H00202830,&H99000000,1,0,0,0,100,100,0,0,1,3,1,8,60,60,60,1 +Style: Body,Arial,36,&H00F8FAFC,&H000000FF,&H00202830,&H99000000,0,0,0,0,100,100,0,0,1,2,1,5,90,90,80,1 + +[Events] +Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text +Dialogue: 0,0:00:00.00,0:00:02.20,Title,,0,0,0,,Repository Artifact Prune Guard +Dialogue: 0,0:00:02.20,0:00:04.80,Body,,0,0,0,,Checks prune candidates before scientific release or export. +Dialogue: 0,0:00:04.80,0:00:07.60,Body,,0,0,0,,Blocks artifacts still bound to citations, DOI manifests, exports, or reproducibility packets. +Dialogue: 0,0:00:07.60,0:00:10.00,Body,,0,0,0,,Demo result: 1 approved prune, 4 blocked prunes, deterministic reviewer packet generated. diff --git a/repository-artifact-prune-guard/reports/demo.mp4 b/repository-artifact-prune-guard/reports/demo.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..863a87ab2c38791c2058d8328c7ccf92f57d27f5 GIT binary patch literal 58352 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 + + Artifact Prune Guard + Status: blocked + + Approved 1 + + Blocked 4 + diff --git a/repository-artifact-prune-guard/sampleRepository.js b/repository-artifact-prune-guard/sampleRepository.js new file mode 100644 index 00000000..26f427e1 --- /dev/null +++ b/repository-artifact-prune-guard/sampleRepository.js @@ -0,0 +1,88 @@ +"use strict"; + +const sampleRepository = { + now: "2026-05-31T04:45:00.000Z", + policy: { + minimumUnreferencedAgeDays: 30, + }, + artifacts: [ + { + id: "fig-survival-curve", + path: "results/figures/survival-curve.svg", + kind: "figure", + hash: "sha256:fig-001", + createdAt: "2026-03-01T10:00:00.000Z", + lastReferencedAt: "2026-03-20T10:00:00.000Z", + }, + { + id: "raw-screening-table", + path: "data/raw/screening-table.csv", + kind: "dataset", + hash: "sha256:data-raw-017", + createdAt: "2026-01-12T10:00:00.000Z", + lastReferencedAt: "2026-01-13T10:00:00.000Z", + }, + { + id: "notebook-run-packet", + path: "notebooks/run-2026-05-01/replay-packet.json", + kind: "reproducibility-packet", + hash: "sha256:packet-022", + createdAt: "2026-05-01T10:00:00.000Z", + lastReferencedAt: "2026-05-01T10:00:00.000Z", + }, + { + id: "orphan-scratch-plot", + path: "results/tmp/orphan-scratch-plot.png", + kind: "scratch", + hash: "sha256:tmp-003", + createdAt: "2026-02-01T10:00:00.000Z", + lastReferencedAt: "2026-02-01T10:00:00.000Z", + }, + { + id: "protocol-appendix", + path: "protocols/appendix-a.md", + kind: "protocol", + hash: "sha256:protocol-009", + createdAt: "2026-02-10T10:00:00.000Z", + lastReferencedAt: "2026-02-10T10:00:00.000Z", + retentionHold: "IRB audit window closes 2026-12-31", + }, + ], + references: [ + { + source: "manuscript/citations.json#figure-2", + sourceType: "citation", + severity: "blocker", + artifacts: [{ id: "fig-survival-curve", hash: "sha256:fig-001" }], + }, + { + source: "metadata.json#doi:10.5555/scibase.demo.v1", + sourceType: "doi", + severity: "blocker", + artifacts: [{ id: "fig-survival-curve", hash: "sha256:fig-001" }], + }, + { + source: "exports/release-v1/manifest.json", + sourceType: "export", + severity: "blocker", + artifacts: [{ id: "raw-screening-table", hash: "sha256:data-raw-017" }], + }, + { + source: "results/reproducibility/run-2026-05-01.json", + sourceType: "reproducibility", + severity: "blocker", + artifacts: [{ id: "notebook-run-packet", hash: "sha256:packet-022" }], + }, + ], + pruneCandidates: [ + "fig-survival-curve", + "raw-screening-table", + "notebook-run-packet", + "orphan-scratch-plot", + "protocol-appendix", + ], +}; + +module.exports = { + sampleRepository, +}; diff --git a/repository-artifact-prune-guard/test.js b/repository-artifact-prune-guard/test.js new file mode 100644 index 00000000..24d7ea0d --- /dev/null +++ b/repository-artifact-prune-guard/test.js @@ -0,0 +1,63 @@ +"use strict"; + +const assert = require("assert"); +const { analyzeArtifactPrunePlan, daysBetween } = require("./artifactPruneGuard"); +const { sampleRepository } = require("./sampleRepository"); + +assert.strictEqual(daysBetween("2026-01-01T00:00:00.000Z", "2026-01-31T00:00:00.000Z"), 30); + +const packet = analyzeArtifactPrunePlan(sampleRepository); +assert.strictEqual(packet.status, "blocked"); +assert.strictEqual(packet.summary.candidates, 5); +assert.strictEqual(packet.summary.approved, 1); +assert.strictEqual(packet.summary.blocked, 4); +assert.deepStrictEqual(packet.approvedPrunes.map((item) => item.artifactId), ["orphan-scratch-plot"]); + +const blockedCodes = new Map( + packet.blockedPrunes.map((item) => [ + item.artifactId, + item.blockers.map((blocker) => blocker.code), + ]), +); +assert.deepStrictEqual(blockedCodes.get("fig-survival-curve"), [ + "CITATION_EVIDENCE_BOUND", + "DOI_VERSION_BOUND", +]); +assert.deepStrictEqual(blockedCodes.get("raw-screening-table"), ["EXPORT_MANIFEST_BOUND"]); +assert(blockedCodes.get("notebook-run-packet").includes("REPRODUCIBILITY_PACKET_BOUND")); +assert(blockedCodes.get("notebook-run-packet").includes("MINIMUM_AGE_NOT_MET")); +assert(blockedCodes.get("protocol-appendix").includes("RETENTION_HOLD")); + +const hashDriftPacket = analyzeArtifactPrunePlan({ + now: "2026-05-31T00:00:00.000Z", + policy: { minimumUnreferencedAgeDays: 1 }, + artifacts: [ + { + id: "model-card", + path: "results/model-card.json", + hash: "sha256:new", + createdAt: "2026-05-01T00:00:00.000Z", + lastReferencedAt: "2026-05-01T00:00:00.000Z", + }, + ], + references: [ + { + source: "exports/v2/manifest.json", + sourceType: "export", + artifacts: [{ id: "model-card", hash: "sha256:old" }], + }, + ], + pruneCandidates: ["model-card"], +}); +assert.strictEqual(hashDriftPacket.blockedPrunes[0].blockers[0].code, "REFERENCE_HASH_DRIFT"); + +const missingPacket = analyzeArtifactPrunePlan({ + artifacts: [], + references: [], + pruneCandidates: ["missing-artifact"], + now: "2026-05-31T00:00:00.000Z", +}); +assert.strictEqual(missingPacket.status, "blocked"); +assert.strictEqual(missingPacket.warnings[0].code, "UNKNOWN_CANDIDATE"); + +console.log("artifact-prune-guard tests passed");