When catalyst-api restarts and the bridge re-attaches to an already-
converged child cluster, the informer initial-list returns HRs already
in Ready=True. The previous processEvent path relied implicitly on the
zero-value of w.states[componentID] (empty string) being different
from the derived state — which works today but would silently regress
if a future refactor pre-seeded w.states from a prior snapshot.
Caught on prov t112.omani.works (f2e7f02e6ffb6a18, 2026-05-15): 4 HRs
converged across primary + sin-2 regions before/after the pod restart
at 19:16, but the mothership Jobs API kept reporting:
install-self-sovereign-cutover → running (kubectl: Ready=True)
install-powerdns → running (kubectl: Ready=True)
install-catalyst-platform → running (kubectl: Ready=True)
install-sin-2:reloader → failed (kubectl: Ready=True)
D6 (0 pending / 0 running) and D7 (mothership ≡ child) both failed.
Fix shape: processEvent's emission policy is now EXPLICITLY "first
observation OR real transition". `hadPrev` (the two-return-value map
lookup) is false on the FIRST event for componentID regardless of the
state value, so the dispatch fires unconditionally on attach. The
dedupe via prev != state still suppresses sub-second status-patch
churn that helm-controller's observedGeneration touches produce.
Idempotency: the jobs.Bridge's lastState map dedupes (componentID,
state) re-emissions at the bridge layer (Bridge.OnHelmReleaseEvent
line ~478), and the openova-flow-server's TypeSnapshot envelope is
idempotent at the receiver — so a re-emit propagated by the
flow_emitter periodic loop is safe.
Two new tests pin the contract:
- TestTransition_AttachTimeReady_EmitsSucceededViaSubscribe asserts
a Watcher attaching to a child cluster with 4 already-Ready HRs
emits exactly one State=installed event per HR, BOTH on the
primary emit callback AND through Subscribe (the bridge wiring).
- TestTransition_FirstObservation_NeverDedupsAcrossWatchers asserts
that constructing a new Watcher against the same fake client
(the Pod-restart shape) re-emits the full component-event set,
because w.states is independent per Watcher.
Co-authored-by: hatiyildiz <hatice.yildiz@openova.io>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>