Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions workspace-access-ledger/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# Workspace Access Ledger

Self-contained user and project management milestone for [SCIBASE.AI issue #11](https://github.com/SCIBASE-AI/SCIBASE.AI/issues/11).

The issue asks for identity, researcher profiles, scientific workspaces, permissions, invitations, and audit history. This module provides a deterministic governance slice that reviewers can run locally without external auth providers.

## What It Adds

- Unified identity summaries for email, ORCID, GitHub/OAuth, SAML, MFA, and anonymous mode.
- Identity security review that flags privileged roles without MFA and anonymous users with write-capable access.
- Researcher profiles with institution, field, keywords, ORCID sync, activity, citation-style metrics, and reputation score.
- Project-space access evaluation with visibility, RBAC, and object-level rules.
- Project lifecycle report with workspace components, citation/funding/institution metadata, archive approval, retention dates, and invitation expiry review.
- External collaborator invitations with role, read-only mode, expiry, and invitation hash.
- Collaborator onboarding plan with required identity providers, MFA gates, invite-acceptance route contracts, blocker reasons, and audit events.
- Hashed audit events for project access history.
- Workspace dashboard with identity coverage, profile metrics, project summary, lifecycle status, pending invitations, and access decision counts.
- Sample workspace fixture, tests, requirement map, CLI demo, and short demo GIF.

## Run

```bash
cd workspace-access-ledger
npm run check
npm test
npm run demo
```

Expected demo shape:

```json
{
"workspace": "Microbiome Atlas Lab",
"identitySummary": {
"users": 3,
"orcidLinked": 2,
"samlLinked": 1
},
"identitySecurity": {
"status": "ready"
},
"lifecycle": {
"activeProjects": 1,
"archivedProjects": 1,
"incompleteProjects": 0
},
"onboarding": {
"readyCount": 0,
"blockedCount": 2
},
"allowedCount": 2,
"deniedCount": 1
}
```

## Demo Artifact

See [docs/demo.gif](docs/demo.gif) for a short visual walkthrough. The SVG source is included at [docs/demo.svg](docs/demo.svg).

## Files

- `src/access-ledger.js` - identity, profiles, access policy, invitations, collaborator onboarding, project lifecycle, audit, dashboard.
- `data/sample-workspace.json` - reviewable workspace/project fixture.
- `test/access-ledger.test.js` - dependency-free Node tests.
- `scripts/demo.js` - CLI demo.
- `docs/issue-11-requirement-map.md` - maps implementation to bounty requirements.

## AI-Assisted Disclosure

This contribution was produced with AI assistance and manually verified with the local commands above.
154 changes: 154 additions & 0 deletions workspace-access-ledger/data/sample-workspace.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
{
"workspace": {
"id": "workspace-atlas",
"name": "Microbiome Atlas Lab",
"asOf": "2026-05-14T00:00:00Z",
"users": [
{
"id": "u-owner",
"email": "owner@example.edu",
"name": "Principal Investigator",
"institution": "Northstar University",
"institutionId": "northstar",
"field": "environmental health",
"keywords": ["microbiome", "public health"],
"profileVisibility": "public",
"mfaEnabled": true,
"identities": [
{ "provider": "email", "subject": "owner@example.edu" },
{ "provider": "orcid", "subject": "0000-0002-1111-2222" },
{ "provider": "saml", "subject": "northstar:owner" }
]
},
{
"id": "u-reviewer",
"email": "reviewer@example.edu",
"name": "External Reviewer",
"institution": "Open Review Lab",
"institutionId": "open-review",
"field": "statistics",
"keywords": ["reproducibility", "methods"],
"profileVisibility": "public",
"mfaEnabled": true,
"identities": [
{ "provider": "email", "subject": "reviewer@example.edu" },
{ "provider": "github", "subject": "reviewer-gh" },
{ "provider": "orcid", "subject": "0000-0003-3333-4444" }
]
},
{
"id": "u-anon",
"email": "anon@example.org",
"name": "Anonymous Commenter",
"profileVisibility": "private",
"anonymousMode": true,
"mfaEnabled": false,
"identities": [{ "provider": "email", "subject": "anon@example.org" }]
}
],
"projects": [
{
"id": "project-private",
"title": "Coastal flooding microbiome study",
"visibility": "private",
"institutionId": "northstar",
"requireOrcid": true,
"requireMfa": true,
"members": [
{ "userId": "u-owner", "role": "owner" },
{ "userId": "u-reviewer", "role": "reviewer" }
],
"policy": {
"manuscript": { "read": "viewer", "write": "contributor" },
"code": { "read": "reviewer", "write": "contributor" },
"dataset": { "read": "admin", "download": "admin" }
},
"documents": ["manuscript.md", "lab-notes.md"],
"code": ["analysis/normalize.py"],
"datasets": ["data/samples.csv"],
"discussionThreads": ["thread-methods", "thread-batch-effects"],
"citations": ["doi:10.5555/flood.microbiome"],
"fundingSources": ["NSF-OPEN-2026"],
"institutions": ["northstar"],
"objectRules": [
{
"objectType": "dataset",
"action": "read",
"effect": "allow",
"userIds": ["u-reviewer"]
}
]
},
{
"id": "project-public",
"title": "Open protocol notes",
"visibility": "public",
"members": [{ "userId": "u-owner", "role": "owner" }],
"policy": {
"manuscript": { "read": "viewer", "write": "contributor" }
},
"documents": ["protocol.md"],
"code": [],
"datasets": [],
"discussionThreads": [],
"citations": ["doi:10.5555/open.protocol"],
"fundingSources": [],
"institutions": ["northstar"],
"archiveRequestedAt": "2026-05-01T00:00:00Z",
"archivedAt": "2026-05-08T00:00:00Z",
"retentionDays": 365,
"objectRules": []
}
],
"invitations": [
{
"id": "invite-1",
"email": "collaborator@example.edu",
"projectId": "project-private",
"role": "contributor",
"status": "pending",
"readOnly": false,
"expiresAt": "2026-05-21T00:00:00Z"
},
{
"id": "invite-2",
"email": "expired@example.edu",
"projectId": "project-private",
"role": "viewer",
"status": "expired",
"readOnly": true,
"expiresAt": "2026-05-01T00:00:00Z"
}
],
"auditLog": [
{ "id": "audit-1", "actorId": "u-owner", "projectId": "project-private", "action": "project.created" },
{ "id": "audit-2", "actorId": "u-owner", "projectId": "project-private", "action": "reviewer.invited" },
{ "id": "audit-3", "actorId": "u-owner", "projectId": "project-public", "action": "project.archive.approved" }
]
},
"activityByUser": {
"u-owner": {
"publications": ["paper-1", "paper-2"],
"reviews": ["review-1"],
"projects": ["project-private", "project-public"],
"downloads": 240,
"forks": 8,
"endorsements": 12,
"reproducibilityScore": 0.91
},
"u-reviewer": {
"publications": ["paper-3"],
"reviews": ["review-2", "review-3", "review-4"],
"projects": ["project-private"],
"downloads": 90,
"forks": 3,
"endorsements": 7,
"reproducibilityScore": 0.87
}
},
"accessRequests": [
{ "userId": "u-reviewer", "projectId": "project-private", "objectType": "dataset", "action": "read" },
{ "userId": "u-reviewer", "projectId": "project-private", "objectType": "dataset", "action": "download" },
{ "userId": "u-anon", "projectId": "project-public", "objectType": "manuscript", "action": "read" }
]
}
Binary file added workspace-access-ledger/docs/demo.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added workspace-access-ledger/docs/demo.mp4
Binary file not shown.
34 changes: 34 additions & 0 deletions workspace-access-ledger/docs/demo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
29 changes: 29 additions & 0 deletions workspace-access-ledger/docs/issue-11-requirement-map.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Issue #11 Requirement Map

This module is a deterministic milestone for SCIBASE issue #11, User & Project Management. It focuses on identity, researcher profiles, project-space access policies, invitations, and audit governance.

| Issue requirement | Implementation |
| --- | --- |
| Email/OAuth/ORCID/SAML identity | `buildUnifiedIdentity()` models linked provider identities, ORCID links, SAML links, MFA, and anonymous mode. |
| 2FA and anonymous-mode safeguards | `buildIdentitySecurityReview()` flags privileged project roles without MFA and anonymous users with write-capable membership. |
| Researcher profiles | `buildResearcherProfile()` summarizes institution, field, keywords, ORCID sync, activity, citations-style metrics, and reputation. |
| Project spaces | Sample projects include manuscripts, code, datasets, discussion threads, citations, funding sources, institutions, visibility, members, and object rules. |
| Create, manage, and archive projects | `buildProjectLifecycleReport()` reports project component completeness, lifecycle state, archive approval, archived date, retention date, and lifecycle hash. |
| Visibility settings | `evaluateAccess()` supports public, private, institutional-only, and role/object-rule paths. |
| Role-based access | Role ranks cover Owner, Admin, Contributor, Reviewer, and Viewer. |
| Object-level control | Project policies and `objectRules` can allow or deny actions such as dataset read/download independently of role defaults. |
| External collaborator invitations | `createInvitation()` creates time-limited invitation records with role and read-only state, while `buildProjectLifecycleReport()` reviews active/expired invitation status. |
| Invitation acceptance and onboarding | `buildCollaboratorOnboardingPlan()` reports required identity providers, MFA gates, invite-acceptance route contracts, blocker reasons, and audit events before a collaborator can join a project. |
| Audit log | `appendAuditEvent()` appends hashed audit events. |
| Reputation metrics | Dashboard profiles include downloads, forks, endorsements, peer reviews, collaborations, and reproducibility score. |
| Reviewer demo | `npm run demo` prints identity summary, access decisions, and dashboard hash for `data/sample-workspace.json`. |

## Verification

```bash
npm run check
npm test
npm run demo
```

The module is dependency-free and isolated under `workspace-access-ledger/`.
12 changes: 12 additions & 0 deletions workspace-access-ledger/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"name": "scibase-workspace-access-ledger",
"version": "0.1.0",
"private": true,
"description": "Identity and project-space access governance module for SCIBASE issue #11.",
"type": "commonjs",
"scripts": {
"check": "node --check src/access-ledger.js && node --check scripts/demo.js && node --check test/access-ledger.test.js",
"demo": "node scripts/demo.js",
"test": "node test/access-ledger.test.js"
}
}
48 changes: 48 additions & 0 deletions workspace-access-ledger/scripts/demo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
"use strict";

