Merge "Add Synchronization topic in acm runtime"
diff --git a/models/src/main/java/org/onap/policy/clamp/models/acm/concepts/AutomationComposition.java b/models/src/main/java/org/onap/policy/clamp/models/acm/concepts/AutomationComposition.java
index 0cf1f99..eb5b6dc 100644
--- a/models/src/main/java/org/onap/policy/clamp/models/acm/concepts/AutomationComposition.java
+++ b/models/src/main/java/org/onap/policy/clamp/models/acm/concepts/AutomationComposition.java
@@ -1,6 +1,6 @@
 /*-
  * ============LICENSE_START=======================================================
- * Copyright (C) 2021-2023 Nordix Foundation.
+ * Copyright (C) 2021-2024 Nordix Foundation.
  * ================================================================================
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -52,6 +52,10 @@
     @NonNull
     private LockState lockState = LockState.NONE;
 
+    private String lastMsg;
+
+    private Integer phase;
+
     private Map<UUID, AutomationCompositionElement> elements;
 
     private StateChangeResult stateChangeResult;
@@ -69,6 +73,8 @@
         this.restarting = otherAutomationComposition.restarting;
         this.deployState = otherAutomationComposition.deployState;
         this.lockState = otherAutomationComposition.lockState;
+        this.lastMsg = otherAutomationComposition.lastMsg;
+        this.phase = otherAutomationComposition.phase;
         this.elements = PfUtils.mapMap(otherAutomationComposition.elements, AutomationCompositionElement::new);
         this.stateChangeResult = otherAutomationComposition.stateChangeResult;
     }
diff --git a/models/src/main/java/org/onap/policy/clamp/models/acm/concepts/Participant.java b/models/src/main/java/org/onap/policy/clamp/models/acm/concepts/Participant.java
index 5bdf4d3..6ddec61 100644
--- a/models/src/main/java/org/onap/policy/clamp/models/acm/concepts/Participant.java
+++ b/models/src/main/java/org/onap/policy/clamp/models/acm/concepts/Participant.java
@@ -49,6 +49,9 @@
     @NonNull
     private Map<UUID, ParticipantSupportedElementType> participantSupportedElementTypes = new HashMap<>();
 
+    @NonNull
+    private Map<UUID, ParticipantReplica> replicas = new HashMap<>();
+
     /**
      * Copy constructor.
      *
@@ -60,5 +63,6 @@
         this.lastMsg = otherParticipant.lastMsg;
         this.participantSupportedElementTypes = PfUtils.mapMap(otherParticipant.getParticipantSupportedElementTypes(),
                 ParticipantSupportedElementType::new);
+        this.replicas = PfUtils.mapMap(otherParticipant.replicas, ParticipantReplica::new);
     }
 }
diff --git a/models/src/main/java/org/onap/policy/clamp/models/acm/concepts/ParticipantReplica.java b/models/src/main/java/org/onap/policy/clamp/models/acm/concepts/ParticipantReplica.java
new file mode 100644
index 0000000..23f7219
--- /dev/null
+++ b/models/src/main/java/org/onap/policy/clamp/models/acm/concepts/ParticipantReplica.java
@@ -0,0 +1,53 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2024 Nordix Foundation.
+ * ================================================================================
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.clamp.models.acm.concepts;
+
+import java.util.UUID;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+import lombok.NonNull;
+
+@NoArgsConstructor
+@Data
+@EqualsAndHashCode
+public class ParticipantReplica {
+
+    @NonNull
+    private UUID replicaId;
+
+    @NonNull
+    private ParticipantState participantState = ParticipantState.ON_LINE;
+
+    @NonNull
+    private String lastMsg;
+
+    /**
+     * Copy constructor.
+     *
+     * @param other the ParticipantReplica to copy from
+     */
+    public ParticipantReplica(ParticipantReplica other) {
+        this.replicaId = other.replicaId;
+        this.participantState = other.participantState;
+        this.lastMsg = other.lastMsg;
+    }
+}
diff --git a/models/src/main/java/org/onap/policy/clamp/models/acm/persistence/concepts/JpaAutomationComposition.java b/models/src/main/java/org/onap/policy/clamp/models/acm/persistence/concepts/JpaAutomationComposition.java
index 5e27fde..0bf6a9e 100644
--- a/models/src/main/java/org/onap/policy/clamp/models/acm/persistence/concepts/JpaAutomationComposition.java
+++ b/models/src/main/java/org/onap/policy/clamp/models/acm/persistence/concepts/JpaAutomationComposition.java
@@ -1,6 +1,6 @@
 /*-
  * ============LICENSE_START=======================================================
- * Copyright (C) 2021-2023 Nordix Foundation.
+ * Copyright (C) 2021-2024 Nordix Foundation.
  * ================================================================================
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -32,6 +32,7 @@
 import jakarta.persistence.JoinColumn;
 import jakarta.persistence.OneToMany;
 import jakarta.persistence.Table;
+import java.sql.Timestamp;
 import java.util.ArrayList;
 import java.util.LinkedHashMap;
 import java.util.List;
@@ -44,6 +45,7 @@
 import org.onap.policy.clamp.models.acm.concepts.DeployState;
 import org.onap.policy.clamp.models.acm.concepts.LockState;
 import org.onap.policy.clamp.models.acm.concepts.StateChangeResult;
+import org.onap.policy.clamp.models.acm.utils.TimestampHelper;
 import org.onap.policy.common.parameters.annotations.NotNull;
 import org.onap.policy.common.parameters.annotations.Valid;
 import org.onap.policy.models.base.PfAuthorative;
@@ -98,6 +100,13 @@
     private StateChangeResult stateChangeResult;
 
     @Column
+    @NotNull
+    private Timestamp lastMsg;
+
+    @Column
+    private Integer phase;
+
+    @Column
     private String description;
 
     @NotNull
@@ -149,6 +158,8 @@
         this.restarting = copyConcept.restarting;
         this.deployState = copyConcept.deployState;
         this.lockState = copyConcept.lockState;
+        this.lastMsg = copyConcept.lastMsg;
+        this.phase = copyConcept.phase;
         this.description = copyConcept.description;
         this.stateChangeResult = copyConcept.stateChangeResult;
         this.elements = PfUtils.mapList(copyConcept.elements, JpaAutomationCompositionElement::new);
@@ -177,6 +188,8 @@
         automationComposition.setRestarting(restarting);
         automationComposition.setDeployState(deployState);
         automationComposition.setLockState(lockState);
+        automationComposition.setLastMsg(lastMsg.toString());
+        automationComposition.setPhase(phase);
         automationComposition.setDescription(description);
         automationComposition.setStateChangeResult(stateChangeResult);
         automationComposition.setElements(new LinkedHashMap<>(this.elements.size()));
@@ -199,6 +212,8 @@
         this.restarting = automationComposition.getRestarting();
         this.deployState = automationComposition.getDeployState();
         this.lockState = automationComposition.getLockState();
+        this.lastMsg = TimestampHelper.toTimestamp(automationComposition.getLastMsg());
+        this.phase = automationComposition.getPhase();
         this.description = automationComposition.getDescription();
         this.stateChangeResult = automationComposition.getStateChangeResult();
         this.elements = new ArrayList<>(automationComposition.getElements().size());
@@ -229,6 +244,16 @@
             return result;
         }
 
+        result = lastMsg.compareTo(other.lastMsg);
+        if (result != 0) {
+            return result;
+        }
+
+        result = ObjectUtils.compare(phase, other.phase);
+        if (result != 0) {
+            return result;
+        }
+
         result = ObjectUtils.compare(version, other.version);
         if (result != 0) {
             return result;
diff --git a/models/src/main/java/org/onap/policy/clamp/models/acm/persistence/concepts/JpaParticipant.java b/models/src/main/java/org/onap/policy/clamp/models/acm/persistence/concepts/JpaParticipant.java
index cfde557..f35fff9 100644
--- a/models/src/main/java/org/onap/policy/clamp/models/acm/persistence/concepts/JpaParticipant.java
+++ b/models/src/main/java/org/onap/policy/clamp/models/acm/persistence/concepts/JpaParticipant.java
@@ -41,6 +41,8 @@
 import lombok.EqualsAndHashCode;
 import lombok.NonNull;
 import org.apache.commons.lang3.ObjectUtils;
+import org.hibernate.annotations.LazyCollection;
+import org.hibernate.annotations.LazyCollectionOption;
 import org.onap.policy.clamp.models.acm.concepts.Participant;
 import org.onap.policy.clamp.models.acm.concepts.ParticipantState;
 import org.onap.policy.clamp.models.acm.utils.TimestampHelper;
@@ -84,11 +86,18 @@
         foreignKey = @ForeignKey(name = "supported_element_fk"))
     private List<@NotNull @Valid JpaParticipantSupportedElementType> supportedElements;
 
+    @NotNull
+    @OneToMany
+    @LazyCollection(LazyCollectionOption.FALSE)
+    @JoinColumn(name = "participantId", referencedColumnName = "participantId",
+            foreignKey = @ForeignKey(name = "participant_replica_fk"))
+    private List<@NotNull @Valid JpaParticipantReplica> replicas;
+
     /**
      * The Default Constructor creates a {@link JpaParticipant} object with a null key.
      */
     public JpaParticipant() {
-        this(UUID.randomUUID().toString(), ParticipantState.ON_LINE, new ArrayList<>());
+        this(UUID.randomUUID().toString(), ParticipantState.ON_LINE, new ArrayList<>(), new ArrayList<>());
     }
 
     /**
@@ -96,13 +105,17 @@
      *
      * @param participantId the participant id
      * @param participantState the state of the participant
+     * @param supportedElements the list of supported Element Type
+     * @param replicas the list of replica
      */
     public JpaParticipant(@NonNull String participantId, @NonNull final ParticipantState participantState,
-            @NonNull final List<JpaParticipantSupportedElementType> supportedElements) {
+            @NonNull final List<JpaParticipantSupportedElementType> supportedElements,
+            @NonNull final List<JpaParticipantReplica> replicas) {
         this.participantId = participantId;
         this.participantState = participantState;
         this.supportedElements = supportedElements;
         this.lastMsg = TimestampHelper.nowTimestamp();
+        this.replicas = replicas;
     }
 
     /**
@@ -115,6 +128,7 @@
         this.description = copyConcept.description;
         this.participantId = copyConcept.participantId;
         this.supportedElements = copyConcept.supportedElements;
+        this.replicas = copyConcept.replicas;
         this.lastMsg = copyConcept.lastMsg;
     }
 
@@ -139,7 +153,9 @@
             participant.getParticipantSupportedElementTypes()
                 .put(UUID.fromString(element.getId()), element.toAuthorative());
         }
-
+        for (var replica : this.replicas) {
+            participant.getReplicas().put(UUID.fromString(replica.getReplicaId()), replica.toAuthorative());
+        }
         return participant;
     }
 
@@ -148,14 +164,22 @@
         this.setParticipantState(participant.getParticipantState());
         this.participantId = participant.getParticipantId().toString();
         this.lastMsg = TimestampHelper.toTimestamp(participant.getLastMsg());
-        this.supportedElements = new ArrayList<>(participant.getParticipantSupportedElementTypes().size());
 
+        this.supportedElements = new ArrayList<>(participant.getParticipantSupportedElementTypes().size());
         for (var elementEntry : participant.getParticipantSupportedElementTypes().entrySet()) {
             var jpaParticipantSupportedElementType = new JpaParticipantSupportedElementType();
             jpaParticipantSupportedElementType.setParticipantId(this.participantId);
             jpaParticipantSupportedElementType.fromAuthorative(elementEntry.getValue());
             this.supportedElements.add(jpaParticipantSupportedElementType);
         }
+
+        this.replicas = new ArrayList<>(participant.getReplicas().size());
+        for (var replicaEntry : participant.getReplicas().entrySet()) {
+            var jpaReplica = new JpaParticipantReplica();
+            jpaReplica.setParticipantId(this.participantId);
+            jpaReplica.fromAuthorative(replicaEntry.getValue());
+            this.replicas.add(jpaReplica);
+        }
     }
 
     @Override
diff --git a/models/src/main/java/org/onap/policy/clamp/models/acm/persistence/concepts/JpaParticipantReplica.java b/models/src/main/java/org/onap/policy/clamp/models/acm/persistence/concepts/JpaParticipantReplica.java
new file mode 100644
index 0000000..beaab60
--- /dev/null
+++ b/models/src/main/java/org/onap/policy/clamp/models/acm/persistence/concepts/JpaParticipantReplica.java
@@ -0,0 +1,88 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2024 Nordix Foundation.
+ * ================================================================================
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.clamp.models.acm.persistence.concepts;
+
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.Id;
+import jakarta.persistence.Inheritance;
+import jakarta.persistence.InheritanceType;
+import jakarta.persistence.Table;
+import java.sql.Timestamp;
+import java.util.UUID;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NonNull;
+import org.onap.policy.clamp.models.acm.concepts.ParticipantReplica;
+import org.onap.policy.clamp.models.acm.concepts.ParticipantState;
+import org.onap.policy.clamp.models.acm.utils.TimestampHelper;
+import org.onap.policy.common.parameters.annotations.NotNull;
+import org.onap.policy.models.base.PfAuthorative;
+import org.onap.policy.models.base.Validated;
+
+@Entity
+@Table(name = "ParticipantReplica")
+@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
+@Data
+@EqualsAndHashCode(callSuper = false)
+public class JpaParticipantReplica extends Validated implements PfAuthorative<ParticipantReplica> {
+
+    @Id
+    @NotNull
+    private String replicaId;
+
+    @NotNull
+    @Column
+    private String participantId;
+
+    @Column
+    @NotNull
+    private ParticipantState participantState;
+
+    @Column
+    @NotNull
+    private Timestamp lastMsg;
+
+    public JpaParticipantReplica() {
+        this(UUID.randomUUID().toString(), UUID.randomUUID().toString());
+    }
+
+    public JpaParticipantReplica(@NonNull String replicaId, @NonNull String participantId) {
+        this.replicaId = replicaId;
+        this.participantId = participantId;
+    }
+
+    @Override
+    public ParticipantReplica toAuthorative() {
+        var participantReplica = new ParticipantReplica();
+        participantReplica.setReplicaId(UUID.fromString(replicaId));
+        participantReplica.setParticipantState(participantState);
+        participantReplica.setLastMsg(lastMsg.toString());
+        return participantReplica;
+    }
+
+    @Override
+    public void fromAuthorative(@NonNull ParticipantReplica participantReplica) {
+        this.replicaId = participantReplica.getReplicaId().toString();
+        this.participantState = participantReplica.getParticipantState();
+        this.lastMsg = TimestampHelper.toTimestamp(participantReplica.getLastMsg());
+    }
+}
diff --git a/models/src/main/java/org/onap/policy/clamp/models/acm/utils/AcmUtils.java b/models/src/main/java/org/onap/policy/clamp/models/acm/utils/AcmUtils.java
index 0293bd3..5f523ad 100644
--- a/models/src/main/java/org/onap/policy/clamp/models/acm/utils/AcmUtils.java
+++ b/models/src/main/java/org/onap/policy/clamp/models/acm/utils/AcmUtils.java
@@ -29,7 +29,6 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
-import java.util.Queue;
 import java.util.UUID;
 import java.util.function.Function;
 import java.util.function.UnaryOperator;
@@ -71,7 +70,7 @@
 @NoArgsConstructor(access = AccessLevel.PRIVATE)
 public final class AcmUtils {
     public static final String ENTRY = "entry ";
-    private static StringToMapConverter MAP_CONVERTER = new StringToMapConverter();
+    private static final StringToMapConverter MAP_CONVERTER = new StringToMapConverter();
 
     /**
      * Get the Policy information in the service template for the deploy message to participants.
@@ -379,6 +378,7 @@
             final DeployState deployState, final LockState lockState) {
         automationComposition.setDeployState(deployState);
         automationComposition.setLockState(lockState);
+        automationComposition.setLastMsg(TimestampHelper.now());
 
         if (MapUtils.isEmpty(automationComposition.getElements())) {
             return;
diff --git a/models/src/test/java/org/onap/policy/clamp/models/acm/concepts/ParticipantReplicaTest.java b/models/src/test/java/org/onap/policy/clamp/models/acm/concepts/ParticipantReplicaTest.java
new file mode 100644
index 0000000..8df6db6
--- /dev/null
+++ b/models/src/test/java/org/onap/policy/clamp/models/acm/concepts/ParticipantReplicaTest.java
@@ -0,0 +1,68 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2024 Nordix Foundation.
+ * ================================================================================
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.clamp.models.acm.concepts;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+
+import java.util.UUID;
+import org.junit.jupiter.api.Test;
+import org.onap.policy.clamp.models.acm.utils.CommonTestData;
+
+class ParticipantReplicaTest {
+
+    @Test
+    void testParticipantLombok() {
+        assertDoesNotThrow(() -> new ParticipantReplica());
+        var p0 = new ParticipantReplica();
+
+        assertThat(p0.toString()).contains("ParticipantReplica(");
+        assertThat(p0.hashCode()).isNotZero();
+        assertNotEquals(null, p0);
+
+        var p1 = new ParticipantReplica();
+
+        p1.setReplicaId(CommonTestData.getReplicaId());
+        p1.setParticipantState(ParticipantState.ON_LINE);
+
+        assertThat(p1.toString()).contains("ParticipantReplica(");
+        assertNotEquals(0, p1.hashCode());
+        assertNotEquals(p1, p0);
+        assertNotEquals(null, p1);
+
+        var p2 = new ParticipantReplica();
+        assertThatThrownBy(() -> p2.setParticipantState(null)).isInstanceOf(NullPointerException.class);
+        assertEquals(p2, p0);
+    }
+
+    @Test
+    void testCopyConstructor() {
+        var p0 = new ParticipantReplica();
+        p0.setReplicaId(UUID.randomUUID());
+        p0.setParticipantState(ParticipantState.ON_LINE);
+
+        var p2 = new ParticipantReplica(p0);
+        assertEquals(p2, p0);
+    }
+}
diff --git a/models/src/test/java/org/onap/policy/clamp/models/acm/persistence/concepts/JpaAutomationCompositionTest.java b/models/src/test/java/org/onap/policy/clamp/models/acm/persistence/concepts/JpaAutomationCompositionTest.java
index 66554e7..b56e778 100644
--- a/models/src/test/java/org/onap/policy/clamp/models/acm/persistence/concepts/JpaAutomationCompositionTest.java
+++ b/models/src/test/java/org/onap/policy/clamp/models/acm/persistence/concepts/JpaAutomationCompositionTest.java
@@ -1,6 +1,6 @@
 /*-
  * ============LICENSE_START=======================================================
- * Copyright (C) 2021-2023 Nordix Foundation.
+ * Copyright (C) 2021-2024 Nordix Foundation.
  * ================================================================================
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -27,6 +27,8 @@
 import static org.junit.jupiter.api.Assertions.assertNotEquals;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
+import java.sql.Timestamp;
+import java.time.Instant;
 import java.util.ArrayList;
 import java.util.LinkedHashMap;
 import java.util.UUID;
@@ -35,6 +37,7 @@
 import org.onap.policy.clamp.models.acm.concepts.DeployState;
 import org.onap.policy.clamp.models.acm.concepts.LockState;
 import org.onap.policy.clamp.models.acm.concepts.StateChangeResult;
+import org.onap.policy.clamp.models.acm.utils.TimestampHelper;
 import org.onap.policy.models.base.PfConceptKey;
 
 /**
@@ -93,9 +96,9 @@
 
     @Test
     void testJpaAutomationComposition() {
-        var jpaAutomationComposition = createJpaAutomationCompositionInstance();
-
         var automationComposition = createAutomationCompositionInstance();
+        var jpaAutomationComposition = new JpaAutomationComposition(automationComposition);
+
         assertEquals(automationComposition, jpaAutomationComposition.toAuthorative());
 
         var target = UUID.randomUUID();
@@ -125,7 +128,7 @@
 
     @Test
     void testJpaAutomationCompositionValidation() {
-        var testJpaAutomationComposition = createJpaAutomationCompositionInstance();
+        var testJpaAutomationComposition = new JpaAutomationComposition(createAutomationCompositionInstance());
 
         assertThatThrownBy(() -> testJpaAutomationComposition.validate(null))
                 .hasMessageMatching("fieldName is marked .*ull but is null");
@@ -135,7 +138,7 @@
 
     @Test
     void testJpaAutomationCompositionCompareTo() {
-        var jpaAutomationComposition = createJpaAutomationCompositionInstance();
+        var jpaAutomationComposition = new JpaAutomationComposition(createAutomationCompositionInstance());
 
         var otherJpaAutomationComposition = new JpaAutomationComposition(jpaAutomationComposition);
         assertEquals(0, jpaAutomationComposition.compareTo(otherJpaAutomationComposition));
@@ -168,6 +171,16 @@
         jpaAutomationComposition.setVersion("0.0.1");
         assertEquals(0, jpaAutomationComposition.compareTo(otherJpaAutomationComposition));
 
+        jpaAutomationComposition.setLastMsg(Timestamp.from(Instant.EPOCH));
+        assertNotEquals(0, jpaAutomationComposition.compareTo(otherJpaAutomationComposition));
+        jpaAutomationComposition.setLastMsg(otherJpaAutomationComposition.getLastMsg());
+        assertEquals(0, jpaAutomationComposition.compareTo(otherJpaAutomationComposition));
+
+        jpaAutomationComposition.setPhase(0);
+        assertNotEquals(0, jpaAutomationComposition.compareTo(otherJpaAutomationComposition));
+        jpaAutomationComposition.setPhase(null);
+        assertEquals(0, jpaAutomationComposition.compareTo(otherJpaAutomationComposition));
+
         jpaAutomationComposition.setDeployState(DeployState.DEPLOYED);
         assertNotEquals(0, jpaAutomationComposition.compareTo(otherJpaAutomationComposition));
         jpaAutomationComposition.setDeployState(DeployState.UNDEPLOYED);
@@ -225,19 +238,12 @@
         assertEquals(ac2, ac0);
     }
 
-    private JpaAutomationComposition createJpaAutomationCompositionInstance() {
-        var testAutomationComposition = createAutomationCompositionInstance();
-        var testJpaAutomationComposition = new JpaAutomationComposition();
-        testJpaAutomationComposition.fromAuthorative(testAutomationComposition);
-
-        return testJpaAutomationComposition;
-    }
-
     private AutomationComposition createAutomationCompositionInstance() {
         var testAutomationComposition = new AutomationComposition();
         testAutomationComposition.setName("automation-composition");
         testAutomationComposition.setInstanceId(UUID.fromString(INSTANCE_ID));
         testAutomationComposition.setVersion("0.0.1");
+        testAutomationComposition.setLastMsg(TimestampHelper.now());
         testAutomationComposition.setCompositionId(UUID.fromString(COMPOSITION_ID));
         testAutomationComposition.setElements(new LinkedHashMap<>());
 
diff --git a/models/src/test/java/org/onap/policy/clamp/models/acm/persistence/concepts/JpaParticipantReplicaTest.java b/models/src/test/java/org/onap/policy/clamp/models/acm/persistence/concepts/JpaParticipantReplicaTest.java
new file mode 100644
index 0000000..d777608
--- /dev/null
+++ b/models/src/test/java/org/onap/policy/clamp/models/acm/persistence/concepts/JpaParticipantReplicaTest.java
@@ -0,0 +1,72 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * Copyright (C) 2024 Nordix Foundation.
+ * ================================================================================
+ * 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.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.clamp.models.acm.persistence.concepts;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+
+import java.util.UUID;
+import org.junit.jupiter.api.Test;
+import org.onap.policy.clamp.models.acm.concepts.ParticipantReplica;
+import org.onap.policy.clamp.models.acm.concepts.ParticipantState;
+import org.onap.policy.clamp.models.acm.utils.TimestampHelper;
+
+class JpaParticipantReplicaTest {
+
+    @Test
+    void testJpaParticipantReplicaConstructor() {
+        assertThatThrownBy(() -> new JpaParticipantReplica(UUID.randomUUID().toString(), null))
+                .hasMessageMatching("participantId is marked .*ull but is null");
+
+        assertThatThrownBy(() -> new JpaParticipantReplica(null, UUID.randomUUID().toString()))
+                .hasMessageMatching("replicaId is marked .*ull but is null");
+
+        assertDoesNotThrow(() -> new JpaParticipantReplica());
+        assertDoesNotThrow(() -> new JpaParticipantReplica(UUID.randomUUID().toString(), UUID.randomUUID().toString()));
+    }
+
+    @Test
+    void testJpaParticipantReplica() {
+        var p0 = new JpaParticipantReplica();
+
+        assertThat(p0.toString()).contains("JpaParticipantReplica(");
+        assertThat(p0.hashCode()).isNotZero();
+        assertNotEquals(null, p0);
+
+        var p1 = new JpaParticipantReplica();
+        p1.setParticipantState(ParticipantState.ON_LINE);
+
+        assertThat(p1.toString()).contains("ParticipantReplica(");
+        assertNotEquals(0, p1.hashCode());
+        assertNotEquals(p1, p0);
+        assertNotEquals(null, p1);
+
+        var p2 = new JpaParticipantReplica();
+        p2.setReplicaId(p0.getReplicaId());
+        p2.setParticipantId(p0.getParticipantId());
+        p2.setLastMsg(p0.getLastMsg());
+        p2.setParticipantState(p0.getParticipantState());
+        assertEquals(p2, p0);
+    }
+}
diff --git a/models/src/test/java/org/onap/policy/clamp/models/acm/persistence/concepts/JpaParticipantTest.java b/models/src/test/java/org/onap/policy/clamp/models/acm/persistence/concepts/JpaParticipantTest.java
index e64a689..e0f2f55 100644
--- a/models/src/test/java/org/onap/policy/clamp/models/acm/persistence/concepts/JpaParticipantTest.java
+++ b/models/src/test/java/org/onap/policy/clamp/models/acm/persistence/concepts/JpaParticipantTest.java
@@ -52,20 +52,25 @@
         assertThatThrownBy(() -> new JpaParticipant((JpaParticipant) null))
             .hasMessageMatching("copyConcept is marked .*ull but is null");
 
-        assertThatThrownBy(() -> new JpaParticipant(null, null, null)).hasMessageMatching(NULL_KEY_ERROR);
-
-        assertThatThrownBy(() -> new JpaParticipant(null, ParticipantState.ON_LINE, new ArrayList<>()))
+        assertThatThrownBy(() -> new JpaParticipant(null, ParticipantState.ON_LINE,
+                new ArrayList<>(), new ArrayList<>()))
             .hasMessageMatching(NULL_KEY_ERROR);
 
-        assertThatThrownBy(() -> new JpaParticipant(UUID.randomUUID().toString(), null, new ArrayList<>()))
+        assertThatThrownBy(() -> new JpaParticipant(UUID.randomUUID().toString(), null,
+                new ArrayList<>(), new ArrayList<>()))
             .hasMessageMatching("participantState is marked .*ull but is null");
 
-        assertThatThrownBy(() -> new JpaParticipant(UUID.randomUUID().toString(), ParticipantState.ON_LINE, null))
+        assertThatThrownBy(() -> new JpaParticipant(UUID.randomUUID().toString(), ParticipantState.ON_LINE,
+                null, new ArrayList<>()))
             .hasMessageMatching("supportedElements is marked .*ull but is null");
 
+        assertThatThrownBy(() -> new JpaParticipant(UUID.randomUUID().toString(), ParticipantState.ON_LINE,
+                new ArrayList<>(), null))
+                .hasMessageMatching("replicas is marked .*ull but is null");
+
         assertDoesNotThrow(() -> new JpaParticipant());
         assertDoesNotThrow(() -> new JpaParticipant(UUID.randomUUID().toString(),
-            ParticipantState.ON_LINE, new ArrayList<>()));
+            ParticipantState.ON_LINE, new ArrayList<>(), new ArrayList<>()));
     }
 
     @Test
@@ -158,6 +163,7 @@
         testParticipant.setParticipantId(UUID.randomUUID());
         testParticipant.setLastMsg(TimestampHelper.now());
         testParticipant.setParticipantSupportedElementTypes(new LinkedHashMap<>());
+        testParticipant.setReplicas(new LinkedHashMap<>());
 
         return testParticipant;
     }
diff --git a/models/src/test/java/org/onap/policy/clamp/models/acm/persistence/provider/AutomationCompositionProviderTest.java b/models/src/test/java/org/onap/policy/clamp/models/acm/persistence/provider/AutomationCompositionProviderTest.java
index 463e958..8e7e50d 100644
--- a/models/src/test/java/org/onap/policy/clamp/models/acm/persistence/provider/AutomationCompositionProviderTest.java
+++ b/models/src/test/java/org/onap/policy/clamp/models/acm/persistence/provider/AutomationCompositionProviderTest.java
@@ -85,6 +85,7 @@
 
         var createdAutomationComposition = automationCompositionProvider.createAutomationComposition(inputAc);
         inputAc.setInstanceId(createdAutomationComposition.getInstanceId());
+        inputAc.setLastMsg(createdAutomationComposition.getLastMsg());
         assertEquals(inputAc, createdAutomationComposition);
     }
 
diff --git a/models/src/test/java/org/onap/policy/clamp/models/acm/utils/CommonTestData.java b/models/src/test/java/org/onap/policy/clamp/models/acm/utils/CommonTestData.java
index b8075c3..3bd1549 100644
--- a/models/src/test/java/org/onap/policy/clamp/models/acm/utils/CommonTestData.java
+++ b/models/src/test/java/org/onap/policy/clamp/models/acm/utils/CommonTestData.java
@@ -51,9 +51,9 @@
     }
 
     /**
-     * Returns participantId for test cases.
+     * Returns participant replica Id for test cases.
      *
-     * @return participant Id
+     * @return replica Id
      */
     public static UUID getReplicaId() {
         return REPLICA_ID;
diff --git a/models/src/test/resources/providers/TestAutomationCompositions.json b/models/src/test/resources/providers/TestAutomationCompositions.json
index 24f5a48..c75337b 100644
--- a/models/src/test/resources/providers/TestAutomationCompositions.json
+++ b/models/src/test/resources/providers/TestAutomationCompositions.json
@@ -5,6 +5,7 @@
             "instanceId": "809c62b3-8918-41b9-a748-e21eb79c6c89",
             "deployState": "UNDEPLOYED",
             "lockState": "NONE",
+            "lastMsg": "2024-05-22 10:04:37.6020187",
             "elements": {
                 "709c62b3-8918-41b9-a747-e21eb79c6c20": {
                     "id": "709c62b3-8918-41b9-a747-e21eb79c6c20",
@@ -56,6 +57,7 @@
             "instanceId": "809c62b3-8918-41b9-a748-e21eb79c6c90",
             "deployState": "UNDEPLOYED",
             "lockState": "NONE",
+            "lastMsg": "2024-05-22 10:04:37.6020187",
             "elements": {
                 "709c62b3-8918-41b9-a747-e21eb79c6c24": {
                     "id": "709c62b3-8918-41b9-a747-e21eb79c6c24",
diff --git a/models/src/test/resources/providers/TestParticipant.json b/models/src/test/resources/providers/TestParticipant.json
index 3f19baa..3335865 100644
--- a/models/src/test/resources/providers/TestParticipant.json
+++ b/models/src/test/resources/providers/TestParticipant.json
@@ -22,5 +22,12 @@
       "typeName": "org.onap.policy.clamp.acm.AutomationCompositionElement",
       "typeVersion": "1.0.0"
     }
+  },
+  "replicas": {
+    "82fd8ef9-1d1e-4343-9b28-7f9564ee3de6":{
+      "replicaId": "82fd8ef9-1d1e-4343-9b28-7f9564ee3de6",
+      "lastMsg": "2024-05-22 10:04:37.6020187",
+      "participantState": "ON_LINE"
+    }
   }
 }
diff --git a/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/SupervisionAcHandler.java b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/SupervisionAcHandler.java
index d6fa5d8..802c660 100644
--- a/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/SupervisionAcHandler.java
+++ b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/SupervisionAcHandler.java
@@ -86,8 +86,9 @@
             AcmUtils.setCascadedState(automationComposition, DeployState.DEPLOYING, LockState.NONE);
         }
         automationComposition.setStateChangeResult(StateChangeResult.NO_ERROR);
-        automationCompositionProvider.updateAutomationComposition(automationComposition);
         var startPhase = ParticipantUtils.getFirstStartPhase(automationComposition, acDefinition.getServiceTemplate());
+        automationComposition.setPhase(startPhase);
+        automationCompositionProvider.updateAutomationComposition(automationComposition);
         executor.execute(
             () -> automationCompositionDeployPublisher.send(automationComposition, acDefinition.getServiceTemplate(),
                 startPhase, true));
@@ -112,8 +113,9 @@
         }
         automationComposition.setStateChangeResult(StateChangeResult.NO_ERROR);
         automationComposition.setCompositionTargetId(null);
