feat(mesh): cross-node routing + presence breaker (Phase 3)
- NodeConfig.address: routable host peers proxy to (wired registry + wire) - compute_routes emits 'remote' routes for consumed peer services (local requires ref satisfied by an online peer -> <address>:<port>) - _host_remote_block: fail-fast reverse_proxy (2s dial, passive health); presence expiry removes the route entirely = the primary circuit-breaker - mesh_gateway.py: API re-renders + reloads the Caddyfile on mesh change, iff content changed (no-op until a cross-node service is consumed) - tests: route emit / address fallback / breaker-absent / unconsumed Logic verified hermetically + against primer's real registry (castle-api -> primer:9020); live integration proven a no-op on civil.
This commit is contained in:
@@ -32,6 +32,10 @@ class NodeConfig:
|
||||
tunnel_id: str | None = None
|
||||
# Emit the cert_obtained → `castle tls reconcile` hook (needs events-exec plugin).
|
||||
cert_hook: bool = False
|
||||
# Routable host peers use to reach this node's services (LAN IP/hostname).
|
||||
# Defaults to the hostname; set explicitly when the hostname isn't resolvable
|
||||
# cross-node. Used to build `remote` gateway routes to this node.
|
||||
address: str | None = None
|
||||
# Fleet role: "authority" may write shared config/secrets to the mesh;
|
||||
# "follower" reconciles from it. Static (no election) — the authority is
|
||||
# pinned in castle.yaml. When the authority is down, shared state is
|
||||
@@ -158,6 +162,7 @@ def load_registry(path: Path | None = None) -> NodeRegistry:
|
||||
tunnel_id=node_data.get("tunnel_id"),
|
||||
cert_hook=node_data.get("cert_hook", False),
|
||||
role=node_data.get("role", "follower"),
|
||||
address=node_data.get("address"),
|
||||
)
|
||||
|
||||
deployed: dict[str, Deployment] = {}
|
||||
@@ -249,6 +254,8 @@ def save_registry(registry: NodeRegistry, path: Path | None = None) -> None:
|
||||
data["node"]["cert_hook"] = registry.node.cert_hook
|
||||
if registry.node.role and registry.node.role != "follower":
|
||||
data["node"]["role"] = registry.node.role
|
||||
if registry.node.address:
|
||||
data["node"]["address"] = registry.node.address
|
||||
|
||||
for key, comp in registry.deployed.items():
|
||||
entry: dict = {
|
||||
|
||||
Reference in New Issue
Block a user