Improve federation API docs and errors

This commit is contained in:
Carl Niklas Rydberg 2026-01-18 12:03:18 +01:00
parent 5cc56b2ce8
commit d8a6603ddc
6 changed files with 84 additions and 47 deletions

View file

@ -73,6 +73,8 @@ Status legend: ✅ implemented, 🟡 planned/in-progress, ⬜ not started.
| `ASL/INDEXES/1` | 🟡 | Taxonomy planned. | | `ASL/INDEXES/1` | 🟡 | Taxonomy planned. |
| `ASL/TGK-EXEC-PLAN/1` | 🟡 | Encoding implemented; executor out of scope. | | `ASL/TGK-EXEC-PLAN/1` | 🟡 | Encoding implemented; executor out of scope. |
| `ENC/ASL-TGK-EXEC-PLAN/1` | ✅ | Plan encoding implemented. | | `ENC/ASL-TGK-EXEC-PLAN/1` | ✅ | Plan encoding implemented. |
| `ASL/FEDERATION/1` | ✅ | Core federation primitives implemented. |
| `ASL/FEDERATION-REPLAY/1` | ✅ | Deterministic replay and view construction implemented. |
| `ASL/SYSTEM/1` | 🟡 | Cross-cutting view planned. | | `ASL/SYSTEM/1` | 🟡 | Cross-cutting view planned. |
| `TGK/1` | 🟡 | Semantic layer planned. | | `TGK/1` | 🟡 | Semantic layer planned. |
@ -348,3 +350,15 @@ Status legend: ✅ completed, ⬜ pending.
embedded commit-message appendix; tightened wording throughout; bumped the embedded commit-message appendix; tightened wording throughout; bumped the
document version/date. document version/date.
- Tests: N/A (documentation-only change). - Tests: N/A (documentation-only change).
## 2026-02-XX — Federation core (`tier1/asl-federation-1.md`, `tier1/asl-federation-replay-1.md`)
- Scope: core federation registry, ingest validation, deterministic replay, view
construction, and resolve semantics.
- Findings: missing record typing and identity coverage for PER/TGK/tombstones;
policy gating needed explicit per-domain + optional per-record handling; view
build and resolve error reporting needed explicit codes and tests.
- Resolution: added federation registry storage, ingest validation, replay/view
build, resolve APIs, and tests for ordering, tombstone scoping, conflicts,
bounds, and metadata preservation; documented middle-layer boundary and
ref-only remote fetch guidance.
- Tests: user reported “100% tests passed, 0 tests failed out of 29”.

View file

@ -21,7 +21,7 @@ typedef struct {
uint8_t policy_ok; uint8_t policy_ok;
uint8_t reserved[6]; uint8_t reserved[6];
amduat_hash_id_t policy_hash_id; amduat_hash_id_t policy_hash_id;
amduat_octets_t policy_hash; /* Empty when unknown. */ amduat_octets_t policy_hash; /* Empty when unknown; caller-owned bytes. */
} amduat_fed_domain_state_t; } amduat_fed_domain_state_t;
typedef struct { typedef struct {
@ -44,9 +44,11 @@ const amduat_fed_domain_state_t *amduat_fed_registry_value_lookup(
void amduat_fed_registry_value_free(amduat_fed_registry_value_t *value); void amduat_fed_registry_value_free(amduat_fed_registry_value_t *value);
/* Encode/Decode allocate buffers; caller frees via amduat_octets_free. */
bool amduat_fed_registry_encode(const amduat_fed_registry_value_t *value, bool amduat_fed_registry_encode(const amduat_fed_registry_value_t *value,
amduat_octets_t *out_bytes); amduat_octets_t *out_bytes);
/* Decode allocates policy_hash bytes; free via amduat_fed_registry_value_free. */
bool amduat_fed_registry_decode(amduat_octets_t bytes, bool amduat_fed_registry_decode(amduat_octets_t bytes,
amduat_fed_registry_value_t *out_value); amduat_fed_registry_value_t *out_value);

View file

@ -28,7 +28,7 @@ typedef struct {
typedef struct { typedef struct {
amduat_fed_record_type_t type; amduat_fed_record_type_t type;
amduat_reference_t ref; amduat_reference_t ref; /* PER/TGK identities are ASL references. */
} amduat_fed_record_id_t; } amduat_fed_record_id_t;
typedef struct { typedef struct {
@ -44,6 +44,7 @@ typedef struct {
size_t len; size_t len;
} amduat_fed_replay_view_t; } amduat_fed_replay_view_t;
/* Caller frees record ids with amduat_fed_replay_view_free. */
bool amduat_fed_record_validate(const amduat_fed_record_t *record); bool amduat_fed_record_validate(const amduat_fed_record_t *record);
bool amduat_fed_replay_domain(const amduat_fed_record_t *records, bool amduat_fed_replay_domain(const amduat_fed_record_t *records,

View file

@ -39,7 +39,16 @@ typedef struct {
size_t denies_len; size_t denies_len;
} amduat_fed_view_t; } amduat_fed_view_t;
bool amduat_fed_view_build(const amduat_fed_record_t *records, typedef enum {
AMDUAT_FED_VIEW_OK = 0,
AMDUAT_FED_VIEW_ERR_INVALID = 1,
AMDUAT_FED_VIEW_ERR_CONFLICT = 2,
AMDUAT_FED_VIEW_ERR_OOM = 3
} amduat_fed_view_error_t;
/* Caller frees records/denies with amduat_fed_view_free. */
amduat_fed_view_error_t amduat_fed_view_build(
const amduat_fed_record_t *records,
size_t count, size_t count,
uint32_t local_domain_id, uint32_t local_domain_id,
const amduat_fed_view_bounds_t *bounds, const amduat_fed_view_bounds_t *bounds,

View file

@ -75,22 +75,23 @@ static void amduat_fed_policy_deny_free(amduat_fed_policy_deny_t *deny) {
memset(deny, 0, sizeof(*deny)); memset(deny, 0, sizeof(*deny));
} }
static bool amduat_fed_view_append(amduat_fed_view_t *view, static amduat_fed_view_error_t amduat_fed_view_append(
amduat_fed_view_t *view,
const amduat_fed_record_t *records, const amduat_fed_record_t *records,
size_t count) { size_t count) {
size_t i; size_t i;
size_t base_len; size_t base_len;
if (count == 0u) { if (count == 0u) {
return true; return AMDUAT_FED_VIEW_OK;
} }
if (view->len > SIZE_MAX - count) { if (view->len > SIZE_MAX - count) {
return false; return AMDUAT_FED_VIEW_ERR_OOM;
} }
if (view->records == NULL) { if (view->records == NULL) {
view->records = (amduat_fed_record_t *)calloc(count, sizeof(*view->records)); view->records = (amduat_fed_record_t *)calloc(count, sizeof(*view->records));
if (view->records == NULL) { if (view->records == NULL) {
return false; return AMDUAT_FED_VIEW_ERR_OOM;
} }
} else { } else {
amduat_fed_record_t *next = amduat_fed_record_t *next =
@ -98,7 +99,7 @@ static bool amduat_fed_view_append(amduat_fed_view_t *view,
(view->len + count) * (view->len + count) *
sizeof(*view->records)); sizeof(*view->records));
if (next == NULL) { if (next == NULL) {
return false; return AMDUAT_FED_VIEW_ERR_OOM;
} }
view->records = next; view->records = next;
} }
@ -115,7 +116,7 @@ static bool amduat_fed_view_append(amduat_fed_view_t *view,
amduat_fed_record_free(&view->records[j]); amduat_fed_record_free(&view->records[j]);
} }
view->len = base_len; view->len = base_len;
return false; return AMDUAT_FED_VIEW_ERR_CONFLICT;
} }
continue; continue;
} }
@ -127,14 +128,15 @@ static bool amduat_fed_view_append(amduat_fed_view_t *view,
amduat_fed_record_free(&view->records[j]); amduat_fed_record_free(&view->records[j]);
} }
view->len = base_len; view->len = base_len;
return false; return AMDUAT_FED_VIEW_ERR_OOM;
} }
view->len++; view->len++;
} }
return true; return AMDUAT_FED_VIEW_OK;
} }
bool amduat_fed_view_build(const amduat_fed_record_t *records, amduat_fed_view_error_t amduat_fed_view_build(
const amduat_fed_record_t *records,
size_t count, size_t count,
uint32_t local_domain_id, uint32_t local_domain_id,
const amduat_fed_view_bounds_t *bounds, const amduat_fed_view_bounds_t *bounds,
@ -146,7 +148,7 @@ bool amduat_fed_view_build(const amduat_fed_record_t *records,
amduat_fed_view_t view; amduat_fed_view_t view;
if (out_view == NULL) { if (out_view == NULL) {
return false; return AMDUAT_FED_VIEW_ERR_INVALID;
} }
out_view->records = NULL; out_view->records = NULL;
out_view->len = 0; out_view->len = 0;
@ -155,7 +157,7 @@ bool amduat_fed_view_build(const amduat_fed_record_t *records,
out_view->denies_len = 0; out_view->denies_len = 0;
if (records == NULL && count != 0u) { if (records == NULL && count != 0u) {
return false; return AMDUAT_FED_VIEW_ERR_INVALID;
} }
view.records = NULL; view.records = NULL;
@ -165,13 +167,13 @@ bool amduat_fed_view_build(const amduat_fed_record_t *records,
view.denies_len = 0; view.denies_len = 0;
if (denies_len != 0u && denies == NULL) { if (denies_len != 0u && denies == NULL) {
return false; return AMDUAT_FED_VIEW_ERR_INVALID;
} }
if (denies_len != 0u) { if (denies_len != 0u) {
view.denies = (amduat_fed_policy_deny_t *)calloc(denies_len, view.denies = (amduat_fed_policy_deny_t *)calloc(denies_len,
sizeof(*view.denies)); sizeof(*view.denies));
if (view.denies == NULL) { if (view.denies == NULL) {
return false; return AMDUAT_FED_VIEW_ERR_OOM;
} }
for (i = 0; i < denies_len; ++i) { for (i = 0; i < denies_len; ++i) {
if (!amduat_fed_policy_deny_clone(&denies[i], &view.denies[i])) { if (!amduat_fed_policy_deny_clone(&denies[i], &view.denies[i])) {
@ -180,7 +182,7 @@ bool amduat_fed_view_build(const amduat_fed_record_t *records,
amduat_fed_policy_deny_free(&view.denies[j]); amduat_fed_policy_deny_free(&view.denies[j]);
} }
free(view.denies); free(view.denies);
return false; return AMDUAT_FED_VIEW_ERR_OOM;
} }
} }
view.denies_len = denies_len; view.denies_len = denies_len;
@ -214,7 +216,7 @@ bool amduat_fed_view_build(const amduat_fed_record_t *records,
(amduat_fed_record_t *)calloc(domain_count, sizeof(*domain_records)); (amduat_fed_record_t *)calloc(domain_count, sizeof(*domain_records));
if (domain_records == NULL) { if (domain_records == NULL) {
amduat_fed_view_free(&view); amduat_fed_view_free(&view);
return false; return AMDUAT_FED_VIEW_ERR_OOM;
} }
domain_count = 0; domain_count = 0;
@ -234,7 +236,7 @@ bool amduat_fed_view_build(const amduat_fed_record_t *records,
} }
free(domain_records); free(domain_records);
amduat_fed_view_free(&view); amduat_fed_view_free(&view);
return false; return AMDUAT_FED_VIEW_ERR_OOM;
} }
domain_count++; domain_count++;
} }
@ -251,20 +253,25 @@ bool amduat_fed_view_build(const amduat_fed_record_t *records,
free(domain_records); free(domain_records);
if (!ok) { if (!ok) {
amduat_fed_view_free(&view); amduat_fed_view_free(&view);
return false; return AMDUAT_FED_VIEW_ERR_INVALID;
} }
if (!amduat_fed_view_append(&view, {
amduat_fed_view_error_t append_err;
append_err = amduat_fed_view_append(&view,
domain_view.records, domain_view.records,
domain_view.len)) { domain_view.len);
if (append_err != AMDUAT_FED_VIEW_OK) {
amduat_fed_replay_view_free(&domain_view); amduat_fed_replay_view_free(&domain_view);
amduat_fed_view_free(&view); amduat_fed_view_free(&view);
return false; return append_err;
}
} }
amduat_fed_replay_view_free(&domain_view); amduat_fed_replay_view_free(&domain_view);
} }
*out_view = view; *out_view = view;
return true; return AMDUAT_FED_VIEW_OK;
} }
void amduat_fed_view_free(amduat_fed_view_t *view) { void amduat_fed_view_free(amduat_fed_view_t *view) {

View file

@ -105,7 +105,8 @@ static int test_view_and_resolve(void) {
denies[0].id.ref = ref_d; denies[0].id.ref = ref_d;
denies[0].reason_code = 0; denies[0].reason_code = 0;
if (!amduat_fed_view_build(records, 4, 1, bounds, 2, denies, 1, &view)) { if (amduat_fed_view_build(records, 4, 1, bounds, 2, denies, 1, &view) !=
AMDUAT_FED_VIEW_OK) {
fprintf(stderr, "view build failed\n"); fprintf(stderr, "view build failed\n");
return 1; return 1;
} }
@ -167,7 +168,8 @@ static int test_view_conflict(void) {
bounds[1].snapshot_id = 1; bounds[1].snapshot_id = 1;
bounds[1].log_prefix = 10; bounds[1].log_prefix = 10;
if (amduat_fed_view_build(records, 2, 1, bounds, 2, NULL, 0, &view)) { if (amduat_fed_view_build(records, 2, 1, bounds, 2, NULL, 0, &view) !=
AMDUAT_FED_VIEW_ERR_CONFLICT) {
fprintf(stderr, "expected conflict\n"); fprintf(stderr, "expected conflict\n");
amduat_fed_view_free(&view); amduat_fed_view_free(&view);
return 1; return 1;
@ -191,11 +193,13 @@ static int test_view_rebuild_metadata(void) {
bounds[0].snapshot_id = 1; bounds[0].snapshot_id = 1;
bounds[0].log_prefix = 10; bounds[0].log_prefix = 10;
if (!amduat_fed_view_build(records, 1, 1, bounds, 1, NULL, 0, &view_a)) { if (amduat_fed_view_build(records, 1, 1, bounds, 1, NULL, 0, &view_a) !=
AMDUAT_FED_VIEW_OK) {
fprintf(stderr, "view rebuild build A failed\n"); fprintf(stderr, "view rebuild build A failed\n");
return 1; return 1;
} }
if (!amduat_fed_view_build(records, 1, 1, bounds, 1, NULL, 0, &view_b)) { if (amduat_fed_view_build(records, 1, 1, bounds, 1, NULL, 0, &view_b) !=
AMDUAT_FED_VIEW_OK) {
fprintf(stderr, "view rebuild build B failed\n"); fprintf(stderr, "view rebuild build B failed\n");
amduat_fed_view_free(&view_a); amduat_fed_view_free(&view_a);
return 1; return 1;