Repository navigation
feat(fleet-write): pr_create carries labels; os-dev declares skip-changeset in the create's stroke - #24175
Conversation
…s-dev declares skip-changeset there The relay's `pr_create` op gains an optional `labels` list, the shape its `assignees` leg already has: a later POST to the created pull's issue-labels endpoint at the number the create answered, judged against `labelled` so a name the answer lacks is a FAILED action, never a quiet partial. Both legs now fill from the action's FIRST answer (the create's), never a later leg's. The validator reuses the typed `labels` list `labels_add` takes. `.claude/agents/os-dev.md`: three writes instead of four; `skip-changeset` is declared in the `pr_create` stroke and `label-write.mjs` serves only a label found after the PR exists; a classifier refusal routes through the seat to the maintainer's word instead of the seat substituting. Claude-Session: https://claude.ai/code/session_012PxNHU3dfov2hbpg9ycZ8q Co-authored-by: Claude <noreply@anthropic.com>
…write goes to the maintainer's word, the seat never applies it on its own The seat-side twin of the os-dev.md routing line: on a classifier refusal of the dev's `skip-changeset` write the seat routes to the maintainer and applies the label only after that word AND its own tests/docs-only check, naming both in the comment; substituting for a refused action is permission laundering. The dev's `deviations` still carries the refused command verbatim. 77 -> 77 lines, two lines rewritten in place. Claude-Session: https://claude.ai/code/session_012PxNHU3dfov2hbpg9ycZ8q Co-authored-by: Claude <noreply@anthropic.com>
Contract reviewServed-tier: Read 2026-10-17T14:04Z in seat by ① Derived judgmentsGoverned rules text (the face that sets the tier).
The relay code (read at the head; the lane seat's own ACCEPT at
Check-runs on ② Semver level
③ Boundary flagsDev reports 6234597716 (round 1) and 6234816652 (round 2):
Reviewer's own flags: none. Implemented-by: VERDICT: PASS |
维护者速读改了什么:① 中继 为什么改:同一条 风险与代价(含回滚):不能证明分类器一定放行带 席位意见:ACCEPT。三个脚本逐处读过,三次消融各自转红再复原;两处规则文本与代码一致(见上方 你要做的:无。 席位核对Seat Implemented-by:
|
Fixes #24157
Clause-②: no
What this PR does
Two parts, one PR, the card's route 1 + 3 (fold
skip-changesetinto thepr_createstroke; rewrite the 「席位代做」 line). Option 4 (relaxingCheck Changeset) is a gate change and is not here.1. The relay op —
pr_createcarrieslabelsscripts/pm/fleet-write/ops.mjs:pr_creategains an optionallabelslist. It becomes a later request of the SAME action,POST /repos/REPO/issues/NUMBER/labelswithfromAnswer: 'number'andlabelled: [...a.labels]— the exact shape theassigneesleg has (assigned). The row still spendspull-requestswithalsoSpends: ['issues'], so the token minted for the stroke already covers the labels POST;PERMISSIONSis unchanged and the workflow's mint step needed no edit.scripts/pm/fleet-write/validate.mjs: the typed check is the onelabels_addalready uses —FIELDS.labels(array of 1..20 strings, each ≤ 50 bytes, no duplicates) is applied to any op whose key list admitslabels, so addinglabelsto the row'soptionalis the whole validator change; the self-test gains thepr_createlabels cases beside the assignees ones, the minimal-shape ledger nameslabels, and the 「exactly ONE descriptor reads an earlier answer」 pin becomes 「exactly TWO — assignees then labels」, with a new pin that every such leg is an additive POST to/assigneesor/labels, never the whole-set replace.scripts/pm/fleet-write/execute.mjs— the one place that had to learn the leg (the dispatch order assumed none): ①requestLandedjudges alabelleddescriptor's 2xx against the names sent — the labels endpoint answers the pull's whole label set, so a name it lacks is a FAILED action naming it, never a quiet partial; more than sent (the Auto Label workflow'sdocumentation/size/*) is a landing. ②resultOfprints the labels as the platform lists them on that row. ③fromAnswerlegs are now filled from the action's FIRST answer (the create's), not the PREVIOUS request's: with both legs present the previous answer of the labels leg would have been the assignee POST's issue object, which happens to carry a number but is not the create. The self-test pins the label leg landing on the number the create answered while the assignee answer carries a different one.scripts/pm/fleet-write/dispatch.mjsand.github/workflows/fleet-write.ymlneed nothing: dispatch's self-test is green unchanged (222 cases), and the workflow only runsvalidate.mjs --from-env --github-outputthenexecute.mjs.2. The dev contract —
.claude/agents/os-dev.md, 404 → 404 lines (ceiling 404, headroom 0)Four lines rewritten in place, no line added or removed. Each line's old text and where every dropped clause survives:
git push、pr_create(draft,含 assignee)、label-write(仅标签)、os-dev-report评论git push、pr_create(draft,含 assignee 与 labels)、os-dev-report评论label-write(仅标签) as a budgeted stroke → it is no longer a default stroke; the late-label case is named on line 303skip-changeset以pr_create的labels同笔申报;后发现的标签才走label-write.mjsscripts/pm/label-write.mjs:取现集、加法 POST、回读比 union、缺者重挂一次并报告label-write.mjsitself performs and prints on every invocation (its header: 「the four-step label write, as ONE spelling … Every invocation performs all four … re-added ONCE and REPORTED」), so the contract line was a mirror of the tool header, which AGENTS.md makes the authority; 「写恒经 scripts/pm/」→ line 55 (GitHub 写一律经scripts/pm/写工具) and line 303 names the tool for the late-label casedeviations记命令与拒因,席位代做;⛔ 不换路重发(curl/MCP/手工)deviations记命令与拒因,席位只转维护者定夺;⛔ 不换路重发curl/fetch/gh api -X POST…write」, and 「不换路重发」 covers any manual routeLines 61 / 303 / 305 / 309 measure 104 / 118 / 119 / 117 bytes against the 120-byte line budget. No re-wrap, no merge, no ceiling raise.
Mechanism assumptions, measured
bd4276a91(origin/main had advanced from the order's697301491when the worktree was cut):pr_createhadoptional: ['body', 'assignees']only; the filer's 「the relay'spr_createop takeslabels」 was false, so option 1 needed the op leg.label-write.mjscommand). This PR cannot show the classifier will accept apr_createcarryinglabels; the measured fact is that thepr_createstroke has not been refused since the assignee precedent pm tooling: the os-dev's PR-assignee write (label-write.mjs --assign) is refused by the auto-mode permission classifier on 4 of 12 objectui PRs in one seat's session — same command, same brief, nondeterministic #22467. This PR's ownskip-changesethad to go the OLD way (label-write.mjs) because a PR cannot use the leg it introduces.pr-automation.ymlwritessize/*and the path labels by additive POST (theAuto LabelandCheck PR Sizejobs), so the two never overwrite each other.Check Changesetreads the label live (a re-read plus a settling read, because the frozenopenedpayload cannot see a label applied after creation; its own comment measures label latency at 10–45 s after creation). The leg lands inside the relay's own stroke, seconds after the create, so it falls inside that settling window — and thelabeledevent re-triggers the job in any case.execute.mjs… to confirm nothing else must learn the new leg」 — it did: withoutlabelledthe executor would have passed a 200 whose answer lacks the label (ablation B, below, shows exactly that quiet partial), and the fill source had to move from the previous answer to the create's (ablation A).Verification (final commit
f9e63d6c3)Relay self-tests (rule ⑤ — the edited scripts' own suites; no
*.test.*file names them):node scripts/pm/fleet-write/validate.mjs --self-test→ exit 0, 158 cases / 11 batteries (was 152).node scripts/pm/fleet-write/execute.mjs --self-test→ exit 0, 128 cases / 16 batteries (was 120; the assignees-leg battery is re-declared inSELF_TEST_BATTERIESas the assignees-and-labels battery, pin 11 → 19).node scripts/pm/fleet-write/dispatch.mjs --self-test→ exit 0, 222 cases / 20 batteries (unchanged).ops.mjshas no--self-testof its own (its pins live in the validate and execute suites).Ablations — each via
node scripts/ablation-replace.mjs(anchor hit ×1, blob changed, command run, restore proven by blob == HEAD blob andgit diff HEADempty; nodist/is involved, these are plain node scripts):execute.mjs:if (opener === null) opener = r;→opener = r;(back to previous-answer fill). Expected red; observed 2 of 128 red — 「at the number the CREATE answered, not the assignee answer's」 and the row-text case. Blobf246498…→af59de9…→ restoredf246498….execute.mjs:const missing = req.labelled.filter(→const missing = [].filter(. Expected red; observed 1 of 128 red — 「a 200 whose answer lacks a name sent is a FAILED action … never a quiet partial」 (the run passed with exit 0 and 3 writes under the mutation: the quiet partial). Blobf246498…→69e1572…→ restoredf246498….ops.mjs:optional: ['body', 'assignees', 'labels']→['body', 'assignees']. Expected red; observed 6 of 158 validator cases red (the labels acceptance cases, the minimal-shape ledger, the two-descriptor pin). Blob6ad2cb6…→83deec1…→ restored6ad2cb6….Gates — derived at
f9e63d6c3withnode scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack(no paths; 48 families, the path-named derivation at the base pluscheck:pm-write-pace, which namesexecute.mjs). Every command run in the foreground with its exit code captured before any pipe: all 48 exit 0. One of them,pnpm --filter @objectstack/lint run check:doc-formula-expressions, first answered exit 3 (PREREQUISITE NOT MET: no@objectstack/formula/@objectstack/lintbuild in the fresh worktree — a non-measurement, not counted); afterpnpm exec turbo run build --filter=@objectstack/formula --filter=@objectstack/lintbehindscripts/pm/os-verify-lock.sh(VERDICT command-exit 0) it re-ran to exit 0. Reconciled with--ran:node scripts/pm/dispatch-gates.mjs --ran ran.list --repo objectstack-ai/objectstack→ exit 0, 「48 derived, 48 run, 0 NOT-MEASURED, 0 UNRUN」, every family carrying a recorded exit code. The five families the order named (check:pm-skill-ratchet,check:pm-skill-id-lint,check:pm-governed-prose,check:agent-model-declared,check:pm-settings-deny-roster) are each exit 0. Repo-wide scans (pnpm lint,test:repo) are CI's; the PR's check-runs are the authority for every family CI runs.Acceptance notes
.claude/skills/pm-dispatch/references/review-checklist.mdline 22 still tells the seat 「dev 挂skip-changeset遭分类器拒 ⇒ 席位自核 tests/docs-only 即自挂」 — the seat-side twin of the line this PR rewrites, outside this card's file surface. Reported in the dev report for the seat; not changed here.dispatch.mjs's seat-side read-back (assigneesReading) has no labels twin; the relay row (execute.mjs) is the read-back line 305 now names. Observation only, nothing filed..claude/**): this PR stays draft and awaits a## Contract reviewrecord;scripts/pm/**and.claude/**publish nothing →skip-changeset, applied the old way (reported in the dev report with endpoint and status if refused).维护者速读(草稿)
改了什么:中继的
pr_create操作新增可选labels,开 PR 的同一笔里顺手贴标签(和已有的 assignee 腿同一形状);执行器只认「答案里列出了每个送出的标签」才算落地,缺一个就报 FAILED 停下。os-dev.md四行原地改写:写预算四笔变三笔;skip-changeset在开 PR 那一笔申报,事后才发现的标签才用label-write.mjs;分类器拒写时席位只转维护者定夺、不代做。行数 404 → 404,未抬上限。为什么改:同一条
label-write命令,一次放行一次被拒(PR #24139 / PR #24153),被拒后原文让「席位代做」,而席位的 harness 把替子代理补做被拒动作视为洗权限,于是每次被拒都变成一次向您提问。把标签并入从未被拒的pr_create一笔,是 #22467 处理 assignee 时走过的同一条路。风险与代价(含回滚):不能证明分类器一定放行带
labels的pr_create,只能说该笔自 #22467 以来未被拒过。本 PR 自身的skip-changeset仍按旧路贴(新腿要合并后才可用)。回滚 = revert 本 PR 一个 commit;中继无状态,不影响已开 PR。席位意见:(留空,席位定稿)
你要做的:本 PR 触及
.claude/**(Tier S),由席位做达档复核后入队落地,无需您点击;如希望顺手也改review-checklist.md第 22 行的席位侧措辞,请一句话说明,席位另立卡。Generated by Claude Code