Skip to content

Commit 25413fc

Browse files
os-zhuangclaude
andauthored
chore(release): 破坏性授权变更 changeset 提级 major + v15 升级指南 (#2950)
* chore(release): bump breaking authz changesets to major + v15 upgrade guide The ADR-0095 authz kernel chain carries breaking behavior changes for multi-org deployments (Layer 0 tenant isolation, member write narrowing, cross-tenant write guard, org-admin private-object narrowing, tenant_isolation retirement). Bump the three breaking @objectstack/plugin-security changesets from minor to major; additive changesets (A3/A4/A5/C2/posture/#2936) keep their level and follow the major via dependent bumps. Add content/docs/releases/v15.mdx as the upgrade guide. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019QRUvVfpvSycAHMMF2xTxs * docs: avoid reserved word in v15 release notes (role-word guard) --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 28b7c28 commit 25413fc

5 files changed

Lines changed: 117 additions & 4 deletions

File tree

.changeset/adr-0095-d1-tenant-layer-0.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
"@objectstack/plugin-security": minor
2+
"@objectstack/plugin-security": major
33
---
44

55
ADR-0095 D1: tenant isolation is now **Layer 0** — an independent, always-first,

.changeset/authz-2937-insert-tenant-guard.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
'@objectstack/plugin-security': minor
2+
'@objectstack/plugin-security': major
33
---
44

55
fix(plugin-security): #2937 — Layer 0 insert post-image 租户检查(伪造 organization_id 的用户 insert 现被拒)

.changeset/authz-tenant-write-wall-fix.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
'@objectstack/plugin-security': minor
2+
'@objectstack/plugin-security': major
33
---
44

55
fix(plugin-security): 堵跨租户 UPDATE 写 + org_admin 越 private 租户对象墙(security)

content/docs/releases/meta.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
22
"title": "Releases",
33
"icon": "Tag",
4-
"pages": ["index", "v14", "v13", "v12", "v9", "implementation-status"]
4+
"pages": ["index", "v15", "v14", "v13", "v12", "v9", "implementation-status"]
55
}

content/docs/releases/v15.mdx

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
---
2+
title: v15.0.0
3+
description: Authorization kernel chain (ADR-0095) — tenant isolation becomes an independent Layer 0, a monotonic posture ladder derived from capabilities, cross-tenant write hardening, and record-grained access explanation. Breaking changes affect multi-org deployments only.
4+
---
5+
6+
**Authorization kernel hardening (ADR-0095) + supporting features (tracking framework#2920).**
7+
This release restructures the authorization hot path: tenant isolation moves out
8+
of the business-RLS pass into an independent, always-first **Layer 0**; principal
9+
tiering becomes an explicit, capability-derived **posture ladder**; and the tenant
10+
write wall is closed on the update path. Several **behavior changes affect
11+
multi-org (`tenancy.mode = 'multi'` + `@objectstack/organizations`) deployments
12+
only** — single-org and physically-isolated (env-per-database, ADR-0002) deployments
13+
are unaffected.
14+
15+
Read this before upgrading a multi-org or enterprise-hierarchy deployment.
16+
17+
## Breaking changes
18+
19+
### Multi-org deployments only
20+
21+
- **Cross-tenant read leak closed (Layer 0).** A permissive business RLS policy
22+
(e.g. `status == 'public'`) can no longer OR-widen tenant scope. Tenant isolation
23+
is now AND-composed as the outermost filter (`Layer0(tenant) AND Layer1(business RLS)`);
24+
a foreign-org row a business policy matched is now invisible.
25+
- **Member by-id writes narrow to owner-only.** The previous OR-merge silently
26+
widened `owner_only_writes` back to org-wide, so a member could by-id update/delete
27+
*any* record in their org. Writes are now owner-scoped as authored. **Migration:**
28+
if your deployment intentionally relied on members editing each other's records
29+
org-wide, grant an explicit per-object edit permission set (position-distributed)
30+
where that is wanted — the baseline `member_default` no longer permits it.
31+
- **Cross-tenant writes via `organization_id` blocked (insert + update).** A user
32+
could previously plant a row into another tenant by supplying a forged
33+
`organization_id` on insert, or by reassigning it on update. Both are now denied
34+
for user contexts (Layer 0 post-image check, symmetric across insert/update/bulk);
35+
`organization_id` is effectively immutable outside system context. Default writes
36+
(no `organization_id`) and system-context writes (import, migration, per-org seed
37+
replay) are unaffected.
38+
- **Org admins no longer cross the tenant wall on `private` objects.** The Layer 0
39+
cross-tenant exemption is now gated on a genuine platform-admin capability
40+
(`manage_metadata` / `manage_platform_settings` / `studio.access` / `manage_users`),
41+
not on the `viewAllRecords` / `modifyAllRecords` super-bits that `organization_admin`
42+
also holds. On `access.default: 'private'` tenant objects an org admin is now walled
43+
to their own organization (previously unfiltered). Genuine platform admins are
44+
unaffected; the super-bits continue to drive only the in-org Layer 1 business-RLS
45+
short-circuit (TENANT_ADMIN semantics unchanged).
46+
- **No-active-org writes fail closed.** A write by a principal with no active
47+
organization on a tenant object is now denied.
48+
- **Seeded `tenant_isolation` policy retired.** The wildcard `tenant_isolation`
49+
RLS policy is removed from the `organization_admin` / `member_default` /
50+
`viewer_readonly` sets (isolation is enforced by Layer 0 instead). The
51+
`_self` / `_org` identity-table carve-outs and `owner_only_writes/deletes` are
52+
unchanged; customized seeded sets keep their overlays (ADR-0094).
53+
54+
### Enterprise hierarchy RLS (`@objectstack/security-enterprise`)
55+
56+
- **`unit` scope anchors to the position's business unit.** `unit` / `unit_and_below`
57+
now prefer `sys_user_position.business_unit_id` (the position's BU) over
58+
`sys_business_unit_member` membership. **Deployments that do not populate
59+
`sys_user_position.business_unit_id` see no change** (every user falls back to
60+
membership). Once anchors are populated, a user's `unit` visibility narrows to
61+
their position BU; multiple positions union; expired/inactive positions fall back
62+
to membership.
63+
64+
## Non-breaking hardening (same effective visibility)
65+
66+
- **RLS safety nets recognize canonical `==` (#2936).** The field-existence /
67+
tenancy-disabled nets were inert for `==`-form policies; now recognized. Only
68+
visible effect: on column-less system tables a member's by-id write is now an
69+
explicit fail-closed deny rather than a phantom-column filter — same result.
70+
- **Self-delegation position anchors are subtree-constrained.** A delegated position's
71+
`business_unit_id` must fall within the delegator's own effective anchor — closing a
72+
lateral-visibility escalation the position-anchor change would otherwise have opened.
73+
- **Hierarchy resolver queries are org-scoped (defense in depth).** Owner-id resolution
74+
now filters by organization directly, and private hierarchy-scoped objects that lack
75+
an `organization_id` column fail closed at boot.
76+
77+
## New features (additive)
78+
79+
- **A3** — the `sys_user` record page gains **Permission Sets** (direct grant) and
80+
**Business Units** assignment tabs.
81+
- **A4** — record-level provenance vocabulary unifies to **platform / package / admin**
82+
across `sys_capability` / `sys_permission_set` / `sys_position`, surfaced as a badge;
83+
legacy values self-heal at boot (no destructive migration).
84+
- **A5** — package-level capability declaration API (`defineCapability`): a package's
85+
own capabilities flow into the registry with package provenance.
86+
- **B2/B4** — an explicit, monotonic **posture ladder**
87+
(`PLATFORM_ADMIN > TENANT_ADMIN > MEMBER > EXTERNAL`) derived from capability grants
88+
(never the identity provider's admin designation); an additive, derived, explainable field. Enforcement is
89+
behavior-preserving.
90+
- **C2**`security.explain` extends to **record granularity**: explain why a specific
91+
record is visible/invisible to a user, layer by layer (permission set → position →
92+
sharing → row rule → effective row filter), with Layer 0/1 and posture surfaced in the
93+
Studio Access panel.
94+
- **C1** — hierarchy RLS enterprise package reaches GA (subtree/reporting-chain caching,
95+
customer docs, license gate).
96+
97+
## Upgrade checklist
98+
99+
- **Multi-org:** review any workflow that relies on members editing each other's records
100+
org-wide (grant an explicit edit permission set) and any integration that supplied a
101+
cross-tenant `organization_id` on user-context writes (route it through system context
102+
or a dedicated endpoint).
103+
- **Enterprise hierarchy:** the upgrade itself is inert; plan BU-anchor data before you
104+
begin populating `sys_user_position.business_unit_id`, since `unit` visibility then
105+
narrows to the position BU.
106+
- **Before production:** a multi-org / hierarchy / tenant-write-guard **enterprise
107+
end-to-end smoke test in a licensed environment is still owed** — open-core CI cannot
108+
exercise the multi-org path.
109+
110+
## References
111+
112+
ADR-0095 (kernel chain) · ADR-0090 Addendum (position-anchor) · ADR-0094 (overlay
113+
preservation) · framework#2920 (tracking).

0 commit comments

Comments
 (0)