Skip to content

driver-memory: a partial syncSchema re-registration silently flips a platform-global object's UNIQUE partition from global to per-organization — the explicit tenancy.enabled: false opt-out is not sticky here the way it is in driver-sql #16729

Description

@os-musk

Found while implementing #16589 (read-side tenant scope for the in-memory driver). Separate concern, separate path, filed rather than folded in.

The asymmetry

SqlDriver keeps a sticky record of an explicit tenancy.enabled: false opt-out — tenantOptOutByTable, maintained by computeAndRecordTenantField. Its docblock states the reason verbatim:

A schema that carries a tenancy declaration is authoritative: it sets or clears the opt-out and is computed normally. A schema WITHOUT one (partial re-registration — e.g. the lifecycle archive path passes only { name, fields } to syncSchema) preserves a previously declared opt-out instead of letting the implicit organization_id heuristic re-scope a platform-global table.

driver-memory has no such record. InMemoryDriver.syncSchema recomputes the uniqueness constraints from whatever schema this call happens to carry, through tenantFieldOf in packages/drivers/driver-memory/src/memory-unique-constraint.ts, which reads the passed schema and nothing else. A second syncSchema for the same object without a tenancy block therefore falls through to the implicit organization_id heuristic.

Repro — the exported functions, against the built artifact

Run from the repository root after pnpm --filter @objectstack/driver-memory build. Both symbols are published from the package index, so this is the shipped behaviour, not a private detail:

node --input-type=module -e "
import { uniqueConstraintsFromFields, tenantFieldOf } from './packages/drivers/driver-memory/dist/index.mjs';
const full = { name: 'sys_license', fields: { id: {type:'string'}, key: {type:'string', unique:true}, organization_id: {type:'string'} }, tenancy: { enabled: false } };
const partial = { name: 'sys_license', fields: full.fields };
console.log('tenantFieldOf(full)   =', JSON.stringify(tenantFieldOf(full)));
console.log('tenantFieldOf(partial)=', JSON.stringify(tenantFieldOf(partial)));
console.log('constraints(full)   =', JSON.stringify(uniqueConstraintsFromFields(full)));
console.log('constraints(partial)=', JSON.stringify(uniqueConstraintsFromFields(partial)));
"

Measured on origin/main at 6ba0db4e0b (@objectstack/driver-memory 17.3.0):

tenantFieldOf(full)   = null
tenantFieldOf(partial)= "organization_id"
constraints(full)   = [{"field":"key","scopeField":null}]
constraints(partial)= [{"field":"key","scopeField":"organization_id"}]

Why it matters

scopeField: null is a GLOBAL uniqueness partition — one key per install, which is what tenancy.enabled: false declares. scopeField: "organization_id" is one key per organization. So after a partial re-registration, a duplicate the declared constraint refuses can land instead, and nothing announces the change: the declaration says global, the enforcement is per-organization. That is the declared-vs-enforced shape Prime Directive #10 forbids, reached by a state change rather than by a missing check — the mirror image of the closed #8323 (tenant-scoped objects getting GLOBAL indexes).

It is silent in both directions. Nothing logs the flip, and the constraint's own refusal message names the field, never the partition it used.

Boundaries, so the next reader does not re-derive them

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions