- Add deployment-name branding to titles, mastheads, and OG tags - Share one grant delivery-state query with lineage across grants surfaces - Show pool status/usage, org owners, and config readiness - Make billing views projection-aware with recency and sync vocabulary - Guard FedWiki creation without domains and render route-aware 404s
8.4 KiB
organization
Purpose
Defines the organization model: organizations, members, workspaces, roles, and scoped role assignments.
Requirements
Requirement: Organizations table
The system SHALL maintain an organizations table with fields: org_id (UUIDv7 primary key, DEFAULT uuidv7()), name (VARCHAR(255), NOT NULL), slug (VARCHAR(100), UNIQUE, NOT NULL), org_type (VARCHAR(20), NOT NULL, FK → organization.org_types(org_type)), owner_person_id (UUID, FK → persons, NOT NULL), status (VARCHAR(20), NOT NULL, DEFAULT 'active'), created_at (TIMESTAMPTZ), updated_at (TIMESTAMPTZ).
Scenario: Organization slug uniqueness
- WHEN an attempt is made to create an organization with a slug that already exists
- THEN the database SHALL reject the insert with a unique constraint violation
Scenario: Organization has an owner
- WHEN an organization is created
- THEN it SHALL reference a
personsrecord asowner_person_id
Scenario: Organization type referential integrity
- WHEN an attempt is made to create an organization with an
org_typethat does not exist inorg_types - THEN the database SHALL reject the insert with a foreign key constraint violation
Requirement: Organization members
The system SHALL maintain an org_members table with fields: org_member_id (UUIDv7 primary key), org_id (UUID, FK → organizations, NOT NULL), person_id (UUID, FK → persons, NOT NULL), role_id (UUID, FK → roles, NOT NULL), status (VARCHAR(20), NOT NULL, DEFAULT 'active'), joined_at (TIMESTAMPTZ, NOT NULL, DEFAULT NOW()), removed_at (TIMESTAMPTZ), created_at (TIMESTAMPTZ), updated_at (TIMESTAMPTZ). The combination of org_id and person_id SHALL be unique (a person can be a member of an org only once).
Scenario: Person joins organization
- WHEN an
org_membersrecord is created linking a person to an organization with a role - THEN the person SHALL be considered a member of that organization with the permissions granted by the assigned role
Scenario: Duplicate membership prevented
- WHEN an attempt is made to add a person to an organization they already belong to
- THEN the database SHALL reject the insert with a unique constraint violation on
(org_id, person_id)
Requirement: Workspaces
The system SHALL maintain a workspaces table with fields: workspace_id (UUIDv7 primary key), org_id (UUID, FK → organizations, NOT NULL), name (VARCHAR(255), NOT NULL), slug (VARCHAR(100), NOT NULL), description (TEXT), status (VARCHAR(20), NOT NULL, DEFAULT 'active'), created_at (TIMESTAMPTZ), updated_at (TIMESTAMPTZ). The combination of org_id and slug SHALL be unique (workspace slugs are unique within an organization).
Scenario: Workspace belongs to organization
- WHEN a workspace is created
- THEN it SHALL reference exactly one organization via
org_id
Scenario: Workspace slug unique within org
- WHEN an attempt is made to create a workspace with a slug that already exists within the same organization
- THEN the database SHALL reject the insert with a unique constraint violation on
(org_id, slug)
Requirement: Roles with flat permission model
The system SHALL maintain a roles table with fields: role_id (UUIDv7 primary key), org_id (UUID, FK → organizations, nullable), role_name (VARCHAR(100), NOT NULL), display_name (VARCHAR(255), NOT NULL), description (TEXT), is_system (BOOLEAN, NOT NULL, DEFAULT FALSE), permissions (TEXT[], NOT NULL), created_at (TIMESTAMPTZ), updated_at (TIMESTAMPTZ). System roles (is_system = TRUE) have org_id = NULL. Custom roles have org_id set.
Scenario: System roles are seeded
- WHEN the organization module's migration runs
- THEN the following system roles SHALL exist:
owner,admin,member,billing,viewer,platform_admin - AND each role SHALL have the permission arrays defined in the design's role specification
- AND all system roles SHALL have
is_system = TRUEandorg_id = NULL
Scenario: Permission format
- WHEN a role's permissions are queried
- THEN each permission SHALL be a string in
resource:actionformat (e.g.,billing:manage,workspace:delete)
Requirement: Scoped role assignments
The system SHALL maintain a role_assignments table with fields: assignment_id (UUIDv7 primary key), role_id (UUID, FK → roles, NOT NULL), person_id (UUID, FK → persons, NOT NULL), org_id (UUID, FK → organizations, NOT NULL), scope_type (VARCHAR(20), NOT NULL — values: organization, workspace, pool), scope_id (UUID, NOT NULL), created_at (TIMESTAMPTZ), updated_at (TIMESTAMPTZ). The combination of role_id, person_id, org_id, scope_type, and scope_id SHALL be unique.
Scenario: Organization-scoped assignment
- WHEN a role assignment is created with
scope_type = 'organization'andscope_idequal to anorg_id - THEN the person SHALL have that role's permissions for the entire organization
Scenario: Workspace-scoped assignment
- WHEN a role assignment is created with
scope_type = 'workspace'andscope_idequal to aworkspace_id - THEN the person SHALL have that role's permissions for that specific workspace only
Requirement: Organization module owns its schema
The organization module SHALL own its database schema via migrations in internal/organization/migrations/. The organization module's sqlc configuration SHALL read both identity and organization migrations to resolve cross-module FK references. It SHALL generate into the internal/organization/ Go package.
Scenario: Organization migration creates tables
- WHEN the organization module's migration runs (after identity migration)
- THEN the
organizations,org_members,workspaces,roles, androle_assignmentstables SHALL exist with all specified columns, constraints, and indexes
Requirement: Updated at triggers
The system SHALL automatically update the updated_at timestamp on organizations, org_members, workspaces, roles, and role_assignments rows whenever they are modified.
Scenario: Organization record updated
- WHEN any field on an
organizationsrecord is modified - THEN the
updated_atfield SHALL be set to the current timestamp via a database trigger
Requirement: Operator presentation of organizations is honest about what the system knows
Operator surfaces presenting organizations, memberships, and persons SHALL NOT render state the console does not master or evaluate as if it were live state:
- The organizations list SHALL NOT render
organizations.statusas a colored state badge while nothing in the system reads that column; the column is omitted or rendered as neutral text until a real lifecycle consumes it. - Each organization SHALL display its owner (the guaranteed owner membership) on the list and the detail surface, so an organization cannot read as unowned.
- The reserved System organization SHALL carry a visible synthetic marker distinguishing it from member organizations, and its row SHALL NOT invite ordinary member-org actions as if it were one.
- Membership role badges SHALL NOT imply console authorization: where a database role (e.g.
owner) is displayed, the surface SHALL make clear it is an organizational fact, not a permission grant (authorization derives from IdP roles at login). - Org types SHALL render once, by display name; concatenations that double display name and slug (e.g. "Personal personal") are retired.
- Person surfaces SHALL NOT badge a person as
activeor otherwise imply account-enablement knowledge that lives in the identity provider.
Scenario: Organization rows show an owner
- WHEN an operator views the organizations list
- THEN each row identifies the organization's owner person
Scenario: Unread status is not badged
- WHEN an operator views the organizations list
- THEN no green state badge renders from the unread
statuscolumn
Scenario: System organization is marked synthetic
- WHEN an operator views a listing that includes the reserved System organization
- THEN the row carries a visible marker naming it as the synthetic system tenant
Scenario: Org type renders once by display name
- WHEN an operator views any surface rendering an organization's type
- THEN the type renders as its display name a single time, with no appended slug duplicate