主系統不再連 Agent;Agent 常駐一條長輪詢向主系統取指令,同步問答(測試連線、證據檔、查日誌)走同一條加「等回答插槽」;直推程式留著用開關關掉、客戶不用開 8443;心跳維持只表存活。
🔴 本版已凍結。現行版本是 design-v1.1.md(無人值守調整:批次 7→6、P-0 判據寫死、驗收改本機模擬)。
| 項目 | 內容 |
|---|---|
| 狀態 | 定案(決策者 2026-11-07 逐項裁示),待開卡 |
| 日期 | 2026-11-07 |
| 討論稿 | discussion.md(D1~D8 的推導與選項比較) |
| 推翻 | FR-170 §3.x「落地版同內網直推一定通」前提;FR-171 A 指令通道(D2、D3、D11) |
| 保留 | FR-171 B 設定通道、C 自動換證、K 金鑰登記簿、E 事件通道、DPoP;11-01「心跳只表存活」裁示 |
| 拆卡底表 | handoff/cards.py(13 張 7 批,python cards.py 驗批) |
| 版本 | 日期 | 內容 |
|---|---|---|
| v1.0 | 2026-11-07 | 定案 |
客戶網路確定不讓主系統連進 Agent 主機。現況八種「主系統主動連 Agent 8443」的功能在這種客戶一個都送不到:派工、重啟、升級、設定變更通知、立即換證、取消檢測(六種指令);測試連線與工具帳密、證據檔四操作與遠端查日誌(同步問答)。心跳補推也是連 8443,救不了。
做完後的狀態:
| 項目 | 現況 | 目標 |
|---|---|---|
| 主系統 → Agent 的連線 | 直推 8443(mTLS+JWT) | 零。主系統不主動連 Agent |
| Agent → 主系統 | 心跳(300 秒)、事件、設定拉取、ack/結果 | 同上,加一條常駐長輪詢 GET /agents/commands?wait=30 |
| 六種指令怎麼到 Agent | 直推,打不通等心跳補推 | 主系統寫 DB 正本+敲 Redis 鈴 → 長輪詢立刻回 → Agent 做 |
| 同步問答(7、8) | 主系統直連當場拿回答 | 主系統排請求吊著等 → Agent 長輪詢取走做 → 回覆上來 → Redis 喚醒等著的那條 → 回瀏覽器 |
| 證據檔 bytes | 主系統直連 Agent 串流 | 經主系統暫存中轉(Agent 送上來/Agent 來拿),取走即刪 |
| 直推程式 | 主路徑 | 留著,主系統與 Agent 各一個開關預設關 |
| Agent 8443 | 六個指令端點+health | 照起、只剩 /health//agent-info;客戶不用開埠 |
| 心跳 | 只表存活 | 不變;長輪詢不算心跳,離線判定不動 |
| gunicorn | sync worker |
gthread 16 執行緒+長輪詢/等回答各配額 8 條(批 0 驗過才翻) |
| 舊 Agent | — | 全部重裝,不留相容路徑 |
整案一句話:Agent 永遠是發起方;主系統有事就讓掛著的那條請求立刻回去。
| 批 | 做什麼 | 卡 | 完成怎麼判定(決策者檢查法) |
|---|---|---|---|
| 1 | 先驗 gunicorn 撐不撐得住:sync 重現卡死、gthread+配額證明一般 API 不受影響 |
P-0 | 看證據檔三組數字:sync 吊 2 條 health p95 > 25 秒;gthread 吊 20 條 p95 與沒吊時差 < 50 ms。通過走長輪詢,不通過退定時輪詢(其餘卡不變) |
| 2 | 地基三張:契約、Agent 長輪詢元件、主系統長輪詢端點 | F-1 F-2 F-3 | 123 Agent 裝 F-2 版、DEV 起 F-3 工作區的主系統:DB 插一張 pending 單,Agent 主控台 3 秒內看到開跑 |
| 3 | 環境卡:主系統釘新版套件、gunicorn 翻預設、agent_requests 表 |
E-1 | USE_GUNICORN=true 起 log 印 gthread;\d compliance.agent_requests 有表 |
| 4 | 等回答插槽(跨 worker 喚醒、配額、逾時、過期) | A-1 | gunicorn 2 worker 實打:兩條 ask 各在自己的 worker 醒來;殺 Redis → 30 秒 504 不 500 |
| 5 | 指令六種(主系統側)、回答九種(Agent 側)、jedi-detection 三 client 換底 | A-2 A-3 A-4 | 123 Agent 主機 iptables 擋 8443 入站:派工、重啟、升級、改授權清冊、立即換證、取消檢測各 3 秒內反應;按「測試連線」2 秒內看到結果 |
| 6 | 證據檔四操作、遠端查日誌、FE 連線欄 | B-1 B-2 B-3 | 同上防火牆:證據存 Agent 的租戶上傳/預覽/下載/轉 PDF/刪除都正常、/tmp/agent-outbound 取走後為空;支援頁查 Agent 日誌查得到;連線欄只剩在線/離線 |
| 7 | 守衛測試、FR-170/171 指路行、手冊與資安報告 | C-1 | 守衛三支綠;手冊 grep 不到「8443 要對主系統開放」 |
依賴:批 1 的結論決定 E-1 翻不翻 gunicorn 預設;批 2 三張互不依賴;批 5 三張分屬三個 repo。決策者功課:批 1 驗完看一眼數字拍板 D2;批 5 驗收前把 123 Agent 主機 8443 入站擋掉(或告訴首腦擋)。
| # | 決策 | 定案 | 理由 | 被排除 |
|---|---|---|---|---|
| D1 | 長輪詢一條請求吊多久 | 30 秒 | 低於多數 proxy/LB 的 60 秒閒置上限;GitHub runner 50 秒、SQS 上限 20 秒,取中間;gunicorn timeout 120 秒不用動 |
5 秒(Agent 一直重掛、log 塞滿);60 秒(撞 proxy 閒置切線) |
| D2 | 長輪詢吊住 gunicorn worker | 批 0 驗過才定。通過→gthread 16 執行緒+配額(長輪詢 ≤ 8、等回答 ≤ 8,超過立刻回不吊)+每台 Agent 只准一條(第二條踢第一條) |
出貨 sync worker 數=nproc 封頂 4,1 台 Agent 就佔 1 個 worker、按「測試連線」再佔 1 個,2 核機當場吊死。三道:容量/防線/衛生。本機 DEV 看不出來(dev server 本來多執行緒) |
另起 RUN_MODE=agent-poll 獨立進程(多一個行程進 compose/installer/升級鏈,幾十台 Agent 不值得;上百台再換);不驗直接上(多執行緒下 DI/session 沒實打過) |
| D2 退路 | 批 0 不通過 | 退定時輪詢:同一支 GET /agents/commands 不帶 wait、每 10~15 秒問一次 |
兩條路只差端點吊不吊,Agent 側不改一行;代價同步問答最壞多等一個間隔 | — |
| D3 | 指令與請求的過期 | agent_requests 預設 5 分鐘過期,過期的 Agent 拿不到、主系統標 expired;工單/重啟/升級不過期 |
Agent 離線一週回來拿到一週前的「測試連線」沒意義;派工與升級要保留。取消檢測過期沒關係,還在跑就再按 | 全部不過期(Agent 回來做一堆沒人收的事);全部過期(派工會丟) |
| D4 | 證據檔怎麼拿 | A 經主系統中轉:下載排 send_file Agent 上傳到主系統暫存再串給瀏覽器;上傳主系統收檔暫存排 fetch_file Agent 來拿;暫存 /tmp/agent-outbound/<request_id>、取走即刪、孤兒 1 小時清 |
任何網路都通。代價:大檔等待約翻倍;暫存短暫落主系統磁碟(FR-039「不落雲端」是 SaaS 承諾,落地版磁碟是客戶的)。若客戶在意「磁碟曾經有過」,暫存加密另開卡 | B 瀏覽器拿票直連 Agent 8444(零暫存、單趟,但前提與 D5 同,等 D5 有答案再看) |
| D5 | 源碼包瀏覽器直傳 8444 | 擱置,本案不動 | 那是瀏覽器連 Agent,不是主系統連 Agent;防火牆擋的是主系統↔︎Agent | — |
| D6 | 主系統直推程式 | 留著、開關關:AGENT_DIRECT_PUSH_ENABLED 預設 False;AgentCommandService.send() 開頭擋;AgentDataPlaneClient 與主系統 mTLS client 憑證簽發留著 |
決策者「怕未來又說要開」。未來要開=主系統改一值、Agent 改一值、客戶開埠,程式不改。實查 8444 上傳票簽發在 agent_ticket_service.py 與它無關 |
整支刪(留殼怕被當成還能直推——用守衛測試擋) |
| D7 | Agent 8443 | 照起、六個指令 blueprint 用開關 data_plane_commands_enabled 預設不掛;只剩 /health、/agent-info;客戶不用開埠 |
同 D6。8443 對外通不通功能完全不受影響;安裝手冊刪「8443 要對主系統開放」;資安報告 8443 仍列、註明「預設只有健檢」 | 改聽 localhost(未來要開又得改);整個收掉 |
| D8 | FR-170/171 文件 | 各檔頭加一行指向本案,不改 v1.0 內文 | 「定稿後不改 v1.0」規範 | 改寫內文 |
| — | 舊 Agent | 全部重裝,不留直推→長輪詢的兩段升級 | 決策者裁「舊的先不管」;123、POC 本案出貨後重裝 | 主系統先收長輪詢暫不刪直推→Agent 升→再關直推(多一棒) |
%%{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
autonumber
participant A as Agent command_pull
participant M as 主系統 /agents/commands
participant DB as DB 正本
participant R as Redis
participant U as 管理員
A->>M: GET /agents/commands?wait=30(通行證+DPoP)
M->>DB: 查這台待辦(pending 單/restart/update/agent_requests waiting)
Note over M: 沒有 → waiter.wait(uid, 30) 訂閱 agent:<uid>
U->>M: 按「派工」
M->>DB: 寫正本(after_commit)
M->>R: publish agent:<uid>
R-->>M: 喚醒
M->>DB: 再查一次
M-->>A: 200 {commands:[{kind:"task", task_uid, payload}]}
A->>A: 分派表 → task_intake → task_queue.submit
A->>M: POST /agents/tasks/<uid>/ack(既有)
M->>DB: status=dispatched
A->>M: GET /agents/commands?wait=30(立刻再掛)
Note over M,A: 30 秒沒事 → 204 → Agent 立刻再掛
正本仍在 DB:工單表、restart_requested、rollout 表、agent_requests。Redis 只是「醒來查表」的鈴;鈴沒響最壞 30 秒逾時後 Agent 再掛一條時查表也會查到。沒有第二個佇列(FR-171 D3 原則不變)。
防撞:task uid 冪等、ack 即 dispatched、Agent 本機單執行緒佇列(沿用 FR-171 三道);restart 被 Agent 取走時主系統立刻清 restart_requested(不然每條長輪詢都回 restart)。
%%{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
autonumber
participant U as 瀏覽器
participant M as 主系統 worker X
participant R as Redis
participant M2 as 主系統 worker Y
participant A as Agent
U->>M: POST 測試連線 {agent_uid, target}
M->>M: ask():agent_requests(kind=probe, waiting, expires=+5m)
M->>R: publish agent:<uid>;subscribe reply:<request_id>
Note over M: 吊著等,最多 30 秒(配額 ≤ 8 條,滿了 503)
A->>M2: GET /agents/commands(掛在 worker Y)
M2-->>A: {commands:[{kind:"probe", request_id, payload}]}
A->>M2: POST /agents/replies/<id> {status:"taken"}
A->>A: 跑 probe
A->>M2: POST /agents/replies/<id> {status:"done", result}
M2->>M2: agent_requests done、result 落 DB
M2->>R: publish reply:<request_id>
R-->>M: 喚醒
M->>M: 讀 agent_requests.result
M-->>U: 200 {reachable: true}
Redis 是為了跨 gunicorn worker:瀏覽器吊在 worker X、Agent 回覆打到 worker Y,進程內 Event 喚不醒對方。結果本身落 DB,Redis 只傳「好了」。
逾時:30 秒沒等到回 504 GRC_504_AGENT_NO_REPLY,agent_requests 留 waiting,Agent 晚到的回覆照樣落 DB。taken 與 failed:Agent 拿到要回答的先回 taken(主系統據此分辨「沒拿到」與「在做」);做失敗回 failed reason=,主系統立刻喚醒瀏覽器回錯誤,不讓它等到逾時。
| 操作 | 流程 |
|---|---|
| 上傳到 Agent | 主系統收檔 → spool.save(request_id) → ask(fetch_file, {request_id, name, sha256}) → Agent GET /agents/files/<request_id> 下載、驗 sha、FileUploadService.save → reply done {file_uid, sha256} → 主系統寫參照 row、刪暫存 |
| 下載/預覽 | ask(send_file, {file_uid}) → Agent 讀本機、POST /agents/replies/<id> multipart → 主系統 spool.save → 喚醒 → spool.open 串給瀏覽器 → 讀完刪 |
| 轉 PDF | 同下載,kind send_pdf,Agent 側先走既有轉檔 |
| 刪除 | fire(delete_file, {file_uid}),不等回答 |
錯誤碼不變:504→FILE_AGENT_UNREACHABLE、Agent failed→FILE_AGENT_BAD_RESPONSE(FE 依碼顯示)。
GET /api/1.0/agents/commands?wait=30 → 200 CommandsResponse | 204
POST /api/1.0/agents/replies/<request_id> body Reply(JSON)或 multipart(file + Reply 欄位)
GET /api/1.0/agents/files/<request_id> → bytes(Agent 來拿主系統暫存的檔)
Command {kind, request_id?, task_uid?, payload, expires_at?}
CommandsResponse {commands: [Command], server_time_utc}
Reply {status: taken|done|failed, result?, reason?}
十三個 kind:
| kind | 誰寫正本 | 要回答 | Agent 接哪支 |
|---|---|---|---|
task |
agent_tasks(既有) |
ack(既有端點) | task_intake → task_queue.submit |
restart |
restart_requested(既有) |
否 | ops.request_restart |
update |
rollout 表(既有) | 否(升級事件既有) | update_sync.trigger |
config_changed |
agent_requests |
否 | config_pull.request() |
credentials_renew |
agent_requests |
否 | cert_renew.request_now() |
detection_cancel |
agent_requests |
否 | scan/app/cancel.py |
probe |
agent_requests |
是 | scan/app/probe.py |
credentials_set |
agent_requests |
是 | scan/app/credential_store |
fetch_file |
agent_requests |
是 | files/app/blob_ops.fetch |
send_file |
agent_requests |
是(multipart) | files/app/blob_ops.send |
send_pdf |
agent_requests |
是(multipart) | files/app/blob_ops.send_pdf |
delete_file |
agent_requests |
否 | files/app/blob_ops.delete |
query_logs |
agent_requests |
是 | platform/audit_log.list_logs |
既有直推路徑常數與 body dataclass 不刪(D6),檔頭註解改「直推預設關,長輪詢是主路徑」。
套件只做端點與組裝,不認識 Redis、不建表,四個 port 由宿主注入(app/ports/command_channel.py):
| port | 方法 | 主系統實作(A-1) |
|---|---|---|
waiter |
wait(uid, timeout) -> bool/notify(uid)/preempt(uid) |
infra/remote_agent/redis_command_waiter.py:Redis pub/sub + 本地 threading.Event 鏡像;長輪詢配額 ≤ 8 |
spool |
save(request_id, stream)/open(request_id)/discard(request_id) |
infra/remote_agent/file_spool.py:/tmp/agent-outbound/ |
command_sources |
restart_for(agent) -> mode?/update_for(agent) -> info?/requests_for(agent) -> [Command] |
包既有 restart_requested、rollout_service().update_for_agent、agent_requests waiting 未過期列 |
on_reply |
(request_id, status, result) |
agent_request_service.reply |
端點組裝順序:pending 且 _due 的工單(搬 redispatch_pending 的查單邏輯)→ restart → update → agent_requests;一次最多 20 條;有東西立刻回,沒有 waiter.wait 後再查一次;同台第二條進來 preempt 讓第一條回 204。
app/remote_agent/service/agent_request_service.py 三個方法,7a、7b、8a、8b 都只呼叫這三個,不各寫等待邏輯:
ask(agent, kind, payload, timeout=30):寫表(expires_at=+5 分)→ notify → subscribe reply:<id> 吊著 → 喚醒讀 DB result → 回;配額滿 503 GRC_503_AGENT_BUSY;逾時 504 GRC_504_AGENT_NO_REPLY。fire(agent, kind, payload):寫表+notify,不等。reply(request_id, status, result):落 DB+publish reply:<id>;taken 只改狀態不喚醒。背景排程(core/scheduler_agent.py):expire_requests 每分鐘、reap_orphans 每小時。
agent_requests 表(E-1)compliance.agent_requests:id/uid/agent_id FK/tenant_id/kind/payload jsonb/status waiting|taken|done|failed|expired/result jsonb/reason/expires_at/taken_at/done_at/created 系列。索引 (agent_id, status, expires_at)。GRANT cm_app、RLS 照 agent_tasks。主線 phase=active → 出貨基線待重產。
為什麼只加一張表:取消、換證(沒正本)與測試連線、帳密、證據檔、查日誌(要回答)性質相同——「一次性、短命、要麼做完要麼過期」;工單/重啟/升級各有自己更複雜的狀態機,硬併等於重寫。
guidant_agent/sync/app/command_pull.py,照 config_pull.py 的 WorkerComponent 骨架:
dict[kind -> handler],不是 if 鏈;F-2 接四個(task/restart/update/config_changed)、其餘留 no-op 給 A-3 填。taken 再做,做完 done,失敗 failed reason=。task → 不進佇列、reply failed reason=upgrading(現況直推 409 語意)。reply_sender.py,送不到進 event_sender 同一條 outbox(新 kind reply)。last_seen。抽 app 層:cmd_tasks._parse → task_intake.py;detection_route 的 cancel/probe → scan/app/cancel.py/probe.py;blob_route 四操作 → files/app/blob_ops.py。route 殼改薄、開關開時仍呼叫同一支,不複製。
main.py gunicorn options 加 worker_class(env GUNICORN_WORKER_CLASS)、threads(GUNICORN_THREADS);P-0 只加入口不翻預設,E-1 依 P-0 結論翻成 gthread/16。install.sh 寫 .env 補這兩個變數。
| 側 | 變數 | 預設 | 擋在哪 |
|---|---|---|---|
| 主系統 | AGENT_DIRECT_PUSH_ENABLED(config/config.py) |
False | AgentCommandService.send() 開頭回 CommandResult.disabled() |
| Agent | data_plane_commands_enabled(config/config.py AgentAuthConfig) |
False | data_plane_server.register_command_blueprints 開了才掛六個 cmd_* |
守衛(C-1):預設皆 False;主系統開關 False 時 patch AgentDataPlaneClient.command 成 raise,跑派工/重啟/升級/換證/設定變更五條全綠。
| 元件 | 現況 | 本案動作 |
|---|---|---|
Agent config_pull.py |
設定通道 WorkerComponent | reuse 形狀:command_pull.py 照它寫 |
Agent cmd_* 的 app 層函式、task_queue、event_sender+outbox、cert_renew.request_now、update_sync.trigger、ops.request_restart |
直推 handler 呼叫的本體 | reuse:分派表直接呼叫;route 殼留著用開關 |
BE AgentCommandService.redispatch_pending/_due |
心跳回來查 pending 單 | extend:查單邏輯搬進套件端點組裝;推送部分留著加開關 |
BE rollout_service().update_for_agent |
算該不該升 | reuse:command_sources.update_for |
BE _agent_config_notify |
直推 /config/notify |
extend:改寫 agent_requests(config_changed)+notify |
jedi-remote-agent AgentHeartbeatRoute 守門 |
AUTH_AGENT |
reuse:三條新端點掛同一組 |
core/extensions.redis_client |
已有 | reuse:pub/sub |
| jedi-detection 三支 client | 直推 HTTP | extend:改收 channel port |
remote_agent_adapter |
直連 blob | extend:四操作改 ask/fire,錯誤碼不變 |
agent_requests 表、agent_request_service、redis_command_waiter、file_spool、command_pull、reply_sender、task_intake |
無 | new |
批=可同時跑的一組;登記型檔各只讓一張卡碰(契約 F-1、routing.py F-3、Agent registry.py/data_plane_server.py F-2、remote_agent.py plugin 掛點 A-1、main.py/migration E-1、jedi-detection 三 client A-4)。底表 handoff/cards.py,python cards.py 斷言:依賴在更早的批、同批同 repo 不改同一檔。
| 批 | 卡 | repo | 標題 | reuse/extend/new | 依賴 | model/effort |
|---|---|---|---|---|---|---|
| 1 | P-0 | main | gunicorn 長輪詢併發驗證:sync 重現卡死、gthread+配額證明一般 API 不受影響 | new 驗證腳本+假端點;extend main.py 加 worker_class/threads 入口 |
— | opus/medium |
| 2 | F-1 | jedi-packages | 契約 0.3.0:長輪詢取指令與回答的形狀 | extend commands.py(登記型 owner) |
— | opus/medium |
| 2 | F-2 | agent | Agent 長輪詢元件 command_pull、分派表、退避、回覆 outbox、8443 指令開關 | new command_pull/reply_sender/task_intake;extend registry.py/data_plane_server.py(登記型 owner) |
— | opus/high |
| 2 | F-3 | jedi-packages | 主系統長輪詢端點、replies、files,四 port | new route+ports;extend routing.py(登記型 owner) |
— | opus/high |
| 3 | E-1 | main | 環境卡:釘新版套件、gunicorn 翻預設、agent_requests migration |
extend pyproject/main.py/install.sh;new migration |
F-1 F-3 P-0 | sonnet/medium |
| 4 | A-1 | main | 等回答插槽:ask/fire/reply、Redis 跨 worker、配額、過期;實作四 port | new service+entity/repo/waiter/spool;extend DI/plugin mount/排程 | E-1 | opus/high |
| 5 | A-2 | main | 指令六種改「寫正本+喚醒」,取消與換證寫 agent_requests,直推加開關 |
extend _agent_commands/_agent_config_notify/_agent_keys/AgentCommandService |
A-1 | opus/medium |
| 5 | A-3 | agent | 回答九種接線、taken→done/failed、抽 app 層 | extend 分派表;new scan/app/cancel/probe、files/app/blob_ops |
F-2 | opus/high |
| 5 | A-4 | jedi-packages | jedi-detection 三 client 改 channel port | extend 三 client;new port | A-1 F-1 | sonnet/medium |
| 6 | B-1 | main | 證據檔四操作改走插槽 | extend remote_agent_adapter |
A-1 A-3 | opus/medium |
| 6 | B-2 | main | 遠端查日誌改 ask | extend agent_log_query_app_service |
A-1 A-3 | sonnet/low |
| 6 | B-3 | fe | 連線欄三態改二態 | extend RemoteAgentManage.vue |
A-2 | sonnet/low |
| 7 | C-1 | main | 守衛測試、FR-170/171 指路行、手冊與資安報告 | new 守衛;extend 文件 | A-2 B-1 B-2 B-3 | sonnet/medium |
各卡的改哪些檔、新開哪些檔、驗收條件以 cards.py 為準(Notion 卡從它產)。
全部在 123 Agent 主機 iptables 擋 8443 入站(iptables -I INPUT -p tcp --dport 8443 ! -s 127.0.0.1 -j DROP)的狀態下做:
| # | 動作 | 預期 |
|---|---|---|
| 1 | 主系統派一張源碼掃描單 | Agent 主控台 3 秒內看到開跑;完成後主系統看到結果 |
| 2 | 管理頁按重啟 | Agent 重啟一次,不重複重啟 |
| 3 | 分批升級轉成下發 | Agent 3 秒內開始下載、升級事件回主系統 |
| 4 | 改一條授權清冊 | Agent 3 秒內拉到 |
| 5 | 按立即換證 | Agent log 有 renew、agent_requests 該列 done |
| 6 | 派一張檢測再按取消 | Agent 3 秒內中止 |
| 7 | 檢測工具頁按「測試連線」 | 2 秒內看到結果 |
| 8 | 檢測工具頁存帳密 | Agent 側帳密庫有該筆 |
| 9 | 證據存 Agent 的租戶:上傳/預覽/下載/轉 PDF/刪除 | 全部正常;/tmp/agent-outbound 取走後為空;100 MB 檔時間記錄 |
| 10 | 支援頁查 Agent 日誌 | 查得到 |
| 11 | Agent 停掉再做 1、7、9 | 1 的單留 pending、Agent 回來 3 秒內開跑;7、9 30 秒內回「Agent 無回應」不 500 |
| 12 | Redis 停掉再做 7 | 30 秒 504 不 500;Redis 回來後 7 正常 |
| 13 | 20 條假長輪詢吊著 | 一般頁面回應時間無差(P-0 腳本) |
| 14 | 兩個開關 | 預設 False;grep 主系統 .send( 呼叫只剩開關後那一處;Agent 8443 POST /tasks 404 |
| 15 | 連線欄 | 只剩在線/離線 |
gthread 執行緒常態吃滿)。