Repository: cerbos/cerbos
Pull request #799 (2022-03-29 14:16:20)

feat: REPL for conditions
Introduces a REPL to help experimenting with CEL conditions in an interactive environment.

It is shipped as a sub command of the Cerbos binary (`cerbos repl`) to help with rapid prototyping.


![repl](https://user-images.githubusercontent.com/1127094/160630977-61230ffd-1efb-4e59-9e7d-a3f9df131458.gif)



Patch:
diff --git a/api/genpb/cerbos/request/v1/hashpb_helpers.pb.go b/api/genpb/cerbos/request/v1/hashpb_helpers.pb.go
index 1531016a..df7fc34b 100644
--- a/api/genpb/cerbos/request/v1/hashpb_helpers.pb.go
+++ b/api/genpb/cerbos/request/v1/hashpb_helpers.pb.go
@@ -861,6 +861,13 @@ func cerbos_request_v1_PlaygroundValidateRequest_hashpb_sum(m *PlaygroundValidat
 	}
 }
 
+func cerbos_request_v1_ReloadStoreRequest_hashpb_sum(m *ReloadStoreRequest, hasher hash.Hash, ignore map[string]struct{}) {
+	if _, ok := ignore["cerbos.request.v1.ReloadStoreRequest.wait"]; !ok {
+		_, _ = hasher.Write(protowire.AppendVarint(nil, protowire.EncodeBool(m.Wait)))
+
+	}
+}
+
 func cerbos_request_v1_ResourceSet_hashpb_sum(m *ResourceSet, hasher hash.Hash, ignore map[string]struct{}) {
 	if _, ok := ignore["cerbos.request.v1.ResourceSet.kind"]; !ok {
 		_, _ = hasher.Write(protowire.AppendString(nil, m.Kind))
diff --git a/api/genpb/cerbos/request/v1/request.pb.go b/api/genpb/cerbos/request/v1/request.pb.go
index 3be0f4cb..3b626a63 100644
--- a/api/genpb/cerbos/request/v1/request.pb.go
+++ b/api/genpb/cerbos/request/v1/request.pb.go
@@ -1318,6 +1318,53 @@ func (x *DeleteSchemaRequest) GetId() []string {
 	return nil
 }
 
+type ReloadStoreRequest struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Wait bool `protobuf:"varint,1,opt,name=wait,proto3" json:"wait,omitempty"`
+}
+
+func (x *ReloadStoreRequest) Reset() {
+	*x = ReloadStoreRequest{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_cerbos_request_v1_request_proto_msgTypes[20]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *ReloadStoreRequest) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ReloadStoreRequest) ProtoMessage() {}
+
+func (x *ReloadStoreRequest) ProtoReflect() protoreflect.Message {
+	mi := &file_cerbos_request_v1_request_proto_msgTypes[20]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use ReloadStoreRequest.ProtoReflect.Descriptor instead.
+func (*ReloadStoreRequest) Descriptor() ([]byte, []int) {
+	return file_cerbos_request_v1_request_proto_rawDescGZIP(), []int{20}
+}
+
+func (x *ReloadStoreRequest) GetWait() bool {
+	if x != nil {
+		return x.Wait
+	}
+	return false
+}
+
 type CheckResourceBatchRequest_BatchEntry struct {
 	state         protoimpl.MessageState
 	sizeCache     protoimpl.SizeCache
@@ -1330,7 +1377,7 @@ type CheckResourceBatchRequest_BatchEntry struct {
 func (x *CheckResourceBatchRequest_BatchEntry) Reset() {
 	*x = CheckResourceBatchRequest_BatchEntry{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_cerbos_request_v1_request_proto_msgTypes[22]
+		mi := &file_cerbos_request_v1_request_proto_msgTypes[23]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -1343,7 +1390,7 @@ func (x *CheckResourceBatchRequest_BatchEntry) String() string {
 func (*CheckResourceBatchRequest_BatchEntry) ProtoMessage() {}
 
 func (x *CheckResourceBatchRequest_BatchEntry) ProtoReflect() protoreflect.Message {
-	mi := &file_cerbos_request_v1_request_proto_msgTypes[22]
+	mi := &file_cerbos_request_v1_request_proto_msgTypes[23]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -1385,7 +1432,7 @@ type AuxData_JWT struct {
 func (x *AuxData_JWT) Reset() {
 	*x = AuxData_JWT{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_cerbos_request_v1_request_proto_msgTypes[23]
+		mi := &file_cerbos_request_v1_request_proto_msgTypes[24]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -1398,7 +1445,7 @@ func (x *AuxData_JWT) String() string {
 func (*AuxData_JWT) ProtoMessage() {}
 
 func (x *AuxData_JWT) ProtoReflect() protoreflect.Message {
-	mi := &file_cerbos_request_v1_request_proto_msgTypes[23]
+	mi := &file_cerbos_request_v1_request_proto_msgTypes[24]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -1440,7 +1487,7 @@ type ListAuditLogEntriesRequest_TimeRange struct {
 func (x *ListAuditLogEntriesRequest_TimeRange) Reset() {
 	*x = ListAuditLogEntriesRequest_TimeRange{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_cerbos_request_v1_request_proto_msgTypes[24]
+		mi := &file_cerbos_request_v1_request_proto_msgTypes[25]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -1453,7 +1500,7 @@ func (x *ListAuditLogEntriesRequest_TimeRange) String() string {
 func (*ListAuditLogEntriesRequest_TimeRange) ProtoMessage() {}
 
 func (x *ListAuditLogEntriesRequest_TimeRange) ProtoReflect() protoreflect.Message {
-	mi := &file_cerbos_request_v1_request_proto_msgTypes[24]
+	mi := &file_cerbos_request_v1_request_proto_msgTypes[25]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -2009,15 +2056,23 @@ var file_cerbos_request_v1_request_proto_rawDesc = []byte{
 	0x22, 0xe2, 0x41, 0x01, 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, 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,
+	0x68, 0x65, 0x6d, 0x61, 0x28, 0x73, 0x29, 0x20, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22,
+	0x7a, 0x0a, 0x12, 0x52, 0x65, 0x6c, 0x6f, 0x61, 0x64, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x52, 0x65,
+	0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x47, 0x0a, 0x04, 0x77, 0x61, 0x69, 0x74, 0x18, 0x01, 0x20,
+	0x01, 0x28, 0x08, 0x42, 0x33, 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, 0xe2, 0x41, 0x01, 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 (
@@ -2033,7 +2088,7 @@ func file_cerbos_request_v1_request_proto_rawDescGZIP() []byte {
 }
 
 var file_cerbos_request_v1_request_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
-var file_cerbos_request_v1_request_proto_msgTypes = make([]protoimpl.MessageInfo, 25)
+var file_cerbos_request_v1_request_proto_msgTypes = make([]protoimpl.MessageInfo, 26)
 var file_cerbos_request_v1_request_proto_goTypes = []interface{}{
 	(ListAuditLogEntriesRequest_Kind)(0),          // 0: cerbos.request.v1.ListAuditLogEntriesRequest.Kind
 	(*ResourcesQueryPlanRequest)(nil),             // 1: cerbos.request.v1.ResourcesQueryPlanRequest
@@ -2056,53 +2111,54 @@ var file_cerbos_request_v1_request_proto_goTypes = []interface{}{
 	(*ListSchemasRequest)(nil),                    // 18: cerbos.request.v1.ListSchemasRequest
 	(*GetSchemaRequest)(nil),                      // 19: cerbos.request.v1.GetSchemaRequest
 	(*DeleteSchemaRequest)(nil),                   // 20: cerbos.request.v1.DeleteSchemaRequest
-	nil,                                           // 21: cerbos.request.v1.ResourceSet.InstancesEntry
-	nil,                                           // 22: cerbos.request.v1.AttributesMap.AttrEntry
-	(*CheckResourceBatchRequest_BatchEntry)(nil),  // 23: cerbos.request.v1.CheckResourceBatchRequest.BatchEntry
-	(*AuxData_JWT)(nil),                           // 24: cerbos.request.v1.AuxData.JWT
-	(*ListAuditLogEntriesRequest_TimeRange)(nil),  // 25: cerbos.request.v1.ListAuditLogEntriesRequest.TimeRange
-	(*v1.Principal)(nil),                          // 26: cerbos.engine.v1.Principal
-	(*v1.ResourcesQueryPlanRequest_Resource)(nil), // 27: cerbos.engine.v1.ResourcesQueryPlanRequest.Resource
-	(*v1.Resource)(nil),                           // 28: cerbos.engine.v1.Resource
-	(*v11.Policy)(nil),                            // 29: cerbos.policy.v1.Policy
-	(*durationpb.Duration)(nil),                   // 30: google.protobuf.Duration
-	(*v12.Schema)(nil),                            // 31: cerbos.schema.v1.Schema
-	(*structpb.Value)(nil),                        // 32: google.protobuf.Value
-	(*timestamppb.Timestamp)(nil),                 // 33: google.protobuf.Timestamp
+	(*ReloadStoreRequest)(nil),                    // 21: cerbos.request.v1.ReloadStoreRequest
+	nil,                                           // 22: cerbos.request.v1.ResourceSet.InstancesEntry
+	nil,                                           // 23: cerbos.request.v1.AttributesMap.AttrEntry
+	(*CheckResourceBatchRequest_BatchEntry)(nil),  // 24: cerbos.request.v1.CheckResourceBatchRequest.BatchEntry
+	(*AuxData_JWT)(nil),                           // 25: cerbos.request.v1.AuxData.JWT
+	(*ListAuditLogEntriesRequest_TimeRange)(nil),  // 26: cerbos.request.v1.ListAuditLogEntriesRequest.TimeRange
+	(*v1.Principal)(nil),                          // 27: cerbos.engine.v1.Principal
+	(*v1.ResourcesQueryPlanRequest_Resource)(nil), // 28: cerbos.engine.v1.ResourcesQueryPlanRequest.Resource
+	(*v1.Resource)(nil),                           // 29: cerbos.engine.v1.Resource
+	(*v11.Policy)(nil),                            // 30: cerbos.policy.v1.Policy
+	(*durationpb.Duration)(nil),                   // 31: google.protobuf.Duration
+	(*v12.Schema)(nil),                            // 32: cerbos.schema.v1.Schema
+	(*structpb.Value)(nil),                        // 33: google.protobuf.Value
+	(*timestamppb.Timestamp)(nil),                 // 34: google.protobuf.Timestamp
 }
 var file_cerbos_request_v1_request_proto_depIdxs = []int32{
-	26, // 0: cerbos.request.v1.ResourcesQueryPlanRequest.principal:type_name -> cerbos.engine.v1.Principal
-	27, // 1: cerbos.request.v1.ResourcesQueryPlanRequest.resource:type_name -> cerbos.engine.v1.ResourcesQueryPlanRequest.Resource
+	27, // 0: cerbos.request.v1.ResourcesQueryPlanRequest.principal:type_name -> cerbos.engine.v1.Principal
+	28, // 1: cerbos.request.v1.ResourcesQueryPlanRequest.resource:type_name -> cerbos.engine.v1.ResourcesQueryPlanRequest.Resource
 	6,  // 2: cerbos.request.v1.ResourcesQueryPlanRequest.aux_data:type_name -> cerbos.request.v1.AuxData
-	26, // 3: cerbos.request.v1.CheckResourceSetRequest.principal:type_name -> cerbos.engine.v1.Principal
+	27, // 3: cerbos.request.v1.CheckResourceSetRequest.principal:type_name -> cerbos.engine.v1.Principal
 	3,  // 4: cerbos.request.v1.CheckResourceSetRequest.resource:type_name -> cerbos.request.v1.ResourceSet
 	6,  // 5: cerbos.request.v1.CheckResourceSetRequest.aux_data:type_name -> cerbos.request.v1.AuxData
-	21, // 6: cerbos.request.v1.ResourceSet.instances:type_name -> cerbos.request.v1.ResourceSet.InstancesEntry
-	22, // 7: cerbos.request.v1.AttributesMap.attr:type_name -> cerbos.request.v1.AttributesMap.AttrEntry
-	26, // 8: cerbos.request.v1.CheckResourceBatchRequest.principal:type_name -> cerbos.engine.v1.Principal
-	23, // 9: cerbos.request.v1.CheckResourceBatchRequest.resources:type_name -> cerbos.request.v1.CheckResourceBatchRequest.BatchEntry
+	22, // 6: cerbos.request.v1.ResourceSet.instances:type_name -> cerbos.request.v1.ResourceSet.InstancesEntry
+	23, // 7: cerbos.request.v1.AttributesMap.attr:type_name -> cerbos.request.v1.AttributesMap.AttrEntry
+	27, // 8: cerbos.request.v1.CheckResourceBatchRequest.principal:type_name -> cerbos.engine.v1.Principal
+	24, // 9: cerbos.request.v1.CheckResourceBatchRequest.resources:type_name -> cerbos.request.v1.CheckResourceBatchRequest.BatchEntry
 	6,  // 10: cerbos.request.v1.CheckResourceBatchRequest.aux_data:type_name -> cerbos.request.v1.AuxData
-	24, // 11: cerbos.request.v1.AuxData.jwt:type_name -> cerbos.request.v1.AuxData.JWT
+	25, // 11: cerbos.request.v1.AuxData.jwt:type_name -> cerbos.request.v1.AuxData.JWT
 	7,  // 12: cerbos.request.v1.PlaygroundValidateRequest.files:type_name -> cerbos.request.v1.File
 	7,  // 13: cerbos.request.v1.PlaygroundTestRequest.files:type_name -> cerbos.request.v1.File
 	7,  // 14: cerbos.request.v1.PlaygroundEvaluateRequest.files:type_name -> cerbos.request.v1.File
-	26, // 15: cerbos.request.v1.PlaygroundEvaluateRequest.principal:type_name -> cerbos.engine.v1.Principal
-	28, // 16: cerbos.request.v1.PlaygroundEvaluateRequest.resource:type_name -> cerbos.engine.v1.Resource
+	27, // 15: cerbos.request.v1.PlaygroundEvaluateRequest.principal:type_name -> cerbos.engine.v1.Principal
+	29, // 16: cerbos.request.v1.PlaygroundEvaluateRequest.resource:type_name -> cerbos.engine.v1.Resource
 	6,  // 17: cerbos.request.v1.PlaygroundEvaluateRequest.aux_data:type_name -> cerbos.request.v1.AuxData
 	7,  // 18: cerbos.request.v1.PlaygroundProxyRequest.files:type_name -> cerbos.request.v1.File
 	2,  // 19: cerbos.request.v1.PlaygroundProxyRequest.check_resource_set:type_name -> cerbos.request.v1.CheckResourceSetRequest
 	5,  // 20: cerbos.request.v1.PlaygroundProxyRequest.check_resource_batch:type_name -> cerbos.request.v1.CheckResourceBatchRequest
 	1,  // 21: cerbos.request.v1.PlaygroundProxyRequest.resources_query_plan:type_name -> cerbos.request.v1.ResourcesQueryPlanRequest
-	29, // 22: cerbos.request.v1.AddOrUpdatePolicyRequest.policies:type_name -> cerbos.policy.v1.Policy
+	30, // 22: cerbos.request.v1.AddOrUpdatePolicyRequest.policies:type_name -> cerbos.policy.v1.Policy
 	0,  // 23: cerbos.request.v1.ListAuditLogEntriesRequest.kind:type_name -> cerbos.request.v1.ListAuditLogEntriesRequest.Kind
-	25, // 24: cerbos.request.v1.ListAuditLogEntriesRequest.between:type_name -> cerbos.request.v1.ListAuditLogEntriesRequest.TimeRange
-	30, // 25: cerbos.request.v1.ListAuditLogEntriesRequest.since:type_name -> google.protobuf.Duration
-	31, // 26: cerbos.request.v1.AddOrUpdateSchemaRequest.schemas:type_name -> cerbos.schema.v1.Schema
+	26, // 24: cerbos.request.v1.ListAuditLogEntriesRequest.between:type_name -> cerbos.request.v1.ListAuditLogEntriesRequest.TimeRange
+	31, // 25: cerbos.request.v1.ListAuditLogEntriesRequest.since:type_name -> google.protobuf.Duration
+	32, // 26: cerbos.request.v1.AddOrUpdateSchemaRequest.schemas:type_name -> cerbos.schema.v1.Schema
 	4,  // 27: cerbos.request.v1.ResourceSet.InstancesEntry.value:type_name -> cerbos.request.v1.AttributesMap
-	32, // 28: cerbos.request.v1.AttributesMap.AttrEntry.value:type_name -> google.protobuf.Value
-	28, // 29: cerbos.request.v1.CheckResourceBatchRequest.BatchEntry.resource:type_name -> cerbos.engine.v1.Resource
-	33, // 30: cerbos.request.v1.ListAuditLogEntriesRequest.TimeRange.start:type_name -> google.protobuf.Timestamp
-	33, // 31: cerbos.request.v1.ListAuditLogEntriesRequest.TimeRange.end:type_name -> google.protobuf.Timestamp
+	33, // 28: cerbos.request.v1.AttributesMap.AttrEntry.value:type_name -> google.protobuf.Value
+	29, // 29: cerbos.request.v1.CheckResourceBatchRequest.BatchEntry.resource:type_name -> cerbos.engine.v1.Resource
+	34, // 30: cerbos.request.v1.ListAuditLogEntriesRequest.TimeRange.start:type_name -> google.protobuf.Timestamp
+	34, // 31: cerbos.request.v1.ListAuditLogEntriesRequest.TimeRange.end:type_name -> google.protobuf.Timestamp
 	32, // [32:32] is the sub-list for method output_type
 	32, // [32:32] is the sub-list for method input_type
 	32, // [32:32] is the sub-list for extension type_name
@@ -2356,8 +2412,8 @@ func file_cerbos_request_v1_request_proto_init() {
 				return nil
 			}
 		}
-		file_cerbos_request_v1_request_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*CheckResourceBatchRequest_BatchEntry); i {
+		file_cerbos_request_v1_request_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*ReloadStoreRequest); i {
 			case 0:
 				return &v.state
 			case 1:
@@ -2369,7 +2425,7 @@ func file_cerbos_request_v1_request_proto_init() {
 			}
 		}
 		file_cerbos_request_v1_request_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*AuxData_JWT); i {
+			switch v := v.(*CheckResourceBatchRequest_BatchEntry); i {
 			case 0:
 				return &v.state
 			case 1:
@@ -2381,6 +2437,18 @@ func file_cerbos_request_v1_request_proto_init() {
 			}
 		}
 		file_cerbos_request_v1_request_proto_msgTypes[24].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*AuxData_JWT); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_cerbos_request_v1_request_proto_msgTypes[25].Exporter = func(v interface{}, i int) interface{} {
 			switch v := v.(*ListAuditLogEntriesRequest_TimeRange); i {
 			case 0:
 				return &v.state
@@ -2410,7 +2478,7 @@ func file_cerbos_request_v1_request_proto_init() {
 			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
 			RawDescriptor: file_cerbos_request_v1_request_proto_rawDesc,
 			NumEnums:      1,
-			NumMessages:   25,
+			NumMessages:   26,
 			NumExtensions: 0,
 			NumServices:   0,
 		},
diff --git a/api/genpb/cerbos/request/v1/request.pb.validate.go b/api/genpb/cerbos/request/v1/request.pb.validate.go
index 13e82edc..beb0f14a 100644
--- a/api/genpb/cerbos/request/v1/request.pb.validate.go
+++ b/api/genpb/cerbos/request/v1/request.pb.validate.go
@@ -3441,6 +3441,110 @@ var _ interface {
 	ErrorName() string
 } = DeleteSchemaRequestValidationError{}
 
+// Validate checks the field values on ReloadStoreRequest with the rules
+// defined in the proto definition for this message. If any rules are
+// violated, the first error encountered is returned, or nil if there are no violations.
+func (m *ReloadStoreRequest) Validate() error {
+	return m.validate(false)
+}
+
+// ValidateAll checks the field values on ReloadStoreRequest with the rules
+// defined in the proto definition for this message. If any rules are
+// violated, the result is a list of violation errors wrapped in
+// ReloadStoreRequestMultiError, or nil if none found.
+func (m *ReloadStoreRequest) ValidateAll() error {
+	return m.validate(true)
+}
+
+func (m *ReloadStoreRequest) validate(all bool) error {
+	if m == nil {
+		return nil
+	}
+
+	var errors []error
+
+	// no validation rules for Wait
+
+	if len(errors) > 0 {
+		return ReloadStoreRequestMultiError(errors)
+	}
+
+	return nil
+}
+
+// ReloadStoreRequestMultiError is an error wrapping multiple validation errors
+// returned by ReloadStoreRequest.ValidateAll() if the designated constraints
+// aren't met.
+type ReloadStoreRequestMultiError []error
+
+// Error returns a concatenation of all the error messages it wraps.
+func (m ReloadStoreRequestMultiError) Error() string {
+	var msgs []string
+	for _, err := range m {
+		msgs = append(msgs, err.Error())
+	}
+	return strings.Join(msgs, "; ")
+}
+
+// AllErrors returns a list of validation violation errors.
+func (m ReloadStoreRequestMultiError) AllErrors() []error { return m }
+
+// ReloadStoreRequestValidationError is the validation error returned by
+// ReloadStoreRequest.Validate if the designated constraints aren't met.
+type ReloadStoreRequestValidationError struct {
+	field  string
+	reason string
+	cause  error
+	key    bool
+}
+
+// Field function returns field value.
+func (e ReloadStoreRequestValidationError) Field() string { return e.field }
+
+// Reason function returns reason value.
+func (e ReloadStoreRequestValidationError) Reason() string { return e.reason }
+
+// Cause function returns cause value.
+func (e ReloadStoreRequestValidationError) Cause() error { return e.cause }
+
+// Key function returns key value.
+func (e ReloadStoreRequestValidationError) Key() bool { return e.key }
+
+// ErrorName returns error name.
+func (e ReloadStoreRequestValidationError) ErrorName() string {
+	return "ReloadStoreRequestValidationError"
+}
+
+// Error satisfies the builtin error interface
+func (e ReloadStoreRequestValidationError) Error() string {
+	cause := ""
+	if e.cause != nil {
+		cause = fmt.Sprintf(" | caused by: %v", e.cause)
+	}
+
+	key := ""
+	if e.key {
+		key = "key for "
+	}
+
+	return fmt.Sprintf(
+		"invalid %sReloadStoreRequest.%s: %s%s",
+		key,
+		e.field,
+		e.reason,
+		cause)
+}
+
+var _ error = ReloadStoreRequestValidationError{}
+
+var _ interface {
+	Field() string
+	Reason() string
+	Key() bool
+	Cause() error
+	ErrorName() string
+} = ReloadStoreRequestValidationError{}
+
 // Validate checks the field values on CheckResourceBatchRequest_BatchEntry
 // with the rules defined in the proto definition for this message. If any
 // rules are violated, the first error encountered is returned, or nil if
diff --git a/api/genpb/cerbos/request/v1/request_hashpb.pb.go b/api/genpb/cerbos/request/v1/request_hashpb.pb.go
index df7dfb42..672827c7 100644
--- a/api/genpb/cerbos/request/v1/request_hashpb.pb.go
+++ b/api/genpb/cerbos/request/v1/request_hashpb.pb.go
@@ -167,3 +167,11 @@ func (m *DeleteSchemaRequest) HashPB(hasher hash.Hash, ignore map[string]struct{
 		cerbos_request_v1_DeleteSchemaRequest_hashpb_sum(m, hasher, ignore)
 	}
 }
+
+// HashPB computes a hash of the message using the given hash function
+// The ignore set must contain fully-qualified field names (pkg.msg.field) that should be ignored from the hash
+func (m *ReloadStoreRequest) HashPB(hasher hash.Hash, ignore map[string]struct{}) {
+	if m != nil {
+		cerbos_request_v1_ReloadStoreRequest_hashpb_sum(m, hasher, ignore)
+	}
+}
diff --git a/api/genpb/cerbos/request/v1/request_vtproto.pb.go b/api/genpb/cerbos/request/v1/request_vtproto.pb.go
index 241bd8d3..4c532d30 100644
--- a/api/genpb/cerbos/request/v1/request_vtproto.pb.go
+++ b/api/genpb/cerbos/request/v1/request_vtproto.pb.go
@@ -1547,6 +1547,49 @@ func (m *DeleteSchemaRequest) MarshalToSizedBufferVT(dAtA []byte) (int, error) {
 	return len(dAtA) - i, nil
 }
 
+func (m *ReloadStoreRequest) MarshalVT() (dAtA []byte, err error) {
+	if m == nil {
+		return nil, nil
+	}
+	size := m.SizeVT()
+	dAtA = make([]byte, size)
+	n, err := m.MarshalToSizedBufferVT(dAtA[:size])
+	if err != nil {
+		return nil, err
+	}
+	return dAtA[:n], nil
+}
+
+func (m *ReloadStoreRequest) MarshalToVT(dAtA []byte) (int, error) {
+	size := m.SizeVT()
+	return m.MarshalToSizedBufferVT(dAtA[:size])
+}
+
+func (m *ReloadStoreRequest) MarshalToSizedBufferVT(dAtA []byte) (int, error) {
+	if m == nil {
+		return 0, nil
+	}
+	i := len(dAtA)
+	_ = i
+	var l int
+	_ = l
+	if m.unknownFields != nil {
+		i -= len(m.unknownFields)
+		copy(dAtA[i:], m.unknownFields)
+	}
+	if m.Wait {
+		i--
+		if m.Wait {
+			dAtA[i] = 1
+		} else {
+			dAtA[i] = 0
+		}
+		i--
+		dAtA[i] = 0x8
+	}
+	return len(dAtA) - i, nil
+}
+
 func encodeVarint(dAtA []byte, offset int, v uint64) int {
 	offset -= sov(v)
 	base := offset
@@ -2230,6 +2273,21 @@ func (m *DeleteSchemaRequest) SizeVT() (n int) {
 	return n
 }
 
+func (m *ReloadStoreRequest) SizeVT() (n int) {
+	if m == nil {
+		return 0
+	}
+	var l int
+	_ = l
+	if m.Wait {
+		n += 2
+	}
+	if m.unknownFields != nil {
+		n += len(m.unknownFields)
+	}
+	return n
+}
+
 func sov(x uint64) (n int) {
 	return (bits.Len64(x|1) + 6) / 7
 }
@@ -5547,6 +5605,77 @@ func (m *DeleteSchemaRequest) UnmarshalVT(dAtA []byte) error {
 	}
 	return nil
 }
+func (m *ReloadStoreRequest) UnmarshalVT(dAtA []byte) error {
+	l := len(dAtA)
+	iNdEx := 0
+	for iNdEx < l {
+		preIndex := iNdEx
+		var wire uint64
+		for shift := uint(0); ; shift += 7 {
+			if shift >= 64 {
+				return ErrIntOverflow
+			}
+			if iNdEx >= l {
+				return io.ErrUnexpectedEOF
+			}
+			b := dAtA[iNdEx]
+			iNdEx++
+			wire |= uint64(b&0x7F) << shift
+			if b < 0x80 {
+				break
+			}
+		}
+		fieldNum := int32(wire >> 3)
+		wireType := int(wire & 0x7)
+		if wireType == 4 {
+			return fmt.Errorf("proto: ReloadStoreRequest: wiretype end group for non-group")
+		}
+		if fieldNum <= 0 {
+			return fmt.Errorf("proto: ReloadStoreRequest: illegal tag %d (wire type %d)", fieldNum, wire)
+		}
+		switch fieldNum {
+		case 1:
+			if wireType != 0 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Wait", wireType)
+			}
+			var v int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflow
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				v |= int(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			m.Wait = bool(v != 0)
+		default:
+			iNdEx = preIndex
+			skippy, err := skip(dAtA[iNdEx:])
+			if err != nil {
+				return err
+			}
+			if (skippy < 0) || (iNdEx+skippy) < 0 {
+				return ErrInvalidLength
+			}
+			if (iNdEx + skippy) > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...)
+			iNdEx += skippy
+		}
+	}
+
+	if iNdEx > l {
+		return io.ErrUnexpectedEOF
+	}
+	return nil
+}
 func skip(dAtA []byte) (n int, err error) {
 	l := len(dAtA)
 	iNdEx := 0
diff --git a/api/genpb/cerbos/response/v1/hashpb_helpers.pb.go b/api/genpb/cerbos/response/v1/hashpb_helpers.pb.go
index 405c6e59..bb2a47d1 100644
--- a/api/genpb/cerbos/response/v1/hashpb_helpers.pb.go
+++ b/api/genpb/cerbos/response/v1/hashpb_helpers.pb.go
@@ -1403,6 +1403,9 @@ func cerbos_response_v1_PlaygroundValidateResponse_hashpb_sum(m *PlaygroundValid
 	}
 }
 
+func cerbos_response_v1_ReloadStoreResponse_hashpb_sum(m *ReloadStoreResponse, hasher hash.Hash, ignore map[string]struct{}) {
+}
+
 func cerbos_response_v1_ResourcesQueryPlanResponse_Expression_Operand_hashpb_sum(m *ResourcesQueryPlanResponse_Expression_Operand, hasher hash.Hash, ignore map[string]struct{}) {
 	if m.Node != nil {
 		if _, ok := ignore["cerbos.response.v1.ResourcesQueryPlanResponse.Expression.Operand.node"]; !ok {
diff --git a/api/genpb/cerbos/response/v1/response.pb.go b/api/genpb/cerbos/response/v1/response.pb.go
index a0415ba7..7b28dd8a 100644
--- a/api/genpb/cerbos/response/v1/response.pb.go
+++ b/api/genpb/cerbos/response/v1/response.pb.go
@@ -1168,6 +1168,44 @@ func (*DeleteSchemaResponse) Descriptor() ([]byte, []int) {
 	return file_cerbos_response_v1_response_proto_rawDescGZIP(), []int{16}
 }
 
+type ReloadStoreResponse struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+}
+
+func (x *ReloadStoreResponse) Reset() {
+	*x = ReloadStoreResponse{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_cerbos_response_v1_response_proto_msgTypes[17]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *ReloadStoreResponse) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ReloadStoreResponse) ProtoMessage() {}
+
+func (x *ReloadStoreResponse) ProtoReflect() protoreflect.Message {
+	mi := &file_cerbos_response_v1_response_proto_msgTypes[17]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use ReloadStoreResponse.ProtoReflect.Descriptor instead.
+func (*ReloadStoreResponse) Descriptor() ([]byte, []int) {
+	return file_cerbos_response_v1_response_proto_rawDescGZIP(), []int{17}
+}
+
 type ResourcesQueryPlanResponse_Expression struct {
 	state         protoimpl.MessageState
 	sizeCache     protoimpl.SizeCache
@@ -1180,7 +1218,7 @@ type ResourcesQueryPlanResponse_Expression struct {
 func (x *ResourcesQueryPlanResponse_Expression) Reset() {
 	*x = ResourcesQueryPlanResponse_Expression{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_cerbos_response_v1_response_proto_msgTypes[17]
+		mi := &file_cerbos_response_v1_response_proto_msgTypes[18]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -1193,7 +1231,7 @@ func (x *ResourcesQueryPlanResponse_Expression) String() string {
 func (*ResourcesQueryPlanResponse_Expression) ProtoMessage() {}
 
 func (x *ResourcesQueryPlanResponse_Expression) ProtoReflect() protoreflect.Message {
-	mi := &file_cerbos_response_v1_response_proto_msgTypes[17]
+	mi := &file_cerbos_response_v1_response_proto_msgTypes[18]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -1235,7 +1273,7 @@ type ResourcesQueryPlanResponse_Filter struct {
 func (x *ResourcesQueryPlanResponse_Filter) Reset() {
 	*x = ResourcesQueryPlanResponse_Filter{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_cerbos_response_v1_response_proto_msgTypes[18]
+		mi := &file_cerbos_response_v1_response_proto_msgTypes[19]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -1248,7 +1286,7 @@ func (x *ResourcesQueryPlanResponse_Filter) String() string {
 func (*ResourcesQueryPlanResponse_Filter) ProtoMessage() {}
 
 func (x *ResourcesQueryPlanResponse_Filter) ProtoReflect() protoreflect.Message {
-	mi := &file_cerbos_response_v1_response_proto_msgTypes[18]
+	mi := &file_cerbos_response_v1_response_proto_msgTypes[19]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -1290,7 +1328,7 @@ type ResourcesQueryPlanResponse_Meta struct {
 func (x *ResourcesQueryPlanResponse_Meta) Reset() {
 	*x = ResourcesQueryPlanResponse_Meta{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_cerbos_response_v1_response_proto_msgTypes[19]
+		mi := &file_cerbos_response_v1_response_proto_msgTypes[20]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -1303,7 +1341,7 @@ func (x *ResourcesQueryPlanResponse_Meta) String() string {
 func (*ResourcesQueryPlanResponse_Meta) ProtoMessage() {}
 
 func (x *ResourcesQueryPlanResponse_Meta) ProtoReflect() protoreflect.Message {
-	mi := &file_cerbos_response_v1_response_proto_msgTypes[19]
+	mi := &file_cerbos_response_v1_response_proto_msgTypes[20]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -1348,7 +1386,7 @@ type ResourcesQueryPlanResponse_Expression_Operand struct {
 func (x *ResourcesQueryPlanResponse_Expression_Operand) Reset() {
 	*x = ResourcesQueryPlanResponse_Expression_Operand{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_cerbos_response_v1_response_proto_msgTypes[20]
+		mi := &file_cerbos_response_v1_response_proto_msgTypes[21]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -1361,7 +1399,7 @@ func (x *ResourcesQueryPlanResponse_Expression_Operand) String() string {
 func (*ResourcesQueryPlanResponse_Expression_Operand) ProtoMessage() {}
 
 func (x *ResourcesQueryPlanResponse_Expression_Operand) ProtoReflect() protoreflect.Message {
-	mi := &file_cerbos_response_v1_response_proto_msgTypes[20]
+	mi := &file_cerbos_response_v1_response_proto_msgTypes[21]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -1442,7 +1480,7 @@ type CheckResourceSetResponse_ActionEffectMap struct {
 func (x *CheckResourceSetResponse_ActionEffectMap) Reset() {
 	*x = CheckResourceSetResponse_ActionEffectMap{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_cerbos_response_v1_response_proto_msgTypes[21]
+		mi := &file_cerbos_response_v1_response_proto_msgTypes[22]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -1455,7 +1493,7 @@ func (x *CheckResourceSetResponse_ActionEffectMap) String() string {
 func (*CheckResourceSetResponse_ActionEffectMap) ProtoMessage() {}
 
 func (x *CheckResourceSetResponse_ActionEffectMap) ProtoReflect() protoreflect.Message {
-	mi := &file_cerbos_response_v1_response_proto_msgTypes[21]
+	mi := &file_cerbos_response_v1_response_proto_msgTypes[22]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -1496,7 +1534,7 @@ type CheckResourceSetResponse_Meta struct {
 func (x *CheckResourceSetResponse_Meta) Reset() {
 	*x = CheckResourceSetResponse_Meta{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_cerbos_response_v1_response_proto_msgTypes[22]
+		mi := &file_cerbos_response_v1_response_proto_msgTypes[23]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -1509,7 +1547,7 @@ func (x *CheckResourceSetResponse_Meta) String() string {
 func (*CheckResourceSetResponse_Meta) ProtoMessage() {}
 
 func (x *CheckResourceSetResponse_Meta) ProtoReflect() protoreflect.Message {
-	mi := &file_cerbos_response_v1_response_proto_msgTypes[22]
+	mi := &file_cerbos_response_v1_response_proto_msgTypes[23]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -1544,7 +1582,7 @@ type CheckResourceSetResponse_Meta_EffectMeta struct {
 func (x *CheckResourceSetResponse_Meta_EffectMeta) Reset() {
 	*x = CheckResourceSetResponse_Meta_EffectMeta{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_cerbos_response_v1_response_proto_msgTypes[25]
+		mi := &file_cerbos_response_v1_response_proto_msgTypes[26]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -1557,7 +1595,7 @@ func (x *CheckResourceSetResponse_Meta_EffectMeta) String() string {
 func (*CheckResourceSetResponse_Meta_EffectMeta) ProtoMessage() {}
 
 func (x *CheckResourceSetResponse_Meta_EffectMeta) ProtoReflect() protoreflect.Message {
-	mi := &file_cerbos_response_v1_response_proto_msgTypes[25]
+	mi := &file_cerbos_response_v1_response_proto_msgTypes[26]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -1599,7 +1637,7 @@ type CheckResourceSetResponse_Meta_ActionMeta struct {
 func (x *CheckResourceSetResponse_Meta_ActionMeta) Reset() {
 	*x = CheckResourceSetResponse_Meta_ActionMeta{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_cerbos_response_v1_response_proto_msgTypes[26]
+		mi := &file_cerbos_response_v1_response_proto_msgTypes[27]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -1612,7 +1650,7 @@ func (x *CheckResourceSetResponse_Meta_ActionMeta) String() string {
 func (*CheckResourceSetResponse_Meta_ActionMeta) ProtoMessage() {}
 
 func (x *CheckResourceSetResponse_Meta_ActionMeta) ProtoReflect() protoreflect.Message {
-	mi := &file_cerbos_response_v1_response_proto_msgTypes[26]
+	mi := &file_cerbos_response_v1_response_proto_msgTypes[27]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -1655,7 +1693,7 @@ type CheckResourceBatchResponse_ActionEffectMap struct {
 func (x *CheckResourceBatchResponse_ActionEffectMap) Reset() {
 	*x = CheckResourceBatchResponse_ActionEffectMap{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_cerbos_response_v1_response_proto_msgTypes[29]
+		mi := &file_cerbos_response_v1_response_proto_msgTypes[30]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -1668,7 +1706,7 @@ func (x *CheckResourceBatchResponse_ActionEffectMap) String() string {
 func (*CheckResourceBatchResponse_ActionEffectMap) ProtoMessage() {}
 
 func (x *CheckResourceBatchResponse_ActionEffectMap) ProtoReflect() protoreflect.Message {
-	mi := &file_cerbos_response_v1_response_proto_msgTypes[29]
+	mi := &file_cerbos_response_v1_response_proto_msgTypes[30]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -1717,7 +1755,7 @@ type PlaygroundFailure_Error struct {
 func (x *PlaygroundFailure_Error) Reset() {
 	*x = PlaygroundFailure_Error{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_cerbos_response_v1_response_proto_msgTypes[31]
+		mi := &file_cerbos_response_v1_response_proto_msgTypes[32]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -1730,7 +1768,7 @@ func (x *PlaygroundFailure_Error) String() string {
 func (*PlaygroundFailure_Error) ProtoMessage() {}
 
 func (x *PlaygroundFailure_Error) ProtoReflect() protoreflect.Message {
-	mi := &file_cerbos_response_v1_response_proto_msgTypes[31]
+	mi := &file_cerbos_response_v1_response_proto_msgTypes[32]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -1775,7 +1813,7 @@ type PlaygroundEvaluateResponse_EvalResult struct {
 func (x *PlaygroundEvaluateResponse_EvalResult) Reset() {
 	*x = PlaygroundEvaluateResponse_EvalResult{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_cerbos_response_v1_response_proto_msgTypes[32]
+		mi := &file_cerbos_response_v1_response_proto_msgTypes[33]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -1788,7 +1826,7 @@ func (x *PlaygroundEvaluateResponse_EvalResult) String() string {
 func (*PlaygroundEvaluateResponse_EvalResult) ProtoMessage() {}
 
 func (x *PlaygroundEvaluateResponse_EvalResult) ProtoReflect() protoreflect.Message {
-	mi := &file_cerbos_response_v1_response_proto_msgTypes[32]
+	mi := &file_cerbos_response_v1_response_proto_msgTypes[33]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -1850,7 +1888,7 @@ type PlaygroundEvaluateResponse_EvalResultList struct {
 func (x *PlaygroundEvaluateResponse_EvalResultList) Reset() {
 	*x = PlaygroundEvaluateResponse_EvalResultList{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_cerbos_response_v1_response_proto_msgTypes[33]
+		mi := &file_cerbos_response_v1_response_proto_msgTypes[34]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -1863,7 +1901,7 @@ func (x *PlaygroundEvaluateResponse_EvalResultList) String() string {
 func (*PlaygroundEvaluateResponse_EvalResultList) ProtoMessage() {}
 
 func (x *PlaygroundEvaluateResponse_EvalResultList) ProtoReflect() protoreflect.Message {
-	mi := &file_cerbos_response_v1_response_proto_msgTypes[33]
+	mi := &file_cerbos_response_v1_response_proto_msgTypes[34]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -2417,15 +2455,18 @@ var file_cerbos_response_v1_response_proto_rawDesc = []byte{
 	0x74, 0x65, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
 	0x3a, 0x20, 0x92, 0x41, 0x1d, 0x0a, 0x1b, 0x32, 0x19, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x20,
 	0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x28, 0x73, 0x29, 0x20, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e,
-	0x73, 0x65, 0x42, 0x77, 0x0a, 0x1a, 0x64, 0x65, 0x76, 0x2e, 0x63, 0x65, 0x72, 0x62, 0x6f, 0x73,
-	0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
-	0x5a, 0x40, 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, 0x73, 0x70,
-	0x6f, 0x6e, 0x73, 0x65, 0x2f, 0x76, 0x31, 0x3b, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
-	0x76, 0x31, 0xaa, 0x02, 0x16, 0x43, 0x65, 0x72, 0x62, 0x6f, 0x73, 0x2e, 0x41, 0x70, 0x69, 0x2e,
-	0x56, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f,
-	0x74, 0x6f, 0x33,
+	0x73, 0x65, 0x22, 0x33, 0x0a, 0x13, 0x52, 0x65, 0x6c, 0x6f, 0x61, 0x64, 0x53, 0x74, 0x6f, 0x72,
+	0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x3a, 0x1c, 0x92, 0x41, 0x19, 0x0a, 0x17,
+	0x32, 0x15, 0x52, 0x65, 0x6c, 0x6f, 0x61, 0x64, 0x20, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x20, 0x72,
+	0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x77, 0x0a, 0x1a, 0x64, 0x65, 0x76, 0x2e, 0x63,
+	0x65, 0x72, 0x62, 0x6f, 0x73, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31, 0x2e, 0x72, 0x65, 0x73,
+	0x70, 0x6f, 0x6e, 0x73, 0x65, 0x5a, 0x40, 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, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2f, 0x76, 0x31, 0x3b, 0x72, 0x65, 0x73,
+	0x70, 0x6f, 0x6e, 0x73, 0x65, 0x76, 0x31, 0xaa, 0x02, 0x16, 0x43, 0x65, 0x72, 0x62, 0x6f, 0x73,
+	0x2e, 0x41, 0x70, 0x69, 0x2e, 0x56, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
+	0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
 }
 
 var (
@@ -2441,7 +2482,7 @@ func file_cerbos_response_v1_response_proto_rawDescGZIP() []byte {
 }
 
 var file_cerbos_response_v1_response_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
-var file_cerbos_response_v1_response_proto_msgTypes = make([]protoimpl.MessageInfo, 34)
+var file_cerbos_response_v1_response_proto_msgTypes = make([]protoimpl.MessageInfo, 35)
 var file_cerbos_response_v1_response_proto_goTypes = []interface{}{
 	(ResourcesQueryPlanResponse_Filter_Kind)(0),           // 0: cerbos.response.v1.ResourcesQueryPlanResponse.Filter.Kind
 	(*ResourcesQueryPlanResponse)(nil),                    // 1: cerbos.response.v1.ResourcesQueryPlanResponse
@@ -2461,74 +2502,75 @@ var file_cerbos_response_v1_response_proto_goTypes = []interface{}{
 	(*ListSchemasResponse)(nil),                           // 15: cerbos.response.v1.ListSchemasResponse
 	(*GetSchemaResponse)(nil),                             // 16: cerbos.response.v1.GetSchemaResponse
 	(*DeleteSchemaResponse)(nil),                          // 17: cerbos.response.v1.DeleteSchemaResponse
-	(*ResourcesQueryPlanResponse_Expression)(nil),         // 18: cerbos.response.v1.ResourcesQueryPlanResponse.Expression
-	(*ResourcesQueryPlanResponse_Filter)(nil),             // 19: cerbos.response.v1.ResourcesQueryPlanResponse.Filter
-	(*ResourcesQueryPlanResponse_Meta)(nil),               // 20: cerbos.response.v1.ResourcesQueryPlanResponse.Meta
-	(*ResourcesQueryPlanResponse_Expression_Operand)(nil), // 21: cerbos.response.v1.ResourcesQueryPlanResponse.Expression.Operand
-	(*CheckResourceSetResponse_ActionEffectMap)(nil),      // 22: cerbos.response.v1.CheckResourceSetResponse.ActionEffectMap
-	(*CheckResourceSetResponse_Meta)(nil),                 // 23: cerbos.response.v1.CheckResourceSetResponse.Meta
-	nil,                                                   // 24: cerbos.response.v1.CheckResourceSetResponse.ResourceInstancesEntry
-	nil,                                                   // 25: cerbos.response.v1.CheckResourceSetResponse.ActionEffectMap.ActionsEntry
-	(*CheckResourceSetResponse_Meta_EffectMeta)(nil),      // 26: cerbos.response.v1.CheckResourceSetResponse.Meta.EffectMeta
-	(*CheckResourceSetResponse_Meta_ActionMeta)(nil),      // 27: cerbos.response.v1.CheckResourceSetResponse.Meta.ActionMeta
-	nil, // 28: cerbos.response.v1.CheckResourceSetResponse.Meta.ResourceInstancesEntry
-	nil, // 29: cerbos.response.v1.CheckResourceSetResponse.Meta.ActionMeta.ActionsEntry
-	(*CheckResourceBatchResponse_ActionEffectMap)(nil), // 30: cerbos.response.v1.CheckResourceBatchResponse.ActionEffectMap
-	nil,                             // 31: cerbos.response.v1.CheckResourceBatchResponse.ActionEffectMap.ActionsEntry
-	(*PlaygroundFailure_Error)(nil), // 32: cerbos.response.v1.PlaygroundFailure.Error
-	(*PlaygroundEvaluateResponse_EvalResult)(nil),     // 33: cerbos.response.v1.PlaygroundEvaluateResponse.EvalResult
-	(*PlaygroundEvaluateResponse_EvalResultList)(nil), // 34: cerbos.response.v1.PlaygroundEvaluateResponse.EvalResultList
-	(*emptypb.Empty)(nil),                             // 35: google.protobuf.Empty
-	(*v1.TestResults)(nil),                            // 36: cerbos.policy.v1.TestResults
-	(*v11.AccessLogEntry)(nil),                        // 37: cerbos.audit.v1.AccessLogEntry
-	(*v11.DecisionLogEntry)(nil),                      // 38: cerbos.audit.v1.DecisionLogEntry
-	(*v1.Policy)(nil),                                 // 39: cerbos.policy.v1.Policy
-	(*v12.Schema)(nil),                                // 40: cerbos.schema.v1.Schema
-	(*structpb.Value)(nil),                            // 41: google.protobuf.Value
-	(*v12.ValidationError)(nil),                       // 42: cerbos.schema.v1.ValidationError
-	(v13.Effect)(0),                                   // 43: cerbos.effect.v1.Effect
+	(*ReloadStoreResponse)(nil),                           // 18: cerbos.response.v1.ReloadStoreResponse
+	(*ResourcesQueryPlanResponse_Expression)(nil),         // 19: cerbos.response.v1.ResourcesQueryPlanResponse.Expression
+	(*ResourcesQueryPlanResponse_Filter)(nil),             // 20: cerbos.response.v1.ResourcesQueryPlanResponse.Filter
+	(*ResourcesQueryPlanResponse_Meta)(nil),               // 21: cerbos.response.v1.ResourcesQueryPlanResponse.Meta
+	(*ResourcesQueryPlanResponse_Expression_Operand)(nil), // 22: cerbos.response.v1.ResourcesQueryPlanResponse.Expression.Operand
+	(*CheckResourceSetResponse_ActionEffectMap)(nil),      // 23: cerbos.response.v1.CheckResourceSetResponse.ActionEffectMap
+	(*CheckResourceSetResponse_Meta)(nil),                 // 24: cerbos.response.v1.CheckResourceSetResponse.Meta
+	nil,                                                   // 25: cerbos.response.v1.CheckResourceSetResponse.ResourceInstancesEntry
+	nil,                                                   // 26: cerbos.response.v1.CheckResourceSetResponse.ActionEffectMap.ActionsEntry
+	(*CheckResourceSetResponse_Meta_EffectMeta)(nil),      // 27: cerbos.response.v1.CheckResourceSetResponse.Meta.EffectMeta
+	(*CheckResourceSetResponse_Meta_ActionMeta)(nil),      // 28: cerbos.response.v1.CheckResourceSetResponse.Meta.ActionMeta
+	nil, // 29: cerbos.response.v1.CheckResourceSetResponse.Meta.ResourceInstancesEntry
+	nil, // 30: cerbos.response.v1.CheckResourceSetResponse.Meta.ActionMeta.ActionsEntry
+	(*CheckResourceBatchResponse_ActionEffectMap)(nil), // 31: cerbos.response.v1.CheckResourceBatchResponse.ActionEffectMap
+	nil,                             // 32: cerbos.response.v1.CheckResourceBatchResponse.ActionEffectMap.ActionsEntry
+	(*PlaygroundFailure_Error)(nil), // 33: cerbos.response.v1.PlaygroundFailure.Error
+	(*PlaygroundEvaluateResponse_EvalResult)(nil),     // 34: cerbos.response.v1.PlaygroundEvaluateResponse.EvalResult
+	(*PlaygroundEvaluateResponse_EvalResultList)(nil), // 35: cerbos.response.v1.PlaygroundEvaluateResponse.EvalResultList
+	(*emptypb.Empty)(nil),                             // 36: google.protobuf.Empty
+	(*v1.TestResults)(nil),                            // 37: cerbos.policy.v1.TestResults
+	(*v11.AccessLogEntry)(nil),                        // 38: cerbos.audit.v1.AccessLogEntry
+	(*v11.DecisionLogEntry)(nil),                      // 39: cerbos.audit.v1.DecisionLogEntry
+	(*v1.Policy)(nil),                                 // 40: cerbos.policy.v1.Policy
+	(*v12.Schema)(nil),                                // 41: cerbos.schema.v1.Schema
+	(*structpb.Value)(nil),                            // 42: google.protobuf.Value
+	(*v12.ValidationError)(nil),                       // 43: cerbos.schema.v1.ValidationError
+	(v13.Effect)(0),                                   // 44: cerbos.effect.v1.Effect
 }
 var file_cerbos_response_v1_response_proto_depIdxs = []int32{
-	19, // 0: cerbos.response.v1.ResourcesQueryPlanResponse.filter:type_name -> cerbos.response.v1.ResourcesQueryPlanResponse.Filter
-	20, // 1: cerbos.response.v1.ResourcesQueryPlanResponse.meta:type_name -> cerbos.response.v1.ResourcesQueryPlanResponse.Meta
-	24, // 2: cerbos.response.v1.CheckResourceSetResponse.resource_instances:type_name -> cerbos.response.v1.CheckResourceSetResponse.ResourceInstancesEntry
-	23, // 3: cerbos.response.v1.CheckResourceSetResponse.meta:type_name -> cerbos.response.v1.CheckResourceSetResponse.Meta
-	30, // 4: cerbos.response.v1.CheckResourceBatchResponse.results:type_name -> cerbos.response.v1.CheckResourceBatchResponse.ActionEffectMap
-	32, // 5: cerbos.response.v1.PlaygroundFailure.errors:type_name -> cerbos.response.v1.PlaygroundFailure.Error
+	20, // 0: cerbos.response.v1.ResourcesQueryPlanResponse.filter:type_name -> cerbos.response.v1.ResourcesQueryPlanResponse.Filter
+	21, // 1: cerbos.response.v1.ResourcesQueryPlanResponse.meta:type_name -> cerbos.response.v1.ResourcesQueryPlanResponse.Meta
+	25, // 2: cerbos.response.v1.CheckResourceSetResponse.resource_instances:type_name -> cerbos.response.v1.CheckResourceSetResponse.ResourceInstancesEntry
+	24, // 3: cerbos.response.v1.CheckResourceSetResponse.meta:type_name -> cerbos.response.v1.CheckResourceSetResponse.Meta
+	31, // 4: cerbos.response.v1.CheckResourceBatchResponse.results:type_name -> cerbos.response.v1.CheckResourceBatchResponse.ActionEffectMap
+	33, // 5: cerbos.response.v1.PlaygroundFailure.errors:type_name -> cerbos.response.v1.PlaygroundFailure.Error
 	4,  // 6: cerbos.response.v1.PlaygroundValidateResponse.failure:type_name -> cerbos.response.v1.PlaygroundFailure
-	35, // 7: cerbos.response.v1.PlaygroundValidateResponse.success:type_name -> google.protobuf.Empty
+	36, // 7: cerbos.response.v1.PlaygroundValidateResponse.success:type_name -> google.protobuf.Empty
 	4,  // 8: cerbos.response.v1.PlaygroundTestResponse.failure:type_name -> cerbos.response.v1.PlaygroundFailure
-	36, // 9: cerbos.response.v1.PlaygroundTestResponse.results:type_name -> cerbos.policy.v1.TestResults
+	37, // 9: cerbos.response.v1.PlaygroundTestResponse.results:type_name -> cerbos.policy.v1.TestResults
 	4,  // 10: cerbos.response.v1.PlaygroundEvaluateResponse.failure:type_name -> cerbos.response.v1.PlaygroundFailure
-	34, // 11: cerbos.response.v1.PlaygroundEvaluateResponse.success:type_name -> cerbos.response.v1.PlaygroundEvaluateResponse.EvalResultList
+	35, // 11: cerbos.response.v1.PlaygroundEvaluateResponse.success:type_name -> cerbos.response.v1.PlaygroundEvaluateResponse.EvalResultList
 	4,  // 12: cerbos.response.v1.PlaygroundProxyResponse.failure:type_name -> cerbos.response.v1.PlaygroundFailure
 	2,  // 13: cerbos.response.v1.PlaygroundProxyResponse.check_resource_set:type_name -> cerbos.response.v1.CheckResourceSetResponse
 	3,  // 14: cerbos.response.v1.PlaygroundProxyResponse.check_resource_batch:type_name -> cerbos.response.v1.CheckResourceBatchResponse
 	1,  // 15: cerbos.response.v1.PlaygroundProxyResponse.resources_query_plan:type_name -> cerbos.response.v1.ResourcesQueryPlanResponse
-	35, // 16: cerbos.response.v1.AddOrUpdatePolicyResponse.success:type_name -> google.protobuf.Empty
-	37, // 17: cerbos.response.v1.ListAuditLogEntriesResponse.access_log_entry:type_name -> cerbos.audit.v1.AccessLogEntry
-	38, // 18: cerbos.response.v1.ListAuditLogEntriesResponse.decision_log_entry:type_name -> cerbos.audit.v1.DecisionLogEntry
-	39, // 19: cerbos.response.v1.GetPolicyResponse.policies:type_name -> cerbos.policy.v1.Policy
-	40, // 20: cerbos.response.v1.GetSchemaResponse.schemas:type_name -> cerbos.schema.v1.Schema
-	21, // 21: cerbos.response.v1.ResourcesQueryPlanResponse.Expression.operands:type_name -> cerbos.response.v1.ResourcesQueryPlanResponse.Expression.Operand
+	36, // 16: cerbos.response.v1.AddOrUpdatePolicyResponse.success:type_name -> google.protobuf.Empty
+	38, // 17: cerbos.response.v1.ListAuditLogEntriesResponse.access_log_entry:type_name -> cerbos.audit.v1.AccessLogEntry
+	39, // 18: cerbos.response.v1.ListAuditLogEntriesResponse.decision_log_entry:type_name -> cerbos.audit.v1.DecisionLogEntry
+	40, // 19: cerbos.response.v1.GetPolicyResponse.policies:type_name -> cerbos.policy.v1.Policy
+	41, // 20: cerbos.response.v1.GetSchemaResponse.schemas:type_name -> cerbos.schema.v1.Schema
+	22, // 21: cerbos.response.v1.ResourcesQueryPlanResponse.Expression.operands:type_name -> cerbos.response.v1.ResourcesQueryPlanResponse.Expression.Operand
 	0,  // 22: cerbos.response.v1.ResourcesQueryPlanResponse.Filter.kind:type_name -> cerbos.response.v1.ResourcesQueryPlanResponse.Filter.Kind
-	21, // 23: cerbos.response.v1.ResourcesQueryPlanResponse.Filter.condition:type_name -> cerbos.response.v1.ResourcesQueryPlanResponse.Expression.Operand
-	41, // 24: cerbos.response.v1.ResourcesQueryPlanResponse.Expression.Operand.value:type_name -> google.protobuf.Value
-	18, // 25: cerbos.response.v1.ResourcesQueryPlanResponse.Expression.Operand.expression:type_name -> cerbos.response.v1.ResourcesQueryPlanResponse.Expression
-	25, // 26: cerbos.response.v1.CheckResourceSetResponse.ActionEffectMap.actions:type_name -> cerbos.response.v1.CheckResourceSetResponse.ActionEffectMap.ActionsEntry
-	42, // 27: cerbos.response.v1.CheckResourceSetResponse.ActionEffectMap.validation_errors:type_name -> cerbos.schema.v1.ValidationError
-	28, // 28: cerbos.response.v1.CheckResourceSetResponse.Meta.resource_instances:type_name -> cerbos.response.v1.CheckResourceSetResponse.Meta.ResourceInstancesEntry
-	22, // 29: cerbos.response.v1.CheckResourceSetResponse.ResourceInstancesEntry.value:type_name -> cerbos.response.v1.CheckResourceSetResponse.ActionEffectMap
-	43, // 30: cerbos.response.v1.CheckResourceSetResponse.ActionEffectMap.ActionsEntry.value:type_name -> cerbos.effect.v1.Effect
-	29, // 31: cerbos.response.v1.CheckResourceSetResponse.Meta.ActionMeta.actions:type_name -> cerbos.response.v1.CheckResourceSetResponse.Meta.ActionMeta.ActionsEntry
-	27, // 32: cerbos.response.v1.CheckResourceSetResponse.Meta.ResourceInstancesEntry.value:type_name -> cerbos.response.v1.CheckResourceSetResponse.Meta.ActionMeta
-	26, // 33: cerbos.response.v1.CheckResourceSetResponse.Meta.ActionMeta.ActionsEntry.value:type_name -> cerbos.response.v1.CheckResourceSetResponse.Meta.EffectMeta
-	31, // 34: cerbos.response.v1.CheckResourceBatchResponse.ActionEffectMap.actions:type_name -> cerbos.response.v1.CheckResourceBatchResponse.ActionEffectMap.ActionsEntry
-	42, // 35: cerbos.response.v1.CheckResourceBatchResponse.ActionEffectMap.validation_errors:type_name -> cerbos.schema.v1.ValidationError
-	43, // 36: cerbos.response.v1.CheckResourceBatchResponse.ActionEffectMap.ActionsEntry.value:type_name -> cerbos.effect.v1.Effect
-	43, // 37: cerbos.response.v1.PlaygroundEvaluateResponse.EvalResult.effect:type_name -> cerbos.effect.v1.Effect
-	42, // 38: cerbos.response.v1.PlaygroundEvaluateResponse.EvalResult.validation_errors:type_name -> cerbos.schema.v1.ValidationError
-	33, // 39: cerbos.response.v1.PlaygroundEvaluateResponse.EvalResultList.results:type_name -> cerbos.response.v1.PlaygroundEvaluateResponse.EvalResult
+	22, // 23: cerbos.response.v1.ResourcesQueryPlanResponse.Filter.condition:type_name -> cerbos.response.v1.ResourcesQueryPlanResponse.Expression.Operand
+	42, // 24: cerbos.response.v1.ResourcesQueryPlanResponse.Expression.Operand.value:type_name -> google.protobuf.Value
+	19, // 25: cerbos.response.v1.ResourcesQueryPlanResponse.Expression.Operand.expression:type_name -> cerbos.response.v1.ResourcesQueryPlanResponse.Expression
+	26, // 26: cerbos.response.v1.CheckResourceSetResponse.ActionEffectMap.actions:type_name -> cerbos.response.v1.CheckResourceSetResponse.ActionEffectMap.ActionsEntry
+	43, // 27: cerbos.response.v1.CheckResourceSetResponse.ActionEffectMap.validation_errors:type_name -> cerbos.schema.v1.ValidationError
+	29, // 28: cerbos.response.v1.CheckResourceSetResponse.Meta.resource_instances:type_name -> cerbos.response.v1.CheckResourceSetResponse.Meta.ResourceInstancesEntry
+	23, // 29: cerbos.response.v1.CheckResourceSetResponse.ResourceInstancesEntry.value:type_name -> cerbos.response.v1.CheckResourceSetResponse.ActionEffectMap
+	44, // 30: cerbos.response.v1.CheckResourceSetResponse.ActionEffectMap.ActionsEntry.value:type_name -> cerbos.effect.v1.Effect
+	30, // 31: cerbos.response.v1.CheckResourceSetResponse.Meta.ActionMeta.actions:type_name -> cerbos.response.v1.CheckResourceSetResponse.Meta.ActionMeta.ActionsEntry
+	28, // 32: cerbos.response.v1.CheckResourceSetResponse.Meta.ResourceInstancesEntry.value:type_name -> cerbos.response.v1.CheckResourceSetResponse.Meta.ActionMeta
+	27, // 33: cerbos.response.v1.CheckResourceSetResponse.Meta.ActionMeta.ActionsEntry.value:type_name -> cerbos.response.v1.CheckResourceSetResponse.Meta.EffectMeta
+	32, // 34: cerbos.response.v1.CheckResourceBatchResponse.ActionEffectMap.actions:type_name -> cerbos.response.v1.CheckResourceBatchResponse.ActionEffectMap.ActionsEntry
+	43, // 35: cerbos.response.v1.CheckResourceBatchResponse.ActionEffectMap.validation_errors:type_name -> cerbos.schema.v1.ValidationError
+	44, // 36: cerbos.response.v1.CheckResourceBatchResponse.ActionEffectMap.ActionsEntry.value:type_name -> cerbos.effect.v1.Effect
+	44, // 37: cerbos.response.v1.PlaygroundEvaluateResponse.EvalResult.effect:type_name -> cerbos.effect.v1.Effect
+	43, // 38: cerbos.response.v1.PlaygroundEvaluateResponse.EvalResult.validation_errors:type_name -> cerbos.schema.v1.ValidationError
+	34, // 39: cerbos.response.v1.PlaygroundEvaluateResponse.EvalResultList.results:type_name -> cerbos.response.v1.PlaygroundEvaluateResponse.EvalResult
 	40, // [40:40] is the sub-list for method output_type
 	40, // [40:40] is the sub-list for method input_type
 	40, // [40:40] is the sub-list for extension type_name
@@ -2747,7 +2789,7 @@ func file_cerbos_response_v1_response_proto_init() {
 			}
 		}
 		file_cerbos_response_v1_response_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*ResourcesQueryPlanResponse_Expression); i {
+			switch v := v.(*ReloadStoreResponse); i {
 			case 0:
 				return &v.state
 			case 1:
@@ -2759,7 +2801,7 @@ func file_cerbos_response_v1_response_proto_init() {
 			}
 		}
 		file_cerbos_response_v1_response_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*ResourcesQueryPlanResponse_Filter); i {
+			switch v := v.(*ResourcesQueryPlanResponse_Expression); i {
 			case 0:
 				return &v.state
 			case 1:
@@ -2771,7 +2813,7 @@ func file_cerbos_response_v1_response_proto_init() {
 			}
 		}
 		file_cerbos_response_v1_response_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*ResourcesQueryPlanResponse_Meta); i {
+			switch v := v.(*ResourcesQueryPlanResponse_Filter); i {
 			case 0:
 				return &v.state
 			case 1:
@@ -2783,7 +2825,7 @@ func file_cerbos_response_v1_response_proto_init() {
 			}
 		}
 		file_cerbos_response_v1_response_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*ResourcesQueryPlanResponse_Expression_Operand); i {
+			switch v := v.(*ResourcesQueryPlanResponse_Meta); i {
 			case 0:
 				return &v.state
 			case 1:
@@ -2795,7 +2837,7 @@ func file_cerbos_response_v1_response_proto_init() {
 			}
 		}
 		file_cerbos_response_v1_response_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*CheckResourceSetResponse_ActionEffectMap); i {
+			switch v := v.(*ResourcesQueryPlanResponse_Expression_Operand); i {
 			case 0:
 				return &v.state
 			case 1:
@@ -2807,6 +2849,18 @@ func file_cerbos_response_v1_response_proto_init() {
 			}
 		}
 		file_cerbos_response_v1_response_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*CheckResourceSetResponse_ActionEffectMap); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_cerbos_response_v1_response_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} {
 			switch v := v.(*CheckResourceSetResponse_Meta); i {
 			case 0:
 				return &v.state
@@ -2818,7 +2872,7 @@ func file_cerbos_response_v1_response_proto_init() {
 				return nil
 			}
 		}
-		file_cerbos_response_v1_response_proto_msgTypes[25].Exporter = func(v interface{}, i int) interface{} {
+		file_cerbos_response_v1_response_proto_msgTypes[26].Exporter = func(v interface{}, i int) interface{} {
 			switch v := v.(*CheckResourceSetResponse_Meta_EffectMeta); i {
 			case 0:
 				return &v.state
@@ -2830,7 +2884,7 @@ func file_cerbos_response_v1_response_proto_init() {
 				return nil
 			}
 		}
-		file_cerbos_response_v1_response_proto_msgTypes[26].Exporter = func(v interface{}, i int) interface{} {
+		file_cerbos_response_v1_response_proto_msgTypes[27].Exporter = func(v interface{}, i int) interface{} {
 			switch v := v.(*CheckResourceSetResponse_Meta_ActionMeta); i {
 			case 0:
 				return &v.state
@@ -2842,7 +2896,7 @@ func file_cerbos_response_v1_response_proto_init() {
 				return nil
 			}
 		}
-		file_cerbos_response_v1_response_proto_msgTypes[29].Exporter = func(v interface{}, i int) interface{} {
+		file_cerbos_response_v1_response_proto_msgTypes[30].Exporter = func(v interface{}, i int) interface{} {
 			switch v := v.(*CheckResourceBatchResponse_ActionEffectMap); i {
 			case 0:
 				return &v.state
@@ -2854,7 +2908,7 @@ func file_cerbos_response_v1_response_proto_init() {
 				return nil
 			}
 		}
-		file_cerbos_response_v1_response_proto_msgTypes[31].Exporter = func(v interface{}, i int) interface{} {
+		file_cerbos_response_v1_response_proto_msgTypes[32].Exporter = func(v interface{}, i int) interface{} {
 			switch v := v.(*PlaygroundFailure_Error); i {
 			case 0:
 				return &v.state
@@ -2866,7 +2920,7 @@ func file_cerbos_response_v1_response_proto_init() {
 				return nil
 			}
 		}
-		file_cerbos_response_v1_response_proto_msgTypes[32].Exporter = func(v interface{}, i int) interface{} {
+		file_cerbos_response_v1_response_proto_msgTypes[33].Exporter = func(v interface{}, i int) interface{} {
 			switch v := v.(*PlaygroundEvaluateResponse_EvalResult); i {
 			case 0:
 				return &v.state
@@ -2878,7 +2932,7 @@ func file_cerbos_response_v1_response_proto_init() {
 				return nil
 			}
 		}
-		file_cerbos_response_v1_response_proto_msgTypes[33].Exporter = func(v interface{}, i int) interface{} {
+		file_cerbos_response_v1_response_proto_msgTypes[34].Exporter = func(v interface{}, i int) interface{} {
 			switch v := v.(*PlaygroundEvaluateResponse_EvalResultList); i {
 			case 0:
 				return &v.state
@@ -2913,7 +2967,7 @@ func file_cerbos_response_v1_response_proto_init() {
 		(*ListAuditLogEntriesResponse_AccessLogEntry)(nil),
 		(*ListAuditLogEntriesResponse_DecisionLogEntry)(nil),
 	}
-	file_cerbos_response_v1_response_proto_msgTypes[20].OneofWrappers = []interface{}{
+	file_cerbos_response_v1_response_proto_msgTypes[21].OneofWrappers = []interface{}{
 		(*ResourcesQueryPlanResponse_Expression_Operand_Value)(nil),
 		(*ResourcesQueryPlanResponse_Expression_Operand_Expression)(nil),
 		(*ResourcesQueryPlanResponse_Expression_Operand_Variable)(nil),
@@ -2924,7 +2978,7 @@ func file_cerbos_response_v1_response_proto_init() {
 			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
 			RawDescriptor: file_cerbos_response_v1_response_proto_rawDesc,
 			NumEnums:      1,
-			NumMessages:   34,
+			NumMessages:   35,
 			NumExtensions: 0,
 			NumServices:   0,
 		},
diff --git a/api/genpb/cerbos/response/v1/response.pb.validate.go b/api/genpb/cerbos/response/v1/response.pb.validate.go
index 9f569a9c..cc53205e 100644
--- a/api/genpb/cerbos/response/v1/response.pb.validate.go
+++ b/api/genpb/cerbos/response/v1/response.pb.validate.go
@@ -2490,6 +2490,108 @@ var _ interface {
 	ErrorName() string
 } = DeleteSchemaResponseValidationError{}
 
+// Validate checks the field values on ReloadStoreResponse with the rules
+// defined in the proto definition for this message. If any rules are
+// violated, the first error encountered is returned, or nil if there are no violations.
+func (m *ReloadStoreResponse) Validate() error {
+	return m.validate(false)
+}
+
+// ValidateAll checks the field values on ReloadStoreResponse with the rules
+// defined in the proto definition for this message. If any rules are
+// violated, the result is a list of violation errors wrapped in
+// ReloadStoreResponseMultiError, or nil if none found.
+func (m *ReloadStoreResponse) ValidateAll() error {
+	return m.validate(true)
+}
+
+func (m *ReloadStoreResponse) validate(all bool) error {
+	if m == nil {
+		return nil
+	}
+
+	var errors []error
+
+	if len(errors) > 0 {
+		return ReloadStoreResponseMultiError(errors)
+	}
+
+	return nil
+}
+
+// ReloadStoreResponseMultiError is an error wrapping multiple validation
+// errors returned by ReloadStoreResponse.ValidateAll() if the designated
+// constraints aren't met.
+type ReloadStoreResponseMultiError []error
+
+// Error returns a concatenation of all the error messages it wraps.
+func (m ReloadStoreResponseMultiError) Error() string {
+	var msgs []string
+	for _, err := range m {
+		msgs = append(msgs, err.Error())
+	}
+	return strings.Join(msgs, "; ")
+}
+
+// AllErrors returns a list of validation violation errors.
+func (m ReloadStoreResponseMultiError) AllErrors() []error { return m }
+
+// ReloadStoreResponseValidationError is the validation error returned by
+// ReloadStoreResponse.Validate if the designated constraints aren't met.
+type ReloadStoreResponseValidationError struct {
+	field  string
+	reason string
+	cause  error
+	key    bool
+}
+
+// Field function returns field value.
+func (e ReloadStoreResponseValidationError) Field() string { return e.field }
+
+// Reason function returns reason value.
+func (e ReloadStoreResponseValidationError) Reason() string { return e.reason }
+
+// Cause function returns cause value.
+func (e ReloadStoreResponseValidationError) Cause() error { return e.cause }
+
+// Key function returns key value.
+func (e ReloadStoreResponseValidationError) Key() bool { return e.key }
+
+// ErrorName returns error name.
+func (e ReloadStoreResponseValidationError) ErrorName() string {
+	return "ReloadStoreResponseValidationError"
+}
+
+// Error satisfies the builtin error interface
+func (e ReloadStoreResponseValidationError) Error() string {
+	cause := ""
+	if e.cause != nil {
+		cause = fmt.Sprintf(" | caused by: %v", e.cause)
+	}
+
+	key := ""
+	if e.key {
+		key = "key for "
+	}
+
+	return fmt.Sprintf(
+		"invalid %sReloadStoreResponse.%s: %s%s",
+		key,
+		e.field,
+		e.reason,
+		cause)
+}
+
+var _ error = ReloadStoreResponseValidationError{}
+
+var _ interface {
+	Field() string
+	Reason() string
+	Key() bool
+	Cause() error
+	ErrorName() string
+} = ReloadStoreResponseValidationError{}
+
 // Validate checks the field values on ResourcesQueryPlanResponse_Expression
 // with the rules defined in the proto definition for this message. If any
 // rules are violated, the first error encountered is returned, or nil if
diff --git a/api/genpb/cerbos/response/v1/response_vtproto.pb.go b/api/genpb/cerbos/response/v1/response_vtproto.pb.go
index 60d0054a..e8298270 100644
--- a/api/genpb/cerbos/response/v1/response_vtproto.pb.go
+++ b/api/genpb/cerbos/response/v1/response_vtproto.pb.go
@@ -1931,6 +1931,39 @@ func (m *DeleteSchemaResponse) MarshalToSizedBufferVT(dAtA []byte) (int, error)
 	return len(dAtA) - i, nil
 }
 
+func (m *ReloadStoreResponse) MarshalVT() (dAtA []byte, err error) {
+	if m == nil {
+		return nil, nil
+	}
+	size := m.SizeVT()
+	dAtA = make([]byte, size)
+	n, err := m.MarshalToSizedBufferVT(dAtA[:size])
+	if err != nil {
+		return nil, err
+	}
+	return dAtA[:n], nil
+}
+
+func (m *ReloadStoreResponse) MarshalToVT(dAtA []byte) (int, error) {
+	size := m.SizeVT()
+	return m.MarshalToSizedBufferVT(dAtA[:size])
+}
+
+func (m *ReloadStoreResponse) MarshalToSizedBufferVT(dAtA []byte) (int, error) {
+	if m == nil {
+		return 0, nil
+	}
+	i := len(dAtA)
+	_ = i
+	var l int
+	_ = l
+	if m.unknownFields != nil {
+		i -= len(m.unknownFields)
+		copy(dAtA[i:], m.unknownFields)
+	}
+	return len(dAtA) - i, nil
+}
+
 func encodeVarint(dAtA []byte, offset int, v uint64) int {
 	offset -= sov(v)
 	base := offset
@@ -2803,6 +2836,18 @@ func (m *DeleteSchemaResponse) SizeVT() (n int) {
 	return n
 }
 
+func (m *ReloadStoreResponse) SizeVT() (n int) {
+	if m == nil {
+		return 0
+	}
+	var l int
+	_ = l
+	if m.unknownFields != nil {
+		n += len(m.unknownFields)
+	}
+	return n
+}
+
 func sov(x uint64) (n int) {
 	return (bits.Len64(x|1) + 6) / 7
 }
@@ -7023,6 +7068,57 @@ func (m *DeleteSchemaResponse) UnmarshalVT(dAtA []byte) error {
 	}
 	return nil
 }
+func (m *ReloadStoreResponse) UnmarshalVT(dAtA []byte) error {
+	l := len(dAtA)
+	iNdEx := 0
+	for iNdEx < l {
+		preIndex := iNdEx
+		var wire uint64
+		for shift := uint(0); ; shift += 7 {
+			if shift >= 64 {
+				return ErrIntOverflow
+			}
+			if iNdEx >= l {
+				return io.ErrUnexpectedEOF
+			}
+			b := dAtA[iNdEx]
+			iNdEx++
+			wire |= uint64(b&0x7F) << shift
+			if b < 0x80 {
+				break
+			}
+		}
+		fieldNum := int32(wire >> 3)
+		wireType := int(wire & 0x7)
+		if wireType == 4 {
+			return fmt.Errorf("proto: ReloadStoreResponse: wiretype end group for non-group")
+		}
+		if fieldNum <= 0 {
+			return fmt.Errorf("proto: ReloadStoreResponse: illegal tag %d (wire type %d)", fieldNum, wire)
+		}
+		switch fieldNum {
+		default:
+			iNdEx = preIndex
+			skippy, err := skip(dAtA[iNdEx:])
+			if err != nil {
+				return err
+			}
+			if (skippy < 0) || (iNdEx+skippy) < 0 {
+				return ErrInvalidLength
+			}
+			if (iNdEx + skippy) > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...)
+			iNdEx += skippy
+		}
+	}
+
+	if iNdEx > l {
+		return io.ErrUnexpectedEOF
+	}
+	return nil
+}
 func skip(dAtA []byte) (n int, err error) {
 	l := len(dAtA)
 	iNdEx := 0
diff --git a/api/genpb/cerbos/svc/v1/svc.pb.go b/api/genpb/cerbos/svc/v1/svc.pb.go
index bd081db3..b7b98a7b 100644
--- a/api/genpb/cerbos/svc/v1/svc.pb.go
+++ b/api/genpb/cerbos/svc/v1/svc.pb.go
@@ -101,7 +101,7 @@ var file_cerbos_svc_v1_svc_proto_rawDesc = []byte{
 	0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x3a, 0x01, 0x2a, 0x1a, 0x21, 0x92, 0x41,
 	0x1e, 0x12, 0x1c, 0x43, 0x65, 0x72, 0x62, 0x6f, 0x73, 0x20, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79,
 	0x20, 0x44, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x32,
-	0x91, 0x0b, 0x0a, 0x12, 0x43, 0x65, 0x72, 0x62, 0x6f, 0x73, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x53,
+	0xb0, 0x0c, 0x0a, 0x12, 0x43, 0x65, 0x72, 0x62, 0x6f, 0x73, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x53,
 	0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0xc9, 0x01, 0x0a, 0x11, 0x41, 0x64, 0x64, 0x4f, 0x72,
 	0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x2b, 0x2e, 0x63,
 	0x65, 0x72, 0x62, 0x6f, 0x73, 0x2e, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x76, 0x31,
@@ -187,63 +187,73 @@ var file_cerbos_svc_v1_svc_proto_rawDesc = []byte{
 	0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x38, 0x92, 0x41, 0x20, 0x12, 0x0d, 0x44, 0x65, 0x6c, 0x65,
 	0x74, 0x65, 0x20, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x62, 0x0f, 0x0a, 0x0d, 0x0a, 0x09, 0x42,
 	0x61, 0x73, 0x69, 0x63, 0x41, 0x75, 0x74, 0x68, 0x12, 0x00, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x0f,
-	0x2a, 0x0d, 0x2f, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2f, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x1a,
-	0x22, 0x92, 0x41, 0x1f, 0x12, 0x1d, 0x43, 0x65, 0x72, 0x62, 0x6f, 0x73, 0x20, 0x61, 0x64, 0x6d,
-	0x69, 0x6e, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x73, 0x65, 0x72, 0x76,
-	0x69, 0x63, 0x65, 0x32, 0xe5, 0x04, 0x0a, 0x17, 0x43, 0x65, 0x72, 0x62, 0x6f, 0x73, 0x50, 0x6c,
-	0x61, 0x79, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12,
-	0x97, 0x01, 0x0a, 0x12, 0x50, 0x6c, 0x61, 0x79, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x56, 0x61,
-	0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x12, 0x2c, 0x2e, 0x63, 0x65, 0x72, 0x62, 0x6f, 0x73, 0x2e,
-	0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x6c, 0x61, 0x79, 0x67,
-	0x72, 0x6f, 0x75, 0x6e, 0x64, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71,
-	0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x63, 0x65, 0x72, 0x62, 0x6f, 0x73, 0x2e, 0x72, 0x65,
-	0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x6c, 0x61, 0x79, 0x67, 0x72,
-	0x6f, 0x75, 0x6e, 0x64, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70,
-	0x6f, 0x6e, 0x73, 0x65, 0x22, 0x23, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1d, 0x22, 0x18, 0x2f, 0x61,
-	0x70, 0x69, 0x2f, 0x70, 0x6c, 0x61, 0x79, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2f, 0x76, 0x61,
-	0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x3a, 0x01, 0x2a, 0x12, 0x87, 0x01, 0x0a, 0x0e, 0x50, 0x6c,
-	0x61, 0x79, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x54, 0x65, 0x73, 0x74, 0x12, 0x28, 0x2e, 0x63,
-	0x65, 0x72, 0x62, 0x6f, 0x73, 0x2e, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x76, 0x31,
-	0x2e, 0x50, 0x6c, 0x61, 0x79, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x54, 0x65, 0x73, 0x74, 0x52,
-	0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x63, 0x65, 0x72, 0x62, 0x6f, 0x73, 0x2e,
-	0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x6c, 0x61, 0x79,
-	0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
-	0x73, 0x65, 0x22, 0x1f, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x19, 0x22, 0x14, 0x2f, 0x61, 0x70, 0x69,
-	0x2f, 0x70, 0x6c, 0x61, 0x79, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2f, 0x74, 0x65, 0x73, 0x74,
-	0x3a, 0x01, 0x2a, 0x12, 0x97, 0x01, 0x0a, 0x12, 0x50, 0x6c, 0x61, 0x79, 0x67, 0x72, 0x6f, 0x75,
-	0x6e, 0x64, 0x45, 0x76, 0x61, 0x6c, 0x75, 0x61, 0x74, 0x65, 0x12, 0x2c, 0x2e, 0x63, 0x65, 0x72,
-	0x62, 0x6f, 0x73, 0x2e, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x50,
-	0x6c, 0x61, 0x79, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x45, 0x76, 0x61, 0x6c, 0x75, 0x61, 0x74,
-	0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x63, 0x65, 0x72, 0x62, 0x6f,
-	0x73, 0x2e, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x6c,
+	0x2a, 0x0d, 0x2f, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x2f, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x12,
+	0x9c, 0x01, 0x0a, 0x0b, 0x52, 0x65, 0x6c, 0x6f, 0x61, 0x64, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x12,
+	0x25, 0x2e, 0x63, 0x65, 0x72, 0x62, 0x6f, 0x73, 0x2e, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
+	0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6c, 0x6f, 0x61, 0x64, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x52,
+	0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x63, 0x65, 0x72, 0x62, 0x6f, 0x73, 0x2e,
+	0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x6c, 0x6f,
+	0x61, 0x64, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22,
+	0x3d, 0x92, 0x41, 0x1f, 0x12, 0x0c, 0x52, 0x65, 0x6c, 0x6f, 0x61, 0x64, 0x20, 0x73, 0x74, 0x6f,
+	0x72, 0x65, 0x62, 0x0f, 0x0a, 0x0d, 0x0a, 0x09, 0x42, 0x61, 0x73, 0x69, 0x63, 0x41, 0x75, 0x74,
+	0x68, 0x12, 0x00, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x15, 0x12, 0x13, 0x2f, 0x61, 0x64, 0x6d, 0x69,
+	0x6e, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2f, 0x72, 0x65, 0x6c, 0x6f, 0x61, 0x64, 0x1a, 0x22,
+	0x92, 0x41, 0x1f, 0x12, 0x1d, 0x43, 0x65, 0x72, 0x62, 0x6f, 0x73, 0x20, 0x61, 0x64, 0x6d, 0x69,
+	0x6e, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x73, 0x65, 0x72, 0x76, 0x69,
+	0x63, 0x65, 0x32, 0xe5, 0x04, 0x0a, 0x17, 0x43, 0x65, 0x72, 0x62, 0x6f, 0x73, 0x50, 0x6c, 0x61,
+	0x79, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x97,
+	0x01, 0x0a, 0x12, 0x50, 0x6c, 0x61, 0x79, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x56, 0x61, 0x6c,
+	0x69, 0x64, 0x61, 0x74, 0x65, 0x12, 0x2c, 0x2e, 0x63, 0x65, 0x72, 0x62, 0x6f, 0x73, 0x2e, 0x72,
+	0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x6c, 0x61, 0x79, 0x67, 0x72,
+	0x6f, 0x75, 0x6e, 0x64, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75,
+	0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x63, 0x65, 0x72, 0x62, 0x6f, 0x73, 0x2e, 0x72, 0x65, 0x73,
+	0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x6c, 0x61, 0x79, 0x67, 0x72, 0x6f,
+	0x75, 0x6e, 0x64, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f,
+	0x6e, 0x73, 0x65, 0x22, 0x23, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1d, 0x22, 0x18, 0x2f, 0x61, 0x70,
+	0x69, 0x2f, 0x70, 0x6c, 0x61, 0x79, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2f, 0x76, 0x61, 0x6c,
+	0x69, 0x64, 0x61, 0x74, 0x65, 0x3a, 0x01, 0x2a, 0x12, 0x87, 0x01, 0x0a, 0x0e, 0x50, 0x6c, 0x61,
+	0x79, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x54, 0x65, 0x73, 0x74, 0x12, 0x28, 0x2e, 0x63, 0x65,
+	0x72, 0x62, 0x6f, 0x73, 0x2e, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e,
+	0x50, 0x6c, 0x61, 0x79, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65,
+	0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x63, 0x65, 0x72, 0x62, 0x6f, 0x73, 0x2e, 0x72,
+	0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x6c, 0x61, 0x79, 0x67,
+	0x72, 0x6f, 0x75, 0x6e, 0x64, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
+	0x65, 0x22, 0x1f, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x19, 0x22, 0x14, 0x2f, 0x61, 0x70, 0x69, 0x2f,
+	0x70, 0x6c, 0x61, 0x79, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x2f, 0x74, 0x65, 0x73, 0x74, 0x3a,
+	0x01, 0x2a, 0x12, 0x97, 0x01, 0x0a, 0x12, 0x50, 0x6c, 0x61, 0x79, 0x67, 0x72, 0x6f, 0x75, 0x6e,
+	0x64, 0x45, 0x76, 0x61, 0x6c, 0x75, 0x61, 0x74, 0x65, 0x12, 0x2c, 0x2e, 0x63, 0x65, 0x72, 0x62,
+	0x6f, 0x73, 0x2e, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x6c,
 	0x61, 0x79, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x45, 0x76, 0x61, 0x6c, 0x75, 0x61, 0x74, 0x65,
-	0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x23, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1d,
-	0x22, 0x18, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x6c, 0x61, 0x79, 0x67, 0x72, 0x6f, 0x75, 0x6e,
-	0x64, 0x2f, 0x65, 0x76, 0x61, 0x6c, 0x75, 0x61, 0x74, 0x65, 0x3a, 0x01, 0x2a, 0x12, 0x8b, 0x01,
-	0x0a, 0x0f, 0x50, 0x6c, 0x61, 0x79, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x50, 0x72, 0x6f, 0x78,
-	0x79, 0x12, 0x29, 0x2e, 0x63, 0x65, 0x72, 0x62, 0x6f, 0x73, 0x2e, 0x72, 0x65, 0x71, 0x75, 0x65,
-	0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x6c, 0x61, 0x79, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64,
-	0x50, 0x72, 0x6f, 0x78, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x63,
-	0x65, 0x72, 0x62, 0x6f, 0x73, 0x2e, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x76,
-	0x31, 0x2e, 0x50, 0x6c, 0x61, 0x79, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x50, 0x72, 0x6f, 0x78,
-	0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x20, 0x82, 0xd3, 0xe4, 0x93, 0x02,
-	0x1a, 0x22, 0x15, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x6c, 0x61, 0x79, 0x67, 0x72, 0x6f, 0x75,
-	0x6e, 0x64, 0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x3a, 0x01, 0x2a, 0x42, 0xe1, 0x01, 0x0a, 0x15,
-	0x64, 0x65, 0x76, 0x2e, 0x63, 0x65, 0x72, 0x62, 0x6f, 0x73, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76,
-	0x31, 0x2e, 0x73, 0x76, 0x63, 0x5a, 0x36, 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, 0x73, 0x76, 0x63, 0x2f, 0x76, 0x31, 0x3b, 0x73, 0x76, 0x63, 0x76, 0x31, 0xaa, 0x02, 0x11,
-	0x43, 0x65, 0x72, 0x62, 0x6f, 0x73, 0x2e, 0x41, 0x70, 0x69, 0x2e, 0x56, 0x31, 0x2e, 0x53, 0x76,
-	0x63, 0x92, 0x41, 0x7b, 0x12, 0x3f, 0x0a, 0x06, 0x43, 0x65, 0x72, 0x62, 0x6f, 0x73, 0x22, 0x2d,
-	0x0a, 0x06, 0x43, 0x65, 0x72, 0x62, 0x6f, 0x73, 0x12, 0x12, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a,
-	0x2f, 0x2f, 0x63, 0x65, 0x72, 0x62, 0x6f, 0x73, 0x2e, 0x64, 0x65, 0x76, 0x1a, 0x0f, 0x69, 0x6e,
-	0x66, 0x6f, 0x40, 0x63, 0x65, 0x72, 0x62, 0x6f, 0x73, 0x2e, 0x64, 0x65, 0x76, 0x32, 0x06, 0x6c,
-	0x61, 0x74, 0x65, 0x73, 0x74, 0x2a, 0x01, 0x02, 0x32, 0x10, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63,
-	0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6a, 0x73, 0x6f, 0x6e, 0x3a, 0x10, 0x61, 0x70, 0x70, 0x6c,
-	0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6a, 0x73, 0x6f, 0x6e, 0x5a, 0x11, 0x0a, 0x0f,
-	0x0a, 0x09, 0x42, 0x61, 0x73, 0x69, 0x63, 0x41, 0x75, 0x74, 0x68, 0x12, 0x02, 0x08, 0x01, 0x62,
-	0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+	0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2e, 0x2e, 0x63, 0x65, 0x72, 0x62, 0x6f, 0x73,
+	0x2e, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x6c, 0x61,
+	0x79, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x45, 0x76, 0x61, 0x6c, 0x75, 0x61, 0x74, 0x65, 0x52,
+	0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x23, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1d, 0x22,
+	0x18, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x6c, 0x61, 0x79, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64,
+	0x2f, 0x65, 0x76, 0x61, 0x6c, 0x75, 0x61, 0x74, 0x65, 0x3a, 0x01, 0x2a, 0x12, 0x8b, 0x01, 0x0a,
+	0x0f, 0x50, 0x6c, 0x61, 0x79, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x50, 0x72, 0x6f, 0x78, 0x79,
+	0x12, 0x29, 0x2e, 0x63, 0x65, 0x72, 0x62, 0x6f, 0x73, 0x2e, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73,
+	0x74, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x6c, 0x61, 0x79, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x50,
+	0x72, 0x6f, 0x78, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x63, 0x65,
+	0x72, 0x62, 0x6f, 0x73, 0x2e, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x76, 0x31,
+	0x2e, 0x50, 0x6c, 0x61, 0x79, 0x67, 0x72, 0x6f, 0x75, 0x6e, 0x64, 0x50, 0x72, 0x6f, 0x78, 0x79,
+	0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x20, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1a,
+	0x22, 0x15, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x6c, 0x61, 0x79, 0x67, 0x72, 0x6f, 0x75, 0x6e,
+	0x64, 0x2f, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x3a, 0x01, 0x2a, 0x42, 0xe1, 0x01, 0x0a, 0x15, 0x64,
+	0x65, 0x76, 0x2e, 0x63, 0x65, 0x72, 0x62, 0x6f, 0x73, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x76, 0x31,
+	0x2e, 0x73, 0x76, 0x63, 0x5a, 0x36, 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,
+	0x73, 0x76, 0x63, 0x2f, 0x76, 0x31, 0x3b, 0x73, 0x76, 0x63, 0x76, 0x31, 0xaa, 0x02, 0x11, 0x43,
+	0x65, 0x72, 0x62, 0x6f, 0x73, 0x2e, 0x41, 0x70, 0x69, 0x2e, 0x56, 0x31, 0x2e, 0x53, 0x76, 0x63,
+	0x92, 0x41, 0x7b, 0x12, 0x3f, 0x0a, 0x06, 0x43, 0x65, 0x72, 0x62, 0x6f, 0x73, 0x22, 0x2d, 0x0a,
+	0x06, 0x43, 0x65, 0x72, 0x62, 0x6f, 0x73, 0x12, 0x12, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f,
+	0x2f, 0x63, 0x65, 0x72, 0x62, 0x6f, 0x73, 0x2e, 0x64, 0x65, 0x76, 0x1a, 0x0f, 0x69, 0x6e, 0x66,
+	0x6f, 0x40, 0x63, 0x65, 0x72, 0x62, 0x6f, 0x73, 0x2e, 0x64, 0x65, 0x76, 0x32, 0x06, 0x6c, 0x61,
+	0x74, 0x65, 0x73, 0x74, 0x2a, 0x01, 0x02, 0x32, 0x10, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61,
+	0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6a, 0x73, 0x6f, 0x6e, 0x3a, 0x10, 0x61, 0x70, 0x70, 0x6c, 0x69,
+	0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6a, 0x73, 0x6f, 0x6e, 0x5a, 0x11, 0x0a, 0x0f, 0x0a,
+	0x09, 0x42, 0x61, 0x73, 0x69, 0x63, 0x41, 0x75, 0x74, 0x68, 0x12, 0x02, 0x08, 0x01, 0x62, 0x06,
+	0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
 }
 
 var file_cerbos_svc_v1_svc_proto_goTypes = []interface{}{
@@ -259,26 +269,28 @@ var file_cerbos_svc_v1_svc_proto_goTypes = []interface{}{
 	(*v1.ListSchemasRequest)(nil),           // 9: cerbos.request.v1.ListSchemasRequest
 	(*v1.GetSchemaRequest)(nil),             // 10: cerbos.request.v1.GetSchemaRequest
 	(*v1.DeleteSchemaRequest)(nil),          // 11: cerbos.request.v1.DeleteSchemaRequest
-	(*v1.PlaygroundValidateRequest)(nil),    // 12: cerbos.request.v1.PlaygroundValidateRequest
-	(*v1.PlaygroundTestRequest)(nil),        // 13: cerbos.request.v1.PlaygroundTestRequest
-	(*v1.PlaygroundEvaluateRequest)(nil),    // 14: cerbos.request.v1.PlaygroundEvaluateRequest
-	(*v1.PlaygroundProxyRequest)(nil),       // 15: cerbos.request.v1.PlaygroundProxyRequest
-	(*v11.CheckResourceSetResponse)(nil),    // 16: cerbos.response.v1.CheckResourceSetResponse
-	(*v11.CheckResourceBatchResponse)(nil),  // 17: cerbos.response.v1.CheckResourceBatchResponse
-	(*v11.ServerInfoResponse)(nil),          // 18: cerbos.response.v1.ServerInfoResponse
-	(*v11.ResourcesQueryPlanResponse)(nil),  // 19: cerbos.response.v1.ResourcesQueryPlanResponse
-	(*v11.AddOrUpdatePolicyResponse)(nil),   // 20: cerbos.response.v1.AddOrUpdatePolicyResponse
-	(*v11.ListPoliciesResponse)(nil),        // 21: cerbos.response.v1.ListPoliciesResponse
-	(*v11.GetPolicyResponse)(nil),           // 22: cerbos.response.v1.GetPolicyResponse
-	(*v11.ListAuditLogEntriesResponse)(nil), // 23: cerbos.response.v1.ListAuditLogEntriesResponse
-	(*v11.AddOrUpdateSchemaResponse)(nil),   // 24: cerbos.response.v1.AddOrUpdateSchemaResponse
-	(*v11.ListSchemasResponse)(nil),         // 25: cerbos.response.v1.ListSchemasResponse
-	(*v11.GetSchemaResponse)(nil),           // 26: cerbos.response.v1.GetSchemaResponse
-	(*v11.DeleteSchemaResponse)(nil),        // 27: cerbos.response.v1.DeleteSchemaResponse
-	(*v11.PlaygroundValidateResponse)(nil),  // 28: cerbos.response.v1.PlaygroundValidateResponse
-	(*v11.PlaygroundTestResponse)(nil),      // 29: cerbos.response.v1.PlaygroundTestResponse
-	(*v11.PlaygroundEvaluateResponse)(nil),  // 30: cerbos.response.v1.PlaygroundEvaluateResponse
-	(*v11.PlaygroundProxyResponse)(nil),     // 31: cerbos.response.v1.PlaygroundProxyResponse
+	(*v1.ReloadStoreRequest)(nil),           // 12: cerbos.request.v1.ReloadStoreRequest
+	(*v1.PlaygroundValidateRequest)(nil),    // 13: cerbos.request.v1.PlaygroundValidateRequest
+	(*v1.PlaygroundTestRequest)(nil),        // 14: cerbos.request.v1.PlaygroundTestRequest
+	(*v1.PlaygroundEvaluateRequest)(nil),    // 15: cerbos.request.v1.PlaygroundEvaluateRequest
+	(*v1.PlaygroundProxyRequest)(nil),       // 16: cerbos.request.v1.PlaygroundProxyRequest
+	(*v11.CheckResourceSetResponse)(nil),    // 17: cerbos.response.v1.CheckResourceSetResponse
+	(*v11.CheckResourceBatchResponse)(nil),  // 18: cerbos.response.v1.CheckResourceBatchResponse
+	(*v11.ServerInfoResponse)(nil),          // 19: cerbos.response.v1.ServerInfoResponse
+	(*v11.ResourcesQueryPlanResponse)(nil),  // 20: cerbos.response.v1.ResourcesQueryPlanResponse
+	(*v11.AddOrUpdatePolicyResponse)(nil),   // 21: cerbos.response.v1.AddOrUpdatePolicyResponse
+	(*v11.ListPoliciesResponse)(nil),        // 22: cerbos.response.v1.ListPoliciesResponse
+	(*v11.GetPolicyResponse)(nil),           // 23: cerbos.response.v1.GetPolicyResponse
+	(*v11.ListAuditLogEntriesResponse)(nil), // 24: cerbos.response.v1.ListAuditLogEntriesResponse
+	(*v11.AddOrUpdateSchemaResponse)(nil),   // 25: cerbos.response.v1.AddOrUpdateSchemaResponse
+	(*v11.ListSchemasResponse)(nil),         // 26: cerbos.response.v1.ListSchemasResponse
+	(*v11.GetSchemaResponse)(nil),           // 27: cerbos.response.v1.GetSchemaResponse
+	(*v11.DeleteSchemaResponse)(nil),        // 28: cerbos.response.v1.DeleteSchemaResponse
+	(*v11.ReloadStoreResponse)(nil),         // 29: cerbos.response.v1.ReloadStoreResponse
+	(*v11.PlaygroundValidateResponse)(nil),  // 30: cerbos.response.v1.PlaygroundValidateResponse
+	(*v11.PlaygroundTestResponse)(nil),      // 31: cerbos.response.v1.PlaygroundTestResponse
+	(*v11.PlaygroundEvaluateResponse)(nil),  // 32: cerbos.response.v1.PlaygroundEvaluateResponse
+	(*v11.PlaygroundProxyResponse)(nil),     // 33: cerbos.response.v1.PlaygroundProxyResponse
 }
 var file_cerbos_svc_v1_svc_proto_depIdxs = []int32{
 	0,  // 0: cerbos.svc.v1.CerbosService.CheckResourceSet:input_type -> cerbos.request.v1.CheckResourceSetRequest
@@ -293,28 +305,30 @@ var file_cerbos_svc_v1_svc_proto_depIdxs = []int32{
 	9,  // 9: cerbos.svc.v1.CerbosAdminService.ListSchemas:input_type -> cerbos.request.v1.ListSchemasRequest
 	10, // 10: cerbos.svc.v1.CerbosAdminService.GetSchema:input_type -> cerbos.request.v1.GetSchemaRequest
 	11, // 11: cerbos.svc.v1.CerbosAdminService.DeleteSchema:input_type -> cerbos.request.v1.DeleteSchemaRequest
-	12, // 12: cerbos.svc.v1.CerbosPlaygroundService.PlaygroundValidate:input_type -> cerbos.request.v1.PlaygroundValidateRequest
-	13, // 13: cerbos.svc.v1.CerbosPlaygroundService.PlaygroundTest:input_type -> cerbos.request.v1.PlaygroundTestRequest
-	14, // 14: cerbos.svc.v1.CerbosPlaygroundService.PlaygroundEvaluate:input_type -> cerbos.request.v1.PlaygroundEvaluateRequest
-	15, // 15: cerbos.svc.v1.CerbosPlaygroundService.PlaygroundProxy:input_type -> cerbos.request.v1.PlaygroundProxyRequest
-	16, // 16: cerbos.svc.v1.CerbosService.CheckResourceSet:output_type -> cerbos.response.v1.CheckResourceSetResponse
-	17, // 17: cerbos.svc.v1.CerbosService.CheckResourceBatch:output_type -> cerbos.response.v1.CheckResourceBatchResponse
-	18, // 18: cerbos.svc.v1.CerbosService.ServerInfo:output_type -> cerbos.response.v1.ServerInfoResponse
-	19, // 19: cerbos.svc.v1.CerbosService.ResourcesQueryPlan:output_type -> cerbos.response.v1.ResourcesQueryPlanResponse
-	20, // 20: cerbos.svc.v1.CerbosAdminService.AddOrUpdatePolicy:output_type -> cerbos.response.v1.AddOrUpdatePolicyResponse
-	21, // 21: cerbos.svc.v1.CerbosAdminService.ListPolicies:output_type -> cerbos.response.v1.ListPoliciesResponse
-	22, // 22: cerbos.svc.v1.CerbosAdminService.GetPolicy:output_type -> cerbos.response.v1.GetPolicyResponse
-	23, // 23: cerbos.svc.v1.CerbosAdminService.ListAuditLogEntries:output_type -> cerbos.response.v1.ListAuditLogEntriesResponse
-	24, // 24: cerbos.svc.v1.CerbosAdminService.AddOrUpdateSchema:output_type -> cerbos.response.v1.AddOrUpdateSchemaResponse
-	25, // 25: cerbos.svc.v1.CerbosAdminService.ListSchemas:output_type -> cerbos.response.v1.ListSchemasResponse
-	26, // 26: cerbos.svc.v1.CerbosAdminService.GetSchema:output_type -> cerbos.response.v1.GetSchemaResponse
-	27, // 27: cerbos.svc.v1.CerbosAdminService.DeleteSchema:output_type -> cerbos.response.v1.DeleteSchemaResponse
-	28, // 28: cerbos.svc.v1.CerbosPlaygroundService.PlaygroundValidate:output_type -> cerbos.response.v1.PlaygroundValidateResponse
-	29, // 29: cerbos.svc.v1.CerbosPlaygroundService.PlaygroundTest:output_type -> cerbos.response.v1.PlaygroundTestResponse
-	30, // 30: cerbos.svc.v1.CerbosPlaygroundService.PlaygroundEvaluate:output_type -> cerbos.response.v1.PlaygroundEvaluateResponse
-	31, // 31: cerbos.svc.v1.CerbosPlaygroundService.PlaygroundProxy:output_type -> cerbos.response.v1.PlaygroundProxyResponse
-	16, // [16:32] is the sub-list for method output_type
-	0,  // [0:16] is the sub-list for method input_type
+	12, // 12: cerbos.svc.v1.CerbosAdminService.ReloadStore:input_type -> cerbos.request.v1.ReloadStoreRequest
+	13, // 13: cerbos.svc.v1.CerbosPlaygroundService.PlaygroundValidate:input_type -> cerbos.request.v1.PlaygroundValidateRequest
+	14, // 14: cerbos.svc.v1.CerbosPlaygroundService.PlaygroundTest:input_type -> cerbos.request.v1.PlaygroundTestRequest
+	15, // 15: cerbos.svc.v1.CerbosPlaygroundService.PlaygroundEvaluate:input_type -> cerbos.request.v1.PlaygroundEvaluateRequest
+	16, // 16: cerbos.svc.v1.CerbosPlaygroundService.PlaygroundProxy:input_type -> cerbos.request.v1.PlaygroundProxyRequest
+	17, // 17: cerbos.svc.v1.CerbosService.CheckResourceSet:output_type -> cerbos.response.v1.CheckResourceSetResponse
+	18, // 18: cerbos.svc.v1.CerbosService.CheckResourceBatch:output_type -> cerbos.response.v1.CheckResourceBatchResponse
+	19, // 19: cerbos.svc.v1.CerbosService.ServerInfo:output_type -> cerbos.response.v1.ServerInfoResponse
+	20, // 20: cerbos.svc.v1.CerbosService.ResourcesQueryPlan:output_type -> cerbos.response.v1.ResourcesQueryPlanResponse
+	21, // 21: cerbos.svc.v1.CerbosAdminService.AddOrUpdatePolicy:output_type -> cerbos.response.v1.AddOrUpdatePolicyResponse
+	22, // 22: cerbos.svc.v1.CerbosAdminService.ListPolicies:output_type -> cerbos.response.v1.ListPoliciesResponse
+	23, // 23: cerbos.svc.v1.CerbosAdminService.GetPolicy:output_type -> cerbos.response.v1.GetPolicyResponse
+	24, // 24: cerbos.svc.v1.CerbosAdminService.ListAuditLogEntries:output_type -> cerbos.response.v1.ListAuditLogEntriesResponse
+	25, // 25: cerbos.svc.v1.CerbosAdminService.AddOrUpdateSchema:output_type -> cerbos.response.v1.AddOrUpdateSchemaResponse
+	26, // 26: cerbos.svc.v1.CerbosAdminService.ListSchemas:output_type -> cerbos.response.v1.ListSchemasResponse
+	27, // 27: cerbos.svc.v1.CerbosAdminService.GetSchema:output_type -> cerbos.response.v1.GetSchemaResponse
+	28, // 28: cerbos.svc.v1.CerbosAdminService.DeleteSchema:output_type -> cerbos.response.v1.DeleteSchemaResponse
+	29, // 29: cerbos.svc.v1.CerbosAdminService.ReloadStore:output_type -> cerbos.response.v1.ReloadStoreResponse
+	30, // 30: cerbos.svc.v1.CerbosPlaygroundService.PlaygroundValidate:output_type -> cerbos.response.v1.PlaygroundValidateResponse
+	31, // 31: cerbos.svc.v1.CerbosPlaygroundService.PlaygroundTest:output_type -> cerbos.response.v1.PlaygroundTestResponse
+	32, // 32: cerbos.svc.v1.CerbosPlaygroundService.PlaygroundEvaluate:output_type -> cerbos.response.v1.PlaygroundEvaluateResponse
+	33, // 33: cerbos.svc.v1.CerbosPlaygroundService.PlaygroundProxy:output_type -> cerbos.response.v1.PlaygroundProxyResponse
+	17, // [17:34] is the sub-list for method output_type
+	0,  // [0:17] is the sub-list for method input_type
 	0,  // [0:0] is the sub-list for extension type_name
 	0,  // [0:0] is the sub-list for extension extendee
 	0,  // [0:0] is the sub-list for field type_name
diff --git a/api/genpb/cerbos/svc/v1/svc.pb.gw.go b/api/genpb/cerbos/svc/v1/svc.pb.gw.go
index 5702b8b8..f934e5af 100644
--- a/api/genpb/cerbos/svc/v1/svc.pb.gw.go
+++ b/api/genpb/cerbos/svc/v1/svc.pb.gw.go
@@ -480,6 +480,42 @@ func local_request_CerbosAdminService_DeleteSchema_0(ctx context.Context, marsha
 
 }
 
+var (
+	filter_CerbosAdminService_ReloadStore_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)}
+)
+
+func request_CerbosAdminService_ReloadStore_0(ctx context.Context, marshaler runtime.Marshaler, client CerbosAdminServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
+	var protoReq requestv1.ReloadStoreRequest
+	var metadata runtime.ServerMetadata
+
+	if err := req.ParseForm(); err != nil {
+		return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
+	}
+	if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_CerbosAdminService_ReloadStore_0); err != nil {
+		return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
+	}
+
+	msg, err := client.ReloadStore(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
+	return msg, metadata, err
+
+}
+
+func local_request_CerbosAdminService_ReloadStore_0(ctx context.Context, marshaler runtime.Marshaler, server CerbosAdminServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
+	var protoReq requestv1.ReloadStoreRequest
+	var metadata runtime.ServerMetadata
+
+	if err := req.ParseForm(); err != nil {
+		return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
+	}
+	if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_CerbosAdminService_ReloadStore_0); err != nil {
+		return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
+	}
+
+	msg, err := server.ReloadStore(ctx, &protoReq)
+	return msg, metadata, err
+
+}
+
 func request_CerbosPlaygroundService_PlaygroundValidate_0(ctx context.Context, marshaler runtime.Marshaler, client CerbosPlaygroundServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
 	var protoReq requestv1.PlaygroundValidateRequest
 	var metadata runtime.ServerMetadata
@@ -950,6 +986,30 @@ func RegisterCerbosAdminServiceHandlerServer(ctx context.Context, mux *runtime.S
 
 	})
 
+	mux.Handle("GET", pattern_CerbosAdminService_ReloadStore_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
+		ctx, cancel := context.WithCancel(req.Context())
+		defer cancel()
+		var stream runtime.ServerTransportStream
+		ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
+		inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
+		var err error
+		ctx, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/cerbos.svc.v1.CerbosAdminService/ReloadStore", runtime.WithHTTPPathPattern("/admin/store/reload"))
+		if err != nil {
+			runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
+			return
+		}
+		resp, md, err := local_request_CerbosAdminService_ReloadStore_0(ctx, inboundMarshaler, server, req, pathParams)
+		md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
+		ctx = runtime.NewServerMetadataContext(ctx, md)
+		if err != nil {
+			runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
+			return
+		}
+
+		forward_CerbosAdminService_ReloadStore_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
+
+	})
+
 	return nil
 }
 
@@ -1451,6 +1511,27 @@ func RegisterCerbosAdminServiceHandlerClient(ctx context.Context, mux *runtime.S
 
 	})
 
+	mux.Handle("GET", pattern_CerbosAdminService_ReloadStore_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
+		ctx, cancel := context.WithCancel(req.Context())
+		defer cancel()
+		inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
+		var err error
+		ctx, err = runtime.AnnotateContext(ctx, mux, req, "/cerbos.svc.v1.CerbosAdminService/ReloadStore", runtime.WithHTTPPathPattern("/admin/store/reload"))
+		if err != nil {
+			runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
+			return
+		}
+		resp, md, err := request_CerbosAdminService_ReloadStore_0(ctx, inboundMarshaler, client, req, pathParams)
+		ctx = runtime.NewServerMetadataContext(ctx, md)
+		if err != nil {
+			runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
+			return
+		}
+
+		forward_CerbosAdminService_ReloadStore_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
+
+	})
+
 	return nil
 }
 
@@ -1474,6 +1555,8 @@ var (
 	pattern_CerbosAdminService_GetSchema_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"admin", "schema"}, ""))
 
 	pattern_CerbosAdminService_DeleteSchema_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"admin", "schema"}, ""))
+
+	pattern_CerbosAdminService_ReloadStore_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"admin", "store", "reload"}, ""))
 )
 
 var (
@@ -1496,6 +1579,8 @@ var (
 	forward_CerbosAdminService_GetSchema_0 = runtime.ForwardResponseMessage
 
 	forward_CerbosAdminService_DeleteSchema_0 = runtime.ForwardResponseMessage
+
+	forward_CerbosAdminService_ReloadStore_0 = runtime.ForwardResponseMessage
 )
 
 // RegisterCerbosPlaygroundServiceHandlerFromEndpoint is same as RegisterCerbosPlaygroundServiceHandler but
diff --git a/api/genpb/cerbos/svc/v1/svc_grpc.pb.go b/api/genpb/cerbos/svc/v1/svc_grpc.pb.go
index 25981730..d742d313 100644
--- a/api/genpb/cerbos/svc/v1/svc_grpc.pb.go
+++ b/api/genpb/cerbos/svc/v1/svc_grpc.pb.go
@@ -226,6 +226,7 @@ type CerbosAdminServiceClient interface {
 	ListSchemas(ctx context.Context, in *v1.ListSchemasRequest, opts ...grpc.CallOption) (*v11.ListSchemasResponse, error)
 	GetSchema(ctx context.Context, in *v1.GetSchemaRequest, opts ...grpc.CallOption) (*v11.GetSchemaResponse, error)
 	DeleteSchema(ctx context.Context, in *v1.DeleteSchemaRequest, opts ...grpc.CallOption) (*v11.DeleteSchemaResponse, error)
+	ReloadStore(ctx context.Context, in *v1.ReloadStoreRequest, opts ...grpc.CallOption) (*v11.ReloadStoreResponse, error)
 }
 
 type cerbosAdminServiceClient struct {
@@ -331,6 +332,15 @@ func (c *cerbosAdminServiceClient) DeleteSchema(ctx context.Context, in *v1.Dele
 	return out, nil
 }
 
+func (c *cerbosAdminServiceClient) ReloadStore(ctx context.Context, in *v1.ReloadStoreRequest, opts ...grpc.CallOption) (*v11.ReloadStoreResponse, error) {
+	out := new(v11.ReloadStoreResponse)
+	err := c.cc.Invoke(ctx, "/cerbos.svc.v1.CerbosAdminService/ReloadStore", in, out, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
 // CerbosAdminServiceServer is the server API for CerbosAdminService service.
 // All implementations must embed UnimplementedCerbosAdminServiceServer
 // for forward compatibility
@@ -343,6 +353,7 @@ type CerbosAdminServiceServer interface {
 	ListSchemas(context.Context, *v1.ListSchemasRequest) (*v11.ListSchemasResponse, error)
 	GetSchema(context.Context, *v1.GetSchemaRequest) (*v11.GetSchemaResponse, error)
 	DeleteSchema(context.Context, *v1.DeleteSchemaRequest) (*v11.DeleteSchemaResponse, error)
+	ReloadStore(context.Context, *v1.ReloadStoreRequest) (*v11.ReloadStoreResponse, error)
 	mustEmbedUnimplementedCerbosAdminServiceServer()
 }
 
@@ -374,6 +385,9 @@ func (UnimplementedCerbosAdminServiceServer) GetSchema(context.Context, *v1.GetS
 func (UnimplementedCerbosAdminServiceServer) DeleteSchema(context.Context, *v1.DeleteSchemaRequest) (*v11.DeleteSchemaResponse, error) {
 	return nil, status.Errorf(codes.Unimplemented, "method DeleteSchema not implemented")
 }
+func (UnimplementedCerbosAdminServiceServer) ReloadStore(context.Context, *v1.ReloadStoreRequest) (*v11.ReloadStoreResponse, error) {
+	return nil, status.Errorf(codes.Unimplemented, "method ReloadStore not implemented")
+}
 func (UnimplementedCerbosAdminServiceServer) mustEmbedUnimplementedCerbosAdminServiceServer() {}
 
 // UnsafeCerbosAdminServiceServer may be embedded to opt out of forward compatibility for this service.
@@ -534,6 +548,24 @@ func _CerbosAdminService_DeleteSchema_Handler(srv interface{}, ctx context.Conte
 	return interceptor(ctx, in, info, handler)
 }
 
+func _CerbosAdminService_ReloadStore_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(v1.ReloadStoreRequest)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(CerbosAdminServiceServer).ReloadStore(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: "/cerbos.svc.v1.CerbosAdminService/ReloadStore",
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(CerbosAdminServiceServer).ReloadStore(ctx, req.(*v1.ReloadStoreRequest))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
 // CerbosAdminService_ServiceDesc is the grpc.ServiceDesc for CerbosAdminService service.
 // It's only intended for direct use with grpc.RegisterService,
 // and not to be introspected or modified (even as a copy)
@@ -569,6 +601,10 @@ var CerbosAdminService_ServiceDesc = grpc.ServiceDesc{
 			MethodName: "DeleteSchema",
 			Handler:    _CerbosAdminService_DeleteSchema_Handler,
 		},
+		{
+			MethodName: "ReloadStore",
+			Handler:    _CerbosAdminService_ReloadStore_Handler,
+		},
 	},
 	Streams: []grpc.StreamDesc{
 		{
diff --git a/api/public/cerbos/request/v1/request.proto b/api/public/cerbos/request/v1/request.proto
index ff28acb5..1b1fdd64 100644
--- a/api/public/cerbos/request/v1/request.proto
+++ b/api/public/cerbos/request/v1/request.proto
@@ -625,3 +625,18 @@ message DeleteSchemaRequest {
     }
   ];
 }
+
+message ReloadStoreRequest {
+  option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = {
+    json_schema: {
+      description: "Reload store request"
+    }
+  };
+
+  bool wait = 1 [
+    (google.api.field_behavior) = OPTIONAL,
+    (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
+      description: "Wait until the reloading process finalizes"
+    }
+  ];
+}
diff --git a/api/public/cerbos/response/v1/response.proto b/api/public/cerbos/response/v1/response.proto
index 1138e77b..1f7fb0c4 100644
--- a/api/public/cerbos/response/v1/response.proto
+++ b/api/public/cerbos/response/v1/response.proto
@@ -381,3 +381,11 @@ message DeleteSchemaResponse {
     }
   };
 }
+
+message ReloadStoreResponse {
+  option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = {
+    json_schema: {
+      description: "Reload store response"
+    }
+  };
+}
diff --git a/api/public/cerbos/svc/v1/svc.proto b/api/public/cerbos/svc/v1/svc.proto
index 4e403998..39bcf815 100644
--- a/api/public/cerbos/svc/v1/svc.proto
+++ b/api/public/cerbos/svc/v1/svc.proto
@@ -219,6 +219,21 @@ service CerbosAdminService {
       }
     };
   }
+
+  rpc ReloadStore(cerbos.request.v1.ReloadStoreRequest) returns (cerbos.response.v1.ReloadStoreResponse) {
+    option (google.api.http) = {
+      get: "/admin/store/reload",
+    };
+    option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
+      summary: "Reload store",
+      security: {
+        security_requirement: {
+          key: "BasicAuth";
+          value: {};
+        }
+      }
+    };
+  }
 }
 
 service CerbosPlaygroundService {
diff --git a/client/admin.go b/client/admin.go
index 56a94ad6..eac50eca 100644
--- a/client/admin.go
+++ b/client/admin.go
@@ -33,6 +33,7 @@ type AdminClient interface {
 	AddOrUpdateSchema(ctx context.Context, schemas *SchemaSet) error
 	ListSchemas(ctx context.Context) ([]string, error)
 	GetSchema(ctx context.Context, ids ...string) ([]*schemav1.Schema, error)
+	ReloadStore(ctx context.Context, wait bool) error
 }
 
 // NewAdminClient creates a new admin client.
@@ -248,3 +249,19 @@ func (c *GrpcAdminClient) GetSchema(ctx context.Context, ids ...string) ([]*sche
 
 	return res.Schemas, nil
 }
+
+func (c *GrpcAdminClient) ReloadStore(ctx context.Context, wait bool) error {
+	req := &requestv1.ReloadStoreRequest{
+		Wait: wait,
+	}
+	if err := req.Validate(); err != nil {
+		return fmt.Errorf("could not validate reload store request: %w", err)
+	}
+
+	_, err := c.client.ReloadStore(ctx, req, grpc.PerRPCCredentials(c.creds))
+	if err != nil {
+		return fmt.Errorf("could not reload store: %w", err)
+	}
+
+	return nil
+}
diff --git a/cmd/cerbos/main.go b/cmd/cerbos/main.go
index 90747d61..1b570b87 100644
--- a/cmd/cerbos/main.go
+++ b/cmd/cerbos/main.go
@@ -8,6 +8,7 @@ import (
 
 	"github.com/cerbos/cerbos/cmd/cerbos/compile"
 	"github.com/cerbos/cerbos/cmd/cerbos/healthcheck"
+	"github.com/cerbos/cerbos/cmd/cerbos/repl"
 	"github.com/cerbos/cerbos/cmd/cerbos/run"
 	"github.com/cerbos/cerbos/cmd/cerbos/server"
 	"github.com/cerbos/cerbos/internal/outputcolor"
@@ -15,11 +16,13 @@ import (
 )
 
 func main() {
+	//nolint: govet
 	var cli struct {
 		Compile     compile.Cmd     `cmd:"" help:"Compile and test policies"`
 		Server      server.Cmd      `cmd:"" help:"Start Cerbos server (PDP)"`
 		Healthcheck healthcheck.Cmd `cmd:"" help:"Healthcheck utility" aliases:"hc"`
 		Run         run.Cmd         `cmd:"" help:"Run a command in the context of a Cerbos PDP"`
+		Repl        repl.Cmd        `cmd:"" help:"Start a REPL to try out conditions"`
 		Version     kong.VersionFlag
 	}
 
diff --git a/cmd/cerbos/repl/internal/banner.txt b/cmd/cerbos/repl/internal/banner.txt
new file mode 100644
index 00000000..21072471
--- /dev/null
+++ b/cmd/cerbos/repl/internal/banner.txt
@@ -0,0 +1,6 @@
+               _               
+  ___ ___ _ __| |__   ___  ___ 
+ / __/ _ \ '__| '_ \ / _ \/ __|
+| (_|  __/ |  | |_) | (_) \__ \
+ \___\___|_|  |_.__/ \___/|___/
+                               
diff --git a/cmd/cerbos/repl/internal/cel.go b/cmd/cerbos/repl/internal/cel.go
new file mode 100644
index 00000000..fa8ddcc4
--- /dev/null
+++ b/cmd/cerbos/repl/internal/cel.go
@@ -0,0 +1,60 @@
+// Copyright 2021-2022 Zenauth Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+package internal
+
+import (
+	"fmt"
+
+	enginev1 "github.com/cerbos/cerbos/api/genpb/cerbos/engine/v1"
+	"github.com/cerbos/cerbos/internal/conditions"
+	"github.com/google/cel-go/checker/decls"
+	"github.com/google/cel-go/common/types"
+	exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
+)
+
+const lastResultVar = "_"
+
+var (
+	qualifiedPrincipal = conditions.Fqn(conditions.CELPrincipalField)
+	qualifiedResource  = conditions.Fqn(conditions.CELResourceField)
+
+	specialVars = buildSpecialVarsSet()
+)
+
+func buildSpecialVarsSet() map[string]struct{} {
+	m := make(map[string]struct{}, len(conditions.StdEnvDecls)+1)
+
+	m[lastResultVar] = struct{}{}
+	for _, d := range conditions.StdEnvDecls {
+		m[d.GetName()] = struct{}{}
+	}
+
+	return m
+}
+
+func resetVarsAndDecls() (variables, map[string]*exprpb.Decl) {
+	vars := variables{}
+	for v := range specialVars {
+		vars[v] = types.NullValue
+	}
+
+	// request and variable decls are already defined in StdEnv
+	decls := map[string]*exprpb.Decl{lastResultVar: decls.NewVar(lastResultVar, decls.Dyn)}
+
+	return vars, decls
+}
+
+func getCheckInput(vars variables) (*enginev1.CheckInput, error) {
+	v, ok := vars[conditions.CELRequestIdent]
+	if !ok || v == nil || v == types.NullValue {
+		return &enginev1.CheckInput{}, nil
+	}
+
+	vv, ok := v.Value().(*enginev1.CheckInput)
+	if !ok {
+		return nil, fmt.Errorf("unexpected type %T for %q", v.Value(), conditions.CELRequestIdent)
+	}
+
+	return vv, nil
+}
diff --git a/cmd/cerbos/repl/internal/directive.go b/cmd/cerbos/repl/internal/directive.go
new file mode 100644
index 00000000..f0d5508e
--- /dev/null
+++ b/cmd/cerbos/repl/internal/directive.go
@@ -0,0 +1,51 @@
+// Copyright 2021-2022 Zenauth Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+package internal
+
+import (
+	"fmt"
+
+	participle "github.com/alecthomas/participle/v2"
+	"github.com/alecthomas/participle/v2/lexer"
+)
+
+func NewParser() (*participle.Parser, error) {
+	lex, err := lexer.New(lexer.Rules{
+		"Root": {
+			{Name: "Ident", Pattern: `[a-zA-Z]\w*(\.\w+)*`},
+			{Name: "Assign", Pattern: `=`, Action: lexer.Push("Assign")},
+			{Name: "Whitespace", Pattern: `\s+`},
+		},
+		"Assign": {
+			{Name: "Any", Pattern: `.*$`, Action: lexer.Pop()},
+		},
+	})
+	if err != nil {
+		return nil, fmt.Errorf("failed to create lexer: %w", err)
+	}
+
+	parser, err := participle.Build(&REPLDirective{},
+		participle.Lexer(lex),
+		participle.Elide("Whitespace"),
+	)
+	if err != nil {
+		return nil, fmt.Errorf("failed to create parser: %w", err)
+	}
+
+	return parser, nil
+}
+
+//nolint:govet
+type REPLDirective struct {
+	Exit  bool          `parser:"@('q'|'quit'|'exit')"`
+	Reset bool          `parser:"| @'reset'"`
+	Vars  bool          `parser:"| @'vars'"`
+	Help  bool          `parser:"| @('h' | 'help')"`
+	Let   *LetDirective `parser:"| @@"`
+}
+
+type LetDirective struct {
+	Name string `parser:"'let' @Ident"`
+	Expr string `parser:"'=' @Any"`
+}
diff --git a/cmd/cerbos/repl/internal/help.txt b/cmd/cerbos/repl/internal/help.txt
new file mode 100644
index 00000000..9bdd6b24
--- /dev/null
+++ b/cmd/cerbos/repl/internal/help.txt
@@ -0,0 +1,61 @@
+# Cerbos REPL
+
+This program provides an interactive environment to quickly develop CEL conditions to be used in Cerbos policies.
+See https://docs.cerbos.dev/cerbos/latest/policies/conditions.html for details of all available CEL functions that
+you can try out here.
+
+Type any valid CEL expression at the prompt and hit enter to evaluate it and output the result.
+
+You can also define variables, inspect current state and perform other special operation using directives.
+Directives are commands prefixed with a colon (":") character. The following directives are available:
+
+  :h                | Show this help
+  :help             | Show this help
+  :let              | Define variables
+  :q                | Exit the program
+  :quit             | Exit the program
+  :reset            | Clear all variables
+  :vars             | View defined variables
+
+## Evaluating CEL conditions
+You can type in any valid CEL expression at the prompt to immediately evaluate it.
+
+  1 + 1             | outputs 2
+  "test".charAt(1)  | outputs "e"
+
+The result of evaluating the last expression is stored in the special variable "_".
+
+  1 + 1             | outputs 2 and sets _ to 2 
+  _ + 5             | outputs 7 and sets _ to 7
+
+
+## Define variables
+You can define variables using the :let directive. These variables can then be used in subsequent expressions.
+
+  :let x = 12       | declares a variable named x with the value set to 12
+  :let y = 1 + 5    | declares a variable named y with the result of evaluating 1 + 5
+  x + y             | outputs 18
+
+You can also use the :let directive to set the special Cerbos variables "request", "request.resource", 
+"request.principal", "R", "P", "variables" and "V". In these cases, the value must be a valid JSON string.
+
+  :let request = {"principal":{"id":"john","roles":["employee"],"attr":{"scope":"foo.bar.baz.qux"}},"resource":{"id":"x1","kind":"leave_request","attr":{"scope":"foo.bar"}}}
+
+  :let P = {"id":"john","roles":["employee"],"attr":{"scope":"foo.bar.baz.qux"}}
+
+
+## View variables
+You can view the currently defined variables by using the :vars directive.
+
+  :vars             | prints all defined variables
+
+
+## Clear state
+You can destroy all variables and reset the REPL with the :reset directive.
+
+  :reset            | removes all variables and reset the REPL state.
+
+
+## Exit the REPL
+
+Type :quit, :q or :exit to exit the REPL.
diff --git a/cmd/cerbos/repl/internal/repl.go b/cmd/cerbos/repl/internal/repl.go
new file mode 100644
index 00000000..a2f13fc4
--- /dev/null
+++ b/cmd/cerbos/repl/internal/repl.go
@@ -0,0 +1,415 @@
+// Copyright 2021-2022 Zenauth Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+package internal
+
+import (
+	"encoding/json"
+	"errors"
+	"fmt"
+	"io"
+	"reflect"
+	"sort"
+	"strings"
+
+	_ "embed"
+
+	participle "github.com/alecthomas/participle/v2"
+	enginev1 "github.com/cerbos/cerbos/api/genpb/cerbos/engine/v1"
+	"github.com/cerbos/cerbos/internal/conditions"
+	"github.com/cerbos/cerbos/internal/outputcolor"
+	"github.com/cerbos/cerbos/internal/printer"
+	"github.com/cerbos/cerbos/internal/printer/colored"
+	"github.com/google/cel-go/cel"
+	"github.com/google/cel-go/checker/decls"
+	"github.com/google/cel-go/common"
+	"github.com/google/cel-go/common/types"
+	"github.com/google/cel-go/common/types/ref"
+	"github.com/google/cel-go/interpreter"
+	"github.com/peterh/liner"
+	exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
+	"google.golang.org/protobuf/encoding/protojson"
+	"google.golang.org/protobuf/proto"
+)
+
+var (
+	//go:embed banner.txt
+	banner string
+	//go:embed help.txt
+	helpText string
+
+	errExit        = errors.New("exit")
+	errInvalidExpr = errors.New("invalid expr")
+
+	listType = reflect.TypeOf([]interface{}{})
+	mapType  = reflect.TypeOf(map[string]interface{}{})
+)
+
+const (
+	commentPrefix   = '#'
+	directivePrefix = ':'
+	prompt          = "-> "
+	secondaryPrompt = "> "
+)
+
+type REPL struct {
+	vars     variables
+	decls    map[string]*exprpb.Decl
+	reader   *liner.State
+	parser   *participle.Parser
+	output   Output
+	toRefVal func(interface{}) ref.Val
+}
+
+func NewREPL(reader *liner.State, output Output) (*REPL, error) {
+	parser, err := NewParser()
+	if err != nil {
+		return nil, err
+	}
+
+	repl := &REPL{
+		reader:   reader,
+		parser:   parser,
+		output:   output,
+		toRefVal: conditions.StdEnv.TypeAdapter().NativeToValue,
+	}
+
+	return repl, repl.reset()
+}
+
+func (r *REPL) Loop() error {
+	r.output.Println(banner)
+	r.output.Println(fmt.Sprintf("Type %s to get help", colored.REPLCmd(":help")))
+	r.output.Println()
+
+	for {
+		input := r.readInput()
+		if input == "" {
+			continue
+		}
+
+		r.reader.AppendHistory(input)
+		if err := r.handleInput(input); err != nil {
+			if errors.Is(err, errExit) {
+				return nil
+			}
+
+			if !errors.Is(err, errInvalidExpr) {
+				r.output.PrintErr("Failed to process input", err)
+			}
+		}
+	}
+}
+
+func (r *REPL) readInput() string {
+	var input strings.Builder
+	currPrompt := prompt
+
+	for {
+		line, err := r.reader.Prompt(currPrompt)
+		if err != nil {
+			if errors.Is(err, io.EOF) || errors.Is(err, liner.ErrPromptAborted) {
+				return ""
+			}
+
+			r.output.PrintErr("Failed to read input", err)
+			return ""
+		}
+
+		line = strings.TrimSpace(line)
+		if line == "" {
+			return input.String()
+		}
+
+		if strings.HasSuffix(line, "\\") {
+			input.WriteString(strings.TrimSuffix(line, "\\"))
+			input.WriteString(" ")
+			currPrompt = secondaryPrompt
+		} else {
+			input.WriteString(line)
+			return input.String()
+		}
+	}
+}
+
+func (r *REPL) handleInput(input string) error {
+	switch input[0] {
+	case directivePrefix:
+		return r.processDirective(input[1:])
+	case commentPrefix:
+		return nil
+	default:
+		return r.processExpr(lastResultVar, input)
+	}
+}
+
+func (r *REPL) processDirective(line string) error {
+	var directive REPLDirective
+	if err := r.parser.ParseString("", line, &directive); err != nil {
+		return fmt.Errorf("invalid directive %q: %w", line, err)
+	}
+
+	switch {
+	case directive.Exit:
+		return errExit
+	case directive.Reset:
+		return r.reset()
+	case directive.Vars:
+		return r.showVars()
+	case directive.Help:
+		return r.help()
+	case directive.Let != nil:
+		prefix, _, _ := strings.Cut(directive.Let.Name, ".")
+		if _, ok := specialVars[prefix]; ok {
+			return r.setSpecialVar(directive.Let.Name, directive.Let.Expr)
+		}
+		return r.processExpr(directive.Let.Name, directive.Let.Expr)
+	default:
+		return fmt.Errorf("unknown directive %q", line)
+	}
+}
+
+func (r *REPL) reset() error {
+	r.vars, r.decls = resetVarsAndDecls()
+	r.output.Println()
+
+	return nil
+}
+
+func (r *REPL) help() error {
+	r.output.Println(helpText)
+	r.output.Println()
+	return nil
+}
+
+func (r *REPL) showVars() error {
+	varNames := make([]string, len(r.vars))
+	i := 0
+	for name := range r.vars {
+		varNames[i] = name
+		i++
+	}
+
+	sort.Strings(varNames)
+	for _, name := range varNames {
+		r.output.PrintResult(name, r.vars[name])
+	}
+
+	return nil
+}
+
+func (r *REPL) setSpecialVar(name, value string) error {
+	switch name {
+	case lastResultVar:
+		return fmt.Errorf("%s is a read-only variable", lastResultVar)
+
+	case conditions.CELRequestIdent:
+		request := &enginev1.CheckInput{}
+		if err := protojson.Unmarshal([]byte(value), request); err != nil {
+			return fmt.Errorf("failed to unmarhsal JSON as %q: %w", name, err)
+		}
+
+		requestVal := r.toRefVal(request)
+		r.output.PrintResult(name, requestVal)
+
+		r.vars[conditions.CELRequestIdent] = requestVal
+		r.vars[conditions.CELPrincipalAbbrev] = r.toRefVal(request.Principal)
+		r.vars[conditions.CELResourceAbbrev] = r.toRefVal(request.Resource)
+
+	case conditions.CELPrincipalAbbrev, qualifiedPrincipal:
+		request, err := getCheckInput(r.vars)
+		if err != nil {
+			return err
+		}
+
+		principal := &enginev1.Principal{}
+		if err := protojson.Unmarshal([]byte(value), principal); err != nil {
+			return fmt.Errorf("failed to unmarhsal JSON as %q: %w", name, err)
+		}
+
+		request.Principal = principal
+
+		principalVal := r.toRefVal(request.Principal)
+		r.output.PrintResult(name, principalVal)
+
+		r.vars[conditions.CELRequestIdent] = r.toRefVal(request)
+		r.vars[conditions.CELPrincipalAbbrev] = principalVal
+		r.vars[conditions.CELResourceAbbrev] = r.toRefVal(request.Resource)
+
+	case conditions.CELResourceAbbrev, qualifiedResource:
+		request, err := getCheckInput(r.vars)
+		if err != nil {
+			return err
+		}
+
+		resource := &enginev1.Resource{}
+		if err := protojson.Unmarshal([]byte(value), resource); err != nil {
+			return fmt.Errorf("failed to unmarhsal JSON as %q: %w", name, err)
+		}
+
+		request.Resource = resource
+
+		resourceVal := r.toRefVal(request.Resource)
+		r.output.PrintResult(name, resourceVal)
+
+		r.vars[conditions.CELRequestIdent] = r.toRefVal(request)
+		r.vars[conditions.CELPrincipalAbbrev] = r.toRefVal(request.Principal)
+		r.vars[conditions.CELResourceAbbrev] = resourceVal
+
+	case conditions.CELVariablesIdent, conditions.CELVariablesAbbrev:
+		var v map[string]interface{}
+		if err := json.Unmarshal([]byte(value), &v); err != nil {
+			return fmt.Errorf("failed to unmarshal JSON as %q: %w", name, err)
+		}
+
+		varsVal := r.toRefVal(v)
+		r.output.PrintResult(name, varsVal)
+
+		r.vars[conditions.CELVariablesIdent] = varsVal
+		r.vars[conditions.CELVariablesAbbrev] = varsVal
+
+	default:
+		return fmt.Errorf("setting %q is unsupported", name)
+	}
+
+	return nil
+}
+
+func (r *REPL) processExpr(name, expr string) error {
+	val, tpe, err := r.evalExpr(expr)
+	if err != nil {
+		return err
+	}
+
+	r.output.PrintResult(name, val)
+	r.vars[name] = val
+	r.decls[name] = decls.NewVar(name, tpe)
+
+	return nil
+}
+
+func (r *REPL) evalExpr(expr string) (ref.Val, *exprpb.Type, error) {
+	env, err := r.mkEnv()
+	if err != nil {
+		return nil, nil, fmt.Errorf("failed to create environment: %w", err)
+	}
+
+	ast, issues := env.Compile(expr)
+	if issues != nil && issues.Err() != nil {
+		src := common.NewTextSource(expr)
+		for _, ce := range issues.Errors() {
+			r.output.Print(colored.REPLError(ce.ToDisplayString(src)))
+		}
+		r.output.Println()
+
+		return nil, nil, errInvalidExpr
+	}
+
+	val, _, err := conditions.Eval(env, ast, r.vars)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	tpe := decls.Dyn
+	if t, ok := env.TypeProvider().FindType(val.Type().TypeName()); ok {
+		tpe = t
+	}
+
+	return val, tpe, nil
+}
+
+func (r *REPL) mkEnv() (*cel.Env, error) {
+	decls := make([]*exprpb.Decl, len(r.decls))
+	i := 0
+	for _, d := range r.decls {
+		decls[i] = d
+		i++
+	}
+
+	return conditions.StdEnv.Extend(cel.Declarations(decls...))
+}
+
+// variables is a type that provides the interpreter.Activation interface.
+type variables map[string]ref.Val
+
+func (v variables) ResolveName(name string) (interface{}, bool) {
+	val, ok := v[name]
+	return val, ok
+}
+
+func (v variables) Parent() interpreter.Activation {
+	return nil
+}
+
+type Output interface {
+	Print(string, ...interface{})
+	Println(...interface{})
+	PrintResult(string, ref.Val)
+	PrintJSON(interface{})
+	PrintErr(string, error)
+}
+
+type PrinterOutput struct {
+	*printer.Printer
+	level outputcolor.Level
+}
+
+func NewPrinterOutput(stdout, stderr io.Writer) *PrinterOutput {
+	return &PrinterOutput{
+		Printer: printer.New(stdout, stderr),
+		level:   outputcolor.DefaultLevel(),
+	}
+}
+
+func (po *PrinterOutput) Print(format string, args ...interface{}) {
+	po.Printf(format, args...)
+	po.Println()
+}
+
+func (po *PrinterOutput) PrintResult(name string, value ref.Val) {
+	po.Printf("%s = ", colored.REPLVar(name))
+
+	if types.IsPrimitiveType(value) {
+		po.PrintJSON(value.Value())
+		return
+	}
+
+	switch value.Type() {
+	case types.MapType:
+		if v, err := value.ConvertToNative(mapType); err == nil {
+			po.PrintJSON(v)
+			return
+		}
+	case types.ListType:
+		if v, err := value.ConvertToNative(listType); err == nil {
+			po.PrintJSON(v)
+			return
+		}
+	}
+
+	//nolint: ifshort
+	goVal := value.Value()
+	if v, ok := goVal.(proto.Message); ok {
+		if err := po.PrintProtoJSON(v, po.level); err != nil {
+			po.Println("<...>")
+		}
+		po.Println()
+	} else {
+		po.PrintJSON(goVal)
+	}
+}
+
+func (po *PrinterOutput) PrintJSON(obj interface{}) {
+	if err := po.Printer.PrintJSON(obj, po.level); err != nil {
+		po.Println("<...>")
+	}
+	po.Println()
+}
+
+func (po *PrinterOutput) PrintErr(msg string, err error) {
+	po.Println(colored.REPLError(fmt.Sprintf("Error: %s", msg)))
+	if err != nil {
+		po.Printf(" %v\n", err)
+	}
+	po.Println()
+}
diff --git a/cmd/cerbos/repl/repl.go b/cmd/cerbos/repl/repl.go
new file mode 100644
index 00000000..96b07d07
--- /dev/null
+++ b/cmd/cerbos/repl/repl.go
@@ -0,0 +1,106 @@
+// Copyright 2021-2022 Zenauth Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+package repl
+
+import (
+	"errors"
+	"fmt"
+	"os"
+	"path/filepath"
+
+	"github.com/adrg/xdg"
+	"github.com/alecthomas/kong"
+	"github.com/cerbos/cerbos/cmd/cerbos/repl/internal"
+	"github.com/peterh/liner"
+)
+
+type Cmd struct {
+	History string `help:"Path to history file" type:"path"`
+}
+
+func (c *Cmd) Run(k *kong.Kong) error {
+	histFile := getHistoryFile(c.History)
+
+	reader := liner.NewLiner()
+	reader.SetMultiLineMode(true)
+
+	defer func() {
+		if err := writeHistory(reader, histFile); err != nil {
+			fmt.Fprintf(os.Stderr, "Failed to persist history: %v", err)
+		}
+
+		_ = reader.Close()
+	}()
+
+	if err := loadHistory(reader, histFile); err != nil {
+		fmt.Fprintf(os.Stderr, "Failed to read history: %v", err)
+	}
+
+	r, err := internal.NewREPL(reader, internal.NewPrinterOutput(k.Stdout, k.Stderr))
+	if err != nil {
+		return fmt.Errorf("failed to initialize the REPL: %w", err)
+	}
+
+	return r.Loop()
+}
+
+func getHistoryFile(path string) string {
+	if path == "" {
+		dir := filepath.Join(xdg.DataHome, "cerbos")
+		//nolint:gomnd
+		if err := os.MkdirAll(dir, 0o744); err != nil {
+			fmt.Fprintf(os.Stderr, "Failed to create directory %q (%v): history is disabled", dir, err)
+			return ""
+		}
+
+		return filepath.Join(dir, ".cerbos_repl_history")
+	}
+
+	finfo, err := os.Stat(path)
+	if err != nil && !errors.Is(err, os.ErrNotExist) {
+		fmt.Fprintf(os.Stderr, "Failed to stat history file %q (%v): history is disabled", path, err)
+		return ""
+	}
+
+	if finfo != nil && finfo.IsDir() {
+		return filepath.Join(path, ".cerbos_repl_history")
+	}
+
+	return path
+}
+
+func loadHistory(reader *liner.State, histFile string) error {
+	if histFile == "" {
+		return nil
+	}
+
+	f, err := os.Open(histFile)
+	if err != nil {
+		if errors.Is(err, os.ErrNotExist) {
+			return nil
+		}
+
+		return err
+	}
+	defer f.Close()
+
+	_, err = reader.ReadHistory(f)
+	return err
+}
+
+func writeHistory(reader *liner.State, histFile string) error {
+	if histFile == "" {
+		return nil
+	}
+
+	f, err := os.Create(histFile)
+	if err != nil {
+		return err
+	}
+
+	defer f.Close()
+
+	_, err = reader.WriteHistory(f)
+	return err
+}
diff --git a/cmd/cerbosctl/root/root.go b/cmd/cerbosctl/root/root.go
index 4bbdb6b9..ff1b005c 100644
--- a/cmd/cerbosctl/root/root.go
+++ b/cmd/cerbosctl/root/root.go
@@ -9,6 +9,7 @@ import (
 	"github.com/cerbos/cerbos/cmd/cerbosctl/get"
 	"github.com/cerbos/cerbos/cmd/cerbosctl/internal/flagset"
 	"github.com/cerbos/cerbos/cmd/cerbosctl/put"
+	"github.com/cerbos/cerbos/cmd/cerbosctl/store"
 	"github.com/cerbos/cerbos/cmd/cerbosctl/version"
 )
 
@@ -39,6 +40,7 @@ type Cli struct {
 	Put       put.Cmd       `cmd:"" help:"Put policies or schemas"`
 	Decisions decisions.Cmd `cmd:"" help:"Interactive decision log viewer"`
 	Audit     audit.Cmd     `cmd:"" help:"View audit logs"`
+	Store     store.Cmd     `cmd:"" help:"Store operations"`
 }
 
 func (c *Cli) Help() string {
diff --git a/cmd/cerbosctl/store/reload.go b/cmd/cerbosctl/store/reload.go
new file mode 100644
index 00000000..2c9a2e1b
--- /dev/null
+++ b/cmd/cerbosctl/store/reload.go
@@ -0,0 +1,43 @@
+// Copyright 2021-2022 Zenauth Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+package store
+
+import (
+	"context"
+	"fmt"
+
+	"github.com/alecthomas/kong"
+
+	cmdclient "github.com/cerbos/cerbos/cmd/cerbosctl/internal/client"
+)
+
+const reloadCmdHelp = `# Reload the store
+cerbosctl store reload
+
+# Reload the store and wait until it finishes
+cerbosctl store reload --wait`
+
+type ReloadCmd struct {
+	Wait bool `help:""`
+}
+
+func (rc *ReloadCmd) Run(k *kong.Kong, ctx *cmdclient.Context) error {
+	_, _ = fmt.Fprint(k.Stdout, "Initiated a store reload\n")
+	err := ctx.AdminClient.ReloadStore(context.Background(), rc.Wait)
+	if err != nil {
+		return err
+	}
+
+	if rc.Wait {
+		_, _ = fmt.Fprint(k.Stdout, "Successfully reloaded the store\n")
+	} else {
+		_, _ = fmt.Fprint(k.Stdout, "Reload request submitted\n")
+	}
+
+	return nil
+}
+
+func (rc *ReloadCmd) Help() string {
+	return reloadCmdHelp
+}
diff --git a/cmd/cerbosctl/store/store.go b/cmd/cerbosctl/store/store.go
new file mode 100644
index 00000000..017371f4
--- /dev/null
+++ b/cmd/cerbosctl/store/store.go
@@ -0,0 +1,8 @@
+// Copyright 2021-2022 Zenauth Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+package store
+
+type Cmd struct {
+	Reload ReloadCmd `cmd:""`
+}
diff --git a/docs/modules/api/pages/admin_api.adoc b/docs/modules/api/pages/admin_api.adoc
index d6f94104..4ead009a 100644
--- a/docs/modules/api/pages/admin_api.adoc
+++ b/docs/modules/api/pages/admin_api.adoc
@@ -267,3 +267,36 @@ curl -k -u cerbos:cerbosAdmin -X DELETE \
 ----
 {}
 ----
+
+[#store-management]
+== Store Management
+
+=== Reload store
+
+----
+GET /admin/store/reload
+----
+
+Issue a GET request to the endpoint to force a reload of the store.
+
+.Reload the store
+[source,shell]
+----
+curl -k -u cerbos:cerbosAdmin -X GET \
+    'https://localhost:3592/admin/store/reload'
+----
+
+.Reload the store and block until it finishes
+[source,shell]
+----
+curl -k -u cerbos:cerbosAdmin -X GET \
+    'https://localhost:3592/admin/store/reload?wait=true'
+----
+
+.Response
+[source,json,linenums]
+----
+{}
+----
+
+NOTE: This endpoint requires a reloadable storage driver such as xref:configuration:storage.adoc#blob[blob], xref:configuration:storage.adoc#disk[disk] and xref:configuration:storage.adoc#git[git] to be configured.
diff --git a/docs/modules/cli/pages/cerbos.adoc b/docs/modules/cli/pages/cerbos.adoc
index dc777ec8..fe68d68b 100644
--- a/docs/modules/cli/pages/cerbos.adoc
+++ b/docs/modules/cli/pages/cerbos.adoc
@@ -9,6 +9,7 @@ This binary provides the following sub commands:
 
 `compile`:: Validate, compile and run tests on a policy repo
 `healthcheck`:: Perform a healthcheck on a Cerbos PDP
+`repl`:: An interactive REPL (read-evaluate-print-loop) for CEL conditions
 `run`:: Start a PDP and run a command within its context
 `server`:: Start the PDP server
 
@@ -123,6 +124,74 @@ manual
   --no-tls              Don't use TLS ($CERBOS_HC_NOTLS)
 ----
 
+[#repl]
+== `repl` Command
+
+The REPL is an interactive utility to experiment with xref:policies:conditions.adoc[CEL conditions] used in Cerbos policy rules. All Cerbos library functions and special variables (`request`, `R`, `P` and so on) are available in this environment. 
+
+You can type in valid CEL expressions at the prompt to instantly evaluate them.
+
+[listing]
+----
+-> 5 + 1
+_ = 6
+
+-> "test".charAt(1)
+_ = "e"
+----
+
+The special variable `_` holds the result of the last expression evaluated.
+
+[listing]
+----
+-> 5 + 5
+_ = 10
+
+-> _ * 10
+_ = 100
+----
+
+You can define variables using the `:let` directive.
+
+[listing]
+----
+-> :let x = hierarchy("a.b.c")
+x = [
+  "a",
+  "b",
+  "c"
+]
+
+-> :let y = hierarchy("a.b")
+y = [
+  "a",
+  "b"
+]
+
+-> x.immediateChildOf(y)
+_ = true
+----
+
+You can also set special variables used in Cerbos policies (`request`, `variables`, `R`, `P`, `V`) and try out CEL expressions using them.
+
+[listing]
+----
+-> :let request = {\
+>   "principal":{"id":"john","roles":["employee"],"attr":{"scope":"foo.bar.baz.qux"}},\
+>   "resource":{"id":"x1","kind":"leave_request","attr":{"scope":"foo.bar"}}\
+> }
+
+-> hierarchy(R.attr.scope).ancestorOf(hierarchy(P.attr.scope))
+_ = true
+----
+
+Type `:vars` to display the values of all the variables currently defined in the environment.
+
+Type `:help` or `:h` to display help. Type `:quit`, `:q` or `:exit` to exit the REPL. 
+
+TIP: Use the up/down arrow keys (or kbd:[Ctrl+P]/kbd:[Ctrl+N]) to navigate command history. Most of the standard line-editing commands such as kbd:[Ctrl+a], kbd:[Ctrl+h], kbd:[Ctrl+r] are supported as well. 
+
+
 [#run]
 == `run` Command
 
diff --git a/docs/modules/cli/pages/cerbosctl.adoc b/docs/modules/cli/pages/cerbosctl.adoc
index d2a2fe23..b3187bba 100644
--- a/docs/modules/cli/pages/cerbosctl.adoc
+++ b/docs/modules/cli/pages/cerbosctl.adoc
@@ -43,6 +43,7 @@ Available Commands:
   get         List or view policies and schemas
   help        Help about any command
   put         Put policies or schemas
+  store       Store operations
   version     Show cerbosctl and PDP version
 
 Flags:
@@ -265,4 +266,24 @@ cerbosctl put schema ./dir/to/schemas ./other/dir/to/schemas
 ----
 cerbosctl put schema --recursive ./dir/to/schemas
 cerbosctl put schema -R ./dir/to/schemas
+----
+
+[#store]
+== `store`
+
+Trigger operations on the policy store of the PDP
+
+[#reload]
+=== `reload`
+
+Reloads the store.
+
+.Reload the store
+----
+cerbosctl store reload
+----
+
+.Reload the store and wait until it finishes
+----
+cerbosctl store reload --wait`
 ----
\ No newline at end of file
diff --git a/docs/modules/configuration/pages/storage.adoc b/docs/modules/configuration/pages/storage.adoc
index 2878974e..7b2104fc 100644
--- a/docs/modules/configuration/pages/storage.adoc
+++ b/docs/modules/configuration/pages/storage.adoc
@@ -4,6 +4,7 @@ include::ROOT:partial$attributes.adoc[]
 
 Cerbos supports multiple backends for storing policies. Which storage driver to use is defined by the `driver` setting.
 
+[id="disk-driver"]
 == Disk driver
 
 The disk driver is a way to serve the policies from a directory on the filesystem. Any `.yaml`, `.yml` or `.json` files in the directory tree rooted at the given path will be read and parsed as policies.
@@ -86,6 +87,7 @@ storage:
     workDir: ${HOME}/tmp/cerbos/work
     updatePollInterval: 10s
 ----
+[id="git-driver"]
 == Git driver
 
 Git is the preferred method of storing Cerbos policies. The server is smart enough to detect when new commits are made to the git repository and refresh its state based on the changes.
diff --git a/docs/modules/policies/pages/conditions.adoc b/docs/modules/policies/pages/conditions.adoc
index ea7e6473..32cd40cd 100644
--- a/docs/modules/policies/pages/conditions.adoc
+++ b/docs/modules/policies/pages/conditions.adoc
@@ -4,6 +4,8 @@ include::ROOT:partial$attributes.adoc[]
 
 A powerful feature of Cerbos policies is the ability to define conditions that are evaluated against the data provided in the request. Conditions are written using the link:https://github.com/google/cel-spec/blob/master/doc/intro.md[Common Expression Language (CEL)].
 
+TIP: Cerbos ships with an interactive REPL that can be used to experiment with writing CEL conditions. It can be started by running `cerbos repl`. See xref:cli:cerbos.adoc#repl[the REPL documentation] for more information.  
+
 .CEL condition
 [source,yaml,linenums]
 ----
diff --git a/go.mod b/go.mod
index de84c6b8..3d575e70 100644
--- a/go.mod
+++ b/go.mod
@@ -5,8 +5,10 @@ go 1.17
 require (
 	contrib.go.opencensus.io/exporter/prometheus v0.4.1
 	github.com/Masterminds/sprig/v3 v3.2.2
+	github.com/adrg/xdg v0.4.0
 	github.com/alecthomas/chroma v0.10.0
 	github.com/alecthomas/kong v0.5.0
+	github.com/alecthomas/participle/v2 v2.0.0-alpha7
 	github.com/aws/aws-sdk-go v1.43.2
 	github.com/bluele/gcache v0.0.2
 	github.com/cespare/xxhash v1.1.0
@@ -47,6 +49,7 @@ require (
 	github.com/oklog/ulid/v2 v2.0.2
 	github.com/olekukonko/tablewriter v0.0.5
 	github.com/ory/dockertest/v3 v3.8.1
+	github.com/peterh/liner v1.2.2
 	github.com/planetscale/vtprotobuf v0.0.0-20210616093554-9236f7c7b8ca
 	github.com/prometheus/client_golang v1.12.1
 	github.com/pterm/pterm v0.12.39
diff --git a/go.sum b/go.sum
index e2793fb9..bec8261f 100644
--- a/go.sum
+++ b/go.sum
@@ -188,11 +188,16 @@ github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:H
 github.com/StackExchange/wmi v1.2.1/go.mod h1:rcmrprowKIVzvc+NUiLncP2uuArMWLCbu9SBzvHz7e8=
 github.com/acomagu/bufpipe v1.0.3 h1:fxAGrHZTgQ9w5QqVItgzwj235/uYZYgbXitB+dLupOk=
 github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4=
+github.com/adrg/xdg v0.4.0 h1:RzRqFcjH4nE5C6oTAxhBtoE2IRyjBSa62SCbyPidvls=
+github.com/adrg/xdg v0.4.0/go.mod h1:N6ag73EX4wyxeaoeHctc1mas01KZgsj5tYiAIwqJE/E=
 github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw=
 github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbfjek=
 github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s=
 github.com/alecthomas/kong v0.5.0 h1:u8Kdw+eeml93qtMZ04iei0CFYve/WPcA5IFh+9wSskE=
 github.com/alecthomas/kong v0.5.0/go.mod h1:uzxf/HUh0tj43x1AyJROl3JT7SgsZ5m+icOv1csRhc0=
+github.com/alecthomas/participle/v2 v2.0.0-alpha7 h1:cK4vjj0VSgb3lN1nuKA5F7dw+1s1pWBe5bx7nNCnN+c=
+github.com/alecthomas/participle/v2 v2.0.0-alpha7/go.mod h1:NumScqsC42o9x+dGj8/YqsIfhrIQjFEOFovxotbBirA=
+github.com/alecthomas/repr v0.0.0-20181024024818-d37bc2a10ba1/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ=
 github.com/alecthomas/repr v0.0.0-20210801044451-80ca428c5142 h1:8Uy0oSf5co/NZXje7U1z8Mpep++QJOldL2hs/sBQf48=
 github.com/alecthomas/repr v0.0.0-20210801044451-80ca428c5142/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8=
 github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
@@ -1175,6 +1180,7 @@ github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9
 github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
 github.com/mattn/go-oci8 v0.1.1/go.mod h1:wjDx6Xm9q7dFtHJvIlrI99JytznLw5wQ4R+9mNXJwGI=
 github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
+github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
 github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
 github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
 github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
@@ -1326,6 +1332,8 @@ github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrap
 github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
 github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
 github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
+github.com/peterh/liner v1.2.2 h1:aJ4AOodmL+JxOZZEL2u9iJf8omNRpqHc/EbrK+3mAXw=
+github.com/peterh/liner v1.2.2/go.mod h1:xFwJyiKIXJZUKItq5dGHZSTBRAuG/CpeNpWLyiNRNwI=
 github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE=
 github.com/phpdave11/gofpdf v1.4.2/go.mod h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2dXMnm1mY=
 github.com/phpdave11/gofpdi v1.0.12/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI=
@@ -2006,6 +2014,7 @@ golang.org/x/sys v0.0.0-20211004093028-2c5d950f24ef/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211013075003-97ac67df715c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
diff --git a/internal/conditions/cel.go b/internal/conditions/cel.go
index 155017e1..2cdf3f44 100644
--- a/internal/conditions/cel.go
+++ b/internal/conditions/cel.go
@@ -35,6 +35,14 @@ var (
 	FalseExpr     *exprpb.CheckedExpr
 )
 
+var StdEnvDecls = []*exprpb.Decl{
+	decls.NewVar(CELRequestIdent, decls.NewObjectType("cerbos.engine.v1.CheckInput")),
+	decls.NewVar(CELPrincipalAbbrev, decls.NewObjectType("cerbos.engine.v1.Principal")),
+	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)),
+}
+
 func init() {
 	var err error
 
@@ -96,6 +104,7 @@ func ResourceAttributeNames(s string) []string {
 
 func newCELQueryPlanEnvOptions() []cel.EnvOption {
 	return []cel.EnvOption{
+		cel.CrossTypeNumericComparisons(true),
 		cel.Types(&requestv1.ResourcesQueryPlanRequest{}, &enginev1.Principal{}, &enginev1.Resource{}),
 		cel.Declarations(
 			decls.NewVar(CELRequestIdent, decls.NewObjectType("cerbos.request.v1.ResourcesQueryPlanRequest")),
@@ -115,14 +124,9 @@ func newCELQueryPlanEnvOptions() []cel.EnvOption {
 
 func newCELEnvOptions() []cel.EnvOption {
 	return []cel.EnvOption{
+		cel.CrossTypeNumericComparisons(true),
 		cel.Types(&enginev1.CheckInput{}, &enginev1.Principal{}, &enginev1.Resource{}),
-		cel.Declarations(
-			decls.NewVar(CELRequestIdent, decls.NewObjectType("cerbos.engine.v1.CheckInput")),
-			decls.NewVar(CELPrincipalAbbrev, decls.NewObjectType("cerbos.engine.v1.Principal")),
-			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)),
-		),
+		cel.Declarations(StdEnvDecls...),
 		ext.Strings(),
 		ext.Encoders(),
 		CerbosCELLib(),
diff --git a/internal/outputcolor/level.go b/internal/outputcolor/level.go
index 63b931ae..232d25bf 100644
--- a/internal/outputcolor/level.go
+++ b/internal/outputcolor/level.go
@@ -21,6 +21,10 @@ const (
 	Ansi16m = Level(supportscolor.Ansi16m)
 )
 
+func DefaultLevel() Level {
+	return Level(supportscolor.SupportsColor(os.Stdout.Fd(), supportscolor.SniffFlagsOption(false)).Level)
+}
+
 var TypeMapper = kong.TypeMapper(reflect.TypeOf((*Level)(nil)), kong.MapperFunc(decode))
 
 func (l *Level) Resolve(disable bool) Level {
@@ -32,7 +36,7 @@ func (l *Level) Resolve(disable bool) Level {
 		return *l
 	}
 
-	return Level(supportscolor.SupportsColor(os.Stdout.Fd(), supportscolor.SniffFlagsOption(false)).Level)
+	return DefaultLevel()
 }
 
 func (l Level) Enabled() bool {
diff --git a/internal/printer/colored/colored.go b/internal/printer/colored/colored.go
index 71e4e4cd..57470c7f 100644
--- a/internal/printer/colored/colored.go
+++ b/internal/printer/colored/colored.go
@@ -6,22 +6,26 @@ package colored
 import "github.com/fatih/color"
 
 var (
-	Header                  = color.New(color.FgHiWhite, color.Bold).SprintFunc()
-	FileName                = color.New(color.FgCyan).SprintFunc()
+	Action                  = color.New(color.FgYellow).SprintFunc()
 	ErrorMsg                = color.New(color.FgRed).SprintFunc()
-	PassedTest              = color.New(color.FgGreen).SprintFunc()
-	SkippedTest             = color.New(color.FgHiWhite).SprintFunc()
-	FailedTest              = color.New(color.FgRed).SprintFunc()
 	ErroredTest             = color.New(color.FgRed).SprintFunc()
-	Suite                   = color.New(color.FgBlue, color.Bold).SprintFunc()
+	FailedTest              = color.New(color.FgRed).SprintFunc()
+	FileName                = color.New(color.FgCyan).SprintFunc()
+	Header                  = color.New(color.FgHiWhite, color.Bold).SprintFunc()
+	PassedTest              = color.New(color.FgGreen).SprintFunc()
 	Principal               = color.New(color.FgCyan).SprintFunc()
+	REPLCmd                 = color.New(color.FgYellow).SprintFunc()
+	REPLError               = color.New(color.FgRed).SprintFunc()
+	REPLVar                 = color.New(color.FgCyan).SprintFunc()
 	Resource                = color.New(color.FgBlue).SprintFunc()
-	Action                  = color.New(color.FgYellow).SprintFunc()
+	SkippedTest             = color.New(color.FgHiWhite).SprintFunc()
+	SuccessfulTest          = color.New(color.FgGreen).SprintFunc()
+	Suite                   = color.New(color.FgBlue, color.Bold).SprintFunc()
 	Trace                   = color.New(color.FgHiWhite).SprintFunc()
 	TraceComponentKey       = color.New(color.FgBlue).SprintFunc()
 	TraceComponentSeparator = color.New(color.FgHiBlack).SprintFunc()
 	TraceEventActivated     = color.New(color.FgGreen).SprintFunc()
-	TraceEventSkipped       = color.New(color.FgHiWhite).SprintFunc()
 	TraceEventEffectAllow   = color.New(color.FgGreen).SprintFunc()
 	TraceEventEffectDeny    = color.New(color.FgRed).SprintFunc()
+	TraceEventSkipped       = color.New(color.FgHiWhite).SprintFunc()
 )
diff --git a/internal/storage/blob/store.go b/internal/storage/blob/store.go
index 5aa33422..d42fd732 100644
--- a/internal/storage/blob/store.go
+++ b/internal/storage/blob/store.go
@@ -17,6 +17,7 @@ import (
 
 	"github.com/aws/aws-sdk-go/aws"
 	"github.com/aws/aws-sdk-go/aws/session"
+	"github.com/grpc-ecosystem/go-grpc-middleware/logging/zap/ctxzap"
 	"go.uber.org/zap"
 	"gocloud.dev/blob"
 
@@ -330,3 +331,23 @@ func (s *Store) LoadPolicy(ctx context.Context, file ...string) ([]*policy.Wrapp
 func (s *Store) RepoStats(ctx context.Context) storage.RepoStats {
 	return s.idx.RepoStats(ctx)
 }
+
+func (s *Store) Reload(ctx context.Context) error {
+	changes, err := s.clone(ctx)
+	if err != nil {
+		return fmt.Errorf("failed to clone: %w", err)
+	}
+
+	if failures := changes.failures(); failures > 0 {
+		ctxzap.Extract(ctx).Warn(fmt.Sprintf("Failed to download (%d) files", failures))
+	}
+
+	evts, err := s.idx.Reload(ctx)
+	if err != nil {
+		return fmt.Errorf("failed to reload the index: %w", err)
+	}
+
+	s.NotifySubscribers(evts...)
+
+	return nil
+}
diff --git a/internal/storage/conf.go b/internal/storage/conf.go
index 4f8c43cd..ab8087c3 100644
--- a/internal/storage/conf.go
+++ b/internal/storage/conf.go
@@ -6,8 +6,9 @@ package storage
 import (
 	"fmt"
 
-	"github.com/cerbos/cerbos/internal/config"
 	"gopkg.in/yaml.v2"
+
+	"github.com/cerbos/cerbos/internal/config"
 )
 
 const ConfKey = "storage"
diff --git a/internal/storage/disk/disk.go b/internal/storage/disk/disk.go
index 30a9092a..a221d697 100644
--- a/internal/storage/disk/disk.go
+++ b/internal/storage/disk/disk.go
@@ -49,7 +49,11 @@ func NewStore(ctx context.Context, conf *Conf) (*Store, error) {
 		return nil, err
 	}
 
-	s := &Store{conf: conf, idx: idx, SubscriptionManager: storage.NewSubscriptionManager(ctx)}
+	s := &Store{
+		conf:                conf,
+		idx:                 idx,
+		SubscriptionManager: storage.NewSubscriptionManager(ctx),
+	}
 	if conf.WatchForChanges {
 		if err := watchDir(ctx, dir, s.idx, s.SubscriptionManager, defaultCooldownPeriod); err != nil {
 			return nil, err
@@ -103,3 +107,13 @@ func (s *Store) LoadPolicy(ctx context.Context, file ...string) ([]*policy.Wrapp
 func (s *Store) RepoStats(ctx context.Context) storage.RepoStats {
 	return s.idx.RepoStats(ctx)
 }
+
+func (s *Store) Reload(ctx context.Context) error {
+	evts, err := s.idx.Reload(ctx)
+	if err != nil {
+		return fmt.Errorf("failed to reload the index: %w", err)
+	}
+	s.NotifySubscribers(evts...)
+
+	return nil
+}
diff --git a/internal/storage/git/store.go b/internal/storage/git/store.go
index 02def883..5d073bf1 100644
--- a/internal/storage/git/store.go
+++ b/internal/storage/git/store.go
@@ -16,6 +16,7 @@ import (
 	"github.com/go-git/go-git/v5"
 	"github.com/go-git/go-git/v5/plumbing"
 	"github.com/go-git/go-git/v5/plumbing/object"
+	"github.com/grpc-ecosystem/go-grpc-middleware/logging/zap/ctxzap"
 	"go.uber.org/zap"
 
 	policyv1 "github.com/cerbos/cerbos/api/genpb/cerbos/policy/v1"
@@ -155,6 +156,27 @@ func (s *Store) RepoStats(ctx context.Context) storage.RepoStats {
 	return s.idx.RepoStats(ctx)
 }
 
+func (s *Store) Reload(ctx context.Context) error {
+	changes, err := s.pullAndCompare(ctx)
+	if err != nil {
+		return fmt.Errorf("failed to pull: %w", err)
+	}
+
+	if changes == nil {
+		ctxzap.Extract(ctx).Info("No new commits: reload not required")
+		return nil
+	}
+
+	evts, err := s.idx.Reload(ctx)
+	if err != nil {
+		return fmt.Errorf("failed to reload the index: %w", err)
+	}
+
+	s.NotifySubscribers(evts...)
+
+	return nil
+}
+
 func isEmptyDir(dir string) (bool, error) {
 	d, err := os.Open(dir)
 	if err != nil {
diff --git a/internal/storage/index/builder.go b/internal/storage/index/builder.go
index 88abb6e1..2191f04e 100644
--- a/internal/storage/index/builder.go
+++ b/internal/storage/index/builder.go
@@ -71,13 +71,19 @@ func WithRootDir(rootDir string) BuildOpt {
 	}
 }
 
-// Build builds an index from the policy files stored in a directory.
-func Build(ctx context.Context, fsys fs.FS, opts ...BuildOpt) (Index, error) {
+func mkBuildOpts(opts ...BuildOpt) buildOptions {
 	o := buildOptions{rootDir: "."}
 	for _, optFn := range opts {
 		optFn(&o)
 	}
 
+	return o
+}
+
+// Build builds an index from the policy files stored in a directory.
+func Build(ctx context.Context, fsys fs.FS, opts ...BuildOpt) (Index, error) {
+	o := mkBuildOpts(opts...)
+
 	if err := checkValidDir(fsys, o.rootDir); err != nil {
 		return nil, err
 	}
@@ -136,7 +142,7 @@ func Build(ctx context.Context, fsys fs.FS, opts ...BuildOpt) (Index, error) {
 		return nil, err
 	}
 
-	return ib.build(fsys, o.rootDir)
+	return ib.build(fsys, opts...)
 }
 
 type indexBuilder struct {
@@ -232,7 +238,7 @@ func (idx *indexBuilder) addDep(child, parent namer.ModuleID) {
 	idx.dependents[parent][child] = struct{}{}
 }
 
-func (idx *indexBuilder) build(fsys fs.FS, rootDir string) (*index, error) {
+func (idx *indexBuilder) build(fsys fs.FS, opts ...BuildOpt) (*index, error) {
 	logger := zap.L().Named("index")
 
 	nErr := len(idx.missing) + len(idx.duplicates) + len(idx.loadFailures) + len(idx.missingScopes)
@@ -275,7 +281,8 @@ func (idx *indexBuilder) build(fsys fs.FS, rootDir string) (*index, error) {
 		fileToModID:  idx.fileToModID,
 		dependents:   idx.dependents,
 		dependencies: idx.dependencies,
-		schemaLoader: NewSchemaLoader(fsys, rootDir),
+		buildOpts:    opts,
+		schemaLoader: NewSchemaLoader(fsys, mkBuildOpts(opts...).rootDir),
 		stats:        idx.stats.collate(),
 	}, nil
 }
diff --git a/internal/storage/index/index.go b/internal/storage/index/index.go
index 9ad2761b..85450e58 100644
--- a/internal/storage/index/index.go
+++ b/internal/storage/index/index.go
@@ -13,13 +13,17 @@ import (
 	"sort"
 	"sync"
 
+	"go.opencensus.io/stats"
+	"go.uber.org/zap"
+	"golang.org/x/sync/singleflight"
+
 	policyv1 "github.com/cerbos/cerbos/api/genpb/cerbos/policy/v1"
 	"github.com/cerbos/cerbos/internal/namer"
 	"github.com/cerbos/cerbos/internal/observability/metrics"
 	"github.com/cerbos/cerbos/internal/policy"
 	"github.com/cerbos/cerbos/internal/schema"
 	"github.com/cerbos/cerbos/internal/storage"
-	"go.opencensus.io/stats"
+	"github.com/grpc-ecosystem/go-grpc-middleware/logging/zap/ctxzap"
 )
 
 var (
@@ -49,6 +53,7 @@ type Index interface {
 	ListSchemaIDs(context.Context) ([]string, error)
 	LoadSchema(context.Context, string) (io.ReadCloser, error)
 	LoadPolicy(context.Context, ...string) ([]*policy.Wrapper, error)
+	Reload(ctx context.Context) ([]storage.Event, error)
 }
 
 type index struct {
@@ -59,7 +64,9 @@ type index struct {
 	dependencies map[namer.ModuleID]map[namer.ModuleID]struct{}
 	modIDToFile  map[namer.ModuleID]string
 	schemaLoader *SchemaLoader
+	sfGroup      singleflight.Group
 	stats        storage.RepoStats
+	buildOpts    []BuildOpt
 	mu           sync.RWMutex
 }
 
@@ -428,3 +435,60 @@ func (idx *index) LoadPolicy(_ context.Context, file ...string) ([]*policy.Wrapp
 func (idx *index) RepoStats(_ context.Context) storage.RepoStats {
 	return idx.stats
 }
+
+func (idx *index) Reload(ctx context.Context) ([]storage.Event, error) {
+	log := ctxzap.Extract(ctx)
+	log.Info("Initiated a store reload")
+	ievts, err, shared := idx.sfGroup.Do("reload", func() (interface{}, error) {
+		idxIface, err := Build(ctx, idx.fsys, idx.buildOpts...)
+		if err != nil {
+			log.Error("Failed to build index while re-indexing")
+			return nil, err
+		}
+
+		newIdx, ok := idxIface.(*index)
+		if !ok {
+			return nil, err
+		}
+
+		var evts []storage.Event
+		for mID := range newIdx.modIDToFile {
+			if _, ok := idx.modIDToFile[mID]; !ok {
+				log.Debug("Detected added policy with while re-indexing", zap.String("moduleID", mID.String()))
+				evts = append(evts, storage.NewPolicyEvent(storage.EventAddOrUpdatePolicy, mID))
+			}
+		}
+		for mID := range idx.modIDToFile {
+			if _, ok := newIdx.modIDToFile[mID]; !ok {
+				log.Debug("Detected deleted policy while re-indexing", zap.String("moduleID", mID.String()))
+				evts = append(evts, storage.NewPolicyEvent(storage.EventDeletePolicy, mID))
+			}
+		}
+
+		idx.mu.RLock()
+		defer idx.mu.RUnlock()
+		idx.fileToModID = newIdx.fileToModID
+		idx.executables = newIdx.executables
+		idx.dependents = newIdx.dependents
+		idx.dependencies = newIdx.dependencies
+		idx.modIDToFile = newIdx.modIDToFile
+		idx.schemaLoader = newIdx.schemaLoader
+		idx.stats = newIdx.stats
+
+		return evts, nil
+	})
+	if err != nil {
+		return nil, err
+	}
+	if shared {
+		log.Debug("shared multiple calls to the reload index function")
+	}
+	log.Info("Successfully reloaded the store")
+
+	evts, ok := ievts.([]storage.Event)
+	if !ok {
+		return nil, fmt.Errorf("failed to type assert storage events")
+	}
+
+	return evts, nil
+}
diff --git a/internal/storage/reload.go b/internal/storage/reload.go
new file mode 100644
index 00000000..634a089f
--- /dev/null
+++ b/internal/storage/reload.go
@@ -0,0 +1,32 @@
+// Copyright 2021-2022 Zenauth Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+package storage
+
+import (
+	"context"
+	"fmt"
+
+	"github.com/grpc-ecosystem/go-grpc-middleware/logging/zap/ctxzap"
+
+	"golang.org/x/sync/singleflight"
+)
+
+var sfGroup singleflight.Group
+
+func Reload(ctx context.Context, rs ReloadableStore) error {
+	_, err, shared := sfGroup.Do("admin_reload", func() (interface{}, error) {
+		if err := rs.Reload(ctx); err != nil {
+			return nil, fmt.Errorf("failed to reload the store: %w", err)
+		}
+		return nil, nil
+	})
+	if err != nil {
+		return err
+	}
+	if shared {
+		ctxzap.Extract(ctx).Debug("shared multiple calls to the reload store API")
+	}
+
+	return nil
+}
diff --git a/internal/storage/store.go b/internal/storage/store.go
index 96b20fab..f4f8bbd3 100644
--- a/internal/storage/store.go
+++ b/internal/storage/store.go
@@ -118,6 +118,12 @@ type MutableStore interface {
 	Delete(context.Context, ...namer.ModuleID) error
 }
 
+// ReloadableStore is a store that allows reloading (blob, disk, git).
+type ReloadableStore interface {
+	Store
+	Reload(context.Context) error
+}
+
 // Instrumented stores expose repository stats.
 type Instrumented interface {
 	RepoStats(context.Context) RepoStats
diff --git a/internal/svc/admin_svc.go b/internal/svc/admin_svc.go
index 7b46870d..c286f9a6 100644
--- a/internal/svc/admin_svc.go
+++ b/internal/svc/admin_svc.go
@@ -233,6 +233,38 @@ func (cas *CerbosAdminService) DeleteSchema(ctx context.Context, req *requestv1.
 	return &responsev1.DeleteSchemaResponse{}, nil
 }
 
+func (cas *CerbosAdminService) ReloadStore(ctx context.Context, req *requestv1.ReloadStoreRequest) (*responsev1.ReloadStoreResponse, error) {
+	log := ctxzap.Extract(ctx)
+	if err := cas.checkCredentials(ctx); err != nil {
+		return nil, err
+	}
+
+	rs, ok := cas.store.(storage.ReloadableStore)
+	if !ok {
+		return nil, status.Error(codes.Unimplemented, "Configured store is not reloadable")
+	}
+
+	reload := func(ctx context.Context) error {
+		if err := storage.Reload(ctx, rs); err != nil {
+			log.Error("failed to reload store", zap.Error(err))
+			return err
+		}
+		return nil
+	}
+
+	if !req.Wait {
+		//nolint:errcheck
+		go reload(ctxzap.ToContext(context.Background(), log))
+		return &responsev1.ReloadStoreResponse{}, nil
+	}
+
+	if err := reload(ctx); err != nil {
+		return nil, status.Error(codes.Internal, "failed to reload store")
+	}
+
+	return &responsev1.ReloadStoreResponse{}, nil
+}
+
 func (cas *CerbosAdminService) ListAuditLogEntries(req *requestv1.ListAuditLogEntriesRequest, stream svcv1.CerbosAdminService_ListAuditLogEntriesServer) error {
 	ctx := stream.Context()
 
diff --git a/schema/jsonschema/cerbos/request/v1/ReloadStoreRequest.schema.json b/schema/jsonschema/cerbos/request/v1/ReloadStoreRequest.schema.json
new file mode 100644
index 00000000..1e0cefc6
--- /dev/null
+++ b/schema/jsonschema/cerbos/request/v1/ReloadStoreRequest.schema.json
@@ -0,0 +1,11 @@
+{
+  "$id": "https://api.cerbos.dev/cerbos/request/v1/ReloadStoreRequest.schema.json",
+  "$schema": "http://json-schema.org/draft-07/schema#",
+  "type": "object",
+  "additionalProperties": false,
+  "properties": {
+    "wait": {
+      "type": "boolean"
+    }
+  }
+}
diff --git a/schema/jsonschema/cerbos/response/v1/ReloadStoreResponse.schema.json b/schema/jsonschema/cerbos/response/v1/ReloadStoreResponse.schema.json
new file mode 100644
index 00000000..94726842
--- /dev/null
+++ b/schema/jsonschema/cerbos/response/v1/ReloadStoreResponse.schema.json
@@ -0,0 +1,6 @@
+{
+  "$id": "https://api.cerbos.dev/cerbos/response/v1/ReloadStoreResponse.schema.json",
+  "$schema": "http://json-schema.org/draft-07/schema#",
+  "type": "object",
+  "additionalProperties": false
+}
diff --git a/schema/openapiv2/cerbos/svc/v1/svc.swagger.json b/schema/openapiv2/cerbos/svc/v1/svc.swagger.json
index 8d765ad2..0b924ba0 100644
--- a/schema/openapiv2/cerbos/svc/v1/svc.swagger.json
+++ b/schema/openapiv2/cerbos/svc/v1/svc.swagger.json
@@ -443,6 +443,43 @@
         ]
       }
     },
+    "/admin/store/reload": {
+      "get": {
+        "summary": "Reload store",
+        "operationId": "CerbosAdminService_ReloadStore",
+        "responses": {
+          "200": {
+            "description": "A successful response.",
+            "schema": {
+              "$ref": "#/definitions/v1ReloadStoreResponse"
+            }
+          },
+          "default": {
+            "description": "An unexpected error response.",
+            "schema": {
+              "$ref": "#/definitions/googlerpcStatus"
+            }
+          }
+        },
+        "parameters": [
+          {
+            "name": "wait",
+            "description": "Wait until the reloading process finalizes",
+            "in": "query",
+            "required": false,
+            "type": "boolean"
+          }
+        ],
+        "tags": [
+          "CerbosAdminService"
+        ],
+        "security": [
+          {
+            "BasicAuth": []
+          }
+        ]
+      }
+    },
     "/api/check": {
       "post": {
         "summary": "Check",
@@ -2294,6 +2331,10 @@
         }
       }
     },
+    "v1ReloadStoreResponse": {
+      "type": "object",
+      "description": "Reload store response"
+    },
     "v1ResourcePolicy": {
       "type": "object",
       "properties": {
diff --git a/tools/go.mod b/tools/go.mod
index b0032f2d..f4381171 100644
--- a/tools/go.mod
+++ b/tools/go.mod
@@ -81,7 +81,7 @@ require (
 	github.com/beorn7/perks v1.0.1 // indirect
 	github.com/bkielbasa/cyclop v1.2.0 // indirect
 	github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb // indirect
-	github.com/blizzy78/varnamelen v0.6.1 // indirect
+	github.com/blizzy78/varnamelen v0.7.0 // indirect
 	github.com/bombsimon/wsl/v3 v3.3.0 // indirect
 	github.com/breml/bidichk v0.2.2 // indirect
 	github.com/breml/errchkjson v0.2.3 // indirect
diff --git a/tools/go.sum b/tools/go.sum
index 789b77e6..5de099bd 100644
--- a/tools/go.sum
+++ b/tools/go.sum
@@ -295,6 +295,8 @@ github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb h1:m935MPodAbYS46DG4
 github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI=
 github.com/blizzy78/varnamelen v0.6.1 h1:kttPCLzXFa+0nt++Cw9fb7GrSSM4KkyIAoX/vXsbuqA=
 github.com/blizzy78/varnamelen v0.6.1/go.mod h1:zy2Eic4qWqjrxa60jG34cfL0VXcSwzUrIx68eJPb4Q8=
+github.com/blizzy78/varnamelen v0.7.0 h1:Y7LzPAysnU3YAabhqXkP9/G9/r7Ks9fUEvOjXoXStlY=
+github.com/blizzy78/varnamelen v0.7.0/go.mod h1:V9TzQZ4fLJ1DSrjVDfl89H7aMnTvKkApdHeyESmyR7k=
 github.com/bojand/ghz v0.106.1 h1:u9RmtTVuPqk1Pg5s3dw3XxkNERmNDZRRpciTza4sOgU=
 github.com/bojand/ghz v0.106.1/go.mod h1:l3GXrJtJB3KbuhJ3Inr4Osu2fVwIEvJ5U69I2YA1h14=
 github.com/bojand/hri v1.1.0/go.mod h1:qwGosuHpNn1S0nyw/mExN0+WZrDf4bQyWjhWh51y3VY=


Test patch:
diff --git a/cmd/cerbos/repl/internal/directive_test.go b/cmd/cerbos/repl/internal/directive_test.go
new file mode 100644
index 00000000..ffe9c75a
--- /dev/null
+++ b/cmd/cerbos/repl/internal/directive_test.go
@@ -0,0 +1,178 @@
+// Copyright 2021-2022 Zenauth Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+package internal
+
+import (
+	"strings"
+	"testing"
+
+	"github.com/stretchr/testify/require"
+)
+
+func TestDirectiveParser(t *testing.T) {
+	testCases := []struct {
+		directive string
+		wantErr   bool
+		check     func(*testing.T, *REPLDirective)
+	}{
+		{
+			directive: "q",
+			check: func(t *testing.T, rd *REPLDirective) {
+				t.Helper()
+				require.True(t, rd.Exit)
+				require.False(t, rd.Reset)
+				require.False(t, rd.Vars)
+				require.False(t, rd.Help)
+				require.Nil(t, rd.Let)
+			},
+		},
+		{
+			directive: "quit",
+			check: func(t *testing.T, rd *REPLDirective) {
+				t.Helper()
+				require.True(t, rd.Exit)
+				require.False(t, rd.Reset)
+				require.False(t, rd.Vars)
+				require.False(t, rd.Help)
+				require.Nil(t, rd.Let)
+			},
+		},
+		{
+			directive: "exit",
+			check: func(t *testing.T, rd *REPLDirective) {
+				t.Helper()
+				require.True(t, rd.Exit)
+				require.False(t, rd.Reset)
+				require.False(t, rd.Vars)
+				require.False(t, rd.Help)
+				require.Nil(t, rd.Let)
+			},
+		},
+		{
+			directive: "reset",
+			check: func(t *testing.T, rd *REPLDirective) {
+				t.Helper()
+				require.False(t, rd.Exit)
+				require.True(t, rd.Reset)
+				require.False(t, rd.Vars)
+				require.False(t, rd.Help)
+				require.Nil(t, rd.Let)
+			},
+		},
+		{
+			directive: "vars",
+			check: func(t *testing.T, rd *REPLDirective) {
+				t.Helper()
+				require.False(t, rd.Exit)
+				require.False(t, rd.Reset)
+				require.True(t, rd.Vars)
+				require.False(t, rd.Help)
+				require.Nil(t, rd.Let)
+			},
+		},
+		{
+			directive: "help",
+			check: func(t *testing.T, rd *REPLDirective) {
+				t.Helper()
+				require.False(t, rd.Exit)
+				require.False(t, rd.Reset)
+				require.False(t, rd.Vars)
+				require.True(t, rd.Help)
+				require.Nil(t, rd.Let)
+			},
+		},
+		{
+			directive: "h",
+			check: func(t *testing.T, rd *REPLDirective) {
+				t.Helper()
+				require.False(t, rd.Exit)
+				require.False(t, rd.Reset)
+				require.False(t, rd.Vars)
+				require.True(t, rd.Help)
+				require.Nil(t, rd.Let)
+			},
+		},
+		{
+			directive: "let x = true",
+			check: func(t *testing.T, rd *REPLDirective) {
+				t.Helper()
+				checkLetIsDefined(t, rd)
+				require.Equal(t, "x", rd.Let.Name)
+				require.Equal(t, "true", strings.TrimSpace(rd.Let.Expr))
+			},
+		},
+		{
+			directive: "let num = 25",
+			check: func(t *testing.T, rd *REPLDirective) {
+				t.Helper()
+				checkLetIsDefined(t, rd)
+				require.Equal(t, "num", rd.Let.Name)
+				require.Equal(t, "25", strings.TrimSpace(rd.Let.Expr))
+			},
+		},
+		{
+			directive: "let num=25.12",
+			check: func(t *testing.T, rd *REPLDirective) {
+				t.Helper()
+				checkLetIsDefined(t, rd)
+				require.Equal(t, "num", rd.Let.Name)
+				require.Equal(t, "25.12", strings.TrimSpace(rd.Let.Expr))
+			},
+		},
+		{
+			directive: `let str = "wibble wobble"`,
+			check: func(t *testing.T, rd *REPLDirective) {
+				t.Helper()
+				checkLetIsDefined(t, rd)
+				require.Equal(t, "str", rd.Let.Name)
+				require.Equal(t, `"wibble wobble"`, strings.TrimSpace(rd.Let.Expr))
+			},
+		},
+		{
+			directive: `let array = ["wibble", "wobble"]`,
+			check: func(t *testing.T, rd *REPLDirective) {
+				t.Helper()
+				checkLetIsDefined(t, rd)
+				require.Equal(t, "array", rd.Let.Name)
+				require.Equal(t, `["wibble", "wobble"]`, strings.TrimSpace(rd.Let.Expr))
+			},
+		},
+		{
+			directive: `let nested_map = {"k1": [true, false], "k2": {"kk1": true}, "k3": 12}`,
+			check: func(t *testing.T, rd *REPLDirective) {
+				t.Helper()
+				checkLetIsDefined(t, rd)
+				require.Equal(t, "nested_map", rd.Let.Name)
+				require.Equal(t, `{"k1": [true, false], "k2": {"kk1": true}, "k3": 12}`, strings.TrimSpace(rd.Let.Expr))
+			},
+		},
+	}
+
+	parser, err := NewParser()
+	require.NoError(t, err)
+
+	for _, tc := range testCases {
+		tc := tc
+		t.Run(tc.directive, func(t *testing.T) {
+			have := &REPLDirective{}
+			err := parser.ParseString("", tc.directive, have)
+			if tc.wantErr {
+				require.Error(t, err)
+				return
+			}
+
+			require.NoError(t, err)
+			tc.check(t, have)
+		})
+	}
+}
+
+func checkLetIsDefined(t *testing.T, rd *REPLDirective) {
+	t.Helper()
+	require.False(t, rd.Exit)
+	require.False(t, rd.Reset)
+	require.False(t, rd.Vars)
+	require.False(t, rd.Help)
+	require.NotNil(t, rd.Let)
+}
diff --git a/cmd/cerbos/repl/internal/repl_test.go b/cmd/cerbos/repl/internal/repl_test.go
new file mode 100644
index 00000000..12432d06
--- /dev/null
+++ b/cmd/cerbos/repl/internal/repl_test.go
@@ -0,0 +1,316 @@
+// Copyright 2021-2022 Zenauth Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+package internal
+
+import (
+	"testing"
+
+	enginev1 "github.com/cerbos/cerbos/api/genpb/cerbos/engine/v1"
+	"github.com/cerbos/cerbos/internal/conditions"
+	"github.com/google/cel-go/common/types/ref"
+	"github.com/google/go-cmp/cmp"
+	"github.com/stretchr/testify/require"
+	"google.golang.org/protobuf/testing/protocmp"
+	"google.golang.org/protobuf/types/known/structpb"
+)
+
+type DirectiveTest struct {
+	Directive string
+	Check     func(*testing.T, *mockOutput)
+	WantErr   bool
+}
+
+func TestREPL(t *testing.T) {
+	toRefVal := conditions.StdEnv.TypeAdapter().NativeToValue
+
+	testCases := []struct {
+		name       string
+		directives []DirectiveTest
+	}{
+		{
+			name: "simple_expression",
+			directives: []DirectiveTest{
+				{
+					Directive: `1+1`,
+					Check: func(t *testing.T, m *mockOutput) {
+						t.Helper()
+						require.Equal(t, lastResultVar, m.resultName)
+						require.Equal(t, toRefVal(2), m.resultVal)
+					},
+				},
+				{
+					Directive: `_ + 1`,
+					Check: func(t *testing.T, m *mockOutput) {
+						t.Helper()
+						require.Equal(t, lastResultVar, m.resultName)
+						require.Equal(t, toRefVal(3), m.resultVal)
+					},
+				},
+			},
+		},
+		{
+			name: "set_variable_and_reset",
+			directives: []DirectiveTest{
+				{
+					Directive: `:let x = 1 + 5`,
+					Check: func(t *testing.T, m *mockOutput) {
+						t.Helper()
+						require.Equal(t, "x", m.resultName)
+						require.Equal(t, toRefVal(6), m.resultVal)
+					},
+				},
+				{
+					Directive: `x * 2`,
+					Check: func(t *testing.T, m *mockOutput) {
+						t.Helper()
+						require.Equal(t, lastResultVar, m.resultName)
+						require.Equal(t, toRefVal(12), m.resultVal)
+					},
+				},
+				{
+					Directive: `:reset`,
+					Check:     func(t *testing.T, m *mockOutput) { t.Helper() },
+				},
+				{
+					Directive: `x * 2`,
+					WantErr:   true,
+				},
+				{
+					Directive: `:let x = "test".charAt(1)`,
+					Check: func(t *testing.T, m *mockOutput) {
+						t.Helper()
+						require.Equal(t, "x", m.resultName)
+						require.Equal(t, toRefVal("e"), m.resultVal)
+					},
+				},
+			},
+		},
+		{
+			name: "set_request_variable",
+			directives: []DirectiveTest{
+				{
+					Directive: `:let request = {"principal":{"id":"john","roles":["employee"],"attr":{"scope":"foo.bar.baz.qux"}},"resource":{"id":"x1","kind":"leave_request","attr":{"scope":"foo.bar"}}}`,
+					Check: func(t *testing.T, m *mockOutput) {
+						t.Helper()
+						require.Equal(t, "request", m.resultName)
+
+						want := &enginev1.CheckInput{
+							Principal: &enginev1.Principal{
+								Id:    "john",
+								Roles: []string{"employee"},
+								Attr:  map[string]*structpb.Value{"scope": structpb.NewStringValue("foo.bar.baz.qux")},
+							},
+							Resource: &enginev1.Resource{
+								Id:   "x1",
+								Kind: "leave_request",
+								Attr: map[string]*structpb.Value{"scope": structpb.NewStringValue("foo.bar")},
+							},
+						}
+
+						require.Empty(t, cmp.Diff(want, m.resultVal.Value(), protocmp.Transform()))
+					},
+				},
+				{
+					Directive: `P`,
+					Check: func(t *testing.T, m *mockOutput) {
+						t.Helper()
+						require.Equal(t, lastResultVar, m.resultName)
+
+						want := &enginev1.Principal{
+							Id:    "john",
+							Roles: []string{"employee"},
+							Attr:  map[string]*structpb.Value{"scope": structpb.NewStringValue("foo.bar.baz.qux")},
+						}
+						require.Empty(t, cmp.Diff(want, m.resultVal.Value(), protocmp.Transform()))
+					},
+				},
+				{
+					Directive: `"employee" in request.principal.roles`,
+					Check: func(t *testing.T, m *mockOutput) {
+						t.Helper()
+						require.Equal(t, lastResultVar, m.resultName)
+						require.Equal(t, toRefVal(true), m.resultVal)
+					},
+				},
+				{
+					Directive: `R`,
+					Check: func(t *testing.T, m *mockOutput) {
+						t.Helper()
+						require.Equal(t, lastResultVar, m.resultName)
+
+						want := &enginev1.Resource{
+							Id:   "x1",
+							Kind: "leave_request",
+							Attr: map[string]*structpb.Value{"scope": structpb.NewStringValue("foo.bar")},
+						}
+						require.Empty(t, cmp.Diff(want, m.resultVal.Value(), protocmp.Transform()))
+					},
+				},
+				{
+					Directive: `hierarchy(request.resource.attr.scope).siblingOf(hierarchy("foo.baz"))`,
+					Check: func(t *testing.T, m *mockOutput) {
+						t.Helper()
+						require.Equal(t, lastResultVar, m.resultName)
+						require.Equal(t, toRefVal(true), m.resultVal)
+					},
+				},
+			},
+		},
+		{
+			name: "set_principal_variable",
+			directives: []DirectiveTest{
+				{
+					Directive: `:let P = {"id":"john","roles":["employee"],"attr":{"scope":"foo.bar.baz.qux"}}`,
+					Check: func(t *testing.T, m *mockOutput) {
+						t.Helper()
+						require.Equal(t, "P", m.resultName)
+
+						want := &enginev1.Principal{
+							Id:    "john",
+							Roles: []string{"employee"},
+							Attr:  map[string]*structpb.Value{"scope": structpb.NewStringValue("foo.bar.baz.qux")},
+						}
+						require.Empty(t, cmp.Diff(want, m.resultVal.Value(), protocmp.Transform()))
+					},
+				},
+				{
+					Directive: `request`,
+					Check: func(t *testing.T, m *mockOutput) {
+						t.Helper()
+						require.Equal(t, lastResultVar, m.resultName)
+
+						want := &enginev1.CheckInput{
+							Principal: &enginev1.Principal{
+								Id:    "john",
+								Roles: []string{"employee"},
+								Attr:  map[string]*structpb.Value{"scope": structpb.NewStringValue("foo.bar.baz.qux")},
+							},
+						}
+						require.Empty(t, cmp.Diff(want, m.resultVal.Value(), protocmp.Transform()))
+					},
+				},
+			},
+		},
+		{
+			name: "set_resource_variable",
+			directives: []DirectiveTest{
+				{
+					Directive: `:let request.resource = {"id":"x1","kind":"leave_request","attr":{"scope":"foo.bar"}}`,
+					Check: func(t *testing.T, m *mockOutput) {
+						t.Helper()
+						require.Equal(t, "request.resource", m.resultName)
+
+						want := &enginev1.Resource{
+							Id:   "x1",
+							Kind: "leave_request",
+							Attr: map[string]*structpb.Value{"scope": structpb.NewStringValue("foo.bar")},
+						}
+						require.Empty(t, cmp.Diff(want, m.resultVal.Value(), protocmp.Transform()))
+					},
+				},
+				{
+					Directive: `request`,
+					Check: func(t *testing.T, m *mockOutput) {
+						t.Helper()
+						require.Equal(t, lastResultVar, m.resultName)
+
+						want := &enginev1.CheckInput{
+							Resource: &enginev1.Resource{
+								Id:   "x1",
+								Kind: "leave_request",
+								Attr: map[string]*structpb.Value{"scope": structpb.NewStringValue("foo.bar")},
+							},
+						}
+						require.Empty(t, cmp.Diff(want, m.resultVal.Value(), protocmp.Transform()))
+					},
+				},
+				{
+					Directive: `hierarchy(R.attr.scope).siblingOf(hierarchy("foo.baz"))`,
+					Check: func(t *testing.T, m *mockOutput) {
+						t.Helper()
+						require.Equal(t, lastResultVar, m.resultName)
+						require.Equal(t, toRefVal(true), m.resultVal)
+					},
+				},
+			},
+		},
+		{
+			name: "set_variables_variable",
+			directives: []DirectiveTest{
+				{
+					Directive: `:let V = {"foo":"bar"}`,
+					Check: func(t *testing.T, m *mockOutput) {
+						t.Helper()
+						require.Equal(t, "V", m.resultName)
+
+						want := map[string]interface{}{"foo": "bar"}
+						require.Equal(t, want, m.resultVal.Value())
+					},
+				},
+				{
+					Directive: `variables.foo`,
+					Check: func(t *testing.T, m *mockOutput) {
+						t.Helper()
+						require.Equal(t, lastResultVar, m.resultName)
+						require.Equal(t, "bar", m.resultVal.Value())
+					},
+				},
+			},
+		},
+	}
+
+	for _, tc := range testCases {
+		tc := tc
+		t.Run(tc.name, func(t *testing.T) {
+			mockOut := &mockOutput{}
+			repl, err := NewREPL(nil, mockOut)
+			require.NoError(t, err)
+
+			for _, d := range tc.directives {
+				t.Run(d.Directive, func(t *testing.T) {
+					err := repl.handleInput(d.Directive)
+					if d.WantErr {
+						require.Error(t, err)
+						return
+					}
+
+					require.NoError(t, err)
+					d.Check(t, mockOut)
+				})
+			}
+		})
+	}
+}
+
+type mockOutput struct {
+	msg        string
+	args       []interface{}
+	jsonObj    interface{}
+	resultName string
+	resultVal  ref.Val
+	err        error
+}
+
+func (mo *mockOutput) Print(msg string, args ...interface{}) {
+	mo.msg = msg
+	mo.args = args
+}
+
+func (mo *mockOutput) Println(args ...interface{}) {
+	mo.args = args
+}
+
+func (mo *mockOutput) PrintResult(name string, val ref.Val) {
+	mo.resultName = name
+	mo.resultVal = val
+}
+
+func (mo *mockOutput) PrintJSON(obj interface{}) {
+	mo.jsonObj = obj
+}
+
+func (mo *mockOutput) PrintErr(msg string, err error) {
+	mo.msg = msg
+	mo.err = err
+}
diff --git a/internal/storage/blob/store_test.go b/internal/storage/blob/store_test.go
index 9528c0f4..020b62aa 100644
--- a/internal/storage/blob/store_test.go
+++ b/internal/storage/blob/store_test.go
@@ -6,18 +6,23 @@ package blob
 import (
 	"context"
 	"errors"
+	"fmt"
 	"path/filepath"
 	"testing"
 	"time"
 
 	"github.com/stretchr/testify/require"
+	"gocloud.dev/blob"
 
 	"github.com/cerbos/cerbos/internal/schema"
 	"github.com/cerbos/cerbos/internal/storage"
 	"github.com/cerbos/cerbos/internal/storage/index"
+	"github.com/cerbos/cerbos/internal/storage/internal"
 	"github.com/cerbos/cerbos/internal/test"
 )
 
+var keysInStore []string
+
 type clonerFunc func(ctx context.Context) (*CloneResult, error)
 
 func (r clonerFunc) Clone(ctx context.Context) (*CloneResult, error) { return r(ctx) }
@@ -70,6 +75,68 @@ func TestNewStore(t *testing.T) {
 	})
 }
 
+func TestReloadable(t *testing.T) {
+	t.Setenv("AWS_ACCESS_KEY_ID", minioUsername)
+	t.Setenv("AWS_SECRET_ACCESS_KEY", minioPassword)
+
+	dir := t.TempDir()
+	store, bucket := mkStore(t, dir)
+	internal.TestSuiteReloadable(store, mkAddFn(t, bucket), mkDeleteFn(t, bucket))(t)
+}
+
+func mkDeleteFn(t *testing.T, bucket *blob.Bucket) internal.MutateStoreFn {
+	t.Helper()
+
+	return func() error {
+		for _, key := range keysInStore {
+			err := bucket.Delete(context.Background(), key)
+			if err != nil {
+				return fmt.Errorf("failed to delete from the store: %w", err)
+			}
+		}
+
+		return nil
+	}
+}
+
+func mkAddFn(t *testing.T, bucket *blob.Bucket) internal.MutateStoreFn {
+	t.Helper()
+
+	return func() error {
+		var err error
+		keysInStore, err = uploadDirToBucket(t, context.Background(), test.PathToDir(t, "store"), bucket)
+		if err != nil {
+			return fmt.Errorf("failed to add to the store: %w", err)
+		}
+		return nil
+	}
+}
+
+func mkStore(t *testing.T, dir string) (*Store, *blob.Bucket) {
+	t.Helper()
+
+	endpoint := startMinio(context.Background(), t, bucketName)
+	conf := mkConf(t, dir, bucketName, endpoint)
+	bucket, err := newBucket(context.Background(), conf)
+	require.NoError(t, err)
+	cloner, err := NewCloner(bucket, storeFS{dir})
+	require.NoError(t, err)
+	store, err := NewStore(context.Background(), conf, cloner)
+	require.NoError(t, err)
+
+	return store, bucket
+}
+
+func mkConf(t *testing.T, dir, bucketName, endpoint string) *Conf {
+	t.Helper()
+
+	conf := &Conf{WorkDir: dir}
+	conf.SetDefaults()
+	conf.Bucket = MinioBucketURL(bucketName, endpoint)
+
+	return conf
+}
+
 type mockIndex struct {
 	index.Index
 	addOrUpdate func(index.Entry) (storage.Event, error)
diff --git a/internal/storage/blob/tests.go b/internal/storage/blob/tests.go
index 57b297d0..2a97ca08 100644
--- a/internal/storage/blob/tests.go
+++ b/internal/storage/blob/tests.go
@@ -24,9 +24,10 @@ import (
 	"github.com/cerbos/cerbos/internal/test"
 )
 
-var (
+const (
 	minioUsername = "minioadmin"
 	minioPassword = "minioadmin"
+	bucketName    = "test"
 )
 
 const timeout = 5 * time.Minute
@@ -83,7 +84,6 @@ func newMinioBucket(ctx context.Context, t *testing.T, prefix string) *blob.Buck
 	ctx, cancelFunc := context.WithDeadline(ctx, deadline)
 	defer cancelFunc()
 
-	bucketName := "test"
 	endpoint := startMinio(ctx, t, bucketName)
 
 	param := UploadParam{
diff --git a/internal/storage/disk/disk_test.go b/internal/storage/disk/disk_test.go
new file mode 100644
index 00000000..bf338c4b
--- /dev/null
+++ b/internal/storage/disk/disk_test.go
@@ -0,0 +1,93 @@
+// Copyright 2021-2022 Zenauth Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+package disk
+
+import (
+	"context"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"os"
+	"path"
+	"path/filepath"
+	"testing"
+
+	"github.com/stretchr/testify/require"
+
+	"github.com/cerbos/cerbos/internal/storage/internal"
+	"github.com/cerbos/cerbos/internal/test"
+)
+
+func TestReloadable(t *testing.T) {
+	storeDir := t.TempDir()
+	store := mkStore(t, storeDir)
+
+	internal.TestSuiteReloadable(store, mkAddFn(t, storeDir), mkDeleteFn(t, storeDir))(t)
+}
+
+func mkStore(t *testing.T, dir string) *Store {
+	t.Helper()
+
+	store, err := NewStore(context.Background(), &Conf{Directory: dir})
+	require.NoError(t, err)
+
+	return store
+}
+
+func mkDeleteFn(t *testing.T, storeDir string) internal.MutateStoreFn {
+	t.Helper()
+
+	return func() error {
+		dir, err := ioutil.ReadDir(storeDir)
+		if err != nil {
+			return fmt.Errorf("failed to read directory while deleting from the store: %w", err)
+		}
+		for _, d := range dir {
+			err = os.RemoveAll(path.Join([]string{storeDir, d.Name()}...))
+			if err != nil {
+				return fmt.Errorf("failed to remove contents while deleting from the store: %w", err)
+			}
+		}
+
+		return nil
+	}
+}
+
+func mkAddFn(t *testing.T, storeDir string) internal.MutateStoreFn {
+	t.Helper()
+
+	return func() error {
+		err := test.FindPolicyFiles(t, "store", func(path string) error {
+			in, err := os.Open(path)
+			if err != nil {
+				return err
+			}
+			defer in.Close()
+
+			pathToFile := filepath.Join(storeDir, filepath.Base(filepath.Dir(path)), filepath.Base(path))
+			err = os.MkdirAll(filepath.Dir(pathToFile), 0o744)
+			if err != nil {
+				return err
+			}
+
+			out, err := os.Create(pathToFile)
+			if err != nil {
+				return err
+			}
+			defer out.Close()
+
+			_, err = io.Copy(out, in)
+			if err != nil {
+				return err
+			}
+
+			return nil
+		})
+		if err != nil {
+			return err
+		}
+
+		return nil
+	}
+}
diff --git a/internal/storage/git/store_test.go b/internal/storage/git/store_test.go
index 3a769905..d0ac7682 100644
--- a/internal/storage/git/store_test.go
+++ b/internal/storage/git/store_test.go
@@ -8,8 +8,10 @@ import (
 	"fmt"
 	"io"
 	"io/fs"
+	"io/ioutil"
 	"math/rand"
 	"os"
+	"path"
 	"path/filepath"
 	"testing"
 	"time"
@@ -27,6 +29,7 @@ import (
 	"github.com/cerbos/cerbos/internal/schema"
 	"github.com/cerbos/cerbos/internal/storage"
 	"github.com/cerbos/cerbos/internal/storage/index"
+	"github.com/cerbos/cerbos/internal/storage/internal"
 	"github.com/cerbos/cerbos/internal/test"
 	"github.com/cerbos/cerbos/internal/test/mocks"
 )
@@ -431,6 +434,76 @@ func TestUpdateStore(t *testing.T) {
 	})
 }
 
+func TestReloadable(t *testing.T) {
+	ps := genPolicySet(1)
+	sourceGitDir := t.TempDir()
+	checkoutDir := t.TempDir()
+	store := mkEmptyStoreAndRepo(t, sourceGitDir, checkoutDir)
+
+	internal.TestSuiteReloadable(store, mkAddFn(t, sourceGitDir, ps), mkDeleteFn(t, sourceGitDir))(t)
+}
+
+func mkDeleteFn(t *testing.T, sourceGitDir string) internal.MutateStoreFn {
+	t.Helper()
+
+	return func() error {
+		err := commitToGitRepo(sourceGitDir, "Delete all", func(wt *git.Worktree) error {
+			dir, err := ioutil.ReadDir(sourceGitDir)
+			if err != nil {
+				return fmt.Errorf("failed to read directory while deleting from the store: %w", err)
+			}
+
+			for _, d := range dir {
+				if d.Name() == ".git" {
+					continue
+				}
+				err = os.RemoveAll(path.Join([]string{sourceGitDir, d.Name()}...))
+				if err != nil {
+					return fmt.Errorf("failed to remove contents while deleting from the store: %w", err)
+				}
+			}
+
+			_, err = wt.Add(".")
+			return err
+		})
+		if err != nil {
+			return err
+		}
+
+		return nil
+	}
+}
+
+func mkAddFn(t *testing.T, sourceGitDir string, ps policySet) internal.MutateStoreFn {
+	t.Helper()
+
+	return func() error {
+		err := commitToGitRepo(sourceGitDir, "Add policy", func(wt *git.Worktree) error {
+			if err := writePolicySet(filepath.Join(sourceGitDir, policyDir), ps); err != nil {
+				return err
+			}
+
+			_, err := wt.Add(".")
+			return err
+		})
+		if err != nil {
+			return err
+		}
+
+		return nil
+	}
+}
+
+func mkEmptyStoreAndRepo(t *testing.T, sourceGitDir, checkoutDir string) *Store {
+	t.Helper()
+
+	_ = createGitRepo(t, sourceGitDir, 0)
+	store, err := NewStore(context.Background(), mkConf(t, sourceGitDir, checkoutDir))
+	require.NoError(t, err)
+
+	return store
+}
+
 func anyIndexEntry(_ index.Entry) bool { return true }
 
 func requireIndexContains(t *testing.T, store *Store, wantFiles []string) {
diff --git a/internal/storage/internal/tests.go b/internal/storage/internal/tests.go
new file mode 100644
index 00000000..8afde60c
--- /dev/null
+++ b/internal/storage/internal/tests.go
@@ -0,0 +1,52 @@
+// Copyright 2021-2022 Zenauth Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+//go:build tests
+// +build tests
+
+package internal
+
+import (
+	"context"
+	"testing"
+
+	"github.com/stretchr/testify/require"
+
+	"github.com/cerbos/cerbos/internal/storage"
+)
+
+// MutateStoreFn points to a function which mutates the store (ex: add, delete a policy).
+type MutateStoreFn func() error
+
+func TestSuiteReloadable(store storage.ReloadableStore, addFn, deleteFn MutateStoreFn) func(*testing.T) {
+	//nolint:thelper
+	return func(t *testing.T) {
+		policies, err := store.ListPolicyIDs(context.Background())
+		require.NoError(t, err)
+		require.Len(t, policies, 0)
+
+		err = addFn()
+		require.NoError(t, err)
+
+		policies, err = store.ListPolicyIDs(context.Background())
+		require.NoError(t, err)
+		require.Len(t, policies, 0)
+
+		err = store.Reload(context.Background())
+		require.NoError(t, err)
+
+		policies, err = store.ListPolicyIDs(context.Background())
+		require.NoError(t, err)
+		require.NotZero(t, len(policies))
+
+		err = deleteFn()
+		require.NoError(t, err)
+
+		err = store.Reload(context.Background())
+		require.NoError(t, err)
+
+		policies, err = store.ListPolicyIDs(context.Background())
+		require.NoError(t, err)
+		require.Len(t, policies, 0)
+	}
+}
diff --git a/internal/test/mocks/Index.go b/internal/test/mocks/Index.go
index 25f60856..519c1afb 100644
--- a/internal/test/mocks/Index.go
+++ b/internal/test/mocks/Index.go
@@ -270,6 +270,29 @@ func (_m *Index) LoadSchema(_a0 context.Context, _a1 string) (io.ReadCloser, err
 	return r0, r1
 }
 
+// Reload provides a mock function with given fields: ctx
+func (_m *Index) Reload(ctx context.Context) ([]storage.Event, error) {
+	ret := _m.Called(ctx)
+
+	var r0 []storage.Event
+	if rf, ok := ret.Get(0).(func(context.Context) []storage.Event); ok {
+		r0 = rf(ctx)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).([]storage.Event)
+		}
+	}
+
+	var r1 error
+	if rf, ok := ret.Get(1).(func(context.Context) error); ok {
+		r1 = rf(ctx)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
 // RepoStats provides a mock function with given fields: _a0
 func (_m *Index) RepoStats(_a0 context.Context) storage.RepoStats {
 	ret := _m.Called(_a0)