← 回看板
plan

客戶端「開卡記錄」頁面只顯示真實開卡、排除綁卡事件

exclude-binding-from-customer-issuances · 建立於 2026-05-28

prospec 流程進度

探索需求規劃設計任務實作驗證歸檔

跨 repo 進度

owl(產品層)
定意圖
定義需求與契約意圖
cockatiel(後端)
待開始
待該 repo 自跑 prospec
raven(前端)
無需求
UI Scope: none(後端回應變乾淨即可)

exclude-binding-from-customer-issuances

客戶端「開卡記錄」頁面只顯示真實開卡、排除綁卡事件

Background

客戶端「開卡記錄」頁面目前意外混入了綁卡記錄。根本原因是「綁卡」事件在資料層落到同一張 Voucher 表(推測以 source 欄位區分如 MANUAL/IMPORTED 等類型),導致客戶查詢開卡時兩種記錄都被回傳。本變更從產品意圖層收斂「開卡記錄」應該只回真實的開卡事件,澄清客戶端的語意一致性。

本變更為 spec 反思 + 規格清理性質,同時刻意作為驗證 prospec workflow 在 monorepo 跨 submodule 場景穩定性的標的。

User Stories

US-1: 客戶查看純淨的開卡記錄 [P1]

As a 已登入的客戶, I want 查看我的「開卡記錄」頁面、清單只包含真實的開卡事件(不含綁卡), So that 我能清楚知道我是怎麼取得每張卡的、不會被綁卡記錄混淆「我買了什麼」的認知。

Acceptance Scenarios:

  • WHEN 客戶開啟「開卡記錄」頁,THEN 清單只顯示「開卡」類型事件,綁卡事件不出現
  • WHEN 客戶歷史含有混合的開卡 + 綁卡記錄,THEN 開卡記錄頁仍只顯示開卡部分
  • WHEN admin/designer 透過既有查詢介面取得記錄,THEN 仍可看到完整資訊(綁卡不被誤過濾)

Independent Test: 以已登入客戶身分呼叫「我的開卡記錄」查詢介面,驗證 ① 回應內不含任何綁卡事件 ② 同帳號的綁卡記錄仍可(若有設計)透過其他入口取得 ③ admin/designer 既有查詢介面回應筆數與本變更前一致。

Edge Cases

  • 客戶歷史完全只有綁卡記錄:開卡記錄頁顯示「無記錄」提示(非錯誤、非空白畫面)
  • 客戶歷史完全只有開卡記錄:正常顯示、無任何過濾副作用
  • 資料層未來新增第三種來源(如 PROMOTIONAL gift card):本變更不預先處理,依 Constitution P1 留給 cockatiel plan 階段定義語意

Functional Requirements

  • FR-001: 客戶身分執行「開卡記錄」查詢時,回應結果不得包含綁卡來源的事件
  • FR-002: 變更不得影響 admin/designer 透過既有查詢介面取得完整資料(含綁卡)的能力
  • FR-003: 過濾邏輯需發生在後端(FR-001 的保證),前端不依賴 client-side filtering,以確保 mobile / 其他 client 行為一致

Success Criteria

  • SC-001: 客戶端「開卡記錄」頁面 100% 不顯示綁卡事件(驗證方式:以測試帳號注入混合資料、查詢後驗證回應 zero 綁卡項)
  • SC-002: admin/designer 既有查詢介面回傳資料量在本變更前後一致(驗證方式:對比變更前後同 query 的回應筆數差為 0)

Related Modules

owl prospec/ai-knowledge/_index.md 模組索引故意為空(owl 設計上不持有 code 模組)。以下從跨 submodule ai-knowledge 推斷(apps/cockatiel/docs/ai-knowledge/_index.mdapps/raven/docs/ai-knowledge/_index.md):

  • cockatiel/voucher:開卡記錄查詢的核心模組,本變更主要落點
  • cockatiel/imported-voucher:綁卡事件源頭,需確認 source 標記在綁卡時被正確寫入
  • cockatiel/customer:客戶身分驗證與查詢入口(如查詢由 customer 模組轉發)
  • raven/customer:voucher dashboard 與「開卡記錄」頁所在模組(UI Scope: none — 不預期動 component,僅內容變乾淨)
  • raven/binding:綁卡 flow 模組,本變更不動,但語意上對偶(綁卡資訊歸屬此模組)

Open Questions

  • NEEDS CLARIFICATION: 「客戶能否另外查詢自己的綁卡記錄」explicitly out-of-scope。若 PM 未來要求支援,將另開 change request(建議名稱:add-customer-bindings-query)。
  • NEEDS CLARIFICATION: 過濾邏輯具體實作(既有 endpoint 加 query param vs 新增專屬客戶端 endpoint)由 cockatiel /prospec-plan 階段拍板,本 spec 不規定。
  • WARN: owl prospec/ai-knowledge/_index.md 模組索引為空(owl 設計上不持有 code)— Related Modules 段落來自跨 submodule 手動參照、非自動匹配。已記錄為 prospec 上游 issue 候選。

