Skip to content

feat: [ source-facts ] 同一行上的 method 是字面值,不是推論 (closes #97) - #106

Merged
CarlLee1983 merged 1 commit into
mainfrom
agent/issue-97-labelled-endpoint
Aug 17, 2026
Merged

feat: [ source-facts ] 同一行上的 method 是字面值,不是推論 (closes #97)#106
CarlLee1983 merged 1 commit into
mainfrom
agent/issue-97-labelled-endpoint

Conversation

@CarlLee1983

Copy link
Copy Markdown
Owner

先講結論:#97 寫的規則不能照做

issue 的決定是「同行內同時出現完整 URL 與大寫 HTTP method 字面值才算端點宣告」,
前提是「母體裡有八份使用 GitBook 近親寫法的來源」。把那條規則套到十三案的每一份
Markdown 來源上(排除圍籬內),結果是 0 個真宣告、6 個假宣告:

假陽性 內容
tappay-backend-api.md:7 Our server adapts [REST](https://en.wikipedia.org/…) archetype, so all requests are sent using HTTP POST. → 讀成 POST /wiki/Representational_state_transfer
RefundApi.md:16,66 / VoidApi.md:67,167 CyberSource 散文,路徑在連結裡、method 在同句稍後
rsg-game-transfer-wallet.zh-TW.md:1 38 KB 單行 dump

fail-closed 閘門下這是最壞的方向:假事實會擋掉正確的擷取。所以改以母體實際的形狀
重新窄化(已與 @CarlLee1983 確認方向)。

母體真正的三種形狀

來源 寫法 這次收下?
jili-legacy-gaming-pdf |URL|<API URL>/Login|Method|GET|,與攤平版 URL <API URL>/CreateMember Method POST — 同一行、兩邊都是字面值
ecpay-creditcard-pdf(14 處) - 正式環境:https://…- HTTP Method :POST 相隔六行 否 — 跨行,正是 ADR 0007 拒絕的推論
tappay-backend(1 處) method 在表頭 | POST | Url |,URL 在資料列 否 — 跨行

newebpay、line-pay、cybersource、github/paypal webhooks 沒有任何同行規則救得回來。

窄化由五道條件撐著

標籤要獨立成詞(否則 curlURLs 都是標籤)、method 只認大寫字面值、值有 80 字元
上限且整個值必須就是一條路徑(URL /old was renamed, see table. Method POST 不算)、
只有管線區塊的第一列能當宣告、標題之前的宣告在第一個標題結束。

後兩道是試著弄壞第一版才發現的:真參數表中間的 |URL|/callback|Method|POST| 設定列
會一次生出假端點又把表切斷;declaring_level 為 0 的節永遠不會結束,整份文件其後的
表都會歸給它(這個 bug 在舊的 METHOD /path 路徑上就存在,只是新形狀讓它變常見)。

兩個回溯問題

分隔符原本寫成 \s*[::]?\s*[|\s]\s*,一個空白能由三個量詞裡的任何一個吃掉 ——
近乎命中的對齊排版純文字(pdftotext -layout 的正常輸出)十六個空白要 1.97 秒,
二十五個空白十五秒跑不完。改成單一扁平字元類別後,值的開頭又能吃冒號,四千字元的
冒號行要 7.55 秒;把 :: 排出值的開頭類別後,38,011 字元(母體最長行)0.5 毫秒。
兩種 padding 都有計時斷言 —— 正確性測試看不見這一類 bug。

放寬之後才浮出來的假事實

jili 第一次被閘門判到就吐出一個假要求:KickMemberAll 的參數叫 Success。PDF 轉檔
把錯誤碼表黏在參數表尾巴,而 Success 是錯誤訊息。修法有兩個要點:

  • 切在挑名稱欄之前,不是逐列 break。那些列的碼欄 0 不是識別字,留在 body 裡會
    _name_column 挑到第二欄,連真欄位 GameId 都一起漏掉 —— 只加 break 的話是
    「假事實沒了,真事實也沒來」。
  • 判準要兩個條件:整列只有一格有字,且那一格自稱是錯誤碼欄。少了前者,一個真的
    叫「錯誤碼」的上游透傳欄(常見欄位)會把它所在的參數表靜默截斷。

效果(實測,非推估)

  • jili-legacy-gaming-pdfSOURCE_FACTS_UNSCANNED 消失,KickMemberAll 讀出真欄位 GameId
  • 其餘十二案零新增、零損失 —— repo 內 1189 份 .md 做過改動前後的 SourceFacts 全量對照。
  • 判到不等於判完,ADR 與 expect 檔都寫明:21 條 URL+Method 只讀了 11 條(八條寫成
    Method|Get| 首字大寫、兩條被刪除線包住),且該來源多數參數表以空白格開頭,11 個端點
    裡只有 2 個帶得出欄位事實。

決策紀錄

測試計畫

  • tests/source_facts/test_labelled_endpoint.py(新):22 組正反例參數化 + 5 個行為測試,
    六個母體假陽性全部以反例入列,兩種 padding 的計時斷言,以及「標題前宣告放棄自己的
    子標題表格」這個代價的明著釘死。
  • tests/source_facts/test_markdown.py:黏表 fixture 改用 JiLi_zh-tw.pdf.md:387-392
    逐字內容(含重複的 Parameter 表頭 —— 上一版用了消毒過的表頭,那會挑到不同的欄而讓
    測試因為錯的理由通過);新增「真的叫錯誤碼的欄位不會截斷表格」。
  • tests/source_facts/test_scanner_divergence.py:新分歧列入表。
  • 全套 2291 passed、ruff 乾淨、benchmark 期望檔同步。

經 code-reviewer 兩輪審查:第一輪 2 CRITICAL(ReDoS、表中設定列)+ 3 HIGH,全部修正;
第二輪確認四項修好、找出冒號回溯殘留與 HIGH 3 的取捨未釘,均已處理。

已知後續(不在本 PR)

_field_name 會在第一個空白截斷,所以英文寫法的 | Error Code | string | … | 會登記成
參數 Error。既有行為、與 #97 無關,但這個 diff 讓那一列變得讀得到,值得另開 issue。

Closes #97

閘門原本只認 `METHOD /path`。#97 提的窄化規則是「同行內同時出現完整 URL 與
大寫 method 字面值」,實測到十三案的每一份 Markdown 來源上是 0 個真宣告、
6 個假宣告 —— tappay 的「so all requests are sent using HTTP POST.」加上句中
的 Wikipedia 連結會被讀成 `POST /wiki/Representational_state_transfer`。照那條
規則出貨,淨效果比現狀更差。

母體真正的形狀是三種,各自越過不同的線:jili 把宣告寫成
`|URL|<API URL>/Login|Method|GET|`,同一行、兩邊都是字面值;ecpay 的 URL 與
`- HTTP Method :POST` 隔了六行;tappay 的 method 在表頭、URL 在資料列。收下
第一種,後兩種維持不認 —— 跨行湊 method 正是 ADR 0007 判定為推論的那件事,
它的核心主張沒有被推翻,所以 0011 是補充而不是 supersede。

窄化由五道條件撐著:標籤要獨立成詞(否則 `curl`、`URLs` 就是標籤)、method 只
認大寫、值有 80 字元上限且整個值必須就是一條路徑、只有管線區塊的第一列能當
宣告、標題之前的宣告在第一個標題結束。後兩道是試著弄壞第一版時才發現的:
真參數表中間的 `|URL|/callback|Method|POST|` 設定列會一次生出假端點又把表
切斷,而 declaring_level 為 0 的節永遠不會結束,整份文件其後的表都會歸給它。

分隔符寫成單一扁平字元類別、值的開頭排掉冒號,兩者都是為了回溯:拆成三段
量詞時十六個空白的近乎命中要兩秒,值容許冒號起頭時四千字元要七秒半,而母體
最長的一行是 38,011 字元。正確性測試看不見這一類 bug,所以直接量時間。

放寬之後 jili 第一次被閘門判到,立刻吐出一個假事實:PDF 轉檔把錯誤碼表黏在
參數表尾巴,`Success` 這個錯誤訊息被記成參數。切在挑名稱欄之前而不是逐列
break —— 那些列的碼欄 `0` 不是識別字,留著會讓名稱欄挑錯欄,連真欄位 GameId
一起漏掉。判準要兩個條件:整列只有一格有字,且那一格自稱是錯誤碼欄;少了
前者,一個真的叫「錯誤碼」的上游透傳欄會把它所在的表靜默截斷。

效果:jili 的 SOURCE_FACTS_UNSCANNED 消失,其餘十二案零新增、零損失(1189 份
Markdown 全量前後對照)。判到不等於判完 —— 21 條 URL+Method 只讀了 11 條,
其餘八條寫成 `Method|Get|`、兩條被刪除線包住;且該來源多數參數表以空白格
開頭,11 個端點裡只有 2 個帶得出欄位事實。這些寫進 ADR 與 expect 檔。

Closes #97
@CarlLee1983
CarlLee1983 merged commit 017c7a7 into main Aug 17, 2026
1 check passed
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.

來源受理 #2:端點寫成純 URL、method 在散文裡——窄化辨識規則

1 participant