Files
harmony/iot/iot-agent-v0
Jean-Gabriel Gill-Couture e50ab741fc feat(iot-operator): Deployment CRD controller writing to NATS KV
Implement the A1 task from the IoT walking-skeleton roadmap:

- CRD (kube-derive): `iot.nationtech.io/v1alpha1/Deployment`, namespaced,
  with `targetDevices`, `score {type, data}`, `rollout.strategy`, and a
  status subresource carrying `observedScoreString`.
- Controller: `kube::runtime::Controller` + `finalizer` helper. On Apply,
  writes `<device_id>.<deployment_name>` into NATS KV bucket
  `desired-state` and patches `.status.observedScoreString` via
  server-side apply. Skips KV write + status patch when the score is
  unchanged to avoid reconcile-loop churn. On Cleanup, removes the
  per-device keys before releasing the finalizer.
- CLI: `gen-crd` subcommand prints the CRD YAML from the Rust types;
  `run` (default) starts the controller. `deploy/crd.yaml` is generated
  by that subcommand — single source of truth, no drift.
- Deploy manifests: `deploy/operator.yaml` (Namespace, SA, ClusterRole,
  ClusterRoleBinding, Deployment) and generated `deploy/crd.yaml`.

Agent fixes surfaced while aligning with the operator's key layout:

- Watch filter: was `starts_with("desired-state.<id>.")` on
  `watch_all()`; bucket name is not a key prefix, so it never matched.
  Now uses `bucket.watch("<id>.>")` with the NATS wildcard and handles
  `Put`/`Delete`/`Purge` distinctly.
- Multi-server connect: was joining `nats.urls` with `","` into a single
  malformed URL. Pass the `Vec<String>` to `ConnectOptions::connect`.
- `credentials.type` is now validated (rejects unknown discriminators)
  so a v0.2 `zitadel` config doesn't silently fall back to shared creds.

Verification on feat/iot-walking-skeleton:
- cargo clippy --no-deps -D warnings: clean (agent + operator).
- cargo fmt --check: clean.
- x86_64 + aarch64 cross-compile: both build.
- podman module unit tests: pass.
2026-04-18 09:05:56 -04:00
..