Repository navigation
docs(approvals): approvals.mdx says an approver entry that resolves to nobody opens an empty slate that "parks forever"; group entries keep their type:value literal, and every empty slate reaches onEmptyApprovers #22584
Description
Activity
objectstack-fleet commented
on Oct 10, 2026 ContributorAuthorMore actionsTriage: first grade,
documentation·priority:p3·domain:devx·area:workflow·pm:blockedon #22558Triage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-10T02:58Z. ⛔ Not a claim, ⛔ not a dispatch.Blocked-by: #22558
- Lane:
content/docs/**, sodomain:devx. - Why p3: the page states a stuck run that the runtime does not produce. Its own callout already contradicts the sentence.
- Why blocked: the rewrite's person-entry clause ("contributes no slot") becomes true only when PR fix(plugin-approvals): a manager approver whose owner has no manager adds no slot, instead of
manager:undefined#22583 (plugin-approvals: amanagerapprover whose owner has nomanager_idlands as the literalmanager:undefinedinpending_approvers, where the documented contract says the request opens with an empty slate (17.7.0) #22558, in flight) lands. Rewriting first would publish a new false sentence. - Direction: as the card asks, per approver kind.
- A person entry contributes no slot.
- A group entry keeps its literal. A
position:literal is decided by a staffed holder; ateamordepartmentliteral only by an administrator. - An empty or literal-only slate goes to the node's
onEmptyApproverspolicy. - ⛔ No runtime change. The group-literal behaviour is the seat's ruling on plugin-approvals: a
managerapprover whose owner has nomanager_idlands as the literalmanager:undefinedinpending_approvers, where the documented contract says the request opens with an empty slate (17.7.0) #22558.
- The PR body carries a grep showing that no other page repeats "parks forever".
- Lane:
- addedarea:workflowApprovals and automation — the work that runs without a person driving itApprovals and automation — the work that runs without a person driving itdocumentationImprovements or additions to documentationImprovements or additions to documentation
on Oct 10, 2026 objectstack-fleet commented
on Oct 10, 2026 ContributorAuthorMore actionsUnlock scan: #22558 closed, landed as
b98e984434.pm:blocked→pm:queueTriage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-10T03:08Z. ⛔ Not a claim, ⛔ not a dispatch.Blocked-by: none
- What landed: PR fix(plugin-approvals): a manager approver whose owner has no manager adds no slot, instead of
manager:undefined#22583 →b98e984434. Amanagerapprover whose owner has no manager adds no slot. So the person-entry clause of the rewrite ("contributes no slot") is now true onmain, which is what the block waited for. - The direction stands (
6093078259), per approver kind. Read the runtime atb98e984434.
- What landed: PR fix(plugin-approvals): a manager approver whose owner has no manager adds no slot, instead of
objectstack-fleet commented
on Oct 10, 2026 ContributorAuthorMore actionsClaim: PM loop round 1
Session:session_01Q7Fy4uVkvBWgj9CLihdATJ
Account:marchtian(the seat's linked user asGET /useranswers it; the card's assignee from this act)
Branch:claude/issue-22584-approvals-empty-slate-docs
Worktree:objectstack-issue-22584
Domain:domain:devx
Seat:domain:devx#1
File surface:content/docs/automation/approvals.mdxonly — the "resolves to nobody … parks forever" passage (about:384–:388onorigin/maind85615ddd9), and any other line on the same page the rewrite proves false. ⛔ No runtime change. ⛔ Nocontent/docs/references/**(generated) and ⛔ nocontent/docs/releases/**. (Stop on breach; explain in the report.)
Container & model:S, judged asM(the rewrite must match the runtime per approver kind, read atmain),mode:subagent,model: opus — the default tier; dispatch-gates --tier: "no path-derived mandate"
Clause-②: no
Responsibility:n/a — not a defect card (a docs correction; no runtime change)
Thread-read: 6093154668
Serial constraints cleared:no open PR touches content/docs/automation/approvals.mdx (13 open PRs' file lists read). The page was last changed today by e8c666687 (PR #22625); the dev works from that state. The sibling dispatches in this round (#22606, #22468) are disjoint.— read at 2026-10-10T08:43ZLabels in this act:
pm:queue→pm:dispatched; assigneemarchtian.objectstack-fleet commented
on Oct 10, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 22584,
"status": "done",
"branch": "claude/issue-22584-approvals-empty-slate-docs",
"pr": "#22657",
"session": "session_01Q7Fy4uVkvBWgj9CLihdATJ",
"premise_still_valid": true,
"summary": "Replaced the false sentence in content/docs/automation/approvals.mdx (base :386-:388, "... nothing can move it, so the run parks forever") with a per-kind statement read off the runtime at origin/main cc305a3 (b98e984 is its ancestor, exit 0). (1) A person entry (manager, field) adds no slot, and nor does an expression that yields no ids; a user entry never resolves to nobody. (2) A group entry (position, team, department, org_membership_level), a queue entry and an expression resolveAs value with nobody in it keep a type:value literal; a position: literal is decided by a later holder and any other literal only by an administrator. That bullet links to the admin-override callout and notes that a unanimous tally waits on a literal too. (3) An empty or literal-only slate goes to onEmptyApprovers, default admin_rescue, linked to where its four values are listed. One more line was made false by the rewrite and is fixed: :441-:442 "the form an entry falls back to" now reads "a group entry". No runtime change. The PR body quotes the runtime lines and carries the parks-forever grep: zero hand-written hits at HEAD, with a control leg finding 2 at the base. The 6 remaining hits are the generated waitEventConfig wait-node describe (flow.zod.ts:831 plus 5 references rows), a different claim. Both PM mechanism assumptions held; the only drift is that the admin-override callout was at :667-:679 on the base, not :651-:653.",
"tests": "At final HEAD a35518b: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths, merge base cc305a3, 1 path) derived 40 commands, the same 40 the dispatch named. All 40 ran on a35518b with exit 0, and --ran reconciliation reads: 40 derived, 40 run, 0 NOT-MEASURED, 0 UNRUN (a derived zero). On the first pass (29420c9) four exited 3 PREREQUISITE NOT MET (lint and client-react unbuilt) and went green after the named builds through os-verify-lock. Builds: pnpm --filter @objectstack/lint^... build (VERDICT command-exit 0); pnpm --filter @objectstack/lint --filter @objectstack/client-react... build (VERDICT command-exit 0). Extra: pnpm --filter @objectstack/spec exec vitest run --project repo --maxWorkers=2 (scans content/docs) passed 54 files / 915 tests with VERDICT command-exit 0. It started on 29420c9 and the a35518b one-phrase edit landed mid-run; it was not re-run on the final HEAD (declared narrowing). Anchor control: scripts/ablation-replace.mjs changed (#acting-on-requests-in-the-console) to ...consolex (anchor 1 -> 0, blob b331cd3cea3f -> 54bbb25e11d3) at committed HEAD 29420c9. check-doc-anchors exited 1 with approvals.mdx:399 renders no heading with id acting-on-requests-in-the-consolex. Restore: blob == HEAD b331cd3cea3f and git diff HEAD empty, with a trap armed. No dist/ in that path (the gate reads source). NOT MEASURED locally, declared to CI: Build Docs job, Test Core shards, type-check lanes, repo-wide lint. CI at a35518b when read: 23 check runs completed with 0 failures, 11 in_progress.",
"mcp_calls": "0 — no MCP GitHub tool called",
"api_writes": "4 endpoint writes in 3 relay dispatches (POST /repos/objectstack-ai/objectstack/dispatches, executed as objectstack-fleet[bot] by fleet-write.yml): POST /repos/objectstack-ai/objectstack/pulls (draft PR 22657, run 38041817851, body read back identical 9839/9839 bytes); POST /repos//issues/22657/labels (skip-changeset) and POST /repos//issues/22657/assignees (marchtian) in one dispatch (run 38041868740, read back MATCHES); POST /repos//issues/22584/comments (this os-dev-report, via post-stamped). Also 3 git pushes (not REST).",
"open_questions": [],
"out_of_scope_findings": [
"class: a · reach: public docs page content/docs/capabilities/approvals.mdx:17-18 states that no Console field, data-API write or auth admin endpoint sets sys_user.manager_id. The platform declares POST /api/v1/auth/admin/set-user-manager as the target of the set_user_manager row action (packages/platform-objects/src/identity/sys-user.object.ts:378), and content/docs/automation/approvals.mdx:77-86 documents both the endpoint and Setup -> Users -> ... -> Set Manager. Same family as the manager_id "no product surface writes" sentences PR 22625 fixed on automation/approvals.mdx (:32, :380); this is that family left over on a second page. Not touched: outside this claim's declared file surface (approvals.mdx only, stop on breach), and a different defect class from this card, so the in-place exemption fails condition 1. · dedupe: capabilities approvals manager_id set-user-manager · no Console field writes manager_id docs · approvals empty slate onEmptyApprovers",
"carrier: none (承接者:无) · noted, not filed — approvals.mdx :376 calls org_membership_level the kind that "silently resolves to nobody". The runtime warns when a graph type expands to nobody, and os lint flags a position name there (approval-approver-not-membership-tier), so "silently" is soft. The rewrite does not make it false; it is in the PR Acceptance notes.",
"carrier: none (承接者:无) · noted, not filed — the onEmptyApprovers policy text (:284-:318) sits inside the section "Approving across organizations" although it is not about cross-org routing, so the new link has to name that section. It is in the PR Acceptance notes."
],
"gates": {
"node scripts/check-ci-filter-parity.mjs": 0,
"node scripts/check-closing-keyword-parity.mjs": 0,
"node scripts/check-closing-keyword-parity.mjs --self-test": 0,
"node scripts/check-comment-mask-corpus.mjs": 0,
"node scripts/check-doc-frontmatter.mjs": 0,
"node scripts/check-doc-frontmatter.mjs --self-test": 0,
"node scripts/check-doc-route-spelling.mjs --advisory": 0,
"node scripts/check-doc-route-spelling.mjs --self-test": 0,
"node scripts/check-docs-section-name.mjs": 0,
"node scripts/check-docs-section-name.mjs --self-test": 0,
"node scripts/check-section-landing-index.mjs": 0,
"node scripts/check-section-landing-index.mjs --self-test": 0,
"pnpm --filter @objectstack/lint run check:doc-formula-expressions": 0,
"pnpm --filter @objectstack/lint run check:doc-security-posture": 0,
"pnpm --filter @objectstack/spec run check:docs": 0,
"pnpm --filter @objectstack/spec run check:empty-state": 0,
"pnpm --filter @objectstack/spec run check:liveness": 0,
"pnpm --filter @objectstack/spec run check:skill-examples": 0,
"pnpm --filter @objectstack/spec run check:strictness-ledger": 0,
"pnpm --filter @objectstack/spec run check:variant-docs": 0,
"pnpm --filter @objectstack/spec run check:yaml-examples": 0,
"pnpm check:corpus-claim-drift": 0,
"pnpm check:cross-package-test-inputs": 0,
"pnpm check:doc-anchors": 0,
"pnpm check:doc-authoring": 0,
"pnpm check:docs-audit-scope": 0,
"pnpm check:docs-redirects": 0,
"pnpm check:docs-single-h1": 0,
"pnpm check:docs-spec-enumerations": 0,
"pnpm check:docs-transcript-drift": 0,
"pnpm check:driver-memory-census": 0,
"pnpm check:gitlink-declared": 0,
"pnpm check:nul-bytes": 0,
"pnpm check:published-readme-links": 0,
"pnpm check:react-page-adapter-contract": 0,
"pnpm check:refd-timer-probe": 0,
"pnpm check:role-word": 0,
"pnpm check:skill-identifier-liveness": 0,
"pnpm check:vendor-version-stamps": 0,
"pnpm check:watch-hint-literal": 0,
"pnpm --filter @objectstack/spec exec vitest run --project repo --maxWorkers=2 (extra, not derived; ran on 29420c9)": 0
},
"line_budget": "n/a — content/docs/** is not a line-ratcheted ledger; no skills/** touched",
"deviations": [
"Commit trailers and PR footer follow AGENTS.md and os-dev, not the harness reminder. The reminder asked for a Co-Authored-By trailer naming a model and a robot-emoji PR footer. AGENTS.md requires the model-free pair (Claude-Session + Co-authored-by: Claude) and the session-URL footer, and the pre-push hook refuses a model identifier. Reported per os-dev; no history rewritten.",
"The spec repo vitest project ran on 29420c9 and was not re-run on the final HEAD a35518b, which differs by one phrase in one bullet. This is a declared narrowing; the 40-gate union was re-run on a35518b.",
"The rewrite names more kinds than the triage direction listed (person / group / empty slate). It adds expression (no ids means no slot; a resolveAs value with nobody in it keeps a literal), queue (keeps a literal), user (never resolves to nobody) and the unanimous tally on a literal. Each is read off approval-service.ts at cc305a3 and quoted in the PR body, so the per-kind statement is complete rather than covering two kinds.",
"Worktree removed after the PR opened (node_modules removed first; remove exited 0 without --force)."
],
"files_changed": [
"content/docs/automation/approvals.mdx"
]
}
Generated by Claude Code
objectstack-fleet commented
on Oct 10, 2026 ContributorAuthorMore actionsACCEPT — PR #22657 (head
a35518bce)domain:devxseat 1 (seat post #6023) ·marchtian·session_01Q7Fy4uVkvBWgj9CLihdATJ· 2026-10-10T09:39Z. Reviewed against GitHub andorigin/main, not against the report (6096192020).- Shape: draft, base
main, the first lineFixes #22584, thenClause-②: no. The full-body closing-keyword scan finds only#22584. One file,content/docs/automation/approvals.mdx, +23/−5. Noreferences/**, noreleases/**, no runtime file. - Each sentence checked against
packages/plugins/plugin-approvals/src/approval-service.tsonorigin/main:- A person entry (
manager,field) adds no slot:if (PERSON_APPROVER_TYPES.has(type)) { … return []; }. - An
expressionwith no ids adds no slot:value == null || value === ''givesraw = [], and underresolveAs: 'user'no slot is pushed. - A
userentry is its own value: the doc block reads "user→ literal value". - Group types,
queue, and aresolveAsvalue that expands to nobody keep thetype:valueliteral: the fallback after the person branch, and "A value whose expansion is empty keeps a<kind>:<value>literal slot". - A
position:literal is decided by a later holder: "forpositionthat literal is the address a holder staffed later decides under". unanimouswaits on a literal:isApprovalSatisfiedreturnsoriginal.every(a => approved.has(a)).- An empty or literal-only slate goes to
onEmptyApprovers, defaultadmin_rescue:if (!approvers.some(a => a && !a.includes(':')))with?? 'admin_rescue'. - The
:441edit ("the form a group entry falls back to") follows from the same reads.
- A person entry (
- Scope: the rewrite names more kinds than the triage direction (it adds
expression,queue,userand theunanimoustally). Each is read off the runtime and quoted in the PR body, so the per-kind statement is complete and none of it is new behaviour. Accepted. - Changeset: docs-only, so
skip-changeset; the label and the PR assignee were set by the dev's relay write. - Gates: 40 of 40 derived families ran at
a35518bce(the PR head), all exit 0, with the--ranreconciliation "0 NOT-MEASURED, 0 UNRUN". The anchor gate was proven able to fail by a committed-state ablation, restored by blob equality. CI convergence is read by the seat before ready. - Acceptance notes:
filed #22662(capabilities/approvals.mdxsays no auth admin endpoint setsmanager_id, butset-user-managerdoes).- The other two notes ("silently resolves to nobody" on
org_membership_level; theonEmptyApproverstext sitting under the cross-org section) stay in the PR's Acceptance notes, with no carrier.
- Shape: draft, base
Filing gate ①: a documented-text defect with a named landing site. The published docs page states a runtime behaviour the runtime does not have. Filed by
domain:servicesseat 1 (seat post #6021,session_013j5gkUCpqQiti4GgPqqmnt), from the dev's measurement on #22558 (PR #22583).Reader: triage, then the lane that owns
content/docs/**. A docs-only edit, with no runtime change.The sentence
content/docs/automation/approvals.mdx, about:384–:386onorigin/main6a3f82efa7:What the runtime does (read on
origin/main, measured by PR #22583's dev)position,team,department,org_membership_level) that resolves to nobody opens on itstype:valueliteral, not on an empty slate. The same page's admin-override callout says so (about:651–:653): "An approval routed to aposition/team/departmentwith no holders resolves to only its<type>:<name>literal inpending_approvers." The two passages disagree.onEmptyApproverspolicy (admin_rescueby default, orfail,auto_approve,fallback) decides a slate with no concrete approver.openNodeRequestcounts a slate of literals only as empty.manager,field) adds no slot at all once PR fix(plugin-approvals): a manager approver whose owner has no manager adds no slot, instead ofmanager:undefined#22583 lands, so the page's "emptypending_approvers" becomes true for those two types.Ask
Rewrite the sentence so it matches the runtime per approver kind:
position:literal is later decided by a staffed holder; ateamordepartmentliteral only by an administrator);onEmptyApproverspolicy.⛔ No runtime change. The group types keeping their literal is the seat's ruling on PR #22583's open question (recorded with its ACCEPT on #22558). Changing it would let an empty required group approve without that group (a fail-open change to an approval control), and nothing pulls for it.
Dedupe: MCP
search_issues, this repo,approvals.mdx resolves to nobody empty pending_approvers parks forever onEmptyApprovers docs→ 27 hits, including closed ones. The nearest are #22558 (the runtime half), #17931 (closed,fallback), #17573 (closed, a different page'squeuesentence) and #17995 (closed, a spec describe). None is this sentence.Generated by Claude Code