Skip to content

two declared enumerations of the approval onEmptyApprovers vocabulary go to 3 of 4 members when #17931 lands #18527

Description

@os-warren

Class: a declared vocabulary that goes incomplete the moment #17931 lands. Filed by the domain:spec execution seat on its dev's report; the dev correctly stopped rather than widening, because both carriers are outside its declared file surface and one of them is a governed surface.

The reading

PR #18525 (card #17931, ruled by #16678 Phase 2 §8.2) adds a fourth member to the approval node's empty-slate policy: onEmptyApprovers: 'fallback', with a sibling fallbackApprovers. Two places enumerate that vocabulary and will list 3 of 4 once it lands:

  • skills/objectstack-automation/SKILL.md line 728
  • docs/qa/platform-checklist/areas/approvals.json

Why it is a card and not a rider on that PR

Blocked-by: #17931

What the fix is

Add the 'fallback' member (and its fallbackApprovers sibling, where the carrier describes the key's payload) to both enumerations, matching whatever wording each carrier already uses for the other three — ⛔ not a new gloss, and ⛔ nothing else in either file.

Successor

The domain:skills seat for skills/objectstack-automation/SKILL.md; docs/qa/platform-checklist/areas/approvals.json belongs to the checklist owner. Routing is triage's call — this card is filed unrouted.

Dedupe words: objectstack-automation SKILL onEmptyApprovers, platform-checklist approvals.json, fallbackApprovers, empty-slate policy table, admin_rescue fail auto_approve.


Generated by Claude Code

Activity

  1. os-justin commented on Sep 17, 2026

    @os-justin
    Collaborator

    Unblocked — pm:blocked → pm:queue, skills seat session_01Gqi43smmqjJ5sUrhfoPeKu, 2026-09-17T15:47Z. The card's own Blocked-by: #17931 is discharged: #17931 closed completed at 2026-09-17T08:06:24Z on PR #18525, squash b0eb9a59c on main, and the fourth member exists there now — packages/spec/src/automation/approval.test.ts :352 「ApprovalNodeConfigSchema — onEmptyApprovers: 'fallback' + fallbackApprovers」 read on origin/main f8eaf6704. The two carriers this card names still enumerate three (skills/objectstack-automation/SKILL.md, 961 lines, and docs/qa/platform-checklist/areas/approvals.json), so the premise 「3 of 4 once it lands」 is now the present tense. Enters the total order as p3 Task; skills/** is a governed surface, so the line budget and the tier are set at dispatch and the landing is the maintainer's.


    Generated by Claude Code

  2. os-justin commented on Sep 17, 2026

    @os-justin
    Collaborator

    Claim: PM loop round 1
    Session: session_01Gqi43smmqjJ5sUrhfoPeKu
    Branch: claude/issue-18527-on-empty-approvers-fallback-member
    Worktree: objectstack-issue-18527
    Domain: domain:skills
    Seat: domain:skills#1
    File surface: skills/objectstack-automation/SKILL.md (published skills/**, governed; the onEmptyApprovers table row :728 enumerates admin_rescue / fail / auto_approve — three of the four members ApprovalNodeConfigSchema declares since PR #18525 (#17931) landed fallback + fallbackApprovers; deliverable = the fourth member in that row's own register, ⛔ no new gloss, ⛔ nothing else in the file; token ratchet 12511 / 12768 at 2026-09-17T20:55Z — 257 tokens of headroom, so the addition pays for itself only if it stays inside it, before / after read with scripts/check-skills-token-ratchet.mjs) and docs/qa/platform-checklist/areas/approvals.json (the card says it enumerates 3 of 4 — ⚠️ the seat's grep at 20:55Z finds onEmptyApprovers only in prose at :580 / :631 ('fail' on a showcase stage) and a variants list at :825 that is the ESCALATION action vocabulary, not this one; the dev locates the real enumeration or reports that carrier's premise false and leaves the file untouched); source of truth for the member's meaning = packages/spec/src/automation/approval.zod.ts and approval.test.ts :352, ⛔ not the card's prose; governed ⇒ draft at the governed terminal, the seat's four-piece, the maintainer lands
    Container & model: M(第四成员入词表 + 第二载体核实 + 字数棘轮前后), mode:subagent, model: claude-fable-5-1 — node scripts/pm/dispatch-gates.mjs --tier --repo objectstack-ai/objectstack skills/objectstack-automation/SKILL.md docs/qa/platform-checklist/areas/approvals.json at 2026-09-17T20:55Z on the fast-forwarded tree (d9ba33df4): 「Model tier — MANDATORY: claude-fable-5-1 (derived from the file surface, not recalled)」; the seat's contract-tier review at delivery
    Clause-②: no
    Thread-read: 5715686627
    Serial constraints cleared: Blocked-by: #17931 discharged at 2026-09-17T15:47Z (5717231274); 30 open PRs' file lists read at 2026-09-17T20:54Z — none touches either file; os-verify-lock.sh --status at 2026-09-17T20:54Z: free; shared checkout at origin/main d9ba33df4. Hot-file queue behind this card: none on either file. Slot: one of the two freed by #18661's and #18693's deliveries (PR #18784 and PR #18786, under the seat's review); #18683 in flight; PR #18775 (#18733) in the merge queue. Total order at 2026-09-17T20:56Z: p2 — #18536 (SKILL.md serial) · #18699 (behind PR #18725) · #18743 (SKILL.md serial) · #18744 (behind PR #18775; its deliverable burns the shared credential's GraphQL pool — the seat schedules it for a window with no landing pending) · #18755 (SKILL.md serial) · #18764 (behind #18683); p3 by age — this card · #18622 · #18662 (behind PR #18784) · #18664 (FREE, next) · #18665 (SKILL.md serial) · #18686 (behind #18744) · #18705 (MANDATORY, published skill) · #18721 (FREE).
    Ruling-ref: 5715686627


    Generated by Claude Code

  3. os-justin commented on Sep 17, 2026

    @os-justin
    Collaborator

    os-dev-report

    {
      "issue": 18527,
      "status": "done",
      "branch": "claude/issue-18527-on-empty-approvers-fallback-member",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/18795",
      "head": "2b1d5784e89e80874464e20421cc3d0e7531dc02",
      "files": ["skills/objectstack-automation/SKILL.md", "docs/qa/platform-checklist/areas/approvals.json"],
      "session": "session_01Gqi43smmqjJ5sUrhfoPeKu",
      "premise_still_valid": true,
      "summary": "Added the fourth onEmptyApprovers member ('fallback', paired with fallbackApprovers) to both carriers in each carrier's own register, semantics read from packages/spec/src/automation/approval.zod.ts (:884 enum with 'fallback'; :868-869 opens the request on fallbackApprovers; :878-882 same resolver as approvers, a fallback that resolves to nobody degrades to admin_rescue; :906-907 fallbackApprovers array min 1; :897-898 required iff 'fallback' and refused otherwise via checkFallbackApproversPairing :728-767), approval.test.ts:352, and the runtime fork approval-service.ts:2782-2836. Carrier 1: SKILL.md :728 (the only enumerating line; :635 names the key, :653/:693 pick 'fail') gains one parenthetical in the row's register, no other line. Carrier 2: the card's premise HOLDS — the claim's grep (3 hits) undercounted a 15-hit file: item approvals.approver-resolution-matrix (:1176-:1265, revision 1 dated 2026-08-30, older than the card) enumerates the three members in its steps :1207, its fork clause :1236 (+verify :1238) and its source citations :1256/:1257; the 'fallback' leg was added to each, and per the checklist's own change register (README.md:287, validator :2170) revision 1 to 2 with one history row ref #18527; the escalation variants at :825 untouched. Draft PR #18795 at the governed terminal (skills/** hit, check-governed-merges --test exit 3), Fixes #18527, Clause-②: no, no model identifier. Deviations to note: origin/main moved d9ba33df4 to e5705a8ea between claim and worktree creation, every reading was re-taken on e5705a8ea (ratchet baseline unchanged 12511/12768); the card had no assignee on arrival (worked anyway, per rule 2); the dispatch asked for a four-line 维护者速读 while the os-dev definition prescribes five sections — the definition wins, the body carries the five (怎么验 folded into 你要做的); the PR carries a 'documentation' label set by another actor, left as is.",
      "measurements": {
        "token_ratchet": "scripts/check-skills-token-ratchet.mjs (ceil(utf8 bytes / 4)) — before on e5705a8ea: 12511 tokens (ceiling 12768; headroom 257); after on 2b1d5784e8: 12567 tokens (ceiling 12768; headroom 201); +56 tokens = +224 bytes, ceiling untouched, no ruling needed",
        "line_readings": "SKILL.md 961 to 961 lines; package (all ten SKILL.md under skills/) 6145 to 6145 lines; the edited row was already the file's widest line (512 chars, now 700)",
        "carrier_1_before": "| `onEmptyApprovers` | What an EMPTY resolved slate does: `admin_rescue` (default — …), `fail` (node fails — treat an empty slate as a config bug), `auto_approve` (skip the request, continue down `approve` with `output.autoApproved = true` — opt-in because it silently waves the record through). Declare it explicitly on any node with an `expression` approver (linted) |",
        "carrier_1_after": "… `auto_approve` (… silently waves the record through), `fallback` (request opens on the sibling `fallbackApprovers` instead — same shape as `approvers`, required by this policy and refused under any other; a fallback that itself resolves to nobody degrades to `admin_rescue`). Declare it explicitly on any node with an `expression` approver (linted) |",
        "carrier_2": "approvals.json item approvals.approver-resolution-matrix: :1207 steps appended the 'fallback' fork leg (request opens on the fallback slate, pending_approvers holds the fallback's resolved ids, 'opening on the declared fallbackApprovers' warn; fallback resolving to nobody degrades to admin_rescue with the 'resolved to nobody either' warn — both warn fragments verbatim from approval-service.ts:2807/:2821); :1236 clause inserted the 'fallback' policy; :1238 verify appended the fallback pending_approvers assertion; :1256 source now 'admin_rescue|fail|auto_approve|fallback, default admin_rescue' plus a fallbackApprovers citation naming checkFallbackApproversPairing; :1257 source outcome list gained 'fallbackApprovers re-expansion'; revision 1 to 2; history row {revision 2, 2026-09-17, ref #18527}",
        "changeset": "skip-changeset, decided by the Require-a-changeset step of .github/workflows/pr-automation.yml (:757-:780, route 2 names skills/ and docs/ as releases-nothing, skills/** spelled out at :771); measured: 0 of 83 tracked package.json files[] name a skills or docs/qa path (positive control: 70 name dist); the 14 changeset-triggered families do not apply (no changeset written); label written via additive POST /issues/18795/labels HTTP 200, read back documentation,size/s,skip-changeset — nothing stripped",
        "governed": "node scripts/pm/check-governed-merges.mjs --test on the two paths: exit 3, skills/** x1 (published skills catalog), one hit governs the whole PR; docs/qa path not on the register; served tier MANDATORY per dispatch-gates.mjs --tier from the skills/** surface"
      },
      "tests": "Gates at 2b1d5784e8: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (change set from git, 2 paths vs merge base e5705a8ea) derived 24 commands; all 24 run with redirect-then-$? capture; --ran reconcile: 'Run reconciliation — 24 derived, 24 run, 0 NOT-MEASURED, 0 UNRUN.' exit 0. All 24 exit 0: check-ci-filter-parity; check-closing-keyword-parity (+--self-test); check-comment-mask-corpus; check-doc-route-spelling --advisory (+--self-test); check-skills-token-ratchet (+--self-test); pnpm --filter @objectstack/lint run check:doc-formula-expressions (after building @objectstack/lint... under os-verify-lock.sh: 'VERDICT command-exit 0 · held the lock 137s · waited 0s'); pnpm --filter @objectstack/spec run check:skill-docs ('Skill docs in sync'); pnpm check:agent-test-spelling, corpus-claim-drift, cross-package-test-inputs, doc-authoring, driver-memory-census, nul-bytes, platform-checklist ('OK — 15 areas, 264 items (264 active) … symbol anchors 577/633 resolved'), pm-governed-merges, refd-timer-probe, role-word, skill-compatibility, skill-frame-sync, skill-identifier-liveness, watch-hint-literal. Beyond the derived set: pnpm lint (repo-wide eslint . --no-inline-config) exit 0 at 2b1d5784e8; roster families check-published-list-mirrors.mjs exit 0, check-platform-checklist-watchdog.mjs exit 0, check-changeset-fixed.mjs exit 0; control-character self-scan over both files: no match (grep exit 1); JSON parse of approvals.json OK. No ① dependency-closure build and no ② package test owed (diff touches no package). Reverse check from the committed head (trap-restored, absolute paths): revision reverted 2 to 1 alone (node exact-once replacement, git diff --numstat 1 1) makes node scripts/check-platform-checklist.mjs exit 1 with '\"revision\" (1) must equal the last history entry's revision (2)'; restored via git checkout HEAD -- path: git hash-object equals HEAD blob 4dd89a76661948c3b1108e35a4a5df92d7a6283a, git diff HEAD empty, git status --porcelain empty, validator exit 0 again. No dist ablation applies (no built artifact is read by any gate here). PR body read back after creation: byte-identical to what was sent except the trailing newline; one session-URL footer. CI on PR #18795: in_progress at report time (not awaited, per the definition).",
      "mcp_calls": "0 — no MCP GitHub tool used; all reads and writes over the REST proxy with curl",
      "api_writes": "4 write items (5 calls): git push x2 (empty-branch probe e5705a8ea, then content 2b1d5784e8); POST /repos/objectstack-ai/objectstack/pulls x1 (HTTP 201, #18795, draft); POST /repos/objectstack-ai/objectstack/issues/18795/labels x1 (HTTP 200, skip-changeset); POST /repos/objectstack-ai/objectstack/issues/18527/comments x1 (this report). No PATCH, no assignee write, no reviewer request, no draft flip.",
      "open_questions": [],
      "out_of_scope_findings": [
        "noted, not filed — 承接者: the skills seat (this report): the claim's carrier-2 reading (three onEmptyApprovers hits at :580/:631/:825) undercounted a 15-hit file; the enumerating item approvals.approver-resolution-matrix predates the card (revision 1, 2026-08-30). Premise correction to the claim, not to the card; nothing to file.",
        "noted, not filed — 承接者: 无: SKILL.md :728 is now 700 characters on one line (was already the file's widest at 512); table rows in this file are single lines by construction and no gate reads line width. Not a defect."
      ]
    }
    

    Generated by Claude Code

  4. os-justin commented on Sep 17, 2026

    @os-justin
    Collaborator

    ACCEPT — in-seat contract-tier review of PR #18795 (head 2b1d5784e, report 5721375720) by the skills seat, 2026-09-17T21:40Z. Verified against GitHub and a scratch worktree at the head, ⛔ not against the report. Governed: skills/** ⇒ this ACCEPT is the first half of the four-piece; the maintainer's approval is the landing.

    • Scope = the claim (5721118247), one premise of the CLAIM corrected: two files, +9 / −8 in one commit — skills/objectstack-automation/SKILL.md (:728, the one enumerating row) and docs/qa/platform-checklist/areas/approvals.json (the approvals.approver-resolution-matrix item); skip-changeset decided by the repo's own Require a changeset step (pr-automation.yml :757–:780 names skills/ and docs/ as releases-nothing; 0 of 83 package.json files[] name either path, positive control 70 name dist); Clause-②: no on the PR body and the claim, check-clause2-carriers --pair 18795 0 (seat's run); check-governed-merges --test exit 3 (skills/** ×1 — one hit governs the whole PR). The seat's claim said the JSON carrier might hold no enumeration (its grep found 3 hits); the file holds 15, and the item's steps :1207, fork clause :1236 + verify :1238 and sources :1256 / :1257 DO enumerate the three members (revision 1, 2026-08-30, older than the card) — the card's premise holds for both carriers, the claim's aside did not, said here.
    • The member's meaning, read from the schema, ⛔ not the card: approval.zod.ts :884 onEmptyApprovers: z.enum(['admin_rescue', 'fail', 'auto_approve', 'fallback']).default('admin_rescue'); :906–:907 fallbackApprovers: z.array(ApprovalNodeApproverSchema).min(1).optional() 「Approvers the request opens on when onEmptyApprovers is 'fallback'」; :897–:898 required iff 'fallback' and refused otherwise, both arms enforced by checkFallbackApproversPairing (:728–:767); approval.test.ts :352; the runtime fork approval-service.ts :2782–:2836 (a fallback that resolves to nobody degrades to admin_rescue, with its own warn). The seat re-read :884 / :897–:898 / :906–:907 / test :352 on the head.
    • Carrier 1, read in the diff: :728 gains ONE parenthetical in the row's own register — 「fallback (request opens on the sibling fallbackApprovers instead — same shape as approvers, required by this policy and refused under any other; a fallback that itself resolves to nobody degrades to admin_rescue)」 — before the row's closing sentence; no other line (:635 names the key, :653 / :693 pick 'fail' — not enumerations). Token ratchet: 12511 → 12567 / 12768 (headroom 257 → 201; seat's run on the head), no ceiling raised, no ruling spent; 961 → 961 lines.
    • Carrier 2, read in the diff: steps :1207 gain the 'fallback' fork leg (opens on the fallback slate, pending_approvers holds the fallback's resolved ids, the 「opening on the declared fallbackApprovers」 warn; a fallback resolving to nobody degrades to admin_rescue with the 「resolved to nobody either」 warn — both fragments verbatim from approval-service.ts :2807 / :2821); the fork clause :1236 and its verify :1238 gain the fallback policy and the pending_approvers assertion; sources :1256 / :1257 now cite admin_rescue|fail|auto_approve|fallback and fallbackApprovers with checkFallbackApproversPairing; revision 1 → 2 with one history row (ref #18527), per the checklist's own register (README.md :287, validator :2170). The escalation variants at :825 untouched. check-platform-checklist on the head: OK — 15 areas, 264 items (seat's run). Reverse check accepted as read: reverting the revision alone reds the validator (「must equal the last history entry's revision」), restored by blob hash.
    • Gates: 24 derived, 24 run, 0 NOT-MEASURED, all exit 0 (incl. check-skills-token-ratchet + self-test, check:doc-formula-expressions after building @objectstack/lint under the verify lock, check:skill-docs 「in sync」, check:platform-checklist, check:skill-frame-sync, check:pm-governed-merges); beyond the set: pnpm lint exit 0, check-published-list-mirrors / check-platform-checklist-watchdog / check-changeset-fixed exit 0; control-byte scan clean; JSON parses — per the report. Seat-side: ratchet, validator, governed exit 3, pair 0, schema lines, harness CURRENT. CI on the head: 35 check runs read explicitly, every one completed — 24 success, 11 skipped (all in the expected-skips roster, exit 0); commit status success; none failure.
    • Writes: api_writes 4 items (draft PR; skip-changeset read back beside the labeler's size/s and another actor's documentation, left alone; this report), mcp_calls 0; PR body written once at create, one session-URL footer, byte-identical on read-back.
    • Deviations, disclosed: main moved d9ba33df4 → e5705a8ea between the claim and the worktree — every reading re-taken on e5705a8ea (ratchet baseline unchanged); the dispatch asked for a four-line 速读 and the os-dev definition prescribes five sections — the definition won, and the seat's 终稿 below is the record.
    • Out-of-scope, noted: :728 is now 700 characters on one line (it was already the file's widest at 512); table rows are single lines by construction and no gate reads line width. Nothing to file.
    • Landing (governed): four-piece — this ACCEPT on the card, the seat's ## 维护者速读(终稿) on the PR, needs-user-decision, review requested from os-zhuang + hotlong; after an authorized APPROVED the seat strips needs-user-decision and os-zhuang enqueues (ruling C). Hot-file queue behind it on either file: none.

    Generated by Claude Code

  5. os-justin commented on Sep 18, 2026

    @os-justin
    Collaborator

    LANDED — PR #18795 (#18527, governed published skills/objectstack-automation/SKILL.md :728 gains the fallback parenthetical — the fourth onEmptyApprovers member in the row's own register — and docs/qa/platform-checklist/areas/approvals.json item approvals.approver-resolution-matrix revision 1 → 2; token ratchet 12567 / 12768; MANDATORY tier) merged through the queue at 2026-09-18T02:05:55Z (merged_at), squash a09d79708bc472967bee5c22fbb6eb53188b1e14 (single parent 6427ee2480a359e028f5c76da75152a14368f6d0, an ancestor of origin/main). Governed surface (published skills/**): the seat's ACCEPT 5721611788 here, ## 维护者速读(终稿) 5721612115 on the PR, needs-user-decision + reviewers at 2026-09-17T21:40Z; os-zhuang APPROVED at 2026-09-18T01:14:42Z (review 5243123351 on the ACCEPT head 2b1d5784e8), marked ready and enqueued by hand at 2026-09-18T01:15:49Z; the seat stripped needs-user-decision at 2026-09-18T01:16:43Z per ruling C (--pair 18795 0, CI 40 / 15 skipped rostered, harness CURRENT at 88aa326deb). The card auto-closed completed at 2026-09-18T02:05:56Z on the PR's Fixes; pm:dispatched is stripped in the same act. Landing criterion per the seat's publication register: MERGED through the queue, read from merged_at.


    Generated by Claude Code

  6. added a commit that references this issue on Sep 28, 2026
    a09d797
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions