FR-190 · 設計定案 v1.0 · 2026-11-09

Agent 主控台權限能力點化

7 個範圍拆成 26 顆能力點;三邊共用一份清單;同步機制沿用;14 張卡 7 批

本文件是 FR-190 的定案設計。討論稿 D1~D9 全部裁定(見 §3),拆卡表經 handoff/cards.py 程式驗過依賴與同批撞檔。讀者只看這一份就能開工;討論過程與被排除方案在 討論稿。

狀態:設計定案 v1.0(2026-11-09) 14 張卡 7 批;批 1=環境卡+契約卡 前作 FR-166/166.1;推翻 FR-166 一條(重啟下放離線操作者) 討論稿 discussion.md · 雛形 prototype/index.html
§1

變更紀錄

日期 版本 內容
2026-11-09 v1.0 討論稿 D1~D9 全部定案;拆卡 14 張 7 批
2026-11-09 v1.0 開卡後資安審查:26 顆模組前綴從 detection 改 agent(避開 seed 的 detection.* 萬用字元 pattern,見 §5.1);P1-2 加守衛測試。卡未派、就地改
§2

需求背景與端到端流程

Agent 主控台的權限由主系統決定:使用者點「進入主控台」時主系統逐顆查他持有的 Agent 能力點,把對應範圍寫進五分鐘票;斷線用的離線操作者名單也是每次心跳依當下能力點重算下發。這套機制正確,但只有 7 個範圍:configure 包 9 個設定頁、download-files 包 5 種下載、看與執行不分、主系統派的掃描紀錄掛在 configure。管理員做不到「可下載報告但不可下載證據」「只管 LDAP 不碰帳密」。

本案把 7 個範圔拆成 11 個主控台功能 × 檢視/設定/執行/下載=26 顆,全部進主系統能力點表,角色頁以「Agent 管理 › Agent 主控台」子頁分組逐顆給;三邊(主系統、jedi-remote-agent、evidence-agent)改讀 jedi-agent-protocol 同一份清單。

%%{init: {'theme':'base','themeVariables':{'primaryColor':'#E2F0F1','primaryTextColor':'#14201F','primaryBorderColor':'#0E7C86','secondaryColor':'#EEF2F3','secondaryTextColor':'#14201F','tertiaryColor':'#FBFCFC','tertiaryTextColor':'#14201F','lineColor':'#4A5A5C','textColor':'#14201F','mainBkg':'#E2F0F1','nodeBorder':'#0E7C86','nodeTextColor':'#14201F','edgeLabelBackground':'#FBFCFC','titleColor':'#14201F','clusterBkg':'#FBFCFC','clusterBorder':'#E4EAEB','actorBkg':'#E2F0F1','actorTextColor':'#14201F','actorBorder':'#0E7C86','actorLineColor':'#C7D1D2','signalColor':'#4A5A5C','signalTextColor':'#14201F','labelBoxBkgColor':'#EEF2F3','labelBoxBorderColor':'#C7D1D2','labelTextColor':'#14201F','loopTextColor':'#14201F','noteBkgColor':'#F6EBD5','noteTextColor':'#14201F','noteBorderColor':'#9C6B12','activationBkgColor':'#EEF2F3','activationBorderColor':'#C7D1D2','sequenceNumberColor':'#FFFFFF'}}}%%
sequenceDiagram
    participant Admin as 管理員
    participant Main as 主系統
    participant Agent as Agent
    participant User as 使用者
    Admin->>Main: 角色頁「Agent 主控台」子頁勾顆(或按「比照帶入」)
    User->>Main: 點「進入主控台」
    Main->>Main: capability_checker 逐顆查 26 顆 → 持有的寫進票 scope
    User->>Agent: 帶票登入
    Agent->>Agent: 不認得的範圍丟掉並記數;全丟才拒
    Agent-->>User: 側欄依 *.read 出現;按鈕依 update/execute/download
    Main->>Agent: 每次心跳:離線名單帶該使用者當下 24 顆內持有的
圖 1 — 權限兩條路(都已存在,本案只換顆粒)
§3

分工概述(30 秒版)

