Skip to content
Africa OS Knowledgebase
Esc
navigateopen⌘Jpreview
On this page

Organizations

The organizations package - OrganizationService, MembershipService, RegistryService, schemas, and the errors they raise.

@africaos/organizations is the tenant package. It owns the organization model, memberships, and the application registry. It is one of only four locked platform packages, and its services appear in every request runtime.

The architecture page Multi-tenancy explains the model; this page documents the code.

The services

The package exposes three Effect services, plus a context tag:

Service Owns
OrganizationService The organization itself: create, lookup, lifecycle, list, applications.
MembershipService Who belongs: invite, status transitions, removal.
RegistryService The application registry and per-organization enablement.
OrganizationContext The active-organization context value ({ organizationId }).

OrganizationService

platform/organizations/src/service.ts:

Method Signature Fails with
create { name, ownerUserId } SlugTaken
listForUser userId -
listAll - (admin only) -
count - -
get / getBySlug id / slug OrganizationNotFound
setStatus id, status IllegalOrganizationTransition
getContext - -
listEnabledApplications organizationId -
enableApplication organizationId, applicationSlug -

create runs inside withTransaction: it slugifies the name (deduplicating name, name-2, …), inserts the org as pending, inserts the creator as an active member, creates an owner role wired to every registered permission, and grants it to the creator. The whole sequence is atomic.

setStatus enforces the lifecycle state machine from lifecycle.ts (see Multi-tenancy). Setting verified also stamps verified_at.

listForUser returns only the user’s active memberships. This is what feeds the organization switcher and active-organization resolution.

enableApplication and listEnabledApplications manage organization_applications. The richer registry surface (with full metadata and typed failures) lives on RegistryService.

MembershipService

platform/organizations/src/membership.ts:

Method Signature Fails with
invite organizationId, email UserNotFound, AlreadyMember
setStatus membershipId, status MembershipNotFound, IllegalMembershipTransition
remove membershipId MembershipNotFound, IllegalMembershipTransition

invite looks the user up by email on public.user (joining through the platform profile), refuses duplicate memberships, and inserts the membership as invited. setStatus enforces the membership state machine from the same file; remove is a transition to removed, which is terminal (the member must be re-invited).

RegistryService

platform/organizations/src/registry.ts (documented in detail on the Application registry page):

Method Purpose
listForOrganization Enabled applications for an org, as full metadata.
listAll Every registered application, no org context.
register Upsert an application definition from app.config.ts.
getForOrganization One enabled application by slug, with typed failures.
setEnabled Toggle enablement for an org.

Schemas

The shared schemas live in platform/organizations/src/schemas/organization.ts:

  • organizationStatusSchema - pending | reviewing | verified | suspended.
  • membershipStatusSchema - invited | active | suspended | removed.
  • OrganizationStatus / MembershipStatus - the derived types.

These are the single source of truth for the status strings; both lifecycle state machines parse rows through them.

Errors

The package’s typed errors (platform/organizations/src/errors.ts) include:

  • OrganizationNotFound({ id } | { slug })
  • SlugTaken({ slug })
  • IllegalOrganizationTransition({ from, to })
  • NotOrganizationMember()
  • MembershipNotFound({ id })
  • AlreadyMember({ email })
  • UserNotFound({ email })
  • IllegalMembershipTransition({ from, to })
  • ApplicationNotFound({ slug })
  • ApplicationDisabled({ slug })
  • InvalidApplicationMetadata({ errors })

Server functions catch these on the Effect failure channel and fold them into serializable results.

Layers and testing

  • OrganizationService.Live, MembershipService.Live, and RegistryService.Live wire the implementations to whatever Database and Logger provide.
  • Each also exposes a Test layer backed by Database.Test, so service tests run without Postgres.
  • Implementations are factored into makeOrganizationService(sql, logger), makeMembershipService(sql), and makeRegistryService(sql, logger).

Next steps

Last updated on August 18, 2026