You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Token/JWT:
- api-changes-rebac.md: add APPLE to VerificationMethod enum and applicableVerificationMethods()
- roles-and-acl.md: fix token refresh — orgs re-resolved from DB, flags NOT re-resolved (frozen at login)
- roles-and-acl.md: fix "each team (and optionally org)" → team-scoped only; no org-level custom role set
- roles-and-acl.md: remove "per team/org" phrasing from UOA responsibilities list
SCIM:
- roles-and-acl.md: add userName (email) change behavior — rejected with HTTP 400 (immutable)
- roles-and-acl.md: add POST /scim/v2/Bulk — not supported, returns HTTP 405
- roles-and-acl.md: note PATCH /Users update user attributes (with email change ref)
Feature flags:
- feature-flags.md: clarify role matrix UI "Add Role" delegates to team role endpoints
- feature-flags.md: add PUT overrides with empty flags:{} returns HTTP 400
- apps.md: clarify pollIntervalSeconds applies to both startup and flag query polls
- apps.md: add DELETE App — SDK must re-validate on next launch even within TTL
Architecture:
- architecture-api.md: fix flag route paths to include /org/:orgId prefix
Brief supersession notes:
- brief.md §24.3: add note that domain-scoped org model is superseded by api-changes-rebac.md §1
- brief.md §24.5: add lead→admin supersession note for personal team creation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: Docs/Requirements/apps.md
+2-2Lines changed: 2 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -60,7 +60,7 @@ Request body for `POST /org/:orgId/apps`:
60
60
}
61
61
```
62
62
63
-
`DELETE` is destructive — cascades to all kill switch entries and flag definitions for the App. Requires confirmation (`?confirm=true` query param).
63
+
`DELETE` is destructive — cascades to all kill switch entries and flag definitions for the App. Requires confirmation (`?confirm=true` query param).**SDK cache behavior after App deletion:** SDKs holding a cached startup response must re-validate on next app launch or foreground resume, even if the cache TTL has not expired. After deletion, the `/apps/startup` endpoint returns an empty response (same as unknown `appIdentifier`) — the SDK should treat this gracefully and not block the app.
64
64
65
65
An App belongs to an **org**, not a team. Teams manage who has access; Apps define what those people are using.
66
66
@@ -396,6 +396,6 @@ Every SDK must:
396
396
397
397
2.**Web app versioning** — **decided: use `semver` scheme with `versionName` only.** Web apps pass their semver tag (e.g. `1.5.0`) or deploy timestamp (as a date scheme, e.g. `2024.01.15`) as `versionName`. `buildNumber` is optional for web. The `versionScheme` field on the kill switch entry specifies which scheme to use.
398
398
399
-
3.**Flag sync in token vs query / polling interval** — **decided: SDK polls on foreground resume, minimum 60 seconds between checks, default 5 minutes.** This is configurable per App (see`pollIntervalSeconds`field — to be added to App data model as an optional integer, default 300, minimum 60). Token-embedded flags are login-time snapshots; mid-session changes require poll.
399
+
3.**Flag sync in token vs query / polling interval** — **decided: SDK polls on foreground resume, minimum 60 seconds between checks, default 5 minutes.** This is configurable per App via`pollIntervalSeconds`(default 300, minimum 60, maximum 3600). Token-embedded flags are login-time snapshots; mid-session changes require poll. The `pollIntervalSeconds` value applies to both the `/apps/startup` endpoint poll **and** any direct `/apps/:appId/flags` query polls. SDKs must not poll either endpoint more frequently than `pollIntervalSeconds` (minimum 60 seconds between calls).
400
400
401
401
4.**Multiple matching kill switches** — **decided: highest `priority` integer wins.** If two entries have identical priority, the one with the earlier `createdAt` wins (first created takes precedence). This must be deterministic — no random tiebreaking. The `priority` field is an integer from 0 to 1000 (default 0). Entries with the same priority and `createdAt` are processed in ascending `id` order as a final tiebreaker.
Copy file name to clipboardExpand all lines: Docs/Requirements/feature-flags.md
+4-3Lines changed: 4 additions & 3 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -153,9 +153,10 @@ New section in the sidebar, scoped to the selected App:
153
153
154
154
- Grid view: rows = flags, columns = roles
155
155
- Each cell = toggle (on/off)
156
-
- "Add Role" button — adds a column
156
+
- "Add Role" button — adds a column by creating a new custom role on the selected team via the team role endpoints (`POST /org/:orgId/teams/:teamId/roles`). The matrix column appears once the role exists on at least one team in the org.
157
+
- "Rename Role" / "Delete Role" / "Set Default" — these operations are team role management, delegated to `PATCH/DELETE/PUT /org/:orgId/teams/:teamId/roles/:name[/default]`. The matrix API itself only manages cell values.
157
158
- "Add Flag" button — adds a row (or reuses a flag defined in the flags section)
158
-
- Default role indicator (tick icon on column header, clickable to reassign)
159
+
- Default role indicator (tick icon on column header, clickable to reassign via team role endpoint)
159
160
160
161
### User override UI
161
162
@@ -201,7 +202,7 @@ Roles are derived from the team custom role union (see role matrix section). Add
201
202
202
203
```
203
204
GET /apps/:appId/flags/overrides/:userId — get all overrides for a user (resolved state + source)
204
-
PUT /apps/:appId/flags/overrides/:userId — set one or more overrides (body: { flags: { [key]: boolean } })
205
+
PUT /apps/:appId/flags/overrides/:userId — set one or more overrides (body: { flags: { [key]: boolean } }). Empty `flags: {}` returns HTTP 400 (nothing to set; use DELETE to clear overrides).
205
206
DELETE /apps/:appId/flags/overrides/:userId/:flagKey — remove a specific override
206
207
DELETE /apps/:appId/flags/overrides/:userId — remove all overrides for a user
Copy file name to clipboardExpand all lines: Docs/Requirements/roles-and-acl.md
+7-4Lines changed: 7 additions & 4 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -27,8 +27,8 @@ These are roles that a developer or org defines for their own product. UOA store
27
27
- A user's UOA role and their custom role are completely orthogonal
28
28
29
29
UOA's only responsibilities for custom roles:
30
-
- Store the role definitions per team/org, including which is the default
31
-
- Validate that a role assigned to a user exists in the team/org's defined list
30
+
- Store the role definitions per team, including which is the default
31
+
- Validate that a role assigned to a user exists in the team's defined list
32
32
- Return the role label(s) in the access token and via API
33
33
- Enforce nothing beyond that
34
34
@@ -69,7 +69,7 @@ Teams are the primary registration unit. Not every setup needs a full enterprise
69
69
70
70
## Custom role definitions
71
71
72
-
Each team (and optionally org) defines its own role names. One role is marked as the default.
72
+
Each team defines its own role names. One role is marked as the default. Custom roles are team-scoped — there is no independently-defined org-level custom role set. The `org.customRole` field in the token is a convenience field derived from the user's primary team (see Token output section below).
73
73
74
74
```json
75
75
{
@@ -152,7 +152,7 @@ Note: `flags` is present only when `feature_flags_enabled = true` on the App ass
152
152
153
153
**`orgs` when user has zero org memberships:** When org features are enabled but the user belongs to no org, `orgs` is an empty array (`"orgs": []`), not absent. The field is always present when `org_features.enabled = true`.
154
154
155
-
**Token refresh behavior:** When a refresh token is used to issue a new access token, `orgs[]` claims are **re-resolved from the current database state** at that moment — they are not cached from the original login. Flag values embedded in the token are also re-resolved at refreshtime. Refresh tokens themselves carry no org or flag data.
155
+
**Token refresh behavior:** When a refresh token is used to issue a new access token, `orgs[]` claims are **re-resolved from the current database state** at that moment — they are not cached from the original login. Refresh tokens themselves carry no org data. **Flag values are NOT re-resolved on refresh** — they reflect the state at login time (see `feature-flags.md §Resolved decisions #3`). For real-time flag changes mid-session, the consuming app calls the `/apps/:appId/flags` query endpoint directly.
156
156
157
157
**`org.uoaRole` derivation for multi-team users:** The org-level `uoaRole` is the highest UOA system role the user holds across all teams in that org (accounting for inheritance). If a user is `owner` on one team and `admin` on another, their org-level `uoaRole` is `owner`. If the user has no `owner` or `admin` role on any team (and no org-level role assignment), `uoaRole` is omitted.
158
158
@@ -241,6 +241,7 @@ GET /scim/v2/Groups/:id — read a single group/team with its members
DELETE /scim/v2/Groups/:id — delete team and sever ScimGroupMapping
244
+
POST /scim/v2/Bulk — not supported; returns HTTP 405
244
245
```
245
246
246
247
**SCIM bearer token:** An opaque UUID token issued per org via the admin panel. It has no expiry by default (long-lived). A single org may have multiple active tokens (for rolling rotation or multiple IdP integrations). Tokens are stored hashed; plain value shown only at creation. Revoked via admin panel (`DELETE /internal/admin/orgs/:orgId/scim-tokens/:tokenId`). Scoped to a single org — cannot be used across orgs.
@@ -254,6 +255,8 @@ DELETE /scim/v2/Groups/:id — delete team and sever ScimGroupMapping
254
255
- If a user with the same email already exists in UOA, the SCIM-provisioned user is linked to the existing account (matched by email)
255
256
-`externalId` (IdP-provided) is stored on the UOA user record for stable re-linking on reprovision
256
257
258
+
**Email (userName) changes via PATCH:**`userName` maps to UOA email (the canonical user identifier). **Email changes via SCIM PATCH are rejected with HTTP 400** — email is immutable after account creation. If an IdP sends a new `userName` in a PATCH body, UOA returns `{ "schemas": ["urn:ietf:params:scim:api:messages:2.0:Error"], "status": 400, "detail": "userName is immutable" }`. The IdP should provision a new user with the new email and deprovision the old one separately.
259
+
257
260
**Custom role assignment:** The custom role assigned to SCIM-provisioned members defaults to the team's default custom role. If the IdP sends a role via SCIM enterprise extension (`urn:ietf:params:scim:schemas:extension:enterprise:2.0:User:organization` or a custom schema attribute `uoa:customRole`), that role is validated against the team's defined custom roles and assigned if valid. If the role is unknown, provisioning proceeds with the default role (not rejected).
Copy file name to clipboardExpand all lines: Docs/brief.md
+3-1Lines changed: 3 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -809,6 +809,8 @@ groupMembers GroupMember[]
809
809
810
810
### 24.3 Organisation
811
811
812
+
> **Note:** The "one org per domain" constraint and domain-based org scoping below predates the ReBAC model. `api-changes-rebac.md §1` removes `Organisation.domain` — orgs are no longer tied to a single client domain. Auto-enrolment via `OrgEmailDomainRule` replaces domain-scoped org membership. Implementers should follow `api-changes-rebac.md §1–3` for the current model.
813
+
812
814
* An organisation belongs to a **domain** and is the top-level tenant concept.
813
815
* A domain can have **multiple organisations**.
814
816
* A user belongs to **exactly one organisation** per domain (with global `user_scope`, the same person could belong to different orgs on different domains).
@@ -886,7 +888,7 @@ Organisation slugs are derived from the `name` field:
886
888
* Every org member must belong to at least one team.
887
889
* On org membership add, user is auto-added to the default team.
888
890
* If `org_features.user_needs_team = true`, successful auth must self-heal users with no team membership:
889
-
* If the user already belongs to an org on the domain but has zero teams, create a personal team named `"{user name}'s team"` and add them as `lead`.
891
+
* If the user already belongs to an org on the domain but has zero teams, create a personal team named `"{user name}'s team"` and add them as `lead`.**⚠ SUPERSEDED:**`lead` is replaced by `admin` per `api-changes-rebac.md §1`.
890
892
* If the user does not belong to any org on the domain, create a new personal org for them, create a default personal team named `"{user name}'s team"`, and place them there.
0 commit comments