-        automationCompositionProvider.updateAutomationComposition(automationComposition);
         var startPhase = ParticipantUtils.getFirstStartPhase(automationComposition, acDefinition.getServiceTemplate());
+        automationComposition.setPhase(startPhase);
+        automationCompositionProvider.updateAutomationComposition(automationComposition);
         executor.execute(
             () -> automationCompositionStateChangePublisher.send(automationComposition, startPhase, true));
     }
@@ -136,8 +138,9 @@
             AcmUtils.setCascadedState(automationComposition, DeployState.DEPLOYED, LockState.UNLOCKING);
         }
         automationComposition.setStateChangeResult(StateChangeResult.NO_ERROR);
-        automationCompositionProvider.updateAutomationComposition(automationComposition);
         var startPhase = ParticipantUtils.getFirstStartPhase(automationComposition, acDefinition.getServiceTemplate());
+        automationComposition.setPhase(startPhase);
+        automationCompositionProvider.updateAutomationComposition(automationComposition);
         executor.execute(
             () -> automationCompositionStateChangePublisher.send(automationComposition, startPhase, true));
     }
@@ -160,8 +163,9 @@
             AcmUtils.setCascadedState(automationComposition, DeployState.DEPLOYED, LockState.LOCKING);
         }
         automationComposition.setStateChangeResult(StateChangeResult.NO_ERROR);
-        automationCompositionProvider.updateAutomationComposition(automationComposition);
         var startPhase = ParticipantUtils.getFirstStartPhase(automationComposition, acDefinition.getServiceTemplate());
+        automationComposition.setPhase(startPhase);
+        automationCompositionProvider.updateAutomationComposition(automationComposition);
         executor.execute(
             () -> automationCompositionStateChangePublisher.send(automationComposition, startPhase, true));
     }
@@ -187,8 +191,9 @@
     public void delete(AutomationComposition automationComposition, AutomationCompositionDefinition acDefinition) {
         AcmUtils.setCascadedState(automationComposition, DeployState.DELETING, LockState.NONE);
         automationComposition.setStateChangeResult(StateChangeResult.NO_ERROR);
-        automationCompositionProvider.updateAutomationComposition(automationComposition);
         var startPhase = ParticipantUtils.getFirstStartPhase(automationComposition, acDefinition.getServiceTemplate());
+        automationComposition.setPhase(startPhase);
+        automationCompositionProvider.updateAutomationComposition(automationComposition);
         executor.execute(
             () -> automationCompositionStateChangePublisher.send(automationComposition, startPhase, true));
     }
diff --git a/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/SupervisionScanner.java b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/SupervisionScanner.java
index 96e75df..06d4646 100644
--- a/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/SupervisionScanner.java
+++ b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/SupervisionScanner.java
@@ -23,9 +23,7 @@
 package org.onap.policy.clamp.acm.runtime.supervision;
 
 import java.util.HashMap;
