amduat-api/docs/federation-coordinator.md
Carl Niklas Rydberg a4b501e48d federation
2026-01-21 19:51:26 +01:00

356 lines
11 KiB
Markdown

# Federation Coordinator Middle Layer Spec
This document specifies the middle-layer coordinator that orchestrates federation
above the core C substrate. It remains transport- and policy-aware while keeping
core semantics unchanged.
## Scope and Goals
- Maintain per-domain replay bounds and an admitted set.
- Fetch and ingest published records from remotes.
- Build federation views via core APIs.
- Resolve missing bytes by fetching artifacts into a cache store.
- Keep storage layout private (no extents or blocks exposed).
- Align with tier1 federation semantics and replay determinism.
- Federation view is the union of admitted domains regardless of topology.
Non-goals:
- Re-implement core semantics in this layer.
- Introduce a single global snapshot ID (federation is per-domain).
## Core Dependencies
The coordinator calls into vendor core APIs (`vendor/amduat/include/amduat/fed/*`)
and MUST stay aligned with their signatures:
- `amduat_fed_registry_*` (persist per-domain admission state)
- `amduat_fed_ingest_validate` (record validation + conflict detection)
- `amduat_fed_replay_build` (deterministic replay per domain)
- `amduat_fed_view_build` + `amduat_fed_resolve` (build view and resolve ref-only)
Core expects per-domain replay bounds `{domain_id, snapshot_id, log_prefix}` and
does not handle transport, auth, caching policy, or remote fetch.
Alignment note: the daemon-layer API in this repo still needs updates to match
current vendor core types and signatures. This spec reflects vendor headers as
the source of truth.
Tier1 alignment (normative):
- `ASL/FEDERATION/1`
- `ASL/FEDERATION-REPLAY/1`
- `ASL/DOMAIN-MODEL/1`
- `ASL/POLICY-HASH/1`
- `ENC/ASL-CORE-INDEX/1` (canonical in `vendor/amduat/tier1/enc-asl-core-index-1.md`)
## Data Structures
### Registry State (per domain, persisted)
```
struct amduat_fed_domain_state {
uint32_t domain_id;
uint64_t snapshot_id;
uint64_t log_prefix;
uint64_t last_logseq;
uint8_t admitted;
uint8_t policy_ok;
uint8_t reserved[6];
amduat_hash_id_t policy_hash_id;
amduat_octets_t policy_hash; /* Empty when unknown; caller-owned bytes. */
};
```
Registry bytes are stored via `amduat_fed_registry_store_*` in the local ASL
store; the coordinator owns admission workflows and policy compatibility checks.
### Admitted Set (in-memory)
```
struct amduat_fed_admitted_set {
uint32_t *domain_ids; // sorted, unique
size_t len;
};
```
The admitted set is derived from registry entries with `admitted != 0` and
`policy_ok != 0`.
### Snapshot Vector (per view build)
```
struct amduat_fed_snapshot_vector {
amduat_fed_view_bounds_t *bounds;
size_t len;
uint64_t vector_epoch;
};
```
### Record Staging (per fetch batch)
```
struct amduat_fed_record {
amduat_fed_record_meta_t meta;
amduat_fed_record_id_t id;
uint64_t logseq;
uint64_t snapshot_id;
uint64_t log_prefix;
};
```
Records MUST carry the fields required by `ASL/FEDERATION-REPLAY/1`, and replay
ordering MUST be deterministic (sort by `logseq`, then canonical identity).
Record metadata includes visibility and optional cross-domain source identity.
### View and Policy Denies
```
struct amduat_fed_view_bounds {
uint32_t domain_id;
uint64_t snapshot_id;
uint64_t log_prefix;
};
struct amduat_fed_policy_deny {
amduat_fed_record_id_t id;
uint32_t reason_code;
};
```
### Fetch Backlog / Retry State
```
struct amduat_fed_fetch_state {
uint32_t domain_id;
uint64_t next_snapshot_id;
uint64_t next_log_prefix;
uint64_t next_logseq;
uint64_t backoff_ms;
uint64_t last_attempt_ms;
uint32_t consecutive_failures;
};
```
### Cache Store Metadata (optional)
```
struct amduat_fed_cache_policy {
bool enabled;
uint64_t max_bytes;
uint64_t used_bytes;
uint32_t ttl_seconds;
uint32_t prefetch_depth;
};
```
## Coordinator Interfaces
### Configuration
```
struct amduat_fed_coord_cfg {
const char *registry_path;
amduat_asl_store_t *authoritative_store;
amduat_asl_store_t *cache_store; // optional
amduat_asl_store_t *session_store; // optional
amduat_fed_transport transport;
amduat_fed_cache_policy cache_policy;
amduat_fed_policy_hooks policy_hooks;
};
```
### Lifecycle and Operations
```
int amduat_fed_coord_open(
const struct amduat_fed_coord_cfg *cfg,
struct amduat_fed_coord **out);
int amduat_fed_coord_close(struct amduat_fed_coord *c);
int amduat_fed_coord_load_registry(struct amduat_fed_coord *c);
int amduat_fed_coord_set_admitted(
struct amduat_fed_coord *c,
uint32_t domain_id,
bool admitted);
int amduat_fed_coord_tick(struct amduat_fed_coord *c, uint64_t now_ms);
int amduat_fed_coord_resolve(
struct amduat_fed_coord *c,
amduat_reference_t ref,
amduat_artifact_t *out);
```
## API Status
Planned coordinator surface (not yet implemented):
- `amduat_fed_coord_open`
- `amduat_fed_coord_close`
- `amduat_fed_coord_load_registry`
- `amduat_fed_coord_set_admitted`
- `amduat_fed_coord_tick`
- `amduat_fed_coord_resolve`
Implemented in core (already available):
- `amduat_fed_registry_*`
- `amduat_fed_ingest_validate`
- `amduat_fed_replay_build`
- `amduat_fed_view_build`
- `amduat_fed_resolve`
## Transport Abstraction
Minimal interface that hides protocol, auth, and topology:
```
struct amduat_fed_transport {
int (*get_records)(
void *ctx, uint32_t domain_id,
uint64_t snapshot_id, uint64_t log_prefix,
uint64_t from_logseq,
amduat_fed_record_iter *out_iter);
int (*get_artifact)(
void *ctx, amduat_reference_t ref,
amduat_sink *out_sink);
};
```
Transport MUST return records that can be validated with `amduat_fed_record_validate`
and MUST provide all fields required by `ASL/FEDERATION-REPLAY/1`.
Transport MUST NOT surface internal-only records from foreign domains.
## Storage and Encodings
- The coordinator stores records/artifacts via ASL store APIs and does not touch
segment layouts or extents directly.
- Federation metadata in index records is encoded by core per
`ENC/ASL-CORE-INDEX/1`; the coordinator must not override it.
- Cache store semantics are best-effort and do not affect authoritative state.
## Policies
- Admission is per-domain and controlled via registry entries and `policy_ok`.
- Policy compatibility uses `policy_hash_id` + `policy_hash` (ASL/POLICY-HASH/1).
- `policy_ok` is computed during admission by comparing local policy hash to the
remote domain's published policy hash.
- Admission and policy compatibility MUST be enforced before any foreign state is
admitted into the federation view.
- Per-record filtering, if used, MUST be deterministic and SHOULD be expressed
as policy denies passed to `amduat_fed_view_build` rather than by dropping
records before validation.
- Cache write policy is middle-layer only (fetch-on-miss, optional prefetch).
- Eviction is local (LRU or segmented queues) and must not leak layout.
- Conflict policy: reject on identity collision with differing metadata and keep
bounds stable until operator intervention (ASL/FEDERATION-REPLAY/1).
## Sequencing and Consistency
- Deterministic views require a stable snapshot vector per build.
- Bounds advance only after successful ingest; they never move backwards.
- Before validation, records are ordered by `(logseq, canonical identity)` and
filtered to `logseq <= log_prefix`.
- Tombstones and shadowing apply only within the source domain.
- Use `vector_epoch` to swap snapshot vectors atomically after a build.
- Persist registry updates atomically via `amduat_fed_registry_store_put` before
swapping the snapshot vector.
- If a remote retracts or regresses, keep local bounds and mark domain degraded.
## Core Flow
### Startup
- Load registry.
- Derive admitted set from `admitted != 0` and `policy_ok != 0`.
- Build initial snapshot vector.
### Periodic Tick
- For each admitted domain, fetch records up to bound and validate.
- Update `last_logseq`, and advance bounds if remote snapshot moves forward.
- Build federation view using `amduat_fed_view_build` over the staged records,
with optional policy denies derived from coordinator hooks.
- Swap snapshot vector atomically after registry updates.
### Resolve Loop
- Call `amduat_fed_resolve` against the latest built `amduat_fed_view_t`.
- If `AMDUAT_FED_RESOLVE_FOUND_REMOTE_NO_BYTES`, fetch bytes via transport into cache store.
- Retry resolve after cache write completes.
## Example Tick Pseudocode
```
int amduat_fed_coord_tick(struct amduat_fed_coord *c, uint64_t now_ms) {
amduat_fed_snapshot_vector vec = build_snapshot_vector(c);
clear(staged_records_all);
build_policy_denies(&policy_denies, &policy_denies_len);
for each domain in vec.bounds:
bound = &vec.bounds[i];
if !admitted(bound->domain_id) continue;
if backoff_active(bound->domain_id, now_ms) continue;
clear(staged_records);
iter = transport.get_records(
bound->domain_id,
bound->snapshot_id,
bound->log_prefix,
state.next_logseq);
while iter.next(record):
if !amduat_fed_record_validate(&record):
mark_domain_error(bound->domain_id);
break;
append(staged_records, record);
sort_by_logseq_then_id(staged_records);
clamp_to_log_prefix(staged_records, bound->log_prefix);
rc = amduat_fed_ingest_validate(
staged_records, staged_len, &err_index, &conflict_index);
if rc == AMDUAT_FED_INGEST_ERR_CONFLICT:
mark_domain_error(bound->domain_id);
continue;
if rc == AMDUAT_FED_INGEST_ERR_INVALID:
mark_domain_error(bound->domain_id);
continue;
update_registry_bounds(bound->domain_id, state);
append_all(staged_records_all, staged_records);
amduat_fed_view_build(
staged_records_all, staged_len_all,
local_domain_id, vec.bounds, vec.len,
policy_denies, policy_denies_len,
&view);
swap_snapshot_vector(c, build_snapshot_vector(c));
return 0;
}
```
## Example Resolve Pseudocode
```
int amduat_fed_coord_resolve(
struct amduat_fed_coord *c,
amduat_reference_t ref,
amduat_artifact_t *out) {
view = c->last_view;
rc = amduat_fed_resolve(&view, c->authoritative_store, ref, out);
if (rc == AMDUAT_FED_RESOLVE_FOUND_REMOTE_NO_BYTES && c->cache_store) {
rc = transport.get_artifact(ref, sink_for_cache(c->cache_store));
if (rc == 0) {
rc = amduat_fed_resolve(&view, c->authoritative_store, ref, out);
}
}
return rc;
}
```
## Coordinator Wiring Example
```
amduat_fed_transport_unix_t unix_transport;
amduat_fed_coord_cfg_t cfg;
amduat_fed_coord_t *coord = NULL;
amduat_fed_transport_unix_init(&unix_transport, "amduatd.sock");
memset(&cfg, 0, sizeof(cfg));
cfg.local_domain_id = 1;
cfg.authoritative_store = &store;
cfg.cache_store = &cache_store;
cfg.transport = amduat_fed_transport_unix_ops(&unix_transport);
if (amduat_fed_coord_open(&cfg, &coord) == AMDUAT_FED_COORD_OK) {
amduat_fed_coord_tick(coord, now_ms);
amduat_fed_coord_resolve(coord, some_ref, &artifact);
amduat_fed_coord_close(coord);
}
```