Data model overview
Postgres via Drizzle; every tenant-owned table carries workspace_id and every
repository method takes it as a required first argument — queries physically cannot
forget the tenancy filter (DATA_ACCESS).
Core spine:
user ─ workspace_member ─ workspace ─ project ─ work_item ─ comment / attachment / activity │ └ parent_id (one-level sub-tasks) ├ workflow_state (categories: backlog/unstarted/started/completed/cancelled) ├ cycle (date-only timeboxes) └ saved_viewworkspace ─ objective / module (roadmap) · notification · audit_event · outbox_event · email_outbox · idempotency_keySemantics worth knowing before changing anything:
- Identifiers (
SKY-42) mint fromproject.next_item_sequnder lock — unique, sequential, immutable. - Revisions:
work_item.revisionbacks compare-and-set updates (409 with the authoritative body on staleness). Same contract on cycles. - Soft deletes (
deleted_at) on items/comments; listings always filter them. - Activity + outbox rows commit in the mutation’s transaction — proven by fault-injection tests.
- Migrations live in
packages/db; CI runs both fresh-install and upgrade paths.
Full model: DATA_MODEL.