詢問查詢機制 — 決議與未決清單

2026-08-01 · 對應 docs/plans/2026-08-01-inquiry-lookup-flow-spec.md §0.5 本輪邊界 · 分支 docs/notification-open-questions @ 4fdc0f4

查詢頁設計稿已到齊at-contact-us-plugin @ f5d8b591638213, 檔名同時改成流程序號): 舊的 4.3-* 四張已改名為 4.3.1-*(Send Inquiry 側)。 三張稿把版面完全定死了,下面 B 區的 tab 決議與 A 區的附件決議都獲得印證, 但也帶出兩個新的規格張力(見 C 區)。

A 本次拍板(2026-08-01 本 session)

你剛剛回答的兩題,已可直接進實作。

已拍板 查詢頁的用戶附件:顯示檔名並可下載 客戶規格 D7

D7 的「答:」原本是空的,只有開發方建議「顯示檔名、可下載(走同一簽章機制)」。

決議:顯示檔名並可下載,走同一組簽章。

連帶要求:現行下載端點 admin_post_inwin_atc_download 是後台專用 (Caps::can_view() + action-specific nonce),未登入用戶走不通。本輪需新增一條 前台簽章下載路徑,授權鏈為「驗 token/session → 取 case_hash 對應案件 → 確認該案件的 email_hash 等於憑證中的 → 確認附件的 (form_type, case_id) 指向該案件 → PathGuard::resolve_within() 後輸出」。 既有 DownloadEndpoint::authorise() 的純函式設計可沿用,但授權來源要多一種。

⚠️ 這等於在 2026-08-01-inquiry-attachment-hardening.md 剛封起來的附件直連防線上 再開一個受控入口。該 runbook 的三環境 curl -I 驗收項目要同步擴充: 「無憑證直接打前台下載端點必須非 200」。

設計稿印證:單筆稿的「您的詢問」區塊畫出「附件:📎 photo.jpg 📎 spec.pdf」, 迴紋針 + 檔名的樣式就是可點連結;「我們的回覆」區塊同樣有「附件:📎 manual.pdf」。 這個決議與 Lilith 的稿一致。

已拍板 信 2(客服通知)固定繁體中文,信內標注用戶語系 notification-open-questions 第 9 題

客戶 G-1 答「以送出時的 ICL_LANGUAGE_CODE 為準」,但那講的是給用戶的信; 客戶從未針對客服信表態。直接照抄會得到「日本用戶送出 → 客服收到日文通知信」。

決議:信 2 框架固定 zh-hant,用戶填寫的內容原樣呈現,另加一列「用戶語系:{locale}」。

收件人 Nelly.hsieh@in-win.com.tw / service@art-tangency.com 都在台灣。 本題整體仍屬下一輪(第 9 題未結案),本輪先擇一並在 spec 註記 —— 這是你先前的裁示。

B 我逕行決定、請追認

規格留了選擇空間或根本沒寫,我依既有實作慣例擇一。不同意的話現在講,改起來便宜。

逕行決定 查詢頁落地在表單頁的 Check Record tab,不另開獨立頁 C10 ↔ spec §6 衝突

兩份文件對不上:客戶 C10 答「4.3.2 查看紀錄是獨立頁面,由 Lilith 建頁貼 [at_contact_records]」(2026-07-28);spec §6 則寫「Check Record tab 由『顯示但未開放』改為實作四態頁面」(2026-08-01)。

採 spec §6:magic link 落在 /contact-us/?atc_e=…&atc_exp=…&atc_t=…, shortcode 偵測到 token 參數就自動切到 Check Record tab。

C10 的核心理由是「簽章連結必須有落地 URL」—— 帶 query string 的表單頁同樣是落地 URL, 理由不受影響。且 C9 的答覆(雙入口 tab)比 C10 晚,tab 本身就是客戶看過的設計。 不另開頁也省掉「兩個 shortcode、兩個頁面、四個 Elementor 頁要維護」。

設計稿印證,此項已可視為定案:三張 4.3.2-* 稿的頁首都是同一組 Send Inquiry / Check Record tab、Check Record 呈紅色 active 狀態, 麵包屑一律是 Support > Contact US,footer 與五個據點區塊也完全相同 —— 畫的就是同一頁的另一個 tab,不是另一個頁面。C10 的「獨立頁面」已被後續設計取代。

