Repository: dbt-labs/dbt-core

Issue:
[CT-2611] [Feature] support for `access` selection syntax 
### 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

As a dbt user, I want to be able to select my models according to their `access` property like I can with most (if not all!) other properties and configs. This is helpful if I need to operate on subsets of groups depending on their access

Sample usage:

```bash
# which members of my finance group are public?
$ dbt ls group:finance,access:public
```

### Describe alternatives you've considered

doing post-command filtering, going for a long walk outside

### Who will this benefit?

folks who want to operate or interact with resources using their access as a selection criteria. specifically, this simplifies some of the code we're writing in dbt-meshify! 

### Are you interested in contributing this feature?

yes 

### Anything else?

_No response_


Discussion:

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
@@ -36,6 +36,7 @@ class MethodName(StrEnum):
     FQN = "fqn"
     Tag = "tag"
     Group = "group"
+    Access = "access"
     Source = "source"
     Path = "path"
     File = "file"
@@ -230,6 +231,16 @@ def search(self, included_nodes: Set[UniqueId], selector: str) -> Iterator[Uniqu
                 yield node
 
 
+class AccessSelectorMethod(SelectorMethod):
+    def search(self, included_nodes: Set[UniqueId], selector: str) -> Iterator[UniqueId]:
+        """yields model nodes matching the specified access level"""
+        for node, real_node in self.parsed_nodes(included_nodes):
+            if not isinstance(real_node, ModelNode):
+                continue
+            if selector == real_node.access:
+                yield node
+
+
 class SourceSelectorMethod(SelectorMethod):
     def search(self, included_nodes: Set[UniqueId], selector: str) -> Iterator[UniqueId]:
         """yields nodes from included are the specified source."""
@@ -713,6 +724,7 @@ class MethodManager:
         MethodName.FQN: QualifiedNameSelectorMethod,
         MethodName.Tag: TagSelectorMethod,
         MethodName.Group: GroupSelectorMethod,
+        MethodName.Access: AccessSelectorMethod,
         MethodName.Source: SourceSelectorMethod,
         MethodName.Path: PathSelectorMethod,
         MethodName.File: FileSelectorMethod,


Test patch:
diff --git a/test/unit/test_graph_selector_methods.py b/test/unit/test_graph_selector_methods.py
--- a/test/unit/test_graph_selector_methods.py
+++ b/test/unit/test_graph_selector_methods.py
@@ -32,6 +32,7 @@
     QualifiedNameSelectorMethod,
     TagSelectorMethod,
     GroupSelectorMethod,
+    AccessSelectorMethod,
     SourceSelectorMethod,
     PathSelectorMethod,
     FileSelectorMethod,
@@ -63,6 +64,7 @@ def make_model(
     depends_on_macros=None,
     version=None,
     latest_version=None,
+    access=None,
 ):
     if refs is None:
         refs = []
@@ -121,6 +123,7 @@ def make_model(
         checksum=FileHash.from_contents(""),
         version=version,
         latest_version=latest_version,
+        access=access,
     )
 
 
@@ -921,6 +924,22 @@ def test_select_group(manifest, view_model):
     assert not search_manifest_using_method(manifest, method, "not_my_group")
 
 
+def test_select_access(manifest, view_model):
+    change_node(
+        manifest,
+        view_model.replace(
+            access="public",
+        ),
+    )
+    methods = MethodManager(manifest, None)
+    method = methods.get_method("access", [])
+    assert isinstance(method, AccessSelectorMethod)
+    assert method.arguments == []
+
+    assert search_manifest_using_method(manifest, method, "public") == {"view_model"}
+    assert not search_manifest_using_method(manifest, method, "private")
+
+
 def test_select_source(manifest):
     methods = MethodManager(manifest, None)
     method = methods.get_method("source", [])