Skip to content

docs(adr): ADR-0086 authz metadata↔config boundary + cross-package composition#2559

Merged
os-zhuang merged 2 commits into
mainfrom
claude/authorization-model-design-elkcfj
Jul 4, 2026
Merged

docs(adr): ADR-0086 authz metadata↔config boundary + cross-package composition#2559
os-zhuang merged 2 commits into
mainfrom
claude/authorization-model-design-elkcfj

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Closes the design/ADR task in #2557.

Adds ADR-0086 (docs/adr/0086-authz-metadata-config-boundary-and-cross-package-composition.md) — the model-wide decision on which authorization primitives are package metadata vs environment/system-admin config, and how permissions from multiple installed packages compose.

What it decides

  • Q1/Q2 — Classification & the DEFINITION↔ASSIGNMENT principle. Every primitive (Role, PermissionSet/Profile, ObjectPermission, FLS, RLS, sharing rule, OWD, capabilities, assignment, tenancy, users) is classified metadata / config / hybrid with a one-line rationale and mainstream precedent (Salesforce / ServiceNow / Dataverse / OutSystems). The line is the near-universal one ADR-0066 half-named: definitions travel with the app (metadata); subject bindings + env-specific values stay as config/data. It's recorded on the existing metadata-persistence.managedBy: package|platform|user provenance axis — no new axis (PD Convert to monorepo with scoped packages #8).
  • Q3/Q4 — Composition & package default access. A package ships its own permission sets (Salesforce managed-package shape / "Shape B"), managedBy:'package' + owning packageId, seeded on install by a new bootstrapDeclaredPermissions (the missing sibling of bootstrapDeclaredRoles) that migrates the already-declarable stack.permissions into sys_permission_set. Runtime composes by the existing most-permissive UNION, conflict-free thanks to package-namespaced object api names. The "one shared set with disjoint slices" shape stays an env-admin-only construct. Closes the ADR-0078 inert-metadata gap and operationalizes ADR-0066 D5.
  • Q5/Q6 — Governance & two doors. Package Access door edits only the package's object slice, under draft/publish; env-admin door keeps the cross-package all-objects matrix and assignment, edited live. Two doors, one metadata.

Phased plan (each independently shippable)

  • P0 (objectui) — scope the Access matrix to { packageId } + slice-merge on save; closes the "84 objects" leak and the data-loss trap (mirrors objectui#2197).
  • P1 (framework)PermissionSetSchema.packageId + bootstrapDeclaredPermissions so packages ship default access for their own objects.
  • P2 — env-admin owns cross-package composition + assignment; package door under draft/publish; overlay/mute subtract layer.

Scope

Docs-only (one new ADR file, 351 lines). No code, spec, or runtime changes — the phased implementation is deferred to follow-up PRs per the plan above.

🤖 Generated with Claude Code

https://claude.ai/code/session_01KfK8SFcfhbfgE2WxX8JQDp


Generated by Claude Code

…mposition (#2557)

Draws the model-wide line between authorization primitives that are package
metadata (versioned, shipped, draft/published, portable) and those that are
environment/system-admin config (live, env-specific, bound to real subjects),
and specifies how grants from several installed packages compose.

Key decisions:
- D1/D2 Classification on the DEFINITION↔ASSIGNMENT axis, recorded on the
  existing `metadata-persistence.managedBy` provenance axis (no new axis).
- D3 permission set carries owning `packageId` + per-record `managedBy`.
- D4 composition = package ships its own sets (Shape B); runtime unions;
  shared-slice (Shape A) stays an env-admin-only construct.
- D5 add `bootstrapDeclaredPermissions` to seed `stack.permissions` (closes the
  ADR-0078 inert-metadata gap; operationalizes ADR-0066 D5).
- D6/D7 package Access door = draft/published metadata scoped to the package
  slice; env-admin door = live cross-package matrix + assignment. Two doors,
  one metadata.
- Phased plan P0 (objectui scope + slice-merge), P1 (framework seeding),
  P2 (two doors + overlay/mute subtract).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KfK8SFcfhbfgE2WxX8JQDp
@vercel

vercel Bot commented Jul 4, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
spec Ready Ready Preview, Comment Jul 4, 2026 5:25am

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation size/m labels Jul 4, 2026
@os-zhuang os-zhuang added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Jul 4, 2026 — with Claude
Points readers from this ADR's package-authoring/composition slice to the
whole-lifecycle authorization gap map (P0–P3) tracked in #2561.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KfK8SFcfhbfgE2WxX8JQDp
@os-zhuang
os-zhuang marked this pull request as ready for review July 4, 2026 12:03
@os-zhuang
os-zhuang merged commit b9babda into main Jul 4, 2026
15 checks passed
@os-zhuang
os-zhuang deleted the claude/authorization-model-design-elkcfj branch July 4, 2026 12:03
os-zhuang added a commit that referenced this pull request Jul 16, 2026
…hen + role-gated option (#2559) (#3006)

Adds `showcase_cascade`, a served fixture that exercises the B3 server-side
option-value enforcement (objectql `evaluateOptionVisibility`, #2915 / #1583):

- country → province cascade — `province` declares `dependsOn: ['country']`
  and each option gates itself with a `visibleWhen` CEL predicate. The client
  narrows the offered set; the rule-validator rejects an out-of-set submit
  (`{ field, code: 'invalid_option' }`).
- a role-gated `tier.restricted` option (`'admin' in current_user.positions`),
  enforced server-side the same way.

`public_read_write` (no permission set → absent from the ADR-0090 access-matrix),
wired into the Showcase app's Data Model nav. Verified live against a fresh
backend: POST /api/v1/data/showcase_cascade rejects country=us+province=zj
(400 invalid_option) and accepts country=cn+province=zj (201);
GET /api/v1/meta/object/showcase_cascade serves the per-option visibleWhen to the
client. Drives objectui e2e/live/cascading-options.spec.ts.


Claude-Session: https://claude.ai/code/session_01S91NyYJURiQTKmF9q3AXxg

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants