客戶端「開卡記錄」頁面只顯示真實開卡、排除綁卡事件
prospec 流程進度
跨 repo 進度
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.md與apps/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
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 副本)
- 本 change archive 時,將 proposal.md 的 US-1 + FR-001/002/003 + SC-001/002 sync 進 owl
跨 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
raven 端對應調整(如有)
- 若 cockatiel 改 endpoint 或回應結構,raven 透過 Phase 4 4b(待建)contract-sync workflow 自動拉新 openapi.yaml 並重 gen TS 型別
- 4b 未完成前可手動 cp openapi.yaml(短期 workaround)
驗證 & 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:
- 客戶端查詢「我的開卡記錄」時,回應僅含 cockatiel
voucher模組的 Voucher entity 衍生記錄,不含 cockatielimported-voucher模組的 ImportedVoucher 衍生記錄 - 客戶歷史含有混合資料(同時有 Voucher 與 ImportedVoucher)時,仍只回開卡(Voucher)部分
- 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:
- cockatiel 客戶端查詢 endpoint 直接回應已過濾後的結果(client 拿到的 payload 不含綁卡項)
- 即使前端不做任何 filtering,回應內仍無綁卡項
- 過濾邏輯的單元測試覆蓋於 cockatiel 端(不在 raven 端)
Priority: High
MODIFIED
(無 — 本變更為 ADDED-only;既有需求未變更)
REMOVED
(無 — 本變更為 ADDED-only)