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 ` + + + Challenge Reviewer Workload SLA Guard + ${escapeXml(result.summary.challengeId)} - ${escapeXml(result.summary.status)} + + Blockers + + + ${result.summary.blockers} + Warnings + + + ${result.summary.warnings} + ${rows} + Review gate + + + ${escapeXml(result.summary.status)} + Audit digest: ${result.auditDigest.slice(0, 24)}... + + +`; +} + +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 @@ + + + + Challenge Reviewer Workload SLA Guard + sci-bounty-review-2026-05 - hold_review_window + + Blockers + + + 9 + Warnings + + + 3 + Ada River + + + 130% + Ben Cross + + + 75% + Cora Field + + + 56% + Drew Lane + + + 63% + Eli Stone + + + 100% + Review gate + + + hold_review_window + Audit digest: 4eeac8cabcce4c5cb4bb5f42... + + 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();