Epic: spaces membership v2 — app-owned model (default) + IdP-write as optional power mode #211

Closed
opened 2026-08-13 15:16:46 +00:00 by Cordy · 2 comments
Owner

Umbrella for the spaces/membership rework decided after the openbao dogfood surfaced #209/#210.

Decision (full analysis in the ADR sub-issue): today spaces management requires Cairn to hold write access to the IdP (spaces.Manager, gated on the storage.encryption.keycloak* admin client). That doesn't generalise — it needs a realm-admin-grade credential (manage-users), is an enterprise non-starter for AD (the very reason openbao custody exists), conflates config with key custody, and cuts against Cairn's minimal-blast-radius line.

Target model (Option C):

  • IdP groups → read-only spaces, everywhere, from the groups claim / memberOf. No credential.
  • Cairn-created spaces → app-owned objects (owner + members) in the encrypted .cairn-state/ backend, exactly like shares/holds/peering trust. No IdP write, works on local/oidc/ldap/AD uniformly.
  • IdP-write (Option A) stays as an explicit, off-by-default, documented power mode for Keycloak-centric shops that want the directory as the single book of record.
  • Frontend feature-detects capabilities and surfaces real errors (folds in the old "graceful degrade" step — it's just part of this, not a separate phase).

Sub-issues: ADR/docs · app-owned membership store · scope integration · config decouple + optional power mode · frontend feature-detect + error surfacing · member picker · handbook docs.

Resolves as it lands: #209 (create-space 404 / empty error) and #210 (add-member no feedback + picker).

Greenlit end-to-end; worked through in order.

Umbrella for the spaces/membership rework decided after the openbao dogfood surfaced #209/#210. **Decision (full analysis in the ADR sub-issue):** today spaces management requires Cairn to hold **write access to the IdP** (`spaces.Manager`, gated on the `storage.encryption.keycloak*` admin client). That doesn't generalise — it needs a realm-admin-grade credential (`manage-users`), is an enterprise non-starter for AD (the very reason `openbao` custody exists), conflates config with key custody, and cuts against Cairn's minimal-blast-radius line. **Target model (Option C):** - **IdP groups → read-only spaces**, everywhere, from the `groups` claim / `memberOf`. No credential. - **Cairn-created spaces → app-owned objects** (owner + members) in the encrypted `.cairn-state/` backend, exactly like shares/holds/peering trust. No IdP write, works on `local`/`oidc`/`ldap`/AD uniformly. - **IdP-write (Option A)** stays as an explicit, off-by-default, documented power mode for Keycloak-centric shops that want the directory as the single book of record. - Frontend feature-detects capabilities and surfaces real errors (folds in the old "graceful degrade" step — it's just part of this, not a separate phase). **Sub-issues:** ADR/docs · app-owned membership store · scope integration · config decouple + optional power mode · frontend feature-detect + error surfacing · member picker · handbook docs. **Resolves as it lands:** #209 (create-space 404 / empty error) and #210 (add-member no feedback + picker). Greenlit end-to-end; worked through in order.
Author
Owner

Complete. All sub-issues merged green to main:

  • #212 ADR (decision recorded)
  • #213 app-owned membership store (encrypted state-backend)
  • #214 scope grants access from app-owned membership
  • #215 config decoupled + IdP-write demoted to an off-by-default power mode
  • #216 API dispatch + /me capability + frontend feature-detect + robust errors
  • #217 member-add validation + feedback (candidate dropdown split to #226)
  • #218 handbook rewrite (spaces.md) + ADR cross-link

App-owned spaces are now the default: create-space and member management work with no IdP client, under every auth mode and key custody. #209 and #210 are fixed in code. Remaining follow-up: #226 (candidate autocomplete, low priority).

Next: cut v0.6.17, redeploy the dogfood, verify live.

**Complete.** All sub-issues merged green to `main`: - #212 ADR (decision recorded) - #213 app-owned membership store (encrypted state-backend) - #214 scope grants access from app-owned membership - #215 config decoupled + IdP-write demoted to an off-by-default power mode - #216 API dispatch + `/me` capability + frontend feature-detect + robust errors - #217 member-add validation + feedback (candidate dropdown split to #226) - #218 handbook rewrite (`spaces.md`) + ADR cross-link App-owned spaces are now the default: create-space and member management work with no IdP client, under every auth mode and key custody. #209 and #210 are fixed in code. Remaining follow-up: #226 (candidate autocomplete, low priority). Next: cut `v0.6.17`, redeploy the dogfood, verify live.
Author
Owner

Epic landed and verified end-to-end on both deployment types.

Shipped (all sub-issues closed):

  • #212 ADR docs/adr/0001-spaces-membership.md (two-model decision, blast-radius/AD/SoD reasoning, industry grounding, security/trust table), linked from ARCHITECTURE.md.
  • #213 app-owned membership store in the encrypted state-backend (owner + members, CRUD, role transitions, persistence round-trip; owner cannot be demoted/removed).
  • #214 scope driver grants space access from app-owned membership; IdP groups stay read-only; /spaces list unions both, deduped.
  • #215 config decoupled from storage.encryption.keycloak*auth.groups (read client for live groups #96; manage power mode for IdP-write), legacy back-compat preserved; default = app-owned, no IdP calls.
  • #216 capability feature-detect (me.spaces {create, manageMembers}) + real error surfacing (no more empty Create failed:).
  • #217 member field: already-member / owner / not-found validation + add/remove feedback.
  • #218 handbook spaces.md rewrite (two kinds of space, config, power-mode blast-radius, back-compat), encryption.md coupling fixed, ADR cross-linked.

Released v0.6.17, plus v0.6.18 hotfix for the members-dialog reading the raw Response without .json() (empty/"not iterable" list).

Live verification (v0.6.18):

  • cairn-openbao — app-owned path (no IdP admin client): create-space works, members list renders, add member works, already-member + owner validation fire, space survived a redeploy.
  • cairn-enc — IdP-managed power mode (boot log IdP-managed spaces enabled (power mode)... requires manage-users): members dialog renders the directory-group members, already-member validation fires. Confirms the #216 gating selects the right store per instance and the v0.6.18 fix works on both paths.

Resolves #209. The only remaining thread is the candidate autocomplete polish (#226), which also carries the picker portion of #210 — in progress now; #210 will close together with #226. Closing the epic.

Epic landed and verified end-to-end on both deployment types. **Shipped (all sub-issues closed):** - #212 ADR `docs/adr/0001-spaces-membership.md` (two-model decision, blast-radius/AD/SoD reasoning, industry grounding, security/trust table), linked from ARCHITECTURE.md. - #213 app-owned membership store in the encrypted state-backend (owner + members, CRUD, role transitions, persistence round-trip; owner cannot be demoted/removed). - #214 scope driver grants space access from app-owned membership; IdP groups stay read-only; `/spaces` list unions both, deduped. - #215 config decoupled from `storage.encryption.keycloak*` → `auth.groups` (read client for live groups #96; `manage` power mode for IdP-write), legacy back-compat preserved; default = app-owned, no IdP calls. - #216 capability feature-detect (`me.spaces {create, manageMembers}`) + real error surfacing (no more empty `Create failed:`). - #217 member field: already-member / owner / not-found validation + add/remove feedback. - #218 handbook `spaces.md` rewrite (two kinds of space, config, power-mode blast-radius, back-compat), encryption.md coupling fixed, ADR cross-linked. Released **v0.6.17**, plus **v0.6.18** hotfix for the members-dialog reading the raw `Response` without `.json()` (empty/"not iterable" list). **Live verification (v0.6.18):** - **cairn-openbao** — app-owned path (no IdP admin client): create-space works, members list renders, add member works, already-member + owner validation fire, space survived a redeploy. - **cairn-enc** — IdP-managed power mode (boot log `IdP-managed spaces enabled (power mode)... requires manage-users`): members dialog renders the directory-group members, already-member validation fires. Confirms the #216 gating selects the right store per instance and the v0.6.18 fix works on both paths. Resolves #209. The **only** remaining thread is the candidate autocomplete polish (#226), which also carries the picker portion of #210 — in progress now; #210 will close together with #226. Closing the epic.
Cordy closed this issue 2026-08-13 22:49:49 +00:00
Sign in to join this conversation.
No labels
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: Cordy/Cairn#211
No description provided.