◆ wport | pm_54

W101 Admin CLI(公司後台終端介面)PRD

pm_54

來源 doc/feature/pm_54/admin-cli-prd.md

W101 Admin CLI(公司後台終端介面)PRD

PRD 代號pm_54 延續pm_41(wport CLI v2 企業線)、pm_47(wport CLI v3 個人線・OAuth device flow)、pm_48(MCP connector・BR-039 通道來源審計)、pm_37(Enterprise REST) 參考架構hotfire-digital/wport-cli@wport/cli 0.9.2)——本 PRD 沿用其分層、契約與安全慣例,但另發套件、另發 registry(ADR-003) 目標 repoW101-AMS(後端,主要工作量)、W101-Admin-Web(GUI parity 對照基準+新增 1 個 web 觸點)、新建 W101-Admin-CLI 產品原則(沿 pm_41/pm_47,本 PRD 縮限):Agent-first、deny-list 制、限量取代禁令 —— 但 admin 線因權限最高,額外加上「通道不得擴權」硬約束(BR-API-CLI-019) 本 PRD 的核心事實:GUI parity 在此案做不到也不該做——前端 18 個路由模組中僅約 2/3 有真實後端端點,其餘為 mock-only(§3.4、§13.2)


1. 文件資訊

  • 文件類型:CLI + OAuth + 後端 API 擴充需求規格書
  • 適用對象:CLI 工程師、後端(AMS)、前端(Admin-Web,1 個 web 觸點)、DevOps、資安、產品、QA、內部營運
  • 最後更新:2026/09/09
  • 版本:1.2.0
  • PRD 識別碼pm_54
  • 對應 Storybook待產出(§9 W-1 裝置驗證頁;文字規格為現階段 SoT,見 §17 sync-fix #9)
  • Storybook(local-prd):N/A — 尚未產出,以本 PRD §9 為 SoT
  • Storybook(online):N/A
  • 文件入口doc/feature/pm_54/admin-cli-prd.md
  • 建立日期:2026/08/25
  • 套件名稱@wport/admin-cli(私有 registry)/執行檔 wportadm(ADR-003)
  • 代號對照pm_41 = 企業線 CLI|pm_47 = 個人線 CLI|pm_48 = MCP|本 PRD pm_54 = 後台 admin 線 CLI

檔頭的 版本最後更新 等於 §1.1 修訂紀錄最後一列。

1.1 修訂紀錄

版本日期摘要
1.0.02026/08/25初版。依 wport-cli@c49a610W101-AMS@bf0fb57W101-Admin-Web@0ee5ee9 實查撰寫。三項 Eric 拍板:①認證採 admin device flow OAuth(ADR-001)②v1 範圍=AMS 已實作端點全包(ADR-002)③私有 registry 發布(ADR-003)。含 14 條 ADR、7 條新 BR 提案(BR-API-CLI-018~024)。三項關鍵發現(皆為實查所得,非推論):①AMS 現況發 7 天全權限 JWT、無 refresh、無單一 session 撤銷 ②AMS 全 src/X-Source 收錄,與 BR-039 有落差 ③role 快取於 token 且 80 個 @Auth 端點無一宣告 permissions,DB 權限層形同虛設(§19 問題 9)
1.0.12026/08/26PRD 代號自 pm_53 改為 pm_54(撞號更正,內容零變動)。pm_53 已於 2026/08/20 配發給「職缺生命週期與 SEO 索引健康度」PRD(ericlu-sys/job-posting 分支,當時尚未推送至 origin,故 v1.0.0 撰寫時的撞號檢查只掃 refs/remotes/origin 而漏看)。教訓已回寫至 §19 問題 10:配號前須掃全 ref + 其他 worktree,不能只看已推送分支
1.0.22026/08/26併入原本要另開 PRD 的授權模型查核結果,但不指定修法(Eric:授權怎麼做交給後端構思;亦符合 gen-prd Rule 31 的 PRD/Spec 界線)。三處變更:(一) §2 台帳新增 8 列實查事實——RBAC 四張表與四個角色代碼皆存在、hasRoleCode() 正確,但 resolveAdminIdentity() 只做二分、getPermissions() 為寫死 stub、建立管理員的 DTO 無角色欄位且從不寫 admin_user_role、新帳號 MFA 預設關閉、API 回應含明碼密碼。(二) §3.4 N-4 補誠實範圍——該條約束的是「CLI 不新增旁路」,不代表 admin 線一定過得了 MFA(現況 MFA 為每帳號可選)。(三) §19 問題 1 改寫為六項現況+三項產品層要求(MFA 強制、預設最小權限、可降權與撤銷),機制留白;問題 9 併入其中;sync-fix #16 改為單一交辦項
1.0.32026/08/26依 Eric 對 web 觸點範圍的裁決補齊:(一) §9 新增 W-2「CLI 存取頁」(設定 > 帳號設定 新增分頁,與 MFA 設定相鄰)與 W-3「管理員列表 CLI 治理視圖」,兩者皆標 v2;並記錄 W-2 不能放 會員管理 > 管理員列表 的原因——該區塊全部端點限 SUPER_ADMIN,一般 admin 打不開,自助安裝的雞生蛋問題不會被解掉。(二) §14 版本凍結表補 W-2/W-3 兩列與延後理由。(三) §19 新增問題 11,記錄「軟刪除管理員=緊急撤銷路徑」這條替代方案的成立依據與其 all-or-nothing 限制,避免後人重新推導。另釐清:安裝憑證屬套件存取權,與後台存取權為不同信任域,不牴觸 ADR-001 與核彈 N-4
1.1.02026/09/09依 Eric 補充真實需求來源,取代原本純推導的策略理由。(一) §3.1 新增「實際驅動力」表:D-1 IDE 已成為同仁工作介面、D-2 每月股東報告需抓後台控制台資料、D-3 行銷 UTM 管理。(二) §3.2 驅動力表新增兩列:IDE 的使用者已擴散到行銷與客服組(不只 RD),以及本案沿用 pm_41pm_47 既有 CLI 底盤而非從零開始。(三) §2 台帳新增 3 列並補 wport_skills 為引用 repo——實查發現股東報告管線已在取 AMS 主控台,但認證是「以 chrome-token.mjs 從 Chrome 撈未過期 token」,撈不到則手動登入或在 .env 放明碼 AMS_EMAILAMS_PASSWORD。此現況使 D-2 同時成為功能需求與資安改善,並與 §19 問題 1(7 天 JWT)為同一件事的兩面
1.2.02026/09/09北極星改寫(Eric:核心是同仁都已在 IDE,讓大家更好做事)。原 US-1「30 家公司審核自動化」是從「哪些工作可自動化」推導出來的,不是同仁實際提出的需求;改以 D-1 為北極星——讓後台從「要切出去做的系統」變成「工作流裡的一個命令」,衡量標準是完成一件後台工作需不需要離開編輯器。原審核情境降為三個代表情境之一(S-3),與 S-1 股東月報取數、S-2 行銷 UTM 並列。連帶依「修正擴散」原則全文回掃 US-1 的 7 處引用(§3.5 S1/S6、§6.4 標題、§8.4、ADR-008、§14、§19 問題 7)全部改指 S-3;§3.5 S1 一併改寫為「後台工作可全程於終端完成,不需離開編輯器」

2. 開發進度與設計來源

開發進度

  • CLI PR #:[TBD](新建 repo W101-Admin-CLI
  • 後端 PR #W101-AMS):[TBD](admin device flow AS + session 撤銷 + X-Source 收錄 + audit channel)
  • 前端 PR #W101-Admin-Web):[TBD](§9 W-1 裝置驗證頁 + 已授權 CLI session 清單)

事實基礎(實查證據台帳)

本表是本 PRD 的證據台帳(gen-prd Rule 33)。凡斷言「既有程式怎麼運作」的句子都必須在此有一列; 沒有列的斷言,該句自帶 (推論,未實查)。來源欄格式固定為 repo@<short-sha> path:line

Repo 基準(Rule 34 前置檢查,2026/08/25 執行 git fetch):

RepoBranchSha日期
wport-cliorigin/mainc49a6102026/08/24
W101-Admin-Weborigin/develop0ee5ee92026/08/12
W101-AMSorigin/devbf0fb572026/08/12
wport_skillsmain8be06d12026/08/26

基準選擇說明:本機 W101-Admin-Web 停在 main(落後 origin/develop 130 commits)、W101-AMS 停在 prod(與 origin/dev 相差 155/24)。實查 git diff --stat 後確認該落差主要為 squash-merge vs merge-commit 的歷史差異src/ 實際內容差異僅限 gallery 兩個 view 與 company 模組的 chat-rooms repository,皆不在本 PRD 斷言範圍內。所有行號一律以 git show origin/<branch>:<path> 取得,未取自本機工作樹。

斷言 → 證據

事實來源
AMS 掛 global prefix,值為 ams-api,故 wire 路徑一律 /ams-api/...W101-AMS@bf0fb57 src/main.ts:40config/env/.env.example:14
Admin-Web 前端亦以 /ams-api 為 API prefixW101-Admin-Web@0ee5ee9 .env.development:26.env.production
admin 登入成功發 7 天 JWT('7d'),無 refresh tokenW101-AMS@bf0fb57 src/modules/admin/service/admin.service.ts:139(未啟用 MFA 路徑)、:120(信任裝置路徑)、:180(MFA 驗證後)
未啟用信任裝置時,登入先發 5 分鐘 TEMP token(另一組 secret),需再過 MFAW101-AMS@bf0fb57 src/modules/admin/service/admin.service.ts:130-133
MFA 為 TOTP,驗證窗格 window: 2W101-AMS@bf0fb57 src/modules/admin/service/admin.service.ts:167,171
信任裝置有效期 30 天,以 cookie device_token 承載W101-AMS@bf0fb57 src/modules/admin/service/admin.service.ts:186
JWT payload 僅含 { id, email, role };驗證時不忽略過期、從 Authorization Bearer 取值W101-AMS@bf0fb57 src/modules/auth/jwt.strategy.ts:11,12,35-37
授權採 @Auth(roles, permissions?),內部串 JwtAuthGuard + RolesPermissionsGuard 兩層W101-AMS@bf0fb57 src/modules/auth/decorators/auth.decorator.ts:7-8
角色檢查走 Permission.checkRolePermission(比對 token payload 的 role);功能權限另查 DBW101-AMS@bf0fb57 src/modules/auth/guards/roles-permissions.guard.ts:30-47:41
DB 權限查詢僅在端點宣告 permissions 時才執行;現況 80 個 @Auth 端點中有 0 個帶第二參數,故實務上只跑 role 檢查W101-AMS@bf0fb57 src/modules/auth/guards/roles-permissions.guard.ts:39git grep "@Auth(\[" -- 'src/**/*.controller.ts' 計數(2026/08/25 執行)
每請求都會查帳號狀態(checkAccountStatus),故停用帳號即時生效;但 role 直接取自 token payloadW101-AMS@bf0fb57 src/modules/auth/jwt.strategy.ts:26,37
RBAC 四張表在 admin_management schema 全部存在:rolepermissionrole_permissionadmin_user_roleW101-AMS@bf0fb57 src/database/entities/admin_management/entities/Role.tsPermission.tsRolePermission.tsAdminUserRole.ts
系統定義四個管理員角色代碼:superadminadminmanagersupportW101-AMS@bf0fb57 src/common/constants/admin-roles.constants.ts:5-9
hasRoleCode()admin_user_role join role.code 查角色,實作正確W101-AMS@bf0fb57 src/modules/admin/repository/admin.repository.ts:21
resolveAdminIdentity() 只問「是不是 superadmin」,回傳二選一;managersupport 執行期等同完整 adminW101-AMS@bf0fb57 src/modules/admin/service/admin.service.ts:40-43
getPermissions() 是寫死 stub,對所有管理員回傳固定三個字串,註解自承「還沒時間實作,先返回假數據」W101-AMS@bf0fb57 src/modules/admin/service/admin.service.ts:267-272,註解在 :269
CreateAdminUserDtoUpdateAdminUserDto 各只有 email 一個欄位,無角色、無狀態W101-AMS@bf0fb57 src/modules/admin/dto/create-admin-user.dto.tsupdate-admin-user.dto.ts
createAdminUser() 全程未寫入 admin_user_role;新管理員 mfaEnabled: falsestatus: ACTIVE,且 API 回應含明碼隨機密碼W101-AMS@bf0fb57 src/modules/admin/service/admin-user.service.ts:22-66,關鍵在 :46,49,65
登入時 if (admin.mfaEnabled) 為偽即直接發完整 token(MFA 為每帳號可選)W101-AMS@bf0fb57 src/modules/admin/service/admin.service.ts:106
刪除管理員為軟刪除(寫 deletedAt),且不得刪除自己W101-AMS@bf0fb57 src/modules/admin/service/admin-user.service.ts:130-141
isActive() 要求 deletedAt IS NULL status === ACTIVE;此檢查經 checkAccountStatus 在每個請求執行,故軟刪除或停用後下一請求即 401W101-AMS@bf0fb57 src/modules/admin/repository/admin.repository.ts:193-199src/modules/admin/service/admin.service.ts:279-281src/modules/auth/jwt.strategy.ts:26
帳號設定頁已為分頁結構(BasicSetting / SafetySetting),且 SafetySetting 即 MFA 綁定所在W101-Admin-Web@0ee5ee9 src/views/setting/account/account.vue:3-4,52-53src/views/setting/account/SafetySetting.vue:18,23,37
會員管理 選單包含公司列表/求職者列表/管理員列表三項W101-Admin-Web@0ee5ee9 src/router/modules/members.ts:14,20,26,32
auth/mfa controller 路徑W101-AMS@bf0fb57 src/modules/auth/controllers/mfa.controller.ts:17
admin/enterprise-api-keys controller 路徑W101-AMS@bf0fb57 src/modules/enterprise-api-key/controllers/enterprise-api-key-admin.controller.ts:27
admin/partner-keys controller 路徑W101-AMS@bf0fb57 src/modules/partner-key/controllers/partner-key-admin.controller.ts:35
marketing/posts controller 路徑W101-AMS@bf0fb57 src/modules/marketing/controllers/posts.controller.ts:26
siteadmin/v2 controller 路徑W101-AMS@bf0fb57 src/modules/siteadmin/controllers/siteadmin-dashboard.controller.ts:12
scheduler controller 路徑W101-AMS@bf0fb57 src/scheduling/controllers/scheduler.controller.ts:11
後台 admin 與前台 account 完全分離,admin 登入走 /admin/* + MFAW101-Admin-Web@0ee5ee9 CLAUDE.md:15
參考 CLI 顯示金鑰一律遮罩至末四碼(maskKeywport-cli@c49a610 packages/cli/src/lib/credentials-store.ts:73
全域 ValidationPipewhitelist + forbidNonWhitelisted(多帶欄位直接 400)W101-AMS@bf0fb57 src/main.ts:78-79
Swagger 僅在 NODE_ENV !== 'production' 掛載於 /ams-api/api-docW101-AMS@bf0fb57 src/main.ts:90,110
既有節流常數:DEFAULT 5/60s、STRICT 5/900s、ADMIN_LIST 20/60s、ADMIN_ACTION 10/60sW101-AMS@bf0fb57 src/common/constants/throttle.constants.ts:6-7,11,16,21
後台操作稽核表已存在,欄位含 adminUserIdipAddressrequestIdchangedFieldsmetadataremarksW101-AMS@bf0fb57 src/modules/admin-operation-logs/interfaces/admin-operation-log.interface.ts:42,51,53
稽核字典已定義 module/action/table 三組 enumW101-AMS@bf0fb57 src/modules/admin-operation-logs/interfaces/admin-operation-log.interface.ts:59,71,87
AMS 全 src/ 無任何 X-Source 收錄grep -rn "X-Source|x-source" src/ 零結果)W101-AMS@bf0fb57(negative grep,2026/08/25 執行)
job/v2 僅有 search:enc_id/deactivate無單筆詳情端點W101-AMS@bf0fb57 src/modules/job/controllers/job.controller.ts:14,23,57
company/v2 提供 search/detail/info 更新/文件審核/刪除W101-AMS@bf0fb57 src/modules/company/controllers/company.controller.ts:29,38,75,104,141,192
resume/v2 提供 search/detail/停用狀態切換W101-AMS@bf0fb57 src/modules/resume/controllers/resume.controller.ts:14,23,57,84
account/v2 提供 search/detail/狀態切換W101-AMS@bf0fb57 src/modules/account/controllers/account.controller.ts:16,25,59,86
admin/users 全部端點限 SUPER_ADMINW101-AMS@bf0fb57 src/modules/admin/controller/admin-user.controller.ts:17,25,55,80,111,143
前端管理員頁確實打 /admin/users/auth/mfa/admin/disable/{id}W101-Admin-Web@0ee5ee9 src/api/members/admins.ts:7,19,52,64
Admin-Web 本地 pnpm dev 為純 Mock 模式,未匹配的請求會 throw、不 fallback 真實 APIW101-Admin-Web@0ee5ee9 CLAUDE.md:47mock/README.md:25,162
Admin-Web 真實後端不包 envelope,直接回 flat shapeW101-Admin-Web@0ee5ee9 mock/README.md:11
sub_accounts(子帳號)已廢棄,新 code 不得使用W101-Admin-Web@0ee5ee9 CLAUDE.md:22
legacy endpoint(/members/jobSeekersData/members/merchantsXxx/job/* 單數)標 @deprecated,逐步移除W101-Admin-Web@0ee5ee9 CLAUDE.md:23
後台三環境:dev-admin/staging-admin/admin.wport.meW101-Admin-Web@0ee5ee9 CLAUDE.md:51-53
參考 CLI 的 exit code 契約為 0/2/3/4/5wport-cli@c49a610 packages/cli/src/lib/errors.ts:6-10
參考 CLI 的設定解析優先序:flag > env var > config > 預設;base URL 刻意不落地 config(SSRF 面)wport-cli@c49a610 packages/cli/src/lib/global-opts.ts:38,58-61,64
參考 CLI 憑證檔採 atomic write + 0o600,並依 build channel 分檔wport-cli@c49a610 packages/cli/src/lib/credentials-store.ts:63-64,160,163
參考 CLI 對所有終端輸出做 ANSI/控制字元淨化,防 escape injectionwport-cli@c49a610 packages/cli/src/lib/output.ts:19,43-44,100,108
參考 CLI 已具備 X-Source 出口與 Idempotency-KeyIf-Match 寫入 headerwport-cli@c49a610 packages/core/src/enterprise-transport.ts:61,93-95
股東月報資料管線的四個來源之一為 AMS 主控台wport_skills@8be06d1 src/gen-shareholder-report/SKILL.md:42
該管線取得 AMS 存取權的方式:先以 chrome-token.mjs 從 Chrome 撈尚未過期的 tokenwport_skills@8be06d1 src/gen-shareholder-report/SKILL.md:45
撈不到時的替代方案為請 Eric 手動登入 admin.wport.me,或於 .env明碼 AMS_EMAIL / AMS_PASSWORDwport_skills@8be06d1 src/gen-shareholder-report/SKILL.md:46
參考 CLI 以 CLI_SOURCE = 'cli' 單一常數收斂來源標記(pm_48 交付 B/BR-039)wport-cli@c49a610 packages/cli/src/lib/global-opts.ts:17

設計稿

  • Figma:N/A(唯一新畫面為 §9 W-1 裝置驗證頁,沿用 Admin-Web 既有 Naive UI 版型)

3. 功能摘要(Feature Summary)

3.1 功能概述

  • 功能名稱:W101 Admin CLI(wportadm
  • 主要使用者角色
    • 內部後台管理員admin / super_admin):以終端機執行審核、治理、稽核查詢等重複性後台工作
    • AI Agent(Claude/Cursor,代表已授權的管理員操作):執行「查詢 → 判讀 → 逐筆處置 → 留下理由」的審核流水線
    • CI/排程(受限):僅讀取類與明確授權的維運任務(如 scheduler status
  • 核心目標
    1. 把後台重複性審核工作(公司文件審核、職缺下架、履歷停用、帳號狀態)從「一筆一筆點 GUI」變成可腳本化、可稽核、可被 Agent 驅動的命令
    2. 金鑰治理(企業 API Key、合作夥伴 Key)的事故處理時間從分鐘級壓到秒級——rotate / revoke 一行指令
    3. 建立 wport 的 admin 線通道治理基礎:可撤銷的短期憑證、通道來源標記、逐筆稽核,補上目前 GUI session(7 天 JWT)做不到的三件事
    4. 沿用 pm_41/pm_47 的終端契約(exit code、--output json--fields--minimal),內部工程師與 Agent 零學習成本

實際驅動力(Eric 2026/09/09)

以下三項是本案的真實需求來源,不是推導出來的策略理由:

#驅動力現況CLI 帶來的改變
D-1IDE 已成為同仁的工作介面,未來在 IDE 內與各服務互動是必然趨勢後台只有 GUI,任何取數都得離開編輯器、切到瀏覽器、手動點選與複製資料與操作直接進到工作流所在的地方,不必離開 IDE
D-2每月股東報告需要抓後台控制台的資料資料管線已在取 AMS 主控台,但認證方式是從 Chrome 撈尚未過期的 token,撈不到就請 Eric 手動登入一次,再不然就在 .env明碼的 AMS_EMAIL / AMS_PASSWORD(見 §2 台帳)wportadm dashboard stats --output json 直接取數;認證改用短期、可撤銷、可稽核的 device flow token,不再需要撈瀏覽器 token,也不必把 admin 帳密落地
D-3行銷規劃的 UTM 管理marketing/utm 端點已存在,但只能透過 GUI 逐筆操作wportadm marketing utm list/create/update/delete,可腳本化、可批次產生

D-2 同時是資安改善:目前之所以「撈瀏覽器 token」這招可行,正是因為 admin JWT 效期長達 7 天(§2 台帳)。這條驅動力與 §19 問題 1 是同一件事的兩面 —— 一個講「不好用」,一個講「不安全」。

北極星:「同仁已經在 IDE 裡工作,就讓後台的事也能在 IDE 裡做完」

這是本案的核心命題,其餘情境都是它的具體形態。

公司同仁——不只 RD,行銷與客服組也是——一天多數時間待在 IDE。目前只要碰到後台的事, 就得中斷手上的工作:切到瀏覽器、登入、逐層點選、手動複製貼回來。這個切換成本本身不大, 但它發生得很頻繁,而且擋住了自動化——凡是只能用滑鼠完成的事,就無法被腳本或 Agent 接手。

CLI 要達成的不是「多一種操作方式」,而是讓後台從「要切出去做的系統」變成「工作流裡的一個命令」。 衡量標準很簡單:同仁完成一件後台工作,需不需要離開編輯器。

這個命題對 CLI 的四個硬需求(皆納 v1):

  1. 機器可讀輸出--output json / --fields / --minimal,讓結果能直接進下一段腳本或交給 Agent
  2. 可分頁批次讀取:不必為了取一份清單而逐頁點選
  3. 寫入可稽核--reason 必填並寫入 admin_operation_logs,通道可辨識(channel=admin_cli
  4. 認證不打斷工作流:一次 login,之後 token 自動 refresh,不需反覆登入或手動搬憑證

⚠️ 第 3 點目前做不到:AMS 稽核表雖有 metadata 可擴充,但全 src/X-Source 收錄,也沒有 channel 欄位語意。這是 v1 的 Blocking 後端工作(§17 sync-fix #3)。

三個代表情境(對應 §3.1 的 D-1~D-3)

#情境現況痛點CLI 後
S-1股東月報取數(D-2)資料管線已在取 AMS 主控台,但得靠 chrome-token.mjs 從 Chrome 撈 token、或在 .env 放明碼帳密(§2 台帳)wportadm dashboard stats --output json 直接進管線;認證改用短期可撤銷 token
S-2行銷 UTM 管理(D-3)marketing/utm 端點已存在,但只能 GUI 逐筆建立marketing utm list/create 可腳本化、可批次產生並回收結果
S-3審核流水線(延伸情境)30 家待審公司只能逐家點開判讀把審核標準交給 Agent:companies search --status pending → 逐家 viewapprovereject --reason "...",全程留稽核

S-3 是延伸而非主軸(v1.1.0 調整):初版曾以審核自動化為北極星,但那是從「哪些工作可自動化」推導出來的, 不是同仁實際提出的需求。真正的需求是 D-1——人已經在 IDE 裡了。審核自動化是這個前提成立後自然長出來的用法之一, 不是目的本身。

3.2 為什麼現在做(策略理由)

驅動力說明不做的代價
IDE 已是全公司的工作介面,不只 RD工程師不用說,行銷與客服組也開始熟悉 IDE。當同仁一天多數時間都在編輯器裡,「跟 IDE 相性好」就從加分項變成基本要求——而 CLI 是與 IDE/Agent 相性最好的介面形態後台永遠是那個要「切出去做」的系統;非工程同仁的自動化需求無處著力
我們已經有實作 CLI 的經驗pm_41(企業線)與 pm_47(個人線)已交付至 @wport/cli 0.9.2,分層、exit code 契約、憑證儲存、輸出淨化、OAuth device flow 全部驗證過一輪。本案是沿用既有底盤,不是從零開始既有投資無法攤提;下次要做時人與經驗都更遠
審核是可自動化的重複勞動公司文件審核、職缺下架、履歷停用皆為「有明確標準 + 逐筆判定 + 需留理由」的工作,正是 Agent 最擅長的形態;目前 100% 靠人點 GUI營運人力隨公司數線性增長;審核延遲直接影響企業客戶開通速度
金鑰事故的 MTTR企業 API Key 外洩時,現況要登入後台、找到該公司、進金鑰頁、點輪替;CLI 是 wportadm keys rotate <enc_id> --reason "leak"事故處理時間受限於「有沒有人能開瀏覽器」
通道治理的最後一塊pm_41/pm_47/pm_48 已把企業線、個人線、MCP 的通道治理(BR-039 來源審計、BR-040 跨通道 deny list)打好底盤,admin 線是唯一還沒有通道概念的權限最高的通道反而最不可治理
現況 GUI session 才是風險本體實查:登入發 7 天全權限 JWT無 refresh無法單獨撤銷單一 sessionadmin.service.ts:139)。筆電遺失只能改密碼這個風險不會因為「不做 CLI」而消失;做 CLI 正好逼我們把可撤銷 session 建起來
稽核基礎設施已備妥admin_operation_logs 的 module/action/table 字典與 changedFieldsremarks 欄位都已存在,只差通道語意已投入的稽核建設無法涵蓋新通道
時機成本AMS 端點已收斂到 /v2 新架構(company/v2job/v2resume/v2account/v2),legacy 端點正在退場等 legacy 完全移除後再做,CLI 要重寫一次對映

關於「權限最高所以不該做 CLI」的反向論證:這個直覺是對的方向、錯的結論。真正的風險不是「多一個通道」,而是「通道無法治理」。現況 GUI 通道的憑證是 7 天全權限、不可撤銷、不分來源;本 PRD 交付的 admin CLI 通道反而是第一個具備短 TTL、可撤銷、可限流、可逐筆稽核的 admin 通道。若因噎廢食不做,GUI 的既有風險一樣留在原地(§12 ADR-001 Consequences)。

3.3 SaaS Benchmark(高權限管理型 CLI 的認證慣例,RD 可自行驗證)

平台CLI 登入方式長期 API key?Session 可撤銷?
GitHub CLI(gh auth loginOAuth device flow(預設)可選 PAT是(Settings → Applications)
Auth0 CLI(auth0 loginOAuth device flow可選 M2M client
Stripe CLI(stripe login瀏覽器授權,發限時 restricted key是(但 restricted)是(Dashboard)
Vercel CLI瀏覽器授權 / email OTP
Cloudflare WranglerOAuth 瀏覽器授權可選 API token
Shopify CLI瀏覽器 OAuthPartner token
Heroku CLI瀏覽器授權
1Password CLI(op桌面 app 生物辨識,短期 session否(刻意)
AWS CLI長期 access key SSO device flow需靠 IAM 治理層

結論:管理型高權限 CLI 已收斂到 device-code / 瀏覽器 OAuth + 短 TTL + 可逐一撤銷 session。唯一大量使用長期 key 的 AWS,是因為其上有 IAM 這一整套獨立治理層在兜底——wport 沒有等價物,因此不核發長期 admin key(ADR-001、BR-API-CLI-018)。此結論與 pm_47 個人線「不發個人 API 金鑰」的裁決同源。

3.4 範圍界定(三層:✅ v1 會做 / 🕐 v2 以後 / ☢️ 絕對不做)

✅ v1(必含)

A. 認證與 session 治理

  1. wportadm login:admin OAuth device flow(開瀏覽器、MFA 在瀏覽器完成)
  2. wportadm logout / whoami
  3. wportadm sessions list / sessions revoke [--all-others]
  4. token 自動 refresh(過期前緩衝換新),refresh token rotation

B. 審核與治理命令(對映 AMS 已實作端點)

  1. companies:search/view/update/approve/reject/delete
  2. jobs:search/deactivate(無 view,後端缺端點,見 §17 #6)
  3. resumes:search/view/disable/enable
  4. accounts:search/view/status
  5. admins:list/create/update/delete/reset-password/mfa-disable(限 SUPER_ADMIN
  6. mfa:status/initialize/verify-setup/disable/rebind/devices list/devices remove(操作者本人)
  7. keys(企業 API Key):list/view/create/rename/rotate/quota/revoke/usage/audit-logs/companies search/companies members
  8. partners(合作夥伴):keys CRUD+regenerate/services bind|unbind|batch/tags CRUD/companies+tags
  9. marketing:posts/tasks/utm 各自 list/create/update/delete
  10. dashboard stats
  11. scheduler status / scheduler run <taskName>

C. 通用契約

  1. exit code 契約(沿用 0/2/3/4/5,新增 6 = 需重新認證,ADR-006)
  2. --output table|json--fields--minimal--api--timeout--no-color
  3. --confirm(破壞性操作)、--reason(所有寫入必填,ADR-007)
  4. wportadm doctor:解析後設定、連通性、schema 指紋、目前身分與權限
  5. wportadm config set|get|path|reset

D. 後端配套(AMS,皆為 v1 Blocking)

  1. admin device flow 授權伺服器(4 個端點,§8.1)
  2. CLI session 清單與撤銷 API
  3. X-Source 收錄 + 稽核 channel 語意(補 BR-039 落差)
  4. 寫入端點支援 Idempotency-Key

E. 前端配套(Admin-Web)

  1. W-1 裝置驗證頁 /cli/activate
  2. 帳號設定內「已授權的 CLI session」清單(可撤銷)

🕐 v2 以後(明確不在 v1)

  • 前端 mock-only 模組的 CLI 化:purchase(訂單/方案/點數)、platform(廣告/通知/推薦設定)、system(角色/選單/備份紀錄)、gallerydrivemessages(線上回報/聯繫我們)、historydeveloper(Loki) → 這些前端頁面存在,但 AMS 無對應 controller,屬 mock-only(§13.2 對照表)。CLI 化的前置是後端先補端點,不在本 PRD 範圍
  • 互動式審核 wizard(無 flag 時引導)
  • Shell auto-completion
  • Standalone binary(免 Node runtime)
  • CLI 自身 i18n(v1 全繁中 + 英文 flag 名)
  • MCP server 化(admin 線接 pm_48 架構)

☢️ 核彈 Deny List(admin 線永久禁止,對齊 BR-040)

#禁止項理由
N-1無差別批次刪除/停用 —— 不提供 --file 批次刪除、不提供萬用字元或「全選」語意的破壞性操作一個手滑等於全站事故;上限也救不了
N-2全站 PII 批次匯出 —— 不提供 accounts export / resumes export 全量下載個資外洩的單點;讀取一律逐筆或分頁
N-3自我提權與自我鎖定 —— CLI 不得修改操作者自身的 role/permission/帳號狀態權限邊界必須由他人授予
N-4繞過 MFA 的憑證取得 —— CLI 不新增任何免 MFA 取得 admin token 的路徑(含「CI 專用長期 key」)開後門等於沒有

⚠️ N-4 的誠實範圍:這條約束的是「CLI 不得新增旁路」,不等於「admin 線一定過得了 MFA」。實查顯示 MFA 目前是每帳號可選——新建管理員 mfaEnabled: false,登入時該旗標為偽即直接發完整 token(§2 台帳)。因此 device flow 的瀏覽器授權步驟,遇到未啟用 MFA 的帳號一樣會直接放行。修這件事不在本 PRD 範圍(見 §19 問題 1)。

這四項是上限也救不了的類別,與 pm_41/pm_47 的核彈清單同一判準:不是「限量」能解決的,才進核彈清單;能限量的一律走 BR-API-CLI-021/022 的限量條款(memory:CLI 能力哲學=deny-list 制 + 限量取代禁令)。

3.5 成功條件

#條件量測
S1後台工作可全程於終端完成,不需離開編輯器S-1/S-2/S-3 三個情境各自在 dev 環境跑通一次,全程零開啟 GUI
S2稽核 100% 覆蓋每筆 CLI 寫入皆可在 admin_operation_logs 反查,且能以 channel 區分 CLI/GUI;抽驗 50 筆命中率 100%
S3憑證可治理CLI session 可於 GUI 與 CLI 逐一撤銷;撤銷後殘存 ≤ 30s(§15)
S4金鑰事故 MTTR 下降企業 Key rotate/revoke 從「登入→導覽→操作」壓到單一命令;量測命令執行至生效 < 5s
S5不擴權滲透測試:以 admin(非 super_admin)token 呼叫 admins create 必得 403;CLI 無任何路徑可取得超出該 admin GUI 可見範圍的資料
S6Agent 可用Claude 以 --output json + --fields 完成 S-3 全流程,無需人工介入解析
S7不污染既有通道上線後 GUI 登入成功率、既有 API 錯誤率無統計顯著變化

4. 商業規則對齊(Business Rules Alignment)

4.1 本功能使用到的既有業務規則

規則內容摘要本 PRD 的關係
BR-039通道寫入 source 審計:跨通道(CLI/MCP/SDK/Enterprise API)寫入須記錄來源本 PRD 發現 AMS 尚未實作;v1 Blocking 補齊(§17 #3)
BR-040核彈 deny list 與破壞性操作 confirm 語意,跨通道共用;禁止開後門提高額度§3.4 核彈清單 N-1~N-4 依此制定;--confirm 語意沿用
BR-API-CLI-001CLI 破壞性操作需 --confirm/互動確認(pm_41)沿用,並加嚴為「同時需 --reason」(BR-API-CLI-020)

4.2 新規則提案(沿 BR-API-CLI 系列續編,自 018 起)

編號說明:主序列 BR-04x 目前有搶號情形(pm_46 提案 BR-039~043、pm_52 提案 BR-042,而 business-rules.md canonical 僅到 BR-041)。為避免再撞號,本 PRD 一律使用 BR-API-CLI 系列,續 pm_47 的 017 之後編號。

  • BR-API-CLI-018(建議新增):Admin CLI 認證僅限 OAuth device flow不核發任何長期 admin API key。access token TTL 1 小時;refresh token 30 天 sliding/90 天 absolute,且必須 rotation(每次換發、舊者失效)。偵測到已失效 refresh token 被重用時,撤銷該 token family 全部憑證並記 audit event。
  • BR-API-CLI-019(建議新增・資安核心):Admin CLI token 繼承該 admin 既有 role/permission,不得擴權。後端須以 channel=admin_cli 標記該通道,並得對此通道單獨套用 deny list 與流量上限;通道標記不得由 client 自行宣告為其他值(以 token 綁定為準,X-Source 僅供交叉比對,不作為授權依據)。
  • BR-API-CLI-020(建議新增):所有經 CLI 的寫入必須寫入 admin_operation_logsmetadata 須含 channel破壞性與審核類操作必須帶 --reason(1~200 字,必填),理由寫入 remarks。缺 --reason 於 client 端即擋下(exit 2),不送出請求。
  • BR-API-CLI-021(建議新增):Admin CLI 破壞性操作一律單筆 + --confirm;不提供批次刪除、不提供萬用字元選取(對齊核彈 N-1)。非破壞性的批次讀取不受此限。
  • BR-API-CLI-022(建議新增):Admin CLI 不提供全站 PII 匯出accountsresumes 讀取一律逐筆或分頁,單次分頁上限對齊 GUI 既有上限(RD 依實作端點定值)。
  • BR-API-CLI-023(建議新增):Admin CLI 不得修改操作者自身的 role/permission/帳號狀態(防自我提權與自我鎖定)。後端須在 domain 層擋下(不僅 client 端),對 admins update|delete 指向自身時回 403。
  • BR-API-CLI-024(建議新增):Admin CLI session 可於後台 GUI 與 CLI 逐一撤銷;撤銷後殘存 ≤ 30 秒。每 admin active CLI session ≤ 5,超額時汰換最久未使用者並通知。

5. 資料實體與欄位(Data Entities & Fields)

本節描述需要追蹤什麼資料、為什麼,不規定 JSON 欄位名、雜湊演算法或 DB schema(gen-prd Rule 31)——那些屬 OpenAPI Spec,由 RD 決定。

5.1 實體列表

#實體新增/既有目的
E-1Admin CLI 授權請求(device authorization request)新增承載 device flow 的待授權狀態,短生命週期
E-2Admin CLI Session新增一台裝置一份;治理單位=可列出、可撤銷的最小粒度
E-3Admin Refresh Token Family新增支撐 rotation 與「重用即全家撤銷」的偵測
E-4後台操作稽核記錄既有,需擴充既有表已具備操作者/目標/變更欄位/理由;缺通道語意
E-5CLI 本機憑證檔新增(client 端)存放 token;不上傳、不進 git

5.2 實體資料需求說明

E-1 Admin CLI 授權請求 需追蹤:待授權的裝置識別、給人看的短代碼、發起時間與到期時間、輪詢節流間隔、目前狀態(待授權/已授權/已拒絕/已過期)、以及授權完成後綁定到哪位 admin。 生命週期:(建議 ≤ 10 分鐘,RD 定值);過期即不可用,且不可重複兌換。 安全需求:短代碼須為人類可念但不可預測;輪詢須有節流,避免暴力兌換。

E-2 Admin CLI Session 需追蹤:屬於哪位 admin、建立時間、最後使用時間、來源裝置描述(作業系統/CLI 版本/建立時 IP)、目前是否有效、撤銷時間與撤銷者。 為什麼要有:這是 BR-API-CLI-024 的治理單位,也是補上現況「7 天 JWT 無法單獨撤銷」缺口的核心(§2 證據台帳)。 不追蹤:不記錄 CLI 執行過的命令內容(那是稽核表 E-4 的責任,避免雙寫與資料重複)。

E-3 Admin Refresh Token Family 需追蹤:同一次登入衍生的 refresh token 鏈、目前有效的那一枚、是否偵測到已失效者被重用。 為什麼要有:rotation 若無 family 概念,token 被竊後無法判斷是「合法 client 換新」還是「攻擊者重用舊的」。

E-4 後台操作稽核記錄(既有,需擴充) 既有已具備:操作者、模組、動作類型、目標表、目標 ID 與名稱、變更欄位明細、metadata、備註、IP、UA、requestId(admin-operation-log.interface.ts:42,51,53)。 本 PRD 需要新增的語意:這筆操作來自哪個通道(GUI/admin CLI/未來 MCP)。實作上可放 metadata 或獨立欄位,由 RD 決定;PRD 只要求「可用單一條件過濾出所有 CLI 操作」。 另需確保:remarks 對 CLI 破壞性操作為必填(BR-API-CLI-020)。

E-5 CLI 本機憑證檔 需追蹤:access token 與其到期時刻、refresh token、目前登入者的顯示名稱與 email、session 建立時間、目前連線的後端環境(dev/staging/prod)。 安全需求:檔案權限收斂至 owner-only、atomic write(沿用 wport-cli@c49a610 credentials-store.ts:160,163 的既有模式);不同環境分檔存放,避免誤用 dev 憑證打 prod(沿用同檔 :63-64 的 channel 分檔慣例)。 硬性禁止:任何流程不得 echo token 到終端、不得寫入 shell history、不得進 git。


6. CLI 命令面與資料需求(Command Surface & Data Needs)

6.1 命令樹(v1 目標)

wportadm
├─ login / logout / whoami                  # OAuth device flow(§6.3)
├─ sessions list | revoke [--all-others]    # CLI session 治理
├─ doctor                                   # 診斷:設定 / 連通性 / 身分 / 權限 / schema 指紋
├─ config set | get | path | reset

├─ companies                                # → company/v2
│   ├─ search   [--status --keyword --page --page-size]
│   ├─ view     <enc_id>
│   ├─ update   <enc_id> --file patch.json --reason "..."
│   ├─ approve  <enc_id> --reason "..."     # document-approval
│   ├─ reject   <enc_id> --reason "..."     # document-approval
│   └─ delete   <enc_id> --confirm --reason "..."

├─ jobs                                     # → job/v2
│   ├─ search   [--status --keyword --page --page-size]
│   └─ deactivate <enc_id> --confirm --reason "..."
│                                           # ⚠ 無 view:後端缺端點(§17 #6)
├─ resumes                                  # → resume/v2
│   ├─ search / view <enc_id>
│   ├─ disable  <enc_id> --confirm --reason "..."
│   └─ enable   <enc_id> --reason "..."

├─ accounts                                 # → account/v2(前台求職者會員)
│   ├─ search / view <enc_id>
│   └─ status   <enc_id> --set <state> --confirm --reason "..."

├─ admins                                   # → admin/users(限 SUPER_ADMIN)
│   ├─ list / create --file a.json / update <enc_id> --file p.json
│   ├─ delete <enc_id> --confirm --reason "..."
│   ├─ reset-password <enc_id> --reason "..."
│   └─ mfa-disable <enc_id> --confirm --reason "..."

├─ mfa                                      # → auth/mfa(操作者本人)
│   ├─ status / initialize / verify-setup / disable / rebind
│   └─ devices list | remove <device_id>

├─ keys                                     # → admin/enterprise-api-keys
│   ├─ list / view <enc_id> / create --file k.json
│   ├─ rename <enc_id> --name "..."
│   ├─ rotate <enc_id> --confirm --reason "..." [--reveal]
│   ├─ quota  <enc_id> --set <n> --reason "..."
│   ├─ revoke <enc_id> --confirm --reason "..."
│   ├─ usage  <enc_id> [--period YYYY-MM]
│   ├─ audit-logs <enc_id> [--page --page-size]
│   └─ companies search | members <enc_company_id>

├─ partners                                 # → admin/partner-keys
│   ├─ keys list | view | create | update | regenerate | delete
│   ├─ services list | bind | unbind | batch | update
│   ├─ tags list | search | view | create | update | delete
│   └─ companies list | tags get | tags set

├─ marketing                                # → marketing/*
│   ├─ posts list | create | update | delete
│   ├─ tasks list | create | update | delete
│   └─ utm   list | create | update | delete

├─ dashboard stats [--from --to]            # → siteadmin/v2/dashboard/stats
└─ scheduler status | run <taskName> --confirm --reason "..."

6.2 全域契約

契約說明
Exit code0/2/3/4/5/6沿用 pm_41;新增 6 = 需重新認證(ADR-006)
設定優先序flag > env > config 檔 > 預設沿用 wport-cli@c49a610 global-opts.ts:38
API base不落地 config,僅 --api / WPORTADM_API_BASE沿用參考 CLI 的 SSRF 收斂決策(global-opts.ts:64
輸出TTY → table;pipe → json沿用
欄位投影--fields <list> / --minimal僅 JSON 模式;降 Agent token
終端淨化所有 table/訊息輸出過 ANSI 淨化沿用 output.ts:43-44,100
來源標記每請求帶 X-Source: admin_cliBR-039;後端須先支援收錄
冪等寫入端點帶 Idempotency-Key後端須先支援(§17 #4)
破壞性--confirm + --reason 皆必填BR-API-CLI-020/021
憑證owner-only、atomic write、依環境分檔沿用 credentials-store.ts:160,163

6.3 認證命令

# 首次登入:開瀏覽器完成授權(含 MFA),CLI 端輪詢取 token
wportadm login
#   → 顯示短代碼與驗證網址;SSH 環境加 --no-browser 只印網址

wportadm whoami                 # 顯示身分、role、權限摘要、session 建立時間(離線)
wportadm sessions list          # 列出此帳號所有 CLI session
wportadm sessions revoke <id>   # 撤銷指定 session
wportadm sessions revoke --all-others   # 保留當前,踢掉其餘(筆電遺失情境)
wportadm logout                 # 撤銷當前 session(server 端)+ 清除本機憑證

設計要點:MFA 一律在瀏覽器完成,CLI 端不收 TOTP 碼(核彈 N-4)。token 於過期前自動 refresh,使用者無感;refresh 失敗(撤銷/過期)時以 exit 6 明示需重跑 login,讓腳本能區分「重登即可」與「真的無權限(exit 3)」。

6.4 審核類命令(情境 S-3)

# 拉待審清單(Agent 讀 JSON)
wportadm companies search --status pending --output json --minimal

# 逐家取詳情
wportadm companies view <enc_id> --output json

# 判定並留下理由(--reason 必填,寫入稽核 remarks)
wportadm companies approve <enc_id> --reason "統編與登記文件相符,負責人一致"
wportadm companies reject  <enc_id> --reason "登記文件過期(2024/12 到期)"

# 職缺下架(破壞性 → 需 --confirm)
wportadm jobs deactivate <enc_id> --confirm --reason "違反 BR-009 職缺內容規範"

# 履歷停用 / 復用
wportadm resumes disable <enc_id> --confirm --reason "檢舉屬實:冒用他人經歷"
wportadm resumes enable  <enc_id> --reason "申訴成立,恢復"

6.5 金鑰治理命令(S4 主線)

wportadm keys list --output json
wportadm keys rotate <enc_id> --confirm --reason "疑似外洩,客戶回報" --reveal
wportadm keys revoke <enc_id> --confirm --reason "合約終止"
wportadm keys usage  <enc_id> --period 2026-08
wportadm keys audit-logs <enc_id> --page 1

--reveal 僅在 rotatecreate 當下輸出一次完整新金鑰;其餘任何輸出一律遮罩至末四碼(沿用 wport-cli@c49a610 credentials-store.ts:73maskKey 慣例)。


7. 使用者動作與後端需求(User Actions & Backend Needs)

#動作後端需求新建?
ACT-1login 取得授權device flow 四端點(§8.1)新建
ACT-2授權頁完成 MFA沿用既有 admin 登入 + MFA 流程(admin.service.ts:167既有
ACT-3token 自動 refreshrefresh 端點 + rotation + family 重用偵測新建
ACT-4sessions list/revokeCLI session 查詢與撤銷 API新建
ACT-5所有讀取類命令沿用既有 /v2 端點,無需改動既有
ACT-6所有寫入類命令端點沿用既有;須新增X-Source 收錄、稽核 channel 語意、Idempotency-Key 支援既有端點 + 擴充
ACT-7--reason 寫入稽核既有 remarks 欄位承接;須改為 CLI 通道必填既有 + 擴充
ACT-8自我提權防護domain 層擋下指向自身的 role/status 變更新建
ACT-9通道限流channel=admin_cli 可單獨設限(沿用既有 Throttle 基礎設施)既有 + 擴充

ACT-6 的三件事必須分開看:①端點路徑與 DTO 不改(CLI 打的是既有 /v2 端點)②X-Source 收錄與稽核 channel必做(BR-039 落差,v1 Blocking)③Idempotency-Key必做但可分批(僅破壞性與建立類端點需要,讀取類不需要)。


8. API Hints(提示用,非最終 API 設計)

路徑一律含 global prefix /ams-apiW101-AMS@bf0fb57 src/main.ts:40)。以下為需求提示,最終契約由 RD 於 OpenAPI Spec 定案。

8.1 Admin Device Flow(新建)

用途方法路徑提示認證
取得裝置代碼POST/ams-api/admin/oauth/device/code無(僅需 client 識別)
輪詢換 tokenPOST/ams-api/admin/oauth/device/token無(憑 device_code)
Refresh tokenPOST/ams-api/admin/oauth/token/refresh憑 refresh token
撤銷POST/ams-api/admin/oauth/revokeBearer

RFC 8628 為對標基準(同 pm_47 個人線)。授權頁本身走 Admin-Web(§9),不在此列。

8.2 CLI Session 治理(新建)

用途方法路徑提示權限
列出自己的 CLI sessionGET/ams-api/admin/cli-sessions本人
撤銷單一 sessionDELETE/ams-api/admin/cli-sessions/{id}本人
撤銷其餘全部POST/ams-api/admin/cli-sessions/revoke-others本人

8.3 既有端點(CLI 直接使用,路徑實查自 controller)

命令群既有 controller 路徑來源
companiescompany/v2W101-AMS@bf0fb57 src/modules/company/controllers/company.controller.ts:29
jobsjob/v2.../job/controllers/job.controller.ts:14
resumesresume/v2.../resume/controllers/resume.controller.ts:14
accountsaccount/v2.../account/controllers/account.controller.ts:16
adminsadmin/users.../admin/controller/admin-user.controller.ts:17
mfaauth/mfa.../auth/controllers/mfa.controller.ts:17
keysadmin/enterprise-api-keys.../enterprise-api-key/controllers/enterprise-api-key-admin.controller.ts:27
partnersadmin/partner-keys.../partner-key/controllers/partner-key-admin.controller.ts:35
marketing postsmarketing/posts.../marketing/controllers/posts.controller.ts:26
dashboardsiteadmin/v2.../siteadmin/controllers/siteadmin-dashboard.controller.ts:12
schedulerschedulerW101-AMS@bf0fb57 src/scheduling/controllers/scheduler.controller.ts:11

8.4 需注意的既有行為

  • 全域 ValidationPipeforbidNonWhitelistedsrc/main.ts:79):CLI 送出 body 時多帶一個欄位就會 400--file 讀入的 JSON 必須先過本地 schema 驗證再送出,否則使用者會收到難以理解的 400。
  • Swagger 僅非 production 掛載src/main.ts:90):CLI 的型別 codegen 只能從 dev/staging 取 spec,不能指望 prod 有 /ams-api/api-doc。此點與參考 CLI 的 gen:openapi:prod(從 prod swagger 取)做法相反,須改為從 dev 取或凍結 spec 進 repo(ADR-009)。
  • 既有節流偏嚴:DEFAULT 為 5 次/60 秒(throttle.constants.ts:6-7)。CLI 的批次讀取情境(S-3 拉 30 家公司)會撞到 ADMIN_LIST 20 次/60 秒的上限,須為 CLI 通道評估獨立額度(§17 #8)。

9. Web 觸點(Admin-Web,文字規格)

三個觸點只有 W-1 是 v1。W-2/W-3 列 v2,理由見各節與 §14 —— 兩者在 v1 都有可用的替代路徑, 不擋出貨。這個取捨的完整推導記於 §19 問題 11,避免後人重新煩惱一次。

W-1 裝置驗證頁 /cli/activatev1,Blocking

  • 進入方式wportadm login 開啟瀏覽器導向此頁(或使用者手動開啟後貼上代碼)
  • 未登入時:導向既有 admin 登入頁,完成帳密 + MFA 後回到本頁(沿用既有流程,W101-Admin-Web@0ee5ee9 CLAUDE.md:15
  • 頁面內容
    1. 代碼輸入框(若網址已帶代碼則預填並唯讀)
    2. 顯示請求來源摘要:CLI 版本、作業系統、發起 IP、發起時間
    3. 明確文案:「授權後,這台裝置將能以你的身分({role})操作後台。你可以隨時在『帳號設定 → 已授權的 CLI』撤銷。」
    4. 兩個按鈕:授權拒絕
  • 結果狀態:授權成功、已拒絕、代碼過期、代碼無效 —— 四種各有明確文案與後續指引
  • 安全要求:授權按鈕須防 CSRF;代碼比對失敗須節流,不得無限嘗試

W-2 CLI 存取頁(設定 > 帳號設定 新增分頁)(v2

位置理由src/views/setting/account/account.vue 已是分頁結構(BasicSetting / SafetySetting), 而 SafetySetting.vue 正是管 MFA 的地方(mfa_enabled / mfa_secret / 綁定流程)。CLI 的 device flow 本來就要過 MFA,兩者相鄰在使用者心智上連得起來。且此頁每個 admin 都要能開 —— 這是它不能放在 會員管理 > 管理員列表 的原因(該區塊全部端點限 SUPER_ADMIN,見 §2 台帳)。

  • 安裝:取得套件的方式 + 可複製的指令
  • 快速上手loginwhoami → 一兩個實際命令
  • 我的已授權裝置:裝置描述、建立時間、最後使用時間、建立時 IP、是否為本次 session;可逐一撤銷、一鍵撤銷其餘
  • 能力範圍說明:明講「後台部分頁面 CLI 沒有」(ADR-002 的後果)
  • 與 CLI sessions list/revoke 讀寫同一組 API(§8.2),確保兩邊一致

為何列 v2:「我的裝置 + 撤銷」CLI 自己就有 sessions list/revoke,不需要畫面;「安裝說明與憑證」 在 v1 的使用者規模(數名內部 admin)以人工交付即可。殘留風險低:人工發出的安裝憑證即使離職後未收回, 持有者也只是能下載一支 CLI —— 沒有有效 admin 帳號它什麼都做不了。套件存取權與後台存取權是不同信任域, 這一點在 v1 與 v2 都成立(連帶說明:安裝憑證不是 admin API key,不牴觸 ADR-001 與核彈 N-4)。

W-3 管理員列表的 CLI 治理視圖(會員管理 > 管理員列表)(v2

位置理由:該頁本來就是「管理其他 admin」的地方(建帳號、重設密碼、關閉他人 MFA 皆在此), 「撤銷他人 CLI session」屬同一類動作。

  • 欄位:該管理員是否有活躍的 CLI session、最後使用時間
  • 動作super_admin 強制撤銷指定管理員的全部 CLI session
  • 權限:沿用該頁既有的 SUPER_ADMIN 限制

為何列 v2:v1 有可用的緊急路徑 —— DELETE /ams-api/admin/users/{enc_id} 為軟刪除 (admin-user.service.ts:141),而 isActive() 要求 deletedAt IS NULLstatus === ACTIVEadmin.repository.ts:193-199),此檢查在 jwt.strategy.ts:26 每個請求都會跑。因此刪除管理員後 其 CLI session 於下一個請求即失效。 已知限制:刪帳號是 all-or-nothing,無法「只收 CLI 但保留 GUI」。「筆電遺失但人仍在職」的情境, v1 只能刪帳號再重建 —— 這正是 W-3 的價值,但發生頻率不足以擋版。


10. Flowcharts

10.1 登入(Device Flow)

flowchart TD
    A[wportadm login] --> B[POST device/code]
    B --> C[顯示短代碼 + 驗證網址]
    C --> D{有瀏覽器?}
    D -->|是| E[自動開啟 /cli/activate]
    D -->|--no-browser| F[僅印出網址,使用者自行開啟]
    E --> G[Admin-Web /cli/activate]
    F --> G
    G --> H{已登入後台?}
    H -->|否| I[導既有 admin 登入頁]
    I --> J[帳密驗證]
    J --> K{已啟用 MFA?}
    K -->|是| L[TOTP 驗證]
    K -->|否| M[直接通過]
    L --> M
    M --> G
    H -->|是| N[顯示授權請求摘要]
    N --> O{使用者決定}
    O -->|授權| P[標記 request 已授權]
    O -->|拒絕| Q[標記已拒絕]
    B -.CLI 同時開始輪詢.-> R[POST device/token 輪詢]
    R --> S{狀態?}
    S -->|待授權| T[依 interval 等待後重試]
    T --> R
    S -->|已授權| U[取得 access + refresh token]
    S -->|已拒絕| V[exit 3:使用者拒絕授權]
    S -->|已過期| W[exit 3:代碼過期,請重跑 login]
    S -->|輪詢過快| T
    P --> S
    Q --> S
    U --> X[寫入本機憑證檔 owner-only]
    X --> Y[印出身分與 role,exit 0]

10.2 一般命令執行(含 token 生命週期與稽核)

sequenceDiagram
    participant U as 使用者/Agent
    participant C as wportadm
    participant S as 本機憑證檔
    participant A as AMS
    participant L as admin_operation_logs

    U->>C: wportadm companies approve <id> --reason "..."
    C->>C: 驗證參數(--reason 必填、破壞性需 --confirm)
    alt 參數不合法
        C-->>U: exit 2(不送出請求)
    end
    C->>S: 讀取 access token
    alt token 即將過期
        C->>A: POST token/refresh(rotation)
        alt refresh 失敗(已撤銷/過期)
            A-->>C: 401
            C-->>U: exit 6(需重跑 login)
        end
        A-->>C: 新 access + 新 refresh
        C->>S: atomic write 更新憑證
    end
    C->>A: PATCH company/v2/{id}/document-approval<br/>Bearer + X-Source: admin_cli + Idempotency-Key
    A->>A: JwtAuthGuard → RolesPermissionsGuard
    alt 權限不足
        A-->>C: 403
        C-->>U: exit 3(權限不足,非重登可解)
    end
    A->>A: domain 檢查(含自我提權防護)
    A->>L: 寫稽核(channel=admin_cli、remarks=reason)
    A-->>C: 200
    C->>C: 輸出淨化(ANSI/控制字元)
    C-->>U: table 或 json,exit 0

10.3 Edge Case 覆蓋矩陣

分類情境CLI 行為
Network後端不可達exit 4,訊息含目前 base URL 與 doctor 提示
Network逾時--timeout 中止,exit 4
Network輪詢期間斷網依 interval 重試至代碼過期;過期後 exit 3
Performance撞到節流(429)讀取 Retry-After 並印出建議等待秒數,exit 3
Data/Input--file JSON 格式錯本地驗證即擋,exit 2,指出出錯欄位
Data/Input--file 多帶欄位本地擋下,避免後端 forbidNonWhitelisted 回難懂的 400(§8.4)
Data/Input--reason 缺漏或超長exit 2,不送出請求
Data/Inputenc_id 不存在exit 3,訊息明示查無此資源
Data/Input輸出含終端跳脫序列一律淨化後輸出(output.ts:43-44
User Interrupt輪詢中 Ctrl+C不留半吊子憑證;已建立的授權請求自然過期
User Interrupt寫入中斷線Idempotency-Key 可安全重試
Auth/Lifecycleaccess token 過期自動 refresh,使用者無感
Auth/Lifecyclerefresh token 過期/已撤銷exit 6,明示重跑 login
Auth/Lifecyclesession 被他人撤銷下一請求 401 → exit 6(殘存 ≤ 30s)
Auth/Lifecycle帳號被停用exit 3;不得暗示帳號狀態細節給未授權者
Auth/Lifecyclerefresh token 重用偵測該 family 全撤,記 audit event,exit 6
Auth/Lifecycle帳號中途被停用下一請求即 401(jwt.strategy.ts:26 每請求查狀態)→ exit 6
Auth/Lifecyclerole 中途被降級不會即時生效——role 快取於 token,最長需等 1 小時 TTL 到期換新(§15)。需立即生效者由管理者撤銷該 session(≤30s)。降級生效後下一請求 403 → exit 3(非 6,避免誤導重登)
Logical對自己執行 admins delete本地擋 + 後端 403(BR-API-CLI-023)
Logical已審核的公司再次 approve後端回既有狀態衝突錯誤,exit 3
Logical已撤銷的金鑰再次 revoke冪等視為成功或明確狀態衝突,RD 定調(§19 問題 3)
Logicaldev 憑證誤打 prod憑證依環境分檔,天然隔離;doctor 印出目前環境

11. 實作備註(Implementation Notes)

11.1 CLI 必做

  1. 沿用參考 repo 的 monorepo 分層(core 傳輸與型別/cli 命令層),但不共用套件(ADR-003)
  2. exit code 契約集中一處定義,任何命令不得自行 process.exit 繞過
  3. 所有終端輸出走淨化 helper(output.ts:43-44 同款)
  4. 憑證 atomic write + owner-only + 依環境分檔
  5. --file 輸入一律先過本地 schema 驗證再送出(§8.4)
  6. token refresh 需處理併發:同一時刻多個命令不得各自 refresh 造成 rotation 互斥失敗

11.2 後端必做(AMS,v1 Blocking)

  1. device flow 授權伺服器(§8.1)
  2. CLI session 治理 API(§8.2)
  3. X-Source 收錄 + 稽核 channel 語意(BR-039 落差)
  4. 寫入端點支援 Idempotency-Key
  5. 自我提權/自我鎖定的 domain 層防護(BR-API-CLI-023)
  6. CLI 通道獨立節流額度評估(§8.4)

11.3 禁止事項

  1. 不得為 CLI 開任何免 MFA 的憑證取得路徑(核彈 N-4)
  2. 不得讓 client 自行宣告 channel 作為授權依據(BR-API-CLI-019)
  3. 不得把 admin token 寫入 log、shell history 或錯誤訊息
  4. 不得為了「方便」而放寬 forbidNonWhitelisted

11.4 相依性

相依影響阻塞?
AMS device flow ASlogin 無法運作
AMS session APIsessions 命令無法運作
AMS X-Source 收錄S2 稽核成功條件無法達成
Admin-Web W-1 頁device flow 無授權介面
AMS Idempotency-Key斷線重試不安全是(可分批)
私有 registry 設定無法散布是(非技術)

12. ADR(架構決策紀錄)

ADR-001:Admin CLI 採 OAuth Device Flow,不沿用現有登入、不發長期 key

  • Status:Accepted
  • Context:AMS 現況登入發 7 天全權限 JWT、無 refresh、無法單獨撤銷單一 session(admin.service.ts:139)。CLI 若沿用,等於把最高權限憑證長期落地磁碟且不可治理。
  • Decision:新建 admin device flow 授權伺服器;access token TTL 1h、refresh 30d sliding/90d absolute 且 rotation;MFA 一律在瀏覽器完成。不核發長期 admin API key。
  • Consequences:後端工作量最大(新增 AS + session 治理)。換得可撤銷、可限流、可稽核的通道。既有 GUI 的 7 天 JWT 風險不在本 PRD 範圍內修復,列為 §19 問題 1。
  • Decision maker:Eric|Date:2026-08-25

ADR-002:v1 範圍=AMS 已實作端點全包,明確放棄 GUI parity

  • Status:Accepted
  • Context:Admin-Web 有 18 個路由模組、34 個 api 檔,但 AMS 僅 12 組真端點;purchaseplatformsystem 等為 mock-only(§13.2)。pm_41/pm_47 的「GUI 能做什麼 CLI 就做什麼」在此無法成立。
  • Decision:v1 覆蓋所有 AMS 已實作端點;mock-only 模組明列為 out of scope,其 CLI 化的前置是後端先補端點。
  • Consequences:CLI 上線時「後台有些頁面 CLI 沒有」是預期行為,須在 README 與 doctor 明示,避免被當成缺陷回報。
  • Decision maker:Eric|Date:2026-08-25

ADR-003:獨立套件 + 私有 registry,不併入 @wport/cli

  • Status:Accepted
  • Context@wport/cli 是公開 npm 套件,面向外部企業與求職者。admin CLI 握有後台最高權限,其命令面、端點結構、權限模型若公開發布等同送出攻擊地圖。
  • Decision:另發 @wport/admin-cli(執行檔 wportadm)至私有 registry(GitHub Packages 或 npm private scope),僅內部 admin 可安裝。
  • Consequences:需維護第二套 release 流程;兩套 CLI 的共用邏輯(輸出淨化、憑證儲存、exit code)會有一定程度重複。刻意接受此重複——跨 registry 共用套件會把私有實作細節洩進公開相依鏈。
  • Decision maker:Eric|Date:2026-08-25

ADR-004:通道不得擴權,token 繼承既有 role/permission

  • Status:Accepted
  • Context:admin CLI 若自帶權限模型,會出現「GUI 看不到但 CLI 做得到」的不一致,且無法沿用既有 RolesPermissionsGuardroles-permissions.guard.ts:30-47)。
  • Decision:CLI token 完全繼承該 admin 的 role 與 DB 權限;後端額外標記 channel=admin_cli,僅用於限縮(deny list、限流),不得用於放寬。
  • Consequences:CLI 能力面自動隨既有權限模型演進,零額外維護。代價是無法為 CLI 設計「唯讀專用」的窄權限 token——列為 v2(§14)。
  • Decision maker:Eric|Date:2026-08-25

ADR-005:channel 以 token 綁定為準,X-Source 僅供交叉比對

  • Status:Accepted
  • Context:BR-039 要求記錄寫入來源。若單靠 client 送的 X-Source header,任何持有 token 者都能偽造來源,稽核即失效。
  • Decision:通道身分由 token 本身承載(device flow 發出的 token 天然帶 admin_cli 屬性);X-Source header 仍照送,但僅作為交叉比對與除錯訊號,不作為授權或稽核的權威來源
  • Consequences:稽核可信度提高。若兩者不一致,應記為異常事件供監控。
  • Decision maker:Eric|Date:2026-08-25

ADR-006:新增 exit code 6 = 需重新認證

  • Status:Accepted
  • Context:參考 CLI 的契約中,401(token 過期)與 403(權限不足)都落在 exit 3。對自動化腳本而言,前者「重登即可」、後者「重登也沒用」,混為一談會導致無窮重試迴圈。
  • Decision:新增 exit 6 專指「憑證失效,需重跑 login」;403 維持 exit 3。
  • Consequences:與 @wport/cli 的 exit code 契約產生一處差異,須在兩份 README 明確對照。判定為值得的差異——admin CLI 的自動化情境比公開 CLI 更依賴這個區分。
  • Decision maker:Eric|Date:2026-08-25

ADR-007:所有寫入必填 --reason,破壞性另需 --confirm

  • Status:Accepted
  • Context:稽核表已有 remarks 欄位(admin-operation-log.interface.ts:42 所屬 interface),但 GUI 未強制填寫,導致事後追查常常只看到「誰在何時改了什麼」,缺少「為什麼」。
  • Decision:CLI 通道一律強制 --reason(1~200 字),寫入 remarks;破壞性操作另需 --confirm。缺漏於 client 端即擋(exit 2),不送出請求。
  • Consequences:CLI 的稽核品質高於 GUI。這會凸顯 GUI 端未強制填理由的落差,建議後續對齊,列 §19 問題 2。
  • Decision maker:Eric|Date:2026-08-25

ADR-008:破壞性操作不提供批次

  • Status:Accepted
  • Context:pm_47 個人線採「限量取代禁令」(批次投遞 ≤20/次)。但 admin 線的破壞性操作(刪公司、刪管理員)沒有等價的「限量後仍可接受」的失誤成本。
  • Decision:破壞性操作一律單筆 + --confirm;不提供 --file 批次、不提供萬用字元。非破壞性讀取不受限。
  • Consequences:S-3 的 30 家審核需逐筆呼叫(Agent 自然會迴圈,非阻礙)。與 pm_47 的哲學差異須在文件說明:限量取代禁令適用於可回復的操作,不適用於不可回復的
  • Decision maker:Eric|Date:2026-08-25

ADR-009:型別 codegen 來源改為 dev 環境或凍結 spec

  • Status:Accepted
  • Context:參考 CLI 的預設 codegen 從 prod swagger 取(gen:openapi:prod)。但 AMS 的 Swagger 僅在非 production 掛載src/main.ts:90),prod 無 /ams-api/api-doc
  • Decision:codegen 來源改為 dev/staging 環境;並將取得的 spec 凍結進 repo 作為 fixture,使 CI 可離線重現且能 diff 出後端 DTO 變動。
  • Consequences:與參考 CLI 的做法相反,須在 README 明確說明原因,避免後人「照抄 prod 版」而困惑。
  • Decision maker:Eric|Date:2026-08-25

ADR-010:不做 MCP,v1 只做 CLI

  • Status:Accepted
  • Context:pm_48 已建立 MCP connector 架構,理論上 admin 線可比照。
  • Decision:v1 只交付 CLI。admin 線 MCP 化列 v2,且須先累積 CLI 通道的實際稽核與濫用資料。
  • Consequences:Agent 需透過 CLI 而非原生 MCP tool 操作,體驗略遜但完全可用。換得「先在可控通道觀察一段時間」的安全緩衝。
  • Decision maker:Eric|Date:2026-08-25

ADR-011:不做 mock 模式

  • Status:Accepted
  • Context:Admin-Web 本地開發為純 Mock 模式(CLAUDE.md:47),CLI 是否比照?
  • Decision:不做。CLI 一律打真實後端,環境以 --api 或環境變數切換,並依環境分檔存憑證。
  • Consequences:CLI 的本地開發需要一個可用的 dev 後端。避免了「mock 與真實行為不一致」這類最難查的缺陷。
  • Decision maker:Eric|Date:2026-08-25

ADR-012:jobs 無 view 子命令,不為此擋版

  • Status:Accepted
  • Context:實查 job/v2 僅有 searchdeactivate,無單筆詳情端點(job.controller.ts:14,23,57)。
  • Decision:v1 的 jobs 命令群不提供 view;以 search 的結果欄位替代。後端補端點後再加,不因此擋 v1 出貨。
  • Consequencesjobs deactivate 前無法在 CLI 內取得完整職缺內容供判讀,Agent 需依賴 search 回傳欄位。列入 §17 sync-fix #6 追蹤。
  • Decision maker:Eric|Date:2026-08-25

ADR-013:CLI session 上限 5,超額汰換最久未使用

  • Status:Accepted
  • Context:無上限則憑證累積無法治理;上限過低則多機工作(筆電/桌機/CI)受阻。
  • Decision:每 admin active CLI session ≤ 5,超額時汰換最久未使用者並於下次登入告知。
  • Consequences:正常使用不受影響。異常累積會自然浮現。數值屬營運參數,調整不需改版 PRD。
  • Decision maker:Eric|Date:2026-08-25

ADR-014:不追蹤 CLI 執行過的命令內容

  • Status:Accepted
  • Context:session 實體是否要記錄「這個 session 跑過哪些命令」?
  • Decision:不記錄。命令的後果已由既有 admin_operation_logs 逐筆承接(含變更欄位與理由);session 只治理憑證生命週期。
  • Consequences:避免雙寫與資料重複。代價是「純讀取類命令」不留稽核痕跡——判定可接受,與 GUI 瀏覽行為不留稽核一致。
  • Decision maker:Eric|Date:2026-08-25

13. 四大一致性表格

13.1 欄位對照表

概念CLI 呈現後端來源SoT版本
資源識別碼<enc_id> 位置參數/v2 端點的 enc_id path param後端v1
操作理由--reason稽核 remarksPRD(BR-API-CLI-020)v1
通道X-Source: admin_cli(僅比對)token 綁定之 channel後端(ADR-005)v1
冪等鍵自動產生,不外露Idempotency-Key header後端v1
操作者身分whoami 輸出JWT payload { id, email, role }後端v1
分頁--page / --page-size各端點既有分頁參數後端v1
環境--api / 環境變數N/A(client 端)PRDv1

13.2 GUI 模組 vs 後端端點對照(本 PRD 的範圍界定依據)

Admin-Web 模組前端 api 檔AMS controllerv1 CLI
會員-公司src/api/members/company.tscompany/v2companies
會員-求職者src/api/members/job-seekers.tsaccount/v2accounts
會員-管理員src/api/members/admins.tsadmin/usersadmins
職缺src/api/jobs/all.tsjob/v2jobs(無 view)
履歷src/api/resumes/all.tsresume/v2resumes
企業 API Keysrc/api/enterprise-api-keys/index.tsadmin/enterprise-api-keyskeys
合作夥伴src/api/partner/*.tsadmin/partner-keyspartners
行銷規劃src/api/marketing/*.tsmarketing/*marketing
儀表板src/api/dashboard/console.tssiteadmin/v2(部分)dashboard(僅 stats)
訂單管理src/api/purchase/*.ts❌ v2
平台-廣告/通知src/api/platform/*.ts❌ v2
系統-角色/選單/備份src/api/system/*.ts❌ v2
訊息管理src/api/messages/*.ts❌ v2
媒體庫src/api/gallery/media.ts❌ v2
雲端硬碟src/api/drive/file.ts❌ v2
歷史紀錄src/api/history/*.ts❌ v2
開發者-Lokisrc/api/developer/loki.ts❌ v2

「AMS controller 無」=該前端模組目前由 mock/ 提供資料,無真實後端端點(W101-Admin-Web@0ee5ee9 mock/README.md:25)。

13.3 常數表

常數SoT版本
access token TTL1 小時PRD(BR-API-CLI-018)v1
refresh token30 天 sliding/90 天 absolutePRD(BR-API-CLI-018)v1
device code 有效期≤ 10 分鐘(RD 定值)RDv1
session 撤銷生效≤ 30 秒PRD(BR-API-CLI-024)v1
active CLI session 上限5PRD(ADR-013)v1
--reason 長度1~200 字PRD(BR-API-CLI-020)v1
exit code0/2/3/4/5/6PRD(ADR-006)v1
既有 admin JWT7 天(現況,本 PRD 不改admin.service.ts:139現況
既有 TEMP token5 分鐘admin.service.ts:133現況
既有信任裝置30 天admin.service.ts:186現況
既有節流 DEFAULT5 次/60 秒throttle.constants.ts:6-7現況
既有節流 ADMIN_LIST20 次/60 秒throttle.constants.ts:16現況

13.4 事件字典

事件觸發時機Payload 要點SoT版本
admin_cli.session_createddevice flow 授權完成admin、裝置描述、IP、CLI 版本後端v1
admin_cli.session_revoked撤銷(自撤/他撤/汰換)session、撤銷者、原因分類後端v1
admin_cli.refresh_reuse_detected偵測到失效 refresh 被重用token family、來源 IP後端v1
admin_cli.write任何經 CLI 的寫入沿用既有稽核欄位 + channel + reason後端v1
admin_cli.source_mismatchX-Source 與 token channel 不符兩者值、admin、IP後端v1

13.5 術語字典

術語定義不是什麼
admin後台管理員,與前台 account 完全分離(CLAUDE.md:15不是前台的企業使用者
account前台會員(求職者/企業使用者)不是後台管理員
channel請求來源通道(web / admin_cli / 未來 mcp不是 X-Source header 本身(ADR-005)
CLI session一台裝置的一份 CLI 授權,治理最小單位不是 HTTP session,也不是瀏覽器 session
破壞性操作不可逆或需人工復原的操作(刪除、撤銷、停用)不含可自由來回切換的狀態變更
GUI parity「GUI 有的 CLI 都有」本 PRD 明確不追求(ADR-002)

14. 版本凍結表

項目v1/v2Blocking?理由決策者日期
Device flow 認證v1無此則 CLI 無法登入Eric2026-08-25
CLI session 治理v1ADR-001 的治理承諾Eric2026-08-25
X-Source 收錄 + channel 稽核v1BR-039 落差;S2 成功條件Eric2026-08-25
W-1 裝置驗證頁v1device flow 無介面則不成立Eric2026-08-25
W-2 CLI 存取頁(帳號設定)v2「我的裝置」CLI 已有 sessions 命令;安裝說明在 v1 規模可人工交付(§9 W-2)Eric2026-08-26
W-3 管理員列表 CLI 治理視圖v2v1 以軟刪除管理員作為緊急撤銷路徑,下一請求即失效(§9 W-3)Eric2026-08-26
審核類命令群v1情境 S-3Eric2026-08-25
金鑰治理命令群v1S4 主線Eric2026-08-25
Idempotency-Keyv1是(可分批)斷線重試安全性Eric2026-08-25
自我提權防護v1BR-API-CLI-023Eric2026-08-25
CLI 通道獨立節流v1可先沿用既有額度觀察Eric2026-08-25
jobs viewv2後端缺端點(ADR-012)Eric2026-08-25
mock-only 模組 CLI 化v2後端須先補端點(ADR-002)Eric2026-08-25
唯讀專用窄權限 tokenv2ADR-004 ConsequencesEric2026-08-25
admin 線 MCPv2ADR-010Eric2026-08-25
Shell completion/binary/i18nv2DX polishEric2026-08-25
Storybook mockup(W-1)v1§9 文字規格已足以開工Eric2026-08-25

15. Token / API 契約完整性

項目決定
TTLaccess 1 小時;refresh 30 天 sliding/90 天 absolute;device code ≤ 10 分鐘
Refresh 策略必須 rotation;舊 token 立即失效;以 family 追蹤
重用偵測失效 refresh 被重用 → 撤銷該 family 全部憑證 + 記 admin_cli.refresh_reuse_detected
撤銷政策可由本人於 CLI 或 GUI 撤銷;logout 撤銷當前;revoke --all-others 保留當前
撤銷生效≤ 30 秒(BR-API-CLI-024)
過期行為access 過期 → 自動 refresh;refresh 失效 → exit 6,明示重跑 login
即時權限重查現況分兩層,須分開講:①帳號狀態每請求查 DB(jwt.strategy.ts:26)→ 停用即時生效;②role 直接取自 token payload(jwt.strategy.ts:37)→ 降權不會即時生效;③DB 功能權限查詢僅在端點宣告 permissions 時執行,現況 80 個 @Auth 端點皆未宣告roles-permissions.guard.ts:39),故從未執行。
本 PRD 的處置:access token TTL 壓到 1 小時,即為 role 降權的最大生效延遲上界;需要更快者,走 session 撤銷(≤30s)而非等 role 傳播(§19 問題 9)
冪等破壞性與建立類端點須支援 Idempotency-Key;讀取類不需要
通道權威token 綁定為準;X-Source 僅交叉比對(ADR-005)
不擴權token 繼承既有 role/permission,channel 僅用於限縮(BR-API-CLI-019)
Session 上限每 admin ≤ 5,超額汰換最久未使用(ADR-013)

16. Mock Data Schema

N/A —— 本 PRD 不產 Storybook mockup(§9 為文字規格)。CLI 本身不做 mock 模式(ADR-011)。


17. Sync-Fix List

#項目負責Blocking?
1建立 W101-Admin-CLI repo,設定私有 registry 與 release 流程CLI + DevOps
2AMS 實作 admin device flow AS(§8.1)與 session 治理 API(§8.2)後端
3AMS 補 X-Source 收錄 + 稽核 channel 語意(BR-039 落差,全 src/ 目前零實作)後端
4AMS 破壞性/建立類端點支援 Idempotency-Key後端是(可分批)
5AMS 加自我提權/自我鎖定的 domain 層防護(BR-API-CLI-023)後端
6AMS 補 job/v2 單筆詳情端點(現況僅 search + deactivate,ADR-012)後端否(v2)
7Admin-Web 實作 W-1 裝置驗證頁 + W-2 session 清單(§9)前端
8評估 CLI 通道獨立節流額度(既有 DEFAULT 5/60s 對批次讀取偏嚴,§8.4)後端
9產出 W-1 的 Storybook mockup(可用 gen-storybook-mockup-gen前端
10business-rules.md 納入 BR-API-CLI-018~024(§4.2)PM
11codegen 來源設定為 dev/staging 並凍結 spec 進 repo(ADR-009)CLI
12README 明確說明「後台有些頁面 CLI 沒有」為預期行為(ADR-002 Consequences)CLI
13README 對照 @wport/cli@wport/admin-cli 的 exit code 差異(ADR-006)CLI
14評估 GUI 端是否比照強制填寫操作理由(ADR-007 Consequences)產品
15scripts/prd-lint.py 併入 main(目前僅存於 origin/ericlu-sys/prd-refinePM
16授權模型現況交後端設計(§19 問題 1 的六項現況+三項產品層要求);其中 DTO 缺角色欄位、建立時未寫 admin_user_role、API 回應含明碼密碼三項屬漏寫,可先行修復後端是(影響 ADR-004 的有效性)

18. Email Template

N/A —— 本 PRD 不觸發任何郵件。

唯一相近的情境是 session 汰換(ADR-013)與 refresh 重用偵測(§15)。兩者 v1 以後台事件記錄呈現,不寄信;若後續要加安全通知信,屬獨立需求。


19. PRD 問題清單

#PRD 章節/規則問題建議修法類別影響SoT
1§3.2、§3.4 N-4、ADR-004既有授權模型的六項現況(全部實查,見 §2 台帳):①建立管理員的 DTO 只有 email,無角色欄位 ②createAdminUser() 從不寫入 admin_user_role ③無角色列時 resolveAdminIdentity 判為 ADMIN,通過幾乎所有 @Auth 守衛 ④getPermissions() 是寫死 stub,且 80 個端點無一宣告 permissions ⑤新帳號 mfaEnabled: false,登入不需 MFA ⑥完整 token 7 天、無 refresh、不可單獨撤銷。合起來的效果是:每個管理員實際上都是全權,且無法降權、停用或撤銷。這直接削弱本 PRD 的 ADR-004(「token 繼承既有 role/permission,不擴權」)——繼承一個無上限的權限,等於沒有約束授權模型要怎麼修,由後端設計(scope、permission 表、或其他形態皆可,本 PRD 不指定機制)。本 PRD 只提出三項產品層要求:MFA 必須為強制新帳號預設最小權限必須可降權與撤銷。另有三項純屬漏寫、與授權架構無關,可逕行修復:DTO 缺角色欄位、建立時未寫 admin_user_role、API 回應含明碼密碼資安後端設計
2ADR-007CLI 強制填 --reason,GUI 未強制,導致同一稽核表的資料品質不一致評估 GUI 對破壞性操作亦強制填理由(sync-fix #14)一致性產品
3§10.3對已撤銷的金鑰再次 revoke:應視為冪等成功,或回狀態衝突?RD 於 OpenAPI Spec 定調並在 CLI 對應錯誤訊息契約RD
4§5.2 E-1device code 有效期寫「≤ 10 分鐘(RD 定值)」,未鎖死RD 定值後回寫 §13.3 常數表契約RD
5§4.2BR-API-CLI-018~024 為提案,尚未進 business-rules.md。另:§4.2 的編號說明引述了主序列的既有搶號情形——BR-039BR-041 已配發卻被 pm_46 重複提案,BR-042 則同時被 pm_46pm_52 當成新規則。此三個號碼非本 PRD 提案,僅為說明本 PRD 為何改用 BR-API-CLI 系列而引述本 PRD 一律用 BR-API-CLI 系列規避,不碰主序列;BR-039/041/042 的重編屬既有跨 PRD 問題,由 PM 統一裁決(見 sync-fix #10)。在裁決前,本列即為這三個號碼的待辦掛點流程PM
6§13.2mock-only 模組的判定依據是「AMS 無對應 controller」,未逐一確認是否有其他後端服務承接RD 覆核;若有其他服務承接,回寫本表並重評 v1 範圍事實RD
7§8.4、§17 #8既有節流 DEFAULT 5 次/60 秒對 S-3 的批次讀取偏嚴,但實際會不會撞到取決於端點各自套用哪組常數,本 PRD 未逐端點確認實作前逐端點盤點 @Throttle 標註效能RD
8§17 #15prd-lint 目前不在 main,僅存於 origin/ericlu-sys/prd-refine(commit 33e887e);本 PRD 的 lint 以該分支取出的版本執行併入 main 後所有 PRD 統一基準流程PM
9§15、§10.3role 快取於 token,降權不即時生效jwt.strategy.ts:37);且 DB 功能權限查詢在現行端點上從未執行(roles-permissions.guard.ts:39,80 端點無一宣告 permissions)。本 PRD 以 1 小時 TTL 與 session 撤銷(≤30s)兜住 CLI 通道,但根因屬授權模型,非本 PRD 可解併入問題 1 一併由後端設計;本 PRD 不預設修法資安後端設計
10§1.1v1.0.0 誤用 pm_53——該號已於 2026/08/20 配發給「職缺生命週期與 SEO 索引健康度」PRD。根因:撞號檢查只掃 refs/remotes/origin,而該 PRD 所在分支當時未推送配號前掃全 ref(refs/headsrefs/remotes)+ 所有 worktree 工作區;已於 v1.0.1 改號為 pm_54流程PM
11§9、§14v1 僅交付 W-1;W-2/W-3 延後,且 v1 的安裝憑證為人工交付、無自助管道已確認兩條替代路徑成立:①「我的裝置」由 CLI sessions list/revoke 涵蓋 ②撤銷他人以軟刪除管理員達成(admin-user.service.ts:141admin.repository.ts:193-199jwt.strategy.ts:26 每請求檢查,下一請求即 401)。殘留限制:刪帳號 all-or-nothing,無法只收 CLI 保留 GUI,「筆電遺失但人在職」須刪帳號再重建。使用者規模成長或出現該情境時即應排入 W-2/W-3範圍PRD

20. 下一步(Next Steps)

  1. 本 PRD 交 gen-prd-checker 做對抗性審查(Backend RD/Security/SRE 三 persona)——admin 線是最高權限通道,這一關不可略過
  2. RD 依 §8 產出 AMS 的 OpenAPI Spec(device flow + session API + 既有端點的 channel/冪等擴充)
  3. 前端依 §9 產出 W-1/W-2;可先用 gen-storybook-mockup-gen 出 mockup
  4. gen-trello-project-setup 建卡(1 goal + FE/BE 兩張 delivery,另需第三張給 CLI repo)
  5. §4.2 的 BR 提案送審後寫入 business-rules.md
  6. §19 問題 1(GUI 通道的 7 天 JWT)獨立立案——本 PRD 只負責不讓新通道重蹈覆轍,不負責修舊的

附錄 A:三條 CLI 產品線對照

面向pm_41 企業線pm_47 個人線pm_54 後台線
使用者企業 HR求職者內部 admin
套件@wport/cli(公開)@wport/cli(同套件)@wport/admin-cli私有
認證API key wpk_live_OAuth device flowOAuth device flow(MFA 在瀏覽器
長期憑證有(可輪替)(核彈 N-4)
權限模型key 綁公司token 綁 user繼承既有 admin role,不擴權
批次寫入有(jobs batch)有(apply ≤20)(ADR-008)
理由必填(ADR-007)
GUI parity追求追求明確放棄(ADR-002)
exit code0/2/3/4/50/2/3/4/50/2/3/4/5/6

關鍵差異的理由:前兩條線的使用者是客戶,設計目標是能力最大化(deny-list 制、限量取代禁令)。後台線的使用者是我們自己,設計目標是可治理——同樣的 deny-list 哲學,但判準從「會不會擋到使用者」換成「出事時查不查得出來、關不關得掉」。