From 60f7e7549db6c9c12466813648472572f5113473 Mon Sep 17 00:00:00 2001 From: Joey Cera <89586838+jtc268@users.noreply.github.com> Date: Thu, 28 May 2026 02:14:57 -0400 Subject: [PATCH 1/2] Add collaborative notebook reproducibility guard --- .../README.md | 31 ++ .../acceptance-notes.md | 13 + collab-notebook-reproducibility-guard/demo.js | 103 ++++++ .../index.js | 350 ++++++++++++++++++ .../make-demo-video.py | 116 ++++++ .../package.json | 12 + .../reports/clean-notebook-packet.json | 94 +++++ .../reports/demo.mp4 | Bin 0 -> 88432 bytes .../notebook-reproducibility-report.md | 15 + .../reports/summary.svg | 23 ++ .../reports/unsafe-notebook-packet.json | 198 ++++++++++ .../reports/warning-notebook-packet.json | 64 ++++ .../requirements-map.md | 15 + .../sample-data.js | 235 ++++++++++++ collab-notebook-reproducibility-guard/test.js | 81 ++++ 15 files changed, 1350 insertions(+) create mode 100644 collab-notebook-reproducibility-guard/README.md create mode 100644 collab-notebook-reproducibility-guard/acceptance-notes.md create mode 100644 collab-notebook-reproducibility-guard/demo.js create mode 100644 collab-notebook-reproducibility-guard/index.js create mode 100644 collab-notebook-reproducibility-guard/make-demo-video.py create mode 100644 collab-notebook-reproducibility-guard/package.json create mode 100644 collab-notebook-reproducibility-guard/reports/clean-notebook-packet.json create mode 100644 collab-notebook-reproducibility-guard/reports/demo.mp4 create mode 100644 collab-notebook-reproducibility-guard/reports/notebook-reproducibility-report.md create mode 100644 collab-notebook-reproducibility-guard/reports/summary.svg create mode 100644 collab-notebook-reproducibility-guard/reports/unsafe-notebook-packet.json create mode 100644 collab-notebook-reproducibility-guard/reports/warning-notebook-packet.json create mode 100644 collab-notebook-reproducibility-guard/requirements-map.md create mode 100644 collab-notebook-reproducibility-guard/sample-data.js create mode 100644 collab-notebook-reproducibility-guard/test.js diff --git a/collab-notebook-reproducibility-guard/README.md b/collab-notebook-reproducibility-guard/README.md new file mode 100644 index 00000000..18cbccb7 --- /dev/null +++ b/collab-notebook-reproducibility-guard/README.md @@ -0,0 +1,31 @@ +# Collaborative Notebook Reproducibility Guard + +This module adds a dependency-free pre-acceptance guard for embedded Jupyter-style outputs in the real-time collaborative research editor. + +It checks whether notebook outputs are reproducible enough to be accepted into a shared manuscript section. The guard reviews execution order, kernel/runtime fingerprints, dependency lock hashes, random seed capture, input artifact fingerprints, stale output timestamps, unsafe rich HTML, private local paths, and section-version drift. + +## Commands + +```bash +npm test +npm run demo +npm run video +npm run check +``` + +## Outputs + +The demo writes deterministic artifacts to `reports/`: + +- `unsafe-notebook-packet.json` +- `warning-notebook-packet.json` +- `clean-notebook-packet.json` +- `notebook-reproducibility-report.md` +- `summary.svg` +- `demo.mp4` + +## Status Lanes + +- `hold_notebook_outputs`: block shared manuscript insertion and require a clean rerun. +- `stage_for_reproducibility_review`: allow review only after metadata is completed. +- `accept_notebook_outputs`: output packet is ready for collaborative acceptance. diff --git a/collab-notebook-reproducibility-guard/acceptance-notes.md b/collab-notebook-reproducibility-guard/acceptance-notes.md new file mode 100644 index 00000000..eb031794 --- /dev/null +++ b/collab-notebook-reproducibility-guard/acceptance-notes.md @@ -0,0 +1,13 @@ +# Acceptance Notes + +Validation performed locally: + +- `node test.js` +- `npm test` +- `npm run demo` +- `npm run video` +- `npm run check` +- `ffprobe` on `reports/demo.mp4` +- `git diff --check` + +The data is synthetic. No external services, credentials, private manuscripts, live collaborator data, or payment details are used. diff --git a/collab-notebook-reproducibility-guard/demo.js b/collab-notebook-reproducibility-guard/demo.js new file mode 100644 index 00000000..50952e47 --- /dev/null +++ b/collab-notebook-reproducibility-guard/demo.js @@ -0,0 +1,103 @@ +const fs = require('fs'); +const path = require('path'); + +const { assessNotebookOutputPacket } = require('./index'); +const { cleanPacket, unsafePacket, warningPacket } = require('./sample-data'); + +const reportsDir = path.join(__dirname, 'reports'); +fs.mkdirSync(reportsDir, { recursive: true }); + +const unsafeReview = assessNotebookOutputPacket(unsafePacket); +const warningReview = assessNotebookOutputPacket(warningPacket); +const cleanReview = assessNotebookOutputPacket(cleanPacket); + +writeJson('unsafe-notebook-packet.json', unsafeReview); +writeJson('warning-notebook-packet.json', warningReview); +writeJson('clean-notebook-packet.json', cleanReview); +writeMarkdownReport(unsafeReview, warningReview, cleanReview); +writeSummarySvg(unsafeReview, warningReview, cleanReview); + +console.log('Generated notebook reproducibility artifacts:'); +console.log(`- ${path.join(reportsDir, 'unsafe-notebook-packet.json')}`); +console.log(`- ${path.join(reportsDir, 'warning-notebook-packet.json')}`); +console.log(`- ${path.join(reportsDir, 'clean-notebook-packet.json')}`); +console.log(`- ${path.join(reportsDir, 'notebook-reproducibility-report.md')}`); +console.log(`- ${path.join(reportsDir, 'summary.svg')}`); + +function writeJson(filename, value) { + fs.writeFileSync(path.join(reportsDir, filename), `${JSON.stringify(value, null, 2)}\n`); +} + +function writeMarkdownReport(unsafeReview, warningReview, cleanReview) { + const lines = [ + '# Collaborative Notebook Reproducibility Guard', + '', + 'Synthetic demo packet review for issue #12.', + '', + '| Packet | Status | Cells | Blockers | Warnings | Action |', + '| --- | --- | ---: | ---: | ---: | --- |', + row('Unsafe packet', unsafeReview), + row('Warning packet', warningReview), + row('Clean packet', cleanReview), + '', + '## Reviewer Notes', + '', + '- Unsafe packets are held before shared manuscript insertion.', + '- Rich HTML outputs are sanitized and local/private paths are redacted.', + '- Clean continuous reruns with runtime, seed, dependency lock, and input fingerprints are accepted.' + ]; + + fs.writeFileSync(path.join(reportsDir, 'notebook-reproducibility-report.md'), `${lines.join('\n')}\n`); +} + +function row(label, packet) { + const summary = packet.reproducibilitySummary; + return `| ${label} | ${packet.status} | ${summary.cells} | ${summary.blockers} | ${summary.warnings} | ${primaryAction(packet)} |`; +} + +function primaryAction(packet) { + return ( + packet.actions.find((action) => action.startsWith('hold_notebook_outputs')) || + packet.actions.find((action) => action.startsWith('accept_notebook_outputs')) || + packet.actions[0] + ); +} + +function writeSummarySvg(unsafeReview, warningReview, cleanReview) { + const cards = [ + { label: 'Unsafe', packet: unsafeReview, color: '#b91c1c', x: 28 }, + { label: 'Warning', packet: warningReview, color: '#b45309', x: 318 }, + { label: 'Clean', packet: cleanReview, color: '#047857', x: 608 } + ]; + + const cardSvg = cards.map(({ label, packet, color, x }) => { + const summary = packet.reproducibilitySummary; + return [ + ``, + `${label}`, + `${escapeXml(packet.status)}`, + `Cells: ${summary.cells}`, + `Blockers: ${summary.blockers}`, + `Warnings: ${summary.warnings}` + ].join('\n'); + }).join('\n'); + + const svg = [ + '', + '', + 'Notebook Output Reproducibility Guard', + 'Pre-acceptance validation for collaborative Jupyter-style output packets', + cardSvg, + '' + ].join('\n'); + + fs.writeFileSync(path.join(reportsDir, 'summary.svg'), `${svg}\n`); +} + +function escapeXml(value) { + return String(value) + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"'); +} diff --git a/collab-notebook-reproducibility-guard/index.js b/collab-notebook-reproducibility-guard/index.js new file mode 100644 index 00000000..d6079e99 --- /dev/null +++ b/collab-notebook-reproducibility-guard/index.js @@ -0,0 +1,350 @@ +const crypto = require('crypto'); + +function assessNotebookOutputPacket(packet) { + const cells = packet.cells || []; + const orderFindings = assessExecutionOrder(cells); + const assessedCells = cells.map((cell) => assessCell(cell, packet)); + const findings = [ + ...orderFindings, + ...assessedCells.flatMap((item) => item.findings) + ].sort(compareFindings); + + const status = chooseStatus(findings); + const reviewPacket = { + packetId: packet.packetId, + workspaceId: packet.workspaceId, + manuscriptId: packet.manuscriptId, + status, + acceptanceLanes: chooseAcceptanceLanes(status), + assessedAt: packet.receivedAt, + cells: assessedCells.map((item) => item.sanitizedCell), + findings, + actions: buildActions(packet, findings), + reproducibilitySummary: buildSummary(cells, findings) + }; + + reviewPacket.auditDigest = digestPacket(reviewPacket); + return reviewPacket; +} + +function assessCell(cell, packet) { + const findings = []; + const sanitizedCell = sanitizeCellBase(cell); + + if (!cell.kernel || !cell.kernel.name || !cell.kernel.version) { + findings.push(finding({ + code: 'MISSING_KERNEL_VERSION', + severity: 'blocker', + cellId: cell.id, + message: 'Notebook output is missing the kernel name or version used to produce it.' + })); + } else if (!cell.kernel.runtimeDigest) { + findings.push(finding({ + code: 'MISSING_RUNTIME_DIGEST', + severity: 'warning', + cellId: cell.id, + message: 'Kernel version is present but the runtime digest was not captured.' + })); + } + + const expectedLock = expectedDependencyLock(packet, cell); + if (expectedLock && cell.dependencyLock?.digest !== expectedLock.digest) { + findings.push(finding({ + code: 'DEPENDENCY_LOCK_MISMATCH', + severity: 'blocker', + cellId: cell.id, + message: `Notebook output was created with dependency lock ${cell.dependencyLock?.digest || 'missing'}, expected ${expectedLock.digest}.` + })); + } + + if (cell.stochastic && !cell.randomSeed) { + findings.push(finding({ + code: 'MISSING_RANDOM_SEED', + severity: 'blocker', + cellId: cell.id, + message: 'Stochastic notebook cell output cannot be accepted without a captured random seed.' + })); + } + + const missingInputs = (cell.inputs || []).filter((input) => !input.digest); + if (missingInputs.length) { + findings.push(finding({ + code: 'MISSING_INPUT_FINGERPRINT', + severity: 'blocker', + cellId: cell.id, + message: `${missingInputs.length} notebook input artifact(s) lack a content fingerprint.` + })); + } + + if (isStaleOutput(cell, packet, expectedLock)) { + findings.push(finding({ + code: 'STALE_NOTEBOOK_OUTPUT', + severity: 'blocker', + cellId: cell.id, + message: 'Notebook output predates the current data, dependency, or manuscript section revision.' + })); + } + + if (hasUntrustedRichHtml(cell.outputs)) { + findings.push(finding({ + code: 'UNTRUSTED_RICH_HTML_OUTPUT', + severity: 'blocker', + cellId: cell.id, + message: 'Notebook rich output contains untrusted HTML or scriptable attributes.' + })); + sanitizedCell.outputs = sanitizeOutputs(cell.outputs || []); + } + + if (containsPrivatePathInOutputs(cell.outputs)) { + findings.push(finding({ + code: 'PRIVATE_OUTPUT_PATH', + severity: 'blocker', + cellId: cell.id, + message: 'Notebook output references a local or private filesystem path.' + })); + sanitizedCell.outputs = sanitizeOutputs(sanitizedCell.outputs || cell.outputs || []); + } + + const currentSectionVersion = packet.currentSectionVersions?.[cell.sectionId]; + if (currentSectionVersion && cell.sectionVersion !== currentSectionVersion) { + findings.push(finding({ + code: 'SECTION_VERSION_MISMATCH', + severity: 'blocker', + cellId: cell.id, + message: `Output targets section version ${cell.sectionVersion || 'missing'}, expected ${currentSectionVersion}.` + })); + } + + sanitizedCell.reviewState = findings.length ? 'reproducibility_review_required' : 'ready_for_collaborative_acceptance'; + return { sanitizedCell, findings }; +} + +function assessExecutionOrder(cells) { + const findings = []; + const byNotebook = new Map(); + + for (const cell of cells) { + const key = cell.notebookId || 'unknown-notebook'; + if (!byNotebook.has(key)) byNotebook.set(key, []); + byNotebook.get(key).push(cell); + } + + for (const [notebookId, notebookCells] of byNotebook.entries()) { + const counts = new Map(); + for (const cell of notebookCells) { + if (!Number.isInteger(cell.executionCount) || cell.executionCount <= 0) { + findings.push(finding({ + code: 'INVALID_EXECUTION_COUNT', + severity: 'blocker', + cellId: cell.id, + message: `Notebook ${notebookId} cell has no positive execution counter.` + })); + continue; + } + counts.set(cell.executionCount, (counts.get(cell.executionCount) || 0) + 1); + } + + for (const cell of notebookCells) { + if (counts.get(cell.executionCount) > 1) { + findings.push(finding({ + code: 'DUPLICATE_EXECUTION_COUNT', + severity: 'blocker', + cellId: cell.id, + message: `Execution count ${cell.executionCount} appears more than once in ${notebookId}.` + })); + } + } + + const ordered = notebookCells + .filter((cell) => Number.isInteger(cell.executionCount)) + .sort((a, b) => a.executionCount - b.executionCount); + for (let index = 1; index < ordered.length; index += 1) { + if (ordered[index].executionCount !== ordered[index - 1].executionCount + 1) { + findings.push(finding({ + code: 'EXECUTION_ORDER_GAP', + severity: 'blocker', + cellId: ordered[index].id, + message: `Notebook ${notebookId} output sequence is not a clean rerun from a continuous kernel.` + })); + break; + } + } + } + + return findings; +} + +function sanitizeCellBase(cell) { + const sanitized = clone(cell); + sanitized.outputs = sanitizeOutputs(cell.outputs || []); + return sanitized; +} + +function sanitizeOutputs(outputs) { + return outputs.map((output) => { + const sanitized = { ...output }; + if (typeof sanitized.content === 'string') { + sanitized.content = stripUnsafeHtml(redactPrivatePaths(sanitized.content)); + } + if (sanitized.mime === 'text/html') { + sanitized.trusted = false; + } + return sanitized; + }); +} + +function hasUntrustedRichHtml(outputs = []) { + return outputs.some((output) => ( + output.mime === 'text/html' && + (output.trusted !== true || / containsPrivatePath(output.content || '')); +} + +function containsPrivatePath(value) { + return /(?:file:\/\/|\/Users\/[^ \n<]+|\/home\/[^ \n<]+|private-lab|patient-export|restricted-dataset)/i.test(value); +} + +function redactPrivatePaths(value) { + return value + .replace(/file:\/\/[^ \n<]+/gi, '[redacted-local-path]') + .replace(/\/Users\/[^ \n<]+/g, '[redacted-local-path]') + .replace(/\/home\/[^ \n<]+/g, '[redacted-local-path]') + .replace(/private-lab\/[^\s<]+/gi, 'private-lab/[redacted]') + .replace(/restricted-dataset\/[^\s<]+/gi, 'restricted-dataset/[redacted]'); +} + +function stripUnsafeHtml(value) { + return value + .replace(/]*>[\s\S]*?<\/script>/gi, '[removed-script]') + .replace(/\son(?:error|click|load)\s*=\s*["'][^"']*["']/gi, '') + .replace(/javascript:/gi, 'blocked-javascript:'); +} + +function expectedDependencyLock(packet, cell) { + const locks = packet.acceptedDependencyLocks || {}; + return locks[cell.notebookId] || locks[cell.kernel?.name] || null; +} + +function isStaleOutput(cell, packet, expectedLock) { + const executedAt = Date.parse(cell.lastExecutedAt); + if (!executedAt) return true; + + const freshnessDates = [ + packet.currentDataRevisionAt, + packet.currentNotebookRevisionAt, + expectedLock?.updatedAt + ].map((value) => Date.parse(value)).filter(Boolean); + + return freshnessDates.some((freshAt) => executedAt < freshAt); +} + +function chooseStatus(findings) { + if (findings.some((item) => item.severity === 'blocker')) return 'hold_notebook_outputs'; + if (findings.length) return 'stage_for_reproducibility_review'; + return 'accept_notebook_outputs'; +} + +function chooseAcceptanceLanes(status) { + if (status === 'hold_notebook_outputs') { + return { + manuscriptInsertion: 'blocked', + reviewerPreview: 'sanitized', + auditRetention: 'reproducibility_hold' + }; + } + if (status === 'stage_for_reproducibility_review') { + return { + manuscriptInsertion: 'review_required', + reviewerPreview: 'watermarked', + auditRetention: 'standard' + }; + } + return { + manuscriptInsertion: 'allowed', + reviewerPreview: 'allowed', + auditRetention: 'standard' + }; +} + +function buildActions(packet, findings) { + if (!findings.length) return [`accept_notebook_outputs:${packet.packetId}`]; + + const actions = new Set(); + if (findings.some((item) => item.severity === 'blocker')) { + actions.add(`hold_notebook_outputs:${packet.packetId}`); + actions.add('rerun_notebook_from_clean_kernel'); + } + for (const item of findings) { + if (item.code === 'MISSING_RUNTIME_DIGEST') actions.add(`capture_runtime_digest:${item.cellId}`); + if (item.code === 'DEPENDENCY_LOCK_MISMATCH') actions.add(`refresh_dependency_lock:${item.cellId}`); + if (item.code === 'MISSING_RANDOM_SEED') actions.add(`record_random_seed:${item.cellId}`); + if (item.code === 'MISSING_INPUT_FINGERPRINT') actions.add(`attach_input_fingerprints:${item.cellId}`); + if (item.code === 'STALE_NOTEBOOK_OUTPUT') actions.add(`rerun_stale_cell:${item.cellId}`); + if (item.code === 'UNTRUSTED_RICH_HTML_OUTPUT') actions.add(`sanitize_rich_output:${item.cellId}`); + if (item.code === 'PRIVATE_OUTPUT_PATH') actions.add(`redact_private_output_paths:${item.cellId}`); + if (item.code === 'SECTION_VERSION_MISMATCH') actions.add(`rebase_output_to_current_section:${item.cellId}`); + if (item.code === 'EXECUTION_ORDER_GAP' || item.code === 'DUPLICATE_EXECUTION_COUNT') { + actions.add('reset_kernel_and_run_all_cells'); + } + } + return Array.from(actions).sort(); +} + +function buildSummary(cells, findings) { + const blockers = findings.filter((item) => item.severity === 'blocker').length; + const warnings = findings.filter((item) => item.severity === 'warning').length; + const notebooks = new Set(cells.map((cell) => cell.notebookId || 'unknown-notebook')); + const trustedOutputs = cells.flatMap((cell) => cell.outputs || []).filter((output) => output.trusted === true).length; + + return { + notebooks: notebooks.size, + cells: cells.length, + trustedOutputs, + blockers, + warnings + }; +} + +function finding({ code, severity, cellId, message }) { + return { code, severity, cellId, message }; +} + +function compareFindings(a, b) { + const severityRank = { blocker: 0, warning: 1, info: 2 }; + return ( + severityRank[a.severity] - severityRank[b.severity] || + String(a.cellId || '').localeCompare(String(b.cellId || '')) || + a.code.localeCompare(b.code) + ); +} + +function digestPacket(packet) { + return digestValue(stableStringify(packet)); +} + +function digestValue(value) { + return crypto.createHash('sha256').update(String(value)).digest('hex'); +} + +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 clone(value) { + return JSON.parse(JSON.stringify(value)); +} + +module.exports = { + assessNotebookOutputPacket, + sanitizeOutputs, + redactPrivatePaths, + stripUnsafeHtml +}; diff --git a/collab-notebook-reproducibility-guard/make-demo-video.py b/collab-notebook-reproducibility-guard/make-demo-video.py new file mode 100644 index 00000000..19407f04 --- /dev/null +++ b/collab-notebook-reproducibility-guard/make-demo-video.py @@ -0,0 +1,116 @@ +import subprocess +from pathlib import Path + +ROOT = Path(__file__).resolve().parent +OUT = ROOT / "reports" / "demo.mp4" +TMP = ROOT / "reports" / "video-tmp" + +TMP.mkdir(parents=True, exist_ok=True) + +slides = [ + { + "title": "Unsafe packet", + "subtitle": "Held before manuscript insertion", + "body": "Blocks stale outputs, duplicate execution counts, unsafe HTML, private paths", + "action": "Action: rerun notebook from a clean kernel", + "color": "0xb91c1c", + }, + { + "title": "Warning packet", + "subtitle": "Runtime digest review required", + "body": "Stages metadata-only gaps without blocking collaborators", + "action": "Action: capture runtime digest", + "color": "0xb45309", + }, + { + "title": "Clean packet", + "subtitle": "Accepted for collaborative use", + "body": "Requires lock hash, input fingerprints, seed, and continuous execution order", + "action": "Action: accept notebook outputs", + "color": "0x047857", + }, +] + +FONT_CANDIDATES = [ + "/System/Library/Fonts/Supplemental/Arial.ttf", + "/Library/Fonts/Arial.ttf", + "/System/Library/Fonts/Helvetica.ttc", +] +FONT = next((font for font in FONT_CANDIDATES if Path(font).exists()), None) + + +def drawtext(text, x, y, size, color="white"): + escaped = text.replace("\\", "\\\\").replace(":", "\\:").replace("'", "\\'") + parts = [] + if FONT: + parts.append(f"fontfile='{FONT}'") + parts.extend([ + f"text='{escaped}'", + f"x={x}", + f"y={y}", + f"fontsize={size}", + f"fontcolor={color}", + ]) + return "drawtext=" + ":".join(parts) + + +segments = [] +for index, slide in enumerate(slides): + segment = TMP / f"segment_{index}.mp4" + filters = [ + "drawbox=x=48:y=48:w=864:h=444:color=white@0.12:t=fill", + drawtext("Notebook Output Reproducibility Guard", 76, 78, 28), + drawtext(slide["title"], 76, 150, 58), + drawtext(slide["subtitle"], 80, 230, 32), + drawtext(slide["body"], 80, 306, 22), + drawtext(slide["action"], 80, 360, 24), + ] + subprocess.run( + [ + "ffmpeg", + "-y", + "-f", + "lavfi", + "-i", + f"color=c={slide['color']}:s=960x540:d=3", + "-vf", + ",".join(filters), + "-pix_fmt", + "yuv420p", + str(segment), + ], + cwd=str(ROOT), + check=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + segments.append(segment) + +concat_file = TMP / "segments.txt" +concat_file.write_text("".join(f"file '{segment}'\n" for segment in segments), encoding="utf-8") + +subprocess.run( + [ + "ffmpeg", + "-y", + "-f", + "concat", + "-safe", + "0", + "-i", + str(concat_file), + "-c", + "copy", + str(OUT), + ], + cwd=str(ROOT), + check=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, +) + +for file in TMP.glob("*"): + file.unlink() +TMP.rmdir() + +print(f"Wrote {OUT} ({OUT.stat().st_size} bytes)") diff --git a/collab-notebook-reproducibility-guard/package.json b/collab-notebook-reproducibility-guard/package.json new file mode 100644 index 00000000..3163504e --- /dev/null +++ b/collab-notebook-reproducibility-guard/package.json @@ -0,0 +1,12 @@ +{ + "name": "collab-notebook-reproducibility-guard", + "version": "1.0.0", + "private": true, + "description": "Dependency-free reproducibility guard for collaborative notebook outputs in research editor manuscripts.", + "scripts": { + "test": "node test.js", + "demo": "node demo.js", + "video": "python3 make-demo-video.py", + "check": "node --check index.js && node --check sample-data.js && node --check test.js && node --check demo.js" + } +} diff --git a/collab-notebook-reproducibility-guard/reports/clean-notebook-packet.json b/collab-notebook-reproducibility-guard/reports/clean-notebook-packet.json new file mode 100644 index 00000000..463b3ede --- /dev/null +++ b/collab-notebook-reproducibility-guard/reports/clean-notebook-packet.json @@ -0,0 +1,94 @@ +{ + "packetId": "notebook-output-packet-clean", + "workspaceId": "workspace-neuro-42", + "manuscriptId": "ms-neuro-response", + "status": "accept_notebook_outputs", + "acceptanceLanes": { + "manuscriptInsertion": "allowed", + "reviewerPreview": "allowed", + "auditRetention": "standard" + }, + "assessedAt": "2026-05-28T10:35:00Z", + "cells": [ + { + "id": "cell-clean-load", + "notebookId": "nb-response-model", + "sectionId": "results", + "sectionVersion": "sec-results-v9", + "executionCount": 1, + "lastExecutedAt": "2026-05-28T10:05:00Z", + "kernel": { + "name": "python", + "version": "3.11.8", + "runtimeDigest": "sha256:runtime-current" + }, + "dependencyLock": { + "manager": "pip-tools", + "digest": "sha256:lockfile-current-77a" + }, + "stochastic": false, + "inputs": [ + { + "id": "cohort-export", + "digest": "sha256:cohort-stable" + } + ], + "outputs": [ + { + "mime": "text/plain", + "content": "Loaded 128 anonymized rows from cohort artifact sha256:cohort-stable.", + "trusted": true + } + ], + "reviewState": "ready_for_collaborative_acceptance" + }, + { + "id": "cell-clean-fit", + "notebookId": "nb-response-model", + "sectionId": "results", + "sectionVersion": "sec-results-v9", + "executionCount": 2, + "lastExecutedAt": "2026-05-28T10:08:00Z", + "kernel": { + "name": "python", + "version": "3.11.8", + "runtimeDigest": "sha256:runtime-current" + }, + "dependencyLock": { + "manager": "pip-tools", + "digest": "sha256:lockfile-current-77a" + }, + "stochastic": true, + "randomSeed": { + "value": 20260528, + "capturedAt": "2026-05-28T10:07:59Z" + }, + "inputs": [ + { + "id": "cohort-export", + "digest": "sha256:cohort-stable" + } + ], + "outputs": [ + { + "mime": "text/markdown", + "content": "Model AUC: 0.82. Bootstrap CI generated with seed 20260528.", + "trusted": true + } + ], + "reviewState": "ready_for_collaborative_acceptance" + } + ], + "findings": [], + "actions": [ + "accept_notebook_outputs:notebook-output-packet-clean" + ], + "reproducibilitySummary": { + "notebooks": 1, + "cells": 2, + "trustedOutputs": 2, + "blockers": 0, + "warnings": 0 + }, + "auditDigest": "2c600c8f6698badf782674dc103ce798936b00df1db2fab78b357c18fdcd88d6" +} diff --git a/collab-notebook-reproducibility-guard/reports/demo.mp4 b/collab-notebook-reproducibility-guard/reports/demo.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..ddd07fe389745a38fa04c4a0dec4753cdd5e53d8 GIT binary patch literal 88432 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 YqaP=ODg^+S`R2f7J_{#sna})x0A&og4gdfE literal 0 HcmV?d00001 diff --git a/collab-notebook-reproducibility-guard/reports/notebook-reproducibility-report.md b/collab-notebook-reproducibility-guard/reports/notebook-reproducibility-report.md new file mode 100644 index 00000000..b534a1ec --- /dev/null +++ b/collab-notebook-reproducibility-guard/reports/notebook-reproducibility-report.md @@ -0,0 +1,15 @@ +# Collaborative Notebook Reproducibility Guard + +Synthetic demo packet review for issue #12. + +| Packet | Status | Cells | Blockers | Warnings | Action | +| --- | --- | ---: | ---: | ---: | --- | +| Unsafe packet | hold_notebook_outputs | 3 | 10 | 1 | hold_notebook_outputs:notebook-output-packet-unsafe | +| Warning packet | stage_for_reproducibility_review | 1 | 0 | 1 | capture_runtime_digest:cell-methods-summary | +| Clean packet | accept_notebook_outputs | 2 | 0 | 0 | accept_notebook_outputs:notebook-output-packet-clean | + +## Reviewer Notes + +- Unsafe packets are held before shared manuscript insertion. +- Rich HTML outputs are sanitized and local/private paths are redacted. +- Clean continuous reruns with runtime, seed, dependency lock, and input fingerprints are accepted. diff --git a/collab-notebook-reproducibility-guard/reports/summary.svg b/collab-notebook-reproducibility-guard/reports/summary.svg new file mode 100644 index 00000000..cf797f16 --- /dev/null +++ b/collab-notebook-reproducibility-guard/reports/summary.svg @@ -0,0 +1,23 @@ + + +Notebook Output Reproducibility Guard +Pre-acceptance validation for collaborative Jupyter-style output packets + +Unsafe +hold_notebook_outputs +Cells: 3 +Blockers: 10 +Warnings: 1 + +Warning +stage_for_reproducibility_review +Cells: 1 +Blockers: 0 +Warnings: 1 + +Clean +accept_notebook_outputs +Cells: 2 +Blockers: 0 +Warnings: 0 + diff --git a/collab-notebook-reproducibility-guard/reports/unsafe-notebook-packet.json b/collab-notebook-reproducibility-guard/reports/unsafe-notebook-packet.json new file mode 100644 index 00000000..6a32b0ad --- /dev/null +++ b/collab-notebook-reproducibility-guard/reports/unsafe-notebook-packet.json @@ -0,0 +1,198 @@ +{ + "packetId": "notebook-output-packet-unsafe", + "workspaceId": "workspace-neuro-42", + "manuscriptId": "ms-neuro-response", + "status": "hold_notebook_outputs", + "acceptanceLanes": { + "manuscriptInsertion": "blocked", + "reviewerPreview": "sanitized", + "auditRetention": "reproducibility_hold" + }, + "assessedAt": "2026-05-28T10:15:00Z", + "cells": [ + { + "id": "cell-load-data", + "notebookId": "nb-response-model", + "sectionId": "results", + "sectionVersion": "sec-results-v8", + "executionCount": 12, + "lastExecutedAt": "2026-05-28T09:30:00Z", + "kernel": { + "name": "python", + "version": "3.11.8" + }, + "dependencyLock": { + "manager": "pip-tools", + "digest": "sha256:lockfile-old-12b" + }, + "stochastic": false, + "inputs": [ + { + "id": "cohort-export", + "path": "restricted-dataset/cohort.csv" + } + ], + "outputs": [ + { + "mime": "text/plain", + "content": "Loaded cohort from [redacted-local-path]", + "trusted": true + } + ], + "reviewState": "reproducibility_review_required" + }, + { + "id": "cell-fit-model", + "notebookId": "nb-response-model", + "sectionId": "results", + "sectionVersion": "sec-results-v9", + "executionCount": 14, + "lastExecutedAt": "2026-05-28T10:00:00Z", + "kernel": { + "name": "python", + "version": "3.11.8", + "runtimeDigest": "sha256:runtime-current" + }, + "dependencyLock": { + "manager": "pip-tools", + "digest": "sha256:lockfile-current-77a" + }, + "stochastic": true, + "inputs": [ + { + "id": "cohort-export", + "digest": "sha256:cohort-stable" + } + ], + "outputs": [ + { + "mime": "text/html", + "content": "[removed-script]", + "trusted": false + } + ], + "reviewState": "reproducibility_review_required" + }, + { + "id": "cell-render-figure", + "notebookId": "nb-response-model", + "sectionId": "results", + "sectionVersion": "sec-results-v9", + "executionCount": 14, + "lastExecutedAt": "2026-05-28T10:04:00Z", + "kernel": { + "name": "python", + "version": "3.11.8", + "runtimeDigest": "sha256:runtime-current" + }, + "dependencyLock": { + "manager": "pip-tools", + "digest": "sha256:lockfile-current-77a" + }, + "stochastic": false, + "inputs": [ + { + "id": "model-summary", + "digest": "sha256:model-summary" + } + ], + "outputs": [ + { + "mime": "image/svg+xml", + "content": "Response model", + "trusted": true + } + ], + "reviewState": "ready_for_collaborative_acceptance" + } + ], + "findings": [ + { + "code": "DUPLICATE_EXECUTION_COUNT", + "severity": "blocker", + "cellId": "cell-fit-model", + "message": "Execution count 14 appears more than once in nb-response-model." + }, + { + "code": "EXECUTION_ORDER_GAP", + "severity": "blocker", + "cellId": "cell-fit-model", + "message": "Notebook nb-response-model output sequence is not a clean rerun from a continuous kernel." + }, + { + "code": "MISSING_RANDOM_SEED", + "severity": "blocker", + "cellId": "cell-fit-model", + "message": "Stochastic notebook cell output cannot be accepted without a captured random seed." + }, + { + "code": "UNTRUSTED_RICH_HTML_OUTPUT", + "severity": "blocker", + "cellId": "cell-fit-model", + "message": "Notebook rich output contains untrusted HTML or scriptable attributes." + }, + { + "code": "DEPENDENCY_LOCK_MISMATCH", + "severity": "blocker", + "cellId": "cell-load-data", + "message": "Notebook output was created with dependency lock sha256:lockfile-old-12b, expected sha256:lockfile-current-77a." + }, + { + "code": "MISSING_INPUT_FINGERPRINT", + "severity": "blocker", + "cellId": "cell-load-data", + "message": "1 notebook input artifact(s) lack a content fingerprint." + }, + { + "code": "PRIVATE_OUTPUT_PATH", + "severity": "blocker", + "cellId": "cell-load-data", + "message": "Notebook output references a local or private filesystem path." + }, + { + "code": "SECTION_VERSION_MISMATCH", + "severity": "blocker", + "cellId": "cell-load-data", + "message": "Output targets section version sec-results-v8, expected sec-results-v9." + }, + { + "code": "STALE_NOTEBOOK_OUTPUT", + "severity": "blocker", + "cellId": "cell-load-data", + "message": "Notebook output predates the current data, dependency, or manuscript section revision." + }, + { + "code": "DUPLICATE_EXECUTION_COUNT", + "severity": "blocker", + "cellId": "cell-render-figure", + "message": "Execution count 14 appears more than once in nb-response-model." + }, + { + "code": "MISSING_RUNTIME_DIGEST", + "severity": "warning", + "cellId": "cell-load-data", + "message": "Kernel version is present but the runtime digest was not captured." + } + ], + "actions": [ + "attach_input_fingerprints:cell-load-data", + "capture_runtime_digest:cell-load-data", + "hold_notebook_outputs:notebook-output-packet-unsafe", + "rebase_output_to_current_section:cell-load-data", + "record_random_seed:cell-fit-model", + "redact_private_output_paths:cell-load-data", + "refresh_dependency_lock:cell-load-data", + "rerun_notebook_from_clean_kernel", + "rerun_stale_cell:cell-load-data", + "reset_kernel_and_run_all_cells", + "sanitize_rich_output:cell-fit-model" + ], + "reproducibilitySummary": { + "notebooks": 1, + "cells": 3, + "trustedOutputs": 2, + "blockers": 10, + "warnings": 1 + }, + "auditDigest": "93a4bc47b453aef1692c052c2ec1493e8b7d0457e2f983f30a16abb6c74fec50" +} diff --git a/collab-notebook-reproducibility-guard/reports/warning-notebook-packet.json b/collab-notebook-reproducibility-guard/reports/warning-notebook-packet.json new file mode 100644 index 00000000..ebf63ac5 --- /dev/null +++ b/collab-notebook-reproducibility-guard/reports/warning-notebook-packet.json @@ -0,0 +1,64 @@ +{ + "packetId": "notebook-output-packet-warning", + "workspaceId": "workspace-neuro-42", + "manuscriptId": "ms-neuro-response", + "status": "stage_for_reproducibility_review", + "acceptanceLanes": { + "manuscriptInsertion": "review_required", + "reviewerPreview": "watermarked", + "auditRetention": "standard" + }, + "assessedAt": "2026-05-28T10:25:00Z", + "cells": [ + { + "id": "cell-methods-summary", + "notebookId": "nb-methods-audit", + "sectionId": "methods", + "sectionVersion": "sec-methods-v3", + "executionCount": 1, + "lastExecutedAt": "2026-05-28T10:20:00Z", + "kernel": { + "name": "python", + "version": "3.11.8" + }, + "dependencyLock": { + "manager": "pip-tools", + "digest": "sha256:lockfile-methods" + }, + "stochastic": false, + "inputs": [ + { + "id": "protocol-yaml", + "digest": "sha256:protocol-v3" + } + ], + "outputs": [ + { + "mime": "text/markdown", + "content": "Protocol audit matched the locked methods section.", + "trusted": true + } + ], + "reviewState": "reproducibility_review_required" + } + ], + "findings": [ + { + "code": "MISSING_RUNTIME_DIGEST", + "severity": "warning", + "cellId": "cell-methods-summary", + "message": "Kernel version is present but the runtime digest was not captured." + } + ], + "actions": [ + "capture_runtime_digest:cell-methods-summary" + ], + "reproducibilitySummary": { + "notebooks": 1, + "cells": 1, + "trustedOutputs": 1, + "blockers": 0, + "warnings": 1 + }, + "auditDigest": "d9019926c34ff26cfb97dc1caad437a788edd94336aad7efc3e48042846a9fe8" +} diff --git a/collab-notebook-reproducibility-guard/requirements-map.md b/collab-notebook-reproducibility-guard/requirements-map.md new file mode 100644 index 00000000..8bfc5249 --- /dev/null +++ b/collab-notebook-reproducibility-guard/requirements-map.md @@ -0,0 +1,15 @@ +# Requirements Map + +Issue #12 asks for a real-time collaborative research editor with embedded Jupyter notebooks, version history, and shared review workflows. + +This slice covers the notebook-output acceptance boundary: + +| Issue #12 requirement | Coverage in this module | +| --- | --- | +| Embedded Jupyter notebooks | Validates Jupyter-style cell output packets before manuscript insertion | +| Real-time collaboration | Produces deterministic accept/review/hold lanes for collaborators | +| Version history and autosave | Checks output timestamps against current data, notebook, dependency, and section revisions | +| Inline review workflow | Emits reviewer actions and audit digests for reproducibility remediation | +| Scientific rigor | Requires runtime, dependency, seed, input, and execution-order evidence before acceptance | + +Non-overlap: this is focused on notebook output reproducibility. It does not duplicate clipboard/import provenance, local cache privacy, reference merge, data availability, LaTeX macro safety, presence liveness, suggestion provenance, or broad editor scaffolding. diff --git a/collab-notebook-reproducibility-guard/sample-data.js b/collab-notebook-reproducibility-guard/sample-data.js new file mode 100644 index 00000000..a7be06ae --- /dev/null +++ b/collab-notebook-reproducibility-guard/sample-data.js @@ -0,0 +1,235 @@ +const unsafePacket = { + packetId: 'notebook-output-packet-unsafe', + workspaceId: 'workspace-neuro-42', + manuscriptId: 'ms-neuro-response', + receivedAt: '2026-05-28T10:15:00Z', + currentDataRevisionAt: '2026-05-28T09:40:00Z', + currentNotebookRevisionAt: '2026-05-28T09:45:00Z', + currentSectionVersions: { + results: 'sec-results-v9' + }, + acceptedDependencyLocks: { + 'nb-response-model': { + digest: 'sha256:lockfile-current-77a', + updatedAt: '2026-05-28T09:50:00Z' + } + }, + cells: [ + { + id: 'cell-load-data', + notebookId: 'nb-response-model', + sectionId: 'results', + sectionVersion: 'sec-results-v8', + executionCount: 12, + lastExecutedAt: '2026-05-28T09:30:00Z', + kernel: { + name: 'python', + version: '3.11.8' + }, + dependencyLock: { + manager: 'pip-tools', + digest: 'sha256:lockfile-old-12b' + }, + stochastic: false, + inputs: [ + { id: 'cohort-export', path: 'restricted-dataset/cohort.csv' } + ], + outputs: [ + { + mime: 'text/plain', + content: 'Loaded cohort from /Users/alex/private-lab/patient-export/cohort.csv', + trusted: true + } + ] + }, + { + id: 'cell-fit-model', + notebookId: 'nb-response-model', + sectionId: 'results', + sectionVersion: 'sec-results-v9', + executionCount: 14, + lastExecutedAt: '2026-05-28T10:00:00Z', + kernel: { + name: 'python', + version: '3.11.8', + runtimeDigest: 'sha256:runtime-current' + }, + dependencyLock: { + manager: 'pip-tools', + digest: 'sha256:lockfile-current-77a' + }, + stochastic: true, + inputs: [ + { id: 'cohort-export', digest: 'sha256:cohort-stable' } + ], + outputs: [ + { + mime: 'text/html', + content: '', + trusted: false + } + ] + }, + { + id: 'cell-render-figure', + notebookId: 'nb-response-model', + sectionId: 'results', + sectionVersion: 'sec-results-v9', + executionCount: 14, + lastExecutedAt: '2026-05-28T10:04:00Z', + kernel: { + name: 'python', + version: '3.11.8', + runtimeDigest: 'sha256:runtime-current' + }, + dependencyLock: { + manager: 'pip-tools', + digest: 'sha256:lockfile-current-77a' + }, + stochastic: false, + inputs: [ + { id: 'model-summary', digest: 'sha256:model-summary' } + ], + outputs: [ + { + mime: 'image/svg+xml', + content: 'Response model', + trusted: true + } + ] + } + ] +}; + +const warningPacket = { + packetId: 'notebook-output-packet-warning', + workspaceId: 'workspace-neuro-42', + manuscriptId: 'ms-neuro-response', + receivedAt: '2026-05-28T10:25:00Z', + currentDataRevisionAt: '2026-05-28T09:40:00Z', + currentNotebookRevisionAt: '2026-05-28T09:45:00Z', + currentSectionVersions: { + methods: 'sec-methods-v3' + }, + acceptedDependencyLocks: { + 'nb-methods-audit': { + digest: 'sha256:lockfile-methods', + updatedAt: '2026-05-28T09:41:00Z' + } + }, + cells: [ + { + id: 'cell-methods-summary', + notebookId: 'nb-methods-audit', + sectionId: 'methods', + sectionVersion: 'sec-methods-v3', + executionCount: 1, + lastExecutedAt: '2026-05-28T10:20:00Z', + kernel: { + name: 'python', + version: '3.11.8' + }, + dependencyLock: { + manager: 'pip-tools', + digest: 'sha256:lockfile-methods' + }, + stochastic: false, + inputs: [ + { id: 'protocol-yaml', digest: 'sha256:protocol-v3' } + ], + outputs: [ + { + mime: 'text/markdown', + content: 'Protocol audit matched the locked methods section.', + trusted: true + } + ] + } + ] +}; + +const cleanPacket = { + packetId: 'notebook-output-packet-clean', + workspaceId: 'workspace-neuro-42', + manuscriptId: 'ms-neuro-response', + receivedAt: '2026-05-28T10:35:00Z', + currentDataRevisionAt: '2026-05-28T09:40:00Z', + currentNotebookRevisionAt: '2026-05-28T09:45:00Z', + currentSectionVersions: { + results: 'sec-results-v9' + }, + acceptedDependencyLocks: { + 'nb-response-model': { + digest: 'sha256:lockfile-current-77a', + updatedAt: '2026-05-28T09:50:00Z' + } + }, + cells: [ + { + id: 'cell-clean-load', + notebookId: 'nb-response-model', + sectionId: 'results', + sectionVersion: 'sec-results-v9', + executionCount: 1, + lastExecutedAt: '2026-05-28T10:05:00Z', + kernel: { + name: 'python', + version: '3.11.8', + runtimeDigest: 'sha256:runtime-current' + }, + dependencyLock: { + manager: 'pip-tools', + digest: 'sha256:lockfile-current-77a' + }, + stochastic: false, + inputs: [ + { id: 'cohort-export', digest: 'sha256:cohort-stable' } + ], + outputs: [ + { + mime: 'text/plain', + content: 'Loaded 128 anonymized rows from cohort artifact sha256:cohort-stable.', + trusted: true + } + ] + }, + { + id: 'cell-clean-fit', + notebookId: 'nb-response-model', + sectionId: 'results', + sectionVersion: 'sec-results-v9', + executionCount: 2, + lastExecutedAt: '2026-05-28T10:08:00Z', + kernel: { + name: 'python', + version: '3.11.8', + runtimeDigest: 'sha256:runtime-current' + }, + dependencyLock: { + manager: 'pip-tools', + digest: 'sha256:lockfile-current-77a' + }, + stochastic: true, + randomSeed: { + value: 20260528, + capturedAt: '2026-05-28T10:07:59Z' + }, + inputs: [ + { id: 'cohort-export', digest: 'sha256:cohort-stable' } + ], + outputs: [ + { + mime: 'text/markdown', + content: 'Model AUC: 0.82. Bootstrap CI generated with seed 20260528.', + trusted: true + } + ] + } + ] +}; + +module.exports = { + unsafePacket, + warningPacket, + cleanPacket +}; diff --git a/collab-notebook-reproducibility-guard/test.js b/collab-notebook-reproducibility-guard/test.js new file mode 100644 index 00000000..0132736e --- /dev/null +++ b/collab-notebook-reproducibility-guard/test.js @@ -0,0 +1,81 @@ +const assert = require('assert'); + +const { assessNotebookOutputPacket } = require('./index'); +const { cleanPacket, unsafePacket, warningPacket } = require('./sample-data'); + +function findingCodes(packet) { + return packet.findings.map((finding) => finding.code).sort(); +} + +function testHoldsUnsafeNotebookOutputsBeforeManuscriptAcceptance() { + const packet = assessNotebookOutputPacket(unsafePacket); + + assert.equal(packet.status, 'hold_notebook_outputs'); + assert.equal(packet.acceptanceLanes.manuscriptInsertion, 'blocked'); + assert.equal(packet.acceptanceLanes.reviewerPreview, 'sanitized'); + assert.equal(packet.acceptanceLanes.auditRetention, 'reproducibility_hold'); + assert.deepEqual(findingCodes(packet), [ + 'DEPENDENCY_LOCK_MISMATCH', + 'DUPLICATE_EXECUTION_COUNT', + 'DUPLICATE_EXECUTION_COUNT', + 'EXECUTION_ORDER_GAP', + 'MISSING_INPUT_FINGERPRINT', + 'MISSING_RANDOM_SEED', + 'MISSING_RUNTIME_DIGEST', + 'PRIVATE_OUTPUT_PATH', + 'SECTION_VERSION_MISMATCH', + 'STALE_NOTEBOOK_OUTPUT', + 'UNTRUSTED_RICH_HTML_OUTPUT' + ]); + + assert.ok(packet.actions.includes('hold_notebook_outputs:notebook-output-packet-unsafe')); + assert.ok(packet.actions.includes('rerun_notebook_from_clean_kernel')); + assert.ok(packet.actions.includes('reset_kernel_and_run_all_cells')); + assert.ok(packet.actions.includes('sanitize_rich_output:cell-fit-model')); + assert.ok(packet.actions.includes('redact_private_output_paths:cell-load-data')); + assert.match(packet.auditDigest, /^[a-f0-9]{64}$/); + + const loadCell = packet.cells.find((cell) => cell.id === 'cell-load-data'); + const modelCell = packet.cells.find((cell) => cell.id === 'cell-fit-model'); + assert.match(loadCell.outputs[0].content, /\[redacted-local-path]/); + assert.equal(modelCell.outputs[0].trusted, false); + assert.doesNotMatch(modelCell.outputs[0].content, / cell.reviewState === 'ready_for_collaborative_acceptance'), true); + assert.match(packet.auditDigest, /^[a-f0-9]{64}$/); +} + +const tests = [ + testHoldsUnsafeNotebookOutputsBeforeManuscriptAcceptance, + testStagesWarningOnlyPacketForReproducibilityReview, + testAcceptsCleanContinuousRerunPacket +]; + +for (const test of tests) { + test(); +} + +console.log(`collab-notebook-reproducibility-guard tests passed (${tests.length})`); From 9f7dd32a80ca89cb70015db4ae786b80c15f097e Mon Sep 17 00:00:00 2001 From: Joey Cera <89586838+jtc268@users.noreply.github.com> Date: Thu, 28 May 2026 02:48:02 -0400 Subject: [PATCH 2/2] Strengthen notebook guard verification --- .../README.md | 1 + .../acceptance-notes.md | 2 +- collab-notebook-reproducibility-guard/demo.js | 51 +++++++++++++++++++ .../package.json | 3 +- .../reports/maintainer-verification-packet.md | 37 ++++++++++++++ 5 files changed, 92 insertions(+), 2 deletions(-) create mode 100644 collab-notebook-reproducibility-guard/reports/maintainer-verification-packet.md diff --git a/collab-notebook-reproducibility-guard/README.md b/collab-notebook-reproducibility-guard/README.md index 18cbccb7..5ed758d4 100644 --- a/collab-notebook-reproducibility-guard/README.md +++ b/collab-notebook-reproducibility-guard/README.md @@ -21,6 +21,7 @@ The demo writes deterministic artifacts to `reports/`: - `warning-notebook-packet.json` - `clean-notebook-packet.json` - `notebook-reproducibility-report.md` +- `maintainer-verification-packet.md` - `summary.svg` - `demo.mp4` diff --git a/collab-notebook-reproducibility-guard/acceptance-notes.md b/collab-notebook-reproducibility-guard/acceptance-notes.md index eb031794..5a0e08a1 100644 --- a/collab-notebook-reproducibility-guard/acceptance-notes.md +++ b/collab-notebook-reproducibility-guard/acceptance-notes.md @@ -6,7 +6,7 @@ Validation performed locally: - `npm test` - `npm run demo` - `npm run video` -- `npm run check` +- `npm run check` (syntax, tests, report generation, and demo video generation) - `ffprobe` on `reports/demo.mp4` - `git diff --check` diff --git a/collab-notebook-reproducibility-guard/demo.js b/collab-notebook-reproducibility-guard/demo.js index 50952e47..91c89872 100644 --- a/collab-notebook-reproducibility-guard/demo.js +++ b/collab-notebook-reproducibility-guard/demo.js @@ -15,6 +15,7 @@ writeJson('unsafe-notebook-packet.json', unsafeReview); writeJson('warning-notebook-packet.json', warningReview); writeJson('clean-notebook-packet.json', cleanReview); writeMarkdownReport(unsafeReview, warningReview, cleanReview); +writeMaintainerVerificationPacket(unsafeReview, warningReview, cleanReview); writeSummarySvg(unsafeReview, warningReview, cleanReview); console.log('Generated notebook reproducibility artifacts:'); @@ -22,6 +23,7 @@ console.log(`- ${path.join(reportsDir, 'unsafe-notebook-packet.json')}`); console.log(`- ${path.join(reportsDir, 'warning-notebook-packet.json')}`); console.log(`- ${path.join(reportsDir, 'clean-notebook-packet.json')}`); console.log(`- ${path.join(reportsDir, 'notebook-reproducibility-report.md')}`); +console.log(`- ${path.join(reportsDir, 'maintainer-verification-packet.md')}`); console.log(`- ${path.join(reportsDir, 'summary.svg')}`); function writeJson(filename, value) { @@ -55,6 +57,55 @@ function row(label, packet) { return `| ${label} | ${packet.status} | ${summary.cells} | ${summary.blockers} | ${summary.warnings} | ${primaryAction(packet)} |`; } +function writeMaintainerVerificationPacket(unsafeReview, warningReview, cleanReview) { + const blockerCodes = unsafeReview.findings + .filter((finding) => finding.severity === 'blocker') + .map((finding) => finding.code); + const sanitizedLoadCell = unsafeReview.cells.find((cell) => cell.id === 'cell-load-data'); + const sanitizedModelCell = unsafeReview.cells.find((cell) => cell.id === 'cell-fit-model'); + const lines = [ + '# Maintainer Verification Packet', + '', + 'This packet gives reviewers a compact checklist for issue #12 acceptance.', + '', + '## Acceptance Evidence', + '', + `- Unsafe packet lane: ${unsafeReview.status}`, + `- Warning packet lane: ${warningReview.status}`, + `- Clean packet lane: ${cleanReview.status}`, + `- Unsafe blocker count: ${unsafeReview.reproducibilitySummary.blockers}`, + `- Warning count: ${warningReview.reproducibilitySummary.warnings}`, + `- Clean accepted cells: ${cleanReview.reproducibilitySummary.cells}`, + '', + '## Guard Coverage', + '', + '| Requirement | Evidence |', + '| --- | --- |', + `| Execution-order continuity | ${blockerCodes.includes('EXECUTION_ORDER_GAP') ? 'covered by unsafe packet' : 'missing'} |`, + `| Dependency/version integrity | ${blockerCodes.includes('DEPENDENCY_LOCK_MISMATCH') ? 'covered by unsafe packet' : 'missing'} |`, + `| Random-seed capture | ${blockerCodes.includes('MISSING_RANDOM_SEED') ? 'covered by unsafe packet' : 'missing'} |`, + `| Input artifact fingerprints | ${blockerCodes.includes('MISSING_INPUT_FINGERPRINT') ? 'covered by unsafe packet' : 'missing'} |`, + `| Stale output detection | ${blockerCodes.includes('STALE_NOTEBOOK_OUTPUT') ? 'covered by unsafe packet' : 'missing'} |`, + `| Section-version drift | ${blockerCodes.includes('SECTION_VERSION_MISMATCH') ? 'covered by unsafe packet' : 'missing'} |`, + `| Rich HTML sanitization | ${sanitizedModelCell?.outputs?.[0]?.trusted === false ? 'sanitized and marked untrusted' : 'missing'} |`, + `| Private path redaction | ${/\[redacted-local-path]/.test(sanitizedLoadCell?.outputs?.[0]?.content || '') ? 'redacted in output packet' : 'missing'} |`, + '', + '## Deterministic Digests', + '', + `- Unsafe packet digest: ${unsafeReview.auditDigest}`, + `- Warning packet digest: ${warningReview.auditDigest}`, + `- Clean packet digest: ${cleanReview.auditDigest}`, + '', + '## Local Command', + '', + '```bash', + 'npm run check', + '```' + ]; + + fs.writeFileSync(path.join(reportsDir, 'maintainer-verification-packet.md'), `${lines.join('\n')}\n`); +} + function primaryAction(packet) { return ( packet.actions.find((action) => action.startsWith('hold_notebook_outputs')) || diff --git a/collab-notebook-reproducibility-guard/package.json b/collab-notebook-reproducibility-guard/package.json index 3163504e..fac878e2 100644 --- a/collab-notebook-reproducibility-guard/package.json +++ b/collab-notebook-reproducibility-guard/package.json @@ -7,6 +7,7 @@ "test": "node test.js", "demo": "node demo.js", "video": "python3 make-demo-video.py", - "check": "node --check index.js && node --check sample-data.js && node --check test.js && node --check demo.js" + "syntax": "node --check index.js && node --check sample-data.js && node --check test.js && node --check demo.js", + "check": "npm run syntax && npm test && npm run demo && npm run video" } } diff --git a/collab-notebook-reproducibility-guard/reports/maintainer-verification-packet.md b/collab-notebook-reproducibility-guard/reports/maintainer-verification-packet.md new file mode 100644 index 00000000..8567faa5 --- /dev/null +++ b/collab-notebook-reproducibility-guard/reports/maintainer-verification-packet.md @@ -0,0 +1,37 @@ +# Maintainer Verification Packet + +This packet gives reviewers a compact checklist for issue #12 acceptance. + +## Acceptance Evidence + +- Unsafe packet lane: hold_notebook_outputs +- Warning packet lane: stage_for_reproducibility_review +- Clean packet lane: accept_notebook_outputs +- Unsafe blocker count: 10 +- Warning count: 1 +- Clean accepted cells: 2 + +## Guard Coverage + +| Requirement | Evidence | +| --- | --- | +| Execution-order continuity | covered by unsafe packet | +| Dependency/version integrity | covered by unsafe packet | +| Random-seed capture | covered by unsafe packet | +| Input artifact fingerprints | covered by unsafe packet | +| Stale output detection | covered by unsafe packet | +| Section-version drift | covered by unsafe packet | +| Rich HTML sanitization | sanitized and marked untrusted | +| Private path redaction | redacted in output packet | + +## Deterministic Digests + +- Unsafe packet digest: 93a4bc47b453aef1692c052c2ec1493e8b7d0457e2f983f30a16abb6c74fec50 +- Warning packet digest: d9019926c34ff26cfb97dc1caad437a788edd94336aad7efc3e48042846a9fe8 +- Clean packet digest: 2c600c8f6698badf782674dc103ce798936b00df1db2fab78b357c18fdcd88d6 + +## Local Command + +```bash +npm run check +```