Repository: julien6387/supvisors (language: python)

Issue:
aborted SHUTTING_DOWN
very unlikely in normal operation but possible anyway .
if supvisors sshutdown or sreload is requested from node X and immediately after, a supervisor shutdown is sent to node X, then the stopping sequence of the applications is aborted and the initial shutdown/restart cannot be completed.
that leads the other nodes to eventually re-sync but the situation is a bit strange as there has been some applications properly stopped and there's nothing to be repaired.
this is not blocking but definitely not desired.

easy scenario to repeat:
supervisorctl sshutdown ; supervisorctl shutdown



Discussion / hints:

Fix patch:
diff --git a/CHANGES.rst b/CHANGES.rst
index 1199b15e..296b28f1 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -1,20 +1,72 @@
 Change Log
 ==========
 
+0.7 (2021-08-15)
+----------------
+
+* Fixed `Issue #92 <https://github.com/julien6387/supvisors/issues/92>`_.
+  The *Master* drives the state of all **Supvisors** instances and a simplified state machine has been assigned
+  to non-master **Supvisors** instances. The loss of the *Master* instance is managed in all relevant states.
+
+* Fixed issue about applications that would be started automatically whereas their ``start_sequence`` is 0.
+  The regression has been introduced during the implementation of applications repair in **Supvisors 0.6**.
+
+* Enable stop sequence on *unmanaged* applications.
+
+* In the application navigation part of the Web UI, add a red light to applications having a failure raised.
+
+* New application rules ``distributed`` and ``addresses`` added to the **Supvisors** rules file.
+  Non-distributed applications have all their processes started on the same node chosen in accordance with the
+  ``addresses`` and the ``starting_strategy``.
+
+* Starting strategy added to the application rules.
+
+* Fixed issue when choosing node in ``Starter``. The starting strategies considers the current load of the nodes
+  and includes the requests that have not been satisfied yet.
+
+* Fixed issue with infinite process restart when the process crashes and ``RESTART_PROCESS`` is set on the program
+  in the **Supvisors** rules file. When the process crashes, only the *Supervisor* ``autorestart`` applies.
+  The **Supvisors** ``RESTART_PROCESS`` applies only when the node becomes inactive.
+
+* Fixed exception when forcing the state on a process that is unknown to the local Supervisor.
+
+* Promote the ``RESTART_PROCESS`` into ``RESTART_APPLICATION`` if the application is stopped.
+
+* For the *Master* election, give a priority to nodes declared in the ``forced_synchro_if`` option if used.
+
+* When using the ``forced_synchro_if`` option and when ``auto_fence`` is activated, do not isolate nodes as long as
+  ``synchro_timeout`` has not passed.
+
+* In the ``INITALIZATION`` state, skip the synchronization phase upon notification of a known *Master* and adopt it.
+
+* Add reciprocity to isolation even if ``auto_fence`` is not activated.
+
+* In the process description of the Web UI Application page, add information about the node_name.
+  In particular, it is useful to know where the process was running when it is stopped.
+
+* Start adding use cases to documentation, inspired by real examples.
+
+* Documentation updated.
+
+
 0.6 (2021-08-01)
 ----------------
 
+* Applications that are not declared in the rules file are not *managed*.
+  *Unmanaged* applications have no start/stop sequence, no state and status (always STOPPED) and **Supvisors**
+  does not raise a conflict if multiple instances are running over multiple nodes.
+
 * Improve **Supvisors** stability when dealing with remote programs undefined locally.
 
 * Add expand / shrink actions to applications to the ``ProcAddressView`` of the Web UI.
 
 * Upon authorization of a new node in **Supvisors**, back to ``DEPLOYMENT`` state to repair applications.
 
-* Add RPC ``change_log_level`` to dynamically change the **Supvisors** log level.
+* Add RPC ``change_log_level`` to dynamically change the **Supvisors** logger level.
 
 * Application state is evaluated only against the starting sequence of its processes.
 
-* Fixed blocking when *Master* is stopped while in ``DEPLOYMENT`` state.
+* Fixed blocking issue when *Master* is stopped while in ``DEPLOYMENT`` state.
 
 * Fixed issue with applications that would not fully stop when using the ``STOP_APPLICATION`` starting failure strategy.
 
diff --git a/README.md b/README.md
index eecacb5d..2ae3d1d3 100644
--- a/README.md
+++ b/README.md
@@ -18,10 +18,11 @@ The main features are:
 
 ## Supervisor Enhancements
 
-**Supvisors** proposes a fix to the following Supervisor issues:
+**Supvisors** proposes a contribution to the following Supervisor issues:
    * [#122 - supervisord Starts All Processes at the Same Time](https://github.com/Supervisor/supervisor/issues/122)
    * [#456 - Add the ability to set different "restart policies" on process workers](https://github.com/Supervisor/supervisor/issues/456)
    * [#520 - allow a program to wait for another to stop before being stopped?](https://github.com/Supervisor/supervisor/issues/520)
+   * [#723 - Restart waits for all processes to stop before starting any](https://github.com/Supervisor/supervisor/issues/723)
    * [#874 - Bring down one process when other process gets killed in a group](https://github.com/Supervisor/supervisor/issues/874)
    * [#1023 - Pass arguments to program when starting a job?](https://github.com/Supervisor/supervisor/issues/1023)
    * [#1150 - Why do event listeners not report the process exit status when stopped/crashed?](https://github.com/Supervisor/supervisor/issues/1150)
diff --git a/docs/configuration.rst b/docs/configuration.rst
index d3fcee51..4373a47c 100644
--- a/docs/configuration.rst
+++ b/docs/configuration.rst
@@ -52,8 +52,8 @@ to unpredictable behavior.
 
     .. hint::
 
-        If the `netifaces <https://pypi.python.org/pypi/netifaces>`_ package is installed, it is possible to use IP addresses
-        in addition to node names.
+        If the `netifaces <https://pypi.python.org/pypi/netifaces>`_ package is installed, it is possible to use
+        IP addresses in addition to node names.
 
         Like the node names, the IP addresses are expected to be known to every related systems in the list.
         If it's not the case, check the network configuration.
@@ -137,7 +137,7 @@ to unpredictable behavior.
 ``stats_periods``
 
     The list of periods for which the statistics will be provided in the **Supvisors** :ref:`dashboard`, separated by commas.
-    Up to 3 values are allowed in [``5`` ; ``3600``] seconds, each of them MUST be a multiple of ``5``.
+    Up to 3 values are allowed in [``5`` ; ``3600``] seconds, each of them MUST be a multiple of 5.
 
     *Default*:  ``10``.
 
@@ -310,9 +310,8 @@ Here follows the definition of the attributes and rules applicable to a ``progra
 ``addresses``
 
     This element gives the list of nodes where the process can be started. The node names are to be taken from
-    the ``address_list`` defined in defined in `[supvisors] Section Values`_, and separated by commas.
-    Special values may also be applied.
-    The wildcard ``*`` stands for all node names in ``address_list``.
+    the ``address_list`` defined in `[supvisors] Section Values`_, and separated by commas.
+    Special values can be applied. The wildcard ``*`` stands for all node names in ``address_list``.
     Any node list including a ``*`` is strictly equivalent to ``*`` alone.
     The hashtag ``#`` must be used within a ``pattern`` element and eventually complemented by a list of nodes.
     The aim is to assign the nth node of either ``address_list`` or the subsequent node list to the nth instance
@@ -590,6 +589,57 @@ Here follows the definition of the attributes and rules applicable to an ``appli
 
     *Required*:  Yes.
 
+.. note::
+
+    The applications that are declared in a *Supervisor* group and in this rules file are considered as *Managed* in
+    **Supvisors**. The applications will benefit from **Supvisors** functionalities such as a staged start sequence,
+    an entry in the navigation menu of the Web UI (see :ref:`dashboard_application`), an operational status and a
+    management of the uniqueness of the application programs across the nodes used.
+
+    The applications that are declared in a *Supervisor* group and NOT in this rules file are considered as *Unmanaged*
+    in **Supvisors**. In this case, **Supvisors** will provide only a minimal set of functionalities in the WebUI.
+
+``distributed``
+
+    In the introduction, it is written that the aim of **Supvisors** is to manage distributed applications.
+    However, it may happen that some applications are not designed to be distributed (for example due to inter-process
+    communication choices) and thus distributing the application processes over a set of nodes would just make
+    the application non operational.
+    If set to ``true``, **Supvisors** will start all the application processes on the same node, provided that a node
+    can be found based on the application rules ``starting_strategy`` and ``addresses``.
+
+    *Default*:  ``true``.
+
+    *Required*:  No.
+
+``addresses``
+
+    This element is only used when ``distributed`` is set to ``false`` and gives the list of nodes where the application
+    processes can be started. The node names are to be taken from the ``address_list`` defined in
+    `[supvisors] Section Values`_, and separated by commas.
+    Special values can be applied. The wildcard ``*`` stands for all node names in ``address_list``.
+    Any node list including a ``*`` is strictly equivalent to ``*`` alone. Unlike the process rules ``addresses``,
+    the hashtag ``#`` is NOT valid here.
+
+    *Default*:  ``*``.
+
+    *Required*:  No.
+
+.. note::
+
+    When the application is not to be distributed (``distributed`` set to ``false``), the rule ``addresses`` of the
+    application programs is not considered.
+
+.. hint::
+
+    The ``distributed`` and ``addresses`` elements of the application rules have been introduced in **Supvisors 0.7**.
+    In previous versions, there is a workaround to address non-distributed applications using the starting strategy
+    ``CONFIG`` only and assuming that all processes have the same ``addresses`` definition.
+
+    From **Supvisors 0.7**, it is then possible to start non-distributed applications using ``LESS_LOADED`` or
+    ``MOST_LOADED`` starting strategies.
+
+
 ``start_sequence``
 
     This element gives the starting rank of the application in the ``DEPLOYMENT`` state, when applications are started automatically.
@@ -619,6 +669,16 @@ Here follows the definition of the attributes and rules applicable to an ``appli
 
         It only works when calling **Supvisors**' ``restart`` or ``shutdown`` XML-RPC.
 
+``starting_strategy``
+
+    The strategy used to start applications on nodes.
+    Possible values are in { ``CONFIG``, ``LESS_LOADED``, ``MOST_LOADED``, ``LOCAL`` }.
+    The use of this option is detailed in :ref:`starting_strategy`.
+
+    *Default*:  the value set (or defaulted) in the :ref:`supvisors_section` of the Supervisor configuration file.
+
+    *Required*:  No.
+
 ``starting_failure_strategy``
 
     This element gives the strategy applied upon a major failure in the starting phase of an application.
@@ -693,7 +753,7 @@ Here follows a complete example of rules files. It is used in **Supvisors** self
             <expected_loading>25</expected_loading>
         </model>
 
-        <!-- starter checking application -->
+        <!-- complex test application -->
         <application name="test">
             <start_sequence>1</start_sequence>
             <stop_sequence>4</stop_sequence>
@@ -704,10 +764,6 @@ Here follows a complete example of rules files. It is used in **Supvisors** self
                 <expected_loading>1</expected_loading>
             </program>
 
-            <pattern name="check_">
-                <addresses>cliche81</addresses>
-            </pattern>
-
         </application>
 
         <!-- import application -->
@@ -760,6 +816,7 @@ Here follows a complete example of rules files. It is used in **Supvisors** self
         <application name="my_movies">
             <start_sequence>4</start_sequence>
             <stop_sequence>2</stop_sequence>
+            <starting_strategy>CONFIG</starting_strategy>
             <starting_failure_strategy>CONTINUE</starting_failure_strategy>
 
             <program name="manager">
@@ -821,11 +878,13 @@ Here follows a complete example of rules files. It is used in **Supvisors** self
 
         <!-- player application -->
         <application name="player">
+            <distributed>false</distributed>
+            <addresses>cliche81,cliche83</addresses>
             <start_sequence>5</start_sequence>
+            <starting_strategy>LESS_LOADED</starting_strategy>
             <starting_failure_strategy>ABORT</starting_failure_strategy>
 
             <program name="test_reader">
-                <addresses>cliche81</addresses>
                 <start_sequence>1</start_sequence>
                 <required>true</required>
                 <wait_exit>true</wait_exit>
@@ -833,7 +892,6 @@ Here follows a complete example of rules files. It is used in **Supvisors** self
             </program>
 
             <program name="movie_player">
-                <addresses>cliche81</addresses>
                 <start_sequence>2</start_sequence>
                 <expected_loading>13</expected_loading>
             </program>
@@ -844,6 +902,7 @@ Here follows a complete example of rules files. It is used in **Supvisors** self
         <application name="web_movies">
             <start_sequence>6</start_sequence>
             <stop_sequence>1</stop_sequence>
+            <starting_strategy>MOST_LOADED</starting_strategy>
 
             <program name="web_browser">
                 <addresses>*</addresses>
@@ -854,8 +913,7 @@ Here follows a complete example of rules files. It is used in **Supvisors** self
 
         </application>
 
-
-        <!-- web_movies application -->
+        <!-- disk_reader_81 application -->
         <application name="disk_reader_81">
             <start_sequence>1</start_sequence>
         </application>
diff --git a/docs/dashboard.rst b/docs/dashboard.rst
index 26a324e4..d62038a9 100644
--- a/docs/dashboard.rst
+++ b/docs/dashboard.rst
@@ -8,7 +8,7 @@ and the **Supvisors** extension provides its own web user interface, as a replac
 
 .. note:: *About the browser compliance*.
 
-    The CSS of the web pages has been written for Firefox ESR 60.3.0.
+    The CSS of the web pages has been written for Firefox ESR 78.5.0.
     The compatibility with other browsers or other versions of Firefox is unknown.
 
 All pages are divided into 3 parts:
@@ -40,7 +40,7 @@ The color gives the state of the Address, as seen by the **Supvisors** instance
 Only the hyperlinks of the ``RUNNING`` nodes are active. The browser is redirected to the `Address page`_ of the corresponding Web Server.
 The **Supvisors** instance playing the role of "Master" is pointed out with the ✪ sign.
 
-Below is the Application part that lists all the *managed* applications defined through the
+Below is the Application part that lists all the *Managed* applications defined through the
 `group sections <http://supervisord.org/configuration.html#group-x-section-settings>`_ of the Supervisor configuration file
 and also declared in the **Supvisors** :ref:`rules_file`.
 The color gives the state of the Application, as seen by the **Supvisors** instance that is displaying this page:
@@ -51,10 +51,12 @@ The color gives the state of the Application, as seen by the **Supvisors** insta
     * green-to-yellow gradient for ``STOPPING``,
     * green for ``RUNNING``.
 
+An additional red light is displayed in the event where a failure has been raised on the application.
 All hyperlinks are active. The browser is redirected to the corresponding `Application page`_ on the local Web Server.
 
 The bottom part of the menu contains a contact link and copyright information.
 
+.. _dashboard_main:
 
 Main Page
 ---------
@@ -236,16 +238,26 @@ description and enables the user to perform some actions on them:
     * Tail stdout log (auto-refreshed),
     * Tail stderr log (auto-refreshed).
 
-**Supvisors** also provides expand / shrink actions per application to enable the user to show / hide blocks of processes.
-
 **Supvisors** shows additional information for each process, such as:
 
     * the loading declared for the process in the rules file,
     * the CPU usage of the process during the last period (only if the process is ``RUNNING``),
     * the instant memory (Resident Set Size) occupation of the process at the last period tick (only if the process is ``RUNNING``),
 
-A click on the CPU or RAM measures shows detailed statistics about the process.
-More particularly, **Supvisors** displays a table showing for both CPU and Memory:
+All processes are grouped by their application and **Supvisors** provides expand / shrink actions per application
+to enable the user to show / hide blocks of processes.
+The application line displays:
+
+    * the overall state of the application, considering all nodes,
+    * a description of the operational status of the application,
+    * considering the application processes that are running on this node:
+
+        * the sum of their expected loading,
+        * the sum of their CPU usage,
+        * the sum of their instant memory occupation.
+
+A click on the CPU or RAM measures shows detailed statistics about the process. This is not active on the application values.
+More particularly, **Supvisors** displays on the right side of the page a table showing for both CPU and Memory:
 
     * the last measure,
     * the mean value,
@@ -283,6 +295,8 @@ The Network table shows statistics about the receive and sent flows on each netw
 Clicking on a button associated to the resource displays detailed statistics (graph and table), similarly to the process buttons.
 
 
+.. _dashboard_application:
+
 Application Page
 ----------------
 
diff --git a/docs/images/supvisors_scenario_1.png b/docs/images/supvisors_scenario_1.png
new file mode 100644
index 00000000..d1227a5c
Binary files /dev/null and b/docs/images/supvisors_scenario_1.png differ
diff --git a/docs/index.rst b/docs/index.rst
index 8867a291..dd1d3e2f 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -15,6 +15,12 @@
     event_interface.rst
     special.rst
 
+.. toctree::
+    :maxdepth: 2
+    :caption: Use Cases
+
+    scenario_1.rst
+
 .. toctree::
     :maxdepth: 2
     :caption: Release History
diff --git a/docs/introduction.rst b/docs/introduction.rst
index 351272a9..18ef5309 100644
--- a/docs/introduction.rst
+++ b/docs/introduction.rst
@@ -6,16 +6,17 @@ Overview
 
 **Supvisors** is a control system for distributed applications over multiple Supervisor_ instances.
 
-This piece of software is born from a common need in embedded systems where applications are distributed over several nodes.
+This piece of software was born from a common need in embedded systems where applications are distributed over several
+nodes.
 
 This problematic comes with the following challenges:
 
-    * have a detailed status of the application,
-    * have basic statistics about the resources taken by the application,
+    * have a detailed status of the applications,
+    * have basic statistics about the resources taken by the applications,
     * have a basic status of the nodes,
     * start / stop processes dynamically,
-    * distribute the same application according to different platforms,
-    * control the application from outside of the platform,
+    * distribute the same application over different platforms,
+    * control the applications from nodes outside of the platform,
     * secure the control / status interfaces,
     * deal with loading (CPU, memory, etc),
     * deal with failures:
@@ -24,17 +25,23 @@ This problematic comes with the following challenges:
         + crash of a process,
         + crash of a node.
 
-Icing on the cake:
+As a bonus:
 
     * it should be free, open source, without export control,
     * it shouldn't require specific administration rights (root).
 
-After some researches on the net, it seems that there is no simple, free and open source solution that meets all these requirements.
-Supervisor_ can handle a part of these requirements but it only works on a single UNIX-like operating system.
+After some researches on the net - at the time -, it seemed that there was no simple, free and open source solution
+meeting all these requirements. Of course, there are now orchestration solutions like Kubernetes, Docker Swarm, Mesos,
+etc. coming with more or less complexity, some working on containers only.
 
+Supervisor_ can handle a part of the requirements but it only works on a single UNIX-like operating system.
 The Supervisor website references some `third parties <http://supervisord.org/plugins.html>`_
-that deal with multiple Supervisor instances but they only consist in dashboards and none of them is focused on the application itself.
-Nevertheless, the extensibility of Supervisor made it possible to implement the missing requirements.
+that deal with multiple Supervisor instances but they only consist in dashboards and they focus on the nodes rather than
+on the applications and their possible distribution over nodes.
+Nevertheless, the extensibility of Supervisor makes it possible to implement the missing requirements.
+
+**Supvisors** works as a Supervisor plugin and is intended for those who are already familiar with Supervisor or
+who have neither the time nor the resources to invest in a complex orchestration tool.
 
 In this documentation, a *Supvisors instance* refers to a *Supervisor instance* including a **Supvisors** extension.
 
@@ -64,7 +71,7 @@ Installation
 +===============+============+=================================================================+
 | Supervisor_   | 4.2.1      | Base software, extended by **Supvisors**                        |
 +---------------+------------+-----------------------------------------------------------------+
-| PyZMQ_        | 20.0.0     | Python binding of ZeroMQ                                        |
+| PyZMQ_        | 22.0.3     | Python binding of ZeroMQ                                        |
 +---------------+------------+-----------------------------------------------------------------+
 | psutil_       | 5.7.3      | *Information about processes and system utilization (optional)* |
 +---------------+------------+-----------------------------------------------------------------+
@@ -117,13 +124,14 @@ Install **Supvisors** with the following command:
 Running **Supvisors**
 ---------------------
 
-**Supvisors** runs as a plugin of Supervisor so it follows the same principle as `Running Supervisor <http://supervisord.org/running.html>`_
-but using multiple UNIX-like operating systems.
+**Supvisors** runs as a plugin of Supervisor so it follows the same principle as
+`Running Supervisor <http://supervisord.org/running.html>`_ but using multiple UNIX-like operating systems.
 
 However, the Supervisor configuration file **MUST**:
 
-    * be configured with an internet socket (refer to the `inet-http-server <http://supervisord.org/configuration.html#inet-http-server-section-settings>`_ section settings),
-    * include the :command:`[supvisors]` section (refer to the :ref:`Configuration` part).
+    * be configured with an internet socket (refer to the `inet-http-server <http://supervisord.org/configuration.html#inet-http-server-section-settings>`_ section settings) ;
+    * include the :command:`[supvisors]` section (refer to the :ref:`Configuration` part) ;
+    * be identical on all considered nodes.
 
 .. note::
 
diff --git a/docs/scenario_1.rst b/docs/scenario_1.rst
new file mode 120000
index 00000000..3a7bce88
--- /dev/null
+++ b/docs/scenario_1.rst
@@ -0,0 +1,1 @@
+../supvisors/test/use_cases/scenario_1/scenario_1.rst
\ No newline at end of file
diff --git a/docs/special.rst b/docs/special.rst
index f637dedb..05f3bdd9 100644
--- a/docs/special.rst
+++ b/docs/special.rst
@@ -6,62 +6,76 @@ Special Features
 Synchronizing **Supvisors** instances
 -------------------------------------
 
-The ``INITIALIZATION`` state of **Supvisors** is used as a synchronization phase so that all **Supvisors** instances are mutually aware of each other.
+The ``INITIALIZATION`` state of **Supvisors** is used as a synchronization phase so that all **Supvisors** instances
+are mutually aware of each other.
 
 The following options defined in the :ref:`supvisors_section` of the Supervisor configuration file are particularly
-used for synchronizing multiple instances of Supervisor:
+used for synchronizing multiple instances of *Supervisor*:
 
-    * the ``address_list``,
-    * the ``internal_port``,
-    * the ``synchro_timeout``,
-    * the ``auto_fence``.
+    * ``address_list``,
+    * ``force_synchro_if``,
+    * ``internal_port``,
+    * ``synchro_timeout``,
+    * ``auto_fence``.
 
 Once started, all **Supvisors** instances publish the events received, especially the ``TICK`` events that are
-triggered every 5 seconds, on their ``PUBLISH`` ZeroMQ socket bound on the ``internal_port``.
+triggered every 5 seconds, on their ``PUBLISH`` *ZeroMQ* socket bound on the ``internal_port``.
 
 On the other side, all **Supvisors** instances start a thread that subscribes to the internal events
-through an internal ``SUBSCRIBE`` ZeroMQ socket connected to the ``internal_port`` of **all** nodes of the ``address_list``.
+through an internal ``SUBSCRIBE`` *ZeroMQ* socket connected to the ``internal_port`` of **all** nodes
+of the ``address_list``.
 
-At the beginning, all Address instances are in an ``UNKNOWN`` state.
-When the first ``TICK`` event is received from a remote **Supvisors** instance, the local **Supvisors** instance:
+At the beginning, all nodes are in an ``UNKNOWN`` state.
+When the first ``TICK`` event is received from a remote **Supvisors** instance, a sort of hand-shake is performed
+between the 2 nodes. The local **Supvisors** instance:
 
-    * sets the remote Address state to ``CHECKING``,
-    * performs a ``supvisors.get_address_info(local_address)`` XML-RPC to the remote **Supvisors** instance, in order to know how it is seen by the remote instance.
+    * sets the remote node state to ``CHECKING``,
+    * performs a couple of XML-RPC to the remote **Supvisors** instance:
+
+        + ``supvisors.get_master_address()`` and ``supvisors.get_supvisors_state()`` in order to know if the remote instance is already in an established state.
+        + ``supvisors.get_address_info(local_address)`` in order to know how the local node is perceived by the remote instance.
 
 At this stage, 2 possibilities:
 
     * the local **Supvisors** instance is seen as ``ISOLATED`` by the remote instance:
 
-        + it sets the remote Address state to ``ISOLATED``,
-        + it disconnects the URL of the remote **Supvisors** instance from the ``SUBSCRIBE`` ZeroMQ socket,
+        + the remote node is then reciprocally set to ``ISOLATED``,
+        + the *URL* of the remote **Supvisors** instance is disconnected from the ``SUBSCRIBE`` *ZeroMQ* socket,
 
     * the local **Supvisors** instance is NOT seen as ``ISOLATED`` by the remote instance:
 
-        + it performs a ``supervisor.getAllProcessInfo()`` XML-RPC to the remote instance,
-        + it loads the processes information into the internal data model,
-        + it sets the remote address state to ``RUNNING``.
+        + a ``supervisor.getAllProcessInfo()`` XML-RPC is requested to the remote instance,
+        + the processes information is loaded into the internal data structure,
+        + the remote node is finally set to ``RUNNING``.
 
 When all **Supvisors** instances are identified as ``RUNNING`` or ``ISOLATED``, the synchronization is completed.
 **Supvisors** then is able to work with the set (or subset) of nodes declared in ``address_list``.
 
-However, it may happen that some **Supvisors** instances do not publish as expected (very late starting,
-no starting at all, system down, network down, etc). Each **Supvisors** instance waits for ``synchro_timeout``
-seconds to give a chance to all other instances to publish. When this delay is exceeded, all the **Supvisors**
-instances that are **not** identified as ``RUNNING`` or ``ISOLATED`` are set to:
+However, it may happen that some **Supvisors** instances do not publish (very late starting, no starting at all,
+system down, network down, etc). Each **Supvisors** instance waits for ``synchro_timeout`` seconds to give a chance
+to all other instances to publish. When this delay is exceeded, all the **Supvisors** instances that are **not**
+identified as ``RUNNING`` or ``ISOLATED`` are set to:
 
     * ``SILENT`` if `Auto-Fencing`_ is **not** activated,
     * ``ISOLATED`` if `Auto-Fencing`_ is activated.
 
-Another possibility is when it is predictable that some nodes may be started later. For example, the pool of nodes may include
-servers that will always be started from the very beginning and workstations that may be started only on demand. In this case,
-it would be a pity to always wait for ``synchro_timeout`` seconds. That's why the ``force_synchro_if`` attribute has been
-introduced so that the synchronization phase is considered completed when a subset of the nodes declared in ``address_list``
-are ``RUNNING``.
+Another possibility is when it is predictable that some nodes may be started later. For example, the pool of nodes
+may include servers that will always be started from the very beginning and workstations that may be started only
+on demand. In this case, it would be a pity to always wait for ``synchro_timeout`` seconds. That's why the
+``force_synchro_if`` attribute has been introduced so that the synchronization phase is considered completed
+when a subset of the nodes declared in ``address_list`` are ``RUNNING``.
+
+Whatever the number of available nodes, **Supvisors** elects a *Master* among the active nodes and enters
+the ``DEPLOYMENT`` state to start automatically the applications.
+By default, the *Master* node is the node having the smallest name among all the active nodes, unless the attribute
+``force_synchro_if`` is used. In the latter case, candidates are taken from this list in priority.
 
-In these 2 cases, **Supvisors** will start to work with a subset of active nodes among those declared in ``address_list``.
+.. note:: *About late nodes*
 
-Whatever the number of available nodes, **Supvisors** elects a *Master* among the active nodes
-and enters the ``DEPLOYMENT`` state to start automatically the applications.
+    Back to this case, here is what happens when a node is started while the others are already in ``OPERATION``.
+    During the hand-shake, the local **Supvisors** instance gets the *Master* identified by the remote **Supvisors**.
+    That confirms that the local node is a late starter and thus the local **Supvisors** instance adopts this *Master*
+    too and skips the synchronization phase.
 
 
 .. _auto_fencing:
@@ -106,12 +120,12 @@ If the network failure is fixed, both sets of **Supvisors** are still running bu
 Extra Arguments
 ----------------
 
-When using Supervisor, colleagues have often asked if it would be possible to add extra arguments to the command
+When using *Supervisor*, colleagues have often asked if it would be possible to add extra arguments to the command
 line of a program without declaring them in the ini file. Indeed, the applicative context is evolving at runtime and it may
 be quite useful to give some information to the new process (options, path, URL of a server, URL of a display, ...),
 especially when dealing with distributed applications.
 
-With Supervisor, it is possible to inform the process with a ``supervisor.sendProcessStdin`` XML-RPC.
+With *Supervisor*, it is possible to inform the process with a ``supervisor.sendProcessStdin`` XML-RPC.
 The first drawback is that it requires to update the source code of an existing program that is already capable of
 reading instructions from its command line. That is not always possible.
 On the other hand, colleagues found the solution so clumsy that they finally preferred to use a dedicated com to configure the process.
@@ -124,14 +138,14 @@ to the command line before the process is started:
 
 .. hint::
 
-    These additional commands are an answer to the following Supervisor request:
+    These additional commands are an answer to the following *Supervisor* request:
 
         * `#1023 - Pass arguments to program when starting a job? <https://github.com/Supervisor/supervisor/issues/1023>`_
 
 .. note::
 
-    The extra arguments of the program are shared by all Supervisor instances.
-    Once used, they are published through a **Supvisors** internal event and are stored directly into the Supervisor
+    The extra arguments of the program are shared by all *Supervisor* instances.
+    Once used, they are published through a **Supvisors** internal event and are stored directly into the *Supervisor*
     internal configuration of the programs.
 
     In other words, considering 2 nodes A and B, a process that is started on node A with extra arguments and
@@ -158,9 +172,10 @@ Choosing a node
 
 The following rules are applicable whatever the chosen strategy:
 
-    * the process must not be already in a *running* state in a broad sense, i.e. ``RUNNING``, ``STARTING`` or ``BACKOFF``,
-    * the chosen node must be ``RUNNING``,
-    * the *loading* of the chosen node must not exceed 100% when adding the ``loading`` of the process to be started.
+    * the process must not be already in a *running* state in a broad sense, i.e. ``RUNNING``, ``STARTING`` or ``BACKOFF`` ;
+    * the program definition must be known to the node ;
+    * the node must be ``RUNNING`` ;
+    * the *loading* of the node must not exceed 100% when adding the ``loading`` of the process to be started.
 
 The *loading* of the chosen node is defined as the sum of the ``loading`` of each process running on this address.
 
@@ -207,7 +222,10 @@ This principle is used for starting a single process using a ``supvisors.start_p
 Starting an application
 ~~~~~~~~~~~~~~~~~~~~~~~
 
-The application start sequence is defined when entering the ``DEPLOYMENT`` state of **Supvisors**.
+The application start sequence is re-evaluated every time a new node becomes active in **Supvisors**. Indeed, as
+explained above, the internal data structure is updated with the programs configured in the *Supervisor* instance
+of the new node and this new data may have an impact on the application start sequence.
+
 It corresponds to a dictionary where:
 
     * the keys correspond to the list of ``start_sequence`` values defined in the program rules of the application,
@@ -215,14 +233,18 @@ It corresponds to a dictionary where:
 
 .. hint::
 
-    The logic applied here is an answer to the following Supervisor unresolved issues:
+    The logic applied here is an answer to the following *Supervisor* unresolved issues:
 
         * `#122 - supervisord Starts All Processes at the Same Time <https://github.com/Supervisor/supervisor/issues/122>`_
         * `#456 - Add the ability to set different "restart policies" on process workers <https://github.com/Supervisor/supervisor/issues/456>`_
 
 .. note::
 
-    The programs having a ``start_sequence`` lower or equal to 0 are not considered, as they are not meant to be autostarted.
+    Only the *Managed* applications can have a start sequence, i.e. only those that are declared in the **Supvisors**
+    :ref:`rules_file`.
+
+    The programs having a ``start_sequence`` lower or equal to 0 are not considered in the start sequence, as they are
+    not meant to be automatically started.
 
 The internal *Starter* of **Supvisors** applies the following algorithm to start an application:
 
@@ -306,25 +328,47 @@ Possible values are:
 Running Failure strategy
 ------------------------
 
-The ``autorestart`` option of Supervisor may be used to restart automatically a process that has crashed or has exited unexpectedly (or not).
-However, when the node itself crashes or becomes unreachable, the other Supervisor instances cannot do anything about that.
+The ``autorestart`` option of *Supervisor* may be used to restart automatically a process that has crashed
+or has exited unexpectedly (or not). However, when the node itself crashes or becomes unreachable,
+the other *Supervisor* instances cannot do anything about that.
 
 **Supvisors** uses the ``running_failure_strategy`` option of the rules file to warm restart a process that was
 running on a node that has crashed, in accordance with the default ``starting_strategy`` set in the
 :ref:`supvisors_section` and with the ``address_list`` program rules set in the :ref:`rules_file`.
 
-This option can be also used to stop or restart the whole application after a process crash.
+This option can be also used to stop or restart the whole application after a process crash. Indeed, it may happen
+that some applications cannot survive if one of their programs is just restarted.
 
 Possible values are:
 
     * ``CONTINUE``: Skip the failure. The application keeps running.
-    * ``RESTART_PROCESS``: Restart the process.
+    * ``RESTART_PROCESS``: Restart the lost process on another node.
     * ``STOP_APPLICATION``: Stop the application.
     * ``RESTART_APPLICATION``: Restart the application.
 
+.. attention::
+
+    The ``RESTART_PROCESS`` is NOT intended to replace the *Supervisor* ``autorestart`` on the local node.
+    Provided a program definition where ``autorestart`` is set to ``false`` in the *Supervisor* configuration file
+    and where the ``running_failure_strategy`` option is set to ``RESTART_PROCESS`` in the **Supvisors** rules file,
+    if the process crashes, **Supvisors** will NOT restart the process.
+
+.. note::
+
+    Given that this option is set on the program rules, program strategies within an application may be incompatible
+    in the event of multiple failures. That's why priorities have been set on this strategy.
+    ``STOP_APPLICATION`` supersedes ``RESTART_APPLICATION``, which itself supersedes ``RESTART_PROCESS`` and finally
+    ``CONTINUE``. So if a program with the ``RESTART_APPLICATION`` option fails at the same time that a program
+    of the same application with the ``STOP_APPLICATION`` option, only the ``STOP_APPLICATION`` will be applied.
+
+    When the ``RESTART_PROCESS`` strategy is evaluated, if the application is fully stopped - supposedly because of the
+    failure -, **Supvisors** will promote the ``RESTART_PROCESS`` into ``RESTART_APPLICATION``. The idea is to benefit
+    from a full start sequence at application level rather than uncorrelated program restarts in the event of multiple
+    failures within the same application.
+
 .. hint::
 
-   The ``STOP_APPLICATION`` strategy provides an answer to the following Supervisor request:
+   The ``STOP_APPLICATION`` strategy provides an answer to the following *Supervisor* request:
 
       * `#874 - Bring down one process when other process gets killed in a group <https://github.com/Supervisor/supervisor/issues/874>`_
 
@@ -343,7 +387,7 @@ Stopping a process
 The internal *Stopper* of **Supvisors** applies the following algorithm to stop a process:
 
 | if process state is ``RUNNING``:
-|     perform a ``supervisor.stopProcess(namespec)`` XML-RPC to the Supervisor instance where the process is running
+|     perform a ``supervisor.stopProcess(namespec)`` XML-RPC to the *Supervisor* instance where the process is running
 |
 
 This single job is considered completed when:
@@ -358,18 +402,30 @@ This principle is used for stopping a single process using a ``supvisors.stop_pr
 Stopping an application
 ~~~~~~~~~~~~~~~~~~~~~~~
 
-The application stop sequence is defined at the beginning the ``DEPLOYMENT`` phase of **Supvisors**.
+The application stop sequence is defined at the same moment than the application start sequence.
 It corresponds to a dictionary where:
 
     * the keys correspond to the list of ``stop_sequence`` values defined in the program rules of the application,
     * the value associated to a key is the list of programs having this key as ``stop_sequence``.
 
+.. note::
+
+    The *Unmanaged* applications do have a stop sequence. All their programs have the default ``stop_sequence``
+    set to ``0``.
+
 .. hint::
 
-    The logic applied here is an answer to the following Supervisor unresolved issue:
+    The logic applied here is an answer to the following *Supervisor* unresolved issue:
 
         * `#520 - allow a program to wait for another to stop before being stopped? <https://github.com/Supervisor/supervisor/issues/520>`_
 
+.. hint::
+
+    All the programs sharing the same ``stop_sequence`` are stopped simultaneously, which solves some of the requests
+    described in the following *Supervisor* unresolved issue:
+
+        * `#723 - Restart waits for all processes to stop before starting any <https://github.com/Supervisor/supervisor/issues/723>`_
+
 The internal *Stopper* of **Supvisors** applies the following algorithm to stop an application:
 
 | while application stop sequence is not empty:
@@ -426,7 +482,7 @@ although all of them may have the capability to start it.
 
 Nevertheless, it is still likely to happen in a few cases:
 
-    * using a request to Supervisor itself (through web ui, supervisorctl, XML-RPC),
+    * using a request to *Supervisor* itself (through web ui, supervisorctl, XML-RPC),
     * upon a network failure.
 
 .. attention::
diff --git a/docs/xml_rpc.rst b/docs/xml_rpc.rst
index 8c1c1f26..448c8e88 100644
--- a/docs/xml_rpc.rst
+++ b/docs/xml_rpc.rst
@@ -128,9 +128,12 @@ Status
             Key                         Type            Description
             =========================== =============== ===========
             'application_name'          ``str``         The Application name.
-            'managed'                   ``bool``        The Application managed status in **Supvisors**.
+            'managed'                   ``bool``        The Application managed status in **Supvisors**. When ``False``, the following attributes are not provided.
+            'distributed'               ``bool``        The Application distribution status in **Supvisors**.
+            'addresses'                 ``list(str)``   The list of all nodes where the non-distributed application processes can be started, provided only if ``distributed`` is ``False``.
             'start_sequence'            ``int``         The Application starting rank when starting all applications, in [0;127].
             'stop_sequence'             ``int``         The Application stopping rank when stopping all applications, in [0;127].
+            'starting_strategy'         ``str``         The strategy applied when starting application automatically, in [``'CONFIG'``, ``'LESS_LOADED'``, ``'MOST_LOADED'``, ``'LOCAL'``].
             'starting_failure_strategy' ``str``         The strategy applied when a process crashes in a starting application, in [``'ABORT'``, ``'STOP'``, ``'CONTINUE'``].
             'running_failure_strategy'  ``str``         The strategy applied when a process crashes in a running application, in [``'CONTINUE'``, ``'RESTART_PROCESS'``, ``'STOP_APPLICATION'``, ``'RESTART_APPLICATION'``].
             =========================== =============== ===========
diff --git a/supvisors/address.py b/supvisors/address.py
index fc7e95cd..2b5b56e1 100644
--- a/supvisors/address.py
+++ b/supvisors/address.py
@@ -84,7 +84,7 @@ def serial(self):
                 'statename': self.state.name,
                 'remote_time': capped_int(self.remote_time),
                 'local_time': capped_int(self.local_time),
-                'loading': self.get_load()}
+                'loading': self.get_loading()}
 
     # methods
     def inactive(self, current_time: float):
@@ -128,18 +128,20 @@ def pid_processes(self):
                 for process in self.processes.values()
                 if process.pid_running_on(self.address_name)]
 
-    def get_load(self):
-        """ Return the loading of the node, by summing the declared load of the processes running on that node. """
-        node_load = sum(process.rules.expected_load
-                        for process in self.running_processes())
-        self.logger.debug('AddressStatus.get_load: node_name={} node_load={}'.format(self.address_name, node_load))
+    def get_loading(self) -> int:
+        """ Return the loading of the node, by summing the declared load of the processes running on that node.
+
+        :return: the total loading
+        """
+        node_load = sum(process.rules.expected_load for process in self.running_processes())
+        self.logger.debug('AddressStatus.get_loading: node_name={} node_load={}'.format(self.address_name, node_load))
         return node_load
 
     # dictionary for transitions
     _Transitions = {AddressStates.UNKNOWN: (AddressStates.CHECKING, AddressStates.ISOLATING, AddressStates.SILENT),
                     AddressStates.CHECKING: (AddressStates.RUNNING, AddressStates.ISOLATING, AddressStates.SILENT),
                     AddressStates.RUNNING: (AddressStates.SILENT, AddressStates.ISOLATING),
-                    AddressStates.SILENT: (AddressStates.CHECKING,),
+                    AddressStates.SILENT: (AddressStates.CHECKING, AddressStates.ISOLATING),
                     AddressStates.ISOLATING: (AddressStates.ISOLATED,),
                     AddressStates.ISOLATED: ()
                     }
diff --git a/supvisors/application.py b/supvisors/application.py
index 23ae10db..379149ae 100644
--- a/supvisors/application.py
+++ b/supvisors/application.py
@@ -17,55 +17,69 @@
 # limitations under the License.
 # ======================================================================
 
-from typing import Dict, List, Sequence
+from typing import Any, Dict, List, Sequence
 
 from supervisor.loggers import Logger
-from supervisor.states import *
+from supervisor.states import ProcessStates, STOPPED_STATES
 
 from .process import ProcessStatus
-from .ttypes import ApplicationStates, Payload, StartingFailureStrategies, RunningFailureStrategies
+from .ttypes import (ApplicationStates, NameList, Payload, StartingStrategies,
+                     StartingFailureStrategies, RunningFailureStrategies)
 
 
 class ApplicationRules(object):
     """ Definition of the rules for starting an application, iaw rules file.
 
     Attributes are:
-        - default: True if not overwritten by rules file
-        - start_sequence: defines the order of this application when starting all the applications in the DEPLOYMENT state (0 means: no automatic start),
-        - stop_sequence: defines the order of this application when stopping all the applications (0 means: immediate stop),
-        - starting_failure_strategy: defines the strategy (in StartingFailureStrategies) to apply when a required process cannot be started during the starting of the application,
-        - running_failure_strategy: defines the default strategy (in RunningFailureStrategies) to apply when a required process crashes when the application is running.
+        - managed: set to True when application rules are found from the rules file;
+        - distributed: set to False if all processes must be running on the same node;
+        - node_names: the nodes where the application can be started (all by default) if not distributed,
+        - start_sequence: defines the order of this application when starting all the applications in the DEPLOYMENT state (0 means: no automatic start);
+        - stop_sequence: defines the order of this application when stopping all the applications (0 means: immediate stop);
+        - starting_strategy: defines the strategy to apply when choosing the node where the process shall be started during the starting of the application;
+        - starting_failure_strategy: defines the strategy to apply when a required process cannot be started during the starting of the application;
+        - running_failure_strategy: defines the default strategy to apply when a required process crashes when the application is running.
     """
 
     def __init__(self) -> None:
         """ Initialization of the attributes. """
-        self.managed = False
-        self.start_sequence = 0
-        self.stop_sequence = 0
-        self.starting_failure_strategy = StartingFailureStrategies.ABORT
-        self.running_failure_strategy = RunningFailureStrategies.CONTINUE
+        self.managed: bool = False
+        self.distributed: bool = True
+        self.node_names: NameList = ['*']
+        self.start_sequence: int = 0
+        self.stop_sequence: int = 0
+        self.starting_strategy: StartingStrategies = StartingStrategies.CONFIG
+        self.starting_failure_strategy: StartingFailureStrategies = StartingFailureStrategies.ABORT
+        self.running_failure_strategy: RunningFailureStrategies = RunningFailureStrategies.CONTINUE
 
     def __str__(self) -> str:
         """ Get the application rules as string.
 
         :return: the printable application rules
         """
-        return 'managed={} start_sequence={} stop_sequence={} starting_failure_strategy={} running_failure_strategy={}' \
-            .format(self.managed, self.start_sequence, self.stop_sequence,
-                    self.starting_failure_strategy.name,
+        return 'managed={} distributed={} node_names={} start_sequence={} stop_sequence={} starting_strategy={}'\
+               ' starting_failure_strategy={} running_failure_strategy={}' \
+            .format(self.managed, self.distributed, self.node_names, self.start_sequence, self.stop_sequence,
+                    self.starting_strategy.name, self.starting_failure_strategy.name,
                     self.running_failure_strategy.name)
 
     # serialization
     def serial(self) -> Payload:
         """ Get a serializable form of the application rules.
+        Do not send not applicable information.
 
         :return: the application rules in a dictionary
         """
-        return {'managed': self.managed,
-                'start_sequence': self.start_sequence,
-                'stop_sequence': self.stop_sequence,
-                'starting_failure_strategy': self.starting_failure_strategy.name,
-                'running_failure_strategy': self.running_failure_strategy.name}
+        if self.managed:
+            payload = {'managed': True, 'distributed': self.distributed,
+                       'start_sequence': self.start_sequence, 'stop_sequence': self.stop_sequence,
+                       'starting_strategy': self.starting_strategy.name,
+                       'starting_failure_strategy': self.starting_failure_strategy.name,
+                       'running_failure_strategy': self.running_failure_strategy.name}
+            if not self.distributed:
+                payload['addresses'] = self.node_names
+            return payload
+        return {'managed': False}
 
 
 class ApplicationStatus(object):
@@ -85,27 +99,29 @@ class ApplicationStatus(object):
     """
 
     # types for annotations
-    ApplicationSequence = Dict[int, List[ProcessStatus]]
+    ProcessList = List[ProcessStatus]
+    ApplicationSequence = Dict[int, ProcessList]
     PrintableApplicationSequence = Dict[int, Sequence[str]]
     ProcessMap = Dict[str, ProcessStatus]
 
-    def __init__(self, application_name: str, rules: ApplicationRules, logger: Logger) -> None:
+    def __init__(self, application_name: str, rules: ApplicationRules, supvisors: Any) -> None:
         """ Initialization of the attributes.
 
         :param application_name: the name of the application
         :param rules: the rules applicable to the application
-        :param logger: the common logger used throughout Supvisors
+        :param supvisors: the global Supvisors structure
         """
         # keep reference to common logger
-        self.logger = logger
+        self.supvisors = supvisors
+        self.logger: Logger = supvisors.logger
         # information part
-        self.application_name = application_name
-        self._state = ApplicationStates.STOPPED
-        self.major_failure = False
-        self.minor_failure = False
+        self.application_name: str = application_name
+        self._state: ApplicationStates = ApplicationStates.STOPPED
+        self.major_failure: bool = False
+        self.minor_failure: bool = False
         # process part
         self.processes: ApplicationStatus.ProcessMap = {}
-        self.rules = rules
+        self.rules: ApplicationRules = rules
         self.start_sequence: ApplicationStatus.ApplicationSequence = {}
         self.stop_sequence: ApplicationStatus.ApplicationSequence = {}
 
@@ -152,6 +168,15 @@ def state(self, new_state: ApplicationStates) -> None:
             self._state = new_state
             self.logger.info('Application.state: {} is {}'.format(self.application_name, self.state.name))
 
+    def has_running_processes(self) -> str:
+        """ Get a description of the operational status of the application.
+
+        :return: the operational status as string
+        """
+        for process in self.processes.values():
+            if process.running():
+                return True
+
     def get_operational_status(self) -> str:
         """ Get a description of the operational status of the application.
 
@@ -194,6 +219,24 @@ def add_process(self, process: ProcessStatus) -> None:
         """
         self.processes[process.process_name] = process
 
+    def possible_nodes(self) -> NameList:
+        """ Return the list of nodes where the application could be started.
+        To achieve that, two conditions:
+            - the Supervisor node must know all the application programs;
+            - the node must be declared in the applicable nodes in the rules file.
+
+        :return: the list of nodes where the program could be started
+        """
+        node_names = self.rules.node_names
+        if '*' in self.rules.node_names:
+            node_names = self.supvisors.address_mapper.node_names
+        # get the nodes common to all application processes
+        actual_nodes = [set(process.info_map.keys()) for process in self.processes.values()]
+        if actual_nodes:
+            actual_nodes = actual_nodes[0].intersection(*actual_nodes)
+        # intersect with rules
+        return [node_name for node_name in node_names if node_name in actual_nodes]
+
     @staticmethod
     def printable_sequence(application_sequence: ApplicationSequence) -> PrintableApplicationSequence:
         """ Get printable application sequence for log traces.
@@ -212,20 +255,40 @@ def update_sequences(self) -> None:
         """
         self.start_sequence.clear()
         self.stop_sequence.clear()
-        # consider only managed applications
+        # consider only managed applications for start sequence
         if self.rules.managed:
             # fill ordering iaw process rules
             for process in self.processes.values():
                 self.start_sequence.setdefault(process.rules.start_sequence, []).append(process)
-                self.stop_sequence.setdefault(process.rules.stop_sequence, []).append(process)
-            self.logger.debug('ApplicationStatus.update_sequences: application_name={}'
-                              ' start_sequence={} stop_sequence={}'
-                              .format(self.application_name,
-                                      self.printable_sequence(self.start_sequence),
-                                      self.printable_sequence(self.stop_sequence)))
+            self.logger.debug('ApplicationStatus.update_sequences: application_name={} start_sequence={}'
+                              .format(self.application_name, self.printable_sequence(self.start_sequence)))
         else:
             self.logger.info('ApplicationStatus.update_sequences: application_name={}'
-                             ' is not managed so sequences are not defined'. format(self.application_name))
+                             ' is not managed so start sequence is undefined'. format(self.application_name))
+        # stop sequence is applicable to all applications
+        for process in self.processes.values():
+            # fill ordering iaw process rules
+            self.stop_sequence.setdefault(process.rules.stop_sequence, []).append(process)
+        self.logger.debug('ApplicationStatus.update_sequences: application_name={} stop_sequence={}'
+                          .format(self.application_name, self.printable_sequence(self.stop_sequence)))
+
+    def get_start_sequenced_processes(self) -> ProcessList:
+        """ Return the processes included in the application start sequence.
+        The sequence 0 is removed as it corresponds to processes that are not meant to be auto-started.
+
+        :return: the processes included in the application start sequence.
+        """
+        return [process for seq, sub_seq in self.start_sequence.items()
+                for process in sub_seq if seq > 0]
+
+    def get_start_sequence_expected_load(self) -> int:
+        """ Return the sum of the expected loading of the processes in the starting sequence.
+        This is used only in the event where the application is not distributed and the whole application loading
+        has to be checked on a single node.
+
+        :return: the expected loading of the application.
+        """
+        return sum(process.rules.expected_load for process in self.get_start_sequenced_processes())
 
     def update_status(self) -> None:
         """ Update the state of the application iaw the state of its sequenced processes.
@@ -240,8 +303,6 @@ def update_status(self) -> None:
         sequenced_processes = [process for sub_seq in self.start_sequence.values()
                                for process in sub_seq]
         if not sequenced_processes:
-            # TODO: check if it is relevant to evaluate the application state in this case,
-            #  knowing that this application could include multiple "conflicts" due to its unmanaged nature
             self.logger.debug('ApplicationStatus.update_status: application_name={}'
                               ' is not managed so always STOPPED'. format(self.application_name))
             self.state = ApplicationStates.STOPPED
diff --git a/supvisors/client/java/org/supvisors/common/SupvisorsApplicationRules.java b/supvisors/client/java/org/supvisors/common/SupvisorsApplicationRules.java
index e06ffb1e..ef52c7de 100644
--- a/supvisors/client/java/org/supvisors/common/SupvisorsApplicationRules.java
+++ b/supvisors/client/java/org/supvisors/common/SupvisorsApplicationRules.java
@@ -33,12 +33,24 @@ public class SupvisorsApplicationRules implements SupvisorsAnyInfo {
     /** The managed status of the application. */
     private Boolean isManaged;
 
+    /** The distribution status of the application. */
+    private Boolean isDistributed;
+
+    /**
+     * The applicable nodes when not application cannot be distributed.
+     * If all known nodes are applicable, '*' is used.
+     */
+    private List nodes;
+
     /** The starting order in the application starting. */
     private Integer startSequence;
 
     /** The stopping order in the application stopping. */
     private Integer stopSequence;
 
+    /** The strategy applied to choose nodes at application starting time. */
+    private StartingStrategy startingStrategy;
+
     /** The strategy applied when a process crashes at application starting time. */
     private StartingFailureStrategy startingFailureStrategy;
 
@@ -53,10 +65,18 @@ public class SupvisorsApplicationRules implements SupvisorsAnyInfo {
     public SupvisorsApplicationRules(HashMap rulesInfo)  {
         this.applicationName = (String) rulesInfo.get("application_name");
         this.isManaged = (Boolean) rulesInfo.get("managed");
-        this.startSequence = (Integer) rulesInfo.get("start_sequence");
-        this.stopSequence = (Integer) rulesInfo.get("stop_sequence");
-        this.startingFailureStrategy = StartingFailureStrategy.valueOf((String) rulesInfo.get("starting_failure_strategy"));
-        this.runningFailureStrategy = RunningFailureStrategy.valueOf((String) rulesInfo.get("running_failure_strategy"));
+        if (this.isManaged) {
+            this.isDistributed = (Boolean) rulesInfo.get("distributed");
+            if (!this.isDistributed) {
+                Object[] nodes = (Object[]) rulesInfo.get("addresses");
+                this.nodes = Arrays.asList(nodes);
+            }
+            this.startSequence = (Integer) rulesInfo.get("start_sequence");
+            this.stopSequence = (Integer) rulesInfo.get("stop_sequence");
+            this.startingStrategy = StartingStrategy.valueOf((String) rulesInfo.get("starting_strategy"));
+            this.startingFailureStrategy = StartingFailureStrategy.valueOf((String) rulesInfo.get("starting_failure_strategy"));
+            this.runningFailureStrategy = RunningFailureStrategy.valueOf((String) rulesInfo.get("running_failure_strategy"));
+        }
     }
 
     /**
@@ -86,6 +106,25 @@ public Boolean isManaged() {
         return this.isManaged;
     }
 
+    /**
+     * The isDistributed method returns the distribution status of the application in Supvisors.
+     *
+     * @return Boolean: The distribution status.
+     */
+    public Boolean isDistributed() {
+        return this.isDistributed;
+    }
+
+    /**
+     * The getNodes method returns the nodes where the application processes can be started,
+     *  when application cannot be distributed.
+     *
+     * @return List: The list of nodes.
+     */
+    public List getNodes() {
+        return this.nodes;
+    }
+
     /**
      * The getStartSequence method returns the starting order of the application when starting all the applications.
      *
@@ -104,11 +143,21 @@ public Integer getStopSequence() {
         return this.stopSequence;
     }
 
+    /**
+     * The getStartingStrategy method returns the strategy applied to choose nodes
+     *  when the application is starting.
+     *
+     * @return StartingStrategy: The strategy.
+     */
+    public StartingStrategy getStartingStrategy() {
+        return this.startingStrategy;
+    }
+
     /**
      * The getStartingFailureStrategy method returns the strategy applied if the process crashes
      * when the application is starting.
      *
-     * @return RunningFailureStrategy: The strategy.
+     * @return StartingFailureStrategy: The strategy.
      */
     public StartingFailureStrategy getStartingFailureStrategy() {
         return this.startingFailureStrategy;
@@ -130,11 +179,20 @@ public RunningFailureStrategy getRunningFailureStrategy() {
      * @return String: The contents of the instance.
      */
     public String toString() {
-        return "SupvisorsApplicationRules(applicationName=" + this.applicationName
-            + " managed=" + this.isManaged
-            + " startSequence=" + this.startSequence + " stopSequence=" + this.stopSequence
-            + " startingFailureStrategy=" + this.startingFailureStrategy
-            + " runningFailureStrategy=" + this.runningFailureStrategy + ")";
+        String rulesString = "SupvisorsApplicationRules(applicationName=" + this.applicationName
+            + " managed=" + this.isManaged;
+        if (this.isManaged) {
+            rulesString += " distributed=" + this.isDistributed;
+            if (!this.isDistributed) {
+                rulesString += " nodes=" + this.nodes;
+            }
+            rulesString += " startSequence=" + this.startSequence + " stopSequence=" + this.stopSequence
+                + " startingStrategy=" + this.startingStrategy
+                + " startingFailureStrategy=" + this.startingFailureStrategy
+                + " runningFailureStrategy=" + this.runningFailureStrategy;
+        }
+        rulesString += ")";
+        return rulesString;
     }
 
 }
diff --git a/supvisors/client/java/org/supvisors/common/SupvisorsProcessRules.java b/supvisors/client/java/org/supvisors/common/SupvisorsProcessRules.java
index ccda6ad0..3533b354 100644
--- a/supvisors/client/java/org/supvisors/common/SupvisorsProcessRules.java
+++ b/supvisors/client/java/org/supvisors/common/SupvisorsProcessRules.java
@@ -37,10 +37,10 @@ public class SupvisorsProcessRules implements SupvisorsAnyInfo {
     private String processName;
 
     /**
-     * The applicable addresses.
-     * If all known addresses are applicable, '*' is used.
+     * The applicable nodes.
+     * If all known nodes are applicable, '*' is used.
      */
-    private List addresses;
+    private List nodes;
 
     /** The starting order in the application starting. */
     private Integer startSequence;
@@ -75,8 +75,8 @@ public SupvisorsProcessRules(HashMap rulesInfo)  {
         this.processName = (String) rulesInfo.get("process_name");
         this.applicationName = (String) rulesInfo.get("application_name");
         this.namespec = DataConversion.stringsToNamespec(this.applicationName, this.processName);
-        Object[] addresses = (Object[]) rulesInfo.get("addresses");
-        this.addresses = Arrays.asList(addresses);
+        Object[] nodes = (Object[]) rulesInfo.get("addresses");
+        this.nodes = Arrays.asList(nodes);
         this.startSequence = (Integer) rulesInfo.get("start_sequence");
         this.stopSequence = (Integer) rulesInfo.get("stop_sequence");
         this.required = (Boolean) rulesInfo.get("required");
@@ -113,12 +113,12 @@ public String getName() {
     }
 
     /**
-     * The getAddresses method returns the addresses where the process can be started.
+     * The getNodes method returns the addresses where the process can be started.
      *
-     * @return List: The list of addresses.
+     * @return List: The list of nodes.
      */
-    public List getAddresses() {
-        return this.addresses;
+    public List getNodes() {
+        return this.nodes;
     }
 
     /**
@@ -183,7 +183,7 @@ public RunningFailureStrategy getRunningFailureStrategy() {
      */
     public String toString() {
         return "SupvisorsProcessrules(namespec=" + this.namespec
-            + " addresses=" + this.addresses + " startSequence=" + this.startSequence
+            + " nodes=" + this.nodes + " startSequence=" + this.startSequence
             + " stopSequence=" + this.stopSequence + " required=" + this.required
             + " waitExit=" + this.waitExit + " expectedLoading=" + this.expectedLoading
             + " runningFailureStrategy=" + this.runningFailureStrategy + ")";
diff --git a/supvisors/client/java/org/supvisors/rpc/SupvisorsXmlRpc.java b/supvisors/client/java/org/supvisors/rpc/SupvisorsXmlRpc.java
index dcf8dbdd..1ca91be7 100644
--- a/supvisors/client/java/org/supvisors/rpc/SupvisorsXmlRpc.java
+++ b/supvisors/client/java/org/supvisors/rpc/SupvisorsXmlRpc.java
@@ -19,6 +19,7 @@
 import java.net.MalformedURLException;
 import java.util.List;
 import java.util.HashMap;
+import java.util.Iterator;
 import org.apache.xmlrpc.XmlRpcException;
 import org.supvisors.common.*;
 
@@ -427,7 +428,7 @@ public static void main (String[] args) throws MalformedURLException, XmlRpcExce
         HashMap<String, SupvisorsApplicationInfo> applications = supvisors.getAllApplicationInfo();
         System.out.println(applications);
         System.out.println("### Testing supvisors.getApplicationInfo(...) ###");
-        String applicationName = applications.entrySet().iterator().next().getValue().getName();
+        String applicationName = applications.entrySet().iterator().next().getKey();
         SupvisorsApplicationInfo applicationInfo = supvisors.getApplicationInfo(applicationName);
         System.out.println(applicationInfo);
 
@@ -452,8 +453,10 @@ public static void main (String[] args) throws MalformedURLException, XmlRpcExce
 
         // test application rules rpc
         System.out.println("### Testing supvisors.getApplicationRules(...) ###");
-        SupvisorsApplicationRules applicationRules = supvisors.getApplicationRules(applicationName);
-        System.out.println(applicationRules);
+        for (String appliName : applications.keySet()) {
+            SupvisorsApplicationRules applicationRules = supvisors.getApplicationRules(appliName);
+            System.out.println(applicationRules);
+        }
 
         // test process rules rpc
         System.out.println("### Testing supvisors.getProcessRules(...) ###");
diff --git a/supvisors/commander.py b/supvisors/commander.py
index fb9cd28b..7edb66e0 100644
--- a/supvisors/commander.py
+++ b/supvisors/commander.py
@@ -19,14 +19,14 @@
 
 import time
 
-from typing import Any, Dict, List, Set
+from typing import Any, Dict, List, Optional, Set
 
 from supervisor.childutils import get_asctime
 from supervisor.states import ProcessStates
 
 from .application import ApplicationStatus
 from .process import ProcessStatus
-from .strategy import get_node
+from .strategy import get_node, LoadRequestMap
 from .ttypes import StartingStrategies, StartingFailureStrategies
 
 
@@ -35,9 +35,11 @@ class ProcessCommand(object):
 
     Attributes are:
         - process: the process wrapped,
-        - strategy: the strategy used to start the process if applicable,
+        - node_name: the node to which the command is requested,
         - request_time: the date when the command is requested,
-        - ignore_wait_exit: used to command a process out of its application,
+        - strategy: the strategy used to start the process if applicable,
+        - distributed: set to False if the process belongs to an application that cannot de distributed,
+        - ignore_wait_exit: used to command a process out of its application starting sequence,
         - extra_args: additional arguments to the command line.
     """
     TIMEOUT = 10
@@ -48,22 +50,25 @@ def __init__(self, process: ProcessStatus, strategy: StartingStrategies = None)
         :param process: the process status to wrap
         :param strategy: the applicable starting strategy
         """
-        self.process = process
-        self.strategy = strategy
-        self.request_time = 0
-        self.ignore_wait_exit = False
-        self.extra_args = ''
+        self.process: ProcessStatus = process
+        self.node_name: Optional[str] = None
+        self.request_time: int = 0
+        # the following attributes are only for Starter
+        self.strategy: StartingStrategies = strategy
+        self.distributed: bool = True
+        self.ignore_wait_exit: bool = False
+        self.extra_args: str = ''
 
     def __str__(self) -> str:
         """ Get the process command as string.
 
         :return: the printable process command
         """
-        return 'process={} state={} last_event_time={} strategy={} request_time={} ' \
-               'ignore_wait_exit={} extra_args="{}"' \
+        return 'process={} state={} last_event_time={} requested_node={} request_time={} strategy={}' \
+               ' distributed=True ignore_wait_exit={} extra_args="{}"' \
             .format(self.process.namespec, self.process.state, self.process.last_event_time,
-                    self.strategy.value if self.strategy else 'None',
-                    self.request_time,  self.ignore_wait_exit, self.extra_args)
+                    self.node_name, self.request_time, self.strategy.value if self.strategy else 'None',
+                    self.ignore_wait_exit, self.extra_args)
 
     def timed_out(self, now: int) -> bool:
         """ Return True if there is still no event TIMEOUT seconds past the request.
@@ -138,8 +143,7 @@ def printable_planned_sequence(self) -> PrintablePlannedSequence:
         return {application_sequence: {application_name: {sequence: Commander.printable_command_list(commands)
                                                           for sequence, commands in sequences.items()}
                                        for application_name, sequences in applications.items()}
-                for application_sequence, applications
-                in self.planned_sequence.items()}
+                for application_sequence, applications in self.planned_sequence.items()}
 
     def printable_planned_jobs(self) -> PrintablePlannedJobs:
         """ Simple form of planned_jobs, so that it can be printed.
@@ -178,7 +182,7 @@ def get_process_command(process: ProcessStatus, jobs: CommandList) -> ProcessCom
         return next((command for command in jobs if command.process is process), None)
 
     def trigger_jobs(self) -> None:
-        """ Triggers a sequence of the jobs planned (start or stop).
+        """ Triggers the next sequence from the jobs planned (start or stop).
 
         :return: None
         """
@@ -193,8 +197,18 @@ def trigger_jobs(self) -> None:
             for application_name in list(self.planned_jobs.keys()):
                 self.logger.info('Commander.trigger_jobs: triggering sequence {} - application_name={}'
                                  .format(min_sequence, application_name))
+                self.prepare_application_jobs(application_name)
                 self.process_application_jobs(application_name)
 
+    def prepare_application_jobs(self, application_name, application: ApplicationStatus = None) -> None:
+        """ Prepare the ProcessCommand instances linked to application planned jobs.
+        Implemented in Starter only.
+
+        :param application_name: the application name
+        :param application: the application if available
+        :return: None
+        """
+
     def process_application_jobs(self, application_name: str) -> None:
         """ Triggers the next sequenced group of the application.
 
@@ -242,7 +256,7 @@ def process_job(self, command: ProcessCommand, jobs: CommandList) -> bool:
         """
         raise NotImplementedError
 
-    def check_progress(self, condition: str, process_state: ProcessStates) -> bool:
+    def is_job_completed(self, condition: str, process_state: ProcessStates) -> bool:
         """ Check the progress of the application starting or stopping.
 
         :param condition: the ProcessStatus method name to be used to check progress
@@ -250,7 +264,7 @@ def check_progress(self, condition: str, process_state: ProcessStates) -> bool:
         :return: True when there is no more pending jobs
         """
         in_progress = self.in_progress()
-        self.logger.debug('Starter.check_progress: in_progress={}'.format(in_progress))
+        self.logger.debug('Starter.is_job_completed: in_progress={}'.format(in_progress))
         if in_progress:
             commands = [command for command_list in self.current_jobs.values()
                         for command in command_list]
@@ -260,18 +274,17 @@ def check_progress(self, condition: str, process_state: ProcessStates) -> bool:
                 # if it doesn't, the whole sequencing would block waiting for an event that may never happen
                 # typically, this can happen if request is sent to a supervisor that is shutting down
                 now = int(time.time())
-                self.logger.trace('Commander.check_progress: now={} checking commands={}'
+                self.logger.trace('Commander.is_job_completed: now={} checking commands={}'
                                   .format(now, [str(command) for command in commands]))
                 for command in commands:
                     # get the ProcessStatus method corresponding to condition and call it
                     class_method = getattr(ProcessStatus, condition)
                     if class_method(command.process):
                         if command.timed_out(now):
-                            self.logger.error('Commander.check_progress: {} still {} after {} seconds so abort'
+                            self.logger.error('Commander.is_job_completed: {} still {} after {} seconds so abort'
                                               .format(command.process.namespec, condition, ProcessCommand.TIMEOUT))
                             # generate a process event for this process to inform all Supvisors instances
                             reason = 'Still {} {} seconds after request'.format(condition, ProcessCommand.TIMEOUT)
-                            # self.supvisors.context.force_process_state(command.process.namespec, process_state, reason)
                             self.supvisors.listener.force_process_state(command.process.namespec, process_state, reason)
                             # don't wait for event, just abort the job
                             jobs = self.current_jobs[command.process.application_name]
@@ -282,13 +295,13 @@ def check_progress(self, condition: str, process_state: ProcessStates) -> bool:
             else:
                 # no commands in the pipe
                 # this can happen when nothing had to be stopped inside the planned_jobs
-                self.logger.warn('Starter.check_progress: no commands in progress but planned sequence still ongoing')
+                self.logger.warn('Starter.is_job_completed: no commands in progress but planned sequence still ongoing')
                 # check if there are planned jobs
                 if not self.planned_jobs:
                     # trigger next sequence of applications
                     self.trigger_jobs()
                 else:
-                    self.logger.critical('Starter.check_progress: UNEXPECTED')
+                    self.logger.critical('Starter.is_job_completed: UNEXPECTED')
         # return True when no more pending jobs
         return not self.in_progress()
 
@@ -335,13 +348,6 @@ def __init__(self, supvisors: Any) -> None:
         # attributes
         self.application_stop_requests = []
 
-    def get_default_strategy(self) -> StartingStrategies:
-        """ Return the default starting strategy from options.
-
-        :return: the starting strategy set in the supvisors section of the supervisor configuration file
-        """
-        return self.supvisors.options.starting_strategy
-
     def abort(self) -> None:
         """ Abort all planned current jobs.
         Do not erase current_jobs. Wait for their completion.
@@ -358,18 +364,16 @@ def start_applications(self) -> None:
 
         :return: None
         """
-        strategy = self.get_default_strategy()
-        self.logger.info('Starter.start_applications: start all applications using strategy {}'.format(strategy.name))
+        self.logger.info('Starter.start_applications: start all applications')
         # internal call: default strategy always used
         for application in self.supvisors.context.applications.values():
             self.logger.debug('Starter.start_applications: application={} start_sequence={} never_started={}'
                               .format(str(application), application.rules.start_sequence, application.never_started()))
             # auto-started applications (start_sequence > 0) are not restarted if they have been stopped intentionally
             # give a chance to all applications in failure
-            if (application.rules.start_sequence > 0 and application.never_started()) \
-                    or application.major_failure or application.minor_failure:
-                # if application has never been started, start all in sequence
-                self.store_application_start_sequence(application, strategy)
+            if application.rules.start_sequence > 0 \
+                    and (application.never_started() or application.major_failure or application.minor_failure):
+                self.store_application_start_sequence(application)
         # start work
         self.trigger_jobs()
 
@@ -379,7 +383,7 @@ def default_start_application(self, application: ApplicationStatus) -> bool:
         :param application: the application to start
         :return: True if start completed (nothing to do actually)
         """
-        return self.start_application(self.get_default_strategy(), application)
+        return self.start_application(application.rules.starting_strategy, application)
 
     def start_application(self, strategy: StartingStrategies, application: ApplicationStatus) -> bool:
         """ Plan and start the necessary jobs to start the application in parameter, with the strategy requested.
@@ -396,19 +400,23 @@ def start_application(self, strategy: StartingStrategies, application: Applicati
             self.logger.debug('Starter.start_application: planned_sequence={}'
                               .format(self.printable_planned_sequence()))
             if self.planned_sequence:
-                # add application immediately to planned jobs if something in list
+                # add application immediately to planned jobs if something already in list
                 self.planned_jobs.update(self.planned_sequence.pop(min(self.planned_sequence.keys())))
+                # prepare commands and trigger application jobs
+                self.prepare_application_jobs(application.application_name, application)
                 self.process_application_jobs(application.application_name)
         # return True when started
         return not self.in_progress()
 
     def default_start_process(self, process: ProcessStatus) -> bool:
         """ Plan and start the necessary job to start the process in parameter, with the default strategy.
+        Default strategy is taken from the application owning this process.
 
         :param process: the process to start
         :return: True if no job has been queued.
         """
-        return self.start_process(self.get_default_strategy(), process)
+        application = self.supvisors.context.applications[process.application_name]
+        return self.start_process(application.rules.starting_strategy, process)
 
     def start_process(self, strategy: StartingStrategies, process: ProcessStatus, extra_args: str = '') -> bool:
         """ Plan and start the necessary job to start the process in parameter, with the strategy requested.
@@ -428,19 +436,19 @@ def start_process(self, strategy: StartingStrategies, process: ProcessStatus, ex
         command.ignore_wait_exit = True
         # push program list in job list and start work
         job = self.current_jobs.setdefault(process.application_name, [])
-        starting = self.process_job(command, job)
+        queued = self.process_job(command, job)
         # upon failure, remove inProgress entry if empty
         if not job:
             del self.current_jobs[process.application_name]
-        # return True when started
-        return not starting
+        # return True when job queued
+        return not queued
 
-    def check_starting(self) -> bool:
+    def is_starting_completed(self) -> bool:
         """ Check the progress of the application starting.
 
         :return: True when starting is completed
         """
-        return self.check_progress('stopped', ProcessStates.FATAL)
+        return self.is_job_completed('stopped', ProcessStates.FATAL)
 
     def on_event(self, process) -> None:
         """ Triggers the following of the start sequencing, depending on the new process status. """
@@ -518,15 +526,57 @@ def on_event_out_of_sequence(self, process: ProcessStatus) -> None:
             if process not in planned_process_jobs:
                 self.process_failure(process)
 
-    def store_application_start_sequence(self, application, strategy) -> None:
+    def store_application_start_sequence(self, application: ApplicationStatus,
+                                         strategy: StartingStrategies = None) -> None:
         """ Copy the start sequence and remove programs that are not meant to be started automatically,
-        i.e. their start_sequence is 0. """
+        i.e. their start_sequence is 0.
+        When strategy is not provided, use the application default starting strategy.
+
+        :param application: the application to start
+        :param strategy: the strategy to be used to choose nodes where programs shall be started
+        :return: None
+        """
+        if strategy is None:
+            strategy = application.rules.starting_strategy
         application_sequence = {seq: [ProcessCommand(process, strategy) for process in processes]
                                 for seq, processes in application.start_sequence.items()
                                 if seq > 0}
         if len(application_sequence) > 0:
             sequence = self.planned_sequence.setdefault(application.rules.start_sequence, {})
             sequence[application.application_name] = application_sequence
+            self.logger.debug('Starter.store_application_start_sequence: start sequence stored for application_name={}'
+                              ' with strategy={}'.format(application.application_name, strategy.name))
+
+    def prepare_application_jobs(self, application_name, application: ApplicationStatus = None) -> None:
+        """ Prepare the ProcessCommand instances linked to application planned jobs.
+
+        :param application_name: the application name
+        :param application: the application if available
+        :return: None
+        """
+        # get application if not provided
+        if not application:
+            application = self.supvisors.context.applications[application_name]
+        if not application.rules.distributed:
+            # get all ProcessCommand instances of the application
+            commands = [process for sequence in self.planned_jobs[application_name].values()
+                        for process in sequence]
+            strategy = next((command.strategy for command in commands if command.strategy is not None),
+                            application.rules.starting_strategy)
+            # find node iaw strategy
+            node_name = get_node(self.supvisors, strategy, application.possible_nodes(),
+                                 application.get_start_sequence_expected_load(), self.get_load_requests())
+            self.logger.info('Starter.prepare_application_jobs: node_name={} found for non-distributed'
+                             ' application_name={} using strategy={}'
+                             .format(node_name, application_name, strategy.name))
+            # apply the node to all commands
+            for command in commands:
+                # distributed information must be set to differentiate 2 cases:
+                #     * application is distributed, so node_name has to be resolved later in process_jobs
+                #     * application is not distributed and no applicable node_name has been found
+                command.distributed = False
+                command.node_name = node_name
+        # TODO: could node_name be resolved here, even for distributed applications ?
 
     def process_job(self, command: ProcessCommand, jobs: Commander.CommandList) -> bool:
         """ Start the process on the relevant node.
@@ -535,31 +585,32 @@ def process_job(self, command: ProcessCommand, jobs: Commander.CommandList) -> b
         :param jobs: the list of jobs in progress
         :return: True if a job has been queued.
         """
-        starting = False
+        queued = False
         process = command.process
         self.logger.debug('Starter.process_job: process={} stopped={}'.format(process.namespec, process.stopped()))
         if process.stopped():
-            # whatever a node is found or not to start the process, the job is considered started
-            # in every cases, a notification is expected from Supervisor
-            command.request_time = time.time()
-            jobs.append(command)
-            starting = True
-            # find node iaw strategy
-            node_name = get_node(self.supvisors, command.strategy, process.possible_nodes(),
-                                 process.rules.expected_load)
-            if node_name:
-                self.logger.info('Starter.process_job: request starting of {} at node={}'
-                                 .format(process.namespec, node_name))
+            # node_name has already been decided for a non-distributed application
+            if command.distributed:
+                # find node iaw strategy
+                command.node_name = get_node(self.supvisors, command.strategy, process.possible_nodes(),
+                                             process.rules.expected_load, self.get_load_requests())
+            if command.node_name:
                 # use asynchronous xml rpc to start program
-                self.supvisors.zmq.pusher.send_start_process(node_name, process.namespec, command.extra_args)
-                self.logger.debug('Starter.process_job: {} requested to start on {} at {}'
-                                  .format(process.namespec, node_name, get_asctime(command.request_time)))
+                self.supvisors.zmq.pusher.send_start_process(command.node_name, process.namespec, command.extra_args)
+                self.logger.debug('Starter.process_job: {} requested to start on {} (strategy={}) at {}'
+                                  .format(process.namespec, command.node_name, command.strategy.name,
+                                          get_asctime(command.request_time)))
+                # push command into current jobs
+                command.request_time = time.time()
+                jobs.append(command)
+                queued = True
             else:
                 self.logger.warn('Starter.process_job: no resource available to start {}'.format(process.namespec))
                 self.supvisors.listener.force_process_state(process.namespec, ProcessStates.FATAL,
                                                             'no resource available')
-        # return True when the job is started
-        return starting
+                self.process_failure(process)
+        # return True when the job is queued
+        return queued
 
     def process_failure(self, process):
         """ Updates the start sequence when a process could not be started. """
@@ -599,6 +650,20 @@ def after_jobs(self, application_name: str) -> None:
             self.application_stop_requests.remove(application_name)
             self.supvisors.stopper.stop_application(application)
 
+    def get_load_requests(self) -> LoadRequestMap:
+        """ From current_jobs, extract by node the processes that are requested to start but still stopped
+        and sum their expected load.
+
+        :return: the additional loading per node
+        """
+        load_request_map = {}
+        for command_list in self.current_jobs.values():
+            for command in command_list:
+                # if process is not stopped, its loading is already considered in AddressStatus
+                if command.process.stopped():
+                    load_request_map.setdefault(command.node_name, []).append(command.process.rules.expected_load)
+        return {node_name: sum(load_list) for node_name, load_list in load_request_map.items()}
+
 
 class Stopper(Commander):
     """ Class handling the stopping of processes and applications. """
@@ -608,8 +673,8 @@ def stop_applications(self):
         self.logger.info('Stopper.stop_applications: stop all applications')
         # stopping initialization: push program list in jobs list
         for application in self.supvisors.context.applications.values():
-            # do not stop an application that is not running
-            if application.running() and application.rules.stop_sequence >= 0:
+            # do not check the application state are running processes may be excluded in the evaluation
+            if application.has_running_processes() and application.rules.stop_sequence >= 0:
                 self.store_application_stop_sequence(application)
         self.logger.debug('Stopper.stop_applications: planned_sequence={}'.format(self.printable_planned_sequence()))
         # start work
@@ -619,7 +684,7 @@ def stop_application(self, application: ApplicationStatus) -> bool:
         """ Plan and start the necessary jobs to stop the application in parameter. """
         self.logger.info('Stopper.stop_application: stop application {}'.format(application.application_name))
         # push program list in jobs list and start work
-        if application.running():
+        if application.has_running_processes():
             self.store_application_stop_sequence(application)
             self.logger.debug('Stopper.stop_application: planned_sequence={}'.format(self.printable_planned_sequence()))
             # add application immediately to planned jobs
@@ -665,14 +730,14 @@ def process_job(self, command: ProcessCommand, jobs):
             jobs.append(command)
             return True
 
-    def check_stopping(self) -> bool:
+    def is_stopping_completed(self) -> bool:
         """ Check the progress of the application stopping.
         If no corresponding process event received before long, consider the process STOPPED
         as very likely due to a supervisor being shut down.
 
         :return: True when stopping jobs are completed
         """
-        return self.check_progress('running', ProcessStates.STOPPED)
+        return self.is_job_completed('running', ProcessStates.STOPPED)
 
     def on_event(self, process: ProcessStatus) -> None:
         """ Triggers the following of the stop sequencing, depending on the new process status. """
diff --git a/supvisors/context.py b/supvisors/context.py
index 221b7397..31e35f72 100644
--- a/supvisors/context.py
+++ b/supvisors/context.py
@@ -21,8 +21,6 @@
 
 from .address import *
 from .application import ApplicationRules, ApplicationStatus
-from .infosource import SupervisordSource
-from .listener import SupervisorListener
 from .process import *
 from .ttypes import AddressStates, NameList, PayloadList
 
@@ -30,12 +28,10 @@
 class Context(object):
     """ The Context class holds the main data of Supvisors:
 
-    - addresses: the dictionary of all AddressStatus (key is address),
-    - forced_addresses: the dictionary of the minimal set of AddressStatus (key is address),
+    - nodes: the dictionary of all AddressStatus (key is node_name),
     - applications: the dictionary of all ApplicationStatus (key is application name),
-    - master_address: the address of the Supvisors master,
-    - master: a boolean telling if the local address is the master address.
-    - new: a boolean telling if this context has just been started.
+    - master_node_name: the name of the Supvisors master,
+    - is_master: a boolean telling if the local node is the master node.
     """
 
     def __init__(self, supvisors: Any):
@@ -46,14 +42,12 @@ def __init__(self, supvisors: Any):
         # attributes
         self.nodes: Dict[str, AddressStatus] = {node_name: AddressStatus(node_name, self.logger)
                                                 for node_name in self.supvisors.address_mapper.node_names}
-        self.forced_nodes: Dict[str, AddressStatus] = {node_name: status
-                                                       for node_name, status in self.nodes.items()
-                                                       if node_name in self.supvisors.options.force_synchro_if}
         self.applications: Dict[str, ApplicationStatus] = {}
         # master attributes
-        self._master_node_name = ''
-        self._is_master = False
-        self.master_operational = False
+        self._master_node_name: str = ''
+        self._is_master: bool = False
+        # start time to manage end of synchronization phase
+        self.start_date = 0
 
     def reset(self) -> None:
         """ Reset the context to prepare a new synchronization phase.
@@ -62,6 +56,7 @@ def reset(self) -> None:
         :return: None
         """
         self.master_node_name = ''
+        self.start_date = int(time())
         for status in self.nodes.values():
             status.reset()
 
@@ -78,23 +73,25 @@ def master_node_name(self, node_name) -> None:
         self.logger.info('Context.master_node_name: {}'.format(node_name))
         self._master_node_name = node_name
         self._is_master = node_name == self.supvisors.address_mapper.local_node_name
-        self.master_operational = False
 
     # methods on nodes
     def unknown_nodes(self) -> NameList:
         """ Return the AddressStatus instances in UNKNOWN state. """
         return self.nodes_by_states([AddressStates.UNKNOWN, AddressStates.CHECKING, AddressStates.ISOLATING])
 
-    def unknown_forced_nodes(self) -> NameList:
-        """ Return the AddressStatus instances in UNKNOWN state. """
-        return [status.address_name
-                for status in self.forced_nodes.values()
-                if status.state in [AddressStates.UNKNOWN, AddressStates.CHECKING, AddressStates.ISOLATING]]
-
     def running_nodes(self) -> NameList:
         """ Return the AddressStatus instances in RUNNING state. """
         return self.nodes_by_states([AddressStates.RUNNING])
 
+    def running_core_nodes(self) -> bool:
+        """ Check if core nodes are in RUNNING state.
+
+        :return: True if all core nodes are in RUNNING state
+        """
+        if self.supvisors.options.force_synchro_if:
+            running_nodes = self.running_nodes()
+            return all(node_name in running_nodes for node_name in self.supvisors.options.force_synchro_if)
+
     def isolating_nodes(self) -> NameList:
         """ Return the AddressStatus instances in ISOLATING state. """
         return self.nodes_by_states([AddressStates.ISOLATING])
@@ -105,11 +102,9 @@ def isolation_nodes(self) -> NameList:
 
     def nodes_by_states(self, states: List[AddressStates]) -> NameList:
         """ Return the AddressStatus instances sorted by state. """
-        return [status.address_name
-                for status in self.nodes.values()
-                if status.state in states]
+        return [status.address_name for status in self.nodes.values() if status.state in states]
 
-    def invalid(self, status: AddressStatus) -> None:
+    def invalid(self, status: AddressStatus, fence=None) -> None:
         """ Declare SILENT or ISOLATING the AddressStatus in parameter, according to the auto_fence option.
         A local node is never ISOLATING, whatever the option is set or not.
         Give it a chance to restart. """
@@ -124,20 +119,12 @@ def invalid(self, status: AddressStatus) -> None:
             #       by design, the local node cannot be ISOLATED. that's precisely the aim of the following instructions
             self.logger.critical('Context.invalid: local node is SILENT')
             status.state = AddressStates.SILENT
-        elif self.supvisors.options.auto_fence:
+        elif fence or self.supvisors.options.auto_fence:
             status.state = AddressStates.ISOLATING
         else:
             status.state = AddressStates.SILENT
-
-    def end_synchro(self) -> None:
-        """ Declare as SILENT the nodes that are still not responsive at the end of the INITIALIZATION state.
-
-        :return: None
-        """
-        # consider problem if no tick received at the end of synchro time
-        for status in self.nodes.values():
-            if status.state == AddressStates.UNKNOWN:
-                self.invalid(status)
+        # publish AddressStatus
+        self.supvisors.zmq.publisher.send_address_status(status.serial())
 
     # methods on applications / processes
     def get_managed_applications(self) -> Iterator[ApplicationStatus]:
@@ -180,11 +167,13 @@ def setdefault_application(self, application_name: str) -> Optional[ApplicationS
             # load rules from rules file
             rules = ApplicationRules()
             if self.supvisors.parser:
+                # apply default starting strategy from options
+                rules.starting_strategy = self.supvisors.options.starting_strategy
                 self.supvisors.parser.load_application_rules(application_name, rules)
                 self.logger.debug('Context.setdefault_application: application={} rules={}'
                                   .format(application_name, rules))
             # create new instance
-            application = ApplicationStatus(application_name, rules, self.logger)
+            application = ApplicationStatus(application_name, rules, self.supvisors)
             self.applications[application_name] = application
         return application
 
@@ -219,7 +208,7 @@ def setdefault_process(self, info: Payload) -> Optional[ProcessStatus]:
     def load_processes(self, node_name: str, all_info: PayloadList) -> None:
         """ Load application dictionary from process info got from Supervisor on address. """
         self.logger.trace('Context.load_processes: node_name={} all_info={}'.format(node_name, all_info))
-        # get AddressStatus corresponding to address
+        # get AddressStatus corresponding to node_name
         status = self.nodes[node_name]
         # store processes into their application entry
         for info in all_info:
@@ -230,6 +219,10 @@ def load_processes(self, node_name: str, all_info: PayloadList) -> None:
                 process.add_info(node_name, info)
                 # share the instance to the Supervisor instance that holds it
                 status.add_process(process)
+        # re-evaluate application sequences and status
+        for application in self.applications.values():
+            application.update_sequences()
+            application.update_status()
 
     # methods on events
     def on_authorization(self, node_name: str, authorized: bool) -> Optional[bool]:
@@ -249,31 +242,38 @@ def on_authorization(self, node_name: str, authorized: bool) -> Optional[bool]:
                     status.state = AddressStates.RUNNING
                     return True
                 self.logger.warn('Context.on_authorization: local is not authorized to deal with {}'.format(node_name))
-                self.invalid(status)
+                self.invalid(status, True)
         else:
             self.logger.warn('Context.on_authorization: got authorization from unexpected node={}'.format(node_name))
 
-    def on_tick_event(self, node_name: str, event):
+    def on_tick_event(self, node_name: str, event: Payload):
         """ Method called upon reception of a tick event from the remote Supvisors instance, telling that it is active.
         Supvisors checks that the handling of the event is valid in case of auto fencing.
         The method also updates the times of the corresponding AddressStatus and the ProcessStatus depending on it.
         Finally, the updated AddressStatus is published. """
-        if self.supvisors.address_mapper.valid(node_name):
-            status = self.nodes[node_name]
-            # ISOLATED address is not updated anymore
-            if not status.in_isolation():
-                self.logger.debug('Context.on_tick_event: got tick {} from location={}'.format(event, node_name))
-                # asynchronous port-knocking used to check if remote Supvisors instance considers
-                # the local instance as isolated
-                if status.state in [AddressStates.UNKNOWN, AddressStates.SILENT]:
-                    status.state = AddressStates.CHECKING
-                    self.supvisors.zmq.pusher.send_check_node(node_name)
-                # update internal times
-                status.update_times(event['when'], int(time()))
-                # publish AddressStatus event
-                self.supvisors.zmq.publisher.send_address_status(status.serial())
-        else:
-            self.logger.warn('Context.on_tick_event: got tick from unexpected location={}'.format(node_name))
+        # check if node_name is known
+        if not self.supvisors.address_mapper.valid(node_name):
+            self.logger.error('Context.on_tick_event: got tick from unexpected location={}'.format(node_name))
+            return
+        # check if local tick has been received yet
+        if node_name != self.supvisors.address_mapper.local_node_name:
+            status = self.nodes[self.supvisors.address_mapper.local_node_name]
+            if status.state != AddressStates.RUNNING:
+                self.logger.debug('Context.on_tick_event: waiting for local tick')
+                return
+        # process node event
+        status = self.nodes[node_name]
+        # ISOLATED nodes are not updated anymore
+        if not status.in_isolation():
+            self.logger.debug('Context.on_tick_event: got tick {} from location={}'.format(event, node_name))
+            # asynchronous port-knocking used to check how the remote Supvisors instance considers the local instance
+            if status.state in [AddressStates.UNKNOWN, AddressStates.SILENT]:
+                status.state = AddressStates.CHECKING
+                self.supvisors.zmq.pusher.send_check_node(node_name)
+            # update internal times
+            status.update_times(event['when'], int(time()))
+            # publish AddressStatus event
+            self.supvisors.zmq.publisher.send_address_status(status.serial())
 
     def on_process_event(self, node_name: str, event: Payload) -> Optional[ProcessStatus]:
         """ Method called upon reception of a process event from the remote Supvisors instance.
@@ -298,7 +298,7 @@ def on_process_event(self, node_name: str, event: Payload) -> Optional[ProcessSt
                 else:
                     # refresh process info depending on the nature of the process event
                     if 'forced' in event:
-                        process.force_state(event)
+                        process.force_state(event['state'], event['spawnerr'])
                         del event['forced']
                     else:
                         process.update_info(node_name, event)
@@ -310,7 +310,6 @@ def on_process_event(self, node_name: str, event: Payload) -> Optional[ProcessSt
                             self.logger.warn('Context.on_process_event: cannot apply extra args to {} unknown'
                                              ' to local Supervisor'.format(process.namespec))
                     # refresh application status
-                    application = self.applications[process.application_name]
                     application.update_status()
                     # publish process event, status and application status
                     publisher = self.supvisors.zmq.publisher
@@ -327,20 +326,23 @@ def on_timer_event(self) -> Set[ProcessStatus]:
         process_failures = set()
         # find all nodes that do not send their periodic tick
         current_time = time()
-        invalid_nodes = [status for status in self.nodes.values() if status.inactive(current_time)]
-        for status in invalid_nodes:
-            # update node status state
-            self.invalid(status)
-            # publish AddressStatus event
-            self.supvisors.zmq.publisher.send_address_status(status.serial())
-            # invalidate node in processes
-            process_failures.update({process for process in status.running_processes()
-                                     if process.invalidate_node(status.address_name)})
+        # do not check for invalidation before synchro_timeout
+        if (current_time - self.start_date) > self.supvisors.options.synchro_timeout:
+            for status in self.nodes.values():
+                if status.state == AddressStates.UNKNOWN:
+                    # invalid unknown nodes
+                    self.invalid(status)
+                elif status.inactive(current_time):
+                    # invalid silent nodes
+                    self.invalid(status)
+                    # invalidate node in processes
+                    process_failures.update({process for process in status.running_processes()
+                                             if process.invalidate_node(status.address_name)})
         #  return all processes that declare a failure
         return process_failures
 
     def handle_isolation(self) -> NameList:
-        """ Move ISOLATING addresses to ISOLATED and publish related events. """
+        """ Move ISOLATING nodes to ISOLATED and publish related events. """
         node_names = self.isolating_nodes()
         for node_name in node_names:
             status = self.nodes[node_name]
diff --git a/supvisors/listener.py b/supvisors/listener.py
index 311a8cb0..b63fbfb8 100644
--- a/supvisors/listener.py
+++ b/supvisors/listener.py
@@ -142,8 +142,8 @@ def on_tick(self, event: events.TickEvent) -> None:
 
     def on_remote_event(self, event: events.RemoteCommunicationEvent) -> None:
         """ Called when a RemoteCommunicationEvent is notified.
-        This is used to sequence the events received from the Supvisors thread
-        with the other events handled by the local Supervisor. """
+        This is used to sequence the events received from the Supvisors thread with the other events handled
+        by the local Supervisor. """
         self.logger.debug('SupervisorListener.on_remote_event: got Remote event from supervisord: {} / {}'
                           .format(event.type, event.data))
         if event.type == RemoteCommEvents.SUPVISORS_AUTH:
@@ -156,20 +156,20 @@ def on_remote_event(self, event: events.RemoteCommunicationEvent) -> None:
     def unstack_event(self, message: str):
         """ Unstack and process one event from the event queue. """
         event_type, event_node, event_data = json.loads(message)
-        if event_type == InternalEventHeaders.TICK:
+        if event_type == InternalEventHeaders.TICK.value:
             self.logger.trace('SupervisorListener.unstack_event: got tick event from {}: {}'
                               .format(event_node, event_data))
             self.supvisors.fsm.on_tick_event(event_node, event_data)
-        elif event_type == InternalEventHeaders.PROCESS:
+        elif event_type == InternalEventHeaders.PROCESS.value:
             self.logger.trace('SupervisorListener.unstack_event: got process event from {}: {}'
                               .format(event_node, event_data))
             self.supvisors.fsm.on_process_event(event_node, event_data)
-        elif event_type == InternalEventHeaders.STATISTICS:
+        elif event_type == InternalEventHeaders.STATISTICS.value:
             # this Supvisors could handle statistics even if psutil is not installed
             self.logger.trace('SupervisorListener.unstack_event: got statistics event from {}: {}'
                               .format(event_node, event_data))
             self.supvisors.statistician.push_statistics(event_node, event_data)
-        elif event_type == InternalEventHeaders.STATE:
+        elif event_type == InternalEventHeaders.STATE.value:
             self.logger.trace('SupervisorListener.unstack_event: got OPERATION event from {}'
                               .format(event_node))
             self.supvisors.fsm.on_state_event(event_node, event_data)
@@ -200,7 +200,12 @@ def force_process_state(self, namespec: str, state: ProcessStates, reason: str)
         # create payload from event
         application_name, process_name = split_namespec(namespec)
         payload = {'group': application_name, 'name': process_name, 'state': state, 'forced': True,
-                   'extra_args': self.supvisors.info_source.get_extra_args(namespec),
-                   'now': int(time.time()), 'pid': 0, 'expected': False, 'spawnerr': reason }
+                   'now': int(time.time()), 'pid': 0, 'expected': False, 'spawnerr': reason}
+        # get extra_args if process is known to local Supervisor
+        try:
+            payload['extra_args'] = self.supvisors.info_source.get_extra_args(namespec)
+        except KeyError:
+            self.logger.trace('SupervisorListener.force_process_state: namespec={} cannot get extra_args'
+                              ' because the program is unknown to local Supervisor'.format(namespec))
         self.logger.debug('SupervisorListener.force_process_state: payload={}'.format(payload))
         self.publisher.send_process_event(payload)
diff --git a/supvisors/mainloop.py b/supvisors/mainloop.py
index 52125877..a1b6d599 100644
--- a/supvisors/mainloop.py
+++ b/supvisors/mainloop.py
@@ -19,10 +19,14 @@
 
 import json
 
+from http.client import CannotSendRequest, IncompleteRead
 from threading import Event, Thread
 from typing import Any
 from sys import stderr
 
+from supervisor.compat import xmlrpclib
+from supervisor.xmlrpc import RPCError
+
 from supvisors.rpcrequests import getRPCInterface
 from supvisors.supvisorszmq import SupvisorsZmq
 from supvisors.ttypes import AddressStates
@@ -40,6 +44,10 @@ class SupvisorsMainLoop(Thread):
         - proxy: the proxy to the internal RPC interface.
     """
 
+    # to avoid a long list of exceptions in catches
+    RpcExceptions = (KeyError, ValueError, OSError, ConnectionResetError,
+                     CannotSendRequest, IncompleteRead, xmlrpclib.Fault, RPCError)
+
     def __init__(self, supvisors: Any) -> None:
         """ Initialization of the attributes.
 
@@ -112,21 +120,27 @@ def check_requests(self, sockets, poll_result) -> None:
 
     def send_request(self, header, body) -> None:
         """ Perform the XML-RPC according to the header. """
-        if header == DeferredRequestHeaders.CHECK_NODE:
+        if header == DeferredRequestHeaders.CHECK_NODE.value:
             node_name, = body
             self.check_node(node_name)
-        elif header == DeferredRequestHeaders.START_PROCESS:
+        elif header == DeferredRequestHeaders.START_PROCESS.value:
             node_name, namespec, extra_args = body
             self.start_process(node_name, namespec, extra_args)
-        elif header == DeferredRequestHeaders.STOP_PROCESS:
+        elif header == DeferredRequestHeaders.STOP_PROCESS.value:
             node_name, namespec = body
             self.stop_process(node_name, namespec)
-        elif header == DeferredRequestHeaders.RESTART:
+        elif header == DeferredRequestHeaders.RESTART.value:
             node_name, = body
             self.restart(node_name)
-        elif header == DeferredRequestHeaders.SHUTDOWN:
+        elif header == DeferredRequestHeaders.SHUTDOWN.value:
             node_name, = body
             self.shutdown(node_name)
+        elif header == DeferredRequestHeaders.RESTART_ALL.value:
+            node_name, = body
+            self.restart_all(node_name)
+        elif header == DeferredRequestHeaders.SHUTDOWN_ALL.value:
+            node_name, = body
+            self.shutdown_all(node_name)
 
     def check_node(self, node_name: str) -> None:
         """ Check isolation and get all process info asynchronously. """
@@ -136,8 +150,8 @@ def check_node(self, node_name: str) -> None:
             master_node_name = supvisors_rpc.get_master_address()
             supvisors_state = supvisors_rpc.get_supvisors_state()
             # check authorization
-            status = supvisors_rpc.get_address_info(node_name)
-            authorized = status['statecode'] not in [AddressStates.ISOLATING, AddressStates.ISOLATED]
+            status = supvisors_rpc.get_address_info(self.supvisors.address_mapper.local_node_name)
+            authorized = AddressStates(status['statecode']) not in [AddressStates.ISOLATING, AddressStates.ISOLATED]
             # get process info if authorized
             if authorized:
                 # get information about all processes handled by Supervisor
@@ -148,7 +162,7 @@ def check_node(self, node_name: str) -> None:
             self.send_remote_comm_event(RemoteCommEvents.SUPVISORS_AUTH,
                                         'node_name:{} authorized:{} master_node_name:{} supvisors_state:{}'
                                         .format(node_name, authorized, master_node_name, supvisors_state['statename']))
-        except:
+        except SupvisorsMainLoop.RpcExceptions:
             print('[ERROR] failed to check address {}'.format(node_name), file=stderr)
 
     def start_process(self, node_name: str, namespec: str, extra_args: str) -> None:
@@ -156,7 +170,7 @@ def start_process(self, node_name: str, namespec: str, extra_args: str) -> None:
         try:
             proxy = getRPCInterface(node_name, self.env)
             proxy.supvisors.start_args(namespec, extra_args, False)
-        except:
+        except SupvisorsMainLoop.RpcExceptions:
             print('[ERROR] failed to start process {} on {} with extra_args="{}"'
                   .format(namespec, node_name, extra_args), file=stderr)
 
@@ -165,7 +179,7 @@ def stop_process(self, node_name: str, namespec: str) -> None:
         try:
             proxy = getRPCInterface(node_name, self.env)
             proxy.supervisor.stopProcess(namespec, False)
-        except:
+        except SupvisorsMainLoop.RpcExceptions:
             print('[ERROR] failed to stop process {} on {}'.format(namespec, node_name), file=stderr)
 
     def restart(self, node_name: str) -> None:
@@ -173,21 +187,37 @@ def restart(self, node_name: str) -> None:
         try:
             proxy = getRPCInterface(node_name, self.env)
             proxy.supervisor.restart()
-        except:
-            print('[ERROR] failed to restart address {}'.format(node_name), file=stderr)
+        except SupvisorsMainLoop.RpcExceptions:
+            print('[ERROR] failed to restart node {}'.format(node_name), file=stderr)
 
     def shutdown(self, node_name: str) -> None:
-        """ Stop process asynchronously. """
+        """ Shutdown a Supervisor instance asynchronously. """
         try:
             proxy = getRPCInterface(node_name, self.env)
             proxy.supervisor.shutdown()
-        except:
-            print('[ERROR] failed to shutdown address {}'.format(node_name), file=stderr)
+        except SupvisorsMainLoop.RpcExceptions:
+            print('[ERROR] failed to shutdown node {}'.format(node_name), file=stderr)
+
+    def restart_all(self, node_name: str) -> None:
+        """ Ask the Supvisors Master to restart Supvisors. """
+        try:
+            proxy = getRPCInterface(node_name, self.env)
+            proxy.supvisors.restart()
+        except SupvisorsMainLoop.RpcExceptions:
+            print('[ERROR] failed to send Supvisors restart to Master {}'.format(node_name), file=stderr)
+
+    def shutdown_all(self, node_name: str) -> None:
+        """ Ask the Supvisors Master to shutdown Supvisors. """
+        try:
+            proxy = getRPCInterface(node_name, self.env)
+            proxy.supvisors.shutdown()
+        except SupvisorsMainLoop.RpcExceptions:
+            print('[ERROR] failed to send Supvisors shutdown to Master {}'.format(node_name), file=stderr)
 
     def send_remote_comm_event(self, event_type: str, event_data) -> None:
         """ Shortcut for the use of sendRemoteCommEvent. """
         try:
             self.proxy.supervisor.sendRemoteCommEvent(event_type, event_data)
-        except:
+        except SupvisorsMainLoop.RpcExceptions:
             # expected on restart / shutdown
             print('[WARN] failed to send event to Supervisor: {} - {}'.format(event_type, event_data), file=stderr)
diff --git a/supvisors/options.py b/supvisors/options.py
index 2e05ada4..39cd3d94 100644
--- a/supvisors/options.py
+++ b/supvisors/options.py
@@ -27,7 +27,7 @@
                                   list_of_strings)
 from supervisor.options import ServerOptions
 
-from supvisors.ttypes import ConciliationStrategies, StartingStrategies
+from .ttypes import ConciliationStrategies, StartingStrategies
 
 
 # Options of main section
@@ -42,7 +42,7 @@ class SupvisorsOptions(object):
         - event_port: port number used to publish all Supvisors events,
         - auto_fence: when True, Supvisors won't try to reconnect to a Supvisors instance that has been inactive,
         - synchro_timeout: time in seconds that Supvisors waits for all expected Supvisors instances to publish,
-        - force_synchro_if: subset of address_list that will force the end of syncho when all RUNNING,
+        - force_synchro_if: subset of address_list that will force the end of synchro when all RUNNING,
         - conciliation_strategy: strategy used to solve conflicts when Supvisors has detected multiple running instances of the same program,
         - starting_strategy: strategy used to start processes on addresses,
         - stats_periods: list of periods for which the statistics will be provided in the Supvisors web page,
@@ -137,7 +137,7 @@ def server_configs_from_parser(self, parser):
         opt.auto_fence = boolean(parser.getdefault('auto_fence', 'false'))
         opt.synchro_timeout = self.to_timeout(parser.getdefault('synchro_timeout', '15'))
         opt.force_synchro_if = filter(None, list_of_strings(parser.getdefault('force_synchro_if', None)))
-        opt.force_synchro_if = [node for node in opt.force_synchro_if if node in opt.address_list]
+        opt.force_synchro_if = {node for node in opt.force_synchro_if if node in opt.address_list}
         opt.conciliation_strategy = self.to_conciliation_strategy(parser.getdefault('conciliation_strategy', 'USER'))
         opt.starting_strategy = self.to_starting_strategy(parser.getdefault('starting_strategy', 'CONFIG'))
         # configure statistics
diff --git a/supvisors/process.py b/supvisors/process.py
index 042ea31a..2c6760df 100644
--- a/supvisors/process.py
+++ b/supvisors/process.py
@@ -74,13 +74,14 @@ def check_start_sequence(self, namespec: str) -> None:
             self.required = False
 
     def check_autorestart(self, namespec: str) -> None:
-        """ Disable autorestart when RunningFailureStrategies is not CONTINUE.
+        """ Disable autorestart when RunningFailureStrategies is related to applications.
         In these cases, Supvisors triggers behaviors that are different so supervisor.
 
         :param namespec: the namespec of the program considered.
         :return: None
         """
-        if self.running_failure_strategy != RunningFailureStrategies.CONTINUE:
+        if self.running_failure_strategy in [RunningFailureStrategies.STOP_APPLICATION,
+                                             RunningFailureStrategies.RESTART_APPLICATION]:
             if self.supvisors.info_source.autorestart(namespec):
                 self.supvisors.info_source.disable_autorestart(namespec)
                 self.logger.warn('ProcessRules.check_autorestart: program={} - autorestart disabled due to '
@@ -179,6 +180,7 @@ def __init__(self, application_name: str, process_name: str, rules: ProcessRules
         :param application_name: the name of the application the process belongs to
         :param process_name: the name of the process
         :param rules: the rules loaded from the rules file
+        :param supvisors: the global Supvisors structure
         """
         # keep a reference of the Supvisors data
         self.supvisors = supvisors
@@ -219,15 +221,16 @@ def state(self, new_state: ProcessStates) -> None:
             self._state = new_state
             self.logger.info('ProcessStatus.state: {} is {}'.format(self.namespec, self.state_string()))
 
-    def force_state(self, payload: Payload) -> None:
+    def force_state(self, state: ProcessStates, reason: str = None) -> None:
         """ Force the process state due to an unexpected event.
 
-        :param payload: the event payload
+        :param state: the forced state
+        :param reason: the reason why the state is forced
         :return: None
         """
         self.last_event_time = int(time())
-        self.forced_state = payload['state']
-        self.forced_reason = payload['spawnerr']
+        self.forced_state = state
+        self.forced_reason = reason
 
     @property
     def extra_args(self) -> str:
@@ -325,14 +328,14 @@ def conflicting(self) -> bool:
         return len(self.running_nodes) > 1
 
     @staticmethod
-    def update_description(process_info: Payload) -> str:
+    def update_description(info: Payload) -> str:
         """ Return the Supervisor way to describe the process status.
 
-        :param process_info: a subset of the dict received from Supervisor.getProcessInfo.
+        :param info: a subset of the dict received from Supervisor.getProcessInfo.
         :return: the description of the process status
         """
         # IDE warning on first parameter but ignored as the Supervisor function should have been set as staticmethod
-        return SupervisorNamespaceRPCInterface._interpretProcessInfo(None, process_info)
+        return SupervisorNamespaceRPCInterface._interpretProcessInfo(None, info)
 
     def get_last_description(self) -> Tuple[Optional[str], str]:
         """ Get the latest description received from the process across all nodes.
@@ -357,12 +360,15 @@ def get_last_description(self) -> Tuple[Optional[str], str]:
             self.logger.trace('ProcessStatus.get_last_description: namespec={} - node_name={} [running]description={}'
                               .format(self.namespec, node_name, info['description']))
         else:
-            # sort info_map them by stop date
+            # none running. sort info_map them by stop date
             node_name, info = max(self.info_map.items(), key=lambda x: x[1]['stop'])
             self.logger.trace('ProcessStatus.get_last_description: namespec={} - node_name={} [stopped]description={}'
                               .format(self.namespec, node_name, info['description']))
-        # extract description from information found
-        return node_name, info['description']
+        # extract description from information found and add node_name
+        desc = info['description']
+        if desc and desc != 'Not started':
+            desc = desc + ' on ' + node_name
+        return node_name, desc
 
     # methods
     def state_string(self) -> str:
diff --git a/supvisors/rpcinterface.py b/supvisors/rpcinterface.py
index d3582e34..001ba940 100644
--- a/supvisors/rpcinterface.py
+++ b/supvisors/rpcinterface.py
@@ -311,7 +311,7 @@ def stop_application(self, application_name, wait=True):
             raise RPCError(Faults.BAD_NAME, application_name)
         # check application is not already STOPPED
         application = self.supvisors.context.applications[application_name]
-        if application.state == ApplicationStates.STOPPED:
+        if not application.has_running_processes():
             raise RPCError(Faults.NOT_RUNNING, application_name)
         # stop the application
         done = self.supvisors.stopper.stop_application(application)
@@ -456,8 +456,11 @@ def start_process(self, strategy: EnumParameterType, namespec, extra_args='', wa
         for process in processes:
             done &= self.supvisors.starter.start_process(strategy_enum, process, extra_args)
         self.logger.debug('startProcess {} done={}'.format(process.namespec, done))
-        # wait until application fully RUNNING or (failed)
-        if wait and not done:
+        if done:
+            # one of the jobs has not been queued. something wrong happened (lack of resources ?)
+            raise RPCError(Faults.ABNORMAL_TERMINATION, namespec)
+        # wait until application fully RUNNING or failed
+        if wait:
             def onwait():
                 # check starter
                 if self.supvisors.starter.in_progress():
@@ -560,7 +563,9 @@ def onwait():
         return onwait  # deferred
 
     def conciliate(self, strategy: EnumParameterType) -> bool:
-        """ Apply the conciliation strategy only if **Supvisors** is in ``CONCILIATION`` state, with a USER strategy.
+        """ Apply the conciliation strategy only if **Supvisors** is in ``CONCILIATION`` state,
+        with an ``USER`` conciliation strategy (using other strategies would trigger an automatic behavior that wouldn't
+        give a chance to this XML-RPC).
 
         *@param* ``ConciliationStrategies strategy``: the strategy used to conciliate, as a string or as a value.
 
@@ -569,7 +574,7 @@ def conciliate(self, strategy: EnumParameterType) -> bool:
             * with code ``Faults.BAD_SUPVISORS_STATE`` if **Supvisors** is not in state ``CONCILIATION``,
             * with code ``Faults.BAD_STRATEGY`` if strategy is unknown to **Supvisors**.
 
-        *@return* ``bool``: ``True`` if conciliation is triggered, ``False`` when strategy is USER.
+        *@return* ``bool``: ``True`` if conciliation is triggered, ``False`` when the conciliation strategy is ``USER``.
         """
         self._check_conciliation()
         strategy_enum = self._get_conciliation_strategy(strategy)
@@ -602,8 +607,8 @@ def shutdown(self) -> bool:
         return True
 
     def change_log_level(self, level_param: LevelsByName) -> bool:
-        """ Change the logger level for the local Supvisors.
-        If Supvisors logger is configured as AUTO, this will impact the Supervisor logger too.
+        """ Change the logger level for the local **Supvisors**.
+        If **Supvisors** logger is configured as ``AUTO``, this will impact the Supervisor logger too.
 
         *@param* ``LevelsByName level``: the new logger level, as a string or as a value.
 
diff --git a/supvisors/rpcrequests.py b/supvisors/rpcrequests.py
index 1314070c..a5775f85 100644
--- a/supvisors/rpcrequests.py
+++ b/supvisors/rpcrequests.py
@@ -22,10 +22,8 @@
 from supervisor.compat import xmlrpclib
 from supervisor.xmlrpc import SupervisorTransport
 
-from supvisors.rpcinterface import RPCInterface
 
-
-def getRPCInterface(node_name: str, env: Mapping[str, str]) -> RPCInterface:
+def getRPCInterface(node_name: str, env: Mapping[str, str]):
     """ The getRPCInterface creates a proxy to a supervisor XML-RPC server.
     Information about the HTTP configuration is required in env. """
     # get configuration info from env
diff --git a/supvisors/sparser.py b/supvisors/sparser.py
index 7940aa9d..57b2cbb4 100644
--- a/supvisors/sparser.py
+++ b/supvisors/sparser.py
@@ -26,9 +26,9 @@
 from supervisor.datatypes import list_of_strings
 from supervisor.options import split_namespec
 
-from supvisors.application import ApplicationRules
-from supvisors.process import ProcessRules
-from supvisors.ttypes import StartingFailureStrategies, RunningFailureStrategies, EnumClassType
+from .application import ApplicationRules
+from .process import ProcessRules
+from .ttypes import StartingStrategies, StartingFailureStrategies, RunningFailureStrategies, EnumClassType
 
 
 # XSD contents for XML validation
@@ -41,6 +41,14 @@
             <xs:maxInclusive value="100"/>
         </xs:restriction>
     </xs:simpleType>
+    <xs:simpleType name="StartingStrategy" final="restriction" >
+        <xs:restriction base="xs:string">
+            <xs:enumeration value="CONFIG" />
+            <xs:enumeration value="LESS_LOADED" />
+            <xs:enumeration value="MOST_LOADED" />
+            <xs:enumeration value="LOCAL" />
+        </xs:restriction>
+    </xs:simpleType>
     <xs:simpleType name="StartingFailureStrategy" final="restriction" >
         <xs:restriction base="xs:string">
             <xs:enumeration value="ABORT" />
@@ -71,8 +79,11 @@
     </xs:complexType>
     <xs:complexType name="ApplicationModel">
         <xs:sequence>
+            <xs:element type="xs:boolean" name="distributed" minOccurs="0" maxOccurs="1"/>
+            <xs:element type="xs:string" name="addresses" minOccurs="0" maxOccurs="1"/>
             <xs:element type="xs:byte" name="start_sequence" minOccurs="0" maxOccurs="1"/>
             <xs:element type="xs:byte" name="stop_sequence" minOccurs="0" maxOccurs="1"/>
+            <xs:element type="StartingStrategy" name="starting_strategy" minOccurs="0" maxOccurs="1"/>
             <xs:element type="StartingFailureStrategy" name="starting_failure_strategy" minOccurs="0" maxOccurs="1"/>
             <xs:element type="RunningFailureStrategy" name="running_failure_strategy" minOccurs="0" maxOccurs="1"/>
             <xs:choice minOccurs="0" maxOccurs="unbounded">
@@ -102,6 +113,9 @@ class Parser(object):
     # for recursive references to program models, depth is limited to 3
     LOOP_CHECK = 3
 
+    # annotation types
+    AnyRules = Union[ApplicationRules, ProcessRules]
+
     def __init__(self, supvisors: Any) -> None:
         """ The constructor parses the XML file and stores references to models and patterns found.
 
@@ -133,8 +147,11 @@ def load_application_rules(self, application_name: str, rules: ApplicationRules)
         application_elt = self.root.find('./application[@name="{}"]'.format(application_name))
         if application_elt is not None:
             rules.managed = True
+            self.load_boolean(application_elt, 'distributed', rules)
+            self.load_application_nodes(application_elt, rules)
             self.load_sequence(application_elt, 'start_sequence', rules)
             self.load_sequence(application_elt, 'stop_sequence', rules)
+            self.load_enum(application_elt, 'starting_strategy', StartingStrategies, rules)
             self.load_enum(application_elt, 'starting_failure_strategy', StartingFailureStrategies, rules)
             self.load_enum(application_elt, 'running_failure_strategy', RunningFailureStrategies, rules)
             self.logger.debug('Parser.load_application_rules: application {} - rules {}'
@@ -224,6 +241,22 @@ def get_model_element(self, elt: Any) -> Optional[Any]:
         model = elt.findtext('reference')
         return self.models.get(model, None)
 
+    def load_application_nodes(self, elt: Any, rules: ApplicationRules) -> None:
+        """ Get the nodes where the non-distributed application is authorized to run.
+
+        :param elt: the XML element containing rules definition for an application
+        :param rules: the application structure used to store the rules found
+        :return: None
+        """
+        value = elt.findtext('addresses')
+        if value:
+            # sort and trim list of values
+            node_names = list(OrderedDict.fromkeys(filter(None, list_of_strings(value))))
+            if '*' in node_names:
+                rules.node_names = ['*']
+            else:
+                rules.node_names = self.supvisors.address_mapper.filter(node_names)
+
     def load_program_nodes(self, elt: Any, rules: ProcessRules) -> None:
         """ Get the nodes where the program is authorized to run.
 
@@ -250,7 +283,7 @@ def load_program_nodes(self, elt: Any, rules: ProcessRules) -> None:
             else:
                 rules.node_names = self.supvisors.address_mapper.filter(node_names)
 
-    def load_sequence(self, elt: Any, attr_string: str, rules: Union[ApplicationRules, ProcessRules]) -> None:
+    def load_sequence(self, elt: Any, attr_string: str, rules: AnyRules) -> None:
         """ Return the sequence value found from the XML element.
         The value must be greater than or equal to 0.
 
@@ -293,7 +326,7 @@ def load_expected_loading(self, elt: Any, rules: ProcessRules) -> None:
                 self.logger.warn('Parser.load_expected_loading: not an integer for {} expected_loading: {}'
                                  .format(elt.get('name'), str_value))
 
-    def load_boolean(self, elt: Any, attr_string: str, rules: ProcessRules) -> None:
+    def load_boolean(self, elt: Any, attr_string: str, rules: AnyRules) -> None:
         """ Return the boolean value found from XML element.
 
         :param elt: the XML element containing rules definition for an application or a program
@@ -310,8 +343,7 @@ def load_boolean(self, elt: Any, attr_string: str, rules: ProcessRules) -> None:
                 self.logger.warn('Parser.load_boolean: not a boolean-like for {} {}: {}'
                                  .format(elt.get('name'), attr_string, str_value))
 
-    def load_enum(self, elt: Any, attr_string: str, klass: EnumClassType,
-                  rules: Union[ApplicationRules, ProcessRules]) -> None:
+    def load_enum(self, elt: Any, attr_string: str, klass: EnumClassType, rules: AnyRules) -> None:
         """ Return the running_failure_strategy value found from XML element.
         The value MUST correspond to an enumeration value of RunningFailureStrategies.
 
diff --git a/supvisors/statemachine.py b/supvisors/statemachine.py
index ad2d19a8..e63372fc 100644
--- a/supvisors/statemachine.py
+++ b/supvisors/statemachine.py
@@ -18,10 +18,13 @@
 # ======================================================================
 
 from time import time
-from typing import Any, Callable, Optional
+from typing import Any, Optional
 
+from supervisor.loggers import Logger
+
+from .context import Context
 from .strategy import conciliate_conflicts
-from .ttypes import AddressStates, SupvisorsStates, NameList, Payload
+from .ttypes import AddressStates, RunningFailureStrategies, SupvisorsStates, NameList, Payload, PayloadList
 
 
 class AbstractState(object):
@@ -70,8 +73,10 @@ def check_nodes(self) -> SupvisorsStates:
         :return: the suggested state if local or Master node is not active anymore
         """
         if self.context.nodes[self.local_node_name].state != AddressStates.RUNNING:
+            self.logger.critical('AbstractState.check_nodes: local node not RUNNING anymore')
             return SupvisorsStates.INITIALIZATION
         if self.context.nodes[self.context.master_node_name].state != AddressStates.RUNNING:
+            self.logger.warn('AbstractState.check_nodes: Master node not RUNNING anymore')
             return SupvisorsStates.INITIALIZATION
 
     def abort_jobs(self) -> None:
@@ -82,25 +87,6 @@ def abort_jobs(self) -> None:
         self.supvisors.failure_handler.abort()
         self.supvisors.starter.abort()
 
-    def apply_nodes_func(self, func: Callable[[str], None]) -> None:
-        """ Perform the action func on all addresses.
-        The local address is the last to be performed.
-
-        :param func: the function callable using a node name as parameter
-        :return: None
-        """
-        # send func request to all locals (but self address)
-        for status in self.context.nodes.values():
-            if status.address_name != self.local_node_name:
-                if status.state == AddressStates.RUNNING:
-                    func(status.address_name)
-                    self.logger.warn('supervisord {} on {}'.format(func.__name__, status.address_name))
-                else:
-                    self.logger.info('cannot {} supervisord on {}: Remote state is {}'
-                                     .format(func.__name__, status.address_name, status.state.name))
-        # send request to self supervisord
-        func(self.local_node_name)
-
 
 class InitializationState(AbstractState):
     """ In the INITIALIZATION state, Supvisors synchronizes to all known instances.
@@ -109,20 +95,11 @@ class InitializationState(AbstractState):
         - start_date: the date when entering this state.
     """
 
-    def __init__(self, supvisors: Any):
-        """ Initialization of the attributes.
-
-        :param supvisors: the global Supvisors structure
-        """
-        AbstractState.__init__(self, supvisors)
-        self.start_date = 0
-
     def enter(self) -> None:
         """ When entering in the INITIALIZATION state, reset the context.
 
         :return: None
         """
-        self.start_date = int(time())
         # clear any existing job
         self.abort_jobs()
         # reset context, keeping the isolation status
@@ -131,25 +108,23 @@ def enter(self) -> None:
     def next(self) -> SupvisorsStates:
         """ Wait for nodes to publish until:
             - all are active,
-            - or all defined in the optional *force_synchro_if* option are active,
+            - or all core nodes defined in the optional *force_synchro_if* option are active,
             - or timeout is reached.
 
         :return: the new Supvisors state
         """
-        # cannot get out of this state without local supervisor RUNNING
+        if (time() - self.context.start_date) > self.supvisors.options.synchro_timeout:
+            self.logger.warn('InitializationState.next: synchro timed out')
+        # cannot get out of this state without local node RUNNING
         running_nodes = self.context.running_nodes()
         if self.local_node_name in running_nodes:
             # synchro done if the state of all nodes is known
             if len(self.context.unknown_nodes()) == 0:
-                self.logger.info('InitializationState.next: all nodes are RUNNING')
-                return SupvisorsStates.DEPLOYMENT
-            # synchro done if the state of all forced nodes is known
-            if self.context.forced_nodes and len(self.context.unknown_forced_nodes()) == 0:
-                self.logger.info('InitializationState.next: all forced nodes are RUNNING')
+                self.logger.info('InitializationState.next: all nodes are in a known state')
                 return SupvisorsStates.DEPLOYMENT
-            # if synchro timeout reached, stop synchro and work with known nodes
-            if (time() - self.start_date) > self.supvisors.options.synchro_timeout:
-                self.logger.warn('InitializationState.next: synchro timed out')
+            # synchro done all core nodes are running
+            if self.context.running_core_nodes():
+                self.logger.info('InitializationState.next: all core nodes are RUNNING')
                 return SupvisorsStates.DEPLOYMENT
             self.logger.debug('InitializationState.next: still waiting for remote Supvisors instances to publish')
         else:
@@ -163,33 +138,29 @@ def exit(self) -> None:
         :return: None
         """
         # force state of missing Supvisors instances
-        self.context.end_synchro()
         node_names = self.context.running_nodes()
         self.logger.info('InitializationState.exit: working with nodes {}'.format(node_names))
         # elect master node among working nodes only if not fixed before
         if not self.context.master_node_name:
+            # choose Master among the core nodes because these nodes are expected to be more present
+            if self.supvisors.options.force_synchro_if:
+                running_core_node_names = set(node_names).intersection(self.supvisors.options.force_synchro_if)
+                if running_core_node_names:
+                    node_names = running_core_node_names
             # arbitrarily choice : master node has the 'lowest' node_name among running nodes
             self.context.master_node_name = min(node_names)
 
 
-class DeploymentState(AbstractState):
+class MasterDeploymentState(AbstractState):
     """ In the DEPLOYMENT state, Supvisors starts automatically the applications having a starting model. """
 
     def enter(self):
-        """ When entering in the DEPLOYMENT state, define the start and stop sequences.
-        Only the MASTER can perform the automatic start and stop. """
-        for application in self.context.applications.values():
-            application.update_sequences()
-            # TODO: application update_status is here because node invalidation does not do the job ?
-            application.update_status()
-        # only the Supvisors master starts applications
-        if self.context.is_master:
-            self.supvisors.starter.start_applications()
+        """ Trigger the automatic start and stop. """
+        self.supvisors.fsm.redeploy_mark = False
+        self.supvisors.starter.start_applications()
 
     def next(self) -> SupvisorsStates:
-        """ 2 conditions to exit the state:
-            - local is master and starting sequence is over,
-            - local is not master and the master node has declared itself operational.
+        """ Check if the starting tasks are completed.
 
         :return: the new Supvisors state
         """
@@ -197,25 +168,16 @@ def next(self) -> SupvisorsStates:
         next_state = self.check_nodes()
         if next_state:
             return next_state
-        # normal behavior
-        if self.context.is_master and self.supvisors.starter.check_starting() \
-                or not self.context.is_master and self.context.master_operational:
+        # Master goes to OPERATION when starting is completed
+        if self.supvisors.starter.is_starting_completed():
             return SupvisorsStates.OPERATION
+        # stay in current state
         return SupvisorsStates.DEPLOYMENT
 
 
-class OperationState(AbstractState):
+class MasterOperationState(AbstractState):
     """ In the OPERATION state, Supvisors is waiting for requests. """
 
-    def enter(self) -> None:
-        """ When entering in the OPERATION state, the master notifies all other Supvisors instances,
-        so that themselves enter in OPERATION state.
-
-        :return: None
-        """
-        if self.context.is_master:
-            self.context.master_operational = True
-
     def next(self) -> SupvisorsStates:
         """ Check that all nodes are still active.
         Look after possible conflicts due to multiple running instances of the same program. """
@@ -224,28 +186,25 @@ def next(self) -> SupvisorsStates:
         if next_state:
             return next_state
         # normal behavior. check if jobs in progress
-        if self.supvisors.starter.check_starting() and self.supvisors.stopper.check_stopping():
+        if self.supvisors.starter.is_starting_completed() and self.supvisors.stopper.is_stopping_completed():
             # check duplicated processes
             if self.context.conflicting():
                 return SupvisorsStates.CONCILIATION
             # a redeploy mark has set due to a new node in Supvisors
             # back to DEPLOYMENT state to repair what may have failed before
             if self.supvisors.fsm.redeploy_mark:
-                self.supvisors.fsm.redeploy_mark = False
                 return SupvisorsStates.DEPLOYMENT
         return SupvisorsStates.OPERATION
 
 
-class ConciliationState(AbstractState):
+class MasterConciliationState(AbstractState):
     """ In the CONCILIATION state, Supvisors conciliates the conflicts. """
 
     def enter(self) -> None:
-        """ When entering in the CONCILIATION state, conciliate automatically the conflicts.
-        Only the MASTER can conciliate conflicts. """
-        if self.context.is_master:
-            conciliate_conflicts(self.supvisors,
-                                 self.supvisors.options.conciliation_strategy,
-                                 self.context.conflicts())
+        """ When entering in the CONCILIATION state, conciliate automatically the conflicts. """
+        conciliate_conflicts(self.supvisors,
+                             self.supvisors.options.conciliation_strategy,
+                             self.context.conflicts())
 
     def next(self) -> SupvisorsStates:
         """ Check that all addresses are still active.
@@ -255,45 +214,46 @@ def next(self) -> SupvisorsStates:
         if next_state:
             return next_state
         # check eventual jobs in progress
-        if self.supvisors.starter.check_starting() and self.supvisors.stopper.check_stopping():
+        if self.supvisors.starter.is_starting_completed() and self.supvisors.stopper.is_stopping_completed():
             # back to OPERATION when there is no conflict anymore
             if not self.context.conflicting():
-                if self.context.is_master or self.context.master_operational:
-                    return SupvisorsStates.OPERATION
+                return SupvisorsStates.OPERATION
             # new conflicts may happen while conciliation is in progress
             # call enter again to trigger a new conciliation
             self.enter()
         return SupvisorsStates.CONCILIATION
 
 
-class RestartingState(AbstractState):
+class MasterRestartingState(AbstractState):
     """ In the RESTARTING state, Supvisors stops all applications before triggering a full restart. """
 
     def enter(self) -> None:
         """ When entering in the RESTARTING state, stop all applications.
         The current design is that the current node drives the job and not necessarily the Master.
-        FIXME Issue #92: If the current node becomes silent in this phase, it will leave Supvisors in a bad state
-         with applications partially stopped and other Supvisors instances not aware.
 
-        :return:
+        :return: None
         """
         self.abort_jobs()
         self.supvisors.stopper.stop_applications()
 
     def next(self) -> SupvisorsStates:
         """ Wait for all processes to be stopped. """
-        # FIXME: upon local failure, it can't be assumed here that we can go back to INITIALIZATION state
-        #  so just exit ? sync by Master ?
-        if self.supvisors.stopper.check_stopping():
+        next_state = self.check_nodes()
+        if next_state:
+            # no way going back to INITIALIZATION state at this point
+            return SupvisorsStates.SHUTDOWN
+        if self.supvisors.stopper.is_stopping_completed():
             return SupvisorsStates.SHUTDOWN
         return SupvisorsStates.RESTARTING
 
     def exit(self) -> None:
         """ When leaving the RESTARTING state, request the full restart. """
-        self.apply_nodes_func(self.supvisors.zmq.pusher.send_restart)
+        self.supvisors.zmq.pusher.send_restart(self.local_node_name)
+        # other nodes will shutdown on reception of SHUTDOWN state
+        # due to Supvisors design, the state publication will be fired before the send_shutdown
 
 
-class ShuttingDownState(AbstractState):
+class MasterShuttingDownState(AbstractState):
     """ In the SHUTTING_DOWN state, Supvisors stops all applications before triggering a full shutdown. """
 
     def enter(self):
@@ -304,20 +264,70 @@ def enter(self):
     def next(self):
         """ Wait for all processes to be stopped. """
         # check eventual jobs in progress
-        # FIXME: same issue as in RestartingState
-        if self.supvisors.stopper.check_stopping():
+        next_state = self.check_nodes()
+        if next_state:
+            # no way going back to INITIALIZATION state at this point
+            return SupvisorsStates.SHUTDOWN
+        if self.supvisors.stopper.is_stopping_completed():
             return SupvisorsStates.SHUTDOWN
         return SupvisorsStates.SHUTTING_DOWN
 
     def exit(self):
-        """ When leaving the SHUTTING_DOWN state, request the full shutdown. """
-        self.apply_nodes_func(self.supvisors.zmq.pusher.send_shutdown)
+        """ When leaving the SHUTTING_DOWN state, request the Supervisor shutdown. """
+        self.supvisors.zmq.pusher.send_shutdown(self.local_node_name)
+        # other nodes will shutdown on reception of SHUTDOWN state
+        # due to Supvisors design, the state publication will be fired before the send_shutdown
 
 
 class ShutdownState(AbstractState):
     """ This is the final state. """
 
 
+class SlaveMainState(AbstractState):
+
+    def next(self) -> SupvisorsStates:
+        """ the non-master instances are just checking if local and Master instances are still running.
+
+        :return: the next state
+        """
+        # common check on local and Master nodes
+        next_state = self.check_nodes()
+        if next_state:
+            return next_state
+
+
+class SlaveRestartingState(AbstractState):
+    """ In the RESTARTING state, Supvisors stops all applications before triggering a full restart. """
+
+    def enter(self) -> None:
+        """ When entering in the RESTARTING state, abort all pending tasks applications.
+
+        :return: None
+        """
+        self.abort_jobs()
+
+    def next(self) -> SupvisorsStates:
+        """ Wait for all processes to be stopped. """
+        next_state = self.check_nodes()
+        if next_state:
+            # no way going back to INITIALIZATION state at this point
+            return SupvisorsStates.SHUTDOWN
+
+    def exit(self) -> None:
+        """ When leaving the RESTARTING state, request the Supervisor restart. """
+        self.supvisors.zmq.pusher.send_restart(self.local_node_name)
+
+
+class SlaveShuttingDownState(SlaveRestartingState):
+    """ In the SHUTTING_DOWN state, Supvisors stops all applications before triggering a full shutdown.
+    Only the exit actions are different from the RESTARTING state.
+    """
+
+    def exit(self) -> None:
+        """ When leaving the SHUTTING_DOWN state, request the Supervisor shutdown. """
+        self.supvisors.zmq.pusher.send_shutdown(self.local_node_name)
+
+
 class FiniteStateMachine:
     """ This class implements a very simple behaviour of FiniteStateMachine based on a single event.
     A state is able to evaluate itself for transitions. """
@@ -328,11 +338,11 @@ def __init__(self, supvisors: Any) -> None:
         :param supvisors: the Supvisors global structure
         """
         self.supvisors = supvisors
-        self.context = supvisors.context
-        self.logger = supvisors.logger
-        self.state = None
-        self.instance = AbstractState(supvisors)
-        self.redeploy_mark = False
+        self.context: Context = supvisors.context
+        self.logger: Logger = supvisors.logger
+        self.state: Optional[SupvisorsStates] = None
+        self.instance: AbstractState = AbstractState(supvisors)
+        self.redeploy_mark: bool = False
         # Trigger first state / INITIALIZATION
         self.set_state(SupvisorsStates.INITIALIZATION)
 
@@ -344,16 +354,19 @@ def next(self) -> None:
         """
         self.set_state(self.instance.next())
 
-    def set_state(self, next_state: SupvisorsStates) -> None:
+    def set_state(self, next_state: SupvisorsStates, force_transition: bool = None) -> None:
         """ Update the current state of the state machine and transitions as long as possible.
+        The transition can be forced, especially when getting the first Master state.
 
         :param next_state: the new state
+        :param force_transition: if True, transition validity is not checked
         :return: None
         """
-        while next_state != self.state:
-            if next_state not in self._Transitions[self.state]:
-                self.logger.error('FiniteStateMachine.on_authorization: unexpected transition from {} to {}'
-                                  .format(self.state, next_state))
+        while next_state is not None and next_state != self.state:
+            # check that the transition is allowed
+            if not force_transition and next_state not in self._Transitions[self.state]:
+                self.logger.critical('FiniteStateMachine.set_state: unexpected transition from {} to {}'
+                                     .format(self.state, next_state))
                 break
             # exit the current state
             self.instance.exit()
@@ -365,7 +378,10 @@ def set_state(self, next_state: SupvisorsStates) -> None:
                 self.supvisors.zmq.internal_publisher.send_state_event(self.serial())
                 self.supvisors.zmq.publisher.send_supvisors_status(self.serial())
             # create the new state and enters it
-            self.instance = self._StateInstances[self.state](self.supvisors)
+            if self.context.is_master:
+                self.instance = self._MasterStateInstances[self.state](self.supvisors)
+            else:
+                self.instance = self._SlaveStateInstances[self.state](self.supvisors)
             self.instance.enter()
             # evaluate current state
             next_state = self.instance.next()
@@ -382,13 +398,12 @@ def on_timer_event(self) -> NameList:
             for process in process_failures:
                 self.supvisors.failure_handler.add_default_job(process)
             self.supvisors.failure_handler.trigger_jobs()
-        # check if new isolating remotes and return the list of newly isolated addresses
+        # check if new isolating remotes and return the list of newly isolated nodes
         return self.context.handle_isolation()
 
     def on_tick_event(self, node_name: str, event: Payload):
         """ This event is used to refresh the data related to the address. """
         self.context.on_tick_event(node_name, event)
-        # could call the same behaviour as on_timer_event if necessary
 
     def on_process_event(self, node_name: str, event: Payload) -> None:
         """ This event is used to refresh the process data related to the event and address.
@@ -406,20 +421,27 @@ def on_process_event(self, node_name: str, event: Payload) -> None:
             # feed stopper with event
             self.supvisors.stopper.on_event(process)
             # trigger an automatic (so master only) behaviour for a running failure
-            if process.crashed() and self.context.is_master:
+            # process crash triggered only if running failure strategy related to application
+            # Supvisors does not replace Supervisor in the present matter (use autorestart if necessary)
+            if process.crashed() and self.context.is_master and \
+                    process.rules.running_failure_strategy in [RunningFailureStrategies.STOP_APPLICATION,
+                                                               RunningFailureStrategies.RESTART_APPLICATION]:
                 self.supvisors.failure_handler.add_default_job(process)
 
     def on_state_event(self, node_name, event: Payload) -> None:
-        """ This event is used to get te operational state of the master node.
+        """ This event is used to get the FSM state of the master node.
 
         :param node_name: the node that sent the event
         :param event: the state event
         :return: None
         """
-        if node_name == self.context.master_node_name:
-            self.context.master_operational = SupvisorsStates(event['statecode']) == SupvisorsStates.OPERATION
+        if not self.context.is_master and node_name == self.context.master_node_name:
+            master_state = SupvisorsStates(event['statecode'])
+            self.logger.info('FiniteStateMachine.on_state_event: Master node_name={} transitioned to state={}'
+                             .format(node_name, master_state))
+            self.set_state(master_state)
 
-    def on_process_info(self, node_name: str, info: Payload) -> None:
+    def on_process_info(self, node_name: str, info: PayloadList) -> None:
         """ This event is used to fill the internal structures with processes available on node.
 
         :param node_name: the node that sent the event
@@ -440,7 +462,7 @@ def on_authorization(self, node_name: str, authorized: bool, master_node_name: s
         :return: None
         """
         self.logger.info('FiniteStateMachine.on_authorization: node_name={} authorized={} master_node_name={}'
-                         .format(node_name, authorized, master_node_name, supvisors_state))
+                         ' supvisors_state={}'.format(node_name, authorized, master_node_name, supvisors_state))
         if self.context.on_authorization(node_name, authorized):
             # a new node comes in group
             # a DEPLOYMENT phase is considered as applications could not be fully started due to this missing node
@@ -461,33 +483,42 @@ def on_authorization(self, node_name: str, authorized: bool, master_node_name: s
                     self.logger.warn('FiniteStateMachine.on_authorization: accept master_node={} declared by node={}'
                                      .format(master_node_name, node_name))
                     self.context.master_node_name = master_node_name
-                if master_node_name == node_name and supvisors_state == SupvisorsStates.OPERATION:
-                    # if the remote node is the master, it must be operational
-                    self.context.master_operational = True
                 if master_node_name != self.context.master_node_name:
                     # 2 different perceptions of the master, likely due to a split-brain situation
-                    # unset useless redeploy_mark
-                    self.redeploy_mark = False
                     # so going back to INITIALIZATION to fix
                     self.logger.warn('FiniteStateMachine.on_authorization: master node conflict. '
                                      ' local declares {} - remote ({}) declares {}'
                                      .format(self.context.master_node_name, node_name, master_node_name))
                     # no need to restrict to [DEPLOYMENT, OPERATION, CONCILIATION] as other transitions are forbidden
                     self.set_state(SupvisorsStates.INITIALIZATION)
+                elif master_node_name == node_name:
+                    # accept the remote Master state
+                    # FIXME: not possible as long as local node itself is not authorized !
+                    self.logger.info('FiniteStateMachine.on_authorization: Master node_name={} is in state={}'
+                                     .format(node_name, supvisors_state))
+                    self.set_state(supvisors_state, True)
 
     def on_restart(self) -> None:
         """ This event is used to transition the state machine to the RESTARTING state.
 
         :return: None
         """
-        self.set_state(SupvisorsStates.RESTARTING)
+        if self.context.is_master:
+            self.set_state(SupvisorsStates.RESTARTING)
+        else:
+            # re-route command to Master
+            self.supvisors.zmq.pusher.send_restart_all(self.context.master_node_name)
 
     def on_shutdown(self) -> None:
         """ This event is used to transition the state machine to the SHUTTING_DOWN state.
 
         :return: None
         """
-        self.set_state(SupvisorsStates.SHUTTING_DOWN)
+        if self.context.is_master:
+            self.set_state(SupvisorsStates.SHUTTING_DOWN)
+        else:
+            # re-route command to Master
+            self.supvisors.zmq.pusher.send_shutdown_all(self.context.master_node_name)
 
     # serialization
     def serial(self) -> Payload:
@@ -498,13 +529,21 @@ def serial(self) -> Payload:
         return {'statecode': self.state.value, 'statename': self.state.name}
 
     # Map between state enumerations and classes
-    _StateInstances = {SupvisorsStates.INITIALIZATION: InitializationState,
-                       SupvisorsStates.DEPLOYMENT: DeploymentState,
-                       SupvisorsStates.OPERATION: OperationState,
-                       SupvisorsStates.CONCILIATION: ConciliationState,
-                       SupvisorsStates.RESTARTING: RestartingState,
-                       SupvisorsStates.SHUTTING_DOWN: ShuttingDownState,
-                       SupvisorsStates.SHUTDOWN: ShutdownState}
+    _MasterStateInstances = {SupvisorsStates.INITIALIZATION: InitializationState,
+                             SupvisorsStates.DEPLOYMENT: MasterDeploymentState,
+                             SupvisorsStates.OPERATION: MasterOperationState,
+                             SupvisorsStates.CONCILIATION: MasterConciliationState,
+                             SupvisorsStates.RESTARTING: MasterRestartingState,
+                             SupvisorsStates.SHUTTING_DOWN: MasterShuttingDownState,
+                             SupvisorsStates.SHUTDOWN: ShutdownState}
+
+    _SlaveStateInstances = {SupvisorsStates.INITIALIZATION: InitializationState,
+                            SupvisorsStates.DEPLOYMENT: SlaveMainState,
+                            SupvisorsStates.OPERATION: SlaveMainState,
+                            SupvisorsStates.CONCILIATION: SlaveMainState,
+                            SupvisorsStates.RESTARTING: SlaveRestartingState,
+                            SupvisorsStates.SHUTTING_DOWN: SlaveShuttingDownState,
+                            SupvisorsStates.SHUTDOWN: ShutdownState}
 
     # Transitions allowed between states
     _Transitions = {None: [SupvisorsStates.INITIALIZATION],
diff --git a/supvisors/strategy.py b/supvisors/strategy.py
index 04c9b060..7e4f3d32 100644
--- a/supvisors/strategy.py
+++ b/supvisors/strategy.py
@@ -17,15 +17,20 @@
 # limitations under the License.
 # ======================================================================
 
-from typing import Mapping, Sequence, Tuple
+from typing import Any, Mapping, Optional, Sequence, Set, Tuple
 
+from .application import ApplicationStatus
+from .process import ProcessStatus
 from .ttypes import AddressStates, NameList, ConciliationStrategies, StartingStrategies, RunningFailureStrategies
 
+# types for annotations
+LoadRequestMap = Mapping[str, int]
+
 
 class AbstractStrategy(object):
     """ Base class for a common constructor. """
 
-    def __init__(self, supvisors):
+    def __init__(self, supvisors: Any):
         """ Initialization of the attributes.
 
         :param supvisors: the global Supvisors instance
@@ -43,36 +48,39 @@ class AbstractStartingStrategy(AbstractStrategy):
     LoadingValidityMap = Mapping[str, LoadingValidity]
     NodeLoadMap = Sequence[Tuple[str, int]]
 
-    def is_loading_valid(self, node_name: str, expected_load: int) -> LoadingValidity:
+    def is_loading_valid(self, node_name: str, expected_load: int, load_request_map: LoadRequestMap) -> LoadingValidity:
         """ Return True and current load if remote Supvisors instance is active
         and can support the additional load.
 
         :param node_name: the node name tested
         :param expected_load: the load to add to the node
+        :param load_request_map: the unconsidered loads
         :return: a tuple with a boolean telling if the additional load is possible on node and the current load
         """
-        self.logger.trace('AbstractStartingStrategy.is_loading_valid: node_name={} expected_load={}'
-                          .format(node_name, expected_load))
+        self.logger.trace('AbstractStartingStrategy.is_loading_valid: node_name={} expected_load={} load_request_map={}'
+                          .format(node_name, expected_load, load_request_map))
         if node_name in self.supvisors.context.nodes.keys():
             status = self.supvisors.context.nodes[node_name]
             self.logger.trace('AbstractStartingStrategy.is_loading_valid: node {} state={}'
                               .format(node_name, status.state.name))
             if status.state == AddressStates.RUNNING:
-                load = status.get_load()
-                self.logger.debug('AbstractStartingStrategy.is_loading_valid:node={} loading={} expected_load={}'
-                                  .format(node_name, load, expected_load))
-                return load + expected_load < 100, load
+                loading = status.get_loading() + load_request_map.get(node_name, 0)
+                self.logger.debug('AbstractStartingStrategy.is_loading_valid: node_name={} loading={} expected_load={}'
+                                  .format(node_name, loading, expected_load))
+                return loading + expected_load <= 100, loading
             self.logger.trace('AbstractStartingStrategy.is_loading_valid: node {} not RUNNING'.format(node_name))
         return False, 0
 
-    def get_loading_and_validity(self, node_names: NameList, expected_load: int) -> LoadingValidityMap:
+    def get_loading_and_validity(self, node_names: NameList, expected_load: int,
+                                 load_request_map: LoadRequestMap) -> LoadingValidityMap:
         """ Return the report of loading capability of all nodes iaw the additional load required.
 
         :param node_names: the nodes considered
-        :param expected_load: the additional load to consider
+        :param expected_load: the additional load to consider for the program to be started
+        :param load_request_map: the unconsidered loads
         :return: the list of nodes that can hold the additional load
         """
-        loading_validity_map = {node_name: self.is_loading_valid(node_name, expected_load)
+        loading_validity_map = {node_name: self.is_loading_valid(node_name, expected_load, load_request_map)
                                 for node_name in node_names}
         self.logger.trace('AbstractStartingStrategy.get_loading_and_validity: loading_validity_map={}'
                           .format(loading_validity_map))
@@ -87,26 +95,54 @@ def sort_valid_by_loading(self, loading_validity_map: LoadingValidityMap) -> Nod
         self.logger.trace('AbstractStartingStrategy.sort_valid_by_loading: sorted_nodes={}'.format(sorted_nodes))
         return sorted_nodes
 
+    def get_node(self, node_names: NameList, expected_load: int, load_request_map: LoadRequestMap) -> Optional[str]:
+        """ Choose the node that can support the additional load requested.
+        The load of the processes that have just been requested to start are to be considered separately because they
+        are not considered yet in AddressStatus.
+
+        :param node_names: the candidate nodes
+        :param expected_load: the load of the program to be started
+        :param load_request_map: the unconsidered loads
+        :return: the list of nodes that can hold the additional load
+        """
+        raise NotImplementedError
+
 
 class ConfigStrategy(AbstractStartingStrategy):
     """ Strategy designed to choose the node using the order defined in the configuration file. """
 
-    def get_node(self, node_names, expected_load):
-        """ Choose the first node that can support the additional load requested. """
-        self.logger.debug('ConfigStrategy.get_node: node_names={} expected_load={}'
-                          .format(node_names, expected_load))
-        loading_validity_map = self.get_loading_and_validity(node_names, expected_load)
+    def get_node(self, node_names: NameList, expected_load: int, load_request_map: LoadRequestMap) -> Optional[str]:
+        """ Choose the first node in the list that can support the additional load requested.
+        The load of the processes that have just been requested to start are to be considered separately because they
+        are not considered yet in AddressStatus.
+
+        :param node_names: the candidate nodes
+        :param expected_load: the load of the program to be started
+        :param load_request_map: the unconsidered loads
+        :return: the list of nodes that can hold the additional load
+        """
+        self.logger.debug('ConfigStrategy.get_node: node_names={} expected_load={} request_map={}'
+                          .format(node_names, expected_load, load_request_map))
+        loading_validity_map = self.get_loading_and_validity(node_names, expected_load, load_request_map)
         return next((node_name for node_name, (validity, _) in loading_validity_map.items() if validity), None)
 
 
 class LessLoadedStrategy(AbstractStartingStrategy):
     """ Strategy designed to share the loading among all the nodes. """
 
-    def get_node(self, node_names, expected_load):
-        """ Choose the node having the lowest loading that can support the additional load requested. """
-        self.logger.trace('LessLoadedStrategy.get_node: node_names={} expected_load={}'
-                          .format(node_names, expected_load))
-        loading_validity_map = self.get_loading_and_validity(node_names, expected_load)
+    def get_node(self, node_names: NameList, expected_load: int, load_request_map: LoadRequestMap) -> Optional[str]:
+        """ Choose the node having the lowest loading that can support the additional load requested.
+        The load of the processes that have just been requested to start are to be considered separately because they
+        are not considered yet in AddressStatus.
+
+        :param node_names: the candidate nodes
+        :param expected_load: the load of the program to be started
+        :param load_request_map: the unconsidered loads
+        :return: the list of nodes that can hold the additional load
+        """
+        self.logger.trace('LessLoadedStrategy.get_node: node_names={} expected_load={} request_map={}'
+                          .format(node_names, expected_load, load_request_map))
+        loading_validity_map = self.get_loading_and_validity(node_names, expected_load, load_request_map)
         sorted_nodes = self.sort_valid_by_loading(loading_validity_map)
         return sorted_nodes[0][0] if sorted_nodes else None
 
@@ -114,10 +150,19 @@ def get_node(self, node_names, expected_load):
 class MostLoadedStrategy(AbstractStartingStrategy):
     """ Strategy designed to maximize the loading of a node. """
 
-    def get_node(self, node_names, expected_load):
-        """ Choose the node having the highest loading that can support the additional load requested. """
-        self.logger.trace('MostLoadedStrategy: node_names={} expected_load={}'.format(node_names, expected_load))
-        loading_validity_map = self.get_loading_and_validity(node_names, expected_load)
+    def get_node(self, node_names: NameList, expected_load: int, load_request_map: LoadRequestMap) -> Optional[str]:
+        """ Choose the node having the highest loading that can support the additional load requested.
+        The load of the processes that have just been requested to start are to be considered separately because they
+        are not considered yet in AddressStatus.
+
+        :param node_names: the candidate nodes
+        :param expected_load: the load of the program to be started
+        :param load_request_map: the unconsidered loads
+        :return: the list of nodes that can hold the additional load
+        """
+        self.logger.trace('MostLoadedStrategy: node_names={} expected_load={} load_request_map={}'
+                          .format(node_names, expected_load, load_request_map))
+        loading_validity_map = self.get_loading_and_validity(node_names, expected_load, load_request_map)
         sorted_nodes = self.sort_valid_by_loading(loading_validity_map)
         return sorted_nodes[-1][0] if sorted_nodes else None
 
@@ -125,15 +170,25 @@ def get_node(self, node_names, expected_load):
 class LocalStrategy(AbstractStartingStrategy):
     """ Strategy designed to start the process on the local node. """
 
-    def get_node(self, node_names, expected_load):
-        """ Choose the local node provided that it can support the additional load requested. """
-        self.logger.trace('LocalStrategy: node_names={} expected_load={}'.format(node_names, expected_load))
-        loading_validity_map = self.get_loading_and_validity(node_names, expected_load)
+    def get_node(self, node_names: NameList, expected_load: int, load_request_map: LoadRequestMap) -> Optional[str]:
+        """ Choose the local node provided that it can support the additional load requested.
+        The load of the processes that have just been requested to start are to be considered separately because they
+        are not considered yet in AddressStatus.
+
+        :param node_names: the candidate nodes
+        :param expected_load: the load of the program to be started
+        :param load_request_map: the unconsidered loads
+        :return: the list of nodes that can hold the additional load
+        """
+        self.logger.trace('LocalStrategy: node_names={} expected_load={} load_request_map={}'
+                          .format(node_names, expected_load, load_request_map))
+        loading_validity_map = self.get_loading_and_validity(node_names, expected_load, load_request_map)
         local_node_name = self.supvisors.address_mapper.local_node_name
         return local_node_name if loading_validity_map.get(local_node_name, (False,))[0] else None
 
 
-def get_node(supvisors, strategy, node_rules, expected_load):
+def get_node(supvisors: Any, strategy: StartingStrategies, node_rules: NameList, expected_load: int,
+             load_request_map: LoadRequestMap) -> Optional[str]:
     """ Creates a strategy and let it find a node to start a process having a defined load. """
     instance = None
     if strategy == StartingStrategies.CONFIG:
@@ -145,7 +200,7 @@ def get_node(supvisors, strategy, node_rules, expected_load):
     if strategy == StartingStrategies.LOCAL:
         instance = LocalStrategy(supvisors)
     # apply strategy result
-    return instance.get_node(node_rules, expected_load) if instance else None
+    return instance.get_node(node_rules, expected_load, load_request_map) if instance else None
 
 
 # Strategy management for Conciliation
@@ -283,13 +338,13 @@ class RunningFailureHandler(AbstractStrategy):
     def __init__(self, supvisors):
         AbstractStrategy.__init__(self, supvisors)
         # the initial jobs
-        self.stop_application_jobs = set()
-        self.restart_application_jobs = set()
-        self.restart_process_jobs = set()
-        self.continue_process_jobs = set()
+        self.stop_application_jobs: Set[ApplicationStatus] = set()
+        self.restart_application_jobs: Set[ApplicationStatus] = set()
+        self.restart_process_jobs: Set[ProcessStatus] = set()
+        self.continue_process_jobs: Set[ProcessStatus] = set()
         # the deferred jobs
-        self.start_application_jobs = set()
-        self.start_process_jobs = set()
+        self.start_application_jobs: Set[ApplicationStatus] = set()
+        self.start_process_jobs: Set[ProcessStatus] = set()
 
     def abort(self):
         """ Clear all sets. """
@@ -300,144 +355,235 @@ def abort(self):
         self.start_application_jobs = set()
         self.start_process_jobs = set()
 
+    def add_stop_application_job(self, application: ApplicationStatus) -> None:
+        """ Add the application name to the stop_application_jobs, checking if this job supersedes other jobs.
+
+        :param application: the application to stop due to a failed process
+        :return: None
+        """
+        self.logger.info('RunningFailureHandler.add_stop_application_job: adding {}'.format(application.application_name))
+        self.stop_application_jobs.add(application)
+        # stop_application_jobs take precedence over all other jobs related to this application
+        self.restart_application_jobs.discard(application)
+        self.start_application_jobs.discard(application)
+        for job_set in [self.restart_process_jobs, self.start_process_jobs, self.continue_process_jobs]:
+            for process in list(job_set):
+                if process.application_name == application.application_name:
+                    job_set.discard(process)
+
+    def add_restart_application_job(self, application: ApplicationStatus) -> None:
+        """ Add the application name to the restart_application_jobs, checking if this job supersedes other jobs
+        and assuming that stop_application_jobs and start_application_jobs (deferred restart) take precedence
+        over restart_application_jobs.
+
+        :param application: the application to restart due to a failed process
+        :return: None
+        """
+        if application in self.stop_application_jobs | self.start_application_jobs:
+            self.logger.info('RunningFailureHandler.add_restart_application_job: {} not added because already'
+                             ' in stop_application_jobs or start_application_jobs'
+                             .format(application.application_name))
+            return
+        self.logger.info('RunningFailureHandler.add_restart_application_job: adding {}'
+                         .format(application.application_name))
+        self.restart_application_jobs.add(application)
+        # restart_application_jobs take precedence over all process jobs
+        # remove only processes that are declared in the application start sequence
+        sequenced_processes = application.get_start_sequenced_processes()
+        for job_set in [self.restart_process_jobs, self.start_process_jobs, self.continue_process_jobs]:
+            for process in list(job_set):
+                if process.application_name == application.application_name and process in sequenced_processes:
+                    job_set.remove(process)
+
+    def add_restart_process_job(self, application: ApplicationStatus, process: ProcessStatus) -> None:
+        """ Add the process to the restart_process_jobs, checking if this job supersedes other jobs and assuming that:
+            * stop_application_jobs takes precedence over restart_process_jobs ;
+            * restart_application_jobs takes precedence over restart_process_jobs if the process is in the application starting sequence ;
+            * start_application_jobs (deferred application restart) takes precedence over restart_process_jobs ;
+            * start_process_jobs (deferred process restart) takes precedence over restart_process_jobs.
+
+        :param application: the application including the failed process to restart
+        :param process: the failed process to restart
+        :return: None
+        """
+        if application in self.stop_application_jobs:
+            self.logger.info('RunningFailureHandler.add_restart_process_job: {} not added because {} already'
+                             ' in stop_application_jobs'.format(process.namespec, process.application_name))
+            return
+        if application in self.restart_application_jobs | self.start_application_jobs:
+            if process in application.get_start_sequenced_processes():
+                self.logger.info('RunningFailureHandler.add_restart_process_job: {} not added because {} already'
+                                 ' in stop_application_jobs'.format(process.namespec, process.application_name))
+                return
+        if process in self.start_process_jobs:
+            self.logger.info('RunningFailureHandler.add_continue_process_job: {} not added because already'
+                             ' in start_process_jobs'.format(process.namespec))
+            return
+        self.logger.info('RunningFailureHandler.add_restart_process_job: adding {}'.format(process.namespec))
+        self.restart_process_jobs.add(process)
+        # restart_process_jobs take precedence over continue_process_jobs
+        self.continue_process_jobs.discard(process)
+
+    def add_continue_process_job(self, application: ApplicationStatus, process: ProcessStatus) -> None:
+        """ Add the application name to the continue_process_jobs, checking if this job supersedes other jobs
+        and assuming that all other jobs takes precedence over continue_process_jobs.
+
+        :param application: the application including the failed process
+        :param process: the failed process
+        :return: None
+        """
+        if application in self.stop_application_jobs:
+            self.logger.info('RunningFailureHandler.add_continue_process_job: {} not added because {} already'
+                             ' in stop_application_jobs'.format(process.namespec, process.application_name))
+            return
+        if application in self.restart_application_jobs | self.start_application_jobs:
+            if process in application.get_start_sequenced_processes():
+                self.logger.info('RunningFailureHandler.add_continue_process_job: {} not added because already'
+                                 ' in stop_application_jobs'.format(process.namespec, process.application_name))
+                return
+        if process in self.restart_process_jobs | self.start_process_jobs:
+            self.logger.info('RunningFailureHandler.add_continue_process_job: {} not added because already'
+                             ' in restart_process_jobs or start_process_jobs'.format(process.namespec))
+            return
+        self.logger.info('RunningFailureHandler.add_continue_process_job: adding {}'.format(process.namespec))
+        self.continue_process_jobs.add(process)
+
     def add_job(self, strategy, process):
         """ Add a process or the related application name in the relevant set,
         iaw the strategy set in parameter and the priorities defined above. """
         self.logger.trace('RunningFailureHandler.add_job: START stop_application_jobs={} restart_application_jobs={}'
-                          ' restart_application_jobs={} restart_process_jobs={} continue_process_jobs={}'
+                          ' restart_process_jobs={} continue_process_jobs={}'
                           ' start_application_jobs={} start_process_jobs={}'
-                          .format(self.stop_application_jobs, self.restart_application_jobs,
-                                  self.restart_application_jobs, self.restart_process_jobs, self.continue_process_jobs,
-                                  self.start_application_jobs, self.start_process_jobs))
-        application_name = process.application_name
+                          .format(self.stop_application_jobs, self.restart_application_jobs, self.restart_process_jobs,
+                                  self.continue_process_jobs, self.start_application_jobs, self.start_process_jobs))
+        application = self.supvisors.context.applications[process.application_name]
+        # new job may supersede others of may be superseded by existing ones
         if strategy == RunningFailureStrategies.STOP_APPLICATION:
-            self.logger.info('RunningFailureHandler.add_job: adding {} to stop_application_jobs'
-                             .format(application_name))
-            self.stop_application_jobs.add(application_name)
-            self.restart_application_jobs.discard(application_name)
-            self.restart_process_jobs = set(filter(lambda x: x.application_name != application_name,
-                                                   self.restart_process_jobs))
-            self.continue_process_jobs = set(filter(lambda x: x.application_name != application_name,
-                                                    self.continue_process_jobs))
+            self.add_stop_application_job(application)
         elif strategy == RunningFailureStrategies.RESTART_APPLICATION:
-            if application_name not in self.stop_application_jobs:
-                self.logger.info('RunningFailureHandler.add_job: adding {} to restart_application_jobs'
-                                 .format(application_name))
-                self.restart_application_jobs.add(application_name)
-                self.restart_process_jobs = set(filter(lambda x: x.application_name != application_name,
-                                                       self.restart_process_jobs))
-                self.continue_process_jobs = set(filter(lambda x: x.application_name != application_name,
-                                                        self.continue_process_jobs))
-            else:
-                self.logger.info('RunningFailureHandler.add_job: {} not added to restart_application_jobs'
-                                 ' because already in stop_application_jobs'.format(application_name))
+            self.add_restart_application_job(application)
         elif strategy == RunningFailureStrategies.RESTART_PROCESS:
-            if process.application_name not in (self.stop_application_jobs | self.restart_application_jobs):
-                self.logger.info('RunningFailureHandler.add_job: adding {} to restart_process_jobs'
-                                 .format(process.namespec))
-                self.restart_process_jobs.add(process)
-                self.continue_process_jobs.discard(process)
-            else:
-                self.logger.info('RunningFailureHandler.add_job: {} not added to restart_process_jobs'
-                                 ' because already in stop_application_jobs or restart_application_jobs'
-                                 .format(application_name))
+            self.add_restart_process_job(application, process)
         elif strategy == RunningFailureStrategies.CONTINUE:
-            if process.application_name not in (self.stop_application_jobs | self.restart_application_jobs) and \
-                    process not in self.restart_process_jobs:
-                self.logger.info('RunningFailureHandler.add_job: adding {} to continue_process_jobs'
-                                 .format(process.namespec))
-                self.continue_process_jobs.add(process)
-            else:
-                self.logger.info('RunningFailureHandler.add_job: {} not added to continue_process_jobs'
-                                 ' because already in stop_application_jobs or restart_application_jobs'
-                                 ' or restart_process_jobs'.format(application_name))
+            self.add_continue_process_job(application, process)
         self.logger.trace('RunningFailureHandler.add_job: END stop_application_jobs={} restart_application_jobs={}'
-                          ' restart_application_jobs={} restart_process_jobs={} continue_process_jobs={}'
+                          ' restart_process_jobs={} continue_process_jobs={}'
                           ' start_application_jobs={} start_process_jobs={}'
-                          .format(self.stop_application_jobs, self.restart_application_jobs,
-                                  self.restart_application_jobs, self.restart_process_jobs, self.continue_process_jobs,
-                                  self.start_application_jobs, self.start_process_jobs))
+                          .format(self.stop_application_jobs, self.restart_application_jobs, self.restart_process_jobs,
+                                  self.continue_process_jobs, self.start_application_jobs, self.start_process_jobs))
 
-    def add_default_job(self, process):
+    def add_default_job(self, process: ProcessStatus):
         """ Add a process or the related application name in the relevant set,
         iaw the strategy set in process rules and the priorities defined above. """
         self.add_job(process.rules.running_failure_strategy, process)
+        # check strategy promotion
+        if process.rules.running_failure_strategy == RunningFailureStrategies.RESTART_PROCESS:
+            # this is the case where the node has been invalidated
+            # if the application is stopped due to such failure, it is likely that the full application needs a restart
+            # rather than uncorrelated process restarts
+            application = self.supvisors.context.applications[process.application_name]
+            if application.stopped() and process in application.get_start_sequenced_processes():
+                # promote to RESTART_APPLICATION if process is in application start_sequence
+                # the job that has just been added will be swiped out as this strategy takes precedence
+                self.logger.warn('RunningFailureHandler.add_default_job: program={} with strategy=RESTART_PROCESS'
+                                 ' in an application stopped will use a promoted strategy=RESTART_APPLICATION'
+                                 .format(process.namespec))
+                self.add_job(RunningFailureStrategies.RESTART_APPLICATION, process)
 
-    def get_job_applications(self) -> bool:
+    def get_job_applications(self) -> Set[str]:
         """ Get all application names involved in Commanders.
 
         :return: the list of application names
         """
         return self.supvisors.starter.get_job_applications() | self.supvisors.stopper.get_job_applications()
 
+    def trigger_stop_application_jobs(self, job_applications: Set[str]) -> None:
+        """ Trigger the STOP_APPLICATION strategy on stored jobs. """
+        for application in list(self.stop_application_jobs):
+            if application.application_name in job_applications:
+                self.logger.debug('RunningFailureHandler.trigger_stop_application_jobs: {} stop deferred'
+                                  .format(application.application_name))
+            else:
+                self.logger.info('RunningFailureHandler.trigger_stop_application_jobs: stopping {}'
+                                 .format(application.application_name))
+                self.stop_application_jobs.remove(application)
+                self.supvisors.stopper.stop_application(application)
+
+    def trigger_restart_application_jobs(self, job_applications: Set[str]) -> None:
+        """ Trigger the RESTART_APPLICATION strategy on stored jobs.
+        Stop application if necessary and defer start until application is fully stopped. """
+        for application in list(self.restart_application_jobs):
+            if application.application_name in job_applications:
+                self.logger.debug('RunningFailureHandler.trigger_restart_application_jobs: {} restart deferred'
+                                  .format(application.application_name))
+            else:
+                self.logger.warn('RunningFailureHandler.trigger_restart_application_jobs: stopping {}'
+                                 .format(application.application_name))
+                self.restart_application_jobs.remove(application)
+                # first stop the application
+                self.supvisors.stopper.stop_application(application)
+                # defer the application starting
+                self.start_application_jobs.add(application)
+
+    def trigger_restart_process_jobs(self, job_applications: Set[str]) -> None:
+        """ Trigger the RESTART_PROCESS strategy on stored jobs.
+        Stop process if necessary and defer start until process is stopped. """
+        for process in list(self.restart_process_jobs):
+            if process.application_name in job_applications:
+                self.logger.debug('RunningFailureHandler.trigger_restart_process_jobs: {} restart deferred'
+                                  .format(process.namespec))
+            else:
+                self.logger.info('RunningFailureHandler.trigger_restart_process_jobs: stopping {}'
+                                 .format(process.namespec))
+                self.restart_process_jobs.remove(process)
+                self.supvisors.stopper.stop_process(process)
+                # defer the process starting
+                self.start_process_jobs.add(process)
+
+    def trigger_start_application_jobs(self, job_applications: Set[str]) -> None:
+        """ Trigger the deferred start of RESTART_APPLICATION strategy on stored jobs. """
+        for application in list(self.start_application_jobs):
+            if application.stopped() and application.application_name not in job_applications:
+                self.logger.info('RunningFailureHandler.trigger_start_application_jobs: starting {}'
+                                 .format(application.application_name))
+                self.start_application_jobs.remove(application)
+                self.supvisors.starter.default_start_application(application)
+            else:
+                self.logger.debug('RunningFailureHandler.trigger_start_application_jobs: {} start deferred'
+                                  .format(application.application_name))
+
+    def trigger_start_process_jobs(self, job_applications: Set[str]) -> None:
+        """ Trigger the deferred start of RESTART_PROCESS strategy on stored jobs. """
+        for process in list(self.start_process_jobs):
+            if process.stopped() and process.application_name not in job_applications:
+                self.logger.warn('RunningFailureHandler.trigger_start_process_jobs: starting {}'
+                                 .format(process.namespec))
+                self.start_process_jobs.remove(process)
+                self.supvisors.starter.default_start_process(process)
+            else:
+                self.logger.debug('RunningFailureHandler.trigger_start_process_jobs: {} start deferred'
+                                  .format(process.namespec))
+
+    def trigger_continue_process_jobs(self) -> None:
+        """ Trigger the CONTINUE strategy on stored jobs. """
+        for process in self.continue_process_jobs:
+            self.logger.info('RunningFailureHandler.trigger_continue_process_jobs: continue despite failure of {}'
+                             .format(process.namespec))
+        self.continue_process_jobs = set()
+
     def trigger_jobs(self):
         """ Trigger the configured strategy when a process of a running application crashes. """
         job_applications = self.get_job_applications()
         # consider applications to stop
-        if self.stop_application_jobs:
-            for application_name in self.stop_application_jobs.copy():
-                application = self.supvisors.context.applications[application_name]
-                if application_name in job_applications:
-                    self.logger.debug('RunningFailureHandler.trigger_jobs: stop application {} deferred'
-                                      .format(application_name))
-                else:
-                    self.logger.info('RunningFailureHandler.trigger_jobs: stop application {}'
-                                     .format(application_name))
-                    self.stop_application_jobs.remove(application_name)
-                    self.supvisors.stopper.stop_application(application)
+        self.trigger_stop_application_jobs(job_applications)
         # consider applications to restart
-        if self.restart_application_jobs:
-            for application_name in self.restart_application_jobs.copy():
-                application = self.supvisors.context.applications[application_name]
-                if application_name in job_applications:
-                    self.logger.debug('RunningFailureHandler.trigger_jobs: restart application {} deferred'
-                                      .format(application_name))
-                else:
-                    self.logger.warn('RunningFailureHandler.trigger_jobs: restart application {}'
-                                     .format(application_name))
-                    self.restart_application_jobs.remove(application_name)
-                    # first stop the application
-                    self.supvisors.stopper.stop_application(application)
-                    # defer the application starting
-                    self.start_application_jobs.add(application)
+        self.trigger_restart_application_jobs(job_applications)
         # consider processes to restart
-        if self.restart_process_jobs:
-            for process in self.restart_process_jobs.copy():
-                if process.application_name in job_applications:
-                    self.logger.debug('RunningFailureHandler.trigger_jobs: restart process {} deferred'
-                                      .format(process.namespec))
-                else:
-                    self.logger.info('RunningFailureHandler.trigger_jobs: restart process {}'
-                                     .format(process.namespec))
-                    self.restart_process_jobs.remove(process)
-                    self.supvisors.stopper.stop_process(process)
-                    # defer the process starting
-                    self.start_process_jobs.add(process)
+        self.trigger_restart_process_jobs(job_applications)
         # consider applications to start
-        if self.start_application_jobs:
-            for application in self.start_application_jobs.copy():
-                if application.stopped() and application.application_name not in job_applications:
-                    self.logger.info('RunningFailureHandler.trigger_jobs: start application {}'
-                                     .format(application.application_name))
-                    self.start_application_jobs.remove(application)
-                    self.supvisors.starter.default_start_application(application)
-                else:
-                    self.logger.debug('RunningFailureHandler.trigger_jobs: start application {} deferred'
-                                      .format(application.application_name))
+        self.trigger_start_application_jobs(job_applications)
         # consider processes to start
-        if self.start_process_jobs:
-            for process in self.start_process_jobs.copy():
-                if process.stopped() and process.application_name not in job_applications:
-                    self.logger.warn('RunningFailureHandler.trigger_jobs: start process {}'
-                                     .format(process.namespec))
-                    self.start_process_jobs.remove(process)
-                    self.supvisors.starter.default_start_process(process)
-                else:
-                    self.logger.debug('RunningFailureHandler.trigger_jobs: start process {} deferred'
-                                      .format(process.namespec))
+        self.trigger_start_process_jobs(job_applications)
         # log only the continuation jobs
-        if self.continue_process_jobs:
-            for process in self.continue_process_jobs:
-                self.logger.info('RunningFailureHandler.trigger_jobs: continue despite of crashed process {}'
-                                 .format(process.namespec))
-            self.continue_process_jobs = set()
+        self.trigger_continue_process_jobs()
+
diff --git a/supvisors/supvisorsctl.py b/supvisors/supvisorsctl.py
index d7e18f9e..9f6fc8c6 100644
--- a/supvisors/supvisorsctl.py
+++ b/supvisors/supvisorsctl.py
@@ -204,17 +204,34 @@ def do_application_rules(self, arg):
                     rules_list.append(rules)
             # print results
             if rules_list:
-                max_appli = max(len(rules['application_name']) for rules in rules_list) + 2
-                template = '%(appli)-{}s%(managed)-12s%(start_seq)-5s%(stop_seq)-5s' \
-                           '%(starting_strategy)-12s%(running_strategy)-12s' \
-                    .format(max_appli)
+                # get longer from application names and title
+                max_appli = max(len(rules['application_name']) for rules in rules_list)
+                max_appli = max(max_appli, len('Application')) + 2
+                # get longer from distribution nodes and title
+                max_nodes = max(len(rules.get('addresses', '')) for rules in rules_list)
+                max_nodes = max(max_nodes, len('Nodes')) + 2
+                # print title
+                template_managed = '%(appli)-{}s%(managed)-9s%(distributed)-13s%(nodes)-{}s' \
+                                   '%(start_seq)-7s%(stop_seq)-7s' \
+                                   '%(starting_strategy)-13s%(starting_failure_strategy)-18s' \
+                                   '%(running_failure_strategy)s' \
+                    .format(max_appli, max_nodes)
+                title = {'appli': 'Application', 'managed': 'Managed', 'distributed': 'Distributed',
+                         'nodes': 'Nodes', 'start_seq': 'Start', 'stop_seq': 'Stop', 'starting_strategy': 'Starting',
+                         'starting_failure_strategy': 'Starting_Failure', 'running_failure_strategy': 'Running_Failure'}
+                self.ctl.output(template_managed % title)
                 for rules in rules_list:
-                    line = template % {'appli': rules['application_name'],
-                                       'managed': ('  managed' if rules['managed'] else 'unmanaged'),
-                                       'start_seq': rules['start_sequence'],
-                                       'stop_seq': rules['stop_sequence'],
-                                       'starting_strategy': rules['starting_failure_strategy'],
-                                       'running_strategy': rules['running_failure_strategy']}
+                    if rules['managed']:
+                        payload = {'appli': rules['application_name'], 'managed': True,
+                                   'distributed': rules['distributed'], 'nodes': rules.get('addresses', ''),
+                                   'start_seq': rules['start_sequence'], 'stop_seq': rules['stop_sequence'],
+                                   'starting_strategy': rules['starting_strategy'],
+                                   'starting_failure_strategy': rules['starting_failure_strategy'],
+                                   'running_failure_strategy': rules['running_failure_strategy']}
+                        line = template_managed % payload
+                    else:
+                        template_unmanaged = '%(appli)-{}sFalse'.format(max_appli)
+                        line = template_unmanaged % {'appli': rules['application_name']}
                     self.ctl.output(line)
 
     def help_application_rules(self):
diff --git a/supvisors/supvisorszmq.py b/supvisors/supvisorszmq.py
index f3017ec0..b2a1bf92 100644
--- a/supvisors/supvisorszmq.py
+++ b/supvisors/supvisorszmq.py
@@ -76,7 +76,7 @@ def send_tick_event(self, payload: Payload) -> None:
         :return: None
         """
         self.logger.trace('send TickEvent {}'.format(payload))
-        self.socket.send_pyobj((InternalEventHeaders.TICK, self.node_name, payload))
+        self.socket.send_pyobj((InternalEventHeaders.TICK.value, self.node_name, payload))
 
     def send_process_event(self, payload: Payload) -> None:
         """ Publish the process event with PyZmq.
@@ -85,7 +85,7 @@ def send_process_event(self, payload: Payload) -> None:
         :return: None
         """
         self.logger.trace('send ProcessEvent {}'.format(payload))
-        self.socket.send_pyobj((InternalEventHeaders.PROCESS, self.node_name, payload))
+        self.socket.send_pyobj((InternalEventHeaders.PROCESS.value, self.node_name, payload))
 
     def send_statistics(self, payload: Payload) -> None:
         """ Publish the statistics with PyZmq.
@@ -94,7 +94,7 @@ def send_statistics(self, payload: Payload) -> None:
         :return: None
         """
         self.logger.trace('send Statistics {}'.format(payload))
-        self.socket.send_pyobj((InternalEventHeaders.STATISTICS, self.node_name, payload))
+        self.socket.send_pyobj((InternalEventHeaders.STATISTICS.value, self.node_name, payload))
 
     def send_state_event(self, payload: Payload) -> None:
         """ Publish the Master state event with PyZmq.
@@ -103,7 +103,7 @@ def send_state_event(self, payload: Payload) -> None:
         :return: None
         """
         self.logger.trace('send Supvisors state {}'.format(payload))
-        self.socket.send_pyobj((InternalEventHeaders.STATE, self.node_name, payload))
+        self.socket.send_pyobj((InternalEventHeaders.STATE.value, self.node_name, payload))
 
 
 class InternalEventSubscriber(object):
@@ -445,18 +445,26 @@ def close(self):
         """
         self.socket.close(ZMQ_LINGER)
 
-    def send_check_node(self, node_name: str) -> None:
+    def send_message(self, header: DeferredRequestHeaders, body: Tuple) -> None:
         """ Send request to check authorization to deal with the node.
 
-        :param node_name: the node name to check
+        :param header: the message type
+        :param body: the parameters to send
         :return: None
         """
-        self.logger.debug('RequestPusher.send_check_node: node_name={}'.format(node_name))
+        self.logger.trace('RequestPusher.send_message: header={}'.format(header.name))
         try:
-            self.socket.send_pyobj((DeferredRequestHeaders.CHECK_NODE, (node_name,)),
-                                   zmq.NOBLOCK)
+            self.socket.send_pyobj((header.value, body), zmq.NOBLOCK)
         except zmq.error.Again:
-            self.logger.error('RequestPusher.send_check_node: CHECK_NODE not sent')
+            self.logger.error('RequestPusher.send_message: {} not sent'.format(header.name))
+
+    def send_check_node(self, node_name: str) -> None:
+        """ Send request to check authorization to deal with the node.
+
+        :param node_name: the node name to check
+        :return: None
+        """
+        self.send_message(DeferredRequestHeaders.CHECK_NODE, (node_name,))
 
     def send_isolate_nodes(self, node_names: NameList) -> None:
         """ Send request to isolate nodes.
@@ -464,12 +472,7 @@ def send_isolate_nodes(self, node_names: NameList) -> None:
         :param node_names: the nodes to isolate
         :return: Node
         """
-        self.logger.trace('RequestPusher.send_isolate_nodes: node_names={}'.format(node_names))
-        try:
-            self.socket.send_pyobj((DeferredRequestHeaders.ISOLATE_NODES, node_names),
-                                   zmq.NOBLOCK)
-        except zmq.error.Again:
-            self.logger.error('RequestPusher.send_isolate_nodes: ISOLATE_NODES not sent')
+        self.send_message(DeferredRequestHeaders.ISOLATE_NODES, node_names)
 
     def send_start_process(self, node_name: str, namespec: str, extra_args: str) -> None:
         """ Send request to start process.
@@ -479,12 +482,7 @@ def send_start_process(self, node_name: str, namespec: str, extra_args: str) ->
         :param extra_args: the additional arguments to be passed to the command line
         :return: None
         """
-        self.logger.trace('send START_PROCESS {} to {} with {}'.format(namespec, node_name, extra_args))
-        try:
-            self.socket.send_pyobj((DeferredRequestHeaders.START_PROCESS, (node_name, namespec, extra_args)),
-                                   zmq.NOBLOCK)
-        except zmq.error.Again:
-            self.logger.error('START_PROCESS not sent')
+        self.send_message(DeferredRequestHeaders.START_PROCESS, (node_name, namespec, extra_args))
 
     def send_stop_process(self, node_name: str, namespec: str) -> None:
         """ Send request to stop process.
@@ -493,12 +491,7 @@ def send_stop_process(self, node_name: str, namespec: str) -> None:
         :param namespec: the process namespec
         :return: None
         """
-        self.logger.trace('send STOP_PROCESS {} to {}'.format(namespec, node_name))
-        try:
-            self.socket.send_pyobj((DeferredRequestHeaders.STOP_PROCESS, (node_name, namespec)),
-                                   zmq.NOBLOCK)
-        except zmq.error.Again:
-            self.logger.error('STOP_PROCESS not sent')
+        self.send_message(DeferredRequestHeaders.STOP_PROCESS, (node_name, namespec))
 
     def send_restart(self, node_name: str):
         """ Send request to restart a Supervisor.
@@ -506,12 +499,7 @@ def send_restart(self, node_name: str):
         :param node_name: the node name where Supvisors has to be restarted
         :return: None
         """
-        self.logger.trace('send RESTART {}'.format(node_name))
-        try:
-            self.socket.send_pyobj((DeferredRequestHeaders.RESTART, (node_name,)),
-                                   zmq.NOBLOCK)
-        except zmq.error.Again:
-            self.logger.error('RESTART not sent')
+        self.send_message(DeferredRequestHeaders.RESTART, (node_name,))
 
     def send_shutdown(self, node_name: str):
         """ Send request to shutdown a Supervisor.
@@ -519,12 +507,23 @@ def send_shutdown(self, node_name: str):
         :param node_name: the node name where Supvisors has to be shut down
         :return: None
         """
-        self.logger.trace('send SHUTDOWN {}'.format(node_name))
-        try:
-            self.socket.send_pyobj((DeferredRequestHeaders.SHUTDOWN, (node_name,)),
-                                   zmq.NOBLOCK)
-        except zmq.error.Again:
-            self.logger.error('SHUTDOWN not sent')
+        self.send_message(DeferredRequestHeaders.SHUTDOWN, (node_name,))
+
+    def send_restart_all(self, node_name: str):
+        """ Send request to restart the Supvisors Master.
+
+        :param node_name: the Supvisors Master
+        :return: None
+        """
+        self.send_message(DeferredRequestHeaders.RESTART_ALL, (node_name,))
+
+    def send_shutdown_all(self, node_name: str):
+        """ Send request to shutdown the Supvisors Master.
+
+        :param node_name: the Supvisors Master
+        :return: None
+        """
+        self.send_message(DeferredRequestHeaders.SHUTDOWN_ALL, (node_name,))
 
 
 class SupervisorZmq(object):
diff --git a/supvisors/tools/breed.py b/supvisors/tools/breed.py
new file mode 100755
index 00000000..6febc630
--- /dev/null
+++ b/supvisors/tools/breed.py
@@ -0,0 +1,137 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# ======================================================================
+# Copyright 2021 Julien LE CLEACH
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# ======================================================================
+
+import os
+
+from configparser import ConfigParser
+from pathlib import Path
+from typing import Dict, Mapping, Sequence, Tuple
+
+# annotation types
+TemplateGroups = Mapping[str, int]
+TemplatePrograms = Mapping[str, Sequence[str]]
+SectionConfigMap = Dict[str, ConfigParser]
+FileConfigMap = Dict[Path, ConfigParser]
+
+
+def read_config_files(src_dir: str, file_pattern: str) -> Tuple[SectionConfigMap, FileConfigMap]:
+    """ Get all config files and create a parser for each of them.
+
+    :param src_dir: the source folder
+    :param file_pattern: the filepath pattern to search for config files from the source folder
+    :return: the parsers grouped by section names and by file names
+    """
+    parser_map, parser_files = {}, {}
+    for filename in Path(src_dir).glob(file_pattern):
+        config = ConfigParser(interpolation=None)
+        config.read(filename)
+        parser_files[os.path.abspath(filename)] = config
+        for section in config.sections():
+            parser_map[section] = config
+    return parser_map, parser_files
+
+
+def write_config_files(dst_folder: str, parser_files: FileConfigMap) -> None:
+    """ Write the contents of the parsers in new config files.
+
+    :param dst_folder: the destination folder
+    :param parser_files: the parsers to write, identified by their original file name
+    :return: None
+    """
+    # remove the common part of the filepaths to simplify the output in dst_folder
+    common_path = os.path.commonpath(parser_files)
+    for filename, config in parser_files.items():
+        filepath = os.path.join(dst_folder, os.path.relpath(filename, common_path))
+        # create path if it doesn't exist
+        folder_name = os.path.dirname(filepath)
+        os.makedirs(folder_name, exist_ok=True)
+        # write new config file from parser
+        with open(filepath, 'w') as configfile:
+            config.write(configfile)
+
+
+def breed_groups(parsers, template_groups: TemplateGroups) -> TemplatePrograms:
+    """ Find template groups in config files and replace them by X versions of the group.
+    Return the template programs that have to be multiplied.
+
+    :param parsers: all the config parsers found
+    :param template_groups: the template groups
+    :return: the template programs
+    """
+    template_programs = {}
+    for group, cardinality in template_groups.items():
+        if group in parsers:
+            parser = parsers[group]
+            programs = parser[group]['programs'].split(',')
+            # duplicate and update <cardinality> versions of the group
+            for idx in range(1, cardinality + 1):
+                new_section = group + '_%02d' % idx
+                new_programs = [program + '_%02d' % idx for program in programs]
+                parser[new_section] = {'programs': ','.join(new_programs)}
+                for program in programs:
+                    template_programs.setdefault('program:' + program, []).append(program + '_%02d' % idx)
+            # remove template
+            del parser[group]
+    return template_programs
+
+
+def breed_programs(parsers, template_programs: TemplatePrograms) -> None:
+    """ Find template programs in config files and replace them by X versions of the program.
+
+    :param parsers: all the config parsers found
+    :param template_programs: the template programs
+    :return: None
+    """
+    for program, new_programs in template_programs.items():
+        if program in parsers:
+            parser = parsers[program]
+            # duplicate and update X versions of the program
+            # at the end, remove template
+            for new_program in new_programs:
+                # copy template section to new_section
+                new_section = 'program:' + new_program
+                parser[new_section] = parser[program]
+            # remove template
+            del parser[program]
+
+
+if __name__ == '__main__':
+    """ Create X definitions of group and programs based on group/program template.
+    This is typically useful when an application could be started X times.
+    As there's no concept of homogeneous group in Supervisor, this script duplicates X times the definition of a group
+    and its related programs and removes the original definition.
+    The resulting configuration files are written to a separate folder SW_DST_DIR.
+    """
+    # the template groups
+    breed = {'player': 4, 'web_movies': 16}
+    # program parameters
+    SW_TEMPLATE_DIR = '.'
+    SW_DST_DIR = 'temp'
+    SEARCH_PATTERN = '../test/etc/**/*ini'
+    # get one parser per template file
+    config_map, config_files = read_config_files(SW_TEMPLATE_DIR, SEARCH_PATTERN)
+    # print(list(config_map.keys()))
+    # update all groups configurations
+    group_breed = {'group:' + key: value for key, value in breed.items()}
+    program_breed = breed_groups(config_map, group_breed)
+    # print(program_breed)
+    # update program configurations found in groups
+    breed_programs(config_map, program_breed)
+    # write files
+    write_config_files(SW_DST_DIR, config_files)
diff --git a/supvisors/ui/css/menu.css b/supvisors/ui/css/menu.css
index 87d5f4b1..37a985cb 100644
--- a/supvisors/ui/css/menu.css
+++ b/supvisors/ui/css/menu.css
@@ -14,18 +14,6 @@ nav li {
     border-radius: 0px 6px 0 6px;
 }
 
-nav li li {
-    border-radius: 0;
-    background: #333;
-    border: none;
-    margin: 0;
-    padding: 0 0 0 5px;
-}
-
-nav li li li {
-    background: #222;
-}
-
 nav li a {
     display: block;
     text-decoration: none;
@@ -33,10 +21,6 @@ nav li a {
     padding: 7px 0 7px 4px;
 }
 
-nav li li:last-child {
-    border-radius: 0 0 0 6px;
-}
-
 nav .active {
     background-image: linear-gradient(165deg, var(--dark2-color) 40%, var(--dark1-color), orange);
     font-weight: bold;
@@ -53,7 +37,6 @@ nav li:before {
 }
 
 nav li:hover {
-    padding-right: 24px;
     padding-left:8px;
 }
 
@@ -65,10 +48,3 @@ nav li:hover:before {
 nav li:active {
     background-image: linear-gradient(165deg, var(--dark2-color) 40%, var(--dark1-color), orange);
 }
-
-nav li li:hover {
-    background: url(../img/arrow-orange_12.png) no-repeat;
-    background-position: left 10px;
-    padding-left: 15px;
-}
-
diff --git a/supvisors/ui/css/ui_style.css b/supvisors/ui/css/ui_style.css
index 34a76079..a8ee3aa2 100644
--- a/supvisors/ui/css/ui_style.css
+++ b/supvisors/ui/css/ui_style.css
@@ -578,6 +578,12 @@ nav li.STOPPING.active {
     background-image: linear-gradient(165deg, #cc0, green 20%, #1a1a1a 40%, #1a1a1a);
 }
 
+nav li.failure:after {
+    content: url(../img/red_light_30.png);
+    float: right;
+    position: relative;
+    top: -30px;
+}
 
 /*
  * Increase / Stable / Decrease styles
diff --git a/supvisors/ui/img/red_light_30.png b/supvisors/ui/img/red_light_30.png
new file mode 100644
index 00000000..7af4e55f
Binary files /dev/null and b/supvisors/ui/img/red_light_30.png differ
diff --git a/supvisors/utils.py b/supvisors/utils.py
index 002149cd..55358441 100644
--- a/supvisors/utils.py
+++ b/supvisors/utils.py
@@ -17,11 +17,12 @@
 # limitations under the License.
 # ======================================================================
 
+from enum import Enum
 from math import sqrt
 from time import gmtime, localtime, strftime, time
 
 
-class InternalEventHeaders:
+class InternalEventHeaders(Enum):
     """ Enumeration class for the headers in messages between Listener and MainLoop. """
     TICK, PROCESS, STATISTICS, STATE = range(4)
 
@@ -43,9 +44,9 @@ class EventHeaders:
 
 
 # for deferred XML-RPC requests
-class DeferredRequestHeaders:
+class DeferredRequestHeaders(Enum):
     """ Enumeration class for the headers of deferred XML-RPC messages sent to MainLoop."""
-    CHECK_NODE, ISOLATE_NODES, START_PROCESS, STOP_PROCESS, RESTART, SHUTDOWN = range(6)
+    CHECK_NODE, ISOLATE_NODES, START_PROCESS, STOP_PROCESS, RESTART, SHUTDOWN, RESTART_ALL, SHUTDOWN_ALL = range(8)
 
 
 def simple_localtime(now=None):
diff --git a/supvisors/version.txt b/supvisors/version.txt
index 966f626a..ca23b09f 100644
--- a/supvisors/version.txt
+++ b/supvisors/version.txt
@@ -1,1 +1,1 @@
-version=0.6
+version=0.7
diff --git a/supvisors/viewapplication.py b/supvisors/viewapplication.py
index b32c2334..17ca42e2 100644
--- a/supvisors/viewapplication.py
+++ b/supvisors/viewapplication.py
@@ -18,6 +18,7 @@
 # ======================================================================
 
 from supervisor.http import NOT_DONE_YET
+from supervisor.states import ProcessStates
 from supervisor.xmlrpc import RPCError
 
 from .application import ApplicationStatus
@@ -65,13 +66,12 @@ def write_header(self, root):
         elt.content(self.application.state.name)
         # set LED iaw major/minor failures
         elt = root.findmeld('state_led_mid')
-        if self.application.running():
-            if self.application.major_failure:
-                elt.attrib['class'] = 'status_red'
-            elif self.application.minor_failure:
-                elt.attrib['class'] = 'status_yellow'
-            else:
-                elt.attrib['class'] = 'status_green'
+        if self.application.major_failure:
+            elt.attrib['class'] = 'status_red'
+        elif self.application.minor_failure:
+            elt.attrib['class'] = 'status_yellow'
+        elif self.application.running():
+            elt.attrib['class'] = 'status_green'
         else:
             elt.attrib['class'] = 'status_empty'
         # write options
@@ -138,11 +138,12 @@ def get_process_data(self) -> PayloadList:
         for process in self.application.processes.values():
             namespec = process.namespec
             node_name, description = self.get_process_last_desc(namespec)
+            unexpected_exit = process.state == ProcessStates.EXITED and not process.expected_exit
             nb_cores, proc_stats = self.view_ctx.get_process_stats(namespec, node_name)
             data.append({'application_name': process.application_name, 'process_name': process.process_name,
                          'namespec': namespec, 'node_name': node_name,
                          'statename': process.state_string(), 'statecode': process.state,
-                         'gravity': process.state_string(),
+                         'gravity': 'FATAL' if unexpected_exit else process.state_string(),
                          'running_nodes': list(process.running_nodes),
                          'description': description,
                          'expected_load': process.rules.expected_load, 'nb_cores': nb_cores, 'proc_stats': proc_stats})
diff --git a/supvisors/viewhandler.py b/supvisors/viewhandler.py
index 428b625e..50ee907d 100644
--- a/supvisors/viewhandler.py
+++ b/supvisors/viewhandler.py
@@ -153,7 +153,9 @@ def write_nav_applications(self, root, appli):
         # forced to list otherwise not easily testable
         for li_elt, item in mid_elt.repeat(list(applications)):
             # set element class
-            li_elt.attrib['class'] = item.state.name + (' active' if item.application_name == appli else '')
+            li_elt.attrib['class'] = '{} {} {}'.format(item.state.name,
+                                                       'active' if item.application_name == appli else '',
+                                                       'failure' if item.major_failure or item.minor_failure else '')
             # set hyperlink attributes
             elt = li_elt.findmeld('appli_a_mid')
             if self.supvisors.fsm.state == SupvisorsStates.INITIALIZATION:
diff --git a/supvisors/viewprocaddress.py b/supvisors/viewprocaddress.py
index 00f114f2..2dffabdf 100644
--- a/supvisors/viewprocaddress.py
+++ b/supvisors/viewprocaddress.py
@@ -76,7 +76,7 @@ def get_process_data(self) -> Tuple[PayloadList, PayloadList]:
         for info in all_info:
             namespec = make_namespec(info['group'], info['name'])
             process = self.sup_ctx.get_process(namespec)
-            unexpected_exit = info['state'] == ProcessStates.EXITED and not process.expected_exit
+            unexpected_exit = info['state'] == ProcessStates.EXITED and 'Bad exit code' in info['spawnerr']
             expected_load = process.rules.expected_load
             nb_cores, proc_stats = self.view_ctx.get_process_stats(namespec)
             data.append({'application_name': info['group'], 'process_name': info['name'], 'namespec': namespec,
diff --git a/supvisors/viewsupstatus.py b/supvisors/viewsupstatus.py
index ef67c998..a357621a 100644
--- a/supvisors/viewsupstatus.py
+++ b/supvisors/viewsupstatus.py
@@ -19,10 +19,10 @@
 
 from supervisor.web import StatusView
 
-from supvisors.utils import simple_localtime
-from supvisors.viewcontext import *
-from supvisors.viewhandler import ViewHandler
-from supvisors.webutils import *
+from .utils import simple_localtime
+from .viewcontext import *
+from .viewhandler import ViewHandler
+from .webutils import *
 
 
 class SupvisorsAddressView(StatusView):
@@ -61,7 +61,7 @@ def write_header(self, root):
         elt.content(status.state.name)
         # set node load
         elt = root.findmeld('percent_mid')
-        elt.content('{}%'.format(status.get_load()))
+        elt.content('{}%'.format(status.get_loading()))
         # set last tick date: remote_time and local_time should be identical
         # since self is running on the 'remote' address
         elt = root.findmeld('date_mid')
diff --git a/supvisors/viewsupvisors.py b/supvisors/viewsupvisors.py
index 3c3b01d5..3db9ca70 100644
--- a/supvisors/viewsupvisors.py
+++ b/supvisors/viewsupvisors.py
@@ -101,7 +101,7 @@ def _write_node_box_title(self, node_div_elt, status: AddressStatus) -> None:
         elt.content(status.state.name)
         # set node current load
         elt = node_div_elt.findmeld('percent_td_mid')
-        elt.content('{}%'.format(status.get_load()))
+        elt.content('{}%'.format(status.get_loading()))
 
     @staticmethod
     def _write_node_box_processes(node_div_elt, status):


Test patch:
diff --git a/supvisors/test/etc/common/web_movies/program_web_browser.ini b/supvisors/test/etc/common/web_movies/program_web_browser.ini
index d8a096ff..65e4487c 100644
--- a/supvisors/test/etc/common/web_movies/program_web_browser.ini
+++ b/supvisors/test/etc/common/web_movies/program_web_browser.ini
@@ -1,7 +1,7 @@
 [program:web_browser]
 command=firefox --new-window http://localhost:60000
 autostart=false
-autorestart=false
+autorestart=unexpected
 startsecs=5
 startretries=0
 redirect_stderr=true
diff --git a/supvisors/test/etc/my_movies.xml b/supvisors/test/etc/my_movies.xml
index 1d98f5b9..1f606b81 100644
--- a/supvisors/test/etc/my_movies.xml
+++ b/supvisors/test/etc/my_movies.xml
@@ -85,6 +85,7 @@
     <application name="my_movies">
         <start_sequence>4</start_sequence>
         <stop_sequence>2</stop_sequence>
+        <starting_strategy>CONFIG</starting_strategy>
         <starting_failure_strategy>CONTINUE</starting_failure_strategy>
 
         <program name="manager">
@@ -146,11 +147,13 @@
 
     <!-- player application -->
     <application name="player">
+        <distributed>false</distributed>
+        <addresses>cliche81,cliche83</addresses>
         <start_sequence>5</start_sequence>
+        <starting_strategy>MOST_LOADED</starting_strategy>
         <starting_failure_strategy>ABORT</starting_failure_strategy>
 
         <program name="test_reader">
-            <addresses>cliche81</addresses>
             <start_sequence>1</start_sequence>
             <required>true</required>
             <wait_exit>true</wait_exit>
@@ -158,7 +161,6 @@
         </program>
 
         <program name="movie_player">
-            <addresses>cliche81</addresses>
             <start_sequence>2</start_sequence>
             <expected_loading>13</expected_loading>
         </program>
@@ -169,6 +171,7 @@
     <application name="web_movies">
         <start_sequence>6</start_sequence>
         <stop_sequence>1</stop_sequence>
+        <starting_strategy>LESS_LOADED</starting_strategy>
 
         <program name="web_browser">
             <addresses>*</addresses>
@@ -179,8 +182,7 @@
 
     </application>
 
-
-    <!-- web_movies application -->
+    <!-- disk_reader_81 application -->
     <application name="disk_reader_81">
         <start_sequence>1</start_sequence>
     </application>
diff --git a/supvisors/test/scripts/check_conciliation_strategy.py b/supvisors/test/scripts/check_conciliation_strategy.py
index 1563b08c..28b8ddf9 100644
--- a/supvisors/test/scripts/check_conciliation_strategy.py
+++ b/supvisors/test/scripts/check_conciliation_strategy.py
@@ -18,6 +18,7 @@
 # ======================================================================
 
 import sys
+import time
 import unittest
 
 from supervisor.compat import xmlrpclib
@@ -239,7 +240,7 @@ def _check_no_conflict(self):
     def _create_database_conflicts(self):
         """ Create conflicts on database application. """
         # start all movie_server programs on all addresses
-        for address, proxy in self.proxies.items():
+        for node_name, proxy in self.proxies.items():
             for idx in range(3):
                 try:
                     program = 'movie_server_0%d' % (idx + 1)
@@ -249,15 +250,17 @@ def _create_database_conflicts(self):
                 else:
                     # confirm starting through events
                     event = self._get_next_process_event()
-                    self.assertDictContainsSubset({'name': program, 'state': 10, 'address': address}, event)
+                    assert {'name': program, 'state': 10, 'address': node_name}.items() < event.items()
                     event = self._get_next_process_event()
-                    self.assertDictContainsSubset({'name': program, 'state': 20, 'address': address}, event)
+                    assert {'name': program, 'state': 20, 'address': node_name}.items() < event.items()
         # check supvisors event: CONCILIATION state is expected
         event = self._get_next_supvisors_event()
         self.assertEqual('CONCILIATION', event['statename'])
 
     def _check_database_conflicts(self):
         """ Test conflicts on database application using XML-RPC. """
+        # change of state in Slave Supvisors may take up to one tick
+        time.sleep(5)
         # check that the conflicts are detected in all Supvisors instances
         for proxy in self.proxies.values():
             # test that Supvisors is in CONCILIATION state (in all instances)
@@ -271,7 +274,7 @@ def _check_database_conflicts(self):
     def _create_manager_conflicts(self):
         """ Create conflicts on the my_movies:manager process. """
         # start the manager program on all addresses
-        for address, proxy in self.proxies.items():
+        for node_name, proxy in self.proxies.items():
             try:
                 proxy.supervisor.startProcess('my_movies:manager')
             except xmlrpclib.Fault as exc:
@@ -279,15 +282,17 @@ def _create_manager_conflicts(self):
             else:
                 # confirm starting through events
                 event = self._get_next_process_event()
-                self.assertDictContainsSubset({'name': 'manager', 'state': 10, 'address': address}, event)
+                assert {'name': 'manager', 'state': 10, 'address': node_name}.items() < event.items()
                 event = self._get_next_process_event()
-                self.assertDictContainsSubset({'name': 'manager', 'state': 20, 'address': address}, event)
+                assert {'name': 'manager', 'state': 20, 'address': node_name}.items() < event.items()
         # check supvisors event: CONCILIATION state is expected
         event = self._get_next_supvisors_event()
         self.assertEqual('CONCILIATION', event['statename'])
 
     def _check_manager_conflicts(self):
         """ Test conflicts on the my_movies:manager process using RPC. """
+        # change of state in Slave Supvisors may take up to one tick
+        time.sleep(5)
         # check that the conflicts are detected in all Supvisors instances
         for proxy in self.proxies.values():
             # test that Supvisors is in CONCILIATION state (in all instances)
diff --git a/supvisors/test/scripts/check_running_strategy.py b/supvisors/test/scripts/check_running_strategy.py
index aa872e57..2901ec9c 100644
--- a/supvisors/test/scripts/check_running_strategy.py
+++ b/supvisors/test/scripts/check_running_strategy.py
@@ -21,7 +21,7 @@
 import unittest
 
 from queue import Empty
-
+from supervisor.states import ProcessStates
 from supvisors.ttypes import StartingStrategies
 
 from .event_queues import SupvisorsEventQueues
@@ -35,6 +35,11 @@ def setUp(self):
         """ Used to swallow process events related to this process. """
         # call parent
         RunningAddressesTest.setUp(self)
+        # determine web_browser node_name
+        info = self.local_supvisors.get_process_info('web_movies:web_browser')[0]
+        if info['statecode'] != ProcessStates.RUNNING:
+            print('[ERROR] web_movies:web_browser not RUNNING')
+        self.web_browser_node_name = info['addresses'][0]
         # as this process has just been started, STARTING / RUNNING events might be received
         # other events may be triggered from tearDown too
         has_events = True
@@ -59,11 +64,6 @@ def tearDown(self):
         except:
             # exception is expected if process already running
             pass
-        try:
-            self.local_supvisors.start_process(StartingStrategies.CONFIG, 'web_movies:web_browser')
-        except:
-            # exception is expected if process already running
-            pass
         try:
             self.local_supvisors.start_application(StartingStrategies.CONFIG, 'my_movies')
         except:
@@ -92,29 +92,23 @@ def test_continue(self):
         with self.assertRaises(Empty):
             self.evloop.application_queue.get(True, 2)
 
-    def test_restart_process(self):
+    def _test_restart_process(self):
         """ Test the RESTART_PROCESS running failure strategy. """
+        # FIXME
         print('### Testing RESTART_PROCESS running failure strategy')
-        # force the web_browser to exit with a fake segmentation fault
-        self.local_supervisor.signalProcess('web_movies:web_browser', 'SEGV')
-        # an EXIT event is expected for this process
-        event = self._get_next_process_event()
-        assert {'name': 'web_browser', 'expected': False, 'state': 100}.items() < event.items()
-        # application should be stopped: no more process running
-        event = self._get_next_application_status()
-        subset = {'application_name': 'web_movies', 'major_failure': False, 'minor_failure': True,
-                  'statename': 'STOPPED'}
-        assert subset.items() < event.items()
-        # STARTING / RUNNING events are expected for this process
+        # call for restart on the node where web_browser is running
+        proxy = self.proxies[self.web_browser_node_name]
+        proxy.supervisor.restart()
+        # STARTING / RUNNING events are expected for this process from a new node
         event = self._get_next_process_event()
         assert {'name': 'web_browser', 'state': 10}.items() < event.items()
-        # application should be starting
+        node_name = event['address']
         event = self._get_next_application_status()
         subset = {'application_name': 'web_movies', 'major_failure': False, 'minor_failure': False,
                   'statename': 'STARTING'}
         assert subset.items() < event.items()
         event = self._get_next_process_event()
-        assert {'name': 'web_browser', 'state': 20}.items() < event.items()
+        assert {'name': 'web_browser', 'state': 20, 'address': node_name}.items() < event.items()
         # application should be running
         event = self._get_next_application_status()
         subset = {'application_name': 'web_movies', 'major_failure': False, 'minor_failure': False,
@@ -260,7 +254,6 @@ def test_suite():
 if __name__ == '__main__':
     # get arguments
     import argparse
-
     parser = argparse.ArgumentParser(description='Check the Supvisors running failure strategies.')
     parser.add_argument('-p', '--port', type=int, default=60002, help="the event port of Supvisors")
     args = parser.parse_args()
diff --git a/supvisors/test/scripts/check_start_sequence.py b/supvisors/test/scripts/check_start_sequence.py
index 3dbb4e94..3cc5c63e 100644
--- a/supvisors/test/scripts/check_start_sequence.py
+++ b/supvisors/test/scripts/check_start_sequence.py
@@ -206,9 +206,9 @@ def check_hmi_starting(self):
         """ Check the starting of the hmi program. """
         # define the expected events for the hmi program
         program = self.context.get_program('my_movies:hmi')
-        address = self.HOST_02 if self.HOST_02 in self.nodes else self.HOST_01
-        program.add_event(ProcessStateEvent(ProcessStates.STARTING, address))
-        program.add_event(ProcessStateEvent(ProcessStates.RUNNING, address))
+        node_name = self.HOST_02 if self.HOST_02 in self.nodes else self.HOST_01
+        program.add_event(ProcessStateEvent(ProcessStates.STARTING, node_name))
+        program.add_event(ProcessStateEvent(ProcessStates.RUNNING, node_name))
         # check that the events received are compliant
         self.check_events('my_movies')
         self.assertFalse(self.context.has_events('my_movies'))
@@ -230,9 +230,10 @@ def check_test_reader_starting(self):
         """ Check the starting of the test_reader program. """
         # define the expected events for the test_reader program
         program = self.context.get_program('player:test_reader')
-        program.add_event(ProcessStateEvent(ProcessStates.STARTING, self.HOST_01))
-        program.add_event(ProcessStateEvent(ProcessStates.RUNNING, self.HOST_01))
-        program.add_event(ProcessStateEvent(ProcessStates.EXITED, self.HOST_01))
+        node_name = self.HOST_01 if self.HOST_01 in self.nodes else self.HOST_03
+        program.add_event(ProcessStateEvent(ProcessStates.STARTING, node_name))
+        program.add_event(ProcessStateEvent(ProcessStates.RUNNING, node_name))
+        program.add_event(ProcessStateEvent(ProcessStates.EXITED, node_name))
         # check that the events received are compliant
         self.check_events('player')
         self.assertFalse(self.context.has_events('player'))
@@ -247,10 +248,12 @@ def check_web_movies_starting(self):
         application.add_program(program)
         # define the expected events for the web_browser program
         # WARN: if errors happen, like "AssertionError: 'RUNNING' != 'BACKOFF'"
-        # this is probably because there is an instance of firefox already running before supervisord has been started
-        # close it before the test
-        program.add_event(ProcessStateEvent(ProcessStates.STARTING, self.HOST_01))
-        program.add_event(ProcessStateEvent(ProcessStates.RUNNING, self.HOST_01))
+        #  this is probably because there is an instance of firefox already running before supervisord has been started
+        #  close it before the test
+        node_name = next(node_name for node_name in [self.HOST_03, self.HOST_01, self.HOST_02]
+                         if node_name in self.nodes)
+        program.add_event(ProcessStateEvent(ProcessStates.STARTING, node_name))
+        program.add_event(ProcessStateEvent(ProcessStates.RUNNING, node_name))
         # check that the events received are compliant
         self.check_events('web_movies')
         self.assertFalse(self.context.has_events('web_movies'))
diff --git a/supvisors/test/scripts/check_starting_strategy.py b/supvisors/test/scripts/check_starting_strategy.py
index 99d11c84..331a1475 100644
--- a/supvisors/test/scripts/check_starting_strategy.py
+++ b/supvisors/test/scripts/check_starting_strategy.py
@@ -39,7 +39,7 @@ def setUp(self):
         # check the loading on running addresses
         # initial state is cliche81=14% cliche82=15% cliche83=5%
         self._refresh_loading()
-        self.assertEqual([14, 15, 5], list(self.loading.values()))
+        assert list(self.loading.values()) == [10, 15, 9]
         # check that 10 converter programs are STOPPED
         processes_info = self.local_supvisors.get_process_info('my_movies:*')
         converters = [info for info in processes_info
@@ -104,36 +104,36 @@ def test_config(self):
         # so applicable order is the one defined in the supvisors section,
         # i.e. cliche81, cliche82, cliche83, cliche84 (not running)
         self._start_converter(0)
-        self.assertEqual([39, 15, 5], list(self.loading.values()))
+        self.assertEqual([35, 15, 9], list(self.loading.values()))
         # continue with cliche81
         self._start_converter(1)
-        self.assertEqual([64, 15, 5], list(self.loading.values()))
+        self.assertEqual([60, 15, 9], list(self.loading.values()))
         # try with converter_04 to check the alt config where cliche83 comes first
         self._start_converter(4)
-        self.assertEqual([64, 15, 30], list(self.loading.values()))
+        self.assertEqual([60, 15, 34], list(self.loading.values()))
         # there is still place on cliche81
         self._start_converter(2)
-        self.assertEqual([89, 15, 30], list(self.loading.values()))
+        self.assertEqual([85, 15, 34], list(self.loading.values()))
         # cliche81 is full. cliche82 will be used now
         self._start_converter(3)
-        self.assertEqual([89, 40, 30], list(self.loading.values()))
+        self.assertEqual([85, 40, 34], list(self.loading.values()))
         # there is still place on cliche82
         # try with converter_07 to check the alt config
         # cliche81 is full, so second address in config will be used (cliche83)
         self._start_converter(7)
-        self.assertEqual([89, 40, 55], list(self.loading.values()))
+        self.assertEqual([85, 40, 59], list(self.loading.values()))
         # there is still place on cliche82
         self._start_converter(5)
-        self.assertEqual([89, 65, 55], list(self.loading.values()))
+        self.assertEqual([85, 65, 59], list(self.loading.values()))
         # cliche81 is full. cliche82 will be used now
         self._start_converter(6)
-        self.assertEqual([89, 90, 55], list(self.loading.values()))
+        self.assertEqual([85, 90, 59], list(self.loading.values()))
         # cliche81 & cliche82 are full. cliche83 will be used now
         self._start_converter(8)
-        self.assertEqual([89, 90, 80], list(self.loading.values()))
+        self.assertEqual([85, 90, 84], list(self.loading.values()))
         # last converter cannot be started: no resource left
         self._start_converter_failed(9)
-        self.assertEqual([89, 90, 80], list(self.loading.values()))
+        self.assertEqual([85, 90, 84], list(self.loading.values()))
 
     def test_less_loaded(self):
         """ Test the LESS_LOADED starting strategy.
@@ -141,26 +141,26 @@ def test_less_loaded(self):
         print('### Testing LESS_LOADED starting strategy')
         self.strategy = StartingStrategies.LESS_LOADED
         self._start_converter(0)
-        self.assertEqual([14, 15, 30], list(self.loading.values()))
+        self.assertEqual([10, 15, 34], list(self.loading.values()))
         self._start_converter(1)
-        self.assertEqual([39, 15, 30], list(self.loading.values()))
+        self.assertEqual([35, 15, 34], list(self.loading.values()))
         self._start_converter(2)
-        self.assertEqual([39, 40, 30], list(self.loading.values()))
+        self.assertEqual([35, 40, 34], list(self.loading.values()))
         self._start_converter(3)
-        self.assertEqual([39, 40, 55], list(self.loading.values()))
+        self.assertEqual([35, 40, 59], list(self.loading.values()))
         self._start_converter(4)
-        self.assertEqual([64, 40, 55], list(self.loading.values()))
+        self.assertEqual([60, 40, 59], list(self.loading.values()))
         self._start_converter(5)
-        self.assertEqual([64, 65, 55], list(self.loading.values()))
+        self.assertEqual([60, 65, 59], list(self.loading.values()))
         self._start_converter(6)
-        self.assertEqual([64, 65, 80], list(self.loading.values()))
+        self.assertEqual([60, 65, 84], list(self.loading.values()))
         self._start_converter(7)
-        self.assertEqual([89, 65, 80], list(self.loading.values()))
+        self.assertEqual([85, 65, 84], list(self.loading.values()))
         self._start_converter(8)
-        self.assertEqual([89, 90, 80], list(self.loading.values()))
+        self.assertEqual([85, 90, 84], list(self.loading.values()))
         # last converter cannot be started: no resource left
         self._start_converter_failed(9)
-        self.assertEqual([89, 90, 80], list(self.loading.values()))
+        self.assertEqual([85, 90, 84], list(self.loading.values()))
 
     def test_most_loaded(self):
         """ Test the MOST_LOADED starting strategy.
@@ -168,26 +168,26 @@ def test_most_loaded(self):
         print('### Testing MOST_LOADED starting strategy')
         self.strategy = StartingStrategies.MOST_LOADED
         self._start_converter(0)
-        self.assertEqual([14, 40, 5], list(self.loading.values()))
+        self.assertEqual([10, 40, 9], list(self.loading.values()))
         self._start_converter(1)
-        self.assertEqual([14, 65, 5], list(self.loading.values()))
+        self.assertEqual([10, 65, 9], list(self.loading.values()))
         self._start_converter(2)
-        self.assertEqual([14, 90, 5], list(self.loading.values()))
+        self.assertEqual([10, 90, 9], list(self.loading.values()))
         self._start_converter(3)
-        self.assertEqual([39, 90, 5], list(self.loading.values()))
+        self.assertEqual([35, 90, 9], list(self.loading.values()))
         self._start_converter(4)
-        self.assertEqual([64, 90, 5], list(self.loading.values()))
+        self.assertEqual([60, 90, 9], list(self.loading.values()))
         self._start_converter(5)
-        self.assertEqual([89, 90, 5], list(self.loading.values()))
+        self.assertEqual([85, 90, 9], list(self.loading.values()))
         self._start_converter(6)
-        self.assertEqual([89, 90, 30], list(self.loading.values()))
+        self.assertEqual([85, 90, 34], list(self.loading.values()))
         self._start_converter(7)
-        self.assertEqual([89, 90, 55], list(self.loading.values()))
+        self.assertEqual([85, 90, 59], list(self.loading.values()))
         self._start_converter(8)
-        self.assertEqual([89, 90, 80], list(self.loading.values()))
+        self.assertEqual([85, 90, 84], list(self.loading.values()))
         # last converter cannot be started: no resource left
         self._start_converter_failed(9)
-        self.assertEqual([89, 90, 80], list(self.loading.values()))
+        self.assertEqual([85, 90, 84], list(self.loading.values()))
 
     def test_local(self):
         """ Test the LOCAL starting strategy.
@@ -196,14 +196,14 @@ def test_local(self):
         self.strategy = StartingStrategies.LOCAL
         # this test should be started only from cliche81 so processes should be started only on cliche81
         self._start_converter(0)
-        self.assertEqual([39, 15, 5], list(self.loading.values()))
+        self.assertEqual([35, 15, 9], list(self.loading.values()))
         self._start_converter(1)
-        self.assertEqual([64, 15, 5], list(self.loading.values()))
+        self.assertEqual([60, 15, 9], list(self.loading.values()))
         self._start_converter(2)
-        self.assertEqual([89, 15, 5], list(self.loading.values()))
+        self.assertEqual([85, 15, 9], list(self.loading.values()))
         # next converter cannot be started: no resource left
         self._start_converter_failed(3)
-        self.assertEqual([89, 15, 5], list(self.loading.values()))
+        self.assertEqual([85, 15, 9], list(self.loading.values()))
 
 
 def test_suite():
diff --git a/supvisors/test/scripts/check_stop_sequence.py b/supvisors/test/scripts/check_stop_sequence.py
index c08fe083..eec48d97 100644
--- a/supvisors/test/scripts/check_stop_sequence.py
+++ b/supvisors/test/scripts/check_stop_sequence.py
@@ -43,7 +43,7 @@ def test_sequencing(self):
         # send restart request
         self.proxy.supvisors.restart()
         # test the stopping of service application
-        # self.check_service_stopping()
+        self.check_service_stopping()
         # test the stopping of web_movies application
         self.check_web_movies_stopping()
         # test the stopping of my_movies application
@@ -71,6 +71,8 @@ def create_context(self):
         # set required status
         self.context.get_program('my_movies:manager').required = True
         self.context.get_program('my_movies:web_server').required = True
+        # set managed status
+        self.context.get_application('service').managed = False
 
     def check_converter_running(self):
         """ Check the start sequence of the converter program. """
@@ -88,7 +90,7 @@ def check_service_stopping(self):
         program = self.context.get_program('service:disk_handler')
         if program.state in RUNNING_STATES:
             node_names = program.node_names.copy()
-            # event sequence on nodes is quite unpredictible so test only process states
+            # event sequence on nodes is quite unpredictable so test only process states
             while len(node_names) > 1:
                 # STOPPING then STOPPED on one node in list. still RUNNING on others
                 program.add_event(ProcessStateEvent(ProcessStates.RUNNING))
diff --git a/supvisors/test/scripts/running_addresses.py b/supvisors/test/scripts/running_addresses.py
index 9dec705a..a2eee66a 100644
--- a/supvisors/test/scripts/running_addresses.py
+++ b/supvisors/test/scripts/running_addresses.py
@@ -56,8 +56,8 @@ def setUp(self):
         # assumption is made that this test is run on Supvisors Master address
         self.assertEqual(gethostname(), self.local_supvisors.get_master_address())
         # keep a reference to all RPC proxies
-        self.proxies = {address: rpcrequests.getRPCInterface(address, os.environ)
-                        for address in self.running_nodes}
+        self.proxies = {node_name: rpcrequests.getRPCInterface(node_name, os.environ)
+                        for node_name in self.running_nodes}
         # create the thread of event subscriber
         self.zcontext = zmq.Context.instance()
         self.logger = create_logger(logfile=r'./log/running_addresses.log')
diff --git a/supvisors/test/scripts/sequence_checker.py b/supvisors/test/scripts/sequence_checker.py
index 9a86508a..61148ee7 100644
--- a/supvisors/test/scripts/sequence_checker.py
+++ b/supvisors/test/scripts/sequence_checker.py
@@ -82,6 +82,7 @@ class Application:
     def __init__(self, application_name):
         """ Initialization of the attributes. """
         self.application_name = application_name
+        self.managed = True
         # create dict of states / process_names
         self.major_failure, self.minor_failure = (False,) * 2
         # event dictionary
@@ -97,38 +98,42 @@ def get_program(self, program_name):
 
     def is_starting(self):
         """ Return True if the application has a starting program. """
-        for program in self.programs.values():
-            if program.state in (ProcessStates.STARTING, ProcessStates.BACKOFF):
-                return True
+        if self.managed:
+            for program in self.programs.values():
+                if program.state in (ProcessStates.STARTING, ProcessStates.BACKOFF):
+                    return True
         return False
 
     def is_stopping(self):
         """ Return True if the application has a stopping program and no starting program. """
         stopping = False
-        for program in self.programs.values():
-            if program.state in (ProcessStates.STARTING, ProcessStates.BACKOFF):
-                return False
-            if program.state == ProcessStates.STOPPING:
-                stopping = True
+        if self.managed:
+            for program in self.programs.values():
+                if program.state in (ProcessStates.STARTING, ProcessStates.BACKOFF):
+                    return False
+                if program.state == ProcessStates.STOPPING:
+                    stopping = True
         return stopping
 
     def is_running(self):
         """ Return True if the application has a running program and no starting or stopping program. """
         running = False
-        for program in self.programs.values():
-            if program.state in (ProcessStates.STARTING,
-                                 ProcessStates.BACKOFF,
-                                 ProcessStates.STOPPING):
-                return False
-            if program.state == ProcessStates.RUNNING:
-                running = True
+        if self.managed:
+            for program in self.programs.values():
+                if program.state in (ProcessStates.STARTING,
+                                     ProcessStates.BACKOFF,
+                                     ProcessStates.STOPPING):
+                    return False
+                if program.state == ProcessStates.RUNNING:
+                    running = True
         return running
 
     def is_stopped(self):
         """ Return True if the application has only stopped programs. """
-        for program in self.programs.values():
-            if program.state in [ProcessStates.STOPPING] + list(RUNNING_STATES):
-                return False
+        if self.managed:
+            for program in self.programs.values():
+                if program.state in [ProcessStates.STOPPING] + list(RUNNING_STATES):
+                    return False
         return True
 
     def has_major_failure(self):
diff --git a/supvisors/test/use_cases/scenario_1/bin/common.sh b/supvisors/test/use_cases/scenario_1/bin/common.sh
new file mode 100755
index 00000000..30a2a055
--- /dev/null
+++ b/supvisors/test/use_cases/scenario_1/bin/common.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+zenity --info --text=$1
+
diff --git a/supvisors/test/use_cases/scenario_1/bin/config_manager.sh b/supvisors/test/use_cases/scenario_1/bin/config_manager.sh
new file mode 100755
index 00000000..f8d5d4c3
--- /dev/null
+++ b/supvisors/test/use_cases/scenario_1/bin/config_manager.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+./bin/common.sh $0
+
diff --git a/supvisors/test/use_cases/scenario_1/bin/data_processing.sh b/supvisors/test/use_cases/scenario_1/bin/data_processing.sh
new file mode 100755
index 00000000..f8d5d4c3
--- /dev/null
+++ b/supvisors/test/use_cases/scenario_1/bin/data_processing.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+./bin/common.sh $0
+
diff --git a/supvisors/test/use_cases/scenario_1/bin/data_recorder.sh b/supvisors/test/use_cases/scenario_1/bin/data_recorder.sh
new file mode 100755
index 00000000..f8d5d4c3
--- /dev/null
+++ b/supvisors/test/use_cases/scenario_1/bin/data_recorder.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+./bin/common.sh $0
+
diff --git a/supvisors/test/use_cases/scenario_1/bin/external_interface.sh b/supvisors/test/use_cases/scenario_1/bin/external_interface.sh
new file mode 100755
index 00000000..f8d5d4c3
--- /dev/null
+++ b/supvisors/test/use_cases/scenario_1/bin/external_interface.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+./bin/common.sh $0
+
diff --git a/supvisors/test/use_cases/scenario_1/bin/hci.sh b/supvisors/test/use_cases/scenario_1/bin/hci.sh
new file mode 100755
index 00000000..f8d5d4c3
--- /dev/null
+++ b/supvisors/test/use_cases/scenario_1/bin/hci.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+./bin/common.sh $0
+
diff --git a/supvisors/test/use_cases/scenario_1/bin/sensor_acquisition.sh b/supvisors/test/use_cases/scenario_1/bin/sensor_acquisition.sh
new file mode 100755
index 00000000..f8d5d4c3
--- /dev/null
+++ b/supvisors/test/use_cases/scenario_1/bin/sensor_acquisition.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+./bin/common.sh $0
+
diff --git a/supvisors/test/use_cases/scenario_1/bin/sensor_processing.sh b/supvisors/test/use_cases/scenario_1/bin/sensor_processing.sh
new file mode 100755
index 00000000..f8d5d4c3
--- /dev/null
+++ b/supvisors/test/use_cases/scenario_1/bin/sensor_processing.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+./bin/common.sh $0
+
diff --git a/supvisors/test/use_cases/scenario_1/bin/wait_nfs_mount.sh b/supvisors/test/use_cases/scenario_1/bin/wait_nfs_mount.sh
new file mode 100755
index 00000000..bffbe566
--- /dev/null
+++ b/supvisors/test/use_cases/scenario_1/bin/wait_nfs_mount.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+sleep 5
+
diff --git a/supvisors/test/use_cases/scenario_1/etc/cliche81/group_cliche81.ini b/supvisors/test/use_cases/scenario_1/etc/cliche81/group_cliche81.ini
new file mode 100644
index 00000000..f5fc9705
--- /dev/null
+++ b/supvisors/test/use_cases/scenario_1/etc/cliche81/group_cliche81.ini
@@ -0,0 +1,2 @@
+[group:scenario_1]
+programs=wait_nfs_mount_1,hci,config_manager,data_processing,external_interface,data_recorder
diff --git a/supvisors/test/use_cases/scenario_1/etc/cliche81/programs_cliche81.ini b/supvisors/test/use_cases/scenario_1/etc/cliche81/programs_cliche81.ini
new file mode 100644
index 00000000..d538b64a
--- /dev/null
+++ b/supvisors/test/use_cases/scenario_1/etc/cliche81/programs_cliche81.ini
@@ -0,0 +1,44 @@
+[program:hci]
+command=./bin/hci.sh
+autostart=false
+killasgroup=true
+stopasgroup=true
+redirect_stderr=true
+stdout_logfile=log/%(program_name)s_%(ENV_CUR_DATE)s_%(ENV_CUR_TIME)s.log
+stdout_logfile_backups=5
+
+[program:config_manager]
+command=./bin/config_manager.sh
+autostart=false
+killasgroup=true
+stopasgroup=true
+redirect_stderr=true
+stdout_logfile=log/%(program_name)s_%(ENV_CUR_DATE)s_%(ENV_CUR_TIME)s.log
+stdout_logfile_backups=5
+
+[program:data_processing]
+command=./bin/data_processing.sh
+autostart=false
+killasgroup=true
+stopasgroup=true
+redirect_stderr=true
+stdout_logfile=log/%(program_name)s_%(ENV_CUR_DATE)s_%(ENV_CUR_TIME)s.log
+stdout_logfile_backups=5
+
+[program:external_interface]
+command=./bin/external_interface.sh
+autostart=false
+killasgroup=true
+stopasgroup=true
+redirect_stderr=true
+stdout_logfile=log/%(program_name)s_%(ENV_CUR_DATE)s_%(ENV_CUR_TIME)s.log
+stdout_logfile_backups=5
+
+[program:data_recorder]
+command=./bin/data_recorder.sh
+autostart=false
+killasgroup=true
+stopasgroup=true
+redirect_stderr=true
+stdout_logfile=log/%(program_name)s_%(ENV_CUR_DATE)s_%(ENV_CUR_TIME)s.log
+stdout_logfile_backups=5
diff --git a/supvisors/test/use_cases/scenario_1/etc/cliche81/wait_nfs_mount.ini b/supvisors/test/use_cases/scenario_1/etc/cliche81/wait_nfs_mount.ini
new file mode 100644
index 00000000..0cd01f82
--- /dev/null
+++ b/supvisors/test/use_cases/scenario_1/etc/cliche81/wait_nfs_mount.ini
@@ -0,0 +1,8 @@
+[program:wait_nfs_mount_1]
+command=./bin/wait_nfs_mount.sh
+autostart=false
+killasgroup=true
+stopasgroup=true
+redirect_stderr=true
+stdout_logfile=log/%(program_name)s_%(ENV_CUR_DATE)s_%(ENV_CUR_TIME)s.log
+stdout_logfile_backups=5
diff --git a/supvisors/test/use_cases/scenario_1/etc/cliche82/group_cliche82.ini b/supvisors/test/use_cases/scenario_1/etc/cliche82/group_cliche82.ini
new file mode 100644
index 00000000..32272435
--- /dev/null
+++ b/supvisors/test/use_cases/scenario_1/etc/cliche82/group_cliche82.ini
@@ -0,0 +1,2 @@
+[group:scenario_1]
+programs=wait_nfs_mount_2,sensor_acquisition_1,sensor_processing_1
diff --git a/supvisors/test/use_cases/scenario_1/etc/cliche82/programs_cliche82.ini b/supvisors/test/use_cases/scenario_1/etc/cliche82/programs_cliche82.ini
new file mode 100644
index 00000000..04367733
--- /dev/null
+++ b/supvisors/test/use_cases/scenario_1/etc/cliche82/programs_cliche82.ini
@@ -0,0 +1,18 @@
+[program:sensor_acquisition_1]
+command=./bin/sensor_acquisition.sh
+autostart=false
+killasgroup=true
+stopasgroup=true
+redirect_stderr=true
+stdout_logfile=log/%(program_name)s_%(ENV_CUR_DATE)s_%(ENV_CUR_TIME)s.log
+stdout_logfile_backups=5
+
+[program:sensor_processing_1]
+command=./bin/sensor_processing.sh
+autostart=false
+killasgroup=true
+stopasgroup=true
+redirect_stderr=true
+stdout_logfile=log/%(program_name)s_%(ENV_CUR_DATE)s_%(ENV_CUR_TIME)s.log
+stdout_logfile_backups=5
+
diff --git a/supvisors/test/use_cases/scenario_1/etc/cliche82/wait_nfs_mount.ini b/supvisors/test/use_cases/scenario_1/etc/cliche82/wait_nfs_mount.ini
new file mode 100644
index 00000000..24c97944
--- /dev/null
+++ b/supvisors/test/use_cases/scenario_1/etc/cliche82/wait_nfs_mount.ini
@@ -0,0 +1,8 @@
+[program:wait_nfs_mount_2]
+command=./bin/wait_nfs_mount.sh
+autostart=false
+killasgroup=true
+stopasgroup=true
+redirect_stderr=true
+stdout_logfile=log/%(program_name)s_%(ENV_CUR_DATE)s_%(ENV_CUR_TIME)s.log
+stdout_logfile_backups=5
diff --git a/supvisors/test/use_cases/scenario_1/etc/cliche83/group_cliche83.ini b/supvisors/test/use_cases/scenario_1/etc/cliche83/group_cliche83.ini
new file mode 100644
index 00000000..3f6294c7
--- /dev/null
+++ b/supvisors/test/use_cases/scenario_1/etc/cliche83/group_cliche83.ini
@@ -0,0 +1,2 @@
+[group:scenario_1]
+programs=wait_nfs_mount_3,sensor_acquisition_2,sensor_processing_2
diff --git a/supvisors/test/use_cases/scenario_1/etc/cliche83/programs_cliche83.ini b/supvisors/test/use_cases/scenario_1/etc/cliche83/programs_cliche83.ini
new file mode 100644
index 00000000..27026a4b
--- /dev/null
+++ b/supvisors/test/use_cases/scenario_1/etc/cliche83/programs_cliche83.ini
@@ -0,0 +1,17 @@
+[program:sensor_acquisition_2]
+command=./bin/sensor_acquisition.sh
+autostart=false
+killasgroup=true
+stopasgroup=true
+redirect_stderr=true
+stdout_logfile=log/%(program_name)s_%(ENV_CUR_DATE)s_%(ENV_CUR_TIME)s.log
+stdout_logfile_backups=5
+
+[program:sensor_processing_2]
+command=./bin/sensor_processing.sh
+autostart=false
+killasgroup=true
+stopasgroup=true
+redirect_stderr=true
+stdout_logfile=log/%(program_name)s_%(ENV_CUR_DATE)s_%(ENV_CUR_TIME)s.log
+stdout_logfile_backups=5
diff --git a/supvisors/test/use_cases/scenario_1/etc/cliche83/wait_nfs_mount.ini b/supvisors/test/use_cases/scenario_1/etc/cliche83/wait_nfs_mount.ini
new file mode 100644
index 00000000..110d0e07
--- /dev/null
+++ b/supvisors/test/use_cases/scenario_1/etc/cliche83/wait_nfs_mount.ini
@@ -0,0 +1,8 @@
+[program:wait_nfs_mount_3]
+command=./bin/wait_nfs_mount.sh
+autostart=false
+killasgroup=true
+stopasgroup=true
+redirect_stderr=true
+stdout_logfile=log/%(program_name)s_%(ENV_CUR_DATE)s_%(ENV_CUR_TIME)s.log
+stdout_logfile_backups=5
diff --git a/supvisors/test/use_cases/scenario_1/etc/localhost/group_localhost.ini b/supvisors/test/use_cases/scenario_1/etc/localhost/group_localhost.ini
new file mode 100644
index 00000000..6e49d773
--- /dev/null
+++ b/supvisors/test/use_cases/scenario_1/etc/localhost/group_localhost.ini
@@ -0,0 +1,2 @@
+[group:scenario_1]
+programs=hci,config_manager,data_processing,external_interface,data_recorder,sensor_acquisition_1,sensor_processing_1,sensor_acquisition_2,sensor_processing_2
diff --git a/supvisors/test/use_cases/scenario_1/etc/supervisord.conf b/supvisors/test/use_cases/scenario_1/etc/supervisord.conf
new file mode 120000
index 00000000..3521b274
--- /dev/null
+++ b/supvisors/test/use_cases/scenario_1/etc/supervisord.conf
@@ -0,0 +1,1 @@
+supervisord_distributed.conf
\ No newline at end of file
diff --git a/supvisors/test/use_cases/scenario_1/etc/supervisord_distributed.conf b/supvisors/test/use_cases/scenario_1/etc/supervisord_distributed.conf
new file mode 100644
index 00000000..b966bb90
--- /dev/null
+++ b/supvisors/test/use_cases/scenario_1/etc/supervisord_distributed.conf
@@ -0,0 +1,38 @@
+[inet_http_server]
+port=:61000
+
+[supervisord]
+logfile=log/supervisord.log
+logfile_backups=10
+loglevel=info
+pidfile=/tmp/supervisord.pid
+nodaemon=false
+umask=002
+
+[rpcinterface:supervisor]
+supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
+
+[supervisorctl]
+serverurl=http://localhost:61000
+
+[include]
+files = %(host_node_name)s/*.ini
+
+[supvisors]
+address_list=cliche81,cliche82,cliche83
+rules_file=etc/supvisors_rules.xml
+auto_fence=false
+internal_port=61001
+event_port=61002
+synchro_timeout=20
+starting_strategy=CONFIG
+conciliation_strategy=INFANTICIDE
+stats_periods=5,60,600
+stats_histo=100
+
+[rpcinterface:supvisors]
+supervisor.rpcinterface_factory = supvisors.plugin:make_supvisors_rpcinterface 
+
+[ctlplugin:supvisors]
+supervisor.ctl_factory = supvisors.supvisorsctl:make_supvisors_controller_plugin
+
diff --git a/supvisors/test/use_cases/scenario_1/etc/supervisord_localhost.conf b/supvisors/test/use_cases/scenario_1/etc/supervisord_localhost.conf
new file mode 100644
index 00000000..9d858081
--- /dev/null
+++ b/supvisors/test/use_cases/scenario_1/etc/supervisord_localhost.conf
@@ -0,0 +1,38 @@
+[inet_http_server]
+port=:61000
+
+[supervisord]
+logfile=log/supervisord.log
+logfile_backups=10
+loglevel=info
+pidfile=/tmp/supervisord.pid
+nodaemon=false
+umask=002
+
+[rpcinterface:supervisor]
+supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
+
+[supervisorctl]
+serverurl=http://localhost:61000
+
+[include]
+files = */*.ini localhost/group-scenario_1.ini
+
+[supvisors]
+address_list=cliche81
+rules_file=etc/supvisors-rules.xml
+auto_fence=false
+internal_port=61001
+event_port=61002
+synchro_timeout=15
+starting_strategy=CONFIG
+conciliation_strategy=INFANTICIDE
+stats_periods=5,60,600
+stats_histo=100
+
+[rpcinterface:supvisors]
+supervisor.rpcinterface_factory = supvisors.plugin:make_supvisors_rpcinterface 
+
+[ctlplugin:supvisors]
+supervisor.ctl_factory = supvisors.supvisorsctl:make_supvisors_controller_plugin
+
diff --git a/supvisors/test/use_cases/scenario_1/etc/supvisors_rules.xml b/supvisors/test/use_cases/scenario_1/etc/supvisors_rules.xml
new file mode 100644
index 00000000..e32e7937
--- /dev/null
+++ b/supvisors/test/use_cases/scenario_1/etc/supvisors_rules.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<root>
+    <!-- models -->
+    <model name="model_scenario_1">
+        <start_sequence>2</start_sequence>
+        <required>true</required>
+    </model>
+
+	<!-- Scenario 1 Application -->
+        <application name="scenario_1">
+		    <start_sequence>1</start_sequence>
+            <starting_failure_strategy>CONTINUE</starting_failure_strategy>
+
+            <pattern name="">
+			    <reference>model_scenario_1</reference>
+            </pattern>
+
+            <pattern name="wait_nfs_mount">
+			    <reference>model_scenario_1</reference>
+                <start_sequence>1</start_sequence>
+			    <wait_exit>true</wait_exit>
+            </pattern>
+	</application>
+
+</root>
+
diff --git a/supvisors/test/use_cases/scenario_1/scenario_1.rst b/supvisors/test/use_cases/scenario_1/scenario_1.rst
new file mode 100644
index 00000000..d3ea7093
--- /dev/null
+++ b/supvisors/test/use_cases/scenario_1/scenario_1.rst
@@ -0,0 +1,433 @@
+Scenario 1
+==========
+
+Context
+-------
+
+In this use case, the application is distributed over 3 nodes. The process distribution is fixed.
+The application logs and other data are written to a disk that is made available through a NFS mount point.
+
+
+Requirements
+------------
+
+Here are the use case requirements:
+
+    1. Due to the inter-processes communication scheme, the process distribution shall be fixed.
+    2. The application shall wait for the NFS mount point before it is started.
+    3. An operational status of the application shall be provided.
+    4. The user shall not be able to start an unexpected application process on any other node.
+    5. The application shall be restarted on the 3 nodes upon user request.
+    6. There shall be a non-distributed configuration for developers' use, assuming a different inter-processes communication scheme.
+    7. The non-distributed configuration shall not wait for the NFS mount point.
+
+
+Supervisor configuration
+------------------------
+
+There are undoubtedly many ways to skin the cat. Here follows one solution.
+
+As an answer to **Requirement 1** and **Requirement 4**, let's split the Supervisor configuration file into 4 parts:
+
+    * the ``supervisord.conf`` configuration file ;
+    * the program definitions and the group definition (``.ini`` files) for the first node ;
+    * the program definitions and the group definition (``.ini`` files) for the second node ;
+    * the program definitions and the group definition (``.ini`` files) for the third node.
+
+All program are configured using ``autostart=true``.
+
+For packaging facility, the full configuration is available to all nodes but the ``include`` section of the
+configuration file uses the ``host_node_name`` so that the running configuration is actually different on all nodes.
+
+.. code-block:: ini
+
+    [include]
+    files = %(host_node_name)s/*.ini
+
+The resulting file tree would be as follows.
+
+.. code-block:: bash
+
+    [bash] > tree
+    .
+    ├── etc
+    │         ├── cliche81
+    │         │         ├── group_cliche81.ini
+    │         │         └── programs_cliche81.ini
+    │         ├── cliche82
+    │         │         ├── group_cliche82.ini
+    │         │         └── programs_cliche82.ini
+    │         ├── cliche83
+    │         │         ├── group_cliche83.ini
+    │         │         └── programs_cliche83.ini
+    │         └── supervisord.conf
+
+
+For **Requirement 6**, let's just define a group where all programs are declared.
+The proposal is to have 2 *Supervisor* configuration files, one for the distributed application and the other
+for the non-distributed application, the variation being just in the include section.
+
+About **Requirement 2**, *Supervisor* does not provide any facility to stage the starting sequence (refer to
+`Issue #122 - supervisord Starts All Processes at the Same Time <https://github.com/Supervisor/supervisor/issues/122>`_).
+A workaround here would be to insert a wait loop in all the application programs (in the program command line
+or in the program source code). The idea of pushing this wait loop outside the *Supervisor* scope - just before
+starting ``supervisord`` - is excluded as it would impose this dependency on other applications eventually managed
+by *Supervisor*.
+
+With regard to **Requirement 7**, this workaround would require different program commands or parameters, so finally
+different program definitions from *Supervisor* configuration perspective.
+
+.. code-block:: bash
+
+    [bash] > tree
+    .
+    ├── etc
+    │         ├── cliche81
+    │         │         ├── group_cliche81.ini
+    │         │         └── programs_cliche81.ini
+    │         ├── cliche82
+    │         │         ├── group_cliche82.ini
+    │         │         └── programs_cliche82.ini
+    │         ├── cliche83
+    │         │         ├── group_cliche83.ini
+    │         │         └── programs_cliche83.ini
+    │         ├── localhost
+    │         │         ├── group_localhost.ini
+    │         │         └── programs_localhost.ini
+    │         ├── supervisord.conf -> supervisord_distributed.conf
+    │         ├── supervisord_distributed.conf
+    │         ├── supervisord_localhost.conf
+    │         └── supvisors-rules.xml
+
+Here is the resulting ``include`` sections:
+
+.. code-block:: ini
+
+    # include section for distributed application in supervisord_distributed.conf
+    [include]
+    files = %(host_node_name)s/*.ini
+
+    # include section for non-distributed application in supervisord_localhost.conf
+    [include]
+    files = localhost/*.ini
+
+*Supervisor* provides nothing for **Requirement 3**. The user has to evaluate the operational status based on the process
+status provided by the *Supervisor* instances on the 3 nodes, either using multiple ``supervisorctl`` shell commands,
+XML-RPCs or event listeners.
+
+To restart the whole application (**Requirement 5**), the user can perform ``supervisorctl`` shell commands or
+XML-RPCs on each *Supervisor* instance.
+
+.. code-block:: bash
+
+    [bash] > for i in cliche81 cliche82 cliche83
+    ... do
+    ...    supervisorctl -s http://$i:<port> restart <group>:*
+    ... done
+
+
+Eventually, all the requirements could be met using *Supervisor* but it would require additional development
+at application level to build an operational status, based on process information provided by *Supervisor*.
+
+It would also require some additional complexity in the configuration files and in the program command lines
+to manage a staged starting sequence of the programs in the group and to manage the distribution of the application
+over different platforms.
+
+
+Involving **Supvisors**
+-----------------------
+
+A solution based on **Supvisors** could use the following *Supervisor* configuration (same principles as the previous
+section):
+
+    * the ``supervisord_distributed.conf`` configuration file for the distributed application ;
+    * the ``supervisord_localhost.conf`` configuration file for the non-distributed application ;
+    * the program definitions and the group definition (``.ini`` files) for the first node ;
+    * the program definitions and the group definition (``.ini`` files) for the second node ;
+    * the program definitions and the group definition (``.ini`` files) for the third node ;
+    * the group definition including all application programs for a local node.
+
+All programs are now configured using ``autostart=false``.
+
+About **Requirement 2**, **Supvisors** manages staged starting sequences and it offers a possibility to wait for a
+planned exit of a process in the sequence.
+So let's define a new program ``wait_nfs_mount_X`` per node and whose role is to exit (using an expected exit code,
+as defined in `Supervisor program configuration <http://supervisord.org/configuration.html#program-x-section-values>`_)
+as soon as the NFS mount is available.
+
+Complying about **Requirement 7** is just about avoiding the inclusion of the ``wait_nfs_mount_X`` programs in the
+*Supervisor* configuration file in the case of a non-distributed application. That's why the *Supervisor*
+configuration of these programs is isolated from the configuration of the other programs.
+THat way, **Supvisors** makes it possible to avoid an impact to program definitions, scripts and source code
+when dealing with such a requirement.
+
+Here follows what the include section may look like in both *Supervisor* configuration files.
+
+.. code-block:: ini
+
+    # include section for distributed application in supervisord_distributed.conf (unchanged)
+    [include]
+    files = %(host_node_name)s/*.ini
+
+    # include section for non-distributed application in supervisord_localhost.conf
+    # the same program definitions as the distributed application are used
+    [include]
+    files = %(host_node_name)s/programs_*.ini localhost/group_localhost.ini
+
+Now that programs are not started automatically by *Supervisor*, a **Supvisors** rules file is needed to define
+the staged starting sequence. A first naive - yet functional - approach would be to use a model for all programs
+to be started on the same node.
+
+.. code-block:: xml
+
+    <?xml version="1.0" encoding="UTF-8" standalone="no"?>
+    <root>
+        <!-- models -->
+        <model name="model_cliche81">
+            <addresses>cliche81</addresses>
+            <start_sequence>2</start_sequence>
+            <required>true</required>
+        </model>
+        <model name="model_cliche82">
+            <reference>model_cliche81</reference>
+            <addresses>cliche82</addresses>
+        </model>
+        <model name="model_cliche83">
+            <reference>model_cliche81</reference>
+            <addresses>cliche83</addresses>
+        </model>
+        <!-- Scenario 1 Application -->
+        <application name="scenario_1">
+            <start_sequence>1</start_sequence>
+            <starting_failure_strategy>CONTINUE</starting_failure_strategy>
+            <!-- Programs on cliche81 -->
+            <program name="hci">
+                <reference>model_cliche81</reference>
+            </program>
+            <program name="config_manager">
+                <reference>model_cliche81</reference>
+            </program>
+            <program name="data_processing">
+                <reference>model_cliche81</reference>
+            </program>
+            <program name="external_interface">
+                <reference>model_cliche81</reference>
+            </program>
+            <program name="data_recorder">
+                <reference>model_cliche81</reference>
+            </program>
+            <program name="wait_nfs_mount_1">
+                <reference>model_cliche81</reference>
+                <start_sequence>1</start_sequence>
+                <wait_exit>true</wait_exit>
+            </program>
+            <!-- Programs on cliche82 -->
+            <program name="sensor_acquisition_1">
+                <reference>model_cliche82</reference>
+            </program>
+            <program name="sensor_processing_1">
+                <reference>model_cliche82</reference>
+            </program>
+            <program name="wait_nfs_mount_2">
+                <reference>model_cliche82</reference>
+                <start_sequence>1</start_sequence>
+                <wait_exit>true</wait_exit>
+            </program>
+            <!-- Programs on cliche83 -->
+            <program name="sensor_acquisition_2">
+                <reference>model_cliche83</reference>
+            </program>
+            <program name="sensor_processing_2">
+                <reference>model_cliche83</reference>
+            </program>
+            <program name="wait_nfs_mount_3">
+                <reference>model_cliche83</reference>
+                <start_sequence>1</start_sequence>
+                <wait_exit>true</wait_exit>
+            </program>
+        </application>
+    </root>
+
+.. note::
+
+    A few words about how the ``wait_nfs_mount_X`` programs have been introduced here. It has to be noted that:
+
+        * the ``start_sequence`` of these programs is lower than the ``start_sequence`` of the other application programs ;
+        * their attribute ``wait_exit`` is set to ``true``.
+
+    The consequence is that the 3 programs ``wait_nfs_mount_X`` are started first on their respective node
+    when starting the ``scenario_1`` application. Then **Supvisors** waits for *all* of them to exit before it triggers
+    the starting of the other programs.
+
+Well, assuming that the node name could be included as a prefix to the program names, that would simplify
+the rules file a bit.
+
+.. code-block:: xml
+
+    <?xml version="1.0" encoding="UTF-8" standalone="no"?>
+    <root>
+        <!-- models -->
+        <model name="model_cliche81">
+            <addresses>cliche81</addresses>
+            <start_sequence>2</start_sequence>
+            <required>true</required>
+        </model>
+        <model name="model_cliche82">
+            <reference>model_cliche81</reference>
+            <addresses>cliche82</addresses>
+        </model>
+        <model name="model_cliche83">
+            <reference>model_cliche81</reference>
+            <addresses>cliche83</addresses>
+        </model>
+        <!-- Scenario 1 Application -->
+        <application name="scenario_1">
+            <start_sequence>1</start_sequence>
+            <starting_failure_strategy>CONTINUE</starting_failure_strategy>
+            <!-- Programs on cliche81 -->
+            <pattern name="cliche81_">
+                <reference>model_cliche81</reference>
+            </pattern>
+            <program name="wait_nfs_mount_1">
+                <reference>model_cliche81</reference>
+                <start_sequence>1</start_sequence>
+                <wait_exit>true</wait_exit>
+            </program>
+            <!-- Programs on cliche82 -->
+            <pattern name="cliche82_">
+                <reference>model_cliche82</reference>
+            </pattern>
+            <program name="wait_nfs_mount_2">
+                <reference>model_cliche82</reference>
+                <start_sequence>1</start_sequence>
+                <wait_exit>true</wait_exit>
+            </program>
+            <!-- Programs on cliche83 -->
+            <pattern name="cliche83_">
+                <reference>model_cliche83</reference>
+            </pattern>
+            <program name="wait_nfs_mount_3">
+                <reference>model_cliche83</reference>
+                <start_sequence>1</start_sequence>
+                <wait_exit>true</wait_exit>
+            </program>
+        </application>
+    </root>
+
+A bit shorter, still functional but the program names are now quite ugly. And the non-distributed version has not been
+considered yet. With this approach, a different rules file is required to replace the node names with the developer's
+host name - assumed called ``cliche81`` here for the example.
+
+.. code-block:: xml
+
+    <?xml version="1.0" encoding="UTF-8" standalone="no"?>
+    <root>
+        <!-- Scenario 1 Application -->
+        <application name="scenario_1">
+            <start_sequence>1</start_sequence>
+            <starting_failure_strategy>CONTINUE</starting_failure_strategy>
+            <!-- Programs on localhost -->
+            <pattern name="">
+                <addresses>cliche81</addresses>
+                <start_sequence>1</start_sequence>
+                <required>true</required>
+            </pattern>
+        </application>
+    </root>
+
+This rules file is very simple here as all programs have the exactly same rules.
+
+.. hint::
+
+    When the same rules apply to all programs in an application, an empty pattern can be used as it will match
+    all program names of the application.
+
+But actually, there is a much more simple solution in the present case. Let's consider this instead:
+
+.. code-block:: xml
+
+    <?xml version="1.0" encoding="UTF-8" standalone="no"?>
+    <root>
+        <!-- models -->
+        <model name="model_scenario_1">
+            <start_sequence>2</start_sequence>
+            <required>true</required>
+        </model>
+        <!-- Scenario 1 Application -->
+        <application name="scenario_1">
+            <start_sequence>1</start_sequence>
+            <starting_failure_strategy>CONTINUE</starting_failure_strategy>
+            <pattern name="">
+                <reference>model_scenario_1</reference>
+            </pattern>
+            <pattern name="wait_nfs_mount">
+                <reference>model_scenario_1</reference>
+                <start_sequence>1</start_sequence>
+                <wait_exit>true</wait_exit>
+            </pattern>
+        </application>
+    </root>
+
+Much shorter. Yet it does the same. For both the distributed application and the non-distributed application !
+
+The main point is that the ``addresses`` attribute is not used at all. Clearly, this gives **Supvisors**
+the authorization to start all programs on every nodes. However **Supvisors** knows about the *Supervisor* configuration
+in the 3 nodes. When choosing a node to start a program, **Supvisors** considers the intersection between the authorized
+nodes - all of them here - and the possible nodes, i.e. the active nodes where the program is defined in *Supervisor*.
+One of the first decisions in this use case is that every programs are known to only one *Supervisor* instance so that
+gives **Supvisors** only one possibility.
+
+For **Requirement 3**, **Supvisors** provides the operational status of the application based on the status of its
+processes, in accordance with their importance. In the present example, all programs are defined with the same
+importance (``required`` set to ``true``). This status is made available through the **Supvisors**
+:ref:`dashboard_application`, the :ref:`xml_rpc` and the :ref:`event_interface`.
+
+The key point here is that **Supvisors** is able to build a single application from the processes configured
+on the 3 nodes because the same group name (``scenario_1``) is used in all *Supervisor* configuration files.
+
+.. image:: images/supvisors_scenario_1.png
+    :alt: Supvisors Use Cases - Scenario 1
+    :align: center
+
+The final file tree is as follows.
+
+.. code-block:: bash
+
+    [bash] > tree
+    .
+    ├── etc
+    │         ├── cliche81
+    │         │         ├── group_cliche81.ini
+    │         │         ├── programs_cliche81.ini
+    │         │         └── wait_nfs_mount.ini
+    │         ├── cliche82
+    │         │         ├── group_cliche82.ini
+    │         │         ├── programs_cliche82.ini
+    │         │         └── wait_nfs_mount.ini
+    │         ├── cliche83
+    │         │         ├── group_cliche83.ini
+    │         │         ├── programs_cliche83.ini
+    │         │         └── wait_nfs_mount.ini
+    │         ├── localhost
+    │         │         └── group_localhost.ini
+    │         ├── supervisord.conf -> supervisord_distributed.conf
+    │         ├── supervisord_distributed.conf
+    │         ├── supervisord_localhost.conf
+    │         └── supvisors_rules.xml
+
+To restart the whole application (**Requirement 5**), the user can perform a single ``supervisorctl`` shell command
+or a single XML-RPC on **Supvisors** from any node.
+
+.. code-block:: bash
+
+    [bash] > supervisorctl restart_application <strategy> <group>
+
+
+As a conclusion, all the requirements are met using **Supvisors** and without any impact on the application to be
+supervised. **Supvisors** brings gain over application control and status.
+
+
+Example
+-------
+
+The full example is available in `Supvisors Use Cases - Scenario 1 <https://github.com/julien6387/supvisors/tree/master/supvisors/test/use_cases/scenario_1>`_.
diff --git a/supvisors/test/use_cases/scenario_1/start.sh b/supvisors/test/use_cases/scenario_1/start.sh
new file mode 100755
index 00000000..e66a5f99
--- /dev/null
+++ b/supvisors/test/use_cases/scenario_1/start.sh
@@ -0,0 +1,15 @@
+#!/bin/bash
+export CUR_DATE=`date +'%y%m%d'`
+export CUR_TIME=`date +'%H%M%S'`
+
+test_dir=$(dirname "$(readlink -f "$0")")
+
+# start supervisor on remote machines
+for i in cliche81 cliche82 cliche83
+do
+	echo "start Supvisors on" $i
+	ssh $i "export DISPLAY=:0 ; cd $test_dir ; rm -f log/* etc/supervisord.conf ; ln -s supervisord_distributed.conf etc/supervisord.conf ; export CUR_DATE=$CUR_DATE ; export CUR_TIME=$CUR_TIME ; supervisord"
+done
+
+firefox http://localhost:61000 &
+
diff --git a/supvisors/test/use_cases/scenario_1/start_local.sh b/supvisors/test/use_cases/scenario_1/start_local.sh
new file mode 100755
index 00000000..0bc5a05c
--- /dev/null
+++ b/supvisors/test/use_cases/scenario_1/start_local.sh
@@ -0,0 +1,14 @@
+#!/bin/bash
+export CUR_DATE=`date +'%y%m%d'`
+export CUR_TIME=`date +'%H%M%S'`
+
+test_dir=$(dirname "$(readlink -f "$0")")
+
+echo "start Supvisors on" `hostname`
+cd $test_dir
+rm -f log/* etc/supervisord.conf
+ln -s supervisord_local.conf etc/supervisord.conf
+supervisord
+
+firefox http://localhost:61000 &
+
diff --git a/supvisors/tests/configurations.py b/supvisors/tests/configurations.py
index 21355690..7340ba2e 100644
--- a/supvisors/tests/configurations.py
+++ b/supvisors/tests/configurations.py
@@ -121,6 +121,7 @@
     </application>
 
     <application name="dummy_application_B">
+        <distributed>non</distributed>
         <start_sequence>1</start_sequence>
         <stop_sequence>4</stop_sequence>
         <starting_failure_strategy>STOP</starting_failure_strategy>
@@ -176,6 +177,8 @@
     </application>
 
     <application name="dummy_application_C">
+        <distributed>false</distributed>
+        <addresses>192.256.16.10,*</addresses>
         <start_sequence>20</start_sequence>
         <stop_sequence>0</stop_sequence>
         <starting_failure_strategy>ABORT</starting_failure_strategy>
@@ -275,8 +278,10 @@
     </application>
 
     <application name="dummy_application_B">
+        <distributed>false</distributed>
         <start_sequence>1</start_sequence>
         <stop_sequence>4</stop_sequence>
+        <starting_strategy>CONFIG</starting_strategy>
         <starting_failure_strategy>STOP</starting_failure_strategy>
         <running_failure_strategy>RESTART_PROCESS</running_failure_strategy>
 
@@ -314,8 +319,10 @@
     </application>
 
     <application name="dummy_application_C">
+        <distributed>true</distributed>
         <start_sequence>20</start_sequence>
         <stop_sequence>0</stop_sequence>
+        <starting_strategy>LOCAL</starting_strategy>
         <starting_failure_strategy>ABORT</starting_failure_strategy>
         <running_failure_strategy>STOP_APPLICATION</running_failure_strategy>
 
@@ -338,8 +345,11 @@
     </application>
 
     <application name="dummy_application_D">
+        <distributed>false</distributed>
+        <addresses>10.0.0.1, 10.0.0.5</addresses>
         <start_sequence>-1</start_sequence>
         <stop_sequence>100</stop_sequence>
+        <starting_strategy>LESS_LOADED</starting_strategy>
         <starting_failure_strategy>CONTINUE</starting_failure_strategy>
         <running_failure_strategy>RESTART_APPLICATION</running_failure_strategy>
 
@@ -364,6 +374,7 @@
     </application>
 
     <application name="dummy_application_E">
+        <starting_strategy>MOST_LOADED</starting_strategy>
         <program name="dummy_program_E">
             <reference>dummy_model_05</reference>
         </program>
diff --git a/supvisors/tests/conftest.py b/supvisors/tests/conftest.py
index a90890d7..d9fa76fc 100644
--- a/supvisors/tests/conftest.py
+++ b/supvisors/tests/conftest.py
@@ -37,7 +37,7 @@ def create_any_process(supvisors):
 
 def create_application(application_name, supvisors):
     """ Create an ApplicationStatus. """
-    return ApplicationStatus(application_name, ApplicationRules(), supvisors.logger)
+    return ApplicationStatus(application_name, ApplicationRules(), supvisors)
 
 
 # fixture for common global structures
diff --git a/supvisors/tests/test_address.py b/supvisors/tests/test_address.py
index 48212fc3..62147f05 100644
--- a/supvisors/tests/test_address.py
+++ b/supvisors/tests/test_address.py
@@ -153,15 +153,15 @@ def test_pid_process(filled_node):
     assert {('sample_test_1:xfontsel', 80879), ('sample_test_2:yeux_01', 80882)} == set(filled_node.pid_processes())
 
 
-def test_get_load(filled_node):
-    """ Test the get_load method. """
+def test_get_loading(filled_node):
+    """ Test the get_loading method. """
     # check the loading of the address: gives 0 by default because no rule has been loaded
-    assert filled_node.get_load() == 0
+    assert filled_node.get_loading() == 0
     # change expected_loading of any stopped process
     process = random.choice([proc for proc in filled_node.processes.values() if proc.stopped()])
     process.rules.expected_load = 50
-    assert filled_node.get_load() == 0
+    assert filled_node.get_loading() == 0
     # change expected_loading of any running process
     process = random.choice([proc for proc in filled_node.processes.values() if proc.running()])
     process.rules.expected_load = 50
-    assert filled_node.get_load() == 50
+    assert filled_node.get_loading() == 50
diff --git a/supvisors/tests/test_application.py b/supvisors/tests/test_application.py
index f2f625f8..211bd787 100644
--- a/supvisors/tests/test_application.py
+++ b/supvisors/tests/test_application.py
@@ -22,7 +22,7 @@
 
 from supvisors.application import *
 
-from .base import database_copy, any_process_info, any_stopped_process_info
+from .base import database_copy, any_process_info, any_stopped_process_info, any_process_info_by_state
 from .conftest import create_application, create_process
 
 
@@ -37,22 +37,37 @@ def test_rules_create(rules):
     """ Test the values set at construction. """
     # check application default rules
     assert not rules.managed
+    assert rules.distributed
+    assert rules.node_names == ['*']
     assert rules.start_sequence == 0
     assert rules.stop_sequence == 0
+    assert rules.starting_strategy == StartingStrategies.CONFIG
     assert rules.starting_failure_strategy == StartingFailureStrategies.ABORT
     assert rules.running_failure_strategy == RunningFailureStrategies.CONTINUE
 
 
 def test_rules_str(rules):
     """ Test the string output. """
-    assert str(rules) == 'managed=False start_sequence=0 stop_sequence=0'\
-                         ' starting_failure_strategy=ABORT running_failure_strategy=CONTINUE'
+    assert str(rules) == "managed=False distributed=True node_names=['*'] start_sequence=0 stop_sequence=0"\
+                         " starting_strategy=CONFIG starting_failure_strategy=ABORT running_failure_strategy=CONTINUE"
 
 
 def test_rules_serial(rules):
     """ Test the serialization of the ApplicationRules object. """
-    assert rules.serial() == {'managed': False, 'start_sequence': 0, 'stop_sequence': 0,
-                              'starting_failure_strategy': 'ABORT', 'running_failure_strategy': 'CONTINUE'}
+    # default is not managed so result is short
+    assert rules.serial() == {'managed': False}
+    # check managed and distributed
+    rules.managed = True
+    assert rules.serial() == {'managed': True, 'distributed': True,
+                              'start_sequence': 0, 'stop_sequence': 0,
+                              'starting_strategy': 'CONFIG', 'starting_failure_strategy': 'ABORT',
+                              'running_failure_strategy': 'CONTINUE'}
+    # finally check managed and not distributed
+    rules.distributed = False
+    assert rules.serial() == {'managed': True, 'distributed': False, 'addresses': ['*'],
+                              'start_sequence': 0, 'stop_sequence': 0,
+                              'starting_strategy': 'CONFIG', 'starting_failure_strategy': 'ABORT',
+                              'running_failure_strategy': 'CONTINUE'}
 
 
 # ApplicationStatus part
@@ -69,6 +84,7 @@ def test_application_create(supvisors):
     assert not application.stop_sequence
     # check application default rules
     assert not application.rules.managed
+    assert application.rules.distributed
     assert application.rules.start_sequence == 0
     assert application.rules.stop_sequence == 0
     assert application.rules.starting_failure_strategy == StartingFailureStrategies.ABORT
@@ -108,6 +124,28 @@ def test_application_never_started(supvisors):
     assert not application.never_started()
 
 
+def test_application_has_running_processes(supvisors):
+    """ Test the ApplicationStatus.has_running_processes method used to know if at least one process is RUNNING. """
+    application = create_application('ApplicationTest', supvisors)
+    assert not application.has_running_processes()
+    # add a stopped process
+    info = any_stopped_process_info()
+    process = create_process(info, supvisors)
+    process.add_info('10.0.0.1', info)
+    application.add_process(process)
+    application.update_status()
+    assert application.stopped()
+    assert not application.has_running_processes()
+    # add a running process
+    info = any_process_info_by_state(ProcessStates.RUNNING)
+    process = create_process(info, supvisors)
+    process.add_info('10.0.0.1', info)
+    application.add_process(process)
+    application.update_status()
+    assert application.stopped()
+    assert application.has_running_processes()
+
+
 def test_application_get_operational_status(supvisors):
     """ Test the ApplicationStatus.get_operational_status method used to get a descriptive operational status. """
     # create address status instance
@@ -173,6 +211,42 @@ def test_application_add_process(supvisors):
     assert process is application.processes[process.process_name]
 
 
+def test_application_possible_nodes(supvisors):
+    """ Test the ApplicationStatus.possible_nodes method. """
+    application = create_application('ApplicationTest', supvisors)
+    # add a process to the application
+    info = any_process_info_by_state(ProcessStates.STARTING)
+    process1 = create_process(info, supvisors)
+    for node_name in ['10.0.0.2', '10.0.0.3', '10.0.0.4']:
+        process1.add_info(node_name, info)
+    application.add_process(process1)
+    # add another process to the application
+    info = any_stopped_process_info()
+    process2 = create_process(info, supvisors)
+    for node_name in ['10.0.0.1', '10.0.0.4']:
+        process2.add_info(node_name, info)
+    application.add_process(process2)
+    # default node_names is '*' in process rules
+    assert application.possible_nodes() == ['10.0.0.4']
+    # set a subset of node_names in process rules so that there's no intersection with received status
+    application.rules.node_names = ['10.0.0.1', '10.0.0.2']
+    assert application.possible_nodes() == []
+    # increase received status
+    process1.add_info('10.0.0.1', info)
+    assert application.possible_nodes() == ['10.0.0.1']
+    # reset rules
+    application.rules.node_names = ['*']
+    assert application.possible_nodes() == ['10.0.0.1', '10.0.0.4']
+    # test with full status and all nodes in rules
+    for node_name in supvisors.address_mapper.node_names:
+        process1.add_info(node_name, info)
+        process2.add_info(node_name, info)
+    assert application.possible_nodes() == supvisors.address_mapper.node_names
+    # restrict again nodes in rules
+    application.rules.node_names = ['10.0.0.5']
+    assert application.possible_nodes() == ['10.0.0.5']
+
+
 @pytest.fixture
 def filled_application(supvisors):
     """ Create an ApplicationStatus and add all processes of the database. """
@@ -193,9 +267,10 @@ def test_application_update_sequences(filled_application):
     filled_application.update_sequences()
     # check the sequencing of the starting
     sequences = sorted({process.rules.start_sequence for process in filled_application.processes.values()})
-    # by default applications are unmanaged so sequences are empty
+    # by default, applications are unmanaged so start sequence is empty
     assert not filled_application.start_sequence
-    assert not filled_application.stop_sequence
+    assert filled_application.stop_sequence
+    # stop sequence contents is tested afterwards
     # force application to managed and call sequencer again
     filled_application.rules.managed = True
     filled_application.update_sequences()
@@ -217,6 +292,25 @@ def test_application_update_sequences(filled_application):
                        if sequence == proc.rules.stop_sequence], key=lambda x: x.process_name)
 
 
+def test_application_get_start_sequence_expected_load(filled_application):
+    """ Test the ApplicationStatus.get_start_sequence_expected_load method. """
+    # as sequences are empty, total is 0
+    assert filled_application.get_start_sequence_expected_load() == 0
+    # set application to managed, update sequences and status
+    filled_application.rules.managed = True
+    filled_application.update_sequences()
+    # process rules still have a expected_load set to 0
+    assert filled_application.get_start_sequence_expected_load() == 0
+    print(ApplicationStatus.printable_sequence(filled_application.start_sequence))
+    # update all process loads to 10
+    for proc_list in filled_application.start_sequence.values():
+        for process in proc_list:
+            process.rules.expected_load = 10
+    seq_0_size = len(filled_application.start_sequence.get(0, []))
+    seq_1_2_size = len(filled_application.processes) - seq_0_size
+    assert filled_application.get_start_sequence_expected_load() == 10 * seq_1_2_size
+
+
 def test_application_update_status(filled_application):
     """ Test the rules to update the status of the application method. """
     # as application is not managed, application is STOPPED there are no failures
diff --git a/supvisors/tests/test_commander.py b/supvisors/tests/test_commander.py
index 7882e3cf..c10e78e4 100644
--- a/supvisors/tests/test_commander.py
+++ b/supvisors/tests/test_commander.py
@@ -55,8 +55,8 @@ def test_str():
     command.request_time = 4321
     command.ignore_wait_exit = True
     command.extra_args = '-s test args'
-    assert str(command) == 'process=proc_1 state=RUNNING last_event_time=1234 strategy=0 request_time=4321'\
-                           ' ignore_wait_exit=True extra_args="-s test args"'
+    assert str(command) == 'process=proc_1 state=RUNNING last_event_time=1234 requested_node=None request_time=4321'\
+                           ' strategy=0 distributed=True ignore_wait_exit=True extra_args="-s test args"'
 
 
 def test_timed_out():
@@ -274,21 +274,21 @@ def test_commander_process_job(commander):
         commander.process_job(None, None)
 
 
-def test_commander_check_progress(mocker, commander, command_list):
+def test_commander_is_job_completed(mocker, commander, command_list):
     """ Test the Commander.check_progress method. """
     mocked_trigger = mocker.patch('supvisors.commander.Commander.trigger_jobs')
     mocked_after = mocker.patch('supvisors.commander.Commander.after_event')
     mocked_force = commander.supvisors.listener.force_process_state = Mock()
     # test with no sequence in progress
-    assert commander.check_progress('stopped', ProcessStates.FATAL)
+    assert commander.is_job_completed('stopped', ProcessStates.FATAL)
     # test with no current jobs but planned sequence and no planned jobs
     commander.planned_sequence = {3: {'else': {}}}
-    assert not commander.check_progress('stopped', ProcessStates.FATAL)
+    assert not commander.is_job_completed('stopped', ProcessStates.FATAL)
     assert mocked_trigger.call_args_list == [call()]
     mocked_trigger.reset_mock()
     # test with no current jobs but planned sequence and planned jobs (unexpected case)
     commander.planned_jobs = {'if': {2: []}}
-    assert not commander.check_progress('stopped', ProcessStates.FATAL)
+    assert not commander.is_job_completed('stopped', ProcessStates.FATAL)
     assert not mocked_trigger.called
     # set test current_jobs
     # xfontsel is RUNNING, xlogo is STOPPED, yeux_00 is EXITED, yeux_01 is RUNNING
@@ -301,7 +301,7 @@ def test_commander_check_progress(mocker, commander, command_list):
         for command in command_list:
             command.request_time = time.time()
     # stopped processes have a recent request time: nothing done
-    completed = commander.check_progress('stopped', ProcessStates.FATAL)
+    completed = commander.is_job_completed('stopped', ProcessStates.FATAL)
     assert not completed
     assert commander.printable_current_jobs() == {'sample_test_1': ['sample_test_1:xfontsel', 'sample_test_1:xlogo'],
                                                   'sample_test_2': ['sample_test_2:yeux_00', 'sample_test_2:yeux_01']}
@@ -314,7 +314,7 @@ def test_commander_check_progress(mocker, commander, command_list):
             command.process.last_event_time = 0
             command.request_time = 0
     # stopped processes have an old request time: actions taken on state and sequence
-    completed = commander.check_progress('stopped', ProcessStates.FATAL)
+    completed = commander.is_job_completed('stopped', ProcessStates.FATAL)
     assert not completed
     assert commander.printable_current_jobs() == {'sample_test_1': ['sample_test_1:xfontsel'],
                                                   'sample_test_2': ['sample_test_2:yeux_01']}
@@ -330,7 +330,7 @@ def test_commander_check_progress(mocker, commander, command_list):
         for command in command_list:
             command.request_time = time.time()
     # stopped processes have a recent request time: nothing done
-    completed = commander.check_progress('running', ProcessStates.UNKNOWN)
+    completed = commander.is_job_completed('running', ProcessStates.UNKNOWN)
     assert not completed
     assert commander.printable_current_jobs() == {'sample_test_1': ['sample_test_1:xfontsel'],
                                                   'sample_test_2': ['sample_test_2:yeux_01']}
@@ -343,7 +343,7 @@ def test_commander_check_progress(mocker, commander, command_list):
             command.process.last_event_time = 0
             command.request_time = 0
     # stopped processes have an old request time: actions taken on state and sequence
-    completed = commander.check_progress('running', ProcessStates.UNKNOWN)
+    completed = commander.is_job_completed('running', ProcessStates.UNKNOWN)
     assert not completed
     assert commander.printable_current_jobs() == {'sample_test_1': [], 'sample_test_2': []}
     reason = 'Still running 10 seconds after request'
@@ -422,15 +422,17 @@ def test_starter_store_application_start_sequence(starter, command_list):
     """ Test the Starter.store_application_start_sequence method. """
     # create 2 application start_sequences
     appli1 = create_application('sample_test_1', starter.supvisors)
+    appli1.rules.starting_strategy = StartingStrategies.LESS_LOADED
     for command in command_list:
         if command.process.application_name == 'sample_test_1':
             appli1.start_sequence.setdefault(len(command.process.namespec) % 3, []).append(command.process)
     appli2 = create_application('sample_test_2', starter.supvisors)
+    appli2.rules.starting_strategy = StartingStrategies.MOST_LOADED
     for command in command_list:
         if command.process.application_name == 'sample_test_2':
             appli2.start_sequence.setdefault(len(command.process.namespec) % 3, []).append(command.process)
     # call method and check result
-    starter.store_application_start_sequence(appli1, StartingStrategies.LESS_LOADED)
+    starter.store_application_start_sequence(appli1)
     # check that application sequence 0 is not in starter planned sequence
     expected = {0: {'sample_test_1': {1: ['sample_test_1:xfontsel', 'sample_test_1:xlogo'],
                                       2: ['sample_test_1:xclock']}}}
@@ -503,10 +505,10 @@ def test_starter_process_failure_required(starter):
     assert starter.application_stop_requests == ['appli_1']
 
 
-def test_starter_check_starting(mocker, starter):
+def test_starter_is_starting_completed(mocker, starter):
     """ Test the Starter.check_starting method. """
-    mocked_check = mocker.patch('supvisors.commander.Commander.check_progress', return_value=True)
-    assert starter.check_starting()
+    mocked_check = mocker.patch('supvisors.commander.Commander.is_job_completed', return_value=True)
+    assert starter.is_starting_completed()
     assert mocked_check.call_args_list == [call('stopped', ProcessStates.FATAL)]
 
 
@@ -547,7 +549,7 @@ def test_starter_on_event_in_sequence(mocker, starter, command_list):
     # set context for current_jobs
     for command in command_list:
         starter.current_jobs.setdefault(command.process.application_name, []).append(command)
-    # add application context
+    # add applications to context
     application = create_application('sample_test_1', starter.supvisors)
     application.rules.starting_failure_strategy = StartingFailureStrategies.CONTINUE
     starter.supvisors.context.applications['sample_test_1'] = application
@@ -678,12 +680,53 @@ def test_starter_on_event_out_of_sequence(mocker, starter, command_list):
     assert not mocked_failure.called
 
 
+def test_starter_prepare_application_jobs(mocker, starter, command_list):
+    """ Test the Starter.prepare_application_jobs method. """
+    mocked_node_getter = mocker.patch('supvisors.commander.get_node', return_value='10.0.0.1')
+    mocked_app_nodes = mocker.patch('supvisors.application.ApplicationStatus.possible_nodes',
+                                    return_value=['10.0.0.1', '10.0.0.2'])
+    mocked_app_load = mocker.patch('supvisors.application.ApplicationStatus.get_start_sequence_expected_load',
+                                   return_value=27)
+    mocker.patch.object(starter, 'get_load_requests', return_value={'10.0.0.1': 28})
+    # check initial context
+    starter.planned_jobs = {'sample_test_1': {1: [get_test_command(command_list, 'xfontsel')],
+                                              2: [get_test_command(command_list, 'xlogo')]}}
+    starter.current_jobs = {}
+    assert all(command.distributed and not command.node_name
+               for sequence in starter.planned_jobs['sample_test_1'].values()
+               for command in sequence)
+    # add applications to context
+    application = create_application('sample_test_1', starter.supvisors)
+    application.rules.starting_strategy = StartingStrategies.MOST_LOADED
+    starter.supvisors.context.applications['sample_test_1'] = application
+    # test application not provided / application distributed
+    starter.prepare_application_jobs('sample_test_1')
+    assert not mocked_node_getter.called
+    assert not mocked_app_nodes.called
+    assert not mocked_app_load.called
+    # commands unchanged
+    assert all(command.distributed and not command.node_name
+               for sequence in starter.planned_jobs['sample_test_1'].values()
+               for command in sequence)
+    # test application provided / application not distributed
+    application.rules.distributed = False
+    starter.prepare_application_jobs('sample_test_1')
+    assert mocked_node_getter.call_args_list == [call(starter.supvisors, StartingStrategies.MOST_LOADED,
+                                                      ['10.0.0.1', '10.0.0.2'], 27, {'10.0.0.1': 28})]
+    # check commands
+    assert all(not command.distributed and command.node_name == '10.0.0.1'
+               for sequence in starter.planned_jobs['sample_test_1'].values()
+               for command in sequence)
+
+
 def test_starter_process_job(mocker, starter, command_list):
     """ Test the Starter.process_job method. """
     # get patches
     mocked_node_getter = mocker.patch('supvisors.commander.get_node')
     mocked_force = starter.supvisors.listener.force_process_state
     mocked_pusher = starter.supvisors.zmq.pusher.send_start_process
+    mocker.patch.object(starter, 'get_load_requests', return_value={'10.0.0.1': 28})
+    mocked_failure = mocker.patch.object(starter, 'process_failure')
     # test with a possible starting address
     mocked_node_getter.return_value = '10.0.0.1'
     # 1. test with running process
@@ -696,21 +739,25 @@ def test_starter_process_job(mocker, starter, command_list):
     assert jobs == []
     assert not mocked_node_getter.called
     assert not mocked_pusher.called
-    # failure method is not called
+    # failure methods are not called
     assert not mocked_force.called
+    assert not mocked_failure.called
     # 2.a test with stopped process
     command = get_test_command(command_list, 'xlogo')
+    command.strategy = StartingStrategies.MOST_LOADED
     command.ignore_wait_exit = True
     jobs = []
     # call the process_jobs
     starter.process_job(command, jobs)
     # starting methods are called
     assert jobs == [command]
-    assert mocked_node_getter.call_args_list == [call(starter.supvisors, None, ['10.0.0.1'], 0)]
+    assert mocked_node_getter.call_args_list == [call(starter.supvisors, StartingStrategies.MOST_LOADED,
+                                                      ['10.0.0.1'], 0,{'10.0.0.1': 28})]
     assert mocked_pusher.call_args_list == [call('10.0.0.1', 'sample_test_1:xlogo', '')]
     mocked_pusher.reset_mock()
-    # failure method is not called
+    # failure methods are not called
     assert not mocked_force.called
+    assert not mocked_failure.called
     # 3. test with no starting address
     mocked_node_getter.return_value = None
     # test with stopped process
@@ -720,10 +767,11 @@ def test_starter_process_job(mocker, starter, command_list):
     # call the process_jobs
     starter.process_job(command, jobs)
     # starting methods are not called but job is in list though
-    assert jobs == [command]
+    assert jobs == []
     assert not mocked_pusher.called
-    # failure method is called
+    # failure methods are called
     assert mocked_force.call_args_list == [call('sample_test_1:xlogo', ProcessStates.FATAL, 'no resource available')]
+    assert mocked_failure.call_args_list == [call(command.process)]
 
 
 def test_starter_start_process_failure(mocker, starter, command_list):
@@ -773,9 +821,12 @@ def test_starter_default_start_process(mocker, starter):
     """ Test the Starter.default_start_process method. """
     mocked_start = mocker.patch.object(starter, 'start_process', return_value=True)
     # test that default_start_process just calls start_process with the default strategy
-    process = Mock()
+    dummy_application = create_application('dummy_application', starter.supvisors)
+    dummy_application.rules.starting_strategy = StartingStrategies.LOCAL
+    starter.supvisors.context.applications['dummy_application'] = dummy_application
+    process = Mock(application_name='dummy_application')
     assert starter.default_start_process(process)
-    assert mocked_start.call_args_list == [call(starter.supvisors.options.starting_strategy, process)]
+    assert mocked_start.call_args_list == [call(StartingStrategies.LOCAL, process)]
 
 
 def test_starter_start_application(mocker, starter, command_list):
@@ -812,15 +863,30 @@ def test_starter_default_start_application(mocker, starter):
     """ Test the Starter.default_start_application method. """
     mocked_start = mocker.patch.object(starter, 'start_application', return_value=True)
     # test that default_start_application just calls start_application with the default strategy
-    application = Mock()
+    application = Mock(rules=Mock(starting_strategy=StartingStrategies.MOST_LOADED))
     assert starter.default_start_application(application)
-    assert mocked_start.call_args_list == [call(starter.supvisors.options.starting_strategy, application)]
+    assert mocked_start.call_args_list == [call(StartingStrategies.MOST_LOADED, application)]
 
 
 def test_starter_start_applications(mocker, starter, command_list):
     """ Test the Starter.start_applications method. """
     mocked_store = mocker.patch.object(starter, 'store_application_start_sequence')
     mocked_trigger = mocker.patch.object(starter, 'trigger_jobs')
+    # create one stopped application with a start_sequence == 0
+    sample_test_3 = create_application('sample_test_3', starter.supvisors)
+    sample_test_3.rules.start_sequence = 0
+    starter.supvisors.context.applications['crash'] = sample_test_3
+    # call starter start_applications and check nothing is triggered
+    starter.start_applications()
+    assert not mocked_store.called
+    assert mocked_trigger.call_args_list == [call()]
+    mocked_trigger.reset_mock()
+    # test again with failure set
+    sample_test_3.major_failure = True
+    starter.start_applications()
+    assert not mocked_store.called
+    assert mocked_trigger.call_args_list == [call()]
+    mocked_trigger.reset_mock()
     # create one running application
     sample_test_1 = create_application('sample_test_1', starter.supvisors)
     sample_test_1.rules.start_sequence = 1
@@ -861,11 +927,10 @@ def test_starter_start_applications(mocker, starter, command_list):
     sample_test_3 = create_application('sample_test_3', starter.supvisors)
     sample_test_3.rules.start_sequence = 0
     starter.supvisors.context.applications['crash'] = sample_test_3
-    # call starter start_applications and check what is triggered (1 start / 3 repair)
+    # call starter start_applications and check what is triggered
     starter.start_applications()
-    mocked_store.assert_has_calls([call(sample_test_2, StartingStrategies.CONFIG),
-                                   call(sample_test_major, StartingStrategies.CONFIG),
-                                   call(sample_test_minor, StartingStrategies.CONFIG)], any_order=True)
+    mocked_store.assert_has_calls([call(sample_test_2,), call(sample_test_major), call(sample_test_minor)],
+                                  any_order=True)
     assert mocked_trigger.call_args_list == [call()]
 
 
@@ -894,6 +959,19 @@ def test_starter_after_jobs(mocker, starter):
     assert mocked_stop.call_args_list == [call(appli_2)]
 
 
+def test_starter_get_load_requests(starter, command_list):
+    """ Test the Starter.get_load_requests method. """
+    # test with empty current_jobs
+    assert starter.get_load_requests() == {}
+    # fill jobs
+    for idx, command in enumerate(command_list):
+        command.node_name = '10.0.0.1' if idx % 2 else '10.0.0.2'
+        command.process.rules.expected_load = 10
+        starter.current_jobs.setdefault(command.process.application_name, []).append(command)
+    # 4 stopped processes in context
+    assert starter.get_load_requests() == {'10.0.0.2': 20, '10.0.0.1': 20}
+
+
 # Stopper part
 @pytest.fixture
 def stopper(supvisors):
@@ -908,8 +986,8 @@ def test_stopper_create(stopper):
 
 def test_stopper_check_stopping(mocker, stopper):
     """ Test the Stopper.check_stopping method. """
-    mocked_check = mocker.patch('supvisors.commander.Commander.check_progress', return_value=True)
-    assert stopper.check_stopping()
+    mocked_check = mocker.patch('supvisors.commander.Commander.is_job_completed', return_value=True)
+    assert stopper.is_stopping_completed()
     assert mocked_check.call_args_list == [call('running', ProcessStates.STOPPED)]
 
 
@@ -1082,14 +1160,14 @@ def success_job(*args, **_):
 
     mocked_jobs = mocker.patch.object(stopper, 'process_job', side_effect=success_job)
     # test start_application on a stopped application
-    appli._state = ApplicationStates.STOPPED
+    assert appli.state == ApplicationStates.STOPPED
     assert stopper.stop_application(appli)
     assert stopper.planned_sequence == {}
     assert stopper.planned_jobs == {}
     assert stopper.current_jobs == {}
     assert not mocked_jobs.called
-    # test start_application on a stopped application
-    appli._state = ApplicationStates.RUNNING
+    # test start_application on an application having at least one running process
+    mocker.patch.object(appli, 'has_running_processes', return_value=True)
     assert not stopper.stop_application(appli)
     # only planned jobs and not current jobs because of process_application_jobs patch
     assert stopper.planned_sequence == {}
@@ -1102,7 +1180,7 @@ def test_stopper_stop_applications(mocker, stopper, command_list):
     """ Test the Stopper.stop_applications method. """
     # create one running application with a start_sequence > 0
     appli = create_application('sample_test_1', stopper.supvisors)
-    appli._state = ApplicationStates.RUNNING
+    mocker.patch.object(appli, 'has_running_processes', return_value=True)
     appli.rules.stop_sequence = 2
     stopper.supvisors.context.applications['sample_test_1'] = appli
     for command in command_list:
@@ -1113,7 +1191,7 @@ def test_stopper_stop_applications(mocker, stopper, command_list):
     stopper.supvisors.context.applications['sample_test_2'] = appli
     # create one running application with a start_sequence == 0
     appli = create_application('crash', stopper.supvisors)
-    appli._state = ApplicationStates.RUNNING
+    mocker.patch.object(appli, 'has_running_processes', return_value=True)
     appli.rules.stop_sequence = 0
     stopper.supvisors.context.applications['crash'] = appli
     for command in command_list:
diff --git a/supvisors/tests/test_context.py b/supvisors/tests/test_context.py
index 9d41c664..db5987ca 100644
--- a/supvisors/tests/test_context.py
+++ b/supvisors/tests/test_context.py
@@ -26,6 +26,7 @@
 from supvisors.ttypes import AddressStates, ApplicationStates, InvalidTransition
 
 from .base import DummyAddressMapper, database_copy, any_process_info
+from .conftest import create_application
 
 
 @pytest.fixture
@@ -62,7 +63,36 @@ def test_create(supvisors, context):
     assert context.applications == {}
     assert context._master_node_name == ''
     assert not context._is_master
-    assert not context.master_operational
+    assert context.start_date == 0
+
+
+def test_reset(mocker, context):
+    """ Test the reset of Context values. """
+    mocker.patch('supvisors.context.time', return_value=3600)
+    # change master definition
+    context.master_node_name = '127.0.0.1'
+    assert context.is_master
+    # change node states
+    context.nodes['127.0.0.1']._state = AddressStates.RUNNING
+    context.nodes['10.0.0.1']._state = AddressStates.SILENT
+    context.nodes['10.0.0.2']._state = AddressStates.ISOLATING
+    context.nodes['10.0.0.3']._state = AddressStates.ISOLATED
+    context.nodes['10.0.0.4']._state = AddressStates.RUNNING
+    # add an application
+    application = create_application('dummy_appli', context.supvisors)
+    context.applications['dummy_appli'] = application
+    # call reset and check result
+    context.reset()
+    assert set(DummyAddressMapper().node_names), set(context.nodes.keys())
+    context.nodes['127.0.0.1']._state = AddressStates.UNKNOWN
+    context.nodes['10.0.0.1']._state = AddressStates.SILENT
+    context.nodes['10.0.0.2']._state = AddressStates.ISOLATING
+    context.nodes['10.0.0.3']._state = AddressStates.ISOLATED
+    context.nodes['10.0.0.4']._state = AddressStates.UNKNOWN
+    assert context.applications == {'dummy_appli': application}
+    assert context._master_node_name == ''
+    assert not context._is_master
+    assert context.start_date == 3600
 
 
 def test_master_node_name(context):
@@ -71,28 +101,23 @@ def test_master_node_name(context):
     assert context.master_node_name == ''
     assert not context.is_master
     assert not context._is_master
-    assert not context.master_operational
-    context.master_operational = True
     context.master_node_name = '10.0.0.1'
     assert context.master_node_name == '10.0.0.1'
     assert context._master_node_name == '10.0.0.1'
     assert not context.is_master
     assert not context._is_master
-    assert not context.master_operational
-    context.master_operational = True
     context.master_node_name = '127.0.0.1'
     assert context.master_node_name == '127.0.0.1'
     assert context._master_node_name == '127.0.0.1'
     assert context.is_master
     assert context._is_master
-    assert not context.master_operational
 
 
 def test_nodes_by_states(context):
     """ Test the access to addresses in unknown state. """
     # test initial states
     assert context.unknown_nodes() == DummyAddressMapper().node_names
-    assert context.unknown_forced_nodes() == []
+    assert context.running_core_nodes() is None
     assert context.running_nodes() == []
     assert context.isolating_nodes() == []
     assert context.isolation_nodes() == []
@@ -107,7 +132,7 @@ def test_nodes_by_states(context):
     context.nodes['10.0.0.4']._state = AddressStates.RUNNING
     # test new states
     assert context.unknown_nodes() == ['10.0.0.2', '10.0.0.5']
-    assert context.unknown_forced_nodes() == []
+    assert context.running_core_nodes() is None
     assert context.running_nodes() == ['127.0.0.1', '10.0.0.4']
     assert context.isolating_nodes() == ['10.0.0.2']
     assert context.isolation_nodes() == ['10.0.0.2', '10.0.0.3']
@@ -117,13 +142,13 @@ def test_nodes_by_states(context):
     assert context.nodes_by_states([AddressStates.UNKNOWN]) == ['10.0.0.5']
 
 
-def test_unknown_forced_nodes(supvisors):
-    """ Test the access to addresses in unknown state. """
+def test_running_core_nodes(supvisors):
+    """ Test if the core nodes are in a RUNNING state. """
     supvisors.options.force_synchro_if = ['10.0.0.1', '10.0.0.4']
     context = Context(supvisors)
     # test initial states
     assert context.unknown_nodes() == DummyAddressMapper().node_names
-    assert context.unknown_forced_nodes() == ['10.0.0.1', '10.0.0.4']
+    assert not context.running_core_nodes()
     # change states
     context.nodes['127.0.0.1']._state = AddressStates.RUNNING
     context.nodes['10.0.0.2']._state = AddressStates.ISOLATING
@@ -131,25 +156,30 @@ def test_unknown_forced_nodes(supvisors):
     context.nodes['10.0.0.4']._state = AddressStates.RUNNING
     # test new states
     assert context.unknown_nodes() == ['10.0.0.1', '10.0.0.2', '10.0.0.5']
-    assert context.unknown_forced_nodes() == ['10.0.0.1']
+    assert not context.running_core_nodes()
     # change states
     context.nodes['10.0.0.1']._state = AddressStates.SILENT
     # test new states
     assert context.unknown_nodes() == ['10.0.0.2', '10.0.0.5']
-    assert context.unknown_forced_nodes() == []
+    assert not context.running_core_nodes()
+    # change states
+    context.nodes['10.0.0.1']._state = AddressStates.RUNNING
+    # test new states
+    assert context.unknown_nodes() == ['10.0.0.2', '10.0.0.5']
+    assert context.running_core_nodes()
 
 
-def check_invalid_node_status(context, node_name, new_state):
+def check_invalid_node_status(context, node_name, new_state, fence=None):
     # get address status
-    address_status = context.nodes[node_name]
+    node = context.nodes[node_name]
     # check initial state
-    assert address_status.state == AddressStates.UNKNOWN
+    assert node.state == AddressStates.UNKNOWN
     # invalidate address
-    context.invalid(address_status)
+    context.invalid(node, fence)
     # check new state
-    assert address_status.state == new_state
+    assert node.state == new_state
     # restore address state
-    address_status._state = AddressStates.UNKNOWN
+    node._state = AddressStates.UNKNOWN
 
 
 def test_invalid(mocker, context):
@@ -158,41 +188,18 @@ def test_invalid(mocker, context):
     mocker.patch.object(context.supvisors.options, 'auto_fence', True)
     # test address state with auto_fence and local_address
     check_invalid_node_status(context, '127.0.0.1', AddressStates.SILENT)
+    check_invalid_node_status(context, '127.0.0.1', AddressStates.SILENT, True)
     # test address state with auto_fence and other than local_address
     check_invalid_node_status(context, '10.0.0.1', AddressStates.ISOLATING)
+    check_invalid_node_status(context, '10.0.0.1', AddressStates.ISOLATING, True)
     # test address state without auto_fence
     mocker.patch.object(context.supvisors.options, 'auto_fence', False)
     # test address state without auto_fence and local_address
     check_invalid_node_status(context, '127.0.0.1', AddressStates.SILENT)
+    check_invalid_node_status(context, '127.0.0.1', AddressStates.SILENT, True)
     # test address state without auto_fence and other than local_address
     check_invalid_node_status(context, '10.0.0.2', AddressStates.SILENT)
-
-
-def test_end_synchro(mocker, context):
-    """ Test the end of synchronization phase. """
-    # choose two addresses and change their state
-    for address_status in context.nodes.values():
-        assert address_status.state == AddressStates.UNKNOWN
-    context.nodes['10.0.0.2']._state = AddressStates.RUNNING
-    context.nodes['10.0.0.4']._state = AddressStates.ISOLATED
-    # call end of synchro with auto_fence activated
-    context.end_synchro()
-    # check that UNKNOWN addresses became ISOLATING, but local address
-    assert context.nodes['127.0.0.1'].state == AddressStates.SILENT
-    assert context.nodes['10.0.0.1'].state == AddressStates.ISOLATING
-    assert context.nodes['10.0.0.3'].state == AddressStates.ISOLATING
-    assert context.nodes['10.0.0.5'].state == AddressStates.ISOLATING
-    # reset states and set (local excepted)
-    context.nodes['10.0.0.1']._state = AddressStates.UNKNOWN
-    context.nodes['10.0.0.3']._state = AddressStates.UNKNOWN
-    context.nodes['10.0.0.5']._state = AddressStates.UNKNOWN
-    # call end of synchro with auto_fencing deactivated
-    mocker.patch.object(context.supvisors.options, 'auto_fence', False)
-    context.end_synchro()
-    # check that UNKNOWN addresses became SILENT
-    assert context.nodes['10.0.0.1'].state == AddressStates.SILENT
-    assert context.nodes['10.0.0.3'].state == AddressStates.SILENT
-    assert context.nodes['10.0.0.5'].state == AddressStates.SILENT
+    check_invalid_node_status(context, '10.0.0.2', AddressStates.ISOLATING, True)
 
 
 def test_get_managed_applications(filled_context):
@@ -324,8 +331,10 @@ def test_setdefault_process(context):
     assert application2.rules.managed
 
 
-def test_load_processes(context):
+def test_load_processes(mocker, context):
     """ Test the storage of processes handled by Supervisor on a given address. """
+    mocker.patch('supvisors.application.ApplicationStatus.update_sequences')
+    mocker.patch('supvisors.application.ApplicationStatus.update_status')
     # check application list
     assert context.applications == {}
     for node in context.nodes.values():
@@ -349,6 +358,10 @@ def test_load_processes(context):
                                                                   'sample_test_1:xlogo', 'sample_test_2:sleep',
                                                                   'sample_test_2:yeux_00', 'sample_test_2:yeux_01']
     assert context.nodes['10.0.0.2'].processes == {}
+    # check application calls
+    assert all(application.update_sequences.called and application.update_status.called
+               for application in context.applications.values())
+    mocker.resetall()
     # load ProcessInfoDatabase in other known address
     context.load_processes('10.0.0.2', database_copy())
     # check context contents
@@ -362,6 +375,10 @@ def test_load_processes(context):
                                                                   'sample_test_1:xlogo', 'sample_test_2:sleep',
                                                                   'sample_test_2:yeux_00', 'sample_test_2:yeux_01']
     assert context.nodes['10.0.0.1'].processes == context.nodes['10.0.0.2'].processes
+    # check application calls
+    assert all(application.update_sequences.called and application.update_status.called
+               for application in context.applications.values())
+    mocker.resetall()
     # load different database in other known address
     info = any_process_info()
     info.update({'group': 'dummy_application', 'name': 'dummy_process'})
@@ -376,6 +393,9 @@ def test_load_processes(context):
     assert sorted(context.applications['firefox'].processes.keys()) == ['firefox']
     assert sorted(context.applications['sample_test_1'].processes.keys()) == ['xclock', 'xfontsel', 'xlogo']
     assert sorted(context.applications['sample_test_2'].processes.keys()) == ['sleep', 'yeux_00', 'yeux_01']
+    # check application calls
+    assert all(application.update_sequences.called and application.update_status.called
+               for application in context.applications.values())
 
 
 def test_authorization(mocker, context):
@@ -409,12 +429,12 @@ def test_authorization(mocker, context):
     with pytest.raises(InvalidTransition):
         context.on_authorization('10.0.0.4', True)
     assert context.nodes['10.0.0.4'].state == AddressStates.SILENT
-    # check state becomes SILENT if not authorized and auto fencing deactivated
+    # check state becomes ISOLATED reciprocally if not authorized and even if auto fencing deactivated
     mocker.patch.object(context.supvisors.options, 'auto_fence', False)
     for state in [AddressStates.UNKNOWN, AddressStates.CHECKING, AddressStates.SILENT, AddressStates.RUNNING]:
         context.nodes['10.0.0.5']._state = state
         context.on_authorization('10.0.0.5', False)
-        assert context.nodes['10.0.0.5'].state == AddressStates.SILENT
+        assert context.nodes['10.0.0.5'].state == AddressStates.ISOLATING
 
 
 def test_tick_event(mocker, context):
@@ -426,7 +446,15 @@ def test_tick_event(mocker, context):
     context.on_tick_event('10.0.0.0', {})
     assert not mocked_check.called
     assert not mocked_send.called
-    # get address status used for tests
+    # check no processing as long as local node is not RUNNING
+    assert context.supvisors.address_mapper.local_node_name == '127.0.0.1'
+    assert context.nodes['127.0.0.1'].state == AddressStates.UNKNOWN
+    context.on_tick_event('10.0.0.1', {})
+    assert not mocked_check.called
+    assert not mocked_send.called
+    # set local node state to RUNNING
+    context.nodes['127.0.0.1']._state = AddressStates.RUNNING
+    # get node status used for tests
     address = context.nodes['10.0.0.1']
     # check no change with known address in isolation
     for state in [AddressStates.ISOLATING, AddressStates.ISOLATED]:
@@ -435,8 +463,7 @@ def test_tick_event(mocker, context):
         assert address.state == state
         assert not mocked_check.called
         assert not mocked_send.called
-    # check that address is CHECKING and check_address is called
-    # before address time is updated and address status is sent
+    # check that node is CHECKING and send_check_node is called before node time is updated and address status is sent
     for state in [AddressStates.UNKNOWN, AddressStates.SILENT]:
         address._state = state
         context.on_tick_event('10.0.0.1', {'when': 1234})
@@ -448,7 +475,7 @@ def test_tick_event(mocker, context):
                                                     'remote_time': 1234, 'local_time': 3600, 'loading': 0})]
         mocked_check.reset_mock()
         mocked_send.reset_mock()
-    # check that address time is updated and address status is sent
+    # check that node time is updated and address status is sent
     mocked_check.reset_mock()
     mocked_send.reset_mock()
     for state in [AddressStates.CHECKING, AddressStates.RUNNING]:
@@ -600,7 +627,7 @@ def test_process_event(mocker, context):
                   'major_failure': False, 'minor_failure': True})]
 
 
-def test_timer_event(mocker, context):
+def test_on_timer_event(mocker, context):
     """ Test the handling of a timer event. """
     mocker.patch('supvisors.context.time', return_value=3600)
     mocked_send = context.supvisors.zmq.publisher.send_address_status
@@ -610,20 +637,31 @@ def test_timer_event(mocker, context):
     context.nodes['10.0.0.2'].__dict__.update({'_state': AddressStates.RUNNING, 'local_time': 3588})
     context.nodes['10.0.0.3'].__dict__.update({'_state': AddressStates.SILENT, 'local_time': 1800})
     context.nodes['10.0.0.4'].__dict__.update({'_state': AddressStates.ISOLATING, 'local_time': 0})
-    context.nodes['10.0.0.5'].__dict__.update({'_state': AddressStates.ISOLATED, 'local_time': 0})
+    context.nodes['10.0.0.5'].__dict__.update({'_state': AddressStates.UNKNOWN, 'local_time': 0})
     # patch the expected future invalidated node
     proc_1 = Mock(rules=Mock(expected_load=3), **{'invalidate_node.return_value': False})
     proc_2 = Mock(rules=Mock(expected_load=12), **{'invalidate_node.return_value': True})
     mocker.patch.object(context.nodes['10.0.0.2'], 'running_processes', return_value=[proc_1, proc_2])
+    # test when start_date is more recent than synchro_timeout
+    context.start_date = 3590
+    assert context.on_timer_event() == set()
+    assert not mocked_send.called
+    assert not proc_1.invalidate_node.called
+    assert not proc_2.invalidate_node.called
+    assert context.nodes['10.0.0.5'].state == AddressStates.UNKNOWN
     # test call and check results
+    context.start_date = 3589
     assert context.on_timer_event() == {proc_2}
+    assert context.nodes['10.0.0.5'].state == AddressStates.ISOLATING
     assert mocked_send.call_args_list == [call({'address_name': '10.0.0.2', 'statecode': 4, 'statename': 'ISOLATING',
-                                                'remote_time': 0, 'local_time': 3588, 'loading': 15})]
+                                                'remote_time': 0, 'local_time': 3588, 'loading': 15}),
+                                          call({'address_name': '10.0.0.5', 'statecode': 4, 'statename': 'ISOLATING',
+                                                'remote_time': 0, 'local_time': 0, 'loading': 0})]
     assert proc_2.invalidate_node.call_args_list == [(call('10.0.0.2'))]
-    # only '10.0.0.2' node changed state
+    # only '10.0.0.2' and '10.0.0.5' nodes changed state
     for node_name, state in [('127.0.0.1', AddressStates.RUNNING), ('10.0.0.1', AddressStates.RUNNING),
                              ('10.0.0.2', AddressStates.ISOLATING), ('10.0.0.3', AddressStates.SILENT),
-                             ('10.0.0.4', AddressStates.ISOLATING), ('10.0.0.5', AddressStates.ISOLATED)]:
+                             ('10.0.0.4', AddressStates.ISOLATING), ('10.0.0.5', AddressStates.ISOLATING)]:
         assert context.nodes[node_name].state == state
 
 
diff --git a/supvisors/tests/test_initializer.py b/supvisors/tests/test_initializer.py
index 760f0ddc..d6ff2f3b 100644
--- a/supvisors/tests/test_initializer.py
+++ b/supvisors/tests/test_initializer.py
@@ -18,10 +18,9 @@
 # ======================================================================
 
 import pytest
-import sys
 
 from supervisor.loggers import Logger
-from unittest.mock import patch, Mock
+from unittest.mock import Mock
 
 from .base import DummyAddressMapper, DummySupervisor, DummyOptions
 
@@ -106,9 +105,11 @@ def test_address_exception(mocker):
 def test_parser_exception(mocker):
     """ Test the values set at construction. """
     mocker.patch('supvisors.initializer.Parser', side_effect=Exception)
-    mocker.patch('supvisors.initializer.AddressMapper', local_address='127.0.0.1')
-    mocker.patch('supvisors.initializer.loggers')
-    mocker.patch('supvisors.initializer.SupvisorsServerOptions')
+    mocker.patch('supvisors.initializer.AddressMapper', return_value=DummyAddressMapper())
+    mocker.patch('supvisors.initializer.Supvisors.create_logger', return_value=Mock(spec=Logger))
+    mocked_supv_options = DummyOptions()
+    mocked_srv_options = Mock(supvisors_options=mocked_supv_options)
+    mocker.patch('supvisors.initializer.SupvisorsServerOptions', return_value=mocked_srv_options)
     # create Supvisors instance
     from supvisors.initializer import Supvisors
     supvisors = Supvisors(DummySupervisor())
diff --git a/supvisors/tests/test_listener.py b/supvisors/tests/test_listener.py
index 23ae9e7b..b5eedeb4 100644
--- a/supvisors/tests/test_listener.py
+++ b/supvisors/tests/test_listener.py
@@ -19,7 +19,7 @@
 
 import pytest
 
-from unittest.mock import call, patch, Mock, DEFAULT
+from unittest.mock import call, Mock, DEFAULT
 from supervisor.events import *
 
 from supvisors.listener import *
@@ -240,3 +240,10 @@ def test_force_process_state(mocker, listener):
     expected = [call({'name': 'process', 'group': 'appli', 'state': 200, 'forced': True,
                       'extra_args': '-h', 'now': 56, 'pid': 0, 'expected': False, 'spawnerr': 'bad luck'})]
     assert listener.publisher.send_process_event.call_args_list == expected
+    listener.publisher.send_process_event.reset_mock()
+    # test the call with unknown process in Supervisor
+    listener.supvisors.info_source.get_extra_args.side_effect = KeyError
+    listener.force_process_state('appli:process', 200, 'bad luck')
+    expected = [call({'name': 'process', 'group': 'appli', 'state': 200, 'forced': True,
+                      'now': 56, 'pid': 0, 'expected': False, 'spawnerr': 'bad luck'})]
+    assert listener.publisher.send_process_event.call_args_list == expected
diff --git a/supvisors/tests/test_mainloop.py b/supvisors/tests/test_mainloop.py
index 280e1b66..49af0bc4 100644
--- a/supvisors/tests/test_mainloop.py
+++ b/supvisors/tests/test_mainloop.py
@@ -140,10 +140,10 @@ def test_check_requests(mocker, main_loop):
 
 def test_check_node(mocker, mocked_rpc, main_loop):
     """ Test the protocol to get the processes handled by a remote Supervisor. """
-    mocked_stderr = mocker.patch('supvisors.mainloop.stderr')
+    mocker.patch('supvisors.mainloop.stderr')
     mocked_evt = mocker.patch('supvisors.mainloop.SupvisorsMainLoop.send_remote_comm_event')
     # test rpc error: no event is sent to local Supervisor
-    mocked_rpc.side_effect = Exception
+    mocked_rpc.side_effect = ValueError
     main_loop.check_node('10.0.0.1')
     assert mocked_rpc.call_count == 2
     assert mocked_rpc.call_args == call('10.0.0.1', main_loop.env)
@@ -189,7 +189,7 @@ def test_start_process(mocker, mocked_rpc, main_loop):
     """ Test the protocol to start a process handled by a remote Supervisor. """
     mocker.patch('supvisors.mainloop.stderr')
     # test rpc error
-    mocked_rpc.side_effect = Exception
+    mocked_rpc.side_effect = KeyError
     main_loop.start_process('10.0.0.1', 'dummy_process', 'extra args')
     assert mocked_rpc.call_count == 2
     assert mocked_rpc.call_args == call('10.0.0.1', main_loop.env)
@@ -209,7 +209,7 @@ def test_stop_process(mocker, mocked_rpc, main_loop):
     """ Test the protocol to stop a process handled by a remote Supervisor. """
     mocker.patch('supvisors.mainloop.stderr')
     # test rpc error
-    mocked_rpc.side_effect = Exception
+    mocked_rpc.side_effect = ConnectionResetError
     main_loop.stop_process('10.0.0.1', 'dummy_process')
     assert mocked_rpc.call_count == 2
     assert mocked_rpc.call_args == call('10.0.0.1', main_loop.env)
@@ -229,7 +229,7 @@ def test_restart(mocker, mocked_rpc, main_loop):
     """ Test the protocol to restart a remote Supervisor. """
     mocker.patch('supvisors.mainloop.stderr')
     # test rpc error
-    mocked_rpc.side_effect = Exception
+    mocked_rpc.side_effect = OSError
     main_loop.restart('10.0.0.1')
     assert mocked_rpc.call_count == 2
     assert mocked_rpc.call_args == call('10.0.0.1', main_loop.env)
@@ -249,7 +249,7 @@ def test_shutdown(mocker, mocked_rpc, main_loop):
     """ Test the protocol to shutdown a remote Supervisor. """
     mocker.patch('supvisors.mainloop.stderr')
     # test rpc error
-    mocked_rpc.side_effect = Exception
+    mocked_rpc.side_effect = RPCError(12)
     main_loop.shutdown('10.0.0.1')
     assert mocked_rpc.call_count == 2
     assert mocked_rpc.call_args == call('10.0.0.1', main_loop.env)
@@ -265,11 +265,51 @@ def test_shutdown(mocker, mocked_rpc, main_loop):
     assert mocked_shutdown.call_args == call()
 
 
+def test_restart_all(mocker, mocked_rpc, main_loop):
+    """ Test the protocol to restart Supvisors. """
+    mocker.patch('supvisors.mainloop.stderr')
+    # test rpc error
+    mocked_rpc.side_effect = OSError
+    main_loop.restart_all('10.0.0.1')
+    assert mocked_rpc.call_count == 2
+    assert mocked_rpc.call_args == call('10.0.0.1', main_loop.env)
+    # test with a mocked rpc interface
+    rpc_intf = DummyRpcInterface()
+    mocked_rpc.side_effect = None
+    mocked_rpc.return_value = rpc_intf
+    mocked_supervisor = mocker.patch.object(rpc_intf.supvisors, 'restart')
+    main_loop.restart_all('10.0.0.1')
+    assert mocked_rpc.call_count == 3
+    assert mocked_rpc.call_args == call('10.0.0.1', main_loop.env)
+    assert mocked_supervisor.call_count == 1
+    assert mocked_supervisor.call_args == call()
+
+
+def test_shutdown_all(mocker, mocked_rpc, main_loop):
+    """ Test the protocol to shutdown Supvisors. """
+    mocker.patch('supvisors.mainloop.stderr')
+    # test rpc error
+    mocked_rpc.side_effect = RPCError(12)
+    main_loop.shutdown_all('10.0.0.1')
+    assert mocked_rpc.call_count == 2
+    assert mocked_rpc.call_args == call('10.0.0.1', main_loop.env)
+    # test with a mocked rpc interface
+    rpc_intf = DummyRpcInterface()
+    mocked_rpc.side_effect = None
+    mocked_rpc.return_value = rpc_intf
+    mocked_shutdown = mocker.patch.object(rpc_intf.supvisors, 'shutdown')
+    main_loop.shutdown_all('10.0.0.1')
+    assert mocked_rpc.call_count == 3
+    assert mocked_rpc.call_args == call('10.0.0.1', main_loop.env)
+    assert mocked_shutdown.call_count == 1
+    assert mocked_shutdown.call_args == call()
+
+
 def test_comm_event(mocker, mocked_rpc, main_loop):
     """ Test the protocol to send a comm event to the local Supervisor. """
     mocker.patch('supvisors.mainloop.stderr')
     # test rpc error
-    mocker.patch.object(main_loop.proxy.supervisor, 'sendRemoteCommEvent', side_effect=Exception)
+    mocker.patch.object(main_loop.proxy.supervisor, 'sendRemoteCommEvent', side_effect=RPCError(100))
     main_loop.send_remote_comm_event('event type', 'event data')
     # test with a mocked rpc interface
     mocked_supervisor = mocker.patch.object(main_loop.proxy.supervisor, 'sendRemoteCommEvent')
@@ -280,7 +320,7 @@ def test_comm_event(mocker, mocked_rpc, main_loop):
 def check_call(main_loop, mocked_loop, method_name, request, args):
     """ Perform a main loop request and check what has been called. """
     # send request
-    main_loop.send_request(request, args)
+    main_loop.send_request(request.value, args)
     # test mocked main loop
     for key, mocked in mocked_loop.items():
         if key == method_name:
@@ -296,7 +336,8 @@ def test_send_request(mocker, main_loop):
     # patch main loop subscriber
     mocked_loop = mocker.patch.multiple(main_loop, check_node=DEFAULT,
                                         start_process=DEFAULT, stop_process=DEFAULT,
-                                        restart=DEFAULT, shutdown=DEFAULT)
+                                        restart=DEFAULT, shutdown=DEFAULT,
+                                        restart_all=DEFAULT, shutdown_all=DEFAULT)
     # test check address
     check_call(main_loop, mocked_loop, 'check_node',
                DeferredRequestHeaders.CHECK_NODE, ('10.0.0.2',))
@@ -312,3 +353,9 @@ def test_send_request(mocker, main_loop):
     # test shutdown
     check_call(main_loop, mocked_loop, 'shutdown',
                DeferredRequestHeaders.SHUTDOWN, ('10.0.0.2',))
+    # test restart_all
+    check_call(main_loop, mocked_loop, 'restart_all',
+               DeferredRequestHeaders.RESTART_ALL, ('10.0.0.2',))
+    # test shutdown
+    check_call(main_loop, mocked_loop, 'shutdown_all',
+               DeferredRequestHeaders.SHUTDOWN_ALL, ('10.0.0.2',))
diff --git a/supvisors/tests/test_options.py b/supvisors/tests/test_options.py
index c252e521..2ef98574 100644
--- a/supvisors/tests/test_options.py
+++ b/supvisors/tests/test_options.py
@@ -20,8 +20,6 @@
 import pytest
 import sys
 
-from supervisor.datatypes import Automatic
-
 from supvisors.options import *
 from supvisors.ttypes import ConciliationStrategies, StartingStrategies
 
@@ -196,7 +194,7 @@ def test_default_options(mocker, server_opt):
     assert opt.event_port == 65002
     assert not opt.auto_fence
     assert opt.synchro_timeout == 15
-    assert opt.force_synchro_if == []
+    assert opt.force_synchro_if == set()
     assert opt.conciliation_strategy == ConciliationStrategies.USER
     assert opt.starting_strategy == StartingStrategies.CONFIG
     assert opt.stats_periods == [10]
@@ -218,7 +216,7 @@ def test_defined_options(mocker, server_opt):
     assert opt.event_port == 60002
     assert opt.auto_fence
     assert opt.synchro_timeout == 20
-    assert opt.force_synchro_if == ['cliche01', 'cliche03']
+    assert opt.force_synchro_if == {'cliche01', 'cliche03'}
     assert opt.conciliation_strategy == ConciliationStrategies.SENICIDE
     assert opt.starting_strategy == StartingStrategies.MOST_LOADED
     assert opt.stats_periods == [5, 60, 600]
diff --git a/supvisors/tests/test_process.py b/supvisors/tests/test_process.py
index 681797d9..bf071674 100644
--- a/supvisors/tests/test_process.py
+++ b/supvisors/tests/test_process.py
@@ -101,12 +101,12 @@ def test_rules_check_start_sequence(rules):
 def test_rules_check_autorestart(rules):
     """ Test the dependency related to running failure strategy in process rules.
     Done in a separate test as it impacts the supervisor internal model. """
-    # test that only the CONTINUE strategy keeps the autorestart
+    # test that only the CONTINUE and RESTART_PROCESS strategies keep the autorestart
     mocked_disable = rules.supvisors.info_source.disable_autorestart
     for strategy in RunningFailureStrategies:
         rules.running_failure_strategy = strategy
         rules.check_autorestart('dummy_process_1')
-        if strategy == RunningFailureStrategies.CONTINUE:
+        if strategy in [RunningFailureStrategies.CONTINUE, RunningFailureStrategies.RESTART_PROCESS]:
             assert not mocked_disable.called
         else:
             assert mocked_disable.call_args_list == [call('dummy_process_1')]
@@ -229,7 +229,7 @@ def test_status_stopped_process(supvisors):
     assert not process.pid_running_on('10.0.0.1')
     assert not process.pid_running_on('10.0.0.2')
     # test again with forced state
-    process.force_state({'state': ProcessStates.FATAL, 'spawnerr': ''})
+    process.force_state(ProcessStates.FATAL, '')
     assert process._state == ProcessStates.STOPPED
     assert process.stopped()
     assert not process.running()
@@ -253,7 +253,7 @@ def test_status_backoff_process(supvisors):
     assert not process.pid_running_on('10.0.0.1')
     assert not process.pid_running_on('10.0.0.2')
     # test again with forced state
-    process.force_state({'state': ProcessStates.STOPPED, 'spawnerr': ''})
+    process.force_state(ProcessStates.STOPPED)
     assert process._state == ProcessStates.BACKOFF
     assert process.stopped()
     assert not process.running()
@@ -364,7 +364,7 @@ def test_serialization(supvisors):
     loaded = pickle.loads(dumped)
     assert loaded == serialized
     # test again with forced state
-    process.force_state({'state': ProcessStates.FATAL, 'spawnerr': 'anything'})
+    process.force_state(ProcessStates.FATAL, 'anything')
     assert process._state == ProcessStates.STOPPED
     serialized = process.serial()
     assert serialized == {'application_name': info['group'], 'process_name': info['name'],
@@ -377,19 +377,19 @@ def test_get_last_description(supvisors):
     # create ProcessStatus instance
     process = create_process({'group': 'dummy_application', 'name': 'dummy_proc'}, supvisors)
     process.info_map = {'10.0.0.1': {'local_time': 10, 'stop': 32, 'description': 'desc1'},
-                        '10.0.0.2': {'local_time': 30, 'stop': 12, 'description': 'desc2'},
+                        '10.0.0.2': {'local_time': 30, 'stop': 12, 'description': 'Not started'},
                         '10.0.0.3': {'local_time': 20, 'stop': 22, 'description': 'desc3'}}
     # state is not forced by default
     # test method return on non-running process
-    assert process.get_last_description() == ('10.0.0.1', 'desc1')
+    assert process.get_last_description() == ('10.0.0.1', 'desc1 on 10.0.0.1')
     # test method return on running process
     process.running_nodes.add('10.0.0.3')
-    assert process.get_last_description() == ('10.0.0.3', 'desc3')
+    assert process.get_last_description() == ('10.0.0.3', 'desc3 on 10.0.0.3')
     # test method return on multiple running processes
     process.running_nodes.add('10.0.0.2')
-    assert process.get_last_description() == ('10.0.0.2', 'desc2')
+    assert process.get_last_description() == ('10.0.0.2', 'Not started')
     # test again with forced state
-    process.force_state({'state': ProcessStates.FATAL, 'spawnerr': 'global crash'})
+    process.force_state(ProcessStates.FATAL, 'global crash')
     assert process.get_last_description() == (None, 'global crash')
     process.running_nodes = set()
     assert process.get_last_description() == (None, 'global crash')
@@ -422,7 +422,7 @@ def test_add_info(supvisors):
     # check forced_state
     assert process.forced_state is None
     assert process.forced_reason is None
-    process.force_state({'state': ProcessStates.FATAL, 'spawnerr': 'failure'})
+    process.force_state(ProcessStates.FATAL, 'failure')
     assert process.forced_state == ProcessStates.FATAL
     assert process.forced_reason == 'failure'
     assert process.state == ProcessStates.FATAL
@@ -521,7 +521,7 @@ def test_update_info(supvisors):
     # check forced_state
     assert process.forced_state is None
     assert process.forced_reason is None
-    process.force_state({'state': ProcessStates.FATAL, 'spawnerr': 'failure'})
+    process.force_state(ProcessStates.FATAL, 'failure')
     assert process.forced_state == ProcessStates.FATAL
     assert process.forced_reason == 'failure'
     assert process.state == ProcessStates.FATAL
diff --git a/supvisors/tests/test_rpcinterface.py b/supvisors/tests/test_rpcinterface.py
index e6363c61..f47a3d57 100644
--- a/supvisors/tests/test_rpcinterface.py
+++ b/supvisors/tests/test_rpcinterface.py
@@ -19,12 +19,13 @@
 
 import pytest
 
-from supervisor.loggers import LOG_LEVELS_BY_NUM
 from unittest.mock import call, Mock
 
 from supvisors.plugin import expand_faults
 from supvisors.rpcinterface import *
-from supvisors.ttypes import ConciliationStrategies, SupvisorsStates
+from supvisors.ttypes import ApplicationStates, ConciliationStrategies, SupvisorsStates
+
+from .conftest import create_application
 
 
 @pytest.fixture(autouse=True)
@@ -46,7 +47,6 @@ def test_creation(supvisors, rpc):
 
 def test_api_version(rpc):
     """ Test the get_api_version RPC. """
-    from supvisors.rpcinterface import API_VERSION
     assert rpc.get_api_version() == API_VERSION
 
 
@@ -68,7 +68,6 @@ def test_master_address(rpc):
 
 def test_strategies(rpc):
     """ Test the get_strategies RPC. """
-    from supvisors.ttypes import ConciliationStrategies, StartingStrategies
     # prepare context
     rpc.supvisors.options.auto_fence = True
     rpc.supvisors.options.conciliation_strategy = ConciliationStrategies.INFANTICIDE
@@ -100,11 +99,13 @@ def test_all_addresses_info(rpc):
 
 def test_application_info(mocker, rpc):
     """ Test the get_application_info RPC. """
-    mocked_serial = mocker.patch('supvisors.rpcinterface.RPCInterface._get_application',
-                                 return_value=Mock(**{'serial.return_value': {'name': 'appli'}}))
+    application = create_application('TestApplication', rpc.supvisors)
+    mocked_serial = mocker.patch('supvisors.rpcinterface.RPCInterface._get_application', return_value=application)
     mocked_check = mocker.patch('supvisors.rpcinterface.RPCInterface._check_from_deployment')
     # test RPC call
-    assert rpc.get_application_info('dummy') == {'name': 'appli'}
+    assert rpc.get_application_info('dummy') == {'application_name': 'TestApplication',
+                                                 'major_failure': False, 'minor_failure': False,
+                                                 'statecode': 0, 'statename': 'STOPPED'}
     assert mocked_check.call_args_list == [call()]
     assert mocked_serial.call_args_list == [call('dummy')]
 
@@ -186,12 +187,30 @@ def test_all_local_process_info(mocker, rpc):
 
 def test_application_rules(mocker, rpc):
     """ Test the get_application_rules RPC. """
+    application = create_application('TestApplication', rpc.supvisors)
     mocked_check = mocker.patch('supvisors.rpcinterface.RPCInterface._check_from_deployment')
-    mocked_get = mocker.patch('supvisors.rpcinterface.RPCInterface._get_application',
-                              return_value=Mock(**{'rules.serial.return_value': {'start': 1, 'stop': 2,
-                                                                                 'required': True}}))
-    # test RPC call with application name
-    assert rpc.get_application_rules('appli') == {'application_name': 'appli', 'start': 1, 'stop': 2, 'required': True}
+    mocked_get = mocker.patch('supvisors.rpcinterface.RPCInterface._get_application', return_value=application)
+    # test RPC call with application name and unmanaged application
+    expected = {'application_name': 'appli', 'managed': False}
+    assert rpc.get_application_rules('appli') == expected
+    assert mocked_check.call_args_list == [call()]
+    assert mocked_get.call_args_list == [call('appli')]
+    mocker.resetall()
+    # test RPC call with application name and managed/distributed application
+    application.rules.managed = True
+    expected = {'application_name': 'appli', 'managed': True, 'distributed': True,
+                'start_sequence': 0, 'stop_sequence': 0, 'starting_strategy': 'CONFIG',
+                'starting_failure_strategy': 'ABORT', 'running_failure_strategy': 'CONTINUE'}
+    assert rpc.get_application_rules('appli') == expected
+    assert mocked_check.call_args_list == [call()]
+    assert mocked_get.call_args_list == [call('appli')]
+    mocker.resetall()
+    # test RPC call with application name and managed/non-distributed application
+    application.rules.distributed = False
+    expected = {'application_name': 'appli', 'managed': True, 'distributed': False, 'addresses': ['*'],
+                'start_sequence': 0, 'stop_sequence': 0, 'starting_strategy': 'CONFIG',
+                'starting_failure_strategy': 'ABORT', 'running_failure_strategy': 'CONTINUE'}
+    assert rpc.get_application_rules('appli') == expected
     assert mocked_check.call_args_list == [call()]
     assert mocked_get.call_args_list == [call('appli')]
 
@@ -234,7 +253,6 @@ def test_conflicts(mocker, rpc):
 def test_start_application(mocker, rpc):
     """ Test the start_application RPC. """
     mocked_check = mocker.patch('supvisors.rpcinterface.RPCInterface._check_operating')
-    from supvisors.ttypes import ApplicationStates, StartingStrategies
     # prepare context
     rpc.supvisors.context.applications = {'appli_1': Mock()}
     # get patches
@@ -328,9 +346,9 @@ def test_start_application(mocker, rpc):
 def test_stop_application(mocker, rpc):
     """ Test the stop_application RPC. """
     mocked_check = mocker.patch('supvisors.rpcinterface.RPCInterface._check_operating_conciliation')
-    from supvisors.ttypes import ApplicationStates
     # prepare context
-    rpc.supvisors.context.applications = {'appli_1': Mock()}
+    appli_1 = Mock(**{'has_running_processes.return_value': False})
+    rpc.supvisors.context.applications = {'appli_1': appli_1}
     # get patches
     mocked_stop = rpc.supvisors.stopper.stop_application
     mocked_progress = rpc.supvisors.stopper.in_progress
@@ -344,7 +362,6 @@ def test_stop_application(mocker, rpc):
     mocked_check.reset_mock()
     # test RPC call with stopped application
     application = rpc.supvisors.context.applications['appli_1']
-    application.state = ApplicationStates.STOPPED
     with pytest.raises(RPCError) as exc:
         rpc.stop_application('appli_1')
     assert exc.value.args == (Faults.NOT_RUNNING, 'appli_1')
@@ -353,55 +370,54 @@ def test_stop_application(mocker, rpc):
     assert mocked_progress.call_count == 0
     mocked_check.reset_mock()
     # test RPC call with running application
-    for appli_state in [ApplicationStates.STOPPING, ApplicationStates.RUNNING, ApplicationStates.STARTING]:
-        application.state = appli_state
-        # test no wait and done
-        mocked_stop.return_value = True
-        result = rpc.stop_application('appli_1', False)
-        assert not result
-        assert mocked_check.call_args_list == [call()]
-        assert mocked_stop.call_args_list == [call(application)]
-        assert mocked_progress.call_count == 0
-        mocked_check.reset_mock()
-        mocked_stop.reset_mock()
-        # test wait and done
-        mocked_stop.return_value = True
-        result = rpc.stop_application('appli_1')
-        assert not result
-        assert mocked_check.call_args_list == [call()]
-        assert mocked_stop.call_args_list == [call(application)]
-        assert mocked_progress.call_count == 0
-        mocked_check.reset_mock()
-        mocked_stop.reset_mock()
-        # test wait and not done
-        mocked_stop.return_value = False
-        result = rpc.stop_application('appli_1')
-        # result is a function
-        assert callable(result)
-        assert mocked_check.call_args_list == [call()]
-        assert mocked_stop.call_args_list == [call(application)]
-        assert mocked_progress.call_count == 0
-        # test returned function: return True when job in progress
-        mocked_progress.return_value = True
-        assert result() == NOT_DONE_YET
-        assert mocked_progress.call_args_list == [call()]
-        mocked_progress.reset_mock()
-        # test returned function: raise exception if job not in progress anymore and application not running
-        mocked_progress.return_value = False
-        for _ in [ApplicationStates.STOPPING, ApplicationStates.RUNNING, ApplicationStates.STARTING]:
-            with pytest.raises(RPCError) as exc:
-                result()
-            assert exc.value.args == (Faults.ABNORMAL_TERMINATION, 'appli_1')
-            assert mocked_progress.call_args_list == [call()]
-            mocked_progress.reset_mock()
-        # test returned function: return True if job not in progress anymore and application running
-        application.state = ApplicationStates.STOPPED
-        assert result()
+    appli_1.has_running_processes.return_value = True
+    # test no wait and done
+    mocked_stop.return_value = True
+    result = rpc.stop_application('appli_1', False)
+    assert not result
+    assert mocked_check.call_args_list == [call()]
+    assert mocked_stop.call_args_list == [call(application)]
+    assert mocked_progress.call_count == 0
+    mocked_check.reset_mock()
+    mocked_stop.reset_mock()
+    # test wait and done
+    mocked_stop.return_value = True
+    result = rpc.stop_application('appli_1')
+    assert not result
+    assert mocked_check.call_args_list == [call()]
+    assert mocked_stop.call_args_list == [call(application)]
+    assert mocked_progress.call_count == 0
+    mocked_check.reset_mock()
+    mocked_stop.reset_mock()
+    # test wait and not done
+    mocked_stop.return_value = False
+    result = rpc.stop_application('appli_1')
+    # result is a function
+    assert callable(result)
+    assert mocked_check.call_args_list == [call()]
+    assert mocked_stop.call_args_list == [call(application)]
+    assert mocked_progress.call_count == 0
+    # test returned function: return True when job in progress
+    mocked_progress.return_value = True
+    assert result() == NOT_DONE_YET
+    assert mocked_progress.call_args_list == [call()]
+    mocked_progress.reset_mock()
+    # test returned function: raise exception if job not in progress anymore and application not running
+    mocked_progress.return_value = False
+    for _ in [ApplicationStates.STOPPING, ApplicationStates.RUNNING, ApplicationStates.STARTING]:
+        with pytest.raises(RPCError) as exc:
+            result()
+        assert exc.value.args == (Faults.ABNORMAL_TERMINATION, 'appli_1')
         assert mocked_progress.call_args_list == [call()]
-        # reset patches for next loop
-        mocked_check.reset_mock()
-        mocked_stop.reset_mock()
         mocked_progress.reset_mock()
+    # test returned function: return True if job not in progress anymore and application running
+    application.state = ApplicationStates.STOPPED
+    assert result()
+    assert mocked_progress.call_args_list == [call()]
+    # reset patches for next loop
+    mocked_check.reset_mock()
+    mocked_stop.reset_mock()
+    mocked_progress.reset_mock()
 
 
 def test_restart_application(mocker, rpc):
@@ -529,7 +545,6 @@ def test_start_args(mocker, rpc):
 def test_start_process(mocker, rpc):
     """ Test the start_process RPC. """
     mocked_check = mocker.patch('supvisors.rpcinterface.RPCInterface._check_operating')
-    from supvisors.ttypes import StartingStrategies
     # get patches
     mocked_start = rpc.supvisors.starter.start_process
     mocked_progress = rpc.supvisors.starter.in_progress
@@ -579,8 +594,9 @@ def test_start_process(mocker, rpc):
     mocked_start.reset_mock()
     # test RPC call no wait and done
     mocked_start.return_value = True
-    result = rpc.start_process(1, 'appli:*', 'argument list', False)
-    assert result
+    with pytest.raises(RPCError) as exc:
+        rpc.start_process(1, 'appli:*', 'argument list', False)
+    assert exc.value.args == (Faults.ABNORMAL_TERMINATION, 'appli:*')
     assert mocked_check.call_args_list == [call()]
     assert mocked_start.call_args_list == [call(StartingStrategies.LESS_LOADED, proc_1, 'argument list'),
                                            call(StartingStrategies.LESS_LOADED, proc_2, 'argument list')]
@@ -588,8 +604,9 @@ def test_start_process(mocker, rpc):
     mocked_check.reset_mock()
     mocked_start.reset_mock()
     # test RPC call with wait and done
-    result = rpc.start_process(2, 'appli:*', wait=True)
-    assert result
+    with pytest.raises(RPCError) as exc:
+        rpc.start_process(2, 'appli:*', wait=True)
+    assert exc.value.args == (Faults.ABNORMAL_TERMINATION, 'appli:*')
     assert mocked_start.call_args_list == [call(StartingStrategies.MOST_LOADED, proc_1, ''),
                                            call(StartingStrategies.MOST_LOADED, proc_2, '')]
     assert not mocked_progress.called
diff --git a/supvisors/tests/test_sparser.py b/supvisors/tests/test_sparser.py
index 1d065e3a..b5f42ed4 100644
--- a/supvisors/tests/test_sparser.py
+++ b/supvisors/tests/test_sparser.py
@@ -30,16 +30,21 @@
 
 def assert_default_application_rules(rules):
     """ Check that rules contains default values. """
-    assert_application_rules(rules, False, 0, 0, StartingFailureStrategies.ABORT, RunningFailureStrategies.CONTINUE)
+    assert_application_rules(rules, False, True, ['*'], 0, 0, StartingStrategies.CONFIG,
+                             StartingFailureStrategies.ABORT, RunningFailureStrategies.CONTINUE)
 
 
-def assert_application_rules(rules, managed, start, stop, starting_strategy, running_strategy):
+def assert_application_rules(rules, managed, distributed, node_names, start, stop, starting_strategy,
+                             starting_failure_strategy, running_failure_strategy):
     """ Check the application rules. """
     assert rules.managed == managed
+    assert rules.distributed == distributed
+    assert rules.node_names == node_names
     assert rules.start_sequence == start
     assert rules.stop_sequence == stop
-    assert rules.starting_failure_strategy == starting_strategy
-    assert rules.running_failure_strategy == running_strategy
+    assert rules.starting_strategy == starting_strategy
+    assert rules.starting_failure_strategy == starting_failure_strategy
+    assert rules.running_failure_strategy == running_failure_strategy
 
 
 def assert_default_process_rules(rules):
@@ -83,24 +88,24 @@ def check_valid(parser):
     assert_default_application_rules(rules)
     # check first application
     rules = load_application_rules(parser, 'dummy_application_A')
-    assert_application_rules(rules, True, 0, 0, StartingFailureStrategies.ABORT,
-                             RunningFailureStrategies.CONTINUE)
+    assert_application_rules(rules, True, True, ['*'], 0, 0, StartingStrategies.CONFIG,
+                             StartingFailureStrategies.ABORT, RunningFailureStrategies.CONTINUE)
     # check second application
     rules = load_application_rules(parser, 'dummy_application_B')
-    assert_application_rules(rules, True, 1, 4, StartingFailureStrategies.STOP,
-                             RunningFailureStrategies.RESTART_PROCESS)
+    assert_application_rules(rules, True, False, ['*'], 1, 4, StartingStrategies.CONFIG,
+                             StartingFailureStrategies.STOP, RunningFailureStrategies.RESTART_PROCESS)
     # check third application
     rules = load_application_rules(parser, 'dummy_application_C')
-    assert_application_rules(rules, True, 20, 0, StartingFailureStrategies.ABORT,
-                             RunningFailureStrategies.STOP_APPLICATION)
+    assert_application_rules(rules, True, True, ['*'], 20, 0, StartingStrategies.LOCAL,
+                             StartingFailureStrategies.ABORT, RunningFailureStrategies.STOP_APPLICATION)
     # check fourth application
     rules = load_application_rules(parser, 'dummy_application_D')
-    assert_application_rules(rules, True, 0, 100, StartingFailureStrategies.CONTINUE,
-                             RunningFailureStrategies.RESTART_APPLICATION)
+    assert_application_rules(rules, True, False, ['10.0.0.1', '10.0.0.5'], 0, 100, StartingStrategies.LESS_LOADED,
+                             StartingFailureStrategies.CONTINUE, RunningFailureStrategies.RESTART_APPLICATION)
     # check loop application
     rules = load_application_rules(parser, 'dummy_application_E')
-    assert_application_rules(rules, True, 0, 0, StartingFailureStrategies.ABORT,
-                             RunningFailureStrategies.CONTINUE)
+    assert_application_rules(rules, True, True,  ['*'], 0, 0,  StartingStrategies.MOST_LOADED,
+                             StartingFailureStrategies.ABORT, RunningFailureStrategies.CONTINUE)
     # check program from unknown application: all default
     rules = load_process_rules(parser, 'dummy_application_X', 'dummy_program_X0')
     assert_default_process_rules(rules)
@@ -173,20 +178,20 @@ def check_invalid(parser):
     assert_default_application_rules(rules)
     # check first application
     rules = load_application_rules(parser, 'dummy_application_A')
-    assert_application_rules(rules, True, 0, 0, StartingFailureStrategies.ABORT,
-                             RunningFailureStrategies.CONTINUE)
+    assert_application_rules(rules, True, True, ['*'], 0, 0, StartingStrategies.CONFIG,
+                             StartingFailureStrategies.ABORT, RunningFailureStrategies.CONTINUE)
     # check second application
     rules = load_application_rules(parser, 'dummy_application_B')
-    assert_application_rules(rules, True, 1, 4, StartingFailureStrategies.STOP,
-                             RunningFailureStrategies.RESTART_PROCESS)
+    assert_application_rules(rules, True, True, ['*'], 1, 4, StartingStrategies.CONFIG,
+                             StartingFailureStrategies.STOP, RunningFailureStrategies.RESTART_PROCESS)
     # check third application
     rules = load_application_rules(parser, 'dummy_application_C')
-    assert_application_rules(rules, True, 20, 0, StartingFailureStrategies.ABORT,
-                             RunningFailureStrategies.STOP_APPLICATION)
+    assert_application_rules(rules, True, False, ['*'], 20, 0, StartingStrategies.CONFIG,
+                             StartingFailureStrategies.ABORT, RunningFailureStrategies.STOP_APPLICATION)
     # check fourth application
     rules = load_application_rules(parser, 'dummy_application_D')
-    assert_application_rules(rules, True, 0, 100, StartingFailureStrategies.CONTINUE,
-                             RunningFailureStrategies.RESTART_APPLICATION)
+    assert_application_rules(rules, True, True, ['*'], 0, 100, StartingStrategies.CONFIG,
+                             StartingFailureStrategies.CONTINUE, RunningFailureStrategies.RESTART_APPLICATION)
     # check program from unknown application: all default
     rules = load_process_rules(parser, 'dummy_application_X', 'dummy_program_X0')
     assert_default_process_rules(rules)
diff --git a/supvisors/tests/test_statemachine.py b/supvisors/tests/test_statemachine.py
index 4089f779..1dfe70b4 100644
--- a/supvisors/tests/test_statemachine.py
+++ b/supvisors/tests/test_statemachine.py
@@ -23,10 +23,7 @@
 
 from supvisors.address import AddressStatus
 from supvisors.statemachine import *
-from supvisors.ttypes import AddressStates, ApplicationStates, SupvisorsStates
-
-from .base import database_copy
-from .conftest import create_application, create_process
+from supvisors.ttypes import AddressStates, SupvisorsStates
 
 
 @pytest.fixture
@@ -70,11 +67,6 @@ def test_abstract_state(supvisors_ctx):
     state.abort_jobs()
     assert supvisors_ctx.failure_handler.abort.called
     assert supvisors_ctx.starter.abort.called
-    # test apply_addresses_func method
-    mock_function = Mock()
-    mock_function.__name__ = 'dummy_name'
-    state.apply_nodes_func(mock_function)
-    assert mock_function.call_args_list == [call('10.0.0.2'), call('10.0.0.4'), call('127.0.0.1')]
 
 
 def test_initialization_state(mocker, supvisors_ctx):
@@ -85,7 +77,6 @@ def test_initialization_state(mocker, supvisors_ctx):
     # test that all active nodes have been reset to UNKNOWN
     state.enter()
     assert state.context.master_node_name == ''
-    assert int(time()) >= state.start_date
     nodes = supvisors_ctx.context.nodes
     assert nodes['127.0.0.1'].state == AddressStates.UNKNOWN
     assert nodes['10.0.0.1'].state == AddressStates.SILENT
@@ -94,6 +85,8 @@ def test_initialization_state(mocker, supvisors_ctx):
     assert nodes['10.0.0.4'].state == AddressStates.UNKNOWN
     assert nodes['10.0.0.5'].state == AddressStates.ISOLATED
     # 2. test next method
+    # trigger log for synchro time out
+    state.context.start_date = 0
     # test that Supvisors wait for all nodes to be running or a given timeout is reached
     # test case no node is running, especially local node
     result = state.next()
@@ -109,138 +102,83 @@ def test_initialization_state(mocker, supvisors_ctx):
     nodes['10.0.0.3']._state = AddressStates.ISOLATED
     result = state.next()
     assert result == SupvisorsStates.DEPLOYMENT
-    # test case where end of synchro is forced based on a subset of nodes
-    supvisors_ctx.context.forced_nodes = {'10.0.0.2': nodes['10.0.0.2'], '10.0.0.4':  nodes['10.0.0.4']}
-    nodes['10.0.0.1']._state = AddressStates.UNKNOWN
+    # test case where end of synchro is forced based on core nodes running
+    supvisors_ctx.options.force_synchro_if = {'10.0.0.2', '10.0.0.4'}
     nodes['10.0.0.3']._state = AddressStates.UNKNOWN
-    result = state.next()
-    assert result == SupvisorsStates.DEPLOYMENT
-    # test case where nodes are still unknown and timeout is reached
-    supvisors_ctx.context.forced_nodes = {}
-    state.start_date = time() - 11
+    nodes['10.0.0.4']._state = AddressStates.RUNNING
     result = state.next()
     assert result == SupvisorsStates.DEPLOYMENT
     # 3. test exit method
-    # test that context end_synchro is called and master is the lowest string among address names
-    nodes['10.0.0.4']._state = AddressStates.RUNNING
-    mocked_synchro = mocker.patch.object(supvisors_ctx.context, 'end_synchro')
+    # test when master_node_name is already set: no change
+    supvisors_ctx.context.master_node_name = '127.0.0.1'
+    state.exit()
+    assert supvisors_ctx.context.master_node_name == '127.0.0.1'
+    # test when master_node_name is not set and no core nodes
+    # check master is the lowest string among running node names
+    supvisors_ctx.context.master_node_name = None
+    supvisors_ctx.options.force_synchro_if = {}
     state.exit()
-    assert mocked_synchro.call_count == 1
+    assert supvisors_ctx.context.running_nodes() == ['127.0.0.1', '10.0.0.2', '10.0.0.4']
     assert supvisors_ctx.context.master_node_name == '10.0.0.2'
+    # test when master_node_name is not set and forced nodes are used
+    # check master is the lowest string among the intersection between running node names and forced nodes
+    supvisors_ctx.context.master_node_name = None
+    supvisors_ctx.options.force_synchro_if = {'10.0.0.3', '10.0.0.4'}
+    state.exit()
+    assert supvisors_ctx.context.running_nodes() == ['127.0.0.1', '10.0.0.2', '10.0.0.4']
+    assert supvisors_ctx.context.master_node_name == '10.0.0.4'
 
 
-def test_deployment_state(mocker, supvisors_ctx):
+def test_master_deployment_state(mocker, supvisors_ctx):
     """ Test the Deployment state of the fsm. """
-    state = DeploymentState(supvisors_ctx)
+    state = MasterDeploymentState(supvisors_ctx)
     assert isinstance(state, AbstractState)
     # test enter method
-    # test that start_applications is  called only when local address is the master address
     mocked_starter = supvisors_ctx.starter.start_applications
-    supvisors_ctx.context._is_master = False
-    state.enter()
-    assert not mocked_starter.called
-    # now master address is local
-    supvisors_ctx.context._is_master = True
-    state.enter()
-    # create application context
-    application = create_application('sample_test_2', supvisors_ctx)
-    application.rules.managed = True
-    supvisors_ctx.context.applications['sample_test_2'] = application
-    for info in database_copy():
-        if info['group'] == 'sample_test_2':
-            process = create_process(info, supvisors_ctx)
-            process.rules.start_sequence = len(process.namespec) % 3
-            process.rules.stop_sequence = len(process.namespec) % 3 + 1
-            process.add_info('10.0.0.1', info)
-            application.add_process(process)
-    # sample_test_2 has 3 processes: one FATAL, one EXITED and one RUNNING
-    # test application updates. at this point, Application.update_status has not been called
-    supvisors_ctx.context._is_master = False
-    assert application.state == ApplicationStates.STOPPED
-    assert not application.minor_failure
-    assert not application.major_failure
-    assert application.start_sequence == {}
-    assert application.stop_sequence == {}
+    supvisors_ctx.fsm.redeploy_mark = True
     state.enter()
-    # enter triggered Application.update_status
-    assert application.state == ApplicationStates.RUNNING
-    assert application.minor_failure
-    assert not application.major_failure
-    # list order may differ, so break down
-    assert sorted(application.start_sequence.keys()) == [0, 1]
-    assert len(application.start_sequence[0]) == 2
-    assert all(item in [application.processes['yeux_01'], application.processes['yeux_00']]
-               for item in application.start_sequence[0])
-    assert application.start_sequence[1] == [application.processes['sleep']]
-    assert sorted(application.stop_sequence.keys()) == [1, 2]
-    assert len(application.stop_sequence[1]) == 2
-    assert all(item in [application.processes['yeux_01'], application.processes['yeux_00']]
-               for item in application.stop_sequence[1])
-    assert application.stop_sequence[2] == [application.processes['sleep']]
+    assert mocked_starter.called
+    assert not supvisors_ctx.fsm.redeploy_mark
     # test next method if check_nodes return something
-    mocker.patch.object(state, 'check_nodes', return_value='new state')
-    assert state.next() == 'new state'
-    assert not supvisors_ctx.starter.check_starting.called
+    mocker.patch.object(state, 'check_nodes', return_value=SupvisorsStates.INITIALIZATION)
+    assert state.next() == SupvisorsStates.INITIALIZATION
+    assert not supvisors_ctx.starter.is_starting_completed.called
     # test next method if check_nodes return nothing
     state.check_nodes.return_value = None
     # test next method if the local node is master
     supvisors_ctx.context._is_master = True
-    # stay in DEPLOYMENT if a start sequence is in progress, whatever the local operational status
-    supvisors_ctx.starter.check_starting.return_value = False
-    for operational in [True, False]:
-        supvisors_ctx.context.master_operational = operational
-        result = state.next()
-        assert result == SupvisorsStates.DEPLOYMENT
-    # return OPERATION  and no start sequence is in progress, whatever the local operational status
-    supvisors_ctx.starter.check_starting.return_value = True
-    for operational in [True, False]:
-        supvisors_ctx.context.master_operational = operational
-        result = state.next()
-        assert result == SupvisorsStates.OPERATION
-    # test next method if the local node is NOT master
-    supvisors_ctx.context._is_master = False
-    # stay in DEPLOYMENT if the master node is not operational, whatever the start sequence status
-    supvisors_ctx.context.master_operational = False
-    for starting in [True, False]:
-        supvisors_ctx.starter.check_starting.return_value = starting
-        result = state.next()
-        assert result == SupvisorsStates.DEPLOYMENT
-    # return OPERATION if the master node is operational, whatever the start sequence status
-    supvisors_ctx.context.master_operational = True
-    for starting in [True, False]:
-        supvisors_ctx.starter.check_starting.return_value = starting
-        result = state.next()
-        assert result == SupvisorsStates.OPERATION
+    # stay in DEPLOYMENT if a start sequence is in progress
+    supvisors_ctx.starter.is_starting_completed.return_value = False
+    result = state.next()
+    assert result == SupvisorsStates.DEPLOYMENT
+    # return OPERATION and no start sequence is in progress
+    supvisors_ctx.starter.is_starting_completed.return_value = True
+    result = state.next()
+    assert result == SupvisorsStates.OPERATION
     # no exit implementation. just call it without test
     state.exit()
 
 
-def test_operation_state(mocker, supvisors_ctx):
+def test_master_operation_state(mocker, supvisors_ctx):
     """ Test the Operation state of the fsm. """
-    state = OperationState(supvisors_ctx)
+    mocked_start = supvisors_ctx.starter.is_starting_completed
+    # create instance
+    state = MasterOperationState(supvisors_ctx)
     assert isinstance(state, AbstractState)
-    # test enter method if local is not master
-    assert not supvisors_ctx.context.master_operational
-    assert not supvisors_ctx.context.is_master
-    state.enter()
-    assert not supvisors_ctx.context.master_operational
-    # test enter method if local is master
-    supvisors_ctx.context._is_master = True
-    assert supvisors_ctx.context.is_master
+    # no enter implementation. just call it without test
     state.enter()
-    assert supvisors_ctx.context.master_operational
     # test next method if check_nodes return something
-    mocker.patch.object(state, 'check_nodes', return_value='new state')
-    assert state.next() == 'new state'
-    assert not supvisors_ctx.starter.check_starting.called
+    mocker.patch.object(state, 'check_nodes', return_value=SupvisorsStates.INITIALIZATION)
+    assert state.next() == SupvisorsStates.INITIALIZATION
+    assert not mocked_start.called
     # test next method if check_nodes return nothing
     state.check_nodes.return_value = None
     # do not leave OPERATION state if a starting or a stopping is in progress
-    mocked_start = mocker.patch.object(supvisors_ctx.starter, 'check_starting', return_value=False)
+    mocked_start.return_value = False
     result = state.next()
     assert result == SupvisorsStates.OPERATION
     mocked_start.return_value = True
-    mocked_stop = mocker.patch.object(supvisors_ctx.stopper, 'check_stopping', return_value=False)
+    mocked_stop = mocker.patch.object(supvisors_ctx.stopper, 'is_stopping_completed', return_value=False)
     result = state.next()
     assert result == SupvisorsStates.OPERATION
     mocked_stop.return_value = True
@@ -255,11 +193,6 @@ def test_operation_state(mocker, supvisors_ctx):
     supvisors_ctx.fsm.redeploy_mark = True
     result = state.next()
     assert result == SupvisorsStates.DEPLOYMENT
-    assert not supvisors_ctx.fsm.redeploy_mark
-    # re-test while not marked for re-deployment
-    result = state.next()
-    assert result == SupvisorsStates.OPERATION
-    assert not supvisors_ctx.fsm.redeploy_mark
     # transit to CONCILIATION if conflict detected
     mocked_conflict.return_value = True
     result = state.next()
@@ -268,44 +201,40 @@ def test_operation_state(mocker, supvisors_ctx):
     state.exit()
 
 
-def test_conciliation_state(mocker, supvisors_ctx):
+def test_master_conciliation_state(mocker, supvisors_ctx):
     """ Test the Conciliation state of the fsm. """
     mocked_conciliate = mocker.patch('supvisors.statemachine.conciliate_conflicts')
+    mocked_start = supvisors_ctx.starter.is_starting_completed
+    mocked_stop = supvisors_ctx.stopper.is_stopping_completed
     # create instance
-    state = ConciliationState(supvisors_ctx)
+    state = MasterConciliationState(supvisors_ctx)
     assert isinstance(state, AbstractState)
     # test enter method
     mocker.patch.object(supvisors_ctx.context, 'conflicts', return_value=[1, 2, 3])
-    # nothing done if local is not master
-    supvisors_ctx.context._is_master = False
-    state.enter()
-    assert not mocked_conciliate.called
-    # conciliation called if local is master
-    supvisors_ctx.context._is_master = True
     state.enter()
     assert mocked_conciliate.call_args_list == [call(supvisors_ctx, 0, [1, 2, 3])]
     # test next method if check_nodes return something
-    mocker.patch.object(state, 'check_nodes', return_value='new state')
-    assert state.next() == 'new state'
-    assert not supvisors_ctx.starter.check_starting.called
+    mocker.patch.object(state, 'check_nodes', return_value=SupvisorsStates.INITIALIZATION)
+    assert state.next() == SupvisorsStates.INITIALIZATION
+    assert not mocked_start.called
     # test next method if check_nodes return nothing
     state.check_nodes.return_value = None
     # do not leave CONCILIATION state if a starting or a stopping is in progress
-    supvisors_ctx.starter.check_starting.return_value = False
-    supvisors_ctx.stopper.check_stopping.return_value = False
+    mocked_start.return_value = False
+    mocked_stop.return_value = False
     result = state.next()
     assert result == SupvisorsStates.CONCILIATION
-    supvisors_ctx.starter.check_starting.return_value = True
-    supvisors_ctx.stopper.check_stopping.return_value = False
+    mocked_start.return_value = True
+    mocked_stop.return_value = False
     result = state.next()
     assert result == SupvisorsStates.CONCILIATION
-    supvisors_ctx.starter.check_starting.return_value = False
-    supvisors_ctx.stopper.check_stopping.return_value = True
+    mocked_start.return_value = False
+    mocked_stop.return_value = True
     result = state.next()
     assert result == SupvisorsStates.CONCILIATION
     # consider that no starting or stopping is in progress
-    supvisors_ctx.starter.check_starting.return_value = True
-    supvisors_ctx.stopper.check_stopping.return_value = True
+    mocked_start.return_value = True
+    mocked_stop.return_value = True
     # if local address and master address are RUNNING and conflict still detected, re-enter CONCILIATION
     mocker.patch.object(supvisors_ctx.context, 'conflicting', return_value=True)
     mocked_enter = mocker.patch.object(state, 'enter')
@@ -320,52 +249,65 @@ def test_conciliation_state(mocker, supvisors_ctx):
     state.exit()
 
 
-def test_restarting_state(mocker, supvisors_ctx):
+def test_master_restarting_state(mocker, supvisors_ctx):
     """ Test the Restarting state of the fsm. """
-    state = RestartingState(supvisors_ctx)
+    mocked_starter = supvisors_ctx.starter.abort
+    mocked_stopper = supvisors_ctx.stopper.stop_applications
+    mocked_stopping = supvisors_ctx.stopper.is_stopping_completed
+    # create instance to test
+    state = MasterRestartingState(supvisors_ctx)
     assert isinstance(state, AbstractState)
     # test enter method: starting ang stopping in progress are aborted
-    mocked_starter = mocker.patch.object(supvisors_ctx.starter, 'abort')
-    mocked_stopper = mocker.patch.object(supvisors_ctx.stopper, 'stop_applications')
     state.enter()
     assert mocked_starter.call_count == 1
     assert mocked_stopper.call_count == 1
+    # test next method if check_nodes return something
+    mocker.patch.object(state, 'check_nodes', return_value=SupvisorsStates.INITIALIZATION)
+    assert state.next() == SupvisorsStates.SHUTDOWN
+    assert not mocked_stopping.called
+    # test next method if check_nodes return nothing
+    state.check_nodes.return_value = None
     # test next method: all processes are stopped
-    mocked_stopping = mocker.patch.object(supvisors_ctx.stopper, 'check_stopping', return_value=True)
+    mocked_stopping.return_value = True
     result = state.next()
     assert result == SupvisorsStates.SHUTDOWN
     mocked_stopping.return_value = False
     result = state.next()
     assert result == SupvisorsStates.RESTARTING
     # test exit method: call to pusher send_restart for all addresses
-    mocked_apply = mocker.patch.object(state, 'apply_nodes_func')
+    assert not state.supvisors.zmq.pusher.send_restart.called
     state.exit()
-    assert mocked_apply.call_count == 1
-    assert mocked_apply.call_args == call(supvisors_ctx.zmq.pusher.send_restart)
+    assert state.supvisors.zmq.pusher.send_restart.call_args_list == [call('127.0.0.1')]
 
 
-def test_shutting_down_state(mocker, supvisors_ctx):
+def test_master_shutting_down_state(mocker, supvisors_ctx):
     """ Test the ShuttingDown state of the fsm. """
-    state = ShuttingDownState(supvisors_ctx)
+    mocked_starter = supvisors_ctx.starter.abort
+    mocked_stopper = supvisors_ctx.stopper.stop_applications
+    mocked_stopping = supvisors_ctx.stopper.is_stopping_completed
+    # create instance to test
+    state = MasterShuttingDownState(supvisors_ctx)
     assert isinstance(state, AbstractState)
     # test enter method: starting ang stopping in progress are aborted
-    mocked_starter = mocker.patch.object(supvisors_ctx.starter, 'abort')
-    mocked_stopper = mocker.patch.object(supvisors_ctx.stopper, 'stop_applications')
     state.enter()
     assert mocked_starter.call_count == 1
     assert mocked_stopper.call_count == 1
+    # test next method if check_nodes return something
+    mocker.patch.object(state, 'check_nodes', return_value=SupvisorsStates.INITIALIZATION)
+    assert state.next() == SupvisorsStates.SHUTDOWN
+    assert not mocked_stopping.called
+    # test next method if check_nodes return nothing
+    state.check_nodes.return_value = None
     # test next method: all processes are stopped
-    mocked_stopping = mocker. patch.object(supvisors_ctx.stopper, 'check_stopping', return_value=True)
     result = state.next()
     assert result == SupvisorsStates.SHUTDOWN
     mocked_stopping.return_value = False
     result = state.next()
     assert result == SupvisorsStates.SHUTTING_DOWN
-    # test exit method: call to pusher send_shutdown for all addresses
-    mocked_apply = mocker.patch.object(state, 'apply_nodes_func')
+    # test exit method: call to pusher send_restart for all addresses
+    assert not state.supvisors.zmq.pusher.send_shutdown.called
     state.exit()
-    assert mocked_apply.call_count == 1
-    assert mocked_apply.call_args == call(supvisors_ctx.zmq.pusher.send_shutdown)
+    assert state.supvisors.zmq.pusher.send_shutdown.call_args_list == [call('127.0.0.1')]
 
 
 def test_shutdown_state(supvisors_ctx):
@@ -378,6 +320,64 @@ def test_shutdown_state(supvisors_ctx):
     state.exit()
 
 
+def test_slave_main_state(mocker, supvisors_ctx):
+    """ Test the SlaveMain state of the fsm. """
+    # create instance to test
+    state = SlaveMainState(supvisors_ctx)
+    assert isinstance(state, AbstractState)
+    # no enter implementation. just call it without test
+    state.enter()
+    # test next method if check_nodes return something
+    mocker.patch.object(state, 'check_nodes', return_value=SupvisorsStates.INITIALIZATION)
+    assert state.next() == SupvisorsStates.INITIALIZATION
+    # test next method if check_nodes return nothing
+    state.check_nodes.return_value = None
+    # test next method: no next state proposed
+    assert state.next() is None
+    # no exit implementation. just call it without test
+    state.exit()
+
+
+def test_slave_restarting_state(mocker, supvisors_ctx):
+    """ Test the SlaveRestarting state of the fsm. """
+    # create instance to test
+    state = SlaveRestartingState(supvisors_ctx)
+    assert isinstance(state, AbstractState)
+    # no enter implementation. just call it without test
+    state.enter()
+    # test next method if check_nodes return something
+    mocker.patch.object(state, 'check_nodes', return_value=SupvisorsStates.INITIALIZATION)
+    assert state.next() == SupvisorsStates.SHUTDOWN
+    # test next method if check_nodes return nothing
+    state.check_nodes.return_value = None
+    # test next method: no next state proposed
+    assert state.next() is None
+    # test exit
+    assert not state.supvisors.zmq.pusher.send_restart.called
+    state.exit()
+    assert state.supvisors.zmq.pusher.send_restart.call_args_list == [call('127.0.0.1')]
+
+
+def test_slave_shutting_down_state(mocker, supvisors_ctx):
+    """ Test the SlaveShuttingDown state of the fsm. """
+    # create instance to test
+    state = SlaveShuttingDownState(supvisors_ctx)
+    assert isinstance(state, SlaveRestartingState)
+    # no enter implementation. just call it without test
+    state.enter()
+    # test next method if check_nodes return something
+    mocker.patch.object(state, 'check_nodes', return_value=SupvisorsStates.INITIALIZATION)
+    assert state.next() == SupvisorsStates.SHUTDOWN
+    # test next method if check_nodes return nothing
+    state.check_nodes.return_value = None
+    # test next method: no next state proposed
+    assert state.next() is None
+    # test exit
+    assert not state.supvisors.zmq.pusher.send_shutdown.called
+    state.exit()
+    assert state.supvisors.zmq.pusher.send_shutdown.call_args_list == [call('127.0.0.1')]
+
+
 @pytest.fixture
 def fsm(supvisors):
     """ Create the FiniteStateMachine instance to test. """
@@ -410,14 +410,23 @@ def test_serial(fsm):
 
 
 # Patch all state events
-STATES = [cls.__name__ for cls in FiniteStateMachine._StateInstances.values()]
+MASTER_STATES = [cls.__name__ for cls in FiniteStateMachine._MasterStateInstances.values()]
+SLAVE_STATES = ['InitializationState', 'SlaveMainState', 'SlaveRestartingState', 'SlaveShuttingDownState',
+                'ShutdownState']
 EVENTS = ['enter', 'next', 'exit']
 
 
 @pytest.fixture
-def mock_events(mocker):
-    return [mocker.patch('supvisors.statemachine.%s.%s' % (cls, evt))
-            for cls in STATES
+def mock_master_events(mocker):
+    return [mocker.patch('supvisors.statemachine.%s.%s' % (cls, evt), return_value=None)
+            for cls in MASTER_STATES
+            for evt in EVENTS]
+
+
+@pytest.fixture
+def mock_slave_events(mocker):
+    return [mocker.patch('supvisors.statemachine.%s.%s' % (cls, evt), return_value=None)
+            for cls in SLAVE_STATES
             for evt in EVENTS]
 
 
@@ -428,75 +437,111 @@ def compare_calls(call_counts, mock_events):
         mocked.reset_mock()
 
 
-def test_simple_set_state(fsm, mock_events):
+def test_master_simple_set_state(fsm, mock_master_events):
     """ Test single transitions of the state machine using set_state method.
-    Beware of the fixture sequence.
-    If mock_events is set before fsm, mocks will capture the calls triggered from the FiniteStateMachine constructor.
+    As it is a Master FSM, transitions are checked.
+    Beware of the fixture sequence. If mock_master_events is set before fsm, mocks would capture the calls triggered
+    from the FiniteStateMachine constructor.
     """
     instance_ref = fsm.instance
     # test set_state with identical state parameter
     fsm.set_state(SupvisorsStates.INITIALIZATION)
-    compare_calls([0, 0, 0, 0, 0, 0], mock_events)
+    compare_calls([0, 0, 0, 0, 0, 0], mock_master_events)
+    assert fsm.instance is instance_ref
+    assert fsm.state == SupvisorsStates.INITIALIZATION
+    # test set_state with not authorized transition for master
+    fsm.context._is_master = True
+    fsm.set_state(SupvisorsStates.OPERATION)
+    compare_calls([0, 0, 0, 0, 0, 0], mock_master_events)
+    assert fsm.instance is instance_ref
+    assert fsm.state == SupvisorsStates.INITIALIZATION
+    # test set_state with authorized transition
+    fsm.set_state(SupvisorsStates.DEPLOYMENT)
+    compare_calls([0, 0, 1, 1, 1, 0], mock_master_events)
+    assert fsm.instance is not instance_ref
+    assert fsm.state == SupvisorsStates.DEPLOYMENT
+
+
+def test_slave_simple_set_state(fsm, mock_slave_events):
+    """ Test single transitions of the state machine using set_state method.
+    Same transition rules apply.
+    """
+    instance_ref = fsm.instance
+    # test set_state with identical state parameter
+    fsm.set_state(SupvisorsStates.INITIALIZATION)
+    compare_calls([0, 0, 0, 0, 0, 0], mock_slave_events)
     assert fsm.instance is instance_ref
     assert fsm.state == SupvisorsStates.INITIALIZATION
     # test set_state with not authorized transition
     fsm.set_state(SupvisorsStates.OPERATION)
-    compare_calls([0, 0, 0, 0, 0, 0], mock_events)
+    compare_calls([0, 0, 0, 0, 0, 0], mock_slave_events)
     assert fsm.instance is instance_ref
     assert fsm.state == SupvisorsStates.INITIALIZATION
     # test set_state with authorized transition
     fsm.set_state(SupvisorsStates.DEPLOYMENT)
-    compare_calls([0, 0, 1, 1, 1, 0], mock_events)
+    compare_calls([0, 0, 1, 1, 1, 0], mock_slave_events)
     assert fsm.instance is not instance_ref
     assert fsm.state == SupvisorsStates.DEPLOYMENT
+    # test set_state with unauthorized transition
+    fsm.set_state(SupvisorsStates.SHUTDOWN)
+    compare_calls([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], mock_slave_events)
+    assert fsm.state == SupvisorsStates.DEPLOYMENT
+    # test set_state with unauthorized transition but forced
+    fsm.set_state(SupvisorsStates.SHUTDOWN, True)
+    compare_calls([0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0], mock_slave_events)
+    assert fsm.state == SupvisorsStates.SHUTDOWN
 
 
-def test_complex_set_state(fsm, mock_events):
-    """ Test multiple transitions of the state machine using set_state method. """
-    mock_events[4].return_value = SupvisorsStates.OPERATION
+def test_master_complex_set_state(fsm, mock_master_events):
+    """ Test multiple transitions of the Master FSM using set_state method. """
+    mock_master_events[4].return_value = SupvisorsStates.OPERATION
     instance_ref = fsm.instance
     # test set_state with authorized transition
+    fsm.context._is_master = True
     fsm.set_state(SupvisorsStates.DEPLOYMENT)
-    compare_calls([0, 0, 1, 1, 1, 1, 1, 1, 0], mock_events)
+    compare_calls([0, 0, 1, 1, 1, 1, 1, 1, 0], mock_master_events)
     assert fsm.instance is not instance_ref
     assert fsm.state == SupvisorsStates.OPERATION
 
 
-def test_no_next(fsm, mock_events):
+def test_master_no_next(fsm, mock_master_events):
     """ Test no transition of the state machine using next_method. """
-    mock_events[1].return_value = SupvisorsStates.INITIALIZATION
+    mock_master_events[1].return_value = SupvisorsStates.INITIALIZATION
     instance_ref = fsm.instance
     # test set_state with authorized transition
+    fsm.context._is_master = True
     fsm.next()
-    compare_calls([0, 1, 0], mock_events)
+    compare_calls([0, 1, 0], mock_master_events)
     assert fsm.instance is instance_ref
     assert fsm.state == SupvisorsStates.INITIALIZATION
 
 
-def test_simple_next(fsm, mock_events):
+def test_master_simple_next(fsm, mock_master_events):
     """ Test single transition of the state machine using next_method. """
-    mock_events[1].return_value = SupvisorsStates.DEPLOYMENT
-    mock_events[4].return_value = SupvisorsStates.DEPLOYMENT
+    mock_master_events[1].return_value = SupvisorsStates.DEPLOYMENT
+    mock_master_events[4].return_value = SupvisorsStates.DEPLOYMENT
     instance_ref = fsm.instance
     # test set_state with authorized transition
+    fsm.context._is_master = True
     fsm.next()
-    compare_calls([0, 1, 1, 1, 1, 0], mock_events)
+    compare_calls([0, 1, 1, 1, 1, 0], mock_master_events)
     assert fsm.instance is not instance_ref
     assert fsm.state == SupvisorsStates.DEPLOYMENT
 
 
-def test_complex_next(fsm, mock_events):
+def test_master_complex_next(fsm, mock_master_events):
     """ Test multiple transitions of the state machine using next_method. """
-    mock_events[1].side_effect = [SupvisorsStates.DEPLOYMENT, SupvisorsStates.DEPLOYMENT]
-    mock_events[4].side_effect = [SupvisorsStates.OPERATION, SupvisorsStates.OPERATION]
-    mock_events[7].side_effect = [SupvisorsStates.CONCILIATION, SupvisorsStates.INITIALIZATION,
-                                  SupvisorsStates.RESTARTING]
-    mock_events[10].side_effect = [SupvisorsStates.OPERATION]
-    mock_events[13].return_value = SupvisorsStates.SHUTDOWN
+    mock_master_events[1].side_effect = [SupvisorsStates.DEPLOYMENT, SupvisorsStates.DEPLOYMENT]
+    mock_master_events[4].side_effect = [SupvisorsStates.OPERATION, SupvisorsStates.OPERATION]
+    mock_master_events[7].side_effect = [SupvisorsStates.CONCILIATION, SupvisorsStates.INITIALIZATION,
+                                         SupvisorsStates.RESTARTING]
+    mock_master_events[10].side_effect = [SupvisorsStates.OPERATION]
+    mock_master_events[13].return_value = SupvisorsStates.SHUTDOWN
     instance_ref = fsm.instance
     # test set_state with authorized transition
+    fsm.context._is_master = True
     fsm.next()
-    compare_calls([1, 2, 2, 2, 2, 2, 3, 3, 3, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0], mock_events)
+    compare_calls([1, 2, 2, 2, 2, 2, 3, 3, 3, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0], mock_master_events)
     assert fsm.instance is not instance_ref
     assert fsm.state == SupvisorsStates.SHUTDOWN
 
@@ -560,7 +605,7 @@ def test_process_event(mocker, fsm):
     assert not mocked_start_evt.called
     assert not mocked_stop_evt.called
     assert not mocked_add.called
-    # same test when process is found but local node is not master
+    # when process is found but local node is not master, only starter and stopper are called
     mocked_ctx.return_value = process
     mocked_ctx.reset_mock()
     fsm.supvisors.context._is_master = False
@@ -573,25 +618,48 @@ def test_process_event(mocker, fsm):
     mocked_ctx.reset_mock()
     mocked_start_evt.reset_mock()
     mocked_stop_evt.reset_mock()
-    # reset master
+    # process is found and local node is master. starter and stopper are called and the process has crashed
+    # test with running_failure_strategy set to CONTINUE / RESTART_PROCESS so job is not added to failure handler
     fsm.supvisors.context._is_master = True
-    fsm.on_process_event('10.0.0.1', {'process_name': 'dummy_proc'})
-    assert mocked_ctx.call_args_list == [call('10.0.0.1', {'process_name': 'dummy_proc'})]
-    assert mocked_start_evt.call_args_list == [call(process)]
-    assert mocked_stop_evt.call_args_list == [call(process)]
-    assert mocked_add.call_args_list == [call(process)]
-    # reset mocks
-    mocked_ctx.reset_mock()
-    mocked_start_evt.reset_mock()
-    mocked_stop_evt.reset_mock()
-    mocked_add.reset_mock()
+    for strategy in [RunningFailureStrategies.CONTINUE, RunningFailureStrategies.RESTART_PROCESS]:
+        process.rules.running_failure_strategy = strategy
+        fsm.on_process_event('10.0.0.1', {'process_name': 'dummy_proc'})
+        assert mocked_ctx.call_args_list == [call('10.0.0.1', {'process_name': 'dummy_proc'})]
+        assert mocked_start_evt.call_args_list == [call(process)]
+        assert mocked_stop_evt.call_args_list == [call(process)]
+        assert mocked_add.call_args_list == []
+        # reset mocks
+        mocked_ctx.reset_mock()
+        mocked_start_evt.reset_mock()
+        mocked_stop_evt.reset_mock()
+    # test with running_failure_strategy set to RESTART_APPLICATION / STOP_APPLICATION
+    # so job is added to failure handler
+    for strategy in [RunningFailureStrategies.RESTART_APPLICATION, RunningFailureStrategies.STOP_APPLICATION]:
+        process.rules.running_failure_strategy = strategy
+        fsm.on_process_event('10.0.0.1', {'process_name': 'dummy_proc'})
+        assert mocked_ctx.call_args_list == [call('10.0.0.1', {'process_name': 'dummy_proc'})]
+        assert mocked_start_evt.call_args_list == [call(process)]
+        assert mocked_stop_evt.call_args_list == [call(process)]
+        assert mocked_add.call_args_list == [call(process)]
+        # reset mocks
+        mocked_ctx.reset_mock()
+        mocked_start_evt.reset_mock()
+        mocked_stop_evt.reset_mock()
+        mocked_add.reset_mock()
     # test when process has not crashed
     process.crashed.return_value = False
-    fsm.on_process_event('10.0.0.1', {'process_name': 'dummy_proc'})
-    assert mocked_ctx.call_args_list == [call('10.0.0.1', {'process_name': 'dummy_proc'})]
-    assert mocked_start_evt.call_args_list == [call(process)]
-    assert mocked_stop_evt.call_args_list == [call(process)]
-    assert not mocked_add.called
+    for strategy in RunningFailureStrategies:
+        process.rules.running_failure_strategy = strategy
+        fsm.on_process_event('10.0.0.1', {'process_name': 'dummy_proc'})
+        assert mocked_ctx.call_args_list == [call('10.0.0.1', {'process_name': 'dummy_proc'})]
+        assert mocked_start_evt.call_args_list == [call(process)]
+        assert mocked_stop_evt.call_args_list == [call(process)]
+        assert not mocked_add.called
+        # reset mocks
+        mocked_ctx.reset_mock()
+        mocked_start_evt.reset_mock()
+        mocked_stop_evt.reset_mock()
+        mocked_add.reset_mock()
 
 
 def test_on_process_info(mocker, fsm):
@@ -603,37 +671,37 @@ def test_on_process_info(mocker, fsm):
     assert mocked_load.call_args == call('10.0.0.1', {'info': 'dummy_info'})
 
 
-def test_on_state_event(fsm):
+def test_on_state_event(mocker, fsm):
     """ Test the actions triggered in state machine upon reception of a Master state event. """
+    mocked_state = mocker.patch.object(fsm, 'set_state')
     fsm.context.master_node_name = '10.0.0.1'
-    assert not fsm.supvisors.context.master_operational
     # test event not sent by Master node
     for state in SupvisorsStates:
         payload = {'statecode': state}
         fsm.on_state_event('10.0.0.2', payload)
-        assert not fsm.supvisors.context.master_operational
+        assert not mocked_state.called
     # test event sent by Master node
     for state in SupvisorsStates:
         payload = {'statecode': state}
         fsm.on_state_event('10.0.0.1', payload)
-        assert fsm.supvisors.context.master_operational == (state == SupvisorsStates.OPERATION)
+        assert mocked_state.call_args_list == [call(state)]
+        mocker.resetall()
 
 
 def test_on_authorization(mocker, fsm):
     """ Test the actions triggered in state machine upon reception of an authorization event. """
     # prepare context
-    fsm.supvisors.context._master_node_name = ''
-    fsm.supvisors.context.master_operational = False
-    fsm.supvisors.context._is_master = False
-    mocker.patch.object(fsm.context, 'conflicting', return_value=False)
     mocked_auth = mocker.patch.object(fsm.context, 'on_authorization', return_value=False)
     # set initial condition
     assert fsm.supvisors.address_mapper.local_node_name == '127.0.0.1'
+    nodes = fsm.context.nodes
+    nodes['127.0.0.1']._state = AddressStates.RUNNING
+    nodes['10.0.0.5']._state = AddressStates.RUNNING
     # test rejected authorization
     fsm.on_authorization('10.0.0.1', False, '10.0.0.5', SupvisorsStates.INITIALIZATION)
     assert mocked_auth.call_args_list == [call('10.0.0.1', False)]
-    assert fsm.supvisors.context.master_node_name == ''
-    assert not fsm.supvisors.context.master_operational
+    assert fsm.state == SupvisorsStates.INITIALIZATION
+    assert fsm.context.master_node_name == ''
     assert not fsm.redeploy_mark
     # reset mocks
     mocked_auth.reset_mock()
@@ -641,72 +709,86 @@ def test_on_authorization(mocker, fsm):
     # test authorization when no master node provided
     fsm.on_authorization('10.0.0.1', True, '', SupvisorsStates.INITIALIZATION)
     assert mocked_auth.call_args == call('10.0.0.1', True)
-    assert fsm.supvisors.context.master_node_name == ''
-    assert not fsm.supvisors.context.master_operational
+    assert fsm.state == SupvisorsStates.INITIALIZATION
+    assert fsm.context.master_node_name == ''
     assert not fsm.redeploy_mark
     # reset mocks
     mocked_auth.reset_mock()
     # test authorization and master node assignment
     fsm.on_authorization('10.0.0.1', True, '10.0.0.5', SupvisorsStates.INITIALIZATION)
     assert mocked_auth.call_args == call('10.0.0.1', True)
-    assert fsm.supvisors.context._master_node_name == '10.0.0.5'
-    assert not fsm.supvisors.context.master_operational
+    assert fsm.state == SupvisorsStates.INITIALIZATION
+    assert fsm.context.master_node_name == '10.0.0.5'
     assert not fsm.redeploy_mark
     # reset mocks
     mocked_auth.reset_mock()
     # test authorization and master node operational
     fsm.on_authorization('10.0.0.5', True, '10.0.0.5', SupvisorsStates.OPERATION)
     assert mocked_auth.call_args == call('10.0.0.5', True)
-    assert fsm.supvisors.context.master_node_name == '10.0.0.5'
-    assert fsm.supvisors.context.master_operational
+    assert fsm.state == SupvisorsStates.OPERATION
+    assert fsm.context._master_node_name == '10.0.0.5'
     assert not fsm.redeploy_mark
     # reset mocks
     mocked_auth.reset_mock()
     # test authorization and master node conflict
     fsm.on_authorization('10.0.0.3', True, '10.0.0.4', SupvisorsStates.OPERATION)
     assert mocked_auth.call_args == call('10.0.0.3', True)
-    assert fsm.supvisors.context.master_node_name == '10.0.0.5'
     assert fsm.state == SupvisorsStates.INITIALIZATION
-    assert fsm.supvisors.context.master_operational
+    assert fsm.context.master_node_name == ''
     assert not fsm.redeploy_mark
     # change context while instance is not master
-    nodes = fsm.supvisors.context.nodes
     nodes['127.0.0.1']._state = AddressStates.RUNNING
     nodes['10.0.0.5']._state = AddressStates.RUNNING
-    # as master is operational, automatic transition to OPERATION
+    # as local is not master is operational, no automatic transition
     fsm.set_state(SupvisorsStates.DEPLOYMENT)
-    assert fsm.state == SupvisorsStates.OPERATION
+    assert fsm.state == SupvisorsStates.DEPLOYMENT
     # set current instance as master
     fsm.supvisors.context._is_master = True
     # test authorization when no master node provided
     fsm.on_authorization('10.0.0.4', True, '', SupvisorsStates.INITIALIZATION)
     assert mocked_auth.call_args == call('10.0.0.4', True)
-    assert fsm.state == SupvisorsStates.OPERATION
+    assert fsm.state == SupvisorsStates.DEPLOYMENT
     assert fsm.supvisors.context.master_node_name == '10.0.0.5'
-    assert fsm.supvisors.context.master_operational
     assert fsm.redeploy_mark
     # test authorization and master node conflict
     fsm.on_authorization('10.0.0.5', True, '10.0.0.4', SupvisorsStates.OPERATION)
     assert mocked_auth.call_args == call('10.0.0.5', True)
     assert fsm.state == SupvisorsStates.INITIALIZATION
     assert fsm.supvisors.context.master_node_name == ''
-    assert not fsm.supvisors.context.master_operational
-    assert not fsm.redeploy_mark
+    assert fsm.redeploy_mark
 
 
 def test_restart_event(mocker, fsm):
     """ Test the actions triggered in state machine upon reception of a restart event. """
     # inject restart event and test call to fsm set_state RESTARTING
     mocked_fsm = mocker.patch.object(fsm, 'set_state')
+    mocked_zmq = fsm.supvisors.zmq.pusher.send_restart_all
+    fsm.supvisors.context.master_node_name = '10.0.0.1'
+    # test when not master
+    fsm.on_restart()
+    assert not mocked_fsm.called
+    assert mocked_zmq.call_args_list == [call('10.0.0.1')]
+    mocked_zmq.reset_mock()
+    # test when master
+    fsm.context._is_master = True
     fsm.on_restart()
-    assert mocked_fsm.call_count == 1
-    assert mocked_fsm.call_args == call(SupvisorsStates.RESTARTING)
+    assert not mocked_zmq.called
+    assert mocked_fsm.call_args_list == [call(SupvisorsStates.RESTARTING)]
 
 
 def test_shutdown_event(mocker, fsm):
     """ Test the actions triggered in state machine upon reception of a shutdown event. """
     # inject shutdown event and test call to fsm set_state SHUTTING_DOWN
     mocked_fsm = mocker.patch.object(fsm, 'set_state')
+    mocked_zmq = fsm.supvisors.zmq.pusher.send_shutdown_all
+    fsm.supvisors.context.master_node_name = '10.0.0.1'
+    # test when not master
+    fsm.on_shutdown()
+    assert not mocked_fsm.called
+    assert mocked_zmq.call_args_list == [call('10.0.0.1')]
+    mocked_zmq.reset_mock()
+    # test when master
+    fsm.context._is_master = True
     fsm.on_shutdown()
-    assert mocked_fsm.call_count == 1
-    assert mocked_fsm.call_args == call(SupvisorsStates.SHUTTING_DOWN)
+    assert not mocked_zmq.called
+    assert mocked_fsm.call_args_list == [call(SupvisorsStates.SHUTTING_DOWN)]
diff --git a/supvisors/tests/test_strategy.py b/supvisors/tests/test_strategy.py
index 20c52253..670aa595 100644
--- a/supvisors/tests/test_strategy.py
+++ b/supvisors/tests/test_strategy.py
@@ -22,7 +22,6 @@
 from unittest.mock import Mock, call
 
 from supvisors.address import AddressStatus
-from supvisors.process import ProcessStatus
 from supvisors.strategy import *
 from supvisors.ttypes import AddressStates, ConciliationStrategies, RunningFailureStrategies, StartingStrategies
 
@@ -30,7 +29,7 @@
 def mock_node(mocker, status: AddressStatus, node_state: AddressStates, load: int):
     """ Mock the AddressStatus. """
     status._state = node_state
-    mocker.patch.object(status, 'get_load', return_value=load)
+    mocker.patch.object(status, 'get_loading', return_value=load)
 
 
 @pytest.fixture
@@ -45,48 +44,56 @@ def filled_nodes(mocker, supvisors):
     return supvisors
 
 
+@pytest.fixture
+def load_request_map():
+    return {'127.0.0.1': 0, '10.0.0.3': 10, '10.0.0.5': 20}
+
+
 @pytest.fixture
 def starting_strategy(filled_nodes):
     """ Create the AbstractStartingStrategy instance to test. """
     return AbstractStartingStrategy(filled_nodes)
 
 
-def test_is_loading_valid(starting_strategy):
+def test_is_loading_valid(starting_strategy, load_request_map):
     """ Test the validity of an address with an additional loading. """
     # test unknown address
-    assert starting_strategy.is_loading_valid('10.0.0.0', 1) == (False, 0)
+    assert starting_strategy.is_loading_valid('10.0.0.0', 1, load_request_map) == (False, 0)
     # test not RUNNING address
-    assert starting_strategy.is_loading_valid('10.0.0.1', 1) == (False, 0)
-    assert starting_strategy.is_loading_valid('10.0.0.2', 1) == (False, 0)
-    assert starting_strategy.is_loading_valid('10.0.0.4', 1) == (False, 0)
+    assert starting_strategy.is_loading_valid('10.0.0.1', 1, load_request_map) == (False, 0)
+    assert starting_strategy.is_loading_valid('10.0.0.2', 1, load_request_map) == (False, 0)
+    assert starting_strategy.is_loading_valid('10.0.0.4', 1, load_request_map) == (False, 0)
     # test loaded RUNNING address
-    assert starting_strategy.is_loading_valid('127.0.0.1', 55) == (False, 50)
-    assert starting_strategy.is_loading_valid('10.0.0.3', 85) == (False, 20)
-    assert starting_strategy.is_loading_valid('10.0.0.5', 25) == (False, 80)
+    assert starting_strategy.is_loading_valid('127.0.0.1', 55, load_request_map) == (False, 50)
+    assert starting_strategy.is_loading_valid('10.0.0.3', 85, load_request_map) == (False, 30)
+    assert starting_strategy.is_loading_valid('10.0.0.5', 25, load_request_map) == (False, 100)
     # test not loaded RUNNING address
-    assert starting_strategy.is_loading_valid('127.0.0.1', 45) == (True, 50)
-    assert starting_strategy.is_loading_valid('10.0.0.3', 75) == (True, 20)
-    assert starting_strategy.is_loading_valid('10.0.0.5', 15) == (True, 80)
+    assert starting_strategy.is_loading_valid('127.0.0.1', 45, load_request_map) == (True, 50)
+    assert starting_strategy.is_loading_valid('10.0.0.3', 65, load_request_map) == (True, 30)
+    assert starting_strategy.is_loading_valid('10.0.0.5', 0, load_request_map) == (True, 100)
 
 
-def test_get_loading_and_validity(starting_strategy):
+def test_get_loading_and_validity(starting_strategy, load_request_map):
     """ Test the determination of the valid addresses with an additional loading. """
     # test valid addresses with different additional loadings
     node_names = starting_strategy.supvisors.address_mapper.node_names
     # first test
     expected = {'127.0.0.1': (True, 50), '10.0.0.1': (False, 0), '10.0.0.2': (False, 0),
-                '10.0.0.3': (True, 20), '10.0.0.4': (False, 0), '10.0.0.5': (True, 80)}
-    assert starting_strategy.get_loading_and_validity(node_names, 15) == expected
+                '10.0.0.3': (True, 30), '10.0.0.4': (False, 0), '10.0.0.5': (False, 100)}
+    assert starting_strategy.get_loading_and_validity(node_names, 15, load_request_map) == expected
     # second test
     expected = {'127.0.0.1': (True, 50), '10.0.0.1': (False, 0), '10.0.0.2': (False, 0),
-                '10.0.0.3': (True, 20), '10.0.0.4': (False, 0), '10.0.0.5': (False, 80)}
-    assert starting_strategy.get_loading_and_validity(starting_strategy.supvisors.context.nodes.keys(), 45) == expected
+                '10.0.0.3': (True, 30), '10.0.0.4': (False, 0), '10.0.0.5': (False, 100)}
+    assert starting_strategy.get_loading_and_validity(starting_strategy.supvisors.context.nodes.keys(), 45,
+                                                      load_request_map) == expected
     # third test
-    expected = {'127.0.0.1': (False, 50), '10.0.0.3': (True, 20), '10.0.0.5': (False, 80)}
-    assert starting_strategy.get_loading_and_validity(['127.0.0.1', '10.0.0.3', '10.0.0.5'], 75) == expected
+    expected = {'127.0.0.1': (False, 50), '10.0.0.3': (True, 30), '10.0.0.5': (False, 100)}
+    assert starting_strategy.get_loading_and_validity(['127.0.0.1', '10.0.0.3', '10.0.0.5'], 65,
+                                                      load_request_map) == expected
     # fourth test
-    expected = {'127.0.0.1': (False, 50), '10.0.0.3': (False, 20), '10.0.0.5': (False, 80)}
-    assert starting_strategy.get_loading_and_validity(['127.0.0.1', '10.0.0.3', '10.0.0.5'], 85) == expected
+    expected = {'127.0.0.1': (False, 50), '10.0.0.3': (False, 30), '10.0.0.5': (False, 100)}
+    assert starting_strategy.get_loading_and_validity(['127.0.0.1', '10.0.0.3', '10.0.0.5'], 85,
+                                                      load_request_map) == expected
 
 
 def test_sort_valid_by_loading(starting_strategy):
@@ -104,68 +111,83 @@ def test_sort_valid_by_loading(starting_strategy):
     assert starting_strategy.sort_valid_by_loading(parameters) == []
 
 
-def test_config_strategy(filled_nodes):
+def test_abstract_get_node(starting_strategy):
+    """ Test that the AbstractStartingStrategy.get_node method is not implemented. """
+    node_names = starting_strategy.supvisors.address_mapper.node_names
+    with pytest.raises(NotImplementedError):
+        starting_strategy.get_node(node_names, 0, {})
+
+
+def test_config_strategy(filled_nodes, load_request_map):
     """ Test the choice of an address according to the CONFIG strategy. """
     strategy = ConfigStrategy(filled_nodes)
     # test CONFIG strategy with different values
     node_names = filled_nodes.address_mapper.node_names
-    assert strategy.get_node(node_names, 15) == '127.0.0.1'
-    assert strategy.get_node(node_names, 45) == '127.0.0.1'
-    assert strategy.get_node(node_names, 75) == '10.0.0.3'
-    assert strategy.get_node(node_names, 85) is None
+    assert strategy.get_node(node_names, 0, load_request_map) == '127.0.0.1'
+    assert strategy.get_node(node_names, 15, load_request_map) == '127.0.0.1'
+    assert strategy.get_node(node_names, 45, load_request_map) == '127.0.0.1'
+    assert strategy.get_node(node_names, 65, load_request_map) == '10.0.0.3'
+    assert strategy.get_node(node_names, 85, load_request_map) is None
 
 
-def test_less_loaded_strategy(filled_nodes):
+def test_less_loaded_strategy(filled_nodes, load_request_map):
     """ Test the choice of an address according to the LESS_LOADED strategy. """
     strategy = LessLoadedStrategy(filled_nodes)
     # test LESS_LOADED strategy with different values
     node_names = filled_nodes.address_mapper.node_names
-    assert strategy.get_node(node_names, 15) == '10.0.0.3'
-    assert strategy.get_node(node_names, 45) == '10.0.0.3'
-    assert strategy.get_node(node_names, 75) == '10.0.0.3'
-    assert strategy.get_node(node_names, 85) is None
+    assert strategy.get_node(node_names, 0, load_request_map) == '10.0.0.3'
+    assert strategy.get_node(node_names, 15, load_request_map) == '10.0.0.3'
+    assert strategy.get_node(node_names, 45, load_request_map) == '10.0.0.3'
+    assert strategy.get_node(node_names, 65, load_request_map) == '10.0.0.3'
+    assert strategy.get_node(node_names, 85, load_request_map) is None
 
 
-def test_most_loaded_strategy(filled_nodes):
+def test_most_loaded_strategy(filled_nodes, load_request_map):
     """ Test the choice of an address according to the MOST_LOADED strategy. """
     strategy = MostLoadedStrategy(filled_nodes)
     # test MOST_LOADED strategy with different values
     node_names = filled_nodes.address_mapper.node_names
-    assert strategy.get_node(node_names, 15) == '10.0.0.5'
-    assert strategy.get_node(node_names, 45) == '127.0.0.1'
-    assert strategy.get_node(node_names, 75) == '10.0.0.3'
-    assert strategy.get_node(node_names, 85) is None
+    assert strategy.get_node(node_names, 0, load_request_map) == '10.0.0.5'
+    assert strategy.get_node(node_names, 15, load_request_map) == '127.0.0.1'
+    assert strategy.get_node(node_names, 45, load_request_map) == '127.0.0.1'
+    assert strategy.get_node(node_names, 65, load_request_map) == '10.0.0.3'
+    assert strategy.get_node(node_names, 85, load_request_map) is None
 
 
-def test_local_strategy(filled_nodes):
+def test_local_strategy(filled_nodes, load_request_map):
     """ Test the choice of an address according to the LOCAL strategy. """
     strategy = LocalStrategy(filled_nodes)
     # test LOCAL strategy with different values
     node_names = filled_nodes.address_mapper.node_names
     assert strategy.supvisors.address_mapper.local_node_name == '127.0.0.1'
-    assert strategy.get_node(node_names, 15) == '127.0.0.1'
-    assert strategy.get_node(node_names, 45) == '127.0.0.1'
-    assert strategy.get_node(node_names, 75) is None
+    assert strategy.get_node(node_names, 0, load_request_map) == '127.0.0.1'
+    assert strategy.get_node(node_names, 15, load_request_map) == '127.0.0.1'
+    assert strategy.get_node(node_names, 45, load_request_map) == '127.0.0.1'
+    assert strategy.get_node(node_names, 65, load_request_map) is None
 
 
-def test_get_node(filled_nodes):
+def test_get_node(filled_nodes, load_request_map):
     """ Test the choice of a node according to a strategy. """
     # test CONFIG strategy
     node_names = filled_nodes.address_mapper.node_names
-    assert get_node(filled_nodes, StartingStrategies.CONFIG, node_names, 15) == '127.0.0.1'
-    assert get_node(filled_nodes, StartingStrategies.CONFIG, node_names, 75) == '10.0.0.3'
-    assert get_node(filled_nodes, StartingStrategies.CONFIG, node_names, 85) is None
+    assert get_node(filled_nodes, StartingStrategies.CONFIG, node_names, 0, load_request_map) == '127.0.0.1'
+    assert get_node(filled_nodes, StartingStrategies.CONFIG, node_names, 15, load_request_map) == '127.0.0.1'
+    assert get_node(filled_nodes, StartingStrategies.CONFIG, node_names, 65, load_request_map) == '10.0.0.3'
+    assert get_node(filled_nodes, StartingStrategies.CONFIG, node_names, 85, load_request_map) is None
     # test LESS_LOADED strategy
-    assert get_node(filled_nodes, StartingStrategies.LESS_LOADED, node_names, 15) == '10.0.0.3'
-    assert get_node(filled_nodes, StartingStrategies.LESS_LOADED, node_names, 75) == '10.0.0.3'
-    assert get_node(filled_nodes, StartingStrategies.LESS_LOADED, node_names, 85) is None
+    assert get_node(filled_nodes, StartingStrategies.LESS_LOADED, node_names, 0, load_request_map) == '10.0.0.3'
+    assert get_node(filled_nodes, StartingStrategies.LESS_LOADED, node_names, 15, load_request_map) == '10.0.0.3'
+    assert get_node(filled_nodes, StartingStrategies.LESS_LOADED, node_names, 65, load_request_map) == '10.0.0.3'
+    assert get_node(filled_nodes, StartingStrategies.LESS_LOADED, node_names, 85, load_request_map) is None
     # test MOST_LOADED strategy
-    assert get_node(filled_nodes, StartingStrategies.MOST_LOADED, node_names, 15) == '10.0.0.5'
-    assert get_node(filled_nodes, StartingStrategies.MOST_LOADED, node_names, 75) == '10.0.0.3'
-    assert get_node(filled_nodes, StartingStrategies.MOST_LOADED, node_names, 85) is None
+    assert get_node(filled_nodes, StartingStrategies.MOST_LOADED, node_names, 0, load_request_map) == '10.0.0.5'
+    assert get_node(filled_nodes, StartingStrategies.MOST_LOADED, node_names, 15, load_request_map) == '127.0.0.1'
+    assert get_node(filled_nodes, StartingStrategies.MOST_LOADED, node_names, 65, load_request_map) == '10.0.0.3'
+    assert get_node(filled_nodes, StartingStrategies.MOST_LOADED, node_names, 85, load_request_map) is None
     # test LOCAL strategy
-    assert get_node(filled_nodes, StartingStrategies.LOCAL, node_names, 15) == '127.0.0.1'
-    assert get_node(filled_nodes, StartingStrategies.LOCAL, node_names, 75) is None
+    assert get_node(filled_nodes, StartingStrategies.LOCAL, node_names, 0, load_request_map) == '127.0.0.1'
+    assert get_node(filled_nodes, StartingStrategies.LOCAL, node_names, 15, load_request_map) == '127.0.0.1'
+    assert get_node(filled_nodes, StartingStrategies.LOCAL, node_names, 65, load_request_map) is None
 
 
 def create_process_status(name, timed_nodes):
@@ -336,59 +358,224 @@ def test_running_abort(handler):
     compare_sets(handler)
 
 
-def test_add_job(handler):
-    """ Test the addition of a new job using a strategy. """
-    # create a dummy process
+@pytest.fixture
+def add_jobs():
+    """ Return common structure for add_jobs tests. """
     process_1 = Mock(application_name='dummy_application_A')
     process_2 = Mock(application_name='dummy_application_A')
     process_3 = Mock(application_name='dummy_application_B')
+    process_list = [process_1, process_2, process_3]
+    application_A = Mock(application_name='dummy_application_A',
+                         **{'get_start_sequenced_processes.return_value': [process_2]})
+    application_B = Mock(application_name='dummy_application_B')
+    application_list = [application_A, application_B]
+    return process_list, application_list
+
+
+def test_add_stop_application_job(handler, add_jobs):
+    """ Test the addition of a new job using a STOP_APPLICATION strategy. """
+    # create dummy applications and processes
+    proc_list, app_list = add_jobs
+    application_A, application_B = app_list
+    _, _, process_3 = proc_list
+    # check that stop_application_jobs is updated and that other jobs are cleaned
+    for job_set in [handler.restart_application_jobs, handler.start_application_jobs]:
+        job_set.update(app_list)
+    for job_set in [handler.restart_process_jobs, handler.start_process_jobs, handler.continue_process_jobs]:
+        job_set.update(proc_list)
+    assert application_A not in handler.stop_application_jobs
+    handler.add_stop_application_job(application_A)
+    compare_sets(handler, stop_app={application_A}, restart_app={application_B}, start_app={application_B},
+                 restart_proc={process_3}, start_proc={process_3}, continue_proc={process_3})
+
+
+def test_add_restart_application_job(handler, add_jobs):
+    """ Test the addition of a new job using a RESTART_APPLICATION strategy. """
+    # create dummy applications and processes
+    proc_list, (application_A, application_B) = add_jobs
+    process_1, process_2, process_3 = proc_list
+    # check that restart_application_jobs is not updated when application is already in stop_application_jobs
+    assert application_A not in handler.restart_application_jobs
+    handler.stop_application_jobs.add(application_A)
+    handler.add_restart_application_job(application_A)
+    compare_sets(handler, stop_app={application_A})
+    # check that restart_application_jobs is not updated when application is already in start_application_jobs
+    handler.stop_application_jobs.discard(application_A)
+    handler.start_application_jobs.add(application_A)
+    handler.add_restart_application_job(application_A)
+    compare_sets(handler, start_app={application_A})
+    # check that restart_application_jobs is updated otherwise and that other jobs are cleaned
+    handler.start_application_jobs.discard(application_A)
+    for job_set in [handler.restart_process_jobs, handler.start_process_jobs, handler.continue_process_jobs]:
+        job_set.update(proc_list)
+    handler.add_restart_application_job(application_A)
+    expected_proc_set = {process_1, process_3}
+    compare_sets(handler, restart_app={application_A}, restart_proc=expected_proc_set,
+                 start_proc=expected_proc_set, continue_proc=expected_proc_set)
+
+
+def test_add_restart_process_job(add_jobs, handler):
+    """ Test the addition of a new job using a RESTART_PROCESS strategy. """
+    # create dummy applications and processes
+    (process_1, process_2, process_3), (application_A, application_B) = add_jobs
+    # check that add_restart_process_job is not updated when application is already in stop_application_jobs
+    compare_sets(handler)
+    assert process_1 not in handler.continue_process_jobs
+    handler.stop_application_jobs.add(application_A)
+    compare_sets(handler, stop_app={application_A})
+    handler.add_restart_process_job(application_A, process_1)
+    compare_sets(handler, stop_app={application_A})
+    handler.stop_application_jobs.discard(application_A)
+    # check that add_restart_process_job is not updated when application is already in restart_application_jobs
+    # or in start_application_jobs and in the application start sequence
+    handler.restart_application_jobs.add(application_A)
+    compare_sets(handler, restart_app={application_A})
+    handler.add_restart_process_job(application_A, process_2)
+    compare_sets(handler, restart_app={application_A})
+    handler.restart_application_jobs.discard(application_A)
+    handler.start_application_jobs.add(application_A)
+    compare_sets(handler, start_app={application_A})
+    handler.add_restart_process_job(application_A, process_2)
+    compare_sets(handler, start_app={application_A})
+    handler.start_application_jobs.discard(application_A)
+    # check that add_restart_process_job is not updated when process is already in start_process_jobs
+    handler.start_process_jobs.add(process_2)
+    compare_sets(handler, start_proc={process_2})
+    handler.add_restart_process_job(application_A, process_2)
+    compare_sets(handler, start_proc={process_2})
+    handler.start_process_jobs.discard(process_2)
+    # check that add_restart_process_job is updated when application is already in restart_application_jobs
+    # or in start_application_jobs and not in the application start sequence
+    handler.restart_application_jobs.add(application_A)
+    compare_sets(handler, restart_app={application_A})
+    handler.add_restart_process_job(application_A, process_1)
+    compare_sets(handler, restart_app={application_A}, restart_proc={process_1})
+    handler.restart_application_jobs.discard(application_A)
+    handler.restart_process_jobs.discard(process_1)
+    handler.start_application_jobs.add(application_A)
+    compare_sets(handler, start_app={application_A})
+    handler.add_restart_process_job(application_A, process_1)
+    compare_sets(handler, start_app={application_A}, restart_proc={process_1})
+
+
+def test_add_continue_process_job(add_jobs, handler):
+    """ Test the addition of a new job using a CONTINUE strategy. """
+    # create dummy applications and processes
+    (process_1, process_2, process_3), (application_A, application_B) = add_jobs
+    # check that continue_process_jobs is not updated when application is already in stop_application_jobs
+    compare_sets(handler)
+    assert process_1 not in handler.continue_process_jobs
+    handler.stop_application_jobs.add(application_A)
+    compare_sets(handler, stop_app={application_A})
+    handler.add_continue_process_job(application_A, process_1)
+    compare_sets(handler, stop_app={application_A})
+    handler.stop_application_jobs.discard(application_A)
+    # check that continue_process_jobs is not updated when application is already in restart_application_jobs
+    # or in start_application_jobs and in the application start sequence
+    handler.restart_application_jobs.add(application_A)
+    compare_sets(handler, restart_app={application_A})
+    handler.add_continue_process_job(application_A, process_2)
+    compare_sets(handler, restart_app={application_A})
+    handler.restart_application_jobs.discard(application_A)
+    handler.start_application_jobs.add(application_A)
+    compare_sets(handler, start_app={application_A})
+    handler.add_continue_process_job(application_A, process_2)
+    compare_sets(handler, start_app={application_A})
+    handler.start_application_jobs.discard(application_A)
+    # check that continue_process_jobs is not updated when process is already in restart_process_jobs
+    # or in start_process_jobs
+    handler.restart_process_jobs.add(process_2)
+    compare_sets(handler, restart_proc={process_2})
+    handler.add_continue_process_job(application_A, process_2)
+    compare_sets(handler, restart_proc={process_2})
+    handler.restart_process_jobs.discard(process_2)
+    handler.start_process_jobs.add(process_2)
+    compare_sets(handler, start_proc={process_2})
+    handler.add_continue_process_job(application_A, process_2)
+    compare_sets(handler, start_proc={process_2})
+    handler.start_process_jobs.discard(process_2)
+    # check that continue_process_jobs is updated when application is already in restart_application_jobs
+    # or in start_application_jobs and not in the application start sequence
+    handler.restart_application_jobs.add(application_A)
+    compare_sets(handler, restart_app={application_A})
+    handler.add_continue_process_job(application_A, process_1)
+    compare_sets(handler, restart_app={application_A}, continue_proc={process_1})
+    handler.restart_application_jobs.discard(application_A)
+    handler.continue_process_jobs.discard(process_1)
+    handler.start_application_jobs.add(application_A)
+    compare_sets(handler, start_app={application_A})
+    handler.add_continue_process_job(application_A, process_1)
+    compare_sets(handler, start_app={application_A}, continue_proc={process_1})
+
+
+def test_add_job(mocker, handler):
+    """ Test the addition of a new job using a strategy. """
+    mocker.patch.object(handler, 'add_stop_application_job')
+    mocker.patch.object(handler, 'add_restart_application_job')
+    mocker.patch.object(handler, 'add_restart_process_job')
+    mocker.patch.object(handler, 'add_continue_process_job')
+    # set context
+    process = Mock(application_name='dummy_application')
+    application = Mock(application_name='dummy_application')
+    handler.supvisors.context.applications = {'dummy_application': application}
     # test adding CONTINUE jobs
-    handler.add_job(RunningFailureStrategies.CONTINUE, process_1)
-    compare_sets(handler, continue_proc={process_1})
-    # test adding RESTART_PROCESS jobs
-    handler.add_job(RunningFailureStrategies.RESTART_PROCESS, process_2)
-    compare_sets(handler, restart_proc={process_2}, continue_proc={process_1})
-    # check process is removed from CONTINUE jobs when RESTART_PROCESS requested
-    handler.add_job(RunningFailureStrategies.RESTART_PROCESS, process_1)
-    compare_sets(handler, restart_proc={process_2, process_1})
+    handler.add_job(RunningFailureStrategies.CONTINUE, process)
+    assert not handler.add_stop_application_job.called
+    assert not handler.add_restart_application_job.called
+    assert not handler.add_restart_process_job.called
+    assert handler.add_continue_process_job.call_args_list == [call(application, process)]
+    mocker.resetall()
     # test adding RESTART_PROCESS jobs
-    handler.add_job(RunningFailureStrategies.RESTART_PROCESS, process_3)
-    compare_sets(handler, restart_proc={process_2, process_1, process_3})
-    # check no impact when adding a new process to RESTART_PROCESS jobs when process is already in there
-    handler.add_job(RunningFailureStrategies.RESTART_PROCESS, process_3)
-    compare_sets(handler, restart_proc={process_2, process_1, process_3})
+    handler.add_job(RunningFailureStrategies.RESTART_PROCESS, process)
+    assert not handler.add_stop_application_job.called
+    assert not handler.add_restart_application_job.called
+    assert handler.add_restart_process_job.call_args_list == [call(application, process)]
+    assert not handler.add_continue_process_job.called
+    mocker.resetall()
     # test adding RESTART_APPLICATION jobs
-    # check process is not added to RESTART_PROCESS jobs when application already in RESTART_APPLICATION jobs
-    # check process is not added to CONTINUE jobs when application already in RESTART_APPLICATION jobs
-    handler.add_job(RunningFailureStrategies.RESTART_APPLICATION, process_1)
-    handler.add_job(RunningFailureStrategies.RESTART_PROCESS, process_2)
-    handler.add_job(RunningFailureStrategies.CONTINUE, process_1)
-    compare_sets(handler, restart_app={'dummy_application_A'}, restart_proc={process_3})
-    # check process is removed from RESTART_APPLICATION jobs when STOP_APPLICATION requested
-    handler.add_job(RunningFailureStrategies.STOP_APPLICATION, process_2)
-    compare_sets(handler, stop_app={'dummy_application_A'}, restart_proc={process_3})
-    # check process is not added to RESTART_APPLICATION jobs when application already in STOP_APPLICATION jobs
-    handler.add_job(RunningFailureStrategies.RESTART_APPLICATION, process_2)
-    compare_sets(handler, stop_app={'dummy_application_A'}, restart_proc={process_3})
-    # check no impact when adding a new process to STOP_APPLICATION jobs when its application is already in there
-    handler.add_job(RunningFailureStrategies.STOP_APPLICATION, process_1)
-    compare_sets(handler, stop_app={'dummy_application_A'}, restart_proc={process_3})
-    # check process is not added to RESTART_PROCESS jobs when application already in STOP_APPLICATION jobs
-    handler.add_job(RunningFailureStrategies.RESTART_PROCESS, process_2)
-    compare_sets(handler, stop_app={'dummy_application_A'}, restart_proc={process_3})
-    # check process is not added to CONTINUE jobs when application already in STOP_APPLICATION jobs
-    handler.add_job(RunningFailureStrategies.CONTINUE, process_1)
-    compare_sets(handler, stop_app={'dummy_application_A'}, restart_proc={process_3})
+    handler.add_job(RunningFailureStrategies.RESTART_APPLICATION, process)
+    assert not handler.add_stop_application_job.called
+    assert handler.add_restart_application_job.call_args_list == [call(application)]
+    assert not handler.add_restart_process_job.called
+    assert not handler.add_continue_process_job.called
+    mocker.resetall()
+    # test adding STOP_APPLICATION jobs
+    handler.add_job(RunningFailureStrategies.STOP_APPLICATION, process)
+    assert handler.add_stop_application_job.call_args_list == [call(application)]
+    assert not handler.add_restart_application_job.called
+    assert not handler.add_restart_process_job.called
+    assert not handler.add_continue_process_job.called
+
 
 def test_add_default_job(mocker, handler):
     """ Test the addition of a new job using the strategy configured. """
-    # create a dummy process
-    process = Mock()
-    process.rules = Mock(running_failure_strategy=2)
-    # add a series of jobs
     mocked_add = mocker.patch.object(handler, 'add_job')
+    process = Mock(application_name='dummy_application')
+    application = Mock(**{'stopped.return_value': False, 'get_start_sequenced_processes.return_value': []})
+    handler.supvisors.context.applications['dummy_application'] = application
+    # add a series of jobs without using RESTART_PROCESS
+    for strategy in RunningFailureStrategies:
+        if strategy != RunningFailureStrategies.RESTART_PROCESS:
+            process.rules.running_failure_strategy = strategy
+            handler.add_default_job(process)
+            assert mocked_add.call_args_list == [call(strategy, process)]
+            mocker.resetall()
+    # test RESTART_PROCESS on an application still running
+    process.rules.running_failure_strategy = RunningFailureStrategies.RESTART_PROCESS
     handler.add_default_job(process)
-    assert mocked_add.call_args_list == [call(2, process)]
+    assert mocked_add.call_args_list == [call(RunningFailureStrategies.RESTART_PROCESS, process)]
+    mocker.resetall()
+    # test RESTART_PROCESS on a stopped application but with process out of start sequence
+    application.stopped.return_value = True
+    handler.add_default_job(process)
+    assert mocked_add.call_args_list == [call(RunningFailureStrategies.RESTART_PROCESS, process)]
+    mocker.resetall()
+    # test RESTART_PROCESS on a stopped application and with process in start sequence
+    # a second job is added
+    application.get_start_sequenced_processes.return_value = [process]
+    handler.add_default_job(process)
+    assert mocked_add.call_args_list == [call(RunningFailureStrategies.RESTART_PROCESS, process),
+                                         call(RunningFailureStrategies.RESTART_APPLICATION, process)]
 
 
 def test_get_job_applications(handler):
@@ -400,79 +587,119 @@ def test_get_job_applications(handler):
     assert handler.get_job_applications() == {'if', 'then', 'else'}
 
 
-def mocked_application(supvisors, application_name, stopped):
-    """ Return a mocked ApplicationStatus. """
-    application = Mock(application_name=application_name, **{'stopped.side_effect': [stopped, True]})
-    supvisors.context.applications[application_name] = application
-    return application
-
-
-def mocked_process(namespec, application_name, stopped):
-    """ Return a mocked ProcessStatus. """
-    return Mock(application_name=application_name, namespec=namespec, **{'stopped.side_effect': [stopped, True]})
+def test_trigger_stop_application_jobs(add_jobs, handler):
+    """ Test the triggering of stop application jobs. """
+    # create dummy applications and processes
+    _, app_list = add_jobs
+    application_A, application_B = app_list
+    # update context
+    compare_sets(handler)
+    handler.stop_application_jobs.update(app_list)
+    compare_sets(handler, stop_app=set(app_list))
+    # test start_process calls depending on process state and involvement in Starter
+    handler.trigger_stop_application_jobs({'dummy_application_B'})
+    compare_sets(handler, stop_app={application_B})
+    assert handler.supvisors.stopper.stop_application.call_args_list == [call(application_A)]
+
+
+def test_restart_application_jobs(add_jobs, handler):
+    """ Test the triggering of restart application jobs. """
+    # create dummy applications and processes
+    _, app_list = add_jobs
+    application_A, application_B = app_list
+    # update context
+    compare_sets(handler)
+    handler.restart_application_jobs.update(app_list)
+    compare_sets(handler, restart_app=set(app_list))
+    # test start_process calls depending on process state and involvement in Starter
+    handler.trigger_restart_application_jobs({'dummy_application_B'})
+    compare_sets(handler, restart_app={application_B}, start_app={application_A})
+    assert handler.supvisors.stopper.stop_application.call_args_list == [call(application_A)]
+
+
+def test_trigger_restart_process_jobs(add_jobs, handler):
+    """ Test the triggering of restart process jobs. """
+    # create dummy applications and processes
+    proc_list, _ = add_jobs
+    process_1, process_2, process_3 = proc_list
+    # update context
+    compare_sets(handler)
+    handler.restart_process_jobs.update(proc_list)
+    compare_sets(handler, restart_proc=set(proc_list))
+    # test start_process calls depending on process state and involvement in Starter
+    handler.trigger_restart_process_jobs({'dummy_application_B'})
+    compare_sets(handler, restart_proc={process_3}, start_proc={process_1, process_2})
+    handler.supvisors.stopper.stop_process.assert_has_calls([call(process_1), call(process_2)], any_order=True)
+    assert not call(process_3) in handler.supvisors.stopper.stop_process.call_args_list
+
+
+def test_trigger_start_application_jobs(add_jobs, handler):
+    """ Test the triggering of start application jobs. """
+    # create dummy applications and processes
+    _, app_list = add_jobs
+    application_A, application_B = app_list
+    # check that continue_process_jobs is not updated when application is already in stop_application_jobs
+    compare_sets(handler)
+    # update context
+    for application, stopped in zip(app_list, [True, False]):
+        application.stopped.return_value = stopped
+    compare_sets(handler)
+    handler.start_application_jobs.update(app_list)
+    compare_sets(handler, start_app=set(app_list))
+    # test start_application calls depending on application state and involvement in Starter
+    handler.trigger_start_application_jobs({'dummy_application_A'})
+    compare_sets(handler, start_app=set(app_list))
+    assert not handler.supvisors.starter.default_start_process.called
+    # test start_process calls depending on process state and involvement in Starter
+    handler.trigger_start_application_jobs({'dummy_application_B'})
+    compare_sets(handler, start_app={application_B})
+    assert handler.supvisors.starter.default_start_application.call_args_list == [call(application_A)]
+
+
+def test_trigger_start_process_jobs(add_jobs, handler):
+    """ Test the triggering of start process jobs. """
+    # create dummy applications and processes
+    proc_list, _ = add_jobs
+    process_1, process_2, process_3 = proc_list
+    # update context
+    for process, stopped in zip(proc_list, [True, False, True]):
+        process.stopped.return_value = stopped
+    compare_sets(handler)
+    handler.start_process_jobs.update(proc_list)
+    compare_sets(handler, start_proc=set(proc_list))
+    # test start_process calls depending on process state and involvement in Starter
+    handler.trigger_start_process_jobs({'dummy_application_B'})
+    compare_sets(handler, start_proc={process_2, process_3})
+    assert handler.supvisors.starter.default_start_process.call_args_list == [call(process_1)]
+
+
+def test_trigger_continue_process_jobs(add_jobs, handler):
+    """ Test the triggering of continue jobs. """
+    # create dummy applications and processes
+    proc_list, _ = add_jobs
+    # update context
+    compare_sets(handler)
+    handler.continue_process_jobs.update(proc_list)
+    compare_sets(handler, continue_proc=set(proc_list))
+    # check that continue_process_jobs is emptied after call to trigger_continue_process_jobs
+    handler.trigger_continue_process_jobs()
+    compare_sets(handler)
 
 
 def test_trigger_jobs(mocker, handler):
     """ Test the processing of jobs. """
-    # create mocked applications
-    stop_appli_A = mocked_application(handler.supvisors, 'stop_application_A', False)
-    stop_appli_B = mocked_application(handler.supvisors, 'stop_application_B', False)
-    restart_appli_A = mocked_application(handler.supvisors, 'restart_application_A', False)
-    restart_appli_B = mocked_application(handler.supvisors, 'restart_application_B', False)
-    start_appli_A = mocked_application(handler.supvisors, 'start_application_A', True)
-    start_appli_B = mocked_application(handler.supvisors, 'start_application_B', True)
-    # create mocked processes
-    restart_process_1 = mocked_process('restart_process_1', 'restart_application_1', False)
-    restart_process_2 = mocked_process('restart_process_2', 'restart_application_2', False)
-    start_process_1 = mocked_process('start_process_1', 'start_application_1', True)
-    start_process_2 = mocked_process('start_process_2', 'start_application_2', True)
-    continue_process = mocked_process('continue_process', 'continue_application', False)
-    # pre-fill sets
-    handler.stop_application_jobs = {'stop_application_A', 'stop_application_B'}
-    handler.restart_application_jobs = {'restart_application_A', 'restart_application_B'}
-    handler.restart_process_jobs = {restart_process_1, restart_process_2}
-    handler.continue_process_jobs = {continue_process}
-    handler.start_application_jobs = {start_appli_A, start_appli_B}
-    handler.start_process_jobs = {start_process_1, start_process_2}
-    # get patches to starter and stopper
-    mocked_stop_app = handler.supvisors.stopper.stop_application
-    mocked_start_app = handler.supvisors.starter.default_start_application
-    mocked_stop_proc = handler.supvisors.stopper.stop_process
-    mocked_start_proc = handler.supvisors.starter.default_start_process
-    # patch check_commander so that it is considered that applications are already being handled in Start / Stopper
-    application_list = {'stop_application_A', 'stop_application_B', 'restart_application_A', 'restart_application_B',
-                        'start_application_A', 'start_application_B', 'restart_application_1', 'restart_application_2',
-                        'start_application_1', 'start_application_2', 'continue_application'}
-    mocked_jobs = mocker.patch.object(handler, 'get_job_applications', return_value=application_list)
-    # test jobs trigger
-    handler.trigger_jobs()
-    print(mocked_start_app.call_args_list)
-    # check there has been no application calls
-    assert not mocked_stop_app.called
-    assert not mocked_start_app.called
-    assert not mocked_stop_proc.called
-    assert not mocked_start_proc.called
-    # check impact on sets
-    compare_sets(handler, stop_app={'stop_application_A', 'stop_application_B'},
-                 restart_app={'restart_application_A', 'restart_application_B'},
-                 start_app={start_appli_A, start_appli_B},
-                 restart_proc={restart_process_1, restart_process_2},
-                 start_proc={start_process_1, start_process_2})
-    # patch check_commander so that it is considered that applications are not being handled in Start / Stopper
-    mocked_jobs.return_value = set()
+    mocker.patch.object(handler, 'trigger_stop_application_jobs')
+    mocker.patch.object(handler, 'trigger_restart_application_jobs')
+    mocker.patch.object(handler, 'trigger_restart_process_jobs')
+    mocker.patch.object(handler, 'trigger_start_application_jobs')
+    mocker.patch.object(handler, 'trigger_start_process_jobs')
+    mocker.patch.object(handler, 'trigger_continue_process_jobs')
+    mocker.patch.object(handler, 'get_job_applications', return_value={'dummy_application_A'})
+    # test calls
     handler.trigger_jobs()
-    # check stop application calls
-    expected = [call(stop_appli_A), call(stop_appli_B), call(restart_appli_A), call(restart_appli_B)]
-    mocked_stop_app.assert_has_calls(expected, any_order=True)
-    # test start application calls
-    expected = [call(start_appli_A), call(start_appli_B)]
-    mocked_start_app.assert_has_calls(expected, any_order=True)
-    # test stop process calls
-    expected = [call(restart_process_1), call(restart_process_2)]
-    mocked_stop_proc.assert_has_calls(expected, any_order=True)
-    # test start process calls
-    expected = [call(start_process_1), call(start_process_2)]
-    mocked_start_proc.assert_has_calls(expected, any_order=True)
-    # check impact on sets
-    compare_sets(handler, start_app={restart_appli_A, restart_appli_B},
-                 start_proc={restart_process_1, restart_process_2})
+    assert handler.trigger_stop_application_jobs.call_args_list == [call({'dummy_application_A'})]
+    assert handler.trigger_restart_application_jobs.call_args_list == [call({'dummy_application_A'})]
+    assert handler.trigger_restart_process_jobs.call_args_list == [call({'dummy_application_A'})]
+    assert handler.trigger_start_application_jobs.call_args_list == [call({'dummy_application_A'})]
+    assert handler.trigger_start_process_jobs.call_args_list == [call({'dummy_application_A'})]
+    assert handler.trigger_continue_process_jobs.call_args_list == [call()]
diff --git a/supvisors/tests/test_supvisorsctl.py b/supvisors/tests/test_supvisorsctl.py
index 0f3f5494..b3d79794 100644
--- a/supvisors/tests/test_supvisorsctl.py
+++ b/supvisors/tests/test_supvisorsctl.py
@@ -21,7 +21,6 @@
 
 from unittest.mock import call, Mock
 
-from supervisor.loggers import LevelsByDescription
 from supervisor.supervisorctl import Controller
 from supervisor.xmlrpc import Faults
 
@@ -269,19 +268,21 @@ def test_application_rules(controller, plugin, mocked_check):
     mocked_appli.return_value = [{'application_name': 'appli_1'}, {'application_name': 'appli_2'},
                                  {'application_name': 'appli_3'}]
     mocked_rpc = plugin.supvisors().get_application_rules
-    returned_rules = [{'application_name': 'appli_1', 'managed': True, 'start_sequence': 2, 'stop_sequence': 3,
-                       'starting_failure_strategy': 2, 'running_failure_strategy': 1},
-                      {'application_name': 'appli_2', 'managed': True, 'start_sequence': 1, 'stop_sequence': 0,
-                       'starting_failure_strategy': 0, 'running_failure_strategy': 2},
-                      {'application_name': 'appli_3', 'managed': False, 'start_sequence': 0, 'stop_sequence': 10,
-                       'starting_failure_strategy': 2, 'running_failure_strategy': 0}]
-    # first possiblity: no argument
+    returned_rules = [{'application_name': 'appli_1', 'managed': True, 'distributed': True,
+                       'start_sequence': 2, 'stop_sequence': 3, 'starting_strategy': 'CONFIG',
+                       'starting_failure_strategy': 'ABORT', 'running_failure_strategy': 'CONTINUE'},
+                      {'application_name': 'appli_2', 'managed': True,
+                       'distributed': False, 'addresses': ['10.0.0.1', '10.0.0.2', '10.0.0.3'],
+                       'start_sequence': 1, 'stop_sequence': 0, 'starting_strategy': 'LESS_LOADED',
+                       'starting_failure_strategy': 'CONTINUE', 'running_failure_strategy': 'RESTART_APPLICATION'},
+                      {'application_name': 'appli_3', 'managed': False}]
+    # first possibility: no argument
     mocked_rpc.side_effect = returned_rules
     _check_call(controller, mocked_check, mocked_rpc, plugin.help_application_rules, plugin.do_application_rules,
                 '', [call('appli_1'), call('appli_2'), call('appli_3')])
     assert mocked_appli.call_args_list == [call(), call()]
     mocked_appli.reset_mock()
-    # second possiblity: use 'all'
+    # second possibility: use 'all'
     mocked_rpc.side_effect = returned_rules
     _check_call(controller, mocked_check, mocked_rpc, plugin.help_application_rules, plugin.do_application_rules,
                 'all', [call('appli_1'), call('appli_2'), call('appli_3')])
diff --git a/supvisors/tests/test_supvisorszmq.py b/supvisors/tests/test_supvisorszmq.py
index 403b4114..7a6c9995 100644
--- a/supvisors/tests/test_supvisorszmq.py
+++ b/supvisors/tests/test_supvisorszmq.py
@@ -122,7 +122,7 @@ def test_disconnection(supvisors, internal_publisher, internal_subscriber):
     internal_publisher.send_tick_event(payload)
     # check the reception of the tick event
     msg = internal_subscriber_receive(internal_subscriber, 'Tick')
-    assert msg == (InternalEventHeaders.TICK, local_node_name, payload)
+    assert msg == (InternalEventHeaders.TICK.value, local_node_name, payload)
     # test local disconnection
     internal_subscriber.disconnect([local_node_name])
     # send a tick event from the local publisher
@@ -141,7 +141,7 @@ def test_tick_event(supvisors, internal_publisher, internal_subscriber):
     internal_publisher.send_tick_event(payload)
     # check the reception of the tick event
     msg = internal_subscriber_receive(internal_subscriber, 'Tick')
-    assert msg == (InternalEventHeaders.TICK, local_node_name, payload)
+    assert msg == (InternalEventHeaders.TICK.value, local_node_name, payload)
 
 
 def test_process_event(supvisors, internal_publisher, internal_subscriber):
@@ -153,7 +153,7 @@ def test_process_event(supvisors, internal_publisher, internal_subscriber):
     internal_publisher.send_process_event(payload)
     # check the reception of the process event
     msg = internal_subscriber_receive(internal_subscriber, 'Process')
-    assert msg == (InternalEventHeaders.PROCESS, local_node_name, payload)
+    assert msg == (InternalEventHeaders.PROCESS.value, local_node_name, payload)
 
 
 def test_statistics(supvisors, internal_publisher, internal_subscriber):
@@ -165,7 +165,7 @@ def test_statistics(supvisors, internal_publisher, internal_subscriber):
     internal_publisher.send_statistics(payload)
     # check the reception of the statistics event
     msg = internal_subscriber_receive(internal_subscriber, 'Statistics')
-    assert msg == (InternalEventHeaders.STATISTICS, local_node_name, payload)
+    assert msg == (InternalEventHeaders.STATISTICS.value, local_node_name, payload)
 
 
 def test_state_event(supvisors, internal_publisher, internal_subscriber):
@@ -177,7 +177,7 @@ def test_state_event(supvisors, internal_publisher, internal_subscriber):
     internal_publisher.send_state_event(payload)
     # check the reception of the process event
     msg = internal_subscriber_receive(internal_subscriber, 'State')
-    assert msg == (InternalEventHeaders.STATE, local_node_name, payload)
+    assert msg == (InternalEventHeaders.STATE.value, local_node_name, payload)
 
 
 @pytest.fixture
@@ -204,7 +204,7 @@ def test_check_node(mocker, pusher, puller):
     """ The method tests that the 'Check Address' request is sent and received correctly. """
     pusher.send_check_node('10.0.0.1')
     request = puller.receive()
-    assert request == (DeferredRequestHeaders.CHECK_NODE, ('10.0.0.1',))
+    assert request == (DeferredRequestHeaders.CHECK_NODE.value, ('10.0.0.1',))
     # test that the pusher socket is not blocking
     mocker.patch.object(pusher.socket, 'send_pyobj', side_effect=zmq.error.Again)
     pusher.send_check_node('10.0.0.1')
@@ -217,7 +217,7 @@ def test_isolate_nodes(mocker, pusher, puller):
     """ The method tests that the 'Isolate Nodes' request is sent and received correctly. """
     pusher.send_isolate_nodes(['10.0.0.1', '10.0.0.2'])
     request = puller.receive()
-    assert request == (DeferredRequestHeaders.ISOLATE_NODES, (['10.0.0.1', '10.0.0.2']))
+    assert request == (DeferredRequestHeaders.ISOLATE_NODES.value, (['10.0.0.1', '10.0.0.2']))
     # test that the pusher socket is not blocking
     mocker.patch.object(pusher.socket, 'send_pyobj', side_effect=zmq.error.Again)
     pusher.send_isolate_nodes(['10.0.0.1', '10.0.0.2'])
@@ -230,7 +230,7 @@ def test_start_process(mocker, pusher, puller):
     """ The method tests that the 'Start Process' request is sent and received correctly. """
     pusher.send_start_process('10.0.0.1', 'application:program', ['-extra', 'arguments'])
     request = puller.receive()
-    assert request == (DeferredRequestHeaders.START_PROCESS,
+    assert request == (DeferredRequestHeaders.START_PROCESS.value,
                        ('10.0.0.1', 'application:program', ['-extra', 'arguments']))
     # test that the pusher socket is not blocking
     mocker.patch.object(pusher.socket, 'send_pyobj', side_effect=zmq.error.Again)
@@ -244,7 +244,7 @@ def test_stop_process(mocker, pusher, puller):
     """ The method tests that the 'Stop Process' request is sent and received correctly. """
     pusher.send_stop_process('10.0.0.1', 'application:program')
     request = puller.receive()
-    assert request == (DeferredRequestHeaders.STOP_PROCESS, ('10.0.0.1', 'application:program'))
+    assert request == (DeferredRequestHeaders.STOP_PROCESS.value, ('10.0.0.1', 'application:program'))
     # test that the pusher socket is not blocking
     mocker.patch.object(pusher.socket, 'send_pyobj', side_effect=zmq.error.Again)
     pusher.send_stop_process('10.0.0.1', 'application:program')
@@ -257,7 +257,7 @@ def test_restart(mocker, pusher, puller):
     """ The method tests that the 'Restart' request is sent and received correctly. """
     pusher.send_restart('10.0.0.1')
     request = puller.receive()
-    assert request == (DeferredRequestHeaders.RESTART, ('10.0.0.1',))
+    assert request == (DeferredRequestHeaders.RESTART.value, ('10.0.0.1',))
     # test that the pusher socket is not blocking
     mocker.patch.object(pusher.socket, 'send_pyobj', side_effect=zmq.error.Again)
     pusher.send_restart('10.0.0.1')
@@ -270,7 +270,7 @@ def test_shutdown(mocker, pusher, puller):
     """ The method tests that the 'Shutdown' request is sent and received correctly. """
     pusher.send_shutdown('10.0.0.1')
     request = puller.receive()
-    assert request == (DeferredRequestHeaders.SHUTDOWN, ('10.0.0.1',))
+    assert request == (DeferredRequestHeaders.SHUTDOWN.value, ('10.0.0.1',))
     # test that the pusher socket is not blocking
     mocker.patch.object(pusher.socket, 'send_pyobj', side_effect=zmq.error.Again)
     pusher.send_shutdown('10.0.0.1')
@@ -279,6 +279,32 @@ def test_shutdown(mocker, pusher, puller):
     pusher.send_shutdown('10.0.0.1')
 
 
+def test_restart_all(mocker, pusher, puller):
+    """ The method tests that the 'RestartAll' request is sent and received correctly. """
+    pusher.send_restart_all('10.0.0.1')
+    request = puller.receive()
+    assert request == (DeferredRequestHeaders.RESTART_ALL.value, ('10.0.0.1',))
+    # test that the pusher socket is not blocking
+    mocker.patch.object(pusher.socket, 'send_pyobj', side_effect=zmq.error.Again)
+    pusher.send_restart_all('10.0.0.1')
+    # test that absence of puller does not block the pusher or raise any exception
+    puller.close()
+    pusher.send_restart_all('10.0.0.1')
+
+
+def test_shutdown_all(mocker, pusher, puller):
+    """ The method tests that the 'ShutdownAll' request is sent and received correctly. """
+    pusher.send_shutdown_all('10.0.0.1')
+    request = puller.receive()
+    assert request == (DeferredRequestHeaders.SHUTDOWN_ALL.value, ('10.0.0.1',))
+    # test that the pusher socket is not blocking
+    mocker.patch.object(pusher.socket, 'send_pyobj', side_effect=zmq.error.Again)
+    pusher.send_shutdown_all('10.0.0.1')
+    # test that absence of puller does not block the pusher or raise any exception
+    puller.close()
+    pusher.send_shutdown_all('10.0.0.1')
+
+
 @pytest.fixture
 def publisher(supvisors):
     test_publisher = EventPublisher(supvisors.options.event_port, supvisors.logger)
diff --git a/supvisors/tests/test_viewapplication.py b/supvisors/tests/test_viewapplication.py
index b3e42ff5..20aabc89 100644
--- a/supvisors/tests/test_viewapplication.py
+++ b/supvisors/tests/test_viewapplication.py
@@ -86,7 +86,8 @@ def test_write_header(mocker, view):
     mocked_period = mocker.patch('supvisors.viewhandler.ViewHandler.write_periods')
     mocked_strategy = mocker.patch('supvisors.viewapplication.ApplicationView.write_starting_strategy')
     view.application_name = 'dummy_appli'
-    view.application = Mock(state=ApplicationStates.STOPPED, **{'running.return_value': False})
+    view.application = Mock(state=ApplicationStates.STOPPED, major_failure=False, minor_failure=False,
+                            **{'running.return_value': False})
     # patch the meld elements
     led_mid = Mock(attrib={'class': ''})
     state_mid = Mock()
diff --git a/supvisors/tests/test_viewhandler.py b/supvisors/tests/test_viewhandler.py
index 2f9232f7..b1dcf70a 100644
--- a/supvisors/tests/test_viewhandler.py
+++ b/supvisors/tests/test_viewhandler.py
@@ -285,15 +285,16 @@ def test_write_nav_applications_initialization(handler):
     # patch the meld elements
     href_elt = Mock(attrib={})
     appli_elt = Mock(attrib={}, **{'findmeld.return_value': href_elt})
-    mocked_mid = Mock(**{'repeat.return_value': [(appli_elt, Mock(application_name='dummy_appli',
-                                                                  state=ApplicationStates.RUNNING))]})
+    mocked_appli = Mock(application_name='dummy_appli', major_failure=False, minor_failure=False,
+                        state=ApplicationStates.RUNNING)
+    mocked_mid = Mock(**{'repeat.return_value': [(appli_elt, mocked_appli)]})
     mocked_root = Mock(**{'findmeld.return_value': mocked_mid})
     # test call with application name different from parameter
     handler.view_ctx = Mock(**{'format_url.return_value': 'an url'})
     handler.write_nav_applications(mocked_root, 'dumb_appli')
     assert mocked_root.findmeld.call_args_list == [call('appli_li_mid')]
     assert mocked_mid.repeat.call_args_list == [call([])]
-    assert appli_elt.attrib['class'] == 'RUNNING'
+    assert appli_elt.attrib['class'] == 'RUNNING  '
     assert appli_elt.findmeld.call_args_list == [call('appli_a_mid')]
     assert href_elt.attrib['class'] == 'off'
     assert handler.view_ctx.format_url.call_args_list == []
@@ -303,11 +304,12 @@ def test_write_nav_applications_initialization(handler):
     mocked_mid.repeat.reset_mock()
     appli_elt.findmeld.reset_mock()
     href_elt.content.reset_mock()
-    # test call with application name identical to parameter
+    # test call with application name identical to parameter and add a failure
+    mocked_appli.minor_failure = True
     handler.write_nav_applications(mocked_root, 'dummy_appli')
     assert mocked_root.findmeld.call_args_list == [call('appli_li_mid')]
     assert [call([])] == mocked_mid.repeat.call_args_list
-    assert appli_elt.attrib['class'] == 'RUNNING active'
+    assert appli_elt.attrib['class'] == 'RUNNING active failure'
     assert appli_elt.findmeld.call_args_list == [call('appli_a_mid')]
     assert href_elt.attrib['class'] == 'off'
     assert handler.view_ctx.format_url.call_args_list == []
@@ -321,15 +323,17 @@ def test_write_nav_applications_operation(handler):
     # patch the meld elements
     href_elt = Mock(attrib={})
     appli_elt = Mock(attrib={}, **{'findmeld.return_value': href_elt})
-    mocked_mid = Mock(**{'repeat.return_value': [(appli_elt, Mock(application_name='dummy_appli',
-                                                                  state=ApplicationStates.RUNNING))]})
+    mocked_appli = Mock(application_name='dummy_appli', major_failure=False, minor_failure=False,
+                        state=ApplicationStates.RUNNING)
+    mocked_mid = Mock(**{'repeat.return_value': [(appli_elt, mocked_appli)]})
     mocked_root = Mock(**{'findmeld.return_value': mocked_mid})
-    # test call with application name different from parameter
+    # test call with application name different from parameter and failure
+    mocked_appli.major_failure = True
     handler.view_ctx = Mock(**{'format_url.return_value': 'an url'})
     handler.write_nav_applications(mocked_root, 'dumb_appli')
     assert mocked_root.findmeld.call_args_list == [call('appli_li_mid')]
     assert mocked_mid.repeat.call_args_list == [call([])]
-    assert appli_elt.attrib['class'] == 'RUNNING'
+    assert appli_elt.attrib['class'] == 'RUNNING  failure'
     assert appli_elt.findmeld.call_args_list == [call('appli_a_mid')]
     assert href_elt.attrib['class'] == 'on'
     assert handler.view_ctx.format_url.call_args_list == [call('', 'application.html', appliname='dummy_appli')]
@@ -342,10 +346,11 @@ def test_write_nav_applications_operation(handler):
     href_elt.attributes.reset_mock()
     href_elt.content.reset_mock()
     # test call with application name identical to parameter
+    mocked_appli.major_failure = False
     handler.write_nav_applications(mocked_root, 'dummy_appli')
     assert mocked_root.findmeld.call_args_list == [call('appli_li_mid')]
     assert mocked_mid.repeat.call_args_list == [call([])]
-    assert appli_elt.attrib['class'] == 'RUNNING active'
+    assert appli_elt.attrib['class'] == 'RUNNING active '
     assert appli_elt.findmeld.call_args_list == [call('appli_a_mid')]
     assert href_elt.attrib['class'] == 'on'
     assert handler.view_ctx.format_url.call_args_list == [call('', 'application.html', appliname='dummy_appli')]
diff --git a/supvisors/tests/test_viewsupstatus.py b/supvisors/tests/test_viewsupstatus.py
index 93769a4d..c4798824 100644
--- a/supvisors/tests/test_viewsupstatus.py
+++ b/supvisors/tests/test_viewsupstatus.py
@@ -71,7 +71,7 @@ def test_write_header(mocker, view):
     mocked_mids = [Mock(attrib={}) for _ in range(4)]
     mocked_root = Mock(**{'findmeld.side_effect': mocked_mids * 2})
     # first call tests with not master
-    mocked_status = Mock(remote_time=3600, state=AddressStates.RUNNING, **{'get_load.return_value': 12})
+    mocked_status = Mock(remote_time=3600, state=AddressStates.RUNNING, **{'get_loading.return_value': 12})
     view.sup_ctx._is_master = False
     view.sup_ctx.nodes['127.0.0.1'] = mocked_status
     view.write_header(mocked_root)
diff --git a/supvisors/tests/test_viewsupvisors.py b/supvisors/tests/test_viewsupvisors.py
index c91ed8dc..8cf72ce0 100644
--- a/supvisors/tests/test_viewsupvisors.py
+++ b/supvisors/tests/test_viewsupvisors.py
@@ -111,15 +111,13 @@ def test_write_contents(mocker, view):
 def test_write_node_box_title(view):
     """ Test the _write_node_box_title method. """
     # patch context
-    mocked_status = Mock(address_name='10.0.0.1', state=AddressStates.RUNNING,
-                         **{'get_load.return_value': 17})
+    mocked_status = Mock(address_name='10.0.0.1', state=AddressStates.RUNNING, **{'get_loading.return_value': 17})
     view.view_ctx = Mock(**{'format_url.return_value': 'an url'})
     # build root structure with one single element
     mocked_node_mid = Mock(attrib={})
     mocked_state_mid = Mock(attrib={})
     mocked_percent_mid = Mock(attrib={})
-    mocked_root = Mock(**{'findmeld.side_effect': [mocked_node_mid, mocked_state_mid,
-                                                   mocked_percent_mid] * 2})
+    mocked_root = Mock(**{'findmeld.side_effect': [mocked_node_mid, mocked_state_mid, mocked_percent_mid] * 2})
     # test call in RUNNING state
     view._write_node_box_title(mocked_root, mocked_status)
     # test address element
@@ -138,8 +136,7 @@ def test_write_node_box_title(view):
     mocked_node_mid.attrib['class'] = ''
     mocked_state_mid.attrib['class'] = ''
     # test call in SILENT state
-    mocked_status = Mock(address_name='10.0.0.1', state=AddressStates.SILENT,
-                         **{'get_load.return_value': 0})
+    mocked_status = Mock(address_name='10.0.0.1', state=AddressStates.SILENT, **{'get_loading.return_value': 0})
     view._write_node_box_title(mocked_root, mocked_status)
     # test node element
     assert mocked_node_mid.attrib['class'] == ''