MOTIR-392Done
6.10.7 Vitest — org model + membership gating + backfill
Estimate: 50m · Depends on: 6.10.4
Lock the org tier with tests over a real Postgres (the project convention; the only allowed vi.mock is getSession()). Exercise the model, the gating, and the backfill for real.
The model + repositories (6.10.3):
- An org + an
OrganizationMembershipcreate/read round-trips; the(organizationId, userId)uniqueness holds; theWorkspace.organizationIdrelation resolves both ways. - Write repo methods require
tx(a compile-time guarantee; assert the create runs inside a transaction).
The access gating (6.10.4):
- A user who is a member of a WORKSPACE but NOT of its ORG is DENIED access (org membership gates workspace access) and sees 404-not-403 cross-tenant.
- An org OWNER/ADMIN is granted admin-equivalent access to EVERY workspace under the org (the role composes above the 6.4
MemberRole); an org MEMBER falls back to their per-workspace role. - Membership direction (6.10.2 §5): adding a user to a WORKSPACE auto-creates their org membership (assert the
OrganizationMembershiprow appears); adding a user to the ORG creates NO workspace membership (assert an org-only member reaches zero workspaces until explicitly added); removing from the org revokes all workspace access while removing from a workspace leaves the org membership intact. - The cross-workspace member listing returns members across the org’s workspaces and PAGINATES (assert a page boundary, not a full-table load — the at-scale rule).
- A role change / removal is one transaction; a concurrent membership change serializes via the row lock (no lost update).
The backfill (6.10.3):
- Seeding pre-org workspaces then running the backfill creates exactly ONE default org per workspace, points each at it, and makes an owner membership; NO workspace is left with a null
organizationId; re-running the backfill is idempotent (no duplicates).
Acceptance criteria
- The above cases pass over a real Postgres (only
getSession()mocked); the gating + backfill + pagination are exercised for real, not asserted on mocks. - The member-of-workspace-but-not-org denial, the org-owner-spans-all-workspaces grant, and the one-default-org-per-workspace backfill each have a direct test.
- New service/repo code respects the per-file coverage gate (
motir-core/CLAUDE.md§ coverage); the empty-input / no-membership / idempotent-backfill guards each have a direct test (a new repo method’s empty-input branch needs its own assertion or the gate fails).
Context refs
- 6.10.3 / 6.10.4 (the schema + gating under test).
motir-core/CLAUDE.md§ tests-use-real-Postgres + § coverage gate.motir-core/tests/helpers/db.ts— the per-test truncation helper the suite runs over.