FR-190 · 定版交付

定版交付:Agent 主控台權限能力點化

已合回 feature/FR-172,未 push;套件 jedi-agent-protocol 0.6.1、jedi-remote-agent 1.18.3 已在 Nexus;出貨基線已含本案 migration;STG/POC 未上

Agent 主控台的權限從 7 個大範圔改成 26 顆能力點:11 個主控台功能,每個功能依「檢視、設定、執行、下載」分開給。管理員在主系統角色設定頁「Agent」組的「Agent 主控台」子頁逐顆勾,或按「比照主系統功能帶入」一次帶入。使用者進主控台時主系統把他持有的顆寫進入場券,Agent 照票開頁面、擋 API;斷線用的離線操作者名單每次心跳依當下持有的顆重算。主系統、jedi-remote-agent、Agent 三邊都讀 jedi-agent-protocol 的同一份清單。

四 repo 已合回 feature/FR-172 出貨基線已重產並乾淨裝驗 畫面未實機手測 STG/POC 未上

一句話

Agent 主控台權限變成 26 顆主系統能力點 agent.<功能>.<動作>,角色頁逐顆給;簽票與離線名單照既有管道把持有的顆帶到 Agent;Agent 版本舊時丟掉不認得的顆、照常登入並提示;主系統派下來的掃描紀錄歸「掃描 · 檢視」。


1. 交付物

本案動到四個程式 repo 與兩類文件,另外重產了出貨基線。契約(清單本身)在 jedi-agent-protocol;主系統這邊是資料遷移、端點守門與角色頁;Agent 這邊是驗票與路由守門;前端分主控台與主站兩塊。下表列每個 repo 交付了什麼、落在哪些檔。

Repo 內容 位置
jedi-agent-protocol 0.6.1 console_scopes 模組:26 顆清單(資源、動作、中英顯示名、離線可下放旗標、對應主系統功能)、OFFLINE_GRANTABLE(24 顆)、MIRRORS(15 條)、scope_of()/capability_of() 互轉、CAPABILITY_MODULE='agent'、LICENSE_MODULE='remote-agent-manage'、HOME_ROUTE='remote-agent-console';錯誤碼 TICKET_SCOPE_EMPTY;三邊鏡像測試 jedi_agent_protocol/console_scopes.py、error_codes.py、tests/test_console_scopes.py、tests/test_mirror.py
jedi-remote-agent 1.18.3 CAPABILITIES(4 顆管理用+26 顆主控台)、簽票範圍表、離線名單範圍表三張表由 console_scopes 推導;舊 7 顆退場,新顆 legacy_name 記舊名 jedi_remote_agent/plugin/contract.py
主系統 BE 主線 migration:ui_routes 子頁 remote-agent-console、舊 7 顆字面持有者展開到新顆(含 pattern 角色與客戶自建角色)、刪舊顆與三張關聯表的列、角色範本「稽核員」預帶 agent.scan.read/agent.scan.download;_ADMIN_CAPABILITIES 全部換 agent.*,執行紀錄下載依紀錄種類細判;權限矩陣產生器把 protocol 清單內的顆視為「Agent 端有守」;唯讀端點 GET /roles/<uid>/agent-console-mirror;守衛測試「出貨 seed 的萬用字元 pattern 不得命中 agent.*」 scripts/sql/2026-11-10-fr190-agent-console-capabilities.sql、core/plugins/remote_agent.py、core/plugins/_agent_local_runs.py、scripts/deliverables/gen_permission_matrix.py、api/access/routes/agent_console_mirror_route.py(19 行)、app/access/service/agent_console_mirror_service.py(24 行)、test/test_fr190_seed_patterns_exclude_agent_module.py
Agent CONSOLE_SCOPES 改 import 契約;驗票把不認得的範圔丟掉並回數量,全部不認得才 403;/api/me 多回 dropped_scopes;73 條主控台路由守門一對一換成 26 顆短名;執行紀錄不分來源歸 scan.read;全 blueprint 單一 scope 對表守衛 guidant_agent/shared/constants.py、sync/domain/agent_auth.py、identity/app/session.py、console/api/*.py、test/test_fr190_scope_table.py
主系統 FE 主控台:router.js 每頁 scope 與 13 個 view 的按鈕判斷換 26 顆短名、頂部舊版 Agent 橫幅;主站:角色樹新增 agent 模組 26 顆標籤與說明、動作順序加 download、「Agent 主控台」子頁列的「比照主系統功能帶入」鈕、離線操作者對話框權限標籤、Agent 管理頁按鈕換顆、權限常數檔重產 console/router.js、console/Shell.vue、src/config/locales/i18n/*/permission-tree.json、src/composables/usePermissionGrouping.js、src/components/access/PermissionMenuTree.vue、src/composables/useAgentConsoleMirror.js、src/config/capabilities.js
文件 頁面 SPEC 三頁、Agent 使用手冊權限節 docs/spec-site/current/evidence/agent-console.md §3、evidence/remote-agent-manage.md、system-admin/access-role-detail.md §4.2;docs/user-manual/agent/13-standalone-operation.md「角色權限:26 項」
出貨基線 188 基線庫已套本案 migration;02-schema.sql、04-seed-core.sql、99-stamp.sql 重產;本機乾淨 postgres:16 完整 init.sh 裝驗通過 scripts/init/

2. 26 顆能力點

能力點全名 agent.<功能>.<動作>;入場券與 Agent 守門用去掉 agent. 的 <功能>.<動作>。模組 agent 同時是角色樹第一層的分組名。清單的單一來源是 jedi_agent_protocol.console_scopes,三邊都從那裡讀,不各自硬寫。

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

共 26 顆;離線操作者可下放 24 顆,只有 update.read、update.execute 不下放(換版不可逆,斷線時不該有人換版;重啟可逆、斷線卡住要能現場重啟,所以下放)。

模組為什麼是 agent 而不是 detection:出貨 seed 的系統角色帶萬用字元 pattern——稽核主管 detection.*.*,稽核員、一般成員、唯讀 detection.*.read。能力點若放在 detection 底下,新裝客戶的稽核主管升級後自動持有全部 26 顆(含檔案下載、重啟、換版),唯讀角色自動持有 11 顆檢視。agent 是新模組,沒有任何既有 pattern 命中。DEV 的角色沒有這些 pattern,在 DEV 驗不出來,所以守衛測試掃 seed 檔本身。


3. 權限怎麼到 Agent

權限從角色設定頁到 Agent 主控台走兩條路:使用者在線時點「進入主控台」拿入場券(即時路),斷線時用離線操作者名單登入(離線路)。兩條路都讀同一份角色持有,本案沒有新增任何同步機制,只把寫進票與名單的範圍從 7 個換成 26 個。下圖是完整順序。

