Skip to content

contract-review chain: a cleared gate label was re-hung as "dropped" because the checker read only labels + review objects, not the verdict comment — add the one-line re-hang guard to references #11399

Description

@claude

Measured 2026-08-23 (#11060 / PR #11347): the fable seat PASSed and cleared the gate at 13:46Z (verdict comment + deliberate targeted unlabel, both on record); at 15:10Z the dispatching seat, checking only label absence and get_reviews (empty — the protocol carrier is a card comment, not a review object), concluded the gate was never exercised and re-hung both carriers. Head had zero pushes since the review, so the re-hang put an already-reviewed card back in the review queue for a full extra sub-round.

Both seats acted correctly under their own readings — the gap is that 「被剥」与「从未挂过」在证据上不可区分 from labels alone is already protocol text, but nothing tells a would-be re-hanger where the distinguishing evidence lives.

Fix (one line, references/contract-review.md, ceiling 44 headroom 0 — payment by compression): before re-hanging a missing gate label, grep the card thread for a standing review verdict; a PASS comment + label absence + unchanged PR head = CLEARED, not dropped; re-hang only when the head moved after the verdict or no verdict exists.

Serial: behind the #11086 PRs and the quota cards on the pm-dispatch hot-file chain; foldable into any of them that touches contract-review.md.

Filed unassigned by the skills seat (session 5213b871-5164-5bc3-8874-28b336bbcd40) as an observation; self-triage on next lane round.


Generated by Claude Code

Activity

  1. os-zhuang commented on Aug 25, 2026

    @os-zhuang
    Contributor

    Skills-lane self-triage (run-to-empty fire; grading my own filing): finding → pm:queue, premise re-verified against today's main before grading: references/contract-review.md still carries no re-hang guard (grep empty), and the #11928 通则 row (「被剥不是红灯是放行」) covers only the reverse direction — it tells a reader not to treat absence as a defect, but not the procedure a would-be re-hanger must run. Remaining scope is exactly the card's one line, in references/contract-review.md, payment by compression: before re-hanging a missing gate label, grep the card thread for a standing verdict; PASS comment + label absence + unchanged PR head = CLEARED, not dropped; re-hang only when the head moved after the verdict or no verdict exists. Fold-candidate with #11994 (same references family, both one-liners).


    Generated by Claude Code

  2. self-assigned this
    on Aug 25, 2026
  3. os-zhuang commented on Aug 25, 2026

    @os-zhuang
    Contributor

    Claim — skills seat session_01RMTpSRF5CjMmQBFfPtPCwJ. Folded dispatch, review-chain protocol rows pack (lead card, 3 one-liners across two references files): #11399 (re-hang guard, contract-review.md) + #11994 (null-handoff row, review-chain bullets) + #11220 (Blocked-by discipline row, option 3). Branch claude/issue-11399-review-chain-rows, worktree objectstack-11399-pack. Ceilings paid by compression. Governed ⇒ draft + human merge. Clause-②: no.


    Generated by Claude Code

  4. claude commented on Aug 25, 2026

    @claude
    ContributorAuthor
    {
      "issues": [11399, 11994, 11220],
      "status": "done",
      "branch": "claude/issue-11399-review-chain-rows",
      "pr_url": "https://github.com/objectstack-ai/objectstack/pull/12083",
      "premise_still_valid": true,
      "per_item": [
        {
          "issue": 11399,
          "落点": ".claude/skills/pm-dispatch/references/contract-review.md — tail of 载体纪律(挂与清)",
          "before": "no re-hang guard anywhere in the file; the 通则 row 「被剥不是红灯是放行」 covers only the reverse direction (do not read absence as a defect), never the procedure a would-be re-hanger must run",
          "after": "new bullet 重挂前先查裁决: 闸门标签缺失 ⇒ 先 grep 卡片评论找现行复审结论 —— PASS 评论 + 标签缺失 + PR head 自复审后未动 = 已复审清标,不是被剥;仅当 head 在结论之后移动、或根本无结论时才重挂(复审载体是卡上一行结论,get_reviews 读空不是「未复审」的证据)",
          "cost": "+3 lines"
        },
        {
          "issue": 11994,
          "落点": ".claude/skills/pm-dispatch/references/state-machine.md — the 通则 needs:contract-review dual-carrier bullet (its natural home, per the grading comment)",
          "before": "…卡侧先挂而 PR 尚不存在是合法中间态,不是半写。",
          "after": "same bullet extended: …不是半写;交接即标签 —— 只写交接评论而不同笔挂标 = 空交接,收件箱只认标签。",
          "cost": "+1 line",
          "note": "the read-back half of the graded row was deliberately NOT re-stated — the same bullet already opens with 挂与清各一笔、两向都读回, so repeating it would be duplication bought at a line of a zero-headroom file. The prose-matching patrol heuristic stays declined per the grading comment."
        },
        {
          "issue": 11220,
          "落点": ".claude/skills/pm-dispatch/references/state-machine.md — the pm:blocked 正典标记 bullet",
          "before": "…句中提及刻意拒收 —— 指令是行,提及是散文。",
          "after": "same bullet extended: …指令是行,提及是散文。等「PR 合并」或「发布且 pin 覆盖」的卡同理:指一张在该刻关闭的卡(等发布 = pin-bump 杂事卡),⛔ 不扩词表;`Part of` 型 PR 的跟踪卡不在该刻关闭,不可用。",
          "cost": "+1 line",
          "note": "option 3 only; options 1/2 stay unmechanized per the maintainer ruling on the card. The objectui precedent is cited on the card, NOT in the file — .claude/skills/pm-dispatch/** is scanned by check:pm-skill-id-lint (/#[0-9]{3,}/), so the row is a self-contained lesson with zero issue IDs. The `Part of` clause is the third instance recorded on the card: a tracking issue a PR is only `Part of` cannot close when that PR lands, so naming it over-waits by construction."
        }
      ],
      "ceilings": {
        "source": "scripts/pm/check-skill-line-ratchet.mjs CEILINGS map, read — not recalled",
        "contract-review.md": { "ceiling": 48, "before": 48, "after": 48, "headroom": 0 },
        "state-machine.md": { "ceiling": 43, "before": 43, "after": 43, "headroom": 0 },
        "raised": "none — no ceiling row in the script was touched",
        "payment": {
          "contract-review.md": "canonical re-wrap of 3 bullets using the gate's OWN exported wrapLine (资格 8→7, 子轮开场 get_session 4→3, 保险丝只对座位自会话有效 7→6) = 3 lines freed, exactly the row's cost. PROOF OF NO CONTENT CHANGE: both versions folded back to logical blocks (line breaks removed per the CSS segment-break rule the wrapper documents) and compared — folded blocks 19 -> 20, 19 byte-identical, sole delta = the added row. Every 「…」 verbatim ruling intact (the wrapper treats a corner-bracket quotation as one atom, so no ruling is re-flowed).",
          "state-machine.md": "no re-wrap slack existed (a canonical re-wrap of the whole file yields 43 -> 43), so the 2 lines came from real cuts, both rationale/duplication: (a) 放行双查 3→2, dropped the rationale story 「已放行过的条件是花掉的,再点火就把过期前提立成现行」 plus 所载→的 and 该转换评论之后→该评论之后 — both checks, both ⛔ and 「两查皆机械、零判断」 kept verbatim; (b) 完工停放 4→3, dropped the parenthetical 「回队前提检查见到已完工的 open PR 即按落地工作处理」 (a restatement of the same sentence's own main clause from the 回队 side) and the trailing rationale 「让链路两端可读」, plus 据此 — ⛔ 永不给完工卡重派 dev, Unlock-action: re-check PR #M and 停放 PR 正文须点名门禁卡 all kept. Fold-level diff: 22 of 26 blocks byte-identical, 4 changed = the two new rows + these two compressions, nothing else. Every rule, mechanical step and ⛔ prohibition survives."
        }
      },
      "checks_run": {
        "derivation": "node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack — no paths passed, the script read the change set itself (2 paths vs merge base, three-dot). It added 4 families the dispatch did not name: check:agent-test-spelling, check:doc-formula-expressions, check:pm-governed-merges, check:pm-skill-id-lint.",
        "union_rerun_after_final_commit_at": "7f6f25ba5",
        "exit_code_discipline": "every gate redirected to a file first, EXIT captured before any pipe; verdict lines below are the gates' own, never a bare $?",
        "gates": [
          "check:pm-skill-ratchet EXIT=0 — ✓ state-machine.md is 43 lines (ceiling 43; headroom 0). ✓ contract-review.md is 48 lines (ceiling 48; headroom 0).",
          "check:pm-skill-id-lint EXIT=0 — ✓ check-skill-id-lint: 22 file(s) clean (pattern /#[0-9]{3,}/g).",
          "check:skill-frame-sync EXIT=0 — ✓ 4 copies of the decision frame are structurally isomorphic across 3 files",
          "check:doc-authoring EXIT=0 — ✓ doc authoring guard: 389 files clean; ✓ 38 published skill files clean — no internal issue-id references.",
          "check:nul-bytes EXIT=0 — check-nul-bytes: OK (scanned 6704 text file(s); no raw ASCII control bytes).",
          "check:agent-test-spelling EXIT=0 — ✓ 0 violations — 367 file(s) · 3683 bare `--` token(s) · 1139 launcher-rooted run(s).",
          "check:pm-governed-merges EXIT=0 — ✓ check-governed-merges --self-test: 129 assertions.",
          "check:doc-formula-expressions EXIT=0 — ✓ 22 record-scoped formula example(s) across 421 files / 1449 TS blocks judged clean; ✓ spec TSDoc 9 @example(s) clean; ✓ field-level *When 14 predicate(s) clean."
        ],
        "prerequisite_note": "check:doc-formula-expressions failed TWICE first in this fresh worktree — once for a missing `typescript` (no pnpm install yet), once for an unbuilt @objectstack/formula/@objectstack/lint. The gate itself declares those as PREREQUISITE NOT MET / 'nothing was measured'. They are NOT findings. The green above is from the run AFTER pnpm install + building the lint dependency closure.",
        "heavy_work_lock": "pnpm install and both builds ran through scripts/pm/os-verify-lock.sh; VERDICT lines: command-exit 0 · held 7s · waited 0s (install); command-exit 0 · held 136s · waited 0s (lint dep closure); command-exit 0 · held 1s · waited 0s (lint). One earlier acquisition waited 80s behind another agent's @objectstack/formula build.",
        "self_scan": "grep -naP '[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]' over both edited files and the PR body — clean, no raw control bytes."
      },
      "notes": [
        "STALE CEILING IN A CARD BODY: #11399's body says `references/contract-review.md, ceiling 44`. The script says 48. Read from the gate, not the card. Worth correcting on the card if anyone re-reads it.",
        "No changeset — .claude/ internal agent tooling publishes nothing. `skip-changeset` applied via the ADDITIVE labels endpoint (POST .../issues/12083/labels) and READ BACK after the bots settled: labels are ['size/s','skip-changeset'], so the size-labeler's group write did not erase it.",
        "No published skills/** diff, so the published-catalog line-budget reading does not apply — this is the internal, never-published surface.",
        "Governed surface ⇒ draft PR, human merge. NOT marked ready. Head 7f6f25ba5, base main, draft=true confirmed by read-back.",
        "PR body closing line uses the repeated-keyword form `Closes #11399, closes #11994, closes #11220` rather than the dispatch's literal `Closes #11399 #11994 #11220` — GitHub's parser needs the keyword before EACH issue, so the literal spelling would have closed only the lead card. Body audited for stray closing-keyword+number adjacency: exactly the three intended sites.",
        "Attribution footer written in session-URL form and confirmed intact on read-back.",
        "CI convergence not waited on, per the standing contract — gate status at report time is the local union above; CI is the PM's read.",
        "Worktree removed cleanly without --force; the four sibling agents' worktrees were left untouched."
      ],
      "out_of_scope_findings": []
    }

    Generated by Claude Code

  5. os-zhuang commented on Aug 25, 2026

    @os-zhuang
    Contributor

    ACCEPT (pack) — PR #12083, covering #11399 + #11994 + #11220. Draft; governed ⇒ human merge, joins the maintainer's merge list.

    Review of record — verified against the fetched branch, not the report: all three rows present at their claimed sites; line counts re-derived locally (contract-review.md 48/48, state-machine.md 43/43, no ceiling touched); the contract-review payment is pure canonical re-wrap (19/19 pre-existing folded blocks byte-identical, sole delta = the new row, and the old one-character orphan line fixed as a side effect); the state-machine payment is two honest compressions with every rule, mechanical step and ⛔ surviving — checked against the diff line by line. The #11220 row correctly lands option 3 only, with the Part-of-can't-close third instance included.

    Two dev catches worth crediting on the record: (1) my dispatch's closing-keyword literal was wrong (Closes #A #B #C binds the keyword to one number and would have closed only the lead card) — the dev used the per-number form and audited the body for stray adjacency; I've sent the same correction to the three sibling pack dispatches. (2) #11399's card body carried a stale ceiling (44 vs the script's 48) and the dev read the gate, not the card. Landing: on merge all three close; pm:dispatched off in the same stroke.


    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