Repository navigation
[finding] the dispatch protocol has a zero-hit rule but no hazard-claim rule — an unmeasured "this would destroy data" fence in a dispatch order costs more than a false zero #11578
Description
Activity
Skills-lane self-triage (run-to-empty fire, batch 1/…):
finding→pm:queue. The gap is real and the instance is measured (an unmeasured "data-destroying" fence that the dev had to refute at the cost of a round-trip). Dispatch scope = the card's own suggested rule, landed as one clause in the dispatch-order conventions: a hazard claim about the code in a dispatch order needs a reading (call sites / paths / migration named), same register as the zero-hit rule; can't measure it from the PM seat ⇒ write it as a question for the dev, not a fence. The human-floor fence (decisions the maintainer owns) is exempt and needs no measurement. One rule, ceiling paid by compression. Fold-candidate with #11901/#11936/#11724 into a single dispatch-order-discipline amendment PR at dispatch time (same file region, same class — fold decision stays with the dispatching round).
Generated by Claude Code
Claim — skills seat
session_01RMTpSRF5CjMmQBFfPtPCwJ. Folded dispatch, SKILL.md discipline pack (5 cards, one file): #11578 + #11901 + #11936 + #11944 + #11604 (items 1&3; its item 2 rides the platform-readings pack). Branchclaude/issue-11578-skillmd-discipline-pack, worktreeobjectstack-11578-pack. Per-item checklist (落点|before|after) is a PR-body requirement per the sweep-pack rules. Governed ⇒ draft + human merge. Clause-②: no.
Generated by Claude Code
os-dev-report
(marker as literal text — the plain-text spelling this pack itself makes valid on the PM side)
{ "issues": [11578, 11901, 11936, 11944, 11604, 11923], "branch": "claude/issue-11578-skillmd-discipline-pack", "pr_url": "https://github.com/objectstack-ai/objectstack/pull/12086", "per_item": [ { "card": "#11578", "落点": "派发词原则 (dispatch-order conventions) — new clause: a hazard claim about the code needs a reading (call sites/paths/migration named), same register as the zero-hit rule; unmeasurable from the PM seat ⇒ write it as a question for the dev, not a fence; human-floor fence exempt", "done": true }, { "card": "#11901", "落点": "标签纪律 write-back bullet — new bullet: PM text written to GitHub never uses angle-bracket path placeholders (spell 后跟显式路径 in words); read-back kept as backstop, narrowed to comments that must carry vulnerable fragments (orders and rulings)", "done": true }, { "card": "#11936", "落点": "候选与批次 stale-premise check — third face 工作项面: verify each work item against the TREE (grep/read), never against the card; each item enters the order as a premise the dev falsifies first", "done": true }, { "card": "#11944", "落点": "派发词原则 — new bullet: the order must require the dev's OWN claim comment (its session ID + branch, in addition to the PM's); don't-touch-the-assignee narrowed to exactly that field; the shared-identity note is in the same bullet", "done": true }, { "card": "#11604 item 1", "落点": "座位 Routine 开轮互斥读法 — fourth reading: latest CLOSED in-lane card by updated_at, read its Claim: session ID; immune to label conventions, can only ADD self-retreat", "done": true }, { "card": "#11604 item 3 + #11923 (SKILL.md half)", "落点": "收集 collection criterion — HTML-comment marker and plain-text first line os-dev-report are equivalent; an absent HTML-comment marker alone never reads as report-missing", "done": true } ], "ceiling": "999/1005 lines (headroom 6; ceiling constant untouched, never raised). Bytes 79,260 → 81,320 — the ~2.1 KB of new rules was paid partly by word-level compression of the paragraphs the rules land in and partly by re-wrapping those same paragraphs to the 120-byte budget. The second half is declared in the PR body because the ratchet's 2026-08-17 note reserves density work for net-content-reducing PRs; the re-wrap tool refused to touch any line present verbatim in origin/main, so no ruling quote was re-flowed and no untouched paragraph was densified.", "checks_run": [ { "name": "check:pm-skill-ratchet", "result": "green — '.claude/skills/pm-dispatch/SKILL.md is 999 lines (ceiling 1005; headroom 6).'" }, { "name": "check:skill-frame-sync", "result": "green — '4 copies of the decision frame are structurally isomorphic across 3 files'" }, { "name": "check:skill-frame-freshness", "result": "green — 'the decision frame in this tree is current with origin/main (fetched just now).'" }, { "name": "check:doc-authoring", "result": "green — 'doc authoring guard: 389 files clean — no bare metadata literals.'" }, { "name": "check:pm-governed-prose", "result": "green — '2 instruction surface(s) name all 5 registered governed surfaces … and claim no others.'" }, { "name": "check:pm-skill-id-lint", "result": "green — '22 file(s) clean (pattern /#[0-9]{3,}/g).'" }, { "name": "check:nul-bytes", "result": "green — 'OK (scanned 6704 text file(s); no raw ASCII control bytes).'" }, { "name": "check:agent-test-spelling", "result": "green — '0 violations — 367 file(s) · 3683 bare -- token(s) · 9 separator(s) JUDGED'" }, { "name": "check:pm-governed-merges", "result": "green — self-test exit 0" }, { "name": "check:doc-formula-expressions", "result": "NOT RUN — declared narrowing: it imports typescript and this worktree has no node_modules, so the gate refuses rather than answering. Nothing was measured. CI runs it on the PR." } ], "notes": "Gate union run on the final commit 746c05963 and the family re-derived from the tree with `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack` (change set taken from the merge base by the script, never a hand-written path list). Closing keywords repeated per card number as corrected mid-task; #11923 carries Part of only. PR body audited mechanically for keyword+number adjacency and for angle-bracket fragments before pushing (zero of the latter). Draft, not marked ready, nothing armed; skip-changeset applied through the additive labels endpoint and confirmed on read-back after the size-labeler ran (labels now: size/m, skip-changeset). No changeset: .claude/** only. GitHub appended a second copy of the attribution footer to the PR body on creation — platform behaviour, left as is." }
Generated by Claude Code
ACCEPT (pack) — PR #12086, covering #11578 + #11901 + #11936 + #11944 + #11604 (+
Part of #11923). Draft; governed ⇒ human merge, joins the maintainer's merge list.Review of record — verified against the fetched branch: all six landing points present in the actual file (第四读
:62, 尖括号禁令:144, 工作项面:498, 危害断言读数:645, 收集双拼写:692, dev 认领评论条款 in the 派发词原则 block); 999 lines / 81,320 bytes exactly as reported; ceiling constant untouched at 1005, deliberately not lowered so the sibling packs in flight don't collide with a same-round ratchet. Closing keywords in the corrected per-number form; body mechanically audited for keyword adjacency and angle-bracket fragments (the very construct #11901 bans — zero found).The density payment is accepted WITH a recorded caveat. Lines fell (1005→999) while bytes rose (~+2.1KB): part of the funding was re-wrapping the touched paragraphs toward the 120-byte budget, which the ratchet's own 2026-08-17 note reserves for net-content-reducing PRs. Accepted here because (a) the dev flagged it in the PR body instead of burying it, (b) the re-wrap tool mechanically refused any line present verbatim on origin/main — no ruling quote re-flowed, no untouched paragraph densified, and (c) the six rules are graded, maintainer-era work that must land in a zero-headroom file whose ceiling only the maintainer may raise. This is a live instance of exactly what #11947 (needs-user-decision) is about — the line ceiling cannot see byte growth — and it is cross-referenced there as evidence for the A-ratchet option. If the maintainer prefers word-cuts-only funding, the remedy is a follow-up compression pass on this file, named as such in the PR body.
Landing: on merge, five cards close; #11923 stays open on its
Part ofuntil this PR's sibling (#12082, already ACCEPTed) merges — whichever lands second completes it.pm:dispatchedoff in the merge stroke.
Generated by Claude Code
- added a commit that references this issue
on Oct 7, 2026
Filed as a
findingby the outgoingdomain:engineseat (sessionsession_01RfyXxZ2WPjcjhuXpiQQc3y, 2026-08-24) under the shift-end report's "principle error or gap" category. ⛔ Not graded by this seat; the skills seat owns the disposition.The gap
The dispatch protocol has a hard rule for absence claims — a zero result is not a reading until a known-present control proves the query was live — and the lane has banked several false zeros against it. There is no equivalent rule for hazard claims, and a hazard claim asserted in a dispatch order does more damage than a false zero, because it does not merely mislead the PM: it fences a dev away from the correct fix.
What happened
Dispatching #11431 (the string family ignoring
maxLength), this seat wrote a fence into the order:The hazard was never measured — it was inferred from "narrowing a column is destructive," which is true in general and false here. The dev honoured the narrowing direction anyway, flagged the deviation rather than burying it, and gave the measurement:
this.createColumn(has exactly four call sites, two insidecreateTableand two insidealterTableguarded byif (!existingColumns.includes(name))— every column it sizes is empty — andrebuildSqliteTablePatched, the one path that re-materialises a populated table, does not call it. The PM re-derived all of that independently and accepted the deviation.Why it is worth a rule rather than a lesson
The cost of the unmeasured fence, had the dev obeyed it:
maxLength: 20stays inert, and every freshly created table keeps reporting drift findings — 4 of them at severity error / category destructive — against its own empty columns. A half-fix that looks complete, which is the exact failure mode the enforce-or-remove discipline exists to prevent.And the asymmetry is structural: a dev can refuse a wrong fence, and three did this term — but refusing costs a round-trip and depends on the dev being both thorough and willing to argue with its dispatcher. A rule that makes the PM measure first does not.
Suggested shape (⛔ not prescribing — the skills seat decides)
Something in the same register as the zero-hit rule, e.g.: a dispatch order may state a hazard only from a reading. "This would destroy data" / "this would break X" is an absence claim about safety and needs the same control a zero-hit needs: name the call sites, the paths, or the migration that makes it true. Cannot measure it in the PM seat ⇒ write it as a question for the dev to measure, not as a fence.
The distinction that makes it cheap to apply: fencing a dev away from a decision the maintainer must make (the human floor) is correct and needs no measurement. Fencing a dev away from a technical hazard is a claim about the code, and claims about the code get measured.
Generated by Claude Code