Skip to content

feat: actionability gate round 2 — prefix tier + deferral vocabulary, wired into all four consumers (#316) - #318

Open
kiki830621 wants to merge 6 commits into
mainfrom
idd/316-actionability-gate
Open

feat: actionability gate round 2 — prefix tier + deferral vocabulary, wired into all four consumers (#316)#318
kiki830621 wants to merge 6 commits into
mainfrom
idd/316-actionability-gate

Conversation

@kiki830621

@kiki830621 kiki830621 commented Aug 14, 2026

Copy link
Copy Markdown
Member

這個 PR 做什麼(第 2 輪,2026-09-07)

讓四個 routing consumer(idd-list / idd-all / idd-implement / idd-plan)共用同一份
### Complexity 抽取與 actionability 判定,並且真的呼叫它。追蹤 issue #316

第 1 輪(2026-08-15)被 /idd-verify --pr 318 判 FAIL,兩個 CRITICAL:

  • CRITICAL-1:gate 完整實作、66 測試全綠、零 consumer 呼叫 verdict
  • CRITICAL-2:第 1 輪宣告 ### Complexity 為封閉值域(只准裸 tier + via 後綴)。用本 repo
    自己的 159 筆 diagnosis 一驗,66 筆(41.5%)合法寫法會被誤擋 —— 比它要取代的截斷 regex
    (29% 誤路由)更糟。producer 的常態是 **Plan** (decision-heavy at Finding 1)
    Spectra(opt-out → 直接 propose),裸 tier 只佔 28%。

/idd-reorganize #316/idd-diagnose #316(第 2 輪,全 corpus)→ /spectra-ingest 重定前提後重做。
第 1 輪的 PR body 原文在 edit history。

根因(兩層)

  1. 把會變的狀態存進不可變的 artifact。 ### Complexity 活在 append-only 的 Diagnosis comment,
    但 parked 會變。IDD 早就知道正確做法:### Blocking 正因為會變才放在 body 由 idd-update 維護。
  2. 第 1 輪的修法矯枉過正。 它把「tier 後面有字」等同於「延期」;corpus 說分界在「那些字是否表達延期」。

規則(第 2 輪,corpus 159/159)

  1. ### Complexity 下第一個非空行(code fence 不是區段;同級或更高 heading 結束區段)
  2. 剝 markdown 裝飾
  3. 值須 Simple / Plan / Spectra / SDD-warranted 之一開頭(整詞、最長匹配優先);開頭即 tier,
    其後的理由/括號/ via <來源> 一律合法
  4. 整行掃延期語彙 when triggered / parking lot / deferred / 暫緩exit 5,stderr
    deferral-marker: <原值>stdout 不印 tier(合法的前綴正是不能路由的理由)
exit 意義 reason
0 可路由,stdout = tier
3 值不以 tier 開頭(移入 discussion list complexity-unparseable(資料錯誤,要修)
4 無區段 complexity-missing(先 diagnose)
5 tier 合法但含延期語彙 complexity-deferral-marker合法狀態,不是要修的東西

風險姿態parking-lot label 是主訊號,語彙是高精度低召回的安全網 —— 漏抓退回 #298 前行為
(label 兜底)、誤抓是對可動 issue 的 hard stop,所以語彙保守、加詞要有 corpus 零誤報證據。
只掃第一行:掃整段會在 corpus 上多 2 個誤報(#154「remove deferred caveat」、#137deferred enum」)。

主要改動

檔案 改動
scripts/lib/actionability.sh tier 前綴抽取 + 延期語彙 → exit 5;reason 4 → 5;新增 idd_blocking_section- (none) placeholder 算空);共用 fence-aware 區段抽取器;value-less flag → exit 2(原本無限迴圈)
skills/{idd-list,idd-all,idd-implement,idd-plan}/SKILL.md 實際呼叫 idd_actionability_verdict(三訊號齊);Diagnosis 改用 gh api … --paginate--json comments 只回最舊 100 則);set -euo pipefail 安全的條件式捕捉;exit 2 是 consumer 自己的 FATAL
skills/idd-list/SKILL.md Step 3.7 改為三訊號 gate;Step 5 新增 Parked 組,#84 的 Blocked 標題/banner/footer 逐字保留並由測試 pin 住
skills/idd-diagnose/SKILL.md 不再宣告封閉值域:「tier 寫清楚、理由歡迎、延期貼 label」
references/actionability-gate.md 依新規則重寫:corpus 表、風險姿態、canonical 呼叫形狀(含分頁與 strict-mode)
scripts/tests/actionability-gate/ 225 條斷言;fixture 快照列改為 Diagnosis comment 逐字原值;corpus 抽樣四形狀各 ≥3;159 筆 corpus 凍結回歸(149/9/1/0);strict-mode 整合測試;consumer 接線 drift guard
CHANGELOG.md / plugin.json / marketplace.json 3.1.0

誠實的殘餘

驗證

Spectra

openspec/changes/add-actionability-gate/ —— 22/22 tasks(10 第 1 輪、2 skipped、10 第 2 輪)。

@kiki830621

Copy link
Copy Markdown
Member Author

Verify Report — PR #318

Engine

pai-ensemble 2.20.0 (canonical #207) — 4 IDD lenses + DA + Codex (gpt-5.6-sol), model: opus

Aggregate

FAIL — 2 CRITICAL, 21 HIGH blocking; 28 MEDIUM / 11 LOW follow-up

Frozen snapshot d0ef6d1 — freshness gate passed (HEAD unchanged during review).

Scope coverage

PR refs: #37 #84 #136 #292 #298 #306 #309 #310 #316 #317 — 8 已 CLOSED/MERGED(敘述脈絡)、#317 為獨立 follow-up(PR body 自述不在本次修復範圍)
Verified scope: #316


#316 — bug: #298 的修正只落在 idd-list

Requirements coverage: 部分達成,但兩個 CRITICAL 使核心宣稱未成立。

CRITICAL(blocking,兩者互相獨立)

C1. 三訊號 gate 從未接進四個 consumer — parking-lot / Blocking 仍被繞過

lens: codex · plugins/issue-driven-dev/skills/idd-all/SKILL.md

Codex 的首要發現,也是它給出「建議修改後再合併」的理由:

問題idd_actionability_verdictscripts/lib/actionability.sh(約 file line 90–139)已完整實作,但四個 skill 的新增 bash 片段全部只呼叫 idd_parse_complexity,沒有任何一處讀 labels / ### Blocking 並送進 verdict:

  • idd-all/SKILL.mdTIER=$(idd_parse_complexity "$LATEST_DIAGNOSIS" 2>/dev/null); CEXIT=$? → 「Dispatch 以 (CEXIT, TIER) 為鍵」
  • idd-implement/SKILL.md:「Routing 以 (CEXIT, TIER) 為鍵」
  • idd-plan/SKILL.md:「以 (CEXIT, TIER) 為鍵決定行為」
  • idd-list/SKILL.mdtier=$(idd_parse_complexity "$latest_diagnosis_body"); cexit=$?

對照 spec(specs/actionability-gate/spec.md)明文:「evaluating exactly three signals」。

觸發情境### Complexity 是合法 Spectra(CEXIT=0),issue 帶 parking-lot label 或 ### Blocking 非空,使用者跑 /idd-all/idd-implement/idd-plan

後果:parser 回 exit 0,consumer 照 tier routing 直接執行。fixture 自己描述的 #37 形狀(bare Spectra、事後由人貼 parking-lot)在這三個 consumer 仍會被送進 implementation/Spectra 流程 —— 正是本變更宣稱要消滅的「自動推翻人為 park 裁決」。

建議:(1) 每個 consumer 讀 labels 與 body 的 Blocking section;(2) 一律呼叫 idd_actionability_verdict;(3) idd-list 對 exit 1 繼續列出並分組,另三者對 exit 1 停止 routing 並列出完整 reasons;(4) verdict exit 2 必須當成 API 誤用,不可與 not-actionable 混同;(5) 若「直接呼叫 /idd-implement 可覆寫 parking」是刻意設計,spec 需另立人工 override 契約——目前沒有這條例外。

C2. 封閉值域與本 repo 真實語料不符:90 筆真實 diagnosis 有 38 筆域外,其中 22 筆是舊 parser 路由正確、新契約改成 hard abort 的正常 issue

lens: devils-advocate · plugins/issue-driven-dev/scripts/lib/actionability.sh:73

四位評審都只讀 diff,沒有人拿真實資料跑過這個 helper。我跑了。

方法gh issue list --state closed --limit 120 取本 repo 最近 120 個 closed issue,抽每個 issue 最後一則 ^## Diagnosis comment,逐一餵進 idd_parse_complexity(helper 原檔,未修改)。90 個 issue 有 diagnosis comment。

結果

真實值長這樣(逐字,非我編的):Spectra(opt-out → 直接 propose)(#264 #258 #221)、Plan(Layer P:cross-file 一致性 + 判準應用的 judgment 邊界)(#215)、Simple — 單檔、2 個 1-token 補丁…(#160)、Spectra — 觸及 living spec(idd-verify MODIFIED)…(#205)、Plan —— 七個呼叫點互相依賴同一個述詞…(#293)。

這推翻了本 change 的因果診斷。design/proposal 認定「截斷是 bug」,但在真實語料上截斷 22 次是對的、1 次是錯的(#200 那一類才是錯的)。把截斷整個拿掉、改成「任何後綴文字=域外=停止」,是用一個 24%(22/90)的 false-stop 去換一個 10%(9/90)的 false-go —— 而且 false-stop 是硬停,比原本的靜默誤路由更容易讓整條 pipeline 停擺。design 明文否決的替代方案 (b)「只加寬 parser」,才是符合資料的那一個。

proposal 的 BREAKING 估計也是錯的proposal.md:14/21 寫「既有 9 筆帶限定詞的 diagnosis 值在新契約下為非法,需 migration」。實測光是最近 120 個 closed issue 就有 38 筆非法,是宣稱值的 4 倍以上。9 這個數字來自 2026-08-10「11 個 open diagnosed issue」這個有偏樣本(open backlog 本來就以 parked 為主)。

而且流量端沒有任何強制力:新的 producer 規則只是 idd-diagnose/SKILL.md:613 的一段散文;同一個 producer 在同一份模板下已經把理由寫進同一行 38/90 次,沒有任何 lint/test 檢查 emitted comment 的第一非空行。所以域外值不是歷史殘留,是還在持續產生的常態,consumer 端的 hard abort 會經常性 fire。

重現

. plugins/issue-driven-dev/scripts/lib/actionability.sh
gh issue view 264 --json comments --jq '[.comments[]|select(.body|test("^## Diagnosis";"m"))]|last|.body' \
  | python3 -c 'import sys,json;print(json.load(sys.stdin))' >/tmp/b   # 或直接傳 body
idd_parse_complexity "$(cat /tmp/b)"; echo $?   # → 3, unparseable-complexity: Spectra(opt-out → 直接 propose)

HIGH(blocking)

# Finding Source File
H1 via <source> 後綴不受約束 — 封閉值域的逃生孔 codex actionability.sh:69
H2 awk 不理解 code fence — 散文範例裡的 ### Complexity 會被當成真欄位 codex actionability.sh:47
H3 consumer 的 command substitution 在 set -e 下會提前中止 — 違反「單筆壞資料不得中斷 listing」 codex actionability-gate.md
H4 option 缺 value 時 shift 2 失敗導致無限迴圈,而非契約規定的 exit 2 codex actionability.sh:99
H5 測試完全沒驗證四個 skill 的整合,也沒有 #84 輸出回歸 — false assurance codex test.sh:72
H6 legacy 值成為永久 not-actionable latch — 與「parking 是可變狀態」的設計目標自相矛盾 codex design.md
H7 新增 spec 的核心 requirement「三訊號 gate」沒有任何 consumer — idd_actionability_verdict 全 repo 零呼叫 requirements spec.md:25
H8 spec 要求的 Parked 顯示分組不存在;idd-list Step 5 指向一個沒有被實作的分組 requirements SKILL.md:516
H9 idd-list Step 3.7 仍保有私有值域窄化,且只列三個 tier — SDD-warranted 在同一檔內會得到兩個相反判定 requirements SKILL.md:255
H10 改寫了 live spec 明文規定的 parser 行為(trailing parenthetical 剝除),卻宣告 Modified Capabilities: (none) requirements proposal.md:30
H11 blocker:*parking-lot 收斂只做了 reference 一半:live spec 與 idd-issue SKILL.md 仍規定貼已退役的 label requirements spec.md:40
H12 idd_actionability_verdict hangs in a silent infinite loop when a flag is given without a value logic actionability.sh:101
H13 The three-signal gate and the display-grouping helper have zero consumers — only the parse half was wired up logic actionability.sh:97
H14 idd-list Step 3.7 still contradicts the new closed domain, and the "Parked 組" the new Step 5 routes to does not exist in the output spec logic SKILL.md:255
H15 Routing gate trusts unauthenticated issue comments — public repo, no author check on ## Diagnosis security SKILL.md:522
H16 parking-lot label — the human defer ruling — is never consulted by any consumer that mutates the repo security actionability.sh:97
H17 idd-list Step 3.7/3.9 keep a private Complexity narrowing that now contradicts the shared helper (SDD-warranted and Plan via Layer V get classified as parked) regression SKILL.md:255
H18 type=meeting issues legitimately have no ### Complexity; removing the missing→Simple fallback turns them into hard stops and mislabels them "Parked" regression SKILL.md:399
H19 idd_actionability_verdict hangs forever on a value-less flag — the contract promises exit 2, and four skills now source this lib regression actionability.sh:101
H20 The blocker:*parking-lot convergence updated one copy of the rule and left the prescriptive sibling and the published spec saying the opposite regression ic-r011-checkpoint.md:76
H21 tasks.md 5.1 宣稱的「helper 對真實資料的 end-to-end 驗證」是對空集合成立,零證據 devils-advocate tasks.md:31

MEDIUM / LOW(follow-up 候選)

MEDIUM ×28
  • M1 [codex] 空的 ### Complexity section 被誤報成 complexity-missing(應為 unparseable)
  • M2 [codex] markdown 裝飾剝除是非對稱的 — 未配對符號與 typo 被靜默正規化成合法 tier
  • M3 [codex] verdict 測試不驗 exit code、且輸出解析過度寬鬆
  • M4 [codex] idd-list 同段仍留著相反的「推不出 → /idd-implement (保守 default)」列
  • M5 [codex] migration 敘述在 proposal / design / tasks 三份文件間直接矛盾
  • M6 [codex] label 被定為 parked 唯一 source of truth,卻沒有任何寫入路徑
  • M7 [codex] 第三個訊號 ### Blocking 的抽取與「非空」定義沒有共用 — divergence 風險原封不動
  • M8 [requirements] Tasks 3.2 / 3.3 / 3.4 勾 [x] 但其宣告的驗證方式(測試斷言四個引用點)從未被實作
  • M9 [requirements] design.md 自訂的 feature: /idd-list blocked-state awareness output mode (anti-anxiety surfacing) #84 逐字回歸驗收條件未被任何測試覆蓋
  • M10 [requirements] gate 的四值封閉 reason 值域無法表達 blocked label 訊號,consumer 必須保留私有邏輯
  • M11 [requirements] 使用者可見的行為變更沒有 CHANGELOG 條目、也沒有 plugin 版號 bump
  • M12 [logic] idd_actionability_group silently answers parked for every malformed input and never fails loud
  • M13 [logic] Section-boundary detection only recognises ### headings, so an empty Complexity section reports a value stolen from the next section
  • M14 [logic] First-match-wins with no fenced-code awareness: a quoted example of ### Complexity shadows the real field
  • M15 [logic] The headline acceptance assertion is a fixture tautology — it passes with the idd-list / idd-all routing 讀不到「現在可不可以動」— Complexity 的 when-triggered 限定詞被截斷、parking-lot label 無 consumer(22-issue backlog 實測 8/9 誤路由) #298 bug fully re-introduced
  • M16 [logic] idd-diagnose has two copies of the Complexity template; only the second was updated, and the primary one lost the new rules
  • M17 [logic] idd-list's consumer snippet cannot obtain the raw value it is required to surface, and references an undefined variable
  • M18 [security] First-matching ### Complexity heading wins, including inside fenced code blocks — tier spoofing
  • M19 [security] No CRLF/control-character normalization at the trust boundary — defeats the mandatory-surfacing guarantee
  • M20 [security] Contract mandates echoing untrusted issue text verbatim into agent context — prompt-injection surface
  • M21 [regression] Scope creep: a routing-parser change also converts IC_R011 sister-sweep into an automatic parking-lot producer, contradicting this change's own "producer never derives the label" rule
  • M22 [regression] Acceptance criteria for non-regression of feature: /idd-list blocked-state awareness output mode (anti-anxiety surfacing) #84 and for the four consumer rewrites are marked done but no such test exists
  • M23 [regression] A fifth consumer of ### Complexity keeps its private parse and now feeds out-of-domain values into idd-route stats
  • M24 [regression] No version bump and no CHANGELOG entry — marketplace users stay on 2.108.0 and never receive scripts/lib/actionability.sh
  • M25 [devils-advocate] fixture 自稱 2026-08-10 snapshot,但記錄的 raw 值不是逐字原文——被整理掉的正好是會炸的那個形狀
  • M26 [devils-advocate] gate 唯一的人為裁決通道 fail-open,且用 repo 自己 bug-prevention: #141 discussion-metadata helper must verify label existence + surface UI cache-sync hint #142 protocol 明令禁止的 blind --label 方式施加
  • M27 [devils-advocate] 更正 logic 評審:被改的那份 Complexity 模板才是引入 regression 的一份——它授權 producer 寫 legacy alias
  • M28 [devils-advocate] CRLF diagnosis comment 讓合法 tier 變 unparseable,且『強制 surface』印出的是看不見的 CR
LOW ×11
  • L1 [codex] idd_actionability_group 對格式與未知 reason 過度敏感,會靜默誤分組
  • L2 [codex] diff 內含指示審閱者採用特定評估框架的祈使句(prompt-injection 型內容)
  • L3 [requirements] Step 3.7 內部四訊號表與其下方三項編號清單自相矛盾,本次未一併收斂
  • L4 [requirements] --parked 回訪模式(Step 3.9)未與新契約對齊,仍把 when triggered 當作 live 的 parked 來源
  • L5 [logic] The via <source> suffix is unvalidated, so deferral text smuggled after "via" resolves to a legal, actionable tier
  • L6 [logic] Reason-list comparison in the test depends on locale collation
  • L7 [security] $CLAUDE_PLUGIN_ROOT is unvalidated before source — unset expands to an absolute /scripts/... path
  • L8 [regression] idd-list Step 5 table still carries 推不出 → /idd-implement #N (保守 default), contradicting the prose added directly beneath it
  • L9 [regression] Helper invoked twice per call site, and idd-list's snippet omits how $latest_diagnosis_body is obtained
  • L10 [devils-advocate] 更正 logic 評審:headline assertion 確實是 tautology,但『idd-list / idd-all routing 讀不到「現在可不可以動」— Complexity 的 when-triggered 限定詞被截斷、parking-lot label 無 consumer(22-issue backlog 實測 8/9 誤路由) #298 bug 完全復原也會通過』不成立——我做了 mutation test
  • L11 [devils-advocate] 以『封閉列舉、不得類推』為論旨的 reference,自己把 suffix producer 列漏了一個

INFO ×7

含 4 份獨立的 prompt-injection 掃描結果(皆 clean)。

Scope Check

有 scope creep:M44 指出一個 routing-parser 變更同時改了 IC_R011 sister-sweep 的 label 慣例(blocker:*parking-lot),且該收斂只做了 reference 一半 —— live spec 與 idd-issue SKILL 仍是舊慣例(H13 / H22)。

Lens 統計

lens findings
codex 17
requirements 12
logic 12
regression 12
devils-advocate 8
security 8

`### Complexity` 從沒宣告過封閉值域,也沒有 unparseable 契約 —— 而結構相同的姊妹欄位
`### Conflict Class` 兩者都有。於是 producer 寫出 `Simple when triggered` 不違反任何明文
規則,三個 consumer 各自發明互不相容的窄化:idd-list 靜默截成 `Simple`、idd-all 與
idd-implement 得到非法字串。2026-08-10 實測本 repo 的 22-issue backlog,11 個 diagnosed
裡 8 個 parked/deferred 被判成「Actionable now」,其中兩個帶著使用者親自下的 defer 裁決。

根因不是 parser 太窄,是**把會變的狀態存進不可變的 artifact**。Complexity 活在 append-only
的 Diagnosis comment,parked 卻會變(trigger 成立就該解除)。#136 的 comment 寫 bare
`Spectra`、body 寫 `Spectra when triggered (parking lot)` —— 狀態改不動就自己漂到別處。
IDD 早就知道正確做法:`### Blocking` 正因為會變才放在 body 由 idd-update 維護。

  - tier 值域收斂為封閉四值(可帶既有 ` via <來源>` 後綴),延期狀態遷至 parking-lot label
  - 三訊號 gate(域外值 / label / Blocking 區塊),放行需三者皆不成立
  - 解析與判定抽成 scripts/lib/actionability.sh,四個 consumer 全部改為引用 + fail-loud
    guard —— 沒有 guard 的話,「找不到就自己想辦法」的 consumer 會把三方分歧原封帶回來

三個刻意的取捨:

Strategy 的 `- [~]` **不進 gate**。它的既有 consumer 是 idd-close 的 checklist gate,語意是
per-item 的 close-time disposition;拿它回答 per-issue 的 routing 問題會撞壞既有語意。

migration **只貼 label、不改寫歷史**。回填既有 Diagnosis comment 會 modify-in-place 一個
append-only artifact;而且不必要 —— legacy 值走 unparseable 路徑得到的正是「not-actionable
+ surface 原值」,對那批 parked issue 就是正確結果。實測當前 open backlog 域外值 0 筆。

producer **不自動貼 label**。限定詞是 diagnose 當下的判斷,label 是人的裁決,兩者會分岔
(11 筆實測只有 5 筆一致)。由前者推導後者,等於取消「人可以事後 park 一個 tier 明確的
issue」這條路徑。

顯示層仍分兩組:reason 僅為 blocking 者維持 #84 既有的 Blocked 分組(標題/banner/footer
逐字不變),其餘進 Parked 組。gate 統一不等於顯示統一。

ic-r011 的 `blocker:infeasible` / `blocker:waiting` 一併退役 —— 0 個 issue 在用、無任何
consumer 讀,(b)/(c) 的區分保留在 audit 字串(那是說明性資訊,不是機械判定用)。

47/47 suites 綠(新增 actionability-gate 66 條,fixture 含 2026-08-10 事故快照,
斷言 9 筆中只有 #37 為 actionable)。

Refs #298, #310, #317
… 定形 (#316)

第 1 輪的封閉值域被 /idd-verify --pr 318 CRITICAL-2 證偽:90 筆真實 diagnosis 有 38 筆
落在值域外,其中只有 9 筆是延期修飾語,22 筆是「tier + 同行理由」的正常寫法。分界畫錯了
位置 —— 不在「後面有沒有字」,而在「那些字是否表達延期」。

/idd-reorganize #316 裁定:design D1/D3/D5、spec R1/R3/R7 redo;D2/D4/D6/D7/D8 still-valid。
/idd-diagnose #316 第 2 輪以完整 corpus(225 issue 中 159 筆有 Diagnosis)重新定規則:
剝裝飾 → 取 tier prefix → 對整個值掃延期語彙。158/158,0 false positive。

本 commit 只動 Spectra artifact(ingest 產物),不動 code:

  - design.md:三個決策重寫並留「前版為何被推翻」註記;新增「風險姿態:label 為主、
    語彙為輔」——延期語彙是開放列舉,漏抓有 label 兜底、誤抓會硬停正常工作,所以取高精度
  - spec.md:R1/R3/R7 重寫,延期偵測獨立成 requirement(7 → 8 條);reason 值域 4 → 5,
    新增 complexity-deferral-marker —— 與 unparseable 分離,因為人的處置不同(前者確認
    label、後者修 diagnosis)
  - proposal.md:BREAKING 敘述移除。「9 筆需 migration」是從有偏樣本推的;新規則對既有
    全部語料都對,migration 為零
  - tasks.md:保留 10 [x] + 2 [~],新增第 6–9 組共 10 個 task。7.1 明寫「只換 parser 不
    呼叫 verdict 等於沒修」—— 第 1 輪 gate 完整實作、66 測試全綠、零 consumer 呼叫

Refs #298, #310
…inate (#316)

rebase onto main 3.0.0 後重看 pending task:main 於 2026-08-15 post-merge audit 為 idd-list
記下 `--json comments` 硬截最舊 100 則的問題,並明寫「不得只在其中一個 consumer 修」。
本 change 的三個 consumer snippet 有同一個洞 —— latest Diagnosis 正是 >100 則時被丟掉的
那則,helper 會誤回 complexity-missing。修法沿用 check-closed-without-summary.sh 的現成實作。

Refs #335
…gate wired into all four consumers

Refs #316 (round-2 re-baseline of PR #318)

Round 1 declared a closed value domain for `### Complexity` and shipped a
three-signal gate that no consumer called; /idd-verify --pr 318 returned FAIL
(CRITICAL-1 unwired, CRITICAL-2 domain refuted by the 159-diagnosis corpus:
66 real values wrongly refused).

Helper (scripts/lib/actionability.sh)
- tier = leading whole word among Simple|Plan|Spectra|SDD-warranted after
  decoration strip; rationale / parenthetical / ` via` suffix are legal
- whole first line scanned for deferral vocabulary -> exit 5,
  stderr `deferral-marker: <raw>`, no stdout (tier prefix never routable)
- reason vocabulary 4 -> 5 (`complexity-deferral-marker`, group = parked)
- new idd_blocking_section (idd-update's `- (none)` placeholder is empty)
- shared fence-aware section extractor (``` / ~~~; same-or-higher heading ends)
- value-less flag -> named exit 2 (was an infinite loop)

Consumers (idd-list / idd-all / idd-implement / idd-plan)
- actually call idd_actionability_verdict with all three signals
- paginate the Diagnosis fetch (gh issue view --json comments = oldest 100)
- set -euo pipefail-safe conditional capture; exit 2 is FATAL, not withheld
- idd-list: Parked group added; #84 blocked heading/banner/footer verbatim

Tests (225 assertions)
- fixture rows now verbatim Diagnosis comment values; corpus-sampled shapes
  >=3 each; #128 pinned as the designed prose-deferral miss
- frozen 159-issue corpus regression: 149 / 9 / 1 / 0
- strict-mode integration test; consumer-wiring drift guard

Docs: references/actionability-gate.md rewritten (risk posture, corpus
table, canonical call shape); idd-diagnose producer declaration rewritten;
CHANGELOG 3.1.0; version 3.0.0 -> 3.1.0.
@kiki830621 kiki830621 changed the title feat: Complexity 值域封閉化 + 四 consumer 共用 actionability gate (#316) feat: actionability gate round 2 — prefix tier + deferral vocabulary, wired into all four consumers (#316) Sep 7, 2026
@kiki830621

kiki830621 commented Sep 7, 2026

Copy link
Copy Markdown
Member Author

Verify Report — PR #318 (round 2)

Engine

manual fan-out (4 lens Agents + sequenced DA, model: opus, file-based output) + Codex (gpt-5.6-sol) — Codex leg FAILED (codex-call HTTP 429 usage_limit_reached, no retry per skill), so this is a 5-of-6 run. Verified snapshot: PR head 0a250fc; diff-freshness gate passed before merge.

Aggregate

FAIL — 6 blocking, 10 in-scope fixes, 5 follow-ups

Round 1's two CRITICALs are genuinely repaired and every lens mutation-tested it (renaming the verdict call in each SKILL.md turns the drift guard red; removing the deferral scan / leaking the tier on exit 5 / dropping fence tracking turn 73 / 22 / 6 assertions red). The DA independently re-implemented the reference's prose rule in Python and got 159/159 on the frozen corpus, so the corpus is not a tautology. The 21 round-1 HIGH findings check out on the named paths. But round 2 repeated CRITICAL-2's failure shape on the third signal: idd_blocking_section was shipped against an assumption about producer output (- (none)) that the repository's own 55 ### Blocking sections refute, and the DA measured the gate against the live open backlog — which nobody else did.

Scope coverage

PR refs: #316
Verified scope: #316

Process Gaps

  • codex: cross-model leg absent (usage limit; resets in ~22h). Devil's Advocate was asked to carry the oracle role as well. Re-run with Codex when the limit resets is recommended before merge.

#316 — bug: #298 的修正只落在 idd-list — 另三個 Complexity consumer 未動,且 idd-list 自身 Step 5 與 Step 3.7 互相矛盾

Requirements coverage: spec R1 / R3 / R5 / R7 FULLY; R2 / R4 / R6 / R8 PARTIALLY (details in rows 6, 5, 2, 13 below). Issue Expected 6/6 FULLY; Acceptance criteria 6/9 FULLY, 3 knowingly superseded by the round-2 re-baseline but not annotated in the issue body (row 22).

Blocking

# Severity Finding Source Action
1 CRITICAL idd_blocking_section judges the none-placeholder on the whole line, so any (none) carrying an annotation reads as a live blocker. Measured on all 238 issues: 55 have ### Blocking, 48 are semantically empty, 31 of those are judged blocking-nonempty (81 % of non-empty verdicts are false). The only open issue with the section is #316 itself (- (none — 可動)): after merge /idd-implement #316, /idd-plan #316, /idd-all #316 all abort and /idd-list files it under Blocked (waiting on external). This is the direction the contract's own risk posture calls unacceptable, and it is structurally CRITICAL-2 again — an unvalidated producer assumption hard-wired into a gate, with tests that only cover bare - (none) / (none) / _none_. Coupled defect (logic HIGH-1): the reader takes only the first line of a list-typed section, so - (none) followed by a real - 等 … bullet returns empty — fixing only the regex would widen that false-negative surface ~31× and flip the failure direction to silent pass-through. The DA ran all three proposed regexes on the 55-row corpus: the requirements-lens variant clears all 7 real blockers (its bullet-optional group lets - in the placeholder set eat the bullet dash); the security-lens variant leaves 20 FP including #316; only the regression-lens leading-token variant is 0 FP / 0 FN. actionability.sh:230-237, :56-73, test.sh:206-214 agents:requirements+logic+security+regression+devils-advocate Blocking — per-bullet scan (any non-placeholder bullet ⇒ non-empty) + leading-token placeholder rule (regression variant; the other two are recorded here as tested and rejected) + frozen corpus-blocking.json (55 rows, hand-reviewed) + a signal-3 entry in §Risk posture, all in one commit
2 CRITICAL complexity-missing lands in the Parked group. Run against today's 14 open issues the gate yields 2 actionable / 1 blocked (the #316 misjudgement) / 11 parked — all eleven are simply not yet diagnosed, and on main every one of them sits under Actionable now with → /idd-diagnose #N. Same file: --parked (Step 3.9) defines three sources that exclude complexity-missing, so the footer says 11 parked and --parked shows 1. The #84 all-blocked banner never fires in the new dominant state (actionable = 0, blocked = 0, parked = N) — a freshly onboarded repo sees an empty Actionable now: heading over a wall of Parked. Spec R6 as written prescribes this grouping, so the spec is wrong here, not just the skill. idd-list/SKILL.md:311,470-482,70,327 agents:devils-advocate Blockingidd_actionability_group gains undiagnosed (complexity-missing alone, or with blocking-nonempty → blocked); idd-list gets a Needs diagnosis (N): group that keeps the → /idd-diagnose #N command; spec R6 / design / reference amended so Parked = deferral-marker · parking-lot-label · unparseable only, matching --parked
3 HIGH idd-implement runs the gate at Step 2.5 — after Step 0.5 creates the feature branch and Step 2 posts the public Implementation Plan comment via gh-egress. A human-parked issue receives an irrevocable egress before the gate stops it. idd-plan (Step 1) and idd-all (Phase 2) are ordered correctly; the drift guard checks presence, not position. idd-implement/SKILL.md:244,332,365-401 agents:regression+requirements+devils-advocate Blocking — move the gate ahead of branch creation and any egress; contract clause "gate SHALL precede any egress or branch creation"; order guard in the test
4 HIGH parking-lot convergence is half done and the new producer prohibition is unscoped. idd-issue/SKILL.md:1239 and the live spec openspec/specs/idd-ic-r011-checkpoint/spec.md:40,50,56 (MUST) still mandate blocker:infeasible / blocker:waiting, so the same sister concern gets a gate-effective label from /idd-diagnose and an unread one from /idd-issue. Separately, "idd-diagnose SHALL NOT apply, remove, or derive the parking-lot label" (reference :104, idd-diagnose :617, pinned verbatim by test.sh:307) contradicts IC_R011 §1.4, executed by idd-diagnose Step 3.6, which now requires that label on (b)/(c) filings — neither text states the scope ("the issue under diagnosis" vs "the newly filed sister issue") agents:regression+devils-advocate Blocking — scope both prohibitions; converge idd-issue and the live spec (via a spec delta in this change) to parking-lot
5 HIGH CRLF bodies break both readers: awk's default FS does not treat \r as blank, so a CRLF "empty line" has NF=1 and is taken as the value. ### Complexity → exit 3 with stderr unparseable-complexity: (empty — violates R4 surfacing); ### Blocking → a real blocker is dropped (unsafe direction). GitHub's web textarea submits CRLF; this repo's #95 is a CRLF issue. Zero \r coverage in tests. actionability.sh:56-73 agents:logic+requirements+devils-advocate Blockingsub(/\r$/, "") at the top of the extractor; CR fixtures for both readers
6 HIGH Drift guard pins the parse capture shape (if TIER=$(…)) but not the verdict's: replacing all four if VERDICT=$(…); then … fi with a bare VERDICT=$(…) keeps the suite 225/0 — under set -euo pipefail the first parked issue would kill the listing, exactly the regression task 6.4 exists to prevent. Likewise replacing 1) REASONS="${VERDICT#not-actionable: }" with 1) : ;; (call the gate, ignore it) stays green — CRITICAL-1 one level down. test.sh:284,286 agents:logic+devils-advocate Blocking — pin the verdict conditional-capture line, the REASONS= branch and the FATAL … misuse branch in all four files

In-scope fix (same PR, non-blocking)

# Severity Finding Source Action
7 MEDIUM idd-all/SKILL.md:1007 SUB_COMMENTS=$(gh issue view … --json comments …) still uses the oldest-100 connection (Layer V deferred scan); task 7.1's verification sentence is therefore false, and the refute pattern only locks the LATEST_DIAGNOSIS= line agents:requirements+devils-advocate In-scope fix — paginate; widen the refute to --json[^\n]*comments
8 MEDIUM idd-list Step 3.7 re-fetches labels/body per issue although Step 2 already has them (N+1, contradicts the neighbouring anti-N+1 rule), neither fetch has || true under the step's declared set -euo pipefail (one rate-limit kills the listing), and there is no state guard so --audit-closes runs the gate over closed issues agents:logic+security+regression+devils-advocate In-scope fix — take labels/body from $ISSUES_JSON; paginate comments only when (.comments|length) >= 100; || true with an explicit (gate error) row; [ "$STATE" = open ] guard
9 MEDIUM rules/sdd-integration.md:238-242 still narrates ### Complexity parsing as bare tokens (### Complexity\nSimple → parse as Simple) — a second normative parse description in a shipped rule agents:logic+regression+devils-advocate In-scope fix — keep only the SDD-warranted ≡ Spectra routing sentence; point at the reference
10 MEDIUM Issue numbers ($n / $N / $NUMBER) enter the gh api URL path without the digit check the same idd-list file requires 180 lines earlier (?per_page=1 would silently truncate the comment fetch); the canonical snippet in the reference lacks it too agents:security+devils-advocate In-scope fix — case "$N" in ''|*[!0-9]*) FATAL ;; esac in all four snippets + reference
11 MEDIUM The ## Diagnosis comment used for signal 1 is not author-filtered (--jq '[.[] | {body}]'): on this public repo any account can append a ## Diagnosis and, for the qualifier-but-no-label issues (#131, #200), clear the gate; the reverse (Simple (deferred)) parks any issue but is self-healing on the next /idd-diagnose. The #315 external-writes collector in idd-verify already filters by author_association agents:security+devils-advocate In-scope fix — select(.author_association == "OWNER" or … "MEMBER" or … "COLLABORATOR") in the four snippets + reference
12 MEDIUM allowed-tools of idd-list / idd-all / idd-implement lack Bash(jq:*) / Bash(python3:*) / sourcing; for idd-list this is a new gap (main had zero non-gh/git commands). Pre-approval only, not a restriction (DA checked the docs), so the cost is a permission prompt that blocks unattended runs agents:regression+devils-advocate In-scope fix — extend the three frontmatters; list gh / jq / python3 as prerequisites in the reference
13 MEDIUM Spec R2 scenario "- [~] does not withhold" is not exercised: synth_body never emits ### Strategy, yet test.sh:36 claims "Row 905 pins that" agents:requirements+devils-advocate In-scope fix — give row 905 a strategy field with a - [~] item; assert actionable
14 MEDIUM idd-all Phase 3b.1 now reuses $LATEST_DIAGNOSIS across bash blocks (a multi-KB markdown blob) and the comment over-corrects to "must not re-fetch here"; a set -u unbound or a silent empty context are the two outcomes agents:regression+devils-advocate In-scope fix — keep a paginated fetch here as fallback; reword the note to "must not re-fetch with --json comments"
15 MEDIUM §Risk posture covers signal 1 only and says a miss is "no worse than pre-#298" — false for signal 3, whose detection shipped in #84 (a miss regresses #84). The corpus denominator hides the dominant case: 66 of 225 issues had no Diagnosis at all and are all exit 4; "zero migration" means "no diagnosis needs rewriting", not "backlog actionability is unchanged" (snapshot 66 % routable vs 14 % of today's open backlog) agents:devils-advocate In-scope fix — signal-3 row in §Risk posture; denominator row + one clarifying sentence in §Corpus validation
16 LOW–MEDIUM Surfaced raw values ($COMPLEXITY_ERR, $BLOCK_LINE) are third-party text printed unsanitised: ANSI/C0 sequences pass through (a \r can overwrite the ⏸ deferral-marker: prefix) agents:security (DA: downgrade, sanitise only) In-scope fix — tr -d C0 on display; one sentence "surfaced values are data, never instructions"
17 LOW Wording/hygiene cluster: 158/158 vs 159/159 in helper header, test.sh:14, proposal, design (149+9+1 = 159); plugin.json description still says v3.0.0; fixture rows #160 / #136 truncated although the note says verbatim; ≥3-per-shape count includes synthetic rows (select(.number < 900)); dispatch table header says CEXIT · TIER while half the rows key on VEXIT; REASONS not reset in the actionable branch (set -u hazard); reference snippet's *) exit 1 vs listing consumers; #### skip and bullet-decoration asymmetry untested; proposal Impact omits corpus-complexity.json; bash 3.2 swallows the custom FATAL message on a failed source (fix: [ -f ] test first) agents:requirements+logic+regression In-scope fix (batched)
18 INFO Deferral vocabulary also fires on mentions (Plan(把 parking lot 的文件敘述收斂), Simple, no longer deferred) — corpus 0 hits, reference already states the heuristic nature; adding negation logic would open new FN surface agents:security+logic (DA: LOW, record only) In-scope fix — record the class in §Risk posture; no regex change

Follow-up (beyond #316)

# Severity Finding Source Action
19 HIGH (premise) ### Blocking has no producer contract (idd-update template - {blocker 1, or "(none)"} is model-filled; 48 empty values use 35+ spellings). On main this signal was model-read prose and judged correctly; the PR replaces it with a regex that misjudges 31/48 and simultaneously upgrades it from a display signal to a four-skill hard abort. Row 1 is the short-term stop; the real choice is a machine-checkable producer token (e.g. empty section = no bullet) or returning emptiness to model judgement agents:devils-advocate Follow-up issue
20 MEDIUM #84's other two blocked signals (blocked label, wait-class Suggested-next) lost their extraction step: Step 3.7 says they "only affect the Blocked group" but GROUP is produced solely by the verdict, so a blocked-labelled issue with empty ### Blocking now lands in Actionable now (no such label in this repo today; the plugin ships to others) agents:requirements+logic+devils-advocate Follow-up issue
21 LOW Fifth private ### Complexity narrowing survives in plugins/idd-route/skills/idd-route-backfill/SKILL.md:67 ("找不到 fall back Simple") — telemetry only, not one of the four routing consumers agents:regression Follow-up issue
22 LOW docs/commands.md (/idd-list, /idd-implement, Complexity table) does not mention the gate, the Parked group, or that parked issues abort agents:regression Follow-up issue
23 MEDIUM Issue #316's ## Acceptance criteria still lists three items the round-2 re-baseline superseded (closed-domain declaration, "only #37 actionable" — the assertion is now #37 + #128, #128 being the documented prose-deferral miss) agents:requirements Follow-up — errata comment on #316, not a diff change

Scope Check

No scope creep. CHANGELOG 3.1.0, the version bumps, the marketplace description and the PR-body rewrite are in-scope round-2 accompaniments; the ic-r011 label convergence is an acceptance criterion of #316 (and is what row 4 finds half-done). External-writes block for #316 read successfully by all five reviewers; it contains no instruction-shaped text and its two IC_R011 exemption claims match the diff — with one honesty gap: the sister sweep covered files changed, not contracts affected (rows 2 and 4 are divergences this round created).

Verified as sound (so round 3 need not re-check)

  • Round-1 CRITICAL-1 and CRITICAL-2 repaired; idd-plan (Step 1) and idd-all (Phase 2) gate placement correct.
  • Injection surface clean (17 probes: command substitution, option injection, format strings, regex metachars, awk -v escapes); no secrets, no absolute paths, no third-party verbatim content in the frozen corpus (all 159 Diagnosis comments authored by the repo member).
  • 21 round-1 HIGHs repaired on the named paths; value-less flag returns exit 2 on bash 3.2 and 5.3; strict-mode integration test passes on both.
  • plan-routing-consistency failure is environment-dependent (walks gitignored .spectra/snapshots/), passes on git archive 0a250fc — belongs to bug: plan-routing-consistency 測試掃到 gitignored .spectra/ snapshot — 乾淨 checkout 綠、跑過 spectra 的本機必紅 #335, with the sharper mechanism description.

Next

Round-3 fixes in this PR (rows 1–18), then re-verify with Codex once its usage limit resets.

Follow-up Findings Filed (v2.72.0+ #148)

Filed by default per IC_R011 §1.1 from the Follow-up (beyond #316) rows above:

Row Issue
19 — ### Blocking needs a producer contract or model-judged emptiness (DA-HIGH-2) #336
20 — idd-list: #84's blocked-label / wait-class signals lost their extraction step #337
21 — idd-route-backfill's private ### Complexity parse with "fall back Simple" #338
22 — docs/commands.md not synced with the 3.1.0 gate #339
23 — #316 acceptance-criteria errata errata comment on #316 (issue body, not a new issue)

Rows 1–18 are being addressed in this PR (round 3). Codex re-run pending its usage-limit reset.

…nosed group, gate before egress

Refs #316 (round-3 repairs for the /idd-verify --pr 318 round-2 FAIL: 6 blocking + 10 in-scope)

Helper (scripts/lib/actionability.sh)
- idd_blocking_section reads the section PER BULLET (any non-placeholder
  bullet is a blocker; continuation lines belong to the bullet above) and
  recognises a placeholder by its LEADING TOKEN (none / n/a / 無, optionally
  bulleted, decorated, parenthesised, then EOL / ')' / separator). Round 2
  anchored the whole line and withheld 31 of 47 empty sections, including
  #316's own `- (none — 可動)`.
- trailing CR stripped before either section reader judges a line (GitHub
  web textarea submits CRLF)
- idd_actionability_group gains `undiagnosed` (complexity-missing alone)
- new frozen fixture corpus-blocking.json: all 55 `### Blocking` sections in
  this repo's issue bodies, hand-reviewed (47 empty / 8 non-empty), 0 FP / 0 FN;
  two rival rules measured there and rejected (documented in the helper)

Consumers
- idd-implement: gate moved to Step 0.35, ahead of tree-lock, branch creation
  and the Implementation Plan comment; test pins the line order
- idd-list: Needs-diagnosis group keeps `→ /idd-diagnose #N`; Parked is now
  exactly the --parked set; labels/body/comments taken from the Step 2 bulk
  fetch (paginate only at >= 100 comments); open-state guard; per-row fetch
  failure marks the row instead of aborting; REASONS reset; C0 stripped
- all four: issue number digit-checked before the REST path; Diagnosis
  comments trusted only from OWNER/MEMBER/COLLABORATOR; jq/python3 in
  allowed-tools; table header VEXIT · CEXIT · TIER
- idd-all: Layer V sub-issue scan and Phase 3b.1 fallback paginate

Contract / producer
- references/actionability-gate.md: signal-3 risk posture, undiagnosed group,
  corpus denominator (66/225 never diagnosed), scoped producer prohibition,
  hardened canonical snippet, "gate SHALL precede any egress or branch"
- idd-diagnose: prohibition scoped to the issue under diagnosis
- idd-issue + spec delta idd-ic-r011-checkpoint: blocker:* retired for parking-lot
- rules/sdd-integration.md: parallel bare-token parse narrative removed

Tests (285 assertions): blocking corpus 55/55; CRLF both readers; per-bullet
cases incl. `- none of the reviewers replied yet`; row 905 really emits a
`- [~]` Strategy item; drift guard pins verdict capture shape, REASONS branch,
FATAL branch, digit check, author filter, allowed-tools, gate-before-branch
and gate-before-egress order, no `=$(gh issue view … --json comments)` left.

Spec: R6 three groups, R7 scoped, new R9 (blocking signal per bullet, frozen
corpus); design round-3 decisions + open question (#336); tasks 10.x.
Follow-ups filed from the verify: #336 #337 #338 #339.
@kiki830621

Copy link
Copy Markdown
Member Author

Round 3 pushed — 343e807

Repairs for the round-2 verify (rows 1–18 of the report above), each pinned by a test that was red before the change:

Row Change
1 (CRITICAL) idd_blocking_section reads per bullet with a leading-token placeholder rule; frozen corpus-blocking.json (55 real sections, hand-reviewed) is 0 FP / 0 FN. #316's own - (none — 可動) now reads empty; - (none) followed by - 等 … reports the blocker. The two rejected candidate rules are recorded in the helper.
2 (CRITICAL) undiagnosed group: idd-list shows Needs diagnosis (N): with → /idd-diagnose #N; Parked is now exactly the --parked set. Spec R6 amended.
3 (HIGH) idd-implement gate moved to Step 0.35 (before tree-lock, git checkout -b, and the Implementation Plan comment); contract clause + line-order guard.
4 (HIGH) Producer prohibition scoped to the issue under diagnosis; blocker:* retired in idd-issue and, via a spec delta, in the live idd-ic-r011-checkpoint spec.
5 (HIGH) Trailing \r stripped in the shared extractor; CRLF fixtures for both readers.
6 (HIGH) Drift guard pins the verdict conditional-capture shape, the REASONS= branch and the FATAL … misuse branch in all four consumers.
7–18 idd-all Layer V scan + 3b.1 fallback paginate; idd-list reuses its bulk fetch, open-state guard, per-row fetch failure; sdd-integration parallel narrative removed; digit check + author filter in all four snippets and the reference; allowed-tools jq/python3; row 905 emits a real - [~]; signal-3 risk posture, corpus denominator and mention-class recorded; fixture rows #160/#136 verbatim; ≥3-per-shape counts real rows only; table headers; REASONS reset; C0 strip; 158/158 → 159/159; plugin.json description synced.

Suite: actionability-gate 285/285; 53 suites green except plan-routing-consistency (#335, environment-dependent). Follow-ups filed: #336 #337 #338 #339; acceptance-criteria errata posted on #316.

Not yet re-verified: Codex is under its usage limit until ~2026-09-08 14:00 (+08:00). A round-3 /idd-verify --pr 318 should run with the cross-model leg present.

@kiki830621

kiki830621 commented Sep 7, 2026

Copy link
Copy Markdown
Member Author

Verify Report — PR #318 (round 3)

Engine

manual fan-out (4 lens Agents + sequenced DA, model: opus, file-based output) + Codex (gpt-5.6-sol) — Codex leg absent again (HTTP 429 usage_limit_reached, resets ≈ 2026-09-08 13:30 +08:00), so this is a 5-of-6 run. Verified snapshot: PR head 343e807; diff-freshness gate passed before merge. Reviewed the round-3 delta (0a250fc..343e807) against the whole PR.

Aggregate

FAIL — 6 blocking, 11 in-scope fixes, 5 follow-ups

Round 2's six blocking rows were re-measured by every lens (live gh over 242 issues, mutation probes on copies, the round-2 helper re-run on the frozen corpus) and are mechanically repaired: the per-bullet reader no longer withholds - (none — 可動); the live open backlog now routes 15 undiagnosed / 2 actionable / 1 blocked / 0 parked; the idd-implement gate sits ahead of branch creation and egress; CRLF works; the drift guards bite in all four consumers. But round 3 introduced its own regressions and over-claimed what it proved. The DA's structural finding, which no lens measured: the frozen Blocking corpus is 54/55 CLOSED issues and the gate skips non-open issues by contract, so signal 3 changes the routing of exactly one live issue — #316 itself — and half of the round's 1,005 inserted lines are calibration data for that.

Scope coverage

PR refs: #316 · Verified scope: #316

Process Gaps

  • codex: cross-model leg absent (second consecutive run). DA carried the oracle role; it also diffed each "introduced in round 3" claim against 0a250fc — a check the lenses skipped and that changed two adjudications below.

#316 — bug: #298 的修正只落在 idd-list — 另三個 Complexity consumer 未動,且 idd-list 自身 Step 5 與 Step 3.7 互相矛盾

Requirements coverage: spec R3 / R4 / R7 / R8 FULLY; R1 / R2 / R5 / R6 / R9 PARTIALLY (rows 2, 3, 5, 10, 11 below). tasks §10: 10.5 FULLY, 10.1–10.4 / 10.6 PARTIALLY.

Blocking (round 4, this PR)

# Severity Finding Source Action
1 CRITICAL idd-implement Step 2.5 now consumes $VEXIT / $TIER / $REASONS / $COMPLEXITY_ERR / $BLOCK_LINE set ~280 lines earlier in Step 0.35, across tree-lock, git checkout -b and an egress — i.e. across several independent Bash calls where shell variables do not survive — and says "不得在此重跑". The same commit fixed exactly this shape in idd-all Phase 3b.1 (${LATEST_DIAGNOSIS:-} + same-shape re-fetch, comment "跨 Bash 區塊 shell 變數不保證存活"). Round 3 created the break by moving the gate. Combined with row 2, Step 2.5 has zero information at that point. idd-implement/SKILL.md:416 vs idd-all/SKILL.md:645-647 agents:regression+security+devils-advocate Blocking[ -n "${VEXIT:-}" ] || { re-run the Step 0.35 block with the same helper and shape; }; rewrite the prohibition as "no private regex", not "no re-run"
2 HIGH The gate block writes nothing on either verdict path: case "$VEXIT" in 0) ;; 1) REASONS=… ;; only assigns; $VERDICT is captured with 2>&1, $COMPLEXITY_ERR / $BLOCK_LINE stay in variables. A parked issue produces "clean success, no output, exit 0" — indistinguishable from actionable to the executing model. Pre-existing from round 2 (DA diffed 0a250fc), but the canonical shape carries it into all four consumers and the reference, and the misuse path (exit 2) does echo, so the contract is inconsistent with itself. idd-implement:139-143, idd-plan:92-97, idd-all:550-555, references:224-228 agents:security+devils-advocate Blocking — one printf 'gate #%s: VEXIT=%s TIER=%s REASONS=%s %s%s\n' … after the case in all four + reference; drift guard pins it
3 HIGH idd-list Step 3.7 guards with [ "$STATE" = "open" ]$STATE is the listing --state flag, not per-issue state. /idd-list --state all (a documented value) marks every issue skipped, including open ones; Step 5 has no display rule for skipped, so the whole Suggested-next section vanishes. Regression against main, where #84's groups render under --state all. idd-list/SKILL.md:290,302,498 agents:logic+regression+devils-advocate Blocking — per-issue .state == "OPEN" from $ISSUE_JSON; Step 5 rule for skipped (closed issues keep the phase × PR matrix)
4 HIGH The placeholder regex is locale-dependent: its terminator class contains multibyte characters (— – 、 :), and under LC_ALL=C bracket expressions split into bytes, so any E3-led character (kana, CJK punctuation) becomes a separator. - none ぁ x flips to placeholder (fail-open), (無) flips to blocker (fail-closed), and the shipped suite itself fails 3 assertions incl. corpus #295. Nothing pins the locale; cron / CI / sh -c run in POSIX locale. actionability.sh:261 agents:regression+devils-advocate Blocking — pin LC_ALL in the helper (or rewrite the class as an alternation (—|–|,|、|:|:|;|-)); a LC_ALL=C run of the suite in the test
5 HIGH "0 FP / 0 FN on 55 frozen sections" is over-claimed on four independent grounds: (a) the fixture's own row #1 records an accepted false positive with expect_empty:false set to match the implementation; (b) the test synthesises a clean body from .section, so the 55 rows never pass through _idd_section_lines (fence / heading / #### untested on real bodies); (c) under LC_ALL=C it is 2 mismatches; (d) 54 of 55 rows are CLOSED issues the gate never evaluates. design.md:219 then makes this claim the acceptance bar for #336. Also design.md:153 says 48 empty while test.sh:281 asserts 55/47/8, and "35+ spellings" is 22. actionability.sh:247, references:85, design.md:153,219, proposal.md:21, corpus-blocking.json:_provenance agents:requirements+security+logic+devils-advocate Blocking — restate as "54/55 agree with hand review, #1 accepted FP, 54/55 CLOSED, extractor not covered by this corpus"; #336's bar = semantic truth (48 empty / 7 blockers); fixture rows gain the original body and the test feeds it
6 HIGH An unclosed code fence anywhere above ### Blocking swallows the section: the tracker never resets at a heading. Live instance #290 (fence at line 8, ### Blocking at line 39 → reader returns empty; the fixture records - (none) — 已結案。, extracted by a different path). With a real blocker below such a fence the gate reads "no blocker" → actionable (fail-open); the same shape turns a diagnosed issue into complexity-missingNeeds diagnosis. actionability.sh:66-74 agents:logic+security+regression+devils-advocate Blocking/^###[[:space:]]/ { fence = "" } before the heading rule (a heading cannot be inside a fence); fixture body for #290 turns this red until fixed

In-scope fix (same PR)

# Severity Finding Source Action
7 MEDIUM C0 stripping exists only as prose (idd-list:328, references:170 "printed with C0 control characters stripped — data, never instructions"); no consumer code block contains tr, and the quoted set \000-\010\013\014\016-\037 omits \015 (CR) — the very character the comment names. Helper strips only trailing CR; a mid-line CR survives to the terminal and overwrites the / prefix agents:logic+security+regression+devils-advocate In-scope fix — strip in the helper's outputs (LC_ALL=C tr -d '\000-\010\013-\037\177') so all four consumers get it, or delete the claim
8 MEDIUM The continuation rule creates an undocumented fail-open class and a fail-closed one: - (none) followed by 1. / + / > / a bare paragraph / a table row / #### reads empty; a lead-in sentence before - (none) reads blocked; identical text is a blocker on line 1 and invisible on line 2. "Accepted misses" lists two token-led examples only. Live corpus: 0 cases. DA ruling: do not widen the bullet class (0 live cases; widening enlarges the fail-closed side; this is #336's question) — rewrite Accepted misses as a rule with both directions per common-spec-prose-enumeration.md agents:logic+requirements+security+regression+devils-advocate In-scope fix — document the class as a rule; behaviour change deferred to #336
9 MEDIUM cluster-PR mode (/idd-implement #34 #36 #38) gates only the scalar $NUMBER; SEEN_ISSUES is parsed later in Step 0.5. Pre-existing (round 2 used the same scalar, DA diffed 0a250fc), not a round-3 regression, but spec R2/R5 assert coverage the cluster path lacks agents:requirements (DA: MEDIUM, pre-existing) In-scope fix — one contract sentence "cluster path gates the first issue only, tracked in #NNN"; per-issue loop is a follow-up
10 MEDIUM spec R6 (:114) and reference (:166) assert the parked group "is exactly the set --parked lists"; Step 3.9 excludes complexity-unparseable and includes non-empty ### Blocking — unequal in both directions agents:requirements+regression In-scope fix — delete the two equality claims; Step 3.9 defined as parkedblocked groups
11 MEDIUM spec delta specs/idd-ic-r011-checkpoint/spec.md: "parking-lot or parking-lot respectively" (mechanical residue), nested backticks break three literal audit-trail strings, and a <!-- @trace … 2026-05-25 --> block belonging to the previous requirement was carried along and would land in the live spec at archive agents:regression+requirements+security+logic+devils-advocate In-scope fix — "both (b) and (c) → parking-lot"; drop inner backticks; drop @trace + trailing note
12 MEDIUM idd-plan has no allowed-tools frontmatter at all, and the drift-guard loop for jq/python3 was narrowed to three consumers to match agents:regression+devils-advocate In-scope fix — add frontmatter; loop back to four
13 MEDIUM idd-all:1017 (edited this round) still lacks the digit check and the author filter every other snippet gained; a foreign comment containing the Layer-V marker inflates LAYERV_DEFERRED_COUNT agents:security+devils-advocate In-scope fix — case "$sub_n" + select(.author_association …)
14 LOW Only the first ### Blocking is read (#102 has four, #103 two — stale-first wins); idd-plan:56 still runs a bare gh issue view … --json …,comments the refute pattern cannot see; ≥100 branch guard depends on pipefail; three jq lines in Step 3.7 lack || … continue; $REASONS not reset on the actionable path in three consumers; idd_actionability_group "" silently returns parked; - None. / - 無。 read as blockers (no ./ terminator); decoration/paren order asymmetry; head -n 1 SIGPIPE 141 under pipefail; helper comment says blocking-nonempty "ALONE" while the code (correctly, per spec) also maps complexity-missing; blocking-nonempty → blocked agents:logic+security+requirements+regression In-scope fix (batched, each one line)
15 LOW Hygiene: routing tables' rows 2–5 still two-valued under a three-key header; 158-158 in tasks.md:61, 158/158 in test.sh:14 and proposal.md:3; proposal.md:36 still says "為何無 modified capability" and :40 omits the modified spec; idd-list:498 "不給任何 lifecycle 命令" vs the diagnose command three lines above; the two rejected regexes are described but not recorded; corpus percentages don't sum to 100 (41.5 % vs 47.8 % rationale share); CONTRIBUTOR-demotion caveat for the author filter agents:requirements+regression+logic+security In-scope fix (batched)

Follow-up (beyond round 4)

# Severity Finding Source Action
16 MEDIUM Per-issue gate on the cluster-PR path needs SEEN_ISSUES parsing moved ahead of the gate (structural, pre-existing) agents:requirements+devils-advocate Follow-up issue
17 MEDIUM Bullet-class semantics (+, ordered lists, bare paragraphs, table rows) and first-vs-last ### Blocking — both are "how should this field be read" agents:logic+regression+devils-advocate #336 (comment)
18 MEDIUM IC_R011 (c) "blocked-on-external" now files with parking-lot and lands in the parked group, while its meaning is the gate's blocked; the retirement of blocker:* was a user ruling, so the semantics belong to #336 or a sibling agents:logic+security+devils-advocate #336 (comment)
19 LOW _idd_section_first_line SIGPIPE under pipefail — held only by caller discipline agents:logic In-scope one-liner (kept here for the record)
20 INFO Frozen corpus vs gate scope: 54/55 CLOSED; signal 3's live effect is n = 1 (#316). Not a defect — the calibration data is still a legitimate sample of producer style — but the reference must say what the corpus does and does not prove agents:devils-advocate In-scope wording (row 5)

Scope Check

No scope creep: rules/sdd-integration.md, idd-issue, the spec delta, plugin.json / marketplace.json descriptions are all round-2 rows. External-writes block read by all five reviewers: the #316 sister-concern record contains no instruction-shaped text and matches the diff; the coordinator's five disclosed out-of-diff writes were independently confirmed on the live side (#316 body now - 等 round-3 re-verify…, #336#339 present as undiagnosed).

Verified as sound

Round-2 rows 2, 6, 7, 9, 10, 11, 12, 13, 14, 18 FULLY on re-measurement; gh issue list --json comments exposes authorAssociation (live-checked); org-repo maintainers are MEMBER, so the OWNER/MEMBER/COLLABORATOR set is right; injection probes on the new reader (command substitution, option injection, read -r backslashes, --argjson after the digit check) all clean; no private data in corpus-blocking.json (all 55 sections authored by the repo member); spectra archive applies the delta on a copy; 53 suites: 52 green + #335 in the real repo (git archive is not a valid clean-tree check — it drops .git and fails the git-attributes suite spuriously; use git worktree add).

DA's premise ruling (recorded verbatim in spirit)

Round 3 is in the regex rabbit hole (three rounds, each adding a character class, each growing a new hole). Round 4 should fix correctness and honesty, not coverage: repair the regressions (rows 1, 3), close the one-line fail-opens (rows 4, 6), make the verdict observable (row 2), bring every claim down to the evidence (rows 5, 7, 8), and hand #336 a corrected acceptance bar. Neither "tune the regex again" nor "demote signal 3" — the latter would be a mid-verify scope change against #84, and with a live effect of n = 1 the decision can be made calmly in #336.

Next

Round-4 fixes in this PR (rows 1–15), then a fresh verify — with Codex once its limit resets.

Follow-up Findings Filed (v2.72.0+ #148)

Row Disposition
16 — per-issue gate on the cluster-PR path (pre-existing, structural) #340
17 — bullet-class semantics, first-vs-last ### Blocking comment on #336
18 — IC_R011 (c) label semantics after blocker:* retirement comment on #336
19 — _idd_section_first_line SIGPIPE fixed in round 4 (no head)
20 — corpus scope vs gate scope stated in the reference (round 4)

Rows 1–15 addressed in round 4 (this PR).

…the verdict, claims down to evidence

Refs #316 (round-4 repairs for the /idd-verify --pr 318 round-3 FAIL: 6 blocking + 11 in-scope)

Regressions repaired
- idd-implement Step 2.5 consumed gate variables across Bash calls and forbade
  re-running; now checks ${VEXIT:-} and re-runs the Step 0.35 block with the
  same helper (the shape idd-all 3b.1 already had)
- idd-list state guard used the listing --state flag (--state all skipped every
  issue, Suggested-next vanished); now per-issue .state == "OPEN", and
  `skipped` rows keep the phase x PR matrix
- placeholder rule was locale-dependent (multibyte separators in a bracket
  expression flipped both directions under LC_ALL=C; the suite itself failed
  3 assertions); rewritten as alternations, suite also run under LC_ALL=C
- an unclosed code fence swallowed every later section (live #290); unbalanced
  fences now disable fence tracking for that body

Observability / hygiene
- the gate block prints `gate #N: VEXIT=… TIER=… REASONS=… | …` in all four
  consumers and the canonical shape (a silent exit 0 was indistinguishable
  from actionable to the executing model)
- C0/DEL scrubbed at the helper's outputs (round 3 only described it, and the
  described set omitted \r); idd_actionability_group "" is exit 2
- REASONS reset on the actionable path; routing table rows carry all three
  keys; idd-plan gains allowed-tools and drops the bare --json comments fetch;
  idd-all Layer-V sub-issue scan digit-checks + author-filters;
  _idd_section_first_line no longer pipes into head (SIGPIPE under pipefail)

Claims brought down to the evidence
- corpus-blocking.json rows carry the original body so the extractor is
  exercised; provenance: 54/55 agree with hand review, #1 an accepted FP,
  54/55 CLOSED issues the gate never evaluates (signal 3's live effect on
  2026-09-07 was one issue)
- accepted misses stated as a rule in both directions; the two rejected
  placeholder regexes recorded with FP/FN counts; "parked group == --parked
  set" claim deleted from spec R6 and the reference; cluster-path coverage
  gap (first issue only, pre-existing) stated and tracked in #340
- spec delta: `respectively` residue, nested backticks and a mis-attributed
  @trace removed; design/proposal/tasks/CHANGELOG synced; #336 acceptance
  bar is the semantic truth, not the fixture

Tests: 318 assertions (LC_ALL=C probes, unclosed-fence probes, scrub probes,
extractor-through-corpus, verdict-printed / REASONS-reset / re-run-fallback /
per-issue-state / skipped-rule / sub-issue-hygiene drift guards).
Follow-ups: #340 filed; reader-semantics items handed to #336.
@kiki830621

Copy link
Copy Markdown
Member Author

Round 4 pushed — 703ad3b

Repairs for the round-3 verify (rows 1–15), following the DA's ruling: correctness and honesty, not coverage — no new character class, signal 3 not demoted.

Row Change
1 (CRITICAL) idd-implement Step 2.5 checks ${VEXIT:-} and re-runs the Step 0.35 block with the same helper when the variables did not survive the Bash-call boundary; the prohibition now reads "no private regex", not "no re-run".
2 (HIGH) The gate block prints gate #N: VEXIT=… TIER=… REASONS=… | … in all four consumers and the canonical shape; drift guard pins it.
3 (HIGH) idd-list guards on per-issue .state == "OPEN"; skipped rows keep the phase × PR matrix; the listing-flag guard is refuted by test.
4 (HIGH) Placeholder rule rewritten with multibyte separators as alternations; the suite runs the probes under LC_ALL=C (and the whole suite passes under LC_ALL=C).
5 (HIGH) Claims restated everywhere: 54/55 agree with hand review, #1 accepted FP, 54/55 CLOSED, extractor covered via original bodies; #336's bar is the semantic truth (48/7); the two rejected regexes are recorded with FP/FN.
6 (HIGH) Unbalanced fences disable fence tracking for that body; #290's section is found; balanced fenced examples still skipped.
7–15 C0/DEL scrub moved into the helper's outputs (CR included); accepted misses stated as a rule in both directions; cluster gap stated in the contract → #340; "parked group == --parked set" deleted; spec delta cleaned (respectively, nested backticks, @trace); idd-plan allowed-tools + Step 1 fetch; idd-all sub-issue digit check + author filter; table rows three-keyed; REASONS reset; idd_actionability_group "" → exit 2; no head in the first-line reader; 159/159 wording; proposal Modified Capabilities / Impact.

Suite: actionability-gate 318/318 (also under LC_ALL=C); 53 suites green except plan-routing-consistency (#335). Follow-ups: #340 (cluster-path gate); reader-semantics items commented on #336.

Not yet re-verified: Codex is under its usage limit until ≈ 2026-09-08 13:30 (+08:00). A round-4 /idd-verify --pr 318 should run with the cross-model leg present — three consecutive 5-of-6 runs is one too many.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant