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
73 changes: 73 additions & 0 deletions notebook-result-replay-ledger/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# Notebook Result Replay Ledger

This module adds a focused computation-aware reproducibility slice for
SCIBASE project repositories. It models notebook cells, data/code/environment
dependencies, result artifacts, and release gates so a repository version can
answer one practical question before publication:

> Which scientific outputs were generated from the current data, code, and
> runtime evidence, and which outputs must be replayed before this version is
> cited or released?

The implementation is dependency-free and can run as a local validation step,
pre-release check, or future API service.

## What It Covers

- Hashes notebook inputs, code, runtime specs, and result artifacts into a
stable replay digest.
- Detects stale result outputs after upstream data, code, notebook, or
environment changes.
- Separates release-blocking failures from advisory replay work.
- Builds rollback-ready replay packets for reviewers and maintainers.
- Emits citation/version impact notes when a tagged repository version should
not advertise a clean "cite this project" badge.
- Includes tests, sample scientific repository data, a CLI demo, a requirement
map, and a short demo video.

## Quick Start

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

Expected demo summary:

```text
Release decision: block-release
Replay findings: 2
Release-blocking artifacts: 2
```

## Repository Layout

```text
notebook-result-replay-ledger/
data/sample-repository.json
docs/demo.svg
docs/demo.mp4
docs/requirement-map.md
scripts/demo.js
src/replay-ledger.js
test/replay-ledger.test.js
```

## Design Notes

The ledger intentionally stays narrower than a full project repository backend.
It assumes SCIBASE already has repository components such as `data/`, `code/`,
`notebooks/`, `results/`, `protocols/`, and `metadata.json`. This package adds
the reproducibility layer that ties those components together at publication
time:

1. A notebook cell declares its data, code, notebook, and environment inputs.
2. A result artifact stores the dependency hashes used when it was last
replayed.
3. The ledger recomputes a current replay digest from the repository manifest.
4. Any mismatch, failed run, or late dependency change becomes a finding.
5. Findings are classified into release-blocking or advisory outcomes.

That makes the result suitable for a pull request check, a release checklist, or
a reviewer-facing reproducibility panel.
176 changes: 176 additions & 0 deletions notebook-result-replay-ledger/data/sample-repository.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
{
"schemaVersion": "replay-ledger.v1",
"repository": {
"id": "scibase-neuro-organoid-atlas",
"name": "Neuro Organoid Response Atlas",
"releaseCandidate": "preprint-v2.2-rc1",
"lastStableTag": "preprint-v2.1",
"doi": "10.5555/scibase.neuro-organoid-atlas.v2",
"citationBadge": "cite-this-project"
},
"components": [
{
"id": "data.raw-counts",
"kind": "data",
"path": "data/raw/organoid-expression.csv",
"hash": "sha256:data-raw-counts-v4",
"previousHash": "sha256:data-raw-counts-v3",
"changedAt": "2026-05-12T10:00:00Z",
"lastStableTag": "preprint-v2.1",
"largeFile": true
},
{
"id": "data.qc-manifest",
"kind": "data",
"path": "data/processed/qc-manifest.json",
"hash": "sha256:data-qc-manifest-v2",
"previousHash": "sha256:data-qc-manifest-v1",
"changedAt": "2026-05-12T10:35:00Z",
"lastStableTag": "preprint-v2.1"
},
{
"id": "code.normalizer",
"kind": "code",
"path": "code/analysis/normalize_counts.py",
"hash": "sha256:normalizer-v5",
"previousHash": "sha256:normalizer-v4",
"changedAt": "2026-05-13T08:25:00Z",
"lastStableTag": "preprint-v2.1"
},
{
"id": "code.plotter",
"kind": "code",
"path": "code/analysis/build_figures.py",
"hash": "sha256:plotter-v2",
"previousHash": "sha256:plotter-v2",
"changedAt": "2026-05-10T12:00:00Z",
"lastStableTag": "preprint-v2.1"
},
{
"id": "notebook.run-analysis",
"kind": "notebook",
"path": "notebooks/run_analysis.ipynb",
"hash": "sha256:run-analysis-v7",
"previousHash": "sha256:run-analysis-v6",
"changedAt": "2026-05-13T09:10:00Z",
"lastStableTag": "preprint-v2.1"
},
{
"id": "notebook.qc-review",
"kind": "notebook",
"path": "notebooks/qc_review.ipynb",
"hash": "sha256:qc-review-v3",
"previousHash": "sha256:qc-review-v3",
"changedAt": "2026-05-11T14:00:00Z",
"lastStableTag": "preprint-v2.1"
},
{
"id": "env.analysis",
"kind": "environment",
"path": "environment.yml",
"hash": "sha256:conda-analysis-2026-05-14",
"previousHash": "sha256:conda-analysis-2026-05-08",
"changedAt": "2026-05-14T07:45:00Z",
"lastStableTag": "preprint-v2.1"
},
{
"id": "result.figure-1a",
"kind": "result",
"path": "results/figure-1a.png",
"hash": "sha256:figure-1a-before-data-update",
"lastReplayedAt": "2026-05-11T16:20:00Z",
"status": "passed",
"releaseCritical": true,
"citationCritical": true,
"recordedDependencyHashes": {
"data.raw-counts": "sha256:data-raw-counts-v3",
"code.normalizer": "sha256:normalizer-v4",
"code.plotter": "sha256:plotter-v2",
"notebook.run-analysis": "sha256:run-analysis-v6",
"env.analysis": "sha256:conda-analysis-2026-05-08"
}
},
{
"id": "result.model-metrics",
"kind": "result",
"path": "results/model-metrics.json",
"hash": "sha256:model-metrics-current",
"lastReplayedAt": "2026-05-14T09:15:00Z",
"status": "passed",
"releaseCritical": true,
"citationCritical": true,
"recordedDependencyHashes": {
"data.raw-counts": "sha256:data-raw-counts-v4",
"code.normalizer": "sha256:normalizer-v5",
"notebook.run-analysis": "sha256:run-analysis-v7",
"env.analysis": "sha256:conda-analysis-2026-05-14"
}
},
{
"id": "result.qc-table",
"kind": "result",
"path": "results/qc-table.csv",
"hash": "sha256:qc-table-failed-replay",
"lastReplayedAt": "2026-05-14T08:05:00Z",
"status": "failed",
"releaseCritical": true,
"citationCritical": false,
"failureReason": "Notebook replay stopped after container image changed.",
"recordedDependencyHashes": {
"data.qc-manifest": "sha256:data-qc-manifest-v2",
"notebook.qc-review": "sha256:qc-review-v3",
"env.analysis": "sha256:conda-analysis-2026-05-14"
}
},
{
"id": "protocol.analysis-plan",
"kind": "protocol",
"path": "protocols/analysis-plan.md",
"hash": "sha256:analysis-plan-v2",
"changedAt": "2026-05-09T10:00:00Z",
"lastStableTag": "preprint-v2.1"
},
{
"id": "metadata.project",
"kind": "metadata",
"path": "metadata.json",
"hash": "sha256:metadata-v4",
"changedAt": "2026-05-14T11:00:00Z",
"lastStableTag": "preprint-v2.1"
}
],
"notebookCells": [
{
"id": "cell.figure-1a",
"notebookId": "notebook.run-analysis",
"label": "Generate Figure 1A response heatmap",
"inputComponentIds": ["data.raw-counts"],
"codeComponentIds": ["code.normalizer", "code.plotter"],
"environmentComponentId": "env.analysis",
"outputResultIds": ["result.figure-1a"]
},
{
"id": "cell.model-metrics",
"notebookId": "notebook.run-analysis",
"label": "Recompute classifier metrics",
"inputComponentIds": ["data.raw-counts"],
"codeComponentIds": ["code.normalizer"],
"environmentComponentId": "env.analysis",
"outputResultIds": ["result.model-metrics"]
},
{
"id": "cell.qc-table",
"notebookId": "notebook.qc-review",
"label": "Validate sample quality table",
"inputComponentIds": ["data.qc-manifest"],
"codeComponentIds": [],
"environmentComponentId": "env.analysis",
"outputResultIds": ["result.qc-table"]
}
],
"releaseRules": {
"blockOnFailedReplay": true,
"blockOnCriticalStaleOutputs": true,
"warnOnCitationCriticalStaleOutputs": true
}
}
Binary file added notebook-result-replay-ledger/docs/demo.mp4
Binary file not shown.
52 changes: 52 additions & 0 deletions notebook-result-replay-ledger/docs/demo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
48 changes: 48 additions & 0 deletions notebook-result-replay-ledger/docs/requirement-map.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Requirement Map

