You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(authz): widen the driver's native tenant scope to the group membership union — ADR-0105 D2 reaches the wire (#3623) (#3631)
The Layer 0 wall correctly compiled organization_id IN accessible_org_ids
under the group posture, but the engine also propagated the active-org
tenantId into DriverOptions unconditionally, and the SQL driver's native
scoping ANDed organization_id = tenantId under the union — collapsing every
group read back to active-org (isolated) reach. Found by the cloud-side
ee-group-showcase dogfood (cloud#880), the first end-to-end boot of group
against a real driver.
- spec: DriverOptions.tenantIds — the union tenant access set. Native
scoping widens reads/updates/deletes/aggregates to IN (...), keeping the
NULL-tenant global-row carve-out (#2734); inserts still stamp from
tenantId (active org = write target, D5). Absent/empty => equality
fallback: fail toward isolation, never toward exposure.
- objectql: setTenancyPostureProvider seam + buildDriverOptions threads
ExecutionContext.accessible_org_ids as tenantIds when the posture is
group.
- driver-sql: applyTenantScope honors tenantIds via whereIn at the single
read-side chokepoint.
- plugin-security: wires the provider at start() — from the enforcement
layer on purpose, so the driver wall only widens while the Layer 0 union
wall enforces above it; embeddings without plugin-security keep equality.
Verified: objectql 1091, driver-sql 293, plugin-security 593, spec 6672
tests green; spec docs regenerated (check:docs in sync); repo grep guards
clean.
Claude-Session: https://claude.ai/code/session_015FebXPaaGrLhGKw1LHPbpL
Co-authored-by: Claude <noreply@anthropic.com>
|**tenantIds**|`string[]`| optional | Union tenant access set (group posture): native read scoping widens to organization_id IN (...); inserts still stamp from tenantId |
98
99
|**timezone**|`string`| optional | Business reference timezone (IANA) for date-dependent generation, e.g. autonumber date tokens |
99
100
|**preserveAudit**|`boolean`| optional | Historical import: keep a supplied updated_at instead of force-stamping now (from ExecutionContext.preserveAudit) |
* [ADR-0105 D2 / #3623] Tenant access SET for union-scoped postures.
43
+
*
44
+
* Under the `group` tenancy posture a caller's read reach is every
45
+
* organization they hold a membership in (`organization_id IN (...)`), not
46
+
* just the active one. The engine threads `ExecutionContext.accessible_org_ids`
47
+
* here so the driver's NATIVE tenant scoping widens to the same union the
48
+
* Layer 0 authorization wall enforces — otherwise the driver's
49
+
* `organization_id = tenantId` equality ANDs under the union and collapses
50
+
* group reads back to active-org reach.
51
+
*
52
+
* Semantics for drivers that implement native scoping:
53
+
* - non-empty array → scope reads/updates/deletes/aggregates with `IN`,
54
+
* keeping any NULL-tenant global-row carve-out the equality path has;
55
+
* - absent or empty → fall back to `tenantId` equality (fail toward
56
+
* isolation, never toward exposure);
57
+
* - insert-side tenant injection ALWAYS uses `tenantId` (the active
58
+
* organization is the write target — ADR-0105 D5).
59
+
*/
60
+
tenantIds: z.array(z.string()).optional().describe('Union tenant access set (group posture): native read scoping widens to organization_id IN (...); inserts still stamp from tenantId'),
61
+
41
62
/**
42
63
* Business reference timezone (IANA name, e.g. `Asia/Shanghai`) for the
43
64
* request, threaded from `ExecutionContext.timezone` (ADR-0053). Drivers that
0 commit comments