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
13 changes: 13 additions & 0 deletions artifact-retention-tombstone-ledger/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Artifact Retention Tombstone Ledger

This self-contained slice reviews hosted scientific artifacts before deletion or disposal.
It preserves citation continuity and checksum evidence by deciding whether an artifact must be retained, tombstoned, or safely disposed.

## Run locally

```bash
node artifact-retention-tombstone-ledger/test.js
node artifact-retention-tombstone-ledger/demo.js
```

Demo outputs are written to `artifact-retention-tombstone-ledger/demo-output/`.
7 changes: 7 additions & 0 deletions artifact-retention-tombstone-ledger/acceptance-notes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Acceptance Notes

- The module is dependency-free and uses synthetic data only.
- Tests cover active holds, unelapsed retention, missing checksums, DOI/citation tombstone requirements, and destructive delete prevention.
- The ready-path test proves an eligible artifact can be disposed with a tombstone packet.
- Demo artifacts include JSON, Markdown, SVG, and MP4 output.
- The slice is distinct from prior #14 submissions around FAIR manifests, compute governance, provenance, quarantine, quota/deduplication, preview cache, raw instrument preview, and notebook preview.
Binary file not shown.
13 changes: 13 additions & 0 deletions artifact-retention-tombstone-ledger/demo-output/demo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
13 changes: 13 additions & 0 deletions artifact-retention-tombstone-ledger/demo-output/disposal-review.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Artifact retention and tombstone review

Project: hosting-cardio-fluid-dynamics
Status: hold

## Holds
- raw-trials-2024: Clear or document the legal, IRB, funder, or citation hold before disposal.
- simulation-cache-v3: Generate and preserve SHA-256 evidence before any deletion or tombstone export.
- derived-public-csv: Record DOI, title, type, checksum, license, and replacement URI before disposal.
- notebook-results-v1: Use a tombstone-preserving disposal mode for cited scientific artifacts.
- raw-trials-2024: Wait until the retention window elapses or obtain an explicit retention override.

Audit digest: fa494d5362739c320e1bdcc1764ea662c55944682a87715de046142c29f13568
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
{
"packetType": "artifact-retention-tombstone-ledger",
"projectId": "hosting-cardio-fluid-dynamics",
"generatedAt": "2026-05-20T12:00:00Z",
"overallStatus": "hold",
"decisions": [
{
"artifactId": "raw-trials-2024",
"decision": "hold",
"tombstoneRequired": false,
"retentionUntil": "2027-01-01T00:00:00Z",
"disposalMode": "tombstone"
},
{
"artifactId": "simulation-cache-v3",
"decision": "hold",
"tombstoneRequired": false,
"retentionUntil": "2025-01-01T00:00:00Z",
"disposalMode": "delete"
},
{
"artifactId": "derived-public-csv",
"decision": "tombstone_required",
"tombstoneRequired": true,
"retentionUntil": "2025-01-01T00:00:00Z",
"disposalMode": "tombstone"
},
{
"artifactId": "notebook-results-v1",
"decision": "hold",
"tombstoneRequired": true,
"retentionUntil": "2025-01-01T00:00:00Z",
"disposalMode": "hard_delete"
}
],
"holds": [
{
"artifactId": "raw-trials-2024",
"code": "active_policy_hold",
"owner": "data-steward",
"severity": "blocker",
"evidence": "raw-trials-2024 has active hold(s): irb-hold.",
"requiredAction": "Clear or document the legal, IRB, funder, or citation hold before disposal."
},
{
"artifactId": "simulation-cache-v3",
"code": "checksum_manifest_missing",
"owner": "compute-admin",
"severity": "blocker",
"evidence": "simulation-cache-v3 is missing a checksum manifest.",
"requiredAction": "Generate and preserve SHA-256 evidence before any deletion or tombstone export."
},
{
"artifactId": "derived-public-csv",
"code": "citation_tombstone_required",
"owner": "repository-owner",
"severity": "blocker",
"evidence": "derived-public-csv is cited or DOI-backed but lacks complete tombstone metadata.",
"requiredAction": "Record DOI, title, type, checksum, license, and replacement URI before disposal."
},
{
"artifactId": "notebook-results-v1",
"code": "destructive_delete_not_allowed",
"owner": "repository-owner",
"severity": "blocker",
"evidence": "notebook-results-v1 requested hard delete despite citation dependencies.",
"requiredAction": "Use a tombstone-preserving disposal mode for cited scientific artifacts."
},
{
"artifactId": "raw-trials-2024",
"code": "retention_window_not_elapsed",
"owner": "data-steward",
"severity": "blocker",
"evidence": "raw-trials-2024 must be retained until 2027-01-01T00:00:00Z.",
"requiredAction": "Wait until the retention window elapses or obtain an explicit retention override."
}
],
"tombstonePlan": [
{
"artifactId": "raw-trials-2024",
"decision": "hold",
"requiredMetadata": {
"doi": null,
"title": "Raw protected waveform trials",
"artifactType": "dataset",
"sha256": "4f8a6c0f6a3e7c85d236d2dd7251c2a6d4c6f5974f4e1a17c95bbf58b9fd0041",
"license": "restricted-dua",
"replacementUri": null,
"disposalReason": "superseded protected dataset"
},
"preserveChecksum": true,
"publicLandingPage": false
},
{
"artifactId": "simulation-cache-v3",
"decision": "hold",
"requiredMetadata": {
"doi": null,
"title": "Simulation cache v3",
"artifactType": "model-cache",
"sha256": null,
"license": "internal",
"replacementUri": null,
"disposalReason": "cache eviction"
},
"preserveChecksum": false,
"publicLandingPage": false
},
{
"artifactId": "derived-public-csv",
"decision": "tombstone_required",
"requiredMetadata": {
"doi": "10.5555/scibase.derived-pressure",
"title": "Derived public pressure table",
"artifactType": "dataset",
"sha256": "9b340ad03e9c4d2fdc1768c374fd898d021a7e11a62f1c812f12792b3e65cf3d",
"license": "cc-by-4.0",
"replacementUri": "https://doi.org/10.5555/scibase.derived-pressure-v2",
"disposalReason": "replaced by corrected table"
},
"preserveChecksum": true,
"publicLandingPage": true
},
{
"artifactId": "notebook-results-v1",
"decision": "hold",
"requiredMetadata": {
"doi": "10.5555/scibase.notebook-results-v1",
"title": "Notebook results v1",
"artifactType": "notebook",
"sha256": "ce8a3b1337fb61e3a0b273a7f085d45f943fda03f852e37caad06cc8215998e6",
"license": "cc-by-4.0",
"replacementUri": null,
"disposalReason": "author requested cleanup"
},
"preserveChecksum": true,
"publicLandingPage": true
}
],
"auditDigest": "fa494d5362739c320e1bdcc1764ea662c55944682a87715de046142c29f13568"
}
75 changes: 75 additions & 0 deletions artifact-retention-tombstone-ledger/demo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
const fs = require('fs');
const path = require('path');
const { execFileSync } = require('child_process');
const { buildTombstoneLedgerPacket } = require('./index');
const { sampleHostingProject } = require('./sample-data');

