diff --git a/project-data-residency-transfer-guard/demo.js b/project-data-residency-transfer-guard/demo.js
new file mode 100644
index 00000000..f50f8110
--- /dev/null
+++ b/project-data-residency-transfer-guard/demo.js
@@ -0,0 +1,79 @@
+const fs = require("fs");
+const path = require("path");
+
+const { evaluateResidencyTransfers } = require("./index");
+const { scenarios } = require("./sample-data");
+
+const reportDir = path.join(__dirname, "reports");
+fs.mkdirSync(reportDir, { recursive: true });
+
+const report = evaluateResidencyTransfers(scenarios);
+fs.writeFileSync(
+ path.join(reportDir, "data-residency-transfer-report.json"),
+ `${JSON.stringify(report, null, 2)}\n`
+);
+
+const lines = [
+ "# Project Data Residency Transfer Guard",
+ "",
+ `Generated: ${report.generatedAt}`,
+ `Report digest: \`${report.reportDigest}\``,
+ "",
+ "## Summary",
+ "",
+ `- Total scenarios: ${report.summary.total}`,
+ `- Approved: ${report.summary.approved}`,
+ `- Needs review: ${report.summary.needs_review}`,
+ `- Blocked: ${report.summary.blocked}`,
+ `- Findings: ${report.summary.findingCount}`,
+ "",
+ "## Decisions",
+ "",
+];
+
+for (const decision of report.decisions) {
+ lines.push(`### ${decision.id}`);
+ lines.push("");
+ lines.push(`- Decision: **${decision.decision}**`);
+ lines.push(`- Action: \`${decision.action}\``);
+ lines.push(`- Object: \`${decision.targetObject}\``);
+ lines.push(`- Classification: \`${decision.classification}\``);
+ lines.push(`- Route: ${decision.homeRegion} -> ${decision.destinationRegion}`);
+ lines.push(`- Audit digest: \`${decision.auditDigest}\``);
+ lines.push("- Findings:");
+ const findings = decision.findings.length
+ ? decision.findings
+ : [{ severity: "info", code: "none", message: "No blocking or review findings." }];
+ for (const finding of findings) {
+ lines.push(` - ${finding.severity}: ${finding.code} - ${finding.message}`);
+ }
+ lines.push("");
+}
+if (lines[lines.length - 1] === "") {
+ lines.pop();
+}
+
+fs.writeFileSync(path.join(reportDir, "data-residency-transfer-report.md"), `${lines.join("\n")}\n`);
+
+const blocked = report.summary.blocked;
+const review = report.summary.needs_review;
+const approved = report.summary.approved;
+const svg = `
+`;
+fs.writeFileSync(path.join(reportDir, "summary.svg"), svg);
+
+console.log(JSON.stringify(report.summary, null, 2));
diff --git a/project-data-residency-transfer-guard/index.js b/project-data-residency-transfer-guard/index.js
new file mode 100644
index 00000000..964e4de9
--- /dev/null
+++ b/project-data-residency-transfer-guard/index.js
@@ -0,0 +1,143 @@
+const crypto = require("crypto");
+
+const SENSITIVE_CLASSIFICATIONS = new Set([
+ "restricted-human-genomic",
+ "clinical-derived-sensitive",
+ "controlled-dataset",
+ "embargoed-artifact",
+]);
+
+function isSensitive(classification) {
+ return SENSITIVE_CLASSIFICATIONS.has(String(classification || "").toLowerCase());
+}
+
+function isCrossRegion(scenario) {
+ return scenario.project.homeRegion !== scenario.request.destinationRegion;
+}
+
+function stableDigest(value) {
+ return crypto.createHash("sha256").update(JSON.stringify(value)).digest("hex");
+}
+
+function daysUntil(dateText, now = new Date("2026-05-29T00:00:00Z")) {
+ if (!dateText) return 0;
+ const target = new Date(`${dateText}T00:00:00Z`);
+ if (Number.isNaN(target.getTime())) return 0;
+ return Math.ceil((target.getTime() - now.getTime()) / 86_400_000);
+}
+
+function evaluateScenario(scenario) {
+ const findings = [];
+ const sensitive = isSensitive(scenario.request.targetClassification);
+ const crossRegion = isCrossRegion(scenario);
+ const externalInstitution =
+ scenario.project.homeInstitution !== scenario.request.actorInstitution;
+
+ if (sensitive && crossRegion && scenario.evidence.dpaStatus !== "approved") {
+ findings.push({
+ severity: "blocker",
+ code: "missing-cross-region-dpa",
+ message: "Sensitive project data cannot cross residency boundaries without an approved DPA.",
+ });
+ }
+
+ if (sensitive && scenario.evidence.duaStatus === "expired") {
+ findings.push({
+ severity: "blocker",
+ code: "expired-data-use-agreement",
+ message: "The data-use agreement is expired for the requested transfer.",
+ });
+ }
+
+ const embargoDays = daysUntil(scenario.evidence.exportEmbargoUntil);
+ if (embargoDays > 0) {
+ findings.push({
+ severity: "blocker",
+ code: "active-export-embargo",
+ message: `Export embargo remains active for ${embargoDays} day(s).`,
+ });
+ }
+
+ if (externalInstitution && !scenario.evidence.externalPartnerAllowed) {
+ findings.push({
+ severity: "blocker",
+ code: "external-partner-not-allowed",
+ message: "Project policy does not allow this external partner transfer.",
+ });
+ }
+
+ if (!scenario.evidence.collaboratorAffiliationVerified) {
+ findings.push({
+ severity: "review",
+ code: "unverified-affiliation",
+ message: "Collaborator affiliation must be verified before access is granted.",
+ });
+ }
+
+ if (sensitive && !scenario.evidence.dataStewardApproval) {
+ findings.push({
+ severity: "review",
+ code: "missing-data-steward-approval",
+ message: "Sensitive object access requires data-steward approval evidence.",
+ });
+ }
+
+ if (scenario.project.visibility === "public" && !sensitive && findings.length === 0) {
+ findings.push({
+ severity: "info",
+ code: "public-metadata-safe",
+ message: "Public non-sensitive metadata transfer is allowed with current evidence.",
+ });
+ }
+
+ const blockers = findings.filter((finding) => finding.severity === "blocker");
+ const reviewItems = findings.filter((finding) => finding.severity === "review");
+ const decision = blockers.length ? "blocked" : reviewItems.length ? "needs_review" : "approved";
+
+ return {
+ id: scenario.id,
+ projectId: scenario.project.id,
+ action: scenario.request.action,
+ targetObject: scenario.request.targetObject,
+ classification: scenario.request.targetClassification,
+ homeRegion: scenario.project.homeRegion,
+ destinationRegion: scenario.request.destinationRegion,
+ crossRegion,
+ externalInstitution,
+ decision,
+ findings,
+ auditDigest: stableDigest({
+ scenario,
+ decision,
+ findingCodes: findings.map((finding) => finding.code),
+ }),
+ };
+}
+
+function evaluateResidencyTransfers(scenarios) {
+ const decisions = scenarios.map(evaluateScenario);
+ const summary = decisions.reduce(
+ (acc, decision) => {
+ acc.total += 1;
+ acc[decision.decision] += 1;
+ acc.findingCount += decision.findings.length;
+ return acc;
+ },
+ { total: 0, approved: 0, needs_review: 0, blocked: 0, findingCount: 0 }
+ );
+
+ return {
+ generatedAt: "2026-05-29T00:00:00.000Z",
+ guard: "project-data-residency-transfer-guard",
+ summary,
+ decisions,
+ reportDigest: stableDigest({ summary, decisions }),
+ };
+}
+
+module.exports = {
+ evaluateResidencyTransfers,
+ evaluateScenario,
+ isSensitive,
+ isCrossRegion,
+};
diff --git a/project-data-residency-transfer-guard/readme.md b/project-data-residency-transfer-guard/readme.md
new file mode 100644
index 00000000..c62fa4d0
--- /dev/null
+++ b/project-data-residency-transfer-guard/readme.md
@@ -0,0 +1,27 @@
+# Project Data Residency Transfer Guard
+
+This is a focused User & Project Management slice for SCIBASE project spaces. It evaluates project access and sharing changes before data crosses institutional, jurisdictional, or residency boundaries.
+
+The guard uses synthetic reviewer scenarios only. It does not call identity providers, OAuth, SAML, ORCID, production access-control systems, external APIs, payment systems, live projects, credentials, tokens, or private user data.
+
+## What It Checks
+
+- Project visibility and home institution region.
+- Requested destination region and external collaborator institution.
+- Dataset/object classification, including restricted human genomic and clinical-derived data.
+- DPA, DUA, IRB, export embargo, and data-steward approval evidence.
+- Deterministic audit digests for reviewer handoff.
+
+## Run
+
+```bash
+node project-data-residency-transfer-guard/test.js
+node project-data-residency-transfer-guard/demo.js
+node project-data-residency-transfer-guard/render-video.js
+```
+
+Generated reviewer artifacts are written to `project-data-residency-transfer-guard/reports/`.
+
+## Scope Boundary
+
+This slice is distinct from existing #11 work around RBAC/workspace ledgers, privacy review, member lifecycle/offboarding, institutional recertification, anonymous review, identity merge/export, data-room consent, profile sync, archive handoff, access-audit anomaly, role delegation, invitation/MFA, funding attribution, service-token governance, deletion/erasure, break-glass, visibility transition, provisioning baseline, object-permission inheritance, reputation anomaly, session step-up, and collaborator conflict-of-interest guards.
diff --git a/project-data-residency-transfer-guard/render-video.js b/project-data-residency-transfer-guard/render-video.js
new file mode 100644
index 00000000..9542c55e
--- /dev/null
+++ b/project-data-residency-transfer-guard/render-video.js
@@ -0,0 +1,73 @@
+const { execFileSync } = require("child_process");
+const fs = require("fs");
+const path = require("path");
+
+const { evaluateResidencyTransfers } = require("./index");
+const { scenarios } = require("./sample-data");
+
+const reportDir = path.join(__dirname, "reports");
+const mp4Path = path.join(reportDir, "demo.mp4");
+const framePath = path.join(reportDir, "demo-frame.ppm");
+const report = evaluateResidencyTransfers(scenarios);
+
+function rgb(hex) {
+ return [
+ Number.parseInt(hex.slice(0, 2), 16),
+ Number.parseInt(hex.slice(2, 4), 16),
+ Number.parseInt(hex.slice(4, 6), 16),
+ ];
+}
+
+function rect(buffer, width, x, y, w, h, color) {
+ const [r, g, b] = rgb(color);
+ for (let row = y; row < y + h; row += 1) {
+ for (let col = x; col < x + w; col += 1) {
+ const idx = (row * width + col) * 3;
+ buffer[idx] = r;
+ buffer[idx + 1] = g;
+ buffer[idx + 2] = b;
+ }
+ }
+}
+
+const width = 960;
+const height = 540;
+const pixels = Buffer.alloc(width * height * 3, 0xf6);
+for (let i = 0; i < pixels.length; i += 3) {
+ pixels[i] = 0xf6;
+ pixels[i + 1] = 0xf0;
+ pixels[i + 2] = 0xdf;
+}
+rect(pixels, width, 52, 48, 856, 444, "fffaf0");
+rect(pixels, width, 52, 48, 856, 4, "614a2f");
+rect(pixels, width, 52, 488, 856, 4, "614a2f");
+rect(pixels, width, 52, 48, 4, 444, "614a2f");
+rect(pixels, width, 904, 48, 4, 444, "614a2f");
+rect(pixels, width, 96, 160, Math.max(1, report.summary.approved) * 120, 58, "2f855a");
+rect(pixels, width, 96, 248, Math.max(1, report.summary.needs_review) * 120, 58, "b7791f");
+rect(pixels, width, 96, 336, Math.max(1, report.summary.blocked) * 120, 58, "b83232");
+rect(pixels, width, 96, 432, 720, 12, "614a2f");
+fs.writeFileSync(framePath, Buffer.concat([Buffer.from(`P6\n${width} ${height}\n255\n`), pixels]));
+
+execFileSync(
+ "ffmpeg",
+ [
+ "-y",
+ "-loop",
+ "1",
+ "-framerate",
+ "24",
+ "-i",
+ framePath,
+ "-t",
+ "5",
+ "-vf",
+ "format=yuv420p",
+ "-movflags",
+ "+faststart",
+ mp4Path,
+ ],
+ { stdio: "inherit" }
+);
+
+console.log(mp4Path);
diff --git a/project-data-residency-transfer-guard/reports/data-residency-transfer-report.json b/project-data-residency-transfer-guard/reports/data-residency-transfer-report.json
new file mode 100644
index 00000000..1e181a09
--- /dev/null
+++ b/project-data-residency-transfer-guard/reports/data-residency-transfer-report.json
@@ -0,0 +1,98 @@
+{
+ "generatedAt": "2026-05-29T00:00:00.000Z",
+ "guard": "project-data-residency-transfer-guard",
+ "summary": {
+ "total": 4,
+ "approved": 2,
+ "needs_review": 0,
+ "blocked": 2,
+ "findingCount": 5
+ },
+ "decisions": [
+ {
+ "id": "pub-seq-archive-eu-review",
+ "projectId": "project-atlas-42",
+ "action": "grant_dataset_read",
+ "targetObject": "dataset:human-genomes-v3",
+ "classification": "restricted-human-genomic",
+ "homeRegion": "EU",
+ "destinationRegion": "US",
+ "crossRegion": true,
+ "externalInstitution": true,
+ "decision": "blocked",
+ "findings": [
+ {
+ "severity": "blocker",
+ "code": "missing-cross-region-dpa",
+ "message": "Sensitive project data cannot cross residency boundaries without an approved DPA."
+ },
+ {
+ "severity": "blocker",
+ "code": "active-export-embargo",
+ "message": "Export embargo remains active for 33 day(s)."
+ },
+ {
+ "severity": "review",
+ "code": "missing-data-steward-approval",
+ "message": "Sensitive object access requires data-steward approval evidence."
+ }
+ ],
+ "auditDigest": "5833dcf52dc90fd540b3b5805320322a1be5204e3081ef44bc4dda04539e542d"
+ },
+ {
+ "id": "open-metadata-catalogue",
+ "projectId": "project-open-19",
+ "action": "publish_metadata_snapshot",
+ "targetObject": "metadata:materials-index",
+ "classification": "public-metadata",
+ "homeRegion": "US",
+ "destinationRegion": "US",
+ "crossRegion": false,
+ "externalInstitution": false,
+ "decision": "approved",
+ "findings": [
+ {
+ "severity": "info",
+ "code": "public-metadata-safe",
+ "message": "Public non-sensitive metadata transfer is allowed with current evidence."
+ }
+ ],
+ "auditDigest": "671f131e434ab50a1548feae1bd9389942765501defd705319c546a7566c50a7"
+ },
+ {
+ "id": "private-clinical-partner-sync",
+ "projectId": "project-clinical-7",
+ "action": "export_analysis_notebook",
+ "targetObject": "notebook:clinical-endpoint-model",
+ "classification": "clinical-derived-sensitive",
+ "homeRegion": "US",
+ "destinationRegion": "EU",
+ "crossRegion": true,
+ "externalInstitution": true,
+ "decision": "blocked",
+ "findings": [
+ {
+ "severity": "blocker",
+ "code": "expired-data-use-agreement",
+ "message": "The data-use agreement is expired for the requested transfer."
+ }
+ ],
+ "auditDigest": "7edb86c12360ad8342e565634a529b704bd90a972effe71eeac92b02ddd6bbb7"
+ },
+ {
+ "id": "institutional-only-student-handoff",
+ "projectId": "project-field-23",
+ "action": "grant_document_comment",
+ "targetObject": "document:field-protocol-draft",
+ "classification": "internal-collaboration",
+ "homeRegion": "EU",
+ "destinationRegion": "EU",
+ "crossRegion": false,
+ "externalInstitution": false,
+ "decision": "approved",
+ "findings": [],
+ "auditDigest": "b5064e73fafbfd1e2c9d756868baca9f8af162c5d9b98c44454382fd0ea9c184"
+ }
+ ],
+ "reportDigest": "df69e10d399a5cdd39225df47e8e67c725a226b0e1eaf6c9540afe12ce467573"
+}
diff --git a/project-data-residency-transfer-guard/reports/data-residency-transfer-report.md b/project-data-residency-transfer-guard/reports/data-residency-transfer-report.md
new file mode 100644
index 00000000..70e9dde3
--- /dev/null
+++ b/project-data-residency-transfer-guard/reports/data-residency-transfer-report.md
@@ -0,0 +1,60 @@
+# Project Data Residency Transfer Guard
+
+Generated: 2026-05-29T00:00:00.000Z
+Report digest: `df69e10d399a5cdd39225df47e8e67c725a226b0e1eaf6c9540afe12ce467573`
+
+## Summary
+
+- Total scenarios: 4
+- Approved: 2
+- Needs review: 0
+- Blocked: 2
+- Findings: 5
+
+## Decisions
+
+### pub-seq-archive-eu-review
+
+- Decision: **blocked**
+- Action: `grant_dataset_read`
+- Object: `dataset:human-genomes-v3`
+- Classification: `restricted-human-genomic`
+- Route: EU -> US
+- Audit digest: `5833dcf52dc90fd540b3b5805320322a1be5204e3081ef44bc4dda04539e542d`
+- Findings:
+ - blocker: missing-cross-region-dpa - Sensitive project data cannot cross residency boundaries without an approved DPA.
+ - blocker: active-export-embargo - Export embargo remains active for 33 day(s).
+ - review: missing-data-steward-approval - Sensitive object access requires data-steward approval evidence.
+
+### open-metadata-catalogue
+
+- Decision: **approved**
+- Action: `publish_metadata_snapshot`
+- Object: `metadata:materials-index`
+- Classification: `public-metadata`
+- Route: US -> US
+- Audit digest: `671f131e434ab50a1548feae1bd9389942765501defd705319c546a7566c50a7`
+- Findings:
+ - info: public-metadata-safe - Public non-sensitive metadata transfer is allowed with current evidence.
+
+### private-clinical-partner-sync
+
+- Decision: **blocked**
+- Action: `export_analysis_notebook`
+- Object: `notebook:clinical-endpoint-model`
+- Classification: `clinical-derived-sensitive`
+- Route: US -> EU
+- Audit digest: `7edb86c12360ad8342e565634a529b704bd90a972effe71eeac92b02ddd6bbb7`
+- Findings:
+ - blocker: expired-data-use-agreement - The data-use agreement is expired for the requested transfer.
+
+### institutional-only-student-handoff
+
+- Decision: **approved**
+- Action: `grant_document_comment`
+- Object: `document:field-protocol-draft`
+- Classification: `internal-collaboration`
+- Route: EU -> EU
+- Audit digest: `b5064e73fafbfd1e2c9d756868baca9f8af162c5d9b98c44454382fd0ea9c184`
+- Findings:
+ - info: none - No blocking or review findings.
diff --git a/project-data-residency-transfer-guard/reports/demo.mp4 b/project-data-residency-transfer-guard/reports/demo.mp4
new file mode 100644
index 00000000..f5061d3d
Binary files /dev/null and b/project-data-residency-transfer-guard/reports/demo.mp4 differ
diff --git a/project-data-residency-transfer-guard/reports/summary.svg b/project-data-residency-transfer-guard/reports/summary.svg
new file mode 100644
index 00000000..fa8447d3
--- /dev/null
+++ b/project-data-residency-transfer-guard/reports/summary.svg
@@ -0,0 +1,15 @@
+
diff --git a/project-data-residency-transfer-guard/sample-data.js b/project-data-residency-transfer-guard/sample-data.js
new file mode 100644
index 00000000..f0d25c38
--- /dev/null
+++ b/project-data-residency-transfer-guard/sample-data.js
@@ -0,0 +1,128 @@
+const scenarios = [
+ {
+ id: "pub-seq-archive-eu-review",
+ project: {
+ id: "project-atlas-42",
+ title: "Pan-European rare-disease sequencing archive",
+ visibility: "institutional-only",
+ homeInstitution: "Karolinska Institute",
+ homeRegion: "EU",
+ dataSteward: "github:steward-eu",
+ },
+ request: {
+ actor: "github:reviewer-us",
+ actorInstitution: "Northbridge Genomics",
+ actorRegion: "US",
+ action: "grant_dataset_read",
+ targetObject: "dataset:human-genomes-v3",
+ targetClassification: "restricted-human-genomic",
+ destinationRegion: "US",
+ purpose: "external reproducibility review",
+ },
+ evidence: {
+ dpaStatus: "missing",
+ duaStatus: "approved",
+ irbStatus: "approved",
+ exportEmbargoUntil: "2026-07-01",
+ institutionalPolicy: "eu-sensitive-data-stays-eu",
+ collaboratorAffiliationVerified: true,
+ dataStewardApproval: false,
+ externalPartnerAllowed: true,
+ },
+ },
+ {
+ id: "open-metadata-catalogue",
+ project: {
+ id: "project-open-19",
+ title: "Open materials metadata catalogue",
+ visibility: "public",
+ homeInstitution: "LTC Lab",
+ homeRegion: "US",
+ dataSteward: "github:steward-us",
+ },
+ request: {
+ actor: "github:catalog-curator",
+ actorInstitution: "LTC Lab",
+ actorRegion: "US",
+ action: "publish_metadata_snapshot",
+ targetObject: "metadata:materials-index",
+ targetClassification: "public-metadata",
+ destinationRegion: "US",
+ purpose: "public catalogue refresh",
+ },
+ evidence: {
+ dpaStatus: "not-required",
+ duaStatus: "not-required",
+ irbStatus: "not-required",
+ exportEmbargoUntil: null,
+ institutionalPolicy: "public-metadata-ok",
+ collaboratorAffiliationVerified: true,
+ dataStewardApproval: true,
+ externalPartnerAllowed: true,
+ },
+ },
+ {
+ id: "private-clinical-partner-sync",
+ project: {
+ id: "project-clinical-7",
+ title: "Private clinical outcomes workspace",
+ visibility: "private",
+ homeInstitution: "UCSF",
+ homeRegion: "US",
+ dataSteward: "github:clinical-steward",
+ },
+ request: {
+ actor: "github:partner-ops",
+ actorInstitution: "TrialOps GmbH",
+ actorRegion: "EU",
+ action: "export_analysis_notebook",
+ targetObject: "notebook:clinical-endpoint-model",
+ targetClassification: "clinical-derived-sensitive",
+ destinationRegion: "EU",
+ purpose: "contract research partner review",
+ },
+ evidence: {
+ dpaStatus: "approved",
+ duaStatus: "expired",
+ irbStatus: "approved",
+ exportEmbargoUntil: null,
+ institutionalPolicy: "partner-transfer-requires-current-dua",
+ collaboratorAffiliationVerified: true,
+ dataStewardApproval: true,
+ externalPartnerAllowed: true,
+ },
+ },
+ {
+ id: "institutional-only-student-handoff",
+ project: {
+ id: "project-field-23",
+ title: "Institutional field study workspace",
+ visibility: "institutional-only",
+ homeInstitution: "University of Oslo",
+ homeRegion: "EU",
+ dataSteward: "github:field-steward",
+ },
+ request: {
+ actor: "github:student-collab",
+ actorInstitution: "University of Oslo",
+ actorRegion: "EU",
+ action: "grant_document_comment",
+ targetObject: "document:field-protocol-draft",
+ targetClassification: "internal-collaboration",
+ destinationRegion: "EU",
+ purpose: "student collaborator handoff",
+ },
+ evidence: {
+ dpaStatus: "not-required",
+ duaStatus: "not-required",
+ irbStatus: "approved",
+ exportEmbargoUntil: null,
+ institutionalPolicy: "same-institution-collaboration-ok",
+ collaboratorAffiliationVerified: true,
+ dataStewardApproval: true,
+ externalPartnerAllowed: false,
+ },
+ },
+];
+
+module.exports = { scenarios };
diff --git a/project-data-residency-transfer-guard/test.js b/project-data-residency-transfer-guard/test.js
new file mode 100644
index 00000000..0559ed8f
--- /dev/null
+++ b/project-data-residency-transfer-guard/test.js
@@ -0,0 +1,39 @@
+const assert = require("assert");
+
+const { evaluateResidencyTransfers, evaluateScenario, isSensitive } = require("./index");
+const { scenarios } = require("./sample-data");
+
+assert.strictEqual(isSensitive("restricted-human-genomic"), true);
+assert.strictEqual(isSensitive("public-metadata"), false);
+
+const euExport = evaluateScenario(scenarios[0]);
+assert.strictEqual(euExport.decision, "blocked");
+assert(euExport.findings.some((finding) => finding.code === "missing-cross-region-dpa"));
+assert(euExport.findings.some((finding) => finding.code === "active-export-embargo"));
+assert(euExport.findings.some((finding) => finding.code === "missing-data-steward-approval"));
+
+const publicMetadata = evaluateScenario(scenarios[1]);
+assert.strictEqual(publicMetadata.decision, "approved");
+assert(publicMetadata.findings.some((finding) => finding.code === "public-metadata-safe"));
+
+const expiredDua = evaluateScenario(scenarios[2]);
+assert.strictEqual(expiredDua.decision, "blocked");
+assert(expiredDua.findings.some((finding) => finding.code === "expired-data-use-agreement"));
+
+const sameInstitution = evaluateScenario(scenarios[3]);
+assert.strictEqual(sameInstitution.decision, "approved");
+assert.strictEqual(sameInstitution.crossRegion, false);
+assert.strictEqual(sameInstitution.externalInstitution, false);
+
+const report = evaluateResidencyTransfers(scenarios);
+assert.deepStrictEqual(report.summary, {
+ total: 4,
+ approved: 2,
+ needs_review: 0,
+ blocked: 2,
+ findingCount: 5,
+});
+assert.match(report.reportDigest, /^[0-9a-f]{64}$/);
+assert(report.decisions.every((decision) => /^[0-9a-f]{64}$/.test(decision.auditDigest)));
+
+console.log("project-data-residency-transfer-guard tests passed");