FR-074.5 / CM-1541 — 04-04 錄影 LOG(append-only,每棒追加一 block)

現況看 fr074-04-04-STATE.md(living)。本檔只記「發生過什麼、為什麼這樣決定、推翻了什麼」。


§1

第 1 棒 — 2026-09-03(製片 runner,Opus 5)

交付:A1/A2 錄完並 commit;B1~C 未開始(等 OpenVAS)。

commits(test repo,branch feature/FR-069.5,未 push)

hash 內容
a1d2ace 阻塞診斷寫進 outline(未寫 feature、未錄影)
05f0941 A1/A2 依 user 拍板選項 1 改寫劇本
0e6dab8 feature 產出 + A1 錄影 + merge 腳本補條目
1346fa8 A2 錄製完成 + 修 S0 上傳 bug + 03-status 補八個坑

🔴 推翻了什麼:原大綱的 A 段劇本前提不成立

開錄前唯讀盤點發現:04-03 建的七個任務狀態全是 TODO執行者抽屜沒有「開始執行」鈕 (FE JobExecutionDrawer.vue:218 canEdit = props.canExecute && currentStatus === 'PROCESSING'MyTasksView.vue:553 只在 PROCESSING/COMPLETED 給 canExecute)。 原大綱「Alex 逐一按開始執行」在真實流程裡做不到。

轉 PROCESSING 的唯一入口是 PM 規劃頁的「開始執行任務」,而該 service (jedi_task_platform TaskExecutionService.start_task_execution)內含 _auto_dispatch_detection_scans(FR-056.5 #5):轉狀態的同時自動派掉尚無執行紀錄的 detection 任務的第一次掃描,只排除 scan_mode=upload 的列 (infra/flow_control/repository/task_execution_query.py:149-212)。 190 只有 SonarQube 是 upload → 一按下去六支自動開掃、執行者一個鈕都沒按。

給了 user 三個選項(改劇本/改資料/只錄得了的),user 拍板選項 1:改劇本貼合產品。 → A1 改 PM 視角(@auth-pm)、A2 改執行者視角,全章只有 SonarQube 示範手動發動。

沒有選 2(清資料)是對的:清執行紀錄屬 190 寫入型異動,且 CINC 後來真的失敗又重跑成功, 反而成了 C 段「掃描失敗怎麼重試」的真實素材。

A2 錄了四次,前三次各撞一個坑(逐項修,第四次 100/100 全綠)

死在 原因 修法
1 步驟 36(CINC) 硬等「執行中」,但該支 19:46 已 failed 拿掉全部「等待執行狀態出現執行中」,改導覽執行紀錄區+字卡講時間差
2 步驟 36(GCB) GCB 是 auto 完成模式,掃完 COMPLETED、從預設「進行中」清單消失 A2 開頭補 我篩選「全部」的任務,字卡順勢解釋此行為
3 步驟 92(SonarQube) S0 uploadSourceArchive() 的 bug(見下) 改走 filechooser 真實路徑
4 全綠,9:37

🔴 修了一支 S0 檔(越過卡片停手線,已在 commit 與卡片寫明)

training/pages/detection-tools/DetectionTaskExecPage.jsuploadSourceArchive(): 原本直接對隱藏 input setInputFiles(S0 判斷 Playwright 對隱藏 input 可行——這點本身沒錯, 錯在跳過了 FE 的歸屬邏輯)。

FE 的壓縮包是按分派列歸屬的:pickSourceFile(row) 先把 sourceFilePickTarget 設成該列 key, uploadSourceFile() 再用它決定檔案掛哪一列(JobExecutionDrawer.vue:428-437)。 直接餵 input → target 停在 null → 檔案落到 _default key → 該列仍算缺檔 → executeBlockedByMissingSource 成立 → 「開始執行」維持 p-disabled

症狀極具欺騙性:上傳 toast 有出、看起來成功,但按鈕點不下去,畫面還留著 「此任務需上傳原始碼壓縮包才能執行」。

為什麼選擇修而不是繞過:這是 S0 的真 bug(S0 當時 190 上 0 檢測任務、測不出來), 繞過只會讓別章再踩一次。只動這一個方法,未動其他 S0 檔與共用層,並在方法上寫明成因。

順帶拍到的真實素材:CINC 失敗與重跑

CINC 第一次派工真的失敗,錯誤是 Sudo cannot prompt for password because there is no terminal (sudo 需要 TTY/NOPASSWD,不完全等於帳號權限不足——回報 user 時已指出這點)。 user 補權限後,A2 錄影中由劇本按下「重新執行」重跑成功(執行紀錄 id 8、9)。 失敗紀錄不是 active 執行 → 按下去不跳確認框,故不接 我確認重新執行

這段順勢改寫成「掃描失敗怎麼重試」的字卡(權限不足會失敗/失敗紀錄留著方便排查/ 補好後在抽屜按一次即可),比原本平鋪直敘的「看一眼設定」更有教學價值。

留給 B2 的坑:CINC 抽屜現在失敗與成功紀錄並存,第一筆 取的是 nth(0), 錄前務必先唯讀確認畫面排序。

其他實查校正(已寫進 outline 與 03-status)

  • 抽屜標題是「任務指南」不是「任務指引」
  • OpenSCAP 是兩組分派版型(exec-info-grid 3 個、完整參數 toggle 2 個、「第 N 組」Tag)
  • OpenVAS 無「完整參數」摺疊
  • Seg-H 設的 22:00 排程組提早跑完了 → 原本寫「排在當晚十點」的字卡與畫面不符, 已改成講兩組各跑各的、排程組會先顯示排程中

方法論筆記

開錄前的唯讀盤點是這棒最值錢的動作。若照卡片直接寫 feature 再跑,會在第一個動作就失敗、 且看起來像 selector 問題(實際是產品前置流程),很容易被誤診成「S0 的 step 寫錯」而去改 S0 檔。 先用 Playwright 唯讀 probe + DB SELECT 把七個抽屜逐一看過,才定位到真因。

同理,A2 每次失敗後都先唯讀 probe 確認「按鈕到底是什麼狀態」再改劇本, 避免了「改一次錄一次、一次十分鐘」的盲修迴圈(實際仍花四輪,但每輪都修在真因上)。


§2

第 2 棒 — 2026-09-03(製片 runner,接續第 1 棒)

交付:B1/B2/B3/B4/C 五段撰寫並全數正式錄完;OpenVAS 從 B3 拆出成 B5 獨立段, 重跑成功後一併正式錄;八段合併出 04-detection-04-execute-collect.mp4(26:23、91.5MB)。 本章拍攝到此收工。

commits(test repo,branch feature/FR-069.5,未 push)

hash 內容
4cb71f9 B1~C 五段完成、B5 新增、全八段合併完片

🔴 開錄前實查:OpenVAS 第一輪逾時失敗

接手時第 1 棒留的狀態是「只差 OpenVAS 還在跑」,但唯讀盤點(SELECT 執行紀錄)發現 它其實已經逾時失敗OpenVAS task … 逾時(3600s)未完成,平台對單次掃描有一小時 上限)。這推翻了「等一下就會全部成功」的假設。