%%{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','actorBkg':'#E2F0F1','actorTextColor':'#14201F','actorBorder':'#0E7C86','actorLineColor':'#C7D1D2','signalColor':'#4A5A5C','signalTextColor':'#14201F','noteBkgColor':'#F6EBD5','noteTextColor':'#14201F','noteBorderColor':'#9C6B12','sequenceNumberColor':'#FFFFFF'}}}%%
sequenceDiagram
  autonumber
  actor M as 管理員
  participant H as 主系統 API
  actor U as 使用者(瀏覽器)
  participant A as Agent(:8444 主控台)
  M->>H: 角色頁「Agent 主控台」子頁勾顆,或按「比照主系統功能帶入」
  H->>H: GET /roles/{uid}/agent-console-mirror 依 MIRRORS 算出該加的顆(只算不寫)
  M->>H: 儲存角色
  U->>H: POST /remote-agents/{uid}/console-ticket
  H->>H: 逐顆查使用者持有的 26 顆,寫進票 scope;一顆都沒有回 403
  H-->>U: 五分鐘入場券(RS256,scope 原樣 26 個短名字串)
  U->>A: 帶票登入
  A->>A: 範圍過濾:認得的留下、不認得的丟掉並計數;全部不認得回 403 TICKET_SCOPE_EMPTY
  A-->>U: session;/api/me 回 scope 與 dropped_scopes,前端依 *.read 開分頁、依 update/execute/download 開按鈕
  A->>H: 心跳
  H->>H: 離線操作者名單:每人依當下持有的顆算可下放範圍(24 顆內)
  H-->>A: 整份名單;縮權的人踢掉已登入 session
圖 1 權限從主系統角色到 Agent 主控台的兩條路。第 4~6 步是即時路(使用者點進主控台),第 9~11 步是離線路(每次心跳);兩條路讀同一份角色持有,改角色不必重新授權。

第 5 步的「持有」由 jedi-iam 展開服務算,含萬用字元 pattern(*.*.* 的系統管理員自然持有 26 顆)。第 8 步丟掉不認得的範圍只會讓人少拿權限、不會多拿,所以主系統可以先升、Agent 慢慢跟;舊 Agent 收到新顆名會全部丟掉,等同登入失敗。反向不相容:新 Agent 不認舊的 7 個範圍名,舊主系統發的票在新 Agent 上全丟、回 403,升級順序必須主系統先。第 11 步的名單只決定「能不能離線登入這台」,範圔永遠跟著角色,主系統改角色下一次心跳就生效。


4. 規則

  • 守門只收緊不放寬。每條 Agent 路由與主系統端點換顆後,新顆持有者集合是舊顆持有者集合的子集;主系統執行紀錄的三個下載端點守 scan.download/netdiag.download 任一,route 內再依紀錄種類細判一次。
  • 主系統端點對應:進入主控台(簽票)與 Agent 清單可見→26 顆任一;重啟→ops.execute;換發憑證→keys.update;代設工具帳密→tools.update;補更新基準→ops.update。Agent 管理頁本身的新增、檢視、修改、刪除仍是 detection.remote-agent.* 四顆,不動。
  • 執行紀錄不分來源。本機手動與主系統派工的掃描紀錄都歸 scan.read 看、scan.download 帶走;網路除錯紀錄歸 netdiag.*。
  • 「比照主系統功能帶入」是一次性動作,不是連動。依 MIRRORS 15 條把對應顆勾起來,管理員可再取消,要按儲存才寫入。對照:檢測工具的檢視/修改→工具設定的檢視/設定;掃描設定檔檢視→掃描檢視、修改→掃描執行;LDAP 設定→LDAP;平台授權→授權;儲存設定→維運檢視/設定;產品更新→更新;Agent 管理的修改→金鑰設定+維運執行;Agent 管理的新增→連線設定;主系統證據下載→檔案下載。日誌與網路除錯沒有對應的主系統功能,不會被帶入。
  • 既有角色的升級。migration 把字面持有舊 7 顆的角色(含客戶自建)展開到新顆:configure→工具、LDAP、金鑰、授權、維運、連線六組的檢視+設定共 14 顆再加掃描檢視;browse-files→檔案檢視、日誌檢視;run-local-scan→掃描檢視+執行、日誌檢視;net-diag→除錯檢視+執行、日誌檢視;download-files→檔案、掃描、除錯、日誌四顆下載,ops.download(診斷包)只給同時持 configure 者;restart→ops.execute;upgrade→更新兩顆。萬用字元持有者不展開。展開後刪舊 7 顆與 role_capabilities、route_capabilities、role_capability_patterns 的對應列。
  • 出貨預帶:系統管理員靠 *.*.*;稽核員 agent.scan.read、agent.scan.download;檔案下載四顆與更新兩顆不預帶給任何非管理員角色。
  • 票上寫法:scope 是 26 個短名的字串陣列,原樣寫,約 2.5 KB;Agent 登入入口 body 上限 8 KB。
  • 錯誤碼:Agent 路由沒權限 403 AGENT_CONSOLE_SCOPE_DENIED;票上範圍全部不認得 403 TICKET_SCOPE_EMPTY;主系統一顆都沒有簽不到票 403 AGENT_CONSOLE_NO_SCOPE。
  • 前端只決定看得到、點得進,真正的牆是 Agent 端 require_session(<短名>) 與主系統 _ADMIN_CAPABILITIES;直接打 API 一樣被擋。

5. 驗證結果

自動測試(四 repo 工作區,合回前;protocol 鏡像測試在合回後對主線再跑一次):

範圍 結果
jedi-agent-protocol 96 條綠,含三邊鏡像(remote-agent 不再硬寫舊名、Agent CONSOLE_SCOPES 改 import、主系統 _ADMIN_CAPABILITIES 無舊名)
jedi-remote-agent 606 條綠
Agent 全套 2542 條綠;test_fr190_scope_table.py 對 26 顆各建單一 scope session 逐支打全部主控台路由,放行集合等於對照表
主系統 BE 守衛 test_fr190_seed_patterns_exclude_agent_module、test_legacy_capability_name_references、test_module_boundaries 綠;基線重產後 test_plugin_capabilities_are_seeded、test_seed_capabilities_are_all_accounted_for 綠
主系統 FE 權限相關 5 檔 74 條綠;主控台與主站 vite build 過;改動檔無 border-left

DEV 資料庫(migration 套用後):capabilities 中 module='agent' 26 顆、舊 7 顆 0;ui_routes 有 remote-agent-console(tree_page、非選單)綁 26 顆;role_capabilities、route_capabilities 無孤兒;原持 configure 等舊顆的角色已展開(例如 IT 執行人員持 21 顆、系統管理員 26 顆)。

主系統端點實打(HEAD 起在 8123):系統管理員簽票 200、票帶 26 個範圍;只持 agent.scan.download+detection.remote-agent.read 的角色對掃描報告過守門、對除錯報告 403。

出貨基線:188 基線庫套本案 migration 後重產三份產物;本機乾淨 postgres:16 跑完整 init.sh(198 表、271 條 RLS policy、cm_app 缺權限 0),agent 顆 26、舊顆 0、子頁綁 26、基線 marker 在、稽核員預帶 2、非系統角色萬用字元命中 agent.* 為 0。

未實機驗證:Agent 的新票登入 200、舊票 403 是用 Flask 測試 client 打的,沒另起 Agent 程序;掃描報告下載沒拿到 200(DEV 的 Agent 離線,過守門後連不上);角色頁子頁、帶入鈕、主控台側欄與橫幅沒有人在瀏覽器實機點過。


6. 這一輪沒做完的

三件事還沒做,讀本頁時不要把它們當成已完成。

畫面手測。角色頁「Agent」組底下的子頁 26 顆與「比照主系統功能帶入」鈕、主控台側欄依權限顯示、舊版 Agent 的頂部橫幅,全部只有自動測試驗過,沒有人在瀏覽器實機點過。DEV 的 Agent 要重起到含本案的版本才看得到。由決策者接。

push 與上版。四個 repo 的 feature/FR-172 都有本案的 commit,尚未 push;STG 與 POC 都沒上。上版順序必須主系統先、Agent 後,因為新 Agent 不認舊的 7 個範圍名。由決策者接。

主線本來就失敗的一條守衛。test_fr132_capability_home_route 的 test_dev_every_capability_has_a_home 列出 13 顆 proj.* 專案角色能力點沒有 home_route,與本案無關,由 FR-194 處理。


7. 文件定位

本頁只寫交付了什麼與現在長什麼樣。為什麼這樣決定、過程中發生什麼、怎麼操作,分別在設計、歷史、手冊三類文件裡,照下表找。

你想知道 看哪份
為什麼拆 26 顆、為什麼模組是 agent、被排除了哪些做法 design.md §3、docs/analysis/2026-11-10-agent-console-capability-granularity.md
討論過程、對照表怎麼來的 discussion.md
畫面長什麼樣(審稿用解釋版,非產品文案) prototype/index.html
使用者操作怎麼做 手冊 docs/user-manual/agent/13-standalone-operation.md「角色權限:26 項」
頁面行為規格 SPEC evidence/agent-console.md §3、evidence/remote-agent-manage.md、system-admin/access-role-detail.md §4.2
每張卡做了什麼、commits、驗收結果 handoff/2026-11-10-FR-190-SUMMARY.md、README.md
拆卡怎麼排批 handoff/cards.py