704 lines
38 KiB
Markdown
704 lines
38 KiB
Markdown
|
|
# 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
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
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)
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
(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
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
pending --> queued --> running --> succeeded
|
|||
|
|
running --> failed
|
|||
|
|
running --> cancel_requested --> cancelled
|
|||
|
|
queued|pending --> cancelled
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- 更新必須 **guarded**(樂觀條件或等價);Redis lock value = `workerID`(`AGENTS.md`)。
|
|||
|
|
|
|||
|
|
### 3.4 OutboxBundle / OutboxStep
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
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 綁定
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
parent_uid = null --> parent_uid = inviter_uid : 會員 apply 邀請碼(一次)
|
|||
|
|
parent_uid != null --> (會員不可改)
|
|||
|
|
admin: parent_uid 任意合法調整(防環:不可掛到自己的子孫下)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3.6 海巡掃描路徑
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
dev_mode_enabled == false --> scan_via_api
|
|||
|
|
dev_mode_enabled == true --> scan_via_crawler (Chrome extension session)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
兩條路徑皆須實作;結果統一寫入 ScoutPost 語意。
|
|||
|
|
|
|||
|
|
## 4. 使用者流程(行為)
|
|||
|
|
|
|||
|
|
### 4.1 會員進站(P0)
|
|||
|
|
|
|||
|
|
**主路徑(現前端):**
|
|||
|
|
|
|||
|
|
1. Admin 代建島民 → **數字 uid**、臨時密碼、角色、邀請碼、可選 parent;密碼僅回傳一次。
|
|||
|
|
2. 島民 email/密碼 login → JWT。
|
|||
|
|
3. 未驗證信箱:對齊前端閘(僅驗證/登出/有限 profile)。
|
|||
|
|
|
|||
|
|
**完整 API(頁面可暫無):**
|
|||
|
|
|
|||
|
|
1. 自助註冊、寄碼、驗證、三方 OAuth 註冊/登入/綁定解綁等 **後端齊全**。
|
|||
|
|
2. 行為與權限與 admin 代建一致(數字 uid、角色預設 member、產邀請碼)。
|
|||
|
|
3. 前端何時開頁不阻擋 API 驗收。
|
|||
|
|
|
|||
|
|
### 4.2 Threads OAuth(P0)
|
|||
|
|
|
|||
|
|
1. 已登入會員請求「開始綁定」→ 系統回授權 URL(含 state)。
|
|||
|
|
2. 使用者在 Threads/Meta 完成授權 → callback 至後端。
|
|||
|
|
3. 後端換 token、寫入 ThreadsAccount(密文 token **不進** JWT、不進前端永久存儲)。
|
|||
|
|
4. 前端列表顯示 username、connection、session 過期語意。
|
|||
|
|
5. 刷新 session:成功延長/更新 `session_*` 欄位;失敗標記 error。
|
|||
|
|
|
|||
|
|
### 4.3 互回/單篇 → Outbox 真發送(P1)
|
|||
|
|
|
|||
|
|
1. 會員存 ThreadPlay(含 steps、lead/cast、`schedule_start_at`、間隔)或 compose 單篇(含 `schedule_start_at`)。
|
|||
|
|
2. submit/publishSingle → 建立 OutboxBundle+Steps(`scheduled_at` 算好)。
|
|||
|
|
3. **Worker** 到點取步驟 → publishing → 呼叫 Threads → published 或 failed。
|
|||
|
|
4. 會員看 Outbox 詳情;失敗可 retry(合規則)。
|
|||
|
|
|
|||
|
|
### 4.4 海巡(P1)
|
|||
|
|
|
|||
|
|
1. 會員維護 Brand/Product;prepareBrief → 可審 scan_terms。
|
|||
|
|
2. runScan:讀會員 `dev_mode_enabled` 選 API 或爬蟲。
|
|||
|
|
3. 命中列表可 draft outreach/skip/mark/send(發送仍受 Outbox 或直接回覆契約約束,spec 實作時與前端 mock 對齊)。
|
|||
|
|
|
|||
|
|
### 4.5 邀請(P1)
|
|||
|
|
|
|||
|
|
1. 每位會員有唯一 `invite_code`。
|
|||
|
|
2. 無邀請人:`applyInviteCode` 綁定;已綁定拒絕。
|
|||
|
|
3. 會員讀自己網絡(邀請人、直邀、延伸計數)。
|
|||
|
|
4. Admin:搜尋、焦點調 `parent_uid`、防環。
|
|||
|
|
|
|||
|
|
### 4.6 設定 AI/Exa 與金鑰模式(P0)
|
|||
|
|
|
|||
|
|
1. **系統預設 key(平台模式)**
|
|||
|
|
- 寫在 **唯一 etc 設定檔**(或 env 覆寫同鍵):至少
|
|||
|
|
- AI:各 provider 的平台 API key(或目前啟用 provider 的 key)
|
|||
|
|
- 搜尋:Exa 平台 API key
|
|||
|
|
- 會員未填自己的 key 時,所有 AI/搜尋呼叫走 **platform keys**。
|
|||
|
|
|
|||
|
|
2. **會員 BYOK**
|
|||
|
|
- 會員在設定填寫自己的 AI key 和/或 Exa key → 該類呼叫改走會員 key。
|
|||
|
|
- 讀取 API 只回 `*_configured`(是否已填),**永不回明文**。
|
|||
|
|
- 清除會員 key → 回到平台 key。
|
|||
|
|
|
|||
|
|
3. **解析順序(每次外部呼叫)**
|
|||
|
|
|
|||
|
|
```text
|
|||
|
|
if 會員該 meter 已配置自己的 key:
|
|||
|
|
use BYOK key → key_mode = byok
|
|||
|
|
else if etc 有平台 key:
|
|||
|
|
use platform key → key_mode = platform
|
|||
|
|
else:
|
|||
|
|
fail 明確錯誤(未設定可用金鑰)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
4. Provider/model:單一 provider(`xai`|`opencode-go`)+單一 model;換 provider 回模型清單並保留同名或預設第一個。
|
|||
|
|
5. `dev_mode_enabled` 只影響海巡掃描路徑,與 BYOK 正交。
|
|||
|
|
|
|||
|
|
### 4.7 用量怎麼算(平台 vs BYOK)— 契約(分開計、分開顯示)
|
|||
|
|
|
|||
|
|
目標:
|
|||
|
|
|
|||
|
|
1. **平台代付**才扣方案 **credits**,並計入「平台用量」。
|
|||
|
|
2. **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)
|
|||
|
|
|
|||
|
|
1. **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 存在性可測。
|
|||
|
|
2. **P1 過關:** P0 全過 + §9.9~§9.16 全過(含 OB 真發送、SC 雙路徑、IV)。
|
|||
|
|
3. 每條 **ID 必須在測試清單有對應 case 名稱**(自動化或手測腳本表);禁止「大概可以」。
|
|||
|
|
4. 外部依賴(Threads/AI)可用 **契約測試 + 受控沙箱**;但 OB-02/TH-02 不得用「只改 DB 狀態」冒充。
|
|||
|
|
|
|||
|
|
## 10. 開放規格問題
|
|||
|
|
|
|||
|
|
1. 成功碼若 stand-alone 現況≠102000,搬遷時統一策略(建議:**統一 102000**)。
|
|||
|
|
2. OAuth callback 網域與 state 儲存(Redis/DB)細節。
|
|||
|
|
3. 爬蟲 session 與 gateway 的傳遞安全(僅 dev_mode)。
|
|||
|
|
4. 目錄落點:**`apps/backend`**(gateway + `cmd/worker` 等同 monorepo 根;已定)。
|
|||
|
|
5. BYOK 是否要加「平台服務費」(每呼叫固定點)— **預設否**;僅記 event,日後可加。
|
|||
|
|
6. 三方登入支援哪些 provider 第一版(Google/Apple/…)— **plan 列最低集合**,API 介面先通用。
|
|||
|
|
|
|||
|
|
## 11. 批准
|
|||
|
|
|
|||
|
|
- [x] Spec 已批准(2026-07-10 / 使用者「spec 通過」):
|