Skip to content

Commit ef18030

Browse files
claude[bot]claude
andauthored
docs(approvals): qualify the manager approver as a directory-sync dependency (#17575)
`{ type: 'manager' }` is the canonical first rung of a tiered approval ladder and the type the docs reach for first, but it resolves `sys_user.manager_id` — a column with no product write surface. An app that authors it passes `validate` and `lint`, then opens its first request with an empty slate and waits forever; with the default `lockRecord: true` the record stays locked. Nothing on the authoring path said so. Three prose additions, no behaviour change: - `content/docs/automation/approvals.mdx` — a `manager` callout beside the existing `position` vs `org_membership_level` one, carrying the remedy text verbatim from `@objectstack/lint`'s `MANAGER_ONLY_REMEDY` + `MANAGER_ONLY_ROUTES` so the two statements cannot drift; a pointer from the "who approves" sentence that first offers the type; and a clause in the resolve-by-kind paragraph, which listed `manager` unqualified next to the kinds it already grades. - `content/docs/capabilities/approvals.mdx` — the capability list offered "the submitter's direct manager" as a peer of styles that are configured in-product. Qualified in place. The routes are stated as measured, not as a promise: a seed or other system-context write is the one route with a writer in-repo; SCIM and directory sync are named as something the deployment's own provisioning supplies, because this platform declares the SCIM `manager` attribute without projecting it and its admin bulk import excludes the column deliberately. Claude-Session: https://claude.ai/code/session_01ToDPcx9AESFubJkDiFMtKW Co-authored-by: claude <noreply@anthropic.com>
1 parent 40098a4 commit ef18030

2 files changed

Lines changed: 39 additions & 3 deletions

File tree

‎content/docs/automation/approvals.mdx‎

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ This is **not** a schedule-only concern, and for approvals it is the common case
2828

2929
### 3. The approval node
3030

31-
The node declares **who approves** (a named user, a position, the submitter's manager, …) and what happens on approve / reject / escalate. The authoritative shape is [`ApprovalNodeConfig` / `ApproverType`](/docs/references/automation/approval); a flow strings the trigger, the approval node, and the post-decision branches together.
31+
The node declares **who approves** (a named user, a position, the submitter's manager, …) and what happens on approve / reject / escalate. Not every kind is configured the same way: the submitter's manager resolves from a column no product surface writes, so read the `manager` callout below before you reach for it. The authoritative shape is [`ApprovalNodeConfig` / `ApproverType`](/docs/references/automation/approval); a flow strings the trigger, the approval node, and the post-decision branches together.
3232

3333
```ts
3434
// Illustrative — see the Approval reference for the exact node schema.
@@ -63,6 +63,29 @@ Authored `type: 'role'` on 15.x? That is the deprecated spelling of `org_members
6363
(ADR-0090 D3): it still resolves, warns at runtime, and is removed in the next major.
6464
</Callout>
6565

66+
<Callout type="warn">
67+
**`manager` is a directory-sync dependency, not something you configure here.**
68+
`{ type: 'manager' }` resolves at runtime from `sys_user.manager_id` on the record's owner, and
69+
that column is not one an operator can fill in from inside the product.
70+
71+
`sys_user.manager_id` has no product write surface — the data API's managed-update whitelist is
72+
`{name, image, locale}`, the auth admin endpoints do not accept the column and the Console
73+
renders no field for it, so it is never populated by editing the user in the Console. On this
74+
platform the column is written by a seed, or by any other system-context write, which bypasses
75+
the managed-update whitelist. SCIM provisioning and directory sync can populate it too, but only
76+
through a provisioning path your own deployment supplies: this platform declares the SCIM
77+
`manager` attribute without projecting it onto the column, and its admin bulk import does not
78+
write it either.
79+
80+
So where `manager_id` is unset the slate resolves to nobody: the request opens with an empty
81+
`pending_approvers` and waits forever, and under the default `lockRecord: true` the record stays
82+
locked with no in-product recovery. Populate the column for everyone who submits this request,
83+
or add a fallback approver that cannot resolve empty — e.g.
84+
`{ type: 'org_membership_level', value: 'owner' }`. `os lint` reports this at authoring time
85+
(`approval-approvers-may-resolve-empty`, `info`) and carries the same remedy text, so the two
86+
cannot drift apart.
87+
</Callout>
88+
6689
Approving is itself a gated action — model "may approve" as a capability (`approve_invoice`) the approver's permission set grants, and gate the approve action's `requiredPermissions` on it so the gate is enforced on **both** the UI and the server (ADR-0066 D4).
6790

6891
## Approval nodes in practice
@@ -303,7 +326,9 @@ hierarchy. `queue` still parses so stored flows keep loading, but it is **not
303326
implemented** by the runtime and is no longer offered for authoring (#3508) —
304327
a queue entry resolves to nobody. Route to a `team`, `department`, or
305328
`position` instead. `field`, `manager`, and `expression` resolve against the record's
306-
**live** state at node entry (#3447). `position`, `department`,
329+
**live** state at node entry (#3447); `manager` additionally depends on
330+
`sys_user.manager_id`, which no product surface writes — see [the `manager`
331+
callout above](#3-the-approval-node). `position`, `department`,
307332
`org_membership_level` and `expression` may additionally name which
308333
organization's directory resolves them — see
309334
[Approving across organizations](#cross-org-approvers). An entry that resolves

‎content/docs/capabilities/approvals.mdx‎

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,18 @@ Discounts, contracts, spend — anything that needs a human "yes" becomes an app
88

99
## Who approves — eight resolution styles
1010

11-
A specific person · everyone holding a position (e.g. sales managers) · the submitter's direct manager · a team · a department (optionally including sub-departments) · organization leadership · **a person named on the record itself** (e.g. the "regional director" field) · a shared queue (first responder claims it).
11+
A specific person · everyone holding a position (e.g. sales managers) · the submitter's direct manager (needs a provisioned reporting chain — see below) · a team · a department (optionally including sub-departments) · organization leadership · **a person named on the record itself** (e.g. the "regional director" field) · a shared queue (first responder claims it).
12+
13+
<Callout type="warn">
14+
**Routing to the submitter's manager needs a reporting chain you provide.** Unlike a position you
15+
staff, a team you add people to, or a field you name on the record, this style does not get its
16+
data from inside the product: it resolves `sys_user.manager_id`, and no Console field, data-API
17+
write or auth admin endpoint sets that column. Your own provisioning fills it — a seed or other
18+
system-context write, or a SCIM / directory-sync path your deployment supplies. Until it is
19+
filled, a manager-only approval step resolves to nobody and the request waits. The exact remedy,
20+
and the fallback that needs no provisioning at all, are in
21+
[Automation → Approvals](/docs/automation/approvals).
22+
</Callout>
1223

1324
## How it passes — four decision modes
1425

0 commit comments

Comments
 (0)