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

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>

This branch was successfully deployed

1 active deployment
Preview — 1f947d92 Deployed Jul 4, 2026 by vercel[bot]
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