逕行決定 信 2 收件人放設定頁欄位,不寫成常數 spec §0.5

spec 原文是「以常數設定頁欄位提供,不查路由表」,兩者皆可。

採設定頁欄位,預設值即現行 Elementor 在寄的那兩個地址。

日後 CS 三維路由補上時,改的是「收件人來源從設定欄位換成查表」,兩種做法的可逆性一樣; 但設定頁欄位讓客戶自己就能改收件人,不必為了換一個信箱發版。 同時把 spec §11 清單裡的「設定頁四欄」一次補齊(寄件者名稱/寄件者 Email/備援收件信箱/查詢連結有效天數)。

逕行決定 30 分鐘 session 用簽章 cookie,頁內切案件靠 case_hash spec §2.4 / §3 安全約束 3

spec 只寫「驗證後 session 30 分鐘」「session 綁定類型」,沒指定實作形式。

簽章 cookie(無伺服器狀態):HMAC(type|email_hash|exp),30 分鐘。

頁內從清單切到單筆時走 ?atc_view={case_hash} —— case_hash 是 keyed HMAC, 本來就設計成可出現在 URL 上;由 cookie 授權 email 作用域,再驗該案件確實屬於這個 email_hash。這樣不必為每個清單項目重新簽一次完整 token (重簽會靜默延長 exp,與 §7「每封信簽發當下重新計算 exp」的意圖打架)。

逕行決定 migration 一併 backfill 既有案件的兩個 hash 欄位 spec §6 未提

spec 只說在 CanonicalSchema::shared_case_columns() / shared_case_keys()email_hash / case_hash 欄位與索引,沒交代既有列怎麼辦。

加一個版本化 data step(Migrator::data_steps() 目前是空陣列,正好是第一個), 用同一張表的 email / sn 明文欄位回填。

不回填的話既有案件的 hash 是空字串 —— 查詢一律落空,而且是靜默落空。 mlab 目前案件數趨近於零,成本可忽略;正式站上線前跑一次即可。 回填必須在 inwin_atc_hash_key 產生之後執行,順序不能顛倒。

逕行決定 重發連結的節流另計,不與送出節流共用計數器 spec §3 安全約束 5

spec 要求「同一 Email 60 秒內只能請求一次」。既有 RateLimiter 是送出用的 IP_LIMIT=3/10 分 + EMAIL_LIMIT=5/24 小時,語意不同。

沿用 RateLimiter 這個類別的注入式設計,但用獨立的 transient 前綴與獨立常數。

共用計數器會讓「查了三次紀錄」把「還能送幾張單」吃掉,反過來也一樣 —— 兩者是不同的濫用面向。 RateLimiterallow()/record()/release() 競態限制(見該類別 docblock)在這裡同樣存在,同樣接受。

逕行決定 信件語系沿用兩版(zh-hant / en),不做客戶說的 3 語系 9 模板 客戶 F2 ↔ 站台現況

客戶 F2 答「3 語系 × 3 類 = 9 個模板,其餘語系 fallback 英文」。 但站上 WPML active 語系只有 en,且既有 ResultScreen 就是 zh-hant / en 兩版、完全相等比對。

本輪信 1 / 信 4 沿用 ResultScreen 的兩版模式。

第三語系(日文)等 WPML 真的啟用該語系再補,模板結構本來就支援多加一版。 現在做 9 個模板,其中 3 個永遠不會被觸發。

逕行決定 atc_replies 不建表,但留一個回傳空陣列的讀取縫 spec §0.5 / §6

spec 要求「查詢頁的回覆區塊版面與 atc_replies 的讀取路徑要先留, 以『查無回覆』空狀態呈現」,但表本輪不建 —— 讀取路徑無表可讀。

定義一個 replies provider 介面,本輪的實作固定回 [],並在 docblock 寫明 「下一輪換成真正的 repository,呼叫端不動」。

這樣版面、空狀態、與呼叫點都到位,下一輪只換一個實作類別,不必重做查詢頁 —— 正是 spec 那句話的目的。

C 仍不確定、會擋到實作

