Skip to content

Commit 9e7fbd3

Browse files
pm(scripts): retired lane aliases are written as domain:server at the label doors; domain:rest joins the retired block (#30841)
<!-- claude-projects-attribution: {"github_login":"os-tesla"} --> _Requested by **Tesla** · [project thread](https://claude.ai/code/project/chan_01AXsQkLFKRdQCqQ3RTr6Jao?thread=cmsg_01AXsQkLFKRdQCqQ3RTr6JaoHnQXbeW1Xvd4ozovU9hTSm)_ Fixes #30827 Clause-②: no Metadata-protocol: none ## 维护者速读 - 改了什么:给卡片打标签的三个入口(issue-create、label-write、fleet-write 中继 CLI)遇到退役车道标签 `domain:engine` / `domain:services` / `domain:cli` / `domain:rest` 时,自动改写成 `domain:server` 再写,并在输出里写一行 `label domain:engine → domain:server (retired alias)`;`domain:rest` 补进退役标签清单。 - 为什么改:退役标签对象按规则保留,GitHub 打标签时又会静默新建不存在的标签,所以删不掉也拦不住误用;11-05/06 一个席位新开的 7 张卡全打了 `domain:engine`,每张都要分诊手工改标。 - 风险与回滚:只改写、从不拒绝,不是新门禁;`--remove` / `labels_remove` 保持原样,分诊照旧能摘掉旧标签;回滚即还原本 PR,五个文件均为内部工具脚本,不发布。 - 先看:`scripts/pm/fleet-write/dispatch.mjs` 的 `rewriteRetiredLaneLabels`(唯一改写函数);`scripts/pm/label-write.mjs` 的 `parseOptions`(改写放在算目标集之前,回读才对得上);`scripts/pm/check-half-states.mjs` 的 `LANE_LABEL_ALIASES`(唯一别名表,新增 `domain:rest` 一行)。 - 两分钟验证:`node scripts/pm/label-write.mjs --self-test`,预期最后一行 `✓ label-write self-test: 144 cases pass across 12 batteries`。 - 需要你做的:无,席位落地。 ## What changes - **One alias table.** `LANE_LABEL_ALIASES` in `scripts/pm/check-half-states.mjs` already maps the retired lane spellings to `domain:server` for every lane comparison on the board. It gains one row, `'domain:rest': 'domain:server'`, and its self-test pin now names the four spellings. No second table and no new module: all three doors already import from this module's graph. - **One rewrite function.** `rewriteRetiredLaneLabels` and `retiredLaneRewriteLine` in `scripts/pm/fleet-write/dispatch.mjs` read that map. A retired spelling becomes `domain:server`. A duplicate that the rewrite itself made (`domain:engine` next to `domain:server`) collapses to one, so the validator's "named twice" never fires on it. A duplicate the caller wrote is still left for the validator. ⛔ It rewrites and never refuses. - **Three doors**, each printing the read-back words `label domain:engine → domain:server (retired alias)`: - `fleet-write/dispatch.mjs` CLI (`--actions-file`): rewrites the `labels` of `labels_add`, `issue_create` and `pr_create` before packing, with one stderr line per rewrite, dry run included. `labels_remove` is deliberately left literal. - `issue-create.mjs`: `planFrom` rewrites `--label`, so both the direct and relay transports send `domain:server`. The dry run adds a `rewritten :` line and the read-back line appends the rewrite. - `label-write.mjs`: `parseOptions` rewrites `--add` before step ② computes the target, so step ④'s read-back diffs against the label that really lands. Step ② prints `② rewritten — …`. `--remove` stays literal, so triage's relabel (`--add domain:server --remove domain:engine`) works as before. - The rewrite sits at the doors, ⛔ not inside `packRequest`. label-write diffs its read-back against the target it computed, so a rewrite below that target would read back as a stripped label (re-add once, then exit 4). - **`scripts/pm/ensure-pm-labels.sh`:** `domain:rest` joins the retired block next to the other three, with the same description shape: "Retired 2026-11-06, alias of domain:server (rest surface); the relay writes domain:server instead" (97 characters). The header comments now say four alias rows and name the write doors. - No change to `fleet-write/{ops,validate,execute}.mjs`, no new gate, no workflow change. ## Tests New self-test batteries, each declared on its file's frozen roster with the floor raised by one: - `fleet-write/dispatch.mjs`: "retired lane aliases at the seat's door", 13 cases. Covers each of the four spellings, the rows read from the map, live `domain:*` and non-lane controls, rewrite-made vs caller-made duplicates, the three adding ops vs `labels_remove`, the rewritten stroke packing through the shared validator, and an end-to-end CLI dry run whose payload carries `domain:server` with the stderr line. Floor 27 → 28. - `issue-create.mjs`: "the retired lane aliases", 8 cases. Covers each spelling, collapse beside `domain:server`, a live-label control, the dry-run lines, a direct create's POST body and read-back line, a no-alias control, and a relay create whose ONE dispatch carries `domain:server`. Floor 8 → 9. - `label-write.mjs`: "the retired lane aliases", 8 cases. Covers each spelling at parse, collapse, a live-label control, `--remove` kept literal, a direct write landing `domain:server` with a clean read-back and the ② line, triage's relabel unchanged, and a relay write. Floor 11 → 12. - `check-half-states.mjs` "domain:server lane aliases" battery: one new case (`domain:rest` reads as `domain:server`). The map pin now names four spellings (26 cases vs floor 22). ## Acceptance notes - **Residue for the seat to route:** the retired-lane line of `.claude/skills/pm-dispatch/SKILL.md` (「退役 `domain:*` 只退流通,对象保留;`domain:engine`/`domain:services`/`domain:cli` 读作 `domain:server`」) names three aliases, and the machine table now holds four. That line is `.claude/skills/**` text, outside this card's surface, so it is left untouched. - The rewrite runs at the SEAT side, in the doors on the sender's checkout. A seat on a checkout older than this merge, or a raw `repository_dispatch` that bypasses these scripts, still writes the literal label. The executor (`fleet-write/execute.mjs`) is unchanged by design (no gate; the order kept the surface to the doors). - `close-cards.mjs` and `issue-transfer.mjs` also pack through `packRequest`. Neither adds a `domain:*` label, so they are untouched. - The line `ui/scripts/__tests__/cross-file-line-citation-census.test.ts` cites `scripts/pm/check-half-states.mjs` as a path string only (bucket classification) and does not exercise its behaviour, so it was not re-run. ## Verification Local, on head `95a02ba0` (the branch with `origin/main` merged in; `fleet-write/validate.mjs` had moved on main). The same 57 also ran green on the pre-merge head `e38a555d`, and the derivation is identical on both: <details><summary>Self-tests of the edited scripts</summary> ``` node scripts/pm/fleet-write/dispatch.mjs --self-test :: exit 0 ✓ 331 cases pass across 28 batteries node scripts/pm/issue-create.mjs --self-test :: exit 0 ✓ 64 cases pass across 9 batteries node scripts/pm/label-write.mjs --self-test :: exit 0 ✓ 144 cases pass across 12 batteries node scripts/pm/check-half-states.mjs --self-test :: exit 0 ✓ 5253 cases pass (domain:server lane aliases 26/22) node scripts/pm/check-label-desc-cap.mjs :: exit 0 46 descriptions ≤100 characters bash -n scripts/pm/ensure-pm-labels.sh :: exit 0 npx eslint --no-inline-config FOUR_MJS_FILES :: exit 0 ``` </details> <details><summary>Derived gates — 57 of 57 exit 0</summary> `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` derived 57 commands for this diff, and each ran with stdin redirected from /dev/null. Reconciliation: `dispatch-gates.mjs --ran`, "57 derived famil(ies) accounted for — 57 run, 0 NOT-MEASURED". ``` node scripts/check-closing-keyword-parity.mjs :: exit 0 node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0 node scripts/check-comment-mask-corpus.mjs :: exit 0 node scripts/check-declaration-mirrors.mjs :: exit 0 node scripts/check-declaration-mirrors.mjs --self-test :: exit 0 node scripts/check-issue-citations.mjs :: exit 0 node scripts/check-platform-checklist-watchdog.mjs :: exit 0 node scripts/check-platform-checklist-watchdog.mjs --self-test :: exit 0 node scripts/check-release-section-coverage.mjs :: exit 0 node scripts/check-release-section-coverage.mjs --strict :: exit 0 node scripts/check-self-test-wired.mjs :: exit 0 node scripts/check-self-test-wired.mjs --self-test :: exit 0 node scripts/check-self-test-workflow-commands.mjs :: exit 0 node scripts/check-self-test-workflow-commands.mjs --self-test :: exit 0 node scripts/check-whole-set-label-write.mjs :: exit 0 node scripts/check-whole-set-label-write.mjs --self-test :: exit 0 node scripts/ci/plan-test-core-shards.mjs --self-test :: exit 0 node scripts/ci/scheduled-full-run.mjs --self-test :: exit 0 node scripts/pm/bare-root-worklist.mjs --self-test :: exit 0 node scripts/pm/board-snapshot.mjs --self-test :: exit 0 node scripts/pm/sweep-closed-cards.mjs --self-test :: exit 0 node scripts/render-release-coverage-anchor.mjs --self-test :: exit 0 pnpm check:agent-test-spelling :: exit 0 pnpm check:bash32-floor :: exit 0 pnpm check:ci-filter-parity :: exit 0 pnpm check:cli-command-ids :: exit 0 pnpm check:closing-target-claim :: exit 0 pnpm check:commit-card-trailers :: exit 0 pnpm check:cross-package-test-inputs :: exit 0 pnpm check:driver-memory-census :: exit 0 pnpm check:entry-guard :: exit 0 pnpm check:gitlink-declared :: exit 0 pnpm check:issue-citations :: exit 0 pnpm check:nul-bytes :: exit 0 pnpm check:parse-guard :: exit 0 pnpm check:partof-closing-keyword :: exit 0 pnpm check:platform-checklist :: exit 0 pnpm check:pm-close-cards :: exit 0 pnpm check:pm-dispatch-gates :: exit 0 pnpm check:pm-fleet-write-dispatch :: exit 0 pnpm check:pm-fleet-write-execute :: exit 0 pnpm check:pm-fleet-write-validate :: exit 0 pnpm check:pm-half-states :: exit 0 pnpm check:pm-issue-create :: exit 0 pnpm check:pm-issue-transfer :: exit 0 pnpm check:pm-label-desc-cap :: exit 0 pnpm check:pm-label-write :: exit 0 pnpm check:pm-post-stamped :: exit 0 pnpm check:pm-record-recognisers :: exit 0 pnpm check:pm-write-pace :: exit 0 pnpm check:pnpm-filter-targets :: exit 0 pnpm check:pnpm-flag-spellings :: exit 0 pnpm check:ratchet-remedy-authority :: exit 0 pnpm check:refd-timer-probe :: exit 0 pnpm check:scripts-symbol-anchors :: exit 0 pnpm check:single-claim-paths :: exit 0 pnpm check:watch-hint-literal :: exit 0 ``` </details> `node scripts/pm/check-governed-merges.mjs --test FIVE_PATHS --additions 201 --deletions 21`: "0 of 5 path(s) hit the register … NOT governed"; size 222 ≤ 3000. Changeset: none. `skip-changeset`, because every path is under `scripts/pm/**`, which publishes nothing. --- _Generated by [Claude Code](https://claude.ai/code/session_01L9qibvJcUgxPPZNDodAhy1)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent de0f692 commit 9e7fbd3

5 files changed

Lines changed: 201 additions & 21 deletions

File tree

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

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1109,22 +1109,29 @@ export function labelNames(issue) {
11091109
* Lane labels retired INTO another lane, read as that lane everywhere this
11101110
* sweep compares a card's `domain:*` label with a seat's (maintainer ruling
11111111
* 2026-10-22, letter 乙: `domain:engine`, `domain:services` and `domain:cli`
1112-
* fold into `domain:server`). The transition is designed to interrupt nothing:
1112+
* fold into `domain:server`; `domain:rest`, never a lane of its own, is read
1113+
* the same way by the maintainer's 2026-11-06 「车道的问题同意」). The
1114+
* transition is designed to interrupt nothing:
11131115
* in-flight cards keep their old label until they land, open cards are
11141116
* relabelled in batches by triage after the merge lands, and seat posts are
1115-
* retitled at each seat's next round — so for a while one lane is spelled four
1117+
* retitled at each seat's next round — so for a while one lane is spelled five
11161118
* ways on the board. Every lane comparison below goes through
11171119
* {@link canonicalLane}, which is what lets a `domain:server` seat count a
11181120
* `domain:engine` queue card as its own, a seat still titled `domain:cli` see
11191121
* a claim on a `domain:server` card, and the census count a card carrying both
11201122
* spellings once. ⛔ An alias map, never a vocabulary: it says nothing about
11211123
* which labels EXIST (that is `scripts/pm/ensure-pm-labels.sh`), and the
1122-
* cleanup PR that retires the three label objects deletes these rows with them.
1124+
* cleanup PR that retires the label objects deletes these rows with them. The
1125+
* write doors (`issue-create.mjs`, `label-write.mjs`, `fleet-write/dispatch.mjs`)
1126+
* read this same map to WRITE `domain:server` in an alias's place
1127+
* (`rewriteRetiredLaneLabels`, `fleet-write/dispatch.mjs`): a rewrite, never a
1128+
* refusal.
11231129
*/
11241130
export const LANE_LABEL_ALIASES = Object.freeze({
11251131
'domain:engine': 'domain:server',
11261132
'domain:services': 'domain:server',
11271133
'domain:cli': 'domain:server',
1134+
'domain:rest': 'domain:server',
11281135
});
11291136

11301137
/**
@@ -32036,12 +32043,13 @@ async function selfTest() {
3203632043
b(BATTERY_SERVER, 'alias: `domain:engine` reads as `domain:server`', canonicalLane('domain:engine'), 'domain:server');
3203732044
b(BATTERY_SERVER, 'alias: `domain:services` reads as `domain:server`', canonicalLane('domain:services'), 'domain:server');
3203832045
b(BATTERY_SERVER, 'alias: `domain:cli` reads as `domain:server`', canonicalLane('domain:cli'), 'domain:server');
32046+
b(BATTERY_SERVER, 'alias: `domain:rest` reads as `domain:server`', canonicalLane('domain:rest'), 'domain:server');
3203932047
b(BATTERY_SERVER, 'alias control: `domain:server` is its own canonical form', canonicalLane('domain:server'), 'domain:server');
3204032048
b(BATTERY_SERVER, 'alias control: a lane the map does not name passes through untouched', canonicalLane('domain:spec'), 'domain:spec');
3204132049
b(BATTERY_SERVER, 'alias control: a non-lane label passes through untouched', canonicalLane('pm:queue'), 'pm:queue');
3204232050
b(BATTERY_SERVER, 'alias control: a non-string is handed back, never thrown on', canonicalLane(undefined), undefined);
3204332051
b(BATTERY_SERVER, 'alias map: frozen', Object.isFrozen(LANE_LABEL_ALIASES), true);
32044-
b(BATTERY_SERVER, 'alias map: it names exactly the three retired lanes', Object.keys(LANE_LABEL_ALIASES).sort().join(), 'domain:cli,domain:engine,domain:services');
32052+
b(BATTERY_SERVER, 'alias map: it names exactly the four retired lane spellings', Object.keys(LANE_LABEL_ALIASES).sort().join(), 'domain:cli,domain:engine,domain:rest,domain:services');
3204532053
b(BATTERY_SERVER, 'alias map: every value is the one surviving lane', Object.values(LANE_LABEL_ALIASES).every((v) => v === 'domain:server'), true);
3204632054
// The seat title: a post not yet retitled is the merged lane's seat, number intact.
3204732055
b(BATTERY_SERVER, 'seat title: a `domain:engine · seat 2` post reads as `domain:server`, seat 2', [seatLane(seat38('[PM seat] domain:engine · seat 2 — 🟢 os-b')).lane, seatLane(seat38('[PM seat] domain:engine · seat 2 — 🟢 os-b')).seat].join(), 'domain:server,2');

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

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,8 @@
4848
# into GitHub's autocomplete. Their leftover label OBJECTS may still exist in
4949
# the repos; deleting those objects is a separate, deliberate PM action. One
5050
# TRANSITIONAL exception, recorded at its rows in the domain-lane block below:
51-
# the three lanes that folded into domain:server on 2026-10-22 keep alias rows
51+
# the three lanes that folded into domain:server on 2026-10-22, and the
52+
# domain:rest spelling read the same way since 2026-11-06, keep alias rows
5253
# until their last open card lands.
5354
# The objectui-repo STREAM labels domain:spec / domain:devx / domain:tooling,
5455
# retired in objectui only on 2026-10-22 (maintainer ruling 「甲,现在做」), are
@@ -528,17 +529,22 @@ done
528529
# until they land and open cards are relabelled by triage only after the merge
529530
# lands, so the objects stay in use for a while and a reader hovering one must
530531
# learn where it went. The description is the whole point of those rows:
531-
# create-if-missing is a no-op on objects that exist. The readers that keep
532-
# seeing every card through the old spellings live in
533-
# scripts/pm/check-half-states.mjs (LANE_LABEL_ALIASES). The cleanup PR that
534-
# retires the three objects (zero open cards on each) deletes these three rows
535-
# and that map together, and the header's rule resumes for them.
532+
# create-if-missing is a no-op on objects that exist. domain:rest, never a lane
533+
# of its own and carried by no card, joins them on the maintainer's 2026-11-06
534+
# 「车道的问题同意」 so its object stops reading as a live lane. The readers that
535+
# keep seeing every card through the old spellings live in
536+
# scripts/pm/check-half-states.mjs (LANE_LABEL_ALIASES), and the write doors
537+
# (issue-create, label-write, fleet-write/dispatch) read that same map to write
538+
# domain:server in an alias's place. The cleanup PR that retires the objects
539+
# (zero open cards on each) deletes these rows and that map together, and the
540+
# header's rule resumes for them.
536541
for D in server devx spec skills ui; do
537542
gh label create "domain:$D" -R objectstack-ai/objectstack -c bfd4f2 -d "Domain lane — seat card indexed by label:pm:seat" 2>/dev/null || true
538543
done
539544
gh label create domain:engine -R objectstack-ai/objectstack -c bfd4f2 -d "Retired 2026-10-22, alias of domain:server (engine surface); in-flight cards keep it until they land" 2>/dev/null || true
540545
gh label create domain:services -R objectstack-ai/objectstack -c bfd4f2 -d "Retired 2026-10-22, alias of domain:server (services surface); in-flight cards keep it until landed" 2>/dev/null || true
541546
gh label create domain:cli -R objectstack-ai/objectstack -c bfd4f2 -d "Retired 2026-10-22, alias of domain:server (cli surface); in-flight cards keep it until they land" 2>/dev/null || true
547+
gh label create domain:rest -R objectstack-ai/objectstack -c bfd4f2 -d "Retired 2026-11-06, alias of domain:server (rest surface); the relay writes domain:server instead" 2>/dev/null || true
542548

543549
# domain:cloud routes a WHOLE card to the cloud PM (the `repo:cloud` seat): any
544550
# card that needs cloud code, a cloud producer, a cloud fact or carries a cloud

0 commit comments

Comments
 (0)