Skip to content

pm-dispatch: pm:epic marks every card an epic reserves, not only the parent — a reserved card never carries pm:queue, and pm:queue means handed over (maintainer 2026-09-05) #15666

Description

@os-steve

Filed by the domain:skills seat (session session_019RfFHiRCSs3JXLK4cwcfox, os-steve) on maintainer rulings given in the live PM chat, 2026-09-05 02:0xZ–02:2xZ. Self-triaged into the lane: priority:p2, governed surface, pm:blocked behind PR #15460 (the rules-only rewrite of the same file, in the merge queue).
Blocked-by: #15412

The incident (cloud repo, reported by the maintainer)

An epic (专题) session opened a card with pm:queue, then implemented it itself; a patrol lane had lawfully claimed the same card 29 minutes earlier. One card, two implementations. The epic's own post-mortem: it skipped the claim protocol's full-thread re-read, and it had put the card into the public queue while assuming nobody would take it.

The rulings (verbatim, untranslated)

cloud 仓又出现了 epic 和 项目经理重复开发。这个是专题skills 的缺陷吧?是否建议开专题的时候就认领?或者加新的lable

开卡即认领,不会被分诊清扫吧?

用现成的子树保留,而不是提前认领,你不加新的 lable 我在列表页看不清

ok

⇒ Ruled: the reservation must be visible on every reserved card in the list page. The seat's recommendation — reuse pm:epic on the sub-issues rather than add a new label — was accepted with the「ok」.

What changes (rules only, house style)

.claude/skills/pm-dispatch/SKILL.md, section「Epic 子树车道」and the pm:epic row of the label table:

  1. An epic opens its cards under the parent's subtree and puts pm:epic on each of them; ⛔ never pm:queue. A card carrying pm:epic is not a candidate for any domain seat; label:pm:epic is the whole reserved set, parents and children.
  2. Work starts with the claim atomic pair as written — the label write first, then the Claim: comment, then the full-thread re-read; pm:epic stays on the card after the claim.
  3. Leaving the subtree (handing a card to a lane, or transferring it to the spec seat) removes pm:epic and adds pm:queue in the same label write.
  4. pm:epic and pm:queue on one card is a half-state — reserved and handed over at once. pm:queue means handed over: taking such a card back means the full claim protocol, re-read included.
  5. Single-lane repos (no domain:*): open-and-claim in one act is lawful there; in multi-lane repos the domain-label prerequisite stands and the subtree marker is the reservation.

references/core-rules.md: one digest line only if the digest carries the epic rule today (check; ⛔ no new section).

scripts/pm/ensure-pm-labels.sh: the pm:epic description becomes "Reserved by a dedicated epic PM — parent or sub-issue; other PMs never take it; never together with pm:queue" (wording free, meaning fixed). Seeding the cloud repo is done by whoever runs the script with access there — note it in the script's repo-list comment; ⛔ no credentials logic.

Why not a new label (recorded so it is not re-derived)

pm:epic is already in the patrol's visibility set (PM_STATE_LABELS in scripts/pm/check-half-states.mjs) and not in the six-state exclusive set, so a child carrying pm:epic + pm:dispatched is legal and trips neither H25 nor H29; triage does not grade a card that has a named reader. A new label would have to be learned by the triage disjunction, the patrol and the unlock scan; per-epic epic:#n labels would sprawl.

Serial and gates

Same file as PR #15460 (member 1b of #15379), in the merge queue: this card dispatches after it lands and edits the rewritten file — one rule per line, ≤120 bytes, no dates or quotations in the file (the quotations above stay on this card). The rewritten SKILL.md is re-pinned at its landed count with headroom 0, so the added lines are paid for inside the epic section (⛔ no ceiling raise). Gates: check:pm-skill-ratchet, check:skill-frame-sync, check:pm-governed-prose, check:pm-skill-id-lint, and whatever dispatch-gates --commands derives. Governed ⇒ draft PR, in-seat review, os-zhuang + hotlong, human merge.

The two patrol rows that make these states visible are the sibling card, filed in the same act.

