feat(source-facts): name the unclosed fence instead of failing silently (closes #82) - #88
Conversation
…ailing silently 關閉行帶 info string(```json)依 CommonMark 不算關閉,所以掃描器從那一行起把整份 文件當成圍籬內容:掃出零筆、沒有任何錯誤,與「這份來源本來就沒結構」完全無法區分。 判定維持嚴格(ADR 0008)。放寬會在最貴的那個案例上出錯——兩個相鄰的開啟圍籬會被 讀成一開一關,夾在中間的程式碼範例因此變成來源事實,而假事實在 fail-closed 閘門下 會擋掉正確的擷取。改的是可見性:SourceFacts 記下未關閉的圍籬開在第幾行,涵蓋投影 帶著這個純量,SOURCE_FACTS_UNSCANNED 的訊息與 verify-extraction 的預告直接點名行號 ——成因已知時就不該叫 operator 從三種可能裡自己猜。 量測:十三個 benchmark case 掃過 320 個圍籬行,零個 info-string 關閉、零份文件掃到 檔尾仍在圍籬內。這是把便宜的揭露做在前面,不是在滅火。 markdown_drafts 那套掃描器是寬容的(任何以標記開頭的行都算關閉),刻意不動:它的 產物是給人審的非權威草稿,外洩一段範例的代價是審閱者多看一眼;source_facts 餵的是 fail-closed 閘門,同樣的外洩代價是一份正確的擷取。分歧記在 ADR 0008,收斂與否留給 掃描器分歧那張票。
…m real loss code review 的 HIGH 成立:揭露只在事實數為零時才發,所以「前半讀得好好的、圍籬之後 全部沒讀到」這種來源完全不會浮現——比全篇未讀更危險,因為閘門判了前半、報告是乾淨的。 而它一旦開口(零匹配那條分支)還會叫 operator 去查一個並不存在的 extraction 缺陷。 改法:成因已知時壓過其他措辭,判準也從「零匹配」放寬成「零匹配或有未讀的尾巴」。 回報條件同時收緊成更誠實的一條:只有在掃描器看見「長得像關閉、卻不算關閉」的行時 才主張有內容沒被讀到。圍籬單純沒有收尾不算——CommonMark 在檔尾關閉未終止的圍籬, 那份文件在每個讀者眼中都一樣,沒有任何內容因為我們的判定而遺失,回報只會叫人去修 一個不存在的缺陷,而「修好」之後事實數仍然是零。 其餘 review 項目:ADR 0008 改寫成程式碼實際實作的規則(含 EOF 圍籬與半讀來源兩段); 兩份 operator manual 的新段落原本插進了兩項並列的中間、把句子切斷,改成自己的段落; AGENTS.md 補上 validate 那列與 correction 分類那列(ADR 0008 的 falsification 點名 fact_coverage.py,那列正是它的邊界文件);fence_line 的 0 哨兵改成 None; 測試補上 tilde 圍籬、先關後開、EOF 圍籬、半讀來源與非 Markdown 來源的投影分支, 並拿掉一個用 or 串起來、少了行號也會通過的斷言。
|
Code review 的 HIGH 與兩個 MEDIUM 都成立,已修正並推上 HIGH — 半讀的來源仍然沉默,開口時還指錯方向。 揭露原本掛在 MEDIUM — EOF 圍籬被當成有損失的缺陷。 這條讓我把回報條件整個換掉,換成比 review 建議的更誠實的一條:只有在掃描器看見「長得像關閉、卻不算關閉」的那一行時才回報。圍籬單純沒有收尾不算——CommonMark 在檔尾關閉未終止的圍籬,那份文件在每個讀者眼中都一樣,沒有任何內容因為我們的嚴格判定而遺失;回報只會叫人去修一個不存在的缺陷,而且「修好」之後事實數仍然是零。review 建議的「其後沒有非空白行才不報」會漏掉這一點:未終止的最後一段範例後面通常還有範例內容。 MEDIUM — 手冊插句切斷了並列句。 兩份都改成自己的段落,並補上「即使前半有事實對上也照樣回報」與「單純沒收尾不在此列」兩件事。 LOW 全部採納: ADR 0008 改寫成程式碼實際實作的規則(新增「只在有被拒絕的關閉行時才記錄」與「半讀來源同樣回報」兩段,falsification 條件同步)。
|
Closes #82。基於
main(不是 #87 的分支)——兩者檔案不重疊。決定:維持嚴格判定,把失效變可見
#82 列了兩個方向。選方向 2(偵測而非猜測),理由記在新的 ADR 0008。
放寬「關閉行不得帶 info string」會在最貴的那個案例上出錯:兩個相鄰的開啟圍籬(JSON request 範例接 JSON response 範例,其中一個關閉行多帶了 info string)會被讀成一開一關,夾在中間的範例因此落在圍籬之外、變成來源事實。假事實在 fail-closed 閘門下會擋掉正確的擷取——這個專案一貫拒絕承擔的那種傷害(ADR 0007)。
「只在嚴格掃描掃到檔尾仍在圍籬內時才寬容重掃」這個折衷也寫進 ADR 的 Considered options 並被否決:來源真的被截斷時同樣會掃到檔尾仍在圍籬內,而那正是寬容重掃會把半截範例讀成散文的時候。
改了什麼
SourceFacts.unclosed_fence_line記下未關閉的圍籬開在第幾行。FactCoverage)帶著這個純量——validate 仍然不認識來源事實的內部結構。SOURCE_FACTS_UNSCANNED的訊息在成因已知時直接點名行號與常見寫法(以 ```json 結尾),不再列出三種可能讓 operator 自己猜;verify-extraction的預告同步。量測
十三個 benchmark case、320 個圍籬行:零個 info-string 關閉、零份文件掃到檔尾仍在圍籬內。這是把便宜的揭露做在前面,不是在滅火——也因此 benchmark 期望值沒有任何變動。
兩套掃描器的分歧
markdown_drafts/markdown.py是寬容的(任何以標記開頭的行都算關閉),刻意不動:它的產物是給人審的非權威草稿,外洩一段範例的代價是審閱者多看一眼;source_facts餵的是 fail-closed 閘門,同樣的外洩代價是一份正確的擷取。分歧記在 ADR 0008 的 Consequences,收斂與否留給 #83。Test plan
None(tests/source_facts/test_markdown.py)tests/validate/test_fact_coverage.py,打validate_outputs)tests/test_cli_verify_extraction_fact_coverage.py)uv run pytest2180 passed(連跑三次,含兩次隨機順序)uv run python scripts/quality_gate.py --strict-localPASS、npm run docs:checkPASS