Skip to content

Platform Spec: Identity & Access

FieldValue
Layer_platform — global, cross-cutting. Every feature consumes it; no feature redefines it.
StatusStub — derived from recurring needs across Personal Page, MVP Nominations, Weekly Goals. Awaiting sign-off + the permission-model decision.
OwnerTBD (Duncan + external team at/after stack call)
Contract/contracts/_platform/identity.yaml
Governsprinciples.md PR-04 (tenancy), PR-05 (server-authoritative visibility), PR-06 (permission model)

Why this exists. Three feature specs independently needed the same primitives — identity, roles, “who reports to whom”, per-resource visibility. That repetition is the signal: this is platform, not feature. Defined once here; feature contracts reference these schemas and call these endpoints rather than re-implementing access logic.

The global API surface every section feeds on. This spec owns Identity & Access; the siblings below are planned _platform specs (stubs to follow as demand confirms):

Platform concernSpecDriven by
Identity, roles, permissions, org-chart visibilitythis specPersonal Page FR-10/11, MVP Q4, Weekly Goals FR-17–20
User directory (canonical employee list)_platform/user-directory.md (planned)MVP Q5 (nominee/member pickers), mentions, any people picker
Notifications (in-app + email)_platform/notifications.mdWeekly Summary §4.4/4.5, Weekly Goals OQ-04
Actions→outcomes spine (domain core)_spine/goals-outcomes.md (planned)the linking object; Weekly Goals G1

Tenancy is an invariant (PR-04), enforced in every contract — not a callable service.

  • Current principal. Every authenticated request resolves to a principal: userId, tenantId, functional roles[], and identity claims (auth/SSO approach is a pre-build dependency in the operating model).
  • GET /me returns the principal + their effective capabilities, so the frontend renders the right controls without guessing.
  • Tenant scoping is implicit and mandatory (PR-04): the principal’s tenantId bounds every query; cross-tenant access is impossible.
RoleMeaning
employeeDefault. Acts on own resources; relationship-based access to others.
people_teamOrg-wide visibility for People-area reporting/exports (MVP §3.4, Weekly Goals §6, Weekly Summary §4.7).
senior_leadershipOrg-wide read of People-area data (Weekly Summary FR-38); peer of People Team for visibility.
adminPlatform administration; superset of People Team for access purposes.
feature-elevatedNarrow grants attached to a principal for one capability (e.g. see others’ Mojo AI insights — Personal Page FR-11). Not a role; an explicit grant.

3. Relationship to a resource owner (org-chart-derived)

Section titled “3. Relationship to a resource owner (org-chart-derived)”

Computed from the reporting-line graph, per resource:

  • self — viewer owns it.
  • colleague — same org, no management relationship.
  • line_manager — viewer is the owner’s direct manager.
  • manager_of_managers — owner is anywhere below the viewer in the reporting chain (recursive — a recursive CTE, see PR-13).

A viewer may act on a resource if a functional role grants it OR their relationship to the owner grants it. Default-deny otherwise. Visibility filtering is server-authoritative (PR-05) — restricted fields are omitted from the response body, never merely hidden client-side.

Reference visibility ladder (generalised from Weekly Goals §5): own → direct reports → full chain → org-wide. Features inherit it.

Gospl is the source of truth for the org chart / reporting line (decided 2026-06-10). HomeRun remains the system of record for personal/HR data (PR-01), but the reporting structure is mastered in Gospl — it is org structure, not personal data, and it drives access decisions, so it lives where the access logic lives. HomeRun’s hierarchy (if any) is not authoritative for Gospl.

The reporting-line graph is platform-owned and read by access checks and by features:

  • reportsTo per user (the edge) — the same graph the Personal Page Role Page reads (FR-20) and the field Gospl already persists (PR-02).
  • Derived reads: direct reports of a user, and the full chain below a user (recursive). These power Weekly Goals manager visibility (FR-18/19) and the scoped non-setter report (FR-22).

Implication — Gospl needs org-chart maintenance. Because Gospl masters the hierarchy, there must be a People-Team/Admin capability to edit reporting lines (assign/reassign manager, move a person, handle leavers). This is a new platform-area capability — candidate for its own spec (_platform/org-chart-admin.md or an org-area feature).

