Skip to content

feat(pm): a PR over 5,000 changed lines lands only by a human merge — size predicate in check-governed-merges --test, the same reading in dispatch-gates, one rule line in SKILL.md and landing-operations - #19033

Merged
os-elon-musk merged 6 commits into
mainfrom
claude/issue-19012-size-threshold-human-merge
Sep 19, 2026

Conversation

@os-elon-musk

Copy link
Copy Markdown
Collaborator

Fixes #19012
Clause-②: no

Maintainer ruling (verbatim, 2026-09-18)

「还有应该完善skills,修改代码量超过某个行数(比如5000)就应该人工审核。」

Read as: a pull request whose changed line count — GitHub's additions + deletions on the PR, generated files INCLUDED — exceeds 5,000 lands only by a human merge, at the same terminal as governed text (ACCEPT on the card, needs-user-decision on the PR, a final 维护者速读, review requested from GOVERNED_APPROVERS); no seat flips it ready or arms auto-merge. 5,000 is the ruled default (「比如」), declared once as HUMAN_MERGE_LINE_THRESHOLD in scripts/pm/check-governed-merges.mjs, so it moves by one word from the maintainer and one edit. The case that prompted it, PR #18971 (+238,310 / −119, of which 237,706 lines were regenerated artefacts), is the first PR the rule governs — an exemption for generated files would exempt exactly it, so there is none.

What changed

  1. scripts/pm/check-governed-merges.mjs — the SIZE predicate. --pr N reads additions / deletions off the same GET /repos/OWNER/REPO/pulls/N that gives changed_files (a PR object missing the pair is a refusal on exit 1 — never a size of zero, never a "not governed" answer); --branch REF counts the same merge-base range with git diff --numstat --no-renames (a binary file is 0 lines, as GitHub counts it); --test PATHS takes --additions N --deletions N as a pair, or prints size: NOT MEASURED on stdout naming the modes that read it. Either limb exits on the GOVERNED code 3, so every caller that already routes 3 to the human terminal routes an oversized PR there without a new code; --json carries size and humanMerge (governed stays the path limb). A certified generated-artifact regeneration lifts the PATH off the register and lifts nothing from the size. The queue guard's testVerdict(paths) reading is unchanged (no size handed in ⇒ the path answer as before).
  2. scripts/pm/dispatch-gates.mjs — the same reading at dispatch time. With no paths (the derived run) it prints Changed lines — N (+a / -d; generated files INCLUDED) vs the human-merge threshold 5000: under or ⛔ OVER — this PR lands only by a HUMAN MERGE … beside the tier verdict (human and --tier modes), the count on stderr with the rest of the provenance, and changedLines in --json. The count is --numstat off the merge base against the working tree plus untracked files counted from disk (under-derivation refused, like the path list). An explicit path list carries no diff and prints NOT MEASURED, never a silent under. The threshold is imported from the gate — one declaration, no second copy.
  3. Rule text. .claude/skills/pm-dispatch/SKILL.md gains one line beside the four-piece-terminal trigger (line 608, 111 B): 「改动 >5000 行(含生成物)同换终局四件套,⛔ 无事实层例外;读数 = PR additions+deletions。」 references/landing-operations.md line 26 folds the size limb into the pre-check row, now spelled --pr N (which reads paths and size in one call), 117 B, ceiling unchanged at 69. The SKILL.md ceiling rises 812 → 813 in scripts/pm/check-skill-line-ratchet.mjs under the ratchet's own maintainer exit, the ruling quoted in the entry (the 811 → 812 precedent's form).

Readings — before / after, measured

