浏览 1.0.0 文档
1.0.0 文档修订 2

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。

本章记录的是 aitait-runner 和恢复工具所使用的服务端接口。普通 用户应优先看 `ait` 命令指南, 因为客户端会强制执行时序和兼容性检查,而裸 HTTP 调用方得自己实现 这些。

安全与传输边界#

把 1.0.0 的监听器当成可信网络内的服务边界。不要把裸监听器暴露到公网。 把它放在环回地址或经过审查的私有网络上,并在可信入口处终结 TLS、调用 方校验、请求限额和访问策略。默认监听器是 127.0.0.1:8088;要换绑定地址,请使用服务端命令的显式选项。

恢复令牌和 Worker Job 租约令牌属于运维能力,不能替代入口处的认证。 不要记录或泄露它们。

下面的示例中:

Code · bash
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 Packapplication/vnd.ait.remote-sync.object-pack+zstd同一值
Tree Packapplication/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

服务端生成的应用错误使用这个稳定的响应体:

Code · json
{"error":"human-readable diagnostic"}
状态码含义
200读取、幂等重放、更新、动作或二进制下载成功。
201新建了 Repository 权威或恢复会话。
400路径值、请求字段或状态迁移无效,或路由自行处理的 JSON 格式有误。
404未知的路由、Repository、实体、pack、文件或动作后缀。
405路由存在,但这个 HTTP 方法没有绑定。
409权威冲突、生命周期冲突、Line head 过期、受治理的目标 Line 移动,或退役确认不匹配。
413请求体超过该路由当前生效的限额。
415JSON 提取器或二进制路由拒绝了这个请求的媒体类型。
422JSON 语法可以接受,但没法反序列化成带类型的请求。
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 生命周期准入#

GETHEADOPTIONS 请求可以通过生命周期准入。 /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 候选。

发现类端点#

健康检查#

Code · bash
curl --fail-with-body "$BASE/healthz"

就绪时的响应:

Code · json
{
  "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 当作进程已就绪。它并不能证明备份是 最新的,或者是可恢复的。

握手#

Code · bash
curl --fail-with-body "$BASE/v1/handshake"

响应字段如下:

字段含义
ready就绪路由器的状态。
contract_versionagent/服务端的协议兼容版本。
package_version正在运行的 ait-server 包版本。
authority_backend1.0.0 中是 binary_v0
repository_identityrepository_index
ci_capabilities远程同步特性和原生 runner 契约。
operational_capabilitiesRepository 与 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.shci/run.ps1 入口。

运维能力#

GET /v1/native/capabilities 返回 operational_capabilities 对象, 不带更大的握手载荷。在动手写直连的 Worker Job 客户端之前先看它。

Repository 注册与 Repository CI#

注册一个权威#

Code · bash
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 占用, 只有在名称和策略标志也一致时才会走重放;否则注册就是冲突。

Code · json
{
  "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 持久化下来。不要在多个权威共用同一个 展示名时,靠名称重新发现再猜一个。

发现并读取权威#

Code · bash
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"

列表响应包含 contractrepositoriescount 和一个 discovery 对象。discovery.routing_authorityfalse:按名称过滤是用来盘点 清单的,不是用来选路由的。单项响应把一个 repository 包在 ait.server.repository-authority.v1 里。

生命周期取值为 1 active、2 retiring、3 purged。已 purged 的条目 不出现在列表发现里。

入队 Repository CI#

这个路由在 Repository 段上使用动作后缀:

Code · bash
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_indexsnapshot_id、物理的 snapshot_indexqueued、 Worker Job 投影,以及 delivery: "binary_worker_job"

Repository 退役、导出与恢复#

退役是 API 层面针对一个完整数字 Repository 权威的备份/导出流程。它被 刻意拆成多段:先停掉新的写操作,让 Worker Job 排空,校验每个导出文件, 最后清除还要求一次确切的确认。

1. 开始并轮询退役#

下面这些 POST 操作没有请求体:

Code · bash
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"

响应内容为:

Code · json
{
  "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 编码后取用:

Code · bash
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 过去:

Code · bash
curl --fail-with-body \
  -H 'Content-Type: application/json' \
  --data-binary @remote-export-manifest.json \
  "$BASE/v1/native/repository-authorities/$REPOSITORY_INDEX/retirement/purge"

服务端会重新计算它当前的完整导出,并要求完全相等。不完整的确认、变过 的文件清单,或者作业尚未排空,都会被拒绝。成功时返回 contractpurgedalready_purged 和终态的 Repository 投影。重放同一份有效的 确认是幂等的。

中止退役#

在清除之前,这个无请求体的操作会把 Repository 恢复成 active 状态:

Code · bash
curl --fail-with-body -X POST \
  "$BASE/v1/native/repository-authorities/$REPOSITORY_INDEX/retirement/abort"

响应会报告 aborted: truealready_aborted。已 purged 的权威无法 重新激活。

恢复一个已导出的权威#

用校验过的清单,加上新注册表条目要用的 u8 策略标志,开一个会话:

Code · bash
curl --fail-with-body \
  -H 'Content-Type: application/json' \
  -d '{"manifest":<complete-manifest>,"policy_flags":0}' \
  "$BASE/v1/native/repository-restores"

带类型的请求只接受 manifestpolicy_flags 两个字段。清单必须 满足:

字段规则
schemaait.remote-export.v1
statecomplete
repo_name非空的源 Repository 名称。
namespace导出时的确切命名空间。它不能已被某个存活的 Repository 占用。
exported_at_s以 Unix 秒表示的导出时间。
files{path, size, sha256} 条目组成的确切数组。

201 响应返回一个随机的 32 个字符的十六进制 restore_token,以及 state: "uploading"。未提交的会话最多接纳 64 个。

按每个文件确切的相对路径和媒体类型逐个上传:

Code · bash
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>"

每次上传都会对照清单里的大小和摘要做检查。然后用无请求体的方式提交:

Code · bash
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 只能认领 7patchset.ci)和 11repo.ci)。其余类型属于服务端内部操作, 外部认领端点会拒绝它们。

Worker Job 状态是 1 queued、2 running、3 succeeded 和 4 failed。 (repository_index, worker_job_index) 这一对就是公开身份。

列出与查看#

Code · bash
curl --get --fail-with-body \
  --data 'state_kind=1' \
  --data 'limit=50' \
  "$BASE/v1/native/repository-authorities/$REPOSITORY_INDEX/worker-jobs"

state_kind 可选,取值必须在 1..4limit 默认 50,必须在 11000 之间。结果按最新在前排列。每个作业投影包含标识符、 kind/type、状态、重试状态、结果、尝试次数上限、错误类型、Patchset 或 Snapshot 引用、可用性、加锁与更新时间,以及墓碑状态。Patchset CI 作业 还会带上当前的紧凑 CI 投影。

读取指定的某个作业:

Code · bash
curl --fail-with-body \
  "$BASE/v1/native/repository-authorities/$REPOSITORY_INDEX/worker-jobs/42"

认领下一个或认领指定的#

全局认领可以带一个可选的 Repository 过滤条件:

Code · json
{
  "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。没有任何 匹配时,响应是 200claimed_job: null

要认领一个已经选好的作业,把下面这个确切的请求体 POST 到 .../worker-jobs/{worker_job_index}:claim

Code · json
{"accepted_runtime_contracts":["ait.runner.native-job.v3"]}

认领成功会返回 attempt_count、十六进制的 lease_tokenlease_expires_at_s、作业身份和 runtime_request。这个运行时请求会 选定一个远程 Snapshot,声明直接执行的 ci/run 参数向量,并设置超时。 请严格按这个请求执行;不要拿当前工作区顶替。

心跳、完成与失败#

心跳要求带上当前的尝试次数和租约,并且禁止带 detail

Code · json
{"attempt_count":1,"lease_token":"<hex-token>"}

在租约到期之前,把它 POST 到 .../{worker_job_index}:heartbeat

完成必须带 detail。对 repo.ci 来说,固定的作业类型就足以完成持久 化。对 patchset.cidetail 必须标明作业类型 7,并携带一份 ait.runner.native-result.v1 结果:

Code · json
{
  "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}
      ]
    }
  }
}

终态结果的 statussucceededcommand_failedtimed_outtests_statuspassfail;每个 suite 的状态是 passfailsuite_result_count 必须等于数组长度。服务端会据此推导出紧凑 的 CI 证据,并原子地挂到选定的 Patchset 上。

失败必须带上错误信息和重试判断:

Code · json
{
  "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,或对它做比较并设置:

Code · json
{
  "head_snapshot_id": "SNP-NEW",
  "expected_head_snapshot_id": "SNP-OLD"
}

两个字段都是可选且可为 null 的。expected_head_snapshot_id 是比较并 设置的守卫。直接的远程同步可以给一条空的默认 Line 做引导、重放它当前 的 head,或者移动另一条 Line。但它不能把已初始化的默认 main 移到 另一个 Snapshot;那会返回 409GOVERNED_TARGET_LINE_REQUIRES_LAND,必须走 Land 完成。

关闭一条 Line,POST 到 .../lines/{line_name}:close

Code · json
{"status":"archived"}

省略时 status 默认为 archived

检测 Snapshot 是否存在#

Code · bash
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#

Code · bash
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 默认为 truepath 可选,用来收窄导出的文件投影。 这是 JSON 形式的 Snapshot 导出,不是裸 pack 下载。

zstd 批量远程同步#

安全的传输顺序是:先请求一个 plan,上传缺失的裸 pack,提交它们的清单 行和 Snapshot,然后按受治理 Line 的规则读取或更新 Line。拉取则是反向 过程:先请求一份清单,再下载它列出的 pack。

存在性 plan#

Code · json
{
  "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_idsmissing_snapshot_ids
  • present_object_pack_idsmissing_object_pack_ids;以及
  • present_tree_pack_idsmissing_tree_pack_ids

裸 pack 上传与下载#

Code · bash
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#

Code · json
{
  "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_lineline_head_updated_after_ingestraw_binary_upload: true。已有的 不可变内容只有在与权威一致时才会被跳过;不一致就是错误,而不是覆盖。

import 与 pull 清单#

GET .../import-manifests/{snapshot_id} 返回一个完整的闭包:

Code · json
{
  "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:

Code · json
{
  "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_idboundary_snapshot_ids。Snapshot 行按从所需的 祖先边界指向请求 head 的顺序排列;pack 清单则做过去重并按依赖排序。 下载返回的 pack ID,并把返回的那些行数组原封不动传给目标端的 commit。

Task 生命周期#

创建与读取 Task#

创建一个不绑定 Plan 的 Task:

Code · json
{"title":"Correct release readback","intent":"Verify the published artifact"}

可选的 task_id 只有在等于服务端推导出的下一个 ID 时才被接受。可选的 Plan 关联要三个字段一起用:

Code · json
{
  "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_idrepository_index 这类由路由自己掌握 的字段会被拒绝。

Task 响应包含 task_id、Repository 身份、序号、标题、意图、Plan 关联 和漂移投影、状态,以及时间戳。状态是 activecompletedabandoned

GET .../tasks 列出,用 GET .../tasks/{task_ref} 读取,关闭则 POST 到 .../tasks/{task_ref}:close

Code · json
{"status":"completed"}

关闭时接受的状态是 completedabandonedcanceledcancelled;后三个都会投影成 abandoned

原子式 Task 启动#

POST .../task-start 会创建或选定确切的 Plan revision 和条目,然后在 一次操作里追加 Task 和第一个 Change:

Code · json
{
  "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_idplan.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_completedtask_abandonedready_to_closeno_changesin_progress

GET .../read/queue-summaryGET .../read/task-queue 接受可选的 status=<value>。前者返回工作流的聚合计数,后者返回 Task 队列的行。 GET .../read/reviewer-inbox 返回该 Repository 当前的评审工作。这些 都是派生出来的读模型:写入权威请用实体路由。

Change、Patchset、评审、策略、CI 与 Land#

创建与关闭 Change#

Code · json
{
  "task_id": "T-0001",
  "title": "Implement verified readback",
  "base_line": "main",
  "fork_snapshot_id": "SNP-BASE"
}

task_idtitlebase_line 是必填的。fork_snapshot_id 可选, 但如果给了,就必须等于创建时基础 Line 的 head。可选的 change_id 必须等于服务端推导出的完整身份或短身份。

Change 响应包含 change_id、完整的 change_ref、Task 和 Repository、 标题、基础 Line、fork Snapshot、状态、当前和选定的 Patchset、 land 目标,以及时间戳。状态可以是 draftactivereviewlandedarchivedsupersededabandoned

用下列状态之一关闭:

Code · json
{"status":"archived"}

接受的取值是 archivedsupersededabandonedcanceledcancelledlanded 会被拒绝,因为那个状态由成功的 Land 推导得出。

发布与选定 Patchset#

POST 到 .../changes/{change_ref}/patchsets

Code · json
{
  "base_snapshot_id": "SNP-BASE",
  "revision_snapshot_id": "SNP-REVISION",
  "summary": "Add verified readback",
  "author_mode": "ai_with_human_review"
}

必填的作者模式取值是 human_onlyhuman_with_ai_assistai_with_human_reviewai_only_experimental。可选的 patchset_idpatchset_number 只有在与推导出的下一个身份一致时才被接受。 用同样的 Change/base/revision 三元组重放,会返回已有的那个 Patchset。

Patchset 响应包含身份、Snapshot、摘要、作者与发布状态、撤回标志、 改动文件的统计和路径、评估状态、创建时间、CI 运行序号,以及紧凑的 overall/tests/lint 证据。

选定 Patchset,POST 到 .../changes/{change_ref}:selectPatchset

Code · json
{"patchset_id":"T-0001/C-01/P-02"}

这个 Patchset 必须属于该 Change,而且不能已被撤回或作废。

发起与记录评审#

一次只发起一个评审组:

Code · json
{
  "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

Code · json
{
  "patchset_id": "T-0001/C-01/P-02",
  "reviewer": "reviewer@example.invalid",
  "action": "approve",
  "comment": "Verified",
  "blocking": false
}

动作取值是 requestcommenttask_commentcode_review_summaryapprovetask_approverequest_changestask_request_changesdefertask_deferdismisscomment 默认为空,blocking 默认为 false。这个 Patchset 必须是其 Change 当前选定的 Patchset。

评审列表响应包含 reviews,以及批准、阻塞、评论和总计这几个汇总计数。

Attestation 与策略#

PUT .../patchsets/{patchset_id}/attestation 追加一条 attestation:

Code · json
{
  "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 取值为 unknownpendingpassfail。 默认值是 unknownrevoked: falserequire_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 返回那条决定和它的各项检查。首次评估之前,它返回:

Code · json
{"patchset_id":"...","decision":"pending","checks":[]}

运行与查看 Patchset CI#

用下面这个请求体启动 CI:

Code · json
{"trigger":"manual_rerun"}

POST 到 .../patchsets/{patchset_id}:runCi。不给 trigger 时默认是 manual_rerunmanual_rerunbase_stale_after_land_rerun 会显式 推进这次运行;其他非空的 trigger 在有可用的既有终态证据时会复用它。 新启动或仍在进行的运行通过 Worker Job 送达。响应会报告 queuedjobdeliverytrigger 和 Patchset 的 CI 投影。

查看用:

Code · bash
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,必须在 11000 之间;唯一有名字的 投影是 readiness。 响应会报告可用性、运行序号、完成时间、状态和计数、是否存在可用的证据、 选定的/最新的作业,以及近期作业。1.0.0 的紧凑权威在这里不保留完整的 逐 suite 历史,所以这个读模型里的 selected_suite_idssuite_results 是空的。

原子式 Task Land#

优先用这个路由,而不是直接对 Change 用 :submit

Code · json
{
  "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"
}

contractidempotency_keytask_or_change_refmode 是必填的。 key 的上限是 256 字节。只有当某个 Task 恰好只有一个可 land 的 Change 时,task_or_change_ref 才可以是 Task;否则请传确切的 Change。 target_line 默认取 Change 的基础 Line。mode 取值为 directmergeff-onlypatchset_id 可选,但必须是已选定的那个。比较并设置的 守卫可以叫 expected_head_snapshot_id,也可以叫 expected_target_line_head

成功或重放会返回原子契约、重放标志、顶层的 Task、Change、Patchset、 目标和已 land 的 Snapshot 标识符,外加完整的 taskchangepatchsetland 投影,以及可选的 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 之前转换 成远程工作流记录。顶层请求是:

Code · json
{
  "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_idlocal_change_idlocal_change_ref原来的本地身份。
expected_remote_task_idexpected_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_idlanded_snapshot_idlanded_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 标题的覆盖值。
statusdraftactivearchivedsuperseded;默认是 draftactive 会按 draft/open 状态存储和投影。
summaryrevision 摘要。
artifact_path相对 Repository 的 Markdown artifact 路径。
artifact_selectorartifact 内部的稳定选择符。
artifact_heading给人看的根标题。
items归一化的条目数组;默认为空。
artifact_body用来打包 revision artifact 的确切 Markdown 正文。
packed_artifact兼容客户端在用时给出的既有打包 artifact 描述符。
expected_head_revision_id仅修订时可用,否并发修订的比较并设置守卫。

每个 items[] 对象可以包含 plan_item_ref、条目文本、 checkbox_stateheading_path。没有可用引用的条目,无法被 Task 关联选中。

POST .../sprints 创建。调用方自行提供的 plan_id 会被拒绝,因为 它由服务端推导。用 GET .../sprints 列出;可选的 artifact_path=<exact-path> 用来收窄结果。用 GET .../sprints/{plan_id} 读取其中一个。

更新状态用:

Code · json
{"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:

Code · json
{"contains_terms":["release","readback"]}

POST 到 .../sprint-plan-ids/by-contains。词条必须是 JSON 字符串数组; 归一化后为空集时返回空数组。

解析 Task 关联,把下面这些字段的任意组合 POST 过去:

Code · json
{
  "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 是怎么串起来的。生产环境的 客户端必须保留每一个返回的标识符,并在遇到任何非成功状态时停下。

  1. GET /healthz,直到 200ready: true
  2. GET /v1/handshake;核对协议和 runner 契约。
  3. POST /v1/native/repository-authorities 注册一次;把数字形式的 Repository 索引持久化下来。
  4. 用远程同步的 plan、裸 pack 上传和 commit 来发布不可变的 Snapshot 内容,同时不推进已初始化的受治理 main Line。
  5. 创建,或者原子地启动绑定 Plan 的 Task 和 Change。
  6. 发布并选定那个 revision Snapshot 已上传的 Patchset。
  7. 加上 attestation 和所需的评审请求。
  8. POST Patchset 的 :runCi;由一个 ait-runner 认领、心跳并完成由此 产生的 Worker Job。
  9. POST Patchset 的 :evaluatePolicy;查看返回的决定。
  10. POST /task-land,带上幂等键和期望的目标 head。
  11. 读取 Land 和 Task 审计;只有成功的 Land 才会推进受治理的 main, 也才允许最终完成 Task。

日常使用请让 ait workflow readyait-runnerait task land 来 走这套流程。直接的 API 是留给兼容集成,以及需要显式控制传输层的运维 人员的。

运维核验清单#

  • 监听器处于私有网络,或者由可信的 TLS 入口保护。
  • 健康检查和握手分开确认;不要把紧凑的端点列表当成完整的 API 目录。
  • 每个请求都用保存下来的数字 Repository 索引来选路。
  • 写操作的重试,在契约提供幂等键时使用幂等键,在 Line 可能移动的地方 使用比较并设置的 head。
  • 503 要尊重 Retry-After409 要去查清楚,而不是闷头重试。
  • 二进制媒体类型、字节限额、pack ID、文件大小和摘要都要严格校验。
  • 退役导出已完整,并且独立持久化之后,才做清除。
  • 恢复流程在隔离的服务器上演练过,并且记下了新分配的 Repository 索引。
  • worker 租约令牌只留在内存或受保护的临时状态里,绝不拿来当通用的 API 认证。

关于部署、保存边界和灾难恢复流程,参见 `ait-server`:远程权威与恢复; 关于服务端/runner 的运行边界,参见 远程基础设施

Version authority

Checked against the exact 1.0.0 source

This page is public documentation, not a second product contract. Use the exact source and distribution contract for release authority.

Owning component Snapshots
  • ait-coreSNP-B06A48DA0245
  • ait-serverSNP-E90456E6425E
  • ait-runnerSNP-6B0A1BB3AFAD
  • ait-pythonSNP-973E3BFAF3DE
  • ait-nodeSNP-F962CC66AA62