FR-166.1 · 設計文件 v2 · 2026-10-29(D1~D10 定案;27 張卡 10 批)

下載收進權限點、剩餘天數看得到、profile 自動到位、主系統設定 Agent 掃描工具、稽核走 logging 管線、登入後能切語言

FR-166 收尾後 PM 提六條回饋;每條先找既有機制再決定怎麼接,沒有一條另起爐灶

PM 看完 FR-166 手測版提六點:證據檔只准線上看、下載要獨立權限且出貨全鎖;離線還剩幾天能登入要顯示;CINC profile 推到 Agent 要看得到有沒有成功、能補推;工具帳密要能從主系統設定但資料仍存 Agent;要有操作與事件日誌可瀏覽;登入後沒得切語言。本文件把六條各自對到既有機制:下載權限點掛在 jedi-file-upload 的簽票那一層一處生效;profile 照弱點資料庫的心跳同步模型;主系統設定工具帳密走測試連線同一條直連通道當場存;稽核改接 Python logging 管線(jedi-common 的 system_logs、標準庫 QueueHandler),不再自寫 jsonl;剩餘天數與語言切換是純畫面。所有下載出口與 FR-169 的檔案下載加密接在一起。

D1~D8 定案 27 張卡 10 批;Agent 7/FE 8/BE 6/套件 6 下載/匯出能力點分模組:7 顆 download+2 顆 export,出貨零角色;上傳者本人免 稽核:一個入口、filter 補操作者、handler 當 sink 主系統兩條路通 Agent:心跳(拉)與資料面 8443 直推;設定帳密走直推 下載一律加密(FR-169):主控台檔案/報告/日誌匯出/診斷包 陷阱:jedi-common 0.0.31 的 DBLogHandler 不過濾,要包過渡子類

狀態:v2 定案、實作完成待收尾|日期:2026-10-29|母卡 CM-3199、子卡 CM-3200~CM-3222、CM-3282~CM-3285|前作 FR-166(主控台、D23 離線權限同主系統、HTML 預覽)、FR-156(弱點資料心跳同步)、FR-157(工具帳密搬進 Agent)、FR-033(主系統稽核事件管線)|並行 FR-169(下載檔加密) 開發 branch:五個 repo 皆在 feature/FR-139;runner 開工 git branch --show-current 再確認,不對就停下問,不切

🔴 這份是設計決策,不是現況。 上線後的畫面與 API 現況看 SPEC 頁。

變更紀錄

日期 變更 對應
2026-10-29 初版:PM 六條回饋 → D1~D8,17 張卡 6 批;同日開卡 母卡 CM-3199,子卡 CM-3200~CM-3216
2026-10-29 v2:D1/D2 改分模組能力點;D5 改直連當場存;新增 D9(離線超過天數 AD 也鎖)、D10(日誌覆蓋率與匯出);工具頁改名「Agent 掃描工具設定」並重做;與 FR-169 加密接齊 子卡 CM-3217~3222、CM-3282~3285

1. 需求背景與端到端

PM 2026-10-28 看完 FR-166 手測版後的六條原話與決策者補充:

# PM 原話 決策者補充(2026-10-29)
1 Agent 檔案的地方也是只能預覽、下載拔掉;所有報告不要讓下載只能預覽;下載要可以設定成權限點,目前全部都鎖起來 全系統所有證據檔案下載都收進權限點,不限 Agent;不能預覽的格式就不給看
2 離線情況下還剩幾天就不能再離線登入,要顯示在儀表板 —
3 CINC Auditor profile 推到 Agent 也要讓 Agent 知道有更新成功,也要可以補更新 比照弱點資料庫那套
4 掃描工具設定檔本機還是要留機制給 user 從主系統設定,讓人遠端幫忙設置不透過 Agent 畫面,但資料還是存在 Agent 等於在主系統開放編輯權限;Agent 離線直接說連不上
5 要有日誌管理,記錄 user 的操作行為跟 log audit,要有 UI 可瀏覽 只記操作與事件,系統 log 先不要;比照主系統 system_logs;用 logging 機制,本機有範例,不要硬刻
6 登入後沒有 i18n 切換功能 —

做完之後

  • 沒有「下載證據」權限的人,主站十個地方的下載鈕都不見、Agent 主控台只剩預覽、主系統「Agent 掃描執行紀錄」頁報告也只剩預覽。出貨預設沒有任何角色有這顆,管理員要給才有。SVG 這類 Agent 不 inline 的格式就看不到。
  • 主控台總覽多一張卡「離線登入還能用 N 天」,到 0 變紅並說明只剩本機管理員能登;離線密碼與 AD 一起鎖,連回主系統才能再用。
  • 主控台總覽多一張卡「檢測基準」:幾支、多久前更新、幾支失敗,可按「立即檢查更新」,健康區多一盞燈。
  • 主系統上傳新版 profile 後,每台 Agent 下次心跳自己比對、背景拉、回報;profile 頁每個版本展開看得到每台 Agent「已取得/待更新/失敗/離線」,有「補更新」鈕。主控台也有「立即檢查」。
  • 主系統「檢測工具」頁翻回來改名「Agent 掃描工具設定」:左邊選一台 Agent,右邊是那台每支工具一張卡(已設定/未設定、設定時間、設定方式、最近測試);每張卡一顆功能選單:設定、測試連線、清除,都開彈窗、當場生效。帳密只過一手、主系統不落地;Agent 離線整頁鎖並說明。
  • Agent 稽核改寫進本機 SQLite 的 system_logs 表,欄位與主系統同一份;主控台多「日誌」分頁可依人、事件、日期篩,可匯出 CSV。預覽、進頁、開工具表單、開執行紀錄詳情也記;升級時舊版 jsonl 稽核一次搬進表。回傳主系統的機制不變,游標改自增 id。
  • 所有下載(主控台檔案、報告、日誌匯出、診斷包,主系統 Agent 執行紀錄報告)都照 FR-169 加密:下載前顯示一次性密碼。
  • 主控台登入後右上角可切中英。

2. 分工概述

flowchart LR
  subgraph 主系統
    FU[jedi-file-upload 簽票<br/>action=preview/download]
    CAP[(各模組 download 能力點<br/>認領者自己查)]
    HB[心跳回應<br/>profiles 清單+補更新旗標]
    DP[資料面直推 8443<br/>PUT/DELETE /detection/credentials]
    SL[(system_logs)]
  end
  subgraph Agent
    PC[profile_cache 既有取檔]
    PS[profile_sync 照 vulndb_sync]
    CS[credential_store]
    AP[audit() → Filter → Queue → DBLogHandler 子類]
    SQ[(SQLite system_logs)]
    UI[主控台:預覽/剩餘天數/日誌/語言]
  end
  CAP --> FU
  HB --> PS --> PC
  DP --> CS
  AP --> SQ --> |心跳 console_events| SL

主系統與 Agent 之間有兩條路:Agent 出站的心跳(每 60 秒拉一次,profile 清單、補更新旗標夾在回應裡),以及主系統連進 Agent 資料面 8443 的直推(mTLS+綁定 agent 身分的短效 JWT,回應比對 Agent 指紋)。要當場知道結果的動作(測試連線、取證據檔、設定與清除工具帳密)走直推;狀態同步走心跳。直推的前提是主系統連得到 Agent 的 8443:落地版同在客戶內網一定通,SaaS 版要客戶開放。

3. 決策定案(D1~D8)

# 決策 定案 理由 被排除
D1 下載權限點掛哪一層 掛在 jedi-file-upload 簽票端點+認領者:簽票分 action=preview|download,下載端點只收 download 票;登記表多一個 FILE_ACTION_DOWNLOAD 動作,每類檔案的認領者(證據、SSP 佐證、意見回饋附件…)在 download 時自己決定要不要多看一顆能力點。看不到的檔一律 404,看得到但不能下載 403 FILE_UPLOAD_403002 主系統所有檔案下載都走 /file/download/<uid> 一條路,票是唯一門;但「哪些檔算客戶資料」只有認領它的模組知道,全局一顆守門會把公版範本也鎖掉 全局一顆 各模組 download 能力點(能力點名撞三段約束,且分不出客戶資料與公版);前端逐處擋(漏一處就是洞);另開第二條下載端點
D2 能力點命名與預設 每類資料用自己模組的 download/export 動作:下載 7 顆(grc.job-evidence.download、grc.audit.download、oscal.ssp.download、detection.source-scan.download、evidence.classification.download、workspace.feedback.download、detection.remote-agent.download-files),匯出補 2 顆只擋 read 的客戶資料(grc.audit.export、grc.risk.export);角色設定頁多一欄「下載/匯出」讓管理員逐模組決定;出貨全部不綁任何角色,Administrator 靠 *.*.* 自然持有。判準:內容是客戶自己的資料就鎖;產品帶來的公版/範本、各種匯入範本、管理功能匯出不擋。13 類上傳檔鎖 9 開 4(資源庫建議附件、框架 PDF、檢測規則包、系統共用資產開放)。上傳者本人永遠能下載自己上傳的檔(比 upload_files.created_user),但仍要先看得到;範本 SSP 的佐證文件只鎖下載、預覽照舊 PM 原話「目前全部都鎖起來」;管理員要能分模組開,一顆全系統的做不到;上傳者拿不回自己的檔不合理 一顆全系統 各模組 download 能力點;綁預設角色;連匯入範本一起鎖
D3 Agent 主控台的下載 票上新範圍 download-files ↔︎ detection.remote-agent.download-files(主控台檔案頁全是掃描報告,整頁視同證據;網路除錯報告一樣鎖);離線操作者同樣照 D23 由心跳算;檔案頁補預覽端點走既有 inline_policy;不能 inline 的格式(svg、xhtml)不給看 與 D23 同一份真相;預覽機制 FR-166 已有 給 svg 另開消毒預覽
D4 profile 同步模型 照弱點資料庫:心跳回應附 profiles[](現行版本 uid/version/sha256/file_uid),Agent 比對本機快取、背景拉、下次心跳回報每支狀態;主系統存 agent_profile_states;「補更新」=心跳旗標讓 Agent 清冷卻立刻檢查;主控台「立即檢查」同 vulndb 那顆 掛點(heartbeat_extras)與 Agent 端流程(vulndb_sync)都現成;取檔與快取 profile_cache.py 現成,只差「主動比對」這一步 主系統主動推(主系統連不進 Agent);改掉派工時 lazy 拉(沒壞)
D5 主系統設定/清除工具帳密的通道 直連 Agent 資料面:主系統 PUT/DELETE Agent 的 /detection/credentials,與測試連線同一道 mTLS+JWT+回應指紋比對;Agent 照工具目錄 schema 驗欄位(與主控台存檔同一套),不合回 400 不存;回來的狀態當場寫回 remote_agents.configured_tools。帳密不寫 tenant_detection_tool_configs、不進派工單;帶帳密只走加密通道;Agent 離線回 409;稽核 6086/6087 method=remote 不帶值 測試連線本來就直推,設定若改等心跳,同一頁兩種速度、使用者以為壞了;測試連線能通的環境設定就能通 走 credential_handover 派工單等心跳(要輪詢、最多等一分鐘);主系統存一份再同步(第二份真相);離線排隊(使用者不知道何時生效)
D9 離線超過天數時 AD 登入 一起鎖,不留例外;只剩本機管理員能登,連回主系統才能再用。原因碼沿用 method_blocked,擋在送 AD 之前 斷線越久名單越可能過時(撤銷收不到),AD 密碼對不代表授權還有效 AD 不受天數限制(FR-166 D17,推翻)
D10 日誌覆蓋與匯出 舊 console-audit.jsonl 啟動時一次搬進表(冪等);預覽記自己的碼 6230(與下載 6088 分開,主系統「檔案離開 Agent」統計不被預覽灌水);進頁 6231、開工具表單 6232、開執行紀錄詳情 6233(同一對象 5 分鐘內只記一次);匯出 CSV 6236,守「看日誌」範圍+download-files、走 FR-169 加密。6230~6236 只留本機 換表前寫在 jsonl 的紀錄不搬,日誌分頁就看不到升級前的操作;「看」類動作要留紀錄才查得到誰看過客戶資料 前端切頁主動打 API 記進頁(要多一支 API,先不做)
D6 Agent 稽核的架構 接 Python logging 管線:入口只有 jedi-remote-agent 既有 audit(logger, event_name, event_code, **fields);ConsoleActorFilter 從 flask.g.console_session 補操作者(背景執行緒補 system);jedi-common SecretMaskingFilter 遮密;QueueHandler→QueueListener→DBLogHandler 子類寫 SQLite system_logs(表、DTO、service 用 jedi-common 現成);心跳回傳改讀表、游標改自增 id 41 個呼叫點現在手傳 actor、哪些回傳是寫死集合、sink 只有 jsonl,加一個 sink 就改 record();logging 天生就是「一入口多 sink 中間掛 filter」,主系統已這樣用 自寫 SQLite audit 模組(第二套管線);只改 jsonl 成寫表(架構不變)
D7 資料庫 留 SQLite,不起 Postgres 123 密集手測三天稽核 61 筆;WAL+busy_timeout 已設;QueueListener 讓寫表永遠單執行緒;多一個容器牽動安裝/升級/wipe/診斷包全部腳本 Postgres(反悔條件:多台共用狀態或跨 Agent 集中查,後者已靠心跳回傳做到)
D8 jedi-common 版本 Agent 釘 0.0.31 不升;DBLogHandler 用薄子類補「只放行帶 event_code、缺欄補 None」兩段(1.3.6 原生有),升版即刪 jedi-file-upload 0.0.22 也釘舊版,兩個一起升牽動範圍不小,與本案無關 本案順便升

4. 現況接入點盤點

全部開卡前實查(2026-10-29)。

主題 既有機制 位置 缺什麼
下載出口 主系統所有檔案下載走 /file/download/<uid>?st=,票由 /file/access-token/<uid> 簽,守門=file_access_guard(簽章票或 JWT)+file_ownership_guard(歸屬),無能力點 jedi-file-upload/api/routes/upload_file_route.py 141、160;宿主接線 core/plugins/file_upload.py 351 票不分用途;FileUploadAdapters 沒有下載守門欄
前端下載呼叫 10 處:signedDownloadUtil.js(簽票唯一入口)、JobExecutionDrawer.vue 1434、RoundEvidenceView.vue 80、SystemFeedbackView/Form.vue、EvidenceFilePreviewDialog.vue 135、useSbomViewer.js、useScanFileDownload.js;Agent 執行紀錄 AgentLocalRunsView.vue 213 FE repo 都沒有 v-can
Agent 主控台下載 檔案頁 files_download 守 files 範圍;報告預覽走 inline_policy+CSP sandbox(FR-166);runs.js canDownload 看 files/net-diag core/console/files.py 169、console/views/Files.vue 90、console/runs.js 82 檔案頁無預覽;範圍不分瀏覽與下載
能力點 ↔︎ 票範圍 CONSOLE_SCOPE_CAPABILITIES 五列、OFFLINE_OPERATOR_SCOPE_CAPABILITIES 四列 jedi_remote_agent/plugin/contract.py 44、52 缺 download
離線天數 心跳下發 offline_login_days;Agent heartbeat_status.offline_days();總覽 banner 已顯示「已離線 n 天/上限 N」 console/views/Overview.vue 141 沒算剩餘、沒有卡
弱點資料同步(範本) 心跳附加 vulndb(heartbeat_extras 鉤子)→ Agent vulndb_sync.observe 比對→背景下載→回報 vulndb_date;主控台 POST /vulndb/check 只清冷卻喚心跳 core/plugins/_agent_vulndb.py、evidence-agent/core/vulndb_sync.py、ops_routes.py 104 —
profile 取檔 派工時 _resolve_profile → profile_cache 按 sha256 命中或拉;原子解壓 evidence-agent/core/profile_cache.py 115~250 沒有「主動比對清單」;主系統沒有每台 Agent 的狀態
帳密交接 credential_handover 工單(升級搬家用):主系統從雲端密文解出 fields 進單,Agent put_if_absent 存本機、回執工具代號 jedi_detection/app/service/credential_handover_service.py;evidence-agent/core/credential_handover.py 只有搬家;當場設定另走直推(D5)
主系統直推 Agent 測試連線 AgentProbeClient、取證據檔 RemoteAgentAdapter:mTLS+綁定 agent 身分的短效 JWT;Agent 資料面 /blob、/detection 前綴由 data_plane_auth 守門 jedi_detection/infra/detection_tools/connector/agent_probe_client.py;infra/upload_file/remote_agent_adapter.py;evidence-agent/core/data_plane_auth.py 沒有設定帳密的端點
檢測工具頁 tool-plugin-manage enable=0(FR-166 隱藏);ToolPluginManage.vue 766 行 scripts/sql/2026-10-28-fr166-hide-tool-plugin-manage-menu.sql 翻回並改用途
主系統稽核管線 logger.info(extra={event_code}) → CustomFormatter 補操作者 → DBLogHandler 只放行帶事件碼 → public.system_logs;套件 audit() helper jedi-common 1.3.6 logger/;jedi_remote_agent/common/audit.py —
Agent 稽核 audit.record(action, actor, **fields) 寫 jsonl;REPORTED_ACTIONS 11 種隨心跳回傳;ISO 微秒游標;_MASKED_KEYS 自遮;41 個呼叫點手傳 actor evidence-agent/core/console/audit.py 155;logging 只有 basicConfig 整段換管線
Agent SQLite jedi-common init_db,WAL+busy_timeout 5s,local_db 版本表 v2 四張表 core/app_factory.py 38、91;core/local_db/migrations.py v3 加 system_logs
jedi-common 0.0.31 DBLogHandler.emit 直接讀 record.user_uid/event_code、不過濾;CreateSystemLogDTO/system_log_service.add_system_log(@transaction)現成;session_context 是 ContextVar,listener 執行緒自開 scope 沒問題 Agent venv jedi_common/logger/db_log/ 過濾與補欄要子類
主控台語言 登入頁有 toggleLocale;殼只有深淺色與登出;翻譯檔中英齊 console/Login.vue 200、console/Shell.vue 55 殼沒鈕、沒記住
主站日誌頁(範本) UserLogs.vue 讀 api_logs:act_time/level/user_name/url/method/source_ip src/views/action-log/UserLogs.vue —

5. 詳細設計

5.1 下載權限點(T1:A1、A1b、A2、A2b、B1、B4、B5、A5、F2、F6)

票分用途。 GET /file/access-token/<uid>?action=preview|download,省略=preview(舊前端不壞)。票的 scope 字串在既有 file_access_scope 後接 :download,下載端點 file_access_guard 只收帶 :download 的票,pdf-preview 端點兩種都收。簽 download 票前先跑 adapters.download_guard(宿主注入,None 時不擋)。

認領者各看自己那顆。 登記表 FILE_ACTION_DOWNLOAD;主系統 core/plugins/_file_ownership.py::assert_download_capability 一支共用:上傳者本人直接放行,否則查該模組的 download 能力點;9 類認領者各自呼叫它。能力點 migration 照 FR-166 形狀但第三段「預設角色授權」整段不寫。匯出端點照既有 @require_capability("<模組>.export") 形狀補 2 顆。

Agent 端:票範圍 download-files;files_download、netdiag 報告、runs 報告三條下載都改這個範圍;新增 GET /console/files/<uid>/preview 走 inline_policy。主系統「Agent 掃描執行紀錄」報告下載端點 _ADMIN_CAPABILITIES 改 detection.remote-agent.download-files。

前端:signedDownloadUtil.fileDownloadUrl 帶 action=download;下載鈕顯示條件=「持該模組 download 能力點,或是自己上傳的」,各頁帶各自那顆;共用預覽框由呼叫端給能力點;匯出鈕看各自 export 顆;主控台檔案頁改「預覽」為主、下載鈕看 me.scope 含 download-files。

陷阱:Agent 驗票見到不認得的範圍整張拒收,所以 Agent(A2 套件+A5)要先認得 download-files,主系統才可以簽;升級順序 Agent 先。

5.2 離線剩餘天數(T2:F1)

總覽卡:剩餘 = offline_login_days − offline_days,只在 net === OFFLINE 顯示;≤0 紅字+「只剩本機管理員可登入」。資料心跳已下發、Agent /console/state 已回,純畫面。到期後離線密碼與 AD 一起鎖(D9):Agent operator_session_valid 不分登入方式都看 offline_login_disabled(),登入當下與每個請求重驗走同一支;登入頁兩個分頁一起灰。

5.3 profile 同步(T3:A4、A7、B2、F4)

照 vulndb_sync.py 逐段對應:

vulndb profile
心跳回應 vulndb:{bundle_date,sha256,size} profiles:[{uid,version,sha256,file_uid}](只含 is_current)
observe() 比日期 observe() 逐支比 profile_cache.is_cache_valid(cache_dir, sha256)
背景執行緒下載+驗章+原子切換 背景執行緒呼叫既有 profile_cache._populate_cache(抽成公開介面,不複製)
回報 vulndb_date/vulndb_sync_error payload profiles:[{uid,version,sha256,state,error}]
主系統 remote_agents.vulndb_* 三欄 新表 agent_profile_states(一台多支)
主控台 POST /vulndb/check 清冷卻 POST /profiles/check 同款;主系統「補更新」=心跳回應帶 profiles_check:true 旗標(照 restart 旗標形狀)

失敗冷卻同 vulndb,避免每次心跳重拉。

主控台總覽「檢測基準」卡:Agent profile_sync.summary() 回 {total, ok, error, last_synced_at, listed},取得時間用快取完成標記檔的 mtime(重啟不丟);卡片顯示「N 天前更新/M 支基準 K 支失敗」,主系統沒下發清單時顯示「主系統未提供」;「立即檢查更新」與弱點資料卡共用 useSyncCheck;健康區 profiles 燈超過 7 天或有失敗轉黃。

5.4 Agent 掃描工具設定(T4:A9、B3、F5、F8,直連改寫)

頁面:主系統「合規與稽核 → Agent 掃描工具設定」(route agent-tool-credentials,門檻 detection.remote-agent.configure)。左欄選 Agent(連線中/離線、已設定 n/N 支、最近心跳);右欄是那台每支工具一張卡:已設定/未設定、設定時間、設定方式(由主系統設定/Agent 主控台)、最近測試結果。只列租戶啟用、要帳密的工具。卡片標題列一顆「⋮」功能選單:

動作 彈窗 走哪條路 能力點
設定 照工具 schema 出表單,按「儲存」;已設定時提示會整組覆蓋 POST /remote-agents/{uid}/tool-credentials → 主系統直推 Agent PUT /detection/credentials detection.remote-agent.configure
測試連線 要連目標主機的工具從設備清冊挑;有兩組憑證的工具先選組 既有 POST /detection-tools/configs/{uid}/test-connection 帶 agent_uid,Agent 用本機帳密測、結果記回心跳 detection.plugin.update
清除 確認窗說明之後派工會因未設定而失敗 DELETE /remote-agents/{uid}/tool-credentials → 直推 DELETE /detection/credentials detection.remote-agent.configure

頁頂「管理啟用的工具」對話框是租戶層開關:關掉的工具整個停用,這頁不列、任務設定也選不到。

直推流程:

  1. is_online false → 409 FILE_AGENT_409003,不打 Agent。工具目錄不認得 → 404。
  2. infra/remote_agent/agent_credential_client.py 以 mTLS+短效 JWT 打 Agent;帶帳密的 PUT 在非 full 模式直接拒(不走裸 http);回應比對 X-Agent-Fingerprint,不符拒用並稽核 6081。
  3. Agent 端 /detection/credentials 走 credential_handover.store(照工具目錄 schema 用 secret_form.validate 驗、寫 credential_store、稽核 6086/6087 method=handover);欄位不合回 400,訊息只有欄名。
  4. 主系統把回來的 status 寫回 remote_agents.configured_tools(assign_fields),畫面當場翻面;下一次心跳照常以 Agent 自報值覆蓋。
  5. 主系統稽核 AGENT_CREDENTIAL_SET/AGENT_CREDENTIAL_CLEARED method=remote,只帶工具代號。

離線與舊版 Agent:右欄整頁鎖(離線說明連不上、舊版說明請先升級),每 30 秒重抓 Agent,連回自動解鎖;填到一半斷線會關彈窗。

5.5 Agent 稽核管線(T5:A3、A6、F3)

業務碼 ──audit(logger,"AGENT_X",AgentEventCode.AGENT_X,**fields)──▶ logger "agent"
                                                                     │
                                       ConsoleActorFilter(補 user_uid/user_nickname/login_method/source_ip;無 request→system)
                                       SecretMaskingFilter(jedi-common)
                                                                     │
                                                              QueueHandler(請求不等 DB)
                                                                     │  專屬執行緒
                                                              QueueListener ──▶ AgentDBLogHandler(DBLogHandler)
                                                                                  只放行 event_code≠None/''/'-';缺欄補 None
                                                                                  system_log_service.add_system_log(@transaction 自開 scope)
                                                                                       │
                                                                                 SQLite system_logs
                                                                                       │
                                                        心跳 console_events:select id>cursor and event_code in REPORTED
                                                        ack → cursor=max(id)(自增 id,重啟不重送)
  • 呼叫點:41 處從 audit("credential_set", g.console_session.sub, tool=code) 改成 audit(logger, "AGENT_CREDENTIAL_SET", AgentEventCode.AGENT_CREDENTIAL_SET, tool=code)。actor 參數消失。
  • 哪些回傳主系統:事件碼表的屬性(套件 AgentEventCode 旁一個 REPORTED 集合),不再是 audit.py 另一個集合。
  • 新事件碼(A2 在套件加、B1 鏡像):AGENT_LOGOUT、AGENT_CREDENTIAL_TESTED、AGENT_PASSWORD_CHANGED、AGENT_HEARTBEAT_LOST、AGENT_HEARTBEAT_RESTORED、AGENT_VULNDB_UPDATED、AGENT_PROFILE_UPDATED、AGENT_LOCAL_SCAN_REFUSED、AGENT_LDAP_CHANGED、AGENT_STORAGE_CHANGED、AGENT_SUPPORT_BUNDLE_DOWNLOADED,從 6207 起。
  • 保留期 180 天,心跳每日一次 delete where act_time < now-180d。
  • 覆蓋率(D10):core/console/audit_import.py 啟動時把舊 console-audit.jsonl(含 .1)照時間排序一次寫進表,35 個舊動作名對到事件碼、對不到的記 6235;同一交易寫一列 6234 當冪等標記,寫完才把檔改名 .imported;act_time 原樣保留到微秒,主系統以 at 去重不會重複。audit_once 同一 session 同一對象在窗內只記第一筆(只在真的記了才更新時刻)。
  • 匯出(D10):GET /console/api/logs/export 同一組篩選、不分頁、上限 50,000 列,CSV 帶 BOM、以 = + - @ 開頭的儲存格加單引號;守「看日誌」範圍+download-files 兩層;走 file_protect.send_protected(FR-169)。
  • 過渡層:AgentDBLogHandler 只存在因為 0.0.31 的 emit 不過濾且直讀 record.user_uid;檔頭註解只寫這一句與「升 jedi-common ≥1.3.6 即刪」。
  • A3 開工第一件事:實測 QueueListener 執行緒上呼叫 add_system_log,@transaction 能自開 session_scope(session_context 是 ContextVar,新執行緒為空,應可);不行就在 handler 內顯式 with session_scope()。

5.6 語言切換(T6:F1)

Login.vue 的 toggleLocale 抽成 useLocale.js(localStorage 記住,照 theme.js 形狀),殼頂欄與登入頁共用。

