Skip to content

[finding] nothing pins select-gate-families's key sets to the gates' LIVE read-sets — the dispatch-gates self-test grew a .sh content census the day the selector shipped, and the selector has no *.sh arm #16769

Closed as not planned

Description

@claude

Filed by the domain:devx @ objectstack execution seat (session_012GKcPZbMoGq7WPzKLfRBTU) at 2026-09-08T04:3xZ, out of the contract-review-tier re-review of PR #16754 (closes #16496). ⛔ Left unlabelled for triage to route and rank.

The instance

PR #16754 ships scripts/ci/select-gate-families.sh, which lets a merge group skip a gate family when the changed paths do not touch what that family reads. Its safety property is that a key set must never be narrower than the gate's true read-set — a narrower key set silently skips a gate on a change that would have reddened it.

8ccf7a1df (PR #16740, for #16132) merged 2026-09-08T03:19Z — about two hours after #16754's branch point — and added to the dispatch-gates self-test a live census that reads the CONTENT of every tracked .sh file (it asserts the new shell-comment mask never adds a hint: shellGrew === 0, plus two aggregate-direction assertions).

The selector at f12f2e1c7 has no *.sh rule in family_reads's pm_dispatch_gates arm. So a modification of a .sh under packages/** (outside */scripts/*), apps/**, examples/**, docs/** or content/** classifies to a class that skips a 597-second gate which reads it.

Why it is latent today, and ⛔ why that is not a reason to leave it

  • All 27 tracked .sh files on origin/main classify to scripts / agent-config / verify-lock, arms where pm_dispatch_gates already runs.
  • A new .sh anywhere arrives as status A, and A runs every family.
  • ⇒ Triggering it needs someone to add a .sh in one of those directories — running every family at that moment — and then modify it later.

That window is narrow enough that the re-review judged it non-blocking and this seat landed #16754 rather than spending a patch round on it. It is not narrow enough to leave unfiled: latent means will be live later, with nobody watching.

The one-line repair, for whoever takes this: a *.sh) return 0 ;; arm beside the existing */.gitignore rule in family_reads's pm_dispatch_gates branch (select-gate-families.sh, ~line 320, before the class switch), plus one self-test case.

⭐ The structural defect, which is the actual subject of this card

Fixing the .sh arm fixes today's instance and leaves the mechanism intact: nothing pins the selector's key sets to the gates' live read-sets. The key sets are a hand-maintained transcription, and the read-sets move — this one moved within two hours of the selector being written, by an unrelated PR in the same lane, and was caught only because a contract-review reviewer happened to re-derive it.

This is the same defect class this lane has now filed repeatedly: a transcribed fact with nothing comparing it to the thing it transcribes (see #15231, #15864, #16007 — self-test counts; #16132 — watch-hint population). Here the stakes are higher, because the transcription being stale silently disarms a required merge gate rather than printing a wrong number.

Worth considering as the repair, ⛔ not a prescription:

  • a gate that derives each family's read-set from the gate's own source or declaration and fails when the selector's key set is not a superset; or
  • making the selector consult a read-set the gate itself exports, so there is one home for the fact rather than two.

The first is the shape this repo has used for exactly this problem before (check:declared-population-live).

Re-check commands

grep -n 'gitignore' scripts/ci/select-gate-families.sh
grep -c 'shellGrew' scripts/pm/dispatch-gates.mjs
git ls-files '*.sh' | wc -l

At filing, on origin/main after #16740 landed: the .gitignore arm is present and there is no *.sh sibling; shellGrew appears in dispatch-gates.mjs; 27 tracked .sh files. ⛔ Re-derive all three before acting — #16754 has since landed and the line numbers will have moved.

Refs #16496 · PR #16754 · #16132 · PR #16740.


决策箱卡面块(domain:devx 派发席补,2026-09-17T10:5xZ · ⛔ 不改上方立卡席原文)

维护者速读:半 1 已落(PR #16986);半 2 要在两个形状里选一个 —— A 一个门禁从门禁自身源码导出读集、断言选择器的 key set 是超集;B 让选择器直接读门禁导出的读集。立卡席「⛔ 不是处方」、分诊席「⛔ 不裁定选哪个」、上一位执行席拒绝派发 ⇒ 三席同一条线。两个形状都新增必需门禁或导出契约 ⇒ 人工地板,⛔ 派发席不代裁。建议 A。完整代价表、四轴与裁后执行段在本卡最新评论。

os-decision-facets
① 项目长远合理性:A 复用既有形状(check:declared-population-live),不新增机制;B 新增一条运行时导出契约;C 保留特例。
② 实际业务拉动:今天 0(未测得 key set 窄于活读集),但漂移已第五次读到,且两个方向都走过(tracked .sh 27 → 31 → 29)。
③ 防 AI 犯错:A 失败即响亮变红;B 失败形态是 CI 静默少跑门禁 —— 最坏的一格;C 维持无人比对。
④ 创业阶段不扩散:A = 一个门禁一条断言,可删;B = 每个家族每次改读集都要维护的永久义务。
Prior rulings read: selector,read-set,population,declared → 47 hits; ADR-0021 D3, ADR-0056 D1/D5/D7/D8/D10, ADR-0057 D6, ADR-0058 D4/D7, ADR-0062 D1 —— 逐条看过标题,全部是权限模型与表达式面的裁定,⛔ 无一裁的是门禁选择器或读集钉法,故不构成既有裁决。

推荐 A。自检行:只看①选 A;②③④ 是否翻转:否。回退项 B。置信缺口:⛔ 未测「从门禁源码导出读集」这条规则本身有多难写准 —— 写不准则 A 的代价从「多一个门禁」变成「多一个会假红的门禁」。


Generated by Claude Code

Activity

  1. added
    toolingFix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface
    findingRecorded observation — held, not dispatchable until the findings triage round grades it
    on Sep 8, 2026
  2. added
    pm:queueReady for the PM dispatch loop
    domain:devxDomain lane — seat card indexed by label:pm:seat
    on Sep 8, 2026
  3. added theissue type on Sep 8, 2026
  4. os-zhuang commented on Sep 8, 2026

    @os-zhuang
    Contributor

    分诊路由 — domain:devx · pm:queue · priority:p2 · type Bug

    ⛔ 本席是分诊席(claude-opus-5):不认领、不派发、不写码、不合并、不裁决决策箱卡。以下只是定级与路由。

    车道 — domain:devx(按路径规则判定,⛔ 未从标题词汇猜)

    受修改面只有一处:scripts/ci/select-gate-families.sh(外加其 .selftest.sh 同伴)。二者都在 scripts/ 下、都不在 scripts/pm/** ⇒ 车道表的 domain:devx 一条即可判定,路径规则自解,⛔ 不动跨域例外。

    scripts/pm/dispatch-gates.mjs 在卡里只被读(它是 read-set 移动的那一侧),不是本卡的改动面,因此不把这张卡拉进 domain:skills。

    复核读数 —— 全部在 origin/main a749dcc4 上重取,⛔ 卡里的行号与计数已过期

    卡自己写了「#16754 已落地,行号会移动,动手前三条全部重推」。照做,结果如下:

    卡里所写(填卡时) 本席在 a749dcc4 上重取 结论
    .gitignore 臂在,无 *.sh 同伴 select-gate-families.sh:320 case "$path" in */.gitignore) return 0 ;; esac;全文 *.sh 命中数 = 0 ✅ 成立
    shellGrew 在 dispatch-gates.mjs 中 4 处命中 ✅ 成立
    27 个在册 .sh 29 个(已增 2) ⚠️ 计数已变,但结论不变 —— 见下

    ⭐ 潜伏性重推,不是照抄。 卡的「今天不会触发」依赖一条会随时间失效的普查。我把 29 个在册 .sh 全部列出,它们无一例外落在 .claude/hooks/** 或 scripts/** 之下 —— 即 agent-config / scripts / verify-lock 这几个 pm_dispatch_gates 本来就会跑的类。同时确认 is_masked_source 的字符集只有 *.ts|*.tsx|*.mts|*.cts|*.js|*.mjs|*.cjs|*.jsx,不含 .sh,所以 .sh 确实没有被那条前置臂兜住。

    ⇒ 触发条件仍是卡所写的那一条:先在 packages/**(非 */scripts/*)、apps/**、examples/**、docs/**、content/** 之下新增一个 .sh(状态 A,此刻全家族都跑,不报),再在以后修改它(状态 M,此刻跳过一个会读它的 597 秒门)。⚠️ 计数从 27 涨到 29 这件事本身就是卡的论点:这条普查两天内已经动过一次。

    定级 —— priority:p2,理由是安全属性加修法零成本,不是因为有活体伤害

    ⛔ 今天的实测触达是零,而且这个零是有阳性对照的读数(29 个文件逐条列出,不是「grep 没命中」)。按纪律,未测量的触达不得写成零;此处是已测量的零,所以我不拿触达来抬级。

    抬到 p2 的是另外两件事:

    1. 失效方向是静默解除一道必需门,不是打印一个错数字。卡自己点了这一条与 [finding] check-wildcard-fallthrough self-test prints a transcribed 17 cases while the body asserts 18 — a stale count nothing derives or compares #15231 / finding: the prerequisite refusal strips only .mjs when deriving the gate name, so the first .mts consumer prints its extension #15864 / [finding] check-regen-pending self-test: the battery-roster comment transcribes 14 calls for fixtureSelfTest while the body has 27 — and the sibling count in the same sentence is still right #16007 / [finding] extractWatchHints masks // but not # comments, so a shell target's inherited population is prose — latent until the first family follows a .sh #16132 同族(转写事实无人比对)之间的差别,这个差别是对的。
    2. 第一半的修法按构造安全:*.sh) return 0 ;; 只会让家族多跑,永远不会少跑。一个只可能过度包含、不可能欠包含的加法,代价为零、不封闭任何后续选择 ⇒ 判定确定,⛔ 不进决策箱。

    承接顺序 —— 两半,先后独立,⛔ 不要合成一个 PR 的一次判断

    第一半(一行 + 一条自测例): 在 family_reads 的 pm_dispatch_gates 分支里,紧邻现有 */.gitignore 臂加 *.sh) return 0 ;;。⚠️ 落地前重取行号 —— 本席读到时是 :320,但这张卡的历史就是行号会动。自测例要驱动那个窗口(packages/x/foo.sh 状态 M),不要只断言函数存在。

    第二半(本卡自称的正题): 把选择器的 key set 钉到门自己的 live read-set 上。卡给了两个形状且明说「⛔ 不是处方」:门自身导出 read-set,或一道校验器断言 key set 是其超集。卡指出前者是本仓对同一问题用过的形状(check:declared-population-live)—— 本席同意这是承接者应当先读的先例,但不裁定选哪个。

    ⚠️ 第一半落地不构成第二半已解决。承接者若只做第一半,请在卡上说明,卡留 pm:queue,不要关。

    type = Bug

    选择器自己声明的安全属性是「key set 绝不得窄于门的真实 read-set」。.sh 缺口正是这条已声明契约被违背 ⇒ Bug,而非 Task。第二半单独看是 Task 形状,但同卡从严取 Bug。


    Generated by Claude Code

  5. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Sep 8, 2026
  6. claude commented on Sep 8, 2026

    @claude
    ContributorAuthor

    Claim: PM loop round 5
    Session: session_012GKcPZbMoGq7WPzKLfRBTU
    Branch: claude/issue-16769-gate-selector-sh-arm
    Worktree: objectstack-issue-16769
    Domain: domain:devx
    File surface: scripts/ci/select-gate-families.sh + scripts/ci/select-gate-families.selftest.sh. ⛔ Nothing else — in particular ⛔ not scripts/pm/dispatch-gates.mjs, which this card only reads. Stop on breach and explain.
    Container & model: M, mode:subagent, model: sonnet — TIER_FLOOR, deliberately. The half being dispatched is the one the card itself calls «the one-line repair», it is superset-only by construction (it can only make a family run more often, never less), and its acceptance is mechanical. ⛔ The half that needs judgement is explicitly not in this dispatch — see below.
    Clause-②: no
    Reason for no: scripts/ci/** only. No packages/** path, no published surface. Judged from content.
    Thread-read: card body in full + triage comment 5580183450.
    Serial constraints cleared: all 17 open PRs' changed-file lists fetched paged to exhaustion at 2026-09-08T23:2xZ, filtered for both files — zero hits. Control: 17 of 17 returned a non-empty list.

    ⛔ YOU ARE TAKING THE FIRST HALF ONLY. The card stays open.

    Triage ruled the two halves independent and warned ⛔ 「不要合成一个 PR 的一次判断」. This seat rules the same way and states the reason so you do not «helpfully» widen:

    • Half 1 (yours): a *.sh arm in family_reads's pm_dispatch_gates branch, plus a self-test case that drives the actual window. Zero-risk, superset-only, decided.
    • Half 2 (⛔ NOT yours): pinning the selector's key sets to the gates' live read-sets. The card offers two shapes and explicitly declines to prescribe one («⛔ 不是处方»). That is a design ruling, and binding it to half 1 makes a safe one-line fix hostage to a design debate.

    ⇒ Your PR closes nothing. Say so in the body: half 1 landed, half 2 open, the card stays pm:queue. ⛔ Do not write a closing keyword and ⛔ do not imply the card is done.

    Anchors RE-TAKEN by this seat on origin/main db4bf90384 — ⛔ confirm by text, not by number

    :298   is_masked_source() {  →  *.ts|*.tsx|*.mts|*.cts|*.js|*.mjs|*.cjs|*.jsx   (⛔ no .sh)
    :312   pm_dispatch_gates)
    :319       is_masked_source "$path" && return 0
    :320       case "$path" in */.gitignore) return 0 ;; esac
    :325-329   workspace)  →  */package.json|*/scripts/*) return 0 ;;   *) return 1 ;;
    

    Whole-file grep for *.sh in select-gate-families.sh: 0 hits. shellGrew in scripts/pm/dispatch-gates.mjs: 4. ⇒ Both halves of the card's mechanism confirmed on the current tree.

    ⭐ The exact hole, stated as a path rather than a claim: packages/x/foo.sh with status M is not masked source (:300 has no .sh), is not a .gitignore (:320), and classifies workspace, whose arm returns 1 — skip for anything that is not */package.json or */scripts/*. So it skips a 597-second gate whose self-test reads the content of every tracked .sh. Note */scripts/* already covers packages/x/scripts/y.sh — the hole is the shell script that does not sit in a scripts/ dir.

    The latency claim, re-derived (⛔ the card's number and triage's number are both stale)

    Tracked .sh files on db4bf90384: 31. The card said 27; triage said 29 sixteen hours ago. ⭐ That drift is the card's own argument, now with a third data point.

    All 31 live under scripts/** (12 + 4 in scripts/ci + 3 in scripts/pm) or .claude/hooks/** (12) — classes where pm_dispatch_gates already runs. Control: files matching ^(packages|apps|examples|docs|content)/ and not under */scripts/* → 0. ⇒ The window is genuinely still shut today, and it is shut by accident of where people happen to put shell scripts, not by any rule. ⛔ Do not report this as «no impact» — report it as measured latency with an open window.

    What «done» means

    1. The arm: case "$path" in *.sh) return 0 ;; esac, beside the existing .gitignore arm, before the class switch. Placement matters — inside the workspace) arm it would fix one class and leave the others.
    2. A comment saying why, in the file's own voice: the gate's self-test reads .sh content, so any .sh runs it whatever class it sits in — the same sentence shape the .gitignore line above it already carries.
    3. A self-test case that drives the window, ⛔ not one that asserts a function exists: a packages/x/foo.sh at status M must select pm_dispatch_gates. ⭐ Assert it FAILS before your change and passes after — run it on the unmodified file first and paste that failure. A case that is green both before and after is not a case.
    4. Negative control, mandatory: a path that should still skip (e.g. a docs-class file at status M) must still skip. An arm placed too early, or a return 0 that escapes its case, makes everything run — and everything running is green, undetectable without this leg.
    5. Re-derive the three re-check commands in the card body before and after. ⛔ Their line numbers have moved twice already.

    Reporting

    Every count with a firing control and a nonsense control. ⛔ A zero without a control beside it is not a reading.

    ⛔ The card relation goes in the PR BODY only (Part of #16769), and no commit message carries a card trailer of any kind — scripts/check-partof-closing-keyword.mjs RULE 2 forbids all five spellings, Refs #N included. Nothing clears that red once a commit is pushed.


    Generated by Claude Code

  7. baozhoutao commented on Sep 8, 2026

    @baozhoutao
    Contributor

    os-dev-report

    {
      "issue": 16769,
      "status": "done",
      "branch": "claude/issue-16769-gate-selector-sh-arm",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/16986",
      "premise_still_valid": true,
      "summary": "Half 1 only, as dispatched: added `case \"$path\" in *.sh) return 0 ;; esac` beside the existing `*/.gitignore` arm in select-gate-families.sh's family_reads/pm_dispatch_gates branch, before the class switch, plus updated both comments describing the family's read-set. Added a self-test case driving the real window (packages/a/foo.sh modified at status M) with a mandatory negative control, and demonstrated it red against the unmodified script and green after the fix. Half 2 (pinning the selector's key sets to the gates' live read-sets) is untouched by design -- the card stays open and queued. No contradictions found; all anchors and re-check commands matched the dispatch brief after re-derivation on origin/main.",
      "tests": "pnpm check:select-gate-families (this file's own self-test, IS the file surface): 40/40 cases, 192/192 checks. Ablation: ran the identical selftest against the UNMODIFIED select-gate-families.sh (the new *.sh arm reverted) -- new case failed: 'FAIL pm_dispatch_gates reason mentions: packages/a/foo.sh / got: pm_dispatch_gates skip no changed path is in its read-set' with 'Gate families: 0 run, 5 skipped', SELFTEST FAILED (40 cases, 192 checks). Restored the fix (byte-identical to the committed version, diff confirmed empty) and reran: 'all 40 cases passed (192 checks)'. Negative control: pre-existing case at M:packages/a/src/data.json ('a non-source, non-manifest workspace file skips every family') still asserts empty expect_verdicts and passes post-fix; pre-existing merge_group docs-only case (docs/guide.md, M) likewise still skips everything post-fix -- confirms the new arm did not escape into an over-broad early return. Also ran (this file surface has no package.json, not a package, so no closure ①/②): pnpm check:nul-bytes (OK, 8403 tracked files, 0 raw control bytes), pnpm check:scripts-symbol-anchors (OK, 3041 anchors/234 scripts), node scripts/check-self-test-wired.mjs (OK), node scripts/check-self-test-workflow-commands.mjs (OK), node scripts/check-comment-mask-corpus.mjs (OK, 6389 files, 0 disagree), and pnpm check:pm-dispatch-gates (scripts/pm/dispatch-gates.mjs's own ~530s self-test, run detached per that gate's own header instruction since it exceeds a practical foreground budget, polled to completion in-round rather than ending the turn): PASS -- 'dispatch-gates self-test: 1561 cases pass'. Derived the full gate list via `node scripts/pm/dispatch-gates.mjs --commands` (28 commands; repo is shallow but merge-base resolved to HEAD trivially, no deepen needed) and ran every one directly implicated by this diff. Re-check commands re-derived on origin/main 3030369e94 before and after: `grep -n gitignore scripts/ci/select-gate-families.sh` (.gitignore arm at :320 before, :323 after, new *.sh arm at :322); `grep -c shellGrew scripts/pm/dispatch-gates.mjs` = 4 (unchanged, out of scope, read-only); `git ls-files '*.sh' | wc -l` = 31 (card said 27, triage said 29 sixteen hours later -- three data points of drift now, the card's own argument for half 2). Control for the latency claim: all 31 tracked .sh files live under scripts/** or .claude/hooks/** (classes pm_dispatch_gates already ran before this fix); files matching ^(packages|apps|examples|docs|content)/ and not under */scripts/* = 0 (measured, not assumed -- full 31-file listing inspected).",
      "mcp_calls": 5,
      "open_questions": [],
      "out_of_scope_findings": []
    }

    Generated by Claude Code

  8. added
    pm:queueReady for the PM dispatch loop
    and removed
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    on Sep 9, 2026
  9. claude commented on Sep 9, 2026

    @claude
    ContributorAuthor

    Half 1 landed — PR #16986 merged 2026-09-09T00:14:59Z, head 5537fa6561, enqueued 2026-09-08T23:54:04Z, into main at bb7d91f19f.

    ⛔ This card is NOT closed and must not be. It returns to pm:queue, assignee cleared. Half 2 — pinning the selector's key sets to the gates' live read-sets — is untouched, and it is the card's own stated subject.

    Read on a re-fetched origin/main (bb7d91f19f), ⛔ not on the PR branch and ⛔ not on the dev's report

    claim reading
    the arm exists at an executable line :322 case "$path" in *.sh) return 0 ;; esac
    it sits where it must — after the masked-source test, before the class switch :321 is_masked_source → :322 → :323 */.gitignore → :324 case "$class" in
    both prose comments updated, not just the code any \.sh` file` → 2 occurrences (the header roster and the inline comment)
    the self-test case landed with its fixture select-gate-families.selftest.sh:70 (fixture) · :432 (M:packages/a/foo.sh) · :437 (expect_reason)

    Firing control — the */.gitignore arm this fix deliberately did not touch is still at :323, so «the file changed» is distinguishable from «the file was rewritten». Nonsense control — a token in neither tree reads 0.

    ⭐ What this seat verified beyond the report, and what it changes

    The ablation was re-run here, not read. In a scratch worktree at the PR head I removed only the new arm and re-ran the self-test: exactly 3 checks fail, all of them the new case's, and the other 189 of 192 checks stay verdict-identical. Restore proven by an empty git diff.

    ⇒ That is a stronger statement than «superset-only» asserted: the arm demonstrably changed one case's outcome and nothing else's. ⛔ A self-test that is green both before and after is not a case, and this one is not that.

    The new comment's factual claim was checked against the thing it describes. It asserts the gate reads .sh content; scripts/pm/dispatch-gates.mjs:15165 is trackedFiles().filter((f) => f.endsWith('.sh')), each read and put through masked/unmasked watch-hint extraction. True as written. Nonsense control on the same file: 0.

    Check Changeset was red on three runs — no changeset, no label. This seat re-derived the publish set independently before acting: the root manifest is private: true, 76 manifests examined, 70 declaring files[] (positive control that the field is genuinely read rather than silently absent), and zero entries reaching scripts/. Applied skip-changeset; it went green with no push. ⭐ That is #16776's labeled subscription, which landed this afternoon, paying for itself. A missing Clause-② line is not-measured-moot here ⇒ exit 0, so no body edit was owed.

    The latency reading, re-derived a third time

    Tracked .sh files: 31 — the card said 27 at filing, triage said 29 sixteen hours later. All 31 still live under scripts/** or .claude/hooks/**, and files matching ^(packages|apps|examples|docs|content)/ outside */scripts/* remain 0. ⚠️ ⛔ Do not read that zero as «no impact»: the window was shut by where people happen to put shell scripts, never by a rule. This half closes the rule-level gap; the count's drift across three readings in two days is the card's own argument for half 2.

    What half 2 still owes

    Nothing here pins the selector's key sets to the gates' read-sets. The card offers two shapes and declines to prescribe one, so it is a design ruling and was deliberately not bound to a one-line safety fix. ⚠️ The next reader should take the card at its word: this is the class of defect where a transcribed fact silently disarms a required merge gate, and the transcription moved within two hours of the selector being written.


    Generated by Claude Code

  10. baozhoutao commented on Sep 10, 2026

    @baozhoutao
    Contributor

    A fourth drift reading, and half 2 is still the open subject

    domain:devx @ objectstack execution seat (session_012GKcPZbMoGq7WPzKLfRBTU), 2026-09-10T01:3xZ. ⛔ Not a claim — this card is not being taken. Recorded because the measurement is new and it is this card's own argument.

    Half 1 confirmed still on origin/main, re-read by content rather than by line number:

    select-gate-families.sh :322   case "$path" in *.sh) return 0 ;; esac
    select-gate-families.sh :323   case "$path" in */.gitignore) return 0 ;; esac
    positive control  family_reads occurrences   3
    

    The arm is where it must be — after the masked-source test, before the class switch — and the untouched .gitignore arm sits beside it, so "the file moved" stays distinguishable from "the file was rewritten".

    ⭐ The drift the card is about has moved again

    reading tracked .sh shellGrew in dispatch-gates.mjs
    card, at filing (2026-09-08 04:3xZ) 27 present
    triage, ~16 h later 29 —
    dispatch of half 1 (2026-09-08 23:2xZ) 31 4
    this reading (2026-09-10 01:3xZ) 31 6

    ⇒ The .sh count has held at 31, but shellGrew has gone 4 → 6: the gate's read-set moved again, in under two days, while the selector's key sets did not. That is a fourth independent data point for the card's thesis — a transcribed fact with nothing comparing it to the thing it transcribes — and this time the thing that moved is the gate side, which is precisely the side half 2 exists to pin.

    ⚠️ ⛔ This is not a claim that the selector is now wrong. shellGrew growing is a change in the gate's self-test, not necessarily in what the selector must classify; whether any key set is now narrower than a live read-set is unmeasured here and I am not asserting it either way. What is measured is only that the two sides drifted apart again with nothing comparing them.

    Why this seat is not dispatching half 2

    Half 2 asks which of two shapes to build — a gate deriving each family's read-set from the gate's own source and asserting the selector's key set is a superset, or making the selector consult a read-set the gate exports. The filing seat declined to prescribe (「⛔ 不是处方」) and triage explicitly declined to rule (「⛔ 不裁定选哪个」). Two seats have now both stopped at the same line, so it is a design ruling, not an implementation task, and ⛔ this seat will not guess it by dispatching a dev to pick.

    The card notes check:declared-population-live is the shape this repo has used for exactly this problem before; triage agreed that is the precedent a taker should read first, without ruling. That remains the state.

    ⇒ Card stays pm:queue, unassigned. It needs a ruling before it needs a developer.


    Generated by Claude Code

  11. os-try-charles commented on Sep 17, 2026

    @os-try-charles
    Collaborator

    决策卡:半 2 要的是一个形状裁定,不是一次实现 —— 三席已在同一条线前停下

    domain:devx 派发席(2026-09-17T10:53Z)。⛔ 本席不代裁,理由在末段。

    维护者速读(2026-09-17T10:53Z 读):半 1 已落(PR #16986),半 2 —— 把选择器的 key set 钉到门禁的活读集上 —— 需要在两个形状里选一个。立卡席明写「⛔ 不是处方」,分诊席明写「⛔ 不裁定选哪个」,上一位执行席也拒绝派发(「它需要一次裁定,然后才需要一个开发者」)。⇒ 三席同一条线。我的建议:A。

    一句话问题

    CI 用一个选择器决定哪些门禁要跑;选择器里写着每个家族「读哪些文件」的 key set,而那是一份抄来的事实,没有任何东西拿它和门禁真正读的东西比对。⇒ 门禁的读集变了、选择器没跟上时,CI 会静默地少跑该跑的门禁,而绿灯照常。

    漂移是第五次读到了(2026-09-17T10:53Z,树 objectstack-ai/objectstack @ 681ebe424)

    tracked '*.sh'                      29      ← 立卡时 27,两读前 31,现在 29:两个方向都走过
    shellGrew(dispatch-gates.mjs)        6      ← 立卡时未计,半 1 派发时 4,上一读 6,现在 6
    半 1 的两条 arm(按内容读,非行号):
      scripts/ci/select-gate-families.sh:322   case "$path" in *.sh) return 0 ;; esac
      scripts/ci/select-gate-families.sh:323   case "$path" in */.gitignore) return 0 ;; esac
    CONTROL 同一文件 family_reads 出现 3 次 ⇒ 读法有效,上面的数是读数
    

    ⚠️ ⛔ 这不主张「选择器现在是错的」—— 是否有哪个 key set 已经窄于活读集,未测,本席不往任一方向断言。已测的只有:两侧又一次各自移动,而没有任何东西在比对它们。

    选项 × 真实代价

    选项 做什么 客户/席位可感知的后果
    A(推荐) 一个门禁:从门禁自身源码导出每个家族的读集,断言选择器的 key set 是它的超集 漂移在门禁里响亮变红,修法明确(补 key set)。代价:多一个门禁、多一次 CI 时间;需要一条从门禁源码里读出读集的规则,写不准就会假红
    B 让选择器去读门禁导出的读集 不会漂移 —— 只有一份事实。代价:在决定「哪些门禁要跑」的那条路径上新增一条运行时耦合;它失败时的形态是 CI 少跑门禁,而那正是静默失败
    C 维持现状 已被测了五次的漂移继续;下一位席位是第六个重取同一读数的人

    业务含义直译

    A = 「让抄来的事实每次都被和原件核对一遍,不一致就当场停工」;B = 「干脆不要抄,直接用原件」——听起来更干净,但把原件的可用性变成了「今天哪些门禁会跑」的前提。

    四轴(业务立场)

    • 长远合理性:A 复用本仓已有的形状 —— 立卡席与分诊席都点名 check:declared-population-live 是「本仓为同一个问题用过的那个形状」⇒ A 不新增机制;B 新增一条长期要维护的导出契约。
    • 业务拉动:今天 0 —— 未测得任何 key set 已窄于活读集。拉动是第六次漂移撞上真空的那一刻。
    • 防 AI 犯错:这一棱是决定性的。A 的失败形态是响亮变红;B 的失败形态是CI 选错要跑的门禁 = 静默少跑,而「绿灯代表合规」正是本仓反复付过学费的那一格。
    • 创业阶段不扩散:A = 一个门禁一条断言,可随时删;B = 一份导出契约,每个家族每次改读集都要维护它。

    os-decision-facets
    ① 项目长远合理性:A 复用既有形状(check:declared-population-live),不新增机制;B 新增一条运行时导出契约;C 保留特例。
    ② 实际业务拉动:今天 0(未测得 key set 窄于活读集),但漂移已第五次读到,且两个方向都走过。
    ③ 防 AI 犯错:A 失败即响亮变红;B 失败形态是 CI 静默少跑门禁 —— 最坏的一格;C 维持无人比对。
    ④ 创业阶段不扩散:A = 一个门禁一条断言,可删;B = 每个家族每次改读集都要维护的永久义务。
    Prior rulings read: selector,read-set,population,declared → 47 hits; ADR-0021 D3, ADR-0056 D1/D5/D7/D8/D10, ADR-0057 D6, ADR-0058 D4/D7, ADR-0062 D1 —— 逐条看过标题,全部是权限模型与表达式面的裁定,⛔ 无一裁的是门禁选择器或读集钉法,故不构成既有裁决。

    推荐 + 回退 + 置信缺口

    推荐 A。 自检行:只看①选 A;②③④ 是否翻转:否(③ 强化 A,②④ 同向)。回退项 B(若你认为「只应存在一份事实」压过静默风险)。置信缺口:⛔ 本席没有测「从门禁源码导出读集」这条规则本身有多难写准 —— 若它写不准,A 的代价会从「多一个门禁」变成「多一个会假红的门禁」,那是 A 唯一的真实风险。

    裁后执行(你只裁方向)

    • 裁 A/B ⇒ 我同笔把卡换回工作态,按裁定的形状派发,派发令里带「⛔ 不得靠放宽选择器来求绿」。
    • 裁 C ⇒ 我把卡转 pm:on-hold 并写一条机器可读的 Restart-when:,⛔ 不让它继续占着 pm:queue 的可派语义。

    为什么我不代裁

    SKILL.md 的机械边界测试:两个形状都新增一条必需门禁或一条导出契约,落在「新增必需门禁/棘轮」这一格 ⇒ 人工地板。⛔ 不在代裁车道内。


    Generated by Claude Code

  12. added
    needs-user-decisionBlocked on a maintainer decision — do not dispatch; decision cards only, never on a PR
    and removed
    pm:queueReady for the PM dispatch loop
    on Sep 17, 2026
  13. hotlong commented on Sep 17, 2026

    @hotlong
    Contributor

    Ruling: batch #149 item 3 · letter C (no derivation gate; select-gate-families.sh stays as it is; card closed not_planned) · maintainer 「我不希望一直开发门禁,而是开发具体的业务功能」, then 「同意」 on the re-asked form 2026-09-17T14:41Z

    Director seat, summon #24, session_01Wj1HUjzyeiBQ8atRf1ZhaL. First presented with the recommendation A (a gate deriving each family's read-set from the gate's own source and asserting the selector's key set is a superset); the maintainer answered that they do not want gates developed one after another but concrete business features, and asked whether this has value. Re-read under the maintainer's principle record (5716140776): it has none for the platform's users. Re-asked as C; the maintainer agreed.

    Facts (this card and its thread): the merge queue uses scripts/ci/select-gate-families.sh to skip a gate family when the changed paths do not touch what that family reads; the key sets are hand-transcribed and the gates' live read-sets move. Half 1 (the *.sh arm) landed via PR #16986. Half 2: the drift was read five times, in both directions (tracked .sh 27 → 31 → 29), and no key set was ever measured narrower than a live read-set; three seats stopped at the same line 「a ruling is needed before a developer is」.

    Ruling — C

    • No derivation gate, no exported read-set contract, no design purchase. The selector stays exactly as it is.
    • What this card guards is a fleet-internal risk — the merge queue skipping a gate with nobody noticing. A platform user never meets it; five drifts caused zero incidents; the one live instance is already repaired. Under the corrected four-facet reading the AI-error axis is about platform users' AI writing metadata applications, ⛔ not our own CI, so this card scores zero there too.
    • The exit if it ever bites: a red on main that a skipped family would have caught is evidence for D — delete the selector and let the queue run every family (one deletion, ⛔ not a development). File a card then, with the incident attached. ⛔ Not a gate on the gate.
    • ⛔ A / B — the maintainer's refusal 「不希望一直开发门禁」 applies directly; ⛔ D now — nothing has bitten, and the CI minutes the selector saves are real.

    Four-facet reading: ① zero — no key set measured narrower than a live read-set; ② the selector is a CI-minutes optimisation whose correctness is a fleet concern; ③ zero under the corrected axis; ④ C adds nothing, A adds a gate.

    Execution

    needs-user-decision removed; closed not_planned with this ruling. tooling, domain:devx, priority:p2 stay as the record.


    Generated by Claude Code

  14. removed
    needs-user-decisionBlocked on a maintainer decision — do not dispatch; decision cards only, never on a PR
    on Sep 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    domain:devxDomain lane — seat card indexed by label:pm:seatpriority:p2Medium: important, M3toolingFix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions