Add space doctor endpoint with deterministic checks and tests

This commit is contained in:
Carl Niklas Rydberg 2026-01-24 10:59:49 +01:00
parent af11665a35
commit f3a065c8ab
8 changed files with 1563 additions and 3 deletions

View file

@ -27,7 +27,7 @@ target_link_libraries(amduat_federation
set(amduatd_sources src/amduatd.c src/amduatd_http.c src/amduatd_caps.c set(amduatd_sources src/amduatd.c src/amduatd_http.c src/amduatd_caps.c
src/amduatd_space.c src/amduatd_concepts.c src/amduatd_space.c src/amduatd_concepts.c
src/amduatd_store.c src/amduatd_derivation_index.c src/amduatd_store.c src/amduatd_derivation_index.c
src/amduatd_fed.c) src/amduatd_fed.c src/amduatd_space_doctor.c)
if(AMDUATD_ENABLE_UI) if(AMDUATD_ENABLE_UI)
list(APPEND amduatd_sources src/amduatd_ui.c) list(APPEND amduatd_sources src/amduatd_ui.c)
endif() endif()
@ -46,8 +46,8 @@ target_compile_definitions(amduatd
target_link_libraries(amduatd target_link_libraries(amduatd
PRIVATE amduat_tgk amduat_pel amduat_format amduat_asl_store_fs PRIVATE amduat_tgk amduat_pel amduat_format amduat_asl_store_fs
amduat_asl_store_index_fs amduat_asl_derivation_index_fs amduat_asl_store_index_fs amduat_asl_derivation_index_fs
amduat_asl amduat_enc amduat_hash_asl1 amduat_util amduat_asl_record amduat_asl amduat_enc amduat_hash_asl1
amduat_federation amduat_util amduat_federation
) )
add_executable(amduat_pel_gc add_executable(amduat_pel_gc
@ -136,3 +136,24 @@ target_link_libraries(amduatd_test_fed_cfg
) )
add_test(NAME amduatd_fed_cfg COMMAND amduatd_test_fed_cfg) add_test(NAME amduatd_fed_cfg COMMAND amduatd_test_fed_cfg)
add_executable(amduatd_test_space_doctor
tests/test_amduatd_space_doctor.c
src/amduatd_space_doctor.c
src/amduatd_space.c
src/amduatd_store.c
)
target_include_directories(amduatd_test_space_doctor
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/vendor/amduat/include
)
target_link_libraries(amduatd_test_space_doctor
PRIVATE amduat_asl_store_fs amduat_asl_pointer_fs amduat_asl_record
amduat_asl_store_index_fs amduat_asl amduat_enc amduat_util
amduat_hash_asl1
)
add_test(NAME amduatd_space_doctor COMMAND amduatd_test_space_doctor)

View file

@ -208,11 +208,25 @@ Precedence rules:
When capability tokens are used, the requested space must match the token's When capability tokens are used, the requested space must match the token's
space (or the token must be unscoped), otherwise the request is rejected. space (or the token must be unscoped), otherwise the request is rejected.
## Space doctor
Check deterministic invariants for the effective space:
```sh
curl --unix-socket amduatd.sock http://localhost/v1/space/doctor
curl --unix-socket amduatd.sock http://localhost/v1/space/doctor \
-H 'X-Amduat-Space: demo'
```
When the daemon uses the `fs` store backend, index-only checks are reported as
`"skipped"`; the `index` backend runs them.
## Current endpoints ## Current endpoints
- `GET /v1/meta``{store_id, encoding_profile_id, hash_id, api_contract_ref}` - `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` → contract bytes (JSON) (+ `X-Amduat-Contract-Ref` header)
- `GET /v1/contract?format=ref``{ref}` - `GET /v1/contract?format=ref``{ref}`
- `GET /v1/space/doctor` → deterministic space health checks
- `GET /v1/ui` → browser UI for authoring/running programs - `GET /v1/ui` → browser UI for authoring/running programs
- `GET /v1/fed/records?domain_id=...&from_logseq=...&limit=...``{domain_id, snapshot_id, log_prefix, next_logseq, records[]}` (published artifacts + tombstones + PER + TGK edges) - `GET /v1/fed/records?domain_id=...&from_logseq=...&limit=...``{domain_id, snapshot_id, log_prefix, next_logseq, records[]}` (published artifacts + tombstones + PER + TGK edges)
- `GET /v1/fed/artifacts/{ref}` → raw bytes for federation resolve - `GET /v1/fed/artifacts/{ref}` → raw bytes for federation resolve

View file

@ -42,6 +42,7 @@
#include "amduatd_fed.h" #include "amduatd_fed.h"
#include "amduatd_store.h" #include "amduatd_store.h"
#include "amduatd_derivation_index.h" #include "amduatd_derivation_index.h"
#include "amduatd_space_doctor.h"
#include <errno.h> #include <errno.h>
#include <signal.h> #include <signal.h>
@ -112,6 +113,7 @@ static const char k_amduatd_contract_v1_json[] =
"{\"method\":\"GET\",\"path\":\"/v1/meta\"}," "{\"method\":\"GET\",\"path\":\"/v1/meta\"},"
"{\"method\":\"HEAD\",\"path\":\"/v1/meta\"}," "{\"method\":\"HEAD\",\"path\":\"/v1/meta\"},"
"{\"method\":\"GET\",\"path\":\"/v1/contract\"}," "{\"method\":\"GET\",\"path\":\"/v1/contract\"},"
"{\"method\":\"GET\",\"path\":\"/v1/space/doctor\"},"
"{\"method\":\"POST\",\"path\":\"/v1/capabilities\"}," "{\"method\":\"POST\",\"path\":\"/v1/capabilities\"},"
"{\"method\":\"GET\",\"path\":\"/v1/cap/resolve\"}," "{\"method\":\"GET\",\"path\":\"/v1/cap/resolve\"},"
"{\"method\":\"GET\",\"path\":\"/v1/fed/records\"}," "{\"method\":\"GET\",\"path\":\"/v1/fed/records\"},"
@ -786,6 +788,67 @@ static bool amduatd_handle_get_contract(int fd,
} }
} }
static bool amduatd_handle_get_space_doctor(int fd,
amduat_asl_store_t *store,
const amduatd_http_req_t *req,
const amduatd_cfg_t *dcfg,
const amduatd_fed_cfg_t *fed_cfg,
const amduatd_caps_t *caps,
const char *root_path) {
amduat_asl_pointer_store_t pointer_store;
amduatd_space_doctor_report_t report;
char *json = NULL;
bool ok = false;
int status = 200;
if (store == NULL || req == NULL || dcfg == NULL || root_path == NULL) {
return amduatd_send_json_error(fd, 500, "Internal Server Error",
"internal error");
}
if (caps != NULL && caps->enabled && req->x_capability[0] != '\0') {
const char *reason = NULL;
if (!amduatd_caps_check_space(caps, dcfg, req, &reason)) {
if (reason != NULL && strcmp(reason, "wrong-space") == 0) {
return amduatd_send_json_error(fd, 403, "Forbidden",
"space not permitted by capability");
}
return amduatd_send_json_error(fd, 403, "Forbidden",
"invalid capability");
}
}
if (!amduat_asl_pointer_store_init(&pointer_store, root_path)) {
return amduatd_send_json_error(fd, 500, "Internal Server Error",
"pointer store error");
}
if (!amduatd_space_doctor_run(store,
&pointer_store,
req->effective_space,
dcfg,
fed_cfg,
&report)) {
return amduatd_send_json_error(fd, 500, "Internal Server Error",
"doctor failed");
}
if (!amduatd_space_doctor_report_json(&report, &json)) {
amduatd_space_doctor_report_free(&report);
return amduatd_send_json_error(fd, 500, "Internal Server Error",
"encode error");
}
if (report.fail_count != 0u) {
status = 500;
}
ok = amduatd_http_send_json(fd,
status,
status == 200 ? "OK" : "Internal Server Error",
json,
false);
free(json);
amduatd_space_doctor_report_free(&report);
return ok;
}
static bool amduatd_seed_api_contract(amduat_asl_store_t *store, static bool amduatd_seed_api_contract(amduat_asl_store_t *store,
const amduat_asl_store_fs_config_t *cfg, const amduat_asl_store_fs_config_t *cfg,
amduat_reference_t *out_ref) { amduat_reference_t *out_ref) {
@ -4015,6 +4078,17 @@ static bool amduatd_handle_conn(int fd,
ok = amduatd_handle_get_contract(fd, store, &req, api_contract_ref); ok = amduatd_handle_get_contract(fd, store, &req, api_contract_ref);
goto conn_cleanup; goto conn_cleanup;
} }
if (strcmp(req.method, "GET") == 0 &&
strcmp(no_query, "/v1/space/doctor") == 0) {
ok = amduatd_handle_get_space_doctor(fd,
store,
&req,
effective_cfg,
fed_cfg,
caps,
root_path);
goto conn_cleanup;
}
if (strcmp(req.method, "GET") == 0 && if (strcmp(req.method, "GET") == 0 &&
strcmp(no_query, "/v1/fed/records") == 0) { strcmp(no_query, "/v1/fed/records") == 0) {
ok = amduatd_handle_get_fed_records(fd, store, fed_cfg, &req); ok = amduatd_handle_get_fed_records(fd, store, fed_cfg, &req);

View file

@ -1114,6 +1114,67 @@ static bool amduatd_cap_verify_token(const amduatd_caps_t *cap,
return true; return true;
} }
bool amduatd_caps_check_space(const amduatd_caps_t *cap,
const amduatd_cfg_t *dcfg,
const amduatd_http_req_t *req,
const char **out_reason) {
const char *cap_header = NULL;
uint8_t *token_bytes = NULL;
size_t token_len = 0u;
amduatd_cap_token_t token;
const char *reason = "invalid";
bool ok = false;
if (out_reason != NULL) {
*out_reason = NULL;
}
if (cap == NULL || dcfg == NULL || req == NULL) {
if (out_reason != NULL) {
*out_reason = "internal";
}
return false;
}
if (!cap->enabled) {
return true;
}
cap_header = req->x_capability;
if (cap_header == NULL || cap_header[0] == '\0') {
return true;
}
if (strncmp(cap_header, "cap_", 4u) != 0) {
if (out_reason != NULL) {
*out_reason = "invalid";
}
return false;
}
if (!amduatd_b64url_decode(cap_header + 4u, &token_bytes, &token_len)) {
if (out_reason != NULL) {
*out_reason = "invalid";
}
return false;
}
memset(&token, 0, sizeof(token));
if (!amduatd_cap_verify_token(cap,
dcfg,
token_bytes,
token_len,
&token,
&reason)) {
if (out_reason != NULL) {
*out_reason = reason != NULL ? reason : "invalid";
}
free(token_bytes);
amduatd_cap_token_free(&token);
return false;
}
ok = true;
free(token_bytes);
amduatd_cap_token_free(&token);
return ok;
}
static bool amduatd_handle_post_capabilities( static bool amduatd_handle_post_capabilities(
int fd, int fd,
amduat_asl_store_t *store, amduat_asl_store_t *store,

View file

@ -44,6 +44,11 @@ bool amduatd_caps_handle(amduatd_ctx_t *ctx,
const amduatd_http_req_t *req, const amduatd_http_req_t *req,
amduatd_http_resp_t *resp); amduatd_http_resp_t *resp);
bool amduatd_caps_check_space(const amduatd_caps_t *caps,
const amduatd_cfg_t *dcfg,
const amduatd_http_req_t *req,
const char **out_reason);
#ifdef __cplusplus #ifdef __cplusplus
} /* extern "C" */ } /* extern "C" */
#endif #endif

860
src/amduatd_space_doctor.c Normal file
View file

@ -0,0 +1,860 @@
#include "amduatd_space_doctor.h"
#include "amduat/asl/record.h"
#include "amduat/enc/asl_log.h"
#include "amduat/enc/asl1_core_codec.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
char *data;
size_t len;
size_t cap;
} amduatd_doctor_strbuf_t;
static void amduatd_doctor_strbuf_free(amduatd_doctor_strbuf_t *b) {
if (b == NULL) {
return;
}
free(b->data);
b->data = NULL;
b->len = 0u;
b->cap = 0u;
}
static bool amduatd_doctor_strbuf_reserve(amduatd_doctor_strbuf_t *b,
size_t extra) {
size_t need;
size_t next_cap;
char *next;
if (b == NULL) {
return false;
}
if (extra == 0u) {
return true;
}
if (b->len > SIZE_MAX - extra) {
return false;
}
need = b->len + extra;
if (need <= b->cap) {
return true;
}
next_cap = b->cap != 0u ? b->cap : 256u;
while (next_cap < need) {
if (next_cap > (SIZE_MAX / 2u)) {
next_cap = need;
break;
}
next_cap *= 2u;
}
next = (char *)realloc(b->data, next_cap);
if (next == NULL) {
return false;
}
b->data = next;
b->cap = next_cap;
return true;
}
static bool amduatd_doctor_strbuf_append(amduatd_doctor_strbuf_t *b,
const char *data,
size_t len) {
if (b == NULL) {
return false;
}
if (data == NULL) {
len = 0u;
}
if (!amduatd_doctor_strbuf_reserve(b, len + 1u)) {
return false;
}
if (len != 0u) {
memcpy(b->data + b->len, data, len);
}
b->len += len;
b->data[b->len] = '\0';
return true;
}
static bool amduatd_doctor_strbuf_append_cstr(amduatd_doctor_strbuf_t *b,
const char *s) {
return amduatd_doctor_strbuf_append(b,
s != NULL ? s : "",
s != NULL ? strlen(s) : 0u);
}
static bool amduatd_doctor_strbuf_append_char(amduatd_doctor_strbuf_t *b,
char c) {
return amduatd_doctor_strbuf_append(b, &c, 1u);
}
static char *amduatd_doctor_strdup(const char *s) {
size_t len;
char *copy;
if (s == NULL) {
return NULL;
}
len = strlen(s);
copy = (char *)malloc(len + 1u);
if (copy == NULL) {
return NULL;
}
memcpy(copy, s, len);
copy[len] = '\0';
return copy;
}
static const char *amduatd_space_doctor_status_name(
amduatd_space_doctor_status_t status) {
switch (status) {
case AMDUATD_DOCTOR_OK:
return "ok";
case AMDUATD_DOCTOR_WARN:
return "warn";
case AMDUATD_DOCTOR_FAIL:
return "fail";
case AMDUATD_DOCTOR_SKIPPED:
return "skipped";
default:
return "unknown";
}
}
static void amduatd_space_doctor_report_init(
amduatd_space_doctor_report_t *report) {
if (report == NULL) {
return;
}
memset(report, 0, sizeof(*report));
report->backend = AMDUATD_STORE_BACKEND_FS;
}
static void amduatd_space_doctor_report_clear(
amduatd_space_doctor_report_t *report) {
if (report == NULL) {
return;
}
for (size_t i = 0u; i < report->checks_len; ++i) {
free(report->checks[i].name);
free(report->checks[i].detail);
}
free(report->checks);
amduatd_space_doctor_report_init(report);
}
void amduatd_space_doctor_report_free(amduatd_space_doctor_report_t *report) {
amduatd_space_doctor_report_clear(report);
}
static bool amduatd_space_doctor_report_add(
amduatd_space_doctor_report_t *report,
const char *name,
amduatd_space_doctor_status_t status,
const char *detail) {
size_t next_len;
amduatd_space_doctor_check_t *next;
amduatd_space_doctor_check_t *entry;
if (report == NULL || name == NULL) {
return false;
}
if (report->checks_len > SIZE_MAX - 1u) {
return false;
}
next_len = report->checks_len + 1u;
next = (amduatd_space_doctor_check_t *)realloc(
report->checks, next_len * sizeof(*next));
if (next == NULL) {
return false;
}
report->checks = next;
entry = &report->checks[report->checks_len];
memset(entry, 0, sizeof(*entry));
entry->name = amduatd_doctor_strdup(name);
entry->detail = amduatd_doctor_strdup(detail != NULL ? detail : "");
if (entry->name == NULL || entry->detail == NULL) {
free(entry->name);
free(entry->detail);
return false;
}
entry->status = status;
report->checks_len = next_len;
switch (status) {
case AMDUATD_DOCTOR_OK:
report->ok_count++;
break;
case AMDUATD_DOCTOR_WARN:
report->warn_count++;
break;
case AMDUATD_DOCTOR_FAIL:
report->fail_count++;
break;
case AMDUATD_DOCTOR_SKIPPED:
report->skipped_count++;
break;
default:
break;
}
return true;
}
static bool amduatd_space_doctor_build_collection_head_name(
const char *name,
char **out_name) {
size_t name_len;
size_t total_len;
char *buffer;
size_t offset = 0u;
if (name == NULL || out_name == NULL) {
return false;
}
if (!amduat_asl_pointer_name_is_valid(name)) {
return false;
}
name_len = strlen(name);
total_len = 11u + name_len + 5u + 1u;
buffer = (char *)malloc(total_len);
if (buffer == NULL) {
return false;
}
memcpy(buffer + offset, "collection/", 11u);
offset += 11u;
memcpy(buffer + offset, name, name_len);
offset += name_len;
memcpy(buffer + offset, "/head", 5u);
offset += 5u;
buffer[offset] = '\0';
*out_name = buffer;
return true;
}
static bool amduatd_space_doctor_build_collection_log_head_name(
const char *name,
char **out_name) {
size_t name_len;
size_t log_name_len;
size_t total_len;
char *buffer;
size_t offset = 0u;
if (name == NULL || out_name == NULL) {
return false;
}
if (!amduat_asl_pointer_name_is_valid(name)) {
return false;
}
name_len = strlen(name);
log_name_len = 11u + name_len + 4u;
total_len = 4u + log_name_len + 5u + 1u;
buffer = (char *)malloc(total_len);
if (buffer == NULL) {
return false;
}
memcpy(buffer + offset, "log/", 4u);
offset += 4u;
memcpy(buffer + offset, "collection/", 11u);
offset += 11u;
memcpy(buffer + offset, name, name_len);
offset += name_len;
memcpy(buffer + offset, "/log", 4u);
offset += 4u;
memcpy(buffer + offset, "/head", 5u);
offset += 5u;
buffer[offset] = '\0';
*out_name = buffer;
return true;
}
enum {
AMDUATD_EDGE_INDEX_MAGIC_LEN = 8,
AMDUATD_EDGE_INDEX_VERSION = 1
};
static const uint8_t k_amduatd_edge_index_magic[AMDUATD_EDGE_INDEX_MAGIC_LEN] = {
'A', 'S', 'L', 'E', 'I', 'X', '1', '\0'
};
typedef struct {
bool has_graph_ref;
amduat_reference_t graph_ref;
} amduatd_edge_index_state_view_t;
static void amduatd_edge_index_state_view_free(
amduatd_edge_index_state_view_t *state) {
if (state == NULL) {
return;
}
if (state->has_graph_ref) {
amduat_reference_free(&state->graph_ref);
}
memset(state, 0, sizeof(*state));
}
static bool amduatd_doctor_read_u32_le(const uint8_t *data,
size_t len,
size_t *offset,
uint32_t *out) {
if (len - *offset < 4u) {
return false;
}
*out = (uint32_t)data[*offset] |
((uint32_t)data[*offset + 1u] << 8) |
((uint32_t)data[*offset + 2u] << 16) |
((uint32_t)data[*offset + 3u] << 24);
*offset += 4u;
return true;
}
static bool amduatd_doctor_read_u64_le(const uint8_t *data,
size_t len,
size_t *offset,
uint64_t *out) {
if (len - *offset < 8u) {
return false;
}
*out = (uint64_t)data[*offset] |
((uint64_t)data[*offset + 1u] << 8) |
((uint64_t)data[*offset + 2u] << 16) |
((uint64_t)data[*offset + 3u] << 24) |
((uint64_t)data[*offset + 4u] << 32) |
((uint64_t)data[*offset + 5u] << 40) |
((uint64_t)data[*offset + 6u] << 48) |
((uint64_t)data[*offset + 7u] << 56);
*offset += 8u;
return true;
}
static bool amduatd_edge_index_state_view_decode(
amduat_octets_t payload,
amduatd_edge_index_state_view_t *out_state) {
size_t offset = 0u;
uint32_t version = 0u;
uint32_t ref_len = 0u;
amduat_octets_t ref_bytes;
uint64_t ignored_offset = 0u;
if (out_state == NULL) {
return false;
}
memset(out_state, 0, sizeof(*out_state));
if (payload.data == NULL ||
payload.len < AMDUATD_EDGE_INDEX_MAGIC_LEN + 4u + 8u + 4u) {
return false;
}
if (memcmp(payload.data, k_amduatd_edge_index_magic,
AMDUATD_EDGE_INDEX_MAGIC_LEN) != 0) {
return false;
}
offset += AMDUATD_EDGE_INDEX_MAGIC_LEN;
if (!amduatd_doctor_read_u32_le(payload.data, payload.len, &offset, &version) ||
version != AMDUATD_EDGE_INDEX_VERSION) {
return false;
}
if (!amduatd_doctor_read_u64_le(payload.data, payload.len, &offset,
&ignored_offset) ||
!amduatd_doctor_read_u32_le(payload.data, payload.len, &offset, &ref_len)) {
return false;
}
(void)ignored_offset;
if (payload.len - offset < ref_len) {
return false;
}
if (ref_len != 0u) {
ref_bytes = amduat_octets(payload.data + offset, ref_len);
if (!amduat_enc_asl1_core_decode_reference_v1(ref_bytes,
&out_state->graph_ref)) {
amduatd_edge_index_state_view_free(out_state);
return false;
}
out_state->has_graph_ref = true;
offset += ref_len;
}
return offset == payload.len;
}
static amduatd_store_backend_t amduatd_space_doctor_backend_from_store(
const amduat_asl_store_t *store) {
if (store == NULL) {
return AMDUATD_STORE_BACKEND_FS;
}
if (store->ops.log_scan != NULL && store->ops.current_state != NULL) {
return AMDUATD_STORE_BACKEND_INDEX;
}
return AMDUATD_STORE_BACKEND_FS;
}
typedef struct {
const char *label;
char *pointer_name;
bool pointer_exists;
amduat_reference_t pointer_ref;
} amduatd_doctor_pointer_t;
static void amduatd_space_doctor_pointer_free(amduatd_doctor_pointer_t *ptr) {
if (ptr == NULL) {
return;
}
free(ptr->pointer_name);
ptr->pointer_name = NULL;
ptr->pointer_exists = false;
amduat_reference_free(&ptr->pointer_ref);
}
bool amduatd_space_doctor_run(amduat_asl_store_t *store,
amduat_asl_pointer_store_t *pointer_store,
const amduatd_space_t *effective_space,
const amduatd_cfg_t *cfg,
const amduatd_fed_cfg_t *fed_cfg,
amduatd_space_doctor_report_t *out_report) {
amduatd_doctor_pointer_t pointers[3];
amduat_octets_t edges_collection = amduat_octets(NULL, 0u);
amduat_octets_t edges_index_head = amduat_octets(NULL, 0u);
char *collection_head = NULL;
char *collection_log_head = NULL;
bool names_ok = true;
bool ok = false;
size_t i;
if (out_report == NULL) {
return false;
}
amduatd_space_doctor_report_init(out_report);
if (store == NULL || pointer_store == NULL || cfg == NULL) {
return false;
}
(void)cfg;
out_report->backend = amduatd_space_doctor_backend_from_store(store);
if (effective_space != NULL &&
effective_space->enabled &&
effective_space->space_id.data != NULL) {
out_report->scoped = true;
snprintf(out_report->space_id, sizeof(out_report->space_id), "%s",
(const char *)effective_space->space_id.data);
}
memset(pointers, 0, sizeof(pointers));
pointers[0].label = "edges_index_head";
pointers[1].label = "edges_collection_snapshot_head";
pointers[2].label = "edges_collection_log_head";
for (i = 0u; i < 3u; ++i) {
memset(&pointers[i].pointer_ref, 0, sizeof(pointers[i].pointer_ref));
}
if (!amduatd_space_edges_collection_name(effective_space, &edges_collection) ||
!amduatd_space_edges_index_head_name(effective_space, &edges_index_head)) {
(void)amduatd_space_doctor_report_add(out_report,
"pointer_name_validation",
AMDUATD_DOCTOR_FAIL,
"failed to scope pointer names");
names_ok = false;
goto doctor_after_names;
}
if (!amduatd_space_doctor_build_collection_head_name(
(const char *)edges_collection.data, &collection_head) ||
!amduatd_space_doctor_build_collection_log_head_name(
(const char *)edges_collection.data, &collection_log_head)) {
(void)amduatd_space_doctor_report_add(out_report,
"pointer_name_validation",
AMDUATD_DOCTOR_FAIL,
"failed to build collection heads");
names_ok = false;
goto doctor_after_names;
}
if (!amduat_asl_pointer_name_is_valid(
(const char *)edges_collection.data) ||
!amduat_asl_pointer_name_is_valid(
(const char *)edges_index_head.data) ||
!amduat_asl_pointer_name_is_valid(collection_head) ||
!amduat_asl_pointer_name_is_valid(collection_log_head)) {
(void)amduatd_space_doctor_report_add(out_report,
"pointer_name_validation",
AMDUATD_DOCTOR_FAIL,
"invalid pointer name encoding");
names_ok = false;
goto doctor_after_names;
}
(void)amduatd_space_doctor_report_add(out_report,
"pointer_name_validation",
AMDUATD_DOCTOR_OK,
"scoped pointer names valid");
doctor_after_names:
if (!names_ok) {
(void)amduatd_space_doctor_report_add(out_report,
"edges_index_head",
AMDUATD_DOCTOR_SKIPPED,
"invalid pointer names");
(void)amduatd_space_doctor_report_add(out_report,
"edges_collection_snapshot_head",
AMDUATD_DOCTOR_SKIPPED,
"invalid pointer names");
(void)amduatd_space_doctor_report_add(out_report,
"edges_collection_log_head",
AMDUATD_DOCTOR_SKIPPED,
"invalid pointer names");
(void)amduatd_space_doctor_report_add(out_report,
"cas_edges_index_head",
AMDUATD_DOCTOR_SKIPPED,
"invalid pointer names");
(void)amduatd_space_doctor_report_add(out_report,
"cas_edges_collection_snapshot_head",
AMDUATD_DOCTOR_SKIPPED,
"invalid pointer names");
(void)amduatd_space_doctor_report_add(out_report,
"cas_edges_collection_log_head",
AMDUATD_DOCTOR_SKIPPED,
"invalid pointer names");
(void)amduatd_space_doctor_report_add(out_report,
"edge_index_state_parse",
AMDUATD_DOCTOR_SKIPPED,
"invalid pointer names");
(void)amduatd_space_doctor_report_add(out_report,
"edge_index_graph_ref",
AMDUATD_DOCTOR_SKIPPED,
"invalid pointer names");
goto doctor_index_checks;
}
pointers[0].pointer_name = amduatd_doctor_strdup(
(const char *)edges_index_head.data);
pointers[1].pointer_name = amduatd_doctor_strdup(collection_head);
pointers[2].pointer_name = amduatd_doctor_strdup(collection_log_head);
if (pointers[0].pointer_name == NULL ||
pointers[1].pointer_name == NULL ||
pointers[2].pointer_name == NULL) {
(void)amduatd_space_doctor_report_add(out_report,
"pointer_store",
AMDUATD_DOCTOR_FAIL,
"oom");
goto doctor_cleanup;
}
for (i = 0u; i < 3u; ++i) {
bool exists = false;
amduat_asl_pointer_error_t perr =
amduat_asl_pointer_get(pointer_store,
pointers[i].pointer_name,
&exists,
&pointers[i].pointer_ref);
if (perr != AMDUAT_ASL_POINTER_OK) {
char detail[256];
snprintf(detail, sizeof(detail), "pointer error: %s",
pointers[i].pointer_name);
(void)amduatd_space_doctor_report_add(out_report,
pointers[i].label,
AMDUATD_DOCTOR_FAIL,
detail);
continue;
}
pointers[i].pointer_exists = exists;
if (exists) {
char detail[256];
snprintf(detail, sizeof(detail), "present: %s",
pointers[i].pointer_name);
(void)amduatd_space_doctor_report_add(out_report,
pointers[i].label,
AMDUATD_DOCTOR_OK,
detail);
} else {
char detail[256];
snprintf(detail, sizeof(detail), "missing: %s",
pointers[i].pointer_name);
(void)amduatd_space_doctor_report_add(out_report,
pointers[i].label,
AMDUATD_DOCTOR_WARN,
detail);
}
}
for (i = 0u; i < 3u; ++i) {
const char *check_name = NULL;
switch (i) {
case 0u:
check_name = "cas_edges_index_head";
break;
case 1u:
check_name = "cas_edges_collection_snapshot_head";
break;
case 2u:
check_name = "cas_edges_collection_log_head";
break;
default:
check_name = "cas_pointer_ref";
break;
}
if (!pointers[i].pointer_exists) {
(void)amduatd_space_doctor_report_add(out_report,
check_name,
AMDUATD_DOCTOR_SKIPPED,
"pointer missing");
continue;
}
{
amduat_artifact_t artifact;
memset(&artifact, 0, sizeof(artifact));
if (amduat_asl_store_get(store, pointers[i].pointer_ref, &artifact) !=
AMDUAT_ASL_STORE_OK) {
(void)amduatd_space_doctor_report_add(out_report,
check_name,
AMDUATD_DOCTOR_FAIL,
"store get failed");
} else {
(void)amduatd_space_doctor_report_add(out_report,
check_name,
AMDUATD_DOCTOR_OK,
"ref readable");
}
amduat_artifact_free(&artifact);
}
}
if (!pointers[0].pointer_exists) {
(void)amduatd_space_doctor_report_add(out_report,
"edge_index_state_parse",
AMDUATD_DOCTOR_SKIPPED,
"pointer missing");
(void)amduatd_space_doctor_report_add(out_report,
"edge_index_graph_ref",
AMDUATD_DOCTOR_SKIPPED,
"pointer missing");
} else {
amduat_asl_record_t record;
amduatd_edge_index_state_view_t state;
const char *schema = "tgk/edge_index_state";
bool parsed = false;
memset(&record, 0, sizeof(record));
memset(&state, 0, sizeof(state));
if (amduat_asl_record_store_get(store,
pointers[0].pointer_ref,
&record) != AMDUAT_ASL_STORE_OK) {
(void)amduatd_space_doctor_report_add(out_report,
"edge_index_state_parse",
AMDUATD_DOCTOR_FAIL,
"record load failed");
} else if (record.schema.len != strlen(schema) ||
memcmp(record.schema.data, schema, record.schema.len) != 0) {
(void)amduatd_space_doctor_report_add(out_report,
"edge_index_state_parse",
AMDUATD_DOCTOR_FAIL,
"unexpected schema");
} else if (!amduatd_edge_index_state_view_decode(record.payload, &state)) {
(void)amduatd_space_doctor_report_add(out_report,
"edge_index_state_parse",
AMDUATD_DOCTOR_FAIL,
"payload decode failed");
} else {
(void)amduatd_space_doctor_report_add(out_report,
"edge_index_state_parse",
AMDUATD_DOCTOR_OK,
"edge index state decoded");
parsed = true;
}
if (parsed && state.has_graph_ref) {
amduat_artifact_t graph_artifact;
memset(&graph_artifact, 0, sizeof(graph_artifact));
if (amduat_asl_store_get(store, state.graph_ref, &graph_artifact) !=
AMDUAT_ASL_STORE_OK) {
(void)amduatd_space_doctor_report_add(out_report,
"edge_index_graph_ref",
AMDUATD_DOCTOR_FAIL,
"graph ref missing");
} else {
(void)amduatd_space_doctor_report_add(out_report,
"edge_index_graph_ref",
AMDUATD_DOCTOR_OK,
"graph ref readable");
}
amduat_artifact_free(&graph_artifact);
} else if (parsed) {
(void)amduatd_space_doctor_report_add(out_report,
"edge_index_graph_ref",
AMDUATD_DOCTOR_SKIPPED,
"no graph ref");
} else {
(void)amduatd_space_doctor_report_add(out_report,
"edge_index_graph_ref",
AMDUATD_DOCTOR_SKIPPED,
"edge index state unreadable");
}
amduatd_edge_index_state_view_free(&state);
amduat_asl_record_free(&record);
}
doctor_index_checks:
if (out_report->backend == AMDUATD_STORE_BACKEND_INDEX) {
amduat_asl_index_state_t state;
amduat_asl_log_record_t *records = NULL;
size_t record_count = 0u;
if (!amduat_asl_index_current_state(store, &state)) {
(void)amduatd_space_doctor_report_add(out_report,
"index_current_state",
AMDUATD_DOCTOR_FAIL,
"current_state failed");
} else {
(void)amduatd_space_doctor_report_add(out_report,
"index_current_state",
AMDUATD_DOCTOR_OK,
"current_state ok");
}
{
amduat_asl_store_error_t scan_err =
amduat_asl_log_scan(store, &records, &record_count);
if (scan_err == AMDUAT_ASL_STORE_OK) {
(void)amduatd_space_doctor_report_add(out_report,
"index_log_scan",
AMDUATD_DOCTOR_OK,
"log_scan ok");
} else {
(void)amduatd_space_doctor_report_add(out_report,
"index_log_scan",
AMDUATD_DOCTOR_FAIL,
"log_scan failed");
}
if (records != NULL) {
amduat_enc_asl_log_free(records, record_count);
}
}
} else {
(void)amduatd_space_doctor_report_add(out_report,
"index_current_state",
AMDUATD_DOCTOR_SKIPPED,
"requires index backend");
(void)amduatd_space_doctor_report_add(out_report,
"index_log_scan",
AMDUATD_DOCTOR_SKIPPED,
"requires index backend");
}
{
char detail[256];
bool fed_enabled = fed_cfg != NULL && fed_cfg->enabled;
bool require_index_backend = fed_enabled;
snprintf(detail, sizeof(detail),
"enabled=%s require_index_backend=%s",
fed_enabled ? "true" : "false",
require_index_backend ? "true" : "false");
(void)amduatd_space_doctor_report_add(out_report,
"federation",
AMDUATD_DOCTOR_OK,
detail);
}
ok = true;
doctor_cleanup:
for (i = 0u; i < 3u; ++i) {
amduatd_space_doctor_pointer_free(&pointers[i]);
}
free((void *)edges_collection.data);
free((void *)edges_index_head.data);
free(collection_head);
free(collection_log_head);
if (!ok) {
amduatd_space_doctor_report_clear(out_report);
}
return ok;
}
bool amduatd_space_doctor_report_json(
const amduatd_space_doctor_report_t *report,
char **out_json) {
amduatd_doctor_strbuf_t b;
char header[256];
if (out_json != NULL) {
*out_json = NULL;
}
if (report == NULL || out_json == NULL) {
return false;
}
memset(&b, 0, sizeof(b));
if (!amduatd_doctor_strbuf_append_cstr(&b, "{")) {
amduatd_doctor_strbuf_free(&b);
return false;
}
if (!amduatd_doctor_strbuf_append_cstr(&b, "\"effective_space\":{")) {
amduatd_doctor_strbuf_free(&b);
return false;
}
if (report->scoped) {
if (snprintf(header, sizeof(header),
"\"mode\":\"scoped\",\"space_id\":\"%s\"",
report->space_id) <= 0 ||
!amduatd_doctor_strbuf_append_cstr(&b, header)) {
amduatd_doctor_strbuf_free(&b);
return false;
}
} else {
if (!amduatd_doctor_strbuf_append_cstr(&b, "\"mode\":\"unscoped\"")) {
amduatd_doctor_strbuf_free(&b);
return false;
}
}
if (!amduatd_doctor_strbuf_append_cstr(&b, "},")) {
amduatd_doctor_strbuf_free(&b);
return false;
}
if (snprintf(header, sizeof(header),
"\"store_backend\":\"%s\",",
amduatd_store_backend_name(report->backend)) <= 0 ||
!amduatd_doctor_strbuf_append_cstr(&b, header)) {
amduatd_doctor_strbuf_free(&b);
return false;
}
if (!amduatd_doctor_strbuf_append_cstr(&b, "\"checks\":[")) {
amduatd_doctor_strbuf_free(&b);
return false;
}
for (size_t i = 0u; i < report->checks_len; ++i) {
const amduatd_space_doctor_check_t *check = &report->checks[i];
if (i != 0u) {
(void)amduatd_doctor_strbuf_append_char(&b, ',');
}
if (!amduatd_doctor_strbuf_append_cstr(&b, "{\"name\":\"") ||
!amduatd_doctor_strbuf_append_cstr(&b, check->name) ||
!amduatd_doctor_strbuf_append_cstr(&b, "\",\"status\":\"") ||
!amduatd_doctor_strbuf_append_cstr(
&b,
amduatd_space_doctor_status_name(check->status)) ||
!amduatd_doctor_strbuf_append_cstr(&b, "\",\"detail\":\"") ||
!amduatd_doctor_strbuf_append_cstr(&b, check->detail) ||
!amduatd_doctor_strbuf_append_cstr(&b, "\"}")) {
amduatd_doctor_strbuf_free(&b);
return false;
}
}
if (!amduatd_doctor_strbuf_append_cstr(&b, "],")) {
amduatd_doctor_strbuf_free(&b);
return false;
}
if (snprintf(header, sizeof(header),
"\"summary\":{"
"\"ok_count\":%llu,"
"\"warn_count\":%llu,"
"\"fail_count\":%llu,"
"\"skipped_count\":%llu"
"}}\n",
(unsigned long long)report->ok_count,
(unsigned long long)report->warn_count,
(unsigned long long)report->fail_count,
(unsigned long long)report->skipped_count) <= 0 ||
!amduatd_doctor_strbuf_append_cstr(&b, header)) {
amduatd_doctor_strbuf_free(&b);
return false;
}
*out_json = b.data;
return true;
}

View file

@ -0,0 +1,60 @@
#ifndef AMDUATD_SPACE_DOCTOR_H
#define AMDUATD_SPACE_DOCTOR_H
#include "amduat/asl/asl_pointer_fs.h"
#include "amduat/asl/core.h"
#include "amduat/asl/store.h"
#include "amduatd_caps.h"
#include "amduatd_fed.h"
#include "amduatd_store.h"
#include <stdbool.h>
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef enum {
AMDUATD_DOCTOR_OK = 0,
AMDUATD_DOCTOR_WARN = 1,
AMDUATD_DOCTOR_FAIL = 2,
AMDUATD_DOCTOR_SKIPPED = 3
} amduatd_space_doctor_status_t;
typedef struct {
char *name;
amduatd_space_doctor_status_t status;
char *detail;
} amduatd_space_doctor_check_t;
typedef struct {
bool scoped;
char space_id[AMDUAT_ASL_POINTER_NAME_MAX + 1u];
amduatd_store_backend_t backend;
amduatd_space_doctor_check_t *checks;
size_t checks_len;
size_t ok_count;
size_t warn_count;
size_t fail_count;
size_t skipped_count;
} amduatd_space_doctor_report_t;
bool amduatd_space_doctor_run(amduat_asl_store_t *store,
amduat_asl_pointer_store_t *pointer_store,
const amduatd_space_t *effective_space,
const amduatd_cfg_t *cfg,
const amduatd_fed_cfg_t *fed_cfg,
amduatd_space_doctor_report_t *out_report);
bool amduatd_space_doctor_report_json(
const amduatd_space_doctor_report_t *report,
char **out_json);
void amduatd_space_doctor_report_free(amduatd_space_doctor_report_t *report);
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif /* AMDUATD_SPACE_DOCTOR_H */

View file

@ -0,0 +1,465 @@
#ifndef _POSIX_C_SOURCE
#define _POSIX_C_SOURCE 200809L
#endif
#include "amduatd_space_doctor.h"
#include "amduat/asl/asl_store_fs_meta.h"
#include "amduat/asl/core.h"
#include "amduat/asl/none.h"
#include "amduat/asl/record.h"
#include "amduat/enc/asl1_core_codec.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static char *amduatd_test_make_temp_dir(void) {
char tmpl[] = "/tmp/amduatd-doctor-XXXXXX";
char *dir = mkdtemp(tmpl);
size_t len;
char *copy;
if (dir == NULL) {
perror("mkdtemp");
return NULL;
}
len = strlen(dir);
copy = (char *)malloc(len + 1u);
if (copy == NULL) {
fprintf(stderr, "failed to allocate temp dir copy\n");
return NULL;
}
memcpy(copy, dir, len + 1u);
return copy;
}
static bool amduatd_check_status(const amduatd_space_doctor_report_t *report,
const char *name,
amduatd_space_doctor_status_t expected) {
if (report == NULL || name == NULL) {
return false;
}
for (size_t i = 0u; i < report->checks_len; ++i) {
if (strcmp(report->checks[i].name, name) == 0) {
return report->checks[i].status == expected;
}
}
return false;
}
static bool amduatd_build_collection_head_name(const char *name,
char **out_name) {
size_t name_len;
size_t total_len;
char *buffer;
size_t offset = 0u;
if (name == NULL || out_name == NULL) {
return false;
}
if (!amduat_asl_pointer_name_is_valid(name)) {
return false;
}
name_len = strlen(name);
total_len = 11u + name_len + 5u + 1u;
buffer = (char *)malloc(total_len);
if (buffer == NULL) {
return false;
}
memcpy(buffer + offset, "collection/", 11u);
offset += 11u;
memcpy(buffer + offset, name, name_len);
offset += name_len;
memcpy(buffer + offset, "/head", 5u);
offset += 5u;
buffer[offset] = '\0';
*out_name = buffer;
return true;
}
static bool amduatd_build_collection_log_head_name(const char *name,
char **out_name) {
size_t name_len;
size_t log_name_len;
size_t total_len;
char *buffer;
size_t offset = 0u;
if (name == NULL || out_name == NULL) {
return false;
}
if (!amduat_asl_pointer_name_is_valid(name)) {
return false;
}
name_len = strlen(name);
log_name_len = 11u + name_len + 4u;
total_len = 4u + log_name_len + 5u + 1u;
buffer = (char *)malloc(total_len);
if (buffer == NULL) {
return false;
}
memcpy(buffer + offset, "log/", 4u);
offset += 4u;
memcpy(buffer + offset, "collection/", 11u);
offset += 11u;
memcpy(buffer + offset, name, name_len);
offset += name_len;
memcpy(buffer + offset, "/log", 4u);
offset += 4u;
memcpy(buffer + offset, "/head", 5u);
offset += 5u;
buffer[offset] = '\0';
*out_name = buffer;
return true;
}
static void amduatd_store_u32_le(uint8_t *out, uint32_t value) {
out[0] = (uint8_t)(value & 0xffu);
out[1] = (uint8_t)((value >> 8) & 0xffu);
out[2] = (uint8_t)((value >> 16) & 0xffu);
out[3] = (uint8_t)((value >> 24) & 0xffu);
}
static void amduatd_store_u64_le(uint8_t *out, uint64_t value) {
out[0] = (uint8_t)(value & 0xffu);
out[1] = (uint8_t)((value >> 8) & 0xffu);
out[2] = (uint8_t)((value >> 16) & 0xffu);
out[3] = (uint8_t)((value >> 24) & 0xffu);
out[4] = (uint8_t)((value >> 32) & 0xffu);
out[5] = (uint8_t)((value >> 40) & 0xffu);
out[6] = (uint8_t)((value >> 48) & 0xffu);
out[7] = (uint8_t)((value >> 56) & 0xffu);
}
static bool amduatd_build_edge_index_state_payload(
amduat_reference_t graph_ref,
amduat_octets_t *out_payload) {
static const uint8_t k_magic[8] = {
'A', 'S', 'L', 'E', 'I', 'X', '1', '\0'
};
amduat_octets_t ref_bytes = amduat_octets(NULL, 0u);
uint8_t *payload = NULL;
size_t payload_len = 0u;
size_t offset = 0u;
if (out_payload == NULL) {
return false;
}
*out_payload = amduat_octets(NULL, 0u);
if (!amduat_enc_asl1_core_encode_reference_v1(graph_ref, &ref_bytes)) {
return false;
}
if (ref_bytes.len > UINT32_MAX) {
free((void *)ref_bytes.data);
return false;
}
payload_len = 8u + 4u + 8u + 4u + ref_bytes.len;
payload = (uint8_t *)malloc(payload_len);
if (payload == NULL) {
free((void *)ref_bytes.data);
return false;
}
memcpy(payload + offset, k_magic, 8u);
offset += 8u;
amduatd_store_u32_le(payload + offset, 1u);
offset += 4u;
amduatd_store_u64_le(payload + offset, 0u);
offset += 8u;
amduatd_store_u32_le(payload + offset, (uint32_t)ref_bytes.len);
offset += 4u;
memcpy(payload + offset, ref_bytes.data, ref_bytes.len);
offset += ref_bytes.len;
free((void *)ref_bytes.data);
*out_payload = amduat_octets(payload, payload_len);
return offset == payload_len;
}
static int amduatd_test_empty_store(void) {
char *root = amduatd_test_make_temp_dir();
amduat_asl_store_fs_config_t cfg;
amduatd_store_ctx_t store_ctx;
amduat_asl_store_t store;
amduat_asl_pointer_store_t pointer_store;
amduatd_space_t space;
amduatd_cfg_t dcfg;
amduatd_fed_cfg_t fed_cfg;
amduatd_space_doctor_report_t report;
if (root == NULL) {
return 1;
}
memset(&cfg, 0, sizeof(cfg));
if (!amduat_asl_store_fs_init_root(root, NULL, &cfg)) {
fprintf(stderr, "failed to init store root\n");
free(root);
return 1;
}
memset(&store_ctx, 0, sizeof(store_ctx));
memset(&store, 0, sizeof(store));
if (!amduatd_store_init(&store,
&cfg,
&store_ctx,
root,
AMDUATD_STORE_BACKEND_FS)) {
fprintf(stderr, "failed to init fs backend\n");
free(root);
return 1;
}
if (!amduat_asl_pointer_store_init(&pointer_store, root)) {
fprintf(stderr, "failed to init pointer store\n");
free(root);
return 1;
}
if (!amduatd_space_init(&space, NULL, false)) {
fprintf(stderr, "failed to init space\n");
free(root);
return 1;
}
memset(&dcfg, 0, sizeof(dcfg));
dcfg.space = space;
memset(&fed_cfg, 0, sizeof(fed_cfg));
if (!amduatd_space_doctor_run(&store,
&pointer_store,
&dcfg.space,
&dcfg,
&fed_cfg,
&report)) {
fprintf(stderr, "doctor run failed\n");
free(root);
return 1;
}
if (report.backend != AMDUATD_STORE_BACKEND_FS) {
fprintf(stderr, "expected fs backend\n");
amduatd_space_doctor_report_free(&report);
free(root);
return 1;
}
if (!amduatd_check_status(&report,
"pointer_name_validation",
AMDUATD_DOCTOR_OK) ||
!amduatd_check_status(&report,
"edges_index_head",
AMDUATD_DOCTOR_WARN) ||
!amduatd_check_status(&report,
"edges_collection_snapshot_head",
AMDUATD_DOCTOR_WARN) ||
!amduatd_check_status(&report,
"edges_collection_log_head",
AMDUATD_DOCTOR_WARN) ||
!amduatd_check_status(&report,
"index_current_state",
AMDUATD_DOCTOR_SKIPPED) ||
!amduatd_check_status(&report,
"index_log_scan",
AMDUATD_DOCTOR_SKIPPED)) {
fprintf(stderr, "unexpected doctor status for empty store\n");
amduatd_space_doctor_report_free(&report);
free(root);
return 1;
}
amduatd_space_doctor_report_free(&report);
free(root);
return 0;
}
static int amduatd_test_minimal_fixture(void) {
char *root = amduatd_test_make_temp_dir();
amduat_asl_store_fs_config_t cfg;
amduatd_store_ctx_t store_ctx;
amduat_asl_store_t store;
amduat_asl_pointer_store_t pointer_store;
amduatd_space_t space;
amduatd_cfg_t dcfg;
amduatd_fed_cfg_t fed_cfg;
amduatd_space_doctor_report_t report;
amduat_reference_t none_ref;
amduat_reference_t graph_ref;
amduat_reference_t edge_index_ref;
amduat_octets_t collection_name = amduat_octets(NULL, 0u);
amduat_octets_t index_head_name = amduat_octets(NULL, 0u);
char *collection_head = NULL;
char *collection_log_head = NULL;
amduat_octets_t payload = amduat_octets(NULL, 0u);
bool swapped = false;
if (root == NULL) {
return 1;
}
memset(&cfg, 0, sizeof(cfg));
if (!amduat_asl_store_fs_init_root(root, NULL, &cfg)) {
fprintf(stderr, "failed to init store root\n");
free(root);
return 1;
}
memset(&store_ctx, 0, sizeof(store_ctx));
memset(&store, 0, sizeof(store));
if (!amduatd_store_init(&store,
&cfg,
&store_ctx,
root,
AMDUATD_STORE_BACKEND_FS)) {
fprintf(stderr, "failed to init fs backend\n");
free(root);
return 1;
}
if (!amduat_asl_pointer_store_init(&pointer_store, root)) {
fprintf(stderr, "failed to init pointer store\n");
free(root);
return 1;
}
if (!amduatd_space_init(&space, NULL, false)) {
fprintf(stderr, "failed to init space\n");
free(root);
return 1;
}
memset(&dcfg, 0, sizeof(dcfg));
dcfg.space = space;
memset(&fed_cfg, 0, sizeof(fed_cfg));
{
amduat_artifact_t none_artifact;
if (!amduat_asl_none_artifact(&none_artifact)) {
fprintf(stderr, "failed to build none artifact\n");
free(root);
return 1;
}
if (amduat_asl_store_put(&store, none_artifact, &none_ref) !=
AMDUAT_ASL_STORE_OK) {
fprintf(stderr, "failed to store none artifact\n");
amduat_artifact_free(&none_artifact);
free(root);
return 1;
}
amduat_artifact_free(&none_artifact);
}
{
amduat_artifact_t graph_artifact =
amduat_artifact(amduat_octets("graph", 5u));
if (amduat_asl_store_put(&store, graph_artifact, &graph_ref) !=
AMDUAT_ASL_STORE_OK) {
fprintf(stderr, "failed to store graph artifact\n");
free(root);
return 1;
}
}
if (!amduatd_build_edge_index_state_payload(graph_ref, &payload)) {
fprintf(stderr, "failed to encode edge index state\n");
free(root);
return 1;
}
if (amduat_asl_record_store_put(&store,
amduat_octets("tgk/edge_index_state", 20u),
payload,
&edge_index_ref) != AMDUAT_ASL_STORE_OK) {
fprintf(stderr, "failed to store edge index record\n");
free((void *)payload.data);
free(root);
return 1;
}
free((void *)payload.data);
if (!amduatd_space_edges_collection_name(&dcfg.space, &collection_name) ||
!amduatd_space_edges_index_head_name(&dcfg.space, &index_head_name)) {
fprintf(stderr, "failed to build scoped names\n");
free(root);
return 1;
}
if (!amduatd_build_collection_head_name((const char *)collection_name.data,
&collection_head) ||
!amduatd_build_collection_log_head_name(
(const char *)collection_name.data, &collection_log_head)) {
fprintf(stderr, "failed to build collection heads\n");
free(root);
return 1;
}
if (amduat_asl_pointer_cas(&pointer_store,
(const char *)index_head_name.data,
false,
NULL,
&edge_index_ref,
&swapped) != AMDUAT_ASL_POINTER_OK || !swapped ||
amduat_asl_pointer_cas(&pointer_store,
collection_head,
false,
NULL,
&none_ref,
&swapped) != AMDUAT_ASL_POINTER_OK || !swapped ||
amduat_asl_pointer_cas(&pointer_store,
collection_log_head,
false,
NULL,
&none_ref,
&swapped) != AMDUAT_ASL_POINTER_OK || !swapped) {
fprintf(stderr, "failed to seed pointers\n");
free(root);
return 1;
}
if (!amduatd_space_doctor_run(&store,
&pointer_store,
&dcfg.space,
&dcfg,
&fed_cfg,
&report)) {
fprintf(stderr, "doctor run failed\n");
free(root);
return 1;
}
if (!amduatd_check_status(&report,
"edges_index_head",
AMDUATD_DOCTOR_OK) ||
!amduatd_check_status(&report,
"edges_collection_snapshot_head",
AMDUATD_DOCTOR_OK) ||
!amduatd_check_status(&report,
"edges_collection_log_head",
AMDUATD_DOCTOR_OK) ||
!amduatd_check_status(&report,
"edge_index_state_parse",
AMDUATD_DOCTOR_OK) ||
!amduatd_check_status(&report,
"edge_index_graph_ref",
AMDUATD_DOCTOR_OK) ||
!amduatd_check_status(&report,
"index_current_state",
AMDUATD_DOCTOR_SKIPPED)) {
fprintf(stderr, "unexpected doctor status for fixture\n");
amduatd_space_doctor_report_free(&report);
free(root);
return 1;
}
amduatd_space_doctor_report_free(&report);
free((void *)collection_name.data);
free((void *)index_head_name.data);
free(collection_head);
free(collection_log_head);
free(root);
return 0;
}
int main(void) {
if (amduatd_test_empty_store() != 0) {
return 1;
}
if (amduatd_test_minimal_fixture() != 0) {
return 1;
}
return 0;
}