feat(mesh): fleet role + shared config/presence KV (Phase 2)
- add static 'role' (authority|follower) to NodeConfig/CastleConfig, wired from castle.yaml through the registry to the mesh wire; civil pinned authority - castle-presence: TTL KV bucket each node renews (churn signal); delete-on-stop - castle-config: authority-gated get/put + change-watch SSE (follower reconcile hook hangs off it) - bound the NATS drain on stop() so shutdown can't hang - tests: role config/registry round-trip, wire round-trip, role write-gating Single-node verified; follower reconcile pending the second node.
This commit is contained in:
@@ -184,6 +184,9 @@ class CastleConfig:
|
||||
# built-in defaults so tests/callers that don't care stay valid.
|
||||
data_dir: Path = field(default_factory=lambda: _DEFAULT_DATA_DIR)
|
||||
repos_dir: Path = field(default_factory=lambda: _DEFAULT_REPOS_DIR)
|
||||
# Fleet role: "authority" (may write shared config/secrets to the mesh) or
|
||||
# "follower" (reconciles from it). Static — pinned here, no election.
|
||||
role: str = "follower"
|
||||
# Construction convenience only (not stored): a flat name→spec dict is routed
|
||||
# into the per-kind stores by kind_for. Lets callers/tests hand us a flat map
|
||||
# without pre-splitting it; there is still no flat `deployments` attribute.
|
||||
@@ -465,6 +468,7 @@ def load_config(root: Path | None = None) -> CastleConfig:
|
||||
agents=agents,
|
||||
data_dir=data_dir,
|
||||
repos_dir=repos_dir,
|
||||
role=data.get("role", "follower"),
|
||||
**stores,
|
||||
)
|
||||
return config
|
||||
|
||||
Reference in New Issue
Block a user