Skip to content

Two derivations of "is this object tenant-scoped" disagree on systemFields: false: the wall composes its predicate, the platform withholds the index #8608

Description

@os-zhuang

Filed by the #8459 dev agent, measured while implementing that card's ruling. Unassigned, out of #8459's scope — that card ruled on who declared the column; this is a different axis (which opt-out row was used) and needs its own decision.

The disagreement

Two places answer "is this object tenant-scoped", and they read different declarations:

declarationresolveInjectedSystemColumns().tenant (spec, drives the index)plugin-security's tenancyDisabled (drives the wall)
tenancy.enabled: falsewithholdsdisabled — no wall
systemFields.tenant: falsewithholdsdisabled — no wall
managedBy: 'better-auth'withholdsn/a (no org column exists)
systemFields: falsewithholdsNOT disabled — wall still applies

security-plugin.ts derives it as exactly two clauses:

consttenancyDisabled=(objasany)?.tenancy?.enabled===false||(objasany)?.systemFields?.tenant===false;

systemFields: false — the hard object-level opt-out — is not among them. And Layer 0's other gate is objectFields.has('organization_id'), so an object using the hard opt-out while declaring its own organization_id gets the wall predicate AND-composed onto essentially every read, with no platform index behind it.

Measured

applySystemFields(obj, { multiTenant: true }).indexes, on origin/main plus the #8459 change:

systemFields: false + author-declared organization_id => null
systemFields.tenant: false + author-declared organization_id => null
plain object + author-declared organization_id => [{"fields":["organization_id"]}] (#8459)

Row 2 is correct and deliberate: security agrees the object is not walled, so there is no predicate for an index to serve. Row 1 is the disagreement — the only row where the wall filters on a column the platform declines to index. Same "hottest predicate unindexed" shape #8459 closed, reached by a different route, and not introduced by #8459 (it withheld the index for these objects before that change too, via the injected-column condition).

The security half is asserted from the source above, not measured end to end; the objectql half is measured.

The decision this needs

Which derivation is authoritative for systemFields: false?

A and B differ in observable behaviour, so this is not a pure refactor and should not be self-adjudicated. Note also that they disagree about which side is the defect, which is why no option here is obviously "the fix".

Dedup

Searched: systemFields false hard opt-out tenancyDisabled organization_id tenant wall, tenancyDisabled cache systemFields false not covered security plugin. Zero open neighbours. Nearest related, none a twin: #8459 (whose declaration governs the index), #8323 (tenant-scoped UNIQUE indexes going global — driver DDL normalization), #6810 (moved the tenant index into indexes[]).

Related

#8459 · #8375 · #6810 · #7865 (the convergence map option C would extend) · ADR-0105


Generated by Claude Code

Metadata

Metadata

Assignees

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions