QBear 陪伴每一個小小世界
MVP

香港繁體中文 / 版本 1.0 / 2026 年 10 月 8 日

A LITTLE BEAR. A BIG LITTLE WORLD.

QBear.

陪伴,讓童年
多一點奇妙。

一隻公仔,一段對話。
讓小小的好奇心,有個溫柔的回應。

探索 QBear 計劃

AI 語音公仔 · iOS & Android MVP

小熊相伴 · 一起長大
自然語音即時互動對話
掃碼啟動每隻公仔獨立綁定
一小時陪伴啟動贈送 3,600 秒
家長守護安全與私隱設計

BUSINESS CASE & TECHNICAL DESIGN

開啟技術設計工作台 · 前置設定與畫面線框 →

商業個案及技術設計

以每隻公仔獨立語音額度為核心的 iOS 及 Android MVP

2026 年 10 月 8 日 | 版本 1.0

適用讀者:項目發起人、產品負責人、工程團隊、安全與私隱顧問及營運團隊。

本文件提供投資及工程評審基線:公仔 QR Code 一次啟動獲贈 3,600 秒自然即時對話,用完後由家長購買增值時間。內容涵蓋商業可行性、財務情境、語音架構、帳本交易、平台支付、兒童安全及交付驗收。

每隻公仔獨立獲贈額度,由家長管理並由伺服器執行安全及扣額。

建議決策:先完成供應商地區資格、兒童資料處理及商店 QR 解鎖審批的驗證,再投入小批量生產與家庭試點。

文件狀態:供商業及工程評審;財務數字屬情境假設,技術規格屬建議基線,尚未構成已部署或已測試的系統。

閱讀指引與內容索引

文件採用四類證據標籤,避免將產品決定、官方規則與商業推算混為一談。所有平台資料的查閱日均為 2026 年 10 月 8 日;推出前須再核實。來源編號見附錄 C。

標籤定義如何使用
D 已確認決定原對話使用者明確選定的產品方向作為 MVP 約束;改動須有產品決策紀錄
F 已核實事實本次查閱的官方文件可支持的能力或規則引用 [S編號];不等同審批或帳戶資格已獲批準
A 待確認假設價格、成本、需求、年齡、市場及容量推算先測試或取得報價,才可轉為預算承諾
P 建議設計本文件提出的工程及營運方案經設計評審、原型及驗收後才可採用
部分章節閱讀重點
商業決策1 至 6摘要、定位、模式、財務、風險、里程碑
產品與架構7 至 11Flutter、Realtime、安全、私隱
交易與整合12 至 17資料模型、QR、API、租約、原子扣額、IAP
營運與交付18 至 21監控、驗收、交付、待決事項
工程附錄A 至 CSQL、交易規格、官方來源

範圍與基本名詞

公仔是實體商品及角色載體;MVP 假設語音由家長的手機收音及播放,公仔本身不設咪高峰、喇叭、電池或藍牙。若要讓公仔直接發聲,須另立硬件、認證、功耗及連線設計。此項屬 A,並非原對話已確認決定。

wallet 指公仔的秒數錢包;ledger 指不可覆寫的交易帳本;lease 指短期預留秒數及伺服器使用許可;Voice Gateway 指受控語音閘道;IAP 指 Apple/Google 應用程式內購買。Supabase Realtime 資料推送與 OpenAI Realtime 語音是不同服務。

1 商業摘要與投資建議

QBear 把實體公仔、角色語音及家長控制結合,讓兒童在家長管理下以廣東話自然傾偈、聽故事及進行簡單語言遊戲。購買公仔後掃描唯一 QR Code,首次啟動獲贈一小時;每隻公仔的額度獨立,增值亦指定到該公仔。上述平台、對話模式及額度安排屬 D。

商業主張

實體商品提供送禮及收藏價值;語音體驗提供重複使用理由。初期定位為家庭娛樂與親子互動,不宣稱治療、心理輔導、托兒或已證實學習成效。付費者為家長,使用者為兒童;角色風格及內容必須服從安全及家長設定。

決策門檻目前判斷下一步證據
地區與供應商香港未列於 OpenAI API 支援地區 [S4]取得適用供應商的書面資格確認;不能以海外閘道當作規避方案
兒童資料13 歲以下資料處理須先落實 ZDR [S3]確認帳戶、端點及模型可用的設定與合約
商店解鎖實體玩具 QR 並非自動豁免 IAP [S6]提供 QR 及 IAP 替代解鎖方案供商店審閱
單位經濟示例基準貢獻為每隻 HK$147.10以廠商報價、渠道條件及真實語音用量取代假設
體驗與安全廣東話、打斷及風險攔截未經實測受控原型、紅隊測試、家庭試點

建議投入方式

分階段批核資源:先做政策與技術可行性驗證,其後完成交易及支付閉環,最後進行 50 至 100 戶家庭的受控試點。任何重大安全事件、扣額不一致、地區資格未解決或未取得兒童資料處理條件,均阻止正式推出。

本財務個案以一批 1,000 隻公仔作情境分析,不代表市場預測。基準情境不足以單批收回開發投入,因此須驗證復購、渠道效率及成本,再決定擴大生產。

2 市場定位與需求驗證

A 目標客群:以香港用語及廣東話體驗服務家庭,初步研究 6 至 11 歲兒童及其家長;年齡範圍、首發地區及供應商合規尚待決定。香港文案定位不代表 OpenAI 可合法向香港終端使用者提供服務。

可比較方案常見購買理由QBear 建議差異
一般毛公仔外觀、手感、送禮保留實體價值,加上可重複互動的角色
預錄故事玩具可預期、安全、離線自然輪流對話;需承擔網絡及生成內容風險
通用語音助理廣泛問答、方便廣東話角色、明確分鐘額度、家長控制
兒童學習 App內容進度及學習任務親子娛樂優先;學習成效須另行研究

上表是定位框架,並非經市場調研證實的競爭排名;不引用未核實市場規模、增長率或競品售價。護城河應來自角色 IP、可靠的廣東話體驗、供應鏈及家長信任,而非只依靠通用模型能力。

推出前需求研究

研究樣本與方法 A決策指標 P
家長訪談15 至 20 戶;測試故事、閒談及送禮情境家長能準確理解扣額、AI 身分及資料流向
支付意願HK$249/299/349 隨機概念測試記錄實際預訂或付費意向;不以點讚代替收入
受控體驗50 至 100 戶;4 星期啟動完成率 ≥90%;第 4 周家庭使用率 ≥30%
增值驗證體驗用完後由家長選擇觀察付費轉換及退款;不向兒童施壓購買

推廣與渠道

先採官網預訂、實體示範及小型合作渠道,並分別計算每渠道的實收、退貨及獲客成本。App 內增值使用商店支付;公仔實物交易在實物銷售渠道完成。試點不設廣告、兒童社交、公開分享或網頁搜尋。

3 商業模式與產品規則

項目MVP 規則狀態
初次啟動每隻公仔唯一 QR 一次兌換,原子加 3,600 秒D
額度歸屬秒數屬公仔;同一家庭多隻公仔互不共享D
增值家長選定公仔後,以 IAP 購買秒數包D/P
並行使用每隻公仔最多一個語音 session;不同公仔可各自使用P
跨裝置同一家長登入取回伺服器餘額;不信任本機計時P
有效期建議已贈送與已購秒數均不自動到期P 待商業及法律確認
移轉與失物MVP 不設自行轉售移轉;客服核實後走審計流程P
服務終止銷售條款列最低服務期限及未用額度處理A 待定

家長與兒童使用旅程

家長註冊 → 完成年齡及資料處理同意 → 掃碼啟動 → 選擇公仔及角色 → 同意咪高峰權限 → 開始對話 → 顯示剩餘時間 → 暫停或結束 → 家長專區增值。購買、帳戶變更、外部連結及資料刪除均放在家長關卡後。

計時的消費者說明

P 計時以「已就緒的對話時間」為準,包括聆聽、模型思考及播放;建立連線、權限等待、暫停、斷線及服務故障不扣時間。連續沒有有效互動 20 秒自動暫停,之後須明確按繼續。App 開始前要說明這是對話時間,不是只計兒童說話的秒數。

P 每次 session 最長 15 分鐘,家長可設每日上限及休息提醒;提示剩餘 60 秒及 10 秒。餘額不足時停止語音,兒童畫面只請家長協助,不展示催促購買或倒數促銷。

收入與責任分配

公仔銷售收入承擔製造、渠道、獲客及贈送語音成本;增值收入承擔平台費及後續語音成本。贈送 60 分鐘是未來服務承擔,不能只以當月實際使用成本判斷盈利;模型另列全數兌換情境。

4 收入與成本假設

以下全部為 A,貨幣為港元,分析窗口為購買後 12 個月。沒有任何數字是 OpenAI 模型報價或廠商已確認成本。模型只估算貢獻及回本量,未包括稅項、營運資金、存貨減值或會計收入遞延。

變數基準 A說明
公仔建議零售價 P299消費者售價;非市場驗證結果
渠道折讓 d20%品牌實收 P × (1−d) = 239.20
製造與包裝 K55公仔、印刷、防刮標籤及品控
物流與售後 S15品牌每單成本;不含重複配送極端情境
獲客成本 CAC25每隻銷售分攤;需按渠道分開量度
贈送使用率 u60%每隻預期用 36 分鐘;另測試 100%
語音全變動成本 c0.20/分鐘情境輸入,含模型、轉錄、安全檢查及閘道流量
增值包 Q 與時長 m38/60 分鐘IAP 商品假設;不等於已核準 SKU
平台綜合扣減 f30%保守情境;15% 只作敏感度,不代表資格
退款準備 r3%以增值總額另扣;平台退款結算需實測
12 個月付費率 a25%每隻公仔所屬家長的增值付費比例
付費者購包數 b3每名付費者 12 個月總包數
開發投入 Fdev450,000工程、設計、測試及評審示例
固定營運 Fops10,000/月人手與固定雲端費;避免與 c 重複

如何取得可用的語音成本

P 以至少 100 個受控 session 量度廣東話、長短故事、打斷及靜音情境,收集供應商實際 usage 與帳單。c = 所有變動費用 ÷ 已結算對話分鐘。模型價目按音訊/文字輸入輸出、快取及其他計費項逐一乘用量,再換算港元;不可只把每 token 售價直接稱為每分鐘成本。

5 單位經濟與敏感度分析

全部結果由第 4 章情境假設計算。每個增值包以全部 60 分鐘使用作保守成本準備;未用秒數仍屬待提供服務。

Ctoy = P × (1−d) − K − S − CAC − 60 × u × c
Cpack = Q × (1−f−r) − m × c
Cunit = Ctoy + a × b × Cpack
回本銷量 = ceil((Fdev + 12 × Fops) / Cunit)
基準計算結果 HK$含義
公仔貢獻 Ctoy137.00239.20 − 55 − 15 − 25 − 7.20
增值每包貢獻 Cpack13.4638 × 67% − 60 × 0.20
每隻預期增值貢獻10.09525% × 3 × 13.46
每隻總貢獻 Cunit147.095不包括固定營運與開發投入
首年回本量3,876 隻570,000 ÷ 147.095 向上取整
語音 c 每分鐘CpackCunit首年回本量
HK$0.1019.46155.203,673
HK$0.2013.46147.103,876
HK$0.401.46130.904,355
HK$0.60−10.54114.704,970

增值包盈虧平衡 c = 38 × 67% ÷ 60 = HK$0.4243/分鐘。若實測超過此值,增值每包將錄得負貢獻;須調整售價、分鐘數、供應商或對話輸出上限。

單一變動其他保持基準每隻總貢獻 HK$
公仔零售價249/299/349107.10/147.10/187.10
贈送額度使用率40%/60%/100%149.50/147.10/142.30
平台扣減15%/30%151.37/147.10
增值付費率10%/25%/40%141.04/147.10/153.15

5 財務批次情境與決策含義

1,000 隻公仔、每隻 HK$299,品牌公仔實收 HK$239,200。基準增值包數 = 1,000 × 25% × 3 = 750 包;增值總額 HK$28,500,扣除平台及退款準備後 HK$19,095。總淨收入 HK$258,295,變動成本 HK$111,200,總貢獻 HK$147,095。扣首年固定營運 HK$120,000 後,尚餘 HK$27,095 抵付開發費;再扣 HK$450,000,示例結果為 −HK$422,905。

合併情境 A保守基準較佳
售價/渠道折讓249/30%299/20%349/15%
CAC/贈送使用率40/100%25/60%20/40%
c/平台扣減0.40/30%0.20/30%0.10/15%
付費率/購包數10%/225%/340%/4
每隻總貢獻40.592147.095244.506
1,000 隻首年結果−529,408−422,905−325,494
首年回本銷量14,0433,8762,332

可用的管理結論

情境顯示售價、渠道折讓及獲客成本的影響,比在低付費率下增加增值包數更大。不能以未驗證的復購收入掩蓋首批開發成本。公仔若在零售渠道大量折讓,應按品牌實收計算,而非按標價認列現金收入。

P 正式財務模型須加入模具、首批最低訂購量、退貨率、貨款帳期、稅務、支付匯率及服務準備。每月以實際使用率重估贈送及已購未用秒數的未來成本,並監控現金與存貨。

成本控制規則

閘道按公仔、家庭及全項目設定限額;每次最多 15 分鐘、無互動自動暫停、限制角色回覆長度、精簡會話上下文、取消被打斷的生成,並限制並行 session。模型變更先經廣東話及安全評測,再由伺服器版本化發布。全項目達日預算 80% 告警、100% 停止新 session;既有 session 在已核準租約結束後停止。這些百分比屬 P。

6 風險與里程碑

風險程度控制及負責人
香港終端服務資格重大產品/法務取得書面確認或選擇合規替代供應商;未解決不推出
兒童資料與 ZDR重大私隱負責人確認端點、合約及啟用證據;不用真實兒童資料做早期原型
生成內容傷害重大安全負責人紅隊、風險攔截、受控 fallback 及停用開關
QR 外流與盜領高隱藏兌換碼、防拆包裝、HMAC 雜湊、限速、客服核實
IAP/QR 審批高提交演示 QR、替代 IAP 解鎖及帳戶刪除流程;產品負責
成本或需求落差高小批量、真實成本量度、渠道分開核算;商業負責
帳本錯誤/支付重播高唯一鍵、列鎖、冪等、對帳及 outbox;工程負責
斷線/供應商中斷中高租約硬停止、故障不扣額、重試及公告;營運負責

以上程度是 P 的項目評估,並非統計事故機率。每項須有實際負責人、截止日期及關閉證據。

階段 A建議時間交付與出口條件
M0 可行性第 1 至 2 周地區、兒童資料及 QR/IAP 路徑有可行證據;財務輸入清單
M1 語音原型第 3 至 4 周成人/合成測試音訊;廣東話、打斷、前景切換及安全 gate
M2 帳本與 App第 5 至 7 周QR、錢包、短租約、RLS、客服工具;重播及並行測試通過
M3 支付及安全第 8 至 10 周雙商店 sandbox、退款、資料刪除、紅隊及對帳
M4 家庭試點第 11 至 14 周完成同意與資格後 50 至 100 戶;成本及安全達標
M5 推出評審試點後商店批準、無重大缺陷、回復演練、服務及售後條款

A 人手基線:1 名產品/設計、1 至 2 名 Flutter 工程師、1 名後端工程師、兼職 QA、安全與法務支援。時間須按審批、供應商回覆及人手調整;商店審批不保證在此窗口內完成。

7 整體系統架構

App 經語音閘道接入供應商,Supabase 管理身份和交易,支付通知及營運服務在伺服器處理。

P 控制平面由 Supabase Auth、短請求 API 及 PostgreSQL 組成;媒體平面由可維持長連線的 Voice Gateway 承擔。不要把長時間雙向音訊代理寄望於未驗證時限及資源限制的 Edge Function。

元件責任信任邊界
Flutter介面、收音播放、家長流程只能顯示秒數;不決定扣額或供應商參數
業務 API驗證 JWT、所有權、QR、IAP、session不接受客戶端聲稱已付款或已使用時間
Voice Gateway音訊轉發、租約硬停止、安全 gate持有供應商密鑰;簽署計時事件
PostgreSQL列鎖、唯一約束、錢包與帳本金額及秒數的唯一事實來源
營運 worker回收、通知、對帳、刪除任務最小權限;每次操作有審計

P dev/staging/production 分開帳戶、資料庫與密鑰;App 僅使用公開連線金鑰及個人 access token。service_role、供應商密鑰、QR pepper 及支付私鑰只存於伺服器 secrets。

8 Flutter iOS 與 Android 設計

模組主要職責交付要求
auth_parent家長登入、刪除帳戶、重新驗證Keychain/Keystore 儲存 token;敏感操作重新認證
consent_safety年齡組別、同意版本、家長關卡撤回同意即停止語音;不能只依賴本機旗標
doll_activation相機掃碼、deep link、啟動結果只接受指定域名;掃碼後需登入及確認
wallet餘額、可用秒數、session 結算以伺服器 revision 更新;不以本機倒數寫帳
voice收音播放、打斷、網絡及音訊焦點可測試 transport 介面;native audio adapter
parent_store選定公仔、商店商品、購買恢復處理 pending、cancel、success、retry
settings_support每日限額、休息、私隱、客服家長區管理;不提供兒童外部連結

分層與狀態管理

P 採 feature 分模組及 presentation/domain/data 分層;狀態管理可採 Riverpod 或 BLoC,先選定一種。Repository 封裝 Auth、業務 API、wallet stream 與 IAP;VoiceTransport 提供 connect/sendAudio/cancel/close,供應商與傳輸細節不散落於 UI。套件及 SDK 版本在工程啟動時鎖定。

idle → connecting → ready → active
active → paused → active
active → reconnecting → connecting
任何狀態 → stopping → settled
安全/租約/餘額故障 → blocked → settled

P 只有收到 Gateway ready 且租約有效才進入 active。UI 顯示本機推算值供倒數,重新連線必須取得伺服器快照。不能在 offline 狀態開始新對話。

裝置生命週期

iOS 設定適當 audio session、咪高峰用途文字及中斷處理;Android 處理 RECORD_AUDIO、audio focus 及路由變更。MVP 進入背景、鎖屏、來電、權限撤回或耳機拔除時立即暫停並通知 Gateway;Gateway 另以連線與租約停止作兜底。藍牙耳機、回音消除、兒童細聲、雙語夾雜及低階 Android 均納入實機測試。

9 Realtime 語音與 Voice Gateway

F OpenAI Realtime 支援即時語音對話及打斷;官方文件提供 WebRTC 與伺服器 WebSocket 路徑 [S1]。本文件選擇 P 的 Flutter → Gateway → 供應商受控代理,令安全、費用與租約由同一伺服器控制。OpenAI 實作須先符合地區與兒童資料條件。

媒體與控制流程

App 先取得 session 專用短效 Gateway token,再以 TLS WebSocket 傳送有序音訊 frame。Gateway 連供應商,管理 voice、instructions、turn detection、輸出上限及取消。模型名稱、snapshot、聲線及可用 codec 由伺服器 allowlist 選定,配置及提示詞有版本;不在文件填入未核實價格。

項目P 基線驗證方式
App 傳輸WSS 二進制 frame;20 至 40 ms握手協商音訊格式、序號及 session ID
供應商格式按所選端點轉為支援格式不要把舊 beta 事件混用 GA 事件
緩衝與背壓有限播放及發送 queue;超時暫停模擬弱網及高延遲,禁止無限累積
話輪偵測伺服器 VAD 加可調靜音門檻廣東話尾音、背景電視及細聲測試
打斷即時清空播放;取消供應商輸出同步實際已播放位置,截斷未聽內容
上下文session 內有限內容;不跨 session 自動記憶長故事成本、安全及 token 用量壓測

可撤銷的連線與故障模式

Gateway token 綁定家長、公仔、session、裝置及 nonce,建議 60 秒內使用一次;連線後的權限由租約控制。每個 session 只有一個 media owner,故障移轉使用 fence_epoch 擋住舊 owner。所有輸入、輸出及新 response 都要檢查租約;到期須停止上下游連線及清空播放。

供應商超時、連線斷開或安全檢查不可用,轉 paused,不自動消耗新的額度。以本地預錄中性訊息通知家長。重連需重新取得有效租約,不能拿舊 token 延續免費對話。WebRTC 直連加 sideband 可作未來低延遲選項,但只有可證明伺服器強制停止及安全控制時才採用 [S2]。

10 兒童安全與內容控制

F OpenAI 未成年指引要求額外安全保障,包括適齡披露、內容過濾及高風險升級機制 [S3]。P QBear 明確告知「這是 AI 角色,可能答錯」,不假裝真人,不要求保密,不建立排他關係,不引導兒童依賴付費互動。

層級P 控制失效時處理
開始前家長同意、年齡組別、角色白名單、無開放工具未符合資格不開音訊 session
輸入短暫轉錄/分類;個資、虐待、自傷及性內容偵測低信心使用澄清或安全預錄回應
生成適齡系統指示、短回覆、禁止廣告/秘密/診療不能只靠 system prompt 當安全保證
輸出 gate生成的音訊先緩衝,對齊文字分句並檢查未核準的句子不向 App 播放
事後最少化風險類別、家長舉報、人工升級重大事故停用角色/模型/項目

即時語音的安全取捨

Realtime 音訊若直接串流播放,事後轉錄攔截可能來不及。P MVP 使用分句輸出 gate:Gateway 等到可對齊的文字及音訊片段完成安全判定,再放行;未放行音訊不得送到手機。這會增加延遲。若端點無法可靠對齊或延遲未達標,改用「轉錄 → 受控文字生成與檢查 → 語音播放」的受控模式;不得以降低安全標準維持即時感。

