From af3e75508c47144a8a11503daa6310e0af031af6 Mon Sep 17 00:00:00 2001 From: Carl Niklas Rydberg Date: Mon, 22 Dec 2025 19:37:41 +0100 Subject: [PATCH] Seed and advertise API contract --- README.md | 9 +- registry/README.md | 20 ++++ registry/amduatd-api-contract.v1.json | 2 + registry/api-contract.jsonl | 1 + registry/api-contract.schema.md | 20 ++++ src/amduatd.c | 163 +++++++++++++++++++++++--- 6 files changed, 198 insertions(+), 17 deletions(-) create mode 100644 registry/README.md create mode 100644 registry/amduatd-api-contract.v1.json create mode 100644 registry/api-contract.jsonl create mode 100644 registry/api-contract.schema.md diff --git a/README.md b/README.md index 59de98e..81694b5 100644 --- a/README.md +++ b/README.md @@ -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` diff --git a/registry/README.md b/registry/README.md new file mode 100644 index 0000000..846c70b --- /dev/null +++ b/registry/README.md @@ -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). + diff --git a/registry/amduatd-api-contract.v1.json b/registry/amduatd-api-contract.v1.json new file mode 100644 index 0000000..d4961e8 --- /dev/null +++ b/registry/amduatd-api-contract.v1.json @@ -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"}}}}} + diff --git a/registry/api-contract.jsonl b/registry/api-contract.jsonl new file mode 100644 index 0000000..c28b90c --- /dev/null +++ b/registry/api-contract.jsonl @@ -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."} diff --git a/registry/api-contract.schema.md b/registry/api-contract.schema.md new file mode 100644 index 0000000..02ccfa6 --- /dev/null +++ b/registry/api-contract.schema.md @@ -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. + diff --git a/src/amduatd.c b/src/amduatd.c index 64e5cfc..8d6da77 100644 --- a/src/amduatd.c +++ b/src/amduatd.c @@ -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;