Adds Reconcile(desired, observed) — the pure decision an operator's
reconcile loop runs: given a desired Agent/Service/Flow resource and the
observed cluster state, it returns the one action to converge (create /
update / noop) plus Ready/Error status conditions.
No controller-runtime, no client-go: the decision is a pure function of
desired + observed, so it's fully unit-testable without a cluster. A
future operator binary supplies Observed from the live cluster and applies
the Action; only that adapter needs the Kubernetes client — keeping the
heavy dependency out of the core module.
Covers #4842 (Option B). Tests: create-when-absent, noop-when-matched-and-
ready, update-on-drift, progressing-when-under-replicated, error-on-invalid
-spec.
Claude-Session: https://claude.ai/code/session_01CmdEY7pYmV5zzwCjNJ4ykL
Co-authored-by: Claude <noreply@anthropic.com>
The CRD manifests were kept in two places — real YAML under config/crd/
(for kubectl apply) and byte-identical const strings in manifests.go
(for the Go CRDManifests map) — which will silently drift.
Make config/crd/*.yaml the single source of truth and go:embed it;
CRDManifests now reads the embedded bytes. Drops ~120 lines of
duplicated YAML, no behavior change (still stdlib-only, tests unchanged).
Claude-Session: https://claude.ai/code/session_01CmdEY7pYmV5zzwCjNJ4ykL
Co-authored-by: Claude <noreply@anthropic.com>