ait-server REST API 參考
呼叫每一個 1.1.1 ait-server HTTP 操作,包含與實作對齊的請求欄位、響應、錯誤、恢復流程和安全邊界。
適用對象: 整合方、runner 作者與維運
這份參考涵蓋什麼#
這是 1.1.1 版 ait-server 發布版路由器對外暴露的完整 HTTP 介面面。 它包含 51 個路由模板、71 個 HTTP 方法繫結,以及把共享路由所 接受的動作字尾展開之後的 78 個可呼叫操作。
這套 API 是 JSON over HTTP,既有資源讀寫,也有 :runCi、:close、 :submit 這類顯式的動作端點。這些動作端點是有意為之的;整個介面面 並不是嚴格統一的 CRUD API。
本章記錄的是 ait、ait-runner 和恢復工具所使用的服務端介面。普通 使用者應優先看 `ait` 命令指南, 因為客戶端會強制執行時序和相容性檢查,而裸 HTTP 呼叫方得自己實作 這些。
安全與傳輸邊界#
把 1.1.1 的監聽器當成可信網路內的服務邊界。不要把裸監聽器暴露到公網。 把它放在環回地址或經過審查的私有網路上,並在可信入口處終結 TLS、呼叫 方校驗、請求限額和訪問策略。預設監聽器是 127.0.0.1:8088;要換繫結地址,請使用服務端命令的顯式選項。
恢復令牌和 Worker Job 租約令牌屬於維運能力,不能替代入口處的認證。 不要記錄或洩露它們。
下面的示例中:
BASE=http://127.0.0.1:8088
REPOSITORY_INDEX=17通用協議規則#
定址與識別符號#
{repository_index}和{worker_job_index}是規範的無符號十進位制u32值。0有效;01、正負號、空格以及超過4294967295的值 都會被拒絕。- Repository 名稱屬於展示和發現用的資料。名稱可以重複,也不決定權威 歸屬;決定權威的是數字形式的 Repository 索引。
- Task、Change、Patchset、Snapshot、Land、Plan 和 revision 識別符號都是 不透明的公開識別符號。請保持它們原樣的拼寫,必要時對路徑段做 URL 編碼。
- 以
_at_s結尾的時間欄位是無符號 Unix 秒。created_at這類欄位 是 RFC 3339 字串。某個事件不存在時,投影裡通常是null,而不是 編造一個時間戳。
請求頭與請求體限額#
JSON 請求用 Content-Type: application/json,JSON 響應用 Accept: application/json。標準 JSON 提取器套用框架預設的 2 MiB 請求體上限。 以下批次路由把請求上限提高到 2 GiB:
- zstd 批次 plan、commit 和 pull-manifest 請求;
- zstd Object Pack 與 Tree Pack 傳輸路由;以及
- Repository 恢復檔案上傳。
裸 pack 和恢復檔案的請求體要求確切的媒體型別:
| 請求體 | 必需的 Content-Type | 響應型別 |
|---|---|---|
| Object Pack | application/vnd.ait.remote-sync.object-pack+zstd | 同一值 |
| Tree Pack | application/vnd.ait.remote-sync.tree-pack+zstd | 同一值 |
| 退役或恢復檔案 | application/vnd.ait.remote-authority-file.v1 | 同一值 |
空的 Object Pack 和 Tree Pack 上傳會被拒絕。超過目前生效體積限額的 請求,在進入處理器之前就會被拒絕。
成功與錯誤響應#
多數成功操作傳回 200 OK,並帶上該路由特有的 JSON 值。Repository 註冊 在追加了新權威時傳回 201 Created,在傳回已匹配的既有註冊時傳回 200 OK。啟動 Repository 恢復會話傳回 201 Created。
服務端生成的應用錯誤使用這個穩定的響應體:
{"error":"human-readable diagnostic"}| 狀態碼 | 含義 |
|---|---|
200 | 讀取、冪等重放、更新、動作或二進位下載成功。 |
201 | 新建了 Repository 權威或恢復會話。 |
400 | 路徑值、請求欄位或狀態遷移無效,或路由自行處理的 JSON 格式有誤。 |
404 | 未知的路由、Repository、實體、pack、檔案或動作字尾。 |
405 | 路由存在,但這個 HTTP 方法沒有繫結。 |
409 | 權威衝突、生命週期衝突、Line head 過期、受治理的目標 Line 移動,或退役確認不匹配。 |
413 | 請求體超過該路由目前生效的限額。 |
415 | JSON 提取器或二進位路由拒絕了這個請求的媒體型別。 |
422 | JSON 語法可以接受,但沒法反序列化成帶型別的請求。 |
500 | 權威操作損壞、不相容、I/O 失敗,或其他內部原因失敗。 |
503 | 啟動尚未完成,或某個權威正忙但可重試。應用生成的 503 錯誤會帶上 Retry-After: 1。 |
不要把診斷文字當成長期有效的機器契約來解析。請按 HTTP 狀態碼和文件 寫明的響應欄位做分支;文字留給維運人員診斷用。
啟動行為#
監聽器在全部服務權威開啟完成之前就已經可用。啟用之前:
GET /healthz傳回503和{"ready":false,"status":"starting"};- 其他所有路徑傳回
503和{"error":"ait-server is starting","ready":false,"status":"starting"}。
就緒路由器會原子地替換掉啟動期代理。啟用之後, GET /healthz 傳回 200,且 ready: true。
Repository 生命週期準入#
GET、HEAD 和 OPTIONS 請求可以透過生命週期準入。 /v1/native/repository-authorities/{repository_index}/... 下的寫操作 通常要求 Repository 處於 active 狀態。
退役狀態查詢/啟動、退役清除、退役中止,以及 Worker Job 的 :claim、 :heartbeat、:complete 和 :fail 這幾個排空動作,在 Repository 退役期間仍然可用。針對退役中或已清除的 Repository 的其他寫操作會被 拒絕。這樣在途工作能排空,備份或恢復流程能走完,同時又不再接納新的 工作流程工作。
完整操作索引#
握手傳回的緊湊 endpoints 陣列只是發現用的提示,不是完整的路由列表。 下面這些表才是 1.1.1 發布版的完整介面面。
服務發現#
| 方法與路徑 | 操作 |
|---|---|
GET /healthz | 讀取就緒狀態和權威後端。 |
GET /v1/handshake | 讀取協議、包、CI、runner 和緊湊端點能力。 |
GET /v1/native/capabilities | 讀取維運層面的 Repository 與 Worker Job 契約。 |
Repository 登錄檔、備份、恢復與作業#
| 方法與路徑 | 操作 |
|---|---|
GET /v1/native/repository-authorities | 列出或發現 Repository 權威。 |
POST /v1/native/repository-authorities | 註冊一個 Repository 權威。 |
GET /v1/native/repository-authorities/{repository_index} | 讀取一個 Repository 權威。 |
POST /v1/native/repository-authorities/{repository_index}:runCi | 入隊 Repository CI。 |
GET /v1/native/repository-authorities/{repository_index}/retirement | 讀取退役的排空/匯出狀態。 |
POST /v1/native/repository-authorities/{repository_index}/retirement | 開始退役。 |
GET /v1/native/repository-authorities/{repository_index}/retirement/files/{file_path} | 下載清單中列出的一個權威檔案。 |
POST /v1/native/repository-authorities/{repository_index}/retirement/purge | 在收到確切的匯出確認之後清除。 |
POST /v1/native/repository-authorities/{repository_index}/retirement/abort | 把退役中的 Repository 恢復成 active 狀態。 |
POST /v1/native/repository-restores | 開始一次恢復會話。 |
PUT /v1/native/repository-restores/{restore_token}/files/{file_path} | 上傳清單中列出的一個恢復檔案。 |
POST /v1/native/repository-restores/{restore_token}/commit | 校驗並啟用恢復出來的 Repository。 |
POST /v1/native/worker-jobs:claim | 認領下一個匹配的外部 runner 作業。 |
GET /v1/native/repository-authorities/{repository_index}/worker-jobs | 列出 Repository 的 Worker Job。 |
GET /v1/native/repository-authorities/{repository_index}/worker-jobs/{worker_job_index} | 讀取一個 Worker Job。 |
POST /v1/native/repository-authorities/{repository_index}/worker-jobs/{worker_job_index}:claim | 認領指定的那一個作業。 |
POST /v1/native/repository-authorities/{repository_index}/worker-jobs/{worker_job_index}:heartbeat | 延長活動中的租約。 |
POST /v1/native/repository-authorities/{repository_index}/worker-jobs/{worker_job_index}:complete | 提交一次成功的嘗試。 |
POST /v1/native/repository-authorities/{repository_index}/worker-jobs/{worker_job_index}:fail | 記錄一次可重試或終態的嘗試失敗。 |
Line、Snapshot 與遠端同步#
| 方法與路徑 | 操作 |
|---|---|
GET /v1/native/repository-authorities/{repository_index}/lines | 列出 Line。 |
GET /v1/native/repository-authorities/{repository_index}/lines/{line_name} | 讀取一個 Line。 |
PUT /v1/native/repository-authorities/{repository_index}/lines/{line_name} | 以比較並設定的方式建立或更新一個 Line。 |
POST /v1/native/repository-authorities/{repository_index}/lines/{line_name}:close | 關閉一個 Line。 |
POST /v1/native/repository-authorities/{repository_index}/snapshots:exists | 批次檢測 Snapshot 是否存在。 |
GET /v1/native/repository-authorities/{repository_index}/snapshots/{snapshot_id} | 匯出 Snapshot 後設資料,可選帶上內容。 |
POST /v1/native/repository-authorities/{repository_index}/remote-sync/zstd-bulk/plan | 區分出已有和缺失的 Snapshot 與 pack。 |
POST /v1/native/repository-authorities/{repository_index}/remote-sync/zstd-bulk/commit | 提交已上傳的 pack 後設資料、定位符、Snapshot,以及可選的 Line 更新。 |
GET /v1/native/repository-authorities/{repository_index}/remote-sync/zstd-bulk/import-manifests/{snapshot_id} | 取得一個 Snapshot 的匯入閉包。 |
POST /v1/native/repository-authorities/{repository_index}/remote-sync/zstd-bulk/pull-manifests | 取得一個有界祖先範圍的匯入閉包。 |
GET /v1/native/repository-authorities/{repository_index}/remote-sync/zstd-bulk/object-packs/{pack_id} | 下載一個 Object Pack。 |
PUT /v1/native/repository-authorities/{repository_index}/remote-sync/zstd-bulk/object-packs/{pack_id} | 上傳一個 Object Pack。 |
GET /v1/native/repository-authorities/{repository_index}/remote-sync/zstd-bulk/tree-packs/{pack_id} | 下載一個 Tree Pack。 |
PUT /v1/native/repository-authorities/{repository_index}/remote-sync/zstd-bulk/tree-packs/{pack_id} | 上傳一個 Tree Pack。 |
Task 與佇列讀模型#
| 方法與路徑 | 操作 |
|---|---|
GET /v1/native/repository-authorities/{repository_index}/tasks | 列出 Task,最新的在前。 |
POST /v1/native/repository-authorities/{repository_index}/tasks | 建立一個 Task,可選擇繫結 Plan。 |
POST /v1/native/repository-authorities/{repository_index}/task-start | 原子地建立/修訂/選定 Plan 條目、Task 和第一個 Change。 |
GET /v1/native/repository-authorities/{repository_index}/tasks/{task_ref} | 讀取一個 Task。 |
POST /v1/native/repository-authorities/{repository_index}/tasks/{task_ref}:close | 完成或放棄一個 Task。 |
GET /v1/native/repository-authorities/{repository_index}/read/tasks/{task_id}/audit | 讀取 Task 收尾證據。 |
GET /v1/native/repository-authorities/{repository_index}/read/queue-summary | 讀取佇列彙總投影。 |
GET /v1/native/repository-authorities/{repository_index}/read/task-queue | 讀取 Task 佇列投影。 |
GET /v1/native/repository-authorities/{repository_index}/read/reviewer-inbox | 讀取分配給該 Repository 的評審工作。 |
Change、Patchset、評審、策略、CI 與 Land#
| 方法與路徑 | 操作 |
|---|---|
GET /v1/native/repository-authorities/{repository_index}/changes | 列出 Change,最新的在前。 |
POST /v1/native/repository-authorities/{repository_index}/changes | 在某個 Task 下建立一個 Change。 |
GET /v1/native/repository-authorities/{repository_index}/changes/{change_ref} | 讀取一個 Change。 |
POST /v1/native/repository-authorities/{repository_index}/changes/{change_ref}:close | 歸檔、放棄或替代一個 Change。 |
POST /v1/native/repository-authorities/{repository_index}/changes/{change_ref}:selectPatchset | 選定一個 Patchset。 |
POST /v1/native/repository-authorities/{repository_index}/changes/{change_ref}:requestReview | 向一個評審組發起評審。 |
POST /v1/native/repository-authorities/{repository_index}/changes/{change_ref}:submit | 為一個 Change 提交直接 Land。 |
GET /v1/native/repository-authorities/{repository_index}/changes/{change_ref}/patchsets | 按序號順序列出 Patchset。 |
POST /v1/native/repository-authorities/{repository_index}/changes/{change_ref}/patchsets | 發布一個 Patchset。 |
GET /v1/native/repository-authorities/{repository_index}/changes/{change_ref}/reviews | 讀取評審記錄行和彙總計數。 |
POST /v1/native/repository-authorities/{repository_index}/changes/{change_ref}/reviews | 記錄一次評審動作。 |
GET /v1/native/repository-authorities/{repository_index}/patchsets/{patchset_id} | 讀取一個 Patchset 及其 CI 投影。 |
POST /v1/native/repository-authorities/{repository_index}/patchsets/{patchset_id}:runCi | 啟動或複用 Patchset CI,必要時入隊一個 Worker Job。 |
POST /v1/native/repository-authorities/{repository_index}/patchsets/{patchset_id}:evaluatePolicy | 評估目前的 attestation、CI 和評審關卡。 |
GET /v1/native/repository-authorities/{repository_index}/patchsets/{patchset_id}/attestation | 讀取最新的 attestation。 |
PUT /v1/native/repository-authorities/{repository_index}/patchsets/{patchset_id}/attestation | 追加一條新的 attestation。 |
GET /v1/native/repository-authorities/{repository_index}/patchsets/{patchset_id}/policy | 讀取最新的策略決定,或待評估狀態的投影。 |
GET /v1/native/repository-authorities/{repository_index}/read/patchsets/{patchset_id}/ci-status | 讀取緊湊的 CI 就緒度以及近期作業。 |
GET /v1/native/repository-authorities/{repository_index}/lands/{submission_id} | 讀取一次 Land 嘗試。 |
POST /v1/native/repository-authorities/{repository_index}/task-land | 原子地解析並 Land 一個 Task 或指定的 Change。 |
POST /v1/native/repository-authorities/{repository_index}/history-promotion:prepare | 為受治理的遠端 Land 準備已記錄的本地 land 歷史。 |
Sprint 與 Plan 權威#
| 方法與路徑 | 操作 |
|---|---|
GET /v1/native/repository-authorities/{repository_index}/sprints | 列出 Plan,可按 artifact 路徑過濾。 |
POST /v1/native/repository-authorities/{repository_index}/sprints | 建立一個 Plan 和它的第一個 revision。 |
GET /v1/native/repository-authorities/{repository_index}/sprints/{plan_id} | 讀取 Plan 詳情和 head revision。 |
PATCH /v1/native/repository-authorities/{repository_index}/sprints/{plan_id} | 修改 Plan 狀態。 |
GET /v1/native/repository-authorities/{repository_index}/sprints/{plan_id}/revisions | 列出 Plan revision,最新的在前。 |
POST /v1/native/repository-authorities/{repository_index}/sprints/{plan_id}/revisions | 追加一個 revision,可選帶 head 比較並設定。 |
GET /v1/native/repository-authorities/{repository_index}/sprints/{plan_id}/revisions/{plan_revision_id} | 讀取一個完整的 revision。 |
GET /v1/native/repository-authorities/{repository_index}/sprints/{plan_id}/revisions/{plan_revision_id}/artifacts | 讀取同一個完整 revision,並帶上它打包後的 artifact 投影。 |
PUT /v1/native/repository-authorities/{repository_index}/sprints/{plan_id}/revisions/{plan_revision_id}/artifacts | 預留路由;1.1.1 拒絕寫入支撐性 artifact。 |
POST /v1/native/repository-authorities/{repository_index}/sprint-plan-ids/by-contains | 查詢匹配全部歸一化詞條的活動 Plan ID。 |
POST /v1/native/repository-authorities/{repository_index}/sprint-task-linkage/resolve | 歸一化並校驗 Task 到 Plan 的關聯。 |
GET /v1/native/repository-authorities/{repository_index}/read/plans/candidate-inputs | 讀取匹配的 Plan 及其關聯的 Task 候選。 |
發現類端點#
健康檢查#
curl --fail-with-body "$BASE/healthz"就緒時的響應:
{
"ready": true,
"authority_backend": "binary_v0",
"repository_identity": "repository_index",
"operational_capabilities": {
"contract": "ait.server.operational-capabilities.v1",
"repository_identity": "binary-repository-index.v0",
"worker_job_identity": "binary-worker-job-key.v0",
"runner_contracts": ["ait.runner.native-job.v3"]
}
}把 HTTP 200 加上 ready: true 當作程序已就緒。它並不能證明備份是 最新的,或者是可恢復的。
握手#
curl --fail-with-body "$BASE/v1/handshake"響應欄位如下:
| 欄位 | 含義 |
|---|---|
ready | 就緒路由器的狀態。 |
contract_version | agent/服務端的協議相容版本。 |
package_version | 正在執行的 ait-server 包版本。 |
authority_backend | 1.1.1 中是 binary_v0。 |
repository_identity | repository_index。 |
ci_capabilities | 遠端同步特性和原生 runner 契約。 |
operational_capabilities | Repository 與 Worker Job 的身份契約。 |
supported_async_job_types | 服務端已知的十種持久作業型別名。 |
endpoints | 緊湊的發現子集;不是完整的操作索引。 |
原生 runner 能力宣告的是 ait.runner.native-job.v3、結果契約 ait.runner.native-result.v1、佇列契約 ait.server.worker-job.service.v1、遠端 Snapshot 輸入、直接引數 執行,以及平臺對應的 ci/run.sh 或 ci/run.ps1 入口。
維運能力#
GET /v1/native/capabilities 傳回 operational_capabilities 物件, 不帶更大的握手載荷。在動手寫直連的 Worker Job 客戶端之前先看它。
Repository 註冊與 Repository CI#
註冊一個權威#
curl --fail-with-body \
-H 'Content-Type: application/json' \
-d '{"repository_name":"ait-native-site","namespace":"w","policy_flags":0}' \
"$BASE/v1/native/repository-authorities"| 請求欄位 | 是否必需 | 規則 |
|---|---|---|
repository_name | 是 | 非空的 UTF-8 展示名;允許重複。 |
namespace | 是 | 零個、一個或兩個 ASCII 字母、數字、_ 或 -。服務端按確切的零填充位元組儲存。 |
policy_flags | 是 | 無符號 u8。 |
未知欄位會被拒絕。一個非空名稱空間如果已被某個存活的 Repository 佔用, 只有在名稱和策略標誌也一致時才會走重放;否則註冊就是衝突。
{
"contract": "ait.server.repository-registration.v1",
"created": true,
"repository": {
"repository_index": 17,
"repository_name": "ait-native-site",
"lifecycle_kind": 1,
"namespace": "w",
"policy_flags": 0,
"created_at_s": 1786800000,
"updated_at_s": 1786800000,
"tombstoned": false
}
}把響應裡的 repository_index 持久化下來。不要在多個權威共用同一個 展示名時,靠名稱重新發現再猜一個。
發現並讀取權威#
curl --get --fail-with-body \
--data-urlencode 'repository_name=ait-native-site' \
"$BASE/v1/native/repository-authorities"
curl --fail-with-body \
"$BASE/v1/native/repository-authorities/$REPOSITORY_INDEX"列表響應包含 contract、repositories、count 和一個 discovery 物件。discovery.routing_authority 是 false:按名稱過濾是用來盤點 清單的,不是用來選路由的。單項響應把一個 repository 包在 ait.server.repository-authority.v1 裡。
生命週期取值為 1 active、2 retiring、3 purged。已 purged 的條目 不出現在列表發現裡。
入隊 Repository CI#
這個路由在 Repository 段上使用動作字尾:
curl --fail-with-body \
-H 'Content-Type: application/json' \
-d '{"snapshot_id":"SNP-EXAMPLE"}' \
"$BASE/v1/native/repository-authorities/$REPOSITORY_INDEX:runCi"snapshot_id 是可選的。不填時,服務端選用邏輯 main 的目前 head。 兩種途徑都解析不到 Snapshot 時,請求失敗。響應包含 repository_index、snapshot_id、物理的 snapshot_index、queued、 Worker Job 投影,以及 delivery: "binary_worker_job"。
Repository 退役、匯出與恢復#
退役是 API 層面針對一個完整數字 Repository 權威的備份/匯出流程。它被 刻意拆成多段:先停掉新的寫操作,讓 Worker Job 排空,校驗每個匯出檔案, 最後清除還要求一次確切的確認。
1. 開始並輪詢退役#
下面這些 POST 操作沒有請求體:
curl --fail-with-body -X POST \
"$BASE/v1/native/repository-authorities/$REPOSITORY_INDEX/retirement"
curl --fail-with-body \
"$BASE/v1/native/repository-authorities/$REPOSITORY_INDEX/retirement"響應內容為:
{
"contract": "ait.server.repository-retirement.v1",
"repository": {"repository_index":17,"lifecycle_kind":2},
"drain": {"queued_worker_jobs":0,"running_worker_jobs":0},
"ready_for_export": true,
"manifest": {
"schema": "ait.remote-export.v1",
"state": "complete",
"repo_name": "ait-native-site",
"namespace": "w",
"exported_at_s": 1786800100,
"files": [
{"path":"...","size":1234,"sha256":"..."}
]
}
}在排隊中和執行中的 Worker Job 雙雙歸零之前,manifest 一直是 null。 只有 ready_for_export: true 才是取用所列檔案的許可。
2. 下載並校驗每個檔案#
對 manifest.files[] 的每一條,把它的相對 path 做 URL 編碼後取用:
curl --fail-with-body \
-o authority-file.bin \
"$BASE/v1/native/repository-authorities/$REPOSITORY_INDEX/retirement/files/<encoded-path>"響應是裸的 application/vnd.ait.remote-authority-file.v1。請對照清單校驗 size 和小寫十六進位制的 sha256。目前清單裡沒有的路徑傳回 404。
3. 只在匯出已持久化之後才清除#
把 整個未經改動的 manifest 物件 作為清除請求體 POST 過去:
curl --fail-with-body \
-H 'Content-Type: application/json' \
--data-binary @remote-export-manifest.json \
"$BASE/v1/native/repository-authorities/$REPOSITORY_INDEX/retirement/purge"服務端會重新計算它目前的完整匯出,並要求完全相等。不完整的確認、變過 的檔案清單,或者作業尚未排空,都會被拒絕。成功時傳回 contract、 purged、already_purged 和終態的 Repository 投影。重放同一份有效的 確認是冪等的。
中止退役#
在清除之前,這個無請求體的操作會把 Repository 恢復成 active 狀態:
curl --fail-with-body -X POST \
"$BASE/v1/native/repository-authorities/$REPOSITORY_INDEX/retirement/abort"響應會報告 aborted: true 和 already_aborted。已 purged 的權威無法 重新啟用。
恢復一個已匯出的權威#
用校驗過的清單,加上新登錄檔條目要用的 u8 策略標誌,開一個會話:
curl --fail-with-body \
-H 'Content-Type: application/json' \
-d '{"manifest":<complete-manifest>,"policy_flags":0}' \
"$BASE/v1/native/repository-restores"帶型別的請求只接受 manifest 和 policy_flags 兩個欄位。清單必須 滿足:
| 欄位 | 規則 |
|---|---|
schema | ait.remote-export.v1 |
state | complete |
repo_name | 非空的源 Repository 名稱。 |
namespace | 匯出時的確切名稱空間。它不能已被某個存活的 Repository 佔用。 |
exported_at_s | 以 Unix 秒錶示的匯出時間。 |
files | 由 {path, size, sha256} 條目組成的確切陣列。 |
201 響應傳回一個隨機的 32 個字元的十六進位制 restore_token,以及 state: "uploading"。未提交的會話最多接納 64 個。
按每個檔案確切的相對路徑和媒體型別逐個上傳:
curl --fail-with-body -X PUT \
-H 'Content-Type: application/vnd.ait.remote-authority-file.v1' \
--data-binary @authority-file.bin \
"$BASE/v1/native/repository-restores/$RESTORE_TOKEN/files/<encoded-path>"每次上傳都會對照清單裡的大小和摘要做檢查。然後用無請求體的方式提交:
curl --fail-with-body -X POST \
"$BASE/v1/native/repository-restores/$RESTORE_TOKEN/commit"提交會校驗整個暫存歸檔,追加一條新的數字 Repository 條目,把恢復出來 的權威歸一化到那個新索引上,並傳回 ait.server.repository-restore.v1。在同一個存活會話上重放提交,會傳回 已記錄的響應。恢復不保留舊的數字索引;響應裡的新索引才是權威。
Worker Job 相關 API#
服務端認識十種持久作業型別,但 1.1.1 的外部 runner 只能認領 7 (patchset.ci)和 11(repo.ci)。其餘型別屬於服務端內部操作, 外部認領端點會拒絕它們。
Worker Job 狀態是 1 queued、2 running、3 succeeded 和 4 failed。 (repository_index, worker_job_index) 這一對就是公開身份。
列出與檢視#
curl --get --fail-with-body \
--data 'state_kind=1' \
--data 'limit=50' \
"$BASE/v1/native/repository-authorities/$REPOSITORY_INDEX/worker-jobs"state_kind 可選,取值必須在 1..4。limit 預設 50,必須在 1 到 1000 之間。結果按最新在前排列。每個作業投影包含識別符號、 kind/type、狀態、重試狀態、結果、嘗試次數上限、錯誤型別、Patchset 或 Snapshot 引用、可用性、加鎖與更新時間,以及墓碑狀態。Patchset CI 作業 還會帶上目前的緊湊 CI 投影。
讀取指定的某個作業:
curl --fail-with-body \
"$BASE/v1/native/repository-authorities/$REPOSITORY_INDEX/worker-jobs/42"認領下一個或認領指定的#
全域性認領可以帶一個可選的 Repository 過濾條件:
{
"accepted_job_kinds": [7, 11],
"repository_indexes": [17],
"accepted_runtime_contracts": ["ait.runner.native-job.v3"]
}把它發到 POST /v1/native/worker-jobs:claim。兩個陣列都不能有重複項。 repository_indexes 為空陣列表示所有正在服務的 Repository。沒有任何 匹配時,響應是 200 且 claimed_job: null。
要認領一個已經選好的作業,把下面這個確切的請求體 POST 到 .../worker-jobs/{worker_job_index}:claim:
{"accepted_runtime_contracts":["ait.runner.native-job.v3"]}認領成功會傳回 attempt_count、十六進位制的 lease_token、 lease_expires_at_s、作業身份和 runtime_request。這個執行時請求會 選定一個遠端 Snapshot,宣告直接執行的 ci/run 引數向量,並設定超時。 請嚴格按這個請求執行;不要拿目前工作區頂替。
心跳、完成與失敗#
心跳要求帶上目前的嘗試次數和租約,並且禁止帶 detail:
{"attempt_count":1,"lease_token":"<hex-token>"}在租約到期之前,把它 POST 到 .../{worker_job_index}:heartbeat。
完成必須帶 detail。對 repo.ci 來說,固定的作業型別就足以完成持久 化。對 patchset.ci,detail 必須標明作業型別 7,並攜帶一份 ait.runner.native-result.v1 結果:
{
"attempt_count": 1,
"lease_token": "<hex-token>",
"detail": {
"job_kind": 7,
"result": {
"contract": "ait.runner.native-result.v1",
"status": "succeeded",
"tests_status": "pass",
"suite_result_count": 1,
"suite_results": [
{"suite_id":"cargo_test","status":"pass","blocking":true}
]
}
}
}終態結果的 status 是 succeeded、command_failed 或 timed_out; tests_status 是 pass 或 fail;每個 suite 的狀態是 pass 或 fail。suite_result_count 必須等於陣列長度。服務端會據此推匯出緊湊 的 CI 證據,並原子地掛到選定的 Patchset 上。
失敗必須帶上錯誤資訊和重試判斷:
{
"attempt_count": 1,
"lease_token": "<hex-token>",
"detail": {
"retryable": true,
"error": "runner lost its isolated attempt process"
}
}error 的取值上限是 4,096 個字元。只有 retryable 為 true 且還有剩餘 嘗試次數時,服務端才會重新入隊。 完成、失敗和心跳都會拒絕過期的嘗試次數或租約令牌。
Line 與 Snapshot#
讀取與更新 Line#
GET .../lines 傳回全部 Line 投影。GET .../lines/{line_name} 傳回 一個投影,包含它的名稱、狀態和目前的 head_snapshot_id。
用下面這個請求體建立 Line,或對它做比較並設定:
{
"head_snapshot_id": "SNP-NEW",
"expected_head_snapshot_id": "SNP-OLD"
}兩個欄位都是可選且可為 null 的。expected_head_snapshot_id 是比較並 設定的守衛。直接的遠端同步可以給一條空的預設 Line 做引導、重放它目前 的 head,或者移動另一條 Line。但它不能把已初始化的預設 main 移到 另一個 Snapshot;那會傳回 409 和 GOVERNED_TARGET_LINE_REQUIRES_LAND,必須走 Land 完成。
關閉一條 Line,POST 到 .../lines/{line_name}:close:
{"status":"archived"}省略時 status 預設為 archived。
檢測 Snapshot 是否存在#
curl --fail-with-body \
-H 'Content-Type: application/json' \
-d '{"snapshot_ids":["SNP-A","SNP-B"]}' \
"$BASE/v1/native/repository-authorities/$REPOSITORY_INDEX/snapshots:exists"響應會對給定的 Snapshot ID 做分類,而不傳輸它們的內容。
匯出單個 Snapshot#
curl --get --fail-with-body \
--data 'include_content=true' \
--data-urlencode 'path=src/main.rs' \
"$BASE/v1/native/repository-authorities/$REPOSITORY_INDEX/snapshots/SNP-EXAMPLE"include_content 預設為 true。path 可選,用來收窄匯出的檔案投影。 這是 JSON 形式的 Snapshot 匯出,不是裸 pack 下載。
zstd 批次遠端同步#
安全的傳輸順序是:先請求一個 plan,上傳缺失的裸 pack,提交它們的清單 行和 Snapshot,然後按受治理 Line 的規則讀取或更新 Line。拉取則是反向 過程:先請求一份清單,再下載它列出的 pack。
存在性 plan#
{
"snapshot_ids": ["SNP-HEAD"],
"object_packs": ["OPK-A", {"pack_id":"OPK-B"}],
"tree_packs": ["TPK-A"]
}POST 到 .../remote-sync/zstd-bulk/plan。這三個陣列都可以為空。pack 條目可以是 ID,也可以是帶 pack_id 的清單物件。響應在下面這些欄位裡 保持請求順序:
checked_snapshot_ids;present_snapshot_ids和missing_snapshot_ids;present_object_pack_ids和missing_object_pack_ids;以及present_tree_pack_ids和missing_tree_pack_ids。
裸 pack 上傳與下載#
curl --fail-with-body -X PUT \
-H 'Content-Type: application/vnd.ait.remote-sync.object-pack+zstd' \
--data-binary @object-pack.zst \
"$BASE/v1/native/repository-authorities/$REPOSITORY_INDEX/remote-sync/zstd-bulk/object-packs/OPK-A"Tree Pack 傳輸走對應的 tree-packs 路徑和媒體型別。路徑裡的 pack_id 必須是合法的單個路徑段,而且要和上傳的 pack 後設資料一致。上傳響應會 描述被接受的 pack;GET 傳回確切的原始位元組。
Commit#
{
"contract": "ait.remote_sync.zstd_bulk.commit.v1",
"object_packs": [],
"tree_packs": [],
"blob_locators": [],
"tree_locators": [],
"snapshots": [],
"line_update": null
}每個清單欄位都接受陣列或 null;缺失的欄位按空處理。pack、定位符和 Snapshot 物件就是 import 或 pull 清單原樣給出的那些行。不要自行合成, 也不要丟掉它們的完整性欄位。line_update 存在時,包含 line_name、 可為 null 的 head_snapshot_id 和可為 null 的 expected_head_snapshot_id。
響應會報告被更新插入和被跳過的 Object Pack、Tree Pack、Blob、Tree 和 Snapshot,另外還有 remote_line、 line_head_updated_after_ingest 和 raw_binary_upload: true。已有的 不可變內容只有在與權威一致時才會被跳過;不一致就是錯誤,而不是覆蓋。
import 與 pull 清單#
GET .../import-manifests/{snapshot_id} 傳回一個完整的閉包:
{
"contract": "ait.remote_sync.zstd_bulk.import_manifest.v1",
"repo_name": "ait-native-site",
"snapshot_id": "SNP-HEAD",
"snapshots": [],
"object_packs": [],
"tree_packs": [],
"blob_locators": [],
"tree_locators": [],
"line_update": null
}要取有界的祖先範圍,POST:
{
"contract": "ait.remote_sync.zstd_bulk.pull_manifest.request.v1",
"head_snapshot_id": "SNP-HEAD",
"have_snapshot_ids": ["SNP-BOUNDARY"]
}have_snapshot_ids 不能有重複項,條目上限是 100,000。響應契約是 ait.remote_sync.zstd_bulk.pull_manifest.v1,並額外帶上 head_snapshot_id 和 boundary_snapshot_ids。Snapshot 行按從所需的 祖先邊界指向請求 head 的順序排列;pack 清單則做過去重並按依賴排序。 下載傳回的 pack ID,並把傳回的那些行陣列原封不動傳給目標端的 commit。
Task 生命週期#
建立與讀取 Task#
建立一個不繫結 Plan 的 Task:
{"title":"Correct release readback","intent":"Verify the published artifact"}可選的 task_id 只有在等於服務端推匯出的下一個 ID 時才被接受。可選的 Plan 關聯要三個欄位一起用:
{
"title": "Correct release readback",
"intent": "Verify the published artifact",
"plan_id": "PR-12",
"origin_plan_revision_id": "plan-revision:19",
"plan_item_ref": "site/release/readback"
}如果給了 plan_id 而沒給 revision,路由會解析出目前的 head。如果給了 revision 而沒給 Plan ID,它會在選定的 Repository 內搜尋。條目必須存在 於那個 revision 中。repo_id、repository_index 這類由路由自己掌握 的欄位會被拒絕。
Task 響應包含 task_id、Repository 身份、序號、標題、意圖、Plan 關聯 和漂移投影、狀態,以及時間戳。狀態是 active、completed 或 abandoned。
用 GET .../tasks 列出,用 GET .../tasks/{task_ref} 讀取,關閉則 POST 到 .../tasks/{task_ref}:close:
{"status":"completed"}關閉時接受的狀態是 completed、abandoned、canceled 或 cancelled;後三個都會投影成 abandoned。
原子式 Task 啟動#
POST .../task-start 會建立或選定確切的 Plan revision 和條目,然後在 一次操作裡追加 Task 和第一個 Change:
{
"contract": "task-start-atomic/v1",
"idempotency_key": "client-unique-key",
"plan_item_ref": "site/release/readback",
"plan": {
"action": "existing",
"plan_id": "PR-12",
"plan_revision_id": "plan-revision:19"
},
"task": {
"title": "Correct release readback",
"intent": "Verify the published artifact"
},
"change": {
"title": "Implement verified readback",
"base_line": "main"
}
}idempotency_key 必填,上限 256 位元組。plan.action 取值為:
existing:需要plan_id和目前 head 的plan_revision_id;create:需要在plan.payload下給出一份 Plan revision 載荷;或者revise:需要plan_id、plan.payload,以及目前的expected_head_revision_id,除非那個確切的 revision 已經存在。
Task 的 Plan 關聯和 Change 的 task_id 由服務端推導;呼叫方不得自行 提供這些推導欄位。用同樣的繫結和載荷重放,會傳回已記錄的原子結果。
Task 審計與佇列#
GET .../read/tasks/{task_id}/audit?target_line=main 傳回該 Task、 它的全部 Change、目標 Line 的 head、未關閉和已 land 的 Change 計數, 以及一個結論:task_completed、task_abandoned、ready_to_close、 no_changes 或 in_progress。
GET .../read/queue-summary 和 GET .../read/task-queue 接受可選的 status=<value>。前者傳回工作流程的聚合計數,後者傳回 Task 佇列的行。 GET .../read/reviewer-inbox 傳回該 Repository 目前的評審工作。這些 都是派生出來的讀模型:寫入權威請用實體路由。
Change、Patchset、評審、策略、CI 與 Land#
建立與關閉 Change#
{
"task_id": "T-0001",
"title": "Implement verified readback",
"base_line": "main",
"fork_snapshot_id": "SNP-BASE"
}task_id、title 和 base_line 是必填的。fork_snapshot_id 可選, 但如果給了,就必須等於建立時基礎 Line 的 head。可選的 change_id 必須等於服務端推匯出的完整身份或短身份。
Change 響應包含 change_id、完整的 change_ref、Task 和 Repository、 標題、基礎 Line、fork Snapshot、狀態、目前和選定的 Patchset、 land 目標,以及時間戳。狀態可以是 draft、active、review、 landed、archived、superseded 或 abandoned。
用下列狀態之一關閉:
{"status":"archived"}接受的取值是 archived、superseded、abandoned、canceled 和 cancelled。landed 會被拒絕,因為那個狀態由成功的 Land 推導得出。
發布與選定 Patchset#
POST 到 .../changes/{change_ref}/patchsets:
{
"base_snapshot_id": "SNP-BASE",
"revision_snapshot_id": "SNP-REVISION",
"summary": "Add verified readback",
"author_mode": "ai_with_human_review"
}必填的作者模式取值是 human_only、human_with_ai_assist、 ai_with_human_review 或 ai_only_experimental。可選的 patchset_id 和 patchset_number 只有在與推匯出的下一個身份一致時才被接受。 用同樣的 Change/base/revision 三元組重放,會傳回已有的那個 Patchset。
Patchset 響應包含身份、Snapshot、摘要、作者與發布狀態、撤回標誌、 改動檔案的統計和路徑、評估狀態、建立時間、CI 執行序號,以及緊湊的 overall/tests/lint 證據。
選定 Patchset,POST 到 .../changes/{change_ref}:selectPatchset:
{"patchset_id":"T-0001/C-01/P-02"}這個 Patchset 必須屬於該 Change,而且不能已被撤回或作廢。
發起與記錄評審#
一次只發起一個評審組:
{
"patchset_id": "T-0001/C-01/P-02",
"reviewer_groups": ["maintainers"],
"note": "Please check release semantics"
}把它發到 .../changes/{change_ref}:requestReview。可選的 review_id 只有在與推匯出的身份一致時才被接受。
記錄一次評審,POST 到 .../changes/{change_ref}/reviews:
{
"patchset_id": "T-0001/C-01/P-02",
"reviewer": "reviewer@example.invalid",
"action": "approve",
"comment": "Verified",
"blocking": false
}動作取值是 request、comment、task_comment、code_review_summary、 approve、task_approve、request_changes、task_request_changes、 defer、task_defer 和 dismiss。comment 預設為空,blocking 預設為 false。這個 Patchset 必須是其 Change 目前選定的 Patchset。
評審列表響應包含 reviews,以及核准、阻塞、評論和總計這幾個彙總計數。
Attestation 與策略#
用 PUT .../patchsets/{patchset_id}/attestation 追加一條 attestation:
{
"author_mode": "ai_with_human_review",
"verification_state": "pass",
"revoked": false,
"require_tests_pass": true,
"require_human_review": true,
"require_lint_pass": true
}verification_state 取值為 unknown、pending、pass 或 fail。 預設值是 unknown、revoked: false、require_tests_pass: true,人工 評審和 lint 這兩項要求預設為 false。如果給了 author_mode,它必須與 Patchset 一致。呼叫方提供的 detail.patchset_ci 會被拒絕,因為 CI 的 完成歸 Patchset 和 Worker Job 權威管。
GET .../attestation 傳回最新的 attestation,沒有時傳回 404。它包含 各項要求標誌、這條記錄是否有 CI 支撐、作者模式,以及目前的 tests/lint 彙總。
POST .../patchsets/{patchset_id}:evaluatePolicy 接受一個空 JSON 物件, 並基於最新的 attestation、緊湊 CI 和即時的評審核准追加一條決定。 GET .../policy 傳回那條決定和它的各項檢查。首次評估之前,它傳回:
{"patchset_id":"...","decision":"pending","checks":[]}執行與檢視 Patchset CI#
用下面這個請求體啟動 CI:
{"trigger":"manual_rerun"}POST 到 .../patchsets/{patchset_id}:runCi。不給 trigger 時預設是 manual_rerun。manual_rerun 和 base_stale_after_land_rerun 會顯式 推進這次執行;其他非空的 trigger 在有可用的既有終態證據時會複用它。 新啟動或仍在進行的執行透過 Worker Job 送達。響應會報告 queued、 job、delivery、trigger 和 Patchset 的 CI 投影。
檢視用:
curl --get --fail-with-body \
--data 'recent_limit=10' \
--data 'projection=readiness' \
"$BASE/v1/native/repository-authorities/$REPOSITORY_INDEX/read/patchsets/<patchset-id>/ci-status"recent_limit 預設 10,必須在 1 到 1000 之間;唯一有名字的 投影是 readiness。 響應會報告可用性、執行序號、完成時間、狀態和計數、是否存在可用的證據、 選定的/最新的作業,以及近期作業。1.1.1 的緊湊權威在這裡不保留完整的 逐 suite 歷史,所以這個讀模型裡的 selected_suite_ids 和 suite_results 是空的。
原子式 Task Land#
優先用這個路由,而不是直接對 Change 用 :submit:
{
"contract": "task-land-atomic/v1",
"idempotency_key": "client-unique-land-key",
"task_or_change_ref": "T-0001/C-01",
"target_line": "main",
"mode": "direct",
"patchset_id": "T-0001/C-01/P-02",
"expected_head_snapshot_id": "SNP-BASE"
}contract、idempotency_key、task_or_change_ref 和 mode 是必填的。 key 的上限是 256 位元組。只有當某個 Task 恰好只有一個可 land 的 Change 時,task_or_change_ref 才可以是 Task;否則請傳確切的 Change。 target_line 預設取 Change 的基礎 Line。mode 取值為 direct、merge 或 ff-only。patchset_id 可選,但必須是已選定的那個。比較並設定的 守衛可以叫 expected_head_snapshot_id,也可以叫 expected_target_line_head。
成功或重放會傳回原子契約、重放標誌、頂層的 Task、Change、Patchset、 目標和已 land 的 Snapshot 識別符號,外加完整的 task、change、 patchset、land 投影,以及可選的 history_promotion 投影。
直接呼叫 POST .../changes/{change_ref}:submit 也接受這些 Land 欄位, 但不要求原子契約。它更底層,也不提供同樣的 Task 解析和冪等聚合結果。
用 GET .../lands/{submission_id} 讀取任意一次嘗試。Land 投影包含 選定的 Patchset、目標 Line、模式、狀態、失敗型別、結果、已 land 的 Snapshot、Line 動作,以及時間戳。失敗型別包括基礎過期、策略/評審/CI 被阻塞、衝突、目標更新失敗和內部錯誤。
history promotion 的 prepare 操作#
這個專用操作會把已記錄的本地 land 歷史,在受治理的遠端 Land 之前轉換 成遠端工作流程記錄。頂層請求是:
{
"contract": "history-promotion-prepare/v1",
"idempotency_key": "client-unique-promotion-key",
"target_line": "main",
"base_snapshot_id": "SNP-BASE",
"revision_snapshot_id": "SNP-FINAL",
"author_mode": "ai_with_human_review",
"summary": "Promote recorded local history",
"entries": []
}entries 必須包含 1 到 64 個條目。每個條目包含:
| 欄位 | 含義 |
|---|---|
local_task_id、local_change_id、local_change_ref | 原來的本地身份。 |
expected_remote_task_id、expected_remote_change_ref | 可選的失敗即中止的發布身份。 |
task | {title, intent, plan_id?, origin_plan_revision_id?, plan_item_ref?}。 |
change | {title, base_line, fork_snapshot_id}。 |
pre_land_target_snapshot_id、landed_snapshot_id、landed_at_s | 已記錄的本地 Land 邊界。 |
snapshots | 該邊界對應的 1 到 64 個 {snapshot_id, created_at_s} 條目。 |
這個端點會在準備聚合 Patchset 之前,校驗順序、身份、Snapshot 祖先、 Plan 繫結和冪等性。裸呼叫方應當使用相容的 ait 客戶端生成的確切回執, 而不是手工拼這份載荷。
Sprint 與 Plan API#
HTTP 路徑用的是 sprints;響應物件用的是 Plan 識別符號。一個 Plan revision 由一份 Markdown artifact 加上歸一化的條目後設資料支撐。
Plan revision 載荷#
建立和修訂共用這些欄位:
| 欄位 | 是否必需 | 含義 |
|---|---|---|
title | 建立:是;修訂:否 | Plan 標題,或對 revision 標題的覆蓋值。 |
status | 否 | draft、active、archived 或 superseded;預設是 draft。active 會按 draft/open 狀態儲存和投影。 |
summary | 否 | revision 摘要。 |
artifact_path | 是 | 相對 Repository 的 Markdown artifact 路徑。 |
artifact_selector | 否 | artifact 內部的穩定選擇符。 |
artifact_heading | 是 | 給人看的根標題。 |
items | 否 | 歸一化的條目陣列;預設為空。 |
artifact_body | 否 | 用來打包 revision artifact 的確切 Markdown 正文。 |
packed_artifact | 否 | 相容客戶端在用時給出的既有打包 artifact 描述符。 |
expected_head_revision_id | 僅修訂時可用,否 | 併發修訂的比較並設定守衛。 |
每個 items[] 物件可以包含 plan_item_ref、條目文字、 checkbox_state 和 heading_path。沒有可用引用的條目,無法被 Task 關聯選中。
用 POST .../sprints 建立。呼叫方自行提供的 plan_id 會被拒絕,因為 它由服務端推導。用 GET .../sprints 列出;可選的 artifact_path=<exact-path> 用來收窄結果。用 GET .../sprints/{plan_id} 讀取其中一個。
更新狀態用:
{"status":"archived"}把共用的 revision 載荷 POST 到 .../sprints/{plan_id}/revisions 就能 追加一個 revision。列出或讀取 revision 用對應的 GET 路由。
/artifacts 的 GET 路由傳回的完整 revision 投影,和 revision 的 GET 路由一樣。對應的 PUT 存在只是為了協議相容,但 1.1.1 一律拒絕它,因為 單獨寫支撐性 artifact 不屬於目前生效的緊湊 Plan 佈局。請改為把 artifact 正文放進 Plan 的建立或修訂操作裡。
Plan 查詢與 Task 關聯輔助介面#
查詢包含全部給定歸一化詞條的活動 Plan:
{"contains_terms":["release","readback"]}POST 到 .../sprint-plan-ids/by-contains。詞條必須是 JSON 字串陣列; 歸一化後為空集時傳回空陣列。
解析 Task 關聯,把下面這些欄位的任意組合 POST 過去:
{
"plan_id": "PR-12",
"origin_plan_revision_id": "plan-revision:19",
"plan_item_ref": "site/release/readback"
}既沒有 Plan 也沒有 revision 時,三個欄位都傳回 null。沒有 Plan 關聯 的條目會被拒絕。缺失的 Plan 或 revision 身份會在這個數字 Repository 內部解析,然後再校驗歸屬關係和條目是否存在。
GET .../read/plans/candidate-inputs?contains=release,readback 會把 逗號分隔的詞條歸一化,傳回匹配的活動 Plan 以及關聯的 Task 候選。它是 給選擇類介面用的讀模型,不是寫入端點。
端到端裸 HTTP 示例#
這段精簡過的流程展示了這些較底層的 API 是怎麼串起來的。生產環境的 客戶端必須保留每一個傳回的識別符號,並在遇到任何非成功狀態時停下。
GET /healthz,直到200且ready: true。GET /v1/handshake;核對協議和 runner 契約。- 用
POST /v1/native/repository-authorities註冊一次;把數字形式的 Repository 索引持久化下來。 - 用遠端同步的 plan、裸 pack 上傳和 commit 來發布不可變的 Snapshot 內容,同時不推進已初始化的受治理
mainLine。 - 建立,或者原子地啟動繫結 Plan 的 Task 和 Change。
- 發布並選定那個 revision Snapshot 已上傳的 Patchset。
- 加上 attestation 和所需的評審請求。
- POST Patchset 的
:runCi;由一個ait-runner認領、心跳並完成由此 產生的 Worker Job。 - POST Patchset 的
:evaluatePolicy;檢視傳回的決定。 - POST
/task-land,帶上冪等鍵和期望的目標 head。 - 讀取 Land 和 Task 審計;只有成功的 Land 才會推進受治理的
main, 也才允許最終完成 Task。
日常使用請讓 ait workflow ready、ait-runner 和 ait task finish 來 走這套流程。直接的 API 是留給相容整合,以及需要顯式控制傳輸層的維運 人員的。
維運核驗清單#
- 監聽器處於私有網路,或者由可信的 TLS 入口保護。
- 健康檢查和握手分開確認;不要把緊湊的端點列表當成完整的 API 目錄。
- 每個請求都用儲存下來的數字 Repository 索引來選路。
- 寫操作的重試,在契約提供冪等鍵時使用冪等鍵,在 Line 可能移動的地方 使用比較並設定的 head。
503要尊重Retry-After;409要去查清楚,而不是悶頭重試。- 二進位媒體型別、位元組限額、pack ID、檔案大小和摘要都要嚴格校驗。
- 退役匯出已完整,並且獨立持久化之後,才做清除。
- 恢復流程在隔離的伺服器上演練過,並且記下了新分配的 Repository 索引。
- worker 租約令牌只留在記憶體或受保護的臨時狀態裡,絕不拿來當通用的 API 認證。
關於部署、儲存邊界和災難恢復流程,參見 `ait-server`:遠端權威與恢復; 關於服務端/runner 的執行邊界,參見 遠端基礎設施。