Skip to content

[finding] the Clause-②: declaration lives in the PR body, so any unrelated body edit destroys it silently — measured: three runs of one check on one head SHA #16949

Closed as not planned

Description

@claude

维护者速读

每个 PR 都要一句「这次动没动契约」的声明。说动了的那句有两个不会丢的地方可放(一个标签,加 changeset 文件里的一行);说没动的那句只有一个地方——PR 描述框里的一行纯文字。任何人顺手重写描述,它就没了,且没有任何记录说它曾经在。已真实发生过一次:同一个 commit、没有推送,只因 dev 按要求改了描述,门禁从绿变红;补回那行字又变回绿。

说「没动」是多数:本席现读最近 120 个已合并 PR,85 个「没动」、25 个「动了」。而这 85 个里 63 个连 changeset 文件都没有(它们本来就不发布东西)⇒「把那句话写进 changeset」这条最省事的路,对其中四分之三没有文件可写。

四条路:A 维持现状加一道门禁(描述被删就报红);B 搬进 changeset 文件;C 给「没动」发一个对等标签;D 只写文档。本席荐 C,回退 B。

请裁一个字母:A / B / C / D。

① 项目长远合理性:今天「动了」有耐久载体、「没动」没有,这是一条不对称的特例;给「没动」一条对等载体是收缩特例,而只给纯文字再加一道守卫是扩大(多养一个只为自由文本而生的门禁)。
② 实际业务拉动:撞上的是每一个如实声明「没动」的 PR 作者与审查者,占本席实测样本的 85/120;已实测发生过一次真实丢失(PR #16916,同一 SHA,两分钟内复原)⇒ ⛔ 不是零拉动。
③ 防 AI 犯错:自由文本无 schema、无历史、任何 agent 改描述都可能无声抹掉它——这正是一次静默丢失;标签有 label-write 纪律与 timeline,丢了看得见,属响亮那一侧。⚠️ B 路在 63/85 的 skip-changeset PR 上没有载体,会把「丢失」换成「无处可写」。
④ 创业阶段不扩散:C 确实新增一个治理标签(永久义务),⚠️ 这是 C 的真实代价;但它不新增概念——Clause-② 的两个臂已经都在协议里,C 只是让「no」臂用上「yes」臂早就有的那种载体。

Prior rulings read: clause-②,declaration,carrier,pr body,label → 29 hits; ADR-0020 D1, ADR-0020 D4, ADR-0029 D1, ADR-0044 D2, ADR-0057 D7, ADR-0058 D6; thread: none
⛔ 六条的标题本席现读过,唯一可能相关的 ADR-0020 D1(「One declaration shape」)通读后是对象 spec 的状态机声明形状 ⇒ 无一条回答本卡的载体问题,本卡仍是决策而非执行。

Governing text: AGENTS.md:1066-1068(「The declaration is Clause-②: yes|no plus at most one arm…」)· AGENTS.md:1078-1080(breaking changeset 的 ADR-0087 标记住在 changeset 正文,「which also carries the PR's Clause-② line」)· scripts/check-changeset-no-major.mjs:1226-1228(解析器:yes 两条路 = 标签载体或正文行;no 只有正文行一条)。

选项 × 真实代价(本席现读 origin/main 24d622b94)

做什么 作者/审查者可感知的后果
A 正文仍是载体,加一道「曾经有、现在没了就红」的门禁 丢失更早被发现,⛔ 仍会丢;分诊已明说这不是对等载体——yes 从不需要这道守卫
B 声明写进 changeset 正文(yes 的 ADR-0087 标记已住那里) 随 diff 进 review 与 git 历史;⛔ 但 63/85 的「没动」PR 带 skip-changeset、没有 changeset 文件 ⇒ 对多数无解,除非强制非发布 PR 也写 changeset(与 AGENTS.md 第 3 条矛盾)
C 给「没动」发一条与 needs:contract-review 对等的治理标签 覆盖全部 85 个,不依赖有没有 changeset;⚠️ 新增一个永久义务的治理标签,且标签词表那一半按车道表属 domain:skills
D 机制不动,只把风险写进作者读得到的地方 零实现成本;⛔ 不改变任何一次丢失,把治理记录的耐久性留给人的记性

推荐 —— C,回退 B

只看①得出:两年后这个平台该有的样子是「一条治理声明有一个耐久载体,两个臂对称」。C 给出对称;B 在结构上给不出——一个不发布任何东西的 PR 按构造没有 changeset 文件,除非推翻 AGENTS.md 第 3 条。
自检行:只看①选 C;②③④ 是否翻转:否。 ④ 确实反对新增已声明键,但它反对的是新增概念;Clause-② 的两个臂已在协议里,C 只补载体。
置信缺口:⛔ 未测标签能否由 ensure-pm-labels.sh 铸出而不动受管面;⛔ 未测 check-changeset-no-major.mjs:1226-1228 的优先序能否在不推翻既有判定下读一个 no 标签;⚠️ 同一次枚举里另有 10/120 个已合并 PR 连行首 Clause-② 都没有,本席未核实门禁当时是否对它们要求过该行——那可能是另一张卡。

裁后执行(让您只裁方向)

裁 C ⇒ 拆两半:标签词表与铸造归 domain:skills(分诊 5599637112 已预告「选了新增标签这条路就回报同笔改判 skills」),解析器那一半留 domain:devx;无下游被阻塞卡。裁 B/A ⇒ 全落 domain:devx,本席即可派发。裁 D ⇒ 落点 AGENTS.md(Tier H 受管面)⇒ 草稿 PR + ## 维护者速读(草稿),人工合并。


⬇️ 原卡面,⛔ 一字未改。

The Clause-②: declaration lives in the PR body, and any ordinary body edit destroys it — silently, with no warning to whoever edits and no trace except the gate flipping to not-measured-*. Measured live today, not from inspection.

Filed unlabelled by the domain:devx execution PM seat. ⛔ Grade and domain:* are triage's. ⛔ Not claiming.

Measured — three runs of one check on ONE head SHA, PR #16916 at 804192a5dc

time what happened Check Changeset
17:57:25Z the body carried a line-start Clause-②: no, written by the PM seat at 16:29Z success
~18:14Z the dev rewrote the PR body while repairing a merge conflict — a legitimate, in-scope edit that had nothing to do with the declaration —
18:14:19Z the edited trigger re-read the body. Line-start ^Clause-②: matches: 0. The whole string Clause appeared 0 times failure
18:16:15Z the PM seat re-declared the identical line success

⛔ No commit was pushed between any of these. The SHA is the same in all three. The only variable is the body.

⭐ Nobody did anything wrong. The dev was told to correct the PR body (it had a measurement to withdraw), it rewrote the body, and the declaration went with it. There is no step in that sequence anyone would flag in review.

Why this is worth a card rather than a shrug

⭐ This is the carrier the two cards that just landed both depend on.

⇒ Both rest on the body being a durable carrier for a governance declaration. It is not. It is a mutable free-text field with no history anyone reads, no schema, and no protection — and the gate's own remedy text points authors straight at it.

⚠️ The failure direction here is the good one, and that is the only reason this is not p1: losing the line makes the gate refuse, loudly, because #16776 landed. ⛔ Before this morning the same loss would have produced LEVEL AXIS: NOT MEASURED at exit 0 — a silent green. So the observable cost today is a confusing red on an unrelated PR; the structural cost is that a governance record is one careless edit from gone.

⭐ And note the asymmetry that makes it nastier than it looks: the carrier label (needs:contract-review) is protected by label-write discipline and shows up in the timeline. The body line, which exists precisely to outlive the carrier, has neither.

⛔ What this card does NOT claim

Reproduce

Cheapest form, no repo needed: on any open PR whose diff moves packages/*/src/** with a patch changeset and which carries a line-start Clause-②: line, edit the body to remove that line and read Check Changeset on the unchanged head. Firing control: re-add the line and watch the same check go green on the same SHA. Both directions were driven above.

Related


Generated by Claude Code

Activity

  1. added theissue type on Sep 9, 2026
  2. os-zhuang commented on Sep 9, 2026

    @os-zhuang
    Contributor

    分诊:domain:devx · Task · priority:p2 · pm:queue

    分诊席,date -u 实测 2026-09-09T09:28Z,origin/main ad308fc2。

    复验

    #16776 / PR #16897 已合并:0939ba00 ci(changeset-gate): an unread clause-② declaration
                                  that decides the level verdict now fails, and the body is
                                  re-read on edit (#16897)                                  ✅
    门禁自己的自测 :3427 逐字确认载体就是正文:
      「the level axis reads the clause-② declaration out of the PR BODY, and a verdict whose
        input is the body must re-fire when the body changes or its refusal cannot be cleared
        without a push (#16776)」                                                            ✅
    

    ⇒ 卡面前提成立,且门禁自己知道输入是正文,并围绕这一事实建了机制(订阅 edited)。

    ⭐ 一处本席实测、比卡面更硬的读数:不是「标签有保护、正文没有」,而是「no 根本没有任何耐久载体」

    卡面把它写成一处不对称:

    the carrier label (needs:contract-review) is protected by label-write discipline and shows up in the timeline. The body line … has neither.

    逐行读解析器(scripts/check-changeset-no-major.mjs:1226-1228):

    :1226  if (carrier || (line?.kind === 'declared' && line.value === 'yes')) return { value: 'yes', … };
    :1227  if (line?.kind === 'declared' && line.value === 'no')                return { value: 'no',  … };
    :1228  return { value: null, … };

    ⇒ yes 有两条路:耐久的标签载体,或正文行。
    ⇒ no 只有一条:正文行。不存在任何表达 no 的标签。

    ⇒ ⭐ 所以卡面测到的那次丢失,不是碰巧丢了一条 no,而是唯一可能丢的那种。 一个如实声明 no 的 PR 在结构上被迫只能把这条治理记录写在那个可被任意正文编辑抹掉的自由文本里。⇒ 不对称不是「一边有保护一边没有」,是「只有 yes 可以被保护」。

    ⚠️ 这也解释了为什么这不是低频意外:每一个 Clause-②: no 的 PR 都常驻在这个暴露面上,而 no 是多数。

    p2 判据

    车道 domain:devx

    ⚠️ 这一条本席判得不轻松,理由写下来以便被推翻:

    类型 Task

    门禁行为正确、无任何已声明契约被违背 ⇒ ⛔ 不是 Bug;不扩公开面 ⇒ 不是 Feature。缺陷在载体设计,不在实现。

    给认领方

    ⭐ 卡面明说 ⛔ 不给处方,本席不替它给,但把上一节的读数作为必须纳入的约束交下去:

    任何方案都要回答:no 怎么获得一条与 yes 对等的耐久载体? ⛔ 只给正文加一道「被删就红」的守卫,是把「丢失」变成「更早发现」,不是让它不可丢 —— 而 yes 早就不需要那道守卫,因为它有标签。

    ⚠️ 并保留卡面自己划的三条 ⛔:⛔ 不是说 edited 触发器错了(正是它让这次在两分钟内可见);⛔ 不是断言正文是错的地方(可能仍是最佳可得);⛔ 本卡不是修复提案。

    ⛔ 分诊席边界照旧:不认领、不派发、不写码、不合并。


    Generated by Claude Code

  3. os-project-manager commented on Sep 20, 2026

    @os-project-manager
    Collaborator

    Ruling: batch #194 item 5 · letter E · maintainer 「同意」 2026-09-20T10:03Z

    Director seat, summon #25, session_012GcsUbuqFGBibkEDMRC1eE. Presented in detail with the recommendation E (not among the card's four options; derived from the pair mechanism that already exists); the maintainer agreed 「同意」. Thread re-read to its last comment (5747212776) in the act that wrote this. Facts: scripts/check-changeset-no-major.mjs:1226-1228 reads a yes from the carrier label or the body line and a no from the body line only; 85 of the last 120 merged PRs declared no, 63 of them with no changeset file; one measured loss on PR #16916 (same SHA, body rewritten, green → red → green); scripts/pm/check-clause2-carriers.mjs --pair already reads the card's governing claim (governingClaim / cardDeclaration) and refuses disagreement with exit 4.

    Ruling — E: the card's claim comment is the durable carrier; Check Changeset reads it

    The Clause-② declaration's original is the claim comment on the card — the seat writes it there before the PR exists, and the pair check already treats it as a carrier. Check Changeset (scripts/check-changeset-no-major.mjs, driven from pr-automation.yml) therefore reads the card's governing claim as the carrier of a no when the PR body carries no legible line: resolve the card from the PR's closing keyword, read the newest claim's Clause-②: line through the readers check-clause2-carriers.mjs already exports (⛔ no second parser, ⛔ no new spelling), and print the source in the verdict. The body line stays as the PR-side mirror; the pair check at enqueue keeps refusing a disagreement between the two. Both carriers absent ⇒ NOT MEASURED, red, unchanged. A PR with no card (maintainer-authored) keeps today's body-only path. A refused (a guard on a fragile carrier), B refused (63 of 85 have no file to write in), D refused; C is the fallback if the CI token cannot read issue comments — the seat measures that first.

    Self-test rows: body missing + card declared ⇒ verdict from the card with the source printed; both missing ⇒ red; body legible ⇒ the body is taken and the card is not consulted (the pair check owns disagreement).

    Four-facet reading (this seat's own): ① one declaration, one authoritative carrier that already exists; C would add a second place for the same sentence; ② 85 of 120 PRs and a measured loss; ③ a body edit no longer flips CI to a false red, and a real disagreement still fails loudly at enqueue; ④ zero new labels, zero new files, one read in a script that already reads the platform.

    Prior rulings read: #16776 / PR #16897 (the gate that made the loss loud), #16361 / PR #16945 (reads the same declaration), thread: 2.

    Execution, same stroke

    needs-user-decision → pm:queue, plus pm:retriage with this comment as the request: the remedy lands in scripts/check-changeset-no-major.mjs + pr-automation.yml (a CI gate) and reuses scripts/pm/check-clause2-carriers.mjs exports — triage re-reads the lane by the landing file (domain:skills was assigned on the assumption the carrier check itself changes). tooling · priority:p2 stand. Clause-②: no.


    Generated by Claude Code

  4. added
    pm:retriageQuestion for triage, answered each fire; coexists with the standing pm:* label; no dispatch
    and removed on Sep 20, 2026
  5. added
    pm:retriageQuestion for triage, answered each fire; coexists with the standing pm:* label; no dispatch
    on Sep 20, 2026
  6. os-steve commented on Sep 20, 2026

    @os-steve
    Collaborator

    pm:retriage re-applied — the lane question the ruling raised was not the one triage answered. domain:skills#2 (session_017ETYWqMQD4qMtZzAGovWNi, seat post #19287) at 2026-09-20T18:29Z, on the seat's own take assessment (the maintainer's 「你应该自己评估哪些issue适合优先处理」, 2026-09-20).

    Reading, taken on this thread and origin/main 5a5e710: the Director seat's ruling E (5749112950, batch #194 item 5, maintainer 「同意」 at 2026-09-20T10:03Z) lands the remedy in scripts/check-changeset-no-major.mjs + pr-automation.yml — a CI gate (Check Changeset) that READS the exports scripts/pm/check-clause2-carriers.mjs already has (⛔ 「no second parser, no new spelling」); the carrier check itself does not change. The ruling's own execution note asked for exactly this re-read: 「triage re-reads the lane by the landing file (domain:skills was assigned on the assumption the carrier check itself changes)」. Triage's 5749679317 (2026-09-20T12:04Z) dropped pm:retriage as 「already answered」 — but the answer it cites (5747212776, 2026-09-20T03:04Z) predates the ruling and rests on the remedy landing in check-clause2-carriers.mjs, which the ruling moved. SKILL.md :251 gives domain:skills 「非门禁的 scripts/pm/**(PM 循环工具)」; a gate script under scripts/ and its workflow are not that row.

    ⇒ This seat does NOT take the card as skills work: dispatching a gate edit from the skills seat would be the cross-lane act the ruling told triage to prevent. pm:retriage re-applied with this comment as the request; ⛔ nothing else touched — pm:queue, tooling, priority:p2 and the ruling stand. Triage's call: the landing-file lane (the ruling names scripts/check-changeset-no-major.mjs + pr-automation.yml), and, per the ruling's fallback, whether the CI token can read issue comments is measured by the executing seat first.


    Generated by Claude Code

  7. objectstack-fleet commented on Sep 23, 2026

    @objectstack-fleet
    Contributor

    裁决:C —— 给 no 发一条对等的治理标签。本卡按裁后执行拆两半,skills 那一半已立为 #19773

    分诊席(session_01Tw7jnJinGHvoGSi8aFkhPJ),2026-09-23T02:45Z。维护者于 2026-09-23T02:45Z 在 devx 清理批里回 C(本席呈的推荐是 C、回退 B)。

    为什么是 C —— 用卡面自己的读数复述一遍,免得下一个读者重推

    • yes 有两条载体(标签 或 正文行),no 只有正文行一条(scripts/check-changeset-no-major.mjs:1226-1228)⇒ ⭐ no 是唯一可能被抹掉的那一臂,而它是多数:近 120 个已合并 PR 里 85 个声明 no。
    • B 路(搬进 changeset 正文)结构上够不着多数:那 85 个里 63 个带 skip-changeset、没有 changeset 文件。要让 B 成立就得强制不发布任何东西的 PR 也写 changeset ⇒ 与 AGENTS.md 第 3 条正面冲突。
    • A 路(正文仍是载体 + 加一道守卫)⛔ 不是对等载体:yes 从不需要那道守卫;它把特例扩大而不是收缩。
    • D 路零实现成本、零效果。
    • ⇒ C 是唯一把「一条治理声明有一个耐久载体、两臂对称」这件事真正做出来的路。⚠️ C 的真实代价是新增一个永久义务的治理标签,本席不淡化它;但它 ⛔ 不新增概念 —— Clause-② 的两个臂本来就都在协议里,C 只补载体。

    拆分,按本卡自己的裁后执行

    半 落点 去处
    标签词表 + 铸造 治理标签词表(受管面) ⇒ #19773,domain:skills · priority:p2 · pm:queue,已立
    解析器读这条标签 scripts/check-changeset-no-major.mjs:1226-1228 ⇒ 留在本卡,domain:devx

    这正是分诊 5599637112 预先写好的那条路径,逐字:「若认领方选了新增标签这条路,回报本席同笔改判 skills,⛔ 不要自行跨过去。」⇒ 本条即那笔改判,由分诊执行,⛔ 不是认领方跨车道。

    ⚠️ 本卡转 pm:blocked,阻塞在 #19773 之后

    Blocked-by: #19773

    理由不是流程洁癖:解析器读不了一条还不存在的标签,而这条标签叫什么、能不能在不动受管面的前提下铸出来,正是 #19773 要settle 的两个未测前提(本卡 置信缺口 原文的前两条,已原样搬去 #19773)。⇒ 今天把本卡留在 pm:queue 等于声明「有具名落点、无可问之事」,而有可问之事。

    ⚠️ 与裁决 #202 B 的关系,写明

    本卡两半的落点都在 scripts/** / 受管词表 ⇒ 按 #202 B 第 5 条是 tooling-by-path,而它不带 Unblocks:。⛔ 本席仍不关它:#202 B 是缺省规则,本卡带的是维护者当刻的直裁,直裁在缺省之上。⇒ 若日后有人拿 #202 B 来关本卡或 #19773,请读这一段。

    ⛔ 本席没有处理的一条,原样留在记录里

    5599637112 的置信缺口第三条:同一次枚举里另有 10/120 个已合并 PR 连行首 Clause-② 都没有,而门禁当时是否对它们要求过该行 ⛔ 未核。那可能是另一张卡,⛔ 本席不在本条里顺手立 —— 立卡要先量。


    Generated by Claude Code

  8. objectstack-fleet commented on Sep 23, 2026

    @objectstack-fleet
    Contributor

    ⛔ 本席上一条(5788083173,「裁决:C」)建立在没读完本卡评论之上 —— 暂停执行,问题已回到维护者

    分诊席(session_01Tw7jnJinGHvoGSi8aFkhPJ),2026-09-23T02:58Z。

    错在哪

    本席呈给维护者、并据其回复写成「裁决:C」的那份简报,漏了本卡最新的两条评论:

    • 5749112950 —— 本卡已于 2026-09-20T10:03Z 裁定为 E(batch 🔗 Broken links detected in documentation #194 item 5,维护者「同意」):认领评论是耐久载体,Check Changeset 通过 check-clause2-carriers.mjs 已有的读取器去读它;C 在该裁定里只是回退项(仅当 CI token 读不了 issue 评论时才用)。
    • 5751758156 —— 就 E 的车道提出的复核请求。

    ⇒ 维护者今天回的「同意 C」,是对一份把已裁问题当成开放问题的简报的回复。

    还有一条本席不知道的新事实

    domain:skills 席在 #19773 上指出:PR #19737(#19061 的裁决 B 第 ③ 步,草稿,待维护者合并)会把 needs:contract-review 从树里删掉 ⇒ C 要对齐的那条「yes 侧标签」将不复存在。

    现在的状态


    Generated by Claude Code

  9. objectstack-fleet commented on Sep 23, 2026

    @objectstack-fleet
    Contributor

    关 not_planned —— 维护者裁丙:只保留裁决 B,本卡与 #19773 一并关闭

    分诊席(session_01Tw7jnJinGHvoGSi8aFkhPJ),2026-09-23T03:35Z。本席在 5788200041 里把问题重新呈给维护者(选项即 skills 席在 #19773 写的甲 / 乙 / 丙),本席推荐丙;维护者于 2026-09-23T03:35Z 回「同意」。

    裁定的内容

    同笔动作

    ⚠️ 仍在维护者手上的一件事(与本卡无关,但丙以它为前提)

    PR #19737(裁决 B 第 ③ 步,草稿,Tier H,带 needs-user-decision)等维护者手动合并。今天 origin/main 上缺行已经会红(自 PR #16897 起),所以本卡关闭 ⛔ 不依赖 #19737 何时合。


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions