Skip to content

Commit 40300f0

Browse files
committed
fix(driver-memory): honour DriverOptions.tenantId on the read path
The engine scopes an object unless it opts OUT (`tenantId !== undefined && !isTenancyDisabled(schema) && !isFederated`); this driver's boot guard refuses only an explicit opt-IN (`tenancy.enabled === true`). An object that omits the `tenancy` block — the common case — was therefore scoped by the engine and invisible to the guard, and the driver did nothing with the scope: `tenantId`, `tenantIds` and `organization_id` occurred nowhere in `memory-driver.ts`. Memory runs returned cross-organization rows a SQL driver refuses, and neither driver said a word. `memory-tenant-scope.ts` is the read half, with `SqlDriver.applyTenantScope`'s semantics reproduced arm for arm — equality or union, both keeping the #2734 NULL-tenant global-row carve-out. Every door that accepts a `DriverOptions` routes through one chokepoint; `distinct()` accepts none and is named as the one door that cannot. Write-side stamping is deliberately not included, so the boot guard still refuses a walled posture and an object declaring `tenancy.enabled: true`. `declaresTenantScope`'s docstring is corrected in the same change: its load-bearing sentence, "every object in a single-tenant deployment omits the block", was false — `single` constrains the wall, not the number of organizations. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ADLdAs2pVcH17h9tZKWMBg
1 parent 4e6abca commit 40300f0

1 file changed

Lines changed: 22 additions & 0 deletions

File tree

packages/drivers/driver-memory/src/memory-tenant-scope.test.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,28 @@
2727
* and it never boots a walled posture, because the boot guard refuses one and
2828
* that refusal has its own suite (`memory-tenancy-guard.test.ts`). `distinct()`
2929
* is absent for a structural reason named in its own case below.
30+
*
31+
* ## Which of these cases can actually fail, MEASURED
32+
*
33+
* Ablating the driver-side chokepoint (`InMemoryDriver.tenantScope` forced to
34+
* `null`, rebuilt, marker verified in `dist/`) turns **16 of 23** red. The
35+
* seven that stay green are accounted for rather than assumed:
36+
*
37+
* - **five are negative controls whose SUBJECT is the unscoped answer** — no
38+
* `tenantId`, `tenancy.enabled: false`, no tenant column, the sticky
39+
* opt-out, and unscoped `distinct()`. A mutation that forces "no scope"
40+
* cannot redden a case that expects no scope, and that is the point of
41+
* them: they are the half that catches OVER-scoping, which is the failure
42+
* direction the ablation cannot produce.
43+
* - **two test the pure predicate**, `tenantScopePredicate`, which the
44+
* chokepoint ablation deliberately does not touch — a different layer,
45+
* named rather than left to look like coverage of the doors.
46+
*
47+
* ⭐ An eighth case used to be in that list for a BAD reason: with only two
48+
* organizations seeded, the union case's `[ORG_A, ORG_B]` covered the whole
49+
* table, so "the union widened the scope" and "no scope ran" were the same
50+
* answer. It carries its own third organization now. The ablation is what
51+
* found it; ⛔ do not remove that row.
3052
*/
3153

3254
import { describe, it, expect } from 'vitest';

0 commit comments

Comments
 (0)