diff --git a/CMakeLists.txt b/CMakeLists.txt index 4597847..730d92b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,6 +15,6 @@ target_include_directories(amduatd ) target_link_libraries(amduatd - PRIVATE amduat_pel amduat_format amduat_asl_store_fs amduat_asl amduat_enc - amduat_hash_asl1 amduat_util + PRIVATE amduat_tgk amduat_pel amduat_format amduat_asl_store_fs amduat_asl + amduat_enc amduat_hash_asl1 amduat_util ) diff --git a/README.md b/README.md index ab1a1a4..e513ec5 100644 --- a/README.md +++ b/README.md @@ -100,19 +100,47 @@ curl --unix-socket amduatd.sock -X POST http://localhost/v1/pel/programs \ -d '{"nodes":[{"id":1,"op":{"name":"pel.bytes.concat","version":1},"inputs":[{"external":{"input_index":0}},{"external":{"input_index":1}}],"params_hex":""}],"roots":[{"node_id":1,"output_index":0}]}' ``` +Create a named concept and publish a ref (so you can use the name instead of hex refs): + +```sh +curl --unix-socket amduatd.sock -X POST http://localhost/v1/concepts \ + -H 'Content-Type: application/json' \ + -d '{"name":"hello","ref":""}' +``` + +Publish a new version: + +```sh +curl --unix-socket amduatd.sock -X POST http://localhost/v1/concepts/hello/publish \ + -H 'Content-Type: application/json' \ + -d '{"ref":""}' +``` + +Resolve the latest ref: + +```sh +curl --unix-socket amduatd.sock http://localhost/v1/resolve/hello +``` + ## Current endpoints - `GET /v1/meta` → `{store_id, encoding_profile_id, hash_id, api_contract_ref}` - `GET /v1/contract` → contract bytes (JSON) (+ `X-Amduat-Contract-Ref` header) - `GET /v1/contract?format=ref` → `{ref}` +- `GET /v1/ui` → browser UI for authoring/running programs - `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` - `GET|HEAD /v1/artifacts/{ref}` - raw bytes default - artifact framing: `?format=artifact` +- `POST /v1/concepts` + - request: `{name, ref?}` (`name` is lowercase; `ref` publishes an initial version) + - response: `{name, concept_ref}` +- `POST /v1/concepts/{name}/publish` → publishes a new version `{ref}` +- `GET /v1/resolve/{name}` → `{ref}` (latest published) - `POST /v1/pel/run` - - request: `{program_ref, input_refs[], params_ref?, scheme_ref?}` (refs are hex strings; omit `scheme_ref` to use `dag`) + - request: `{program_ref, input_refs[], params_ref?, scheme_ref?}` (`program_ref`/`input_refs`/`params_ref` accept hex refs or concept names; omit `scheme_ref` to use `dag`) - response: `{result_ref, trace_ref?, output_refs[], status}` - `POST /v1/pel/programs` - request: authoring JSON for `PEL/PROGRAM-DAG/1` (kernel ops only; `params_hex` is raw hex bytes) diff --git a/registry/amduatd-api-contract.v1.json b/registry/amduatd-api-contract.v1.json index d16fd64..c1eb70f 100644 --- a/registry/amduatd-api-contract.v1.json +++ b/registry/amduatd-api-contract.v1.json @@ -1 +1 @@ -{"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"},{"method":"POST","path":"/v1/pel/programs"}],"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"}}},"pel_program_author_request":{"type":"object","required":["nodes","roots"],"properties":{"nodes":{"type":"array"},"roots":{"type":"array"}}}}} +{"contract":"AMDUATD/API/1","base_path":"/v1","endpoints":[{"method":"GET","path":"/v1/ui"},{"method":"GET","path":"/v1/meta"},{"method":"HEAD","path":"/v1/meta"},{"method":"GET","path":"/v1/contract"},{"method":"POST","path":"/v1/concepts"},{"method":"POST","path":"/v1/concepts/{name}/publish"},{"method":"GET","path":"/v1/resolve/{name}"},{"method":"POST","path":"/v1/artifacts"},{"method":"GET","path":"/v1/artifacts/{ref}"},{"method":"HEAD","path":"/v1/artifacts/{ref}"},{"method":"POST","path":"/v1/pel/run"},{"method":"POST","path":"/v1/pel/programs"}],"schemas":{"pel_run_request":{"type":"object","required":["program_ref","input_refs"],"properties":{"program_ref":{"type":"string","description":"hex ref or concept name"},"input_refs":{"type":"array","items":{"type":"string","description":"hex ref or concept name"}},"params_ref":{"type":"string","description":"hex ref or concept name"},"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"}}},"pel_program_author_request":{"type":"object","required":["nodes","roots"],"properties":{"nodes":{"type":"array"},"roots":{"type":"array"}}}}} diff --git a/registry/api-contract.jsonl b/registry/api-contract.jsonl index 620a134..5f75a0c 100644 --- a/registry/api-contract.jsonl +++ b/registry/api-contract.jsonl @@ -1 +1 @@ -{"registry":"AMDUATD/API","contract":"AMDUATD/API/1","handle":"amduat.api.amduatd.contract.v1@1","media_type":"application/json","status":"active","bytes_sha256":"7c15cf17844d0b4637f4c164088a260ab898f235643659bc8fd5230a6b5745ee","notes":"Seeded into the ASL store at amduatd startup; ref is advertised via /v1/meta."} +{"registry":"AMDUATD/API","contract":"AMDUATD/API/1","handle":"amduat.api.amduatd.contract.v1@1","media_type":"application/json","status":"active","bytes_sha256":"e7459c83f8473e4665ae30c3ead65385bf666618500e8d782b0b0450ae55a3e0","notes":"Seeded into the ASL store at amduatd startup; ref is advertised via /v1/meta."} diff --git a/src/amduatd.c b/src/amduatd.c index bc9dea3..5f2a4a0 100644 --- a/src/amduatd.c +++ b/src/amduatd.c @@ -9,7 +9,9 @@ #include "amduat/enc/asl1_core_codec.h" #include "amduat/enc/pel1_result.h" #include "amduat/enc/pel_program_dag.h" +#include "amduat/enc/tgk1_edge.h" #include "amduat/format/pel.h" +#include "amduat/tgk/core.h" #include "amduat/pel/program_dag.h" #include "amduat/pel/program_dag_desc.h" #include "amduat/pel/opreg_kernel.h" @@ -25,6 +27,7 @@ #include #include #include +#include #include #include @@ -34,15 +37,86 @@ static const char *const AMDUATD_DEFAULT_ROOT = ".amduat-asl"; static const char *const AMDUATD_DEFAULT_SOCK = "amduatd.sock"; +static const char *const AMDUATD_EDGES_FILE = ".amduatd.edges"; + +static const uint32_t AMDUATD_TGK_EDGE_TYPE = 0u; + +typedef struct { + amduat_reference_t *refs; + size_t len; + size_t cap; +} amduatd_ref_list_t; + +static void amduatd_ref_list_free(amduatd_ref_list_t *list) { + size_t i; + if (list == NULL) { + return; + } + for (i = 0; i < list->len; ++i) { + amduat_reference_free(&list->refs[i]); + } + free(list->refs); + list->refs = NULL; + list->len = 0; + list->cap = 0; +} + +static bool amduatd_ref_list_push(amduatd_ref_list_t *list, + amduat_reference_t ref) { + amduat_reference_t cloned; + amduat_reference_t *next; + + if (list == NULL) { + return false; + } + memset(&cloned, 0, sizeof(cloned)); + if (!amduat_reference_clone(ref, &cloned)) { + return false; + } + if (list->len == list->cap) { + size_t next_cap = list->cap != 0 ? list->cap * 2u : 64u; + next = (amduat_reference_t *)realloc(list->refs, + next_cap * sizeof(*list->refs)); + if (next == NULL) { + amduat_reference_free(&cloned); + return false; + } + list->refs = next; + list->cap = next_cap; + } + list->refs[list->len++] = cloned; + return true; +} + +typedef struct { + const char *root_path; + char edges_path[1024]; + amduat_reference_t rel_aliases_ref; + amduat_reference_t rel_materializes_ref; + amduatd_ref_list_t edge_refs; +} amduatd_concepts_t; + +static void amduatd_concepts_free(amduatd_concepts_t *c) { + if (c == NULL) { + return; + } + amduat_reference_free(&c->rel_aliases_ref); + amduat_reference_free(&c->rel_materializes_ref); + amduatd_ref_list_free(&c->edge_refs); +} static const char k_amduatd_contract_v1_json[] = "{" "\"contract\":\"AMDUATD/API/1\"," "\"base_path\":\"/v1\"," "\"endpoints\":[" + "{\"method\":\"GET\",\"path\":\"/v1/ui\"}," "{\"method\":\"GET\",\"path\":\"/v1/meta\"}," "{\"method\":\"HEAD\",\"path\":\"/v1/meta\"}," "{\"method\":\"GET\",\"path\":\"/v1/contract\"}," + "{\"method\":\"POST\",\"path\":\"/v1/concepts\"}," + "{\"method\":\"POST\",\"path\":\"/v1/concepts/{name}/publish\"}," + "{\"method\":\"GET\",\"path\":\"/v1/resolve/{name}\"}," "{\"method\":\"POST\",\"path\":\"/v1/artifacts\"}," "{\"method\":\"GET\",\"path\":\"/v1/artifacts/{ref}\"}," "{\"method\":\"HEAD\",\"path\":\"/v1/artifacts/{ref}\"}," @@ -54,9 +128,9 @@ static const char k_amduatd_contract_v1_json[] = "\"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\"}," + "\"program_ref\":{\"type\":\"string\",\"description\":\"hex ref or concept name\"}," + "\"input_refs\":{\"type\":\"array\",\"items\":{\"type\":\"string\",\"description\":\"hex ref or concept name\"}}," + "\"params_ref\":{\"type\":\"string\",\"description\":\"hex ref or concept name\"}," "\"scheme_ref\":{\"type\":\"string\",\"description\":\"hex ref or 'dag'\"}" "}" "}," @@ -148,6 +222,13 @@ static const char k_amduatd_ui_html[] = " \" />\n" "
scheme_ref (optional, default dag)
\n" " \n" + "
Concept
\n" + "
name (lowercase, [a-z0-9_./-])
\n" + " \n" + "
\n" + " \n" + " \n" + "
\n" "
\n" " \n" " /v1/contract\n" @@ -197,6 +278,35 @@ static const char k_amduatd_ui_html[] = " }\n" " });\n" "\n" + " el('btnConceptCreate').addEventListener('click', async () => {\n" + " try {\n" + " const name = el('conceptName').value.trim();\n" + " const resp = await fetch('/v1/concepts', {\n" + " method: 'POST',\n" + " headers: { 'Content-Type': 'application/json' },\n" + " body: JSON.stringify({ name })\n" + " });\n" + " out(await resp.text());\n" + " } catch (e) {\n" + " out(String(e));\n" + " }\n" + " });\n" + "\n" + " el('btnConceptPublish').addEventListener('click', async () => {\n" + " try {\n" + " const name = el('conceptName').value.trim();\n" + " const ref = el('programRef').value.trim();\n" + " const resp = await fetch(`/v1/concepts/${encodeURIComponent(name)}/publish`, {\n" + " method: 'POST',\n" + " headers: { 'Content-Type': 'application/json' },\n" + " body: JSON.stringify({ ref })\n" + " });\n" + " out(await resp.text());\n" + " } catch (e) {\n" + " out(String(e));\n" + " }\n" + " });\n" + "\n" " el('btnRun').addEventListener('click', async () => {\n" " try {\n" " const program_ref = el('programRef').value.trim();\n" @@ -247,6 +357,522 @@ static bool amduatd_write_all(int fd, const uint8_t *buf, size_t len) { return true; } +static bool amduatd_read_exact(int fd, uint8_t *buf, size_t len); + +static bool amduatd_read_file(const char *path, uint8_t **out, size_t *out_len) { + uint8_t *buf = NULL; + size_t cap = 0; + size_t len = 0; + FILE *f; + + if (out != NULL) { + *out = NULL; + } + if (out_len != NULL) { + *out_len = 0; + } + if (path == NULL || out == NULL || out_len == NULL) { + return false; + } + f = fopen(path, "rb"); + if (f == NULL) { + return false; + } + for (;;) { + size_t n; + if (len == cap) { + size_t next_cap = cap != 0 ? cap * 2u : 4096u; + uint8_t *next = (uint8_t *)realloc(buf, next_cap); + if (next == NULL) { + free(buf); + fclose(f); + return false; + } + buf = next; + cap = next_cap; + } + n = fread(buf + len, 1, cap - len, f); + len += n; + if (n == 0) { + if (ferror(f)) { + free(buf); + fclose(f); + return false; + } + break; + } + } + fclose(f); + *out = buf; + *out_len = len; + return true; +} + +static bool amduatd_read_urandom(uint8_t *out, size_t len) { + int fd; + if (out == NULL || len == 0) { + return false; + } + fd = open("/dev/urandom", O_RDONLY); + if (fd < 0) { + return false; + } + if (!amduatd_read_exact(fd, out, len)) { + close(fd); + return false; + } + close(fd); + return true; +} + +static bool amduatd_name_valid(const char *name) { + size_t i; + size_t len; + if (name == NULL) { + return false; + } + len = strlen(name); + if (len == 0 || len > 64) { + return false; + } + for (i = 0; i < len; ++i) { + unsigned char c = (unsigned char)name[i]; + if ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '_' || + c == '-' || c == '.' || c == '/') { + continue; + } + return false; + } + return true; +} + +static bool amduatd_build_prefixed_bytes(const char *prefix, + const char *text, + uint8_t **out, + size_t *out_len) { + size_t p_len; + size_t t_len; + size_t len; + uint8_t *buf; + + if (out != NULL) { + *out = NULL; + } + if (out_len != NULL) { + *out_len = 0; + } + if (prefix == NULL || text == NULL || out == NULL || out_len == NULL) { + return false; + } + p_len = strlen(prefix); + t_len = strlen(text); + if (p_len > SIZE_MAX - 1u || t_len > SIZE_MAX - (p_len + 1u)) { + return false; + } + len = p_len + 1u + t_len; + buf = (uint8_t *)malloc(len); + if (buf == NULL) { + return false; + } + memcpy(buf, prefix, p_len); + buf[p_len] = 0; + memcpy(buf + p_len + 1u, text, t_len); + *out = buf; + *out_len = len; + return true; +} + +static bool amduatd_concepts_seed_relation(amduat_asl_store_t *store, + const amduat_asl_store_fs_config_t *cfg, + const char *relation_name, + amduat_reference_t *out_ref) { + uint8_t *bytes = NULL; + size_t bytes_len = 0; + amduat_artifact_t artifact; + + if (out_ref == NULL) { + return false; + } + *out_ref = amduat_reference(0u, amduat_octets(NULL, 0u)); + if (store == NULL || cfg == NULL || relation_name == NULL) { + return false; + } + if (!amduatd_build_prefixed_bytes("AMDUATD/RELATION/1", relation_name, &bytes, + &bytes_len)) { + return false; + } + artifact = amduat_artifact(amduat_octets(bytes, bytes_len)); + if (!amduat_asl_ref_derive(artifact, + cfg->config.encoding_profile_id, + cfg->config.hash_id, + out_ref, + NULL)) { + free(bytes); + return false; + } + (void)amduat_asl_store_put(store, artifact, out_ref); + free(bytes); + return true; +} + +static bool amduatd_concepts_init(amduatd_concepts_t *c, + amduat_asl_store_t *store, + const amduat_asl_store_fs_config_t *cfg, + const char *root_path) { + uint8_t *file_bytes = NULL; + size_t file_len = 0; + size_t i; + + if (c == NULL || store == NULL || cfg == NULL || root_path == NULL) { + return false; + } + memset(c, 0, sizeof(*c)); + c->root_path = root_path; + (void)snprintf(c->edges_path, sizeof(c->edges_path), "%s/%s", root_path, + AMDUATD_EDGES_FILE); + + if (!amduatd_concepts_seed_relation(store, cfg, "aliases", + &c->rel_aliases_ref)) { + return false; + } + if (!amduatd_concepts_seed_relation(store, cfg, "materializesAs", + &c->rel_materializes_ref)) { + return false; + } + + if (!amduatd_read_file(c->edges_path, &file_bytes, &file_len)) { + return true; + } + + { + char *text = (char *)file_bytes; + char *line = text; + char *end = text + file_len; + while (line < end) { + char *nl = memchr(line, '\n', (size_t)(end - line)); + size_t n = nl != NULL ? (size_t)(nl - line) : (size_t)(end - line); + char *tmp; + amduat_reference_t ref; + bool ok; + + while (n != 0 && (line[n - 1] == '\r' || line[n - 1] == ' ' || + line[n - 1] == '\t')) { + n--; + } + while (n != 0 && (*line == ' ' || *line == '\t')) { + line++; + n--; + } + if (n != 0) { + tmp = (char *)malloc(n + 1u); + if (tmp == NULL) { + free(file_bytes); + return false; + } + memcpy(tmp, line, n); + tmp[n] = '\0'; + memset(&ref, 0, sizeof(ref)); + ok = amduat_asl_ref_decode_hex(tmp, &ref); + free(tmp); + if (ok) { + (void)amduatd_ref_list_push(&c->edge_refs, ref); + amduat_reference_free(&ref); + } + } + if (nl == NULL) { + break; + } + line = nl + 1; + } + } + + free(file_bytes); + + for (i = 0; i < c->edge_refs.len; ++i) { + if (c->edge_refs.refs[i].hash_id == 0 || + c->edge_refs.refs[i].digest.data == NULL || + c->edge_refs.refs[i].digest.len == 0) { + continue; + } + } + + return true; +} + +static bool amduatd_concepts_append_edge_ref(amduatd_concepts_t *c, + amduat_reference_t edge_ref) { + char *hex = NULL; + FILE *f; + bool ok; + + if (c == NULL) { + return false; + } + if (!amduat_asl_ref_encode_hex(edge_ref, &hex)) { + return false; + } + f = fopen(c->edges_path, "ab"); + if (f == NULL) { + free(hex); + return false; + } + ok = fprintf(f, "%s\n", hex) > 0; + fclose(f); + if (!ok) { + free(hex); + return false; + } + free(hex); + return amduatd_ref_list_push(&c->edge_refs, edge_ref); +} + +static bool amduatd_concepts_derive_name_ref( + const amduat_asl_store_fs_config_t *cfg, + const char *name, + amduat_reference_t *out_ref) { + uint8_t *bytes = NULL; + size_t bytes_len = 0; + amduat_artifact_t artifact; + + if (out_ref == NULL) { + return false; + } + *out_ref = amduat_reference(0u, amduat_octets(NULL, 0u)); + if (cfg == NULL || name == NULL) { + return false; + } + if (!amduatd_build_prefixed_bytes("AMDUATD/NAME/1", name, &bytes, + &bytes_len)) { + return false; + } + artifact = amduat_artifact(amduat_octets(bytes, bytes_len)); + if (!amduat_asl_ref_derive(artifact, + cfg->config.encoding_profile_id, + cfg->config.hash_id, + out_ref, + NULL)) { + free(bytes); + return false; + } + free(bytes); + return true; +} + +static bool amduatd_concepts_put_name_artifact(amduat_asl_store_t *store, + const char *name, + amduat_reference_t *out_ref) { + uint8_t *bytes = NULL; + size_t bytes_len = 0; + amduat_artifact_t artifact; + + if (out_ref != NULL) { + *out_ref = amduat_reference(0u, amduat_octets(NULL, 0u)); + } + if (store == NULL || name == NULL || out_ref == NULL) { + return false; + } + if (!amduatd_build_prefixed_bytes("AMDUATD/NAME/1", name, &bytes, + &bytes_len)) { + return false; + } + artifact = amduat_artifact(amduat_octets(bytes, bytes_len)); + if (amduat_asl_store_put(store, artifact, out_ref) != AMDUAT_ASL_STORE_OK) { + free(bytes); + return false; + } + free(bytes); + return true; +} + +static bool amduatd_concepts_put_concept_id(amduat_asl_store_t *store, + const amduat_asl_store_fs_config_t *cfg, + amduat_reference_t *out_ref) { + uint8_t rnd[16]; + uint8_t *bytes = NULL; + size_t bytes_len = 0; + amduat_artifact_t artifact; + + if (out_ref != NULL) { + *out_ref = amduat_reference(0u, amduat_octets(NULL, 0u)); + } + if (store == NULL || cfg == NULL || out_ref == NULL) { + return false; + } + if (!amduatd_read_urandom(rnd, sizeof(rnd))) { + return false; + } + bytes_len = strlen("AMDUATD/CONCEPT-ID/1") + 1u + sizeof(rnd); + bytes = (uint8_t *)malloc(bytes_len); + if (bytes == NULL) { + return false; + } + memcpy(bytes, "AMDUATD/CONCEPT-ID/1", strlen("AMDUATD/CONCEPT-ID/1")); + bytes[strlen("AMDUATD/CONCEPT-ID/1")] = 0; + memcpy(bytes + strlen("AMDUATD/CONCEPT-ID/1") + 1u, rnd, sizeof(rnd)); + artifact = amduat_artifact(amduat_octets(bytes, bytes_len)); + if (!amduat_asl_ref_derive(artifact, + cfg->config.encoding_profile_id, + cfg->config.hash_id, + out_ref, + NULL)) { + free(bytes); + return false; + } + if (amduat_asl_store_put(store, artifact, out_ref) != AMDUAT_ASL_STORE_OK) { + free(bytes); + return false; + } + free(bytes); + return true; +} + +static bool amduatd_concepts_put_edge(amduat_asl_store_t *store, + amduatd_concepts_t *c, + amduat_reference_t from, + amduat_reference_t to, + amduat_reference_t relation_concept_ref, + amduat_reference_t *out_edge_ref) { + amduat_tgk_edge_body_t edge; + amduat_reference_t from_arr[1]; + amduat_reference_t to_arr[1]; + amduat_octets_t bytes; + amduat_artifact_t artifact; + + if (out_edge_ref != NULL) { + *out_edge_ref = amduat_reference(0u, amduat_octets(NULL, 0u)); + } + if (store == NULL || c == NULL || out_edge_ref == NULL) { + return false; + } + + memset(&edge, 0, sizeof(edge)); + edge.type = AMDUATD_TGK_EDGE_TYPE; + from_arr[0] = from; + to_arr[0] = to; + edge.from = from_arr; + edge.from_len = 1; + edge.to = to_arr; + edge.to_len = 1; + edge.payload = relation_concept_ref; + + bytes = amduat_octets(NULL, 0); + if (!amduat_enc_tgk1_edge_encode_v1(&edge, &bytes)) { + return false; + } + artifact = amduat_artifact_with_type(bytes, + amduat_type_tag(AMDUAT_TYPE_TAG_TGK1_EDGE_V1)); + if (amduat_asl_store_put(store, artifact, out_edge_ref) != AMDUAT_ASL_STORE_OK) { + free((void *)bytes.data); + return false; + } + free((void *)bytes.data); + return amduatd_concepts_append_edge_ref(c, *out_edge_ref); +} + +static bool amduatd_concepts_lookup_alias(amduat_asl_store_t *store, + const amduat_asl_store_fs_config_t *cfg, + const amduatd_concepts_t *c, + const char *name, + amduat_reference_t *out_concept_ref) { + amduat_reference_t name_ref; + size_t i; + + if (out_concept_ref != NULL) { + *out_concept_ref = amduat_reference(0u, amduat_octets(NULL, 0u)); + } + if (store == NULL || cfg == NULL || c == NULL || name == NULL || + out_concept_ref == NULL) { + return false; + } + if (!amduatd_concepts_derive_name_ref(cfg, name, &name_ref)) { + return false; + } + + for (i = c->edge_refs.len; i > 0; --i) { + amduat_artifact_t artifact; + amduat_tgk_edge_body_t edge; + amduat_asl_store_error_t err; + + memset(&artifact, 0, sizeof(artifact)); + err = amduat_asl_store_get(store, c->edge_refs.refs[i - 1u], &artifact); + if (err != AMDUAT_ASL_STORE_OK) { + continue; + } + if (!artifact.has_type_tag || + artifact.type_tag.tag_id != AMDUAT_TYPE_TAG_TGK1_EDGE_V1) { + amduat_asl_artifact_free(&artifact); + continue; + } + memset(&edge, 0, sizeof(edge)); + if (!amduat_enc_tgk1_edge_decode_v1(artifact.bytes, &edge)) { + amduat_asl_artifact_free(&artifact); + continue; + } + if (edge.from_len == 1 && edge.to_len == 1 && + amduat_reference_eq(edge.payload, c->rel_aliases_ref) && + amduat_reference_eq(edge.from[0], name_ref)) { + amduat_reference_clone(edge.to[0], out_concept_ref); + amduat_enc_tgk1_edge_free(&edge); + amduat_asl_artifact_free(&artifact); + amduat_reference_free(&name_ref); + return true; + } + amduat_enc_tgk1_edge_free(&edge); + amduat_asl_artifact_free(&artifact); + } + + amduat_reference_free(&name_ref); + return false; +} + +static bool amduatd_concepts_resolve_latest(amduat_asl_store_t *store, + const amduatd_concepts_t *c, + amduat_reference_t concept_ref, + amduat_reference_t *out_ref) { + size_t i; + + if (out_ref != NULL) { + *out_ref = amduat_reference(0u, amduat_octets(NULL, 0u)); + } + if (store == NULL || c == NULL || out_ref == NULL) { + return false; + } + + for (i = c->edge_refs.len; i > 0; --i) { + amduat_artifact_t artifact; + amduat_tgk_edge_body_t edge; + amduat_asl_store_error_t err; + + memset(&artifact, 0, sizeof(artifact)); + err = amduat_asl_store_get(store, c->edge_refs.refs[i - 1u], &artifact); + if (err != AMDUAT_ASL_STORE_OK) { + continue; + } + if (!artifact.has_type_tag || + artifact.type_tag.tag_id != AMDUAT_TYPE_TAG_TGK1_EDGE_V1) { + amduat_asl_artifact_free(&artifact); + continue; + } + memset(&edge, 0, sizeof(edge)); + if (!amduat_enc_tgk1_edge_decode_v1(artifact.bytes, &edge)) { + amduat_asl_artifact_free(&artifact); + continue; + } + if (edge.from_len == 1 && edge.to_len == 1 && + amduat_reference_eq(edge.payload, c->rel_materializes_ref) && + amduat_reference_eq(edge.from[0], concept_ref)) { + amduat_reference_clone(edge.to[0], out_ref); + amduat_enc_tgk1_edge_free(&edge); + amduat_asl_artifact_free(&artifact); + return true; + } + amduat_enc_tgk1_edge_free(&edge); + amduat_asl_artifact_free(&artifact); + } + + return false; +} + static bool amduatd_read_exact(int fd, uint8_t *buf, size_t len) { size_t off = 0; while (off < len) { @@ -941,6 +1567,57 @@ static bool amduatd_decode_ref_hex_str(const char *s, return ok; } +typedef enum { + AMDUATD_REF_OK = 0, + AMDUATD_REF_ERR_INVALID = 1, + AMDUATD_REF_ERR_NOT_FOUND = 2, + AMDUATD_REF_ERR_INTERNAL = 3 +} amduatd_ref_status_t; + +static amduatd_ref_status_t amduatd_decode_ref_or_name_latest( + amduat_asl_store_t *store, + const amduat_asl_store_fs_config_t *cfg, + const amduatd_concepts_t *concepts, + const char *s, + size_t len, + amduat_reference_t *out_ref) { + amduat_reference_t concept_ref; + char *tmp = NULL; + + if (out_ref != NULL) { + memset(out_ref, 0, sizeof(*out_ref)); + } + if (store == NULL || cfg == NULL || concepts == NULL || s == NULL || + out_ref == NULL) { + return AMDUATD_REF_ERR_INTERNAL; + } + + if (amduatd_decode_ref_hex_str(s, len, out_ref)) { + return AMDUATD_REF_OK; + } + + if (!amduatd_copy_json_str(s, len, &tmp)) { + return AMDUATD_REF_ERR_INTERNAL; + } + if (!amduatd_name_valid(tmp)) { + free(tmp); + return AMDUATD_REF_ERR_INVALID; + } + memset(&concept_ref, 0, sizeof(concept_ref)); + if (!amduatd_concepts_lookup_alias(store, cfg, concepts, tmp, &concept_ref)) { + free(tmp); + return AMDUATD_REF_ERR_NOT_FOUND; + } + free(tmp); + + if (!amduatd_concepts_resolve_latest(store, concepts, concept_ref, out_ref)) { + amduat_reference_free(&concept_ref); + return AMDUATD_REF_ERR_NOT_FOUND; + } + amduat_reference_free(&concept_ref); + return AMDUATD_REF_OK; +} + static bool amduatd_send_json_error(int fd, int code, const char *reason, @@ -1366,6 +2043,8 @@ static bool amduatd_handle_post_artifacts(int fd, static bool amduatd_handle_post_pel_run(int fd, amduat_asl_store_t *store, + const amduat_asl_store_fs_config_t *cfg, + const amduatd_concepts_t *concepts, const amduatd_http_req_t *req) { uint8_t *body = NULL; const char *p = NULL; @@ -1394,6 +2073,10 @@ static bool amduatd_handle_post_pel_run(int fd, return amduatd_send_json_error(fd, 500, "Internal Server Error", "internal error"); } + if (cfg == NULL || concepts == NULL) { + return amduatd_send_json_error(fd, 500, "Internal Server Error", + "internal error"); + } if (req->content_length > (1u * 1024u * 1024u)) { return amduatd_send_json_error(fd, 413, "Payload Too Large", @@ -1448,13 +2131,25 @@ static bool amduatd_handle_post_pel_run(int fd, if (key_len == strlen("program_ref") && memcmp(key, "program_ref", key_len) == 0) { + amduatd_ref_status_t st; if (have_program_ref) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "duplicate program_ref"); goto pel_run_cleanup; } - if (!amduatd_json_parse_string_noesc(&p, end, &sv, &sv_len) || - !amduatd_decode_ref_hex_str(sv, sv_len, &program_ref)) { + if (!amduatd_json_parse_string_noesc(&p, end, &sv, &sv_len)) { + ok = amduatd_send_json_error(fd, 400, "Bad Request", + "invalid program_ref"); + goto pel_run_cleanup; + } + st = amduatd_decode_ref_or_name_latest(store, cfg, concepts, sv, sv_len, + &program_ref); + if (st == AMDUATD_REF_ERR_NOT_FOUND) { + ok = amduatd_send_json_error(fd, 404, "Not Found", + "program_ref not found"); + goto pel_run_cleanup; + } + if (st != AMDUATD_REF_OK) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid program_ref"); goto pel_run_cleanup; @@ -1483,13 +2178,25 @@ static bool amduatd_handle_post_pel_run(int fd, have_scheme_ref = true; } else if (key_len == strlen("params_ref") && memcmp(key, "params_ref", key_len) == 0) { + amduatd_ref_status_t st; if (has_params_ref) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "duplicate params_ref"); goto pel_run_cleanup; } - if (!amduatd_json_parse_string_noesc(&p, end, &sv, &sv_len) || - !amduatd_decode_ref_hex_str(sv, sv_len, ¶ms_ref)) { + if (!amduatd_json_parse_string_noesc(&p, end, &sv, &sv_len)) { + ok = amduatd_send_json_error(fd, 400, "Bad Request", + "invalid params_ref"); + goto pel_run_cleanup; + } + st = amduatd_decode_ref_or_name_latest(store, cfg, concepts, sv, sv_len, + ¶ms_ref); + if (st == AMDUATD_REF_ERR_NOT_FOUND) { + ok = amduatd_send_json_error(fd, 404, "Not Found", + "params_ref not found"); + goto pel_run_cleanup; + } + if (st != AMDUATD_REF_OK) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid params_ref"); goto pel_run_cleanup; @@ -1516,12 +2223,25 @@ static bool amduatd_handle_post_pel_run(int fd, for (;;) { amduat_reference_t ref; memset(&ref, 0, sizeof(ref)); - if (!amduatd_json_parse_string_noesc(&p, end, &sv, &sv_len) || - !amduatd_decode_ref_hex_str(sv, sv_len, &ref)) { + if (!amduatd_json_parse_string_noesc(&p, end, &sv, &sv_len)) { ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid input_ref"); goto pel_run_cleanup; } + { + amduatd_ref_status_t st = amduatd_decode_ref_or_name_latest( + store, cfg, concepts, sv, sv_len, &ref); + if (st == AMDUATD_REF_ERR_NOT_FOUND) { + ok = amduatd_send_json_error(fd, 404, "Not Found", + "input_ref not found"); + goto pel_run_cleanup; + } + if (st != AMDUATD_REF_OK) { + ok = amduatd_send_json_error(fd, 400, "Bad Request", + "invalid input_ref"); + goto pel_run_cleanup; + } + } if (input_refs_len == cap) { size_t next_cap = cap != 0 ? cap * 2u : 4u; amduat_reference_t *next; @@ -2472,10 +3192,401 @@ static bool amduatd_handle_get_ui(int fd) { false); } +static bool amduatd_handle_post_concepts(int fd, + amduat_asl_store_t *store, + const amduat_asl_store_fs_config_t *cfg, + amduatd_concepts_t *concepts, + const amduatd_http_req_t *req) { + uint8_t *body = NULL; + const char *p = NULL; + const char *end = NULL; + char *name = NULL; + bool have_name = false; + bool have_ref = false; + amduat_reference_t target_ref; + amduat_reference_t name_ref; + amduat_reference_t concept_ref; + amduat_reference_t edge_ref; + char *concept_hex = NULL; + char json[4096]; + bool ok = false; + + memset(&target_ref, 0, sizeof(target_ref)); + memset(&name_ref, 0, sizeof(name_ref)); + memset(&concept_ref, 0, sizeof(concept_ref)); + memset(&edge_ref, 0, sizeof(edge_ref)); + + if (store == NULL || cfg == NULL || concepts == NULL || req == NULL) { + return amduatd_send_json_error(fd, 500, "Internal Server Error", + "internal error"); + } + if (req->content_length == 0) { + return amduatd_send_json_error(fd, 400, "Bad Request", "missing body"); + } + if (req->content_length > (256u * 1024u)) { + return amduatd_send_json_error(fd, 413, "Payload Too Large", + "payload too large"); + } + body = (uint8_t *)malloc(req->content_length); + if (body == NULL) { + return amduatd_send_json_error(fd, 500, "Internal Server Error", "oom"); + } + if (!amduatd_read_exact(fd, body, req->content_length)) { + free(body); + return false; + } + p = (const char *)body; + end = (const char *)body + req->content_length; + if (!amduatd_json_expect(&p, end, '{')) { + ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid json"); + goto concepts_cleanup; + } + + for (;;) { + const char *key = NULL; + size_t key_len = 0; + const char *sv = NULL; + size_t sv_len = 0; + const char *cur = NULL; + + cur = amduatd_json_skip_ws(p, end); + if (cur < end && *cur == '}') { + p = cur + 1; + break; + } + if (!amduatd_json_parse_string_noesc(&p, end, &key, &key_len) || + !amduatd_json_expect(&p, end, ':')) { + ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid json"); + goto concepts_cleanup; + } + + if (key_len == strlen("name") && memcmp(key, "name", key_len) == 0) { + if (have_name || !amduatd_json_parse_string_noesc(&p, end, &sv, &sv_len) || + !amduatd_copy_json_str(sv, sv_len, &name)) { + ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid name"); + goto concepts_cleanup; + } + if (!amduatd_name_valid(name)) { + ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid name"); + goto concepts_cleanup; + } + have_name = true; + } else if (key_len == strlen("ref") && memcmp(key, "ref", key_len) == 0) { + if (have_ref || !amduatd_json_parse_string_noesc(&p, end, &sv, &sv_len) || + !amduatd_decode_ref_hex_str(sv, sv_len, &target_ref)) { + ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid ref"); + goto concepts_cleanup; + } + have_ref = true; + } else { + if (!amduatd_json_skip_value(&p, end, 0)) { + ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid json"); + goto concepts_cleanup; + } + } + + cur = amduatd_json_skip_ws(p, end); + if (cur < end && *cur == ',') { + p = cur + 1; + continue; + } + if (cur < end && *cur == '}') { + p = cur + 1; + break; + } + ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid json"); + goto concepts_cleanup; + } + + if (!have_name) { + ok = amduatd_send_json_error(fd, 400, "Bad Request", "missing name"); + goto concepts_cleanup; + } + + if (!amduatd_concepts_put_name_artifact(store, name, &name_ref)) { + ok = amduatd_send_json_error(fd, 500, "Internal Server Error", + "store error"); + goto concepts_cleanup; + } + + { + amduat_reference_t existing; + memset(&existing, 0, sizeof(existing)); + if (amduatd_concepts_lookup_alias(store, cfg, concepts, name, &existing)) { + amduat_reference_free(&existing); + ok = amduatd_send_json_error(fd, 409, "Conflict", "name exists"); + goto concepts_cleanup; + } + } + + if (!amduatd_concepts_put_concept_id(store, cfg, &concept_ref)) { + ok = amduatd_send_json_error(fd, 500, "Internal Server Error", + "store error"); + goto concepts_cleanup; + } + + if (!amduatd_concepts_put_edge(store, + concepts, + name_ref, + concept_ref, + concepts->rel_aliases_ref, + &edge_ref)) { + ok = amduatd_send_json_error(fd, 500, "Internal Server Error", + "store error"); + goto concepts_cleanup; + } + + if (have_ref) { + if (!amduatd_concepts_put_edge(store, + concepts, + concept_ref, + target_ref, + concepts->rel_materializes_ref, + &edge_ref)) { + ok = amduatd_send_json_error(fd, 500, "Internal Server Error", + "store error"); + goto concepts_cleanup; + } + } + + if (!amduat_asl_ref_encode_hex(concept_ref, &concept_hex)) { + ok = amduatd_send_json_error(fd, 500, "Internal Server Error", + "encode error"); + goto concepts_cleanup; + } + + { + int n = snprintf(json, + sizeof(json), + "{" + "\"name\":\"%s\"," + "\"concept_ref\":\"%s\"" + "}\n", + name, + concept_hex); + free(concept_hex); + if (n <= 0 || (size_t)n >= sizeof(json)) { + ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "error"); + goto concepts_cleanup; + } + ok = amduatd_http_send_json(fd, 200, "OK", json, false); + } + +concepts_cleanup: + free(body); + free(name); + amduat_reference_free(&target_ref); + amduat_reference_free(&name_ref); + amduat_reference_free(&concept_ref); + amduat_reference_free(&edge_ref); + return ok; +} + +static bool amduatd_path_extract_name(const char *path, + const char *prefix, + char *out, + size_t cap) { + size_t plen; + const char *p; + size_t len; + + if (out != NULL && cap != 0) { + out[0] = '\0'; + } + if (path == NULL || prefix == NULL || out == NULL || cap == 0) { + return false; + } + plen = strlen(prefix); + if (strncmp(path, prefix, plen) != 0) { + return false; + } + p = path + plen; + if (*p == '\0') { + return false; + } + len = strlen(p); + if (len >= cap) { + len = cap - 1; + } + memcpy(out, p, len); + out[len] = '\0'; + return true; +} + +static bool amduatd_handle_post_concept_publish(int fd, + amduat_asl_store_t *store, + const amduat_asl_store_fs_config_t *cfg, + amduatd_concepts_t *concepts, + const char *name, + const amduatd_http_req_t *req) { + uint8_t *body = NULL; + const char *p = NULL; + const char *end = NULL; + amduat_reference_t target_ref; + amduat_reference_t concept_ref; + amduat_reference_t edge_ref; + bool have_ref = false; + bool ok = false; + + memset(&target_ref, 0, sizeof(target_ref)); + memset(&concept_ref, 0, sizeof(concept_ref)); + memset(&edge_ref, 0, sizeof(edge_ref)); + + if (store == NULL || cfg == NULL || concepts == NULL || name == NULL || + req == NULL) { + return amduatd_send_json_error(fd, 500, "Internal Server Error", + "internal error"); + } + if (!amduatd_name_valid(name)) { + return amduatd_send_json_error(fd, 400, "Bad Request", "invalid name"); + } + if (req->content_length == 0) { + return amduatd_send_json_error(fd, 400, "Bad Request", "missing body"); + } + if (req->content_length > (256u * 1024u)) { + return amduatd_send_json_error(fd, 413, "Payload Too Large", + "payload too large"); + } + + if (!amduatd_concepts_lookup_alias(store, cfg, concepts, name, &concept_ref)) { + return amduatd_send_json_error(fd, 404, "Not Found", "unknown concept"); + } + + body = (uint8_t *)malloc(req->content_length); + if (body == NULL) { + ok = amduatd_send_json_error(fd, 500, "Internal Server Error", "oom"); + goto publish_cleanup; + } + if (!amduatd_read_exact(fd, body, req->content_length)) { + free(body); + amduat_reference_free(&concept_ref); + return false; + } + + p = (const char *)body; + end = (const char *)body + req->content_length; + if (!amduatd_json_expect(&p, end, '{')) { + ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid json"); + goto publish_cleanup; + } + for (;;) { + const char *key = NULL; + size_t key_len = 0; + const char *sv = NULL; + size_t sv_len = 0; + const char *cur = NULL; + + cur = amduatd_json_skip_ws(p, end); + if (cur < end && *cur == '}') { + p = cur + 1; + break; + } + if (!amduatd_json_parse_string_noesc(&p, end, &key, &key_len) || + !amduatd_json_expect(&p, end, ':')) { + ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid json"); + goto publish_cleanup; + } + if (key_len == strlen("ref") && memcmp(key, "ref", key_len) == 0) { + if (have_ref || !amduatd_json_parse_string_noesc(&p, end, &sv, &sv_len) || + !amduatd_decode_ref_hex_str(sv, sv_len, &target_ref)) { + ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid ref"); + goto publish_cleanup; + } + have_ref = true; + } else { + if (!amduatd_json_skip_value(&p, end, 0)) { + ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid json"); + goto publish_cleanup; + } + } + cur = amduatd_json_skip_ws(p, end); + if (cur < end && *cur == ',') { + p = cur + 1; + continue; + } + if (cur < end && *cur == '}') { + p = cur + 1; + break; + } + ok = amduatd_send_json_error(fd, 400, "Bad Request", "invalid json"); + goto publish_cleanup; + } + if (!have_ref) { + ok = amduatd_send_json_error(fd, 400, "Bad Request", "missing ref"); + goto publish_cleanup; + } + + if (!amduatd_concepts_put_edge(store, + concepts, + concept_ref, + target_ref, + concepts->rel_materializes_ref, + &edge_ref)) { + ok = amduatd_send_json_error(fd, 500, "Internal Server Error", + "store error"); + goto publish_cleanup; + } + + ok = amduatd_http_send_json(fd, 200, "OK", "{\"ok\":true}\n", false); + +publish_cleanup: + free(body); + amduat_reference_free(&target_ref); + amduat_reference_free(&concept_ref); + amduat_reference_free(&edge_ref); + return ok; +} + +static bool amduatd_handle_get_resolve(int fd, + amduat_asl_store_t *store, + const amduat_asl_store_fs_config_t *cfg, + const amduatd_concepts_t *concepts, + const char *name) { + amduat_reference_t concept_ref; + amduat_reference_t latest_ref; + char *hex = NULL; + char json[2048]; + int n; + + memset(&concept_ref, 0, sizeof(concept_ref)); + memset(&latest_ref, 0, sizeof(latest_ref)); + + if (store == NULL || cfg == NULL || concepts == NULL || name == NULL) { + return amduatd_send_json_error(fd, 500, "Internal Server Error", + "internal error"); + } + if (!amduatd_name_valid(name)) { + return amduatd_send_json_error(fd, 400, "Bad Request", "invalid name"); + } + if (!amduatd_concepts_lookup_alias(store, cfg, concepts, name, &concept_ref)) { + return amduatd_send_json_error(fd, 404, "Not Found", "unknown concept"); + } + if (!amduatd_concepts_resolve_latest(store, concepts, concept_ref, + &latest_ref)) { + amduat_reference_free(&concept_ref); + return amduatd_send_json_error(fd, 404, "Not Found", "no versions"); + } + amduat_reference_free(&concept_ref); + + if (!amduat_asl_ref_encode_hex(latest_ref, &hex)) { + amduat_reference_free(&latest_ref); + return amduatd_send_json_error(fd, 500, "Internal Server Error", + "encode error"); + } + amduat_reference_free(&latest_ref); + n = snprintf(json, sizeof(json), "{\"ref\":\"%s\"}\n", hex); + free(hex); + if (n <= 0 || (size_t)n >= sizeof(json)) { + return amduatd_send_json_error(fd, 500, "Internal Server Error", "error"); + } + return amduatd_http_send_json(fd, 200, "OK", json, false); +} + static bool amduatd_handle_conn(int fd, amduat_asl_store_t *store, const amduat_asl_store_fs_config_t *cfg, - amduat_reference_t api_contract_ref) { + amduat_reference_t api_contract_ref, + amduatd_concepts_t *concepts) { amduatd_http_req_t req; char no_query[1024]; @@ -2503,12 +3614,39 @@ static bool amduatd_handle_conn(int fd, return amduatd_handle_post_artifacts(fd, store, &req); } if (strcmp(req.method, "POST") == 0 && strcmp(no_query, "/v1/pel/run") == 0) { - return amduatd_handle_post_pel_run(fd, store, &req); + return amduatd_handle_post_pel_run(fd, store, cfg, concepts, &req); } if (strcmp(req.method, "POST") == 0 && strcmp(no_query, "/v1/pel/programs") == 0) { return amduatd_handle_post_pel_programs(fd, store, &req); } + if (strcmp(req.method, "POST") == 0 && strcmp(no_query, "/v1/concepts") == 0) { + return amduatd_handle_post_concepts(fd, store, cfg, concepts, &req); + } + if (strcmp(req.method, "POST") == 0 && + strncmp(no_query, "/v1/concepts/", 13) == 0 && + strstr(no_query, "/publish") != NULL) { + char name[256]; + char *slash; + if (!amduatd_path_extract_name(no_query, "/v1/concepts/", name, + sizeof(name))) { + return amduatd_send_json_error(fd, 400, "Bad Request", "invalid path"); + } + slash = strstr(name, "/publish"); + if (slash == NULL || strcmp(slash, "/publish") != 0) { + return amduatd_send_json_error(fd, 400, "Bad Request", "invalid path"); + } + *slash = '\0'; + return amduatd_handle_post_concept_publish(fd, store, cfg, concepts, name, + &req); + } + if (strcmp(req.method, "GET") == 0 && strncmp(no_query, "/v1/resolve/", 12) == 0) { + const char *name = no_query + 12; + if (name[0] == '\0') { + return amduatd_send_json_error(fd, 400, "Bad Request", "missing name"); + } + return amduatd_handle_get_resolve(fd, store, cfg, concepts, name); + } if (strcmp(req.method, "GET") == 0 && strncmp(no_query, "/v1/artifacts/", 14) == 0) { return amduatd_handle_get_artifact(fd, store, &req, req.path, false); @@ -2540,10 +3678,12 @@ int main(int argc, char **argv) { amduat_asl_store_fs_t fs; amduat_asl_store_t store; amduat_reference_t api_contract_ref; + amduatd_concepts_t concepts; int i; int sfd = -1; memset(&api_contract_ref, 0, sizeof(api_contract_ref)); + memset(&concepts, 0, sizeof(concepts)); for (i = 1; i < argc; ++i) { if (strcmp(argv[i], "--root") == 0) { @@ -2584,6 +3724,10 @@ int main(int argc, char **argv) { fprintf(stderr, "error: failed to seed api contract\n"); return 8; } + if (!amduatd_concepts_init(&concepts, &store, &cfg, root)) { + fprintf(stderr, "error: failed to init concept edges\n"); + return 8; + } signal(SIGINT, amduatd_on_signal); signal(SIGTERM, amduatd_on_signal); @@ -2634,11 +3778,12 @@ int main(int argc, char **argv) { break; } - (void)amduatd_handle_conn(cfd, &store, &cfg, api_contract_ref); + (void)amduatd_handle_conn(cfd, &store, &cfg, api_contract_ref, &concepts); (void)close(cfd); } amduat_reference_free(&api_contract_ref); + amduatd_concepts_free(&concepts); (void)unlink(sock_path); (void)close(sfd); return 0; diff --git a/tier1/ms.md b/tier1/ms.md new file mode 100644 index 0000000..279624b --- /dev/null +++ b/tier1/ms.md @@ -0,0 +1,505 @@ +# AMDUAT-MS/1 — Mapping Surface Specification + +Status: Draft +Owner: Architecture +Version: 0.2.2 +SoT: Yes +Last Updated: 2025-11-30 +Linked Phase Pack: PH07 +Tags: [composition, execution, deterministic] + +identity_authority: amduat.programme +lineage_id: L-PENDING-MS1 +doc_code: AMDUAT-MS/1 +code_status: tentative +doc_code_aliases: [] + +location: /amduat/tier1/ms.md +surface: developer +internal_revision: 3 +provenance_mode: full + +--- + +## Overview + +**AMDUAT-MS/1** standardises the executable mapping surface that turns a +**Concept** plus a **Context Frame** into deterministic **Data** bytes. The +surface orchestrates **FPS/1 primitives** through **FCS/1 recipes** and records +runs through **FER/1**; certification, provenance, and policy decisions remain +with **FCT/1** and phase evidence packs. MS/1 governs observable mapping +behaviour and context binding rules without authorising new governance or +relation taxonomies. + +--- + +## Core Model + +MS/1 retains Amduat's two primitive node kinds and treats all other structure as +concept-typed relations. + +* **Concept node (C):** Abstract identifier that can describe, type, or govern + any graph element. A concept can own multiple materialisations without losing + identity. +* **Data node (D):** Immutable byte sequence addressed by CID (SHA-256). All + executions produce Data nodes. +* **Relation instances:** Every edge is annotated with a `relation_concept` that + identifies its semantics. Implementations MUST NOT embed a fixed relation + enumeration; relation concepts are first-class concepts. + +Non-normative relation concept examples include `represents` (C→D), +`materializesAs` (C→D), `requiresKey` (C→C), `withinDomain` (C→C), `computedBy` +(D→C), and `hasProvenance` (D→D). Ecosystems MAY register additional concepts and +MUST treat each registered concept as a normal graph node. + +--- + +## Context Frames + +A **Context Frame (CF)** is a deterministic multimap of `{ key_concept → value +}` that constrains execution. + +* **Keys** are concept identifiers; strings are optional. +* **Values** are canonical scalars (integers, enums keyed by concept ID) or CIDs + to Data nodes for larger payloads. +* **Context CID:** `CID_context = sha256(canonical_encode(CF))`. The canonical + encoding MUST order keys by concept ID and normalise values so identical frames + generate identical CIDs. + +### Scoping and Refinement + +* Frames form a tree during pipeline execution. Each mapping step receives one + frame. +* Child frames inherit bindings from the parent. A child MAY add new bindings or + narrow an existing binding but MUST NOT contradict established bindings. +* Branch communication is explicit. Publishing a refinement to siblings requires + constructing a new **published frame** and referencing it; no sideways state is + implicit. + +### Gaps and Ambiguity + +* Missing required bindings yield a **Gap Artifact** (Data) that records the + missing keys and the decision point. No payload bytes are emitted. +* When multiple admissible bindings remain, the step returns an **Ambiguity + Artifact** (Data) enumerating admissible alternatives and the rule needed to + disambiguate. Resolution demands a refined frame. + +Gap and Ambiguity artifacts are ordinary Data nodes with CIDs and MAY be audited +like any other output. + +--- + +## Mapping Surface Semantics + +The surface defines the total function: + +``` +MS_map : (Concept C, ContextFrame CF) -> Result +Result = Produced(Data bytes) | Gap(Data) | Ambiguity(Data) +``` + +### Executable Form + +* Every mapping MUST be realised as an **FCS/1 recipe** parameterised by **PCB1** + blocks. +* Recipes MAY only invoke **FPS/1 primitives** (`put`, `get`, `slice`, + `concatenate`, `reverse`, `splice`) and compositions thereof. + +### Determinism and Replayability + +For fixed `(C, CF)` inputs and fixed referenced Data CIDs, implementations MUST +produce identical bytes. + +Each run MUST emit a **FER/1 run record** (Data) that declares: + +* the recipe concept ID (FCS/1), +* the PCB1 parameter block (as Data CID), +* all input CIDs (including `CID_context`), +* the output CID, and +* relevant environment hashes (e.g., FPS/1 library surfaces) that affect + determinism. + +Replaying the FCS/1 recipe with the same inputs MUST yield the same CID. + +### Fidelity Predicates + +Each mapping declares (via relation concepts) an **equivalence predicate** that +states when the produced bytes faithfully represent the target concept. Fidelity +predicates are themselves concepts and MAY be certified through **FCT/1**. + +### Domain Keys + +Disambiguation relies on **Domain concepts** expressed as context keys (e.g., +`withinDomain`). Recipes MAY require domain bindings such as classical vs. +quantum state spaces to guarantee deterministic interpretation. + +### Function Interface Patterns + +Implementations SHOULD expose a narrow callable surface so concept inputs and +Determinism guarantees remain auditable. + +1. **Primary signature:** + * `Result ms_map(concept_id, context_frame)` is the canonical entry point. + * `concept_id` MUST be a stable concept node reference (e.g., CID or registry + handle). Implementations MUST NOT infer the target concept from global + mutable state. + * `context_frame` MUST be the complete set of bindings used for the run. Any + binding derived from other inputs MUST be reflected back into the frame + before execution. +2. **Auxiliary parameters:** + * Additional parameters are only permitted when they can be canonicalised to + deterministic Data CIDs and recorded in the FER/1 run record. + * Preferred pattern: `attachments: Sequence[DataCID] = ()`. Each attachment + MUST already exist as a Data node, and the mapping MUST reference the CIDs + explicitly in the FER/1 log. + * Alternative pattern: `concept_inputs: Sequence[ConceptID] = ()` for + secondary concept handles. Each element MUST also appear in the effective + context frame (e.g., under a relation-specific key). Passing the concept ID + without updating the frame is a violation. +3. **Rejected pattern:** Opaque keyword arguments or process-level environment + variables MUST NOT influence execution. Implementations discovering such usage + MUST emit an Ambiguity artifact describing the missing context binding + instead. + +The following pseudocode illustrates a compliant wrapper: + +``` +def ms_map(concept_id: ConceptID, + context_frame: ContextFrame, + *, + attachments: Sequence[DataCID] = (), + concept_inputs: Sequence[ConceptID] = ()) -> Result: + frame = context_frame.with_bindings({ + relation_for(ci): ci for ci in concept_inputs + }) + frame = frame.with_bindings({ + relation_for(cid): cid for cid in attachments + }) + record = run_fcs_recipe(concept_id, frame, attachments) + return normalise_result(record) +``` + +`relation_for` denotes a deterministic lookup from relation concept to the key +under which the binding is stored. Implementations MAY inline more efficient +mechanisms, but the observable effect MUST match first updating the frame and +then invoking the recipe. + +### Media (MIME) Types vs. MS Context + +**MIME/media types** label already-produced Data so downstream systems can +interpret byte strings (e.g., `text/plain; charset=utf-8`, `image/png`). An +**MS context frame** captures the *pre-execution* bindings that shape which bytes +will be emitted. They relate but are not interchangeable: + +* **Where they live:** Context bindings exist before execution and are hashed + into `CID_context`. MIME labels are attached after Data exists (e.g., CIL + payload metadata, HTTP headers, or relation edges such as + `ms.produces_media_type`). +* **What they encode:** Context keys describe decision levers (encoding, domain, + fidelity policy) that an FCS recipe uses to deterministically produce bytes. + MIME types describe how consumers should parse already-materialised bytes. A + context MAY carry a desired media-type concept ("emit this as + `application/pdf`") but it is still a binding that constrains execution, not a + replacement for Content-Type headers. +* **Interoperability:** When MS outputs feed MIME-governed ecosystems, recipes + SHOULD register a concept such as `ms.media_type` and declare it via + `ms.requires_key` if the choice affects determinism (e.g., choosing between + `text/csv` vs. `application/json`). FER/1 receipts then bind the produced Data + both to the governing context frame and to a media-type relation so downstream + MIME routers, storage overlays, and catalogues reconcile the two perspectives. + +MIME types are one example of **interpretation contracts** that MS contexts can +align with. The same pattern applies to CAD kernels with STEP schemas, audio +encoders with bitstream levels, or ML artefacts with `model.format` +descriptors. MS/1 keeps them deterministic by requiring the selection to be a +context binding (recorded before execution) while also allowing publication +surfaces to mirror the choice via their native metadata channels. + +--- + +## Pipelines and Composition + +A **Pipeline** is a concept that composes ordered mapping steps. + +* Each step executes `MS_map(C_i, CF_i)`. +* Pipelines return either Produced(Data) or an Artifact (Gap/Ambiguity). +* Given identical inputs, pipelines MUST be byte-stable. +* **Branch isolation:** each branch operates on a forked frame. Publishing + updates requires emitting a new frame with a fresh `CID_context`, preserving + immutability. + +--- + +## Conformance Criteria + +Implementations conform to MS/1 when they satisfy all of the following: + +1. Accept `(C, CF)` and emit either Produced(Data) or a Gap/Ambiguity artifact. +2. Produce outputs reproducibly via FCS/1 + FPS/1, and record each run via FER/1. +3. Encode mutable decisions through concept-typed context keys only; no hidden + flags. +4. Address all bytes by SHA-256 CIDs and guarantee identical inputs replay to + identical CIDs. +5. Treat relation types as concepts without hard-coded enumerations. + +## Dependencies (pinned drafts) + +MS/1 relies on upstream drafts; consumers MUST treat these versions as pinned +for this specification and handle later changes as upstream drift: + +* FCS/1 v0.2.1 (Draft) +* FPS/1 v0.4.3 (Draft) +* FLS/1 v0.1.0 (Draft) + +Any adoption in PH10+ MUST re-pin consciously if these upstream specs change. + +## Evidence (PH08 runtime) + +PH08 reference executions demonstrate MS/1 behaviour; cite these when asserting +readiness or approval: + +* `logs/ph08/evidence/ms/PH08-EV-MS-RUNTIME-001/` +* `logs/ph08/evidence/ms/PH08-EV-MS-ACCEPT-001/` +* `logs/ph08/evidence/ms/PH08-EV-MS-BIND-001/` +* `logs/ph08/evidence/ms/PH08-EV-MS-CORE-001/` +* `logs/ph08/evidence/ms/PH08-EV-MS-GATES-001/` +* `logs/ph08/evidence/ms/PH08-EV-MS-LADDER-001/` +* `logs/ph08/evidence/ms/PH08-EV-MS-PIPE-001/` +* `logs/ph08/evidence/ms/PH08-EV-MS-ML-EVAL-001/` + +--- + +## Worked Ladder Example (Zero → Byte) + +The following non-normative ladder illustrates how MS/1 composes mappings from +abstract numbers to textual bytes. + +1. Concepts: `C_Number65`, `C_CodePoint(U+0041)`, `C_LetterA`, `C_WordA`. +2. Context frame bindings: `withinDomain → C_Unicode15`, + `C_TextEncoding → C_UTF8`. +3. Steps: + * Interpret `C_Number65` as `C_CodePoint(U+0041)` by applying a mapping rule + concept. + * Map `C_CodePoint(U+0041)` within the Unicode/UTF-8 frame to Data `0x41`. + * Map `C_WordA` under the same frame to Data `0x41`. + +Each run records its FER/1 linkage so the byte `0x41` can be replayed. + +--- + +## Context Evolution and Replay Discipline + +### Context Revisions During Pipelines + +When a branch detects missing bindings (e.g., absent `C_TextEncoding`), it MUST +emit an Ambiguity artifact detailing admissible encodings. Progress resumes by +creating a refined frame `CF' = CF ∪ { C_TextEncoding → C_UTF8 }` and re-running +only the affected branch. Publishing the refinement to siblings is explicit and +produces a new parent frame with a distinct `CID_context`. + +### Replay-First Evolution + +MS/1 guards against semantic drift when the knowledge base or registry grows: + +* **Frame immutability:** Every FER/1 record captures the `CID_context` that was + in force at execution time. New bindings (e.g., alternative encodings) produce + new frame hashes and therefore new provenance edges. Historical runs replay + byte-for-byte because their frames never mutate. +* **Required bindings:** Recipes MUST register mandatory keys via + `ms.requires_key`. When governance tightens (for example, by requiring a + `string.encoding` key), existing frames lacking the key yield deterministic + Gap/Ambiguity artifacts instead of silently adopting defaults. +* **Replay-first migration:** If an ecosystem needs the new semantics, it reruns + the recorded FCS/1 recipe with a refined frame. The new FER/1 record cites the + refined `CID_context`, making comparisons between legacy and refreshed runs + explicit and auditable. +* **Artifact parity:** Gap and Ambiguity outputs are stored as first-class Data + nodes. Audits can therefore demonstrate exactly where a richer knowledge base + demanded additional context, keeping provenance complete even when execution + paused. + +Collectively, these rules let operators expand the registry or tighten policies +without jeopardising determinism or traceability. + +--- + +## Hashing Bits and Abstract Values + +MS/1 hashes bytes, not abstract concepts. A bit becomes hashable only once it is +materialised (e.g., via `C_BitAsOctet`). When domain or packing decisions remain +unbound, the mapping MUST emit an Ambiguity or Gap artifact rather than guess. + +--- + +## Risk Controls + +MS/1 aligns with existing Amduat controls: + +* **Semantic drift:** Context keys are concepts with versioned materialisations; + frame hashes reveal mismatches. +* **Provenance loss:** Every Produced(Data) links to a FER/1 record via + `hasProvenance`. +* **Undocumented mutation:** Frames are immutable; refinements create new + contexts. +* **Rights obligations:** Licences and attribution obligations appear as context + keys referencing policy Data. Recipes MAY decline to execute when obligations + are unmet, provided the decision is deterministic. + +--- + +## Acceptance Checks + +An MS/1 implementation SHOULD ship the following self-tests: + +1. **Replay test:** identical `(C, CF)` inputs produce the same CID and FER/1 log. +2. **Gap test:** missing required keys yield deterministic Gap artifacts. +3. **Ambiguity test:** admissible alternatives are enumerated stably. +4. **Scope test:** sibling branches remain unchanged unless a published frame is + adopted. +5. **Fidelity test:** the declared equivalence predicate is machine-verifiable. + +--- + +## Registry Alignment + +MS/1 is production-registered in the CRS/1 concept registry. Implementations +MUST treat the following handles and digests as canonical when emitting or +validating graphs: + +| Symbol | Registry Handle | Kind | SHA-256 Digest | Notes | +| --- | --- | --- | --- | --- | +| `MS/1` | `crs:concept/amduat.ms.surface@1` | Concept | `d140ac54367a88fa2459e3fedf0b2fde934f9ac73568f8a159e2b0c1c1828c70` | Primary mapping surface concept anchoring `MS_map` executions. | +| `ms.produces` | `crs:concept/amduat.ms.relation.produces@1` | Relation concept | `447a9f454d78f5b2ee300fe416138a864789e133b2eb9a84e32592aa9dd47965` | Annotates `Concept → Data` edges that capture Produced(Data) bytes. | +| `ms.requires_key` | `crs:concept/amduat.ms.relation.requires_key@1` | Relation concept | `a90295a8ca3006e062a5a1d5a6220330e53ba00677736b6f4a18efcec1169f6a` | Declares mandatory context key bindings for deterministic execution. | +| `ms.within_domain` | `crs:concept/amduat.ms.relation.within_domain@1` | Relation concept | `0993dff2531dd32ea32b98925bf8a5cbc88c88ed28cd3c3575e8affc84d7fa2d` | Expresses domain refinements used to disambiguate mappings. | +| `ms.fidelity_predicate` | `crs:concept/amduat.ms.relation.fidelity_predicate@1` | Relation concept | `7e159182789d89269b743c19da58d34acc3279d86650cfef83efd4f2c210c66a` | Binds outputs to the declared fidelity predicate concept. | +| `ms.byteValue` | `crs:concept/amduat.ms.relation.byte_value@1` | Relation concept | `4c43dd3a37ae695bac476e4dc62d8d8c2abda6c555668d4d863810d0053056c3` | Records byte concepts and their literal value bindings within the ladder corpus. | +| `ms.codePoint` | `crs:concept/amduat.ms.relation.code_point@1` | Relation concept | `bb7301d2fa0c5058cbd53019625bfd38ecb59e42010b64a9f0b0ada7dc494117` | Links textual concepts to Unicode code points under registered contexts. | +| `ms.symbolSequence` | `crs:concept/amduat.ms.relation.symbol_sequence@1` | Relation concept | `115035e5dc2db7e9ab2e4255f50aee56d222b04f7b7434dbbec76620cf36aa6d` | Declares ordered relations between code points/bytes when emitting strings. | +| `ms.upperCasePolicy` | `crs:concept/amduat.ms.relation.upper_case_policy@1` | Relation concept | `6f5165648c1069178c2e8a615bd24bd02a3a367df6f8e6ac21050f42eeea484c` | Encodes casing policies that constrain textual ladders. | +| `ms.titleCasePolicy` | `crs:concept/amduat.ms.relation.title_case_policy@1` | Relation concept | `7f10f109b578b69b079fc9f284ed73c69b82434b31db0a999a872d2b47958ae5` | Encodes title-casing policies enforced during deterministic mapping. | + +The registry sidecar at `/amduat/registry/predicates.jsonl` mirrors these +entries, allowing auditors to verify digests independent of this specification. +Implementations MUST fail closed when encountering unregistered aliases for +these handles. + +--- + +## Phase 07 Cross-Stream Integration + +Phase 07 workstreams bind their certificates, receipts, facts, overlays, and +domain dossiers to MS/1 contexts through a shared TypeTag grid: + +* `/amduat/phases/ph07/notes/PH07-CIL-XMAP-001.md` (`CIL-X1`) and + `vectors/ph07/cil/PH07-CIL-XMAP-001.json` declare the authoritative + cross-stream mapping entries and TypeTag ranges that all semantic surfaces + must cite before reaching Draft Ready gates. The JSON registry is logged + under `PH07-EV-CIL-ATTEST-001` so downstream profiles can dereference the + same identifiers without ambiguity. +* `/amduat/phases/ph07/notes/PH07-CROSS-CHECK-001.md` records the harness that + enforces these bindings. The checklist stored in + `logs/ph07/evidence/cil/PH07-EV-CIL-ATTEST-001/PH07-CROSS-TV-001.md` + validates every FER/FCT/OI manifest against the refreshed XMAP IDs before the + `*-5` ledger exits (`FER-5`, `FCT-5`, `OI-5`) can advance. +* `/amduat/phases/ph07/notes/PH07-FER-SCHEMA-001.md` (ledger `FER-1`, evidence + `PH07-EV-FER-RUN-001`) pins `xmap_refs[]`, certificate anchors, and replay + bundles to the XMAP rows so every receipt explicitly declares which MS/1 + context governed execution. +* `/amduat/phases/ph07/notes/PH07-FCT-SCHEMA-001.md` (ledger `FCT-1`, evidence + `PH07-EV-FCT-FACTS-001`) introduces the `trust_spine` block that carries the + required `xmap_ref`, `receipt_refs[]`, and `anchor_certs[]`, keeping fact + acceptance policies tied to the same mapping IDs. +* `/amduat/phases/ph07/notes/PH07-OI-HARNESS-001.md` (ledger `OI-5`, evidence + `PH07-EV-OI-VIEWS-001`) proves overlay descriptors and workspace views publish + `mapping_profile.xmap_refs[]` plus TGK edge expectations aligned with + `XMAP-CIL-CUSTOM-V1`. +* `/amduat/phases/ph07/notes/PH07-DOM-HARNESS-001.md` (ledger `DOM-5`, evidence + `PH07-EV-DOM-APPS-001`) extends the same guarantees to domain pilot dossiers, + confirming their overlays, facts, and receipts cite approved TypeTags and TGK + edge sets. + +MS/1 implementations participating in PH07 MUST therefore emit the same mapping +handles and evidence references recorded in these notes so provenance can be +validated across CIL/FER/FCT/OI boundaries. + +--- + +## Phase 05 Textual Ladder Scope and Evidence + +Phase 05 extends MS/1 from abstract examples to a production ladder that binds +textual concepts to deterministic bytes. The ladder introduces: + +* A `byte` concept family with 256 child concepts (`byte/0x00` … `byte/0xFF`) + whose CRS/1 relations emit single-octet Data nodes and optionally record the + radix context via `ms.byteValue` predicates. +* UTF-8 code point concepts that sequence byte concepts through + `ms.symbolSequence` relations while asserting `ms.within_domain` bindings to + Unicode 15 and UTF-8 domain concepts. +* Casing policy concepts (e.g., `allCaps`, `titleCase`) that require the + `string.casingPolicy` context key and advertise fidelity predicates so + downstream tooling can reject ambiguous casing decisions via + `ms.upperCasePolicy` and `ms.titleCasePolicy` handles. +* Dictionary word concepts implemented as FCS/1 recipes that concatenate code + points into byte strings, emitting FER/1 receipts that cite the governing + casing policy and context frame. + +Predicate registries gain canonical handles (`ms.byteValue`, `ms.codePoint`, +`ms.symbolSequence`, `ms.produces`, `ms.requires_key`, `ms.upperCasePolicy`, +`ms.titleCasePolicy`) so tooling can resolve ladder edges without bespoke +enumerations. Missing casing bindings MUST yield Ambiguity artifacts +(`ERR_MS_POLICY_MISSING`/`ERR_MS_AMBIGUITY`); absent code points produce Gap +artifacts (`ERR_MS_GAP`); undeclared predicates raise +`ERR_MS_UNDECLARED_PREDICATE`. + +Evidence for the ladder is captured under +`/amduat/vectors/ph05/ms1-text/manifest.json` and the reserved +`/amduat/logs/ph05/evidence/ms1/` surfaces: + +* `PH05-EV-MS-CTX-001/` — CTX/1 context frames with predicate registry vectors + (domain separator `AMDUAT:CTX\0`, reject `ERR_CTX_UNKNOWN_KEY`). +* `PH05-EV-MS-LADDER-001/` — Dual-run FER/1-backed positive ladders for bytes, + code points, and dictionary outputs with SA/PA guardrails. +* `PH05-EV-MS-ERRORS-001/` — Gap/ambiguity/missing policy/undeclared predicate + receipts mapped to ADR-006. + +--- + +## Phase Alignment and Readiness + +* **PH07 (Semantic Surfaces):** MS/1 is authoritative for every PH07 semantic + surface (CIL/FER/FCT/OI) and must be cited wherever mapping semantics are + referenced. PH07 workstreams MAY extend context vocabularies or registry rows + so long as they remain MS/1-conformant; no runtime commits are expected in + this phase beyond harness stubs and governance evidence. +* **PH08 (Reference Implementation):** The reference `ms_map` runtime, parity + harnesses, and subsystem wiring reside in the `KRN-2` campaign slotted for + Phase 08. PH08 SHALL use this specification verbatim, emitting FER/1 records + and exercising the acceptance checks in §Acceptance Checks. +* **Downstream pilots (PH09+):** Reproducible ML, CI/CD, data mesh, and notebook + pilots inherit the MS/1 contract by invoking the PH08 reference surface. Those + phases MAY add domain-specific context keys or ladders, but they MUST register + them through CRS/1 and capture evidence via the surfaces reserved in + §Phase 05 Textual Ladder Scope and Evidence. + +Declaring these boundaries keeps the approval pathway clear: PH07 completes the +specification, PH08 proves the executable substrate, and later phases consume it +without reopening MS/1 fundamentals. + +--- + +## Document History + + + +* **0.1.0 (2025-11-10):** Initial draft capturing deterministic concept-to-data mapping surface. +* **0.1.1 (2025-11-11):** Add interface patterns for mapping functions and constrain auxiliary parameters. +* **0.1.2 (2025-11-12):** Formalize registry concept handles and digests for MS/1. +* **0.1.3 (2025-11-14):** Documented PH05 textual ladder scope, predicate handles, and evidence surfaces. +* **0.1.4 (2025-11-15):** Aligned MS/1 evidence references with CTX/1, ladder, and error reservations. +* **0.1.5 (2025-11-18):** Added ms.* predicate handles, CTX/1 domain separator, ADR-006 error mapping, and dual-run evidence guardrails. +* **0.1.6 (2025-11-18):** Added context-evolution guardrails and PH07→PH08 readiness boundaries. +* **0.1.7 (2025-11-19):** Clarified MIME/media-type relationship to MS context frames and execution bindings. +* **0.2.0 (2025-11-19):** Standardized metadata, headings, and evidence alignment per DOCSTD. +* **0.2.1 (2025-11-19):** Synced registry handles and documented PH07 XMAP/harness integration. +* **0.2.2 (2025-11-30):** Added DOCID header, pinned upstream draft dependencies, and referenced PH08 MS/1 evidence bundles.