整案一句話:7 個範圍拆成 26 顆,三邊改讀同一份清單,角色頁逐顆給,同步機制不動。

階段 批 做什麼 完成怎麼判定(決策者檢查法)
P0 契約 1 環境卡(兩個 repo 改走本機路徑裝套件)+ protocol 套件寫下 26 顆清單 開 jedi_agent_protocol/console_scopes.py 看到 11 資源 × 動作的表;python -c "import jedi_remote_agent; print(jedi_remote_agent.__file__)" 印出套件 repo 路徑
P1 四邊接上 2~3 jedi-remote-agent 三張表改推導;主系統 migration+端點換顆+矩陣;Agent 驗票丟不認得+60 條守門換顆;主控台 FE 換顆+橫幅;主站角色頁文案 對照雛形第 1~3 頁:角色頁看到「Agent 管理 › Agent 主控台」子頁 26 顆;給角色只勾「檔案 · 檢視」「掃描 · 檢視」「掃描 · 下載」,用該角色進主控台:側欄只剩總覽/掃描/執行紀錄/檔案/授權,檔案頁無下載鈕,執行紀錄有下載鈕
P2 帶入鈕 4~5 主系統唯讀端點算對照 → FE 角色頁子頁標題列一顆鈕 對照雛形第 1 頁:按「比照主系統功能帶入」,有「檢測工具 · 修改」的角色自動勾上「工具設定 · 設定」,可再拔
P3 收口 6~7 兩支套件發版、兩個 repo 改回釘版、SPEC 與手冊 用釘版 wheel 起主系統 HEAD,簽票端點 200;手冊「角色權限」節列 26 顆

依賴:批 1 的環境卡是硬前置——主系統釘 jedi-remote-agent==1.17.6、Agent 釘 jedi-agent-protocol==0.5.3,不改走本機路徑,批 2 的卡 import 到的是舊發行版、測試假綠。

測試前提:26 顆新權限的守門在新版 Agent 裡,驗收用的 Agent 必須先裝到 P1-4/P1-5 的版本。

§4

決策定案表

# 決策 定案 理由 被排除
D1 顆粒 11 功能 × 檢視/設定/執行/下載,共 26 顆(表見 §5.1) 拆一半等於再做一次;四個動作是角色頁現成的詞 只拆 download 一顆
D2 舊 Agent 收到不認得的範圍 丟掉、照常登入、頂部橫幅提示;全部不認得才拒 丟掉只會少給不會多給;主系統可先升、Agent 慢慢跟 整張票拒收(現況)
D3 主系統派的掃描紀錄 歸 scan.read/scan.download,不分來源 對使用者都是掃描結果 維持掛 configure
D4 角色頁排法 角色樹多一個「Agent」大組(capabilities.module='agent' 自動成第一層分組),底下一頁「Agent 主控台」(ui_routes 一列 remote-agent-console,is_menu=false、tree_page=true,掛在 Agent 管理底下但分組看 module),26 顆標籤帶功能前綴依資源分組。Agent 管理頁本身的 4 顆仍在「檢測」組 一層分清「管清單」與「進主控台做事」;不為 11 個畫面造 11 列;模組換 agent 是資安審查結論(§5.1) 11 個子頁;平鋪;留在 detection 模組
D5 舊 7 顆退場 migration 把舊顆授權展開到新顆(含 pattern 角色),然後刪舊顆與三張關聯表的列;新顆 legacy_name 記舊名。對照見 §5.2 出貨前無客戶;留退役顆是第二套真相 保留標退役
D6 離線操作者不下放 只不下放 update.read、update.execute;重啟 ops.execute 下放(推翻 FR-166 D14「重啟不下放」) 斷線時卡住要能現場重啟;重啟可逆、換版不可逆 維持 FR-166 的線
D7 主系統端點換顆 照 §5.6 對照表 舊顆刪掉後這些端點沒人守 —
D8 票上寫法 原樣寫 26 個字串 <資源>.<動作>(能力點名去 agent. 前綴) 約 2.5 KB,登入上限 8 KB;可讀性除錯值錢 位元編碼
D9 跟主系統對應功能連動 不連動,但一次給:兩處等價的(工具帳密、金鑰換發)守同一顆;其餘靠角色範本預帶+角色頁「比照主系統功能帶入」鈕(依 protocol MIRRORS 算,一次性、可再拔) 風險面不同的事綁死會多給權限;勾兩次的痛點用鈕解 自動跟;全部手勾
§5

