Skip to content

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

Description

@os-zhuang

Found while implementing #8468, sweeping every platform object with the real lint rule. Filed unassigned and deliberately separate from #8554 (the five clean inheritors), because the ruling of 2026-08-13 says an instance goes straight to the queue unless it carries a genuine semantic argument for an installation-wide namespace, which must then be stated explicitly. This one does, so it is stated below rather than queued.

The declaration

packages/platform-objects/src/system/sys-setting.object.ts:

indexes: [// Primary lookup path: (namespace, key, scope, user_id?) is what{fields: ['namespace','key','scope','user_id'],unique: true},
...
]

Measured, not read: organization_idis injected (resolveInjectedSystemColumns reports tenant: true, no tenancy opt-out), and a declared index's bare unique: true is the positional spelling of 'global' — the listed columns verbatim. So the physical key is installation-wide on a tenant-scoped object, and organization_id is not part of it.

Why it is not simply a sixth inheritor

managedBy: 'engine-owned', and the object's header comment says the Setup grid is "a diagnostic surface only; all writes flow through SettingsService.set() so the resolver stays the single source of truth." So the rows are not admin-authored in the sense the ruling used, and the settings resolver may legitimately want one installation-wide row per (namespace, key, scope, user_id) — a platform default that every organization resolves against.

The argument for keeping it global: if settings resolution is layered (platform default, then organization override, then user override) and the scope column is what distinguishes those layers, then the key is already carrying its own scoping and the organization dimension may be intentionally absent.

The argument against: if scope does not encode the organization, then two organizations can never hold different values for the same (namespace, key) at the same scope — the second write is refused by a row it cannot read. That is both a cross-tenant oracle and a hard functional ceiling on per-organization configuration, which is a capability a multi-tenant platform is normally expected to have.

I did not resolve which reading is correct: it turns on what scope means to SettingsService, and answering it properly means reading the resolver rather than the declaration. That is the work this card is asking for, and it is why I am not asserting a defect.

What to do

Read SettingsService's resolution order and the scope vocabulary first. Then either:

Either way the end state should not be a positional default. Whoever takes it should run the live 409/201 probe first, per the ruling's discipline on #8468.

Also worth a look, same sweep, not asserted

These carry a bare declared unique: true on tenant-scoped objects but are plausibly intentional (identifiers/tokens that are meant to be globally unique, or composites of already-unique ids). Listing them so a future sweep does not have to re-derive the triage:

  • sys_job['name'] — engine-owned; job names are probably platform-defined.
  • sys_notification['dedup_key'], sys_http_delivery['source', 'dedup_key'] — dedup keys likely already embed their scope.
  • sys_audience_binding_suggestion['package_id', 'permission_set_name', 'anchor'] — package-scoped suggestion rows.
  • sys_notification_subscription['topic', 'principal'] — the one I am least sure about in this group.
  • sys_share_link['token'], sys_presence['session_id'] — tokens/ids, legitimately global.

Objects that already list organization_id in their index fields (sys_metadata, sys_metadata_history, sys_view_definition, sys_user_permission_set, sys_user_position, sys_business_unit) are the ADR-0120 S6 hand-written composite and are valid indefinitely — not findings.

Related

Found by session session_012WMpuAfA2KSdDjGF6tm1bH.


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