Seed and advertise API contract

This commit is contained in:
Carl Niklas Rydberg 2025-12-22 19:37:41 +01:00
parent 08922ef22d
commit af3e75508c
6 changed files with 198 additions and 17 deletions

View file

@ -37,6 +37,12 @@ Query store meta:
curl --unix-socket amduatd.sock http://localhost/v1/meta
```
Discover the store-backed API contract ref:
```sh
curl --unix-socket amduatd.sock http://localhost/v1/contract
```
Upload raw bytes:
```sh
@ -68,7 +74,8 @@ curl --unix-socket amduatd.sock -X POST http://localhost/v1/pel/run \
## Current endpoints
- `GET /v1/meta``{store_id, encoding_profile_id, hash_id}`
- `GET /v1/meta``{store_id, encoding_profile_id, hash_id, api_contract_ref}`
- `GET /v1/contract``{ref}` (fetch bytes via `GET /v1/artifacts/{ref}`)
- `POST /v1/artifacts`
- raw bytes: `Content-Type: application/octet-stream` (+ optional `X-Amduat-Type-Tag: 0x...`)
- artifact framing: `Content-Type: application/vnd.amduat.asl.artifact+v1`

20
registry/README.md Normal file
View file

@ -0,0 +1,20 @@
# amduat-api registry (draft)
This directory mirrors the core `vendor/amduat/registry/` model for the
**amduatd HTTP API contract**.
## Goal
- Keep a stable, machine-readable API contract in-repo.
- Seed that contract into the daemon's single ASL store root at startup.
- Advertise the store-backed contract `ref` via `GET /v1/meta`.
The daemon remains thin: the contract bytes are data, and the store-backed `ref`
acts as the version identifier.
## Files
- `api-contract.schema.md` — JSONL manifest schema for API contracts.
- `api-contract.jsonl` — manifest of published contracts.
- `amduatd-api-contract.v1.json` — contract bytes (v1).

View file

@ -0,0 +1,2 @@
{"contract":"AMDUATD/API/1","base_path":"/v1","endpoints":[{"method":"GET","path":"/v1/meta"},{"method":"HEAD","path":"/v1/meta"},{"method":"GET","path":"/v1/contract"},{"method":"POST","path":"/v1/artifacts"},{"method":"GET","path":"/v1/artifacts/{ref}"},{"method":"HEAD","path":"/v1/artifacts/{ref}"},{"method":"POST","path":"/v1/pel/run"}],"schemas":{"pel_run_request":{"type":"object","required":["program_ref","input_refs"],"properties":{"program_ref":{"type":"string","description":"hex ref"},"input_refs":{"type":"array","items":{"type":"string","description":"hex ref"}},"params_ref":{"type":"string","description":"hex ref"},"scheme_ref":{"type":"string","description":"hex ref or 'dag'"}}},"pel_run_response":{"type":"object","required":["result_ref","output_refs","status"],"properties":{"result_ref":{"type":"string","description":"hex ref"},"trace_ref":{"type":"string","description":"hex ref"},"output_refs":{"type":"array","items":{"type":"string","description":"hex ref"}},"status":{"type":"string"}}}}}

View file

@ -0,0 +1 @@
{"registry":"AMDUATD/API","contract":"AMDUATD/API/1","handle":"amduat.api.amduatd.contract.v1@1","media_type":"application/json","status":"active","bytes_sha256":"9bc4d4c8cf9512afebfb4b6674c58c93d6f47831e948f1795804a27872556263","notes":"Seeded into the ASL store at amduatd startup; ref is advertised via /v1/meta."}

View file

@ -0,0 +1,20 @@
# Schema: registry/api-contract.jsonl
Each line is one JSON object (JSONL). This mirrors the conventions described in
`vendor/amduat/registry/README.md`.
## Canonical descriptor digest
`bytes_sha256` is the SHA-256 digest (lowercase hex) of the exact contract bytes
as stored in the corresponding `registry/*.json` file.
## Fields
- `registry` (string, required): registry name, fixed to `AMDUATD/API`.
- `contract` (string, required): contract series identifier (e.g. `AMDUATD/API/1`).
- `handle` (string, required): stable handle (e.g. `amduat.api.amduatd.contract.v1@1`).
- `media_type` (string, required): content type of the bytes (e.g. `application/json`).
- `status` (string, required): `active` | `deprecated` | `reserved`.
- `bytes_sha256` (string, required): sha256 of the bytes file.
- `notes` (string, optional): human notes.

View file

@ -5,6 +5,7 @@
#include "amduat/asl/asl_store_fs_meta.h"
#include "amduat/asl/ref_text.h"
#include "amduat/asl/store.h"
#include "amduat/asl/ref_derive.h"
#include "amduat/enc/asl1_core_codec.h"
#include "amduat/enc/pel1_result.h"
#include "amduat/format/pel.h"
@ -30,6 +31,43 @@
static const char *const AMDUATD_DEFAULT_ROOT = ".amduat-asl";
static const char *const AMDUATD_DEFAULT_SOCK = "amduatd.sock";
static const char k_amduatd_contract_v1_json[] =
"{"
"\"contract\":\"AMDUATD/API/1\","
"\"base_path\":\"/v1\","
"\"endpoints\":["
"{\"method\":\"GET\",\"path\":\"/v1/meta\"},"
"{\"method\":\"HEAD\",\"path\":\"/v1/meta\"},"
"{\"method\":\"GET\",\"path\":\"/v1/contract\"},"
"{\"method\":\"POST\",\"path\":\"/v1/artifacts\"},"
"{\"method\":\"GET\",\"path\":\"/v1/artifacts/{ref}\"},"
"{\"method\":\"HEAD\",\"path\":\"/v1/artifacts/{ref}\"},"
"{\"method\":\"POST\",\"path\":\"/v1/pel/run\"}"
"],"
"\"schemas\":{"
"\"pel_run_request\":{"
"\"type\":\"object\","
"\"required\":[\"program_ref\",\"input_refs\"],"
"\"properties\":{"
"\"program_ref\":{\"type\":\"string\",\"description\":\"hex ref\"},"
"\"input_refs\":{\"type\":\"array\",\"items\":{\"type\":\"string\",\"description\":\"hex ref\"}},"
"\"params_ref\":{\"type\":\"string\",\"description\":\"hex ref\"},"
"\"scheme_ref\":{\"type\":\"string\",\"description\":\"hex ref or 'dag'\"}"
"}"
"},"
"\"pel_run_response\":{"
"\"type\":\"object\","
"\"required\":[\"result_ref\",\"output_refs\",\"status\"],"
"\"properties\":{"
"\"result_ref\":{\"type\":\"string\",\"description\":\"hex ref\"},"
"\"trace_ref\":{\"type\":\"string\",\"description\":\"hex ref\"},"
"\"output_refs\":{\"type\":\"array\",\"items\":{\"type\":\"string\",\"description\":\"hex ref\"}},"
"\"status\":{\"type\":\"string\"}"
"}"
"}"
"}"
"}\n";
static volatile sig_atomic_t amduatd_should_exit = 0;
static void amduatd_on_signal(int signo) {
@ -695,19 +733,47 @@ static bool amduatd_parse_type_tag_hex(const char *text,
static bool amduatd_handle_meta(int fd,
const amduat_asl_store_fs_config_t *cfg,
amduat_reference_t api_contract_ref,
bool head_only) {
char json[512];
int n = snprintf(json,
sizeof(json),
"{"
"\"store_id\":\"%s\","
"\"encoding_profile_id\":\"0x%04x\","
"\"hash_id\":\"0x%04x\""
"}\n",
cfg != NULL ? cfg->store_id : "",
cfg != NULL ? (unsigned int)cfg->config.encoding_profile_id
: 0u,
cfg != NULL ? (unsigned int)cfg->config.hash_id : 0u);
char json[1024];
char *contract_hex = NULL;
int n;
if (api_contract_ref.hash_id != 0 && api_contract_ref.digest.data != NULL &&
api_contract_ref.digest.len != 0) {
(void)amduat_asl_ref_encode_hex(api_contract_ref, &contract_hex);
}
if (contract_hex != NULL) {
n = snprintf(json,
sizeof(json),
"{"
"\"store_id\":\"%s\","
"\"encoding_profile_id\":\"0x%04x\","
"\"hash_id\":\"0x%04x\","
"\"api_contract_ref\":\"%s\""
"}\n",
cfg != NULL ? cfg->store_id : "",
cfg != NULL ? (unsigned int)cfg->config.encoding_profile_id
: 0u,
cfg != NULL ? (unsigned int)cfg->config.hash_id : 0u,
contract_hex);
} else {
n = snprintf(json,
sizeof(json),
"{"
"\"store_id\":\"%s\","
"\"encoding_profile_id\":\"0x%04x\","
"\"hash_id\":\"0x%04x\","
"\"api_contract_ref\":null"
"}\n",
cfg != NULL ? cfg->store_id : "",
cfg != NULL ? (unsigned int)cfg->config.encoding_profile_id
: 0u,
cfg != NULL ? (unsigned int)cfg->config.hash_id : 0u);
}
free(contract_hex);
if (n <= 0 || (size_t)n >= sizeof(json)) {
return amduatd_http_send_text(fd, 500, "Internal Server Error", "error\n",
head_only);
@ -715,6 +781,58 @@ static bool amduatd_handle_meta(int fd,
return amduatd_http_send_json(fd, 200, "OK", json, head_only);
}
static bool amduatd_handle_get_contract(int fd,
amduat_reference_t api_contract_ref) {
char *hex = NULL;
char json[2048];
int n;
if (api_contract_ref.hash_id == 0 || api_contract_ref.digest.data == NULL ||
api_contract_ref.digest.len == 0) {
return amduatd_http_send_json(fd, 404, "Not Found", "{\"ref\":null}\n",
false);
}
if (!amduat_asl_ref_encode_hex(api_contract_ref, &hex)) {
return amduatd_http_send_text(fd, 500, "Internal Server Error",
"encode error\n", false);
}
n = snprintf(json, sizeof(json), "{\"ref\":\"%s\"}\n", hex);
free(hex);
if (n <= 0 || (size_t)n >= sizeof(json)) {
return amduatd_http_send_text(fd, 500, "Internal Server Error", "error\n",
false);
}
return amduatd_http_send_json(fd, 200, "OK", json, false);
}
static bool amduatd_seed_api_contract(amduat_asl_store_t *store,
const amduat_asl_store_fs_config_t *cfg,
amduat_reference_t *out_ref) {
amduat_artifact_t artifact;
amduat_asl_store_error_t err;
if (out_ref != NULL) {
memset(out_ref, 0, sizeof(*out_ref));
}
if (store == NULL || cfg == NULL || out_ref == NULL) {
return false;
}
artifact = amduat_artifact(amduat_octets(k_amduatd_contract_v1_json,
strlen(k_amduatd_contract_v1_json)));
(void)amduat_asl_ref_derive(artifact,
cfg->config.encoding_profile_id,
cfg->config.hash_id,
out_ref,
NULL);
err = amduat_asl_store_put(store, artifact, out_ref);
if (err != AMDUAT_ASL_STORE_OK) {
return false;
}
return true;
}
static bool amduatd_handle_get_artifact(int fd,
amduat_asl_store_t *store,
const char *path,
@ -1306,7 +1424,8 @@ pel_run_cleanup:
static bool amduatd_handle_conn(int fd,
amduat_asl_store_t *store,
const amduat_asl_store_fs_config_t *cfg) {
const amduat_asl_store_fs_config_t *cfg,
amduat_reference_t api_contract_ref) {
amduatd_http_req_t req;
char no_query[1024];
@ -1317,10 +1436,13 @@ static bool amduatd_handle_conn(int fd,
amduatd_path_without_query(req.path, no_query, sizeof(no_query));
if (strcmp(req.method, "GET") == 0 && strcmp(no_query, "/v1/meta") == 0) {
return amduatd_handle_meta(fd, cfg, false);
return amduatd_handle_meta(fd, cfg, api_contract_ref, false);
}
if (strcmp(req.method, "HEAD") == 0 && strcmp(no_query, "/v1/meta") == 0) {
return amduatd_handle_meta(fd, cfg, true);
return amduatd_handle_meta(fd, cfg, api_contract_ref, true);
}
if (strcmp(req.method, "GET") == 0 && strcmp(no_query, "/v1/contract") == 0) {
return amduatd_handle_get_contract(fd, api_contract_ref);
}
if (strcmp(req.method, "POST") == 0 && strcmp(no_query, "/v1/artifacts") == 0) {
@ -1359,9 +1481,12 @@ int main(int argc, char **argv) {
amduat_asl_store_fs_config_t cfg;
amduat_asl_store_fs_t fs;
amduat_asl_store_t store;
amduat_reference_t api_contract_ref;
int i;
int sfd = -1;
memset(&api_contract_ref, 0, sizeof(api_contract_ref));
for (i = 1; i < argc; ++i) {
if (strcmp(argv[i], "--root") == 0) {
if (i + 1 >= argc) {
@ -1397,6 +1522,11 @@ int main(int argc, char **argv) {
}
amduat_asl_store_init(&store, cfg.config, amduat_asl_store_fs_ops(), &fs);
if (!amduatd_seed_api_contract(&store, &cfg, &api_contract_ref)) {
fprintf(stderr, "error: failed to seed api contract\n");
return 8;
}
signal(SIGINT, amduatd_on_signal);
signal(SIGTERM, amduatd_on_signal);
@ -1446,10 +1576,11 @@ int main(int argc, char **argv) {
break;
}
(void)amduatd_handle_conn(cfd, &store, &cfg);
(void)amduatd_handle_conn(cfd, &store, &cfg, api_contract_ref);
(void)close(cfd);
}
amduat_reference_free(&api_contract_ref);
(void)unlink(sock_path);
(void)close(sfd);
return 0;