Skip to content

fix: spectra-archive-post-ic linked-issue escape hatch + prose detection (#170) - #171

Merged
kiki830621 merged 2 commits into
mainfrom
idd/170-post-ic-linked-issue
May 31, 2026
Merged

kiki830621 merged 2 commits into
mainfrom
idd/170-post-ic-linked-issue

Conversation

@kiki830621

@kiki830621 kiki830621 commented May 31, 2026 •

Copy link
Copy Markdown
Member

Refs #170

Summary

Fixes the spectra-archive-post-ic.sh double-failure surfaced when archiving #167: detection missed the prose-only issue #N reference AND --linked-issue rejected on an empty candidate set — so both the auto-detector and its manual override failed together exactly when the fallback was needed (#167's Implementation Complete had to be posted by hand).

  • Escape hatch restored: --linked-issue membership validation now only runs when detection found candidates (the disambiguation case); an empty set treats the explicit override as authoritative.
  • Wider detection: fallback 2 regex extended to match the IDD-prose issue #N form that /spectra-propose writes, not just Refs / Closes / Fixes trailers.

Checklist

  • Diagnose (Simple)
  • Implement (1 commit, TDD)
  • Verify (6-AI on this PR)
  • Verify-gated: after merge, run the idd-close skill manually on the issue (enforces gate + closing summary). Do NOT add an auto-close trailer.

Test plan

  • bash .claude/scripts/tests/spectra-archive-post-ic/test.sh → 13/13 PASS (2 new fixtures + 11 existing).

…ion (#170)

The --linked-issue override validated membership against the detected
candidate set even when that set was EMPTY, so when detection found
nothing the explicit override was rejected too — both failure modes fired
together exactly when the fallback was needed (#167's IC had to be posted
manually). Now the membership check applies only when candidates were
actually detected; an empty set treats --linked-issue as the authoritative
escape hatch. Integer validation reordered first (emit_outcome exits).

Also widen detection fallback 2 to match the IDD-prose form "issue #N"
(what /spectra-propose writes in tasks.md), not just Refs/Closes/Fixes.

Tests: +2 fixtures (12 prose-issue detection, 13 empty-candidate override);
11 existing still pass (fixture 09 stays rejected on its non-empty set).

Refs #170
@kiki830621

Copy link
Copy Markdown
Member Author

Verify Report — PR #171 (#170)

Engine

5 general-purpose Agents (Requirements / Logic / Security / Regression / Devil's Advocate) + Codex (gpt-5.5, xhigh). Test suite re-run independently by 3 reviewers + Codex: 13/13 PASS.

Aggregate

PASS — 0 hard-blocking. The core #170 fix (empty-set escape hatch + integer-first reorder) is confirmed correct by all 6: emit_outcome exits unconditionally (reorder valid), the [ -n "$CANDIDATES" ] guard is load-bearing (empty set would else false-fail), fixture 09's reject contract is byte-for-byte preserved, no injection surface, scope clean.

Findings (merged across 6 sources)

# Sev Finding Source Action
1 P2 F2 regex [Ii]ssue #N is unanchored: reissue #5/tissue #9 substring-match (LOW); and cross-reference prose (see issue #164, 可能 issue #46) becomes a spurious candidate. Codex's sharp point: a single spurious candidate makes the set non-empty → defeats the escape hatch (--linked-issue 555 rejected). Confirmed on a shipped archive (2026-05-10-add-multi-finding-source-mode → {46,48}). codex + all 5 agents In-scope fix (word-boundary anchor)
2 P3 Override integer check ^[0-9]+$ allows 0, but the error message says "positive integer". --linked-issue 0 posts to /issues/0. codex In-scope fix (reject 0 → ^[1-9][0-9]*$)
3 MEDIUM Escape hatch is a net-new path where empty-set + --linked-issue <wrong-but-existing> silently posts the IC supersession anchor to an unrelated live issue (no existence/correctness guard). Coupled with #1, the membership-validation semantics conflate exit-75 disambiguation with user override. agents:devils-advocate + codex Follow-up (membership-semantics design)
4 LOW Fixture 13 passes only because the harness runs from /tmp (F3 git-log empty there). From the repo cwd, F3 finds the fixture's own #170 commit → candidate {170} → escape hatch not exercised (Codex reproduced). Production trigger (uncommitted archive at post-IC time) differs. codex + DA In-scope fix (document the coupling)
5 P3 emit_outcome returns exit 0 on failure messages (inconsistent with the "0 = success" header). codex By-design — best-effort per #56 (archive succeeded; IC post is convenience). No change.
6 trivial spectra-archive/SKILL.md says "All 11 fixtures pass" — now 13+ (this PR added 2). regression In-scope fix (count; this PR caused the drift)

Scope Check

Clean — diff touches only spectra-archive-post-ic.sh + fixtures 12/13. No scope creep.

Plan

Per same-PR-fix discipline: fixing #1 (word-boundary), #2 (reject 0), #4 (doc), #6 (count) in this PR + re-running the suite. #3 (the deeper membership-semantics conflation + wrong-existing mis-post) → follow-up issue. #5 accepted as by-design.

- Word-boundary anchor the fallback-2 regex `(^|[^[:alnum:]_])` so `[Ii]ssue`
  no longer substring-matches `reissue`/`tissue` (verify P2, 6/6 consensus).
  Codex's escalation: a spurious candidate flips the path off the escape hatch.
- Reject 0 in the --linked-issue integer check (`^[1-9][0-9]*$`) — matches the
  "positive integer" message; `0` would otherwise post to /issues/0 (codex P3).
- Document fixture 13's /tmp-cwd coupling: escape-hatch reachability is
  detection-emptiness-dependent; production posts the IC pre-commit so F3
  git-log sees an untracked archive path → empty (DA-3 / codex).
- SKILL.md fixture count 11 -> 14 in both mirrors (this PR added 3 fixtures).

Tests 13 -> 14 (word-boundary fixture). All GREEN. The deeper membership-
semantics design (spurious candidate defeats override; wrong-but-existing
silent mis-post) is tracked in #172.

Refs #170
@kiki830621

Copy link
Copy Markdown
Member Author

Verify findings resolved (commit 8db1423)

In-PR fixes for the 6-AI round:

# Sev Fix
1 P2 Word-boundary anchor (^|[^[:alnum:]_]) on the fallback-2 regex — reissue #5/tissue #9 no longer false-match. + fixture 14 asserting it.
2 P3 --linked-issue integer check tightened to ^[1-9][0-9]*$ — rejects 0 (was posting to /issues/0 despite the "positive integer" message).
4 LOW fixture 13 README.md documents the /tmp-cwd coupling (escape-hatch reachability is detection-emptiness-dependent; production posts IC pre-commit → F3 sees an untracked archive → empty).
6 trivial SKILL.md fixture count 11 → 14 (both mirrors).
3 MEDIUM Follow-up #172 — the deeper membership-semantics design (spurious candidate defeats override; wrong-but-existing silent mis-post). Beyond #170's reported scope.
5 P3 emit_outcome exit-0-on-failure — accepted as by-design (best-effort IC post per #56; archive itself already succeeded).

Tests: 13 → 14, all GREEN. bash -n clean. The core #170 double-failure (empty-set escape hatch + integer-first reorder) was confirmed correct by all 6 reviewers and is unchanged.

Phase: verified (no remaining blocking or in-PR findings; #172 tracks the design follow-up).

@kiki830621
kiki830621 merged commit 4b79694 into main May 31, 2026
@kiki830621
kiki830621 deleted the idd/170-post-ic-linked-issue branch May 31, 2026 10:37
kiki830621 added a commit that referenced this pull request Aug 29, 2026
**1. #317 的偵測器:檔案層級的 deference 是一張赦免令。**

`docs/workflows.md:570` 寫著「Plan tier EnterPlanMode 需 user approve,unsupervised
loop 無人 approve,**卡住**」—— 正是我在**同一個檔案 163 行之前**剛修掉的那句話,
原樣復活。

**偵測器找到了它,然後丟掉。** 因為 deference 是**檔案層級**檢查:我在 407 行加
的那個「見 dispatch table」指標,讓**整份文件**的其他複述都被豁免。**我的修法自己
造出了藏住違反的赦免。**

改成 per-claim:deference 必須落在該宣稱附近(±10 行,比 claim 的 ±5/同列寬,
因為表格的 caption 合理地涵蓋它的列),不是檔案裡任何一處。

這條在 acid 下**第一次沒有重量** —— 還原成檔案層級仍然全綠,因為被藏住的那個
違反在同一個 commit 裡也被修掉了。**這正是這一輪一直在生產的形狀:守衛的對象被
移除了,於是沒有東西證明守衛有效。** 補一個專門的控制組(植入一個「一處 defer、
遠處複述」的檔案),還原成檔案層級才會紅。

**2. 我把不是 heading 的東西釘成了「summary 在那裡」。**

`<!-- x --> ## Closing Summary` 與 `<a name="cs"></a>## Closing Summary`
**不是 CommonMark heading** —— ATX heading 必須是該行的開頭,前面有 inline HTML
就是段落。**用 markdown-it 實測,不是推論。**

上一輪我把 `html_pfx` 加進**嚴格**的 `lead_re` 去接受它們,然後寫測試把那個行為
釘住 —— 一個非 heading 被宣告成 `casing`(正面斷言)。**放寬嚴格那一半正是
round 5 的壞法,而我在寫下那句話的下一輪又做了一次。**

`lead_re` 拿掉 html_pfx。`present_re` / `html_re` 保留 —— 在那裡過度偵測只是不
採取破壞性動作。fixture #170/#171 的斷言反過來:兩者現在是 advisory(`present`),
且明確斷言**不得**進 CASING。

acid:`html_pfx` 放回 lead_re → 紅 4;deference 還原成檔案層級 → 紅 1。
全 suite 52/52,classifier 142 assertions。
kiki830621 added a commit that referenced this pull request Aug 31, 2026
round-10 的 backstop 幾乎抓得到形狀辨識器抓得到的一切,所以它**遮住**了它們。
實測:清空 `html_pfx`(round-9 整條 inline-tag 白名單)在 `mentioned` 拆出來之前
全綠;把四個辨識器換成 `false` 只紅兩條。round 9 那 45 行「哪些 tag 可以算不可見
前綴」的推理,沒有任何東西守著。

拆出 `mentioned` 之後恢復了大部分重量(四辨識器全殺 → 21 紅),但 `html_pfx`
自己仍只動得了兩條。所以 stage 1 單獨跑一次:把出貨的 script **複製**一份、關掉
backstop 分支、對同一批 fixture 跑。不在production script 加測試專用開關 —— 一個
會改變安全分類器判定的環境變數,遲早會被不是在測試的人碰到。

**而這一跑翻出了本輪最尖銳的一個事實**:round 9 加的七個「讀者看得見、辨識器看
不見」fixture,有**五個**根本不被 round 9 自己的 regex 認得。

    #170 #171 #172   present        辨識器真的看得到
    #173 #183 #184   unrecognised   stage 1 完全看不到,
    #185 #186        unrecognised   唯一的覆蓋來自 round-10 的 backstop

也就是說那五條 `is NOT in MISSING` 斷言,一直是靠**晚一輪才寫的機制**通過的。
fixture 測的是**結果**,斷言的文字暗示的是**機制**,中間沒有東西把兩者接起來。

原因是結構性的,所以記錄而不是補洞:#184 的 tag 屬性跨行,而掃描是逐行的,任何
單行 regex 都不可能匹配;#183 的 `<b>` 不在 `html_re`(h1-h6 + summary),而
`emph_re` 管的是 markdown 強調不是 HTML 粗體;#173 的 `<details>` 與 #186 的
`<blockquote>` 不在 `html_pfx` 白名單裡 —— **而且應該不在**:它們會 render 出可見
元素,round 9 自己的判準就是只有不可見前綴才能跳過。為了換一條綠線把它們加進
白名單,會破壞那條白名單存在的意義。

把辨識器加寬到涵蓋它們,就是 round 10 已經取代掉的那條列舉跑步機。缺的不是覆蓋
範圍,是「哪一層負責什麼」的誠實陳述 —— 所以斷言就寫成那個:`s1_seen` 與
`s1_backstop_only` 兩組,後者在某天真的被 stage 1 認出來時會轉紅並要求更新註記。

三個 mutation 各自轉紅:html_pfx 清空(2)/ 放寬成任意 tag、即 round-5 回歸(5)/
拿掉 html_re(3)。56 個 suite 全綠。
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