diff --git a/sponsor-regulatory-attestation-packet-guard/README.md b/sponsor-regulatory-attestation-packet-guard/README.md new file mode 100644 index 00000000..39957d0f --- /dev/null +++ b/sponsor-regulatory-attestation-packet-guard/README.md @@ -0,0 +1,44 @@ +# Sponsor Regulatory Attestation Packet Guard + +This module adds a focused Scientific Bounty System control for SCIBASE issue #18. It evaluates synthetic sponsor compliance packets before a scientific challenge opens for submissions. The goal is to keep sponsor-side regulatory paperwork from being confused with solver payout eligibility, escrow accounting, review scoring, submission packaging, or team split routing. + +The guard is dependency-free and does not call payment systems, sanctions vendors, tax systems, identity providers, sponsor repositories, or external services. It only evaluates local synthetic evidence for sponsor legal identity, authorized signer approval, source-of-funds attestation, restricted-party screening, tax reporting readiness, export-control classification, regulated-domain review, public rules digest, and compliance contact readiness. + +## What It Catches + +- Challenges backed by an unidentified sponsor or missing signer authority evidence. +- Missing or stale source-of-funds attestations. +- Third-party prize funding without an upstream sponsor disclosure digest. +- Blocked, review-needed, stale, or unresolved beneficial-owner sanctions screening. +- High-value prizes without a sponsor-side tax reporting plan. +- Cross-border payout expectations without withholding review. +- Restricted export-control classifications without counsel approval. +- Regulated challenge domains without a regulatory review packet. +- Missing immutable public rules, cancellation/refund language, or compliance contact details. + +## Outputs + +- Challenge statuses: `ready_to_open`, `hold_for_compliance`, or `block_challenge`. +- Overall decision: `ready_to_open`, `hold_for_compliance_review`, or `block_challenge_publication`. +- Deterministic findings with severity, evidence, and remediation. +- Sponsor packet digests and an audit digest for repeatable review. +- JSON, Markdown, SVG, and MP4 demo artifacts under `reports/`. + +## Usage + +```bash +npm run check +npm test +npm run demo +``` + +The demo writes: + +- `reports/sponsor-regulatory-attestation-packet.json` +- `reports/sponsor-regulatory-attestation-report.md` +- `reports/summary.svg` +- `reports/demo.mp4` when `ffmpeg` is available + +## Scope Boundaries + +This is separate from existing issue #18 work on intake, challenge rubrics, submission workspaces, evidence freezes, arbitration, payout routing, team split ledgers, escrow readiness, payout eligibility, duplicate solver detection, sponsor scorecards, review integrity, appeals, deadline fairness, benchmark leakage, data-use agreements, human-subjects compliance, award transparency, and closeout retention. It focuses only on sponsor-side regulatory attestation before a challenge can open lawfully. diff --git a/sponsor-regulatory-attestation-packet-guard/demo.js b/sponsor-regulatory-attestation-packet-guard/demo.js new file mode 100644 index 00000000..37f0010a --- /dev/null +++ b/sponsor-regulatory-attestation-packet-guard/demo.js @@ -0,0 +1,129 @@ +"use strict"; + +const fs = require("fs"); +const path = require("path"); +const { spawnSync } = require("child_process"); +const { + evaluateSponsorAttestation, + renderMarkdownReport, + renderSvgSummary +} = require("./index"); +const sampleData = require("./sample-data"); + +const reportsDir = path.join(__dirname, "reports"); +fs.mkdirSync(reportsDir, { recursive: true }); + +const result = evaluateSponsorAttestation(sampleData); +const jsonPath = path.join(reportsDir, "sponsor-regulatory-attestation-packet.json"); +const markdownPath = path.join(reportsDir, "sponsor-regulatory-attestation-report.md"); +const svgPath = path.join(reportsDir, "summary.svg"); +const mp4Path = path.join(reportsDir, "demo.mp4"); +const framesDir = path.join(reportsDir, "demo-frames"); + +fs.writeFileSync(jsonPath, `${JSON.stringify(result, null, 2)}\n`); +fs.writeFileSync(markdownPath, renderMarkdownReport(result)); +fs.writeFileSync(svgPath, renderSvgSummary(result)); + +function hexToRgb(hex) { + const normalized = hex.replace("#", ""); + return [ + parseInt(normalized.slice(0, 2), 16), + parseInt(normalized.slice(2, 4), 16), + parseInt(normalized.slice(4, 6), 16) + ]; +} + +function drawRect(buffer, width, x, y, rectWidth, rectHeight, color) { + const [red, green, blue] = hexToRgb(color); + const xEnd = Math.min(width, x + rectWidth); + const yEnd = Math.min(720, y + rectHeight); + for (let row = Math.max(0, y); row < yEnd; row += 1) { + for (let col = Math.max(0, x); col < xEnd; col += 1) { + const offset = (row * width + col) * 3; + buffer[offset] = red; + buffer[offset + 1] = green; + buffer[offset + 2] = blue; + } + } +} + +function writePpmFrame(filePath, progress) { + const width = 1280; + const height = 720; + const buffer = Buffer.alloc(width * height * 3); + drawRect(buffer, width, 0, 0, width, height, "#f8fafc"); + drawRect(buffer, width, 64, 64, 1152, 592, "#ffffff"); + drawRect(buffer, width, 64, 64, 1152, 4, "#d8dee7"); + drawRect(buffer, width, 64, 652, 1152, 4, "#d8dee7"); + drawRect(buffer, width, 64, 64, 4, 592, "#d8dee7"); + drawRect(buffer, width, 1212, 64, 4, 592, "#d8dee7"); + drawRect(buffer, width, 96, 112, 900, 18, "#1b1f24"); + drawRect(buffer, width, 96, 166, 620, 12, "#5b6572"); + drawRect(buffer, width, 96, 218, 840, 10, "#5b6572"); + + const statuses = [ + [result.summary.readyCount, "#176b3a"], + [result.summary.holdCount, "#8a5a00"], + [result.summary.blockCount, "#9d1c2f"], + [result.summary.findingCount, "#116d6e"] + ]; + const max = Math.max(1, ...statuses.map(([count]) => count)); + statuses.forEach(([count, color], index) => { + const y = 284 + index * 72; + const barWidth = Math.round((count / max) * 650 * progress); + drawRect(buffer, width, 96, y + 12, 168, 18, "#5b6572"); + drawRect(buffer, width, 296, y, Math.max(6, barWidth), 42, color); + }); + drawRect(buffer, width, 96, 590, Math.round(880 * progress), 10, "#116d6e"); + + const header = Buffer.from(`P6\n${width} ${height}\n255\n`, "ascii"); + fs.writeFileSync(filePath, Buffer.concat([header, buffer])); +} + +fs.rmSync(framesDir, { recursive: true, force: true }); +fs.mkdirSync(framesDir, { recursive: true }); +for (let frame = 0; frame < 36; frame += 1) { + const progress = Math.min(1, (frame + 1) / 24); + writePpmFrame(path.join(framesDir, `frame-${String(frame).padStart(3, "0")}.ppm`), progress); +} + +const ffmpeg = spawnSync("ffmpeg", [ + "-y", + "-framerate", + "6", + "-i", + path.join(framesDir, "frame-%03d.ppm"), + "-c:v", + "libx264", + "-pix_fmt", + "yuv420p", + mp4Path +], { encoding: "utf8" }); + +fs.rmSync(framesDir, { recursive: true, force: true }); + +if (ffmpeg.status !== 0) { + fs.writeFileSync(path.join(reportsDir, "demo-mp4-fallback.txt"), [ + "ffmpeg was not able to render demo.mp4 in this environment.", + "The SVG and Markdown demo artifacts were still generated.", + "", + ffmpeg.stderr || ffmpeg.stdout || "No ffmpeg output." + ].join("\n")); +} else if (fs.existsSync(path.join(reportsDir, "demo-mp4-fallback.txt"))) { + fs.unlinkSync(path.join(reportsDir, "demo-mp4-fallback.txt")); +} + +console.log(`Decision: ${result.releaseDecision}`); +console.log(`Challenges: ${result.summary.challengeCount}`); +console.log(`Ready: ${result.summary.readyCount}`); +console.log(`Held: ${result.summary.holdCount}`); +console.log(`Blocked: ${result.summary.blockCount}`); +console.log(`Critical findings: ${result.summary.criticalCount}`); +console.log(`Warnings: ${result.summary.warningCount}`); +console.log(`Audit digest: ${result.auditDigest}`); +console.log(`Wrote ${path.relative(process.cwd(), jsonPath)}`); +console.log(`Wrote ${path.relative(process.cwd(), markdownPath)}`); +console.log(`Wrote ${path.relative(process.cwd(), svgPath)}`); +if (fs.existsSync(mp4Path)) { + console.log(`Wrote ${path.relative(process.cwd(), mp4Path)}`); +} diff --git a/sponsor-regulatory-attestation-packet-guard/index.js b/sponsor-regulatory-attestation-packet-guard/index.js new file mode 100644 index 00000000..033c7010 --- /dev/null +++ b/sponsor-regulatory-attestation-packet-guard/index.js @@ -0,0 +1,567 @@ +"use strict"; + +const crypto = require("crypto"); + +const MS_PER_DAY = 24 * 60 * 60 * 1000; +const HIGH_VALUE_PRIZE_USD = 600; +const RESTRICTED_EXPORT_CLASSES = new Set(["itar", "ear-controlled", "dual-use-restricted", "sanctioned-use"]); + +function parseDate(value, fieldName) { + const date = new Date(value); + if (Number.isNaN(date.getTime())) { + throw new Error(`Invalid date for ${fieldName}: ${value}`); + } + return date; +} + +function daysBetween(start, end) { + return Math.floor((parseDate(end, "end") - parseDate(start, "start")) / MS_PER_DAY); +} + +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 looksLikeDigest(value) { + return typeof value === "string" && /^sha256:[a-f0-9]{64}$/i.test(value); +} + +function normalizeText(value) { + return String(value || "").trim().toLowerCase(); +} + +function normalizeJurisdiction(value) { + return String(value || "").trim().toUpperCase(); +} + +function addFinding(findings, challenge, severity, code, message, evidence, remediation) { + findings.push({ + challengeId: challenge.id, + challengeTitle: challenge.title, + severity, + code, + message, + evidence, + remediation + }); +} + +function evidenceAgeDays(now, timestamp) { + if (!timestamp) { + return null; + } + return daysBetween(timestamp, now); +} + +function validateSponsorIdentity(challenge, findings) { + const sponsor = challenge.sponsor || {}; + const signer = sponsor.authorizedSigner || {}; + + if (!sponsor.legalName) { + addFinding( + findings, + challenge, + "critical", + "missing_sponsor_legal_identity", + "The challenge sponsor has no legal entity name.", + { sponsorName: sponsor.displayName || null }, + "Attach the sponsor legal entity name before the challenge opens for submissions." + ); + } + + if (!sponsor.entityIdentifier) { + addFinding( + findings, + challenge, + "warning", + "missing_sponsor_entity_identifier", + "The sponsor does not include a registration, institution, or tax entity identifier.", + { legalName: sponsor.legalName || null }, + "Record a sponsor entity identifier so the prize source can be audited without relying on display names." + ); + } + + if (!signer.name || !signer.role || !looksLikeDigest(signer.authorityDigest)) { + addFinding( + findings, + challenge, + "critical", + "missing_authorized_signer_evidence", + "The sponsor packet does not prove that an authorized signer approved the challenge.", + { + signerName: signer.name || null, + signerRole: signer.role || null, + authorityDigest: signer.authorityDigest || null + }, + "Add signer name, role, and a SHA-256 digest of the sponsor authorization record." + ); + } +} + +function validateFundingAttestation(challenge, findings, now, options) { + const prize = challenge.prize || {}; + const funding = challenge.funding || {}; + + if (!Number.isFinite(prize.amountUsd) || prize.amountUsd <= 0) { + addFinding( + findings, + challenge, + "critical", + "invalid_prize_amount", + "The challenge prize amount is missing or invalid.", + { amountUsd: prize.amountUsd || null }, + "Record a positive USD-equivalent prize amount before the challenge is published." + ); + } + + if (!funding.sourceOfFundsAttested || !looksLikeDigest(funding.statementDigest)) { + addFinding( + findings, + challenge, + "critical", + "missing_source_of_funds_attestation", + "The sponsor has not supplied a verifiable source-of-funds attestation.", + { + sourceOfFundsAttested: Boolean(funding.sourceOfFundsAttested), + statementDigest: funding.statementDigest || null + }, + "Require a signed source-of-funds statement digest before accepting submissions." + ); + } + + const attestationAge = evidenceAgeDays(now, funding.attestedAt); + if (attestationAge === null) { + addFinding( + findings, + challenge, + "warning", + "missing_funding_attestation_timestamp", + "The source-of-funds attestation has no timestamp.", + { attestedAt: null }, + "Attach the signature timestamp so stale funding packets can be revalidated." + ); + } else if (attestationAge > options.maxAttestationAgeDays) { + addFinding( + findings, + challenge, + "warning", + "stale_source_of_funds_attestation", + "The source-of-funds attestation is older than the freshness window.", + { + attestedAt: funding.attestedAt, + ageDays: attestationAge, + maxAttestationAgeDays: options.maxAttestationAgeDays + }, + "Refresh sponsor funding attestations before the challenge is opened or extended." + ); + } + + if (funding.thirdPartySponsor === true && !looksLikeDigest(funding.thirdPartyDisclosureDigest)) { + addFinding( + findings, + challenge, + "critical", + "missing_third_party_funding_disclosure", + "Third-party prize funding is marked but no sponsor disclosure digest is attached.", + { thirdPartySponsor: true, thirdPartyDisclosureDigest: funding.thirdPartyDisclosureDigest || null }, + "Attach a disclosure digest naming the upstream funder and any restrictions on the prize." + ); + } +} + +function validateSanctionsScreening(challenge, findings, now, options) { + const screening = challenge.sanctionsScreening || {}; + const status = normalizeText(screening.status); + + if (!status) { + addFinding( + findings, + challenge, + "critical", + "missing_sanctions_screening", + "The sponsor packet does not include sanctions or restricted-party screening status.", + { status: null }, + "Screen the sponsor, beneficial owners, and payment administrator before opening the challenge." + ); + } else if (status === "blocked") { + addFinding( + findings, + challenge, + "critical", + "blocked_sanctions_screening", + "The sponsor packet contains an unresolved blocked sanctions screening result.", + { status, reference: screening.reference || null }, + "Block the challenge until compliance counsel clears or rejects the sponsor." + ); + } else if (status === "review") { + addFinding( + findings, + challenge, + "warning", + "sanctions_screening_requires_review", + "The sponsor screening result requires compliance review.", + { status, reference: screening.reference || null }, + "Hold publication until the screening review is resolved." + ); + } + + const checkedAge = evidenceAgeDays(now, screening.checkedAt); + if (checkedAge === null) { + addFinding( + findings, + challenge, + "warning", + "missing_sanctions_screening_timestamp", + "The sanctions screening result has no checked timestamp.", + { checkedAt: null }, + "Record when the restricted-party screening was performed." + ); + } else if (checkedAge > options.maxScreeningAgeDays) { + addFinding( + findings, + challenge, + "warning", + "stale_sanctions_screening", + "The sanctions screening result is older than the allowed freshness window.", + { checkedAt: screening.checkedAt, ageDays: checkedAge, maxScreeningAgeDays: options.maxScreeningAgeDays }, + "Refresh the screening packet before accepting submissions or paying prize funds." + ); + } + + for (const owner of screening.beneficialOwners || []) { + if (owner.sanctionsHit === true && !looksLikeDigest(owner.resolutionDigest)) { + addFinding( + findings, + challenge, + "critical", + "unresolved_beneficial_owner_screening_hit", + "A beneficial owner has a screening hit without a resolution record.", + { ownerId: owner.id || null, role: owner.role || null }, + "Attach a compliance resolution digest or block the challenge." + ); + } + } +} + +function validateTaxAndReporting(challenge, findings) { + const prize = challenge.prize || {}; + const tax = challenge.taxReporting || {}; + const isHighValue = Number(prize.amountUsd || 0) >= HIGH_VALUE_PRIZE_USD; + const sponsorJurisdiction = normalizeJurisdiction((challenge.sponsor || {}).jurisdiction); + const payoutJurisdictions = (challenge.expectedPayoutJurisdictions || []).map(normalizeJurisdiction); + const crossBorder = payoutJurisdictions.some((jurisdiction) => jurisdiction && jurisdiction !== sponsorJurisdiction); + + if (isHighValue && !looksLikeDigest(tax.reportingPlanDigest)) { + addFinding( + findings, + challenge, + "critical", + "missing_high_value_tax_reporting_plan", + "A high-value prize lacks a tax reporting plan digest.", + { amountUsd: prize.amountUsd, reportingPlanDigest: tax.reportingPlanDigest || null }, + "Attach the sponsor-side tax reporting plan before submissions are opened." + ); + } + + if (tax.formStatus !== "complete" && tax.formStatus !== "not_applicable") { + addFinding( + findings, + challenge, + isHighValue ? "critical" : "warning", + "incomplete_tax_form_status", + "The sponsor packet does not mark tax form readiness as complete or not applicable.", + { formStatus: tax.formStatus || null, amountUsd: prize.amountUsd || null }, + "Resolve sponsor-side tax form readiness before prize commitments are advertised." + ); + } + + if (crossBorder && tax.withholdingReviewComplete !== true) { + addFinding( + findings, + challenge, + "critical", + "missing_cross_border_withholding_review", + "Expected payout jurisdictions differ from the sponsor jurisdiction but withholding review is incomplete.", + { sponsorJurisdiction, expectedPayoutJurisdictions: payoutJurisdictions }, + "Complete cross-border withholding review before challenge launch." + ); + } +} + +function validateExportAndRegulatedDomain(challenge, findings) { + const exportControl = challenge.exportControl || {}; + const classification = normalizeText(exportControl.classification || "unknown"); + + if (classification === "unknown") { + addFinding( + findings, + challenge, + "warning", + "unknown_export_control_classification", + "The challenge has no export-control classification.", + { classification: exportControl.classification || null }, + "Classify the challenge as exempt, EAR99, restricted, or not applicable before publication." + ); + } else if (RESTRICTED_EXPORT_CLASSES.has(classification) && !looksLikeDigest(exportControl.counselApprovalDigest)) { + addFinding( + findings, + challenge, + "critical", + "restricted_export_without_counsel_approval", + "A restricted export-control classification lacks counsel approval evidence.", + { classification: exportControl.classification, counselApprovalDigest: exportControl.counselApprovalDigest || null }, + "Attach counsel approval and participant restrictions before publishing the challenge." + ); + } + + if (exportControl.internationalParticipation === true && !looksLikeDigest(exportControl.participationControlsDigest)) { + addFinding( + findings, + challenge, + "warning", + "missing_international_participation_controls", + "International participation is expected but no participant-control digest is attached.", + { internationalParticipation: true }, + "Record eligibility controls for international participants before the challenge opens." + ); + } + + if (challenge.regulatedDomain === true && !looksLikeDigest(challenge.regulatoryReviewDigest)) { + addFinding( + findings, + challenge, + "critical", + "missing_regulated_domain_review_packet", + "The challenge is marked as regulated but lacks a regulatory review packet digest.", + { regulatoryReviewDigest: challenge.regulatoryReviewDigest || null }, + "Attach the domain-specific regulatory review packet before publishing the challenge." + ); + } +} + +function validatePublicTerms(challenge, findings) { + const terms = challenge.publicTerms || {}; + + if (!looksLikeDigest(terms.rulesDigest)) { + addFinding( + findings, + challenge, + "critical", + "missing_public_rules_digest", + "The challenge does not have immutable public rules evidence.", + { rulesDigest: terms.rulesDigest || null }, + "Attach a SHA-256 digest of the public rules, deliverables, and evaluation disclosures." + ); + } + + if (!terms.cancellationPolicy || !terms.refundPolicy) { + addFinding( + findings, + challenge, + "warning", + "missing_cancellation_or_refund_policy", + "The public terms do not identify cancellation and refund handling.", + { + cancellationPolicy: terms.cancellationPolicy || null, + refundPolicy: terms.refundPolicy || null + }, + "Publish cancellation and refund language so sponsors and solvers share the same expectations." + ); + } + + if (!challenge.complianceContact || !challenge.complianceContact.email) { + addFinding( + findings, + challenge, + "warning", + "missing_compliance_contact", + "The challenge lacks a sponsor compliance escalation contact.", + { complianceContact: challenge.complianceContact || null }, + "Add a compliance contact for screening, tax, export-control, or sponsor-attestation questions." + ); + } +} + +function statusForFindings(findings) { + if (findings.some((finding) => finding.severity === "critical")) { + return "block_challenge"; + } + if (findings.some((finding) => finding.severity === "warning")) { + return "hold_for_compliance"; + } + return "ready_to_open"; +} + +function evaluateSponsorAttestation(input, userOptions = {}) { + if (!input || !Array.isArray(input.challenges)) { + throw new Error("Expected input.challenges to be an array"); + } + + const options = { + maxAttestationAgeDays: 90, + maxScreeningAgeDays: 30, + ...userOptions + }; + const now = input.generatedAt || new Date().toISOString(); + const findings = []; + const challenges = []; + + for (const challenge of input.challenges) { + const start = findings.length; + validateSponsorIdentity(challenge, findings); + validateFundingAttestation(challenge, findings, now, options); + validateSanctionsScreening(challenge, findings, now, options); + validateTaxAndReporting(challenge, findings); + validateExportAndRegulatedDomain(challenge, findings); + validatePublicTerms(challenge, findings); + + const challengeFindings = findings.slice(start); + const status = statusForFindings(challengeFindings); + challenges.push({ + challengeId: challenge.id, + title: challenge.title, + sponsorLegalName: (challenge.sponsor || {}).legalName || null, + amountUsd: (challenge.prize || {}).amountUsd || null, + sponsorJurisdiction: normalizeJurisdiction((challenge.sponsor || {}).jurisdiction), + status, + criticalCount: challengeFindings.filter((finding) => finding.severity === "critical").length, + warningCount: challengeFindings.filter((finding) => finding.severity === "warning").length, + packetDigest: digest({ + id: challenge.id, + sponsor: challenge.sponsor || {}, + prize: challenge.prize || {}, + funding: challenge.funding || {}, + sanctionsScreening: challenge.sanctionsScreening || {}, + taxReporting: challenge.taxReporting || {}, + exportControl: challenge.exportControl || {}, + publicTerms: challenge.publicTerms || {} + }) + }); + } + + const summary = { + challengeCount: challenges.length, + readyCount: challenges.filter((challenge) => challenge.status === "ready_to_open").length, + holdCount: challenges.filter((challenge) => challenge.status === "hold_for_compliance").length, + blockCount: challenges.filter((challenge) => challenge.status === "block_challenge").length, + findingCount: findings.length, + criticalCount: findings.filter((finding) => finding.severity === "critical").length, + warningCount: findings.filter((finding) => finding.severity === "warning").length + }; + + const releaseDecision = summary.blockCount > 0 + ? "block_challenge_publication" + : summary.holdCount > 0 + ? "hold_for_compliance_review" + : "ready_to_open"; + + const result = { + generatedAt: now, + releaseDecision, + summary, + challenges, + findings + }; + result.auditDigest = digest(result); + return result; +} + +function renderMarkdownReport(result) { + const lines = [ + "# Sponsor Regulatory Attestation Packet Review", + "", + `Generated: ${result.generatedAt}`, + `Decision: ${result.releaseDecision}`, + `Audit digest: ${result.auditDigest}`, + "", + "## Summary", + "", + `- Challenges reviewed: ${result.summary.challengeCount}`, + `- Ready: ${result.summary.readyCount}`, + `- Hold for compliance: ${result.summary.holdCount}`, + `- Blocked: ${result.summary.blockCount}`, + `- Critical findings: ${result.summary.criticalCount}`, + `- Warnings: ${result.summary.warningCount}`, + "", + "## Challenge Status", + "", + "| Challenge | Sponsor | Amount USD | Status | Critical | Warnings |", + "| --- | --- | ---: | --- | ---: | ---: |" + ]; + + for (const challenge of result.challenges) { + lines.push(`| ${challenge.title} | ${challenge.sponsorLegalName || "Unknown"} | ${challenge.amountUsd || 0} | ${challenge.status} | ${challenge.criticalCount} | ${challenge.warningCount} |`); + } + + lines.push("", "## Findings", ""); + if (result.findings.length === 0) { + lines.push("No findings."); + } else { + for (const finding of result.findings) { + lines.push(`### ${finding.severity.toUpperCase()}: ${finding.code}`); + lines.push(""); + lines.push(`Challenge: ${finding.challengeTitle}`); + lines.push(""); + lines.push(finding.message); + lines.push(""); + lines.push(`Remediation: ${finding.remediation}`); + lines.push(""); + } + } + + while (lines[lines.length - 1] === "") { + lines.pop(); + } + return `${lines.join("\n")}\n`; +} + +function renderSvgSummary(result) { + const width = 960; + const height = 540; + const max = Math.max(1, result.summary.readyCount, result.summary.holdCount, result.summary.blockCount); + const bars = [ + ["Ready", result.summary.readyCount, "#176b3a", 150], + ["Hold", result.summary.holdCount, "#8a5a00", 230], + ["Blocked", result.summary.blockCount, "#9d1c2f", 310], + ["Findings", result.summary.findingCount, "#116d6e", 390] + ]; + + const barMarkup = bars.map(([label, count, color, y]) => { + const barWidth = Math.max(8, Math.round((count / max) * 560)); + return [ + `${label}`, + ``, + `${count}` + ].join("\n"); + }).join("\n"); + + return [ + ``, + '', + '', + 'Sponsor Regulatory Attestation Guard', + `Decision: ${result.releaseDecision}`, + barMarkup, + `Audit digest: ${result.auditDigest.slice(0, 24)}...`, + "" + ].join("\n"); +} + +module.exports = { + evaluateSponsorAttestation, + renderMarkdownReport, + renderSvgSummary, + stableStringify, + daysBetween, + normalizeJurisdiction +}; diff --git a/sponsor-regulatory-attestation-packet-guard/package.json b/sponsor-regulatory-attestation-packet-guard/package.json new file mode 100644 index 00000000..8a0fd12b --- /dev/null +++ b/sponsor-regulatory-attestation-packet-guard/package.json @@ -0,0 +1,13 @@ +{ + "name": "sponsor-regulatory-attestation-packet-guard", + "version": "1.0.0", + "private": true, + "description": "Dependency-free sponsor regulatory attestation packet guard for SCIBASE issue #18.", + "main": "index.js", + "scripts": { + "check": "node --check index.js && node --check sample-data.js && node --check demo.js && node --check test.js", + "test": "node test.js", + "demo": "node demo.js" + }, + "license": "MIT" +} diff --git a/sponsor-regulatory-attestation-packet-guard/reports/demo.mp4 b/sponsor-regulatory-attestation-packet-guard/reports/demo.mp4 new file mode 100644 index 00000000..70f5fa45 Binary files /dev/null and b/sponsor-regulatory-attestation-packet-guard/reports/demo.mp4 differ diff --git a/sponsor-regulatory-attestation-packet-guard/reports/sponsor-regulatory-attestation-packet.json b/sponsor-regulatory-attestation-packet-guard/reports/sponsor-regulatory-attestation-packet.json new file mode 100644 index 00000000..999faece --- /dev/null +++ b/sponsor-regulatory-attestation-packet-guard/reports/sponsor-regulatory-attestation-packet.json @@ -0,0 +1,278 @@ +{ + "generatedAt": "2026-06-01T00:00:00.000Z", + "releaseDecision": "block_challenge_publication", + "summary": { + "challengeCount": 4, + "readyCount": 1, + "holdCount": 1, + "blockCount": 2, + "findingCount": 18, + "criticalCount": 10, + "warningCount": 8 + }, + "challenges": [ + { + "challengeId": "challenge-open-neuro-imaging", + "title": "Open Neuro Imaging Benchmark Prize", + "sponsorLegalName": "Atlas Neuro Collective Foundation", + "amountUsd": 500, + "sponsorJurisdiction": "JP", + "status": "ready_to_open", + "criticalCount": 0, + "warningCount": 0, + "packetDigest": "86207838861d8c77030c2dd27042e56e6eed668a399d521402266dbdafe0013e" + }, + { + "challengeId": "challenge-catalyst-literature", + "title": "Cross-border Catalyst Literature Review", + "sponsorLegalName": "Materials Insight Lab LLC", + "amountUsd": 900, + "sponsorJurisdiction": "US", + "status": "hold_for_compliance", + "criticalCount": 0, + "warningCount": 7, + "packetDigest": "8b83fb2bc717a6a6cbb5756b3927c3b783a3727f737618d15282e9956668f377" + }, + { + "challengeId": "challenge-clinical-proteomics", + "title": "Clinical Proteomics Translational Prize", + "sponsorLegalName": "Proteomics Venture Studio Inc.", + "amountUsd": 2500, + "sponsorJurisdiction": "US", + "status": "block_challenge", + "criticalCount": 7, + "warningCount": 0, + "packetDigest": "f2a3353fad23be1339a22b98b027f0f359f57afbbe821c7d9a2c7503cbb15486" + }, + { + "challengeId": "challenge-dual-use-sensors", + "title": "Dual-use Sensor Fusion Model Challenge", + "sponsorLegalName": "Frontier Robotics Initiative", + "amountUsd": 1200, + "sponsorJurisdiction": "GB", + "status": "block_challenge", + "criticalCount": 3, + "warningCount": 1, + "packetDigest": "e189c8ab8419ccbd0c861faf6959e84ad4618bf27454fdb27bae228a51ef5583" + } + ], + "findings": [ + { + "challengeId": "challenge-catalyst-literature", + "challengeTitle": "Cross-border Catalyst Literature Review", + "severity": "warning", + "code": "stale_source_of_funds_attestation", + "message": "The source-of-funds attestation is older than the freshness window.", + "evidence": { + "attestedAt": "2026-02-01T00:00:00.000Z", + "ageDays": 120, + "maxAttestationAgeDays": 90 + }, + "remediation": "Refresh sponsor funding attestations before the challenge is opened or extended." + }, + { + "challengeId": "challenge-catalyst-literature", + "challengeTitle": "Cross-border Catalyst Literature Review", + "severity": "warning", + "code": "sanctions_screening_requires_review", + "message": "The sponsor screening result requires compliance review.", + "evidence": { + "status": "review", + "reference": "SCR-2026-MAT-019" + }, + "remediation": "Hold publication until the screening review is resolved." + }, + { + "challengeId": "challenge-catalyst-literature", + "challengeTitle": "Cross-border Catalyst Literature Review", + "severity": "warning", + "code": "stale_sanctions_screening", + "message": "The sanctions screening result is older than the allowed freshness window.", + "evidence": { + "checkedAt": "2026-04-01T00:00:00.000Z", + "ageDays": 61, + "maxScreeningAgeDays": 30 + }, + "remediation": "Refresh the screening packet before accepting submissions or paying prize funds." + }, + { + "challengeId": "challenge-catalyst-literature", + "challengeTitle": "Cross-border Catalyst Literature Review", + "severity": "warning", + "code": "unknown_export_control_classification", + "message": "The challenge has no export-control classification.", + "evidence": { + "classification": "unknown" + }, + "remediation": "Classify the challenge as exempt, EAR99, restricted, or not applicable before publication." + }, + { + "challengeId": "challenge-catalyst-literature", + "challengeTitle": "Cross-border Catalyst Literature Review", + "severity": "warning", + "code": "missing_international_participation_controls", + "message": "International participation is expected but no participant-control digest is attached.", + "evidence": { + "internationalParticipation": true + }, + "remediation": "Record eligibility controls for international participants before the challenge opens." + }, + { + "challengeId": "challenge-catalyst-literature", + "challengeTitle": "Cross-border Catalyst Literature Review", + "severity": "warning", + "code": "missing_cancellation_or_refund_policy", + "message": "The public terms do not identify cancellation and refund handling.", + "evidence": { + "cancellationPolicy": "published", + "refundPolicy": null + }, + "remediation": "Publish cancellation and refund language so sponsors and solvers share the same expectations." + }, + { + "challengeId": "challenge-catalyst-literature", + "challengeTitle": "Cross-border Catalyst Literature Review", + "severity": "warning", + "code": "missing_compliance_contact", + "message": "The challenge lacks a sponsor compliance escalation contact.", + "evidence": { + "complianceContact": null + }, + "remediation": "Add a compliance contact for screening, tax, export-control, or sponsor-attestation questions." + }, + { + "challengeId": "challenge-clinical-proteomics", + "challengeTitle": "Clinical Proteomics Translational Prize", + "severity": "critical", + "code": "missing_source_of_funds_attestation", + "message": "The sponsor has not supplied a verifiable source-of-funds attestation.", + "evidence": { + "sourceOfFundsAttested": false, + "statementDigest": null + }, + "remediation": "Require a signed source-of-funds statement digest before accepting submissions." + }, + { + "challengeId": "challenge-clinical-proteomics", + "challengeTitle": "Clinical Proteomics Translational Prize", + "severity": "critical", + "code": "blocked_sanctions_screening", + "message": "The sponsor packet contains an unresolved blocked sanctions screening result.", + "evidence": { + "status": "blocked", + "reference": "SCR-2026-CLIN-007" + }, + "remediation": "Block the challenge until compliance counsel clears or rejects the sponsor." + }, + { + "challengeId": "challenge-clinical-proteomics", + "challengeTitle": "Clinical Proteomics Translational Prize", + "severity": "critical", + "code": "unresolved_beneficial_owner_screening_hit", + "message": "A beneficial owner has a screening hit without a resolution record.", + "evidence": { + "ownerId": "owner-pvs-2", + "role": "fund administrator" + }, + "remediation": "Attach a compliance resolution digest or block the challenge." + }, + { + "challengeId": "challenge-clinical-proteomics", + "challengeTitle": "Clinical Proteomics Translational Prize", + "severity": "critical", + "code": "missing_high_value_tax_reporting_plan", + "message": "A high-value prize lacks a tax reporting plan digest.", + "evidence": { + "amountUsd": 2500, + "reportingPlanDigest": null + }, + "remediation": "Attach the sponsor-side tax reporting plan before submissions are opened." + }, + { + "challengeId": "challenge-clinical-proteomics", + "challengeTitle": "Clinical Proteomics Translational Prize", + "severity": "critical", + "code": "incomplete_tax_form_status", + "message": "The sponsor packet does not mark tax form readiness as complete or not applicable.", + "evidence": { + "formStatus": "missing", + "amountUsd": 2500 + }, + "remediation": "Resolve sponsor-side tax form readiness before prize commitments are advertised." + }, + { + "challengeId": "challenge-clinical-proteomics", + "challengeTitle": "Clinical Proteomics Translational Prize", + "severity": "critical", + "code": "missing_cross_border_withholding_review", + "message": "Expected payout jurisdictions differ from the sponsor jurisdiction but withholding review is incomplete.", + "evidence": { + "sponsorJurisdiction": "US", + "expectedPayoutJurisdictions": [ + "US", + "IN", + "BR" + ] + }, + "remediation": "Complete cross-border withholding review before challenge launch." + }, + { + "challengeId": "challenge-clinical-proteomics", + "challengeTitle": "Clinical Proteomics Translational Prize", + "severity": "critical", + "code": "missing_regulated_domain_review_packet", + "message": "The challenge is marked as regulated but lacks a regulatory review packet digest.", + "evidence": { + "regulatoryReviewDigest": null + }, + "remediation": "Attach the domain-specific regulatory review packet before publishing the challenge." + }, + { + "challengeId": "challenge-dual-use-sensors", + "challengeTitle": "Dual-use Sensor Fusion Model Challenge", + "severity": "critical", + "code": "missing_third_party_funding_disclosure", + "message": "Third-party prize funding is marked but no sponsor disclosure digest is attached.", + "evidence": { + "thirdPartySponsor": true, + "thirdPartyDisclosureDigest": null + }, + "remediation": "Attach a disclosure digest naming the upstream funder and any restrictions on the prize." + }, + { + "challengeId": "challenge-dual-use-sensors", + "challengeTitle": "Dual-use Sensor Fusion Model Challenge", + "severity": "critical", + "code": "restricted_export_without_counsel_approval", + "message": "A restricted export-control classification lacks counsel approval evidence.", + "evidence": { + "classification": "dual-use-restricted", + "counselApprovalDigest": null + }, + "remediation": "Attach counsel approval and participant restrictions before publishing the challenge." + }, + { + "challengeId": "challenge-dual-use-sensors", + "challengeTitle": "Dual-use Sensor Fusion Model Challenge", + "severity": "warning", + "code": "missing_international_participation_controls", + "message": "International participation is expected but no participant-control digest is attached.", + "evidence": { + "internationalParticipation": true + }, + "remediation": "Record eligibility controls for international participants before the challenge opens." + }, + { + "challengeId": "challenge-dual-use-sensors", + "challengeTitle": "Dual-use Sensor Fusion Model Challenge", + "severity": "critical", + "code": "missing_public_rules_digest", + "message": "The challenge does not have immutable public rules evidence.", + "evidence": { + "rulesDigest": "draft" + }, + "remediation": "Attach a SHA-256 digest of the public rules, deliverables, and evaluation disclosures." + } + ], + "auditDigest": "c5968d2c0869aab4504f3f1db83698e87cbe9bfba735a8fba7027c343c72d94b" +} diff --git a/sponsor-regulatory-attestation-packet-guard/reports/sponsor-regulatory-attestation-report.md b/sponsor-regulatory-attestation-packet-guard/reports/sponsor-regulatory-attestation-report.md new file mode 100644 index 00000000..2a903814 --- /dev/null +++ b/sponsor-regulatory-attestation-packet-guard/reports/sponsor-regulatory-attestation-report.md @@ -0,0 +1,169 @@ +# Sponsor Regulatory Attestation Packet Review + +Generated: 2026-06-01T00:00:00.000Z +Decision: block_challenge_publication +Audit digest: c5968d2c0869aab4504f3f1db83698e87cbe9bfba735a8fba7027c343c72d94b + +## Summary + +- Challenges reviewed: 4 +- Ready: 1 +- Hold for compliance: 1 +- Blocked: 2 +- Critical findings: 10 +- Warnings: 8 + +## Challenge Status + +| Challenge | Sponsor | Amount USD | Status | Critical | Warnings | +| --- | --- | ---: | --- | ---: | ---: | +| Open Neuro Imaging Benchmark Prize | Atlas Neuro Collective Foundation | 500 | ready_to_open | 0 | 0 | +| Cross-border Catalyst Literature Review | Materials Insight Lab LLC | 900 | hold_for_compliance | 0 | 7 | +| Clinical Proteomics Translational Prize | Proteomics Venture Studio Inc. | 2500 | block_challenge | 7 | 0 | +| Dual-use Sensor Fusion Model Challenge | Frontier Robotics Initiative | 1200 | block_challenge | 3 | 1 | + +## Findings + +### WARNING: stale_source_of_funds_attestation + +Challenge: Cross-border Catalyst Literature Review + +The source-of-funds attestation is older than the freshness window. + +Remediation: Refresh sponsor funding attestations before the challenge is opened or extended. + +### WARNING: sanctions_screening_requires_review + +Challenge: Cross-border Catalyst Literature Review + +The sponsor screening result requires compliance review. + +Remediation: Hold publication until the screening review is resolved. + +### WARNING: stale_sanctions_screening + +Challenge: Cross-border Catalyst Literature Review + +The sanctions screening result is older than the allowed freshness window. + +Remediation: Refresh the screening packet before accepting submissions or paying prize funds. + +### WARNING: unknown_export_control_classification + +Challenge: Cross-border Catalyst Literature Review + +The challenge has no export-control classification. + +Remediation: Classify the challenge as exempt, EAR99, restricted, or not applicable before publication. + +### WARNING: missing_international_participation_controls + +Challenge: Cross-border Catalyst Literature Review + +International participation is expected but no participant-control digest is attached. + +Remediation: Record eligibility controls for international participants before the challenge opens. + +### WARNING: missing_cancellation_or_refund_policy + +Challenge: Cross-border Catalyst Literature Review + +The public terms do not identify cancellation and refund handling. + +Remediation: Publish cancellation and refund language so sponsors and solvers share the same expectations. + +### WARNING: missing_compliance_contact + +Challenge: Cross-border Catalyst Literature Review + +The challenge lacks a sponsor compliance escalation contact. + +Remediation: Add a compliance contact for screening, tax, export-control, or sponsor-attestation questions. + +### CRITICAL: missing_source_of_funds_attestation + +Challenge: Clinical Proteomics Translational Prize + +The sponsor has not supplied a verifiable source-of-funds attestation. + +Remediation: Require a signed source-of-funds statement digest before accepting submissions. + +### CRITICAL: blocked_sanctions_screening + +Challenge: Clinical Proteomics Translational Prize + +The sponsor packet contains an unresolved blocked sanctions screening result. + +Remediation: Block the challenge until compliance counsel clears or rejects the sponsor. + +### CRITICAL: unresolved_beneficial_owner_screening_hit + +Challenge: Clinical Proteomics Translational Prize + +A beneficial owner has a screening hit without a resolution record. + +Remediation: Attach a compliance resolution digest or block the challenge. + +### CRITICAL: missing_high_value_tax_reporting_plan + +Challenge: Clinical Proteomics Translational Prize + +A high-value prize lacks a tax reporting plan digest. + +Remediation: Attach the sponsor-side tax reporting plan before submissions are opened. + +### CRITICAL: incomplete_tax_form_status + +Challenge: Clinical Proteomics Translational Prize + +The sponsor packet does not mark tax form readiness as complete or not applicable. + +Remediation: Resolve sponsor-side tax form readiness before prize commitments are advertised. + +### CRITICAL: missing_cross_border_withholding_review + +Challenge: Clinical Proteomics Translational Prize + +Expected payout jurisdictions differ from the sponsor jurisdiction but withholding review is incomplete. + +Remediation: Complete cross-border withholding review before challenge launch. + +### CRITICAL: missing_regulated_domain_review_packet + +Challenge: Clinical Proteomics Translational Prize + +The challenge is marked as regulated but lacks a regulatory review packet digest. + +Remediation: Attach the domain-specific regulatory review packet before publishing the challenge. + +### CRITICAL: missing_third_party_funding_disclosure + +Challenge: Dual-use Sensor Fusion Model Challenge + +Third-party prize funding is marked but no sponsor disclosure digest is attached. + +Remediation: Attach a disclosure digest naming the upstream funder and any restrictions on the prize. + +### CRITICAL: restricted_export_without_counsel_approval + +Challenge: Dual-use Sensor Fusion Model Challenge + +A restricted export-control classification lacks counsel approval evidence. + +Remediation: Attach counsel approval and participant restrictions before publishing the challenge. + +### WARNING: missing_international_participation_controls + +Challenge: Dual-use Sensor Fusion Model Challenge + +International participation is expected but no participant-control digest is attached. + +Remediation: Record eligibility controls for international participants before the challenge opens. + +### CRITICAL: missing_public_rules_digest + +Challenge: Dual-use Sensor Fusion Model Challenge + +The challenge does not have immutable public rules evidence. + +Remediation: Attach a SHA-256 digest of the public rules, deliverables, and evaluation disclosures. diff --git a/sponsor-regulatory-attestation-packet-guard/reports/summary.svg b/sponsor-regulatory-attestation-packet-guard/reports/summary.svg new file mode 100644 index 00000000..d65f291a --- /dev/null +++ b/sponsor-regulatory-attestation-packet-guard/reports/summary.svg @@ -0,0 +1,19 @@ + + + +Sponsor Regulatory Attestation Guard +Decision: block_challenge_publication +Ready + +1 +Hold + +1 +Blocked + +2 +Findings + +18 +Audit digest: c5968d2c0869aab4504f3f1d... + \ No newline at end of file diff --git a/sponsor-regulatory-attestation-packet-guard/requirements-map.md b/sponsor-regulatory-attestation-packet-guard/requirements-map.md new file mode 100644 index 00000000..103b30ea --- /dev/null +++ b/sponsor-regulatory-attestation-packet-guard/requirements-map.md @@ -0,0 +1,18 @@ +# Requirements Map + +## SCIBASE Issue #18: Scientific Bounty System + +| Issue capability | Implementation coverage | +| --- | --- | +| Challenge posting portal | Reviews sponsor-side legal identity, authorized signer authority, prize amount, public rules digest, cancellation/refund policy, and compliance contact readiness before a challenge can open. | +| Timeline and prize schedule trust | Validates source-of-funds attestation freshness, third-party funding disclosures, high-value tax reporting plans, and cross-border withholding review before prize commitments are advertised. | +| Arbitration and payout trust | Blocks sponsor packets with unresolved sanctions screening, beneficial-owner hits, restricted export-control classifications, or missing regulated-domain review before downstream arbitration or payment workflows are reached. | +| Public vs. private challenge controls | Requires immutable public rules evidence and participant-control documentation when international participation or export-sensitive work is expected. | +| Repeatable sponsor audit trail | Emits deterministic findings, sponsor packet digests, JSON/Markdown/SVG/MP4 reviewer artifacts, and an audit digest from synthetic evidence only. | + +## Non-Goals + +- No live sanctions, tax, payment, escrow, KYC, identity, or sponsor-system calls. +- No solver payout eligibility scoring, team split routing, milestone payout ledger, or escrow balance accounting. +- No submission package building, workspace privacy checks, reviewer scoring, arbitration decisions, appeals processing, or duplicate solver detection. +- No private sponsor data, credentials, bank details, taxpayer identifiers, regulated research records, or real sanctions records. diff --git a/sponsor-regulatory-attestation-packet-guard/sample-data.js b/sponsor-regulatory-attestation-packet-guard/sample-data.js new file mode 100644 index 00000000..fddd8622 --- /dev/null +++ b/sponsor-regulatory-attestation-packet-guard/sample-data.js @@ -0,0 +1,190 @@ +"use strict"; + +const digestA = "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; +const digestB = "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"; +const digestC = "sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"; +const digestD = "sha256:dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"; + +module.exports = { + generatedAt: "2026-06-01T00:00:00.000Z", + challenges: [ + { + id: "challenge-open-neuro-imaging", + title: "Open Neuro Imaging Benchmark Prize", + sponsor: { + displayName: "Atlas Neuro Collective", + legalName: "Atlas Neuro Collective Foundation", + entityIdentifier: "JP-NPO-000421", + jurisdiction: "JP", + authorizedSigner: { + name: "Mika Sato", + role: "Director of Research Programs", + authorityDigest: digestA + } + }, + prize: { amountUsd: 500 }, + expectedPayoutJurisdictions: ["JP"], + funding: { + sourceOfFundsAttested: true, + statementDigest: digestB, + attestedAt: "2026-05-20T00:00:00.000Z" + }, + sanctionsScreening: { + status: "clear", + reference: "SCR-2026-NEURO-001", + checkedAt: "2026-05-29T00:00:00.000Z", + beneficialOwners: [ + { id: "owner-foundation-board", role: "board", sanctionsHit: false } + ] + }, + taxReporting: { + formStatus: "not_applicable", + withholdingReviewComplete: true + }, + exportControl: { + classification: "exempt", + internationalParticipation: false + }, + publicTerms: { + rulesDigest: digestC, + cancellationPolicy: "published", + refundPolicy: "published" + }, + complianceContact: { email: "compliance@example.test" } + }, + { + id: "challenge-catalyst-literature", + title: "Cross-border Catalyst Literature Review", + sponsor: { + displayName: "Materials Insight Lab", + legalName: "Materials Insight Lab LLC", + entityIdentifier: "US-DE-880011", + jurisdiction: "US", + authorizedSigner: { + name: "J. Morgan", + role: "Chief Scientist", + authorityDigest: digestA + } + }, + prize: { amountUsd: 900 }, + expectedPayoutJurisdictions: ["US", "DE"], + funding: { + sourceOfFundsAttested: true, + statementDigest: digestB, + attestedAt: "2026-02-01T00:00:00.000Z" + }, + sanctionsScreening: { + status: "review", + reference: "SCR-2026-MAT-019", + checkedAt: "2026-04-01T00:00:00.000Z", + beneficialOwners: [ + { id: "owner-mil-1", role: "member", sanctionsHit: false } + ] + }, + taxReporting: { + formStatus: "complete", + reportingPlanDigest: digestD, + withholdingReviewComplete: true + }, + exportControl: { + classification: "unknown", + internationalParticipation: true + }, + publicTerms: { + rulesDigest: digestC, + cancellationPolicy: "published" + } + }, + { + id: "challenge-clinical-proteomics", + title: "Clinical Proteomics Translational Prize", + sponsor: { + displayName: "Proteomics Venture Studio", + legalName: "Proteomics Venture Studio Inc.", + entityIdentifier: "US-CA-772210", + jurisdiction: "US", + authorizedSigner: { + name: "Lena Ortiz", + role: "VP Programs", + authorityDigest: digestA + } + }, + prize: { amountUsd: 2500 }, + expectedPayoutJurisdictions: ["US", "IN", "BR"], + funding: { + sourceOfFundsAttested: false, + attestedAt: "2026-05-25T00:00:00.000Z" + }, + sanctionsScreening: { + status: "blocked", + reference: "SCR-2026-CLIN-007", + checkedAt: "2026-05-28T00:00:00.000Z", + beneficialOwners: [ + { id: "owner-pvs-2", role: "fund administrator", sanctionsHit: true } + ] + }, + taxReporting: { + formStatus: "missing", + withholdingReviewComplete: false + }, + exportControl: { + classification: "ear99", + internationalParticipation: true, + participationControlsDigest: digestD + }, + regulatedDomain: true, + publicTerms: { + rulesDigest: digestC, + cancellationPolicy: "published", + refundPolicy: "published" + }, + complianceContact: { email: "legal@example.test" } + }, + { + id: "challenge-dual-use-sensors", + title: "Dual-use Sensor Fusion Model Challenge", + sponsor: { + displayName: "Frontier Robotics Initiative", + legalName: "Frontier Robotics Initiative", + entityIdentifier: "GB-CIC-441200", + jurisdiction: "GB", + authorizedSigner: { + name: "Priya Shah", + role: "Managing Director", + authorityDigest: digestA + } + }, + prize: { amountUsd: 1200 }, + expectedPayoutJurisdictions: ["GB", "US"], + funding: { + sourceOfFundsAttested: true, + statementDigest: digestB, + attestedAt: "2026-05-18T00:00:00.000Z", + thirdPartySponsor: true + }, + sanctionsScreening: { + status: "clear", + reference: "SCR-2026-ROBO-009", + checkedAt: "2026-05-30T00:00:00.000Z", + beneficialOwners: [ + { id: "owner-fri-1", role: "director", sanctionsHit: false } + ] + }, + taxReporting: { + formStatus: "complete", + reportingPlanDigest: digestD, + withholdingReviewComplete: true + }, + exportControl: { + classification: "dual-use-restricted", + internationalParticipation: true + }, + publicTerms: { + rulesDigest: "draft", + cancellationPolicy: "published", + refundPolicy: "published" + }, + complianceContact: { email: "controls@example.test" } + } + ] +}; diff --git a/sponsor-regulatory-attestation-packet-guard/test.js b/sponsor-regulatory-attestation-packet-guard/test.js new file mode 100644 index 00000000..93deb5eb --- /dev/null +++ b/sponsor-regulatory-attestation-packet-guard/test.js @@ -0,0 +1,117 @@ +"use strict"; + +const assert = require("assert"); +const { + evaluateSponsorAttestation, + renderMarkdownReport, + renderSvgSummary, + stableStringify, + daysBetween, + normalizeJurisdiction +} = require("./index"); +const sampleData = require("./sample-data"); + +const digest = "sha256:1111111111111111111111111111111111111111111111111111111111111111"; + +function testSampleReview() { + const result = evaluateSponsorAttestation(sampleData); + + assert.strictEqual(result.releaseDecision, "block_challenge_publication"); + assert.strictEqual(result.summary.challengeCount, 4); + assert.strictEqual(result.summary.readyCount, 1); + assert.strictEqual(result.summary.holdCount, 1); + assert.strictEqual(result.summary.blockCount, 2); + assert.ok(result.summary.criticalCount >= 7, "expected blocking sponsor packet findings"); + assert.ok(result.summary.warningCount >= 5, "expected compliance hold warnings"); + assert.ok(result.auditDigest.match(/^[a-f0-9]{64}$/), "expected sha256 audit digest"); + + const statuses = Object.fromEntries(result.challenges.map((challenge) => [challenge.challengeId, challenge.status])); + assert.strictEqual(statuses["challenge-open-neuro-imaging"], "ready_to_open"); + assert.strictEqual(statuses["challenge-catalyst-literature"], "hold_for_compliance"); + assert.strictEqual(statuses["challenge-clinical-proteomics"], "block_challenge"); + assert.strictEqual(statuses["challenge-dual-use-sensors"], "block_challenge"); + + const codes = new Set(result.findings.map((finding) => finding.code)); + assert.ok(codes.has("missing_source_of_funds_attestation")); + assert.ok(codes.has("blocked_sanctions_screening")); + assert.ok(codes.has("missing_high_value_tax_reporting_plan")); + assert.ok(codes.has("restricted_export_without_counsel_approval")); + assert.ok(codes.has("missing_public_rules_digest")); + assert.ok(codes.has("missing_third_party_funding_disclosure")); +} + +function testCleanChallenge() { + const input = { + generatedAt: "2026-06-01T00:00:00.000Z", + challenges: [ + { + id: "clean-challenge", + title: "Clean Challenge", + sponsor: { + displayName: "Clean Lab", + legalName: "Clean Lab Foundation", + entityIdentifier: "US-NPO-100", + jurisdiction: "US", + authorizedSigner: { + name: "A. Nguyen", + role: "Program Officer", + authorityDigest: digest + } + }, + prize: { amountUsd: 1000 }, + expectedPayoutJurisdictions: ["US"], + funding: { + sourceOfFundsAttested: true, + statementDigest: digest, + attestedAt: "2026-05-29T00:00:00.000Z" + }, + sanctionsScreening: { + status: "clear", + reference: "SCR-CLEAN-1", + checkedAt: "2026-05-29T00:00:00.000Z", + beneficialOwners: [{ id: "owner-clean-1", role: "board", sanctionsHit: false }] + }, + taxReporting: { + formStatus: "complete", + reportingPlanDigest: digest, + withholdingReviewComplete: true + }, + exportControl: { + classification: "ear99", + internationalParticipation: false + }, + publicTerms: { + rulesDigest: digest, + cancellationPolicy: "published", + refundPolicy: "published" + }, + complianceContact: { email: "clean@example.test" } + } + ] + }; + + const result = evaluateSponsorAttestation(input); + assert.strictEqual(result.releaseDecision, "ready_to_open"); + assert.strictEqual(result.summary.findingCount, 0); + assert.strictEqual(result.challenges[0].status, "ready_to_open"); +} + +function testRenderersAndUtilities() { + const result = evaluateSponsorAttestation(sampleData); + const markdown = renderMarkdownReport(result); + const svg = renderSvgSummary(result); + + assert.ok(markdown.includes("Sponsor Regulatory Attestation Packet Review")); + assert.ok(markdown.includes(result.auditDigest)); + assert.ok(svg.includes("