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
approval-approvers-may-resolve-empty covers position and not manager — the rung whose column no product surface can write is the one the rule stays silent on #16748
Split out of #16678 ("Expected capability", option 2), which offers three ways to close it and says "Any one of these closes it". Option 2 is a coverage gap in a rule that already exists, is measurable in-tree, and is independent of the decision option 1 needs — so it gets its own card rather than sitting behind that decision. #16678 keeps the write-surface half.
⛔ Filed by the triage seat, unassigned. The app-side measurement is the original reporter's (objectstack-ai/hotclm, @objectstack/* 17.3.0); the in-tree readings below are mine.
The gap
packages/lint/src/validate-approval-approvers.ts declares, in its own rule table (:23):
| approval-approvers-may-resolve-empty | info | empty-position dead-end (#3424) |
⇒ the rule reasons about position rungs and says nothing about { type: 'manager' }.
Why the omission is the wrong way round
The rule exists for the empty-position dead-end (#3424): an approver slate that resolves to nobody, with lockRecord: true turning that into a stranded record. { type: 'manager' } has the same failure shape and a worse cause:
rung
why it can resolve empty
can the operator fix it in-product?
{ type: 'position', … }
the position is unstaffed
yes — staff it
{ type: 'manager' }
sys_user.manager_id is unset
⛔ no — see #16678: the data API refuses the column (Editable fields: name, image, locale), the auth admin endpoints do not accept it, and the Console renders no field
Confirmed in-tree: packages/plugins/plugin-auth/src/identity-write-guard.test.ts:186 pins getManagedUpdateWhitelist('sys_user') as exactly {name, image, locale}, and packages/platform-objects/src/identity/sys-user.object.ts:55 states the same narrowing.
⇒ the rule warns about the rung an operator can rescue and stays silent on the rung they cannot. And { type: 'manager' } is, as #16678 puts it, "the canonical first rung of every tiered approval ladder, and what the docs reach for first" — so the silent case is also the common one.
Negative control: a stack authoring { type: 'manager' } where the relevant users do carry manager_id must not fire, if the rule can see that; if it cannot see row data at lint time, the message must say the check is static and say so plainly rather than asserting the slate is empty. ⛔ A lint rule must not claim a runtime fact it did not read.
Regression control: the existing position arm keeps firing on exactly what it fires on today. ⛔ A refactor that generalises both arms must not change the position verdicts.
Related: #16678 (the write-surface half, and the source measurement) · #3424 (closed; the empty-position dead-end this rule was written for) · ADR-0092 (the platform-object write narrowing that refuses the column) · #16679 (the operational escape hatch for a stranded record, and why it is API-only today)
Split out of #16678 ("Expected capability", option 2), which offers three ways to close it and says "Any one of these closes it". Option 2 is a coverage gap in a rule that already exists, is measurable in-tree, and is independent of the decision option 1 needs — so it gets its own card rather than sitting behind that decision. #16678 keeps the write-surface half.
⛔ Filed by the triage seat, unassigned. The app-side measurement is the original reporter's (
objectstack-ai/hotclm,@objectstack/*17.3.0); the in-tree readings below are mine.The gap
packages/lint/src/validate-approval-approvers.tsdeclares, in its own rule table (:23):Measured on
origin/main5e53d73d:⇒ the rule reasons about
positionrungs and says nothing about{ type: 'manager' }.Why the omission is the wrong way round
The rule exists for the empty-position dead-end (#3424): an approver slate that resolves to nobody, with
lockRecord: trueturning that into a stranded record.{ type: 'manager' }has the same failure shape and a worse cause:{ type: 'position', … }{ type: 'manager' }sys_user.manager_idis unsetEditable fields: name, image, locale), the auth admin endpoints do not accept it, and the Console renders no fieldConfirmed in-tree:
packages/plugins/plugin-auth/src/identity-write-guard.test.ts:186pinsgetManagedUpdateWhitelist('sys_user')as exactly{name, image, locale}, andpackages/platform-objects/src/identity/sys-user.object.ts:55states the same narrowing.⇒ the rule warns about the rung an operator can rescue and stays silent on the rung they cannot. And
{ type: 'manager' }is, as #16678 puts it, "the canonical first rung of every tiered approval ladder, and what the docs reach for first" — so the silent case is also the common one.What is NOT claimed
manager_idshould be writable. That is{ type: 'manager' }resolves a column no product surface can write:sys_user.manager_idis refused by the data API and absent from the auth admin endpoints #16678's question and it is deliberately left there — this card is satisfied either way, because a rule that warns is correct whether or not the column later gains a write surface (the warning would simply stop firing once one is populated).{ type: 'manager' }is authored across real stacks. One app is not a population; the grade below does not rest on a rate.positionarm is wrong. It is correct and stays.Acceptance
{ type: 'manager' }— at the sameinfotier as itspositionsibling, with a message naming the real remedy (populatesys_user.manager_idvia SCIM / import / directory sync; see{ type: 'manager' }resolves a column no product surface can write:sys_user.manager_idis refused by the data API and absent from the auth admin endpoints #16678 for why no product surface writes it today).{ type: 'manager' }where the relevant users do carrymanager_idmust not fire, if the rule can see that; if it cannot see row data at lint time, the message must say the check is static and say so plainly rather than asserting the slate is empty. ⛔ A lint rule must not claim a runtime fact it did not read.positionarm keeps firing on exactly what it fires on today. ⛔ A refactor that generalises both arms must not change thepositionverdicts.{ type: 'manager' }resolves a column no product surface can write:sys_user.manager_idis refused by the data API and absent from the auth admin endpoints #16678 later givesmanager_ida product write surface, this rule's message needs the remedy line updated — note that dependency in the code so it is not missed.Related: #16678 (the write-surface half, and the source measurement) · #3424 (closed; the empty-position dead-end this rule was written for) · ADR-0092 (the platform-object write narrowing that refuses the column) · #16679 (the operational escape hatch for a stranded record, and why it is API-only today)