前言

前面的系列文章完成了 Task Document、Block Runtime、互動式 HTML、跨職能流程圖、AI Agent API 與公開資源權限。功能和測試都完成後,仍然有最後一個問題:怎麼把新版外掛放進正式環境,而且在出錯時真的能回到上一版?

「測試通過」只代表目前工作目錄在目前環境可以運作,不代表即將上傳的壓縮檔就是那份程式,也不代表資料庫備份可以還原,更不代表上一版外掛能讀取目前資料。

這篇記錄 KnockersTaskBlocks 如何把 build、測試、Artifact、Migration、部署與回滾組成同一份 release contract。

直接把工作目錄 SCP 上去有什麼問題?

開發目錄通常同時包含:

  • Source code 與未編譯前端。
  • node_modules、測試 fixture 與暫存檔。
  • 尚未提交的修改。
  • 本機專用設定。
  • 不應出現在正式站的原型與紀錄。

如果每次部署都臨時決定要複製哪些檔案,正式環境就很難回答:目前究竟是哪個 commit?前端 bundle 對應哪一份 source?回滾應該還原哪個目錄?

因此部署單位不是工作目錄,而是一份建置後不再修改的 runtime archive。

Release Artifact 應該自我描述

每次發布產生:

  • 外掛 runtime archive。
  • release-manifest.json
  • Artifact 與備份的 SHA-256 清單。
  • 版本與相容 Kanboard 資訊。
  • 已通過的 test gates。
  • Bundle size measurement。

泛化後的 manifest 大致如下:

{
  "release": "TaskBlocks-1.4.0",
  "sourceCommit": "<git-commit>",
  "pluginVersion": "1.4.0",
  "schemaVersion": 3,
  "gates": {
    "phpTests": "pass",
    "javascriptTests": "pass",
    "browserTests": "pass",
    "bundleBudget": "pass"
  }
}

Manifest 不是用來取代 Git,而是把「這個檔案如何產生」和「它通過哪些條件」放進同一個可驗證交付物。

上傳前後都重新計算 checksum,能發現壓縮、傳輸或解壓流程是否改變內容。Archive 也要檢查沒有 AppleDouble、測試憑證、Source map、開發設定和不需要的 dependency。

發布 Gate 不只是 Unit Test

這個外掛同時有 PHP、React、瀏覽器與多資料庫行為,因此 release gate 分成多層:

PHP

  • Schema migration。
  • Task Document validation 與 projection。
  • Revision conflict。
  • Public scope 與 resource ownership。
  • 所有 runtime PHP 檔案 syntax lint。

JavaScript

  • Editor 與 Viewer Registry。
  • Interactive HTML lifecycle。
  • Swimlane Domain transformation。
  • Share management UI。

真實瀏覽器

  • 編輯、摘要、唯讀與公開模式。
  • HTML sandbox、靜態預覽與全螢幕。
  • 流程圖拖曳、連線與唯讀限制。
  • 附件、Internal Link 與撤銷後失效。

Supply chain 與 Artifact

  • Dependency vulnerability gate。
  • Archive integrity。
  • Manifest、version 與 source commit 一致。
  • 必要 assets 都在 runtime archive 中。

只有全部通過的 candidate 才能進入 web root。

Bundle Budget 需要成為阻擋條件

加入 React Flow 後,最容易發生的退化是把重型 chunk 不小心合併進所有任務頁的初始 JavaScript。

這個專案為 release 設定 project-specific budget:

  • Task Document 對初始 JavaScript 的增加量超過門檻就失敗。
  • Swimlane Diagram chunk 超過獨立門檻就失敗。
  • 沒有流程圖的頁面不能請求 React Flow assets。

門檻不是通用 Web 標準,而是根據目前 baseline 和可接受的使用體驗制定。重點是把數字寫進 build script,讓 regression 自動阻擋 release,而不是部署後才憑感覺判斷頁面變慢。

Runtime performance 也使用大型 fixture 測量 mount、drag、connect、cycle traversal 與 long task。圖演算法如果漏掉 visited set,即使小型測試都通過,也可能在循環或大型文件中凍結瀏覽器。

Migration 必須在支援的資料庫實際執行

外掛支援 SQLite、MySQL 與 PostgreSQL,不能只在 SQLite unit test 通過就推論其他資料庫安全。

