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/TGK-EXEC-PLAN/1` | 🟡 | Encoding implemented; executor out of scope. |
| `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. |
| `TGK/1` | 🟡 | Semantic layer planned. |
@ -348,3 +350,15 @@ Status legend: ✅ completed, ⬜ pending.
embedded commit-message appendix; tightened wording throughout; bumped the
document version/date.
- 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 reserved[6];
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;
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);
/* Encode/Decode allocate buffers; caller frees via amduat_octets_free. */
bool amduat_fed_registry_encode(const amduat_fed_registry_value_t *value,
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,
amduat_fed_registry_value_t *out_value);

View file

@ -28,7 +28,7 @@ typedef struct {
typedef struct {
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;
typedef struct {
@ -44,6 +44,7 @@ typedef struct {
size_t len;
} 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_replay_domain(const amduat_fed_record_t *records,

View file

@ -39,14 +39,23 @@ typedef struct {
size_t denies_len;
} amduat_fed_view_t;
bool amduat_fed_view_build(const amduat_fed_record_t *records,
size_t count,
uint32_t local_domain_id,
const amduat_fed_view_bounds_t *bounds,
size_t bounds_len,
const amduat_fed_policy_deny_t *denies,
size_t denies_len,
amduat_fed_view_t *out_view);
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,
uint32_t local_domain_id,
const amduat_fed_view_bounds_t *bounds,
size_t bounds_len,
const amduat_fed_policy_deny_t *denies,
size_t denies_len,
amduat_fed_view_t *out_view);
void amduat_fed_view_free(amduat_fed_view_t *view);

View file

@ -75,22 +75,23 @@ static void amduat_fed_policy_deny_free(amduat_fed_policy_deny_t *deny) {
memset(deny, 0, sizeof(*deny));
}
static bool amduat_fed_view_append(amduat_fed_view_t *view,
const amduat_fed_record_t *records,
size_t count) {
static amduat_fed_view_error_t amduat_fed_view_append(
amduat_fed_view_t *view,
const amduat_fed_record_t *records,
size_t count) {
size_t i;
size_t base_len;
if (count == 0u) {
return true;
return AMDUAT_FED_VIEW_OK;
}
if (view->len > SIZE_MAX - count) {
return false;
return AMDUAT_FED_VIEW_ERR_OOM;
}
if (view->records == NULL) {
view->records = (amduat_fed_record_t *)calloc(count, sizeof(*view->records));
if (view->records == NULL) {
return false;
return AMDUAT_FED_VIEW_ERR_OOM;
}
} else {
amduat_fed_record_t *next =
@ -98,7 +99,7 @@ static bool amduat_fed_view_append(amduat_fed_view_t *view,
(view->len + count) *
sizeof(*view->records));
if (next == NULL) {
return false;
return AMDUAT_FED_VIEW_ERR_OOM;
}
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]);
}
view->len = base_len;
return false;
return AMDUAT_FED_VIEW_ERR_CONFLICT;
}
continue;
}
@ -127,26 +128,27 @@ static bool amduat_fed_view_append(amduat_fed_view_t *view,
amduat_fed_record_free(&view->records[j]);
}
view->len = base_len;
return false;
return AMDUAT_FED_VIEW_ERR_OOM;
}
view->len++;
}
return true;
return AMDUAT_FED_VIEW_OK;
}
bool amduat_fed_view_build(const amduat_fed_record_t *records,
size_t count,
uint32_t local_domain_id,
const amduat_fed_view_bounds_t *bounds,
size_t bounds_len,
const amduat_fed_policy_deny_t *denies,
size_t denies_len,
amduat_fed_view_t *out_view) {
amduat_fed_view_error_t amduat_fed_view_build(
const amduat_fed_record_t *records,
size_t count,
uint32_t local_domain_id,
const amduat_fed_view_bounds_t *bounds,
size_t bounds_len,
const amduat_fed_policy_deny_t *denies,
size_t denies_len,
amduat_fed_view_t *out_view) {
size_t i;
amduat_fed_view_t view;
if (out_view == NULL) {
return false;
return AMDUAT_FED_VIEW_ERR_INVALID;
}
out_view->records = NULL;
out_view->len = 0;
@ -155,7 +157,7 @@ bool amduat_fed_view_build(const amduat_fed_record_t *records,
out_view->denies_len = 0;
if (records == NULL && count != 0u) {
return false;
return AMDUAT_FED_VIEW_ERR_INVALID;
}
view.records = NULL;
@ -165,13 +167,13 @@ bool amduat_fed_view_build(const amduat_fed_record_t *records,
view.denies_len = 0;
if (denies_len != 0u && denies == NULL) {
return false;
return AMDUAT_FED_VIEW_ERR_INVALID;
}
if (denies_len != 0u) {
view.denies = (amduat_fed_policy_deny_t *)calloc(denies_len,
sizeof(*view.denies));
if (view.denies == NULL) {
return false;
return AMDUAT_FED_VIEW_ERR_OOM;
}
for (i = 0; i < denies_len; ++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]);
}
free(view.denies);
return false;
return AMDUAT_FED_VIEW_ERR_OOM;
}
}
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));
if (domain_records == NULL) {
amduat_fed_view_free(&view);
return false;
return AMDUAT_FED_VIEW_ERR_OOM;
}
domain_count = 0;
@ -234,7 +236,7 @@ bool amduat_fed_view_build(const amduat_fed_record_t *records,
}
free(domain_records);
amduat_fed_view_free(&view);
return false;
return AMDUAT_FED_VIEW_ERR_OOM;
}
domain_count++;
}
@ -251,20 +253,25 @@ bool amduat_fed_view_build(const amduat_fed_record_t *records,
free(domain_records);
if (!ok) {
amduat_fed_view_free(&view);
return false;
return AMDUAT_FED_VIEW_ERR_INVALID;
}
if (!amduat_fed_view_append(&view,
domain_view.records,
domain_view.len)) {
amduat_fed_replay_view_free(&domain_view);
amduat_fed_view_free(&view);
return false;
{
amduat_fed_view_error_t append_err;
append_err = amduat_fed_view_append(&view,
domain_view.records,
domain_view.len);
if (append_err != AMDUAT_FED_VIEW_OK) {
amduat_fed_replay_view_free(&domain_view);
amduat_fed_view_free(&view);
return append_err;
}
}
amduat_fed_replay_view_free(&domain_view);
}
*out_view = view;
return true;
return AMDUAT_FED_VIEW_OK;
}
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].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");
return 1;
}
@ -167,7 +168,8 @@ static int test_view_conflict(void) {
bounds[1].snapshot_id = 1;
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");
amduat_fed_view_free(&view);
return 1;
@ -191,11 +193,13 @@ static int test_view_rebuild_metadata(void) {
bounds[0].snapshot_id = 1;
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");
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");
amduat_fed_view_free(&view_a);
return 1;