feat(security)!: ADR-0090 P3 — security publish linter (D7), delegated administration (D12), BU assignment anchor#2711
Merged
Conversation
…d administration (D12), BU assignment anchor D7 — validateSecurityPosture in @objectstack/lint, gating os compile and reported by os lint. Seven rules, each with a failing fixture: unset OWD on custom objects (the objectui#2348 leave_request shape), retired OWD aliases (fix-it), external dial wider than internal (D11), wildcard VAMA outside the platform admin set (ADR-0066), high-privilege everyone-suggested sets, the reserved word 'role' in security identifiers/labels (D3), and advisory private-object-read-without-depth. The linter immediately caught real gaps: app-crm shipped six objects with no OWD, app-showcase two, plus a 'role' field on showcase_project_membership and the CLI golden corpus — all fixed in this commit (grandfather stamps + engagement rename), proving the rule set against its own repo. D12 — DelegatedAdminGate in plugin-security. PermissionSetSchema.adminScope (persisted as sys_permission_set.admin_scope) declares WHERE (BU subtree), WHAT (manageAssignments / manageBindings / authorEnvironmentSets) and WHICH sets a delegate may hand out (allowlist). Writes to sys_user_position, sys_position_permission_set, sys_user_permission_set and sys_permission_set are now governed: tenant admins (superuser wildcard) pass to ordinary CRUD/RLS; delegates need a covering scope — subtree-anchored assignments, allowlisted sets only (to others AND themselves), single-row writes, granted_by audit stamping; plain CRUD holders with no scope are denied. Granting/authoring a set that carries an adminScope requires strict containment. everyone/guest anchors stay tenant-level only, and stored assignments to an anchor are rejected for every caller. ADR-0090 Addendum — sys_user_position.business_unit_id lands with its three consumers: D12 delegation boundary (enforced), audit fact, and the depth-anchor contract documented on IHierarchyScopeResolver for enterprise resolvers. D9 tightening — describeHighPrivilegeBits moved to @objectstack/spec/security (shared by lint + runtime, re-exported from plugin-security) plus new describeAnchorForbiddenBits: guest bindings now also reject edit bits. D3 sweep — SysRole→SysPosition, SysUserRole→SysUserPosition, SysRolePermissionSet→SysPositionPermissionSet (one-step, no aliases); sys_position actions/list views/labels de-roled; sys_business_unit_member.role_in_business_unit→function_in_business_unit. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012oLzaP8n7A3YKFmgaHWC8H
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
📓 Docs Drift CheckThis PR changes 5 package(s): 100 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
… (CodeQL) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012oLzaP8n7A3YKFmgaHWC8H
os-zhuang
marked this pull request as ready for review
July 9, 2026 06:30
os-zhuang
added a commit
that referenced
this pull request
Jul 9, 2026
…on Model v2 vocabulary (ADR-0090) (#2717) - roles.mdx → positions.mdx: flat positions (岗位), assignment BU anchor, built-in identity positions, everyone/guest audience anchors; the hierarchy narrative moves to business units. - profiles.mdx → migration tombstone: the Profile concept was removed (D2); maps each former use to everyone-anchor bindings, isDefault suggestions, and position-bound sets. - permission-sets.mdx: rewritten as the single capability container — union semantics, access depth (moved here from profiles), capabilities, built-in sets (additive member_default baseline), governed assignment tables, isDefault suggestion, adminScope delegated administration, package provenance. - sharing-rules.mdx: OWD default corrected to fail-closed private (D1 — the page previously documented the pre-v2 fail-open default), canonical four values only, externalSharingModel dial (D11), recipient types position / unit_and_subordinates / team. - authorization.mdx: position vocabulary, D1/D11 in the enforcement chain, delegated-admin gate in anti-escalation, new explain-engine section (D6), D7 linter + access-matrix snapshot added to governance, ADR-0090 in the index. - permissions-matrix.mdx: role-hierarchy section replaced with business-unit hierarchy & positions; isProfile removed from samples; position recipients. - permission-metadata.mdx: isProfile → isDefault/adminScope in the field table; union-semantics section replaces Profile-vs-Set; current_user.positions. - index.mdx: five-concepts overview, v2 implementation-status callout, best practices and example updated. - access-recipes.mdx / field-level-security.mdx: link + heading fixes. Authoritative reference: docs/design/permission-model.md; decision record: ADR-0090 (P1 #2697, P2 #2708, P3 #2711, P4 #2716). Claude-Session: https://claude.ai/code/session_012oLzaP8n7A3YKFmgaHWC8H Co-authored-by: Claude <noreply@anthropic.com>
This was referenced Jul 10, 2026
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Third wave of ADR-0090 (Permission Model v2): the D7 security-domain publish linter and D12 delegated administration, plus the Addendum's assignment-level BU anchor and a D3 vocabulary sweep. Follows #2695 (ADR), #2697 (P1), #2708 (P2).
D7 — security publish linter (
@objectstack/lint→os compile/os lint)validateSecurityPosture(stack)— pure, spec-only, shared shape with every other lint rule. Error rules gateos compile; each mirrors a runtime enforcement point (ADR-0049: no advisory security):security-owd-unset(error)security-owd-alias(error, fix-it)security-external-wider-than-internal(error)security-wildcard-vama(error)security-anchor-high-privilege(error)security-role-word(error)security-private-no-readscope(info)The linter immediately paid for itself: it caught
examples/app-crmshipping six objects with no OWD, two more in app-showcase, arolefield onshowcase_project_membership, and undeclared OWD across the CLI's golden AI-authoring corpus — all fixed here (grandfather stamps per the P1 pattern;role→engagement).D12 — delegated administration (
DelegatedAdminGate, plugin-security)PermissionSetSchema.adminScope(businessUnit+includeSubtree+manageAssignments/manageBindings/authorEnvironmentSets+assignablePermissionSets[]), persisted assys_permission_set.admin_scope, hydrated by the dbLoader and both seed paths.sys_user_position/sys_position_permission_set/sys_user_permission_set/sys_permission_setare now governed: tenant-level admins (ADR-0066 wildcard) pass through to ordinary CRUD/RLS; delegates need a covering scope; plain CRUD on RBAC tables no longer makes a permission administrator (fail closed).business_unit_id), every set reached must be allowlisted (to others AND themselves), bindings require the position's full blast radius inside the subtree, single-row writes only,granted_byauto-stamped.adminScoperequires a held scope that strictly contains it — handing your own exact scope to a peer is refused (no lateral propagation).everyone/gueststay tenant-level only; stored assignments to an anchor are rejected for every caller (anchors are implicit).Addendum — assignment-level BU anchor
sys_user_position.business_unit_id(+ index) with its three sanctioned consumers: D12 boundary (enforced here), audit fact, and the depth-anchor contract documented onIHierarchyScopeResolverfor enterprise resolver implementations. Capability bits are never BU-scoped.D9 tightening + D3 sweep
describeHighPrivilegeBitsmoved to@objectstack/spec/security(single definition for lint + runtime; re-exported from plugin-security); newdescribeAnchorForbiddenBits— guest bindings now also reject edit bits (read-only tier; create stays the case-by-case exception).SysRole→SysPosition,SysUserRole→SysUserPosition,SysRolePermissionSet→SysPositionPermissionSet;sys_positionactions/list views/labels de-roled;sys_business_unit_member.role_in_business_unit→function_in_business_unit(+ 4-locale label updates).Out of scope (deliberate)
ai_seatis request-time synthesized today).hierarchy-scope-resolverdepth-anchor implementation lives in the cloud repo (contract documented here).Verification
example-crm+example-showcase+app-todo+embed-objectqlcompile with the linter activecheck:liveness✓ (adminScope entry, stale isProfile entry removed) ·check:api-surface✓ · changeset: major (plugin-security, platform-objects) / minor (spec, lint, cli)🤖 Generated with Claude Code
https://claude.ai/code/session_012oLzaP8n7A3YKFmgaHWC8H
Generated by Claude Code