Migration smoke 會在 disposable database 中驗證:

  • 空白安裝可以建立最新 schema。
  • 舊 schema 可以升級到目前版本。
  • Unique constraint、foreign key 與 cascade 符合預期。
  • 既有 Task Document 與 Revision 數量沒有被改寫。
  • 新版程式可以讀取升級後資料。

對會刪除或重建資料的測試,預設必須 fail closed:只有資料庫名稱符合測試 prefix、環境變數明確確認,而且 target 位於 allow-list 時才允許執行。

正式回滾則盡量不做 destructive down migration。若 release 只新增資料表或索引,緊急回復舊程式時可以保留新表不用,避免事故中同時操作程式與資料毀損風險。

有備份,不代表可以還原

部署前會備份:

  • Database dump。
  • 現有外掛完整目錄。
  • Configuration 與必要自訂資產。
  • 新舊 Artifact checksum。

但備份真正有價值的條件,是可以在隔離環境還原並啟動。

回滾演練不只做 gzip -t 或列出 tar 內容,而是:

  1. 將 database dump 還原到 disposable database。
  2. 使用部署前外掛建立隔離的應用程式副本。
  3. 啟動 PHP 與資料庫。
  4. 用瀏覽器開啟登入任務與匿名公開頁。
  5. 驗證 Task Document、附件、連結與相容內容。
  6. 關閉服務並移除暫存環境。

這才能證明舊外掛真的可以讀取目前資料,而不是只證明壓縮檔沒有損壞。

正式替換要保留立即前一版

部署時先把 candidate 解壓到 web root 外,完成:

  • PHP lint。
  • 必要檔案與 assets 檢查。
  • Plugin version 與 manifest 核對。
  • Owner、group 與 mode 驗證。

通過後再做目錄層級的替換,並保留立即前一版。若新版本 origin smoke 失敗,可以把 candidate 移出並恢復上一個完整目錄,不在事故中逐檔猜測哪些檔案需要還原。

Database restore 是最後手段。若部署沒有執行破壞性 migration,也沒有寫壞內容,優先回復外掛檔案可以降低停機時間與資料回退範圍。

Production Smoke 應該使用可還原資料

正式環境仍需要驗證真正的 Web Server、PHP、Database、CSP 與瀏覽器行為,但不能留下測試資料。

Smoke fixture 的做法是:

  1. 保存測試任務原本的完整文件與 Revision。
  2. 加入可以辨識的 HTML、流程圖、附件與連結。
  3. 驗證登入、公開、全螢幕與撤銷流程。
  4. 確認所有新 assets 由 origin 正常回應。
  5. 還原原始文件與公開狀態。
  6. 再次比對原始內容與測試資料是否完全移除。

若驗收腳本需要 API Key、Session 或分享 Token,只能在執行時從環境取得,不能把 credential 或完整 response 寫入 repository 和永久 measurement。

部署後還要看什麼?

成功頁面載入一次不代表部署完成。最後還要檢查:

  • PHP、Web Server 與應用程式錯誤紀錄。
  • Browser console 是否出現外掛資產或 runtime error。
  • Cache-busting asset 是否為新版本。
  • Public response 是否維持 no-store。
  • Task Document、Revision 與公開狀態是否回到驗收前基準。

監控要先建立部署前 baseline,再比較部署後新增內容,才能區分既有第三方外掛噪音與這次 release 造成的問題。

整個系列最後得到的結論

從 Markdown 任務描述開始,KnockersTaskBlocks 最後加入了結構化文件、Block Runtime、互動式 HTML、流程圖、AI Agent API 與單一任務分享。每一項功能都帶來新的資料與安全邊界,但它們有相同的開發原則:

  • 不修改 Kanboard 核心。
  • 新功能沿用原生身分、權限與資源 ownership。
  • Task Document 保留版本、相容內容與降級閱讀。
  • 重型功能按需載入,並有明確 guardrail。
  • Public access 以 Scope 約束每一個子資源。
  • Release 以可驗證 Artifact 和實際回滾演練收尾。

對 legacy system 進行延伸開發,真正困難的不是把新介面做出來,而是確保升級、停用、公開、並行修改與部署失敗時,既有系統仍然保持可理解、可操作和可回復。

系列起點可以從這篇開始閱讀:Kanboard 任務內容區塊外掛開發實錄:從 Markdown 描述到結構化文件