Found while implementing #8554. This is a record correction plus one judgement case; the clean inheritors the same sweep found are filed separately as #8577, and neither is fixed in #8554's PR (its R5 fixes scope at exactly five objects).
The record being corrected
#8554's triage comment states that this card "plus #8555's explicitly-triaged negative list is now the bounded set, and it is measurement-backed" — offered as the correction to #8468's earlier, weaker "closed set" claim, which had been a source read.
Re-running #8554's own described method — execute lintUnscopedDeclaredIndexes from packages/lint/src/data-model-rules.ts over every loadable *.object.ts, then filter by whether resolveInjectedSystemColumns injects organization_id — does not produce six objects. Measured on origin/main @ bf1ea923e5:
scanned 77 *.object.ts files → 76 loadable definitions (1 unloadable)
RAW rule findings (spelling alone, no tenancy inference): 41
TENANT-SCOPED after the organization_id filter: 20
20, not 6. The described filter (rule verdict × tenancy) is not what produced the five-object table; a further human judgement about which keys are authored names was applied on top, and it was never stated as a step. That undeclared step is where two clean inheritors (#8577) were dropped.
This is not a complaint about the five that were filed — they were right, and they were the highest-value five. It is that the bound is not measurement-backed in the way the record says, so the next sweep should not trust it as a stopping point.
The full measured triage of all 20
The missing step, written down. Three categories, and only the third is the defect class:
A. ADR-0120 S6 hand-written organization composite — valid indefinitely, not defects (7)
| object | declared index |
|---|
sys_metadata_history | [organization_id, event_seq] |
sys_metadata_history | [organization_id, type, name, version] |
sys_metadata | [type, name, organization_id, package_id] |
sys_view_definition | [name, organization_id, owner] |
sys_business_unit | [code, organization_id] |
sys_user_permission_set | [user_id, permission_set_id, organization_id] |
sys_user_position | [user_id, position, organization_id] |
The tenant column is already in the key, so the constraint is per-organization today. This is the group #8468 identified and called valid indefinitely.
B. Key is server-assigned ids, opaque tokens or dedup keys — globally unique by construction (9)
sys_notification [dedup_key] · sys_business_unit_member [business_unit_id, user_id] · sys_migration_journal [run_id, seq] · sys_position_permission_set [position_id, permission_set_id] · sys_share_link [token] · sys_http_delivery [source, dedup_key] · sys_notification_delivery [notification_id, recipient_id, channel] · sys_notification_receipt [notification_id, user_id, channel] · sys_presence [session_id]
No cross-tenant oracle over authored values: an id or token minted by the server cannot be guessed by a tenant, so a 409 on one carries no information about another tenant's naming. ⚠️ These are still bare unique: true and the v18 train (#5082) will require them spelled 'global' explicitly — they are correct today, not finished.
C. The defect class after the same judgement (4) — sys_setting (#8555, judgement), sys_notification_subscription + sys_audience_binding_suggestion (#8577, clean inheritors, measured), and sys_job below.
sys_job — the second #8555-shaped judgement case
packages/platform-objects/src/audit/sys-job.object.ts:
managedBy: 'engine-owned'
name: Field.text({ … description: 'Unique job identifier (snake_case)' })
indexes: [{ fields: ['name'], unique: true }, …]
materialized: uniq_sys_job_name on (name)
org_jia creates 'nightly_rollup' → 201
org_yi creates the SAME name → 409 UNIQUE_VIOLATION
org_yi creates 'only_in_yi' → 201 ← control
The oracle reproduces. But I am not asserting the answer, for the same reason #8555 was filed rather than queued: sys_job is engine-owned and the object describes itself as a "Catalogue of registered background jobs" — rows produced by the scheduler for jobs registered in code at boot, not authored by tenant admins. If the job catalogue is genuinely a property of the deployment rather than of a tenant, then the correct fix is the opposite one — spell it unique: 'global' explicitly so the intent stops being a positional accident (#5082), and correct the field's description, which currently publishes the bare claim.
The reading that settles it: does anything write sys_job rows per organization? If every row comes from a boot-time registration, 'global' is right and the object is tenant-scoped only incidentally. If a tenant can register or shadow a job, it is the #8323 class and inherits the ruling. Both branches resolve without the maintainer once that reading is done, so this is filed withoutpm:queue, exactly as #8555 was, with the semantic argument stated rather than assumed.
⚠️ Whichever branch wins, the bare spelling is not an acceptable end state — that half was never in question (#8468 ruling).
Method note for the next sweep
The rule fires on spelling alone and does no tenancy inference by design (documented on #4698 — an authoring-time guess would be wrong half the time, since organization_id is kernel-injected rather than authored). So the tenancy filter is necessary and not sufficient: category B above is tenant-scoped, bare-spelled, and not a defect. A sweep that reports "rule verdict × tenancy" as its bound will keep over-reporting by ~3x and under-reporting whatever the unstated judgement step drops. The three-category triage above is the shape that actually terminates, and it is now written down so it need not be re-derived.
Related
Filed unassigned. Found by session session_012WMpuAfA2KSdDjGF6tm1bH.
Generated by Claude Code
Found while implementing #8554. This is a record correction plus one judgement case; the clean inheritors the same sweep found are filed separately as #8577, and neither is fixed in #8554's PR (its R5 fixes scope at exactly five objects).
The record being corrected
#8554's triage comment states that this card "plus #8555's explicitly-triaged negative list is now the bounded set, and it is measurement-backed" — offered as the correction to #8468's earlier, weaker "closed set" claim, which had been a source read.
Re-running #8554's own described method — execute
lintUnscopedDeclaredIndexesfrompackages/lint/src/data-model-rules.tsover every loadable*.object.ts, then filter by whetherresolveInjectedSystemColumnsinjectsorganization_id— does not produce six objects. Measured onorigin/main@bf1ea923e5:20, not 6. The described filter (rule verdict × tenancy) is not what produced the five-object table; a further human judgement about which keys are authored names was applied on top, and it was never stated as a step. That undeclared step is where two clean inheritors (#8577) were dropped.
This is not a complaint about the five that were filed — they were right, and they were the highest-value five. It is that the bound is not measurement-backed in the way the record says, so the next sweep should not trust it as a stopping point.
The full measured triage of all 20
The missing step, written down. Three categories, and only the third is the defect class:
A. ADR-0120 S6 hand-written organization composite — valid indefinitely, not defects (7)
sys_metadata_history[organization_id, event_seq]sys_metadata_history[organization_id, type, name, version]sys_metadata[type, name, organization_id, package_id]sys_view_definition[name, organization_id, owner]sys_business_unit[code, organization_id]sys_user_permission_set[user_id, permission_set_id, organization_id]sys_user_position[user_id, position, organization_id]The tenant column is already in the key, so the constraint is per-organization today. This is the group #8468 identified and called valid indefinitely.
B. Key is server-assigned ids, opaque tokens or dedup keys — globally unique by construction (9)
sys_notification [dedup_key]·sys_business_unit_member [business_unit_id, user_id]·sys_migration_journal [run_id, seq]·sys_position_permission_set [position_id, permission_set_id]·sys_share_link [token]·sys_http_delivery [source, dedup_key]·sys_notification_delivery [notification_id, recipient_id, channel]·sys_notification_receipt [notification_id, user_id, channel]·sys_presence [session_id]No cross-tenant oracle over authored values: an id or token minted by the server cannot be guessed by a tenant, so a 409 on one carries no information about another tenant's naming.⚠️ These are still bare
unique: trueand the v18 train (#5082) will require them spelled'global'explicitly — they are correct today, not finished.C. The defect class after the same judgement (4) —
sys_setting(#8555, judgement),sys_notification_subscription+sys_audience_binding_suggestion(#8577, clean inheritors, measured), andsys_jobbelow.sys_job— the second #8555-shaped judgement casepackages/platform-objects/src/audit/sys-job.object.ts:The oracle reproduces. But I am not asserting the answer, for the same reason #8555 was filed rather than queued:
sys_jobisengine-ownedand the object describes itself as a "Catalogue of registered background jobs" — rows produced by the scheduler for jobs registered in code at boot, not authored by tenant admins. If the job catalogue is genuinely a property of the deployment rather than of a tenant, then the correct fix is the opposite one — spell itunique: 'global'explicitly so the intent stops being a positional accident (#5082), and correct the field'sdescription, which currently publishes the bare claim.The reading that settles it: does anything write
sys_jobrows per organization? If every row comes from a boot-time registration,'global'is right and the object is tenant-scoped only incidentally. If a tenant can register or shadow a job, it is the #8323 class and inherits the ruling. Both branches resolve without the maintainer once that reading is done, so this is filed withoutpm:queue, exactly as #8555 was, with the semantic argument stated rather than assumed.Method note for the next sweep
The rule fires on spelling alone and does no tenancy inference by design (documented on #4698 — an authoring-time guess would be wrong half the time, since
organization_idis kernel-injected rather than authored). So the tenancy filter is necessary and not sufficient: category B above is tenant-scoped, bare-spelled, and not a defect. A sweep that reports "rule verdict × tenancy" as its bound will keep over-reporting by ~3x and under-reporting whatever the unstated judgement step drops. The three-category triage above is the shape that actually terminates, and it is now written down so it need not be re-derived.Related
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 · Five more instances of the #8323 class: admin- and user-authored names on tenant-scoped objects still carry installation-wide unique indexes #8554 — the class, its ruling, and the batches.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 first judgement case.truebecomes'global'plus a loud refusal); category B is its largest consumer.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 the spelling at authoring time.Filed unassigned. Found by session
session_012WMpuAfA2KSdDjGF6tm1bH.Generated by Claude Code