feat: [ sources ] 次級佐證來源路徑:信件摘錄與補充載體 - #105
Merged
Merged
Conversation
有些規範性資訊只存在於供應商的信件裡 —— 金鑰怎麼取得、測試環境在哪、 上線前要做什麼。先前只有兩條路:不放進 pipeline,資訊變成 missing;或讓 agent 讀進擷取結果,於是 provenance 宣稱一條主張有來源支撐,而那個來源 不存在於任何 manifest 條目。第二條讓報告說謊,而且沒有任何機制會發現。 新增第三條:人工摘錄成 Markdown,經 `import-supplementary-note` 匯入並 寫出綁定內容的 `.source.json` sidecar。manifest 據此把來源標成 `authority: supplementary`。它填得了 missing、支撐不了 explicit_support, 與正式文件衝突時正式文件勝。形狀取自 `rendered_url.py` —— 那條路徑要解 的問題完全相同。 等級放在來源層而非主張層:不可重新取得是載體固有的性質,對它承載的每 一條主張都成立;放主張層等於把同一判斷重複 N 次,並給了 N 次判錯的機會。 也刻意不併進 `derived_support`,那個關係指的是推理距離,混用之後沒有人 分得出一個 derived_support 是哪一種。 三處會咬人的交互作用,都有回歸測試: - `source_guard.source_violations` 在唯一文件時整個跳過,所以它問的是 `sole_normative_source()` —— 否則加一份摘錄會讓一份單一手冊的 run 在 建立目錄前被拒。但 `classify_item` 問的仍是 `sole_source()`(含次級), 因為它的 fallback 是「把無法解析的 locator 歸給唯一的文件」,而摘錄 就是第二份文件。兩者拆開之前,一條寫著「供應商信件」的引用會被記成 manual.md 支持 —— 正是這個功能要防的事,由它的修正重新引進。 - sidecar 讀取端 fail closed:缺席才是 normative,讀不動不是。宣告綁在 `source_file` 與 `imported_sha256` 上,否則一個兩行的複製 sidecar 就能 把正式手冊降級,而降級後它會整份退出新鮮度指紋。 - shadow/strict 撤掉次級佐證的支撐提案與證據引用。filename-only 引用本 來就降級,暴露面是 v1 精確證據 —— 它擁有自己宣告的 claim path,會把 摘錄以與手冊同等的身分寫進 Core candidate。撤掉而非改標 insufficient, 因為 Core 禁止 runtime 提出那個關係:那是它驗證後的結論。 其他:`record-fingerprint` 排除次級佐證,並對零可重取得來源 fail loud (空指紋不會失敗,它會永遠回報新鮮);逐條 warning 級 `SUPPLEMENTARY_SUPPORT` 點名只靠次級佐證成立的 plan item,含 `schemas[].field_evidence[]`;計分歸 source grounding。 接受的破口寫進 ADR 0010:摘錄是人寫的,`excerpted_by` 買到的是可追責, 不是可驗證。這是唯一帶著這個代價的來源類別。 規格裡「衝突時正式文件勝且不產生 SOURCE_CONFLICT」目前做不到 —— `source_conflicts[]` 是自由文字、沒有逐來源歸屬,要做得擴充擷取 schema。 記在 ADR 的 Not decided here。 Closes #102 🤖 Generated with Claude Code
`scripts/release.py prepare` 不碰 HTML 手冊,而 #104 改了 source-risk 的 規則集、#105 新增了一個取源指令與一個來源等級 —— 依 AGENTS.md 的非商榷 規則,這些必須在同一次發布裡改完。 operator manual(zh + en): - source-risk 段落改寫成兩個方向(操縱 vs 洩漏),補上五條新規則、只有 自證性材料會擋的理由、Luhn 與測試卡號排除、以及 warning 的 500 筆 獨立額度與它存在的原因 - 新增 `import-supplementary-note` 一節與目錄項,含接受的破口與 sidecar 的 fail-closed 行為 architecture manual(zh + en): - source_risk/ 模組描述補上洩漏方向與 warning 額度 - manifest/ 模組描述補上 authority 欄位、sidecar 標定與 fail-closed - 流程圖加上 import-supplementary-note 🤖 Generated with Claude Code
`258dce4` 只改了 operator 與 architecture manual。掃過另外六份之後,發現 三處實際錯誤與四處列舉漏項: 錯誤: - onboarding 的 freshness 描述說「本地取 sha256」,但 record.py 已排除 次級佐證,且零可重取得來源會直接拒絕寫出 - onboarding 的 manifest 模組描述沒有 authority 欄位 - introduction 的「CLI 28 個命令」—— 0.37.0 有 27 個 @app.command 加 2 個群組,湊不出 28,計數基準無法重建。與其換一個同樣無法驗證的數字, 改成不帶數字的敘述,下次加指令不會再錯一次 列舉漏項(這類位置漏了就等於錯): - onboarding 的 CLI 指令卡片缺 import-supplementary-note,而同區塊列了 同源的 import-rendered-url - onboarding 的套件邊界表缺 supplementary_note.py 一列;它也被錯放進 read-side I/O 清單,實際上是寫入出口 - onboarding 的 source_risk 描述只講掃描,沒講新增的洩漏方向 - onboarding 的流程圖第 2 階同上 另補:核心不變式那段加上次級佐證的但書 —— missing 現在有第二條合法 填法,而那段的脈絡讀起來像是只能靠重讀正式文件。 刻意不改:index.html 的轉址頁摘要(權重低)、introduction 的策展式取源 細節清單(不是完整枚舉)、onboarding 修正迴圈的 issue code 二分 (SUPPLEMENTARY_SUPPORT 是 warning,不影響可修正/不可修正的分類)。 同批更新 PRODUCT_EXTENSION_ROADMAP.md 的 source acquisition 範圍與 DESIGN_DECISIONS.md 的來源風險/治理缺口段落。 🤖 Generated with Claude Code
5 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #102(規格),實作 #96 記錄的缺口。前置條件 #101 已於 #104 合併。
問題
有些規範性資訊只存在於供應商的信件或通訊軟體訊息裡——金鑰怎麼取得、測試環境位址、上線前必須完成的事、某個商戶參數的實際意義。供應商往往也沒有打算把它寫進文件。同樣的情況也出現在對方另外給的欄位對照試算表。
先前只有兩條路,兩條都不好。不放進 pipeline:資訊變成
missing,整合契約缺一塊,但報告是誠實的。或讓 agent 讀進擷取結果:契約完整了,但provenance.json會宣稱一條規範性主張有來源支撐,而那個來源不存在於任何 manifest 條目——報告在說謊,且沒有任何機制會發現。做法
新增第三條路:人工摘錄成 Markdown,經
import-supplementary-note匯入,寫出綁定內容的.source.jsonsidecar(出處、收到時間、摘錄者、SHA-256)。manifest據此把來源標成authority: supplementary,沒有 sidecar 的一律是normative——現存的每一份來源都是正式文件,這個預設是事實而非相容性妥協。次級佐證做得到:被引用、填補
missing、讓一條主張成立。做不到:與正式文件衝突時勝出、與正式文件在報告裡混為一談、支撐 Core 的explicit_support。等級放在來源層,不是主張層。 不可重新取得是載體固有的性質,對它承載的每一條主張都成立;放主張層等於把同一判斷重複 N 次,並給了 N 次判錯的機會。也刻意不併進
derived_support——那個關係指的是推理距離,混用之後沒有人分得出一個derived_support是哪一種。分界線是可重新取得性,不是媒介。供應商工程師寫的信和 PDF 一樣出自供應商,媒介不是它較弱的原因;較弱的原因是
freshness/靠 SHA-256 比對偵測漂移、governance/據此觸發重審,而一封信沒有 URL、沒有版本、沒有第二次抓取。三處會咬人的交互作用
都有回歸測試,都不是設計時預見的。
sole_source()有兩個用途,必須拆開。source_guard.source_violations在 manifest 收斂成唯一文件時整個跳過,所以加一份摘錄若讓文件數變二,一份單一手冊的 run 會在建立目錄前就被邊界拒絕(exit 2)。它現在問sole_normative_source()。但classify_item仍問sole_source()(含次級),因為它的 fallback 是「把無法解析的 locator 歸給唯一的文件」,而摘錄就是第二份文件——兩者拆開之前,一條寫著"source": "供應商信件 2026-08-16"的引用會被記成manual.md支持,手冊裡根本沒有那句話。這正是本功能要防的失敗,由它的修正重新引進。不對稱是重點:跳過邊界檢查只是把問題延後到逐條驗證,把 locator 歸給某份文件卻是在斷言那份文件的內容。sidecar 讀取端必須 fail closed。 缺席才是
normative,讀不動不是——一個截斷的寫入或錯誤的權限,否則就能讓一份次級佐證靜默升級成正式文件,重新進入sole_source()與指紋,SUPPLEMENTARY_SUPPORT一條都不報。宣告也綁在內容上(source_file+imported_sha256),否則一個從別處複製來的兩行 sidecar 就能把正式手冊降級,而降級後它會整份退出新鮮度指紋。shadow/strict 撤掉次級佐證的支撐提案與證據引用。 實測發現 filename-only 引用本來就降級成
insufficient,真正的暴露面只有 v1 精確證據——它擁有自己宣告的 claim path,會把摘錄以與手冊同等的身分寫進 Core candidate。撤掉而非改標insufficient:ClaimSupportProposal直接拒絕後者("runtime may only propose explicit or derived support"),因為不足是 Core 驗證後的結論,不是 runtime 能宣告的。其他
record-fingerprint排除次級佐證,並對零可重取得來源 fail loud——空指紋不會失敗,它會永遠回報新鮮,那是無聲的監控喪失。逐條 warning 級SUPPLEMENTARY_SUPPORT點名只靠次級佐證成立的 plan item(含schemas[].field_evidence[]:「手冊有 Order schema,信裡才說notify_url必填」正是這個形狀),計分歸 source grounding。刻意不用 run 層警告——SOURCE_FACTS_UNSCANNED已經證明那會噪音化。plan item 狀態維持supported,因為unverified的意思是引用對不上 manifest 來源,而摘錄對得上,混用會讓該 code 的修正指引建議一件做不到的事。接受的破口
摘錄是人寫的,摘錄者可能寫錯或過度解讀,pipeline 無法分辨。
excerpted_by買到的是可追責,不是可驗證。這是唯一帶著這個代價的來源類別,寫在 ADR 0010 裡。接受它是因為排除這條路不會讓資訊停止存在——只會讓它從沒有紀錄的管道進來。規格裡沒做到的一條
「衝突時正式文件勝且不產生
SOURCE_CONFLICT」目前做不到:source_conflicts[]是擷取 agent 寫的自由文字,只有area與detail,沒有逐來源歸屬,validate/沒有依據分辨衝突的哪一邊是次級佐證。要做得擴充擷取 schema 並升extraction_contract_version。記在 ADR 0010 的 Not decided here,規則暫時以擷取 agent 的指引形式存在。Test plan
uv run pytest→ 2256 passeduv run ruff check .→ cleannpm run docs:check→ clean新增 18 個測試,全在兩個事先談定的 seam 上:
import-supplementary-noteCLI(成功路徑、無時區時間戳、拒絕覆蓋、路徑當檔名、非 Markdown、缺摘錄者、sidecar 讀不動、sidecar 與內容不符、rendered-URL sidecar 仍是 normative)與assemble的 run-dir 產物(邊界不拒絕、無法解析的 locator 不歸給手冊、逐條點名、有正式支撐者不點名、指紋排除、零可重取得來源拒寫指紋、shadow 不給 explicit_support)。shadow 那個測試帶對照組:同樣形狀的精確證據指向
manual.md時確實產生explicit_support。第一版沒有對照組而空轉——core/只有一個error.json,斷言的迴圈根本沒跑到。一併修掉的既有問題
_within_root我一開始傳了未解析的 root,在 macOS 的/tmp→/private/tmp底下會誤判整個掃描(pytest 用已解析路徑所以測不出來)。tests/test_cli_freshness.py的寫檔/防覆寫測試用的是零來源 manifest,等於順帶宣稱空指紋可以接受;改成給它一份真實來源。ADR
新增 ADR 0010。它的證偽條件在實作過程中被修過一次——原本寫「當
plan/classify.py在sole_source()裡計入次級佐證時失效」,而正確的修法恰恰是計入;條件指名了錯的機制。現在指向source_guard.py、validate/authority.py、shadow/bridge.py、freshness/record.py與manifest/models.py。