Deploying with no site registered hangs in Deploying instead of raising an error

What happens

Deploying a workflow on an engine with no site registered (no worker chart installed) leaves the deployment stuck in Deploying forever. Nothing in the UI says why, and the deployment never fails — it just never progresses.

Reproduced on a local install (docker-compose + ryax-engine 26.9.0-rc4, no ryax-worker-k8s release, so site is empty), following the README "Test locally with Kubernetes in Docker" flow but skipping the worker step.

Why

BaseScheduler.schedule() reads the sites and hands them to the placement policy:

# core/ryax/runner/application/scheduler.py:69
with self.uow_factory() as uow:
    pending_connections = uow.execution_repository.list_pending()
    sites = uow.site_repository.list()          # [] when no worker is registered
...
sched_result = await self._place(pending_connections, sites)

InternalScheduler._place() only turns a placement failure into a user-visible error for two exception types:

# core/ryax/runner/application/internal_scheduler.py:65
try:
    node_pool = self.site_placement_service.schedule_one(sites, connection)
    allocations.append((connection.id, node_pool.id))
except (NoResourcesFoundWithConstraints, NotEnoughResourcesException) as err:
    errors.append((connection.id, f"Unable to schedule the Execution ..."))

With sites == [], _resolve_candidate_node_pools() yields no candidates and the execution produces neither an allocation nor an error, so it stays pending and is retried on every scheduling round indefinitely. "No sites at all" is a different condition from "no site matches the constraints", and it is not reported as either.

The empty-site case also breaks the metrics path, which assumes a site was always chosen:

File "ryax/runner/application/handlers/execution_handlers.py", line 585,
  in on_demand_complete_execution_metric
    assert execution.site_id is not None
AssertionError

That fires from the on_error retry chain (execution_handlers.py:247, "proceeding with retry chain anyway"), so it is a symptom rather than the cause — but a bare assert on operational state will keep producing noisy tracebacks whenever site_id is unset.

Expected

Deploying with no usable site should fail fast with a message the user can act on — something like "No site is registered: install a worker chart before deploying" — surfaced on the deployment in the UI, rather than hanging in Deploying.

Suggested fix

  • In schedule() or _place(), treat an empty site list (and an empty candidate node-pool list) as an explicit scheduling error, so it flows through the existing ExecutionErroredEvent path already used for NoResourcesFoundWithConstraints.
  • Distinguish the two cases in the message: no site registered at all vs no site satisfies the constraints. The first is a setup problem, the second a capacity/constraint problem.
  • Replace the assert execution.site_id is not None at execution_handlers.py:585 with a guarded early return, so an execution that never got a site does not raise inside the error handler.

Notes

This is most likely to hit new users: the README's local-install flow installs the engine first and the worker afterwards, so there is a window where the platform is up, the UI is usable, and deploying silently hangs.