docs(automation): add the fourth onEmptyApprovers member (fallback) to the skill config table and the approvals checklist - #18795
Conversation
…'s config table and the approvals checklist The approval node's empty-slate policy gained a fourth member, `onEmptyApprovers: 'fallback'` paired with `fallbackApprovers` (ApprovalNodeConfigSchema, packages/spec/src/automation/approval.zod.ts). Two carriers still enumerated three of the four: - skills/objectstack-automation/SKILL.md — the `onEmptyApprovers` row of the Node Config table gains `fallback` in the row's own parenthetical register, naming `fallbackApprovers` as its sibling. Token ratchet 12511 -> 12567 of 12768 (headroom 257 -> 201). - docs/qa/platform-checklist/areas/approvals.json — the approvals.approver-resolution-matrix item's empty-slate fork gains the `fallback` leg in its steps, its fork clause and verify, and both source citations; revision 1 -> 2 with a history row, per the checklist's own change register. Semantics are read from the schema docblock and the runtime fork in plugin-approvals (openNodeRequest), not from the card's prose. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gqi43smmqjJ5sUrhfoPeKu
维护者速读(终稿)skills 席 改了什么 — 审批节点的空名单策略 为什么改 — 词表是 spec 声明的( 风险与代价(含回滚) — 纯文档与清单改动,不发布任何 npm 内容( 席位意见 — 荐合。这是「spec 声明 > 实现 > 文档面」的最小对齐:声明已落,文档面补齐,无新语义。 你要做的(一个动作) — Approve 本 PR(或直接人工合并);席位随后按裁定 C 落地。 Generated by Claude Code |
Fixes #18527
Clause-②: no
What
The approval node's empty-slate policy has four members since PR #18525 (card #17931) landed on
main, and two carriers still enumerated three. This PR adds the fourth member —onEmptyApprovers: 'fallback'with its siblingfallbackApprovers— to each carrier in that carrier's own register. Two files, nothing else:git diff --statat2b1d5784e8=docs/qa/platform-checklist/areas/approvals.json | 15 ++++++++-------andskills/objectstack-automation/SKILL.md | 2 +-(9 insertions, 8 deletions; the SKILL.md change is one table row edited in place).The vocabulary, and where the fourth member is read from
Read on
origin/maine5705a8ea(the merge base of this branch), from the schema and its tests — not from the card's prose:packages/spec/src/automation/approval.zod.ts:884—onEmptyApprovers: z.enum(['admin_rescue', 'fail', 'auto_approve', 'fallback']).default('admin_rescue'):886-887.describe(…"'fallback' opens the request on fallbackApprovers instead":868-869docblock:fallback— open the request on {@link ApprovalNodeConfig.fallbackApprovers} instead. The only policy that NAMES people:878-882docblock: The fallback slate is resolved by the SAME resolver asapprovers… A fallback that itself resolves to nobody degrades toadmin_rescue:906-907—fallbackApprovers: z.array(ApprovalNodeApproverSchema).min(1).optional().describe("Approvers the request opens on when onEmptyApprovers is 'fallback'"):891-892docblock: Same shape asapprovers;:897-898: Required iff the policy is'fallback', and refused otherwise — both arms are enforced by {@link checkFallbackApproversPairing} (the refinement at:728-767)packages/spec/src/automation/approval.test.ts:352describe("ApprovalNodeConfigSchema — onEmptyApprovers: 'fallback' + fallbackApprovers"— accepts the pair, refuses'fallback'without a list, refuses an empty list, refuses a list under the default policy.packages/plugins/plugin-approvals/src/approval-service.ts:2782-2836(openNodeRequest) — under'fallback'the declared list goes through the sameexpandApproversas the primary slate; if it lands a concrete person the request opens on it (approvers = fallbackApprovers,:2817) with the warnresolved to no concrete approver — opening on the declared fallbackApprovers per onEmptyApprovers: 'fallback'.(:2805-2807); if it too resolves to nobody the warn is… AND its declared fallbackApprovers resolved to nobody either — falling back to admin_rescue.(:2820-2822) and theadmin_rescuepath follows (:2846-2851).Carrier 1 —
skills/objectstack-automation/SKILL.md:728 (the only enumerating line):635 names the key without enumerating, :653 and :693 are examples that pick
'fail'; only the Node Config table row enumerates. Before:After (the fourth parenthetical, same register; the tail sentence unchanged):
Every clause of the gloss maps to a schema line above (opens on
fallbackApprovers:868; same shape :892; required iff / refused otherwise :898; degrades toadmin_rescue:880-881). No new row forfallbackApprovers, no example, no other line.Token ratchet (
node scripts/check-skills-token-ratchet.mjs, conventionceil(utf8 bytes / 4)): before, one5705a8ea: 「✓ check-skills-token-ratchet: skills/objectstack-automation/SKILL.md is 12511 tokens (ceiling 12768; headroom 257).」 — after, on2b1d5784e8: 「✓ check-skills-token-ratchet: skills/objectstack-automation/SKILL.md is 12567 tokens (ceiling 12768; headroom 201).」 (+56 tokens = 224 bytes; ceiling untouched, no ruling needed). Line readings (theskills/**rule): file 961 → 961 lines; package — all tenSKILL.mdunderskills/— 6145 → 6145 lines. The edited row was already the file's widest line (512 chars); it is now 700 chars.check:doc-authoring,check:skill-frame-syncandcheck:skill-compatibilityare green on it.Carrier 2 —
docs/qa/platform-checklist/areas/approvals.json: the enumeration exists, and it was editedPremise correction to the claim (5721118247), not to the card: the seat's grep found
onEmptyApproversonly at :580 / :631 (prose,'fail'on a showcase stage) and thevariantslist at :825 (the escalation action vocabulary). One5705a8eathe file has 15 hits, and the itemapprovals.approver-resolution-matrix(:1176-:1265, revision 1 dated 2026-08-30, i.e. older than the card) enumerates the members three times, plus the runtime fork's outcomes once:steps— 「empty-slate forks, one scratch flow per policy over an UNSTAFFED position: onEmptyApprovers absent/admin_rescue → …; 'fail' → …; 'auto_approve' → …」clause— 「the empty-slate fork honors the declared policy: admin_rescue (the default) …; 'fail' …; 'auto_approve' …」 with itsverifyat :1238source— 「(onEmptyApprovers admin_rescue|fail|auto_approve, default admin_rescue)」source— 「(the empty-slate fork: 'empty' = no concrete person, NO_APPROVERS / autoApproved / admin_rescue warn)」The escalation
variantsat :825 are untouched (a different vocabulary). Edits, each in the line's own register:approval-service.ts:2807and:2821).admin_rescueassertion).docs/qa/platform-checklist/README.md:287「Change — edit the fields, bumprevision, append ahistoryentry」; enforced byscripts/check-platform-checklist.mjs:2170):revision1 → 2 and onehistoryrow{ "revision": 2, "date": "2026-09-17", … "ref": "#18527" }. The revision-1 history text (「all three onEmptyApprovers forks were untested」) is a dated record and stays as written.Not touched: the item's
title,knownGaps,negativeand the escalation item.pnpm check:platform-checklist→ exit 0 (「check-platform-checklist: OK — 15 areas, 264 items (264 active) … symbol anchors: 577/633 resolved」, same counts as before the edit).Reverse check of the register, from the committed head (trap-restored, absolute paths): with only the
revisionreverted to 1 on disk (node exact-once replacement;git diff --numstat=1 1),node scripts/check-platform-checklist.mjs→ exit 1: 「✗ approvals.json · approvals.approver-resolution-matrix: "revision" (1) must equal the last history entry's revision (2) — a semantic edit bumps both」. Restored withgit checkout HEAD -- docs/qa/platform-checklist/areas/approvals.json:git hash-object= HEAD blob4dd89a76661948c3b1108e35a4a5df92d7a6283a,git diff HEADempty,git status --porcelainempty, validator exit 0 again.Changeset
skip-changeset, decided by the repo's own gate — the 「Require a changeset (or the skip-changeset label)」 step of.github/workflows/pr-automation.yml(:757-:780): route 2 「It releases nothing (.github/, .claude/, skills/, docs/, content/, …) → apply the 'skip-changeset' label. PREFERRED」, withskills/**spelled out at :771. Measured on this tree: a walk over the 83 trackedpackage.jsonmanifests finds 0 whosefiles[]names askillspath and 0 namingdocs/qa/platform-checklist(positive control: 70 namedist); noskills/*/package.json, nodocs/qa/package.json. The 14 changeset-triggered families the derivation lists under 「Once a changeset exists」 do not apply (no changeset written).node scripts/check-changeset-fixed.mjs(roster family) → exit 0.Gates (worktree at
2b1d5784e8)node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack— change set derived from git, 2 paths vs merge basee5705a8ea; 24 commands derived, all 24 run with redirect-then-$?capture;--ranreconcile: 「Run reconciliation — 24 derived, 24 run, 0 NOT-MEASURED, 0 UNRUN.」, exit 0.pnpm --filter @objectstack/lint run check:doc-formula-expressionswas run after building@objectstack/lint...underscripts/pm/os-verify-lock.sh(「VERDICT command-exit 0 · held the lock 137s · waited 0s」).Beyond the derived set:
pnpm lint(repo-wide,eslint . --no-inline-config) → exit 0 at2b1d5784e8; roster familiesnode scripts/check-published-list-mirrors.mjs→ exit 0 andnode scripts/check-platform-checklist-watchdog.mjs→ exit 0; control-character self-scan (grep -naPover both files) → no match. No ① dependency-closure build and no ② package test are owed — the diff touches no package.Governed:
node scripts/pm/check-governed-merges.mjs --test skills/objectstack-automation/SKILL.md docs/qa/platform-checklist/areas/approvals.json→ exit 3,skills/**×1 hit (the published skills catalog), 「One hit governs the whole PR」. Served tier: MANDATORY, derived from theskills/**surface bydispatch-gates.mjs --tier. Draft at the governed terminal; the seat's four-piece, the maintainer lands.Acceptance notes
onEmptyApprovershits) undercounted a 15-hit file; the enumerating item predates the card. Corrected above; nothing to file.onEmptyApproverstable row is now 700 characters on one line; the file's table rows are single lines by construction and no gate reads line width here. Not a defect.维护者速读(草稿)
改了什么:审批节点空名单策略
onEmptyApprovers自 PR #18525 起有四个成员,两处仍只列三个。①skills/objectstack-automation/SKILL.md第 728 行 Node Config 表的该行,按本行既有的括号注释格式补上第四个成员fallback(请求改在兄弟键fallbackApprovers上开出;与approvers同形;此策略下必填、其它策略下拒收;备选名单也解析为空时退化为admin_rescue)。②docs/qa/platform-checklist/areas/approvals.json里approvals.approver-resolution-matrix这一条的空名单分叉:steps、验收子句及其 verify、两条 source 引用各补上fallback一腿,并按清单自身规则 revision 1→2、加一条 history。两文件之外一字未动。为什么改:词表是 spec 声明的(
approval.zod.ts:884),已发布 skill 与测试清单各自枚举它;少列一个成员,AI 作者按 skill 写元数据就不会知道有fallback,测试清单也测不到这条分叉。语义全部取自 schema docblock 与运行时分叉,不取卡面。风险与代价(含回滚):纯文档/清单改动,不发布任何 npm 内容(
files[]实测 0 命中);skills token 棘轮 12511→12567(上限 12768,余量 201),不动上限。回滚 = revert 这一个提交。席位意见:
你要做的:确认第 728 行新增括号注释与 schema 语义一致(见上文逐句对照),点合并;怎么验:
node scripts/check-skills-token-ratchet.mjs与pnpm check:platform-checklist两条门禁的判定行已在正文引用。Generated by Claude Code