Skip to content

feat(server): organizations tenant layer — migration + session (PR1/4)#69

Merged
Isonimus merged 1 commit into
mainfrom
feat/organizations-layer-pr1
Jun 20, 2026
Merged

feat(server): organizations tenant layer — migration + session (PR1/4)#69
Isonimus merged 1 commit into
mainfrom
feat/organizations-layer-pr1

Conversation

@Isonimus

Copy link
Copy Markdown
Contributor

Why

The hosted box is effectively single-tenant today: data is scoped by project_id, but there is no company entity above projects and the admin owner role is a global superuser. Safe for self-host, unsafe for a multi-customer box (Company A's owner could read Company B's data; no billing root). This is PR1 of 4 introducing an organizations tenant layer. See [ADR-0005 / ROADMAP] (landing in PR4).

What (inert foundation — no behaviour change)

  • Migration 013: organizations table (with per-org require_2fa); nullable organization_id FKs on admin_users, projects, admin_invites.
  • Backfill: existing installs fold into one 'Default' org (seeding require_2fa from the install-wide setting), so self-host behaves exactly as before. A fresh empty DB gets no org here — the bootstrap script (PR3) creates the first org + owner together.
  • organization_id stays nullable; the NOT NULL backstop lands in PR2 once every writer is org-aware.
  • Session plumbing: AdminUser / validateSession now carry organizationId for the authz re-scoping in PR2.

organization_id is off the /v1 data hot path — a project API key already resolves to a project_id. Orgs are an admin/billing concern only.

Verification

  • Migration applied to dev DB: 1 Default org, all admins + projects assigned.
  • Full server suite green: 128 passing. type-check + lint clean.

Follow-ups

PR2 authz re-scoping (owner → org-scoped) + per-org 2FA + NOT NULL backstop + isolation tests · PR3 org-aware bootstrap · PR4 docs.

🤖 Generated with Claude Code

Introduce an organizations table as the tenant layer above projects, the
unit of isolation (and future anchor for metering/billing). Adds nullable
organization_id FKs to admin_users, projects, and admin_invites, plus a
per-org require_2fa policy column.

The migration backfills a single 'Default' org and assigns every existing
admin and project to it (seeding require_2fa from the install-wide setting),
so a single-org self-host install behaves exactly as before. Columns are
left nullable here; the NOT NULL backstop lands in a later migration once
every writer is org-aware.

AdminUser/validateSession now carry organizationId so downstream authz can
scope owners to their own org. No behaviour change yet.
@Isonimus Isonimus merged commit 82fbae6 into main Jun 20, 2026
4 checks passed
@Isonimus Isonimus deleted the feat/organizations-layer-pr1 branch June 20, 2026 08:15
Isonimus added a commit that referenced this pull request Jun 21, 2026
- ADR-0005: organizations are the tenant boundary; owner re-scoped from
  global to org-scoped; 404-not-403 on cross-org; per-org 2FA; the
  nullable->backfill->NOT NULL rollout; deferred public signup. Indexed in
  the ADR README.
- ROADMAP: flip the multi-tenancy section from planned to SHIPPED (migrations
  013-014, PRs #69-#72), keeping public signup + metering/billing + org-mgmt
  UI as explicit follow-ups.
- OpenAPI: note that all /admin responses are org-scoped (cross-org -> 404).
  Lints clean.
- CHANGELOG: Internal entry for the organizations layer.
Isonimus added a commit that referenced this pull request Jun 21, 2026
- ADR-0005: organizations are the tenant boundary; owner re-scoped from
  global to org-scoped; 404-not-403 on cross-org; per-org 2FA; the
  nullable->backfill->NOT NULL rollout; deferred public signup. Indexed in
  the ADR README.
- ROADMAP: flip the multi-tenancy section from planned to SHIPPED (migrations
  013-014, PRs #69-#72), keeping public signup + metering/billing + org-mgmt
  UI as explicit follow-ups.
- OpenAPI: note that all /admin responses are org-scoped (cross-org -> 404).
  Lints clean.
- CHANGELOG: Internal entry for the organizations layer.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant