Skip to content
Open
6 changes: 5 additions & 1 deletion hub/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ TEST_DATABASE_URL=postgresql://replica:replica@127.0.0.1:5432/postgres
# HUB_GITHUB_AUTH_CLIENT_ID=
# HUB_GITHUB_AUTH_CLIENT_SECRET=

# GitHub proves identity by default. Select legacy-repository only for compatibility.
# GitHub proves identity by default. github-synchronized admits users through explicitly
# linked repository sources. legacy-repository preserves the earlier compatibility behavior.
HUB_ADMISSION_POLICY=hub-managed

# Generate separate host-held values. Bootstrap can enroll the first administrator once.
Expand All @@ -45,3 +46,6 @@ HUB_GITHUB_CREDENTIAL_PROVIDER=database
# GITHUB_CLIENT_ID=
# GITHUB_CLIENT_SECRET=
# GITHUB_PRIVATE_KEY=

# Required only for /api/github/webhooks. Keep it separate from user-auth credentials.
# HUB_GITHUB_WEBHOOK_SECRET=<GitHub App webhook secret>
4 changes: 2 additions & 2 deletions hub/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

Hub is being rebuilt on `build/hub/new` using TypeScript, Express, React, Vite, and Vitest. This branch lets contributors start the new app locally. It is not ready to replace the existing Hub.

The API exposes its health check, GitHub authentication, sessions, and installation membership administration. The frontend provides GitHub sign-in, one-use administrator enrollment and recovery, and a minimal membership administration surface. Existing prototype components, features, and pages remain in Storybook, but they are not connected to the new product APIs.
The API exposes its health check, GitHub authentication, sessions, installation membership administration, prototype authorization, and opt-in GitHub-synchronized admission and prototype baselines. The frontend provides GitHub sign-in, one-use administrator enrollment and recovery, and a minimal membership administration surface. Existing prototype components, features, and pages remain in Storybook, but they are not connected to the new product APIs.

Contributors can use PostgreSQL and Prisma locally or in the devcontainer. The [background-job foundation](docs/background-jobs.md) provides PostgreSQL-backed BullMQ queues and independent on-demand and scheduled workers, without product jobs yet. GitHub sign-in works when its dedicated client credentials are configured. Hub-owned admission, invitations, membership roles and lifecycle, administrator recovery, and session revocation are implemented. Prototype authorization, saved prototype data, live updates, product jobs, and deployment are not implemented.
Contributors can use PostgreSQL and Prisma locally or in the devcontainer. The [background-job foundation](docs/background-jobs.md) provides PostgreSQL-backed BullMQ queues and independent on-demand and scheduled workers. GitHub sign-in works when its dedicated client credentials are configured. Hub-owned admission, optional GitHub-synchronized authorization with Hub overrides, invitations, membership roles and lifecycle, administrator recovery, session revocation, and prototype metadata are implemented. Artifact ingestion, previews, reviews, product jobs, and deployment are not implemented.

Use [Development](docs/development.md) to start the API and frontend. Review [Architecture](docs/architecture.md), [code architecture conventions](docs/backend-architecture.md), and [coding conventions](docs/coding-conventions.md) before adding behavior.