這幾項我沒有足夠依據自己決定,但也還沒到非答不可 —— 排在對應 task 開始前給答案即可。 前兩項是讀完新設計稿才浮出來的。

未決 · 新 清單頁的「已回覆」標籤 vs 客戶 D8「前台不顯示任何狀態標籤」 設計稿 ↔ D8

客戶 D8 明確答「前台不顯示任何狀態標籤」,理由寫得很完整 —— 「回覆區塊的有無本身就是狀態」,還附了一張對照表,並提到附帶好處是 「省去一組需要 10 語系翻譯的介面字串」。

但新的清單稿在每一列右側放了一欄無標題的「已回覆」標籤 (CN202607300001 有、CN202607280015 沒有、CN202607150003 有)。 單筆稿則確實沒有任何狀態標籤,完全符合 D8。

我的判讀:這不是矛盾,是 D8 的理由在清單頁不成立。

D8 的推論前提是「使用者看得到回覆區塊」—— 那只在單筆頁為真。清單頁看不到任何回覆內容, 沒有標籤就無從分辨哪幾筆已處理,D8 想省掉的那個資訊反而沒有替代來源。 所以我傾向照稿做:清單頁保留標籤、單筆頁不放標籤。

要你確認的:
  • 同意「清單有、單筆無」這個分界嗎?
  • 若同意,D8 的「不顯示任何狀態標籤」需要在 spec 註記為已被設計稿部分推翻, 否則下一個人讀 D8 會把標籤拿掉。
  • 本輪 atc_replies 不建表 → 所有列都會是「未回覆」(無標籤)。 標籤的版面要先做,但實際上這一輪不會有任何一列亮起來。
未決 · 新 清單列要從哪裡點進單筆頁 設計稿未標示

清單稿的四欄是「案件編號 / 送出時間 / 聯絡主題 / (已回覆)」, 沒有任何一欄畫成連結樣式,也沒有右側的〔查看〕按鈕欄 —— 但單筆稿的頁首有「← 返回我的詢問紀錄」,證明兩頁之間確實要能互相走。

對照 spec §4 的信 4 版面,信件裡是每列右側一個明確的〔查看〕。清單頁稿上沒有對應物。

我的傾向:整列可點(cursor: pointer + hover 底色),案件編號同時做成連結樣式。

不另加一欄〔查看〕,因為稿上的欄寬已經排定,加欄會破壞與稿的對齊; 案件編號本來就是這一列的識別,做成連結最不需要解釋。 若你要求嚴格照稿(完全不改樣式、只有整列可點),也可以,但無障礙上會弱一些。

未決 寄件者 Email 的網域限制在三個環境各是什麼行為 spec §5

spec 拍板「存檔時驗證寄件者網域必須等於站台網域wp_parse_url(home_url(), PHP_URL_HOST) 去掉 www.)」, 且明文「不得提供『我知道風險,強制存檔』的勾選框」,唯一放寬路徑是 apply_filters('inwin_atc_allowed_sender_domains', …)

照這條規則,三個環境算出來的合法寄件網域分別是 inwin.mlab.host / inwin.demo1.app / 正式站網域。 問題在於 mlab 與 demo1 這兩個網域沒有真實信箱、也沒有 SPF 記錄, 寄出去必進垃圾桶或直接被拒。

要回答的:
  • 開發/測試環境是靠 inwin_atc_allowed_sender_domains filter 在 wp-config.php 放行,還是靠 Mailpit 攔截、根本不出站?
  • 正式站的 noreply@ 信箱誰去 SiteGround 開?這是上線的硬前置, 不開就整套機制失效。
未決 正式頁 1216 / 1218 何時換 shortcode contact-rma-spec §12 交付清單

查詢機制做完之後,/contact-us//rma/ 才第一次有完整迴圈可交付。 目前這兩頁仍是舊的 Elementor 表單,只有 preview 頁(103975 / 103976)在跑新 shortcode。

要回答的:本輪 PR merge 後就換,還是等信 3 與 CS 路由也做完(下一輪)再一次換?

影響的是「客服收不到通知」這個功能倒退持續多久。信 2 本輪就會補上, 所以換過去之後客服是收得到的 —— 缺的只有「已回覆通知」。

D 環境層,不擋實作但擋驗收

