Skip to Content
概念提交管線

提交管線

POST /worker/versions 會在建立修訂版之前完成驗證與建置。之後的部署重用儲存的 bundle — 絕不重新編譯、絕不讀 npm。

project archive (≤2 MiB gzip tar) → provider readiness gate (503 when the Edge Worker provider is not configured) → envelope + archive gates (413/422, before unbounded parsing) → credentialless builder (Node subprocess, no network, no credentials) archive fence → manifest v2 → package/lockfile vs catalog → source policy → esbuild bundle (generated router entry + user modules) → canonical manifest + SBOM + build receipt → artifact-store uploads (cleanup intent committed FIRST) → one transaction: worker (if first) + immutable ready version + audit + intent adoption

provider 關卡是 route 內的第一個檢查(回應 413 的請求大小上限檢查在它之前):EDGE_WORKERS_PROVIDER 選擇雲端 provider(預設)或地端 provider。雲端 provider 永遠就緒;EDGE_WORKERS_PROVIDER 值無法辨識,或地端部署的設定不完整時,POST /worker/versions 在任何壓縮檔檢查或建置之前回應 503 Edge Worker provider is not configured,不會建立任何東西。

builder 是壓縮檔的權威

edge/department-workers/scripts/build.mjs,由後端以子行程呼叫(--manifest --project --out --receipt,皆為絕對路徑),環境只有六個變數(LANG、LC_ALL、NODE_ENV、PATH、SOURCE_DATE_EPOCH=0、TZ=UTC)— 建置中不存在任何 Cloudflare/產物儲存/租戶憑證,依賴解析也不碰網路。Python 端永不解開 tarball、永不執行提交的程式碼;它事後重新驗證 builder 的證據。成功時有任何非空輸出、或證據有偏差,就是 builder_invalid_output(503)。stderr 必須恰為一行 edge_worker_build_failed:<code>;internal_build_error 與 approved_dependency_unavailable 是可重試的平台失敗(503),其餘任何代碼都是對所提交專案的拒絕(422,見下)。

主要關卡(精確 builder 代碼見錯誤):

  • 壓縮檔圍欄:確定性 gzip(MTIME 為零、FLG 為零)、嚴格 ustar(mode 0644、uid/gid 0、mtime 0、路徑依位元組順序嚴格遞增、忽略大小寫後不得有兩個路徑相同、僅限一般檔案、padding 全零)、≤256 個檔案/解壓 ≤4 MiB/單檔 ≤1 MiB;禁用目錄段(.git、node_modules、dist、build、out、coverage、.hg、.svn);禁止預建產物(.bundle.、.min.、.map、.node、.wasm);恰好四個根檔案 + src/**。
  • 原始碼政策:只允許靜態 ESM — 沒有 import()、沒有 require、沒有 import =、沒有 triple-slash reference;相對匯入必須在壓縮檔內解析;bare import 必須是目錄項目;AST 圍欄禁止 eval、Function、WebAssembly、Reflect.apply/construct 與 apply/bind/call 花招,並且對輸出的 bundle 再跑一次。
  • esbuild,釘死版本(0.28.2、target: es2022、workerd conditions):entry 是生成的 router(來自 runtime-template.mjs);使用者程式碼只能經由虛擬模組 teamsync:user-source 進入;cloudflare:workers 只有生成的 entry 能匯入。任何 esbuild 警告都會使建置失敗。bundle ≤5 MiB。
  • zod jitless:唯一核可的依賴被原始碼轉換為停用 JIT 路徑,三個被修補的檔案以 SHA-256 釘死 — 雜湊漂移直接 fail-closed。

builder 的拒絕如何傳到呼叫者

builder 的具體代碼(manifest_route_ambiguous、project_archive_not_deterministic 等)不會經由 HTTP 回傳。後端把所有不可重試的 builder 代碼收斂成同一個通用 422,所以解析 detail[].type 的用戶端對任何被拒絕的專案都只會看到 project_invalid:

結果狀態回應本文
builder 拒絕了專案(除了兩個可重試代碼以外的任何 edge_worker_build_failed:<code>)422detail: [{loc: ["body","project"], type: "project_invalid", msg: "project failed the locked Worker build policy"}]
builder 接受了專案,但後端對回傳 manifest 的自我重新驗證失敗422同樣形狀;type: "manifest_v2_invalid",msg 為 root teamsync.worker.json is not a valid Worker manifest v2(或 root teamsync.worker.json must contain valid Unicode)
internal_build_error、approved_dependency_unavailable、builder 未安裝、builder 行程無法啟動或逾時503detail: {code: "builder_unavailable", message: "Department Worker builder is unavailable"}
builder 的 stderr 不是恰好一行可辨識的內容,或其輸出格式錯誤(包含回執屬於不同的 builder 政策,或摘要與輸出檔案不符)503detail: {code: "builder_invalid_output", message: "Department Worker builder is unavailable"}
後端對證據(摘要、回執、SBOM 及其 schema)的交叉檢查失敗503detail: {code: "builder_invalid_output", message: "Department Worker builder returned invalid evidence"}

要知道確切的拒絕代碼,請在本機執行同一個 builder:teamsync-worker validate(以及 pack、submit、dev、generate)會印出 teamsync_worker_failed:authoritative builder rejected project: <code>。請使用你要提交的那個後端所提供的 CLI,讓兩端執行相同的 builder 政策(範本與原始碼下載)。建置之前的外層失敗(project_multipart_invalid、project_archive_invalid 等)維持各自的 type;完整的分支表在提交版本。

確定性身分

同一份被接受的輸入在同一政策下重跑會產生相同的摘要(測試強制輸出逐位元組相同):

摘要範圍
project_digest提交的壓縮檔位元組
manifest_digest正規 manifest 位元組(去掉結尾換行)
contract_digest正規 api 物件
lock_digest / dependency_digestlockfile/解析後的依賴圖
bundle_digest輸出的 index.mjs
sbom_digest / build_receipt_digest正規 SBOM/回執
release_generationsha256("teamsync-edge-worker-release-generation-v2\0" + project_digest + "\0" + dependency_digest + "\0" + builder_policy_version)

建置回執(receiptVersion: "1")釘住以上全部,外加完整的 19 欄 builderPolicy(目前 department-worker-project-v2.10)與靜態資料證據。SBOM 是 CycloneDX 1.6:根元件 urn:teamsync:department-worker:<name>@<version>、每個目錄依賴一個 library 元件(來自 npm integrity 的 SHA-512、SPDX license、purl)、每個靜態 JSON 檔一個 data 元件。兩者存在版本列上(MEDIUMTEXT),並由 GET /worker/versions/{id} 以型別化形式回傳。

builder 政策是 release_generation 的輸入之一,所以同一份壓縮檔在較新的政策下提交會得到不同的 release_generation — 並且一如往常產生新的修訂版。

builder 政策與能力關卡

每個版本都記錄了建置它的政策(版本上的 build_policy_version,回執中的 builderPolicy.version)。後端拒絕政策與自己不同的 builder 回執,所以每一次新提交都以後端目前的政策建置:截至後端 5.22.0 為 department-worker-project-v2.10(5.10.11 時是 v2.5,5.21.0 時是 v2.9;v2.6 到 v2.9 這幾步都在後端 v5.13.0 首次發布,v2.10 則是在 v5.22.0)。GET /starters 以 builder_policy_version 回報相同的值。版本會保留建置它時的政策:舊版本不會被重建,舊的 release 繼續跑在舊的 runtime 上。

執行期功能由正在上線的 release 的最低政策決定(政策以數字的 major.minor 比較;格式錯誤或不屬於此家族的政策字串永遠不算符合):

最低政策能力解鎖什麼低於最低政策的上線 release 的回應
department-worker-project-v2.6secrets僅可寫入的 Worker 機密:機密清單,以及 handler 內的 context.secrets.get(name)(Worker 機密)GET /worker/secrets:409 worker_secrets_unsupported。DELETE /worker/secrets/{name}:200,deleted: false(這種 release 不可能存有機密)
department-worker-project-v2.7salted_secrets加鹽的機密生命週期,每一次新的機密寫入都需要PUT /worker/secrets/{name}:409 worker_secrets_unsupported
department-worker-project-v2.8operation_settings逐操作存取與操作 API 金鑰(逐操作存取);部署請求中的 operation_settings;生成的 OpenAPI 內的逐操作欄位;Worker 全域金鑰輪替變成僅限舊版讀取或取代操作設定、輪替或撤銷操作金鑰,以及帶有 operation_settings 的部署請求:409 operation_settings_require_supported_release
department-worker-project-v2.9service_account_credentials服務帳號綁定、憑證交付與 context.teamsync(apiBase、authHeaders())(服務帳號)綁定服務帳號:409 service_account_credentials_require_supported_release
department-worker-project-v2.10state_migration平台維運者可以把 Worker 連同它儲存的一切搬到另一個 Worker 位置(搬移 Worker);handler 契約與 API 路由都不變沒有任何路由受影響。在部署以 v2.10 或更新版本建置的修訂版之前,搬移會被拒絕;Worker 照常繼續服務

這些 409 的回應本文都帶有代碼:detail 為 {code, message}。訊息分別是 This deployed Worker release does not support Worker secrets; re-upload and deploy the project、Re-upload and deploy this Worker with the operation-settings runtime policy before changing per-operation access,以及 The <live or target> release does not support service-account bindings; re-upload and deploy the project first。

作者要做什麼。 在政策升級之前建置的版本永遠不會獲得新功能。重新上傳專案(POST /worker/versions 會以目前政策重新建置並建立新修訂版),然後部署該修訂版。任何上線 release 早於該功能的 Worker 都適用同樣的做法,包含在升級到 5.21.0 之前部署的 Worker。

降級規則。 在較新的上線 release 上部署較舊政策的修訂版,不是被拒絕,就是先清除較舊 runtime 無法持有的狀態,因此不會有東西被無聲地遺棄:

  • 一旦有以 v2.8 或更新政策建置的 release 在線上,就不能部署以低於 v2.8 建置的修訂版:409 operation_settings_require_supported_release(The target release does not support operation settings),即使部署請求沒有帶 operation_settings 也一樣。
  • 在支援機密(v2.6 或更新)的上線 release 上部署以低於 v2.6 建置的修訂版,會先清除該部門 Worker 的全部 Worker 機密,不檢查是否真有機密存在;稽核事實為 secrets.purged_before_legacy_deploy。
  • 上線 release 已交付過服務帳號憑證時,部署以低於 v2.9 建置的修訂版會被拒絕:409 service_account_credentials_require_supported_release(Unbind every service account (PUT operation-settings) before deploying a release without service-account support)。

崩潰安全的提交編排

  1. 在任何一個產物寫入之前,先提交一筆租戶擁有的 artifact_cleanup outbox 意圖,指名精確的 project + bundle key。
  2. 兩個產物帶 sha256digest metadata 上傳,且不帶 object Tagging,key 帶租戶前綴(edge-workers/<sha256(tenant)[:32]>/{project|bundle}/<digest>-<artifact_id>.<ext>)。
  3. 單一交易:必要時建立單例 Worker(資料庫唯一約束是併發首次提交的最終權威 → 409)、插入不可變 ready 版本(revision = latest_revision + 1)、寫入 submission.created 稽核事實,並原子性**收養(取消)**清理意圖。
  4. 提交確認遺失時,route 會用每一個身分釘選欄位(租戶、建立者、各摘要、兩個物件 key、專案大小與 builder 政策版本;reconcile_committed_submission)讀回租戶擁有的版本,再決定修訂版是否落地;清理意圖以 artifact_id 讀回而不是再造一筆。未提交成功的提交會讓意圖立即到期(submission_not_committed),而週期性調節器只刪除沒有被引用的 key — 請求崩潰或產物儲存中斷既不會產生無主計費物件,也不會刪掉已提交的證據(產物與清理)。

201 回應回傳 worker 加上完整版本視圖。提交永不隱式部署 — 見部署與輪詢。部門管理者可以用 GET /worker/versions/{version_id}/source 再次下載任一版本儲存的壓縮檔(提交版本)。

Last updated on