user 拍板:不為它停工——把 B3 拆開,OpenVAS 連同「弱點分級怎麼處理」字卡整段搬到 新的 B5(獨立後補段),B3 只留 ZAP。合併腳本原本用固定陣列+merge_one 遇缺檔會整組 中止的機制,若照抄會讓 B5 缺席時連其餘七段都合不出來——改成動態陣列,B5 有就併入、 沒有就跳過,並在插點放在 B3(網站)與 B4(原始碼)之間,維持「主機弱點→網站→原始碼」 的敘事層次。

第二輪重跑 21:08 起算,21:50 成功(42 分鐘,summary 含完整嚴重度分級: critical 0/high 0/medium 6/low 2/log 78/findings 86)。時間點剛好落在本棒批次 正式錄影開始之前,B5 因此沒有真的走到「後補」那條路——直接和其他七段一起正式錄完。

批次驗證抓到兩個第 1 棒交接時沒預見的真問題

第一輪 SKIP_OVERLAYS=true 批次驗證(B1/B2/B3/B4/C,194 步)跑出 4/5 綠,只有 C 段死:

  1. C 段前提本身是錯的:PM 在「我的任務」頁一筆任務都看不到。查根因是 public.vw_user_job_queue 硬篩 user_id = 當前使用者infra/readmodel/tasks/my_grc_jobs_query.py:114),七個檢測任務 user_id 全是 Alex(id 9),Bob(id 8)本來就不在這張表上——這是產品設計,不是權限漏配。 改走專案規劃頁的任務面板,內容照舊(完成模式回顧、重跑取消、無實質檢查、總結)。

  2. B1 人工完成段的按鈕會消失:批次驗證跑完後 OpenSCAP 任務被真的按完成結案 (status = COMPLETED),FE canEdit = canExecute && status === 'PROCESSING' 條件不再成立,「完成任務」鈕不再顯示。正式錄若照原劇本硬按會卡住。 兩條路:退回 PROCESSING(190 寫入型異動,依環境鐵律要 user 明示)或改字卡。 user 拍板 B 案:改字卡說明「已經按過完成」,不動 190 資料,而且畫面上這個 任務現在正好就是完成狀態,字卡跟畫面一致,不是硬凹。

