ait-server REST API 参考
调用每一个 1.0.0 ait-server HTTP 操作,包含与实现对齐的请求字段、响应、错误、恢复流程和安全边界。
适用人群: 集成方、runner 作者与运维
这份参考涵盖什么#
这是 1.0.0 版 ait-server 发布版路由器对外暴露的完整 HTTP 接口面。 它包含 51 个路由模板、71 个 HTTP 方法绑定,以及把共享路由所 接受的动作后缀展开之后的 78 个可调用操作。
这套 API 是 JSON over HTTP,既有资源读写,也有 :runCi、:close、 :submit 这类显式的动作端点。这些动作端点是有意为之的;整个接口面 并不是严格统一的 CRUD API。
本章记录的是 ait、ait-runner 和恢复工具所使用的服务端接口。普通 用户应优先看 `ait` 命令指南, 因为客户端会强制执行时序和兼容性检查,而裸 HTTP 调用方得自己实现 这些。
安全与传输边界#
把 1.0.0 的监听器当成可信网络内的服务边界。不要把裸监听器暴露到公网。 把它放在环回地址或经过审查的私有网络上,并在可信入口处终结 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.0.0 发布版的完整接口面。
服务发现#
| 方法与路径 | 操作 |
|---|---|
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.0.0 拒绝写入支撑性 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.0.0 中是 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.0.0 的外部 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.0.0 的紧凑权威在这里不保留完整的 逐 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.0.0 一律拒绝它,因为 单独写支撑性 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 land 来 走这套流程。直接的 API 是留给兼容集成,以及需要显式控制传输层的运维 人员的。
运维核验清单#
- 监听器处于私有网络,或者由可信的 TLS 入口保护。
- 健康检查和握手分开确认;不要把紧凑的端点列表当成完整的 API 目录。
- 每个请求都用保存下来的数字 Repository 索引来选路。
- 写操作的重试,在契约提供幂等键时使用幂等键,在 Line 可能移动的地方 使用比较并设置的 head。
503要尊重Retry-After;409要去查清楚,而不是闷头重试。- 二进制媒体类型、字节限额、pack ID、文件大小和摘要都要严格校验。
- 退役导出已完整,并且独立持久化之后,才做清除。
- 恢复流程在隔离的服务器上演练过,并且记下了新分配的 Repository 索引。
- worker 租约令牌只留在内存或受保护的临时状态里,绝不拿来当通用的 API 认证。
关于部署、保存边界和灾难恢复流程,参见 `ait-server`:远程权威与恢复; 关于服务端/runner 的运行边界,参见 远程基础设施。