governed: a fork PR is a proposal, never a delivery — the FORK predicate beside PATH and SIZE, plus the charter's external-contributions section - #19526
Merged
Conversation
…ate beside PATH and SIZE, and the charter's 外部贡献 section Ruled: batch 207 item 1, letter 是 (maintainer, verbatim: 「fork PR 的处理同意你的意见。」; the director's record on the card carries the four-step intake). A pull request whose head lives in another repository — head.repo.full_name differs from base.repo.full_name, or head.repo is null because the fork was deleted (fail closed) — is a proposal, never a delivery, whatever its paths: no AI seat flips it ready, enqueues it, arms auto-merge on it or approves it; the owning seat adopts the diff onto an internal branch (Co-authored-by: the contributor) and lands that. scripts/pm/check-governed-merges.mjs (+80 net incl. self-tests): a third limb beside PATH and SIZE. `forkVerdict(pull)` reads the pair off the PR object `--pr` already fetches; `testVerdict` carries `fork` and `checks`; `landsByHumanMerge` fires on any limb, so the fork exits on the existing GOVERNED code through the Tier H terminal with its own reason sentence. `--pr` also reads the head's check-run count (one GET on the chosen channel): a head with ZERO check runs prints NOT MEASURED — never green — in the register the size limb already uses; `--test` / `--branch` cannot see a head repo and say NOT MEASURED there too. Self-test battery beside the SIZE cases: fork head; deleted fork repo; same-repo head on ordinary and on governed (H and S) paths unchanged; zero / non-zero / unread check runs; the readings off an injected `--pr` fetch. AGENTS.md (1109 → 1109): one sentence in Prime Directive 14's paragraph; paid by retiring the Skills section's restatement of the register and its tiers, whose home is that directive (lines 259–275). SKILL.md (+1, 815 → 816 / 819): one rule line in 入队与落地. references/core-rules.md (151 → 151): the landing-rule line rewritten in place to carry the twin. New references/external-contributions.md (16 lines, every line ≤ 120 bytes): the ruled 外部贡献 section — card first, the human decides the problem, the seat adopts the diff, a fork's CI is never approved by a seat, and the supply-chain clause deferred. Claude-Session: https://claude.ai/code/session_017ETYWqMQD4qMtZzAGovWNi Co-authored-by: Claude <noreply@anthropic.com>
Collaborator
Author
Contract reviewServed-tier: ① Derived judgments
② Semver levelNone — nothing published; ③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
os-zhuang
approved these changes
Sep 21, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #19470
Clause-②: no
The ruling this lands
Maintainer, batch #207 item 1, letter 是, verbatim and untranslated: 「fork PR 的处理同意你的意见。」 — recorded by the director on the card (comment 5754996112) as: a fork PR is a proposal, not a delivery. No agent seat flips it ready, enqueues it, arms auto-merge on it, or approves it — ever. It enters through the card door (card first; the human decides the problem, not the code; the seat adopts the diff and never lands the fork PR; a fork's CI is never approved to run by a seat, zero check runs read NOT MEASURED, never green), with the minimal mechanism: a second predicate beside the governed one in
scripts/pm/check-governed-merges.mjs, one sentence in AGENTS.md, one short 〈外部贡献〉 section in the charter, the NOT-MEASURED wording. No new label, no new sweep; the supply-chain clause is deferred until the first such fork PR appears.The live specimen, PR #19342 (head
jinyitao123/objectstack,author_association: FIRST_TIME_CONTRIBUTOR, 0 check runs on5fa7b6d, 0 labels), is read here as evidence only. It stays untouched by this PR: its disposal is the director's and triage's act in the same stroke, and this branch writes nothing on it.What changed (five files, all on the claim's surface)
scripts/pm/check-governed-merges.mjsAGENTS.md.claude/skills/pm-dispatch/SKILL.md.claude/skills/pm-dispatch/references/core-rules.md.claude/skills/pm-dispatch/references/external-contributions.md1.
check-governed-merges.mjs— the FORK limbforkVerdict(pull)(pure, exported, besidesizeVerdict): readshead.repo.full_nameandbase.repo.full_nameoff the PR object; NOT MEASURED when there is nobase.repoto compare against; a fork when the head repo differs — or isnull(the fork was deleted): a deleted fork is still a fork, fail closed.testVerdict(paths, { size, fork, checks })carriesforkandchecks;humanMergefires on any of the three limbs;landsByHumanMergereads the fork limb too, so a fork exits on the EXISTING GOVERNED code (3) through the Tier H terminal — every caller that already routes 3 to the human hand routes a fork there without learning a new code.applyGeneratedExceptionskeeps the fork limb across a generated-artifact lift (a lift moves a PATH, never the head repo).renderForkLines(fork, checks)(pure, exported): the fork sentence under the verdict — a PROPOSAL, never a delivery, whatever its paths; no AI seat flips it ready, enqueues it, arms auto-merge on it or approves it; a seat's review is required INPUT, never the permission; the owning seat adopts the diff onto an internal branch (Co-authored-by:the contributor) and lands THAT; requests to the contributor go only as review comments on the fork PR, which is closed with thanks and the landing link. When the PATH limb is clear the head line readspaths: none on the register — the HEAD REPO decides this PR:(the SIZE limb's shape); on a governed diff the sentence rides under the tier block, Tier S included (a fork head on a.claude/**path is still adopted, never landed).--pr Nreads the head's check-run count offGET /repos/{slug}/commits/{sha}/check-runs— one more GET, on the channel already chosen for the PR read.total_count: 0printscheck runs on head SHA: 0 — NOT MEASURED, never greenin the register the size limb already uses; a count that did not read printsNOT READ (reason) — read as NOT MEASURED, never green; a non-zero count prints nothing.--testand--branchcannot see a head repo and now say so on stdout (head repo: NOT MEASURED — this verdict cannot tell a fork PR from an internal one; --pr N reads it), the same discipline as the size limb: a verdict silent about a leg it did not run reports a clearance it never measured. The--branch/--testbyte-identity pin still holds (both print the same line).--jsoncarriesforkandchecksbesidesize.⭐ the FORK predicate: head repo ≠ base repo is a proposal, never a delivery(6 cases, floor 6, roster 30 → 31), placed between the SIZE battery and the audit-half battery, never at the tail: fork head on ordinary paths ⇒ H route with the fork sentence and noordinary queue landing applies;head.repo === null⇒ the same; same-repo head on ordinary paths ⇒ unchanged (NOT governed, no fork line); same-repo head onAGENTS.md/.claude/agents/os-dev.md⇒ Tier H / Tier S word for word, while a fork head on the Tier S path still prints the fork sentence; no PR object ⇒ NOT MEASURED said; zero check runs ⇒ the NOT-MEASURED line, 43 ⇒ absent, unread ⇒NOT READ; andfetchPullFilesagainst an injected fetch reads the head repo off its own GET and the count off the head sha. Header docblock gains a "The FORK predicate" section; the summary line names the battery.2.
AGENTS.md— one sentence, net 0Prime Directive #14, first paragraph, :264 before (102 B):
:264–:266 after (113 B / 116 B / 106 B):
"whatever its paths" is the sentence's shape saying the fork rule is a predicate on the PR, not a surface: the paragraph still names exactly the register's six surfaces and no
**-shaped span was added, sopnpm check:pm-governed-prosestays green (quoted below).Payment (+2 lines bought by one retired restatement, ⛔ not by the ceiling, not by re-wrap): the three paragraphs of Prime Directive #14 are greedy-packed already (878 / 1789 / 918 characters joined ⇒ 8 / 16 / 8 lines at the 120-column cap, exactly what they occupy), so re-wrap buys nothing. Retired: the Skills section's line (pre-edit :800, 117 B, plus its trailing blank line):
It restated the register and its tiers, whose home is the directive it pointed at. Surviving home and grep proof on this head:
grep -n 'skills/\*\*' AGENTS.md→ :6 (the CLAUDE-conflict clause), :260 (the register names.claude/**andskills/**), :273 (Tier H namesskills/**);grep -n 'Tier S' AGENTS.md→ :258, :276 (Tier S = all of.claude/**), :292;grep -n 'Both roots' AGENTS.md→ no hits. The Skills section keeps its two-root catalog lines; only the restatement left.3.
SKILL.md— one rule line in 〈入队与落地〉 (+1, 108 B)Inserted at :643, directly under the section's pointer line (
- 细则见 references/landing-operations.md,落地窗口查阅。):Placement (four axes): 业务需求 — the acts the rule forbids (ready / 入队 / auto-merge / 批准) are the acts this section governs, so a seat reading its landing checklist meets the fork rule where it would otherwise act; 长远合理性 — one rule line, one pointer, the detail in a references file, the same shape as the section's first line; 防 AI 犯错 — the line sits before the 条款② gate and the PASS ⇒ ready ⇒ auto-merge line, so the fork stop is read before the enqueue reflex; 创业阶段不扩散 — ≤ +1, no new section. ⛔ Not in the 分诊 / 定级 region (:174–:175, :362, :368–:372, :379 are #19494's) and not on PR #19488's lines (the
area:*rows, :119–:134, :215, :243, :259, :294, :310, :371–:372, :470–:471). PR #19513 edits :632–:633 and :647 / :653 of the same section; :643 sits between its two hunks with unchanged context on both sides, so the later lander mergesorigin/mainonce with no conflict expected.4.
references/core-rules.md— the twin, paid in place (151 → 151):122 before (118 B) → after (112 B):
Every landing-rule line in the core subset sat at 111–118 B of the 120 B cap, so the twin could only ride a compression: 「命中…即分叉」 and 「席内…复核」 are folded to 「规则层」 and 「达档」 (the SKILL.md lines they summarise are unchanged), and the freed bytes carry the rule. No clause dropped. PR #19513 and PR #19488 touch :55–:69, :102, :106–:113 of this file, not :122.
5.
references/external-contributions.md— new (16 lines, every line ≤ 120 B)Frame as its siblings (title, a 「见 SKILL.md 〈…〉」 pointer line, one rule per line). Bytes per line: 45 · 0 · 117 · 117 · 0 · 113 · 77 · 97 · 92 · 102 · 110 · 113 · 106 · 113 · 116 · 120. Content, the four points of the ruling in substance: ① card first — triage's fire scans open PRs whose head repo ≠ base repo, files a card from the PR body graded like any card, posts the ONE fixed comment (
this repository works card-first; filed as #N; this PR stays a draft until the card is graded), a fork PR with no card does not exist on the board; ② the human decides the problem, not the code — a plain reproducible defect routes to a seat, floor items (security / permission boundary, contract, feature) go to the decision box as a business question; ③ the seat adopts the diff, never lands the fork PR — internal branch, cherry-pick or re-implementation,Co-authored-by:the contributor, full gate derivation + at-tier review + the queue as for internal work, requests to the contributor only as review comments on the fork PR, closed with thanks and the landing link when the internal PR lands; ④ a fork's CI is never approved to run by a seat, zero check runs read NOT MEASURED, never green; the machine face (check-governed-merges.mjs --pr Nroutes head repo ≠ base repo through the H(人合) exit,head.reponull the same); and the deferred supply-chain line.check-skill-line-ratchet.mjsdoes not require a CEILINGS row for a new references file (it enumerates onlyreferences/lanes/for uncovered files; the file is simply outside the ratchet's map, asreferences/instrument-discipline.mdon PR #19513 is). No row was added: the ratchet script is outside this card's file surface. Noted under Acceptance notes for the seat.The two
--pr 19342readingsBefore (origin/main 8fc6a5f),
node scripts/pm/check-governed-merges.mjs --pr 19342 :: exit 0:After (this head 0a50588),
node scripts/pm/check-governed-merges.mjs --pr 19342 :: exit 3:--jsonon the same run:governed: false,tier: null,humanMerge: true,fork: { measured: true, isFork: true, headRepo: "jinyitao123/objectstack", baseRepo: "objectstack-ai/objectstack" },checks: { sha: "5fa7b6dc9d76657a57e30fbcd8010277261254f7", total: 0, reason: null }.Design choices, on the four axes
head.repo === nullreads as a fork (fail closed). 实际业务需求: the API returnshead.repo: nullexactly when a contributor deleted the fork after opening the PR — a PR nobody can rebuild the head of is the least reviewable shape a fork PR takes, and the population it comes from is the fork population. 项目长远合理性: the predicate is closed (head ≠ base, with an unreadable head on the far side of ≠), so no third state grows beside it. 防 AI 犯错: the alternative — treating an unreadable head as "not a fork" — is a??fallback in the consumer that turns a missing fact into a clearance; the fail-closed reading is the loud one, and its words name the deleted repo. 创业阶段不扩散: zero extra code — oneheadRepo === null ||in the predicate, one(deleted fork repo)in the words, one self-test case. The one case deliberately left NOT MEASURED rather than forked is a PR object with nobase.repoat all (a fixture shape, never seen from the API): there is nothing to compare against, and NOT MEASURED is never a clearance either.Where the SKILL.md line sits — the four axes are given under §3 above; the 分诊 / 定级 region belongs to #19494 and the feature-axis lines to PR #19488, so the landing section was the only region on the claim.
PM mechanism assumptions — verified
check-governed-merges.mjshad 0 hits forhead.repo/author_association/FIRST_TIME; the PR object is fetched infetchPullFiles(fetchJsonOveronGET /repos/{slug}/pulls/{n}, the read that giveschanged_filesand the size pair), and the proxy returnshead.repo(jinyitao123/objectstackon feat(auth): allow explicit intranet HTTP OAuth #19342,base.repoobjectstack-ai/objectstack). The fork limb reads that same object; the check-run count is a second GET on the same channel.⚖️ landing tier: H(人合) — the maintainer's hand, or an authorized APPROVED review (GOVERNED_APPROVERS) and then the owning seat lands it; the SIZE limb's terminal readsThe same terminal as a Tier H governed diff: no seat flips it ready, enqueues it, or arms auto-merge. The fork sentence keeps the three verbs, addsapproves it, and adds the adoption step instead of the seat-lands-it clause (a fork PR is never landed by the seat).pnpm check:pm-skill-ratchet :: exit 0andpnpm check:pm-governed-prose :: exit 0; on this head both exit 0 again (quoted below).node scripts/pm/check-governed-merges.mjs --pron this PR's own number is run after the PR exists; the reading (expected: exit 3, GOVERNED, Tier H —AGENTS.mdandskills-free.claude/**on the register, one Tier H path making the whole PR Tier H) goes into theos-dev-reportcomment on the card. This PR stays draft for the maintainer's hand.Verification (this head 0a50588)
node scripts/pm/check-governed-merges.mjs --self-test :: exit 0— 441 assertions, the new battery registered at its floor; the two--pr 19342readings above;--test README.md :: exit 0(NOT governed,head repo: NOT MEASUREDon stdout),--test AGENTS.md :: exit 3;npx eslint scripts/pm/check-governed-merges.mjs :: exit 0.pnpm check:pm-skill-ratchet :: exit 0(AGENTS.md 1109 / 1109 headroom 0; SKILL.md 816 / 819; core-rules.md 151 / 151),pnpm check:pm-skill-id-lint :: exit 0,pnpm check:pm-governed-prose :: exit 0(2 surfaces name all 6 registered surfaces and claim no others),pnpm check:nul-bytes :: exit 0; control-character scan of the five files: 0 hits.node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackwith no paths on 0a50588: 44 commands. Every command run in the foreground with its exit captured before any pipe (cmd > log 2>&1; EXIT=$?), then reconciled:node scripts/pm/dispatch-gates.mjs --ran ran-union.txt --repo objectstack-ai/objectstack :: exit 0— 44 derived, 44 run, 0 NOT-MEASURED, 0 UNRUN, every exit 0. One family needed its inputs built first:pnpm --filter @objectstack/lint run check:doc-formula-expressionsansweredPREREQUISITE NOT MET(exit 3,@objectstack/formulathen@objectstack/lintnot built in a fresh worktree) untilpnpm --filter '@objectstack/lint...' buildran under the shared verification lock (os-verify-lock.sh, VERDICT command-exit 0, held 176 s); re-run on the built tree it exits 0 (22 record-scoped examples judged clean). Heavy families in the union (check-governed-queue-guard.mjs --self-test,check:pm-dispatch-gates,check-skills-token-ratchet.mjs) all green.pnpm test/typecheckis owed — the diff touches nopackages/**source;pnpm lint(the repo-level eslint) is CI's run, the edited script was linted directly.维护者速读(草稿)
改了什么 — 三处一致的规则:fork 上来的 PR(head 仓 ≠ base 仓,含 fork 已删的情况)是提案不是交付。①
check-governed-merges.mjs在「路径受管」「改动超 5000 行」之外加了第三个判据「head 是 fork」,命中即走既有的「人合」出口,并打印自己的理由句;--pr同时读 head 上的 check run 数,0 条时打印「NOT MEASURED,不是绿」。②AGENTS.md第 14 条基本原则加一句(行数不变,用 Skills 节里一句重复表述抵付)。③ PM 技能的落地条款加一行,核心条款一行原地改写承接,并新增一页external-contributions.md写清四步:先立卡、人裁问题、席位采纳 diff 内部落地、fork 的 CI 席位不代批。供应链条款按裁决延后。为什么改 — 您 2026-09-21 裁了「fork PR 的处理同意你的意见」。之前合并前防线只看改了哪些文件、只约束我们自己的席位,#19342 那样的外部 PR(OAuth 放宽到明文 HTTP,0 条 CI)按机器判定可走普通队列;现在它被机器点名为提案,任何席位都不能放行。
风险与代价(含回滚) — 代价:每张外部 PR 都要您(或授权批准账户)拍板,今天 100 张里 1 张;
--pr多一次 API 读。行为变化:--test/--branch的输出多一行「head 仓未测」提示,不改退出码。回滚:revert 本 PR 一个 commit 即可,无数据、无迁移。席位意见 — (留空,席位定稿时填)
你要做的 — 点合并(Tier H:本 PR 改了
AGENTS.md,只由您的手落地)。#19342 本身按同一批裁决由总监席 / 分诊席处置,不在本 PR 内。Acceptance notes
references/external-contributions.mdcarries no CEILINGS row incheck-skill-line-ratchet.mjs(the ratchet does not require one and the script is outside this card's surface), so the new file is invisible to the line ratchet until pinned; a first pin at the landed count (16) is not a raise. carrier: thedomain:skillsseat.--pr Nnow spends one extra GET per run (/commits/{sha}/check-runs?per_page=1) on the chosen channel; a failed read is carried asNOT READ (reason)and never a zero.--test/--branchstdout gained thehead repo: NOT MEASUREDline under every verdict — deliberate, mirrors the size limb; a grep reader keyed onNOT MEASUREDalone now matches on every--testrun without size flags too (it already did for the size line).pull_request_targetin four workflow files, the MCP merge route against a fork head, fork PR history beyond the newest 100); none is on this card's surface and none was measured here. carrier: thedomain:skillsseat.Generated by Claude Code