Skip to content

Commit bea5334

Browse files
committed
docs(pm-dispatch): contract review r0 — vacant-triage route, downgrade reason kept, H74 reads the three audit exemptions
Four should-fixes from the contract review of the first commit, in one round: - execution-duties.md: the knock line routes a vacant triage seat to the director (project-forms.md routes only `Request: main-red` that way), and the downgraded tier with its reason is written on the claim comment's Container & model line again — folded into the effort line, so the file stays at 188 and no line passes 120 bytes. - check-half-states.mjs H74: the `[maintainer]` title test was a no-op (no such spelling exists on the board); it now reads SKILL.md's non-fleet card through one shared `isFleetCard` (human author, no seat signature) that H67's read order uses too. A main-red anchor card is exempt by its measured title openings, and a sub-issue that inherits its parent's domain or grade is exempt through the sub-issues parent read H45 already takes — bought only for a card whose thread was read and found clean, with an unreadable parent fired as UNJUDGED. The battery pins each exemption beside the twin that still fires. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WGufzgjcGBFsSmg6tHbbJq
1 parent 197f87e commit bea5334

2 files changed

Lines changed: 123 additions & 19 deletions

File tree

‎.claude/skills/pm-dispatch/references/execution-duties.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@
6060
- ③ 新卡预读:复现、查重查询与命中数、一行 `Suggested-grade: p<n> · <理由>`;⛔ 不写级标签。
6161
- ④ 本车道 `finding` 卡补缺的 `reach:` 或复现,或写明为何不合三类。
6262
- ⛔ 不静默等分诊:敲信箱键(`project-forms.md`);报「等分诊」须引所敲评论 id,引不出即未问。
63-
- 候选缺审计评论 ⇒ 当轮敲 `Request: triage · #n · first-touch audit`,⛔ 不改报维护者。
63+
- 缺审计评论当轮敲 `Request: triage · #n · first-touch audit`,分诊空席敲总监,⛔ 不改报维护者。
6464

6565
## 认领(先认领后动工)
6666

@@ -118,7 +118,7 @@
118118
- 自审随面定档:仅申报面车道席作 `TIER_DEFAULT` 记录;余面未达档 ⛔ 不自审,起隔离子代理。
119119
- 降档出口两条:额度耗尽豁免(达档实测不可用才落默认判断档,⛔ 不再往下)与主动预降。
120120
- 额度墙只向下(降档或降并发)⛔ 永不向上;临时偏离记座位贴带结束条件,逐次巡检重读。
121-
- effort 按卡难度定、与档位并列,写进 Container & model 行与派发令,派发显式传参,⛔ 不留空。
121+
- effort 按卡难度定,与档位、降档理由写进 Container & model 行与派发令,显式传参,⛔ 不留空。
122122
- S 机械 `medium`;M `high`(默认);L/XL/security/p1/契约复核/四轴分析/ADR `xhigh`;`max` 凭点名/常设令。
123123
- 解析顺序、pin 语义与允许名单回退陷阱以 os-dev 定义 frontmatter 注释为权威。
124124
- 分诊 suggestion 行是输入不是决定,不采纳给理由。

‎scripts/pm/check-half-states.mjs‎

Lines changed: 121 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8167,8 +8167,48 @@ export const H74_EXEMPT_LABELS = Object.freeze([
81678167
'queue-flake-anchor',
81688168
]);
81698169