輸入檢查是補充控制,音訊仍可能先到供應商;不能承諾兒童個資不會傳出。所有轉錄及安全端點亦須符合地區與保留條件。MVP 不使用任意網頁搜尋、社交、支付工具或外部網址。

高風險處理與家長溝通

自傷、虐待或危險行為觸發適齡的安全支持及求助提示,停止有害指引,按預先評審的程序升級。涉及可能施害家長時,不能機械式把逐字內容推送該家長;須由兒童保障顧問制定處理。App 不宣稱 24 小時有人監察,不自動向外部人士傳送訊息,亦不代替緊急服務。

P 發布門檻:完成至少 300 個廣東話、夾英文及提示注入情境;嚴重有害音訊不得放行,誤攔截及延遲另記錄。零事件只代表測試樣本結果,不是永遠安全的保證。

11 私隱與資料治理

F 香港私隱框架包括目的與收集、準確與保留、用途、安全、透明及查閱改正等保障原則 [S10]。P 須完成資料流程評估、兒童適齡說明及家長同意;家長關卡並不自動等於法律上已取得充分同意。

F OpenAI 對 13 歲以下或未達適用數碼同意年齡兒童的個人資料處理要求先落實 ZDR [S3]。ZDR 須獲批準且有端點限制;Realtime 官方資料表列為可用 ZDR,但本項目的帳戶、模型及合約仍待確認 [S5]。不落地儲存音訊並不等於沒有收集或處理。

資料P 收集範圍P 保留/存取
家長帳戶登入 email、同意版本;不預設收地址至刪除;驗證後提供查閱與改正
兒童設定年齡組別、隨機 ID、可選暱稱不收出生日期、學校、定位或聲紋
原始音訊只為當次語音與安全 gate 處理本方 RAM 短暫緩衝,session 結束清除
轉錄與對話僅當次上下文及風險判定預設不寫入 DB、logs 或分析工具
操作事件session ID、秒數、錯誤及成本用量建議 30 日;聚合去識別指標 12 個月
風險事件分類、嚴重度、處理狀態建議 30 日;特殊個案另有法律與最少化評審
支付與帳本交易識別、秒數、退款及審計保留期按稅務及爭議要求確認,不寫死假定年數

刪除及跨境處理

P 家長於 App 提出刪除後,先撤回語音權限、關閉 session、撤銷 refresh session,再排程刪除可刪資料;短效 access token 外,敏感 API 另查帳戶停用旗標。依法需保留的支付記錄去識別化及限制用途。備份有滾動到期政策,復原後重播刪除清單。

P 私隱通知列明 Supabase、語音供應商、支付平台、處理地區、用途、保留、查閱及投訴渠道。先取得處理合約及次處理者清單;跨境及其他市場法例由專業顧問確認。Kids 分類、第三方分析及語音資料傳輸另作商店審批,不以一般家長同意推定符合所有商店規則 [S6、S9]。

12 PostgreSQL 資料模型與 RLS

P 資料庫以公仔 wallet 與不可覆寫 ledger 為核心;先扣預留再結算。秒數用 bigint 整數,不用浮點分鐘;時間戳用 timestamptz UTC,畫面依使用者時區顯示。SQL 基線見附錄 A;真正 migration 須在隔離環境驗證,並非已執行上線版本。

資料表關鍵欄位/約束用途
parents/consentsauth user FK、停用旗標;同意版本只由家長/服務持有,不設兒童登入
dollsserial UNIQUE;owner_id;state每隻公仔唯一身份及家長所有權
walletsdoll_id PK;balance ≥ reserved ≥ 0可用秒數 = balance − reserved
sessionsdoll_id、owner_id、state、fence_epochpartial UNIQUE 保證每隻一個活躍 session
leasessession_id、grant、settled、expires_at短期預留;每 session 一個 open lease
ledgerdoll_id、delta、kind、dedupe_key UNIQUE贈送、增值、使用、釋放審計及退款調整
purchasesstore+environment+transaction_key UNIQUE商店驗證、指定公仔及通知對帳
qr_codes 私有token_hash UNIQUE;redeemed_at只存雜湊、一次兌換;App 不可讀
outbox/audit 私有event key UNIQUE;重試狀態提交後派發消息;客服操作可追溯

授權矩陣與規則

F Supabase 暴露 schema 的資料表須啟用 RLS;有權連到表與有權讀取某列是不同層次 [S11]。P authenticated 只可讀自己 dolls、wallets、sessions、ledger、purchases 及同意狀態;anon 不可讀。App 不直接寫任何所有權、餘額、QR、lease 或付款欄位。

P 狀態修改全經 JWT 驗證的業務 API,再由受限後端角色執行交易。私有 QR、支付原始資料及 outbox 不暴露到 Data API。不用可由使用者修改的 user_metadata 作授權;RLS 所有權查詢需索引。管理介面使用獨立身份、MFA 及按職責授權。

P 後端函式預設 SECURITY INVOKER;如必須 SECURITY DEFINER,置於非暴露 schema,固定 search_path、驗證 caller 及資源所有權、撤銷 PUBLIC execute。不能用 service_role 當作已驗證家長所有權的替代。ledger 更新/刪除對一般後端角色亦撤銷,修正只可新增補償分錄。

13 QR Code 生成與兌換

生成與實物保護

P 批次管理員為每隻公仔建立 UUID、不可猜 serial 及至少 128 bit CSPRNG 兌換密碼。QR 包含官方啟動 URL 及版本/密碼,不含家長或兒童資料。服務端以 HMAC-SHA-256(token, pepper) 存雜湊,pepper 與版本只存 secrets;不能以連續 serial 作兌換密碼。

P 永久展示 QR 若直接帶兌換密碼,零售展示時可被預先盜領。建議公仔後方保留啟動入口,真正唯一兌換碼用防刮/防拆標籤遮蓋;或採外部入口 QR 加包裝內秘密碼。印刷檔加密傳送、限制人員、批次簽收及按協議刪除。

一次啟動交易

POST /v1/activations/redeem
Authorization: Bearer <parent JWT>
Idempotency-Key: <UUID>
{"qr_token":"<opaque secret>","consent_version":"v1"}
201 {"doll_id":"...","granted_seconds":3600,
     "balance_seconds":3600,"wallet_revision":1}

P 伺服器驗證家長、同意、請求大小及限速後,計算 token_hash 並鎖 qr_codes 列;核對未撤銷、未兌換及批次狀態。依固定鎖序取得 doll 與 wallet,綁定 owner、加 3,600 秒、寫唯一 activation 分錄、標記 redeemed_at 及 outbox,全部在同一交易提交。失敗全部回滾。

狀況回應保障
同家長同碼重試200 原兌換結果不再加額;不同冪等鍵亦不重複送
同碼兩人同時兌換一個成功;另一個 409不透露另一位家長身份
無效/停用/已被領碼泛化錯誤+客服入口降低枚舉及洩漏風險
冪等鍵重用不同 body409 IDEMPOTENCY_CONFLICTbody hash 比對,不返回另一操作結果
交易提交後回應丟失以冪等键重取不得由客服手動再次贈送

