diff --git a/README.md b/README.md
index d338cf6..71c4bb3 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,6 @@
# deepevents.ai
deepevents.ai main codebase
+
+## Modules
+
+- [`user-project-management`](./user-project-management) - runnable prototype for identity, researcher profiles, scientific project spaces, permissions, audit logs, and reputation metrics.
diff --git a/user-project-management/README.md b/user-project-management/README.md
new file mode 100644
index 0000000..ab5b6ed
--- /dev/null
+++ b/user-project-management/README.md
@@ -0,0 +1,60 @@
+# User & Project Management
+
+This module is a self-contained implementation for SCIBASE.AI issue #11. It models identity, researcher profiles, project spaces, permissions, sharing, audit logs, and reputation metrics for scientific collaboration.
+
+## What It Covers
+
+- Authentication through email, OAuth-style linked providers, ORCID, SAML, and MFA enforcement.
+- Researcher profiles with ORCID sync, affiliations, keywords, activity, citation-style reputation metrics, and public/private profile mode.
+- Scientific project spaces with documents, code, datasets, discussion threads, citations, funding metadata, and archive-ready status.
+- Role-based access control for Owner, Admin, Contributor, Reviewer, and Viewer.
+- Object-level permissions for project, documents, code, datasets, and review scopes.
+- Time-limited collaborator invitations and project-level audit logs.
+- Browser dashboard and JSON API for reviewer smoke testing.
+
+## Run Locally
+
+```bash
+cd user-project-management
+npm test
+npm start
+```
+
+Then open `http://localhost:4130`.
+
+## API Surface
+
+- `GET /api/dashboard`
+- `GET /api/auth/check`
+- `GET /api/users/user-alice/orcid`
+- `GET /api/users/user-alice/reputation`
+- `GET /api/projects/create`
+- `GET /api/access/check`
+
+## Requirement Mapping
+
+- Authentication and identity: implemented by `authenticateIdentity`.
+- ORCID/OAuth/SAML/account linking: represented in user identity links and institutional SAML metadata.
+- Researcher profiles: implemented by user records, `syncOrcidProfile`, activity, keywords, and profile visibility.
+- Citation and reputation metrics: implemented by `computeReputation`.
+- Project spaces: implemented by `createProjectSpace` and `demoWorkspace.projects`.
+- Permissions and access control: implemented by `rolePermissions`, `evaluateAccess`, and object scopes.
+- External collaborators and time-limited access: implemented by `inviteCollaborator`.
+- Project audit log: implemented by per-project `auditLog`.
+
+## Verification
+
+```bash
+npm test
+node src/server.js
+```
+
+Optional smoke checks:
+
+```bash
+curl -s http://localhost:4130/api/dashboard
+curl -s http://localhost:4130/api/auth/check
+curl -s http://localhost:4130/api/access/check
+```
+
+Demo artifacts are committed under `docs/demo/`, including `dashboard.png` and `user-project-management-demo.mp4`.
diff --git a/user-project-management/docs/demo-script.md b/user-project-management/docs/demo-script.md
new file mode 100644
index 0000000..e108687
--- /dev/null
+++ b/user-project-management/docs/demo-script.md
@@ -0,0 +1,6 @@
+# Demo Script
+
+1. Run `npm test` to verify authentication, ORCID sync, reputation, project creation, permissions, invitations, and dashboard payloads.
+2. Run `npm start` and open `http://localhost:4130`.
+3. Confirm the dashboard shows the project space, identity links, access matrix, and researcher profiles.
+4. Smoke-test `/api/auth/check`, `/api/users/user-alice/reputation`, and `/api/access/check`.
diff --git a/user-project-management/docs/demo/dashboard.png b/user-project-management/docs/demo/dashboard.png
new file mode 100644
index 0000000..f817a0e
Binary files /dev/null and b/user-project-management/docs/demo/dashboard.png differ
diff --git a/user-project-management/docs/demo/user-project-management-demo.mp4 b/user-project-management/docs/demo/user-project-management-demo.mp4
new file mode 100644
index 0000000..09596d4
Binary files /dev/null and b/user-project-management/docs/demo/user-project-management-demo.mp4 differ
diff --git a/user-project-management/package.json b/user-project-management/package.json
new file mode 100644
index 0000000..e8b2cc6
--- /dev/null
+++ b/user-project-management/package.json
@@ -0,0 +1,14 @@
+{
+ "name": "@scibase/user-project-management",
+ "version": "0.1.0",
+ "private": true,
+ "description": "Self-contained user and project management prototype for SCIBASE.AI issue #11.",
+ "type": "module",
+ "scripts": {
+ "start": "node src/server.js",
+ "test": "node --test test/*.test.js"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+}
diff --git a/user-project-management/public/app.js b/user-project-management/public/app.js
new file mode 100644
index 0000000..a4a0029
--- /dev/null
+++ b/user-project-management/public/app.js
@@ -0,0 +1,35 @@
+const dashboard = await fetch("/api/dashboard").then((response) => response.json());
+
+document.querySelector("#projectTitle").textContent = dashboard.project.title;
+document.querySelector("#project").innerHTML = [
+ row("Visibility", dashboard.project.visibility),
+ row("Resources", Object.values(dashboard.project.resources).flat().length),
+ row("Collaborators", dashboard.project.collaborators.length),
+ row("Audit events", dashboard.project.auditLog.length)
+].join("");
+
+document.querySelector("#identity").innerHTML = `
+ ${row("Authenticated", dashboard.auth.authenticated)}
+ ${row("Provider", dashboard.auth.provider)}
+ ${row("Linked identities", dashboard.auth.identityLinks.join(", "))}
+`;
+
+document.querySelector("#access").innerHTML = dashboard.accessMatrix
+ .map((rule) => `
${rule.role}${rule.principalId}${rule.permissions.join(", ")}
`)
+ .join("");
+
+document.querySelector("#profiles").innerHTML = dashboard.profiles
+ .map(
+ (profile) => `
+
+ ${profile.name}
+ ${profile.institution}
+ Score ${profile.reputation.score} ยท ${profile.orcid}
+
+ `
+ )
+ .join("");
+
+function row(label, value) {
+ return `${label}${String(value)}
`;
+}
diff --git a/user-project-management/public/index.html b/user-project-management/public/index.html
new file mode 100644
index 0000000..72f8457
--- /dev/null
+++ b/user-project-management/public/index.html
@@ -0,0 +1,40 @@
+
+
+
+
+
+ SCIBASE User Project Management
+
+
+
+
+
+ SCIBASE.AI / issue #11
+ User & Project Management
+
+
+
+ Project Space
+ Loading...
+
+
+
+ Identity
+ Authentication and ORCID sync
+
+
+
+ Access Control
+ Roles and object permissions
+
+
+
+ Profiles
+ Reputation and activity
+
+
+
+
+
+
+
diff --git a/user-project-management/public/styles.css b/user-project-management/public/styles.css
new file mode 100644
index 0000000..0dafc17
--- /dev/null
+++ b/user-project-management/public/styles.css
@@ -0,0 +1,119 @@
+:root {
+ --ink: #161917;
+ --muted: #64716a;
+ --line: #d9e0dc;
+ --paper: #f5f2ec;
+ --panel: #ffffff;
+ --green: #176b4e;
+ --plum: #6f4568;
+}
+
+* {
+ box-sizing: border-box;
+}
+
+body {
+ margin: 0;
+ background: var(--paper);
+ color: var(--ink);
+ font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
+}
+
+.shell {
+ max-width: 1280px;
+ margin: 0 auto;
+ padding: 32px;
+}
+
+header {
+ display: flex;
+ align-items: end;
+ justify-content: space-between;
+ gap: 24px;
+ margin-bottom: 24px;
+}
+
+p,
+h1,
+h2 {
+ margin: 0;
+}
+
+header p,
+.label {
+ color: var(--muted);
+ font-size: 12px;
+ font-weight: 900;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+}
+
+h1 {
+ max-width: 760px;
+ font-size: clamp(38px, 6vw, 76px);
+ line-height: 0.95;
+}
+
+h2 {
+ margin-top: 8px;
+ font-size: 24px;
+}
+
+.grid {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 16px;
+}
+
+.panel {
+ min-height: 260px;
+ border: 1px solid var(--line);
+ background: var(--panel);
+ padding: 24px;
+}
+
+.hero {
+ grid-row: span 2;
+}
+
+.row,
+.rule,
+.profile {
+ border-top: 1px solid var(--line);
+ padding-top: 12px;
+ margin-top: 14px;
+}
+
+.row span,
+.rule span,
+.profile span,
+.profile small,
+.rule small {
+ display: block;
+ color: var(--muted);
+ line-height: 1.4;
+}
+
+.row strong,
+.rule strong,
+.profile strong {
+ display: block;
+ color: var(--green);
+ overflow-wrap: anywhere;
+}
+
+.rule strong {
+ color: var(--plum);
+}
+
+@media (max-width: 820px) {
+ .shell {
+ padding: 18px;
+ }
+
+ header,
+ .grid {
+ display: grid;
+ grid-template-columns: 1fr;
+ }
+}
diff --git a/user-project-management/src/management-core.js b/user-project-management/src/management-core.js
new file mode 100644
index 0000000..8ed5354
--- /dev/null
+++ b/user-project-management/src/management-core.js
@@ -0,0 +1,219 @@
+export const demoWorkspace = {
+ users: [
+ {
+ id: "user-alice",
+ name: "Alice Chen",
+ email: "alice@scibase.example",
+ institution: "SCIBASE Oncology Lab",
+ field: "Cancer biology",
+ orcid: "0000-0002-1825-0097",
+ oauth: ["ORCID", "Google", "GitHub"],
+ mfaEnabled: true,
+ profileMode: "public",
+ keywords: ["organoids", "biomarkers", "reproducibility"],
+ metrics: { downloads: 1240, forks: 18, endorsements: 42, reproducibilityScore: 94 },
+ activity: ["Created organoid response atlas", "Reviewed validation cohort", "Published v1 DOI"]
+ },
+ {
+ id: "user-mateo",
+ name: "Mateo Rivera",
+ email: "mateo@open-repro.example",
+ institution: "Open Reproducibility Center",
+ field: "Computational biology",
+ orcid: "0000-0003-2201-4120",
+ oauth: ["ORCID", "GitHub", "LinkedIn"],
+ mfaEnabled: true,
+ profileMode: "public",
+ keywords: ["pipelines", "statistics", "notebooks"],
+ metrics: { downloads: 840, forks: 11, endorsements: 35, reproducibilityScore: 91 },
+ activity: ["Added reproducibility workflow", "Opened peer review thread"]
+ }
+ ],
+ institutions: [
+ { id: "inst-sci", name: "SCIBASE Oncology Lab", samlEntityId: "https://sso.scibase.example/saml", domains: ["scibase.example"] },
+ { id: "inst-open", name: "Open Reproducibility Center", samlEntityId: "https://sso.open-repro.example/saml", domains: ["open-repro.example"] }
+ ],
+ projects: [
+ {
+ id: "project-organoid-response",
+ title: "Organoid chemotherapy response atlas",
+ visibility: "institutional-only",
+ status: "active",
+ ownerId: "user-alice",
+ metadata: {
+ funding: ["Open Science Accelerator"],
+ citation: "Chen and Rivera, 2026",
+ tags: ["oncology", "organoids"]
+ },
+ resources: {
+ documents: ["manuscript/main.md", "protocols/organoid-culture.md"],
+ code: ["code/run_analysis.py"],
+ datasets: ["data/growth_curves.csv"],
+ discussions: ["review-statistics", "data-access"],
+ citations: ["10.5555/scibase.project-organoid-response.v1"]
+ },
+ collaborators: ["user-alice", "user-mateo"],
+ accessRules: [
+ { principalId: "user-alice", role: "Owner", scope: "project", expiresAt: null },
+ { principalId: "user-mateo", role: "Contributor", scope: "code", expiresAt: null },
+ { principalId: "external-reviewer-1", role: "Reviewer", scope: "documents", expiresAt: "2026-06-01T00:00:00.000Z" }
+ ],
+ auditLog: [
+ { actorId: "user-alice", action: "project.created", target: "project-organoid-response", createdAt: "2026-05-01T10:00:00.000Z" },
+ { actorId: "user-alice", action: "access.invited", target: "external-reviewer-1", createdAt: "2026-05-04T13:00:00.000Z" },
+ { actorId: "user-mateo", action: "code.updated", target: "code/run_analysis.py", createdAt: "2026-05-05T15:30:00.000Z" }
+ ]
+ }
+ ]
+};
+
+export const rolePermissions = {
+ Owner: ["manage_project", "manage_access", "edit_documents", "edit_code", "download_data", "review", "archive"],
+ Admin: ["manage_access", "edit_documents", "edit_code", "download_data", "review"],
+ Contributor: ["edit_documents", "edit_code", "review"],
+ Reviewer: ["read_documents", "review"],
+ Viewer: ["read_documents"]
+};
+
+export function authenticateIdentity({ provider, identifier, mfaCode }, workspace = demoWorkspace) {
+ const user = workspace.users.find((item) => item.email === identifier || item.orcid === identifier);
+ if (!user) return { authenticated: false, reason: "unknown_identity" };
+ const providerAllowed = provider === "email" || user.oauth.includes(provider) || provider === "SAML";
+ const mfaSatisfied = !user.mfaEnabled || /^\d{6}$/.test(mfaCode || "");
+ return {
+ authenticated: providerAllowed && mfaSatisfied,
+ userId: user.id,
+ provider,
+ identityLinks: [...user.oauth, "email", "SAML"],
+ reason: providerAllowed ? (mfaSatisfied ? "ok" : "mfa_required") : "provider_not_linked"
+ };
+}
+
+export function syncOrcidProfile(userId, workspace = demoWorkspace) {
+ const user = getUser(userId, workspace);
+ return {
+ userId,
+ orcid: user.orcid,
+ importedPublications: [
+ `${user.name}. Reproducible organoid assays. 2026.`,
+ `${user.name}. Scientific workflow governance. 2025.`
+ ],
+ affiliations: [user.institution],
+ keywords: user.keywords
+ };
+}
+
+export function computeReputation(userId, workspace = demoWorkspace) {
+ const user = getUser(userId, workspace);
+ const score = Math.round(
+ Math.log1p(user.metrics.downloads) * 5 +
+ user.metrics.forks * 1.5 +
+ user.metrics.endorsements * 2 +
+ user.metrics.reproducibilityScore * 0.9
+ );
+ return {
+ userId,
+ score,
+ breakdown: [
+ { label: "downloads", value: user.metrics.downloads },
+ { label: "forks", value: user.metrics.forks },
+ { label: "endorsements", value: user.metrics.endorsements },
+ { label: "reproducibility", value: user.metrics.reproducibilityScore }
+ ]
+ };
+}
+
+export function createProjectSpace({ title, ownerId, visibility = "private", tags = [] }, workspace = demoWorkspace) {
+ getUser(ownerId, workspace);
+ const id = `project-${slug(title)}`;
+ const project = {
+ id,
+ title,
+ visibility,
+ status: "active",
+ ownerId,
+ metadata: { funding: [], citation: "", tags },
+ resources: { documents: [], code: [], datasets: [], discussions: [], citations: [] },
+ collaborators: [ownerId],
+ accessRules: [{ principalId: ownerId, role: "Owner", scope: "project", expiresAt: null }],
+ auditLog: [{ actorId: ownerId, action: "project.created", target: id, createdAt: "2026-05-09T00:00:00.000Z" }]
+ };
+ return { workspace: { ...workspace, projects: [...workspace.projects, project] }, project };
+}
+
+export function evaluateAccess({ userId, projectId, action, objectType = "project" }, workspace = demoWorkspace) {
+ const project = getProject(projectId, workspace);
+ const rules = project.accessRules.filter((rule) => rule.principalId === userId && !isExpired(rule.expiresAt));
+ const allowedRules = rules.filter((rule) => rule.scope === "project" || rule.scope === objectType);
+ const granted = allowedRules.some((rule) => (rolePermissions[rule.role] || []).includes(action));
+ return {
+ granted,
+ userId,
+ projectId,
+ action,
+ objectType,
+ matchedRoles: allowedRules.map((rule) => rule.role)
+ };
+}
+
+export function inviteCollaborator({ actorId, projectId, principalId, role, scope = "project", expiresAt = null }, workspace = demoWorkspace) {
+ const access = evaluateAccess({ userId: actorId, projectId, action: "manage_access" }, workspace);
+ if (!access.granted) throw new Error("Actor cannot manage project access");
+ const project = getProject(projectId, workspace);
+ const updatedProject = {
+ ...project,
+ collaborators: [...new Set([...project.collaborators, principalId])],
+ accessRules: [...project.accessRules, { principalId, role, scope, expiresAt }],
+ auditLog: [
+ ...project.auditLog,
+ { actorId, action: "access.invited", target: principalId, createdAt: "2026-05-09T00:00:00.000Z" }
+ ]
+ };
+ return replaceProject(workspace, updatedProject);
+}
+
+export function buildProjectDashboard(workspace = demoWorkspace) {
+ const project = workspace.projects[0];
+ return {
+ auth: authenticateIdentity({ provider: "ORCID", identifier: workspace.users[0].orcid, mfaCode: "123456" }, workspace),
+ profiles: workspace.users.map((user) => ({
+ ...user,
+ orcidSync: syncOrcidProfile(user.id, workspace),
+ reputation: computeReputation(user.id, workspace)
+ })),
+ project,
+ accessMatrix: project.accessRules.map((rule) => ({
+ ...rule,
+ permissions: rolePermissions[rule.role] || []
+ })),
+ policyChecks: [
+ evaluateAccess({ userId: "user-alice", projectId: project.id, action: "manage_access" }, workspace),
+ evaluateAccess({ userId: "user-mateo", projectId: project.id, action: "download_data", objectType: "datasets" }, workspace),
+ evaluateAccess({ userId: "external-reviewer-1", projectId: project.id, action: "review", objectType: "documents" }, workspace)
+ ]
+ };
+}
+
+function getUser(userId, workspace) {
+ const user = workspace.users.find((item) => item.id === userId);
+ if (!user) throw new Error(`Unknown user: ${userId}`);
+ return user;
+}
+
+function getProject(projectId, workspace) {
+ const project = workspace.projects.find((item) => item.id === projectId);
+ if (!project) throw new Error(`Unknown project: ${projectId}`);
+ return project;
+}
+
+function replaceProject(workspace, project) {
+ return { ...workspace, projects: workspace.projects.map((item) => (item.id === project.id ? project : item)) };
+}
+
+function isExpired(expiresAt) {
+ return Boolean(expiresAt && new Date(expiresAt).getTime() < new Date("2026-05-09T00:00:00.000Z").getTime());
+}
+
+function slug(value) {
+ return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "");
+}
diff --git a/user-project-management/src/server.js b/user-project-management/src/server.js
new file mode 100644
index 0000000..e7b367e
--- /dev/null
+++ b/user-project-management/src/server.js
@@ -0,0 +1,63 @@
+import http from "node:http";
+import { readFile } from "node:fs/promises";
+import { extname, join, resolve, sep } from "node:path";
+import { fileURLToPath } from "node:url";
+import {
+ authenticateIdentity,
+ buildProjectDashboard,
+ computeReputation,
+ createProjectSpace,
+ demoWorkspace,
+ evaluateAccess,
+ syncOrcidProfile
+} from "./management-core.js";
+
+const root = join(fileURLToPath(new URL("..", import.meta.url)), "public");
+const port = Number(process.env.PORT || 4130);
+const contentTypes = { ".html": "text/html; charset=utf-8", ".css": "text/css; charset=utf-8", ".js": "text/javascript; charset=utf-8" };
+
+const server = http.createServer(async (request, response) => {
+ try {
+ const url = new URL(request.url, `http://${request.headers.host}`);
+ if (url.pathname === "/api/dashboard") return json(response, buildProjectDashboard());
+ if (url.pathname === "/api/auth/check") return json(response, authenticateIdentity({ provider: "ORCID", identifier: demoWorkspace.users[0].orcid, mfaCode: "123456" }));
+ if (url.pathname.startsWith("/api/users/") && url.pathname.endsWith("/orcid")) return json(response, syncOrcidProfile(url.pathname.split("/")[3]));
+ if (url.pathname.startsWith("/api/users/") && url.pathname.endsWith("/reputation")) return json(response, computeReputation(url.pathname.split("/")[3]));
+ if (url.pathname === "/api/projects/create") return json(response, createProjectSpace({ title: "Prospective validation cohort", ownerId: "user-alice", tags: ["validation"] }).project);
+ if (url.pathname === "/api/access/check") return json(response, evaluateAccess({ userId: "user-mateo", projectId: "project-organoid-response", action: "edit_code", objectType: "code" }));
+ return await serveStatic(url.pathname === "/" ? "/index.html" : url.pathname, response);
+ } catch (error) {
+ response.writeHead(500, { "content-type": "application/json; charset=utf-8" });
+ response.end(JSON.stringify({ error: error.message }));
+ }
+});
+
+server.listen(port, () => {
+ console.log(`User project management demo running at http://localhost:${port}`);
+});
+
+function json(response, body) {
+ response.writeHead(200, { "content-type": "application/json; charset=utf-8" });
+ response.end(JSON.stringify(body, null, 2));
+}
+
+async function serveStatic(pathname, response) {
+ const filePath = resolve(root, pathname.replace(/^\/+/, ""));
+ if (!filePath.startsWith(`${root}${sep}`)) {
+ response.writeHead(403, { "content-type": "text/plain; charset=utf-8" });
+ response.end("Forbidden");
+ return;
+ }
+ try {
+ const body = await readFile(filePath);
+ response.writeHead(200, { "content-type": contentTypes[extname(filePath)] || "application/octet-stream" });
+ response.end(body);
+ } catch (error) {
+ if (error.code === "ENOENT") {
+ response.writeHead(404, { "content-type": "text/plain; charset=utf-8" });
+ response.end("Not found");
+ return;
+ }
+ throw error;
+ }
+}
diff --git a/user-project-management/test/management-core.test.js b/user-project-management/test/management-core.test.js
new file mode 100644
index 0000000..06005da
--- /dev/null
+++ b/user-project-management/test/management-core.test.js
@@ -0,0 +1,57 @@
+import test from "node:test";
+import assert from "node:assert/strict";
+import {
+ authenticateIdentity,
+ buildProjectDashboard,
+ computeReputation,
+ createProjectSpace,
+ demoWorkspace,
+ evaluateAccess,
+ inviteCollaborator,
+ syncOrcidProfile
+} from "../src/management-core.js";
+
+test("authenticates linked identities with MFA", () => {
+ const ok = authenticateIdentity({ provider: "ORCID", identifier: demoWorkspace.users[0].orcid, mfaCode: "123456" });
+ const missingMfa = authenticateIdentity({ provider: "ORCID", identifier: demoWorkspace.users[0].orcid });
+
+ assert.equal(ok.authenticated, true);
+ assert.equal(missingMfa.authenticated, false);
+ assert.equal(missingMfa.reason, "mfa_required");
+});
+
+test("syncs ORCID profile data and computes reputation", () => {
+ const sync = syncOrcidProfile("user-alice");
+ const reputation = computeReputation("user-alice");
+
+ assert.equal(sync.orcid, demoWorkspace.users[0].orcid);
+ assert.ok(sync.importedPublications.length >= 2);
+ assert.ok(reputation.score > 100);
+});
+
+test("creates project spaces and evaluates object-level permissions", () => {
+ const { workspace, project } = createProjectSpace({ title: "Metabolomics validation", ownerId: "user-alice", tags: ["metabolomics"] });
+ const ownerAccess = evaluateAccess({ userId: "user-alice", projectId: project.id, action: "archive" }, workspace);
+ const contributorAccess = evaluateAccess({ userId: "user-mateo", projectId: "project-organoid-response", action: "edit_code", objectType: "code" });
+
+ assert.equal(project.visibility, "private");
+ assert.equal(ownerAccess.granted, true);
+ assert.equal(contributorAccess.granted, true);
+});
+
+test("invites collaborators with audit logs and builds dashboard payload", () => {
+ const workspace = inviteCollaborator({
+ actorId: "user-alice",
+ projectId: "project-organoid-response",
+ principalId: "external-statistician",
+ role: "Reviewer",
+ scope: "documents"
+ });
+ const project = workspace.projects[0];
+ const dashboard = buildProjectDashboard(workspace);
+
+ assert.ok(project.collaborators.includes("external-statistician"));
+ assert.ok(project.auditLog.some((entry) => entry.action === "access.invited"));
+ assert.equal(dashboard.auth.authenticated, true);
+ assert.ok(dashboard.policyChecks.length >= 3);
+});