◆ wport | pm_47

wport CLI v3(Candidate / Personal 線・OAuth)PRD

wport CLI v3(求職者線・OAuth)

來源 doc/feature/pm_47/candidate-cli-oauth-prd.md

wport CLI v3(Candidate / Personal 線・OAuth)PRD

PRD 代號pm_47
延續pm_41(wport CLI v2 企業線,已交付至 @wport/cli 0.5.0)、pm_37(Enterprise REST)、pm_42(應徵紀錄)、pm_43(收藏與追蹤中心)、pm_44(封鎖公司)
CLI 套件@wport/cli(同一套件擴充,不另發新套件)
產品原則(Eric,沿 pm_41)GUI 能做什麼,CLI 就做什麼;僅法遵級、核彈行為永久排除。
認證裁決(Eric 2026/06/24,pm_41 DR-5):個人線不使用 API 金鑰wpk_personal_ 廢止);一律 OAuth 2.0 Device Authorization Grant(RFC 8628)——wport login 開瀏覽器授權,token 可隨時於帳號設定撤銷。
本 PRD 無 mockup:CLI 產品無 UI mockup 需求;OAuth 需要的三個極簡 web 觸點(裝置驗證頁 / 授權同意頁 / 已授權裝置清單)以文字規格交付(§9),是否補 mockup 由 FE 排程時再議。


1. 文件資訊

  • 文件類型:CLI + OAuth + REST API 擴充需求規格書
  • 適用對象:CLI 工程師、後端、前端(web 觸點)、產品、QA、AI Agent 整合(Cursor / Claude)
  • 最後更新:2026/07/14
  • 版本:1.2.0(2026/07/14:①v1.1.0 Eric 裁決「限量取代禁令」——批次投遞(≤20/次 + 20/day 硬上限)、本人 resumes/applications 匯出、profile update、站內信 chats 全數改納 v1;核彈清單收斂為四項;apply 新增 --message 必填;新增 DR-9、BR-API-CLI-017。②v1.2.0 Eric 裁決 Agent-first 能力最大化(DR-10,deny-list 制)+ US-1 北極星情境「個人資料 markdown → Agent → 履歷一次做完」——新增 resumes schema/template/validate、整份聚合寫入編排(§6.4.1,後端分節 DTO 為 SoT)、resumes publish/unpublish。③v1.2.1 照片/附件裁決:photo_url/portfolio_links 皆 URL 欄位入聚合格式,v1 無 binary 上傳。④v1.2.2 通道分離:寫入日上限僅計 personal API 通道(計數器掛 namespace guard),GUI 不計入不受限;domain 規則(BR-011、訊息必填)維持通道無關;寫入記 channel 供稽核。⑤v1.2.3 web 面最小化:W-3 裝置清單 GUI 降 v2,session 治理改 CLI wport sessions list/revoke [--all-others](DR-7 改版);v1 web 觸點僅剩 W-1/W-2 兩極簡頁)
  • 建立日期:2026/07/14
  • 對應 Coda rowId:[新建列,見 §16]
  • 對應 Storybookhttps://storybook.wport.me/?path=/docs/mockups-user-clioauth--docs(W-1 裝置驗證頁 /activate + W-2 授權同意頁 wireframe,story id mockups-user-clioauth;story 檔 storybook/src/stories/mockups/user/CliOauth.stories.ts。CLI 本體仍無 mockup,此為 §9 兩個 web 觸點的畫面)
  • 代號對照pm_41 = 企業 CLI(v2,已交付);本 PRD = pm_47(求職者 CLI,v3);pm_39 = 第三方登入(裝置驗證頁沿用既有 web 登入,含 pm_39 社群登入)

2. 開發進度與設計來源

開發進度

  • CLI 現況@wport/cli 0.5.0(2026/07/07 發版)——pm_41 企業命令樹已完整覆蓋(jobs / talents / campaigns / company / usage / keys),prod 煙霧測試通過(唯一 major finding F-001 為後端 talents respond 500,屬企業線,不阻塞本 PRD)
  • CLI PR #:[TBD](apps/cli,同 repo 疊加 personal 命令樹)
  • 後端 PR #:[TBD](OAuth AS + /api/v1/personal/* namespace)
  • 前端 PR #:[TBD](§9 三個 web 觸點)

設計來源

  • pm_41 §3.2 Personal 線規格預留wport login|whoami|logout + wport personal resumes ... + wport personal apply(本 PRD 將其具體化並擴大至 GUI parity)
  • 競品對標:GitHub CLI(gh auth login)、Stripe CLI(stripe login)、Vercel CLI、Heroku CLI、npm CLI(npm login --auth-type=web)——業界對「人」的 CLI 登入已收斂到 browser-based / device-code OAuth
  • 內部 SoTpm_42(應徵紀錄 API 契約)、pm_43(收藏/Saved Filter)、pm_44(封鎖公司)、BR-010/011(履歷上限、投遞冷卻)

3. 功能摘要(Feature Summary)

3.1 功能概述

  • 功能名稱:wport CLI v3 — Candidate(Personal)線
  • 主要使用者角色
    • 求職者(工程師 / 技術背景求職者):終端機管理個人資料、履歷、投遞、應徵紀錄與 HR 對話
    • AI Agent(Claude / Cursor 等,代表求職者本人操作):以使用者已授權的 OAuth session 執行「搜尋 → 逐缺客製訊息 → 投遞 → 追蹤對話」完整求職工作流(DR-9)
  • 核心目標
    • 補齊 @wport/cli 個人線:從現況僅公開 jobs search/view 擴展至 與求職者 GUI 等價 的個人資料、履歷、投遞、應徵紀錄、站內信操作
    • 建立 wport 第一個 OAuth 2.0 授權伺服器能力(device-code flow),為未來第三方整合(MCP server、合作方 app)鋪路
    • 沿用 pm_41 的終端機契約(exit code、--output json--fields/--minimal、Idempotency),Agent 零學習成本

旗艦情境 US-1(Eric 2026/07/14,北極星):「個人資料 markdown → AI Agent → 履歷一次做完」

使用者已在個人筆記(如 Obsidian)維護結構化的個人資訊 SSOT(基本資料 / 現職 / 學歷 / 重要經歷 / 專長 / 證照…)。把這份 markdown 貼給 AI Agent,Agent 透過 CLI 一次完成:建立履歷 → 填滿所有節 → 驗證 → (可選)公開。使用者從頭到尾不開 GUI。

  • 這個情境對 CLI 的三個硬需求(皆納 v1,見 DR-10):
    1. Schema 自描述:Agent 要能問 CLI「履歷長什麼樣」——resumes schema / resumes template
    2. 整份聚合寫入:後端履歷為分節 DTO(教育/經歷/技能/證照/語言/自傳/求職條件/作品連結),CLI 收單一聚合 JSON 並代為編排多節寫入,逐節回報成敗
    3. 可迭代的驗證resumes validate --file 本地先驗 + server 422 帶欄位級錯誤,Agent 能自我修正
  • 責任邊界:筆記中的敏感區塊(如 🔒 付款/電匯資料)與履歷欄位無對映,schema 之外的資料 CLI 一律拒收——Agent 貼錯也不會流出

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

✅ 包含範圍(pm_47 v1 交付)——逐條命令清單

A. OAuth 認證(頂層命令,不掛在 personal 下)

