Repository: dbt-labs/dbt-core

Issue:
[CT-2544] Add "other" relation attribute to reffable node classes
## Description
As part of the work for clone #7256, reffable nodes need to hold a reference to their "other" counterparts in other schemas.

Jeremy already has [a draft PR](https://github.com/dbt-labs/dbt-core/pull/7258) up that accomplishes this and uses the name `"state_relation"` as seen in the links below, however we are not beholden to this name.

As always, the language team has the final say in all things artifacts so make sure to get their sign off on whatever approach is taken.

## Dependencies
Adapter work from #7549 will be dependent on the work in this ticket.

## Acceptance Criteria
- The reffable node classes (i.e. `ModelNode`, `SeedNode`, and `SnapshotNode`) have a field that stores a relation to an "other" counterpart in another schema
- This new attribute is not written to `manifest.json`
- This new attribute is somehow tested

## Links from the draft PR
Separating `RelationalNode` from `ParsedNodeMandatory`:
https://github.com/dbt-labs/dbt-core/blob/5f08d70336af77908890784fa6ac1399d50ca2d5/core/dbt/contracts/graph/nodes.py#L231-L243

Adding a relation to a reffable node:
https://github.com/dbt-labs/dbt-core/blob/5f08d70336af77908890784fa6ac1399d50ca2d5/core/dbt/contracts/graph/nodes.py#L570-L574

Serializing the new relation attribute out of manifest writing:
https://github.com/dbt-labs/dbt-core/blob/5f08d70336af77908890784fa6ac1399d50ca2d5/core/dbt/contracts/graph/manifest.py#L1256-L1262

Adding the new relation attribute to required node keys for manifest tests:
https://github.com/dbt-labs/dbt-core/blob/5f08d70336af77908890784fa6ac1399d50ca2d5/test/unit/test_manifest.py#L91-L92


Discussion:

Resolving patch:
diff --git a/core/dbt/contracts/graph/manifest.py b/core/dbt/contracts/graph/manifest.py
--- a/core/dbt/contracts/graph/manifest.py
+++ b/core/dbt/contracts/graph/manifest.py
@@ -1316,6 +1316,8 @@ def __post_serialize__(self, dct):
         for unique_id, node in dct["nodes"].items():
             if "config_call_dict" in node:
                 del node["config_call_dict"]
+            if "state_relation" in node:
+                del node["state_relation"]
         return dct
 
 
diff --git a/core/dbt/contracts/graph/nodes.py b/core/dbt/contracts/graph/nodes.py
--- a/core/dbt/contracts/graph/nodes.py
+++ b/core/dbt/contracts/graph/nodes.py
@@ -270,6 +270,17 @@ def add_public_node(self, value: str):
             self.public_nodes.append(value)
 
 
+@dataclass
+class StateRelation(dbtClassMixin):
+    alias: str
+    database: Optional[str]
+    schema: str
+
+    @property
+    def identifier(self):
+        return self.alias
+
+
 @dataclass
 class ParsedNodeMandatory(GraphNode, HasRelationMetadata, Replaceable):
     alias: str
@@ -615,6 +626,7 @@ class ModelNode(CompiledNode):
     constraints: List[ModelLevelConstraint] = field(default_factory=list)
     version: Optional[NodeVersion] = None
     latest_version: Optional[NodeVersion] = None
+    state_relation: Optional[StateRelation] = None
 
     @property
     def is_latest_version(self) -> bool:
@@ -797,6 +809,7 @@ class SeedNode(ParsedNode):  # No SQLDefaults!
     # and we need the root_path to load the seed later
     root_path: Optional[str] = None
     depends_on: MacroDependsOn = field(default_factory=MacroDependsOn)
+    state_relation: Optional[StateRelation] = None
 
     def same_seeds(self, other: "SeedNode") -> bool:
         # for seeds, we check the hashes. If the hashes are different types,
@@ -995,6 +1008,7 @@ class IntermediateSnapshotNode(CompiledNode):
 class SnapshotNode(CompiledNode):
     resource_type: NodeType = field(metadata={"restrict": [NodeType.Snapshot]})
     config: SnapshotConfig
+    state_relation: Optional[StateRelation] = None
 
 
 # ====================================


Test patch:
diff --git a/test/unit/test_manifest.py b/test/unit/test_manifest.py
--- a/test/unit/test_manifest.py
+++ b/test/unit/test_manifest.py
@@ -93,6 +93,7 @@
         "version",
         "latest_version",
         "constraints",
+        "state_relation",
     }
 )