A LITTLE BEAR. A BIG LITTLE WORLD.
QBear.
陪伴,讓童年
多一點奇妙。
一隻公仔,一段對話。
讓小小的好奇心,有個溫柔的回應。
AI 語音公仔 · iOS & Android MVP
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 至 11 | Flutter、Realtime、安全、私隱 |
| 交易與整合 | 12 至 17 | 資料模型、QR、API、租約、原子扣額、IAP |
| 營運與交付 | 18 至 21 | 監控、驗收、交付、待決事項 |
| 工程附錄 | A 至 C | SQL、交易規格、官方來源 |
範圍與基本名詞
公仔是實體商品及角色載體;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 | 說明 |
|---|---|---|
| 公仔建議零售價 P | 299 | 消費者售價;非市場驗證結果 |
| 渠道折讓 d | 20% | 品牌實收 P × (1−d) = 239.20 |
| 製造與包裝 K | 55 | 公仔、印刷、防刮標籤及品控 |
| 物流與售後 S | 15 | 品牌每單成本;不含重複配送極端情境 |
| 獲客成本 CAC | 25 | 每隻銷售分攤;需按渠道分開量度 |
| 贈送使用率 u | 60% | 每隻預期用 36 分鐘;另測試 100% |
| 語音全變動成本 c | 0.20/分鐘 | 情境輸入,含模型、轉錄、安全檢查及閘道流量 |
| 增值包 Q 與時長 m | 38/60 分鐘 | IAP 商品假設;不等於已核準 SKU |
| 平台綜合扣減 f | 30% | 保守情境;15% 只作敏感度,不代表資格 |
| 退款準備 r | 3% | 以增值總額另扣;平台退款結算需實測 |
| 12 個月付費率 a | 25% | 每隻公仔所屬家長的增值付費比例 |
| 付費者購包數 b | 3 | 每名付費者 12 個月總包數 |
| 開發投入 Fdev | 450,000 | 工程、設計、測試及評審示例 |
| 固定營運 Fops | 10,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$ | 含義 |
|---|---|---|
| 公仔貢獻 Ctoy | 137.00 | 239.20 − 55 − 15 − 25 − 7.20 |
| 增值每包貢獻 Cpack | 13.46 | 38 × 67% − 60 × 0.20 |
| 每隻預期增值貢獻 | 10.095 | 25% × 3 × 13.46 |
| 每隻總貢獻 Cunit | 147.095 | 不包括固定營運與開發投入 |
| 首年回本量 | 3,876 隻 | 570,000 ÷ 147.095 向上取整 |
| 語音 c 每分鐘 | Cpack | Cunit | 首年回本量 |
|---|---|---|---|
| HK$0.10 | 19.46 | 155.20 | 3,673 |
| HK$0.20 | 13.46 | 147.10 | 3,876 |
| HK$0.40 | 1.46 | 130.90 | 4,355 |
| HK$0.60 | −10.54 | 114.70 | 4,970 |
增值包盈虧平衡 c = 38 × 67% ÷ 60 = HK$0.4243/分鐘。若實測超過此值,增值每包將錄得負貢獻;須調整售價、分鐘數、供應商或對話輸出上限。
| 單一變動 | 其他保持基準 | 每隻總貢獻 HK$ |
|---|---|---|
| 公仔零售價 | 249/299/349 | 107.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%/2 | 25%/3 | 40%/4 |
| 每隻總貢獻 | 40.592 | 147.095 | 244.506 |
| 1,000 隻首年結果 | −529,408 | −422,905 | −325,494 |
| 首年回本銷量 | 14,043 | 3,876 | 2,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 整體系統架構
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 → settledP 只有收到 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/consents | auth user FK、停用旗標;同意版本 | 只由家長/服務持有,不設兒童登入 |
| dolls | serial UNIQUE;owner_id;state | 每隻公仔唯一身份及家長所有權 |
| wallets | doll_id PK;balance ≥ reserved ≥ 0 | 可用秒數 = balance − reserved |
| sessions | doll_id、owner_id、state、fence_epoch | partial UNIQUE 保證每隻一個活躍 session |
| leases | session_id、grant、settled、expires_at | 短期預留;每 session 一個 open lease |
| ledger | doll_id、delta、kind、dedupe_key UNIQUE | 贈送、增值、使用、釋放審計及退款調整 |
| purchases | store+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 | 不透露另一位家長身份 |
| 無效/停用/已被領碼 | 泛化錯誤+客服入口 | 降低枚舉及洩漏風險 |
| 冪等鍵重用不同 body | 409 IDEMPOTENCY_CONFLICT | body 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/dolls | JWT | 只列自己公仔與狀態 |
| GET /v1/dolls/{id}/wallet | id | balance、reserved、available、revision |
| POST /v1/activations/redeem | qr_token、同意版本 | 一次啟動;見第 13 章 |
| POST /v1/sessions | doll_id、device_nonce | session_id、一次 Gateway token、protocol |
| POST /v1/sessions/{id}/pause | Idempotency-Key | 停媒體、結算並釋放;可重試 |
| POST /v1/sessions/{id}/close | Idempotency-Key | 最後結算;回應權威餘額 |
| POST /v1/purchase-intents | doll_id、store、product_id | 綁定家長、公仔與服務端商品映射 |
| POST /v1/purchases/verify | intent_id、商店憑據 | 驗證後一次加秒數;pending 不加額 |
| POST /v1/consents | 版本、明確選項 | 保存與撤回;撤回停止 session |
| DELETE /v1/account | 重新驗證 token | 202 排程;即時停用語音 |
| 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_seconds | open lease 尚未消耗的預留總額 |
| available_seconds | balance − reserved;任何時候不得小於 0 |
| lease.granted_seconds | min(15, available);不足 15 亦可使用剩餘秒數 |
| lease.settled_seconds | 此租約已結算的累積整數秒數,單調遞增 |
| 期限與 fencing | DB 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 P | Google 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 最多一個 active | partial unique 與交易衝突結果 |
| 餘額邊界 | 1 秒、0 秒、最後租約與退款同時發生不為負 | 不變量及 ledger 對帳 |
| 計時與取整 | 總 billable 60,200 ms 最終只扣 61 秒 | 跨租約、pause、重試及重連測試 |
| 斷線與故障 | 背景/斷網停計時;租約到期不再放行音訊 | 封包記錄與 Gateway deadline |
| 重播與 fence | 結算事件重播不重扣;舊 owner 無法續租/送音訊 | 重排、延遲及崩潰注入 |
| IAP | pending 不加;重播僅一次;提交後失聯可恢復 | 雙商店 sandbox、outbox、退款樣本 |
| 安全 | 至少 300 情境;嚴重有害音訊零放行 | 人工覆核錄音僅用成人/合成資料 |
| 私隱 | 日誌無 token/音訊/逐字稿;刪除流程可追溯 | 掃描、資料清單及刪除演練 |
| 語音體驗 | 端點检測完成→核準首音 p95 ≤2.5 秒 | 至少 100 話輪;包含安全 gate |
| 打斷 | 使用者開始說話→停止播放 p95 ≤300 ms | 實機、耳機與不同網絡 |
| 容量與回復 | 150 session 30 分鐘;無帳本差異;回復達 RTO | 負載、故障及備份還原報告 |
語音門檻如未達標,先調整 gate、傳輸及話輪偵測;不以刪除輸出安全檢查達標。上線前重大/高風險缺陷須清零;其他缺陷須有擁有人及接受理由。首次正式生產改動先小流量發布並設回退。
20 工程交付與責任分工
| 交付項目 | 最低內容 | 驗收人 |
|---|---|---|
| Flutter App | iOS/Android 原始碼、鎖檔、簽署與發布指引 | 工程/QA |
| 後端與 Gateway | API、媒體 proxy、安全 gate、計時、限流、容器及健康檢查 | 後端/安全 |
| DB migration | DDL、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 |
| 兒童年齡及 ZDR | 6 至 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;額度與事件測試向量
| 操作 | balance | reserved | available |
|---|---|---|---|
| 啟動後 | 3600 | 0 | 3600 |
| 預留 15 秒 | 3600 | 15 | 3585 |
| 使用並結算 5 秒 | 3595 | 10 | 3585 |
| 同一事件重試 | 3595 | 10 | 3585 |
| 暫停並釋放 10 秒 | 3595 | 0 | 3595 |
| 有效付款增值 3600 秒 | 7195 | 0 | 7195 |
不能把 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、自然即時傾偈、每隻公仔獨立一小時及家長增值。以上作為產品決定,不作為價格、法規或模型能力的證據。
沒有符合的章節,請嘗試其他關鍵字。