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
1 change: 1 addition & 0 deletions hub/docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Hub is being rebuilt. The [Hub overview](../README.md) describes what the baseli
- [Database](database.md): Prisma schema conventions, migrations, and the shared client.
- [Identity](identity.md): GitHub user authentication, provider-neutral sessions, admission policy, and source-integration credentials.
- [Membership and administration](membership-and-administration.md): Hub admission, invitations, roles, lifecycle, bootstrap, and recovery.
- [Prototype authorization](prototype-authorization.md): workspaces, prototypes, immutable versions, direct roles, explicit deny, and policy precedence.
- [Logging](logging.md): application events, automatic request records, and payload safety.
- [Background jobs](background-jobs.md): PostgreSQL queues, workers, schedules, setup, and test isolation.
- [Testing and operations](testing-and-operations.md): baseline checks and their limits.
Expand Down
6 changes: 3 additions & 3 deletions hub/docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@ Hub contributors are rebuilding the app on `build/hub/new` before merging it int

The team has chosen TypeScript, Express for the API, React and Vite for the frontend, and Vitest for tests. The old backend and the frontend code that connected to it have been removed rather than rewritten.

The API responds to a health check and provides GitHub authentication, Hub sessions, and installation membership administration. The frontend displays sign-in, administrator enrollment and recovery, and a minimal membership administration page with light and dark themes. Storybook keeps the existing components and pages available for reuse.
The API responds to a health check and provides GitHub authentication, Hub sessions, installation membership administration, and Hub-native prototype authorization. The frontend displays sign-in, administrator enrollment and recovery, and a minimal membership administration page with light and dark themes. Storybook keeps the existing components and pages available for reuse.

Contributors can use PostgreSQL and Prisma locally or in the devcontainer. The [identity system](identity.md) separates Hub users, external identities, installation membership, authentication-provider configuration, sessions, authorization attempts, security records, and GitHub source-integration credentials. GitHub user authorization, Hub-owned admission, provider-neutral sessions, invitations, membership lifecycle, and administrator recovery work when user-auth credentials are configured. The [background-job foundation](background-jobs.md) provides PostgreSQL-backed queues and workers without product jobs. Deployment and prototype APIs have not been built. This app cannot yet evaluate prototype access, find repositories, import Builds, host previews, save product data, or support reviews.
Contributors can use PostgreSQL and Prisma locally or in the devcontainer. The [identity system](identity.md) separates Hub users, external identities, installation membership, authentication-provider configuration, sessions, authorization attempts, security records, and GitHub source-integration credentials. GitHub user authorization, Hub-owned admission, provider-neutral sessions, invitations, membership lifecycle, and administrator recovery work when user-auth credentials are configured. The [background-job foundation](background-jobs.md) provides PostgreSQL-backed queues and workers without product jobs. The [prototype authorization system](prototype-authorization.md) adds workspaces, prototypes, immutable version records, direct roles, explicit deny, and one policy evaluator. Deployment has not been built. This app cannot yet find repositories, ingest artifacts, host previews, or support reviews.

## Backend

API code lives under `server/`. As features are added, route handlers will read requests, call domains, and return responses. The [code conventions](backend-architecture.md) explain repositories, integrations, utilities, domains, and which layers may import others. Put these directories under `server/` when they are needed; do not add empty implementations.

`GET /api/health` returns `{ "status": "ok" }`. Authentication routes start and complete GitHub authorization, restore or revoke Hub sessions, and perform fresh verification. Administration routes list memberships and invitations, create or revoke invitations, and change membership roles or lifecycle. Unknown paths return a JSON 404 response; invalid JSON is rejected before routing. `createApp()` builds the Express app without starting a server. `server/index.ts` composes the authentication and administration runtime and starts the API at a local address.
`GET /api/health` returns `{ "status": "ok" }`. Authentication routes start and complete GitHub authorization, restore or revoke Hub sessions, and perform fresh verification. Administration routes list memberships and invitations, create or revoke invitations, and change membership roles or lifecycle. Product routes create and read workspaces, prototypes, and immutable versions; archive prototypes; explain effective access; and administer direct access rules. Unknown paths return a JSON 404 response; invalid JSON is rejected before routing. `createApp()` builds the Express app without starting a server. `server/index.ts` composes the authentication, administration, and prototype authorization runtime and starts the API at a local address.

