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
Found while implementing #8468 (the third instance), running the real lint rule over every platform object rather than reading declarations by eye. Not fixed there — #8468's scope is sys_position alone. Filed unassigned.
Per the maintainer ruling of 2026-08-13 on #8468, an instance of this shape inherits that answer and goes straight to the lane queue rather than back to the decision box. None of the five below carries a semantic argument for an installation-wide namespace; where one might exist I have filed separately instead (see "Related").
Why this contradicts the "closed set" claim on #8468
The #8468 triage comment stated that the sweep behind it had bounded the rest of the platform's objects, so "whatever is ruled here closes the class for the platform's own metadata rather than leaving a tail." That is not the case. The original sweep was a source read; this one executes lintUnscopedDeclaredIndexes from packages/lint/src/data-model-rules.ts over all 76 loadable *.object.ts definitions and cross-references each finding against resolveInjectedSystemColumns — i.e. the rule's own verdict, filtered by whether organization_id is actually injected.
The rule fires on the spelling alone and deliberately does no tenancy inference, so its raw output includes many legitimately-global better-auth objects. The tenancy filter is what separates them.
The five instances
All are tenant-scoped (organization_id injected, no tenancy opt-out) and declare a bare unique: true on a declared index over authored content — the positional spelling of 'global', i.e. the listed columns verbatim.
sys_permission_set is the strongest case and the most awkward one: it sits in the same directory as the two objects already fixed, and together with sys_capability and sys_position it is the third leg of the ADR-0090 RBAC triad. Its own header comment reads "tenants may add custom rows (created via UI / API) but the schema itself is locked" — the same sentence that made sys_position a defect. Two organizations cannot both have a permission set named sales_readonly.
sys_notification_preference is the near-exact analogue of the already-ruled sys_user_preference: same archetype (a per-user K/V row), same defect shape. Note that sys_user_preference is managedBy: 'system-data' too, so managedBy is not the discriminator — the ruling's phrase is admin-authored content, which is about provenance of the rows, not the management mode of the object.
Expected consequence
The same one measured on #8468 and #8323: a per-value 409 on a row the caller cannot read (a cross-tenant existence oracle over another organization's naming), plus a functional dead end — the second organization simply cannot use the name and the refusal does not say why. For sys_notification_preference the shape matches sys_user_preference's measured symptom instead: a user in two organizations cannot hold independent per-topic toggles.
⚠️Not yet measured live per object.#8468 established that the static read is reliable for this shape by reproducing the oracle on a real engine, but the ruling's own discipline is that the probe comes first. Whoever takes this should run the 409/201 probe per object before migrating, exactly as #8468 did.
Fix shape (unchanged from #8461 / #8468, and free)
⚠️ The pin must exercise the deployed-installation migration path, not only a fresh database. Respelling changes the index's generated name, so on a deployed database drift otherwise reads as two findings — composite missing (safe) and old global index orphaned (destructive) — and an operator applying only the safe half keeps the defect while the plan reads as applied.
Found while implementing #8468 (the third instance), running the real lint rule over every platform object rather than reading declarations by eye. Not fixed there — #8468's scope is
sys_positionalone. Filed unassigned.Per the maintainer ruling of 2026-08-13 on #8468, an instance of this shape inherits that answer and goes straight to the lane queue rather than back to the decision box. None of the five below carries a semantic argument for an installation-wide namespace; where one might exist I have filed separately instead (see "Related").
Why this contradicts the "closed set" claim on #8468
The #8468 triage comment stated that the sweep behind it had bounded the rest of the platform's objects, so "whatever is ruled here closes the class for the platform's own metadata rather than leaving a tail." That is not the case. The original sweep was a source read; this one executes
lintUnscopedDeclaredIndexesfrompackages/lint/src/data-model-rules.tsover all 76 loadable*.object.tsdefinitions and cross-references each finding againstresolveInjectedSystemColumns— i.e. the rule's own verdict, filtered by whetherorganization_idis actually injected.The rule fires on the spelling alone and deliberately does no tenancy inference, so its raw output includes many legitimately-global better-auth objects. The tenancy filter is what separates them.
The five instances
All are tenant-scoped (
organization_idinjected, notenancyopt-out) and declare a bareunique: trueon a declared index over authored content — the positional spelling of'global', i.e. the listed columns verbatim.managedBysys_permission_setplugin-security{ fields: ['name'], unique: true }configsys_sharing_ruleplugin-sharing{ fields: ['name'], unique: true }configsys_webhookplugin-webhooks{ fields: ['name'], unique: true }configsys_email_templateplatform-objects{ fields: ['name', 'locale'], unique: true }configsys_notification_preferenceservice-messaging{ fields: ['user_id', 'topic', 'channel'], unique: true }system-datasys_permission_setis the strongest case and the most awkward one: it sits in the same directory as the two objects already fixed, and together withsys_capabilityandsys_positionit is the third leg of the ADR-0090 RBAC triad. Its own header comment reads "tenants may add custom rows (created via UI / API) but the schema itself is locked" — the same sentence that madesys_positiona defect. Two organizations cannot both have a permission set namedsales_readonly.sys_notification_preferenceis the near-exact analogue of the already-ruledsys_user_preference: same archetype (a per-user K/V row), same defect shape. Note thatsys_user_preferenceismanagedBy: 'system-data'too, somanagedByis not the discriminator — the ruling's phrase is admin-authored content, which is about provenance of the rows, not the management mode of the object.Expected consequence
The same one measured on #8468 and #8323: a per-value
409on a row the caller cannot read (a cross-tenant existence oracle over another organization's naming), plus a functional dead end — the second organization simply cannot use the name and the refusal does not say why. Forsys_notification_preferencethe shape matchessys_user_preference's measured symptom instead: a user in two organizations cannot hold independent per-topic toggles.Fix shape (unchanged from #8461 / #8468, and free)
unique: 'organization'.replace_unique_indexmigration arm generalized to declared indexes by fix(platform-objects,plugin-security,driver-sql): scope sys_user_preference and sys_capability uniqueness per organization (#8323) #8461 covers these unchanged — verified onsys_positioninsys_position.nameis the third instance of the #8323 class: an admin-authored name on a tenant-scoped RBAC object carries an installation-wide unique index #8468 with no modification toschema-drift.ts.sys_position.nameis the third instance of the #8323 class: an admin-authored name on a tenant-scoped RBAC object carries an installation-wide unique index #8468 had to (describe()plus the generated reference page).Related
sys_user_preferenceandsys_capability.sys_position.nameis the third instance of the #8323 class: an admin-authored name on a tenant-scoped RBAC object carries an installation-wide unique index #8468 — the third instance (sys_position), where the ruling's standing consequence was recorded.truebecomes'global'plus a loud refusal).unique: trueon a tenant-scoped object under group/isolated postures — point authors at the explicit 'organization' | 'global' choice #8379 — the publish-time advisory that would catch this at authoring time.Found by session
session_012WMpuAfA2KSdDjGF6tm1bH.Generated by Claude Code