Files
Jean-Gabriel Gill-Couture 8a6a9f1a03 refactor(iot): Deployment.targetSelector + Device CRD (DaemonSet-like)
Kills the "CRD owns a list of device ids" smell. Deployment CR now
carries a standard K8s LabelSelector; Device is a first-class cluster-
scoped CR (like Node). Matching, desired-state KV writes, and status
aggregation all run off selector evaluation against the Device cache
— no list of device ids anywhere in the CRD spec.

Cross-resource model:
- Agent publishes DeviceInfo (with labels) to NATS `device-info` KV.
- device_reconciler watches that bucket → server-side-applies a
  cluster-scoped Device CR with metadata.labels + spec.inventory.
- Deployment controller is now just validation + finalizer cleanup.
- fleet_aggregator watches Deployment CRs + Device CRs + device-state
  KV, maintains in-memory selector → target device sets, writes/deletes
  `desired-state.<device>.<deployment>` KV on match changes, patches
  `.status.aggregate` at 1 Hz with matchedDeviceCount + phase counters.

Applied CRD shape verified on a live k3d cluster:
  kubectl get crd deployments.iot.nationtech.io -o json
    .spec.versions[0].schema.openAPIV3Schema.properties.spec
      → rollout / score / targetSelector (matchLabels + matchExpressions)
    .spec.versions[0].schema.openAPIV3Schema.properties.status.aggregate
      → matchedDeviceCount / succeeded / failed / pending / lastError
  kubectl get crd devices.iot.nationtech.io -o json
    .spec.scope = "Cluster"
    .spec.versions[0].schema.openAPIV3Schema.properties.spec
      → inventory (nullable, camelCased fields)

Load-test run: DEVICES=20 GROUP_SIZES=10,5,5 DURATION=20
  all 3 CRs hit expected matched=N / succeeded+failed+pending=N.

Other changes:
- k8s-openapi gets the `schemars` feature so LabelSelector derives JsonSchema.
- InventorySnapshot uses `#[serde(rename_all = "camelCase")]` for consistency with the rest of the CRD schema.
- agent publishes `device-id=<id>` as a default label so the
  example_iot_apply_deployment `--target-device <id>` shorthand
  works out-of-the-box (implemented as `--selector device-id=<id>`).
- example_iot_apply_deployment gains `--selector key=value` repeatable flag.
- load-test.sh explore banner exposes Device CR commands + new
  matchedDeviceCount column.
2026-04-22 22:55:38 -04:00

23 lines
889 B
TOML

[package]
name = "harmony-reconciler-contracts"
version = "0.1.0"
edition = "2024"
license.workspace = true
# Cross-boundary types shared between a harmony operator (central,
# writing desired state to NATS JetStream KV) and a harmony agent
# (on-host, watching KV and reconciling). Deliberately lean: pure
# serde data types, bucket/key constants, small helpers. No tokio,
# no async-nats, no harmony. The on-device agent build pulls this
# in alongside a minimal async-nats client; the operator pulls it
# alongside kube-rs; harmony itself treats it as just another
# module. None of those consumers should pay for the others' deps.
[dependencies]
chrono = { workspace = true, features = ["serde"] }
harmony_types = { path = "../harmony_types" }
schemars = "0.8.22"
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
thiserror = { workspace = true }