reading before (43f476688) after (this head)
check-governed-merges.mjs --pr 18971 (live API through the proxy) exit 0 — ✅ NOT governed — ordinary queue landing applies exit 3 — ⛔ HUMAN MERGE — 238429 changed line(s) (+238310 / -119) > 5000
--pr 18994 (2 files, +15 / −1) exit 0 exit 0 — size: 16 changed line(s) (+15 / -1) ≤ 5000 — under the human-merge threshold
--pr 18921 (SKILL.md, +6 / −6) exit 3 GOVERNED exit 3 GOVERNED, plus size: 12 changed line(s) … under
check-governed-merges.mjs --self-test 328 assertions, 26 batteries 369 assertions, 27 batteries (new battery: the SIZE predicate, floor 30)
dispatch-gates.mjs --tier (no paths, this worktree) no size line Changed lines — 722 (+691 / -31; generated files INCLUDED) vs the human-merge threshold 5000: under.
dispatch-gates.mjs --tier packages/spec/src/index.ts no size line Changed lines — NOT MEASURED: a path list carries no diff to count …
check:pm-dispatch-gates (detached, tail --pid) 1849 cases (the dispatch's reading at 43f476688) 1862 cases pass (795.6 s, detached; +13 cases)
check:pm-skill-ratchet SKILL.md 812 / 812 · landing-operations.md 69 / 69 SKILL.md 813 / 813 · landing-operations.md 69 / 69

Self-test pins on the threshold: exactly 5,000 changed lines is under; 5,001 is over; the +238,310 / −119 pair reads 238,429 and is over; a certified pure regeneration over the threshold still lands by a human merge; the verdict is byte-identical through --branch and through --test once the same list and numbers are handed in.

Line budget (measured)

  • SKILL.md: 812 → 813 lines; ceiling 812 → 813 (maintainer exit). A fold was not available: 0 of 598 adjacent bullet pairs merge under the 120-byte cap (smallest 123 B); the trigger line (607) stands at 118 B; the rule's shortest self-contained form is 111 B; deleting a ruled clause is refused on the state-machine precedent.
  • references/landing-operations.md: 69 → 69 lines (line 26: 118 B → 117 B).
  • check:pm-skill-id-lint: 27 files clean (no issue-ID citation in either line).

Gates (this head; exit codes captured before any pipe)

Derived with node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack from the worktree at 7fdd61ca0 (42 commands; change set 5 paths, 724 changed lines by its own reading), every one run with cmd > log 2>&1; status=$? and reconciled with --ran:

node scripts/check-ci-filter-parity.mjs :: exit 0
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-scripts-symbol-anchors.mjs :: exit 0
node scripts/check-scripts-symbol-anchors.mjs --self-test :: 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-skills-token-ratchet.mjs :: exit 0
node scripts/check-skills-token-ratchet.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/pm/bare-root-worklist.mjs --self-test :: exit 0
node scripts/pm/check-governed-queue-guard.mjs --self-test :: exit 0
node scripts/pm/check-harness-current.mjs --self-test :: exit 0
pnpm --filter @objectstack/lint run check:doc-formula-expressions :: exit 0
pnpm check:agent-test-spelling :: exit 0
pnpm check:bash32-floor :: exit 0
pnpm check:cli-command-ids :: exit 0
pnpm check:cross-package-test-inputs :: exit 0
pnpm check:declared-population-live :: exit 0
pnpm check:doc-authoring :: exit 0
pnpm check:driver-memory-census :: exit 0
pnpm check:entry-guard :: exit 0
pnpm check:nul-bytes :: exit 0
pnpm check:parse-guard :: exit 0
pnpm check:pm-expected-skips :: exit 0
pnpm check:pm-governed-prose :: exit 0
pnpm check:pm-half-states :: exit 0
pnpm check:pm-skill-id-lint :: exit 0
pnpm check:pm-skill-ratchet :: exit 0
pnpm check:pnpm-filter-targets :: exit 0
pnpm check:ratchet-remedy-authority :: exit 0
pnpm check:refd-timer-probe :: exit 0
pnpm check:skill-frame-sync :: exit 0
pnpm check:watch-hint-literal :: exit 0
pnpm check:pm-governed-merges :: exit 0
pnpm check:pm-dispatch-gates :: exit 0

dispatch-gates --ran: 42 derived, 42 run, 0 NOT-MEASURED, 0 UNRUN (verdict line: ✓ dispatch-gates --ran: 42 derived famil(ies) accounted for — 42 run, 0 NOT-MEASURED). check:pm-dispatch-gates ran detached (nohup + tail --pid, 795.6 s on this box): ✓ dispatch-gates self-test: 1862 cases pass. check:pm-governed-merges: ✓ check-governed-merges --self-test: 369 assertions. check:doc-formula-expressions exited 3 (PREREQUISITE NOT MET: @objectstack/formula / @objectstack/lint not built) on the first pass; both were built under scripts/pm/os-verify-lock.sh (VERDICT command-exit 0, 152 s held) and the gate reran green — the exit 3 was never a measurement.

NOT MEASURED locally, by the derivation itself (CI-only, value-bearing argv): scripts/check-shard-attestation.mjs --emit …, scripts/check-test-completeness.mjs …, scripts/pm/check-half-states.mjs --format=markdown --provenance=…; plus the 11 wide-population families and the 50 artifact-roster families CI runs on every PR, outside the derived total by design. pnpm lint (repo-wide eslint) is CI-owned and was not run here. No package build/test is owed: the diff touches no packages/** file (no ①/② in the local verification scope), so the only lock-wrapped run was the formula/lint build above.

Line-budget after the final commit (7fdd61ca0): check:pm-skill-ratchet.claude/skills/pm-dispatch/SKILL.md is 813 lines (ceiling 813; headroom 0), references/landing-operations.md is 69 lines (ceiling 69; headroom 0); check:pm-skill-id-lint — 27 file(s) clean.

Deviations from the dispatch brief

  1. Mechanism assumption 1 said a failing size read exits PREREQUISITE NOT MET (3). Under --pr, 3 already means GOVERNED — the file's own rule is that no invocation carries both meanings — so a PR object without the pair is a REFUSAL on the derivation code 1 (a stated refusal, never 0, never a size of zero). The ruling's intent (never read as "not governed") is kept.
  2. "812 / 812 — fold or pay": measured, neither was available (above), so the SKILL.md line lands under the ratchet's own maintainer exit (812 → 813), the form the 811 → 812 entry took. The hunk sits at :608, disjoint from PR skills(pm-dispatch): key the clause-② contract review by lane — spec and skills owe it on every round, other lanes owe none #18903's bands (:509–:525, :633–:675) and from the two PRs that landed on SKILL.md meanwhile (merged into this branch; the line is still there once). If the seat prefers the follow-up route, drop commit 3's SKILL.md hunk and the ratchet entry together.
  3. --branch derives the size itself (--numstat on the range it lists) rather than taking passed-in numbers; the flags beside a deriving mode (--pr, --branch) are refused as two readings of one number, the way two mode flags are.
  4. dispatch-gates.mjs's self-test pins a NAMED census of live population markers by file and line; the import block moved this file's own inherited-population marker from :702 to :705, so that one row is updated — the census exists to be updated exactly this way.

Acceptance notes

  • to file (class b — a declared contract the queue cannot yet hold): the queue guard's merge_group leg reads the PATH register only; a seat that skips the landing pre-check can still enqueue an oversized PR. Dedupe words: queue guard size threshold, merge_group additions deletions, check-governed-queue-guard 5000, human merge line count.
  • to file (class b): AGENTS.md §7 lists "two classes of PR never enter this path on green alone" (governed surface; Version Packages) — the ruled third class is missing from the rules layer. Dedupe words: AGENTS.md green alone third class, 5000 lines human merge AGENTS.
  • noted, not filed: the post-merge sweep (default mode of check-governed-merges.mjs) lists governed-surface merges only; an oversized PR that landed through the queue is not listed. 承接者: the skills seat, together with the queue-guard follow-up above.
  • noted, not filed: check:doc-formula-expressions exits 3 (PREREQUISITE NOT MET) on a fresh worktree until @objectstack/formula and @objectstack/lint are built — by design of that gate; built under the verify lock here and rerun. 承接者: none.

维护者速读(草稿)

改了什么:落地前检 check-governed-merges.mjs 新增「体量」判据:PR 的 additions + deletions 超过 5000 行(含生成物)⇒ 只能人合,与受管面走同一终点;dispatch-gates 在派发/认领时就把同一读数印在 tier 行旁;SKILL.md 与 landing-operations.md 各落一行规则。阈值只声明一次(HUMAN_MERGE_LINE_THRESHOLD = 5000),改它是一个词。

为什么改:您 2026-09-18 的裁决。触发案例是 PR #18971(+238,310 / −119,其中 237,706 行是生成物)只凭 AI 审查就经队列合入;生成物不豁免,否则恰好豁免它。

风险与代价(含回滚):大 PR 的落地从「席位挂 auto-merge」变成「等您点一下」,每张超 5000 行的 PR 多一次人工动作;回滚 = revert 本 PR(纯脚本 + 两行规则文本,无发布物)。已知缺口:队列守卫的 merge_group 腿尚未读体量,眼下靠席位跑落地前检;已列为后续单。

席位意见:(留空)

你要做的:确认 5000 这个默认值(「比如」)是否就是您要的;是 ⇒ 人合本 PR;要改数字 ⇒ 说一个数即可。


Generated by Claude Code

…itions + deletions over 5000 lands only by a human merge

Maintainer ruling 2026-09-18, verbatim: 「还有应该完善skills,修改代码量超过某个行数(比如5000)就应该人工审核。」

The threshold is declared once as HUMAN_MERGE_LINE_THRESHOLD = 5000. --pr reads
the pair off the same GET that gives changed_files (its absence is a refusal,
never a size of zero); --branch counts the merge-base range with --numstat
(binary files at zero, as on GitHub); --test takes --additions/--deletions as a
pair or says NOT MEASURED on stdout. Either limb exits on the GOVERNED code, so
every caller that routes 3 to the human terminal routes an oversized PR there.
Generated files are included — a certified regeneration lifts the path off the
register and lifts nothing from the size. Self-test: a new battery pinned on
both sides of the threshold and on the PR that prompted the ruling.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BTeBejoPUvRHN8WdAJC6oF
…ier verdict

The same 2026-09-18 human-merge line threshold the landing gate enforces,
read at dispatch time off the worktree's own diff (numstat off the merge base
against the working tree, untracked files counted from disk, binary files at
zero) so a seat knows before ACCEPT that the PR needs a human. An explicit
path list carries no diff and prints NOT MEASURED, never a silent under. The
threshold is imported from check-governed-merges.mjs — one declaration, no
second copy. --json carries the reading as changedLines; --commands keeps it
on stderr with the rest of the provenance.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BTeBejoPUvRHN8WdAJC6oF
…md line, the landing-operations pre-check row, the ceiling 812 → 813

Maintainer ruling 2026-09-18, verbatim: 「还有应该完善skills,修改代码量超过某个行数(比如5000)就应该人工审核。」

SKILL.md gains one 111-byte rule line beside the four-piece-terminal trigger;
landing-operations.md folds the size limb into the pre-check row (now spelled
--pr N, which reads paths and size in one call) at 117 bytes, ceiling
unchanged. The SKILL.md ceiling rises 812 → 813 under the ratchet's own
maintainer exit: measured, the rule could not be paid in place — 0 of 598
adjacent bullet pairs merge under the 120-byte cap (smallest 123 B) and the
trigger line stands at 118 B.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BTeBejoPUvRHN8WdAJC6oF
…lock

The threshold import moved this file's inherited-population marker from
line 702 to 705; the census row that names it by file and line moves with
it — the row is named, never counted, so the move is recorded rather than
absorbed.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BTeBejoPUvRHN8WdAJC6oF

Copy link
Copy Markdown
Collaborator Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 7fdd61ca0c

① Derived judgments

② Semver level

  • skip-changeset is correct (scripts/pm/** and .claude/**, nothing published moves). Clause-②: no on the claim (5729457765) and in the body; --pair 19033 at 2026-09-18T12:45Z: exit 0, no widening tell.

③ Boundary flags

  • Dev flags: no open questions. Six declared deviations — the missing-pair refusal on code 1 rather than 3 (code 3 already means GOVERNED under --pr; the intent 「never read as not governed」 kept — agreed); the ceiling raise (①); --branch derives its own size and refuses passed-in numbers as two readings of one (agreed); the dispatch-gates census row :702 → :705 (the census exists for that); the commit trailer amended to the model-free pair before the first push (AGENTS.md outranks the harness reminder — agreed); the body written once. Four out_of_scope: two filed by the seat — # (the merge-queue guard's merge_group leg reads the path register only, so an enqueued oversized PR still merges: the queue does not yet hold the contract) and # (AGENTS.md §7 names two classes that never land on green alone; the ruling makes three) — both class (b), the first the more urgent (the rule is inert at the queue until it lands); two noted (the post-merge sweep blind to size — folded into the first card; check:doc-formula-expressions needing a build).
  • Gates: 42 derived / 42 run / 0 NOT-MEASURED at 7fdd61ca0 (one exit-3 prerequisite cleared by building formula + lint under the verify lock, re-run 0); check:pm-dispatch-gates 1862 detached; check:pm-governed-merges 369. 0 MCP calls; 4 REST channels as budgeted; label-write.mjs ran without a classifier denial.
  • CI on this head at 2026-09-18T12:47Z: 15 success · 11 skipped · 7 in_progress — nothing red; the landing act reads it again.
  • Landing: governed ⇒ draft at the four-piece terminal (ACCEPT on [ruling] a PR whose changed line count exceeds 5,000 lands only by a human merge — rule line in SKILL.md + landing-operations.md, and a size predicate in the landing gate #19012, needs-user-decision on this PR, the 维护者速读 below, review requested from os-zhuang and hotlong). On an authorized APPROVED: ruling C by the seat, channel permitting — or the approver's own hand.

Implemented-by: claude/issue-19012-size-threshold-human-merge
Reviewed-by: session_01BTeBejoPUvRHN8WdAJC6oF

VERDICT: PASS


Generated by Claude Code

Copy link
Copy Markdown
Collaborator Author

维护者速读(终稿)· skills 席 · 2026-09-18T12:49Z

这个 PR 做什么:您 12:33Z 的字「修改代码量超过某个行数(比如5000)就应该人工审核」落成机械门禁。落地前置检查 check-governed-merges.mjs --pr N 现在同时读 PR 的 additions + deletions(含生成文件,无豁免):超过 5000 ⇒ 与受管文本同一终点 —— 席位不能标 ready、不能入队、不能挂自动合并,只能您或批准人手合。阈值只写在一处(HUMAN_MERGE_LINE_THRESHOLD = 5000),您一句话就能改。派发工具在派发时就打印同一读数,席位在派发前就知道这个 PR 将需要人合。规则文本各一行进 SKILL.md(:608)与落地细则(:26)。

在真 PR 上的读数:PR #18971(+238,310 / −119)—— 旧版本判「不受管,照常入队」;这个版本判「HUMAN MERGE,23.8 万行 > 5000」。今天其他 PR(最大 +692)全部「under」。

⚠️ 请您看一眼的一件事:SKILL.md 满额 812 行,这一行折不进任何现有行(dev 量了 598 对相邻行,最小合并后 123 字节 > 120 上限;删已裁条款按先例不允许),所以它把 SKILL.md 的行数上限从 812 抬到 813,并把您的原话记在棘轮表里。抬上限按规则要人裁 —— 您批准这个 PR 即视为批准这次抬升

它还没盖住的两处,已立卡:合并队列的守卫仍只看路径(#:席位若跳过前置检查直接入队,超大 PR 仍会合进去 —— 更急);AGENTS.md §7 还写着「两类 PR 不能凭绿灯合入」,现在是三类(#)。

验收:复核记录 5730219787(PASS)、ACCEPT 5730220167;门禁自测 328 → 369、派发工具自测 1849 → 1862;派生门禁 42 / 42 绿;CI 在跑,截至 12:47Z 无红。

请您做的一件事:Approve(受管文本),或直接 ready + squash 合并。要问的只有一个字:5000 是不是您要的数 —— 是就照批,不是回个数我改常量。


Generated by Claude Code

Copy link
Copy Markdown
Collaborator Author

Correction (skills seat) · 2026-09-18T12:51Z — the review of record 5730219787 and the 维护者速读 5730220889 above name the two follow-up cards with EMPTY numbers (a variable that did not expand when the seat's filing step failed before the post). The cards, filed at 2026-09-18T12:50Z: #19036 (the merge-queue guard's merge_group leg reads the path register only — the more urgent) and #19037 (AGENTS.md §7's 「two classes」 becomes three). Nothing else in either comment changes; the seat's own error, the second of its kind this shift.


Generated by Claude Code

One conflicted file, scripts/pm/check-governed-merges.mjs: the tiered
register from main meets the SIZE predicate on this branch. Both sides kept:
GOVERNED_TIER_* beside HUMAN_MERGE_LINE_THRESHOLD; `tier` beside `humanMerge`
on the post-lift verdict; the Tier H block prints its landing-tier line and
then the size line; the Tier S block gains the same size line; the self-test
summary carries both suffixes; the roster floor is recounted to the 28
batteries the merged file declares (27 and 26 on the two sides, none
lowered). The size line's "same terminal" now names Tier H, the terminal the
rules layer keeps under the tiers. SKILL.md, the ratchet ceiling and
landing-operations.md merged clean: 813 lines, ceiling 813.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BTeBejoPUvRHN8WdAJC6oF
@os-elon-musk
os-elon-musk marked this pull request as ready for review September 19, 2026 00:46
@os-elon-musk
os-elon-musk added this pull request to the merge queue Sep 19, 2026
Merged via the queue into main with commit e872ef4 Sep 19, 2026
36 checks passed
@os-elon-musk
os-elon-musk deleted the claude/issue-19012-size-threshold-human-merge branch September 19, 2026 01:05
os-tesla pushed a commit that referenced this pull request Sep 19, 2026
…lists one — the SIZE limb reaches the queue

The 2026-09-18 ruling landed seat-side (PR #19033) while the queue leg of
check-governed-queue-guard.mjs kept handing testVerdict no size, so a PR over
HUMAN_MERGE_LINE_THRESHOLD changed lines that a seat enqueued still merged.

Queue guard: a fourth leg reads every queued pull request's additions /
deletions off the same pull object the head read uses (makePullReader grew a
`size`, no second endpoint), judges it through the sibling's IMPORTED
predicate (testVerdict([], { size }) + landsByHumanMerge — this file declares
no threshold and spells no comparison, pinned against its own source), and
REFUSES on exit 8 with the two numbers, the threshold and the human-merge
remedy printed; an unreadable size fails CLOSED on exit 9; the pull_request
leg stays silent and byte-identical; exit precedence governed > size >
carrier is a pure function, pinned on every combination.

Post-merge sweep: classifyCommit is now landsByHumanMerge on testVerdict, the
size read LOCALLY off the landed diff by one `git diff-tree --numstat` per
mainline commit (commitChanges, replacing commitPaths' --name-only with a
byte-identical path list), so an oversized landing with no governed path is
an entry on the size limb alone — counted apart in the head, listed with a
⛔ SIZE row and the same attribution column.

Self-tests: guard 261 → 296, governed-merges 410 → 435.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W5y9kRg1YtYaMQYExVLRc2
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/l skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants