Experimental HTTP transport, revision 1
This is the first post-milestone-1 transport binding. It changes no canonical object schemas, IDs, or local repository format. The reference server is a single-repository, loopback-only development service, not the hosted AIPHub.
Scope and authorization
The base path is /aip/v1/repositories/{name}. A name contains 1..64 ASCII
letters, digits, hyphens or underscores. It is an administrator-selected routing
label, not a filesystem path or canonical ObjectID. Each service instance exposes
one explicitly configured repository. Objects never resolve across repositories.
Private mode requires a bearer token for every request, including existence and
inventory queries. Public mode permits anonymous GET and HEAD only. PUT always
requires the configured token. A token grants read/write access to this one
repository; account login, per-user roles and multiple credentials are deferred.
Authorization runs before object lookup and returns the same 401 response for
missing or incorrect credentials, irrespective of object existence. Credentials
are accepted only in the Authorization header, never query parameters or cookies.
Responses use Cache-Control: no-store; CORS is not enabled. Requests with an
Origin header are rejected in this server-to-server prototype.
The CLI is aip serve --name NAME [--listen 127.0.0.1:7777] [--visibility private|public]. It requires AIP_SERVE_TOKEN in its environment:
at least 32 printable ASCII characters without whitespace. Operators must supply
a randomly generated secret through their credential tooling. AIP does not
print, persist, or put the token in command arguments. Private is the default.
The listener must be a literal loopback IP and a port. Plain HTTP is permitted
only for this local profile. Any future remote deployment MUST use HTTPS and
separately define credential issuance, rotation, quotas and hosted authorization.
serve runs until interrupted. On graceful shutdown it returns the normal CLI
success envelope with repository name and listen address; startup failures use
the existing CLI error envelope. There is no stream of JSON events on stdout.
The server neither executes uploaded code nor changes HEAD or working files.
Objects and discovery
| Request relative to base | Response |
|---|---|
GET /objects?after=ID&limit=N |
JSON inventory page, ascending ObjectIDs. |
HEAD /objects/{id} |
200 if a verified object exists, 404 if absent; no body. |
GET /objects/{id} |
200 with exact canonical bytes, Content-Type: application/cbor. |
PUT /objects/{id} |
Canonical bytes with Content-Type: application/cbor; JSON acknowledgment. |
IDs must be full lowercase SHA-256 hex. PUT checks the URL hash, canonical encoding, schema, all referenced objects and graph relationships before storing anything. Dependencies MUST arrive first. Missing dependencies or invalid graphs return 422 and do not poison the visible CAS. Unknown versions are rejected. An identical PUT succeeds without replacing existing bytes. An existing corrupt object is an error, never overwritten. Received objects are immediately visible to inventory and reverse queries, even without registry references. PUT does not implicitly create an intent/state registry reference.
Inventory includes every object type, including evidence and decisions, so
reverse associations are discoverable. Success data is
{"ids":[ID,...],"next":ID|null}; next is the final returned ID when more entries
exist. The default limit is 256; accepted limits are 1..1000. Omit after for the
first page. All query parameters are single-valued; unknown parameters fail.
Inventory is not a transaction across pages. With concurrent uploads, clients
must reconcile again from the beginning to discover IDs inserted before their
cursor. No deletion or garbage collection is offered by this binding. Readers
must independently validate downloaded bytes and graphs.
Object reads verify the reachable graph before success. Inventory checks bytes, encoding and schema for returned IDs; it is discovery, not a graph-validation certificate. The implementation enumerates loose object filenames for every page and is intended for small repositories.
Registry references
Only states/{id} and intents/{id} are supported. HEAD is local workspace
selection and is never exposed or updated through this binding. There are no
named branch tips, remote workspace selection or arbitrary reference paths.
GET /refs/{kind}/{id} returns data {"id":ID} or 404 if absent. It verifies
the reference’s content, target type and reachable graph.
PUT /refs/{kind}/{id} accepts application/json with exactly
{"expected":null|ID,"next":ID}. Both fields are required, with no duplicate
or unknown fields. Next MUST equal the ID in the reference’s name. Expected is
null for creation, or that same ID for reaffirmation. Under the repository write
lock, the server compares the observed reference with expected, verifies the
target graph/type, then durably publishes the reference. Mismatched expected
returns 409 without changing the reference. A retry after a lost response should
resolve the reference first. This is compare-and-swap on one reference, not a
transaction over several references or uploaded objects.
Responses and limits
JSON responses use {"ok":true,"data":...} or
{"ok":false,"error":{"code":CODE,"message":TEXT}}. Messages are diagnostic;
clients use status and code. Object PUT success data is {"id":ID}. Successful
JSON requests return 200, including duplicate object uploads and new references.
| HTTP status | Error code | Meaning |
|---|---|---|
| 400 | invalid_request | Invalid query, hash mismatch or reference body. |
| 401 | unauthorized | Missing or incorrect bearer credential. |
| 403 | origin_forbidden | Browser-origin requests are outside this profile. |
| 404 | not_found | Unknown/malformed authorized route or missing object/reference. |
| 405 | method_not_allowed | Unsupported method; Allow names supported methods. |
| 409 | conflict | Reference precondition failed. |
| 413 | too_large | Body exceeds the configured protocol limit. |
| 415 | unsupported_media_type | Wrong content type or encoded request body. |
| 422 | invalid_object | Object schema, references or relationships are invalid. |
| 500 | storage_error | Corruption, write contention or storage failure. |
| 503 | busy | Another request is being processed; retry later. |
HEAD never returns a response body, including on errors. Object bodies are limited to 67,108,864 bytes, reference bodies to 1024 bytes. Compressed request bodies are not accepted. Unknown JSON fields, duplicate keys, trailing values and missing fields are rejected. The reference handler permits one active request at a time and rejects excess concurrent requests with 503 to bound memory use. It sets header/read/write/idle timeouts. Graph limits remain those in encoding.md; no constant-time graph-validation or large-repository performance promise is made.
Transfer procedure and future hosting
Discover object IDs, compare with the destination inventory, download missing objects, traverse their references, and upload in dependency-first order. Include evidence and decisions discovered in inventory, not just a state’s forward closure. Verify destination graphs, then create/reaffirm the selected state and intent registry references. Transfers can be retried because object IDs are immutable; complete objects from interrupted transfers remain discoverable.
The initial server implements this wire boundary; a user-facing synchronization client is subsequent work. R2 storage, D1 metadata, Astro 7/Starlight distribution pages, multi-user authentication and hosted public/private repositories belong to AIPHub adapters. They must preserve the byte/hash and authorization boundaries above. Neither Cloudflare nor AIPHub becomes a local AIP dependency.