Skip to content

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

Description

@os-zhuang

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) |

Measured on origin/main 5e53d73d:

target : git grep -c "'manager'"  validate-approval-approvers.ts   ->  0
control: git grep -c "'position'" validate-approval-approvers.ts   ->  4     (the instrument fires)
rule id : :60  export const APPROVAL_APPROVERS_MAY_RESOLVE_EMPTY = 'approval-approvers-may-resolve-empty';

⇒ 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.

What is NOT claimed

Acceptance

  1. The rule fires on a stack that authors { type: 'manager' } — at the same info tier as its position sibling, with a message naming the real remedy (populate sys_user.manager_id via SCIM / import / directory sync; see { type: 'manager' } resolves a column no product surface can write: sys_user.manager_id is refused by the data API and absent from the auth admin endpoints #16678 for why no product surface writes it today).
  2. 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.
  3. 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.
  4. If { type: 'manager' } resolves a column no product surface can write: sys_user.manager_id is refused by the data API and absent from the auth admin endpoints #16678 later gives manager_id a 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)

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions