Mirror copy for gu-log SP-251. Original by Thariq (@trq212): Know your unknowns. English | 繁中
了解你的未知 · 實作中
← 所有範例

實作筆記

不要讓建置中途的意外消失在滾動歷史裡,請 agent 在發生時記錄每一個偏離計畫的地方——這樣意外就會成為你下次嘗試的輸入。

Prompt
「在你建置匯出功能時,維護一份實作筆記檔案。如果你遇到邊界情況迫使你偏離計畫,選擇保守的選項,記錄在『偏離』區塊下,然後繼續做。」
Claude 產出的成品
docs/notes/export-feature-implementation.md 開始於 14:02 · 最後寫入 16:51 · 分支 feat/review-export
11
筆記錄
4
偏離
2
需要你判斷
14:02
計畫確認14:02

步驟 1 — 匯出任務 model 和 queue 接線,照計畫

apps/api/src/exports/exportJob.ts 新增 ExportJob,排入現有的 BullMQ media-tasks queue,用專門的 export job name。Migration 0121_export_jobs 完全符合計畫的 schema(status enum、requested_byreview_idformat)。

14:18
計畫確認14:18

步驟 2 — POST /api/v1/reviews/:id/export endpoint

Request validation 依計畫重用 apps/api/src/validation/ 中的 zod schema。format 接受 mp4-burnin | csv | bundle。回傳 202 加上 job id;polling endpoint 依計畫延到步驟 6。

14:29
發現14:29

Review.duration_ms 是反正規化的,偶爾過時

三筆 fixture review 的 duration_ms 跟來源素材 probe 資料不符(看起來重新上傳時沒有刷新)。匯出改為從 MediaAsset.probe.duration 讀取——跟 playbackManifest.ts 現有做法一樣。沒有計畫變更,只是給之後碰這塊的人的陷阱提醒。

14:41
偏離 1/414:41

舊版註解不一定有 frame timestamp

計畫說的
每個註解都有 frame_ts;burn-in renderer 可以排序並把它們都放到時間軸上。
程式碼揭露的
annotations 中約 12% 的 row 早於 migration 0087frame_ts = NULL——它們是 per-frame 註解存在前的一般審閱評論。
保守選擇
將 null timestamp 的註解排除在影片 burn-in 之外;在 CSV sidecar 中包含它們,frame_ts 留空並加上 legacy_comment 標記。匯出 bundle 不會悄悄丟掉任何東西。
待重新評估
可以從 created_at 對照 review session 推算 timestamp——決定這樣在影片中顯示是否夠誠實。
15:05
計畫確認15:05

步驟 3 — 透過 ffmpeg drawtext/drawbox 做註解 burn-in,照計畫

Filtergraph builder 在 apps/worker/src/exports/burnin.ts。計畫的 per-annotation overlay windows(enable='between(t,a,b)')可行;在 40 個註解的壓測 fixture 驗證過——worker instance class 上渲染時間是實時的 1.4 倍,在計畫的 2 倍預算內。

15:22
偏離 2/415:22

Queue 把 Date 物件來回傳成字串

計畫說的
ExportJobPayload.requestedAt: Date,傳給 worker 並與 review 的 updated_at 比較以偵測過時的匯出。
程式碼揭露的
BullMQ 把 payload JSON 序列化到 Redis,所以 worker 收到的是 ISO 字串,不是 Date。過時比較在單元測試中悄悄強制轉型通過了,但在整合測試的重新投遞 job 上失敗。
保守選擇
把 payload 型別定義為線上格式(requestedAt: string ISO-8601),在 worker 邊界的 exportConsumer.ts 解析一次。不用自訂 serializer。
待重新評估
其他 media-tasks payload 可能有一樣的潛在 bug——一個共用的 job payload zod codec 可以修掉這整類問題。
15:48
偏離 3/415:48

Zip streaming 已經存在——拿掉計畫中的 archiver 依賴

計畫說的
archiver 來把匯出 bundle(影片 + CSV + 縮圖)建成 zip stream 到 S3。
程式碼揭露的
packages/media/src/zipStream.ts 已經有 streaming zip 加 backpressure 處理——它驅動批次素材下載功能,從三月就上線了。
保守選擇
直接重用 zipStream.ts。少一個依賴,bundle 匯出也繼承了跟批次下載一樣測試過的 backpressure 行為。
待重新評估
zipStreampackages/media 但現在被兩個功能用——值得在清理時提升到 packages/shared
16:07
發現16:07

Websocket progress channel 慣例已經存在

轉碼 pipeline 發送 media:progress:{assetId} 事件。遵循同樣的慣例——匯出發送 export:progress:{jobId}——這樣前端可以重用 apps/web/src/hooks/useProgressChannel(),而不是計畫草擬的 polling UI。polling endpoint 也保留了,作為備用。

16:20
偏離 4/416:20

訪客審閱者可以看 review 但不能下載素材

計畫說的
匯出權限 = review 層級 ACL:任何能打開 review 的人都能匯出。
程式碼揭露的
Workspace 層級的 guest_reviewer 角色授予 review 存取權但 can_download_assets = false——客戶用它讓外部利害關係人可以評論但不能把素材帶走。只檢查 review ACL 會讓訪客透過匯出洩漏原始影片。
保守選擇
所有格式的匯出對 guest_reviewer 回傳 403,跟 assetAccess.ts 現有的素材下載檢查一致。最嚴格的防禦解讀。
待重新評估
理論上訪客應該還是能拿到 CSV(只有註解,沒有素材)。這是產品決策,不是我的——記錄在下面。
16:33
人類待辦16:33

決定訪客審閱者匯出政策

目前行為(來自偏離 4):訪客在所有匯出格式都收到 403。如果你希望訪客可以匯出不含素材的註解 CSV,在 exportPolicy.ts 大約改 10 行加一個測試——但這會改變「無法下載素材」對客戶的意義,所以應該由你決定。不阻擋 merge 或 QA。

16:51
人類待辦16:51

確認匯出 bundle 的保留期限

計畫沒有指定產生的 bundle 的 TTL。我把它們放在 exports/ S3 prefix 下,7 天 lifecycle rule,跟分享連結到期時間一致——保守做法,沒有對客戶承諾更長時間。但如果合約或資料保留政策另有規定,infra/s3.tf 的 lifecycle rule 是唯一要改的一行。