.. _architecture_operator_overrides: *********************************** Operator Actions: QA wait-for-ready *********************************** The OET supports operator actions: operator interventions made at runtime that control the behaviour of observing scripts. The first use case for this functionality is to give operators control over whether observing scripts wait for the system to be 'QA Ready' before proceeding with a scan. This behaviour is split into two mechanisms: a persistent global state that applies to every script run on that subarray, and a one-shot scan-level override that bypasses the wait for the current scan only. Persistent state ---------------- The :class:`~ska_oso_oet.procedure.application.ScriptExecutionService` holds a :class:`~ska_oso_oet.procedure.application.ScriptContext` instance whose ``wait_for_qa_ready`` attribute is a simple boolean. When ``True`` (the default), the context signals to scripts that they should wait for QA readiness before each scan. This flag is initialised from the ``OET_WAIT_FOR_QA_READY`` environment variable (see ``src/ska_oso_oet/config.py``), defaulting to ``true`` if unset. It can be changed at runtime by an operator and persists across script executions for the lifetime of the OET instance — that is, until the pod restarts. When a script is started, the current flag value is injected into the script's keyword arguments as ``kwargs["context"]["wait_for_qa_ready"]``, so every script receives the up-to-date value at launch time. Scan-level override ------------------- In addition to the persistent flag, an operator can send a one-shot **override** signal. This does not change the persistent state at all — it is a fire-and- forget event published on the ``operator.wait_for_qa_ready.override`` pypubsub topic. Scripts that want to honour it must subscribe to that topic and use the signal to release whatever blocking wait they hold in their scan loop. The OET does not track whether a running script acted on an override. The semantics of "current scan" are defined entirely by the script. Internal event flow ------------------- All operator actions are mediated by pypubsub topics defined in :mod:`~ska_oso_oet.event.topics`: .. list-table:: :widths: 45 55 :header-rows: 1 * - Topic - Purpose * - ``topics.operator.wait_for_qa_ready.override`` - One-shot scan bypass. * - ``topics.operator.wait_for_qa_ready.enable`` - Persistent enable. * - ``topics.operator.wait_for_qa_ready.disable`` - Persistent disable. * - ``topics.request.operator.override.state`` - Request topic used by ``GET /operator-actions``. * - ``topics.operator.override.state`` - Response topic carrying the current :class:`~ska_oso_oet.procedure.application.ScriptContext`. The sequence for a persistent state change is: 1. FastAPIWorker receives the POST and publishes to the relevant ``topics.operator.wait_for_qa_ready.*`` topic. 2. The :class:`~ska_oso_oet.procedure.application.ScriptExecutionService` handler methods (``handle_wait_for_qa_ready_enable``, ``handle_wait_for_qa_ready_disable``) sees these pubsub messages and mutate the value of ``ScriptContext.wait_for_qa_ready`` to match. 3. The next script to start receives the updated value of ``wait_for_qa_ready`` via its ``context`` argument. 4. Already-running scripts can also subscribe to the same topics to react to state changes mid-execution. For a state query (``GET``), the :class:`~ska_oso_oet.main.ScriptExecutionServiceWorker` uses a synchronous request–response pattern over pypubsub: it publishes to ``request.operator.override.state`` and returns the :class:`~ska_oso_oet.procedure.application.ScriptContext` received on ``operator.override.state``. All operator actions are broadcast on the OET SSE stream, so external clients can observe override activity in real time without polling. This allows, for example, the OET UI to reflect the state set by the OST in another session. REST API -------- Operator actions are served under the ``/operator-actions`` endpoint: .. list-table:: :widths: 15 30 55 :header-rows: 1 * - Method - Endpoint - Purpose * - ``GET`` - ``/operator-actions`` - Returns the current :class:`~ska_oso_oet.procedure.application.ScriptContext`. Only the persistent flag is reported; scan-level overrides are not tracked. * - ``POST`` - ``/operator-actions`` - Publishes an operator action. Returns ``202 Accepted`` immediately — the action propagates asynchronously through the event bus. POST body ========= The POST body is ``{"topic": ""}`` where ```` is one of: .. list-table:: :widths: 40 60 :header-rows: 1 * - Topic - Effect * - ``operator.wait_for_qa_ready.enable`` - Set the persistent flag to ``True``. Affects all subsequent scripts. * - ``operator.wait_for_qa_ready.disable`` - Set the persistent flag to ``False``. Affects all subsequent scripts. * - ``operator.wait_for_qa_ready.override`` - One-shot signal to bypass the QA wait for the current scan. The persistent flag is unchanged. Authentication ============== ``GET`` requires the ``ACTIVITY_READ`` scope; ``POST`` requires ``ACTIVITY_EXECUTE``. Both require an authorised role (``SW_ENGINEER``, the telescope operator role, or ``APP2APP``). CLI --- The ``oet wait_for_qa_ready`` subcommand wraps the REST API: .. code-block:: bash # always wait for QA before scanning (persists) oet wait_for_qa_ready enable --subarray_id=1 # never wait for QA before scanning (persists) oet wait_for_qa_ready disable --subarray_id=1 # bypass the QA wait for the current scan only oet wait_for_qa_ready override --subarray_id=1