Browse RC.8 documentation
RC.8 Documentation revision 1

ait-server REST API Reference

Call every RC.8 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.8 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.8 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:

Code · bash
BASE=http://127.0.0.1:8088
REPOSITORY_INDEX=17

Common protocol rules#

Addressing and identifiers#

  • {repository_index} and {worker_job_index} are canonical unsigned base-10 u32 values. 0 is valid; 01, signs, spaces, and values above 4294967295 are 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_s are unsigned Unix seconds. Fields such as created_at are RFC 3339 strings. A missing event is normally null in 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:

BodyRequired Content-TypeResponse type
Object Packapplication/vnd.ait.remote-sync.object-pack+zstdsame value
Tree Packapplication/vnd.ait.remote-sync.tree-pack+zstdsame value
Retirement or restore fileapplication/vnd.ait.remote-authority-file.v1same 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:

Code · json
{"error":"human-readable diagnostic"}
StatusMeaning
200Successful read, idempotent replay, update, action, or binary download.
201New Repository authority or restore session created.
400Invalid path value, request field, state transition, or malformed JSON handled by the route.
404Unknown route, Repository, entity, pack, file, or action suffix.
405The route exists but the HTTP method is not bound.
409Authority conflict, lifecycle conflict, stale Line head, governed target-Line movement, or mismatched retirement acknowledgement.
413Request body exceeds the active route limit.
415A JSON extractor or binary route rejects the request media type.
422JSON syntax was accepted but could not be deserialized into the typed request.
500Corrupt, incompatible, I/O-failed, or otherwise internal authority operation.
503Startup 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 /healthz returns 503 and {"ready":false,"status":"starting"};
  • every other path returns 503 and {"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.8 release surface.

Service discovery#

Method and pathOperation
GET /healthzRead readiness and authority backend.
GET /v1/handshakeRead protocol, package, CI, runner, and compact endpoint capabilities.
GET /v1/native/capabilitiesRead operational Repository and Worker Job contracts.

Repository registry, backup, restore, and jobs#

Method and pathOperation
GET /v1/native/repository-authoritiesList or discover Repository authorities.
POST /v1/native/repository-authoritiesRegister a Repository authority.
GET /v1/native/repository-authorities/{repository_index}Read one Repository authority.
POST /v1/native/repository-authorities/{repository_index}:runCiEnqueue Repository CI.
GET /v1/native/repository-authorities/{repository_index}/retirementRead retirement drain/export status.
POST /v1/native/repository-authorities/{repository_index}/retirementBegin 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/purgePurge after exact export acknowledgement.
POST /v1/native/repository-authorities/{repository_index}/retirement/abortReturn a retiring Repository to active state.
POST /v1/native/repository-restoresBegin 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}/commitValidate and activate a restored Repository.
POST /v1/native/worker-jobs:claimClaim the next matching external-runner job.
GET /v1/native/repository-authorities/{repository_index}/worker-jobsList 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}:claimClaim one exact job.
POST /v1/native/repository-authorities/{repository_index}/worker-jobs/{worker_job_index}:heartbeatExtend an active lease.
POST /v1/native/repository-authorities/{repository_index}/worker-jobs/{worker_job_index}:completeCommit a successful attempt.
POST /v1/native/repository-authorities/{repository_index}/worker-jobs/{worker_job_index}:failRecord a retryable or terminal attempt failure.

Lines, Snapshots, and remote sync#

Method and pathOperation
GET /v1/native/repository-authorities/{repository_index}/linesList 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}:closeClose a Line.
POST /v1/native/repository-authorities/{repository_index}/snapshots:existsTest 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/planClassify present and missing Snapshots and packs.
POST /v1/native/repository-authorities/{repository_index}/remote-sync/zstd-bulk/commitCommit 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-manifestsGet 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 pathOperation
GET /v1/native/repository-authorities/{repository_index}/tasksList Tasks, newest first.
POST /v1/native/repository-authorities/{repository_index}/tasksCreate a Task, optionally Plan-bound.
POST /v1/native/repository-authorities/{repository_index}/task-startAtomically 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}:closeComplete or abandon a Task.
GET /v1/native/repository-authorities/{repository_index}/read/tasks/{task_id}/auditRead Task closeout evidence.
GET /v1/native/repository-authorities/{repository_index}/read/queue-summaryRead the queue summary projection.
GET /v1/native/repository-authorities/{repository_index}/read/task-queueRead the Task queue projection.
GET /v1/native/repository-authorities/{repository_index}/read/reviewer-inboxRead review work assigned to the Repository.