現況接入點盤點

元件 現況 本案動作 卡
jedi-agent-protocol 事件碼/錯誤碼/指令/設定/事件/信任錨;無權限範圍 new console_scopes.py;bump 0.6.0 P0-1
jedi-remote-agent plugin/contract.py 三張表 硬寫 11 顆+兩張對照 extend:從 protocol 推導 P1-1
簽票 RemoteAgentConsoleTicketRoute 逐顆 capability_checker 沿用,清單換 P1-1
離線名單 OfflineOperatorService._scopes_for 每次心跳重算 沿用,對照表換 P1-1
主系統 capabilities 表 套件宣告由 flatten_pkg_migrations.sh 攤成 _capabilities.sql 每次升級重跑 沿用:26 顆自動進表;展開與刪舊另寫主線 migration P1-2
主系統 ui_routes tree_page 機制(FR-132) extend:加 remote-agent-console 一列 P1-2
主系統 _ADMIN_CAPABILITIES/KEY_ADMIN_CAPABILITIES 約 40 端點守舊顆 extend:照 §5.6 換 P1-3
gen_permission_matrix.py 空殼判定 掃主系統與 jedi 套件原始碼;沒掃 Agent extend:protocol 清單內的顆視為 Agent 端有守 P1-3
Agent shared/constants.py CONSOLE_SCOPES 硬寫 7 個 extend:import protocol P1-4
Agent agent_auth.verify_console_ticket 不認得整張拒 extend:過濾+記數;全丟才拒 P1-4
Agent console/api/*.py 約 60 條 require_session 7 個範圍名 extend:一對一換;runs_routes 來源判斷收掉 P1-5
主控台 FE router.js+13 個 view 硬寫 scope extend:換顆;Shell.vue 加橫幅 P1-6
主站 permission-tree.json/remote-agent.json/ACTION_ORDER 11 顆文案、5 條 scope 文案、無 download extend P1-7
角色頁 PermissionMenuTree.vue page 列 有「只看」link 鈕 extend:子頁列多一顆「比照帶入」 P2-2
§6

詳細設計

5.1 26 顆清單(protocol console_scopes.py 的內容)

能力點全名 agent.<資源>.<動作>(模組是 agent,不是 detection);票上與 Agent 守門用 <資源>.<動作>。license_module 一律 remote-agent-manage(授權照模組鍵不變)、home_route 一律 remote-agent-console。

🔴 為什麼不放 detection 底下

出貨 seed(scripts/init/04-seed-core.sql)四個系統角色帶萬用字元:稽核主管 detection.*.*、稽核員/一般成員/唯讀 detection.*.read。26 顆若叫 detection.agent-*,新裝客戶的稽核主管升級後自動拿到全部 26 顆(含檔案下載、重啟、換版),唯讀角色拿到 11 顆檢視(含「這台全部證據不分專案」)。FR-166 的 7 顆已經踩在這個洞上,本案順手補掉:模組改 agent,沒有任何既有 pattern 命中;P1-2 加守衛測試「seed 內非 *.*.* 的 pattern 不得命中任何 agent.*」。DEV 沒有這些 pattern,runner 在 DEV 測不出來,所以守衛要掃 seed 檔本身。

資源 read(檢視) update(設定) execute(執行) download(下載) 離線可下放
files 檔案 列表、預覽、上傳者清單 — — 證據檔原檔 全部
scan 掃描 工具、目標、基準、紀錄(含派工,D3) — 執行、取消、重送結果 報告原檔、工具 log 全部
netdiag 網路除錯 目標、紀錄、預估、報告預覽 — 執行、重送 除錯報告 全部
logs 日誌 查日誌 — — 匯出 全部
tools 工具設定 看已設定(不回密碼) 改帳密、測試連線、清除 — — 全部
ldap LDAP 看設定 改來源、改本機、測試、重新信任 — — 全部
keys 金鑰 看狀態 換發 — — 全部
license 授權 看狀態 上傳、線上啟用 — — 全部
ops 維運 服務狀態、離線登入設定、儲存設定 改名稱、主系統位址、儲存、離線登入;補更新弱點庫/基準/目標 重啟 診斷包 全部(含重啟,D6)
update 更新 版本、檢查新版 — 線上換版、離線上傳換版 — 不下放
setup 連線設定 註冊狀態 註冊、探測、換主系統 — — 全部

共 26 顆(4+4+4+2+2+2+2+2+4+2+2)。OFFLINE_GRANTABLE 24 顆。

MIRRORS(D9 帶入對照,主系統能力點 → Agent 顆):detection.plugin.update → tools.update、detection.plugin.read → tools.read、detection.profile.read → scan.read、detection.profile.update → scan.execute、system.ldap-config.read → ldap.read、system.ldap-config.update → ldap.update、platform.license.read → license.read、platform.license.update → license.update、system.storage-config.read → ops.read、system.storage-config.update → ops.update、system.product-update.read → update.read、system.product-update.update → update.execute、detection.remote-agent.update → keys.update, ops.execute、detection.remote-agent.create → setup.update、grc.job-evidence.download → files.download。無對照:logs.*、netdiag.*、各顆 read 以外沒列到的。

為什麼放 protocol 不放 jedi-remote-agent:Agent 不裝 jedi-remote-agent(帶 Flask/jedi-common),只裝 protocol;三邊都 import 得到的只有這裡。

5.2 舊顆 → 新顆展開(D5,P1-2 migration)

舊顆 展開成
configure tools.*、ldap.*、keys.*、license.*、ops.read、ops.update、setup.*(14 顆)+ scan.read(D3)
browse-files files.read、logs.read
run-local-scan scan.read、scan.execute、logs.read
net-diag netdiag.read、netdiag.execute、logs.read
download-files files.download、scan.download、netdiag.download、logs.download;ops.download 只給同時持 configure 者(診斷包現況要兩顆都有)
restart ops.execute
upgrade update.read、update.execute

展開走 role_capabilities 與 role_capability_patterns 兩條路(照 FR-166 migration 第 3 段形狀)。刪舊顆前先 DELETE 三張關聯表(role_capabilities、route_capabilities、role_capability_patterns 字面 pattern)的對應列——這三張表對 capabilities 無外鍵,不清會留孤兒。Administrator 靠 *.*.* 自然持有,不另處理。出貨預帶照 §5.1「離線可下放」旁的角色欄不在本案 migration 內(角色範本在 _capability_grants.sql 模板,P1-2 一併補 IT Manager/稽核員兩列)。

5.3 Agent 守門對照(P1-5)

檔 舊 新
files.py 列表/上傳者/預覽 files files.read
files.py 下載 download-files files.download
scan_routes.py tools/targets/profiles/schema/runs GET run-local-scan scan.read
scan_routes.py uploads/runs POST/cancel run-local-scan scan.execute
runs_routes.py 清單與明細 依 (kind, source) 三種 掃描(不分來源)scan.read;除錯 netdiag.read
runs_routes.py resend 同上 掃描 scan.execute;除錯 netdiag.execute
netdiag_routes.py dashboard/targets/estimate/runs GET/report preview net-diag netdiag.read
netdiag_routes.py runs POST net-diag netdiag.execute
netdiag_routes.py report 下載 download-files netdiag.download
log_routes.py 查 四顆任一 logs.read
log_routes.py 匯出 四顆任一+download-files logs.download
tools.py GET configure tools.read
tools.py PUT/DELETE/test configure tools.update
ldap_routes.py GET configure ldap.read
ldap_routes.py PUT/DELETE/test/retrust configure(寫入另加 local_admin_only) ldap.update(local_admin_only 不動)
keys_routes.py GET configure keys.read
keys_routes.py renew configure keys.update
license_routes.py GET 任何 session 不變(任何登入者可看)
license_routes.py upload/activate configure license.update
ops_routes.py status restart 或 configure ops.read
ops_routes.py offline-login GET/storage GET configure ops.read
ops_routes.py offline-login PUT/agent-name/base-url/storage PUT・DELETE・test/vulndb・profiles・targets check configure ops.update
ops_routes.py restart restart ops.execute
ops_routes.py support-bundle configure+download-files ops.download(單顆)
update_routes.py status/check update update.read
update_routes.py request/offline update update.execute
setup_routes.py state configure setup.read
setup_routes.py probe/enroll;cloud_change_routes.py probe/apply configure setup.update
pages.py me/overview;local_auth_routes.py password 任何 session 不變

5.4 主控台 FE 對照(P1-6)

router.js:scan→scan.read;netdiag→netdiag.read;runs→['scan.read','netdiag.read'];logs→logs.read;tools→tools.read;ldap→ldap.read;files→files.read;ops→ops.read;keys→keys.read;license→null(不變);update→update.read;setup→setup.update。各 view 的操作鈕:canDownload 看各自 *.download;canConfigure 看各自 *.update;掃描/除錯「執行」鈕看 *.execute;Ops.vue 重啟鈕看 ops.execute。Shell.vue:me.dropped_scopes > 0 顯示 .hint 橫幅,文案進 agent-console.json。

5.5 主站 FE 對照(P1-7)

Agent 管理頁按鈕 v-can:「進入主控台」→26 顆任一(reuse useMenuItemsCan 的 ANY);「重啟」→ops.execute;「換發憑證」→keys.update;「補更新基準」→ops.update;執行紀錄頁下載→scan.download/netdiag.download 依列種類。

5.6 主系統端點對照(D7,P1-3)

端點 舊 新
RemoteAgentConsoleTicketRoute.post configure/browse-files/upgrade 任一 26 顆任一
RemoteAgentLocalRunReportRoute.get/ReportViewRoute.get/ToolLogRoute.get download-files scan.download、netdiag.download ANY;route 內依紀錄 kind 細判
RemoteAgentLocalRunsRoute.get/DetailRoute.get _AGENT_LOCAL_RUNS read+scan.read+netdiag.read ANY
RemoteAgentToolCredentialsRoute.post/delete configure tools.update
RemoteAgentProfilesCheckRoute.post configure ops.update
RemoteAgentRestartRoute.post restart ops.execute
AgentCredentialsRenewRoute.post remote-agent.update keys.update
_AGENT_LIST(清單可見) read+browse-files read+26 顆任一
RemoteAgentsRoute.get(下拉) 四顆含 browse-files browse-files 換 files.read

5.7 Agent 驗票(D2,P1-4)

verify_console_ticket 回 (claims, dropped: list[str]):scope 過濾成 [s for s in scope if s in CONSOLE_SCOPES],空 → 403 TICKET_SCOPE_EMPTY(新錯誤碼進 protocol error_codes);TicketScopeUnknown 類別與路徑移除。ConsoleSession.dropped_scopes: int,不落地(_PERSISTED 不含)。/api/me 多 dropped_scopes。離線名單 offline_operators.py 第 85 行的過濾改讀新清單,行為不變。舊 7 個名字一律不認(不做舊主系統相容)。

5.8 資安規範對照

  • SR-02(授權):守門只收緊不放寬——26 顆對 7 顆是細分,任一端點的新顆集合 ⊆ 舊顆持有者集合(§5.3 表逐列可驗);守衛測試 test_fr190_scope_table.py 對每顆單一 session 逐支打。
  • SR-01(身分):D2 丟掉不認得的範圔只縮不放;全丟拒絕用既有同一回應形狀。
  • 票不帶訊息、不加心跳鍵(FR-181):本案不動心跳與事件契約,protocol 只加 console_scopes 模組與一個錯誤碼。
§7

拆分表

由 handoff/cards.py 產;批=可同時派的一組,階段只是分組。python handoff/cards.py 驗依賴在更早批、同批不撞檔。

卡 階段 批 repo 做什麼 reuse/extend/new 依賴 model/effort
P0-0 P0 1 main 環境卡:主系統與 Agent 改走本機路徑裝 protocol 與 remote-agent(assume-unchanged) reuse:pyproject 既有 path 註解行 — sonnet/medium
P0-1 P0 1 jedi-packages protocol console_scopes.py:26 顆、OFFLINE_GRANTABLE、MIRRORS、scope_of/capability_of;bump 0.6.0 new:照 event_codes 零相依形狀 — opus/high
P1-1 P1 2 jedi-packages remote-agent 三張表改推導;bump 1.18.0 extend P0-1 opus/high
P1-2 P1 2 main migration:子頁、展開、刪舊顆、角色範本預帶;守衛測試 extend:照 FR-166/FR-132 migration 形狀 P0-0、P0-1 opus/high
P1-3 P1 2 main 端點守門換顆(§5.6);矩陣加 download 與 Agent 端守門;重產 capabilities.js extend P0-1 sonnet/medium
P1-4 P1 2 agent 身分層:import protocol、驗票丟不認得、me 回 dropped extend P0-0、P0-1 opus/high
P1-6 P1 2 fe 主控台 FE 換顆、橫幅、i18n extend P0-1 opus/medium
P1-5 P1 3 agent 60 條守門換顆(§5.3);全 blueprint 對表測試 extend P1-4 opus/high
P1-7 P1 3 fe 主站角色樹文案、ACTION_ORDER、離線操作者標籤、按鈕換顆、commit capabilities.js extend P1-3 sonnet/medium
P2-1 P2 4 main 唯讀端點 GET /roles/<uid>/agent-console-mirror new(一支 service+route;對照來自 protocol) P1-2、P1-3 sonnet/medium
P2-2 P2 5 fe 角色頁子頁列「比照帶入」鈕 extend:照「只看」鈕形狀 P2-1、P1-7 sonnet/medium
P3-1 P3 6 jedi-packages 發版 protocol 0.6.0、remote-agent 1.18.0 reuse 發版流程 批 2~5 全部 sonnet/medium
P3-2 P3 7 main 改回釘版、取消 assume-unchanged、釘版 wheel 起 HEAD 實打 reuse ENV.md 反向 P3-1 sonnet/medium
P3-3 P3 7 main SPEC 三頁、Agent 手冊權限節 extend P2-2 sonnet/medium

登記型檔 owner:protocol console_scopes.py→P0-1;remote-agent contract.py→P1-1;BE _ADMIN_CAPABILITIES→P1-3;BE manifest.tsv→P1-2;Agent constants.py+agent_auth.py→P1-4;Agent console/api/*.py→P1-5(一張卡全換,拆開會撞 __init__.py);FE console router.js→P1-6;FE 主站三支 i18n/capabilities.js→P1-7;FE api.js→P2-2。

§8

端到端驗收

# 步驤 期待
1 角色頁「檢測 › Agent 管理」 底下 4 顆管理用+「Agent 主控台」子頁;子頁 26 顆依資源分組、動作 read→update→execute→download;舊 7 顆不見
2 建角色只勾 files.read、scan.read、scan.download,用該角色登主系統進主控台 側欄:總覽/掃描/執行紀錄/檔案/授權;檔案頁只有預覽;執行紀錄列出手動與派工掃描、各有下載鈕、除錯紀錄不出現;直接打 /console/api/files/<uid>/download 403
3 同角色對舊版 Agent(1.7.4) 登入成功、側欄只剩總覽/執行紀錄(舊版認得 configure…都不在票上)/授權;頂部橫幅「有 3 項權限尚未支援」
4 把該使用者加為離線操作者,拔網線,用離線密碼登入 畫面同第 2 步
5 管理員拔掉 scan.download 下次進主控台下載鈕消失;離線名單下一次心跳縮權、已登入 session 被踢
6 角色頁對有 detection.plugin.update 的角色按「比照主系統功能帶入」 「工具設定 · 設定」自動勾上,toast「已帶入 N 項」,可取消
7 主系統「Agent 掃描工具設定」頁代設帳密 只有 tools.update 的角色看得到按鈕;無則 403
8 DEV 套完 migration capabilities 無舊 7 顆、有 26 顆;原持 configure 的角色現持 15 顆;CAPABILITY_CATALOG VERSION +1;回寫「出貨基線待重產」
9 P3-2 後用釘版 wheel 起 HEAD 簽票 200;Agent 用舊票 403、新票 200