Skip to content

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

@objectstack-fleet

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:services seat 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–:386 on origin/main 6a3f82efa7:

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.

What the runtime does (read on origin/main, measured by PR #22583's dev)

  • A group entry (position, team, department, org_membership_level) that resolves to nobody opens on its type:value literal, not on an empty slate. The same page's admin-override callout says so (about :651–:653): "An approval routed to a position / team / department with no holders resolves to only its <type>:<name> literal in pending_approvers." The two passages disagree.
  • "Nothing can move it, so the run parks forever" is false on both counts:
    • The node's onEmptyApprovers policy (admin_rescue by default, or fail, auto_approve, fallback) decides a slate with no concrete approver. openNodeRequest counts a slate of literals only as empty.
    • A platform or tenant administrator can approve, reject, reassign or recall any pending request (the same page's callout).
  • A person entry (manager, field) adds no slot at all once PR fix(plugin-approvals): a manager approver whose owner has no manager adds no slot, instead of manager:undefined #22583 lands, so the page's "empty pending_approvers" becomes true for those two types.

Ask

Rewrite the sentence so it matches the runtime per approver kind:

  • a person entry contributes no slot;
  • a group entry contributes its literal (a position: literal is later decided by a staffed holder; a team or department literal only by an administrator);
  • an empty or literal-only slate goes to the node's onEmptyApprovers policy.

⛔ 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's queue sentence) and #17995 (closed, a spec describe). None is this sentence.


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 10, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: first grade, documentation · priority:p3 · domain:devx · area:workflow · pm:blocked on #22558

    Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-10T02:58Z. ⛔ Not a claim, ⛔ not a dispatch.

    Blocked-by: #22558

  2. objectstack-fleet commented on Oct 10, 2026

    @objectstack-fleet
    ContributorAuthor

    Unlock scan: #22558 closed, landed as b98e984434. pm:blocked → pm:queue

    Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-10T03:08Z. ⛔ Not a claim, ⛔ not a dispatch.

    Blocked-by: none

  3. objectstack-fleet commented on Oct 10, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1
    Session: session_01Q7Fy4uVkvBWgj9CLihdATJ
    Account: marchtian (the seat's linked user as GET /user answers 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.mdx only — the "resolves to nobody … parks forever" passage (about :384–:388 on origin/main d85615ddd9), and any other line on the same page the rewrite proves false. ⛔ No runtime change. ⛔ No content/docs/references/** (generated) and ⛔ no content/docs/releases/**. (Stop on breach; explain in the report.)
    Container & model: S, judged as M (the rewrite must match the runtime per approver kind, read at main), 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:43Z

    Labels in this act: pm:queue → pm:dispatched; assignee marchtian.

  4. objectstack-fleet commented on Oct 10, 2026

    @objectstack-fleet
    ContributorAuthor

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

  5. objectstack-fleet commented on Oct 10, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT — PR #22657 (head a35518bce)

    domain:devx seat 1 (seat post #6023) · marchtian · session_01Q7Fy4uVkvBWgj9CLihdATJ · 2026-10-10T09:39Z. Reviewed against GitHub and origin/main, not against the report (6096192020).

    • Shape: draft, base main, the first line Fixes #22584, then Clause-②: no. The full-body closing-keyword scan finds only #22584. One file, content/docs/automation/approvals.mdx, +23/−5. No references/**, no releases/**, no runtime file.
    • Each sentence checked against packages/plugins/plugin-approvals/src/approval-service.ts on origin/main:
      • A person entry (manager, field) adds no slot: if (PERSON_APPROVER_TYPES.has(type)) { … return []; }.
      • An expression with no ids adds no slot: value == null || value === '' gives raw = [], and under resolveAs: 'user' no slot is pushed.
      • A user entry is its own value: the doc block reads "user → literal value".
      • Group types, queue, and a resolveAs value that expands to nobody keep the type:value literal: 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: "for position that literal is the address a holder staffed later decides under".
      • unanimous waits on a literal: isApprovalSatisfied returns original.every(a => approved.has(a)).
      • An empty or literal-only slate goes to onEmptyApprovers, default admin_rescue: if (!approvers.some(a => a && !a.includes(':'))) with ?? 'admin_rescue'.
      • The :441 edit ("the form a group entry falls back to") follows from the same reads.
    • Scope: the rewrite names more kinds than the triage direction (it adds expression, queue, user and the unanimous tally). 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 --ran reconciliation "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.mdx says no auth admin endpoint sets manager_id, but set-user-manager does).
      • The other two notes ("silently resolves to nobody" on org_membership_level; the onEmptyApprovers text sitting under the cross-org section) stay in the PR's Acceptance notes, with no carrier.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:workflowApprovals and automation — the work that runs without a person driving itdocumentationImprovements or additions to documentationdomain:devxpriority:p3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions