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
Two MORE clean inheritors of the #8323 class the #8554 sweep missed: sys_notification_subscription and sys_audience_binding_suggestion — both measured live #8577
Found while implementing #8554 (the five-instance batch), by re-running that card's own executed sweep and then probing every tenant-scoped finding instead of eyeballing which ones looked like authored names. Not fixed there — #8554's R5 fixes its scope at exactly five objects, so these are filed rather than folded in.
Per the maintainer ruling of 2026-08-13 on #8468, both objects below are the ruled shape — tenant-scoped, no tenancy opt-out, bare unique: true on a declared index over authored content — so they inherit that answer and go straight to the lane queue. Neither carries a semantic argument for an installation-wide namespace. The judgement cases the same sweep turned up are filed separately.
Both are measured, not read
Real engine (SqlDriver, better-sqlite3), OS_TENANCY_POSTURE=isolated, driving the real shipped declarations loaded from source.
indexes: [{ fields: ['topic', 'principal'], unique: true }, { fields: ['topic'] }]
materialized: uniq_sys_notification_subscription_topic_principal on (topic, principal)
org_jia creates (billing.invoice, role:sales_manager) → 201
org_yi creates the SAME pair → 409 UNIQUE_VIOLATION
org_yi creates (billing.invoice, role:only_yi) → 201 ← control
org_yi creates (billing.invoice, user:u1) → 201 ← control
org_yi GET on the colliding pair → total 0
This is the direct sibling of sys_notification_preference, one of #8554's five: same package, same directory, same ADR-0030 Layer 3, same managedBy: 'system-data', same archetype. Its own header says the rows are "Admin/user-writable DATA on a platform-defined schema: authored from the Setup 'Notification Subscriptions' grid" — admin-authored content by the ruling's own phrase.
principal is role:x / team:x / user:id / a bare user id. Note what that means now: role and position names are per-organization (#8461, #8556), so role:sales_manager denotes a different principal in each organization while colliding on one installation-wide key. And a user in two organizations cannot subscribe to the same topic in both — the exact symptom measured on sys_user_preference in #8323.
indexes: [{ fields: ['package_id', 'permission_set_name', 'anchor'], unique: true }, …]
materialized: uniq_sys_audience_binding_suggestion_79a05fef
on (package_id, permission_set_name, anchor) ← hash-suffixed, name is over 60 chars
org_jia creates (com.acme.crm, sales_readonly, everyone) → 201
org_yi creates the SAME triple → 409 UNIQUE_VIOLATION
org_yi creates (com.acme.crm, other_set, everyone) → 201 ← control
⚠️This one is worse than a naming oracle, and I think it is the most serious finding in the whole #8323 lineage so far. The key is not a name two tenants happen to choose alike — it is package_id + the package's OWN permission-set name + anchor, i.e. exactly the same triple for every tenant that installs the same package. The row is per-tenant by construction (the object's header: rows are produced when a package's declaration is observed, and "resolved when a tenant admin confirms").
So the consequence is not an enumeration side channel, it is a functional dead end on the package-install path: the second and every later organization to install a package silently never gets its binding suggestion row, so its admin is never asked to bind the package's default permission set to the everyone anchor, and its users never receive that set. ADR-0090 D5/D9 exists precisely so this is never auto-bound; the effect here is that for every tenant after the first, it is never bound at all and nothing says so.
This does not depend on #8554 landing — package-declared set names are identical across tenants today. #8554 widens the collision surface (admin-authored set names become per-organization and may now legitimately repeat), but the dead end above is live on main right now.
⚠️ The pin must exercise the deployed-installation migration path, not only a fresh database — respelling changes the index's generated name, so drift otherwise reads as two findings and an operator applying only the safe half keeps the defect while the plan reads as applied.
Check published text for uniqueness claims. Neither object's declaration carries one in its own field text; the generated reference pages should be re-checked at fix time.
⚠️sys_audience_binding_suggestion's replacement index name is hash-suffixed (buildIndexName truncates past INDEX_NAME_MAX = 60), as sys_notification_preference's is in #8554. That is handled by the existing arm — measured — but it is the case where a name-based assumption would hide.
Found while implementing #8554 (the five-instance batch), by re-running that card's own executed sweep and then probing every tenant-scoped finding instead of eyeballing which ones looked like authored names. Not fixed there — #8554's R5 fixes its scope at exactly five objects, so these are filed rather than folded in.
Per the maintainer ruling of 2026-08-13 on #8468, both objects below are the ruled shape — tenant-scoped, no tenancy opt-out, bare
unique: trueon a declared index over authored content — so they inherit that answer and go straight to the lane queue. Neither carries a semantic argument for an installation-wide namespace. The judgement cases the same sweep turned up are filed separately.Both are measured, not read
Real engine (
SqlDriver, better-sqlite3),OS_TENANCY_POSTURE=isolated, driving the real shipped declarations loaded from source.1.
sys_notification_subscription—packages/services/service-messaging/src/objects/notification-subscription.object.tsThis is the direct sibling of
sys_notification_preference, one of #8554's five: same package, same directory, same ADR-0030 Layer 3, samemanagedBy: 'system-data', same archetype. Its own header says the rows are "Admin/user-writable DATA on a platform-defined schema: authored from the Setup 'Notification Subscriptions' grid" — admin-authored content by the ruling's own phrase.principalisrole:x/team:x/user:id/ a bare user id. Note what that means now: role and position names are per-organization (#8461, #8556), sorole:sales_managerdenotes a different principal in each organization while colliding on one installation-wide key. And a user in two organizations cannot subscribe to the same topic in both — the exact symptom measured onsys_user_preferencein #8323.2.
sys_audience_binding_suggestion—packages/plugins/plugin-security/src/objects/sys-audience-binding-suggestion.object.tspackage_id+ the package's OWN permission-set name + anchor, i.e. exactly the same triple for every tenant that installs the same package. The row is per-tenant by construction (the object's header: rows are produced when a package's declaration is observed, and "resolved when a tenant admin confirms").So the consequence is not an enumeration side channel, it is a functional dead end on the package-install path: the second and every later organization to install a package silently never gets its binding suggestion row, so its admin is never asked to bind the package's default permission set to the
everyoneanchor, and its users never receive that set. ADR-0090 D5/D9 exists precisely so this is never auto-bound; the effect here is that for every tenant after the first, it is never bound at all and nothing says so.This does not depend on #8554 landing — package-declared set names are identical across tenants today. #8554 widens the collision surface (admin-authored set names become per-organization and may now legitimately repeat), but the dead end above is live on
mainright now.Fix shape (unchanged from #8461 / #8468 / #8554)
unique: 'organization', spelled explicitly.replace_unique_indexarm covers both unchanged — verified on the five of Five more instances of the #8323 class: admin- and user-authored names on tenant-scoped objects still carry installation-wide unique indexes #8554 without touchingschema-drift.ts, including a 3-column composite and a hash-suffixed replacement name.sys_audience_binding_suggestion's replacement index name is hash-suffixed (buildIndexNametruncates pastINDEX_NAME_MAX = 60), assys_notification_preference's is in #8554. That is handled by the existing arm — measured — but it is the case where a name-based assumption would hide.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 / PR fix(plugin-security,spec): scope sys_position.name uniqueness per organization (#8468) #8556 — the third instance, where the ruling's standing consequence was recorded.sys_setting's unique key is installation-wide on a tenant-scoped object — but unlike the #8323 class it has a real argument for staying that way #8555 —sys_setting, the judgement case.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.Filed unassigned. Found by session
session_012WMpuAfA2KSdDjGF6tm1bH.Generated by Claude Code