Expand description
cloud.vps.* verb signatures — the spike catalog (R409-T3).
Per W144 §“What this doc is not deciding” and R409-T11 (catalog-shape
postmortem), the broader cloud.* / dns.* / etc. surface is gated on
validating this shape against a second native provider (R409-T10:
DigitalOcean). Only cloud.vps.create, cloud.vps.destroy, and
cloud.vps.status land here — the three verbs W144 explicitly calls out
as “already partially present in MachineProvider.”
Wire types are plain serde structs (with optional schemars::JsonSchema
under the json-schema feature). They are deliberately not the same
as the in-crate domain types (crate::provider::ServerSpec,
crate::provider::ServerStatus) — the adapter is the boundary that
converts between them. Keeping wire and domain types separate is what
lets the same verb shape serve Hetzner, DigitalOcean, and (eventually)
a synthetic LocalDocker adapter without leaking vendor-specific fields.
@yah:ticket(R594-F5, “floating_ip.* envoy verb: raft floating-ingress ownership commands the provider IP to follow placement”)
@yah:status(review)
@yah:assignee(agent:claude)
@yah:at(2026-07-02T19:56:32Z)
@yah:phase(P4)
@yah:parent(R594)
@yah:next(“New envoy verb family alongside dns.* — assign/reassign a provider floating IP (Hetzner floating IP / OVH Additional IP / Vultr reserved IP) to the node that raft says owns floating ingress (raft/mod.rs:8 SetIngressOwner already exists). Provider mobility constraints verified 2026-07 and folded into W267 §Tier 1: Hetzner within network zone, OVH within DC/region, Vultr region-bound. This is the sovereign-tier analog of the external-identity-follows-placement property R591 names for Headscale/CF-Tunnel — LINKED behind R591 (live-peer-owned, never claim) so the two follow-placement mechanisms land coherently, and behind R572-F3 (public-ip taint lives on machine TOML).”)
@yah:verify(“cargo test -p yah-cloud envoy::floating_ip; fixture: ownership flip drives exactly one reassign call; idempotent re-apply”)
@yah:tier(Warrior)
@yah:handoff(“Landed the floating_ip.* envoy verb family + Hetzner/OVH/Vultr provider adapters, code+mock-tests only (no live calls, per constraint).\n\nHOMED IN: oss/yubaba/crates/cloud/src/envoy/floating_ip.rs (new module, mirrors dns_record.rs’s wire-signature-only shape: FloatingIpAssign/FloatingIpStatus marker types, Input/Output structs, InternalVerb impls). Added VerbCategory::FloatingIp ("floating_ip" namespace) to envoy.rs, registered in known_verb_descriptors() (9->11), and fixed VerbCategory’s #[serde(rename_all)] from "lowercase" to "snake_case" (a pre-existing latent bug that only bit once a multi-word variant existed: "FloatingIp" lowercased to "floatingip" instead of "floating_ip" — no-op for the six existing single-word variants).\n\nPROVIDER ABSTRACTION: oss/yubaba/crates/cloud/src/provider/floating_ip.rs defines FloatingIpProvider trait (resolve_target/current_assignment/reassign) + reconcile_assignment (the shared idempotent+zone-checked core) + on_ingress_owner_changed (the Rust-level callable entry point, doc-commented with exactly where leader.rs should call it — not touched, peer-owned). Three adapters, one file each, same shape as hetzner_envoy.rs/digitalocean.rs (thin reqwest client, with_base_url override, EnvoyAdapter + FloatingIpProvider impls): hetzner_floating_ip.rs, ovh_floating_ip.rs (new — no prior OVH code existed anywhere in-tree; auth is a placeholder header, flagged for real OVH request-signing before live use), vultr_floating_ip.rs.\n\nMACHINE RESOLUTION: reads existing MachineConfig.location/.region only (no new fields). Hetzner: location (DC code) -> network zone via a small table (hil/ash/fsn1-nbg1-hel1/sin). OVH: location if set else region (today’s OVH-labeled machines are provider="static" with region set but no location — R572-F3’s problem, not touched). Vultr: location directly (region-bound, no mapping needed) with a sanity cross-check against the live instance’s own region.\n\nIDEMPOTENCY: reconcile_assignment fetches current_assignment first; attached_to == target.attach_id -> reassigned:false, zero network calls to the reassign endpoint. Zone mismatch bails before any reassign call. Verified in 3 independent test layers: pure fake-provider unit tests (provider/floating_ip.rs), and per-provider axum in-process mock tests (127.0.0.1:0, stateful Arcgit field on ServiceComponent literal) — not touched by this ticket, not caused by this change (file has zero diff from HEAD), looks like another peer’s in-flight WIP on the shared tree.\n\nLive-provider verification (real credentials, real reassign against Hetzner/OVH/Vultr) is explicitly deferred to the operator, same as every other cloud envoy adapter.\n\nFiles: oss/yubaba/crates/cloud/src/envoy.rs, oss/yubaba/crates/cloud/src/envoy/floating_ip.rs (new), oss/yubaba/crates/cloud/src/provider/mod.rs, oss/yubaba/crates/cloud/src/provider/floating_ip.rs (new), oss/yubaba/crates/cloud/src/provider/hetzner_floating_ip.rs (new), oss/yubaba/crates/cloud/src/provider/ovh_floating_ip.rs (new), oss/yubaba/crates/cloud/src/provider/vultr_floating_ip.rs (new), oss/yubaba/crates/cloud/src/lib.rs.”)
Structs§
- Cloud
VpsCreate - Marker type for the
cloud.vps.createverb. - Cloud
VpsCreate Input - Request body for
cloud.vps.create. - Cloud
VpsCreate Output - Response body for
cloud.vps.create. - Cloud
VpsDestroy - Marker type for the
cloud.vps.destroyverb. - Cloud
VpsDestroy Input - Request body for
cloud.vps.destroy. - Cloud
VpsDestroy Output - Response body for
cloud.vps.destroy. Empty — adapters returnOk({})whether the server existed or was already gone (idempotent). - Cloud
VpsStatus - Marker type for the
cloud.vps.statusverb. - Cloud
VpsStatus Input - Request body for
cloud.vps.status. - Cloud
VpsStatus Output - Response body for
cloud.vps.status.
Enums§
- VpsPhase
- Canonical VPS lifecycle phase. Maps onto
crate::provider::ServerStatuswithout theUnknown(String)payload — the free-form vendor detail rides inCloudVpsStatusOutput::detailinstead so the wire shape stays a closed enum that downstream UI can render directly.
Functions§
- server_
status_ to_ output - Pure conversion: domain
crate::provider::ServerStatus→ wireCloudVpsStatusOutput.