Skip to content

feat(security)!: ADR-0090 P3 — security publish linter (D7), delegated administration (D12), BU assignment anchor - #2711

Merged
os-zhuang merged 2 commits into
mainfrom
claude/adr-0090-p3-linter-delegated-admin
Jul 9, 2026
Merged

feat(security)!: ADR-0090 P3 — security publish linter (D7), delegated administration (D12), BU assignment anchor#2711
os-zhuang merged 2 commits into
mainfrom
claude/adr-0090-p3-linter-delegated-admin

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

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/lintos compile / os lint)

validateSecurityPosture(stack) — pure, spec-only, shared shape with every other lint rule. Error rules gate os compile; each mirrors a runtime enforcement point (ADR-0049: no advisory security):

RuleOrigin
security-owd-unset (error)objectui#2348 leave_request incident
security-owd-alias (error, fix-it)D4 canonical enum
security-external-wider-than-internal (error)D11 external ≤ internal
security-wildcard-vama (error)ADR-0066 superuser wildcard
security-anchor-high-privilege (error)D5/D9 — isDefault (everyone-suggested) sets held to the anchor tier
security-role-word (error)D3 vocabulary freeze (ARIA/page roles exempt)
security-private-no-readscope (info)admin-intent mismatch class

The linter immediately paid for itself: it caught examples/app-crm shipping six objects with no OWD, two more in app-showcase, a role field on showcase_project_membership, and undeclared OWD across the CLI's golden AI-authoring corpus — all fixed here (grandfather stamps per the P1 pattern; roleengagement).

