Spec: 巡樓後端(Phase C · live)
Status: approved
Status note: 使用者 2026-07-10「spec 通過」
Source: docs/product/haixun-backend/requirements.md(approved)
Last updated: 2026-07-10
修訂:平台 key/BYOK 計費;會員 API 齊全、頁面可不開;§9 嚴格驗收場景全量
1. 摘要
本規格定義 Harbor Desk(apps/web)接 live 時後端必須具備的對外行為與跨切契約。
骨架 搬入並改造 stand-alone-service,撰寫 一律 go-zero(.api → generate → handler/logic/module)。
P0:數字 uid、權限、Threads OAuth、Job/通知、AI·Exa·dev 設定(平台 key+BYOK)。
P1:創作/Outbox 真發送、人設、靈感研究、用量、海巡雙路徑、邀請關係。
會員相關 API 必須完整(含註冊/三方登入等);前端頁面可不提供(現階段 UI 以 admin 代建+email 登入為主)。
Worker 為 獨立 binary;Mongo index init 與 seeder 分包裝。
2. 名詞與實體
| 名詞 |
定義 |
| Member |
租戶內登入主體;主鍵 uid 為數字;以 email(或三方綁定)登入;角色含至少 member/admin |
| Permission |
能力點或角色集合;中介層依 token 內身份擋 API |
| Platform keys |
寫在 系統設定檔(etc) 的 AI/Exa 等金鑰;全站預設用這組代打外部服務 |
| BYOK |
Bring Your Own Key:會員在設定填了自己的 AI 或 Exa key 時,該 meter 改打會員 key |
| Key mode |
單次呼叫解析結果:platform 或 byok(寫入 UsageEvent) |
| ThreadsAccount |
經 OAuth 綁定的 Threads 帳號 |
| ThreadPlay |
互回方案;可指定 schedule_start_at |
| OutboxBundle / OutboxStep |
發送佇列;worker 真打 Threads |
| Job |
背景任務;guarded 更新 |
| Notification |
站內通知 |
| Persona / Brand / Scout* |
人設、品牌、海巡 |
| Invite |
邀請碼、parent_uid(數字 uid) |
| AiSettings / PlacementSettings |
會員級覆寫:provider/model、是否已配 key、dev_mode |
| Usage / Plan |
free|starter|pro;事件含是否 BYOK |
| Gateway / Worker |
go-zero HTTP;獨立 worker |
3. 狀態機
3.1 Member
created (admin 代建 | 自助註冊 API | 三方首次登入) --> email_unverified | active
email_unverified --> active : 驗證成功(規則依註冊管道)
active --> suspended : admin 停權
suspended --> active : admin 復權
* --> password_reset_pending : 申請重設(token 時限)
- 頁面:現階段前端不提供公開註冊/三方登入 UI。
- API:會員域 完整提供(含註冊、三方 OAuth 綁定/登入等),供日後開頁或外部客戶端;未開 UI ≠ 不做 API。
- 停權會員:登入拒絕;既有 token 在 me/中介層視為無效。
3.2 ThreadsAccount(OAuth)
(none) --> authorizing : 發起 OAuth
authorizing --> connected : callback 成功換 token
authorizing --> failed : 使用者拒絕/換票失敗
connected --> connected : refresh session 成功
connected --> expired_or_error : token 失效
expired_or_error --> connected : 重新授權或 refresh 成功
* --> disconnected : 解除綁定(刪除或標記不可用)
is_usable:僅 connected 且未過期可供 lead/cast/發送。
3.3 Job
pending --> queued --> running --> succeeded
running --> failed
running --> cancel_requested --> cancelled
queued|pending --> cancelled
- 更新必須 guarded(樂觀條件或等價);Redis lock value =
workerID(AGENTS.md)。
3.4 OutboxBundle / OutboxStep
Bundle: scheduling --> active --> completed
active --> partial_failed
* --> cancelled
Step: draft|scheduled --> publishing --> published
publishing --> failed
failed --> publishing : retry(規則允許時)
* --> cancelled|blocked
- 真發送:
published 僅在 Threads API(或等價)回成功後寫入。
- root(主貼)失敗: 後續 reply 不得標為 published;可標 blocked/維持 scheduled。
- 步驟
scheduled_at 來自 play 的 schedule_start_at 與間隔;未到時間 worker 不送。
- 單篇 compose 亦必帶可選
schedule_start_at(預設 now)。
3.5 Invite 綁定
parent_uid = null --> parent_uid = inviter_uid : 會員 apply 邀請碼(一次)
parent_uid != null --> (會員不可改)
admin: parent_uid 任意合法調整(防環:不可掛到自己的子孫下)
3.6 海巡掃描路徑
dev_mode_enabled == false --> scan_via_api
dev_mode_enabled == true --> scan_via_crawler (Chrome extension session)
兩條路徑皆須實作;結果統一寫入 ScoutPost 語意。
4. 使用者流程(行為)
4.1 會員進站(P0)
主路徑(現前端):
- Admin 代建島民 → 數字 uid、臨時密碼、角色、邀請碼、可選 parent;密碼僅回傳一次。
- 島民 email/密碼 login → JWT。
- 未驗證信箱:對齊前端閘(僅驗證/登出/有限 profile)。
完整 API(頁面可暫無):
- 自助註冊、寄碼、驗證、三方 OAuth 註冊/登入/綁定解綁等 後端齊全。
- 行為與權限與 admin 代建一致(數字 uid、角色預設 member、產邀請碼)。
- 前端何時開頁不阻擋 API 驗收。
4.2 Threads OAuth(P0)
- 已登入會員請求「開始綁定」→ 系統回授權 URL(含 state)。
- 使用者在 Threads/Meta 完成授權 → callback 至後端。
- 後端換 token、寫入 ThreadsAccount(密文 token 不進 JWT、不進前端永久存儲)。
- 前端列表顯示 username、connection、session 過期語意。
- 刷新 session:成功延長/更新
session_* 欄位;失敗標記 error。
4.3 互回/單篇 → Outbox 真發送(P1)
- 會員存 ThreadPlay(含 steps、lead/cast、
schedule_start_at、間隔)或 compose 單篇(含 schedule_start_at)。
- submit/publishSingle → 建立 OutboxBundle+Steps(
scheduled_at 算好)。
- Worker 到點取步驟 → publishing → 呼叫 Threads → published 或 failed。
- 會員看 Outbox 詳情;失敗可 retry(合規則)。
4.4 海巡(P1)
- 會員維護 Brand/Product;prepareBrief → 可審 scan_terms。
- runScan:讀會員
dev_mode_enabled 選 API 或爬蟲。
- 命中列表可 draft outreach/skip/mark/send(發送仍受 Outbox 或直接回覆契約約束,spec 實作時與前端 mock 對齊)。
4.5 邀請(P1)
- 每位會員有唯一
invite_code。
- 無邀請人:
applyInviteCode 綁定;已綁定拒絕。
- 會員讀自己網絡(邀請人、直邀、延伸計數)。
- Admin:搜尋、焦點調
parent_uid、防環。
4.6 設定 AI/Exa 與金鑰模式(P0)
-
系統預設 key(平台模式)
- 寫在 唯一 etc 設定檔(或 env 覆寫同鍵):至少
- AI:各 provider 的平台 API key(或目前啟用 provider 的 key)
- 搜尋:Exa 平台 API key
- 會員未填自己的 key 時,所有 AI/搜尋呼叫走 platform keys。
-
會員 BYOK
- 會員在設定填寫自己的 AI key 和/或 Exa key → 該類呼叫改走會員 key。
- 讀取 API 只回
*_configured(是否已填),永不回明文。
- 清除會員 key → 回到平台 key。
-
解析順序(每次外部呼叫)
if 會員該 meter 已配置自己的 key:
use BYOK key → key_mode = byok
else if etc 有平台 key:
use platform key → key_mode = platform
else:
fail 明確錯誤(未設定可用金鑰)
- Provider/model:單一 provider(
xai|opencode-go)+單一 model;換 provider 回模型清單並保留同名或預設第一個。
dev_mode_enabled 只影響海巡掃描路徑,與 BYOK 正交。
4.7 用量怎麼算(平台 vs BYOK)— 契約(分開計、分開顯示)
目標:
- 平台代付才扣方案 credits,並計入「平台用量」。
- BYOK 不扣方案 credits,但 必須另計次數,且 報表/API/前端展示與平台用量完全分開,禁止混成一條「總次數」讓人分不清誰付的。
4.7.1 記帳規則
| 情況 |
扣方案 credits |
記什麼 |
event 標記 |
| AI · platform |
是 |
平台點數 + 平台次數 |
key_mode=platform,credits>0 |
| AI · byok |
否 |
僅 BYOK 次數(credits 固定 0) |
key_mode=byok |
| 搜尋 · platform |
是 |
同上 |
platform |
| 搜尋 · byok |
否 |
僅 BYOK 次數 |
byok |
| 混合(同流程 AI+搜尋) |
各 meter 獨立 |
兩筆 event 可不同 key_mode |
例:AI byok、搜尋 platform → 平台只扣搜尋點;BYOK 只加 AI 次數 |
| 雙端皆無 key |
失敗 |
不寫成功用量 |
錯誤可讀 |
| 外部呼叫失敗 |
不扣成功點 |
可選 failed 事件 |
不計入成功 BYOK/平台次數 |
額度檢查(僅 platform): 呼叫前查剩餘 credits → 不足拒絕 → 成功後原子扣點並寫 platform event。
BYOK: 不查方案 credits;可另設 BYOK 專用 rate limit(每分鐘/日,etc 可配);計入 byok_count 系列欄位。
4.7.2 彙總欄位(必須分開,禁止混加)
UsageMonthSummary(或等價)至少拆成兩組,不得只回一個模糊的 total_calls:
| 欄位組 |
含義 |
計入條件 |
| platform |
credits_used、credits_remaining、可選 platform_call_count by meter |
key_mode=platform 成功事件 |
| byok |
byok_call_count 總計 + by meter(如 ai_copy / web_search / ai_research / ai_image) |
key_mode=byok 成功事件 |
- 平台「已用點數」只加總 platform。
- BYOK「使用次數」只加總 byok;不得加進 platform 的 used 或讓 UI 誤顯示為同一進度條。
- 明細
listEvents 每筆必有 key_mode;篩選參數建議支援 key_mode=platform|byok|all(預設 all 但列表仍標示清楚)。
4.7.3 顯示契約(前端/Admin)
| 畫面 |
必須 |
| 會員用量摘要 |
兩塊:① 方案點數(平台)② BYOK 本月次數(可按 meter 拆) |
| 進度條/剩餘 |
只綁平台點數;BYOK 用數字計數,不用同一條 bar 混算 |
| 事件明細 |
每列標示「平台/自己的 Key」 |
| Admin 全體用量 |
平台消耗與 BYOK 次數 分欄或分圖,禁止單一「總呼叫」混加 |
4.7.4 為何
- 經濟清楚:誰付錢、扣誰的額度。
- 產品清楚:會員知道自帶 key 用了幾次,不被算進方案進度。
- 營運可稽核:日後若對 BYOK 加服務費,已有獨立計數。
4.8 失敗通則
- 未實作功能:非 2xx 成功空 data;回明確錯誤碼與 message。
- Auth 失敗:不強制清前端所有 local 狀態的「假成功」;前端依錯誤處理(對齊既有精神)。
5. 介面契約
5.1 跨切
| 項目 |
契約 |
| 成功 envelope |
{ "code": 102000, "message": "...", "data": ..., "error": null }(與 AGENTS.md 一致;若 stand-alone 既有成功碼不同,搬改時統一為 102000 並在 migrate 文件註明) |
| 失敗 |
code ≠ 成功;message 可讀;error 可帶細節;禁止 data 裝成功空列表假裝有實作 |
| 時間 |
全部 UTC unix nanoseconds int64 |
| 列表 |
請求 page(從 1)、pageSize;回應 pagination + list |
| Auth |
Authorization: Bearer <access_token>;refresh 另端點 |
| 權限 |
Auth middleware 解析 uid;Admin middleware/permission 檢查 admin |
| 身分 |
對外 JSON 會員主鍵欄位名 uid 型別數字(JSON number);禁止字串 slug 主鍵 |
5.2 能力分組(對齊 apps/web repos · 路徑前綴建議 /api/v1)
下列為 行為邊界;精確 path 可在 go-zero .api 微調,但 能力不得缺。未列 P2 可不做。
| 域 |
主要行為 |
權限 |
| Auth(API 齊全) |
login, logout, refresh, me, updateProfile, requestPasswordReset, resetPassword, sendEmailVerification, verifyEmail;register;三方 OAuth 登入/綁定/解綁(provider 清單可配置) |
公開/己;UI 可暫不暴露 register/三方 |
| Admin Members |
listPage, get, create, setSuspended, setEmailVerified, setRoles, resetPassword |
admin |
| Threads Accounts |
oauthStart, oauthCallback, list, refreshSession, disconnect |
member |
| Jobs |
list, get, createDemo/依 template 建立 |
member |
| Notifications |
list, unreadCount, markRead, markAllRead |
member |
| Settings |
get/save Ai, get/save Placement, listModels(provider);回傳是否 platform_key_available/member_key_configured(無明文) |
member |
| Personas |
CRUD, setActive, analyzeText, analyzeAccount |
member |
| Plays |
list/get/save/remove, resolveExternalLink, submit, listByPost, listByExternalUrl |
member |
| Outbox |
list/get, retryStep, remove;(發送由 worker,無「假成功」模擬當 live 完成條件) |
member |
| OwnPosts / Mentions / Compose |
ownPosts: list/sync/lastSyncedAt/generateReply/sendReply/analyzePost;mentions: list/generateReply/markReplied/skip;compose: mimic、analyzeViral、publishSingle(+schedule)。不做 generateFromFormula |
member |
| Inspiration / Research / Media |
僅現 UI: trends(list/refresh)、elements CRUD、session(get/save/clear)、chat(chat|generate);research.search;generateImage;attach 真上傳(持久 URL)。不做舊靈感 list/viral/bookmark/spark/searchTrends |
member |
| Usage |
summary, events(含 key_mode), plan, purchase(mock pay), admin tenant summary/analytics, setMemberPrefs |
member/admin |
| Scout |
brands/products CRUD, importProductFromUrl, prepareBrief, runScanFromBrief, posts, outreach*, homework*。不做 ScoutTopic CRUD(現 UI 未用) |
member |
| Invite |
getMyNetwork, applyInviteCode, admin getTree/move/listParentCandidates |
member/admin |
5.3 前端對照(live 必須可驅動)
| 前端區域 |
後端域 |
| Login / Verify / Reset / Profile |
Auth |
| AdminUsers |
Admin Members + Usage prefs |
| Crew 帳號 |
Threads Accounts OAuth |
| Crew 人設 |
Personas |
| Studio 方案/Wizard/單篇+排程 |
Plays, Compose, Outbox |
| Outbox 頁 |
Outbox + Worker |
| Jobs / 鈴鐺 |
Jobs, Notifications |
| Settings |
Settings + 擴充 ZIP(靜態或 downloads API) |
| Scout / Brands |
Scout |
| Invite |
Invite |
| Usage / Plans |
Usage |
5.4 Job template(P0–P1 最低)
| Template |
觸發 |
成功 |
demo 或健康檢查類 |
手動/設定 |
進度跑完 succeeded + 通知 |
persona_analyze_* |
分析人設 |
persona 更新 |
threads_sync_posts |
同步已發 |
own posts 更新 |
outbox_publish_step |
worker 輪詢 |
步驟 published/failed |
scout_scan |
海巡掃描 |
ScoutPost 寫入 |
inspire/research 可選 |
長任務 |
結果落庫或回寫 session |
6. 與現況對照
| 能力 |
Retain |
Replace |
Remove |
備註 |
stand-alone-service go-zero 骨架 |
✅ 結構、middleware、module 模式 |
業務域改巡樓 |
電商 cart/order/product… |
直接搬 |
| stand-alone 多份 etc |
— |
收斂為一份 etc/gateway.yaml(+ env) |
gateway-dev/prod/nas 碎檔常態 |
決策 #9 |
| stand-alone 數字/可產 uid 方向 |
✅ 數字 uid |
對齊巡樓 Member |
前端 mock 字串 uid 不當契約 |
決策 #10 |
| stand-alone permission/admin middleware |
✅ 概念 |
收斂 member|admin |
電商權限雜訊 |
決策 #11 |
apps/web mock 行為 |
✅ 產品語意 |
live HTTP 實作 |
localStorage 當唯一真相 |
行為來源 |
apps/web mock user_* 字串 |
僅 mock |
live 用數字 uid |
當 API 契約 |
|
old/backend 海巡/OAuth/發文 |
演算法參考 |
新 module 重寫 |
整包搬 old |
|
| Outbox 前端 simulate 按鈕 |
mock 可留 |
live 以 worker 真發為準 |
以 simulate 當 live 完成 |
決策 #3 |
| 舊靈感 list/viral/spark… |
— |
— |
Remove;只做現 UI chat 流 |
決策 2026-07-10 |
| generateFromFormula / ScoutTopic |
— |
— |
Remove(現 UI 未用) |
決策 2026-07-10 |
| 附圖 attachLocal |
mock data URL |
真上傳 + 可取 URL |
假上傳當完成 |
決策 #19 |
| Insights 真管線 |
頁面 local 可留 |
— |
Phase C 後端真管線 |
決策 #20 P2 |
| Member uid |
JSON number |
FE toString 顯示 |
字串 slug 主鍵 |
決策 #10+#21 |
| 公開註冊 頁面 |
— |
— |
前端暫不開 |
決策 #1 UI |
| 公開註冊/三方 API |
✅ 完整實作 |
— |
不做半套 |
API 齊、頁可後開 |
| AI/搜尋 key |
etc 平台 key |
會員 BYOK 覆寫 |
僅會員 key、無平台 fallback 當唯一模式 |
§4.6–4.7 |
| Mongo index 混在啟動 |
— |
獨立 init 包 |
啟動時隱式 bulk |
決策 #12 |
| Seeder 混業務 |
— |
獨立 seeder 包 |
與 init 合併 |
決策 #13 |
7. 非功能
- 安全/權限: JWT 鑑權;admin 路由雙重檢查;provider API key 加密或至少與 JWT 分離存儲;OAuth token 僅服務端。
- 錯誤碼: 業務錯誤用穩定數字/scope(可沿 stand-alone FullCode 精神);成功 102000。
- 可觀測: Job
progress_percent/progress_summary;Outbox step error 字串;worker 日誌不含 key。
- 效能邊界(初值): 列表預設 pageSize≤50;Outbox worker 併發可配置;海巡單次命中上限可配置(spec 實作預設 50–100)。
- 設定: 單一 etc 檔;secret 優先環境變數覆寫。
8. 資料保存(邏輯層)
| 實體 |
關鍵欄位 |
生命週期 |
| Member |
uid(number), email, password_hash, roles[], status, email_verified, invite_code, parent_uid?, profile… |
admin 建→用→可停權 |
| ThreadsAccount |
id, owner_uid, platform ids, tokens(server), connection, session_expires_at |
OAuth 建→刷新→解除 |
| ThreadPlay |
id, owner_uid, steps[], schedule_start_at, status |
草稿→提交 |
| OutboxBundle/Step |
id, play_id, steps status, scheduled_at, error |
至 completed/取消 |
| Job |
id, template_type, status, progress_*, error |
終態保留可查 |
| Notification |
id, uid, kind, ref_*, read_at |
可累積 |
| Persona / Brand / Product / Scout* |
owner_uid + 領域欄位 |
CRUD |
| Invite 邊 |
parent_uid on Member |
補填或 admin 調 |
| Platform secrets |
etc/env:AI keys、Exa key |
部署設定 |
| AiSettings / Placement |
per uid:provider, model, member keys configured, dev_mode |
覆寫更新 |
| UsageEvent |
uid, meter, credits, key_mode, created_at |
platform 與 byok 分列 |
| UsageMonthSummary |
platform 點數欄 + byok 次數欄(可 by meter) |
禁止混成唯一總次數 |
| Plan prefs |
uid, plan_id, unlimited? |
只約束 platform 扣點 |
| Indexes |
各集合查詢鍵 |
init 指令維護 |
| Seed |
admin 帳、demo 選用 |
seeder 指令 |
9. 驗收場景(Given / When / Then)— 嚴格 PM 全量
通過標準: 下列每一條皆可自動化或手測重現;任一失敗=該域未過關。
共通斷言(凡成功回應皆適用,下稱「成功 envelope」):
code=102000;message 非空;時間欄位為 int64 unix ns UTC;列表含 pagination(page,pageSize,total,totalPages)+ list;絕不在未實作時回 200/102000 且 data 空成功。
共通斷言(失敗): code≠102000;message 人類可讀;不得洩漏其他使用者密碼/明文 API key/OAuth token。
9.0 基礎設施/工程約束
| ID |
Given |
When |
Then |
| A-01 |
倉庫交付 |
檢視後端專案 |
源自 stand-alone-service 改造;go-zero .api+handler/logic/module 結構存在 |
| A-02 |
交付物 |
列 etc/ |
僅一份主設定檔(如 gateway.yaml);無多份 dev/prod/nas 常態碎檔 |
| A-03 |
交付物 |
建置 |
存在 gateway 與 獨立 worker 兩個可執行目標 |
| A-04 |
乾淨 Mongo |
只跑 init |
必要 index 存在;無強制插入業務種子會員 |
| A-05 |
已 init 空業務 |
只跑 seeder |
種子 admin(或文件約定帳)出現;可重複執行不產生重複關鍵唯一鍵(idempotent) |
| A-06 |
gateway 啟動 |
不跑 seeder |
服務可起、健康檢查 OK;不依賴 seeder 才會 bind port |
| A-07 |
任意 API |
成功 |
成功 envelope;失敗非空成功 |
| A-08 |
文件/契約 |
查會員主鍵 |
uid 為數字(JSON number),非 user_xxx 字串主鍵 |
9.1 Auth · 登入/會話/權限
| ID |
Given |
When |
Then |
| AU-01 |
已 seeder 的有效帳密 |
login 正確 |
102000;回 access+refresh;body 含數字 uid、roles |
| AU-02 |
有效帳 |
login 錯誤密碼 |
失敗;不洩漏「帳號是否存在」以外必要資訊(至少不回 token) |
| AU-03 |
不存在 email |
login |
失敗(與 AU-02 同形可接受) |
| AU-04 |
有效 access |
me |
回當前會員;uid 數字;與 login 一致 |
| AU-05 |
無 Authorization |
me |
401/未授權類失敗 |
| AU-06 |
過期 access |
me |
失敗;不回成功空 member |
| AU-07 |
有效 refresh |
refresh |
新 access(及可選新 refresh);舊 access 策略文件化(作廢或並存短時) |
| AU-08 |
無效 refresh |
refresh |
失敗 |
| AU-09 |
已登入 |
logout |
成功;之後用原 access me 失敗(或等同登出語意) |
| AU-10 |
suspended 會員 |
login |
失敗,message 表明停權 |
| AU-11 |
曾登入後被停權 |
me 或受保護 API |
失敗/拒絕 |
| AU-12 |
非 admin |
任一 admin-only API |
權限失敗 |
| AU-13 |
admin |
同一 admin API |
可成功(其餘條件滿足時) |
| AU-14 |
access 屬於 uid=A |
讀改 uid=B 的私有資源 |
失敗(除非 admin 且 API 允許) |
9.2 Auth · 註冊 API(頁面可不存在)
| ID |
Given |
When |
Then |
| AR-01 |
註冊 API 已部署 |
文件/OpenAPI/.api |
端點存在,可被整合測試呼叫 |
| AR-02 |
合法 email+密碼 |
POST 註冊 |
102000;得數字 uid;預設角色含 member;有 invite_code |
| AR-03 |
email 已存在 |
再註冊 |
失敗 |
| AR-04 |
密碼過短/非法 |
註冊 |
失敗,可讀原因 |
| AR-05 |
註冊成功且需驗證 |
未驗證 login 進主功能 |
對齊閘:主功能拒或僅允許驗證流 |
| AR-06 |
註冊後 |
用回傳或流程寄碼→verify |
email_verified=true |
| AR-07 |
錯誤驗證碼 |
verify |
失敗;verified 仍 false |
| AR-08 |
過期驗證碼 |
verify |
失敗 |
| AR-09 |
前端無註冊頁 |
只打 API |
仍可完整走完 AR-02~AR-06(API 驗收不依賴 UI) |
9.3 Auth · 重設密碼/改密/改資料
| ID |
Given |
When |
Then |
| AP-01 |
已知 email |
requestPasswordReset |
一律成功形(或不洩漏是否存在);有 token 機制 |
| AP-02 |
有效 reset token |
resetPassword 新密碼 |
成功;舊密碼 login 失敗;新密碼 login 成功 |
| AP-03 |
過期/用過 token |
resetPassword |
失敗 |
| AP-04 |
已登入 |
updateProfile 改 display_name |
成功;me 反映 |
| AP-05 |
已登入 |
改 email |
成功後 email_verified=false(或文件約定);需重驗證 |
| AP-06 |
改密 |
錯 current_password |
失敗 |
| AP-07 |
改密 |
正確 current + 合法 new |
成功;新密碼可登 |
| AP-08 |
頭像 |
合法圖上傳/data |
成功;非法格式/過大失敗 |
9.4 Auth · 三方登入 API(頁面可不開)
| ID |
Given |
When |
Then |
| AO-01 |
配置至少一 provider |
開始三方登入 |
回授權 URL 或等價 |
| AO-02 |
首次三方成功 callback |
完成 |
新建 Member 數字 uid;可發 JWT |
| AO-03 |
同三方帳再次登入 |
callback |
同一 uid,不重複建檔 |
| AO-04 |
已 email 登入會員 |
綁定三方 |
綁定成功;之後可用三方登 |
| AO-05 |
解綁 |
呼叫解綁 |
成功後不可再用該三方登(除非再綁) |
| AO-06 |
三方失敗/拒權 |
callback |
失敗可讀;不建半套會員 |
9.5 Admin 島民管理
| ID |
Given |
When |
Then |
| AD-01 |
admin |
createMember 合法 |
數字 uid;臨時密碼僅此次回應;DB 有密碼雜湊 |
| AD-02 |
admin |
create 重複 email |
失敗 |
| AD-03 |
admin |
listPage page=1 |
pagination 正確;list 元素含數字 uid |
| AD-04 |
admin |
query 名稱/email/uid |
只回匹配列 |
| AD-05 |
admin |
setSuspended true |
該帳 login 失敗 |
| AD-06 |
admin |
setSuspended false |
可再 login |
| AD-07 |
admin |
停權自己 |
失敗 |
| AD-08 |
系統僅一 active admin |
停權或降級該 admin |
失敗(至少保留一位) |
| AD-09 |
admin |
setRoles 加 admin |
成功 |
| AD-10 |
admin |
setEmailVerified |
狀態與 verified_at 正確 |
| AD-11 |
admin |
resetPassword 無自訂 |
回新臨時密碼;舊密失效 |
| AD-12 |
admin |
resetPassword 指定密 |
該密可登 |
| AD-13 |
member |
createMember |
權限失敗 |
| AD-14 |
admin |
getUser 存在/不存在 |
有則完整;無則 404 形失敗 |
9.6 Threads OAuth 帳號
| ID |
Given |
When |
Then |
| TH-01 |
已登入 |
oauthStart |
回授權 URL;state 可驗證 |
| TH-02 |
合法 callback |
換票成功 |
帳號列表多一筆;connection=connected;is_usable=true;token 不在 JSON 明文 |
| TH-03 |
使用者拒絕授權 |
callback |
失敗/無 usable 帳;不寫假 connected |
| TH-04 |
換票失敗 |
callback |
failed 語意;可讀錯誤 |
| TH-05 |
已綁帳號 |
list |
屬當前 uid;含 session 過期欄位語意 |
| TH-06 |
connected 帳 |
refreshSession 成功 |
更新 session_expires/refreshed;仍 usable |
| TH-07 |
refresh 失敗 |
refreshSession |
標記 error/不可用;不假裝成功 |
| TH-08 |
已綁 |
disconnect |
列表不再 usable;不可再當 lead 發送 |
| TH-09 |
未 OAuth 只「登記」 |
(若有旁路) |
P0 不得靠旁路算驗收通過;驗收以 TH-02 為準 |
| TH-10 |
他會員帳號 id |
我方 refresh/disconnect |
失敗 |
9.7 Job 與通知
| ID |
Given |
When |
Then |
| JB-01 |
已登入 |
建立 demo Job |
status 從 pending/queued 可查 |
| JB-02 |
Job running |
worker 推進 |
progress_percent 單調不亂跳;summary 更新 |
| JB-03 |
Job 成功 |
終態 |
status=succeeded;completed_at 有值 |
| JB-04 |
Job 失敗 |
終態 |
status=failed;error 非空 |
| JB-05 |
Job 成功/失敗 |
通知 |
對應 uid 有 Notification;unread+1 |
| JB-06 |
有未讀 |
unreadCount |
數字正確 |
| JB-07 |
未讀通知 |
markRead |
read_at 有值;unread-1 |
| JB-08 |
多則未讀 |
markAllRead |
unread=0 |
| JB-09 |
他 uid 的 job |
我 get |
失敗 |
| JB-10 |
非法狀態跳轉 |
強改(若有內部 API) |
guarded 拒絕 |
9.8 設定 · AI/Exa/dev · 平台 key/BYOK
| ID |
Given |
When |
Then |
| ST-01 |
已登入 |
getAi |
有 provider/model;api_key_configured 布林;無明文 key |
| ST-02 |
已登入 |
saveAi 換 provider |
成功;research_* 與 provider/model 一致 |
| ST-03 |
provider=xai |
listModels |
回該 provider 清單非空 |
| ST-04 |
換 provider |
listModels+預設 model |
同名保留否則第一個 |
| ST-05 |
etc 有平台 AI key、會員未填 |
任 AI 呼叫成功 |
key_mode=platform;扣 credits;平台次數+1;BYOK 次數不變 |
| ST-06 |
會員 save AI key |
getAi |
configured=true;仍無明文 |
| ST-07 |
會員已填 AI key |
AI 呼叫成功 |
key_mode=byok;credits=0;方案剩餘不變;BYOK 次數+1;平台 used 不變 |
| ST-08 |
會員清除 AI key |
再 AI |
回 platform(若 etc 有) |
| ST-09 |
etc 無 AI 且會員無 |
AI 呼叫 |
失敗可讀;不假成功 |
| ST-10 |
etc 有 Exa、會員未填 |
搜尋成功 |
platform 扣點;平台搜尋次數+1 |
| ST-11 |
會員填 Exa key |
搜尋成功 |
byok;不扣點;BYOK 搜尋次數+1 |
| ST-12 |
AI byok + 搜尋 platform 同流程 |
各記 event |
平台只扣搜尋;BYOK 只加 AI 次;摘要兩組分開 |
| ST-13 |
platform 額度不足 |
AI 呼叫 |
拒絕、不打外部(預設) |
| ST-14 |
getPlacement |
— |
Exa 語意;dev_mode 布林;無明文 key |
| ST-15 |
savePlacement dev_mode true/false |
get |
值持久 |
| ST-16 |
擴充 ZIP |
GET 下載 |
可下載非空 zip(靜態或 API) |
9.9 用量與方案
| ID |
Given |
When |
Then |
| USG-01 |
新會員 |
getSummary |
plan 預設合理;platform 剩餘與 byok 次數=0 分欄 |
| USG-02 |
僅 platform 消耗後 |
getSummary |
platform used↑ remaining↓;byok 次數不變 |
| USG-03 |
僅 BYOK 消耗後 |
getSummary |
platform remaining 不變;byok_call_count↑(可按 meter) |
| USG-04 |
混合:AI byok + 搜尋 platform |
getSummary |
平台只扣搜尋點;BYOK 只加 AI 次數;兩組數字各自正確 |
| USG-05 |
listEvents |
— |
每筆含 meter、credits、key_mode、時間 ns |
| USG-06 |
listEvents 篩 key_mode=byok |
— |
僅 byok 列;無 platform 列 |
| USG-07 |
listEvents 篩 platform |
— |
僅 platform 列 |
| USG-08 |
回傳 summary JSON |
檢查 |
禁止單一 total_calls 把 platform+byok 加總當唯一欄位(若有 grand total 必須另標且文件化;預設不要) |
| USG-09 |
前端用量頁契約 |
對照 API |
點數條只吃 platform;BYOK 另區顯示次數 |
| USG-10 |
admin tenant analytics |
— |
平台消耗與 BYOK 次數 分欄/分序列,不可混成一條 |
| USG-11 |
member |
purchasePlan(假付款) |
plan 變更;purchase 紀錄 |
| USG-12 |
admin |
setMemberPrefs plan |
該 uid 平台方案變 |
| USG-13 |
admin |
unlimited |
platform 不因額度拒;byok 計數仍獨立累加 |
| USG-14 |
member |
set 他人 prefs |
失敗 |
9.10 人設 Persona
| ID |
Given |
When |
Then |
| PE-01 |
已登入 |
create/save |
得 id;list 可見 |
| PE-02 |
多則 |
setActiveId |
getActive 為該 id |
| PE-03 |
刪除 active |
remove |
active 重設規則明確(空或另一則) |
| PE-04 |
analyzeFromText |
成功 |
status/ready 欄位更新;可落庫 |
| PE-05 |
analyzeFromAccount |
成功或 Job |
結果可查;失敗 Job failed+通知 |
| PE-06 |
他 uid 人設 |
我改刪 |
失敗 |
9.11 互回方案 Play 與單篇 Compose
| ID |
Given |
When |
Then |
| PL-01 |
合法 lead+steps |
save |
持久;get 一致 |
| PL-02 |
reply 帳號不在 speakers |
submit |
失敗 |
| PL-03 |
合法 play + schedule_start_at 未來 |
submit |
Outbox 建立;step.scheduled_at 對齊 |
| PL-04 |
resolveExternalLink 合法 URL |
— |
回 target 預覽欄位 |
| PL-05 |
非法 URL |
resolve |
失敗 |
| PL-06 |
compose 單篇 + schedule 未來 |
publishSingle |
Outbox 一 step;scheduled_at=指定時間 |
| PL-07 |
compose 不傳 schedule |
publishSingle |
scheduled_at≈now(允許小誤差) |
| PL-08 |
無可用帳號 |
publish/submit |
失敗 |
| PL-09 |
刪 play |
remove |
list 不再見;已提交 outbox 策略明確(保留或級聯文件化) |
| PL-10 |
已有綁 ownPost 的 play |
listByPost |
只回該 post 的方案 |
| PL-11 |
已 resolve 外部 URL |
listByExternalUrl |
只回該 URL 正規化後的方案 |
| CP-01 |
已登入+人設 |
compose.mimic |
回仿寫正文;platform/byok 計費適用 |
| CP-02 |
已登入 |
compose.analyzeViral |
回結構分析欄位;計費適用 |
9.12 Outbox 真發送(Worker)
| ID |
Given |
When |
Then |
| OB-01 |
step 未到 scheduled_at |
worker tick |
不呼叫 Threads;狀態仍 scheduled |
| OB-02 |
step 已到點且 token 有效 |
worker |
publishing→published;Threads 側可見(或 sandbox 可證) |
| OB-03 |
Threads API 失敗 |
worker |
step=failed;error 非空;非 published |
| OB-04 |
root failed |
查 bundle |
後續 reply 皆非 published(blocked/scheduled 等) |
| OB-05 |
root 成功 reply 到點 |
worker |
reply published |
| OB-06 |
failed step 可重試 |
retryStep |
再進 publishing;成功則 published |
| OB-07 |
不可重試狀態 |
retryStep |
失敗 |
| OB-08 |
全部成功 |
bundle |
status=completed |
| OB-09 |
部分失敗 |
bundle |
partial_failed |
| OB-10 |
remove bundle |
— |
list 不可見;worker 不再送 |
| OB-11 |
他 uid outbox |
我 get/retry |
失敗 |
| OB-12 |
live 環境 |
僅靠前端 simulate |
不算 OB-02 驗收通過 |
9.13 已發貼文/提及
| ID |
Given |
When |
Then |
| OP-01 |
usable 帳 |
sync |
列表更新;lastSyncedAt 更新 |
| OP-02 |
sync 失敗 |
— |
錯誤可讀;不清空當成功 |
| OP-03 |
有貼文 |
generateReply |
回文字;platform 或 byok 計費規則適用 |
| OP-04 |
sendReply |
成功 |
Threads 可見;本地狀態已回 |
| OP-05 |
sendReply 失敗 |
— |
本地不標已送成功 |
| OP-06 |
mention |
markReplied / skip |
狀態正確 |
| OP-07 |
他帳 accountId |
sync |
失敗 |
| OP-08 |
有貼文 |
analyzePost |
分析欄位更新;platform/byok 計費適用 |
| OP-09 |
有 mention |
list(可篩 account) |
契約完整;時間 ns |
| OP-10 |
有 mention |
generateReply |
回文字草稿;計費適用 |
| OP-11 |
— |
generateFromFormula |
不提供(Remove;呼叫應 404/未實作失敗,不得假成功) |
9.14 靈感/研究/媒體
範圍: 僅現 UI(InspirePanel + Today 趨勢)。舊 list/viral/bookmark/spark/searchTrends 不做、不驗收。
| ID |
Given |
When |
Then |
| IN-01 |
listTrends |
— |
有 list(可空但契約完整) |
| IN-02 |
refreshTrends |
— |
更新或明確錯誤 |
| IN-03 |
save/remove element |
— |
CRUD 正確 |
| IN-04 |
chat mode=chat |
— |
session 寫入 messages |
| IN-05 |
chat mode=generate |
— |
draft.body 乾淨正文可取 |
| IN-06 |
research.search |
platform/byok |
計費規則同 §9.8 |
| IN-07 |
generateImage |
— |
回 url;計費規則適用 |
| IN-08 |
attach/upload(真上傳) |
合法檔 multipart(或等價) |
回持久 url(+id);非法/過大失敗;禁止僅回本機 data URL 當 live 完成 |
| IN-09 |
未實作上傳 |
呼叫 |
非 102000+空成功 |
9.15 海巡 Scout
| ID |
Given |
When |
Then |
| SC-01 |
已登入 |
createBrand/saveProduct |
持久 list 可見 |
| SC-02 |
刪 brand |
— |
所屬 product 策略明確(級聯或拒刪) |
| SC-03 |
prepareBrief 有 intent |
— |
回 brief含 scan_terms 可改 |
| SC-04 |
intent 空 |
prepareBrief |
失敗 |
| SC-05 |
dev_mode=false |
runScanFromBrief |
走 API 路徑(log/欄位 scan_path=api 可證) |
| SC-06 |
dev_mode=true 且 session 可用 |
runScan |
走 crawler(scan_path=crawler) |
| SC-07 |
dev_mode=true 但無 session |
runScan |
失敗可讀或明確降級策略(文件+實作一致) |
| SC-08 |
掃描成功 |
listPosts |
有命中;時間 ns |
| SC-09 |
draftOutreach |
— |
草稿文字寫入 |
| SC-10 |
skip / markPublished |
— |
狀態對 |
| SC-11 |
sendOutreach 真送 |
— |
對齊發送契約;失敗不假 published |
| SC-12 |
removePost / removeTheme |
— |
列表消失 |
| SC-13 |
homework save/get/list/remove |
— |
CRUD 正確 |
| SC-14 |
importProductFromUrl 合法 |
— |
回填草稿欄位;非法 URL 失敗 |
9.16 邀請 Invite
| ID |
Given |
When |
Then |
| IV-01 |
新會員 |
getMyNetwork |
有 invite_code;parent 空或 seeder 值 |
| IV-02 |
無 parent |
apply 合法碼 |
parent=邀請人 uid;upline 可見 |
| IV-03 |
已 parent |
apply 任意碼 |
失敗 alreadyBound |
| IV-04 |
apply 自己的碼 |
— |
失敗 |
| IV-05 |
apply 不存在碼 |
— |
失敗 |
| IV-06 |
apply 空碼 |
— |
失敗 |
| IV-07 |
有直邀 |
getMyNetwork |
downlines 正確;計數一致 |
| IV-08 |
admin |
getTree |
森林結構完整 |
| IV-09 |
admin |
move 到合法父 |
parent 更新;子孫跟著 |
| IV-10 |
admin |
move 到自己子孫下 |
失敗防環 |
| IV-11 |
admin |
move 到自己 |
失敗 |
| IV-12 |
member |
moveMember |
權限失敗 |
| IV-13 |
admin |
listParentCandidates(排除子樹) |
不含該子樹節點 |
9.17 契約與安全紅線
| ID |
Given |
When |
Then |
| X-01 |
任一未實作路徑 |
呼叫 |
非 102000+空 data |
| X-02 |
成功回應 |
檢查 |
無 password、無 raw api key、無 oauth access_token 欄位 |
| X-03 |
worker/gateway log |
打 key 的請求 |
log 無明文 key |
| X-04 |
列表 page=0 或負 |
— |
正規化或 400,行為一致 |
| X-05 |
pageSize 過大 |
— |
cap 到上限(如 50) |
| X-06 |
時間欄位 |
抽樣 |
皆 ns 量級 int64(非秒、非 ms 混用) |
9.18 驗收執行約定(嚴格 PM)
- P0 過關: §9.0 + §9.1 + §9.3 + §9.5 + §9.6 + §9.7 + §9.8(ST-01~16 中與 P0 相關)+ §9.17 全過;§9.2/§9.4 API 存在性可測。
- P1 過關: P0 全過 + §9.9~§9.16 全過(含 OB 真發送、SC 雙路徑、IV)。
- 每條 ID 必須在測試清單有對應 case 名稱(自動化或手測腳本表);禁止「大概可以」。
- 外部依賴(Threads/AI)可用 契約測試 + 受控沙箱;但 OB-02/TH-02 不得用「只改 DB 狀態」冒充。
10. 開放規格問題
- 成功碼若 stand-alone 現況≠102000,搬遷時統一策略(建議:統一 102000)。
- OAuth callback 網域與 state 儲存(Redis/DB)細節。
- 爬蟲 session 與 gateway 的傳遞安全(僅 dev_mode)。
- 目錄落點:
apps/backend(gateway + cmd/worker 等同 monorepo 根;已定)。
- BYOK 是否要加「平台服務費」(每呼叫固定點)— 預設否;僅記 event,日後可加。
- 三方登入支援哪些 provider 第一版(Google/Apple/…)— plan 列最低集合,API 介面先通用。
11. 批准