feat: Complexity 值域封閉化 + 四 consumer 共用 actionability gate (#316) - #318
feat: Complexity 值域封閉化 + 四 consumer 共用 actionability gate (#316)#318kiki830621 wants to merge 1 commit into
Conversation
`### 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
Verify Report — PR #318Enginepai-ensemble 2.20.0 (canonical #207) — 4 IDD lenses + DA + Codex (gpt-5.6-sol), model: opus AggregateFAIL — 2 CRITICAL, 21 HIGH blocking; 28 MEDIUM / 11 LOW follow-up Frozen snapshot Scope coveragePR refs: #37 #84 #136 #292 #298 #306 #309 #310 #316 #317 — 8 已 CLOSED/MERGED(敘述脈絡)、#317 為獨立 follow-up(PR body 自述不在本次修復範圍) #316 — bug: #298 的修正只落在 idd-listRequirements coverage: 部分達成,但兩個 CRITICAL 使核心宣稱未成立。 CRITICAL(blocking,兩者互相獨立)C1. 三訊號 gate 從未接進四個 consumer — parking-lot / Blocking 仍被繞過lens: Codex 的首要發現,也是它給出「建議修改後再合併」的理由: 問題:
對照 spec( 觸發情境: 後果:parser 回 exit 0,consumer 照 tier routing 直接執行。fixture 自己描述的 建議:(1) 每個 consumer 讀 labels 與 body 的 Blocking section;(2) 一律呼叫 C2. 封閉值域與本 repo 真實語料不符:90 筆真實 diagnosis 有 38 筆域外,其中 22 筆是舊 parser 路由正確、新契約改成 hard abort 的正常 issuelens: 四位評審都只讀 diff,沒有人拿真實資料跑過這個 helper。我跑了。 方法: 結果:
真實值長這樣(逐字,非我編的): 這推翻了本 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 估計也是錯的: 而且流量端沒有任何強制力:新的 producer 規則只是 重現: . 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)
MEDIUM / LOW(follow-up 候選)MEDIUM ×28
LOW ×11
INFO ×7含 4 份獨立的 prompt-injection 掃描結果(皆 clean)。 Scope Check有 scope creep:M44 指出一個 routing-parser 變更同時改了 IC_R011 sister-sweep 的 label 慣例( Lens 統計
|
這個 PR 做什麼
把
### Complexity的值域封閉化,並讓四個 routing consumer 共用同一份 actionability 判定。#298的修正(PR #309 / #306)只落在idd-list,其餘三個 consumer 未動;且idd-list自身Step 5 仍文載會截斷的 regex,與新增的 Step 3.7 直接矛盾。本 PR 承接剩餘缺口(#316)。
根因
不是 parser 太窄,是把會變的狀態存進不可變的 artifact。
### Complexity活在 append-only的 Diagnosis comment,但 parked 會變(trigger 成立就該解除)。
#136的 comment 寫 bareSpectra、body 寫Spectra when triggered (parking lot)—— 狀態改不動就自己漂到別處去了。IDD 早就知道正確做法:
### Blocking正因為會變才放在 body 由idd-update維護。本 PR 把同一條推論套用到 parked 上。
主要改動
scripts/lib/actionability.shidd_parse_complexity/idd_actionability_verdict/idd_actionability_groupreferences/actionability-gate.mdparallel-orchestration.md的 Conflict Class 契約對稱scripts/tests/actionability-gate/skills/idd-list/SKILL.mdskills/idd-all/SKILL.mdskills/idd-implement/SKILL.mdskills/idd-plan/SKILL.mdskills/idd-diagnose/SKILL.mdparking-lotlabelreferences/ic-r011-checkpoint.mdblocker:*退役,收斂為parking-lot三個刻意的取捨
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 當下的 AI 判斷,label 是人的裁決,兩者會分岔
(2026-08-10 的 11 筆實測只有 5 筆一致)。由前者推導後者,等於取消「人可以事後 park 一個
tier 明確的 issue」這條路徑。
行為變更(非純重構,請留意)
idd-implement原本_(missing / unclear)_ → 預設當 Simple,現在改為停止並要求先跑/idd-diagnose。那個寬鬆預設正是契約禁止的「代 user 挑 tier」。idd-all/idd-plan對域外值從未定義行為改為明確 abort + surface 原值。#84的 Blocked 分組行為不變 —— gate 統一但顯示分兩組,Blocked 的標題/banner/footer逐字保留。
驗證
actionability-gate66 條)Plan via Layer V、Spectra via hard-gate (sdd_bias))回歸覆蓋Spectra
change
add-actionability-gate—— proposal / design / spec / tasks 皆在本 PR 內。10/12 tasks 完成,2 個標
- [~](migration 類別在 live backlog 為空集合,附實測理由)。過程中另外發現、已獨立開案
idd-plan對idd-all的 Plan routing 描述仍是舊版全稱句(#292修正的殘留)Refs #298, #310, #316, #317