Seed and advertise API contract
This commit is contained in:
parent
08922ef22d
commit
af3e75508c
|
|
@ -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
20
registry/README.md
Normal 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).
|
||||
|
||||
2
registry/amduatd-api-contract.v1.json
Normal file
2
registry/amduatd-api-contract.v1.json
Normal 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"}}}}}
|
||||
|
||||
1
registry/api-contract.jsonl
Normal file
1
registry/api-contract.jsonl
Normal 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."}
|
||||
20
registry/api-contract.schema.md
Normal file
20
registry/api-contract.schema.md
Normal 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.
|
||||
|
||||
163
src/amduatd.c
163
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;
|
||||
|
|
|
|||
Loading…
Reference in a new issue