附帶發現:GCB/CINC 沒有「發現 N 項」(陷阱④,第 1 棒交接檔沒抓到)

DB 實查兩支的 summary 形狀,只有 pass/fail/error/notapplicable沒有 findings 欄位。FE shouldShowFindings()JobExecutionDrawer.vue:944)在 findings === undefined 時直接不顯示「發現 N 項」文字,而 step 應看到發現項目統計 比對的正是 /發現 \d+ 項/——對這兩支下這個斷言必然逾時 30 秒失敗。B1(GCB)、B2(CINC)已拿掉 斷言、字卡改口徑講「只給通過/失敗/不適用三個數,失敗那幾條就是要處理的」。

自己犯的錯:字卡文案用真實換行而不是 \n

改寫 B1、C 兩段的說明卡片時,我把新增內容用真實多行字串貼進去,但 Gherkin step 的 參數必須是單行、內部用 \n 字面值換行(前面所有段落都是這樣寫)。第一輪批次驗證前 cucumber-js 就 parse error 擋下,訊息很清楚(expected: #EOF ... got '這段說明會跟 證據一起...'),修正兩次(第一次漏改了 C 段)才過。這不是產品或劇本問題,是我 自己寫 feature 時違反既有格式慣例,記在這裡提醒之後改字卡一律檢查有沒有誤用真實 換行。

CINC 排序陷阱:第 1 棒標「未能驗證」,第 2 棒已排除

第 1 棒交接檔標記「CINC 第一筆可能取到失敗那筆,錄前務必先唯讀確認」。第 2 棒查了 BE 排序邏輯:jedi-detectionlist_by_job_execution_orderedstarted_at DESC, id DESC,加上 DB 實查 CINC 三筆紀錄的實際順序(id 9 → 8 → 1), 確認最新的成功紀錄排在最上面我開啟第一筆執行的詳情 可以直接用,不必改成 指定第 N 筆。這個陷阱從「風險」降級為「已驗證安全」。

merge 前的 TRIM_HEAD 決策

第 1 棒交接檔提醒本章 A2/B 段起點(我的任務頁)比 04-03 規劃頁短,要求合併前先截圖 決定 TRIM_HEAD。第 2 棒實測:B 系列(我的任務頁)4 秒就到乾淨畫面,A1/C(規劃頁) 在 6~9 秒才到位。沿用既有預設 TRIM_HEAD=8——對最慢的規劃頁路徑安全,對 B 系列 只是多切 4 秒空景不傷內容,最短的 B3(99 秒)扣 8 秒後仍有 91 秒,不會被砍成空檔。 沒有另開特例值。

方法論筆記:交接檔的「現況」是快照,不是承諾

第 1 棒交接檔寫「只差 OpenVAS 還在跑」,這句話在交接當下是真的,但第 2 棒接手時 (隔了一段時間)它已經逾時失敗又重跑。任何交接檔裡「正在進行」「即將完成」這類 動態狀態,接手時都要重新唯讀確認,不能當作接手當下依然成立的事實——這跟 feedback_card_env_assertions_verify_or_mark_assumption 是同一個教訓在錄影場景的 版本。若當時直接照抄「等 OpenVAS 跑完」去等,會多空等超過一小時才發現它已死透。


§3

第 3 棒 — 2026-09-03(製片 runner,接續第 2 棒,同日)

交付:修正 B1~B5 全五段的統計導覽 selector 誤圈、修正 B5 的 OpenVAS PDF 預覽卡死, 五段重錄、重新合併。

commits(test repo,branch feature/FR-069.5,未 push)

hash 內容
15f7b7f 修正統計導覽 selector 誤圈與 OpenVAS PDF 預覽卡死

觸發:user 開合併片實看,一眼抓到 OpenVAS 統計導覽圈錯地方

user 打開第 2 棒交付的合併片,截圖指出 B5 段「弱點掃描統計」的導覽黃框圈住的是 「開始時間 2026-09-03 21:08:03/結束時間 21:50:35/觸發者 Alex Wang/逾時秒數 72000」這個區塊,但字卡講的是「嚴重、高、中、低幾條;發現數是這幾級的加總」—— 真正的統計數字(critical/發現項目/high/log/low/medium)在框外面下方。同時 user 也回報 OpenVAS 報告預覽一直在轉圈,不對。

排查方式:不猜,寫唯讀 probe 直接對 190 驗證

兩個問題都沒有直接改 code 猜答案,而是先寫 Playwright 唯讀腳本(帶 support/auth-states/executor.json 直連 190,開頁面→開任務→開執行詳情/證據區) 逐一驗證:

  • selector 問題:讀 FE 源碼確認 .exec-info-gridJobExecutionDrawer.vue 裡出現 5 次,其中執行詳情對話框那個(:2987)圈的是 metadata,真正含統計數字的 區塊在 :3087v-if="execInfo.summary"),標題文字是「掃描結果統計」(i18n key execution_info_summary)。改用 text="掃描結果統計" >> xpath=.. 這個 selector 在 probe 裡實測:count=1innerText 完整輸出六個統計值。B1(兩處)、B2(兩處)、 B3、B4、B5 全部七處都圈錯,不只 user 截圖抓到的那一處——GCB/CINC 因為字卡文字 模糊,肉眼比較看不出矛盾,但底層是同一個 bug。

  • PDF 預覽問題:查 DB 確認 OpenVAS 報告的 upload_files.file_ext = 'pdf' (其他六支都是 html)。probe 用 page.on('requestfailed') 攔到 iframe 的 請求被標記 net::ERR_ABORTED;同一個 URL 用 page.request.get()(純 HTTP fetch,不經瀏覽器渲染邏輯)打卻回應完全正常(200、application/pdf、 139846 bytes 有效 PDF)。再用 page.on('download') 確認瀏覽器把這個請求 當成下載觸發,不是 inline 顯示。最後測了拿掉 context 的 acceptDownloads: true 選項,行為完全沒變化——排除是測試環境設定的問題, 是 headless Chromium 沒有內建 PDF viewer 這個既定行為,跟其他六支的 HTML 報告是完全不同的處理路徑。

為什麼兩個問題在批次驗證階段都沒被抓到

SKIP_OVERLAYS=true 的驗證模式完全跳過導覽框渲染overlay.steps.js 每個 導覽 step 開頭都是 if (SKIP_OVERLAYS === 'true') return),所以 selector 選錯 不會讓驗證失敗——.exec-info-grid 這個 selector 本身是存在的、count > 0,只是 指向錯的元素,批次驗證的「綠燈」只證明流程走得完,完全沒有驗證畫面內容是否正確。 PDF 預覽問題同理:我點擊第一份掃描報告開啟預覽 這個 step 只等 iframe 元素 attached(DOM 節點存在),不驗證它是否真的載入了內容,所以卡死轉圈的畫面在 自動化驗證裡也是「通過」。兩個 bug 都只有真人看過實際畫面或影片才會被發現, 這正是本棒方法論教訓要強調的:批次驗證證明「跑得完」,不證明「對不對」,最終 交付前一定要有人真的看過產出。

處理範圍:selector 修正影響全部五段,B5 修 fallback

因為 .exec-info-grid 的錯誤是七處共用,只重錄 B5 沒有意義——B1~B4 的導覽框 在畫面上也是圈錯的,只是沒被 user 明確指出。第 3 棒把 selector 修正套用到全部 七處,SKIP_OVERLAYS=true 批次驗證 B1~B5(selector 改動影響全部五段)跑 197 步全綠後,五段全部重錄(不只 B5),再重新合併八段。B5 額外加了 「改導覽下載報告」的 fallback,其餘四段只有導覽框指向修正,字卡與流程不變。

交付確認

  • SKIP_OVERLAYS=true 批次驗證:5 scenarios、197 steps 全綠
  • 正式重錄 B1/B2/B3/B5/B4:5 scenarios、197 steps 全綠
  • 截圖抽查 B5 執行詳情對話框:黃框正確圈住「掃描結果統計:critical:0 發現項目: 86 high:0 log:78 low:2 medium:6」整行,與字卡「嚴重、高、中、低各幾條; 發現數是這幾級的加總」一致
  • 截圖抽查 B5 PDF fallback:字幕「這份報告是 PDF,直接下載看」正常顯示,無轉圈殘留
  • merge 重新跑:8 段全合,04-detection-04-execute-collect.mp4(26:23、90.5MB)

方法論筆記:綠燈是流程完整性的證明,不是畫面正確性的證明

這是本棒最重要的教訓,值得獨立記一次:SKIP_OVERLAYS=true 這種跳過視覺渲染的 快速驗證模式,天生就無法抓到「selector 選對元素但圈錯範圍」或「元素存在但內容 沒載入」這類問題——它只回答「這個 selector 找不找得到東西」,回答不了「找到的 東西是不是使用者真正該看到的東西」。這跟第 1 棒「開錄前唯讀盤點最值錢」、 第 2 棒「交接檔的現況是快照不是承諾」是同一條主線的延伸:任何形式的自動化 綠燈都只覆蓋它實際檢查的那個維度,不能被當成「這段內容沒問題」的證明。 統計導覽這種「選對元素、但選錯是不是視覺上正確的那個元素」的錯誤,只有真人 看畫面或看最終影片才抓得到,這正是為什麼 user 最後看片這一步不能省。


§4

第 4 棒 — 2026-09-04(製片 runner,接續第 3 棒)

交付:重錄 A1(文案調整)、重新合併八段、對合併片做頭尾裁切(去頭 6 秒、去尾 5 秒)。

commit

無(本棒未動任何 outline/feature 原始碼,純錄影/合併/裁切操作;mp4 不入版控)。

A1 重錄原因與範圍

user 反映 A1「開始執行任務」確認對話框的文案有調整,要求重錄該段。查證目前 FE 文案:

開始執行任務 將發出批次通知給所有已指派人員,並讓已指派的證據蒐集任務進入執行階段(轉為進行中)。 上傳型掃描任務需由執行者上傳檔案後手動執行。確定要繼續嗎?

跟劇本既有字卡(「按下去,會先跳確認」「按下去的瞬間發生三件事」)內容不衝突, 但現在的 step 點下去只等 800ms 就自動按「確定開始」,畫面上這個對話框幾乎一閃而過。 問 user 是否要順勢加停留+導覽讓觀眾看清楚,user 選擇維持現狀,只重錄不調整時序

SKIP_OVERLAYS=true 驗證 23 步全綠後正式重錄,2:14(129.3s,跟原版 2:09 接近)。

重新合併八段+裁切頭尾

新 A1 mp4 換上後重新跑 merge-phase-videos.sh,八段全合成功。

user 要求「總影片前 6 秒、後 8 秒剪掉,都是沒用的片段」。逐幀截圖查證片尾發現 按字面 8 秒會切到還在畫面上的「這章學了什麼」總結卡(倒數 5~8 秒左右都是總結卡 或它的淡出動畫,倒數 5 秒之後才是真正的空景/過場畫面)。問 user 確認後,改成只切 倒數 5 秒的真空景,保留完整總結卡。

最終用 ffmpeg -ss 6 -i <合併版> -t <keep> -c:v libx264 ... 重新編碼裁切 (去頭 6 秒、去尾 5 秒,keep = 1583.12 - 6 - 5 = 1572.12),輸出 04-detection-04-execute-collect.mp4(26:12、88.9MB)。截圖抽查頭尾確認: 開頭直接是規劃頁乾淨畫面,片尾在總結卡完整結束後才收在規劃頁畫面,沒有殘留字卡 或淡出動畫。

方法論筆記:使用者給的秒數是感覺值,不是精確測量值,剪之前先截圖核對

user 說「後 8 秒」是觀影當下的直覺印象,不是逐秒算出來的精確值。若照字面直接執行, 會把總結卡整段切掉,變成一個新的內容缺陷(片尾用戶說剪掉「沒用的片段」,但若切掉 的是總結卡,觀眾反而看不到完整結論)。任何裁切指令在真的下刀之前,先逐秒截圖 核對邊界在畫面上到底是什麼內容,跟第 3 棒「批次驗證綠燈不等於畫面正確」是同一條 主線——這次是「使用者口頭指令的秒數不等於畫面真實邊界」,一樣不能盲信數字直接執行。