-import java.util.Map;
 import java.util.UUID;
-import java.util.stream.Collectors;
 import org.onap.policy.clamp.acm.runtime.main.parameters.AcRuntimeParameterGroup;
 import org.onap.policy.clamp.acm.runtime.supervision.comm.AutomationCompositionDeployPublisher;
 import org.onap.policy.clamp.acm.runtime.supervision.comm.AutomationCompositionStateChangePublisher;
@@ -51,9 +49,6 @@
 public class SupervisionScanner {
     private static final Logger LOGGER = LoggerFactory.getLogger(SupervisionScanner.class);
 
-    private final TimeoutHandler<UUID> acTimeout = new TimeoutHandler<>();
-    private final Map<UUID, Integer> phaseMap = new HashMap<>();
-
     private final long maxStatusWaitMs;
 
     private final AutomationCompositionProvider automationCompositionProvider;
@@ -79,8 +74,6 @@
         this.acDefinitionProvider = acDefinitionProvider;
         this.automationCompositionStateChangePublisher = automationCompositionStateChangePublisher;
         this.automationCompositionDeployPublisher = automationCompositionDeployPublisher;
-
-        acTimeout.setMaxWaitMs(acRuntimeParameterGroup.getParticipantParameters().getMaxStatusWaitMs());
         this.maxStatusWaitMs = acRuntimeParameterGroup.getParticipantParameters().getMaxStatusWaitMs();
     }
 
