稽核與控制面稽核
稽核寫入器
同交易、有界、只追加。每筆事實在 edge_worker_audit_logs 一列;永不記錄原始碼、bundle、API 金鑰、Worker 機密值或服務帳號密碼 — 只有摘要(機密值與服務帳號憑證使用加鹽摘要)。寫入器把 actor_type(32)、auth_method(32)、action(128)、resource_type(64)、result(32)、reason_code(64)、digest(64)截斷到欄位長度,flush 但永不 commit(交易由 route/task 擁有)。
冪等性在應用層:帶明確的 audit_id,寫入器在所有正規化欄位吻合時回傳既有列,漂移時丟出 "audit id is already bound to different facts"。帶明確 id 的寫入在 SAVEPOINT 內插入,所以兩個行程對同一個 id 競爭時,由主鍵決定誰勝出,落敗的一方以加鎖讀取重新讀取該列,而不是讓自己的交易失敗。控制面稽核通道利用這點,把 outbox 列 id 當 audit id,讓復原重放冪等;退役確認基於同樣的理由使用確定性 id。表本身沒有唯一約束。
記錄的事實
actor_type/auth_method 是 principal 的值(例如 user/jwt),任務寫入的事實則為 system/system。result ∈ success(預設)/ failed / uncertain。所有資料列都帶有部門的 company_id 與 department_id。
提交與部署
action | resource_type | 行為者 | digest |
|---|---|---|---|
submission.created | edge_worker_version | principal | manifest 摘要 |
deployment.submitted | edge_worker_version | principal | manifest 摘要 |
deployment.reconciliation_submitted | edge_worker_version | principal(公司管理者修復) | manifest 摘要 |
deployment.completed | edge_worker_version | system | bundle 摘要 |
deployment.failed | edge_worker_version | system、result="failed"、reason_code = 錯誤代碼(dispatch_not_accepted、retry_exhausted、retry_transport_unavailable、department_deleted…) | manifest 或 bundle 摘要 |
deployment.uncertain | edge_worker_version | system、result="uncertain"、reason_code = 錯誤代碼 | bundle(退回 manifest)摘要 |
secrets.purged_before_legacy_deploy | edge_worker | system(resource_id = Worker id) | — |
存取政策與執行期設定
action | resource_type | 行為者 | digest |
|---|---|---|---|
access_policy.replaced | edge_worker_access_policy | principal | 正規 {ip_allowlist, mode} 的 sha256 |
access_policy.api_key.rotated | edge_worker_access_policy | principal | 金鑰的 SHA-256 摘要(永遠不是金鑰本身) |
access_policy.api_key.revoked / access_policy.api_key.revoke_noop | edge_worker_access_policy | principal | sha256("revoked") |
runtime_config.replaced | edge_worker_runtime_config | principal | 正規值 JSON 的 sha256 |
runtime_config.deleted | edge_worker_runtime_config | principal | sha256("null") |
操作設定、Worker 機密與服務帳號憑證
這些全都走下方的控制面稽核通道(行為者 = 發出請求的 principal;resource_id = Worker id)。唯一例外:存活的 release 早於 Worker 機密支援時,刪除機密會由請求自己的交易直接寫入 secrets.delete_noop。
action | resource_type | digest |
|---|---|---|
operation_settings.replaced | edge_worker_operation_settings | 正規 {api_base, default_service_account_id, operations} 的 sha256 |
operation_settings.staged | edge_worker_operation_settings | 相同的摘要形式;部署為目標 release 暫存設定表時,改寫此動作而非 replaced(指令帶有 liveReleaseGeneration) |
operation_settings.api_key.rotated | edge_worker_operation_settings | 金鑰的 SHA-256 摘要(永遠不是金鑰本身) |
operation_settings.api_key.revoked / operation_settings.api_key.revoke_noop | edge_worker_operation_settings | sha256("operation-key-revoked:<operation_id>") |
secrets.put | edge_worker_secret | 加鹽的值摘要 sha256(salt || value);salt 從不儲存 |
secrets.deleted / secrets.delete_noop | edge_worker_secret | sha256("delete:<NAME>") |
service_accounts.credentials.put | edge_worker_service_account | 憑證的加鹽 SHA-256(passwordDigest) |
service_accounts.credentials.deleted / service_accounts.credentials.delete_noop | edge_worker_service_account | sha256("delete-service-account:<id>") |
服務帳號(直接資料列)
與變更寫在同一個資料庫交易內,不經過 outbox;resource_type 為 service_account,resource_id 為服務帳號 id,digest 為所示變更集合之正規 JSON(鍵排序、無空格)的 SHA-256。
action | 時機 | 雜湊的變更集合 |
|---|---|---|
service_account.created | 建立 | {"department_id":"<id>","username":"<name>"} |
service_account.credential_reset | 重設,不論結果 | {"delivery":"<delivered, not_deployed, unsupported_release, not_bound or failed>"} |
service_account.deleted | 刪除路由 | {"deleted":true} |
service_account.deleted | 部門刪除,每個服務帳號一列;auth_method="department_delete",行為者為 user(執行刪除的管理者)或 system | {"deleted":true,"reason":"department_deleted","service_account_id":"<id>"} |
service_account.chatrooms_changed | 每個處理過的聊天室一列 | {"operation":"<add or remove>","room_id":"<id>","status":"<status>"} |
見服務帳號。
退役確認
系統資料列(actor_type="system"、actor_id=""、auth_method="system"、resource_type="edge_worker"、resource_id = Worker id),使用確定性 id,立即提交;沒有任何 API 公開它們。詳見退役。
action | reason_code | 意義 |
|---|---|---|
department.worker_retired | — | provider 已完成 Worker script 的退役 |
department.worker_script_neutralized | 被處理的 dispatch namespace(共用位置的 namespace,或該環境自己舊有位置的 namespace) | 該 namespace 的雲端墓碑已結清 |
department.worker_script_deleted | 被處理的 dispatch namespace(同上) | 每次嘗試處理該 script 之前(不一定接著上傳)寫入的標記;記錄已對該 namespace 採取過動作 |
department.worker_tombstone_deleted | 被處理的 dispatch namespace(同上) | 已退役 Worker 的墓碑 script 從該 namespace 移除之後寫入 |
僅限地端的 edge_worker.resync 系統資料列(resource_type 為 edge_worker_version 或 edge_worker,result 為 success 或 failed)記錄 resync 的結算與變更。reason_code 為 promoted(被攔下的部署已完成)、settled_failed(被攔下的部署失敗)、edge_worker_deleted(被攔下的部署因其 Worker 已刪除而失敗)、cleared、retired、unretired、rolled_back、edge_data_held 或 edge_data_mismatch_accepted;resync CLI 見地端。
控制面稽核:變更必須以回執背書的事實收尾
存取政策、執行期設定、操作設定(取代、部署暫存、金鑰輪替/撤銷)、Worker 機密(寫入/刪除)與服務帳號憑證(寫入/刪除)的變更,走耐久的 control_audit outbox 意圖,且在 provider I/O 之前提交:
- 請求路徑:入列意圖(寬限 300 秒)→ 把簽章的
purpose=control指令送到 Durable Object → 成功時盡力結算(寫稽核+完成)。本地結算失敗被刻意吞掉 — provider 回執才是耐久的重放權威;絕不毀掉成功的控制結果,尤其是一次性的 API 金鑰。provider 的 revision 衝突把意圖結算為failed revision_conflict(HTTP 409);Durable Object 回傳的有型別拒絕(例如secret_limit_exceeded)則以該代碼把意圖結算為failed;結果未知則排 15 秒重試(provider_outcome_unknown,HTTP 503)。 - 復原(60 秒迴圈):讀取 Durable Object 回執(
control.receipt.get)— 永不重送變更 — 依下方的稽核契約、預期 revision 與預期摘要驗證精確的操作結果 — 然後在單一交易內寫下確定性稽核列並完成意圖。回執不存在 →failed control_receipt_not_found;回執連續三次無效 →uncertain control_receipt_invalid(需維運人員關注);不帶已記錄服務位置、且無法對應到 Worker 目前服務位置的回執列 →failed legacy_serving_target_unavailable(自後端 5.22.0 起)。 - 確認輪:完成後以
control.receipt.ack告知 Durable Object 可丟棄回執;必須恰好回{"acknowledged": true},失敗按 15 秒 → 3600 秒階梯重試;上述legacy_serving_target_unavailable的情況則改為停止重試。
指令 operation | 稽核 action(no-op 動作) |
|---|---|
access_policy.replace | access_policy.replaced |
access_policy.api_key.rotate | access_policy.api_key.rotated |
access_policy.api_key.revoke | access_policy.api_key.revoked(access_policy.api_key.revoke_noop) |
runtime_config.replace | runtime_config.replaced |
runtime_config.delete | runtime_config.deleted |
operation_settings.replace | operation_settings.replaced,部署暫存時為 operation_settings.staged |
operation_settings.api_key.rotate | operation_settings.api_key.rotated |
operation_settings.api_key.revoke | operation_settings.api_key.revoked(operation_settings.api_key.revoke_noop) |
secrets.put | secrets.put |
secrets.delete | secrets.deleted(secrets.delete_noop) |
service_accounts.credentials.put | service_accounts.credentials.put |
service_accounts.credentials.delete | service_accounts.credentials.deleted(service_accounts.credentials.delete_noop) |
回執規則:
- 結果的鍵集合必須恰為
{conflict, current},兩個 API 金鑰撤銷另加revoked,secrets.delete與service_accounts.credentials.delete另加deleted;conflict必須為 false。 - 存取政策與執行期設定:revision 為
base+1(no-op 撤銷為base+0),另須與指令的模式、允許清單、後四碼或值摘要一致。 - 操作設定:
current.state == "reconciled"且current.revision等於baseRevision + 1(什麼都沒撤銷的撤銷為+ 0);replace 另外要求current.releaseGeneration等於指令的值。 - 機密:
put要求current鍵恰為{name, sha256, updated_at},且名稱與值摘要須與指令相同;delete要求{name, deleted, updated_at},且current.deleted等於result.deleted(false時採用 no-op 動作)。 - 服務帳號憑證:
put要求{serviceAccountId, username, passwordDigest, updated_at}且與指令相同;delete要求{serviceAccountId, deleted, updated_at}(false時採用 no-op 動作)。 updated_at必須是有效時間戳。無 revision 的操作(secrets.*、service_accounts.credentials.*)不帶baseRevision。
已退役的 Worker。 讀取回執之前,兩個輪次都先詢問該 Worker 是否已退役:它的部門已軟刪除且存在 department.worker_retired 事實,或它的公司已被硬刪除(沒有任何公司有該 id 的部門資料列,也沒有 Worker 資料列)。若是,則不呼叫 provider。復原輪取消 outbox 列(cancelled,last_error_code="worker_retired",last_error="Worker retired before its control receipt was read"),且不寫稽核列,因為該次變更的回執隨 Worker 的 Durable Object 一起消失了。確認輪停止重試已完成的列(next_attempt_at 清空,last_error_code="worker_retired",last_error="provider receipt retired with its Worker")。兩者都以 INFO 記錄 Edge Worker control receipt settled: its Worker is retired — 絕不當成錯誤、絕不重試 — 而 60 秒迴圈的彙總行把它們計為 retired 與 ack_retired。
明文機密永遠進不了 outbox — 入列驗證會拒絕 Worker 層級金鑰指令上的 api_key/apiKey 欄位、操作金鑰指令上的 key/apiKey 欄位、機密指令上的 value 欄位,以及服務帳號憑證指令上的 password/salt 欄位;流動的只有摘要(API 金鑰的 SHA-256 與後四碼;機密值或憑證的加鹽 SHA-256,salt 只會加進即時的 provider 指令)。