8170-
/** Exempt by title — a card the maintainer filed by hand opens `[maintainer]`. */
8171-
export const H74_MAINTAINER_TITLE = /^\s*\[maintainer\]/iu;
8170+
/**
8171+
* Exempt by title — a main-red anchor card (SKILL.md: 「main-red 锚卡,免审计评论」).
8172+
* The anchor has no label and no template; its spelling is measured off the
8173+
* board (2026-11-03, 167 title hits): a seat-filed anchor opens `main red:` /
8174+
* `main red (…)` / `main red since …` (#28622, #27806, #24057), and the
8175+
* scheduled-run anchors end their prefix with `: red on main` (#29494,
8176+
* #28580). ⛔ A card ABOUT main-red (`pm-dispatch(main-red): …`, #29445) opens
8177+
* with neither and stays in the population.
8178+
*/
8179+
export const H74_MAIN_RED_ANCHOR_TITLE = /^\s*main[ -]red\b|:\s*red on main\b/iu;
8180+
8181+
/**
8182+
* The parent read answered with something other than a row or a 404 — the
8183+
* sub-issue relation is UNREADABLE, which the predicate reports as UNJUDGED
8184+
* (#4690: an unknown is never an accusation and never a clean reading).
8185+
*/
8186+
export const H74_PARENT_UNREADABLE = Object.freeze({ unreadable: true });
8187+
8188+
/**
8189+
* Is this card the fleet's? An App opened it (`user.type` `Bot`, every
8190+
* `[bot]` login) OR its body opens with a seat signature, since seat-operated
8191+
* human accounts file cards too. An unreadable author reads as the fleet's,
8192+
* ⛔ never promoted to a human's. The reading of execution-duties.md 「非车队卡
8193+
* (作者非 `Bot` 且首行非席位签名)」, shared with H67's read order. Pure.
8194+
*/
8195+
export function isFleetCard(issue) {
8196+
if (readSignatureLine(issue?.body) !== null) return true;
8197+
const author = artefactAuthor(issue);
8198+
return !(author && !author.isApp);
8199+
}
8200+
8201+
/**
8202+
* Does this sub-issue INHERIT its parent's domain or grade (SKILL.md: 「sub-issue
8203+
* 承父卡域 … 免审计评论」)? True when the parent row carries one of the card's
8204+
* own `domain:*` or `priority:*` labels. Pure; a parent that is not a row
8205+
* (null, undefined, the unreadable sentinel) inherits nothing.
8206+
*/
8207+
export function h74InheritsFromParent(issue, parent) {
8208+
if (!parent || parent === H74_PARENT_UNREADABLE || !Array.isArray(parent.labels)) return false;
8209+
const inherited = labelNames(parent);
8210+
return labelNames(issue).some((l) => (l.startsWith('domain:') || l.startsWith('priority:')) && inherited.includes(l));
8211+
}
81728212

