Repository: RasaHQ/rasa
Pull request: domain_for_core_training_provider
**Proposed changes**:
- closes #10145

**Status (please check what you already did)**:
- [ ] added some tests for the functionality
- [ ] updated the documentation
- [ ] updated the changelog (please check [changelog](https://github.com/RasaHQ/rasa/tree/main/changelog) for instructions)
- [ ] reformat files using `black` (please check [Readme](https://github.com/RasaHQ/rasa#code-style) for instructions)


Problem statement:
Changing the required slots of a form retrains the model
### Rasa Open Source version

3.0.0rc1

### Rasa SDK version

_No response_

### Rasa X version

_No response_

### Python version

3.8

### What operating system are you using?

OSX

### What happened?

When you change the required slots of a form in the domain then the policies retrain. This is not necessary. 

### Command / Request

_No response_

### Relevant log output

_No response_

**Definition of done**
- [ ] List what changes to domain would need to cause a retrain
- [ ] Change the` domain without responses` component (so only the things that need to cause a retrain actually cause a retrain)
- [ ] Add integration test that ensures this works as expected

Hints from issue discussion:

Commit message: Merge branch '3.0.x' into 10145/avoid-core-retraining

Patch to review:
diff --git a/rasa/core/actions/action.py b/rasa/core/actions/action.py
index 19f22daaa962..97e50bb5128b 100644
--- a/rasa/core/actions/action.py
+++ b/rasa/core/actions/action.py
@@ -38,7 +38,12 @@
     ACTIVE_LOOP,
     ACTION_VALIDATE_SLOT_MAPPINGS,
     MAPPING_TYPE,
+    MAPPING_CUSTOM,
     PREDEFINED_MAPPINGS,
+    MAPPING_FROM_ENTITY,
+    MAPPING_FROM_INTENT,
+    MAPPING_FROM_TEXT,
+    MAPPING_FROM_TRIGGER_INTENT,
     LOOP_REJECTED,
 )
 from rasa.shared.core.domain import Domain
@@ -1009,8 +1014,9 @@ def _matches_mapping_conditions(
     def _verify_mapping_conditions(
         mapping: Dict[Text, Any], tracker: "DialogueStateTracker", slot_name: Text
     ) -> bool:
-        if mapping.get(MAPPING_CONDITIONS) and mapping.get(MAPPING_TYPE) != str(
-            SlotMapping.FROM_TRIGGER_INTENT
+        if (
+            mapping.get(MAPPING_CONDITIONS)
+            and mapping.get(MAPPING_TYPE) != MAPPING_FROM_TRIGGER_INTENT
         ):
             if not ActionExtractSlots._matches_mapping_conditions(
                 mapping, tracker, slot_name
@@ -1129,7 +1135,7 @@ def _fails_unique_entity_mapping_check(
     ) -> bool:
         from rasa.core.actions.forms import FormAction
 
-        if mapping.get("type") != str(SlotMapping.FROM_ENTITY):
+        if mapping.get("type") != MAPPING_FROM_ENTITY:
             return False
 
         form_name = tracker.active_loop_name
@@ -1196,9 +1202,7 @@ async def run(
                     if tracker.get_slot(slot.name) != value:
                         slot_events.append(SlotSet(slot.name, value))
 
-                should_fill_custom_slot = mapping.get(MAPPING_TYPE) == str(
-                    SlotMapping.CUSTOM
-                )
+                should_fill_custom_slot = mapping.get(MAPPING_TYPE) == MAPPING_CUSTOM
 
                 if should_fill_custom_slot:
                     (
@@ -1225,20 +1229,20 @@ def extract_slot_value_from_predefined_mapping(
     mapping: Dict[Text, Any], tracker: "DialogueStateTracker",
 ) -> List[Any]:
     """Extracts slot value if slot has an applicable predefined mapping."""
-    should_fill_entity_slot = mapping.get(MAPPING_TYPE) == str(
-        SlotMapping.FROM_ENTITY
-    ) and SlotMapping.entity_is_desired(mapping, tracker,)
+    should_fill_entity_slot = mapping.get(
+        MAPPING_TYPE
+    ) == MAPPING_FROM_ENTITY and SlotMapping.entity_is_desired(mapping, tracker,)
 
-    should_fill_intent_slot = mapping.get(MAPPING_TYPE) == str(SlotMapping.FROM_INTENT)
+    should_fill_intent_slot = mapping.get(MAPPING_TYPE) == MAPPING_FROM_INTENT
 
-    should_fill_text_slot = mapping.get(MAPPING_TYPE) == str(SlotMapping.FROM_TEXT)
+    should_fill_text_slot = mapping.get(MAPPING_TYPE) == MAPPING_FROM_TEXT
 
     active_loops_in_mapping_conditions = [
         active_loop.get(ACTIVE_LOOP)
         for active_loop in mapping.get(MAPPING_CONDITIONS, [])
     ]
     should_fill_trigger_slot = (
-        mapping.get(MAPPING_TYPE) == str(SlotMapping.FROM_TRIGGER_INTENT)
+        mapping.get(MAPPING_TYPE) == MAPPING_FROM_TRIGGER_INTENT
         and tracker.active_loop_name not in active_loops_in_mapping_conditions
     )
 
diff --git a/rasa/core/actions/forms.py b/rasa/core/actions/forms.py
index 831d0253240b..0b6ae134c9e1 100644
--- a/rasa/core/actions/forms.py
+++ b/rasa/core/actions/forms.py
@@ -7,6 +7,7 @@
 from rasa.core.actions.loops import LoopAction
 from rasa.core.channels import OutputChannel
 from rasa.shared.core.domain import Domain, KEY_SLOTS
+from rasa.shared.core.constants import MAPPING_FROM_ENTITY
 
 from rasa.core.actions.action import ActionExecutionRejection, RemoteAction
 from rasa.shared.core.constants import (
@@ -88,7 +89,7 @@ def from_entity(
         )
 
         return {
-            "type": str(SlotMapping.FROM_ENTITY),
+            "type": MAPPING_FROM_ENTITY,
             "entity": entity,
             "intent": intent,
             "not_intent": not_intent,
@@ -151,7 +152,7 @@ def _create_unique_entity_mappings(self, domain: Domain) -> Set[Text]:
         domain_slots = domain.as_dict().get(KEY_SLOTS)
         for slot in domain.required_slots_for_form(self.name()):
             for slot_mapping in domain_slots.get(slot).get("mappings"):
-                if slot_mapping.get("type") == str(SlotMapping.FROM_ENTITY):
+                if slot_mapping.get("type") == MAPPING_FROM_ENTITY:
                     mapping_as_string = json.dumps(slot_mapping, sort_keys=True)
                     if mapping_as_string in unique_entity_slot_mappings:
                         unique_entity_slot_mappings.remove(mapping_as_string)
diff --git a/rasa/core/migrate.py b/rasa/core/migrate.py
index 07530bb136a9..dfc11eff655c 100644
--- a/rasa/core/migrate.py
+++ b/rasa/core/migrate.py
@@ -9,14 +9,19 @@
     REQUIRED_SLOTS_KEY,
     IGNORED_INTENTS,
 )
-from rasa.shared.core.constants import ACTIVE_LOOP, REQUESTED_SLOT
+from rasa.shared.core.constants import (
+    ACTIVE_LOOP,
+    REQUESTED_SLOT,
+    MAPPING_FROM_ENTITY,
+    MAPPING_FROM_TRIGGER_INTENT,
+    MAPPING_TYPE,
+)
 from rasa.shared.core.domain import (
     KEY_ENTITIES,
     KEY_SLOTS,
     KEY_FORMS,
     Domain,
 )
-from rasa.shared.core.slot_mappings import SlotMapping
 from rasa.shared.exceptions import RasaException
 
 
@@ -31,9 +36,9 @@ def _create_back_up(
 def _get_updated_mapping_condition(
     condition: Dict[Text, Text], mapping: Dict[Text, Any], slot_name: Text
 ) -> Dict[Text, Text]:
-    if mapping.get("type") not in [
-        str(SlotMapping.FROM_ENTITY),
-        str(SlotMapping.FROM_TRIGGER_INTENT),
+    if mapping.get(MAPPING_TYPE) not in [
+        MAPPING_FROM_ENTITY,
+        MAPPING_FROM_TRIGGER_INTENT,
     ]:
         return {**condition, REQUESTED_SLOT: slot_name}
     return condition
@@ -113,7 +118,7 @@ def _migrate_auto_fill(
 ) -> Dict[Text, Any]:
     if slot_name in entities and properties.get("auto_fill", True) is True:
         from_entity_mapping = {
-            "type": str(SlotMapping.FROM_ENTITY),
+            "type": MAPPING_FROM_ENTITY,
             "entity": slot_name,
         }
         mappings = properties.get("mappings", [])
diff --git a/rasa/engine/recipes/default_recipe.py b/rasa/engine/recipes/default_recipe.py
index b7d1d819a8b9..d00cf0b2439d 100644
--- a/rasa/engine/recipes/default_recipe.py
+++ b/rasa/engine/recipes/default_recipe.py
@@ -30,8 +30,8 @@
 from rasa.engine.storage.resource import Resource
 from rasa.graph_components.converters.nlu_message_converter import NLUMessageConverter
 from rasa.graph_components.providers.domain_provider import DomainProvider
-from rasa.graph_components.providers.domain_without_response_provider import (
-    DomainWithoutResponsesProvider,
+from rasa.graph_components.providers.domain_for_core_training_provider import (
+    DomainForCoreTrainingProvider,
 )
 from rasa.graph_components.providers.nlu_training_data_provider import (
     NLUTrainingDataProvider,
@@ -449,9 +449,9 @@ def _add_core_train_nodes(
             is_target=True,
             is_input=True,
         )
-        train_nodes["domain_without_responses_provider"] = SchemaNode(
+        train_nodes["domain_for_core_training_provider"] = SchemaNode(
             needs={"domain": "domain_provider"},
-            uses=DomainWithoutResponsesProvider,
+            uses=DomainForCoreTrainingProvider,
             constructor_name="create",
             fn="provide",
             config={},
@@ -468,7 +468,7 @@ def _add_core_train_nodes(
         train_nodes["training_tracker_provider"] = SchemaNode(
             needs={
                 "story_graph": "story_graph_provider",
-                "domain": "domain_without_responses_provider",
+                "domain": "domain_for_core_training_provider",
             },
             uses=TrainingTrackerProvider,
             constructor_name="create",
@@ -499,7 +499,7 @@ def _add_core_train_nodes(
             train_nodes[f"train_{component_name}{idx}"] = SchemaNode(
                 needs={
                     "training_trackers": "training_tracker_provider",
-                    "domain": "domain_without_responses_provider",
+                    "domain": "domain_for_core_training_provider",
                     **(
                         {"precomputations": "end_to_end_features_provider"}
                         if requires_end_to_end_data
@@ -522,7 +522,7 @@ def _add_end_to_end_features_for_training(
         train_nodes["story_to_nlu_training_data_converter"] = SchemaNode(
             needs={
                 "story_graph": "story_graph_provider",
-                "domain": "domain_without_responses_provider",
+                "domain": "domain_for_core_training_provider",
             },
             uses=CoreFeaturizationInputConverter,
             constructor_name="create",
diff --git a/rasa/graph_components/providers/domain_for_core_training_provider.py b/rasa/graph_components/providers/domain_for_core_training_provider.py
new file mode 100644
index 000000000000..88f8a779625d
--- /dev/null
+++ b/rasa/graph_components/providers/domain_for_core_training_provider.py
@@ -0,0 +1,92 @@
+from __future__ import annotations
+
+import copy
+from typing import Dict, Text, Any
+
+from rasa.engine.graph import GraphComponent, ExecutionContext
+from rasa.engine.storage.resource import Resource
+from rasa.engine.storage.storage import ModelStorage
+from rasa.shared.core.domain import (
+    KEY_RESPONSES,
+    Domain,
+    SESSION_CONFIG_KEY,
+    KEY_SLOTS,
+    KEY_FORMS,
+)
+from rasa.shared.core.constants import SLOT_MAPPINGS
+
+
+class DomainForCoreTrainingProvider(GraphComponent):
+    """Provides domain without information that is irrelevant for core training.
+
+    The information that we retain includes:
+    - intents and their "used" and "ignored" entities because intents influence the
+      next action prediction directly and the latter flags determine whehter the
+      listed entities influence the next action prediction
+    - entities, their roles and groups, and their `influence_conversation` flag because
+      all of those items are used by policies
+    - slots names along with their types, since this type information determines the
+      pre-featurization of slot values
+    - response keys (i.e. `utter_*) because those keys may appear in stories
+    - form names because those appear in stories
+
+    This information that we drop (or replace with default values) includes:
+    - the 'session_config' which determines details of a session e.g. whether data is
+      transferred from one session to the next (this is replaced with defaults as it
+      cannot just be removed)
+    - how slots are filled (i.e. 'mappings' key under 'slots') because slots are not
+      actually extracted by core components
+    - the actual text of a 'response' because those are only used by response selectors
+    - the actual configuration of 'forms' because those are not actually executed
+      by core components
+
+    References:
+        - `rasa.core.featurizer.tracker_featurizer.py` (used by all policies)
+        - `rasa.core.featurizer.single_state_featurizer.py` (used by ML policies)
+        - `rasa.shared.core.domain.get_active_state` (used by above)
+        - `rasa.shared.core.slots.as_features` (used by above)
+    """
+
+    @classmethod
+    def create(
+        cls,
+        config: Dict[Text, Any],
+        model_storage: ModelStorage,
+        resource: Resource,
+        execution_context: ExecutionContext,
+    ) -> DomainForCoreTrainingProvider:
+        """Creates component (see parent class for full docstring)."""
+        return cls()
+
+    def provide(self, domain: Domain) -> Domain:
+        """Recreates the given domain but drops information that is irrelevant for core.
+
+        Args:
+            domain: A domain.
+
+        Returns:
+            A similar domain without information that is irrelevant for core training.
+        """
+        return self.create_pruned_version(domain)
+
+    @staticmethod
+    def create_pruned_version(domain: Domain) -> Domain:
+        """Recreates the given domain but drops information that is irrelevant for core.
+
+        Args:
+            domain: A domain.
+
+        Returns:
+             A similar domain without information that is irrelevant for core training.
+        """
+        serialized_domain = copy.deepcopy(domain.as_dict())
+
+        serialized_domain.pop("config", None)  # `store_entities_as_slots`
+        serialized_domain.pop(SESSION_CONFIG_KEY, None)
+        for slot in serialized_domain[KEY_SLOTS]:
+            serialized_domain[KEY_SLOTS][slot][SLOT_MAPPINGS] = []
+        for response_name in serialized_domain[KEY_RESPONSES]:
+            serialized_domain[KEY_RESPONSES][response_name] = []
+        for form_name in serialized_domain[KEY_FORMS]:
+            serialized_domain[KEY_FORMS][form_name] = {}
+        return Domain.from_dict(serialized_domain)
diff --git a/rasa/graph_components/providers/domain_without_response_provider.py b/rasa/graph_components/providers/domain_without_response_provider.py
deleted file mode 100644
index 75a49ff513ee..000000000000
--- a/rasa/graph_components/providers/domain_without_response_provider.py
+++ /dev/null
@@ -1,41 +0,0 @@
-from __future__ import annotations
-
-import copy
-from typing import Dict, Text, Any
-
-from rasa.engine.graph import GraphComponent, ExecutionContext
-from rasa.engine.storage.resource import Resource
-from rasa.engine.storage.storage import ModelStorage
-from rasa.shared.core.domain import KEY_RESPONSES, Domain
-
-
-class DomainWithoutResponsesProvider(GraphComponent):
-    """Provides domain without information about responses."""
-
-    @classmethod
-    def create(
-        cls,
-        config: Dict[Text, Any],
-        model_storage: ModelStorage,
-        resource: Resource,
-        execution_context: ExecutionContext,
-    ) -> DomainWithoutResponsesProvider:
-        """Creates component (see parent class for full docstring)."""
-        return cls()
-
-    def provide(self, domain: Domain) -> Domain:
-        """Recreates the given domain but acts as if responses have not been specified.
-
-        Args:
-            domain: A domain.
-
-        Returns:
-            Domain that has been created from the same parameters as the given domain
-            but with an empty set of responses.
-        """
-        serialized_domain = copy.deepcopy(domain.as_dict())
-
-        for response_name in serialized_domain[KEY_RESPONSES]:
-            serialized_domain[KEY_RESPONSES][response_name] = []
-
-        return Domain.from_dict(serialized_domain)
diff --git a/rasa/graph_components/validators/finetuning_validator.py b/rasa/graph_components/validators/finetuning_validator.py
index 86b7426bacb3..a53e73c05e0c 100644
--- a/rasa/graph_components/validators/finetuning_validator.py
+++ b/rasa/graph_components/validators/finetuning_validator.py
@@ -13,9 +13,12 @@
 from rasa.shared.importers.importer import TrainingDataImporter
 import rasa.shared.utils.io
 from rasa.utils.tensorflow.constants import EPOCHS
+from rasa.graph_components.providers.domain_for_core_training_provider import (
+    DomainForCoreTrainingProvider,
+)
 
 FINGERPRINT_CONFIG_WITHOUT_EPOCHS_KEY = "config-without-epochs"
-FINGERPRINT_CORE = "domain-without-responses"
+FINGERPRINT_CORE = "domain-without-responses"  # can we change the name of this key?
 FINGERPRINT_NLU = "nlu-labels"
 FINGERPRINT_VERSION = "rasa-version"
 
@@ -34,8 +37,8 @@ class FinetuningValidator(GraphComponent):
 
     Finetuning is possible if, compared to the initial training phase, it holds that
     1. the configuration (except for "epoch" keys) does not change
-    2. the domain (except for "responses") does not change - or we're not finetuning
-       the core part
+    2. the domain (except for e.g. "responses") does not change - or we're not
+       finetuning the core part
     3. the intents, entities, entity groups, entity roles, and action names that
        appeared in the original NLU training data, appear in the NLU training data
        used for finetuning, and no new such items (i.e. intents, entities, entity
@@ -150,17 +153,17 @@ def _validate(self, importer: TrainingDataImporter) -> None:
             # NOTE: If there's a consistency check between domain and core training data
             # that ensures domain and core training data are consistent, then we can
             # drop this check.
-            fingerprint_core = self._get_fingerprint_of_domain_without_responses(
+            fingerprint_core = self._get_fingerprint_of_domain_pruned_for_core(
                 domain=importer.get_domain()
             )
             self._compare_or_memorize(
                 fingerprint_key=FINGERPRINT_CORE,
                 new_fingerprint=fingerprint_core,
                 error_message=(
-                    "Cannot finetune because more than just the responses have been "
-                    "changed in the domain."
-                    "Please revert all settings in your domain file (except the "
-                    "'responses')."
+                    "Cannot finetune because keys that affect the training of core "
+                    "components have changed."
+                    "Please revert all settings in your domain file that affect the "
+                    "training of core components."
                 ),
             )
 
@@ -208,17 +211,16 @@ def _compare_or_memorize(
             self._fingerprints[fingerprint_key] = new_fingerprint
 
     @staticmethod
-    def _get_fingerprint_of_domain_without_responses(domain: Domain) -> Text:
-        """Returns a fingerprint of a version of the given domain without responses.
+    def _get_fingerprint_of_domain_pruned_for_core(domain: Domain) -> Text:
+        """Returns a fingerprint of a pruned version of the domain relevant for core.
 
         Args:
             domain: a domain
         Returns:
             fingerprint
         """
-        domain = copy.copy(domain)
-        domain.responses = {}
-        return domain.fingerprint()
+        pruned_domain = DomainForCoreTrainingProvider.create_pruned_version()
+        return pruned_domain.fingerprint()
 
     def _get_fingerprint_of_schema_without_irrelevant_keys(self,) -> Text:
         """Returns a fingerprint of the given schema with certain items removed.
diff --git a/rasa/shared/core/constants.py b/rasa/shared/core/constants.py
index 0e8c5ef227c6..937076d531a4 100644
--- a/rasa/shared/core/constants.py
+++ b/rasa/shared/core/constants.py
@@ -1,5 +1,4 @@
 import rasa.shared.constants as constants
-from rasa.shared.core.slot_mappings import SlotMapping
 
 DEFAULT_CATEGORICAL_SLOT_VALUE = "__other__"
 
@@ -86,17 +85,24 @@
     SLOT_LAST_OBJECT_TYPE,
 }
 
-PREDEFINED_MAPPINGS = {
-    str(SlotMapping.FROM_ENTITY),
-    str(SlotMapping.FROM_INTENT),
-    str(SlotMapping.FROM_TEXT),
-    str(SlotMapping.FROM_TRIGGER_INTENT),
-}
 
 SLOT_MAPPINGS = "mappings"
 MAPPING_CONDITIONS = "conditions"
 MAPPING_TYPE = "type"
 
+MAPPING_FROM_ENTITY = "from_entity"
+MAPPING_FROM_INTENT = "from_intent"
+MAPPING_FROM_TRIGGER_INTENT = "from_trigger_intent"
+MAPPING_FROM_TEXT = "from_text"
+MAPPING_CUSTOM = "custom"
+
+PREDEFINED_MAPPINGS = {
+    MAPPING_FROM_ENTITY,
+    MAPPING_FROM_INTENT,
+    MAPPING_FROM_TRIGGER_INTENT,
+    MAPPING_FROM_TEXT,
+}
+
 # the keys for `State` (USER, PREVIOUS_ACTION, SLOTS, ACTIVE_LOOP)
 # represent the origin of a `SubState`
 USER = "user"
diff --git a/rasa/shared/core/domain.py b/rasa/shared/core/domain.py
index cf12795fd929..5fad1c92a0b1 100644
--- a/rasa/shared/core/domain.py
+++ b/rasa/shared/core/domain.py
@@ -31,7 +31,12 @@
     IGNORED_INTENTS,
 )
 import rasa.shared.core.constants
-from rasa.shared.core.slot_mappings import SlotMapping
+from rasa.shared.core.constants import (
+    MAPPING_CUSTOM,
+    MAPPING_TYPE,
+    MAPPING_CONDITIONS,
+    MAPPING_FROM_ENTITY,
+)
 from rasa.shared.exceptions import RasaException, YamlException, YamlSyntaxException
 import rasa.shared.nlu.constants
 import rasa.shared.utils.validation
@@ -41,7 +46,6 @@
 from rasa.shared.core.slots import Slot, CategoricalSlot, TextSlot, AnySlot, ListSlot
 from rasa.shared.utils.validation import KEY_TRAINING_DATA_FORMAT_VERSION
 from rasa.shared.constants import RESPONSE_CONDITION
-from rasa.shared.core.constants import MAPPING_CONDITIONS
 from rasa.shared.nlu.constants import (
     ENTITY_ATTRIBUTE_TYPE,
     ENTITY_ATTRIBUTE_ROLE,
@@ -1222,9 +1226,9 @@ def slots_for_entities(self, entities: List[Dict[Text, Any]]) -> List[SlotSet]:
                 matching_entities = []
 
                 for mapping in slot.mappings:
-                    if mapping.get("type") != str(
-                        SlotMapping.FROM_ENTITY
-                    ) or mapping.get(MAPPING_CONDITIONS):
+                    if mapping.get("type") != MAPPING_FROM_ENTITY or mapping.get(
+                        MAPPING_CONDITIONS
+                    ):
                         continue
 
                     for entity in entities:
@@ -1779,10 +1783,10 @@ def count_slot_mapping_statistics(self) -> Tuple[int, int, int]:
         for slot in self.slots:
             total_mappings += len(slot.mappings)
             for mapping in slot.mappings:
-                if mapping.get("type") == str(SlotMapping.CUSTOM):
+                if mapping.get(MAPPING_TYPE) == str(MAPPING_CUSTOM):
                     custom_mappings += 1
 
-                if "conditions" in mapping:
+                if MAPPING_CONDITIONS in mapping:
                     conditional_mappings += 1
 
         return (total_mappings, custom_mappings, conditional_mappings)
diff --git a/rasa/shared/core/slot_mappings.py b/rasa/shared/core/slot_mappings.py
index fa171749b7da..ccb966263c1c 100644
--- a/rasa/shared/core/slot_mappings.py
+++ b/rasa/shared/core/slot_mappings.py
@@ -1,4 +1,3 @@
-from enum import Enum
 from typing import Text, Dict, Any, List, Optional, TYPE_CHECKING
 
 from rasa.shared.constants import DOCS_URL_SLOTS, IGNORED_INTENTS
@@ -7,6 +6,18 @@
     ENTITY_ATTRIBUTE_TYPE,
     ENTITY_ATTRIBUTE_ROLE,
     ENTITY_ATTRIBUTE_GROUP,
+    INTENT,
+    NOT_INTENT,
+    INTENT_NAME_KEY,
+)
+from rasa.shared.core.constants import (
+    SLOT_MAPPINGS,
+    MAPPING_FROM_INTENT,
+    MAPPING_FROM_TEXT,
+    MAPPING_FROM_TRIGGER_INTENT,
+    MAPPING_FROM_ENTITY,
+    MAPPING_CUSTOM,
+    MAPPING_CONDITIONS,
 )
 
 if TYPE_CHECKING:
@@ -14,18 +25,8 @@
     from rasa.shared.core.domain import Domain
 
 
-class SlotMapping(Enum):
-    """Defines the available slot mappings."""
-
-    FROM_ENTITY = 0
-    FROM_INTENT = 1
-    FROM_TRIGGER_INTENT = 2
-    FROM_TEXT = 3
-    CUSTOM = 4
-
-    def __str__(self) -> Text:
-        """Returns a string representation of the object."""
-        return self.name.lower()
+class SlotMapping:
+    """Defines functionality for the available slot mappings."""
 
     @staticmethod
     def validate(mapping: Dict[Text, Any], slot_name: Text) -> None:
@@ -48,11 +49,11 @@ def validate(mapping: Dict[Text, Any], slot_name: Text) -> None:
             )
 
         validations = {
-            str(SlotMapping.FROM_ENTITY): ["entity"],
-            str(SlotMapping.FROM_INTENT): ["value"],
-            str(SlotMapping.FROM_TRIGGER_INTENT): ["value"],
-            str(SlotMapping.FROM_TEXT): [],
-            str(SlotMapping.CUSTOM): [],
+            MAPPING_FROM_ENTITY: ["entity"],
+            MAPPING_FROM_INTENT: ["value"],
+            MAPPING_FROM_TRIGGER_INTENT: ["value"],
+            MAPPING_FROM_TEXT: [],
+            MAPPING_CUSTOM: [],
         }
 
         mapping_type = mapping.get("type")
@@ -80,7 +81,7 @@ def _get_active_loop_ignored_intents(
     ) -> List[Text]:
         from rasa.shared.core.constants import ACTIVE_LOOP
 
-        mapping_conditions = mapping.get("conditions")
+        mapping_conditions = mapping.get(MAPPING_CONDITIONS)
         active_loop_match = True
         ignored_intents = []
 
@@ -104,8 +105,8 @@ def intent_is_desired(
         mapping: Dict[Text, Any], tracker: "DialogueStateTracker", domain: "Domain",
     ) -> bool:
         """Checks whether user intent matches slot mapping intent specifications."""
-        mapping_intents = SlotMapping.to_list(mapping.get("intent", []))
-        mapping_not_intents = SlotMapping.to_list(mapping.get("not_intent", []))
+        mapping_intents = SlotMapping.to_list(mapping.get(INTENT, []))
+        mapping_not_intents = SlotMapping.to_list(mapping.get(NOT_INTENT, []))
 
         active_loop_name = tracker.active_loop_name
         if active_loop_name:
@@ -116,7 +117,7 @@ def intent_is_desired(
                 )
             )
 
-        intent = tracker.latest_message.intent.get("name")
+        intent = tracker.latest_message.intent.get(INTENT_NAME_KEY)
 
         intent_not_blocked = not mapping_intents and intent not in mapping_not_intents
 
@@ -183,7 +184,7 @@ def check_mapping_validity(
         from rasa.shared.core.constants import MAPPING_TYPE
 
         if (
-            mapping.get(MAPPING_TYPE) == str(SlotMapping.FROM_ENTITY)
+            mapping.get(MAPPING_TYPE) == MAPPING_FROM_ENTITY
             and mapping.get(ENTITY_ATTRIBUTE_TYPE) not in domain.entities
         ):
             rasa.shared.utils.io.raise_warning(
@@ -194,8 +195,8 @@ def check_mapping_validity(
             return False
 
         if (
-            mapping.get(MAPPING_TYPE) == str(SlotMapping.FROM_INTENT)
-            and mapping.get("intent") not in domain.intents
+            mapping.get(MAPPING_TYPE) == MAPPING_FROM_INTENT
+            and mapping.get(INTENT) not in domain.intents
         ):
             rasa.shared.utils.io.raise_warning(
                 f"Slot '{slot_name}' uses a 'from_intent' mapping for "
@@ -217,7 +218,7 @@ def validate_slot_mappings(domain_slots: Dict[Text, Any]) -> None:
     )
 
     for slot_name, properties in domain_slots.items():
-        mappings = properties.get("mappings")
+        mappings = properties.get(SLOT_MAPPINGS)
 
         for slot_mapping in mappings:
             SlotMapping.validate(slot_mapping, slot_name)
diff --git a/rasa/shared/nlu/constants.py b/rasa/shared/nlu/constants.py
index d13d64cc4a95..0c89d0d667ab 100644
--- a/rasa/shared/nlu/constants.py
+++ b/rasa/shared/nlu/constants.py
@@ -1,6 +1,7 @@
 TEXT = "text"
 TEXT_TOKENS = "text_tokens"
 INTENT = "intent"
+NOT_INTENT = "not_intent"
 RESPONSE = "response"
 RESPONSE_SELECTOR = "response_selector"
 INTENT_RESPONSE_KEY = "intent_response_key"
diff --git a/rasa/validator.py b/rasa/validator.py
index b497a4d33115..bc8178586b9d 100644
--- a/rasa/validator.py
+++ b/rasa/validator.py
@@ -18,7 +18,7 @@
 from rasa.shared.core.events import ActionExecuted, ActiveLoop
 from rasa.shared.core.events import UserUttered
 from rasa.shared.core.generator import TrainingDataGenerator
-from rasa.shared.core.slot_mappings import SlotMapping
+from rasa.shared.core.constants import MAPPING_FROM_TRIGGER_INTENT, MAPPING_TYPE
 from rasa.shared.core.training_data.structures import StoryGraph
 from rasa.shared.importers.importer import TrainingDataImporter
 from rasa.shared.nlu.training_data.training_data import TrainingData
@@ -381,7 +381,7 @@ def verify_slot_mappings(self) -> bool:
                         everything_is_alright = False
 
                 if (
-                    mapping.get("type") == str(SlotMapping.FROM_TRIGGER_INTENT)
+                    mapping.get(MAPPING_TYPE) == MAPPING_FROM_TRIGGER_INTENT
                     and slot.name not in all_required_slots
                 ):
                     rasa.shared.utils.io.raise_warning(
diff --git a/tests/graph_components/providers/test_domain_for_core_training_provider.py b/tests/graph_components/providers/test_domain_for_core_training_provider.py
new file mode 100644
index 000000000000..bc50f742a0cb
--- /dev/null
+++ b/tests/graph_components/providers/test_domain_for_core_training_provider.py
@@ -0,0 +1,97 @@
+from typing import Text, Union, Dict
+
+import pytest
+
+from rasa.graph_components.providers.domain_for_core_training_provider import (
+    DomainForCoreTrainingProvider,
+)
+from rasa.engine.graph import ExecutionContext
+from rasa.engine.storage.resource import Resource
+from rasa.engine.storage.storage import ModelStorage
+from rasa.shared.core.domain import (
+    KEY_RESPONSES,
+    Domain,
+    SESSION_CONFIG_KEY,
+    KEY_SLOTS,
+    KEY_FORMS,
+    SESSION_EXPIRATION_TIME_KEY,
+    CARRY_OVER_SLOTS_KEY,
+)
+from rasa.shared.core.constants import SLOT_MAPPINGS
+from rasa.shared.constants import REQUIRED_SLOTS_KEY
+
+
+@pytest.mark.parametrize(
+    "input_domain",
+    [
+        "data/test_domains/conditional_response_variations.yml",  # responses
+        "data/test_domains/default_with_slots.yml",  # slots
+        "data/test_domains/default_with_mapping.yml",  # slot mappings
+        {KEY_FORMS: {"form1": {REQUIRED_SLOTS_KEY: ["slot1"]}}},  # form
+        {
+            SESSION_CONFIG_KEY: {
+                SESSION_EXPIRATION_TIME_KEY: (
+                    2 * Domain.empty().session_config.session_expiration_time
+                ),
+                CARRY_OVER_SLOTS_KEY: (
+                    not Domain.empty().session_config.carry_over_slots
+                ),
+            }
+        },
+    ],
+)
+def test_provide(
+    default_model_storage: ModelStorage,
+    default_execution_context: ExecutionContext,
+    input_domain: Union[Text, Dict],
+):
+    # prepare input
+    if isinstance(input_domain, str):
+        original_domain = Domain.from_file(path=input_domain)
+    else:
+        original_domain = Domain.from_dict(input_domain)
+
+    # pass through component
+    component = DomainForCoreTrainingProvider.create(
+        {"arbitrary-unused": 234},
+        default_model_storage,
+        Resource("xy"),
+        default_execution_context,
+    )
+    modified_domain = component.provide(domain=original_domain)
+
+    # convert to dict for comparison
+    modified_dict = modified_domain.as_dict()
+    original_dict = original_domain.as_dict()
+    default_dict = Domain.empty().as_dict()
+
+    assert sorted(original_dict.keys()) == sorted(modified_dict.keys())
+    for key in original_dict.keys():
+
+        # replaced with default values
+        if key in ["config", SESSION_CONFIG_KEY]:
+            assert modified_dict[key] == default_dict[key]
+
+        # for slots, we drop the specification of how they are filled
+        elif key == KEY_SLOTS:
+            for slot_key in original_dict[key]:
+                assert modified_dict[key][slot_key][SLOT_MAPPINGS] == []
+                original_dict[key][slot_key][SLOT_MAPPINGS] = []  # drop for comparison
+                assert original_dict[key][slot_key] == modified_dict[key][slot_key]
+
+        # for responses, we only keep the keys
+        elif key == KEY_RESPONSES:
+            assert set(modified_dict[key].keys()) == set(original_dict[key].keys())
+            for sub_key in original_dict[key]:
+                assert modified_dict[key][sub_key] == []
+
+        # for forms, we only keep the keys (and the Domain will add a default key)
+        elif key == KEY_FORMS:
+            assert set(modified_dict[key].keys()) == set(original_dict[key].keys())
+            for sub_key in original_dict[key]:
+                assert set(modified_dict[key][sub_key].keys()) == {REQUIRED_SLOTS_KEY}
+                assert modified_dict[key][sub_key][REQUIRED_SLOTS_KEY] == {}
+
+        # everything else remains unchanged
+        else:
+            assert original_dict[key] == modified_dict[key]
diff --git a/tests/graph_components/providers/test_domain_without_responses_provider.py b/tests/graph_components/providers/test_domain_without_responses_provider.py
deleted file mode 100644
index f6302485debc..000000000000
--- a/tests/graph_components/providers/test_domain_without_responses_provider.py
+++ /dev/null
@@ -1,69 +0,0 @@
-from typing import Text
-
-import pytest
-from rasa.graph_components.providers.domain_without_response_provider import (
-    DomainWithoutResponsesProvider,
-)
-from rasa.engine.graph import ExecutionContext
-from rasa.engine.storage.resource import Resource
-from rasa.engine.storage.storage import ModelStorage
-from rasa.shared.core.domain import KEY_E2E_ACTIONS, KEY_RESPONSES, Domain
-
-
-@pytest.mark.parametrize(
-    "domain_yml",
-    [
-        ("data/test_domains/travel_form.yml"),
-        ("data/test_domains/mixed_retrieval_intents.yml"),
-    ],
-)
-def test_provide(
-    default_model_storage: ModelStorage,
-    default_execution_context: ExecutionContext,
-    domain_yml: Text,
-):
-
-    component = DomainWithoutResponsesProvider.create(
-        {"arbitrary-unused": 234},
-        default_model_storage,
-        Resource("xy"),
-        default_execution_context,
-    )
-
-    original_domain = Domain.from_file(path=domain_yml)
-    modified_domain = component.provide(domain=original_domain)
-
-    modified_dict = modified_domain.as_dict()
-    original_dict = original_domain.as_dict()
-
-    # all configurations not impacted by responses stay intact
-    assert sorted(original_dict.keys()) == sorted(modified_dict.keys())
-    for key in original_dict.keys():
-        if key not in [KEY_RESPONSES, KEY_E2E_ACTIONS]:
-            assert original_dict[key] == modified_dict[key]
-
-    # Note: The given domains do contain responses and some actions for which no
-    # responses are defined...
-    reminder = (
-        f"This test needs to be adapted. The domain yamls have been changed so that "
-        f"they no longer contain some actions and some responses - which do not "
-        f"coincide (i.e. not all responses are listed as actions). Hence "
-        f"{domain_yml} is no longer a good test case here. Please remove the yaml from "
-        f"the parameterization or replace it with an different yaml."
-    )
-    assert original_domain.responses, reminder
-    assert original_domain.user_actions, reminder
-    assert set(original_domain.user_actions).difference(
-        original_domain.responses
-    ), reminder
-
-    # Assert that the recreated copy does not contain any response information
-    assert modified_domain.responses.keys()
-    assert not any(modified_domain.responses.values())
-
-    assert original_domain.responses.keys()
-    assert all(original_domain.responses.values())
-
-    del modified_dict[KEY_RESPONSES]
-    del original_dict[KEY_RESPONSES]
-    assert modified_dict == original_dict


Code review comments:
diff_hunk: @@ -208,17 +211,16 @@ def _compare_or_memorize(
             self._fingerprints[fingerprint_key] = new_fingerprint
 
     @staticmethod
-    def _get_fingerprint_of_domain_without_responses(domain: Domain) -> Text:
-        """Returns a fingerprint of a version of the given domain without responses.
+    def _get_fingerprint_of_domain_pruned_for_core(domain: Domain) -> Text:
+        """Returns a fingerprint of a pruned version of the domain relevant for core.
 
         Args:
             domain: a domain
         Returns:
             fingerprint
         """
-        domain = copy.copy(domain)
-        domain.responses = {}
-        return domain.fingerprint()
+        pruned_domain = DomainForCoreTrainingProvider.create_pruned_version()
path: rasa/graph_components/validators/finetuning_validator.py
text: @user1:
I'm confused by this line - won't the pruned version be passed to this node as an input?

@author:
The finetuning validation should happen before the core parts of the graph (i.e. before the domain without ... provider)

diff_hunk: @@ -0,0 +1,92 @@
+from __future__ import annotations
+
+import copy
+from typing import Dict, Text, Any
+
+from rasa.engine.graph import GraphComponent, ExecutionContext
+from rasa.engine.storage.resource import Resource
+from rasa.engine.storage.storage import ModelStorage
+from rasa.shared.core.domain import (
+    KEY_RESPONSES,
+    Domain,
+    SESSION_CONFIG_KEY,
+    KEY_SLOTS,
+    KEY_FORMS,
+)
+from rasa.shared.core.constants import SLOT_MAPPINGS
+
+
+class DomainForCoreTrainingProvider(GraphComponent):
+    """Provides domain without information that is irrelevant for core training.
+
+    The information that we retain includes:
+    - intents and their "used" and "ignored" entities because intents influence the
+      next action prediction directly and the latter flags determine whehter the
path: rasa/graph_components/providers/domain_for_core_training_provider.py
text: @user1:
```suggestion
      next action prediction directly and the latter flags determine whether the
```