diff --git a/scientific-bounty-deadline-fairness-guard/README.md b/scientific-bounty-deadline-fairness-guard/README.md
new file mode 100644
index 00000000..33cbe1e9
--- /dev/null
+++ b/scientific-bounty-deadline-fairness-guard/README.md
@@ -0,0 +1,28 @@
+# Scientific Bounty Deadline Fairness Guard
+
+Focused slice for SCIBASE issue #18, Scientific Bounty System.
+
+This module evaluates deadline extensions, timezone cutoffs, late submissions, and solver notice parity before a scientific bounty challenge proceeds to arbitration or award release. It is intentionally narrower than general intake, scoring, payout routing, evidence freeze, anonymous review packets, or post-closeout retention.
+
+## What it checks
+
+- Challenge deadlines are timezone-explicit and normalized before submission decisions are made.
+- Sponsor deadline extensions are approved, published, and scoped to all eligible solver teams.
+- Every eligible team receives the same new deadline inside the configured notice SLA.
+- Submissions received after the original deadline are accepted only when a valid public extension covers them.
+- Submissions received after the current deadline are rejected or held for arbitration if they were accepted.
+- Evidence-freeze windows are reopened with a new snapshot instead of mutating the original frozen record.
+
+## Local verification
+
+```bash
+node scientific-bounty-deadline-fairness-guard/test.js
+node scientific-bounty-deadline-fairness-guard/demo.js
+node scientific-bounty-deadline-fairness-guard/make-demo-video.js
+```
+
+Generated reviewer artifacts are written to `scientific-bounty-deadline-fairness-guard/reports/`.
+
+## Safety
+
+The fixtures are synthetic. The module does not call payment processors, use private challenge data, touch credentials, or perform any external write action.
diff --git a/scientific-bounty-deadline-fairness-guard/demo.js b/scientific-bounty-deadline-fairness-guard/demo.js
new file mode 100644
index 00000000..b797c0bb
--- /dev/null
+++ b/scientific-bounty-deadline-fairness-guard/demo.js
@@ -0,0 +1,24 @@
+const fs = require("fs");
+const path = require("path");
+const { challenges } = require("./sample-data");
+const { evaluateChallenges, renderMarkdownReport, renderSvgReport } = require("./index");
+
+function ensureDir(dir) {
+ fs.mkdirSync(dir, { recursive: true });
+}
+
+function runDemo() {
+ const report = evaluateChallenges(challenges);
+ const reportDir = path.join(__dirname, "reports");
+ ensureDir(reportDir);
+
+ fs.writeFileSync(path.join(reportDir, "deadline-fairness-review.json"), `${JSON.stringify(report, null, 2)}\n`);
+ fs.writeFileSync(path.join(reportDir, "deadline-fairness-review.md"), renderMarkdownReport(report));
+ fs.writeFileSync(path.join(reportDir, "deadline-fairness-summary.svg"), renderSvgReport(report));
+
+ console.log("deadline fairness demo generated");
+ console.log(`decision summary: ${JSON.stringify(report.summary)}`);
+ console.log(`reports: ${reportDir}`);
+}
+
+runDemo();
diff --git a/scientific-bounty-deadline-fairness-guard/index.js b/scientific-bounty-deadline-fairness-guard/index.js
new file mode 100644
index 00000000..237796e6
--- /dev/null
+++ b/scientific-bounty-deadline-fairness-guard/index.js
@@ -0,0 +1,443 @@
+const HOUR_MS = 60 * 60 * 1000;
+const MINUTE_MS = 60 * 1000;
+
+function hasExplicitTimezone(value) {
+ return typeof value === "string" && /(?:Z|[+-]\d{2}:\d{2})$/.test(value);
+}
+
+function parseTime(value, field, issues, context) {
+ if (!value || typeof value !== "string") {
+ issues.push({
+ severity: "critical",
+ code: "missing-time",
+ field,
+ context,
+ message: `${field} is missing or not a string.`,
+ });
+ return Number.NaN;
+ }
+
+ if (!hasExplicitTimezone(value)) {
+ issues.push({
+ severity: "high",
+ code: "ambiguous-timezone",
+ field,
+ context,
+ value,
+ message: `${field} must include Z or an explicit UTC offset before deadline decisions are made.`,
+ });
+ }
+
+ const parsed = Date.parse(value);
+ if (Number.isNaN(parsed)) {
+ issues.push({
+ severity: "critical",
+ code: "invalid-time",
+ field,
+ context,
+ value,
+ message: `${field} is not parseable as a timestamp.`,
+ });
+ }
+
+ return parsed;
+}
+
+function eligibleTeams(challenge) {
+ return (challenge.teams || []).filter((team) => team.eligible !== false);
+}
+
+function latestExtensionForCurrentDeadline(challenge, issues) {
+ const currentDeadline = challenge.currentDeadline;
+ const matching = (challenge.extensionEvents || []).filter((event) => event.newDeadline === currentDeadline);
+ if (matching.length === 0) {
+ if (challenge.currentDeadline !== challenge.originalDeadline) {
+ issues.push({
+ severity: "critical",
+ code: "missing-extension-event",
+ message: "Current deadline differs from the original deadline but no matching extension event exists.",
+ });
+ }
+ return null;
+ }
+
+ return matching[matching.length - 1];
+}
+
+function evaluateExtension(challenge, originalMs, currentMs, issues, actions) {
+ if (!(currentMs > originalMs)) {
+ return { extensionRequired: false, valid: true, event: null, noticeCoverage: [] };
+ }
+
+ const event = latestExtensionForCurrentDeadline(challenge, issues);
+ if (!event) {
+ actions.push("Hold arbitration until the deadline change has an approved extension event.");
+ return { extensionRequired: true, valid: false, event: null, noticeCoverage: [] };
+ }
+
+ const approvedAt = parseTime(event.approvedAt, "extension.approvedAt", issues, event.id);
+ const publishedAt = parseTime(event.publishedAt, "extension.publishedAt", issues, event.id);
+ const eventDeadlineMs = parseTime(event.newDeadline, "extension.newDeadline", issues, event.id);
+ let valid = true;
+
+ if (!event.approvedBy) {
+ valid = false;
+ issues.push({
+ severity: "critical",
+ code: "missing-extension-approval",
+ context: event.id,
+ message: "Deadline extension is missing an approving sponsor, arbiter, or challenge admin.",
+ });
+ }
+
+ if (event.scope !== "all-eligible-teams") {
+ valid = false;
+ issues.push({
+ severity: "critical",
+ code: "private-extension",
+ context: event.id,
+ message: "Deadline extension is not scoped to all eligible teams.",
+ });
+ }
+
+ if (approvedAt > originalMs) {
+ valid = false;
+ issues.push({
+ severity: "high",
+ code: "retroactive-extension-approval",
+ context: event.id,
+ message: "Extension was approved after the original cutoff; arbitration needs an explicit fairness review.",
+ });
+ }
+
+ if (publishedAt > originalMs) {
+ valid = false;
+ issues.push({
+ severity: "high",
+ code: "extension-published-after-cutoff",
+ context: event.id,
+ message: "Extension was published after the original cutoff, so some solvers could have stopped work early.",
+ });
+ }
+
+ if (eventDeadlineMs !== currentMs) {
+ valid = false;
+ issues.push({
+ severity: "critical",
+ code: "deadline-event-mismatch",
+ context: event.id,
+ message: "Extension event deadline does not match the active challenge deadline.",
+ });
+ }
+
+ const teams = eligibleTeams(challenge);
+ const noticesByTeam = new Map((event.notices || []).map((notice) => [notice.teamId, notice]));
+ const noticeCoverage = teams.map((team) => {
+ const notice = noticesByTeam.get(team.id);
+ if (!notice) {
+ valid = false;
+ issues.push({
+ severity: "critical",
+ code: "missing-team-notice",
+ context: `${event.id}:${team.id}`,
+ message: `${team.name} did not receive the deadline-extension notice.`,
+ });
+ return { teamId: team.id, status: "missing" };
+ }
+
+ const notifiedAt = parseTime(notice.notifiedAt, "notice.notifiedAt", issues, `${event.id}:${team.id}`);
+ const visibleDeadlineMs = parseTime(
+ notice.visibleDeadline,
+ "notice.visibleDeadline",
+ issues,
+ `${event.id}:${team.id}`,
+ );
+ const lateByMinutes = Math.max(0, Math.ceil((notifiedAt - publishedAt) / MINUTE_MS));
+ const noticeStatus = lateByMinutes > (challenge.noticeSlaMinutes || 60) ? "late" : "on-time";
+
+ if (noticeStatus === "late") {
+ valid = false;
+ issues.push({
+ severity: "high",
+ code: "late-team-notice",
+ context: `${event.id}:${team.id}`,
+ message: `${team.name} received the extension notice ${lateByMinutes} minutes after publication.`,
+ });
+ }
+
+ if (visibleDeadlineMs !== currentMs) {
+ valid = false;
+ issues.push({
+ severity: "critical",
+ code: "inconsistent-visible-deadline",
+ context: `${event.id}:${team.id}`,
+ message: `${team.name} saw a different deadline than the active challenge deadline.`,
+ });
+ }
+
+ return {
+ teamId: team.id,
+ status: noticeStatus,
+ lateByMinutes,
+ };
+ });
+
+ if (!challenge.freezeWindow || !challenge.freezeWindow.originalSnapshotHash) {
+ valid = false;
+ issues.push({
+ severity: "high",
+ code: "missing-original-freeze",
+ message: "Original evidence freeze snapshot is missing.",
+ });
+ }
+
+ if (!challenge.freezeWindow || !challenge.freezeWindow.reopenedAt || !challenge.freezeWindow.reopenedSnapshotHash) {
+ valid = false;
+ issues.push({
+ severity: "high",
+ code: "missing-extension-freeze-reopen",
+ message: "Deadline extension did not create a new freeze snapshot for the extended window.",
+ });
+ }
+
+ if (!valid) {
+ actions.push("Hold award release until every eligible team has equal extension evidence.");
+ }
+
+ return { extensionRequired: true, valid, event, noticeCoverage };
+}
+
+function evaluateSubmissions(challenge, originalMs, currentMs, extensionValid, issues, actions) {
+ return (challenge.submissions || []).map((submission) => {
+ const submittedAtMs = parseTime(submission.submittedAt, "submission.submittedAt", issues, submission.id);
+ let decision = "accept-original-window";
+ let reason = "Submitted before the original cutoff.";
+
+ if (submittedAtMs > currentMs) {
+ decision = submission.status === "accepted" ? "hold-accepted-after-current-cutoff" : "reject-late";
+ reason = "Submitted after the active challenge cutoff.";
+ if (submission.status === "accepted") {
+ issues.push({
+ severity: "critical",
+ code: "accepted-after-current-cutoff",
+ context: submission.id,
+ message: `${submission.id} was accepted after the current challenge deadline.`,
+ });
+ actions.push(`Remove ${submission.id} from scoring or run an explicit arbitration exception.`);
+ }
+ } else if (submittedAtMs > originalMs) {
+ if (extensionValid) {
+ decision = "accept-valid-extension-window";
+ reason = "Submitted after the original cutoff but inside a valid public extension.";
+ } else if (submission.status === "accepted") {
+ decision = "hold-accepted-under-invalid-extension";
+ reason = "Submission used an extension that failed fairness checks.";
+ issues.push({
+ severity: "critical",
+ code: "accepted-under-invalid-extension",
+ context: submission.id,
+ message: `${submission.id} was accepted after the original cutoff without a valid equal extension.`,
+ });
+ } else {
+ decision = "reject-late";
+ reason = "Submitted after the original cutoff and no valid extension applies.";
+ }
+ }
+
+ return {
+ submissionId: submission.id,
+ teamId: submission.teamId,
+ submittedAt: submission.submittedAt,
+ status: submission.status,
+ decision,
+ reason,
+ };
+ });
+}
+
+function scoreFromIssues(issues) {
+ const weights = {
+ critical: 35,
+ high: 18,
+ medium: 8,
+ low: 3,
+ };
+ const deduction = issues.reduce((sum, issue) => sum + (weights[issue.severity] || 5), 0);
+ return Math.max(0, 100 - deduction);
+}
+
+function decisionFromIssues(issues, submissionDecisions) {
+ const hasCritical = issues.some((issue) => issue.severity === "critical");
+ const hasHigh = issues.some((issue) => issue.severity === "high");
+ const hasRejectableLate = submissionDecisions.some((submission) => submission.decision === "reject-late");
+
+ if (hasCritical) return "hold-arbitration";
+ if (hasHigh) return "needs-fairness-review";
+ if (hasRejectableLate) return "reject-late-submissions";
+ return "clear-for-scoring";
+}
+
+function evaluateChallenge(challenge) {
+ const issues = [];
+ const actions = [];
+ const originalMs = parseTime(challenge.originalDeadline, "originalDeadline", issues, challenge.id);
+ const currentMs = parseTime(challenge.currentDeadline, "currentDeadline", issues, challenge.id);
+
+ if (currentMs < originalMs) {
+ issues.push({
+ severity: "critical",
+ code: "deadline-shortened",
+ message: "Current deadline is earlier than the original deadline.",
+ });
+ actions.push("Restore the original cutoff or collect explicit consent from all eligible teams.");
+ }
+
+ const extension = evaluateExtension(challenge, originalMs, currentMs, issues, actions);
+ const submissions = evaluateSubmissions(challenge, originalMs, currentMs, extension.valid, issues, actions);
+ const score = scoreFromIssues(issues);
+ const decision = decisionFromIssues(issues, submissions);
+
+ if (actions.length === 0 && decision === "clear-for-scoring") {
+ actions.push("Proceed to scoring with the normalized current deadline.");
+ }
+
+ return {
+ challengeId: challenge.id,
+ title: challenge.title,
+ originalDeadline: challenge.originalDeadline,
+ currentDeadline: challenge.currentDeadline,
+ decision,
+ fairnessScore: score,
+ extension,
+ submissions,
+ issueCounts: issues.reduce((counts, issue) => {
+ counts[issue.severity] = (counts[issue.severity] || 0) + 1;
+ return counts;
+ }, {}),
+ issues,
+ actions: Array.from(new Set(actions)),
+ };
+}
+
+function evaluateChallenges(challenges) {
+ const results = challenges.map(evaluateChallenge);
+ const summary = {
+ challengeCount: results.length,
+ clearForScoring: results.filter((result) => result.decision === "clear-for-scoring").length,
+ heldForArbitration: results.filter((result) => result.decision === "hold-arbitration").length,
+ needsFairnessReview: results.filter((result) => result.decision === "needs-fairness-review").length,
+ rejectLateSubmissionSets: results.filter((result) => result.decision === "reject-late-submissions").length,
+ averageFairnessScore: Math.round(results.reduce((sum, result) => sum + result.fairnessScore, 0) / results.length),
+ };
+
+ return {
+ generatedAt: new Date("2026-05-28T00:00:00Z").toISOString(),
+ requirementMap: [
+ "Challenge posting portal: validates timeline and extension policy before sponsor changes are published.",
+ "Submission engine: classifies original-window, valid-extension, and late submissions deterministically.",
+ "Arbitration and reward distribution: holds scoring or award release when deadline fairness evidence is incomplete.",
+ "Audit logs: emits reviewer-ready actions, issue codes, and freeze-window expectations.",
+ ],
+ summary,
+ results,
+ };
+}
+
+function escapeHtml(value) {
+ return String(value)
+ .replace(/&/g, "&")
+ .replace(//g, ">")
+ .replace(/"/g, """);
+}
+
+function renderMarkdownReport(report) {
+ const lines = [
+ "# Deadline Fairness Review",
+ "",
+ `Generated: ${report.generatedAt}`,
+ "",
+ "## Summary",
+ "",
+ `- Challenges reviewed: ${report.summary.challengeCount}`,
+ `- Clear for scoring: ${report.summary.clearForScoring}`,
+ `- Held for arbitration: ${report.summary.heldForArbitration}`,
+ `- Needs fairness review: ${report.summary.needsFairnessReview}`,
+ `- Late-submission rejection sets: ${report.summary.rejectLateSubmissionSets}`,
+ `- Average fairness score: ${report.summary.averageFairnessScore}`,
+ "",
+ "## Requirement Map",
+ "",
+ ...report.requirementMap.map((item) => `- ${item}`),
+ "",
+ "## Challenge Decisions",
+ "",
+ ];
+
+ for (const result of report.results) {
+ lines.push(`### ${result.title}`);
+ lines.push("");
+ lines.push(`- Decision: ${result.decision}`);
+ lines.push(`- Fairness score: ${result.fairnessScore}`);
+ lines.push(`- Original deadline: ${result.originalDeadline}`);
+ lines.push(`- Current deadline: ${result.currentDeadline}`);
+ lines.push(`- Issues: ${result.issues.length}`);
+ for (const action of result.actions) {
+ lines.push(`- Action: ${action}`);
+ }
+ lines.push("");
+ }
+
+ return `${lines.join("\n").trimEnd()}\n`;
+}
+
+function renderSvgReport(report) {
+ const width = 1120;
+ const rowHeight = 92;
+ const height = 160 + report.results.length * rowHeight;
+ const rows = report.results
+ .map((result, index) => {
+ const y = 118 + index * rowHeight;
+ const barWidth = Math.max(24, Math.round(result.fairnessScore * 5.2));
+ const color =
+ result.decision === "clear-for-scoring"
+ ? "#2f9e44"
+ : result.decision === "reject-late-submissions"
+ ? "#f08c00"
+ : "#d6336c";
+ return `
+
+ ${escapeHtml(result.title)}
+ Decision: ${escapeHtml(result.decision)} | Issues: ${result.issues.length}
+
+
+ ${result.fairnessScore}
+ `;
+ })
+ .join("\n");
+
+ return `
+`;
+}
+
+module.exports = {
+ evaluateChallenge,
+ evaluateChallenges,
+ renderMarkdownReport,
+ renderSvgReport,
+ hasExplicitTimezone,
+ HOUR_MS,
+};
diff --git a/scientific-bounty-deadline-fairness-guard/make-demo-video.js b/scientific-bounty-deadline-fairness-guard/make-demo-video.js
new file mode 100644
index 00000000..e15bc2b5
--- /dev/null
+++ b/scientific-bounty-deadline-fairness-guard/make-demo-video.js
@@ -0,0 +1,202 @@
+const fs = require("fs");
+const path = require("path");
+const { spawnSync } = require("child_process");
+const { challenges } = require("./sample-data");
+const { evaluateChallenges } = require("./index");
+
+const WIDTH = 1280;
+const HEIGHT = 720;
+const FPS = 12;
+const FRAMES = 48;
+
+const FONT = {
+ " ": ["000", "000", "000", "000", "000", "000", "000"],
+ "#": ["01010", "11111", "01010", "01010", "11111", "01010", "00000"],
+ ":": ["000", "010", "000", "000", "010", "000", "000"],
+ "-": ["00000", "00000", "00000", "11111", "00000", "00000", "00000"],
+ ".": ["000", "000", "000", "000", "000", "010", "010"],
+ "0": ["01110", "10001", "10011", "10101", "11001", "10001", "01110"],
+ "1": ["00100", "01100", "00100", "00100", "00100", "00100", "01110"],
+ "2": ["01110", "10001", "00001", "00010", "00100", "01000", "11111"],
+ "3": ["11110", "00001", "00001", "01110", "00001", "00001", "11110"],
+ "4": ["00010", "00110", "01010", "10010", "11111", "00010", "00010"],
+ "5": ["11111", "10000", "10000", "11110", "00001", "00001", "11110"],
+ "6": ["01110", "10000", "10000", "11110", "10001", "10001", "01110"],
+ "7": ["11111", "00001", "00010", "00100", "01000", "01000", "01000"],
+ "8": ["01110", "10001", "10001", "01110", "10001", "10001", "01110"],
+ "9": ["01110", "10001", "10001", "01111", "00001", "00001", "01110"],
+ A: ["01110", "10001", "10001", "11111", "10001", "10001", "10001"],
+ B: ["11110", "10001", "10001", "11110", "10001", "10001", "11110"],
+ C: ["01111", "10000", "10000", "10000", "10000", "10000", "01111"],
+ D: ["11110", "10001", "10001", "10001", "10001", "10001", "11110"],
+ E: ["11111", "10000", "10000", "11110", "10000", "10000", "11111"],
+ F: ["11111", "10000", "10000", "11110", "10000", "10000", "10000"],
+ G: ["01111", "10000", "10000", "10011", "10001", "10001", "01111"],
+ H: ["10001", "10001", "10001", "11111", "10001", "10001", "10001"],
+ I: ["11111", "00100", "00100", "00100", "00100", "00100", "11111"],
+ J: ["00111", "00010", "00010", "00010", "10010", "10010", "01100"],
+ K: ["10001", "10010", "10100", "11000", "10100", "10010", "10001"],
+ L: ["10000", "10000", "10000", "10000", "10000", "10000", "11111"],
+ M: ["10001", "11011", "10101", "10101", "10001", "10001", "10001"],
+ N: ["10001", "11001", "10101", "10011", "10001", "10001", "10001"],
+ O: ["01110", "10001", "10001", "10001", "10001", "10001", "01110"],
+ P: ["11110", "10001", "10001", "11110", "10000", "10000", "10000"],
+ Q: ["01110", "10001", "10001", "10001", "10101", "10010", "01101"],
+ R: ["11110", "10001", "10001", "11110", "10100", "10010", "10001"],
+ S: ["01111", "10000", "10000", "01110", "00001", "00001", "11110"],
+ T: ["11111", "00100", "00100", "00100", "00100", "00100", "00100"],
+ U: ["10001", "10001", "10001", "10001", "10001", "10001", "01110"],
+ V: ["10001", "10001", "10001", "10001", "10001", "01010", "00100"],
+ W: ["10001", "10001", "10001", "10101", "10101", "10101", "01010"],
+ X: ["10001", "10001", "01010", "00100", "01010", "10001", "10001"],
+ Y: ["10001", "10001", "01010", "00100", "00100", "00100", "00100"],
+ Z: ["11111", "00001", "00010", "00100", "01000", "10000", "11111"],
+};
+
+function ensureDir(dir) {
+ fs.mkdirSync(dir, { recursive: true });
+}
+
+function color(hex) {
+ const clean = hex.replace("#", "");
+ return [
+ Number.parseInt(clean.slice(0, 2), 16),
+ Number.parseInt(clean.slice(2, 4), 16),
+ Number.parseInt(clean.slice(4, 6), 16),
+ ];
+}
+
+function fill(buffer, hex) {
+ const [r, g, b] = color(hex);
+ for (let index = 0; index < buffer.length; index += 3) {
+ buffer[index] = r;
+ buffer[index + 1] = g;
+ buffer[index + 2] = b;
+ }
+}
+
+function rect(buffer, x, y, width, height, hex) {
+ const [r, g, b] = color(hex);
+ const startX = Math.max(0, Math.floor(x));
+ const startY = Math.max(0, Math.floor(y));
+ const endX = Math.min(WIDTH, Math.floor(x + width));
+ const endY = Math.min(HEIGHT, Math.floor(y + height));
+ for (let py = startY; py < endY; py += 1) {
+ for (let px = startX; px < endX; px += 1) {
+ const offset = (py * WIDTH + px) * 3;
+ buffer[offset] = r;
+ buffer[offset + 1] = g;
+ buffer[offset + 2] = b;
+ }
+ }
+}
+
+function drawChar(buffer, ch, x, y, scale, hex) {
+ const glyph = FONT[ch] || FONT[" "];
+ for (let row = 0; row < glyph.length; row += 1) {
+ for (let col = 0; col < glyph[row].length; col += 1) {
+ if (glyph[row][col] === "1") {
+ rect(buffer, x + col * scale, y + row * scale, scale, scale, hex);
+ }
+ }
+ }
+ return glyph[0].length * scale + scale;
+}
+
+function drawText(buffer, text, x, y, scale, hex) {
+ let cursor = x;
+ for (const ch of String(text).toUpperCase()) {
+ cursor += drawChar(buffer, ch, cursor, y, scale, hex);
+ }
+}
+
+function writePpm(file, buffer) {
+ const header = Buffer.from(`P6\n${WIDTH} ${HEIGHT}\n255\n`, "ascii");
+ fs.writeFileSync(file, Buffer.concat([header, buffer]));
+}
+
+function renderFrame(report, frameIndex, file) {
+ const buffer = Buffer.alloc(WIDTH * HEIGHT * 3);
+ fill(buffer, "#0b1020");
+
+ const progress = (frameIndex + 1) / FRAMES;
+ const cards = [
+ { label: "CLEAR", value: report.summary.clearForScoring, color: "#1c7ed6", x: 80 },
+ { label: "HOLD", value: report.summary.heldForArbitration, color: "#d6336c", x: 460 },
+ { label: "REJECT", value: report.summary.rejectLateSubmissionSets, color: "#f08c00", x: 840 },
+ ];
+
+ rect(buffer, 0, 0, WIDTH, 720, "#0b1020");
+ rect(buffer, 72, 152, Math.round(1128 * progress), 5, "#4dabf7");
+ rect(buffer, 70, 610, Math.round(1140 * progress), 12, "#2f9e44");
+
+ drawText(buffer, "SCIBASE #18", 78, 64, 8, "#ffffff");
+ drawText(buffer, "DEADLINE FAIRNESS GUARD", 78, 128, 6, "#cfe8ff");
+
+ for (const card of cards) {
+ rect(buffer, card.x, 246, 340, 226, card.color);
+ rect(buffer, card.x + 14, 260, 312, 198, "#111827");
+ drawText(buffer, card.label, card.x + 42, 296, 7, "#ffffff");
+ drawText(buffer, String(card.value), card.x + 132, 364, 12, card.color);
+ }
+
+ drawText(buffer, `AVG SCORE ${report.summary.averageFairnessScore}`, 118, 520, 6, "#e7f5ff");
+ drawText(buffer, "SYNTHETIC DATA ONLY", 118, 566, 5, "#adb5bd");
+
+ writePpm(file, buffer);
+}
+
+function ffmpegCandidates() {
+ return ["ffmpeg", "/opt/homebrew/bin/ffmpeg", "/usr/local/bin/ffmpeg"];
+}
+
+function encodeFrames(framesDir, output) {
+ const errors = [];
+ for (const ffmpeg of ffmpegCandidates()) {
+ const result = spawnSync(
+ ffmpeg,
+ [
+ "-y",
+ "-framerate",
+ String(FPS),
+ "-i",
+ path.join(framesDir, "frame-%03d.ppm"),
+ "-c:v",
+ "libx264",
+ "-pix_fmt",
+ "yuv420p",
+ "-movflags",
+ "+faststart",
+ output,
+ ],
+ { encoding: "utf8" },
+ );
+
+ if (result.status === 0 && fs.existsSync(output) && fs.statSync(output).size > 1000) {
+ return;
+ }
+
+ const stderr = (result.stderr || result.error || "").toString();
+ errors.push(`${ffmpeg}: ${stderr.split("\n").slice(-8).join("\n")}`);
+ }
+
+ throw new Error(`ffmpeg failed to encode demo video:\n${errors.join("\n")}`);
+}
+
+function main() {
+ const report = evaluateChallenges(challenges);
+ const reportDir = path.join(__dirname, "reports");
+ const framesDir = path.join(reportDir, "video-frames");
+ const output = path.join(reportDir, "demo.mp4");
+ ensureDir(framesDir);
+
+ for (let frame = 0; frame < FRAMES; frame += 1) {
+ renderFrame(report, frame, path.join(framesDir, `frame-${String(frame).padStart(3, "0")}.ppm`));
+ }
+
+ encodeFrames(framesDir, output);
+ fs.rmSync(framesDir, { recursive: true, force: true });
+ console.log(`demo video generated: ${output}`);
+}
+
+main();
diff --git a/scientific-bounty-deadline-fairness-guard/reports/deadline-fairness-review.json b/scientific-bounty-deadline-fairness-guard/reports/deadline-fairness-review.json
new file mode 100644
index 00000000..5be92bb0
--- /dev/null
+++ b/scientific-bounty-deadline-fairness-guard/reports/deadline-fairness-review.json
@@ -0,0 +1,406 @@
+{
+ "generatedAt": "2026-05-28T00:00:00.000Z",
+ "requirementMap": [
+ "Challenge posting portal: validates timeline and extension policy before sponsor changes are published.",
+ "Submission engine: classifies original-window, valid-extension, and late submissions deterministically.",
+ "Arbitration and reward distribution: holds scoring or award release when deadline fairness evidence is incomplete.",
+ "Audit logs: emits reviewer-ready actions, issue codes, and freeze-window expectations."
+ ],
+ "summary": {
+ "challengeCount": 4,
+ "clearForScoring": 1,
+ "heldForArbitration": 2,
+ "needsFairnessReview": 0,
+ "rejectLateSubmissionSets": 1,
+ "averageFairnessScore": 50
+ },
+ "results": [
+ {
+ "challengeId": "climate-catalyst-forecast",
+ "title": "Regional Climate Catalyst Forecast",
+ "originalDeadline": "2026-06-01T17:00:00Z",
+ "currentDeadline": "2026-06-04T17:00:00Z",
+ "decision": "clear-for-scoring",
+ "fairnessScore": 100,
+ "extension": {
+ "extensionRequired": true,
+ "valid": true,
+ "event": {
+ "id": "ext-001",
+ "requestedBy": "sponsor:climate-nonprofit",
+ "approvedBy": "arbiter:public-review",
+ "approvedAt": "2026-05-31T18:15:00Z",
+ "publishedAt": "2026-05-31T18:30:00Z",
+ "reason": "Hosted benchmark outage affected all solvers.",
+ "scope": "all-eligible-teams",
+ "newDeadline": "2026-06-04T17:00:00Z",
+ "notices": [
+ {
+ "teamId": "atlas-lab",
+ "notifiedAt": "2026-05-31T18:35:00Z",
+ "visibleDeadline": "2026-06-04T17:00:00Z"
+ },
+ {
+ "teamId": "helix-models",
+ "notifiedAt": "2026-05-31T18:36:00Z",
+ "visibleDeadline": "2026-06-04T17:00:00Z"
+ },
+ {
+ "teamId": "northstar-ai",
+ "notifiedAt": "2026-05-31T18:37:00Z",
+ "visibleDeadline": "2026-06-04T17:00:00Z"
+ }
+ ]
+ },
+ "noticeCoverage": [
+ {
+ "teamId": "atlas-lab",
+ "status": "on-time",
+ "lateByMinutes": 5
+ },
+ {
+ "teamId": "helix-models",
+ "status": "on-time",
+ "lateByMinutes": 6
+ },
+ {
+ "teamId": "northstar-ai",
+ "status": "on-time",
+ "lateByMinutes": 7
+ }
+ ]
+ },
+ "submissions": [
+ {
+ "submissionId": "sub-atlas",
+ "teamId": "atlas-lab",
+ "submittedAt": "2026-06-01T16:24:00Z",
+ "status": "accepted",
+ "decision": "accept-original-window",
+ "reason": "Submitted before the original cutoff."
+ },
+ {
+ "submissionId": "sub-helix",
+ "teamId": "helix-models",
+ "submittedAt": "2026-06-02T14:12:00Z",
+ "status": "accepted",
+ "decision": "accept-valid-extension-window",
+ "reason": "Submitted after the original cutoff but inside a valid public extension."
+ },
+ {
+ "submissionId": "sub-northstar",
+ "teamId": "northstar-ai",
+ "submittedAt": "2026-06-04T16:45:00Z",
+ "status": "accepted",
+ "decision": "accept-valid-extension-window",
+ "reason": "Submitted after the original cutoff but inside a valid public extension."
+ }
+ ],
+ "issueCounts": {},
+ "issues": [],
+ "actions": [
+ "Proceed to scoring with the normalized current deadline."
+ ]
+ },
+ {
+ "challengeId": "single-cell-biomarker-race",
+ "title": "Single-cell Biomarker Race",
+ "originalDeadline": "2026-06-02T18:00:00Z",
+ "currentDeadline": "2026-06-03T18:00:00Z",
+ "decision": "hold-arbitration",
+ "fairnessScore": 0,
+ "extension": {
+ "extensionRequired": true,
+ "valid": false,
+ "event": {
+ "id": "ext-private-helix",
+ "requestedBy": "team:helix-models",
+ "approvedBy": "sponsor:biotech-inc",
+ "approvedAt": "2026-06-02T19:30:00Z",
+ "publishedAt": "2026-06-02T20:15:00Z",
+ "reason": "Sponsor accepted one late upload after a private support thread.",
+ "scope": "single-team",
+ "teamId": "helix-models",
+ "newDeadline": "2026-06-03T18:00:00Z",
+ "notices": [
+ {
+ "teamId": "helix-models",
+ "notifiedAt": "2026-06-02T20:17:00Z",
+ "visibleDeadline": "2026-06-03T18:00:00Z"
+ },
+ {
+ "teamId": "cobalt-bio",
+ "notifiedAt": "2026-06-03T08:20:00Z",
+ "visibleDeadline": "2026-06-03T18:00:00Z"
+ }
+ ]
+ },
+ "noticeCoverage": [
+ {
+ "teamId": "atlas-lab",
+ "status": "missing"
+ },
+ {
+ "teamId": "helix-models",
+ "status": "on-time",
+ "lateByMinutes": 2
+ },
+ {
+ "teamId": "cobalt-bio",
+ "status": "late",
+ "lateByMinutes": 725
+ }
+ ]
+ },
+ "submissions": [
+ {
+ "submissionId": "sub-atlas",
+ "teamId": "atlas-lab",
+ "submittedAt": "2026-06-02T17:58:00Z",
+ "status": "accepted",
+ "decision": "accept-original-window",
+ "reason": "Submitted before the original cutoff."
+ },
+ {
+ "submissionId": "sub-helix",
+ "teamId": "helix-models",
+ "submittedAt": "2026-06-03T12:03:00Z",
+ "status": "accepted",
+ "decision": "hold-accepted-under-invalid-extension",
+ "reason": "Submission used an extension that failed fairness checks."
+ },
+ {
+ "submissionId": "sub-cobalt",
+ "teamId": "cobalt-bio",
+ "submittedAt": "2026-06-03T19:04:00Z",
+ "status": "accepted",
+ "decision": "hold-accepted-after-current-cutoff",
+ "reason": "Submitted after the active challenge cutoff."
+ }
+ ],
+ "issueCounts": {
+ "critical": 4,
+ "high": 4
+ },
+ "issues": [
+ {
+ "severity": "critical",
+ "code": "private-extension",
+ "context": "ext-private-helix",
+ "message": "Deadline extension is not scoped to all eligible teams."
+ },
+ {
+ "severity": "high",
+ "code": "retroactive-extension-approval",
+ "context": "ext-private-helix",
+ "message": "Extension was approved after the original cutoff; arbitration needs an explicit fairness review."
+ },
+ {
+ "severity": "high",
+ "code": "extension-published-after-cutoff",
+ "context": "ext-private-helix",
+ "message": "Extension was published after the original cutoff, so some solvers could have stopped work early."
+ },
+ {
+ "severity": "critical",
+ "code": "missing-team-notice",
+ "context": "ext-private-helix:atlas-lab",
+ "message": "Atlas Lab did not receive the deadline-extension notice."
+ },
+ {
+ "severity": "high",
+ "code": "late-team-notice",
+ "context": "ext-private-helix:cobalt-bio",
+ "message": "Cobalt Bio received the extension notice 725 minutes after publication."
+ },
+ {
+ "severity": "high",
+ "code": "missing-extension-freeze-reopen",
+ "message": "Deadline extension did not create a new freeze snapshot for the extended window."
+ },
+ {
+ "severity": "critical",
+ "code": "accepted-under-invalid-extension",
+ "context": "sub-helix",
+ "message": "sub-helix was accepted after the original cutoff without a valid equal extension."
+ },
+ {
+ "severity": "critical",
+ "code": "accepted-after-current-cutoff",
+ "context": "sub-cobalt",
+ "message": "sub-cobalt was accepted after the current challenge deadline."
+ }
+ ],
+ "actions": [
+ "Hold award release until every eligible team has equal extension evidence.",
+ "Remove sub-cobalt from scoring or run an explicit arbitration exception."
+ ]
+ },
+ {
+ "challengeId": "quantum-noise-cutoff",
+ "title": "Quantum Noise Reduction Sprint",
+ "originalDeadline": "2026-06-05T23:59:00Z",
+ "currentDeadline": "2026-06-05T23:59:00Z",
+ "decision": "reject-late-submissions",
+ "fairnessScore": 100,
+ "extension": {
+ "extensionRequired": false,
+ "valid": true,
+ "event": null,
+ "noticeCoverage": []
+ },
+ "submissions": [
+ {
+ "submissionId": "sub-qubit",
+ "teamId": "qubit-north",
+ "submittedAt": "2026-06-05T23:50:00Z",
+ "status": "accepted",
+ "decision": "accept-original-window",
+ "reason": "Submitted before the original cutoff."
+ },
+ {
+ "submissionId": "sub-phase",
+ "teamId": "phase-labs",
+ "submittedAt": "2026-06-06T00:12:00Z",
+ "status": "pending",
+ "decision": "reject-late",
+ "reason": "Submitted after the active challenge cutoff."
+ }
+ ],
+ "issueCounts": {},
+ "issues": [],
+ "actions": []
+ },
+ {
+ "challengeId": "materials-ambiguous-cutoff",
+ "title": "Materials Discovery Prototype",
+ "originalDeadline": "2026-06-07 17:00",
+ "currentDeadline": "2026-06-08 17:00",
+ "decision": "hold-arbitration",
+ "fairnessScore": 0,
+ "extension": {
+ "extensionRequired": true,
+ "valid": false,
+ "event": {
+ "id": "ext-ambiguous-time",
+ "requestedBy": "sponsor:materials-co",
+ "approvedBy": "arbiter:challenge-admin",
+ "approvedAt": "2026-06-06T15:00:00Z",
+ "publishedAt": "2026-06-06T15:30:00Z",
+ "reason": "Sponsor changed uploaded dataset.",
+ "scope": "all-eligible-teams",
+ "newDeadline": "2026-06-08 17:00",
+ "notices": [
+ {
+ "teamId": "lattice-lab",
+ "notifiedAt": "2026-06-06T15:35:00Z",
+ "visibleDeadline": "2026-06-08 17:00"
+ },
+ {
+ "teamId": "polymer-scouts",
+ "notifiedAt": "2026-06-06T16:40:00Z",
+ "visibleDeadline": "2026-06-08 17:00"
+ }
+ ]
+ },
+ "noticeCoverage": [
+ {
+ "teamId": "lattice-lab",
+ "status": "on-time",
+ "lateByMinutes": 5
+ },
+ {
+ "teamId": "polymer-scouts",
+ "status": "late",
+ "lateByMinutes": 70
+ }
+ ]
+ },
+ "submissions": [
+ {
+ "submissionId": "sub-lattice",
+ "teamId": "lattice-lab",
+ "submittedAt": "2026-06-08T16:30:00Z",
+ "status": "accepted",
+ "decision": "hold-accepted-under-invalid-extension",
+ "reason": "Submission used an extension that failed fairness checks."
+ },
+ {
+ "submissionId": "sub-polymer",
+ "teamId": "polymer-scouts",
+ "submittedAt": "2026-06-08T16:55:00Z",
+ "status": "accepted",
+ "decision": "hold-accepted-under-invalid-extension",
+ "reason": "Submission used an extension that failed fairness checks."
+ }
+ ],
+ "issueCounts": {
+ "high": 6,
+ "critical": 2
+ },
+ "issues": [
+ {
+ "severity": "high",
+ "code": "ambiguous-timezone",
+ "field": "originalDeadline",
+ "context": "materials-ambiguous-cutoff",
+ "value": "2026-06-07 17:00",
+ "message": "originalDeadline must include Z or an explicit UTC offset before deadline decisions are made."
+ },
+ {
+ "severity": "high",
+ "code": "ambiguous-timezone",
+ "field": "currentDeadline",
+ "context": "materials-ambiguous-cutoff",
+ "value": "2026-06-08 17:00",
+ "message": "currentDeadline must include Z or an explicit UTC offset before deadline decisions are made."
+ },
+ {
+ "severity": "high",
+ "code": "ambiguous-timezone",
+ "field": "extension.newDeadline",
+ "context": "ext-ambiguous-time",
+ "value": "2026-06-08 17:00",
+ "message": "extension.newDeadline must include Z or an explicit UTC offset before deadline decisions are made."
+ },
+ {
+ "severity": "high",
+ "code": "ambiguous-timezone",
+ "field": "notice.visibleDeadline",
+ "context": "ext-ambiguous-time:lattice-lab",
+ "value": "2026-06-08 17:00",
+ "message": "notice.visibleDeadline must include Z or an explicit UTC offset before deadline decisions are made."
+ },
+ {
+ "severity": "high",
+ "code": "ambiguous-timezone",
+ "field": "notice.visibleDeadline",
+ "context": "ext-ambiguous-time:polymer-scouts",
+ "value": "2026-06-08 17:00",
+ "message": "notice.visibleDeadline must include Z or an explicit UTC offset before deadline decisions are made."
+ },
+ {
+ "severity": "high",
+ "code": "late-team-notice",
+ "context": "ext-ambiguous-time:polymer-scouts",
+ "message": "Polymer Scouts received the extension notice 70 minutes after publication."
+ },
+ {
+ "severity": "critical",
+ "code": "accepted-under-invalid-extension",
+ "context": "sub-lattice",
+ "message": "sub-lattice was accepted after the original cutoff without a valid equal extension."
+ },
+ {
+ "severity": "critical",
+ "code": "accepted-under-invalid-extension",
+ "context": "sub-polymer",
+ "message": "sub-polymer was accepted after the original cutoff without a valid equal extension."
+ }
+ ],
+ "actions": [
+ "Hold award release until every eligible team has equal extension evidence."
+ ]
+ }
+ ]
+}
diff --git a/scientific-bounty-deadline-fairness-guard/reports/deadline-fairness-review.md b/scientific-bounty-deadline-fairness-guard/reports/deadline-fairness-review.md
new file mode 100644
index 00000000..df1dce54
--- /dev/null
+++ b/scientific-bounty-deadline-fairness-guard/reports/deadline-fairness-review.md
@@ -0,0 +1,57 @@
+# Deadline Fairness Review
+
+Generated: 2026-05-28T00:00:00.000Z
+
+## Summary
+
+- Challenges reviewed: 4
+- Clear for scoring: 1
+- Held for arbitration: 2
+- Needs fairness review: 0
+- Late-submission rejection sets: 1
+- Average fairness score: 50
+
+## Requirement Map
+
+- Challenge posting portal: validates timeline and extension policy before sponsor changes are published.
+- Submission engine: classifies original-window, valid-extension, and late submissions deterministically.
+- Arbitration and reward distribution: holds scoring or award release when deadline fairness evidence is incomplete.
+- Audit logs: emits reviewer-ready actions, issue codes, and freeze-window expectations.
+
+## Challenge Decisions
+
+### Regional Climate Catalyst Forecast
+
+- Decision: clear-for-scoring
+- Fairness score: 100
+- Original deadline: 2026-06-01T17:00:00Z
+- Current deadline: 2026-06-04T17:00:00Z
+- Issues: 0
+- Action: Proceed to scoring with the normalized current deadline.
+
+### Single-cell Biomarker Race
+
+- Decision: hold-arbitration
+- Fairness score: 0
+- Original deadline: 2026-06-02T18:00:00Z
+- Current deadline: 2026-06-03T18:00:00Z
+- Issues: 8
+- Action: Hold award release until every eligible team has equal extension evidence.
+- Action: Remove sub-cobalt from scoring or run an explicit arbitration exception.
+
+### Quantum Noise Reduction Sprint
+
+- Decision: reject-late-submissions
+- Fairness score: 100
+- Original deadline: 2026-06-05T23:59:00Z
+- Current deadline: 2026-06-05T23:59:00Z
+- Issues: 0
+
+### Materials Discovery Prototype
+
+- Decision: hold-arbitration
+- Fairness score: 0
+- Original deadline: 2026-06-07 17:00
+- Current deadline: 2026-06-08 17:00
+- Issues: 8
+- Action: Hold award release until every eligible team has equal extension evidence.
diff --git a/scientific-bounty-deadline-fairness-guard/reports/deadline-fairness-summary.svg b/scientific-bounty-deadline-fairness-guard/reports/deadline-fairness-summary.svg
new file mode 100644
index 00000000..318efeda
--- /dev/null
+++ b/scientific-bounty-deadline-fairness-guard/reports/deadline-fairness-summary.svg
@@ -0,0 +1,45 @@
+
diff --git a/scientific-bounty-deadline-fairness-guard/reports/demo.mp4 b/scientific-bounty-deadline-fairness-guard/reports/demo.mp4
new file mode 100644
index 00000000..8c525dc5
Binary files /dev/null and b/scientific-bounty-deadline-fairness-guard/reports/demo.mp4 differ
diff --git a/scientific-bounty-deadline-fairness-guard/sample-data.js b/scientific-bounty-deadline-fairness-guard/sample-data.js
new file mode 100644
index 00000000..ccf2b12a
--- /dev/null
+++ b/scientific-bounty-deadline-fairness-guard/sample-data.js
@@ -0,0 +1,137 @@
+const challenges = [
+ {
+ id: "climate-catalyst-forecast",
+ title: "Regional Climate Catalyst Forecast",
+ originalDeadline: "2026-06-01T17:00:00Z",
+ currentDeadline: "2026-06-04T17:00:00Z",
+ noticeSlaMinutes: 60,
+ teams: [
+ { id: "atlas-lab", name: "Atlas Lab", eligible: true },
+ { id: "helix-models", name: "Helix Models", eligible: true },
+ { id: "northstar-ai", name: "Northstar AI", eligible: true },
+ ],
+ extensionEvents: [
+ {
+ id: "ext-001",
+ requestedBy: "sponsor:climate-nonprofit",
+ approvedBy: "arbiter:public-review",
+ approvedAt: "2026-05-31T18:15:00Z",
+ publishedAt: "2026-05-31T18:30:00Z",
+ reason: "Hosted benchmark outage affected all solvers.",
+ scope: "all-eligible-teams",
+ newDeadline: "2026-06-04T17:00:00Z",
+ notices: [
+ { teamId: "atlas-lab", notifiedAt: "2026-05-31T18:35:00Z", visibleDeadline: "2026-06-04T17:00:00Z" },
+ { teamId: "helix-models", notifiedAt: "2026-05-31T18:36:00Z", visibleDeadline: "2026-06-04T17:00:00Z" },
+ { teamId: "northstar-ai", notifiedAt: "2026-05-31T18:37:00Z", visibleDeadline: "2026-06-04T17:00:00Z" },
+ ],
+ },
+ ],
+ freezeWindow: {
+ originalSnapshotHash: "sha256:91c4e3f4a6b7",
+ reopenedAt: "2026-05-31T18:45:00Z",
+ reopenedSnapshotHash: "sha256:22bd84f02ff3",
+ },
+ submissions: [
+ { id: "sub-atlas", teamId: "atlas-lab", submittedAt: "2026-06-01T16:24:00Z", status: "accepted" },
+ { id: "sub-helix", teamId: "helix-models", submittedAt: "2026-06-02T14:12:00Z", status: "accepted" },
+ { id: "sub-northstar", teamId: "northstar-ai", submittedAt: "2026-06-04T16:45:00Z", status: "accepted" },
+ ],
+ },
+ {
+ id: "single-cell-biomarker-race",
+ title: "Single-cell Biomarker Race",
+ originalDeadline: "2026-06-02T18:00:00Z",
+ currentDeadline: "2026-06-03T18:00:00Z",
+ noticeSlaMinutes: 45,
+ teams: [
+ { id: "atlas-lab", name: "Atlas Lab", eligible: true },
+ { id: "helix-models", name: "Helix Models", eligible: true },
+ { id: "cobalt-bio", name: "Cobalt Bio", eligible: true },
+ ],
+ extensionEvents: [
+ {
+ id: "ext-private-helix",
+ requestedBy: "team:helix-models",
+ approvedBy: "sponsor:biotech-inc",
+ approvedAt: "2026-06-02T19:30:00Z",
+ publishedAt: "2026-06-02T20:15:00Z",
+ reason: "Sponsor accepted one late upload after a private support thread.",
+ scope: "single-team",
+ teamId: "helix-models",
+ newDeadline: "2026-06-03T18:00:00Z",
+ notices: [
+ { teamId: "helix-models", notifiedAt: "2026-06-02T20:17:00Z", visibleDeadline: "2026-06-03T18:00:00Z" },
+ { teamId: "cobalt-bio", notifiedAt: "2026-06-03T08:20:00Z", visibleDeadline: "2026-06-03T18:00:00Z" },
+ ],
+ },
+ ],
+ freezeWindow: {
+ originalSnapshotHash: "sha256:c0ffeeaa1111",
+ },
+ submissions: [
+ { id: "sub-atlas", teamId: "atlas-lab", submittedAt: "2026-06-02T17:58:00Z", status: "accepted" },
+ { id: "sub-helix", teamId: "helix-models", submittedAt: "2026-06-03T12:03:00Z", status: "accepted" },
+ { id: "sub-cobalt", teamId: "cobalt-bio", submittedAt: "2026-06-03T19:04:00Z", status: "accepted" },
+ ],
+ },
+ {
+ id: "quantum-noise-cutoff",
+ title: "Quantum Noise Reduction Sprint",
+ originalDeadline: "2026-06-05T23:59:00Z",
+ currentDeadline: "2026-06-05T23:59:00Z",
+ noticeSlaMinutes: 30,
+ teams: [
+ { id: "qubit-north", name: "Qubit North", eligible: true },
+ { id: "phase-labs", name: "Phase Labs", eligible: true },
+ ],
+ extensionEvents: [],
+ freezeWindow: {
+ originalSnapshotHash: "sha256:90210aabbccd",
+ },
+ submissions: [
+ { id: "sub-qubit", teamId: "qubit-north", submittedAt: "2026-06-05T23:50:00Z", status: "accepted" },
+ { id: "sub-phase", teamId: "phase-labs", submittedAt: "2026-06-06T00:12:00Z", status: "pending" },
+ ],
+ },
+ {
+ id: "materials-ambiguous-cutoff",
+ title: "Materials Discovery Prototype",
+ originalDeadline: "2026-06-07 17:00",
+ currentDeadline: "2026-06-08 17:00",
+ noticeSlaMinutes: 60,
+ teams: [
+ { id: "lattice-lab", name: "Lattice Lab", eligible: true },
+ { id: "polymer-scouts", name: "Polymer Scouts", eligible: true },
+ ],
+ extensionEvents: [
+ {
+ id: "ext-ambiguous-time",
+ requestedBy: "sponsor:materials-co",
+ approvedBy: "arbiter:challenge-admin",
+ approvedAt: "2026-06-06T15:00:00Z",
+ publishedAt: "2026-06-06T15:30:00Z",
+ reason: "Sponsor changed uploaded dataset.",
+ scope: "all-eligible-teams",
+ newDeadline: "2026-06-08 17:00",
+ notices: [
+ { teamId: "lattice-lab", notifiedAt: "2026-06-06T15:35:00Z", visibleDeadline: "2026-06-08 17:00" },
+ { teamId: "polymer-scouts", notifiedAt: "2026-06-06T16:40:00Z", visibleDeadline: "2026-06-08 17:00" },
+ ],
+ },
+ ],
+ freezeWindow: {
+ originalSnapshotHash: "sha256:7e57edddd123",
+ reopenedAt: "2026-06-06T15:40:00Z",
+ reopenedSnapshotHash: "sha256:8a8d9000eeee",
+ },
+ submissions: [
+ { id: "sub-lattice", teamId: "lattice-lab", submittedAt: "2026-06-08T16:30:00Z", status: "accepted" },
+ { id: "sub-polymer", teamId: "polymer-scouts", submittedAt: "2026-06-08T16:55:00Z", status: "accepted" },
+ ],
+ },
+];
+
+module.exports = {
+ challenges,
+};
diff --git a/scientific-bounty-deadline-fairness-guard/test.js b/scientific-bounty-deadline-fairness-guard/test.js
new file mode 100644
index 00000000..545b0a28
--- /dev/null
+++ b/scientific-bounty-deadline-fairness-guard/test.js
@@ -0,0 +1,50 @@
+const assert = require("assert");
+const { challenges } = require("./sample-data");
+const { evaluateChallenge, evaluateChallenges, hasExplicitTimezone } = require("./index");
+
+function byId(report, id) {
+ return report.results.find((result) => result.challengeId === id);
+}
+
+function runTests() {
+ assert.strictEqual(hasExplicitTimezone("2026-06-01T17:00:00Z"), true);
+ assert.strictEqual(hasExplicitTimezone("2026-06-01T17:00:00-05:00"), true);
+ assert.strictEqual(hasExplicitTimezone("2026-06-01 17:00"), false);
+
+ const report = evaluateChallenges(challenges);
+ assert.strictEqual(report.summary.challengeCount, 4);
+
+ const fair = byId(report, "climate-catalyst-forecast");
+ assert.strictEqual(fair.decision, "clear-for-scoring");
+ assert.strictEqual(fair.fairnessScore, 100);
+ assert.ok(fair.submissions.some((submission) => submission.decision === "accept-valid-extension-window"));
+ assert.strictEqual(fair.extension.noticeCoverage.every((notice) => notice.status === "on-time"), true);
+
+ const privateExtension = byId(report, "single-cell-biomarker-race");
+ assert.strictEqual(privateExtension.decision, "hold-arbitration");
+ assert.ok(privateExtension.issues.some((issue) => issue.code === "private-extension"));
+ assert.ok(privateExtension.issues.some((issue) => issue.code === "missing-team-notice"));
+ assert.ok(privateExtension.issues.some((issue) => issue.code === "accepted-after-current-cutoff"));
+ assert.ok(
+ privateExtension.submissions.some(
+ (submission) => submission.decision === "hold-accepted-under-invalid-extension",
+ ),
+ );
+
+ const late = byId(report, "quantum-noise-cutoff");
+ assert.strictEqual(late.decision, "reject-late-submissions");
+ assert.ok(late.submissions.some((submission) => submission.decision === "reject-late"));
+
+ const ambiguous = byId(report, "materials-ambiguous-cutoff");
+ assert.strictEqual(ambiguous.decision, "hold-arbitration");
+ assert.ok(ambiguous.issues.some((issue) => issue.code === "ambiguous-timezone"));
+ assert.ok(ambiguous.issues.some((issue) => issue.code === "late-team-notice"));
+ assert.ok(ambiguous.issues.some((issue) => issue.code === "accepted-under-invalid-extension"));
+
+ const single = evaluateChallenge(challenges[0]);
+ assert.strictEqual(single.actions[0], "Proceed to scoring with the normalized current deadline.");
+
+ console.log("deadline fairness guard tests passed");
+}
+
+runTests();