Platform Spec: Identity & Access
Platform Spec: Identity & Access
Section titled “Platform Spec: Identity & Access”| Field | Value |
|---|---|
| Layer | _platform — global, cross-cutting. Every feature consumes it; no feature redefines it. |
| Status | Stub — derived from recurring needs across Personal Page, MVP Nominations, Weekly Goals. Awaiting sign-off + the permission-model decision. |
| Owner | TBD (Duncan + external team at/after stack call) |
| Contract | /contracts/_platform/identity.yaml |
| Governs | principles.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.
What lives in the platform layer
Section titled “What lives in the platform layer”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 concern | Spec | Driven by |
|---|---|---|
| Identity, roles, permissions, org-chart visibility | this spec | Personal 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.md | Weekly 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.
1. Identity & Session
Section titled “1. Identity & Session”- Current principal. Every authenticated request resolves to a principal:
userId,tenantId, functionalroles[], and identity claims (auth/SSO approach is a pre-build dependency in the operating model). GET /mereturns 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
tenantIdbounds every query; cross-tenant access is impossible.
2. Roles (functional)
Section titled “2. Roles (functional)”| Role | Meaning |
|---|---|
employee | Default. Acts on own resources; relationship-based access to others. |
people_team | Org-wide visibility for People-area reporting/exports (MVP §3.4, Weekly Goals §6, Weekly Summary §4.7). |
senior_leadership | Org-wide read of People-area data (Weekly Summary FR-38); peer of People Team for visibility. |
admin | Platform administration; superset of People Team for access purposes. |
| feature-elevated | Narrow 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).
4. The access rule (PR-06)
Section titled “4. The access rule (PR-06)”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.
5. Org chart / reporting line
Section titled “5. Org chart / reporting line”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:
reportsToper 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).
6. Authentication & SSO
Section titled “6. Authentication & SSO”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 — verifyhdserver-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 + rolesthat 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.
§A. Acceptance Criteria (stub)
Section titled “§A. Acceptance Criteria (stub)”| Ref | Criterion |
|---|---|
| IA-01 | GET /me returns the principal’s userId, tenantId, roles, and effective capabilities. |
| IA-02 | A permission check for (viewer, action, resource) returns allow/deny per the §4 rule; default-deny when neither axis grants. |
| IA-03 | GET /org/users/{userId}/direct-reports returns only the immediate reports, tenant-scoped. |
| IA-04 | GET /org/users/{userId}/chain returns the full recursive sub-tree below the user, tenant-scoped. |
| IA-05 | Any 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
hdrestriction; 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 truthRESOLVED 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.
Shared schema components
Section titled “Shared schema components”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.