5.7 安全規則對照

  • 下載票分用途後,preview 票打 download 端點必 403(A1 突變測試)。
  • 7+2 顆能力點出貨零角色;gen_permission_matrix.py 重產矩陣讓 FE 角色矩陣多一欄「下載/匯出」。
  • 設定帳密:fields 只在這次直推的 request body 過手;主系統 log、稽核 message、回應 body 都不得帶值;帶帳密只走 mTLS 通道;回應比對 Agent 指紋。
  • 所有下載出口走 FR-169 send_protected:主控台檔案、網路除錯報告、診斷包、日誌匯出,主系統 Agent 執行紀錄報告;預覽不加密。
  • 入場券帶 Agent 不認得的範圍時回 AGENT_CONSOLE_TICKET_SCOPE_UNKNOWN(請升級 Agent),不混進「無效或過期」。
  • 稽核 message 經 SecretMaskingFilter;A3 突變:把 filter 拿掉,含 password= 的測試要紅。
  • 日誌端點唯讀、四種範圍任一可看;不回 path/line_no 這種程式座標給使用者。

6. 拆分(27 張實作卡、10 批)

批(Stage)=工作坊同時開幾個小弟一起跑的那一組:同一批不改同一個既有檔、不依賴對方產出。底表在 handoff/cards.py,兩個斷言已過。

登記型檔只讓一張卡碰:manifest.tsv/event_code.py/remote_agent.py adapters(B1、B2、B3 各在不同批);console/routes.py(A6);enroll.py(A7);Shell.vue 與 i18n key(F1 先登記「日誌」項,F3 只新開 view);capabilities.js 由 B1 重產、F2 commit。

套件異動:jedi-file-upload 1.4.0、jedi-remote-agent 1.9.0、jedi-detection 1.7.0、jedi-issue 1.4.0(意見回饋附件認領者),皆已發版、BE 已釘。批 10 與手測直修只動 Agent、BE、FE,沒有新套件版本。

表中 B3/F5 兩列寫的是開卡範圍(派工單+等心跳);行為以 D5 直連為準,見 §5.4。

批 主題 卡 repo reuse/extend/new+理由 改哪些既有檔 新開哪些檔 依賴 驗收
1 下載權限點 A1 jedi-file-upload:簽票分 preview/download 兩種用途、下載端點只收 download 票、宿主可注入下載守門 jedi 套件 extend:UploadFileAccessTokenRoute 加 `?action=preview download(預設 preview,舊前端不壞);FileUploadAdapters加一欄download_guard(宿主注入,None=不擋);UploadFileDownloadRoute` 驗票時只收 action=download 的票;不另開第二條下載端點 jedi_file_upload/api/routes/upload_file_route.py 141(簽票)、160(下載)、jedi_file_upload/plugin/contract.py 125(FileUploadAdapters)、jedi_file_upload/api/guards.py 42(file_access_guard 傳 action) —
1 下載權限點 A2 jedi-remote-agent:主控台票新範圍 download-files(A2b 對到 detection.remote-agent.download-files);AgentEventCode 補本機事件碼;console_event 收 event_code jedi 套件 extend:plugin/contract.py 的 CONSOLE_SCOPE_CAPABILITIES 與 OFFLINE_OPERATOR_SCOPE_CAPABILITIES 各加一列;common/code.py AgentEventCode 從 6207 起加碼;heartbeat_console.ingest_console_events 收的事件多一個 event_code 鍵(沒帶仍照 action 對照,舊 Agent 不壞) jedi_remote_agent/plugin/contract.py 44、52、jedi_remote_agent/common/code.py 89、jedi_remote_agent/app/service/heartbeat_console.py 87 — tests:持有 各模組 download 能力點 的票帶 download-files;離線操作者範圍含它;ingest_console_events 帶 event_code 的事件原碼寫入、只帶 action 的照舊;新碼 6207~ 不與主專案 event_code.py 撞號(開卡已查最大 6206)
1 Agent 日誌 A3 Agent 稽核改走 logging 管線:system_logs(SQLite)+QueueHandler+操作者過濾器+DB handler 過渡子類;41 個呼叫點改用套件 audit() evidence-agent reuse:入口用 jedi-remote-agent common/audit.py::audit(logger, event_name, event_code, **fields);表/DTO/service 用 jedi-common 0.0.31 logger/db_log/system_log;標準庫 QueueHandler+QueueListener。new(過渡層,升 jedi-common 即刪):DBLogHandler 薄子類(只放行帶 event_code、缺欄補 None)。new:ConsoleActorFilter(從 flask.g.console_session 補 user_uid/user_nickname/login_method/ip,背景執行緒補 system)。刪:audit.py 的 jsonl 寫入、ISO 游標、local_rows、REPORTED_ACTIONS/_MASKED_KEYS core/console/audit.py 155(整支改寫:record→委派 audit();pending/ack 改讀表、游標改自增 id)、core/local_db/migrations.py(v3:system_logs 表,欄位照 jedi-common model)、core/app_factory.py 24(logging 接線一處)、core/console/heartbeat.py 51、core/console/{routes,ops_routes,scan_routes,netdiag_routes,tools,files,runs_routes,ldap_routes,local_auth,local_auth_routes}.py、core/{console_allow_cidr,local_state_cli,ldap_source}.py(41 處呼叫改簽名、拿掉 actor 參數) core/audit_pipeline.py(filter、queue、handler 子類、install()) — 登入/設帳密/手動掃描後 sqlite3 data/agent.db 'select event_code,user_name,message from system_logs' 有列且密碼欄為 ***;心跳 console_events 帶 event_code、ack 後游標前進(重啟不重送);背景執行緒事件 user_name=system;把 filter 拿掉 → 測試紅(突變);grep -rn 'console-audit.jsonl' core/ 為 0;寫表失敗只進 app log、請求不失敗;180 天清理在心跳跑一次/天
1 profile 同步 A4 jedi-detection:Agent×profile 取得狀態表、心跳附加欄位 provider、代設帳密的 handover 單(overwrite) jedi 套件 extend:新表 agent_profile_states(agent_id, profile_version_id, sha256, synced_at, error) 照套件既有 migration 形狀;credential_handover_service.start_for_agent 加 tools=/overwrite= 參數(代設走同一支,不另開 service);心跳附加欄位 profiles 的 provider 放套件(主專案接線) jedi_detection/app/service/credential_handover_service.py 91、116、jedi_detection/migrations/(新號) jedi_detection/domain/detection_tools/entity/agent_profile_state_entity.py、jedi_detection/infra/detection_tools/model+repo(agent_profile_state)、jedi_detection/app/service/agent_profile_sync_service.py(current_profiles_for_heartbeat()、record_agent_state()) — DEV 套套件 migration 後 \d 新表有 RLS 四指令與 (agent_id, profile_version_id) 唯一鍵;current_profiles_for_heartbeat() 只回 is_current 版本的 {uid, version, sha256, file_uid};start_for_agent(tools=[x], overwrite=True) 建的單 params 帶 overwrite;既有 handover tests 綠
1 語言切換 F1 主控台殼層:登入後語言切換、總覽「離線登入剩餘天數」卡、選單登記「日誌」分頁(先指向佔位) FE extend:Shell.vue 頂欄把 Login.vue 既有的 toggleLocale 搬成共用(state.js 或新 useLocale.js,localStorage 記住,照 theme.js 形狀);Overview.vue 的 banner 既有 offline_days/offline_login_days 算剩餘天數加一張卡(照 overviewHealth.js 既有卡形狀);登記型檔集中:Shell.vue 選單本卡一次加「日誌」項並佔 i18n key,F3 只新開 view console/Shell.vue 201、console/Login.vue 200(toggleLocale 改呼叫共用)、console/views/Overview.vue 141、console/router.js(logs 路由指向 Placeholder.vue)、i18n zh-tw/en agent-console.json console/useLocale.js — 登入後右上角有地球鈕,切換後重整仍是該語言;總覽有「離線登入還能用 N 天」卡:N=上限−已離線天數、≤0 紅字並寫「只剩本機管理員與 AD 可登」、在線時卡不顯示;側欄多「日誌」項(點進去是佔位);grep border-left console/ 為 0
2 下載權限點 A5 Agent 端:檔案與報告下載只認 download-files 範圍、檔案頁補預覽端點、不能 inline 的格式不給看 evidence-agent extend:files.py 的 files_download 改 require_session('download-files')、新增 files_preview 走既有 inline_policy(照 runs_routes 預覽端點形狀,不複製);netdiag_routes 報告下載同樣改範圍;local_login.py 認得新範圍(票上帶不認得的範圍整張拒收,先認再用) core/console/files.py 169、core/console/netdiag_routes.py(report 下載)、core/console/runs_routes.py(報告下載)、core/console/local_login.py(範圍白名單) A2、A3 curl 8444:票無 download-files 時 GET /console/files/<uid>/download 403、/preview 200 且 HTML 回應帶 Content-Security-Policy: sandbox;svg 預覽 415;主系統票帶 download-files 時 200;tests 含突變(把範圍檢查拿掉要紅)
2 Agent 日誌 A6 Agent 日誌查詢 API:依人/事件碼/日期分頁讀本機 system_logs evidence-agent reuse:jedi-common system_log_service.get_system_log_list(curr_page, page_size, <strong>filters) 已有分頁與篩選,端點只做參數解析與序列化;守門 require_session(('configure','files','run-local-scan','net-diag')) 唯讀;登記型檔**:routes.py 只加一行掛 blueprint core/console/routes.py 212(掛一行) core/console/log_routes.py A3 GET /console/logs?page=1&size=50&user=local-admin&event_code=6093&from=…&to=… 回 {items, total},欄位 act_time/user_name/event_code/message/method;無 session 401;message 裡沒有密碼(A3 已遮)
2 profile 同步 A7 Agent profile 同步:心跳收 profiles 清單比對本機快取、背景補拉、回報每支狀態;主控台「立即檢查」 evidence-agent reuse:取檔與快取用既有 core/profile_cache.py(_populate_cache、is_cache_valid),不另寫下載;流程照 core/vulndb_sync.py 形狀(observe→比對→背景執行緒→回報→冷卻),request_check() 同款;登記型檔:enroll.py 掛一個 observer+payload 加一欄 core/enroll.py 287(payload 加 profiles)、362(observer 一行)、core/console/ops_routes.py(POST /console/profiles/check 照 vulndb/check 形狀) core/profile_sync.py A4 心跳回應帶 profiles:[{uid,version,sha256,file_uid}] 時,本機沒有的在背景拉、拉完 marker sha256 一致;下次心跳 payload `profiles:[{uid,version,sha256,state:ok
2 代設工具帳密 A9 Agent 收代設帳密:handover 單帶 overwrite 時蓋掉本機既有值 evidence-agent extend:credential_handover.store 讀 task.get('overwrite'),True 走 credential_store.put(既有)而非 put_if_absent;回報 summary 多 overwritten core/credential_handover.py 20 A4 tests:overwrite=False 既有不蓋(stored/skipped 同今天);overwrite=True 蓋掉並回 overwritten;log 只帶工具代號;稽核 AGENT_CREDENTIAL_SET method=handover
2 下載權限點 B1 主系統:EventCode 鏡像 6207~6217、console sink 收 event_code(下載守門改由 B4 分模組做) BE extend:migration 照 2026-10-27-fr166-agent-standalone-capabilities.sql 形狀但不綁預設角色;core/plugins/file_upload.py adapters 多注入 download_guard=require_capability('file.download');core/plugins/remote_agent.py _ADMIN_CAPABILITIES 的 RemoteAgentLocalRunReportRoute.get 改 各模組 download 能力點;common/enum/event_code.py 鏡像 A2 新碼;core/plugins/_agent_console_audit.py 收 event_code;跑 scripts/deliverables/gen_permission_matrix.py 重產 FE capabilities.js(FE 檔由 F2 commit) core/plugins/file_upload.py 351、core/plugins/remote_agent.py 186、common/enum/event_code.py 198、core/plugins/_agent_console_audit.py 25、scripts/sql/manifest.tsv scripts/sql/2026-10-29-fr166-1-file-download-capability.sql A1、A2 DEV 套 migration:select count(*) from role_capabilities rc join capabilities c on c.id=rc.capability_id where c.name like '%.download' 為 0(Administrator 靠 *.*.*);無此能力點的帳號 GET /file/access-token/<uid>?action=download 403、?action=preview 200;Agent 執行紀錄報告下載端點同樣 403/200;BE 重啟實打;守衛測試綠;回寫明講「出貨基線待重產」
4 profile 同步 B2 主系統 profile 同步接線:心跳附加 profiles、心跳回報入表、profile 頁查每台 Agent 取得狀態、補更新端點 BE extend:core/plugins/remote_agent.py heartbeat_extras 加 A4 的 provider(照 _agent_vulndb.py 形狀新開 _agent_profile_sync.py);心跳 payload profiles 入表走套件 service;新端點 GET /detection-profiles/<uid>/agents(每台 Agent:version、sha256、synced_at、error)與 POST /remote-agents/<uid>/profiles/check(經心跳要求 Agent 立即檢查,照 restart 旗標形狀);守門 detection.profile.read/detection.remote-agent.configure core/plugins/remote_agent.py 319(heartbeat_extras 加一項)、186(兩端點登記) core/plugins/_agent_profile_sync.py、api/remote_agent/routes/agent_profile_sync_route.py A4、A7 DEV:上傳新版 profile → 123 Agent 下次心跳拉到 → agent_profile_states 有列、GET …/agents 看得到 ok;按補更新 → 心跳回應帶旗標 → Agent 清冷卻;BE 重啟實打;守衛測試綠
3 下載權限點 A1b jedi-file-upload:登記表加 FILE_ACTION_DOWNLOAD、拆掉全局 download_guard 插槽 jedi 套件 extend:domain/ports.py 多一個動作常數;FileUploadAdapters 拿掉 A1 的全局插槽(守門責任下放認領者) jedi_file_upload/domain/ports.py、plugin/contract.py、api/routes/upload_file_route.py A1 tests:download 票簽發前呼叫認領者的 assert_can_access(file, FILE_ACTION_DOWNLOAD);沒有認領者的檔 404
3 下載權限點 A2b jedi-remote-agent:download-files 範圍對到 detection.remote-agent.download-files、CAPABILITIES 申報它 jedi 套件 extend:plugin/contract.py 對照表改一列、能力點清單加一顆(含 home_route) jedi_remote_agent/plugin/contract.py A2 tests:持該顆者票帶 download-files;守衛測試「套件宣告的能力點都在 seed」綠
6 下載權限點 B4 主系統:7 顆客戶資料下載能力點 migration、9 類認領者下載時看自己那顆、上傳者本人免、範本 SSP 佐證只鎖下載 BE new:core/plugins/_file_ownership.py::assert_download_capability 一支共用;extend:9 支認領者各加一行呼叫;event_code.py 鏡像 6218~6227 core/plugins/file_upload.py、各模組 ownership 檔、common/enum/event_code.py、scripts/sql/manifest.tsv scripts/sql/2026-10-29-fr166-1-download-capabilities.sql A1b、A2b、B1 無能力點帳號對他人證據 ?action=download 403、preview 200;對自己上傳的 download 200;role_capabilities 對 7 顆為 0
6 下載權限點 A2c jedi-remote-agent:AgentEventCode 補登 6218~6227 十個 Agent 本機事件碼 jedi 套件 extend:common/code.py 加十列(A3 暫定的碼正式登記) jedi_remote_agent/common/code.py A3 tests:碼不撞、主專案鏡像一致
7 下載權限點 B5 主系統匯出端點補 export 能力點:稽核計畫、POA&M、風險總表、原始碼掃描檔原檔 BE extend:各端點 @require_capability("<模組>.export");migration 2 顆 api/project/routes/ap_export_route.py、api/project/routes/poam_objective_route.py、api/governance/routes/risk_register_route.py、manifest.tsv scripts/sql/2026-10-29-fr166-1-export-capabilities.sql B4 無 grc.risk.export 帳號 POST /governance/risks/export 403 GRC_403022;管理員 200 xlsx
8 下載權限點 F6 主站匯出鈕看各自 export 能力點(稽核計畫/POA&M/風險總表) FE extend:三處匯出鈕加 v-can;capabilities.js 重產 三個 view、src/config/capabilities.js B5 無能力點看不到匯出鈕;有則照常
7 下載權限點 F2 全系統下載鈕分模組看各自 download 能力點、上傳者本人免:主站 10 處+Agent 執行紀錄頁+主控台檔案頁改預覽、執行紀錄對話框下載鈕;commit 重產的 capabilities.js FE extend:主站 signedDownloadUtil.fileDownloadUrl 簽票帶 action=download、filePreviewUrl 帶 preview;10 處下載鈕統一 v-can="各模組 CAP.<模組>_DOWNLOAD"(PrimeVue Menu 項用既有 useMenuItemsCan);主控台 Files.vue 下載鈕改看 me.scope 含 download-files、加「預覽」鈕 reuse ReportPreviewDialog(svg/xhtml 不給看、顯示「此格式僅能下載」);Runs.vue 對話框下載鈕同範圍;runs.js canDownload 改認 download-files src/utils/signedDownloadUtil.js 44、src/components/grc/JobExecutionDrawer.vue 1434(超標 3179 行:只改一行加 v-can)、src/components/grc/evidence-view/RoundEvidenceView.vue 80、src/views/feedback/system/SystemFeedbackView.vue 60、src/views/feedback/system/SystemFeedbackForm.vue 240、src/views/evidence-classification/components/EvidenceFilePreviewDialog.vue 135、src/composables/useSbomViewer.js 26、src/composables/useScanFileDownload.js 19、src/views/remote-agent/AgentLocalRunsView.vue 213、src/config/capabilities.js(B1 重產,本卡 commit)、console/views/Files.vue 90、console/views/Runs.vue 185、console/runs.js 82 B1、A5 無 各模組 download 能力點 的角色:主站 10 處下載鈕全部不見、預覽照常;主控台檔案頁只有預覽、報告對話框無下載;有能力點的角色全部回來;`grep -rn 'DOWNLOAD_FILE|openFileDownload|fileDownloadUrl' src/
4 Agent 日誌 F3 主控台「日誌」分頁:依人/事件/日期篩,欄位比照主站使用者日誌頁 FE reuse:表格與篩選列照主站 src/views/action-log/UserLogs.vue 的欄位與寫法,分段用 SegmentButtons;資料來源 A6 端點;事件碼顯示用既有 i18n lang.event_code.*(grep 確認主站有);只新開 view,不碰 Shell.vue(F1 已登記) console/router.js(logs 指向新 view) console/views/Logs.vue、console/logsApi.js A6、F1 分頁對 123 實際有列;篩人/事件/日期區間有效;message 不顯示密碼;grep border-left 為 0;表格一格一值
5 代設工具帳密 B3 主系統代設 Agent 工具帳密端點:在線才收、建 overwrite handover 單、主系統不落地;檢測工具頁 route 翻回並改名 BE extend:新端點 POST /remote-agents/<uid>/tool-credentials {tool, fields} 走 A4 start_for_agent(tools=[tool], overwrite=True),fields 只進派工單 payload(既有 build_payload 從雲端密文讀 → 本卡加「一次性 fields 直接進單」分支,不存 tenant_detection_tool_configs);Agent 離線(is_online 為 false)回 409 GRC_409xxx;守門 detection.remote-agent.configure;稽核 AGENT_CREDENTIAL_SET method=remote;migration 把 tool-plugin-manage 翻回 enable=1、名稱改「Agent 掃描工具設定」、綁 configure 能力點 core/plugins/remote_agent.py 186(端點登記)、common/code/grc_error_code.py、scripts/sql/manifest.tsv api/remote_agent/routes/agent_tool_credentials_route.py、scripts/sql/2026-10-29-fr166-1-tool-page-agent-credentials.sql A4、A9 在線 Agent:POST 後 123 下次心跳收到單、本機值被蓋、回執 overwritten;離線 Agent 409;select * from tenant_detection_tool_configs 沒新增列;system_logs 有 6086 method=remote 且 message 無帳密;選單翻回且改名;守衛測試綠;回寫明講「出貨基線待重產」
5 profile 同步 F4 主站 profile 頁:每個版本展開「Agent 取得狀態」表+「補更新」鈕 FE extend:ScanToolResourcesView.vue(82 行)加一個子元件掛點;新元件 ProfileAgentStates.vue 讀 B2 端點,表格一格一值(Agent 名、版本、同步時間、狀態 Tag),補更新鈕 v-can="CAP.DETECTION_REMOTE_AGENT_CONFIGURE" src/views/plugin/ScanToolResourcesView.vue 82、src/config/api/api.js、i18n src/views/plugin/components/ProfileAgentStates.vue B2 上傳新版後表格顯示 123 從「待更新」變「已取得」;按補更新有 toast、Agent 下次心跳拉;離線 Agent 顯示「離線」不報錯;grep border-left 為 0
8 代設工具帳密 F5 主站「Agent 掃描工具設定」頁:選 Agent、選工具、填帳密、送出;離線直接說連不上 FE extend:ToolPluginManage.vue 766 行超標:只留殼與路由,新邏輯進新元件 AgentCredentialSetter.vue;工具表單 reuse 既有 tool_schemas/config_field_schema 的表單渲染(grep 主控台 SecretForm.vue 與主站既有工具表單,二擇一抽共用、不第三份);送出後顯示「已送出,等 Agent 下次心跳(最多 N 秒)」、輪詢 handover 進度(既有 credential_handover 欄) src/views/plugin/ToolPluginManage.vue 766(淨減)、src/config/api/api.js、i18n、src/router(頁名) src/views/plugin/components/AgentCredentialSetter.vue B3、F2 選離線 Agent 送出 → 409 toast「這台現在連不上」;在線送出 → 幾十秒內狀態變「Agent 已收」;主系統 DB 無帳密;wc -l ToolPluginManage.vue 比 766 小;表單不自刻第三份;grep border-left 為 0
9 文件 D1 SPEC 與手冊補到現況:下載權限點、profile 同步、代設帳密、Agent 日誌、剩餘天數、語言切換;FE 角色矩陣多一顆 BE extend:docs/spec-site/current/evidence/agent-console.md、remote-agent-manage.md、scan-tool-resources(profile 頁)對應頁各加段+檔頭變更紀錄一行;手冊 docs/user-manual/agent/13-standalone-operation.md 13.x 補操作;docs/claude/reference_permission_system_sop 若列能力點清單補 各模組 download 能力點 docs/spec-site/current/evidence/agent-console.md、docs/spec-site/current/evidence/remote-agent-manage.md、docs/user-manual/agent/13-standalone-operation.md F2、F3、F4、F5 六個主題各有一段;grep -n '原本|第 [0-9] 棒|這次' 命中 0;SPEC 頁 build 過
10 檢測基準+AD 鎖 A10 Agent:總覽「檢測基準」摘要與燈;離線超過天數 AD 登入一併鎖(D9) evidence-agent extend:profile_sync.summary()、overview._profiles_lamp;local_auth.operator_session_valid 拿掉 AD 例外 core/profile_sync.py、core/profile_cache.py、core/console/overview.py、core/console/local_auth.py、core/heartbeat_status.py — AD 登入過期後 401、已登入被踢;local-admin 照登;總覽 info.profiles 與 profiles 燈有值
10 檢測基準卡 F7 主控台:總覽「檢測基準」卡+立即檢查+燈;離線鎖定時 AD 分頁一起灰 FE extend:useVulndbCheck 泛化成 useSyncCheck 兩張卡共用 console/views/Overview.vue、console/Login.vue、console/opsApi.js、console/overviewHealth.js、i18n console/useSyncCheck.js(改名) A10 卡片顯示 N 天前/M 支 K 失敗;「主系統未提供」;文案不再說 AD 不受限
10 Agent 日誌 A11 Agent:日誌覆蓋率——舊 jsonl 一次匯入、預覽記 6230、進頁/開工具表單/開執行紀錄詳情 6231~6233(D10) evidence-agent new:audit_import.py;extend:audit.audit_once、各分頁 API 掛 page_viewed core/console/audit.py、files.py、tools.py、runs_routes.py、scan_routes.py、log_routes.py…、core/app_factory.py core/console/audit_import.py — 123 升級後多 61 筆舊紀錄、jsonl 改名 .imported;走一圈每步一筆
10 工具設定頁 F8 主站「Agent 掃描工具設定」頁重做:左選 Agent、右列工具卡、離線整頁鎖、拆舊目錄 FE new:AgentPicker、AgentToolCard、EnabledToolsDialog、agentToolCards.js;刪 DetectionToolCards.vue 等五檔 AgentCredentialSetter.vue、ToolPluginManage.vue、i18n 見左 — 版面照雛形;離線鎖;舊目錄不見

7. 端到端驗收

# 步驟 期望
1 用沒有任何 download/export 能力點的角色登主站 他人上傳的證據、SSP 佐證、SBOM、掃描檔、意見回饋、Agent 執行紀錄的下載鈕全部不見,自己上傳的仍可下載;預覽照常;稽核計畫/POA&M/風險總表匯出鈕不見
2 同角色跳轉 Agent 主控台檔案頁 只有預覽;svg 顯示「此格式僅能下載」且無下載鈕
3 給該角色對應模組的 download/export 顆 該模組的下載鈕回來;直接打 ?action=download 簽票 200;拿 preview 票打下載端點 403 用途不符
4 123 拔網路 總覽「離線登入還能用 N 天」卡出現,N=租戶設定−已離線天數;超過天數後離線密碼與 AD 分頁一起灰、AD 登入 401,本機管理員照登
5 主站上傳新版 profile profile 頁該版本展開:123「待更新」→ 下次心跳後「已取得」;按補更新 → Agent 立即檢查;主控台總覽「檢測基準」卡顯示幾天前更新
6 主站「Agent 掃描工具設定」對 123 的 Nmap「⋮ → 設定」、儲存、再「⋮ → 測試連線」 儲存當場成功、卡片翻成已設定;欄位不合當場報哪一欄;測試連線結果寫進「最後測試」;123 主控台工具頁看到同一設定時間;主系統 DB 無帳密
7 123 離線時打開那台 右欄整頁鎖並說明連不上;連回 30 秒內自動解鎖
8 做完 1~7 開主控台「日誌」 每一步都有列(含預覽、進頁、開工具表單)、操作者正確、密碼欄 ***;升級前的舊紀錄也在;「匯出 CSV」跳一次性密碼、下載加密檔;主站使用者日誌頁看到回傳的那幾筆
9 登入後切英文、重整 仍是英文
10 主系統升級後、Agent 未升時從主系統開主控台 登入頁說「這台 Agent 版本比主系統舊,請升級」,不是「入場券無效」

8. 風險與陷阱

  • 升級順序 Agent 先:主系統簽的票帶 download-files,舊 Agent 整張拒收,持有者登不進主控台。與 FR-166 P3-0b 同一個坑。
  • 票改用途會不會壞第三方呼叫:action 預設 preview,只有 FE 的 fileDownloadUrl 一處改帶 download;其他簽票呼叫者行為不變。
  • profile 的 cache 路徑是凍結契約 /data/content/cache/:正式版靜 compose 掛可寫 volume;用源碼直接跑的開發機要自己把這個目錄給 Agent 帳號寫入權,否則每支 profile 回報 PermissionError。
  • QueueListener 執行緒上的 session scope:A3 第一件事實測;不行就 handler 內顯式開 scope。
  • SQLite 寫表失敗不得讓請求失敗:DBLogHandler.emit 整支 try/except 走 handleError,1.3.6 有、0.0.31 沒有,子類要補。
  • 設定帳密走直推:主系統要連得到 Agent 的 8443。落地版同內網沒問題;SaaS 版要客戶開放 8443 進來,否則設定、測試連線、取證據檔一起不能用(同一個前提)。
  • 主系統送來的欄位一定照工具 schema 驗:曾有人直打 API 存進錯的欄位名,卡片顯示已設定、掃描時才報缺少憑證。
  • profile 冷卻:拉失敗每次心跳重拉會把頻寬吃光(vulndb 已踩過),冷卻照抄。
  • JobExecutionDrawer.vue 3179 行:F2 只改一行加 v-can,不碰其他。

9. 這份文件的定位

設計決策與拆卡;開工後現況看 SPEC 與手冊;每棒做了什麼看 handoff/FR-166.1-LOG.md。