P 建議依家庭/IP/批次限速並做異常偵測;碼不可寫入 access log、分析事件、referrer 或 crash 報告。App Link/Universal Link 只開家長啟動流程,瀏覽器 GET 不兌換;未裝 App 可提供安裝指引及手動輸入。客服重發、停用及移轉均需購買證明與審計。

14 業務 API 合約

P 除支付 webhook 外,所有端點需已驗證家長 JWT;server 從 token 解析身份,不信任 body 的 parent_id。OpenAPI 檔必須定義 JSON schema、錯誤碼、限速、冪等、版本及 request_id。以下為應用業務端點,不是供應商原生 API。

方法與路徑主要輸入結果及限制
GET /v1/dollsJWT只列自己公仔與狀態
GET /v1/dolls/{id}/walletidbalance、reserved、available、revision
POST /v1/activations/redeemqr_token、同意版本一次啟動;見第 13 章
POST /v1/sessionsdoll_id、device_noncesession_id、一次 Gateway token、protocol
POST /v1/sessions/{id}/pauseIdempotency-Key停媒體、結算並釋放;可重試
POST /v1/sessions/{id}/closeIdempotency-Key最後結算;回應權威餘額
POST /v1/purchase-intentsdoll_id、store、product_id綁定家長、公仔與服務端商品映射
POST /v1/purchases/verifyintent_id、商店憑據驗證後一次加秒數;pending 不加額
POST /v1/consents版本、明確選項保存與撤回;撤回停止 session
DELETE /v1/account重新驗證 token202 排程;即時停用語音
POST /internal/leases/*Gateway mTLS/服務簽章reserve、settle、release;不給 App
POST /webhooks/apple 或 google平台簽名通知驗證來源、去重、記錄,再異步處理

Session 建立的兩階段流程

POST /sessions 先鎖定同一公仔的活躍 session,設定 connecting;尚不扣額。Gateway token 只容許連到此 session。媒體就緒後 Gateway reserve 租約並發布 ready;若權限或供應商連線失敗,關閉 connecting 並不扣額。connecting 最多保留 30 秒,超時由 worker 清理。

錯誤語意

401 未登入/token 失效;403 所有權或資格不符;404 未存在或不可見資源;409 已啟動、活躍 session 或冪等衝突;422 無效輸入;402 INSUFFICIENT_SECONDS;429 限速;503 供應商或安全服務不可用。前端只重試明確可重試錯誤,採帶隨機抖動的指數退避。所有回應避免回傳密碼、付款原文或兒童內容。

15 計時租約與秒數規則

P 每個 active session 一次預留最多 15 秒。Gateway 每 5 秒結算已用量;最多一個 open lease。新租約只在前一租約已結算並關閉後開始,或以單一交易原子續租。App heartbeat 只作連線訊號,不能直接造成扣額。

值或不變量規格 P
balance_seconds未消耗的全部秒數;贈送與增值均計入
reserved_secondsopen lease 尚未消耗的預留總額
available_secondsbalance − reserved;任何時候不得小於 0
lease.granted_secondsmin(15, available);不足 15 亦可使用剩餘秒數
lease.settled_seconds此租約已結算的累積整數秒數,單調遞增
期限與 fencingDB expires_at + Gateway monotonic deadline;兩者取較早
可信時間Gateway monotonic 計量;DB 時間作許可及回收,不用手機時鐘
取整session 累積毫秒轉整數;中途 floor,最終一次 ceil

計時定義與斷線處理

Gateway ready、租約有效且媒體連線健康時才開始 billable clock;靜音仍屬對話,但 20 秒無互動自動暫停。供應商失敗、App 暫停/背景、媒體斷開或安全 gate 故障立即停止計時。結束一次向上取整,最大取整差少於 1 秒,不能每個 heartbeat 向上取整。

P 每次結算傳 session_cumulative_billable_ms、lease_id、fence_epoch 及不可重複的事件 ID;伺服器驗證簽章及單調性,計算尚未結算差額。每個租約的差額不得超出已預留量,且不接受到期後產生的使用。跨租約保留毫秒餘數;新租約允許的毫秒上限為「已扣整數秒+預留秒數」×1,000,須扣除前段餘數,確保最終向上取整不超額。

硬停止與回收

租約到期前完成續租才可繼續;若 DB 不可用、餘額不足或舊 Gateway fence 不符,Gateway 到 deadline 必須取消生成、清空音訊並關閉媒體。持有短效 token 本身並不能延長租約。

P 崩潰後只結算有可信持久化證據的時間;未能證明的尾段釋放並由營運承擔成本,不直接扣足整個預留。若最後證據每 5 秒持久化一次,未收費尾段風險最多約 5 秒;未收到任何證據時最多 15 秒成本須自行承擔。回收任務可每 5 秒掃描,但安全停止不能等待回收任務。

16 原子扣額與交易一致性

預留只限制可用額度;結算才減少總餘額,結束釋放未使用的預留。
BEGIN
鎖 wallet → session → lease(所有交易同一鎖序)
校驗 owner、state、fence、期限與 idempotency body hash
若 event_id 已成功處理:返回原結果
d = 新累積已用秒數 − 上次累積已用秒數
要求 0 ≤ d ≤ granted − settled
wallet.balance -= d;wallet.reserved -= d
lease.settled += d;session.billed += d
INSERT ledger(kind=usage, delta=-d, dedupe_key=event_id)
INSERT outbox(wallet_changed, revision)
COMMIT

預留及釋放交易

reserve 在列鎖內計算 available,增加 reserved、建立唯一 open lease 及其 deadline,不減 balance。release 在同樣鎖序下把 granted − settled 從 reserved 扣除、關閉 lease,並以 delta=0 的分錄記錄釋放量;重試不得再次釋放。續租原子完成舊 lease 最終結算、釋放及新 lease 預留,沒有雙重預留或無許可空窗。

一次性與帳本對帳

QR activation、IAP transaction、usage event 及 refund 各有全局唯一 dedupe_key。DB 約束確保同一事件只產生一次效果,網絡本身仍可至少一次傳遞。冪等記錄須保留足夠長並與資源及 body hash 綁定;永久付款去重不能只靠短期 API 快取。

P 每日檢查 wallet.balance = 所有 ledger.delta_seconds 之和;reserved = 所有 open lease 未用額;每隻最多一個 active session;同一付款只加一次。任何違反即停該公仔新 session,保留證據並作補償分錄,不手改餘額。附錄 B 提供結算 SQL 範例與併發驗收。

17 Apple 與 Google 應用程式內購買

F Google Play 的數碼功能/服務內購一般須使用 Play Billing [S7];Apple 的數碼解鎖亦受 IAP 及例外規則約束 [S6]。P 增值採 consumable 秒數包,由家長操作。實體公仔以實物支付處理,不能推定隨公仔附送的 QR 數碼權利自動獲豁免。

商店與公仔绑定

P 家長先選 doll,再由 server 建立 purchase_intent,鎖定 owner、doll、平台、SKU 及秒數。App 僅顯示商店返回的本地化價格;不由客戶端指定加額。Apple 使用 appAccountToken、Google 使用 obfuscatedAccountId 等平台提供的帳戶關聯欄位,並由後端驗證與內部映射。

階段Apple PGoogle P
發起StoreKit 2/已驗證 Flutter 橋接Play Billing/已驗證 Flutter 橋接
服務端驗證JWS/Server API;bundle、環境、SKU、交易Developer API;package、SKU、token、狀態
付款狀態pending/Ask to Buy 不加秒數PENDING 不加秒數;只接 PURCHASED
加額唯一交易鍵;wallet 與 ledger 同交易提交唯一 purchase token;同樣原子加額
完成已持久化加額後 finish;失敗可重試加額後 consume/ack;outbox 持續重試
通知與退款驗證 Server Notifications;去重對帳驗證 RTDN/服務身份;再查 Developer API

F Google 建議安全後端驗證及加權後即時確認,consumable 以 consume 處理 [S8]。P 用 outbox 確保 DB 已提交後商店確認失敗仍可重試;不能在本機看見 success 就加額。

退款與恢復

P 退款事件以唯一鍵記錄。先停止相關公仔 session,再以補償分錄撤銷未用秒數;已用部分記錄 recovery_debt_seconds 並凍結新使用/增值,按客服政策處理,不能讓 wallet.balance 變負數。是否撤銷全部購包額、只撤未用額、解除凍結或吸收損失,須法務與商業定案。

P 已消耗商品不依賴商店 restore 還原秒數;跨裝置以同一家長登入讀取後端帳本。未完成交易重新驗證,已完成交易返回原結果。無法對應公仔的交易進入人工對帳,不能任意加到當前選中的公仔。

F Apple 3.1.4 對配合獲批準實體玩具的 optional 功能提出 IAP 替代選項條件 [S6]。P 建議提交可在家長區經 IAP 取得等效角色/啟動權利的替代路徑,與 QR 規則分開識別,並取得商店審閱;不是批準保證。

18 監控與營運控制

監控領域P 指標與告警行動
體驗連線成功率、首音延遲、打斷延遲按 OS、網絡、角色及模型版本分群
帳本餘額不變量、重複付款、未回收租約任何不一致即停受影響公仔使用
成本每已扣分鐘全成本、每日費用、異常用量日预算 80% 告警;100% 停新 session
支付待驗證、待 consume/finish、退款對帳差5 分鐘積壓告警;每日與平台核對
安全攔截率、嚴重事件、舉報、誤攔截重大事件立即隔離角色/模型並升級
服務Gateway 連線數、queue、DB 延遲及錯誤背壓、容量擴展、停止新使用
私隱敏感日誌掃描、刪除任務、備份到期任何音訊/token 洩漏按事件程序處理

記錄與追蹤

P request_id、session_id、lease_id、付款 hash、Gateway epoch 及配置版本跨服務追蹤;不得記原始 QR、JWT、付款 token、音訊或兒童逐字內容。metrics 採聚合,logs 欄位白名單;crash SDK、第三方 analytics 及 trace 須經兒童私隱評審,MVP 優先本方最少化監控。

容量與可靠性假設

A 首批規模假設最多 100 個同時語音 session;以 150 個壓測保留餘量。音訊格式、上行與下行 bitrate、轉碼 CPU、安全 gate 延遲及供應商 rate limit 須實測,再設定 autoscaling、連線上限及每家庭並行限制。Supabase plan、備份與 PITR 能力及費用在採購時確認。

P 試點月可用性目標 99.5%;資料庫 RPO 目標 ≤1 小時、RTO ≤4 小時,須由所購 plan 及還原演練支持。付款驗證不可用時不得加額,已付權利記 pending 並可恢復;資料庫不可用時租約到期停止語音。

事故與客服

P Runbook 包括供應商中斷、費用暴增、QR 外流、錯扣、退款、個資事故及有害回應。操作需經家長身份核實;送秒數或移轉公仔有理由、操作者及唯一補償鍵。高額補償及批次更動採雙人審核,禁止直接改 SQL 餘額。停用開關分全項目、角色、模型及家庭,App 顯示清晰服務狀態。

19 測試與驗收標準

以下為 P 的可量度出口標準。所有計時、支付及授權驗收須先在 staging 通過;語音延遲另需香港語言場景及適用支援地區的實際網絡測量。

測試驗收條件證據
一次啟動100 次同碼並行僅一次 +3,600;不同家長不能取得DB ledger、所有權及回應紀錄
公仔隔離A 公仔使用/增值不改 B;他人 API/RLS 全拒絕直接 Data API 與業務 API 測試
session 併發同公仔 100 次 start 最多一個 activepartial unique 與交易衝突結果
餘額邊界1 秒、0 秒、最後租約與退款同時發生不為負不變量及 ledger 對帳
計時與取整總 billable 60,200 ms 最終只扣 61 秒跨租約、pause、重試及重連測試
斷線與故障背景/斷網停計時;租約到期不再放行音訊封包記錄與 Gateway deadline
重播與 fence結算事件重播不重扣;舊 owner 無法續租/送音訊重排、延遲及崩潰注入
IAPpending 不加;重播僅一次;提交後失聯可恢復雙商店 sandbox、outbox、退款樣本
安全至少 300 情境;嚴重有害音訊零放行人工覆核錄音僅用成人/合成資料
私隱日誌無 token/音訊/逐字稿;刪除流程可追溯掃描、資料清單及刪除演練
語音體驗端點检測完成→核準首音 p95 ≤2.5 秒至少 100 話輪;包含安全 gate
打斷使用者開始說話→停止播放 p95 ≤300 ms實機、耳機與不同網絡
容量與回復150 session 30 分鐘;無帳本差異;回復達 RTO負載、故障及備份還原報告

語音門檻如未達標,先調整 gate、傳輸及話輪偵測;不以刪除輸出安全檢查達標。上線前重大/高風險缺陷須清零;其他缺陷須有擁有人及接受理由。首次正式生產改動先小流量發布並設回退。

20 工程交付與責任分工

交付項目最低內容驗收人
Flutter AppiOS/Android 原始碼、鎖檔、簽署與發布指引工程/QA
後端與 GatewayAPI、媒體 proxy、安全 gate、計時、限流、容器及健康檢查後端/安全
DB migrationDDL、RLS、索引、RPC、seed、回滾與不變量測試後端/QA
QR 工具批次生成、雜湊、加密印刷輸出、停用及驗證營運/安全
支付整合SKU 映射、驗證、通知、退款、對帳、outbox工程/財務
合規文件私隱通知、同意、資料評估、服務條款及商店資料法務/產品
品質證據自動測試、實機報告、紅隊、成本樣本及壓測QA/產品
營運交接監控、Runbook、客服權限、備份、密鑰輪換營運/安全

部署與版本管理

P CI 檢查格式、靜態分析、單元及整合測試、RLS 及秘密掃描;資料庫 migration 先在可重建 staging 執行。支付與語音供應商參數經配置發布,SKU 秒數歷史不可覆寫。應用版本、protocol version、schema version、提示詞及安全策略各自保留變更紀錄。

P 敏感資料與生產密鑰不得放入測試 fixtures。工廠印刷檔與供應商測試帳號獨立管理。DB 回滾不得破壞已支付權利;若 schema 變更不可逆,採相容擴展及補償,而非刪除帳本。

推出批核

產品負責人確認功能與計時文案;工程負責人確認不變量、停止控制及回復;安全負責人確認測試與事故流程;法務/私隱確認地區、兒童資料及條款;商業負責人確認實測成本與售後承擔。商店批準及所有重大門檻的證據入庫後才可批準正式售賣。

21 待決事項與決策紀錄

決策建議/待確認擁有人與時點
首發地區與供應商香港 OpenAI 支援未成立;選合規路徑並書面核實產品/法務;M0
兒童年齡及 ZDR6 至 11 歲只屬假設;先完成處理資格私隱/供應商;M0
語音硬件MVP 手機收音播放;公仔硬件另立案產品;M0
QR/IAP 替代解鎖實物 QR + 家長區等效 IAP 路徑供審批產品/商店;M0–M3
秒數計時口徑採已就緒對話時間;包括聆聽/思考/播放產品/法務;M1
安全輸出模式分句 gate;不能可靠控制時轉受控模式安全/工程;M1
模型及聲線以廣東話、安全、成本評測選定;不預定價工程/安全;M1
售價與增值包299 公仔、38/60 分鐘僅示例商業;M0/試點後
有效期與終止補償建議秒數不過期;服務期及未用權利待定法務/商業;M3
轉售/失物/退款客服移轉政策及已用退款處理待定營運/法務;M3
資料保留及備份本方最少化;支付法定期另確認私隱/財務;M3
人手、容量與預算工期 14 周及 HK$450,000 屬估算項目發起人;M0

如何更新本文件

每次決策記錄編號、日期、擁有人、證據來源、選項與採納理由。A 可在得到報價、量測或審批後轉為 F 或 D;P 在完成工程評審後成為實作基線。若首發供應商改變,須重跑地區、資料保留、語音格式、安全、成本及延遲驗收,不只替換模型名稱。

優先次序:先解決地區及兒童資料處理條件,繼而確定商店解鎖路徑與安全輸出模式,最後驗證成本及用戶需求。尚未解決前可以用成人或合成資料做原型,但不可向家長承諾已具備正式兒童服務。

附錄 A SQL schema 基線一

P 以下是設計用 DDL,供工程團隊轉為 migration 並實測。與 auth.users 整合;public 表提供家長只讀快照,private 表不暴露到 Data API。所有增減額須經後端交易,不能給 App 直接寫入。

create schema if not exists private;
revoke all on schema private from public, anon, authenticated;
create table public.parents (
  id uuid primary key references auth.users(id),
  disabled_at timestamptz, created_at timestamptz not null default now()
);
create table public.consents (
  id uuid primary key default gen_random_uuid(),
  parent_id uuid not null references public.parents(id),
  version text not null, age_band text not null,
  accepted_at timestamptz not null default now(),
  revoked_at timestamptz
);
create table public.dolls (
  id uuid primary key default gen_random_uuid(),
  serial text not null unique,
  owner_id uuid references public.parents(id),
  state text not null default 'new'
    check (state in ('new','active','frozen','retired')),
  activated_at timestamptz
);
create index dolls_owner_idx on public.dolls(owner_id);
create table public.wallets (
  doll_id uuid primary key references public.dolls(id),
  balance_seconds bigint not null default 0, reserved_seconds bigint not null default 0,
  recovery_debt_seconds bigint not null default 0,
  revision bigint not null default 0,
  check (balance_seconds >= reserved_seconds), check (reserved_seconds >= 0),
  check (recovery_debt_seconds >= 0)
);
create table public.sessions (
  id uuid primary key default gen_random_uuid(),
  doll_id uuid not null references public.dolls(id),
  parent_id uuid not null references public.parents(id),
  state text not null check (state in
    ('connecting','active','paused','closed','failed')),
  fence_epoch bigint not null default 1,
  cumulative_ms bigint not null default 0 check (cumulative_ms >= 0),
  billed_seconds bigint not null default 0 check (billed_seconds >= 0),
  started_at timestamptz not null default now(),
  ended_at timestamptz
);
create unique index one_live_session on public.sessions(doll_id)
  where state in ('connecting','active','paused');
create table public.leases (
  id uuid primary key default gen_random_uuid(),
  session_id uuid not null references public.sessions(id),
  granted_seconds bigint not null check (granted_seconds between 1 and 15),
  settled_seconds bigint not null default 0,
  fence_epoch bigint not null,
  issued_at timestamptz not null default now(),
  expires_at timestamptz not null, closed_at timestamptz,
  check (settled_seconds between 0 and granted_seconds),
  check (expires_at > issued_at)
);
create unique index one_open_lease on public.leases(session_id)
  where closed_at is null;
create index expired_leases_idx on public.leases(expires_at)
  where closed_at is null;

附錄 A SQL schema 基線二

create table public.purchases (
  id uuid primary key default gen_random_uuid(),
  parent_id uuid not null references public.parents(id),
  doll_id uuid not null references public.dolls(id),
  store text not null check (store in ('apple','google')),
  environment text not null check (environment in ('sandbox','production')),
  transaction_key text not null, product_id text not null,
  granted_seconds bigint not null check (granted_seconds > 0),
  state text not null check (state in
    ('verified','credited','refunded','disputed')),
  created_at timestamptz not null default now(),
  unique (store, environment, transaction_key)
);
create table public.ledger (
  id bigint generated always as identity primary key,
  doll_id uuid not null references public.dolls(id),
  delta_seconds bigint not null,
  reserved_delta bigint not null default 0,
  kind text not null check (kind in
    ('activation','topup','usage','reserve','release','refund','adjustment')),
  dedupe_key text not null unique,
  session_id uuid references public.sessions(id),
  lease_id uuid references public.leases(id),
  purchase_id uuid references public.purchases(id),
  created_at timestamptz not null default now()
);
create index ledger_doll_idx on public.ledger(doll_id, id);
create table private.qr_codes (
  id uuid primary key default gen_random_uuid(),
  doll_id uuid not null unique references public.dolls(id),
  batch_id text not null, token_hash text not null unique,
  pepper_version integer not null,
  revoked_at timestamptz, redeemed_at timestamptz,
  redeemed_by uuid references public.parents(id)
);
create table private.purchase_intents (
  id uuid primary key default gen_random_uuid(),
  parent_id uuid not null references public.parents(id),
  doll_id uuid not null references public.dolls(id),
  store text not null, product_id text not null,
  created_at timestamptz not null default now(),
  fulfilled_purchase_id uuid references public.purchases(id)
);
create table private.api_idempotency (
  scope text not null, event_key text not null,
  request_hash text not null, response jsonb not null,
  created_at timestamptz not null default now(),
  primary key (scope, event_key)
);
create table private.outbox (
  id bigint generated always as identity primary key,
  event_key text not null unique, event_type text not null,
  payload jsonb not null, attempts integer not null default 0,
  available_at timestamptz not null default now(),
  processed_at timestamptz
);
create table private.audit_events (
  id bigint generated always as identity primary key,
  actor_id uuid, action text not null, resource_id uuid,
  reason text, request_id text not null,
  created_at timestamptz not null default now()
);

P purchase_intents 的 store/SKU 由業務 API allowlist 控制;wallet 加額在 purchases 及 ledger 同交易完成。refund 分錄以 purchase_id 追溯,不覆寫原付款;如要逐包計算未用量,可增 credit_lots 及 usage_allocations,以先贈送後最早購包順序消耗。MVP 退款基線採保守凍結與人工對帳,不能假定已能精準分配每包消耗。

附錄 A RLS 與權限基線

alter table public.parents enable row level security;
alter table public.consents enable row level security;
alter table public.dolls enable row level security;
alter table public.wallets enable row level security;
alter table public.sessions enable row level security;
alter table public.leases enable row level security;
alter table public.purchases enable row level security;
alter table public.ledger enable row level security;
revoke all on public.parents, public.consents, public.dolls,
  public.wallets, public.sessions, public.leases,
  public.purchases, public.ledger from anon, authenticated;
grant select on public.parents, public.consents, public.dolls,
  public.wallets, public.sessions, public.purchases,
  public.ledger to authenticated;
create policy parent_read on public.parents for select
  to authenticated using (id = (select auth.uid()));
create policy consent_read on public.consents for select
  to authenticated using (parent_id = (select auth.uid()));
create policy doll_read on public.dolls for select
  to authenticated using (owner_id = (select auth.uid()));
create policy wallet_read on public.wallets for select
  to authenticated using (exists (
    select 1 from public.dolls d
    where d.id = wallets.doll_id and d.owner_id = (select auth.uid())
  ));
create policy session_read on public.sessions for select
  to authenticated using (parent_id = (select auth.uid()) and exists (
    select 1 from public.dolls d
    where d.id = sessions.doll_id and d.owner_id = (select auth.uid())
  ));
create policy purchase_read on public.purchases for select
  to authenticated using (parent_id = (select auth.uid()));
create policy ledger_read on public.ledger for select
  to authenticated using (exists (
    select 1 from public.dolls d
    where d.id = ledger.doll_id and d.owner_id = (select auth.uid())
  ));
-- leases intentionally have no client grant or policy.
-- Never grant client INSERT, UPDATE or DELETE on these tables.
-- private schema remains outside the configured exposed schemas.
revoke all on all tables in schema private from anon, authenticated;
revoke all on all sequences in schema private from anon, authenticated;
revoke all on all functions in schema private from public, anon, authenticated;

工程實作補充

P 後端執行角色只獲所需表的權限;只讀 audit/ledger 與寫入業務函式分開。private 表可加 RLS 作防禦,並在 migration 補上特定服務角色政策。不能只靠表層權限保護會繞過 RLS 的 views;若要公開 view,採 security_invoker 並測試。

P 必測三種身份:未登入、家長 A、家長 B,另測停用帳戶與被撤回的 session。dolls.owner_id、wallets 和 purchases 直接改寫須拒絕。後端服務金鑰即使能繞過 RLS,業務 API 仍須核對家長及公仔所有權。所有 migration 須跑安全 advisor 與 RLS 回歸。

附錄 B 結算交易 SQL 範例

P 以下為後端單次交易範例,命名參數由受信任 Gateway 結算端點提供。不是可直接公開的 RPC。執行前須完成 JWT/服務身份、單調時間、簽名、租約時段、fence 及 event body hash 檢查;失敗 ROLLBACK,不能繼續執行剩餘句子。

BEGIN;
-- Fixed lock order: wallet, session, lease.
SELECT * FROM public.wallets WHERE doll_id = :doll_id FOR UPDATE;
SELECT * FROM public.sessions WHERE id = :session_id FOR UPDATE;
SELECT * FROM public.leases WHERE id = :lease_id FOR UPDATE;
-- Validate IDs, epoch, signed time and replay before updates.
-- Derive :delta server-side; require 0 <= delta <= unspent lease.
UPDATE public.wallets
SET balance_seconds = balance_seconds - :delta,
    reserved_seconds = reserved_seconds - :delta,
    revision = revision + 1
WHERE doll_id = :doll_id
  AND balance_seconds >= :delta AND reserved_seconds >= :delta;
-- Every UPDATE must affect one row, or ROLLBACK.
UPDATE public.leases
SET settled_seconds = settled_seconds + :delta
WHERE id = :lease_id
  AND settled_seconds + :delta <= granted_seconds;
UPDATE public.sessions
SET cumulative_ms = :validated_cumulative_ms,
    billed_seconds = billed_seconds + :delta
WHERE id = :session_id;
INSERT INTO public.ledger
  (doll_id, delta_seconds, reserved_delta, kind,
   dedupe_key, session_id, lease_id)
VALUES (:doll_id, -:delta, -:delta, 'usage',
        :event_key, :session_id, :lease_id);
INSERT INTO private.outbox(event_key, event_type, payload)
VALUES (:event_key, 'wallet_changed', :snapshot_json);
-- Save request hash and response in this transaction.
COMMIT;

額度與事件測試向量

操作balancereservedavailable
啟動後360003600
預留 15 秒3600153585
使用並結算 5 秒3595103585
同一事件重試3595103585
暫停並釋放 10 秒359503595
有效付款增值 3600 秒719507195

不能把 reserved 減少誤當再次扣 balance。每一次預留、結算與釋放均有帳本/審計事件;SQL 條件不取代鎖定後的整體驗證。應以 pgTAP 或等效整合測試、100 路並行、隨機重播及故障注入證明不變量,而非只跑成功路徑。

附錄 C 官方來源與核實紀錄

本文件的 F 只引用以下官方來源,查閱日為 2026 年 10 月 8 日。技術設計、財務及驗收目標屬本項目建議或假設,並非來源已驗證的本項目結果。政策及端點會更新,推出及模型更換前須再核實。

[S1] OpenAI Realtime API

即時語音、WebRTC/WebSocket、GA 接口與事件

https://developers.openai.com/api/docs/guides/realtime

[S2] OpenAI Server side controls

服務端與 sideband 控制路徑

https://developers.openai.com/api/docs/guides/voice-server-controls?voice-api=realtime

[S3] OpenAI Under 18 guidance

未成年保障及 13 歲以下個人資料的 ZDR 條件

https://developers.openai.com/api/docs/guides/safety-checks/under-18-api-guidance

[S4] OpenAI Supported countries

支援地區名單及名單外提供服務風險;香港未列

https://developers.openai.com/api/docs/supported-countries

[S5] OpenAI Data controls

ZDR 審批、限制及 Realtime 資料保留條目

https://developers.openai.com/api/docs/guides/your-data

[S6] Apple App Review Guidelines

1.3、3.1.1、3.1.4、5.1;兒童、支付及實物解鎖

https://developer.apple.com/app-store/review/guidelines/

[S7] Google Play Payments policy

數碼商品/服務內購支付規則

https://support.google.com/googleplay/android-developer/answer/10281818?hl=en

[S8] Google Play Billing security

後端驗證、確認及 consumable 處理

https://developer.android.com/google/play/billing/security

[S9] Google Play Families Policies

兒童受眾、資料、SDK 及披露要求

https://support.google.com/googleplay/android-developer/answer/9893335?hl=en

[S10] 香港私隱專員公署

六項保障資料原則與兒童私隱指引

https://www.pcpd.org.hk/english/data_privacy_law/6_data_protection_principles/principles.html

[S11] Supabase Row Level Security

RLS、列級所有權及函式/view 安全

https://supabase.com/docs/guides/database/postgres/row-level-security

[S12] Supabase Changelog

工程啟動時再核對相關功能變更

https://supabase.com/changelog.md

D 來源紀錄:AI公仔App設計原對話中,使用者選擇 iPhone/Android、自然即時傾偈、每隻公仔獨立一小時及家長增值。以上作為產品決定,不作為價格、法規或模型能力的證據。

沒有符合的章節,請嘗試其他關鍵字。