Skip to content

initial data model: org, user, workspace, env, membership, tokens, audit - #2

Open
catalysync wants to merge 42 commits into
feat/v0-infrafrom
feat/initial-data-model
Open

catalysync wants to merge 42 commits into
feat/v0-infrafrom
feat/initial-data-model

Conversation

@catalysync

Copy link
Copy Markdown
Owner

Summary

First vertical slice of the data layer. Stacked on #1 — must merge after that PR (depends on the alembic scaffolding it adds).

Models

  • Org — tenant root with auth_provider (local | zitadel | workos), auth_config jsonb, billing status, soft delete
  • User, Group, GroupMember — humans + groups scoped to one Org, both with external_id columns for SCIM/IdP linkage
  • Workspace — the project (membership boundary, asset boundary)
  • Environment — first-class within a workspace; partial unique index enforces one default env per workspace
  • Membership — polymorphic principal (User XOR Group), 5-role enum (owner/admin/editor/viewer/guest), check constraint enforces the XOR
  • APIToken — PATs + service principals, hash-only storage, prefix shown in UI
  • AuditEvent — append-only authn/authz log, request_id correlation, inet ip on Postgres / String(45) on sqlite

Plumbing

  • nagara.db.session — async engine + async_sessionmaker + get_session FastAPI dep
  • nagara.db.mixins — UUIDPrimaryKeyMixin, TimestampedMixin, SoftDeleteMixin
  • nagara.models — single import that registers every table on Base.metadata (consumed by alembic env + tests)
  • Alembic post-write hooks now run ruff check --fix + ruff format automatically

Endpoints

  • POST /orgs
  • POST /workspaces — auto-creates the default Environment in the same transaction
  • GET /workspaces?org_id=...
  • POST /workspaces/{id}/members — accepts user XOR group via 422-on-violation pydantic validator

Tests

118 passing; every model has schema + insertion tests, every endpoint has happy-path + duplicate + validation cases.

Test plan

  • just check clean
  • just migrate applies 19574c9d_initial_schema cleanly against a fresh db
  • alembic downgrade base && alembic upgrade head cycles cleanly
  • alembic check reports "No new upgrade operations detected"
  • curl -X POST http://127.0.0.1:8000/orgs -d '{"slug":"acme","name":"Acme"}' -H 'content-type: application/json' returns the new org with id
  • Creating a workspace then querying environments shows exactly one with slug='default', is_default=true

Notes for review

  • No auth on the endpoints yet — the next PR adds an admin-only guard once Zitadel/local auth lands. Routes are reachable by anyone hitting the box; do not deploy before that PR ships.
  • Asset and Subscription from the planning doc are intentionally deferred — scope this PR to the bedrock Org/Workspace/Membership slice
  • Membership role is the conservative 5-role enum from the planning doc; we'll trim to 3 if UI feedback supports it

@catalysync
catalysync force-pushed the feat/initial-data-model branch from ff4ccbf to 68983e1 Compare April 25, 2026 09:02
@catalysync
catalysync force-pushed the feat/v0-infra branch 3 times, most recently from d851a04 to fecd799 Compare April 26, 2026 11:16
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