Expand Down
3 changes: 2 additions & 1 deletion hub/docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ Hub is being rebuilt. The [Hub overview](../README.md) describes what the baseli
- [Development](development.md): local API and frontend setup.
- [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.
- [GitHub integration](github-integration.md): linked repository sources, reconciliation, webhooks, staleness, and permission mapping.
- [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.
- [Prototype authorization](prototype-authorization.md): workspaces, prototypes, immutable versions, Hub overrides, synchronized baselines, 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, 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.
The API responds to a health check and provides GitHub authentication, Hub sessions, installation membership administration, Hub-native prototype authorization, and opt-in GitHub-synchronized admission and prototype baselines. 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. 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.
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, synchronized GitHub baselines, and one policy evaluator. The [GitHub integration](github-integration.md) stores stable repository source records and reconciles current permissions with installation credentials. Deployment has not been built. This app does not ingest artifacts, host previews, browse source, 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. 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.
`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; choose Hub-managed or GitHub-synchronized prototype authorization; explain effective access; and administer direct access rules. Administration routes link repository sources, opt them into admission, inspect synchronization state, and trigger reconciliation. A verified GitHub App webhook route marks affected sources dirty or removed. 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, prototype authorization, and GitHub synchronization runtime, starts the API at a local address, and schedules bounded periodic reconciliation.

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
14 changes: 13 additions & 1 deletion hub/docs/database.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,19 @@ 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.
Workspace and prototype access use separate grant tables so each user has at most one manual 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.

GitHub synchronization uses separate records:

- `GitHubRepositorySource` identifies a repository by immutable GitHub ID and the App and installation configuration that confirmed it. Mutable owner, name, default branch, active/removed state, dirty state, failures, synchronization generation, and timestamps do not define identity. The generation is a compare-and-set boundary for rejecting stale in-flight reconciliation results.
- `GitHubAdmissionAttempt` stores a per-repository, per-GitHub-subject generation. Live sign-ins use it to order one user's permission confirmations without invalidating reconciliation or another user's sign-in.
- `GitHubSynchronizedAccess` stores one user's raw permission, mapped prototype role, admission eligibility, source, status, and last confirmation for one repository source.
- `GitHubAuthorizationPolicy` stores the bounded last-known-good duration. The initial policy is one day and may not exceed seven days.
- `GitHubWebhookDelivery` deduplicates verified deliveries by GitHub delivery ID.

`Prototype.authorizationMode` defaults to `hub-managed`. `sourceRepositoryId` is nullable and applies only when the mode is explicitly `github-synchronized`. Synchronized records never share rows with workspace or prototype grants, so reconciliation cannot overwrite manual Hub decisions.

Installation membership `source` distinguishes linked-source synchronization from the `legacy-synchronized` compatibility policy. Any administrator lifecycle change or assignment of Hub administration converts a synchronized membership to `manual`, so reconciliation cannot undo the Hub override or orphan administration.

## Shared client

Expand Down
56 changes: 56 additions & 0 deletions hub/docs/github-integration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# GitHub integration

GitHub-synchronized authorization uses repository permissions as an opt-in baseline below Hub overrides. It is not repository-gated authorization: Hub links the source, chooses where synchronization applies, maps the permission, enforces staleness, and makes the final admission and prototype decisions.

## Source identity and credentials

A linked source stores GitHub's immutable repository ID, the configured App and installation IDs, the current owner, name, default branch, visibility, active, suspended, or removed state, admission switch, synchronization status, and timestamps. Admission resolves current metadata from that immutable ID before checking a user permission, so a renamed repository cannot be confused with a replacement at its old name. Public and internal sources additionally require GitHub to confirm that the user is an actual collaborator before their permission can supply admission or a prototype baseline. Rename changes mutable fields without replacing the source. Prototype source provenance is separate from build, artifact, manifest, and ingestion records.

All permission checks use an installation token created from the configured GitHub source-integration credentials. GitHub user authorization proves identity only. Hub does not retain or reuse the user's transient token for synchronization.

## Opt-in policies

`HUB_ADMISSION_POLICY=hub-managed` is the default. Select `github-synchronized` to permit linked sources to participate, then enable admission on each intended repository through the administration API. No linked or opted-in source means no synchronized admission.

Each prototype separately selects `hub-managed` or `github-synchronized`. The synchronized mode also requires one active repository source. Unlinking a source removes its synchronized baselines and leaves the prototype without a usable baseline until an administrator selects another source or returns it to Hub-managed mode.

`legacy-repository` is a compatibility policy for current Replica behavior. It scans installation repositories during sign-in and is intentionally not the default or the normal synchronized source model.

## Permission mapping

| Raw GitHub permission | Admission | Prototype baseline |
| --- | --- | --- |
| `admin`, `maintain`, `write` | Member | `editor` |
| `triage` | Member | `commenter` |
| `read` | Member | `viewer` |
| `none` or missing | Denied | None |

GitHub does not map to Hub installation administrator or prototype administrator. GitHub's built-in `role_name` takes precedence over its legacy base `permission` field, so `role_name: triage` maps to commenter even when the base permission is `read`; unknown custom roles fall back to the base permission. Explicit Hub deny wins first, followed by explicit prototype and workspace roles, then the synchronized baseline.

## Reconciliation and webhooks

`POST /api/administration/github/repositories/:repositorySourceId/reconcile` confirms repository metadata and every known GitHub identity's current collaborator permission. The API process also reconciles active sources every 15 minutes. Reconciliation repairs missed webhooks and permission changes for collaborators or teams because GitHub does not emit a complete permission-change stream.

Each reconciliation claims a monotonically increasing source generation while holding the repository row lock. Reconciliation and live admission permission requests also share a separate generation per repository and immutable GitHub subject, so whichever request starts later supersedes older permission results for only that user without invalidating the repository scan or another user's sign-in. These per-user claims use the repository row lock without taking the global identity-store lock. Admission claims before resolving mutable repository metadata and returns a retryable conflict when the source generation changed, so a rename or transfer cannot substitute a replacement repository or appear as permission loss.

`POST /api/github/webhooks` accepts only HMAC-SHA256 verified GitHub App payloads. An irrelevant signed event such as GitHub's initial `ping` is ignored before installation-specific validation. Revocation events match the signed installation ID against trusted linked-source records and do not require usable API credentials, so credential loss cannot block deletion or suspension. Configure the GitHub App to send installation, installation-repository, repository, member, membership, organization, team, and team-add events. Permission-only events mark affected sources dirty without invalidating a healthy in-flight scan; repository identity and installation-repository changes retain source-generation invalidation. Dirty events are coalesced into bounded near-term retry batches, and the retry guard remains active until each batch finishes.

## Staleness and outages

A successful confirmation records its own `lastConfirmedAt`, including a confirmed negative permission that invalidates an older positive cache. A cache fallback is revalidated under the callback transaction before it can reactivate membership or create a session. Installation-token 404 removes matching sources rather than using cache. Credential and configuration failures record an explicit failed status without refreshing confirmation. Hub permits a last-known-good baseline for one day, then fails closed. Source credential failures never block an already active manual Hub member or a usable Hub invitation, but synchronized members remain fail-closed.

Scheduled reconciliation has a single-flight guard. A slow periodic run completes before another scheduled run starts; manual reconciliation still uses source generations to supersede older results safely. Manual reconciliation checks fresh verification before the provider scan, then rechecks only the active administrator session when applying confirmed results, so a long scan does not discard valid results solely because the five-minute freshness window elapsed. Scans include only identities with active Hub membership, issue permission requests with bounded concurrency, and apply confirmations in bounded transactions outside the global identity lock. One user's provider failure leaves that user's timestamp unchanged while other confirmed results apply; the source records a bounded partial-failure status and scheduled runs emit one static operational error record with the source ID and bounded reason.

Manual Hub grants, denies, membership assignments, and suspensions are separate rows. Synchronization never deletes or rewrites them. Repository unlink, installation removal, and permission loss affect synchronized rows only.

## Administration API

- `GET /api/administration/github/repositories`
- `POST /api/administration/github/repositories`
- `PATCH /api/administration/github/repositories/:repositorySourceId`
- `DELETE /api/administration/github/repositories/:repositorySourceId`
- `POST /api/administration/github/repositories/:repositorySourceId/reconcile`
- `PUT /api/prototypes/:prototypeId/authorization`
- `GET /api/prototypes/:prototypeId/access/explanation`

Administration mutations require an active installation administrator, fresh verification, the configured origin, and matching CSRF cookie and header. Repository listing requires the active administrator role but not a fresh verification because it is read-only. Product policy changes require a fresh effective prototype administrator. Request bodies are omitted from logs, responses are private and non-cacheable, and mutations write structured audits in the same transaction.
Loading
Loading