const outputDir = path.join(__dirname, 'demo-output');
fs.mkdirSync(outputDir, { recursive: true });

const packet = buildTombstoneLedgerPacket(sampleHostingProject, {
asOf: '2026-05-20T12:00:00Z',
});

fs.writeFileSync(path.join(outputDir, 'tombstone-ledger-packet.json'), `${JSON.stringify(packet, null, 2)}\n`);

const rows = packet.holds
.map((hold, index) => `<text x="120" y="${310 + index * 46}" font-family="Arial" font-size="22" fill="#243b53">${index + 1}. ${hold.artifactId}: ${hold.code}</text>`)
.join('\n ');

fs.writeFileSync(path.join(outputDir, 'demo.svg'), `<svg xmlns="http://www.w3.org/2000/svg" width="1280" height="720">
<rect width="1280" height="720" fill="#172554"/>
<rect x="70" y="70" width="1140" height="580" rx="18" fill="#f8fafc"/>
<text x="110" y="145" font-family="Arial" font-size="45" font-weight="700" fill="#0f172a">Artifact Retention Tombstone Ledger</text>
<text x="110" y="205" font-family="Arial" font-size="25" fill="#334155">${packet.projectId}</text>
<text x="110" y="270" font-family="Arial" font-size="32" font-weight="700" fill="#b91c1c">${packet.holds.length} disposal blockers</text>
${rows}
<text x="110" y="620" font-family="Arial" font-size="22" fill="#475569">audit digest: ${packet.auditDigest.slice(0, 24)}...</text>
</svg>
`);

fs.writeFileSync(path.join(outputDir, 'disposal-review.md'), [
'# Artifact retention and tombstone review',
'',
`Project: ${packet.projectId}`,
`Status: ${packet.overallStatus}`,
'',
'## Holds',
...packet.holds.map((hold) => `- ${hold.artifactId}: ${hold.requiredAction}`),
'',
`Audit digest: ${packet.auditDigest}`,
'',
].join('\n'));

function renderMp4() {
const videoPath = path.join(outputDir, 'demo.mp4');
const font = 'C\\:/Windows/Fonts/arial.ttf';
const escapeText = (value) => String(value).replace(/\\/g, '\\\\').replace(/:/g, '\\:').replace(/'/g, "\\'");
const filters = [
`drawtext=fontfile='${font}':text='${escapeText('Artifact Retention Tombstone Ledger')}':x=70:y=80:fontsize=41:fontcolor=white`,
`drawtext=fontfile='${font}':text='${escapeText(`${packet.holds.length} disposal blockers before deletion`)}':x=70:y=155:fontsize=32:fontcolor=0xffd166`,
...packet.holds.map((hold, index) =>
`drawtext=fontfile='${font}':text='${escapeText(`${hold.artifactId}: ${hold.code}`)}':x=90:y=${230 + index * 55}:fontsize=26:fontcolor=white`,
),
`drawtext=fontfile='${font}':text='${escapeText(`audit ${packet.auditDigest.slice(0, 20)}...`)}':x=70:y=630:fontsize=24:fontcolor=0x93c5fd`,
].join(',');

execFileSync('ffmpeg', [
'-y',
'-f',
'lavfi',
'-i',
'color=c=0x172554:s=1280x720:d=7',
'-vf',
filters,
'-c:v',
'libx264',
'-pix_fmt',
'yuv420p',
videoPath,
], { stdio: 'inherit' });
}

renderMp4();

console.log(`Wrote demo artifacts to ${outputDir}`);
Loading