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: Access pillar relabel (Position), profile badge → provenance + default badges, OWD context (internal + external dials) in the permission matrix, remove the now-dead alias normalization from objectui#2348

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

No one assigned

    Labels

    No labels
    No labels

    Fields

    No fields configured for Feature.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions