fix: [ source-risk ] 緊鄰的編號不再讓卡號整筆消失 - #108
Merged
Merged
Conversation
`PAYMENT_CARD_CANDIDATE` 是貪婪的:同一行左邊緊鄰另一段數字時,兩者被吃成 一段候選,Luhn 對整串失敗,`finditer` 從候選結尾繼續掃,裡面的卡號不會有 第二次機會。`| 1 | 4539… |` 因為表格分隔而倖存,純文字編號清單則整筆漏掉。 漏的不是誤報而是真卡號,兩個閘都漏:`inspect-source-risk` 的 SR-PAYMENT-CARD 與治理持久化的 privacy gate。#101 在 AGENTS.md 與 README 新增了「card candidates are Luhn-validated」的公開宣稱,宣稱的邊界要與實作對齊。 候選不再等於一個卡號。`iter_payment_card_numbers` 逐段候選再切窗,切點只落 在數字群邊界,取最長的合格窗並回報那個窗的位置。切點不進數字群內部是刻意 的:從一串數字中間切開會憑空造出卡號,而不是找出寫在來源裡的那一個——代價 是沒有分隔符直接黏上去的形狀仍然掃不到,寫進了註解與 AGENTS.md。 Luhn 改成兩條奇偶前綴和。一個數字要不要加倍只取決於它自己的位置與窗尾的 奇偶,不取決於窗從哪裡開始,所以每個窗變成一次減法;沒有這個等式,切窗在 5 MiB 的純數字對抗輸入上是 6 秒級,有了是 1.3 秒(原本 0.3 秒)。整段候選 上限 19 位數,所以每段候選的窗數是常數。`is_payment_card_number` 隨之刪除 而非保留:它的語意正是「一段候選一個卡號」,留著就是留一個好用錯的入口。 測試卡號豁免改成比對窗內含有公告號碼。切窗取最長的合格窗,`88 4111…` 這種 形狀約十分之一會湊出一個更長、不在清單上的合格窗,只認相等的話,一份只是 引用公告號碼的付款文件會拿到警告——豁免存在的理由正是不產生那種警告。 規則的判準從通用迴圈裡的 rule_id 字串分支變成資料(#103 MEDIUM-3)。`_RULES` 每一條帶一個掃描函式,BOM 豁免與卡號政策各自是具名函式:rule_id 打錯一個 字母原本會靜默變成不過濾,而同一 rule_id 已有對應多個樣式的先例。BOM 豁免 原本沒有任何測試,一併補上。函式回傳位移而非 `re.Match`,因為卡號的命中在 候選內部,沒有對應的 match 物件。 `RULESET_VERSION` 3 → 4。偵測結果變了,不升版的話舊 audit 會以「does not match deterministic inspection」失敗,升了才講得出成因與下一步;README 兩份 補上那個下一步(重跑 inspect-source-risk 與 assess-sources)。十三份 gitignored 的 benchmark source-quality 包已重建。 多段數字的來源上誤報率大約加倍(四段 10% → 21%),那是嘗試多個窗的必然代價: 高密度訂單編號文件會更快用掉 500 筆的 warning 額度,治理端也會更常拒絕含 分段參考編號的 case body。兩者都是 fail-closed 方向。 Closes #103
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 #103(MEDIUM-2 與 MEDIUM-3)。兩項都不是 #101 引入的,但 #101 在
AGENTS.md與README.md新增了「card candidates are Luhn-validated」的公開宣稱,宣稱的邊界該與實作對齊。問題
PAYMENT_CARD_CANDIDATE是貪婪的。同一行左邊緊鄰另一段數字時,兩者被吃成一段候選,Luhn 對整串失敗,finditer從候選結尾繼續掃,裡面的卡號不會有第二次機會:Markdown 表格因為有
|分隔而倖存,純文字編號清單整筆漏掉。漏的不是誤報而是真卡號,而且兩個閘都漏:inspect-source-risk的SR-PAYMENT-CARD,以及feedback治理持久化的 privacy gate。做法
候選不再等於一個卡號。
iter_payment_card_numbers逐段候選再切窗,切點只落在數字群邊界,取最長的合格窗並回報那個窗的位置。切點不進數字群內部是刻意的——從一串數字中間切開會憑空造出卡號,而不是找出寫在來源裡的那一個;代價是9994539148803436004這種沒有分隔符直接黏上去的形狀仍然掃不到,寫進了註解與AGENTS.md,免得下一個人當成 bug 重報。Luhn 改成兩條奇偶前綴和:一個數字要不要加倍只取決於它自己的位置與窗尾的奇偶,不取決於窗從哪裡開始,所以每個窗變成一次減法。5 MiB 純數字對抗輸入從 6 秒級降到 1.3 秒(原本 0.3 秒);候選最多 19 位數,所以每段候選的窗數是常數。
is_payment_card_number一併刪除而非保留——它的語意正是「一段候選一個卡號」,留著就是留一個好用錯的入口,現在唯一入口是iter_payment_card_numbers。測試卡號豁免改成比對窗內含有公告號碼。切窗取最長的合格窗,
88 4111…這種形狀約十分之一會湊出一個更長、不在清單上的合格窗;只認相等的話,一份只是引用公告號碼的付款文件會拿到警告,而那正是豁免存在的理由。MEDIUM-3:rule_id 字串特例換成資料
_RULES每一條帶一個掃描函式,BOM 豁免(_is_not_leading_bom)與卡號政策(_payment_card_scanner)各自是具名函式。rule_id 打錯一個字母原本會靜默變成不過濾,而同一 rule_id 已有對應多個樣式的先例。BOM 豁免原本沒有任何測試,一併補上。掃描函式回傳位移而非re.Match,因為卡號的命中在候選內部,沒有對應的 match 物件。遷移
RULESET_VERSION3 → 4。偵測結果變了,不升版的話舊 audit 會以「does not match deterministic inspection」失敗,升了才講得出成因與下一步。兩份 README 補上那個下一步(重跑inspect-source-risk與assess-sources重建 source-quality 包)。十三份 gitignored 的 benchmark source-quality 包已重建(stripe-basic-rest需--max-bytes 20971520,它原本就是這樣產的)。接受的代價
多段數字的來源上誤報率大約加倍(一段 9.9% → 9.9%、四段 10.0% → 20.7%),那是嘗試多個窗的必然結果。可見後果有兩個:高密度訂單編號文件會更快用掉 500 筆的 warning 額度(
SR-WARNINGS-TRUNCATED),治理端也會更常拒絕含分段參考編號的 case body。兩者都是 fail-closed 方向。Test plan
uv run pytest→ 2303 passeduv run ruff check .→ cleannpm run docs:check→ clean新增 3 個 CLI 測試:緊鄰編號的卡號(含 locator 欄位)、緊鄰編號的公告測試卡號不報、BOM 只豁免檔案開頭而非文件中間;
tests/foundry/test_feedback.py的 free-text PII 參數化加上同一形狀,釘住治理端。切窗的正確性另外用兩萬筆隨機數字串的全窗暴力對照驗過(0 不一致),偵測結果對舊實作是嚴格超集。文件
AGENTS.md(privacy.py與source_risk/兩列)、README.md、README.en.md、兩份 operator manual 都改了對應敘述。