ait-server REST API Reference
Call every RC.6 ait-server HTTP operation with implementation-aligned request fields, responses, errors, recovery flows, and security boundaries.
Audience: Integrators, runner authors, and operators
What this reference covers#
This is the complete HTTP surface exposed by the RC.6 ait-server release router. It accounts for 51 route templates, 71 HTTP method bindings, and 78 callable operations after the action suffixes accepted by shared routes are expanded.
The API is JSON over HTTP with resource reads and writes plus explicit action endpoints such as :runCi, :close, and :submit. Those action endpoints are intentional; the surface is not a strictly uniform CRUD API.
This chapter documents the server interface used by ait, ait-runner, and recovery tooling. Normal users should prefer the `ait` command guides because the clients enforce sequencing and compatibility checks that a raw HTTP caller must implement itself.
Security and transport boundary#
Treat the RC.6 listener as a trusted-network service boundary. Do not expose the bare listener to the public Internet. Keep it on loopback or a reviewed private network and terminate TLS, caller verification, request limits, and access policy at a trusted ingress. The default listener is 127.0.0.1:8088; choose a different bind address with the explicit server command option.
The restore token and Worker Job lease token are operational capabilities, not a substitute for ingress authentication. Do not log or disclose them.
For the examples below:
BASE=http://127.0.0.1:8088
REPOSITORY_INDEX=17Common protocol rules#
Addressing and identifiers#
{repository_index}and{worker_job_index}are canonical unsigned base-10u32values.0is valid;01, signs, spaces, and values above4294967295are rejected.- Repository names are display and discovery data. Names can repeat and do not select authority; the numeric Repository index does.
- Task, Change, Patchset, Snapshot, Land, Plan, and revision identifiers are opaque public identifiers. Preserve their spelling and URL-encode path segments when necessary.
- Time fields ending in
_at_sare unsigned Unix seconds. Fields such ascreated_atare RFC 3339 strings. A missing event is normallynullin a projection rather than an invented timestamp.
Request headers and body limits#
Use Content-Type: application/json for JSON requests and Accept: application/json for JSON responses. The standard JSON extractor applies the framework's normal 2 MiB body ceiling. The following bulk routes raise the request ceiling to 2 GiB:
- zstd bulk plan, commit, and pull-manifest requests;
- zstd Object Pack and Tree Pack transfer routes; and
- Repository restore file upload.
Raw pack and recovery-file bodies require an exact media type:
| Body | Required Content-Type | Response type |
|---|---|---|
| Object Pack | application/vnd.ait.remote-sync.object-pack+zstd | same value |
| Tree Pack | application/vnd.ait.remote-sync.tree-pack+zstd | same value |
| Retirement or restore file | application/vnd.ait.remote-authority-file.v1 | same value |
Empty Object Pack and Tree Pack uploads are rejected. A request beyond the active body limit is rejected before the handler runs.
Success and error responses#
Most successful operations return 200 OK with a route-specific JSON value. Repository registration returns 201 Created when it appends a new authority and 200 OK when it returns the matching existing registration. Starting a Repository restore session returns 201 Created.
Server-generated application errors use this stable body:
{"error":"human-readable diagnostic"}| Status | Meaning |
|---|---|
200 | Successful read, idempotent replay, update, action, or binary download. |
201 | New Repository authority or restore session created. |
400 | Invalid path value, request field, state transition, or malformed JSON handled by the route. |
404 | Unknown route, Repository, entity, pack, file, or action suffix. |
405 | The route exists but the HTTP method is not bound. |
409 | Authority conflict, lifecycle conflict, stale Line head, governed target-Line movement, or mismatched retirement acknowledgement. |
413 | Request body exceeds the active route limit. |
415 | A JSON extractor or binary route rejects the request media type. |
422 | JSON syntax was accepted but could not be deserialized into the typed request. |
500 | Corrupt, incompatible, I/O-failed, or otherwise internal authority operation. |
503 | Startup is incomplete or an authority is retryably busy. Application-generated 503 errors include Retry-After: 1. |
Do not parse the diagnostic text as a long-term machine contract. Branch on the HTTP status and the documented response fields; keep the text for operator diagnosis.
Startup behavior#
The listener is available before all serving authority has finished opening. Until activation:
GET /healthzreturns503and{"ready":false,"status":"starting"};- every other path returns
503and{"error":"ait-server is starting","ready":false,"status":"starting"}.
The ready router replaces the startup proxy atomically. After activation, GET /healthz returns 200 with ready: true.
Repository lifecycle admission#
GET, HEAD, and OPTIONS requests pass lifecycle admission. A mutation under /v1/native/repository-authorities/{repository_index}/... normally requires an active Repository.
Retirement status/start, retirement purge, retirement abort, and the Worker Job :claim, :heartbeat, :complete, and :fail drain actions remain available while a Repository is retiring. Other mutations against a retiring or purged Repository are rejected. This lets in-flight work drain and backup or restore procedures finish without admitting new workflow work.
Complete operation index#
The compact endpoints array returned by the handshake is a discovery hint, not the complete route list. The tables below are the complete RC.6 release surface.
Service discovery#
| Method and path | Operation |
|---|---|
GET /healthz | Read readiness and authority backend. |
GET /v1/handshake | Read protocol, package, CI, runner, and compact endpoint capabilities. |
GET /v1/native/capabilities | Read operational Repository and Worker Job contracts. |
Repository registry, backup, restore, and jobs#
| Method and path | Operation |
|---|---|
GET /v1/native/repository-authorities | List or discover Repository authorities. |
POST /v1/native/repository-authorities | Register a Repository authority. |
GET /v1/native/repository-authorities/{repository_index} | Read one Repository authority. |
POST /v1/native/repository-authorities/{repository_index}:runCi | Enqueue Repository CI. |
GET /v1/native/repository-authorities/{repository_index}/retirement | Read retirement drain/export status. |
POST /v1/native/repository-authorities/{repository_index}/retirement | Begin retirement. |
GET /v1/native/repository-authorities/{repository_index}/retirement/files/{file_path} | Download one manifest-listed authority file. |
POST /v1/native/repository-authorities/{repository_index}/retirement/purge | Purge after exact export acknowledgement. |
POST /v1/native/repository-authorities/{repository_index}/retirement/abort | Return a retiring Repository to active state. |
POST /v1/native/repository-restores | Begin a restore session. |
PUT /v1/native/repository-restores/{restore_token}/files/{file_path} | Upload one manifest-listed restore file. |
POST /v1/native/repository-restores/{restore_token}/commit | Validate and activate a restored Repository. |
POST /v1/native/worker-jobs:claim | Claim the next matching external-runner job. |
GET /v1/native/repository-authorities/{repository_index}/worker-jobs | List Repository Worker Jobs. |
GET /v1/native/repository-authorities/{repository_index}/worker-jobs/{worker_job_index} | Read one Worker Job. |
POST /v1/native/repository-authorities/{repository_index}/worker-jobs/{worker_job_index}:claim | Claim one exact job. |
POST /v1/native/repository-authorities/{repository_index}/worker-jobs/{worker_job_index}:heartbeat | Extend an active lease. |
POST /v1/native/repository-authorities/{repository_index}/worker-jobs/{worker_job_index}:complete | Commit a successful attempt. |
POST /v1/native/repository-authorities/{repository_index}/worker-jobs/{worker_job_index}:fail | Record a retryable or terminal attempt failure. |
Lines, Snapshots, and remote sync#
| Method and path | Operation |
|---|---|
GET /v1/native/repository-authorities/{repository_index}/lines | List Lines. |
GET /v1/native/repository-authorities/{repository_index}/lines/{line_name} | Read one Line. |
PUT /v1/native/repository-authorities/{repository_index}/lines/{line_name} | Create or update a Line with compare-and-set. |
POST /v1/native/repository-authorities/{repository_index}/lines/{line_name}:close | Close a Line. |
POST /v1/native/repository-authorities/{repository_index}/snapshots:exists | Test Snapshot presence in bulk. |
GET /v1/native/repository-authorities/{repository_index}/snapshots/{snapshot_id} | Export Snapshot metadata and optionally content. |
POST /v1/native/repository-authorities/{repository_index}/remote-sync/zstd-bulk/plan | Classify present and missing Snapshots and packs. |
POST /v1/native/repository-authorities/{repository_index}/remote-sync/zstd-bulk/commit | Commit uploaded pack metadata, locators, Snapshots, and optional Line update. |
GET /v1/native/repository-authorities/{repository_index}/remote-sync/zstd-bulk/import-manifests/{snapshot_id} | Get one Snapshot import closure. |
POST /v1/native/repository-authorities/{repository_index}/remote-sync/zstd-bulk/pull-manifests | Get a bounded ancestry import closure. |
GET /v1/native/repository-authorities/{repository_index}/remote-sync/zstd-bulk/object-packs/{pack_id} | Download one Object Pack. |
PUT /v1/native/repository-authorities/{repository_index}/remote-sync/zstd-bulk/object-packs/{pack_id} | Upload one Object Pack. |
GET /v1/native/repository-authorities/{repository_index}/remote-sync/zstd-bulk/tree-packs/{pack_id} | Download one Tree Pack. |
PUT /v1/native/repository-authorities/{repository_index}/remote-sync/zstd-bulk/tree-packs/{pack_id} | Upload one Tree Pack. |
Task and queue read models#
| Method and path | Operation |
|---|---|
GET /v1/native/repository-authorities/{repository_index}/tasks | List Tasks, newest first. |
POST /v1/native/repository-authorities/{repository_index}/tasks | Create a Task, optionally Plan-bound. |
POST /v1/native/repository-authorities/{repository_index}/task-start | Atomically create/revise/select a Plan item, Task, and first Change. |
GET /v1/native/repository-authorities/{repository_index}/tasks/{task_ref} | Read one Task. |
POST /v1/native/repository-authorities/{repository_index}/tasks/{task_ref}:close | Complete or abandon a Task. |
GET /v1/native/repository-authorities/{repository_index}/read/tasks/{task_id}/audit | Read Task closeout evidence. |
GET /v1/native/repository-authorities/{repository_index}/read/queue-summary | Read the queue summary projection. |
GET /v1/native/repository-authorities/{repository_index}/read/task-queue | Read the Task queue projection. |
GET /v1/native/repository-authorities/{repository_index}/read/reviewer-inbox | Read review work assigned to the Repository. |
Change, Patchset, review, policy, CI, and Land#
| Method and path | Operation |
|---|---|
GET /v1/native/repository-authorities/{repository_index}/changes | List Changes, newest first. |
POST /v1/native/repository-authorities/{repository_index}/changes | Create a Change under a Task. |
GET /v1/native/repository-authorities/{repository_index}/changes/{change_ref} | Read one Change. |
POST /v1/native/repository-authorities/{repository_index}/changes/{change_ref}:close | Archive, abandon, or supersede a Change. |
POST /v1/native/repository-authorities/{repository_index}/changes/{change_ref}:selectPatchset | Select a Patchset. |
POST /v1/native/repository-authorities/{repository_index}/changes/{change_ref}:requestReview | Request one reviewer group. |
POST /v1/native/repository-authorities/{repository_index}/changes/{change_ref}:submit | Submit direct Land for a Change. |
GET /v1/native/repository-authorities/{repository_index}/changes/{change_ref}/patchsets | List Patchsets in ordinal order. |
POST /v1/native/repository-authorities/{repository_index}/changes/{change_ref}/patchsets | Publish a Patchset. |
GET /v1/native/repository-authorities/{repository_index}/changes/{change_ref}/reviews | Read review rows and summary counts. |
POST /v1/native/repository-authorities/{repository_index}/changes/{change_ref}/reviews | Record a review action. |
GET /v1/native/repository-authorities/{repository_index}/patchsets/{patchset_id} | Read one Patchset and its CI projection. |
POST /v1/native/repository-authorities/{repository_index}/patchsets/{patchset_id}:runCi | Start or reuse Patchset CI and enqueue a Worker Job when required. |
POST /v1/native/repository-authorities/{repository_index}/patchsets/{patchset_id}:evaluatePolicy | Evaluate current attestation, CI, and review gates. |
GET /v1/native/repository-authorities/{repository_index}/patchsets/{patchset_id}/attestation | Read the latest attestation. |
PUT /v1/native/repository-authorities/{repository_index}/patchsets/{patchset_id}/attestation | Append a new attestation. |
GET /v1/native/repository-authorities/{repository_index}/patchsets/{patchset_id}/policy | Read the latest policy decision or pending projection. |
GET /v1/native/repository-authorities/{repository_index}/read/patchsets/{patchset_id}/ci-status | Read compact CI readiness plus recent jobs. |
GET /v1/native/repository-authorities/{repository_index}/lands/{submission_id} | Read one Land attempt. |
POST /v1/native/repository-authorities/{repository_index}/task-land | Atomically resolve and Land one Task or exact Change. |
POST /v1/native/repository-authorities/{repository_index}/history-promotion:prepare | Prepare recorded local-land history for governed remote Land. |
Sprint and Plan authority#
| Method and path | Operation |
|---|---|
GET /v1/native/repository-authorities/{repository_index}/sprints | List Plans, optionally by artifact path. |
POST /v1/native/repository-authorities/{repository_index}/sprints | Create a Plan and first revision. |
GET /v1/native/repository-authorities/{repository_index}/sprints/{plan_id} | Read Plan detail and head revision. |
PATCH /v1/native/repository-authorities/{repository_index}/sprints/{plan_id} | Change Plan status. |
GET /v1/native/repository-authorities/{repository_index}/sprints/{plan_id}/revisions | List Plan revisions newest first. |
POST /v1/native/repository-authorities/{repository_index}/sprints/{plan_id}/revisions | Append a revision with optional head compare-and-set. |
GET /v1/native/repository-authorities/{repository_index}/sprints/{plan_id}/revisions/{plan_revision_id} | Read one full revision. |
GET /v1/native/repository-authorities/{repository_index}/sprints/{plan_id}/revisions/{plan_revision_id}/artifacts | Read the same full revision, including its packed artifact projection. |
PUT /v1/native/repository-authorities/{repository_index}/sprints/{plan_id}/revisions/{plan_revision_id}/artifacts | Reserved route; RC.6 rejects supporting artifact writes. |
POST /v1/native/repository-authorities/{repository_index}/sprint-plan-ids/by-contains | Find active Plan IDs matching all normalized terms. |
POST /v1/native/repository-authorities/{repository_index}/sprint-task-linkage/resolve | Normalize and validate Task-to-Plan linkage. |
GET /v1/native/repository-authorities/{repository_index}/read/plans/candidate-inputs | Read matching Plans and their linked Task candidates. |
Discovery endpoints#
Health#
curl --fail-with-body "$BASE/healthz"Ready response:
{
"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"]
}
}Treat HTTP 200 plus ready: true as process readiness. It does not prove that a backup is current or recoverable.
Handshake#
curl --fail-with-body "$BASE/v1/handshake"The response fields are:
| Field | Meaning |
|---|---|
ready | Ready-router state. |
contract_version | Agent/server protocol compatibility version. |
package_version | Running ait-server package version. |
authority_backend | binary_v0 in RC.6. |
repository_identity | repository_index. |
ci_capabilities | Remote-sync features and native runner contract. |
operational_capabilities | Repository and Worker Job identity contracts. |
supported_async_job_types | Ten durable job type names known by the server. |
endpoints | Compact discovery subset; not the complete operation index. |
The native runner capability declares ait.runner.native-job.v3, result contract ait.runner.native-result.v1, queue contract ait.server.worker-job.service.v1, remote Snapshot input, direct argument execution, and the platform ci/run.sh or ci/run.ps1 entrypoint.
Operational capabilities#
GET /v1/native/capabilities returns the operational_capabilities object without the larger handshake payload. Use it before writing a direct Worker Job client.
Repository registration and Repository CI#
Register an authority#
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"| Request field | Required | Rules |
|---|---|---|
repository_name | yes | Non-empty UTF-8 display name; duplicates are allowed. |
namespace | yes | Zero, one, or two ASCII letters, digits, _, or -. The server stores exact zero-padded bytes. |
policy_flags | yes | Unsigned u8. |
Unknown fields are rejected. A non-empty namespace already owned by a live Repository is replayed only when name and policy flags also match; otherwise registration conflicts.
{
"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
}
}Persist repository_index from the response. Do not rediscover by name and guess when several authorities share a display name.
Discover and read authorities#
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"The list response contains contract, repositories, count, and a discovery object. discovery.routing_authority is false: a name filter is for inventory, not route selection. The item response wraps one repository under ait.server.repository-authority.v1.
Lifecycle values are 1 active, 2 retiring, and 3 purged. Purged entries are omitted from list discovery.
Enqueue Repository CI#
The route uses the action suffix on the Repository segment:
curl --fail-with-body \
-H 'Content-Type: application/json' \
-d '{"snapshot_id":"SNP-EXAMPLE"}' \
"$BASE/v1/native/repository-authorities/$REPOSITORY_INDEX:runCi"snapshot_id is optional. When absent, the server selects logical main's current head. The request fails when neither choice resolves to a Snapshot. The response contains repository_index, snapshot_id, physical snapshot_index, queued, the Worker Job projection, and delivery: "binary_worker_job".
Repository retirement, export, and restore#
Retirement is the API-level backup/export sequence for one complete numeric Repository authority. It is deliberately staged so new mutations stop, Worker Jobs drain, every exported file is verified, and purge requires an exact acknowledgement.
1. Begin and poll retirement#
These POST operations have no request body:
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"The response contains:
{
"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":"..."}
]
}
}manifest is null until queued and running Worker Jobs both reach zero. Only ready_for_export: true is permission to fetch the listed files.
2. Download and verify every file#
For every manifest.files[] entry, URL-encode its relative path and fetch:
curl --fail-with-body \
-o authority-file.bin \
"$BASE/v1/native/repository-authorities/$REPOSITORY_INDEX/retirement/files/<encoded-path>"The response is raw application/vnd.ait.remote-authority-file.v1. Verify both size and lowercase hexadecimal sha256 against the manifest. A path not present in the current manifest returns 404.
3. Purge only after durable export#
POST the entire unchanged manifest object as the purge body:
curl --fail-with-body \
-H 'Content-Type: application/json' \
--data-binary @remote-export-manifest.json \
"$BASE/v1/native/repository-authorities/$REPOSITORY_INDEX/retirement/purge"The server recomputes its current complete export and requires exact equality. It rejects an incomplete acknowledgement, a changed file inventory, or undrained jobs. Success returns contract, purged, already_purged, and the terminal Repository projection. Replaying the same valid acknowledgement is idempotent.
Abort retirement#
Before purge, this no-body operation returns the Repository to active state:
curl --fail-with-body -X POST \
"$BASE/v1/native/repository-authorities/$REPOSITORY_INDEX/retirement/abort"The response reports aborted: true and already_aborted. A purged authority cannot be reactivated.
Restore an exported authority#
Start a session with the verified manifest plus the u8 policy flags for the new registry entry:
curl --fail-with-body \
-H 'Content-Type: application/json' \
-d '{"manifest":<complete-manifest>,"policy_flags":0}' \
"$BASE/v1/native/repository-restores"The typed request accepts exactly manifest and policy_flags. The manifest must have:
| Field | Rule |
|---|---|
schema | ait.remote-export.v1 |
state | complete |
repo_name | Non-empty source Repository name. |
namespace | Exact exported namespace. It must not be owned by a live Repository. |
exported_at_s | Export time in Unix seconds. |
files | Exact array of {path, size, sha256} entries. |
The 201 response returns a random 32-character hexadecimal restore_token and state: "uploading". At most 64 uncommitted sessions are admitted.
Upload each listed file with its exact relative path and media type:
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>"Each upload is checked against the manifest size and digest. Then commit with no request body:
curl --fail-with-body -X POST \
"$BASE/v1/native/repository-restores/$RESTORE_TOKEN/commit"Commit validates the full staged archive, appends a new numeric Repository entry, normalizes the restored authority to that new index, and returns ait.server.repository-restore.v1. Replaying commit on the same live session returns the recorded response. Restore does not preserve the old numeric index; the response's new index is authoritative.
Worker Job API#
The server knows ten durable job kinds, but the external RC.6 runner may claim only 7 (patchset.ci) and 11 (repo.ci). Other kinds are server-side operations and are rejected by the external claim endpoints.
Worker Job states are 1 queued, 2 running, 3 succeeded, and 4 failed. The pair (repository_index, worker_job_index) is the public identity.
List and inspect#
curl --get --fail-with-body \
--data 'state_kind=1' \
--data 'limit=50' \
"$BASE/v1/native/repository-authorities/$REPOSITORY_INDEX/worker-jobs"state_kind is optional and must be 1..4. limit defaults to 50 and must be between 1 and 1000. Results are newest first. Each Job projection contains identifiers, kind/type, state, retry status, outcome, attempt limits, error kind, Patchset or Snapshot reference, availability, lock and update times, and tombstone state. Patchset CI jobs also include the current compact CI projection.
Read one exact Job:
curl --fail-with-body \
"$BASE/v1/native/repository-authorities/$REPOSITORY_INDEX/worker-jobs/42"Claim next or claim exact#
Global claim applies an optional Repository filter:
{
"accepted_job_kinds": [7, 11],
"repository_indexes": [17],
"accepted_runtime_contracts": ["ait.runner.native-job.v3"]
}Send it to POST /v1/native/worker-jobs:claim. Both arrays must be duplicate free. An empty repository_indexes array means all serving Repositories. When nothing matches, the response is 200 with claimed_job: null.
To claim an already selected Job, POST this exact body to .../worker-jobs/{worker_job_index}:claim:
{"accepted_runtime_contracts":["ait.runner.native-job.v3"]}A successful claim returns attempt_count, a hex lease_token, lease_expires_at_s, Job identity, and runtime_request. The runtime request selects a remote Snapshot, declares the direct ci/run argument vector, and sets a timeout. Execute that request exactly; do not substitute the current workspace.
Heartbeat, complete, and fail#
Heartbeat requires the current attempt and lease and forbids detail:
{"attempt_count":1,"lease_token":"<hex-token>"}POST it to .../{worker_job_index}:heartbeat before the lease expires.
Completion requires detail. For repo.ci, the fixed Job kind is sufficient for durable completion. For patchset.ci, detail must identify Job kind 7 and carry an ait.runner.native-result.v1 result:
{
"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}
]
}
}
}Terminal result status is succeeded, command_failed, or timed_out; tests_status is pass or fail; each suite status is pass or fail. suite_result_count must equal the array length. The server derives compact CI evidence and atomically attaches it to the selected Patchset.
Failure requires an error and retry decision:
{
"attempt_count": 1,
"lease_token": "<hex-token>",
"detail": {
"retryable": true,
"error": "runner lost its isolated attempt process"
}
}The error value is limited to 4,096 characters. The server requeues only when retryable is true and attempts remain. Complete, fail, and heartbeat reject a stale attempt count or lease token.
Lines and Snapshots#
Read and update Lines#
GET .../lines returns all Line projections. GET .../lines/{line_name} returns one projection including its name, status, and current head_snapshot_id.
Create or compare-and-set a Line with:
{
"head_snapshot_id": "SNP-NEW",
"expected_head_snapshot_id": "SNP-OLD"
}Both fields are optional and nullable. expected_head_snapshot_id is the compare-and-set guard. Direct remote sync may bootstrap an empty default Line, replay its current head, or move another Line. It may not move an initialized default main to a different Snapshot; that returns 409 with GOVERNED_TARGET_LINE_REQUIRES_LAND and must be completed through Land.
Close a Line by POSTing to .../lines/{line_name}:close:
{"status":"archived"}status defaults to archived when omitted.
Test Snapshot presence#
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"The response classifies the supplied Snapshot IDs without transferring their content.
Export one 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 defaults to true. path optionally narrows the exported file projection. This is a JSON Snapshot export, not a raw pack download.
zstd bulk remote sync#
The safe transfer order is: request a plan, upload missing raw packs, commit their manifest rows and Snapshots, then read or update Lines according to the governed-Line rules. Pull reverses the process by requesting a manifest and downloading its packs.
Presence plan#
{
"snapshot_ids": ["SNP-HEAD"],
"object_packs": ["OPK-A", {"pack_id":"OPK-B"}],
"tree_packs": ["TPK-A"]
}POST to .../remote-sync/zstd-bulk/plan. The three arrays may be empty. Pack entries may be IDs or manifest objects carrying pack_id. The response preserves request order in:
checked_snapshot_ids;present_snapshot_idsandmissing_snapshot_ids;present_object_pack_idsandmissing_object_pack_ids; andpresent_tree_pack_idsandmissing_tree_pack_ids.
Raw pack upload and download#
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 transfer uses the corresponding tree-packs path and media type. The path pack_id must be a valid single segment and must agree with the uploaded pack metadata. Upload responses describe the accepted pack; GET returns the exact raw bytes.
Commit#
{
"contract": "ait.remote_sync.zstd_bulk.commit.v1",
"object_packs": [],
"tree_packs": [],
"blob_locators": [],
"tree_locators": [],
"snapshots": [],
"line_update": null
}Every inventory field accepts an array or null; missing fields are treated as empty. The pack, locator, and Snapshot objects are the exact rows emitted by an import or pull manifest. Do not synthesize or drop their integrity fields. line_update, when present, has line_name, nullable head_snapshot_id, and nullable expected_head_snapshot_id.
The response reports upserted and skipped Object Packs, Tree Packs, Blobs, Trees, and Snapshots, plus remote_line, line_head_updated_after_ingest, and raw_binary_upload: true. Existing immutable content is skipped only when it agrees with authority; a mismatch is an error rather than an overwrite.
Import and pull manifests#
GET .../import-manifests/{snapshot_id} returns one complete closure:
{
"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
}For bounded ancestry, POST:
{
"contract": "ait.remote_sync.zstd_bulk.pull_manifest.request.v1",
"head_snapshot_id": "SNP-HEAD",
"have_snapshot_ids": ["SNP-BOUNDARY"]
}have_snapshot_ids must be duplicate free and is limited to 100,000 entries. The response contract is ait.remote_sync.zstd_bulk.pull_manifest.v1 and adds head_snapshot_id plus boundary_snapshot_ids. Snapshot rows are ordered from the required ancestry boundary toward the requested head; pack inventories are deduplicated and dependency ordered. Download the returned pack IDs and pass the returned row arrays unchanged to the destination commit.
Task lifecycle#
Create and read Tasks#
Create an unplanned Task:
{"title":"Correct release readback","intent":"Verify the published artifact"}Optional task_id is accepted only when it equals the next server-derived ID. Optional Plan linkage uses all three fields together:
{
"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"
}If plan_id is supplied without a revision, the route resolves the current head. If a revision is supplied without a Plan ID, it searches the selected Repository. The item must exist in that revision. Route-owned fields such as repo_id and repository_index are rejected.
Task responses contain task_id, Repository identity, sequence, title, intent, Plan linkage and drift projection, status, and timestamps. Status is active, completed, or abandoned.
List with GET .../tasks, read with GET .../tasks/{task_ref}, and close by POSTing to .../tasks/{task_ref}:close:
{"status":"completed"}Accepted close status is completed, abandoned, canceled, or cancelled; the latter three project as abandoned.
Atomic Task start#
POST .../task-start creates or selects the exact Plan revision and item, then appends the Task and first Change in one operation:
{
"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 is required and limited to 256 bytes. plan.action is:
existing: requiresplan_idand current-headplan_revision_id;create: requires a Plan revision payload underplan.payload; orrevise: requiresplan_id,plan.payload, and the currentexpected_head_revision_idunless the exact revision already exists.
The server derives Task Plan linkage and Change task_id; callers must not supply those derived fields. A replay with the same binding and payload returns the recorded atomic result.
Task audit and queues#
GET .../read/tasks/{task_id}/audit?target_line=main returns the Task, all its Changes, target-Line head, counts of open and landed Changes, and a verdict: task_completed, task_abandoned, ready_to_close, no_changes, or in_progress.
GET .../read/queue-summary and GET .../read/task-queue accept optional status=<value>. The first returns aggregate workflow counts; the second returns Task queue rows. GET .../read/reviewer-inbox returns the Repository's current review work. These are derived read models: use entity routes as the write authority.
Change, Patchset, review, policy, CI, and Land#
Create and close a Change#
{
"task_id": "T-0001",
"title": "Implement verified readback",
"base_line": "main",
"fork_snapshot_id": "SNP-BASE"
}task_id, title, and base_line are required. fork_snapshot_id is optional but, when supplied, must equal the base Line head at creation. Optional change_id must equal the server-derived full or short identity.
Change responses include change_id, full change_ref, Task and Repository, title, base Line, fork Snapshot, status, current and selected Patchset, land target, and timestamps. Status can be draft, active, review, landed, archived, superseded, or abandoned.
Close with one of these statuses:
{"status":"archived"}Accepted values are archived, superseded, abandoned, canceled, and cancelled. landed is rejected because successful Land derives that state.
Publish and select a Patchset#
POST to .../changes/{change_ref}/patchsets:
{
"base_snapshot_id": "SNP-BASE",
"revision_snapshot_id": "SNP-REVISION",
"summary": "Add verified readback",
"author_mode": "ai_with_human_review"
}Required author modes are human_only, human_with_ai_assist, ai_with_human_review, or ai_only_experimental. Optional patchset_id and patchset_number are accepted only when they match the next derived identity. Replaying the same Change/base/revision triple returns the existing Patchset.
Patchset responses include identity, Snapshots, summary, author and publish state, withdrawal flags, changed-file statistics and paths, evaluation state, creation time, CI run sequence, and compact overall/tests/lint evidence.
Select by POSTing to .../changes/{change_ref}:selectPatchset:
{"patchset_id":"T-0001/C-01/P-02"}The Patchset must belong to the Change and must not be withdrawn or invalidated.
Request and record review#
Request exactly one reviewer group:
{
"patchset_id": "T-0001/C-01/P-02",
"reviewer_groups": ["maintainers"],
"note": "Please check release semantics"
}Send it to .../changes/{change_ref}:requestReview. Optional review_id is accepted only when it matches the derived identity.
Record a review by POSTing to .../changes/{change_ref}/reviews:
{
"patchset_id": "T-0001/C-01/P-02",
"reviewer": "reviewer@example.invalid",
"action": "approve",
"comment": "Verified",
"blocking": false
}Actions are request, comment, task_comment, code_review_summary, approve, task_approve, request_changes, task_request_changes, defer, task_defer, and dismiss. comment defaults to empty and blocking to false. The Patchset must be the selected Patchset for its Change.
The review list response contains reviews plus approval, blocking, comment, and total summary counts.
Attestation and policy#
Append an attestation with PUT .../patchsets/{patchset_id}/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 is unknown, pending, pass, or fail. Defaults are unknown, revoked: false, require_tests_pass: true, and both human-review and lint requirements false. If author_mode is supplied, it must agree with the Patchset. Caller-supplied detail.patchset_ci is rejected because CI completion belongs to the Patchset and Worker Job authority.
GET .../attestation returns the latest attestation or 404 when none exists. It includes requirement flags, whether CI backed the record, author mode, and the current tests/lint summary.
POST .../patchsets/{patchset_id}:evaluatePolicy accepts an empty JSON object and appends a decision based on the latest attestation, compact CI, and live review approvals. GET .../policy returns that decision and its checks. Before the first evaluation it returns:
{"patchset_id":"...","decision":"pending","checks":[]}Run and inspect Patchset CI#
Start CI with:
{"trigger":"manual_rerun"}POST to .../patchsets/{patchset_id}:runCi. An absent trigger defaults to manual_rerun. manual_rerun and base_stale_after_land_rerun explicitly advance the run; another non-empty trigger reuses existing terminal evidence when available. A new or still-active run is delivered through a Worker Job. The response reports queued, job, delivery, trigger, and the Patchset CI projection.
Inspect with:
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 defaults to 10 and must be between 1 and 1000; the only named projection is readiness. The response reports availability, run sequence, completion time, status and counts, whether runnable evidence exists, the selected/latest Job, and recent Jobs. RC.6's compact authority does not retain full per-suite history here, so selected_suite_ids and suite_results are empty in this read model.
Atomic Task Land#
Prefer this route over direct 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, and mode are required. The key is limited to 256 bytes. task_or_change_ref may be a Task only when that Task has exactly one landable Change; otherwise send the exact Change. target_line defaults to the Change base Line. Mode is direct, merge, or ff-only. patchset_id is optional but must be selected. The compare-and-set guard may be named expected_head_snapshot_id or expected_target_line_head.
Success or replay returns the atomic contract, replay flag, top-level Task, Change, Patchset, target and landed Snapshot identifiers, plus full task, change, patchset, land, and optional history_promotion projections.
Direct POST .../changes/{change_ref}:submit accepts the Land fields without the atomic contract requirement. It is lower level and does not provide the same Task resolution and idempotent aggregate result.
Read any attempt with GET .../lands/{submission_id}. A Land projection includes selected Patchset, target Line, mode, status, failure kind, result, landed Snapshot, Line action, and timestamps. Failure kinds include stale base, blocked policy/review/CI, conflict, target update failure, and internal error.
History promotion prepare#
This specialized operation converts recorded local-land history into remote workflow records before governed remote Land. The top-level request is:
{
"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 must contain 1 through 64 items. Each item contains:
| Field | Meaning |
|---|---|
local_task_id, local_change_id, local_change_ref | Original local identities. |
expected_remote_task_id, expected_remote_change_ref | Optional fail-closed publication identities. |
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 | Recorded local Land boundary. |
snapshots | One to 64 {snapshot_id, created_at_s} entries for that boundary. |
The endpoint validates ordering, identity, Snapshot ancestry, Plan bindings, and idempotency before preparing the aggregate Patchset. Raw callers should use the exact receipt generated by the compatible ait client rather than constructing this payload by hand.
Sprint and Plan API#
The HTTP path uses sprints; response objects use Plan identifiers. A Plan revision is backed by one Markdown artifact plus normalized item metadata.
Plan revision payload#
Create and revise share these fields:
| Field | Required | Meaning |
|---|---|---|
title | create: yes; revise: no | Plan title or revision title override. |
status | no | draft, active, archived, or superseded; default is draft. active is stored and projected as the draft/open state. |
summary | no | Revision summary. |
artifact_path | yes | Repository-relative Markdown artifact path. |
artifact_selector | no | Stable selector within the artifact. |
artifact_heading | yes | Human-readable root heading. |
items | no | Normalized item array; defaults empty. |
artifact_body | no | Exact Markdown body used to pack the revision artifact. |
packed_artifact | no | Existing packed artifact descriptor when used by a compatible client. |
expected_head_revision_id | revise only, no | Compare-and-set guard for concurrent revision. |
Each items[] object may contain plan_item_ref, item text, checkbox_state, and heading_path. Items without a usable reference cannot be selected by Task linkage.
Create with POST .../sprints. Caller-supplied plan_id is rejected because the server derives it. List with GET .../sprints; optional artifact_path=<exact-path> narrows the result. Read one with GET .../sprints/{plan_id}.
Update status with:
{"status":"archived"}Append a revision by POSTing the shared revision payload to .../sprints/{plan_id}/revisions. List or read revisions with the matching GET routes.
The /artifacts GET route returns the same full revision projection as the revision GET route. The corresponding PUT is present for protocol compatibility but RC.6 always rejects it because separate supporting-artifact writes are not part of the active compact Plan layout. Put the artifact body in the Plan create or revise operation instead.
Plan lookup and Task linkage helpers#
Find active Plans containing all supplied normalized terms:
{"contains_terms":["release","readback"]}POST to .../sprint-plan-ids/by-contains. Terms must be a JSON string array; an empty normalized set returns an empty array.
Resolve Task linkage by POSTing any combination of:
{
"plan_id": "PR-12",
"origin_plan_revision_id": "plan-revision:19",
"plan_item_ref": "site/release/readback"
}No Plan and no revision returns all three fields as null. An item without Plan linkage is rejected. Missing Plan or revision identity is resolved within the numeric Repository, then ownership and item existence are validated.
GET .../read/plans/candidate-inputs?contains=release,readback normalizes the comma-separated terms and returns matching active Plans plus linked Task candidates. It is a read model for selection UI, not a mutation endpoint.
End-to-end raw HTTP example#
This abbreviated sequence shows how the lower-level APIs connect. A production client must preserve every returned identifier and stop on any non-success status.
GET /healthzuntil200andready: true.GET /v1/handshake; verify protocol and runner contracts.- Register once with
POST /v1/native/repository-authorities; persist the numeric Repository index. - Use remote-sync plan, raw pack upload, and commit to publish immutable Snapshot content without advancing an initialized governed
mainLine. - Create or atomically start the Plan-bound Task and Change.
- Publish and select the Patchset whose revision Snapshot was uploaded.
- Add attestation and any required review request.
- POST Patchset
:runCi; anait-runnerclaims, heartbeats, and completes the resulting Worker Job. - POST Patchset
:evaluatePolicy; inspect the returned decision. - POST
/task-landwith an idempotency key and expected target head. - Read the Land and Task audit; only a successful Land advances governed
mainand permits final Task completion.
For ordinary use, let ait workflow ready, ait-runner, and ait task land perform this sequence. The direct API exists for compatible integrations and operators who need explicit transport-level control.
Operator verification checklist#
- The listener is private or protected by a trusted TLS ingress.
- Health and handshake are checked separately; the compact endpoint list is not treated as the full API catalog.
- Every request is routed by the saved numeric Repository index.
- Mutating retries use operation idempotency keys where the contract provides them and compare-and-set heads where Lines can move.
503respectsRetry-After;409is investigated rather than blindly retried.- Binary media types, byte limits, pack IDs, file sizes, and digests are validated exactly.
- Retirement export is complete and independently durable before purge.
- Restore is rehearsed on an isolated server and the newly assigned Repository index is recorded.
- Worker lease tokens stay in memory or protected transient state and are never used as general API authentication.
See ait-server: Remote Authority and Recovery for deployment, preservation boundaries, and disaster-recovery procedure, and Remote Infrastructure for the server/runner operating boundary.