Skip to content

patrol: H16 — open non-draft PR stuck in merge-conflict (mergeable_state dirty) beyond threshold #9980

Closed

Description

@os-warren

Maintainer ruling (2026-08-19, PM chat): 「同意你的建议」—— 批准 devx 事故复盘的两条提议之一(另一条是 lanes 读锚判据,独立卡)。事故出处:PR #9826 合并冲突挂 4 小时无人发现 —— 冲突不红、不响、不产生事件,且 auto-merge 已挂时所有代理信号都读作健康;os-steve 的私人检查器已修,但板面级仪器(H1–H15)无一条看 mergeable_state。教训原话:「一个无法表达某状态的仪器,会把它报成它能表达的最近状态。」

Scope — scripts/pm/check-half-states.mjs,一条新 report-only 谓词(经现有巡逻上锚)

H16:开放、非 draft 的 PR 处于合并冲突(mergeable_state = dirty)超过阈值 → 锚上一行,点名 PR 与其 Fixes 卡。要点:

  • 阈值为命名常量 + rationale 注释(参照 DOMAIN_HALF_STATE_STALE_HOURS 先例;建议 ~2h —— 冲突多由 base 前进造成,给正常解决留窗口)。
  • 平台事实必须处理:mergeable_state 不在列表 API 里,要逐 PR GET;且 GitHub 异步计算 mergeability,读到 unknown/null 时不判(照本文件「不能命名就不 vouch」的窄性纪律,跳过而非报告)。逐 PR GET 只对 sweep 已列出的开放 PR 发起,量小;draft 跳过。
  • Report-only:修复动作(合 base 解冲突)永远是车道 PM 的,谓词只报告。双渲染器普通行,不 loud。
  • 自测:dirty 超阈/未超阈、unknown 跳过、draft 跳过、clean 干净——每形状正负钉住;编号顺延 H16,头部枚举与 H1–Hn 范围文本同步。

Serial

