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)
  • D3 — sys_role→sys_position, sys_user_role→sys_user_position, sys_role_permission_set→sys_position_permission_set, ctx.roles[]→ctx.positions[], current_user.role→current_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)
  • D6 — Explain 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)
  • D10 — Principal 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
  • D11 — External OWD: optional externalSharingModel, default private, validated external ≤ internal; BU depth inapplicable to externals
  • D12 — Delegated 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, role→position 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

Activity

  1. added theissue type on Jul 8, 2026
  2. self-assigned this
    on Jul 10, 2026
  3. os-zhuang commented on Jul 10, 2026

    @os-zhuang
    ContributorAuthor

    Status 2026-07-10 — platform work complete; downstream adoption in flight

    Done (all merged): ADR #2695 · P1 #2697 · P2 #2708 · P3 #2711 · P4 #2716 · docs #2717 · objectui D2 badges objectstack-ai/objectui#2354. Checkboxes updated accordingly.

    In flight (opened today):

    Remaining under this issue:

    • ObjectUI: OWD external dial (D11 externalSharingModel) in the permission matrix / object settings — the only unshipped objectui item
    • Follow-up ADRs stay unopened until their work actually starts (owner preference)

    Found along the way (spun off): spec's BookAudienceSchema still carries a { profile } arm + "role-gated" comments (D2/D3 leftover the forbidden-word lint missed); ApproverType has no position option, so "position X approves" is inexpressible post-D3 (role there resolves against better-auth sys_member.role tiers) — both flagged as separate tasks.

  4. os-zhuang commented on Jul 10, 2026

    @os-zhuang
    ContributorAuthor

    Update: the OWD external dial (D11) and permission-matrix OWD badges shipped as a second commit on objectstack-ai/objectui#2369 (external externalSharingModel select with inline wider-than-internal warning; per-row OWD … / Ext … badges incl. the D1 Private (default) unset case). With that, every pure-UI ObjectUI follow-up on this issue is in that one PR.

    Two adjacent items from the broader v2 UX list are blocked on missing framework surfaces, flagged as separate tasks rather than bolted onto #2369:

    • D5 isDefault install-suggestion confirm UI — no pending-suggestion list / confirm-bind API exists (isDefault is only read at bootstrap as the fallback set)
    • D6 explain panel — the explain engine is an in-process service only; no REST route for a UI to call
  5. os-zhuang commented on Jul 10, 2026

    @os-zhuang
    ContributorAuthor

    v2 downstream adoption chain is closed ✅

    1. feat(app-shell,console,types)!: adopt @objectstack/spec 13 — Permission Model v2 (ADR-0090) UI fallout objectui#2369 merged (619097ed) — spec ^13.0.0 adoption + all pure-UI follow-ups (position relabel, dead alias/profile removal, external OWD dial, matrix OWD badges); closed objectui#2365
    2. feat!: adopt ObjectStack Permission Model v2 (ADR-0090 / spec 13) hotcrm#438 merged (d29c214e) → released v2.0.0 — the adopting release the smoke gate pins to
    3. ci(release): restore blocking live-hotcrm smoke — v2 adoption shipped #2730 merged (b8ce2da3) — HOTCRM_REF → v2.0.0, continue-on-error removed, honoring ci(release): make live-hotcrm downstream smoke advisory during ADR-0090 v2 launch window #2719's restore condition

    First Release run with the gate blocking again is green (29070034679).

    Remaining scope on this issue is the two framework-surface gaps flagged above (D5 install-suggestion confirm flow, D6 explain REST + panel) — both in flight as separate sessions.

  6. os-zhuang commented on Jul 10, 2026

    @os-zhuang
    ContributorAuthor

    Closing as delivered. Final state:

    The six named follow-up ADRs (grant lifecycle, SoD, scale/reorg hardening, ERP dimensions, ALM promotion, portal identity) move to their own parking-lot issue: #2776 — ADRs to be opened when each work stream actually starts.

  7. added a commit that references this issue on Jul 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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