From e33820a5adb92f4b94cf968ccab49032a9f4104f Mon Sep 17 00:00:00 2001 From: Keon Kim Date: Sun, 31 May 2026 15:47:30 +0900 Subject: [PATCH] Add scientific bounty escrow readiness guard --- .../README.md | 34 +++ .../demo.js | 30 ++ .../index.js | 273 ++++++++++++++++++ .../render-video.js | 48 +++ .../reports/demo.mp4 | Bin 0 -> 42422 bytes .../reports/escrow-readiness-review.json | 87 ++++++ .../reports/escrow-readiness-review.md | 55 ++++ .../reports/escrow-readiness-review.svg | 30 ++ .../sample-data.js | 70 +++++ .../test.js | 54 ++++ 10 files changed, 681 insertions(+) create mode 100644 scientific-bounty-escrow-readiness-guard/README.md create mode 100644 scientific-bounty-escrow-readiness-guard/demo.js create mode 100644 scientific-bounty-escrow-readiness-guard/index.js create mode 100644 scientific-bounty-escrow-readiness-guard/render-video.js create mode 100644 scientific-bounty-escrow-readiness-guard/reports/demo.mp4 create mode 100644 scientific-bounty-escrow-readiness-guard/reports/escrow-readiness-review.json create mode 100644 scientific-bounty-escrow-readiness-guard/reports/escrow-readiness-review.md create mode 100644 scientific-bounty-escrow-readiness-guard/reports/escrow-readiness-review.svg create mode 100644 scientific-bounty-escrow-readiness-guard/sample-data.js create mode 100644 scientific-bounty-escrow-readiness-guard/test.js diff --git a/scientific-bounty-escrow-readiness-guard/README.md b/scientific-bounty-escrow-readiness-guard/README.md new file mode 100644 index 00000000..b4a1f9a2 --- /dev/null +++ b/scientific-bounty-escrow-readiness-guard/README.md @@ -0,0 +1,34 @@ +# Scientific Bounty Escrow Readiness Guard + +Self-contained reviewer artifact for the Scientific Bounty System bounty. + +This module checks whether a scientific challenge has enough funded escrow +coverage before teams are allowed to invest time in submissions. It focuses on +funding readiness, not review scoring, duplicate detection, scope control, or +final payout routing. + +## Scope + +- Validate total prize amount, milestone allocations, sponsor deposit, platform + reserve, honorable mention budget, cancellation/refund window, and team split + coverage. +- Produce deterministic publish/hold/remediation decisions before submissions + open. +- Use synthetic data only. No payment rails, bank accounts, wallets, private + sponsor data, off-ramp claims, exchange claims, or external APIs. + +## Commands + +```bash +node scientific-bounty-escrow-readiness-guard/test.js +node scientific-bounty-escrow-readiness-guard/demo.js +node scientific-bounty-escrow-readiness-guard/render-video.js +node --check scientific-bounty-escrow-readiness-guard/index.js +node --check scientific-bounty-escrow-readiness-guard/sample-data.js +node --check scientific-bounty-escrow-readiness-guard/test.js +node --check scientific-bounty-escrow-readiness-guard/demo.js +node --check scientific-bounty-escrow-readiness-guard/render-video.js +``` + +The demo writes JSON, Markdown, SVG, and MP4 reviewer artifacts under +`scientific-bounty-escrow-readiness-guard/reports/`. diff --git a/scientific-bounty-escrow-readiness-guard/demo.js b/scientific-bounty-escrow-readiness-guard/demo.js new file mode 100644 index 00000000..27e1b8d5 --- /dev/null +++ b/scientific-bounty-escrow-readiness-guard/demo.js @@ -0,0 +1,30 @@ +const fs = require("fs"); +const path = require("path"); +const { + evaluateEscrowReadinessBatch, + renderMarkdownReport, + renderSvgReport, +} = require("./index"); +const { challenges } = require("./sample-data"); + +const reportDir = path.join(__dirname, "reports"); +fs.mkdirSync(reportDir, { recursive: true }); + +const report = evaluateEscrowReadinessBatch(challenges); + +fs.writeFileSync( + path.join(reportDir, "escrow-readiness-review.json"), + `${JSON.stringify(report, null, 2)}\n` +); +fs.writeFileSync( + path.join(reportDir, "escrow-readiness-review.md"), + `${renderMarkdownReport(report)}\n` +); +fs.writeFileSync( + path.join(reportDir, "escrow-readiness-review.svg"), + renderSvgReport(report) +); + +console.log( + `wrote ${report.summary.total} escrow readiness decisions to ${path.relative(process.cwd(), reportDir)}` +); diff --git a/scientific-bounty-escrow-readiness-guard/index.js b/scientific-bounty-escrow-readiness-guard/index.js new file mode 100644 index 00000000..a0a31eb6 --- /dev/null +++ b/scientific-bounty-escrow-readiness-guard/index.js @@ -0,0 +1,273 @@ +const crypto = require("crypto"); + +function money(value) { + return Math.round(Number(value || 0) * 100) / 100; +} + +function sum(values) { + return money(values.reduce((total, value) => total + Number(value || 0), 0)); +} + +function requiredCoverage(challenge) { + return money( + challenge.prizeAmount + + challenge.platformReserve + + challenge.honorableMentionBudget + + challenge.cancellationRefundHold + ); +} + +function milestoneTotal(challenge) { + return money(sum((challenge.milestones || []).map((item) => item.percent))); +} + +function validateMilestoneSchedule(challenge) { + const blockers = []; + const warnings = []; + const milestones = challenge.milestones || []; + + const total = milestoneTotal(challenge); + if (Math.abs(total - 1) > 0.0001) { + blockers.push(`milestone percentages sum to ${total}, expected 1`); + } + + let previousDueDay = 0; + for (const milestone of milestones) { + if (!milestone.id) { + blockers.push("milestone is missing an id"); + } + if (milestone.percent <= 0) { + blockers.push(`milestone ${milestone.id || "unknown"} has non-positive percent`); + } + if (milestone.dueDay <= previousDueDay) { + blockers.push(`milestone ${milestone.id || "unknown"} is not in due-day order`); + } + previousDueDay = milestone.dueDay; + } + + const finalDue = milestones.length > 0 ? milestones[milestones.length - 1].dueDay : 0; + if (finalDue < challenge.minSubmissionWindowDays) { + warnings.push( + `final milestone day ${finalDue} is shorter than submission window ${challenge.minSubmissionWindowDays}` + ); + } + + return { blockers, warnings }; +} + +function validateTeamSplits(challenge) { + const blockers = []; + const warnings = []; + for (const split of challenge.teamSplits || []) { + const impliedMinimum = split.maxMembers * split.minMemberSharePercent; + if (impliedMinimum > 100) { + blockers.push( + `team ${split.teamId} requires ${impliedMinimum}% minimum member shares` + ); + } else if (split.minMemberSharePercent < 5) { + warnings.push(`team ${split.teamId} permits very small member shares`); + } + } + return { blockers, warnings }; +} + +function evaluateEscrowReadiness(challenge) { + const blockers = []; + const warnings = []; + const evidence = []; + const actions = []; + + const required = requiredCoverage(challenge); + const deposit = money(challenge.sponsorDeposit); + const deficit = money(Math.max(0, required - deposit)); + const surplus = money(Math.max(0, deposit - required)); + + evidence.push(`required coverage ${required} ${challenge.currency}`); + evidence.push(`sponsor deposit ${deposit} ${challenge.currency}`); + + if (deposit < required) { + blockers.push(`deposit is short by ${deficit} ${challenge.currency}`); + actions.push("hold challenge before submissions open"); + actions.push("request sponsor top-up or lower published awards"); + } else { + evidence.push(`funding surplus ${surplus} ${challenge.currency}`); + } + + if (challenge.refundLockedUntilDaysAfterOpen < 14) { + blockers.push( + `refund lock ${challenge.refundLockedUntilDaysAfterOpen} days is shorter than 14 day minimum` + ); + actions.push("extend refund lock before solver work starts"); + } + + if (challenge.honorableMentionBudget > challenge.prizeAmount * 0.25) { + warnings.push("honorable mention budget exceeds 25% of main prize"); + } + + const milestoneCheck = validateMilestoneSchedule(challenge); + blockers.push(...milestoneCheck.blockers); + warnings.push(...milestoneCheck.warnings); + + const teamCheck = validateTeamSplits(challenge); + blockers.push(...teamCheck.blockers); + warnings.push(...teamCheck.warnings); + + if (blockers.length === 0) { + actions.push("publish funding-ready challenge"); + actions.push("freeze escrow coverage snapshot for reviewer audit"); + } + + const decision = + blockers.length > 0 + ? "hold" + : warnings.length > 0 + ? "publish_with_notice" + : "publish"; + + const result = { + challengeId: challenge.id, + title: challenge.title, + sponsor: challenge.sponsor, + currency: challenge.currency, + decision, + requiredCoverage: required, + sponsorDeposit: deposit, + surplus, + deficit, + evidence, + blockers, + warnings, + actions: [...new Set(actions)], + }; + + return { + ...result, + auditDigest: digest(result), + }; +} + +function evaluateEscrowReadinessBatch(challenges) { + const decisions = challenges.map(evaluateEscrowReadiness); + const summary = decisions.reduce( + (acc, item) => { + acc.total += 1; + acc[item.decision] += 1; + acc.totalRequiredCoverage = money( + acc.totalRequiredCoverage + item.requiredCoverage + ); + acc.totalDeposits = money(acc.totalDeposits + item.sponsorDeposit); + acc.totalDeficit = money(acc.totalDeficit + item.deficit); + return acc; + }, + { + total: 0, + publish: 0, + publish_with_notice: 0, + hold: 0, + totalRequiredCoverage: 0, + totalDeposits: 0, + totalDeficit: 0, + } + ); + + return { + generatedAt: new Date().toISOString(), + summary, + decisions, + }; +} + +function digest(value) { + return crypto + .createHash("sha256") + .update(JSON.stringify(value)) + .digest("hex"); +} + +function renderMarkdownReport(report) { + const lines = [ + "# Scientific Bounty Escrow Readiness Report", + "", + `Generated: ${report.generatedAt}`, + "", + "## Summary", + "", + `- Challenges checked: ${report.summary.total}`, + `- Publish: ${report.summary.publish}`, + `- Publish with notice: ${report.summary.publish_with_notice}`, + `- Hold: ${report.summary.hold}`, + `- Required coverage: ${report.summary.totalRequiredCoverage}`, + `- Deposits: ${report.summary.totalDeposits}`, + `- Deficit: ${report.summary.totalDeficit}`, + "", + "## Decisions", + "", + ]; + + for (const item of report.decisions) { + lines.push(`### ${item.challengeId} - ${item.decision}`); + lines.push(""); + lines.push(`- Title: ${item.title}`); + lines.push(`- Sponsor: ${item.sponsor}`); + lines.push(`- Required coverage: ${item.requiredCoverage} ${item.currency}`); + lines.push(`- Sponsor deposit: ${item.sponsorDeposit} ${item.currency}`); + lines.push(`- Deficit: ${item.deficit} ${item.currency}`); + lines.push(`- Audit digest: ${item.auditDigest}`); + lines.push(`- Evidence: ${item.evidence.join("; ")}`); + lines.push(`- Blockers: ${item.blockers.join("; ") || "none"}`); + lines.push(`- Warnings: ${item.warnings.join("; ") || "none"}`); + lines.push(`- Actions: ${item.actions.join("; ")}`); + lines.push(""); + } + + return lines.join("\n"); +} + +function escapeXml(value) { + return String(value) + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """); +} + +function renderSvgReport(report) { + const rows = report.decisions + .map((item, index) => { + const y = 190 + index * 110; + const color = + item.decision === "publish" + ? "#059669" + : item.decision === "publish_with_notice" + ? "#d97706" + : "#dc2626"; + return ` + + + + ${escapeXml(item.challengeId)} - ${escapeXml(item.decision)} + ${escapeXml(item.title.slice(0, 96))} + required ${item.requiredCoverage} / deposit ${item.sponsorDeposit} / deficit ${item.deficit} + `; + }) + .join(""); + + return ` + + + + Scientific Bounty Escrow Readiness + Publish ${report.summary.publish} / Notice ${report.summary.publish_with_notice} / Hold ${report.summary.hold} + Required ${report.summary.totalRequiredCoverage} - deposits ${report.summary.totalDeposits} - deficit ${report.summary.totalDeficit} + ${rows} +`; +} + +module.exports = { + evaluateEscrowReadiness, + evaluateEscrowReadinessBatch, + milestoneTotal, + renderMarkdownReport, + renderSvgReport, + requiredCoverage, +}; diff --git a/scientific-bounty-escrow-readiness-guard/render-video.js b/scientific-bounty-escrow-readiness-guard/render-video.js new file mode 100644 index 00000000..c52e42d7 --- /dev/null +++ b/scientific-bounty-escrow-readiness-guard/render-video.js @@ -0,0 +1,48 @@ +const fs = require("fs"); +const path = require("path"); +const { execFileSync } = require("child_process"); + +const reportDir = path.join(__dirname, "reports"); +const jsonPath = path.join(reportDir, "escrow-readiness-review.json"); +const svgPath = path.join(reportDir, "escrow-readiness-review.svg"); +const framePath = path.join(reportDir, "escrow-readiness-review.png"); +const outputPath = path.join(reportDir, "demo.mp4"); + +if (!fs.existsSync(jsonPath) || !fs.existsSync(svgPath)) { + require("./demo"); +} + +const report = JSON.parse(fs.readFileSync(jsonPath, "utf8")); + +execFileSync( + "rsvg-convert", + ["--width", "1280", "--height", "720", "--output", framePath, svgPath], + { stdio: "inherit" } +); + +execFileSync( + "ffmpeg", + [ + "-y", + "-loop", + "1", + "-framerate", + "25", + "-i", + framePath, + "-t", + "4", + "-pix_fmt", + "yuv420p", + "-vf", + "scale=1280:720", + "-movflags", + "+faststart", + outputPath, + ], + { stdio: "inherit" } +); + +console.log( + `wrote ${path.relative(process.cwd(), outputPath)} for ${report.summary.total} checked challenges` +); diff --git a/scientific-bounty-escrow-readiness-guard/reports/demo.mp4 b/scientific-bounty-escrow-readiness-guard/reports/demo.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..52af82810144cc7cd11a05cceca3f00abfb765fe GIT binary patch literal 42422 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 literal 0 HcmV?d00001 diff --git a/scientific-bounty-escrow-readiness-guard/reports/escrow-readiness-review.json b/scientific-bounty-escrow-readiness-guard/reports/escrow-readiness-review.json new file mode 100644 index 00000000..df9af126 --- /dev/null +++ b/scientific-bounty-escrow-readiness-guard/reports/escrow-readiness-review.json @@ -0,0 +1,87 @@ +{ + "generatedAt": "2026-05-31T06:47:18.878Z", + "summary": { + "total": 3, + "publish": 1, + "publish_with_notice": 0, + "hold": 2, + "totalRequiredCoverage": 303000, + "totalDeposits": 258000, + "totalDeficit": 48000 + }, + "decisions": [ + { + "challengeId": "challenge-funded-biomarker", + "title": "Identify plasma biomarkers from synthetic single-cell data", + "sponsor": "Northbridge Translational Lab", + "currency": "USD", + "decision": "publish", + "requiredCoverage": 138000, + "sponsorDeposit": 141000, + "surplus": 3000, + "deficit": 0, + "evidence": [ + "required coverage 138000 USD", + "sponsor deposit 141000 USD", + "funding surplus 3000 USD" + ], + "blockers": [], + "warnings": [], + "actions": [ + "publish funding-ready challenge", + "freeze escrow coverage snapshot for reviewer audit" + ], + "auditDigest": "0eac7f9b719a766a48c63b44cbb3e2e006162857f8c77bac9f2836d02998450a" + }, + { + "challengeId": "challenge-underfunded-climate", + "title": "Regional wildfire smoke forecasting benchmark", + "sponsor": "Open Climate Fund", + "currency": "USD", + "decision": "hold", + "requiredCoverage": 109000, + "sponsorDeposit": 61000, + "surplus": 0, + "deficit": 48000, + "evidence": [ + "required coverage 109000 USD", + "sponsor deposit 61000 USD" + ], + "blockers": [ + "deposit is short by 48000 USD", + "refund lock 7 days is shorter than 14 day minimum", + "milestone percentages sum to 0.85, expected 1", + "team smoke-lab requires 120% minimum member shares" + ], + "warnings": [], + "actions": [ + "hold challenge before submissions open", + "request sponsor top-up or lower published awards", + "extend refund lock before solver work starts" + ], + "auditDigest": "6ff5610fa8e52c7f7f9b9cc2ece9309b46d4872f5df3c71583256199e98b2f19" + }, + { + "challengeId": "challenge-invalid-milestones", + "title": "Quantum noise reduction synthetic benchmark", + "sponsor": "Qubit Methods Consortium", + "currency": "USD", + "decision": "hold", + "requiredCoverage": 56000, + "sponsorDeposit": 56000, + "surplus": 0, + "deficit": 0, + "evidence": [ + "required coverage 56000 USD", + "sponsor deposit 56000 USD", + "funding surplus 0 USD" + ], + "blockers": [ + "milestone percentages sum to 1.1, expected 1" + ], + "warnings": [], + "actions": [], + "auditDigest": "4b27ee358936909889641b952c810a3d1d644922deb41f5d6e38126f34a9c231" + } + ] +} diff --git a/scientific-bounty-escrow-readiness-guard/reports/escrow-readiness-review.md b/scientific-bounty-escrow-readiness-guard/reports/escrow-readiness-review.md new file mode 100644 index 00000000..752cf1f4 --- /dev/null +++ b/scientific-bounty-escrow-readiness-guard/reports/escrow-readiness-review.md @@ -0,0 +1,55 @@ +# Scientific Bounty Escrow Readiness Report + +Generated: 2026-05-31T06:47:18.878Z + +## Summary + +- Challenges checked: 3 +- Publish: 1 +- Publish with notice: 0 +- Hold: 2 +- Required coverage: 303000 +- Deposits: 258000 +- Deficit: 48000 + +## Decisions + +### challenge-funded-biomarker - publish + +- Title: Identify plasma biomarkers from synthetic single-cell data +- Sponsor: Northbridge Translational Lab +- Required coverage: 138000 USD +- Sponsor deposit: 141000 USD +- Deficit: 0 USD +- Audit digest: 0eac7f9b719a766a48c63b44cbb3e2e006162857f8c77bac9f2836d02998450a +- Evidence: required coverage 138000 USD; sponsor deposit 141000 USD; funding surplus 3000 USD +- Blockers: none +- Warnings: none +- Actions: publish funding-ready challenge; freeze escrow coverage snapshot for reviewer audit + +### challenge-underfunded-climate - hold + +- Title: Regional wildfire smoke forecasting benchmark +- Sponsor: Open Climate Fund +- Required coverage: 109000 USD +- Sponsor deposit: 61000 USD +- Deficit: 48000 USD +- Audit digest: 6ff5610fa8e52c7f7f9b9cc2ece9309b46d4872f5df3c71583256199e98b2f19 +- Evidence: required coverage 109000 USD; sponsor deposit 61000 USD +- Blockers: deposit is short by 48000 USD; refund lock 7 days is shorter than 14 day minimum; milestone percentages sum to 0.85, expected 1; team smoke-lab requires 120% minimum member shares +- Warnings: none +- Actions: hold challenge before submissions open; request sponsor top-up or lower published awards; extend refund lock before solver work starts + +### challenge-invalid-milestones - hold + +- Title: Quantum noise reduction synthetic benchmark +- Sponsor: Qubit Methods Consortium +- Required coverage: 56000 USD +- Sponsor deposit: 56000 USD +- Deficit: 0 USD +- Audit digest: 4b27ee358936909889641b952c810a3d1d644922deb41f5d6e38126f34a9c231 +- Evidence: required coverage 56000 USD; sponsor deposit 56000 USD; funding surplus 0 USD +- Blockers: milestone percentages sum to 1.1, expected 1 +- Warnings: none +- Actions: + diff --git a/scientific-bounty-escrow-readiness-guard/reports/escrow-readiness-review.svg b/scientific-bounty-escrow-readiness-guard/reports/escrow-readiness-review.svg new file mode 100644 index 00000000..7215b6ce --- /dev/null +++ b/scientific-bounty-escrow-readiness-guard/reports/escrow-readiness-review.svg @@ -0,0 +1,30 @@ + + + + + Scientific Bounty Escrow Readiness + Publish 1 / Notice 0 / Hold 2 + Required 303000 - deposits 258000 - deficit 48000 + + + + + challenge-funded-biomarker - publish + Identify plasma biomarkers from synthetic single-cell data + required 138000 / deposit 141000 / deficit 0 + + + + + challenge-underfunded-climate - hold + Regional wildfire smoke forecasting benchmark + required 109000 / deposit 61000 / deficit 48000 + + + + + challenge-invalid-milestones - hold + Quantum noise reduction synthetic benchmark + required 56000 / deposit 56000 / deficit 0 + + \ No newline at end of file diff --git a/scientific-bounty-escrow-readiness-guard/sample-data.js b/scientific-bounty-escrow-readiness-guard/sample-data.js new file mode 100644 index 00000000..4f66ea12 --- /dev/null +++ b/scientific-bounty-escrow-readiness-guard/sample-data.js @@ -0,0 +1,70 @@ +const challenges = [ + { + id: "challenge-funded-biomarker", + title: "Identify plasma biomarkers from synthetic single-cell data", + sponsor: "Northbridge Translational Lab", + status: "draft", + currency: "USD", + prizeAmount: 120000, + platformReserve: 6000, + sponsorDeposit: 141000, + cancellationRefundHold: 3000, + honorableMentionBudget: 9000, + minSubmissionWindowDays: 45, + refundLockedUntilDaysAfterOpen: 21, + milestones: [ + { id: "proposal", percent: 0.15, dueDay: 15 }, + { id: "prototype", percent: 0.35, dueDay: 35 }, + { id: "final", percent: 0.5, dueDay: 60 }, + ], + teamSplits: [ + { teamId: "lab-alpha", maxMembers: 5, minMemberSharePercent: 10 }, + { teamId: "independent-team", maxMembers: 3, minMemberSharePercent: 12 }, + ], + }, + { + id: "challenge-underfunded-climate", + title: "Regional wildfire smoke forecasting benchmark", + sponsor: "Open Climate Fund", + status: "draft", + currency: "USD", + prizeAmount: 80000, + platformReserve: 4000, + sponsorDeposit: 61000, + cancellationRefundHold: 15000, + honorableMentionBudget: 10000, + minSubmissionWindowDays: 28, + refundLockedUntilDaysAfterOpen: 7, + milestones: [ + { id: "proposal", percent: 0.2, dueDay: 12 }, + { id: "prototype", percent: 0.3, dueDay: 24 }, + { id: "final", percent: 0.35, dueDay: 42 }, + ], + teamSplits: [ + { teamId: "smoke-lab", maxMembers: 6, minMemberSharePercent: 20 }, + ], + }, + { + id: "challenge-invalid-milestones", + title: "Quantum noise reduction synthetic benchmark", + sponsor: "Qubit Methods Consortium", + status: "draft", + currency: "USD", + prizeAmount: 50000, + platformReserve: 2500, + sponsorDeposit: 56000, + cancellationRefundHold: 1000, + honorableMentionBudget: 2500, + minSubmissionWindowDays: 35, + refundLockedUntilDaysAfterOpen: 20, + milestones: [ + { id: "prototype", percent: 0.4, dueDay: 20 }, + { id: "final", percent: 0.7, dueDay: 35 }, + ], + teamSplits: [ + { teamId: "quantum-team", maxMembers: 4, minMemberSharePercent: 8 }, + ], + }, +]; + +module.exports = { challenges }; diff --git a/scientific-bounty-escrow-readiness-guard/test.js b/scientific-bounty-escrow-readiness-guard/test.js new file mode 100644 index 00000000..14fe8375 --- /dev/null +++ b/scientific-bounty-escrow-readiness-guard/test.js @@ -0,0 +1,54 @@ +const assert = require("assert"); +const { + evaluateEscrowReadiness, + evaluateEscrowReadinessBatch, + milestoneTotal, + requiredCoverage, +} = require("./index"); +const { challenges } = require("./sample-data"); + +const funded = challenges.find((item) => item.id === "challenge-funded-biomarker"); +assert.strictEqual(requiredCoverage(funded), 138000); +assert.strictEqual(milestoneTotal(funded), 1); + +const fundedDecision = evaluateEscrowReadiness(funded); +assert.strictEqual(fundedDecision.decision, "publish"); +assert.strictEqual(fundedDecision.deficit, 0); +assert.ok(fundedDecision.actions.includes("publish funding-ready challenge")); + +const underfunded = evaluateEscrowReadiness( + challenges.find((item) => item.id === "challenge-underfunded-climate") +); +assert.strictEqual(underfunded.decision, "hold"); +assert.ok(underfunded.deficit > 0); +assert.ok(underfunded.blockers.some((item) => item.includes("deposit is short"))); +assert.ok(underfunded.blockers.some((item) => item.includes("refund lock"))); + +const invalidMilestones = evaluateEscrowReadiness( + challenges.find((item) => item.id === "challenge-invalid-milestones") +); +assert.strictEqual(invalidMilestones.decision, "hold"); +assert.ok( + invalidMilestones.blockers.some((item) => + item.includes("milestone percentages sum") + ) +); + +const report = evaluateEscrowReadinessBatch(challenges); +assert.deepStrictEqual( + { + total: report.summary.total, + publish: report.summary.publish, + publish_with_notice: report.summary.publish_with_notice, + hold: report.summary.hold, + }, + { + total: 3, + publish: 1, + publish_with_notice: 0, + hold: 2, + } +); +assert.strictEqual(report.decisions[0].auditDigest.length, 64); + +console.log("scientific bounty escrow readiness guard tests passed");