@@ -105,9 +98,6 @@
             }
             scanAutomationComposition(automationComposition, acDefinition.getServiceTemplate());
         }
-        var set = acList.stream().map(AutomationComposition::getInstanceId).collect(Collectors.toSet());
-        acTimeout.removeIfNotPresent(set);
-
         LOGGER.debug("Automation composition scan complete . . .");
     }
 
@@ -143,17 +133,9 @@
             LOGGER.debug("automation composition {} scanned, OK", automationComposition.getInstanceId());
 
             // Clear Timeout on automation composition
-            removeTimeout(automationComposition);
             return;
         }
 
-        if (acTimeout.isTimeout(automationComposition.getInstanceId())
-                && StateChangeResult.NO_ERROR.equals(automationComposition.getStateChangeResult())) {
-            // retry by the user
-            LOGGER.debug("clearing Timeout for the ac instance");
-            clearTimeout(automationComposition, true);
-        }
-
         var completed = true;
         var minSpNotCompleted = 1000; // min startPhase not completed
         var maxSpNotCompleted = 0; // max startPhase not completed
@@ -173,18 +155,18 @@
         }
 
         if (completed) {
-            LOGGER.debug("automation composition scan: transition state {} {} ", automationComposition.getDeployState(),
-                    automationComposition.getLockState());
+            LOGGER.debug("automation composition scan: transition state {} {} completed",
+                    automationComposition.getDeployState(), automationComposition.getLockState());
 
             complete(automationComposition);
         } else {
-            LOGGER.debug("automation composition scan: transition from state {} to {} not completed",
+            LOGGER.debug("automation composition scan: transition state {} {} not completed",
                     automationComposition.getDeployState(), automationComposition.getLockState());
 
             if (DeployState.UPDATING.equals(automationComposition.getDeployState())
                     || DeployState.MIGRATING.equals(automationComposition.getDeployState())) {
                 // UPDATING do not need phases
-                handleTimeout(automationComposition);
+                handleTimeoutUpdate(automationComposition);
                 return;
             }
 
@@ -192,14 +174,11 @@
                     AcmUtils.isForward(automationComposition.getDeployState(), automationComposition.getLockState());
 
             var nextSpNotCompleted = isForward ? minSpNotCompleted : maxSpNotCompleted;
-            var firstStartPhase = isForward ? defaultMin : defaultMax;
 
-            if (nextSpNotCompleted != phaseMap.getOrDefault(automationComposition.getInstanceId(), firstStartPhase)) {
-                phaseMap.put(automationComposition.getInstanceId(), nextSpNotCompleted);
-                sendAutomationCompositionMsg(automationComposition, serviceTemplate, nextSpNotCompleted,
-                        firstStartPhase == nextSpNotCompleted);
+            if (nextSpNotCompleted != automationComposition.getPhase()) {
+                sendAutomationCompositionMsg(automationComposition, serviceTemplate, nextSpNotCompleted, false);
             } else {
-                handleTimeout(automationComposition);
+                handleTimeoutWithPhase(automationComposition, serviceTemplate);
             }
         }
     }
@@ -213,6 +192,7 @@
         }
         automationComposition.setDeployState(AcmUtils.deployCompleted(deployState));
         automationComposition.setLockState(AcmUtils.lockCompleted(deployState, automationComposition.getLockState()));
