Repository: kythe/kythe
Pull request #5456 (2022-11-29 15:48:26)

feat(serving): trigram search index for xref pages


Patch:
diff --git a/external.bzl b/external.bzl
index e4c763e0b..5740b57ff 100644
--- a/external.bzl
+++ b/external.bzl
@@ -405,6 +405,13 @@ def _go_dependencies():
         version = "v0.0.0-20210312221358-fbca930ec8ed",
     )
 
+    go_repository(
+        name = "com_github_google_codesearch",
+        importpath = "github.com/google/codesearch",
+        sum = "h1:VlyAH+AntnIbGGArOUs6sEBdPVwYvf1e8Uw3/TC77cA=",
+        version = "v1.2.0",
+    )
+
     go_repository(
         name = "com_github_creachadair_staticfile",
         importpath = "github.com/creachadair/staticfile",
diff --git a/go.mod b/go.mod
index 0b5bc978f..58717c21a 100644
--- a/go.mod
+++ b/go.mod
@@ -13,6 +13,7 @@ require (
 	github.com/golang/protobuf v1.5.2
 	github.com/golang/snappy v0.0.4
 	github.com/google/brotli/go/cbrotli v0.0.0-20210804124202-19d86fb9a60a
+	github.com/google/codesearch v1.2.0 // indirect
 	github.com/google/go-cmp v0.5.6
 	github.com/google/orderedcode v0.0.1
 	github.com/google/subcommands v1.2.0
diff --git a/go.sum b/go.sum
index 03c4e7b40..5c4d5f041 100644
--- a/go.sum
+++ b/go.sum
@@ -127,6 +127,8 @@ github.com/google/brotli/go/cbrotli v0.0.0-20210804124202-19d86fb9a60a h1:Hxy9Hj
 github.com/google/brotli/go/cbrotli v0.0.0-20210804124202-19d86fb9a60a/go.mod h1:nOPhAkwVliJdNTkj3gXpljmWhjc4wCaVqbMJcPKWP4s=
 github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
 github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
+github.com/google/codesearch v1.2.0 h1:VlyAH+AntnIbGGArOUs6sEBdPVwYvf1e8Uw3/TC77cA=
+github.com/google/codesearch v1.2.0/go.mod h1:9wQjQDVAP7Mvt96tw1KqVeXncdBLOWUYdxRiHlsG6Xc=
 github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
 github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
 github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
diff --git a/kythe/go/serving/xrefs/BUILD b/kythe/go/serving/xrefs/BUILD
index fab8adede..134d6a329 100644
--- a/kythe/go/serving/xrefs/BUILD
+++ b/kythe/go/serving/xrefs/BUILD
@@ -30,6 +30,7 @@ go_library(
         "//kythe/proto:xref_go_proto",
         "//kythe/proto:xref_serving_go_proto",
         "@com_github_golang_snappy//:go_default_library",
+        "@com_github_google_codesearch//index:go_default_library",
         "@org_bitbucket_creachadair_stringset//:go_default_library",
         "@org_golang_google_grpc//codes:go_default_library",
         "@org_golang_google_grpc//status:go_default_library",
diff --git a/kythe/go/serving/xrefs/xrefs.go b/kythe/go/serving/xrefs/xrefs.go
index a8506e071..0cbd19ffc 100644
--- a/kythe/go/serving/xrefs/xrefs.go
+++ b/kythe/go/serving/xrefs/xrefs.go
@@ -854,7 +854,8 @@ func (t *Table) CrossReferences(ctx context.Context, req *xpb.CrossReferencesReq
 			}
 		}
 
-		for _, idx := range cr.PageIndex {
+		pageSet := filter.PageSet(cr)
+		for _, idx := range cr.GetPageIndex() {
 			// Filter anchor pages based on requested build configs
 			if len(buildConfigs) != 0 && !buildConfigs.Contains(idx.BuildConfig) && !xrefs.IsRelatedNodeKind(relatedKinds, idx.Kind) {
 				continue
@@ -862,6 +863,10 @@ func (t *Table) CrossReferences(ctx context.Context, req *xpb.CrossReferencesReq
 
 			switch {
 			case xrefs.IsDefKind(req.DefinitionKind, idx.Kind, cr.Incomplete):
+				if !pageSet.Contains(idx) {
+					reply.Filtered.Definitions += int64(idx.Count)
+					continue
+				}
 				reply.Total.Definitions += int64(idx.Count)
 				if wantMoreCrossRefs && !stats.skipPage(idx) {
 					p, filtered, err := getFilteredPage(ctx, idx.PageKey)
@@ -873,6 +878,10 @@ func (t *Table) CrossReferences(ctx context.Context, req *xpb.CrossReferencesReq
 					stats.addAnchors(&crs.Definition, p.Group)
 				}
 			case xrefs.IsDeclKind(req.DeclarationKind, idx.Kind, cr.Incomplete):
+				if !pageSet.Contains(idx) {
+					reply.Filtered.Declarations += int64(idx.Count)
+					continue
+				}
 				reply.Total.Declarations += int64(idx.Count)
 				if wantMoreCrossRefs && !stats.skipPage(idx) {
 					p, filtered, err := getFilteredPage(ctx, idx.PageKey)
@@ -884,6 +893,10 @@ func (t *Table) CrossReferences(ctx context.Context, req *xpb.CrossReferencesReq
 					stats.addAnchors(&crs.Declaration, p.Group)
 				}
 			case xrefs.IsRefKind(req.ReferenceKind, idx.Kind):
+				if !pageSet.Contains(idx) {
+					reply.Filtered.References += int64(idx.Count)
+					continue
+				}
 				reply.Total.References += int64(idx.Count)
 				if wantMoreCrossRefs && !stats.skipPage(idx) {
 					p, filtered, err := getFilteredPage(ctx, idx.PageKey)
@@ -898,16 +911,20 @@ func (t *Table) CrossReferences(ctx context.Context, req *xpb.CrossReferencesReq
 				var p *srvpb.PagedCrossReferences_Page
 
 				if len(req.Filter) > 0 && xrefs.IsRelatedNodeKind(relatedKinds, idx.Kind) {
-					reply.Total.RelatedNodesByRelation[idx.Kind] += int64(idx.Count)
-					if wantMoreCrossRefs && !stats.skipPage(idx) {
-						var filtered int
-						p, filtered, err = getFilteredPage(ctx, idx.PageKey)
-						if err != nil {
-							return nil, fmt.Errorf("internal error: error retrieving cross-references page: %v", idx.PageKey)
+					if pageSet.Contains(idx) {
+						reply.Total.RelatedNodesByRelation[idx.Kind] += int64(idx.Count)
+						if wantMoreCrossRefs && !stats.skipPage(idx) {
+							var filtered int
+							p, filtered, err = getFilteredPage(ctx, idx.PageKey)
+							if err != nil {
+								return nil, fmt.Errorf("internal error: error retrieving cross-references page: %v", idx.PageKey)
+							}
+							reply.Total.RelatedNodesByRelation[idx.Kind] -= int64(filtered) // update counts to reflect filtering
+							reply.Filtered.RelatedNodesByRelation[idx.Kind] += int64(filtered)
+							stats.addRelatedNodes(crs, p.Group)
 						}
-						reply.Total.RelatedNodesByRelation[idx.Kind] -= int64(filtered) // update counts to reflect filtering
-						reply.Filtered.RelatedNodesByRelation[idx.Kind] += int64(filtered)
-						stats.addRelatedNodes(crs, p.Group)
+					} else {
+						reply.Filtered.RelatedNodesByRelation[idx.Kind] += int64(idx.Count)
 					}
 				}
 
@@ -926,6 +943,10 @@ func (t *Table) CrossReferences(ctx context.Context, req *xpb.CrossReferencesReq
 					}
 				}
 			case xrefs.IsCallerKind(req.CallerKind, idx.Kind):
+				if !pageSet.Contains(idx) {
+					reply.Filtered.Callers += int64(idx.Count)
+					continue
+				}
 				reply.Total.Callers += int64(idx.Count)
 				if wantMoreCrossRefs && !stats.skipPage(idx) {
 					p, filtered, err := getFilteredPage(ctx, idx.PageKey)
diff --git a/kythe/go/serving/xrefs/xrefs_filter.go b/kythe/go/serving/xrefs/xrefs_filter.go
index c3319fedc..c135ee7b7 100644
--- a/kythe/go/serving/xrefs/xrefs_filter.go
+++ b/kythe/go/serving/xrefs/xrefs_filter.go
@@ -17,10 +17,16 @@
 package xrefs
 
 import (
+	"log"
+	"math"
 	"regexp"
+	"regexp/syntax"
 
+	"bitbucket.org/creachadair/stringset"
 	"kythe.io/kythe/go/util/kytheuri"
 
+	"github.com/google/codesearch/index"
+
 	cpb "kythe.io/kythe/proto/common_go_proto"
 	srvpb "kythe.io/kythe/proto/serving_go_proto"
 	xpb "kythe.io/kythe/proto/xref_go_proto"
@@ -40,10 +46,260 @@ func compileCorpusPathFilters(fs *xpb.CorpusPathFilters, pr PathResolver) (*corp
 			return nil, err
 		}
 		f.pattern = append(f.pattern, p)
+
+		if filter.GetType() == xpb.CorpusPathFilter_INCLUDE_ONLY {
+			f.corpusQuery, err = appendQuery(f.corpusQuery, filter.GetCorpus())
+			if err != nil {
+				return nil, err
+			}
+			f.rootQuery, err = appendQuery(f.rootQuery, filter.GetRoot())
+			if err != nil {
+				return nil, err
+			}
+			f.pathQuery, err = appendQuery(f.pathQuery, filter.GetPath())
+			if err != nil {
+				return nil, err
+			}
+			f.resolvedPathQuery, err = appendQuery(f.resolvedPathQuery, filter.GetResolvedPath())
+			if err != nil {
+				return nil, err
+			}
+		}
 	}
 	return f, nil
 }
 
+func appendQuery(qs []*index.Query, pattern string) ([]*index.Query, error) {
+	if pattern == "" {
+		return qs, nil
+	}
+	c, err := syntax.Parse(pattern, syntax.Perl)
+	if err != nil {
+		return nil, err
+	}
+	return append(qs, index.RegexpQuery(c)), nil
+}
+
+type pageSet struct{ KeySet stringset.Set }
+
+func (p *pageSet) Contains(i *srvpb.PagedCrossReferences_PageIndex) bool {
+	return p == nil || p.KeySet.Contains(i.GetPageKey())
+}
+
+func (f *corpusPathFilter) PageSet(set *srvpb.PagedCrossReferences) *pageSet {
+	idx := set.GetPageSearchIndex()
+	if idx == nil || len(f.corpusQuery)+len(f.rootQuery)+len(f.pathQuery)+len(f.resolvedPathQuery) == 0 {
+		return nil
+	}
+
+	if len(set.GetPageIndex()) >= math.MaxUint32 {
+		log.Printf("WARNING: too many pages to perform index search: %d", len(set.GetPageIndex()))
+		return nil
+	}
+
+	list := applyQueries(idx.GetByCorpus(), f.corpusQuery, nil)
+	list = applyQueries(idx.GetByRoot(), f.rootQuery, list)
+	list = applyQueries(idx.GetByPath(), f.pathQuery, list)
+	list = applyQueries(idx.GetByResolvedPath(), f.resolvedPathQuery, list)
+
+	if isAllPages(list) || len(list) == len(set.GetPageIndex()) {
+		return nil
+	}
+
+	s := stringset.NewSize(len(list))
+	for _, p := range list {
+		s.Add(set.GetPageIndex()[p].GetPageKey())
+	}
+
+	return &pageSet{s}
+}
+
+func applyQueries(p *srvpb.PagedCrossReferences_PageSearchIndex_Postings, qs []*index.Query, restrict []uint32) []uint32 {
+	if len(qs) == 0 {
+		return restrict
+	}
+	postings := diffDecodePostings(p)
+	for _, q := range qs {
+		restrict = applyQuery(postings, q, restrict)
+	}
+	return restrict
+}
+
+func diffDecodePostings(p *srvpb.PagedCrossReferences_PageSearchIndex_Postings) postings {
+	res := make(postings, len(p.GetIndex()))
+	for k, v := range p.GetIndex() {
+		res[k] = diffDecode(v.GetPageIndex())
+	}
+	return res
+}
+
+func diffDecode(s []uint32) []uint32 {
+	if len(s) == 0 {
+		return nil
+	}
+	res := make([]uint32, len(s))
+	res[0] = s[0]
+	for i, n := range s[1:] {
+		res[i+1] = res[i] + n
+	}
+	return res
+}
+
+type postings map[uint32][]uint32
+
+func tri(t string) uint32 {
+	return uint32(t[0])<<16 | uint32(t[1])<<8 | uint32(t[2])
+}
+
+func isAllPages(list []uint32) bool { return len(list) == 1 && list[0] == math.MaxUint32 }
+
+var allPages = []uint32{math.MaxUint32}
+
+func applyQuery(idx postings, q *index.Query, restrict []uint32) []uint32 {
+	if isAllPages(restrict) {
+		restrict = nil
+	}
+
+	var list []uint32
+	switch q.Op {
+	case index.QNone:
+		return []uint32{}
+	case index.QAll:
+		if restrict != nil {
+			return restrict
+		}
+		return allPages
+	case index.QAnd:
+		for _, t := range q.Trigram {
+			list = postingAnd(idx, list, tri(t), restrict)
+			if len(list) == 0 {
+				return []uint32{}
+			}
+		}
+		for _, sub := range q.Sub {
+			if list == nil {
+				list = restrict
+			}
+			list = applyQuery(idx, sub, list)
+			if len(list) == 0 {
+				return []uint32{}
+			}
+		}
+	case index.QOr:
+		for _, t := range q.Trigram {
+			list = postingOr(idx, list, tri(t), restrict)
+		}
+		for _, sub := range q.Sub {
+			subList := applyQuery(idx, sub, restrict)
+			list = mergeOr(list, subList)
+		}
+	}
+	return list
+}
+
+func postingList(idx postings, trigram uint32, restrict []uint32) []uint32 {
+	ps := idx[trigram]
+	list := make([]uint32, 0, len(ps))
+	for _, p := range ps {
+		if restrict != nil && !isAllPages(restrict) {
+			i := 0
+			for i < len(restrict) && restrict[i] < p {
+				i++
+			}
+			restrict = restrict[i:]
+			if len(restrict) == 0 || restrict[0] != p {
+				continue
+			}
+		}
+		list = append(list, p)
+	}
+	return list
+}
+
+func postingAnd(idx postings, list []uint32, trigram uint32, restrict []uint32) []uint32 {
+	if list == nil || isAllPages(list) {
+		return postingList(idx, trigram, restrict)
+	}
+
+	var l int
+	res := list[:0]
+	for _, p := range idx[trigram] {
+		if restrict != nil && !isAllPages(restrict) {
+			i := 0
+			for i < len(restrict) && restrict[i] < p {
+				i++
+			}
+			restrict = restrict[i:]
+			if len(restrict) == 0 || restrict[0] != p {
+				continue
+			}
+		}
+		for l < len(list) && list[l] < p {
+			l++
+		}
+		if l == len(list) {
+			return res
+		}
+		if list[l] != p {
+			continue
+		}
+		res = append(res, p)
+	}
+	return res
+}
+
+func mergeOr(l1, l2 []uint32) []uint32 {
+	var l []uint32
+	var i, j int
+	for i < len(l1) || j < len(l2) {
+		switch {
+		case j == len(l2) || (i < len(l1) && l1[i] < l2[j]):
+			l = append(l, l1[i])
+			i++
+		case i == len(l1) || (j < len(l2) && l1[i] > l2[j]):
+			l = append(l, l2[j])
+			j++
+		case l1[i] == l2[j]:
+			l = append(l, l1[i])
+			i++
+			j++
+		}
+	}
+	return l
+}
+
+func postingOr(idx postings, list []uint32, trigram uint32, restrict []uint32) []uint32 {
+	if list == nil {
+		return postingList(idx, trigram, restrict)
+	} else if isAllPages(list) {
+		return list
+	}
+
+	var l int
+	res := list[:0]
+	for _, p := range idx[trigram] {
+		if restrict != nil && !isAllPages(restrict) {
+			i := 0
+			for i < len(restrict) && restrict[i] < p {
+				i++
+			}
+			restrict = restrict[i:]
+			if len(restrict) == 0 || restrict[0] != p {
+				continue
+			}
+		}
+		for l < len(list) && list[l] < p {
+			res = append(res, list[l])
+			l++
+		}
+		if l != len(list) && list[l] == p {
+			l++
+		}
+		res = append(res, p)
+	}
+	return res
+}
+
 func compileCorpusPathFilter(f *xpb.CorpusPathFilter, pr PathResolver) (*corpusPathPattern, error) {
 	p := &corpusPathPattern{pathResolver: pr}
 	if f.GetType() == xpb.CorpusPathFilter_EXCLUDE {
@@ -95,6 +351,8 @@ func (p *corpusPathPattern) Allow(c *cpb.CorpusPath) bool {
 
 type corpusPathFilter struct {
 	pattern []*corpusPathPattern
+
+	corpusQuery, rootQuery, pathQuery, resolvedPathQuery []*index.Query
 }
 
 func (f *corpusPathFilter) Allow(c *cpb.CorpusPath) bool {
diff --git a/kythe/proto/serving.proto b/kythe/proto/serving.proto
index f8c04656b..49ed7d8b9 100644
--- a/kythe/proto/serving.proto
+++ b/kythe/proto/serving.proto
@@ -330,6 +330,31 @@ message PagedCrossReferences {
 
   // The source node's MarkedSource.
   kythe.proto.common.MarkedSource marked_source = 6;
+
+  message PageSearchIndex {
+    message Pages {
+      // Set of page indices.
+      //
+      // These indices are sorted and then encoded as the difference between
+      // consecutive elements.
+      //
+      // Example:
+      //   [2, 5, 6] would be encoded here as [2, 3, 1]
+      repeated uint32 page_index = 1 [packed = true];
+    }
+    message Postings {
+      // trigram -> set of pages containing trigram
+      map<uint32, Pages> index = 1;
+    }
+
+    Postings by_corpus = 1;
+    Postings by_root = 2;
+    Postings by_path = 3;
+    Postings by_resolved_path = 4;
+  }
+
+  // Trigram search index for this set's xref Pages.
+  PageSearchIndex page_search_index = 11;
 }
 
 // A single node's documentation for the xrefs Documentation API.
diff --git a/kythe/proto/serving_go_proto/serving.pb.go b/kythe/proto/serving_go_proto/serving.pb.go
index da8b73875..8a78aee45 100644
--- a/kythe/proto/serving_go_proto/serving.pb.go
+++ b/kythe/proto/serving_go_proto/serving.pb.go
@@ -1235,9 +1235,10 @@ type PagedCrossReferences struct {
 	Group        []*PagedCrossReferences_Group     `protobuf:"bytes,2,rep,name=group,proto3" json:"group,omitempty"`
 	PageIndex    []*PagedCrossReferences_PageIndex `protobuf:"bytes,3,rep,name=page_index,json=pageIndex,proto3" json:"page_index,omitempty"`
 	// Deprecated: Do not use.
-	TotalReferences int32                         `protobuf:"varint,4,opt,name=total_references,json=totalReferences,proto3" json:"total_references,omitempty"`
-	Incomplete      bool                          `protobuf:"varint,5,opt,name=incomplete,proto3" json:"incomplete,omitempty"`
-	MarkedSource    *common_go_proto.MarkedSource `protobuf:"bytes,6,opt,name=marked_source,json=markedSource,proto3" json:"marked_source,omitempty"`
+	TotalReferences int32                                 `protobuf:"varint,4,opt,name=total_references,json=totalReferences,proto3" json:"total_references,omitempty"`
+	Incomplete      bool                                  `protobuf:"varint,5,opt,name=incomplete,proto3" json:"incomplete,omitempty"`
+	MarkedSource    *common_go_proto.MarkedSource         `protobuf:"bytes,6,opt,name=marked_source,json=markedSource,proto3" json:"marked_source,omitempty"`
+	PageSearchIndex *PagedCrossReferences_PageSearchIndex `protobuf:"bytes,11,opt,name=page_search_index,json=pageSearchIndex,proto3" json:"page_search_index,omitempty"`
 }
 
 func (x *PagedCrossReferences) Reset() {
@@ -1329,6 +1330,13 @@ func (x *PagedCrossReferences) GetMarkedSource() *common_go_proto.MarkedSource {
 	return nil
 }
 
+func (x *PagedCrossReferences) GetPageSearchIndex() *PagedCrossReferences_PageSearchIndex {
+	if x != nil {
+		return x.PageSearchIndex
+	}
+	return nil
+}
+
 type Document struct {
 	state         protoimpl.MessageState
 	sizeCache     protoimpl.SizeCache
@@ -2362,6 +2370,171 @@ func (x *PagedCrossReferences_PageIndex) GetBuildConfig() string {
 	return ""
 }
 
+type PagedCrossReferences_PageSearchIndex struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	ByCorpus       *PagedCrossReferences_PageSearchIndex_Postings `protobuf:"bytes,1,opt,name=by_corpus,json=byCorpus,proto3" json:"by_corpus,omitempty"`
+	ByRoot         *PagedCrossReferences_PageSearchIndex_Postings `protobuf:"bytes,2,opt,name=by_root,json=byRoot,proto3" json:"by_root,omitempty"`
+	ByPath         *PagedCrossReferences_PageSearchIndex_Postings `protobuf:"bytes,3,opt,name=by_path,json=byPath,proto3" json:"by_path,omitempty"`
+	ByResolvedPath *PagedCrossReferences_PageSearchIndex_Postings `protobuf:"bytes,4,opt,name=by_resolved_path,json=byResolvedPath,proto3" json:"by_resolved_path,omitempty"`
+}
+
+func (x *PagedCrossReferences_PageSearchIndex) Reset() {
+	*x = PagedCrossReferences_PageSearchIndex{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_kythe_proto_serving_proto_msgTypes[29]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *PagedCrossReferences_PageSearchIndex) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*PagedCrossReferences_PageSearchIndex) ProtoMessage() {}
+
+func (x *PagedCrossReferences_PageSearchIndex) ProtoReflect() protoreflect.Message {
+	mi := &file_kythe_proto_serving_proto_msgTypes[29]
+	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 PagedCrossReferences_PageSearchIndex.ProtoReflect.Descriptor instead.
+func (*PagedCrossReferences_PageSearchIndex) Descriptor() ([]byte, []int) {
+	return file_kythe_proto_serving_proto_rawDescGZIP(), []int{13, 5}
+}
+
+func (x *PagedCrossReferences_PageSearchIndex) GetByCorpus() *PagedCrossReferences_PageSearchIndex_Postings {
+	if x != nil {
+		return x.ByCorpus
+	}
+	return nil
+}
+
+func (x *PagedCrossReferences_PageSearchIndex) GetByRoot() *PagedCrossReferences_PageSearchIndex_Postings {
+	if x != nil {
+		return x.ByRoot
+	}
+	return nil
+}
+
+func (x *PagedCrossReferences_PageSearchIndex) GetByPath() *PagedCrossReferences_PageSearchIndex_Postings {
+	if x != nil {
+		return x.ByPath
+	}
+	return nil
+}
+
+func (x *PagedCrossReferences_PageSearchIndex) GetByResolvedPath() *PagedCrossReferences_PageSearchIndex_Postings {
+	if x != nil {
+		return x.ByResolvedPath
+	}
+	return nil
+}
+
+type PagedCrossReferences_PageSearchIndex_Pages struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	PageIndex []uint32 `protobuf:"varint,1,rep,packed,name=page_index,json=pageIndex,proto3" json:"page_index,omitempty"`
+}
+
+func (x *PagedCrossReferences_PageSearchIndex_Pages) Reset() {
+	*x = PagedCrossReferences_PageSearchIndex_Pages{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_kythe_proto_serving_proto_msgTypes[30]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *PagedCrossReferences_PageSearchIndex_Pages) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*PagedCrossReferences_PageSearchIndex_Pages) ProtoMessage() {}
+
+func (x *PagedCrossReferences_PageSearchIndex_Pages) ProtoReflect() protoreflect.Message {
+	mi := &file_kythe_proto_serving_proto_msgTypes[30]
+	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 PagedCrossReferences_PageSearchIndex_Pages.ProtoReflect.Descriptor instead.
+func (*PagedCrossReferences_PageSearchIndex_Pages) Descriptor() ([]byte, []int) {
+	return file_kythe_proto_serving_proto_rawDescGZIP(), []int{13, 5, 0}
+}
+
+func (x *PagedCrossReferences_PageSearchIndex_Pages) GetPageIndex() []uint32 {
+	if x != nil {
+		return x.PageIndex
+	}
+	return nil
+}
+
+type PagedCrossReferences_PageSearchIndex_Postings struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Index map[uint32]*PagedCrossReferences_PageSearchIndex_Pages `protobuf:"bytes,1,rep,name=index,proto3" json:"index,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
+}
+
+func (x *PagedCrossReferences_PageSearchIndex_Postings) Reset() {
+	*x = PagedCrossReferences_PageSearchIndex_Postings{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_kythe_proto_serving_proto_msgTypes[31]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *PagedCrossReferences_PageSearchIndex_Postings) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*PagedCrossReferences_PageSearchIndex_Postings) ProtoMessage() {}
+
+func (x *PagedCrossReferences_PageSearchIndex_Postings) ProtoReflect() protoreflect.Message {
+	mi := &file_kythe_proto_serving_proto_msgTypes[31]
+	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 PagedCrossReferences_PageSearchIndex_Postings.ProtoReflect.Descriptor instead.
+func (*PagedCrossReferences_PageSearchIndex_Postings) Descriptor() ([]byte, []int) {
+	return file_kythe_proto_serving_proto_rawDescGZIP(), []int{13, 5, 1}
+}
+
+func (x *PagedCrossReferences_PageSearchIndex_Postings) GetIndex() map[uint32]*PagedCrossReferences_PageSearchIndex_Pages {
+	if x != nil {
+		return x.Index
+	}
+	return nil
+}
+
 type IdentifierMatch_Node struct {
 	state         protoimpl.MessageState
 	sizeCache     protoimpl.SizeCache
@@ -2375,7 +2548,7 @@ type IdentifierMatch_Node struct {
 func (x *IdentifierMatch_Node) Reset() {
 	*x = IdentifierMatch_Node{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_kythe_proto_serving_proto_msgTypes[29]
+		mi := &file_kythe_proto_serving_proto_msgTypes[33]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -2388,7 +2561,7 @@ func (x *IdentifierMatch_Node) String() string {
 func (*IdentifierMatch_Node) ProtoMessage() {}
 
 func (x *IdentifierMatch_Node) ProtoReflect() protoreflect.Message {
-	mi := &file_kythe_proto_serving_proto_msgTypes[29]
+	mi := &file_kythe_proto_serving_proto_msgTypes[33]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -2649,7 +2822,7 @@ var file_kythe_proto_serving_proto_rawDesc = []byte{
 	0x65, 0x52, 0x0c, 0x6d, 0x61, 0x72, 0x6b, 0x65, 0x64, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x22,
 	0x22, 0x0a, 0x04, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x0d, 0x0a, 0x09, 0x4f, 0x56, 0x45, 0x52, 0x52,
 	0x49, 0x44, 0x45, 0x53, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x45, 0x58, 0x54, 0x45, 0x4e, 0x44,
-	0x53, 0x10, 0x01, 0x22, 0x81, 0x0b, 0x0a, 0x14, 0x50, 0x61, 0x67, 0x65, 0x64, 0x43, 0x72, 0x6f,
+	0x53, 0x10, 0x01, 0x22, 0x9e, 0x11, 0x0a, 0x14, 0x50, 0x61, 0x67, 0x65, 0x64, 0x43, 0x72, 0x6f,
 	0x73, 0x73, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x12, 0x1d, 0x0a, 0x0a,
 	0x6d, 0x65, 0x72, 0x67, 0x65, 0x5f, 0x77, 0x69, 0x74, 0x68, 0x18, 0x07, 0x20, 0x03, 0x28, 0x09,
 	0x52, 0x09, 0x6d, 0x65, 0x72, 0x67, 0x65, 0x57, 0x69, 0x74, 0x68, 0x12, 0x23, 0x0a, 0x0d, 0x73,
@@ -2677,141 +2850,191 @@ var file_kythe_proto_serving_proto_rawDesc = []byte{
 	0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e,
 	0x6b, 0x79, 0x74, 0x68, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x6d,
 	0x6f, 0x6e, 0x2e, 0x4d, 0x61, 0x72, 0x6b, 0x65, 0x64, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52,
-	0x0c, 0x6d, 0x61, 0x72, 0x6b, 0x65, 0x64, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x1a, 0x56, 0x0a,
-	0x0b, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x65, 0x64, 0x4e, 0x6f, 0x64, 0x65, 0x12, 0x2d, 0x0a, 0x04,
-	0x6e, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x6b, 0x79, 0x74,
+	0x0c, 0x6d, 0x61, 0x72, 0x6b, 0x65, 0x64, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x65, 0x0a,
+	0x11, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x5f, 0x69, 0x6e, 0x64,
+	0x65, 0x78, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x39, 0x2e, 0x6b, 0x79, 0x74, 0x68, 0x65,
+	0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x2e, 0x50,
+	0x61, 0x67, 0x65, 0x64, 0x43, 0x72, 0x6f, 0x73, 0x73, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e,
+	0x63, 0x65, 0x73, 0x2e, 0x50, 0x61, 0x67, 0x65, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x49, 0x6e,
+	0x64, 0x65, 0x78, 0x52, 0x0f, 0x70, 0x61, 0x67, 0x65, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x49,
+	0x6e, 0x64, 0x65, 0x78, 0x1a, 0x56, 0x0a, 0x0b, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x65, 0x64, 0x4e,
+	0x6f, 0x64, 0x65, 0x12, 0x2d, 0x0a, 0x04, 0x6e, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28,
+	0x0b, 0x32, 0x19, 0x2e, 0x6b, 0x79, 0x74, 0x68, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e,
+	0x73, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x04, 0x6e, 0x6f,
+	0x64, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x6c, 0x18, 0x02, 0x20,
+	0x01, 0x28, 0x05, 0x52, 0x07, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x6c, 0x1a, 0xf6, 0x01, 0x0a,
+	0x06, 0x43, 0x61, 0x6c, 0x6c, 0x65, 0x72, 0x12, 0x3b, 0x0a, 0x06, 0x63, 0x61, 0x6c, 0x6c, 0x65,
+	0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x6b, 0x79, 0x74, 0x68, 0x65, 0x2e,
+	0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x2e, 0x45, 0x78,
+	0x70, 0x61, 0x6e, 0x64, 0x65, 0x64, 0x41, 0x6e, 0x63, 0x68, 0x6f, 0x72, 0x52, 0x06, 0x63, 0x61,
+	0x6c, 0x6c, 0x65, 0x72, 0x12, 0x27, 0x0a, 0x0f, 0x73, 0x65, 0x6d, 0x61, 0x6e, 0x74, 0x69, 0x63,
+	0x5f, 0x63, 0x61, 0x6c, 0x6c, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x73,
+	0x65, 0x6d, 0x61, 0x6e, 0x74, 0x69, 0x63, 0x43, 0x61, 0x6c, 0x6c, 0x65, 0x72, 0x12, 0x45, 0x0a,
+	0x0d, 0x6d, 0x61, 0x72, 0x6b, 0x65, 0x64, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x03,
+	0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x6b, 0x79, 0x74, 0x68, 0x65, 0x2e, 0x70, 0x72, 0x6f,
+	0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4d, 0x61, 0x72, 0x6b, 0x65, 0x64,
+	0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x0c, 0x6d, 0x61, 0x72, 0x6b, 0x65, 0x64, 0x53, 0x6f,
+	0x75, 0x72, 0x63, 0x65, 0x12, 0x3f, 0x0a, 0x08, 0x63, 0x61, 0x6c, 0x6c, 0x73, 0x69, 0x74, 0x65,
+	0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x6b, 0x79, 0x74, 0x68, 0x65, 0x2e, 0x70,
+	0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x2e, 0x45, 0x78, 0x70,
+	0x61, 0x6e, 0x64, 0x65, 0x64, 0x41, 0x6e, 0x63, 0x68, 0x6f, 0x72, 0x52, 0x08, 0x63, 0x61, 0x6c,
+	0x6c, 0x73, 0x69, 0x74, 0x65, 0x1a, 0xdb, 0x02, 0x0a, 0x05, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x12,
+	0x12, 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6b,
+	0x69, 0x6e, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x63, 0x6f, 0x6e,
+	0x66, 0x69, 0x67, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x62, 0x75, 0x69, 0x6c, 0x64,
+	0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x3b, 0x0a, 0x06, 0x61, 0x6e, 0x63, 0x68, 0x6f, 0x72,
+	0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x6b, 0x79, 0x74, 0x68, 0x65, 0x2e, 0x70,
+	0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x2e, 0x45, 0x78, 0x70,
+	0x61, 0x6e, 0x64, 0x65, 0x64, 0x41, 0x6e, 0x63, 0x68, 0x6f, 0x72, 0x52, 0x06, 0x61, 0x6e, 0x63,
+	0x68, 0x6f, 0x72, 0x12, 0x58, 0x0a, 0x0c, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x6e,
+	0x6f, 0x64, 0x65, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x6b, 0x79, 0x74, 0x68,
+	0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x2e,
+	0x50, 0x61, 0x67, 0x65, 0x64, 0x43, 0x72, 0x6f, 0x73, 0x73, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65,
+	0x6e, 0x63, 0x65, 0x73, 0x2e, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x65, 0x64, 0x4e, 0x6f, 0x64, 0x65,
+	0x52, 0x0b, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x65, 0x64, 0x4e, 0x6f, 0x64, 0x65, 0x12, 0x48, 0x0a,
+	0x06, 0x63, 0x61, 0x6c, 0x6c, 0x65, 0x72, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x30, 0x2e,
+	0x6b, 0x79, 0x74, 0x68, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x73, 0x65, 0x72, 0x76,
+	0x69, 0x6e, 0x67, 0x2e, 0x50, 0x61, 0x67, 0x65, 0x64, 0x43, 0x72, 0x6f, 0x73, 0x73, 0x52, 0x65,
+	0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x2e, 0x43, 0x61, 0x6c, 0x6c, 0x65, 0x72, 0x52,
+	0x06, 0x63, 0x61, 0x6c, 0x6c, 0x65, 0x72, 0x12, 0x3a, 0x0a, 0x09, 0x66, 0x69, 0x6c, 0x65, 0x5f,
+	0x69, 0x6e, 0x66, 0x6f, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x6b, 0x79, 0x74,
 	0x68, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67,
-	0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x04, 0x6e, 0x6f, 0x64, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6f,
-	0x72, 0x64, 0x69, 0x6e, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x6f, 0x72,
-	0x64, 0x69, 0x6e, 0x61, 0x6c, 0x1a, 0xf6, 0x01, 0x0a, 0x06, 0x43, 0x61, 0x6c, 0x6c, 0x65, 0x72,
-	0x12, 0x3b, 0x0a, 0x06, 0x63, 0x61, 0x6c, 0x6c, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b,
-	0x32, 0x23, 0x2e, 0x6b, 0x79, 0x74, 0x68, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x73,
-	0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x2e, 0x45, 0x78, 0x70, 0x61, 0x6e, 0x64, 0x65, 0x64, 0x41,
-	0x6e, 0x63, 0x68, 0x6f, 0x72, 0x52, 0x06, 0x63, 0x61, 0x6c, 0x6c, 0x65, 0x72, 0x12, 0x27, 0x0a,
-	0x0f, 0x73, 0x65, 0x6d, 0x61, 0x6e, 0x74, 0x69, 0x63, 0x5f, 0x63, 0x61, 0x6c, 0x6c, 0x65, 0x72,
-	0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x73, 0x65, 0x6d, 0x61, 0x6e, 0x74, 0x69, 0x63,
-	0x43, 0x61, 0x6c, 0x6c, 0x65, 0x72, 0x12, 0x45, 0x0a, 0x0d, 0x6d, 0x61, 0x72, 0x6b, 0x65, 0x64,
-	0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e,
-	0x6b, 0x79, 0x74, 0x68, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x6d,
-	0x6f, 0x6e, 0x2e, 0x4d, 0x61, 0x72, 0x6b, 0x65, 0x64, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52,
-	0x0c, 0x6d, 0x61, 0x72, 0x6b, 0x65, 0x64, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x3f, 0x0a,
-	0x08, 0x63, 0x61, 0x6c, 0x6c, 0x73, 0x69, 0x74, 0x65, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32,
-	0x23, 0x2e, 0x6b, 0x79, 0x74, 0x68, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x73, 0x65,
-	0x72, 0x76, 0x69, 0x6e, 0x67, 0x2e, 0x45, 0x78, 0x70, 0x61, 0x6e, 0x64, 0x65, 0x64, 0x41, 0x6e,
-	0x63, 0x68, 0x6f, 0x72, 0x52, 0x08, 0x63, 0x61, 0x6c, 0x6c, 0x73, 0x69, 0x74, 0x65, 0x1a, 0xdb,
-	0x02, 0x0a, 0x05, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x12, 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64,
-	0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x12, 0x21, 0x0a, 0x0c,
-	0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x05, 0x20, 0x01,
-	0x28, 0x09, 0x52, 0x0b, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12,
-	0x3b, 0x0a, 0x06, 0x61, 0x6e, 0x63, 0x68, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32,
-	0x23, 0x2e, 0x6b, 0x79, 0x74, 0x68, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x73, 0x65,
-	0x72, 0x76, 0x69, 0x6e, 0x67, 0x2e, 0x45, 0x78, 0x70, 0x61, 0x6e, 0x64, 0x65, 0x64, 0x41, 0x6e,
-	0x63, 0x68, 0x6f, 0x72, 0x52, 0x06, 0x61, 0x6e, 0x63, 0x68, 0x6f, 0x72, 0x12, 0x58, 0x0a, 0x0c,
-	0x72, 0x65, 0x6c, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x6e, 0x6f, 0x64, 0x65, 0x18, 0x03, 0x20, 0x03,
-	0x28, 0x0b, 0x32, 0x35, 0x2e, 0x6b, 0x79, 0x74, 0x68, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
-	0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x2e, 0x50, 0x61, 0x67, 0x65, 0x64, 0x43, 0x72,
-	0x6f, 0x73, 0x73, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x2e, 0x52, 0x65,
-	0x6c, 0x61, 0x74, 0x65, 0x64, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x0b, 0x72, 0x65, 0x6c, 0x61, 0x74,
-	0x65, 0x64, 0x4e, 0x6f, 0x64, 0x65, 0x12, 0x48, 0x0a, 0x06, 0x63, 0x61, 0x6c, 0x6c, 0x65, 0x72,
-	0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x30, 0x2e, 0x6b, 0x79, 0x74, 0x68, 0x65, 0x2e, 0x70,
-	0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x2e, 0x50, 0x61, 0x67,
-	0x65, 0x64, 0x43, 0x72, 0x6f, 0x73, 0x73, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65,
-	0x73, 0x2e, 0x43, 0x61, 0x6c, 0x6c, 0x65, 0x72, 0x52, 0x06, 0x63, 0x61, 0x6c, 0x6c, 0x65, 0x72,
-	0x12, 0x3a, 0x0a, 0x09, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x06, 0x20,
-	0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x6b, 0x79, 0x74, 0x68, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74,
-	0x6f, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x49, 0x6e,
-	0x66, 0x6f, 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x1a, 0x8d, 0x01, 0x0a,
-	0x04, 0x50, 0x61, 0x67, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x6b, 0x65,
-	0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x61, 0x67, 0x65, 0x4b, 0x65, 0x79,
-	0x12, 0x23, 0x0a, 0x0d, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x74, 0x69, 0x63, 0x6b, 0x65,
-	0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x54,
-	0x69, 0x63, 0x6b, 0x65, 0x74, 0x12, 0x45, 0x0a, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x18, 0x03,
-	0x20, 0x01, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x6b, 0x79, 0x74, 0x68, 0x65, 0x2e, 0x70, 0x72, 0x6f,
-	0x74, 0x6f, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x2e, 0x50, 0x61, 0x67, 0x65, 0x64,
-	0x43, 0x72, 0x6f, 0x73, 0x73, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x2e,
-	0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x1a, 0x73, 0x0a, 0x09,
-	0x50, 0x61, 0x67, 0x65, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x12, 0x0a, 0x04, 0x6b, 0x69, 0x6e,
-	0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x12, 0x14, 0x0a,
-	0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x63, 0x6f,
-	0x75, 0x6e, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18,
-	0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x61, 0x67, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x21,
-	0x0a, 0x0c, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x04,
-	0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69,
-	0x67, 0x4a, 0x04, 0x08, 0x0a, 0x10, 0x0b, 0x22, 0xa9, 0x02, 0x0a, 0x08, 0x44, 0x6f, 0x63, 0x75,
-	0x6d, 0x65, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x18, 0x01,
-	0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x12, 0x45, 0x0a, 0x0d,
-	0x6d, 0x61, 0x72, 0x6b, 0x65, 0x64, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x02, 0x20,
-	0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x6b, 0x79, 0x74, 0x68, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74,
-	0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4d, 0x61, 0x72, 0x6b, 0x65, 0x64, 0x53,
-	0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x0c, 0x6d, 0x61, 0x72, 0x6b, 0x65, 0x64, 0x53, 0x6f, 0x75,
-	0x72, 0x63, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x72, 0x61, 0x77, 0x5f, 0x74, 0x65, 0x78, 0x74, 0x18,
-	0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x72, 0x61, 0x77, 0x54, 0x65, 0x78, 0x74, 0x12, 0x2c,
-	0x0a, 0x04, 0x6c, 0x69, 0x6e, 0x6b, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6b,
-	0x79, 0x74, 0x68, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f,
-	0x6e, 0x2e, 0x4c, 0x69, 0x6e, 0x6b, 0x52, 0x04, 0x6c, 0x69, 0x6e, 0x6b, 0x12, 0x21, 0x0a, 0x0c,
-	0x63, 0x68, 0x69, 0x6c, 0x64, 0x5f, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x18, 0x05, 0x20, 0x03,
-	0x28, 0x09, 0x52, 0x0b, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x54, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x12,
-	0x2d, 0x0a, 0x04, 0x6e, 0x6f, 0x64, 0x65, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e,
+	0x2e, 0x46, 0x69, 0x6c, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x49,
+	0x6e, 0x66, 0x6f, 0x1a, 0x8d, 0x01, 0x0a, 0x04, 0x50, 0x61, 0x67, 0x65, 0x12, 0x19, 0x0a, 0x08,
+	0x70, 0x61, 0x67, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07,
+	0x70, 0x61, 0x67, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x23, 0x0a, 0x0d, 0x73, 0x6f, 0x75, 0x72, 0x63,
+	0x65, 0x5f, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c,
+	0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x54, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x12, 0x45, 0x0a, 0x05,
+	0x67, 0x72, 0x6f, 0x75, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x6b, 0x79,
+	0x74, 0x68, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x6e,
+	0x67, 0x2e, 0x50, 0x61, 0x67, 0x65, 0x64, 0x43, 0x72, 0x6f, 0x73, 0x73, 0x52, 0x65, 0x66, 0x65,
+	0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x2e, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, 0x05, 0x67, 0x72,
+	0x6f, 0x75, 0x70, 0x1a, 0x73, 0x0a, 0x09, 0x50, 0x61, 0x67, 0x65, 0x49, 0x6e, 0x64, 0x65, 0x78,
+	0x12, 0x12, 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04,
+	0x6b, 0x69, 0x6e, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20,
+	0x01, 0x28, 0x05, 0x52, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x70, 0x61,
+	0x67, 0x65, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, 0x61,
+	0x67, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x21, 0x0a, 0x0c, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x63,
+	0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x62, 0x75, 0x69,
+	0x6c, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x1a, 0xb3, 0x05, 0x0a, 0x0f, 0x50, 0x61, 0x67,
+	0x65, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x5f, 0x0a, 0x09,
+	0x62, 0x79, 0x5f, 0x63, 0x6f, 0x72, 0x70, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32,
+	0x42, 0x2e, 0x6b, 0x79, 0x74, 0x68, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x73, 0x65,
+	0x72, 0x76, 0x69, 0x6e, 0x67, 0x2e, 0x50, 0x61, 0x67, 0x65, 0x64, 0x43, 0x72, 0x6f, 0x73, 0x73,
+	0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x2e, 0x50, 0x61, 0x67, 0x65, 0x53,
+	0x65, 0x61, 0x72, 0x63, 0x68, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x2e, 0x50, 0x6f, 0x73, 0x74, 0x69,
+	0x6e, 0x67, 0x73, 0x52, 0x08, 0x62, 0x79, 0x43, 0x6f, 0x72, 0x70, 0x75, 0x73, 0x12, 0x5b, 0x0a,
+	0x07, 0x62, 0x79, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x42,
+	0x2e, 0x6b, 0x79, 0x74, 0x68, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x73, 0x65, 0x72,
+	0x76, 0x69, 0x6e, 0x67, 0x2e, 0x50, 0x61, 0x67, 0x65, 0x64, 0x43, 0x72, 0x6f, 0x73, 0x73, 0x52,
+	0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x2e, 0x50, 0x61, 0x67, 0x65, 0x53, 0x65,
+	0x61, 0x72, 0x63, 0x68, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x2e, 0x50, 0x6f, 0x73, 0x74, 0x69, 0x6e,
+	0x67, 0x73, 0x52, 0x06, 0x62, 0x79, 0x52, 0x6f, 0x6f, 0x74, 0x12, 0x5b, 0x0a, 0x07, 0x62, 0x79,
+	0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x42, 0x2e, 0x6b, 0x79,
+	0x74, 0x68, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x6e,
+	0x67, 0x2e, 0x50, 0x61, 0x67, 0x65, 0x64, 0x43, 0x72, 0x6f, 0x73, 0x73, 0x52, 0x65, 0x66, 0x65,
+	0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x2e, 0x50, 0x61, 0x67, 0x65, 0x53, 0x65, 0x61, 0x72, 0x63,
+	0x68, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x2e, 0x50, 0x6f, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x52,
+	0x06, 0x62, 0x79, 0x50, 0x61, 0x74, 0x68, 0x12, 0x6c, 0x0a, 0x10, 0x62, 0x79, 0x5f, 0x72, 0x65,
+	0x73, 0x6f, 0x6c, 0x76, 0x65, 0x64, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28,
+	0x0b, 0x32, 0x42, 0x2e, 0x6b, 0x79, 0x74, 0x68, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e,
+	0x73, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x2e, 0x50, 0x61, 0x67, 0x65, 0x64, 0x43, 0x72, 0x6f,
+	0x73, 0x73, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x2e, 0x50, 0x61, 0x67,
+	0x65, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x2e, 0x50, 0x6f, 0x73,
+	0x74, 0x69, 0x6e, 0x67, 0x73, 0x52, 0x0e, 0x62, 0x79, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x76, 0x65,
+	0x64, 0x50, 0x61, 0x74, 0x68, 0x1a, 0x2a, 0x0a, 0x05, 0x50, 0x61, 0x67, 0x65, 0x73, 0x12, 0x21,
+	0x0a, 0x0a, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x01, 0x20, 0x03,
+	0x28, 0x0d, 0x42, 0x02, 0x10, 0x01, 0x52, 0x09, 0x70, 0x61, 0x67, 0x65, 0x49, 0x6e, 0x64, 0x65,
+	0x78, 0x1a, 0xea, 0x01, 0x0a, 0x08, 0x50, 0x6f, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x63,
+	0x0a, 0x05, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x4d, 0x2e,
 	0x6b, 0x79, 0x74, 0x68, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x73, 0x65, 0x72, 0x76,
-	0x69, 0x6e, 0x67, 0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x04, 0x6e, 0x6f, 0x64, 0x65, 0x12, 0x23,
-	0x0a, 0x0d, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x65, 0x64, 0x5f, 0x62, 0x79, 0x18,
-	0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x65,
-	0x64, 0x42, 0x79, 0x22, 0xf4, 0x01, 0x0a, 0x0f, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69,
-	0x65, 0x72, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x12, 0x25, 0x0a, 0x0e, 0x71, 0x75, 0x61, 0x6c, 0x69,
-	0x66, 0x69, 0x65, 0x64, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
-	0x0d, 0x71, 0x75, 0x61, 0x6c, 0x69, 0x66, 0x69, 0x65, 0x64, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1b,
-	0x0a, 0x09, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28,
-	0x09, 0x52, 0x08, 0x62, 0x61, 0x73, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x3d, 0x0a, 0x04, 0x6e,
-	0x6f, 0x64, 0x65, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x6b, 0x79, 0x74, 0x68,
-	0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x2e,
-	0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x2e,
-	0x4e, 0x6f, 0x64, 0x65, 0x52, 0x04, 0x6e, 0x6f, 0x64, 0x65, 0x1a, 0x5e, 0x0a, 0x04, 0x4e, 0x6f,
-	0x64, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x18, 0x01, 0x20, 0x01,
-	0x28, 0x09, 0x52, 0x06, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x6f,
-	0x64, 0x65, 0x5f, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6e,
-	0x6f, 0x64, 0x65, 0x4b, 0x69, 0x6e, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x6e, 0x6f, 0x64, 0x65, 0x5f,
-	0x73, 0x75, 0x62, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x6e,
-	0x6f, 0x64, 0x65, 0x53, 0x75, 0x62, 0x6b, 0x69, 0x6e, 0x64, 0x22, 0x8e, 0x01, 0x0a, 0x09, 0x52,
-	0x65, 0x6c, 0x61, 0x74, 0x69, 0x76, 0x65, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x74, 0x69, 0x63, 0x6b,
-	0x65, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x74, 0x69, 0x63, 0x6b, 0x65,
-	0x74, 0x73, 0x12, 0x37, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e,
-	0x32, 0x23, 0x2e, 0x6b, 0x79, 0x74, 0x68, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x73,
-	0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x2e, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x76, 0x65, 0x73,
-	0x2e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x2e, 0x0a, 0x04, 0x54,
-	0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00,
-	0x12, 0x0b, 0x0a, 0x07, 0x50, 0x41, 0x52, 0x45, 0x4e, 0x54, 0x53, 0x10, 0x01, 0x12, 0x0c, 0x0a,
-	0x08, 0x43, 0x48, 0x49, 0x4c, 0x44, 0x52, 0x45, 0x4e, 0x10, 0x02, 0x22, 0x8b, 0x01, 0x0a, 0x09,
-	0x43, 0x61, 0x6c, 0x6c, 0x67, 0x72, 0x61, 0x70, 0x68, 0x12, 0x18, 0x0a, 0x07, 0x74, 0x69, 0x63,
-	0x6b, 0x65, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x74, 0x69, 0x63, 0x6b,
-	0x65, 0x74, 0x73, 0x12, 0x37, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28,
-	0x0e, 0x32, 0x23, 0x2e, 0x6b, 0x79, 0x74, 0x68, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e,
-	0x73, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x2e, 0x43, 0x61, 0x6c, 0x6c, 0x67, 0x72, 0x61, 0x70,
-	0x68, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x2b, 0x0a, 0x04,
-	0x54, 0x79, 0x70, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10,
-	0x00, 0x12, 0x0a, 0x0a, 0x06, 0x43, 0x41, 0x4c, 0x4c, 0x45, 0x52, 0x10, 0x01, 0x12, 0x0a, 0x0a,
-	0x06, 0x43, 0x41, 0x4c, 0x4c, 0x45, 0x45, 0x10, 0x02, 0x22, 0xa2, 0x02, 0x0a, 0x04, 0x44, 0x69,
-	0x66, 0x66, 0x12, 0x23, 0x0a, 0x0b, 0x73, 0x70, 0x61, 0x6e, 0x5f, 0x6c, 0x65, 0x6e, 0x67, 0x74,
-	0x68, 0x18, 0x01, 0x20, 0x03, 0x28, 0x05, 0x42, 0x02, 0x10, 0x01, 0x52, 0x0a, 0x73, 0x70, 0x61,
-	0x6e, 0x4c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x12, 0x3f, 0x0a, 0x09, 0x73, 0x70, 0x61, 0x6e, 0x5f,
-	0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0e, 0x32, 0x1e, 0x2e, 0x6b, 0x79, 0x74,
+	0x69, 0x6e, 0x67, 0x2e, 0x50, 0x61, 0x67, 0x65, 0x64, 0x43, 0x72, 0x6f, 0x73, 0x73, 0x52, 0x65,
+	0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x2e, 0x50, 0x61, 0x67, 0x65, 0x53, 0x65, 0x61,
+	0x72, 0x63, 0x68, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x2e, 0x50, 0x6f, 0x73, 0x74, 0x69, 0x6e, 0x67,
+	0x73, 0x2e, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x05, 0x69, 0x6e,
+	0x64, 0x65, 0x78, 0x1a, 0x79, 0x0a, 0x0a, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x45, 0x6e, 0x74, 0x72,
+	0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x03,
+	0x6b, 0x65, 0x79, 0x12, 0x55, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01,
+	0x28, 0x0b, 0x32, 0x3f, 0x2e, 0x6b, 0x79, 0x74, 0x68, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+	0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x2e, 0x50, 0x61, 0x67, 0x65, 0x64, 0x43, 0x72,
+	0x6f, 0x73, 0x73, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x2e, 0x50, 0x61,
+	0x67, 0x65, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x2e, 0x50, 0x61,
+	0x67, 0x65, 0x73, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x4a, 0x04,
+	0x08, 0x0a, 0x10, 0x0b, 0x22, 0xa9, 0x02, 0x0a, 0x08, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e,
+	0x74, 0x12, 0x16, 0x0a, 0x06, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28,
+	0x09, 0x52, 0x06, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x12, 0x45, 0x0a, 0x0d, 0x6d, 0x61, 0x72,
+	0x6b, 0x65, 0x64, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b,
+	0x32, 0x20, 0x2e, 0x6b, 0x79, 0x74, 0x68, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63,
+	0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4d, 0x61, 0x72, 0x6b, 0x65, 0x64, 0x53, 0x6f, 0x75, 0x72,
+	0x63, 0x65, 0x52, 0x0c, 0x6d, 0x61, 0x72, 0x6b, 0x65, 0x64, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65,
+	0x12, 0x19, 0x0a, 0x08, 0x72, 0x61, 0x77, 0x5f, 0x74, 0x65, 0x78, 0x74, 0x18, 0x03, 0x20, 0x01,
+	0x28, 0x09, 0x52, 0x07, 0x72, 0x61, 0x77, 0x54, 0x65, 0x78, 0x74, 0x12, 0x2c, 0x0a, 0x04, 0x6c,
+	0x69, 0x6e, 0x6b, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x6b, 0x79, 0x74, 0x68,
+	0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4c,
+	0x69, 0x6e, 0x6b, 0x52, 0x04, 0x6c, 0x69, 0x6e, 0x6b, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x68, 0x69,
+	0x6c, 0x64, 0x5f, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52,
+	0x0b, 0x63, 0x68, 0x69, 0x6c, 0x64, 0x54, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x12, 0x2d, 0x0a, 0x04,
+	0x6e, 0x6f, 0x64, 0x65, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x6b, 0x79, 0x74,
 	0x68, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67,
-	0x2e, 0x44, 0x69, 0x66, 0x66, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x42, 0x02, 0x10, 0x01, 0x52, 0x08,
-	0x73, 0x70, 0x61, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x27, 0x0a, 0x0d, 0x73, 0x70, 0x61, 0x6e,
-	0x5f, 0x6e, 0x65, 0x77, 0x6c, 0x69, 0x6e, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x05, 0x42,
-	0x02, 0x10, 0x01, 0x52, 0x0c, 0x73, 0x70, 0x61, 0x6e, 0x4e, 0x65, 0x77, 0x6c, 0x69, 0x6e, 0x65,
-	0x73, 0x12, 0x30, 0x0a, 0x12, 0x73, 0x70, 0x61, 0x6e, 0x5f, 0x66, 0x69, 0x72, 0x73, 0x74, 0x5f,
-	0x6e, 0x65, 0x77, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x04, 0x20, 0x03, 0x28, 0x05, 0x42, 0x02, 0x10,
-	0x01, 0x52, 0x10, 0x73, 0x70, 0x61, 0x6e, 0x46, 0x69, 0x72, 0x73, 0x74, 0x4e, 0x65, 0x77, 0x6c,
-	0x69, 0x6e, 0x65, 0x12, 0x2e, 0x0a, 0x11, 0x73, 0x70, 0x61, 0x6e, 0x5f, 0x6c, 0x61, 0x73, 0x74,
-	0x5f, 0x6e, 0x65, 0x77, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x05, 0x20, 0x03, 0x28, 0x05, 0x42, 0x02,
-	0x10, 0x01, 0x52, 0x0f, 0x73, 0x70, 0x61, 0x6e, 0x4c, 0x61, 0x73, 0x74, 0x4e, 0x65, 0x77, 0x6c,
-	0x69, 0x6e, 0x65, 0x22, 0x29, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x09, 0x0a, 0x05, 0x45,
-	0x51, 0x55, 0x41, 0x4c, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x49, 0x4e, 0x53, 0x45, 0x52, 0x54,
-	0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x44, 0x45, 0x4c, 0x45, 0x54, 0x45, 0x10, 0x02, 0x42, 0x33,
-	0x0a, 0x1f, 0x63, 0x6f, 0x6d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x64, 0x65, 0x76,
-	0x74, 0x6f, 0x6f, 0x6c, 0x73, 0x2e, 0x6b, 0x79, 0x74, 0x68, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74,
-	0x6f, 0x5a, 0x10, 0x73, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x5f, 0x67, 0x6f, 0x5f, 0x70, 0x72,
-	0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+	0x2e, 0x4e, 0x6f, 0x64, 0x65, 0x52, 0x04, 0x6e, 0x6f, 0x64, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x64,
+	0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x65, 0x64, 0x5f, 0x62, 0x79, 0x18, 0x07, 0x20, 0x01,
+	0x28, 0x09, 0x52, 0x0c, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x65, 0x64, 0x42, 0x79,
+	0x22, 0xf4, 0x01, 0x0a, 0x0f, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x4d,
+	0x61, 0x74, 0x63, 0x68, 0x12, 0x25, 0x0a, 0x0e, 0x71, 0x75, 0x61, 0x6c, 0x69, 0x66, 0x69, 0x65,
+	0x64, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x71, 0x75,
+	0x61, 0x6c, 0x69, 0x66, 0x69, 0x65, 0x64, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x62,
+	0x61, 0x73, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08,
+	0x62, 0x61, 0x73, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x3d, 0x0a, 0x04, 0x6e, 0x6f, 0x64, 0x65,
+	0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x6b, 0x79, 0x74, 0x68, 0x65, 0x2e, 0x70,
+	0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x2e, 0x49, 0x64, 0x65,
+	0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x2e, 0x4e, 0x6f, 0x64,
+	0x65, 0x52, 0x04, 0x6e, 0x6f, 0x64, 0x65, 0x1a, 0x5e, 0x0a, 0x04, 0x4e, 0x6f, 0x64, 0x65, 0x12,
+	0x16, 0x0a, 0x06, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
+	0x06, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x6f, 0x64, 0x65, 0x5f,
+	0x6b, 0x69, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6e, 0x6f, 0x64, 0x65,
+	0x4b, 0x69, 0x6e, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x73, 0x75, 0x62,
+	0x6b, 0x69, 0x6e, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x6e, 0x6f, 0x64, 0x65,
+	0x53, 0x75, 0x62, 0x6b, 0x69, 0x6e, 0x64, 0x22, 0x8e, 0x01, 0x0a, 0x09, 0x52, 0x65, 0x6c, 0x61,
+	0x74, 0x69, 0x76, 0x65, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x73,
+	0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x73, 0x12,
+	0x37, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x23, 0x2e,
+	0x6b, 0x79, 0x74, 0x68, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x73, 0x65, 0x72, 0x76,
+	0x69, 0x6e, 0x67, 0x2e, 0x52, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x76, 0x65, 0x73, 0x2e, 0x54, 0x79,
+	0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x2e, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65,
+	0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0b, 0x0a,
+	0x07, 0x50, 0x41, 0x52, 0x45, 0x4e, 0x54, 0x53, 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x43, 0x48,
+	0x49, 0x4c, 0x44, 0x52, 0x45, 0x4e, 0x10, 0x02, 0x22, 0x8b, 0x01, 0x0a, 0x09, 0x43, 0x61, 0x6c,
+	0x6c, 0x67, 0x72, 0x61, 0x70, 0x68, 0x12, 0x18, 0x0a, 0x07, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74,
+	0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x74, 0x69, 0x63, 0x6b, 0x65, 0x74, 0x73,
+	0x12, 0x37, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x23,
+	0x2e, 0x6b, 0x79, 0x74, 0x68, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x73, 0x65, 0x72,
+	0x76, 0x69, 0x6e, 0x67, 0x2e, 0x43, 0x61, 0x6c, 0x6c, 0x67, 0x72, 0x61, 0x70, 0x68, 0x2e, 0x54,
+	0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x2b, 0x0a, 0x04, 0x54, 0x79, 0x70,
+	0x65, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0a,
+	0x0a, 0x06, 0x43, 0x41, 0x4c, 0x4c, 0x45, 0x52, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x43, 0x41,
+	0x4c, 0x4c, 0x45, 0x45, 0x10, 0x02, 0x22, 0xa2, 0x02, 0x0a, 0x04, 0x44, 0x69, 0x66, 0x66, 0x12,
+	0x23, 0x0a, 0x0b, 0x73, 0x70, 0x61, 0x6e, 0x5f, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x18, 0x01,
+	0x20, 0x03, 0x28, 0x05, 0x42, 0x02, 0x10, 0x01, 0x52, 0x0a, 0x73, 0x70, 0x61, 0x6e, 0x4c, 0x65,
+	0x6e, 0x67, 0x74, 0x68, 0x12, 0x3f, 0x0a, 0x09, 0x73, 0x70, 0x61, 0x6e, 0x5f, 0x74, 0x79, 0x70,
+	0x65, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0e, 0x32, 0x1e, 0x2e, 0x6b, 0x79, 0x74, 0x68, 0x65, 0x2e,
+	0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x2e, 0x44, 0x69,
+	0x66, 0x66, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x42, 0x02, 0x10, 0x01, 0x52, 0x08, 0x73, 0x70, 0x61,
+	0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x27, 0x0a, 0x0d, 0x73, 0x70, 0x61, 0x6e, 0x5f, 0x6e, 0x65,
+	0x77, 0x6c, 0x69, 0x6e, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x05, 0x42, 0x02, 0x10, 0x01,
+	0x52, 0x0c, 0x73, 0x70, 0x61, 0x6e, 0x4e, 0x65, 0x77, 0x6c, 0x69, 0x6e, 0x65, 0x73, 0x12, 0x30,
+	0x0a, 0x12, 0x73, 0x70, 0x61, 0x6e, 0x5f, 0x66, 0x69, 0x72, 0x73, 0x74, 0x5f, 0x6e, 0x65, 0x77,
+	0x6c, 0x69, 0x6e, 0x65, 0x18, 0x04, 0x20, 0x03, 0x28, 0x05, 0x42, 0x02, 0x10, 0x01, 0x52, 0x10,
+	0x73, 0x70, 0x61, 0x6e, 0x46, 0x69, 0x72, 0x73, 0x74, 0x4e, 0x65, 0x77, 0x6c, 0x69, 0x6e, 0x65,
+	0x12, 0x2e, 0x0a, 0x11, 0x73, 0x70, 0x61, 0x6e, 0x5f, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x6e, 0x65,
+	0x77, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x05, 0x20, 0x03, 0x28, 0x05, 0x42, 0x02, 0x10, 0x01, 0x52,
+	0x0f, 0x73, 0x70, 0x61, 0x6e, 0x4c, 0x61, 0x73, 0x74, 0x4e, 0x65, 0x77, 0x6c, 0x69, 0x6e, 0x65,
+	0x22, 0x29, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x51, 0x55, 0x41,
+	0x4c, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x49, 0x4e, 0x53, 0x45, 0x52, 0x54, 0x10, 0x01, 0x12,
+	0x0a, 0x0a, 0x06, 0x44, 0x45, 0x4c, 0x45, 0x54, 0x45, 0x10, 0x02, 0x42, 0x33, 0x0a, 0x1f, 0x63,
+	0x6f, 0x6d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x64, 0x65, 0x76, 0x74, 0x6f, 0x6f,
+	0x6c, 0x73, 0x2e, 0x6b, 0x79, 0x74, 0x68, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x5a, 0x10,
+	0x73, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x5f, 0x67, 0x6f, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+	0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
 }
 
 var (
@@ -2827,57 +3050,61 @@ func file_kythe_proto_serving_proto_rawDescGZIP() []byte {
 }
 
 var file_kythe_proto_serving_proto_enumTypes = make([]protoimpl.EnumInfo, 5)
-var file_kythe_proto_serving_proto_msgTypes = make([]protoimpl.MessageInfo, 30)
+var file_kythe_proto_serving_proto_msgTypes = make([]protoimpl.MessageInfo, 34)
 var file_kythe_proto_serving_proto_goTypes = []interface{}{
-	(FileDirectory_Kind)(0),                  // 0: kythe.proto.serving.FileDirectory.Kind
-	(FileDecorations_Override_Kind)(0),       // 1: kythe.proto.serving.FileDecorations.Override.Kind
-	(Relatives_Type)(0),                      // 2: kythe.proto.serving.Relatives.Type
-	(Callgraph_Type)(0),                      // 3: kythe.proto.serving.Callgraph.Type
-	(Diff_Type)(0),                           // 4: kythe.proto.serving.Diff.Type
-	(*Node)(nil),                             // 5: kythe.proto.serving.Node
-	(*Edge)(nil),                             // 6: kythe.proto.serving.Edge
-	(*EdgeGroup)(nil),                        // 7: kythe.proto.serving.EdgeGroup
-	(*PagedEdgeSet)(nil),                     // 8: kythe.proto.serving.PagedEdgeSet
-	(*PageIndex)(nil),                        // 9: kythe.proto.serving.PageIndex
-	(*EdgePage)(nil),                         // 10: kythe.proto.serving.EdgePage
-	(*FileDirectory)(nil),                    // 11: kythe.proto.serving.FileDirectory
-	(*CorpusRoots)(nil),                      // 12: kythe.proto.serving.CorpusRoots
-	(*File)(nil),                             // 13: kythe.proto.serving.File
-	(*RawAnchor)(nil),                        // 14: kythe.proto.serving.RawAnchor
-	(*ExpandedAnchor)(nil),                   // 15: kythe.proto.serving.ExpandedAnchor
-	(*FileInfo)(nil),                         // 16: kythe.proto.serving.FileInfo
-	(*FileDecorations)(nil),                  // 17: kythe.proto.serving.FileDecorations
-	(*PagedCrossReferences)(nil),             // 18: kythe.proto.serving.PagedCrossReferences
-	(*Document)(nil),                         // 19: kythe.proto.serving.Document
-	(*IdentifierMatch)(nil),                  // 20: kythe.proto.serving.IdentifierMatch
-	(*Relatives)(nil),                        // 21: kythe.proto.serving.Relatives
-	(*Callgraph)(nil),                        // 22: kythe.proto.serving.Callgraph
-	(*Diff)(nil),                             // 23: kythe.proto.serving.Diff
-	(*EdgeGroup_Edge)(nil),                   // 24: kythe.proto.serving.EdgeGroup.Edge
-	(*FileDirectory_Entry)(nil),              // 25: kythe.proto.serving.FileDirectory.Entry
-	(*CorpusRoots_Corpus)(nil),               // 26: kythe.proto.serving.CorpusRoots.Corpus
-	(*FileDecorations_Decoration)(nil),       // 27: kythe.proto.serving.FileDecorations.Decoration
-	(*FileDecorations_Override)(nil),         // 28: kythe.proto.serving.FileDecorations.Override
-	(*PagedCrossReferences_RelatedNode)(nil), // 29: kythe.proto.serving.PagedCrossReferences.RelatedNode
-	(*PagedCrossReferences_Caller)(nil),      // 30: kythe.proto.serving.PagedCrossReferences.Caller
-	(*PagedCrossReferences_Group)(nil),       // 31: kythe.proto.serving.PagedCrossReferences.Group
-	(*PagedCrossReferences_Page)(nil),        // 32: kythe.proto.serving.PagedCrossReferences.Page
-	(*PagedCrossReferences_PageIndex)(nil),   // 33: kythe.proto.serving.PagedCrossReferences.PageIndex
-	(*IdentifierMatch_Node)(nil),             // 34: kythe.proto.serving.IdentifierMatch.Node
-	(*common_go_proto.Fact)(nil),             // 35: kythe.proto.common.Fact
-	(*common_go_proto.Span)(nil),             // 36: kythe.proto.common.Span
-	(*common_go_proto.CorpusPath)(nil),       // 37: kythe.proto.common.CorpusPath
-	(*common_go_proto.Hash)(nil),             // 38: kythe.proto.common.Hash
-	(*common_go_proto.Diagnostic)(nil),       // 39: kythe.proto.common.Diagnostic
-	(*common_go_proto.MarkedSource)(nil),     // 40: kythe.proto.common.MarkedSource
-	(*common_go_proto.Link)(nil),             // 41: kythe.proto.common.Link
+	(FileDirectory_Kind)(0),                               // 0: kythe.proto.serving.FileDirectory.Kind
+	(FileDecorations_Override_Kind)(0),                    // 1: kythe.proto.serving.FileDecorations.Override.Kind
+	(Relatives_Type)(0),                                   // 2: kythe.proto.serving.Relatives.Type
+	(Callgraph_Type)(0),                                   // 3: kythe.proto.serving.Callgraph.Type
+	(Diff_Type)(0),                                        // 4: kythe.proto.serving.Diff.Type
+	(*Node)(nil),                                          // 5: kythe.proto.serving.Node
+	(*Edge)(nil),                                          // 6: kythe.proto.serving.Edge
+	(*EdgeGroup)(nil),                                     // 7: kythe.proto.serving.EdgeGroup
+	(*PagedEdgeSet)(nil),                                  // 8: kythe.proto.serving.PagedEdgeSet
+	(*PageIndex)(nil),                                     // 9: kythe.proto.serving.PageIndex
+	(*EdgePage)(nil),                                      // 10: kythe.proto.serving.EdgePage
+	(*FileDirectory)(nil),                                 // 11: kythe.proto.serving.FileDirectory
+	(*CorpusRoots)(nil),                                   // 12: kythe.proto.serving.CorpusRoots
+	(*File)(nil),                                          // 13: kythe.proto.serving.File
+	(*RawAnchor)(nil),                                     // 14: kythe.proto.serving.RawAnchor
+	(*ExpandedAnchor)(nil),                                // 15: kythe.proto.serving.ExpandedAnchor
+	(*FileInfo)(nil),                                      // 16: kythe.proto.serving.FileInfo
+	(*FileDecorations)(nil),                               // 17: kythe.proto.serving.FileDecorations
+	(*PagedCrossReferences)(nil),                          // 18: kythe.proto.serving.PagedCrossReferences
+	(*Document)(nil),                                      // 19: kythe.proto.serving.Document
+	(*IdentifierMatch)(nil),                               // 20: kythe.proto.serving.IdentifierMatch
+	(*Relatives)(nil),                                     // 21: kythe.proto.serving.Relatives
+	(*Callgraph)(nil),                                     // 22: kythe.proto.serving.Callgraph
+	(*Diff)(nil),                                          // 23: kythe.proto.serving.Diff
+	(*EdgeGroup_Edge)(nil),                                // 24: kythe.proto.serving.EdgeGroup.Edge
+	(*FileDirectory_Entry)(nil),                           // 25: kythe.proto.serving.FileDirectory.Entry
+	(*CorpusRoots_Corpus)(nil),                            // 26: kythe.proto.serving.CorpusRoots.Corpus
+	(*FileDecorations_Decoration)(nil),                    // 27: kythe.proto.serving.FileDecorations.Decoration
+	(*FileDecorations_Override)(nil),                      // 28: kythe.proto.serving.FileDecorations.Override
+	(*PagedCrossReferences_RelatedNode)(nil),              // 29: kythe.proto.serving.PagedCrossReferences.RelatedNode
+	(*PagedCrossReferences_Caller)(nil),                   // 30: kythe.proto.serving.PagedCrossReferences.Caller
+	(*PagedCrossReferences_Group)(nil),                    // 31: kythe.proto.serving.PagedCrossReferences.Group
+	(*PagedCrossReferences_Page)(nil),                     // 32: kythe.proto.serving.PagedCrossReferences.Page
+	(*PagedCrossReferences_PageIndex)(nil),                // 33: kythe.proto.serving.PagedCrossReferences.PageIndex
+	(*PagedCrossReferences_PageSearchIndex)(nil),          // 34: kythe.proto.serving.PagedCrossReferences.PageSearchIndex
+	(*PagedCrossReferences_PageSearchIndex_Pages)(nil),    // 35: kythe.proto.serving.PagedCrossReferences.PageSearchIndex.Pages
+	(*PagedCrossReferences_PageSearchIndex_Postings)(nil), // 36: kythe.proto.serving.PagedCrossReferences.PageSearchIndex.Postings
+	nil,                                  // 37: kythe.proto.serving.PagedCrossReferences.PageSearchIndex.Postings.IndexEntry
+	(*IdentifierMatch_Node)(nil),         // 38: kythe.proto.serving.IdentifierMatch.Node
+	(*common_go_proto.Fact)(nil),         // 39: kythe.proto.common.Fact
+	(*common_go_proto.Span)(nil),         // 40: kythe.proto.common.Span
+	(*common_go_proto.CorpusPath)(nil),   // 41: kythe.proto.common.CorpusPath
+	(*common_go_proto.Hash)(nil),         // 42: kythe.proto.common.Hash
+	(*common_go_proto.Diagnostic)(nil),   // 43: kythe.proto.common.Diagnostic
+	(*common_go_proto.MarkedSource)(nil), // 44: kythe.proto.common.MarkedSource
+	(*common_go_proto.Link)(nil),         // 45: kythe.proto.common.Link
 }
 var file_kythe_proto_serving_proto_depIdxs = []int32{
-	35, // 0: kythe.proto.serving.Node.fact:type_name -> kythe.proto.common.Fact
+	39, // 0: kythe.proto.serving.Node.fact:type_name -> kythe.proto.common.Fact
 	15, // 1: kythe.proto.serving.Node.definition_location:type_name -> kythe.proto.serving.ExpandedAnchor
 	5,  // 2: kythe.proto.serving.Edge.source:type_name -> kythe.proto.serving.Node
 	5,  // 3: kythe.proto.serving.Edge.target:type_name -> kythe.proto.serving.Node
-	35, // 4: kythe.proto.serving.Edge.fact:type_name -> kythe.proto.common.Fact
+	39, // 4: kythe.proto.serving.Edge.fact:type_name -> kythe.proto.common.Fact
 	24, // 5: kythe.proto.serving.EdgeGroup.edge:type_name -> kythe.proto.serving.EdgeGroup.Edge
 	5,  // 6: kythe.proto.serving.PagedEdgeSet.source:type_name -> kythe.proto.serving.Node
 	7,  // 7: kythe.proto.serving.PagedEdgeSet.group:type_name -> kythe.proto.serving.EdgeGroup
@@ -2886,48 +3113,55 @@ var file_kythe_proto_serving_proto_depIdxs = []int32{
 	25, // 10: kythe.proto.serving.FileDirectory.entry:type_name -> kythe.proto.serving.FileDirectory.Entry
 	26, // 11: kythe.proto.serving.CorpusRoots.corpus:type_name -> kythe.proto.serving.CorpusRoots.Corpus
 	16, // 12: kythe.proto.serving.File.info:type_name -> kythe.proto.serving.FileInfo
-	36, // 13: kythe.proto.serving.ExpandedAnchor.span:type_name -> kythe.proto.common.Span
-	36, // 14: kythe.proto.serving.ExpandedAnchor.snippet_span:type_name -> kythe.proto.common.Span
+	40, // 13: kythe.proto.serving.ExpandedAnchor.span:type_name -> kythe.proto.common.Span
+	40, // 14: kythe.proto.serving.ExpandedAnchor.snippet_span:type_name -> kythe.proto.common.Span
 	16, // 15: kythe.proto.serving.ExpandedAnchor.file_info:type_name -> kythe.proto.serving.FileInfo
-	37, // 16: kythe.proto.serving.FileInfo.corpus_path:type_name -> kythe.proto.common.CorpusPath
-	38, // 17: kythe.proto.serving.FileInfo.hash:type_name -> kythe.proto.common.Hash
+	41, // 16: kythe.proto.serving.FileInfo.corpus_path:type_name -> kythe.proto.common.CorpusPath
+	42, // 17: kythe.proto.serving.FileInfo.hash:type_name -> kythe.proto.common.Hash
 	13, // 18: kythe.proto.serving.FileDecorations.file:type_name -> kythe.proto.serving.File
 	27, // 19: kythe.proto.serving.FileDecorations.decoration:type_name -> kythe.proto.serving.FileDecorations.Decoration
 	5,  // 20: kythe.proto.serving.FileDecorations.target:type_name -> kythe.proto.serving.Node
 	15, // 21: kythe.proto.serving.FileDecorations.target_definitions:type_name -> kythe.proto.serving.ExpandedAnchor
 	28, // 22: kythe.proto.serving.FileDecorations.target_override:type_name -> kythe.proto.serving.FileDecorations.Override
-	39, // 23: kythe.proto.serving.FileDecorations.diagnostic:type_name -> kythe.proto.common.Diagnostic
+	43, // 23: kythe.proto.serving.FileDecorations.diagnostic:type_name -> kythe.proto.common.Diagnostic
 	16, // 24: kythe.proto.serving.FileDecorations.file_info:type_name -> kythe.proto.serving.FileInfo
 	5,  // 25: kythe.proto.serving.PagedCrossReferences.source_node:type_name -> kythe.proto.serving.Node
 	31, // 26: kythe.proto.serving.PagedCrossReferences.group:type_name -> kythe.proto.serving.PagedCrossReferences.Group
 	33, // 27: kythe.proto.serving.PagedCrossReferences.page_index:type_name -> kythe.proto.serving.PagedCrossReferences.PageIndex
-	40, // 28: kythe.proto.serving.PagedCrossReferences.marked_source:type_name -> kythe.proto.common.MarkedSource
-	40, // 29: kythe.proto.serving.Document.marked_source:type_name -> kythe.proto.common.MarkedSource
-	41, // 30: kythe.proto.serving.Document.link:type_name -> kythe.proto.common.Link
-	5,  // 31: kythe.proto.serving.Document.node:type_name -> kythe.proto.serving.Node
-	34, // 32: kythe.proto.serving.IdentifierMatch.node:type_name -> kythe.proto.serving.IdentifierMatch.Node
-	2,  // 33: kythe.proto.serving.Relatives.type:type_name -> kythe.proto.serving.Relatives.Type
-	3,  // 34: kythe.proto.serving.Callgraph.type:type_name -> kythe.proto.serving.Callgraph.Type
-	4,  // 35: kythe.proto.serving.Diff.span_type:type_name -> kythe.proto.serving.Diff.Type
-	5,  // 36: kythe.proto.serving.EdgeGroup.Edge.target:type_name -> kythe.proto.serving.Node
-	0,  // 37: kythe.proto.serving.FileDirectory.Entry.kind:type_name -> kythe.proto.serving.FileDirectory.Kind
-	14, // 38: kythe.proto.serving.FileDecorations.Decoration.anchor:type_name -> kythe.proto.serving.RawAnchor
-	1,  // 39: kythe.proto.serving.FileDecorations.Override.kind:type_name -> kythe.proto.serving.FileDecorations.Override.Kind
-	40, // 40: kythe.proto.serving.FileDecorations.Override.marked_source:type_name -> kythe.proto.common.MarkedSource
-	5,  // 41: kythe.proto.serving.PagedCrossReferences.RelatedNode.node:type_name -> kythe.proto.serving.Node
-	15, // 42: kythe.proto.serving.PagedCrossReferences.Caller.caller:type_name -> kythe.proto.serving.ExpandedAnchor
-	40, // 43: kythe.proto.serving.PagedCrossReferences.Caller.marked_source:type_name -> kythe.proto.common.MarkedSource
-	15, // 44: kythe.proto.serving.PagedCrossReferences.Caller.callsite:type_name -> kythe.proto.serving.ExpandedAnchor
-	15, // 45: kythe.proto.serving.PagedCrossReferences.Group.anchor:type_name -> kythe.proto.serving.ExpandedAnchor
-	29, // 46: kythe.proto.serving.PagedCrossReferences.Group.related_node:type_name -> kythe.proto.serving.PagedCrossReferences.RelatedNode
-	30, // 47: kythe.proto.serving.PagedCrossReferences.Group.caller:type_name -> kythe.proto.serving.PagedCrossReferences.Caller
-	16, // 48: kythe.proto.serving.PagedCrossReferences.Group.file_info:type_name -> kythe.proto.serving.FileInfo
-	31, // 49: kythe.proto.serving.PagedCrossReferences.Page.group:type_name -> kythe.proto.serving.PagedCrossReferences.Group
-	50, // [50:50] is the sub-list for method output_type
-	50, // [50:50] is the sub-list for method input_type
-	50, // [50:50] is the sub-list for extension type_name
-	50, // [50:50] is the sub-list for extension extendee
-	0,  // [0:50] is the sub-list for field type_name
+	44, // 28: kythe.proto.serving.PagedCrossReferences.marked_source:type_name -> kythe.proto.common.MarkedSource
+	34, // 29: kythe.proto.serving.PagedCrossReferences.page_search_index:type_name -> kythe.proto.serving.PagedCrossReferences.PageSearchIndex
+	44, // 30: kythe.proto.serving.Document.marked_source:type_name -> kythe.proto.common.MarkedSource
+	45, // 31: kythe.proto.serving.Document.link:type_name -> kythe.proto.common.Link
+	5,  // 32: kythe.proto.serving.Document.node:type_name -> kythe.proto.serving.Node
+	38, // 33: kythe.proto.serving.IdentifierMatch.node:type_name -> kythe.proto.serving.IdentifierMatch.Node
+	2,  // 34: kythe.proto.serving.Relatives.type:type_name -> kythe.proto.serving.Relatives.Type
+	3,  // 35: kythe.proto.serving.Callgraph.type:type_name -> kythe.proto.serving.Callgraph.Type
+	4,  // 36: kythe.proto.serving.Diff.span_type:type_name -> kythe.proto.serving.Diff.Type
+	5,  // 37: kythe.proto.serving.EdgeGroup.Edge.target:type_name -> kythe.proto.serving.Node
+	0,  // 38: kythe.proto.serving.FileDirectory.Entry.kind:type_name -> kythe.proto.serving.FileDirectory.Kind
+	14, // 39: kythe.proto.serving.FileDecorations.Decoration.anchor:type_name -> kythe.proto.serving.RawAnchor
+	1,  // 40: kythe.proto.serving.FileDecorations.Override.kind:type_name -> kythe.proto.serving.FileDecorations.Override.Kind
+	44, // 41: kythe.proto.serving.FileDecorations.Override.marked_source:type_name -> kythe.proto.common.MarkedSource
+	5,  // 42: kythe.proto.serving.PagedCrossReferences.RelatedNode.node:type_name -> kythe.proto.serving.Node
+	15, // 43: kythe.proto.serving.PagedCrossReferences.Caller.caller:type_name -> kythe.proto.serving.ExpandedAnchor
+	44, // 44: kythe.proto.serving.PagedCrossReferences.Caller.marked_source:type_name -> kythe.proto.common.MarkedSource
+	15, // 45: kythe.proto.serving.PagedCrossReferences.Caller.callsite:type_name -> kythe.proto.serving.ExpandedAnchor
+	15, // 46: kythe.proto.serving.PagedCrossReferences.Group.anchor:type_name -> kythe.proto.serving.ExpandedAnchor
+	29, // 47: kythe.proto.serving.PagedCrossReferences.Group.related_node:type_name -> kythe.proto.serving.PagedCrossReferences.RelatedNode
+	30, // 48: kythe.proto.serving.PagedCrossReferences.Group.caller:type_name -> kythe.proto.serving.PagedCrossReferences.Caller
+	16, // 49: kythe.proto.serving.PagedCrossReferences.Group.file_info:type_name -> kythe.proto.serving.FileInfo
+	31, // 50: kythe.proto.serving.PagedCrossReferences.Page.group:type_name -> kythe.proto.serving.PagedCrossReferences.Group
+	36, // 51: kythe.proto.serving.PagedCrossReferences.PageSearchIndex.by_corpus:type_name -> kythe.proto.serving.PagedCrossReferences.PageSearchIndex.Postings
+	36, // 52: kythe.proto.serving.PagedCrossReferences.PageSearchIndex.by_root:type_name -> kythe.proto.serving.PagedCrossReferences.PageSearchIndex.Postings
+	36, // 53: kythe.proto.serving.PagedCrossReferences.PageSearchIndex.by_path:type_name -> kythe.proto.serving.PagedCrossReferences.PageSearchIndex.Postings
+	36, // 54: kythe.proto.serving.PagedCrossReferences.PageSearchIndex.by_resolved_path:type_name -> kythe.proto.serving.PagedCrossReferences.PageSearchIndex.Postings
+	37, // 55: kythe.proto.serving.PagedCrossReferences.PageSearchIndex.Postings.index:type_name -> kythe.proto.serving.PagedCrossReferences.PageSearchIndex.Postings.IndexEntry
+	35, // 56: kythe.proto.serving.PagedCrossReferences.PageSearchIndex.Postings.IndexEntry.value:type_name -> kythe.proto.serving.PagedCrossReferences.PageSearchIndex.Pages
+	57, // [57:57] is the sub-list for method output_type
+	57, // [57:57] is the sub-list for method input_type
+	57, // [57:57] is the sub-list for extension type_name
+	57, // [57:57] is the sub-list for extension extendee
+	0,  // [0:57] is the sub-list for field type_name
 }
 
 func init() { file_kythe_proto_serving_proto_init() }
@@ -3285,6 +3519,42 @@ func file_kythe_proto_serving_proto_init() {
 			}
 		}
 		file_kythe_proto_serving_proto_msgTypes[29].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*PagedCrossReferences_PageSearchIndex); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_kythe_proto_serving_proto_msgTypes[30].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*PagedCrossReferences_PageSearchIndex_Pages); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_kythe_proto_serving_proto_msgTypes[31].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*PagedCrossReferences_PageSearchIndex_Postings); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_kythe_proto_serving_proto_msgTypes[33].Exporter = func(v interface{}, i int) interface{} {
 			switch v := v.(*IdentifierMatch_Node); i {
 			case 0:
 				return &v.state
@@ -3303,7 +3573,7 @@ func file_kythe_proto_serving_proto_init() {
 			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
 			RawDescriptor: file_kythe_proto_serving_proto_rawDesc,
 			NumEnums:      5,
-			NumMessages:   30,
+			NumMessages:   34,
 			NumExtensions: 0,
 			NumServices:   0,
 		},


Test patch:
diff --git a/kythe/go/serving/xrefs/xrefs_test.go b/kythe/go/serving/xrefs/xrefs_test.go
index 2b9d83692..e9c70838d 100644
--- a/kythe/go/serving/xrefs/xrefs_test.go
+++ b/kythe/go/serving/xrefs/xrefs_test.go
@@ -19,10 +19,12 @@ package xrefs
 import (
 	"bytes"
 	"context"
+	"math"
 	"sort"
 	"strconv"
 	"testing"
 
+	"bitbucket.org/creachadair/stringset"
 	"kythe.io/kythe/go/services/xrefs"
 	"kythe.io/kythe/go/storage/table"
 	"kythe.io/kythe/go/test/testutil"
@@ -2670,6 +2672,133 @@ func TestDocumentationIndirection(t *testing.T) {
 	}
 }
 
+func TestPageSearchIndex(t *testing.T) {
+	set := &srvpb.PagedCrossReferences{
+		PageSearchIndex: &srvpb.PagedCrossReferences_PageSearchIndex{
+			ByCorpus: &srvpb.PagedCrossReferences_PageSearchIndex_Postings{
+				Index: map[uint32]*srvpb.PagedCrossReferences_PageSearchIndex_Pages{
+					tri("kyt"): pages(0),
+					tri("the"): pages(0, 1, 2),
+					tri("yth"): pages(0),
+					tri("oth"): pages(1),
+					tri("her"): pages(1),
+					tri("Pag"): pages(math.MaxUint32), // short-hand for all pages
+					tri("age"): pages(0, 1, 2),
+					tri("ge_"): pages(1, 2),
+				},
+			},
+			ByRoot: &srvpb.PagedCrossReferences_PageSearchIndex_Postings{
+				Index: map[uint32]*srvpb.PagedCrossReferences_PageSearchIndex_Pages{
+					tri("baz"): pages(1, 2),
+					tri("aze"): pages(1, 2),
+					tri("zel"): pages(1, 2),
+				},
+			},
+			ByPath: &srvpb.PagedCrossReferences_PageSearchIndex_Postings{
+				Index: map[uint32]*srvpb.PagedCrossReferences_PageSearchIndex_Pages{
+					tri("kyt"): pages(0),
+					tri("yth"): pages(0),
+					tri("the"): pages(0),
+					tri("he/"): pages(0),
+					tri("e/g"): pages(0),
+					tri("/go"): pages(0),
+				},
+			},
+			ByResolvedPath: &srvpb.PagedCrossReferences_PageSearchIndex_Postings{
+				Index: map[uint32]*srvpb.PagedCrossReferences_PageSearchIndex_Pages{
+					tri("the"): pages(0, 2),
+					tri("he/"): pages(0, 2),
+					tri("e/b"): pages(0, 2),
+					tri("/ba"): pages(0, 2),
+				},
+			},
+		},
+		PageIndex: []*srvpb.PagedCrossReferences_PageIndex{{
+			PageKey: "kythePage",
+		}, {
+			PageKey: "otherPage_",
+		}, {
+			PageKey: "thePage_",
+		}},
+	}
+
+	// All pages are represented by nil
+	var allPages stringset.Set
+
+	tests := []struct {
+		Filter *xpb.CorpusPathFilters
+		Keys   stringset.Set
+	}{
+		{mustParseFilters(`filter: { type: INCLUDE_ONLY corpus: "kyt" }`), stringset.New("kythePage")},
+		{mustParseFilters(`filter: { type: INCLUDE_ONLY corpus: "kythe" }`), stringset.New("kythePage")},
+		{mustParseFilters(`filter: { type: INCLUDE_ONLY corpus: "kythe|golang" }`), stringset.New("kythePage")},
+		{mustParseFilters(`filter: { type: INCLUDE_ONLY corpus: "other" }`), stringset.New("otherPage_")},
+		{mustParseFilters(`filter: { type: INCLUDE_ONLY corpus: "kythe|other" }`), stringset.New("kythePage", "otherPage_")},
+		{mustParseFilters(`filter: { type: INCLUDE_ONLY corpus: "Page_" }`), stringset.New("thePage_", "otherPage_")},
+		{mustParseFilters(`filter: { type: INCLUDE_ONLY corpus: "the" }`), allPages},
+		{mustParseFilters(`filter: { type: INCLUDE_ONLY corpus: "Page" }`), allPages},
+
+		// Trigrams are separated by field
+		{mustParseFilters(`filter: { type: INCLUDE_ONLY corpus: "kythe/go" }`), stringset.New()},
+		{mustParseFilters(`filter: { type: INCLUDE_ONLY corpus: "bazel" }`), stringset.New()},
+		{mustParseFilters(`filter: { type: INCLUDE_ONLY corpus: "the/ba" }`), stringset.New()},
+		{mustParseFilters(`filter: { type: INCLUDE_ONLY root: "the/ba" }`), stringset.New()},
+		{mustParseFilters(`filter: { type: INCLUDE_ONLY path: "the/ba" }`), stringset.New()},
+		{mustParseFilters(`filter: { type: INCLUDE_ONLY path: "kythe/go" }`), stringset.New("kythePage")},
+		{mustParseFilters(`filter: { type: INCLUDE_ONLY path: "/go" }`), stringset.New("kythePage")},
+		{mustParseFilters(`filter: { type: INCLUDE_ONLY root: "bazel" }`), stringset.New("otherPage_", "thePage_")},
+		{mustParseFilters(`filter: { type: INCLUDE_ONLY resolved_path: "the/ba" }`), stringset.New("kythePage", "thePage_")},
+
+		// Merge filters
+		{mustParseFilters(`filter: { type: INCLUDE_ONLY corpus: "kythe|other" } filter: { type: INCLUDE_ONLY corpus: "other" }`), stringset.New("otherPage_")},
+		{mustParseFilters(`filter: { type: INCLUDE_ONLY resolved_path: "the/ba" } filter: { type: INCLUDE_ONLY root: "bazel" }`), stringset.New("thePage_")},
+
+		// No trigrams; matches everything
+		{mustParseFilters(`filter: { type: INCLUDE_ONLY corpus: "ne" }`), allPages},
+		{mustParseFilters(`filter: { type: INCLUDE_ONLY corpus: "^$" }`), allPages},
+		{mustParseFilters(`filter: { type: INCLUDE_ONLY corpus: "\\.s" }`), allPages},
+
+		// Matches nothing
+		{mustParseFilters(`filter: { type: INCLUDE_ONLY corpus: "nope" }`), stringset.New()},
+		{mustParseFilters(`filter: { type: INCLUDE_ONLY corpus: "rip" }`), stringset.New()},
+		{mustParseFilters(`filter: { type: INCLUDE_ONLY corpus: "kyther" }`), stringset.New()},
+		{mustParseFilters(`filter: { type: INCLUDE_ONLY corpus: "kythe.+google" }`), stringset.New()},
+		{mustParseFilters(`filter: { type: INCLUDE_ONLY corpus: "golang" }`), stringset.New()},
+
+		// Exclusion filter are not handled by the search index; they match all pages.
+		{mustParseFilters(`filter: { type: EXCLUDE corpus: "kythe" }`), allPages},
+	}
+
+	for i, test := range tests {
+		t.Run(strconv.Itoa(i), func(t *testing.T) {
+			t.Logf("CorpusPathFilters: %s", test.Filter)
+			filter, err := compileCorpusPathFilters(test.Filter, nil)
+			testutil.Fatalf(t, "compileCorpusPathFilters: %v", err)
+			found := filter.PageSet(set)
+
+			var expected *pageSet
+			if test.Keys != nil {
+				expected = &pageSet{KeySet: test.Keys}
+			}
+			if diff := compare.ProtoDiff(expected, found); diff != "" {
+				t.Fatalf("Unexpected diff (-expected; +found):\n%s", diff)
+			}
+		})
+	}
+}
+
+func pages(ps ...uint32) *srvpb.PagedCrossReferences_PageSearchIndex_Pages {
+	if len(ps) <= 1 {
+		return &srvpb.PagedCrossReferences_PageSearchIndex_Pages{PageIndex: ps}
+	}
+	encoded := make([]uint32, len(ps))
+	encoded[0] = ps[0]
+	for i, n := range ps[1:] {
+		encoded[i+1] = n - encoded[i]
+	}
+	return &srvpb.PagedCrossReferences_PageSearchIndex_Pages{PageIndex: encoded}
+}
+
 func TestCorpusPathFilters(t *testing.T) {
 	tests := []struct {
 		filters *xpb.CorpusPathFilters