diff --git a/challenge-reviewer-workload-sla-guard/README.md b/challenge-reviewer-workload-sla-guard/README.md
new file mode 100644
index 00000000..54543a1a
--- /dev/null
+++ b/challenge-reviewer-workload-sla-guard/README.md
@@ -0,0 +1,37 @@
+# Challenge Reviewer Workload SLA Guard
+
+This is a focused Scientific Bounty System slice for issue #18. It validates whether a challenge has enough active, under-capacity scientific reviewers and arbitrators before live sponsor scoring, arbitration, or award release proceeds.
+
+The guard evaluates:
+
+- reviewer availability, blackout windows, stale activity, open assignment count, and weekly review capacity
+- overdue and critically stale challenge reviews
+- rubric-criterion coverage by active reviewers with matching expertise
+- arbitration backup coverage for contested scientific bounty decisions
+- escalation actions when review windows should be held before payout or public award announcements
+
+It is intentionally separate from existing #18 slices for intake, rubric readiness, scoring, arbitration ledgers, appeal flows, anti-collusion, escrow settlement, payout eligibility, sponsor reliability, amendment consent, reviewer consensus, IP redaction, milestone progress, evidence freeze, data-room access, clarification freeze, award transparency, benchmark leakage, deliverable acceptance, reproducibility environment, cancellation/no-award, license/dependency checks, human-subjects review, solver withdrawal, embargo release, and evaluator calibration.
+
+## Verification
+
+```bash
+npm run check
+npm test
+npm run demo
+npm run demo:video
+```
+
+`npm run demo` generates:
+
+- `reports/workload-review-packet.json`
+- `reports/workload-review-report.md`
+- `reports/summary.svg`
+- `reports/demo-transcript.md`
+
+`npm run demo:video` generates:
+
+- `reports/demo.avi`
+
+## Safety
+
+All examples use synthetic challenge, reviewer, and assignment records. This module does not call identity services, payment systems, private review dashboards, email systems, challenge workspaces, or external APIs.
\ No newline at end of file
diff --git a/challenge-reviewer-workload-sla-guard/acceptance-notes.md b/challenge-reviewer-workload-sla-guard/acceptance-notes.md
new file mode 100644
index 00000000..efa29927
--- /dev/null
+++ b/challenge-reviewer-workload-sla-guard/acceptance-notes.md
@@ -0,0 +1,26 @@
+# Acceptance Notes
+
+## Reviewer Path
+
+1. Start with `requirements-map.md` to see the issue #18 mapping and scope boundary.
+2. Inspect `index.js` for the deterministic workload, coverage, stale review, and arbitration backup checks.
+3. Inspect `sample-data.js` for synthetic risky and clean challenge packets.
+4. Run `npm test` to verify hold, ready, warning, and digest behavior.
+5. Run `npm run demo` and inspect `reports/workload-review-report.md` plus `reports/summary.svg`.
+6. Run `npm run demo:video` to regenerate the short demo video at `reports/demo.avi`.
+
+## Sponsor Outcome
+
+The bounty poster gets a reviewable operations-control slice that prevents a scientific bounty from entering scoring or award release with unavailable reviewers, overloaded reviewers, stale reviews, uncovered rubric expertise, or missing arbitration backup. Clean challenge packets proceed, due-soon windows produce warnings, and unsafe packets produce explicit blockers plus escalation actions.
+
+## Local Validation
+
+- `npm run check`
+- `npm test`
+- `npm run demo`
+- `npm run demo:video`
+- `git diff --check`
+
+## Data Handling
+
+The module uses synthetic data only. It includes no private reviewer records, no solver identities, no credentials, no payment details, no external service calls, and no live challenge mutations.
diff --git a/challenge-reviewer-workload-sla-guard/demo.js b/challenge-reviewer-workload-sla-guard/demo.js
new file mode 100644
index 00000000..15a9aff0
--- /dev/null
+++ b/challenge-reviewer-workload-sla-guard/demo.js
@@ -0,0 +1,27 @@
+const fs = require("node:fs");
+const path = require("node:path");
+const { evaluateChallengeReviewerWorkload, renderMarkdown, renderSvg } = require("./index");
+const { riskyChallenge, cleanChallenge } = require("./sample-data");
+
+const reportsDir = path.join(__dirname, "reports");
+fs.mkdirSync(reportsDir, { recursive: true });
+
+const riskyResult = evaluateChallengeReviewerWorkload(riskyChallenge);
+const cleanResult = evaluateChallengeReviewerWorkload(cleanChallenge);
+
+fs.writeFileSync(
+ path.join(reportsDir, "workload-review-packet.json"),
+ `${JSON.stringify({ riskyResult, cleanResult }, null, 2)}\n`
+);
+fs.writeFileSync(path.join(reportsDir, "workload-review-report.md"), renderMarkdown(riskyResult));
+fs.writeFileSync(path.join(reportsDir, "summary.svg"), renderSvg(riskyResult));
+
+console.log(
+ [
+ `status=${riskyResult.summary.status}`,
+ `blockers=${riskyResult.summary.blockers}`,
+ `warnings=${riskyResult.summary.warnings}`,
+ `capacityGaps=${riskyResult.summary.criteriaWithCapacityGaps}`,
+ `digest=${riskyResult.auditDigest.slice(0, 16)}`
+ ].join(" ")
+);
diff --git a/challenge-reviewer-workload-sla-guard/index.js b/challenge-reviewer-workload-sla-guard/index.js
new file mode 100644
index 00000000..c4972142
--- /dev/null
+++ b/challenge-reviewer-workload-sla-guard/index.js
@@ -0,0 +1,495 @@
+const crypto = require("node:crypto");
+
+const HOURS = 60 * 60 * 1000;
+
+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 digest(value) {
+ return crypto.createHash("sha256").update(stableStringify(value)).digest("hex");
+}
+
+function asDate(value, label) {
+ const date = new Date(value);
+ if (!Number.isFinite(date.getTime())) {
+ throw new Error(`Invalid date for ${label}: ${value}`);
+ }
+ return date;
+}
+
+function hoursBetween(start, end) {
+ return (asDate(end, "end") - asDate(start, "start")) / HOURS;
+}
+
+function includes(value, list) {
+ return Array.isArray(list) && list.includes(value);
+}
+
+function inWindow(now, window) {
+ return asDate(window.startsAt, "blackout startsAt") <= now && now <= asDate(window.endsAt, "blackout endsAt");
+}
+
+function openAssignments(reviewer, challengeId) {
+ return (reviewer.assignments || []).filter((assignment) => {
+ return assignment.challengeId === challengeId && assignment.status !== "submitted" && assignment.status !== "withdrawn";
+ });
+}
+
+function getReviewerState(reviewer, challengeId, now, policy) {
+ const assignments = openAssignments(reviewer, challengeId);
+ const estimatedHours = assignments.reduce((total, assignment) => total + Number(assignment.estimatedHours || 0), 0);
+ const weeklyBudget = Math.max(Number(reviewer.availability.weeklyHourBudget || 0), 1);
+ const committedHours = Number(reviewer.availability.committedHours || 0) + estimatedHours;
+ const utilization = committedHours / weeklyBudget;
+ const isBlackout = (reviewer.availability.blackoutWindows || []).some((window) => inWindow(now, window));
+ const inactiveHours = hoursBetween(reviewer.lastActiveAt, now);
+ const isInactive = inactiveHours > policy.inactiveReviewerHours;
+ const isUnavailable = reviewer.availability.status !== "active" || isBlackout || isInactive;
+ const isOverloaded =
+ utilization > policy.maxReviewerUtilization ||
+ assignments.length > Number(reviewer.availability.maxConcurrentReviews || 0);
+
+ const staleAssignments = assignments
+ .map((assignment) => {
+ const hoursLate = hoursBetween(assignment.dueAt, now);
+ return {
+ assignmentId: assignment.id,
+ reviewerId: reviewer.id,
+ criterionId: assignment.criterionId,
+ dueAt: assignment.dueAt,
+ hoursLate: Math.max(0, Number(hoursLate.toFixed(1))),
+ isCritical: hoursLate >= policy.staleReviewHours
+ };
+ })
+ .filter((assignment) => assignment.hoursLate > 0);
+
+ return {
+ reviewerId: reviewer.id,
+ displayName: reviewer.displayName,
+ expertise: reviewer.expertise || [],
+ roles: reviewer.roles || [],
+ canArbitrate: Boolean(reviewer.canArbitrate),
+ status: reviewer.availability.status,
+ contactWindowHours: reviewer.contactWindowHours,
+ weeklyBudget,
+ committedHours,
+ openAssignments: assignments.length,
+ utilization: Number(utilization.toFixed(3)),
+ isBlackout,
+ inactiveHours: Number(inactiveHours.toFixed(1)),
+ isInactive,
+ isUnavailable,
+ isOverloaded,
+ staleAssignments
+ };
+}
+
+function reviewerCanCoverCriterion(state, reviewer, criterion, policy) {
+ return (
+ state.status === "active" &&
+ !state.isBlackout &&
+ !state.isInactive &&
+ state.utilization <= policy.maxReviewerUtilization &&
+ state.openAssignments < Number(reviewer.availability.maxConcurrentReviews || 0) &&
+ includes(criterion.requiredExpertise, reviewer.expertise)
+ );
+}
+
+function buildCriterionCoverage(challenge, reviewerStatesById, policy) {
+ return challenge.criteria.map((criterion) => {
+ const eligibleReviewers = challenge.reviewers
+ .filter((reviewer) => reviewerCanCoverCriterion(reviewerStatesById.get(reviewer.id), reviewer, criterion, policy))
+ .map((reviewer) => reviewer.id);
+ const assignedReviewers = challenge.reviewers
+ .filter((reviewer) => openAssignments(reviewer, challenge.challengeId).some((assignment) => assignment.criterionId === criterion.id))
+ .map((reviewer) => reviewer.id);
+ const needed = Number(criterion.minReviewers || policy.minEligibleReviewersPerCriterion);
+
+ return {
+ criterionId: criterion.id,
+ label: criterion.label,
+ requiredExpertise: criterion.requiredExpertise,
+ minReviewers: needed,
+ eligibleReviewers,
+ assignedReviewers,
+ hasEnoughCoverage: eligibleReviewers.length >= needed
+ };
+ });
+}
+
+function buildAssignmentQueue(challenge, reviewerStatesById, now, policy) {
+ const queue = [];
+ for (const reviewer of challenge.reviewers) {
+ const state = reviewerStatesById.get(reviewer.id);
+ for (const assignment of openAssignments(reviewer, challenge.challengeId)) {
+ const hoursUntilDue = hoursBetween(now, assignment.dueAt);
+ const dueBucket =
+ hoursUntilDue < 0
+ ? "past_due"
+ : hoursUntilDue <= policy.dueSoonHours
+ ? "due_soon"
+ : "on_track";
+ queue.push({
+ assignmentId: assignment.id,
+ reviewerId: reviewer.id,
+ criterionId: assignment.criterionId,
+ status: assignment.status,
+ dueAt: assignment.dueAt,
+ estimatedHours: Number(assignment.estimatedHours || 0),
+ dueBucket,
+ hoursUntilDue: Number(hoursUntilDue.toFixed(1)),
+ reviewerUnavailable: state.isUnavailable,
+ reviewerOverloaded: state.isOverloaded
+ });
+ }
+ }
+ return queue.sort((left, right) => {
+ const due = asDate(left.dueAt, "left dueAt") - asDate(right.dueAt, "right dueAt");
+ return due || left.assignmentId.localeCompare(right.assignmentId);
+ });
+}
+
+function addBlocker(blockers, code, message, evidence) {
+ blockers.push({ code, message, evidence });
+}
+
+function addWarning(warnings, code, message, evidence) {
+ warnings.push({ code, message, evidence });
+}
+
+function buildEscalationPlan(blockers, warnings, challenge) {
+ const actions = [];
+ const addAction = (priority, owner, action, evidence) => {
+ actions.push({ priority, owner, action, evidence });
+ };
+
+ for (const blocker of blockers) {
+ if (blocker.code === "assigned_reviewer_unavailable") {
+ addAction(
+ "critical",
+ challenge.policies.escalationLead,
+ "Reassign blocked reviews to eligible backups before sponsor scoring opens.",
+ blocker.evidence
+ );
+ }
+ if (blocker.code === "critical_stale_review") {
+ addAction(
+ "critical",
+ challenge.policies.escalationLead,
+ "Hold award release and request same-day replacement review or arbitration signoff.",
+ blocker.evidence
+ );
+ }
+ if (blocker.code === "criterion_capacity_gap") {
+ addAction(
+ "high",
+ challenge.policies.escalationLead,
+ "Recruit additional eligible reviewers for uncovered rubric criteria.",
+ blocker.evidence
+ );
+ }
+ if (blocker.code === "arbitration_backup_gap") {
+ addAction(
+ "high",
+ challenge.policies.escalationLead,
+ "Name a conflict-free backup arbitrator before any contested decision is released.",
+ blocker.evidence
+ );
+ }
+ }
+
+ for (const warning of warnings) {
+ if (warning.code === "reviewer_near_capacity") {
+ addAction(
+ "medium",
+ challenge.policies.escalationLead,
+ "Keep a standby reviewer warm for near-capacity assignments.",
+ warning.evidence
+ );
+ }
+ if (warning.code === "assignment_due_soon") {
+ addAction(
+ "medium",
+ challenge.policies.escalationLead,
+ "Send due-soon reminders with a replacement deadline.",
+ warning.evidence
+ );
+ }
+ }
+
+ return actions;
+}
+
+function evaluateChallengeReviewerWorkload(challenge) {
+ if (!challenge || typeof challenge !== "object") {
+ throw new Error("Challenge packet is required");
+ }
+
+ const policy = {
+ maxReviewerUtilization: 0.85,
+ nearCapacityUtilization: 0.75,
+ minEligibleReviewersPerCriterion: 2,
+ staleReviewHours: 24,
+ dueSoonHours: 12,
+ inactiveReviewerHours: 72,
+ ...challenge.policies
+ };
+ const now = asDate(policy.now, "policies.now");
+ const challengeId = challenge.challengeId;
+
+ const reviewerStates = challenge.reviewers.map((reviewer) => getReviewerState(reviewer, challengeId, now, policy));
+ const reviewerStatesById = new Map(reviewerStates.map((state) => [state.reviewerId, state]));
+ const coverage = buildCriterionCoverage(challenge, reviewerStatesById, policy);
+ const assignmentQueue = buildAssignmentQueue(challenge, reviewerStatesById, now, policy);
+ const blockers = [];
+ const warnings = [];
+
+ for (const state of reviewerStates) {
+ const assignedOpen = state.openAssignments > 0;
+ if (assignedOpen && state.isUnavailable) {
+ addBlocker(blockers, "assigned_reviewer_unavailable", `${state.displayName} cannot complete an assigned challenge review.`, {
+ reviewerId: state.reviewerId,
+ status: state.status,
+ isBlackout: state.isBlackout,
+ inactiveHours: state.inactiveHours,
+ openAssignments: state.openAssignments
+ });
+ }
+ if (assignedOpen && state.isOverloaded) {
+ addBlocker(blockers, "assigned_reviewer_overloaded", `${state.displayName} is above reviewer capacity policy.`, {
+ reviewerId: state.reviewerId,
+ utilization: state.utilization,
+ openAssignments: state.openAssignments,
+ weeklyBudget: state.weeklyBudget,
+ committedHours: state.committedHours
+ });
+ } else if (assignedOpen && state.utilization >= policy.nearCapacityUtilization) {
+ addWarning(warnings, "reviewer_near_capacity", `${state.displayName} is near reviewer capacity.`, {
+ reviewerId: state.reviewerId,
+ utilization: state.utilization
+ });
+ }
+
+ for (const stale of state.staleAssignments) {
+ const code = stale.isCritical ? "critical_stale_review" : "assignment_past_due";
+ const add = stale.isCritical ? addBlocker : addWarning;
+ add(
+ stale.isCritical ? blockers : warnings,
+ code,
+ `${state.displayName} has a ${stale.hoursLate}-hour stale challenge review.`,
+ stale
+ );
+ }
+ }
+
+ for (const criterion of coverage) {
+ if (!criterion.hasEnoughCoverage) {
+ addBlocker(
+ blockers,
+ "criterion_capacity_gap",
+ `${criterion.label} has ${criterion.eligibleReviewers.length} eligible reviewers, below the required ${criterion.minReviewers}.`,
+ {
+ criterionId: criterion.criterionId,
+ eligibleReviewers: criterion.eligibleReviewers,
+ minReviewers: criterion.minReviewers
+ }
+ );
+ }
+ }
+
+ for (const assignment of assignmentQueue) {
+ if (assignment.dueBucket === "due_soon" && !assignment.reviewerUnavailable && !assignment.reviewerOverloaded) {
+ addWarning(warnings, "assignment_due_soon", `${assignment.assignmentId} is due within ${policy.dueSoonHours} hours.`, {
+ assignmentId: assignment.assignmentId,
+ reviewerId: assignment.reviewerId,
+ criterionId: assignment.criterionId,
+ hoursUntilDue: assignment.hoursUntilDue
+ });
+ }
+ }
+
+ const activeArbitrators = challenge.reviewers
+ .filter((reviewer) => {
+ const state = reviewerStatesById.get(reviewer.id);
+ return reviewer.canArbitrate && !state.isUnavailable && !state.isOverloaded;
+ })
+ .map((reviewer) => reviewer.id);
+ if (policy.arbitrationBackupRequired && activeArbitrators.length < 2) {
+ addBlocker(blockers, "arbitration_backup_gap", "Challenge lacks two active, under-capacity arbitration reviewers.", {
+ activeArbitrators,
+ requiredArbitrators: 2
+ });
+ }
+
+ const summary = {
+ challengeId,
+ title: challenge.title,
+ status:
+ blockers.length > 0
+ ? "hold_review_window"
+ : warnings.length > 0
+ ? "conditional_review_ready"
+ : "ready_for_review",
+ evaluatedAt: now.toISOString(),
+ blockers: blockers.length,
+ warnings: warnings.length,
+ reviewers: reviewerStates.length,
+ openAssignments: assignmentQueue.length,
+ criteriaWithCapacityGaps: coverage.filter((criterion) => !criterion.hasEnoughCoverage).length,
+ activeArbitrators: activeArbitrators.length
+ };
+
+ const result = {
+ summary,
+ policy,
+ reviewerStates,
+ criterionCoverage: coverage,
+ assignmentQueue,
+ blockers,
+ warnings,
+ escalationPlan: buildEscalationPlan(blockers, warnings, challenge)
+ };
+ return {
+ ...result,
+ auditDigest: digest(result)
+ };
+}
+
+function renderMarkdown(result) {
+ const lines = [];
+ lines.push(`# Challenge Reviewer Workload SLA Report`);
+ lines.push("");
+ lines.push(`Challenge: ${result.summary.title} (${result.summary.challengeId})`);
+ lines.push(`Status: ${result.summary.status}`);
+ lines.push(`Audit digest: ${result.auditDigest}`);
+ lines.push("");
+ lines.push("## Decision Summary");
+ lines.push("");
+ lines.push("| Metric | Value |");
+ lines.push("| --- | ---: |");
+ lines.push(`| Blockers | ${result.summary.blockers} |`);
+ lines.push(`| Warnings | ${result.summary.warnings} |`);
+ lines.push(`| Open review assignments | ${result.summary.openAssignments} |`);
+ lines.push(`| Criteria with capacity gaps | ${result.summary.criteriaWithCapacityGaps} |`);
+ lines.push(`| Active arbitrators | ${result.summary.activeArbitrators} |`);
+ lines.push("");
+ lines.push("## Reviewer Load");
+ lines.push("");
+ lines.push("| Reviewer | Open | Utilization | State |");
+ lines.push("| --- | ---: | ---: | --- |");
+ for (const reviewer of result.reviewerStates) {
+ const state = reviewer.isUnavailable
+ ? "unavailable"
+ : reviewer.isOverloaded
+ ? "overloaded"
+ : reviewer.utilization >= result.policy.nearCapacityUtilization
+ ? "near capacity"
+ : "available";
+ lines.push(`| ${reviewer.displayName} | ${reviewer.openAssignments} | ${Math.round(reviewer.utilization * 100)}% | ${state} |`);
+ }
+ lines.push("");
+ lines.push("## Rubric Coverage");
+ lines.push("");
+ lines.push("| Criterion | Eligible Reviewers | Required | Decision |");
+ lines.push("| --- | ---: | ---: | --- |");
+ for (const criterion of result.criterionCoverage) {
+ lines.push(
+ `| ${criterion.label} | ${criterion.eligibleReviewers.length} | ${criterion.minReviewers} | ${
+ criterion.hasEnoughCoverage ? "covered" : "gap"
+ } |`
+ );
+ }
+ lines.push("");
+ lines.push("## Blockers");
+ lines.push("");
+ if (result.blockers.length === 0) {
+ lines.push("No blockers.");
+ } else {
+ for (const blocker of result.blockers) {
+ lines.push(`- ${blocker.code}: ${blocker.message}`);
+ }
+ }
+ lines.push("");
+ lines.push("## Escalation Plan");
+ lines.push("");
+ if (result.escalationPlan.length === 0) {
+ lines.push("No escalation actions required.");
+ } else {
+ for (const action of result.escalationPlan) {
+ lines.push(`- ${action.priority}: ${action.action} Owner: ${action.owner}.`);
+ }
+ }
+ lines.push("");
+ lines.push("Synthetic data only. No private reviewer records, payment rails, identity systems, or external services are used.");
+ lines.push("");
+ return lines.join("\n");
+}
+
+function renderSvg(result) {
+ const blockerWidth = Math.min(440, result.summary.blockers * 44);
+ const warningWidth = Math.min(440, result.summary.warnings * 44);
+ const readyWidth = result.summary.status === "ready_for_review" ? 440 : result.summary.status === "conditional_review_ready" ? 260 : 120;
+ const rows = result.reviewerStates
+ .slice(0, 5)
+ .map((reviewer, index) => {
+ const y = 205 + index * 30;
+ const load = Math.min(260, Math.round(reviewer.utilization * 260));
+ const fill = reviewer.isUnavailable || reviewer.isOverloaded ? "#d94f45" : reviewer.utilization >= 0.75 ? "#c9962f" : "#2f8f68";
+ return [
+ `${escapeXml(reviewer.displayName)}`,
+ ``,
+ ``,
+ `${Math.round(reviewer.utilization * 100)}%`
+ ].join("\n ");
+ })
+ .join("\n ");
+ return `
+`;
+}
+
+function escapeXml(value) {
+ return String(value)
+ .replace(/&/g, "&")
+ .replace(//g, ">")
+ .replace(/"/g, """);
+}
+
+module.exports = {
+ evaluateChallengeReviewerWorkload,
+ renderMarkdown,
+ renderSvg,
+ stableStringify,
+ digest
+};
diff --git a/challenge-reviewer-workload-sla-guard/make-demo-video.js b/challenge-reviewer-workload-sla-guard/make-demo-video.js
new file mode 100644
index 00000000..d035f65a
--- /dev/null
+++ b/challenge-reviewer-workload-sla-guard/make-demo-video.js
@@ -0,0 +1,310 @@
+const fs = require("node:fs");
+const path = require("node:path");
+const { evaluateChallengeReviewerWorkload } = require("./index");
+const { riskyChallenge } = require("./sample-data");
+
+const WIDTH = 480;
+const HEIGHT = 270;
+const FPS = 2;
+const SLIDE_SECONDS = 2;
+const BG = [246, 248, 244];
+const INK = [25, 42, 47];
+const MUTED = [76, 92, 97];
+const RED = [217, 79, 69];
+const AMBER = [201, 150, 47];
+const GREEN = [47, 143, 104];
+const LINE = [184, 197, 194];
+const WHITE = [255, 255, 255];
+
+const FONT = {
+ " ": ["00000", "00000", "00000", "00000", "00000", "00000", "00000"],
+ "-": ["00000", "00000", "00000", "11110", "00000", "00000", "00000"],
+ ".": ["00000", "00000", "00000", "00000", "00000", "01100", "01100"],
+ ":": ["00000", "01100", "01100", "00000", "01100", "01100", "00000"],
+ "/": ["00001", "00010", "00100", "01000", "10000", "00000", "00000"],
+ "%": ["11001", "11010", "00100", "01000", "10110", "00110", "00000"],
+ "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", "11110", "00001", "00001", "10001", "01110"],
+ "6": ["00110", "01000", "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", "00010", "11100"],
+ 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", "01110"],
+ H: ["10001", "10001", "10001", "11111", "10001", "10001", "10001"],
+ I: ["01110", "00100", "00100", "00100", "00100", "00100", "01110"],
+ J: ["00111", "00010", "00010", "00010", "00010", "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 setPixel(frame, x, y, color) {
+ if (x < 0 || y < 0 || x >= WIDTH || y >= HEIGHT) {
+ return;
+ }
+ const offset = (y * WIDTH + x) * 3;
+ frame[offset] = color[0];
+ frame[offset + 1] = color[1];
+ frame[offset + 2] = color[2];
+}
+
+function rect(frame, x, y, width, height, color) {
+ for (let row = y; row < y + height; row += 1) {
+ for (let col = x; col < x + width; col += 1) {
+ setPixel(frame, col, row, color);
+ }
+ }
+}
+
+function text(frame, value, x, y, scale, color) {
+ const chars = String(value).toUpperCase();
+ let cursor = x;
+ for (const char of chars) {
+ const glyph = FONT[char] || FONT[" "];
+ for (let gy = 0; gy < glyph.length; gy += 1) {
+ for (let gx = 0; gx < glyph[gy].length; gx += 1) {
+ if (glyph[gy][gx] === "1") {
+ rect(frame, cursor + gx * scale, y + gy * scale, scale, scale, color);
+ }
+ }
+ }
+ cursor += 6 * scale;
+ }
+}
+
+function baseFrame() {
+ const frame = Buffer.alloc(WIDTH * HEIGHT * 3);
+ for (let i = 0; i < frame.length; i += 3) {
+ frame[i] = BG[0];
+ frame[i + 1] = BG[1];
+ frame[i + 2] = BG[2];
+ }
+ rect(frame, 14, 14, WIDTH - 28, HEIGHT - 28, WHITE);
+ rect(frame, 14, 14, WIDTH - 28, 2, LINE);
+ rect(frame, 14, HEIGHT - 16, WIDTH - 28, 2, LINE);
+ rect(frame, 14, 14, 2, HEIGHT - 28, LINE);
+ rect(frame, WIDTH - 16, 14, 2, HEIGHT - 28, LINE);
+ return frame;
+}
+
+function bar(frame, x, y, width, label, value, color) {
+ text(frame, label, x, y, 2, MUTED);
+ rect(frame, x, y + 20, 270, 12, [217, 226, 228]);
+ rect(frame, x, y + 20, Math.max(5, Math.round(270 * value)), 12, color);
+ text(frame, `${Math.round(value * 100)}%`, x + 285, y + 15, 2, INK);
+}
+
+function makeSlides(result) {
+ const loadRows = result.reviewerStates.slice(0, 4);
+ const slides = [];
+
+ let frame = baseFrame();
+ text(frame, "REVIEWER SLA GUARD", 36, 45, 3, INK);
+ text(frame, "SCIENTIFIC BOUNTY SYSTEM", 38, 78, 2, MUTED);
+ text(frame, result.summary.status, 38, 126, 3, RED);
+ text(frame, `${result.summary.blockers} BLOCKERS`, 38, 168, 2, RED);
+ text(frame, `${result.summary.warnings} WARNINGS`, 38, 194, 2, AMBER);
+ slides.push(frame);
+
+ frame = baseFrame();
+ text(frame, "REVIEWER LOAD", 36, 44, 3, INK);
+ loadRows.forEach((reviewer, index) => {
+ const color = reviewer.isUnavailable || reviewer.isOverloaded ? RED : reviewer.utilization >= 0.75 ? AMBER : GREEN;
+ bar(frame, 40, 86 + index * 40, 270, reviewer.displayName, Math.min(1, reviewer.utilization), color);
+ });
+ slides.push(frame);
+
+ frame = baseFrame();
+ text(frame, "RUBRIC COVERAGE", 36, 44, 3, INK);
+ result.criterionCoverage.forEach((criterion, index) => {
+ const ok = criterion.hasEnoughCoverage;
+ const y = 92 + index * 44;
+ text(frame, criterion.label, 40, y, 2, INK);
+ text(frame, `${criterion.eligibleReviewers.length}/${criterion.minReviewers}`, 350, y, 2, ok ? GREEN : RED);
+ });
+ slides.push(frame);
+
+ frame = baseFrame();
+ text(frame, "STALE REVIEWS", 36, 44, 3, INK);
+ result.blockers
+ .filter((blocker) => blocker.code === "critical_stale_review")
+ .slice(0, 3)
+ .forEach((blocker, index) => {
+ text(frame, blocker.evidence.assignmentId, 42, 92 + index * 45, 2, INK);
+ text(frame, `${blocker.evidence.hoursLate} H LATE`, 42, 116 + index * 45, 2, RED);
+ });
+ text(frame, "AWARD RELEASE HELD", 42, 218, 2, RED);
+ slides.push(frame);
+
+ frame = baseFrame();
+ text(frame, "ESCALATION", 36, 44, 3, INK);
+ result.escalationPlan.slice(0, 4).forEach((action, index) => {
+ text(frame, action.priority, 42, 90 + index * 36, 2, action.priority === "critical" ? RED : AMBER);
+ text(frame, action.owner, 160, 90 + index * 36, 2, INK);
+ });
+ text(frame, "REASSIGN BEFORE SCORING", 42, 226, 2, MUTED);
+ slides.push(frame);
+
+ frame = baseFrame();
+ text(frame, "REVIEW ARTIFACTS", 36, 44, 3, INK);
+ text(frame, "JSON PACKET", 42, 96, 2, GREEN);
+ text(frame, "MARKDOWN REPORT", 42, 128, 2, GREEN);
+ text(frame, "SVG SUMMARY", 42, 160, 2, GREEN);
+ text(frame, "DEMO VIDEO", 42, 192, 2, GREEN);
+ text(frame, result.auditDigest.slice(0, 20), 42, 230, 2, MUTED);
+ slides.push(frame);
+
+ const frames = [];
+ for (const slide of slides) {
+ for (let i = 0; i < FPS * SLIDE_SECONDS; i += 1) {
+ frames.push(slide);
+ }
+ }
+ return frames;
+}
+
+function writeUInt32(value) {
+ const buffer = Buffer.alloc(4);
+ buffer.writeUInt32LE(value >>> 0, 0);
+ return buffer;
+}
+
+function writeInt32(value) {
+ const buffer = Buffer.alloc(4);
+ buffer.writeInt32LE(value, 0);
+ return buffer;
+}
+
+function writeUInt16(value) {
+ const buffer = Buffer.alloc(2);
+ buffer.writeUInt16LE(value, 0);
+ return buffer;
+}
+
+function chunk(id, payload) {
+ const pad = payload.length % 2 === 1 ? Buffer.from([0]) : Buffer.alloc(0);
+ return Buffer.concat([Buffer.from(id, "ascii"), writeUInt32(payload.length), payload, pad]);
+}
+
+function list(type, payload) {
+ return chunk("LIST", Buffer.concat([Buffer.from(type, "ascii"), payload]));
+}
+
+function rgbToBgrBottomUp(frame) {
+ const stride = Math.ceil((WIDTH * 3) / 4) * 4;
+ const out = Buffer.alloc(stride * HEIGHT);
+ for (let y = 0; y < HEIGHT; y += 1) {
+ const sourceY = HEIGHT - 1 - y;
+ for (let x = 0; x < WIDTH; x += 1) {
+ const source = (sourceY * WIDTH + x) * 3;
+ const target = y * stride + x * 3;
+ out[target] = frame[source + 2];
+ out[target + 1] = frame[source + 1];
+ out[target + 2] = frame[source];
+ }
+ }
+ return out;
+}
+
+function avi(frames) {
+ const framePayloads = frames.map(rgbToBgrBottomUp);
+ const frameSize = framePayloads[0].length;
+ const avih = Buffer.concat([
+ writeUInt32(Math.round(1000000 / FPS)),
+ writeUInt32(frameSize * FPS),
+ writeUInt32(0),
+ writeUInt32(0x10),
+ writeUInt32(frames.length),
+ writeUInt32(0),
+ writeUInt32(1),
+ writeUInt32(frameSize),
+ writeUInt32(WIDTH),
+ writeUInt32(HEIGHT),
+ writeUInt32(0),
+ writeUInt32(0),
+ writeUInt32(0),
+ writeUInt32(0)
+ ]);
+ const strh = Buffer.concat([
+ Buffer.from("vids", "ascii"),
+ Buffer.from("DIB ", "ascii"),
+ writeUInt32(0),
+ writeUInt16(0),
+ writeUInt16(0),
+ writeUInt32(0),
+ writeUInt32(1),
+ writeUInt32(FPS),
+ writeUInt32(0),
+ writeUInt32(frames.length),
+ writeUInt32(frameSize),
+ writeUInt32(0xffffffff),
+ writeUInt32(0),
+ writeInt32(0),
+ writeInt32(0),
+ writeInt32(WIDTH),
+ writeInt32(HEIGHT)
+ ]);
+ const strf = Buffer.concat([
+ writeUInt32(40),
+ writeInt32(WIDTH),
+ writeInt32(HEIGHT),
+ writeUInt16(1),
+ writeUInt16(24),
+ writeUInt32(0),
+ writeUInt32(frameSize),
+ writeInt32(2835),
+ writeInt32(2835),
+ writeUInt32(0),
+ writeUInt32(0)
+ ]);
+ const hdrl = list("hdrl", Buffer.concat([chunk("avih", avih), list("strl", Buffer.concat([chunk("strh", strh), chunk("strf", strf)]))]));
+
+ let offset = 4;
+ const idxEntries = [];
+ const frameChunks = [];
+ for (const payload of framePayloads) {
+ const frameChunk = chunk("00db", payload);
+ frameChunks.push(frameChunk);
+ idxEntries.push(Buffer.concat([Buffer.from("00db", "ascii"), writeUInt32(0x10), writeUInt32(offset), writeUInt32(payload.length)]));
+ offset += frameChunk.length;
+ }
+ const movi = list("movi", Buffer.concat(frameChunks));
+ const idx1 = chunk("idx1", Buffer.concat(idxEntries));
+ const body = Buffer.concat([Buffer.from("AVI ", "ascii"), hdrl, movi, idx1]);
+ return Buffer.concat([Buffer.from("RIFF", "ascii"), writeUInt32(body.length), body]);
+}
+
+function main() {
+ const reportsDir = path.join(__dirname, "reports");
+ fs.mkdirSync(reportsDir, { recursive: true });
+ const result = evaluateChallengeReviewerWorkload(riskyChallenge);
+ const video = avi(makeSlides(result));
+ const target = path.join(reportsDir, "demo.avi");
+ fs.writeFileSync(target, video);
+ console.log(`generated ${path.relative(process.cwd(), target)} (${video.length} bytes)`);
+}
+
+main();
diff --git a/challenge-reviewer-workload-sla-guard/package.json b/challenge-reviewer-workload-sla-guard/package.json
new file mode 100644
index 00000000..1054bed5
--- /dev/null
+++ b/challenge-reviewer-workload-sla-guard/package.json
@@ -0,0 +1,13 @@
+{
+ "name": "challenge-reviewer-workload-sla-guard",
+ "version": "1.0.0",
+ "private": true,
+ "description": "Scientific bounty reviewer workload and SLA guard",
+ "scripts": {
+ "check": "node --check index.js && node --check sample-data.js && node --check test.js && node --check demo.js && node --check make-demo-video.js",
+ "test": "node test.js",
+ "demo": "node demo.js",
+ "demo:video": "node make-demo-video.js"
+ },
+ "license": "MIT"
+}
diff --git a/challenge-reviewer-workload-sla-guard/reports/demo-transcript.md b/challenge-reviewer-workload-sla-guard/reports/demo-transcript.md
new file mode 100644
index 00000000..1175dca5
--- /dev/null
+++ b/challenge-reviewer-workload-sla-guard/reports/demo-transcript.md
@@ -0,0 +1,22 @@
+# Demo Transcript
+
+Fresh verification run: 2026-06-15.
+
+Commands run from `challenge-reviewer-workload-sla-guard/`:
+
+```bash
+npm run check
+npm test
+npm run demo
+```
+
+Observed output:
+
+```text
+challenge-reviewer-workload-sla-guard tests passed (4)
+status=hold_review_window blockers=9 warnings=3 capacityGaps=3 digest=4eeac8cabcce4c5c
+```
+
+The demo writes the reviewer packet, markdown report, and SVG summary under `reports/`. The branch also includes `reports/demo.avi` from `npm run demo:video`.
+
+All data is synthetic; no identity, reviewer, payment, challenge workspace, or external API calls are made.
diff --git a/challenge-reviewer-workload-sla-guard/reports/demo.avi b/challenge-reviewer-workload-sla-guard/reports/demo.avi
new file mode 100644
index 00000000..1d1ee4bd
Binary files /dev/null and b/challenge-reviewer-workload-sla-guard/reports/demo.avi differ
diff --git a/challenge-reviewer-workload-sla-guard/reports/summary.svg b/challenge-reviewer-workload-sla-guard/reports/summary.svg
new file mode 100644
index 00000000..29e2d796
--- /dev/null
+++ b/challenge-reviewer-workload-sla-guard/reports/summary.svg
@@ -0,0 +1,41 @@
+
diff --git a/challenge-reviewer-workload-sla-guard/reports/workload-review-packet.json b/challenge-reviewer-workload-sla-guard/reports/workload-review-packet.json
new file mode 100644
index 00000000..fcab5cd7
--- /dev/null
+++ b/challenge-reviewer-workload-sla-guard/reports/workload-review-packet.json
@@ -0,0 +1,782 @@
+{
+ "riskyResult": {
+ "summary": {
+ "challengeId": "sci-bounty-review-2026-05",
+ "title": "Private climate-model challenge final review",
+ "status": "hold_review_window",
+ "evaluatedAt": "2026-05-27T17:00:00.000Z",
+ "blockers": 9,
+ "warnings": 3,
+ "reviewers": 5,
+ "openAssignments": 5,
+ "criteriaWithCapacityGaps": 3,
+ "activeArbitrators": 1
+ },
+ "policy": {
+ "maxReviewerUtilization": 0.85,
+ "nearCapacityUtilization": 0.75,
+ "minEligibleReviewersPerCriterion": 2,
+ "staleReviewHours": 24,
+ "dueSoonHours": 12,
+ "inactiveReviewerHours": 72,
+ "now": "2026-05-27T17:00:00.000Z",
+ "escalationLead": "challenge-ops-lead",
+ "arbitrationBackupRequired": true
+ },
+ "reviewerStates": [
+ {
+ "reviewerId": "rev-ada",
+ "displayName": "Ada River",
+ "expertise": [
+ "climate-modeling",
+ "reproducibility"
+ ],
+ "roles": [
+ "reviewer"
+ ],
+ "canArbitrate": false,
+ "status": "active",
+ "contactWindowHours": 8,
+ "weeklyBudget": 10,
+ "committedHours": 13,
+ "openAssignments": 2,
+ "utilization": 1.3,
+ "isBlackout": false,
+ "inactiveHours": 3,
+ "isInactive": false,
+ "isUnavailable": false,
+ "isOverloaded": true,
+ "staleAssignments": [
+ {
+ "assignmentId": "assign-forecast-ada",
+ "reviewerId": "rev-ada",
+ "criterionId": "forecast-skill",
+ "dueAt": "2026-05-27T09:00:00.000Z",
+ "hoursLate": 8,
+ "isCritical": false
+ }
+ ]
+ },
+ {
+ "reviewerId": "rev-ben",
+ "displayName": "Ben Cross",
+ "expertise": [
+ "ml-validation"
+ ],
+ "roles": [
+ "reviewer"
+ ],
+ "canArbitrate": false,
+ "status": "active",
+ "contactWindowHours": 10,
+ "weeklyBudget": 12,
+ "committedHours": 9,
+ "openAssignments": 1,
+ "utilization": 0.75,
+ "isBlackout": true,
+ "inactiveHours": 4.5,
+ "isInactive": false,
+ "isUnavailable": true,
+ "isOverloaded": false,
+ "staleAssignments": [
+ {
+ "assignmentId": "assign-ml-ben",
+ "reviewerId": "rev-ben",
+ "criterionId": "ml-validation",
+ "dueAt": "2026-05-26T09:00:00.000Z",
+ "hoursLate": 32,
+ "isCritical": true
+ }
+ ]
+ },
+ {
+ "reviewerId": "rev-cora",
+ "displayName": "Cora Field",
+ "expertise": [
+ "climate-modeling",
+ "ml-validation"
+ ],
+ "roles": [
+ "reviewer",
+ "arbitrator"
+ ],
+ "canArbitrate": true,
+ "status": "active",
+ "contactWindowHours": 4,
+ "weeklyBudget": 16,
+ "committedHours": 9,
+ "openAssignments": 1,
+ "utilization": 0.563,
+ "isBlackout": false,
+ "inactiveHours": 1,
+ "isInactive": false,
+ "isUnavailable": false,
+ "isOverloaded": false,
+ "staleAssignments": []
+ },
+ {
+ "reviewerId": "rev-drew",
+ "displayName": "Drew Lane",
+ "expertise": [
+ "reproducibility"
+ ],
+ "roles": [
+ "reviewer"
+ ],
+ "canArbitrate": false,
+ "status": "active",
+ "contactWindowHours": 12,
+ "weeklyBudget": 8,
+ "committedHours": 5,
+ "openAssignments": 1,
+ "utilization": 0.625,
+ "isBlackout": false,
+ "inactiveHours": 124,
+ "isInactive": true,
+ "isUnavailable": true,
+ "isOverloaded": false,
+ "staleAssignments": [
+ {
+ "assignmentId": "assign-repro-drew",
+ "reviewerId": "rev-drew",
+ "criterionId": "reproducibility",
+ "dueAt": "2026-05-25T16:00:00.000Z",
+ "hoursLate": 49,
+ "isCritical": true
+ }
+ ]
+ },
+ {
+ "reviewerId": "rev-eli",
+ "displayName": "Eli Stone",
+ "expertise": [
+ "methodology"
+ ],
+ "roles": [
+ "arbitrator"
+ ],
+ "canArbitrate": true,
+ "status": "active",
+ "contactWindowHours": 6,
+ "weeklyBudget": 6,
+ "committedHours": 6,
+ "openAssignments": 0,
+ "utilization": 1,
+ "isBlackout": false,
+ "inactiveHours": 1.8,
+ "isInactive": false,
+ "isUnavailable": false,
+ "isOverloaded": true,
+ "staleAssignments": []
+ }
+ ],
+ "criterionCoverage": [
+ {
+ "criterionId": "forecast-skill",
+ "label": "Forecast skill and uncertainty",
+ "requiredExpertise": "climate-modeling",
+ "minReviewers": 2,
+ "eligibleReviewers": [
+ "rev-cora"
+ ],
+ "assignedReviewers": [
+ "rev-ada",
+ "rev-cora"
+ ],
+ "hasEnoughCoverage": false
+ },
+ {
+ "criterionId": "ml-validation",
+ "label": "Machine-learning validation",
+ "requiredExpertise": "ml-validation",
+ "minReviewers": 2,
+ "eligibleReviewers": [
+ "rev-cora"
+ ],
+ "assignedReviewers": [
+ "rev-ben"
+ ],
+ "hasEnoughCoverage": false
+ },
+ {
+ "criterionId": "reproducibility",
+ "label": "Reproducibility package",
+ "requiredExpertise": "reproducibility",
+ "minReviewers": 2,
+ "eligibleReviewers": [],
+ "assignedReviewers": [
+ "rev-ada",
+ "rev-drew"
+ ],
+ "hasEnoughCoverage": false
+ }
+ ],
+ "assignmentQueue": [
+ {
+ "assignmentId": "assign-repro-drew",
+ "reviewerId": "rev-drew",
+ "criterionId": "reproducibility",
+ "status": "in_review",
+ "dueAt": "2026-05-25T16:00:00.000Z",
+ "estimatedHours": 3,
+ "dueBucket": "past_due",
+ "hoursUntilDue": -49,
+ "reviewerUnavailable": true,
+ "reviewerOverloaded": false
+ },
+ {
+ "assignmentId": "assign-ml-ben",
+ "reviewerId": "rev-ben",
+ "criterionId": "ml-validation",
+ "status": "in_review",
+ "dueAt": "2026-05-26T09:00:00.000Z",
+ "estimatedHours": 4,
+ "dueBucket": "past_due",
+ "hoursUntilDue": -32,
+ "reviewerUnavailable": true,
+ "reviewerOverloaded": false
+ },
+ {
+ "assignmentId": "assign-forecast-ada",
+ "reviewerId": "rev-ada",
+ "criterionId": "forecast-skill",
+ "status": "in_review",
+ "dueAt": "2026-05-27T09:00:00.000Z",
+ "estimatedHours": 3,
+ "dueBucket": "past_due",
+ "hoursUntilDue": -8,
+ "reviewerUnavailable": false,
+ "reviewerOverloaded": true
+ },
+ {
+ "assignmentId": "assign-forecast-cora",
+ "reviewerId": "rev-cora",
+ "criterionId": "forecast-skill",
+ "status": "queued",
+ "dueAt": "2026-05-28T03:00:00.000Z",
+ "estimatedHours": 3,
+ "dueBucket": "due_soon",
+ "hoursUntilDue": 10,
+ "reviewerUnavailable": false,
+ "reviewerOverloaded": false
+ },
+ {
+ "assignmentId": "assign-repro-ada",
+ "reviewerId": "rev-ada",
+ "criterionId": "reproducibility",
+ "status": "queued",
+ "dueAt": "2026-05-28T04:00:00.000Z",
+ "estimatedHours": 2,
+ "dueBucket": "due_soon",
+ "hoursUntilDue": 11,
+ "reviewerUnavailable": false,
+ "reviewerOverloaded": true
+ }
+ ],
+ "blockers": [
+ {
+ "code": "assigned_reviewer_overloaded",
+ "message": "Ada River is above reviewer capacity policy.",
+ "evidence": {
+ "reviewerId": "rev-ada",
+ "utilization": 1.3,
+ "openAssignments": 2,
+ "weeklyBudget": 10,
+ "committedHours": 13
+ }
+ },
+ {
+ "code": "assigned_reviewer_unavailable",
+ "message": "Ben Cross cannot complete an assigned challenge review.",
+ "evidence": {
+ "reviewerId": "rev-ben",
+ "status": "active",
+ "isBlackout": true,
+ "inactiveHours": 4.5,
+ "openAssignments": 1
+ }
+ },
+ {
+ "code": "critical_stale_review",
+ "message": "Ben Cross has a 32-hour stale challenge review.",
+ "evidence": {
+ "assignmentId": "assign-ml-ben",
+ "reviewerId": "rev-ben",
+ "criterionId": "ml-validation",
+ "dueAt": "2026-05-26T09:00:00.000Z",
+ "hoursLate": 32,
+ "isCritical": true
+ }
+ },
+ {
+ "code": "assigned_reviewer_unavailable",
+ "message": "Drew Lane cannot complete an assigned challenge review.",
+ "evidence": {
+ "reviewerId": "rev-drew",
+ "status": "active",
+ "isBlackout": false,
+ "inactiveHours": 124,
+ "openAssignments": 1
+ }
+ },
+ {
+ "code": "critical_stale_review",
+ "message": "Drew Lane has a 49-hour stale challenge review.",
+ "evidence": {
+ "assignmentId": "assign-repro-drew",
+ "reviewerId": "rev-drew",
+ "criterionId": "reproducibility",
+ "dueAt": "2026-05-25T16:00:00.000Z",
+ "hoursLate": 49,
+ "isCritical": true
+ }
+ },
+ {
+ "code": "criterion_capacity_gap",
+ "message": "Forecast skill and uncertainty has 1 eligible reviewers, below the required 2.",
+ "evidence": {
+ "criterionId": "forecast-skill",
+ "eligibleReviewers": [
+ "rev-cora"
+ ],
+ "minReviewers": 2
+ }
+ },
+ {
+ "code": "criterion_capacity_gap",
+ "message": "Machine-learning validation has 1 eligible reviewers, below the required 2.",
+ "evidence": {
+ "criterionId": "ml-validation",
+ "eligibleReviewers": [
+ "rev-cora"
+ ],
+ "minReviewers": 2
+ }
+ },
+ {
+ "code": "criterion_capacity_gap",
+ "message": "Reproducibility package has 0 eligible reviewers, below the required 2.",
+ "evidence": {
+ "criterionId": "reproducibility",
+ "eligibleReviewers": [],
+ "minReviewers": 2
+ }
+ },
+ {
+ "code": "arbitration_backup_gap",
+ "message": "Challenge lacks two active, under-capacity arbitration reviewers.",
+ "evidence": {
+ "activeArbitrators": [
+ "rev-cora"
+ ],
+ "requiredArbitrators": 2
+ }
+ }
+ ],
+ "warnings": [
+ {
+ "code": "assignment_past_due",
+ "message": "Ada River has a 8-hour stale challenge review.",
+ "evidence": {
+ "assignmentId": "assign-forecast-ada",
+ "reviewerId": "rev-ada",
+ "criterionId": "forecast-skill",
+ "dueAt": "2026-05-27T09:00:00.000Z",
+ "hoursLate": 8,
+ "isCritical": false
+ }
+ },
+ {
+ "code": "reviewer_near_capacity",
+ "message": "Ben Cross is near reviewer capacity.",
+ "evidence": {
+ "reviewerId": "rev-ben",
+ "utilization": 0.75
+ }
+ },
+ {
+ "code": "assignment_due_soon",
+ "message": "assign-forecast-cora is due within 12 hours.",
+ "evidence": {
+ "assignmentId": "assign-forecast-cora",
+ "reviewerId": "rev-cora",
+ "criterionId": "forecast-skill",
+ "hoursUntilDue": 10
+ }
+ }
+ ],
+ "escalationPlan": [
+ {
+ "priority": "critical",
+ "owner": "challenge-ops-lead",
+ "action": "Reassign blocked reviews to eligible backups before sponsor scoring opens.",
+ "evidence": {
+ "reviewerId": "rev-ben",
+ "status": "active",
+ "isBlackout": true,
+ "inactiveHours": 4.5,
+ "openAssignments": 1
+ }
+ },
+ {
+ "priority": "critical",
+ "owner": "challenge-ops-lead",
+ "action": "Hold award release and request same-day replacement review or arbitration signoff.",
+ "evidence": {
+ "assignmentId": "assign-ml-ben",
+ "reviewerId": "rev-ben",
+ "criterionId": "ml-validation",
+ "dueAt": "2026-05-26T09:00:00.000Z",
+ "hoursLate": 32,
+ "isCritical": true
+ }
+ },
+ {
+ "priority": "critical",
+ "owner": "challenge-ops-lead",
+ "action": "Reassign blocked reviews to eligible backups before sponsor scoring opens.",
+ "evidence": {
+ "reviewerId": "rev-drew",
+ "status": "active",
+ "isBlackout": false,
+ "inactiveHours": 124,
+ "openAssignments": 1
+ }
+ },
+ {
+ "priority": "critical",
+ "owner": "challenge-ops-lead",
+ "action": "Hold award release and request same-day replacement review or arbitration signoff.",
+ "evidence": {
+ "assignmentId": "assign-repro-drew",
+ "reviewerId": "rev-drew",
+ "criterionId": "reproducibility",
+ "dueAt": "2026-05-25T16:00:00.000Z",
+ "hoursLate": 49,
+ "isCritical": true
+ }
+ },
+ {
+ "priority": "high",
+ "owner": "challenge-ops-lead",
+ "action": "Recruit additional eligible reviewers for uncovered rubric criteria.",
+ "evidence": {
+ "criterionId": "forecast-skill",
+ "eligibleReviewers": [
+ "rev-cora"
+ ],
+ "minReviewers": 2
+ }
+ },
+ {
+ "priority": "high",
+ "owner": "challenge-ops-lead",
+ "action": "Recruit additional eligible reviewers for uncovered rubric criteria.",
+ "evidence": {
+ "criterionId": "ml-validation",
+ "eligibleReviewers": [
+ "rev-cora"
+ ],
+ "minReviewers": 2
+ }
+ },
+ {
+ "priority": "high",
+ "owner": "challenge-ops-lead",
+ "action": "Recruit additional eligible reviewers for uncovered rubric criteria.",
+ "evidence": {
+ "criterionId": "reproducibility",
+ "eligibleReviewers": [],
+ "minReviewers": 2
+ }
+ },
+ {
+ "priority": "high",
+ "owner": "challenge-ops-lead",
+ "action": "Name a conflict-free backup arbitrator before any contested decision is released.",
+ "evidence": {
+ "activeArbitrators": [
+ "rev-cora"
+ ],
+ "requiredArbitrators": 2
+ }
+ },
+ {
+ "priority": "medium",
+ "owner": "challenge-ops-lead",
+ "action": "Keep a standby reviewer warm for near-capacity assignments.",
+ "evidence": {
+ "reviewerId": "rev-ben",
+ "utilization": 0.75
+ }
+ },
+ {
+ "priority": "medium",
+ "owner": "challenge-ops-lead",
+ "action": "Send due-soon reminders with a replacement deadline.",
+ "evidence": {
+ "assignmentId": "assign-forecast-cora",
+ "reviewerId": "rev-cora",
+ "criterionId": "forecast-skill",
+ "hoursUntilDue": 10
+ }
+ }
+ ],
+ "auditDigest": "4eeac8cabcce4c5cb4bb5f428e6a7da88c6fd46586d57f163c096207e5c1237f"
+ },
+ "cleanResult": {
+ "summary": {
+ "challengeId": "sci-bounty-review-clean",
+ "title": "Private climate-model challenge final review",
+ "status": "ready_for_review",
+ "evaluatedAt": "2026-05-27T17:00:00.000Z",
+ "blockers": 0,
+ "warnings": 0,
+ "reviewers": 5,
+ "openAssignments": 4,
+ "criteriaWithCapacityGaps": 0,
+ "activeArbitrators": 3
+ },
+ "policy": {
+ "maxReviewerUtilization": 0.85,
+ "nearCapacityUtilization": 0.75,
+ "minEligibleReviewersPerCriterion": 2,
+ "staleReviewHours": 24,
+ "dueSoonHours": 12,
+ "inactiveReviewerHours": 72,
+ "now": "2026-05-27T17:00:00.000Z",
+ "escalationLead": "challenge-ops-lead",
+ "arbitrationBackupRequired": true
+ },
+ "reviewerStates": [
+ {
+ "reviewerId": "rev-ada",
+ "displayName": "Ada River",
+ "expertise": [
+ "climate-modeling",
+ "reproducibility"
+ ],
+ "roles": [
+ "reviewer"
+ ],
+ "canArbitrate": false,
+ "status": "active",
+ "contactWindowHours": 8,
+ "weeklyBudget": 20,
+ "committedHours": 6,
+ "openAssignments": 1,
+ "utilization": 0.3,
+ "isBlackout": false,
+ "inactiveHours": 3,
+ "isInactive": false,
+ "isUnavailable": false,
+ "isOverloaded": false,
+ "staleAssignments": []
+ },
+ {
+ "reviewerId": "rev-ben",
+ "displayName": "Ben Cross",
+ "expertise": [
+ "ml-validation",
+ "reproducibility"
+ ],
+ "roles": [
+ "reviewer"
+ ],
+ "canArbitrate": false,
+ "status": "active",
+ "contactWindowHours": 10,
+ "weeklyBudget": 18,
+ "committedHours": 7,
+ "openAssignments": 1,
+ "utilization": 0.389,
+ "isBlackout": false,
+ "inactiveHours": 4.5,
+ "isInactive": false,
+ "isUnavailable": false,
+ "isOverloaded": false,
+ "staleAssignments": []
+ },
+ {
+ "reviewerId": "rev-cora",
+ "displayName": "Cora Field",
+ "expertise": [
+ "climate-modeling",
+ "ml-validation"
+ ],
+ "roles": [
+ "reviewer",
+ "arbitrator"
+ ],
+ "canArbitrate": true,
+ "status": "active",
+ "contactWindowHours": 4,
+ "weeklyBudget": 16,
+ "committedHours": 8,
+ "openAssignments": 1,
+ "utilization": 0.5,
+ "isBlackout": false,
+ "inactiveHours": 1,
+ "isInactive": false,
+ "isUnavailable": false,
+ "isOverloaded": false,
+ "staleAssignments": []
+ },
+ {
+ "reviewerId": "rev-drew",
+ "displayName": "Drew Lane",
+ "expertise": [
+ "reproducibility",
+ "ml-validation"
+ ],
+ "roles": [
+ "reviewer",
+ "arbitrator"
+ ],
+ "canArbitrate": true,
+ "status": "active",
+ "contactWindowHours": 12,
+ "weeklyBudget": 14,
+ "committedHours": 5,
+ "openAssignments": 1,
+ "utilization": 0.357,
+ "isBlackout": false,
+ "inactiveHours": 4,
+ "isInactive": false,
+ "isUnavailable": false,
+ "isOverloaded": false,
+ "staleAssignments": []
+ },
+ {
+ "reviewerId": "rev-eli",
+ "displayName": "Eli Stone",
+ "expertise": [
+ "reproducibility",
+ "ml-validation",
+ "climate-modeling"
+ ],
+ "roles": [
+ "reviewer",
+ "arbitrator"
+ ],
+ "canArbitrate": true,
+ "status": "active",
+ "contactWindowHours": 6,
+ "weeklyBudget": 12,
+ "committedHours": 2,
+ "openAssignments": 0,
+ "utilization": 0.167,
+ "isBlackout": false,
+ "inactiveHours": 1.8,
+ "isInactive": false,
+ "isUnavailable": false,
+ "isOverloaded": false,
+ "staleAssignments": []
+ }
+ ],
+ "criterionCoverage": [
+ {
+ "criterionId": "forecast-skill",
+ "label": "Forecast skill and uncertainty",
+ "requiredExpertise": "climate-modeling",
+ "minReviewers": 2,
+ "eligibleReviewers": [
+ "rev-ada",
+ "rev-cora",
+ "rev-eli"
+ ],
+ "assignedReviewers": [
+ "rev-ada",
+ "rev-cora"
+ ],
+ "hasEnoughCoverage": true
+ },
+ {
+ "criterionId": "ml-validation",
+ "label": "Machine-learning validation",
+ "requiredExpertise": "ml-validation",
+ "minReviewers": 2,
+ "eligibleReviewers": [
+ "rev-ben",
+ "rev-cora",
+ "rev-drew",
+ "rev-eli"
+ ],
+ "assignedReviewers": [
+ "rev-ben"
+ ],
+ "hasEnoughCoverage": true
+ },
+ {
+ "criterionId": "reproducibility",
+ "label": "Reproducibility package",
+ "requiredExpertise": "reproducibility",
+ "minReviewers": 2,
+ "eligibleReviewers": [
+ "rev-ada",
+ "rev-ben",
+ "rev-drew",
+ "rev-eli"
+ ],
+ "assignedReviewers": [
+ "rev-drew"
+ ],
+ "hasEnoughCoverage": true
+ }
+ ],
+ "assignmentQueue": [
+ {
+ "assignmentId": "assign-forecast-ada",
+ "reviewerId": "rev-ada",
+ "criterionId": "forecast-skill",
+ "status": "queued",
+ "dueAt": "2026-05-28T12:00:00.000Z",
+ "estimatedHours": 2,
+ "dueBucket": "on_track",
+ "hoursUntilDue": 19,
+ "reviewerUnavailable": false,
+ "reviewerOverloaded": false
+ },
+ {
+ "assignmentId": "assign-ml-ben",
+ "reviewerId": "rev-ben",
+ "criterionId": "ml-validation",
+ "status": "queued",
+ "dueAt": "2026-05-28T15:00:00.000Z",
+ "estimatedHours": 3,
+ "dueBucket": "on_track",
+ "hoursUntilDue": 22,
+ "reviewerUnavailable": false,
+ "reviewerOverloaded": false
+ },
+ {
+ "assignmentId": "assign-forecast-cora",
+ "reviewerId": "rev-cora",
+ "criterionId": "forecast-skill",
+ "status": "queued",
+ "dueAt": "2026-05-28T18:00:00.000Z",
+ "estimatedHours": 2,
+ "dueBucket": "on_track",
+ "hoursUntilDue": 25,
+ "reviewerUnavailable": false,
+ "reviewerOverloaded": false
+ },
+ {
+ "assignmentId": "assign-repro-drew",
+ "reviewerId": "rev-drew",
+ "criterionId": "reproducibility",
+ "status": "queued",
+ "dueAt": "2026-05-28T20:00:00.000Z",
+ "estimatedHours": 2,
+ "dueBucket": "on_track",
+ "hoursUntilDue": 27,
+ "reviewerUnavailable": false,
+ "reviewerOverloaded": false
+ }
+ ],
+ "blockers": [],
+ "warnings": [],
+ "escalationPlan": [],
+ "auditDigest": "517b3424ac09f74731fb4245800bd0ae763d46d437e65a39b28d5142af6b39a9"
+ }
+}
diff --git a/challenge-reviewer-workload-sla-guard/reports/workload-review-report.md b/challenge-reviewer-workload-sla-guard/reports/workload-review-report.md
new file mode 100644
index 00000000..be89539a
--- /dev/null
+++ b/challenge-reviewer-workload-sla-guard/reports/workload-review-report.md
@@ -0,0 +1,60 @@
+# Challenge Reviewer Workload SLA Report
+
+Challenge: Private climate-model challenge final review (sci-bounty-review-2026-05)
+Status: hold_review_window
+Audit digest: 4eeac8cabcce4c5cb4bb5f428e6a7da88c6fd46586d57f163c096207e5c1237f
+
+## Decision Summary
+
+| Metric | Value |
+| --- | ---: |
+| Blockers | 9 |
+| Warnings | 3 |
+| Open review assignments | 5 |
+| Criteria with capacity gaps | 3 |
+| Active arbitrators | 1 |
+
+## Reviewer Load
+
+| Reviewer | Open | Utilization | State |
+| --- | ---: | ---: | --- |
+| Ada River | 2 | 130% | overloaded |
+| Ben Cross | 1 | 75% | unavailable |
+| Cora Field | 1 | 56% | available |
+| Drew Lane | 1 | 63% | unavailable |
+| Eli Stone | 0 | 100% | overloaded |
+
+## Rubric Coverage
+
+| Criterion | Eligible Reviewers | Required | Decision |
+| --- | ---: | ---: | --- |
+| Forecast skill and uncertainty | 1 | 2 | gap |
+| Machine-learning validation | 1 | 2 | gap |
+| Reproducibility package | 0 | 2 | gap |
+
+## Blockers
+
+- assigned_reviewer_overloaded: Ada River is above reviewer capacity policy.
+- assigned_reviewer_unavailable: Ben Cross cannot complete an assigned challenge review.
+- critical_stale_review: Ben Cross has a 32-hour stale challenge review.
+- assigned_reviewer_unavailable: Drew Lane cannot complete an assigned challenge review.
+- critical_stale_review: Drew Lane has a 49-hour stale challenge review.
+- criterion_capacity_gap: Forecast skill and uncertainty has 1 eligible reviewers, below the required 2.
+- criterion_capacity_gap: Machine-learning validation has 1 eligible reviewers, below the required 2.
+- criterion_capacity_gap: Reproducibility package has 0 eligible reviewers, below the required 2.
+- arbitration_backup_gap: Challenge lacks two active, under-capacity arbitration reviewers.
+
+## Escalation Plan
+
+- critical: Reassign blocked reviews to eligible backups before sponsor scoring opens. Owner: challenge-ops-lead.
+- critical: Hold award release and request same-day replacement review or arbitration signoff. Owner: challenge-ops-lead.
+- critical: Reassign blocked reviews to eligible backups before sponsor scoring opens. Owner: challenge-ops-lead.
+- critical: Hold award release and request same-day replacement review or arbitration signoff. Owner: challenge-ops-lead.
+- high: Recruit additional eligible reviewers for uncovered rubric criteria. Owner: challenge-ops-lead.
+- high: Recruit additional eligible reviewers for uncovered rubric criteria. Owner: challenge-ops-lead.
+- high: Recruit additional eligible reviewers for uncovered rubric criteria. Owner: challenge-ops-lead.
+- high: Name a conflict-free backup arbitrator before any contested decision is released. Owner: challenge-ops-lead.
+- medium: Keep a standby reviewer warm for near-capacity assignments. Owner: challenge-ops-lead.
+- medium: Send due-soon reminders with a replacement deadline. Owner: challenge-ops-lead.
+
+Synthetic data only. No private reviewer records, payment rails, identity systems, or external services are used.
diff --git a/challenge-reviewer-workload-sla-guard/requirements-map.md b/challenge-reviewer-workload-sla-guard/requirements-map.md
new file mode 100644
index 00000000..487a8d69
--- /dev/null
+++ b/challenge-reviewer-workload-sla-guard/requirements-map.md
@@ -0,0 +1,14 @@
+# Requirements Map
+
+| Issue #18 capability | Coverage in this slice |
+| --- | --- |
+| Platform-mediated arbitration system | Confirms the challenge has active, under-capacity arbitration backup before contested decisions can be released. |
+| Optional third-party reviewers or peer validators | Validates reviewer availability, expertise, blackout windows, stale activity, assignment load, and review capacity. |
+| Evaluation criteria and scoring rubric | Checks each rubric criterion has enough eligible scientific reviewers before live scoring opens. |
+| Milestone deadlines | Flags due-soon, past-due, and critically stale review assignments against challenge review windows. |
+| Feedback loop between submitters and sponsors | Produces deterministic escalation actions so challenge operations can reassign reviews before sponsor scoring or award communication. |
+| Create trust on both sides | Holds award release when stale or overloaded review coverage could undermine solver and sponsor trust. |
+
+## Non-Overlap Boundary
+
+This submission focuses on reviewer workload, assignment capacity, stale review windows, and arbitration backup readiness for scientific bounty challenges. It does not implement a full marketplace, scoring engine, payout router, appeal ledger, data-room access guard, anti-collusion system, benchmark leakage detector, cancellation workflow, or evaluator calibration bench.
diff --git a/challenge-reviewer-workload-sla-guard/sample-data.js b/challenge-reviewer-workload-sla-guard/sample-data.js
new file mode 100644
index 00000000..b549992a
--- /dev/null
+++ b/challenge-reviewer-workload-sla-guard/sample-data.js
@@ -0,0 +1,323 @@
+const riskyChallenge = {
+ challengeId: "sci-bounty-review-2026-05",
+ title: "Private climate-model challenge final review",
+ policies: {
+ now: "2026-05-27T17:00:00.000Z",
+ escalationLead: "challenge-ops-lead",
+ maxReviewerUtilization: 0.85,
+ nearCapacityUtilization: 0.75,
+ minEligibleReviewersPerCriterion: 2,
+ staleReviewHours: 24,
+ dueSoonHours: 12,
+ inactiveReviewerHours: 72,
+ arbitrationBackupRequired: true
+ },
+ criteria: [
+ {
+ id: "forecast-skill",
+ label: "Forecast skill and uncertainty",
+ requiredExpertise: "climate-modeling",
+ minReviewers: 2
+ },
+ {
+ id: "ml-validation",
+ label: "Machine-learning validation",
+ requiredExpertise: "ml-validation",
+ minReviewers: 2
+ },
+ {
+ id: "reproducibility",
+ label: "Reproducibility package",
+ requiredExpertise: "reproducibility",
+ minReviewers: 2
+ }
+ ],
+ submissions: [
+ {
+ id: "sub-lake-effect",
+ teamLabel: "team-anonymous-17",
+ criteriaTouched: ["forecast-skill", "ml-validation", "reproducibility"],
+ submittedAt: "2026-05-24T14:20:00.000Z"
+ },
+ {
+ id: "sub-regional-grid",
+ teamLabel: "team-anonymous-31",
+ criteriaTouched: ["forecast-skill", "reproducibility"],
+ submittedAt: "2026-05-24T18:35:00.000Z"
+ }
+ ],
+ reviewers: [
+ {
+ id: "rev-ada",
+ displayName: "Ada River",
+ roles: ["reviewer"],
+ expertise: ["climate-modeling", "reproducibility"],
+ canArbitrate: false,
+ contactWindowHours: 8,
+ lastActiveAt: "2026-05-27T14:00:00.000Z",
+ availability: {
+ status: "active",
+ weeklyHourBudget: 10,
+ committedHours: 8,
+ maxConcurrentReviews: 2,
+ blackoutWindows: []
+ },
+ assignments: [
+ {
+ id: "assign-forecast-ada",
+ challengeId: "sci-bounty-review-2026-05",
+ criterionId: "forecast-skill",
+ dueAt: "2026-05-27T09:00:00.000Z",
+ estimatedHours: 3,
+ status: "in_review"
+ },
+ {
+ id: "assign-repro-ada",
+ challengeId: "sci-bounty-review-2026-05",
+ criterionId: "reproducibility",
+ dueAt: "2026-05-28T04:00:00.000Z",
+ estimatedHours: 2,
+ status: "queued"
+ }
+ ]
+ },
+ {
+ id: "rev-ben",
+ displayName: "Ben Cross",
+ roles: ["reviewer"],
+ expertise: ["ml-validation"],
+ canArbitrate: false,
+ contactWindowHours: 10,
+ lastActiveAt: "2026-05-27T12:30:00.000Z",
+ availability: {
+ status: "active",
+ weeklyHourBudget: 12,
+ committedHours: 5,
+ maxConcurrentReviews: 3,
+ blackoutWindows: [
+ {
+ startsAt: "2026-05-27T00:00:00.000Z",
+ endsAt: "2026-05-29T00:00:00.000Z",
+ reason: "field work"
+ }
+ ]
+ },
+ assignments: [
+ {
+ id: "assign-ml-ben",
+ challengeId: "sci-bounty-review-2026-05",
+ criterionId: "ml-validation",
+ dueAt: "2026-05-26T09:00:00.000Z",
+ estimatedHours: 4,
+ status: "in_review"
+ }
+ ]
+ },
+ {
+ id: "rev-cora",
+ displayName: "Cora Field",
+ roles: ["reviewer", "arbitrator"],
+ expertise: ["climate-modeling", "ml-validation"],
+ canArbitrate: true,
+ contactWindowHours: 4,
+ lastActiveAt: "2026-05-27T16:00:00.000Z",
+ availability: {
+ status: "active",
+ weeklyHourBudget: 16,
+ committedHours: 6,
+ maxConcurrentReviews: 3,
+ blackoutWindows: []
+ },
+ assignments: [
+ {
+ id: "assign-forecast-cora",
+ challengeId: "sci-bounty-review-2026-05",
+ criterionId: "forecast-skill",
+ dueAt: "2026-05-28T03:00:00.000Z",
+ estimatedHours: 3,
+ status: "queued"
+ }
+ ]
+ },
+ {
+ id: "rev-drew",
+ displayName: "Drew Lane",
+ roles: ["reviewer"],
+ expertise: ["reproducibility"],
+ canArbitrate: false,
+ contactWindowHours: 12,
+ lastActiveAt: "2026-05-22T13:00:00.000Z",
+ availability: {
+ status: "active",
+ weeklyHourBudget: 8,
+ committedHours: 2,
+ maxConcurrentReviews: 2,
+ blackoutWindows: []
+ },
+ assignments: [
+ {
+ id: "assign-repro-drew",
+ challengeId: "sci-bounty-review-2026-05",
+ criterionId: "reproducibility",
+ dueAt: "2026-05-25T16:00:00.000Z",
+ estimatedHours: 3,
+ status: "in_review"
+ }
+ ]
+ },
+ {
+ id: "rev-eli",
+ displayName: "Eli Stone",
+ roles: ["arbitrator"],
+ expertise: ["methodology"],
+ canArbitrate: true,
+ contactWindowHours: 6,
+ lastActiveAt: "2026-05-27T15:10:00.000Z",
+ availability: {
+ status: "active",
+ weeklyHourBudget: 6,
+ committedHours: 6,
+ maxConcurrentReviews: 1,
+ blackoutWindows: []
+ },
+ assignments: []
+ }
+ ]
+};
+
+const cleanChallenge = {
+ ...riskyChallenge,
+ challengeId: "sci-bounty-review-clean",
+ policies: {
+ ...riskyChallenge.policies,
+ now: "2026-05-27T17:00:00.000Z"
+ },
+ reviewers: [
+ {
+ id: "rev-ada",
+ displayName: "Ada River",
+ roles: ["reviewer"],
+ expertise: ["climate-modeling", "reproducibility"],
+ canArbitrate: false,
+ contactWindowHours: 8,
+ lastActiveAt: "2026-05-27T14:00:00.000Z",
+ availability: {
+ status: "active",
+ weeklyHourBudget: 20,
+ committedHours: 4,
+ maxConcurrentReviews: 4,
+ blackoutWindows: []
+ },
+ assignments: [
+ {
+ id: "assign-forecast-ada",
+ challengeId: "sci-bounty-review-clean",
+ criterionId: "forecast-skill",
+ dueAt: "2026-05-28T12:00:00.000Z",
+ estimatedHours: 2,
+ status: "queued"
+ }
+ ]
+ },
+ {
+ id: "rev-ben",
+ displayName: "Ben Cross",
+ roles: ["reviewer"],
+ expertise: ["ml-validation", "reproducibility"],
+ canArbitrate: false,
+ contactWindowHours: 10,
+ lastActiveAt: "2026-05-27T12:30:00.000Z",
+ availability: {
+ status: "active",
+ weeklyHourBudget: 18,
+ committedHours: 4,
+ maxConcurrentReviews: 4,
+ blackoutWindows: []
+ },
+ assignments: [
+ {
+ id: "assign-ml-ben",
+ challengeId: "sci-bounty-review-clean",
+ criterionId: "ml-validation",
+ dueAt: "2026-05-28T15:00:00.000Z",
+ estimatedHours: 3,
+ status: "queued"
+ }
+ ]
+ },
+ {
+ id: "rev-cora",
+ displayName: "Cora Field",
+ roles: ["reviewer", "arbitrator"],
+ expertise: ["climate-modeling", "ml-validation"],
+ canArbitrate: true,
+ contactWindowHours: 4,
+ lastActiveAt: "2026-05-27T16:00:00.000Z",
+ availability: {
+ status: "active",
+ weeklyHourBudget: 16,
+ committedHours: 6,
+ maxConcurrentReviews: 3,
+ blackoutWindows: []
+ },
+ assignments: [
+ {
+ id: "assign-forecast-cora",
+ challengeId: "sci-bounty-review-clean",
+ criterionId: "forecast-skill",
+ dueAt: "2026-05-28T18:00:00.000Z",
+ estimatedHours: 2,
+ status: "queued"
+ }
+ ]
+ },
+ {
+ id: "rev-drew",
+ displayName: "Drew Lane",
+ roles: ["reviewer", "arbitrator"],
+ expertise: ["reproducibility", "ml-validation"],
+ canArbitrate: true,
+ contactWindowHours: 12,
+ lastActiveAt: "2026-05-27T13:00:00.000Z",
+ availability: {
+ status: "active",
+ weeklyHourBudget: 14,
+ committedHours: 3,
+ maxConcurrentReviews: 3,
+ blackoutWindows: []
+ },
+ assignments: [
+ {
+ id: "assign-repro-drew",
+ challengeId: "sci-bounty-review-clean",
+ criterionId: "reproducibility",
+ dueAt: "2026-05-28T20:00:00.000Z",
+ estimatedHours: 2,
+ status: "queued"
+ }
+ ]
+ },
+ {
+ id: "rev-eli",
+ displayName: "Eli Stone",
+ roles: ["reviewer", "arbitrator"],
+ expertise: ["reproducibility", "ml-validation", "climate-modeling"],
+ canArbitrate: true,
+ contactWindowHours: 6,
+ lastActiveAt: "2026-05-27T15:10:00.000Z",
+ availability: {
+ status: "active",
+ weeklyHourBudget: 12,
+ committedHours: 2,
+ maxConcurrentReviews: 3,
+ blackoutWindows: []
+ },
+ assignments: []
+ }
+ ]
+};
+
+module.exports = {
+ riskyChallenge,
+ cleanChallenge
+};
diff --git a/challenge-reviewer-workload-sla-guard/test.js b/challenge-reviewer-workload-sla-guard/test.js
new file mode 100644
index 00000000..32d459fd
--- /dev/null
+++ b/challenge-reviewer-workload-sla-guard/test.js
@@ -0,0 +1,63 @@
+const assert = require("node:assert/strict");
+const { evaluateChallengeReviewerWorkload, digest } = require("./index");
+const { riskyChallenge, cleanChallenge } = require("./sample-data");
+
+function codes(items) {
+ return new Set(items.map((item) => item.code));
+}
+
+function testRiskyChallengeHoldsReviewWindow() {
+ const result = evaluateChallengeReviewerWorkload(riskyChallenge);
+ const blockerCodes = codes(result.blockers);
+
+ assert.equal(result.summary.status, "hold_review_window");
+ assert.ok(blockerCodes.has("assigned_reviewer_unavailable"));
+ assert.ok(blockerCodes.has("assigned_reviewer_overloaded"));
+ assert.ok(blockerCodes.has("critical_stale_review"));
+ assert.ok(blockerCodes.has("criterion_capacity_gap"));
+ assert.ok(blockerCodes.has("arbitration_backup_gap"));
+ assert.ok(result.escalationPlan.length >= 4);
+}
+
+function testCleanChallengeIsReady() {
+ const result = evaluateChallengeReviewerWorkload(cleanChallenge);
+
+ assert.equal(result.summary.status, "ready_for_review");
+ assert.equal(result.blockers.length, 0);
+ assert.equal(result.warnings.length, 0);
+ assert.equal(result.summary.criteriaWithCapacityGaps, 0);
+ assert.ok(result.summary.activeArbitrators >= 2);
+}
+
+function testDueSoonWarningIsConditional() {
+ const challenge = structuredClone(cleanChallenge);
+ challenge.reviewers[0].assignments[0].dueAt = "2026-05-28T02:00:00.000Z";
+
+ const result = evaluateChallengeReviewerWorkload(challenge);
+
+ assert.equal(result.summary.status, "conditional_review_ready");
+ assert.ok(codes(result.warnings).has("assignment_due_soon"));
+ assert.equal(result.blockers.length, 0);
+}
+
+function testDigestIsStableAndSensitive() {
+ const first = evaluateChallengeReviewerWorkload(cleanChallenge);
+ const second = evaluateChallengeReviewerWorkload(cleanChallenge);
+ const changed = structuredClone(cleanChallenge);
+ changed.reviewers[0].availability.committedHours = 8;
+ const third = evaluateChallengeReviewerWorkload(changed);
+
+ assert.equal(first.auditDigest, second.auditDigest);
+ assert.notEqual(first.auditDigest, third.auditDigest);
+ assert.equal(digest({ b: 2, a: 1 }), digest({ a: 1, b: 2 }));
+}
+
+function run() {
+ testRiskyChallengeHoldsReviewWindow();
+ testCleanChallengeIsReady();
+ testDueSoonWarningIsConditional();
+ testDigestIsStableAndSensitive();
+ console.log("challenge-reviewer-workload-sla-guard tests passed (4)");
+}
+
+run();