+        automationComposition.setPhase(null);
         if (StateChangeResult.TIMEOUT.equals(automationComposition.getStateChangeResult())) {
             automationComposition.setStateChangeResult(StateChangeResult.NO_ERROR);
         }
@@ -221,21 +201,6 @@
         } else {
             automationCompositionProvider.updateAutomationComposition(automationComposition);
         }
-
-        // Clear timeout on automation composition
-        removeTimeout(automationComposition);
-    }
-
-    private void clearTimeout(AutomationComposition automationComposition, boolean cleanPhase) {
-        acTimeout.clear(automationComposition.getInstanceId());
-        if (cleanPhase) {
-            phaseMap.remove(automationComposition.getInstanceId());
-        }
-    }
-
-    private void removeTimeout(AutomationComposition automationComposition) {
-        acTimeout.remove(automationComposition.getInstanceId());
-        phaseMap.remove(automationComposition.getInstanceId());
     }
 
     private void handleTimeout(AutomationCompositionDefinition acDefinition) {
@@ -253,23 +218,60 @@
         }
     }
 
-    private void handleTimeout(AutomationComposition automationComposition) {
-        var instanceId = automationComposition.getInstanceId();
-        if (acTimeout.isTimeout(instanceId)) {
+    private void handleTimeoutUpdate(AutomationComposition automationComposition) {
+        if (StateChangeResult.TIMEOUT.equals(automationComposition.getStateChangeResult())) {
             LOGGER.debug("The ac instance is in timeout {}", automationComposition.getInstanceId());
             return;
         }
+        var now = TimestampHelper.nowEpochMilli();
+        var lastMsg = TimestampHelper.toEpochMilli(automationComposition.getLastMsg());
+        for (var element : automationComposition.getElements().values()) {
+            if (!AcmUtils.isInTransitionalState(element.getDeployState(), element.getLockState())) {
+                continue;
+            }
+            if ((now - lastMsg) > maxStatusWaitMs) {
+                LOGGER.debug("Report timeout for the ac instance {}", automationComposition.getInstanceId());
+                automationComposition.setStateChangeResult(StateChangeResult.TIMEOUT);
+                automationCompositionProvider.updateAutomationComposition(automationComposition);
+                break;
+            }
+        }
+    }
 
-        if (acTimeout.getDuration(instanceId) > acTimeout.getMaxWaitMs()) {
-            LOGGER.debug("Report timeout for the ac instance {}", automationComposition.getInstanceId());
-            acTimeout.setTimeout(instanceId);
-            automationComposition.setStateChangeResult(StateChangeResult.TIMEOUT);
-            automationCompositionProvider.updateAutomationComposition(automationComposition);
+    private void handleTimeoutWithPhase(AutomationComposition automationComposition,
+            ToscaServiceTemplate serviceTemplate) {
+        if (StateChangeResult.TIMEOUT.equals(automationComposition.getStateChangeResult())) {
+            LOGGER.debug("The ac instance is in timeout {}", automationComposition.getInstanceId());
+            return;
+        }
+        int currentPhase = automationComposition.getPhase();
+        var now = TimestampHelper.nowEpochMilli();
+        var lastMsg = TimestampHelper.toEpochMilli(automationComposition.getLastMsg());
+        for (var element : automationComposition.getElements().values()) {
+            if (!AcmUtils.isInTransitionalState(element.getDeployState(), element.getLockState())) {
+                continue;
+            }
+            var toscaNodeTemplate = serviceTemplate.getToscaTopologyTemplate().getNodeTemplates()
+                    .get(element.getDefinition().getName());
+            int startPhase = ParticipantUtils.findStartPhase(toscaNodeTemplate.getProperties());
+            if (currentPhase != startPhase) {
+                continue;
+            }
+            if ((now - lastMsg) > maxStatusWaitMs) {
+                LOGGER.debug("Report timeout for the ac instance {}", automationComposition.getInstanceId());
+                automationComposition.setStateChangeResult(StateChangeResult.TIMEOUT);
+                automationCompositionProvider.updateAutomationComposition(automationComposition);
+                break;
+            }
         }
     }
 
     private void sendAutomationCompositionMsg(AutomationComposition automationComposition,
             ToscaServiceTemplate serviceTemplate, int startPhase, boolean firstStartPhase) {
+        automationComposition.setLastMsg(TimestampHelper.now());
+        automationComposition.setPhase(startPhase);
+        automationCompositionProvider.updateAutomationComposition(automationComposition);
+
         if (DeployState.DEPLOYING.equals(automationComposition.getDeployState())) {
             LOGGER.debug("retry message AutomationCompositionUpdate");
             automationCompositionDeployPublisher.send(automationComposition, serviceTemplate, startPhase,
@@ -278,7 +280,5 @@
             LOGGER.debug("retry message AutomationCompositionStateChange");
             automationCompositionStateChangePublisher.send(automationComposition, startPhase, firstStartPhase);
         }
-        // Clear timeout on automation composition
-        clearTimeout(automationComposition, false);
     }
 }
diff --git a/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/TimeoutHandler.java b/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/TimeoutHandler.java
deleted file mode 100644
index 3b34252..0000000
--- a/runtime-acm/src/main/java/org/onap/policy/clamp/acm/runtime/supervision/TimeoutHandler.java
+++ /dev/null
@@ -1,87 +0,0 @@
-/*-
- * ============LICENSE_START=======================================================
- *  Copyright (C) 2023-2024 Nordix Foundation.
- * ================================================================================
- * 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.
- *
- * SPDX-License-Identifier: Apache-2.0
- * ============LICENSE_END=========================================================
- */
-
-package org.onap.policy.clamp.acm.runtime.supervision;
-
-import java.time.Instant;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-import lombok.Getter;
-import lombok.Setter;
-
-public class TimeoutHandler<K> {
-    @Getter
-    @Setter
-    private long maxWaitMs;
-
-    private final Set<K> mapTimeout = new HashSet<>();
-    private final Map<K, Long> mapTimer = new HashMap<>();
-
-    public long getDuration(K id) {
-        mapTimer.putIfAbsent(id, getEpochMilli());
-        return getEpochMilli() - mapTimer.get(id);
-    }
-
-    /**
-     * Reset timer and timeout by id.
-     *
-     * @param id the id
-     */
-    public void clear(K id) {
-        mapTimeout.remove(id);
-        mapTimer.put(id, getEpochMilli());
-    }
-
-    /**
-     * Remove timer and timeout by id.
-     *
-     * @param id the id
-     */
-    public void remove(K id) {
-        mapTimeout.remove(id);
-        mapTimer.remove(id);
-    }
-
-    /**
-     * Remove elements that are not present in set.
-     *
-     * @param set the elements that should be present
-     */
-    public void removeIfNotPresent(final Set<K> set) {
-        var res = mapTimeout.stream().filter(el -> !set.contains(el)).toList();
-        if (!res.isEmpty()) {
-            res.forEach(this::remove);
-        }
-    }
-
-    public void setTimeout(K id) {
-        mapTimeout.add(id);
-    }
-
-    public boolean isTimeout(K id) {
-        return mapTimeout.contains(id);
-    }
-
-    protected long getEpochMilli() {
-        return Instant.now().toEpochMilli();
-    }
-}
diff --git a/runtime-acm/src/test/java/org/onap/policy/clamp/acm/runtime/instantiation/rest/InstantiationControllerTest.java b/runtime-acm/src/test/java/org/onap/policy/clamp/acm/runtime/instantiation/rest/InstantiationControllerTest.java
index cd1a385..bcfdea1 100644
--- a/runtime-acm/src/test/java/org/onap/policy/clamp/acm/runtime/instantiation/rest/InstantiationControllerTest.java
+++ b/runtime-acm/src/test/java/org/onap/policy/clamp/acm/runtime/instantiation/rest/InstantiationControllerTest.java
@@ -1,6 +1,6 @@
 /*-
  * ============LICENSE_START=======================================================
- *  Copyright (C) 2021-2023 Nordix Foundation.
+ *  Copyright (C) 2021-2024 Nordix Foundation.
  *  Modifications Copyright (C) 2021 AT&T Intellectual Property. All rights reserved.
  * ================================================================================
  * Licensed under the Apache License, Version 2.0 (the "License");
@@ -155,6 +155,7 @@
 
         var automationCompositionFromDb =
                 instantiationProvider.getAutomationComposition(compositionId, instResponse.getInstanceId());
+        automationCompositionFromRsc.setLastMsg(automationCompositionFromDb.getLastMsg());
 
         assertNotNull(automationCompositionFromDb);
         assertEquals(automationCompositionFromRsc, automationCompositionFromDb);
@@ -223,7 +224,9 @@
         var automationCompositionsQuery = rawresp.readEntity(AutomationCompositions.class);
         assertNotNull(automationCompositionsQuery);
         assertThat(automationCompositionsQuery.getAutomationCompositionList()).hasSize(1);
-        assertEquals(automationComposition, automationCompositionsQuery.getAutomationCompositionList().get(0));
+        var automationCompositionRc = automationCompositionsQuery.getAutomationCompositionList().get(0);
+        automationComposition.setLastMsg(automationCompositionRc.getLastMsg());
+        assertEquals(automationComposition, automationCompositionRc);
     }
 
     @Test
@@ -241,6 +244,7 @@
         assertEquals(Response.Status.OK.getStatusCode(), rawresp.getStatus());
         var automationCompositionGet = rawresp.readEntity(AutomationComposition.class);
         assertNotNull(automationCompositionGet);
+        automationComposition.setLastMsg(automationCompositionGet.getLastMsg());
         assertEquals(automationComposition, automationCompositionGet);
     }
 
@@ -272,7 +276,9 @@
 
         assertNotNull(automationCompositionsFromDb);
         assertThat(automationCompositionsFromDb.getAutomationCompositionList()).hasSize(1);
-        assertEquals(automationComposition, automationCompositionsFromDb.getAutomationCompositionList().get(0));
+        var acFromDb = automationCompositionsFromDb.getAutomationCompositionList().get(0);
+        automationComposition.setLastMsg(acFromDb.getLastMsg());
+        assertEquals(automationComposition, acFromDb);
     }
 
     @Test
diff --git a/runtime-acm/src/test/java/org/onap/policy/clamp/acm/runtime/supervision/SupervisionScannerTest.java b/runtime-acm/src/test/java/org/onap/policy/clamp/acm/runtime/supervision/SupervisionScannerTest.java
index 8ed250f..d5163be 100644
--- a/runtime-acm/src/test/java/org/onap/policy/clamp/acm/runtime/supervision/SupervisionScannerTest.java
+++ b/runtime-acm/src/test/java/org/onap/policy/clamp/acm/runtime/supervision/SupervisionScannerTest.java
@@ -243,10 +243,15 @@
         var automationComposition = InstantiationUtils.getAutomationCompositionFromResource(AC_JSON, "Crud");
         automationComposition.setDeployState(DeployState.DEPLOYING);
         automationComposition.setLockState(LockState.NONE);
+        automationComposition.setPhase(0);
         automationComposition.setCompositionId(compositionId);
-        for (Map.Entry<UUID, AutomationCompositionElement> entry : automationComposition.getElements().entrySet()) {
+        for (var entry : automationComposition.getElements().entrySet()) {
             entry.getValue().setDeployState(DeployState.DEPLOYING);
         }
+        // the first element is already completed
+        automationComposition.getElements().entrySet().iterator().next().getValue()
+                .setDeployState(DeployState.DEPLOYED);
+
         var automationCompositionProvider = mock(AutomationCompositionProvider.class);
         when(automationCompositionProvider.getAcInstancesInTransition()).thenReturn(List.of(automationComposition));
 
@@ -261,14 +266,22 @@
                 acRuntimeParameterGroup);
 
         automationComposition.setStateChangeResult(StateChangeResult.NO_ERROR);
+        automationComposition.setLastMsg(TimestampHelper.now());
         scannerObj2.run();
         verify(automationCompositionProvider, times(1)).updateAutomationComposition(any(AutomationComposition.class));
         assertEquals(StateChangeResult.TIMEOUT, automationComposition.getStateChangeResult());
 
+        //already in TIMEOUT
+        clearInvocations(automationCompositionProvider);
+        scannerObj2.run();
+        verify(automationCompositionProvider, times(0)).updateAutomationComposition(any(AutomationComposition.class));
+
+        clearInvocations(automationCompositionProvider);
         for (Map.Entry<UUID, AutomationCompositionElement> entry : automationComposition.getElements().entrySet()) {
             entry.getValue().setDeployState(DeployState.DEPLOYED);
         }
         scannerObj2.run();
+        verify(automationCompositionProvider, times(1)).updateAutomationComposition(any(AutomationComposition.class));
         assertEquals(StateChangeResult.NO_ERROR, automationComposition.getStateChangeResult());
     }
 
@@ -277,6 +290,7 @@
         var automationComposition = InstantiationUtils.getAutomationCompositionFromResource(AC_JSON, "Crud");
         automationComposition.setDeployState(DeployState.DEPLOYING);
         automationComposition.setLockState(LockState.NONE);
+        automationComposition.setPhase(0);
         automationComposition.setCompositionId(compositionId);
         for (var element : automationComposition.getElements().values()) {
             if ("org.onap.domain.database.Http_PMSHMicroserviceAutomationCompositionElement"
@@ -314,10 +328,15 @@
         var compositionTargetId = UUID.randomUUID();
         automationComposition.setCompositionTargetId(compositionTargetId);
         automationComposition.setLockState(LockState.LOCKED);
+        automationComposition.setLastMsg(TimestampHelper.now());
+        automationComposition.setPhase(0);
         for (var element : automationComposition.getElements().values()) {
             element.setDeployState(DeployState.DEPLOYED);
             element.setLockState(LockState.LOCKED);
         }
+        // first element is not migrated yet
+        automationComposition.getElements().entrySet().iterator().next().getValue()
+                .setDeployState(DeployState.MIGRATING);
 
         var automationCompositionProvider = mock(AutomationCompositionProvider.class);
         when(automationCompositionProvider.getAcInstancesInTransition()).thenReturn(List.of(automationComposition));
@@ -331,7 +350,15 @@
                 acRuntimeParameterGroup);
 
         supervisionScanner.run();
+        verify(automationCompositionProvider, times(0)).updateAutomationComposition(any(AutomationComposition.class));
+        assertEquals(DeployState.MIGRATING, automationComposition.getDeployState());
+
+        // first element is migrated
+        automationComposition.getElements().entrySet().iterator().next().getValue()
+                .setDeployState(DeployState.DEPLOYED);
+        supervisionScanner.run();
         verify(automationCompositionProvider, times(1)).updateAutomationComposition(any(AutomationComposition.class));
+
         assertEquals(DeployState.DEPLOYED, automationComposition.getDeployState());
         assertEquals(compositionTargetId, automationComposition.getCompositionId());
     }
@@ -342,6 +369,7 @@
         automationComposition.setDeployState(DeployState.DEPLOYED);
         automationComposition.setLockState(LockState.UNLOCKING);
         automationComposition.setCompositionId(compositionId);
+        automationComposition.setPhase(0);
         for (var element : automationComposition.getElements().values()) {
             if ("org.onap.domain.database.Http_PMSHMicroserviceAutomationCompositionElement"
                     .equals(element.getDefinition().getName())) {
diff --git a/runtime-acm/src/test/java/org/onap/policy/clamp/acm/runtime/supervision/TimeoutHandlerTest.java b/runtime-acm/src/test/java/org/onap/policy/clamp/acm/runtime/supervision/TimeoutHandlerTest.java
deleted file mode 100644
index 21c5b3d..0000000
--- a/runtime-acm/src/test/java/org/onap/policy/clamp/acm/runtime/supervision/TimeoutHandlerTest.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*-
- * ============LICENSE_START=======================================================
- *  Copyright (C) 2023 Nordix Foundation.
- * ================================================================================
- * 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.
- *
- * SPDX-License-Identifier: Apache-2.0
- * ============LICENSE_END=========================================================
- */
-
-package org.onap.policy.clamp.acm.runtime.supervision;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-import org.junit.jupiter.api.Test;
-
-class TimeoutHandlerTest {
-
-    private static final int ID = 1;
-
-    @Test
-    void testFault() {
-        var timeoutHandler = new TimeoutHandler<Integer>();
-        timeoutHandler.setTimeout(ID);
-        assertThat(timeoutHandler.isTimeout(ID)).isTrue();
-        timeoutHandler.clear(ID);
-        assertThat(timeoutHandler.isTimeout(ID)).isFalse();
-    }
-
-    @Test
-    void testDuration() {
-        var timeoutHandler = new TimeoutHandler<Integer>() {
-            long epochMilli = 0;
-
-            @Override
-            protected long getEpochMilli() {
-                return epochMilli;
-            }
-        };
-        timeoutHandler.epochMilli = 100;
-        var result = timeoutHandler.getDuration(ID);
-        assertThat(result).isZero();
-
-        timeoutHandler.epochMilli += 100;
-        result = timeoutHandler.getDuration(ID);
-        assertThat(result).isEqualTo(100);
-
-        timeoutHandler.epochMilli += 100;
-        result = timeoutHandler.getDuration(ID);
-        assertThat(result).isEqualTo(200);
-
-        timeoutHandler.epochMilli += 100;
-        timeoutHandler.clear(ID);
-        result = timeoutHandler.getDuration(ID);
-        assertThat(result).isZero();
-    }
-}