Found while implementing #8323 (option 2), sweeping the docs for uniqueness claims at the PM's request. Not fixed there — the ruling of 2026-08-13 named exactly two objects, and this is a third. Filed for triage rather than folded in.
The declaration
packages/plugins/plugin-security/src/objects/sys-position.object.ts:
indexes: [
{ fields: ['name'], unique: true },
{ fields: ['active'] },
],
Structurally identical to the sys_capability instance #8323 just fixed:
|
sys_capability (fixed in #8461) |
sys_position |
| declared index |
{ fields: ['name'], unique: true } |
{ fields: ['name'], unique: true } |
tenancy opt-out |
none → gets organization_id |
none → gets organization_id |
| provenance |
managedBy: 'config', admins extend in Setup |
managedBy: 'config', admins author positions |
On a DECLARED index bare unique: true is the positional spelling of 'global' — the listed columns verbatim — so name is an installation-wide key on a tenant-scoped object. That is the #8323 §1 shape exactly: an organization can POST a position name and read 409-vs-201 to learn whether another organization already holds it, while its own read of that name returns nothing.
The reference docs assert the strong reading as though it were intended — content/docs/references/identity/position.mdx: name … "Unique position name (lowercase snake_case)". That line is generated from the field's describe(), so the spec text says installation-unique too.
What I did and did not establish
That intent question is why this is a separate card and not a line in #8461: if per-organization is right the fix is one word plus the same replace_unique_index migration (already generalized to declared indexes in #8461, so the migration machinery is in place and free); if installation-wide is right the correct fix is the opposite — spell it unique: 'global' explicitly so the intent stops being a positional accident, and correct the describe().
Either way the current state is the one spelling ADR-0120 says states no scope at all, on a tenant-scoped object, which unique/unscoped-declared-index already warns about.
Related
I also swept the other platform objects while here. Most bare declared uniques are legitimately installation-wide (sys_session.token, sys_api_key.key, sys_oauth_*.token, sys_device_code.*) and several already hand-write the organization composite (sys_team ['name','organization_id'], sys_business_unit ['code','organization_id'], sys_member ['organization_id','user_id'] — the ADR-0120 S6 legacy spelling, valid indefinitely). sys_position is the one that looked like the fixed pair rather than like either of those groups.
Filed unassigned. Found by session session_012WMpuAfA2KSdDjGF6tm1bH.
Generated by Claude Code
Found while implementing #8323 (option 2), sweeping the docs for uniqueness claims at the PM's request. Not fixed there — the ruling of 2026-08-13 named exactly two objects, and this is a third. Filed for triage rather than folded in.
The declaration
packages/plugins/plugin-security/src/objects/sys-position.object.ts:Structurally identical to the
sys_capabilityinstance #8323 just fixed:sys_capability(fixed in #8461)sys_position{ fields: ['name'], unique: true }{ fields: ['name'], unique: true }tenancyopt-outorganization_idorganization_idmanagedBy: 'config', admins extend in SetupmanagedBy: 'config', admins author positionsOn a DECLARED index bare
unique: trueis the positional spelling of'global'— the listed columns verbatim — sonameis an installation-wide key on a tenant-scoped object. That is the #8323 §1 shape exactly: an organization can POST a position name and read 409-vs-201 to learn whether another organization already holds it, while its own read of that name returns nothing.The reference docs assert the strong reading as though it were intended —
content/docs/references/identity/position.mdx:name… "Unique position name (lowercase snake_case)". That line is generated from the field'sdescribe(), so the spec text says installation-unique too.What I did and did not establish
organization_idis injected), and that the declared-index bare spelling materializes verbatim — the same mechanism measured and pinned in fix(platform-objects,plugin-security,driver-sql): scope sys_user_preference and sys_capability uniqueness per organization (#8323) #8461.sys_position, and — the real question — whether installation-wide position names are intended. Unlike capabilities, positions carry a hierarchy (parent_id), and it is at least arguable that a deployment wants one global position taxonomy. tenant-scoped objects get GLOBAL unique indexes: 409-vs-201 enumerates other tenants' values, and a user's preferences silently stop persisting in their second org #8323'ssys_capabilityhad ADR-0066 D1's "admins EXTEND the registry" to settle intent; I did not find the equivalent statement for positions, so I am not asserting the answer.That intent question is why this is a separate card and not a line in #8461: if per-organization is right the fix is one word plus the same
replace_unique_indexmigration (already generalized to declared indexes in #8461, so the migration machinery is in place and free); if installation-wide is right the correct fix is the opposite — spell itunique: 'global'explicitly so the intent stops being a positional accident, and correct thedescribe().Either way the current state is the one spelling ADR-0120 says states no scope at all, on a tenant-scoped object, which
unique/unscoped-declared-indexalready warns about.Related
sys_user_preferenceandsys_capability.true→'global'plus a loud refusal); whichever way this card is decided, stating the scope explicitly is what that train wants.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 have caught this at authoring time.I also swept the other platform objects while here. Most bare declared uniques are legitimately installation-wide (
sys_session.token,sys_api_key.key,sys_oauth_*.token,sys_device_code.*) and several already hand-write the organization composite (sys_team['name','organization_id'],sys_business_unit['code','organization_id'],sys_member['organization_id','user_id']— the ADR-0120 S6 legacy spelling, valid indefinitely).sys_positionis the one that looked like the fixed pair rather than like either of those groups.Filed unassigned. Found by session
session_012WMpuAfA2KSdDjGF6tm1bH.Generated by Claude Code