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
@os-elon-musk os-elon-musk added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 18, 2026 — with Claude
@github-actions github-actions Bot added the documentation Improvements or additions to documentation label Sep 18, 2026

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
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 28, 2026
…ules match (objectstack-ai#19045)

Fixes objectstack-ai#19025

Clause-②: no — one references-layer file, three lines, line-neutral. No
package, export, schema
or generated artifact moves, and nothing published changes.

## The finding, and what this PR owes

A Claude Code `Bash(...)` permission rule is a literal PREFIX match up
to its first glob. The
landing-call allow rules the maintainer is committing read

```text
Bash(curl -sS -X POST https://api.github.com/repos/objectstack-ai/objectstack/pulls/*/ccr/ready_for_review *)
Bash(curl -sS -X PUT https://api.github.com/repos/objectstack-ai/objectstack/pulls/*/ccr/auto_merge *)
```

so the operative literal prefix is everything before the first glob —
`curl -sS -X POST` (or `PUT`)
followed by the bare, unquoted url up to `/pulls/`. A command that puts
a `-H` flag before the url,
or quotes the url, matches nothing and falls to the session classifier.
The two rows named the
endpoint but never the invocation, so nothing in the corpus prescribed
the flag order.

## Measured on `origin/main` at `dbd474431` before the clause was
written

- `.claude/settings.json`: **47 allow entries, of which 21 are
`Bash(curl ...)` rules**, and all 21
match verb-then-bare-url (`Bash(curl -sS -X VERB
https://api.github.com/...` with the globs after).
Zero exceptions. The dispatch calls all 47 curl rules; measured, 47 is
the *total* allow count and
21 of them are curl rules — the shape claim itself holds for every one
of the 21.
- The two `ccr/` landing rules are **not yet in**
`.claude/settings.json` (`grep -n 'ccr/'` → no hit):
they are in flight on the maintainer's side. This PR prescribes the
spelling those rules match; it
adds no rule, widens none, and says nothing about what the classifier
does.
- `references/landing-operations.md` is untouched (held by objectstack-ai#19033).
Measured there:
`grep -n rest-channel` → **zero hits**, and `grep -rn 'ccr/'` over
`.claude/` hits only
`platform-readings.md:48` and `rest-channel.md`. So that file neither
points at `rest-channel.md`
nor spells either landing call — the dispatch's mechanism assumption 2
is **falsified**, reported
  rather than acted on.

## Before / after — three lines, all in
`.claude/skills/pm-dispatch/references/rest-channel.md`

Ready row (`:48`), 100 B → 120 B:

```text
- - ✓ draft 转 ready `POST .../pulls/{n}/ccr/ready_for_review`,反向 `.../ccr/convert_to_draft`。
+ - ✓ draft 转 ready `curl -sS -X POST .../pulls/{n}/ccr/ready_for_review -d '{}'`,反向 `.../ccr/convert_to_draft`。
```

Auto-merge row (`:52`), 109 B → 120 B:

```text
- - ✓ auto-merge 挂载 `PUT .../pulls/{n}/ccr/auto_merge` 带 `{"merge_method":"SQUASH"}`,`DELETE` 卸载。
+ - ✓ auto-merge 挂载 `curl -sS -X PUT .../pulls/{n}/ccr/auto_merge -d '{"merge_method":"SQUASH"}'`,`DELETE` 卸载。
```

Section heading (`:34`), 32 B → 120 B — it carries the reason in one
clause, and it governs both rows
plus the other 19 write rows of the same section:

```text
- ## 写侧 —— 全部可迁移
+ ## 写侧 —— 全部可迁移;允许规则按首个 glob 前的字面前缀匹配:verb 紧跟裸 url,`-H`/`-d` 后置
```

No other line changed. Every existing fact of both rows is still on its
own row: the `✓`, the
draft-to-ready and auto-merge-mount meanings, the reverse
`.../ccr/convert_to_draft`, the
`{"merge_method":"SQUASH"}` body and the `DELETE` unmount. Both rows
stay grep-able by
`ccr/ready_for_review` (1 hit) and `ccr/auto_merge` (2 hits), which is
how SKILL.md and
`platform-readings.md` reach them.

## Line and byte budget (the ceiling is 82 and the file was at 82)

- Line count: **82 before, 82 after** — line-neutral, nothing folded,
nothing paid, no ceiling touched.
- Bytes of every changed line, measured with `LC_ALL=C awk '{print
length($0)}'` and cross-checked
through the gate's own `classifyLine`: `:34` 120, `:48` 120, `:52` 120.
The cap is 120 B, the file's
own pre-existing maximum (`:3` is 120), and **no line uses a length
exemption**:
  `scanLineLengths` reports `offenders: []` with `exempt entries: 0`.
- Ratchet verdict, verbatim:
`✓ check-skill-line-ratchet:
.claude/skills/pm-dispatch/references/rest-channel.md is 82 lines
(ceiling 82; headroom 0).`

### The dispatch's mechanism assumption 1, measured and falsified

A single ≤120 B line **cannot** carry the verb, the bare url, `-H
"Content-Type: application/json"`,
`-d '{}'` and a reason clause: the command alone is 95 B with the file's
`...` url abbreviation and
128 B with the url written out, before any prose or the row's existing
facts. Measured packings:
the ready row with `-H` included and its reverse-endpoint clause dropped
is 119 B — it fits only by
shedding an existing fact, and the auto-merge row with `-H` and its
`SQUASH` body is 137 B, which the
gate classifies `over` (it re-wraps to 2 lines). The fallback in the
dispatch (reason on the ready
row, a pointer on the auto-merge row) does not fit either — the rows are
at 120 B with their own
facts. So the invocation shape stayed on the rows and the reason moved
up one level, to the section
heading that governs them, at 120 B. `-H "Content-Type:
application/json"` is not repeated in the two
commands because the row four lines above the first one already carries
it as a rule for every write
(`:44`), and this file's own discipline is ⛔ 不在两处各存一份; the heading names
`-H` so the reader
knows where it goes.

## Reader test

A seat about to land a PR greps `ccr/ready_for_review`, lands on `:48`,
and types
`curl -sS -X POST
https://api.github.com/repos/objectstack-ai/objectstack/pulls/19033/ccr/ready_for_review
-H "Content-Type: application/json" -d '{}'`
— verb first, bare url next (the `...` in the row is this file's
abbreviation for
`https://api.github.com/repos/{o}/{r}`, established at `:7` and used by
21 of the file’s rows), header after.
That command's first bytes are the allow rule's literal prefix, so it
never reaches the classifier.
Reading the heading tells the seat *why* the order is not a style
choice.

## Gates

Derived from this worktree with `node scripts/pm/dispatch-gates.mjs
--commands --repo objectstack-ai/objectstack`
(the tool takes its own change set from the merge base — 1 path). 17
families derived, **17 run, all
exit 0**, exit codes captured redirect-then-`$?`; reconciliation:
`✓ dispatch-gates --ran: 17 derived famil(ies) accounted for — 17 run, 0
NOT-MEASURED`.
Named in the dispatch and green here: `check:pm-skill-ratchet`,
`check:pm-skill-id-lint`,
`check:nul-bytes`, `check:skill-frame-sync`, `check:doc-authoring`,
`check:pm-governed-merges`.
`check:doc-formula-expressions` answered `exit 3` (PREREQUISITE NOT MET
— nothing measured) until
`@objectstack/formula` and `@objectstack/lint` were built under
`scripts/pm/os-verify-lock.sh`; it is
`exit 0` after the build. `check:pm-settings-deny-roster` was run beyond
the derivation because its
roster lives under `.claude/`, the directory this diff is in — `exit 0`,
17 declared content-write
tools = 17 enforced deny entries. `check:pm-dispatch-gates` exceeds the
foreground cap and was
detached; its verdict is reported in the dev report rather than guessed
here.

Repo-wide `pnpm lint` and the rest of the farm are CI's run, not this
PR's local scope.

## 维护者速读(草稿)

**改了什么** —— PM 技能的 `references/rest-channel.md` 里,「转 ready」和「挂
auto-merge」两行原来只
写了 endpoint,现在直接写出席位该敲的那条 `curl` 命令;该节的标题多了一句话,说明为什么命令必须以
「动词 + 裸 url」开头(允许规则按首个 glob 前的字面前缀匹配),`-H`/`-d` 一律后置。

**为什么改** —— 维护者正在提交的四条落地允许规则是字面前缀匹配。席位按习惯写法(先 `-H`、url 加引号)
敲出的命令一条规则都不匹配,会落到会话分类器,于是「七个绿 PR 等着人来点」的症状在规则齐备后照样复现。
规则文本不动,本 PR 只补事实层的拼写。

**风险与代价(含回滚)** —— 风险极低:改的是三行说明文字,不碰任何代码、生成物或发布内容;两行仍可被
`ccr/ready_for_review`、`ccr/auto_merge` grep 到(SKILL.md 与
`platform-readings.md` 靠这两个 token
指过来)。代价是两行与标题都顶到 120 字节上限,下次再往这三行加字就得先折行或搬走一个事实。回滚 =
revert 这一个提交,无迁移、无后续动作。

**席位意见** ——

**你要做的** —— 无需动作;这是事实层(`references/`),按席内契约档复核后进队列。若你更希望「为什么」
那句话落在两行自己身上而不是节标题上,请说一声:那需要把 82 行的天花板抬到 83,而抬天花板要你的裁决。

## Out of scope, noted, not filed

- The dispatch's mechanism assumption 2 is falsified
(`landing-operations.md` points nowhere and
spells no `ccr/` call). Not filed as a card: the file is held by open PR
objectstack-ai#19033, whose author is the
  next one to touch those rows.
- `.claude/settings.json` carries no `Bash(curl -sS -X POST
https://api.github.com/repos/objectstack-ai/objectstack/pulls *)`
rule, so opening this PR through the REST proxy still reaches the
classifier. Observation only —
  the allow-rule text is the maintainer's.

---
_Generated by [Claude
Code](https://claude.ai/code/session_01BTeBejoPUvRHN8WdAJC6oF)_

Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 28, 2026
…n alone — over 5,000 changed lines (objectstack-ai#19042)

Fixes objectstack-ai#19037
Clause-②: no

## What changed

`AGENTS.md` Multi-agent discipline §7 said 「Two classes of PR never
enter this path on green alone」 — (a) a governed-surface diff, (b) the
Version Packages / release-performing PR. The maintainer's ruling adds a
third, and the rules layer contradicted the landing rule until it said
so. The paragraph now names three classes:

> (c) a PR whose **changed lines exceed 5,000** (`additions +
deletions`, generated files included) — it lands only by a human merge,
which is its review record.

and the closing instruction reads the size in the same breath as the
file list and the author: 「Read the PR's file list (`get_files`), **its
author and its size** before you arm anything.」

Ruling (verbatim, untranslated), recorded on objectstack-ai#16045 (comment 5729462393)
and carried by objectstack-ai#19012 / PR objectstack-ai#19033:

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

Read as the landing rule already spells it: `additions + deletions` on
the PR, strictly greater than 5,000, generated files INCLUDED, no
exemption; the terminal is the human's merge. The sentence states the
ruling, not a line number of PR objectstack-ai#19033, so it reads true whether or not
that PR has landed. No new rule beyond the ruling: no exemption, no
lower number, no review that is not the human's merge.

**Why the sentence carries no card number or date.** `AGENTS.md`'s
header: a rule 「carries no incident narrative, no ruling date or
quotation, and no issue-number citation (`pnpm check:pm-skill-id-lint`)
— a rule's provenance lives in the PR that landed it」; that gate's
pattern is `/#[0-9]{3,}/` over `AGENTS.md`, so `objectstack-ai#16045` / `objectstack-ai#19012` in
the sentence would go red. The dispatch's mechanism assumption 1 (name
the ruling by card) is falsified by the gate and by the file's own
header — the provenance is this PR body, and the sentence is
self-contained.

## Line budget — net 0 at the ceiling (1099 / 1099)

The paragraph was wrapped at ~88 bytes with a 27-byte last line;
re-flowed at the file's own 120-byte per-line budget (the Prime
Directives block runs to 120 bytes; §8 beside it to 116) it absorbs the
clause with no new line. Every bold and code span stays whole on its
line, as before.

| line | before (bytes) | after (bytes) |
|---|---|---|
| 495 | 90 | 113 |
| 496 | 88 | 117 |
| 497 | 88 | 109 |
| 498 | 81 | 109 |
| 499 | 86 | 111 |
| 500 | 27 | 75 |
| paragraph | 466 | 640 |
| file | 1099 lines | 1099 lines |

Before:

```text
   ⛔ **Two classes of PR never enter this path on green alone:** (a) a diff touching any
   **governed surface** (**Prime Directive objectstack-ai#14**, which names them and holds the current
   list — **this file and `CLAUDE.md` are on it**, so re-read it rather than recalling
   it); (b) the **Version Packages** PR, or any PR whose merge performs a release
   (**Prime Directive objectstack-ai#15**). Read the PR's file list (`get_files`) **and its author**
   before you arm anything.
```

After:

```text
   ⛔ **Three classes of PR never enter this path on green alone:** (a) a diff touching any **governed surface**
   (**Prime Directive objectstack-ai#14**, which names them and holds the current list — **this file and `CLAUDE.md` are on it**,
   so re-read it rather than recalling it); (b) the **Version Packages** PR, or any PR whose merge performs a
   release (**Prime Directive objectstack-ai#15**); (c) a PR whose **changed lines exceed 5,000** (`additions + deletions`,
   generated files included) — it lands only by a human merge, which is its review record. Read the PR's file
   list (`get_files`), **its author and its size** before you arm anything.
```

## Reader test

A seat about to arm a 6,000-line PR reads §7 and stops: 「Three classes …
(c) a PR whose changed lines exceed 5,000 … lands only by a human
merge」. Before this PR the same seat read two classes, found a
6,000-line diff in neither, and armed.

## Other mentions in `AGENTS.md` (mechanism assumption 3)

`grep -n -i -E 'green alone|two classes|Version Packages'` on
`origin/main` `dbd474431`:

- :291, :299, :307, :309 — Prime Directive objectstack-ai#15's own text (no seat
merges the Version Packages PR; that merge is the release trigger).
Left: it is class (b)'s anchor, not an enumeration of the
never-on-green-alone set.
- :495, :498 — the §7 sentence. Changed (this PR).
- No other line enumerates the set. Two neighbours name class (a) alone
by design and were left: the Skills section's 「Both roots are governed
surfaces — human-merge only, or Prime Directive objectstack-ai#14's pinned-approval
path」 (about governed surfaces only), and Post-Task Checklist step 2's
「⛔ Except a diff touching a governed surface」, which defers to §7 by
reference for the arming rule — see Acceptance notes.

## Gates (local, at `845470659`)

`node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` derived 14 families from the changeset (1
path vs merge base `dbd474431`); all 14 ran, exit codes captured
redirect-then-`$?`, plus the two the dispatch named:

```text
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
pnpm check:agent-test-spelling :: exit 0
pnpm check:docs-audit-scope :: exit 0
pnpm check:driver-memory-census :: exit 0
pnpm check:nul-bytes :: exit 0
pnpm check:pm-governed-merges :: exit 0
pnpm check:pm-governed-prose :: exit 0
pnpm check:pm-skill-id-lint :: exit 0
pnpm check:pm-skill-ratchet :: exit 0
pnpm check:refd-timer-probe :: exit 0
pnpm check:required-contexts :: exit 0
pnpm check:watch-hint-literal :: exit 0
pnpm check:skill-frame-sync :: exit 0
pnpm check:doc-authoring :: exit 0
```

Reconciliation: `✓ dispatch-gates --ran: 14 derived famil(ies) accounted
for — 14 run, 0 NOT-MEASURED`. Ratchet: `✓ check-skill-line-ratchet:
AGENTS.md is 1099 lines (ceiling 1099; headroom 0).` and `widest table
row is 768 bytes (pin 768; headroom 0)`. Id-lint: 0 citations in
`AGENTS.md`. Control-byte scan of the file: no match.

Not measured locally: `check:doc-formula-expressions` (a CEL gate over
fenced formula examples in the docs/skills corpus; this diff adds no
fenced block and the family is outside the derivation; CI runs it).
`check:pm-dispatch-gates` ran detached — its verdict is in the report
comment on objectstack-ai#19037.

## Changeset

`skip-changeset`: `AGENTS.md` is a repo-root instruction file; the root
package is private and no package `files[]` ships it — nothing published
moves.

## Acceptance notes

- noted, not filed: Post-Task Checklist step 2 names only the governed
class in its 「⛔ Except」 clause; the size class reaches a dev's own PR
too. It defers to §7 by reference, and the file header's
one-statement-per-rule principle argues against restating; a fold there
is one line of re-flow if the seat wants it. 承接者: skills seat.
- noted, not filed: the size class has no Prime Directive of its own (it
is a ruling, not a directive); a numbered home would be a new directive
beside objectstack-ai#14 / objectstack-ai#15 — a separate decision, not taken here.

## 维护者速读(草稿)

- **改了什么**:`AGENTS.md` 多 agent 纪律 §7 那句「两类 PR 绿了也不能自动进合并队列」改成三类:新增 (c)
改动行数(additions + deletions,含生成文件)超过 5,000 行的 PR,只能由人工合并。文件行数不变(1099 /
1099),只是把那一段按文件自身的行宽重排。
- **为什么改**:您 09-18 的裁决「修改代码量超过某个行数(比如5000)就应该人工审核」已经落进了 PM
的落地规则与门禁脚本,但规则层 `AGENTS.md` 还写着两类,座席读到的规则与落地规则互相矛盾。
- **风险与代价(含回滚)**:纯文本改动,不动任何脚本或门禁;本地 16 项门禁全绿。回滚 = revert 这一个 commit。
- **席位意见**:(留空,由席位定稿)
- **你要做的**:确认这句话与您的裁决一致(尤其「含生成文件、无豁免」与「人工合并即审核记录」两点),然后由您合并。

---
_Generated by [Claude
Code](https://claude.ai/code/session_01BTeBejoPUvRHN8WdAJC6oF)_

Co-authored-by: os-dev <elon@objectstack.ai>
Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 28, 2026
…erge sweep lists one — the SIZE limb reaches the queue (objectstack-ai#19175)

Fixes objectstack-ai#19036
Clause-②: no

## What this PR does

The maintainer's 2026-09-18 ruling — 「修改代码量超过某个行数(比如5000)就应该人工审核」 —
landed seat-side in PR objectstack-ai#19033 (`HUMAN_MERGE_LINE_THRESHOLD = 5000`,
`testVerdict(paths, { size })`, `sizeVerdict`, `landsByHumanMerge` in
`scripts/pm/check-governed-merges.mjs`). The merge-queue guard's
`merge_group` leg kept handing the predicate no size, so a PR over 5,000
changed lines that a seat enqueued still merged — exactly what PR objectstack-ai#18971
did (+238,310 / −119, nothing governed). This PR carries the limb to the
two places the card names.

**Queue guard (`scripts/pm/check-governed-queue-guard.mjs`)** — a fourth
leg, `runSizeGuard` / `sizeGuardVerdict` / `renderSizeVerdict`:

- reads every queued pull request's `additions` / `deletions` off the
same pull object the head read uses — `makePullReader` grew a `size`
field (through the sibling's own `pullSizeFrom`), no second endpoint, no
new workflow scope (`pull-requests: read` already covers the pull
object; the workflow file is untouched and the self-test pins that no
`issues:` scope appeared);
- judges it through the sibling's IMPORTED predicate: `testVerdict([], {
size })` is the size limb alone (the path limb is the governed leg's
question, already answered on the lifted rows), and `landsByHumanMerge`
reads it — this file declares no threshold and spells no comparison,
pinned against its own source;
- REFUSES on a new exit code `EXIT_REFUSED_OVERSIZED = 8`, printing the
two numbers, their sum, the threshold, the limb (SIZE), the source (`GET
/repos/{o}/{r}/pulls/{n}`) and the one remedy — a human merge (the
ruling quoted untranslated; it never advises making the diff smaller);
- an unreadable size (the pull read throws, or the object carries no
pair) is `EXIT_REFUSED_SIZE_UNREADABLE = 9`, fail-CLOSED — this read
DECIDES, unlike the PR head, which has decided nothing since 2026-09-04;
- the `pull_request` leg is silent and read-free (renders `''`), so that
leg's output stays byte-identical;
- the three-leg exit precedence is a pure function, `groupExitCode`:
governed, then size, then carrier — every block is always printed, one
code exits.

**Post-merge sweep (`check-governed-merges.mjs` default mode)**:

- `classifyCommit` is now `landsByHumanMerge(testVerdict(paths, { size
}))`, so a landing is an entry when EITHER limb fired — a governed merge
as before, and an oversized landing with no governed path at all;
- the size is read LOCALLY off the landed diff: `commitChanges` runs one
`git diff-tree --numstat --no-renames -m --first-parent` per mainline
commit (replacing `commitPaths`' `--name-only`; the self-test pins the
path list byte-identical against `--name-only` on a real fixture, a
merge commit read against its first parent, a binary row at zero);
- `renderReport` counts such rows apart (`N governed merge(s) and M
oversized landing(s) with no governed path`), prints a `⛔ SIZE:` row
with the numbers and the threshold, keeps the same attribution column,
and prints GitHub's own pair beside the landed number only when the two
differ (it rides the attribution GET the row already pays for; it never
decides the listing);
- `--json` entries carry `size` and `humanMerge`.

## The PM's mechanism assumptions, measured

- 「the sweep already reads each merged PR (it prints `merged_by`)」 —
**falsified.** The attribution loop reads `GET /pulls/{n}` only for rows
`classifyCommit` already produced (governed merges); an ungoverned
landing is never read at all, so no API read could have made it an
entry. Route taken instead: the local `--numstat` reading above, the
same source `--branch` already uses in this file, zero API, and it also
lets the sweep classify before any attribution is spent.
- 「the size reading cannot ride the existing head read for an oversized
PR with no governed path; one `GET /pulls/N` per PR in the group」 —
**held.** The size leg reads every queued PR through the same reader;
the call count is pinned (`apiCalls ===
carrierPullsInGroup(rows).length`, deduplicated, group order).
- 「fail-closed on an unreadable size」 — **taken**, on a code of its own
(9) rather than the governed leg's 4, for the same reason 6/7 are split
from 3/4: the legs' refusals must stay separable in a log.
- 「pick the exit code already highest in the table」 — **taken**: a
governed-unsatisfied AND oversized group prints both limbs and exits 3.
Consequence worth stating: a merge group naming no pull request now
exits on the size leg's 9 rather than the carrier's 7 (both blocks still
print their own refusal); pinned.
- **Boundary not decided here:** no authorized APPROVED review and no
review of record lifts the size limb — the landed predicate says a human
MERGE, and nothing has ruled the number the way 2026-08-27 ruled paths.
The rendering says so; widening it is a one-line maintainer decision in
the sibling. Listed under open questions in the report, not implemented.

## Acceptance (the seat's checklist)

- oversized PR, NO governed path, in a merge group → refused, size limb
named: the objectstack-ai#18971 replay pin (`⭐
objectstack-ai#18971-replay-an-OVERSIZED-PR-with-NO-governed-path-is-REFUSED-at-the-queue-on-the-size-code`)
— governed leg clear at zero reads, size leg exit 8, `groupExitCode` 8;
the rendered text names `objectstack-ai#18971`, `238429 changed line(s) (+238310 /
-119)`, `EXCEEDS the human-merge line 5000`, `HUMAN MERGE`;
- governed path AND oversized → both limbs rendered, one exit (3),
pinned;
- exactly at the threshold → clear (guard: `⭐
exactly-the-threshold-is-WITHIN-the-comparison-is-strictly-greater`;
sweep: `⭐
exactly-the-threshold-is-NOT-listed-the-comparison-is-strictly-greater`,
and a real fixture commit of exactly 5,000 lines is not listed);
- `pull_request` leg byte-identical: `renderSizeVerdict` returns `''`
there and a throwing spy proves zero reads;
- unreadable size → exit 9 (throw, missing pair, no recorded reading,
group naming no PR), never a pass;
- sweep lists an oversized merged PR, not an at-threshold one: unit pins
plus a REAL CLI sweep over a fixture repo (`PR objectstack-ai#5001` listed with `⛔
SIZE`, `PR objectstack-ai#5000` not), on stdout and in `--json`;
- self-tests green with counts up: guard 261 → 296, governed-merges 410
→ 435; battery rosters +1 each, floors 21 → 22 and 29 → 30;
- workflow file untouched.

## Verification

Self-tests (worktree at `5c7caff` — the branch merged with `origin/main`
`c229223`, which touched neither file; after `pnpm install`):

- `node scripts/pm/check-governed-queue-guard.mjs --self-test` → exit 0,
`296 cases pass` (was `261 cases pass` at `e8667ee`).
- `node scripts/pm/check-governed-merges.mjs --self-test` → exit 0, `435
assertions` (was `410 assertions`).

Ablation — the comparison inverted in the SIBLING, watched from both
files (proves the guard imports the predicate rather than restating it),
through `scripts/ablation-replace.mjs` in WRAP mode against the
committed head `5c7caff` (the final of three runs; the first two are
recorded below because each taught something):

- mutation: anchor `exceeds: changedLines > HUMAN_MERGE_LINE_THRESHOLD,`
× 1 → the same line with the greater-than sign replaced by a less-than
sign, × 1; blob `6e1112bcd55e` → `c7a42160aabd`; on-disk counts read
back inside the mutated window: replacement 1, original 0.
- guard self-test under mutation: exit 1, `14 of 296 case(s) failed`,
all in the objectstack-ai#19036 battery
(`threshold-plus-one-changed-line-is-OVERSIZED`, `one-under-is-WITHIN`,
`an-OVERSIZED-queued-PR-REFUSES-with-code-8`,
`a-within-sibling-does-NOT-carry-an-oversized-PR-through-the-group`,
`objectstack-ai#18971-replay-an-OVERSIZED-PR-with-NO-governed-path-is-REFUSED-…`,
`governed-AND-oversized-prints-BOTH-limbs-…`,
`an-authorized-APPROVAL-…-lifts-NOTHING-from-the-size-…`,
`a-certified-pure-regeneration-lifts-…-NOTHING-from-the-size-at-the-queue-either`,
…).
- governed-merges self-test under mutation: exit 1, `30 failure(s)`, 0
TypeErrors — the pre-existing SIZE battery
(`5001-changed-lines-is-OVER-it`,
`the-PR-that-prompted-the-ruling-reads-238429-…`,
`--test-with-5001-changed-lines-…-exits-3`, …), the new objectstack-ai#19036 battery
(`an-oversized-landing-with-NO-governed-path-is-a-sweep-ENTRY-…`,
`the-head-counts-the-oversized-landing-APART-…`,
`a-REAL-sweep-LISTS-the-over-by-one-landing-…`, …) and two objectstack-ai#13307
live-mirror sweep pins that now see a phantom oversized row (every small
landing reads over an inverted line — the expected direction).
- restore: blob after restore `6e1112bcd55e…` == `HEAD` blob, `git diff
HEAD` empty, `git status --porcelain` empty, anchor count back to 1.
- Run 1 (on `3c7f5ec`): guard 14/296 red as above; the merges leg exited
1 by a **TypeError** in my new battery (a pin dereferenced the fixture
that classifies to null under the mutation), so its failures were not
named — fixed in `28faa51` (null-guarded pins; a red case must be a
named one). Run 2: run against that fix while it was still UNCOMMITTED;
the tool restores to `HEAD` by design, so the fix was discarded by the
restore — caught by the pre/post blob compare (`6e1112…` before,
`859965…` after), re-applied, committed, and run 3 is the record above.
The "commit the fix first" rule, measured on the fix to the pins.

Gates (derived in the worktree with `node scripts/pm/dispatch-gates.mjs
--repo objectstack-ai/objectstack --commands` — 33 commands, identical
to the dispatch's list; reconciled with `--ran`; exit codes captured
after redirection, never through a pipe; run at `5c7caff`, the final
head, after the ablation's restore was proven):

| # | command | exit | verdict line (from the gate's own output) | wall
|
|---|---|---|---|---|
| 1 | `node scripts/check-ci-filter-parity.mjs` | 0 | OK: all 184
declared cross-package glob(s) (131 unique) are covered by `core` or
`crosspkg`, every `crosspkg` entry still covers one, and the `test`
job's `if:` | 0s |
| 2 | `node scripts/check-closing-keyword-parity.mjs` | 0 |
check-closing-keyword-parity: OK (3 parsers agree on all 9 keywords and
both measured separators; sweep found 5 file(s) carrying the grammar
across 8993 tracked | 1s |
| 3 | `node scripts/check-closing-keyword-parity.mjs --self-test` | 0 |
✓ check-closing-keyword-parity --self-test: 40 assertions, 5 mutations
of the shipped parsers each driven to red. | 3s |
| 4 | `node scripts/check-comment-mask-corpus.mjs` | 0 | ✓ comment-mask
corpus sweep [scripts/js-comment-mask.mjs]: 6896 files, 0 disagree, 0
unparseable, 81.9s (comparator self-test: 26 cases pass). | 83s |
| 5 | `node scripts/check-declaration-mirrors.mjs` | 0 | OK: 10
hand-written declaration(s) agree with their modules on name, kind and
required arity. | 0s |
| 6 | `node scripts/check-declaration-mirrors.mjs --self-test` | 0 | All
29 self-test cases passed. | 0s |
| 7 | `node scripts/check-scripts-symbol-anchors.mjs` | 0 | ✅
check-scripts-symbol-anchors: 3524 anchors across 265 scripts resolve —
52 symbol (52 declaration, 0 literal), 3472 file-level, 0 cross-repo, 1
exempt, 2 cont | 4s |
| 8 | `node scripts/check-scripts-symbol-anchors.mjs --self-test` | 0 |
✅ check-scripts-symbol-anchors --self-test: every finding class
provoked, comment-prose projection wired, declined shapes counted not
missed, allowance rows exa | 4s |
| 9 | `node scripts/check-self-test-wired.mjs` | 0 | ✓
check-self-test-wired: every one of the 214 script(s) CI runs that ship
a `--self-test` has that self-test run by CI. | 2s |
| 10 | `node scripts/check-self-test-wired.mjs --self-test` | 0 |
check-self-test-wired --self-test: 3 live ledger row(s) verified, plus
the comment mask, the right boundary, alias resolution and both audit
directions — 10 dec | 2s |
| 11 | `node scripts/check-self-test-workflow-commands.mjs` | 0 | ✓
check-self-test-workflow-commands: no self-test CI runs prints a line
the Actions runner would parse as a workflow command. | 51s |
| 12 | `node scripts/check-self-test-workflow-commands.mjs --self-test`
| 0 | check-self-test-workflow-commands --self-test: both measured parse
rules pinned (legacy form anywhere in a line, current form only at line
start), the innocent- | 3s |
| 13 | `node scripts/check-skills-token-ratchet.mjs` | 0 | ✓
check-skills-token-ratchet: 34 authored bundle file(s) within their
ceilings; 10 generator-owned file(s) measured, not ratcheted. | 0s |
| 14 | `node scripts/check-skills-token-ratchet.mjs --self-test` | 0 | ✓
check-skills-token-ratchet self-test: 65 cases pass. | 1s |
| 15 | `node scripts/check-whole-set-label-write.mjs` | 0 | ✓
check-whole-set-label-write: 0 violations — 328 file(s) over 3 root(s) ·
12 raw mention(s) · 12 in comments/prose (cleared) · 0 in EXECUTABLE
content (judged) · 191 `uses:` pin(s) over 18 di | 2s |
| 16 | `node scripts/check-whole-set-label-write.mjs --self-test` | 0 |
✓ check-whole-set-label-write --self-test: all cases pass (24 fixture
trees + 5 refusals + 1 allowlist hatch) | 0s |
| 17 | `node scripts/pm/bare-root-worklist.mjs --self-test` | 0 | OK
self-test: 81 live row(s), 59 unreachable as spelled, 46 recorded
verdict(s) — none stale, none missing, none contradicted (12 row(s)
whose gate carries the | 29s |
| 18 | `node scripts/pm/check-governed-queue-guard.mjs --self-test` | 0
| ✓ check-governed-queue-guard self-test: 296 cases pass
(register-driven verdicts, the queue/PR event split, latest-decisive
approval reduction, the 2026-09-04 a | 0s |
| 19 | `pnpm check:agent-test-spelling` | 0 | ✓
check-agent-test-spelling: 0 violations — 518 file(s) · 9204 bare `--`
token(s) · 1749 launcher-rooted run(s) · 13 separator(s) JUDGED · 6
vitest-backed scrip | 4s |
| 20 | `pnpm check:bash32-floor` | 0 | ✓ check-bash32-floor: 31 tracked
shell file(s) under scripts/**, .claude/hooks/**, .githooks/** name no
bash 4+ construct outside a comment, a guarded ${VAR:-} | 2s |
| 21 | `pnpm check:cli-command-ids` | 0 | ✓ check-cli-command-ids: 63
module(s) under packages/cli/src/commands examined, all of them
default-export a class whose inheritance chain reaches oclif's `Comm |
11s |
| 22 | `pnpm check:closing-target-claim` | 0 | ✓
check-closing-target-claim self-test: 105 cases pass. | 1s |
| 23 | `pnpm check:cross-package-test-inputs` | 0 | OK: 29 package(s)
read outside themselves, all declared, and turbo.json hashes every
declared glob (6 of them on a split "test:repo" task); 13 walked root(s)
ju | 20s |
| 24 | `pnpm check:driver-memory-census` | 0 |
check-driver-memory-census: OK — every declaration is ledgered, every
ledger entry is live, and every ruled file states "objectstack-ai#6664 census: 2 ruled
consumers". This | 4s |
| 25 | `pnpm check:entry-guard` | 0 | ✓ check:entry-guard: 265 scripts/
file(s) — every entry guard goes through invoked-as.mjs; 206 export
bindings, 206 of them inert on import (0 known-unsafe, ⛔ S | 47s |
| 26 | `pnpm check:nul-bytes` | 0 | check-nul-bytes: OK (scanned 8986
text file(s) -- 8986 tracked, 0 untracked-not-ignored; skipped 7 binary;
no raw ASCII control bytes). | 3s |
| 27 | `pnpm check:parse-guard` | 0 | ✓ check:parse-guard: 264 scripts/
file(s) — every TypeScript parse goes through ts-parse.mjs. | 2s |
| 28 | `pnpm check:pm-governed-merges` | 0 | ✓ check-governed-merges
--self-test: 435 assertions (the unified governed predicate + near
misses, subject→PR spellings, window parsing, the objectstack-ai#12633 landing wind |
6s |
| 29 | `pnpm check:pnpm-filter-targets` | 0 | ✓
check:pnpm-filter-targets: 151/199 `--filter` occurrence(s) across 40
file(s) resolve against 81 workspace package(s); 48 not judged (2
foreign, 26 interpolat | 3s |
| 30 | `pnpm check:ratchet-remedy-authority` | 0 | OK
check-ratchet-remedy-authority: 259 scripts swept (scripts/*.{mjs,mts} +
scripts/pm/*.{mjs,mts}); 15 mark the expanding remedy ⛔ MAINTAINER-ONLY,
5 turn it | 4s |
| 31 | `pnpm check:refd-timer-probe` | 0 | OK check-refd-timer-probe:
6891 source file(s) swept; the process-global timer probe is read in
packages/qa/refd-timer-testkit/src/index.ts and nowhere else. | 12s |
| 32 | `pnpm check:watch-hint-literal` | 0 | ✓ check-watch-hint-literal:
71 declaration(s) across 4 rostered name(s) -- ROOT_DIR_WATCH_HINTS 47,
ROOT_FILE_WATCH_HINTS 13, ROOT_WATCH_HINTS 3, DECLARED_WATCH | 5s |

32 command(s); 0 non-zero exit(s).

Reconciliation (`node scripts/pm/dispatch-gates.mjs --repo
objectstack-ai/objectstack --ran` over a record whose every line carries
`:: exit N`): `33 derived, 32 run, 0 NOT-MEASURED, 1 UNRUN` — the one
unrun family is `pnpm check:pm-dispatch-gates`, which is **NOT MEASURED
at PR-open time**: it runs only under the shared verify lock (≈1,000 s
under contention); a first run started on `3c7f5ec` overlapped the
ablation window (`dispatch-gates.mjs` imports `sizeVerdict` from the
sibling, so a child it spawned in that window could have read the
inverted comparison), and a second run on `5c7caff` is queued behind it.
Its verdict lands in the `os-dev-report` comment on objectstack-ai#19036, not here —
this body is written once.

Lint, narrowed and measured: `npx eslint --no-inline-config --format
json` on the two changed files → exit 0, 2 files, 0 errors, 0 warnings.
Narrowing evidence: ① eslint's configured population
(`eslint.config.mjs`, the `**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}` block)
includes `scripts/pm/*.mjs`, and `--print-config` resolves a config for
the file (parser `typescript-eslint/parser`); ② the JSON output counts 2
files; ③ type-aware linting is not enabled (`parserOptions.project` and
`projectService` both absent in the resolved config), so this diff
cannot move any untouched file's verdict. The repo-level `pnpm lint`
sweep is CI's run.

## Acceptance notes (noted, not filed)

- The `pull_request` event payload carries `additions` / `deletions`, so
a size early warning on the PR leg would cost zero reads. Not taken: the
ruling is about the landing, the seat-side pre-check already refuses
before arming, and the PR leg's byte-identity is a standing constraint.
承接者: the skills seat, if the maintainer wants the forecast.
- The carrier leg and the size leg each read `GET /pulls/{n}` once per
queued PR — the same endpoint twice. A shared per-run pull read would
halve it; kept separate so each leg's refusal and count stay separable
in a log. Groups are small. 承接者: none.
- No governed reference text enumerates the guard's exit table, so codes
8/9 leave no Tier S doc stale (`grep` over
`.claude/skills/pm-dispatch/references/**` and `SKILL.md` for `exit
6`/`exit 7`/`EXIT_REFUSED_CARRIER`: 0 hits).

`skip-changeset`: `scripts/pm/**` publishes nothing from any released
package (no `files[]` of any package ships it).

---
_Generated by [Claude
Code](https://claude.ai/code/session_01W5y9kRg1YtYaMQYExVLRc2)_

---------

Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 28, 2026
…aimed region, not the same file — SKILL.md :441 and its core-rules twin (objectstack-ai#19317)

Fixes objectstack-ai#18891
Clause-②: no
Ruling-ref: 5727164406 — director seat, batch 157 item 2, letter 甲,
maintainer 「其他同意」 2026-09-18T08:10Z

## What changed — two lines, one word each, net 0 in both files

| File | Line | Before | After | Bytes |
|---|---|---|---|---|
| `.claude/skills/pm-dispatch/SKILL.md` | :441 | 「-
同文件单跨轮硬串行;延后不是搁置,被延后那一刻就把已知的坑记到该 issue 上。」 | 「-
同区域单跨轮硬串行;延后不是搁置,被延后那一刻就把已知的坑记到该 issue 上。」 | 107 → 107 |
| `.claude/skills/pm-dispatch/references/core-rules.md` | :105 | 「-
同批独立性按文件面不相交判,⛔ 不按包;同文件硬串行,冲突交合并队列仲裁 ⛔ 不手排。」 | 「- 同批独立性按文件面不相交判,⛔
不按包;同区域硬串行,冲突交合并队列仲裁 ⛔ 不手排。」 | 119 → 119 |

Line counts: SKILL.md 813 / 813, core-rules.md 151 / 151. `:437` is
untouched; `SINGLE_CLAIM_PATHS` stays the only whole-file single-writer
set; the 「延后不是搁置…」 half stays byte-verbatim; the core-rules twin lands
in the same PR as SKILL.md:44 orders. No script, no gate, no other line
of either file. The commit is `5676515` on
`claude/issue-18891-region-level-serial`, base `801415a`.

## Why this rendering and not the ruling's sentence verbatim — measured,
not assumed

The ruling's :441 text 「同区域(认领申报的文件面重叠)单跨轮硬串行;同文件不同区域是普通并发,后落地方解冲突。」 is
123 bytes as a bullet line, and 198 bytes with the 「延后不是搁置…」 half the
ruling keeps. `scripts/pm/check-skill-line-ratchet.mjs` (`pnpm
check:pm-skill-ratchet`, not `check:pm-governed-prose` as the dispatch
assumed) caps every non-exempt line of this file at `MAX_LINE_BYTES =
120` and holds the file at ceiling 813 with headroom 0, so the ruling's
own sentence cannot land as written under the file's own gate without a
script edit that both the ruling (「⛔ no new script, no gate」) and the
dispatch (net 0, no script) refuse. Readings taken on the real gate,
each mutation restored to the HEAD blob (hash-verified, `git diff HEAD`
empty) before the next:

| Run | :441 rendering | `check:pm-skill-ratchet` verdict |
|---|---|---|
| R0 | unmodified | ✓ SKILL.md is 813 lines (ceiling 813; headroom 0) |
| R1 | ruling sentence + deferral half, ONE physical line (198 B) | ✗ 1
line over the 120-byte budget: L441 (198B) |
| R2 | ruling sentence (123 B) / deferral half (79 B), TWO lines | ✗
L441 (123B) over budget AND ✗ 814 lines; the ratchet ceiling is 813 |
| R3 | ruling sentence with 不同区域→异区域 (120 B) / deferral half, TWO lines
| ✗ 814 lines; the ratchet ceiling is 813 |
| R4 | core-rules :105 同文件硬串行 → 同区域硬串行 (119 B) | ✓ core-rules.md is 151
lines (ceiling 151; headroom 0) |
| landed | :441 同文件 → 同区域 (107 B) plus R4 | ✓ both files at ceiling,
headroom 0 (exit 0) |

The neighbours cannot absorb the 74-byte deferral half either: :436 to
:452 measure 118 / 114 / 112 / 114 / 118 / 107 / 117 / 104 / 67 / 115 /
79 / 111 / 82 / 98 / 120 / 117 / 109 bytes, and the only other line that
speaks of deferral (:452, 「阻塞解除后延后单重定价…」, 109 B) has 11 bytes of slack.
A bigram-Jaccard scan over the file's 627 bullet lines finds no provable
duplicate to retire: the top pairs (J 0.59 down to 0.35) are distinct
pointer lines into `references/dispatch-runbook.md` carrying different
topics.

What the one-word rendering keeps, and what it delegates to lines the
ruling left standing: 「同区域…硬串行」 IS the ruling's narrowing (its facet ①:
one line narrower, a special case removed). The 「同文件不同区域是普通并发,后落地方解冲突」
clause is exactly what :437 already states for every non-single-writer
path (「共享其它路径是普通并发,后落地方解冲突」): once :441 no longer claims the whole file,
the same file in different regions is one of those other paths by
elimination, and the two lines contradict on no path. The parenthetical
definition of 区域 is carried by :449 (「认领申报文件面到区域级,拿不准就串行」, the escape
hatch the ruling names as staying), by :278 (「区域写不清就只能整文件串行」) and by
`references/seat-post-protocol.md` :20 (every serial-queue entry is
「每张卡认领的区域」).

The alternative the seat may prefer at review: land the ruling's
sentence as R3 (120 B, two lines) and raise the SKILL.md ceiling 813 →
814 under the ratchet's own maintainer exit (「Raising a ceiling requires
a maintainer ruling quoted in the PR」). The precedent is the last
landing on this file, e872ef4 (PR objectstack-ai#19033, 2026-09-19): 812 → 813 for one
ruled line, with the ratchet comment 「Ruled content is not growth」. That
route is a `scripts/pm/check-skill-line-ratchet.mjs` edit and a net +1,
both outside this dispatch's constraints, so it is stated here and not
taken.

## Tier and landing

- `node scripts/pm/check-governed-merges.mjs --test` with both paths:
exit 3, GOVERNED — Tier S (席内达档复核落地), `.claude/**` ×2; size NOT MEASURED
by `--test` (dispatch-gates' derived count: 4 changed lines, +2 / −2,
under 5000).
- `node scripts/pm/dispatch-gates.mjs --tier` on both paths: MANDATORY
at CONTRACT_REVIEW_TIER (clause ①: the PM dispatch skill MAIN file).
- This PR stays DRAFT; it lands on the owning seat's `## Contract
review` record for head `5676515` (AGENTS.md Prime Directive objectstack-ai#14).
Label: `skip-changeset` (`.claude/**` publishes nothing); no changeset
file.

## Gates — derived union on head `5676515`, each exit captured before
any pipe

`node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` from the worktree with no paths (changeset 2
committed / 0 working tree / 0 untracked vs merge base `801415a`)
derived 20 commands, identical to the dispatch-time lead; `--ran`
reconciles 20 derived / 20 run / 0 UNRUN. Two extras were run because
the derivation flags them: `check:pm-settings-deny-roster` (a roster
under `.claude`, marked silence-is-not-evidence) and
`check:skill-frame-freshness` (pins the four-axis block).

| Command | Exit |
|---|---|
| pnpm check:pm-skill-ratchet | 0 |
| pnpm check:pm-skill-id-lint | 0 (27 files clean) |
| pnpm check:pm-governed-prose | 0 |
| pnpm check:skill-frame-sync | 0 |
| pnpm check:skill-frame-freshness | 0 (current with origin/main) |
| pnpm check:pm-governed-merges | 0 |
| pnpm check:pm-expected-skips | 0 |
| pnpm check:pm-half-states | 0 |
| pnpm check:doc-authoring | 0 |
| pnpm check:nul-bytes | 0 (9034 text files, no raw control bytes) |
| pnpm check:agent-test-spelling | 0 |
| pnpm check:cross-package-test-inputs | 0 |
| pnpm check:driver-memory-census | 0 |
| pnpm check:refd-timer-probe | 0 |
| pnpm check:watch-hint-literal | 0 |
| pnpm check:pm-settings-deny-roster | 0 |
| pnpm --filter @objectstack/lint run check:doc-formula-expressions | 3
then 0 — the first run was PREREQUISITE NOT MET (`@objectstack/formula`
and `@objectstack/lint` not built; the gate says nothing was measured);
after `pnpm exec turbo run build --filter=@objectstack/formula
--filter=@objectstack/lint` under the verify lock (4/4 cached), the
rerun is exit 0: 58 self-test cases, 22 / 9 / 14 examples judged clean |
| node scripts/check-closing-keyword-parity.mjs | 0 |
| node scripts/check-closing-keyword-parity.mjs --self-test | 0 |
| node scripts/check-comment-mask-corpus.mjs | 0 (6920 files, 0
disagree) |
| node scripts/pm/check-governed-queue-guard.mjs --self-test | 0 |
| node scripts/pm/check-harness-current.mjs --self-test | 0 |
| node scripts/pm/check-governed-merges.mjs --test (both paths) | 3 =
GOVERNED, Tier S (the expected code) |

The last commit is `5676515` and every command above ran after it.
`origin/main` moved by one commit since the base (`e3b3cdd`,
metadata-protocol) that touches neither edited file nor the ratchet, so
no merge is owed before review.

## Acceptance notes

- noted, not filed: SKILL.md :120 (the `priority:p0` table row 「⛔
不豁免同文件串行、深度等待与认领协议」), its core-rules twin :36, and
`references/lanes/services.md` :26 (「同文件卡跨轮硬串行」, a lane-specific
stricter rule) still name the serial discipline by FILE. The ruling
scoped :441 and :105 only; these are name references to the discipline,
not a second definition, so they are a naming nit rather than a defect
class. 承接者: the domain:skills seat's next SKILL.md card.
- noted, not filed: `check-closing-keyword-parity` prints an
informational line that
`packages/spec/api-surface-declarations/system.txt` (3,568,357 bytes)
exceeds its 2 MiB cutoff for unregistered files; exit 0 and pre-existing
on `origin/main`. 承接者: none identified.
- The two neighbouring lines the ruling names as staying were read and
left alone: :437 (unchanged by the ruling) and :449 (「拿不准就串行」).

## 维护者速读(草稿)

### 改了什么

派发协议「候选与批次」一节里的一条并发规则改了一个词:原来写「同文件单跨轮硬串行」,现在写「同区域单跨轮硬串行」;核心条款摘要里的同一条规则同步改成「同区域硬串行」。两处各改一个词、各不增减行数;`:437`(single-writer
路径只由 `SINGLE_CLAIM_PATHS` 枚举,其它路径普通并发)原样不动。

### 为什么改
同一节里原有两条互相打架的规则:一条说除 `.objectui-sha`
之外的所有共享文件都是普通并发、后落地的一方解冲突;四行之后另一条说同一文件的卡跨轮必须硬串行。`SINGLE_CLAIM_PATHS`
只有一条,所以对仓里的每一个文件两条规则都给出相反指令;各席位各自选了一条执行,一张停在决策箱里的 PR(25 个 spec 文件)就把
spec
车道大半冻住。裁决(甲)把「硬串行」收窄到「认领申报的文件面重叠的区域」:同文件不同区域是普通并发,后落地方解冲突。改动后,同文件异区域自然落到
`:437` 的「其它路径普通并发」,两条规则在任何路径上都不再冲突。

### 风险与代价(含回滚)
风险:裁决原句(「同区域(认领申报的文件面重叠)单跨轮硬串行;同文件不同区域是普通并发,后落地方解冲突。」)本身 123 字节,超过本文件
120 字节的行宽门禁;加上裁决要求保留的「延后不是搁置…」半句共 198 字节;文件又顶在 813
行的棘轮上限、余量为零,所以裁决原句无法原样落地(门禁实测:一行写法红,两行写法也红)。本 PR
采用最小改法(一个词),把「同文件异区域普通并发」这半句交给紧邻的 `:437` 承担,把「区域」的定义交给 `:449`
与串行队条款承担。若席位认为裁决原句必须逐字落地,替代路线是按棘轮自己的维护者例外把上限抬到 814(先例:2026-09-19 的
e872ef4),那需要改脚本、净增一行,不在本单授权内。回滚:两个文件各回退一个词,零副作用。

### 席位意见
(留空,席位定稿)

### 你要做的
无需你操作:这是 Tier S 受管面,由 skills 席位做契约档复核后经队列落地。只有一处可能需要你一句话:若你希望裁决原句逐字进入
SKILL.md(而不是本 PR 的一词改法),请回「抬上限」,席位据此改走 813 → 814 的路线。

---
_Generated by [Claude
Code](https://claude.ai/code/session_017ETYWqMQD4qMtZzAGovWNi)_

Co-authored-by: Claude <noreply@anthropic.com>
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