Files
harmony/iot
Jean-Gabriel Gill-Couture d21bdef050
Some checks are pending
Run Check Script / check (pull_request) Waiting to run
feat(iot-operator): CEL-validate score.type as a Rust identifier
The CRD previously accepted any string for `score.type`, so typos like
`"pdoman"` or `"PodmnV0"` would be persisted by the apiserver and only
surface on-device as agent-side deserialize warnings. That class of
failure is distasteful and hard to debug.

Replace the auto-derived schema for `ScorePayload` with a hand-rolled
one that keeps the same visible shape but adds two apiserver-level
guardrails:

- `score.type` gets `minLength: 1` and an `x-kubernetes-validations`
  CEL rule requiring it to match `^[A-Za-z_][A-Za-z0-9_]*$` — a valid
  Rust identifier, since score variants *are* Rust struct names in
  `harmony::modules::podman::IotScore`. Message points operators at
  the concrete example `PodmanV0`.
- `score.data` still carries only `x-kubernetes-preserve-unknown-
  fields: true`. The rule validates the discriminator's *shape*, not
  its *value*, so v0.3+ variants (OkdApplyV0, KubectlApplyV0) don't
  require an operator release — preserves ROADMAP §6.1's
  generic-router design.

The `x-kubernetes-preserve-unknown-fields` extension stays scoped to
`score.data` alone; every other field in the CRD has a strict schema,
exactly one preserve-unknown-fields marker and exactly one
validations block in the whole document.

Smoke test extended: phase 2b applies a CR with `score.type: "has
spaces"` and asserts the apiserver rejects it with the CEL message
before the operator ever sees it. Positive phases (kubectl apply ->
NATS KV put -> status observed -> delete -> KV key removed) still
PASS end-to-end.

Matches the `preserve_arbitrary` pattern used by ArgoCD
(`Application.spec.source.helm.valuesObject`) and Flux
(`HelmRelease.spec.values`), both of which similarly use narrow
preserve-unknown-fields on a payload field without coupling the CRD
to their variant catalog.
2026-04-18 10:35:59 -04:00
..