Repository: dbt-labs/dbt-core

Issue:
[CT-2551] Make State Selection Collectively Exhaustive
### Is this your first time submitting a feature request?

- [X] I have read the [expectations for open source contributors](https://docs.getdbt.com/docs/contributing/oss-expectations)
- [X] I have searched the existing issues, and I could not find an existing issue for this feature
- [X] I am requesting a straightforward extension of existing dbt functionality, rather than a Big Idea better suited to a discussion

### Describe the feature

This is a follow on from prior art discussed in #2704.

The thinking here would be to create a selector method that would allow me to impart logic as follows:

```
dbt run --exclude config.materialized:view,state:unmodified
```

This is simply the converse of the `state:modified` selector.

### Describe alternatives you've considered

The converse of the above statement is less elegant. To gather the same selection I'd have to do something more like:

```
dbt run -s config.materialized:table config.materialized:incremental state:modified
```

But this is not preferred as a new custom materialization would require refactoring numerous jobs. My intuition tells me that there may be other issues with this as well.

### Who will this benefit?

We make heavy use of late-binding views in Redshift. For projects getting into the thousands of models, rerunning unchanged views that are late-binding doesn't really provide any value and, over a large number of models, slows down our build times. Ideally, I'd be able to exclude re-running late-binding views from all of my jobs with the addition of an exclusion flag `--exclude config.materialized:view,state:unmodified`.

### Are you interested in contributing this feature?

Sure! If it's confirmed this makes sense to add.

### Anything else?

_No response_


Discussion:
Thanks for opening @trouze !

At a high level, it makes sense to me that that there would be a collection of `state`s that follow Barbara Minto's [MECE principle](https://en.wikipedia.org/wiki/MECE_principle).

e.g., `state:modified` / `state:unmodified`
and `state:new` / `state:old`

We also have a [collection of subselectors](https://docs.getdbt.com/reference/node-selection/methods#the-state-method):
- `state:modified.body`
- `state:modified.configs`
- `state:modified.relation`
- `state:modified.persisted_descriptions`
- `state:modified.macros`
- `state:modified.contract`

Have you given any thought if `state:unmodified.{subselector}`s should be added as well?
In short, no. If I were to, there's probably two approaches:

1. I'd think through use cases of subselectors and determine which would benefit from being able to select the converse. I'm just not that familiar with the subselectors at this moment to determine if any/all would benefit here. OR
2. We could just take the principled stance that all selectors/subselectors should be MECE

I don't have the intuition here as to how much bigger a lift it'd be to add this to all subselectors versus only `modified` and `new`. Might need to lean on others here.
Dug into this a bit over the weekend to see what this might look like and was able to get a [working prototype](https://github.com/dbt-labs/dbt-core/compare/main...trouze:dbt-core:main) going.
That's awesome @trouze! 🤩

@jtcohen6 It seems like adding the following state selectors to complement `state:modified` and `state:new` would be [relatively simple](https://github.com/dbt-labs/dbt-core/compare/main...trouze:dbt-core:main) and useful:
- `state:unmodified`
- `state:old`

It also seems like we could defer adding any new [subselectors](https://docs.getdbt.com/reference/node-selection/methods#the-state-method) until some later date (assuming we get a request for them).

How does this sound to you @jtcohen6 ? Any concerns with going this direction?

Resolving patch:
diff --git a/core/dbt/graph/selector_methods.py b/core/dbt/graph/selector_methods.py
--- a/core/dbt/graph/selector_methods.py
+++ b/core/dbt/graph/selector_methods.py
@@ -542,6 +542,11 @@ def check_modified_content(
         upstream_macro_change = self.check_macros_modified(new)
         return different_contents or upstream_macro_change
 
+    def check_unmodified_content(
+        self, old: Optional[SelectorTarget], new: SelectorTarget, adapter_type: str
+    ) -> bool:
+        return not self.check_modified_content(old, new, adapter_type)
+
     def check_modified_macros(self, old, new: SelectorTarget) -> bool:
         return self.check_macros_modified(new)
 
@@ -586,8 +591,10 @@ def search(self, included_nodes: Set[UniqueId], selector: str) -> Iterator[Uniqu
         state_checks = {
             # it's new if there is no old version
             "new": lambda old, new: old is None,
+            "old": lambda old, new: old is not None,
             # use methods defined above to compare properties of old + new
             "modified": self.check_modified_content,
+            "unmodified": self.check_unmodified_content,
             "modified.body": self.check_modified_factory("same_body"),
             "modified.configs": self.check_modified_factory("same_config"),
             "modified.persisted_descriptions": self.check_modified_factory(
@@ -619,7 +626,11 @@ def search(self, included_nodes: Set[UniqueId], selector: str) -> Iterator[Uniqu
                 previous_node = manifest.metrics[node]
 
             keyword_args = {}
-            if checker.__name__ in ["same_contract", "check_modified_content"]:
+            if checker.__name__ in [
+                "same_contract",
+                "check_modified_content",
+                "check_unmodified_content",
+            ]:
                 keyword_args["adapter_type"] = adapter_type  # type: ignore
 
             if checker(previous_node, real_node, **keyword_args):  # type: ignore


Test patch:
diff --git a/tests/functional/defer_state/test_modified_state.py b/tests/functional/defer_state/test_modified_state.py
--- a/tests/functional/defer_state/test_modified_state.py
+++ b/tests/functional/defer_state/test_modified_state.py
@@ -84,6 +84,34 @@ def test_changed_seed_contents_state(self, project):
         )
         assert len(results) == 0
 
+        results = run_dbt(
+            [
+                "ls",
+                "--resource-type",
+                "seed",
+                "--exclude",
+                "state:unmodified",
+                "--state",
+                "./state",
+            ],
+            expect_pass=True,
+        )
+        assert len(results) == 0
+
+        results = run_dbt(
+            [
+                "ls",
+                "--resource-type",
+                "seed",
+                "--select",
+                "state:unmodified",
+                "--state",
+                "./state",
+            ],
+            expect_pass=True,
+        )
+        assert len(results) == 1
+
         # add a new row to the seed
         changed_seed_contents = seed_csv + "\n" + "3,carl"
         write_file(changed_seed_contents, "seeds", "seed.csv")
@@ -94,10 +122,36 @@ def test_changed_seed_contents_state(self, project):
         assert len(results) == 1
         assert results[0] == "test.seed"
 
+        results = run_dbt(
+            [
+                "ls",
+                "--resource-type",
+                "seed",
+                "--exclude",
+                "state:unmodified",
+                "--state",
+                "./state",
+            ]
+        )
+        assert len(results) == 1
+        assert results[0] == "test.seed"
+
+        results = run_dbt(
+            ["ls", "--resource-type", "seed", "--select", "state:unmodified", "--state", "./state"]
+        )
+        assert len(results) == 0
+
         results = run_dbt(["ls", "--select", "state:modified", "--state", "./state"])
         assert len(results) == 1
         assert results[0] == "test.seed"
 
+        results = run_dbt(["ls", "--exclude", "state:unmodified", "--state", "./state"])
+        assert len(results) == 1
+        assert results[0] == "test.seed"
+
+        results = run_dbt(["ls", "--select", "state:unmodified", "--state", "./state"])
+        assert len(results) == 6
+
         results = run_dbt(["ls", "--select", "state:modified+", "--state", "./state"])
         assert len(results) == 7
         assert set(results) == {
@@ -110,6 +164,17 @@ def test_changed_seed_contents_state(self, project):
             "exposure:test.my_exposure",
         }
 
+        results = run_dbt(["ls", "--select", "state:unmodified+", "--state", "./state"])
+        assert len(results) == 6
+        assert set(results) == {
+            "test.table_model",
+            "test.view_model",
+            "test.ephemeral_model",
+            "test.not_null_view_model_id",
+            "test.unique_view_model_id",
+            "exposure:test.my_exposure",
+        }
+
         shutil.rmtree("./state")
         self.copy_state()
 
@@ -148,6 +213,12 @@ def test_changed_seed_contents_state(self, project):
             )
         assert ">1MB" in str(exc.value)
 
+        # now check if unmodified returns none
+        results = run_dbt(
+            ["ls", "--resource-type", "seed", "--select", "state:unmodified", "--state", "./state"]
+        )
+        assert len(results) == 0
+
         shutil.rmtree("./state")
         self.copy_state()
 
@@ -160,6 +231,34 @@ def test_changed_seed_contents_state(self, project):
         )
         assert len(results) == 0
 
+        results = run_dbt(
+            [
+                "ls",
+                "--resource-type",
+                "seed",
+                "--exclude",
+                "state:unmodified",
+                "--state",
+                "./state",
+            ],
+            expect_pass=True,
+        )
+        assert len(results) == 0
+
+        results = run_dbt(
+            [
+                "ls",
+                "--resource-type",
+                "seed",
+                "--select",
+                "state:unmodified",
+                "--state",
+                "./state",
+            ],
+            expect_pass=True,
+        )
+        assert len(results) == 1
+
 
 class TestChangedSeedConfig(BaseModifiedState):
     def test_changed_seed_config(self, project):
@@ -170,6 +269,34 @@ def test_changed_seed_config(self, project):
         )
         assert len(results) == 0
 
+        results = run_dbt(
+            [
+                "ls",
+                "--resource-type",
+                "seed",
+                "--exclude",
+                "state:unmodified",
+                "--state",
+                "./state",
+            ],
+            expect_pass=True,
+        )
+        assert len(results) == 0
+
+        results = run_dbt(
+            [
+                "ls",
+                "--resource-type",
+                "seed",
+                "--select",
+                "state:unmodified",
+                "--state",
+                "./state",
+            ],
+            expect_pass=True,
+        )
+        assert len(results) == 1
+
         update_config_file({"seeds": {"test": {"quote_columns": False}}}, "dbt_project.yml")
 
         # quoting change -> seed changed
@@ -179,6 +306,25 @@ def test_changed_seed_config(self, project):
         assert len(results) == 1
         assert results[0] == "test.seed"
 
+        results = run_dbt(
+            [
+                "ls",
+                "--resource-type",
+                "seed",
+                "--exclude",
+                "state:unmodified",
+                "--state",
+                "./state",
+            ]
+        )
+        assert len(results) == 1
+        assert results[0] == "test.seed"
+
+        results = run_dbt(
+            ["ls", "--resource-type", "seed", "--select", "state:unmodified", "--state", "./state"]
+        )
+        assert len(results) == 0
+
 
 class TestUnrenderedConfigSame(BaseModifiedState):
     def test_unrendered_config_same(self, project):
@@ -189,6 +335,34 @@ def test_unrendered_config_same(self, project):
         )
         assert len(results) == 0
 
+        results = run_dbt(
+            [
+                "ls",
+                "--resource-type",
+                "model",
+                "--exclude",
+                "state:unmodified",
+                "--state",
+                "./state",
+            ],
+            expect_pass=True,
+        )
+        assert len(results) == 0
+
+        results = run_dbt(
+            [
+                "ls",
+                "--resource-type",
+                "model",
+                "--select",
+                "state:unmodified",
+                "--state",
+                "./state",
+            ],
+            expect_pass=True,
+        )
+        assert len(results) == 3
+
         # although this is the default value, dbt will recognize it as a change
         # for previously-unconfigured models, because it"s been explicitly set
         update_config_file({"models": {"test": {"materialized": "view"}}}, "dbt_project.yml")
@@ -198,6 +372,38 @@ def test_unrendered_config_same(self, project):
         assert len(results) == 1
         assert results[0] == "test.view_model"
 
+        # converse of above statement
+        results = run_dbt(
+            [
+                "ls",
+                "--resource-type",
+                "model",
+                "--exclude",
+                "state:unmodified",
+                "--state",
+                "./state",
+            ]
+        )
+        assert len(results) == 1
+        assert results[0] == "test.view_model"
+
+        results = run_dbt(
+            [
+                "ls",
+                "--resource-type",
+                "model",
+                "--select",
+                "state:unmodified",
+                "--state",
+                "./state",
+            ]
+        )
+        assert len(results) == 2
+        assert set(results) == {
+            "test.table_model",
+            "test.ephemeral_model",
+        }
+
 
 class TestChangedModelContents(BaseModifiedState):
     def test_changed_model_contents(self, project):
@@ -217,6 +423,10 @@ def test_changed_model_contents(self, project):
         assert len(results) == 1
         assert results[0].node.name == "table_model"
 
+        results = run_dbt(["run", "--exclude", "state:unmodified", "--state", "./state"])
+        assert len(results) == 1
+        assert results[0].node.name == "table_model"
+
 
 class TestNewMacro(BaseModifiedState):
     def test_new_macro(self, project):
@@ -241,6 +451,9 @@ def test_new_macro(self, project):
         results = run_dbt(["run", "--models", "state:modified", "--state", "./state"])
         assert len(results) == 0
 
+        results = run_dbt(["run", "--exclude", "state:unmodified", "--state", "./state"])
+        assert len(results) == 0
+
 
 class TestChangedMacroContents(BaseModifiedState):
     def test_changed_macro_contents(self, project):
@@ -258,6 +471,9 @@ def test_changed_macro_contents(self, project):
         results = run_dbt(["run", "--models", "state:modified", "--state", "./state"])
         assert len(results) == 1
 
+        results = run_dbt(["run", "--exclude", "state:unmodified", "--state", "./state"])
+        assert len(results) == 1
+
 
 class TestChangedExposure(BaseModifiedState):
     def test_changed_exposure(self, project):
@@ -271,6 +487,9 @@ def test_changed_exposure(self, project):
         assert len(results) == 1
         assert results[0].node.name == "view_model"
 
+        results = run_dbt(["run", "--exclude", "state:unmodified", "--state", "./state"])
+        assert len(results) == 0
+
 
 class TestChangedContract(BaseModifiedState):
     def test_changed_contract(self, project):
@@ -284,6 +503,11 @@ def test_changed_contract(self, project):
         results = run_dbt(["run", "--models", "state:modified", "--state", "./state"])
         assert len(results) == 1
         assert results[0].node.name == "table_model"
+
+        results = run_dbt(["run", "--exclude", "state:unmodified", "--state", "./state"])
+        assert len(results) == 1
+        assert results[0].node.name == "table_model"
+
         manifest = get_manifest(project.project_root)
         model_unique_id = "model.test.table_model"
         model = manifest.nodes[model_unique_id]
@@ -335,6 +559,11 @@ def test_changed_constraint(self, project):
         results = run_dbt(["run", "--models", "state:modified", "--state", "./state"])
         assert len(results) == 1
         assert results[0].node.name == "table_model"
+
+        results = run_dbt(["run", "--exclude", "state:unmodified", "--state", "./state"])
+        assert len(results) == 1
+        assert results[0].node.name == "table_model"
+
         manifest = get_manifest(project.project_root)
         model_unique_id = "model.test.table_model"
         model = manifest.nodes[model_unique_id]
@@ -390,6 +619,11 @@ def test_changed_materialization(self, project):
         results = run_dbt(["run", "--models", "state:modified", "--state", "./state"])
         assert len(results) == 1
         assert results[0].node.name == "table_model"
+
+        results = run_dbt(["run", "--exclude", "state:unmodified", "--state", "./state"])
+        assert len(results) == 1
+        assert results[0].node.name == "table_model"
+
         manifest = get_manifest(project.project_root)
         model_unique_id = "model.test.table_model"
         model = manifest.nodes[model_unique_id]
@@ -515,6 +749,9 @@ def test_modified_body_and_contract(self, project):
         with pytest.raises(ContractBreakingChangeError):
             results = run_dbt(["run", "-s", "state:modified", "--state", "./state"])
 
+        with pytest.raises(ContractBreakingChangeError):
+            results = run_dbt(["run", "--exclude", "state:unmodified", "--state", "./state"])
+
         # Change both body and contract in a *non-breaking* way (= adding a new column)
         write_file(modified_my_model_non_breaking_yml, "models", "my_model.yml")
         write_file(modified_my_model_non_breaking_sql, "models", "my_model.sql")
diff --git a/tests/unit/test_graph_selector_methods.py b/tests/unit/test_graph_selector_methods.py
--- a/tests/unit/test_graph_selector_methods.py
+++ b/tests/unit/test_graph_selector_methods.py
@@ -1245,7 +1245,6 @@ def test_select_state_no_change(manifest, previous_state):
     method = statemethod(manifest, previous_state)
     assert not search_manifest_using_method(manifest, method, "modified")
     assert not search_manifest_using_method(manifest, method, "new")
-    assert not search_manifest_using_method(manifest, method, "new")
     assert not search_manifest_using_method(manifest, method, "modified.configs")
     assert not search_manifest_using_method(manifest, method, "modified.persisted_descriptions")
     assert not search_manifest_using_method(manifest, method, "modified.relation")
@@ -1263,6 +1262,14 @@ def test_select_state_nothing(manifest, previous_state):
         search_manifest_using_method(manifest, method, "new")
     assert "no comparison manifest" in str(exc.value)
 
+    with pytest.raises(dbt.exceptions.DbtRuntimeError) as exc:
+        search_manifest_using_method(manifest, method, "unmodified")
+    assert "no comparison manifest" in str(exc.value)
+
+    with pytest.raises(dbt.exceptions.DbtRuntimeError) as exc:
+        search_manifest_using_method(manifest, method, "old")
+    assert "no comparison manifest" in str(exc.value)
+
 
 def test_select_state_added_model(manifest, previous_state):
     add_node(manifest, make_model("pkg", "another_model", "select 1 as id"))
@@ -1271,6 +1278,10 @@ def test_select_state_added_model(manifest, previous_state):
     assert search_manifest_using_method(manifest, method, "new") == {"another_model"}
     assert search_manifest_using_method(manifest, method, "modified.body") == {"another_model"}
 
+    # none of these
+    assert not {"another_model"} in search_manifest_using_method(manifest, method, "old")
+    assert not {"another_model"} in search_manifest_using_method(manifest, method, "unmodified")
+
 
 def test_select_state_changed_model_sql(manifest, previous_state, view_model):
     change_node(manifest, view_model.replace(raw_code="select 1 as id"))
@@ -1282,6 +1293,8 @@ def test_select_state_changed_model_sql(manifest, previous_state, view_model):
 
     # none of these
     assert not search_manifest_using_method(manifest, method, "new")
+    assert not {"view_model"} in search_manifest_using_method(manifest, method, "old")
+    assert not {"view_model"} in search_manifest_using_method(manifest, method, "unmodified")
     assert not search_manifest_using_method(manifest, method, "modified.configs")
     assert not search_manifest_using_method(manifest, method, "modified.persisted_descriptions")
     assert not search_manifest_using_method(manifest, method, "modified.relation")
@@ -1296,6 +1309,10 @@ def test_select_state_changed_model_fqn(manifest, previous_state, view_model):
     assert search_manifest_using_method(manifest, method, "modified") == {"view_model"}
     assert not search_manifest_using_method(manifest, method, "new")
 
+    # none of these
+    assert not {"view_model"} in search_manifest_using_method(manifest, method, "old")
+    assert not {"view_model"} in search_manifest_using_method(manifest, method, "unmodified")
+
 
 def test_select_state_added_seed(manifest, previous_state):
     add_node(manifest, make_seed("pkg", "another_seed"))
@@ -1303,12 +1320,17 @@ def test_select_state_added_seed(manifest, previous_state):
     assert search_manifest_using_method(manifest, method, "modified") == {"another_seed"}
     assert search_manifest_using_method(manifest, method, "new") == {"another_seed"}
 
+    # none of these
+    assert not {"another_seed"} in search_manifest_using_method(manifest, method, "old")
+    assert not {"another_seed"} in search_manifest_using_method(manifest, method, "unmodified")
+
 
 def test_select_state_changed_seed_checksum_sha_to_sha(manifest, previous_state, seed):
     change_node(manifest, seed.replace(checksum=FileHash.from_contents("changed")))
     method = statemethod(manifest, previous_state)
     assert search_manifest_using_method(manifest, method, "modified") == {"seed"}
     assert not search_manifest_using_method(manifest, method, "new")
+    assert "seed" not in search_manifest_using_method(manifest, method, "unmodified")
 
 
 def test_select_state_changed_seed_checksum_path_to_path(manifest, previous_state, seed):
@@ -1330,6 +1352,16 @@ def test_select_state_changed_seed_checksum_path_to_path(manifest, previous_stat
     with mock.patch("dbt.contracts.graph.nodes.warn_or_error") as warn_or_error_patch:
         assert not search_manifest_using_method(manifest, method, "new")
         warn_or_error_patch.assert_not_called()
+    with mock.patch("dbt.contracts.graph.nodes.warn_or_error") as warn_or_error_patch:
+        assert search_manifest_using_method(manifest, method, "old")
+        warn_or_error_patch.assert_not_called()
+    with mock.patch("dbt.contracts.graph.nodes.warn_or_error") as warn_or_error_patch:
+        assert search_manifest_using_method(manifest, method, "unmodified")
+        warn_or_error_patch.assert_called_once()
+        event = warn_or_error_patch.call_args[0][0]
+        assert type(event).__name__ == "SeedExceedsLimitSamePath"
+        msg = event.message()
+        assert msg.startswith("Found a seed (pkg.seed) >1MB in size")
 
 
 def test_select_state_changed_seed_checksum_sha_to_path(manifest, previous_state, seed):
@@ -1347,6 +1379,16 @@ def test_select_state_changed_seed_checksum_sha_to_path(manifest, previous_state
     with mock.patch("dbt.contracts.graph.nodes.warn_or_error") as warn_or_error_patch:
         assert not search_manifest_using_method(manifest, method, "new")
         warn_or_error_patch.assert_not_called()
+    with mock.patch("dbt.contracts.graph.nodes.warn_or_error") as warn_or_error_patch:
+        assert search_manifest_using_method(manifest, method, "old")
+        warn_or_error_patch.assert_not_called()
+    with mock.patch("dbt.contracts.graph.nodes.warn_or_error") as warn_or_error_patch:
+        assert search_manifest_using_method(manifest, method, "unmodified")
+        warn_or_error_patch.assert_called_once()
+        event = warn_or_error_patch.call_args[0][0]
+        assert type(event).__name__ == "SeedIncreased"
+        msg = event.message()
+        assert msg.startswith("Found a seed (pkg.seed) >1MB in size")
 
 
 def test_select_state_changed_seed_checksum_path_to_sha(manifest, previous_state, seed):
@@ -1361,6 +1403,12 @@ def test_select_state_changed_seed_checksum_path_to_sha(manifest, previous_state
     with mock.patch("dbt.contracts.graph.nodes.warn_or_error") as warn_or_error_patch:
         assert not search_manifest_using_method(manifest, method, "new")
         warn_or_error_patch.assert_not_called()
+    with mock.patch("dbt.contracts.graph.nodes.warn_or_error") as warn_or_error_patch:
+        assert "seed" not in search_manifest_using_method(manifest, method, "unmodified")
+        warn_or_error_patch.assert_not_called()
+    with mock.patch("dbt.contracts.graph.nodes.warn_or_error") as warn_or_error_patch:
+        assert "seed" in search_manifest_using_method(manifest, method, "old")
+        warn_or_error_patch.assert_not_called()
 
 
 def test_select_state_changed_seed_fqn(manifest, previous_state, seed):
@@ -1368,6 +1416,8 @@ def test_select_state_changed_seed_fqn(manifest, previous_state, seed):
     method = statemethod(manifest, previous_state)
     assert search_manifest_using_method(manifest, method, "modified") == {"seed"}
     assert not search_manifest_using_method(manifest, method, "new")
+    assert "seed" not in search_manifest_using_method(manifest, method, "unmodified")
+    assert "seed" in search_manifest_using_method(manifest, method, "old")
 
 
 def test_select_state_changed_seed_relation_documented(manifest, previous_state, seed):
@@ -1376,7 +1426,9 @@ def test_select_state_changed_seed_relation_documented(manifest, previous_state,
     method = statemethod(manifest, previous_state)
     assert search_manifest_using_method(manifest, method, "modified") == {"seed"}
     assert search_manifest_using_method(manifest, method, "modified.configs") == {"seed"}
+    assert "seed" in search_manifest_using_method(manifest, method, "old")
     assert not search_manifest_using_method(manifest, method, "new")
+    assert "seed" not in search_manifest_using_method(manifest, method, "unmodified")
     assert not search_manifest_using_method(manifest, method, "modified.body")
     assert not search_manifest_using_method(manifest, method, "modified.persisted_descriptions")
 
@@ -1391,7 +1443,9 @@ def test_select_state_changed_seed_relation_documented_nodocs(manifest, previous
     assert search_manifest_using_method(manifest, method, "modified.persisted_descriptions") == {
         "seed"
     }
+    assert "seed" in search_manifest_using_method(manifest, method, "old")
     assert not search_manifest_using_method(manifest, method, "new")
+    assert "seed" not in search_manifest_using_method(manifest, method, "unmodified")
     assert not search_manifest_using_method(manifest, method, "modified.configs")
 
 
@@ -1405,7 +1459,9 @@ def test_select_state_changed_seed_relation_documented_withdocs(manifest, previo
     assert search_manifest_using_method(manifest, method, "modified.persisted_descriptions") == {
         "seed"
     }
+    assert "seed" in search_manifest_using_method(manifest, method, "old")
     assert not search_manifest_using_method(manifest, method, "new")
+    assert "seed" not in search_manifest_using_method(manifest, method, "unmodified")
 
 
 def test_select_state_changed_seed_columns_documented(manifest, previous_state, seed):
@@ -1415,7 +1471,9 @@ def test_select_state_changed_seed_columns_documented(manifest, previous_state,
     method = statemethod(manifest, previous_state)
     assert search_manifest_using_method(manifest, method, "modified") == {"seed"}
     assert search_manifest_using_method(manifest, method, "modified.configs") == {"seed"}
+    assert "seed" in search_manifest_using_method(manifest, method, "old")
     assert not search_manifest_using_method(manifest, method, "new")
+    assert "seed" not in search_manifest_using_method(manifest, method, "unmodified")
     assert not search_manifest_using_method(manifest, method, "modified.persisted_descriptions")
 
 
@@ -1433,7 +1491,9 @@ def test_select_state_changed_seed_columns_documented_nodocs(manifest, previous_
     assert search_manifest_using_method(manifest, method, "modified.persisted_descriptions") == {
         "seed"
     }
+    assert "seed" in search_manifest_using_method(manifest, method, "old")
     assert not search_manifest_using_method(manifest, method, "new")
+    assert "seed" not in search_manifest_using_method(manifest, method, "unmodified")
     assert not search_manifest_using_method(manifest, method, "modified.configs")
 
 
@@ -1451,7 +1511,9 @@ def test_select_state_changed_seed_columns_documented_withdocs(manifest, previou
     assert search_manifest_using_method(manifest, method, "modified.persisted_descriptions") == {
         "seed"
     }
+    assert "seed" in search_manifest_using_method(manifest, method, "old")
     assert not search_manifest_using_method(manifest, method, "new")
+    assert "seed" not in search_manifest_using_method(manifest, method, "unmodified")
     assert not search_manifest_using_method(manifest, method, "modified.configs")
 
 
@@ -1468,7 +1530,11 @@ def test_select_state_changed_test_macro_sql(
     assert search_manifest_using_method(manifest, method, "modified.macros") == {
         "not_null_table_model_id"
     }
+    assert "not_null_table_model_id" in search_manifest_using_method(manifest, method, "old")
     assert not search_manifest_using_method(manifest, method, "new")
+    assert "not_null_table_model_id" not in search_manifest_using_method(
+        manifest, method, "unmodified"
+    )
 
 
 def test_select_state_changed_test_macros(manifest, previous_state):
@@ -1505,7 +1571,11 @@ def test_select_state_changed_test_macros(manifest, previous_state):
         "model1",
         "model2",
     }
+    assert "model1" and "model2" in search_manifest_using_method(manifest, method, "old")
     assert not search_manifest_using_method(manifest, method, "new")
+    assert "model1" and "model2" not in search_manifest_using_method(
+        manifest, method, "unmodified"
+    )
 
 
 def test_select_state_changed_test_macros_with_upstream_change(manifest, previous_state):
@@ -1554,4 +1624,8 @@ def test_select_state_changed_test_macros_with_upstream_change(manifest, previou
         "model1",
         "model2",
     }
+    assert "model1" and "model2" in search_manifest_using_method(manifest, method, "old")
     assert not search_manifest_using_method(manifest, method, "new")
+    assert "model1" and "model2" not in search_manifest_using_method(
+        manifest, method, "unmodified"
+    )