#命令說明
V1-1wport login(含 --no-browser--forceRFC 8628 device flow:CLI 顯示 user code + 驗證 URL(能開瀏覽器就自動開),使用者於 web 登入(含 pm_39 社群登入)並同意授權;CLI 輪詢取得 token
V1-2wport whoami顯示登入身分(display name + email)與 session 資訊
V1-3wport logoutserver-side revoke refresh token + 清除本機憑證
V1-3awport sessions list / revoke <id> [--all-others]session 治理走 CLI(DR-7,取代 GUI 裝置清單);失竊自救:任一新機 login 後 revoke --all-others

B. 個人線命令(wport personal,需 OAuth session)

#命令說明 / 規則
V1-4personal profile view個人資料摘要
V1-5personal profile update --fileEric 2026/07/14 裁決納入 v1;欄位語義 = GUI 會員中心編輯(含 pm_27 國籍/身分種類,[RD 以既有 DTO 為 SoT]);server 驗證與 GUI 同源
V1-6personal resumes list≤6 份全列(BR-010),--fields/--minimal
V1-7personal resumes view <enc_id>單筆完整履歷(聚合所有節)
V1-8personal resumes create --file <full.json>整份聚合格式:CLI 代為編排「建履歷殼 + 各節寫入」(後端為分節 DTO,見 §6.4.1);逐節回報成敗;BR-002 → 422;BR-010 滿 6 份 → 422
V1-9personal resumes update <enc_id> --file [--section <name>]整份或指定節更新;partial success 逐節回報
V1-10personal resumes schema [--section <name>] / resumes templateAgent 自描述配套(US-1):印出聚合格式 JSON Schema(欄位說明 + 範例)/ 骨架檔
V1-11personal resumes validate --file本地依 bundled schema 結構先驗(免打 API);[RD 議定是否加 server dry-run]
V1-12personal resumes copy <enc_id> [--name]複製計入 6 份上限
V1-13personal resumes publish|unpublish <enc_id>履歷公開/取消公開(企業可否搜到;對應既有 update-published-status);可逆故無需 --confirm
V1-14personal resumes delete <enc_id> --confirm--confirm(BR-API-CLI-015)
V1-15personal resumes export [--out <dir>]匯出本人全部履歷完整 JSON(≤6 份);屬「自己資料的讀取便利」,非 BR-020 法遵匯出(見 ☢️ 表註)
V1-16personal apply <job_enc_id> --resume <enc_id> --message|--message-file --confirm單缺投遞;應徵訊息必填(GUI parity:application_message ≤2000 字);BR-011 72h 冷卻;需 --confirm(DR-5)
V1-17personal apply --file <batch.json> --confirm批次投遞(Eric 2026/07/14 裁決納入 v1,推翻原核彈判定);≤20 筆/次、每筆各自必填 message、partial success(比照企業 jobs batch);每筆計入 apply 日上限(DR-6)
V1-18personal applications list(含 --cooling應徵紀錄 + 冷卻狀態 + next_apply_at(契約對齊 pm_42)
V1-19personal applications export [--out <file>]CLI 端自動翻頁把本人應徵紀錄匯成單一 JSON;不需新 server endpoint,受 60/min rate limit 自然節流
V1-20personal chats list / view <room_id> / send <room_id> --body|--body-file站內信/HR 對話(Eric 2026/07/14 裁決納入 v1);對應既有 chat 模組(web 已有);send 受日上限(§8.3);企業端對口 = pm_41 talents respond

C. 後端 / Web 交付(v1 必要配套)

#項目說明
V1-21OAuth 2.0 AS 三端點device authorization + token + revocation(RFC 8628 / 7009),refresh rotation + reuse detection
V1-22/api/v1/personal/* namespaceuser-scoped Bearer;支撐 V1-4~20(profile / resumes 各節 / apply / applications / chats)
V1-23Web 觸點 W-1/W-2(僅兩頁)裝置驗證頁 /activate + 授權同意頁(device flow 本質成本,極簡、沿既有登入殼);W-3 裝置清單 GUI 降 v2(DR-7)

v1 邊界原則(DR-4):只包 GUI 後端已存在 的能力(profile / resumes / apply / applications / chats 的 domain service 均為既有,僅需開 personal REST 面)。上表之外的任何個人線命令都不在 v1。
防濫用原則(Eric 2026/07/14):與其禁功能,不如設上限——所有寫入面均有 server 端硬性 quota(§8.3),CLI 不因來源放寬任何 GUI 規則。

🕐 排除範圍(v1 不做;標注去向與閘門)

項目去向閘門 / 理由
personal saved(收藏職缺/公司)v2閘門 = pm_43 後端落地;CLI 為 thin wrapper,不先於 GUI 後端
personal filters(Saved Filter 列表/套用)v2同上(pm_43)
personal blocks(封鎖/解除封鎖公司,BR-028 上限 50)v2閘門 = pm_44 後端落地
granular OAuth scope 勾選v2v1 固定 scope bundle(DR-3);待第三方 client 需求出現再拆
投遞附加問卷(apply questionnaire)另案對齊 pm_42 排除範圍;問卷職缺 CLI 投遞回 422 引導走 GUI(§10.4)
應徵狀態 badge(審閱中/面試邀約/婉拒)隨 pm_42 v2HR 端流程未落地前,applications list 不出現該欄位
CLI 自身 i18n、shell completion、--watchCLI roadmap 既有項非 pm_47 範圍,見 apps/cli ROADMAP

☢️ 絕對不做(核彈 / 永久禁止,CLI + Personal API 皆不得實作)

行為相關規則說明
刪除個人帳號BR-012不可逆、GDPR;僅 GUI 多步驟流程(pm_24)
GDPR 級帳號個資打包(含系統紀錄、登入軌跡等超出使用者 GUI 可見範圍的法遵資料包)BR-020法遵流程專屬,不經 CLI/API。注意:V1-12/V1-16 的「本人履歷/應徵紀錄匯出」只是自己 GUI 可見資料的讀取便利,不屬此項
個人 API 金鑰wpk_personal_ 任何形式復活)pm_41 DR-5Eric 2026/06/24 裁決:個人線僅 OAuth
以個人 token 存取他人資料BR-017personal namespace 一律 user-scoped(BR-API-CLI-016)

裁決紀錄(Eric 2026/07/14):原 v1.0.0 將「批次投遞」「applications/resumes 全量匯出」列為核彈、「profile update」「站內信」列 v2/另案——均已推翻,改納 v1,以 server 端硬性上限(apply 20/day、chat send 日上限、60/min rate limit)取代功能禁令。核彈清單收斂為上表四項「上限也救不了」的行為(不可逆 / 法遵 / 認證原則 / 資料邊界)。

3.3 成功條件(KPI,個人線上線後 3 個月)

KPI目標備註
完成 OAuth 授權的 unique users≥ 50求職者 CLI 屬技術族群 niche,對標 pm_41 企業線 30 的同量級
經 CLI 完成的投遞≥ 100 筆 / 月且 CLI 投遞的 72h 冷卻違規嘗試 100% 被 server 擋下
經 CLI 的履歷建立/更新≥ 150 次 / 月
聚合 create 首次寫入全節成功≥ 70%US-1 健康度:衡量 schema/validate 自描述品質;低於此值代表 schema 說明不足,Agent 得靠 422 試錯
device flow 授權完成率≥ 80%發出 user code → 成功取 token;低於此值代表 web 觸點流程有摩擦
CLI 個人線 support ticket< 10 件 / 月
token 外洩 / 未授權存取事件0

3.4 設計決策依據(Design Rationale)

DR-1:Device Authorization Grant(RFC 8628)為唯一授權流

  • 採用wport login 走 device flow:POST /oauth/device/codeuser_code + verification_uri → 使用者瀏覽器登入並同意 → CLI 以 device_code 輪詢 POST /oauth/token(interval 5s,slow_down 時退避)
  • 捨棄之替代
    • Authorization Code + PKCE + localhost redirect(Google Cloud CLI 流派):體驗稍順,但 SSH / headless / container 場景會斷;device flow 天然支援「在 A 機器授權 B 機器」
    • 密碼直輸 CLI(legacy Heroku 流派):業界已淘汰——CLI 碰密碼即擴大 phishing 面,且與 pm_39 社群登入(Apple 等無密碼帳號)不相容
  • SaaS benchmarkgh auth login 預設 device flow;stripe loginvercel login 均為 browser-based 授權
  • wport 情境加分:裝置驗證頁沿用既有 web 登入,pm_39 社群登入使用者(無站內密碼)也能無縫授權 CLI

DR-2:Token 模型 — 短效 access + 輪替 refresh

  • 採用
    • Access token:user-scoped Bearer,TTL 1 小時;打 /api/v1/personal/*
    • Refresh token:TTL 30 天 sliding(每次 refresh 展期)+ 90 天 absolute 上限rotation 必做——每次 refresh 換發新 refresh token,舊者立即失效;偵測重用(reuse detection)即撤銷整個 token family 並要求重新 login
    • CLI 在 access token 過期時自動 refresh,使用者無感;refresh 失敗才提示重新 login
  • 理由:對比企業線 wpk_live_ 強制 30/60/90 天到期(BR-API-CLI-008),個人線的等價風控就是短效 access + 可撤銷 refresh——外洩的 access token 1 小時自然失效,refresh token 被偷用會觸發 reuse detection 全撤
  • SaaS benchmark:OAuth 2.0 Security BCP(RFC 9700)明定 public client 的 refresh token 必須 rotation 或 sender-constrained;Auth0 / Okta 預設 rotation + reuse detection
  • Token 格式:access token 建議前綴 wpt_(wport token),與 wpk_live_ 在 log / 支援場景可肉眼區分;[RD 決定 JWT vs opaque,建議 opaque + Redis 查驗,與企業 key 30s 快取機制(spec D6)共用基礎]

DR-3:v1 固定 scope bundle,同意頁全揭露

  • 採用:first-party CLI client 申請固定 scope 集:profile:read resumes applications:apply applications:read;授權同意頁逐條列出能力(「讀取你的個人資料」「管理你的履歷」「代你投遞職缺」「查看你的應徵紀錄」),使用者只有同意/拒絕,不可部分勾選
  • 理由:勾選式 granular scope 是「第三方 client 不可信」的防線;first-party CLI 拆 scope 只增加同意頁摩擦與 support 成本。同意頁全揭露維持知情權
  • SaaS benchmarkgh auth login 對自家 client 用固定 scope 集(可 --scopes 加購);Stripe CLI 直接授權完整帳號
  • v2 預留:token payload 內保留 scope 欄位,未來第三方 client 接入時同一 AS 直接支援勾選

DR-4:GUI parity(求職者版),核彈除外;v1 邊界 = 已上線的 GUI 後端

  • 採用:pm_47 v1 只包 GUI 後端已存在 的能力(個人資料、履歷 CRUD、投遞、應徵紀錄、站內信——domain service 均為既有,僅開 personal REST 面);pm_43 / pm_44 的收藏、Saved Filter、封鎖列為 v2,其後端落地即為 v2 開工閘門
  • 理由:CLI = thin wrapper over REST(pm_41 DR-1);先做 CLI 端會製造「CLI 打 404」的體驗(0.5.0 changelog 已有此教訓——需標注「需後端已部署」)
  • resumes delete 允許--confirm):pm_41 DR-2 已裁決——履歷刪除是使用者自己的資料,非核彈

DR-5:投遞是「對外」動作,CLI 需顯式確認

  • 採用personal apply 必須 --confirm(或互動確認顯示職缺名 + 使用履歷名);無 --confirm 且非互動 → exit 2、不發請求
  • 理由:pm_41 的 --confirm 守則原是給破壞性寫入;投遞不可逆且對外可見(企業端會看到),Agent 代操時尤其需要防「自動投遞失控」。這是 Agent-safety 決策,非對標
  • 配套:server 端 BR-011 冷卻不變是最終防線;CLI 在冷卻中投遞回 409 + next_apply_at(§10.4)

DR-6:批次投遞開放,以硬性上限取代功能禁令(Eric 2026/07/14 裁決,推翻 v1.0.0 原判)

原 v1.0.0 將批次投遞列為核彈(灌檔攻擊面);Eric 裁決:「可以做,設 CLI API 上限就是了」——防濫用手段從「禁功能」改為「限量」。

  • 採用personal apply --file <batch.json> --confirm——單次 ≤ 20 筆每筆各自必填 application_message(無共用模板欄位,客製化責任落在呼叫端)、partial success 語義(比照 pm_41 企業 jobs batch:逐筆回 succeeded/failed,單筆失敗不中斷)
  • 總量防線(真正的護欄)
    • apply 日上限 20/day/user(server 端硬性,單筆與批次共用計數)——批次一次 20 筆 = 當日額度用罄。僅計 personal API 通道(CLI / 第三方 client),GUI 不計入、不受限(Eric 2026/07/14):上限治理的是 API 帶來的自動化量能增量,GUI 投遞有人速天花板 + 既有 bot 防護,設限只會是對現有使用者的倒退;計數器掛 personal namespace guard,一行不動 web 路徑
    • BR-011 72h 冷卻逐筆檢查,不因批次放寬——domain 層規則,通道無關(GUI/CLI 同受約束)
    • 每筆訊息必填 + ≤2000 字(空訊息海投在 DTO 層即不可能)——同為 domain 層,通道無關
    • 套利分析(誠實記錄):通道分離後,理論上可「CLI 20 筆 + GUI 手動再投」——但 GUI 手動投遞本身就是自然限速(逐缺點擊、填表),且 BR-011 讓同缺 72h 只能一次,套利空間對企業端的實際傷害趨近於零;若監控(channel 欄位)發現異常混打模式,再上調治理,不預先懲罰正常人
  • 取捨(誠實記錄):批次讓「模板灌檔」的操作成本更低,這是真實代價;換得的是 Agent 工作流少 N 次 process 啟動與冷卻/失敗的逐筆結構化回報。在 20/day 硬上限下,批次與迴圈的傷害上限相同,禁批次只剩儀式意義——故裁決成立
  • SaaS benchmark:上限式治理對標 GitHub API secondary rate limit(不禁自動化、限速率);LinkedIn Easy Apply 之亂的根因是「無訊息 + 無日限」,兩者本 PRD 都堵住了

DR-7:session 治理 — CLI 自管為主,GUI 延後 v2(Eric 2026/07/14 質疑後改版)

原 v1.0.0 規劃帳號設定「已授權裝置」GUI 頁(W-3)為上線必備。Eric 質疑「這代表要做 GUI?」——重新檢視後:治理入口用 CLI 自己做即可,v1 不做 W-3 GUI

  • 採用(v1):session 治理走 CLI 命令 wport sessions list / revoke <session_id> [--all-others]
    • list:本帳號全部 active CLI session(裝置名 = CLI 送 hostname、授權時間、最後使用、標示目前 session)
    • revoke:撤銷任一 session 的 refresh token family;--all-others 一鍵撤銷「除本機外全部」
    • 撤銷 → 立即失效,access token 最多殘存快取 TTL(對齊 spec D6 的 30s 級別,[RD 定值])
  • 關鍵場景自洽性驗證(為什麼 CLI 足夠):「筆電失竊」場景——受害者在任一新機器 npm i -g @wport/cli && wport login(瀏覽器授權,不依賴舊裝置)→ wport sessions revoke --all-others 完成自救。CLI 的客群定義上就是會用 CLI 的人,不存在「只能用 GUI 自救」的使用者
  • 輔助防線:reuse detection 自動全撤 + 必寄警示信(DR-2)、refresh 30d 自然到期、session 上限 ≤ 10 / user(超過汰換最久未使用)
  • GUI 版(W-3)→ v2:待帳號設定頁有其他翻新需求時順帶做;屆時讀同一 session API,零後端增量
  • SaaS benchmark 重新校準:GitHub / Google 的裝置清單長在 GUI 是因為它們本來就有成熟帳號設定頁;gh auth status + gh auth logout 證明 CLI 自管 session 是成立的模式。我們沒有現成頁面,為此新開 GUI 工程違反本 PRD「CLI 產品無 mockup / 最小 web 面」的定位

DR-8:單一 credential store,雙線並存

  • 採用credentials.json(mode 0600、atomic write,沿 0.2.0 既有機制)同時存企業 wpk_live_enterprise 區)與個人 OAuth token(personal 區);命令 namespace 決定取用哪份憑證,互不干擾
  • 理由:同一位工程師常同時是「自家公司 HR 整合維護者」與「求職者」;兩線憑證衝突會逼使用者反覆 login
  • 禁止:token 永不 echo 全文(whoami 不顯示 token);不寫入 shell history 可觸及處;log 遮罩

DR-9:公開線 + 個人線 = 同一 CLI、可 pipeline 串接(旗艦使用場景)

  • 採用:個人線不另發套件,與公開線(jobs search/view,無需登入)同一 @wport/cli binary;命令是否需要認證由 namespace 決定(jobs 公開、personal 需 OAuth)。明確支持「搜尋 N 缺 → 逐缺研究 → 客製化應徵訊息 → 逐一投遞」的 Agent 工作流(§6.5 範例)
  • 客製化訊息:GUI 投遞本就必填 application_message(自我介紹,≤2000 字,SoT:apply-job.dto.ts);CLI parity 以 --message / --message-file(含 stdin -)承接——Agent 為每缺產生量身訊息正是 CLI 相對 GUI 的增量價值
  • 投遞總量治理(2026/07/14 更新,對齊 DR-6):迴圈逐缺與批次 --file 皆允許;--confirm 對 Agent 只是減速丘不是閘門,真正的總量防線是 apply 日上限 20/day/user(server 端硬性,單筆/批次共用計數,僅計 personal API 通道——GUI 不受限) + BR-011 冷卻(通道無關)+ 每筆訊息必填。20/day 刻意設在「認真求職者一天投得完、灌檔腳本不夠用」的量級
  • SaaS benchmarkgh 公開讀(gh repo view)與登入寫(gh pr create)同一 binary 組合進 script 是 CLI 產品的常態;job board 對標(LinkedIn Easy Apply 濫用史)反面證明「無訊息 + 無日限」會摧毀企業端信任,故訊息必填 + 日上限是對雙邊市場的保護

DR-10:Agent-first 能力最大化——deny-list 制取代 allow-list 制(Eric 2026/07/14)

  • 採用:CLI 能力邊界改為 deny-list 制求職者 GUI 有的能力,CLI 預設就要有;唯二例外是(1)☢️ 核彈四項、(2)配上 server 端硬性上限後仍不可控的行為(目前無)。「CLI 能做到越多越好」(Eric 原話)
  • 理由:CLI 的真正使用者是 AI Agent(US-1)。Agent 工作流斷在任何一個「這功能只有 GUI 有」的點上,整條自動化就退化成「做到一半叫人打開瀏覽器」。allow-list 制(逐功能審核納入)在 Agent 時代是負資產
  • US-1 的三個配套(本 DR 的直接產物)
    1. resumes schema / template:聚合格式自描述——Agent 讀 schema 就能把任意 markdown 個人資料對映成合法 payload,不需要人類先讀文件
    2. 整份聚合寫入 + CLI 編排:後端履歷為 9 類分節 DTO(education / work-experience / certificate / language / professional-skills / autobiography / job-condition / portfolio-links / 名稱與公開狀態);若照純 thin-wrapper 出 9 組子命令,Agent 要打 10+ 次 API 自行管理相依與失敗。CLI 收單一聚合 JSON、代為編排、逐節回報 succeeded/failed——thin-wrapper 原則(DR-1)管的是「不繞過 REST 驗證」,不禁止 client 端編排(0.1.2 jobs view --batch 已有先例)
    3. resumes validate --file:本地結構先驗 + server 422 欄位級錯誤透傳,Agent 可在不消耗寫入額度下迭代修正
  • SaaS benchmarkgh api 讓 agent 直達 GitHub 全 API 面;Stripe CLI fixtures 提供 schema 驅動的資料建立;Terraform 的 schema-first provider 模型證明「機器可讀 schema」是自動化生態的地基
  • 安全邊界不變:能力最大化 ≠ 防護鬆綁——OAuth user-scoped、日上限(BR-API-CLI-017)、--confirm、PII 邊界(BR-API-CLI-016)全部照舊;schema 之外欄位一律拒收(unknown-field reject),筆記裡的敏感區塊(付款資料等)結構上不可能流入

3.5 核彈 Deny List(個人線,永久禁止)

完整清單見 §3.2「☢️ 絕對不做」(單一 SoT,不在此重複):刪除個人帳號(BR-012)、GDPR 級帳號個資打包(BR-020,≠ 本人可見資料匯出)、個人 API 金鑰復活、跨 user 資料存取。原列核彈的批次投遞與本人資料匯出已由 Eric 2026/07/14 裁決改納 v1(配硬性上限)。


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

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

規則 ID規則摘要是否關鍵在本 PRD 的對應
BR-001email 驗證後才能登入裝置驗證頁走既有 web 登入,未驗證者到不了同意頁
BR-002完成個人資料才能新增履歷resumes create 未完成個資 → 422 profile_incomplete
BR-009職缺狀態機對已關閉/刪除職缺 apply → 409(終態不可投)
BR-010履歷最多 6 份resumes create / copy 達上限 → 422 resume_limit_reached
BR-01172h 投遞冷卻apply 冷卻中 → 409 + next_apply_atapplications list 顯示冷卻狀態
BR-012帳號刪除限制核彈 deny
BR-015職缺搜尋顯示條件公開線既有 jobs search 不變;apply 目標須為上架職缺
BR-017資料存取控制personal namespace 僅回本人資料
BR-019資料保留期限應徵紀錄顯示沿 pm_42
BR-020資料匯出規則核彈 deny

4.2 新規則提案(沿 pm_41 BR-API-CLI 系列續編)

  • BR-API-CLI-011(建議新增):個人線 CLI / API 認證僅限 OAuth(device-code flow);不核發任何個人 API 金鑰。個人 access token(wpt_ 前綴)user-scoped、TTL 1h;refresh token 30 天 sliding / 90 天 absolute。
  • BR-API-CLI-012(建議新增・資安):refresh token 必須 rotation(每次換發、舊者失效);偵測到已失效 refresh token 被重用時,撤銷該 token family 全部憑證並記 audit event。
  • BR-API-CLI-013(建議新增;2026/07/14 改版):使用者可檢視並撤銷任一 CLI session——v1 入口為 CLI wport sessions list/revoke(GUI 裝置清單降 v2,讀同一 session API);撤銷後該 session 下一請求 401(快取殘存 ≤ [RD 定值,建議 30s,對齊 spec D6])。每帳號 active session ≤ 10,超額汰換最久未使用。
  • BR-API-CLI-014(改寫,Eric 2026/07/14):CLI 投遞需顯式 --confirm單筆與批次(--file ≤20 筆/次)皆允許,每筆 application_message 必填(1~2000 字)。經 personal API 通道的投遞計入 20/day/user 硬性日上限僅計 API 通道,GUI 不受此限——見 BR-API-CLI-017 通道分離);server 端 BR-011 冷卻與 BR-009 職缺狀態為 domain 層檢查,不分通道、逐筆執行,不因批次或 CLI 來源放寬。
  • BR-API-CLI-017(建議新增,Eric 2026/07/14「限量取代禁令」原則;2026/07/14 補充通道分離):個人線寫入面一律配 server 端硬性日上限:apply 20/day、chat send [建議 50/day]、profile update [建議 10/day];額度不足時批次請求整批 429(附剩餘額度),不部分吞。計數僅涵蓋 /api/v1/personal/* 通道(CLI / 未來第三方 client),GUI(web session)不計入、不受限——上限治理的是自動化量能,不動既有 GUI 體驗;實作上計數器掛在 personal namespace guard,domain 層規則(BR-011 冷卻、訊息必填等)維持通道無關。每筆寫入記錄 channelpersonal_api / web)供稽核與濫用監控。上限值調整屬營運參數,不需改版 PRD。
  • BR-API-CLI-015(建議新增)resumes delete--confirm(或互動輸入履歷名確認);刪除後保留期沿 BR-019 既有規則。
  • BR-API-CLI-016(建議新增)/api/v1/personal/* 所有端點以 token 綁定之 user 為唯一資料邊界;不接受任何跨 user 查詢參數;回傳欄位不得超過該使用者於 GUI 可見範圍。

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

Rule 30:描述需追蹤的資訊與業務目的;JSON 命名由 OpenAPI 決定。

5.1 實體列表

實體用途對應模組
OAuthClient註冊的 client(v1 僅 first-party CLI 一筆)AS
DeviceAuthorizationdevice flow 進行中的授權請求wport login
OAuthSession(token family)refresh token family + 裝置資訊;帳號設定可見可撤login / logout / 裝置清單
Resume履歷 CRUD + export(既有實體)personal resumes
Application應徵紀錄(對齊 pm_42)personal apply / applications
UserProfile個人資料讀寫(含 pm_27 欄位)personal profile
ChatRoom / ChatMessage站內信(既有 chat 模組)personal chats

5.2 實體資料需求說明

DeviceAuthorization

為什麼需要:device flow 的短命中介狀態
必須追蹤device_code(高熵、hash 儲存)、user_code(8 碼、易讀字元集、格式 XXXX-XXXX)、綁定 client、到期(10 分鐘)、輪詢 interval(5s)、狀態(pending / approved / denied / expired)、核准後綁定的 user

OAuthSession(token family)

為什麼需要:session 治理與 reuse detection 的單位(DR-2 / DR-7)
必須追蹤

  • 綁定 user、client、scope bundle 版本
  • 裝置顯示名(CLI 送 hostname,帳號設定可改名)、建立時間、最後使用時間
  • 現行 refresh token(hash)+ 已輪替的前代 hash(reuse detection 用)
  • 狀態:active / revoked(含撤銷來源:user_revoke / logout / reuse_detected / expired / evicted)
  • absolute 到期(建立 + 90 天)

Application(CLI 視角,契約對齊 pm_42)

必須可回傳:公司名、職缺名、職缺狀態(含終態 boundary)、最近應徵時間、apply_count冷卻狀態與 next_apply_at(BR-011)
禁止:任何他人資料;HR 端狀態欄位(pm_42 v2 範圍)在其落地前不出現

Resume(分節結構,US-1 聚合介面)

結構事實:後端履歷為分節 DTOsrc/modules/resumes/dtos/)——education / work-experience / certificate / language / professional-skills / autobiography / job-condition / portfolio-links + 名稱與公開狀態
必須支援:list(6 份內全列 + is_published)、view 聚合全節、create/update 收聚合 JSON 由 CLI 編排分節寫入(§6.4.1)、schema/template/validate 自描述(不打 API)、copy(計入上限)、publish/unpublish、delete、export
格式紀律:聚合 schema 之外欄位一律拒收(unknown-field reject)——US-1 敏感資料邊界


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

@wport/cliexec-wport-cli skill 的 SoT;全域契約沿 pm_41 §6.2。

6.1 命令樹(pm_47 目標;enterprise / jobs / config / doctor 維持不變)

wport
├── login                    # OAuth device flow(個人)
├── whoami                   # 個人身分;未登入時提示 login
├── logout                   # server-side revoke + 清本機
├── sessions                 # session 治理(DR-7,取代 GUI 裝置清單)
│   ├── list                 # 全部 active session(裝置/時間/最後使用/本機標記)
│   └── revoke <session_id> | --all-others
└── personal                 # 需 OAuth session
    ├── profile
    │   ├── view
    │   └── update --file <json>            # Eric 2026/07/14 納入 v1
    ├── resumes
    │   ├── list
    │   ├── view <enc_resume_id>
    │   ├── schema [--section <name>]       # 聚合格式 JSON Schema(Agent 自描述,US-1)
    │   ├── template                        # 骨架 full.json(含範例值)
    │   ├── validate --file <full.json>     # 本地結構先驗,免打 API
    │   ├── create --file <full.json>       # CLI 編排多節寫入,逐節回報(§6.4.1)
    │   ├── update <enc_resume_id> --file <json> [--section <name>]
    │   ├── copy <enc_resume_id> [--name <新名稱>]
    │   ├── publish | unpublish <enc_resume_id>   # 履歷公開狀態(可逆,無需 confirm)
    │   ├── delete <enc_resume_id> --confirm
    │   └── export [--out <dir>]            # 本人 ≤6 份履歷完整 JSON
    ├── apply <job_enc_id> --resume <enc_resume_id>
    │         --message <text> | --message-file <path|->   # 應徵訊息必填(≤2000 字)
    │         --confirm
    ├── apply --file <batch.json> --confirm  # 批次 ≤20 筆/次,每筆必含 message(DR-6)
    ├── applications
    │   ├── list
    │   └── export [--out <file>]           # CLI 端自動翻頁匯出本人紀錄
    └── chats                               # 站內信 / 與 HR 對話(Eric 2026/07/14 納入 v1)
        ├── list
        ├── view <room_id>                  # 訊息分頁讀取
        └── send <room_id> --body <text> | --body-file <path|->

命名注意:頂層已有公開線 wport jobs(search/view 無需登入);投遞掛在 personal apply 而非 jobs apply,明確標示「需登入的個人動作」。

6.2 全域契約增補(相對 pm_41 §6.2)

項目規範
個人認證解析順序credential store personal 區(唯一來源;不提供 --token / env var 直帶 token——防 shell history / CI log 洩漏,[若 Agent 場景證明必要再議])
Token 自動續期access 過期 → CLI 靜默 refresh 重試一次;refresh 失敗 → exit 3 + 請執行 wport login
Exit codes沿 pm_41(0 / 2 / 3 / 4 / 5);login_required 屬 3
寫入 headersIdempotency-Key 自動產生(apply / resumes 寫入),可 --idempotency-key 覆寫
Agent 友善--output json--fields--minimal 全命令支援

6.3 認證命令

wport login

項目說明
流程user_code + verification_uri;TTY 且可開瀏覽器時自動開(帶 verification_uri_complete 預填 code);輪詢至 approved / denied / expired
--no-browser只印 URL + code,不嘗試開瀏覽器(SSH 場景)
輸出成功後顯示 display name + email;token 寫入 credential store(0600),全程不 echo token
已登入時提示現有身分,--force 才重新授權(舊 session 保留,由使用者自行於裝置清單撤銷)

wport whoami / wport logout

  • whoami:display name、email、session 建立時間;--output json 供 Agent 判斷登入態
  • logout:呼叫 revocation endpoint(RFC 7009)撤銷 refresh token family → 清本機 personal 區;server 不可達時仍清本機並警告

6.4 個人資料與履歷命令

命令Flag / 護欄說明
profile view--fields個人資料摘要
profile update--file <json>欄位語義 = GUI 會員中心編輯(含 pm_27 國籍/身分種類);server 驗證與 GUI 同源,[RD 以既有 DTO 為 SoT]
resumes list--fields / --minimal預設欄位:enc_id,name,updated_at,is_complete,is_published;≤6 份無分頁
resumes view--fields完整履歷 JSON(聚合所有節)
resumes schema--section <name>印聚合格式 JSON Schema(欄位型別 + 說明 + 範例);來源 = bundled OpenAPI codegen,離線可用
resumes template--out <file>產骨架 full.json(各節含範例值),Agent 直接照填
resumes validate--file <json>本地結構驗證(unknown-field reject、必填、長度);通過 exit 0、失敗 exit 2 + 欄位級錯誤清單
resumes create--file <full.json>聚合寫入(§6.4.1);BR-002 未完成個資 → 422;BR-010 滿 6 份 → 422
resumes update--file <json> / --section <name>整份或單節更新;partial update;[RD 決定是否支援 --if-match optimistic lock,建議對齊企業線]
resumes copy--name計入 6 份上限
resumes publish / unpublish公開/取消公開(企業人才搜尋可見性;對應既有 update-published-status);可逆,無需 confirm
resumes delete--confirm 必填--confirm → exit 2 不發請求(BR-API-CLI-015)
resumes export--out <dir>本人 ≤6 份履歷完整 JSON 存檔(每份一檔或單檔,[CLI 實作決定])

6.4.1 整份聚合格式與 CLI 編排(US-1 核心機制)

後端履歷為分節 DTO(SoT:src/modules/resumes/dtos/):education / work-experience / certificate / language / professional-skills / autobiography / job-condition / portfolio-links + 履歷名稱與公開狀態。GUI 逐節編輯;CLI 為 Agent 提供單一聚合 JSON 介面。

  • 聚合格式{"name": "...", "photo_url": "...", "educations": [...], "work_experiences": [...], "certificates": [...], "languages": [...], "professional_skills": [...], "autobiography": {...}, "job_condition": {...}, "portfolio_links": [...]}——各節 payload 語義 = 對應既有 DTO,節名與欄位由 resumes schema 自描述
  • 照片與附件 = URL 欄位(Eric 2026/07/14 裁決)photo_urlportfolio_links(作品/附件連結)皆為 URL 字串,聚合格式直接收——v1 不做 binary 上傳流程(Agent 給可公開存取的 URL 即可);[RD 確認 server 是否轉存 URL 圖片至自家 storage(防外鏈失效/竄改),建議轉存]
  • 編排語義create --file):CLI 依序執行「建履歷殼 → 各節寫入」;逐節回報 {section, ok, error?};任一節失敗不回滾已成功節(履歷殼保留為草稿),Agent 依回報修正該節後以 update --section 補寫——與企業 batch 的 partial success 哲學一致
  • 驗證迭代圈schema → Agent 產 full.jsonvalidate(本地,免額度)→ create → 若 server 422(欄位級錯誤)→ Agent 修正 → update --section
  • 敏感資料邊界:聚合格式 unknown-field reject——schema 未定義的鍵一律 exit 2 拒收;個人筆記中的付款/證件等區塊結構上不可能流入履歷

US-1 端到端示例(Obsidian markdown → 履歷完成)

wport personal resumes schema > schema.json     # 1. Agent 讀 schema
# 2. Agent 將「講師資訊.md」等個人 SSOT 對映成 full.json(付款等非履歷區塊自然無處可放)
wport personal resumes validate --file full.json  # 3. 本地先驗,欄位錯誤即改
wport personal resumes create --file full.json --output json  # 4. 聚合寫入,逐節回報
wport personal resumes publish <enc_id>          # 5.(可選)公開供企業搜尋

6.5 投遞與應徵紀錄

wport personal apply <job_enc_id>

Flag說明
--resume <enc_resume_id>必填;指定投遞履歷
--message <text> / --message-file <path|->二擇一必填(互斥,比照 talents respond 的 --body/--body-file);對應 GUI application_message(自我介紹);1~2000 字 CLI 本地先驗,超長 exit 2 不發請求;- 讀 stdin(Agent 產生客製訊息的主要路徑)
--confirm必填(非互動);互動模式顯示「以履歷《X》投遞《公司 – 職缺》?」確認
錯誤面冷卻中 → 409 + next_apply_at(exit 3);職缺終態 → 409;有附加問卷職缺 → 422 questionnaire_required 引導 GUI

典型 pipeline(公開線 + 個人線串接;Agent 求職工作流)

# 1. 公開線搜尋(無需登入),Agent 篩出合適職缺
wport jobs search -k "backend engineer" --output json --minimal > jobs.json
# 2. Agent 逐缺研究(jobs view 拿完整 JD)並產生客製化應徵訊息
wport jobs view <enc_id> --output json
# 3. 個人線逐一投遞:一缺一命令、一訊息、一 confirm
wport personal apply <enc_id> --resume <enc_resume_id> \
  --message-file msg-for-this-job.txt --confirm

邊界(DR-6/DR-9,2026/07/14 更新):迴圈逐一投遞與批次 --file 皆支持;總量由 apply 日上限 20/day(server 端硬性,單筆/批次共用計數,僅計 personal API 通道) 與 BR-011 冷卻收斂;GUI 投遞不計入此額度。

wport personal apply --file <batch.json>(批次投遞,DR-6)

項目說明
格式{"applications":[{"enc_job_id","enc_resume_id","application_message"},...]};≤ 20 筆/次(超過 CLI 本地 exit 2)
訊息每筆各自必填(1~2000 字,CLI 逐筆本地先驗);無共用模板欄位
--confirm必填;互動模式顯示筆數摘要確認
回應partial success:逐筆 {enc_job_id, ok, error?}(比照 pm_41 企業 jobs batch);冷卻中/終態職缺單筆 fail 不中斷
計數每筆計入 apply 日上限;額度不足時整批先於 server 拒絕(429 + 剩餘額度),不做部分吞

wport personal applications list / export

Flag說明
--cooling只列冷卻中的紀錄
-p / -s分頁(預設 20,max 100)
預設欄位job_enc_id,job_title,company_name,last_applied_at,apply_count,can_reapply,next_apply_at,job_status
export [--out <file>]CLI 端自動翻頁彙整本人全部紀錄為單一 JSON;不需新 server endpoint,受 60/min rate limit 自然節流

resumes export 同理:一次讀出本人 ≤6 份履歷完整 JSON 存檔。兩者皆為「自己 GUI 可見資料」的讀取便利,與 BR-020 法遵打包(含系統紀錄)無涉(§3.2 ☢️ 表註)。

6.6 站內信 / HR 對話命令(personal chats,Eric 2026/07/14 納入 v1)

對應既有 chat 模組(web 求職者端已有);企業端對口為 pm_41 talents respond(該線 F-001 500 修復為共同前置,見 §12.2)。

命令Flag / 護欄說明
chats list-p / -s--unread本人聊天室列表(對方公司/職缺、最後訊息時間、未讀數)
chats view <room_id>-p / -s訊息分頁讀取(僅本人參與的房間,非參與者 → 403/404)
chats send <room_id>--body / --body-file <path|->(互斥二擇一必填);≤2000 字 [RD 對齊 chat 既有限制]發送訊息;受 chat send 日上限(§8.3);自動附 Idempotency-Key

刻意不提供:跨房間群發、chats send --batch——訊息是一對一對話,無批次語義。


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

7.1 OAuth / Session

動作 ID動作類型需要後端涉及實體
AUTH-1device code 發起WriteDeviceAuthorization
AUTH-2web 驗證頁輸入 code + 登入 + 同意Write✅(web 觸點)DeviceAuthorization, OAuthSession
AUTH-3CLI 輪詢換 tokenWriteOAuthSession
AUTH-4refresh(rotation)WriteOAuthSession
AUTH-5logout / revokeWriteOAuthSession
AUTH-6sessions list / revoke(CLI;GUI 版 v2)Read/Write✅(session 管理 API)OAuthSession

7.2 Personal CLI

動作 ID動作類型需要後端備註
PER-1profile viewRead
PER-2profile updateWriteEric 2026/07/14;驗證與 GUI 同源
PER-3resumes list/viewRead聚合讀取
PER-3aresumes schema/template/validateLocal❌(bundled schema)US-1 自描述;不打 API
PER-4resumes create/update(聚合編排多節)Write✅(各節既有 DTO 端點)BR-002 / BR-010;逐節 partial success
PER-5resumes copyWriteBR-010
PER-5aresumes publish/unpublishWrite✅(既有 update-published-status)可逆
PER-6resumes deleteWrite--confirm(BR-API-CLI-015)
PER-7resumes exportRead✅(既有讀取端點即可)本人資料讀取便利
PER-8apply(單筆)WriteBR-011 / BR-009 / --confirm(BR-API-CLI-014)
PER-9apply batch(≤20 筆)WriteDR-6;partial success;共用日上限
PER-10applications list / exportRead契約對齊 pm_42;export 為 CLI 端翻頁
PER-11chats list / viewRead既有 chat 模組;僅本人房間
PER-12chats sendWrite日上限(§8.3);Idempotency

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

8.1 OAuth 端點(新建 AS)

  • HINT-OAUTH-1POST /oauth/device/code{device_code, user_code, verification_uri, verification_uri_complete, expires_in: 600, interval: 5}
  • HINT-OAUTH-2POST /oauth/tokengrant_type=urn:ietf:params:oauth:grant-type:device_code)→ 標準錯誤碼 authorization_pending / slow_down / expired_token / access_denied
  • HINT-OAUTH-3POST /oauth/tokengrant_type=refresh_token)→ rotation:回新 access + 新 refresh;舊 refresh 重用 → 400 invalid_grant + family 全撤(BR-API-CLI-012)
  • HINT-OAUTH-4POST /oauth/revoke(RFC 7009)→ logout 用
  • HINT-OAUTH-5(web):GET /activate(裝置驗證頁,可帶 ?user_code=);登入態沿既有 web session(含 pm_39)
  • HINT-OAUTH-6:session 管理 API——list / revoke(含 revoke-all-others);v1 供 CLI sessions 命令使用,v2 GUI 裝置清單讀同一組端點

8.2 Personal namespace(/api/v1/personal/*,Bearer wpt_

  • HINT-GET-PER-1:profile 摘要(PER-1)
  • HINT-MUT-PER-0:profile update(PER-2)——復用 GUI 會員中心編輯 DTO(含 pm_27 欄位驗證)
  • HINT-GET-PER-2:resumes 列表/單筆聚合讀取(PER-3、PER-7 export 復用)——單筆需一次回傳全節(或 CLI 併行拉節組裝,[RD 決定])
  • HINT-MUT-PER-1:resumes 分節寫入(PER-4~6)——復用既有分節 DTO(education / work-experience / certificate / language / professional-skills / autobiography / job-condition / portfolio-links / rename / published-status),personal namespace 開對應端點即可,CLI 端做聚合編排;寫入強制 Idempotency-Key
  • HINT-MUT-PER-2:apply 單筆(PER-8)——payload 含 enc_job_id / enc_resume_id / application_message復用既有 ApplyJobDto,訊息必填 ≤2000 字);冷卻 409 帶 next_apply_at;問卷職缺 422 questionnaire_required
  • HINT-MUT-PER-3:apply batch(PER-9)——{applications:[ApplyJobDto,...]} ≤20;逐筆檢查 BR-011/BR-009;回 succeeded/failed 陣列;日上限額度不足 → 整批 429 + 剩餘額度
  • HINT-GET-PER-3:applications 列表(PER-10)——與 pm_42 前端共用同一契約,勿分岔;export 走同端點翻頁,無新 endpoint
  • HINT-GET-PER-4:chats 房間列表 / 訊息分頁(PER-11)——復用既有 chat 模組讀取面,participant guard
  • HINT-MUT-PER-4:chats send(PER-12)——復用 handleSendMessage 求職者側路徑;錯誤須丟帶 i18n path 的 HttpException(F-001 教訓,勿拋純 Error 變 500)

8.3 Quota / Rate Limit(建議值)

維度預設值備註
個人 rate limit60 calls/min/user對齊企業線每 key 60/min;export 翻頁受此自然節流
個人月 quota不設個人讀自己的資料,無爬蟲面;寫入面各有日上限
apply 日上限20/day/user(硬性,server 端)Eric 2026/07/14:以上限取代功能禁令的核心防線;單筆/批次共用計數;僅計 personal API 通道,GUI 不受限(BR-API-CLI-017 通道分離)
apply batch 單次上限20 筆= 日上限;一批用罄額度(DR-6)
chat send 日上限[PM/RD 議定,建議 50/day/user]對照企業端 respond 100/company/day(BR-API-CLI-003);個人為單人,取半
profile update 頻率[RD 議定,建議 10/day/user]低頻操作,防腳本抖動;不影響正常使用
device code TTL10 minRFC 8628 慣例
輪詢 interval5s(slow_down 時 +5s)
Idempotency TTL24h沿 pm_37

9. Web 觸點(無 mockup;文字規格)

CLI 產品無 mockup。v1 僅兩頁(W-1/W-2)——它們是 device flow 的本質成本(沒有瀏覽器頁就無法授權),但均為極簡頁、沿既有 user app 樣式與登入殼。W-3 已降 v2(DR-7:session 治理由 CLI sessions 命令承接)。

#頁面版本需求
W-1裝置驗證頁 /activatev1輸入 8 碼 user code(verification_uri_complete 可預填);未登入先導既有登入(含 pm_39 社群登入);code 錯誤/過期給明確提示
W-2授權同意頁v1顯示 client 名(wport CLI)、裝置 hostname、scope bundle 逐條能力清單(DR-3);同意 / 拒絕兩鍵;同意後顯示「回到終端機」完成頁
W-3帳號設定 → 已授權裝置v2(Eric 2026/07/14 降級)v1 由 wport sessions list/revoke 承接(DR-7);GUI 版待帳號設定頁翻新時順帶做,讀同一 session API,零後端增量

10. Flowcharts

10.1 Device Flow(登入)

sequenceDiagram
    participant U as 使用者
    participant CLI as @wport/cli
    participant AS as wport OAuth AS
    participant W as Web(/activate)

    U->>CLI: wport login
    CLI->>AS: POST /oauth/device/code
    AS-->>CLI: user_code + verification_uri (10min)
    CLI->>U: 顯示 code(可自動開瀏覽器)
    CLI->>AS: POST /oauth/token(每 5s 輪詢)
    AS-->>CLI: authorization_pending
    U->>W: 開 /activate,登入(含 pm_39)+ 輸入 code
    W->>U: 同意頁(scope 逐條)
    U->>W: 同意
    CLI->>AS: POST /oauth/token
    AS-->>CLI: access(1h) + refresh(rotation)
    CLI->>CLI: 寫入 credentials.json (0600)
    CLI-->>U: 已登入:<name> <email>

10.2 投遞流程(含護欄)

flowchart TD
    A[wport personal apply job --resume r] --> C{--confirm?}
    C -- 否/非互動 --> X2[exit 2 不發請求]
    C -- 是 --> T{token 有效?}
    T -- access 過期 --> R[靜默 refresh] --> T
    T -- refresh 失效 --> X3[exit 3 請重新 login]
    T -- OK --> API[POST personal apply + Idempotency-Key]
    API --> S{server 檢查}
    S -- BR-011 冷卻中 --> E409[409 + next_apply_at → exit 3]
    S -- 職缺終態 BR-009 --> E409b[409 → exit 3]
    S -- 問卷職缺 --> E422[422 引導 GUI → exit 3]
    S -- OK --> OK[200 投遞成功 exit 0]

10.3 Edge Case Coverage

類別情境系統行為CLI 回饋可重試備註
Authaccess token 過期CLI 自動 refresh 重試一次無感DR-2
Authrefresh token 過期/被撤銷401exit 3 + 引導 wport login重新授權
Authrefresh token 重用(外洩)family 全撤 + auditexit 3重新授權BR-API-CLI-012
Authdevice code 逾時(>10min 未授權)AS 回 expired_tokenexit 3 + 引導重跑 loginYes
Auth使用者於同意頁拒絕AS 回 access_deniedexit 3Yes
Auth他機 sessions revoke 後本機續用401(快取殘存 ≤30s 級)exit 3重新授權BR-API-CLI-013;失竊自救場景
Dataresumes create 第 7 份422 resume_limit_reachedexit 3刪除後BR-010
Data聚合 JSON 含 schema 外欄位CLI 本地拒絕(unknown-field reject)exit 2 + 指出欄位修正後US-1 敏感資料邊界
Logical聚合 create 單節 422該節 fail、其餘續寫;殼保留草稿JSON 逐節 succeeded/failedupdate --section 補寫§6.4.1,不回滾
Dataresumes create 個資未完成422 profile_incompleteexit 3 + 引導 GUI完成後BR-002
Datadelete / apply 缺 --confirmCLI 本地拒絕exit 2不發請求
Dataapply 訊息缺漏或 >2000 字CLI 本地拒絕exit 2不發請求;--message/--message-file 互斥二擇一
Logicalapply 冷卻中409 + next_apply_atexit 3冷卻後BR-011
Logicalapply 已關閉職缺409exit 3NoBR-009
Logicalapply 問卷職缺422exit 3 + 引導 GUIGUI另案
Logicalapply 日上限用罄429 + 剩餘額度/重置時間exit 3次日BR-API-CLI-017
Logicalbatch 額度不足(剩 5 送 10)整批 429,不部分吞exit 3 + 顯示剩餘額度減量重送DR-6
Logicalbatch 內單筆冷卻/終態該筆 fail、其餘續處理JSON succeeded/failed失敗項冷卻後partial success
Logicalchats send 非本人房間403/404(i18n HttpException)exit 3NoF-001 教訓
Logicalchat send 日上限用罄429exit 3次日BR-API-CLI-017
Network逾時 / 重複執行寫入Idempotency 去重exit 4 → 重送同 keyYes沿 pm_37
Interruptionlogin 輪詢中 Ctrl+C放棄授權;device code 自然過期Yes不留殘留 session

11. Mock Data Schema

  • CLI 對接真實 API;本地僅 credential store(enterprise + personal 雙區,0600)與 config
  • 開發期建議:AS 三端點先行 + /api/v1/personal/* 可用 stub,CLI 端以 contract test 對 OpenAPI 驗證(沿 gen:openapi codegen 流程)

12. 實作備註(Implementation Notes)

12.1 CLI 必做

  • login 全程不 echo token;credentials.json 寫入沿既有 atomic write + 0600
  • access 過期自動 refresh(單次重試,防迴圈);refresh 失敗訊息必須含 wport login 指令字樣(0.4.0 的 401 提示分流經驗)
  • personal 寫入命令自動附 Idempotency-Key
  • apply / resumes delete--confirm 一律本地 exit 2
  • 更新 exec-wport-cli SKILL:新增 personal 命令樹 + 「login 需人工完成瀏覽器授權」註記(Agent 不可代按同意頁
  • doctor 增列個人線登入態與 AS 連通性探測

12.2 後端必做

  • OAuth AS:RFC 8628 + RFC 7009 + refresh rotation / reuse detection;user_code 高熵短碼、限流防暴力猜測(每 IP 嘗試次數限制)
  • device_code / refresh token 一律 hash 儲存;audit event 覆蓋 issue / refresh / revoke / reuse_detected
  • /api/v1/personal/*:復用既有 web domain services(履歷、投遞、應徵紀錄),user-scoped guard(BR-API-CLI-016)
  • OpenAPI 擴充 personal namespace(CLI codegen 依賴)
  • 教訓回饋(0.5.0 F-001):domain service 拋純 Error 會變 500 generic——personal namespace 一律丟帶 i18n path 的 HttpException

12.3 禁止事項

  • ❌ 個人 API 金鑰任何形式復活
  • ❌ 繞過 apply / chat send / profile update 日上限(BR-API-CLI-017);batch 額度不足不得部分吞
  • ❌ 跨 user 資料存取;chats 非參與者房間
  • ❌ token 明文入 log / email / URL query
  • ❌ CLI 繞過 BR-002/010/011 任何 server 檢查

12.4 相依性

相依說明Blocking
OAuth AS 落地login 的前提
/api/v1/personal/*全部 personal 命令
Web 觸點 W-1/W-2授權流程必要
session 管理 API(HINT-OAUTH-6)wport sessions 治理入口✅(W-3 GUI 已降 v2,DR-7)
pm_42 applications APIapplications list/export 契約共用✅(僅該命令)
既有 chat 模組 personal REST 面chats list/view/send✅(僅 chats);F-001(respond 500)修復為企業⇄求職者對話健康的共同前置
GUI 會員中心編輯 DTOprofile update 欄位 SoT✅(僅該命令)
pm_43 / pm_44 後端saved / blocks 命令v2 閘門

13. 四大一致性表格

13.1 欄位對照表

欄位語義用途SoT版本
wpt_ access token個人認證pm_47v1
user_code(XXXX-XXXX)device flowpm_47 / RFC 8628v1
next_apply_at / can_reapply冷卻呈現pm_42v1
application_message(≤2000 字,必填)apply 應徵訊息既有 ApplyJobDto(apply-job.dto.ts)v1
履歷聚合格式(9 節)resumes create/update/schema既有分節 DTO(src/modules/resumes/dtos/v1
photo_url / portfolio_links(URL 字串)履歷照片/附件匯入既有 aggregate VM / portfolio-links DTOv1
is_published履歷公開狀態既有 update-published-status DTOv1
resume payloadcreate/update既有履歷 DTO [RD 確認]v1
session 裝置名/最後使用裝置清單pm_47 DR-7v1

13.2 常數表

常數來源版本
access token TTL1hDR-2v1
refresh sliding / absolute30d / 90dDR-2v1
device code TTL10 minRFC 8628 慣例v1
輪詢 interval5sRFC 8628v1
active session 上限10 / user(汰舊)DR-7v1
履歷上限6BR-010既有
投遞冷卻72hBR-011既有
apply 日上限20/day/user(硬性)Eric 2026/07/14 / BR-API-CLI-017v1
apply batch 單次上限20 筆DR-6v1
chat send 日上限[建議 50/day/user]BR-API-CLI-017v1
profile update 日上限[建議 10/day/user]BR-API-CLI-017v1
應徵/聊天訊息長度≤2000 字ApplyJobDto / chat 既有既有
page_size max100pm_41 全域既有

13.3 事件字典

事件名觸發訂閱者
personal_oauth_session_created同意頁核准Audit;選配安全提醒信(不含 token,對齊 BR-API-CLI-007 精神)
personal_oauth_session_revokedlogout / sessions revoke / 汰換Audit
personal_oauth_reuse_detectedrefresh 重用Audit + 安全提醒信(建議必寄
personal_apply_submittedCLI 投遞成功(單筆或批次逐筆)既有投遞事件鏈(與 GUI 同源)
personal_chat_message_sentchats send 成功既有 chat 通知鏈(對口 pm_41 talent_respond_sent
personal_write_quota_exceeded任一日上限 429Audit(濫用監控訊號,BR-API-CLI-017)

13.4 術語字典

定義
Personal CLIwport login/whoami/logout + wport personal 命令樹
Device flowRFC 8628:CLI 顯示 code、瀏覽器授權、CLI 輪詢取 token
Token family一次授權產生的 refresh 世代鏈;reuse detection 撤銷單位
Web 觸點v1 僅 W-1 驗證頁 / W-2 同意頁;W-3 裝置清單 GUI = v2(§9)

14. 版本凍結表

項目版本Blocking?決策者日期
個人線認證 = OAuth device flow,無個人金鑰pm_41 v1.2(裁決)/ pm_47 v1(實作)Eric2026/06/24
access 1h + refresh rotation 30/90dpm_47 v1Eric [待確認建議值]2026/07/14
v1 範圍 = profile(view+update) + resumes(全套+export) + apply(單筆+批次) + applications(list+export) + chatspm_47 v1.1Eric2026/07/14
批次投遞開放(≤20/次,配 20/day 硬上限)——推翻 v1.0.0 核彈判定pm_47 v1.1Eric2026/07/14
「限量取代禁令」防濫用原則(BR-API-CLI-017)pm_47 v1.1Eric2026/07/14
Agent-first 能力最大化(deny-list 制)+ US-1 北極星情境(schema/template/validate、聚合寫入編排、publish/unpublish)pm_47 v1.2Eric2026/07/14
履歷照片/附件 = URL 欄位(photo_url/portfolio_links)入聚合格式;v1 無 binary 上傳pm_47 v1.2.1Eric2026/07/14
寫入日上限僅計 personal API 通道,GUI 不計入不受限(通道分離)pm_47 v1.2.2Eric2026/07/14
session 治理走 CLI sessions list/revoke;W-3 裝置清單 GUI 降 v2pm_47 v1.2.3Eric2026/07/14
apply--confirm + 訊息必填pm_47 v1Eric [待確認]2026/07/14
saved / filters / blockspm_47 v2❌ 閘門 = pm_43/44 後端
granular scope 勾選v2(第三方 client 時)
帳號刪除 / GDPR 帳號打包 / 個人金鑰復活 / 跨 user 存取❌ 永不Eric2026/07/14 收斂

15. Token / API 契約完整性

項目規範
個人認證OAuth only;wpt_ access(1h)+ rotating refresh(30d sliding / 90d absolute)
Rotation / reuse每次 refresh 換發;重用 → family 全撤 + audit + 提醒信(BR-API-CLI-012)
撤銷logout(RFC 7009)與 web 裝置清單雙入口;撤銷後 401(快取殘存 ≤30s 級)
Session 上限10 active / user,汰舊
資料邊界personal namespace 一律 user-scoped,無跨 user 參數(BR-API-CLI-016)
Token 傳遞永不入 log / email / URL;CLI 不提供 --token 直帶
Idempotencypersonal 寫入必填;TTL 24h
寫入日上限apply 20/day(硬性,單筆/批次共計);chat send / profile update 依 BR-API-CLI-017;僅計 personal API 通道(GUI 不計),計數器掛 namespace guard;每筆寫入記 channel 供稽核;額度不足批次整批 429
即時權限重檢每請求驗 token + user 狀態(停權帳號 → 401/403)

16. Sync-Fix List

動作OwnerBlocking?
新建 Coda 列 wport CLI v3(求職者線 OAuth) → PRD=pm_47,相依性填 pm_41, pm_42, OAuth ASPM
doc/feature/README.md 索引 + 代號關聯補 pm_47PM✅(本次一併完成)
後端:OAuth AS + personal namespace OpenAPIRD
FE:W-1/W-2 web 觸點(僅兩極簡頁)FE
CLI:login/whoami/logout + personal 命令樹,發版 @wport/cli 0.6.0 [或依 semver 議定]CLI
更新 exec-wport-cli SKILL(personal 命令 + Agent 不可代按同意頁)CLI
BR-API-CLI-011~016 提案回寫 business-rulesPM上線後
pm_42 契約對齊測試(applications list 共用 DTO)RD + CLI

17. Email Template

沿 pm_41 資安鐵則:信件一律不含 token 或可還原片段

信件時機必要性
新裝置授權提醒personal_oauth_session_created選配(預設可不寄,對齊 pm_41 Eric 裁決「避免多餘信件面」)
安全警示:偵測異常 token 使用personal_oauth_reuse_detected建議必寄(外洩訊號,使用者需立即撤銷)
投遞成功通知沿既有 GUI 投遞信既有,不新增

18. PRD 問題清單

#IssueSuggested fixImpact
1access/refresh TTL 建議值(1h / 30d / 90d)待 Eric 拍板§14 版本凍結表確認低(皆為業界預設區間)
2apply 日上限是否設 已拍板(Eric 2026/07/14):20/day 硬性、單筆批次共計、僅計 personal API 通道(GUI 不受限)已結案
2bchat send(建議 50/day)與 profile update(建議 10/day)上限值PM/RD 議定;屬營運參數(BR-API-CLI-017),可上線後調
2cchat 訊息長度上限RD 對齊既有 chat 模組限制回寫 §6.6
2d履歷照片/附件上傳是否入 v1 已拍板(Eric 2026/07/14):照片與附件皆為 URL 欄位(photo_url / portfolio_links),入聚合格式;v1 不做 binary 上傳殘留子題:server 是否轉存外部 URL 圖片至自家 storage(建議轉存,防外鏈失效)→ RD
2eresumes validate 是否加 server dry-run 模式RD 評估;本地驗證先行,dry-run 可 v1.x 補
2f單筆履歷聚合讀取:server 一次回全節 vs CLI 併行拉節組裝RD 決定(HINT-GET-PER-2)
3token 格式 JWT vs opaqueRD 決定;建議 opaque + Redis(與企業 key spec D6 快取同基礎)
4resume create/update payload 欄位語義RD 以既有履歷 DTO 為 SoT 回寫 OpenAPI
5pm_42 applications API 尚未落地時的排程建議 pm_47 後端與 pm_42 後端一併排(共用 DTO 一次到位)
6W-3 裝置清單 mockup 已結案:W-3 GUI 降 v2(Eric 2026/07/14),v1 治理走 wport sessions已結案

19. 下一步(Next Steps)

  1. Eric:確認 §14 三項 [待確認](TTL 值、v1 範圍、apply —confirm)
  2. RD:OAuth AS 技術選型(自建 vs library,如 node-oidc-provider)+ personal namespace OpenAPI
  3. FE:W-1/W-2 排程(僅兩極簡頁;W-3 已降 v2)
  4. CLI:login + personal 命令樹實作(可先對 AS stub 開發)
  5. QA:以「求職者 GUI parity 矩陣」+ §10.3 edge case 表驗收
  6. pm_43 / pm_44 後端落地後啟動 pm_47 v2(saved / filters / blocks)

附錄 A:pm_41(企業線)vs pm_47(個人線)對照

維度pm_41 企業線pm_47 個人線
認證wpk_live_ API key(M2M)OAuth device flow(人)
憑證生命週期強制到期 30/60/90d + rotateaccess 1h + refresh rotation 30/90d
撤銷入口金鑰自助頁(軌道 A)+ AMS(軌道 B)logout + 帳號設定裝置清單
scope申請時勾選 4 scopev1 固定 bundle,同意頁揭露
資料邊界company-scopeduser-scoped
破壞護欄delete/close/batch 需 --confirmdelete + apply--confirm
批次jobs batch ≤100apply batch ≤20/次(= 日上限一次用罄)
quota5000/月/公司無月 quota;寫入面日上限(apply 20 / chat 50 / profile 10)
核彈 deny刪公司/成員治理/bulk export刪帳號/GDPR 帳號打包/金鑰復活/跨 user 存取

文件結束