81738213
/** The heading a signed first-touch record carries — the filing card's spelling, `## Triage`. */
81748214
const H74_AUDIT_HEADING = /^\s{0,3}#{2,6}[ \t]*Triage\b/imu;
@@ -8192,8 +8232,11 @@ export function isTriageAuditRecord(text) {
81928232
* Does H74 speak about this card at all? ISSUES only, never a PR; an OPEN card
81938233
* only (the label on a closed one is residue, H22/H39's reading); a readable
81948234
* `labels` carrying a population label and none of the exempt ones (#4690: an
8195-
* unreadable `labels` is out, never read as unlabelled); not a
8196-
* `[maintainer]`-titled card.
8235+
* unreadable `labels` is out, never read as unlabelled); a FLEET card (the
8236+
* filing card's 「`[maintainer]` cards」 read as SKILL.md's 非车队卡: a human
8237+
* author with no seat signature is never first-touched this way); not a
8238+
* main-red anchor card. The third exemption, a sub-issue inheriting its
8239+
* parent's domain or grade, costs a read and is judged in the predicate.
81978240
*/
81988241
export function h74SpeaksAbout(issue) {
81998242
if (!issue || issue.pull_request) return false;
@@ -8202,7 +8245,19 @@ export function h74SpeaksAbout(issue) {
82028245
const labels = labelNames(issue);
82038246
if (!labels.some((l) => H74_POPULATION_LABELS.includes(l))) return false;
82048247
if (labels.some((l) => H74_EXEMPT_LABELS.includes(l))) return false;
8205-
return !H74_MAINTAINER_TITLE.test(String(issue.title ?? ''));
8248+
if (!isFleetCard(issue)) return false;
8249+
return !H74_MAIN_RED_ANCHOR_TITLE.test(String(issue.title ?? ''));
8250+
}
8251+
8252+
/**
8253+
* Which cards are worth the PARENT read — H74's second gathering policy, pinned
8254+
* for the same reason as the first: only a card whose thread was READ and found
8255+
* clean would fire, so only that card buys the sub-issues parent read
8256+
* (`subIssueParentPath`, H45's fetch class). An unconsulted or unreadable
8257+
* thread buys nothing: its row is silent or UNJUDGED already.
8258+
*/
8259+
export function h74NeedsParentRead(issue, commentBodies) {
8260+
return Array.isArray(commentBodies) && h74QueuedWithoutTriageAudit(issue, commentBodies) !== null;
82068261
}
82078262

82088263
/**
@@ -8223,19 +8278,34 @@ export function needsTriageAuditComments(issue) {
82238278
* (the summary clause counts the gap); `null` consulted and UNREADABLE — the
82248279
* row fires UNJUDGED, never clean; `string[]` read.
82258280
*
8281+
* `parent` carries FOUR input states (H45's three plus the unreadable
8282+
* sentinel): `undefined` not read — the row judges without the sub-issue
8283+
* exemption; `null` the documented 404, parentless; a row — exempt when the
8284+
* card inherits its domain or grade from it; `H74_PARENT_UNREADABLE` — the
8285+
* row fires UNJUDGED, never clean and never silent.
8286+
*
82268287
* ⛔ No angle-bracket fragment and no tracker number in the sentence: it is
82278288
* rendered into a GitHub issue body, and the card it is about is the row's
82288289
* own link.
82298290
*/
8230-
export function h74QueuedWithoutTriageAudit(issue, commentBodies) {
8291+
export function h74QueuedWithoutTriageAudit(issue, commentBodies, parent) {
82318292
if (!h74SpeaksAbout(issue)) return null;
82328293
if (isTriageAuditRecord(issue.body)) return null;
82338294
if (commentBodies === undefined) return null;
82348295
if (Array.isArray(commentBodies) && commentBodies.some((b) => isTriageAuditRecord(b))) return null;
8296+
if (h74InheritsFromParent(issue, parent)) return null;
82358297
const state = labelNames(issue)
82368298
.filter((l) => H74_POPULATION_LABELS.includes(l))
82378299
.map((l) => `\`${l}\``)
82388300
.join(' + ');
8301+
if (parent === H74_PARENT_UNREADABLE) {
8302+
return (
8303+
`${UNJUDGED_MARKER} ${state} card with no triage audit record in either channel (both read) whose ` +
8304+
'sub-issues PARENT read failed this sweep — whether it inherits a domain or grade from a parent is ' +
8305+
'UNJUDGED, not settled. Read the parent by hand before acting: an unreadable relation is not an ' +
8306+
'absent one.'
8307+
);
8308+
}
82398309
if (commentBodies === null) {
82408310
return (
82418311
`${UNJUDGED_MARKER} ${state} card whose body carries no triage audit record and whose comment thread ` +
@@ -8254,7 +8324,8 @@ export function h74QueuedWithoutTriageAudit(issue, commentBodies) {
82548324
'audit` on the triage mailbox in the same round (`record-recognisers.mjs --mailbox-keys` prints the shape) ' +
82558325
'— ⛔ never a silent wait, and never a report to the maintainer in the mailbox\'s place. Report-only: ⛔ ' +
82568326
'never a label, comment or body written from this script. Exempt by design: `tracking`, `pm:epic`, ' +
8257-
'`pm:seat`, `qa-run`, `status:parked` and `queue-flake-anchor` carriers, and `[maintainer]`-titled cards.'
8327+
'`pm:seat`, `qa-run`, `status:parked` and `queue-flake-anchor` carriers; a non-fleet card (human ' +
8328+
'author, no seat signature); a main-red anchor card; a sub-issue inheriting its parent\'s domain or grade.'
82588329
);
82598330
}
82608331

@@ -17431,12 +17502,8 @@ export function h67ReadOrder(issues, cap = H67_TIMELINE_READ_CAP) {
1743117502
return at === -1 ? H67_SELECTION_ORDER_LABELS.length : at;
1743217503
};
1743317504
// Non-fleet before fleet, inside one rank only; a seat-signed body or an
17434-
// unreadable author reads as the fleet's.
17435-
const fleet = (issue) => {
17436-
if (readSignatureLine(issue?.body) !== null) return 1;
17437-
const author = artefactAuthor(issue);
17438-
return author && !author.isApp ? 0 : 1;
17439-
};
17505+
// unreadable author reads as the fleet's (`isFleetCard`, shared with H74).
17506+
const fleet = (issue) => (isFleetCard(issue) ? 1 : 0);
1744017507
const age = (issue) => {
1744117508
const parsed = Date.parse(issue?.created_at ?? '');
1744217509
return Number.isFinite(parsed) ? parsed : Infinity;
@@ -23131,6 +23198,14 @@ async function sweepInto(findings, seen, seenPrs, seenMerged, seenUnscoped, seen
2313123198
/** What H74 gets for a card: `undefined` unconsulted, `null` unreadable, else the bodies. */
2313223199
const triageAuditFor = (issue) =>
2313323200
triageAudit.unreadable.has(issue.number) ? null : triageAudit.comments.get(issue.number);
23201+
/** H74's parent read — H45's fetch class and H45's 404 reading; any other failure is the sentinel. */
23202+
const readTriageAuditParent = async (issue) => {
23203+
try {
23204+
return await rest(subIssueParentPath(OWNER_REPO, issue.number));
23205+
} catch (err) {
23206+
return err?.status === 404 ? null : H74_PARENT_UNREADABLE;
23207+
}
23208+
};
2313423209

2313523210
// H32's gathered seat markers (#11706), judged after the label pages finish:
2313623211
// the predicate needs the LANE INVENTORY, and that is a count over the
@@ -23200,7 +23275,12 @@ async function sweepInto(findings, seen, seenPrs, seenMerged, seenUnscoped, seen
2320023275
// did not list), and the thread is bought only for a body-clean member
2320123276
// (`needsTriageAuditComments`), off the shared cache.
2320223277
if (needsTriageAuditComments(issue)) await gatherTriageAuditComments(issue);
23203-
const unaudited = h74QueuedWithoutTriageAudit(issue, triageAuditFor(issue));
23278+
const auditBodies = triageAuditFor(issue);
23279+
// The sub-issue exemption is the one read bought AFTER both channels are
23280+
// clean: the sub-issues parent (H45's fetch class, same 404 reading). Its
23281+
// failure is never silent — the predicate fires UNJUDGED on the sentinel.
23282+
const auditParent = h74NeedsParentRead(issue, auditBodies) ? await readTriageAuditParent(issue) : undefined;
23283+
const unaudited = h74QueuedWithoutTriageAudit(issue, auditBodies, auditParent);
2320423284
if (unaudited) findings.push([issue, 'H74', unaudited]);
2320523285
// H29 + H30 — the same free reads, one label-set and one timestamp. Their
2320623286
// populations are covered here BY CONSTRUCTION: every H29 pair contains at
@@ -25141,7 +25221,7 @@ export const SELF_TEST_BATTERIES = Object.freeze({
2514125221
// firing case with both record shapes as its positive controls, the body
2514225222
// channel, every exemption beside a non-exempt twin, the population edges,
2514325223
// the three thread states, the text, the summary pair and the band.
25144-
'H74 first-touch audit': 32,
25224+
'H74 first-touch audit': 46,
2514525225
});
2514625226

2514725227
/** The floor on the ROSTER itself — how many batteries must be declared at all. */
@@ -28561,8 +28641,32 @@ async function selfTest() {
2856128641
b(BATTERY74, `⛔ H74 exempt: a \`${exempt}\` carrier is silent`, h74([], { labels: [{ name: 'pm:queue' }, { name: exempt }] }), null);
2856228642
}
2856328643
b(BATTERY74, 'H74 exempt control: the same card with an unrelated label beside `pm:queue` still fires', typeof h74([], { labels: [{ name: 'pm:queue' }, { name: 'tooling' }] }), 'string');
28564-
b(BATTERY74, '⛔ H74 exempt: a `[maintainer]`-titled card is silent, whatever its labels', h74([], { title: '[maintainer] rotate the release key' }), null);
28565-
b(BATTERY74, 'H74 exempt control: `[maintainer]` mid-title is not the prefix — fires', typeof h74([], { title: 'ask [maintainer] about the key' }), 'string');
28644+
// 「`[maintainer]` cards」 read as SKILL.md's 非车队卡: a human author whose body opens with no seat signature.
28645+
const HUMAN74 = { login: 'yibowang', type: 'User' };
28646+
const APP74 = { login: 'objectstack-fleet[bot]', type: 'Bot' };
28647+
b(BATTERY74, '⛔ H74 exempt: a non-fleet card — human author, no seat signature — is silent, whatever its labels', h74([], { user: HUMAN74, body: '## 问题\n\n导出按钮无反应' }), null);
28648+
b(BATTERY74, 'H74 exempt control: the same human account with a SEAT-SIGNED body is the fleet\'s — fires', typeof h74([], { user: HUMAN74 }), 'string');
28649+
b(BATTERY74, 'H74 exempt control: an App author with an unsigned body is the fleet\'s — fires', typeof h74([], { user: APP74, body: '## 问题\n\n导出按钮无反应' }), 'string');
28650+
b(BATTERY74, '⛔ H74 exempt control: an UNREADABLE author is never promoted to a human\'s — fires', typeof h74([], { user: undefined, body: '## 问题\n\n导出按钮无反应' }), 'string');
28651+
b(BATTERY74, 'H74 fleet reader: the one `isFleetCard` drives H67\'s order leg too — a human card sorts before a fleet card in one rank', h67ReadOrder([{ number: 2, labels: [{ name: 'pm:queue' }], user: APP74, body: 'x' }, { number: 1, labels: [{ name: 'pm:queue' }], user: HUMAN74, body: 'x' }]).read.map((i) => i.number).join(), '1,2');
28652+
// A main-red anchor card, by its measured title openings.
28653+
for (const title of ['main red: Dogfood Regression Gate — GET /packages/:id/customizations 404s', 'main red (Dogfood Regression Gate 1/3) since 21c4bdd8: two pins', 'main red since #24009: the census still pins route-ledger.ts', 'Main-red: Spec property liveness cites a retired key', '4-hourly full run: red on main (CI)', 'nightly-tiers: red on main']) {
28654+
b(BATTERY74, `⛔ H74 exempt: a main-red anchor title is silent — ${title.slice(0, 24)}…`, h74([], { title }), null);
28655+
}
28656+
b(BATTERY74, 'H74 exempt control: a card ABOUT main-red (`pm-dispatch(main-red): …`) stays in the population — fires', typeof h74([], { title: 'pm-dispatch(main-red): when the anchor card closes, the held seats are rung' }), 'string');
28657+
b(BATTERY74, 'H74 exempt control: `red on main` without the prefix colon is prose, not an anchor — fires', typeof h74([], { title: 'rest: the export route goes red on main-like boards' }), 'string');
28658+
// A sub-issue inheriting its parent's domain or grade — the read bought last.
28659+
const domainParent = { number: 29600, labels: [{ name: 'tracking' }, { name: 'domain:server' }] };
28660+
const gradeParent = { number: 29601, labels: [{ name: 'tracking' }, { name: 'priority:p2' }] };
28661+
const strangerParent = { number: 29602, labels: [{ name: 'tracking' }, { name: 'domain:ui' }, { name: 'priority:p1' }] };
28662+
b(BATTERY74, '⛔ H74 exempt: a sub-issue whose parent carries its `domain:*` is silent', h74QueuedWithoutTriageAudit(card74(), [], domainParent), null);
28663+
b(BATTERY74, '⛔ H74 exempt: …or its `priority:*`', h74QueuedWithoutTriageAudit(card74(), [], gradeParent), null);
28664+
b(BATTERY74, 'H74 exempt control: a parent sharing NEITHER label inherits nothing — fires', typeof h74QueuedWithoutTriageAudit(card74(), [], strangerParent), 'string');
28665+
b(BATTERY74, 'H74 exempt control: a parentless card (the documented 404) fires, and an unread parent fires as before', [typeof h74QueuedWithoutTriageAudit(card74(), [], null), typeof h74([])].join(), 'string,string');
28666+
b(BATTERY74, 'H74 parent states: the UNREADABLE sentinel fires UNJUDGED, never clean and never silent', (() => { const r = String(h74QueuedWithoutTriageAudit(card74(), [], H74_PARENT_UNREADABLE)); return r.startsWith(UNJUDGED_MARKER) && isUnjudgedFinding(r) && r.includes('PARENT read failed'); })(), true);
28667+
b(BATTERY74, '⛔ H74 parent states: a parent row never EXCUSES a card an audit already answers, and never reaches an out-of-scope card', [h74QueuedWithoutTriageAudit(card74(), [AUDIT74], strangerParent), h74QueuedWithoutTriageAudit(card74({ state: 'closed' }), [], strangerParent)].join(), ',');
28668+
b(BATTERY74, 'H74 parent gating: only a card whose thread was READ and found clean buys the parent read', [h74NeedsParentRead(card74(), []), h74NeedsParentRead(card74(), [AUDIT74]), h74NeedsParentRead(card74(), null), h74NeedsParentRead(card74(), undefined), h74NeedsParentRead(card74({ title: 'main red: x' }), [])].join(), 'true,false,false,false,false');
28669+
b(BATTERY74, 'H74 inherit reader: a null, undefined or sentinel parent inherits nothing, never a crash', [h74InheritsFromParent(card74(), null), h74InheritsFromParent(card74(), undefined), h74InheritsFromParent(card74(), H74_PARENT_UNREADABLE), h74InheritsFromParent(card74(), { labels: undefined })].some(Boolean), false);
2856628670
b(BATTERY74, '⛔ H74 population: a closed card, a PR row, an unreadable `labels` and a missing row are all silent, none a crash', [h74([], { state: 'closed' }), h74([], { pull_request: {} }), h74([], { labels: undefined }), h74QueuedWithoutTriageAudit(undefined, [])].every((v) => v === null), true);
2856728671
b(BATTERY74, 'H74 population: a null body is no crash and no record — the card still fires on an empty thread', typeof h74([], { body: null }), 'string');
2856828672
b(BATTERY74, 'H74 exempt list: the constant carries exactly the filing card\'s six labels', [...H74_EXEMPT_LABELS].sort().join(), ['pm:epic', 'pm:seat', 'qa-run', 'queue-flake-anchor', 'status:parked', 'tracking'].join());

0 commit comments

Comments
 (0)