Principle: decouple the app from any one identity provider. Authentication goes through an SSO broker that speaks OIDC + SAML; each tenant maps to its own IdP connection. Adding a provider later is configuration, not a rewrite.

  • v1 (us, single tenant): Google Workspace via OIDC. Restrict to our Workspace domain using the hd (hosted-domain) claim — verify hd server-side, do not trust the email domain alone. Only @<our-domain> accounts authenticate.
  • Later (multi-tenant): per-tenant IdP. Each tenant configures its own connection — Google, Microsoft Entra ID, Okta, or generic SAML/OIDC. A tenant ↔ IdP mapping is the seam; build it now even though v1 has one entry.
  • Authentication ≠ authorisation. SSO proves who (verified identity + claims: email, name, maybe groups). Roles/permissions stay in Gospl (PR-06) keyed off the verified identity — consistent with the org chart being Gospl-mastered. Do not drive app roles from IdP group claims in v1.
  • Provisioning: JIT — on first successful SSO login, create/link the Gospl user from verified claims. Auto-deprovisioning of leavers (SCIM/directory sync) is later, and belongs with _platform/user-directory.md.
  • Login routing (multi-tenant seam): resolve which tenant’s IdP to use via one of — tenant subdomain (acme.gospl.app), email-domain/hd → tenant lookup, or an org selector. Trivial for v1 (one tenant); design the seam so it’s not retrofitted.
  • Session/token: after the broker authenticates, Gospl issues the bearer JWT carrying tenantId + userId + roles that every contract expects.

Build vs buy (decide at stack meeting): auth is high-risk to hand-roll. Lean managed. Best fit for “multi-tenant, each brings their own SSO” is a B2B SSO provider:

  • WorkOS — purpose-built for per-organisation SSO connections (SAML+OIDC) + SCIM; cleanest fit for this exact shape.
  • Auth0 (Organizations) — mature, enterprise connections per org; pricier at scale.
  • Keycloak (self-host) — per-realm IdPs, full control, no per-MAU cost, but you operate it.
  • Supabase Auth / Cognito — workable if already in that ecosystem, but per-tenant SSO is more DIY. Recommendation: shortlist WorkOS (fastest path to the multi-tenant SSO shape) vs Keycloak (if self-hosting/no per-seat cost matters), pick at the meeting.

Per-tenant auth-connection config (which IdP a tenant uses, client IDs/certs) is sensitive admin configuration — likely a future admin/auth-connections page or part of tenant onboarding. For v1 it is effectively environment config for the single tenant.

RefCriterion
IA-01GET /me returns the principal’s userId, tenantId, roles, and effective capabilities.
IA-02A permission check for (viewer, action, resource) returns allow/deny per the §4 rule; default-deny when neither axis grants.
IA-03GET /org/users/{userId}/direct-reports returns only the immediate reports, tenant-scoped.
IA-04GET /org/users/{userId}/chain returns the full recursive sub-tree below the user, tenant-scoped.
IA-05Any cross-tenant identity/org lookup returns 404 (not 403) — tenant boundaries are invisible across tenants.

§C. Clarifications Needed (blocking — these gate every feature)

Section titled “§C. Clarifications Needed (blocking — these gate every feature)”
  • Auth / SSO — direction set (see §6): SSO broker + tenant↔IdP mapping; v1 = Google Workspace OIDC with hd restriction; roles stay in Gospl; JIT provisioning. Remaining decision: broker vendor (WorkOS vs Keycloak vs Auth0) at the stack meeting.
  • Role assignment — how a user becomes people_team/admin: named list or group/SSO-claim-driven? (MVP Q4, generalised.)
  • Org-chart source of truth RESOLVED 2026-06-10: Gospl is source of truth for the reporting line (not HomeRun). See §5. Open follow-on: who/what seeds the initial chart, and the org-chart maintenance capability (new spec needed).
  • Feature-elevated grants — modelled as explicit per-capability grants vs ad-hoc per-feature flags. Recommend explicit grants so they’re auditable.

Feature contracts currently each redefine UserRef and Error. Decision needed: a shared OpenAPI components file (contracts/_platform/_shared.yaml) that feature contracts $ref, vs the current copy-per-contract. Recommend shared once cross-file $ref tooling is confirmed in the build — keeps UserRef, Error, Role, pagination defined once.