Skip to content

Commit f850ac1

Browse files
committed
docs(pm-dispatch): an unread card thread leaves the review seat's independence bar unjudged
Contract review r1's S4: when the delivered card's comments could not be read, check-owed-reviews no longer prints a seated review seat as free. ownAccountsOf returns null, reviewSeatsOf reads the state `unjudged` (the seated seats named), and the taker names them only with the caveat, the lane's own seat beside them. Two new pins (84 cases). Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WGufzhXV3ABCbTkU1iRRn7
1 parent 6e66dd4 commit f850ac1

1 file changed

Lines changed: 18 additions & 6 deletions

File tree

‎scripts/pm/check-owed-reviews.mjs‎

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,10 @@
6262
* review seat the review seats (`review#N`) against the same posts: `seated`
6363
* (and which), `barred` (every seated one sits on this PR's own
6464
* implementing or dispatching account — `lanes/review.md` 独立性;
65-
* a barred seat is never named), `stood-down`, `absent`,
66-
* `unknown` (roster unread).
65+
* a barred seat is never named), `unjudged` (seated ones, but the
66+
* card's comments could not be read, so the dispatching account
67+
* is unknown and none is printed as free), `stood-down`,
68+
* `absent`, `unknown` (roster unread).
6769
* status `TAKE` (owed, nobody on it, a trigger holds), `WAIT` (owed,
6870
* nobody on it, no trigger yet — the lane's own, a lane's live hold
6971
* included), `HELD` (a live claim on this head), `DONE` (a record on
@@ -258,8 +260,10 @@ export function reviewSeatsOf(seats, ownAccounts = new Set()) {
258260
const rows = seats.filter((s) => s?.lane === REVIEW_LANE);
259261
const name = (s) => `${REVIEW_LANE}#${Number(s.seat) > 0 ? Number(s.seat) : 1}`;
260262
const seatedRows = rows.filter((s) => s.status === 'seated');
263+
// The card's comments could not be read: the dispatching account is unknown, so no seated seat is printed as free.
264+
if (ownAccounts === null && seatedRows.length > 0) return { state: 'unjudged', seated: seatedRows.map(name).sort(), barred: [] };
261265
// `lanes/review.md` 独立性: a review seat never reviews a PR its own account dispatched or implemented.
262-
const isBarred = (s) => typeof s.account === 'string' && ownAccounts.has(s.account.toLowerCase());
266+
const isBarred = (s) => typeof s.account === 'string' && ownAccounts !== null && ownAccounts.has(s.account.toLowerCase());
263267
const seated = seatedRows.filter((s) => !isBarred(s)).map(name).sort();
264268
const barred = seatedRows.filter(isBarred).map(name).sort();
265269
if (seated.length > 0) return { state: 'seated', seated, barred };
@@ -271,9 +275,11 @@ export function reviewSeatsOf(seats, ownAccounts = new Set()) {
271275
* The accounts no review seat of which may take this PR (`lanes/review.md` 独立性): the IMPLEMENTING
272276
* account — the login in the body's `claude-projects-attribution` line — and the DISPATCHING account —
273277
* the login signing each `Claim:` comment on the delivered card. Lowercased logins; an unreadable or
274-
* absent line names no account. Pure.
278+
* absent line names no account. `null` when the card's comments could not be read (`cardComments`
279+
* null beside a delivered card): the dispatching account is then UNJUDGED, not absent. Pure.
275280
*/
276281
export function ownAccountsOf(pull) {
282+
if (pull?.card !== null && pull?.card !== undefined && pull?.cardComments === null) return null;
277283
const out = new Set();
278284
const m = /claude-projects-attribution:\s*(\{[^\n]*?\})/.exec(String(pull?.body ?? ''));
279285
if (m) {
@@ -383,6 +389,10 @@ export function takerOf(status, handoff, lane, review = { state: 'unknown', seat
383389
return `${named} first`;
384390
}
385391
if (review?.state === 'unknown') return laneGone ? `a review seat, or ${seat} next on duty` : `${seat} or a review seat`;
392+
if (review?.state === 'unjudged') {
393+
const named = `a review seat (${review.seated.join(', ')}) unless on this PR's own account (card comments unread)`;
394+
return laneGone ? `${named}, or ${seat} next on duty` : `${named}, or ${seat}`;
395+
}
386396
if (review?.state === 'barred') {
387397
const why = `review seat ${review.barred.join(', ')} is on this PR's own account`;
388398
return laneGone ? `${seat} next on duty (${why})` : `${seat} (${why})`;
@@ -573,8 +583,8 @@ const SELF_TEST_BATTERIES = Object.freeze({
573583
'the owed face: paths, the Clause-② line, the label — drafts included, nothing else owes': 9,
574584
'the record: on this head (by whom, as stated), on an earlier head, none, unjudged': 8,
575585
'the triggers: (a) hand-off or label the same round, (b) a full round, (c) the lane seat': 10,
576-
// 10 → 14: S2 of PR #29967's contract review — a review seat on the PR's own account is barred (`lanes/review.md` 独立性).
577-
'review-seat first: a seated review seat takes before the lane, the lane joins after a further round, and takes alone while none is seated': 14,
586+
// 10 → 14 → 16: S2 and S4 of PR #29967's contract review — a review seat on the PR's own account is barred (`lanes/review.md` 独立性); an unread card thread leaves the bar unjudged.
587+
'review-seat first: a seated review seat takes before the lane, the lane joins after a further round, and takes alone while none is seated': 16,
578588
'a live claim holds the head and a record settles it; a lapsed claim holds nothing': 6,
579589
'the live instance: two Tier H drafts with a hand-off on the current head list as TAKE under (a)': 4,
580590
'the live read: every read through one reader; a failed read is INCOMPLETE, never clean': 6,
@@ -693,6 +703,8 @@ export function selfTest() {
693703
t('⭐ S2: the only seated review seat sits on the IMPLEMENTING account ⇒ barred, the lane\'s own seat takes at once — no further round, no triage', (() => { const r = rv({ body: ATTR, headCommittedAt: fresh, labels: [{ name: WAITING_LABEL }] }, OWN); return r.status === 'TAKE' && r.reviewSeat === 'barred' && r.reviewSeats.length === 0 && r.reviewBarred.join() === 'review#1' && r.taker === "the domain:ui seat (review seat review#1 is on this PR's own account)" && !/triage/.test(renderRow(r)) && renderRow(r).includes('review seat barred (barred review#1)'); })());
694704
t('⭐ S2: a review seat on the DISPATCHING account (the card claim\'s signer) is barred too; a review seat on a third account still takes first, the barred one named', (() => { const r = rv({ card: 25922, cardComments: CLAIMED, headCommittedAt: fresh, labels: [{ name: WAITING_LABEL }] }, [...SEATED, { lane: REVIEW_LANE, seat: 1, status: 'seated', account: 'OS-Zhuang' }, { lane: REVIEW_LANE, seat: 2, status: 'seated', account: 'os-other' }]); const alone = rv({ card: 25922, cardComments: CLAIMED, headCommittedAt: '2026-10-28T10:59:00Z' }, [...SEATED, { lane: REVIEW_LANE, seat: 1, status: 'seated', account: 'os-zhuang' }]); return r.reviewSeat === 'seated' && r.reviewSeats.join() === 'review#2' && r.reviewBarred.join() === 'review#1' && r.taker === 'a review seat (review#2) first' && alone.taker === "the domain:ui seat (review seat review#1 is on this PR's own account)"; })());
695705
t('S2: the lane seat gone and the only review seat barred ⇒ the lane\'s next seat; a review seat with no account on its post is never barred', rv({ body: ATTR, headCommittedAt: fresh }, [{ lane: REVIEW_LANE, seat: 1, status: 'seated', account: 'os-support-ai' }]).taker === "the domain:ui seat next on duty (review seat review#1 is on this PR's own account)" && rv({ body: ATTR, headCommittedAt: fresh, labels: [{ name: WAITING_LABEL }] }, [...SEATED, { lane: REVIEW_LANE, seat: 1, status: 'seated', account: null }]).taker === 'a review seat (review#1) first');
706+
t('⭐ S4: the card\'s comments unreadable ⇒ own accounts null, the bar UNJUDGED: a seated review seat is named with the caveat, never as free, and the lane seat beside it; with no seated review seat the states read as before', (() => { const unread = { card: 25922, cardComments: null, headCommittedAt: fresh, labels: [{ name: WAITING_LABEL }] }; const r = rv(unread, OWN); const gone = rv({ card: 25922, cardComments: null, headCommittedAt: fresh }, [{ lane: REVIEW_LANE, seat: 1, status: 'seated', account: 'os-support-ai' }]); return ownAccountsOf({ card: 25922, cardComments: null }) === null && ownAccountsOf({ card: null, cardComments: [] }).size === 0 && r.reviewSeat === 'unjudged' && r.reviewSeats.join() === 'review#1' && r.reviewBarred.length === 0 && r.taker === "a review seat (review#1) unless on this PR's own account (card comments unread), or the domain:ui seat" && gone.taker === "a review seat (review#1) unless on this PR's own account (card comments unread), or the domain:ui seat next on duty" && rv(unread, SEATED).reviewSeat === 'absent' && rv(unread, [...SEATED, { lane: REVIEW_LANE, seat: 1, status: 'paused' }]).reviewSeat === 'stood-down' && renderRow(r).includes('review seat unjudged review#1'); })());
707+
t('S4: a readable but empty comment thread on the card, or no card at all, judges the bar — the attribution login alone bars', rv({ card: 25922, cardComments: [], body: ATTR, headCommittedAt: fresh, labels: [{ name: WAITING_LABEL }] }, OWN).reviewSeat === 'barred' && rv({ body: ATTR, headCommittedAt: fresh, labels: [{ name: WAITING_LABEL }] }, OWN).reviewSeat === 'barred');
696708
t('the printed row carries the review-seat reading and the taker', renderRow(rv({ headCommittedAt: fresh, labels: [{ name: WAITING_LABEL }] })).includes(`review seat seated review#1,review#2 → (a) label · take: ${BOTH} first`) && renderRow(rv({ headCommittedAt: fresh, labels: [{ name: WAITING_LABEL }] }, SEATED)).includes('review seat absent → (a) label · take: the domain:ui seat (no review seat seated)'));
697709

698710
battery('a live claim holds the head and a record settles it; a lapsed claim holds nothing');

0 commit comments

Comments
 (0)