const sample = require("../data/sample-workspace.json");
const { buildWorkspaceAccessPacket } = require("../src/access-ledger");

const packet = buildWorkspaceAccessPacket(
sample.workspace,
sample.activityByUser,
sample.accessRequests,
);

console.log(
JSON.stringify(
{
workspace: packet.dashboard.workspace.name,
identitySummary: packet.dashboard.identitySummary,
identitySecurity: packet.dashboard.identitySecurity,
lifecycle: {
activeProjects: packet.dashboard.lifecycle.activeProjects,
archivedProjects: packet.dashboard.lifecycle.archivedProjects,
incompleteProjects: packet.dashboard.lifecycle.incompleteProjects,
invitationStatuses: packet.dashboard.lifecycle.invitationReview.map((invitation) => ({
invitationId: invitation.invitationId,
status: invitation.status,
risk: invitation.risk,
})),
},
onboarding: {
readyCount: packet.dashboard.onboarding.readyCount,
blockedCount: packet.dashboard.onboarding.blockedCount,
plans: packet.dashboard.onboarding.plans.map((plan) => ({
invitationId: plan.invitationId,
status: plan.status,
requiredProviders: plan.requiredProviders,
mfaRequired: plan.mfaRequired,
blockers: plan.blockers,
acceptanceRoute: plan.acceptanceRoute,
})),
},
allowedCount: packet.allowedCount,
deniedCount: packet.deniedCount,
decisions: packet.decisions,
dashboardHash: packet.dashboard.dashboardHash,
},
null,
2,
),
);
Loading