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 existingmetadata-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

vercelBot commented Jul 4, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
specReadyReadyPreview, CommentJul 4, 2026 5:25am

Request Review

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation size/m labels Jul 4, 2026
@os-zhuangos-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 mainJul 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 freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mskip-changesetPR 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

@os-zhuang@claude