Activity

  1. self-assigned this
    on Sep 5, 2026
  2. claude commented on Sep 5, 2026

    @claude
    Contributor

    Claim: PM loop round 5 — unblocked the minute PR #15460 landed (02:16Z); the maintainer's live ruling of 2026-09-05, executed on the rewritten SKILL.md
    Session: session_019RfFHiRCSs3JXLK4cwcfox
    Branch: claude/issue-15666-epic-reservation-label-on-children
    Worktree: objectstack-issue-15666
    Domain: domain:skills
    File surface: .claude/skills/pm-dispatch/SKILL.md — the「Epic 子树车道」section (lines 296–307 on main fa125f3bf) and the pm:epic row of the label table (line 118): the five rule lines on the card, paid inside the section (811 of 811, headroom 0; ⛔ no ceiling raise, ⛔ no other section) + scripts/pm/ensure-pm-labels.sh: the pm:epic description (the script already seeds all five repos including cloud, so no repo-list change). ⛔ No edit to core-rules.md (the digest carries no epic line today), state-machine.md, or check-half-states.mjs (that is #15667) (stop on breach; explain in the report)
    Container & model: S, mode:subagent, model: fable (dispatch-gates.mjs --tier .claude/skills/pm-dispatch/SKILL.md scripts/pm/ensure-pm-labels.sh at fa125f3bf, 02:3xZ: MANDATORY — SKILL.md is clause-① mandatory)
    Clause-②: no (PM protocol text; no published contract)
    Serial constraints cleared: PR #15460 (member 1b) MERGED 02:16Z — the section is edited on its rewritten form; no open PR touches SKILL.md or the label script (scan 02:28Z); #15404 (two SKILL.md lines on pm:retriage semantics) is the same file's next card, a different defect ⇒ hard-serial behind this flight (pm:blocked, Blocked-by: #15666); the ratchet map is shared with PR #15641 (dirty, being re-merged by its dev) on disjoint rows and this flight does not touch the map. GOVERNED ⇒ draft PR, in-seat review, os-zhuang + hotlong, human merge, skip-changeset. The PR carries Fixes #15666.

    Decision re-read (02:3xZ): the four maintainer messages quoted on the card are the whole mandate; the「ok」accepted the seat's proposal as written; nothing further owed.


    Generated by Claude Code

  3. claude commented on Sep 5, 2026

    @claude
    Contributor

    os-dev-report

    {
      "issue": 15666,
      "status": "done",
      "branch": "claude/issue-15666-epic-reservation-label-on-children",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/15696",
      "premise_still_valid": true,
      "summary": "Wrote the four rulings into exactly the three surfaces the card names, on the rewritten SKILL.md (base 5eb24f867, origin/main merged in at a55efc6c1): (1) SKILL.md section Epic 子树车道 now carries the five reservation rules, one line each (children hang under the parent and carry pm:epic, never pm:queue, label:pm:epic is the whole reserved set and domain seats never take one; claim atomic pair as written with pm:epic staying after the claim; leaving the subtree swaps pm:epic for pm:queue in one label write; pm:epic + pm:queue is a half-state and pm:queue means handed over, take-back is the full claim protocol incl. re-read; single-lane repos may open-and-claim, multi-lane keep the domain-label prerequisite) — paid inside the section by six joins of adjacent lines (E2+E4, E5+E6a, E6b folded into the rule-2 line, E8+E9, E7+E10, E11+E12), all 14 pre-existing rules and the one pre-existing ⛔ survive, every line ≤120 bytes, file stays 811/811 with the table pin at 342; (2) the pm:epic label-table row now reads reserved by an epic PM, parent or sub-issue, other PMs never take it, ⛔ never with pm:queue (110 bytes); (3) scripts/pm/ensure-pm-labels.sh pm:epic description is now 'Reserved by a dedicated epic PM, parent or sub-issue; other PMs never take it; never with pm:queue' (98 chars, cap 100) — repo list and credentials untouched. Nothing else edited: core-rules.md (no epic digest line exists today), state-machine.md, check-half-states.mjs (sibling card #15667), other sections, ratchet rows. Governed surface ⇒ opened PR #15696 as a DRAFT to main, first line Fixes #15666, skip-changeset added via the REST additive endpoint and read back (delayed re-read at 03:35Z still carries it, plus the labeler's documentation + size/s); reviewers os-zhuang + hotlong requested at 03:17Z. State I did not set, recorded and not reverted (timeline actors read via REST): os-zhuang APPROVED at 03:23:52Z, flipped ready_for_review at 03:23:57Z, enabled auto-merge at 03:24:00Z and added_to_merge_queue at 03:33:28Z; the Governed Surface Queue Guard check reported success on both its runs. I never readied, enqueued, armed or approved anything; whether that approval is an authorized landing of a governed diff (GOVERNED_APPROVERS in scripts/pm/check-governed-queue-guard.mjs) is the PM's/maintainer's read, flagged here rather than acted on. Observations, no action: SKILL.md line 425 (已排队父单… pm:epic 父单的子树除外, outside the section) and check-half-states.mjs comments still describe pm:epic as a parent marker — consistent with the new rule (children carry it too) and the latter file belongs to #15667. The PR body was PATCHed once to repair backslash-escaped backticks inside inline code spans; the platform's footer downgrade on edit was observed as measured (session-form block kept from creation, bare block appended), not re-pasted. The assignee field was empty at pickup (PM-side half state as the prompt anticipates); a REST repo-scoped read probe answered 200 so all GitHub reads and writes went through REST — zero MCP GitHub calls.",
      "tests": "Local, on the merged head edc6d1226 after the final commit (exit codes captured before any pipe; each gate's own verdict line quoted): pnpm check:pm-skill-ratchet exit 0 — '✓ check-skill-line-ratchet: .claude/skills/pm-dispatch/SKILL.md is 811 lines (ceiling 811; headroom 0).' and '✓ check-skill-line-ratchet: .claude/skills/pm-dispatch/SKILL.md: widest table row is 342 bytes (pin 342; headroom 0).'; pnpm check:skill-frame-sync exit 0 — '✓ check-skill-frame-sync: 2 copies of the decision frame are structurally isomorphic across 2 files'; pnpm check:pm-governed-prose exit 0 — '✓ check-governed-prose: 2 instruction surface(s) name all 5 registered governed surfaces (docs/adr/** · .claude/** · skills/** · AGENTS.md · CLAUDE.md) and claim no others.'; pnpm check:pm-skill-id-lint exit 0 — '✓ check-skill-id-lint: 24 file(s) clean'; pnpm check:pm-label-desc-cap exit 0 — '✓ check:pm-label-desc-cap: 23 label descriptions in scripts/pm/ensure-pm-labels.sh, all ≤100 characters (longest: 100, repo:objectui).' (the pm:epic description is 98 code points); pnpm check:nul-bytes exit 0 — 'check-nul-bytes: OK (scanned 7583 text file(s) -- 7583 tracked, 0 untracked-not-ignored; skipped 7 binary; no raw ASCII control bytes).'; node scripts/pm/check-governed-merges.mjs --test .claude/skills/pm-dispatch/SKILL.md scripts/pm/ensure-pm-labels.sh exit 3 as expected — '⛔ GOVERNED — a human merge is the review record for this PR', .claude/** ×1, 'paths not on the register: scripts/pm/ensure-pm-labels.sh'; node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack ⇒ 32 commands (derived from the change set git computed: the two files, committed), every one executed and its printed command recorded byte-for-byte, then --ran exit 0 — '✓ dispatch-gates --ran: 32 derived famil(ies) accounted for — 32 run, 0 NOT-MEASURED.'; 31 of 32 exit 0 (verdict lines in the run logs), the one non-zero is pnpm --filter @objectstack/lint run check:doc-formula-expressions exit 3 'PREREQUISITE NOT MET — the workspace package @objectstack/formula is not built' — after building @objectstack/formula through the lock (VERDICT command-exit 0, held 236s) the next prerequisite is @objectstack/lint's own dist ⇒ NOT MEASURED locally, a whole-tree family over content/docs formula expressions this diff does not touch. The same 32-command union had already run green (same one exit-3) on the pre-merge commit 718c718a5; it was re-run because the first --ran flagged two gate scripts (check-governed-merges.mjs, check-governed-queue-guard.mjs) as moved on origin/main, so origin/main a55efc6c1 was merged in and everything re-measured on edc6d1226. pnpm lint (eslint . --no-inline-config, the whole tree — no narrowing to declare) through scripts/pm/os-verify-lock.sh: 'VERDICT command-exit 0 · held the lock 110s (1m50s) · waited 195s' on edc6d1226 and 'VERDICT command-exit 0 · held the lock 120s' on 718c718a5 — shared-box seconds, not idle-box figures. Byte measurements on HEAD: the 14 section lines are 97/95/117/108/113/111/92/119/110/119/115/117/106/91 bytes (none over 120), row 118 is 110 bytes, the file is 811 lines, widest table row 342. No test suite is touched (rules-only prose and one shell string literal), so no vitest run applies. CI on PR #15696 (head edc6d1226): all 38 check runs completed by 03:33Z, none non-green — Lint & Repo Gates success; Type Check · workspace / source gates / consumer gates / debt ledger success; Test Core success (6 of 6 shards); Dogfood Regression Gate success; Governed Surface Queue Guard success (both runs); No other open PR may claim the same issue / single-writer path success; Check Changeset skipped (skip-changeset present); Build Core, Temporal Conformance and Console Pin Gate skipped by path filter.",
      "mcp_calls": "0 — every GitHub read (issue page payload channel, check runs) and write (draft PR, label, reviewers, body PATCH, report comment) went through the REST channel after a repo-scoped read probe returned 200; MCP GitHub tool schemas were loaded but never invoked",
      "open_questions": [],
      "out_of_scope_findings": []
    }

    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions