Repository: cerbos/cerbos
Pull request #2493 (2025-02-26 11:06:30)

chore: Protect against non leaf REQUIRE_PARENTAL_CONSENT scopes
The documentation states that any `REQUIRE_PARENTAL_CONSENT` scopes must reside at a leaf node in the scope hierarchy, but we do not enforce it in the PDP.

We're not far off supporting this (middle layer `*_CONSENT`), but adding tests uncovered some issues (namely around parent-role row retrieval). Separately, I'm not sure if it would even be a worthwhile feature.

Therefore, adding build time protection seems viable for now.


Patch:
diff --git a/api/genpb/cerbos/private/v1/hashpb_helpers.pb.go b/api/genpb/cerbos/private/v1/hashpb_helpers.pb.go
index eea8b0de..f6530ef5 100644
--- a/api/genpb/cerbos/private/v1/hashpb_helpers.pb.go
+++ b/api/genpb/cerbos/private/v1/hashpb_helpers.pb.go
@@ -4574,6 +4574,13 @@ func cerbos_runtime_v1_IndexBuildErrors_ScopePermissionsConflicts_hashpb_sum(m *
 	}
 }
 
+func cerbos_runtime_v1_IndexBuildErrors_ScopePermissionsOrdering_hashpb_sum(m *v15.IndexBuildErrors_ScopePermissionsOrdering, hasher hash.Hash, ignore map[string]struct{}) {
+	if _, ok := ignore["cerbos.runtime.v1.IndexBuildErrors.ScopePermissionsOrdering.scope"]; !ok {
+		_, _ = hasher.Write(protowire.AppendString(nil, m.GetScope()))
+
+	}
+}
+
 func cerbos_runtime_v1_IndexBuildErrors_hashpb_sum(m *v15.IndexBuildErrors, hasher hash.Hash, ignore map[string]struct{}) {
 	if _, ok := ignore["cerbos.runtime.v1.IndexBuildErrors.disabled"]; !ok {
 		if len(m.Disabled) > 0 {
@@ -4651,6 +4658,16 @@ func cerbos_runtime_v1_IndexBuildErrors_hashpb_sum(m *v15.IndexBuildErrors, hash
 			}
 		}
 	}
+	if _, ok := ignore["cerbos.runtime.v1.IndexBuildErrors.scope_permissions_ordering"]; !ok {
+		if len(m.ScopePermissionsOrdering) > 0 {
+			for _, v := range m.ScopePermissionsOrdering {
+				if v != nil {
+					cerbos_runtime_v1_IndexBuildErrors_ScopePermissionsOrdering_hashpb_sum(v, hasher, ignore)
+				}
+
+			}
+		}
+	}
 }
 
 func cerbos_schema_v1_Schema_hashpb_sum(m *v16.Schema, hasher hash.Hash, ignore map[string]struct{}) {
diff --git a/api/genpb/cerbos/runtime/v1/hashpb_helpers.pb.go b/api/genpb/cerbos/runtime/v1/hashpb_helpers.pb.go
index d907640e..8c1262c8 100644
--- a/api/genpb/cerbos/runtime/v1/hashpb_helpers.pb.go
+++ b/api/genpb/cerbos/runtime/v1/hashpb_helpers.pb.go
@@ -300,6 +300,13 @@ func cerbos_runtime_v1_IndexBuildErrors_ScopePermissionsConflicts_hashpb_sum(m *
 	}
 }
 
+func cerbos_runtime_v1_IndexBuildErrors_ScopePermissionsOrdering_hashpb_sum(m *IndexBuildErrors_ScopePermissionsOrdering, hasher hash.Hash, ignore map[string]struct{}) {
+	if _, ok := ignore["cerbos.runtime.v1.IndexBuildErrors.ScopePermissionsOrdering.scope"]; !ok {
+		_, _ = hasher.Write(protowire.AppendString(nil, m.GetScope()))
+
+	}
+}
+
 func cerbos_runtime_v1_IndexBuildErrors_hashpb_sum(m *IndexBuildErrors, hasher hash.Hash, ignore map[string]struct{}) {
 	if _, ok := ignore["cerbos.runtime.v1.IndexBuildErrors.disabled"]; !ok {
 		if len(m.Disabled) > 0 {
@@ -377,6 +384,16 @@ func cerbos_runtime_v1_IndexBuildErrors_hashpb_sum(m *IndexBuildErrors, hasher h
 			}
 		}
 	}
+	if _, ok := ignore["cerbos.runtime.v1.IndexBuildErrors.scope_permissions_ordering"]; !ok {
+		if len(m.ScopePermissionsOrdering) > 0 {
+			for _, v := range m.ScopePermissionsOrdering {
+				if v != nil {
+					cerbos_runtime_v1_IndexBuildErrors_ScopePermissionsOrdering_hashpb_sum(v, hasher, ignore)
+				}
+
+			}
+		}
+	}
 }
 
 func cerbos_runtime_v1_Output_When_hashpb_sum(m *Output_When, hasher hash.Hash, ignore map[string]struct{}) {
diff --git a/api/genpb/cerbos/runtime/v1/runtime.pb.go b/api/genpb/cerbos/runtime/v1/runtime.pb.go
index e7562c6a..9ca79636 100644
--- a/api/genpb/cerbos/runtime/v1/runtime.pb.go
+++ b/api/genpb/cerbos/runtime/v1/runtime.pb.go
@@ -1061,6 +1061,7 @@ type IndexBuildErrors struct {
 	DisabledDefs              []*IndexBuildErrors_Disabled                  `protobuf:"bytes,6,rep,name=disabled_defs,json=disabledDefs,proto3" json:"disabled_defs,omitempty"`
 	MissingScopeDetails       []*IndexBuildErrors_MissingScope              `protobuf:"bytes,7,rep,name=missing_scope_details,json=missingScopeDetails,proto3" json:"missing_scope_details,omitempty"`
 	ScopePermissionsConflicts []*IndexBuildErrors_ScopePermissionsConflicts `protobuf:"bytes,8,rep,name=scope_permissions_conflicts,json=scopePermissionsConflicts,proto3" json:"scope_permissions_conflicts,omitempty"`
+	ScopePermissionsOrdering  []*IndexBuildErrors_ScopePermissionsOrdering  `protobuf:"bytes,9,rep,name=scope_permissions_ordering,json=scopePermissionsOrdering,proto3" json:"scope_permissions_ordering,omitempty"`
 	unknownFields             protoimpl.UnknownFields
 	sizeCache                 protoimpl.SizeCache
 }
@@ -1153,6 +1154,13 @@ func (x *IndexBuildErrors) GetScopePermissionsConflicts() []*IndexBuildErrors_Sc
 	return nil
 }
 
+func (x *IndexBuildErrors) GetScopePermissionsOrdering() []*IndexBuildErrors_ScopePermissionsOrdering {
+	if x != nil {
+		return x.ScopePermissionsOrdering
+	}
+	return nil
+}
+
 type Errors struct {
 	state protoimpl.MessageState `protogen:"open.v1"`
 	// Types that are valid to be assigned to Kind:
@@ -2725,6 +2733,50 @@ func (x *IndexBuildErrors_ScopePermissionsConflicts) GetScope() string {
 	return ""
 }
 
+type IndexBuildErrors_ScopePermissionsOrdering struct {
+	state         protoimpl.MessageState `protogen:"open.v1"`
+	Scope         string                 `protobuf:"bytes,1,opt,name=scope,proto3" json:"scope,omitempty"`
+	unknownFields protoimpl.UnknownFields
+	sizeCache     protoimpl.SizeCache
+}
+
+func (x *IndexBuildErrors_ScopePermissionsOrdering) Reset() {
+	*x = IndexBuildErrors_ScopePermissionsOrdering{}
+	mi := &file_cerbos_runtime_v1_runtime_proto_msgTypes[64]
+	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+	ms.StoreMessageInfo(mi)
+}
+
+func (x *IndexBuildErrors_ScopePermissionsOrdering) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*IndexBuildErrors_ScopePermissionsOrdering) ProtoMessage() {}
+
+func (x *IndexBuildErrors_ScopePermissionsOrdering) ProtoReflect() protoreflect.Message {
+	mi := &file_cerbos_runtime_v1_runtime_proto_msgTypes[64]
+	if x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use IndexBuildErrors_ScopePermissionsOrdering.ProtoReflect.Descriptor instead.
+func (*IndexBuildErrors_ScopePermissionsOrdering) Descriptor() ([]byte, []int) {
+	return file_cerbos_runtime_v1_runtime_proto_rawDescGZIP(), []int{14, 4}
+}
+
+func (x *IndexBuildErrors_ScopePermissionsOrdering) GetScope() string {
+	if x != nil {
+		return x.Scope
+	}
+	return ""
+}
+
 type IndexBuildErrors_LoadFailure struct {
 	state protoimpl.MessageState `protogen:"open.v1"`
 	File  string                 `protobuf:"bytes,1,opt,name=file,proto3" json:"file,omitempty"`
@@ -2737,7 +2789,7 @@ type IndexBuildErrors_LoadFailure struct {
 
 func (x *IndexBuildErrors_LoadFailure) Reset() {
 	*x = IndexBuildErrors_LoadFailure{}
-	mi := &file_cerbos_runtime_v1_runtime_proto_msgTypes[64]
+	mi := &file_cerbos_runtime_v1_runtime_proto_msgTypes[65]
 	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 	ms.StoreMessageInfo(mi)
 }
@@ -2749,7 +2801,7 @@ func (x *IndexBuildErrors_LoadFailure) String() string {
 func (*IndexBuildErrors_LoadFailure) ProtoMessage() {}
 
 func (x *IndexBuildErrors_LoadFailure) ProtoReflect() protoreflect.Message {
-	mi := &file_cerbos_runtime_v1_runtime_proto_msgTypes[64]
+	mi := &file_cerbos_runtime_v1_runtime_proto_msgTypes[65]
 	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -2762,7 +2814,7 @@ func (x *IndexBuildErrors_LoadFailure) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use IndexBuildErrors_LoadFailure.ProtoReflect.Descriptor instead.
 func (*IndexBuildErrors_LoadFailure) Descriptor() ([]byte, []int) {
-	return file_cerbos_runtime_v1_runtime_proto_rawDescGZIP(), []int{14, 4}
+	return file_cerbos_runtime_v1_runtime_proto_rawDescGZIP(), []int{14, 5}
 }
 
 func (x *IndexBuildErrors_LoadFailure) GetFile() string {
@@ -2798,7 +2850,7 @@ type IndexBuildErrors_Disabled struct {
 
 func (x *IndexBuildErrors_Disabled) Reset() {
 	*x = IndexBuildErrors_Disabled{}
-	mi := &file_cerbos_runtime_v1_runtime_proto_msgTypes[65]
+	mi := &file_cerbos_runtime_v1_runtime_proto_msgTypes[66]
 	ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 	ms.StoreMessageInfo(mi)
 }
@@ -2810,7 +2862,7 @@ func (x *IndexBuildErrors_Disabled) String() string {
 func (*IndexBuildErrors_Disabled) ProtoMessage() {}
 
 func (x *IndexBuildErrors_Disabled) ProtoReflect() protoreflect.Message {
-	mi := &file_cerbos_runtime_v1_runtime_proto_msgTypes[65]
+	mi := &file_cerbos_runtime_v1_runtime_proto_msgTypes[66]
 	if x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -2823,7 +2875,7 @@ func (x *IndexBuildErrors_Disabled) ProtoReflect() protoreflect.Message {
 
 // Deprecated: Use IndexBuildErrors_Disabled.ProtoReflect.Descriptor instead.
 func (*IndexBuildErrors_Disabled) Descriptor() ([]byte, []int) {
-	return file_cerbos_runtime_v1_runtime_proto_rawDescGZIP(), []int{14, 5}
+	return file_cerbos_runtime_v1_runtime_proto_rawDescGZIP(), []int{14, 6}
 }
 
 func (x *IndexBuildErrors_Disabled) GetFile() string {
@@ -3498,7 +3550,7 @@ var file_cerbos_runtime_v1_runtime_proto_rawDesc = string([]byte{
 	0x72, 0x63, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52,
 	0x08, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6e,
 	0x74, 0x65, 0x78, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6e, 0x74,
-	0x65, 0x78, 0x74, 0x22, 0xb7, 0x0b, 0x0a, 0x10, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x42, 0x75, 0x69,
+	0x65, 0x78, 0x74, 0x22, 0xe5, 0x0c, 0x0a, 0x10, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x42, 0x75, 0x69,
 	0x6c, 0x64, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x12, 0x1e, 0x0a, 0x08, 0x64, 0x69, 0x73, 0x61,
 	0x62, 0x6c, 0x65, 0x64, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x42, 0x02, 0x18, 0x01, 0x52, 0x08,
 	0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x57, 0x0a, 0x0e, 0x64, 0x75, 0x70, 0x6c,
@@ -3540,72 +3592,83 @@ var file_cerbos_runtime_v1_runtime_proto_rawDesc = string([]byte{
 	0x73, 0x2e, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f,
 	0x6e, 0x73, 0x43, 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, 0x73, 0x52, 0x19, 0x73, 0x63, 0x6f,
 	0x70, 0x65, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x43, 0x6f, 0x6e,
-	0x66, 0x6c, 0x69, 0x63, 0x74, 0x73, 0x1a, 0x91, 0x01, 0x0a, 0x0c, 0x44, 0x75, 0x70, 0x6c, 0x69,
-	0x63, 0x61, 0x74, 0x65, 0x44, 0x65, 0x66, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x18,
-	0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x6f,
-	0x74, 0x68, 0x65, 0x72, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,
-	0x09, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x6f,
-	0x6c, 0x69, 0x63, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x6f, 0x6c, 0x69,
-	0x63, 0x79, 0x12, 0x36, 0x0a, 0x08, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04,
-	0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x63, 0x65, 0x72, 0x62, 0x6f, 0x73, 0x2e, 0x73, 0x6f,
-	0x75, 0x72, 0x63, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e,
-	0x52, 0x08, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x89, 0x02, 0x0a, 0x0d, 0x4d,
-	0x69, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x25, 0x0a, 0x0e,
-	0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x01,
-	0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x46,
-	0x69, 0x6c, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x65, 0x73, 0x63, 0x18, 0x02, 0x20, 0x01, 0x28,
-	0x09, 0x52, 0x04, 0x64, 0x65, 0x73, 0x63, 0x12, 0x29, 0x0a, 0x10, 0x69, 0x6d, 0x70, 0x6f, 0x72,
-	0x74, 0x69, 0x6e, 0x67, 0x5f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28,
-	0x09, 0x52, 0x0f, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x50, 0x6f, 0x6c, 0x69,
-	0x63, 0x79, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x6b, 0x69, 0x6e,
-	0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x4b,
-	0x69, 0x6e, 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x6e, 0x61,
-	0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74,
-	0x4e, 0x61, 0x6d, 0x65, 0x12, 0x36, 0x0a, 0x08, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e,
-	0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x63, 0x65, 0x72, 0x62, 0x6f, 0x73, 0x2e,
-	0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69,
-	0x6f, 0x6e, 0x52, 0x08, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07,
-	0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63,
-	0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x1a, 0x57, 0x0a, 0x0c, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6e,
-	0x67, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6e,
-	0x67, 0x5f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d,
-	0x6d, 0x69, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x20, 0x0a,
-	0x0b, 0x64, 0x65, 0x73, 0x63, 0x65, 0x6e, 0x64, 0x61, 0x6e, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03,
-	0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x65, 0x6e, 0x64, 0x61, 0x6e, 0x74, 0x73, 0x1a,
-	0x31, 0x0a, 0x19, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69,
-	0x6f, 0x6e, 0x73, 0x43, 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, 0x73, 0x12, 0x14, 0x0a, 0x05,
-	0x73, 0x63, 0x6f, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x63, 0x6f,
-	0x70, 0x65, 0x1a, 0x79, 0x0a, 0x0b, 0x4c, 0x6f, 0x61, 0x64, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72,
-	0x65, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
-	0x04, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x18, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02,
-	0x20, 0x01, 0x28, 0x09, 0x42, 0x02, 0x18, 0x01, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12,
-	0x3c, 0x0a, 0x0d, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73,
-	0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x63, 0x65, 0x72, 0x62, 0x6f, 0x73, 0x2e,
-	0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52,
-	0x0c, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x1a, 0x6e, 0x0a,
-	0x08, 0x44, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x69, 0x6c,
-	0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x16, 0x0a,
-	0x06, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70,
-	0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x36, 0x0a, 0x08, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f,
-	0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x63, 0x65, 0x72, 0x62, 0x6f, 0x73,
-	0x2e, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x6f, 0x73, 0x69, 0x74,
-	0x69, 0x6f, 0x6e, 0x52, 0x08, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0xb0, 0x01,
-	0x0a, 0x06, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x12, 0x53, 0x0a, 0x12, 0x69, 0x6e, 0x64, 0x65,
-	0x78, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x18, 0x01,
-	0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x63, 0x65, 0x72, 0x62, 0x6f, 0x73, 0x2e, 0x72, 0x75,
-	0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x42, 0x75,
-	0x69, 0x6c, 0x64, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x48, 0x00, 0x52, 0x10, 0x69, 0x6e, 0x64,
-	0x65, 0x78, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x12, 0x49, 0x0a,
-	0x0e, 0x63, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x18,
-	0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x63, 0x65, 0x72, 0x62, 0x6f, 0x73, 0x2e, 0x72,
-	0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x69, 0x6c,
-	0x65, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x48, 0x00, 0x52, 0x0d, 0x63, 0x6f, 0x6d, 0x70, 0x69,
-	0x6c, 0x65, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x42, 0x06, 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64,
-	0x42, 0x40, 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, 0x75,
-	0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2f, 0x76, 0x31, 0x3b, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65,
-	0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+	0x66, 0x6c, 0x69, 0x63, 0x74, 0x73, 0x12, 0x7a, 0x0a, 0x1a, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x5f,
+	0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x5f, 0x6f, 0x72, 0x64, 0x65,
+	0x72, 0x69, 0x6e, 0x67, 0x18, 0x09, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3c, 0x2e, 0x63, 0x65, 0x72,
+	0x62, 0x6f, 0x73, 0x2e, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x49,
+	0x6e, 0x64, 0x65, 0x78, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x2e,
+	0x53, 0x63, 0x6f, 0x70, 0x65, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73,
+	0x4f, 0x72, 0x64, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x52, 0x18, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x50,
+	0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x69,
+	0x6e, 0x67, 0x1a, 0x91, 0x01, 0x0a, 0x0c, 0x44, 0x75, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x65,
+	0x44, 0x65, 0x66, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
+	0x09, 0x52, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x6f, 0x74, 0x68, 0x65, 0x72,
+	0x5f, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6f, 0x74, 0x68,
+	0x65, 0x72, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79,
+	0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x36,
+	0x0a, 0x08, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b,
+	0x32, 0x1a, 0x2e, 0x63, 0x65, 0x72, 0x62, 0x6f, 0x73, 0x2e, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65,
+	0x2e, 0x76, 0x31, 0x2e, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x08, 0x70, 0x6f,
+	0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x89, 0x02, 0x0a, 0x0d, 0x4d, 0x69, 0x73, 0x73, 0x69,
+	0x6e, 0x67, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x25, 0x0a, 0x0e, 0x69, 0x6d, 0x70, 0x6f,
+	0x72, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
+	0x52, 0x0d, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x46, 0x69, 0x6c, 0x65, 0x12,
+	0x12, 0x0a, 0x04, 0x64, 0x65, 0x73, 0x63, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x64,
+	0x65, 0x73, 0x63, 0x12, 0x29, 0x0a, 0x10, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67,
+	0x5f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x69,
+	0x6d, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x1f,
+	0x0a, 0x0b, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x04, 0x20,
+	0x01, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x4b, 0x69, 0x6e, 0x64, 0x12,
+	0x1f, 0x0a, 0x0b, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x05,
+	0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x4e, 0x61, 0x6d, 0x65,
+	0x12, 0x36, 0x0a, 0x08, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01,
+	0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x63, 0x65, 0x72, 0x62, 0x6f, 0x73, 0x2e, 0x73, 0x6f, 0x75, 0x72,
+	0x63, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x08,
+	0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x74,
+	0x65, 0x78, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65,
+	0x78, 0x74, 0x1a, 0x57, 0x0a, 0x0c, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x53, 0x63, 0x6f,
+	0x70, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x5f, 0x70, 0x6f,
+	0x6c, 0x69, 0x63, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6d, 0x69, 0x73, 0x73,
+	0x69, 0x6e, 0x67, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73,
+	0x63, 0x65, 0x6e, 0x64, 0x61, 0x6e, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b,
+	0x64, 0x65, 0x73, 0x63, 0x65, 0x6e, 0x64, 0x61, 0x6e, 0x74, 0x73, 0x1a, 0x31, 0x0a, 0x19, 0x53,
+	0x63, 0x6f, 0x70, 0x65, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x43,
+	0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x63, 0x6f, 0x70,
+	0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x1a, 0x30,
+	0x0a, 0x18, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x50, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f,
+	0x6e, 0x73, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x63,
+	0x6f, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x73, 0x63, 0x6f, 0x70, 0x65,
+	0x1a, 0x79, 0x0a, 0x0b, 0x4c, 0x6f, 0x61, 0x64, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x12,
+	0x12, 0x0a, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66,
+	0x69, 0x6c, 0x65, 0x12, 0x18, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01,
+	0x28, 0x09, 0x42, 0x02, 0x18, 0x01, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x3c, 0x0a,
+	0x0d, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x18, 0x03,
+	0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x63, 0x65, 0x72, 0x62, 0x6f, 0x73, 0x2e, 0x73, 0x6f,
+	0x75, 0x72, 0x63, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x0c, 0x65,
+	0x72, 0x72, 0x6f, 0x72, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x1a, 0x6e, 0x0a, 0x08, 0x44,
+	0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x18,
+	0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x70,
+	0x6f, 0x6c, 0x69, 0x63, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x6f, 0x6c,
+	0x69, 0x63, 0x79, 0x12, 0x36, 0x0a, 0x08, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18,
+	0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x63, 0x65, 0x72, 0x62, 0x6f, 0x73, 0x2e, 0x73,
+	0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f,
+	0x6e, 0x52, 0x08, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0xb0, 0x01, 0x0a, 0x06,
+	0x45, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x12, 0x53, 0x0a, 0x12, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x5f,
+	0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x18, 0x01, 0x20, 0x01,
+	0x28, 0x0b, 0x32, 0x23, 0x2e, 0x63, 0x65, 0x72, 0x62, 0x6f, 0x73, 0x2e, 0x72, 0x75, 0x6e, 0x74,
+	0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x42, 0x75, 0x69, 0x6c,
+	0x64, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x48, 0x00, 0x52, 0x10, 0x69, 0x6e, 0x64, 0x65, 0x78,
+	0x42, 0x75, 0x69, 0x6c, 0x64, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x12, 0x49, 0x0a, 0x0e, 0x63,
+	0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x18, 0x02, 0x20,
+	0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x63, 0x65, 0x72, 0x62, 0x6f, 0x73, 0x2e, 0x72, 0x75, 0x6e,
+	0x74, 0x69, 0x6d, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x45,
+	0x72, 0x72, 0x6f, 0x72, 0x73, 0x48, 0x00, 0x52, 0x0d, 0x63, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65,
+	0x45, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x42, 0x06, 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x42, 0x40,
+	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, 0x75, 0x6e, 0x74,
+	0x69, 0x6d, 0x65, 0x2f, 0x76, 0x31, 0x3b, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x76, 0x31,
+	0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
 })
 
 var (
@@ -3620,7 +3683,7 @@ func file_cerbos_runtime_v1_runtime_proto_rawDescGZIP() []byte {
 	return file_cerbos_runtime_v1_runtime_proto_rawDescData
 }
 
-var file_cerbos_runtime_v1_runtime_proto_msgTypes = make([]protoimpl.MessageInfo, 66)
+var file_cerbos_runtime_v1_runtime_proto_msgTypes = make([]protoimpl.MessageInfo, 67)
 var file_cerbos_runtime_v1_runtime_proto_goTypes = []any{
 	(*RunnablePolicySet)(nil),              // 0: cerbos.runtime.v1.RunnablePolicySet
 	(*RuleTable)(nil),                      // 1: cerbos.runtime.v1.RuleTable
@@ -3686,17 +3749,18 @@ var file_cerbos_runtime_v1_runtime_proto_goTypes = []any{
 	(*IndexBuildErrors_MissingImport)(nil), // 61: cerbos.runtime.v1.IndexBuildErrors.MissingImport
 	(*IndexBuildErrors_MissingScope)(nil),  // 62: cerbos.runtime.v1.IndexBuildErrors.MissingScope
 	(*IndexBuildErrors_ScopePermissionsConflicts)(nil), // 63: cerbos.runtime.v1.IndexBuildErrors.ScopePermissionsConflicts
-	(*IndexBuildErrors_LoadFailure)(nil),               // 64: cerbos.runtime.v1.IndexBuildErrors.LoadFailure
-	(*IndexBuildErrors_Disabled)(nil),                  // 65: cerbos.runtime.v1.IndexBuildErrors.Disabled
-	(v1.ScopePermissions)(0),                           // 66: cerbos.policy.v1.ScopePermissions
-	(*v1.Schemas)(nil),                                 // 67: cerbos.policy.v1.Schemas
-	(*v1alpha1.CheckedExpr)(nil),                       // 68: google.api.expr.v1alpha1.CheckedExpr
-	(v11.Effect)(0),                                    // 69: cerbos.effect.v1.Effect
-	(*emptypb.Empty)(nil),                              // 70: google.protobuf.Empty
-	(*v1.SourceAttributes)(nil),                        // 71: cerbos.policy.v1.SourceAttributes
-	(*structpb.Value)(nil),                             // 72: google.protobuf.Value
-	(*v12.Position)(nil),                               // 73: cerbos.source.v1.Position
-	(*v12.Error)(nil),                                  // 74: cerbos.source.v1.Error
+	(*IndexBuildErrors_ScopePermissionsOrdering)(nil),  // 64: cerbos.runtime.v1.IndexBuildErrors.ScopePermissionsOrdering
+	(*IndexBuildErrors_LoadFailure)(nil),               // 65: cerbos.runtime.v1.IndexBuildErrors.LoadFailure
+	(*IndexBuildErrors_Disabled)(nil),                  // 66: cerbos.runtime.v1.IndexBuildErrors.Disabled
+	(v1.ScopePermissions)(0),                           // 67: cerbos.policy.v1.ScopePermissions
+	(*v1.Schemas)(nil),                                 // 68: cerbos.policy.v1.Schemas
+	(*v1alpha1.CheckedExpr)(nil),                       // 69: google.api.expr.v1alpha1.CheckedExpr
+	(v11.Effect)(0),                                    // 70: cerbos.effect.v1.Effect
+	(*emptypb.Empty)(nil),                              // 71: google.protobuf.Empty
+	(*v1.SourceAttributes)(nil),                        // 72: cerbos.policy.v1.SourceAttributes
+	(*structpb.Value)(nil),                             // 73: google.protobuf.Value
+	(*v12.Position)(nil),                               // 74: cerbos.source.v1.Position
+	(*v12.Error)(nil),                                  // 75: cerbos.source.v1.Error
 }
 var file_cerbos_runtime_v1_runtime_proto_depIdxs = []int32{
 	4,   // 0: cerbos.runtime.v1.RunnablePolicySet.resource_policy:type_name -> cerbos.runtime.v1.RunnableResourcePolicySet
@@ -3708,11 +3772,11 @@ var file_cerbos_runtime_v1_runtime_proto_depIdxs = []int32{
 	19,  // 6: cerbos.runtime.v1.RuleTableMetadata.source_attributes:type_name -> cerbos.runtime.v1.RuleTableMetadata.SourceAttributesEntry
 	20,  // 7: cerbos.runtime.v1.RuleTableMetadata.annotations:type_name -> cerbos.runtime.v1.RuleTableMetadata.AnnotationsEntry
 	21,  // 8: cerbos.runtime.v1.RunnableRolePolicySet.meta:type_name -> cerbos.runtime.v1.RunnableRolePolicySet.Metadata
-	66,  // 9: cerbos.runtime.v1.RunnableRolePolicySet.scope_permissions:type_name -> cerbos.policy.v1.ScopePermissions
+	67,  // 9: cerbos.runtime.v1.RunnableRolePolicySet.scope_permissions:type_name -> cerbos.policy.v1.ScopePermissions
 	24,  // 10: cerbos.runtime.v1.RunnableRolePolicySet.resources:type_name -> cerbos.runtime.v1.RunnableRolePolicySet.ResourcesEntry
 	28,  // 11: cerbos.runtime.v1.RunnableResourcePolicySet.meta:type_name -> cerbos.runtime.v1.RunnableResourcePolicySet.Metadata
 	29,  // 12: cerbos.runtime.v1.RunnableResourcePolicySet.policies:type_name -> cerbos.runtime.v1.RunnableResourcePolicySet.Policy
-	67,  // 13: cerbos.runtime.v1.RunnableResourcePolicySet.schemas:type_name -> cerbos.policy.v1.Schemas
+	68,  // 13: cerbos.runtime.v1.RunnableResourcePolicySet.schemas:type_name -> cerbos.policy.v1.Schemas
 	39,  // 14: cerbos.runtime.v1.RunnableDerivedRole.parent_roles:type_name -> cerbos.runtime.v1.RunnableDerivedRole.ParentRolesEntry
 	40,  // 15: cerbos.runtime.v1.RunnableDerivedRole.variables:type_name -> cerbos.runtime.v1.RunnableDerivedRole.VariablesEntry
 	12,  // 16: cerbos.runtime.v1.RunnableDerivedRole.condition:type_name -> cerbos.runtime.v1.Condition
@@ -3724,7 +3788,7 @@ var file_cerbos_runtime_v1_runtime_proto_depIdxs = []int32{
 	46,  // 22: cerbos.runtime.v1.RunnableVariablesSet.variables:type_name -> cerbos.runtime.v1.RunnableVariablesSet.VariablesEntry
 	48,  // 23: cerbos.runtime.v1.RunnablePrincipalPolicySet.meta:type_name -> cerbos.runtime.v1.RunnablePrincipalPolicySet.Metadata
 	49,  // 24: cerbos.runtime.v1.RunnablePrincipalPolicySet.policies:type_name -> cerbos.runtime.v1.RunnablePrincipalPolicySet.Policy
-	68,  // 25: cerbos.runtime.v1.Expr.checked:type_name -> google.api.expr.v1alpha1.CheckedExpr
+	69,  // 25: cerbos.runtime.v1.Expr.checked:type_name -> google.api.expr.v1alpha1.CheckedExpr
 	57,  // 26: cerbos.runtime.v1.Output.when:type_name -> cerbos.runtime.v1.Output.When
 	9,   // 27: cerbos.runtime.v1.Variable.expr:type_name -> cerbos.runtime.v1.Expr
 	58,  // 28: cerbos.runtime.v1.Condition.all:type_name -> cerbos.runtime.v1.Condition.ExprList
@@ -3733,89 +3797,90 @@ var file_cerbos_runtime_v1_runtime_proto_depIdxs = []int32{
 	9,   // 31: cerbos.runtime.v1.Condition.expr:type_name -> cerbos.runtime.v1.Expr
 	59,  // 32: cerbos.runtime.v1.CompileErrors.errors:type_name -> cerbos.runtime.v1.CompileErrors.Err
 	60,  // 33: cerbos.runtime.v1.IndexBuildErrors.duplicate_defs:type_name -> cerbos.runtime.v1.IndexBuildErrors.DuplicateDef
-	64,  // 34: cerbos.runtime.v1.IndexBuildErrors.load_failures:type_name -> cerbos.runtime.v1.IndexBuildErrors.LoadFailure
+	65,  // 34: cerbos.runtime.v1.IndexBuildErrors.load_failures:type_name -> cerbos.runtime.v1.IndexBuildErrors.LoadFailure
 	61,  // 35: cerbos.runtime.v1.IndexBuildErrors.missing_imports:type_name -> cerbos.runtime.v1.IndexBuildErrors.MissingImport
-	65,  // 36: cerbos.runtime.v1.IndexBuildErrors.disabled_defs:type_name -> cerbos.runtime.v1.IndexBuildErrors.Disabled
+	66,  // 36: cerbos.runtime.v1.IndexBuildErrors.disabled_defs:type_name -> cerbos.runtime.v1.IndexBuildErrors.Disabled
 	62,  // 37: cerbos.runtime.v1.IndexBuildErrors.missing_scope_details:type_name -> cerbos.runtime.v1.IndexBuildErrors.MissingScope
 	63,  // 38: cerbos.runtime.v1.IndexBuildErrors.scope_permissions_conflicts:type_name -> cerbos.runtime.v1.IndexBuildErrors.ScopePermissionsConflicts
-	14,  // 39: cerbos.runtime.v1.Errors.index_build_errors:type_name -> cerbos.runtime.v1.IndexBuildErrors
-	13,  // 40: cerbos.runtime.v1.Errors.compile_errors:type_name -> cerbos.runtime.v1.CompileErrors
-	17,  // 41: cerbos.runtime.v1.RuleTable.RuleRow.allow_actions:type_name -> cerbos.runtime.v1.RuleTable.RuleRow.AllowActions
-	12,  // 42: cerbos.runtime.v1.RuleTable.RuleRow.condition:type_name -> cerbos.runtime.v1.Condition
-	12,  // 43: cerbos.runtime.v1.RuleTable.RuleRow.derived_role_condition:type_name -> cerbos.runtime.v1.Condition
-	69,  // 44: cerbos.runtime.v1.RuleTable.RuleRow.effect:type_name -> cerbos.effect.v1.Effect
-	66,  // 45: cerbos.runtime.v1.RuleTable.RuleRow.scope_permissions:type_name -> cerbos.policy.v1.ScopePermissions
-	10,  // 46: cerbos.runtime.v1.RuleTable.RuleRow.emit_output:type_name -> cerbos.runtime.v1.Output
-	18,  // 47: cerbos.runtime.v1.RuleTable.RuleRow.AllowActions.actions:type_name -> cerbos.runtime.v1.RuleTable.RuleRow.AllowActions.ActionsEntry
-	70,  // 48: cerbos.runtime.v1.RuleTable.RuleRow.AllowActions.ActionsEntry.value:type_name -> google.protobuf.Empty
-	71,  // 49: cerbos.runtime.v1.RuleTableMetadata.SourceAttributesEntry.value:type_name -> cerbos.policy.v1.SourceAttributes
-	25,  // 50: cerbos.runtime.v1.RunnableRolePolicySet.Metadata.source_attributes:type_name -> cerbos.runtime.v1.RunnableRolePolicySet.Metadata.SourceAttributesEntry
-	26,  // 51: cerbos.runtime.v1.RunnableRolePolicySet.Metadata.annotations:type_name -> cerbos.runtime.v1.RunnableRolePolicySet.Metadata.AnnotationsEntry
-	27,  // 52: cerbos.runtime.v1.RunnableRolePolicySet.Rule.allow_actions:type_name -> cerbos.runtime.v1.RunnableRolePolicySet.Rule.AllowActionsEntry
-	12,  // 53: cerbos.runtime.v1.RunnableRolePolicySet.Rule.condition:type_name -> cerbos.runtime.v1.Condition
-	22,  // 54: cerbos.runtime.v1.RunnableRolePolicySet.RuleList.rules:type_name -> cerbos.runtime.v1.RunnableRolePolicySet.Rule
-	23,  // 55: cerbos.runtime.v1.RunnableRolePolicySet.ResourcesEntry.value:type_name -> cerbos.runtime.v1.RunnableRolePolicySet.RuleList
-	71,  // 56: cerbos.runtime.v1.RunnableRolePolicySet.Metadata.SourceAttributesEntry.value:type_name -> cerbos.policy.v1.SourceAttributes
-	70,  // 57: cerbos.runtime.v1.RunnableRolePolicySet.Rule.AllowActionsEntry.value:type_name -> google.protobuf.Empty
-	30,  // 58: cerbos.runtime.v1.RunnableResourcePolicySet.Metadata.source_attributes:type_name -> cerbos.runtime.v1.RunnableResourcePolicySet.Metadata.SourceAttributesEntry
-	31,  // 59: cerbos.runtime.v1.RunnableResourcePolicySet.Metadata.annotations:type_name -> cerbos.runtime.v1.RunnableResourcePolicySet.Metadata.AnnotationsEntry
-	33,  // 60: cerbos.runtime.v1.RunnableResourcePolicySet.Policy.derived_roles:type_name -> cerbos.runtime.v1.RunnableResourcePolicySet.Policy.DerivedRolesEntry
-	34,  // 61: cerbos.runtime.v1.RunnableResourcePolicySet.Policy.variables:type_name -> cerbos.runtime.v1.RunnableResourcePolicySet.Policy.VariablesEntry
-	32,  // 62: cerbos.runtime.v1.RunnableResourcePolicySet.Policy.rules:type_name -> cerbos.runtime.v1.RunnableResourcePolicySet.Policy.Rule
-	67,  // 63: cerbos.runtime.v1.RunnableResourcePolicySet.Policy.schemas:type_name -> cerbos.policy.v1.Schemas
-	11,  // 64: cerbos.runtime.v1.RunnableResourcePolicySet.Policy.ordered_variables:type_name -> cerbos.runtime.v1.Variable
-	66,  // 65: cerbos.runtime.v1.RunnableResourcePolicySet.Policy.scope_permissions:type_name -> cerbos.policy.v1.ScopePermissions
-	35,  // 66: cerbos.runtime.v1.RunnableResourcePolicySet.Policy.constants:type_name -> cerbos.runtime.v1.RunnableResourcePolicySet.Policy.ConstantsEntry
-	71,  // 67: cerbos.runtime.v1.RunnableResourcePolicySet.Metadata.SourceAttributesEntry.value:type_name -> cerbos.policy.v1.SourceAttributes
-	36,  // 68: cerbos.runtime.v1.RunnableResourcePolicySet.Policy.Rule.actions:type_name -> cerbos.runtime.v1.RunnableResourcePolicySet.Policy.Rule.ActionsEntry
-	37,  // 69: cerbos.runtime.v1.RunnableResourcePolicySet.Policy.Rule.derived_roles:type_name -> cerbos.runtime.v1.RunnableResourcePolicySet.Policy.Rule.DerivedRolesEntry
-	38,  // 70: cerbos.runtime.v1.RunnableResourcePolicySet.Policy.Rule.roles:type_name -> cerbos.runtime.v1.RunnableResourcePolicySet.Policy.Rule.RolesEntry
-	12,  // 71: cerbos.runtime.v1.RunnableResourcePolicySet.Policy.Rule.condition:type_name -> cerbos.runtime.v1.Condition
-	69,  // 72: cerbos.runtime.v1.RunnableResourcePolicySet.Policy.Rule.effect:type_name -> cerbos.effect.v1.Effect
-	9,   // 73: cerbos.runtime.v1.RunnableResourcePolicySet.Policy.Rule.output:type_name -> cerbos.runtime.v1.Expr
-	10,  // 74: cerbos.runtime.v1.RunnableResourcePolicySet.Policy.Rule.emit_output:type_name -> cerbos.runtime.v1.Output
-	5,   // 75: cerbos.runtime.v1.RunnableResourcePolicySet.Policy.DerivedRolesEntry.value:type_name -> cerbos.runtime.v1.RunnableDerivedRole
-	9,   // 76: cerbos.runtime.v1.RunnableResourcePolicySet.Policy.VariablesEntry.value:type_name -> cerbos.runtime.v1.Expr
-	72,  // 77: cerbos.runtime.v1.RunnableResourcePolicySet.Policy.ConstantsEntry.value:type_name -> google.protobuf.Value
-	70,  // 78: cerbos.runtime.v1.RunnableResourcePolicySet.Policy.Rule.ActionsEntry.value:type_name -> google.protobuf.Empty
-	70,  // 79: cerbos.runtime.v1.RunnableResourcePolicySet.Policy.Rule.DerivedRolesEntry.value:type_name -> google.protobuf.Empty
-	70,  // 80: cerbos.runtime.v1.RunnableResourcePolicySet.Policy.Rule.RolesEntry.value:type_name -> google.protobuf.Empty
-	70,  // 81: cerbos.runtime.v1.RunnableDerivedRole.ParentRolesEntry.value:type_name -> google.protobuf.Empty
-	9,   // 82: cerbos.runtime.v1.RunnableDerivedRole.VariablesEntry.value:type_name -> cerbos.runtime.v1.Expr
-	72,  // 83: cerbos.runtime.v1.RunnableDerivedRole.ConstantsEntry.value:type_name -> google.protobuf.Value
-	44,  // 84: cerbos.runtime.v1.RunnableDerivedRolesSet.Metadata.annotations:type_name -> cerbos.runtime.v1.RunnableDerivedRolesSet.Metadata.AnnotationsEntry
-	5,   // 85: cerbos.runtime.v1.RunnableDerivedRolesSet.DerivedRolesEntry.value:type_name -> cerbos.runtime.v1.RunnableDerivedRole
-	47,  // 86: cerbos.runtime.v1.RunnableVariablesSet.Metadata.annotations:type_name -> cerbos.runtime.v1.RunnableVariablesSet.Metadata.AnnotationsEntry
-	9,   // 87: cerbos.runtime.v1.RunnableVariablesSet.VariablesEntry.value:type_name -> cerbos.runtime.v1.Expr
-	50,  // 88: cerbos.runtime.v1.RunnablePrincipalPolicySet.Metadata.source_attributes:type_name -> cerbos.runtime.v1.RunnablePrincipalPolicySet.Metadata.SourceAttributesEntry
-	51,  // 89: cerbos.runtime.v1.RunnablePrincipalPolicySet.Metadata.annotations:type_name -> cerbos.runtime.v1.RunnablePrincipalPolicySet.Metadata.AnnotationsEntry
-	54,  // 90: cerbos.runtime.v1.RunnablePrincipalPolicySet.Policy.variables:type_name -> cerbos.runtime.v1.RunnablePrincipalPolicySet.Policy.VariablesEntry
-	55,  // 91: cerbos.runtime.v1.RunnablePrincipalPolicySet.Policy.resource_rules:type_name -> cerbos.runtime.v1.RunnablePrincipalPolicySet.Policy.ResourceRulesEntry
-	11,  // 92: cerbos.runtime.v1.RunnablePrincipalPolicySet.Policy.ordered_variables:type_name -> cerbos.runtime.v1.Variable
-	66,  // 93: cerbos.runtime.v1.RunnablePrincipalPolicySet.Policy.scope_permissions:type_name -> cerbos.policy.v1.ScopePermissions
-	56,  // 94: cerbos.runtime.v1.RunnablePrincipalPolicySet.Policy.constants:type_name -> cerbos.runtime.v1.RunnablePrincipalPolicySet.Policy.ConstantsEntry
-	71,  // 95: cerbos.runtime.v1.RunnablePrincipalPolicySet.Metadata.SourceAttributesEntry.value:type_name -> cerbos.policy.v1.SourceAttributes
-	12,  // 96: cerbos.runtime.v1.RunnablePrincipalPolicySet.Policy.ActionRule.condition:type_name -> cerbos.runtime.v1.Condition
-	69,  // 97: cerbos.runtime.v1.RunnablePrincipalPolicySet.Policy.ActionRule.effect:type_name -> cerbos.effect.v1.Effect
-	9,   // 98: cerbos.runtime.v1.RunnablePrincipalPolicySet.Policy.ActionRule.output:type_name -> cerbos.runtime.v1.Expr
-	10,  // 99: cerbos.runtime.v1.RunnablePrincipalPolicySet.Policy.ActionRule.emit_output:type_name -> cerbos.runtime.v1.Output
-	52,  // 100: cerbos.runtime.v1.RunnablePrincipalPolicySet.Policy.ResourceRules.action_rules:type_name -> cerbos.runtime.v1.RunnablePrincipalPolicySet.Policy.ActionRule
-	9,   // 101: cerbos.runtime.v1.RunnablePrincipalPolicySet.Policy.VariablesEntry.value:type_name -> cerbos.runtime.v1.Expr
-	53,  // 102: cerbos.runtime.v1.RunnablePrincipalPolicySet.Policy.ResourceRulesEntry.value:type_name -> cerbos.runtime.v1.RunnablePrincipalPolicySet.Policy.ResourceRules
-	72,  // 103: cerbos.runtime.v1.RunnablePrincipalPolicySet.Policy.ConstantsEntry.value:type_name -> google.protobuf.Value
-	9,   // 104: cerbos.runtime.v1.Output.When.rule_activated:type_name -> cerbos.runtime.v1.Expr
-	9,   // 105: cerbos.runtime.v1.Output.When.condition_not_met:type_name -> cerbos.runtime.v1.Expr
-	12,  // 106: cerbos.runtime.v1.Condition.ExprList.expr:type_name -> cerbos.runtime.v1.Condition
-	73,  // 107: cerbos.runtime.v1.CompileErrors.Err.position:type_name -> cerbos.source.v1.Position
-	73,  // 108: cerbos.runtime.v1.IndexBuildErrors.DuplicateDef.position:type_name -> cerbos.source.v1.Position
-	73,  // 109: cerbos.runtime.v1.IndexBuildErrors.MissingImport.position:type_name -> cerbos.source.v1.Position
-	74,  // 110: cerbos.runtime.v1.IndexBuildErrors.LoadFailure.error_details:type_name -> cerbos.source.v1.Error
-	73,  // 111: cerbos.runtime.v1.IndexBuildErrors.Disabled.position:type_name -> cerbos.source.v1.Position
-	112, // [112:112] is the sub-list for method output_type
-	112, // [112:112] is the sub-list for method input_type
-	112, // [112:112] is the sub-list for extension type_name
-	112, // [112:112] is the sub-list for extension extendee
-	0,   // [0:112] is the sub-list for field type_name
+	64,  // 39: cerbos.runtime.v1.IndexBuildErrors.scope_permissions_ordering:type_name -> cerbos.runtime.v1.IndexBuildErrors.ScopePermissionsOrdering
+	14,  // 40: cerbos.runtime.v1.Errors.index_build_errors:type_name -> cerbos.runtime.v1.IndexBuildErrors
+	13,  // 41: cerbos.runtime.v1.Errors.compile_errors:type_name -> cerbos.runtime.v1.CompileErrors
+	17,  // 42: cerbos.runtime.v1.RuleTable.RuleRow.allow_actions:type_name -> cerbos.runtime.v1.RuleTable.RuleRow.AllowActions
+	12,  // 43: cerbos.runtime.v1.RuleTable.RuleRow.condition:type_name -> cerbos.runtime.v1.Condition
+	12,  // 44: cerbos.runtime.v1.RuleTable.RuleRow.derived_role_condition:type_name -> cerbos.runtime.v1.Condition
+	70,  // 45: cerbos.runtime.v1.RuleTable.RuleRow.effect:type_name -> cerbos.effect.v1.Effect
+	67,  // 46: cerbos.runtime.v1.RuleTable.RuleRow.scope_permissions:type_name -> cerbos.policy.v1.ScopePermissions
+	10,  // 47: cerbos.runtime.v1.RuleTable.RuleRow.emit_output:type_name -> cerbos.runtime.v1.Output
+	18,  // 48: cerbos.runtime.v1.RuleTable.RuleRow.AllowActions.actions:type_name -> cerbos.runtime.v1.RuleTable.RuleRow.AllowActions.ActionsEntry
+	71,  // 49: cerbos.runtime.v1.RuleTable.RuleRow.AllowActions.ActionsEntry.value:type_name -> google.protobuf.Empty
+	72,  // 50: cerbos.runtime.v1.RuleTableMetadata.SourceAttributesEntry.value:type_name -> cerbos.policy.v1.SourceAttributes
+	25,  // 51: cerbos.runtime.v1.RunnableRolePolicySet.Metadata.source_attributes:type_name -> cerbos.runtime.v1.RunnableRolePolicySet.Metadata.SourceAttributesEntry
+	26,  // 52: cerbos.runtime.v1.RunnableRolePolicySet.Metadata.annotations:type_name -> cerbos.runtime.v1.RunnableRolePolicySet.Metadata.AnnotationsEntry
+	27,  // 53: cerbos.runtime.v1.RunnableRolePolicySet.Rule.allow_actions:type_name -> cerbos.runtime.v1.RunnableRolePolicySet.Rule.AllowActionsEntry
+	12,  // 54: cerbos.runtime.v1.RunnableRolePolicySet.Rule.condition:type_name -> cerbos.runtime.v1.Condition
+	22,  // 55: cerbos.runtime.v1.RunnableRolePolicySet.RuleList.rules:type_name -> cerbos.runtime.v1.RunnableRolePolicySet.Rule
+	23,  // 56: cerbos.runtime.v1.RunnableRolePolicySet.ResourcesEntry.value:type_name -> cerbos.runtime.v1.RunnableRolePolicySet.RuleList
+	72,  // 57: cerbos.runtime.v1.RunnableRolePolicySet.Metadata.SourceAttributesEntry.value:type_name -> cerbos.policy.v1.SourceAttributes
+	71,  // 58: cerbos.runtime.v1.RunnableRolePolicySet.Rule.AllowActionsEntry.value:type_name -> google.protobuf.Empty
+	30,  // 59: cerbos.runtime.v1.RunnableResourcePolicySet.Metadata.source_attributes:type_name -> cerbos.runtime.v1.RunnableResourcePolicySet.Metadata.SourceAttributesEntry
+	31,  // 60: cerbos.runtime.v1.RunnableResourcePolicySet.Metadata.annotations:type_name -> cerbos.runtime.v1.RunnableResourcePolicySet.Metadata.AnnotationsEntry
+	33,  // 61: cerbos.runtime.v1.RunnableResourcePolicySet.Policy.derived_roles:type_name -> cerbos.runtime.v1.RunnableResourcePolicySet.Policy.DerivedRolesEntry
+	34,  // 62: cerbos.runtime.v1.RunnableResourcePolicySet.Policy.variables:type_name -> cerbos.runtime.v1.RunnableResourcePolicySet.Policy.VariablesEntry
+	32,  // 63: cerbos.runtime.v1.RunnableResourcePolicySet.Policy.rules:type_name -> cerbos.runtime.v1.RunnableResourcePolicySet.Policy.Rule
+	68,  // 64: cerbos.runtime.v1.RunnableResourcePolicySet.Policy.schemas:type_name -> cerbos.policy.v1.Schemas
+	11,  // 65: cerbos.runtime.v1.RunnableResourcePolicySet.Policy.ordered_variables:type_name -> cerbos.runtime.v1.Variable
+	67,  // 66: cerbos.runtime.v1.RunnableResourcePolicySet.Policy.scope_permissions:type_name -> cerbos.policy.v1.ScopePermissions
+	35,  // 67: cerbos.runtime.v1.RunnableResourcePolicySet.Policy.constants:type_name -> cerbos.runtime.v1.RunnableResourcePolicySet.Policy.ConstantsEntry
+	72,  // 68: cerbos.runtime.v1.RunnableResourcePolicySet.Metadata.SourceAttributesEntry.value:type_name -> cerbos.policy.v1.SourceAttributes
+	36,  // 69: cerbos.runtime.v1.RunnableResourcePolicySet.Policy.Rule.actions:type_name -> cerbos.runtime.v1.RunnableResourcePolicySet.Policy.Rule.ActionsEntry
+	37,  // 70: cerbos.runtime.v1.RunnableResourcePolicySet.Policy.Rule.derived_roles:type_name -> cerbos.runtime.v1.RunnableResourcePolicySet.Policy.Rule.DerivedRolesEntry
+	38,  // 71: cerbos.runtime.v1.RunnableResourcePolicySet.Policy.Rule.roles:type_name -> cerbos.runtime.v1.RunnableResourcePolicySet.Policy.Rule.RolesEntry
+	12,  // 72: cerbos.runtime.v1.RunnableResourcePolicySet.Policy.Rule.condition:type_name -> cerbos.runtime.v1.Condition
+	70,  // 73: cerbos.runtime.v1.RunnableResourcePolicySet.Policy.Rule.effect:type_name -> cerbos.effect.v1.Effect
+	9,   // 74: cerbos.runtime.v1.RunnableResourcePolicySet.Policy.Rule.output:type_name -> cerbos.runtime.v1.Expr
+	10,  // 75: cerbos.runtime.v1.RunnableResourcePolicySet.Policy.Rule.emit_output:type_name -> cerbos.runtime.v1.Output
+	5,   // 76: cerbos.runtime.v1.RunnableResourcePolicySet.Policy.DerivedRolesEntry.value:type_name -> cerbos.runtime.v1.RunnableDerivedRole
+	9,   // 77: cerbos.runtime.v1.RunnableResourcePolicySet.Policy.VariablesEntry.value:type_name -> cerbos.runtime.v1.Expr
+	73,  // 78: cerbos.runtime.v1.RunnableResourcePolicySet.Policy.ConstantsEntry.value:type_name -> google.protobuf.Value
+	71,  // 79: cerbos.runtime.v1.RunnableResourcePolicySet.Policy.Rule.ActionsEntry.value:type_name -> google.protobuf.Empty
+	71,  // 80: cerbos.runtime.v1.RunnableResourcePolicySet.Policy.Rule.DerivedRolesEntry.value:type_name -> google.protobuf.Empty
+	71,  // 81: cerbos.runtime.v1.RunnableResourcePolicySet.Policy.Rule.RolesEntry.value:type_name -> google.protobuf.Empty
+	71,  // 82: cerbos.runtime.v1.RunnableDerivedRole.ParentRolesEntry.value:type_name -> google.protobuf.Empty
+	9,   // 83: cerbos.runtime.v1.RunnableDerivedRole.VariablesEntry.value:type_name -> cerbos.runtime.v1.Expr
+	73,  // 84: cerbos.runtime.v1.RunnableDerivedRole.ConstantsEntry.value:type_name -> google.protobuf.Value
+	44,  // 85: cerbos.runtime.v1.RunnableDerivedRolesSet.Metadata.annotations:type_name -> cerbos.runtime.v1.RunnableDerivedRolesSet.Metadata.AnnotationsEntry
+	5,   // 86: cerbos.runtime.v1.RunnableDerivedRolesSet.DerivedRolesEntry.value:type_name -> cerbos.runtime.v1.RunnableDerivedRole
+	47,  // 87: cerbos.runtime.v1.RunnableVariablesSet.Metadata.annotations:type_name -> cerbos.runtime.v1.RunnableVariablesSet.Metadata.AnnotationsEntry
+	9,   // 88: cerbos.runtime.v1.RunnableVariablesSet.VariablesEntry.value:type_name -> cerbos.runtime.v1.Expr
+	50,  // 89: cerbos.runtime.v1.RunnablePrincipalPolicySet.Metadata.source_attributes:type_name -> cerbos.runtime.v1.RunnablePrincipalPolicySet.Metadata.SourceAttributesEntry
+	51,  // 90: cerbos.runtime.v1.RunnablePrincipalPolicySet.Metadata.annotations:type_name -> cerbos.runtime.v1.RunnablePrincipalPolicySet.Metadata.AnnotationsEntry
+	54,  // 91: cerbos.runtime.v1.RunnablePrincipalPolicySet.Policy.variables:type_name -> cerbos.runtime.v1.RunnablePrincipalPolicySet.Policy.VariablesEntry
+	55,  // 92: cerbos.runtime.v1.RunnablePrincipalPolicySet.Policy.resource_rules:type_name -> cerbos.runtime.v1.RunnablePrincipalPolicySet.Policy.ResourceRulesEntry
+	11,  // 93: cerbos.runtime.v1.RunnablePrincipalPolicySet.Policy.ordered_variables:type_name -> cerbos.runtime.v1.Variable
+	67,  // 94: cerbos.runtime.v1.RunnablePrincipalPolicySet.Policy.scope_permissions:type_name -> cerbos.policy.v1.ScopePermissions
+	56,  // 95: cerbos.runtime.v1.RunnablePrincipalPolicySet.Policy.constants:type_name -> cerbos.runtime.v1.RunnablePrincipalPolicySet.Policy.ConstantsEntry
+	72,  // 96: cerbos.runtime.v1.RunnablePrincipalPolicySet.Metadata.SourceAttributesEntry.value:type_name -> cerbos.policy.v1.SourceAttributes
+	12,  // 97: cerbos.runtime.v1.RunnablePrincipalPolicySet.Policy.ActionRule.condition:type_name -> cerbos.runtime.v1.Condition
+	70,  // 98: cerbos.runtime.v1.RunnablePrincipalPolicySet.Policy.ActionRule.effect:type_name -> cerbos.effect.v1.Effect
+	9,   // 99: cerbos.runtime.v1.RunnablePrincipalPolicySet.Policy.ActionRule.output:type_name -> cerbos.runtime.v1.Expr
+	10,  // 100: cerbos.runtime.v1.RunnablePrincipalPolicySet.Policy.ActionRule.emit_output:type_name -> cerbos.runtime.v1.Output
+	52,  // 101: cerbos.runtime.v1.RunnablePrincipalPolicySet.Policy.ResourceRules.action_rules:type_name -> cerbos.runtime.v1.RunnablePrincipalPolicySet.Policy.ActionRule
+	9,   // 102: cerbos.runtime.v1.RunnablePrincipalPolicySet.Policy.VariablesEntry.value:type_name -> cerbos.runtime.v1.Expr
+	53,  // 103: cerbos.runtime.v1.RunnablePrincipalPolicySet.Policy.ResourceRulesEntry.value:type_name -> cerbos.runtime.v1.RunnablePrincipalPolicySet.Policy.ResourceRules
+	73,  // 104: cerbos.runtime.v1.RunnablePrincipalPolicySet.Policy.ConstantsEntry.value:type_name -> google.protobuf.Value
+	9,   // 105: cerbos.runtime.v1.Output.When.rule_activated:type_name -> cerbos.runtime.v1.Expr
+	9,   // 106: cerbos.runtime.v1.Output.When.condition_not_met:type_name -> cerbos.runtime.v1.Expr
+	12,  // 107: cerbos.runtime.v1.Condition.ExprList.expr:type_name -> cerbos.runtime.v1.Condition
+	74,  // 108: cerbos.runtime.v1.CompileErrors.Err.position:type_name -> cerbos.source.v1.Position
+	74,  // 109: cerbos.runtime.v1.IndexBuildErrors.DuplicateDef.position:type_name -> cerbos.source.v1.Position
+	74,  // 110: cerbos.runtime.v1.IndexBuildErrors.MissingImport.position:type_name -> cerbos.source.v1.Position
+	75,  // 111: cerbos.runtime.v1.IndexBuildErrors.LoadFailure.error_details:type_name -> cerbos.source.v1.Error
+	74,  // 112: cerbos.runtime.v1.IndexBuildErrors.Disabled.position:type_name -> cerbos.source.v1.Position
+	113, // [113:113] is the sub-list for method output_type
+	113, // [113:113] is the sub-list for method input_type
+	113, // [113:113] is the sub-list for extension type_name
+	113, // [113:113] is the sub-list for extension extendee
+	0,   // [0:113] is the sub-list for field type_name
 }
 
 func init() { file_cerbos_runtime_v1_runtime_proto_init() }
@@ -3854,7 +3919,7 @@ func file_cerbos_runtime_v1_runtime_proto_init() {
 			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
 			RawDescriptor: unsafe.Slice(unsafe.StringData(file_cerbos_runtime_v1_runtime_proto_rawDesc), len(file_cerbos_runtime_v1_runtime_proto_rawDesc)),
 			NumEnums:      0,
-			NumMessages:   66,
+			NumMessages:   67,
 			NumExtensions: 0,
 			NumServices:   0,
 		},
diff --git a/api/genpb/cerbos/runtime/v1/runtime_hashpb.pb.go b/api/genpb/cerbos/runtime/v1/runtime_hashpb.pb.go
index e909d2ed..f24ed8d4 100644
--- a/api/genpb/cerbos/runtime/v1/runtime_hashpb.pb.go
+++ b/api/genpb/cerbos/runtime/v1/runtime_hashpb.pb.go
@@ -296,6 +296,14 @@ func (m *IndexBuildErrors_ScopePermissionsConflicts) HashPB(hasher hash.Hash, ig
 	}
 }
 
+// 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 *IndexBuildErrors_ScopePermissionsOrdering) HashPB(hasher hash.Hash, ignore map[string]struct{}) {
+	if m != nil {
+		cerbos_runtime_v1_IndexBuildErrors_ScopePermissionsOrdering_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 *IndexBuildErrors_LoadFailure) HashPB(hasher hash.Hash, ignore map[string]struct{}) {
diff --git a/api/genpb/cerbos/runtime/v1/runtime_vtproto.pb.go b/api/genpb/cerbos/runtime/v1/runtime_vtproto.pb.go
index c91db1c6..af247eaa 100644
--- a/api/genpb/cerbos/runtime/v1/runtime_vtproto.pb.go
+++ b/api/genpb/cerbos/runtime/v1/runtime_vtproto.pb.go
@@ -2951,6 +2951,46 @@ func (m *IndexBuildErrors_ScopePermissionsConflicts) MarshalToSizedBufferVT(dAtA
 	return len(dAtA) - i, nil
 }
 
+func (m *IndexBuildErrors_ScopePermissionsOrdering) 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 *IndexBuildErrors_ScopePermissionsOrdering) MarshalToVT(dAtA []byte) (int, error) {
+	size := m.SizeVT()
+	return m.MarshalToSizedBufferVT(dAtA[:size])
+}
+
+func (m *IndexBuildErrors_ScopePermissionsOrdering) 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 len(m.Scope) > 0 {
+		i -= len(m.Scope)
+		copy(dAtA[i:], m.Scope)
+		i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Scope)))
+		i--
+		dAtA[i] = 0xa
+	}
+	return len(dAtA) - i, nil
+}
+
 func (m *IndexBuildErrors_LoadFailure) MarshalVT() (dAtA []byte, err error) {
 	if m == nil {
 		return nil, nil
@@ -3119,6 +3159,18 @@ func (m *IndexBuildErrors) MarshalToSizedBufferVT(dAtA []byte) (int, error) {
 		i -= len(m.unknownFields)
 		copy(dAtA[i:], m.unknownFields)
 	}
+	if len(m.ScopePermissionsOrdering) > 0 {
+		for iNdEx := len(m.ScopePermissionsOrdering) - 1; iNdEx >= 0; iNdEx-- {
+			size, err := m.ScopePermissionsOrdering[iNdEx].MarshalToSizedBufferVT(dAtA[:i])
+			if err != nil {
+				return 0, err
+			}
+			i -= size
+			i = protohelpers.EncodeVarint(dAtA, i, uint64(size))
+			i--
+			dAtA[i] = 0x4a
+		}
+	}
 	if len(m.ScopePermissionsConflicts) > 0 {
 		for iNdEx := len(m.ScopePermissionsConflicts) - 1; iNdEx >= 0; iNdEx-- {
 			size, err := m.ScopePermissionsConflicts[iNdEx].MarshalToSizedBufferVT(dAtA[:i])
@@ -4599,6 +4651,20 @@ func (m *IndexBuildErrors_ScopePermissionsConflicts) SizeVT() (n int) {
 	return n
 }
 
+func (m *IndexBuildErrors_ScopePermissionsOrdering) SizeVT() (n int) {
+	if m == nil {
+		return 0
+	}
+	var l int
+	_ = l
+	l = len(m.Scope)
+	if l > 0 {
+		n += 1 + l + protohelpers.SizeOfVarint(uint64(l))
+	}
+	n += len(m.unknownFields)
+	return n
+}
+
 func (m *IndexBuildErrors_LoadFailure) SizeVT() (n int) {
 	if m == nil {
 		return 0
@@ -4709,6 +4775,12 @@ func (m *IndexBuildErrors) SizeVT() (n int) {
 			n += 1 + l + protohelpers.SizeOfVarint(uint64(l))
 		}
 	}
+	if len(m.ScopePermissionsOrdering) > 0 {
+		for _, e := range m.ScopePermissionsOrdering {
+			l = e.SizeVT()
+			n += 1 + l + protohelpers.SizeOfVarint(uint64(l))
+		}
+	}
 	n += len(m.unknownFields)
 	return n
 }
@@ -13562,6 +13634,89 @@ func (m *IndexBuildErrors_ScopePermissionsConflicts) UnmarshalVT(dAtA []byte) er
 	}
 	return nil
 }
+func (m *IndexBuildErrors_ScopePermissionsOrdering) 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 protohelpers.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: IndexBuildErrors_ScopePermissionsOrdering: wiretype end group for non-group")
+		}
+		if fieldNum <= 0 {
+			return fmt.Errorf("proto: IndexBuildErrors_ScopePermissionsOrdering: illegal tag %d (wire type %d)", fieldNum, wire)
+		}
+		switch fieldNum {
+		case 1:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Scope", wireType)
+			}
+			var stringLen uint64
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return protohelpers.ErrIntOverflow
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				stringLen |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			intStringLen := int(stringLen)
+			if intStringLen < 0 {
+				return protohelpers.ErrInvalidLength
+			}
+			postIndex := iNdEx + intStringLen
+			if postIndex < 0 {
+				return protohelpers.ErrInvalidLength
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.Scope = string(dAtA[iNdEx:postIndex])
+			iNdEx = postIndex
+		default:
+			iNdEx = preIndex
+			skippy, err := protohelpers.Skip(dAtA[iNdEx:])
+			if err != nil {
+				return err
+			}
+			if (skippy < 0) || (iNdEx+skippy) < 0 {
+				return protohelpers.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 (m *IndexBuildErrors_LoadFailure) UnmarshalVT(dAtA []byte) error {
 	l := len(dAtA)
 	iNdEx := 0
@@ -14177,6 +14332,40 @@ func (m *IndexBuildErrors) UnmarshalVT(dAtA []byte) error {
 				return err
 			}
 			iNdEx = postIndex
+		case 9:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field ScopePermissionsOrdering", wireType)
+			}
+			var msglen int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return protohelpers.ErrIntOverflow
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				msglen |= int(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if msglen < 0 {
+				return protohelpers.ErrInvalidLength
+			}
+			postIndex := iNdEx + msglen
+			if postIndex < 0 {
+				return protohelpers.ErrInvalidLength
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.ScopePermissionsOrdering = append(m.ScopePermissionsOrdering, &IndexBuildErrors_ScopePermissionsOrdering{})
+			if err := m.ScopePermissionsOrdering[len(m.ScopePermissionsOrdering)-1].UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil {
+				return err
+			}
+			iNdEx = postIndex
 		default:
 			iNdEx = preIndex
 			skippy, err := protohelpers.Skip(dAtA[iNdEx:])
diff --git a/api/private/cerbos/runtime/v1/runtime.proto b/api/private/cerbos/runtime/v1/runtime.proto
index 912f99e4..9b0b742d 100644
--- a/api/private/cerbos/runtime/v1/runtime.proto
+++ b/api/private/cerbos/runtime/v1/runtime.proto
@@ -281,6 +281,10 @@ message IndexBuildErrors {
     string scope = 1;
   }
 
+  message ScopePermissionsOrdering {
+    string scope = 1;
+  }
+
   message LoadFailure {
     string file = 1;
     string error = 2 [deprecated = true];
@@ -301,6 +305,7 @@ message IndexBuildErrors {
   repeated Disabled disabled_defs = 6;
   repeated MissingScope missing_scope_details = 7;
   repeated ScopePermissionsConflicts scope_permissions_conflicts = 8;
+  repeated ScopePermissionsOrdering scope_permissions_ordering = 9;
 }
 
 message Errors {
diff --git a/cmd/cerbos/compile/internal/lint/display.go b/cmd/cerbos/compile/internal/lint/display.go
index 26d1da7c..fb3a9781 100644
--- a/cmd/cerbos/compile/internal/lint/display.go
+++ b/cmd/cerbos/compile/internal/lint/display.go
@@ -98,5 +98,13 @@ func displayList(p *printer.Printer, errs *index.BuildError) error {
 		p.Println()
 	}
 
+	if len(errs.ScopePermissionsOrdering) > 0 {
+		p.Println(colored.Header("Scope permission ordering errors"))
+		for _, spo := range errs.ScopePermissionsOrdering {
+			p.Printf("scope %s: REQUIRE_PARENTAL_CONSENT can only be used at leaf nodes\n", spo.Scope)
+		}
+		p.Println()
+	}
+
 	return compileerrors.ErrFailed
 }
diff --git a/internal/storage/index/builder.go b/internal/storage/index/builder.go
index f3a9e9da..a5e21cbf 100644
--- a/internal/storage/index/builder.go
+++ b/internal/storage/index/builder.go
@@ -39,8 +39,8 @@ type BuildError struct {
 }
 
 func (ibe *BuildError) Error() string {
-	return fmt.Sprintf("failed to build index: missing imports=%d, missing scopes=%d, duplicate definitions=%d, load failures=%d, scope permission conflicts=%d",
-		len(ibe.MissingImports), len(ibe.MissingScopeDetails), len(ibe.DuplicateDefs), len(ibe.LoadFailures), len(ibe.ScopePermissionsConflicts))
+	return fmt.Sprintf("failed to build index: missing imports=%d, missing scopes=%d, duplicate definitions=%d, load failures=%d, scope permission conflicts=%d, scope permission ordering errors=%d",
+		len(ibe.MissingImports), len(ibe.MissingScopeDetails), len(ibe.DuplicateDefs), len(ibe.LoadFailures), len(ibe.ScopePermissionsConflicts), len(ibe.ScopePermissionsOrdering))
 }
 
 type buildOptions struct {
@@ -161,21 +161,22 @@ func build(ctx context.Context, fsys fs.FS, opts buildOptions) (Index, error) {
 }
 
 type indexBuilder struct {
-	executables                   ModuleIDSet
-	modIDToFile                   map[namer.ModuleID]string
+	missingResourceScopes         map[string]map[string]map[string]struct{}
+	foundRolePolicyResourceScopes map[string]map[string]map[string]struct{}
 	fileToModID                   map[string]namer.ModuleID
 	dependents                    map[namer.ModuleID]ModuleIDSet
 	dependencies                  map[namer.ModuleID]ModuleIDSet
 	missingScopes                 map[string]map[string]struct{}
+	modIDToFile                   map[namer.ModuleID]string
 	sharedScopePermissionGroups   map[string]map[policyv1.ScopePermissions]struct{}
+	executables                   ModuleIDSet
 	conflictingScopes             map[string]struct{}
-	missingResourceScopes         map[string]map[string]map[string]struct{} // map[{resource}]map[{scope}]map[{version}]struct{}
-	foundRolePolicyResourceScopes map[string]map[string]map[string]struct{} // map[{resource}]map[{scope}]map[{version}]struct{}
 	missing                       map[namer.ModuleID][]*runtimev1.IndexBuildErrors_MissingImport
 	stats                         *statsCollector
-	duplicates                    []*runtimev1.IndexBuildErrors_DuplicateDef
+	scopeTree                     *scopeNode
 	loadFailures                  []*runtimev1.IndexBuildErrors_LoadFailure
 	disabled                      []*runtimev1.IndexBuildErrors_Disabled
+	duplicates                    []*runtimev1.IndexBuildErrors_DuplicateDef
 }
 
 func newIndexBuilder() *indexBuilder {
@@ -192,6 +193,7 @@ func newIndexBuilder() *indexBuilder {
 		missingResourceScopes:         make(map[string]map[string]map[string]struct{}),
 		foundRolePolicyResourceScopes: make(map[string]map[string]map[string]struct{}),
 		stats:                         newStatsCollector(),
+		scopeTree:                     &scopeNode{children: make(map[string]*scopeNode)},
 	}
 }
 
@@ -235,6 +237,47 @@ func (idx *indexBuilder) addDisabled(file string, srcCtx parser.SourceCtx, p *po
 	})
 }
 
+type scopeNode struct {
+	children         map[string]*scopeNode
+	scope            string
+	scopePermissions policyv1.ScopePermissions
+}
+
+func (n *scopeNode) collectInvalidScopes(invalidScopes []string) []string {
+	if n.scopePermissions == policyv1.ScopePermissions_SCOPE_PERMISSIONS_REQUIRE_PARENTAL_CONSENT_FOR_ALLOWS && len(n.children) > 0 {
+		invalidScopes = append(invalidScopes, n.scope)
+	}
+
+	for _, child := range n.children {
+		invalidScopes = child.collectInvalidScopes(invalidScopes)
+	}
+
+	return invalidScopes
+}
+
+func (idx *indexBuilder) addScopeNode(scope string, permission policyv1.ScopePermissions) {
+	if scope == "" {
+		idx.scopeTree.scopePermissions = permission
+		return
+	}
+
+	parts := strings.Split(scope, ".")
+	node := idx.scopeTree
+	for _, part := range parts {
+		if node.children == nil {
+			node.children = make(map[string]*scopeNode)
+		}
+		child, ok := node.children[part]
+		if !ok {
+			child = &scopeNode{scope: part}
+			node.children[part] = child
+		}
+		node = child
+	}
+
+	node.scopePermissions = permission
+}
+
 func (idx *indexBuilder) addPolicy(file string, srcCtx parser.SourceCtx, p policy.Wrapper) {
 	// Is this policy defined elsewhere?
 	if otherFile, ok := idx.modIDToFile[p.ID]; ok && (otherFile != file) {
@@ -330,6 +373,10 @@ func (idx *indexBuilder) addPolicy(file string, srcCtx parser.SourceCtx, p polic
 		// not executable
 	}
 
+	if p.Kind == policy.ResourceKind || p.Kind == policy.PrincipalKind || p.Kind == policy.RolePolicyKind {
+		idx.addScopeNode(p.Scope, scopePermission)
+	}
+
 	sharedScope, ok := idx.sharedScopePermissionGroups[p.Scope]
 	if !ok {
 		sharedScope = make(map[policyv1.ScopePermissions]struct{})
@@ -444,10 +491,11 @@ func (idx *indexBuilder) addDep(child, parent namer.ModuleID) {
 
 func (idx *indexBuilder) build(fsys fs.FS, opts buildOptions) (*index, error) {
 	logger := zap.L().Named("index")
+	var buildErr *BuildError
 
 	nErr := len(idx.missing) + len(idx.duplicates) + len(idx.loadFailures) + len(idx.missingScopes) + len(idx.conflictingScopes)
 	if nErr > 0 {
-		err := &BuildError{
+		buildErr = &BuildError{
 			IndexBuildErrors: &runtimev1.IndexBuildErrors{
 				DuplicateDefs: idx.duplicates,
 				LoadFailures:  idx.loadFailures,
@@ -457,7 +505,7 @@ func (idx *indexBuilder) build(fsys fs.FS, opts buildOptions) (*index, error) {
 		}
 
 		for _, missing := range idx.missing {
-			err.MissingImports = append(err.MissingImports, missing...)
+			buildErr.MissingImports = append(buildErr.MissingImports, missing...)
 		}
 
 		for policyKey, descendants := range idx.missingScopes {
@@ -467,27 +515,44 @@ func (idx *indexBuilder) build(fsys fs.FS, opts buildOptions) (*index, error) {
 			}
 			sort.Strings(sortedDescendants)
 
-			err.MissingScopeDetails = append(err.MissingScopeDetails, &runtimev1.IndexBuildErrors_MissingScope{
+			buildErr.MissingScopeDetails = append(buildErr.MissingScopeDetails, &runtimev1.IndexBuildErrors_MissingScope{
 				Descendants:   sortedDescendants,
 				MissingPolicy: policyKey,
 			})
 		}
 
 		if len(idx.missingScopes) > 0 {
-			sort.Slice(err.MissingScopeDetails, func(i, j int) bool {
-				return sort.StringsAreSorted([]string{err.MissingScopeDetails[i].MissingPolicy, err.MissingScopeDetails[j].MissingPolicy})
+			sort.Slice(buildErr.MissingScopeDetails, func(i, j int) bool {
+				return sort.StringsAreSorted([]string{buildErr.MissingScopeDetails[i].MissingPolicy, buildErr.MissingScopeDetails[j].MissingPolicy})
 			})
 		}
 
 		for s := range idx.conflictingScopes {
-			err.ScopePermissionsConflicts = append(err.ScopePermissionsConflicts, &runtimev1.IndexBuildErrors_ScopePermissionsConflicts{
+			buildErr.ScopePermissionsConflicts = append(buildErr.ScopePermissionsConflicts, &runtimev1.IndexBuildErrors_ScopePermissionsConflicts{
 				Scope: s,
 			})
 		}
+	}
+
+	invalidScopes := []string{}
+	if invalidScopes = idx.scopeTree.collectInvalidScopes(invalidScopes); len(invalidScopes) > 0 {
+		if buildErr == nil {
+			buildErr = &BuildError{
+				IndexBuildErrors: &runtimev1.IndexBuildErrors{},
+			}
+		}
 
-		logBuildFailure(logger, opts.buildFailureLogLevel, err)
+		buildErr.nErr += len(invalidScopes)
+		for _, s := range invalidScopes {
+			buildErr.ScopePermissionsOrdering = append(buildErr.ScopePermissionsOrdering, &runtimev1.IndexBuildErrors_ScopePermissionsOrdering{
+				Scope: s,
+			})
+		}
+	}
 
-		return nil, err
+	if buildErr != nil {
+		logBuildFailure(logger, opts.buildFailureLogLevel, buildErr)
+		return nil, buildErr
 	}
 
 	logger.Info(fmt.Sprintf("Found %d executable policies", len(idx.executables)))
@@ -543,6 +608,10 @@ func logBuildFailure(logger *zap.Logger, level zapcore.Level, err *BuildError) {
 		fields = append(fields, zap.Any("scope_permissions", err.ScopePermissionsConflicts))
 	}
 
+	if len(err.ScopePermissionsOrdering) > 0 {
+		fields = append(fields, zap.Any("scope_permissions", err.ScopePermissionsOrdering))
+	}
+
 	ce.Write(fields...)
 }
 
diff --git a/internal/svc/playground_svc.go b/internal/svc/playground_svc.go
index 5734a041..72c5cb8d 100644
--- a/internal/svc/playground_svc.go
+++ b/internal/svc/playground_svc.go
@@ -379,15 +379,21 @@ func processLintErrors(ctx context.Context, errs *index.BuildError) *responsev1.
 		})
 	}
 
-	for _, scopePermissionConflict := range errs.ScopePermissionsConflicts {
+	for _, c := range errs.ScopePermissionsConflicts {
 		errors = append(errors, &responsev1.PlaygroundFailure_Error{
 			Error: fmt.Sprintf(
 				"Policies sharing scope %s have conflicting scopePermissions\n",
-				scopePermissionConflict.Scope,
+				c.Scope,
 			),
 		})
 	}
 
+	for _, e := range errs.ScopePermissionsOrdering {
+		errors = append(errors, &responsev1.PlaygroundFailure_Error{
+			Error: fmt.Sprintf("scope %s: REQUIRE_PARENTAL_CONSENT can only be used at leaf nodes\n", e.Scope),
+		})
+	}
+
 	SetHTTPStatusCode(ctx, http.StatusBadRequest)
 
 	return &responsev1.PlaygroundFailure{Errors: errors}


Test patch:
diff --git a/cmd/cerbosctl/put/put_test.go b/cmd/cerbosctl/put/put_test.go
index 3ae9bf9e..001c429f 100644
--- a/cmd/cerbosctl/put/put_test.go
+++ b/cmd/cerbosctl/put/put_test.go
@@ -102,6 +102,8 @@ func testPutCmd(clientCtx *cmdclient.Context, globals *flagset.Globals) func(*te
 					"resource.album_object.vdefault",
 					"resource.arn:aws:sns:us-east-1:123456789012:topic-a.vdefault",
 					"resource.calendar_entry.vdefault",
+					"resource.calendar_entry.vdefault/acme",
+					"resource.calendar_entry.vdefault/acme.sales",
 					"resource.equipment_request.vdefault",
 					"resource.equipment_request.vdefault/acme",
 					"resource.example.vdefault",
diff --git a/internal/test/testdata/.jsonschema/IndexBuilderTestCase.schema.json b/internal/test/testdata/.jsonschema/IndexBuilderTestCase.schema.json
index 19360e63..0051c4eb 100644
--- a/internal/test/testdata/.jsonschema/IndexBuilderTestCase.schema.json
+++ b/internal/test/testdata/.jsonschema/IndexBuilderTestCase.schema.json
@@ -74,6 +74,12 @@
           "items": {
             "$ref": "#/definitions/cerbos.runtime.v1.IndexBuildErrors.ScopePermissionsConflicts"
           }
+        },
+        "scopePermissionsOrdering": {
+          "type": "array",
+          "items": {
+            "$ref": "#/definitions/cerbos.runtime.v1.IndexBuildErrors.ScopePermissionsOrdering"
+          }
         }
       }
     },
@@ -176,6 +182,15 @@
         }
       }
     },
+    "cerbos.runtime.v1.IndexBuildErrors.ScopePermissionsOrdering": {
+      "type": "object",
+      "additionalProperties": false,
+      "properties": {
+        "scope": {
+          "type": "string"
+        }
+      }
+    },
     "cerbos.source.v1.Error": {
       "type": "object",
       "additionalProperties": false,
diff --git a/internal/test/testdata/.jsonschema/InspectTestCase.schema.json b/internal/test/testdata/.jsonschema/InspectTestCase.schema.json
index fb9ec617..de4ae4b1 100644
--- a/internal/test/testdata/.jsonschema/InspectTestCase.schema.json
+++ b/internal/test/testdata/.jsonschema/InspectTestCase.schema.json
@@ -983,6 +983,12 @@
           "items": {
             "$ref": "#/definitions/cerbos.runtime.v1.IndexBuildErrors.ScopePermissionsConflicts"
           }
+        },
+        "scopePermissionsOrdering": {
+          "type": "array",
+          "items": {
+            "$ref": "#/definitions/cerbos.runtime.v1.IndexBuildErrors.ScopePermissionsOrdering"
+          }
         }
       }
     },
@@ -1085,6 +1091,15 @@
         }
       }
     },
+    "cerbos.runtime.v1.IndexBuildErrors.ScopePermissionsOrdering": {
+      "type": "object",
+      "additionalProperties": false,
+      "properties": {
+        "scope": {
+          "type": "string"
+        }
+      }
+    },
     "cerbos.source.v1.Error": {
       "type": "object",
       "additionalProperties": false,
diff --git a/internal/test/testdata/index/intermediate_require_parental_consent.yaml b/internal/test/testdata/index/intermediate_require_parental_consent.yaml
new file mode 100644
index 00000000..4f839207
--- /dev/null
+++ b/internal/test/testdata/index/intermediate_require_parental_consent.yaml
@@ -0,0 +1,44 @@
+# yaml-language-server: $schema=../.jsonschema/IndexBuilderTestCase.schema.json
+---
+wantErrList:
+  scopePermissionsOrdering:
+    - scope: acme
+files:
+  "resource.yaml": |-
+    ---
+    apiVersion: api.cerbos.dev/v1
+    resourcePolicy:
+      resource: leave_request
+      rules:
+      - actions: ['*']
+        effect: EFFECT_ALLOW
+        roles:
+        - admin
+      version: "20210210"
+
+  "resource_acme.yaml": |-
+    ---
+    apiVersion: api.cerbos.dev/v1
+    resourcePolicy:
+      resource: leave_request
+      scope: "acme"
+      scopePermissions: SCOPE_PERMISSIONS_REQUIRE_PARENTAL_CONSENT_FOR_ALLOWS
+      rules:
+      - actions: ["view:public"]
+        roles:
+        - admin
+        effect: EFFECT_ALLOW
+      version: "20210210"
+
+  "resource_acme_hr.yaml": |-
+    ---
+    apiVersion: api.cerbos.dev/v1
+    resourcePolicy:
+      resource: leave_request
+      scope: "acme.hr"
+      rules:
+      - actions: ["view:public"]
+        roles:
+        - admin
+        effect: EFFECT_ALLOW
+      version: "20210210"