Change, Patchset, review, policy, CI, and Land#

Method and pathOperation
GET /v1/native/repository-authorities/{repository_index}/changesList Changes, newest first.
POST /v1/native/repository-authorities/{repository_index}/changesCreate 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}:closeArchive, abandon, or supersede a Change.
POST /v1/native/repository-authorities/{repository_index}/changes/{change_ref}:selectPatchsetSelect a Patchset.
POST /v1/native/repository-authorities/{repository_index}/changes/{change_ref}:requestReviewRequest one reviewer group.
POST /v1/native/repository-authorities/{repository_index}/changes/{change_ref}:submitSubmit direct Land for a Change.
GET /v1/native/repository-authorities/{repository_index}/changes/{change_ref}/patchsetsList Patchsets in ordinal order.
POST /v1/native/repository-authorities/{repository_index}/changes/{change_ref}/patchsetsPublish a Patchset.
GET /v1/native/repository-authorities/{repository_index}/changes/{change_ref}/reviewsRead review rows and summary counts.
POST /v1/native/repository-authorities/{repository_index}/changes/{change_ref}/reviewsRecord 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}:runCiStart or reuse Patchset CI and enqueue a Worker Job when required.
POST /v1/native/repository-authorities/{repository_index}/patchsets/{patchset_id}:evaluatePolicyEvaluate current attestation, CI, and review gates.
GET /v1/native/repository-authorities/{repository_index}/patchsets/{patchset_id}/attestationRead the latest attestation.
PUT /v1/native/repository-authorities/{repository_index}/patchsets/{patchset_id}/attestationAppend a new attestation.
GET /v1/native/repository-authorities/{repository_index}/patchsets/{patchset_id}/policyRead the latest policy decision or pending projection.
GET /v1/native/repository-authorities/{repository_index}/read/patchsets/{patchset_id}/ci-statusRead 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-landAtomically resolve and Land one Task or exact Change.
POST /v1/native/repository-authorities/{repository_index}/history-promotion:preparePrepare recorded local-land history for governed remote Land.

Sprint and Plan authority#

Method and pathOperation
GET /v1/native/repository-authorities/{repository_index}/sprintsList Plans, optionally by artifact path.
POST /v1/native/repository-authorities/{repository_index}/sprintsCreate 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}/revisionsList Plan revisions newest first.
POST /v1/native/repository-authorities/{repository_index}/sprints/{plan_id}/revisionsAppend 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}/artifactsRead the same full revision, including its packed artifact projection.
PUT /v1/native/repository-authorities/{repository_index}/sprints/{plan_id}/revisions/{plan_revision_id}/artifactsReserved route; RC.8 rejects supporting artifact writes.
POST /v1/native/repository-authorities/{repository_index}/sprint-plan-ids/by-containsFind active Plan IDs matching all normalized terms.
POST /v1/native/repository-authorities/{repository_index}/sprint-task-linkage/resolveNormalize and validate Task-to-Plan linkage.
GET /v1/native/repository-authorities/{repository_index}/read/plans/candidate-inputsRead matching Plans and their linked Task candidates.

Discovery endpoints#

Health#

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

Ready response:

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"]
  }
}

Treat HTTP 200 plus ready: true as process readiness. It does not prove that a backup is current or recoverable.

Handshake#

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

The response fields are:

FieldMeaning
readyReady-router state.
contract_versionAgent/server protocol compatibility version.
package_versionRunning ait-server package version.
authority_backendbinary_v0 in RC.8.
repository_identityrepository_index.
ci_capabilitiesRemote-sync features and native runner contract.
operational_capabilitiesRepository and Worker Job identity contracts.
supported_async_job_typesTen durable job type names known by the server.
endpointsCompact 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#

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"
Request fieldRequiredRules
repository_nameyesNon-empty UTF-8 display name; duplicates are allowed.
namespaceyesZero, one, or two ASCII letters, digits, _, or -. The server stores exact zero-padded bytes.
policy_flagsyesUnsigned 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.

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
  }
}

Persist repository_index from the response. Do not rediscover by name and guess when several authorities share a display name.

Discover and read authorities#

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"

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:

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 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:

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"

The response contains:

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":"..."}
    ]
  }
}

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:

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

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"

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:

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

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

FieldRule
schemaait.remote-export.v1
statecomplete
repo_nameNon-empty source Repository name.
namespaceExact exported namespace. It must not be owned by a live Repository.
exported_at_sExport time in Unix seconds.
filesExact 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:

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>"

Each upload is checked against the manifest size and digest. Then commit with no request body:

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

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 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:

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

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

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

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

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}
      ]
    }
  }
}

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:

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

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

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

status defaults to archived when omitted.

Test Snapshot presence#

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"

The response classifies the supplied Snapshot IDs without transferring their content.

Export one 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 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#

Code · json
{
  "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_ids and missing_snapshot_ids;
  • present_object_pack_ids and missing_object_pack_ids; and
  • present_tree_pack_ids and missing_tree_pack_ids.

Raw pack upload and download#

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 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#

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

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
}

For bounded ancestry, 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 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:

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

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"
}

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:

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

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 is required and limited to 256 bytes. plan.action is:

  • existing: requires plan_id and current-head plan_revision_id;
  • create: requires a Plan revision payload under plan.payload; or
  • revise: requires plan_id, plan.payload, and the current expected_head_revision_id unless 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#

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

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

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

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

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

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

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 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:

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

Run and inspect Patchset CI#

Start CI with:

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

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 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.8'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:

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"
}

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:

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 must contain 1 through 64 items. Each item contains:

FieldMeaning
local_task_id, local_change_id, local_change_refOriginal local identities.
expected_remote_task_id, expected_remote_change_refOptional 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_sRecorded local Land boundary.
snapshotsOne 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:

FieldRequiredMeaning
titlecreate: yes; revise: noPlan title or revision title override.
statusnodraft, active, archived, or superseded; default is draft. active is stored and projected as the draft/open state.
summarynoRevision summary.
artifact_pathyesRepository-relative Markdown artifact path.
artifact_selectornoStable selector within the artifact.
artifact_headingyesHuman-readable root heading.
itemsnoNormalized item array; defaults empty.
artifact_bodynoExact Markdown body used to pack the revision artifact.
packed_artifactnoExisting packed artifact descriptor when used by a compatible client.
expected_head_revision_idrevise only, noCompare-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:

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

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

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

  1. GET /healthz until 200 and ready: true.
  2. GET /v1/handshake; verify protocol and runner contracts.
  3. Register once with POST /v1/native/repository-authorities; persist the numeric Repository index.
  4. Use remote-sync plan, raw pack upload, and commit to publish immutable Snapshot content without advancing an initialized governed main Line.
  5. Create or atomically start the Plan-bound Task and Change.
  6. Publish and select the Patchset whose revision Snapshot was uploaded.
  7. Add attestation and any required review request.
  8. POST Patchset :runCi; an ait-runner claims, heartbeats, and completes the resulting Worker Job.
  9. POST Patchset :evaluatePolicy; inspect the returned decision.
  10. POST /task-land with an idempotency key and expected target head.
  11. Read the Land and Task audit; only a successful Land advances governed main and 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.
  • 503 respects Retry-After; 409 is 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.

Version authority

Checked against the exact RC.8 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-F136DB9A342B
  • ait-serverSNP-FBF302ADABC5
  • ait-runnerSNP-DF67532B0360
  • ait-pythonSNP-39225802847F
  • ait-nodeSNP-3D474B60B26F