Skip to content

Commit b1eb93c

Browse files
committed
pm(labels): stop creating needs:contract-review and drop its last code legs
ensure-pm-labels.sh no longer creates the label (the GitHub object is the seat's to remove); check-changeset-no-major reads the Clause-② line alone — the label carrier axis, the check-half-states import and the #17229 battery (floor 20 → 19) go; check-clause2-carriers.mjs keeps a LOCAL retired constant because its retired --pair sweep still compares it and the file cannot be deleted yet (the queue guard lazily imports its record recognisers); the label-desc-cap specimen, the workflow comments, the CONTRACT_REVIEW_TIER docblock and post-stamped's near-miss row stop naming the label or --pair. Claude-Session: https://claude.ai/code/session_01Wnstp2kTth7sGXfr8fXypc Co-authored-by: Claude <noreply@anthropic.com>
1 parent 6a6d2b9 commit b1eb93c

10 files changed

Lines changed: 136 additions & 366 deletions

‎.github/workflows/lint.yml‎

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -916,9 +916,9 @@ jobs:
916916
# over-long -d means the label is never created on a repo that lacks it
917917
# and a rerun can never repair it. Headroom is thin (one description sits
918918
# at exactly 100), and the only previous enforcement was the author
919-
# counting by hand. Measured in characters, never bytes: the live
920-
# needs:contract-review description is 97 characters / 101 bytes, so a
921-
# byte-length guard would red a label GitHub accepts. Unconditional, like
919+
# counting by hand. Measured in characters, never bytes: the specimen
920+
# that fixed the measure was 97 characters / 101 bytes, so a byte-length
921+
# guard would red a label GitHub accepts. Unconditional, like
922922
# the other self-tested gates above.
923923
- name: PM label description cap
924924
run: pnpm check:pm-label-desc-cap
@@ -1254,16 +1254,15 @@ jobs:
12541254
# else's pair is a fact about the board, not about the PR that happens to
12551255
# run CI next, and failing that PR would punish the wrong actor.
12561256
#
1257-
# ⛔ Do not "promote" the live sweep into this step. The script's own
1258-
# `--pair <pr>` mode is the per-PR predicate, and it is a PRE-ARM tool a
1259-
# seat runs against its own pair — deliberately not a merge gate, because
1260-
# a new blocking context over board state is a required-set change and
1261-
# the required set is the maintainer's.
1257+
# ⛔ Do not "promote" the live sweep into this step. The script's `--pair`
1258+
# landing pre-check is retired (ruling record 5770886272 on #19061) and
1259+
# nothing runs it; the file stays only because the Governed Surface Queue
1260+
# Guard lazily imports its contract-review record recognisers and
1261+
# `--template`, and this self-test is what keeps those loadable.
12621262
#
12631263
# The self-test is offline and imports `check-half-states.mjs` for the
1264-
# label constant, the `prDeliversCard` delivery relation and the shared
1265-
# PREREQUISITE-NOT-MET exit code — one source for each, so this checker
1266-
# and H31 cannot come to disagree about which PR delivers which card.
1264+
# `prDeliversCard` delivery relation and the shared PREREQUISITE-NOT-MET
1265+
# exit code — one source for each.
12671266
- name: Clause-② carrier checker self-test
12681267
run: pnpm check:pm-clause2-carriers
12691268

‎.github/workflows/pr-automation.yml‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -784,8 +784,8 @@ jobs:
784784
WHAT THE AXIS DOES NOT JUDGE, in its own words: "WHICH of the
785785
packages above received that widening is NOT something this gate
786786
can read, and it does not claim to. Clause ② is declared ONCE, FOR
787-
THE PR" -- neither the carrier label nor the declaration line in
788-
the PR body names a package. The axis cross-checks a declaration
787+
THE PR" -- the declaration line in the PR body names no package.
788+
The axis cross-checks a declaration
789789
against the levels; it never reads the diff to find the act.
790790
Judging the act is still yours, this paragraph is still where the
791791
rule lives, and everything in it the axis does not reach is still

‎scripts/check-changeset-no-major.mjs‎

Lines changed: 85 additions & 300 deletions
Large diffs are not rendered by default.

‎scripts/pm/check-clause2-carriers.mjs‎

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -710,7 +710,6 @@ import { isEntrypoint } from '../invoked-as.mjs';
710710
import {
711711
CLAIM_COMMENT_MARKER,
712712
CONTRACT_REVIEW_HEADING_MARKER,
713-
CONTRACT_REVIEW_LABEL,
714713
DEFAULT_SWEEP_REPO,
715714
EXIT_PREREQUISITE_NOT_MET,
716715
H51_SHA_MIN_HEX,
@@ -726,7 +725,6 @@ import {
726725
claimGovernance,
727726
claimedBranches,
728727
governingClaim,
729-
isGateSemanticLabel,
730728
labelNames,
731729
latestMarkedComment,
732730
markerMatches,
@@ -756,6 +754,23 @@ import { CONTRACT_REVIEW_TIER } from './dispatch-gates.mjs';
756754
// this file is the SWEEP around them, never a second reading of them.
757755
import { CLAUSE2_ARMS, CLAUSE2_VALUES, quoteLine, readClause2Line } from './clause2-line.mjs';
758756

757+
/**
758+
* ⛔ RETIRED — the clause-② gate label and its dual-carrier discipline left the
759+
* tree with ruling record 5770886272 on #19061 (letter B): `ensure-pm-labels.sh`
760+
* no longer creates it, the queue guard's label leg is gone, and the half-state
761+
* rows that patrolled it (H31/H35/H51/H53/H61) are deleted. The constant lives
762+
* HERE, in the one file that still reads it, because this file's `--pair` sweep
763+
* — itself retired by the same ruling, and run by nothing — compares the label
764+
* on its C1/C3 rows and the self-test pins that comparison. The file survives
765+
* only until the record recognisers and `--template` the queue guard imports
766+
* (`locateReviewOfRecord`, `deliveredCardNumber`, `servedTierStands`,
767+
* `isModelIdentifierToken`, `REVIEW_OF_RECORD_THREADS`,
768+
* `REVIEW_OF_RECORD_LOCATION`) are extracted into a module of their own, and
769+
* this constant goes with it. ⛔ Nothing new may import it.
770+
*/
771+
const CONTRACT_REVIEW_LABEL = 'needs:contract-review';
772+
const isGateSemanticLabel = (name) => String(name ?? '') === CONTRACT_REVIEW_LABEL;
773+
759774
/**
760775
* The lanes that OWE a review of record on EVERY round they deliver -- the
761776
* maintainer's lane rule, restated on #18536 and carried into

‎scripts/pm/check-label-desc-cap.mjs‎

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,9 @@
2828
* ## CHARACTERS, not bytes — the measure is the whole point
2929
*
3030
* The cap is on characters, and the descriptions in this file are not ASCII:
31-
* they carry em dashes, circled digits and CJK. The live specimen is
32-
* `needs:contract-review`, whose description is 97 characters and 101 BYTES —
33-
* accepted by GitHub, and red under a naive byte-length guard. A byte guard
31+
* they carry em dashes, circled digits and CJK. The specimen that fixed the
32+
* measure was a label description of 97 characters and 101 BYTES — accepted
33+
* by GitHub, and red under a naive byte-length guard. A byte guard
3434
* would therefore fail a label that works, and the fix a reader would reach for
3535
* (rewriting a correct description to please the gate) is worse than no gate.
3636
*
@@ -379,9 +379,10 @@ function selfTest() {
379379
}
380380
};
381381

382-
// The measure: characters, not bytes, not UTF-16 units.
382+
// The measure: characters, not bytes, not UTF-16 units. The specimen is the
383+
// retired gate label's description, kept as a fixture for its byte/char gap.
383384
const live = 'Clause-② enqueue gate: dispatched below contract-review tier — blocked until the review clears it';
384-
t('the live 97-char/101-byte description measures 97', charLength(live), 97);
385+
t('the 97-char/101-byte specimen measures 97', charLength(live), 97);
385386
t('…and is over the cap only if you measure BYTES', Buffer.byteLength(live, 'utf8') > MAX_DESCRIPTION_CHARS, true);
386387
t('…so it passes this gate', violations([{ label: 'x', line: 1, expanded: live, unresolved: [] }]).length, 0);
387388
t('an astral character counts once, not twice', charLength('🚀'), 1);

‎scripts/pm/check-widening-tells.mjs‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,8 +161,8 @@
161161
* contract this is.
162162
*
163163
* **It writes nothing and hangs no label.** Same call `check-clause2-carriers`
164-
* and `check-half-states` make: a checker that hung `needs:contract-review`
165-
* would be issuing the review verdict, which is 自查放行. ⛔ No new label and no
164+
* and `check-half-states` make: a checker that hung a review-gate label would
165+
* be issuing the review verdict, which is 自查放行. ⛔ No new label and no
166166
* new claim-line syntax exist because of this file — #16448 forbids both, and
167167
* the reader it uses is the sibling's existing `Clause-②:` reader.
168168
*

‎scripts/pm/clause2-line.mjs‎

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,13 @@
3030
* `pnpm install`, and both gates stage their own first-party closure into a
3131
* throwaway repo for their I1/I2 fixtures.
3232
*
33-
* ⚠️ This file is STEP ① of that ruling and nothing else of it has landed yet:
34-
* the `needs:contract-review` label, `check-clause2-carriers.mjs --pair`, the
35-
* double-carrier discipline and the independence pair are retired BY THE RULING
36-
* and still RUN in this tree — the label exists, `--pair` still judges its C4 /
37-
* C6 / C7 rows, and `check-governed-merges.mjs` still prescribes `--pair N` at
38-
* 0 — until step ② (the rule text) and step ③ (the label) land. ⛔ Read the
39-
* paragraph above as what was DECIDED, never as what this tree already does.
33+
* This file was STEP ① of that ruling; step ② retired the rule text and step ③
34+
* the label — its constant, the queue guard's label leg, the half-state rows
35+
* that patrolled it and the changeset gate's label carrier are gone from this
36+
* tree. What still stands beside this reader is `check-clause2-carriers.mjs`,
37+
* kept only because the queue guard's Tier S leg lazily imports its record
38+
* recognisers and `--template`; its `--pair` sweep is retired by the ruling and
39+
* nothing runs it, and the file goes once those recognisers are extracted.
4040
*
4141
* ## ⛔ One reader, and this is it
4242
*

‎scripts/pm/dispatch-gates.mjs‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12228,9 +12228,9 @@ export function residueLines(
1222812228
* reads — clause ②'s CONTRACT-REVIEW tier: the tier the clause-② REVIEW runs
1222912229
* at, both halves of it — the spec and skills lanes' review of every round
1223012230
* they deliver (a card that changes contract accept/reject behaviour or
12231-
* widens the public surface is spec-lane work, whichever seat found it), and
12232-
* the `needs:contract-review` re-review sub-round (its opening self-check
12233-
* reads this). The BUILD of such a card is at the default judgment tier, so
12231+
* widens the public surface is spec-lane work, whichever seat found it) and
12232+
* the `Served-tier:` line every `## Contract review` record carries. The
12233+
* BUILD of such a card is at the default judgment tier, so
1223412234
* this constant is a review tier and never a dispatch mandate. Declared HERE
1223512235
* and only here, as a constant, so a model upgrade is a one-line change in one
1223612236
* file — the clause-① mandate rows below read it, the self-test compares
@@ -12251,7 +12251,7 @@ export function residueLines(
1225112251
* and never self-reviewed. A tier that has been RETIRED never comes back, and
1225212252
* the two cases differ on WHO MAY ACT: a seat reading 「⛔ 不许降档」 onto a
1225312253
* vanished tier holds its whole lane forever, and a seat picking the
12254-
* replacement itself is the silent downgrade the fuse exists to stop. So a
12254+
* replacement itself is the silent downgrade this rule exists to stop. So a
1225512255
* retirement is a maintainer ruling and ⛔ never a seat's reading — and when
1225612256
* the ruling lands, this VALUE is the one line that moves. The ceiling of the
1225712257
* ladder `tierLines` prints is DERIVED from it ({@link TIER_CEILING}) so the
@@ -12277,7 +12277,7 @@ export function residueLines(
1227712277
* that session; this constant is a property of the lane's governance, and the
1227812278
* two ⛔ never trade places.
1227912279
*
12280-
* Rulebook: `.claude/skills/pm-dispatch/SKILL.md` 「入队与落地」 — the clause-② gate and the `needs:contract-review` review-chain bullets.
12280+
* Rulebook: `.claude/skills/pm-dispatch/references/contract-review.md` — the review of record and its `Served-tier:` line.
1228112281
*/
1228212282
export const CONTRACT_REVIEW_TIER = 'claude-fable-5-1';
1228312283

‎scripts/pm/ensure-pm-labels.sh‎

Lines changed: 1 addition & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -297,35 +297,6 @@ for R in objectstack-ai/objectstack objectstack-ai/objectui objectstack-ai/cloud
297297
gh label create pm:epic -R "$R" -c 5319e7 -d "Reserved by a dedicated epic PM, parent or sub-issue; other PMs never take it; never with pm:queue" 2>/dev/null || true
298298
done
299299

300-
# needs:contract-review — the clause-② enqueue gate's re-review chain (SKILL.md
301-
# 入队与落地): a PR whose ACTUAL diff touches the contract surface — or whose
302-
# card's claim comment declares `Clause-②: yes` (the content limb, judged from
303-
# the card, path-independent) — but was dispatched below the contract-review
304-
# tier waits outside the queue under this label until the review sub-round
305-
# clears it. Named consumers: the enqueue gate and the triage sub-round's label
306-
# query. Main repo only — the contract surface (packages/spec) lives here. The
307-
# label names WHAT is reviewed, never a model; the tier's single source is
308-
# CONTRACT_REVIEW_TIER in scripts/pm/dispatch-gates.mjs. The -d below is kept
309-
# ≤100 chars (the hard cap — see the header). It did NOT match the live label
310-
# object: measured 2026-08-20, that object was grey with an EMPTY description,
311-
# so the claim this comment used to make had never been true — nothing had ever
312-
# checked it. `--reconcile` is what makes it true.
313-
#
314-
# ⚠️ WHAT THIS FILE DOES AND DOES NOT DO, because a card read it the other way
315-
# (#13922): everything here reconciles the label OBJECT — its colour and its
316-
# description. It says nothing about whether the label is HUNG correctly on any
317-
# pair, and it never did. Two other files answer that, and neither is this one:
318-
# * `scripts/pm/check-half-states.mjs` H31 — the two carriers of one gate
319-
# compared across the whole board, on the half-state patrol's schedule.
320-
# * `scripts/pm/check-clause2-carriers.mjs` — the same comparison anchored on
321-
# ONE pair (`--pair <pr>`, a pre-arm predicate), plus the two readings H31
322-
# cannot make: the DECLARATION limb quoted three paragraphs up has no other
323-
# reader in this repo, and a gate missing from BOTH carriers is a carrier
324-
# comparison's silent case.
325-
# ⛔ None of the three writes the label. Hanging or clearing a review gate from
326-
# a script would be issuing the review verdict, which is 自查放行.
327-
gh label create needs:contract-review -R objectstack-ai/objectstack -c d93f0b -d "Clause-② enqueue gate: dispatched below contract-review tier — blocked until the review clears it" 2>/dev/null || true
328-
329300
# needs:pack-smoke — the opt-in pre-merge pack smoke (#14214, derived from the
330301
# #14000 ruling; maintainer-approved 2026-09-01, director batch #23, 「同意」).
331302
# Its named consumer is a GATE rather than a query: the job guard in
@@ -408,8 +379,7 @@ gh label create repo:hotcrm -R objectstack-ai/objectstack -c fbca04 -d "Seam c
408379
#
409380
# ⛔ Two acts this record deliberately does NOT perform. Stripping the label off
410381
# those 4 live carriers is a BOARD act for the PM at the landing window —
411-
# nothing in this file ever hangs or clears a label on a card (the
412-
# needs:contract-review block above states the same discipline). And the
382+
# nothing in this file ever hangs or clears a label on a card. And the
413383
# `repo:*` predicate in `scripts/pm/check-half-states.mjs` H14 stays LITERAL:
414384
# narrowing it is a predicate change ruled on #13992, never a side effect of a
415385
# vocabulary record. SKILL.md names no `repo:objectstack` (checked 2026-09-01),

‎scripts/pm/post-stamped.mjs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1923,7 +1923,7 @@ export function claimKeyedLineRefusals(body) {
19231923
rows.push({ key: 'Thread-read', line: keyLine(text, 'Thread-read'), why: `the value reads \`${offendingSpan(thread.value)}\`, which is neither one comment id nor \`none\` — H50 compares it for EQUALITY against one id, so no thread makes this match.` });
19241924
const clause = readClause2Line(text);
19251925
if (clause !== null && clause.kind !== 'declared')
1926-
rows.push({ key: 'Clause-②', line: clause.line, why: clause.kind === 'malformed' ? 'the value slot holds something `readClause2Line` cannot grade — the two spellings are the closed set.' : `\`readClause2Line\` reads this as a NEAR MISS (${clause.reason}), ⛔ not a declaration — \`check-clause2-carriers.mjs --pair\` answers exit 4 on it.` });
1926+
rows.push({ key: 'Clause-②', line: clause.line, why: clause.kind === 'malformed' ? 'the value slot holds something `readClause2Line` cannot grade — the two spellings are the closed set.' : `\`readClause2Line\` reads this as a NEAR MISS (${clause.reason}), ⛔ not a declaration — the changeset and ADR-0087 gates read it as absent.` });
19271927
return rows;
19281928
}
19291929

0 commit comments

Comments
 (0)