Repository navigation
Commit ee3ae03
docs(approvals): an entry that resolves to nobody leaves a slot per its kind, and an empty or literal-only slate goes to onEmptyApprovers (#22657)
Fixes #22584
Clause-②: no
The lifecycle step "The request opens, the run pauses" in
`content/docs/automation/approvals.mdx` said:
> An entry that resolves to nobody is not an error: the request opens
with an empty `pending_approvers` and nothing can move it, so the run
parks forever.
The runtime does neither half of that. This PR replaces the sentence
with a statement per approver kind, following the triage direction on
the card (`6093078259`, confirmed on unlock by `6093154668`). Docs only,
with no runtime change: the group types keeping their literal is the
seat's ruling on #22558.
## The new text (`:388`–`:406` at `a35518bce`)
What an entry that resolves to nobody leaves on the slate depends on its
kind:
- **A person entry** (`manager`, `field`) adds no slot. An `expression`
that yields no ids adds none either. A `user` entry never resolves to
nobody, because its value is the slot.
- **A group entry** (`position`, `team`, `department`,
`org_membership_level`) keeps its `type:value` literal as a slot. So do
a `queue` entry and an `expression` value that `resolveAs` re-expands
into a group with nobody in it.
- A `position:NAME` slot is decided by whoever is staffed into that
position later.
- No person ever holds any other literal, so only an administrator can
decide it. This bullet links to the admin-override callout under "Acting
on requests in the console".
- Beside entries that do resolve, a literal is a slot like any other, so
a `behavior` that needs every slot (`unanimous`) waits on it too.
- **A slate left empty, or holding only literals,** goes to the node's
`onEmptyApprovers` policy. This bullet links to where the policy's four
values are listed. Under the default, `admin_rescue`, the request opens
on that slate and waits for an administrator, or for a holder of a
`position:` literal on it.
The page writes the placeholders as angle-bracket names inside code
spans, as it already does elsewhere. This body writes them as
`type:value` and `position:NAME` because GitHub's body sanitizer drops
angle-bracket fragments. The rewrite links to the page's own
`onEmptyApprovers` text and admin-override callout instead of repeating
them.
## What the runtime does, read at `origin/main` `cc305a3cf`
`cc305a3cf` is this branch's base, and `b98e984434` (the commit the
direction names) is its ancestor: `git merge-base --is-ancestor
b98e984 HEAD` exits 0. Every quote below is from
`packages/plugins/plugin-approvals/src/approval-service.ts` unless
another file is named.
- **Person kinds add no slot.**
- `:958` `const PERSON_APPROVER_TYPES = new Set(['manager', 'field']);`
(the generic type annotation is left out here).
- `:2071` `if (PERSON_APPROVER_TYPES.has(type)) {` … `return [];`
- **`user` never falls back.** `:1995` `if (type === 'user') {` returns
its value through OOO delegation.
- **Every other kind falls through to its literal,** including `queue`:
`:2117` ``return [`${type}:${a.value}`];``
- **`expression`.** An empty result normalizes to `raw = []` and adds no
slot. A `resolveAs` value whose expansion is empty keeps a literal:
`:2258` ``slots.push({ id: `${resolveAs}:${key}`, subGroup: key });``
- **The empty test counts literals as empty.**
- `:3092` `if (!approvers.some(a => a && !a.includes(':'))) {`
- `:3093` `const emptyPolicy = (input.config as any).onEmptyApprovers ??
'admin_rescue';`
- `fail` throws `NO_APPROVERS` (`:3094`).
- `auto_approve` opens nothing (`:3101`).
- `fallback` replaces the slate when the fallback resolves to a concrete
person, and otherwise degrades to `admin_rescue` (`:3119`).
- `admin_rescue` opens with `pending_approvers: approvers.join(','),`
(`:3237`).
- **Who can take a literal.**
- `approver-address.ts:64` `const POSITION_ADDRESS_PREFIXES =
['position:'] as const;`
- The acting addresses are the user id, the account's own email, and the
`position:` address of a held position (`actingAddresses`, `:116`).
- A `team:`, `department:`, `org_membership_level:` or `queue:` slot is
held by no one. Only the admin override decides it.
- **The tally.** `:3366` `return original.length > 0 && original.every(a
=> approved.has(a));` So a literal on a `unanimous` slate must be
approved too, by a staffed holder or by the admin override.
PM mechanism assumptions 1 and 2 held. The one drift is a line number:
the admin-override callout was at `:667`–`:679` on the base, not
`:651`–`:653`, because PR #22625 added lines above it. It is now at
`:685`–`:697`.
## The other line changed on the page
- **`:441`–`:442`.** "`position:finance_manager` — the form an entry
falls back to when it resolves to no users" now reads "a group entry".
Since `b98e984434` a person entry no longer falls back to a literal, so
the rewrite above makes "an entry" false.
No other line on the page is made false by the rewrite. I read these and
left them alone:
- The `manager` callout (`:100`–`:103`) and the notification callout
(now `:480`–`:483`) describe the default policy correctly.
- The admin-override callout is accurate. It names `position`, `team`
and `department` and leaves out `org_membership_level`. The rewrite and
the `role` callout (`:63`–`:67`) cover that type.
## Grep: no other hand-written page repeats "parks forever" or the
empty-slate claim
At `a35518bce`:
```text
$ git grep -n -o -i "parks forever"
content/docs/references/api/automation-api.mdx:157:parks forever
content/docs/references/automation/control-flow.mdx:123:parks forever
content/docs/references/automation/control-flow.mdx:186:parks forever
content/docs/references/automation/flow.mdx:78:parks forever
content/docs/references/automation/flow.mdx:149:parks forever
packages/spec/src/automation/flow.zod.ts:831:parks forever
```
All six hits are one claim. It is the `waitEventConfig` describe at
`packages/spec/src/automation/flow.zod.ts:831`, which
`content/docs/references/**` generates five rows from. It is about a
`wait` node declared without `waitEventConfig`, which is a different
claim and true of a different node. The references tree is generated, so
it is not hand-edited here.
The second grep looks for the empty-slate claim itself across the
hand-written trees, with the generated references excluded:
```text
$ git grep -c -i -E "nothing can move it|empty .?pending_approvers|parks? (forever|indefinitely)" -- content/docs skills docs ":!content/docs/references/**"
(no output, exit 1)
control leg: the same grep at the base cc305a3
cc305a3:content/docs/automation/approvals.mdx:2 (exit 0)
```
Two nearby sentences are not this claim:
- `content/docs/capabilities/approvals.mdx:20` says a manager-only step
"resolves to nobody and the request waits". That is true under the
default policy. A different problem on that page is in the Acceptance
notes.
-
`skills/objectstack-automation/references/state-machines-and-approvals.md:351`
describes `onEmptyApprovers` correctly.
## Verification (final HEAD `a35518bce`)
- **Derived gates.** I ran `node scripts/pm/dispatch-gates.mjs
--commands --repo objectstack-ai/objectstack` with no paths. It took the
change set from merge base `cc305a3cf` (1 path) and derived 40 commands,
the same 40 the dispatch named.
- All 40 ran on `a35518bce`, and each exited 0.
- The `--ran` reconciliation printed "40 derived, 40 run, 0
NOT-MEASURED, 0 UNRUN".
- On the first pass, at `29420c9e6`, four gates exited 3 (PREREQUISITE
NOT MET). `@objectstack/lint` and `@objectstack/client-react` were not
built. After those named builds the four went green, and then all 40
re-ran on the final HEAD.
- **`check:doc-anchors` reads the new in-page link.**
- I used `scripts/ablation-replace.mjs` to change the anchor to
`#acting-on-requests-in-the-consolex` (anchor hits 1 → 0, blob
`b331cd3cea3f` → `54bbb25e11d3`), at committed HEAD `29420c9e6`.
- The gate went red: `content/docs/automation/approvals.mdx:399
#acting-on-requests-in-the-consolex … renders no heading with id
"acting-on-requests-in-the-consolex"`.
- Restore: the blob equals HEAD's (`b331cd3cea3f`) and `git diff HEAD`
is empty.
- **`pnpm --filter @objectstack/spec exec vitest run --project repo
--maxWorkers=2`** (the spec project that scans `content/docs`) exited 0
with 54 files / 915 tests passed.
- It started on `29420c9e6`, and the `a35518bce` edit landed while it
ran. That edit changes one phrase in one bullet ("listed at the end of"
→ "whose four values are listed under").
- It was not re-run on the final HEAD. This is a declared narrowing: the
40-gate union above did re-run there.
- **Not run locally:** CI's `Build Docs` job and the `Test Core` shards,
which are path-scheduled and run in CI's own shell, and the repo-wide
lint. Those runs belong to CI.
## Acceptance notes
- **`content/docs/capabilities/approvals.mdx:17`–`:18` is stale.** It
says "no Console field, data-API write or auth admin endpoint sets that
column" (`sys_user.manager_id`). But `POST
/api/v1/auth/admin/set-user-manager` exists
(`packages/platform-objects/src/identity/sys-user.object.ts:378`, the
`set_user_manager` row action), and this PR's own page documents it.
- This is outside this claim's file surface, and it is a different
defect from this card's. It is reported to the seat, not fixed here.
- **`:376` says `org_membership_level` "silently resolves to nobody".**
The runtime now warns when a graph type expands to nobody, and `os lint`
flags a position name authored there, so "silently" is soft. The rewrite
does not make it false, so it is unchanged. This is an observation only.
- **The `onEmptyApprovers` text sits inside "Approving across
organizations",** although it is not about cross-org routing. The new
link names that section. The text is not moved.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01Q7Fy4uVkvBWgj9CLihdATJ)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent b9f3cab commit ee3ae03
1 file changed
Lines changed: 23 additions & 5 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
383 | 383 | | |
384 | 384 | | |
385 | 385 | | |
386 | | - | |
387 | | - | |
388 | | - | |
| 386 | + | |
| 387 | + | |
| 388 | + | |
| 389 | + | |
| 390 | + | |
| 391 | + | |
| 392 | + | |
| 393 | + | |
| 394 | + | |
| 395 | + | |
| 396 | + | |
| 397 | + | |
| 398 | + | |
| 399 | + | |
| 400 | + | |
| 401 | + | |
| 402 | + | |
| 403 | + | |
| 404 | + | |
| 405 | + | |
| 406 | + | |
389 | 407 | | |
390 | 408 | | |
391 | 409 | | |
| |||
420 | 438 | | |
421 | 439 | | |
422 | 440 | | |
423 | | - | |
424 | | - | |
| 441 | + | |
| 442 | + | |
425 | 443 | | |
426 | 444 | | |
427 | 445 | | |
| |||
0 commit comments