yah-floating-ip 0.8.37

Provider-agnostic floating/reserved-IP mobility: the FloatingIpProvider seam, the idempotent zone-checked reconcile core, and the pure ingress-owner failover planner. Vendor HTTP adapters live in the consumer.
Documentation

Provider-abstracted floating/reserved-IP mobility and ingress failover (R594-F5, R859-F2).

[FloatingIpProvider] is the domain-level trait each vendor adapter implements; [reconcile_assignment] is the shared idempotent + zone-checked core all of them run through, so the "no-op when already assigned" / "reject a cross-zone move" behaviour is written and tested exactly once instead of once per vendor. [plan_ingress_owner_effect] is the pure decision layer above them: given a raft ingress_owner observation, what should public ingress do?

Why this is its own crate

It was cloud::provider::floating_ip until 2026-09-08. R859-F2 landed [plan_ingress_owner_effect] with no production caller, because the caller belongs in yubaba's scheduler tick and a runtime yubaba -> cloud dependency was not a courier's call to make: cloud/Cargo.toml's yah-local-driver comment records R374-F3 carving that crate out of cloud specifically to avoid such an edge, and taking it here would pull velveteen, velveteen-exec, yah-hetzner, yah-mesofact-bundle and yah-almanac into the release daemon shipped to every fleet node. The operator's answer (2026-09-08) was to repeat R374-F3's move rather than reverse it — hence this crate, which both cloud and yubaba depend on and neither owns.

So the dependency budget here is load-bearing, not tidiness: anyhow and async-trait, and nothing else, forever. A reqwest or a serde in this manifest ships to every node in the fleet.

What is above this crate, and where

Two consumers, and this crate depends on neither:

  • yah-floating-ip-adapters — the Hetzner/OVH/Vultr HTTP clients that implement [FloatingIpProvider]. They were in cloud until R859-F3 moved them one layer down so yubaba's ingress effector could actually issue a reassign rather than only decide on one. That crate carries the reqwest this one refuses to.
  • yah-cloud — the floating_ip.* envoy verb layer (cloud::provider::floating_ip_envoy) and the credentialed constructor cloud::provider::floating_ip::floating_ip_provider_for, which resolves vault slots through fob. Neither an envoy catalog nor a credential vault belongs on a fleet node, so neither moved.

This crate holds the seam, the shared core, and the decision logic.

The machine facts this layer needs

[FloatingIpMachine] is a five-field value, not cloud's MachineConfig. That is what makes the crate free of cloud: the adapters only ever read name, location, region, provider and ingress_floating_ip off a machine, so those five fields are the whole of the contract. cloud converts at the boundary (impl From<&MachineConfig> for FloatingIpMachine) and yubaba can build one from whatever it knows about a node without being able to construct a MachineConfig at all — which it cannot, since a fleet node has no .yah/infra/machines/ tree.

This mirrors, at the sovereign-ingress tier, the "external identity follows placement" property R591 names for Headscale via a Cloudflare Tunnel.