yah-cloud 0.8.46

Declarative cloud substrate for yah-managed camps: .yah/cloud/ config schema, MachineProvider drivers (Hetzner + local containerd), cloud-init rendering, and the pond/mesofact reconcilers.
[package]
# `cloud` is squatted on crates.io (an unrelated 0.0.0 crate), so the published
# name carries the yah- prefix per W230. Consumers keep the short `cloud` extern
# name via `cloud = { package = "yah-cloud", … }` — same trick this manifest
# already uses for workload-spec and local-driver — so no `use` path changed.
name = "yah-cloud"
version.workspace = true
edition.workspace = true
authors.workspace = true
license.workspace = true
repository.workspace = true
description = "Declarative cloud substrate for yah-managed camps: .yah/cloud/ config schema, MachineProvider drivers (Hetzner + local containerd), cloud-init rendering, and the pond/mesofact reconcilers."
keywords = ["cloud", "provisioning", "hetzner", "cloud-init", "reconciler"]
categories = ["config", "development-tools"]
# R743-T7: integration tests are declared explicitly below, not autodiscovered
# from tests/*.rs — `tests/main.rs` mods four former per-file binaries into one
# `main` target so they link once. Only affects `[[test]]` discovery; the
# `examples/load_probe.rs` example is still picked up automatically.
autotests = false
# R542 gate CLEARED (2026-07-23): this crate is LAST in the workspace's publish
# order and every dep it waited on is now satisfiable — yah-base, kamaji and
# velveteen were already on crates.io; yah-hetzner + yah-almanac published today;
# velveteen-exec publishes in the same wave, ahead of this workspace (qed before
# yubaba in scripts/release-all.sh's OSS_ORDER). Dropping publish=false; the
# `cloud` name is squatted so it ships as `yah-cloud` (see [lib] name below and
# the `package =` aliases at every consumer).

[lib]
# Lib target keeps the short name so the crate's own tests/examples
# (`use cloud::…`) and every downstream `cloud::` path stay valid.
name = "cloud"
path = "src/lib.rs"

[features]
# Enable the local-docker MachineProvider (tier 1 integration testing).
# Requires a reachable containerd socket: Colima (`colima start --runtime containerd`)
# on macOS, or the system socket on Linux.
local-docker = ["dep:containerd-client", "dep:prost-types"]

# Derive JsonSchema on the public config types so xtask can emit per-kind
# JSON Schemas to .yah/schema/*.toml.schema.json (R222 B4). Off by default;
# the cloud crate's runtime binary deps don't carry schemars.
json-schema = ["dep:schemars", "workload-spec/json-schema", "local-driver/json-schema"]

[dependencies]
# Signalling a previously-spawned local-process workload (SIGTERM/SIGKILL on
# re-reconcile) and probing whether its recorded pid is still alive. Already in
# this workspace's tree via kamaji; declared here so the local_process
# reconciler doesn't shell out to `kill`.
libc = "0.2"
# WorkloadSpec — typed wire format for yubaba workloads. Used by WorkloadConfig
# to validate workload TOML against the schema (R092-F1).
workload-spec = { package = "yah-workload-spec", version = "0.8.46" }
# Almanac feed configs + OnChangeConfig — dispatch_on_change maps on_change
# to the correct reconciler (R330-F4). Cross-workspace since R746-T10 (it lives
# in oss/mesofact now), so it drops the `path =` and resolves through this
# workspace's `[patch.crates-io]` block like every other out-of-tree dep.
yah-almanac = { version = "0.8.46" }

# Shared credential vault — HetznerDriver::from_default_sources() reads
# hetzner-api-token / hetzner-s3-access-key / hetzner-s3-secret-key
# from this vault, falling back to HETZNER_* env vars (R040-F9).
fob = { version = "0.8.46" }
# Shared Hetzner Cloud API client — transport, bearer auth, and server/SSH-key
# operations. Cloud crate adds S3 bucket ops and MachineProvider trait impl on top.
yah-hetzner = { path = "../hetzner", version = "0.8.46" }

# R859-F2: the floating-IP seam + the pure ingress-failover planner, which used
# to live in this crate's `provider::floating_ip`. Extracted so `yubaba` can
# depend on the planner without depending on `cloud` — the same carve-out
# R374-F3 made for `yah-local-driver` below, and made again here by operator
# decision (2026-09-08) rather than reversing it. Short extern name `floating_ip`.
floating_ip = { package = "yah-floating-ip", path = "../floating-ip", version = "0.8.46" }

# R859-F3: the Hetzner/OVH/Vultr HTTP clients that used to be
# `provider::{hetzner,ovh,vultr}_floating_ip`. Moved out for the same reason as
# the seam above — the fleet daemon needs them to actually MOVE an IP, and it
# must not link `cloud` to get them. What stayed here is the envoy verb layer
# (`provider::floating_ip_envoy`) and the `fob` credential lookup.
floating_ip_adapters = { package = "yah-floating-ip-adapters", path = "../floating-ip-adapters", version = "0.8.46" }

# Object-store trait + R2 impl — publish_to_r2 delegates PUT/GET ops here (R498-F5).
yah-object-store = { version = "0.8.46" }

# W272 mesofact bundle store (R599-F1) — content-addressed blob/manifest publish.
# `store` feature pulls the ObjectStore-backed publish/materialize surface.
yah-mesofact-bundle = { version = "0.8.46", features = ["store"] }

# Local-tier infrastructure primitives (docker-CLI driver + S3 SigV4 helpers).
# Both cloud and yubaba depend on this — moved out of cloud in R374-F3 so
# yubaba could own MinIO lifecycle without a reverse yubaba→cloud dep.
local-driver = { package = "yah-local-driver", version = "0.8.46" }

# Kamaji supervisor — the dev-tier capability drivers (yah-pg-dev,
# yah-smtp-dev, yah-s3-fs) run as host binaries through the Native fork+exec
# backend (R490-F2; W199's "ideal native-backend case"), so dev-tier workloads
# share one supervision primitive instead of each calling Command::spawn.
kamaji = { version = "0.8.46", features = ["native-integration"] }

# Forge executor + transform recipe loader. Cloud's static-asset reconciler
# materializes derived assets (W164) by lowering recipes → ForgeSpec and
# handing them to a `dyn ForgeExecutor`. The dep edge intentionally stops
# here: no cloud → qed edge, only cloud → task (R438-T13 architectural rule).
velveteen = { version = "0.8.46" }
velveteen-exec = { version = "0.8.46" }

serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
toml = "0.8"
anyhow = "1.0"
thiserror = "1.0"
async-trait = "0.1"
reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls", "stream"] }
# R876-T23: the camp-pond calls sign with the operator's key like every other
# yubaba client (pasetors + reqwest; no yubaba runtime).
yubaba-auth-token = { version = "0.8.46" }
tokio = { version = "1", features = ["rt", "rt-multi-thread", "macros", "process", "net", "time", "sync", "fs", "io-util"] }
# A few reconcilers' HTTP surfaces (sync_status, mesofact_static, static_asset,
# cloud_vps, pond). The floating-ip providers' axum mocks left with them in
# R859-F3, so this is no longer load-bearing for those.
axum = { version = "0.8", default-features = false, features = ["http1", "json", "tokio"] }
tracing = "0.1"
hmac = { version = "0.12", features = ["std"] }
sha2 = "0.10"
hex = "0.4"
chrono = { version = "0.4", default-features = false, features = ["clock", "serde"] }
base64 = "0.22"
getrandom = "0.2"
blake3 = "1"
# Per-transform tmp output dir (W164 materialize step) — recipe writes to a
# tmp file we BLAKE3-verify before renaming into .yah/cache/derive/transform/.
tempfile = "3"

# containerd gRPC client (optional — only with local-docker feature)
containerd-client = { version = "0.4", optional = true }
prost-types = { version = "0.11", optional = true }

# JSON Schema derivation (optional — only with json-schema feature).
# The xtask `emit-schemas` task enables this; runtime binaries don't.
schemars = { version = "0.8", optional = true }

[dev-dependencies]
tempfile = "3"
# Parses rendered cloud-init in tests to guard against the YAML footgun where a
# `: ` in a bare runcmd scalar turns the entry into a mapping (R330-F28 #12).
serde_yaml = "0.9"
tokio = { version = "1", features = ["rt", "rt-multi-thread", "macros", "test-util"] }

# ── Integration-test targets (R743-T7) ───────────────────────────────────────
# Two, not one, and the split is deliberate. See tests/main.rs's module docs for
# the full reasoning; the short version is that `.yah/qed/pond-smoke.toml` runs
# `cargo test -p cloud --test pond_smoke`, pond_smoke is the only test in the
# crate that binds a fixed external address (MinIO 127.0.0.1:9000) and manages
# named docker containers, and both of its tests assert wall-clock spinup
# budgets that sibling tests on libtest's thread pool would perturb.

# live_workspace_smoke + mesofact_static_e2e + pg_driver_live + whisper_derive_e2e.
[[test]]
name = "main"
path = "tests/main.rs"

# Deliberately isolated. Do not fold into `main`.
[[test]]
name = "pond_smoke"
path = "tests/pond_smoke.rs"