Skip to content

ADR-0090: Permission Model v2 — concept convergence, final naming, AI-authoring safety (tracking) #2696

Description

@os-zhuang

Tracking issue for ADR-0090 (proposed in #2695; companion reference: docs/design/permission-model.md).

Summary

Converge the permission model ahead of launch: five admin-facing concepts (permission set · position · business unit · sharing/OWD · team), secure defaults, one-step renames with no compatibility aliases (launch window), a typed principal model (humans / AI agents / services / guests / externals), scoped delegated administration, and a machine-enforced authoring-safety story for AI-drafted metadata.

Origin incident: an object created without sharingModel + an ordinary C/R/U permission set silently granted org-wide read/write of other users' records (objectstack-ai/objectui#2348 — the Studio OWD control shipped there is the UI half; this ADR fixes the platform half).

Decisions (see ADR for full rationale)

  • D1 — Custom objects default to OWD private; unset state removed (existing metadata grandfathered by explicit stamping)
  • D2 — Profile concept removed (isProfile deleted; isDefault narrows to an install-time suggestion)
  • D3sys_rolesys_position, sys_user_rolesys_user_position, sys_role_permission_setsys_position_permission_set, ctx.roles[]ctx.positions[], current_user.rolecurrent_user.position; "role" becomes a lint-enforced forbidden word (sole exception: better-auth sys_member.role / org_membership_level)
  • D4 — OWD enum drops read/read_write/full aliases; authoring rejects
  • D5 — Built-in everyone position carries default grants (per-request resolution, install-time suggestion prompt, no fallback cliff, high-privilege bits blocked)
  • D6Explain engine P0 + access-matrix snapshot gate on security-domain publishes
  • D7 — Security-domain publish linter + tiered human gates
  • D8 — Teams receive sharing only; never own records, never bind permission sets
  • D9 — Audience anchors: builtin guest position joins everyone; packages suggest audience bindings, never ship shared builtin sets; no admin anchor (superuser wildcard covers new packages)
  • D10Principal taxonomy (kind: human|agent|service|guest|system, audience: internal|external, onBehalfOf); agents act on the intersection of their grants and their delegator's, under a lint ceiling, with human co-sign for destructive ops; services = seatless least-privilege machine identities
  • D11External OWD: optional externalSharingModel, default private, validated external ≤ internal; BU depth inapplicable to externals
  • D12Delegated administration: admin scopes = BU subtree + assignable-set allowlist + structural no-self-escalation; everyone/guest anchors and security publishes stay tenant-level

Phased delivery (each independently shippable, proofs per ADR-0054)

  • Review & accept ADR-0090 (docs(adr): ADR-0090 — Permission Model v2: concept convergence, final naming, AI-authoring safety #2695)
  • P1 — Breaking wave (one coordinated PR, mechanical): D3 renames, D4 enum cleanup, D2 isProfile removal, D1 default flip + grandfather stamping, plus spec shapes: ctx principal taxonomy (D10) + externalSharingModel (D11). Proof: full suite + dogfood re-run of the objectui#2348 scenario showing owner isolation with no explicit OWD authored
  • P2 — Audience anchors (D5+D9): everyone + guest builtin seeding, install-time suggestion prompts, cliff removal. Proof: package install/uninstall grant-liveness e2e + anonymous-principal e2e
  • P3 — Linter + tiered gates + delegated admin (D7+D12 + per-kind lint tiers of D9/D10): publish-pipeline integration; admin scopes with allowlist/no-self-escalation. Proof: failing fixture per lint rule; delegation e2e (subsidiary admin cannot exceed allowlist or subtree)
  • P4 — Explain + matrix gate (D6, intersection- and audience-aware): spec contract, engine, simulator UI, snapshot gate with external-audience column. Proof: matrix-diff drill on a seeded CRM stack incl. agent on-behalf-of and external-portal cases
  • Docs alignment: update content/docs/permissions/* to match docs/design/permission-model.md
  • ObjectUI follow-ups: profile badge → provenance + default badges (objectui#2354); Access pillar relabel (Position), dead alias normalization removal, roleposition loader/preview/i18n + spec ^13 adoption, OWD external dial (D11) + permission-matrix OWD badges (objectui#2369); D5 suggested-binding confirm UI (framework#2746 + objectui side) and D6 explain panel (objectui#2375) landed via follow-up sessions

Named follow-up ADRs (scoped out of 0090, to be opened separately)

  • Grant lifecycle & recertification — time-boxed assignments, delegation-of-duty, break-glass with auto-expiry, periodic access-review campaigns (SOX/等保); substrate for task-scoped agent grants
  • Segregation of Duties (SoD) — declarative conflict rules between permission sets, assignment-time checks + audit report
  • Scale & reorg hardening — membership-set materialization/caching, async share recalculation, batched BU moves; 100k-user × 10M-record benchmark gates P4
  • ERP dimension restrictions — declarative "rows where field ∈ my values" as first-class metadata
  • ALM / environment promotion — export/import + semantic diff of positions/bindings across environments (reuses the D6 matrix)
  • Portal identity & licensing — the product track that activates D11 at scale

Supersedes / amends

ADR-0056 D7 (default-profile fallback → everyone position) · ADR-0057 D5/D7 alias clauses (pre-launch one-step renames). ADR-0057's core (BU tree, scope depth, sys_user_role decoupling) is untouched and load-bearing.

🤖 Generated with Claude Code

https://claude.ai/code/session_012oLzaP8n7A3YKFmgaHWC8H

Metadata

Metadata

Assignees

Labels

No labels
No labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions