- pkg/service/redisstore.go: after the retry loop, return an error wrapping redis.TxFailedErr instead of nil. CreateIngress now fails instead of returning an ingress that was never written, StoreIngress stops before creating the orphan state key, and a dropped UpdateIngressState reaches the caller's existing error log.
- pkg/service/ingress.go: the Fix nil pointer dereference in IOInfoService.UpdateIngressState on concurrent DeleteIngress #4806 guard applied to the two Twirp handlers, treating a nil State as empty. UpdateIngress then applies and stores the request; DeleteIngress deletes without calling the ingress service.
- pkg/service/redisstore.go: UpdateIngress now fails with ErrIngressNotFound when the ingress no longer exists, checked inside the WATCH on the ingress hash so a later delete fails the EXEC. Without it an update racing a DeleteIngress recreated the deleted ingress and its stream key; this was possible before this PR whenever the delete landed after LoadIngress, and the guard in 2 widened the window to the gap between its two reads.
The guard stays at the call sites to match #4806 and keep API responses unchanged.