package runtime import ( "context" "errors" "galaxy/backend/internal/dockerclient" "go.uber.org/zap" ) // publishStartConfigInvalid emits the `runtime.start_config_invalid` // admin notification for a pre-Run validation failure on the start / // patch path. The OperationLog supplies the idempotency key so the // catalog UNIQUE(kind, idempotency_key) constraint deduplicates a // repeated retry on the same operation row. func (s *Service) publishStartConfigInvalid(ctx context.Context, op OperationLog, reason string) { s.publishRuntimeEvent(ctx, "runtime.start_config_invalid", op, map[string]any{ "game_id": op.GameID.String(), "reason": reason, }) } // publishStartFailure emits either `runtime.image_pull_failed` or // `runtime.container_start_failed` depending on whether the Docker // daemon reported a pull-stage error. The two kinds carry the catalog // payload from `backend/README.md` ยง10. func (s *Service) publishStartFailure(ctx context.Context, op OperationLog, imageRef string, runErr error) { if errors.Is(runErr, dockerclient.ErrImagePullFailed) { s.publishRuntimeEvent(ctx, "runtime.image_pull_failed", op, map[string]any{ "game_id": op.GameID.String(), "image_ref": imageRef, }) return } s.publishRuntimeEvent(ctx, "runtime.container_start_failed", op, map[string]any{ "game_id": op.GameID.String(), }) } // publishRuntimeEvent threads the publisher call through the package // logger so a misconfigured publisher cannot silently drop events. func (s *Service) publishRuntimeEvent(ctx context.Context, kind string, op OperationLog, payload map[string]any) { if s.deps.Notification == nil { return } idempotencyKey := kind + ":" + op.GameID.String() + ":" + op.OperationID.String() if err := s.deps.Notification.PublishRuntimeEvent(ctx, kind, idempotencyKey, payload); err != nil { s.deps.Logger.Warn("runtime notification publish failed", zap.String("kind", kind), zap.String("idempotency_key", idempotencyKey), zap.Error(err), ) } }