D12 — delegated administration (DelegatedAdminGate, plugin-security)

  • New spec shape PermissionSetSchema.adminScope (businessUnit + includeSubtree + manageAssignments/manageBindings/authorEnvironmentSets + assignablePermissionSets[]), persisted as sys_permission_set.admin_scope, hydrated by the dbLoader and both seed paths.
  • Writes to sys_user_position / sys_position_permission_set / sys_user_permission_set / sys_permission_set are 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).
  • Delegate rules: assignments must be anchored inside the subtree (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_by auto-stamped.
  • Strict containment: granting or authoring a set that itself carries an adminScope requires a held scope that strictly contains it — handing your own exact scope to a peer is refused (no lateral propagation).
  • everyone/guest stay 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 on IHierarchyScopeResolver for enterprise resolver implementations. Capability bits are never BU-scoped.

D9 tightening + D3 sweep

  • describeHighPrivilegeBits moved to @objectstack/spec/security (single definition for lint + runtime; re-exported from plugin-security); new describeAnchorForbiddenBitsguest bindings now also reject edit bits (read-only tier; create stays the case-by-case exception).
  • One-step renames (no aliases, pre-launch): SysRoleSysPosition, SysUserRoleSysUserPosition, SysRolePermissionSetSysPositionPermissionSet; sys_position actions/list views/labels de-roled; sys_business_unit_member.role_in_business_unitfunction_in_business_unit (+ 4-locale label updates).

Out of scope (deliberate)

  • D6 explain engine + access-matrix snapshot gate and the human-approval publish workflow → P4.
  • D10 agent assignment-time ceilings → needs principal-linked user rows (P4; ai_seat is request-time synthesized today).
  • Enterprise hierarchy-scope-resolver depth-anchor implementation lives in the cloud repo (contract documented here).

Verification

  • New: lint 17/17, delegated-admin gate 24/24, anchor tiers 8/8, spec AdminScope 27/27
  • Suites: plugin-security 225, core 315, runtime 463, plugin-sharing 75, platform-objects 71, cli 466 (golden corpus re-baselined with OWD), objectql 804, metadata-core 100, downstream-contract 14
  • Dogfood 39 files / 191 tests green; example-crm + example-showcase + app-todo + embed-objectql compile with the linter active
  • check: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

…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
@vercel

vercelBot commented Jul 9, 2026

Copy link
Copy Markdown

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

ProjectDeploymentActionsUpdated (UTC)
specReadyReadyPreview, CommentJul 9, 2026 6:21am

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 5 package(s): @objectstack/cli, @objectstack/lint, @objectstack/platform-objects, @objectstack/plugin-security, @objectstack/spec.

100 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/agents.mdx(via @objectstack/spec)
  • content/docs/ai/skills-reference.mdx(via packages/cli, @objectstack/spec)
  • content/docs/ai/skills.mdx(via @objectstack/spec)
  • content/docs/api/client-sdk.mdx(via @objectstack/cli, @objectstack/spec)
  • content/docs/api/data-flow.mdx(via @objectstack/cli)
  • content/docs/api/environment-routing.mdx(via @objectstack/cli, @objectstack/spec)
  • content/docs/api/error-catalog.mdx(via @objectstack/cli, @objectstack/spec)
  • content/docs/api/error-handling-client.mdx(via @objectstack/spec)
  • content/docs/api/error-handling-server.mdx(via @objectstack/spec)
  • content/docs/api/index.mdx(via @objectstack/spec)
  • content/docs/automation/approvals.mdx(via packages/spec)
  • content/docs/automation/flows.mdx(via @objectstack/spec)
  • content/docs/automation/hook-bodies.mdx(via packages/cli, packages/spec)
  • content/docs/automation/hooks.mdx(via @objectstack/spec)
  • content/docs/automation/index.mdx(via @objectstack/spec)
  • content/docs/automation/webhooks.mdx(via @objectstack/spec)
  • content/docs/automation/workflows.mdx(via @objectstack/spec)
  • content/docs/concepts/architecture.mdx(via @objectstack/spec)
  • content/docs/concepts/design-principles.mdx(via packages/spec)
  • content/docs/concepts/index.mdx(via @objectstack/spec)
  • content/docs/concepts/metadata-driven.mdx(via @objectstack/spec)
  • content/docs/concepts/metadata-lifecycle.mdx(via packages/spec)
  • content/docs/concepts/north-star.mdx(via packages/spec)
  • content/docs/data-modeling/analytics.mdx(via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx(via @objectstack/spec)
  • content/docs/data-modeling/external-datasources.mdx(via @objectstack/spec)
  • content/docs/data-modeling/field-types.mdx(via @objectstack/spec)
  • content/docs/data-modeling/fields.mdx(via @objectstack/spec)
  • content/docs/data-modeling/formulas.mdx(via @objectstack/spec)
  • content/docs/data-modeling/index.mdx(via @objectstack/spec)
  • content/docs/data-modeling/objects.mdx(via @objectstack/spec)
  • content/docs/data-modeling/queries.mdx(via @objectstack/spec)
  • content/docs/data-modeling/schema-design.mdx(via @objectstack/spec)
  • content/docs/data-modeling/seed-data.mdx(via @objectstack/spec)
  • content/docs/data-modeling/validation-rules.mdx(via @objectstack/spec)
  • content/docs/data-modeling/validation.mdx(via @objectstack/spec)
  • content/docs/deployment/troubleshooting.mdx(via @objectstack/spec)
  • content/docs/getting-started/build-with-claude-code.mdx(via @objectstack/spec)
  • content/docs/getting-started/cli.mdx(via @objectstack/cli, @objectstack/plugin-security, @objectstack/spec)
  • content/docs/getting-started/common-patterns.mdx(via @objectstack/spec)
  • content/docs/getting-started/examples.mdx(via @objectstack/spec)
  • content/docs/getting-started/quick-reference.mdx(via @objectstack/spec)
  • content/docs/getting-started/quick-start.mdx(via @objectstack/spec)
  • content/docs/getting-started/validating-metadata.mdx(via @objectstack/spec)
  • content/docs/kernel/cluster.mdx(via @objectstack/spec)
  • content/docs/kernel/contracts/auth-service.mdx(via packages/spec)
  • content/docs/kernel/contracts/cache-service.mdx(via packages/spec)
  • content/docs/kernel/contracts/data-engine.mdx(via @objectstack/spec)
  • content/docs/kernel/contracts/index.mdx(via @objectstack/spec)
  • content/docs/kernel/contracts/metadata-service.mdx(via packages/spec)
  • content/docs/kernel/contracts/storage-service.mdx(via packages/spec)
  • content/docs/kernel/index.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/data-service.mdx(via packages/cli)
  • content/docs/kernel/runtime-services/email-service.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/index.mdx(via packages/cli, packages/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/sharing-service.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx(via packages/spec)
  • content/docs/kernel/services-checklist.mdx(via @objectstack/spec)
  • content/docs/permissions/access-recipes.mdx(via packages/plugins/plugin-security)
  • content/docs/permissions/authentication.mdx(via @objectstack/cli)
  • content/docs/permissions/authorization.mdx(via @objectstack/lint, packages/plugins/plugin-security, @objectstack/spec)
  • content/docs/permissions/permission-sets.mdx(via packages/plugins/plugin-security, @objectstack/spec)
  • content/docs/permissions/permissions-matrix.mdx(via packages/plugins/plugin-security, @objectstack/spec)
  • content/docs/permissions/profiles.mdx(via @objectstack/spec)
  • content/docs/permissions/roles.mdx(via @objectstack/spec)
  • content/docs/permissions/sharing-rules.mdx(via @objectstack/plugin-security, @objectstack/spec)
  • content/docs/plugins/adding-a-metadata-type.mdx(via @objectstack/spec)
  • content/docs/plugins/development.mdx(via @objectstack/spec)
  • content/docs/plugins/index.mdx(via @objectstack/plugin-security, @objectstack/spec)
  • content/docs/plugins/packages.mdx(via @objectstack/cli, @objectstack/platform-objects, @objectstack/plugin-security, @objectstack/spec)
  • content/docs/protocol/backward-compatibility.mdx(via @objectstack/spec)
  • content/docs/protocol/diagram.mdx(via packages/spec)
  • content/docs/protocol/knowledge.mdx(via @objectstack/spec)
  • content/docs/protocol/objectos/config-resolution.mdx(via @objectstack/spec)
  • content/docs/protocol/objectos/i18n-standard.mdx(via @objectstack/spec)
  • content/docs/protocol/objectos/lifecycle.mdx(via @objectstack/spec)
  • content/docs/protocol/objectos/plugin-spec.mdx(via @objectstack/cli, @objectstack/spec)
  • content/docs/protocol/objectos/realtime-protocol.mdx(via @objectstack/cli)
  • content/docs/protocol/objectos/runtime-capabilities.mdx(via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx(via packages/spec)
  • content/docs/protocol/objectql/query-syntax.mdx(via @objectstack/spec)
  • content/docs/protocol/objectql/schema.mdx(via @objectstack/spec)
  • content/docs/protocol/objectql/security.mdx(via packages/spec)
  • content/docs/protocol/objectql/state-machine.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/actions.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/concept.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/index.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/layout-dsl.mdx(via packages/spec)
  • content/docs/protocol/objectui/record-alert.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx(via @objectstack/spec)
  • content/docs/releases/implementation-status.mdx(via @objectstack/cli, @objectstack/plugin-security, @objectstack/spec)
  • content/docs/releases/index.mdx(via @objectstack/spec)
  • content/docs/releases/v9.mdx(via @objectstack/spec)
  • content/docs/ui/create-vs-edit-form.mdx(via @objectstack/spec)
  • content/docs/ui/dashboards.mdx(via @objectstack/plugin-security, @objectstack/spec)
  • content/docs/ui/forms.mdx(via @objectstack/spec)
  • content/docs/ui/index.mdx(via @objectstack/spec)
  • content/docs/ui/role-based-interfaces.mdx(via packages/plugins/plugin-security)
  • content/docs/ui/setup-app.mdx(via @objectstack/platform-objects, @objectstack/spec)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

Comment threadpackages/plugins/plugin-security/src/security-plugin.ts Fixed
@os-zhuang
os-zhuang marked this pull request as ready for review July 9, 2026 06:30
@os-zhuang
os-zhuang merged commit b271691 into mainJul 9, 2026
17 checks passed
@os-zhuang
os-zhuang deleted the claude/adr-0090-p3-linter-delegated-admin branch 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>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/xlteststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@os-zhuang@github-advanced-security@claude