polyc-controller 2026.9.0

Conversation CRD + kube reconciler for the polychrome control plane.
[package]
name = "polyc-controller"
version.workspace = true
description = "Conversation CRD + kube reconciler for the polychrome control plane."
edition.workspace = true
license.workspace = true
authors.workspace = true
rust-version.workspace = true
repository.workspace = true

[package.metadata.polychrome]
# Software-system layer — see docs/architecture/. A Component composed
# into Containers; depends inward only (never on a Container).
layer = "component"
# Logical plane role in the separated state, projection, and query topology
# (#1565, chunk A1) — a different question than `layer` above. The canonical
# policy is arch-capabilities.toml; scripts/check_plane_capabilities.py checks it.
plane_role = "shared"

[dependencies]
polyc-k8s-types = { version = "=2026.9.0", path = "../k8s-types" }
# ToolService health checks dial the connector's MCP endpoint via the shared
# streamable-HTTP client. `polychrome-tools` does NOT depend on this crate, so
# this path dep introduces no dependency cycle.
polyc-tools = { version = "=2026.9.0", path = "../tools" }
# For the `ToolExecutor` trait, whose `specs()` reads the connector's cached
# tool catalogue. `polychrome-agent` does NOT depend on this crate (no cycle).
polyc-agent = { version = "=2026.9.0", path = "../agent" }
# For `ToolSpec` — the shape `ToolExecutor::specs()` returns, mapped into the
# reflected `ToolDescriptor` catalog. Already in the graph via agent/tools.
polyc-llm = { version = "=2026.9.0", path = "../llm" }
async-trait.workspace = true
# `control_plane` helpers return `anyhow::Result` — the shared shape both the CLI
# (`polychrome send`) and the TUI cockpit already consume for pod resolution.
anyhow.workspace = true
kube = { workspace = true, features = ["client", "runtime", "derive", "rustls-tls"] }
k8s-openapi.workspace = true
serde.workspace = true
serde_json.workspace = true
# `crdgen`'s CRD emission (`src/bin/crdgen.rs`) — kubectl sniffs a leading `{`
# and treats an entire JSON-documents-joined-by-`---` stream as one JSON blob
# (#1233), so the committed `manifests/crds/crds.yaml` needs real YAML
# documents. The same maintained fork `polyc-control-plane`/`polyc-harness`/
# `polyc-runtime` already pin crate-local (`serde_yaml` is deprecated
# upstream); kept crate-local here too rather than promoted to
# `[workspace.dependencies]`, matching those crates' precedent.
serde_yaml_ng = "0.10"
schemars.workspace = true
thiserror.workspace = true
tracing.workspace = true
futures.workspace = true
prometheus.workspace = true
# `tokio::join!` to drive the Conversation and ToolService controllers
# concurrently from the single `run` entrypoint. Only the macro is needed here
# (the binary owns the runtime), so no runtime feature is pulled in.
# `time` for the ServiceDefinition pre-flight retry sleep (the watch stays
# dormant until the apps namespace/RBAC is provisioned).
tokio = { workspace = true, features = ["macros", "time"] }
# RFC3339 timestamps for the ToolService status `lastCheckedAt`, plus parsing
# them back to compute health-check staleness. Already in the dependency graph
# (k8s-openapi's `Time` wraps `chrono::DateTime`); pinned to the locked 0.4
# line. `plan` stays pure by taking "now" as an argument — chrono is only used
# to format/parse the timestamp string.
chrono = { version = "0.4", default-features = false, features = ["clock", "std"] }
# Standard five-field cron-expression validation for `RoutineSpec.schedule`
# (`Routine`'s pure spec validator) AND the actual next-fire prediction
# (`routine_next_fire`, #1491) the in-process scheduler, the confirmation
# card, and the inspector all share (INV-RL10). Kept crate-local — no other
# crate parses cron expressions, so this is not promoted to
# `[workspace.dependencies]`.
saffron = "0.1.0"
# IANA zone-name validation for `RoutineSpec.timezone` (`Routine`'s pure spec
# validator), and the wall-clock composition `routine_next_fire` needs to turn
# a `cron` schedule's local time into real UTC instants (the "fake UTC" trick
# documented on that module) — handling a nonexistent (spring-forward) or
# ambiguous (fall-back) local time via `LocalResult`. Kept crate-local — no
# other crate validates zone names, so this is not promoted to
# `[workspace.dependencies]`.
chrono-tz = "0.10"
# `CancellationToken` for `fanout::await_watchable_cancellable` (#1370 review
# finding 3) — the in-process routine scheduler's own shutdown must be able
# to unwind its watch preflight, not just tests' use of the same type below.
tokio-util = { workspace = true }

[dev-dependencies]
# `test-util` backs `fanout::tests`' `#[tokio::test(start_paused = true)]`
# (review finding 3's cancellation-during-retry-sleep test), mirroring
# `polyc-control-plane`'s own precedent for the same attribute.
tokio = { workspace = true, features = ["macros", "rt", "rt-multi-thread", "net", "time", "test-util"] }
# Fake `kube::Client` for the `RollHarness` concurrency-cap test (a hand-rolled
# `tower::service_fn` + `kube::Client::new`, the same pattern
# `polyc-control-plane`'s `lease.rs` already uses for `Api<Lease>` — see that
# module's `fakes::fake_client`). Test-only: production never constructs a
# `tower::Service` directly.
tower = { version = "0.5", features = ["util"] }
http = { workspace = true }
# Seam-3 fixture (tests/toolservice_catalog.rs): stand up an in-process MCP
# server so `health_check` reflects a real `list_tools` (schema + annotations)
# into the status catalog. Mirrors the `polyc-tools` round-trip fixture.
rmcp = { version = "3", features = [
    "server",
    "transport-streamable-http-server",
] }
axum = { workspace = true }
serde_json.workspace = true
# e2e only: pin a rustls crypto provider so the kube client's TLS can pick one
# deterministically (rustls 0.23 refuses to auto-select when several are present).
rustls = { version = "0.23", default-features = false, features = ["ring"] }

[features]
# Live-cluster e2e tests (tests/e2e.rs). Off by default and never run in CI,
# which is Linux + cluster-less; run locally against the current kube context
# with `cargo test -p polyc-controller --features e2e`.
e2e = []

[lints]
workspace = true