The [database guide](database.md) defines the multi-file Prisma schema and shared client in integrations. The health route remains a process check, not a database readiness check.

Expand Down
4 changes: 3 additions & 1 deletion hub/docs/database.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Hub database

Hub uses PostgreSQL and Prisma. The initial migration stores the provider-neutral [identity foundation](identity.md); product models have not been added.
Hub uses PostgreSQL and Prisma. The schema stores the provider-neutral [identity foundation](identity.md) and the [prototype authorization](prototype-authorization.md) product core.

[Background jobs](background-jobs.md) use the same database with BullMQ-owned tables and functions in the `bullmq` schema. Prisma does not own those objects. `npm run setup` applies Prisma migrations before BullMQ setup and schedule registration.

Expand Down Expand Up @@ -48,6 +48,8 @@ model Folder {

`uuid()` generates UUIDs through Prisma. `@updatedAt` updates timestamps on Prisma writes, not through a database trigger. Raw SQL writers must supply UUIDs and maintain `updated_at` themselves. Use timezone-aware timestamps for stored instants.

Workspace and prototype access use separate grant tables so each user has at most one rule per scope. The database requires an allowed rule to have a role and an explicit deny to have no role. Prototype versions have a unique sequence within their owning prototype and use restrictive deletion so history cannot be detached or silently removed.

## Shared client

Import `prisma` from `server/integrations/prisma/index.ts` rather than constructing clients in each caller. The integration uses Prisma's [documented global cache pattern](https://www.prisma.io/docs/orm/v7/more/troubleshooting/nextjs#avoiding-multiple-prisma-client-instances) to reuse one client across development module reloads. Production uses the module instance without setting the global variable.
Expand Down
4 changes: 2 additions & 2 deletions hub/docs/identity.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ Sign-out revokes the current database session. Every restored session requires a
- `AuthenticationProvider` and its versioned revisions store provider-neutral authentication configuration. An OIDC-only configuration does not require GitHub source-integration credentials.
- `Session` records the Hub user, external identity, and authentication-provider revision that established it. GitHub source-integration credential rotation does not change session identity context.
- `AuthorizationAttempt` stores digested state and browser bindings, a PKCE verifier, an optional OIDC nonce, provider revision, purpose, optional originating session, and expiry. Consumption deletes the record in the same transaction so concurrent callbacks cannot reuse it.
- `SecurityAudit` stores the event and minimal optional identity context.
- `SecurityAudit` stores the event and minimal optional identity and product context. Audit records describe changes but do not grant access.

Queries are available without a lock. Mutations run through one database transaction that locks a fixed store-state row. Related membership, invitation, session, and audit changes therefore commit or roll back together. Overlapping final-administrator changes and invitation activations observe committed state.

Expand Down Expand Up @@ -54,6 +54,6 @@ Database credentials use AES-256-GCM with contextual additional authenticated da

## Current scope

This layer adds GitHub sign-in, installation admission, invitations, member and administrator lifecycle, one-use bootstrap and recovery, session restoration, sign-out, fresh verification, global and per-member session revocation, structured security records, and the minimal frontend needed to use them. It does not add prototype, repository, workspace, or artifact roles; GitHub membership synchronization; artifact ingestion; production OIDC; account linking; SCIM; or prototype APIs.
This layer adds GitHub sign-in, installation admission, invitations, member and administrator lifecycle, one-use bootstrap and recovery, session restoration, sign-out, fresh verification, global and per-member session revocation, structured security records, and the minimal frontend needed to use them. The separate [prototype authorization](prototype-authorization.md) boundary uses these sessions without treating membership or repository access as product authority. Identity does not add GitHub membership synchronization, artifact ingestion, production OIDC, account linking, or SCIM.

Apply migrations with `npm run db:deploy`. See [Database](database.md) for setup and schema rules.
6 changes: 4 additions & 2 deletions hub/docs/membership-and-administration.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@ Each Hub user has at most one installation membership.
| State | Result |
| --- | --- |
| Active member | May create and restore a Hub session. Product access remains a separate future decision. |
| Active administrator | Has member access and may use installation administration after fresh identity verification. |
| Active administrator | Has member access and may use installation administration after fresh identity verification. Existing product access remains separate. |
| Suspended | Cannot create or use a Hub session. An administrator may restore the membership. |
| Removed | Cannot create or use a Hub session. The retained record preserves audit linkage, and return requires a new invitation. |

Changing a membership to suspended or removed deletes that user's sessions in the same transaction. Every protected request also reloads membership, so a stale cookie cannot preserve access. Hub prevents overlapping administrator changes from removing the final active administrator.

Suspension and removal also check Hub-managed product administration in the same transaction. The change fails with `product_administration_orphaned` until another active user can administer every affected workspace and prototype. A role change from installation administrator to member does not remove explicit workspace or prototype roles.

## Invitations

An invitation targets an external identity assignment:
Expand Down Expand Up @@ -59,4 +61,4 @@ Membership operations write a structured security record with a bounded action,

## Non-goals

This boundary does not define prototype, repository, workspace, or artifact roles. It does not synchronize GitHub organization or repository permissions, implement production OIDC, link accounts across providers, provision users through SCIM, or ingest artifacts.
This boundary does not grant prototype, repository, workspace, or artifact roles. The separate [prototype authorization](prototype-authorization.md) boundary defines Hub-managed product access. Membership does not synchronize GitHub organization or repository permissions, implement production OIDC, link accounts across providers, provision users through SCIM, or ingest artifacts.
75 changes: 75 additions & 0 deletions hub/docs/prototype-authorization.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# Prototype authorization

Hub owns prototype access after authentication and installation admission. A user does not need repository access to use a Hub-managed prototype, and GitHub repository permissions do not grant Hub access.

## Product records

A workspace owns prototypes. A prototype owns an ordered history of immutable versions.

- An active installation administrator may create a workspace. Creation also writes an explicit workspace `administrator` grant for that user in the same transaction.
- A workspace `editor` or `administrator` may create a prototype.
- A prototype `editor` or `administrator` may create its next version.
- A version cannot move to another prototype or be changed after creation.
- Archiving a prototype keeps its versions readable but prevents new versions. Archive and version creation lock and re-read the prototype so overlapping requests cannot add a version after archival.

Artifact payloads are not part of this layer. A version identifies immutable prototype history without storing a build, manifest, or upload.

## Roles

| Role | Product authority |
| --- | --- |
| `viewer` | Read the authorized workspace, prototype, and versions. |
| `commenter` | Includes viewer authority and reserves the role for future review comments. |
| `editor` | Includes commenter authority, creates prototypes within an authorized workspace, creates versions, and archives prototypes. |
| `administrator` | Includes editor authority and changes direct access rules. |

Installation membership and installation administrator status do not grant access to existing workspaces or prototypes. The workspace-creation rule is the only installation-administrator product rule in this layer. After creation, the user's explicit workspace grant supplies authority.

## Direct access and precedence

Hub stores one direct workspace rule and one direct prototype rule per user and scope. A rule is either an explicit deny or an allowed role. Removing a rule restores evaluation from the remaining sources.

The evaluator applies this order:

1. An explicit Hub deny at either the workspace or prototype scope denies access.
2. An explicit prototype role applies, including a lower role that intentionally restricts a stronger workspace role.
3. An explicit workspace role applies.
4. A future optional synchronized baseline may apply.
5. Otherwise Hub denies access.

Every decision reports whether access is allowed, the effective role, the source, and a stable reason. Routes use this evaluator instead of reimplementing precedence. Hub consults an optional baseline only when explicit Hub rules produce no decision, so an unavailable lower-precedence source cannot block an explicit deny or role. Prototype listing accepts that baseline policy source and checks workspace access independently of the current page, so an authorized user receives an empty terminal page instead of an authorization error. This release does not supply a synchronized baseline. Future GitHub synchronization must remain below explicit Hub deny and explicit Hub role rules.

Workspace administrators cannot remove or replace the final active workspace administrator. A prototype access change must also leave at least one active effective prototype administrator. Hub locks the owning records before checking these invariants, so overlapping changes cannot each remove a different final administrator. Suspended and removed users do not satisfy either invariant.

Suspending or removing a membership fails with `product_administration_orphaned` when that user is the final active administrator for a workspace or prototype. An administrator must transfer product administration first. Changing an active installation administrator to a member does not remove an explicit product role, because installation and product authority remain separate.

A workspace administrator can recover prototype access even when a prototype-specific administrator rule does not exist. Explicit prototype deny still blocks that administrator from the denied prototype until another workspace administrator changes the rule. Prototype lists omit prototypes that explicitly deny the requesting user. Workspace list responses contain only workspace fields and never embed prototype or access-rule metadata.

## API and request boundaries

The product API uses:

- `GET` and `POST /api/workspaces`
- `GET /api/workspaces/:workspaceId`
- `PUT` and `DELETE /api/workspaces/:workspaceId/access/:userId`
- `GET` and `POST /api/workspaces/:workspaceId/prototypes`
- `GET /api/prototypes/:prototypeId`
- `POST /api/prototypes/:prototypeId/archive`
- `GET /api/prototypes/:prototypeId/access/explanation`
- `PUT` and `DELETE /api/prototypes/:prototypeId/access/:userId`
- `GET` and `POST /api/prototypes/:prototypeId/versions`
- `GET /api/prototypes/:prototypeId/versions/:versionId`

List routes use an opaque UUID cursor and a limit from 1 through 100. Prototype and version cursors belong to their owning workspace or prototype; a cursor from another scope is invalid. Invalid pagination returns `422 invalid_pagination`, while malformed or missing path resources return their resource-specific `404` error. State-changing requests require the configured origin and matching CSRF cookie and header. Product responses are private and non-cacheable, request payloads are omitted from logs, and mutation routes use a bounded rate limit.

Every product route restores the current session and active installation membership before evaluating product access. Workspace creation and direct access changes also require identity verification from the last five minutes. Mutations repeat the membership check inside their database transaction so a suspended or removed actor cannot complete a stale request.

## Audits

Workspace creation, prototype creation and archival, version creation, role grants, explicit denies, and rule removal write structured security audits in the same transaction as the product change. A failed audit rolls back the product change.

Records identify the actor, session, authentication-provider revision, affected product records, target user when applicable, outcome, and a bounded reason. They do not include cookies, provider tokens, host secrets, request bodies, prototype artifacts, or mutable identity labels.

## Deferred policy sources

GitHub repository synchronization, webhooks, reconciliation, and repository-to-role mapping are not implemented. They may later produce an optional baseline role without changing Hub's direct-rule precedence. Source-provider mutations, public links, artifact ingestion, promotion, retention, and rollback remain separate product boundaries.
2 changes: 2 additions & 0 deletions hub/docs/testing-and-operations.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ The database command uses `--passWithNoTests` so an empty selection exits succes

A dedicated migration case applies the foundation migration, seeds retained lifecycle and security records, then applies the next migration. This catches upgrade behavior that an empty final schema cannot prove, including audit-outcome backfills and lifecycle constraints.

Prototype authorization cases cover direct-rule precedence, explicit deny, no-access defaults, prototype-specific restrictions, cross-prototype isolation, installation membership versus product authority, stale sessions, deterministic overlapping administrator changes, membership lifecycle conflicts, audit rollback, route enforcement, scalar list responses, deny-aware and terminal pagination, version ownership, archive/version races, and explainable decisions. The policy evaluator remains a database-free test target so a future synchronized baseline can be added without changing route-specific rules.

Database-enabled jsdom tests use the browser setup and database hooks together. They can arrange records through server code, but frontend code must not import server modules. The setup does not start an API or connect frontend requests to one.

The database setup follows this lifecycle:
Expand Down
4 changes: 4 additions & 0 deletions hub/schema/access-effect.enum.prisma
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
enum AccessEffect {
allow
deny
}
Loading
Loading