寫程式不受影響,但手測跑不完。做到驗收那一步之前要先處理掉。

環境 mlab 沒有裝任何 SMTP 外掛

spec §5 實查結論。本機開發預期用 Mailpit 攔截,但目前沒有驗證過 wp_mail() 真的會落到 Mailpit(PHP 8.0 isolate 的 sendmail_path 沒查)。 第一個寄信 task 開始前要先確認,否則測出來的「有寄信」其實是靜默失敗。

環境 nginx client_max_body_size 2M vs 附件上限 5MB

站台 conf 寫死 2M,超過 2MB 的附件會被 nginx 以 413 擋掉、PHP 完全看不到。 這條在查詢頁本身不觸發,但「附件可下載」的手測需要先有一個夠大的附件存在。 改 conf 之後記得 不要用 herd restart nginx(它會說謊), 要請你跑 ! sudo kill -HUP <master_pid>

環境 inwin_atc_hash_key 必須進備份清單

spec §2.2 的紅字要求。這把 key 遺失=該站所有詢問紀錄永久查不回來, 連重發都救不了。交付說明要寫,三個環境各自產生的值也要各自記錄。

E 已排程延後(不是未決)

這些已經有結論了,結論就是「下一輪」。列出來避免被誤當成漏掉。

項目為什麼延後
信 3(已獲回覆通知) 需先有後台回覆功能與 atc_replies 表,兩者皆未做
CS 三維路由 open-questions 第 5、6、7 題全未決;本輪以固定收件人繞過。這三題加起來是「一週 vs 一天」的差別
第 1 題:RMA 範圍爭議 客戶規格明文排除 RMA,但實作是共用底層一起做的。屬通知寄送端
第 8 題:代理商國家不產生 submission RMA 專屬,與查詢端無關
第 10 題:mail_failed 對後台清單的影響 需要先有 atc_mail_log;本輪不建

F 客戶端待確認(既有,非本輪新增)

contact-rma-spec §11 的原有清單,沒有一項擋到本輪實作,放這裡只是不讓它們消失。

G 設計稿定死的版面與文案

從三張 4.3.2-* 稿抄下來的,實作時照這個做,不用再回去翻圖。 稿只有繁中版,英文版文案要開發端自己補(比照 ResultScreen::TEMPLATE_EN 的做法)。

狀態版面與文案

check_record_form
標題「查詢您的詢問紀錄」
說明「請輸入您送出詢問時使用的電子信箱,我們會將查詢連結寄給您。」
欄位:E-mail*(必填)/案件編號(選填)
按鈕:取得查詢連結(左側信封 icon,與 Send Inquiry 的 Send 鈕同款)
⚠️ 稿上筆誤:案件編號欄的 placeholder 誤植成 Insert your e-mail,實作要改
③ 清單
check_record_list
標題「您的詢問紀錄」
四欄:案件編號 / 送出時間YYYY-MM-DD,不含時分)/ 聯絡主題(= purpose_label)/ 無標題的「已回覆」標籤欄
表頭下一條細線,列間無框線。範例列 3 筆,實際每頁 20 筆(spec §7)
③ 單筆
check_record
頁首「← 返回我的詢問紀錄
接著 案件編號:… / 送出時間:…含時分,如 2026-07-30 14:32
小標「您的詢問」+ 細線 → 國家/地區、產品分類、聯絡主題、姓名、E-Mail、電話、訊息內容、附件
小標「我們的回覆」+ 細線 → 回覆時間、回覆內容、附件
沒有任何狀態標籤(符合 D8)
兩個順帶確認的點。 單筆頁把用戶自己的 E-Mail 明文顯示出來 —— 這與 spec §3 安全約束 2 「狀態 ② 仍不得顯示 Email」不衝突:那條講的是未驗證的過期狀態 (URL 上只有 email_hash,顯示遮罩值等於用 hash 反推明文); 狀態 ③ 已通過簽章驗證,顯示自己的 Email 是合理的。

另外「我們的回覆」整個區塊本輪不會有內容atc_replies 不建表)。 spec §0.5 已明文要求版面與讀取路徑要先留、以空狀態呈現,不得因為沒資料就省略, 否則下一輪要重做查詢頁。