热文件 check-half-states.mjs 空闲(PR #9963 已落地;#9966 属 devx 道且目标是 ci-failure.mjs)。非治理面。Graded pm:queue, S/M(skills 座,own-lane,session ae0d8bd1-5667-5f58-8ff8-fa22ec154325)。

Activity

  1. self-assigned this
    on Aug 19, 2026
  2. os-warren commented on Aug 19, 2026

    @os-warren
    CollaboratorAuthor

    Claim: session ae0d8bd1-5667-5f58-8ff8-fa22ec154325 · branch claude/issue-9980-h16-dirty-pr

    折叠检查:check-half-states.mjs 空闲(PR #9963 已落地),队列无同文件第二卡 → 单卡。模型档:路径无强制(--tier 实跑),report-only 谓词不改合同 → opus。


    Generated by Claude Code

  3. os-warren commented on Aug 19, 2026

    @os-warren
    CollaboratorAuthor

    os-dev-report

    {
      "issue": 9980,
      "status": "done",
      "branch": "claude/issue-9980-h16-dirty-pr",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/9985",
      "premise_still_valid": true,
      "summary": "H16 lands in scripts/pm/check-half-states.mjs as a report-only predicate: an open, non-draft PR whose mergeable_state is 'dirty' past MERGE_CONFLICT_STALE_HOURS (2h, named constant with the rationale the card asked for, following the DOMAIN_HALF_STATE_STALE_HOURS precedent) anchors one ordinary row in both renderers, naming the PR and the card(s) its body declares (Fixes or Part of, via H7's code-stripped extractors). All four platform facts are handled: mergeable_state is absent from the /pulls list payload so a per-PR GET is taken, bounded to candidates by an exported+pinned gathering policy (h16NeedsDetail) whose never-narrower-than-the-predicate invariant is asserted as an implication over the whole fixture table; unknown/null readings are skipped in silence per the file's narrowness discipline (the docblock argues why this asymmetry with the #4690 unreadable-timestamp direction is deliberate rather than inconsistent); a failed detail GET never fails the sweep, while NO candidate readable at all rethrows as a transport classification per the #4690 posture. auto_merge is deliberately NOT read in H12's finding-reducing direction — that is the incident itself, and it is pinned in both directions including the one-row/two-predicates contrast. Two small extensions beyond the literal card, both declared: h16DetailPassUnreadable was extracted as a pure function so the 'total failure is transport' judgement could be pinned instead of sitting untested in a non-exported loop, and summaryLine now reports 'merge state read on X of Y H16 candidate(s)' because without it a detail pass that read nothing would be indistinguishable from a board with no conflicts. Header docblock enumeration extended and the H1-H15 range string updated to H1-H16; the '(H13-H15)' string was deliberately left alone — it names the unscoped-issue pass, which H16 (PR-side) is not part of. No governed-surface edit was owed: SKILL.md's only reference is a one-line table row carrying no H-range.",
      "tests": "All at final commit d6669295c (git rev-parse --short HEAD), gate union derived by `node scripts/pm/dispatch-gates.mjs` with NO path arguments (script computes its own change set from the merge base: '1 path(s) vs merge base 8fa4e2a76'). Exit codes captured before any pipe via redirect-then-read. Verdict lines as each gate printed them: `pnpm check:pm-half-states` EXIT=0 '✓ check-half-states self-test: 352 cases pass.' · `pnpm check:partof-closing-keyword` EXIT=0 '✓ check-partof-closing-keyword self-test: 28 cases pass.' · `pnpm check:cross-package-test-inputs` EXIT=0 'OK: 12 package(s) read outside themselves, all declared, and turbo.json hashes every declared glob.' · `node scripts/check-cross-package-test-inputs.mjs` (ci.yml form) EXIT=0 same line · `pnpm check:nul-bytes` EXIT=0 'check-nul-bytes: OK (scanned 6322 text file(s) -- 6322 tracked, 0 untracked-not-ignored; skipped 5 binary; no raw ASCII control bytes).' SELF-TEST COUNTS: baseline 292 (measured on fresh origin/main 8fa4e2a76, matching the dispatch) -> 352. NARROWING, declared: the sixth derived entry is the LIVE sweep (half-state-patrol.yml), measured-unavailable here rather than assumed — `--probe` exits 3, 'PREREQUISITE NOT MET — the token in the environment is not a valid GitHub credential', anonymous path also unusable (the third container class this file's own header documents); CI runs it on a runner. `check-partof-closing-keyword.mjs` run bare exits 2 by design (needs PR_BODY from its workflow); the packaged script form is --self-test, which is what is quoted. I additionally ran that guard against this PR's exact body: EXIT=0, 'this PR carries no Part-of/closing-keyword contradiction'. REVERSE VERIFICATION, directions predicted in writing before running (predictions file written first), all three exactly as predicted: (1) delete the dirty gate -> predicted RED at exactly 6 cases with the never-narrower table case staying green; observed '6 of 352 case(s) failed', the 6 named, table case green — it stays green because rows that newly flag are also rows the gate fetches, which is the point of asserting an implication rather than a count. (2) add H12's auto_merge suppression -> predicted a TypeError crash rather than a tally (a suppressed row returns null and the runner calls .includes on it); observed a crash at the exact predicted case, check-half-states.mjs:2921. (3) threshold 2->200 -> predicted the same crash mechanism; observed at check-half-states.mjs:2886. NO REBUILD LEG IS OWED and none was skipped: this is a plain .mjs run from source by node, the self-test resolves nothing through a package exports field or a dist/, and no vitest alias is involved — there is no artifact between the edit and the measurement. Restored after every leg from the commit (git checkout d6669295c -- path), proven byte-identical by git hash-object = bd7775bace54a32d9a6a7f8c6a9a0d06aeba974a equal to the committed blob (not by a matching diffstat), and re-run green (352) each time.",
      "labels_read_back": "[\"skip-changeset\"] — confirmed by a read-back (issue_read get on the PR number, since issue_read get_labels errors on PRs), not by the write. Label set was empty at read time, so the union was that one entry; no bot label was clobbered.",
      "files_changed": ["scripts/pm/check-half-states.mjs (+425/-5, 1 file)"],
      "open_questions": [],
      "out_of_scope_findings": []
    }

    Generated by Claude Code


    Generated by Claude Code

  4. os-warren commented on Aug 19, 2026

    @os-warren
    CollaboratorAuthor

    ACCEPT — PR #9985 复核通过,已 flip ready + auto-merge(显式 SQUASH)。

    复核记录(session ae0d8bd1-5667-5f58-8ff8-fa22ec154325):

    • 亲取 diff:H16 report-only、零标签写入、双渲染器普通行、不 loud ✓。四个平台事实全部处理:逐 PR GET 由导出的采集策略 h16NeedsDetail 限界(「永不窄于谓词」以整张 fixture 表的蕴含式断言,比逐例更强);unknown 静默跳过且 docblock 论证了与 check:react-declaration-parity 是唯一没接进任何 workflow 的源码审计门禁,且无 MANIFEST 时静默 skip 退出 0 —— 它现在永远不可能红 #4690 方向的刻意不对称(平台说「稍后再问」≠ 本该可读而不可读);单次 GET 失败不毁 sweep、零可读上抛为 transport 分类(h16DetailPassUnreadable 纯函数化 + 摘要行 read X of Y —— 两处声明的超卡扩展都接受:没有它们,「一个都没读到」与「无冲突」在报告上不可分)✓。
    • 时钟代理如实:updated_at 为代理、误差方向(只多报不漏报)写在常量 rationale、⛔ 明禁用 timeline fetch 去「修」它、finding 文本向读者点名所示量 —— 正是派发词要求的诚实声明 ✓。
    • 事故性质钉住:armed auto-merge 不降噪(H12 反向,同一行两谓词对照钉);changeset-release 不豁免(论证:它逐推重建,脏了就是真发现);fix(ci): reconstruct the paths cross-package tests really read, instead of trusting quoted prose #9826 标本以历史形状入册并注明当日实测已变 blocked,防后人拿活 PR「验证」fixture ✓。
    • 反向验证三腿全中(含两腿精确预测崩溃机制与行号),恢复以 blob hash 证逐字节一致 ✓。自测 292→352,门禁判定行逐字 ✓。

    落地后:巡逻下一班起,任何仓的冲突 PR 最多 6 小时上锚点名 —— devx 事故的仪器缺口正式闭合。


    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

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions