Repository: cerbos/cerbos
Pull request #1642 (2023-06-14 15:00:53)

enhancement: Add filtering in the ListPolicies RPC
- Adds support to filter by `name` and `scope` using regex, and `version` using full string matching.
- Adds application defined function for SQLite driver.

closes #1609

Sambigeara: Whoops, forgot docs again, just adding
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/cerbos/cerbos/pull/1642?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cerbos) Report
> Merging [#1642](https://app.codecov.io/gh/cerbos/cerbos/pull/1642?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cerbos) (eafb9c7) into [main](https://app.codecov.io/gh/cerbos/cerbos/commit/d2dbcd0520eb1a63818a3da5d13bb62b2fe38bef?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cerbos) (d2dbcd0) will **decrease** coverage by `3.13%`.
> The diff coverage is `18.43%`.

<details><summary>Additional details and impacted files</summary>


[![Impacted file tree graph](https://app.codecov.io/gh/cerbos/cerbos/pull/1642/graphs/tree.svg?width=650&height=150&src=pr&token=tGaxiUZUzL&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cerbos)](https://app.codecov.io/gh/cerbos/cerbos/pull/1642?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cerbos)

```diff
@@            Coverage Diff             @@
##             main    #1642      +/-   ##
==========================================
- Coverage   53.61%   50.49%   -3.13%     
==========================================
  Files         130       90      -40     
  Lines       14969     9562    -5407     
==========================================
- Hits         8026     4828    -3198     
+ Misses       6245     4330    -1915     
+ Partials      698      404     -294     
```


| [Impacted Files](https://app.codecov.io/gh/cerbos/cerbos/pull/1642?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cerbos) | Coverage Δ | |
|---|---|---|
| [internal/storage/db/internal/db.go](https://app.codecov.io/gh/cerbos/cerbos/pull/1642?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cerbos#diff-aW50ZXJuYWwvc3RvcmFnZS9kYi9pbnRlcm5hbC9kYi5nbw==) | `0.00% <0.00%> (ø)` | |
| [internal/storage/db/internal/db\_opts.go](https://app.codecov.io/gh/cerbos/cerbos/pull/1642?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cerbos#diff-aW50ZXJuYWwvc3RvcmFnZS9kYi9pbnRlcm5hbC9kYl9vcHRzLmdv) | `0.00% <0.00%> (ø)` | |
| [internal/storage/db/internal/tests.go](https://app.codecov.io/gh/cerbos/cerbos/pull/1642?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cerbos#diff-aW50ZXJuYWwvc3RvcmFnZS9kYi9pbnRlcm5hbC90ZXN0cy5nbw==) | `0.00% <0.00%> (ø)` | |
| [internal/storage/store.go](https://app.codecov.io/gh/cerbos/cerbos/pull/1642?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cerbos#diff-aW50ZXJuYWwvc3RvcmFnZS9zdG9yZS5nbw==) | `20.58% <ø> (ø)` | |
| [internal/storage/db/sqlite3/sqlite3.go](https://app.codecov.io/gh/cerbos/cerbos/pull/1642?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cerbos#diff-aW50ZXJuYWwvc3RvcmFnZS9kYi9zcWxpdGUzL3NxbGl0ZTMuZ28=) | `32.30% <42.85%> (+3.41%)` | :arrow_up: |
| [internal/util/regexp.go](https://app.codecov.io/gh/cerbos/cerbos/pull/1642?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cerbos#diff-aW50ZXJuYWwvdXRpbC9yZWdleHAuZ28=) | `85.00% <85.00%> (ø)` | |

... and [43 files with indirect coverage changes](https://app.codecov.io/gh/cerbos/cerbos/pull/1642/indirect-changes?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=cerbos)

</details>
Sambigeara: That's a fun test error

Patch:
diff --git a/api/genpb/cerbos/request/v1/hashpb_helpers.pb.go b/api/genpb/cerbos/request/v1/hashpb_helpers.pb.go
index 155219fd..383c40da 100644
--- a/api/genpb/cerbos/request/v1/hashpb_helpers.pb.go
+++ b/api/genpb/cerbos/request/v1/hashpb_helpers.pb.go
@@ -840,6 +840,18 @@ func cerbos_request_v1_ListPoliciesRequest_hashpb_sum(m *ListPoliciesRequest, ha
 		_, _ = hasher.Write(protowire.AppendVarint(nil, protowire.EncodeBool(m.IncludeDisabled)))
 
 	}
+	if _, ok := ignore["cerbos.request.v1.ListPoliciesRequest.name_regexp"]; !ok {
+		_, _ = hasher.Write(protowire.AppendString(nil, m.NameRegexp))
+
+	}
+	if _, ok := ignore["cerbos.request.v1.ListPoliciesRequest.scope_regexp"]; !ok {
+		_, _ = hasher.Write(protowire.AppendString(nil, m.ScopeRegexp))
+
+	}
+	if _, ok := ignore["cerbos.request.v1.ListPoliciesRequest.version_regexp"]; !ok {
+		_, _ = hasher.Write(protowire.AppendString(nil, m.VersionRegexp))
+
+	}
 }
 
 func cerbos_request_v1_ListSchemasRequest_hashpb_sum(m *ListSchemasRequest, hasher hash.Hash, ignore map[string]struct{}) {
diff --git a/api/genpb/cerbos/request/v1/request.pb.go b/api/genpb/cerbos/request/v1/request.pb.go
index 360acfaa..32f069b0 100644
--- a/api/genpb/cerbos/request/v1/request.pb.go
+++ b/api/genpb/cerbos/request/v1/request.pb.go
@@ -1157,7 +1157,10 @@ type ListPoliciesRequest struct {
 	sizeCache     protoimpl.SizeCache
 	unknownFields protoimpl.UnknownFields
 
-	IncludeDisabled bool `protobuf:"varint,1,opt,name=include_disabled,json=includeDisabled,proto3" json:"include_disabled,omitempty"`
+	IncludeDisabled bool   `protobuf:"varint,1,opt,name=include_disabled,json=includeDisabled,proto3" json:"include_disabled,omitempty"`
+	NameRegexp      string `protobuf:"bytes,2,opt,name=name_regexp,json=nameRegexp,proto3" json:"name_regexp,omitempty"`
+	ScopeRegexp     string `protobuf:"bytes,3,opt,name=scope_regexp,json=scopeRegexp,proto3" json:"scope_regexp,omitempty"`
+	VersionRegexp   string `protobuf:"bytes,4,opt,name=version_regexp,json=versionRegexp,proto3" json:"version_regexp,omitempty"`
 }
 
 func (x *ListPoliciesRequest) Reset() {
@@ -1199,6 +1202,27 @@ func (x *ListPoliciesRequest) GetIncludeDisabled() bool {
 	return false
 }
 
+func (x *ListPoliciesRequest) GetNameRegexp() string {
+	if x != nil {
+		return x.NameRegexp
+	}
+	return ""
+}
+
+func (x *ListPoliciesRequest) GetScopeRegexp() string {
+	if x != nil {
+		return x.ScopeRegexp
+	}
+	return ""
+}
+
+func (x *ListPoliciesRequest) GetVersionRegexp() string {
+	if x != nil {
+		return x.VersionRegexp
+	}
+	return ""
+}
+
 type GetPolicyRequest struct {
 	state         protoimpl.MessageState
 	sizeCache     protoimpl.SizeCache
@@ -2321,99 +2345,115 @@ var file_cerbos_request_v1_request_proto_rawDesc = []byte{
 	0x72, 0x12, 0x03, 0xf8, 0x42, 0x01, 0x22, 0x2f, 0x0a, 0x11, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72,
 	0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x3a, 0x1a, 0x92, 0x41, 0x17,
 	0x0a, 0x15, 0x32, 0x13, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x69, 0x6e, 0x66, 0x6f, 0x20,
-	0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x81, 0x01, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74,
+	0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xf7, 0x02, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74,
 	0x50, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
 	0x4c, 0x0a, 0x10, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x64, 0x69, 0x73, 0x61, 0x62,
 	0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x42, 0x21, 0x92, 0x41, 0x1b, 0x32, 0x19,
 	0x49, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x20, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64,
 	0x20, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0xe0, 0x41, 0x01, 0x52, 0x0f, 0x69, 0x6e,
-	0x63, 0x6c, 0x75, 0x64, 0x65, 0x44, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x3a, 0x1c, 0x92,
-	0x41, 0x19, 0x0a, 0x17, 0x32, 0x15, 0x4c, 0x69, 0x73, 0x74, 0x20, 0x70, 0x6f, 0x6c, 0x69, 0x63,
-	0x69, 0x65, 0x73, 0x20, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x83, 0x02, 0x0a, 0x10,
-	0x47, 0x65, 0x74, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
-	0x12, 0xd3, 0x01, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x42, 0xc2, 0x01,
-	0x92, 0x41, 0xa6, 0x01, 0x32, 0x87, 0x01, 0x46, 0x6f, 0x72, 0x20, 0x62, 0x6c, 0x6f, 0x62, 0x2c,
-	0x20, 0x64, 0x69, 0x73, 0x6b, 0x2c, 0x20, 0x67, 0x69, 0x74, 0x20, 0x73, 0x74, 0x6f, 0x72, 0x65,
-	0x73, 0x20, 0x75, 0x73, 0x65, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x20,
-	0x28, 0x3c, 0x66, 0x69, 0x6c, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x3e, 0x2e, 0x79, 0x61, 0x6d, 0x6c,
-	0x29, 0x2e, 0x20, 0x46, 0x6f, 0x72, 0x20, 0x6d, 0x79, 0x73, 0x71, 0x6c, 0x2c, 0x20, 0x70, 0x6f,
-	0x73, 0x74, 0x67, 0x72, 0x65, 0x73, 0x2c, 0x20, 0x73, 0x71, 0x6c, 0x69, 0x74, 0x65, 0x33, 0x20,
-	0x75, 0x73, 0x65, 0x20, 0x69, 0x64, 0x20, 0x28, 0x3c, 0x6b, 0x69, 0x6e, 0x64, 0x3e, 0x2e, 0x3c,
-	0x6e, 0x61, 0x6d, 0x65, 0x3e, 0x2e, 0x3c, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x3e, 0x29,
-	0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x4a, 0x1a,
-	0x22, 0x70, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x2e, 0x73, 0x61, 0x72, 0x61, 0x68,
-	0x2e, 0x76, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x22, 0xe0, 0x41, 0x02, 0xfa, 0x42, 0x12,
-	0x92, 0x01, 0x0f, 0x08, 0x01, 0x10, 0x19, 0x18, 0x01, 0x22, 0x07, 0x72, 0x05, 0x10, 0x01, 0x18,
-	0x80, 0x0a, 0x52, 0x02, 0x69, 0x64, 0x3a, 0x19, 0x92, 0x41, 0x16, 0x0a, 0x14, 0x32, 0x12, 0x47,
-	0x65, 0x74, 0x20, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x20, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73,
-	0x74, 0x22, 0xa0, 0x01, 0x0a, 0x14, 0x44, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x50, 0x6f, 0x6c,
-	0x69, 0x63, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x69, 0x0a, 0x02, 0x69, 0x64,
-	0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x42, 0x59, 0x92, 0x41, 0x3e, 0x32, 0x20, 0x55, 0x6e, 0x69,
+	0x63, 0x6c, 0x75, 0x64, 0x65, 0x44, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x4c, 0x0a,
+	0x0b, 0x6e, 0x61, 0x6d, 0x65, 0x5f, 0x72, 0x65, 0x67, 0x65, 0x78, 0x70, 0x18, 0x02, 0x20, 0x01,
+	0x28, 0x09, 0x42, 0x2b, 0x92, 0x41, 0x25, 0x32, 0x23, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x20,
+	0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x20, 0x62, 0x79, 0x20, 0x6e, 0x61, 0x6d, 0x65,
+	0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x72, 0x65, 0x67, 0x65, 0x78, 0x70, 0xe0, 0x41, 0x01, 0x52,
+	0x0a, 0x6e, 0x61, 0x6d, 0x65, 0x52, 0x65, 0x67, 0x65, 0x78, 0x70, 0x12, 0x4f, 0x0a, 0x0c, 0x73,
+	0x63, 0x6f, 0x70, 0x65, 0x5f, 0x72, 0x65, 0x67, 0x65, 0x78, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28,
+	0x09, 0x42, 0x2c, 0x92, 0x41, 0x26, 0x32, 0x24, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x20, 0x70,
+	0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x20, 0x62, 0x79, 0x20, 0x73, 0x63, 0x6f, 0x70, 0x65,
+	0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x72, 0x65, 0x67, 0x65, 0x78, 0x70, 0xe0, 0x41, 0x01, 0x52,
+	0x0b, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x52, 0x65, 0x67, 0x65, 0x78, 0x70, 0x12, 0x55, 0x0a, 0x0e,
+	0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x72, 0x65, 0x67, 0x65, 0x78, 0x70, 0x18, 0x04,
+	0x20, 0x01, 0x28, 0x09, 0x42, 0x2e, 0x92, 0x41, 0x28, 0x32, 0x26, 0x46, 0x69, 0x6c, 0x74, 0x65,
+	0x72, 0x20, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x20, 0x62, 0x79, 0x20, 0x76, 0x65,
+	0x72, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x72, 0x65, 0x67, 0x65, 0x78,
+	0x70, 0xe0, 0x41, 0x01, 0x52, 0x0d, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x67,
+	0x65, 0x78, 0x70, 0x3a, 0x1c, 0x92, 0x41, 0x19, 0x0a, 0x17, 0x32, 0x15, 0x4c, 0x69, 0x73, 0x74,
+	0x20, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x69, 0x65, 0x73, 0x20, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73,
+	0x74, 0x22, 0x83, 0x02, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52,
+	0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0xd3, 0x01, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20,
+	0x03, 0x28, 0x09, 0x42, 0xc2, 0x01, 0x92, 0x41, 0xa6, 0x01, 0x32, 0x87, 0x01, 0x46, 0x6f, 0x72,
+	0x20, 0x62, 0x6c, 0x6f, 0x62, 0x2c, 0x20, 0x64, 0x69, 0x73, 0x6b, 0x2c, 0x20, 0x67, 0x69, 0x74,
+	0x20, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x73, 0x20, 0x75, 0x73, 0x65, 0x20, 0x66, 0x69, 0x6c, 0x65,
+	0x20, 0x6e, 0x61, 0x6d, 0x65, 0x20, 0x28, 0x3c, 0x66, 0x69, 0x6c, 0x65, 0x6e, 0x61, 0x6d, 0x65,
+	0x3e, 0x2e, 0x79, 0x61, 0x6d, 0x6c, 0x29, 0x2e, 0x20, 0x46, 0x6f, 0x72, 0x20, 0x6d, 0x79, 0x73,
+	0x71, 0x6c, 0x2c, 0x20, 0x70, 0x6f, 0x73, 0x74, 0x67, 0x72, 0x65, 0x73, 0x2c, 0x20, 0x73, 0x71,
+	0x6c, 0x69, 0x74, 0x65, 0x33, 0x20, 0x75, 0x73, 0x65, 0x20, 0x69, 0x64, 0x20, 0x28, 0x3c, 0x6b,
+	0x69, 0x6e, 0x64, 0x3e, 0x2e, 0x3c, 0x6e, 0x61, 0x6d, 0x65, 0x3e, 0x2e, 0x3c, 0x76, 0x65, 0x72,
+	0x73, 0x69, 0x6f, 0x6e, 0x3e, 0x29, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x70, 0x6f,
+	0x6c, 0x69, 0x63, 0x79, 0x4a, 0x1a, 0x22, 0x70, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c,
+	0x2e, 0x73, 0x61, 0x72, 0x61, 0x68, 0x2e, 0x76, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x22,
+	0xe0, 0x41, 0x02, 0xfa, 0x42, 0x12, 0x92, 0x01, 0x0f, 0x08, 0x01, 0x10, 0x19, 0x18, 0x01, 0x22,
+	0x07, 0x72, 0x05, 0x10, 0x01, 0x18, 0x80, 0x0a, 0x52, 0x02, 0x69, 0x64, 0x3a, 0x19, 0x92, 0x41,
+	0x16, 0x0a, 0x14, 0x32, 0x12, 0x47, 0x65, 0x74, 0x20, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x20,
+	0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xa0, 0x01, 0x0a, 0x14, 0x44, 0x69, 0x73, 0x61,
+	0x62, 0x6c, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
+	0x12, 0x69, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x42, 0x59, 0x92, 0x41,
+	0x3e, 0x32, 0x20, 0x55, 0x6e, 0x69, 0x71, 0x75, 0x65, 0x20, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69,
+	0x66, 0x69, 0x65, 0x72, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x74, 0x68, 0x65, 0x20, 0x70, 0x6f, 0x6c,
+	0x69, 0x63, 0x79, 0x4a, 0x1a, 0x22, 0x70, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x2e,
+	0x73, 0x61, 0x72, 0x61, 0x68, 0x2e, 0x76, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x22, 0xe0,
+	0x41, 0x02, 0xfa, 0x42, 0x12, 0x92, 0x01, 0x0f, 0x08, 0x01, 0x10, 0x19, 0x18, 0x01, 0x22, 0x07,
+	0x72, 0x05, 0x10, 0x01, 0x18, 0x80, 0x0a, 0x52, 0x02, 0x69, 0x64, 0x3a, 0x1d, 0x92, 0x41, 0x1a,
+	0x0a, 0x18, 0x32, 0x16, 0x44, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x20, 0x70, 0x6f, 0x6c, 0x69,
+	0x63, 0x79, 0x20, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x9e, 0x01, 0x0a, 0x13, 0x45,
+	0x6e, 0x61, 0x62, 0x6c, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65,
+	0x73, 0x74, 0x12, 0x69, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x42, 0x59,
+	0x92, 0x41, 0x3e, 0x32, 0x20, 0x55, 0x6e, 0x69, 0x71, 0x75, 0x65, 0x20, 0x69, 0x64, 0x65, 0x6e,
+	0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x74, 0x68, 0x65, 0x20, 0x70,
+	0x6f, 0x6c, 0x69, 0x63, 0x79, 0x4a, 0x1a, 0x22, 0x70, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61,
+	0x6c, 0x2e, 0x73, 0x61, 0x72, 0x61, 0x68, 0x2e, 0x76, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74,
+	0x22, 0xe0, 0x41, 0x02, 0xfa, 0x42, 0x12, 0x92, 0x01, 0x0f, 0x08, 0x01, 0x10, 0x19, 0x18, 0x01,
+	0x22, 0x07, 0x72, 0x05, 0x10, 0x01, 0x18, 0x80, 0x0a, 0x52, 0x02, 0x69, 0x64, 0x3a, 0x1c, 0x92,
+	0x41, 0x19, 0x0a, 0x17, 0x32, 0x15, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x20, 0x70, 0x6f, 0x6c,
+	0x69, 0x63, 0x79, 0x20, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x9a, 0x01, 0x0a, 0x18,
+	0x41, 0x64, 0x64, 0x4f, 0x72, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x63, 0x68, 0x65, 0x6d,
+	0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x5c, 0x0a, 0x07, 0x73, 0x63, 0x68, 0x65,
+	0x6d, 0x61, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x63, 0x65, 0x72, 0x62,
+	0x6f, 0x73, 0x2e, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x63, 0x68,
+	0x65, 0x6d, 0x61, 0x42, 0x28, 0x92, 0x41, 0x18, 0x32, 0x10, 0x4c, 0x69, 0x73, 0x74, 0x20, 0x6f,
+	0x66, 0x20, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x73, 0x2e, 0xa0, 0x01, 0x0a, 0xa8, 0x01, 0x01,
+	0xe0, 0x41, 0x02, 0xfa, 0x42, 0x07, 0x92, 0x01, 0x04, 0x08, 0x01, 0x10, 0x0a, 0x52, 0x07, 0x73,
+	0x63, 0x68, 0x65, 0x6d, 0x61, 0x73, 0x3a, 0x20, 0x92, 0x41, 0x1d, 0x0a, 0x1b, 0x32, 0x19, 0x41,
+	0x64, 0x64, 0x2f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x20, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61,
+	0x20, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x34, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74,
+	0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x3a, 0x1e,
+	0x92, 0x41, 0x1b, 0x0a, 0x19, 0x32, 0x17, 0x4c, 0x69, 0x73, 0x74, 0x20, 0x73, 0x63, 0x68, 0x65,
+	0x6d, 0x61, 0x20, 0x69, 0x64, 0x73, 0x20, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x91,
+	0x01, 0x0a, 0x10, 0x47, 0x65, 0x74, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x52, 0x65, 0x71, 0x75,
+	0x65, 0x73, 0x74, 0x12, 0x5f, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x42,
+	0x4f, 0x92, 0x41, 0x34, 0x32, 0x20, 0x55, 0x6e, 0x69, 0x71, 0x75, 0x65, 0x20, 0x69, 0x64, 0x65,
+	0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x74, 0x68, 0x65, 0x20,
+	0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x4a, 0x10, 0x22, 0x70, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70,
+	0x61, 0x6c, 0x2e, 0x6a, 0x73, 0x6f, 0x6e, 0x22, 0xe0, 0x41, 0x02, 0xfa, 0x42, 0x12, 0x92, 0x01,
+	0x0f, 0x08, 0x01, 0x10, 0x19, 0x18, 0x01, 0x22, 0x07, 0x72, 0x05, 0x10, 0x01, 0x18, 0xff, 0x01,
+	0x52, 0x02, 0x69, 0x64, 0x3a, 0x1c, 0x92, 0x41, 0x19, 0x0a, 0x17, 0x32, 0x15, 0x47, 0x65, 0x74,
+	0x20, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x28, 0x73, 0x29, 0x20, 0x72, 0x65, 0x71, 0x75, 0x65,
+	0x73, 0x74, 0x22, 0x97, 0x01, 0x0a, 0x13, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x63, 0x68,
+	0x65, 0x6d, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x5f, 0x0a, 0x02, 0x69, 0x64,
+	0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x42, 0x4f, 0x92, 0x41, 0x34, 0x32, 0x20, 0x55, 0x6e, 0x69,
 	0x71, 0x75, 0x65, 0x20, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x20, 0x66,
-	0x6f, 0x72, 0x20, 0x74, 0x68, 0x65, 0x20, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x4a, 0x1a, 0x22,
-	0x70, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x2e, 0x73, 0x61, 0x72, 0x61, 0x68, 0x2e,
-	0x76, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x22, 0xe0, 0x41, 0x02, 0xfa, 0x42, 0x12, 0x92,
-	0x01, 0x0f, 0x08, 0x01, 0x10, 0x19, 0x18, 0x01, 0x22, 0x07, 0x72, 0x05, 0x10, 0x01, 0x18, 0x80,
-	0x0a, 0x52, 0x02, 0x69, 0x64, 0x3a, 0x1d, 0x92, 0x41, 0x1a, 0x0a, 0x18, 0x32, 0x16, 0x44, 0x69,
-	0x73, 0x61, 0x62, 0x6c, 0x65, 0x20, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x20, 0x72, 0x65, 0x71,
-	0x75, 0x65, 0x73, 0x74, 0x22, 0x9e, 0x01, 0x0a, 0x13, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x50,
-	0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x69, 0x0a, 0x02,
-	0x69, 0x64, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x42, 0x59, 0x92, 0x41, 0x3e, 0x32, 0x20, 0x55,
-	0x6e, 0x69, 0x71, 0x75, 0x65, 0x20, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72,
-	0x20, 0x66, 0x6f, 0x72, 0x20, 0x74, 0x68, 0x65, 0x20, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x4a,
-	0x1a, 0x22, 0x70, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x2e, 0x73, 0x61, 0x72, 0x61,
-	0x68, 0x2e, 0x76, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x22, 0xe0, 0x41, 0x02, 0xfa, 0x42,
-	0x12, 0x92, 0x01, 0x0f, 0x08, 0x01, 0x10, 0x19, 0x18, 0x01, 0x22, 0x07, 0x72, 0x05, 0x10, 0x01,
-	0x18, 0x80, 0x0a, 0x52, 0x02, 0x69, 0x64, 0x3a, 0x1c, 0x92, 0x41, 0x19, 0x0a, 0x17, 0x32, 0x15,
-	0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x20, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x20, 0x72, 0x65,
-	0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x9a, 0x01, 0x0a, 0x18, 0x41, 0x64, 0x64, 0x4f, 0x72, 0x55,
-	0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65,
-	0x73, 0x74, 0x12, 0x5c, 0x0a, 0x07, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x73, 0x18, 0x01, 0x20,
-	0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x63, 0x65, 0x72, 0x62, 0x6f, 0x73, 0x2e, 0x73, 0x63, 0x68,
-	0x65, 0x6d, 0x61, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x42, 0x28, 0x92,
-	0x41, 0x18, 0x32, 0x10, 0x4c, 0x69, 0x73, 0x74, 0x20, 0x6f, 0x66, 0x20, 0x73, 0x63, 0x68, 0x65,
-	0x6d, 0x61, 0x73, 0x2e, 0xa0, 0x01, 0x0a, 0xa8, 0x01, 0x01, 0xe0, 0x41, 0x02, 0xfa, 0x42, 0x07,
-	0x92, 0x01, 0x04, 0x08, 0x01, 0x10, 0x0a, 0x52, 0x07, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x73,
-	0x3a, 0x20, 0x92, 0x41, 0x1d, 0x0a, 0x1b, 0x32, 0x19, 0x41, 0x64, 0x64, 0x2f, 0x75, 0x70, 0x64,
-	0x61, 0x74, 0x65, 0x20, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x20, 0x72, 0x65, 0x71, 0x75, 0x65,
-	0x73, 0x74, 0x22, 0x34, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61,
-	0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x3a, 0x1e, 0x92, 0x41, 0x1b, 0x0a, 0x19, 0x32,
-	0x17, 0x4c, 0x69, 0x73, 0x74, 0x20, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x20, 0x69, 0x64, 0x73,
-	0x20, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x91, 0x01, 0x0a, 0x10, 0x47, 0x65, 0x74,
-	0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x5f, 0x0a,
-	0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x42, 0x4f, 0x92, 0x41, 0x34, 0x32, 0x20,
-	0x55, 0x6e, 0x69, 0x71, 0x75, 0x65, 0x20, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65,
-	0x72, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61,
-	0x4a, 0x10, 0x22, 0x70, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x2e, 0x6a, 0x73, 0x6f,
-	0x6e, 0x22, 0xe0, 0x41, 0x02, 0xfa, 0x42, 0x12, 0x92, 0x01, 0x0f, 0x08, 0x01, 0x10, 0x19, 0x18,
-	0x01, 0x22, 0x07, 0x72, 0x05, 0x10, 0x01, 0x18, 0xff, 0x01, 0x52, 0x02, 0x69, 0x64, 0x3a, 0x1c,
-	0x92, 0x41, 0x19, 0x0a, 0x17, 0x32, 0x15, 0x47, 0x65, 0x74, 0x20, 0x73, 0x63, 0x68, 0x65, 0x6d,
-	0x61, 0x28, 0x73, 0x29, 0x20, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x97, 0x01, 0x0a,
-	0x13, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x52, 0x65, 0x71,
-	0x75, 0x65, 0x73, 0x74, 0x12, 0x5f, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09,
-	0x42, 0x4f, 0x92, 0x41, 0x34, 0x32, 0x20, 0x55, 0x6e, 0x69, 0x71, 0x75, 0x65, 0x20, 0x69, 0x64,
-	0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x74, 0x68, 0x65,
-	0x20, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x4a, 0x10, 0x22, 0x70, 0x72, 0x69, 0x6e, 0x63, 0x69,
-	0x70, 0x61, 0x6c, 0x2e, 0x6a, 0x73, 0x6f, 0x6e, 0x22, 0xe0, 0x41, 0x02, 0xfa, 0x42, 0x12, 0x92,
-	0x01, 0x0f, 0x08, 0x01, 0x10, 0x19, 0x18, 0x01, 0x22, 0x07, 0x72, 0x05, 0x10, 0x01, 0x18, 0xff,
-	0x01, 0x52, 0x02, 0x69, 0x64, 0x3a, 0x1f, 0x92, 0x41, 0x1c, 0x0a, 0x1a, 0x32, 0x18, 0x44, 0x65,
-	0x6c, 0x65, 0x74, 0x65, 0x20, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x28, 0x73, 0x29, 0x20, 0x72,
-	0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x79, 0x0a, 0x12, 0x52, 0x65, 0x6c, 0x6f, 0x61, 0x64,
-	0x53, 0x74, 0x6f, 0x72, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x46, 0x0a, 0x04,
-	0x77, 0x61, 0x69, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x42, 0x32, 0x92, 0x41, 0x2c, 0x32,
-	0x2a, 0x57, 0x61, 0x69, 0x74, 0x20, 0x75, 0x6e, 0x74, 0x69, 0x6c, 0x20, 0x74, 0x68, 0x65, 0x20,
-	0x72, 0x65, 0x6c, 0x6f, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x20, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73,
-	0x73, 0x20, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x73, 0xe0, 0x41, 0x01, 0x52, 0x04,
-	0x77, 0x61, 0x69, 0x74, 0x3a, 0x1b, 0x92, 0x41, 0x18, 0x0a, 0x16, 0x32, 0x14, 0x52, 0x65, 0x6c,
-	0x6f, 0x61, 0x64, 0x20, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x20, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73,
-	0x74, 0x42, 0x73, 0x0a, 0x19, 0x64, 0x65, 0x76, 0x2e, 0x63, 0x65, 0x72, 0x62, 0x6f, 0x73, 0x2e,
-	0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5a, 0x3e,
-	0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x65, 0x72, 0x62, 0x6f,
-	0x73, 0x2f, 0x63, 0x65, 0x72, 0x62, 0x6f, 0x73, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x67, 0x65, 0x6e,
-	0x70, 0x62, 0x2f, 0x63, 0x65, 0x72, 0x62, 0x6f, 0x73, 0x2f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73,
-	0x74, 0x2f, 0x76, 0x31, 0x3b, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x76, 0x31, 0xaa, 0x02,
-	0x15, 0x43, 0x65, 0x72, 0x62, 0x6f, 0x73, 0x2e, 0x41, 0x70, 0x69, 0x2e, 0x56, 0x31, 0x2e, 0x52,
-	0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+	0x6f, 0x72, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x4a, 0x10, 0x22,
+	0x70, 0x72, 0x69, 0x6e, 0x63, 0x69, 0x70, 0x61, 0x6c, 0x2e, 0x6a, 0x73, 0x6f, 0x6e, 0x22, 0xe0,
+	0x41, 0x02, 0xfa, 0x42, 0x12, 0x92, 0x01, 0x0f, 0x08, 0x01, 0x10, 0x19, 0x18, 0x01, 0x22, 0x07,
+	0x72, 0x05, 0x10, 0x01, 0x18, 0xff, 0x01, 0x52, 0x02, 0x69, 0x64, 0x3a, 0x1f, 0x92, 0x41, 0x1c,
+	0x0a, 0x1a, 0x32, 0x18, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x20, 0x73, 0x63, 0x68, 0x65, 0x6d,
+	0x61, 0x28, 0x73, 0x29, 0x20, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x79, 0x0a, 0x12,
+	0x52, 0x65, 0x6c, 0x6f, 0x61, 0x64, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65,
+	0x73, 0x74, 0x12, 0x46, 0x0a, 0x04, 0x77, 0x61, 0x69, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08,
+	0x42, 0x32, 0x92, 0x41, 0x2c, 0x32, 0x2a, 0x57, 0x61, 0x69, 0x74, 0x20, 0x75, 0x6e, 0x74, 0x69,
+	0x6c, 0x20, 0x74, 0x68, 0x65, 0x20, 0x72, 0x65, 0x6c, 0x6f, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x20,
+	0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x20, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65,
+	0x73, 0xe0, 0x41, 0x01, 0x52, 0x04, 0x77, 0x61, 0x69, 0x74, 0x3a, 0x1b, 0x92, 0x41, 0x18, 0x0a,
+	0x16, 0x32, 0x14, 0x52, 0x65, 0x6c, 0x6f, 0x61, 0x64, 0x20, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x20,
+	0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x42, 0x73, 0x0a, 0x19, 0x64, 0x65, 0x76, 0x2e, 0x63,
+	0x65, 0x72, 0x62, 0x6f, 0x73, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x72, 0x65, 0x71,
+	0x75, 0x65, 0x73, 0x74, 0x5a, 0x3e, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d,
+	0x2f, 0x63, 0x65, 0x72, 0x62, 0x6f, 0x73, 0x2f, 0x63, 0x65, 0x72, 0x62, 0x6f, 0x73, 0x2f, 0x61,
+	0x70, 0x69, 0x2f, 0x67, 0x65, 0x6e, 0x70, 0x62, 0x2f, 0x63, 0x65, 0x72, 0x62, 0x6f, 0x73, 0x2f,
+	0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2f, 0x76, 0x31, 0x3b, 0x72, 0x65, 0x71, 0x75, 0x65,
+	0x73, 0x74, 0x76, 0x31, 0xaa, 0x02, 0x15, 0x43, 0x65, 0x72, 0x62, 0x6f, 0x73, 0x2e, 0x41, 0x70,
+	0x69, 0x2e, 0x56, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x62, 0x06, 0x70, 0x72,
+	0x6f, 0x74, 0x6f, 0x33,
 }
 
 var (
diff --git a/api/genpb/cerbos/request/v1/request.pb.validate.go b/api/genpb/cerbos/request/v1/request.pb.validate.go
index 2bb46c58..2a703e22 100644
--- a/api/genpb/cerbos/request/v1/request.pb.validate.go
+++ b/api/genpb/cerbos/request/v1/request.pb.validate.go
@@ -3049,6 +3049,12 @@ func (m *ListPoliciesRequest) validate(all bool) error {
 
 	// no validation rules for IncludeDisabled
 
+	// no validation rules for NameRegexp
+
+	// no validation rules for ScopeRegexp
+
+	// no validation rules for VersionRegexp
+
 	if len(errors) > 0 {
 		return ListPoliciesRequestMultiError(errors)
 	}
diff --git a/api/genpb/cerbos/request/v1/request_vtproto.pb.go b/api/genpb/cerbos/request/v1/request_vtproto.pb.go
index 096211b5..d11105cd 100644
--- a/api/genpb/cerbos/request/v1/request_vtproto.pb.go
+++ b/api/genpb/cerbos/request/v1/request_vtproto.pb.go
@@ -1499,6 +1499,27 @@ func (m *ListPoliciesRequest) MarshalToSizedBufferVT(dAtA []byte) (int, error) {
 		i -= len(m.unknownFields)
 		copy(dAtA[i:], m.unknownFields)
 	}
+	if len(m.VersionRegexp) > 0 {
+		i -= len(m.VersionRegexp)
+		copy(dAtA[i:], m.VersionRegexp)
+		i = encodeVarint(dAtA, i, uint64(len(m.VersionRegexp)))
+		i--
+		dAtA[i] = 0x22
+	}
+	if len(m.ScopeRegexp) > 0 {
+		i -= len(m.ScopeRegexp)
+		copy(dAtA[i:], m.ScopeRegexp)
+		i = encodeVarint(dAtA, i, uint64(len(m.ScopeRegexp)))
+		i--
+		dAtA[i] = 0x1a
+	}
+	if len(m.NameRegexp) > 0 {
+		i -= len(m.NameRegexp)
+		copy(dAtA[i:], m.NameRegexp)
+		i = encodeVarint(dAtA, i, uint64(len(m.NameRegexp)))
+		i--
+		dAtA[i] = 0x12
+	}
 	if m.IncludeDisabled {
 		i--
 		if m.IncludeDisabled {
@@ -2486,6 +2507,18 @@ func (m *ListPoliciesRequest) SizeVT() (n int) {
 	if m.IncludeDisabled {
 		n += 2
 	}
+	l = len(m.NameRegexp)
+	if l > 0 {
+		n += 1 + l + sov(uint64(l))
+	}
+	l = len(m.ScopeRegexp)
+	if l > 0 {
+		n += 1 + l + sov(uint64(l))
+	}
+	l = len(m.VersionRegexp)
+	if l > 0 {
+		n += 1 + l + sov(uint64(l))
+	}
 	n += len(m.unknownFields)
 	return n
 }
@@ -5922,6 +5955,102 @@ func (m *ListPoliciesRequest) UnmarshalVT(dAtA []byte) error {
 				}
 			}
 			m.IncludeDisabled = bool(v != 0)
+		case 2:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field NameRegexp", wireType)
+			}
+			var stringLen uint64
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflow
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				stringLen |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			intStringLen := int(stringLen)
+			if intStringLen < 0 {
+				return ErrInvalidLength
+			}
+			postIndex := iNdEx + intStringLen
+			if postIndex < 0 {
+				return ErrInvalidLength
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.NameRegexp = string(dAtA[iNdEx:postIndex])
+			iNdEx = postIndex
+		case 3:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field ScopeRegexp", wireType)
+			}
+			var stringLen uint64
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflow
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				stringLen |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			intStringLen := int(stringLen)
+			if intStringLen < 0 {
+				return ErrInvalidLength
+			}
+			postIndex := iNdEx + intStringLen
+			if postIndex < 0 {
+				return ErrInvalidLength
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.ScopeRegexp = string(dAtA[iNdEx:postIndex])
+			iNdEx = postIndex
+		case 4:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field VersionRegexp", wireType)
+			}
+			var stringLen uint64
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflow
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				stringLen |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			intStringLen := int(stringLen)
+			if intStringLen < 0 {
+				return ErrInvalidLength
+			}
+			postIndex := iNdEx + intStringLen
+			if postIndex < 0 {
+				return ErrInvalidLength
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.VersionRegexp = string(dAtA[iNdEx:postIndex])
+			iNdEx = postIndex
 		default:
 			iNdEx = preIndex
 			skippy, err := skip(dAtA[iNdEx:])
diff --git a/api/public/cerbos/request/v1/request.proto b/api/public/cerbos/request/v1/request.proto
index 316c5d7e..6a13bfb4 100644
--- a/api/public/cerbos/request/v1/request.proto
+++ b/api/public/cerbos/request/v1/request.proto
@@ -521,6 +521,18 @@ message ListPoliciesRequest {
     (google.api.field_behavior) = OPTIONAL,
     (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "Include disabled policies"}
   ];
+  string name_regexp = 2 [
+    (google.api.field_behavior) = OPTIONAL,
+    (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "Filter policies by name with regexp"}
+  ];
+  string scope_regexp = 3 [
+    (google.api.field_behavior) = OPTIONAL,
+    (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "Filter policies by scope with regexp"}
+  ];
+  string version_regexp = 4 [
+    (google.api.field_behavior) = OPTIONAL,
+    (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "Filter policies by version with regexp"}
+  ];
 }
 
 message GetPolicyRequest {
diff --git a/client/admin.go b/client/admin.go
index b688bd60..d82fe736 100644
--- a/client/admin.go
+++ b/client/admin.go
@@ -177,9 +177,7 @@ func (c *GrpcAdminClient) auditLogs(ctx context.Context, opts AuditLogOptions) (
 }
 
 func (c *GrpcAdminClient) ListPolicies(ctx context.Context, opts ...ListPoliciesOption) ([]string, error) {
-	req := &requestv1.ListPoliciesRequest{
-		IncludeDisabled: false,
-	}
+	req := &requestv1.ListPoliciesRequest{}
 	for _, opt := range opts {
 		opt(req)
 	}
diff --git a/client/model.go b/client/model.go
index 7c87afd1..7a7c45a5 100644
--- a/client/model.go
+++ b/client/model.go
@@ -1259,3 +1259,21 @@ func WithIncludeDisabled() ListPoliciesOption {
 		request.IncludeDisabled = true
 	}
 }
+
+func WithNameRegexp(re string) ListPoliciesOption {
+	return func(request *requestv1.ListPoliciesRequest) {
+		request.NameRegexp = re
+	}
+}
+
+func WithScopeRegexp(re string) ListPoliciesOption {
+	return func(request *requestv1.ListPoliciesRequest) {
+		request.ScopeRegexp = re
+	}
+}
+
+func WithVersionRegexp(v string) ListPoliciesOption {
+	return func(request *requestv1.ListPoliciesRequest) {
+		request.VersionRegexp = v
+	}
+}
diff --git a/cmd/cerbos/repl/internal/repl.go b/cmd/cerbos/repl/internal/repl.go
index b85d811f..00ad3e65 100644
--- a/cmd/cerbos/repl/internal/repl.go
+++ b/cmd/cerbos/repl/internal/repl.go
@@ -323,6 +323,18 @@ func (r *REPL) setSpecialVar(name, value string) error {
 		r.evalPolicyVariables()
 		r.output.PrintResult(name, r.vars[conditions.CELVariablesIdent])
 
+	case conditions.CELGlobalsIdent, conditions.CELGlobalsAbbrev:
+		var globals map[string]any
+		if err := json.Unmarshal([]byte(value), &globals); err != nil {
+			return fmt.Errorf("failed to unmarshal JSON as %q: %w", name, err)
+		}
+
+		globalsVal := r.toRefVal(globals)
+		r.vars[conditions.CELGlobalsIdent] = globalsVal
+		r.vars[conditions.CELGlobalsAbbrev] = globalsVal
+		r.evalPolicyVariables()
+		r.output.PrintResult(name, r.vars[conditions.CELGlobalsIdent])
+
 	default:
 		return fmt.Errorf("setting %q is unsupported", name)
 	}
diff --git a/docs/modules/api/pages/admin_api.adoc b/docs/modules/api/pages/admin_api.adoc
index 3171263c..56f08c90 100644
--- a/docs/modules/api/pages/admin_api.adoc
+++ b/docs/modules/api/pages/admin_api.adoc
@@ -131,10 +131,12 @@ GET /admin/policies
 
 NOTE: This endpoint is still under development and should be considered unstable.
 
-Issue a GET request to the endpoint to list the policies available in the store.
+Issue a GET request to the endpoint to list the policies available in the store. If the policy store supports filtering, you can optionally pass filter parameters to reduce the result set.
 
 Use `includeDisabled=true` query parameter in order to include disabled policies in the response.
 
+Use `nameRegexp`, `scopeRegexp` and `versionRegexp` to filter using the policy name, scope or version with case insensitive regular expressions.
+
 [source,shell]
 ----
 curl -k -u cerbos:cerbosAdmin \
@@ -144,7 +146,7 @@ curl -k -u cerbos:cerbosAdmin \
 [source,shell]
 ----
 curl -k -u cerbos:cerbosAdmin \
-    'https://localhost:3592/admin/policies?pretty&includeDisabled=true'
+    'https://localhost:3592/admin/policies?pretty&includeDisabled=true&nameRegexp=%5Efoo&scopeRegexp=bar%24&versionRegexp=default'
 ----
 
 === Get Policies
diff --git a/docs/modules/configuration/pages/engine.adoc b/docs/modules/configuration/pages/engine.adoc
index e467bb1e..a263eb65 100644
--- a/docs/modules/configuration/pages/engine.adoc
+++ b/docs/modules/configuration/pages/engine.adoc
@@ -13,3 +13,38 @@ engine:
   defaultPolicyVersion: "default"
 ----
 
+[#globals]
+== Globals
+
+Global variables are a way to pass environment-specific information to xref:policies:conditions.adoc[policy conditions]. For example, you might want to grant additional permissions to a role in your staging environment, without creating separate policy versions for different environments.
+
+[source,yaml,linenums]
+----
+engine:
+  globals:
+    environment: "staging"
+----
+
+Values set in `globals` can then be referenced in policy conditions:
+
+[source,yaml,linenums]
+----
+rules:
+  - actions:
+      - view
+    effect: EFFECT_ALLOW
+    roles:
+      - developer
+    condition:
+      match:
+        expr: globals.environment != "production"
+----
+
+As with other configuration settings, environment variables can be used to set global values.
+
+[source,yaml,linenums]
+----
+engine:
+  globals:
+    environment: ${CERBOS_ENVIRONMENT:development}
+----
diff --git a/docs/modules/configuration/pages/index.adoc b/docs/modules/configuration/pages/index.adoc
index 286bb59b..49caef4c 100644
--- a/docs/modules/configuration/pages/index.adoc
+++ b/docs/modules/configuration/pages/index.adoc
@@ -10,7 +10,7 @@ The Cerbos server is configured with a YAML file. Start the server by passing th
 ./{app-name} server --config=/path/to/config.yaml --set=server.httpListenAddr=:3592 --set=engine.defaultPolicyVersion=staging
 ----
 
-NOTE: Config values can reference environment variables by enclosing them between `${}`. E.g. `$$${HOME}$$`.
+NOTE: Config values can reference environment variables by enclosing them between `${}`, for example `$$${HOME}$$`. Defaults can be set using `$$${VAR:default}$$`.
 
 
 == Minimal Configuration
diff --git a/docs/modules/configuration/partials/fullconfiguration.adoc b/docs/modules/configuration/partials/fullconfiguration.adoc
index 384a7db3..989617c1 100644
--- a/docs/modules/configuration/partials/fullconfiguration.adoc
+++ b/docs/modules/configuration/partials/fullconfiguration.adoc
@@ -50,6 +50,7 @@ compile:
   cacheSize: 1024 # CacheSize is the number of compiled policies to cache in memory.
 engine:
   defaultPolicyVersion: "default" # DefaultPolicyVersion defines what version to assume if the request does not specify one.
+  globals: {"environment": "staging"} # Globals are environment-specific variables to be made available to policy conditions.
 schema:
   cacheSize: 1024 # CacheSize defines the number of schemas to cache in memory.
   enforcement: reject # Enforcement defines level of the validations. Possible values are none, warn, reject.
diff --git a/docs/modules/policies/pages/conditions.adoc b/docs/modules/policies/pages/conditions.adoc
index 0849ecde..cf3c6498 100644
--- a/docs/modules/policies/pages/conditions.adoc
+++ b/docs/modules/policies/pages/conditions.adoc
@@ -21,9 +21,11 @@ Within a condition block you have access to several special variables:
 
 `request`:: The entire request object containing data provided about the resource and the principal. Use dots to access nested fields. For example, the expression to get the principal's department attribute is `request.principal.attr.department`.
 `variables`:: Access variables declared in the `variables` section of the policy.
+`globals`:: Access global variables declared in the xref:configuration:engine.adoc#globals[policy engine configuration].
 `P`:: An alias for `request.principal` for accessing the principal object.
 `R`:: An alias for `request.resource` for accessing the resource object.
 `V`:: An alias for `variables` for accessing the policy variables object.
+`G`:: An alias for `globals` for accessing the global variables object.
 
 Every condition expression must evaluate to a boolean true/false value. You can combine complex expressions together in a single condition block by using the `all`, `any`, or `none` operators.
 
diff --git a/internal/conditions/cel.go b/internal/conditions/cel.go
index 35a82e1a..39d1fe51 100644
--- a/internal/conditions/cel.go
+++ b/internal/conditions/cel.go
@@ -25,6 +25,8 @@ const (
 	CELPrincipalField  = "principal"
 	CELVariablesIdent  = "variables"
 	CELVariablesAbbrev = "V"
+	CELGlobalsIdent    = "globals"
+	CELGlobalsAbbrev   = "G"
 	CELAuxDataField    = "aux_data"
 	CELAttrField       = "attr"
 )
@@ -42,6 +44,8 @@ var StdEnvDecls = []*exprpb.Decl{
 	decls.NewVar(CELResourceAbbrev, decls.NewObjectType("cerbos.engine.v1.Resource")),
 	decls.NewVar(CELVariablesIdent, decls.NewMapType(decls.String, decls.Dyn)),
 	decls.NewVar(CELVariablesAbbrev, decls.NewMapType(decls.String, decls.Dyn)),
+	decls.NewVar(CELGlobalsIdent, decls.NewMapType(decls.String, decls.Dyn)),
+	decls.NewVar(CELGlobalsAbbrev, decls.NewMapType(decls.String, decls.Dyn)),
 }
 
 func init() {
@@ -114,6 +118,8 @@ func ExpandAbbrev(s string) string {
 		expanded = Fqn(CELResourceField)
 	case CELVariablesAbbrev:
 		expanded = CELVariablesIdent
+	case CELGlobalsAbbrev:
+		expanded = CELGlobalsIdent
 	}
 
 	if ok {
@@ -136,6 +142,8 @@ func newCELQueryPlanEnvOptions() []cel.EnvOption {
 			decls.NewVar(CELResourceAbbrev, decls.NewObjectType("cerbos.engine.v1.Resource")),
 			decls.NewVar(CELVariablesIdent, decls.NewMapType(decls.String, decls.Dyn)),
 			decls.NewVar(CELVariablesAbbrev, decls.NewMapType(decls.String, decls.Dyn)),
+			decls.NewVar(CELGlobalsIdent, decls.NewMapType(decls.String, decls.Dyn)),
+			decls.NewVar(CELGlobalsAbbrev, decls.NewMapType(decls.String, decls.Dyn)),
 		),
 		ext.Strings(),
 		ext.Encoders(),
diff --git a/internal/engine/conf.go b/internal/engine/conf.go
index 19472038..cd542bfd 100644
--- a/internal/engine/conf.go
+++ b/internal/engine/conf.go
@@ -18,6 +18,8 @@ var errEmptyDefaultVersion = errors.New("engine.defaultVersion must not be an em
 
 // Conf is optional configuration for engine.
 type Conf struct {
+	// Globals are environment-specific variables to be made available to policy conditions.
+	Globals map[string]any `yaml:"globals" conf:",example={\"environment\": \"staging\"}"`
 	// DefaultPolicyVersion defines what version to assume if the request does not specify one.
 	DefaultPolicyVersion string `yaml:"defaultPolicyVersion" conf:",example=\"default\""`
 	NumWorkers           uint   `yaml:"numWorkers" conf:",ignore"`
diff --git a/internal/engine/engine.go b/internal/engine/engine.go
index 669c9226..c82829fd 100644
--- a/internal/engine/engine.go
+++ b/internal/engine/engine.go
@@ -50,13 +50,13 @@ type checkOptions struct {
 	evalParams evalParams
 }
 
-func newCheckOptions(ctx context.Context, opts ...CheckOpt) *checkOptions {
+func newCheckOptions(ctx context.Context, globals map[string]any, opts ...CheckOpt) *checkOptions {
 	var tracerSink tracer.Sink
 	if debugEnabled, ok := os.LookupEnv("CERBOS_DEBUG_ENGINE"); ok && debugEnabled != "false" {
 		tracerSink = tracer.NewZapSink(logging.FromContext(ctx).Named("tracer"))
 	}
 
-	co := &checkOptions{tracerSink: tracerSink, evalParams: defaultEvalParams()}
+	co := &checkOptions{tracerSink: tracerSink, evalParams: defaultEvalParams(globals)}
 	for _, opt := range opts {
 		opt(co)
 	}
@@ -214,7 +214,7 @@ func (engine *Engine) doPlanResources(ctx context.Context, input *enginev1.PlanR
 	result := new(planner.PolicyPlanResult)
 
 	if policy := policySet.GetPrincipalPolicy(); policy != nil {
-		policyEvaluator := planner.PrincipalPolicyEvaluator{Policy: policy}
+		policyEvaluator := planner.PrincipalPolicyEvaluator{Policy: policy, Globals: engine.conf.Globals}
 		result, err = policyEvaluator.EvaluateResourcesQueryPlan(ctx, input)
 		if err != nil {
 			return nil, err
@@ -229,7 +229,7 @@ func (engine *Engine) doPlanResources(ctx context.Context, input *enginev1.PlanR
 	}
 
 	if policy := policySet.GetResourcePolicy(); policy != nil {
-		policyEvaluator := planner.ResourcePolicyEvaluator{Policy: policy, SchemaMgr: engine.schemaMgr}
+		policyEvaluator := planner.ResourcePolicyEvaluator{Policy: policy, Globals: engine.conf.Globals, SchemaMgr: engine.schemaMgr}
 		plan, err := policyEvaluator.EvaluateResourcesQueryPlan(ctx, input)
 		if err != nil {
 			return nil, err
@@ -296,7 +296,7 @@ func (engine *Engine) Check(ctx context.Context, inputs []*enginev1.CheckInput,
 		ctx, span := tracing.StartSpan(ctx, "engine.Check")
 		defer span.End()
 
-		checkOpts := newCheckOptions(ctx, opts...)
+		checkOpts := newCheckOptions(ctx, engine.conf.Globals, opts...)
 
 		// if the number of inputs is less than the threshold, do a serial execution as it is usually faster.
 		// ditto if the worker pool is not initialized
diff --git a/internal/engine/evaluator.go b/internal/engine/evaluator.go
index c51480d0..6a7d903d 100644
--- a/internal/engine/evaluator.go
+++ b/internal/engine/evaluator.go
@@ -31,11 +31,15 @@ import (
 var ErrPolicyNotExecutable = errors.New("policy not executable")
 
 type evalParams struct {
+	globals map[string]any
 	nowFunc func() time.Time
 }
 
-func defaultEvalParams() evalParams {
-	return evalParams{nowFunc: time.Now}
+func defaultEvalParams(globals map[string]any) evalParams {
+	return evalParams{
+		globals: globals,
+		nowFunc: time.Now,
+	}
 }
 
 type Evaluator interface {
@@ -431,6 +435,8 @@ func (ep evalParams) evaluateCELExpr(expr *exprpb.CheckedExpr, variables map[str
 		conditions.CELPrincipalAbbrev: input.Principal,
 		conditions.CELVariablesIdent:  variables,
 		conditions.CELVariablesAbbrev: variables,
+		conditions.CELGlobalsIdent:    ep.globals,
+		conditions.CELGlobalsAbbrev:   ep.globals,
 	}, ep.nowFunc)
 	if err != nil {
 		// ignore expressions that access non-existent keys
diff --git a/internal/engine/planner/planner.go b/internal/engine/planner/planner.go
index 66498b75..9997da7a 100644
--- a/internal/engine/planner/planner.go
+++ b/internal/engine/planner/planner.go
@@ -49,11 +49,13 @@ type (
 
 type ResourcePolicyEvaluator struct {
 	Policy    *runtimev1.RunnableResourcePolicySet
+	Globals   map[string]any
 	SchemaMgr schema.Manager
 }
 
 type PrincipalPolicyEvaluator struct {
-	Policy *runtimev1.RunnablePrincipalPolicySet
+	Policy  *runtimev1.RunnablePrincipalPolicySet
+	Globals map[string]any
 }
 
 func CombinePlans(principalPolicyPlan, resourcePolicyPlan *PolicyPlanResult) *PolicyPlanResult {
@@ -169,7 +171,7 @@ func (ppe *PrincipalPolicyEvaluator) EvaluateResourcesQueryPlan(ctx context.Cont
 					variables[k] = v.Checked.Expr
 				}
 
-				filter, err := evaluateCondition(rule.Condition, input, variables)
+				filter, err := evaluateCondition(rule.Condition, input, ppe.Globals, variables)
 				if err != nil {
 					return nil, err
 				}
@@ -231,7 +233,7 @@ func (rpe *ResourcePolicyEvaluator) EvaluateResourcesQueryPlan(ctx context.Conte
 					for k, v := range dr.Variables {
 						drVariables[k] = v.Checked.Expr
 					}
-					node, err := evaluateCondition(dr.Condition, input, drVariables)
+					node, err := evaluateCondition(dr.Condition, input, rpe.Globals, drVariables)
 					if err != nil {
 						return nil, err
 					}
@@ -280,7 +282,7 @@ func (rpe *ResourcePolicyEvaluator) EvaluateResourcesQueryPlan(ctx context.Conte
 					variables[k] = v.Checked.Expr
 				}
 
-				node, err := evaluateCondition(rule.Condition, input, variables)
+				node, err := evaluateCondition(rule.Condition, input, rpe.Globals, variables)
 				if err != nil {
 					return nil, err
 				}
@@ -373,7 +375,7 @@ func invertNodeBooleanValue(node *enginev1.PlanResourcesAst_Node) *enginev1.Plan
 	return &qpN{Node: &qpNLO{LogicalOperation: lo}}
 }
 
-func evaluateCondition(condition *runtimev1.Condition, input *enginev1.PlanResourcesInput, variables map[string]*exprpb.Expr) (*enginev1.PlanResourcesAst_Node, error) {
+func evaluateCondition(condition *runtimev1.Condition, input *enginev1.PlanResourcesInput, globals map[string]any, variables map[string]*exprpb.Expr) (*enginev1.PlanResourcesAst_Node, error) {
 	if condition == nil {
 		return mkTrueNode(), nil
 	}
@@ -383,7 +385,7 @@ func evaluateCondition(condition *runtimev1.Condition, input *enginev1.PlanResou
 	case *runtimev1.Condition_Any:
 		nodes := make([]*qpN, 0, len(t.Any.Expr))
 		for _, c := range t.Any.Expr {
-			node, err := evaluateCondition(c, input, variables)
+			node, err := evaluateCondition(c, input, globals, variables)
 			if err != nil {
 				return nil, err
 			}
@@ -407,7 +409,7 @@ func evaluateCondition(condition *runtimev1.Condition, input *enginev1.PlanResou
 	case *runtimev1.Condition_All:
 		nodes := make([]*qpN, 0, len(t.All.Expr))
 		for _, c := range t.All.Expr {
-			node, err := evaluateCondition(c, input, variables)
+			node, err := evaluateCondition(c, input, globals, variables)
 			if err != nil {
 				return nil, err
 			}
@@ -430,7 +432,7 @@ func evaluateCondition(condition *runtimev1.Condition, input *enginev1.PlanResou
 	case *runtimev1.Condition_None:
 		nodes := make([]*qpN, 0, len(t.None.Expr))
 		for _, c := range t.None.Expr {
-			node, err := evaluateCondition(c, input, variables)
+			node, err := evaluateCondition(c, input, globals, variables)
 			if err != nil {
 				return nil, err
 			}
@@ -457,7 +459,7 @@ func evaluateCondition(condition *runtimev1.Condition, input *enginev1.PlanResou
 			res.Node = &qpNLO{LogicalOperation: mkAndLogicalOperation(nodes)}
 		}
 	case *runtimev1.Condition_Expr:
-		residual, err := evaluateConditionExpression(t.Expr.Checked, input, variables)
+		residual, err := evaluateConditionExpression(t.Expr.Checked, input, globals, variables)
 		if err != nil {
 			return nil, fmt.Errorf("error evaluating condition %q: %w", t.Expr.Original, err)
 		}
@@ -468,8 +470,8 @@ func evaluateCondition(condition *runtimev1.Condition, input *enginev1.PlanResou
 	return res, nil
 }
 
-func evaluateConditionExpression(expr *exprpb.CheckedExpr, input *enginev1.PlanResourcesInput, variables map[string]*exprpb.Expr) (*exprpb.CheckedExpr, error) {
-	p, err := newEvaluator(input)
+func evaluateConditionExpression(expr *exprpb.CheckedExpr, input *enginev1.PlanResourcesInput, globals map[string]any, variables map[string]*exprpb.Expr) (*exprpb.CheckedExpr, error) {
+	p, err := newEvaluator(input, globals)
 	if err != nil {
 		return nil, err
 	}
@@ -541,12 +543,14 @@ func (p *partialEvaluator) evalPartially(e *exprpb.Expr) (ref.Val, *exprpb.Expr,
 	return val, residual, nil
 }
 
-func newEvaluator(input *enginev1.PlanResourcesInput) (p *partialEvaluator, err error) {
+func newEvaluator(input *enginev1.PlanResourcesInput, globals map[string]any) (p *partialEvaluator, err error) {
 	p = new(partialEvaluator)
 	knownVars := make(map[string]any)
 	knownVars[conditions.CELRequestIdent] = input
 	knownVars[conditions.CELPrincipalAbbrev] = input.Principal
 	knownVars[conditions.Fqn(conditions.CELPrincipalField)] = input.Principal
+	knownVars[conditions.CELGlobalsIdent] = globals
+	knownVars[conditions.CELGlobalsAbbrev] = globals
 
 	p.env = conditions.StdPartialEnv
 	if len(input.Resource.GetAttr()) > 0 {
diff --git a/internal/storage/blob/store.go b/internal/storage/blob/store.go
index d2019783..967ec213 100644
--- a/internal/storage/blob/store.go
+++ b/internal/storage/blob/store.go
@@ -314,7 +314,7 @@ func (s *Store) GetDependents(_ context.Context, ids ...namer.ModuleID) (map[nam
 	return s.idx.GetDependents(ids...)
 }
 
-func (s *Store) ListPolicyIDs(ctx context.Context, _ bool) ([]string, error) {
+func (s *Store) ListPolicyIDs(ctx context.Context, _ storage.ListPolicyIDsParams) ([]string, error) {
 	return s.idx.ListPolicyIDs(ctx)
 }
 
diff --git a/internal/storage/bundle/bundle.go b/internal/storage/bundle/bundle.go
index 7b58b3fa..6bcc5a54 100644
--- a/internal/storage/bundle/bundle.go
+++ b/internal/storage/bundle/bundle.go
@@ -15,6 +15,7 @@ import (
 
 	runtimev1 "github.com/cerbos/cerbos/api/genpb/cerbos/runtime/v1"
 	"github.com/cerbos/cerbos/internal/namer"
+	"github.com/cerbos/cerbos/internal/storage"
 	"github.com/cerbos/cloud-api/credentials"
 	bundlev1 "github.com/cerbos/cloud-api/genpb/cerbos/cloud/bundle/v1"
 	"github.com/spf13/afero"
@@ -201,7 +202,7 @@ func (b *Bundle) GetPolicySet(_ context.Context, id namer.ModuleID) (*runtimev1.
 	return rps, nil
 }
 
-func (b *Bundle) ListPolicyIDs(_ context.Context, _ bool) ([]string, error) {
+func (b *Bundle) ListPolicyIDs(_ context.Context, _ storage.ListPolicyIDsParams) ([]string, error) {
 	output := make([]string, len(b.manifest.PolicyIndex))
 
 	i := 0
diff --git a/internal/storage/bundle/local_source.go b/internal/storage/bundle/local_source.go
index d0c1707d..19d418d0 100644
--- a/internal/storage/bundle/local_source.go
+++ b/internal/storage/bundle/local_source.go
@@ -119,9 +119,9 @@ func (ls *LocalSource) Driver() string {
 	return DriverName
 }
 
-func (ls *LocalSource) ListPolicyIDs(ctx context.Context, includeDisabled bool) (ids []string, err error) {
+func (ls *LocalSource) ListPolicyIDs(ctx context.Context, params storage.ListPolicyIDsParams) (ids []string, err error) {
 	ls.mu.RLock()
-	ids, err = ls.bundle.ListPolicyIDs(ctx, includeDisabled)
+	ids, err = ls.bundle.ListPolicyIDs(ctx, params)
 	ls.mu.RUnlock()
 	return ids, err
 }
diff --git a/internal/storage/bundle/remote_source.go b/internal/storage/bundle/remote_source.go
index 5ee45eb8..2afdd418 100644
--- a/internal/storage/bundle/remote_source.go
+++ b/internal/storage/bundle/remote_source.go
@@ -420,7 +420,7 @@ func (s *RemoteSource) GetPolicySet(ctx context.Context, id namer.ModuleID) (*ru
 	return s.bundle.GetPolicySet(ctx, id)
 }
 
-func (s *RemoteSource) ListPolicyIDs(ctx context.Context, includeDisabled bool) ([]string, error) {
+func (s *RemoteSource) ListPolicyIDs(ctx context.Context, params storage.ListPolicyIDsParams) ([]string, error) {
 	s.mu.RLock()
 	defer s.mu.RUnlock()
 
@@ -428,7 +428,7 @@ func (s *RemoteSource) ListPolicyIDs(ctx context.Context, includeDisabled bool)
 		return nil, ErrBundleNotLoaded
 	}
 
-	return s.bundle.ListPolicyIDs(ctx, includeDisabled)
+	return s.bundle.ListPolicyIDs(ctx, params)
 }
 
 func (s *RemoteSource) ListSchemaIDs(ctx context.Context) ([]string, error) {
diff --git a/internal/storage/bundle/source_wrappers.go b/internal/storage/bundle/source_wrappers.go
index 23a03a57..6ed49d8c 100644
--- a/internal/storage/bundle/source_wrappers.go
+++ b/internal/storage/bundle/source_wrappers.go
@@ -33,9 +33,9 @@ func (instrumentedSource) Driver() string {
 	return DriverName
 }
 
-func (is instrumentedSource) ListPolicyIDs(ctx context.Context, includeDisabled bool) ([]string, error) {
+func (is instrumentedSource) ListPolicyIDs(ctx context.Context, params storage.ListPolicyIDsParams) ([]string, error) {
 	return measureBinaryOp(ctx, is.name, "ListPolicyIDs", func(ctx context.Context) ([]string, error) {
-		return is.source.ListPolicyIDs(ctx, includeDisabled)
+		return is.source.ListPolicyIDs(ctx, params)
 	})
 }
 
diff --git a/internal/storage/bundle/store.go b/internal/storage/bundle/store.go
index 1b8d71a0..ba9cc9e4 100644
--- a/internal/storage/bundle/store.go
+++ b/internal/storage/bundle/store.go
@@ -104,8 +104,8 @@ func (hs *HybridStore) withActiveSource() storage.BinaryStore {
 	return hs.local
 }
 
-func (hs *HybridStore) ListPolicyIDs(ctx context.Context, includeDisabled bool) ([]string, error) {
-	return hs.withActiveSource().ListPolicyIDs(ctx, includeDisabled)
+func (hs *HybridStore) ListPolicyIDs(ctx context.Context, params storage.ListPolicyIDsParams) ([]string, error) {
+	return hs.withActiveSource().ListPolicyIDs(ctx, params)
 }
 
 func (hs *HybridStore) ListSchemaIDs(ctx context.Context) ([]string, error) {
diff --git a/internal/storage/db/internal/db.go b/internal/storage/db/internal/db.go
index 3cb2b2d4..d0bfa012 100644
--- a/internal/storage/db/internal/db.go
+++ b/internal/storage/db/internal/db.go
@@ -11,6 +11,7 @@ import (
 	"io"
 	"net/url"
 	"os"
+	"regexp"
 	"strings"
 
 	"github.com/doug-martin/goqu/v9"
@@ -41,7 +42,7 @@ type DBStorage interface {
 	GetDependents(ctx context.Context, ids ...namer.ModuleID) (map[namer.ModuleID][]namer.ModuleID, error)
 	HasDescendants(ctx context.Context, ids ...namer.ModuleID) (map[namer.ModuleID]bool, error)
 	Delete(ctx context.Context, ids ...namer.ModuleID) error
-	ListPolicyIDs(ctx context.Context, includeDisabled bool) ([]string, error)
+	ListPolicyIDs(ctx context.Context, params storage.ListPolicyIDsParams) ([]string, error)
 	ListSchemaIDs(ctx context.Context) ([]string, error)
 	AddOrUpdateSchema(ctx context.Context, schemas ...*schemav1.Schema) error
 	Disable(ctx context.Context, policyKey ...string) (uint32, error)
@@ -52,7 +53,7 @@ type DBStorage interface {
 }
 
 func NewDBStorage(ctx context.Context, db *goqu.Database, dbOpts ...DBOpt) (DBStorage, error) {
-	opts := &dbOpt{}
+	opts := newDbOpt()
 	for _, opt := range dbOpts {
 		opt(opts)
 	}
@@ -628,13 +629,33 @@ func (s *dbStorage) Delete(ctx context.Context, ids ...namer.ModuleID) error {
 	return nil
 }
 
-func (s *dbStorage) ListPolicyIDs(ctx context.Context, includeDisabled bool) ([]string, error) {
+func (s *dbStorage) ListPolicyIDs(ctx context.Context, listParams storage.ListPolicyIDsParams) ([]string, error) {
 	var policyCoords []namer.PolicyCoords
 	var whereExprs []exp.Expression
-	if !includeDisabled {
+	var postFilters []postRegexpFilter
+
+	if !listParams.IncludeDisabled {
 		whereExprs = append(whereExprs, goqu.C(PolicyTblDisabledCol).Neq(goqu.V(true)))
 	}
 
+	if listParams.NameRegexp != "" {
+		if err := s.updateRegexpFilters(listParams.NameRegexp, PolicyTblNameCol, &whereExprs, &postFilters); err != nil {
+			return nil, err
+		}
+	}
+
+	if listParams.ScopeRegexp != "" {
+		if err := s.updateRegexpFilters(listParams.ScopeRegexp, PolicyTblScopeCol, &whereExprs, &postFilters); err != nil {
+			return nil, err
+		}
+	}
+
+	if listParams.VersionRegexp != "" {
+		if err := s.updateRegexpFilters(listParams.VersionRegexp, PolicyTblVerCol, &whereExprs, &postFilters); err != nil {
+			return nil, err
+		}
+	}
+
 	err := s.db.From(PolicyTbl).
 		Select(
 			goqu.C(PolicyTblKindCol),
@@ -655,14 +676,67 @@ func (s *dbStorage) ListPolicyIDs(ctx context.Context, includeDisabled bool) ([]
 		return nil, fmt.Errorf("could not execute %q query: %w", "ListPolicyIDs", err)
 	}
 
-	policyIDs := make([]string, len(policyCoords))
-	for i := 0; i < len(policyCoords); i++ {
-		policyIDs[i] = policyCoords[i].PolicyKey()
+	policyIDs := make([]string, 0, len(policyCoords))
+	for _, pc := range policyCoords {
+		if checkPostFilters(pc, postFilters) {
+			policyIDs = append(policyIDs, pc.PolicyKey())
+		}
 	}
 
 	return policyIDs, nil
 }
 
+type postRegexpFilter struct {
+	re  *regexp.Regexp
+	col string
+}
+
+// updateRegexpFilters updates either `whereExprs` or `postFilters` in place, dependent on whether regexp support is enabled or not.
+func (s *dbStorage) updateRegexpFilters(namePattern, col string, whereExprs *[]exp.Expression, postFilters *[]postRegexpFilter) error {
+	r, err := s.opts.regexpCache.GetCompiledExpr(namePattern)
+	if err != nil {
+		return err
+	}
+	if s.regexpEnabled() {
+		// We need to pass a *regexp.Regexp expression to `Like` (or `RegexpLike`, which is equivalent) in order for goqu
+		// to correctly parse the query. We need to pass the compiled expression in order for goqu to access (only) the
+		// raw string (https://github.com/doug-martin/goqu/blob/master/exp/bool.go#L148).
+		// We use a cache to prevent the need to recompile arbitrary strings on each request.
+		// In the case of the SQLite driver, to support regexp, we generate an application-defined function in which we
+		// use the cached compiled expressions.
+		*whereExprs = append(*whereExprs, goqu.C(col).ILike(r))
+	} else {
+		*postFilters = append(*postFilters, postRegexpFilter{re: r, col: col})
+	}
+
+	return nil
+}
+
+func (s *dbStorage) regexpEnabled() bool {
+	// TODO(saml) link this to the `dialect` arg passed to `goqu` in the sqlserver package, or rethink how to indicate regexp support
+	return s.db.Dialect() != "sqlserver"
+}
+
+func checkPostFilters(pc namer.PolicyCoords, postFilters []postRegexpFilter) bool {
+	for _, f := range postFilters {
+		switch f.col {
+		case PolicyTblNameCol:
+			if !f.re.MatchString(pc.Name) {
+				return false
+			}
+		case PolicyTblScopeCol:
+			if !f.re.MatchString(pc.Scope) {
+				return false
+			}
+		case PolicyTblVerCol:
+			if !f.re.MatchString(pc.Version) {
+				return false
+			}
+		}
+	}
+	return true
+}
+
 func (s *dbStorage) ListSchemaIDs(ctx context.Context) ([]string, error) {
 	res, err := s.db.Select(goqu.C(SchemaTblIDCol)).From(SchemaTbl).Executor().ScannerContext(ctx)
 	if err != nil {
diff --git a/internal/storage/db/internal/db_opts.go b/internal/storage/db/internal/db_opts.go
index 5533283a..5251106f 100644
--- a/internal/storage/db/internal/db_opts.go
+++ b/internal/storage/db/internal/db_opts.go
@@ -9,6 +9,7 @@ import (
 	"github.com/doug-martin/goqu/v9"
 
 	"github.com/cerbos/cerbos/internal/policy"
+	"github.com/cerbos/cerbos/internal/util"
 )
 
 // DBOpt defines database driver options.
@@ -22,6 +23,13 @@ type (
 type dbOpt struct {
 	upsertPolicy upsertPolicyFunc
 	upsertSchema upsertSchemaFunc
+	regexpCache  *util.RegexpCache
+}
+
+func newDbOpt() *dbOpt {
+	return &dbOpt{
+		regexpCache: util.NewRegexpCache(),
+	}
 }
 
 // WithUpsertSchema sets custom upsert schema function.
@@ -37,3 +45,12 @@ func WithUpsertPolicy(f upsertPolicyFunc) DBOpt {
 		opt.upsertPolicy = f
 	}
 }
+
+// WithRegexpCacheOverride overrides the default regexp cache for DB queries requiring compiled expressions.
+// This is only required for DB drivers that require access to the cache (e.g. The SQLite driver retrieves the compiled
+// expressions for the application-defined function).
+func WithRegexpCacheOverride(c *util.RegexpCache) DBOpt {
+	return func(opt *dbOpt) {
+		opt.regexpCache = c
+	}
+}
diff --git a/internal/storage/db/sqlite3/sqlite3.go b/internal/storage/db/sqlite3/sqlite3.go
index 78bfede2..32cd1303 100644
--- a/internal/storage/db/sqlite3/sqlite3.go
+++ b/internal/storage/db/sqlite3/sqlite3.go
@@ -6,6 +6,7 @@ package sqlite3
 import (
 	"context"
 	"database/sql"
+	"database/sql/driver"
 	"embed"
 	"errors"
 	"fmt"
@@ -18,14 +19,13 @@ import (
 	migratesqlite3 "github.com/golang-migrate/migrate/v4/database/sqlite3"
 	"github.com/golang-migrate/migrate/v4/source/iofs"
 	"go.uber.org/zap"
-
-	// import sqlite3 driver.
-	_ "modernc.org/sqlite"
+	gosqlite3 "modernc.org/sqlite"
 
 	"github.com/cerbos/cerbos/internal/config"
 	"github.com/cerbos/cerbos/internal/observability/logging"
 	"github.com/cerbos/cerbos/internal/storage"
 	"github.com/cerbos/cerbos/internal/storage/db/internal"
+	"github.com/cerbos/cerbos/internal/util"
 )
 
 const DriverName = "sqlite3"
@@ -41,7 +41,37 @@ var (
 	_ storage.MutableStore = (*Store)(nil)
 )
 
+const nRegexpFnArgs = 2
+
+var nameRegexpCache util.RegexpCache
+
 func init() {
+	nameRegexpCache = *util.NewRegexpCache()
+
+	gosqlite3.MustRegisterDeterministicScalarFunction("regexp", nRegexpFnArgs, func(ctx *gosqlite3.FunctionContext, args []driver.Value) (driver.Value, error) {
+		if args[0] == nil || args[1] == nil {
+			return nil, nil
+		}
+
+		re, ok := args[0].(string)
+		if !ok {
+			return nil, fmt.Errorf("arg[0] should be of type: string")
+		}
+
+		s, ok := args[1].(string)
+		if !ok {
+			return nil, fmt.Errorf("arg[1] should be of type: string")
+		}
+
+		r, err := nameRegexpCache.GetCompiledExpr(re)
+		if err != nil {
+			return nil, err
+		}
+
+		b := r.MatchString(s)
+		return b, nil
+	})
+
 	storage.RegisterDriver(DriverName, func(ctx context.Context, confW *config.Wrapper) (storage.Store, error) {
 		conf := new(Conf)
 		if err := confW.GetSection(conf); err != nil {
@@ -69,7 +99,7 @@ func NewStore(ctx context.Context, conf *Conf) (*Store, error) {
 		return nil, fmt.Errorf("failed to migrate schema: %w", err)
 	}
 
-	s, err := internal.NewDBStorage(ctx, goqu.New("sqlite3", db))
+	s, err := internal.NewDBStorage(ctx, goqu.New("sqlite3", db), internal.WithRegexpCacheOverride(&nameRegexpCache))
 	if err != nil {
 		return nil, err
 	}
diff --git a/internal/storage/disk/disk.go b/internal/storage/disk/disk.go
index ae386e70..69e348f4 100644
--- a/internal/storage/disk/disk.go
+++ b/internal/storage/disk/disk.go
@@ -99,7 +99,7 @@ func (s *Store) GetDependents(_ context.Context, ids ...namer.ModuleID) (map[nam
 	return s.idx.GetDependents(ids...)
 }
 
-func (s *Store) ListPolicyIDs(ctx context.Context, _ bool) ([]string, error) {
+func (s *Store) ListPolicyIDs(ctx context.Context, _ storage.ListPolicyIDsParams) ([]string, error) {
 	return s.idx.ListPolicyIDs(ctx)
 }
 
diff --git a/internal/storage/git/store.go b/internal/storage/git/store.go
index 40931498..3d08b66f 100644
--- a/internal/storage/git/store.go
+++ b/internal/storage/git/store.go
@@ -142,7 +142,7 @@ func (s *Store) GetDependents(_ context.Context, ids ...namer.ModuleID) (map[nam
 	return s.idx.GetDependents(ids...)
 }
 
-func (s *Store) ListPolicyIDs(ctx context.Context, _ bool) ([]string, error) {
+func (s *Store) ListPolicyIDs(ctx context.Context, _ storage.ListPolicyIDsParams) ([]string, error) {
 	return s.idx.ListPolicyIDs(ctx)
 }
 
diff --git a/internal/storage/overlay/store.go b/internal/storage/overlay/store.go
index 0c9004d8..c567a6ab 100644
--- a/internal/storage/overlay/store.go
+++ b/internal/storage/overlay/store.go
@@ -179,11 +179,11 @@ func (s *Store) Driver() string {
 	return DriverName
 }
 
-func (s *Store) ListPolicyIDs(ctx context.Context, includeDisabled bool) ([]string, error) {
+func (s *Store) ListPolicyIDs(ctx context.Context, params storage.ListPolicyIDsParams) ([]string, error) {
 	return withCircuitBreaker(
 		s,
-		func() ([]string, error) { return s.baseStore.ListPolicyIDs(ctx, includeDisabled) },
-		func() ([]string, error) { return s.fallbackStore.ListPolicyIDs(ctx, includeDisabled) },
+		func() ([]string, error) { return s.baseStore.ListPolicyIDs(ctx, params) },
+		func() ([]string, error) { return s.fallbackStore.ListPolicyIDs(ctx, params) },
 	)
 }
 
diff --git a/internal/storage/store.go b/internal/storage/store.go
index 8506f17c..aa06c5e9 100644
--- a/internal/storage/store.go
+++ b/internal/storage/store.go
@@ -104,12 +104,19 @@ func NewFromConf(ctx context.Context, confWrapper *config.Wrapper) (Store, error
 	return cons(ctx, confWrapper)
 }
 
+type ListPolicyIDsParams struct {
+	NameRegexp      string
+	ScopeRegexp     string
+	VersionRegexp   string
+	IncludeDisabled bool
+}
+
 // Store is the common interface implemented by storage backends.
 type Store interface {
 	// Driver is the name of the storage backend implementation.
 	Driver() string
 	// ListPolicyIDs returns the policy IDs in the store
-	ListPolicyIDs(context.Context, bool) ([]string, error)
+	ListPolicyIDs(context.Context, ListPolicyIDsParams) ([]string, error)
 	// ListSchemaIDs returns the schema ids in the store
 	ListSchemaIDs(context.Context) ([]string, error)
 	// LoadSchema loads the given schema from the store.
diff --git a/internal/svc/admin_svc.go b/internal/svc/admin_svc.go
index 1bb936d3..e54b1d69 100644
--- a/internal/svc/admin_svc.go
+++ b/internal/svc/admin_svc.go
@@ -121,7 +121,20 @@ func (cas *CerbosAdminService) ListPolicies(ctx context.Context, req *requestv1.
 		return nil, status.Error(codes.NotFound, "store is not configured")
 	}
 
-	policyIds, err := cas.store.ListPolicyIDs(context.Background(), req.IncludeDisabled)
+	// We've historically supported ListPolicies on non-mutable stores, but later introduced filters are not scalable.
+	// Therefore, if any of the filters in question are passed and the store is not mutable, we reject the request.
+	if _, ok := cas.store.(storage.MutableStore); !ok && (req.NameRegexp != "" || req.ScopeRegexp != "" || req.VersionRegexp != "") {
+		return nil, status.Error(codes.Unimplemented, "Store does not support filters")
+	}
+
+	filterParams := storage.ListPolicyIDsParams{
+		NameRegexp:      req.NameRegexp,
+		ScopeRegexp:     req.ScopeRegexp,
+		VersionRegexp:   req.VersionRegexp,
+		IncludeDisabled: req.IncludeDisabled,
+	}
+
+	policyIds, err := cas.store.ListPolicyIDs(context.Background(), filterParams)
 	if err != nil {
 		ctxzap.Extract(ctx).Error("Could not get policy ids", zap.Error(err))
 		return nil, status.Error(codes.Internal, "could not get policy ids")
diff --git a/internal/util/regexp.go b/internal/util/regexp.go
new file mode 100644
index 00000000..8b732791
--- /dev/null
+++ b/internal/util/regexp.go
@@ -0,0 +1,41 @@
+// Copyright 2021-2023 Zenauth Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+package util
+
+import (
+	"fmt"
+	"regexp"
+	"sync"
+)
+
+type RegexpCache struct {
+	cache map[string]*regexp.Regexp
+	mu    sync.RWMutex
+}
+
+func NewRegexpCache() *RegexpCache {
+	return &RegexpCache{
+		cache: make(map[string]*regexp.Regexp),
+	}
+}
+
+// GetCompiledExpr lazily compiles (and stores) regexp.
+func (c *RegexpCache) GetCompiledExpr(re string) (*regexp.Regexp, error) {
+	c.mu.RLock()
+	r, ok := c.cache[re]
+	c.mu.RUnlock()
+
+	if !ok {
+		var err error
+		if r, err = regexp.Compile(re); err != nil {
+			return nil, fmt.Errorf("failed to compile regexp: %s", re)
+		}
+
+		c.mu.Lock()
+		c.cache[re] = r
+		c.mu.Unlock()
+	}
+
+	return r, nil
+}
diff --git a/schema/jsonschema/cerbos/request/v1/ListPoliciesRequest.schema.json b/schema/jsonschema/cerbos/request/v1/ListPoliciesRequest.schema.json
index 7ae651eb..e88fd26b 100644
--- a/schema/jsonschema/cerbos/request/v1/ListPoliciesRequest.schema.json
+++ b/schema/jsonschema/cerbos/request/v1/ListPoliciesRequest.schema.json
@@ -6,6 +6,15 @@
   "properties": {
     "includeDisabled": {
       "type": "boolean"
+    },
+    "nameRegexp": {
+      "type": "string"
+    },
+    "scopeRegexp": {
+      "type": "string"
+    },
+    "versionRegexp": {
+      "type": "string"
     }
   }
 }
diff --git a/schema/openapiv2/cerbos/svc/v1/svc.swagger.json b/schema/openapiv2/cerbos/svc/v1/svc.swagger.json
index 4ba4a23e..c34aae14 100644
--- a/schema/openapiv2/cerbos/svc/v1/svc.swagger.json
+++ b/schema/openapiv2/cerbos/svc/v1/svc.swagger.json
@@ -144,6 +144,27 @@
             "in": "query",
             "required": false,
             "type": "boolean"
+          },
+          {
+            "name": "nameRegexp",
+            "description": "Filter policies by name with regexp",
+            "in": "query",
+            "required": false,
+            "type": "string"
+          },
+          {
+            "name": "scopeRegexp",
+            "description": "Filter policies by scope with regexp",
+            "in": "query",
+            "required": false,
+            "type": "string"
+          },
+          {
+            "name": "versionRegexp",
+            "description": "Filter policies by version with regexp",
+            "in": "query",
+            "required": false,
+            "type": "string"
           }
         ],
         "tags": [


Test patch:
diff --git a/client/admin_test.go b/client/admin_test.go
index 163a7e58..07f6a008 100644
--- a/client/admin_test.go
+++ b/client/admin_test.go
@@ -16,10 +16,12 @@ import (
 	"google.golang.org/grpc"
 
 	auditv1 "github.com/cerbos/cerbos/api/genpb/cerbos/audit/v1"
+	requestv1 "github.com/cerbos/cerbos/api/genpb/cerbos/request/v1"
 	responsev1 "github.com/cerbos/cerbos/api/genpb/cerbos/response/v1"
 	svcv1 "github.com/cerbos/cerbos/api/genpb/cerbos/svc/v1"
 	"github.com/cerbos/cerbos/client/testutil"
 	"github.com/cerbos/cerbos/internal/namer"
+	"github.com/cerbos/cerbos/internal/storage"
 	"github.com/cerbos/cerbos/internal/test"
 )
 
@@ -86,7 +88,9 @@ func TestAuditLogs(t *testing.T) {
 	})
 }
 
-func TestListPolicies(t *testing.T) {
+func setUpAdminClientAndPolicySet(t *testing.T) (AdminClient, *PolicySet) {
+	t.Helper()
+
 	const (
 		adminUsername = "cerbos"
 		adminPassword = "cerbosAdmin"
@@ -117,6 +121,12 @@ func TestListPolicies(t *testing.T) {
 	require.NoError(t, err)
 	require.NoError(t, ac.AddOrUpdatePolicy(context.Background(), ps))
 
+	return ac, ps
+}
+
+func TestListPolicies(t *testing.T) {
+	ac, ps := setUpAdminClientAndPolicySet(t)
+
 	t.Run("should get the list of policies", func(t *testing.T) {
 		have, err := ac.ListPolicies(context.Background(), WithIncludeDisabled())
 		require.NoError(t, err)
@@ -144,3 +154,56 @@ func TestListPolicies(t *testing.T) {
 		}
 	})
 }
+
+func TestFilterPolicies(t *testing.T) {
+	ac, ps := setUpAdminClientAndPolicySet(t)
+
+	testFilter := func(filterOpts ...ListPoliciesOption) {
+		t.Helper()
+
+		filterOpts = append(filterOpts, WithIncludeDisabled())
+
+		have, err := ac.ListPolicies(context.Background(), filterOpts...)
+		require.NoError(t, err)
+		require.NotEmpty(t, have)
+
+		// Bit of gymnastics to convert the client friendly filterOpts to backend-recognised params
+		r := &requestv1.ListPoliciesRequest{}
+		for _, opt := range filterOpts {
+			opt(r)
+		}
+		params := storage.ListPolicyIDsParams{
+			IncludeDisabled: r.IncludeDisabled,
+			NameRegexp:      r.NameRegexp,
+			ScopeRegexp:     r.ScopeRegexp,
+			VersionRegexp:   r.VersionRegexp,
+		}
+
+		policyList := test.FilterPolicies(t, ps.GetPolicies(), params)
+		want := make([]string, len(policyList))
+		for i, p := range policyList {
+			want[i] = namer.PolicyKey(p)
+		}
+		require.ElementsMatch(t, want, have)
+	}
+
+	t.Run("should get the list of filtered policies by name", func(t *testing.T) {
+		testFilter(WithNameRegexp(".*request$"))
+	})
+
+	t.Run("should get the list of filtered policies by scope", func(t *testing.T) {
+		testFilter(WithScopeRegexp("acme"))
+	})
+
+	t.Run("should get the list of filtered policies by version", func(t *testing.T) {
+		testFilter(WithVersionRegexp("20210210"))
+	})
+
+	t.Run("should get the list of filtered policies by all", func(t *testing.T) {
+		testFilter(
+			WithNameRegexp(".*(leave|equipment)_[rw]equest$"),
+			WithScopeRegexp("^acme"),
+			WithVersionRegexp("default$"),
+		)
+	})
+}
diff --git a/cmd/cerbos/repl/internal/repl_test.go b/cmd/cerbos/repl/internal/repl_test.go
index f9dcf8eb..16b2d117 100644
--- a/cmd/cerbos/repl/internal/repl_test.go
+++ b/cmd/cerbos/repl/internal/repl_test.go
@@ -273,6 +273,29 @@ func TestREPL(t *testing.T) {
 				},
 			},
 		},
+		{
+			name: "set_globals_variable",
+			directives: []DirectiveTest{
+				{
+					Directive: `:let G = {"foo":"bar"}`,
+					Check: func(t *testing.T, m *mockOutput) {
+						t.Helper()
+						require.Equal(t, "G", m.resultName)
+
+						want := map[string]any{"foo": "bar"}
+						require.Equal(t, want, m.resultVal.Value())
+					},
+				},
+				{
+					Directive: `globals.foo`,
+					Check: func(t *testing.T, m *mockOutput) {
+						t.Helper()
+						require.Equal(t, lastResultVar, m.resultName)
+						require.Equal(t, "bar", m.resultVal.Value())
+					},
+				},
+			},
+		},
 		{
 			name: "load_derived_roles",
 			directives: []DirectiveTest{
diff --git a/cmd/cerbosctl/put_test.go b/cmd/cerbosctl/put_test.go
index dfc34502..7b5af5f1 100644
--- a/cmd/cerbosctl/put_test.go
+++ b/cmd/cerbosctl/put_test.go
@@ -98,6 +98,7 @@ func testPutCmd(clientCtx *cmdclient.Context, globals *flagset.Globals) func(*te
 					"resource.album_object.vdefault",
 					"resource.equipment_request.vdefault",
 					"resource.equipment_request.vdefault/acme",
+					"resource.global.vdefault",
 					"resource.leave_request.v20210210",
 					"resource.leave_request.vdefault",
 					"resource.leave_request.vdefault/acme",
diff --git a/internal/compile/manager_test.go b/internal/compile/manager_test.go
index b0fba34a..21977bb1 100644
--- a/internal/compile/manager_test.go
+++ b/internal/compile/manager_test.go
@@ -288,7 +288,7 @@ func (ms *MockStore) Delete(ctx context.Context, ids ...namer.ModuleID) error {
 	return args.Error(0)
 }
 
-func (ms *MockStore) ListPolicyIDs(ctx context.Context, _ bool) ([]string, error) {
+func (ms *MockStore) ListPolicyIDs(ctx context.Context, _ storage.ListPolicyIDsParams) ([]string, error) {
 	args := ms.MethodCalled("ListPolicyIDs", ctx)
 	if res := args.Get(0); res == nil {
 		return nil, args.Error(0)
diff --git a/internal/conditions/cel_test.go b/internal/conditions/cel_test.go
index 70efd9d2..dc98fd49 100644
--- a/internal/conditions/cel_test.go
+++ b/internal/conditions/cel_test.go
@@ -44,6 +44,10 @@ func TestExpandAbbrev(t *testing.T) {
 			input: "V.is_admin",
 			want:  "variables.is_admin",
 		},
+		{
+			input: "G.environment",
+			want:  "globals.environment",
+		},
 		{
 			input: "request.principal.attr.department",
 			want:  "request.principal.attr.department",
diff --git a/internal/engine/engine_test.go b/internal/engine/engine_test.go
index 92193fd5..f584e656 100644
--- a/internal/engine/engine_test.go
+++ b/internal/engine/engine_test.go
@@ -196,13 +196,16 @@ func mkEngine(tb testing.TB, p param) (*Engine, context.CancelFunc) {
 		auditLog = audit.NewNopLog()
 	}
 
-	eng, err := New(ctx, Components{
+	engineConf := &Conf{}
+	engineConf.SetDefaults()
+	engineConf.Globals = map[string]any{"environment": "test"}
+
+	eng := NewFromConf(ctx, engineConf, Components{
 		PolicyLoader:      compiler,
 		SchemaMgr:         schemaMgr,
 		AuditLog:          auditLog,
 		MetadataExtractor: audit.NewMetadataExtractorFromConf(&audit.Conf{}),
 	})
-	require.NoError(tb, err)
 
 	return eng, cancelFunc
 }
diff --git a/internal/engine/planner/planner_test.go b/internal/engine/planner/planner_test.go
index bee37c1e..6a60dd52 100644
--- a/internal/engine/planner/planner_test.go
+++ b/internal/engine/planner/planner_test.go
@@ -160,7 +160,7 @@ func Test_evaluateCondition(t *testing.T) {
 	for _, tt := range tests {
 		t.Run(fmt.Sprintf("Expr:%q", tt.args.expr), func(t *testing.T) {
 			is := require.New(t)
-			got, err := evaluateCondition(tt.args.condition, tt.args.input, nil)
+			got, err := evaluateCondition(tt.args.condition, tt.args.input, nil, nil)
 			is.NoError(err)
 			expression := got.GetExpression()
 			is.Equal(tt.want, unparse(t, expression))
@@ -216,7 +216,7 @@ func Test_evaluateCondition(t *testing.T) {
 			got, err := evaluateCondition(c, &enginev1.PlanResourcesInput{
 				Principal: &enginev1.Principal{Attr: principalAttr},
 				Resource:  &enginev1.PlanResourcesInput_Resource{Attr: resourceAttr},
-			}, nil)
+			}, nil, nil)
 			is.NotNil(got)
 			is.NoError(err)
 			operation := got.GetLogicalOperation()
diff --git a/internal/storage/blob/cloner_test.go b/internal/storage/blob/cloner_test.go
index 43112a93..9b0d0537 100644
--- a/internal/storage/blob/cloner_test.go
+++ b/internal/storage/blob/cloner_test.go
@@ -49,5 +49,6 @@ func TestCloneResult(t *testing.T) {
 		"resource_policies/policy_06.yaml",
 		"resource_policies/policy_07.yaml",
 		"resource_policies/policy_07_acme.yaml",
+		"resource_policies/policy_08.yaml",
 	}, result.updateOrAdd)
 }
diff --git a/internal/storage/bundle/local_source_test.go b/internal/storage/bundle/local_source_test.go
index 477d388a..1a98452c 100644
--- a/internal/storage/bundle/local_source_test.go
+++ b/internal/storage/bundle/local_source_test.go
@@ -11,6 +11,7 @@ import (
 	"testing"
 
 	"github.com/cerbos/cerbos/internal/namer"
+	"github.com/cerbos/cerbos/internal/storage"
 	"github.com/cerbos/cerbos/internal/storage/bundle"
 	"github.com/cerbos/cerbos/internal/test"
 	bundlev1 "github.com/cerbos/cloud-api/genpb/cerbos/cloud/bundle/v1"
@@ -44,7 +45,7 @@ func TestLocalSource(t *testing.T) {
 func runTests(have *bundle.LocalSource, manifest *bundlev1.Manifest) func(*testing.T) {
 	return func(t *testing.T) {
 		t.Run("listPolicyIDs", func(t *testing.T) {
-			havePolicies, err := have.ListPolicyIDs(context.Background(), true)
+			havePolicies, err := have.ListPolicyIDs(context.Background(), storage.ListPolicyIDsParams{IncludeDisabled: true})
 			require.NoError(t, err)
 			require.Len(t, havePolicies, len(manifest.PolicyIndex))
 
diff --git a/internal/storage/bundle/remote_source_test.go b/internal/storage/bundle/remote_source_test.go
index 57766c0a..5ed1cf78 100644
--- a/internal/storage/bundle/remote_source_test.go
+++ b/internal/storage/bundle/remote_source_test.go
@@ -11,6 +11,7 @@ import (
 	"testing"
 	"time"
 
+	"github.com/cerbos/cerbos/internal/storage"
 	"github.com/cerbos/cerbos/internal/storage/bundle"
 	"github.com/cerbos/cerbos/internal/test"
 	"github.com/cerbos/cerbos/internal/test/mocks"
@@ -33,7 +34,7 @@ func TestRemoteSource(t *testing.T) {
 			require.NoError(t, err, "Failed to create remote source")
 			require.NoError(t, rs.InitWithClient(context.Background(), mockClient), "Failed to init")
 
-			ids, err := rs.ListPolicyIDs(context.Background(), true)
+			ids, err := rs.ListPolicyIDs(context.Background(), storage.ListPolicyIDsParams{IncludeDisabled: true})
 			require.NoError(t, err, "Failed to call ListPolicyIDs")
 			require.True(t, len(ids) > 0, "Policy IDs are empty")
 		})
@@ -47,7 +48,7 @@ func TestRemoteSource(t *testing.T) {
 			require.NoError(t, err, "Failed to create remote source")
 			require.NoError(t, rs.InitWithClient(context.Background(), mockClient), "Failed to init")
 
-			ids, err := rs.ListPolicyIDs(context.Background(), true)
+			ids, err := rs.ListPolicyIDs(context.Background(), storage.ListPolicyIDsParams{IncludeDisabled: true})
 			require.NoError(t, err, "Failed to call ListPolicyIDs")
 			require.True(t, len(ids) > 0, "Policy IDs are empty")
 		})
@@ -63,7 +64,7 @@ func TestRemoteSource(t *testing.T) {
 
 			require.False(t, rs.IsHealthy(), "Source should be unhealthy")
 
-			_, err = rs.ListPolicyIDs(context.Background(), true)
+			_, err = rs.ListPolicyIDs(context.Background(), storage.ListPolicyIDsParams{IncludeDisabled: true})
 			require.Error(t, err, "Expected error from ListPolicyIDs")
 			require.ErrorIs(t, err, bundle.ErrBundleNotLoaded, "Exepcted bundle not loaded error")
 		})
@@ -78,7 +79,7 @@ func TestRemoteSource(t *testing.T) {
 
 			require.NoError(t, rs.Reload(context.Background()), "Failed to reload")
 
-			ids, err := rs.ListPolicyIDs(context.Background(), true)
+			ids, err := rs.ListPolicyIDs(context.Background(), storage.ListPolicyIDsParams{IncludeDisabled: true})
 			require.NoError(t, err, "Failed to call ListPolicyIDs")
 			require.True(t, len(ids) > 0, "Policy IDs are empty")
 		})
@@ -94,7 +95,7 @@ func TestRemoteSource(t *testing.T) {
 		require.NoError(t, err, "Failed to create remote source")
 		require.NoError(t, rs.InitWithClient(context.Background(), mockClient), "Failed to init")
 
-		ids, err := rs.ListPolicyIDs(context.Background(), true)
+		ids, err := rs.ListPolicyIDs(context.Background(), storage.ListPolicyIDsParams{IncludeDisabled: true})
 		require.NoError(t, err, "Failed to call ListPolicyIDs")
 		require.True(t, len(ids) > 0, "Policy IDs are empty")
 	})
@@ -125,7 +126,7 @@ func TestRemoteSource(t *testing.T) {
 				return rs.IsHealthy() == false
 			}, 60*time.Millisecond, 10*time.Millisecond, "Source should be unhealthy")
 
-			_, err = rs.ListPolicyIDs(context.Background(), true)
+			_, err = rs.ListPolicyIDs(context.Background(), storage.ListPolicyIDsParams{IncludeDisabled: true})
 			require.Error(t, err, "Expected error from ListPolicyIDs")
 			require.ErrorIs(t, err, bundle.ErrBundleNotLoaded, "Exepcted bundle not loaded error")
 		})
@@ -166,7 +167,7 @@ func TestRemoteSource(t *testing.T) {
 			}
 			waitForCallsDone(t, callsDone)
 
-			ids, err := rs.ListPolicyIDs(context.Background(), true)
+			ids, err := rs.ListPolicyIDs(context.Background(), storage.ListPolicyIDsParams{IncludeDisabled: true})
 			require.ErrorIs(t, err, bundle.ErrBundleNotLoaded, "Failed to remove the bundle")
 			require.Len(t, ids, 0, "Policy IDs must be empty")
 		})
@@ -206,7 +207,7 @@ func TestRemoteSource(t *testing.T) {
 			}
 			waitForCallsDone(t, callsDone)
 
-			ids, err := rs.ListPolicyIDs(context.Background(), true)
+			ids, err := rs.ListPolicyIDs(context.Background(), storage.ListPolicyIDsParams{IncludeDisabled: true})
 			require.NoError(t, err, "Failed to call ListPolicyIDs")
 			require.True(t, len(ids) > 0, "Policy IDs are empty")
 		})
@@ -251,7 +252,7 @@ func TestRemoteSource(t *testing.T) {
 			}
 			waitForCallsDone(t, callsDone)
 
-			ids, err := rs.ListPolicyIDs(context.Background(), true)
+			ids, err := rs.ListPolicyIDs(context.Background(), storage.ListPolicyIDsParams{IncludeDisabled: true})
 			require.NoError(t, err, "Failed to call ListPolicyIDs")
 			require.True(t, len(ids) > 0, "Policy IDs are empty")
 		})
@@ -286,7 +287,7 @@ func TestRemoteSource(t *testing.T) {
 
 			require.False(t, rs.IsHealthy(), "Source should be unhealthy")
 
-			ids, err := rs.ListPolicyIDs(context.Background(), true)
+			ids, err := rs.ListPolicyIDs(context.Background(), storage.ListPolicyIDsParams{IncludeDisabled: true})
 			require.NoError(t, err, "Failed to call ListPolicyIDs")
 			require.True(t, len(ids) > 0, "Policy IDs are empty")
 		})
diff --git a/internal/storage/db/internal/tests.go b/internal/storage/db/internal/tests.go
index 4ae2b2f7..7ac970eb 100644
--- a/internal/storage/db/internal/tests.go
+++ b/internal/storage/db/internal/tests.go
@@ -221,7 +221,7 @@ func TestSuite(store DBStorage) func(*testing.T) {
 
 		t.Run("list_policies", func(t *testing.T) {
 			t.Run("should be able to list policies", func(t *testing.T) {
-				have, err := store.ListPolicyIDs(ctx, false)
+				have, err := store.ListPolicyIDs(ctx, storage.ListPolicyIDsParams{})
 				require.NoError(t, err)
 				require.Len(t, have, len(policyList))
 
@@ -234,6 +234,62 @@ func TestSuite(store DBStorage) func(*testing.T) {
 			})
 		})
 
+		t.Run("filter_policies", func(t *testing.T) {
+			testCases := []struct {
+				name   string
+				params storage.ListPolicyIDsParams
+			}{
+				{
+					name: "name regexp",
+					params: storage.ListPolicyIDsParams{
+						IncludeDisabled: true,
+						NameRegexp:      ".*(leave|equipment)_[rw]equest$",
+					},
+				},
+				{
+					name: "scope regexp",
+					params: storage.ListPolicyIDsParams{
+						IncludeDisabled: true,
+						ScopeRegexp:     "^acme",
+					},
+				},
+				{
+					name: "version regexp",
+					params: storage.ListPolicyIDsParams{
+						IncludeDisabled: true,
+						VersionRegexp:   "default$",
+					},
+				},
+				{
+					name: "all regexp",
+					params: storage.ListPolicyIDsParams{
+						IncludeDisabled: true,
+						NameRegexp:      ".*(leave|equipment)_[rw]equest$",
+						ScopeRegexp:     "^acme",
+						VersionRegexp:   "default$",
+					},
+				},
+			}
+
+			for _, tc := range testCases {
+				tc := tc
+				t.Run("should be able to filter policies "+tc.name, func(t *testing.T) {
+					have, err := store.ListPolicyIDs(ctx, tc.params)
+					require.NoError(t, err)
+					filteredPolicyList := test.FilterPolicies(t, policyList, tc.params)
+					require.Greater(t, len(filteredPolicyList), 0)
+					require.Len(t, have, len(filteredPolicyList))
+
+					want := make([]string, len(filteredPolicyList))
+					for i, p := range filteredPolicyList {
+						want[i] = namer.PolicyKeyFromFQN(p.FQN)
+					}
+
+					require.ElementsMatch(t, want, have)
+				})
+			}
+		})
+
 		t.Run("delete", func(t *testing.T) {
 			checkEvents := storage.TestSubscription(store)
 
diff --git a/internal/storage/index/builder_test.go b/internal/storage/index/builder_test.go
index 9e2e033a..1011c436 100644
--- a/internal/storage/index/builder_test.go
+++ b/internal/storage/index/builder_test.go
@@ -44,7 +44,7 @@ func TestBuildIndexWithDisk(t *testing.T) {
 
 	t.Run("check_contents", func(t *testing.T) {
 		data := idxImpl.Inspect()
-		require.Len(t, data, 21)
+		require.Len(t, data, 22)
 
 		rp1 := filepath.Join("resource_policies", "policy_01.yaml")
 		rp2 := filepath.Join("resource_policies", "policy_02.yaml")
diff --git a/internal/storage/internal/tests.go b/internal/storage/internal/tests.go
index e2fad2e6..687f67fd 100644
--- a/internal/storage/internal/tests.go
+++ b/internal/storage/internal/tests.go
@@ -34,21 +34,21 @@ func TestSuiteReloadable(store storage.Store, initFn, addFn, deleteFn MutateStor
 			require.NoError(t, err)
 		}
 
-		policies, err := store.ListPolicyIDs(context.Background(), false)
+		policies, err := store.ListPolicyIDs(context.Background(), storage.ListPolicyIDsParams{})
 		require.NoError(t, err)
 		require.Len(t, policies, expectedLen)
 
 		err = addFn()
 		require.NoError(t, err)
 
-		policies, err = store.ListPolicyIDs(context.Background(), false)
+		policies, err = store.ListPolicyIDs(context.Background(), storage.ListPolicyIDsParams{})
 		require.NoError(t, err)
 		require.Len(t, policies, expectedLen)
 
 		err = r.Reload(context.Background())
 		require.NoError(t, err)
 
-		policies, err = store.ListPolicyIDs(context.Background(), false)
+		policies, err = store.ListPolicyIDs(context.Background(), storage.ListPolicyIDsParams{})
 		require.NoError(t, err)
 		require.Greater(t, len(policies), expectedLen)
 
@@ -58,7 +58,7 @@ func TestSuiteReloadable(store storage.Store, initFn, addFn, deleteFn MutateStor
 		err = r.Reload(context.Background())
 		require.NoError(t, err)
 
-		policies, err = store.ListPolicyIDs(context.Background(), false)
+		policies, err = store.ListPolicyIDs(context.Background(), storage.ListPolicyIDsParams{})
 		require.NoError(t, err)
 		require.Len(t, policies, expectedLen)
 	}
diff --git a/internal/storage/overlay/store_test.go b/internal/storage/overlay/store_test.go
index 3670c8e1..d1795381 100644
--- a/internal/storage/overlay/store_test.go
+++ b/internal/storage/overlay/store_test.go
@@ -253,7 +253,7 @@ func (ms *MockStore) Driver() string {
 	return args.String(0)
 }
 
-func (ms *MockStore) ListPolicyIDs(ctx context.Context, _ bool) ([]string, error) {
+func (ms *MockStore) ListPolicyIDs(ctx context.Context, _ storage.ListPolicyIDsParams) ([]string, error) {
 	args := ms.Called(ctx)
 	if res := args.Get(0); res == nil {
 		return nil, args.Error(0)
diff --git a/internal/test/mocks/Store.go b/internal/test/mocks/Store.go
index f6272c39..f62fc5ed 100644
--- a/internal/test/mocks/Store.go
+++ b/internal/test/mocks/Store.go
@@ -10,6 +10,8 @@ import (
 	io "io"
 
 	mock "github.com/stretchr/testify/mock"
+
+	storage "github.com/cerbos/cerbos/internal/storage"
 )
 
 // Store is an autogenerated mock type for the Store type
@@ -67,15 +69,15 @@ func (_c *Store_Driver_Call) RunAndReturn(run func() string) *Store_Driver_Call
 }
 
 // ListPolicyIDs provides a mock function with given fields: _a0, _a1
-func (_m *Store) ListPolicyIDs(_a0 context.Context, _a1 bool) ([]string, error) {
+func (_m *Store) ListPolicyIDs(_a0 context.Context, _a1 storage.ListPolicyIDsParams) ([]string, error) {
 	ret := _m.Called(_a0, _a1)
 
 	var r0 []string
 	var r1 error
-	if rf, ok := ret.Get(0).(func(context.Context, bool) ([]string, error)); ok {
+	if rf, ok := ret.Get(0).(func(context.Context, storage.ListPolicyIDsParams) ([]string, error)); ok {
 		return rf(_a0, _a1)
 	}
-	if rf, ok := ret.Get(0).(func(context.Context, bool) []string); ok {
+	if rf, ok := ret.Get(0).(func(context.Context, storage.ListPolicyIDsParams) []string); ok {
 		r0 = rf(_a0, _a1)
 	} else {
 		if ret.Get(0) != nil {
@@ -83,7 +85,7 @@ func (_m *Store) ListPolicyIDs(_a0 context.Context, _a1 bool) ([]string, error)
 		}
 	}
 
-	if rf, ok := ret.Get(1).(func(context.Context, bool) error); ok {
+	if rf, ok := ret.Get(1).(func(context.Context, storage.ListPolicyIDsParams) error); ok {
 		r1 = rf(_a0, _a1)
 	} else {
 		r1 = ret.Error(1)
@@ -99,14 +101,14 @@ type Store_ListPolicyIDs_Call struct {
 
 // ListPolicyIDs is a helper method to define mock.On call
 //   - _a0 context.Context
-//   - _a1 bool
+//   - _a1 storage.ListPolicyIDsParams
 func (_e *Store_Expecter) ListPolicyIDs(_a0 interface{}, _a1 interface{}) *Store_ListPolicyIDs_Call {
 	return &Store_ListPolicyIDs_Call{Call: _e.mock.On("ListPolicyIDs", _a0, _a1)}
 }
 
-func (_c *Store_ListPolicyIDs_Call) Run(run func(_a0 context.Context, _a1 bool)) *Store_ListPolicyIDs_Call {
+func (_c *Store_ListPolicyIDs_Call) Run(run func(_a0 context.Context, _a1 storage.ListPolicyIDsParams)) *Store_ListPolicyIDs_Call {
 	_c.Call.Run(func(args mock.Arguments) {
-		run(args[0].(context.Context), args[1].(bool))
+		run(args[0].(context.Context), args[1].(storage.ListPolicyIDsParams))
 	})
 	return _c
 }
@@ -116,7 +118,7 @@ func (_c *Store_ListPolicyIDs_Call) Return(_a0 []string, _a1 error) *Store_ListP
 	return _c
 }
 
-func (_c *Store_ListPolicyIDs_Call) RunAndReturn(run func(context.Context, bool) ([]string, error)) *Store_ListPolicyIDs_Call {
+func (_c *Store_ListPolicyIDs_Call) RunAndReturn(run func(context.Context, storage.ListPolicyIDsParams) ([]string, error)) *Store_ListPolicyIDs_Call {
 	_c.Call.Return(run)
 	return _c
 }
diff --git a/internal/test/test.go b/internal/test/test.go
index 80892f07..797ffccd 100644
--- a/internal/test/test.go
+++ b/internal/test/test.go
@@ -251,3 +251,48 @@ func FindPolicyFiles(t *testing.T, dir string, callback func(string) error) erro
 		return callback(filepath.Join(base, path))
 	})
 }
+
+func FilterPolicies[P *policyv1.Policy | policy.Wrapper](t *testing.T, policies []P, params storage.ListPolicyIDsParams) []P {
+	t.Helper()
+
+	filtered := []P{}
+
+	c := util.NewRegexpCache()
+	for _, p := range policies {
+		var wrapped policy.Wrapper
+		switch a := any(p).(type) {
+		case *policyv1.Policy:
+			wrapped = policy.Wrap(a)
+		case policy.Wrapper:
+			wrapped = a
+		}
+
+		if params.NameRegexp != "" {
+			r, err := c.GetCompiledExpr(params.NameRegexp)
+			require.NoError(t, err)
+			if !r.MatchString(wrapped.Name) {
+				continue
+			}
+		}
+
+		if params.ScopeRegexp != "" {
+			r, err := c.GetCompiledExpr(params.ScopeRegexp)
+			require.NoError(t, err)
+			if !r.MatchString(wrapped.Scope) {
+				continue
+			}
+		}
+
+		if params.VersionRegexp != "" {
+			r, err := c.GetCompiledExpr(params.VersionRegexp)
+			require.NoError(t, err)
+			if !r.MatchString(wrapped.Version) {
+				continue
+			}
+		}
+
+		filtered = append(filtered, p)
+	}
+
+	return filtered
+}
diff --git a/internal/test/testdata/engine/case_18.yaml b/internal/test/testdata/engine/case_18.yaml
new file mode 100644
index 00000000..66d2edf7
--- /dev/null
+++ b/internal/test/testdata/engine/case_18.yaml
@@ -0,0 +1,30 @@
+---
+description: Globals
+inputs: [
+  {
+    "requestId": "test",
+    "actions": ["test"],
+    "principal": {
+      "id": "andy",
+      "roles": [
+        "employee"
+      ]
+    },
+    "resource": {
+      "kind": "global",
+      "id": "test"
+    }
+  }
+]
+wantOutputs: [
+  {
+    "requestId": "test",
+    "resourceId": "test",
+    "actions": {
+      "test": {
+        "effect": "EFFECT_ALLOW",
+        "policy": "resource.global.vdefault"
+      }
+    }
+  }
+]
diff --git a/internal/test/testdata/query_planner/policies/resource_policy_basics.yaml b/internal/test/testdata/query_planner/policies/resource_policy_basics.yaml
index c2e7a692..f43134c3 100644
--- a/internal/test/testdata/query_planner/policies/resource_policy_basics.yaml
+++ b/internal/test/testdata/query_planner/policies/resource_policy_basics.yaml
@@ -21,4 +21,13 @@ resourcePolicy:
       condition:
         match:
           expr: |-
-            P.attr.userQid == request.resource.id
\ No newline at end of file
+            P.attr.userQid == request.resource.id
+    - actions:
+        - reference_globals
+      effect: EFFECT_ALLOW
+      roles:
+        - USER
+      condition:
+        match:
+          expr: |-
+            R.attr.environment == G.environment
diff --git a/internal/test/testdata/query_planner/suite/basics.yaml b/internal/test/testdata/query_planner/suite/basics.yaml
index badc3824..38eeb474 100644
--- a/internal/test/testdata/query_planner/suite/basics.yaml
+++ b/internal/test/testdata/query_planner/suite/basics.yaml
@@ -32,3 +32,15 @@ tests:
             operands:
               - value: z0
               - variable: request.resource.id
+    - action: reference_globals
+      resource:
+        kind: x
+        policyVersion: default
+      want:
+        kind: KIND_CONDITIONAL
+        condition:
+          expression:
+            operator: eq
+            operands:
+              - variable: request.resource.attr.environment
+              - value: test
diff --git a/internal/test/testdata/store/resource_policies/policy_08.yaml b/internal/test/testdata/store/resource_policies/policy_08.yaml
new file mode 100644
index 00000000..95f0360f
--- /dev/null
+++ b/internal/test/testdata/store/resource_policies/policy_08.yaml
@@ -0,0 +1,15 @@
+# yaml-language-server: $schema=https://api.cerbos.dev/latest/cerbos/policy/v1/Policy.schema.json
+---
+apiVersion: api.cerbos.dev/v1
+resourcePolicy:
+  version: default
+  resource: global
+  rules:
+    - actions:
+        - test
+      effect: EFFECT_ALLOW
+      roles:
+        - employee
+      condition:
+        match:
+          expr: globals.environment == "test"
diff --git a/internal/util/regexp_test.go b/internal/util/regexp_test.go
new file mode 100644
index 00000000..386e9a4d
--- /dev/null
+++ b/internal/util/regexp_test.go
@@ -0,0 +1,45 @@
+// Copyright 2021-2023 Zenauth Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+package util_test
+
+import (
+	"testing"
+
+	"github.com/cerbos/cerbos/internal/util"
+	"github.com/stretchr/testify/require"
+)
+
+func TestGetCompiledExpr(t *testing.T) {
+	c := util.NewRegexpCache()
+
+	t.Run("Valid expression", func(t *testing.T) {
+		expr := "^test$"
+
+		re, err := c.GetCompiledExpr(expr)
+
+		require.NoError(t, err, "Failed to get compiled expression from cache")
+		require.NotNil(t, re, "Expected regular expression to be non-nil")
+		require.Equal(t, expr, re.String(), "Expected regular expression to match input")
+	})
+
+	t.Run("Invalid expression", func(t *testing.T) {
+		expr := "\\"
+
+		_, err := c.GetCompiledExpr(expr)
+
+		require.Error(t, err, "Expected error")
+	})
+
+	t.Run("Cached expression", func(t *testing.T) {
+		expr := "^test$"
+
+		re1, err := c.GetCompiledExpr(expr)
+		require.NoError(t, err, "Failed to get compiled expression from cache")
+
+		re2, err := c.GetCompiledExpr(expr)
+		require.NoError(t, err, "Failed to get compiled expression from cache")
+
+		require.Same(t, re1, re2, "Expected to get the same regular expression object from the cache")
+	})
+}