Repository navigation
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
Merged
objectstack-fleet[bot] merged 2 commits intoOct 10, 2026
Conversation
…its kind, and onEmptyApprovers decides an empty one The lifecycle step said an entry that resolves to nobody opens an empty pending_approvers that nothing can move, so the run parks forever. The runtime does neither: a person entry (manager, field) adds no slot, a group entry keeps its type:value literal (a position literal decided by a later holder, any other only by an administrator), and an empty or literal-only slate goes to the node's onEmptyApprovers policy (admin_rescue by default). The queue-filter note now says a GROUP entry falls back to the literal, which the rewrite made the only true reading. Claude-Session: https://claude.ai/code/session_01Q7Fy4uVkvBWgj9CLihdATJ Co-authored-by: Claude <noreply@anthropic.com>
…listed, not "the end" of that section Claude-Session: https://claude.ai/code/session_01Q7Fy4uVkvBWgj9CLihdATJ Co-authored-by: Claude <noreply@anthropic.com>
This was referenced Oct 10, 2026
objectstack-fleet
Bot
deleted the
claude/issue-22584-approvals-empty-slate-docs
branch
October 10, 2026 10:04
This was referenced Oct 10, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #22584
Clause-②: no
The lifecycle step "The request opens, the run pauses" in
content/docs/automation/approvals.mdxsaid: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 by6093154668). Docs only, with no runtime change: the group types keeping their literal is the seat's ruling on #22558.The new text (
:388–:406ata35518bce)What an entry that resolves to nobody leaves on the slate depends on its kind:
manager,field) adds no slot. Anexpressionthat yields no ids adds none either. Auserentry never resolves to nobody, because its value is the slot.position,team,department,org_membership_level) keeps itstype:valueliteral as a slot. So do aqueueentry and anexpressionvalue thatresolveAsre-expands into a group with nobody in it.position:NAMEslot is decided by whoever is staffed into that position later.behaviorthat needs every slot (unanimous) waits on it too.onEmptyApproverspolicy. 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 aposition: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:valueandposition:NAMEbecause GitHub's body sanitizer drops angle-bracket fragments. The rewrite links to the page's ownonEmptyApproverstext and admin-override callout instead of repeating them.What the runtime does, read at
origin/maincc305a3cfcc305a3cfis this branch's base, andb98e984434(the commit the direction names) is its ancestor:git merge-base --is-ancestor b98e984434 HEADexits 0. Every quote below is frompackages/plugins/plugin-approvals/src/approval-service.tsunless another file is named.:958const PERSON_APPROVER_TYPES = new Set(['manager', 'field']);(the generic type annotation is left out here).:2071if (PERSON_APPROVER_TYPES.has(type)) {…return [];usernever falls back.:1995if (type === 'user') {returns its value through OOO delegation.queue::2117return [`${type}:${a.value}`];expression. An empty result normalizes toraw = []and adds no slot. AresolveAsvalue whose expansion is empty keeps a literal::2258slots.push({ id: `${resolveAs}:${key}`, subGroup: key });:3092if (!approvers.some(a => a && !a.includes(':'))) {:3093const emptyPolicy = (input.config as any).onEmptyApprovers ?? 'admin_rescue';failthrowsNO_APPROVERS(:3094).auto_approveopens nothing (:3101).fallbackreplaces the slate when the fallback resolves to a concrete person, and otherwise degrades toadmin_rescue(:3119).admin_rescueopens withpending_approvers: approvers.join(','),(:3237).approver-address.ts:64const POSITION_ADDRESS_PREFIXES = ['position:'] as const;position:address of a held position (actingAddresses,:116).team:,department:,org_membership_level:orqueue:slot is held by no one. Only the admin override decides it.:3366return original.length > 0 && original.every(a => approved.has(a));So a literal on aunanimousslate 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–:679on 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". Sinceb98e984434a 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:
managercallout (:100–:103) and the notification callout (now:480–:483) describe the default policy correctly.position,teamanddepartmentand leaves outorg_membership_level. The rewrite and therolecallout (:63–:67) cover that type.Grep: no other hand-written page repeats "parks forever" or the empty-slate claim
At
a35518bce:All six hits are one claim. It is the
waitEventConfigdescribe atpackages/spec/src/automation/flow.zod.ts:831, whichcontent/docs/references/**generates five rows from. It is about awaitnode declared withoutwaitEventConfig, 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:
Two nearby sentences are not this claim:
content/docs/capabilities/approvals.mdx:20says 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:351describesonEmptyApproverscorrectly.Verification (final HEAD
a35518bce)node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackwith no paths. It took the change set from merge basecc305a3cf(1 path) and derived 40 commands, the same 40 the dispatch named.a35518bce, and each exited 0.--ranreconciliation printed "40 derived, 40 run, 0 NOT-MEASURED, 0 UNRUN".29420c9e6, four gates exited 3 (PREREQUISITE NOT MET).@objectstack/lintand@objectstack/client-reactwere not built. After those named builds the four went green, and then all 40 re-ran on the final HEAD.check:doc-anchorsreads the new in-page link.scripts/ablation-replace.mjsto change the anchor to#acting-on-requests-in-the-consolex(anchor hits 1 → 0, blobb331cd3cea3f→54bbb25e11d3), at committed HEAD29420c9e6.content/docs/automation/approvals.mdx:399 #acting-on-requests-in-the-consolex … renders no heading with id "acting-on-requests-in-the-consolex".b331cd3cea3f) andgit diff HEADis empty.pnpm --filter @objectstack/spec exec vitest run --project repo --maxWorkers=2(the spec project that scanscontent/docs) exited 0 with 54 files / 915 tests passed.29420c9e6, and thea35518bceedit landed while it ran. That edit changes one phrase in one bullet ("listed at the end of" → "whose four values are listed under").Build Docsjob and theTest Coreshards, 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–:18is stale. It says "no Console field, data-API write or auth admin endpoint sets that column" (sys_user.manager_id). ButPOST /api/v1/auth/admin/set-user-managerexists (packages/platform-objects/src/identity/sys-user.object.ts:378, theset_user_managerrow action), and this PR's own page documents it.:376saysorg_membership_level"silently resolves to nobody". The runtime now warns when a graph type expands to nobody, andos lintflags 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.onEmptyApproverstext 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