356 lines
11 KiB
Markdown
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);
|
||
|
|
}
|
||
|
|
```
|