FR-190 · 設計定案 v1.0 · 2026-11-09
7 個範圍拆成 26 顆能力點;三邊共用一份清單;同步機制沿用;14 張卡 7 批
本文件是 FR-190 的定案設計。討論稿 D1~D9 全部裁定(見 §3),拆卡表經 handoff/cards.py 程式驗過依賴與同批撞檔。讀者只看這一份就能開工;討論過程與被排除方案在 討論稿。
| 日期 | 版本 | 內容 |
|---|---|---|
| 2026-11-09 | v1.0 | 討論稿 D1~D9 全部定案;拆卡 14 張 7 批 |
| 2026-11-09 | v1.0 | 開卡後資安審查:26 顆模組前綴從 detection 改 agent(避開 seed 的 detection.* 萬用字元 pattern,見 §5.1);P1-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 顆內持有的
整案一句話: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 的版本。
| # | 決策 | 定案 | 理由 | 被排除 |
|---|---|---|---|---|
| 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 算,一次性、可再拔) |
風險面不同的事綁死會多給權限;勾兩次的痛點用鈕解 | 自動跟;全部手勾 |
| 元件 | 現況 | 本案動作 | 卡 |
|---|---|---|---|
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 |
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 得到的只有這裡。
| 舊顆 | 展開成 |
|---|---|
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/稽核員兩列)。
| 檔 | 舊 | 新 |
|---|---|---|
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 | 不變 |
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。
Agent 管理頁按鈕 v-can:「進入主控台」→26 顆任一(reuse useMenuItemsCan 的 ANY);「重啟」→ops.execute;「換發憑證」→keys.update;「補更新基準」→ops.update;執行紀錄頁下載→scan.download/netdiag.download 依列種類。
| 端點 | 舊 | 新 |
|---|---|---|
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 |
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 個名字一律不認(不做舊主系統相容)。
test_fr190_scope_table.py 對每顆單一 session 逐支打。console_scopes 模組與一個錯誤碼。由 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。
| # | 步驤 | 期待 |
|---|---|---|
| 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 |