### Elasticsearch Version

7.17, 8.6, 8.5

### Installed Plugins

_No response_

### Java Version

_bundled_

### OS Version

Darwin

### Problem Description

Some ILM steps are implicit ie. injected (`migrate` action) or the policy/phase `complete` step.

ILM tries to honour the cached ILM phase however, these implicit steps are not **seen** as cached so might still end up with errors like:
```
step [{"phase":"warm","action":"complete","name":"complete"}] for index [index] with policy [my-policy] does not exist
```
or
```
step [{"phase":"warm","action":"migrate","name":"check-migration"}] for index [index] with policy [my-policy] does not exist
```

The problem lies [here](https://github.com/elastic/elasticsearch/blob/main/x-pack/plugin/ilm/src/main/java/org/elasticsearch/xpack/ilm/IndexLifecycleTransition.java#L86) where we only look at the the explicitly declared actions in the cached phase

### Steps to Reproduce

```    
public void testValidateTransitionToCachedStepWhenMissingPhaseFromPolicy() {
        // we'll test the case when the warm phase was deleted and the next step is the phase complete one

        LifecycleExecutionState.Builder executionState = LifecycleExecutionState.builder()
            .setPhase("warm")
            .setAction("migrate")
            .setStep("check-migration")
            .setPhaseDefinition("""
                {
                        "policy" : "my-policy",
                        "phase_definition" : {
                          "min_age" : "20m",
                          "actions" : {
                            "set_priority" : {
                              "priority" : 150
                            }
                          }
                        },
                        "version" : 1,
                        "modified_date_in_millis" : 1578521007076
                      }""");

        IndexMetadata meta = buildIndexMetadata("my-policy", executionState);

        try (Client client = new NoOpClient(getTestName())) {
            Step.StepKey currentStepKey = new Step.StepKey("warm", MigrateAction.NAME, DataTierMigrationRoutedStep.NAME);
            Step.StepKey nextStepKey = new Step.StepKey("warm", PhaseCompleteStep.NAME, PhaseCompleteStep.NAME);

            Step.StepKey waitForRolloverStepKey = new Step.StepKey("hot", RolloverAction.NAME, WaitForRolloverReadyStep.NAME);
            Step.StepKey rolloverStepKey = new Step.StepKey("hot", RolloverAction.NAME, RolloverStep.NAME);
            Step waitForRolloverReadyStep = new WaitForRolloverReadyStep(
                waitForRolloverStepKey,
                rolloverStepKey,
                client,
                null,
                null,
                null,
                1L,
                null,
                null,
                null,
                null,
                null,
                null
            );

            try {
                IndexLifecycleTransition.validateTransition(
                    meta,
                    currentStepKey,
                    nextStepKey,
                    createOneStepPolicyStepRegistry("my-policy", waitForRolloverReadyStep)
                );
            } catch (Exception e) {
                logger.error(e.getMessage(), e);
                fail("validateTransition should not throw exception on valid transitions");
            }
        }
    }

    public void testValidateTransitionToInjectedMissingStep() {
        // we'll test the case when the warm phase was deleted and the next step is an injected one

        LifecycleExecutionState.Builder executionState = LifecycleExecutionState.builder()
            .setPhase("warm")
            .setAction("migrate")
            .setStep("migrate")
            .setPhaseDefinition("""
                {
                        "policy" : "my-policy",
                        "phase_definition" : {
                          "min_age" : "20m",
                          "actions" : {
                            "set_priority" : {
                              "priority" : 150
                            }
                          }
                        },
                        "version" : 1,
                        "modified_date_in_millis" : 1578521007076
                      }""");

        IndexMetadata meta = buildIndexMetadata("my-policy", executionState);

        try (Client client = new NoOpClient(getTestName())) {
            Step.StepKey currentStepKey = new Step.StepKey("warm", MigrateAction.NAME, MigrateAction.NAME);
            Step.StepKey nextStepKey = new Step.StepKey("warm", MigrateAction.NAME, DataTierMigrationRoutedStep.NAME);

            Step.StepKey waitForRolloverStepKey = new Step.StepKey("hot", RolloverAction.NAME, WaitForRolloverReadyStep.NAME);
            Step.StepKey rolloverStepKey = new Step.StepKey("hot", RolloverAction.NAME, RolloverStep.NAME);
            Step waitForRolloverReadyStep = new WaitForRolloverReadyStep(
                waitForRolloverStepKey,
                rolloverStepKey,
                client,
                null,
                null,
                null,
                1L,
                null,
                null,
                null,
                null,
                null,
                null
            );

            try {
                IndexLifecycleTransition.validateTransition(
                    meta,
                    currentStepKey,
                    nextStepKey,
                    createOneStepPolicyStepRegistry("my-policy", waitForRolloverReadyStep)
                );
            } catch (Exception e) {
                logger.error(e.getMessage(), e);
                fail("validateTransition should not throw exception on valid transitions");
            }
        }
    }
```

### Logs (if relevant)

_No response_
x-pack/plugin/ilm/src/main/java/org/elasticsearch/xpack/ilm/PolicyStepsRegistry.java
} catch (IOException e) {
            throw new ElasticsearchException("failed to load cached steps for " + stepKey, e);
        } catch (XContentParseException parseErr) {
            throw new XContentParseException(
                parseErr.getLocation(),
                "failed to load steps for " + stepKey + " from [" + phaseJson + "]",
                parseErr
            );
        }
        assert phaseSteps.stream().allMatch(step -> step.getKey().phase().equals(phase))
            : "expected phase steps loaded from phase definition for ["
                + index.getName()
                + "] to be in phase ["
                + phase
                + "] but they were not, steps: "
                + phaseSteps;
        
        final Step s = phaseSteps.stream().filter(step -> step.getKey().equals(stepKey)).findFirst().orElse(null);
        if (s != null) {
            cachedSteps.put(indexMetadata.getIndex(), Tuple.tuple(indexMetadata, s));
        }
        return s;
    }
    /**
     * Given a policy and stepkey, return true if a step exists, false otherwise
     */
    public boolean stepExists(final String policy, final Step.StepKey stepKey) {
        Map<Step.StepKey, Step> steps = stepMap.get(policy);
        if (steps == null) {
            return false;