Constitution Check

  • Reviewed against prospec/CONSTITUTION.md
  • P1 (owl 是產品層,不是實作層): ✅ PASS — Acceptance Criteria 與 FR 皆停在意圖層、不指定 endpoint 路徑或 SQL filter
  • P2 (契約意圖在 owl、實體在 cockatiel): ✅ PASS — owl 留下「需要這個查詢能力」的意圖;機器可讀 openapi.yaml 由 cockatiel 產
  • P3 (兩軌分離): ✅ PASS — owl 走 owl prospec 流程、cockatiel 走 cockatiel prospec 流程、contract 同步走 CI(Phase 4 4a 已就位)
  • P4 (跨 repo 協調點只有兩個): ✅ PASS — 沒有新增任何跨 repo 手動協調步驟
  • Constraints: ✅ REQ ID 將用 REQ-OWL-* prefix(於 plan 階段展開);本檔以繁體中文撰寫;owl 不留 openapi.yaml 副本(不適用本變更)

UI Scope

Scope: none

Plan: exclude-binding-from-customer-issuances

客戶端「開卡記錄」頁面只顯示真實開卡、排除綁卡事件

Overview

本變更從產品意圖層收斂「開卡記錄」應該只回真實的開卡事件、不含綁卡。owl 端不持有實作,本 plan 描述「契約意圖如何展開為跨 submodule 的協作」,實作細節留給 cockatiel 自己的 prospec-plan 階段決定。

關鍵 design 決策:owl plan.md 不指定 endpoint 路徑、SQL filter、新增 vs 修改既有 endpoint 等實作選擇(依 Constitution P1+P2)。owl 只規範「客戶端透過自己身分查詢『開卡記錄』時,回應內容不含綁卡來源的記錄」這個契約意圖。

Technical Context (ProductHub Mode)

owl 是 monorepo 產品層 spec hub、本身無 code 模組(Constitution P1)。prospec-plan SKILL 預設 Brownfield/Greenfield 二分法皆不適用,採「ProductHub Mode」:跨 submodule 載 ai-knowledge 當 implicit Brownfield context。詳見 Risk Assessment。

跨 Submodule Affected Modules(透過讀 apps/cockatiel/docs/ai-knowledge + apps/raven/docs/ai-knowledge)