Issue: SCIBASE-AI/SCIBASE.AI#10, Project Repository & Version Control.

## Repository Structure & Components

The sample manifest models scientific repository components across `data/`,
`code/`, `notebooks/`, `results/`, `protocols/`, and `metadata.json`. The
ledger validates that each component has a stable id, path, kind, content hash,
and timestamp where relevant.

## File & Metadata Versioning

Every component carries a `sha256:` content hash plus previous hash and stable
tag metadata where useful. Result artifacts store the dependency hashes used at
the last replay, allowing the ledger to compare recorded evidence against the
current release-candidate manifest.

## Computation-Aware Reproducibility

Notebook cells declare their data inputs, code inputs, notebook file, runtime
spec, and result artifacts. The ledger derives a replay digest from that
evidence, detects stale result outputs after dependency changes, and classifies
failed notebook replays as release blockers.

## Rollback And Release Gating

Each finding creates a rollback packet with the last stable tag, result path,
dependency paths, and replay command. Release-critical stale artifacts return
`block-release`; clean artifacts stay out of the findings list.

## Repository Identifiers & Citation

Citation-critical results generate impact notes when the release candidate
should not present a clean citation badge. This keeps DOI/version metadata from
overstating reproducibility before a result is replayed.

## Programmatic Access & Export

The module exports pure functions (`createReplayLedger`,
`validateRepositoryManifest`, `createReplaySnapshot`) that can back a REST
endpoint, CLI command, pull request check, or export-bundle validator.

## Scope Boundary

This is not another broad repository implementation. It is a focused
reproducibility layer for notebook/result replay evidence inside the project
repository model described by the bounty.
15 changes: 15 additions & 0 deletions notebook-result-replay-ledger/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"name": "notebook-result-replay-ledger",
"version": "0.1.0",
"description": "Dependency-free replay ledger for scientific notebook outputs and release gates.",
"private": true,
"scripts": {
"check": "node scripts/demo.js --json > /dev/null",
"demo": "node scripts/demo.js",
"test": "node --test test/*.test.js"
},
"engines": {
"node": ">=18"
},
"license": "MIT"
}
17 changes: 17 additions & 0 deletions notebook-result-replay-ledger/scripts/demo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
const fs = require("node:fs");
const path = require("node:path");
const { createReplayLedger, formatLedgerReport } = require("../src/replay-ledger");

const manifestPath = path.join(__dirname, "..", "data", "sample-repository.json");
const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
const ledger = createReplayLedger(manifest);

if (process.argv.includes("--json")) {
process.stdout.write(`${JSON.stringify(ledger, null, 2)}\n`);
} else {
process.stdout.write(`${formatLedgerReport(ledger)}\n`);
}

if (ledger.releaseDecision === "invalid-manifest") {
process.exitCode = 1;
}
Loading