Submodule/Module Core Responsibility 本變更角色
cockatiel/voucher 票券開卡建立與管理(Voucher entity;客戶買的真實票券) 「真實開卡」的 domain source
cockatiel/imported-voucher 匯入票券與綁定(ImportedVoucher + VoucherBinding entity;獨立 domain) 「綁卡」的 domain source — 不應出現在客戶端開卡查詢
cockatiel/customer 客戶身分驗證與查詢入口(如 GET /api/v1/customers/my/vouchers 過濾邏輯落點候選
raven/customer voucher dashboard 與「開卡記錄」頁所在模組 UI Scope: none — 後端回應變乾淨即可

Existing Patterns(from owl prospec/ai-knowledge/_conventions.md

  • 跨 repo REQ ID 用前綴避免撞號(REQ-OWL-* / REQ-CKT-* / REQ-RVN-*
  • 規格停在意圖層、不寫 endpoint URL 或 SQL

Architecture Constraints(from owl Constitution)

  • P1: owl 是產品層、plan.md 不寫實作細節
  • P2: 契約意圖在 owl、實體 openapi.yaml 由 cockatiel app.openapi() 產出
  • P3: 兩軌分離 — 不引入跨 repo 手動協調
  • P4: 跨 repo 協調點只有兩個(owl 定意圖 / 契約自動流動)

Affected Modules

Module Impact Changes
owl specs/ High 新增 Feature Spec(archive 時把 proposal sync 進 spec-kit 風格的 specs/00X-*/
cockatiel Medium 後端契約調整:客戶端 voucher 查詢回應不含綁卡來源(細節由 cockatiel 自己 plan)
raven Low UI Scope: none;如 cockatiel 改回應結構,由 Phase 4 4b contract-sync(待建)自動帶型別
跨 repo CI(Phase 4 4a) None 自動驗證 contract freshness 與 oasdiff — 本變更會被 oasdiff 評估

Implementation Steps

  1. owl 端 Feature Spec 沉澱

    • 本 change archive 時,將 proposal.md 的 US-1 + FR-001/002/003 + SC-001/002 sync 進 owl specs/00X-customer-issuance-records/(spec-kit 風格、跟 001/002 對齊)
    • 同步建立 spec.md;非必要不開 data-model.md、designs/、contracts/(依 Constitution P1 不寫實作細節、Constitution P2 不留 openapi.yaml 副本)
  2. 跨 repo contract 意圖傳遞

    • cockatiel 端收到本 change 的 contract 意圖(透過 owl spec),由 cockatiel 自己跑 prospec-explore → new-story → plan → tasks → implement
    • cockatiel 決定具體做法:改 /api/v1/customers/my/vouchers 加 filter / 新增專屬客戶端 endpoint / 改 service 層 query — owl 不規範
    • cockatiel 必須通過自己的 contract gate(Phase 4 4a CI):重 export openapi.yaml、freshness gate 跟 oasdiff breaking gate
  3. raven 端對應調整(如有)

    • 若 cockatiel 改 endpoint 或回應結構,raven 透過 Phase 4 4b(待建)contract-sync workflow 自動拉新 openapi.yaml 並重 gen TS 型別
    • 4b 未完成前可手動 cp openapi.yaml(短期 workaround)
  4. 驗證 & archive

    • 跨 repo 驗證:客戶端實際呼叫 cockatiel、確認回應不含綁卡(SC-001)+ admin/designer 端資料量不變(SC-002)
    • 跑 owl /prospec-verify/prospec-archive 收尾

Risk Assessment

Risk Impact Mitigation
SKILL 預設 Greenfield/Brownfield 二分不適用 owl ProductHub 模式(會誤判 Greenfield 並 recommend prospec knowledge init Medium 採「ProductHub Mode」:跨 submodule 載 ai-knowledge 當 implicit Brownfield context;列入 prospec 上游 issue(task #21)
owl plan.md 過度抽象、cockatiel team 不知道從何開始 Medium proposal.md 含跨 submodule Related Modules + Edge Cases;cockatiel 自己跑 prospec-explore 時有 prompt-level hack 可載 owl spec
Phase 4 4b(raven contract-sync)未完成 → cockatiel 改 contract 後 raven 不會自動拉 Low 本變更短期可手動 cp openapi.yaml;長期靠 4b 解(owl rollout plan 已記)
既有 admin/designer endpoint 被誤過濾 High 由 cockatiel plan 階段嚴控:filter 只在客戶端 endpoint 生效;驗收 SC-002 顯式檢測「資料量不變」
ai-knowledge 揭示的「兩個獨立 entity」與資料層實際可能不同(SQLAlchemy model 層可能加了 source 欄位混合) Low 本 plan 不依賴實作細節;cockatiel plan 階段以實際 model 為準

Delta Spec: exclude-binding-from-customer-issuances

Requirement deltas for this change. REQ ID 採 REQ-OWL-* prefix(owl Constitution Constraints)。

ADDED

REQ-OWL-001: 客戶端「開卡記錄」契約意圖排除綁卡

Feature: exclude-binding-from-customer-issuances Story: US-1

Description: 客戶身分(Authenticated customer)查詢自己的「開卡記錄」時,contract 意圖上回應結果不得包含綁卡(ImportedVoucher 來源)的事件。owl 不規範具體 endpoint 路徑或實作方式(依 Constitution P1+P2),但留下契約意圖以驅動 cockatiel 的 implementation。

Acceptance Criteria:

  1. 客戶端查詢「我的開卡記錄」時,回應僅含 cockatiel voucher 模組的 Voucher entity 衍生記錄,不含 cockatiel imported-voucher 模組的 ImportedVoucher 衍生記錄
  2. 客戶歷史含有混合資料(同時有 Voucher 與 ImportedVoucher)時,仍只回開卡(Voucher)部分
  3. admin/designer 透過既有查詢介面(如 GET /api/v1/vouchers/records 等 Staff 端 endpoint)取得記錄時,仍可看到完整資料(不被誤過濾)— 驗證資料量在本變更前後一致

Priority: High


REQ-OWL-002: 過濾發生位置(後端不前端)

Feature: exclude-binding-from-customer-issuances Story: US-1

Description: 過濾邏輯需發生在後端(cockatiel),前端(raven 或其他 client)不依賴 client-side filtering。確保 mobile / 其他 client 行為一致。

Acceptance Criteria:

  1. cockatiel 客戶端查詢 endpoint 直接回應已過濾後的結果(client 拿到的 payload 不含綁卡項)
  2. 即使前端不做任何 filtering,回應內仍無綁卡項
  3. 過濾邏輯的單元測試覆蓋於 cockatiel 端(不在 raven 端)

Priority: High


MODIFIED

(無 — 本變更為 ADDED-only;既有需求未變更)


REMOVED

(無 — 本變更為 ADDED-only)