Project import generated by Copybara.

GitOrigin-RevId: d9e9e3fb4e31372ec1fb43b178994ca78fa8fe70
diff --git a/api/v3/README.md b/api/v3/README.md
new file mode 100644
index 0000000..088cf32
--- /dev/null
+++ b/api/v3/README.md
@@ -0,0 +1,32 @@
+# Monorail v3.0 pRPC API
+
+This directory holds all the source for the Monorail pRPC API. This API is
+implemented using `.proto` files to describe a `gRPC` interface (services,
+methods, and request/response messages). It then uses a shim which
+converts the
+[`gRPC` server](http://www.grpc.io/docs/tutorials/basic/python.html)
+(which doesn't work on AppEngine, due to lack of support for HTTP/2) into a
+[`pRPC` server](https://godoc.org/github.com/luci/luci-go/grpc/prpc) which
+supports communication over HTTP/1.1, as well as text and JSON IO.
+
+- Resource name formats for each message are found in the message's resource annotation `pattern` field.
+- This v3.0 pRPC API is a resource-oriented API and aims to closely follow the principles at aip.dev.
+
+
+## API Documentation
+
+All resources, methods, request parameters, and responses are documented in
+[./api_proto](./api_proto).
+
+Resource name formats for each message are found in the message's resource annotation `pattern` field.
+
+## Development
+
+### Regenerating Python from Protocol Buffers
+
+In order to regenerate the python server and client stubs from the `.proto`
+files, run this command:
+
+```bash
+$ make prpc_proto_v3
+```
diff --git a/api/v3/__init__.py b/api/v3/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/api/v3/__init__.py
diff --git a/api/v3/api_constants.py b/api/v3/api_constants.py
new file mode 100644
index 0000000..9752242
--- /dev/null
+++ b/api/v3/api_constants.py
@@ -0,0 +1,28 @@
+# Copyright 2020 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style
+# license that can be found in the LICENSE file.
+"""Some constants used by Monorail's v3 API."""
+
+# Max comments per page in the ListComment API.
+MAX_COMMENTS_PER_PAGE = 100
+
+# Max issues per page in the SearchIssues API.
+MAX_ISSUES_PER_PAGE = 100
+
+# Max issues tp fetch in the BatchGetIssues API.
+MAX_BATCH_ISSUES = 100
+
+# Max issues to modify at once in the ModifyIssues API.
+MAX_MODIFY_ISSUES = 100
+
+# Max impacted issues allowed in a ModifyIssues API.
+MAX_MODIFY_IMPACTED_ISSUES = 50
+
+# Max approval values to modify at once in the ModifyIssueApprovalValues API.
+MAX_MODIFY_APPROVAL_VALUES = 100
+
+# Max users to fetch in the BatchGetUsers API.
+MAX_BATCH_USERS = 100
+
+# Max component defs to fetch in the ListComponentDefs API
+MAX_COMPONENTS_PER_PAGE = 100
diff --git a/api/v3/api_proto/__init__.py b/api/v3/api_proto/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/api/v3/api_proto/__init__.py
diff --git a/api/v3/api_proto/feature_objects.proto b/api/v3/api_proto/feature_objects.proto
new file mode 100644
index 0000000..716dc51
--- /dev/null
+++ b/api/v3/api_proto/feature_objects.proto
@@ -0,0 +1,88 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd
+
+// This file defines protobufs for features and related business
+// objects, e.g., hotlists.
+
+syntax = "proto3";
+
+package monorail.v3;
+
+option go_package = "api/v3/api_proto";
+
+import "google/api/field_behavior.proto";
+import "google/api/resource.proto";
+import "google/protobuf/timestamp.proto";
+import "api/v3/api_proto/issue_objects.proto";
+
+// A user-owned list of Issues.
+// Next available tag: 9
+message Hotlist {
+  option (google.api.resource) = {
+    type: "api.crbug.com/Hotlist"
+    pattern: "hotlists/{hotlist_id}"
+  };
+
+  // Resource name of the hotlist.
+  string name = 1;
+  // `display_name` must follow pattern found at `framework_bizobj.RE_HOTLIST_NAME_PATTERN`.
+  string display_name = 2 [ (google.api.field_behavior) = REQUIRED ];
+  // Resource name of the hotlist owner.
+  // Owners can update hotlist settings, editors, owner, and HotlistItems.
+  // TODO(monorail:7023): field_behavior may be changed in the future.
+  string owner = 3 [
+      (google.api.resource_reference) = {type: "api.crbug.com/User"},
+      (google.api.field_behavior) = REQUIRED ];
+  // Resource names of the hotlist editors.
+  // Editors can update hotlist HotlistItems.
+  repeated string editors = 4 [ (google.api.resource_reference) = {type: "api.crbug.com/User"} ];
+  // Summary of the hotlist.
+  string summary = 5 [ (google.api.field_behavior) = REQUIRED ];
+  // More detailed description of the purpose of the hotlist.
+  string description = 6 [ (google.api.field_behavior) = REQUIRED ];
+  // Ordered list of default columns shown on hotlist's issues list view.
+  repeated IssuesListColumn default_columns = 7;
+
+  // Privacy level of a Hotlist.
+  // Next available tag: 2
+  enum HotlistPrivacy {
+    // This value is unused.
+    HOTLIST_PRIVACY_UNSPECIFIED = 0;
+    // Only the owner and editors of the hotlist can view the hotlist.
+    PRIVATE = 1;
+    // Anyone on the web can view the hotlist.
+    PUBLIC = 2;
+  }
+  HotlistPrivacy hotlist_privacy = 8;
+}
+
+
+// Represents the the position of an Issue in a Hotlist.
+// Next available tag: 7
+message HotlistItem {
+  option (google.api.resource) = {
+    type: "api.crbug.com/HotlistItem"
+    pattern: "hotlists/{hotlist_id}/items/{item_id}"
+  };
+
+  // Resource name of the HotlistItem.
+  string name = 1;
+  // The Issue associated with this item.
+  string issue = 2 [
+      (google.api.resource_reference) = {type: "api.crbug.com/Issue"},
+      (google.api.field_behavior) = IMMUTABLE ];
+  // Represents the item's position in the Hotlist in decreasing priority order.
+  // Values will be from 1 to N (the size of the hotlist), each item having a unique rank.
+  // Changes to rank must be made in `RerankHotlistItems`.
+  uint32 rank = 3 [(google.api.field_behavior) = OUTPUT_ONLY];
+  // Resource name of the adder of HotlistItem.
+  string adder = 4 [
+      (google.api.resource_reference) = {type: "api.crbug.com/User"},
+      (google.api.field_behavior) = OUTPUT_ONLY ];
+  // The time this HotlistItem was added to the hotlist.
+  google.protobuf.Timestamp create_time = 5  [ (google.api.field_behavior) = OUTPUT_ONLY ];
+  // User-provided additional details about this item.
+  string note = 6;
+}
\ No newline at end of file
diff --git a/api/v3/api_proto/feature_objects_pb2.py b/api/v3/api_proto/feature_objects_pb2.py
new file mode 100644
index 0000000..37a1d21
--- /dev/null
+++ b/api/v3/api_proto/feature_objects_pb2.py
@@ -0,0 +1,246 @@
+# -*- coding: utf-8 -*-
+# Generated by the protocol buffer compiler.  DO NOT EDIT!
+# source: api/v3/api_proto/feature_objects.proto
+
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import message as _message
+from google.protobuf import reflection as _reflection
+from google.protobuf import symbol_database as _symbol_database
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+from google.api import field_behavior_pb2 as google_dot_api_dot_field__behavior__pb2
+from google.api import resource_pb2 as google_dot_api_dot_resource__pb2
+from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__pb2
+from api.v3.api_proto import issue_objects_pb2 as api_dot_v3_dot_api__proto_dot_issue__objects__pb2
+
+
+DESCRIPTOR = _descriptor.FileDescriptor(
+  name='api/v3/api_proto/feature_objects.proto',
+  package='monorail.v3',
+  syntax='proto3',
+  serialized_options=b'Z\020api/v3/api_proto',
+  create_key=_descriptor._internal_create_key,
+  serialized_pb=b'\n&api/v3/api_proto/feature_objects.proto\x12\x0bmonorail.v3\x1a\x1fgoogle/api/field_behavior.proto\x1a\x19google/api/resource.proto\x1a\x1fgoogle/protobuf/timestamp.proto\x1a$api/v3/api_proto/issue_objects.proto\"\xac\x03\n\x07Hotlist\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x19\n\x0c\x64isplay_name\x18\x02 \x01(\tB\x03\xe0\x41\x02\x12)\n\x05owner\x18\x03 \x01(\tB\x1a\xfa\x41\x14\n\x12\x61pi.crbug.com/User\xe0\x41\x02\x12(\n\x07\x65\x64itors\x18\x04 \x03(\tB\x17\xfa\x41\x14\n\x12\x61pi.crbug.com/User\x12\x14\n\x07summary\x18\x05 \x01(\tB\x03\xe0\x41\x02\x12\x18\n\x0b\x64\x65scription\x18\x06 \x01(\tB\x03\xe0\x41\x02\x12\x36\n\x0f\x64\x65\x66\x61ult_columns\x18\x07 \x03(\x0b\x32\x1d.monorail.v3.IssuesListColumn\x12<\n\x0fhotlist_privacy\x18\x08 \x01(\x0e\x32#.monorail.v3.Hotlist.HotlistPrivacy\"J\n\x0eHotlistPrivacy\x12\x1f\n\x1bHOTLIST_PRIVACY_UNSPECIFIED\x10\x00\x12\x0b\n\x07PRIVATE\x10\x01\x12\n\n\x06PUBLIC\x10\x02:1\xea\x41.\n\x15\x61pi.crbug.com/Hotlist\x12\x15hotlists/{hotlist_id}\"\x90\x02\n\x0bHotlistItem\x12\x0c\n\x04name\x18\x01 \x01(\t\x12*\n\x05issue\x18\x02 \x01(\tB\x1b\xfa\x41\x15\n\x13\x61pi.crbug.com/Issue\xe0\x41\x05\x12\x11\n\x04rank\x18\x03 \x01(\rB\x03\xe0\x41\x03\x12)\n\x05\x61\x64\x64\x65r\x18\x04 \x01(\tB\x1a\xfa\x41\x14\n\x12\x61pi.crbug.com/User\xe0\x41\x03\x12\x34\n\x0b\x63reate_time\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.TimestampB\x03\xe0\x41\x03\x12\x0c\n\x04note\x18\x06 \x01(\t:E\xea\x41\x42\n\x19\x61pi.crbug.com/HotlistItem\x12%hotlists/{hotlist_id}/items/{item_id}B\x12Z\x10\x61pi/v3/api_protob\x06proto3'
+  ,
+  dependencies=[google_dot_api_dot_field__behavior__pb2.DESCRIPTOR,google_dot_api_dot_resource__pb2.DESCRIPTOR,google_dot_protobuf_dot_timestamp__pb2.DESCRIPTOR,api_dot_v3_dot_api__proto_dot_issue__objects__pb2.DESCRIPTOR,])
+
+
+
+_HOTLIST_HOTLISTPRIVACY = _descriptor.EnumDescriptor(
+  name='HotlistPrivacy',
+  full_name='monorail.v3.Hotlist.HotlistPrivacy',
+  filename=None,
+  file=DESCRIPTOR,
+  create_key=_descriptor._internal_create_key,
+  values=[
+    _descriptor.EnumValueDescriptor(
+      name='HOTLIST_PRIVACY_UNSPECIFIED', index=0, number=0,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='PRIVATE', index=1, number=1,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='PUBLIC', index=2, number=2,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+  ],
+  containing_type=None,
+  serialized_options=None,
+  serialized_start=490,
+  serialized_end=564,
+)
+_sym_db.RegisterEnumDescriptor(_HOTLIST_HOTLISTPRIVACY)
+
+
+_HOTLIST = _descriptor.Descriptor(
+  name='Hotlist',
+  full_name='monorail.v3.Hotlist',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='name', full_name='monorail.v3.Hotlist.name', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='display_name', full_name='monorail.v3.Hotlist.display_name', index=1,
+      number=2, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\340A\002', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='owner', full_name='monorail.v3.Hotlist.owner', index=2,
+      number=3, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\372A\024\n\022api.crbug.com/User\340A\002', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='editors', full_name='monorail.v3.Hotlist.editors', index=3,
+      number=4, type=9, cpp_type=9, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\372A\024\n\022api.crbug.com/User', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='summary', full_name='monorail.v3.Hotlist.summary', index=4,
+      number=5, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\340A\002', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='description', full_name='monorail.v3.Hotlist.description', index=5,
+      number=6, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\340A\002', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='default_columns', full_name='monorail.v3.Hotlist.default_columns', index=6,
+      number=7, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='hotlist_privacy', full_name='monorail.v3.Hotlist.hotlist_privacy', index=7,
+      number=8, type=14, cpp_type=8, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+    _HOTLIST_HOTLISTPRIVACY,
+  ],
+  serialized_options=b'\352A.\n\025api.crbug.com/Hotlist\022\025hotlists/{hotlist_id}',
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=187,
+  serialized_end=615,
+)
+
+
+_HOTLISTITEM = _descriptor.Descriptor(
+  name='HotlistItem',
+  full_name='monorail.v3.HotlistItem',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='name', full_name='monorail.v3.HotlistItem.name', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='issue', full_name='monorail.v3.HotlistItem.issue', index=1,
+      number=2, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\372A\025\n\023api.crbug.com/Issue\340A\005', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='rank', full_name='monorail.v3.HotlistItem.rank', index=2,
+      number=3, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\340A\003', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='adder', full_name='monorail.v3.HotlistItem.adder', index=3,
+      number=4, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\372A\024\n\022api.crbug.com/User\340A\003', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='create_time', full_name='monorail.v3.HotlistItem.create_time', index=4,
+      number=5, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\340A\003', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='note', full_name='monorail.v3.HotlistItem.note', index=5,
+      number=6, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=b'\352AB\n\031api.crbug.com/HotlistItem\022%hotlists/{hotlist_id}/items/{item_id}',
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=618,
+  serialized_end=890,
+)
+
+_HOTLIST.fields_by_name['default_columns'].message_type = api_dot_v3_dot_api__proto_dot_issue__objects__pb2._ISSUESLISTCOLUMN
+_HOTLIST.fields_by_name['hotlist_privacy'].enum_type = _HOTLIST_HOTLISTPRIVACY
+_HOTLIST_HOTLISTPRIVACY.containing_type = _HOTLIST
+_HOTLISTITEM.fields_by_name['create_time'].message_type = google_dot_protobuf_dot_timestamp__pb2._TIMESTAMP
+DESCRIPTOR.message_types_by_name['Hotlist'] = _HOTLIST
+DESCRIPTOR.message_types_by_name['HotlistItem'] = _HOTLISTITEM
+_sym_db.RegisterFileDescriptor(DESCRIPTOR)
+
+Hotlist = _reflection.GeneratedProtocolMessageType('Hotlist', (_message.Message,), {
+  'DESCRIPTOR' : _HOTLIST,
+  '__module__' : 'api.v3.api_proto.feature_objects_pb2'
+  # @@protoc_insertion_point(class_scope:monorail.v3.Hotlist)
+  })
+_sym_db.RegisterMessage(Hotlist)
+
+HotlistItem = _reflection.GeneratedProtocolMessageType('HotlistItem', (_message.Message,), {
+  'DESCRIPTOR' : _HOTLISTITEM,
+  '__module__' : 'api.v3.api_proto.feature_objects_pb2'
+  # @@protoc_insertion_point(class_scope:monorail.v3.HotlistItem)
+  })
+_sym_db.RegisterMessage(HotlistItem)
+
+
+DESCRIPTOR._options = None
+_HOTLIST.fields_by_name['display_name']._options = None
+_HOTLIST.fields_by_name['owner']._options = None
+_HOTLIST.fields_by_name['editors']._options = None
+_HOTLIST.fields_by_name['summary']._options = None
+_HOTLIST.fields_by_name['description']._options = None
+_HOTLIST._options = None
+_HOTLISTITEM.fields_by_name['issue']._options = None
+_HOTLISTITEM.fields_by_name['rank']._options = None
+_HOTLISTITEM.fields_by_name['adder']._options = None
+_HOTLISTITEM.fields_by_name['create_time']._options = None
+_HOTLISTITEM._options = None
+# @@protoc_insertion_point(module_scope)
diff --git a/api/v3/api_proto/frontend.proto b/api/v3/api_proto/frontend.proto
new file mode 100644
index 0000000..b6e8564
--- /dev/null
+++ b/api/v3/api_proto/frontend.proto
@@ -0,0 +1,85 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd
+
+syntax = "proto3";
+
+package monorail.v3;
+
+option go_package = "api/v3/api_proto";
+
+import "google/api/field_behavior.proto";
+import "google/api/resource.proto";
+import "api/v3/api_proto/project_objects.proto";
+
+// ***DO NOT CALL rpcs IN THIS SERVICE.***
+// This service is for Monorail's frontend only.
+
+service Frontend {
+  // status: DO NOT USE
+  // Returns all project specific configurations needed for the SPA client.
+  //
+  // Raises:
+  //   INVALID_ARGUMENT if the project resource name provided is invalid.
+  //   NOT_FOUND if the parent project is not found.
+  //   PERMISSION_DENIED if user is not allowed to view this project.
+  rpc GatherProjectEnvironment (GatherProjectEnvironmentRequest) returns (GatherProjectEnvironmentResponse) {};
+
+  // status: DO NOT USE
+  // Returns all of a given user's project memberships.
+  //
+  // Raises:
+  //   NOT_FOUND if the user is not found.
+  //   INVALID_ARGUMENT if the user resource name provided is invalid.
+  rpc GatherProjectMembershipsForUser (GatherProjectMembershipsForUserRequest)
+    returns (GatherProjectMembershipsForUserResponse) {}
+}
+
+
+// Request message for GatherProjectEnvironment
+// Next available tag: 2
+message GatherProjectEnvironmentRequest {
+  // The name of the project these config environments belong to.
+  string parent = 1 [
+    (google.api.resource_reference) = {type: "api.crbug.com/Project"},
+    (google.api.field_behavior) = REQUIRED ];
+}
+
+
+// Response message for GatherProjectEnvironment
+// Next available tag: 9
+message GatherProjectEnvironmentResponse {
+  // Project definitions such as display_name and summary.
+  Project project = 1;
+  // Configurations of this project such as default search term,
+  // default templates for members and non members.
+  ProjectConfig project_config = 2;
+  // List of statuses that belong to this project.
+  repeated StatusDef statuses = 3;
+  // List of well known labels that belong to this project.
+  repeated LabelDef well_known_labels = 4;
+  // List of components that belong to this project.
+  repeated ComponentDef components = 5;
+  // List of custom fields that belong to this project.
+  repeated FieldDef fields = 6;
+  // List of approval fields that belong to this project.
+  repeated ApprovalDef approval_fields = 7;
+  // Saved search queries that admins defined for this project.
+  repeated ProjectSavedQuery saved_queries = 8;
+}
+
+// The request message for Frontend.GatherProjectMembershipsForUser.
+// Next available tag: 2
+message GatherProjectMembershipsForUserRequest {
+  // The name of the user to request.
+  string user = 1 [
+      (google.api.resource_reference) = {type: "api.crbug.com/User"}];
+}
+
+// The response message for Frontend.GatherProjectMembershipsForUser.
+// Next available tag: 2
+message GatherProjectMembershipsForUserResponse {
+  // The projects that the user is a member of.
+  repeated ProjectMember project_memberships = 1;
+}
\ No newline at end of file
diff --git a/api/v3/api_proto/frontend_pb2.py b/api/v3/api_proto/frontend_pb2.py
new file mode 100644
index 0000000..980b6cd
--- /dev/null
+++ b/api/v3/api_proto/frontend_pb2.py
@@ -0,0 +1,291 @@
+# -*- coding: utf-8 -*-
+# Generated by the protocol buffer compiler.  DO NOT EDIT!
+# source: api/v3/api_proto/frontend.proto
+
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import message as _message
+from google.protobuf import reflection as _reflection
+from google.protobuf import symbol_database as _symbol_database
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+from google.api import field_behavior_pb2 as google_dot_api_dot_field__behavior__pb2
+from google.api import resource_pb2 as google_dot_api_dot_resource__pb2
+from api.v3.api_proto import project_objects_pb2 as api_dot_v3_dot_api__proto_dot_project__objects__pb2
+
+
+DESCRIPTOR = _descriptor.FileDescriptor(
+  name='api/v3/api_proto/frontend.proto',
+  package='monorail.v3',
+  syntax='proto3',
+  serialized_options=b'Z\020api/v3/api_proto',
+  create_key=_descriptor._internal_create_key,
+  serialized_pb=b'\n\x1f\x61pi/v3/api_proto/frontend.proto\x12\x0bmonorail.v3\x1a\x1fgoogle/api/field_behavior.proto\x1a\x19google/api/resource.proto\x1a&api/v3/api_proto/project_objects.proto\"P\n\x1fGatherProjectEnvironmentRequest\x12-\n\x06parent\x18\x01 \x01(\tB\x1d\xfa\x41\x17\n\x15\x61pi.crbug.com/Project\xe0\x41\x02\"\x99\x03\n GatherProjectEnvironmentResponse\x12%\n\x07project\x18\x01 \x01(\x0b\x32\x14.monorail.v3.Project\x12\x32\n\x0eproject_config\x18\x02 \x01(\x0b\x32\x1a.monorail.v3.ProjectConfig\x12(\n\x08statuses\x18\x03 \x03(\x0b\x32\x16.monorail.v3.StatusDef\x12\x30\n\x11well_known_labels\x18\x04 \x03(\x0b\x32\x15.monorail.v3.LabelDef\x12-\n\ncomponents\x18\x05 \x03(\x0b\x32\x19.monorail.v3.ComponentDef\x12%\n\x06\x66ields\x18\x06 \x03(\x0b\x32\x15.monorail.v3.FieldDef\x12\x31\n\x0f\x61pproval_fields\x18\x07 \x03(\x0b\x32\x18.monorail.v3.ApprovalDef\x12\x35\n\rsaved_queries\x18\x08 \x03(\x0b\x32\x1e.monorail.v3.ProjectSavedQuery\"O\n&GatherProjectMembershipsForUserRequest\x12%\n\x04user\x18\x01 \x01(\tB\x17\xfa\x41\x14\n\x12\x61pi.crbug.com/User\"b\n\'GatherProjectMembershipsForUserResponse\x12\x37\n\x13project_memberships\x18\x01 \x03(\x0b\x32\x1a.monorail.v3.ProjectMember2\x96\x02\n\x08\x46rontend\x12y\n\x18GatherProjectEnvironment\x12,.monorail.v3.GatherProjectEnvironmentRequest\x1a-.monorail.v3.GatherProjectEnvironmentResponse\"\x00\x12\x8e\x01\n\x1fGatherProjectMembershipsForUser\x12\x33.monorail.v3.GatherProjectMembershipsForUserRequest\x1a\x34.monorail.v3.GatherProjectMembershipsForUserResponse\"\x00\x42\x12Z\x10\x61pi/v3/api_protob\x06proto3'
+  ,
+  dependencies=[google_dot_api_dot_field__behavior__pb2.DESCRIPTOR,google_dot_api_dot_resource__pb2.DESCRIPTOR,api_dot_v3_dot_api__proto_dot_project__objects__pb2.DESCRIPTOR,])
+
+
+
+
+_GATHERPROJECTENVIRONMENTREQUEST = _descriptor.Descriptor(
+  name='GatherProjectEnvironmentRequest',
+  full_name='monorail.v3.GatherProjectEnvironmentRequest',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='parent', full_name='monorail.v3.GatherProjectEnvironmentRequest.parent', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\372A\027\n\025api.crbug.com/Project\340A\002', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=148,
+  serialized_end=228,
+)
+
+
+_GATHERPROJECTENVIRONMENTRESPONSE = _descriptor.Descriptor(
+  name='GatherProjectEnvironmentResponse',
+  full_name='monorail.v3.GatherProjectEnvironmentResponse',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='project', full_name='monorail.v3.GatherProjectEnvironmentResponse.project', index=0,
+      number=1, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='project_config', full_name='monorail.v3.GatherProjectEnvironmentResponse.project_config', index=1,
+      number=2, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='statuses', full_name='monorail.v3.GatherProjectEnvironmentResponse.statuses', index=2,
+      number=3, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='well_known_labels', full_name='monorail.v3.GatherProjectEnvironmentResponse.well_known_labels', index=3,
+      number=4, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='components', full_name='monorail.v3.GatherProjectEnvironmentResponse.components', index=4,
+      number=5, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='fields', full_name='monorail.v3.GatherProjectEnvironmentResponse.fields', index=5,
+      number=6, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='approval_fields', full_name='monorail.v3.GatherProjectEnvironmentResponse.approval_fields', index=6,
+      number=7, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='saved_queries', full_name='monorail.v3.GatherProjectEnvironmentResponse.saved_queries', index=7,
+      number=8, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=231,
+  serialized_end=640,
+)
+
+
+_GATHERPROJECTMEMBERSHIPSFORUSERREQUEST = _descriptor.Descriptor(
+  name='GatherProjectMembershipsForUserRequest',
+  full_name='monorail.v3.GatherProjectMembershipsForUserRequest',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='user', full_name='monorail.v3.GatherProjectMembershipsForUserRequest.user', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\372A\024\n\022api.crbug.com/User', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=642,
+  serialized_end=721,
+)
+
+
+_GATHERPROJECTMEMBERSHIPSFORUSERRESPONSE = _descriptor.Descriptor(
+  name='GatherProjectMembershipsForUserResponse',
+  full_name='monorail.v3.GatherProjectMembershipsForUserResponse',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='project_memberships', full_name='monorail.v3.GatherProjectMembershipsForUserResponse.project_memberships', index=0,
+      number=1, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=723,
+  serialized_end=821,
+)
+
+_GATHERPROJECTENVIRONMENTRESPONSE.fields_by_name['project'].message_type = api_dot_v3_dot_api__proto_dot_project__objects__pb2._PROJECT
+_GATHERPROJECTENVIRONMENTRESPONSE.fields_by_name['project_config'].message_type = api_dot_v3_dot_api__proto_dot_project__objects__pb2._PROJECTCONFIG
+_GATHERPROJECTENVIRONMENTRESPONSE.fields_by_name['statuses'].message_type = api_dot_v3_dot_api__proto_dot_project__objects__pb2._STATUSDEF
+_GATHERPROJECTENVIRONMENTRESPONSE.fields_by_name['well_known_labels'].message_type = api_dot_v3_dot_api__proto_dot_project__objects__pb2._LABELDEF
+_GATHERPROJECTENVIRONMENTRESPONSE.fields_by_name['components'].message_type = api_dot_v3_dot_api__proto_dot_project__objects__pb2._COMPONENTDEF
+_GATHERPROJECTENVIRONMENTRESPONSE.fields_by_name['fields'].message_type = api_dot_v3_dot_api__proto_dot_project__objects__pb2._FIELDDEF
+_GATHERPROJECTENVIRONMENTRESPONSE.fields_by_name['approval_fields'].message_type = api_dot_v3_dot_api__proto_dot_project__objects__pb2._APPROVALDEF
+_GATHERPROJECTENVIRONMENTRESPONSE.fields_by_name['saved_queries'].message_type = api_dot_v3_dot_api__proto_dot_project__objects__pb2._PROJECTSAVEDQUERY
+_GATHERPROJECTMEMBERSHIPSFORUSERRESPONSE.fields_by_name['project_memberships'].message_type = api_dot_v3_dot_api__proto_dot_project__objects__pb2._PROJECTMEMBER
+DESCRIPTOR.message_types_by_name['GatherProjectEnvironmentRequest'] = _GATHERPROJECTENVIRONMENTREQUEST
+DESCRIPTOR.message_types_by_name['GatherProjectEnvironmentResponse'] = _GATHERPROJECTENVIRONMENTRESPONSE
+DESCRIPTOR.message_types_by_name['GatherProjectMembershipsForUserRequest'] = _GATHERPROJECTMEMBERSHIPSFORUSERREQUEST
+DESCRIPTOR.message_types_by_name['GatherProjectMembershipsForUserResponse'] = _GATHERPROJECTMEMBERSHIPSFORUSERRESPONSE
+_sym_db.RegisterFileDescriptor(DESCRIPTOR)
+
+GatherProjectEnvironmentRequest = _reflection.GeneratedProtocolMessageType('GatherProjectEnvironmentRequest', (_message.Message,), {
+  'DESCRIPTOR' : _GATHERPROJECTENVIRONMENTREQUEST,
+  '__module__' : 'api.v3.api_proto.frontend_pb2'
+  # @@protoc_insertion_point(class_scope:monorail.v3.GatherProjectEnvironmentRequest)
+  })
+_sym_db.RegisterMessage(GatherProjectEnvironmentRequest)
+
+GatherProjectEnvironmentResponse = _reflection.GeneratedProtocolMessageType('GatherProjectEnvironmentResponse', (_message.Message,), {
+  'DESCRIPTOR' : _GATHERPROJECTENVIRONMENTRESPONSE,
+  '__module__' : 'api.v3.api_proto.frontend_pb2'
+  # @@protoc_insertion_point(class_scope:monorail.v3.GatherProjectEnvironmentResponse)
+  })
+_sym_db.RegisterMessage(GatherProjectEnvironmentResponse)
+
+GatherProjectMembershipsForUserRequest = _reflection.GeneratedProtocolMessageType('GatherProjectMembershipsForUserRequest', (_message.Message,), {
+  'DESCRIPTOR' : _GATHERPROJECTMEMBERSHIPSFORUSERREQUEST,
+  '__module__' : 'api.v3.api_proto.frontend_pb2'
+  # @@protoc_insertion_point(class_scope:monorail.v3.GatherProjectMembershipsForUserRequest)
+  })
+_sym_db.RegisterMessage(GatherProjectMembershipsForUserRequest)
+
+GatherProjectMembershipsForUserResponse = _reflection.GeneratedProtocolMessageType('GatherProjectMembershipsForUserResponse', (_message.Message,), {
+  'DESCRIPTOR' : _GATHERPROJECTMEMBERSHIPSFORUSERRESPONSE,
+  '__module__' : 'api.v3.api_proto.frontend_pb2'
+  # @@protoc_insertion_point(class_scope:monorail.v3.GatherProjectMembershipsForUserResponse)
+  })
+_sym_db.RegisterMessage(GatherProjectMembershipsForUserResponse)
+
+
+DESCRIPTOR._options = None
+_GATHERPROJECTENVIRONMENTREQUEST.fields_by_name['parent']._options = None
+_GATHERPROJECTMEMBERSHIPSFORUSERREQUEST.fields_by_name['user']._options = None
+
+_FRONTEND = _descriptor.ServiceDescriptor(
+  name='Frontend',
+  full_name='monorail.v3.Frontend',
+  file=DESCRIPTOR,
+  index=0,
+  serialized_options=None,
+  create_key=_descriptor._internal_create_key,
+  serialized_start=824,
+  serialized_end=1102,
+  methods=[
+  _descriptor.MethodDescriptor(
+    name='GatherProjectEnvironment',
+    full_name='monorail.v3.Frontend.GatherProjectEnvironment',
+    index=0,
+    containing_service=None,
+    input_type=_GATHERPROJECTENVIRONMENTREQUEST,
+    output_type=_GATHERPROJECTENVIRONMENTRESPONSE,
+    serialized_options=None,
+    create_key=_descriptor._internal_create_key,
+  ),
+  _descriptor.MethodDescriptor(
+    name='GatherProjectMembershipsForUser',
+    full_name='monorail.v3.Frontend.GatherProjectMembershipsForUser',
+    index=1,
+    containing_service=None,
+    input_type=_GATHERPROJECTMEMBERSHIPSFORUSERREQUEST,
+    output_type=_GATHERPROJECTMEMBERSHIPSFORUSERRESPONSE,
+    serialized_options=None,
+    create_key=_descriptor._internal_create_key,
+  ),
+])
+_sym_db.RegisterServiceDescriptor(_FRONTEND)
+
+DESCRIPTOR.services_by_name['Frontend'] = _FRONTEND
+
+# @@protoc_insertion_point(module_scope)
diff --git a/api/v3/api_proto/frontend_prpc_pb2.py b/api/v3/api_proto/frontend_prpc_pb2.py
new file mode 100644
index 0000000..5087c3a
--- /dev/null
+++ b/api/v3/api_proto/frontend_prpc_pb2.py
@@ -0,0 +1,864 @@
+# Generated by the pRPC protocol buffer compiler plugin.  DO NOT EDIT!
+# source: api/v3/api_proto/frontend.proto
+
+import base64
+import zlib
+
+from google.protobuf import descriptor_pb2
+
+# Includes description of the api/v3/api_proto/frontend.proto and all of its transitive
+# dependencies. Includes source code info.
+FILE_DESCRIPTOR_SET = descriptor_pb2.FileDescriptorSet()
+FILE_DESCRIPTOR_SET.ParseFromString(zlib.decompress(base64.b64decode(
+    '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'
+    'fce+tWoQGScvK9P54+2Wr8quqec7dzz733LNnncrf7mzAmphcvLM/Z0jGdrnMb8UXPbxmnUb1c'
+    'I0FXl/EOv5E308pkU/5I36ksPD02Y701a3Vc1pQX/YKJp7IDJRqLxcrDzP36Av9O7/BbsJjwIF'
+    'eGrRGQPu43ssrJhq1tfXuuRQw/SgX5prPgr49xkd7lbx+fyE1Mjk8N5E9OTZwbyydMYVN+w+hY'
+    'Hlavvt/UPzQ6TqiL38P5win67XWO+G1xYunQ32EVip/JUtt8fyA/Vsj306MBKTvXPzF4Nh+4x0'
+    '4/l8v7W+KdFnXKbXV0upp+ipNM/g0z1sc73+f6qSH4Pd9Yb8ba30u2/126/cVmeXes/TU18yPW'
+    '+kP++hiO1h/KncgPvah2yj+XO+FvjreTqe3+end+6iGucPBTGum77X5K+cjXb6TrnatrN9shNU'
+    'Wk1TKxVtPksxiSUrLMjj5MXm0zOiUyiUuRWbExejiIZzyie/2m4swlUuppgnjE6Jbncx1+Ot5M'
+    'k7XSYkG9lj7sN8Gtb6m2tZk+aOvbsQpv/E5BvZs+47cTddqvF+enZCHamuKGueX53HZ/W5xeTr'
+    '1J5aBybfrLMf4wfb+/HiH4p1R6udrWFjZ53lufkTy9ipqOq7cL6/Cx/it92l9XrixFZflc1p76'
+    'ZQ1WlmJFtdKndknUeVFJrWuVNL60GC+JPjUlUQXheh4VtW6tCqJ34hXEx3ZhM0VO9K0KW79WYQ'
+    'P0arwwfGwKO+g3l2bKCFG2tW3t4aLfy7zD8YNkH6RP+c3TF6uIQ09zB3bxPdfXedl+/qqgv87c'
+    'TVKWf0bCyVlVOLmJWZa5329PdGl6u99Cw3wqKqqxkCLgLJeGh8VHpiIhiIfFR/hhZp/fnuhV8L'
+    'RYmis9onniPzJf8Pwg2WnpV/rtpCGWZ69OEWtztE2p8UdtfUeur9ezI/z1hPq40FaJ/Z2e8Tcs'
+    'Vueh5LGBObuIchXa+u64TgoF+r5gfV4IFhMISejWOVqUl2pT0HWUWPMFGiOEfVxKpRn1vEH5uA'
+    'DB485hvy1eD8j8kdGJwZPnpiYKg6dO5QvjCZnf4jeO5M/mCyTu2/3W3Mi5qf7R4eH8yETgdl7w'
+    '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'
+    'HdB8A=')))
+_INDEX = {
+    f.name: {
+      'descriptor': f,
+      'services': {s.name: s for s in f.service},
+    }
+    for f in FILE_DESCRIPTOR_SET.file
+}
+
+
+FrontendServiceDescription = {
+  'file_descriptor_set': FILE_DESCRIPTOR_SET,
+  'file_descriptor': _INDEX[u'api/v3/api_proto/frontend.proto']['descriptor'],
+  'service_descriptor': _INDEX[u'api/v3/api_proto/frontend.proto']['services'][u'Frontend'],
+}
diff --git a/api/v3/api_proto/hotlists.proto b/api/v3/api_proto/hotlists.proto
new file mode 100644
index 0000000..1f668f7
--- /dev/null
+++ b/api/v3/api_proto/hotlists.proto
@@ -0,0 +1,276 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd
+
+syntax = "proto3";
+
+package monorail.v3;
+
+option go_package = "api/v3/api_proto";
+
+import "api/v3/api_proto/feature_objects.proto";
+import "google/protobuf/field_mask.proto";
+import "google/protobuf/empty.proto";
+import "google/api/field_behavior.proto";
+import "google/api/resource.proto";
+
+// ***ONLY CALL rpcs WITH `status: {ALPHA|STABLE}`***
+// rpcs without `status` are not ready.
+
+// Hotlists service includes all methods needed for managing Hotlists.
+service Hotlists {
+  // status: NOT READY
+  // Creates a new hotlist.
+  //
+  // Raises:
+  //   NOT_FOUND if some given hotlist editors do not exist.
+  //   ALREADY_EXISTS if a hotlist with the same name owned by the user
+  //   already exists.
+  //   INVALID_ARGUMENT if a `hotlist.owner` is given.
+  rpc CreateHotlist (CreateHotlistRequest) returns (Hotlist) {}
+
+  // status: NOT READY
+  // Returns the requested Hotlist.
+  //
+  // Raises:
+  //   NOT_FOUND if the requested hotlist is not found.
+  //   PERMISSION_DENIED if the requester is not allowed to view the hotlist.
+  //   INVALID_ARGUMENT if the given resource name is not valid.
+  rpc GetHotlist (GetHotlistRequest) returns (Hotlist) {}
+
+  // status: NOT READY
+  // Updates a hotlist.
+  //
+  // Raises:
+  //   NOT_FOUND if the hotlist is not found.
+  //   PERMISSION_DENIED if the requester is not allowed to update the hotlist.
+  //   INVALID_ARGUMENT if required fields are missing.
+  rpc UpdateHotlist (UpdateHotlistRequest) returns (Hotlist) {}
+
+  // status: NOT READY
+  // Deletes a hotlist.
+  //
+  // Raises:
+  //   NOT_FOUND if the hotlist is not found.
+  //   PERMISSION_DENIED if the requester is not allowed to delete the hotlist.
+  rpc DeleteHotlist (GetHotlistRequest) returns (google.protobuf.Empty) {}
+
+  // status: NOT READY
+  // Returns a list of all HotlistItems in the hotlist.
+  //
+  // Raises:
+  //   NOT_FOUND if the parent hotlist is not found.
+  //   PERMISSION_DENIED if the requester is not allowed to view the hotlist.
+  //   INVALID_ARGUMENT if the page_token or given hotlist resource name is not
+  //   valid.
+  rpc ListHotlistItems (ListHotlistItemsRequest) returns (ListHotlistItemsResponse) {}
+
+  // status: NOT READY
+  // Reranks a hotlist's items.
+  //
+  // Raises:
+  //   NOT_FOUND if the hotlist or issues to rerank are not found.
+  //   PERMISSION_DENIED if the requester is not allowed to rerank the hotlist
+  //   or view issues they're trying to rerank.
+  //   INVALID_ARGUMENT if the `target_position` is invalid or `hotlist_items`
+  //   is empty or contains items not in the Hotlist.
+  rpc RerankHotlistItems (RerankHotlistItemsRequest) returns (google.protobuf.Empty) {}
+
+  // status: NOT READY
+  // Adds new items associated with given issues to a hotlist.
+  //
+  // Raises:
+  //   NOT_FOUND if the parent hotlist or issues are not found.
+  //   PERMISSION_DENIED if the requester is not allowed to edit the hotlist or
+  //   view issues they are trying to add.
+  //   INVALID_ARGUMENT if the `target_position` is invalid or `hotlist_items`
+  //   is empty or contains items not in the Hotlist.
+  rpc AddHotlistItems (AddHotlistItemsRequest) returns (google.protobuf.Empty) {}
+
+  // status: NOT READY
+  // Removes items associated with given issues from a hotlist.
+  //
+  // Raises:
+  //   NOT_FOUND if the parent hotlist or issues are not found.
+  //   PERMISSION_DENIED if the requester is not allowed to edit the hotlist or
+  //   view issues they are trying to remove.
+  //   INVALID_ARGUMENT if the `target_position` is invalid or `hotlist_items`
+  //   is empty or contains items not in the Hotlist.
+  rpc RemoveHotlistItems (RemoveHotlistItemsRequest) returns (google.protobuf.Empty) {}
+
+  // status: NOT READY
+  // Removes editors assigned to a hotlist.
+  //
+  // Raises:
+  //   NOT_FOUND if the hotlist is not found.
+  //   PERMISSION_DENIED if the requester is not allowed to remove all specified
+  //   editors from the hotlist.
+  //   INVALID_ARGUMENT if any specified editors are not in the hotlist.
+  rpc RemoveHotlistEditors (RemoveHotlistEditorsRequest) returns (google.protobuf.Empty) {}
+
+  // status: NOT READY
+  // Gathers all viewable hotlists that a user is a member of.
+  //
+  // Raises:
+  //   NOT_FOUND if the user is not found.
+  //   INVALID_ARGUMENT if the `user` is invalid.
+  rpc GatherHotlistsForUser (GatherHotlistsForUserRequest) returns (GatherHotlistsForUserResponse) {}
+}
+
+
+// Request message for CreateHotlist method.
+// Next available tag: 2
+message CreateHotlistRequest {
+  // The hotlist to create.
+  // `hotlist.owner` must be empty. The owner of the new hotlist will be
+  // set to the requester.
+  Hotlist hotlist = 1 [ (google.api.field_behavior) = REQUIRED ];
+}
+
+
+// Request message for GetHotlist method.
+// Next available tag: 2
+message GetHotlistRequest {
+  // The name of the hotlist to retrieve.
+  string name = 1 [
+      (google.api.field_behavior) = REQUIRED,
+      (google.api.resource_reference) = {type: "api.crbug.com/Hotlist"}];
+}
+
+
+// Request message for UpdateHotlist method.
+// Next available tag: 2
+message UpdateHotlistRequest {
+  // The hotlist's `name` field is used to identify the hotlist to be updated.
+  Hotlist hotlist = 1 [
+      (google.api.field_behavior) = REQUIRED,
+      (google.api.resource_reference) = {type: "api.crbug.com/Hotlist"} ];
+  // The list of fields to be updated.
+  google.protobuf.FieldMask update_mask = 2 [ (google.api.field_behavior) = REQUIRED ];
+}
+
+
+// Request message for ListHotlistItems method.
+// Next available tag: 5
+message ListHotlistItemsRequest {
+  // The parent hotlist, which owns this collection of items.
+  string parent = 1 [
+      (google.api.field_behavior) = REQUIRED,
+      (google.api.resource_reference) = {type: "api.crbug.com/Hotlist"} ];
+  // The maximum number of items to return. The service may return fewer than
+  // this value.
+  // If unspecified, at most 1000 items will be returned.
+  // The maximum value is 1000; values above 1000 will be coerced to 1000.
+  int32 page_size = 2;
+  // The string of comma separated field names used to order the items.
+  // Adding '-' before a field, reverses the sort order.
+  // E.g. 'stars,-status' sorts the items by number of stars low to high, then
+  // status high to low.
+  // If unspecified, items will be ordered by their rank in the parent.
+  string order_by = 3;
+  // A page token, received from a previous `ListHotlistItems` call.
+  // Provide this to retrieve the subsequent page.
+  //
+  // When paginating, all other parameters provided to `ListHotlistItems` must
+  // match the call that provided the page token.
+  string page_token = 4;
+}
+
+
+// Response to ListHotlistItems call.
+// Next available tag: 3
+message ListHotlistItemsResponse {
+  // The items from the specified hotlist.
+  repeated HotlistItem items = 1;
+  // A token, which can be sent as `page_token` to retrieve the next page.
+  // If this field is omitted, there are no subsequent pages.
+  string next_page_token = 2;
+}
+
+
+// The request used to rerank a Hotlist.
+// Next available tag: 4
+message RerankHotlistItemsRequest {
+  // Resource name of the Hotlist to rerank.
+  string name = 1 [
+      (google.api.resource_reference) = {type: "api.crbug.com/Hotlist"},
+      (google.api.field_behavior) = REQUIRED ];
+  // HotlistItems to be moved. The order of `hotlist_items` will
+  // determine the order of these items after they have been moved.
+  // E.g. With items [a, b, c, d, e], moving [d, c] to `target_position` 3, will
+  // result in items [a, b, e, d, c].
+  repeated string hotlist_items = 2 [
+      (google.api.resource_reference) = {type: "api.crbug.com/HotlistItem"},
+      (google.api.field_behavior) = REQUIRED ];
+  // Target starting position of the moved items.
+  // `target_position` must be between 0 and (# hotlist items - # items being moved).
+  uint32 target_position = 3 [ (google.api.field_behavior) = REQUIRED ];
+}
+
+
+// Request message for an AddHotlistItems call.
+// Next available tag: 4
+message AddHotlistItemsRequest {
+  // Resource name of the Hotlist to add new items to.
+  string parent = 1 [
+      (google.api.field_behavior) = REQUIRED,
+      (google.api.resource_reference) = {type: "api.crbug.com/Hotlist"} ];
+  // Resource names of Issues to associate with new HotlistItems added to `parent`.
+  repeated string issues = 2 [
+      (google.api.field_behavior) = REQUIRED,
+      (google.api.resource_reference) = {type: "api.crbug.com/Issue"} ];
+  // Target starting position of the new items.
+  // `target_position` must be between [0 and # of items that currently exist in
+  // `parent`]. The request will fail if a specified `target_position` is outside
+  // of this range.
+  // New HotlistItems added to a non-last position of the hotlist will
+  // cause ranks of existing HotlistItems below `target_position` to be adjusted.
+  // If no `target_position` is given, new items will be added to the end of
+  // `parent`.
+  uint32 target_position = 3;
+}
+
+
+// Request message for a RemoveHotlistItems call.
+// Next available tag: 3
+message RemoveHotlistItemsRequest {
+  // Resource name of the Hotlist to remove items from.
+  string parent = 1 [
+      (google.api.field_behavior) = REQUIRED,
+      (google.api.resource_reference) = {type: "api.crbug.com/Hotlist"} ];
+  // Resource names of Issues associated with HotlistItems that should be removed.
+  repeated string issues = 2 [
+      (google.api.field_behavior) = REQUIRED,
+      (google.api.resource_reference) = {type: "api.crbug.com/Issue"} ];
+}
+
+
+// Request message for a RemoveHotlistEditors call.
+// Next available tag: 3
+message RemoveHotlistEditorsRequest {
+  // Resource name of the Hotlist to remove editors from.
+  string name = 1 [
+      (google.api.field_behavior) = REQUIRED,
+      (google.api.resource_reference) = {type: "api.crbug.com/Hotlist"} ];
+  // Resource names of Users associated with the hotlist that should be removed.
+  repeated string editors = 2 [
+      (google.api.field_behavior) = REQUIRED,
+      (google.api.resource_reference) = {type: "api.crbug.com/User"} ];
+}
+
+
+// Request message for a GatherHotlistsForUser call.
+// Next available tag: 2
+message GatherHotlistsForUserRequest {
+  // Resource name of the user whose hotlists we want to fetch.
+  string user = 1 [ (google.api.field_behavior) = REQUIRED,
+      (google.api.resource_reference) = {type: "api.crbug.com/User"} ];
+}
+
+
+// Response message for a GatherHotlistsForUser call.
+// Next available tag: 2
+message GatherHotlistsForUserResponse {
+  repeated Hotlist hotlists = 1;
+}
\ No newline at end of file
diff --git a/api/v3/api_proto/hotlists_pb2.py b/api/v3/api_proto/hotlists_pb2.py
new file mode 100644
index 0000000..0258602
--- /dev/null
+++ b/api/v3/api_proto/hotlists_pb2.py
@@ -0,0 +1,690 @@
+# -*- coding: utf-8 -*-
+# Generated by the protocol buffer compiler.  DO NOT EDIT!
+# source: api/v3/api_proto/hotlists.proto
+
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import message as _message
+from google.protobuf import reflection as _reflection
+from google.protobuf import symbol_database as _symbol_database
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+from api.v3.api_proto import feature_objects_pb2 as api_dot_v3_dot_api__proto_dot_feature__objects__pb2
+from google.protobuf import field_mask_pb2 as google_dot_protobuf_dot_field__mask__pb2
+from google.protobuf import empty_pb2 as google_dot_protobuf_dot_empty__pb2
+from google.api import field_behavior_pb2 as google_dot_api_dot_field__behavior__pb2
+from google.api import resource_pb2 as google_dot_api_dot_resource__pb2
+
+
+DESCRIPTOR = _descriptor.FileDescriptor(
+  name='api/v3/api_proto/hotlists.proto',
+  package='monorail.v3',
+  syntax='proto3',
+  serialized_options=b'Z\020api/v3/api_proto',
+  create_key=_descriptor._internal_create_key,
+  serialized_pb=b'\n\x1f\x61pi/v3/api_proto/hotlists.proto\x12\x0bmonorail.v3\x1a&api/v3/api_proto/feature_objects.proto\x1a google/protobuf/field_mask.proto\x1a\x1bgoogle/protobuf/empty.proto\x1a\x1fgoogle/api/field_behavior.proto\x1a\x19google/api/resource.proto\"B\n\x14\x43reateHotlistRequest\x12*\n\x07hotlist\x18\x01 \x01(\x0b\x32\x14.monorail.v3.HotlistB\x03\xe0\x41\x02\"@\n\x11GetHotlistRequest\x12+\n\x04name\x18\x01 \x01(\tB\x1d\xe0\x41\x02\xfa\x41\x17\n\x15\x61pi.crbug.com/Hotlist\"\x92\x01\n\x14UpdateHotlistRequest\x12\x44\n\x07hotlist\x18\x01 \x01(\x0b\x32\x14.monorail.v3.HotlistB\x1d\xe0\x41\x02\xfa\x41\x17\n\x15\x61pi.crbug.com/Hotlist\x12\x34\n\x0bupdate_mask\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.FieldMaskB\x03\xe0\x41\x02\"\x81\x01\n\x17ListHotlistItemsRequest\x12-\n\x06parent\x18\x01 \x01(\tB\x1d\xe0\x41\x02\xfa\x41\x17\n\x15\x61pi.crbug.com/Hotlist\x12\x11\n\tpage_size\x18\x02 \x01(\x05\x12\x10\n\x08order_by\x18\x03 \x01(\t\x12\x12\n\npage_token\x18\x04 \x01(\t\"\\\n\x18ListHotlistItemsResponse\x12\'\n\x05items\x18\x01 \x03(\x0b\x32\x18.monorail.v3.HotlistItem\x12\x17\n\x0fnext_page_token\x18\x02 \x01(\t\"\xa0\x01\n\x19RerankHotlistItemsRequest\x12+\n\x04name\x18\x01 \x01(\tB\x1d\xfa\x41\x17\n\x15\x61pi.crbug.com/Hotlist\xe0\x41\x02\x12\x38\n\rhotlist_items\x18\x02 \x03(\tB!\xfa\x41\x1b\n\x19\x61pi.crbug.com/HotlistItem\xe0\x41\x02\x12\x1c\n\x0ftarget_position\x18\x03 \x01(\rB\x03\xe0\x41\x02\"\x8d\x01\n\x16\x41\x64\x64HotlistItemsRequest\x12-\n\x06parent\x18\x01 \x01(\tB\x1d\xe0\x41\x02\xfa\x41\x17\n\x15\x61pi.crbug.com/Hotlist\x12+\n\x06issues\x18\x02 \x03(\tB\x1b\xe0\x41\x02\xfa\x41\x15\n\x13\x61pi.crbug.com/Issue\x12\x17\n\x0ftarget_position\x18\x03 \x01(\r\"w\n\x19RemoveHotlistItemsRequest\x12-\n\x06parent\x18\x01 \x01(\tB\x1d\xe0\x41\x02\xfa\x41\x17\n\x15\x61pi.crbug.com/Hotlist\x12+\n\x06issues\x18\x02 \x03(\tB\x1b\xe0\x41\x02\xfa\x41\x15\n\x13\x61pi.crbug.com/Issue\"w\n\x1bRemoveHotlistEditorsRequest\x12+\n\x04name\x18\x01 \x01(\tB\x1d\xe0\x41\x02\xfa\x41\x17\n\x15\x61pi.crbug.com/Hotlist\x12+\n\x07\x65\x64itors\x18\x02 \x03(\tB\x1a\xe0\x41\x02\xfa\x41\x14\n\x12\x61pi.crbug.com/User\"H\n\x1cGatherHotlistsForUserRequest\x12(\n\x04user\x18\x01 \x01(\tB\x1a\xe0\x41\x02\xfa\x41\x14\n\x12\x61pi.crbug.com/User\"G\n\x1dGatherHotlistsForUserResponse\x12&\n\x08hotlists\x18\x01 \x03(\x0b\x32\x14.monorail.v3.Hotlist2\xe6\x06\n\x08Hotlists\x12J\n\rCreateHotlist\x12!.monorail.v3.CreateHotlistRequest\x1a\x14.monorail.v3.Hotlist\"\x00\x12\x44\n\nGetHotlist\x12\x1e.monorail.v3.GetHotlistRequest\x1a\x14.monorail.v3.Hotlist\"\x00\x12J\n\rUpdateHotlist\x12!.monorail.v3.UpdateHotlistRequest\x1a\x14.monorail.v3.Hotlist\"\x00\x12I\n\rDeleteHotlist\x12\x1e.monorail.v3.GetHotlistRequest\x1a\x16.google.protobuf.Empty\"\x00\x12\x61\n\x10ListHotlistItems\x12$.monorail.v3.ListHotlistItemsRequest\x1a%.monorail.v3.ListHotlistItemsResponse\"\x00\x12V\n\x12RerankHotlistItems\x12&.monorail.v3.RerankHotlistItemsRequest\x1a\x16.google.protobuf.Empty\"\x00\x12P\n\x0f\x41\x64\x64HotlistItems\x12#.monorail.v3.AddHotlistItemsRequest\x1a\x16.google.protobuf.Empty\"\x00\x12V\n\x12RemoveHotlistItems\x12&.monorail.v3.RemoveHotlistItemsRequest\x1a\x16.google.protobuf.Empty\"\x00\x12Z\n\x14RemoveHotlistEditors\x12(.monorail.v3.RemoveHotlistEditorsRequest\x1a\x16.google.protobuf.Empty\"\x00\x12p\n\x15GatherHotlistsForUser\x12).monorail.v3.GatherHotlistsForUserRequest\x1a*.monorail.v3.GatherHotlistsForUserResponse\"\x00\x42\x12Z\x10\x61pi/v3/api_protob\x06proto3'
+  ,
+  dependencies=[api_dot_v3_dot_api__proto_dot_feature__objects__pb2.DESCRIPTOR,google_dot_protobuf_dot_field__mask__pb2.DESCRIPTOR,google_dot_protobuf_dot_empty__pb2.DESCRIPTOR,google_dot_api_dot_field__behavior__pb2.DESCRIPTOR,google_dot_api_dot_resource__pb2.DESCRIPTOR,])
+
+
+
+
+_CREATEHOTLISTREQUEST = _descriptor.Descriptor(
+  name='CreateHotlistRequest',
+  full_name='monorail.v3.CreateHotlistRequest',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='hotlist', full_name='monorail.v3.CreateHotlistRequest.hotlist', index=0,
+      number=1, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\340A\002', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=211,
+  serialized_end=277,
+)
+
+
+_GETHOTLISTREQUEST = _descriptor.Descriptor(
+  name='GetHotlistRequest',
+  full_name='monorail.v3.GetHotlistRequest',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='name', full_name='monorail.v3.GetHotlistRequest.name', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\340A\002\372A\027\n\025api.crbug.com/Hotlist', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=279,
+  serialized_end=343,
+)
+
+
+_UPDATEHOTLISTREQUEST = _descriptor.Descriptor(
+  name='UpdateHotlistRequest',
+  full_name='monorail.v3.UpdateHotlistRequest',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='hotlist', full_name='monorail.v3.UpdateHotlistRequest.hotlist', index=0,
+      number=1, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\340A\002\372A\027\n\025api.crbug.com/Hotlist', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='update_mask', full_name='monorail.v3.UpdateHotlistRequest.update_mask', index=1,
+      number=2, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\340A\002', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=346,
+  serialized_end=492,
+)
+
+
+_LISTHOTLISTITEMSREQUEST = _descriptor.Descriptor(
+  name='ListHotlistItemsRequest',
+  full_name='monorail.v3.ListHotlistItemsRequest',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='parent', full_name='monorail.v3.ListHotlistItemsRequest.parent', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\340A\002\372A\027\n\025api.crbug.com/Hotlist', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='page_size', full_name='monorail.v3.ListHotlistItemsRequest.page_size', index=1,
+      number=2, type=5, cpp_type=1, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='order_by', full_name='monorail.v3.ListHotlistItemsRequest.order_by', index=2,
+      number=3, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='page_token', full_name='monorail.v3.ListHotlistItemsRequest.page_token', index=3,
+      number=4, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=495,
+  serialized_end=624,
+)
+
+
+_LISTHOTLISTITEMSRESPONSE = _descriptor.Descriptor(
+  name='ListHotlistItemsResponse',
+  full_name='monorail.v3.ListHotlistItemsResponse',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='items', full_name='monorail.v3.ListHotlistItemsResponse.items', index=0,
+      number=1, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='next_page_token', full_name='monorail.v3.ListHotlistItemsResponse.next_page_token', index=1,
+      number=2, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=626,
+  serialized_end=718,
+)
+
+
+_RERANKHOTLISTITEMSREQUEST = _descriptor.Descriptor(
+  name='RerankHotlistItemsRequest',
+  full_name='monorail.v3.RerankHotlistItemsRequest',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='name', full_name='monorail.v3.RerankHotlistItemsRequest.name', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\372A\027\n\025api.crbug.com/Hotlist\340A\002', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='hotlist_items', full_name='monorail.v3.RerankHotlistItemsRequest.hotlist_items', index=1,
+      number=2, type=9, cpp_type=9, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\372A\033\n\031api.crbug.com/HotlistItem\340A\002', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='target_position', full_name='monorail.v3.RerankHotlistItemsRequest.target_position', index=2,
+      number=3, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\340A\002', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=721,
+  serialized_end=881,
+)
+
+
+_ADDHOTLISTITEMSREQUEST = _descriptor.Descriptor(
+  name='AddHotlistItemsRequest',
+  full_name='monorail.v3.AddHotlistItemsRequest',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='parent', full_name='monorail.v3.AddHotlistItemsRequest.parent', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\340A\002\372A\027\n\025api.crbug.com/Hotlist', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='issues', full_name='monorail.v3.AddHotlistItemsRequest.issues', index=1,
+      number=2, type=9, cpp_type=9, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\340A\002\372A\025\n\023api.crbug.com/Issue', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='target_position', full_name='monorail.v3.AddHotlistItemsRequest.target_position', index=2,
+      number=3, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=884,
+  serialized_end=1025,
+)
+
+
+_REMOVEHOTLISTITEMSREQUEST = _descriptor.Descriptor(
+  name='RemoveHotlistItemsRequest',
+  full_name='monorail.v3.RemoveHotlistItemsRequest',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='parent', full_name='monorail.v3.RemoveHotlistItemsRequest.parent', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\340A\002\372A\027\n\025api.crbug.com/Hotlist', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='issues', full_name='monorail.v3.RemoveHotlistItemsRequest.issues', index=1,
+      number=2, type=9, cpp_type=9, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\340A\002\372A\025\n\023api.crbug.com/Issue', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=1027,
+  serialized_end=1146,
+)
+
+
+_REMOVEHOTLISTEDITORSREQUEST = _descriptor.Descriptor(
+  name='RemoveHotlistEditorsRequest',
+  full_name='monorail.v3.RemoveHotlistEditorsRequest',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='name', full_name='monorail.v3.RemoveHotlistEditorsRequest.name', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\340A\002\372A\027\n\025api.crbug.com/Hotlist', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='editors', full_name='monorail.v3.RemoveHotlistEditorsRequest.editors', index=1,
+      number=2, type=9, cpp_type=9, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\340A\002\372A\024\n\022api.crbug.com/User', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=1148,
+  serialized_end=1267,
+)
+
+
+_GATHERHOTLISTSFORUSERREQUEST = _descriptor.Descriptor(
+  name='GatherHotlistsForUserRequest',
+  full_name='monorail.v3.GatherHotlistsForUserRequest',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='user', full_name='monorail.v3.GatherHotlistsForUserRequest.user', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\340A\002\372A\024\n\022api.crbug.com/User', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=1269,
+  serialized_end=1341,
+)
+
+
+_GATHERHOTLISTSFORUSERRESPONSE = _descriptor.Descriptor(
+  name='GatherHotlistsForUserResponse',
+  full_name='monorail.v3.GatherHotlistsForUserResponse',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='hotlists', full_name='monorail.v3.GatherHotlistsForUserResponse.hotlists', index=0,
+      number=1, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=1343,
+  serialized_end=1414,
+)
+
+_CREATEHOTLISTREQUEST.fields_by_name['hotlist'].message_type = api_dot_v3_dot_api__proto_dot_feature__objects__pb2._HOTLIST
+_UPDATEHOTLISTREQUEST.fields_by_name['hotlist'].message_type = api_dot_v3_dot_api__proto_dot_feature__objects__pb2._HOTLIST
+_UPDATEHOTLISTREQUEST.fields_by_name['update_mask'].message_type = google_dot_protobuf_dot_field__mask__pb2._FIELDMASK
+_LISTHOTLISTITEMSRESPONSE.fields_by_name['items'].message_type = api_dot_v3_dot_api__proto_dot_feature__objects__pb2._HOTLISTITEM
+_GATHERHOTLISTSFORUSERRESPONSE.fields_by_name['hotlists'].message_type = api_dot_v3_dot_api__proto_dot_feature__objects__pb2._HOTLIST
+DESCRIPTOR.message_types_by_name['CreateHotlistRequest'] = _CREATEHOTLISTREQUEST
+DESCRIPTOR.message_types_by_name['GetHotlistRequest'] = _GETHOTLISTREQUEST
+DESCRIPTOR.message_types_by_name['UpdateHotlistRequest'] = _UPDATEHOTLISTREQUEST
+DESCRIPTOR.message_types_by_name['ListHotlistItemsRequest'] = _LISTHOTLISTITEMSREQUEST
+DESCRIPTOR.message_types_by_name['ListHotlistItemsResponse'] = _LISTHOTLISTITEMSRESPONSE
+DESCRIPTOR.message_types_by_name['RerankHotlistItemsRequest'] = _RERANKHOTLISTITEMSREQUEST
+DESCRIPTOR.message_types_by_name['AddHotlistItemsRequest'] = _ADDHOTLISTITEMSREQUEST
+DESCRIPTOR.message_types_by_name['RemoveHotlistItemsRequest'] = _REMOVEHOTLISTITEMSREQUEST
+DESCRIPTOR.message_types_by_name['RemoveHotlistEditorsRequest'] = _REMOVEHOTLISTEDITORSREQUEST
+DESCRIPTOR.message_types_by_name['GatherHotlistsForUserRequest'] = _GATHERHOTLISTSFORUSERREQUEST
+DESCRIPTOR.message_types_by_name['GatherHotlistsForUserResponse'] = _GATHERHOTLISTSFORUSERRESPONSE
+_sym_db.RegisterFileDescriptor(DESCRIPTOR)
+
+CreateHotlistRequest = _reflection.GeneratedProtocolMessageType('CreateHotlistRequest', (_message.Message,), {
+  'DESCRIPTOR' : _CREATEHOTLISTREQUEST,
+  '__module__' : 'api.v3.api_proto.hotlists_pb2'
+  # @@protoc_insertion_point(class_scope:monorail.v3.CreateHotlistRequest)
+  })
+_sym_db.RegisterMessage(CreateHotlistRequest)
+
+GetHotlistRequest = _reflection.GeneratedProtocolMessageType('GetHotlistRequest', (_message.Message,), {
+  'DESCRIPTOR' : _GETHOTLISTREQUEST,
+  '__module__' : 'api.v3.api_proto.hotlists_pb2'
+  # @@protoc_insertion_point(class_scope:monorail.v3.GetHotlistRequest)
+  })
+_sym_db.RegisterMessage(GetHotlistRequest)
+
+UpdateHotlistRequest = _reflection.GeneratedProtocolMessageType('UpdateHotlistRequest', (_message.Message,), {
+  'DESCRIPTOR' : _UPDATEHOTLISTREQUEST,
+  '__module__' : 'api.v3.api_proto.hotlists_pb2'
+  # @@protoc_insertion_point(class_scope:monorail.v3.UpdateHotlistRequest)
+  })
+_sym_db.RegisterMessage(UpdateHotlistRequest)
+
+ListHotlistItemsRequest = _reflection.GeneratedProtocolMessageType('ListHotlistItemsRequest', (_message.Message,), {
+  'DESCRIPTOR' : _LISTHOTLISTITEMSREQUEST,
+  '__module__' : 'api.v3.api_proto.hotlists_pb2'
+  # @@protoc_insertion_point(class_scope:monorail.v3.ListHotlistItemsRequest)
+  })
+_sym_db.RegisterMessage(ListHotlistItemsRequest)
+
+ListHotlistItemsResponse = _reflection.GeneratedProtocolMessageType('ListHotlistItemsResponse', (_message.Message,), {
+  'DESCRIPTOR' : _LISTHOTLISTITEMSRESPONSE,
+  '__module__' : 'api.v3.api_proto.hotlists_pb2'
+  # @@protoc_insertion_point(class_scope:monorail.v3.ListHotlistItemsResponse)
+  })
+_sym_db.RegisterMessage(ListHotlistItemsResponse)
+
+RerankHotlistItemsRequest = _reflection.GeneratedProtocolMessageType('RerankHotlistItemsRequest', (_message.Message,), {
+  'DESCRIPTOR' : _RERANKHOTLISTITEMSREQUEST,
+  '__module__' : 'api.v3.api_proto.hotlists_pb2'
+  # @@protoc_insertion_point(class_scope:monorail.v3.RerankHotlistItemsRequest)
+  })
+_sym_db.RegisterMessage(RerankHotlistItemsRequest)
+
+AddHotlistItemsRequest = _reflection.GeneratedProtocolMessageType('AddHotlistItemsRequest', (_message.Message,), {
+  'DESCRIPTOR' : _ADDHOTLISTITEMSREQUEST,
+  '__module__' : 'api.v3.api_proto.hotlists_pb2'
+  # @@protoc_insertion_point(class_scope:monorail.v3.AddHotlistItemsRequest)
+  })
+_sym_db.RegisterMessage(AddHotlistItemsRequest)
+
+RemoveHotlistItemsRequest = _reflection.GeneratedProtocolMessageType('RemoveHotlistItemsRequest', (_message.Message,), {
+  'DESCRIPTOR' : _REMOVEHOTLISTITEMSREQUEST,
+  '__module__' : 'api.v3.api_proto.hotlists_pb2'
+  # @@protoc_insertion_point(class_scope:monorail.v3.RemoveHotlistItemsRequest)
+  })
+_sym_db.RegisterMessage(RemoveHotlistItemsRequest)
+
+RemoveHotlistEditorsRequest = _reflection.GeneratedProtocolMessageType('RemoveHotlistEditorsRequest', (_message.Message,), {
+  'DESCRIPTOR' : _REMOVEHOTLISTEDITORSREQUEST,
+  '__module__' : 'api.v3.api_proto.hotlists_pb2'
+  # @@protoc_insertion_point(class_scope:monorail.v3.RemoveHotlistEditorsRequest)
+  })
+_sym_db.RegisterMessage(RemoveHotlistEditorsRequest)
+
+GatherHotlistsForUserRequest = _reflection.GeneratedProtocolMessageType('GatherHotlistsForUserRequest', (_message.Message,), {
+  'DESCRIPTOR' : _GATHERHOTLISTSFORUSERREQUEST,
+  '__module__' : 'api.v3.api_proto.hotlists_pb2'
+  # @@protoc_insertion_point(class_scope:monorail.v3.GatherHotlistsForUserRequest)
+  })
+_sym_db.RegisterMessage(GatherHotlistsForUserRequest)
+
+GatherHotlistsForUserResponse = _reflection.GeneratedProtocolMessageType('GatherHotlistsForUserResponse', (_message.Message,), {
+  'DESCRIPTOR' : _GATHERHOTLISTSFORUSERRESPONSE,
+  '__module__' : 'api.v3.api_proto.hotlists_pb2'
+  # @@protoc_insertion_point(class_scope:monorail.v3.GatherHotlistsForUserResponse)
+  })
+_sym_db.RegisterMessage(GatherHotlistsForUserResponse)
+
+
+DESCRIPTOR._options = None
+_CREATEHOTLISTREQUEST.fields_by_name['hotlist']._options = None
+_GETHOTLISTREQUEST.fields_by_name['name']._options = None
+_UPDATEHOTLISTREQUEST.fields_by_name['hotlist']._options = None
+_UPDATEHOTLISTREQUEST.fields_by_name['update_mask']._options = None
+_LISTHOTLISTITEMSREQUEST.fields_by_name['parent']._options = None
+_RERANKHOTLISTITEMSREQUEST.fields_by_name['name']._options = None
+_RERANKHOTLISTITEMSREQUEST.fields_by_name['hotlist_items']._options = None
+_RERANKHOTLISTITEMSREQUEST.fields_by_name['target_position']._options = None
+_ADDHOTLISTITEMSREQUEST.fields_by_name['parent']._options = None
+_ADDHOTLISTITEMSREQUEST.fields_by_name['issues']._options = None
+_REMOVEHOTLISTITEMSREQUEST.fields_by_name['parent']._options = None
+_REMOVEHOTLISTITEMSREQUEST.fields_by_name['issues']._options = None
+_REMOVEHOTLISTEDITORSREQUEST.fields_by_name['name']._options = None
+_REMOVEHOTLISTEDITORSREQUEST.fields_by_name['editors']._options = None
+_GATHERHOTLISTSFORUSERREQUEST.fields_by_name['user']._options = None
+
+_HOTLISTS = _descriptor.ServiceDescriptor(
+  name='Hotlists',
+  full_name='monorail.v3.Hotlists',
+  file=DESCRIPTOR,
+  index=0,
+  serialized_options=None,
+  create_key=_descriptor._internal_create_key,
+  serialized_start=1417,
+  serialized_end=2287,
+  methods=[
+  _descriptor.MethodDescriptor(
+    name='CreateHotlist',
+    full_name='monorail.v3.Hotlists.CreateHotlist',
+    index=0,
+    containing_service=None,
+    input_type=_CREATEHOTLISTREQUEST,
+    output_type=api_dot_v3_dot_api__proto_dot_feature__objects__pb2._HOTLIST,
+    serialized_options=None,
+    create_key=_descriptor._internal_create_key,
+  ),
+  _descriptor.MethodDescriptor(
+    name='GetHotlist',
+    full_name='monorail.v3.Hotlists.GetHotlist',
+    index=1,
+    containing_service=None,
+    input_type=_GETHOTLISTREQUEST,
+    output_type=api_dot_v3_dot_api__proto_dot_feature__objects__pb2._HOTLIST,
+    serialized_options=None,
+    create_key=_descriptor._internal_create_key,
+  ),
+  _descriptor.MethodDescriptor(
+    name='UpdateHotlist',
+    full_name='monorail.v3.Hotlists.UpdateHotlist',
+    index=2,
+    containing_service=None,
+    input_type=_UPDATEHOTLISTREQUEST,
+    output_type=api_dot_v3_dot_api__proto_dot_feature__objects__pb2._HOTLIST,
+    serialized_options=None,
+    create_key=_descriptor._internal_create_key,
+  ),
+  _descriptor.MethodDescriptor(
+    name='DeleteHotlist',
+    full_name='monorail.v3.Hotlists.DeleteHotlist',
+    index=3,
+    containing_service=None,
+    input_type=_GETHOTLISTREQUEST,
+    output_type=google_dot_protobuf_dot_empty__pb2._EMPTY,
+    serialized_options=None,
+    create_key=_descriptor._internal_create_key,
+  ),
+  _descriptor.MethodDescriptor(
+    name='ListHotlistItems',
+    full_name='monorail.v3.Hotlists.ListHotlistItems',
+    index=4,
+    containing_service=None,
+    input_type=_LISTHOTLISTITEMSREQUEST,
+    output_type=_LISTHOTLISTITEMSRESPONSE,
+    serialized_options=None,
+    create_key=_descriptor._internal_create_key,
+  ),
+  _descriptor.MethodDescriptor(
+    name='RerankHotlistItems',
+    full_name='monorail.v3.Hotlists.RerankHotlistItems',
+    index=5,
+    containing_service=None,
+    input_type=_RERANKHOTLISTITEMSREQUEST,
+    output_type=google_dot_protobuf_dot_empty__pb2._EMPTY,
+    serialized_options=None,
+    create_key=_descriptor._internal_create_key,
+  ),
+  _descriptor.MethodDescriptor(
+    name='AddHotlistItems',
+    full_name='monorail.v3.Hotlists.AddHotlistItems',
+    index=6,
+    containing_service=None,
+    input_type=_ADDHOTLISTITEMSREQUEST,
+    output_type=google_dot_protobuf_dot_empty__pb2._EMPTY,
+    serialized_options=None,
+    create_key=_descriptor._internal_create_key,
+  ),
+  _descriptor.MethodDescriptor(
+    name='RemoveHotlistItems',
+    full_name='monorail.v3.Hotlists.RemoveHotlistItems',
+    index=7,
+    containing_service=None,
+    input_type=_REMOVEHOTLISTITEMSREQUEST,
+    output_type=google_dot_protobuf_dot_empty__pb2._EMPTY,
+    serialized_options=None,
+    create_key=_descriptor._internal_create_key,
+  ),
+  _descriptor.MethodDescriptor(
+    name='RemoveHotlistEditors',
+    full_name='monorail.v3.Hotlists.RemoveHotlistEditors',
+    index=8,
+    containing_service=None,
+    input_type=_REMOVEHOTLISTEDITORSREQUEST,
+    output_type=google_dot_protobuf_dot_empty__pb2._EMPTY,
+    serialized_options=None,
+    create_key=_descriptor._internal_create_key,
+  ),
+  _descriptor.MethodDescriptor(
+    name='GatherHotlistsForUser',
+    full_name='monorail.v3.Hotlists.GatherHotlistsForUser',
+    index=9,
+    containing_service=None,
+    input_type=_GATHERHOTLISTSFORUSERREQUEST,
+    output_type=_GATHERHOTLISTSFORUSERRESPONSE,
+    serialized_options=None,
+    create_key=_descriptor._internal_create_key,
+  ),
+])
+_sym_db.RegisterServiceDescriptor(_HOTLISTS)
+
+DESCRIPTOR.services_by_name['Hotlists'] = _HOTLISTS
+
+# @@protoc_insertion_point(module_scope)
diff --git a/api/v3/api_proto/hotlists_prpc_pb2.py b/api/v3/api_proto/hotlists_prpc_pb2.py
new file mode 100644
index 0000000..bce36ac
--- /dev/null
+++ b/api/v3/api_proto/hotlists_prpc_pb2.py
@@ -0,0 +1,788 @@
+# Generated by the pRPC protocol buffer compiler plugin.  DO NOT EDIT!
+# source: api/v3/api_proto/hotlists.proto
+
+import base64
+import zlib
+
+from google.protobuf import descriptor_pb2
+
+# Includes description of the api/v3/api_proto/hotlists.proto and all of its transitive
+# dependencies. Includes source code info.
+FILE_DESCRIPTOR_SET = descriptor_pb2.FileDescriptorSet()
+FILE_DESCRIPTOR_SET.ParseFromString(zlib.decompress(base64.b64decode(
+    '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')))
+_INDEX = {
+    f.name: {
+      'descriptor': f,
+      'services': {s.name: s for s in f.service},
+    }
+    for f in FILE_DESCRIPTOR_SET.file
+}
+
+
+HotlistsServiceDescription = {
+  'file_descriptor_set': FILE_DESCRIPTOR_SET,
+  'file_descriptor': _INDEX[u'api/v3/api_proto/hotlists.proto']['descriptor'],
+  'service_descriptor': _INDEX[u'api/v3/api_proto/hotlists.proto']['services'][u'Hotlists'],
+}
diff --git a/api/v3/api_proto/issue_objects.proto b/api/v3/api_proto/issue_objects.proto
new file mode 100644
index 0000000..2c5cf69
--- /dev/null
+++ b/api/v3/api_proto/issue_objects.proto
@@ -0,0 +1,349 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd
+
+// This file defines protobufs for issues and related business
+// objects, e.g., field values, comments, and attachments.
+
+syntax = "proto3";
+
+package monorail.v3;
+
+option go_package = "api/v3/api_proto";
+
+import "google/api/field_behavior.proto";
+import "google/api/resource.proto";
+import "google/protobuf/timestamp.proto";
+
+// Represents a comment and any associated changes to an Issue.
+//
+// Comments cannot be Created or Updated through standard methods. The
+// OUTPUT_ONLY annotations here indicate fields that would never be provided
+// by the user even if these methods were made available.
+// Next available tag: 11.
+message Comment {
+
+  // The type of comment.
+  // Next available tag: 9
+  enum Type {
+    // The default comment type. Used if type is omitted.
+    UNSPECIFIED = 0;
+    // A standard comment on an issue.
+    COMMENT = 1;
+    // A comment representing a new description for the issue.
+    DESCRIPTION = 2;
+  }
+
+  // A file attached to a comment.
+  // Next available tag: 8
+  message Attachment {
+    // The name of the attached file.
+    string filename = 1;
+    // It is possible for attachments to be deleted (and undeleted) by the
+    // uploader. The name of deleted attachments are still shown, but the
+    // content is not available.
+    IssueContentState state = 2;
+    // Size of the attached file in bytes.
+    uint64 size = 3;
+    // The type of content contained in the file, using the IANA's media type
+    // https://www.iana.org/assignments/media-types/media-types.xhtml.
+    string media_type = 4;
+    // The URI used for a preview of the attachment (when relelvant).
+    string thumbnail_uri = 5;
+    // The URI used to view the content of the attachment.
+    string view_uri = 6;
+    // The URI used to download the content of the attachment.
+    string download_uri = 7;
+  }
+
+  // This message is only suitable for displaying the amendment to users.
+  // We don't currently offer structured amendments that client code can
+  // reason about, field names can be ambiguous, and we don't have
+  // old_value for most changes.
+  // Next available tag: 4
+  message Amendment {
+    // This may be the name of a built-in or custom field, or relative to
+    // an approval field name.
+    string field_name = 1;
+    // This may be a new value that overwrote the old value, e.g., "Assigned",
+    // or it may be a space-separated list of changes, e.g., "Size-L -Size-S".
+    string new_or_delta_value = 2;
+    // old_value is only used when the user changes the summary.
+    string old_value = 3;
+  }
+
+  option (google.api.resource) = {
+    type: "api.crbug.com/Comment"
+    pattern: "projects/{project}/issues/{issue}/comments/{comment}"
+  };
+
+  // Resource name of the comment.
+  string name = 1;
+  // The state of the comment.
+  IssueContentState state = 2 [(google.api.field_behavior) = OUTPUT_ONLY];
+  // The type of comment.
+  Type type = 3;
+  // The text of the comment.
+  string content = 4;
+  // Resource name of the author of the comment.
+  string commenter = 5 [
+    (google.api.resource_reference) = { type: "api.crbug.com/User" },
+    (google.api.field_behavior) = OUTPUT_ONLY
+  ];
+  // The time this comment was added to the Issue.
+  google.protobuf.Timestamp create_time = 6
+      [(google.api.field_behavior) = OUTPUT_ONLY];
+  // Optional string full text of an email that caused this comment to be added.
+  string inbound_message = 7 [(google.api.field_behavior) = OUTPUT_ONLY];
+  // The approval this comment is associated with, if applicable.
+  string approval = 8
+      [(google.api.resource_reference) = { type: "api.crbug.com/ApprovalValue" }];
+  // Any changes made to the issue in association with this comment.
+  repeated Amendment amendments = 9 [(google.api.field_behavior) = OUTPUT_ONLY];
+  // Any attachments uploaded in association with this comment.
+  repeated Attachment attachments = 10
+      [(google.api.field_behavior) = OUTPUT_ONLY];
+}
+
+
+// Many values on an issue can be set either explicitly or by a rule.
+//
+// Note: Though Derivations are used as OUTPUT_ONLY, values including them
+// will still be ingested even though the Derivation is ignored.
+//
+// Next available tag: 3
+enum Derivation {
+  // The default derivation. This value is used if the derivation is omitted.
+  DERIVATION_UNSPECIFIED = 0;
+  // The value was explicitly set on the issue.
+  EXPLICIT = 1;
+  // Value was auto-applied to the issue based on a project's rule. See
+  // monorail/doc/userguide/project-owners.md#how-to-configure-filter-rules
+  RULE = 2;
+}
+
+
+// A value of a custom field for an issue.
+// Next available tag: 5
+message FieldValue {
+  // The project-defined field associated with this value
+  string field = 1 [
+      (google.api.resource_reference) = { type: "api.crbug.com/FieldDef" }];
+  // The value associated with the field.
+  // Mapping of field types to string value:
+  // ENUM_TYPE(int) => str(value)
+  // INT_TYPE(int) => str(value)
+  // STR_TYPE(str) => value
+  // USER_TYPE(int) => the user's resource name
+  // DATE_TYPE(int) => str(int) representing time in seconds since epoch
+  // URL_TYPE(str) => value
+  string value = 2;
+  // How the value was derived.
+  Derivation derivation = 3 [(google.api.field_behavior) = OUTPUT_ONLY];
+  // Issues with phase-specific fields can have values for each phase.
+  string phase = 4;
+}
+
+// Documents and tracks a bug, task, or feature request within a Project.
+// Next available tag: 23
+message Issue {
+  option (google.api.resource) = {
+    type: "api.crbug.com/Issue"
+    pattern: "projects/{project}/issues/{issue}"
+  };
+
+  // A possibly rule-derived component for the issue.
+  // Next available tag: 3
+  message ComponentValue {
+    // The component.
+    string component = 1 [
+      (google.api.resource_reference) = { type: "api.crbug.com/ComponentDef" }
+    ];
+    // How the component was derived.
+    Derivation derivation = 2 [(google.api.field_behavior) = OUTPUT_ONLY];
+  }
+
+  // A possibly rule-derived label for an issue.
+  // Next available tag: 3
+  message LabelValue {
+    // The label.
+    string label = 1;
+    // How the label was derived.
+    Derivation derivation = 2 [(google.api.field_behavior) = OUTPUT_ONLY];
+  }
+
+  // A possibly rule-derived status for an issue.
+  // Next available tag: 3
+  message StatusValue {
+    // The status of the issue. Note that in rare cases this can be a
+    // value not defined in the project's StatusDefs (e.g. if the issue
+    // was moved from another project).
+    string status = 1;
+    // How the status was derived.
+    Derivation derivation = 2 [(google.api.field_behavior) = OUTPUT_ONLY];
+  }
+
+  // A possibly rule-derived user value on an issue.
+  // Next available tag: 3
+  message UserValue {
+    // The user.
+    string user = 1
+        [(google.api.resource_reference) = { type: "api.crbug.com/User" }];
+    // How the user value was derived.
+    Derivation derivation = 2 [(google.api.field_behavior) = OUTPUT_ONLY];
+  }
+
+  // Resource name of the issue.
+  string name = 1;
+  // A brief summary of the issue. Generally displayed as a user-facing title.
+  // TODO(monorail:6988): The UI limits summary length while the backend does
+  // not. Resolve this discrepancy.
+  string summary = 2;
+  // The state of the issue.
+  IssueContentState state = 3 [(google.api.field_behavior) = OUTPUT_ONLY];
+  // The current status of the issue.
+  StatusValue status = 4 [(google.api.field_behavior) = REQUIRED];
+  // The user who created the issue.
+  string reporter = 5 [
+    (google.api.resource_reference) = { type: "api.crbug.com/User" },
+    (google.api.field_behavior) = OUTPUT_ONLY
+  ];
+  // The user currently responsible for the issue. This user must be a member of
+  // the Project.
+  UserValue owner = 6;
+  // Additional users receiving notifications on the issue.
+  repeated UserValue cc_users = 7;
+  // Labels applied to the issue.
+  repeated LabelValue labels = 8;
+  // Components the issue is associated with.
+  repeated ComponentValue components = 9;
+  // Values for custom fields on the issue.
+  repeated FieldValue field_values = 10;
+  // An issue can be merged into another. If this value is set, the issue
+  // referred to should be considered the primary source for further updates.
+  IssueRef merged_into_issue_ref = 11;
+  // Issues preventing the completion of this issue.
+  repeated IssueRef blocked_on_issue_refs = 12;
+  // Issues for which this issue is blocking completion.
+  repeated IssueRef blocking_issue_refs = 13;
+  // The time the issue was reported.
+  google.protobuf.Timestamp create_time = 14
+      [(google.api.field_behavior) = OUTPUT_ONLY];
+  // The most recent time the issue was closed.
+  google.protobuf.Timestamp close_time = 15
+      [(google.api.field_behavior) = OUTPUT_ONLY];
+  // The most recent time the issue was modified.
+  google.protobuf.Timestamp modify_time = 16
+      [(google.api.field_behavior) = OUTPUT_ONLY];
+  // The most recent time a component value was modified.
+  google.protobuf.Timestamp component_modify_time = 17
+      [(google.api.field_behavior) = OUTPUT_ONLY];
+  // The most recent time the status value was modified.
+  google.protobuf.Timestamp status_modify_time = 18
+      [(google.api.field_behavior) = OUTPUT_ONLY];
+  // The most recent time the owner made a modification to the issue.
+  google.protobuf.Timestamp owner_modify_time = 19
+      [(google.api.field_behavior) = OUTPUT_ONLY];
+  // The number of attachments associated with the issue.
+  uint32 attachment_count = 20 [(google.api.field_behavior) = OUTPUT_ONLY];
+  // The number of users who have starred the issue.
+  uint32 star_count = 21 [(google.api.field_behavior) = OUTPUT_ONLY];
+  // Phases of a process the issue is tracking (if applicable).
+  // See monorail/doc/userguide/concepts.md#issue-approvals-and-gates
+  repeated string phases = 22 [
+      (google.api.field_behavior) = OUTPUT_ONLY];
+}
+
+// States that an issue or its comments can be in (aip.dev/216).
+// Next available tag: 4
+enum IssueContentState {
+  // The default value. This value is used if the state is omitted.
+  STATE_UNSPECIFIED = 0;
+  // The Issue or Comment is available.
+  ACTIVE = 1;
+  // The Issue or Comment has been deleted.
+  DELETED = 2;
+  // The Issue or Comment has been flagged as spam.
+  // Takes precedent over DELETED.
+  SPAM = 3;
+}
+
+// Specifies a column in an issues list view.
+// Next available tag: 2
+message IssuesListColumn {
+  // Column name shown in the column header.
+  string column = 1;
+}
+
+// Refers to an issue that may or may not be tracked in Monorail.
+// At least one of `issue` and `ext_identifier` MUST be set; they MUST NOT both
+// be set.
+// Next available tag: 3
+message IssueRef {
+  // Resource name of an issue tracked in Monorail
+  string issue = 1
+      [(google.api.resource_reference) = { type: "api.crbug.com/Issue" }];
+  // For referencing external issues, e.g. b/1234, or a dangling reference
+  // to an old 'codesite' issue.
+  // TODO(monorail:7208): add more documentation on dangling references.
+  string ext_identifier = 2;
+}
+
+// Documents and tracks an approval process.
+// See monorail/doc/userguide/concepts.md#issue-approvals-and-gates
+// Next available tag: 9
+message ApprovalValue {
+  option (google.api.resource) = {
+     type: "api.crbug.com/ApprovalValue"
+     pattern: "projects/{project}/issues/{issue}/approvalValues/{approval}"
+   };
+
+  // Potential states for an approval. Note that these statuses cause different
+  // sets of notifications. See monorail/doc/userguide/email.md
+  // Next available tag: 9
+  enum ApprovalStatus {
+    // The default approval status. This value is used if the status is omitted.
+    APPROVAL_STATUS_UNSPECIFIED = 0;
+    // No status has yet been set on this value.
+    NOT_SET = 1;
+    // This issue needs review from the approvers for this phase.
+    NEEDS_REVIEW = 2;
+    // This approval is not needed for this issue for this phase.
+    NA = 3;
+    // The issue is ready for the approvers to review.
+    REVIEW_REQUESTED = 4;
+    // The approvers have started reviewing this issue.
+    REVIEW_STARTED = 5;
+    // The approvers need more information.
+    NEED_INFO = 6;
+    // The approvers have approved this issue for this phase.
+    APPROVED = 7;
+    // The approvers have indicated this issue is not approved for this phase.
+    NOT_APPROVED = 8;
+  }
+
+  // The resource name.
+  string name = 1;
+  // The resource name of the ApprovalDef.
+  string approval_def = 2 [
+      (google.api.resource_reference) = { type: "api.crbug.com/ApprovalDef" },
+      (google.api.field_behavior) = OUTPUT_ONLY];
+  // The users able to grant this approval.
+  repeated string approvers = 3 [
+      (google.api.resource_reference) = { type: "api.crbug.com/User" }];
+  // The current status of the approval.
+  ApprovalStatus status = 4;
+  // The time `status` was last set.
+  google.protobuf.Timestamp set_time = 5 [
+      (google.api.field_behavior) = OUTPUT_ONLY];
+  // The user who most recently set `status`.
+  string setter = 6 [
+      (google.api.resource_reference) = { type: "api.crbug.com/User" },
+      (google.api.field_behavior) = OUTPUT_ONLY];
+  // The phase the approval is associated with (if applicable).
+  string phase = 7 [
+      (google.api.field_behavior) = OUTPUT_ONLY];
+  // FieldValues with `approval_def` as their parent.
+  repeated FieldValue field_values = 8;
+}
\ No newline at end of file
diff --git a/api/v3/api_proto/issue_objects_pb2.py b/api/v3/api_proto/issue_objects_pb2.py
new file mode 100644
index 0000000..1644b3c
--- /dev/null
+++ b/api/v3/api_proto/issue_objects_pb2.py
@@ -0,0 +1,1123 @@
+# -*- coding: utf-8 -*-
+# Generated by the protocol buffer compiler.  DO NOT EDIT!
+# source: api/v3/api_proto/issue_objects.proto
+
+from google.protobuf.internal import enum_type_wrapper
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import message as _message
+from google.protobuf import reflection as _reflection
+from google.protobuf import symbol_database as _symbol_database
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+from google.api import field_behavior_pb2 as google_dot_api_dot_field__behavior__pb2
+from google.api import resource_pb2 as google_dot_api_dot_resource__pb2
+from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__pb2
+
+
+DESCRIPTOR = _descriptor.FileDescriptor(
+  name='api/v3/api_proto/issue_objects.proto',
+  package='monorail.v3',
+  syntax='proto3',
+  serialized_options=b'Z\020api/v3/api_proto',
+  create_key=_descriptor._internal_create_key,
+  serialized_pb=b'\n$api/v3/api_proto/issue_objects.proto\x12\x0bmonorail.v3\x1a\x1fgoogle/api/field_behavior.proto\x1a\x19google/api/resource.proto\x1a\x1fgoogle/protobuf/timestamp.proto\"\xba\x06\n\x07\x43omment\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x32\n\x05state\x18\x02 \x01(\x0e\x32\x1e.monorail.v3.IssueContentStateB\x03\xe0\x41\x03\x12\'\n\x04type\x18\x03 \x01(\x0e\x32\x19.monorail.v3.Comment.Type\x12\x0f\n\x07\x63ontent\x18\x04 \x01(\t\x12-\n\tcommenter\x18\x05 \x01(\tB\x1a\xfa\x41\x14\n\x12\x61pi.crbug.com/User\xe0\x41\x03\x12\x34\n\x0b\x63reate_time\x18\x06 \x01(\x0b\x32\x1a.google.protobuf.TimestampB\x03\xe0\x41\x03\x12\x1c\n\x0finbound_message\x18\x07 \x01(\tB\x03\xe0\x41\x03\x12\x32\n\x08\x61pproval\x18\x08 \x01(\tB \xfa\x41\x1d\n\x1b\x61pi.crbug.com/ApprovalValue\x12\x37\n\namendments\x18\t \x03(\x0b\x32\x1e.monorail.v3.Comment.AmendmentB\x03\xe0\x41\x03\x12\x39\n\x0b\x61ttachments\x18\n \x03(\x0b\x32\x1f.monorail.v3.Comment.AttachmentB\x03\xe0\x41\x03\x1a\xae\x01\n\nAttachment\x12\x10\n\x08\x66ilename\x18\x01 \x01(\t\x12-\n\x05state\x18\x02 \x01(\x0e\x32\x1e.monorail.v3.IssueContentState\x12\x0c\n\x04size\x18\x03 \x01(\x04\x12\x12\n\nmedia_type\x18\x04 \x01(\t\x12\x15\n\rthumbnail_uri\x18\x05 \x01(\t\x12\x10\n\x08view_uri\x18\x06 \x01(\t\x12\x14\n\x0c\x64ownload_uri\x18\x07 \x01(\t\x1aN\n\tAmendment\x12\x12\n\nfield_name\x18\x01 \x01(\t\x12\x1a\n\x12new_or_delta_value\x18\x02 \x01(\t\x12\x11\n\told_value\x18\x03 \x01(\t\"5\n\x04Type\x12\x0f\n\x0bUNSPECIFIED\x10\x00\x12\x0b\n\x07\x43OMMENT\x10\x01\x12\x0f\n\x0b\x44\x45SCRIPTION\x10\x02:P\xea\x41M\n\x15\x61pi.crbug.com/Comment\x12\x34projects/{project}/issues/{issue}/comments/{comment}\"\x88\x01\n\nFieldValue\x12*\n\x05\x66ield\x18\x01 \x01(\tB\x1b\xfa\x41\x18\n\x16\x61pi.crbug.com/FieldDef\x12\r\n\x05value\x18\x02 \x01(\t\x12\x30\n\nderivation\x18\x03 \x01(\x0e\x32\x17.monorail.v3.DerivationB\x03\xe0\x41\x03\x12\r\n\x05phase\x18\x04 \x01(\t\"\xb1\x0b\n\x05Issue\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x0f\n\x07summary\x18\x02 \x01(\t\x12\x32\n\x05state\x18\x03 \x01(\x0e\x32\x1e.monorail.v3.IssueContentStateB\x03\xe0\x41\x03\x12\x33\n\x06status\x18\x04 \x01(\x0b\x32\x1e.monorail.v3.Issue.StatusValueB\x03\xe0\x41\x02\x12,\n\x08reporter\x18\x05 \x01(\tB\x1a\xfa\x41\x14\n\x12\x61pi.crbug.com/User\xe0\x41\x03\x12+\n\x05owner\x18\x06 \x01(\x0b\x32\x1c.monorail.v3.Issue.UserValue\x12.\n\x08\x63\x63_users\x18\x07 \x03(\x0b\x32\x1c.monorail.v3.Issue.UserValue\x12-\n\x06labels\x18\x08 \x03(\x0b\x32\x1d.monorail.v3.Issue.LabelValue\x12\x35\n\ncomponents\x18\t \x03(\x0b\x32!.monorail.v3.Issue.ComponentValue\x12-\n\x0c\x66ield_values\x18\n \x03(\x0b\x32\x17.monorail.v3.FieldValue\x12\x34\n\x15merged_into_issue_ref\x18\x0b \x01(\x0b\x32\x15.monorail.v3.IssueRef\x12\x34\n\x15\x62locked_on_issue_refs\x18\x0c \x03(\x0b\x32\x15.monorail.v3.IssueRef\x12\x32\n\x13\x62locking_issue_refs\x18\r \x03(\x0b\x32\x15.monorail.v3.IssueRef\x12\x34\n\x0b\x63reate_time\x18\x0e \x01(\x0b\x32\x1a.google.protobuf.TimestampB\x03\xe0\x41\x03\x12\x33\n\nclose_time\x18\x0f \x01(\x0b\x32\x1a.google.protobuf.TimestampB\x03\xe0\x41\x03\x12\x34\n\x0bmodify_time\x18\x10 \x01(\x0b\x32\x1a.google.protobuf.TimestampB\x03\xe0\x41\x03\x12>\n\x15\x63omponent_modify_time\x18\x11 \x01(\x0b\x32\x1a.google.protobuf.TimestampB\x03\xe0\x41\x03\x12;\n\x12status_modify_time\x18\x12 \x01(\x0b\x32\x1a.google.protobuf.TimestampB\x03\xe0\x41\x03\x12:\n\x11owner_modify_time\x18\x13 \x01(\x0b\x32\x1a.google.protobuf.TimestampB\x03\xe0\x41\x03\x12\x1d\n\x10\x61ttachment_count\x18\x14 \x01(\rB\x03\xe0\x41\x03\x12\x17\n\nstar_count\x18\x15 \x01(\rB\x03\xe0\x41\x03\x12\x13\n\x06phases\x18\x16 \x03(\tB\x03\xe0\x41\x03\x1av\n\x0e\x43omponentValue\x12\x32\n\tcomponent\x18\x01 \x01(\tB\x1f\xfa\x41\x1c\n\x1a\x61pi.crbug.com/ComponentDef\x12\x30\n\nderivation\x18\x02 \x01(\x0e\x32\x17.monorail.v3.DerivationB\x03\xe0\x41\x03\x1aM\n\nLabelValue\x12\r\n\x05label\x18\x01 \x01(\t\x12\x30\n\nderivation\x18\x02 \x01(\x0e\x32\x17.monorail.v3.DerivationB\x03\xe0\x41\x03\x1aO\n\x0bStatusValue\x12\x0e\n\x06status\x18\x01 \x01(\t\x12\x30\n\nderivation\x18\x02 \x01(\x0e\x32\x17.monorail.v3.DerivationB\x03\xe0\x41\x03\x1a\x64\n\tUserValue\x12%\n\x04user\x18\x01 \x01(\tB\x17\xfa\x41\x14\n\x12\x61pi.crbug.com/User\x12\x30\n\nderivation\x18\x02 \x01(\x0e\x32\x17.monorail.v3.DerivationB\x03\xe0\x41\x03:;\xea\x41\x38\n\x13\x61pi.crbug.com/Issue\x12!projects/{project}/issues/{issue}\"\"\n\x10IssuesListColumn\x12\x0e\n\x06\x63olumn\x18\x01 \x01(\t\"K\n\x08IssueRef\x12\'\n\x05issue\x18\x01 \x01(\tB\x18\xfa\x41\x15\n\x13\x61pi.crbug.com/Issue\x12\x16\n\x0e\x65xt_identifier\x18\x02 \x01(\t\"\xf2\x04\n\rApprovalValue\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x37\n\x0c\x61pproval_def\x18\x02 \x01(\tB!\xfa\x41\x1b\n\x19\x61pi.crbug.com/ApprovalDef\xe0\x41\x03\x12*\n\tapprovers\x18\x03 \x03(\tB\x17\xfa\x41\x14\n\x12\x61pi.crbug.com/User\x12\x39\n\x06status\x18\x04 \x01(\x0e\x32).monorail.v3.ApprovalValue.ApprovalStatus\x12\x31\n\x08set_time\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.TimestampB\x03\xe0\x41\x03\x12*\n\x06setter\x18\x06 \x01(\tB\x1a\xfa\x41\x14\n\x12\x61pi.crbug.com/User\xe0\x41\x03\x12\x12\n\x05phase\x18\x07 \x01(\tB\x03\xe0\x41\x03\x12-\n\x0c\x66ield_values\x18\x08 \x03(\x0b\x32\x17.monorail.v3.FieldValue\"\xb1\x01\n\x0e\x41pprovalStatus\x12\x1f\n\x1b\x41PPROVAL_STATUS_UNSPECIFIED\x10\x00\x12\x0b\n\x07NOT_SET\x10\x01\x12\x10\n\x0cNEEDS_REVIEW\x10\x02\x12\x06\n\x02NA\x10\x03\x12\x14\n\x10REVIEW_REQUESTED\x10\x04\x12\x12\n\x0eREVIEW_STARTED\x10\x05\x12\r\n\tNEED_INFO\x10\x06\x12\x0c\n\x08\x41PPROVED\x10\x07\x12\x10\n\x0cNOT_APPROVED\x10\x08:]\xea\x41Z\n\x1b\x61pi.crbug.com/ApprovalValue\x12;projects/{project}/issues/{issue}/approvalValues/{approval}*@\n\nDerivation\x12\x1a\n\x16\x44\x45RIVATION_UNSPECIFIED\x10\x00\x12\x0c\n\x08\x45XPLICIT\x10\x01\x12\x08\n\x04RULE\x10\x02*M\n\x11IssueContentState\x12\x15\n\x11STATE_UNSPECIFIED\x10\x00\x12\n\n\x06\x41\x43TIVE\x10\x01\x12\x0b\n\x07\x44\x45LETED\x10\x02\x12\x08\n\x04SPAM\x10\x03\x42\x12Z\x10\x61pi/v3/api_protob\x06proto3'
+  ,
+  dependencies=[google_dot_api_dot_field__behavior__pb2.DESCRIPTOR,google_dot_api_dot_resource__pb2.DESCRIPTOR,google_dot_protobuf_dot_timestamp__pb2.DESCRIPTOR,])
+
+_DERIVATION = _descriptor.EnumDescriptor(
+  name='Derivation',
+  full_name='monorail.v3.Derivation',
+  filename=None,
+  file=DESCRIPTOR,
+  create_key=_descriptor._internal_create_key,
+  values=[
+    _descriptor.EnumValueDescriptor(
+      name='DERIVATION_UNSPECIFIED', index=0, number=0,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='EXPLICIT', index=1, number=1,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='RULE', index=2, number=2,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+  ],
+  containing_type=None,
+  serialized_options=None,
+  serialized_start=3316,
+  serialized_end=3380,
+)
+_sym_db.RegisterEnumDescriptor(_DERIVATION)
+
+Derivation = enum_type_wrapper.EnumTypeWrapper(_DERIVATION)
+_ISSUECONTENTSTATE = _descriptor.EnumDescriptor(
+  name='IssueContentState',
+  full_name='monorail.v3.IssueContentState',
+  filename=None,
+  file=DESCRIPTOR,
+  create_key=_descriptor._internal_create_key,
+  values=[
+    _descriptor.EnumValueDescriptor(
+      name='STATE_UNSPECIFIED', index=0, number=0,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='ACTIVE', index=1, number=1,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='DELETED', index=2, number=2,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='SPAM', index=3, number=3,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+  ],
+  containing_type=None,
+  serialized_options=None,
+  serialized_start=3382,
+  serialized_end=3459,
+)
+_sym_db.RegisterEnumDescriptor(_ISSUECONTENTSTATE)
+
+IssueContentState = enum_type_wrapper.EnumTypeWrapper(_ISSUECONTENTSTATE)
+DERIVATION_UNSPECIFIED = 0
+EXPLICIT = 1
+RULE = 2
+STATE_UNSPECIFIED = 0
+ACTIVE = 1
+DELETED = 2
+SPAM = 3
+
+
+_COMMENT_TYPE = _descriptor.EnumDescriptor(
+  name='Type',
+  full_name='monorail.v3.Comment.Type',
+  filename=None,
+  file=DESCRIPTOR,
+  create_key=_descriptor._internal_create_key,
+  values=[
+    _descriptor.EnumValueDescriptor(
+      name='UNSPECIFIED', index=0, number=0,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='COMMENT', index=1, number=1,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='DESCRIPTION', index=2, number=2,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+  ],
+  containing_type=None,
+  serialized_options=None,
+  serialized_start=838,
+  serialized_end=891,
+)
+_sym_db.RegisterEnumDescriptor(_COMMENT_TYPE)
+
+_APPROVALVALUE_APPROVALSTATUS = _descriptor.EnumDescriptor(
+  name='ApprovalStatus',
+  full_name='monorail.v3.ApprovalValue.ApprovalStatus',
+  filename=None,
+  file=DESCRIPTOR,
+  create_key=_descriptor._internal_create_key,
+  values=[
+    _descriptor.EnumValueDescriptor(
+      name='APPROVAL_STATUS_UNSPECIFIED', index=0, number=0,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='NOT_SET', index=1, number=1,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='NEEDS_REVIEW', index=2, number=2,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='NA', index=3, number=3,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='REVIEW_REQUESTED', index=4, number=4,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='REVIEW_STARTED', index=5, number=5,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='NEED_INFO', index=6, number=6,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='APPROVED', index=7, number=7,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='NOT_APPROVED', index=8, number=8,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+  ],
+  containing_type=None,
+  serialized_options=None,
+  serialized_start=3042,
+  serialized_end=3219,
+)
+_sym_db.RegisterEnumDescriptor(_APPROVALVALUE_APPROVALSTATUS)
+
+
+_COMMENT_ATTACHMENT = _descriptor.Descriptor(
+  name='Attachment',
+  full_name='monorail.v3.Comment.Attachment',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='filename', full_name='monorail.v3.Comment.Attachment.filename', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='state', full_name='monorail.v3.Comment.Attachment.state', index=1,
+      number=2, type=14, cpp_type=8, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='size', full_name='monorail.v3.Comment.Attachment.size', index=2,
+      number=3, type=4, cpp_type=4, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='media_type', full_name='monorail.v3.Comment.Attachment.media_type', index=3,
+      number=4, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='thumbnail_uri', full_name='monorail.v3.Comment.Attachment.thumbnail_uri', index=4,
+      number=5, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='view_uri', full_name='monorail.v3.Comment.Attachment.view_uri', index=5,
+      number=6, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='download_uri', full_name='monorail.v3.Comment.Attachment.download_uri', index=6,
+      number=7, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=582,
+  serialized_end=756,
+)
+
+_COMMENT_AMENDMENT = _descriptor.Descriptor(
+  name='Amendment',
+  full_name='monorail.v3.Comment.Amendment',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='field_name', full_name='monorail.v3.Comment.Amendment.field_name', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='new_or_delta_value', full_name='monorail.v3.Comment.Amendment.new_or_delta_value', index=1,
+      number=2, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='old_value', full_name='monorail.v3.Comment.Amendment.old_value', index=2,
+      number=3, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=758,
+  serialized_end=836,
+)
+
+_COMMENT = _descriptor.Descriptor(
+  name='Comment',
+  full_name='monorail.v3.Comment',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='name', full_name='monorail.v3.Comment.name', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='state', full_name='monorail.v3.Comment.state', index=1,
+      number=2, type=14, cpp_type=8, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\340A\003', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='type', full_name='monorail.v3.Comment.type', index=2,
+      number=3, type=14, cpp_type=8, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='content', full_name='monorail.v3.Comment.content', index=3,
+      number=4, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='commenter', full_name='monorail.v3.Comment.commenter', index=4,
+      number=5, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\372A\024\n\022api.crbug.com/User\340A\003', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='create_time', full_name='monorail.v3.Comment.create_time', index=5,
+      number=6, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\340A\003', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='inbound_message', full_name='monorail.v3.Comment.inbound_message', index=6,
+      number=7, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\340A\003', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='approval', full_name='monorail.v3.Comment.approval', index=7,
+      number=8, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\372A\035\n\033api.crbug.com/ApprovalValue', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='amendments', full_name='monorail.v3.Comment.amendments', index=8,
+      number=9, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\340A\003', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='attachments', full_name='monorail.v3.Comment.attachments', index=9,
+      number=10, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\340A\003', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[_COMMENT_ATTACHMENT, _COMMENT_AMENDMENT, ],
+  enum_types=[
+    _COMMENT_TYPE,
+  ],
+  serialized_options=b'\352AM\n\025api.crbug.com/Comment\0224projects/{project}/issues/{issue}/comments/{comment}',
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=147,
+  serialized_end=973,
+)
+
+
+_FIELDVALUE = _descriptor.Descriptor(
+  name='FieldValue',
+  full_name='monorail.v3.FieldValue',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='field', full_name='monorail.v3.FieldValue.field', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\372A\030\n\026api.crbug.com/FieldDef', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='value', full_name='monorail.v3.FieldValue.value', index=1,
+      number=2, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='derivation', full_name='monorail.v3.FieldValue.derivation', index=2,
+      number=3, type=14, cpp_type=8, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\340A\003', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='phase', full_name='monorail.v3.FieldValue.phase', index=3,
+      number=4, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=976,
+  serialized_end=1112,
+)
+
+
+_ISSUE_COMPONENTVALUE = _descriptor.Descriptor(
+  name='ComponentValue',
+  full_name='monorail.v3.Issue.ComponentValue',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='component', full_name='monorail.v3.Issue.ComponentValue.component', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\372A\034\n\032api.crbug.com/ComponentDef', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='derivation', full_name='monorail.v3.Issue.ComponentValue.derivation', index=1,
+      number=2, type=14, cpp_type=8, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\340A\003', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=2131,
+  serialized_end=2249,
+)
+
+_ISSUE_LABELVALUE = _descriptor.Descriptor(
+  name='LabelValue',
+  full_name='monorail.v3.Issue.LabelValue',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='label', full_name='monorail.v3.Issue.LabelValue.label', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='derivation', full_name='monorail.v3.Issue.LabelValue.derivation', index=1,
+      number=2, type=14, cpp_type=8, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\340A\003', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=2251,
+  serialized_end=2328,
+)
+
+_ISSUE_STATUSVALUE = _descriptor.Descriptor(
+  name='StatusValue',
+  full_name='monorail.v3.Issue.StatusValue',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='status', full_name='monorail.v3.Issue.StatusValue.status', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='derivation', full_name='monorail.v3.Issue.StatusValue.derivation', index=1,
+      number=2, type=14, cpp_type=8, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\340A\003', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=2330,
+  serialized_end=2409,
+)
+
+_ISSUE_USERVALUE = _descriptor.Descriptor(
+  name='UserValue',
+  full_name='monorail.v3.Issue.UserValue',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='user', full_name='monorail.v3.Issue.UserValue.user', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\372A\024\n\022api.crbug.com/User', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='derivation', full_name='monorail.v3.Issue.UserValue.derivation', index=1,
+      number=2, type=14, cpp_type=8, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\340A\003', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=2411,
+  serialized_end=2511,
+)
+
+_ISSUE = _descriptor.Descriptor(
+  name='Issue',
+  full_name='monorail.v3.Issue',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='name', full_name='monorail.v3.Issue.name', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='summary', full_name='monorail.v3.Issue.summary', index=1,
+      number=2, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='state', full_name='monorail.v3.Issue.state', index=2,
+      number=3, type=14, cpp_type=8, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\340A\003', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='status', full_name='monorail.v3.Issue.status', index=3,
+      number=4, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\340A\002', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='reporter', full_name='monorail.v3.Issue.reporter', index=4,
+      number=5, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\372A\024\n\022api.crbug.com/User\340A\003', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='owner', full_name='monorail.v3.Issue.owner', index=5,
+      number=6, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='cc_users', full_name='monorail.v3.Issue.cc_users', index=6,
+      number=7, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='labels', full_name='monorail.v3.Issue.labels', index=7,
+      number=8, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='components', full_name='monorail.v3.Issue.components', index=8,
+      number=9, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='field_values', full_name='monorail.v3.Issue.field_values', index=9,
+      number=10, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='merged_into_issue_ref', full_name='monorail.v3.Issue.merged_into_issue_ref', index=10,
+      number=11, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='blocked_on_issue_refs', full_name='monorail.v3.Issue.blocked_on_issue_refs', index=11,
+      number=12, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='blocking_issue_refs', full_name='monorail.v3.Issue.blocking_issue_refs', index=12,
+      number=13, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='create_time', full_name='monorail.v3.Issue.create_time', index=13,
+      number=14, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\340A\003', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='close_time', full_name='monorail.v3.Issue.close_time', index=14,
+      number=15, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\340A\003', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='modify_time', full_name='monorail.v3.Issue.modify_time', index=15,
+      number=16, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\340A\003', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='component_modify_time', full_name='monorail.v3.Issue.component_modify_time', index=16,
+      number=17, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\340A\003', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='status_modify_time', full_name='monorail.v3.Issue.status_modify_time', index=17,
+      number=18, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\340A\003', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='owner_modify_time', full_name='monorail.v3.Issue.owner_modify_time', index=18,
+      number=19, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\340A\003', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='attachment_count', full_name='monorail.v3.Issue.attachment_count', index=19,
+      number=20, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\340A\003', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='star_count', full_name='monorail.v3.Issue.star_count', index=20,
+      number=21, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\340A\003', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='phases', full_name='monorail.v3.Issue.phases', index=21,
+      number=22, type=9, cpp_type=9, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\340A\003', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[_ISSUE_COMPONENTVALUE, _ISSUE_LABELVALUE, _ISSUE_STATUSVALUE, _ISSUE_USERVALUE, ],
+  enum_types=[
+  ],
+  serialized_options=b'\352A8\n\023api.crbug.com/Issue\022!projects/{project}/issues/{issue}',
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=1115,
+  serialized_end=2572,
+)
+
+
+_ISSUESLISTCOLUMN = _descriptor.Descriptor(
+  name='IssuesListColumn',
+  full_name='monorail.v3.IssuesListColumn',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='column', full_name='monorail.v3.IssuesListColumn.column', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=2574,
+  serialized_end=2608,
+)
+
+
+_ISSUEREF = _descriptor.Descriptor(
+  name='IssueRef',
+  full_name='monorail.v3.IssueRef',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='issue', full_name='monorail.v3.IssueRef.issue', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\372A\025\n\023api.crbug.com/Issue', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='ext_identifier', full_name='monorail.v3.IssueRef.ext_identifier', index=1,
+      number=2, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=2610,
+  serialized_end=2685,
+)
+
+
+_APPROVALVALUE = _descriptor.Descriptor(
+  name='ApprovalValue',
+  full_name='monorail.v3.ApprovalValue',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='name', full_name='monorail.v3.ApprovalValue.name', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='approval_def', full_name='monorail.v3.ApprovalValue.approval_def', index=1,
+      number=2, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\372A\033\n\031api.crbug.com/ApprovalDef\340A\003', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='approvers', full_name='monorail.v3.ApprovalValue.approvers', index=2,
+      number=3, type=9, cpp_type=9, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\372A\024\n\022api.crbug.com/User', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='status', full_name='monorail.v3.ApprovalValue.status', index=3,
+      number=4, type=14, cpp_type=8, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='set_time', full_name='monorail.v3.ApprovalValue.set_time', index=4,
+      number=5, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\340A\003', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='setter', full_name='monorail.v3.ApprovalValue.setter', index=5,
+      number=6, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\372A\024\n\022api.crbug.com/User\340A\003', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='phase', full_name='monorail.v3.ApprovalValue.phase', index=6,
+      number=7, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\340A\003', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='field_values', full_name='monorail.v3.ApprovalValue.field_values', index=7,
+      number=8, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+    _APPROVALVALUE_APPROVALSTATUS,
+  ],
+  serialized_options=b'\352AZ\n\033api.crbug.com/ApprovalValue\022;projects/{project}/issues/{issue}/approvalValues/{approval}',
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=2688,
+  serialized_end=3314,
+)
+
+_COMMENT_ATTACHMENT.fields_by_name['state'].enum_type = _ISSUECONTENTSTATE
+_COMMENT_ATTACHMENT.containing_type = _COMMENT
+_COMMENT_AMENDMENT.containing_type = _COMMENT
+_COMMENT.fields_by_name['state'].enum_type = _ISSUECONTENTSTATE
+_COMMENT.fields_by_name['type'].enum_type = _COMMENT_TYPE
+_COMMENT.fields_by_name['create_time'].message_type = google_dot_protobuf_dot_timestamp__pb2._TIMESTAMP
+_COMMENT.fields_by_name['amendments'].message_type = _COMMENT_AMENDMENT
+_COMMENT.fields_by_name['attachments'].message_type = _COMMENT_ATTACHMENT
+_COMMENT_TYPE.containing_type = _COMMENT
+_FIELDVALUE.fields_by_name['derivation'].enum_type = _DERIVATION
+_ISSUE_COMPONENTVALUE.fields_by_name['derivation'].enum_type = _DERIVATION
+_ISSUE_COMPONENTVALUE.containing_type = _ISSUE
+_ISSUE_LABELVALUE.fields_by_name['derivation'].enum_type = _DERIVATION
+_ISSUE_LABELVALUE.containing_type = _ISSUE
+_ISSUE_STATUSVALUE.fields_by_name['derivation'].enum_type = _DERIVATION
+_ISSUE_STATUSVALUE.containing_type = _ISSUE
+_ISSUE_USERVALUE.fields_by_name['derivation'].enum_type = _DERIVATION
+_ISSUE_USERVALUE.containing_type = _ISSUE
+_ISSUE.fields_by_name['state'].enum_type = _ISSUECONTENTSTATE
+_ISSUE.fields_by_name['status'].message_type = _ISSUE_STATUSVALUE
+_ISSUE.fields_by_name['owner'].message_type = _ISSUE_USERVALUE
+_ISSUE.fields_by_name['cc_users'].message_type = _ISSUE_USERVALUE
+_ISSUE.fields_by_name['labels'].message_type = _ISSUE_LABELVALUE
+_ISSUE.fields_by_name['components'].message_type = _ISSUE_COMPONENTVALUE
+_ISSUE.fields_by_name['field_values'].message_type = _FIELDVALUE
+_ISSUE.fields_by_name['merged_into_issue_ref'].message_type = _ISSUEREF
+_ISSUE.fields_by_name['blocked_on_issue_refs'].message_type = _ISSUEREF
+_ISSUE.fields_by_name['blocking_issue_refs'].message_type = _ISSUEREF
+_ISSUE.fields_by_name['create_time'].message_type = google_dot_protobuf_dot_timestamp__pb2._TIMESTAMP
+_ISSUE.fields_by_name['close_time'].message_type = google_dot_protobuf_dot_timestamp__pb2._TIMESTAMP
+_ISSUE.fields_by_name['modify_time'].message_type = google_dot_protobuf_dot_timestamp__pb2._TIMESTAMP
+_ISSUE.fields_by_name['component_modify_time'].message_type = google_dot_protobuf_dot_timestamp__pb2._TIMESTAMP
+_ISSUE.fields_by_name['status_modify_time'].message_type = google_dot_protobuf_dot_timestamp__pb2._TIMESTAMP
+_ISSUE.fields_by_name['owner_modify_time'].message_type = google_dot_protobuf_dot_timestamp__pb2._TIMESTAMP
+_APPROVALVALUE.fields_by_name['status'].enum_type = _APPROVALVALUE_APPROVALSTATUS
+_APPROVALVALUE.fields_by_name['set_time'].message_type = google_dot_protobuf_dot_timestamp__pb2._TIMESTAMP
+_APPROVALVALUE.fields_by_name['field_values'].message_type = _FIELDVALUE
+_APPROVALVALUE_APPROVALSTATUS.containing_type = _APPROVALVALUE
+DESCRIPTOR.message_types_by_name['Comment'] = _COMMENT
+DESCRIPTOR.message_types_by_name['FieldValue'] = _FIELDVALUE
+DESCRIPTOR.message_types_by_name['Issue'] = _ISSUE
+DESCRIPTOR.message_types_by_name['IssuesListColumn'] = _ISSUESLISTCOLUMN
+DESCRIPTOR.message_types_by_name['IssueRef'] = _ISSUEREF
+DESCRIPTOR.message_types_by_name['ApprovalValue'] = _APPROVALVALUE
+DESCRIPTOR.enum_types_by_name['Derivation'] = _DERIVATION
+DESCRIPTOR.enum_types_by_name['IssueContentState'] = _ISSUECONTENTSTATE
+_sym_db.RegisterFileDescriptor(DESCRIPTOR)
+
+Comment = _reflection.GeneratedProtocolMessageType('Comment', (_message.Message,), {
+
+  'Attachment' : _reflection.GeneratedProtocolMessageType('Attachment', (_message.Message,), {
+    'DESCRIPTOR' : _COMMENT_ATTACHMENT,
+    '__module__' : 'api.v3.api_proto.issue_objects_pb2'
+    # @@protoc_insertion_point(class_scope:monorail.v3.Comment.Attachment)
+    })
+  ,
+
+  'Amendment' : _reflection.GeneratedProtocolMessageType('Amendment', (_message.Message,), {
+    'DESCRIPTOR' : _COMMENT_AMENDMENT,
+    '__module__' : 'api.v3.api_proto.issue_objects_pb2'
+    # @@protoc_insertion_point(class_scope:monorail.v3.Comment.Amendment)
+    })
+  ,
+  'DESCRIPTOR' : _COMMENT,
+  '__module__' : 'api.v3.api_proto.issue_objects_pb2'
+  # @@protoc_insertion_point(class_scope:monorail.v3.Comment)
+  })
+_sym_db.RegisterMessage(Comment)
+_sym_db.RegisterMessage(Comment.Attachment)
+_sym_db.RegisterMessage(Comment.Amendment)
+
+FieldValue = _reflection.GeneratedProtocolMessageType('FieldValue', (_message.Message,), {
+  'DESCRIPTOR' : _FIELDVALUE,
+  '__module__' : 'api.v3.api_proto.issue_objects_pb2'
+  # @@protoc_insertion_point(class_scope:monorail.v3.FieldValue)
+  })
+_sym_db.RegisterMessage(FieldValue)
+
+Issue = _reflection.GeneratedProtocolMessageType('Issue', (_message.Message,), {
+
+  'ComponentValue' : _reflection.GeneratedProtocolMessageType('ComponentValue', (_message.Message,), {
+    'DESCRIPTOR' : _ISSUE_COMPONENTVALUE,
+    '__module__' : 'api.v3.api_proto.issue_objects_pb2'
+    # @@protoc_insertion_point(class_scope:monorail.v3.Issue.ComponentValue)
+    })
+  ,
+
+  'LabelValue' : _reflection.GeneratedProtocolMessageType('LabelValue', (_message.Message,), {
+    'DESCRIPTOR' : _ISSUE_LABELVALUE,
+    '__module__' : 'api.v3.api_proto.issue_objects_pb2'
+    # @@protoc_insertion_point(class_scope:monorail.v3.Issue.LabelValue)
+    })
+  ,
+
+  'StatusValue' : _reflection.GeneratedProtocolMessageType('StatusValue', (_message.Message,), {
+    'DESCRIPTOR' : _ISSUE_STATUSVALUE,
+    '__module__' : 'api.v3.api_proto.issue_objects_pb2'
+    # @@protoc_insertion_point(class_scope:monorail.v3.Issue.StatusValue)
+    })
+  ,
+
+  'UserValue' : _reflection.GeneratedProtocolMessageType('UserValue', (_message.Message,), {
+    'DESCRIPTOR' : _ISSUE_USERVALUE,
+    '__module__' : 'api.v3.api_proto.issue_objects_pb2'
+    # @@protoc_insertion_point(class_scope:monorail.v3.Issue.UserValue)
+    })
+  ,
+  'DESCRIPTOR' : _ISSUE,
+  '__module__' : 'api.v3.api_proto.issue_objects_pb2'
+  # @@protoc_insertion_point(class_scope:monorail.v3.Issue)
+  })
+_sym_db.RegisterMessage(Issue)
+_sym_db.RegisterMessage(Issue.ComponentValue)
+_sym_db.RegisterMessage(Issue.LabelValue)
+_sym_db.RegisterMessage(Issue.StatusValue)
+_sym_db.RegisterMessage(Issue.UserValue)
+
+IssuesListColumn = _reflection.GeneratedProtocolMessageType('IssuesListColumn', (_message.Message,), {
+  'DESCRIPTOR' : _ISSUESLISTCOLUMN,
+  '__module__' : 'api.v3.api_proto.issue_objects_pb2'
+  # @@protoc_insertion_point(class_scope:monorail.v3.IssuesListColumn)
+  })
+_sym_db.RegisterMessage(IssuesListColumn)
+
+IssueRef = _reflection.GeneratedProtocolMessageType('IssueRef', (_message.Message,), {
+  'DESCRIPTOR' : _ISSUEREF,
+  '__module__' : 'api.v3.api_proto.issue_objects_pb2'
+  # @@protoc_insertion_point(class_scope:monorail.v3.IssueRef)
+  })
+_sym_db.RegisterMessage(IssueRef)
+
+ApprovalValue = _reflection.GeneratedProtocolMessageType('ApprovalValue', (_message.Message,), {
+  'DESCRIPTOR' : _APPROVALVALUE,
+  '__module__' : 'api.v3.api_proto.issue_objects_pb2'
+  # @@protoc_insertion_point(class_scope:monorail.v3.ApprovalValue)
+  })
+_sym_db.RegisterMessage(ApprovalValue)
+
+
+DESCRIPTOR._options = None
+_COMMENT.fields_by_name['state']._options = None
+_COMMENT.fields_by_name['commenter']._options = None
+_COMMENT.fields_by_name['create_time']._options = None
+_COMMENT.fields_by_name['inbound_message']._options = None
+_COMMENT.fields_by_name['approval']._options = None
+_COMMENT.fields_by_name['amendments']._options = None
+_COMMENT.fields_by_name['attachments']._options = None
+_COMMENT._options = None
+_FIELDVALUE.fields_by_name['field']._options = None
+_FIELDVALUE.fields_by_name['derivation']._options = None
+_ISSUE_COMPONENTVALUE.fields_by_name['component']._options = None
+_ISSUE_COMPONENTVALUE.fields_by_name['derivation']._options = None
+_ISSUE_LABELVALUE.fields_by_name['derivation']._options = None
+_ISSUE_STATUSVALUE.fields_by_name['derivation']._options = None
+_ISSUE_USERVALUE.fields_by_name['user']._options = None
+_ISSUE_USERVALUE.fields_by_name['derivation']._options = None
+_ISSUE.fields_by_name['state']._options = None
+_ISSUE.fields_by_name['status']._options = None
+_ISSUE.fields_by_name['reporter']._options = None
+_ISSUE.fields_by_name['create_time']._options = None
+_ISSUE.fields_by_name['close_time']._options = None
+_ISSUE.fields_by_name['modify_time']._options = None
+_ISSUE.fields_by_name['component_modify_time']._options = None
+_ISSUE.fields_by_name['status_modify_time']._options = None
+_ISSUE.fields_by_name['owner_modify_time']._options = None
+_ISSUE.fields_by_name['attachment_count']._options = None
+_ISSUE.fields_by_name['star_count']._options = None
+_ISSUE.fields_by_name['phases']._options = None
+_ISSUE._options = None
+_ISSUEREF.fields_by_name['issue']._options = None
+_APPROVALVALUE.fields_by_name['approval_def']._options = None
+_APPROVALVALUE.fields_by_name['approvers']._options = None
+_APPROVALVALUE.fields_by_name['set_time']._options = None
+_APPROVALVALUE.fields_by_name['setter']._options = None
+_APPROVALVALUE.fields_by_name['phase']._options = None
+_APPROVALVALUE._options = None
+# @@protoc_insertion_point(module_scope)
diff --git a/api/v3/api_proto/issues.proto b/api/v3/api_proto/issues.proto
new file mode 100644
index 0000000..988f958
--- /dev/null
+++ b/api/v3/api_proto/issues.proto
@@ -0,0 +1,461 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd
+
+syntax = "proto3";
+
+package monorail.v3;
+
+option go_package = "api/v3/api_proto";
+
+import "google/protobuf/field_mask.proto";
+import "google/api/field_behavior.proto";
+import "google/api/resource.proto";
+import "api/v3/api_proto/issue_objects.proto";
+
+// ***ONLY CALL rpcs WITH `status: {ALPHA|STABLE}`***
+// rpcs without `status` are not ready.
+
+// Issues service includes all methods needed for managing Issues.
+service Issues {
+  // status: ALPHA
+  // Returns the requested Issue.
+  //
+  // Raises:
+  //   INVALID_ARGUMENT if `name` is formatted incorrectly.
+  //   NOT_FOUND if the issue does not exist.
+  //   PERMISSION_DENIED if the requester is not allowed to view the issue.
+  rpc GetIssue (GetIssueRequest) returns (Issue) {}
+
+  // status: ALPHA
+  // Returns the requested Issues.
+  //
+  // Raises:
+  //   INVALID_ARGUMENT if `names` is formatted incorrectly. Or if a parent
+  //       collection in `names` does not match the value in `parent`.
+  //   NOT_FOUND if any of the given issues do not exist.
+  //   PERMISSION_DENIED if the requester does not have permission to view one
+  //       (or more) of the given issues.
+  rpc BatchGetIssues(BatchGetIssuesRequest) returns (BatchGetIssuesResponse) {}
+
+  // status: ALPHA
+  // Searches over issues within the specified projects.
+  //
+  // Raises:
+  //   INVALID_ARGUMENT if project names or search query are invalid.
+  rpc SearchIssues (SearchIssuesRequest) returns (SearchIssuesResponse) {}
+
+  // status: ALPHA
+  // Lists comments for an issue.
+  //
+  // Raises:
+  //   INVALID_ARGUMENT if `parent` is formatted incorrectly or `page_size` < 0.
+  //   NOT_FOUND if `parent` does not exist.
+  //   PERMISSION_DENIED if the requester is not allowed to view `parent`.
+  rpc ListComments (ListCommentsRequest) returns (ListCommentsResponse) {}
+
+  // status: ALPHA
+  // Modifies Issues and creates a new Comment for each.
+  // Issues with NOOP changes and no comment_content will not be updated
+  // and will not be included in the response.
+  // We do not offer a standard UpdateIssue because every issue change
+  // must result in the side-effect of creating a new Comment, and may result in
+  // the side effect of sending a notification. We also want to allow for any
+  // combination of issue changes to be made at once in a monolithic method.
+  //
+  // Raises:
+  //   INVALID_ARGUMENT required fields are missing or fields are formatted
+  //     incorrectly.
+  //   NOT_FOUND if any specified issues are not found.
+  //   PERMISSION_DENIED if the requester is not allowed to make the
+  //     requested change.
+  rpc ModifyIssues (ModifyIssuesRequest) returns (ModifyIssuesResponse) {}
+
+  // status: ALPHA
+  // Modifies ApprovalValues and creates a new Comment for each delta.
+  // We do not offer a standard UpdateApprovalValue because changes result
+  // in creating Comments on the parent Issue, and may have the side effect of
+  // sending notifications. We also want to allow for any combination of
+  // approval changes to be made at once in a monolithic method.
+  // To modify owner add 'owner' to update_mask, though 'owner.user' works too.
+  //
+  // Raises:
+  //   INVALID_ARGUMENT required fields are missing or fields are formatted
+  //     incorrectly.
+  //   NOT_FOUND if any specified ApprovalValues are not found.
+  //   PERMISSION_DENIED if the requester is not allowed to make any of the
+  //     requested changes.
+  rpc ModifyIssueApprovalValues (ModifyIssueApprovalValuesRequest) returns
+      (ModifyIssueApprovalValuesResponse) {}
+
+  // status: ALPHA
+  // Lists approval values for an issue.
+  //
+  // Raises:
+  //   INVALID_ARGUMENT if request `parent` is formatted incorrectly.
+  //   NOT_FOUND if the parent issue does not exist.
+  //   PERMISSION_DENIED if the requester is not allowed to view parent issue.
+  rpc ListApprovalValues (ListApprovalValuesRequest) returns
+      (ListApprovalValuesResponse) {}
+
+  // status: NOT READY
+  // Changes state for a comment. Supported state transitions:
+  //   - ACTIVE -> DELETED
+  //   - ACTIVE -> SPAM
+  //   - DELETED -> ACTIVE
+  //   - SPAM -> ACTIVE
+  //
+  // Raises:
+  //   TODO(crbug/monorail/7867): Document errors when implemented
+  rpc ModifyCommentState (ModifyCommentStateRequest) returns
+      (ModifyCommentStateResponse) {}
+
+  // status: NOT READY
+  // Makes an issue from an IssueTemplate and deltas.
+  //
+  // Raises:
+  //   TODO(crbug/monorail/7197): Document errors when implemented
+  rpc MakeIssueFromTemplate (MakeIssueFromTemplateRequest) returns (Issue) {}
+
+  // status: ALPHA
+  // Makes a basic issue, does not support phases, approvals, or approval
+  // fields.
+  // We do not offer a standard CreateIssue because Issue descriptions are
+  // required, but not included in the Issue proto.
+  //
+  // Raises:
+  //   INVALID_ARGUMENT if any given names does not have a valid format, if any
+  //     fields in the requested issue were invalid, or if proposed values
+  //     violates filter rules that should error.
+  //   NOT_FOUND if no project exists with the given name.
+  //   PERMISSION_DENIED if user lacks sufficient permissions.
+  rpc MakeIssue (MakeIssueRequest) returns (Issue) {}
+}
+
+
+// The request message for Issues.GetIssue.
+// Next available tag: 2
+message GetIssueRequest {
+  // The name of the issue to request.
+  string name = 1 [
+      (google.api.resource_reference) = {type: "api.crbug.com/Issue"},
+      (google.api.field_behavior) = REQUIRED ];
+}
+
+// The request message for Issues.BatchGetIssues.
+// Next available tag: 3
+message BatchGetIssuesRequest {
+  // The project name from which to batch get issues. If included, the parent
+  // of all the issues specified in `names` must match this field.
+  string parent = 1 [
+    (google.api.resource_reference) = {type: "api.crbug.com/Project"} ];
+  // The issues to request. Maximum of 100 can be retrieved.
+  repeated string names = 2 [
+      (google.api.resource_reference) = {type: "api.crbug.com/Issue"} ];
+}
+
+// The response message for Issues.BatchGetIssues.
+// Next available tag: 2
+message BatchGetIssuesResponse {
+  // Issues matching the given request.
+  repeated Issue issues = 1;
+}
+
+// The request message for Issues.SearchIssues.
+// Next available tag: 6
+message SearchIssuesRequest {
+  // The names of Projects in which to search issues.
+  repeated string projects = 1 [
+    (google.api.resource_reference) = {type: "api.crbug.com/Project"},
+    (google.api.field_behavior) = REQUIRED ];
+  // The query string can contain any number of free text and
+  // field search expressions.
+  // Please see https://bugs.chromium.org/p/chromium/issues/searchtips for more
+  // details of how the query string works.
+  //
+  // Canned queries have been deprecated in v3 in favor of search scoping using
+  // parentheses support.
+  // For clients who previously used canned queries, we're providing the
+  // mapping of legacy canned query IDs to Monorail search syntax:
+  //   - Format: (can_id, description, query_string)
+  //   - (1, 'All issues', '')
+  //   - (2, 'Open issues', 'is:open')
+  //   - (3, 'Open and owned by me', 'is:open owner:me')
+  //   - (4, 'Open and reported by me', 'is:open reporter:me')
+  //   - (5, 'Open and starred by me', 'is:open is:starred')
+  //   - (6, 'New issues', 'status:new')
+  //   - (7, 'Issues to verify', 'status=fixed,done')
+  //   - (8, 'Open with comment by me', 'is:open commentby:me')
+  string query = 2;
+  // The maximum number of items to return. The service may return fewer than
+  // this value.
+  // If unspecified, at most 100 items will be returned.
+  // The maximum value is 100; values above 100 will be coerced to 100.
+  int32 page_size = 3;
+  // A page token, received from a previous `SearchIssues` call.
+  // Provide this to retrieve the subsequent page.
+  //
+  // When paginating, all other parameters provided to `SearchIssues` must match
+  // the call that provided the page token.
+  string page_token = 4;
+  // The string of comma separated field names used to order the items.
+  // Adding '-' before a field, reverses the sort order.
+  // E.g. 'stars,-status' sorts the items by number of stars, high to low,
+  // then by status, low to high.
+  string order_by = 5;
+}
+
+// The response message for Issues.SearchIssues.
+// Next available tag: 3
+message SearchIssuesResponse {
+  // Issues matching the given request.
+  repeated Issue issues = 1;
+  // A token, which can be sent as `page_token` to retrieve the next page.
+  // If this field is omitted, there are no subsequent pages.
+  string next_page_token = 2;
+}
+
+// The request message for Issues.ListComments.
+// Next available tag: 5
+message ListCommentsRequest {
+  // The name of the issue for which to list comments.
+  string parent = 1 [
+    (google.api.resource_reference) = {type: "api.crbug.com/Issue"},
+    (google.api.field_behavior) = REQUIRED ];
+  // The maximum number of items to return. The service may return fewer than
+  // this value.
+  // If unspecified, at most 100 items will be returned.
+  // The maximum value is 100; values above 100 will be coerced to 100.
+  int32 page_size = 2;
+  // A page token, received from a previous `ListComments` call.
+  // Provide this to retrieve the subsequent page.
+  //
+  // When paginating, all other parameters provided to `ListComments` must
+  // match the call that provided the page token.
+  string page_token = 3;
+  // For our initial release this filter only supports filtering to comments
+  // related to a specific approval.
+  // For example `approval = "projects/monorail/approvalDefs/1"`,
+  // Note that no further logical or comparison operators are supported
+  string filter = 4;
+}
+
+// The response message for Issues.ListComments
+// Next available tag: 3
+message ListCommentsResponse {
+  // The comments from the specified issue.
+  repeated Comment comments = 1;
+  // A token, which can be sent as `page_token` to retrieve the next page.
+  // If this field is omitted, there are no subsequent pages.
+  string next_page_token = 2;
+}
+
+// An attachment to upload to a comment or description.
+// Next available tag: 3
+message AttachmentUpload {
+  string filename = 1 [ (google.api.field_behavior) = REQUIRED ];
+  bytes content = 2 [ (google.api.field_behavior) = REQUIRED ];
+}
+
+// Holds changes to one issue, used in ModifyIssuesRequest.
+// Next available tag: 9
+message IssueDelta {
+  // The issue's `name` field is used to identify the issue to be
+  // updated. `issue.name` must always be filled.
+  //
+  // Values with rule-based Derivation within `issue` and in `field_vals_remove`
+  // will be ignored.
+  Issue issue = 1 [
+      (google.api.field_behavior) = REQUIRED ];
+  // The list of fields in `issue` to be updated.
+  //
+  // Repeated fields set on `issue` will be appended to.
+  //
+  // Non-repeated fields (e.g. `owner`) can be set with `issue.owner` set and
+  // either 'owner' or 'owner.user' added to `update_mask`.
+  // To unset non-repeated fields back to their default value, `issue.owner`
+  // must contain the default value and `update_mask` must include 'owner.user'
+  // NOT 'owner'.
+  //
+  // Its `field_values`, however, are a special case. Fields can be specified as
+  // single-value or multi-value in their FieldDef.
+  //
+  // Single-value Field: if there is preexisting FieldValue with the same
+  // `field` and `phase`, it will be REPLACED.
+  //
+  // Multi-value Field: a new value will be appended, unless the same `field`,
+  // `phase`, `value` combination already exists. In that case, the FieldValue
+  // will be ignored. In other words, duplicate values are ignored.
+  // (With the exception of crbug.com/monorail/8137 until it is fixed).
+  google.protobuf.FieldMask update_mask = 2 [
+      (google.api.field_behavior) = REQUIRED ];
+
+  // Values to remove from the repeated fields of the issue.
+
+  // Cc's to remove.
+  repeated string ccs_remove = 3 [
+      (google.api.resource_reference) = {type: "api.crbug.com/User"}];
+  // Blocked_on issues to remove.
+  repeated IssueRef blocked_on_issues_remove = 4;
+  // Blocking issues to remove.
+  repeated IssueRef blocking_issues_remove = 5;
+  // Components to remove.
+  repeated string components_remove = 6 [
+      (google.api.resource_reference) = {type: "api.crbug.com/ComponentDef"}];
+  // Labels to remove.
+  repeated string labels_remove = 7;
+  // FieldValues to remove. Any values that did not already exist will be
+  // ignored e.g. if you append a FieldValue in issue and remove it here, it
+  // will still be added.
+  repeated FieldValue field_vals_remove = 8;
+
+  // TODO(crbug.com/monorail/8019): add Attachment uploading and removing.
+}
+
+// Changes to make to an ApprovalValue. Used to ModifyIssueApprovalValues or
+// to MakeIssueFromTemplate.
+//
+// NOTE: The same handling of FieldValues discussed in IssueDelta applies here.
+// Next available tag: 6
+message ApprovalDelta {
+  // The ApprovalValue we want to update. `approval_value.name` must always be
+  // set.
+  ApprovalValue approval_value = 1;
+  // Repeated fields found in `update_mask` will be appended to.
+  google.protobuf.FieldMask update_mask = 2 [
+      (google.api.field_behavior) = REQUIRED ];
+  // Resource names of the approvers we want to remove.
+  repeated string approvers_remove = 3 [
+      (google.api.resource_reference) = { type: "api.crbug.com/User" }
+  ];
+  // FieldValues that do not belong to `approval_value` will trigger error.
+  repeated FieldValue field_vals_remove = 5;
+  // TODO(crbug.com/monorail/8019): add Attachment uploading and removing.
+}
+
+
+// The type of notification a change should trigger.
+// See monorail/doc/userguide/email.md
+// Next available tag: 2
+enum NotifyType {
+  // The default value. This value is unused.
+  NOTIFY_TYPE_UNSPECIFIED = 0;
+  // An email notification should be sent.
+  EMAIL = 1;
+  // No notifcation should be triggered at all.
+  NO_NOTIFICATION = 2;
+}
+
+
+// The request message for Issues.ModifyIssues.
+// Next available tag: 5
+message ModifyIssuesRequest {
+  // The issue changes to make. A maximum of 100 issue changes can be requested.
+  // There is also a constraint of 50 additional 'impacted issues' per
+  // ModifyIssuesRequest. 'Impacted issues' are issues that are adding/removing
+  // `blocked_on`, `blocking`, or `merge`
+  // If you encounter this error, consider significantly smaller batches.
+  repeated IssueDelta deltas = 1;
+  // The type of notification the modifications should trigger.
+  NotifyType notify_type = 2;
+  // The comment text that should be added to each issue in delta.
+  // Max length is 51200 characters.
+  string comment_content = 3;
+  // The attachment that will be to each comment for each issue in delta.
+  repeated AttachmentUpload uploads = 4;
+}
+
+
+// The response message for Issues.ModifyIssues.
+// Next available tag: 2
+message ModifyIssuesResponse {
+  // The updated issues.
+  repeated Issue issues = 1;
+}
+
+// The request message for Issues.ModifyIssueApprovalValues.
+// Next available tag: 4
+message ModifyIssueApprovalValuesRequest {
+  // The ApprovalValue changes to make. Maximum of 100 deltas can be requested.
+  repeated ApprovalDelta deltas = 1;
+  // The type of notification the modifications should trigger.
+  NotifyType notify_type = 2;
+  // The `content` of the Comment created for each change in `deltas`.
+  // Max length is 51200 characters.
+  string comment_content = 3;
+}
+
+// The response message for Issues.ModifyIssueApprovalValuesRequest.
+// Next available tag: 2
+message ModifyIssueApprovalValuesResponse {
+  // The updated ApprovalValues.
+  repeated ApprovalValue approval_values = 1;
+}
+
+// The request message for Issue.ListApprovalValues.
+// Next available tag: 2
+message ListApprovalValuesRequest {
+  // The name of the issue for which to list approval values.
+  string parent = 1 [
+    (google.api.resource_reference) = {type: "api.crbug.com/Issue"},
+    (google.api.field_behavior) = REQUIRED ];
+}
+
+// The response message for Issues.ListApprovalValues.
+// Next available tag: 2
+message ListApprovalValuesResponse {
+  // The approval values from the specified issue.
+  repeated ApprovalValue approval_values = 1;
+}
+
+// The request message for Issues.ModifyCommentState.
+// Next available tag: 3
+message ModifyCommentStateRequest {
+  // Resource name of the comment to modify state.
+  string name = 1 [
+    (google.api.resource_reference) = {type: "api.crbug.com/Comment"},
+    (google.api.field_behavior) = REQUIRED ];
+  // Requested state.
+  IssueContentState state = 2;
+}
+
+// The response message for Issues.ModifyCommentState.
+// Next available tag: 2
+message ModifyCommentStateResponse {
+  // The updated comment after modifying state.
+  Comment comment = 1;
+}
+
+// The request message for MakeIssueFromTemplate.
+// Next available tag: 5
+message MakeIssueFromTemplateRequest {
+  // Resource name of the template to use for filling in default values
+  // and adding approvals and phases.
+  string template = 1 [
+      (google.api.resource_reference) = { type: "api.crbug.com/Template" }
+  ];
+  // The issue differences relative to the `template.issue` default.
+  IssueDelta template_issue_delta = 2;
+  // Changes to fields belonging to approvals relative to template default.
+  // While ApprovalDelta can hold additional information, this method only
+  // allows adding and removing field values, all other deltas will be ignored.
+  repeated ApprovalDelta template_approval_deltas = 3;
+  // The issue description, will be saved as the first comment.
+  string description = 4;
+}
+
+// The request message for MakeIssue.
+// Next available tag: 5
+message MakeIssueRequest {
+  // The name of the project the issue should belong to.
+  string parent = 1 [
+    (google.api.resource_reference) = {type: "api.crbug.com/Project"},
+    (google.api.field_behavior) = REQUIRED ];
+  // The issue to be created.
+  Issue issue = 2;
+  // The issue description.
+  string description = 3;
+  // The type of notification the creation should trigger.
+  NotifyType notify_type = 4;
+}
diff --git a/api/v3/api_proto/issues_pb2.py b/api/v3/api_proto/issues_pb2.py
new file mode 100644
index 0000000..7b8abd3
--- /dev/null
+++ b/api/v3/api_proto/issues_pb2.py
@@ -0,0 +1,1261 @@
+# -*- coding: utf-8 -*-
+# Generated by the protocol buffer compiler.  DO NOT EDIT!
+# source: api/v3/api_proto/issues.proto
+
+from google.protobuf.internal import enum_type_wrapper
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import message as _message
+from google.protobuf import reflection as _reflection
+from google.protobuf import symbol_database as _symbol_database
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+from google.protobuf import field_mask_pb2 as google_dot_protobuf_dot_field__mask__pb2
+from google.api import field_behavior_pb2 as google_dot_api_dot_field__behavior__pb2
+from google.api import resource_pb2 as google_dot_api_dot_resource__pb2
+from api.v3.api_proto import issue_objects_pb2 as api_dot_v3_dot_api__proto_dot_issue__objects__pb2
+
+
+DESCRIPTOR = _descriptor.FileDescriptor(
+  name='api/v3/api_proto/issues.proto',
+  package='monorail.v3',
+  syntax='proto3',
+  serialized_options=b'Z\020api/v3/api_proto',
+  create_key=_descriptor._internal_create_key,
+  serialized_pb=b'\n\x1d\x61pi/v3/api_proto/issues.proto\x12\x0bmonorail.v3\x1a google/protobuf/field_mask.proto\x1a\x1fgoogle/api/field_behavior.proto\x1a\x19google/api/resource.proto\x1a$api/v3/api_proto/issue_objects.proto\"<\n\x0fGetIssueRequest\x12)\n\x04name\x18\x01 \x01(\tB\x1b\xfa\x41\x15\n\x13\x61pi.crbug.com/Issue\xe0\x41\x02\"l\n\x15\x42\x61tchGetIssuesRequest\x12*\n\x06parent\x18\x01 \x01(\tB\x1a\xfa\x41\x17\n\x15\x61pi.crbug.com/Project\x12\'\n\x05names\x18\x02 \x03(\tB\x18\xfa\x41\x15\n\x13\x61pi.crbug.com/Issue\"<\n\x16\x42\x61tchGetIssuesResponse\x12\"\n\x06issues\x18\x01 \x03(\x0b\x32\x12.monorail.v3.Issue\"\x8e\x01\n\x13SearchIssuesRequest\x12/\n\x08projects\x18\x01 \x03(\tB\x1d\xfa\x41\x17\n\x15\x61pi.crbug.com/Project\xe0\x41\x02\x12\r\n\x05query\x18\x02 \x01(\t\x12\x11\n\tpage_size\x18\x03 \x01(\x05\x12\x12\n\npage_token\x18\x04 \x01(\t\x12\x10\n\x08order_by\x18\x05 \x01(\t\"S\n\x14SearchIssuesResponse\x12\"\n\x06issues\x18\x01 \x03(\x0b\x32\x12.monorail.v3.Issue\x12\x17\n\x0fnext_page_token\x18\x02 \x01(\t\"y\n\x13ListCommentsRequest\x12+\n\x06parent\x18\x01 \x01(\tB\x1b\xfa\x41\x15\n\x13\x61pi.crbug.com/Issue\xe0\x41\x02\x12\x11\n\tpage_size\x18\x02 \x01(\x05\x12\x12\n\npage_token\x18\x03 \x01(\t\x12\x0e\n\x06\x66ilter\x18\x04 \x01(\t\"W\n\x14ListCommentsResponse\x12&\n\x08\x63omments\x18\x01 \x03(\x0b\x32\x14.monorail.v3.Comment\x12\x17\n\x0fnext_page_token\x18\x02 \x01(\t\"?\n\x10\x41ttachmentUpload\x12\x15\n\x08\x66ilename\x18\x01 \x01(\tB\x03\xe0\x41\x02\x12\x14\n\x07\x63ontent\x18\x02 \x01(\x0c\x42\x03\xe0\x41\x02\"\x8e\x03\n\nIssueDelta\x12&\n\x05issue\x18\x01 \x01(\x0b\x32\x12.monorail.v3.IssueB\x03\xe0\x41\x02\x12\x34\n\x0bupdate_mask\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.FieldMaskB\x03\xe0\x41\x02\x12+\n\nccs_remove\x18\x03 \x03(\tB\x17\xfa\x41\x14\n\x12\x61pi.crbug.com/User\x12\x37\n\x18\x62locked_on_issues_remove\x18\x04 \x03(\x0b\x32\x15.monorail.v3.IssueRef\x12\x35\n\x16\x62locking_issues_remove\x18\x05 \x03(\x0b\x32\x15.monorail.v3.IssueRef\x12:\n\x11\x63omponents_remove\x18\x06 \x03(\tB\x1f\xfa\x41\x1c\n\x1a\x61pi.crbug.com/ComponentDef\x12\x15\n\rlabels_remove\x18\x07 \x03(\t\x12\x32\n\x11\x66ield_vals_remove\x18\x08 \x03(\x0b\x32\x17.monorail.v3.FieldValue\"\xe0\x01\n\rApprovalDelta\x12\x32\n\x0e\x61pproval_value\x18\x01 \x01(\x0b\x32\x1a.monorail.v3.ApprovalValue\x12\x34\n\x0bupdate_mask\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.FieldMaskB\x03\xe0\x41\x02\x12\x31\n\x10\x61pprovers_remove\x18\x03 \x03(\tB\x17\xfa\x41\x14\n\x12\x61pi.crbug.com/User\x12\x32\n\x11\x66ield_vals_remove\x18\x05 \x03(\x0b\x32\x17.monorail.v3.FieldValue\"\xb5\x01\n\x13ModifyIssuesRequest\x12\'\n\x06\x64\x65ltas\x18\x01 \x03(\x0b\x32\x17.monorail.v3.IssueDelta\x12,\n\x0bnotify_type\x18\x02 \x01(\x0e\x32\x17.monorail.v3.NotifyType\x12\x17\n\x0f\x63omment_content\x18\x03 \x01(\t\x12.\n\x07uploads\x18\x04 \x03(\x0b\x32\x1d.monorail.v3.AttachmentUpload\":\n\x14ModifyIssuesResponse\x12\"\n\x06issues\x18\x01 \x03(\x0b\x32\x12.monorail.v3.Issue\"\x95\x01\n ModifyIssueApprovalValuesRequest\x12*\n\x06\x64\x65ltas\x18\x01 \x03(\x0b\x32\x1a.monorail.v3.ApprovalDelta\x12,\n\x0bnotify_type\x18\x02 \x01(\x0e\x32\x17.monorail.v3.NotifyType\x12\x17\n\x0f\x63omment_content\x18\x03 \x01(\t\"X\n!ModifyIssueApprovalValuesResponse\x12\x33\n\x0f\x61pproval_values\x18\x01 \x03(\x0b\x32\x1a.monorail.v3.ApprovalValue\"H\n\x19ListApprovalValuesRequest\x12+\n\x06parent\x18\x01 \x01(\tB\x1b\xfa\x41\x15\n\x13\x61pi.crbug.com/Issue\xe0\x41\x02\"Q\n\x1aListApprovalValuesResponse\x12\x33\n\x0f\x61pproval_values\x18\x01 \x03(\x0b\x32\x1a.monorail.v3.ApprovalValue\"w\n\x19ModifyCommentStateRequest\x12+\n\x04name\x18\x01 \x01(\tB\x1d\xfa\x41\x17\n\x15\x61pi.crbug.com/Comment\xe0\x41\x02\x12-\n\x05state\x18\x02 \x01(\x0e\x32\x1e.monorail.v3.IssueContentState\"C\n\x1aModifyCommentStateResponse\x12%\n\x07\x63omment\x18\x01 \x01(\x0b\x32\x14.monorail.v3.Comment\"\xd7\x01\n\x1cMakeIssueFromTemplateRequest\x12-\n\x08template\x18\x01 \x01(\tB\x1b\xfa\x41\x18\n\x16\x61pi.crbug.com/Template\x12\x35\n\x14template_issue_delta\x18\x02 \x01(\x0b\x32\x17.monorail.v3.IssueDelta\x12<\n\x18template_approval_deltas\x18\x03 \x03(\x0b\x32\x1a.monorail.v3.ApprovalDelta\x12\x13\n\x0b\x64\x65scription\x18\x04 \x01(\t\"\xa7\x01\n\x10MakeIssueRequest\x12-\n\x06parent\x18\x01 \x01(\tB\x1d\xfa\x41\x17\n\x15\x61pi.crbug.com/Project\xe0\x41\x02\x12!\n\x05issue\x18\x02 \x01(\x0b\x32\x12.monorail.v3.Issue\x12\x13\n\x0b\x64\x65scription\x18\x03 \x01(\t\x12,\n\x0bnotify_type\x18\x04 \x01(\x0e\x32\x17.monorail.v3.NotifyType*I\n\nNotifyType\x12\x1b\n\x17NOTIFY_TYPE_UNSPECIFIED\x10\x00\x12\t\n\x05\x45MAIL\x10\x01\x12\x13\n\x0fNO_NOTIFICATION\x10\x02\x32\x96\x07\n\x06Issues\x12>\n\x08GetIssue\x12\x1c.monorail.v3.GetIssueRequest\x1a\x12.monorail.v3.Issue\"\x00\x12[\n\x0e\x42\x61tchGetIssues\x12\".monorail.v3.BatchGetIssuesRequest\x1a#.monorail.v3.BatchGetIssuesResponse\"\x00\x12U\n\x0cSearchIssues\x12 .monorail.v3.SearchIssuesRequest\x1a!.monorail.v3.SearchIssuesResponse\"\x00\x12U\n\x0cListComments\x12 .monorail.v3.ListCommentsRequest\x1a!.monorail.v3.ListCommentsResponse\"\x00\x12U\n\x0cModifyIssues\x12 .monorail.v3.ModifyIssuesRequest\x1a!.monorail.v3.ModifyIssuesResponse\"\x00\x12|\n\x19ModifyIssueApprovalValues\x12-.monorail.v3.ModifyIssueApprovalValuesRequest\x1a..monorail.v3.ModifyIssueApprovalValuesResponse\"\x00\x12g\n\x12ListApprovalValues\x12&.monorail.v3.ListApprovalValuesRequest\x1a\'.monorail.v3.ListApprovalValuesResponse\"\x00\x12g\n\x12ModifyCommentState\x12&.monorail.v3.ModifyCommentStateRequest\x1a\'.monorail.v3.ModifyCommentStateResponse\"\x00\x12X\n\x15MakeIssueFromTemplate\x12).monorail.v3.MakeIssueFromTemplateRequest\x1a\x12.monorail.v3.Issue\"\x00\x12@\n\tMakeIssue\x12\x1d.monorail.v3.MakeIssueRequest\x1a\x12.monorail.v3.Issue\"\x00\x42\x12Z\x10\x61pi/v3/api_protob\x06proto3'
+  ,
+  dependencies=[google_dot_protobuf_dot_field__mask__pb2.DESCRIPTOR,google_dot_api_dot_field__behavior__pb2.DESCRIPTOR,google_dot_api_dot_resource__pb2.DESCRIPTOR,api_dot_v3_dot_api__proto_dot_issue__objects__pb2.DESCRIPTOR,])
+
+_NOTIFYTYPE = _descriptor.EnumDescriptor(
+  name='NotifyType',
+  full_name='monorail.v3.NotifyType',
+  filename=None,
+  file=DESCRIPTOR,
+  create_key=_descriptor._internal_create_key,
+  values=[
+    _descriptor.EnumValueDescriptor(
+      name='NOTIFY_TYPE_UNSPECIFIED', index=0, number=0,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='EMAIL', index=1, number=1,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='NO_NOTIFICATION', index=2, number=2,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+  ],
+  containing_type=None,
+  serialized_options=None,
+  serialized_start=2768,
+  serialized_end=2841,
+)
+_sym_db.RegisterEnumDescriptor(_NOTIFYTYPE)
+
+NotifyType = enum_type_wrapper.EnumTypeWrapper(_NOTIFYTYPE)
+NOTIFY_TYPE_UNSPECIFIED = 0
+EMAIL = 1
+NO_NOTIFICATION = 2
+
+
+
+_GETISSUEREQUEST = _descriptor.Descriptor(
+  name='GetIssueRequest',
+  full_name='monorail.v3.GetIssueRequest',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='name', full_name='monorail.v3.GetIssueRequest.name', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\372A\025\n\023api.crbug.com/Issue\340A\002', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=178,
+  serialized_end=238,
+)
+
+
+_BATCHGETISSUESREQUEST = _descriptor.Descriptor(
+  name='BatchGetIssuesRequest',
+  full_name='monorail.v3.BatchGetIssuesRequest',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='parent', full_name='monorail.v3.BatchGetIssuesRequest.parent', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\372A\027\n\025api.crbug.com/Project', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='names', full_name='monorail.v3.BatchGetIssuesRequest.names', index=1,
+      number=2, type=9, cpp_type=9, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\372A\025\n\023api.crbug.com/Issue', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=240,
+  serialized_end=348,
+)
+
+
+_BATCHGETISSUESRESPONSE = _descriptor.Descriptor(
+  name='BatchGetIssuesResponse',
+  full_name='monorail.v3.BatchGetIssuesResponse',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='issues', full_name='monorail.v3.BatchGetIssuesResponse.issues', index=0,
+      number=1, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=350,
+  serialized_end=410,
+)
+
+
+_SEARCHISSUESREQUEST = _descriptor.Descriptor(
+  name='SearchIssuesRequest',
+  full_name='monorail.v3.SearchIssuesRequest',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='projects', full_name='monorail.v3.SearchIssuesRequest.projects', index=0,
+      number=1, type=9, cpp_type=9, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\372A\027\n\025api.crbug.com/Project\340A\002', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='query', full_name='monorail.v3.SearchIssuesRequest.query', index=1,
+      number=2, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='page_size', full_name='monorail.v3.SearchIssuesRequest.page_size', index=2,
+      number=3, type=5, cpp_type=1, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='page_token', full_name='monorail.v3.SearchIssuesRequest.page_token', index=3,
+      number=4, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='order_by', full_name='monorail.v3.SearchIssuesRequest.order_by', index=4,
+      number=5, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=413,
+  serialized_end=555,
+)
+
+
+_SEARCHISSUESRESPONSE = _descriptor.Descriptor(
+  name='SearchIssuesResponse',
+  full_name='monorail.v3.SearchIssuesResponse',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='issues', full_name='monorail.v3.SearchIssuesResponse.issues', index=0,
+      number=1, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='next_page_token', full_name='monorail.v3.SearchIssuesResponse.next_page_token', index=1,
+      number=2, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=557,
+  serialized_end=640,
+)
+
+
+_LISTCOMMENTSREQUEST = _descriptor.Descriptor(
+  name='ListCommentsRequest',
+  full_name='monorail.v3.ListCommentsRequest',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='parent', full_name='monorail.v3.ListCommentsRequest.parent', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\372A\025\n\023api.crbug.com/Issue\340A\002', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='page_size', full_name='monorail.v3.ListCommentsRequest.page_size', index=1,
+      number=2, type=5, cpp_type=1, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='page_token', full_name='monorail.v3.ListCommentsRequest.page_token', index=2,
+      number=3, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='filter', full_name='monorail.v3.ListCommentsRequest.filter', index=3,
+      number=4, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=642,
+  serialized_end=763,
+)
+
+
+_LISTCOMMENTSRESPONSE = _descriptor.Descriptor(
+  name='ListCommentsResponse',
+  full_name='monorail.v3.ListCommentsResponse',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='comments', full_name='monorail.v3.ListCommentsResponse.comments', index=0,
+      number=1, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='next_page_token', full_name='monorail.v3.ListCommentsResponse.next_page_token', index=1,
+      number=2, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=765,
+  serialized_end=852,
+)
+
+
+_ATTACHMENTUPLOAD = _descriptor.Descriptor(
+  name='AttachmentUpload',
+  full_name='monorail.v3.AttachmentUpload',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='filename', full_name='monorail.v3.AttachmentUpload.filename', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\340A\002', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='content', full_name='monorail.v3.AttachmentUpload.content', index=1,
+      number=2, type=12, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"",
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\340A\002', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=854,
+  serialized_end=917,
+)
+
+
+_ISSUEDELTA = _descriptor.Descriptor(
+  name='IssueDelta',
+  full_name='monorail.v3.IssueDelta',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='issue', full_name='monorail.v3.IssueDelta.issue', index=0,
+      number=1, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\340A\002', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='update_mask', full_name='monorail.v3.IssueDelta.update_mask', index=1,
+      number=2, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\340A\002', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='ccs_remove', full_name='monorail.v3.IssueDelta.ccs_remove', index=2,
+      number=3, type=9, cpp_type=9, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\372A\024\n\022api.crbug.com/User', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='blocked_on_issues_remove', full_name='monorail.v3.IssueDelta.blocked_on_issues_remove', index=3,
+      number=4, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='blocking_issues_remove', full_name='monorail.v3.IssueDelta.blocking_issues_remove', index=4,
+      number=5, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='components_remove', full_name='monorail.v3.IssueDelta.components_remove', index=5,
+      number=6, type=9, cpp_type=9, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\372A\034\n\032api.crbug.com/ComponentDef', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='labels_remove', full_name='monorail.v3.IssueDelta.labels_remove', index=6,
+      number=7, type=9, cpp_type=9, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='field_vals_remove', full_name='monorail.v3.IssueDelta.field_vals_remove', index=7,
+      number=8, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=920,
+  serialized_end=1318,
+)
+
+
+_APPROVALDELTA = _descriptor.Descriptor(
+  name='ApprovalDelta',
+  full_name='monorail.v3.ApprovalDelta',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='approval_value', full_name='monorail.v3.ApprovalDelta.approval_value', index=0,
+      number=1, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='update_mask', full_name='monorail.v3.ApprovalDelta.update_mask', index=1,
+      number=2, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\340A\002', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='approvers_remove', full_name='monorail.v3.ApprovalDelta.approvers_remove', index=2,
+      number=3, type=9, cpp_type=9, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\372A\024\n\022api.crbug.com/User', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='field_vals_remove', full_name='monorail.v3.ApprovalDelta.field_vals_remove', index=3,
+      number=5, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=1321,
+  serialized_end=1545,
+)
+
+
+_MODIFYISSUESREQUEST = _descriptor.Descriptor(
+  name='ModifyIssuesRequest',
+  full_name='monorail.v3.ModifyIssuesRequest',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='deltas', full_name='monorail.v3.ModifyIssuesRequest.deltas', index=0,
+      number=1, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='notify_type', full_name='monorail.v3.ModifyIssuesRequest.notify_type', index=1,
+      number=2, type=14, cpp_type=8, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='comment_content', full_name='monorail.v3.ModifyIssuesRequest.comment_content', index=2,
+      number=3, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='uploads', full_name='monorail.v3.ModifyIssuesRequest.uploads', index=3,
+      number=4, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=1548,
+  serialized_end=1729,
+)
+
+
+_MODIFYISSUESRESPONSE = _descriptor.Descriptor(
+  name='ModifyIssuesResponse',
+  full_name='monorail.v3.ModifyIssuesResponse',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='issues', full_name='monorail.v3.ModifyIssuesResponse.issues', index=0,
+      number=1, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=1731,
+  serialized_end=1789,
+)
+
+
+_MODIFYISSUEAPPROVALVALUESREQUEST = _descriptor.Descriptor(
+  name='ModifyIssueApprovalValuesRequest',
+  full_name='monorail.v3.ModifyIssueApprovalValuesRequest',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='deltas', full_name='monorail.v3.ModifyIssueApprovalValuesRequest.deltas', index=0,
+      number=1, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='notify_type', full_name='monorail.v3.ModifyIssueApprovalValuesRequest.notify_type', index=1,
+      number=2, type=14, cpp_type=8, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='comment_content', full_name='monorail.v3.ModifyIssueApprovalValuesRequest.comment_content', index=2,
+      number=3, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=1792,
+  serialized_end=1941,
+)
+
+
+_MODIFYISSUEAPPROVALVALUESRESPONSE = _descriptor.Descriptor(
+  name='ModifyIssueApprovalValuesResponse',
+  full_name='monorail.v3.ModifyIssueApprovalValuesResponse',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='approval_values', full_name='monorail.v3.ModifyIssueApprovalValuesResponse.approval_values', index=0,
+      number=1, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=1943,
+  serialized_end=2031,
+)
+
+
+_LISTAPPROVALVALUESREQUEST = _descriptor.Descriptor(
+  name='ListApprovalValuesRequest',
+  full_name='monorail.v3.ListApprovalValuesRequest',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='parent', full_name='monorail.v3.ListApprovalValuesRequest.parent', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\372A\025\n\023api.crbug.com/Issue\340A\002', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=2033,
+  serialized_end=2105,
+)
+
+
+_LISTAPPROVALVALUESRESPONSE = _descriptor.Descriptor(
+  name='ListApprovalValuesResponse',
+  full_name='monorail.v3.ListApprovalValuesResponse',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='approval_values', full_name='monorail.v3.ListApprovalValuesResponse.approval_values', index=0,
+      number=1, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=2107,
+  serialized_end=2188,
+)
+
+
+_MODIFYCOMMENTSTATEREQUEST = _descriptor.Descriptor(
+  name='ModifyCommentStateRequest',
+  full_name='monorail.v3.ModifyCommentStateRequest',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='name', full_name='monorail.v3.ModifyCommentStateRequest.name', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\372A\027\n\025api.crbug.com/Comment\340A\002', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='state', full_name='monorail.v3.ModifyCommentStateRequest.state', index=1,
+      number=2, type=14, cpp_type=8, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=2190,
+  serialized_end=2309,
+)
+
+
+_MODIFYCOMMENTSTATERESPONSE = _descriptor.Descriptor(
+  name='ModifyCommentStateResponse',
+  full_name='monorail.v3.ModifyCommentStateResponse',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='comment', full_name='monorail.v3.ModifyCommentStateResponse.comment', index=0,
+      number=1, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=2311,
+  serialized_end=2378,
+)
+
+
+_MAKEISSUEFROMTEMPLATEREQUEST = _descriptor.Descriptor(
+  name='MakeIssueFromTemplateRequest',
+  full_name='monorail.v3.MakeIssueFromTemplateRequest',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='template', full_name='monorail.v3.MakeIssueFromTemplateRequest.template', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\372A\030\n\026api.crbug.com/Template', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='template_issue_delta', full_name='monorail.v3.MakeIssueFromTemplateRequest.template_issue_delta', index=1,
+      number=2, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='template_approval_deltas', full_name='monorail.v3.MakeIssueFromTemplateRequest.template_approval_deltas', index=2,
+      number=3, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='description', full_name='monorail.v3.MakeIssueFromTemplateRequest.description', index=3,
+      number=4, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=2381,
+  serialized_end=2596,
+)
+
+
+_MAKEISSUEREQUEST = _descriptor.Descriptor(
+  name='MakeIssueRequest',
+  full_name='monorail.v3.MakeIssueRequest',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='parent', full_name='monorail.v3.MakeIssueRequest.parent', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\372A\027\n\025api.crbug.com/Project\340A\002', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='issue', full_name='monorail.v3.MakeIssueRequest.issue', index=1,
+      number=2, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='description', full_name='monorail.v3.MakeIssueRequest.description', index=2,
+      number=3, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='notify_type', full_name='monorail.v3.MakeIssueRequest.notify_type', index=3,
+      number=4, type=14, cpp_type=8, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=2599,
+  serialized_end=2766,
+)
+
+_BATCHGETISSUESRESPONSE.fields_by_name['issues'].message_type = api_dot_v3_dot_api__proto_dot_issue__objects__pb2._ISSUE
+_SEARCHISSUESRESPONSE.fields_by_name['issues'].message_type = api_dot_v3_dot_api__proto_dot_issue__objects__pb2._ISSUE
+_LISTCOMMENTSRESPONSE.fields_by_name['comments'].message_type = api_dot_v3_dot_api__proto_dot_issue__objects__pb2._COMMENT
+_ISSUEDELTA.fields_by_name['issue'].message_type = api_dot_v3_dot_api__proto_dot_issue__objects__pb2._ISSUE
+_ISSUEDELTA.fields_by_name['update_mask'].message_type = google_dot_protobuf_dot_field__mask__pb2._FIELDMASK
+_ISSUEDELTA.fields_by_name['blocked_on_issues_remove'].message_type = api_dot_v3_dot_api__proto_dot_issue__objects__pb2._ISSUEREF
+_ISSUEDELTA.fields_by_name['blocking_issues_remove'].message_type = api_dot_v3_dot_api__proto_dot_issue__objects__pb2._ISSUEREF
+_ISSUEDELTA.fields_by_name['field_vals_remove'].message_type = api_dot_v3_dot_api__proto_dot_issue__objects__pb2._FIELDVALUE
+_APPROVALDELTA.fields_by_name['approval_value'].message_type = api_dot_v3_dot_api__proto_dot_issue__objects__pb2._APPROVALVALUE
+_APPROVALDELTA.fields_by_name['update_mask'].message_type = google_dot_protobuf_dot_field__mask__pb2._FIELDMASK
+_APPROVALDELTA.fields_by_name['field_vals_remove'].message_type = api_dot_v3_dot_api__proto_dot_issue__objects__pb2._FIELDVALUE
+_MODIFYISSUESREQUEST.fields_by_name['deltas'].message_type = _ISSUEDELTA
+_MODIFYISSUESREQUEST.fields_by_name['notify_type'].enum_type = _NOTIFYTYPE
+_MODIFYISSUESREQUEST.fields_by_name['uploads'].message_type = _ATTACHMENTUPLOAD
+_MODIFYISSUESRESPONSE.fields_by_name['issues'].message_type = api_dot_v3_dot_api__proto_dot_issue__objects__pb2._ISSUE
+_MODIFYISSUEAPPROVALVALUESREQUEST.fields_by_name['deltas'].message_type = _APPROVALDELTA
+_MODIFYISSUEAPPROVALVALUESREQUEST.fields_by_name['notify_type'].enum_type = _NOTIFYTYPE
+_MODIFYISSUEAPPROVALVALUESRESPONSE.fields_by_name['approval_values'].message_type = api_dot_v3_dot_api__proto_dot_issue__objects__pb2._APPROVALVALUE
+_LISTAPPROVALVALUESRESPONSE.fields_by_name['approval_values'].message_type = api_dot_v3_dot_api__proto_dot_issue__objects__pb2._APPROVALVALUE
+_MODIFYCOMMENTSTATEREQUEST.fields_by_name['state'].enum_type = api_dot_v3_dot_api__proto_dot_issue__objects__pb2._ISSUECONTENTSTATE
+_MODIFYCOMMENTSTATERESPONSE.fields_by_name['comment'].message_type = api_dot_v3_dot_api__proto_dot_issue__objects__pb2._COMMENT
+_MAKEISSUEFROMTEMPLATEREQUEST.fields_by_name['template_issue_delta'].message_type = _ISSUEDELTA
+_MAKEISSUEFROMTEMPLATEREQUEST.fields_by_name['template_approval_deltas'].message_type = _APPROVALDELTA
+_MAKEISSUEREQUEST.fields_by_name['issue'].message_type = api_dot_v3_dot_api__proto_dot_issue__objects__pb2._ISSUE
+_MAKEISSUEREQUEST.fields_by_name['notify_type'].enum_type = _NOTIFYTYPE
+DESCRIPTOR.message_types_by_name['GetIssueRequest'] = _GETISSUEREQUEST
+DESCRIPTOR.message_types_by_name['BatchGetIssuesRequest'] = _BATCHGETISSUESREQUEST
+DESCRIPTOR.message_types_by_name['BatchGetIssuesResponse'] = _BATCHGETISSUESRESPONSE
+DESCRIPTOR.message_types_by_name['SearchIssuesRequest'] = _SEARCHISSUESREQUEST
+DESCRIPTOR.message_types_by_name['SearchIssuesResponse'] = _SEARCHISSUESRESPONSE
+DESCRIPTOR.message_types_by_name['ListCommentsRequest'] = _LISTCOMMENTSREQUEST
+DESCRIPTOR.message_types_by_name['ListCommentsResponse'] = _LISTCOMMENTSRESPONSE
+DESCRIPTOR.message_types_by_name['AttachmentUpload'] = _ATTACHMENTUPLOAD
+DESCRIPTOR.message_types_by_name['IssueDelta'] = _ISSUEDELTA
+DESCRIPTOR.message_types_by_name['ApprovalDelta'] = _APPROVALDELTA
+DESCRIPTOR.message_types_by_name['ModifyIssuesRequest'] = _MODIFYISSUESREQUEST
+DESCRIPTOR.message_types_by_name['ModifyIssuesResponse'] = _MODIFYISSUESRESPONSE
+DESCRIPTOR.message_types_by_name['ModifyIssueApprovalValuesRequest'] = _MODIFYISSUEAPPROVALVALUESREQUEST
+DESCRIPTOR.message_types_by_name['ModifyIssueApprovalValuesResponse'] = _MODIFYISSUEAPPROVALVALUESRESPONSE
+DESCRIPTOR.message_types_by_name['ListApprovalValuesRequest'] = _LISTAPPROVALVALUESREQUEST
+DESCRIPTOR.message_types_by_name['ListApprovalValuesResponse'] = _LISTAPPROVALVALUESRESPONSE
+DESCRIPTOR.message_types_by_name['ModifyCommentStateRequest'] = _MODIFYCOMMENTSTATEREQUEST
+DESCRIPTOR.message_types_by_name['ModifyCommentStateResponse'] = _MODIFYCOMMENTSTATERESPONSE
+DESCRIPTOR.message_types_by_name['MakeIssueFromTemplateRequest'] = _MAKEISSUEFROMTEMPLATEREQUEST
+DESCRIPTOR.message_types_by_name['MakeIssueRequest'] = _MAKEISSUEREQUEST
+DESCRIPTOR.enum_types_by_name['NotifyType'] = _NOTIFYTYPE
+_sym_db.RegisterFileDescriptor(DESCRIPTOR)
+
+GetIssueRequest = _reflection.GeneratedProtocolMessageType('GetIssueRequest', (_message.Message,), {
+  'DESCRIPTOR' : _GETISSUEREQUEST,
+  '__module__' : 'api.v3.api_proto.issues_pb2'
+  # @@protoc_insertion_point(class_scope:monorail.v3.GetIssueRequest)
+  })
+_sym_db.RegisterMessage(GetIssueRequest)
+
+BatchGetIssuesRequest = _reflection.GeneratedProtocolMessageType('BatchGetIssuesRequest', (_message.Message,), {
+  'DESCRIPTOR' : _BATCHGETISSUESREQUEST,
+  '__module__' : 'api.v3.api_proto.issues_pb2'
+  # @@protoc_insertion_point(class_scope:monorail.v3.BatchGetIssuesRequest)
+  })
+_sym_db.RegisterMessage(BatchGetIssuesRequest)
+
+BatchGetIssuesResponse = _reflection.GeneratedProtocolMessageType('BatchGetIssuesResponse', (_message.Message,), {
+  'DESCRIPTOR' : _BATCHGETISSUESRESPONSE,
+  '__module__' : 'api.v3.api_proto.issues_pb2'
+  # @@protoc_insertion_point(class_scope:monorail.v3.BatchGetIssuesResponse)
+  })
+_sym_db.RegisterMessage(BatchGetIssuesResponse)
+
+SearchIssuesRequest = _reflection.GeneratedProtocolMessageType('SearchIssuesRequest', (_message.Message,), {
+  'DESCRIPTOR' : _SEARCHISSUESREQUEST,
+  '__module__' : 'api.v3.api_proto.issues_pb2'
+  # @@protoc_insertion_point(class_scope:monorail.v3.SearchIssuesRequest)
+  })
+_sym_db.RegisterMessage(SearchIssuesRequest)
+
+SearchIssuesResponse = _reflection.GeneratedProtocolMessageType('SearchIssuesResponse', (_message.Message,), {
+  'DESCRIPTOR' : _SEARCHISSUESRESPONSE,
+  '__module__' : 'api.v3.api_proto.issues_pb2'
+  # @@protoc_insertion_point(class_scope:monorail.v3.SearchIssuesResponse)
+  })
+_sym_db.RegisterMessage(SearchIssuesResponse)
+
+ListCommentsRequest = _reflection.GeneratedProtocolMessageType('ListCommentsRequest', (_message.Message,), {
+  'DESCRIPTOR' : _LISTCOMMENTSREQUEST,
+  '__module__' : 'api.v3.api_proto.issues_pb2'
+  # @@protoc_insertion_point(class_scope:monorail.v3.ListCommentsRequest)
+  })
+_sym_db.RegisterMessage(ListCommentsRequest)
+
+ListCommentsResponse = _reflection.GeneratedProtocolMessageType('ListCommentsResponse', (_message.Message,), {
+  'DESCRIPTOR' : _LISTCOMMENTSRESPONSE,
+  '__module__' : 'api.v3.api_proto.issues_pb2'
+  # @@protoc_insertion_point(class_scope:monorail.v3.ListCommentsResponse)
+  })
+_sym_db.RegisterMessage(ListCommentsResponse)
+
+AttachmentUpload = _reflection.GeneratedProtocolMessageType('AttachmentUpload', (_message.Message,), {
+  'DESCRIPTOR' : _ATTACHMENTUPLOAD,
+  '__module__' : 'api.v3.api_proto.issues_pb2'
+  # @@protoc_insertion_point(class_scope:monorail.v3.AttachmentUpload)
+  })
+_sym_db.RegisterMessage(AttachmentUpload)
+
+IssueDelta = _reflection.GeneratedProtocolMessageType('IssueDelta', (_message.Message,), {
+  'DESCRIPTOR' : _ISSUEDELTA,
+  '__module__' : 'api.v3.api_proto.issues_pb2'
+  # @@protoc_insertion_point(class_scope:monorail.v3.IssueDelta)
+  })
+_sym_db.RegisterMessage(IssueDelta)
+
+ApprovalDelta = _reflection.GeneratedProtocolMessageType('ApprovalDelta', (_message.Message,), {
+  'DESCRIPTOR' : _APPROVALDELTA,
+  '__module__' : 'api.v3.api_proto.issues_pb2'
+  # @@protoc_insertion_point(class_scope:monorail.v3.ApprovalDelta)
+  })
+_sym_db.RegisterMessage(ApprovalDelta)
+
+ModifyIssuesRequest = _reflection.GeneratedProtocolMessageType('ModifyIssuesRequest', (_message.Message,), {
+  'DESCRIPTOR' : _MODIFYISSUESREQUEST,
+  '__module__' : 'api.v3.api_proto.issues_pb2'
+  # @@protoc_insertion_point(class_scope:monorail.v3.ModifyIssuesRequest)
+  })
+_sym_db.RegisterMessage(ModifyIssuesRequest)
+
+ModifyIssuesResponse = _reflection.GeneratedProtocolMessageType('ModifyIssuesResponse', (_message.Message,), {
+  'DESCRIPTOR' : _MODIFYISSUESRESPONSE,
+  '__module__' : 'api.v3.api_proto.issues_pb2'
+  # @@protoc_insertion_point(class_scope:monorail.v3.ModifyIssuesResponse)
+  })
+_sym_db.RegisterMessage(ModifyIssuesResponse)
+
+ModifyIssueApprovalValuesRequest = _reflection.GeneratedProtocolMessageType('ModifyIssueApprovalValuesRequest', (_message.Message,), {
+  'DESCRIPTOR' : _MODIFYISSUEAPPROVALVALUESREQUEST,
+  '__module__' : 'api.v3.api_proto.issues_pb2'
+  # @@protoc_insertion_point(class_scope:monorail.v3.ModifyIssueApprovalValuesRequest)
+  })
+_sym_db.RegisterMessage(ModifyIssueApprovalValuesRequest)
+
+ModifyIssueApprovalValuesResponse = _reflection.GeneratedProtocolMessageType('ModifyIssueApprovalValuesResponse', (_message.Message,), {
+  'DESCRIPTOR' : _MODIFYISSUEAPPROVALVALUESRESPONSE,
+  '__module__' : 'api.v3.api_proto.issues_pb2'
+  # @@protoc_insertion_point(class_scope:monorail.v3.ModifyIssueApprovalValuesResponse)
+  })
+_sym_db.RegisterMessage(ModifyIssueApprovalValuesResponse)
+
+ListApprovalValuesRequest = _reflection.GeneratedProtocolMessageType('ListApprovalValuesRequest', (_message.Message,), {
+  'DESCRIPTOR' : _LISTAPPROVALVALUESREQUEST,
+  '__module__' : 'api.v3.api_proto.issues_pb2'
+  # @@protoc_insertion_point(class_scope:monorail.v3.ListApprovalValuesRequest)
+  })
+_sym_db.RegisterMessage(ListApprovalValuesRequest)
+
+ListApprovalValuesResponse = _reflection.GeneratedProtocolMessageType('ListApprovalValuesResponse', (_message.Message,), {
+  'DESCRIPTOR' : _LISTAPPROVALVALUESRESPONSE,
+  '__module__' : 'api.v3.api_proto.issues_pb2'
+  # @@protoc_insertion_point(class_scope:monorail.v3.ListApprovalValuesResponse)
+  })
+_sym_db.RegisterMessage(ListApprovalValuesResponse)
+
+ModifyCommentStateRequest = _reflection.GeneratedProtocolMessageType('ModifyCommentStateRequest', (_message.Message,), {
+  'DESCRIPTOR' : _MODIFYCOMMENTSTATEREQUEST,
+  '__module__' : 'api.v3.api_proto.issues_pb2'
+  # @@protoc_insertion_point(class_scope:monorail.v3.ModifyCommentStateRequest)
+  })
+_sym_db.RegisterMessage(ModifyCommentStateRequest)
+
+ModifyCommentStateResponse = _reflection.GeneratedProtocolMessageType('ModifyCommentStateResponse', (_message.Message,), {
+  'DESCRIPTOR' : _MODIFYCOMMENTSTATERESPONSE,
+  '__module__' : 'api.v3.api_proto.issues_pb2'
+  # @@protoc_insertion_point(class_scope:monorail.v3.ModifyCommentStateResponse)
+  })
+_sym_db.RegisterMessage(ModifyCommentStateResponse)
+
+MakeIssueFromTemplateRequest = _reflection.GeneratedProtocolMessageType('MakeIssueFromTemplateRequest', (_message.Message,), {
+  'DESCRIPTOR' : _MAKEISSUEFROMTEMPLATEREQUEST,
+  '__module__' : 'api.v3.api_proto.issues_pb2'
+  # @@protoc_insertion_point(class_scope:monorail.v3.MakeIssueFromTemplateRequest)
+  })
+_sym_db.RegisterMessage(MakeIssueFromTemplateRequest)
+
+MakeIssueRequest = _reflection.GeneratedProtocolMessageType('MakeIssueRequest', (_message.Message,), {
+  'DESCRIPTOR' : _MAKEISSUEREQUEST,
+  '__module__' : 'api.v3.api_proto.issues_pb2'
+  # @@protoc_insertion_point(class_scope:monorail.v3.MakeIssueRequest)
+  })
+_sym_db.RegisterMessage(MakeIssueRequest)
+
+
+DESCRIPTOR._options = None
+_GETISSUEREQUEST.fields_by_name['name']._options = None
+_BATCHGETISSUESREQUEST.fields_by_name['parent']._options = None
+_BATCHGETISSUESREQUEST.fields_by_name['names']._options = None
+_SEARCHISSUESREQUEST.fields_by_name['projects']._options = None
+_LISTCOMMENTSREQUEST.fields_by_name['parent']._options = None
+_ATTACHMENTUPLOAD.fields_by_name['filename']._options = None
+_ATTACHMENTUPLOAD.fields_by_name['content']._options = None
+_ISSUEDELTA.fields_by_name['issue']._options = None
+_ISSUEDELTA.fields_by_name['update_mask']._options = None
+_ISSUEDELTA.fields_by_name['ccs_remove']._options = None
+_ISSUEDELTA.fields_by_name['components_remove']._options = None
+_APPROVALDELTA.fields_by_name['update_mask']._options = None
+_APPROVALDELTA.fields_by_name['approvers_remove']._options = None
+_LISTAPPROVALVALUESREQUEST.fields_by_name['parent']._options = None
+_MODIFYCOMMENTSTATEREQUEST.fields_by_name['name']._options = None
+_MAKEISSUEFROMTEMPLATEREQUEST.fields_by_name['template']._options = None
+_MAKEISSUEREQUEST.fields_by_name['parent']._options = None
+
+_ISSUES = _descriptor.ServiceDescriptor(
+  name='Issues',
+  full_name='monorail.v3.Issues',
+  file=DESCRIPTOR,
+  index=0,
+  serialized_options=None,
+  create_key=_descriptor._internal_create_key,
+  serialized_start=2844,
+  serialized_end=3762,
+  methods=[
+  _descriptor.MethodDescriptor(
+    name='GetIssue',
+    full_name='monorail.v3.Issues.GetIssue',
+    index=0,
+    containing_service=None,
+    input_type=_GETISSUEREQUEST,
+    output_type=api_dot_v3_dot_api__proto_dot_issue__objects__pb2._ISSUE,
+    serialized_options=None,
+    create_key=_descriptor._internal_create_key,
+  ),
+  _descriptor.MethodDescriptor(
+    name='BatchGetIssues',
+    full_name='monorail.v3.Issues.BatchGetIssues',
+    index=1,
+    containing_service=None,
+    input_type=_BATCHGETISSUESREQUEST,
+    output_type=_BATCHGETISSUESRESPONSE,
+    serialized_options=None,
+    create_key=_descriptor._internal_create_key,
+  ),
+  _descriptor.MethodDescriptor(
+    name='SearchIssues',
+    full_name='monorail.v3.Issues.SearchIssues',
+    index=2,
+    containing_service=None,
+    input_type=_SEARCHISSUESREQUEST,
+    output_type=_SEARCHISSUESRESPONSE,
+    serialized_options=None,
+    create_key=_descriptor._internal_create_key,
+  ),
+  _descriptor.MethodDescriptor(
+    name='ListComments',
+    full_name='monorail.v3.Issues.ListComments',
+    index=3,
+    containing_service=None,
+    input_type=_LISTCOMMENTSREQUEST,
+    output_type=_LISTCOMMENTSRESPONSE,
+    serialized_options=None,
+    create_key=_descriptor._internal_create_key,
+  ),
+  _descriptor.MethodDescriptor(
+    name='ModifyIssues',
+    full_name='monorail.v3.Issues.ModifyIssues',
+    index=4,
+    containing_service=None,
+    input_type=_MODIFYISSUESREQUEST,
+    output_type=_MODIFYISSUESRESPONSE,
+    serialized_options=None,
+    create_key=_descriptor._internal_create_key,
+  ),
+  _descriptor.MethodDescriptor(
+    name='ModifyIssueApprovalValues',
+    full_name='monorail.v3.Issues.ModifyIssueApprovalValues',
+    index=5,
+    containing_service=None,
+    input_type=_MODIFYISSUEAPPROVALVALUESREQUEST,
+    output_type=_MODIFYISSUEAPPROVALVALUESRESPONSE,
+    serialized_options=None,
+    create_key=_descriptor._internal_create_key,
+  ),
+  _descriptor.MethodDescriptor(
+    name='ListApprovalValues',
+    full_name='monorail.v3.Issues.ListApprovalValues',
+    index=6,
+    containing_service=None,
+    input_type=_LISTAPPROVALVALUESREQUEST,
+    output_type=_LISTAPPROVALVALUESRESPONSE,
+    serialized_options=None,
+    create_key=_descriptor._internal_create_key,
+  ),
+  _descriptor.MethodDescriptor(
+    name='ModifyCommentState',
+    full_name='monorail.v3.Issues.ModifyCommentState',
+    index=7,
+    containing_service=None,
+    input_type=_MODIFYCOMMENTSTATEREQUEST,
+    output_type=_MODIFYCOMMENTSTATERESPONSE,
+    serialized_options=None,
+    create_key=_descriptor._internal_create_key,
+  ),
+  _descriptor.MethodDescriptor(
+    name='MakeIssueFromTemplate',
+    full_name='monorail.v3.Issues.MakeIssueFromTemplate',
+    index=8,
+    containing_service=None,
+    input_type=_MAKEISSUEFROMTEMPLATEREQUEST,
+    output_type=api_dot_v3_dot_api__proto_dot_issue__objects__pb2._ISSUE,
+    serialized_options=None,
+    create_key=_descriptor._internal_create_key,
+  ),
+  _descriptor.MethodDescriptor(
+    name='MakeIssue',
+    full_name='monorail.v3.Issues.MakeIssue',
+    index=9,
+    containing_service=None,
+    input_type=_MAKEISSUEREQUEST,
+    output_type=api_dot_v3_dot_api__proto_dot_issue__objects__pb2._ISSUE,
+    serialized_options=None,
+    create_key=_descriptor._internal_create_key,
+  ),
+])
+_sym_db.RegisterServiceDescriptor(_ISSUES)
+
+DESCRIPTOR.services_by_name['Issues'] = _ISSUES
+
+# @@protoc_insertion_point(module_scope)
diff --git a/api/v3/api_proto/issues_prpc_pb2.py b/api/v3/api_proto/issues_prpc_pb2.py
new file mode 100644
index 0000000..8bf1235
--- /dev/null
+++ b/api/v3/api_proto/issues_prpc_pb2.py
@@ -0,0 +1,822 @@
+# Generated by the pRPC protocol buffer compiler plugin.  DO NOT EDIT!
+# source: api/v3/api_proto/issues.proto
+
+import base64
+import zlib
+
+from google.protobuf import descriptor_pb2
+
+# Includes description of the api/v3/api_proto/issues.proto and all of its transitive
+# dependencies. Includes source code info.
+FILE_DESCRIPTOR_SET = descriptor_pb2.FileDescriptorSet()
+FILE_DESCRIPTOR_SET.ParseFromString(zlib.decompress(base64.b64decode(
+    'eJzsvXt4XNd1H+qZwXMDJA+HFEWNHjwaPfgQAIrUm7TkgABIQiYBBgAty7k2MJg5AMYazCBzBo'
+    'Tg2F/sNHHqNI/ajWXZja34rdhJHMvOo75129RxYyeOk/S6Sb86Td2kTnrj2nXsvD7VTe/6rbX2'
+    'PvvMDClKcdJ+94v+EDHr7LPO3muvvV577bXNb/5SxtxY2qgevXTXUfpncaPZaDWOVuN4M4rH+E'
+    'd+aL1RbzRL1drYpbsK4WqjsVqLjvKj5c2VoyvVqFZZXC/Fj0nzwgFtAaTycDlaK12qNpra4Dqv'
+    'QTOKG5vNcqSPbu3ek8XG8qujcks7VDxldp2JWtN4Mhd9N3W0lT9qeuql9Wh/JswcGjx1/bPj15'
+    'g9hGSs3FzeXB0rN9aPcvMvjWfnuGHxe8w1p0qt8ppFFFtMx03fRqkZ1VuKq/Ds+LXmmjSuC80G'
+    '+jOnLfNjphdI4/3ZMEev7L/c5+ekWXHS7Gv/eLzRqMdR/ojpE9rT13OHho7nxzzijwkSbVH8ZM'
+    'bsmY9KzfJaegQPmIEN6aBgGTx14+XHAIq45vm9ppdwNLdpIDT2OfmRv94MbpRWo8W4+ppof46e'
+    '9NIrBJin3/kbjeGHrcZjUX1/D7/HzRcAyF9nBhrNStRcXN7e38sP+/n3qe3iq83edPefPw3yt5'
+    'td9ejx1qLXBen6DoAv2G4U30W0OleNWxON9XWaMUeru9pm+4qcY6c7RY/sFemRa6fHPtO3Uq21'
+    'oqaSSn8VN8zedP+UGHeagbLClBx7U+TQF+Zcq6smyZwJxlutUnkNr13cqDVKlfwBM0D9ibyllG'
+    'MGsUAaXX+5UW+BYEA6LM8trPhDPcYwuSajWquUHzW9PE+Mq+tEyvvSKv8dZmhzo1JqRSxO+AND'
+    'xwtjIi7GrMQZOw2hcp5ayMtG3gEgf68x5XK82IzWG5fAqmD+a58d32vy6Sm9GEfNuUFqOsct8z'
+    'Nm/3KtUX4sqiw26ovCWxZLD1P9mi5MGK3MXaOvzdYtEzO+l5p9/KBaX23D1nslbHvtSylk58xu'
+    '6jOxA6bX4unjsR14dvwGU0iPbcK2nSSMQfKmYrvF7KiVlqOaw9QPTHPDAtRGE2a3iO5LpaThAH'
+    'f92lTXeS5eVqpR/3et6N+KpPjOrNkxvkHTRkiEH8bNzpICgNkxRiGF074jaHeU/J/fBh45ZQJB'
+    'GTWvllN2uReuRJ/e50mfPyCZdL5Rqa5sp+X3UdNXAbnsgr+2k1mYnHPaLH+/Gao3WoRnsbW9IR'
+    'JpZ9tbM/x8gR7Pmbr7O3/Q7FK5sWiXtUisnQqeEGj+PtO/ySIi1vVwY3rK2gTJnG1Nunpveowv'
+    'QNH9s4wJPSQp/vD1dopq3Xnq75pwxTVz8xW6rsSYMLvS6+LKgxB+2plaGHHxgrkOGqQ7dV6Ini'
+    'uWTKEbxm9np78/Y64T+qgim2/ROrW9Ppay6rpZMfqWs+vyd5veGCh0Mm/qZCudGfmQNC6eM4Vu'
+    'vdCRjkHpMVzlVXcVbBsV35E1N5wvPRbx5043G+sL0fpGzRvXfWagpaBkPvabfemxubdc4/y02W'
+    'v/Fr2yyJysYvCygiJvX/J084LZ71C5edQVlHvOFbTPvpsCx/nQDFWiuNysbrSqDWsO+qDib2ZM'
+    '4IhjCXJPG3s+h8FqDbFD1r7IXs6+sKZFW79yHf1qlwU9Vy0Ljkwbkzwh4/DamdmF6dOPLi48em'
+    'Fq8eLM/IWpienT01OTwYvyg6Z36vz49Lkgk99jds3MLnLT6YnxhenZmSB7/Il+0yeCMv+QGbDu'
+    'Qf6GVDfafJ9Cl2EXX5T/LrMz7WPki6l2Xb2fwi1XbCPrgZBfNMO+6Z4PU691cUoKN1+hhY/WN4'
+    'Lb0Hax39vQdrOgBa2vg9rQdlHBbWi7KTBC+1orurqI9vzo5TB0lc+Fsatt7r6+avKdwjl/ewc9'
+    'un/v4HO28z/UKRvbPnRZEd72ocsLWfrQy801XaVm/nAaxxUk62XWwneYQfdW/sbu2K6I4VT+FU'
+    'F7aOLhH34iYwaD3uBFwedyQcb8cWZgmH/lj/9eJpxobGw3q6trrfD4ncfvDBfWonBijTpc3VwP'
+    'xzdba41mPBaO12ohN4rDZkSG5qWoMmZCMjnDxkrYWqvGoURHwnKjEoX0cxVGaD2qhMvbYSk8NT'
+    '85Gre2a5EJa9VyRFSkl0qtsFyqh8tRuNLYrFfCap2AUXhuemJqZn4qhCcXNpphqWXCtVZrIz5x'
+    '9GgluhTVGhtk3lo7GvKWAPVR+f5RRR8fXY4rxgwMZIN+Guhe+msgGKS/DgI4MOT+zg28KBiiv4'
+    '/w35lgmP4+zH9ngx309y38dy7YSX+PmZ/ODPTRC3vpxw9kgkxhPJS1FoIk9GUaQ7m2ScI6LBHB'
+    '1iOiXiUO61FUIUqs0GDWS/XSKrlN+t6YOf6K8MiRI7Mz5x4NJ8bPnQubG+U4fGR64Wy4BIW/GZ'
+    '8Iv2f83IWz46+dXxg/dW7qdUvU3EizrSrh32zZlksh6ZqQxD1NUamyPWbMMHpLg9obDAQ7zTe4'
+    '8wNZGsANQTa4q/CfM6H9Bn/ChHNRa7NZj3kemsJm1HHuK6EL50pVIu0JE4bh9MzLxs9NTy6Oz5'
+    '25eH5qZiGsroRLsGuWMPs01PVSC+8SQRrNJmnDGnWI3iM1snh69uLMJF7AZ1jrhZUG0Qxdjx6n'
+    'Fc4tL0zNnZ+enyd1szg5NUNayb5hO9bEl/AO0bqxRd9qNcJL1WgrQUt9DuyYiQw3BH1EhgSSJU'
+    'gQHPAgOYIcCY6a/5FVUCY4SKSaLfxJ9nmQKr5aWsVXIFY420S7UigWBPDgv3KjVqMGZAdgvVgs'
+    'jnzr0IPcJzZ0uY0gWOokf6m+Lcs3Clerl6K6EC0mbM9/KlwP1kqXopBW6DohQy/tpJBrb8dwCA'
+    'uh0YwOd/u6N2cZJn8frd4EkiXINbRaE0iOIPcELzXvtuydDcZozh4u/OMO9hZlTh2FcLKjxSpS'
+    '0RNvROUqOb+V0MYZr2ImtWnIUwGBFfNXQo5I8pKs1mkyqhVvZGCsMRpZ3oOg23tJJiWQHEGOB6'
+    'fND1huzAX38cj+rGNk0ItxaCNrLGpKdbcInpMZlUUuy40Y1pKLJC6FLw7v7GQnh+XbupgT9nWU'
+    'gZy+L0U9SOv7UtTLMbFAvX/fq6Ce4CxT71d726nHyr5KnVZpXiJtVCYh2sLfJL63QjUEmLBRqb'
+    'xGg5pO2IfoMHshLK+V6qv6dr0Rtnnd1JBUAkZICk+CPBXDbf0Hqj+cMmyq0UHfeySyC7OxskL0'
+    'KmEU9UqpWQkvMjruECEplzZJv5KmJPYT6So9M+H6ZgzlEG/WWvYDcbUSjUaEsAy8Mmrop9SwR7'
+    'if66Xt5GXj3g6Tt+OoXtGX4WZUyyUIqjF0vVSLG+FWiehAE8tzrEy6bUCp5Wqd2wKL3+cYzYku'
+    '6yX6EBkMjTorWfoCbJ8alm5Z9exz8Tk4rdqEHkZ0K+aVyUKKOkw98aBuCYjAuqIKgwxNxIaKFK'
+    'uH2a554fy/ThYfGkk3EiUjpPEWRA8tiLOpBdFDC+JsakH00II4ywvi03ZB9AYvpwXxaDBSeOby'
+    'SyJtbV/F0gjZNb8ahk2hdoxrJ15YzWC2HVdalylsCPeKbJClmDApq6BO9jSOP33ujJ+DPdu4k5'
+    'as9vqFcGi4QLPKnkXY2KqDJpVKeJD/PAg8XsB4JIRxt7qmj8eINNRmq9F8DF9s/J/A7e2s8W3k'
+    '+sQ06c78vpnQS9z/cuL+Ax4kS5Cbg3EPkiNG7w8OmR+zyrQvWCbuLwe3Fb51GXXqZlqChs9Xq2'
+    'qfn1u7djeMlbf/FuxjH7NHxz6i4zLRcb8HyRKkENzlQXJEsv7gZvN6S8f+oE50bBAdv5rQkUYT'
+    'zk2NTz5qyJmUZcLxS6Gh1Y1j4fzmxkajCXrI41azVI+rvC6ZrqPh+MTC9MumwtGHwsmpc1MLU5'
+    'Pt4PkL4+cFpg0AlMcCRgMPlpq3hdnJ2UMcuTtqnemj991/732HT4STjfImC7ao2SQfONxag41K'
+    'TnwEKC0XR5R+Ilw9Rbh+Ilw9Rbh+IlyDCfeUtVMHgktEuPOFH+1KNrj7sWO3cIU8cvxiUWdjCS'
+    'zxJBI6dhXjOvbA8xvXAI3rEo3reg+SJciNwQMeJEeQyWDavK9HQYPB92doYPcU3tLToVVkUOFy'
+    'KSapWBWx7Xg7FmYIN9ZKNI4Rt/7oT3CN/jIqvK6sYSZYR6VNIvnlhVJZZhknK0fCZfKmga7dDJ'
+    'M3OZhyFQsfwktcGvEJ0r5RKWRvQEXBiL4gYk7FsjP+rNATHtiKEmeCSSLex0YjpiYiowTNpWqj'
+    'xhpatuzD5mYNegoRl5iUSq0ic98pd8hste4MCxo1bxMfDQO6vPSBjgprpTKpqHhzhTRsFXyWOI'
+    'Pg0t2WS4i5iE/6gl0eKAvQblokCSgH0EhwzHwnmTAvCn4oE7wo+DGJvSwkNCING8fkn7B8USfc'
+    'hoOpvzPR4yQGL9EqKC3XSMqUVk+Ex43ZAZTUD0I6EFxr7uOfiI/8MBj4RzPB0cIt/BUM2zqrMh'
+    'ckS/XTPCZ9kUwqfnXAA2UAGgx2eKAcQEGw2+x1oAGA8vjmmA8deGog+BFQacrs86A/MUCd/FHA'
+    'R8wjbP79E9Dln4IuZ56LLulY+WWoc5dQJyOoB4Ibzbsy/BvkeQJjfBvZnIU3Zfhrvg8somqLzJ'
+    '41No04IrEatax/H06vuPU14mk6AwIjbOaIHPuWdRLsYC/GBjqgU7FodBYyOgtPJLOQ0Vl4ArOw'
+    'ywPlAMoHe5jeGZ2FJxAto6Gd8aGYBYL10CycV2gmeDs+8Q4Q4QGmgXbZYwwSd49X1zfXMbBjd9'
+    '5po53NqNWsRhxBdb0h2gpCH9QL0BCxSQLiz+aJVxNQDqACCem9DjQA0A3o3BkfimG8Q5jp5ewh'
+    'PAWmeQ+Y5qwyjXibL5hrdE2BPE+Ba24y9/BPMM27MbwbCrdax5lnEOZoIl3SK4pfI7q8O6GLgP'
+    'oAGgoCD5QBaLfSJavT+26hy0X2fT6AsX4eY516rgXi7/lcZqD3ykARifgABnq9Oc0/MdCn0d8P'
+    'ZYLRwnEnPGLwgO4Lsnx360OjRS74tdvioZE/nYw8p4z9dMIROR350+CIggfKAXQjEX+vAw0AdA'
+    'C9GvGh4IifEsbe50FZvHwI8CPmq336IBN8Et3ZU/hiHw9L4lsx8TLNIVgbgY4SPB9Sf/XN9WXS'
+    'BTTolWZENGMS1iuqu+2go8c3iOVUM4QXahHpfXoWuXg/2S/xWFl3I8YazdWjG0ftT039PCq4Wt'
+    'UNMdARWDSk5KkrNab6WkMiwqn+sh8FVT5RqmOXAg/h7bKGXo6IFysR9a1cElM9vHQX/r9SutRo'
+    'SqCD+x+XGxvAtgm3yqgcW4tiSC6xZWhYp+mVcq3KbuvWGhRsRPp5M65tQ19WQDqvByOk5Q82Wa'
+    'JeqlZ0eRhaLBv8Jfp2LVotlbf917bD6UkWPOfV1HP926YZeVwN6dNsb5wID9GbizAgPENoRPAs'
+    'CnUOywuHjo2EB7HxI4Q+SL8O2kfH6cfshgva4lk1PoGNGNfkLtsERip8WN4LWo+8tuIEnyCYfe'
+    'lu/6VmpL5Bx3v6JPXqPf6rZAc2m93epH/1mXvxXno8Q25RMhS1WuvRlmt0H4GnnYS/RHO1sp00'
+    'fXCl+jhps0qjnvTnftsfNqHU4+nskD5Y3paxuGUM8f/JRIvlVPx/ElpspwfKAbQ7yJs/yCgsG/'
+    'xrvLev8FuinNdVCyVrstqK1lVTYRNjjJvZLSwJ8wEerkRkcsJu5HAfqVo2MhH7JFuv7pTzCOIe'
+    '6w0SptBygpyDmqLsNrEPiNiH1xfdnYjxxknrX5eWG7T6gMO+XW5EzbJ4rgT2pCOCVzzKfg+UAW'
+    'jAk46ImP9rKPRrzJ9Z6uSCz+C9/YU/yITjIWLaIaeejlBXyxGpoYr6Wm6hhku+QliilVerQVzx'
+    'Co2EMEJLVusSedpcjqFgYP6WOFYXPgI/awO7fxzLGmFbp0GNmxAcpCOIn2Nd9zLktu8mdo8EX8'
+    'tiLBHtk5fYmrJD8uiVI3p9Js1N0F2fATft8UBMnX2kR79u6dUT/IZw0+8LN6kARayYOJc8rgi9'
+    'b9lIk2o7lmw0BM6gFosObEFkG6+wVDs4epAmmAQ2HCJ+ExOAxMVI9tRieIL8Or00NbY6xmutGY'
+    '+MypI7yC3iBDeWVsLi0jZcq66ylq01tkaYanU0EwwjgOIhGnm06iFa/UaaVrCwfwO02u2BcgCB'
+    't15GP3qCfwsD49/DwDj9nMbUVVgYaoDjy/8WXH0Dm1I9bGF84fmaUj1qUHwhMSh61JT6QmJK9a'
+    'hB8YXElOpRg+ILYkr9k4zCMsHvAtWNhTdgFekCEptGrdwYvF+KdduIWyx1LBPkeesCgUxJzHmI'
+    'BtLyiJSxjwA24fBi+8qKvRFCZP5uMnE9KjJ/FxO33wPlALqeKHqR5/r3MHF/elWWoZ+2c5l5u0'
+    'fmDUHJ3xPL8Az/xLz9Pjr3JViGd13GrcS3nGlYo6+5PT0dZ6/agb+fjLNXp+33EwenV6ft9xMH'
+    'p1ftwN8Hz37J2oG9iR34n2DvTbAdaKFsB35J7EBWL7089V/+/7V66VVO+nKiXnqVk76cqJde5a'
+    'Qve+qll5XvV563evG56u9SvaS/C/VivASCq1UvvaqOv5LmSDDKVxL10qvq+CuiXv7a0isXfB3v'
+    '7S38twxbyo3NJlna1Va1VKMhi0egYoFDWY062c1qXFsgSz634RojoFdjjYS9HBtDKLsAotrk0e'
+    'MlBD3DJRfmfzAs2n3/JGpacmmjK/HRY8UlUiMzjZbmL5E8WtlsMpFrjdUqkQxhOZxkKDWrMTaM'
+    'NiJSjgizQnzFNtqdUA6K+etpykExfz29lnNCJ6xlyKy+4M8hs/6HL7Mur2z8ab6irsEGwJ+Lrn'
+    'mAf0Jm/SU6FxYO8WeSBAPwcDplwm4n7Lavkr75y0Tf9Km++Uvom7wHygC0h+RkAsoBdFNwQPRN'
+    'H/PSs/9n6Zs+lRLPJnPXp1Li2UTf9KmUeFb0zSsJ1B/8L8zd92Vp7s6H4+SuuBMJsgOI0wjCud'
+    'ZnoKn0fLUr2wvYi/hfmMP9Zhf/xBy+PkudHOce9av+YNCAB8oAZA2cfp0GAkG6JaABgPYF38Fq'
+    'ot9XEwTfHzzkPpoJ3oAvvMS9C2IxqN8DcasBZfR+JRaB8io1+jWc9Qb04yH30Yz96BvQmRebEs'
+    'EHgjdmia7/DHT9zvBsA3F0b4eWPDO738C2KXnzXdJpL0PbB4S22A95I/qbN/85w785VoxB/Ug2'
+    'OFr4XCYJBB6MbTqc4y9rE5MorSMJOx1NXiYvX9NCxsIlWUyCgO3+Um2rtB1zsiQpL44dhrrvyv'
+    '4lQvyjyyV8YpIk4iXZr9bcJkG3xJ4xfnacCFoyTidWV0nwudjkgK7aH05mbsBGtUGJHR4oB5CN'
+    'ag/YqDYmk6gzxpM34HMMQRG//lC/PsgE78FX3gtSvrWfSckWEIJIbl/EDkU23S3BsCcTbUSJG4'
+    'KMTOzFu/Z2eCTOo7ooQLw106iPNtvePBTB21ji2MTS4US+tITSOjfymMEc2IqqrAbsZn6jmd64'
+    'L1Ws1vX2+JckI4BsnQi6pLMry6XyY3iJUFchBFZKyL5he2Yk3RFN7rExOLBWqjnPferb8oJG4l'
+    'OdNbwPqSMBlaZJ4idMA2d0BGE1uGsjLCdVyyIpglhwLDwt3bekczqiRAoIsTLiVekVAnbUx+qo'
+    'S1eUoTICUrn4+rz/Aj84oVvdTbbxyJDizSqYAckptmTnKqZVZLT/sgaWeIeRBlFtOcaYm7pwbn'
+    'xiahJfPO91ST8ouS6XFHOal0ik1Gukd93X7MfIVnCfWuJ3l1LpJKUaZ+rqXttYOF23adFxJFsj'
+    'yXg6Vyiai4G3RX4yubMVUh7Iaomcxdv01nN46BFLkejxcrRh062ScyvO5rn/2F330Zha1RooxD'
+    'ry8ahy2JMJGZIJvFqv90C8gG8IDnqgHEBHgjucTIA0J9AIlnkiE5xAf6/IhCWFZ4On8ZGfygan'
+    'C7vDifJBNYUhs8bM8QesCHTAxDBpX0q+m+UNBAEO/oYP6gXIhtkH1Jh9GpLsBg+UA+hAELqxYU'
+    'ufQDejv1M+lMPsGNmkOarQXPBhfPVY4abwlDvPm9pDkjG67+Woox9OdzRHk/BhdHSPB8oAtJdo'
+    'mID4W0eDO82ognqCnwGmo4Ub5ONYOVf4dI99wQf1AeR/GpGKn8Gnj3igHECjNNN3KKg3+Agw/V'
+    'w2mClcG7oTwN0/3Esf/kj6w72CwZ8cuNofweTc4oFyAN1OvLjXgQYAOoRPn/ehmJyfw+ScM7cq'
+    'tC94Bl+9tbAnPMeHjbv2ro9690y6d33Uu2fSvYNR/Qx6d8AD5QAqUod/0doR/cEvANVI4f0Zb9'
+    'H7HyZbcdsubJYSlWpF03s8KWKlhLELP2RtRvJyu7GpEouEmScnqzbFRELuvIpo1UO6QkCq3CHx'
+    'qkIPisyjAmlv6boP6gNoSC1HAWUA2ucJB2TE/IIIh/8IKgwG/woG3OdhwJElNZHYbpIH2UD6Sy'
+    'rlbAwHUCqy8XGZY0ikYAw36HYUhw2A2YWpExKggOCmj1ZqGt/0p6FSjcubsRqOydlEELTG+0ZE'
+    'rStvFSLf4V/BZLrGLPNP2I6fAuFuIXsVHUinQ25FLh9RtPZY4qmKFu5qHiLJ0Ub9BtV446/s8k'
+    'AZgILgJg+UA+jmoMhpFoMs8D6N1/4NxMSDHdaVO7GTNim6W1n2K1Aan06UxqAqjU8nSmNQlcan'
+    'E6UxqErj01Aa/8YqjUFfafwbURpnFZ4NPoOP/Fo26CncTT3Xk0luGxZqwB2T9+mcWuGDqhw+k/'
+    'D2oCqHzyQrfFCVw2ewwoseKAfQbcHtbgxQDp/B6QXqV86HQv58FiM4bd6TUXAu+JxIg7NpYcDL'
+    'vqHJ4rWGxD3a2EJngbze1VWyDTTTp3jGywlr0/Z3Hnvg8AlORU1Ox6sjytncVi7QD484UEifSx'
+    'MHCulzycIfVIX0uWThD6pC+pxM8PeSAH5R8AWs+y9i3Td4IeBYKybKT9OFO8wSwWYw6fjGcKoj'
+    'Ct1gKo3yURixq5vkZh2N1nE6br1yhWyFXgl5Z4NeWiIn+SdW5u9gZDcVjnCHUpY0wpk2VsluXR'
+    '2OHROm16X88OvXeaAsQDcEN5r7FZQJ/j3aDBcOIgjAHU0PWMepoQ0Pf8a+2++BsgCZYMg8qKBs'
+    '8B+yHKYdISdHMHcgVhrCMOcsUe8j6CAj2O2BGCcCAghCmeBLmLevZa8mcO772lcOnBv68JeyHD'
+    'j/D1n+jen4I3Tl5sJnsomH7Xv30BCkIF38VxNu0u1c+o1m1UnMWHwIzv9GxKUet4iP6ux13nMn'
+    'FgVno5Jnc7C6vlEqu2y8+CAS20zXKEJ4cLq9LZviamJhFbPXxFthR+3aImchKe0Cj8HWWFniZL'
+    '+ldeJqeOrTosujepmEcIsj6DQCXucjPIIq9txi0v/MTHWc34nXaXoJytlYLohlNEz3R8kyNqoz'
+    '/ihZxkZ5+o+wjG/wQDmAYACfV1Am+K9ZjtK9+PLrGNKXU+Bt/n3HgnZfgL5ghMMeiL+xQ+NrRv'
+    'UFgRBf+24FZYOvSD+W/KilZKH4WZDWnAEL8dEFYZhq3Z1hOF96PKxF9dUWnoX3HDuONK61UhOT'
+    '2/TJyBHwJKZmdAl9JetigUb1wlekr48oKBd8Fa8d0l1DPxqInlqNantYbj9t0d5l9y3I56+mJx'
+    'by+auY2Os9UAagG9R4NiqfvyrGM3Y2h4JvYp3/RfaqdjavYqFrktgQffmbWY42384/sc7/PMs7'
+    'm9fwdzS6k86NGlKe/fNkaEPKs3+edVuZQ8qzBLJbmUPKswTCViaMsOHgWQzt+3I0tLnnIcLSNu'
+    'Zlhnm3DHMYEWEM8xCHf4d5mN8S9+JcF7uvQ6q1JRFKuncXYcaDHFbqfCuhzrBS51ugzn4PlAHo'
+    'OvVLhpU63xK/5LyCMsFff7tW9LCu6L9OVvSwrui/Tlb0sK7ov5ZVsqmgbPCGHPejwv1Y0kN1S9'
+    'aes4eQ5GhSJVkeajTAUhXKLV3tqh7WVc0fHvBAGYAGvf5iERMI/X01gXYEb8wRV70JXPWK57Ng'
+    'uhZAuPIi2oGQdI6467AZ45/grn+IHo8Wbkwtonae5d7vUHbhN3xQH0CWXXYouxDoOmLjBJQD6A'
+    '4yvV9BoJ3Bj2DYb8GwH77yYupSxuHKw9xJX/8RDPNmM8M/McwfQ6ffnKORnrjq3fS2Iz1KBsZH'
+    'U/1jyVTv1DH/WM5txO3UMRPIbqoLaACgvejKiA+FYf+Pc25T3ULZZXlzjjfV/y+C7wqeAN3eDr'
+    'qdu6rNvOdDuV3IfAbliuY7+Cco96QwyFHROO2nnK68tbdLWebJhGV2Kcs8mbDMLiXfkwnL7FLy'
+    'PSks810ECoJ3Yujvw9BfenXy16/IceWdsIA+/07hmQn+iZE/hV6/O8eDT3mGlnGcreAO6sXyKR'
+    '5DoIzyVMIogY70qZxL8g90pASy2yGBMspT4J13W0YJEkb5yZzLwg18Rnm3MMotCs8E782xY5IP'
+    '59zpkPYeQspysz0eiN/cyz6JBeUAgk8CNtwdPI25+PDVseFVT4ay4W4EUIUNH+SfmIwP5dRB8e'
+    'WUnYDSCkxbmQN4of4gdyvDMYJBD5QByKhvvlun4UM59llgcOWDj2CQn8xd8ZzEZYJFV/Ja8ohB'
+    'Yny3mtfwT4zvGXTv4zSBhZXuzGZLU3GQJ5aPY3uP47H1tNsZy2Fx8RmSo1EMlANTSpy88ugzCY'
+    '/mlTjP5NwOb16J84wQZ68DDQC0D93O+VDw6MdyQV/wsJlTaCb4BXzidj2JowcVqziJRb4Jn+Cl'
+    'wVUvRbqDFS7Z8Y7pppwO0Os4WJexDnsg/tAOPhVkQTmAbg1uM1/NKCwbfALv3Vv4Yip2aDfSOF'
+    'qiiSIJ8VI9tJPhehU+soYaNKnCYWx4rTVqFd81rNblMBdnUrM3Jgd+OVvFyPnL2E2dF07RzWGZ'
+    'YD9lR6287puyeQ1NfSIRwQLqA8iK4LwaK5+ACD7mgXIA3R3cw0ZengH/HJiuUyOv2n5MbsT1Iy'
+    '5d4l08ns6VajPJT/O6hpSWf55mPngZ/xzMt9cD8Wevpd6+lEB7gl/OaX2iwsnnXplXXo176HO/'
+    'nOMkiGn+ybFWdOnTEP33dJgL9vxSYjo4D1GjbDq+Pbq4PpWMb48urk8llsIeXVyfSiyFPbq4Po'
+    'X19mmrAPYki+tXEgVgoawAPi0K4A6FZ4JfxafzhYI3V7IZrrav11OsJm7e74EYg92536Or6VdF'
+    'VR1WUDb4rHDE/u4c4X0DJvJn09RALz+bzPYe5brPymyfVVAu+HUx6e+7smshh/+TuFXaq9ijbu'
+    '2vJ0JjjzLcr+ecV7FHGe7X2Upf7uOTnHeZjz5onrNKeH5XW8XW4s1m0BVtRR3qjVJrTetXz8mP'
+    'Uz+cMXtoYbQXez210714AaALmVc8oE1WGzWSWXzEZTWqS0XxLn06mfz5V5nMe7K5MxdOfSh70x'
+    'lBcsEWlX0kqtVeWm9s1VHoL374l0+aweAmWl5/lgky5nPD5PjexOW/fnk45HfKjVp4ahOSOw5H'
+    'Q8F2MA5JI5dIuJEiVjdKxJxJ1Qy78359IZyul8fCy5QKu3IFrw3txOiydOIoJ3RUqkg2X96U+C'
+    '+JTehI0ouqSQHBVnpzm/uFozPY4IbNL4WxTMotlUwFPozKB/C9rMaSrPyVBgQ1H2dq1EW46wlh'
+    'EucnjJytPdLWMd5S8IufaZEVl4YhaadlSzHDHF6OVFHYDBf/i/VKW3ew+1QrVdcjObLbrRP0MY'
+    '8WthM0xspmOUr6YZKO/I36YewZ5YqeJi/ZSTqKJE7WYcQpUbMKNetIbXMyTOj33g1qRpNoWp54'
+    '9nmLlKB7FkumcWw454VRIc0S6cVIDNKNwaheISjnmVAn1pG4KTQh7qwgT0rzcY0tYrfS2gKb2N'
+    'JqLnl0o1kFYzXBO3XvSDP2EBfOTs+H87OnFx4Zn5sK6e8Lc7Mvm56cmgxPPUoPp8KJ2QuPzk2f'
+    'ObsQnp09Nzk1Nx+Oz0wSdGZhbvrUxYXZuXkTFsfn6dUiPxmfeTScevmFuan5+XB2Lpw+f+EcTl'
+    'gT+rnxmYXpqfmRcHpm4tzFyemZMyMhYcA2pgnPTZ+fRv2DhdkR/mzne+Hs6fD81NzEWfo5fmr6'
+    '3PTCo/zB09MLM/jY6dk5E46HF8bnFqYnLp4bnwsvXJy7MDs/FWJkk9PzE+fGp89PTY7R9+mb4d'
+    'TLcOR9/iwq16UGasLZR2am5tB7f5jhqSnqJerY4VM8zsnpuamJBQwo+WuCiEcdPDdiQq5JSn8R'
+    'PaZoOONzj44o0vmp77xIrehhODl+fvwMje7Qc1GFJmbi4twUH9QnUsxfPDW/ML1wcWEqPDM7O8'
+    'nEnp+ae9n0xNT8yfDc7DwT7OL8FHVkcnxhnD9NOIhc9Jz+PnVxfpoJNz2zMDU3d/ECqqMepll+'
+    'hChDvRyndyeZwrMzGC14ZWp27lGgBR14BkbCR85OEXwORGVqjYMM80S1iQW/GX2QiEhDSsYZzk'
+    'ydOTd9ZmpmYgqPZ4Hmken5qcM0YdPzaDDNHyYeoI9e5FFjoqhfRv72WHeE5zOcPh2OT75sGj3X'
+    '1sQB89PKLky2ibNK8zGpsRiSNtnPNRaL9NdJrrF4m/4N6C301whDM/o3oLfSX0cZav/GX7fRX0'
+    'WGGv0b0Nvpr5sZeqv+DehB+uuCVnSUvwE9RH8dYOgB/fvjIwMoHvCnGdWBhfeOhEtOFy+xpIxi'
+    'zkMpSeYgyfTt9eVGjZa+2Ois2EcksCiZ41Yj8JMTYXFlrFRshyyPVWgs4VlsMy2ttH1IM0Oh0E'
+    'jWNRsNZ+xS70qaqra8ZNrKQiQGsd2DX1mS4kdLyG9TtH5NTePe4dZjy6imJjvLIUwJyRSzMlNk'
+    'Hsp2cu6zn4CZ3jwx7viFVPnEIX9JeOfUMvXeq/q4ri5+0mSsrQ/Yg9MSGWXSX4318OF5WjfY6G'
+    'LP6RCOBsMi30IqWniLvn2e36aR6flqKC97IMLm+kpop847QA2uqujaEuk6Qx1IG6HRjzq62bMG'
+    'MhigG78wLYnFtW0v7bIBN16LKyHZ0S9gwJMHw9I/fzBiy/bgiW1lz4YYd0qhGmuuieaGd/S4FK'
+    'uiji1froTfYysulsIT4fHj9tdy8iAMK/ToWPLzcbS0P19n/9hGo7uMD3zNifB+Y2ygCGEKV8fO'
+    'UsMpT69+ktLm8ZFt5tjXkDCTjM90aiXYTqMG6o4Lh2uOPujENiPv6ZmQVPXGZuswjfwFD/11dn'
+    'RQgOnkwfY6SpI+GarVWCuR5bTRkKpFzFxGBIAeleQE2hWbcwVmDeWSIItWJYIUDUutoRFZvMma'
+    'sulGUqTMJSzT3FfbvrDiNSDru1SRU+2OHw9L9rM9yGKlBjOhq1dTj8rgrmaVWByf3tY5QUrVxm'
+    'gNpnwHI3JaKhJYZZ2RDbPQdVDe9+yoKlWtNqkzb1FLXZ5qO1qjRmsKsV1GcSuiMXv0ks1SMm7J'
+    'Et0s1Wx3nSHrRJm96ImxdxuNyiYJ8ZBlYCM2K3xuCZazZ4SOQQ41o6RrtksuT69ci0pNGrS1pH'
+    'mFE8U4sRf2cpVtVTK8lQm0wyR/yNuarhupUudShm29u8sMy11kFYP/bOkjE9qrr3iVEu64q4iV'
+    'wn3hrKV5zMvFm8tqp5hPyrjEuhuTSsklZio1abw+6aGOVhsarvPy/GW/lARvqutOCouTimGrxN'
+    'HwjhPI3eWxRFZpGvSUEE8oEntajc3yGmdar4jIld4h57xkkwFbDaORkWV52waybVDP6gqJ4cVt'
+    '3aNV6vS7yM5LvpJhdWEFSKdUSoajtenaaT+SpK0Ly5m29DzNB9f0+fYvCI1kftz4xxK5Ybr2ii'
+    'fDCkvF0SklcVKLpORSu5hsU7sdM9aByc2wcKBQwnQhRQsWQWcaP+fVAH07NbyemMvRwpi0MpfT'
+    '115LRdBFKad1UVoLd1PC5RPhdx17paemsB3gpvx5fOfOrqiPe6iZUtUO0lbFtlDz9rtg3xZHYO'
+    'aWi6+0b7XaDILl59elK419JDyeGn5StU4EI8Suhhpo2kX0QDbQimq6o7R2U8WXdyZhYq4hag1s'
+    'rTNn+KSFVDJocFDLKcyDNhGw3VjxKMfnapcjX+JIjZIOGycp2q1BaYmaJMxGjkQdIZtqZ38Sc6'
+    'DhC6sRR5GS/Yph9VhCndKGrRjphdTasHm95jS0SsOo1WLRyoBiJwUlar7lFnSKgVIWT8Nyr9Cr'
+    'm51juho6HsqUpUOGTtxp6Hh2TmJfx5ESICYRv14Ko0uN2qa/g1NCpZZSXeWcc4AiLeaTWEmP1R'
+    'tbTLk12RhpSlkLLoXKZ/VYtBiltGxqJOfXEy5ITniNucOpnF+K/GRfRY/4p5rT5+pZJcJ/kj6y'
+    'nciZ4erUmRTxRjQYJttSkkIoZQCxnxOO69nClPmmGq3WKJd8AexYiDV8ohLjLjqxrb6hW2+VCG'
+    'rJFvf17d7HyHBjU6y7qdNFUCVVHdWEWGvIcYjEe2MD55ZwQjMm1VaB+ji7sHBBTExxcPg3+sDc'
+    '1qll1azZjKM4zZxu7essXxhfmDjrrFOUBLu4kFrMMeGLyffmL8bEl/VWtUyDOYSGHNRl5WojnO'
+    'xRbRIrSY9i9YvZbZ6ybrM9yiBWnEgztBjpWJvsacsGX0nP6bmCWWzFil/DIfGk7gv7/lwMxh38'
+    '4+AtjYuNbrzEFh35hSSKW2KyHeU8E9gIzdEyNa/hJY29X8IBWSkXCSYEa1r96vJb05HphIWcne'
+    'z8YPvsQrPBd4o41YMb86Rm5YPhsZMWemENfuUG///B8PjJlNdrcfGrDpHSqFKNN2ql7UUevY9S'
+    'n5cqFZQ7S2Hl2RBXttRuxSxpj5d4cdcaDRazMdmkbmho6rphw04Y0pjfmWJ7Cx5dMdUH4YiWPQ'
+    'hjWcIFrIQtWLLbr7sPo3Hbd2cIyUj7Z9LuBETmbD2y7Bm3haRa2PfztFADTePw1XzcBR1b3aSp'
+    'tkraLWP1Lts5o50Z5pmfzusvNxL+SEgrqbUof3p2is6izu7dJ5Mn85vLFhPZjIv2Gw+GD5xss2'
+    'RepxLFm2nJ5LzyjHqTCAyzzSs39zrhvZVoQR0ldlxlK+VQMRlyUcRzNWZBy+ef7a0F4vJoeEPF'
+    'ZzKj4ctQFU1323ScbRZaQ4rjwh3SDAmRnGsscZLwBXfN90GMUxYafZQKbLJhb4v1pgK1kmbBdW'
+    'bgDSy1F+pdStQdusRCirUtSu0ho2AsqQ/7p1L35eiArQ/7TSnAdJNWtklCpDIhlj677QsDPfJK'
+    'ulTsN5NajvbcyDeT6p62VOw3ufSS27N+fcE8173VeaNbq9Sg8xpsu41vWx/5HrODKX9KkeRvMo'
+    'XT01PnJhdPTZ0df9n07Fzb3XDDZmCWdzrGcT0c/Zqb+s6L03P0LJvfZYZmLy6QtlrEnUZBLr/T'
+    'mOkZ97snv8MMTp8/f5EvMwp6TyyZnekh5G/sfkXqrBj0+38Cl7vuPH7dWDLGsVT353as+D9PbZ'
+    'id3lY8NT+VT7W3O/HjV9iJl0f0bsxE5xUhSuak9/d7sj1niLUf/nf7zECwK3hRMBNkzL9Amvku'
+    '3m3/WE9q4/zYA3Zz89y5Caymc3J3VYWY0Gq48Q3SoJF9MoIlxrfqHB+7kwOnYVEfFQ+TqMGxEK'
+    'gKrNdNr3COPfHNMbQGLckqW//uhLziIEv5UcXQWJZSnrxdbA0sbYYbuUSqYFP/xNGjW1tbRFd0'
+    'lKnmruDSm7xGqbP0wkU5I+/MsmWJKZK8QPpOrbTFRb5Xm5FWyajzbiuXMrLbst62cYpKtmPVON'
+    'WAN6Tdxuqp8fnp+RHDl2thJ8zfFeUNxclp8DRv+WHH7KXTM5MjtqyDVirlotvrHIbXg2f+51d0'
+    'e9rZ5mCkTWgCuQ0N2sMrhi11I2rV9aryT+eIiCt4f2038c8eva1sD99Whn2ua+ivl+vul/wN6D'
+    '5v/2yf2z+7lv66S/fa5G/8td/diJbRvwG9zmG41f3dH1yPm9GIob+YG+inbhwicXai8PlcOA5b'
+    'v7qa3N/iOSciE52rfSiptC4Re7Zmicit8thh44L6tOatnmYtMyXGnxp0aTV8LPyuQ54kSMuSw9'
+    'TAyqZXsirmVFW9a+AqXvZEmbzfLpomN9UDaLVQP0pDOM+BNZGH3ZEuVGn0LRoz2I7Itdiqyliv'
+    'CrvX5xE1DJ6jO1YcU2/MMCZ3AIcv+oNb9FcPT7Z91ke/htyzDP26NTiuv3L0657gAfPfsnyw85'
+    'jIwMJ/zHK0pl6BcSAVdMHkji+YaSRyJhxzaMWPqfk7FMYP/YJj1C6QOwIEBxkTyl68b6Dn4BE7'
+    'qGOXiM/9EZYjR6wLf+SIHzt23bL8iOwoTUxCMD6qrZzECXlJ7qxEhEDdXPemvYmmJA4qShDLdm'
+    'GDc3v5lHkUJ16r3Yw5IT2L6pvr1D3CQD3TunF+IFf955VNMm8ivudPTrEeC3qDvDnlDrHexYcm'
+    'j8M2VpeqVHOxJiYxfYhs58lGSmXwnSDJWVZgucGDZAmCU3/vy7ijrPfjgHnhn2TCeV35pRp2NJ'
+    'Q01rHBvGxIL1xybTrS1EFuuI32HS8w4xeVMTKSdSRBVyU+hITdjWZVEsYD79js/czHgXdq9v5g'
+    'R7DTfCrjTs0+yEN5JhNOdvbe8p3lIOVoe6YzCULQ3JGbQpO37IXxStg2aLaSiAkz7ojsPqyUqr'
+    'XNJscRK40QlxEhQiT3VrjQzKHNeJMp22nNHvZGihl5MDVSGRhG+vqsgnLBBOeR/veuI/XE83MO'
+    'tuolePGClF272G7bwTp3haydUV9yFr1dhVzBlI0azhpshkJEXcBYcawwGcqrljA2OfSm5GEnQm'
+    'PPfkzoYOwCfj6ZkCMKGuzwIFmCIBH23ZYh5NK43bhMsAuZWKC8QCpZsaVpdshjawrtKklmuAZM'
+    '/ftIZGq8ccjtX8h+TSC4/WtXEJgP2HH0BuepSVB4a/dxrK9vtsTpeY5h2NUX+Tdd2ZlEEUZOQs'
+    'YeeBKBBhtYs3TZnnCsSKiUj3l4g0ExF/R0yINkCbIz2OVcoLe9O9eZt9vu1nTm7Z43u0+TyJl0'
+    'DeejVv5+0wMDWS+Ev7WL7+G/wb7CHL9R/C89Zk+Xp/m8fxW83vW+3/STffwYGTR8AfjgnP1Jvp'
+    'aRsCdx5zZfZz4450Hyd5jdG5vLZCUves0MNeudC+TBZNL4oNm1FZUe85sOcdOdAHsNJ8ywGlj2'
+    'DnGMPuwYffvIh/Qtvj983AxCfwiG3svQb4patGMZwGuKol/j1/v7GMHBDgTz8rwdh32PhjIYPd'
+    '4iYxm3pfczktu6e5DtKJL38veaft0w2j/AF7Tf0JUR1Aeds43z0yYQJl9E4HQR50D2DzKCA50D'
+    '4YYT1G6ams3tjFO/8/tMn5Tr3z/MHKK/iv+sz+y6GhY7aXp5hRKDPQ8ayDtpIva9QCKOm6E6H0'
+    'gTjshdJU8ZeamTpXpeEEu93OxyXVpsQtIobx59rp6MTdn35vDa3M4o9Ts/aQwHyBYRZN4/cBkq'
+    'cUSzg0oNgZZr+QcSVuu/DKdoLLGD2y6anTajXkc2yJ0Ye86RzelrMrAdTf9n/hbjABwtZvEyOD'
+    'dsgQjlFl5jdqbJg5MPKLLeYi7snZMf+cDkSMiwlOudw5/570gGnOMB3945oynM7eMu3Gd2pAZw'
+    'tZ8uvtZc0xU1McnezTofbCDDABwrn9r/x/2X4bmLfmvBMrdnsxN4ZHDgv/YHr6f/ssVf7jN7u6'
+    '2ZrsuXlr8U0GYi9c7pL1oRvTWUIKPVkDm08/gdV7Uqx7hq2Zy8mX/I9KiIBoYjV4cBa2mO38tf'
+    'bwbxr/BGH/d5AADwRb5gBniZVCKr2txvMJY6G1KZiBmeGEuBfHw5f8AMyaoikyN6nKVn75wstG'
+    'lA8PlXx7SWlTX5EwDw5+9rF9xXjh4ma4lUpVgTi9a92L+bEAzM7RTwrEKLv5A1PSxYdpmhhUcv'
+    'TC1Ozl5E6DKDyCYDTp+bHV8Isu739MzCvXcHOffCRQH0+A3uOh70EsMOC4Lpl09NUou+NITa9C'
+    'NcypBTs7PnggGHE8nhM2eCQYfzzNzsxQuBcRjOT83Pj5+ZCoZci1OPLkzNB8OpbtEndrhPTM1c'
+    'JDsrv9vskE/YTuxqA1FPg6QjgmV3CkAt8sUJ08tsSOy+89z4qalzi17Q2MG80LEHuzA1vkCwXL'
+    'Fs9nYTqF2XkMcL2cvwAuNq54XiH2bNni5KpetHXmJ6hZdFzR7uqp2YsztULb/nmxq5y5gaQNHB'
+    'sK/sEP6iH++9Gv3IsOenBHq7KIGTZncHoqsWxv8gY/ZfjjjPIRKzKZF4sp2CN19+Ejrm+oMZs6'
+    '+7Sdm1Dw+ZPtmo0vnu1F3n+XH7ZOtbvrbPXc4ulN509PRNWXNNV+RdO3qjMeyMiukkkniQISy8'
+    'IGXZb7S2GZ4bAXGD+5OO9nBHb7rMSDsY804TSLYJbo+KeA+fVc3Aid6VUi2O5nbJ43n7FG+Ih+'
+    '+90Zd6Qx67N4pvHjRDngGev9kMv7p0qbRonSqhxBBgF9SxutPs5SY0RvpQuVaKYybaADfN49ks'
+    'Hk3YJ/l7zB5+gwsSb9SiRbh5Masc17PdaHFeG6BHMZmFN/Jrq1Ed6SDRIjnDqKxNfv3iWile27'
+    '8XCE5l92fmrkPDM9puipuN1ytnqVH+hNnHWCS6vVhei8qPLW62Vu7ff73/fe7hPLeZQJOL1CI/'
+    'b4YxGevV11CfG03WoTu7iCaPgmOz+sJ58j9O9M5fmJqanBuyWE5jG86Y1YYj8JAw1GrDkpeIVS'
+    '7LmMk3VWcs3h+kiFUun5EGyuMxrYdrEmL5L+7uGGX7q/TFje3OF/OpL25st792n9m7sbbR+d4R'
+    '/708NWl/8Tb2zPUOuP3X+s29B/kxYv/yYlRH9GQRF8CV4v0HuHFPq7lJXkS5PMUPx/lZ/ojZ3V'
+    'h+dVk4cpHQrFQf338rk3cXHjA/XmBw/jDhjtdKzQ0WyTFNRrT/Nmkq8BkLxoqIt6orLYvxoKwI'
+    'him2QyYAJVIfPsTNdhLc/y4pA7RMPnpYDDcCJl+82+xDIxJ0JZwT9lqPcGuQ/bw+TPWzubm87R'
+    'hrVPoJmGWtvzXjvHjCDPt8nx80wvlkkJARNDE7CfPlFVNki5AZdW56YWpx7uLMwvT5qSDnGfYP'
+    '9wzcHhws/nbW7Ex7avkXm2ttWCWOWotb2LuRI9MsoRz/7NVW81HrEWojN/Plz5kD9caivcF4MQ'
+    'loLZbKOIfREEXosNxQb8xr40RDjGvTNvbNXY59ybpeL20Q/7aa22yfD8wNEGAKv/9O3KSHcXXD'
+    '4MOo/2uKf5Azw769DvenzBorwzLtlita92MTUGUn+sQ4npM3YUaA2SIxRgbm9Ff+jOl7dcy4+x'
+    'h3t9ifh/vheUY++PD84szs3Pnxc3P6ev4601MrvWY7rfQYdLWTQBgQoEurGgb9LS6Go6aX6ZU3'
+    'RikWvCg/YHomZuewIGgFCHTxwvTUBK2J4j2mT4iAxeLIQC/JT8WRsU8vnj81NRdk01PdE/QWY1'
+    'qFnh3+d+OM/6uMGfLsahhEfEJhsVSrlmJlDcOgcUCudur+jpZIb9BXfFfGBO2GbVs3M/87u1n8'
+    'iYzZmbZm27p38//W7n0pa3akbNir7d13m93VSrS+0WgheL7I5972F1lodAYVU18Ym07eO4fXTu'
+    'yZnpw6f2F2YWpm4tHFizMvnZl9ZGYuqLY1+1tc9hdM0N6p/LWmW7doZe8xu2ZmSSeSYpw6fXpq'
+    'YmFe4h6u9UJqgRffkTN7uvSExLh4LOJEjV5N78dgM1wgV1IdHLKF9OKcKtnz4oOLG7MrgUtIac'
+    'Tk5XzQJYTkbfAJbk3PXGCfTNdbrnU9Wi21tYYwz80F9olrTfZLpbEJW0/aQXdk5oYE5pqoFZ9E'
+    'vYbJFGOYNDlodpVWV5tAbhGJX7LTgblh4WEzYOkAVQ1KLG6Is51FIKxuH9JHq/FiEsTP0vOBua'
+    'Fq7AKgxQ+SwZLehCDfZcAm8usO2KHn2LcYO6ft59ybhc9nzIAFk7rtQfojo+s9lQ0yc/wbcLIA'
+    '68wCCsdvzGst4kLji/Z2MTuvCrd3l2EvDMWZa6m2Pdw2sA9c4xPmOosXl0HjHF/yUh8HN67VBp'
+    'P63L5b/O2M2W3dtIoj1nljkmQ/JVcnK3e8NzbuXprzEBTWjUmeXJZspKd0h4m3KcWxNwKCP4fw'
+    'y3K0Wq1r3Fh+2PBLjwu/nPqxy5QkCtqiC/HZzCseWq221jaXuTSPpEMm+6ySojFK/tToasPbdT'
+    '2Z/OlVJiq0Vyaai1Zqcu7+4d/6ctYMctmGH+4PMuZDuwaG+dfflyX6+7JEf1+W6O/LEv19WaK/'
+    'L0v0gssSHf9iNhzfpNXSPBE+RoKgUf+ORLCHh17KoPBlpWaldJjW+Sm+OZGr1lRxhW6tUwFJln'
+    'O4vE3N50v1V9OKPoOTo1ul1kj4cLSyEk5GpbqWKeEKAmN6CMbmMruDNZySnyoa4FePcJV7RElz'
+    'a745Hemf1YoPtmXgyfqpx3LnrV9EA0dqOhKUjJMiOPciMtErYKrXH9o2TTGRuAhFdZ1v3T2sRZ'
+    '8Ou6JPd9BfFzURXf4GdMQr+jTiij6N0l/HNBFd/sZfY/TXfVo2Sv4G9KhX9OmoK/p0p1feSf4G'
+    '9G766wbzelwKNCg/Cq0wsUmUaKyAliWnFGFAOUJJ4pRPLUu6ZdOZKHKhJo4grxJftNbWiQsa9Y'
+    'MtXLj3WFjZ5ET05UajhUtDNjakikGNS03dTz04EWQKr2IOcHmtOH1RxUUcmDhNsmyfpfnIldCx'
+    'd7pL742wAjIhyQeQir/DekTp/mAguM7sdCeUHsCdEibwDiA9oBmhFtJHkKHgJg+SIciB4BYPki'
+    'MI7oE4xkmGD9KYXkFjuiWcdKd7S3KghPw2ny+1Yxl+CVe5vJh/oWMP4f6n4oiwLzTmSKoKL1fB'
+    'SkyXVjOKpNMZHcZDbhgZPUb1EJ+iSiAZguSDfR4kR5DrggLff5fh9NiXoIh28Ua5HK240mhwHY'
+    'NGY2y51CzKgYTkIxn67EtSn8Vlti9JfTbDSPM0BwkkRxCU175PIdngFCdkHwxnrKWgE7ou10LL'
+    '4WQVEF4HkIZ6KtUBJHSeSnUAozrFNxQmkBxBkKm9oJBcMMlXjUyGnFqRFEiRTMKkH9ote4+ozR'
+    'vUGvrLckuW/Q4uGplM9Q4VgCf5Lo4EkiHI7qDoQdCb24i5XqeQnuAMl5Neb+8dAqFX1ze92luM'
+    's1F2ESDL16urtigZUnW9fHdvGD3aAR/SSxB/GFgFZ2gYoQfJEeSW4Da+MjvDE/MwX911mF2OpF'
+    'CSL+J9ReB1AfcdPpzqQi8t04epCwUPkiHI9US3BJIjCK7T2qmQvuClKPDvWuCmwpem8PZxG7v8'
+    'BZIhyAEVGgLJEQS1+i3e/uBcgBvYbAvc/XcuhRdX/50jvDd7kAxBisFhD5IjyAj1z+Id4JTfBO'
+    '8A4T2fwjtAeM8T3gMeBInCoYd3gPCeT+EdDGZxv55rMUh4Z1N4BwnvLOHd50EyBLnWo8wg4Z3l'
+    'u/H+MqMgE1zk+zP/KCPp0pIbrUI7OeWQ0qu0bDbFoXM2huefeWfv4tJKVNu2VzHK5U71Fuo6yG'
+    'esal4rNfnAfBM3teKK25XNelk+XG25Q3uJCiQfelQPzCa9QoI5Kb+oooUF+EQL+8J6DUKjFnsc'
+    'aoiCF1MUNETBi0TB6zxIhiCF4IgHyREE14AuKWQoeJSvMbggJ2g51dUrxu3UiDze3NC1791rW+'
+    'Rmx4tsb8mPu4peT4eop4+mejpES/PRlMzEFUSPkszc70FyBLmehPZhzqJ/Jam815HKuz6l8uwB'
+    'b2z/qKqD+H0lqbprmfmyrOpexVcdBvY3dehVrkNZVV6vch3KqvJ6lVNeWVVer2LlZfFmgiXCcs'
+    'S1gHZaSuFF2e8lt1iyqp2WaLHc5kFyBDlEy8fizQYltwizqnRKKbwQLKUUXvSm5BZhVpVOyS1C'
+    '+V0mLHe4FlAX5RRe1O8uOyGXVXVRpmm43YMAz2Eat8XbE1SckMuq/K6k8OKS2ooTclmV3xUn5L'
+    'Iqvyss5ASCE5YrhOWxIJdA6K0VUgL7TeggmN/VoCe4sTiMIEBtM66yUtzrt6Aeoc1wG7SXoDuC'
+    'fBs0Q9A99I00NEdQXCzkfzkTrBHW6+nLU493/zJ4Yq3jy7Ba1jq+nGF8e4jn0tAcQcF3eQ+aDV'
+    '5NWI+mWmImXt3xLfDKq+lbxTZohqC30CymoTmCQjzYue0NailehGKspeYWirHmxI5AMgQpeLwI'
+    'xVhL8WJfUIfcdi2gGOspvH3cxudxKMY68fgRD5IjiN/f/mADpotrAcW4kcILxbiR6i8U4wb192'
+    'YPkiMIbrX42YySJxNsEprHg1zhn2ZCzreDlLQBTFx6EEpWXDwWznWB+uduOPgEAW/rROGIpW4O'
+    'hE25MUPNKikBYgs6OsTaiHBKDSicaq6VNsaMWyoZ7rEhMRg6CJbKpSssFWvZX0oxkbXuL6UY1l'
+    'r4l1JLxVr5l1JLRSz9rSssFWvcb3V8GUtlq+PLGcbnLxVr6G+lRPRAsA0T0E0s7JntFDvAntnm'
+    'qzoSSIYg16ldJ5AcQWDX/UBGQYPBawnNbYXNZE7EJuDQ44hW0+ic87ZiXqnpRWBBzs3WpfDLtq'
+    'sx5PgMR73Gkt7DjnptajyDRK/XplQZ7KjX8rXXCSRHkGJwK9fVyAXfS7r1jbiQAPoTrP69pD9v'
+    '5Otlcsw0b8jwGXO5XoY9XnLVW1xE0ZZ3q8rxQfjkzajcWK2TTy+XMfPpfOuq7LY4qeNvSApxCK'
+    'gPoCG9DTmnDEagA8FdHigH0L3BA3xZEhycH8jwDRnnwwlOb5Qr+djIj2yJUNfLerLS1KvhO2J0'
+    'ffk93SXYafSE315fkdPzvQza7YGyAF1Dre5h9fcm1Lf+Gupb35ayVxK7kY/ruZnniYBOfFOG72'
+    'HM80+EL34Ig/upDCnB3RZGzQjaHwyZlzsQZulHMrR69hYmwjvlaLbly4bePYzQ1SzXt6rituVq'
+    'U54RBWgqcVIYMaZYqj1d42PGnW/AvasNnAUY96rs8cCZ4EfRdk+qbcaCd7aBswDvptX9Wg+cDd'
+    '7MKAqrOIsdvqK6+goSpFoTaywMZ3Qv2MnWFu4lP3Ynra9WRPIXRaFCLwc+rK6QmLQveaZrrfpY'
+    'hBOjqU5hCG/u7Kt2C331h5sL3sJkT7XFGnpLJ8VwqvUtQjF/uD3BW7+Nw73r+PMbLrjurZ3Dhc'
+    '391s7h9gZPoO01qbZQ+AwO2sBZgPcQcXwUfcHbOlFAt7+tE0UfoXhbJ4r+4Em0zafaQo0zeEcb'
+    'OAswzhL7KAaCt3fOG0T/2zvnDTHWt8u8/XHGgw8G75Il99vkfZZWRysR1xDBCXObPEBL7kyzsb'
+    'khhZS46qXNZmF/Cdoh8arswfu7xsKzjS3y/pojEv6+y7QVcHJXe8Yte9M7373UEPHMymWVP7zF'
+    '/iq7mXJsW8p5ycOkHu9mHdUT60mpTZ8AUCHv6uSRQaLLu8Aje8xxD2yCp9B2X/HG8Bzf7dmdMC'
+    'lUcFWf6px/Q194CvN/jTnkgYeC9wjh99Di2ALZLrlyPGm8cCzf09nzIcL7Hum5zxTDwXs7WRP3'
+    'xr63kymGCcV7wRRp1twRvK9T/OFy0Pd1suYOQvE+sGZ6je0M3o+216ba4uJNBu9uA2cB3kuWkI'
+    '9iV/CBThS4gfIDnSh2EYoPCIoRDxwEH2RaFK+FfIlTYkni6z4S3PH4wU4iBYT7g0IkH/fu4Onn'
+    'gZuvLOzEvZtwPy24rbrMBB+Guvw5X11mBNpPduVRB4K6/BmmUKFwWXWZ9MIauz+TljoZ1YI/A9'
+    'WfTICYuz+bngBrtf5sJwpowZ/tRJENPtKJApg/0olCWwPFLgbyrYcZDnvstgBSNM8kJlePmvXP'
+    'JLXPenSYz2Q48pGAcgDBrrbIM8HHuPCaawPj/WNp5LDcP5ZxF0H3KAE+lnEXQfeozf4xLqzmkG'
+    'eDj6d7DhX58TRyqJGPp5GjVx8H8n0eKAcQev5URmG54JfEXvyRDBd3tQcqucR11NJ0B4TirOlO'
+    'UNR3XwmXG5ydUNXUB/umYeWavOu2tursI7ojfSOhfyAQXl9yYHAsGRrCMr+UHi3iMr+E0e7yQB'
+    'mAAr3aTUA8Nlzt9omswnqCfwlUYeHpLAfk3SUVNAAuboNRxCOuxq2fWdHSy+Bk8PaJCflcmVak'
+    'LYUHxw6OwPpH8HWzVtsexWkargdD781iU3OrioJpE3fcMQoDJIzLDWzQmbC5WYvSF56sVDWlhL'
+    'XloeoYfVsuN+SvyyWr3GNrQ6PfyaUqMg+lJnLItMq4a8eOC6nPESl27K42aDSQcmOPRRz2JqLH'
+    'Us8H9QLkLxhInX+JBXO9B8oBdBO5XG+wbNcbfAqoDhQ2tEy59UKuTHviI0SW9SQvSDrd0qK6ca'
+    'N2KSnGrrVC6/WIy6845vTGg9jNp9Lj6ZV++eOBMfcpjKfggXIA3Uie2cctY/UFv5bhq9PfI4xF'
+    'vIRDO5afXAQ+FWbnW19cIdNU2WI2U3XXaLnRqEUlkKaIkztFLJUiZwMXtYWkcLZ/xxZU4s/oxe'
+    'RxeAjLmHyw0oZQC5vtW6Xtw/ZjMKLbEE249tItSVrjluFDD4bHjt/PrKaNEByfnZw9JNkNh09I'
+    'EsMo+R1iw78koTfCXL+WnoI+moJfS08BjOFfy7DLnoByAOFG+B+0LNUffD7D25iXsD5Z/iB8EO'
+    'vWQiV6XOp5SZFR7+4du59NU3UwDpNSCkZv2vC3UqoSZofjIVNlq5Z6jIUg2+fTo+qnUX0+LZ9h'
+    'n38e8vkGD5QDCDuj/9OOaiD4gozqKxkpn5wsCdspuX1CS7Sz1EZkpWNrX4t3k8Ti0sIobSoF44'
+    'vuGHtRIwNS6dri10Jn8sgWljeuVLjcJuKJtmoLuZARMukqya3dtuIR93952xZdZtFXlRI9qLY8'
+    'UYr9RYoI1RfStBwgWn4hzSFwVL6QFjoIUn0BQid0inQw+B1eo64NwkW/k0aOfbffAfJrPVAGoP'
+    '0e+yFi9DvCfr8wqDATfDXDMbD3DTKhaaUmsqykjkxYtFtwxTG5CMo9SW6pcGXFqi24J6XyY0md'
+    'KJQXLjUrXLHS1XbV7R5B2FY9gCVp0pek9D+mzt0i65YFkjm4aGKNbyioVWz3yhpMYlZwvWHkfG'
+    'xJi0z6PAm5rIstDsWd4kpYRWyrrUWtarkoz22tqY7+IbmH5DpnlPKSOySFu6VLbojy0mrU4jJ4'
+    'IT7kPiFfODwWzluIsRWb+T6NZMPebkdq7Ud0qSL78fYEra0R3A2Zs3IQUYJnx9eEo4pckRzMmo'
+    '40tvdypK9yGOmYNRs8tGWr6MO26rrWqRrBRGEO6o36qLsmIo3XFtnnOXKz5rxprDt2mE2qrlaU'
+    'fAoZ6tUaOG1rLXK5piw/tprIDk34GVqJ6/OLuNQEMGpKZhAzRxPpY6OkqTghyUsfki1VJgtXUY'
+    '/5BEi4RdMOAd4xpvbJ5QvaRsJILjhpbK6uqQGGuUvdxtNOhwVdnhHnrXIFO0nnoNG7tKtWEuSl'
+    'QR3EmoxoWr37Hqr+3qvcdwNjEvVh0RNmDZ2JpAxZ2yDAGBzbON7G17HlQi7Vz2ukVlod8buHS2'
+    '2QD7ftptEkSNguXUoXuljyJCx2zr+aFoKGJOxX0/Y1AhJfhX19wAPlAELc/Fa2BJNbDPemorx2'
+    'fAjq9tqq1TewWO5lz+wbiX/Tq57ZN5Ie9apn9o1E5veqZ/aNxDPrVc/sG4ln1ss+0DcTmd+rnt'
+    'k308ixLf3NROb3qmf2zUTm96pn9k2R+diC7wv+EsN9c5aGe50/3HriSOiYYb/8ZYbTzXbxT4z5'
+    'r5Ix9+mY/yrpVp+O+a+SMffpmP8qGXOfjvmvkjH38ZifzfAupW2DMT+bRo4xP5vheqkJiF+8NT'
+    'jsgXIAYafSIs8G38pw7optA6fqW2nkMIe/leHslQSUAeha3c3oU2+UQMhf+RjvifMe9/dnCdUP'
+    'ZoNc4V3ZLhuL1q6WMK63Bahx3W7bijhDXG3bQ8Qcdd1AbNs/9IrTW11e4VuYyi0xba5Y0Uo+ie'
+    'tdWlwftmp3GfmyBPuIZaVXG5RzbOxVNHydiGTZVOutu44bEgfrZLGOWXJLGgCRzRA73exA4LEf'
+    'yHbZ3bzGb0Jzx42G28C9AO/QqEoCzgCMDc40OAcwdjj9z2eCN2Z1i/NynwdfvrHz84iUvLHz8x'
+    'lBiV3ONDgHMBbAz2SUtXLBD4ON7uy+OX1ZHko/aOclA6uV13a62q3jJ/+Sj8vylvGYy64EhDi4'
+    'wz6oD6Ah9Q76NMRBoBuDOzwQj3QsOGoaCuoJ/nGWHdFXJT1IOn3Z/dhmJGZ81y1X023P1fahx3'
+    '7SB/UC5EsuhAUIZH24Pg0LEAgC9S760R/8eJYE6tuzHYm89lY02SUU+aqiFU4UvTagVn4/s/1b'
+    's0609iuTvzXpYL8y+FuTDvYrc78160RrvzL2W7NOtPYzUz+RdYG+fmXhJ9LIwb5PZJ2v16+s+0'
+    'TWBfr6lW0JZAN9/Sxan8xydrRtg9l8Mo0covVJIL/eA2UAukFlcr+KVgIhQ/r2AdQx+AmQ9t0g'
+    '7b4UabXqiFITbtRPgJo3cZ8GmJrvTKg5oNR8Z9KnAaXmOxNqDig135lQc0Cp+c6EmgPc73dlOR'
+    '/BtgE135VGDkX1LiAPPRC/eDONLgHlAEJOgkWeDX4yy7kvtg2o+ZNp5KDmT2Y5+yUBZQBC+ksC'
+    'ygGE/Bfs6wwG7wM1nwE1i23pd3x1iJSaT1EWPuT7spxGsIt/grLvTyg7qJR9f9K/QaXs+xPKDi'
+    'pl359QdlAp+36h7DsyCssEP5XlsME/zJD45Wx+HP+QxP7kghUObkSxhAq7xtC2UC081htw2pWd'
+    'C6tpLV05RbciV0Z1pCPa/mKWfyo9UKyZn0oPNCNjyOtiG9RZJtABXe2DPMsfAqaia4NZ/lAaOa'
+    'KNH0ojB4E+BOQ3eqAcQCFNvEWeC34amG5zbSCofzqNHIL6p7OcMZOAMgBdpyw7qIKaQLeQIXpW'
+    'QT3BR4DpvsJ94bQ9J85VwvWKNClBBcdSijpZuD20k3Shx+LyQb0AWQt+UCUwgQKvV5DAH5FeJa'
+    'ABgG4L7vVA/QDdGdzj+t4bfLR737VAdkffFd7ZdwRhP5rue6+g9/sOr+Gj6b4jCPvRdN97qe8f'
+    'Tfe9l/r+Uen7R5EEZYJ/h7X7W7kgc3wmfPBv/p8JtaKCOf5bO8IpRCdcfnOSVi9nWqFO+Rrxoi'
+    '2lFpZacsLbX4dGr5JK7m/x1LX4fHxwVq6Ps7GJShjXcEwV5e2rOBhG0wLNjVhDKbkInFRoS+6S'
+    'ExywQMnXr25s1tj7d1FD/54Lm0gERN0TiUpxRyJRFB5R0ni4dJReW2uDbEdsh2iYSA4ScZigCu'
+    'kSN9rccLakOOcqCZdbi2ydrx+Wq+w4wmGvt12XquCpO2RtJ5NdE9wRwLe4dJa5GPNDwSzt1uzd'
+    'vCwsdfur3GzEMcd+OkkQPhLJTox3Dw7H8BrhRkNmQYK0Ho22eOcmIhlbRVl0L1Y1AlIJd2w0qL'
+    '/8UZ7DWLqGOyeN0E0yLGK5odvDDtHPZJcrTWzipc2uYV5Jz7YeGi7jUvl66oL38AiHcWxD6RkH'
+    'ilPXA7qML73LT+/bkdBzE8ebG81V4srX6El0vf05enyD7HNO+qjZb4wwITXZULt8z530H7CgDj'
+    '/CQQ/gP7tZpLtduJOITzzHfL0BFgJAFf/KiYjvdeGZZdTcCyfUtAdjVxj1sh5hiteU/Dx8OdXE'
+    'pxocNnwJmxRybKjFPl+rCls9Gl1HDM4WOhhdrTWWS7VRN4OjzWgVp8O3vYOkPPiGtdm9dFqXlD'
+    'uPBJrkWloaOh/55vvf2fjn45bAM8uXwpNXMDoRbtQ2V6v1wzyU1Ctb0XJcbWGTkoyfS9Rf5N8c'
+    '1oMdTeyr1BtAVtdblGgqayyPGltMdqy1Oj9RytMivqiXSuA5sw+/3KgzrdqHNMbHVCTVEV5Y1D'
+    'FPWu4AiLAV7RBwt2R6sW2y2WppeFLFhXfb8pi7WmlCVoRd3rGcyyTJJ2zHB/3j0LstBosjfL5l'
+    'HYBEoLc0rJgK3R2suEOOb8RQVSDyg29hpelwF7BaRkT8cHNDOaO0Sd2n1SUXopRiPkKsmz+WR8'
+    'R0ReTv38F03SPHjAzbrl+Ezr6l8OUMUaQlQeOHad5DLZ+n13JyST+SvYmS0u0g6r3Gft1188QP'
+    'ZVaEp7bTtxVLO4sY6zfGdvbyZhKSaay0IOaqdS+K4gKkqfddNLWOFDCMnbfvhTeWqc1WqVmJbZ'
+    'BFjWSxTYza6F9MzBWjNvoXE+vSqI3+RViXN3mgHEAIdv3rrMIywR8C1R2Fn8u6HUvIZt6XVMq5'
+    '7TP/cDVXXMDBXt3BVTFqQq4pKu8ml6glDr3dykxuMZYcDjbmjxla8t+9WSVVqjf08l41idXi6C'
+    'gtmEWuj8k3TCijiBjQoPUhnnFc46s9TbYe5AR4LD6BSnzuo0yivRwOtwnz/hNkQ7VchSlDKqah'
+    'IXgZFYZx2JsQ+BJ/mJ4Q+BJ/mJ6QjNA6r96gUV/iD7N8cOcDOYVlg68A1cnCj+d0QtzWmV4gzj'
+    'zOlpOyNFdPoTlLtojcBbThGCgmZ6NFEsA6TM5dqkwZ0b0CueJalqwYWc5G8U9Z8xxuymXHceSy'
+    'P2Br8NUzyy4xpMr3jsosJnwhyR2ce9+tDC2hd6mWbW/KV2yCpfEWNfZu1b6UnV6uhrMatRJ/8d'
+    'Bh6x2XkGxNKLAVWN82HkE8m6SNAHoc1c4dnKevpCcdPslXEqfBqI/3FTgNt3igHEC3a4xFQAMA'
+    'HQ5OeKB+gO4OHuAsPcOvfQ3fmypcLytIJaV/Q5DXP7iJX0v3D4n5X0v3D27i19C/UQ/EH7ozOO'
+    '6BBgC6K5jkbDgFSbu7gwnz/1pp0hP8BT75UOF3s1dg3uNX5l7nFBi2nbb0jmPc0Vjl66SpIRw5'
+    'BKxbrWh9gw2q9ZK4I6pGSrwleXHh9Oj9hrNEqDPfvcn7xSIC5KI8vWAstDfUJuUNpFOVhjOscc'
+    'G85cqSd2ZB7T7hSRQj3WjFycfT347tzqTOHfIf6g13VbcMLjFP+ds1mDR6jNWb3x5LbB/UC5A/'
+    'v3C4/wLze7sHygF0WEO4Rh1uAo0ED3qgfoDuC15sLjAIBx+exff+F/ZFXhy6Ul5OTehua7fqEd'
+    'bViwnuxiDHJghnP6n1aQeCZv8fCMjvLN7tvpIUUmA8wKzO2QgEVrVUs3a6RPcdKvoCIxtsA2cB'
+    'Hg52mHMeOBP8T7TNF/qkyEHxKOfxJ2W5ZjdYg7i8bxWAIlVshrbDZvHtaANnAUbyu//tbPDXWc'
+    '6/vc8fdAXOChhVGfRctRW5Ik9tnOF/BGNhfEEbmD+D3P1dOtG9wetz0Ddu5hENYZDxQH0A2c1J'
+    'o9EQAu3X0IfRaAiBDnqCDdEQAvmCDdEQAkGwfdmKjb7gB/HBYuH/ySam3JlGmyFHS5ZrQD0fQ4'
+    '6EUENqh410IFWT1C8pla6dxdbyKCc2odaJnwfoLEHeqec7jUcQe7JVs8bkVS8hyX+Lr9hk/kkp'
+    'V1YxioY33rpjae9L6mV7/t2JJ09kINPtB9Mzi0y3H8yl7BTsFBPIxjwFlAPoQHCz+d0ehfUHTz'
+    'DfFD7dE87LuQctI24tiDgdGrI33tt7X18ShkUtK150r0jeLmca2CpeENd8B3WTPAW+1XzuwkQY'
+    'b5N9sS4hq21+KfkSFwpBvk6Jr/T1VU3c0Q0yGm3Fn4pUUSO/XlxMFPMJz7QPaivSeA97cY8hPb'
+    'ax4qwm/RKEe3KGhDeAS01qrmciODzVXhzRdPmYUGNLclIkqcc7rUJjWSldkjtCRUxox43EEtIq'
+    '1aeo+M2XJynpwUYT6T0i4FwWJ7EZcj6RRsw1HSqRRhokaIDMxU42kGMxKZta8kjIu+WEmE1fsS'
+    'Fz8Yk0lyJz8YlcSrFh041AvmGFzEUC+YZVP8mfJ9Lyp184F/LHisCB4G34XqL8kPD3tnQXkPD3'
+    'tnQXsFP1NnThNg+UA+gQGfQJiNHfQYo0AfUDdC8JXduFweDJtBRGWuCT6S7gGOmT6S5gS+fJNB'
+    'WQFvhkmgqD1IUn01QYxHmtNBVM8HZ8L+kmknLenu4CknLenu4CXPO3owu3eqAcQAc1iUNAAwAd'
+    '8UaIa5MJdA/16i+sTz8UPIUPHi/8l0w4HafqgVmmf4kJ5a4+sHtDxCd5z2ToQ+i3cKpK0wxhj0'
+    'Qk/NE+Od/lAtp6NSqtyG25XdOz8znaV205HWGtD+RmyY25J21zvG/CWlSKW36qJZ/uskYJf8kO'
+    'QczOWsqlRz2Op9KkRkGOp9KkxsGpp0DqggfKAXSjbmkLaACgMDjmgfoBuiO403yfJfVw8N4cb5'
+    '98dyhXMMQ2rY73Dvk+Bndpt9YH61aqbSzlzhuRitR4tcM+BLKJO+7whj1Mw35vetjDNOz3pofN'
+    'h71ybuNFQDmAbvGW3jAN+73g+3s9UD9Ax4J7zFvtsHcET+ODhwv/wIsaNWx0MSyrmyk3QKhs49'
+    'tJJSjKfibiI94rpttY2yyTMRGqkhBoxalHiR1EiafTlNhBlHg6rZpxZu1pqOZbPFAOICz3cwra'
+    'GXwYmA4VTobuagkmfkc3T9qexDbUohaK17Od1LMPp3u2k3r24XTPcBTuw+hZ0QPlAEIRqx+3ht'
+    '6u4KM5Tgz5vqwXYgvncRGHr6R53fGR2s7gG6zfCZulzUlNNDdk7mFdHRw7KI4TXxYfl7E1Y4vQ'
+    'Sv5Uw06tPc0RH42315cbNcTbxOHXpOhW4qfF/gW1I5LtyF10+yea+C4bPuZKn3FfSei5C/uPaR'
+    'Lvwv5jmsQ4KvjRXCqgtwv7j7ngZuKHN1gGD4Kfl9nfSPh7Y23javkaTTv4xHTh50mdO2Sukgu+'
+    '7Y0noPH8fHo8AY3n59PjwfHEn0+zTEDj+XlhmV+249kdfCLHKe0fybAz5k0Lx3ySq8ndgSEIsK'
+    '7jcL02Sbc7Jts9StvsbHH7xw2wpMAzLdSVdcUIXSc8etC/Mggf1AuQTw8cqfxEzmUnCSgHELJb'
+    'f8PSIx/8S6AaK/zffwN62HtpHGFM53w+J2GSWLBPG+OIc1W0yeNAV5o2eRzoStMmjwNdoM0hD5'
+    'QD6I5g1Hza0mZP8CsiXj72XLSxs4qUvE3yF144q2hW9AtiFv50p8jdQzT5lTRN9hBNfiVNkz1E'
+    'k19Jy4M9RJNfEXnwvQraG3w2x4VC6i+oUIhxm03potHWMCjO2iwCu/vkVxXhDtBgPpsezF5y+D'
+    '+bc1VFBJQByFYVEVAOIFQVeZNMcG/wGzk+Jvqav3FZkRc+LjGXUYOEOmNrkBitQcKg3R4oCxBq'
+    'kMgu1mDwmxjBTsUySFh+E4TYoa8MMpYOUNaCkD4yFPzbXPCi4C09QYaxwiokyECwz3yxl38jgv'
+    'blHMdhP9sLLcAulrevmZypOWYDS2jllyVYSR2IdDefe3XB0cJtVo3CQiYSLle5vJ4LXrZhN4qe'
+    '/EfslGq9hGSvuypWYlJ7V6qfnMCu+cE4xIkjg2gpeZF8bBTx05VoC5viUam12Yz0ynjMNHQ/2+'
+    '18GKHSVm/YnZWxUf7o8RJXBk5lEoSu+elGI/weqXmua/8yl1mFDzK1T0pbjwXvxgSslx7nJ69L'
+    'J3VHXuIHPBTJmwAZbPfkGMZJj6CxpsVyU3+qDJ+MTDM/+/uccmfHjXABu/7Wqj8p9V7ZuLHJLc'
+    'ua8B3LBlHMflH7OR/+5Cl3EkQ9ILuTK4JRko9aW5wO0GpWy65QP89+hKKLZY2UOOWSOjgo4oOZ'
+    'myTKlxOJIqBegKzXMKSR3y/DazjogXIAHdHIt4AGALKRbwH1A4TI99cyCssEf4IPni78XiacrM'
+    'aJu+SFezQaZ68oC4sVb+OpGNprymzeM5GYy+CvEH+27MFt2UqwmGw2j90yZfklG5TESGQcu9Kt'
+    'aqDpkqY1U42aJ8N6tKWRH1lnpUuNquUk3YHzOln0SIwdzT9Jkxg7mn+SJnFG6BIERz1QDqDjKs'
+    'cFNADQ3cGUB+oH6CXBpPlTS+Js8HV88FjhPyWuv10Uf2vev7fynqfLrx6/uWqX31sslgzYL/t6'
+    'msqIx389TWVw39cTr19AOYBuVAUqoAGADpCLn4D6ATpC0/PePoXlgh/sCVBb9M19sGbcaTpLaV'
+    'm56cQMz2wsbfBBlm0RJUpBBFo37JFLe7TQQUSMhoC8+KXRNq5IGwn5/h78+RDgi8L2D4bHTprE'
+    'Sqn4xyFrjcZjMRdLsui0w+dLG5wVzHfyWQntS2l7f19aLictSrVQuxU+Fm1rJzqauA6rp/dgeF'
+    'ybvU7+cUIx3aG20Zlwuq1kEOdFSoIBBKEXOJF5sd1/kFW449Vl3JQCeVuihcxhCMxNNbUgXISb'
+    'kK5B4+MZjVFKZejBEyG4t+05fmGajSc+HNRR4Ig3OW3+FBd6R152dSV0p6tlLXQ/C8pnmWYXpk'
+    '7YItMaBnbmdFtZf1JknL9hzRbmKqmHa6yzLQfPFYEKOpvnV11PBZxlX0AdF6tYNDHRVzDYOudl'
+    '4oN6AfLXJbbOCRRo3r6AeH2hrPntDBoM3gQ8O4rXcqIDNuwW3XYlKRExJ4fYEHwT0A8rLjEEO0'
+    'BZC7pZ0f+QoM8z+nqp3lgsxYv4TII5g0Y+mkw3UNaC5nQsPcGP9nw7KwwyTovVB/UBNOSJMuxZ'
+    'E+iAp0OwZ00gW2EQsdE39/xtVRgcYuue8Fvrfkitewbt9kBZgGDdwy4fDn68h+zy/27tcoQtCT'
+    'IQ7DUfyPJv2OVv72Hn/cezTFW+hDPhfrvDycl9d9zRniahBnwpSa42l6ncoem85O9iilya2RZW'
+    'B1kT1vexO0LGkQqH8besvZF2cpEc7eRBkhbTIG2stbrRG6jFCCmXZOo0t8nYiQ4ilNfkmDAfPa'
+    'xEfDKak6A3YbPbTbGbhSuG1cR7e8IoAuoDyJ5oGlYTj0C2utCwmngEuk6V5bCaeAS6XlNchtXE'
+    'I9DtwQiXqeJrIoJ34Hvv7tEyVfbqCIKiTNWtDoRJ/IkeVL4q7HJBk3UuUs5b7a4VzjGhXTs4C/'
+    'AO8gH3eOBM8K4elynggBY80AbOAozF6qPIBj/Z4wqlOSBOEvW4PIAEzK2RB/AfLW9mgg+CAtcX'
+    'fj2rK55LKigTaHKHXOUszp+T8RtNVJKDElLbklOaWb7hAA4cMeezdTCscBEcqLFwrqQGCX3MYo'
+    'eLgyucbMwE9Xttqackoy0SthRnQ4+qlppNUq5cIJ7LNrKqcsl/tfYyeMu1xvJYOG2LV4yIFrF7'
+    'llAgLbnrhfMDeRtUjEUxq3X/VYjmVU2zPAeT+oNploZJ/cFEqQzrlH8QSmWfB8oBBJZ+Z6/Css'
+    'HHgequwj/q5bmSy3ZdRpiGmaIkJ3aeDSkhmovP6YGFhlZk0ZoZvj6Fv+8uX2N64L177w6XeQm3'
+    'IvI/ajwdK9XHbR0oEx6iR/fePRJu6r+x/suNGKB/HUYZH68Cqx2IuzHXSB04ZRmeQ388ksVlXS'
+    'qeCLISG1KXBLZ3lTO0JJkMLLyGhFVNsyqRhUSGUlK0RqvBaDA8XKk1xHSXUw3JZxE9Ysm5jafu'
+    'Ol/nUegg2CFOuYmE2eu8HqAxzrzxnhUl/7loi31xDkpErn610fQO97Dwkbkyobt1mE95pyw3dx'
+    'VIS8IgKeHuqgU0dLL9TAjJ7ZTcbgJj+0Ny8pG9MEZOwtrYNPjAGVDD6th8PM3rXByuxwUwh1U2'
+    'ESivWnZYHZuP9/Ax7gQ0ANCNmo44rI4NgQ4Fx5z4zgQ/j+/9C198ZwTaT1J1xIEgvn+xh5O8rt'
+    'UYsZfeIGcEr/FbE5JfTItmW2LwF3s4iWvMA2eCTwju6xh3B6fGbdgz9o0dbeAswO3Ys8EnL4M9'
+    'yZb30aA/n+zEroiA/RvDStJc8Hs9nAb+pWGbzeOdC1p2Llmt9JpqbfslYXiu9Jptl8Nt93zVpB'
+    'oFHW0ldTnxgriF1u3ZsqmgktbreZ58VI0NFfnaiMilKhdQ03YH46SKGEtfPTuu/UMutzoGYqtK'
+    '3pEoAIk+6mpKYVVBXm7JYY8EH3dWK+lx0TsJ49hwomb+Y/glPtaG0bb5TIhxrTSjSHYg2NNzdW'
+    'zYoENu0CpKeTVB3m136ixdZanlymtZA9YeB3NpqcYpqdTGBxzHeHN1NYpt6aRUhK3EF8HB8qtG'
+    'UqmsxL4l8KT6k6rHxfWqG00N83oCY5k89ceiSMoJoszAGuaCOEKjCXqhSiqLstohlmxSc1iS1F'
+    'jusd7DhBNxK7qthRCpt3dDs3zS8D6mJnpzGSkODOPKmJJ3qJjodnqziWmAgQJWQ/WaUdwv4+6H'
+    'Md7Hqn5/vNuopMMnOUOrZTNB7ceAjUU8xi4xOnciSD7I7FzebMpRSdZkNSmnlEYIpq/WUfKMj1'
+    'RxPSEkRWspD2FLoqIfV2733jtjDeW1qPyYK09kzTc5GWdYQdL8p84e0SwBM6rz0ZDAFtPxtKxb'
+    '5PweOmwtutTqNvztZoTTQsKQXChJ4wbppYiYPl/UyUsg1TNvG6IeYcC4O9UesWnYLMkuONnx0X'
+    'OSDUkQlfVdrUuxLFV+fA8rH/QAYUagGKKSMz03NpsbDcmPAWGMXRkwYurtGlejvEzu+Ir0Ni4m'
+    '7ypPtfRCpGrLp7jdGvHy9ry5sdIy3Q1GrVe0HpHjMEe0G1W+7TfVFQ6CHuH0+iPmSs3SssnKM6'
+    'm+lQoorsHo/v/KexvouK7rPNR3BgMOL0nwYghS1OjvChJFQAIGJEjJEinHAgFQHBkEYPxIlhQZ'
+    'HAADcCxgBsYApCiGTl9Sv+e4TlZSJV7LWXbz067lRrXb5L00buPIqdzaL0rs+DXrxY6X5cTPy3'
+    'Is1fFv4thq7XZ/e+/zc2cGFCVL77WvNC3OfHPvOfvs87fPPvsHDqDKcU9KgJrl2aSUADXLs0mJ'
+    'GGqWZ9tsUNWdqmYh6CpPcICHwrMQkm/1oG2AEEroPwWKtUVfbmMl8+d9+zIsZ6+Zitno/euvTM'
+    'EcSzTVK7Ip0yjhhgFtprk+lAHk8xey05fbrHp5p2pbvtxm1csCZQEZ9bJA2wBBvXxCoUz0HOrr'
+    'yx9++ZnmTLEwP38uSXVGCvaphvn5c8lRAfPz5zAqrvagLKC8XvgItA3QARooUwq1R8+/qpouLp'
+    'Na8XyyFe1S0Q6PqzC1ft5pugRKAzKaLpjDff0103TtZE3X152ma6dqur7uNF07VdP1ddF0Xc/Q'
+    '9ugbonjcrZnYFuO3sdCnxUKf+Q2nYtyp+swmKGUgKNB2Rd+GAu3vjQIN1m7fFgXaFH+F7P7dV7'
+    'Wrdqmu6buuq3aprum7rqt26Tngu66rdqmu6buuq2Cv973XrKt2cVd9z3XVLu2q77mu2qVd9T2n'
+    'lOyIfgCeviejPIWd3g/a2JF2gb+Cpz8E1XF+SkwFkjYzxnKghCtxKODXJKm4KiAlnRIn7Nxwrl'
+    'KhCR3MZHUok3/omNyhd7Y/dLO6Q5n8Q8zqazwoDQhxm78WKBZEP5vhVfyzbhXX2Fev4T2heEi+'
+    'tms4X7J7bIPSiNvqQxlAPtsC4YhZwjtUaUSQWcI79B6WILOEd+g9LEFYwm9kaHv0zsxlLxQ6eG'
+    'q/M2PncYdO7SYoZaAprSwVvSvzas7dDlU0vCvJH6zn78rYuduh5+B3Zezc7VBFA0Fm7sKi9R9l'
+    'Xqu528Fzl8o3c7dD5y5DnR6UAmTm7u7oFzI0d3/VzF0YgBKSpZ9fDPg7Ju8TMhW+1jAV5Kj4mk'
+    '8Iqee1vjv3At5p5+/WNeUJ1/m7dU15wk2O3bqmPOEmx25dU55wk2O3XhI84SbHbr0keEImx5RC'
+    'QfTeV3UI79Yp/t5kKxBu7L1uCO/WKf5eN4R36xR/rxvCsBh+32s2hHfzEH6fG8K7dQi/zw3h3T'
+    'qE3+eGcBS9H0P4X5ohDJvf92fYVu3ZNH/HEP5ghn0+PMMP54P+Go5freS1HrzGDaoQDtIkxen8'
+    'WCyRzGysmMOxCRJzZNBEQXOZT0SMPliPrSA9NTkMq4OlddprcQlPh8QHEMCmtlJbxmjjFGI1Oq'
+    'DpybXu5amq0cmchu3KuXJdzQhihPVhrzYTwVeUP+yfxbG559kbDI8tlhcqqr4xd32TqkhCQSck'
+    'ZogO70gn6Qfd8I50kn7QTdJIJ+kH3SSNdJJ+MGM9WyKdpAQZz5ZIJylB8GyZUiiInnxVJ2mkk/'
+    'TJZCswSZ90kzTSSfqkm6SRTtIn3SSFGfyHX7NJGvEk/bCbpJFO0g+7SRrpJP2wm6Sd0W9jkn7a'
+    'TFIYov82June8PNp/o5J+lGZpM/61lmsYnuNjbNQx2tvm6X+2/+zzdBOnaEfdWO7U2foR90M7d'
+    'QZ+lE3Qzt1hn7UzdBOnaEfdTO0U2foR2WG/m3AGC7b/x0q/A+ZKJ0091Od7WK5X0Ii9LPivAdx'
+    'BKBJpT4+NTMziTm9UqoulHtlYCyWV9dq0Jr1cai5qqi73ijPwlt6kf1bGzVjTht67+gMBs68RC'
+    'ygmkIzJMSceHLW+91VZ5Wz5sah4WJucmJ6xjJazAmo3duiq/jeXiBMraczUVt0Ld/RWJCeZfiq'
+    'BjgFGAFbez04iD6OZ/d3d4nJE1z0LJVhooTAPLynAU4B3kf13e3Bqejf87PdB30uS5xNE0iQA7'
+    'xId9WTdYEwfr+jAeZikcYwp4MkiD6BAfHJjMap6NQ19xPJcYk19xMZG3i2U9tD0LVq3dGpay5B'
+    'JoBJp559qPB29Rfs1LPPJ0HFzbxzdDJZz7yqO0ennlCeSbYCJ5Rn3M7RqZx6xu0cnXpCecbtHH'
+    'AY+tRrtnN08s7xKbdzdOrO8Sm3c3TqzvEp2Tl+HltDLvosto4XaOvI/5dUPGTVvvbKHstUyeoT'
+    'HFftBY9lojqSilE7rulL4q1vmqQRBSV5gXHyt2Z8x45NalxG+O+wP5MNE1urrZi4snVdbPlej0'
+    'MZgsARL+8G+3XWCwkn+AYSKtVEpg55Q2LV6R2H0OeKPXZMi+jplTWKSpLUNA2PDdfWLszUenp7'
+    '9XKTA93wNJv1Q0HaeJEm2KSESYOb1GczHOT/Myn+jmj2z2LY/BXW2t8Xyx4/eEQiwqS7UuQgoh'
+    'ojx/alBGte1sAVuB9arG30m1BTi8ZWvVKfc8FxKpL5Ja4sLXlv+0VWvTCTcc9imQaFCX8j2cHQ'
+    'YYmRALO1eqOxKGI1jFIP9F2MH+5eqtW6+8RG55E++j5fWi/Mlx4nDMQw9PbNx+wj8SWPojDG64'
+    'Uefae3gCd1Ruc00j2xNNQMkTkb6f6LWOq6eamzIM1+hnc2wBnAu3QRdnAAuCu6rgFOA0ZQYL/C'
+    'IPpLlHxT4lksmn/ZXCGURn8pUzkJcyFwIUvCacCIE7ebYbTuSxhF1ykXpGVfcutaTrWmX8K6ts'
+    'eDAkBduqrktDUEwcSD8//luClfQVEHkf9vJnnP3XqE9qH3z5+lUYf5wdY2LGzWHi1jKVkPsV1J'
+    'SGAOv1qqx4ub62KgpVd2o+rvoxkBZVlQ+2FNMOiaBrZ+JdlasPQrGWvQklN2fiVjPVpzykqCDt'
+    'C+ZliZip5DSb32GWwRzyUL56uUZOHg0nMo/GYPSgNCwB1TeDr6Kkrqsc/gku2rycJxyfbVjLWk'
+    'FCgA1KmO5QJxWQjfbgpvi/4aJbln2gwUelAGkE85bpj+OmMdFQVKA/JHWCb6WsYG0WaACv9asv'
+    'CMPOVTjougr4Hy6zwoDcgE0c7xhvs8SrrFPsP3M8nCEQrn+STlfD8Dym/0oDQgxGF/FsN3T/Rt'
+    '7IDfaacd8G3xaHWhtFbXMMaVqniEqffgppq6m8R7YjOrkflgGaBGbAhzvlJuiHIeny95gY/ooH'
+    'Lm1Qwc7ahhmzQQLhsKfEy/Lfqad3byd47G3x4hqvt3I9rwx2oaMrfiYnCX4rVKWewzksW6wIzc'
+    'am4wnKDWaeldq1UXNTyjd7/tAlhbJyiPq5W6xmzV1Eou1xN9KY6Mcg7BRU28V8YVbNLb00Uc0N'
+    'CHldUK1Yqyais2TZYGT+2jkwESQqlvnjTBeqRs6ScI3mqsOfrJuJdcCuOxMrs01mqPIn4yh9t2'
+    'ptuu3Vz65Yp6q/qqvPWt9h/8fetb8WNJf5xf4H+IF/FSHC+frYQ4j9rA0TbkFdEj/SluO/U1kj'
+    'BjDm8VJ//4+2UcP1zqq/TSP/HRvvhQXzxI/40f4eewnJ8/W1tpblhBX5xveLEvPop38eJKab68'
+    'Qsc/bX2vvLLQt9j0yu3mFclSKmzS58t9S03PHzbPS5hh4qc+vNx3tunhI/ZhidDbc7jXZOUBm/'
+    'ppGhi2qZ2LzT5gbaTVaGqDzvVLmkVVbUI4AGXsD3rJE6nxqSsbvZ7/36YxSpMIiOzWQtNMzZ/r'
+    'Ero9jqFlEHOrcnVhpVZPxmhVp0CRxWAH5Q9ytgbdqKy7AMdsGr3waNyzVqvXK/MrNpA7q06MOZ'
+    'OT4byg8yLGctBhcWhVsyDLrvMI3y3ji7lmrxG73fGl23KRVSrWWJhjdlWFWwV0w2lDix3E7pRq'
+    'HSpRl2GoWAPXjTmwiWBr+ecn0+GslmvrfMxHxRJf3TafA85pKot4tVZnrU1t/lyltlk3zDUJZa'
+    'Vti93K19IyTMVMlGoT2NyPye13QzLlDxL3Inqrxvz3on63aHVyqB6sy/Q2hm3iasWhpnVUwfJH'
+    'RHB9VsaKUKTDxWtPGUF5l5KxMX0GmjOdGCCilPkybYU8jFTWa+SMuHLXz5bW5ajUEDXeGKpJtG'
+    't+hxt5n9hTiV1YqVWL/WbWa6tqPdb4JEq2B1WYsMYmWxYXgUMgUVtqMYni7uX12uZatx7PeZHk'
+    'MMclWaHQMi8JgJ2ZiexNLsiuG9EoyG2YFUmcuWEWPrHER6EaI7KyzidkEnKtwaxN/USMGnYB3C'
+    'Q9GTvpqLTtTSOVi2nvni/Ni4EsNb6yXGVFI4eNZz0sVVkzoXY8RYlE44FjdB9EcfYIEcNymNLZ'
+    'WhYkTFwsmZ4WYJjnou+yq5CK5Hv0AMKCiA+1AzIHkD16ACGoSw3K9+gBhCAkcMkxhDPyP0FJ32'
+    '5X6/E9eswjFMe8d22zGMSf32un81Vf/jvtft4RzX+AMN46lreS4oxHv2ZzCy0DOPWB58yimmtP'
+    'D615HWXF09CHbLJYkiUUYqT4zyOFdksKTJwnLFcaV1NVlk7O4jnuzXB4gVDxLBHwzniEd8bX8z'
+    'YauoX7mKzPK+WCiY2ATu45QhvqwAC/ZzxrC9yqntf3WnmCHkCR9gHsyz3uZ37gsDP/NJO7RRMT'
+    'lUuECJ+FR5lKuy838ifx8hviozAnrjY9JvQ3Fz6YLLxVvqLYuAMPatEtkxqxWNJU/OGWwiA/q+'
+    '7FbsWQiAs8LFwGarvv1lziUNXaFJcg26o1lwyilRJsR2UwNnU+et4OjYbNOynwuR2gx8ZYtSt/'
+    'aKaLyHpmMWP/JXN/ulpZqK3Uqr3q3rDHU67wXNzZAGcAm8x9ezzlCsF7VKO+x1OuEAyNehLOAr'
+    '42ui28KgnT+Z1+uC66NfxcyvsliJ6SZeHplPFaPssJaUTLAGPvsqQQ2Vy34toxDfy/QgtCn36m'
+    'pm6uVvuQMXKRf3Dyb59n41yq1zcRwIF3d2TRtgX19vGrUo7NdoM7K3VOowXeC4IvvcSOdOqjt3'
+    'CBhoOzIkeZMlKlSHtRhSIfL6/X+uWKBQKMtfJHPH3ebTSgPjwEQtyi1URW0fuqxUqdVqILFZP/'
+    'eVMcsf2egOrlqeZehvrlqeZeDqQjGnsZapinmnsZ1wJPNfdywL38lPTykzu9X1LRN0BKb/6Xd9'
+    'oMGNN8xMVOWqSjaVJbaqNWe7PARM0vcWy6Czj/rqrwzgJSxV3nsZTAOZxljXYzSXUF/IIxaTd5'
+    'nMwk8mUJBIXhQNm65/KVQXO9cPSknbi8sMnunHisLjGFEbaQR18oWraGt2Tft8/LNe2Gquo54b'
+    'Y9enClIGiFZGkC55DfGXTPWYK4ADiRYEMtLa+X1s4y2fYBHphCQGiY1YNbKQhq1IKq+Ghs1Hrl'
+    'kkD8K8y8K8g2a8tmxxmjxEZ+TUT7amxMzXHMbdAim7jjiY1yMMEeUWfdK+q05EdQOm5/XC2tP4'
+    'oZJVcIAwO9co6rc57qMh84VMIUudjwoc/wEONhQ4O98aBBRiQaN5X6o6HLvWKKa16F+QzJUZEx'
+    'MGrOC1c0EiS70UIyXj7PPOGRq67czg2cs/pJ9iaTayaxWXEcIau44P18WJvvD1yo3sN4y5+hlG'
+    '9RLMGyo7baH+dLj9OPR45fttjHTa1DVT0KgBNNz1ymjLdvPqZlvFRJ5kkv0frmPM0NwkXk0AJG'
+    'dGLYcSIp09aXvazhGPBmEMBmYr1UYa8cM0S0KKk1Nu/7ibvXZSmaXylVH5VBb2aDujuLVMnF4A'
+    'hTeGny3NSKBwst+0Qee0N8u/TKrfEJf2BbbrE4eKvk9uBmx2PaVjO86/qIGeQqwBTiWwcuW7Ie'
+    'W+hNohOxWPWFhoElPxKhd9heUYuTeLGh+fWGjQv68m80b1zQdH+j3d79ODgA3BXd3ACnAUPFv8'
+    'eD09E3UfKtiWeh6v9mc4VQ93+zuUKo/L+JCg80wFx2T9SbqLAt+hZKHkw822bgnQ1wBnBjhbgG'
+    '+BYq7G+A04APRYfDb0Gh3hX9fTss1rdFAWxDXGpTWWlX5Kx4trJGvb1xHo5FSf8/URogOlpS0W'
+    '4y1wy5zBV2z/ajH9XrtYVKyV5B2lRdtpbQ19w7WwiTfIYlYc71gWHrDOX1pUR8G1GyI/YhtTkb'
+    'XR1e5K84ZL7YzkFP3wbPsiFrGWX2trooLVibAfm5/BhvRg2cYOWi2axCo9ExIctoEnnE6Mm6S+'
+    'XqF93JuktP1i/iZL3XgwJA+zQqc5fK0gQhHWaOIZys/zNK+kfb9GTdpSfr/4yTdVf4y4HF0Ogf'
+    'iQj90/7Bmi2Nk/tt4xWK3wwvOSJb3MtlAQsDRhdWapTZCuYSoyBqMh6flirixo/cIHdwBrCRPx'
+    '0cADbyp4PTgI386eAsYCN/ejDydIj8OeP9EET/yzai5Ob8Gxs5xOOJ4/fLecxkzmrJqYYWQsLm'
+    'cnc2wBnAZho7mKnoim5ogNOAkUj7pzw4Fb0TJV+XX26kmA8sInosQRFHfYtUOVa9mRzLmtCA57'
+    '/zNfEuecUKo6FlWLfe2dwyLMHv3NbUd+AvwSa1vIPTgHFh/SV/uKajd6Poa/J/HDSNVzWBvJKW'
+    'xeJvfZmWcSkS5qpcNd+9q2qsTmul+oZ3aIfl3zmcvDj7UI9mLpWQJ+bAzYvGG7jIfpEEexvYhw'
+    '3l3c3sw4by7mb2YUN5N9i3rwFmRiF8yW9Mh1dL0ouB0lplACY5GJUyKHOh5sOgn/KaG2PA5MYY'
+    'cPY28nT376bC3JQW4JQnuVzYBs3N/iAOerZP8efc/nDbGpaA9er+VJwm2HzNXReG0H5IhLb9aX'
+    '5nOxDWyeTuCbfRYkLFXtjfRr91DN5ScDQWmmsvnJKnp8xruX1h+9rK5nppZX+GC9dvuXyYNfE3'
+    '97fzL/Z798PhNi0nd1W451RxemZi6sG52fHpydHh4sni6Ej0OiL86omp4r3F8aGxsQfnpovj94'
+    '6Nzk0OzcyMTo1HAbW46+TszOzU6Nzp2bGZov0l1X0y7DR0T5mNqiXTiDULZysri6wII74xaxhB'
+    'FLxjK2HOdN+c3fFy1xUac5owI9Vaaf+vZamYHYPXteKipWaqc70ROlYN99janKYyd22L6lbKpr'
+    'YPZKmzdwxef/k+m7LtGHFXrj8ZZg2au6GpEg3B6tUTXEE9tsQT1bCDREbv8RO7zPOs/JsMHhrS'
+    'H5drCOJXINl3gFYNpmBAfqLX6jyLPLPp497n7wfBb6ba7h2aLN73m2Ph9mg3yVT/WyoKwo8hUh'
+    'm+5Qb/j7YY9mbrSNkcDx46fJcaMsdjY8MQ28cqC3TQLy+K/p4XjaE1yLzml774fgndRWL+obiH'
+    'b3D0p+5ekuxNVsVkgN8K3xYiT1rM+XlgsQprXhc1WMugQ9KDWkJtnoUraFTWTHQG8xjJ9KHojZ'
+    'HZ9NjAwPnz54mtIJQ5tyKP1QfGisOj49Oj/UQsvTBbZfd169o+f8GkAMZZbKV0nrXty+sa7BAG'
+    'RxKGCpmglzbO8xF9EUlvKyQDJrhkCKPW+g/gDqwadw9Nx8Xp7vjE0HRxui+MHyjOnJqYnYkfGJ'
+    'qaGhqfKY5OxxNT8fDE+EhxpjgxTt9OxkPjD8ZvKo6P9BlX/vJj0DLV2fyZLX4XvZDWpnrr6GIS'
+    '/9iIkMu4BOKDP0f8qWsS5irJTBwOSy9kmlqEABOQTTpp/OyhjxDw9tDnW0OkudlLn27AA9kb9D'
+    'PQffTpLYzu0M9Ar6JP3YyG+hnofvpUYNR8xqer6dNBRgP9DDRvS7jZft5GstTropiG+U9mtxFt'
+    'N5DseVd+EkK0mxsiSizag0UpNlPTCe/IKkfd+3DT8vTIw48QgTtROvHh2mgbCTzyrZ1ru16/Bf'
+    'TtBjrTyDcEZj0a3ckUdhOFtxCF7+AcRgfonZH8+hVT6JY/dweevBC0ScSPow2uCW4tcm2A2Xc3'
+    'taFbv7UxPea3dvq2g06e8g209kZ36zdkaXhjNMwt6qEW9VGL3kq/pKLbqIRD+alX0KJGpreiGC'
+    'JlD1F8QL+1c3036LeAvsVKMSS02+h/A+EvxFkO8BcQnf8poHPlD2+ANpT9ARKWvEs+XRoMKm6m'
+    'xrQr2Q5ziOzRKYq82hz1CMWGNnW2zxQqCsuiudBXpzcEha4s94pJkj2P1uXS3FSHG2pa4FZLfX'
+    'ow9qjusxkCVdAxC6Z9iu98nK7uWJgMqjtTW6PVwgbTHRiIizS0FpSKRGDmJdEIuqKFqoL37ojk'
+    'wq43E2ovdyVyOlF5UZlwaeAiMqtd8suB/vNNm/PUnvIGx/ORkvS+S8sg4i7Sthjz9XtTKWqH3u'
+    'Nt1aaYXpKFvfDBIO9Y3L22OV/fnC+4PZdzZDN/ut3DLES6gXTMLyg2nYDSJFF5feCifro0sIGi'
+    'COB/L3Un34PiYE4pWVipbS4aYldLVYSMaqRrUkptVQqTSBL/emmBKWxBi/faJfPxko1yLIGCW0'
+    'yGB0urKzpmRQvHJm1iGMcv246S0M39L4O3jYy1FPa/Uq6+Kkx9OTylbbm2WkaKTg6bo6wQ8xxn'
+    'PSSNwVC2V2ieivaCRItfwZ2M7Mnem+uqe9xqMo/VlhuCY7/sWbBSW16G0XQDZ0zJr85MoEroK/'
+    '33v79ZcOXtWqqtwNRl4KJ8eBVbdZILvKJGNRLx4zWJROhSVT0nqUz/66vYvAmv2Ctq5OXI+vEa'
+    'PF/hwGZDCwu1zSqGhgJzJUGusNX6VmNDTyRKv6KmviRBr9XK3bh027X74EusCAcNFVsu4C+1gv'
+    '/3txr0/w80zfv/fzN/+/8Hn5jiQrC0Un6sArVCg9CaEMituaS5SpJqJbySeoInXlJrctgZqRgg'
+    'idFrK5WFC3EZXlTOsLC1cDB9FiX8GJJBZX69tH6hkZdc7CsQC+pn2e194CI+LL36U/8V7AavLk'
+    'lN0/v/rfX6Mv30Mhfr17qPXru6DPP51pWD+wdRNsqHy/wVKoIPBFEq+q0gSuen4yGrGqi4xAqi'
+    '2GffAExrNkruRzewpSJ0AjZ0rJ2m2hy9YX2d1k1VbYs62I1fIFyA/lqASAD5Lh4B3ZvVenmj28'
+    'ZS2+s/ilTAgY0P4OAUYNwz/rQHB9E/xbPd+bdpthelzFwPrsBJYlEvkkRjAA3CSmmzyhYOsC/c'
+    'XDjbJxpCP0+6nls0OAXig8e4x1pvIDcwJFzXAKcAw1Pv/wk8PBX9c344/ydBkmAsdR6VcvvOeu'
+    'nJYlw7XxXNKF+Qi80h25qHSlPcY6xm+IoNwd4lmuuWdymiSVlbR7TaDZNVnc2n5stsemuj0Nag'
+    'wHbWNE0nu0JvkiPoFG7k/gaY247Lxm+m7bD41xiWufwX00luSGx4RNfW+LGN6pTQ6lN477o0cN'
+    'Feo+DdOVGOcKFnmn85Y8oNXbI4sINRqPRNOg/N3+FFIbfbTXwG8wUxBxp3282FR8sbZ3SxSzj8'
+    'NlMiVYqTmj4i5KgavILw9wMPD/U/VOp//JGH6T/08VD/XY/cNsD80btSsZGVVGvVeHNtDeECEC'
+    'Bl4WwJe3p5XQa4Po7D92SpTnOdMw33zOIFm3m4V/i2Wnqssrq5ag30l0JXWl2iJmqQmI2tuEzj'
+    '8fChQ3Z5ENsC7vKsBwWAtmtaSGNTQBDCuP9Rxk70Pw44vdfvZaxrZ0EHzYrkiUqKHHa6N5i9sJ'
+    'bPPBrqSEM5I+PTJomwyTWzubLSUKowEKLMvL19oJIlo6hJ8cX+/VJ7Y/dKiieb1mGlsqzOPhrS'
+    'paKKbnnObH4z5VWEGYJZ2bTGGbgYdw9022+XYt1jDfCGeKxI03xoLP6p+P7SeoVvfvQZ+/0Ncf'
+    'fFbvtg96Xu+HiTlSN2rSvWQ7V+9NFq7fxKeXG5fKKEu6qL9vsczKtZkpzReBfqzQO7gnV4nXn6'
+    'f2M/UBypu+QoTmCsytZ0tlImaWDh7AWeHQgZyIsmG8mUNvoQl7tpARMLcpMDO3TmNJYi9aVkG1'
+    'bTwu5ezzUARHm5DWVYcQpeYwng5gDuIngs+1AGkHHQNvsKQTmN+WsMUQhCDq53B3Y/+VMUtT9/'
+    'oWFWaL7FaoNqWu8qOHZ8o966ypRrrpTiUlyT7Ch9LtiUWuaLP3I3nu/2Ggbjkz9NTm7M2z/F5N'
+    '7jQWlAiMfzmTbF0tFf4b19+afaGppxxVJJK6Gk8WgwMMBlFquYuBt2UpkTgyczsG1cndbRGidN'
+    'ccuFFiNxYcWMzjvemU2VBwi7RCUEB37b1NZIxSs/qSyurDXuQQ2Fe9KlkUAdcvljdCVZEv2uyN'
+    'yGQpdalN5yHXh5RamhybGtRZgW5wszyNLZdhlS2z0oABR6cywtA68r2hu+P6VYW/Qc3uvKv1tz'
+    'RbFZiywAm3VnFZ6YNX2SU5D6+6Bp90HJi73hBaU52MyTg2aZ8tacKtsP6Kg+I/WfCZ1H5aN31u'
+    'PhqRHefUI2DKgfGxh41F4fFSq1gcUarc0bpfqj9QGJ0t7vfu+HbYUEVeq3p6pGoN9zdR5wfIUB'
+    '7HPJKQ7j1+cwxXd7UBpQjmb9fzELVSZ6Qab4CyL4tmysEcbO/H/a2nhaO9Mu9c1SzuXPfGfMFa'
+    'vhSMa0P+tBAaDt3nhEeI8XZDw+ynz9ZqC2LfmH4xF7ISk2oZe7gTaxFEyuvA23VcHfic8U3s6E'
+    'Q2Mg1cFUl3MDSz6hH4h4/mzQLJ7HNt+KuTM2DrrOTvhy6pnNeXcEtWuaEs5CBdwe4odbLYPOLu'
+    'uVX21eeiS5agQqnf7A9ZJJkvQDJ50GKp3+QKTT51NZkx3pf03xNvy5VAtO8e07W5jB0cykI92K'
+    'f6HPQD88AqJ/LLk7epO744yoC8543R1ahZuN6PGYt41ZA4BE39hbNVp4VdtdKdenym/fLJMMq1'
+    'zWDlL93Rviwy+rh5zd3RVfvV3SoCKW/ZCVmNlZD2L+GykjUFmJIJIy5tsl6134+V8PwpuxGpw7'
+    'AvOyObE5q5BMU54zsdHEXnPHaq1ag7tJ4dyR/A2eXSdzeM5kK5On81sbftp3rbEnX5ZuENPV1v'
+    'M92XCbetzAVhE7hbFVxOfcXWGGhRw2U+wYvL7gUVYogvRh8b6axlMn0l8aSk/JG7l+NX1M85tX'
+    'J97UKgsweHSmpOrIxRah26fM19yd4Xb1Aymvi7HnifyLQ11hDh2+sD6/ucz9NkvncVTvHs7dE+'
+    '6QmHZzaDebg+4YzDdZHc4Ypgj9obwDNNcX7q5U52ub1cU5HZ77tzEF/GSH/qZmi7m7w2xpDYJ8'
+    'aWV/lh+LXxy6LrwmSeiQPsKJj6fsG7nhMCSWVxfZ42X/djWxbMW2IfOYEuxey50Md4gfjpQSci'
+    'k3tC7FPifF+C/m/zYIQ/cArGphXOiND/s9d/RljREzPGi01SuPy/Bom+LPsIwl8b5SEstYGQbb'
+    'GcFAyd0U7to4u7k6X6Wy5zbXK2r5u9OCs+uV3NVh9lylfJ5/F/vfbfiOn24Mdy7WzldXaqVF/p'
+    'l7cmqHweiR/Ea43TIX5MiE85q9nZFxtPu2MFelimrrc4vllY3SHGta1J53N/0ysT4CnLs5d024'
+    'vUYlyTNiDp0lgH/svj1s4wbuDnckjZF30OycOH16dHwmCvDryOj08FRxEsaLUerY5AtDp8O9yc'
+    'FlJvPRVvIv+gJSL/69NGCcqwYu6qdL3b9Fvc52xUL04TAj9tvc9hPXvDi0P9yXrE9d7Jem5Mlc'
+    'V5jx+SBfaGKEi+X1yjkWF3RFuCoxTkbszzqm3fMoc+0sndR1RMiX7l/pCDM8ulouXLSc0Flxtb'
+    'R+QSkxX92Sln7ZS9rxsB0fNutMSePs5HcL0/wA8w/vpqb0ldwdsHzm89yVrGL22dxgmGH1qy5f'
+    '17aoEy/JaiKP5l4fZhcW5uDyXadRnn7J17YtLOBLPXd72M7RAur7xcT7uhavjeEBeU8fzg2Fod'
+    'Oe6NJ1Y4tXh81D8rr3Uu5YuFMmmyQj0JUrOUjc0JzasWQ/13Onwr2r5fXl8uIcHPDnZEslCWD/'
+    'DmbZ3mZKpmjE5uSdIr1iMJTEfsBUVK3qCqrv38nkbFWSvjNRNVA9NxruYRR3ll45uy5XTqd5wx'
+    'XTsIV1vPwt7CeobxCnSgrYfWUFbOdX+H2iYLW2WFm6IAVEV0iBvMMlTId7bUfP+WV1XllZe+zb'
+    'p12hp8OcTKxEibkrKzGSV73i3hR28tRJlLbnykrbzW96hRXCyO2lc3xfvb+Lytqlz7sfh/Fbrj'
+    'sMoUDXJ/e6J7cDlmeuCdt53avv3wdPG/ldofzPB2FHcm7l3sBSkyC6ft/w4tC1Yb5pv5BnsIa7'
+    'NxpW7NTLW7HzZ8LQrRJYv3md0AVavvyYNSyEO7yVFt5AujJLHWbR/fEqORdut2skbfdtWFCVl1'
+    'dtsXZP8UM/Xr3Hjr8wdGe4J1m4bHU3vuS23n1rGPGzdZyjhjmuCNgjEUYMe+RbdynM2qWvEGa4'
+    'BG3g/heH9rakYUoeyx0IO8qPbcxZd7p13Wh3EWqd9ta7n8qEuxISb8vteiTcaeRguCFJUSdufH'
+    'HomvDq1tIzjVcRWt132r22y1fse2meJlv2k3uSdi9/W+8Y7E10VIJ4+00Gnx1nx8Jsvbwhq0bm'
+    'ylaNbfQCrxaDVH0ZakuRVy8rFuiTJOWqSOSdRgRp2kizV76Rdv8OrSPJFuZuCK8Zmpycmrh/aG'
+    'xuemZoZnZ6rklGHZ+YmZsehYwahTvHR0dHpuemRu8vjj4QpXLtYWp8KErTIhAJRj+9eXZ0eoZe'
+    'bqOx0KEolT0FLJPbFW5HGXPF8ZMTUXtuZ5gVAujHbVwB1WaR7LFHXhh66LJHrNzxl5aFS/4LBJ'
+    'vvl269JwzdVKVz0L6R0ani/UMQwRsYQYSOvmVyrDhcBCeyYdvU7NholLr1dNjZJFbm9oad4OZo'
+    'Qxlh2D40PFO8f5RKIMaOjI6NgikpFDc9OXQ6Sp/IPRQ1KhHu+/ufDrdH2eh10bPwQ/urVHYnf8'
+    'sNPhsk/NAGD7F2aPjsem21srkaD3GC4nohHsJFIx6q2/QbhZBTYZsA94lws3V1c1LfrvjE9Eh/'
+    'feMCQiqqS5holxBFfx635ptVq7hWVzFxVoM2acOpVRfL58ortTXcdOvsQW8SUO2X+p3H2Xx9MR'
+    'w8Z3zSV5zDhJlvcksn/czXIuIHjETWdTxYD018ecm73qf6K5k2fV4kGHYWcWfjgjhqhcTgLnaX'
+    '2mmdqHbYz3Df6qDPvfw5YLfAm/hzKooY/1es8WdfrLdFQf7XU/FUQ3wjiarBMWcu+NfIC2dL1W'
+    'VxaCb+imQdhiZyCucu0NTdwxomiBgxu7ZYkmtISbbCMQNL64s2QwtGRhhPzM5Mzs7MTYyPPein'
+    'jeELZkTHY1cVk81Yko7zjb5ETZ1395ihSSOA/dDPSVwv2+Qe5yUoJyJLnKOVCXfCHAznsQ0HIA'
+    '3isfjwYXZSEvue/TS4d4dvtKZF1yJydpTO97swtS6i0RYF3hWGkWcyBG+zHeGphMXQ9VFb1JU/'
+    'yoWavCOmV+TWfpbvY5bU4aZuLiyJ1K4GgyKUtbsBTRGKK4rbE+ZEMT0Z5W+EF5XpIVOpODlWtL'
+    '/9wgJ9cUcDmiK0gyo+nbAA6uaG3UVVmJIbckpUy+cTFlrm9qFV1aC5u6l1Ug1aJzzFdDgIj0Hq'
+    'pjuoYp6xfuCe0kv0152mvyTIxUGOcXHUIuivXpj85L3Yw3qrY6vRYBxd/lvZDL/XiAaEIthlEk'
+    '0Tinvjfxt4cBD1UwFx/p8FepWmsXzFSdRbN/TaerG8wuH2ezCz4XLCX3t1uoTx5hqUUbCp8hti'
+    '3vLLg9lAfQPWivWzdASxJluhHxKLA9C62ZVoEfwQQfzeBhRN2hdd04CmCUWy0Ls8NBUN8GA6EE'
+    '8jWUsrjtsgDQ2V40puoIn14OcAp0dKomlCMZz+ic/6dDRIBeTzPxc0zHxpvRr7epFOOayMTfAU'
+    'F4fGhw7C5W6xIsFj3U4ER+dKqVpiN2daeyvLVVGW8cP9HGLV/1x47OzG6kpDExHOYbCpidgOBj'
+    'ngSxJFa/ZHV3NYEoO2RUfZWu8ebuDsVFHugHlksfFchaZqgus8n3s47DJCVawgVkVvA124RT3a'
+    'RBcu/I4SXfsb0DShsJoreiiy52C23Z6ki0Y4UyTh2aQXmqhrICajhTWiAaGNUxAXlHfwFDztoe'
+    '3RnWzbeVcTMUa1+7IIQraCO5sIQsKCO4mgqxrQNKEwCv2llMJBdA8tdEVa6H4UiHjixUBSu47K'
+    'RsksEH6cRlBltM8gnxV4BY4nv1hj83WJtgcDEQQ/wm3Y5gJMUhbdi7otL3AiI5HX2MWNRIG6SZ'
+    'fQ5wUorRsprbQ6X1nerG2qxHPeVIorPJKVzImCqUYYbiOHbLFiH3UrdsBMCakz324RrNjDPHsf'
+    'US5JcG7fYKHEeYQ2+ivsz6t5mJhyzk5mDe/0VtkI7X70VdeJ5oJ1ONG15o51ODEhzTXrME/IDw'
+    'YeHEQn2d73HwcJsmXTFAYx/yEfn1+XUJ9lcM/kcxVZs3uoLkGeuyVrUWXDlcTBBr3kPyt0kOd1'
+    'Tfhti8CS2z8W9/O/090NbcU95cmmtgbcgB3RdQ1omlAYC094aCo6Re9fnT/udb4ZwzZavpPxrF'
+    'gK6wpRtjeQhHXjVBNJ4OkpIqmrAU0TepUmUkSquzfRtBqP2tRaYVv2A1kLDVih7c1sL3CDtfFP'
+    'SAIuQmbk2YO+me9yHRIQgvt2h6QJwXX7bVZKm6Z37stfI5YkvhVYcxXYZPH4Hg9BAV00shySJg'
+    'SGdQ7JEnJ9VFQbZgmd+v4sNXE6uiE6Fd5ixblZKjzK72st/NoCsUziyXYPCQiB3OuQNCEQGG+1'
+    'hnF0ho/25vNSOqb5lg3FbvdAgpdYDB8gXkYeghL3UF+/0do7PUjv/GTURuJ7y04r8Rl162qxmT'
+    '2YqBYb2YNUbc5D0oTsjfZx9DRBsoRcRRWnORqfYtlfy0YP0QgdsWxvM2x/mOD+cMiaE72V6pyL'
+    'BvNi0wfNjxyUjUztrO+M4ahKz4aoDPUICrnGQwJCrqUDpEPShNwa3eYhWaq2PTpsKcwYCueoMw'
+    '+FjyjcHpWwD+VPe5lo1CwHZsWmLzldKC3c5sAuW6ffEBFbuSke9dglSwm2Y4csEdv3e0iaEMgO'
+    'DskSch2N370OEepLNNrvDScV3hYtUOGL0VT+J5i/dm1PkNYcVZAtbF2wGY/gbUTwQoJgBAlZYA'
+    'Moh6QJgf2TQ7JERXv0ZjtKtskoWSRmT4ZvVjAbLVHRk/mheKh6wa6EfLQ1YcfQ+5yQQAk2cUYS'
+    'LfLozdLUWGIrXYe0E4KF0iEBIXtJKHdImhDEinMIiLs5mrAszxqWL0UHovFwXOHt0VmqrkJD+m'
+    '5uhX/U0EPJ4stpwHZqwNlEA7ZTA87yRuuQgJB9tPE4JE3IzdEtHpIlqvwRv900oMIj/m8DzrFY'
+    'j14XXYyC/HNBfBoaE1Hk+KdmI+kgyJuL9kNDpcIi1boos9Y3edxottqZs6wucTpAOXfxNKEZ7u'
+    'lL+kyN4rxhsshqlGA5qLEnCQ0NDFfWiWxI8RghrgrOviSxUJmQFtLVEahDJJFkPcrQiv2Q5JAk'
+    'Pmwim1z+voTewl0uqEWX3c03jQ6Dn/YpcNqMyJRNtaH0/R6SIgQz/G5Fgug8PbE73+d5n5zncO'
+    '2W0ZwcpJrQKJjyAn3fR1KE7Io6wg8ECqWix+mRHfn3BPH9tnzaJmr9PPHdiiudLjHUMQyMZTSd'
+    '+biTEdYpjI1iHGacA5Bkljcri2wzhWf7+XKvXlhdvIkO2f1UibgJwg6czpMb5fV+lFV3FIMHj+'
+    'tOaxCQvJ1aNcmb0z+AWeUvIVzOPfGQ89ApJURcNbUzLGo5Cm53tpP/ALaTufC+rDGd/BkYEv5s'
+    'EI3l7xBDYm2PSZKhyUxaeacwRUmzxJ9pNkv8GZgldiTMEglC7tkuC2UB7QEZb/JRLKCEtUf3hU'
+    '85E8b3oI49+X+R8oZOM31lEwqcZvnamkbYVtNDTnxCna8bHRdxLIxHx2dPz808ODnaU6HDcPyG'
+    'n8ADPfxrbxgXx2e2/nF6Zkp+JJB/FN7Es9OjU8m3jBx8sN7o0TACPX9TDfw5oXhjGYIW2Hp5gT'
+    'O21SvwfCuv1ZDKbXZqrBUhCbPE9yQ7KRCe+p0EQfM90kn9CqWiX8Brp/LXxqfUScjNWl4ReA0w'
+    'JUCw4Bd2elAAaBedjR2UBpTXdMICZQFdS5v8PgfJOk74ddFJ1nwE/O4vylAYFqFJPR34fqvfhl'
+    'xTJTTWdLYy1dXX+ubw4x7lkE9/MckhCKi/mORQWmoHh9YISkW/gsn6XzFZz8QjmuNOLhTgrf5o'
+    'nU+py30x7L/5WLpULuFIzuHvYLmq2cZKsRpnbzGXB4/IZAY3fwWTWU4gEIXeC6rfF9BBZ48gOP'
+    's48BEG08bJ9p/BybZIC4tNKoYFql+70nMqalDrbrXPdJrixbU2pOW/x0LWtfY3aDrno6Qjlbqq'
+    '2iepA/jZRpgdbiHXJOE0YJwA9yfgLOCrUWGm8ResK7+OdWWctermF/WQvS8f2yHuuJAc5j4FOL'
+    'nxix0NMJe3m85vSTgN+Fp1xHVwFjBOcvkkLEP/nwZ8nHuL/sauq6now+jDkS37UDK6XMEG4XVf'
+    'IGVDb36jhdB9T6KJe/M7XV5Ajw9m/X8y2W9mD3gy4JShSTgNGGe8wx4cRB+SPnDLjLSiNf8D5f'
+    '+Hkvw3y9qHkvw3S9uHkvwPlP8fSvI/8Pn/IeH/g/pbKvod8P/fgP+jW/Jfbv1fZgeAB7+DDtgb'
+    '/n5gMXVCbov24QbB6BM2rTufFO4ljUEgdEigcK/VmHFGgRfqyg2dv9nmVfXtJB+51x8pL5mkGB'
+    'WvopC7g+P8izN2STMt6Pu9Xhelstaf1h8aqaz1qd2hSZEdzH61OF8NenAQfUSGxnV2aCgPWo+N'
+    'lI6NjyTHRkrHxkeSYyOlY+MjybGR0rHxkeTYSPlj4yMyNn5Sf0tHf4Cx8XGMjVNbjg3WhalMV3'
+    '0ZwwNz/w8C1pLeZCGMjo+hmX9IElR+B48PL5OPfYi64WPJbkhrN3ws4NuUJJwGnNPw4w7Oopos'
+    'CWX7kzCW1T/ENlkM7/B+CaKnpd9utP3mNb5136W1755O9l1a++7pZN+lte+eTvZdWvvu6WTfpf'
+    '2+e1r6rsC/gY2fEFei61prmcxBpNM8Txz9hBMWzKD+hHPFMQP6E+KK838GigXRMwHrzP51QGNk'
+    'fr1SXjLq0IY5LdkS4O6plwAmoQ+42L9UWhChcINvxWcmRiZ6zFHl2B133Xln7zG57yhKYNm6rU'
+    'WDkZ8/i9s31DeP7FQksMApJ8TqUGAmrJxTlRVVv0CSaAn5qRwLIFE+k2RBIK3brlmizdR6Rpb7'
+    'WxVKRX8SsH706mb9aCOfIU7y03s8KADUpZ7HAqUBXaupqAXKAkL/73OQ9P2fSN8fUTwdfRo1nM'
+    'h3i3iiKYtarbOOMHhw8mu7PCgA1EFNdRAXfpVmYRcoC+jqaMgSljaEfRoi8T0qm0D3+RnU8Gc0'
+    'F2gOmbmNHG/xglputCINGs/PJDsGp8rPOGc+gdKAuvRaQ6AsoH2oMe2jmOD/V8B6z30eyjT/x4'
+    'AVnyuKZ6I/Fz/Khx3B7lpKfOXd9bc32lntwI+bmBqleLVsQzvgSSsg20ZAR8r17fCgANBOr6nQ'
+    'kv65NLWoUHv0ObzWnb8zHnJBVfhCDQkwy5VzHGSkBitFkySzQSdhSkdC8885f3mBuPwd3kiA9p'
+    'Ogvd4QhfrzcxKDZUChbdHnA87wfX3MBrL1uJXKwqt8G1X++WTl26jyzwc2Y4hAAaB9motToDQg'
+    '5OI8rlA2+gJK6s3fGg+7WAeecrJJm+oRAnXkF5KEQB/5BRBytQcFgPLRzR6UBoQ8O3cptD36Ik'
+    'rqyfeIAkckKV/5sWVnQKn4xSQZ0Cp+MckPqBW/CH50e1Aa0IHoYPibZqkOoy+hqFvyvxggpnJC'
+    'RyjOAZrRT4Sggk1MZ7Vn9fJGny89sdvhunSnSyOBxG8V2gt1Pq+tV3idNsluOLDAOotZm2ylVf'
+    'caHFLrvpRscEit+xIaHHtQGtBN0YHwhEI7oi/jtYH8YXOI1mA75qpZM+jZEEzs55nk9g7i9peT'
+    'le8ger4c2EywAgWAuqJeD0oD6osK4UmFdkZfQUn9+dsNPWi4Zsi1lXMGUvWC8Cj0aNpJNH0lSd'
+    'NOoukrSZp2Ek1fAU0HPSgN6NaoT6WYVHZX9FWU9NdBNKibg97jmCkBIUY9cRY9GnZRhfzmNR4U'
+    'ALrWq3AXVfhVqdBBWdQGZfY+B8lC+9cBa7PvUbwjeh41vADa+mzua168cCnTTKdkI/eo7CAqn0'
+    '9S2UFUPg8qb/GgNKDe6DYPyqJen8oOQ+ULQqUZZbujr6OGvwGVhSuhkv0rKgk6dxOdX0/SuZvo'
+    '/HqSm7uJzq8nubmb6PybJJ27DZ1/I3Tep3gE5/JU9C3QeUdrOkueUsCJsC3ojYjebybpjcR7/V'
+    'qixEFpQEejOzwoCwp8eiND77eEXrN/dUbfQQ3fBb23b81XlWEuS24nkfudJLmdRO53QG7Bg9KA'
+    'DkdHPCgLAnxyOw253xVy71c8F/0davgeyD2xNbkSR0wsPpVUzZq7xfaXI9r/Lkl7jmj/O9De70'
+    'FpQIeiQQ/Kghqf9pyh/XtC+72K74m+jxrepOPChptK2tq10EM3EruHhKPvJ+WyPUTs9yGXXe1B'
+    'aUDXRtd7UBbQDXTy2ucgIfb7ECCKdr51RS+ihlFa1ZPEimgD0ZGVoXAcWt9CfuwiOl9M0olEaS'
+    '+6ODkCpQHt80TbLqLzRYi2TkzsMnS+iF1/OPzHZm/dG/0QVfyIhlT+HfEk+ynJJcfaeg0xMpKC'
+    'B+tSseb3JO5vezX3xhZ3NBq/g29nuKh+c09c7y9VF/uXsZm6htP5UajyoQygHZ40uZd48UMcj/'
+    'd7UBrQNWo2I1AW0PVo4SHLjr2GHT/C2BsIl1hc/dlU9Lro5xFG435Wv5RNmlUjc7ABkL1DtQqd'
+    'SjXuKVXWCovlcwODh+/o3drSahfqkZoy0b7wzfwVp953SliIexK3gRL28DIXgXJkS9wBdpoikc'
+    'orpQc2A6UAwSzvdoUCRA2hs1P+Zol4ZFo57F3ce0appqTAvJj1oBSgHdFOEh8zemPxc3img8TH'
+    'loUjlOE80heq0axXASjjl7d7EJe3kw56ZxRKR+9J8Q3jxEtUsLRSWl7WKI5rpVUc00uPiqS1UF'
+    '5kY0OYxasbh0cHjpJcR7sHpQDhlnCaD2+/hFHzyxg1w/G0RnoTzwDOFV1xyqW6mIjB9HKrywW5'
+    'W0C1VGyWhvYx/ooh8kSK4w31xsOahFrjzJ2vGuWh1ni2zHbJMnmM2ukJFwrDqJyeSNloPEbdRB'
+    'DUTX+I1aEt+lW07DfRsn8ZxFM2iJmdDjw5ODfRuk1yxEmPTZbt+LRxZgrjIc7SCgu5KmsZznAZ'
+    'Z/iG5kzSQ+1MfHp2ekYtAI6jaRcEGZ+Y4QSsof62tbYOXMRh+1fBxSgc5q/g4vvBhw9A6zHQrG'
+    'FyLWtugrCzTdn5fsfONmXn+1P2gqpN2fn+FF9QOSiLqttpn+hyEA71hG6LToUfDRQOot9ABdfm'
+    'fyvg6MkmTApWXmpweZ3zs/KYEjPDeH7g8OCRo3yxVYoXS9VlzoFrw6uE2nEwcDwIY9N6ZaN80O'
+    'o8k1qr1w8egtbKBA+NF/U6TVO2VFuUb45CTDzx5zeS/AmkRdv16lGgNCBcPf4Kmp2JPphSz6j8'
+    'Pwy2uMLzjEd1e3o1Np6tPE8wiLAvfBCDSIxhMtlt0T9H2/4FreB8zZeRaz4H/i63JguPl98G+O'
+    '9TUTr/a0E8WYNZc4Xtu3hv0WsJQ5F/eSD+NyI1suUvInHaTKpI1r3Bu3RCMVK4HCfYeIzYsGVj'
+    'Ow3V1ODfxnDcS+usgTBz/vcUJ958U2KLst0htL7UZrVZT+5We/0aqGKu44YGOAUYplL3eHAQ/W'
+    '6KXXBuI7aZsrHgXyhvyKJvzVcMQQ31BaaMHQ1wCjCsK9/swano91J8/3a3CbWEdaJaLi/i5MnW'
+    '9DapuHVZVd0anE30ktuvCY3gQqMGmOuCptYnIB39GybWEGA5r84jIMXGWbUEXpYAbDNcaHsDnA'
+    'KMHe6kB7dFv59iO2MRaa1IuE7bzQWrRXRtpwVHGNNQK5ZlLqmrAU4Bhor2Xg/ORH+AZ6/KD3r2'
+    'hSjeis8SeBEVicbE04/45WMic1G5BjgFGDanxz24PfoYP5u/paFasFlWxUpVogyL2sMvEzpGfn'
+    '1XA5wCjFuI0x68Lfp3eLYzf2erFurXxSvuVOgYucCdDXAK8G4abG/14Gz0cRnWxVaVG0fCxQbN'
+    'DzsrGcIuSw0UjR9vHuNwzPy4jPEDDGOB+WSKb366kpHR1D2g0zxGI+KTbnPJ6MLxyZS98Mno5v'
+    'tJYfVxhYLoj/DaH6fooHOwuQpzv+D5rXu1Ykv7o2StgZS4XdWZGd3SCIIvQpeFsoDyqPeQj2LX'
+    'fwaywGk+lBiUDyV/nOJDyd2Kp6JPoepPw/inx2rzaQXg5buGDO04vPuLgkc6Ji6/70MZQOYwld'
+    'G16FMQ/q7xoDQguJJ1WSgLKAYt9/ooWvNpUH2SdXYZfvkzIsIcuMxtTgt6caHDb0YeFADqVOlB'
+    'IC4f0sPtCrVF/xGv/Rk6+EanJDwjNZ5htQunBmbB0ZbURvXxi9d4UADoWtWOC5QGBO14l4WygH'
+    'pR4yHbifY+5s+kE39C8Uz0f6OSz4K6W5N3SJ4KRq0uDc0emRlTQtaDAkBGhhcoDSin/lgCZQF1'
+    'oepDPoo++3Pps30eysR/VoifVLw9+gtU/XkQf7dYKWKuJ7qwVer4JiWBJRRm6X+RbA5Wzb9wMr'
+    'RAaUDGVlGgLKA9IMZx3Vqnf14IH1Z8W/QFVHKQdg8XS0HN1M74kSzO4GxIzamsa8xCj1bc7Xwh'
+    'OYNwt/OFlL3LyOi6+4WUvcsQKA3oQHSLDS74o4HwpYL95XY3RKToPh5ut0EpOGKWGB5yZA4OUM'
+    'FfEb+lWqrW6hyVIzMlX068I9zj5do1ZZ7osCWajLu3LRNXNufZlV+y7joS18SR0lLKuXXT906e'
+    'eDJ1vWTMLUya+BkPlFdW3lSlYylipdXv+4eFcDstIq+Lfi6IgvBTO7M7+Vtu8OmdMb9Dp9b4xO'
+    'YSny77Nf/uwTqdMTZKuNYpr4uZvsb0DxOBEg7daRL2FqsLhXiL+AiXD1uwpkT0zwsRA5zx0mat'
+    'rdQkFYSmqdQtA8h8pYrrIdBFpzAN8mxykoYJvWmfxA5HpllIvtb73gX3lNjvcptSlYtQtmBHLt'
+    'MNExT11gbCeB31Iz7wle16mXPn8OScpy2aMwYzV9iooILcrrxbWIc1r0ZNm+nIgdHBSok63gSm'
+    'biYCnsOOF4YIauPi5kLZ0RE6Qn4sOkKj70ieS+mVATgg8QXdKiJp01mr7lht9MFeymIW3bRR4+'
+    'pp4Ds2+mOrWnO/Md8rSIcBZ1UuqrZufQyNV2u5uljj9BpslrWKw53wZMNa3PCZITSROySlss1q'
+    'ay1o19YrGFjrGDtVL18xZxI4VZyOpydOzjwwNDUa02cEXCmOjI7EJx6kH0fj4YnJB6eK956aiU'
+    '9NjI2MTk3HQ+MjSK48M1U8MTszMTUd2oTM+AWJlkffMjk1Os1ZmIunJ8eKVJrLzdwXF8eHx2ZH'
+    'iuP39sVUArQyYTxWPF2coedmJvq42ub3kMX59OjU8Cn6OnSiOFaceZArPFmcGUdlJyemwngonh'
+    'yamikOz44NTcWTs1OTE9OjMVo2UpweHhsqnh4dKVD9VGc8ev/o+Ew8fWpobCzZ0DCeeGB8dEpz'
+    'SNtmxidGicqhE2OjqIrbOVKcGh2eQYPcp2FiHhE41hfGHPGFPhE/Rqk5Q1MP9mmh04iNQ60aGo'
+    'tHhk4P3Uut63kprlDHDM9OjSJKJFgxPXtieqY4MzszGt87MTHCzJ4enbq/ODw6fTwem5hmhs1O'
+    'j/axZfoQV01lELvod/p8Yna6yIwrjs+MTk3NcrjJXurlB4gzROUQvTvCHJ4YR2sxVkYnph5Ese'
+    'AD90Bf/MCpUcKnwFTm1hDYME1cG57xH6MKiYnUJNfOeHz03rHivaPjw6P4eQLFPFCcHu2lDisi'
+    'BDvKBJsfGHoQDjioGB1FdIXy2Ru6fdyfcfFkPDRyfxGU69M0AqaLOlyYbcOnlOca4CWm3WQ/B3'
+    'hBXunjHODlgH4GepOXk/smm5P7Zvp0QoPByGegB5DHWfNsy2egt9CnAc3JLZ/x6aCXv/ugzd+N'
+    'TNA3ak5u+fx317Au6B26Beafu4ZGud19nWsBh+yuVapsOqo+BovlNVpF1BsezlKMP84q1PV4pb'
+    'ZQWqE1qLRSRgSSPlpxsAuodRpHqeMNQv0UsKYuIS+L3TnMD9gYICzwdz4XrWwaJ6SyFsQnfevI'
+    'zf6I8Higl+PZmeF4tbJY5ZUdiWjuK1U3sR0c7osP3/X6Q32edetKeY1W/vje9fJyjRboqqVeTQ'
+    'yg3awuaqrnFk/BRo5WyUXWL1woE0LMwEKIrX+1Ut3knNS0it5xyLZvpVZdLsRj5dKaazI90V1f'
+    'pffLi9209MpGXK1BR70W6mOxuP9X6r5yhUWSNeyxsrFLmIpS/PDg0X5atqlXKlUqlspA6Y/0XF'
+    '74QH8O8JO9Ji/MOks7CJcEfdKhQ4cO9/PfmUOHjvHfh9D0u+hP/+HB/iOHZwaPHLv9LvpbuMv8'
+    'eagQn7jA+bdoc1oQSxJtIpfeh6gB5Wod7g8SHEjsaajRdPjfkP5Vy+KHp04Oh/GRI0fucm3hqB'
+    'vljSWOurG+tID/44nCxmMbvZDcyuraw5Hdb7KpYlwo8fjwMTZuou7y5gJXSBO++Jb4DDjT04sj'
+    'kKS2sQ9ZIVQDszvxGaHdtIN7+PXx2bGx3t6Wz/F47znU62WyiQdfiqbl8gZKqS0tli54tEl4B6'
+    '6AXV/PaY2Jx2/ZONcXM0HHX2mTzhU2zuHb5VokD5EIskAyzWEaPYkWHtmyhQ9UqkcG4zP3ljem'
+    'L9Q3yqv4eah+srLC0UW9xp4sjo3O0D4cL20oGVu9c8vShqF0lvaoO44SwdDivyHu6ekRpHdpo7'
+    'B4/hQtHCMaB7U3vvvu+Mhgb/xTMf82Vjtvfjru0rMMgd7F2vk6F6n5o7w1rF6wD8gqdfiO5mlk'
+    'S8Prh+84evTo64/cccgtG/PlJej5ZquVx0wptJg1llJ4ZZ3ZI+0nVghTBriz8KeXTkEeOS8xgl'
+    'EO2GXKOeCVwwOgNzEAjm45AO4rnSvFZ6QjC6qtwSOnkdCz7g0ArKa00gJFnqktX7jMMKf3LFqo'
+    'ls+f2KwgL2FPLxo2rRzSKoQxvS55A54Zl7azb16PeVKars1mDvQWEJhkkWlxPLh9Sx4YJZXx8J'
+    'u8QJJ41TS8Jfk9vY19Q9Nh2HGDfscKeN80CWHqCElAsSqInGnFNNHjk8YpS2zn6itpElhhWX5Z'
+    'q7JUhR0dzimSfTU0ifKosu6L2E0v9V9cpSPNWfqXFq1LMxexpV06dpF2VvovDd5LDxcuQojAQL'
+    '70yEPdIaKL0CyRt1kJtHK+dKFuEtOxpwVHM8DeuFhZhgGFmLxrTX0xV0VirlRG31GbRLfhKnm3'
+    'fry8Xutfc5EaztdMafAi1GRWKt1AKtKJZlKoYXtbrsWba7x5mld7KoVyQcHDrWWgXiIM9dcS+a'
+    'a6HyKpYXMJSeCMklwsZTAOWD7r6SaxqLv3eAINRYx6+2YFPuPwTWG1kAyGOp9YK48jYJDYpCor'
+    'oXqAjNVTqnsq+Xna4YmMXgnvQmdEZzLaMJQ4NmGiqrXSet1Vg/yGRpmMxDhrG3zLznXiXTlSmz'
+    'bUm+jgi+WlJZqXLMTgwrosc60v7h48dPj1WDMP3z5z6PCxI4eOHb69cOgwsU9GNy29+G4X3bVS'
+    'naRRfpLrp4O9lSZv74tRWkEnEC1Y0xz7ro9tCnwBphSPsDMDB44U2adSN4PdBhYLbew+mk8bte'
+    'L0xDRPsp7eFmJbYbX2OK0zEmmsXO2fnZZ8Qg+U5wccKQNT5kZ84N6V2nxpZW5CglcOgKABr5Le'
+    'UOMrFtAYWWn6eJ5rOMEzkKPA9IL5cMY0SO2OtLUI4tiqidSoM7RqLPGrXouI6sKarGxoy+CAps'
+    'ZlYbSA+Gg38Sfzbi9rJEI7kE0l0E/EBw882H9gtf/A4syBU8cOnD52YLpwYOmhgyRuVx4tn6/U'
+    'yyz8g0Gul2g8S2n31RZLPFgP1olWYo3Z6k/KYrWoX2n3eaRH9Hi6zr2N3mTq8aGfpejSWoU7xK'
+    'AiWwutA81lcztNBQcGR+hvGPeCkbV51p+VtJ0bnCNzjScIHZqW2RvIOoLzNKvLsmz5TyPUhcF8'
+    'R5SNOsNfNwmu1OO/K/8LgR9J1Ix/qgHDnvksjuVO/ghbCyDxaXXUuNyBIWx1YnhIYmDU6UzXkH'
+    'yTadzmQRxHIOsl79I4Arhq+JppWxC9S7y3PhvE47Vqf7W8LAfGxLGzZI5XOHG1PnaO64v2JHbO'
+    'U9y7wljTKBE7+L6y6tfJReuLmrdWTrLURzhBmmN2I//0dNWn/w9b8ggXgu9K8iiQ5me9BKW4EH'
+    'wX+52ZC4D/Bpwj87Y=')))
+_INDEX = {
+    f.name: {
+      'descriptor': f,
+      'services': {s.name: s for s in f.service},
+    }
+    for f in FILE_DESCRIPTOR_SET.file
+}
+
+
+IssuesServiceDescription = {
+  'file_descriptor_set': FILE_DESCRIPTOR_SET,
+  'file_descriptor': _INDEX[u'api/v3/api_proto/issues.proto']['descriptor'],
+  'service_descriptor': _INDEX[u'api/v3/api_proto/issues.proto']['services'][u'Issues'],
+}
diff --git a/api/v3/api_proto/permission_objects.proto b/api/v3/api_proto/permission_objects.proto
new file mode 100644
index 0000000..60f8169
--- /dev/null
+++ b/api/v3/api_proto/permission_objects.proto
@@ -0,0 +1,43 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd
+
+// This file defines protobufs for features and related business
+// objects, e.g., hotlists.
+
+syntax = "proto3";
+
+package monorail.v3;
+
+option go_package = "api/v3/api_proto";
+
+// All possible permissions on the Monorail site.
+// Next available tag: 6
+enum Permission {
+  // Default value. This value is unused.
+  PERMISSION_UNSPECIFIED = 0;
+  // The permission needed to add and remove issues from a hotlist.
+  HOTLIST_EDIT = 1;
+  // The permission needed to delete a hotlist or change hotlist
+  // settings/members.
+  HOTLIST_ADMINISTER = 2;
+  // The permission needed to edit an issue.
+  ISSUE_EDIT = 3;
+  // The permission needed to edit a custom field definition.
+  FIELD_DEF_EDIT = 4;
+  // The permission needed to edit the value of a custom field.
+  // More permissions will be required in the specific issue
+  // where the user plans to edit that value, e.g. ISSUE_EDIT.
+  FIELD_DEF_VALUE_EDIT = 5;
+}
+
+
+// The set of a user's permissions for a single resource.
+// Next available tag: 3
+message PermissionSet {
+  // The name of the resource `permissions` applies to.
+  string resource = 1;
+  // All the permissions a user has for `resource`.
+  repeated Permission permissions = 2;
+}
diff --git a/api/v3/api_proto/permission_objects_pb2.py b/api/v3/api_proto/permission_objects_pb2.py
new file mode 100644
index 0000000..2ea90db
--- /dev/null
+++ b/api/v3/api_proto/permission_objects_pb2.py
@@ -0,0 +1,133 @@
+# -*- coding: utf-8 -*-
+# Generated by the protocol buffer compiler.  DO NOT EDIT!
+# source: api/v3/api_proto/permission_objects.proto
+
+from google.protobuf.internal import enum_type_wrapper
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import message as _message
+from google.protobuf import reflection as _reflection
+from google.protobuf import symbol_database as _symbol_database
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+
+
+DESCRIPTOR = _descriptor.FileDescriptor(
+  name='api/v3/api_proto/permission_objects.proto',
+  package='monorail.v3',
+  syntax='proto3',
+  serialized_options=b'Z\020api/v3/api_proto',
+  create_key=_descriptor._internal_create_key,
+  serialized_pb=b'\n)api/v3/api_proto/permission_objects.proto\x12\x0bmonorail.v3\"O\n\rPermissionSet\x12\x10\n\x08resource\x18\x01 \x01(\t\x12,\n\x0bpermissions\x18\x02 \x03(\x0e\x32\x17.monorail.v3.Permission*\x90\x01\n\nPermission\x12\x1a\n\x16PERMISSION_UNSPECIFIED\x10\x00\x12\x10\n\x0cHOTLIST_EDIT\x10\x01\x12\x16\n\x12HOTLIST_ADMINISTER\x10\x02\x12\x0e\n\nISSUE_EDIT\x10\x03\x12\x12\n\x0e\x46IELD_DEF_EDIT\x10\x04\x12\x18\n\x14\x46IELD_DEF_VALUE_EDIT\x10\x05\x42\x12Z\x10\x61pi/v3/api_protob\x06proto3'
+)
+
+_PERMISSION = _descriptor.EnumDescriptor(
+  name='Permission',
+  full_name='monorail.v3.Permission',
+  filename=None,
+  file=DESCRIPTOR,
+  create_key=_descriptor._internal_create_key,
+  values=[
+    _descriptor.EnumValueDescriptor(
+      name='PERMISSION_UNSPECIFIED', index=0, number=0,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='HOTLIST_EDIT', index=1, number=1,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='HOTLIST_ADMINISTER', index=2, number=2,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='ISSUE_EDIT', index=3, number=3,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='FIELD_DEF_EDIT', index=4, number=4,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='FIELD_DEF_VALUE_EDIT', index=5, number=5,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+  ],
+  containing_type=None,
+  serialized_options=None,
+  serialized_start=140,
+  serialized_end=284,
+)
+_sym_db.RegisterEnumDescriptor(_PERMISSION)
+
+Permission = enum_type_wrapper.EnumTypeWrapper(_PERMISSION)
+PERMISSION_UNSPECIFIED = 0
+HOTLIST_EDIT = 1
+HOTLIST_ADMINISTER = 2
+ISSUE_EDIT = 3
+FIELD_DEF_EDIT = 4
+FIELD_DEF_VALUE_EDIT = 5
+
+
+
+_PERMISSIONSET = _descriptor.Descriptor(
+  name='PermissionSet',
+  full_name='monorail.v3.PermissionSet',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='resource', full_name='monorail.v3.PermissionSet.resource', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='permissions', full_name='monorail.v3.PermissionSet.permissions', index=1,
+      number=2, type=14, cpp_type=8, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=58,
+  serialized_end=137,
+)
+
+_PERMISSIONSET.fields_by_name['permissions'].enum_type = _PERMISSION
+DESCRIPTOR.message_types_by_name['PermissionSet'] = _PERMISSIONSET
+DESCRIPTOR.enum_types_by_name['Permission'] = _PERMISSION
+_sym_db.RegisterFileDescriptor(DESCRIPTOR)
+
+PermissionSet = _reflection.GeneratedProtocolMessageType('PermissionSet', (_message.Message,), {
+  'DESCRIPTOR' : _PERMISSIONSET,
+  '__module__' : 'api.v3.api_proto.permission_objects_pb2'
+  # @@protoc_insertion_point(class_scope:monorail.v3.PermissionSet)
+  })
+_sym_db.RegisterMessage(PermissionSet)
+
+
+DESCRIPTOR._options = None
+# @@protoc_insertion_point(module_scope)
diff --git a/api/v3/api_proto/permissions.proto b/api/v3/api_proto/permissions.proto
new file mode 100644
index 0000000..9efba1f
--- /dev/null
+++ b/api/v3/api_proto/permissions.proto
@@ -0,0 +1,61 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+syntax = "proto3";
+
+package monorail.v3;
+
+option go_package = "api/v3/api_proto";
+
+import "google/api/field_behavior.proto";
+import "api/v3/api_proto/permission_objects.proto";
+
+// ***DO NOT CALL rpcs IN THIS SERVICE.***
+// This service is for Monorail's frontend only.
+
+// Permissions service includes all methods needed for fetching permissions.
+service Permissions {
+  // status: DO NOT USE
+  // Returns the requester's permissions for the given resource.
+  //
+  // Raises:
+  //  PERMISSION_DENIED if the given resource does not exist and/or the
+  //      requester does not have permission to view the resource's name space.
+  //  NOT_FOUND if the given resource does not exist.
+  rpc GetPermissionSet (GetPermissionSetRequest) returns (PermissionSet) {}
+
+  // status: DO NOT USE
+  // Returns the requester's permissions for all the given resources.
+  //
+  // Raises:
+  //  PERMISSION_DENIED if any of the given resources do not exist and/or the
+  //      requester does not have permission to view one of the resource's
+  //      name space.
+  // NOT_FOUND if one of the given resources do not exist.
+  rpc BatchGetPermissionSets (BatchGetPermissionSetsRequest) returns (BatchGetPermissionSetsResponse) {}
+}
+
+
+// Request message for the GetPermissionSet emthod.
+// Next available tag: 2
+message GetPermissionSetRequest {
+  // The resource name of the resource permissions to retrieve.
+  string name = 1 [ (google.api.field_behavior) = REQUIRED ];
+}
+
+
+// Request message for the BatchGetPermissionSets method.
+// Next available tag: 2
+message BatchGetPermissionSetsRequest {
+  // The resource names of the resource permissions to retrieve.
+  repeated string names = 1 [ (google.api.field_behavior) = REQUIRED ];
+}
+
+
+// Response message for the BatchGetPermissionSets method.
+// Next available tag: 2
+message BatchGetPermissionSetsResponse {
+  // The Permissions, one for each of the given resources.
+  repeated PermissionSet permission_sets = 1;
+}
diff --git a/api/v3/api_proto/permissions_pb2.py b/api/v3/api_proto/permissions_pb2.py
new file mode 100644
index 0000000..c22e0ab
--- /dev/null
+++ b/api/v3/api_proto/permissions_pb2.py
@@ -0,0 +1,193 @@
+# -*- coding: utf-8 -*-
+# Generated by the protocol buffer compiler.  DO NOT EDIT!
+# source: api/v3/api_proto/permissions.proto
+
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import message as _message
+from google.protobuf import reflection as _reflection
+from google.protobuf import symbol_database as _symbol_database
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+from google.api import field_behavior_pb2 as google_dot_api_dot_field__behavior__pb2
+from api.v3.api_proto import permission_objects_pb2 as api_dot_v3_dot_api__proto_dot_permission__objects__pb2
+
+
+DESCRIPTOR = _descriptor.FileDescriptor(
+  name='api/v3/api_proto/permissions.proto',
+  package='monorail.v3',
+  syntax='proto3',
+  serialized_options=b'Z\020api/v3/api_proto',
+  create_key=_descriptor._internal_create_key,
+  serialized_pb=b'\n\"api/v3/api_proto/permissions.proto\x12\x0bmonorail.v3\x1a\x1fgoogle/api/field_behavior.proto\x1a)api/v3/api_proto/permission_objects.proto\",\n\x17GetPermissionSetRequest\x12\x11\n\x04name\x18\x01 \x01(\tB\x03\xe0\x41\x02\"3\n\x1d\x42\x61tchGetPermissionSetsRequest\x12\x12\n\x05names\x18\x01 \x03(\tB\x03\xe0\x41\x02\"U\n\x1e\x42\x61tchGetPermissionSetsResponse\x12\x33\n\x0fpermission_sets\x18\x01 \x03(\x0b\x32\x1a.monorail.v3.PermissionSet2\xda\x01\n\x0bPermissions\x12V\n\x10GetPermissionSet\x12$.monorail.v3.GetPermissionSetRequest\x1a\x1a.monorail.v3.PermissionSet\"\x00\x12s\n\x16\x42\x61tchGetPermissionSets\x12*.monorail.v3.BatchGetPermissionSetsRequest\x1a+.monorail.v3.BatchGetPermissionSetsResponse\"\x00\x42\x12Z\x10\x61pi/v3/api_protob\x06proto3'
+  ,
+  dependencies=[google_dot_api_dot_field__behavior__pb2.DESCRIPTOR,api_dot_v3_dot_api__proto_dot_permission__objects__pb2.DESCRIPTOR,])
+
+
+
+
+_GETPERMISSIONSETREQUEST = _descriptor.Descriptor(
+  name='GetPermissionSetRequest',
+  full_name='monorail.v3.GetPermissionSetRequest',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='name', full_name='monorail.v3.GetPermissionSetRequest.name', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\340A\002', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=127,
+  serialized_end=171,
+)
+
+
+_BATCHGETPERMISSIONSETSREQUEST = _descriptor.Descriptor(
+  name='BatchGetPermissionSetsRequest',
+  full_name='monorail.v3.BatchGetPermissionSetsRequest',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='names', full_name='monorail.v3.BatchGetPermissionSetsRequest.names', index=0,
+      number=1, type=9, cpp_type=9, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\340A\002', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=173,
+  serialized_end=224,
+)
+
+
+_BATCHGETPERMISSIONSETSRESPONSE = _descriptor.Descriptor(
+  name='BatchGetPermissionSetsResponse',
+  full_name='monorail.v3.BatchGetPermissionSetsResponse',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='permission_sets', full_name='monorail.v3.BatchGetPermissionSetsResponse.permission_sets', index=0,
+      number=1, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=226,
+  serialized_end=311,
+)
+
+_BATCHGETPERMISSIONSETSRESPONSE.fields_by_name['permission_sets'].message_type = api_dot_v3_dot_api__proto_dot_permission__objects__pb2._PERMISSIONSET
+DESCRIPTOR.message_types_by_name['GetPermissionSetRequest'] = _GETPERMISSIONSETREQUEST
+DESCRIPTOR.message_types_by_name['BatchGetPermissionSetsRequest'] = _BATCHGETPERMISSIONSETSREQUEST
+DESCRIPTOR.message_types_by_name['BatchGetPermissionSetsResponse'] = _BATCHGETPERMISSIONSETSRESPONSE
+_sym_db.RegisterFileDescriptor(DESCRIPTOR)
+
+GetPermissionSetRequest = _reflection.GeneratedProtocolMessageType('GetPermissionSetRequest', (_message.Message,), {
+  'DESCRIPTOR' : _GETPERMISSIONSETREQUEST,
+  '__module__' : 'api.v3.api_proto.permissions_pb2'
+  # @@protoc_insertion_point(class_scope:monorail.v3.GetPermissionSetRequest)
+  })
+_sym_db.RegisterMessage(GetPermissionSetRequest)
+
+BatchGetPermissionSetsRequest = _reflection.GeneratedProtocolMessageType('BatchGetPermissionSetsRequest', (_message.Message,), {
+  'DESCRIPTOR' : _BATCHGETPERMISSIONSETSREQUEST,
+  '__module__' : 'api.v3.api_proto.permissions_pb2'
+  # @@protoc_insertion_point(class_scope:monorail.v3.BatchGetPermissionSetsRequest)
+  })
+_sym_db.RegisterMessage(BatchGetPermissionSetsRequest)
+
+BatchGetPermissionSetsResponse = _reflection.GeneratedProtocolMessageType('BatchGetPermissionSetsResponse', (_message.Message,), {
+  'DESCRIPTOR' : _BATCHGETPERMISSIONSETSRESPONSE,
+  '__module__' : 'api.v3.api_proto.permissions_pb2'
+  # @@protoc_insertion_point(class_scope:monorail.v3.BatchGetPermissionSetsResponse)
+  })
+_sym_db.RegisterMessage(BatchGetPermissionSetsResponse)
+
+
+DESCRIPTOR._options = None
+_GETPERMISSIONSETREQUEST.fields_by_name['name']._options = None
+_BATCHGETPERMISSIONSETSREQUEST.fields_by_name['names']._options = None
+
+_PERMISSIONS = _descriptor.ServiceDescriptor(
+  name='Permissions',
+  full_name='monorail.v3.Permissions',
+  file=DESCRIPTOR,
+  index=0,
+  serialized_options=None,
+  create_key=_descriptor._internal_create_key,
+  serialized_start=314,
+  serialized_end=532,
+  methods=[
+  _descriptor.MethodDescriptor(
+    name='GetPermissionSet',
+    full_name='monorail.v3.Permissions.GetPermissionSet',
+    index=0,
+    containing_service=None,
+    input_type=_GETPERMISSIONSETREQUEST,
+    output_type=api_dot_v3_dot_api__proto_dot_permission__objects__pb2._PERMISSIONSET,
+    serialized_options=None,
+    create_key=_descriptor._internal_create_key,
+  ),
+  _descriptor.MethodDescriptor(
+    name='BatchGetPermissionSets',
+    full_name='monorail.v3.Permissions.BatchGetPermissionSets',
+    index=1,
+    containing_service=None,
+    input_type=_BATCHGETPERMISSIONSETSREQUEST,
+    output_type=_BATCHGETPERMISSIONSETSRESPONSE,
+    serialized_options=None,
+    create_key=_descriptor._internal_create_key,
+  ),
+])
+_sym_db.RegisterServiceDescriptor(_PERMISSIONS)
+
+DESCRIPTOR.services_by_name['Permissions'] = _PERMISSIONS
+
+# @@protoc_insertion_point(module_scope)
diff --git a/api/v3/api_proto/permissions_prpc_pb2.py b/api/v3/api_proto/permissions_prpc_pb2.py
new file mode 100644
index 0000000..3afdc60
--- /dev/null
+++ b/api/v3/api_proto/permissions_prpc_pb2.py
@@ -0,0 +1,432 @@
+# Generated by the pRPC protocol buffer compiler plugin.  DO NOT EDIT!
+# source: api/v3/api_proto/permissions.proto
+
+import base64
+import zlib
+
+from google.protobuf import descriptor_pb2
+
+# Includes description of the api/v3/api_proto/permissions.proto and all of its transitive
+# dependencies. Includes source code info.
+FILE_DESCRIPTOR_SET = descriptor_pb2.FileDescriptorSet()
+FILE_DESCRIPTOR_SET.ParseFromString(zlib.decompress(base64.b64decode(
+    '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')))
+_INDEX = {
+    f.name: {
+      'descriptor': f,
+      'services': {s.name: s for s in f.service},
+    }
+    for f in FILE_DESCRIPTOR_SET.file
+}
+
+
+PermissionsServiceDescription = {
+  'file_descriptor_set': FILE_DESCRIPTOR_SET,
+  'file_descriptor': _INDEX[u'api/v3/api_proto/permissions.proto']['descriptor'],
+  'service_descriptor': _INDEX[u'api/v3/api_proto/permissions.proto']['services'][u'Permissions'],
+}
diff --git a/api/v3/api_proto/project_objects.proto b/api/v3/api_proto/project_objects.proto
new file mode 100644
index 0000000..0c2a4c1
--- /dev/null
+++ b/api/v3/api_proto/project_objects.proto
@@ -0,0 +1,543 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd
+
+// This file defines protobufs for projects and their resources.
+
+syntax = "proto3";
+
+package monorail.v3;
+
+option go_package = "api/v3/api_proto";
+
+import "google/protobuf/timestamp.proto";
+import "google/api/field_behavior.proto";
+import "google/api/resource.proto";
+import "api/v3/api_proto/issue_objects.proto";
+import "api/v3/api_proto/permission_objects.proto";
+
+// The top level organization of issues in Monorail.
+//
+// See monorail/doc/userguide/concepts.md#Projects-and-roles.
+// and monorail/doc/userguide/project-owners.md#why-does-monorail-have-projects
+// Next available tag: 5
+message Project {
+  option (google.api.resource) = {
+    type: "api.crbug.com/Project"
+    pattern: "projects/{project}"
+  };
+
+  // Resource name of the project.
+  string name = 1;
+  // Display name of the project.
+  string display_name = 2 [(google.api.field_behavior) = IMMUTABLE];
+  // Summary of the project, ie describing what its use and purpose.
+  string summary = 3;
+  // URL pointing to this project's logo image.
+  string thumbnail_url = 4;
+}
+
+// Potential steps along the development process that an issue can be in.
+//
+// See monorail/doc/userguide/project-owners.md#How-to-configure-statuses
+// (-- aip.dev/not-precedent: "Status" should be reserved for HTTP/gRPC codes
+//     per aip.dev/216. Monorail's Status  preceded the AIP standards, and is
+//     used extensively throughout the system.)
+// Next available tag: 7
+message StatusDef {
+  option (google.api.resource) = {
+    type: "api.crbug.com/StatusDef"
+    pattern: "projects/{project}/statusDefs/{status_def}"
+  };
+
+  // Type of this status.
+  // Next available tag: 4
+  enum StatusDefType {
+    // Default enum value. This value is unused.
+    STATUS_DEF_TYPE_UNSPECIFIED = 0;
+    // This status means issue is open.
+    OPEN = 1;
+    // This status means issue is closed.
+    CLOSED = 2;
+    // This status means issue is merged into another.
+    MERGED = 3;
+  }
+
+  // State of this status.
+  // Next available tag: 3
+  enum StatusDefState {
+    // Default value. This value is unused.
+    STATUS_DEF_STATE_UNSPECIFIED = 0;
+    // This status is deprecated
+    DEPRECATED = 1;
+    // This status is not deprecated
+    ACTIVE = 2;
+  }
+
+  // Resource name of the status.
+  string name = 1;
+  // String value of the status.
+  string value = 2;
+  // Type of this status.
+  StatusDefType type = 3;
+  // Sorting rank of this status. If we sort issues by status
+  // this rank determines the sort order rather than status value.
+  uint32 rank = 4;
+  // Brief explanation of this status.
+  string docstring = 5;
+  // State of this status.
+  StatusDefState state = 6;
+}
+
+// Well-known labels that can be applied to issues within the project.
+//
+// See monorail/doc/userguide/concepts.md#issue-fields-and-labels.
+// Next available tag: 5
+// Labels defined in this project.
+message LabelDef {
+
+  option (google.api.resource) = {
+    type: "api.crbug.com/LabelDef"
+    pattern: "projects/{project}/labelDefs/{label_def}"
+  };
+
+  // State of this label.
+  // Next available tag: 3
+  enum LabelDefState {
+    // Default enum value. This value is unused.
+    LABEL_DEF_STATE_UNSPECIFIED = 0;
+    // This label is deprecated
+    DEPRECATED = 1;
+    // This label is not deprecated
+    ACTIVE = 2;
+  }
+
+  // Resource name of the label.
+  string name = 1;
+  // String value of the label.
+  string value = 2;
+  // Brief explanation of this label.
+  string docstring = 3;
+  // State of this label.
+  LabelDefState state = 4;
+}
+
+// Custom fields defined for the project.
+//
+// See monorail/doc/userguide/concepts.md#issue-fields-and-labels.
+// Check bugs.chromium.org/p/{project}/adminLabels to see the FieldDef IDs.
+// If your code needs to call multiple monorail instances
+// (e.g. monorail-{prod|staging|dev}) FieldDef IDs for FieldDefs
+// with the same display_name will differ between each monorail
+// instance. To see what FieldDef ID to use when calling staging
+// you must check bugs-staging.chromium.org/p/{project}/adminLabels.
+// Next available tag: 15
+message FieldDef {
+  option (google.api.resource) = {
+    type: "api.crbug.com/FieldDef"
+    pattern: "projects/{project}/fieldDefs/{field_def_id}"
+  };
+
+  // Resource name of the field.
+  string name = 1;
+  // Display name of the field.
+  string display_name = 2 [(google.api.field_behavior) = IMMUTABLE];
+  // Brief explanation of this field.
+  string docstring = 3;
+  // Type of this field.
+  // Next available tag: 7
+  enum Type {
+    // Default enum value. This value is unused.
+    TYPE_UNSPECIFIED = 0;
+    // This field can be filled only with enumerated option(s).
+    ENUM = 1;
+    // This field can be filled with integer(s).
+    INT = 2;
+    // This field can be filled with string(s).
+    STR = 3;
+    // This field can be filled with user(s).
+    USER = 4;
+    // This field can be filled with date(s).
+    DATE = 5;
+    // This field can be filled with URL(s).
+    URL = 6;
+  }
+  Type type = 4 [(google.api.field_behavior) = IMMUTABLE];
+
+  // Type of issue this field applies: ie Bug or Enhancement.
+  // Note: type is indicated by any "Type-foo" label or "Type" custom field.
+  string applicable_issue_type = 5;
+  // Administrators of this field.
+  repeated string admins = 6 [
+      (google.api.resource_reference) = { type: "api.crbug.com/User" }
+  ];
+
+  // Traits of this field, ie is required or can support multiple values.
+  // Next available tag: 6
+  enum Traits {
+    // Default enum value. This value is unused.
+    TRAITS_UNSPECIFIED = 0;
+    // This field must be filled out in issues where it's applicable.
+    REQUIRED = 1;
+    // This field defaults to hidden.
+    DEFAULT_HIDDEN = 2;
+    // This field can have multiple values.
+    MULTIVALUED = 3;
+    // This is a phase field, meaning it is repeated for each phase of an
+    // approval process. It cannot be the child of a particular approval.
+    PHASE = 4;
+    // Values of this field can only be edited in issues/templates by editors.
+    // Project owners and field admins are not subject of this restriction.
+    RESTRICTED = 5;
+  }
+  repeated Traits traits = 7;
+
+  // ApprovalDef that this field belongs to, if applicable.
+  // A field may not both have `approval_parent` set and have the PHASE trait.
+  string approval_parent = 8 [
+      (google.api.resource_reference) = { type: "api.crbug.com/ApprovalDef" },
+      (google.api.field_behavior) = IMMUTABLE
+  ];
+
+  // Settings specific to enum type fields.
+  // Next available tag: 2
+  message EnumTypeSettings {
+    // One available choice for an enum field.
+    // Next available tag: 3
+    message Choice {
+      // Value of this choice.
+      string value = 1;
+      // Brief explanation of this choice.
+      string docstring = 2;
+    }
+    repeated Choice choices = 1;
+  }
+  EnumTypeSettings enum_settings = 9;
+
+  // Settings specific to int type fields.
+  // Next available tag: 3
+  message IntTypeSettings {
+    // Minimum value that this field can have.
+    int32 min_value = 1;
+    // Maximum value that this field can have.
+    int32 max_value = 2;
+  }
+  IntTypeSettings int_settings = 10;
+
+  // Settings specific to str type fields.
+  // Next available tag: 2
+  message StrTypeSettings {
+    // Regex that this field value(s) must match.
+    string regex = 1;
+  }
+  StrTypeSettings str_settings = 11;
+
+  // Settings specific to user type fields.
+  // Next available tag: 5
+  message UserTypeSettings {
+    // Event that triggers a notification.
+    // Next available tag: 3
+    enum NotifyTriggers {
+      // Default notify trigger value. This value is unused.
+      NOTIFY_TRIGGERS_UNSPECIFIED = 0;
+      // There are no notifications.
+      NEVER = 1;
+      // Notify whenever any comment is made.
+      ANY_COMMENT = 2;
+    }
+    NotifyTriggers notify_triggers = 1;
+    // Field value(s) can only be set to users that fulfill the role
+    // requirements.
+    // Next available tag: 3
+    enum RoleRequirements {
+      // Default role requirement value. This value is unused.
+      ROLE_REQUIREMENTS_UNSPECIFIED = 0;
+      // There is no requirement.
+      NO_ROLE_REQUIREMENT = 1;
+      // Field value(s) can only be set to users who are members.
+      PROJECT_MEMBER = 2;
+    }
+    RoleRequirements role_requirements = 2;
+    // User(s) named in this field are granted this permission in the issue.
+    string grants_perm = 3;
+    // Field value(s) can only be set to users with this permission.
+    string needs_perm = 4;
+  }
+  UserTypeSettings user_settings = 12;
+
+  // Settings specific to date type fields.
+  // Next available tag: 2
+  message DateTypeSettings {
+    // Action to do when a date field value arrives.
+    // Next available tag: 4
+    enum DateAction {
+      // Default date action value. This value is unused.
+      DATE_ACTION_UNSPECIFIED = 0;
+      // No action will be taken when a date arrives.
+      NO_ACTION = 1;
+      // Notify owner only when a date arrives.
+      NOTIFY_OWNER = 2;
+      // Notify all participants when a date arrives.
+      NOTIFY_PARTICIPANTS = 3;
+    }
+    DateAction date_action = 1;
+  }
+  DateTypeSettings date_settings = 13;
+
+  // Editors of this field, only for RESTRICTED fields.
+  repeated string editors = 14 [
+      (google.api.resource_reference) = { type: "api.crbug.com/User" }
+  ];
+}
+
+// A high level definition of the part of the software affected by an issue.
+//
+// See monorail/doc/userguide/project-owners.md#how-to-configure-components.
+// Check crbug.com/p/{project}/adminComponents to see the ComponenttDef IDs.
+// Next available tag: 12
+message ComponentDef {
+  option (google.api.resource) = {
+    type: "api.crbug.com/ComponentDef"
+    pattern: "projects/{project}/componentDefs/{component_def_id}"
+  };
+
+  // The current state of the component definition.
+  // Next available tag: 3
+  enum ComponentDefState {
+    // Default enum value. This value is unused.
+    COMPONENT_DEF_STATE_UNSPECIFIED = 0;
+    // This component is deprecated
+    DEPRECATED = 1;
+    // This component is not deprecated
+    ACTIVE = 2;
+  }
+
+  // Resource name of the component, aka identifier.
+  // the API will always return ComponentDef names with format:
+  // projects/{project}/componentDefs/<component_def_id>.
+  // However the API will accept ComponentDef names with formats:
+  // projects/{project}/componentDefs/<component_def_id>|<value>.
+  string name = 1;
+  // String value of the component, ie 'Tools>Stability' or 'Blink'.
+  string value = 2;
+  // Brief explanation of this component.
+  string docstring = 3;
+  // Administrators of this component.
+  repeated string admins = 4 [
+      (google.api.resource_reference) = { type: "api.crbug.com/User" }
+  ];
+  // Auto cc'ed users of this component.
+  repeated string ccs = 5 [
+      (google.api.resource_reference) = { type: "api.crbug.com/User" }
+  ];
+  // State of this component.
+  ComponentDefState state = 6;
+  // The user that created this component.
+  string creator = 7 [
+      (google.api.resource_reference) = { type: "api.crbug.com/User" },
+      (google.api.field_behavior) = OUTPUT_ONLY
+  ];
+  // The user that last modified this component.
+  string modifier = 8 [
+      (google.api.resource_reference) = { type: "api.crbug.com/User" },
+      (google.api.field_behavior) = OUTPUT_ONLY
+  ];
+  // The time this component was created.
+  google.protobuf.Timestamp create_time = 9 [
+      (google.api.field_behavior) = OUTPUT_ONLY
+  ];
+  // The time this component was last modified.
+  google.protobuf.Timestamp modify_time = 10 [
+      (google.api.field_behavior) = OUTPUT_ONLY
+  ];
+  // Labels that auto-apply to issues in this component.
+  repeated string labels = 11;
+}
+
+// Defines approvals that issues within the project may need.
+// See monorail/doc/userguide/concepts.md#issue-approvals-and-gates and
+// monorail/doc/userguide/project-owners.md#How-to-configure-approvals
+// Check bugs.chromium.org/p/{project}/adminLabels to see the ApprovalDef IDs.
+// If your code needs to call multiple monorail instances
+// (e.g. monorail-{prod|staging|dev}) ApprovalDef IDs for ApprovalDefs
+// with the same display_name will differ between each monorail
+// instance. To see what ApprovalDef ID to use when calling staging
+// you must check bugs-staging.chromium.org/p/{project}/adminLabels.
+// Next available tag: 7
+message ApprovalDef {
+  option (google.api.resource) = {
+    type: "api.crbug.com/ApprovalDef"
+    pattern: "projects/{project}/approvalDefs/{approval_def_id}"
+  };
+
+  // Resource name of the approval.
+  string name = 1;
+  // Display name of the field.
+  string display_name = 2 [(google.api.field_behavior) = IMMUTABLE];
+  // Brief explanation of this field.
+  string docstring = 3;
+  // Information approvers need from requester.
+  // May be adjusted on the issue after creation.
+  string survey = 4;
+  // Default list of users who can approve this field.
+  // May be adjusted on the issue after creation.
+  repeated string approvers = 5 [
+      (google.api.resource_reference) = { type: "api.crbug.com/User" }
+  ];
+  // Administrators of this field.
+  repeated string admins = 6 [
+      (google.api.resource_reference) = { type: "api.crbug.com/User" }
+  ];
+}
+
+
+// Defines saved queries that belong to a project
+//
+// Next available tag: 4
+message ProjectSavedQuery {
+  option (google.api.resource) = {
+    type: "api.crbug.com/ProjectSavedQuery"
+    pattern: "projects/{project}/savedQueries/{saved_query_id}"
+  };
+
+  // Resource name of this saved query.
+  string name = 1;
+  // Display name of this saved query, ie 'open issues'.
+  string display_name = 2;
+  // Search term of this saved query.
+  string query = 3;
+}
+
+
+// Defines a template for filling issues.
+// Next available tag: 10
+message IssueTemplate {
+  option (google.api.resource) = {
+    type: "api.crbug.com/IssueTemplate"
+    pattern: "projects/{project}/templates/{template_id}"
+  };
+  // Resource name of the template.
+  string name = 1;
+  // Display name of this template.
+  string display_name = 2 [(google.api.field_behavior) = IMMUTABLE];
+  // Canonical Issue for this template.
+  Issue issue = 3;
+  // ApprovalValues to be created with the issue when using this template.
+  repeated ApprovalValue approval_values = 9;
+  // Boolean indicating subsequent issue creation must have delta in summary.
+  bool summary_must_be_edited = 4;
+  // Visibility permission of template.
+  // Next available tag: 3
+  enum TemplatePrivacy {
+    // This value is unused.
+    TEMPLATE_PRIVACY_UNSPECIFIED = 0;
+    // Owner project members may view this template.
+    MEMBERS_ONLY = 1;
+    // Anyone on the web can view this template.
+    PUBLIC = 2;
+  }
+  TemplatePrivacy template_privacy = 5;
+  // Indicator of who if anyone should be the default owner of the issue
+  // created with this template.
+  // Next available tag: 2
+  enum DefaultOwner {
+    // There is no default owner.
+    // This value is used if the default owner is omitted.
+    DEFAULT_OWNER_UNSPECIFIED = 0;
+    // The owner should default to the Issue reporter if the reporter is a
+    // member of the project.
+    PROJECT_MEMBER_REPORTER = 1;
+  }
+  DefaultOwner default_owner = 6;
+  // Boolean indicating whether issue must have a component.
+  bool component_required = 7;
+  // Names of Users who can administer this template.
+  repeated string admins = 8 [
+      (google.api.resource_reference) = { type: "api.crbug.com/User" }];
+}
+
+
+// Defines configurations of a project
+//
+// Next available tag: 11
+message ProjectConfig {
+  option (google.api.resource) = {
+    type: "api.crbug.com/ProjectConfig"
+    pattern: "projects/{project}/config"
+  };
+
+  // Resource name of the project config.
+  string name = 1;
+  // Set of label prefixes that only apply once per issue.
+  // E.g. priority, since no issue can be both Priority-High and Priority-Low.
+  repeated string exclusive_label_prefixes = 2;
+  // Default search query for this project's members.
+  string member_default_query = 3;
+  // TODO(crbug.com/monorail/7517): consider using IssuesListColumn
+  // Default sort specification for this project.
+  string default_sort = 4;
+  // Default columns for displaying issue list for this project.
+  repeated IssuesListColumn default_columns = 5;
+  // Grid view configurations.
+  // Next available tag: 3
+  message GridViewConfig {
+    // Default column dimension in grid view for this project.
+    string default_x_attr = 1;
+    // Default row dimension in grid view for this project.
+    string default_y_attr = 2;
+  }
+  GridViewConfig project_grid_config = 6;
+  // Default template used for issue entry for members of this project.
+  string member_default_template = 7 [
+      (google.api.resource_reference) = { type: "api.crbug.com/Template" }];
+  // Default template used for issue entry for non-members of this project.
+  string non_members_default_template = 8 [
+      (google.api.resource_reference) = { type: "api.crbug.com/Template" }];
+  // URL to browse project's source code revisions for any given revnum.
+  // E.g. https://crrev.com/{revnum}
+  string revision_url_format = 9;
+  // A project's custom URL for the "New issue" link, only if specified.
+  string custom_issue_entry_url = 10;
+}
+
+// Specifies info for a member of a project.
+//
+// Next available tag: 7
+message ProjectMember {
+  // Resource name of the Project Member.
+  // projects/{project}/members/{user_id}
+  string name = 1;
+  // The role the user has in the project.
+  // Next available tag: 4
+  enum ProjectRole {
+    // The user has no role in the project.
+    PROJECT_ROLE_UNSPECIFIED = 0;
+    // The user can make any changes to the project.
+    OWNER = 1;
+    // The user may participate in the project but may not edit the project.
+    COMMITTER = 2;
+    // The user starts with the same permissions as a non-member.
+    CONTRIBUTOR = 3;
+  }
+  ProjectRole role = 2;
+  // Which built-in/standard permissions the user has set.
+  repeated Permission standard_perms = 3;
+  // Custom permissions defined for the user.
+  // eg. "Google" in "Restrict-View-Google" is an example custom permission.
+  repeated string custom_perms = 4;
+  // Annotations about a user configured by project owners.
+  // Visible to anyone who can see the project's settings.
+  string notes = 5;
+  // Whether the user should show up in autocomplete.
+  // Next available tag: 3
+  enum AutocompleteVisibility {
+    // No autocomplete visibility value specified.
+    AUTOCOMPLETE_VISIBILITY_UNSPECIFIED = 0;
+    // The user should not show up in autocomplete.
+    HIDDEN = 1;
+    // The user may show up in autocomplete.
+    SHOWN = 2;
+  }
+  AutocompleteVisibility include_in_autocomplete = 6;
+}
diff --git a/api/v3/api_proto/project_objects_pb2.py b/api/v3/api_proto/project_objects_pb2.py
new file mode 100644
index 0000000..52f0cbb
--- /dev/null
+++ b/api/v3/api_proto/project_objects_pb2.py
@@ -0,0 +1,1715 @@
+# -*- coding: utf-8 -*-
+# Generated by the protocol buffer compiler.  DO NOT EDIT!
+# source: api/v3/api_proto/project_objects.proto
+
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import message as _message
+from google.protobuf import reflection as _reflection
+from google.protobuf import symbol_database as _symbol_database
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__pb2
+from google.api import field_behavior_pb2 as google_dot_api_dot_field__behavior__pb2
+from google.api import resource_pb2 as google_dot_api_dot_resource__pb2
+from api.v3.api_proto import issue_objects_pb2 as api_dot_v3_dot_api__proto_dot_issue__objects__pb2
+from api.v3.api_proto import permission_objects_pb2 as api_dot_v3_dot_api__proto_dot_permission__objects__pb2
+
+
+DESCRIPTOR = _descriptor.FileDescriptor(
+  name='api/v3/api_proto/project_objects.proto',
+  package='monorail.v3',
+  syntax='proto3',
+  serialized_options=b'Z\020api/v3/api_proto',
+  create_key=_descriptor._internal_create_key,
+  serialized_pb=b'\n&api/v3/api_proto/project_objects.proto\x12\x0bmonorail.v3\x1a\x1fgoogle/protobuf/timestamp.proto\x1a\x1fgoogle/api/field_behavior.proto\x1a\x19google/api/resource.proto\x1a$api/v3/api_proto/issue_objects.proto\x1a)api/v3/api_proto/permission_objects.proto\"\x8a\x01\n\x07Project\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x19\n\x0c\x64isplay_name\x18\x02 \x01(\tB\x03\xe0\x41\x05\x12\x0f\n\x07summary\x18\x03 \x01(\t\x12\x15\n\rthumbnail_url\x18\x04 \x01(\t:.\xea\x41+\n\x15\x61pi.crbug.com/Project\x12\x12projects/{project}\"\xa1\x03\n\tStatusDef\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t\x12\x32\n\x04type\x18\x03 \x01(\x0e\x32$.monorail.v3.StatusDef.StatusDefType\x12\x0c\n\x04rank\x18\x04 \x01(\r\x12\x11\n\tdocstring\x18\x05 \x01(\t\x12\x34\n\x05state\x18\x06 \x01(\x0e\x32%.monorail.v3.StatusDef.StatusDefState\"R\n\rStatusDefType\x12\x1f\n\x1bSTATUS_DEF_TYPE_UNSPECIFIED\x10\x00\x12\x08\n\x04OPEN\x10\x01\x12\n\n\x06\x43LOSED\x10\x02\x12\n\n\x06MERGED\x10\x03\"N\n\x0eStatusDefState\x12 \n\x1cSTATUS_DEF_STATE_UNSPECIFIED\x10\x00\x12\x0e\n\nDEPRECATED\x10\x01\x12\n\n\x06\x41\x43TIVE\x10\x02:H\xea\x41\x45\n\x17\x61pi.crbug.com/StatusDef\x12*projects/{project}/statusDefs/{status_def}\"\x83\x02\n\x08LabelDef\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t\x12\x11\n\tdocstring\x18\x03 \x01(\t\x12\x32\n\x05state\x18\x04 \x01(\x0e\x32#.monorail.v3.LabelDef.LabelDefState\"L\n\rLabelDefState\x12\x1f\n\x1bLABEL_DEF_STATE_UNSPECIFIED\x10\x00\x12\x0e\n\nDEPRECATED\x10\x01\x12\n\n\x06\x41\x43TIVE\x10\x02:E\xea\x41\x42\n\x16\x61pi.crbug.com/LabelDef\x12(projects/{project}/labelDefs/{label_def}\"\xcb\r\n\x08\x46ieldDef\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x19\n\x0c\x64isplay_name\x18\x02 \x01(\tB\x03\xe0\x41\x05\x12\x11\n\tdocstring\x18\x03 \x01(\t\x12-\n\x04type\x18\x04 \x01(\x0e\x32\x1a.monorail.v3.FieldDef.TypeB\x03\xe0\x41\x05\x12\x1d\n\x15\x61pplicable_issue_type\x18\x05 \x01(\t\x12\'\n\x06\x61\x64mins\x18\x06 \x03(\tB\x17\xfa\x41\x14\n\x12\x61pi.crbug.com/User\x12,\n\x06traits\x18\x07 \x03(\x0e\x32\x1c.monorail.v3.FieldDef.Traits\x12:\n\x0f\x61pproval_parent\x18\x08 \x01(\tB!\xfa\x41\x1b\n\x19\x61pi.crbug.com/ApprovalDef\xe0\x41\x05\x12=\n\renum_settings\x18\t \x01(\x0b\x32&.monorail.v3.FieldDef.EnumTypeSettings\x12;\n\x0cint_settings\x18\n \x01(\x0b\x32%.monorail.v3.FieldDef.IntTypeSettings\x12;\n\x0cstr_settings\x18\x0b \x01(\x0b\x32%.monorail.v3.FieldDef.StrTypeSettings\x12=\n\ruser_settings\x18\x0c \x01(\x0b\x32&.monorail.v3.FieldDef.UserTypeSettings\x12=\n\rdate_settings\x18\r \x01(\x0b\x32&.monorail.v3.FieldDef.DateTypeSettings\x12(\n\x07\x65\x64itors\x18\x0e \x03(\tB\x17\xfa\x41\x14\n\x12\x61pi.crbug.com/User\x1a~\n\x10\x45numTypeSettings\x12>\n\x07\x63hoices\x18\x01 \x03(\x0b\x32-.monorail.v3.FieldDef.EnumTypeSettings.Choice\x1a*\n\x06\x43hoice\x12\r\n\x05value\x18\x01 \x01(\t\x12\x11\n\tdocstring\x18\x02 \x01(\t\x1a\x37\n\x0fIntTypeSettings\x12\x11\n\tmin_value\x18\x01 \x01(\x05\x12\x11\n\tmax_value\x18\x02 \x01(\x05\x1a \n\x0fStrTypeSettings\x12\r\n\x05regex\x18\x01 \x01(\t\x1a\x92\x03\n\x10UserTypeSettings\x12N\n\x0fnotify_triggers\x18\x01 \x01(\x0e\x32\x35.monorail.v3.FieldDef.UserTypeSettings.NotifyTriggers\x12R\n\x11role_requirements\x18\x02 \x01(\x0e\x32\x37.monorail.v3.FieldDef.UserTypeSettings.RoleRequirements\x12\x13\n\x0bgrants_perm\x18\x03 \x01(\t\x12\x12\n\nneeds_perm\x18\x04 \x01(\t\"M\n\x0eNotifyTriggers\x12\x1f\n\x1bNOTIFY_TRIGGERS_UNSPECIFIED\x10\x00\x12\t\n\x05NEVER\x10\x01\x12\x0f\n\x0b\x41NY_COMMENT\x10\x02\"b\n\x10RoleRequirements\x12!\n\x1dROLE_REQUIREMENTS_UNSPECIFIED\x10\x00\x12\x17\n\x13NO_ROLE_REQUIREMENT\x10\x01\x12\x12\n\x0ePROJECT_MEMBER\x10\x02\x1a\xbf\x01\n\x10\x44\x61teTypeSettings\x12\x46\n\x0b\x64\x61te_action\x18\x01 \x01(\x0e\x32\x31.monorail.v3.FieldDef.DateTypeSettings.DateAction\"c\n\nDateAction\x12\x1b\n\x17\x44\x41TE_ACTION_UNSPECIFIED\x10\x00\x12\r\n\tNO_ACTION\x10\x01\x12\x10\n\x0cNOTIFY_OWNER\x10\x02\x12\x17\n\x13NOTIFY_PARTICIPANTS\x10\x03\"U\n\x04Type\x12\x14\n\x10TYPE_UNSPECIFIED\x10\x00\x12\x08\n\x04\x45NUM\x10\x01\x12\x07\n\x03INT\x10\x02\x12\x07\n\x03STR\x10\x03\x12\x08\n\x04USER\x10\x04\x12\x08\n\x04\x44\x41TE\x10\x05\x12\x07\n\x03URL\x10\x06\"n\n\x06Traits\x12\x16\n\x12TRAITS_UNSPECIFIED\x10\x00\x12\x0c\n\x08REQUIRED\x10\x01\x12\x12\n\x0e\x44\x45\x46\x41ULT_HIDDEN\x10\x02\x12\x0f\n\x0bMULTIVALUED\x10\x03\x12\t\n\x05PHASE\x10\x04\x12\x0e\n\nRESTRICTED\x10\x05:H\xea\x41\x45\n\x16\x61pi.crbug.com/FieldDef\x12+projects/{project}/fieldDefs/{field_def_id}\"\xcc\x04\n\x0c\x43omponentDef\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t\x12\x11\n\tdocstring\x18\x03 \x01(\t\x12\'\n\x06\x61\x64mins\x18\x04 \x03(\tB\x17\xfa\x41\x14\n\x12\x61pi.crbug.com/User\x12$\n\x03\x63\x63s\x18\x05 \x03(\tB\x17\xfa\x41\x14\n\x12\x61pi.crbug.com/User\x12:\n\x05state\x18\x06 \x01(\x0e\x32+.monorail.v3.ComponentDef.ComponentDefState\x12+\n\x07\x63reator\x18\x07 \x01(\tB\x1a\xfa\x41\x14\n\x12\x61pi.crbug.com/User\xe0\x41\x03\x12,\n\x08modifier\x18\x08 \x01(\tB\x1a\xfa\x41\x14\n\x12\x61pi.crbug.com/User\xe0\x41\x03\x12\x34\n\x0b\x63reate_time\x18\t \x01(\x0b\x32\x1a.google.protobuf.TimestampB\x03\xe0\x41\x03\x12\x34\n\x0bmodify_time\x18\n \x01(\x0b\x32\x1a.google.protobuf.TimestampB\x03\xe0\x41\x03\x12\x0e\n\x06labels\x18\x0b \x03(\t\"T\n\x11\x43omponentDefState\x12#\n\x1f\x43OMPONENT_DEF_STATE_UNSPECIFIED\x10\x00\x12\x0e\n\nDEPRECATED\x10\x01\x12\n\n\x06\x41\x43TIVE\x10\x02:T\xea\x41Q\n\x1a\x61pi.crbug.com/ComponentDef\x12\x33projects/{project}/componentDefs/{component_def_id}\"\x81\x02\n\x0b\x41pprovalDef\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x19\n\x0c\x64isplay_name\x18\x02 \x01(\tB\x03\xe0\x41\x05\x12\x11\n\tdocstring\x18\x03 \x01(\t\x12\x0e\n\x06survey\x18\x04 \x01(\t\x12*\n\tapprovers\x18\x05 \x03(\tB\x17\xfa\x41\x14\n\x12\x61pi.crbug.com/User\x12\'\n\x06\x61\x64mins\x18\x06 \x03(\tB\x17\xfa\x41\x14\n\x12\x61pi.crbug.com/User:Q\xea\x41N\n\x19\x61pi.crbug.com/ApprovalDef\x12\x31projects/{project}/approvalDefs/{approval_def_id}\"\x9e\x01\n\x11ProjectSavedQuery\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x14\n\x0c\x64isplay_name\x18\x02 \x01(\t\x12\r\n\x05query\x18\x03 \x01(\t:V\xea\x41S\n\x1f\x61pi.crbug.com/ProjectSavedQuery\x12\x30projects/{project}/savedQueries/{saved_query_id}\"\xe8\x04\n\rIssueTemplate\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x19\n\x0c\x64isplay_name\x18\x02 \x01(\tB\x03\xe0\x41\x05\x12!\n\x05issue\x18\x03 \x01(\x0b\x32\x12.monorail.v3.Issue\x12\x33\n\x0f\x61pproval_values\x18\t \x03(\x0b\x32\x1a.monorail.v3.ApprovalValue\x12\x1e\n\x16summary_must_be_edited\x18\x04 \x01(\x08\x12\x44\n\x10template_privacy\x18\x05 \x01(\x0e\x32*.monorail.v3.IssueTemplate.TemplatePrivacy\x12>\n\rdefault_owner\x18\x06 \x01(\x0e\x32\'.monorail.v3.IssueTemplate.DefaultOwner\x12\x1a\n\x12\x63omponent_required\x18\x07 \x01(\x08\x12\'\n\x06\x61\x64mins\x18\x08 \x03(\tB\x17\xfa\x41\x14\n\x12\x61pi.crbug.com/User\"Q\n\x0fTemplatePrivacy\x12 \n\x1cTEMPLATE_PRIVACY_UNSPECIFIED\x10\x00\x12\x10\n\x0cMEMBERS_ONLY\x10\x01\x12\n\n\x06PUBLIC\x10\x02\"J\n\x0c\x44\x65\x66\x61ultOwner\x12\x1d\n\x19\x44\x45\x46\x41ULT_OWNER_UNSPECIFIED\x10\x00\x12\x1b\n\x17PROJECT_MEMBER_REPORTER\x10\x01:L\xea\x41I\n\x1b\x61pi.crbug.com/IssueTemplate\x12*projects/{project}/templates/{template_id}\"\xb0\x04\n\rProjectConfig\x12\x0c\n\x04name\x18\x01 \x01(\t\x12 \n\x18\x65xclusive_label_prefixes\x18\x02 \x03(\t\x12\x1c\n\x14member_default_query\x18\x03 \x01(\t\x12\x14\n\x0c\x64\x65\x66\x61ult_sort\x18\x04 \x01(\t\x12\x36\n\x0f\x64\x65\x66\x61ult_columns\x18\x05 \x03(\x0b\x32\x1d.monorail.v3.IssuesListColumn\x12\x46\n\x13project_grid_config\x18\x06 \x01(\x0b\x32).monorail.v3.ProjectConfig.GridViewConfig\x12<\n\x17member_default_template\x18\x07 \x01(\tB\x1b\xfa\x41\x18\n\x16\x61pi.crbug.com/Template\x12\x41\n\x1cnon_members_default_template\x18\x08 \x01(\tB\x1b\xfa\x41\x18\n\x16\x61pi.crbug.com/Template\x12\x1b\n\x13revision_url_format\x18\t \x01(\t\x12\x1e\n\x16\x63ustom_issue_entry_url\x18\n \x01(\t\x1a@\n\x0eGridViewConfig\x12\x16\n\x0e\x64\x65\x66\x61ult_x_attr\x18\x01 \x01(\t\x12\x16\n\x0e\x64\x65\x66\x61ult_y_attr\x18\x02 \x01(\t:;\xea\x41\x38\n\x1b\x61pi.crbug.com/ProjectConfig\x12\x19projects/{project}/config\"\xaf\x03\n\rProjectMember\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x34\n\x04role\x18\x02 \x01(\x0e\x32&.monorail.v3.ProjectMember.ProjectRole\x12/\n\x0estandard_perms\x18\x03 \x03(\x0e\x32\x17.monorail.v3.Permission\x12\x14\n\x0c\x63ustom_perms\x18\x04 \x03(\t\x12\r\n\x05notes\x18\x05 \x01(\t\x12R\n\x17include_in_autocomplete\x18\x06 \x01(\x0e\x32\x31.monorail.v3.ProjectMember.AutocompleteVisibility\"V\n\x0bProjectRole\x12\x1c\n\x18PROJECT_ROLE_UNSPECIFIED\x10\x00\x12\t\n\x05OWNER\x10\x01\x12\r\n\tCOMMITTER\x10\x02\x12\x0f\n\x0b\x43ONTRIBUTOR\x10\x03\"X\n\x16\x41utocompleteVisibility\x12\'\n#AUTOCOMPLETE_VISIBILITY_UNSPECIFIED\x10\x00\x12\n\n\x06HIDDEN\x10\x01\x12\t\n\x05SHOWN\x10\x02\x42\x12Z\x10\x61pi/v3/api_protob\x06proto3'
+  ,
+  dependencies=[google_dot_protobuf_dot_timestamp__pb2.DESCRIPTOR,google_dot_api_dot_field__behavior__pb2.DESCRIPTOR,google_dot_api_dot_resource__pb2.DESCRIPTOR,api_dot_v3_dot_api__proto_dot_issue__objects__pb2.DESCRIPTOR,api_dot_v3_dot_api__proto_dot_permission__objects__pb2.DESCRIPTOR,])
+
+
+
+_STATUSDEF_STATUSDEFTYPE = _descriptor.EnumDescriptor(
+  name='StatusDefType',
+  full_name='monorail.v3.StatusDef.StatusDefType',
+  filename=None,
+  file=DESCRIPTOR,
+  create_key=_descriptor._internal_create_key,
+  values=[
+    _descriptor.EnumValueDescriptor(
+      name='STATUS_DEF_TYPE_UNSPECIFIED', index=0, number=0,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='OPEN', index=1, number=1,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='CLOSED', index=2, number=2,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='MERGED', index=3, number=3,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+  ],
+  containing_type=None,
+  serialized_options=None,
+  serialized_start=552,
+  serialized_end=634,
+)
+_sym_db.RegisterEnumDescriptor(_STATUSDEF_STATUSDEFTYPE)
+
+_STATUSDEF_STATUSDEFSTATE = _descriptor.EnumDescriptor(
+  name='StatusDefState',
+  full_name='monorail.v3.StatusDef.StatusDefState',
+  filename=None,
+  file=DESCRIPTOR,
+  create_key=_descriptor._internal_create_key,
+  values=[
+    _descriptor.EnumValueDescriptor(
+      name='STATUS_DEF_STATE_UNSPECIFIED', index=0, number=0,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='DEPRECATED', index=1, number=1,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='ACTIVE', index=2, number=2,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+  ],
+  containing_type=None,
+  serialized_options=None,
+  serialized_start=636,
+  serialized_end=714,
+)
+_sym_db.RegisterEnumDescriptor(_STATUSDEF_STATUSDEFSTATE)
+
+_LABELDEF_LABELDEFSTATE = _descriptor.EnumDescriptor(
+  name='LabelDefState',
+  full_name='monorail.v3.LabelDef.LabelDefState',
+  filename=None,
+  file=DESCRIPTOR,
+  create_key=_descriptor._internal_create_key,
+  values=[
+    _descriptor.EnumValueDescriptor(
+      name='LABEL_DEF_STATE_UNSPECIFIED', index=0, number=0,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='DEPRECATED', index=1, number=1,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='ACTIVE', index=2, number=2,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+  ],
+  containing_type=None,
+  serialized_options=None,
+  serialized_start=903,
+  serialized_end=979,
+)
+_sym_db.RegisterEnumDescriptor(_LABELDEF_LABELDEFSTATE)
+
+_FIELDDEF_USERTYPESETTINGS_NOTIFYTRIGGERS = _descriptor.EnumDescriptor(
+  name='NotifyTriggers',
+  full_name='monorail.v3.FieldDef.UserTypeSettings.NotifyTriggers',
+  filename=None,
+  file=DESCRIPTOR,
+  create_key=_descriptor._internal_create_key,
+  values=[
+    _descriptor.EnumValueDescriptor(
+      name='NOTIFY_TRIGGERS_UNSPECIFIED', index=0, number=0,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='NEVER', index=1, number=1,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='ANY_COMMENT', index=2, number=2,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+  ],
+  containing_type=None,
+  serialized_options=None,
+  serialized_start=2148,
+  serialized_end=2225,
+)
+_sym_db.RegisterEnumDescriptor(_FIELDDEF_USERTYPESETTINGS_NOTIFYTRIGGERS)
+
+_FIELDDEF_USERTYPESETTINGS_ROLEREQUIREMENTS = _descriptor.EnumDescriptor(
+  name='RoleRequirements',
+  full_name='monorail.v3.FieldDef.UserTypeSettings.RoleRequirements',
+  filename=None,
+  file=DESCRIPTOR,
+  create_key=_descriptor._internal_create_key,
+  values=[
+    _descriptor.EnumValueDescriptor(
+      name='ROLE_REQUIREMENTS_UNSPECIFIED', index=0, number=0,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='NO_ROLE_REQUIREMENT', index=1, number=1,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='PROJECT_MEMBER', index=2, number=2,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+  ],
+  containing_type=None,
+  serialized_options=None,
+  serialized_start=2227,
+  serialized_end=2325,
+)
+_sym_db.RegisterEnumDescriptor(_FIELDDEF_USERTYPESETTINGS_ROLEREQUIREMENTS)
+
+_FIELDDEF_DATETYPESETTINGS_DATEACTION = _descriptor.EnumDescriptor(
+  name='DateAction',
+  full_name='monorail.v3.FieldDef.DateTypeSettings.DateAction',
+  filename=None,
+  file=DESCRIPTOR,
+  create_key=_descriptor._internal_create_key,
+  values=[
+    _descriptor.EnumValueDescriptor(
+      name='DATE_ACTION_UNSPECIFIED', index=0, number=0,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='NO_ACTION', index=1, number=1,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='NOTIFY_OWNER', index=2, number=2,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='NOTIFY_PARTICIPANTS', index=3, number=3,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+  ],
+  containing_type=None,
+  serialized_options=None,
+  serialized_start=2420,
+  serialized_end=2519,
+)
+_sym_db.RegisterEnumDescriptor(_FIELDDEF_DATETYPESETTINGS_DATEACTION)
+
+_FIELDDEF_TYPE = _descriptor.EnumDescriptor(
+  name='Type',
+  full_name='monorail.v3.FieldDef.Type',
+  filename=None,
+  file=DESCRIPTOR,
+  create_key=_descriptor._internal_create_key,
+  values=[
+    _descriptor.EnumValueDescriptor(
+      name='TYPE_UNSPECIFIED', index=0, number=0,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='ENUM', index=1, number=1,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='INT', index=2, number=2,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='STR', index=3, number=3,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='USER', index=4, number=4,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='DATE', index=5, number=5,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='URL', index=6, number=6,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+  ],
+  containing_type=None,
+  serialized_options=None,
+  serialized_start=2521,
+  serialized_end=2606,
+)
+_sym_db.RegisterEnumDescriptor(_FIELDDEF_TYPE)
+
+_FIELDDEF_TRAITS = _descriptor.EnumDescriptor(
+  name='Traits',
+  full_name='monorail.v3.FieldDef.Traits',
+  filename=None,
+  file=DESCRIPTOR,
+  create_key=_descriptor._internal_create_key,
+  values=[
+    _descriptor.EnumValueDescriptor(
+      name='TRAITS_UNSPECIFIED', index=0, number=0,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='REQUIRED', index=1, number=1,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='DEFAULT_HIDDEN', index=2, number=2,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='MULTIVALUED', index=3, number=3,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='PHASE', index=4, number=4,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='RESTRICTED', index=5, number=5,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+  ],
+  containing_type=None,
+  serialized_options=None,
+  serialized_start=2608,
+  serialized_end=2718,
+)
+_sym_db.RegisterEnumDescriptor(_FIELDDEF_TRAITS)
+
+_COMPONENTDEF_COMPONENTDEFSTATE = _descriptor.EnumDescriptor(
+  name='ComponentDefState',
+  full_name='monorail.v3.ComponentDef.ComponentDefState',
+  filename=None,
+  file=DESCRIPTOR,
+  create_key=_descriptor._internal_create_key,
+  values=[
+    _descriptor.EnumValueDescriptor(
+      name='COMPONENT_DEF_STATE_UNSPECIFIED', index=0, number=0,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='DEPRECATED', index=1, number=1,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='ACTIVE', index=2, number=2,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+  ],
+  containing_type=None,
+  serialized_options=None,
+  serialized_start=3213,
+  serialized_end=3297,
+)
+_sym_db.RegisterEnumDescriptor(_COMPONENTDEF_COMPONENTDEFSTATE)
+
+_ISSUETEMPLATE_TEMPLATEPRIVACY = _descriptor.EnumDescriptor(
+  name='TemplatePrivacy',
+  full_name='monorail.v3.IssueTemplate.TemplatePrivacy',
+  filename=None,
+  file=DESCRIPTOR,
+  create_key=_descriptor._internal_create_key,
+  values=[
+    _descriptor.EnumValueDescriptor(
+      name='TEMPLATE_PRIVACY_UNSPECIFIED', index=0, number=0,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='MEMBERS_ONLY', index=1, number=1,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='PUBLIC', index=2, number=2,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+  ],
+  containing_type=None,
+  serialized_options=None,
+  serialized_start=4188,
+  serialized_end=4269,
+)
+_sym_db.RegisterEnumDescriptor(_ISSUETEMPLATE_TEMPLATEPRIVACY)
+
+_ISSUETEMPLATE_DEFAULTOWNER = _descriptor.EnumDescriptor(
+  name='DefaultOwner',
+  full_name='monorail.v3.IssueTemplate.DefaultOwner',
+  filename=None,
+  file=DESCRIPTOR,
+  create_key=_descriptor._internal_create_key,
+  values=[
+    _descriptor.EnumValueDescriptor(
+      name='DEFAULT_OWNER_UNSPECIFIED', index=0, number=0,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='PROJECT_MEMBER_REPORTER', index=1, number=1,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+  ],
+  containing_type=None,
+  serialized_options=None,
+  serialized_start=4271,
+  serialized_end=4345,
+)
+_sym_db.RegisterEnumDescriptor(_ISSUETEMPLATE_DEFAULTOWNER)
+
+_PROJECTMEMBER_PROJECTROLE = _descriptor.EnumDescriptor(
+  name='ProjectRole',
+  full_name='monorail.v3.ProjectMember.ProjectRole',
+  filename=None,
+  file=DESCRIPTOR,
+  create_key=_descriptor._internal_create_key,
+  values=[
+    _descriptor.EnumValueDescriptor(
+      name='PROJECT_ROLE_UNSPECIFIED', index=0, number=0,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='OWNER', index=1, number=1,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='COMMITTER', index=2, number=2,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='CONTRIBUTOR', index=3, number=3,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+  ],
+  containing_type=None,
+  serialized_options=None,
+  serialized_start=5244,
+  serialized_end=5330,
+)
+_sym_db.RegisterEnumDescriptor(_PROJECTMEMBER_PROJECTROLE)
+
+_PROJECTMEMBER_AUTOCOMPLETEVISIBILITY = _descriptor.EnumDescriptor(
+  name='AutocompleteVisibility',
+  full_name='monorail.v3.ProjectMember.AutocompleteVisibility',
+  filename=None,
+  file=DESCRIPTOR,
+  create_key=_descriptor._internal_create_key,
+  values=[
+    _descriptor.EnumValueDescriptor(
+      name='AUTOCOMPLETE_VISIBILITY_UNSPECIFIED', index=0, number=0,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='HIDDEN', index=1, number=1,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='SHOWN', index=2, number=2,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+  ],
+  containing_type=None,
+  serialized_options=None,
+  serialized_start=5332,
+  serialized_end=5420,
+)
+_sym_db.RegisterEnumDescriptor(_PROJECTMEMBER_AUTOCOMPLETEVISIBILITY)
+
+
+_PROJECT = _descriptor.Descriptor(
+  name='Project',
+  full_name='monorail.v3.Project',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='name', full_name='monorail.v3.Project.name', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='display_name', full_name='monorail.v3.Project.display_name', index=1,
+      number=2, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\340A\005', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='summary', full_name='monorail.v3.Project.summary', index=2,
+      number=3, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='thumbnail_url', full_name='monorail.v3.Project.thumbnail_url', index=3,
+      number=4, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=b'\352A+\n\025api.crbug.com/Project\022\022projects/{project}',
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=230,
+  serialized_end=368,
+)
+
+
+_STATUSDEF = _descriptor.Descriptor(
+  name='StatusDef',
+  full_name='monorail.v3.StatusDef',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='name', full_name='monorail.v3.StatusDef.name', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='value', full_name='monorail.v3.StatusDef.value', index=1,
+      number=2, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='type', full_name='monorail.v3.StatusDef.type', index=2,
+      number=3, type=14, cpp_type=8, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='rank', full_name='monorail.v3.StatusDef.rank', index=3,
+      number=4, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='docstring', full_name='monorail.v3.StatusDef.docstring', index=4,
+      number=5, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='state', full_name='monorail.v3.StatusDef.state', index=5,
+      number=6, type=14, cpp_type=8, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+    _STATUSDEF_STATUSDEFTYPE,
+    _STATUSDEF_STATUSDEFSTATE,
+  ],
+  serialized_options=b'\352AE\n\027api.crbug.com/StatusDef\022*projects/{project}/statusDefs/{status_def}',
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=371,
+  serialized_end=788,
+)
+
+
+_LABELDEF = _descriptor.Descriptor(
+  name='LabelDef',
+  full_name='monorail.v3.LabelDef',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='name', full_name='monorail.v3.LabelDef.name', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='value', full_name='monorail.v3.LabelDef.value', index=1,
+      number=2, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='docstring', full_name='monorail.v3.LabelDef.docstring', index=2,
+      number=3, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='state', full_name='monorail.v3.LabelDef.state', index=3,
+      number=4, type=14, cpp_type=8, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+    _LABELDEF_LABELDEFSTATE,
+  ],
+  serialized_options=b'\352AB\n\026api.crbug.com/LabelDef\022(projects/{project}/labelDefs/{label_def}',
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=791,
+  serialized_end=1050,
+)
+
+
+_FIELDDEF_ENUMTYPESETTINGS_CHOICE = _descriptor.Descriptor(
+  name='Choice',
+  full_name='monorail.v3.FieldDef.EnumTypeSettings.Choice',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='value', full_name='monorail.v3.FieldDef.EnumTypeSettings.Choice.value', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='docstring', full_name='monorail.v3.FieldDef.EnumTypeSettings.Choice.docstring', index=1,
+      number=2, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=1787,
+  serialized_end=1829,
+)
+
+_FIELDDEF_ENUMTYPESETTINGS = _descriptor.Descriptor(
+  name='EnumTypeSettings',
+  full_name='monorail.v3.FieldDef.EnumTypeSettings',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='choices', full_name='monorail.v3.FieldDef.EnumTypeSettings.choices', index=0,
+      number=1, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[_FIELDDEF_ENUMTYPESETTINGS_CHOICE, ],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=1703,
+  serialized_end=1829,
+)
+
+_FIELDDEF_INTTYPESETTINGS = _descriptor.Descriptor(
+  name='IntTypeSettings',
+  full_name='monorail.v3.FieldDef.IntTypeSettings',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='min_value', full_name='monorail.v3.FieldDef.IntTypeSettings.min_value', index=0,
+      number=1, type=5, cpp_type=1, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='max_value', full_name='monorail.v3.FieldDef.IntTypeSettings.max_value', index=1,
+      number=2, type=5, cpp_type=1, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=1831,
+  serialized_end=1886,
+)
+
+_FIELDDEF_STRTYPESETTINGS = _descriptor.Descriptor(
+  name='StrTypeSettings',
+  full_name='monorail.v3.FieldDef.StrTypeSettings',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='regex', full_name='monorail.v3.FieldDef.StrTypeSettings.regex', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=1888,
+  serialized_end=1920,
+)
+
+_FIELDDEF_USERTYPESETTINGS = _descriptor.Descriptor(
+  name='UserTypeSettings',
+  full_name='monorail.v3.FieldDef.UserTypeSettings',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='notify_triggers', full_name='monorail.v3.FieldDef.UserTypeSettings.notify_triggers', index=0,
+      number=1, type=14, cpp_type=8, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='role_requirements', full_name='monorail.v3.FieldDef.UserTypeSettings.role_requirements', index=1,
+      number=2, type=14, cpp_type=8, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='grants_perm', full_name='monorail.v3.FieldDef.UserTypeSettings.grants_perm', index=2,
+      number=3, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='needs_perm', full_name='monorail.v3.FieldDef.UserTypeSettings.needs_perm', index=3,
+      number=4, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+    _FIELDDEF_USERTYPESETTINGS_NOTIFYTRIGGERS,
+    _FIELDDEF_USERTYPESETTINGS_ROLEREQUIREMENTS,
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=1923,
+  serialized_end=2325,
+)
+
+_FIELDDEF_DATETYPESETTINGS = _descriptor.Descriptor(
+  name='DateTypeSettings',
+  full_name='monorail.v3.FieldDef.DateTypeSettings',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='date_action', full_name='monorail.v3.FieldDef.DateTypeSettings.date_action', index=0,
+      number=1, type=14, cpp_type=8, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+    _FIELDDEF_DATETYPESETTINGS_DATEACTION,
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=2328,
+  serialized_end=2519,
+)
+
+_FIELDDEF = _descriptor.Descriptor(
+  name='FieldDef',
+  full_name='monorail.v3.FieldDef',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='name', full_name='monorail.v3.FieldDef.name', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='display_name', full_name='monorail.v3.FieldDef.display_name', index=1,
+      number=2, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\340A\005', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='docstring', full_name='monorail.v3.FieldDef.docstring', index=2,
+      number=3, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='type', full_name='monorail.v3.FieldDef.type', index=3,
+      number=4, type=14, cpp_type=8, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\340A\005', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='applicable_issue_type', full_name='monorail.v3.FieldDef.applicable_issue_type', index=4,
+      number=5, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='admins', full_name='monorail.v3.FieldDef.admins', index=5,
+      number=6, type=9, cpp_type=9, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\372A\024\n\022api.crbug.com/User', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='traits', full_name='monorail.v3.FieldDef.traits', index=6,
+      number=7, type=14, cpp_type=8, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='approval_parent', full_name='monorail.v3.FieldDef.approval_parent', index=7,
+      number=8, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\372A\033\n\031api.crbug.com/ApprovalDef\340A\005', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='enum_settings', full_name='monorail.v3.FieldDef.enum_settings', index=8,
+      number=9, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='int_settings', full_name='monorail.v3.FieldDef.int_settings', index=9,
+      number=10, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='str_settings', full_name='monorail.v3.FieldDef.str_settings', index=10,
+      number=11, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='user_settings', full_name='monorail.v3.FieldDef.user_settings', index=11,
+      number=12, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='date_settings', full_name='monorail.v3.FieldDef.date_settings', index=12,
+      number=13, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='editors', full_name='monorail.v3.FieldDef.editors', index=13,
+      number=14, type=9, cpp_type=9, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\372A\024\n\022api.crbug.com/User', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[_FIELDDEF_ENUMTYPESETTINGS, _FIELDDEF_INTTYPESETTINGS, _FIELDDEF_STRTYPESETTINGS, _FIELDDEF_USERTYPESETTINGS, _FIELDDEF_DATETYPESETTINGS, ],
+  enum_types=[
+    _FIELDDEF_TYPE,
+    _FIELDDEF_TRAITS,
+  ],
+  serialized_options=b'\352AE\n\026api.crbug.com/FieldDef\022+projects/{project}/fieldDefs/{field_def_id}',
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=1053,
+  serialized_end=2792,
+)
+
+
+_COMPONENTDEF = _descriptor.Descriptor(
+  name='ComponentDef',
+  full_name='monorail.v3.ComponentDef',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='name', full_name='monorail.v3.ComponentDef.name', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='value', full_name='monorail.v3.ComponentDef.value', index=1,
+      number=2, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='docstring', full_name='monorail.v3.ComponentDef.docstring', index=2,
+      number=3, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='admins', full_name='monorail.v3.ComponentDef.admins', index=3,
+      number=4, type=9, cpp_type=9, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\372A\024\n\022api.crbug.com/User', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='ccs', full_name='monorail.v3.ComponentDef.ccs', index=4,
+      number=5, type=9, cpp_type=9, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\372A\024\n\022api.crbug.com/User', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='state', full_name='monorail.v3.ComponentDef.state', index=5,
+      number=6, type=14, cpp_type=8, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='creator', full_name='monorail.v3.ComponentDef.creator', index=6,
+      number=7, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\372A\024\n\022api.crbug.com/User\340A\003', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='modifier', full_name='monorail.v3.ComponentDef.modifier', index=7,
+      number=8, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\372A\024\n\022api.crbug.com/User\340A\003', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='create_time', full_name='monorail.v3.ComponentDef.create_time', index=8,
+      number=9, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\340A\003', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='modify_time', full_name='monorail.v3.ComponentDef.modify_time', index=9,
+      number=10, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\340A\003', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='labels', full_name='monorail.v3.ComponentDef.labels', index=10,
+      number=11, type=9, cpp_type=9, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+    _COMPONENTDEF_COMPONENTDEFSTATE,
+  ],
+  serialized_options=b'\352AQ\n\032api.crbug.com/ComponentDef\0223projects/{project}/componentDefs/{component_def_id}',
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=2795,
+  serialized_end=3383,
+)
+
+
+_APPROVALDEF = _descriptor.Descriptor(
+  name='ApprovalDef',
+  full_name='monorail.v3.ApprovalDef',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='name', full_name='monorail.v3.ApprovalDef.name', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='display_name', full_name='monorail.v3.ApprovalDef.display_name', index=1,
+      number=2, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\340A\005', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='docstring', full_name='monorail.v3.ApprovalDef.docstring', index=2,
+      number=3, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='survey', full_name='monorail.v3.ApprovalDef.survey', index=3,
+      number=4, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='approvers', full_name='monorail.v3.ApprovalDef.approvers', index=4,
+      number=5, type=9, cpp_type=9, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\372A\024\n\022api.crbug.com/User', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='admins', full_name='monorail.v3.ApprovalDef.admins', index=5,
+      number=6, type=9, cpp_type=9, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\372A\024\n\022api.crbug.com/User', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=b'\352AN\n\031api.crbug.com/ApprovalDef\0221projects/{project}/approvalDefs/{approval_def_id}',
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=3386,
+  serialized_end=3643,
+)
+
+
+_PROJECTSAVEDQUERY = _descriptor.Descriptor(
+  name='ProjectSavedQuery',
+  full_name='monorail.v3.ProjectSavedQuery',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='name', full_name='monorail.v3.ProjectSavedQuery.name', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='display_name', full_name='monorail.v3.ProjectSavedQuery.display_name', index=1,
+      number=2, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='query', full_name='monorail.v3.ProjectSavedQuery.query', index=2,
+      number=3, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=b'\352AS\n\037api.crbug.com/ProjectSavedQuery\0220projects/{project}/savedQueries/{saved_query_id}',
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=3646,
+  serialized_end=3804,
+)
+
+
+_ISSUETEMPLATE = _descriptor.Descriptor(
+  name='IssueTemplate',
+  full_name='monorail.v3.IssueTemplate',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='name', full_name='monorail.v3.IssueTemplate.name', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='display_name', full_name='monorail.v3.IssueTemplate.display_name', index=1,
+      number=2, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\340A\005', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='issue', full_name='monorail.v3.IssueTemplate.issue', index=2,
+      number=3, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='approval_values', full_name='monorail.v3.IssueTemplate.approval_values', index=3,
+      number=9, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='summary_must_be_edited', full_name='monorail.v3.IssueTemplate.summary_must_be_edited', index=4,
+      number=4, type=8, cpp_type=7, label=1,
+      has_default_value=False, default_value=False,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='template_privacy', full_name='monorail.v3.IssueTemplate.template_privacy', index=5,
+      number=5, type=14, cpp_type=8, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='default_owner', full_name='monorail.v3.IssueTemplate.default_owner', index=6,
+      number=6, type=14, cpp_type=8, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='component_required', full_name='monorail.v3.IssueTemplate.component_required', index=7,
+      number=7, type=8, cpp_type=7, label=1,
+      has_default_value=False, default_value=False,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='admins', full_name='monorail.v3.IssueTemplate.admins', index=8,
+      number=8, type=9, cpp_type=9, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\372A\024\n\022api.crbug.com/User', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+    _ISSUETEMPLATE_TEMPLATEPRIVACY,
+    _ISSUETEMPLATE_DEFAULTOWNER,
+  ],
+  serialized_options=b'\352AI\n\033api.crbug.com/IssueTemplate\022*projects/{project}/templates/{template_id}',
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=3807,
+  serialized_end=4423,
+)
+
+
+_PROJECTCONFIG_GRIDVIEWCONFIG = _descriptor.Descriptor(
+  name='GridViewConfig',
+  full_name='monorail.v3.ProjectConfig.GridViewConfig',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='default_x_attr', full_name='monorail.v3.ProjectConfig.GridViewConfig.default_x_attr', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='default_y_attr', full_name='monorail.v3.ProjectConfig.GridViewConfig.default_y_attr', index=1,
+      number=2, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=4861,
+  serialized_end=4925,
+)
+
+_PROJECTCONFIG = _descriptor.Descriptor(
+  name='ProjectConfig',
+  full_name='monorail.v3.ProjectConfig',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='name', full_name='monorail.v3.ProjectConfig.name', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='exclusive_label_prefixes', full_name='monorail.v3.ProjectConfig.exclusive_label_prefixes', index=1,
+      number=2, type=9, cpp_type=9, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='member_default_query', full_name='monorail.v3.ProjectConfig.member_default_query', index=2,
+      number=3, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='default_sort', full_name='monorail.v3.ProjectConfig.default_sort', index=3,
+      number=4, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='default_columns', full_name='monorail.v3.ProjectConfig.default_columns', index=4,
+      number=5, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='project_grid_config', full_name='monorail.v3.ProjectConfig.project_grid_config', index=5,
+      number=6, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='member_default_template', full_name='monorail.v3.ProjectConfig.member_default_template', index=6,
+      number=7, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\372A\030\n\026api.crbug.com/Template', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='non_members_default_template', full_name='monorail.v3.ProjectConfig.non_members_default_template', index=7,
+      number=8, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\372A\030\n\026api.crbug.com/Template', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='revision_url_format', full_name='monorail.v3.ProjectConfig.revision_url_format', index=8,
+      number=9, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='custom_issue_entry_url', full_name='monorail.v3.ProjectConfig.custom_issue_entry_url', index=9,
+      number=10, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[_PROJECTCONFIG_GRIDVIEWCONFIG, ],
+  enum_types=[
+  ],
+  serialized_options=b'\352A8\n\033api.crbug.com/ProjectConfig\022\031projects/{project}/config',
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=4426,
+  serialized_end=4986,
+)
+
+
+_PROJECTMEMBER = _descriptor.Descriptor(
+  name='ProjectMember',
+  full_name='monorail.v3.ProjectMember',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='name', full_name='monorail.v3.ProjectMember.name', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='role', full_name='monorail.v3.ProjectMember.role', index=1,
+      number=2, type=14, cpp_type=8, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='standard_perms', full_name='monorail.v3.ProjectMember.standard_perms', index=2,
+      number=3, type=14, cpp_type=8, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='custom_perms', full_name='monorail.v3.ProjectMember.custom_perms', index=3,
+      number=4, type=9, cpp_type=9, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='notes', full_name='monorail.v3.ProjectMember.notes', index=4,
+      number=5, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='include_in_autocomplete', full_name='monorail.v3.ProjectMember.include_in_autocomplete', index=5,
+      number=6, type=14, cpp_type=8, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+    _PROJECTMEMBER_PROJECTROLE,
+    _PROJECTMEMBER_AUTOCOMPLETEVISIBILITY,
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=4989,
+  serialized_end=5420,
+)
+
+_STATUSDEF.fields_by_name['type'].enum_type = _STATUSDEF_STATUSDEFTYPE
+_STATUSDEF.fields_by_name['state'].enum_type = _STATUSDEF_STATUSDEFSTATE
+_STATUSDEF_STATUSDEFTYPE.containing_type = _STATUSDEF
+_STATUSDEF_STATUSDEFSTATE.containing_type = _STATUSDEF
+_LABELDEF.fields_by_name['state'].enum_type = _LABELDEF_LABELDEFSTATE
+_LABELDEF_LABELDEFSTATE.containing_type = _LABELDEF
+_FIELDDEF_ENUMTYPESETTINGS_CHOICE.containing_type = _FIELDDEF_ENUMTYPESETTINGS
+_FIELDDEF_ENUMTYPESETTINGS.fields_by_name['choices'].message_type = _FIELDDEF_ENUMTYPESETTINGS_CHOICE
+_FIELDDEF_ENUMTYPESETTINGS.containing_type = _FIELDDEF
+_FIELDDEF_INTTYPESETTINGS.containing_type = _FIELDDEF
+_FIELDDEF_STRTYPESETTINGS.containing_type = _FIELDDEF
+_FIELDDEF_USERTYPESETTINGS.fields_by_name['notify_triggers'].enum_type = _FIELDDEF_USERTYPESETTINGS_NOTIFYTRIGGERS
+_FIELDDEF_USERTYPESETTINGS.fields_by_name['role_requirements'].enum_type = _FIELDDEF_USERTYPESETTINGS_ROLEREQUIREMENTS
+_FIELDDEF_USERTYPESETTINGS.containing_type = _FIELDDEF
+_FIELDDEF_USERTYPESETTINGS_NOTIFYTRIGGERS.containing_type = _FIELDDEF_USERTYPESETTINGS
+_FIELDDEF_USERTYPESETTINGS_ROLEREQUIREMENTS.containing_type = _FIELDDEF_USERTYPESETTINGS
+_FIELDDEF_DATETYPESETTINGS.fields_by_name['date_action'].enum_type = _FIELDDEF_DATETYPESETTINGS_DATEACTION
+_FIELDDEF_DATETYPESETTINGS.containing_type = _FIELDDEF
+_FIELDDEF_DATETYPESETTINGS_DATEACTION.containing_type = _FIELDDEF_DATETYPESETTINGS
+_FIELDDEF.fields_by_name['type'].enum_type = _FIELDDEF_TYPE
+_FIELDDEF.fields_by_name['traits'].enum_type = _FIELDDEF_TRAITS
+_FIELDDEF.fields_by_name['enum_settings'].message_type = _FIELDDEF_ENUMTYPESETTINGS
+_FIELDDEF.fields_by_name['int_settings'].message_type = _FIELDDEF_INTTYPESETTINGS
+_FIELDDEF.fields_by_name['str_settings'].message_type = _FIELDDEF_STRTYPESETTINGS
+_FIELDDEF.fields_by_name['user_settings'].message_type = _FIELDDEF_USERTYPESETTINGS
+_FIELDDEF.fields_by_name['date_settings'].message_type = _FIELDDEF_DATETYPESETTINGS
+_FIELDDEF_TYPE.containing_type = _FIELDDEF
+_FIELDDEF_TRAITS.containing_type = _FIELDDEF
+_COMPONENTDEF.fields_by_name['state'].enum_type = _COMPONENTDEF_COMPONENTDEFSTATE
+_COMPONENTDEF.fields_by_name['create_time'].message_type = google_dot_protobuf_dot_timestamp__pb2._TIMESTAMP
+_COMPONENTDEF.fields_by_name['modify_time'].message_type = google_dot_protobuf_dot_timestamp__pb2._TIMESTAMP
+_COMPONENTDEF_COMPONENTDEFSTATE.containing_type = _COMPONENTDEF
+_ISSUETEMPLATE.fields_by_name['issue'].message_type = api_dot_v3_dot_api__proto_dot_issue__objects__pb2._ISSUE
+_ISSUETEMPLATE.fields_by_name['approval_values'].message_type = api_dot_v3_dot_api__proto_dot_issue__objects__pb2._APPROVALVALUE
+_ISSUETEMPLATE.fields_by_name['template_privacy'].enum_type = _ISSUETEMPLATE_TEMPLATEPRIVACY
+_ISSUETEMPLATE.fields_by_name['default_owner'].enum_type = _ISSUETEMPLATE_DEFAULTOWNER
+_ISSUETEMPLATE_TEMPLATEPRIVACY.containing_type = _ISSUETEMPLATE
+_ISSUETEMPLATE_DEFAULTOWNER.containing_type = _ISSUETEMPLATE
+_PROJECTCONFIG_GRIDVIEWCONFIG.containing_type = _PROJECTCONFIG
+_PROJECTCONFIG.fields_by_name['default_columns'].message_type = api_dot_v3_dot_api__proto_dot_issue__objects__pb2._ISSUESLISTCOLUMN
+_PROJECTCONFIG.fields_by_name['project_grid_config'].message_type = _PROJECTCONFIG_GRIDVIEWCONFIG
+_PROJECTMEMBER.fields_by_name['role'].enum_type = _PROJECTMEMBER_PROJECTROLE
+_PROJECTMEMBER.fields_by_name['standard_perms'].enum_type = api_dot_v3_dot_api__proto_dot_permission__objects__pb2._PERMISSION
+_PROJECTMEMBER.fields_by_name['include_in_autocomplete'].enum_type = _PROJECTMEMBER_AUTOCOMPLETEVISIBILITY
+_PROJECTMEMBER_PROJECTROLE.containing_type = _PROJECTMEMBER
+_PROJECTMEMBER_AUTOCOMPLETEVISIBILITY.containing_type = _PROJECTMEMBER
+DESCRIPTOR.message_types_by_name['Project'] = _PROJECT
+DESCRIPTOR.message_types_by_name['StatusDef'] = _STATUSDEF
+DESCRIPTOR.message_types_by_name['LabelDef'] = _LABELDEF
+DESCRIPTOR.message_types_by_name['FieldDef'] = _FIELDDEF
+DESCRIPTOR.message_types_by_name['ComponentDef'] = _COMPONENTDEF
+DESCRIPTOR.message_types_by_name['ApprovalDef'] = _APPROVALDEF
+DESCRIPTOR.message_types_by_name['ProjectSavedQuery'] = _PROJECTSAVEDQUERY
+DESCRIPTOR.message_types_by_name['IssueTemplate'] = _ISSUETEMPLATE
+DESCRIPTOR.message_types_by_name['ProjectConfig'] = _PROJECTCONFIG
+DESCRIPTOR.message_types_by_name['ProjectMember'] = _PROJECTMEMBER
+_sym_db.RegisterFileDescriptor(DESCRIPTOR)
+
+Project = _reflection.GeneratedProtocolMessageType('Project', (_message.Message,), {
+  'DESCRIPTOR' : _PROJECT,
+  '__module__' : 'api.v3.api_proto.project_objects_pb2'
+  # @@protoc_insertion_point(class_scope:monorail.v3.Project)
+  })
+_sym_db.RegisterMessage(Project)
+
+StatusDef = _reflection.GeneratedProtocolMessageType('StatusDef', (_message.Message,), {
+  'DESCRIPTOR' : _STATUSDEF,
+  '__module__' : 'api.v3.api_proto.project_objects_pb2'
+  # @@protoc_insertion_point(class_scope:monorail.v3.StatusDef)
+  })
+_sym_db.RegisterMessage(StatusDef)
+
+LabelDef = _reflection.GeneratedProtocolMessageType('LabelDef', (_message.Message,), {
+  'DESCRIPTOR' : _LABELDEF,
+  '__module__' : 'api.v3.api_proto.project_objects_pb2'
+  # @@protoc_insertion_point(class_scope:monorail.v3.LabelDef)
+  })
+_sym_db.RegisterMessage(LabelDef)
+
+FieldDef = _reflection.GeneratedProtocolMessageType('FieldDef', (_message.Message,), {
+
+  'EnumTypeSettings' : _reflection.GeneratedProtocolMessageType('EnumTypeSettings', (_message.Message,), {
+
+    'Choice' : _reflection.GeneratedProtocolMessageType('Choice', (_message.Message,), {
+      'DESCRIPTOR' : _FIELDDEF_ENUMTYPESETTINGS_CHOICE,
+      '__module__' : 'api.v3.api_proto.project_objects_pb2'
+      # @@protoc_insertion_point(class_scope:monorail.v3.FieldDef.EnumTypeSettings.Choice)
+      })
+    ,
+    'DESCRIPTOR' : _FIELDDEF_ENUMTYPESETTINGS,
+    '__module__' : 'api.v3.api_proto.project_objects_pb2'
+    # @@protoc_insertion_point(class_scope:monorail.v3.FieldDef.EnumTypeSettings)
+    })
+  ,
+
+  'IntTypeSettings' : _reflection.GeneratedProtocolMessageType('IntTypeSettings', (_message.Message,), {
+    'DESCRIPTOR' : _FIELDDEF_INTTYPESETTINGS,
+    '__module__' : 'api.v3.api_proto.project_objects_pb2'
+    # @@protoc_insertion_point(class_scope:monorail.v3.FieldDef.IntTypeSettings)
+    })
+  ,
+
+  'StrTypeSettings' : _reflection.GeneratedProtocolMessageType('StrTypeSettings', (_message.Message,), {
+    'DESCRIPTOR' : _FIELDDEF_STRTYPESETTINGS,
+    '__module__' : 'api.v3.api_proto.project_objects_pb2'
+    # @@protoc_insertion_point(class_scope:monorail.v3.FieldDef.StrTypeSettings)
+    })
+  ,
+
+  'UserTypeSettings' : _reflection.GeneratedProtocolMessageType('UserTypeSettings', (_message.Message,), {
+    'DESCRIPTOR' : _FIELDDEF_USERTYPESETTINGS,
+    '__module__' : 'api.v3.api_proto.project_objects_pb2'
+    # @@protoc_insertion_point(class_scope:monorail.v3.FieldDef.UserTypeSettings)
+    })
+  ,
+
+  'DateTypeSettings' : _reflection.GeneratedProtocolMessageType('DateTypeSettings', (_message.Message,), {
+    'DESCRIPTOR' : _FIELDDEF_DATETYPESETTINGS,
+    '__module__' : 'api.v3.api_proto.project_objects_pb2'
+    # @@protoc_insertion_point(class_scope:monorail.v3.FieldDef.DateTypeSettings)
+    })
+  ,
+  'DESCRIPTOR' : _FIELDDEF,
+  '__module__' : 'api.v3.api_proto.project_objects_pb2'
+  # @@protoc_insertion_point(class_scope:monorail.v3.FieldDef)
+  })
+_sym_db.RegisterMessage(FieldDef)
+_sym_db.RegisterMessage(FieldDef.EnumTypeSettings)
+_sym_db.RegisterMessage(FieldDef.EnumTypeSettings.Choice)
+_sym_db.RegisterMessage(FieldDef.IntTypeSettings)
+_sym_db.RegisterMessage(FieldDef.StrTypeSettings)
+_sym_db.RegisterMessage(FieldDef.UserTypeSettings)
+_sym_db.RegisterMessage(FieldDef.DateTypeSettings)
+
+ComponentDef = _reflection.GeneratedProtocolMessageType('ComponentDef', (_message.Message,), {
+  'DESCRIPTOR' : _COMPONENTDEF,
+  '__module__' : 'api.v3.api_proto.project_objects_pb2'
+  # @@protoc_insertion_point(class_scope:monorail.v3.ComponentDef)
+  })
+_sym_db.RegisterMessage(ComponentDef)
+
+ApprovalDef = _reflection.GeneratedProtocolMessageType('ApprovalDef', (_message.Message,), {
+  'DESCRIPTOR' : _APPROVALDEF,
+  '__module__' : 'api.v3.api_proto.project_objects_pb2'
+  # @@protoc_insertion_point(class_scope:monorail.v3.ApprovalDef)
+  })
+_sym_db.RegisterMessage(ApprovalDef)
+
+ProjectSavedQuery = _reflection.GeneratedProtocolMessageType('ProjectSavedQuery', (_message.Message,), {
+  'DESCRIPTOR' : _PROJECTSAVEDQUERY,
+  '__module__' : 'api.v3.api_proto.project_objects_pb2'
+  # @@protoc_insertion_point(class_scope:monorail.v3.ProjectSavedQuery)
+  })
+_sym_db.RegisterMessage(ProjectSavedQuery)
+
+IssueTemplate = _reflection.GeneratedProtocolMessageType('IssueTemplate', (_message.Message,), {
+  'DESCRIPTOR' : _ISSUETEMPLATE,
+  '__module__' : 'api.v3.api_proto.project_objects_pb2'
+  # @@protoc_insertion_point(class_scope:monorail.v3.IssueTemplate)
+  })
+_sym_db.RegisterMessage(IssueTemplate)
+
+ProjectConfig = _reflection.GeneratedProtocolMessageType('ProjectConfig', (_message.Message,), {
+
+  'GridViewConfig' : _reflection.GeneratedProtocolMessageType('GridViewConfig', (_message.Message,), {
+    'DESCRIPTOR' : _PROJECTCONFIG_GRIDVIEWCONFIG,
+    '__module__' : 'api.v3.api_proto.project_objects_pb2'
+    # @@protoc_insertion_point(class_scope:monorail.v3.ProjectConfig.GridViewConfig)
+    })
+  ,
+  'DESCRIPTOR' : _PROJECTCONFIG,
+  '__module__' : 'api.v3.api_proto.project_objects_pb2'
+  # @@protoc_insertion_point(class_scope:monorail.v3.ProjectConfig)
+  })
+_sym_db.RegisterMessage(ProjectConfig)
+_sym_db.RegisterMessage(ProjectConfig.GridViewConfig)
+
+ProjectMember = _reflection.GeneratedProtocolMessageType('ProjectMember', (_message.Message,), {
+  'DESCRIPTOR' : _PROJECTMEMBER,
+  '__module__' : 'api.v3.api_proto.project_objects_pb2'
+  # @@protoc_insertion_point(class_scope:monorail.v3.ProjectMember)
+  })
+_sym_db.RegisterMessage(ProjectMember)
+
+
+DESCRIPTOR._options = None
+_PROJECT.fields_by_name['display_name']._options = None
+_PROJECT._options = None
+_STATUSDEF._options = None
+_LABELDEF._options = None
+_FIELDDEF.fields_by_name['display_name']._options = None
+_FIELDDEF.fields_by_name['type']._options = None
+_FIELDDEF.fields_by_name['admins']._options = None
+_FIELDDEF.fields_by_name['approval_parent']._options = None
+_FIELDDEF.fields_by_name['editors']._options = None
+_FIELDDEF._options = None
+_COMPONENTDEF.fields_by_name['admins']._options = None
+_COMPONENTDEF.fields_by_name['ccs']._options = None
+_COMPONENTDEF.fields_by_name['creator']._options = None
+_COMPONENTDEF.fields_by_name['modifier']._options = None
+_COMPONENTDEF.fields_by_name['create_time']._options = None
+_COMPONENTDEF.fields_by_name['modify_time']._options = None
+_COMPONENTDEF._options = None
+_APPROVALDEF.fields_by_name['display_name']._options = None
+_APPROVALDEF.fields_by_name['approvers']._options = None
+_APPROVALDEF.fields_by_name['admins']._options = None
+_APPROVALDEF._options = None
+_PROJECTSAVEDQUERY._options = None
+_ISSUETEMPLATE.fields_by_name['display_name']._options = None
+_ISSUETEMPLATE.fields_by_name['admins']._options = None
+_ISSUETEMPLATE._options = None
+_PROJECTCONFIG.fields_by_name['member_default_template']._options = None
+_PROJECTCONFIG.fields_by_name['non_members_default_template']._options = None
+_PROJECTCONFIG._options = None
+# @@protoc_insertion_point(module_scope)
diff --git a/api/v3/api_proto/projects.proto b/api/v3/api_proto/projects.proto
new file mode 100644
index 0000000..d902067
--- /dev/null
+++ b/api/v3/api_proto/projects.proto
@@ -0,0 +1,178 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd
+
+syntax = "proto3";
+
+package monorail.v3;
+
+option go_package = "api/v3/api_proto";
+
+import "google/protobuf/empty.proto";
+import "google/api/field_behavior.proto";
+import "google/api/resource.proto";
+import "api/v3/api_proto/project_objects.proto";
+
+// ***ONLY CALL rpcs WITH `status: {ALPHA|STABLE}`***
+// rpcs without `status` are not ready.
+
+// Projects service includes all methods needed for managing Projects.
+service Projects {
+  // status: NOT READY
+  // Creates a new FieldDef (custom field).
+  //
+  // Raises:
+  //   NOT_FOUND if some given users do not exist.
+  //   ALREADY_EXISTS if a field with the same name owned by the project
+  //   already exists.
+  //   INVALID_INPUT if there was a problem with the input.
+  //   PERMISSION_DENIED if the user cannot edit the project.
+  rpc CreateFieldDef (CreateFieldDefRequest) returns (FieldDef) {}
+
+  // status: ALPHA
+  // Creates a new ComponentDef.
+  //
+  // Raises:
+  //   INVALID_INPUT if the request is invalid.
+  //   ALREADY_EXISTS if the component already exists.
+  //   PERMISSION_DENIED if the user is not allowed to create a/this component.
+  //   NOT_FOUND if the parent project or a component cc or admin is not found.
+  rpc CreateComponentDef (CreateComponentDefRequest) returns (ComponentDef) {}
+
+  // status: ALPHA
+  // Deletes a ComponentDef.
+  //
+  // Raises:
+  //   INVALID_INPUT if the request is invalid.
+  //   PERMISSION_DENIED if the user is not allowed to delete a/this component.
+  //   NOT_FOUND if the component or project is not found.
+  rpc DeleteComponentDef (DeleteComponentDefRequest) returns (google.protobuf.Empty) {}
+
+  // status: NOT READY
+  // Returns all templates for specified project.
+  //
+  // Raises:
+  //   NOT_FOUND if the requested parent project is not found.
+  //   INVALID_ARGUMENT if the given `parent` is not valid.
+  rpc ListIssueTemplates (ListIssueTemplatesRequest) returns (ListIssueTemplatesResponse) {}
+
+  // status: ALPHA
+  // Returns all field defs for specified project.
+  //
+  // Raises:
+  //   NOT_FOUND if the request arent project is not found.
+  //   INVALID_ARGUMENT if the given `parent` is not valid.
+  rpc ListComponentDefs (ListComponentDefsRequest) returns (ListComponentDefsResponse) {}
+
+  // status: NOT READY
+  // Returns all projects hosted on Monorail.
+  rpc ListProjects (ListProjectsRequest) returns (ListProjectsResponse) {}
+}
+
+// Request message for CreateFieldDef method.
+// Next available tag: 3
+message CreateFieldDefRequest {
+  // The project resource where this field will be created.
+  string parent = 1 [
+    (google.api.field_behavior) = REQUIRED,
+    (google.api.resource_reference) = {type: "api.crbug.com/Project" }];
+  // The field to create.
+  // It must have a display_name and a type with its corresponding settings.
+  FieldDef fielddef = 2 [ (google.api.field_behavior) = REQUIRED ];
+}
+
+// Request message for CreateComponentDef method.
+// Next available tag: 3
+message CreateComponentDefRequest {
+  // The project resource where this component will be created.
+  string parent = 1 [
+    (google.api.field_behavior) = REQUIRED,
+    (google.api.resource_reference) = {type: "api.crbug.com/Project" }];
+  // The component to create.
+  ComponentDef component_def = 2 [ (google.api.field_behavior) = REQUIRED ];
+}
+
+// Request message for DeleteComponentDef method.
+// Next available tag: 2
+message DeleteComponentDefRequest {
+  // The component to delete.
+  string name = 1 [
+    (google.api.field_behavior) = REQUIRED,
+    (google.api.resource_reference) = {type: "api.crbug.com/ComponentDef"}];
+}
+
+// Request message for ListIssueTemplates
+// Next available tag: 4
+message ListIssueTemplatesRequest {
+  // The name of the project these templates belong to.
+  string parent = 1 [
+    (google.api.resource_reference) = {type: "api.crbug.com/Project"},
+    (google.api.field_behavior) = REQUIRED ];
+  // The maximum number of items to return. The service may return fewer than
+  // this value.
+  int32 page_size = 2;
+  // A page token, received from a previous `ListIssueTemplates` call.
+  // Provide this to retrieve the subsequent page.
+  // When paginating, all other parameters provided to
+  // `ListIssueTemplatesRequest` must match the call that provided the token.
+  string page_token = 3;
+}
+
+// Response message for ListIssueTemplates
+// Next available tag: 3
+message ListIssueTemplatesResponse {
+  // Templates matching the given request.
+  repeated IssueTemplate templates = 1;
+  // A token, which can be sent as `page_token` to retrieve the next page.
+  // If this field is omitted, there are no subsequent pages.
+  string next_page_token = 2;
+}
+
+// Request message for ListComponentDefs
+// Next available tag: 4
+message ListComponentDefsRequest {
+  // The name of the parent project.
+  string parent = 1 [
+    (google.api.resource_reference) = {type: "api.crbug.com/Project"},
+    (google.api.field_behavior) = REQUIRED ];
+  // The maximum number of items to return. The service may return fewer than
+  // this value.
+  int32 page_size = 2;
+  // A page token, received from a previous `ListComponentDefs` call.
+  // Provide this to retrieve the subsequent page.
+  // When paginating, all other parameters provided to
+  // `ListComponentDefsRequest` must match the call that provided the token.
+  string page_token = 3;
+}
+
+// Response message for ListComponentDefs
+// Next available tag: 3
+message ListComponentDefsResponse {
+  // Component defs matching the given request.
+  repeated ComponentDef component_defs = 1;
+  // A token which can be sent as `page_token` to retrieve the next page.
+  // If this field is omitted, there are no subsequent pages.
+  string next_page_token = 2;
+}
+
+// Request message for ListProjects
+// Next available tag: 3
+message ListProjectsRequest {
+  // The maximum number of items to return. The service may return fewer than
+  // this value.
+  int32 page_size = 1;
+  // A page token, received from a previous `ListProjects` call.
+  // Provide this to retrieve the subsequent page.
+  string page_token = 2;
+}
+
+// Response message for ListProjects
+// Next available tag: 3
+message ListProjectsResponse {
+  // Projects matching the given request.
+  repeated Project projects = 1;
+  // A token, which can be sent as `page_token` to retrieve the next page.
+  // If this field is omitted, there are no subsequent pages.
+  string next_page_token = 2;
+}
diff --git a/api/v3/api_proto/projects_pb2.py b/api/v3/api_proto/projects_pb2.py
new file mode 100644
index 0000000..0478142
--- /dev/null
+++ b/api/v3/api_proto/projects_pb2.py
@@ -0,0 +1,554 @@
+# -*- coding: utf-8 -*-
+# Generated by the protocol buffer compiler.  DO NOT EDIT!
+# source: api/v3/api_proto/projects.proto
+
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import message as _message
+from google.protobuf import reflection as _reflection
+from google.protobuf import symbol_database as _symbol_database
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+from google.protobuf import empty_pb2 as google_dot_protobuf_dot_empty__pb2
+from google.api import field_behavior_pb2 as google_dot_api_dot_field__behavior__pb2
+from google.api import resource_pb2 as google_dot_api_dot_resource__pb2
+from api.v3.api_proto import project_objects_pb2 as api_dot_v3_dot_api__proto_dot_project__objects__pb2
+
+
+DESCRIPTOR = _descriptor.FileDescriptor(
+  name='api/v3/api_proto/projects.proto',
+  package='monorail.v3',
+  syntax='proto3',
+  serialized_options=b'Z\020api/v3/api_proto',
+  create_key=_descriptor._internal_create_key,
+  serialized_pb=b'\n\x1f\x61pi/v3/api_proto/projects.proto\x12\x0bmonorail.v3\x1a\x1bgoogle/protobuf/empty.proto\x1a\x1fgoogle/api/field_behavior.proto\x1a\x19google/api/resource.proto\x1a&api/v3/api_proto/project_objects.proto\"t\n\x15\x43reateFieldDefRequest\x12-\n\x06parent\x18\x01 \x01(\tB\x1d\xe0\x41\x02\xfa\x41\x17\n\x15\x61pi.crbug.com/Project\x12,\n\x08\x66ielddef\x18\x02 \x01(\x0b\x32\x15.monorail.v3.FieldDefB\x03\xe0\x41\x02\"\x81\x01\n\x19\x43reateComponentDefRequest\x12-\n\x06parent\x18\x01 \x01(\tB\x1d\xe0\x41\x02\xfa\x41\x17\n\x15\x61pi.crbug.com/Project\x12\x35\n\rcomponent_def\x18\x02 \x01(\x0b\x32\x19.monorail.v3.ComponentDefB\x03\xe0\x41\x02\"M\n\x19\x44\x65leteComponentDefRequest\x12\x30\n\x04name\x18\x01 \x01(\tB\"\xe0\x41\x02\xfa\x41\x1c\n\x1a\x61pi.crbug.com/ComponentDef\"q\n\x19ListIssueTemplatesRequest\x12-\n\x06parent\x18\x01 \x01(\tB\x1d\xfa\x41\x17\n\x15\x61pi.crbug.com/Project\xe0\x41\x02\x12\x11\n\tpage_size\x18\x02 \x01(\x05\x12\x12\n\npage_token\x18\x03 \x01(\t\"d\n\x1aListIssueTemplatesResponse\x12-\n\ttemplates\x18\x01 \x03(\x0b\x32\x1a.monorail.v3.IssueTemplate\x12\x17\n\x0fnext_page_token\x18\x02 \x01(\t\"p\n\x18ListComponentDefsRequest\x12-\n\x06parent\x18\x01 \x01(\tB\x1d\xfa\x41\x17\n\x15\x61pi.crbug.com/Project\xe0\x41\x02\x12\x11\n\tpage_size\x18\x02 \x01(\x05\x12\x12\n\npage_token\x18\x03 \x01(\t\"g\n\x19ListComponentDefsResponse\x12\x31\n\x0e\x63omponent_defs\x18\x01 \x03(\x0b\x32\x19.monorail.v3.ComponentDef\x12\x17\n\x0fnext_page_token\x18\x02 \x01(\t\"<\n\x13ListProjectsRequest\x12\x11\n\tpage_size\x18\x01 \x01(\x05\x12\x12\n\npage_token\x18\x02 \x01(\t\"W\n\x14ListProjectsResponse\x12&\n\x08projects\x18\x01 \x03(\x0b\x32\x14.monorail.v3.Project\x12\x17\n\x0fnext_page_token\x18\x02 \x01(\t2\xb2\x04\n\x08Projects\x12M\n\x0e\x43reateFieldDef\x12\".monorail.v3.CreateFieldDefRequest\x1a\x15.monorail.v3.FieldDef\"\x00\x12Y\n\x12\x43reateComponentDef\x12&.monorail.v3.CreateComponentDefRequest\x1a\x19.monorail.v3.ComponentDef\"\x00\x12V\n\x12\x44\x65leteComponentDef\x12&.monorail.v3.DeleteComponentDefRequest\x1a\x16.google.protobuf.Empty\"\x00\x12g\n\x12ListIssueTemplates\x12&.monorail.v3.ListIssueTemplatesRequest\x1a\'.monorail.v3.ListIssueTemplatesResponse\"\x00\x12\x64\n\x11ListComponentDefs\x12%.monorail.v3.ListComponentDefsRequest\x1a&.monorail.v3.ListComponentDefsResponse\"\x00\x12U\n\x0cListProjects\x12 .monorail.v3.ListProjectsRequest\x1a!.monorail.v3.ListProjectsResponse\"\x00\x42\x12Z\x10\x61pi/v3/api_protob\x06proto3'
+  ,
+  dependencies=[google_dot_protobuf_dot_empty__pb2.DESCRIPTOR,google_dot_api_dot_field__behavior__pb2.DESCRIPTOR,google_dot_api_dot_resource__pb2.DESCRIPTOR,api_dot_v3_dot_api__proto_dot_project__objects__pb2.DESCRIPTOR,])
+
+
+
+
+_CREATEFIELDDEFREQUEST = _descriptor.Descriptor(
+  name='CreateFieldDefRequest',
+  full_name='monorail.v3.CreateFieldDefRequest',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='parent', full_name='monorail.v3.CreateFieldDefRequest.parent', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\340A\002\372A\027\n\025api.crbug.com/Project', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='fielddef', full_name='monorail.v3.CreateFieldDefRequest.fielddef', index=1,
+      number=2, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\340A\002', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=177,
+  serialized_end=293,
+)
+
+
+_CREATECOMPONENTDEFREQUEST = _descriptor.Descriptor(
+  name='CreateComponentDefRequest',
+  full_name='monorail.v3.CreateComponentDefRequest',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='parent', full_name='monorail.v3.CreateComponentDefRequest.parent', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\340A\002\372A\027\n\025api.crbug.com/Project', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='component_def', full_name='monorail.v3.CreateComponentDefRequest.component_def', index=1,
+      number=2, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\340A\002', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=296,
+  serialized_end=425,
+)
+
+
+_DELETECOMPONENTDEFREQUEST = _descriptor.Descriptor(
+  name='DeleteComponentDefRequest',
+  full_name='monorail.v3.DeleteComponentDefRequest',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='name', full_name='monorail.v3.DeleteComponentDefRequest.name', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\340A\002\372A\034\n\032api.crbug.com/ComponentDef', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=427,
+  serialized_end=504,
+)
+
+
+_LISTISSUETEMPLATESREQUEST = _descriptor.Descriptor(
+  name='ListIssueTemplatesRequest',
+  full_name='monorail.v3.ListIssueTemplatesRequest',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='parent', full_name='monorail.v3.ListIssueTemplatesRequest.parent', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\372A\027\n\025api.crbug.com/Project\340A\002', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='page_size', full_name='monorail.v3.ListIssueTemplatesRequest.page_size', index=1,
+      number=2, type=5, cpp_type=1, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='page_token', full_name='monorail.v3.ListIssueTemplatesRequest.page_token', index=2,
+      number=3, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=506,
+  serialized_end=619,
+)
+
+
+_LISTISSUETEMPLATESRESPONSE = _descriptor.Descriptor(
+  name='ListIssueTemplatesResponse',
+  full_name='monorail.v3.ListIssueTemplatesResponse',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='templates', full_name='monorail.v3.ListIssueTemplatesResponse.templates', index=0,
+      number=1, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='next_page_token', full_name='monorail.v3.ListIssueTemplatesResponse.next_page_token', index=1,
+      number=2, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=621,
+  serialized_end=721,
+)
+
+
+_LISTCOMPONENTDEFSREQUEST = _descriptor.Descriptor(
+  name='ListComponentDefsRequest',
+  full_name='monorail.v3.ListComponentDefsRequest',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='parent', full_name='monorail.v3.ListComponentDefsRequest.parent', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\372A\027\n\025api.crbug.com/Project\340A\002', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='page_size', full_name='monorail.v3.ListComponentDefsRequest.page_size', index=1,
+      number=2, type=5, cpp_type=1, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='page_token', full_name='monorail.v3.ListComponentDefsRequest.page_token', index=2,
+      number=3, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=723,
+  serialized_end=835,
+)
+
+
+_LISTCOMPONENTDEFSRESPONSE = _descriptor.Descriptor(
+  name='ListComponentDefsResponse',
+  full_name='monorail.v3.ListComponentDefsResponse',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='component_defs', full_name='monorail.v3.ListComponentDefsResponse.component_defs', index=0,
+      number=1, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='next_page_token', full_name='monorail.v3.ListComponentDefsResponse.next_page_token', index=1,
+      number=2, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=837,
+  serialized_end=940,
+)
+
+
+_LISTPROJECTSREQUEST = _descriptor.Descriptor(
+  name='ListProjectsRequest',
+  full_name='monorail.v3.ListProjectsRequest',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='page_size', full_name='monorail.v3.ListProjectsRequest.page_size', index=0,
+      number=1, type=5, cpp_type=1, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='page_token', full_name='monorail.v3.ListProjectsRequest.page_token', index=1,
+      number=2, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=942,
+  serialized_end=1002,
+)
+
+
+_LISTPROJECTSRESPONSE = _descriptor.Descriptor(
+  name='ListProjectsResponse',
+  full_name='monorail.v3.ListProjectsResponse',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='projects', full_name='monorail.v3.ListProjectsResponse.projects', index=0,
+      number=1, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='next_page_token', full_name='monorail.v3.ListProjectsResponse.next_page_token', index=1,
+      number=2, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=1004,
+  serialized_end=1091,
+)
+
+_CREATEFIELDDEFREQUEST.fields_by_name['fielddef'].message_type = api_dot_v3_dot_api__proto_dot_project__objects__pb2._FIELDDEF
+_CREATECOMPONENTDEFREQUEST.fields_by_name['component_def'].message_type = api_dot_v3_dot_api__proto_dot_project__objects__pb2._COMPONENTDEF
+_LISTISSUETEMPLATESRESPONSE.fields_by_name['templates'].message_type = api_dot_v3_dot_api__proto_dot_project__objects__pb2._ISSUETEMPLATE
+_LISTCOMPONENTDEFSRESPONSE.fields_by_name['component_defs'].message_type = api_dot_v3_dot_api__proto_dot_project__objects__pb2._COMPONENTDEF
+_LISTPROJECTSRESPONSE.fields_by_name['projects'].message_type = api_dot_v3_dot_api__proto_dot_project__objects__pb2._PROJECT
+DESCRIPTOR.message_types_by_name['CreateFieldDefRequest'] = _CREATEFIELDDEFREQUEST
+DESCRIPTOR.message_types_by_name['CreateComponentDefRequest'] = _CREATECOMPONENTDEFREQUEST
+DESCRIPTOR.message_types_by_name['DeleteComponentDefRequest'] = _DELETECOMPONENTDEFREQUEST
+DESCRIPTOR.message_types_by_name['ListIssueTemplatesRequest'] = _LISTISSUETEMPLATESREQUEST
+DESCRIPTOR.message_types_by_name['ListIssueTemplatesResponse'] = _LISTISSUETEMPLATESRESPONSE
+DESCRIPTOR.message_types_by_name['ListComponentDefsRequest'] = _LISTCOMPONENTDEFSREQUEST
+DESCRIPTOR.message_types_by_name['ListComponentDefsResponse'] = _LISTCOMPONENTDEFSRESPONSE
+DESCRIPTOR.message_types_by_name['ListProjectsRequest'] = _LISTPROJECTSREQUEST
+DESCRIPTOR.message_types_by_name['ListProjectsResponse'] = _LISTPROJECTSRESPONSE
+_sym_db.RegisterFileDescriptor(DESCRIPTOR)
+
+CreateFieldDefRequest = _reflection.GeneratedProtocolMessageType('CreateFieldDefRequest', (_message.Message,), {
+  'DESCRIPTOR' : _CREATEFIELDDEFREQUEST,
+  '__module__' : 'api.v3.api_proto.projects_pb2'
+  # @@protoc_insertion_point(class_scope:monorail.v3.CreateFieldDefRequest)
+  })
+_sym_db.RegisterMessage(CreateFieldDefRequest)
+
+CreateComponentDefRequest = _reflection.GeneratedProtocolMessageType('CreateComponentDefRequest', (_message.Message,), {
+  'DESCRIPTOR' : _CREATECOMPONENTDEFREQUEST,
+  '__module__' : 'api.v3.api_proto.projects_pb2'
+  # @@protoc_insertion_point(class_scope:monorail.v3.CreateComponentDefRequest)
+  })
+_sym_db.RegisterMessage(CreateComponentDefRequest)
+
+DeleteComponentDefRequest = _reflection.GeneratedProtocolMessageType('DeleteComponentDefRequest', (_message.Message,), {
+  'DESCRIPTOR' : _DELETECOMPONENTDEFREQUEST,
+  '__module__' : 'api.v3.api_proto.projects_pb2'
+  # @@protoc_insertion_point(class_scope:monorail.v3.DeleteComponentDefRequest)
+  })
+_sym_db.RegisterMessage(DeleteComponentDefRequest)
+
+ListIssueTemplatesRequest = _reflection.GeneratedProtocolMessageType('ListIssueTemplatesRequest', (_message.Message,), {
+  'DESCRIPTOR' : _LISTISSUETEMPLATESREQUEST,
+  '__module__' : 'api.v3.api_proto.projects_pb2'
+  # @@protoc_insertion_point(class_scope:monorail.v3.ListIssueTemplatesRequest)
+  })
+_sym_db.RegisterMessage(ListIssueTemplatesRequest)
+
+ListIssueTemplatesResponse = _reflection.GeneratedProtocolMessageType('ListIssueTemplatesResponse', (_message.Message,), {
+  'DESCRIPTOR' : _LISTISSUETEMPLATESRESPONSE,
+  '__module__' : 'api.v3.api_proto.projects_pb2'
+  # @@protoc_insertion_point(class_scope:monorail.v3.ListIssueTemplatesResponse)
+  })
+_sym_db.RegisterMessage(ListIssueTemplatesResponse)
+
+ListComponentDefsRequest = _reflection.GeneratedProtocolMessageType('ListComponentDefsRequest', (_message.Message,), {
+  'DESCRIPTOR' : _LISTCOMPONENTDEFSREQUEST,
+  '__module__' : 'api.v3.api_proto.projects_pb2'
+  # @@protoc_insertion_point(class_scope:monorail.v3.ListComponentDefsRequest)
+  })
+_sym_db.RegisterMessage(ListComponentDefsRequest)
+
+ListComponentDefsResponse = _reflection.GeneratedProtocolMessageType('ListComponentDefsResponse', (_message.Message,), {
+  'DESCRIPTOR' : _LISTCOMPONENTDEFSRESPONSE,
+  '__module__' : 'api.v3.api_proto.projects_pb2'
+  # @@protoc_insertion_point(class_scope:monorail.v3.ListComponentDefsResponse)
+  })
+_sym_db.RegisterMessage(ListComponentDefsResponse)
+
+ListProjectsRequest = _reflection.GeneratedProtocolMessageType('ListProjectsRequest', (_message.Message,), {
+  'DESCRIPTOR' : _LISTPROJECTSREQUEST,
+  '__module__' : 'api.v3.api_proto.projects_pb2'
+  # @@protoc_insertion_point(class_scope:monorail.v3.ListProjectsRequest)
+  })
+_sym_db.RegisterMessage(ListProjectsRequest)
+
+ListProjectsResponse = _reflection.GeneratedProtocolMessageType('ListProjectsResponse', (_message.Message,), {
+  'DESCRIPTOR' : _LISTPROJECTSRESPONSE,
+  '__module__' : 'api.v3.api_proto.projects_pb2'
+  # @@protoc_insertion_point(class_scope:monorail.v3.ListProjectsResponse)
+  })
+_sym_db.RegisterMessage(ListProjectsResponse)
+
+
+DESCRIPTOR._options = None
+_CREATEFIELDDEFREQUEST.fields_by_name['parent']._options = None
+_CREATEFIELDDEFREQUEST.fields_by_name['fielddef']._options = None
+_CREATECOMPONENTDEFREQUEST.fields_by_name['parent']._options = None
+_CREATECOMPONENTDEFREQUEST.fields_by_name['component_def']._options = None
+_DELETECOMPONENTDEFREQUEST.fields_by_name['name']._options = None
+_LISTISSUETEMPLATESREQUEST.fields_by_name['parent']._options = None
+_LISTCOMPONENTDEFSREQUEST.fields_by_name['parent']._options = None
+
+_PROJECTS = _descriptor.ServiceDescriptor(
+  name='Projects',
+  full_name='monorail.v3.Projects',
+  file=DESCRIPTOR,
+  index=0,
+  serialized_options=None,
+  create_key=_descriptor._internal_create_key,
+  serialized_start=1094,
+  serialized_end=1656,
+  methods=[
+  _descriptor.MethodDescriptor(
+    name='CreateFieldDef',
+    full_name='monorail.v3.Projects.CreateFieldDef',
+    index=0,
+    containing_service=None,
+    input_type=_CREATEFIELDDEFREQUEST,
+    output_type=api_dot_v3_dot_api__proto_dot_project__objects__pb2._FIELDDEF,
+    serialized_options=None,
+    create_key=_descriptor._internal_create_key,
+  ),
+  _descriptor.MethodDescriptor(
+    name='CreateComponentDef',
+    full_name='monorail.v3.Projects.CreateComponentDef',
+    index=1,
+    containing_service=None,
+    input_type=_CREATECOMPONENTDEFREQUEST,
+    output_type=api_dot_v3_dot_api__proto_dot_project__objects__pb2._COMPONENTDEF,
+    serialized_options=None,
+    create_key=_descriptor._internal_create_key,
+  ),
+  _descriptor.MethodDescriptor(
+    name='DeleteComponentDef',
+    full_name='monorail.v3.Projects.DeleteComponentDef',
+    index=2,
+    containing_service=None,
+    input_type=_DELETECOMPONENTDEFREQUEST,
+    output_type=google_dot_protobuf_dot_empty__pb2._EMPTY,
+    serialized_options=None,
+    create_key=_descriptor._internal_create_key,
+  ),
+  _descriptor.MethodDescriptor(
+    name='ListIssueTemplates',
+    full_name='monorail.v3.Projects.ListIssueTemplates',
+    index=3,
+    containing_service=None,
+    input_type=_LISTISSUETEMPLATESREQUEST,
+    output_type=_LISTISSUETEMPLATESRESPONSE,
+    serialized_options=None,
+    create_key=_descriptor._internal_create_key,
+  ),
+  _descriptor.MethodDescriptor(
+    name='ListComponentDefs',
+    full_name='monorail.v3.Projects.ListComponentDefs',
+    index=4,
+    containing_service=None,
+    input_type=_LISTCOMPONENTDEFSREQUEST,
+    output_type=_LISTCOMPONENTDEFSRESPONSE,
+    serialized_options=None,
+    create_key=_descriptor._internal_create_key,
+  ),
+  _descriptor.MethodDescriptor(
+    name='ListProjects',
+    full_name='monorail.v3.Projects.ListProjects',
+    index=5,
+    containing_service=None,
+    input_type=_LISTPROJECTSREQUEST,
+    output_type=_LISTPROJECTSRESPONSE,
+    serialized_options=None,
+    create_key=_descriptor._internal_create_key,
+  ),
+])
+_sym_db.RegisterServiceDescriptor(_PROJECTS)
+
+DESCRIPTOR.services_by_name['Projects'] = _PROJECTS
+
+# @@protoc_insertion_point(module_scope)
diff --git a/api/v3/api_proto/projects_prpc_pb2.py b/api/v3/api_proto/projects_prpc_pb2.py
new file mode 100644
index 0000000..326f69d
--- /dev/null
+++ b/api/v3/api_proto/projects_prpc_pb2.py
@@ -0,0 +1,889 @@
+# Generated by the pRPC protocol buffer compiler plugin.  DO NOT EDIT!
+# source: api/v3/api_proto/projects.proto
+
+import base64
+import zlib
+
+from google.protobuf import descriptor_pb2
+
+# Includes description of the api/v3/api_proto/projects.proto and all of its transitive
+# dependencies. Includes source code info.
+FILE_DESCRIPTOR_SET = descriptor_pb2.FileDescriptorSet()
+FILE_DESCRIPTOR_SET.ParseFromString(zlib.decompress(base64.b64decode(
+    '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'
+    'd+3sj/kzOUzH9mdyOGrE7cEevVT//zc7BrM=')))
+_INDEX = {
+    f.name: {
+      'descriptor': f,
+      'services': {s.name: s for s in f.service},
+    }
+    for f in FILE_DESCRIPTOR_SET.file
+}
+
+
+ProjectsServiceDescription = {
+  'file_descriptor_set': FILE_DESCRIPTOR_SET,
+  'file_descriptor': _INDEX[u'api/v3/api_proto/projects.proto']['descriptor'],
+  'service_descriptor': _INDEX[u'api/v3/api_proto/projects.proto']['services'][u'Projects'],
+}
diff --git a/api/v3/api_proto/user_objects.proto b/api/v3/api_proto/user_objects.proto
new file mode 100644
index 0000000..958efbc
--- /dev/null
+++ b/api/v3/api_proto/user_objects.proto
@@ -0,0 +1,183 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd
+
+// This file defines protobufs for users and related business
+// objects, e.g., users, user preferences.
+
+syntax = "proto3";
+
+package monorail.v3;
+
+option go_package = "api/v3/api_proto";
+
+import "google/api/resource.proto";
+import "google/api/field_behavior.proto";
+
+// User represents a user of the Monorail site.
+// Next available tag: 5
+message User {
+  option (google.api.resource) = {
+    type: "api.crbug.com/User"
+    pattern: "users/{user_id}"
+  };
+  // Resource name of the user.
+  // The API will always return User names with format: users/<user_id>.
+  // However the API will accept User names with formats: users/<user_id> or users/<email>.
+  // To fetch the display_name for any users/<user_id> returned by the API,
+  // you can call {Batch}GetUser{s}.
+  // We represent deleted users within Monorail with `users/1` or `users/2103649657`.
+  string name = 1;
+  // User display_name to show other users using the site.
+  // By default this is the obscured or un-obscured email.
+  string display_name = 2;
+  // Obscured or un-obscured user email or empty if this represents
+  // a deleted user.
+  string email = 4 [ (google.api.field_behavior) = OUTPUT_ONLY ];
+  // User-written indication of their availability or working hours.
+  string availability_message = 3;
+}
+
+
+// UserSettings represents preferences and account settings of a User.
+// Next available tag: 8
+message UserSettings {
+  option (google.api.resource) = {
+    type: "api.crbug.com/UserSettings"
+    pattern: "usersettings/{user_id}"
+  };
+
+  // Potential roles of a user.
+  // Next available tag: 3
+  enum SiteRole {
+    // Default value. This value is unused.
+    SITE_ROLE_UNSPECIFIED = 0;
+    // Normal site user with no special site-wide extra permissions.
+    NORMAL = 1;
+    // Site-wide admin role.
+    ADMIN = 2;
+  }
+
+  // The access the user has to the site.
+  // Next available tag: 3
+  message SiteAccess {
+    // Potential status of a user's access to the site.
+    // Next available tag: 3
+    enum Status {
+      // Default value. This value is unused.
+      STATUS_UNSPECIFIED = 0;
+      // The user has access to the site.
+      FULL_ACCESS = 1;
+      // The user is banned from the site.
+      BANNED = 2;
+    }
+
+    // The status of the user's access to the site.
+    Status status = 1;
+    // An explanation for the value of `status`.
+    string reason = 2;
+  }
+
+  // Trait options for notifications the user receives.
+  // Next available tag: 6;
+  enum NotificationTraits {
+    // Default value. This value is unused.
+    NOTIFICATION_TRAITS_UNSPECIFIED = 0;
+    // Send change notifications for issues where user is owner or cc.
+    NOTIFY_ON_OWNED_OR_CC_ISSUE_CHANGES = 1;
+    // Send change notifications for issues the user has starred.
+    NOTIFY_ON_STARRED_ISSUE_CHANGES = 2;
+    // Send date-type field notifications for issues the user has starred.
+    // See monorail/doc/userguide/email.md#why-did-i-get-a-follow_up-email-notification.
+    NOTIFY_ON_STARRED_NOTIFY_DATES = 3;
+    // Email subject lines should be compact.
+    COMPACT_SUBJECT_LINE = 4;
+    // Include a button link to the issue, in Gmail.
+    GMAIL_INCLUDE_ISSUE_LINK_BUTTON = 5;
+  }
+
+  // Privacy trait options for the user.
+  // Next available tag: 2
+  enum PrivacyTraits {
+    // Default value. This value is unused.
+    PRIVACY_TRAITS_UNSPECIFIED = 0;
+    // Obscure the user's email from non-project members throughout the site.
+    OBSCURE_EMAIL = 1;
+  }
+
+  // Site interaction trait options for the user.
+  // Next available tag: 3
+  enum SiteInteractionTraits {
+    // Default value. This value is unused.
+    SITE_INTERACTION_TRAITS_UNSPECIFIED = 0;
+    // Add 'Restrict-View-Google' labels to new issues the user reports.
+    // Issues will only be visible to the user (issue reporter)
+    // and users with the `Google` permission.
+    REPORT_RESTRICT_VIEW_GOOGLE_ISSUES = 1;
+    // When viewing public issues, show a banner to remind the user not
+    // to post sensitive information.
+    PUBLIC_ISSUE_BANNER = 2;
+  }
+
+  // Resource name of the user that has these settings.
+  string name = 1 [ (google.api.resource_reference) = {type: "api.crbug.com/UserSettings"} ];
+  // The global site role for the user.
+  SiteRole site_role = 2 [ (google.api.field_behavior) = OUTPUT_ONLY ];
+  // Resource name of linked secondary users.
+  repeated string linked_secondary_users = 3 [
+      (google.api.resource_reference) = {type: "api.crbug.com/User"},
+      (google.api.field_behavior) = OUTPUT_ONLY ];
+  // The user's access to the site.
+  SiteAccess site_access = 4 [ (google.api.field_behavior) = OUTPUT_ONLY ];
+  // Notification trait preferences of the user.
+  repeated NotificationTraits notification_traits = 5;
+  // Privacy trait preferences of the user.
+  repeated PrivacyTraits privacy_traits = 6;
+  // Site interaction trait preferences of the user.
+  repeated SiteInteractionTraits site_interaction_traits = 7;
+}
+
+// Defines saved queries that belong to a user.
+//
+// Next available tag: 6
+message UserSavedQuery {
+  option (google.api.resource) = {
+    type: "api.crbug.com/UserSavedQuery"
+    pattern: "users/{user_id}/savedQueries/{saved_query_id}"
+  };
+
+  // Resource name of this saved query.
+  string name = 1;
+  // Display name of this saved query, ie 'open issues'.
+  string display_name = 2;
+  // Search term of this saved query.
+  string query = 3;
+  // List of projects this query can be searched in.
+  repeated string projects = 4 [
+      (google.api.resource_reference) = { type: "api.crbug.com/Project" }
+  ];
+  // Subscription mode of this saved query
+  // Next available tag: 3
+  enum SubscriptionMode {
+    // Default API value. This value is unused.
+    SUBSCRIPTION_MODE_UNSPECIFIED = 0;
+    // Do not subscribe to notifications.
+    NO_NOTIFICATION = 1;
+    // Subscribe to notifications.
+    IMMEDIATE_NOTIFICATION = 2;
+  }
+  SubscriptionMode subscription_mode = 5;
+}
+
+// A project starred by a user.
+//
+// Next available tag: 2
+message ProjectStar {
+  option (google.api.resource) = {
+    type: "api.crbug.com/ProjectStar"
+    pattern: "users/{user_id}/projectStars/{project_name}"
+  };
+  // Resource name of the ProjectStar.
+  string name = 1;
+}
\ No newline at end of file
diff --git a/api/v3/api_proto/user_objects_pb2.py b/api/v3/api_proto/user_objects_pb2.py
new file mode 100644
index 0000000..2c68f09
--- /dev/null
+++ b/api/v3/api_proto/user_objects_pb2.py
@@ -0,0 +1,551 @@
+# -*- coding: utf-8 -*-
+# Generated by the protocol buffer compiler.  DO NOT EDIT!
+# source: api/v3/api_proto/user_objects.proto
+
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import message as _message
+from google.protobuf import reflection as _reflection
+from google.protobuf import symbol_database as _symbol_database
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+from google.api import resource_pb2 as google_dot_api_dot_resource__pb2
+from google.api import field_behavior_pb2 as google_dot_api_dot_field__behavior__pb2
+
+
+DESCRIPTOR = _descriptor.FileDescriptor(
+  name='api/v3/api_proto/user_objects.proto',
+  package='monorail.v3',
+  syntax='proto3',
+  serialized_options=b'Z\020api/v3/api_proto',
+  create_key=_descriptor._internal_create_key,
+  serialized_pb=b'\n#api/v3/api_proto/user_objects.proto\x12\x0bmonorail.v3\x1a\x19google/api/resource.proto\x1a\x1fgoogle/api/field_behavior.proto\"\x86\x01\n\x04User\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x14\n\x0c\x64isplay_name\x18\x02 \x01(\t\x12\x12\n\x05\x65mail\x18\x04 \x01(\tB\x03\xe0\x41\x03\x12\x1c\n\x14\x61vailability_message\x18\x03 \x01(\t:(\xea\x41%\n\x12\x61pi.crbug.com/User\x12\x0fusers/{user_id}\"\x9a\t\n\x0cUserSettings\x12-\n\x04name\x18\x01 \x01(\tB\x1f\xfa\x41\x1c\n\x1a\x61pi.crbug.com/UserSettings\x12:\n\tsite_role\x18\x02 \x01(\x0e\x32\".monorail.v3.UserSettings.SiteRoleB\x03\xe0\x41\x03\x12:\n\x16linked_secondary_users\x18\x03 \x03(\tB\x1a\xfa\x41\x14\n\x12\x61pi.crbug.com/User\xe0\x41\x03\x12>\n\x0bsite_access\x18\x04 \x01(\x0b\x32$.monorail.v3.UserSettings.SiteAccessB\x03\xe0\x41\x03\x12I\n\x13notification_traits\x18\x05 \x03(\x0e\x32,.monorail.v3.UserSettings.NotificationTraits\x12?\n\x0eprivacy_traits\x18\x06 \x03(\x0e\x32\'.monorail.v3.UserSettings.PrivacyTraits\x12P\n\x17site_interaction_traits\x18\x07 \x03(\x0e\x32/.monorail.v3.UserSettings.SiteInteractionTraits\x1a\x98\x01\n\nSiteAccess\x12;\n\x06status\x18\x01 \x01(\x0e\x32+.monorail.v3.UserSettings.SiteAccess.Status\x12\x0e\n\x06reason\x18\x02 \x01(\t\"=\n\x06Status\x12\x16\n\x12STATUS_UNSPECIFIED\x10\x00\x12\x0f\n\x0b\x46ULL_ACCESS\x10\x01\x12\n\n\x06\x42\x41NNED\x10\x02\"<\n\x08SiteRole\x12\x19\n\x15SITE_ROLE_UNSPECIFIED\x10\x00\x12\n\n\x06NORMAL\x10\x01\x12\t\n\x05\x41\x44MIN\x10\x02\"\xea\x01\n\x12NotificationTraits\x12#\n\x1fNOTIFICATION_TRAITS_UNSPECIFIED\x10\x00\x12\'\n#NOTIFY_ON_OWNED_OR_CC_ISSUE_CHANGES\x10\x01\x12#\n\x1fNOTIFY_ON_STARRED_ISSUE_CHANGES\x10\x02\x12\"\n\x1eNOTIFY_ON_STARRED_NOTIFY_DATES\x10\x03\x12\x18\n\x14\x43OMPACT_SUBJECT_LINE\x10\x04\x12#\n\x1fGMAIL_INCLUDE_ISSUE_LINK_BUTTON\x10\x05\"B\n\rPrivacyTraits\x12\x1e\n\x1aPRIVACY_TRAITS_UNSPECIFIED\x10\x00\x12\x11\n\rOBSCURE_EMAIL\x10\x01\"\x81\x01\n\x15SiteInteractionTraits\x12\'\n#SITE_INTERACTION_TRAITS_UNSPECIFIED\x10\x00\x12&\n\"REPORT_RESTRICT_VIEW_GOOGLE_ISSUES\x10\x01\x12\x17\n\x13PUBLIC_ISSUE_BANNER\x10\x02:7\xea\x41\x34\n\x1a\x61pi.crbug.com/UserSettings\x12\x16usersettings/{user_id}\"\xf4\x02\n\x0eUserSavedQuery\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x14\n\x0c\x64isplay_name\x18\x02 \x01(\t\x12\r\n\x05query\x18\x03 \x01(\t\x12,\n\x08projects\x18\x04 \x03(\tB\x1a\xfa\x41\x17\n\x15\x61pi.crbug.com/Project\x12G\n\x11subscription_mode\x18\x05 \x01(\x0e\x32,.monorail.v3.UserSavedQuery.SubscriptionMode\"f\n\x10SubscriptionMode\x12!\n\x1dSUBSCRIPTION_MODE_UNSPECIFIED\x10\x00\x12\x13\n\x0fNO_NOTIFICATION\x10\x01\x12\x1a\n\x16IMMEDIATE_NOTIFICATION\x10\x02:P\xea\x41M\n\x1c\x61pi.crbug.com/UserSavedQuery\x12-users/{user_id}/savedQueries/{saved_query_id}\"h\n\x0bProjectStar\x12\x0c\n\x04name\x18\x01 \x01(\t:K\xea\x41H\n\x19\x61pi.crbug.com/ProjectStar\x12+users/{user_id}/projectStars/{project_name}B\x12Z\x10\x61pi/v3/api_protob\x06proto3'
+  ,
+  dependencies=[google_dot_api_dot_resource__pb2.DESCRIPTOR,google_dot_api_dot_field__behavior__pb2.DESCRIPTOR,])
+
+
+
+_USERSETTINGS_SITEACCESS_STATUS = _descriptor.EnumDescriptor(
+  name='Status',
+  full_name='monorail.v3.UserSettings.SiteAccess.Status',
+  filename=None,
+  file=DESCRIPTOR,
+  create_key=_descriptor._internal_create_key,
+  values=[
+    _descriptor.EnumValueDescriptor(
+      name='STATUS_UNSPECIFIED', index=0, number=0,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='FULL_ACCESS', index=1, number=1,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='BANNED', index=2, number=2,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+  ],
+  containing_type=None,
+  serialized_options=None,
+  serialized_start=811,
+  serialized_end=872,
+)
+_sym_db.RegisterEnumDescriptor(_USERSETTINGS_SITEACCESS_STATUS)
+
+_USERSETTINGS_SITEROLE = _descriptor.EnumDescriptor(
+  name='SiteRole',
+  full_name='monorail.v3.UserSettings.SiteRole',
+  filename=None,
+  file=DESCRIPTOR,
+  create_key=_descriptor._internal_create_key,
+  values=[
+    _descriptor.EnumValueDescriptor(
+      name='SITE_ROLE_UNSPECIFIED', index=0, number=0,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='NORMAL', index=1, number=1,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='ADMIN', index=2, number=2,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+  ],
+  containing_type=None,
+  serialized_options=None,
+  serialized_start=874,
+  serialized_end=934,
+)
+_sym_db.RegisterEnumDescriptor(_USERSETTINGS_SITEROLE)
+
+_USERSETTINGS_NOTIFICATIONTRAITS = _descriptor.EnumDescriptor(
+  name='NotificationTraits',
+  full_name='monorail.v3.UserSettings.NotificationTraits',
+  filename=None,
+  file=DESCRIPTOR,
+  create_key=_descriptor._internal_create_key,
+  values=[
+    _descriptor.EnumValueDescriptor(
+      name='NOTIFICATION_TRAITS_UNSPECIFIED', index=0, number=0,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='NOTIFY_ON_OWNED_OR_CC_ISSUE_CHANGES', index=1, number=1,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='NOTIFY_ON_STARRED_ISSUE_CHANGES', index=2, number=2,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='NOTIFY_ON_STARRED_NOTIFY_DATES', index=3, number=3,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='COMPACT_SUBJECT_LINE', index=4, number=4,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='GMAIL_INCLUDE_ISSUE_LINK_BUTTON', index=5, number=5,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+  ],
+  containing_type=None,
+  serialized_options=None,
+  serialized_start=937,
+  serialized_end=1171,
+)
+_sym_db.RegisterEnumDescriptor(_USERSETTINGS_NOTIFICATIONTRAITS)
+
+_USERSETTINGS_PRIVACYTRAITS = _descriptor.EnumDescriptor(
+  name='PrivacyTraits',
+  full_name='monorail.v3.UserSettings.PrivacyTraits',
+  filename=None,
+  file=DESCRIPTOR,
+  create_key=_descriptor._internal_create_key,
+  values=[
+    _descriptor.EnumValueDescriptor(
+      name='PRIVACY_TRAITS_UNSPECIFIED', index=0, number=0,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='OBSCURE_EMAIL', index=1, number=1,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+  ],
+  containing_type=None,
+  serialized_options=None,
+  serialized_start=1173,
+  serialized_end=1239,
+)
+_sym_db.RegisterEnumDescriptor(_USERSETTINGS_PRIVACYTRAITS)
+
+_USERSETTINGS_SITEINTERACTIONTRAITS = _descriptor.EnumDescriptor(
+  name='SiteInteractionTraits',
+  full_name='monorail.v3.UserSettings.SiteInteractionTraits',
+  filename=None,
+  file=DESCRIPTOR,
+  create_key=_descriptor._internal_create_key,
+  values=[
+    _descriptor.EnumValueDescriptor(
+      name='SITE_INTERACTION_TRAITS_UNSPECIFIED', index=0, number=0,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='REPORT_RESTRICT_VIEW_GOOGLE_ISSUES', index=1, number=1,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='PUBLIC_ISSUE_BANNER', index=2, number=2,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+  ],
+  containing_type=None,
+  serialized_options=None,
+  serialized_start=1242,
+  serialized_end=1371,
+)
+_sym_db.RegisterEnumDescriptor(_USERSETTINGS_SITEINTERACTIONTRAITS)
+
+_USERSAVEDQUERY_SUBSCRIPTIONMODE = _descriptor.EnumDescriptor(
+  name='SubscriptionMode',
+  full_name='monorail.v3.UserSavedQuery.SubscriptionMode',
+  filename=None,
+  file=DESCRIPTOR,
+  create_key=_descriptor._internal_create_key,
+  values=[
+    _descriptor.EnumValueDescriptor(
+      name='SUBSCRIPTION_MODE_UNSPECIFIED', index=0, number=0,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='NO_NOTIFICATION', index=1, number=1,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+    _descriptor.EnumValueDescriptor(
+      name='IMMEDIATE_NOTIFICATION', index=2, number=2,
+      serialized_options=None,
+      type=None,
+      create_key=_descriptor._internal_create_key),
+  ],
+  containing_type=None,
+  serialized_options=None,
+  serialized_start=1619,
+  serialized_end=1721,
+)
+_sym_db.RegisterEnumDescriptor(_USERSAVEDQUERY_SUBSCRIPTIONMODE)
+
+
+_USER = _descriptor.Descriptor(
+  name='User',
+  full_name='monorail.v3.User',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='name', full_name='monorail.v3.User.name', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='display_name', full_name='monorail.v3.User.display_name', index=1,
+      number=2, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='email', full_name='monorail.v3.User.email', index=2,
+      number=4, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\340A\003', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='availability_message', full_name='monorail.v3.User.availability_message', index=3,
+      number=3, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=b'\352A%\n\022api.crbug.com/User\022\017users/{user_id}',
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=113,
+  serialized_end=247,
+)
+
+
+_USERSETTINGS_SITEACCESS = _descriptor.Descriptor(
+  name='SiteAccess',
+  full_name='monorail.v3.UserSettings.SiteAccess',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='status', full_name='monorail.v3.UserSettings.SiteAccess.status', index=0,
+      number=1, type=14, cpp_type=8, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='reason', full_name='monorail.v3.UserSettings.SiteAccess.reason', index=1,
+      number=2, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+    _USERSETTINGS_SITEACCESS_STATUS,
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=720,
+  serialized_end=872,
+)
+
+_USERSETTINGS = _descriptor.Descriptor(
+  name='UserSettings',
+  full_name='monorail.v3.UserSettings',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='name', full_name='monorail.v3.UserSettings.name', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\372A\034\n\032api.crbug.com/UserSettings', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='site_role', full_name='monorail.v3.UserSettings.site_role', index=1,
+      number=2, type=14, cpp_type=8, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\340A\003', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='linked_secondary_users', full_name='monorail.v3.UserSettings.linked_secondary_users', index=2,
+      number=3, type=9, cpp_type=9, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\372A\024\n\022api.crbug.com/User\340A\003', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='site_access', full_name='monorail.v3.UserSettings.site_access', index=3,
+      number=4, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\340A\003', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='notification_traits', full_name='monorail.v3.UserSettings.notification_traits', index=4,
+      number=5, type=14, cpp_type=8, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='privacy_traits', full_name='monorail.v3.UserSettings.privacy_traits', index=5,
+      number=6, type=14, cpp_type=8, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='site_interaction_traits', full_name='monorail.v3.UserSettings.site_interaction_traits', index=6,
+      number=7, type=14, cpp_type=8, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[_USERSETTINGS_SITEACCESS, ],
+  enum_types=[
+    _USERSETTINGS_SITEROLE,
+    _USERSETTINGS_NOTIFICATIONTRAITS,
+    _USERSETTINGS_PRIVACYTRAITS,
+    _USERSETTINGS_SITEINTERACTIONTRAITS,
+  ],
+  serialized_options=b'\352A4\n\032api.crbug.com/UserSettings\022\026usersettings/{user_id}',
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=250,
+  serialized_end=1428,
+)
+
+
+_USERSAVEDQUERY = _descriptor.Descriptor(
+  name='UserSavedQuery',
+  full_name='monorail.v3.UserSavedQuery',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='name', full_name='monorail.v3.UserSavedQuery.name', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='display_name', full_name='monorail.v3.UserSavedQuery.display_name', index=1,
+      number=2, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='query', full_name='monorail.v3.UserSavedQuery.query', index=2,
+      number=3, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='projects', full_name='monorail.v3.UserSavedQuery.projects', index=3,
+      number=4, type=9, cpp_type=9, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\372A\027\n\025api.crbug.com/Project', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='subscription_mode', full_name='monorail.v3.UserSavedQuery.subscription_mode', index=4,
+      number=5, type=14, cpp_type=8, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+    _USERSAVEDQUERY_SUBSCRIPTIONMODE,
+  ],
+  serialized_options=b'\352AM\n\034api.crbug.com/UserSavedQuery\022-users/{user_id}/savedQueries/{saved_query_id}',
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=1431,
+  serialized_end=1803,
+)
+
+
+_PROJECTSTAR = _descriptor.Descriptor(
+  name='ProjectStar',
+  full_name='monorail.v3.ProjectStar',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='name', full_name='monorail.v3.ProjectStar.name', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=b'\352AH\n\031api.crbug.com/ProjectStar\022+users/{user_id}/projectStars/{project_name}',
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=1805,
+  serialized_end=1909,
+)
+
+_USERSETTINGS_SITEACCESS.fields_by_name['status'].enum_type = _USERSETTINGS_SITEACCESS_STATUS
+_USERSETTINGS_SITEACCESS.containing_type = _USERSETTINGS
+_USERSETTINGS_SITEACCESS_STATUS.containing_type = _USERSETTINGS_SITEACCESS
+_USERSETTINGS.fields_by_name['site_role'].enum_type = _USERSETTINGS_SITEROLE
+_USERSETTINGS.fields_by_name['site_access'].message_type = _USERSETTINGS_SITEACCESS
+_USERSETTINGS.fields_by_name['notification_traits'].enum_type = _USERSETTINGS_NOTIFICATIONTRAITS
+_USERSETTINGS.fields_by_name['privacy_traits'].enum_type = _USERSETTINGS_PRIVACYTRAITS
+_USERSETTINGS.fields_by_name['site_interaction_traits'].enum_type = _USERSETTINGS_SITEINTERACTIONTRAITS
+_USERSETTINGS_SITEROLE.containing_type = _USERSETTINGS
+_USERSETTINGS_NOTIFICATIONTRAITS.containing_type = _USERSETTINGS
+_USERSETTINGS_PRIVACYTRAITS.containing_type = _USERSETTINGS
+_USERSETTINGS_SITEINTERACTIONTRAITS.containing_type = _USERSETTINGS
+_USERSAVEDQUERY.fields_by_name['subscription_mode'].enum_type = _USERSAVEDQUERY_SUBSCRIPTIONMODE
+_USERSAVEDQUERY_SUBSCRIPTIONMODE.containing_type = _USERSAVEDQUERY
+DESCRIPTOR.message_types_by_name['User'] = _USER
+DESCRIPTOR.message_types_by_name['UserSettings'] = _USERSETTINGS
+DESCRIPTOR.message_types_by_name['UserSavedQuery'] = _USERSAVEDQUERY
+DESCRIPTOR.message_types_by_name['ProjectStar'] = _PROJECTSTAR
+_sym_db.RegisterFileDescriptor(DESCRIPTOR)
+
+User = _reflection.GeneratedProtocolMessageType('User', (_message.Message,), {
+  'DESCRIPTOR' : _USER,
+  '__module__' : 'api.v3.api_proto.user_objects_pb2'
+  # @@protoc_insertion_point(class_scope:monorail.v3.User)
+  })
+_sym_db.RegisterMessage(User)
+
+UserSettings = _reflection.GeneratedProtocolMessageType('UserSettings', (_message.Message,), {
+
+  'SiteAccess' : _reflection.GeneratedProtocolMessageType('SiteAccess', (_message.Message,), {
+    'DESCRIPTOR' : _USERSETTINGS_SITEACCESS,
+    '__module__' : 'api.v3.api_proto.user_objects_pb2'
+    # @@protoc_insertion_point(class_scope:monorail.v3.UserSettings.SiteAccess)
+    })
+  ,
+  'DESCRIPTOR' : _USERSETTINGS,
+  '__module__' : 'api.v3.api_proto.user_objects_pb2'
+  # @@protoc_insertion_point(class_scope:monorail.v3.UserSettings)
+  })
+_sym_db.RegisterMessage(UserSettings)
+_sym_db.RegisterMessage(UserSettings.SiteAccess)
+
+UserSavedQuery = _reflection.GeneratedProtocolMessageType('UserSavedQuery', (_message.Message,), {
+  'DESCRIPTOR' : _USERSAVEDQUERY,
+  '__module__' : 'api.v3.api_proto.user_objects_pb2'
+  # @@protoc_insertion_point(class_scope:monorail.v3.UserSavedQuery)
+  })
+_sym_db.RegisterMessage(UserSavedQuery)
+
+ProjectStar = _reflection.GeneratedProtocolMessageType('ProjectStar', (_message.Message,), {
+  'DESCRIPTOR' : _PROJECTSTAR,
+  '__module__' : 'api.v3.api_proto.user_objects_pb2'
+  # @@protoc_insertion_point(class_scope:monorail.v3.ProjectStar)
+  })
+_sym_db.RegisterMessage(ProjectStar)
+
+
+DESCRIPTOR._options = None
+_USER.fields_by_name['email']._options = None
+_USER._options = None
+_USERSETTINGS.fields_by_name['name']._options = None
+_USERSETTINGS.fields_by_name['site_role']._options = None
+_USERSETTINGS.fields_by_name['linked_secondary_users']._options = None
+_USERSETTINGS.fields_by_name['site_access']._options = None
+_USERSETTINGS._options = None
+_USERSAVEDQUERY.fields_by_name['projects']._options = None
+_USERSAVEDQUERY._options = None
+_PROJECTSTAR._options = None
+# @@protoc_insertion_point(module_scope)
diff --git a/api/v3/api_proto/users.proto b/api/v3/api_proto/users.proto
new file mode 100644
index 0000000..7d8aa48
--- /dev/null
+++ b/api/v3/api_proto/users.proto
@@ -0,0 +1,161 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+syntax = "proto3";
+
+package monorail.v3;
+
+option go_package = "api/v3/api_proto";
+
+import "google/api/field_behavior.proto";
+import "google/api/resource.proto";
+import "api/v3/api_proto/user_objects.proto";
+import "google/protobuf/empty.proto";
+import "google/protobuf/field_mask.proto";
+
+// ***ONLY CALL rpcs WITH `status: {ALPHA|STABLE}`***
+// rpcs without `status` are not ready.
+
+// Users service includes all methods needed for managing Users.
+service Users {
+  // status: ALPHA
+  // Returns the requested User.
+  //
+  // Raises:
+  //   NOT_FOUND is the user is not found.
+  //   INVALID_ARGUMENT if the `name` is invalid.
+  rpc GetUser (GetUserRequest) returns (User) {}
+
+  // status: ALPHA
+  // Returns all of the requested Users.
+  //
+  // Raises:
+  //   NOT_FOUND if any users are not found.
+  //   INVALID_ARGUMENT if any `names` are invalid.
+  rpc BatchGetUsers (BatchGetUsersRequest) returns (BatchGetUsersResponse) {}
+
+  // status: NOT READY
+  // Updates a User.
+  //
+  // Raises:
+  //   NOT_FOUND if the user is not found.
+  //   PERMISSION_DENIED if the requester is not allowed to update the user.
+  //   INVALID_ARGUMENT if required fields are missing or fields are invalid.
+  rpc UpdateUser (UpdateUserRequest) returns (User) {}
+
+  // status: NOT READY
+  // Stars a given project for the requestor.
+  //
+  // Raises:
+  //   NOT_FOUND if the requested project is not found.
+  //   INVALID_ARGUMENT if the given `project` is not valid.
+  rpc StarProject (StarProjectRequest) returns (ProjectStar) {}
+
+  // status: NOT READY
+  // Unstars a given project for the requestor.
+  //
+  // Raises:
+  //   NOT_FOUND if the requested project is not found.
+  //   INVALID_ARGUMENT if the given `project` is not valid.
+  rpc UnStarProject (UnStarProjectRequest) returns (google.protobuf.Empty) {}
+
+  // status: NOT READY
+  // Lists all of a user's starred projects.
+  //
+  // Raises:
+  //   NOT_FOUND if the requested user is not found.
+  //   INVALID_ARGUMENT if the given `parent` is not valid.
+  rpc ListProjectStars (ListProjectStarsRequest) returns (ListProjectStarsResponse) {}
+}
+
+
+// The request message for Users.GetUser.
+// Next available tag: 2
+message GetUserRequest {
+  // The name of the user to request.
+  string name = 1 [
+      (google.api.resource_reference) = {type: "api.crbug.com/User"},
+      (google.api.field_behavior) = REQUIRED ];
+}
+
+
+// The request message for Users.BatchGetUsers.
+// Next available tag: 2
+message BatchGetUsersRequest {
+  // The name of the users to request. At most 100 may be requested.
+  repeated string names = 1 [
+      (google.api.resource_reference) = {type: "api.crbug.com/User"},
+      (google.api.field_behavior) = REQUIRED ];
+}
+
+
+// The response message for Users.BatchGetUsers.
+// Next available tag: 2
+message BatchGetUsersResponse {
+  // The users that were requested.
+  repeated User users = 1;
+}
+
+
+// The request message for Users.UpdateUser.
+// Next available tag: 3
+message UpdateUserRequest {
+  // The user's `name` field is used to identify the user to be updated.
+  User user = 1 [
+      (google.api.field_behavior) = REQUIRED,
+      (google.api.resource_reference) = {type: "api.crbug.com/User"} ];
+  // The list of fields to be updated.
+  google.protobuf.FieldMask update_mask = 2 [ (google.api.field_behavior) = REQUIRED ];
+}
+
+
+// The request message for Users.StarProject.
+// Next available tag: 2
+message StarProjectRequest {
+  // The resource name for the Project to star.
+  string project = 1 [
+      (google.api.resource_reference) = {type: "api.crbug.com/Project"},
+      (google.api.field_behavior) = REQUIRED ];
+}
+
+
+// The request message for Users.UnStarProject.
+// Next available tag: 2
+message UnStarProjectRequest {
+  // The resource name for the Project to unstar.
+  string project = 1 [
+      (google.api.resource_reference) = {type: "api.crbug.com/Project"},
+      (google.api.field_behavior) = REQUIRED ];
+}
+
+
+// The request message for Users.ListProjectStars.
+// Next available tag: 4
+message ListProjectStarsRequest {
+  // The resource name for the user having stars listed.
+  string parent = 1 [
+      (google.api.resource_reference) = {type: "api.crbug.com/User"},
+      (google.api.field_behavior) = REQUIRED ];
+  // The maximum number of items to return. The service may return fewer than
+  // this value.
+  // If unspecified, at most 1000 items will be returned.
+  int32 page_size = 2;
+  // A page token, received from a previous `ListProjectStars` call.
+  // Provide this to retrieve the subsequent page.
+  //
+  // When paginating, all other parameters provided to `ListProjectStars` must
+  // match the call that provided the page token.
+  string page_token = 3;
+}
+
+
+// The response message for Users.ListProjectStars.
+// Next available tag: 3
+message ListProjectStarsResponse {
+  // Data for each starred project.
+  repeated ProjectStar project_stars = 1;
+  // A token, which can be sent as `page_token` to retrieve the next page.
+  // If this field is omitted, there are no subsequent pages.
+  string next_page_token = 2;
+}
diff --git a/api/v3/api_proto/users_pb2.py b/api/v3/api_proto/users_pb2.py
new file mode 100644
index 0000000..f08b494
--- /dev/null
+++ b/api/v3/api_proto/users_pb2.py
@@ -0,0 +1,472 @@
+# -*- coding: utf-8 -*-
+# Generated by the protocol buffer compiler.  DO NOT EDIT!
+# source: api/v3/api_proto/users.proto
+
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import message as _message
+from google.protobuf import reflection as _reflection
+from google.protobuf import symbol_database as _symbol_database
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+from google.api import field_behavior_pb2 as google_dot_api_dot_field__behavior__pb2
+from google.api import resource_pb2 as google_dot_api_dot_resource__pb2
+from api.v3.api_proto import user_objects_pb2 as api_dot_v3_dot_api__proto_dot_user__objects__pb2
+from google.protobuf import empty_pb2 as google_dot_protobuf_dot_empty__pb2
+from google.protobuf import field_mask_pb2 as google_dot_protobuf_dot_field__mask__pb2
+
+
+DESCRIPTOR = _descriptor.FileDescriptor(
+  name='api/v3/api_proto/users.proto',
+  package='monorail.v3',
+  syntax='proto3',
+  serialized_options=b'Z\020api/v3/api_proto',
+  create_key=_descriptor._internal_create_key,
+  serialized_pb=b'\n\x1c\x61pi/v3/api_proto/users.proto\x12\x0bmonorail.v3\x1a\x1fgoogle/api/field_behavior.proto\x1a\x19google/api/resource.proto\x1a#api/v3/api_proto/user_objects.proto\x1a\x1bgoogle/protobuf/empty.proto\x1a google/protobuf/field_mask.proto\":\n\x0eGetUserRequest\x12(\n\x04name\x18\x01 \x01(\tB\x1a\xfa\x41\x14\n\x12\x61pi.crbug.com/User\xe0\x41\x02\"A\n\x14\x42\x61tchGetUsersRequest\x12)\n\x05names\x18\x01 \x03(\tB\x1a\xfa\x41\x14\n\x12\x61pi.crbug.com/User\xe0\x41\x02\"9\n\x15\x42\x61tchGetUsersResponse\x12 \n\x05users\x18\x01 \x03(\x0b\x32\x11.monorail.v3.User\"\x86\x01\n\x11UpdateUserRequest\x12;\n\x04user\x18\x01 \x01(\x0b\x32\x11.monorail.v3.UserB\x1a\xe0\x41\x02\xfa\x41\x14\n\x12\x61pi.crbug.com/User\x12\x34\n\x0bupdate_mask\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.FieldMaskB\x03\xe0\x41\x02\"D\n\x12StarProjectRequest\x12.\n\x07project\x18\x01 \x01(\tB\x1d\xfa\x41\x17\n\x15\x61pi.crbug.com/Project\xe0\x41\x02\"F\n\x14UnStarProjectRequest\x12.\n\x07project\x18\x01 \x01(\tB\x1d\xfa\x41\x17\n\x15\x61pi.crbug.com/Project\xe0\x41\x02\"l\n\x17ListProjectStarsRequest\x12*\n\x06parent\x18\x01 \x01(\tB\x1a\xfa\x41\x14\n\x12\x61pi.crbug.com/User\xe0\x41\x02\x12\x11\n\tpage_size\x18\x02 \x01(\x05\x12\x12\n\npage_token\x18\x03 \x01(\t\"d\n\x18ListProjectStarsResponse\x12/\n\rproject_stars\x18\x01 \x03(\x0b\x32\x18.monorail.v3.ProjectStar\x12\x17\n\x0fnext_page_token\x18\x02 \x01(\t2\xde\x03\n\x05Users\x12;\n\x07GetUser\x12\x1b.monorail.v3.GetUserRequest\x1a\x11.monorail.v3.User\"\x00\x12X\n\rBatchGetUsers\x12!.monorail.v3.BatchGetUsersRequest\x1a\".monorail.v3.BatchGetUsersResponse\"\x00\x12\x41\n\nUpdateUser\x12\x1e.monorail.v3.UpdateUserRequest\x1a\x11.monorail.v3.User\"\x00\x12J\n\x0bStarProject\x12\x1f.monorail.v3.StarProjectRequest\x1a\x18.monorail.v3.ProjectStar\"\x00\x12L\n\rUnStarProject\x12!.monorail.v3.UnStarProjectRequest\x1a\x16.google.protobuf.Empty\"\x00\x12\x61\n\x10ListProjectStars\x12$.monorail.v3.ListProjectStarsRequest\x1a%.monorail.v3.ListProjectStarsResponse\"\x00\x42\x12Z\x10\x61pi/v3/api_protob\x06proto3'
+  ,
+  dependencies=[google_dot_api_dot_field__behavior__pb2.DESCRIPTOR,google_dot_api_dot_resource__pb2.DESCRIPTOR,api_dot_v3_dot_api__proto_dot_user__objects__pb2.DESCRIPTOR,google_dot_protobuf_dot_empty__pb2.DESCRIPTOR,google_dot_protobuf_dot_field__mask__pb2.DESCRIPTOR,])
+
+
+
+
+_GETUSERREQUEST = _descriptor.Descriptor(
+  name='GetUserRequest',
+  full_name='monorail.v3.GetUserRequest',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='name', full_name='monorail.v3.GetUserRequest.name', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\372A\024\n\022api.crbug.com/User\340A\002', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=205,
+  serialized_end=263,
+)
+
+
+_BATCHGETUSERSREQUEST = _descriptor.Descriptor(
+  name='BatchGetUsersRequest',
+  full_name='monorail.v3.BatchGetUsersRequest',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='names', full_name='monorail.v3.BatchGetUsersRequest.names', index=0,
+      number=1, type=9, cpp_type=9, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\372A\024\n\022api.crbug.com/User\340A\002', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=265,
+  serialized_end=330,
+)
+
+
+_BATCHGETUSERSRESPONSE = _descriptor.Descriptor(
+  name='BatchGetUsersResponse',
+  full_name='monorail.v3.BatchGetUsersResponse',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='users', full_name='monorail.v3.BatchGetUsersResponse.users', index=0,
+      number=1, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=332,
+  serialized_end=389,
+)
+
+
+_UPDATEUSERREQUEST = _descriptor.Descriptor(
+  name='UpdateUserRequest',
+  full_name='monorail.v3.UpdateUserRequest',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='user', full_name='monorail.v3.UpdateUserRequest.user', index=0,
+      number=1, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\340A\002\372A\024\n\022api.crbug.com/User', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='update_mask', full_name='monorail.v3.UpdateUserRequest.update_mask', index=1,
+      number=2, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\340A\002', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=392,
+  serialized_end=526,
+)
+
+
+_STARPROJECTREQUEST = _descriptor.Descriptor(
+  name='StarProjectRequest',
+  full_name='monorail.v3.StarProjectRequest',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='project', full_name='monorail.v3.StarProjectRequest.project', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\372A\027\n\025api.crbug.com/Project\340A\002', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=528,
+  serialized_end=596,
+)
+
+
+_UNSTARPROJECTREQUEST = _descriptor.Descriptor(
+  name='UnStarProjectRequest',
+  full_name='monorail.v3.UnStarProjectRequest',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='project', full_name='monorail.v3.UnStarProjectRequest.project', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\372A\027\n\025api.crbug.com/Project\340A\002', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=598,
+  serialized_end=668,
+)
+
+
+_LISTPROJECTSTARSREQUEST = _descriptor.Descriptor(
+  name='ListProjectStarsRequest',
+  full_name='monorail.v3.ListProjectStarsRequest',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='parent', full_name='monorail.v3.ListProjectStarsRequest.parent', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=b'\372A\024\n\022api.crbug.com/User\340A\002', file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='page_size', full_name='monorail.v3.ListProjectStarsRequest.page_size', index=1,
+      number=2, type=5, cpp_type=1, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='page_token', full_name='monorail.v3.ListProjectStarsRequest.page_token', index=2,
+      number=3, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=670,
+  serialized_end=778,
+)
+
+
+_LISTPROJECTSTARSRESPONSE = _descriptor.Descriptor(
+  name='ListProjectStarsResponse',
+  full_name='monorail.v3.ListProjectStarsResponse',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  create_key=_descriptor._internal_create_key,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='project_stars', full_name='monorail.v3.ListProjectStarsResponse.project_stars', index=0,
+      number=1, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+    _descriptor.FieldDescriptor(
+      name='next_page_token', full_name='monorail.v3.ListProjectStarsResponse.next_page_token', index=1,
+      number=2, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=b"".decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=780,
+  serialized_end=880,
+)
+
+_BATCHGETUSERSRESPONSE.fields_by_name['users'].message_type = api_dot_v3_dot_api__proto_dot_user__objects__pb2._USER
+_UPDATEUSERREQUEST.fields_by_name['user'].message_type = api_dot_v3_dot_api__proto_dot_user__objects__pb2._USER
+_UPDATEUSERREQUEST.fields_by_name['update_mask'].message_type = google_dot_protobuf_dot_field__mask__pb2._FIELDMASK
+_LISTPROJECTSTARSRESPONSE.fields_by_name['project_stars'].message_type = api_dot_v3_dot_api__proto_dot_user__objects__pb2._PROJECTSTAR
+DESCRIPTOR.message_types_by_name['GetUserRequest'] = _GETUSERREQUEST
+DESCRIPTOR.message_types_by_name['BatchGetUsersRequest'] = _BATCHGETUSERSREQUEST
+DESCRIPTOR.message_types_by_name['BatchGetUsersResponse'] = _BATCHGETUSERSRESPONSE
+DESCRIPTOR.message_types_by_name['UpdateUserRequest'] = _UPDATEUSERREQUEST
+DESCRIPTOR.message_types_by_name['StarProjectRequest'] = _STARPROJECTREQUEST
+DESCRIPTOR.message_types_by_name['UnStarProjectRequest'] = _UNSTARPROJECTREQUEST
+DESCRIPTOR.message_types_by_name['ListProjectStarsRequest'] = _LISTPROJECTSTARSREQUEST
+DESCRIPTOR.message_types_by_name['ListProjectStarsResponse'] = _LISTPROJECTSTARSRESPONSE
+_sym_db.RegisterFileDescriptor(DESCRIPTOR)
+
+GetUserRequest = _reflection.GeneratedProtocolMessageType('GetUserRequest', (_message.Message,), {
+  'DESCRIPTOR' : _GETUSERREQUEST,
+  '__module__' : 'api.v3.api_proto.users_pb2'
+  # @@protoc_insertion_point(class_scope:monorail.v3.GetUserRequest)
+  })
+_sym_db.RegisterMessage(GetUserRequest)
+
+BatchGetUsersRequest = _reflection.GeneratedProtocolMessageType('BatchGetUsersRequest', (_message.Message,), {
+  'DESCRIPTOR' : _BATCHGETUSERSREQUEST,
+  '__module__' : 'api.v3.api_proto.users_pb2'
+  # @@protoc_insertion_point(class_scope:monorail.v3.BatchGetUsersRequest)
+  })
+_sym_db.RegisterMessage(BatchGetUsersRequest)
+
+BatchGetUsersResponse = _reflection.GeneratedProtocolMessageType('BatchGetUsersResponse', (_message.Message,), {
+  'DESCRIPTOR' : _BATCHGETUSERSRESPONSE,
+  '__module__' : 'api.v3.api_proto.users_pb2'
+  # @@protoc_insertion_point(class_scope:monorail.v3.BatchGetUsersResponse)
+  })
+_sym_db.RegisterMessage(BatchGetUsersResponse)
+
+UpdateUserRequest = _reflection.GeneratedProtocolMessageType('UpdateUserRequest', (_message.Message,), {
+  'DESCRIPTOR' : _UPDATEUSERREQUEST,
+  '__module__' : 'api.v3.api_proto.users_pb2'
+  # @@protoc_insertion_point(class_scope:monorail.v3.UpdateUserRequest)
+  })
+_sym_db.RegisterMessage(UpdateUserRequest)
+
+StarProjectRequest = _reflection.GeneratedProtocolMessageType('StarProjectRequest', (_message.Message,), {
+  'DESCRIPTOR' : _STARPROJECTREQUEST,
+  '__module__' : 'api.v3.api_proto.users_pb2'
+  # @@protoc_insertion_point(class_scope:monorail.v3.StarProjectRequest)
+  })
+_sym_db.RegisterMessage(StarProjectRequest)
+
+UnStarProjectRequest = _reflection.GeneratedProtocolMessageType('UnStarProjectRequest', (_message.Message,), {
+  'DESCRIPTOR' : _UNSTARPROJECTREQUEST,
+  '__module__' : 'api.v3.api_proto.users_pb2'
+  # @@protoc_insertion_point(class_scope:monorail.v3.UnStarProjectRequest)
+  })
+_sym_db.RegisterMessage(UnStarProjectRequest)
+
+ListProjectStarsRequest = _reflection.GeneratedProtocolMessageType('ListProjectStarsRequest', (_message.Message,), {
+  'DESCRIPTOR' : _LISTPROJECTSTARSREQUEST,
+  '__module__' : 'api.v3.api_proto.users_pb2'
+  # @@protoc_insertion_point(class_scope:monorail.v3.ListProjectStarsRequest)
+  })
+_sym_db.RegisterMessage(ListProjectStarsRequest)
+
+ListProjectStarsResponse = _reflection.GeneratedProtocolMessageType('ListProjectStarsResponse', (_message.Message,), {
+  'DESCRIPTOR' : _LISTPROJECTSTARSRESPONSE,
+  '__module__' : 'api.v3.api_proto.users_pb2'
+  # @@protoc_insertion_point(class_scope:monorail.v3.ListProjectStarsResponse)
+  })
+_sym_db.RegisterMessage(ListProjectStarsResponse)
+
+
+DESCRIPTOR._options = None
+_GETUSERREQUEST.fields_by_name['name']._options = None
+_BATCHGETUSERSREQUEST.fields_by_name['names']._options = None
+_UPDATEUSERREQUEST.fields_by_name['user']._options = None
+_UPDATEUSERREQUEST.fields_by_name['update_mask']._options = None
+_STARPROJECTREQUEST.fields_by_name['project']._options = None
+_UNSTARPROJECTREQUEST.fields_by_name['project']._options = None
+_LISTPROJECTSTARSREQUEST.fields_by_name['parent']._options = None
+
+_USERS = _descriptor.ServiceDescriptor(
+  name='Users',
+  full_name='monorail.v3.Users',
+  file=DESCRIPTOR,
+  index=0,
+  serialized_options=None,
+  create_key=_descriptor._internal_create_key,
+  serialized_start=883,
+  serialized_end=1361,
+  methods=[
+  _descriptor.MethodDescriptor(
+    name='GetUser',
+    full_name='monorail.v3.Users.GetUser',
+    index=0,
+    containing_service=None,
+    input_type=_GETUSERREQUEST,
+    output_type=api_dot_v3_dot_api__proto_dot_user__objects__pb2._USER,
+    serialized_options=None,
+    create_key=_descriptor._internal_create_key,
+  ),
+  _descriptor.MethodDescriptor(
+    name='BatchGetUsers',
+    full_name='monorail.v3.Users.BatchGetUsers',
+    index=1,
+    containing_service=None,
+    input_type=_BATCHGETUSERSREQUEST,
+    output_type=_BATCHGETUSERSRESPONSE,
+    serialized_options=None,
+    create_key=_descriptor._internal_create_key,
+  ),
+  _descriptor.MethodDescriptor(
+    name='UpdateUser',
+    full_name='monorail.v3.Users.UpdateUser',
+    index=2,
+    containing_service=None,
+    input_type=_UPDATEUSERREQUEST,
+    output_type=api_dot_v3_dot_api__proto_dot_user__objects__pb2._USER,
+    serialized_options=None,
+    create_key=_descriptor._internal_create_key,
+  ),
+  _descriptor.MethodDescriptor(
+    name='StarProject',
+    full_name='monorail.v3.Users.StarProject',
+    index=3,
+    containing_service=None,
+    input_type=_STARPROJECTREQUEST,
+    output_type=api_dot_v3_dot_api__proto_dot_user__objects__pb2._PROJECTSTAR,
+    serialized_options=None,
+    create_key=_descriptor._internal_create_key,
+  ),
+  _descriptor.MethodDescriptor(
+    name='UnStarProject',
+    full_name='monorail.v3.Users.UnStarProject',
+    index=4,
+    containing_service=None,
+    input_type=_UNSTARPROJECTREQUEST,
+    output_type=google_dot_protobuf_dot_empty__pb2._EMPTY,
+    serialized_options=None,
+    create_key=_descriptor._internal_create_key,
+  ),
+  _descriptor.MethodDescriptor(
+    name='ListProjectStars',
+    full_name='monorail.v3.Users.ListProjectStars',
+    index=5,
+    containing_service=None,
+    input_type=_LISTPROJECTSTARSREQUEST,
+    output_type=_LISTPROJECTSTARSRESPONSE,
+    serialized_options=None,
+    create_key=_descriptor._internal_create_key,
+  ),
+])
+_sym_db.RegisterServiceDescriptor(_USERS)
+
+DESCRIPTOR.services_by_name['Users'] = _USERS
+
+# @@protoc_insertion_point(module_scope)
diff --git a/api/v3/api_proto/users_prpc_pb2.py b/api/v3/api_proto/users_prpc_pb2.py
new file mode 100644
index 0000000..0d0012a
--- /dev/null
+++ b/api/v3/api_proto/users_prpc_pb2.py
@@ -0,0 +1,622 @@
+# Generated by the pRPC protocol buffer compiler plugin.  DO NOT EDIT!
+# source: api/v3/api_proto/users.proto
+
+import base64
+import zlib
+
+from google.protobuf import descriptor_pb2
+
+# Includes description of the api/v3/api_proto/users.proto and all of its transitive
+# dependencies. Includes source code info.
+FILE_DESCRIPTOR_SET = descriptor_pb2.FileDescriptorSet()
+FILE_DESCRIPTOR_SET.ParseFromString(zlib.decompress(base64.b64decode(
+    'eJzkvQt4ZMdVLurdDz32aKStnrE9bnsy2+3HSGOpNTN2Hp6JYzSSZixHI4mWxo4TYnmrtSW1p9'
+    'Xd9O4eWXYM5HBCHjzugSROzE1iSAIOgQAhCXDJFx4HOPeGAwHuhfCdL4GTCxgCgTzPF3JyyLl3'
+    '/atW1a7drXnYsTmHeyf5rN5r1161atWqVatWrVrlfuh294agUZm4cPsE/VlpNOut+kQ7CptRkX'
+    '/n9mzVa/VmUKkWL9yeP7RRr29UQxSdWK+E1bWV1XAzuFCpN1Xp/HVWgWYY1dvNciivbtq1mpX6'
+    '6sNhuSW15a+X7/lptb0+EW41Wjvy0u98qUjYCqLzqkThO9zBM2HrHOEthd/dDqNWruhmasFWeM'
+    'DxnZH+U/lvTu53c0RBsdxcbW8Uy/WtCZT+3GSqxOUK97j7TwWt8qagiTSeo24W7yNClL4MIlWQ'
+    'aLm6A1PUqNeiMHfYzTKDGdWe48NFi8NFpl29L7zTcYfPNdaCVmi3aNLN4DW3aLevT+WJiIuQV+'
+    'JPc9/h7mkzXubdgRRjyhcVe4uavcXTYO9ZKnEqjXa56hsACmfd3FIraC426+g9TdpL3d6Gggi/'
+    'D35z8lr36iQd8g0w6tKFBXf/udrzifDNjnvtXCVqyTugNl153O1pBM2w1roCoZCSuevd/kawEa'
+    '5ElUdD5le21AfAEj3nDrouv2zVz4e1A2kgLXHxZQAKr3fcA920iDDc5e4VmlcivBChOJDoVuvL'
+    '0kDDQpO71R2qhY+0Vqz6U1z/XoAXNQ3H/yLtZlkKcyfdXpHI3PWJSpIDJ98tWIWrcq9y9yZkOn'
+    'djotRuIydfuFQRxQXCPOm6sajnXpSsvHMM7E7cve4eS4RyhxJluoUrf1EeE645d29CIDsaupuw'
+    '5q/pGkAzUF6ELXC9TgHI3ZxAeBFZzd9ymVKaf6dyr/Y69eu9nzzs9nsZ7yrvJxzPcT/o9A3wU+'
+    '74U44/VW/sNCsbmy3/+NHjR/3lzdCf2mzWtyrtLX+y3dqs0wTgT1arPheKfFLmYfNCuFZ0fWK3'
+    'X1/3W5uVyFca3i/X10KfHjfqF8JmLVzzV3f8wD+1ND0etXaqoetXK+WQCKWPgpZfDmr+auiv19'
+    'u1Nb9SI2Doz81OzcwvzfjrFWKg6/b1pbweonU//erz+ujXYQD79pjf6b6rPJd+j/Jvx9tDv2/i'
+    '3ylvgH6P8++0t5d+38K/M94g/T7i/ozT10Mf76eHGc/J3+2zLPpoH1FJBJWr7bUw8gNq/VZIrF'
+    'iL/FoYrlGz1utNfyuoBRuV2ob6rOgef7V/5MiRhfm5B/ypybk5v9koR/79s8v3+A/RiG61oxP+'
+    'Y5Nzi/dMvm5pefLU3MzjD1FxVxXbrhD6dkuXfMgnbePX6i1id7C2Q3wYAKnUuv3U7r3uu5jyvh'
+    'RRf72X8o7mf9DxdR1cheuXwla7WYuYp00lREQ3SCVsfimoRGF0wvV9f35heeX0wrn5afQbSmNm'
+    'wG9Uz11TRLHZ+fsm52anVyZLZ86dnZlf9ivrXPohTHIPoXyldiGoVqi062nqiODrqfv2WpAUQY'
+    'a8gxYkTZARb8z9gG6U491EjTqbf8dFG4Ueqa/v0rbo4o1b94PaDjcuMty9dPNQnpsn/dHdQIdp'
+    '7fH2WZAUQa4maYwhaYLc4c26/1U3MOWNUQNfkv983EAi1C/NTE4/QMOKtRwReZneWr9Eby3OlM'
+    '7OLi3NLsyvTM/Mz86Y4ppd5htiZX2buNeq+2paN1gvyhfgqDQxCmAXKG5uVaIIY4HGhQXt5hh6'
+    'd4w45lkQsCNHYzaGpAlS9I67v6s5liYGpry78x/dlWOsA4lfG5ULYc2XeZHHqNXk+mVZGYuSRn'
+    'HFo0DV/JB895D+sLP10E93UOuHLUiKIPtIM8UQNPaod9L933XrM97LeUD88u7yUov+tbQ/Q+1/'
+    'eWK8ZKj9L0+Mlwy1/+U8Xn5Ttz/rTVP778v/wq7tx2RoVELAons4QsFmM27KJRVDsvHPTv/plr'
+    'N5eNGGZ6nh09TwayxIiiAHvAkLkibICa/kniUmXOXdS/PSAs9LyzF9NBFFEVlz3LtK44kRRVTO'
+    'k6nnBxfISAhWqzSQg40T/nHMHRlWxffS3HGNewc/YeqYI6bOexP5AlcARaeVKrOANIJUyg2Rr6'
+    'gz8F2fBXEI0s9KPmNU+pznkZTnDKSPIDmqrejui2F9T/V5Z4kr0+7VFvBdfUTbPIHH3CWWmGVi'
+    'xAPEiJnLMCJhUl6aHQ6j7fNuUFUwO+6jZr2K2DG5Kzsimx/+JNVfJyKOHT1KdsAOrBgjQMItRk'
+    'pDF2htSJYge1gDaIhDkJx3rQVJEyTvXc/8c4R/93k3EHmKf07Mv/sN/xybf69i/i3z+HqQ+BcQ'
+    '/04L/5St+O0xEHr8QWLgQfcoP4GBD1FL83mfaxGWwcrbDpvd3EkJdx4y3FGQHoLs8YYsiEMQz7'
+    'vagqQJcsC7zl1kXbFOrdui1p26jHTEi4eLtOx21TLo6HVq2XXu/fyElm0SlQ97Z0QCRcOI5cPT'
+    'HcY9gXkarayRJqis7yRGEgmIml41A9LSXKDusSAOQXq9PRYkTZBBYknOQPoI4hFBp7nf03a/V7'
+    'jf91lgkpGHWUZuF6DjVanO+/M3cWOqpD0h5zJrX4xUUsX82fUWBIhuIBs8hqQJcsS7zYL0EWTM'
+    'u88Q6mhCqzTBL7vfSeCM16Qu3KYunLpMF1prrUtLJ7RGk/ow776Cn9CHLaL+Ag3vMT0K1IqFx7'
+    'meKgU5uID5Q9qfkXHbMnovI13VIr3nWZA0QfbRWiVnIH0EuZrqVeM2E/dJm/rkDLMlY/ffBTNu'
+    's97riC2vd65A8SVWoZfmDCai17Him+QncOZ7qF3f5xBrilfGmnbNYk5WmPM9hjlZYc73GOZkhT'
+    'nfw8zZZyB9BLkaVRdtILjzvcydaywgc4eKgj2vIniP9wMO8eft4M89l+FP53L5Iiy6w3X3AjPR'
+    'Trj7vEPuLD+CR29yqH1vAZNefAkm8WiHH5QsYWWUYXjxQBrWqIhXjKzPAjkA9dMYj0FpgHJkKe'
+    '03oD6A9oOKog0Fv97s8BC/xoIyw96iGPaUIy8c70dQ9zX5H3a4GVvBI5UtWunX2lurRDrpgUor'
+    '3JK5DmstJRJ6SYypTsH99XAbmm0zqLnKA0AmT5tW7P7sOgSkEZYrpFLWxvwgniqPCvbtChlrPG'
+    'UCVYI9DrGHSey1QEx1H82ZMSgN0H6aFv5ZNy3lvQ3fHcj/veNP+vCD+ewHG6NqyiFZabReada3'
+    'yEJsNMMLlXqbNHinYDzkl8mOpEYQ8ALpcdUyxYxmJbygFkdRezWCrNVaXA0sy/s3YXjDHRC0qP'
+    'PHlDlKhZsEbZKItDAdNhRWniV2qXurHbVc4jFNwlwPaFFTaPwhgeOmWXyDkfy2pFiht98Gsdpn'
+    'gdIAXUPGxgME6vV+3BGvUH72csbBFQ6i29Ug6qXKfxyd5rvH+BGD6F0gcJQshOmgFTDqMKC2dp'
+    'jq0qpesRD4IxvUA9AesmRjkAPQtd7NFigN0GFvxH2rIzDHewqoDuZfDwER2djerBAF4oiK0KVB'
+    'BGtee1Ef6up9+FOl3yHrLCHGBqhvVVotSD26PhQ/Q6fARFYLIe9Pxf3WK/L+FPrtgAVKA3S9d8'
+    'NqD7v0bne/L+9ebhMm54ofkgp075mshVG5WWm0dOkjj7l72dF/SpDkXuTmT8/OzE2vnJq5Z/K+'
+    '2YXSyrn5pcWZqVmCTntX5QbcvoXF5dmF+ck5z8FTaeY7z82W6F0qN+TuWTi3vHhueQUOMS+dG3'
+    'Td2XnznMntdftnz549x54wL3viIXcw2YTcwd23IRYarQpJ6IF39fnpkcHj1xXjNhYT5Jf2rtuP'
+    'pxruYLm+ZRU/lUuUX0Q1i86rJ6XERr0a1DaK9ebGxEZYUxtV6hV9GzHTgxqt9QKm56T1+72pzJ'
+    'nJxdl7//Qat88b8rCYcdxfz/QN8EPu+EcyCa/rsTv9M4zXn5ubgjKZUz5SWonW1kI1sUw2aKCE'
+    '+s2Yfx+NSKrKP1486o+gQEFeFUZPuv5Ovc2qGivRdhRqGaUawkfKYaMFXyvxolGtBDXS6nA+Kt'
+    '+rwkGC/YBgqK+2AiocUPHGjl4OSTHS7C5Wxr6/2Wo1TkxMbG9vE19BKHNNXL3RhLh0x4lYF+6K'
+    'KumW2IkED3GDSCmzDqkG2/AhBRvNUKzpmr/drCilGtXXW9s0plx/jZRRs7LabiW4pAmj1toFiE'
+    '80vguTS/7sUsE/Nbk0uzTmsmeWRNS/f7JUmpxfnp1Z8hdK/tTC/PQsZJqeTvuT8w/4r5ydnx7z'
+    'wwor8/ARmjyIeiKxAv6xH3wpDBPVa2tAJsGyD0FqQ5sqtzisg0bYZJdZHd7M2hoc46Q6lPx0t4'
+    'ikgj3hwyQ/+8TnvY992PCKX02/XiVecfUb0GvoV4GhrvwG9Fr6dTtD9W/8OmD86o78BvQ6g+Fm'
+    '87uXlNBV3igJ9GfSpJqu8kZId53IfypNKpU0SmWjxm1QrhjDAKUf9dD2R3Tnj/n1dqvRpgVIrb'
+    'pDTG6Vi6MuulyPeT0Psftm5pGAeA4HDsQO/UuMZPvrLv+Y/5oRSxMkdckoFdC66bUn8THNYK2Q'
+    'nUlX9LGlytT3napput1U7W61qkCoxsVlsMb6cHekyxVqfYvaDLEjdq20KqqtV4TdonlMfeBfhh'
+    'ytjokaWiv0irO+17tJnjLc2fpdDz3tMe8cerrZOy5PaXp6sXen+w/wal3lHVM6MP/nKX+yRt27'
+    'RoOd5h2tT4xcsNAoh5qSmBG2EVS3jylTKFCvXAxyo0NIYsT6j8YwNhUOmslFvEgP+CSadZQNsF'
+    'NBwFEauctQjEeOrNVDdtsdOeKXyaDdCJNkaXks18lqba+vw5SrtKKwun6S/rLskkYKCUEYJb+k'
+    'kc1qNKCPaO20Wd/2aWIgtVavQnaprrWqfMOtgpTPE5oTirKQzHIijzAQZWLFkv2xrWztSG+Zrb'
+    'fJjA55kyjLXXHMy3o59xQ/we66nbrtxvxxmnVqF+CbqNeCKhG8HrSryk2MiqKiP11PTBm8wssa'
+    'Jx+w3GBBUgQ5RPbd+x0BOd7LqMhQ/q2OvyQjnyzYHcMa6Trul4aiogjTGRMTTF1xQezGbhhS+p'
+    'sxNZ+J282sNFzVkq3GZhBhN2od00qz3mhWaJBbTXGEThuSIsheb9D9bd2UlHcXN+WXHH+6m3ot'
+    'd1qCRKJDcXiZ7kTfkU1PnUeEGhueENCaoNWxgUVjFNKyTsY0dSZmvjUyHOtqrVQO0Cc0iYXNJv'
+    'RnO2ozZx/q9Eo/NGq1FD1yV6KlqmFo6felBJT2pqhILv+lXVtqqefLNrZiLXB4QKqlBI1IHn68'
+    'mwqppy9c/QlMC71u16NwE4XYqOGd5qavmCgDGCOOJ0yG8qgljE0aWYY9QcRYI0i3MJmrOwwCQz'
+    'Lyy6HNJjj8wIO9FiRFEDiwf1ILRMa7h4oM5//drmxihfIcuaTVFnqfWVOuNxXvmFvmK4xM2ZLm'
+    'L1XXWO2A0wtEDliQFEGGPM/9ad2OrHeWinj5t+/ejq2tdgt22GWboUdfiGbzZrnVkyS85WaIuT'
+    'VwjadEiYE2S+lbpWrX1FIpWG+FTasx8FOB0j0WJEWQQW/ILIHe8ZNp97LLmtxQx7RaOOsOnyaV'
+    'M20KLoWt3MvcDAxkibq5eZe1h/0FrxVK/EXhrzPuvl3e5nJ2wJkKKssdcHvJPj5PBo1E5+hHWm'
+    'u5a2EjJMOvVt45kEZsWcmC5G5zhxvtVbKSV6xiLhXLljz1YjoufNgd2g6D83bRPVx0EGCr4JQ7'
+    'IAbWSmunER7IcOv9rtZ3tnyPfLVMH+Um3X7MHwpD9iL8m6ESnVj68Jmg6BUX04EeRnC4C8GSet'
+    '+JQ39HTemnhTkZyzRHHOhlJLfsvoLsRBF/l3uJ21uX9WUfx8LdsKsgyBq0pAvnZl1PCfkKAmBW'
+    'KrX1+oF+RnCouyFccIrKzVKx0mCUeM5d4/ZEO7VW8MiBAZYQeSr8bz3u0JWI2Ek3yyOUBOxZ8E'
+    'B9k2Riz3Nk4qS7p8ZbPUoi0lcoU676qFukMs9JpF7lDhmSVprQNCKbE5ejpDijvyvhs9JgmHjO'
+    'TbtuvRbW12l4lasH+i7CpQUU6eJSXUHL1dydsaj1XkRSzqpB1iVt59xBHYUlLetnIoqXbVlJPl'
+    'MN29u0H3M3uQawwmLlshYa0MB5guUfdQeT7Mntd7Nw4KkgymxJPeQ8N01KRiIk8TP3HXGD09zg'
+    'W7t7NIG5s935l7p7Ew240qoLr3Ov3hU1Ccn+Ni3HaeohwwASq6o68Le9F5G5c3ZphaW0r90NPN'
+    'Lf9/le7/voX6rwWz3u/t3GzK7Dl4a/8sUzk7IleaIRka0Gq2GVRoMzMnj8tisalcU5fFJSX+Ze'
+    '4WZERQPDkSvDgLFU4u8Q+oq/SjZ6mOY+ACAXubzbx8NkLdRTm3mGYMliY4UXLizwJFgCvA+w3C'
+    'F3jxpVZHKEj7D2zJbUQJsFBNU/HNFYFtHkKgDg6l/aqbgv7T2MxxJNlcqaWNHLiwPDhKCvNKjA'
+    'CwIt/HLKzbBiGXL3LD+wOLMyvXAOrksHnk0GnJ5bmFz2UuZ5dn75JXd4afPBOQXI2AVuP+5lSW'
+    'AHFILZV81MU4meJITK9MJdypBTCwtzXp/BubRcmp0/4/UbnGdKC+cWPddgODuztDR5ZsbbY0qc'
+    'emB5ZskbSJBFVew1VczMnyM7Kzfs7lVVaCKGOkBEqRcTorAMJwBUIleYcrMshiTug3OTp2bmVi'
+    'ynsYFZrmMLtjgzuUywdKHs7t9Noe46hCxZSF1EFhhXpywU/irl7ttlUtm1krvdrJJlNc2O7jo7'
+    'sWR3TbX8nW1qpC9iagBFl8C+tkv5q/nxJVcyPzLs2U0C2V0mgZPucBeiK1bG3++4By7GnMuoxF'
+    'RCJZ7s5OCNF++Err5+2nGv2d2k3JWGV7g9KgJY+rt77jrLrzs7W76yZ/v0xexCRU0XpW9OuVfv'
+    'inxXQg+6Li9GlemkNHE/Q1h5QcvyulHbZnjvKhAXeFlMaIYJfdFFWtolmEddr1ythDWcX6D13B'
+    'YtXnmq6TuRXQ+qUVgaUq+X9Ft8oVb41hc9iS/Ua/NF4Uf73T2WAZ670R14OLgQrOhFleLEHsAW'
+    'ZWF11N3PRaiNVFG5GkQRM62Pi+bwbgGvpvSb3IvdffzFFs1NlUY1XMEyL+Ipx1A2jBJnpQAois'
+    'gsPMifbYS1sIkjNbQYprIrtK5f2QyizQP7geBU6oBTug4Fz0i5GS42WVu7hwrlTrjXMBbl3V4p'
+    'b4bl8yvt1vrLDlxv188ULnGZKRQ5RyVyS+4AOmOr8ijRXG/yHDq4i2qyOFhckA/O0vrjRHZpcW'
+    'ZmurRHYzmNbTjX3agbBu9RArVR1+wlZpXLqs20NpXFWHTASzCrXD6jCoiMRzQero6ZZX843NXK'
+    'zk+pxsZO94e5RI2Nnc7PXurub2w2ur87Yn+XoyKdH97CK/NmCDfI2oFr7eLWi1yRxL+8EtbgPV'
+    'lBMGgQHTjEhTOtZptWEeXyDL+c5He5I+5wffXhspLIFUKzXnnkwM3M3iG8YHlcZHBulHBHm0Gz'
+    'wSo5os4ID9yiiir4vAZjRETblfWWxnhYjQiGCbYR1wMnEhWPcLFBgtv10mSAknGlo8pwI2Bc4x'
+    '3uNShEii5YC1qBVXqMS4PtZ+Vlgs5me3XHCNa4ohMwLVovmHFeOOEO2HKf63eV5JNBQkbQ1MI0'
+    'zJdXz5AtQmbU3OzyzErp3Pzy7NkZL20Z9vdm+m71Dhf+OOUOJldquZe712q3ShS2Vraxd0MDci'
+    'tQk6ORn/1Sails3U9lTnOR3Jx7qFbHIbDaWtBcW4kdWitBmQQyqquJ0GC5oVZfksLxDDEpRTvE'
+    'N30x8SXreitokPy2mjtsn/eV+ggwg+d/kWUScbPP66f/9ntu4S/T7oBtr2P5U+YZy2GddtMlrf'
+    'viFKayEz3KOC6pL2FGQNhCZYz0leQpd8bteThi3D2Mezffn4X73iVG3n/v0sr8Quns5FxJPs9d'
+    '52aqwaM7yUmPQVfaCYQBDrrkVMOgF3AwTLhZ5lfOdYVj3lW5PjcztVDCgKARoKAri7MzUzQmCi'
+    '92exQTMFgMG+gj9Sg4HP323NlTMyUvlezqjJctRDQKLTv8X2Yx/puOu8eyq2EQ8QmYlaBaCSIR'
+    'DZdBk4Bcadf9Cw2RrNdTeI/jep2GbQeZzv9IMgvvctzBpDXbQd6N/0PJ+1zK3ZuwYa+Uuu92hy'
+    'tr4Vaj3oLzfKUaXgirBwqsNLqdiokairPxd3P47MS+2emZs4sLyzPzUw+snJt/5fzC/fMlr9JR'
+    '7AUc9ouu10lU7lp3N7JoZO9zh+YXaE6kiXHm9OmZqeUl5fcwpZcTA7zw42l33y6U4Mg6G99qET'
+    'V+JdQXYTMs0lJSFjhkC0mAfYXsebUGV8uYoRiuXEpjbq5RjyqtygW45LXzCcuaTMnTb2ZrLVO6'
+    'Fm4EHaWhzNMlT78xpcl+Wau3Yeupcpg7nNIeBTNFxIqPvV4DZIoxTBU57A4FGxtNINeI1Lpk0I'
+    'C5YP5et0/zAVM1OLHSUIvtFBxhNf2SKq1EK7ETP0Xv+0p7KpFxgBaeJoMluQlBa5e+ar3MESyy'
+    'AzZymX2L4pyUL5kv859y3D4Npuk20wham4wueyrlOSV+BpwswBqLgMDxjH6thsEaL3rqW1vUk5'
+    'HuV4FPCRh7YS2chE6UzXBZT78whU+412m8a2SF0oJqLf6oh50b10qBaXmvvy38seMO62XammHW'
+    'WdeNg/2EXd2i3PVdcdJ8VLIQ5LdcN35zUbbRPCU7TLxNqRb2rgJhPQf3y2q4UamJ31g9aPdLxr'
+    'hfTv0vDq3Y4jhITe8pr8O7EN3jvPoVG5XWZnuVEyGocMh4n1WFaIzTemp8o27tup6Mf37Dcd6b'
+    'Sp9ZPPXBVF5FOBYXNXtK4Xo1LKPJ9/7RMym33zvsXeX9UC+Opw/1DfBT7vhvDfiLOhLklESCjE'
+    'uw5OHIx4rCZ4UhUTvKxnYT0ZVHX6ajK2dr5aJ/kaPsiGCMTkxQO0gP1huIdxYGoe06HGVcwlEm'
+    'cJYwNCGGFQ4w5IOD2AeXgwGArFZqQXOH6YrGVJxlvakPe7v+Vn2Ng2SAYYxjXDgssCVHMHXIt9'
+    '54r8MqQUxAGbEBKlSQoyFp7XVCQjGPdBAWIQLCPpzPUROkXwMJHgpW6xdCDu9krrjYna+UQwmy'
+    '0aeD7BpVnIJFDtVHi8nKlhwZ3o0IqszihSaC2rjWLocxHW5MyLdFh6tDo9bq5TbGcqA7aQJhZh'
+    'zKSZJC63ya5GNW60BYK74UM5Bu1LwEgbas44G2bNXq8btInaWI0KKaQlVvmqAlfVSMBiRBQwgF'
+    'EbFFc7GveELSuUbU6UMLrk6yoOJfTboAE2DZaCLcDIGyLRxEMMGlCChbvmd2yV9aOL18/2Rpxq'
+    'ffi6WF+2jmnvZPPUAvZ/yphcUHSrNn7ln271mYm54pLfmT89OIhCVL/tS55YXSkmuiZ/EGUbEz'
+    'r1oszSxxyOzs2cU5nDCPA2nH/Nn5qblz07QOGPMJA07buv7c7FlaSk/7ywtjXG33dwi5PTtTmr'
+    'qHHidPzdLK+wGu8PTs8jwqO71Qcv1Jf3GytDw7dW5usuQvnistLizN+GjZ9OzS1NwkrdKni1Q/'
+    '1enP3IeDukv3IBtDoqGuT3bNTEkCfk0z/VMzRCVCIFEVt3N6tkTmDhoU/5oi5hGBc2Ouz+Hw9I'
+    'v4QZYQUfTAmCBdmvnOc1SKXvrTk2cnz1DrRi7HFeqYqXOlGT5eTKxYOndqaXl2+dzyjH9mYWGa'
+    'mb00U7pvdmpm6aQ/t7DEDDu3NEOETE8uT3LVhIPYRe/p96lzS7PMuNn55ZlS6Rzvu4xSL99PnC'
+    'EqJ+nbaebwwjxaC1mZWSg9ALTgA/fAmH//PTMEL4GpzK1JsAHLvKlluxhVSEykJsXt9OdnzszN'
+    'niFrcgavF4Dm/tmlmVHqsNklFJjlikkGHkDAKipGRxFdrvptie4Y96c/e9qfnL5vFpRLaZKApV'
+    'kRF2bb1D3C86J7/DMpSWNywj9PiqBe+45Ysfsjr2SQf1/QXAtGaZyfCiIVMF4nJVRBmGTXBKSi'
+    'nP3VHSq+FNQephF9ZjPcCraD1ph/b7i+7k+HQU3Fc7Gm4dhlPmwlscxKOemQfDVfriotuBauV2'
+    'qi4ExaFDVJc2mcuplUp8ttsD7AQtZPLarC6iANRsuWVnUHaibwdwlQco0WQZ4LpRMRuIIpFMpy'
+    'JCxuFE2ZpjKRoNIQ+V5vtqJRSc8ySnP1AQ4Yv41+nZNAdPUb0DH6NSbB5eo3oOP065gEoqvf+F'
+    'WkXy9l6C3yG9AJ+nWjBKKr34AepV+HGHpIfgN6B/26wf0+nDPtVw/5lt8ZFKYmoFUVUwo3oMro'
+    'QuoUBxJCFW7ZNCbK+CrEwvWD6gbJRWtzi6SgXjvc8rfrzfP+WpsD0Vfr9RZNGkGjQU/Emiof4n'
+    '8ZUXDCc/IPsgSYuFacvqAuaXLHSZBlZy8thS2ePGimlrBJ6XJXiQIiIWkNEHFwvD7j/zI+uTxo'
+    'zvjf6aW8EetkfoYh9nn+HoLs8V6UOM9/p3eI83HE5/nv9G71DvNRLsSUXuW9mtp0kz8tshvxCR'
+    'FEabdCWy6L8Wn7u4iw692Xm9P2r8Bh4sKYEl/MmDinV+VFFh+5Im5apkurGYbJY/Wv6DpW/4qu'
+    'Y/Wv8HKccSE+Vv8K7zov744LxPHuJiwvKhz0WdYL6/U6UYQ/xdWgWVAHEuJKHKr27kS1OL11d6'
+    'Jah5HmqA9iSJogN3gH3ZcKJOWd4oDsw/68thSkQ3lgqTMxRkFYBCAM9VSCAAR0nkoQgFadIgJu'
+    'sCBpgiBSe1kgac6nMZKf9jm0QpHARws5kjCmQ8gSW8rEDSpzjC02izokVppOUJfmzB175BSwI7'
+    'G2096wV7AgoOYWEq7HBZLxzhCWW/NbndTBEXpltJGWPE3jWRln47xEgC7fqmzIMQ0O1bXi3a1m'
+    'ZIQAG5IliN0MjIIz1AzfgqQJcpN3i3unQLLevYRlLD/KS45WvTHODqOEircnAouELJFwb4KELA'
+    '3Te4mEvAVBQo/r5fi9gqQJguP3gwLp8V5JWMZNiR7C+8oE3h4uo4e/gjgEOSRKQ0HSBLmN9LbG'
+    '28spQIqmRC/hnUvg7SW8c4T3RguCNCEFTvSiIUgTMkb0abx9HPIb4+0jvGcTePsI71nCe8iCIF'
+    'DYt/D2Ed6zCbz93gJhucmU6Ce8Cwm8/YR3gQ+VxhCHINdanOknvAvejSS7/+QIyPXOEZqJ/N84'
+    'KlxaxUaL0o5POSTmVRo2bbWgMzaGtT6zzt5FwXpYxXnrrfoFOcMH/1ZTh2DrqXkzaGJz3G+2az'
+    'ggRLNDu1ZWFVda5tBePAXSGnqcQTZVFZPaDEODrRtZC2P5xCdWIktCXeLguQQHXeLgOeLgdRbE'
+    'IUjeO2JB0gQZpx5+SCB7vAeggfOL6pg5h7oaZWRNI+p1uyFjX86/YClU4GLHC2xvqYfbCxale4'
+    'jSBxKU7qGh+UBCZ+4hSh8gnXnAgqQJcj0p7VGOon8tTXmP05R3fWLK04eksf1TjPOivJamumtZ'
+    '+FRelAdN3gyd9eTBjqwnWYJognTWkwfN5KWznjzIk5fG63DulCOmhNOVTcWRbCqHLAi+8jnblI'
+    'Ygm8oIDR+NN+UFZhCmZNIJEnihWIIEXlATmEGYkkknMINQPZcJy22mBKaLcgJvmvCWjZJLyXRR'
+    'pm641YIAzyi1W+PNeGtGyaVEf68l8CJZxJpRcinR32tGyaVEf6+xklMQnLBcJyznvXQM4cwwLo'
+    'mJbyDo3w0v4x0sDMAJUG1HFZ4U99sliCKUGeiAZgm618t1QB2C7qM6ktA0Qa+nSd2uGaliMmRE'
+    'Dfgzj+xeM2Ris6tmWC2bXTU7jG8fyVwSivQzkLucBUU2nIw3kSiJnni4qy7IysNUV6ED6hD0Ju'
+    'rFJDRNUKgH3bdZzjkTyyImxmqib7Ocl0arHQVBXpq8JYtZzktjy2KPV4PeNiUwMdYSeHu4jC3j'
+    'mBhrJONHLEiaIDa9vV4DpospgYmxkcCLibGRoBcTY4PovdGCpAlyM43Sn3eEPY7XJjSPeOn8/+'
+    'r4HG8HLakdmMi8ICk8oqJf2gVqn7th5xMUvJy34yOWsjngc0ihMasieLlEy1mIpRDhZHehj1PN'
+    '1aBhUjulWZjaNFSuNQKrrP4Llxgq2rK/kBAibd1fSAistvAvJIaKtvIvJIaKsvS3LzFUtHG/3V'
+    'Uzhsp2V80O47OHijb0txMqus/bgQloOhb2zE5CHGDP7JA4HLAgDkGuE7tOQdIEgV33BkdA/d7r'
+    'CM0t+XbcJ8omYNejzmrR3efWEcuu7oVjQZ2bZd8mn/5nUyCM5QxHvawEXrCjXpdoTz+nLbKnsn'
+    '5OOJSzBhLsqNeRJXgzJwpJe99Lc+sPIDGtzr71vZxXrNSns2+93uEz5iovG694aalOhkyoLSxe'
+    'CuP4INbkzbBc36jRmt7HubIin87XS5VhjZMIf32cV0Tn4nq9w7NEDHIAOuTdboHSAL3Eu9P9Lg'
+    'ZlvTcAz3X5s/4UhzdGvKRnI58sujbRuRVTWYtHWmRS55rxZVM6pLBT6wn/XkkFkpbzvQwatkAp'
+    'gK6mUi/m6e/NyOryRWR1uSVhr8R2Ix/XMz3PHYE58c0Op4ZS6asyKn9QyvsZhybBYQ2jYgRFir'
+    'JXGRB66YcdGj3781P+UXU0W8sl1AuOZcJ1tdBcU7kVt8NKU70jDlBX4qQwfEwRNstdSYsV+yMY'
+    '91AHOAUwMiTts8CcKihDQLuso8GDHeAUwMM0ul9ngVPejzKK/AbOYvuvrmy8mhQprS3JPF8r+v'
+    '687AUb3doKzof+saM0vloh6V/Oi2zFwPuVdVKT+iPLdK1Wzoc4MZogCk340W5ahSzQajc37f0Y'
+    'sz1RFmPox7o5hlOtP6Y4Zjc3gxxaz1tzbz/+7JoLqXt7d3Nhc7+9u7lZ7wmUvTpRFhM+g70OcA'
+    'pgnXFMg3u8d3SjwNz+jm4UPYTiHd0oer13omwuURbTOIP3doBTAOMssY2iz3uyu9+g+p/s7jf4'
+    'WJ9U/fa3jgXv996jhtwf0+oz2BhfCzmHCE6Y6+ABGnJnmvV2g1conBbFRLPwegmzQ7yq0gfvby'
+    '/699S3afXXHFPu79tdznYSmp20yI9o6Um6JGpJKi+VAKqu1LNKjMoVb/N6lZeZ6tg2Yrlb8lJn'
+    'CkbGk/O1+rakfejUAJhC3tMtI/3El/dARva5xy2wi0xJGe+awkF/LqxttDZ3Z0wClatSLnX2v0'
+    's1PIX+v9odscB7vPcqxu+jwbENtl0w6XiSeLGwfG835XsI73sV5bZQDHjv6xbNAULxvm6hGCAU'
+    '74NQJEVzr/f+bvW3l1C8v1s09xKK90M0k2Ns0PsplL02UXaQUDB4uAOcAng/WUI2iiHvp7tRDB'
+    'GKn+5GMUQoflqhGLPAnvc086JwLfRLlFBLyr9uI/EI99PdTPII99OKSTbuYe8DzwL3MOH+QDfu'
+    'YcL9AYVbT5eO97OYLn/Rni4dBe0lu3LCgDBdfog5lM9fdLqMqdDG7oeSWseRWfBDmPrjDlDm7s'
+    '8nO0BbrT/fjQKz4M93o0h5v9CNAph/oRuFlAaKIQaigb/ksNtjWANoovml2OTSOTV/yWFrMQY5'
+    'AOUku5vOqkkg2NUaueN9xOFkJLoMjPePJJHDcv+Iw17bGMQfDlOjYlAaIOTa1chT3keTlGOK/G'
+    'gSOaaRjyaRg6qPAvk1FigNECh/yhFY2vtVZS/+sMPZ4/SBSghBFLYk3AGuOG26E5QmWSq7Wufo'
+    'hIqEPugvXZ5c42/N1laN14jmSN+Ybx8IxKovPjBYjJsGt8yvJlsLv8yvOpyWNwY5AHkk/TGI23'
+    'YtWaG/lhJYxvsNoPLzH0ixQ147zNAATm6DVkRCuFw/YCdeZsear9LsqTcuZwtsqRsTyIY9XDw8'
+    'Busfztd2tbozjtM0nA+GvlvApuZ2BQnTpm67bRwGiB+V69igc/1muyqGiY7GIJN9zVTrj1SKVP'
+    'd6pRm1JO8fZwdnirUNDbrduFXcD0ETMWScAbAWl+OFC02fY9hcxoRcV8lf6nWE3OhjEaNWR2Q0'
+    '92xQFiB7wEDr/AYGzPUWKA3Qi2jJ9Xotdlnvt4HqUL7B/RCvQi7Ne5IjeJblJC9YOgtWq6Qe1Q'
+    'vhWnIlGdRqIadfMcJptQe+m99Otier6LLbA2Put9GevAVKA3SQVmYf1YLV4/0eUN2cf68SLJIl'
+    'HNrR8mQ88Ak3ewuZGxF1hBwwLZOnDGV08lLgWq3Xq2EA1hRwcqeAoVLgaOCClFAhnJ316IRKXA'
+    '3e8GpwBMOY1mBBQ3ELm+3bwc6orgxGdAeiKVNekaWC1rik/4q7/GPHX8aiJoXgHF+YXhhR0Q2j'
+    'J1QQwzitO5QNf3fMb7i5fi/ZBT3UBb+X7AIYw7/n8JI9BqUBKng3uW/UItXrfcrhbcwLGJ+sf+'
+    'A+iGRrYS18ROXz4iPcWk7s/WzqqsORH6dScNXWnW9vpVSUm10np+VYOP7CEiw42T6VbFUvtepT'
+    'Sf0M+/xT0M83WKA0QNgZ/ZZuVZ/3J6pVX3D8e5cW5q0hoYlSKXG5a0Rrw7PStbVfFLXl6qzAqm'
+    'jgF8wx9oJ4BqATYvyS6Ey9OiyJdV2TNBfaqmirtkoLsZAhIukkxa2d8YjpJ/rKyP3VbLHqq6gU'
+    'PfSmOhVE9iCFh+pPkrzsI17+SVJCsFD5k6TSgZPqT6B0fDOR9nuf5jFqysBd9Okkcuy7fRrIr7'
+    'VADkAHLPGDx+jTSvx+uV9grvePDvvA3t/PjKaRGuuyQBYyfkFvwRWKKkeveROnbDVpxSotLE+C'
+    '8vk4T5SP5UJzjTNW6iTpertHkv4mswewJo1pkWRJMiWvhtU6JL9uhgWCOThpInaI/Xp1TZNXFm'
+    'eSpP8Vahg5H1uSJJO2TEIvy2CLfLWc4kxYBWyrbYatSrmg3utcU130IbiH9DpHlPKQG+H8vEKS'
+    'aaL6aCNscRo8HxWZKlQNo0V/SUOEqIhmEUQNmA17vR0puR9B0praj9cnaFlXTi7O7obMWDnwKG'
+    'Flh/xRnEWuQAvMqrSUQ69sw0OtL8e6ek07D3XaKqo4Cmk+Q4tUcPEYOgp9UKvXxmkSCXkRncRL'
+    '9ZNqlz4yvWZW0xh3vGB2E3m1wrgqRKhX+B6TbWqujjVl/bHdRHRoLM+YlYhZOlGXBIBRUTKDWD'
+    'iaCB8bp5mKA5Ks8CG1pcpsIaUQIUE54ia2qduhwLva1Nm51MqoPuaHcBVjB3pjUwwwdV+VSo3n'
+    'soXTwYdlGZ4hx61yBjsVzkGtN2FXrdjJS406jDEZUrcin19yUlBUcpZFNiaRHxaUsGhIT8RpyD'
+    'oaAcFg38bxDrmOtBRCWasxUg02xmzydgg738tlutGNkbBd+lAy0cVDlobFzvk/JpWgSxr2H5P2'
+    'NRwS/wj7+pAFSgMEv/nNbAl+BV7e/wIv7/6El1e3D05dmFNfUU7doT6d7/+r8fomKyuzr8YU6Y'
+    'T+X411vs7o/9V4ZaZT+n81XplleQ30tVjnZ2Vl9rUkcmxLfy3W+VlZmX0t1vlZWZl9Tel8bMH3'
+    'eP+E5v5oipp7nd3cWryQKMb5+//J4XCzoT6dv/8bcZt7pM3fiMnSifm/EbdZJ+b/RtxmnZj/G3'
+    'GbVU79bzq8Sxlnp88okJ3Wvgcg5Eu109oT6GZvNJHW/psO71Rq5Cnvnx2OXYlTuGcUyM793gPQ'
+    'HotMUPXPDoev2LnfCYT4lY/wnjjvcf/bFKF6Y8pL59+T2mVjUdvVyo1rbQGKX3e3bUWcIa507C'
+    'Gij3bdQOzYP+Tsp/rIg1IWCMUnxdtSps0lM1qpKulbzOScvVD2+vyozTn1+RXrSis3KMfYyKKA'
+    'KZYom0qtdftxl9TBFlmsJtW+CgMgtrkkTjcaEGTsDalddjevtotQ33GhgQ5wFuC94lWJwQ7A2O'
+    'BMgtMAY4fTrt7xfiAlW5wXqx5y+QPd1cNT8gPd1TsKJXY5k+A0wBgAH9JXL6S9H4IYHd19c/qi'
+    'MpR80SlLLqxWHtvJbLdGnnwsGgIV0HdR2XIt4dIjAS4OJtgG9QC0R1YHPeLiINBB7zYLxC0teh'
+    'NuXUAZ79+leCH6YExBTPRF92OboTLjd91ydXfbc9U0ZHSVNigLkK254BYgkF7D9YhbgEBQqLgJ'
+    'qNd7W4oU6pOprkBeRbneJVT6tRjf6kCf9YmVr251eHvKqFZ9Z8PbYwJ7RcDfHhOo72x4e8qoVn'
+    '1nw9tTRrWqKxueSBlHX6+I8BNJ5BDfJ1JmradvUHgiZRx9+gYFAmlHXy+r1nemODpal0FvvjOJ'
+    'HKr1nUB+vQVyALpBdHKvqFYCIUL61j7kMXgXWPuTYO01CdZK1hHhJpZR7wI3X8Q09TE33x1zs0'
+    '+4+e6Ypj7h5rtjbvYJN98dc7NPuPnumJt9TPd7UhyPoMuAm+9JIsdE9R4g9y0Qf3gjtS4GpQFC'
+    'TIJGnvJ+IsWxL7oMuPkTSeTg5k+kOPolBjkAIfwlBqUBQvwL9nX6vfeDm78EbhY6wu9wxFpSzS'
+    'c4izXk+1McRjDEj+DsT8Wc7RfO/lRMX79w9qdizvYLZ38q5my/cPanFGd/3BGY4/1Mit0Gb3JI'
+    '/XI0P45/qMB+9qSxJmDnRhgpV+GuPrRtZAunhdyuyfiMW01y6apTdOvqhrGucERNL3r5Z5INxZ'
+    'j5mWRDHdWGnAy2fullAh2S0d7PvfxBYCqYMujlDyaRw9v4wSRyMOiDQH7QAqUB8qnjNfK093PA'
+    'dIspA0X9c0nkUNQ/l+KImRjkAHSdiGy/KOqfw0WsN7v3CCjj/QIwvTT/Un9WnxPnLOFqpe2rFF'
+    'RYWKqkThquD+3EJGQ0LhuUBUhb8P2igQnkWVRBA/+CoioG9QF0i/cSC9QL0FHvxYb2rPfh3WmX'
+    'BNldtAu8m3Y4YT+cpD2r0Nu0Y9Xw4STtcMJ+OEl7lmj/cJL2LNH+YUX7hxEE5Xp/irH7R2nPOT'
+    '7v3/Xt/3N9yajgHv+jvf4MvBMmvjkOq1dnWjGdbgYXzKI5KvhBS53wtseh6z/MC8b4/hZrulZr'
+    'Pj44q27G076JNT+q4pgq0ttXcDCMugUzN98LvaP9nD5Noa2Il+MKByxQWutXGu0qr/6N19C+50'
+    'IHEgHR7oFEQdQVSBT6R4Q1Fi5ppVVW2yA7Idsh4iZSB4nYTVCBdonqHctwtqQ45ip2l2uLbAvb'
+    'ArWoRSti5eHgyCy8UFnBTap3m8h41wR3BPAtLt1pLoq2K5i1HfenUZay/VVu1iN1AWw3C/z7Q7'
+    'UTY92Dwz68ut+oq15QTlqLR9u8cxOSjq0gLbrlq+LbzJR0NOqVGt+erPowUqSthmHNVXxTERbE'
+    'SZSxsEP1M9vVlSY68FJH17CsJHtbDg2XN+uRutVCHXnGRbZH2I2jCyrK2FGsQ/d4VWYivtDFuG'
+    'xB3bejXM9NHG+uNzdIKh+Vk+i4xJl3iRpkn3PQR1XXMcaMlGBDIfnFR+kfsCAPP9xBd+Kf3iyS'
+    '3S7cScQnniO+3gADAaA1+8qJkO914Z5V99HxNetaqQkFxUu0elWOMEWbwn5uvjrVxKcaDDbUhE'
+    '0KdWyoxWu+VgW2eji+BR+cTnQwvlGtrwbVcdOD481wA6fDd6yDpNz4urbZrXBaE5S7hACaHX3U'
+    'HE1vmluW2fjn45bAs7AKIK0Kxqf8RrW9UamNclMSn2yHq1GlhU3K9fgCtlE52NHEvkqtDmQ1uU'
+    'WJurLK+qi+zWzHWKupS78V52kQn5NLJfCexYc/rteYV51NKvIxFRXqiFVY2NVPku4AiLAVbRAw'
+    'Wap7sW3SbrXEPSnqImqvjidCInlLTI0IPbwjdS6TNJ8SOz7oH/nWbTHqludnmdYBSBT0prpWU7'
+    '655E1dPc+Hd3gqUPoDOgHdYe5I1III/2G7IZIRtIl8Gl3qQpQg4iPEsvmjZUSZrvD8/SlM133q'
+    'mJHLtutnMGfflH/GIY60lNP4Xup3X9LnQWU1YbIQZtK98SQl20FEvfh+hTrSUdWgzBPhqR29cz'
+    'lmHbvRiOVeW2VwapdMfb0FNVepWV4U4yBNfG+8qTWEgKHtvH2vZGOVymwHzbVIO1nESFa2iSs2'
+    '+mdic8UVG/0zsXXpio3+GViXL7JAaYDg7Pr3KYE53l8B1W35X0yZHUt1w2LMObN9Zh+u5owLON'
+    'grO7iiRl2fc4qqb+NL1OIFvd7KZHUbqrt1EcPBxvwxl4b8d7crNJWqA1Zqr5rUamF8nAbMCufH'
+    '5BsmRFCUGhCn9Qj3OE1+mtJ460GdAI/UmkA0PtOoOlFfDrcdcLUu64ZKuQJThqaYurjgVavQjF'
+    'GrQ7CW+Ktkh2At8VfJDnEUr3OyGnRlLfFXKT6489NpgaW8LwDVyfzb0tIhZussrMUyzpaTiDRn'
+    'T6E+i7eI6DPcsIlzx+CYOhsd3y0Zn7sUnTImewU8OcuQVUaWsVHsU9bch20Ok4AlpaM/YGvw1T'
+    'OrJjCkQtptTfHOkgsV3MGx97uloSX0JtSy40tViw6wdK1Bjb1bsS/VTi9nw9kIW/F6cWRUr44D'
+    'BFsTCmwF1nZciyGWTdLBADmOqvsOi6cvJDsda5IvxIsGV9Z4X8Ci4SYLlAboVvGxKFAfQKPeCQ'
+    'vUC9Ad3p0cpefyZ19EfTP569UIEk1p3xBk0Ydl4heT9CEw/4tJ+rBM/CLoG7dAXNFR77gF6gPo'
+    'dm+ao+EEpMrd4U25f6e1Scb7Oqp8Rf7PUpcQ3uOXll6zKHDZdtpW+xl8RyPaC0OUNw7hsG61wq'
+    '0GG1RbgVqOyDQS8JbkueXT4y9zOUrEV5eNlkNRAeqiPLlgzJdcpVZ6A0XUWt0Y1lTKSGVgnVkQ'
+    'u0/JJJKRNlpRXHmy7kjvTErfIf6hRoaIuglNGhebp1x3FSaNHGO1+jejmW2DsgDZ/YsF99fRv7'
+    'daoDRAo+LCdWXBTaAx7y4L1AvQS72X843ybh8OPnwT9f0/2Bd5uW9SeZlpQnZbd8seoZd6EcFN'
+    'G9SxCcLZS9P6rAFhZv9vcMgPFu4wtcSJFBgPMMvibAwKqxJUtZ2uvPsGFdXAyPo7wCmAB7y97p'
+    'wFdrxvoWwu36OSHBQmOI4/Tsu10OAZxMR9iwJUWkVHaBtsGt/eDnAKYAS/23WnvP+e4vjbl9qN'
+    'XsNiBYIqAjpXaYUmyVOHZNiVoC2Mz+sAczWI3R+Sjs5635fGfGN6Ht4QBrkWqAcgvTnpijeEQA'
+    'fE9eGKN4RAhy3FBm8IgWzFBm8IgaDYntFqo8d7Iyos5P+vVGzKnal3GHI0ZDkH1LMx5EgJ2bcP'
+    'J5CKSWqnlErmzmJreZwDm5DrxI4DNJYg79T7yEY3Bt+TzppVVJ9aAUn2V3zFJstPYnLlKUbQ8M'
+    'bb7lg6aUl8rM+/G/VkqQxEur0x2bOIdHtjOmGnYKeYQNrnqUBpgA55N7p/lhFYr/cEy03+dzP+'
+    'kjr3IGnEtQURJV1DOOcF40Pf+3q37xckrXjBfOLK7dBWFi+o69oO3w5YKWNt4ZcWp/xoh+yLLe'
+    'Wy2uGP4po4UQjidQK+0teeaqIuMsho1Bl/1lQWNVrXqyUmkvn4ZzobtR2Kv4dXcecRHltfN1aT'
+    '1ATlHp8h4Q3goEnF5UwEu6c6kyO6u1SmuLGtYlJUUI91WoXash5cUHeEKjUhhLvKl5CcUm2Oqn'
+    'XzxVlK82C9ifAepeBMFCeJGWI+EUbMOR3WQvE0KKcBIhe7xUAdi0nY1CqOhFa3HBDTtic2RC4+'
+    'kZRSRC4+kU5MbNh0I5BtWCFykUC2YdVL+ueJpP7pVZIL/aNVYJ/3DtQXT34I+HtHkgQE/L0jSQ'
+    'J2qt4BEm6xQGmARsigj0GM/jaaSGNQL0AvIaWrSej33pnUwggLfGeSBBwjfWeSBGzpvDPJBYQF'
+    'vjPJhX4i4Z1JLvTjvFaSC673JOqLyURQzpNJEhCU82SSBCzNnwQJN1ugNECHJYhDgfoAOmK1EN'
+    'cmE+jFRNXX9Zp+j/cUKjye/2vHn40S+cC00N/t+uquPoh7XalPWj2ToQ+l38KpKgkzhD0SkvJH'
+    '+fh8l3Foy9WoNCJ31O2alp3P3r5Ky8wR2vpAbJa6MfekLo7vXb8aBlHLDrXk013aKOGadBOU2V'
+    'lNLOmRj+OpJKuRkOOpJKtxcOopsDpvgdIAHZQtbQXqA8j3jlmgXoBu8466/0azesB7X5q3T77b'
+    'V1cwRDqsjvcO+T4Gc2m35AfbLVVbMbGcd5VWpMIbXfYhkE3ddpvV7AFq9vuSzR6gZr8v2Ww+7J'
+    'U2Gy8KlAboJmvoDVCz3we5f4kF6gXomPdi9+262Xu9D6DC0fz3W16juvYu+mVZZqobIES38e2k'
+    'yinK60z4R6xP3N3a2mGZFJVSVQGBWp1anNhLnPhAkhN7iRMfSE7NOLP2AUzNN1mgNEAY7nMCGv'
+    'R+FphG8id9c7UEM7+LzJOakki7WsRCsSgbJMp+NknZIFH2s0nKcBTuZ0FZwQKlAUISq7dpQ2/I'
+    '+3CaA0P+TcpysflLuIjDnqR53PGR2m7nG6zfKR2lzUFN1Ddk7mFcHS4eVgsnviw+KmNrRiehVf'
+    'FTdd21+jRHNBHtbK3Wq/C3qQW/BEW34nVaZF9QO6aiHZlEs38ige9qw8e9VDWmlpifQ9h/TLJ4'
+    'CPuPSRbjqOCH0wmH3hD2H9PejSQPr9cC7nkfU73fiOW7sdm4UrlG0S45cXeR52npO0Su0hJ8x2'
+    'qPR+35WLI9HrXnY8n24Hjix5Ii41F7PqZE5rd0e4a9X0tzSPsvOLwYs7qFfT7x1eTmwBAU2K7t'
+    'MFS7MdldnW1eJW12trjt4wYYUpCZFvLKmmSEhgiLH/RXNcIGZQGy+YEjlb+WNtFJCpQGCNGtf6'
+    'D5kfN+A6iK+Y9/G/zQ99IYxrjd/XlZxsS+YJs3rmHOFfEmhwNdSd7kcKAryZscDnSBNyMWKA3Q'
+    'bd64+7uaN/u831Hq5SOX443uVYTktWm98NxFRaKin5OwcNXdKncf8eR3kjzZRzz5nSRP9hFPfi'
+    'epD/YRT35H6YPvFdB+75NpThRSe06JQlyz2ZRMGq0Ng8KCjiLQu092VhEmgBrzyWRj9tOC/5Np'
+    'k1VEgRyAdFYRBUoDhKwib1YdnPX+IM3HRB/9ttOKPPd2KXMZOUiIGJ2DxJUcJAwatkApgJCDRO'
+    '1i9Xt/iBYMCpZ+wvKHYMRe+aSfsXSBUhqE8JE93v+Z9q7yfizjOYwVViFB+rxr3M9k+RketGfS'
+    '7If9ZBazAC+xrH3N+EzNMe1YQik7LcF64kCkufncyguOEmazahwWMrFwtcLp9YzzsgO7K+hp/Y'
+    'idUsmXEO91V5SVGOfeVdlPTmDX/HDk48SRC28prSL52Cj8p+vhNjbFw6DVboZyZTx6GnM/2+18'
+    'GGGtI9+wOSujvfzhIwFnBk5EEvim+Ol63X9M5TyXsX+Ry6z8u5jbJ1VZSwTvQAdsBY/wm8eTQd'
+    '2hFfiBFYqKmwAbNHnqGMZJi6GRhMVyUburXD4ZmRR+Xu9zyJ1uN9wFvPTXVv1Jle+VjRsd3LIq'
+    'Ad+R2iCKeF3Uec6HqzxlToLICkjv5CrFqIKPWtscDtBqVsomUT/3foiki2XxlJjJJXFwUKkPFm'
+    '7SKM/EGkWBsgDpVcMe8fw+g1XDYQuUBuiIeL4VqA8g7flWoF6A4Pn+oiMwx/t7VHg6/1nHn65E'
+    '8XLJcveIN05fUeYX1qyNp4KvrynTcc/EYk6Dv07y2dIHt9VWgsako3n0linrL7VBSYJExrFJ3S'
+    'oGmgxpGjOVsHnSr4Xb4vlR4yy4UK9oSZIdOIvIgsVi7Gj+fZLF2NH8+ySLHcUXz5uwQGmAjose'
+    'V6A+gO7wZixQL0B3e9PuVzSLU96XUeGx/H+Ol/56ULxgq39r5D3LJb+s+N0rXvJbg0WzAftlX0'
+    '5yGf74Lye5DOn7crzqV6A0QAdlAlWgPoAO0RI/BvUCdIS65309Akt7b8x4yC36oz2wZsxpOs1p'
+    'NXKTgRmW2Rg0+CDLjlIlwkE4Whv6yKU+WmggSo36gLz8leEOrkgb8/n+Hvx8BeArSuzv8o+ddG'
+    'MrZc0+Dlmt189HnCxJoxOCzwYNjgrmO/m0hra1tL6/L6mX4xJB1Rey/PPhjhDRVcQQLCu9u/zj'
+    'Uuxx9ccoxSRBHa1z/dmOlEEcF6kCDKAILceJ6hdN/l08hRtZXcVNKdC3AQ1kdkOgbyqJAWE83I'
+    'R0EzM+3lEbVaoMOXiiGG5te04uzrLxxIeDuhIc8Sanjp/iRO+Iy66s++Z0tRoLu58F5bNMC8sz'
+    'J3SSaXEDG3O6I60/TWQcv6HNFpYqlQ/X1YttdfBcEIii03F+la2Ew1ntC8jCRU8sEphoTzDYOu'
+    'dhYoOyANnjElvnBPIkbl+BeHwhrfmtDOr33gw8ewvXcqADNuxWzHYlTSLKnNzDhuCbgX5AcClD'
+    'sAuU0qAbBf1bFPoco68FtfpKEK2gmhizg0I2Gmc3UEqDStKWjPcjmeczwyDj1FhtUA9AeyxVhj'
+    '1rAh2y5hDsWRNIZxiEb/RHMy9UhsE9bN0Tfm3d7xHrnkHDFigFEKx72OUD3tsyZJd/SdvlcFsS'
+    'pM/b7/50ip9hlz+Z4cX721LMVb6EM5Z+vcPJwX233dYZJiEGfBAHV7sXydwh4by03kUXmTCzbY'
+    'wOsib02kfvCLmGVTiMv63tjeQiF8HRRh/EYTF1mo0lVzeowbQYIuSSTJ3mDhk74WG48prsE+aj'
+    'h2shn4zmIOg2bHa9KXajkooBMfGejAVFgXoA0ieaBsTEI5DOLjQgJh6BrpPJckBMPAJdLyEuA2'
+    'LiEehWb4zTVPE1Ed6Po76fzEiaKn11BEGRpupmA0InviuDzFf5IeM02eIk5bzVbkrhHBPKdYJT'
+    'AO+lNeA+C+x478mYSAED1OC+DnAKYAxWG0XK+4mMSZRmgDhJlDFxADGYSyMO4M+1bDre0+DA9f'
+    'n/mJIRzykVRAgkuENd5awWf0bHN5rIJIdJSGxLDmlm/YYDOFiImTVbl8AqKcICquiXAjFIqDKN'
+    'HUscXOGkfSbI36tTPcURbaESS7XYkKOqQbNJkysniOe0jTxVmeC/amcavNVqfbXoz+rkFWNqFt'
+    'F7lphAWuquF44P5G1QZSwqs1r2XxXTrKxpWuZgUj+dFGmY1E/Hk8qAdPnTmFSusUBpgCDS784K'
+    'LOV9FKhuz/9glvtKXbZrIsLEzRTGMbFLbEgpphn/nBxYqEtGFsmZYc+nWO+by9eYH/juJXf4qz'
+    'yEWyGtP6rcHeuVR3QeKNcfoVcvuWPMb8vfSP5yIQbIr1Gk8bEysOqGmBtzXZUHTkSG+9Buj4ri'
+    '0ksq7giyEusqLwls7wpHaKlgMojwJgJWJcwqIAuJDKU4aY1kgxFnuL9erSvTXZ1qiKuF94g15w'
+    '7emut8zYpCGsEL4sQykTBbxMsBGteYN9a7gop/LuhkXxyDEtJSv1JvWod7WPmovnJ9c+swn/JO'
+    'WG7mKpCWcoMklLvJFlCXzrYjIVRsp4rtJjC2P1RMPqIXirRI2CzOQg6MATUgC5uPJmWdk8NljA'
+    'NzQHQTgXIyyw7IwuajGT7GHYP6ADoo4YgDsrAh0Ih3zKhvx/sY6vt1W307CtpLWnXMgKC+fyXD'
+    'QV7Xio/YCm9QZwSvtksTkl9JqmadYvBXMhzEVbTAjvdrCvd1jLtLUqMO7I7+Ym8HOAVwJ/aU94'
+    'mLYI+j5W00oOcT3dgFEbB/dUBYmvY+m+Ew8M8N6Gge61zQqlmSVYNHK9Wdu31/Lnh0x8Rw6z1f'
+    'ManGwUedSV2deIHfQvL2bOtQUBXWa608+agaGyqqtjGllyqcQE3KHY7iLGKsfeXsuNCHWG5ZGC'
+    'hbVcUdqQlAeR9lNCWwiiIvt9RhjxgfEyuZ9DjpnXLjaHeiRP6j+QEfa0NrO9ZM8HGtN8NQ7UDw'
+    'Ss/ksWGDDrFBG0jl1QR7d8yps2SWpZZJr6UNWH0czISlumaSSmx8YOEYtTc2wkinTkp42AK+CA'
+    '6WXyVUmcoCXlsCT4KeRD4uzlddb4qb11IYq7RSPx+GKp0g0gxsoi9IIsSbIBeqJKIoK11qSQc1'
+    '+4EKjWWK5R4mnIhbl20tuEitvRvq5ZMu72NKoDenkWLHMK6MCaxDxcS30+0mugEGCkQN2WvGcb'
+    '+MuR/GtSqr2PRYt1Epgk9yhFZLR4LqyoCNVTzarnx05kSQqpDFudxuqqOSPJNVVTqlJEIIfaWG'
+    'lGd8pIrzCSEoWlJ5KLEkLtp+5c7Ve7evobwZls+b9ETafFMn41yeIKn/E2ePqJeAGdn5qEkQi9'
+    'loVo1bxPyOjGqLLjG6Xa67GeK0kBJITpQkfoPkUIRPny/q5CGQoMzahqiFaDDuTtVHbOo6SnIX'
+    'nLzwkXOSdRUgqsZ3paaSZcnkx/ew8kEPMGYME0MYGNOz0W426io+Boxx9ciAEVPrnHHFy8vsji'
+    '7Jb9f45E3mqZZciFRp2RzXWyNW3J7VN1pbJslg1HJF6xF1HOaIkFHh234TpLAT9AiH1x9xL1Us'
+    'qZu0PlPZtxIOxU0Y3TgAKhy3rAS4WT6btBLgZvls0iKGm+WzGZNUdUDcLAS61jIccELhszCSj1'
+    'igXoCQSugLjsAy3l9m2Mn8n+z4MqizF8zFrP3+0XNzMPsqm+oVxZRJlnDNgIxurg3KAmTzF7bT'
+    'X2aMe3lAvC1/mTHuZQXqA0i7lxWoFyC4l08JKOs9g/rG8see/U1zGi3Cz59JUp1ViG2qEX7+TF'
+    'IqEH7+DKTiOgvUB1BeNnwUqBegW0hQSgLq8f7uefV0MU5qxd8lW9GjKtpjcRWh1n8Xe7oUKA2Q'
+    '9nQhHO4fXjBP1wB7uv4h9nQNiKfrH2JP14B4uv5BebpexKB+74vK8TgkN7Gt+Q+z0Sdo4c/8Yu'
+    'xiHBB/ZhcopUFwoO31vgIH2j9pBxqi3b6iHGglfoTt/rXntav2iq/pa3FX7RVf09firtor64Cv'
+    'xV21V3xNX4u7CvF6X3/Bumovd9XX467aK1319bir9kpXfT12Sg56/xU8/aGs8BRxev81wwdpy/'
+    'wInn4LVPv5kgoVSMbM6MiBAFvicMA31KXi4oBU1ynxhZ2t+KiUq1MHM1mDwuRvxUwelD3bb8Wj'
+    'elCY/C2M6ustUBog5G3+vCMwx/v+LGvxT8daXHJfvYD7hOqE5Aurw3mT3WIbnEbcVhuUBchmm6'
+    'M4olX4oDiNCKRV+KDswxJIq/BB2YclEFT4jQzq996QveSGwiAP7TdkzTgelKHdBUppUEkqS3lv'
+    'yj6fY3dQHA1vSvIH+vxNWTN2B2Ud/KasGbuD4mggkB67iGj9wewLNXYHeewSfj12B2XsMmjYAq'
+    'UA0mN3yPuRLI3dH9djFwGgBOmj1990+BmD961qKHy+YyiopeILPiBUPS/03rmV8E46f0h0ylvj'
+    'zh8SnfLWeHAMiU55azw4hkSnvDUeHEOySfDWeHAMySbBW9XgKAnI8Z54XkV4SIb4E8lWIN3YE7'
+    'EID8kQfyIW4SEZ4k/EIoyI4SdfMBEeYhF+MhbhIRHhJ2MRHhIRfjIWYc97F0T457UII+b3XVmO'
+    'Vftsmp8hwk9n+cyHFfgRn0F/AeVXKnmhhVcfgyq6x2mQYnV+wleZzEyumGO+ThJz+3GdBS2++U'
+    'SZ0Ycj3xjSpcUpRB2sN2muxSY8LRLvRwKberW+AWnjK8TqtECTlWtk3VNVp5U5iW31QhhJGIGP'
+    'tD58qk1n8FXOHz6fxbm5V/k0GIqtheWKuG/0Xt+iOJKA6JTKGSLi7ckgfToWb08G6dPxIPVkkD'
+    '4dD1JPBunTWXOyxZNBSiB9ssWTQUognGwpCcjxPvi8DlJPBukHk63AIP1gPEg9GaQfjAepJ4P0'
+    'g/EgRRj8h16wQerxIP1QPEg9GaQfigepJ4P0Q/EgHfZ+EYP0D/QgRSD6L2KQXu3+pzQ/Y5B+XA'
+    '3Sz9rRWexie4GDs1DHCx+bJee3//82QodlhH48lu1hGaEfj0fosIzQj8cjdFhG6MfjETosI/Tj'
+    '8QgdlhH6cTVC/4vDMGy2/yYq/J2sl06G+4nPdi0cVykRxtlxPoI8AvCkUh/fs7y8iDFdDWrlcF'
+    'QJxlq41ajDazbGqeZqyt11tyqL09JrfL610zMWe0PPzCxDcFZVxgKqydUiocKJF89Z7+PqjHNW'
+    '7zh0bMwtLiwtG0arcAJqd693Le/bKxCG1m9lvYx3A+/RGCCVZfC1HeAUwEjYOmqBHe/fo+yBwn'
+    '4V8oQjeoZKN4HB0YX3dYBTAF9D9b3cAqe83+ayhcM2l1WeTZ1IkBO8qO6KknWBMP5+sAPMaHGN'
+    'YU6ExPF+FwLxH7KSp2JYdO7vJuUSOvd3sybx7LC0h0A3SHTHsOhcAukEJsOy9iHkPXJecFjWPv'
+    '8BVNzMM8cwk/XJ53XmGJYVyieTrcAK5ZPxzDEsnPpkPHMMywrlk/HMgQNDv/+CzRzDPHP8fjxz'
+    'DMvM8fvxzDEsM8fvq5njhzE15LxPY+r4e5o68v+c8ieN29ds2UNNBcafEHPVbPAYJspBUhXUjm'
+    '36QJ3W102SjILq8gJ9yN+E8Z04sSh5GXF+h88zmTSx9XpV55WNRNnyvh6nMgSB09a9G3yuMyom'
+    'DsF3kFCpJW7qUF+oXHWyx6Hoi9GeOCEoRkaVjiJM6mqajmJT9cbOcn1kdFQ2NznRDQ+zc3YqSJ'
+    'MvUiebVGnScEzq01lO8v+HKX5GNvvPQmz+M3Ttr6nIHjt5RCLDZLylyElEJUeO6UuVrHlDEldg'
+    'f2it3hrXqabWdKx6JVqJk+NU1M0vfmV93fraRlmz0kz6I2shCYVOf6NuB0OHJSQBYWtRZ7Aocj'
+    'XMUA+MPea/prBerxfGVIzOa8foeTVoFleDRwkGYhj03e1HTBH/cYsi18fnxRH5ZrSIkjKic5Lp'
+    'nljqyg2ROZPp/s+h6gqs6gyQRj+DBzrAWYD3ihKOwQ7A+72DHeA0wEgKbFfoeH8BzDclykJp/k'
+    'V3hXAa/YUaykkwI8ERsiQ4DTDyxA0xGK37HKTooHBBtexzsV7Lidf0c9Br+yyQA9B+0So5aQ2B'
+    'EOLB9//luCl/DVSHcf/fcnKfe3cJHUPvb2+S1GF8cLQNG5v18yFUSdPFdKVSAnP61SDy19pNFa'
+    'AlW3Yzct5HbgRUakHih+WCwbhpYOtfJ1sLlv511gS05ISdf501J1pzwkoC3ULzmmZlynsGmEZN'
+    'GUwRzySR81ZKEjm49AyQ32yB0gAh4Y5Gnvb+BphGTBlssv1NEjk22f4mayIpFcgBaFgOlisQ40'
+    'L6do084/0tMMVlMhrkWqAsQDbl2GH626w5qKhAaYBsCct6n8+aJNoMIOSfTyLPqlI25dgI+jwo'
+    'P2iB0gDpJNo5nnD/DphuNWV4fyaJHKlw/i5JOe/PgPIbLVAaIORh/yzEd5/3FcyAX+2hGfBhf6'
+    'ZWDhqRpDGu1NSJMDk92JZQd33xnoqZlcx8iAyQIDakOa+GHVnO/e3ASnxEC5WHns/E0TE1HJMG'
+    'wtWEgjOmX1H+mjcM8zNn4+/xkNX9ax5N+HN1SZlbiXNwB36jEqr4jCTaODEjt5objENQTVK9jX'
+    'ptTdIzWvvbcQJrcwjK4molkpytcrVSfNcTPcxOz/Adgmty8V6ILdjkac8444CkPqxsVahW4KpX'
+    'zTVZkjx1jFYGuBBKzuapJpgTKRc9JwjeSq45eqWPlzzu+nMhH2ms188jfzKn245Dt+N2M/ZLoX'
+    'pQzqo8+KD5g/8/+CBeBvJytcx/iBf+uu9vbFZcrEdN4miT8oroUf2pju1EDbIwfU5v5Sf/2fOl'
+    '778mGKuM0h//jjH/6Jh/nP7rv5bLQZ1vb9ar3Q0ryoerHR+O+XfgW3xYDVbDKi3/pPWj6pPy2F'
+    'rXJy/Wn6hbShWbpHw4tt5V/pgur9IMEz+l8MbYZlfh201hlaF35NiovpUHbBqnYaDZJnEu5vYB'
+    'EyMtQVMtWtevyy2qEhPCCSh9W+jVPZGSn7rSGrXO/7V1UJrKgMjHWmiYSfhzpFK3+z68DCrcKq'
+    'yVq/UomaNVDgUqWwxxULaQczRoq9KMExxzaHT5vD/SqEdRZbVqErmz60SHM8U2nJV0XpmxnHRY'
+    'HWiVsCDDrm2k71byxVwz24iFePlSMFxkl4oJFuacXTXFrSK64aymxQhxvEo1BypRl2aoigaOdD'
+    'iwzmBr+GdfpsO3WjaavMxHxSq/umk+J5yTqyz8rXrEXpv66oVKvR1p5uoLZVXb1grC12ADoWI6'
+    'S7VObG7n5La7IXnlDy7uRfZWyflvZf3epdVJUT0cqeGtA9vUUStONS1ShcgfZYJLWSUriiIRF6'
+    's9IZLyridzY9oM1Gs6FYAILKshTYUsRmLrdXJGHeWONoOmWip1ZI3XgWoq2zV/w428V8VTqbiw'
+    'YLcW282M6lsSPdZZEpjNQhUhrL6+LYtRYBFI1Aa7DCK/sNGstxsFWZ6zkuQ0x4HSUGiZdQmAGZ'
+    'mJ25viJLuxRANRPGFW1MWZLa34VCQ+kEqOyEqTV8hk5JqAWXP1EzFqKk7gpq4n40M6Ym1bw0js'
+    'Ypq7V4NVFSBLja9s1NjRyGnj2Q9LVdZ1qh3LUaKy8eBg9BhMcT4RogLLEUpnaimrNHG+uumpjM'
+    'C8OPsuHxUSk3yfLEDYELFBPQDpBcg+WYAQaL8ElO+TBQiBcIFLjkFYI78HmL7SI9Hj+2SZR1As'
+    '897Ua2Awf365h9ZXY/mv9tj3jsj9B0jjLbJ8MStOn+iX29xcwwC++sA6zCKea8sPLfc6Ko0nqQ'
+    '85ZDFQKhRmpDo/jyu0d6VA53mCupK8muKyjO0sHuPWCMcpEELPFgHPjLfzzPhSnkbdWHGfUPq5'
+    'GhZ1bgR08sjtNKFOTPB3+mRtkVs18tJRY09QAaA0BTAvj8SvucCxOPxTD+5dmpioXGWIsFl4B1'
+    'Np5uVO/iQ+vsu/A+HEta5iiv5u5MeTyHe7r8jXx4GPC+pdLzVis6QL/bFdjUEuK8eLY42hMi6w'
+    'WMQ3UJt5tx5fHCpem9l12LYSzaWEqBogdlQJY1fno+eNaHRM3kmDL54BRkyOVaP5XT1clK2nlR'
+    'mfX9L7p1uVcr1ar43K8YZ9lnOFx+JABzgLsL65b5/lXCHwPvGo77OcKwSGRz0J7gP4Bu8299ok'
+    'mNbv9OKgd8T9s5T1xvE+odTCb6X0qeVNvpBGeRkQ7B2qK0TaTWOunZDE/1VSCGPym5ra3qqN4c'
+    'bINX4R279jVoxzEEVtJHDg2R23aBtEo2P8qcJjbrvBnpUcTiMFbyXBV73EB+nkjF55h8QhjiIH'
+    'TiWpCqXZqALKR8NmfVxtscCAMVH+yKfPs40k1McJARe7aHVlq8h+1VolIk20U9H3P7fVQWy7J+'
+    'B6+UR3L8P98onuXnZUR3T2Mtwwn+juZWwLfKK7lx3u5U+oXv7ggPUm5X0RpIzm3zZgbsBY4iUu'
+    'ZtJZWpomvaUma7U1CnTW/IBz0+1g/bslxjsbSJV4O4+tBL7DWenoeCSJr4A/0CHt+h4nPYhsWw'
+    'JJYThRtsy5vGXQXS8OetJMHJbbfJwTxSKVUxhpC1n6XOVl6/hKzfumvNqmbYmrni/cNksPrhQE'
+    'VcmWJuAK7ncG3SuGIEaAQySYUIONZtDYZLJNARZMRYCrmTWCXSkYatSCmjqj0aqPqk0Cdb5Cj7'
+    'uimmYNbj44o53YuF8T2b46G1OPORZP0Mo2iZcnJsvBAp+I2ow/kUNLdgalk+blVtA8jxGlthAm'
+    'JkbVOi7ie6pDXnCIhansYs2HMc1DyENLkr2x0OBGJJKbSnTeje9e0ei6tTCvITkrMgSjHp/CVR'
+    '4Jst1IkcyH28wTllw5yh0fA+db/dTtTfqumcRkxXmEjOOC5/Mpab4tuHC9u/5FX8MpvwtaAqsZ'
+    'dbf5cTV4lF7efvKSaB/VtU7WZCkATnSVuQSO724/Ijguh0mXtC5ab6/S2CC4MjkEwbQMDCMn6s'
+    'q05oZ1azgEXgsBYiaaQYVP5WgREVSqVl9/b1/c3VSqaLUa1M4rodejQY47K6uS0WAJU7w8efHQ'
+    '8o8Xd+0TVewu/8WqV474p2zBNtxic/CIutuDm+3PSVu1eEdSRAu5GDBF/8jEJTHLsoW+JDqRi1'
+    'U+6BAs9ZIIfYnpFYk48dc6mh91TFzwl3+xe+KCp/uLPWbvJwY7AO/3bu4ApwGGi3+fBU57XwLm'
+    'I4mycPV/qbtCuPu/1F0hXP5fQoW3dIAZ94g3mqgw430ZmI8nymY0eKADnAW4s0JsA3wZFY53gN'
+    'MAH/WOuV+GQ32/9089iFjv9RzEhsRXmypNW1Vrxc1Kg3q7tY2DRcnzf8ppgOxoSUe7vrlmMr65'
+    'wszZdvajKKqXK4HZgjRXdZlaXNtzH8dC6Mtn2BLmuz4gtnGgvHyUyG+jnOzIfUht7vOucx/jRy'
+    'wyv9nDSU8fxsmySRMZpee2SDkt2JsB+zl8hCejDk6wc1FPVq726OiUZTSILGJkZb1f7Opvxivr'
+    '/bKy/iZW1ldbIAegayQr836xpQmE6zBzDMLK+r8B0w/2ysp6v6ys/xtW1vvdtzkGhkb/d2VCf6'
+    '+9sOZI4+R827mFYjfDuhyRI+7VZgEbA9oXFnTabEW9iVFUbjKWT0MVceO/x0Ieg7MAa/szBjsA'
+    'a/szBqcB1vZnDO4DWNufFhj3dCj7c9l64Xiv7yVKbs7f3ckhlifO36/WY/rmrF051dFCWNiMd6'
+    'ADnAVYD+MYzFTs9w51gNMA4yLt11nglPcGYD6Y3+ikmBcsyvRYhyOO+hZX5Rj3ZlKW5UIDHv/x'
+    'WRNrk1dFYXS0DHrrDd0tgwp+Q29X34G/BNZXy8fgNMDYsP6cLa5p7y1AfX3+95wueZUQyCtpma'
+    '/OW1+iZYxFpbkKa/rZ2qqGdmoEUctatCPy7wJWXnz70IjcXKpSnugFNyuNuxjluLIERzvYhwnl'
+    'Ld3sw4Tylm72YUJ5C9h3TQeYGYX0JT+55F6nLr2YCBqVCYTkQCqVUOZcuQ+DXuXlbowJfTfGRB'
+    'xvo0oXPppycyVBEDtPcjk3A8/NAcd3RvpL/Dt3wO1tQAU0awdSfprA+jF30HXh/VAZ2g6k+Zt+'
+    'QNgnk/sOt5eUCaHdOZChd4PHby3GNBa7ay/eo0qX9Ge5a9yeRrXdDKoHsoxcnnJ5t0/n3zzQw2'
+    '/Mc+E1bq/gyV3r7rtndml5ofTAyrn5pcWZqdnTszPT3lVE+HULpdkzs/OTc3MPrCzNzp+Zm1lZ'
+    'nFxeninNew61eP/pc8vnSjMrZ8/NLc+aN6nCaXdY013SE9WuTCPWlDcr1TV2hBHfmDUMQRa8E1'
+    'U3p7tvxcx4uYPFzjtNmJESrXTgqT5Cs+f4wd24aKgpDTc7QSdq7j5TW+ypzN2wS3XVUNf27j7q'
+    '7D3HX3TpPiuZdkzHW67f5fZpaO5QVyWSgtWqx7mCegzGUzV3kExGq/ipvbo8O/8WnVdPysuNOp'
+    'L4Fcn2nSCtwRRMqFf0WcSjyAqbPmn9/objvDeVOTO5OHvve+fcfm+IbKo3pjzH/XVkKsNT7vhH'
+    'Mj7izZq4stk/fvTYnRLI7M/NTcFsn6uUaaEfrin/PSuNyQZsXv1mzL9Ppe4iM/+oP8I7OPKqME'
+    'qWvb5VMZngt8K7hbgnzef7eRCximjeOGuw4KBF0gOCob7KxhU8Kg2dnUEXI5veVX5j3Gx6YmJi'
+    'e3ub2ApCmXNVVSyamJudmplfmhknYumDczU+vm6Otq/u6CuAsRarBtvsbd9oSrJDBBypNFS4CX'
+    'q9tc1L9DVcelshGzDBJU0YtdYugD2wml+YXPJnlwr+qcml2aUx179/dvmehXPL/v2TpdLk/PLs'
+    'zJK/UPKnFuanZ5dnF+bp6bQ/Of+A/8rZ+ekxfZQ/fARepojDnznid81Kaa2rNwdd9MU/JiPkBj'
+    'aBeOHPGX8iuYS5RjYTp8OSDZmuFiHBBGyTYZKfffQTBt4++n3ExTU3V9OvQyjQd0h+A3oN/XoV'
+    'Q/fIb0CvpV8FhrryG9AD9KvIUP0bv66jX4cZ6shvQPMGw83mdy/ZUld5Pon5d/X1Em2HyPa8M7'
+    '8IIzoeG8qUWDMLi8DXQzM23nGrHHXva7rU02tf81oicADYiQ83eL1k8KinHq7tRfLk0NMhWtOo'
+    'JyRmvcN7GVNYIApvJQq/h+8wuoW+mc43r5jCWP3Fe+DJDUFzifhJtCFuQqyL4jYg7LtAbSjIU4'
+    'bp0e966GkPrTzVE2gd9V4uT7il4W5vils0Qi0aoxY9SG9S3m2E4Wi+9Bxa1Mn03SiGSTlCFN8i'
+    'Tz1c3yF5cujJF4phod1G/5twf8Tv4wR/DtH5BYfWld86BG8onwdIRPKu23RJMii/mxrdrmQ79C'
+    'JyRIYo7tXmrEdA65qrs22mECqoRb2hL4fekBS6sjGqQpLMejRSm+a6OuxQk4LbCsZkYWxRPWZu'
+    'CBRDRytMU4r3fGJf3Qk3mVR3ud4gbWGS6U5M+LMkWmWhIpGYeV15BGPUiqqi9e20ugs76ibUbO'
+    '6qzOlE5WPChMcnHsPNao/beOD/fGV7ldoTtjifj8Ik+12Cg4h7jKZFn7ffu7BIHPqINVVrNKNk'
+    'C1vpg0HeCb/QaK9G7dViPOfyHdnMn0JcmI3IWJBO2Ih83QnApi4qjyYek1+PT7SAigD89/FC8j'
+    's4DlaEknK13l7TxG4FNaSM6qRrUWHdDQuTSBZ/MygzhbvQYn32uP75uMlyrBIF7zIYHgi2qiKz'
+    'ygvHIW0qMI4/Nh2lUjePPwvedjLWUDj+XLn6vDD12fCUpuX6VogrOjltjrBChefE0UOqMRBls4'
+    'VmuWh3VLb4KvZk1JxsfdkU3+PFBvNcfaMjOfazHgXV+sYGgqY7OKMxPz8jgSqhR/rv/3yj4Mrb'
+    'tV6vItRl4jH143ls1WlGeEWN6iTi22sSmdBBTU5OEk778Xls3oKF9ooaeSmyvr0Gr1Y4sdlkuV'
+    'xv1yAaAlgJFOQKWy1fdTb0VAL7FTX1sgS9UJq7U3Ub3X34MhrhsKbiogr8chr8fz5tMP6vaJiP'
+    '/39m/I7/Kx+Y6gjBejV8pAK3QofRmjDITbik3kpS1ar0SnISPPGRRJMjzkjMAHUxer1aKe/4IU'
+    '5RxYGFuxsHS5vA8G1YBpXVZtDc6eQlo30OZkG0ycfeJx7Dj/Xnf+g/h9ng+SWpa3j/S+nrS/TT'
+    's1TWL3QfvXB1aebzrisn93e8Pi/vbvAjXATvdryU9zOOl84v+ZPGNVCJL1ZQjn0+G4BhzUHJ4+'
+    'gGjlSET8CkjjXDVJojO6xXSd1UVa83yMf4FQgboE85yASQ388SUGjXorBVMLnUrraL4ipgx+QH'
+    'iMEpgLHP+L0W2PHeh7KF/MNy24tQprcHqzgksSYbScpjAA9CNWjXOMIB8YXt8uaY8hDa96TLuk'
+    'WSUyA/uI99rGYHuY4m4WAHOAUwTur9344FT3kf4ML5/+gkCYaqs6hUu+/sl16c9evbNeUZ5Q1y'
+    'FXPIseau0OSP6KgZ3mJDsneVzfWieynKk9JoIlttS9+qzuFTqyGH3postHU4sONomq6VXXE0yR'
+    'F0CjfyQAeY247Nxi+ljVh8DGKZy/95OskNlRse2bUlf2ynO8U1/hSeux6feMxso+DbFeUcYaQP'
+    'db95SON148viwA6GwqWvr/OQ+zusLORmuvEfwnhBzoHO2bZdPh+2HhJllzjw202JqlIdUpMiih'
+    'xxg1eQ/n7iNZPjrw7GH33ta+g/9PPo+J2vvW2C+SN7pSpGVl21VvPbjQbSBSBBSnkzwJweNpWA'
+    'S3EsvheDiMY63zQ8cg4fmJuHRxXftoJHKlvtLROgv+7G2CKVNVGSxLQuxmWSx2NHjxr1oGILuM'
+    'v7LJADUL9cC6ljCgiENO7/R9YM9N9z+HqvX86ao51FEZqquicqaXKY4d4R9sJePl3UFUkDnun5'
+    'JX2JsL5rpl2tdmBVDIQps2p2HwizulFUX/HF5/tV7Z3dq654Mtc6VCsbcthHUrpUxNGtyunJbz'
+    'ncQpohhJUtSZ6Bx/zCRME8Pe7LHKsBd/lzszTMJ+f81/n3Bc0K7/xIGfN8l194rGAKFh4v+Ce7'
+    'ohwxa12xH2r3oudr9e1quLYRngqwV/WYeV5BeDVbksuS70JO8yCuoIlTZ5b/X8cPzE5H8eUosc'
+    'FYU1PTZiUka6C8ucOjAykDWWlykEzQGkNe7i4FpiLI9R3YbhxOYyiSs5Qcw6pbWBi1jgaAKOtu'
+    'QyVWfAWvjgSIxwD2IliWbVAWIH1AW88rBMpJzl8diEIg3MH1FsfMJ58CqgP5nY5RIfct1jpc07'
+    'JXwbnjO/3WNaZc7kqZXffr6naUsTjZlETmq/PIBZQvWA1D8MmnkoMb4/ZTGNz7LFAaIOTj+cOM'
+    'wNLef8Z31+Q/keloxhVbJbsZJZ1Lg4kJxjlbw8BtmUGlVwyWzcCxcRHp0TpfmhKrC0Gj8sKqMD'
+    'preacnVRYQPhKVMBz4a11bJxXPfaWyVm10zkEdyC3rUlugMeTSy+j/t72ri3HruM6+pFZajWTp'
+    'ipIsm7Xlq5WslSxy11rJjr2S7fJPu7R3yTXJlarYDsldUhLjXXLNy11pI6iOEaRBggIpUMAICq'
+    'VFULQNAhRp0Z+XPgXoY1CgT33pQ1/yUL0EgfPiIA89fzN37iV3baVA++IXiXvuvTNnzsycOWfm'
+    'zHc64ZLguVDqAyE9GFH6SD3weEVJoMnszibMCP9CD7L4+F4eUvstkoMkZc2xOA+8Y+5x9TAmtD'
+    '3uL/C7Y8nvSa4oCmthBbDpB1HhoVmT4pyC0N+Tut2TnBd7YIHSTA7LZFKrKUvndCl+QEZ1g+tv'
+    'qOBG5Qev+l6ukqfVR1FggD87Pf2BOT6a6vSmWz3QzYOm/4E/zSjt6eB5GmMrGFQpbbyqKCFtXX'
+    'WeDuSKAbC/CE9xDH79BU7xwxYpjqQEzPrfakU15j7iKf6IDd+RjdXGWOP/tbVeVTrTqPphK2d3'
+    'n6+hj1i1RMZ0+8ctkoOk/dZ4RHiPRzwePyC5/tKR2Jbku17eHEhyTOhuJ9AaS0HnyhsESxXedy'
+    'KfwlqZ0Gl0uDoM1aXcwJxP6DM2z//TGTbPPZNvRZ8Z6wu6QZzwbtszmyuBC2p0mjBORgVee/De'
+    'HaUGg7is3/1o88H7Ya3hiHX6WdBLOknSZ4F16oh1+hlbp/8dG9fZkf4oRsvwf8RGSIpO3ynCDC'
+    '+a6XSkO8lP2QK04REQ/eNWcEavc3c0eLugYXW3MhtuBtHjnrWMmQCAUN+YUzVQvLLb3Wn7lfaH'
+    'm22wYUXK0kGyf/e6d/GxeiiIu/vCR28PBFTEiB9tJRL2uEUi+WsrwxFbCUhgZazs5ax36udFdR'
+    'qVwdYljC6rc8gZXsura2Q0jtY8sN7r9vCyydTWpeTOUZ3J561HJPq6TmMmgZz/5Kg9y1A+RiHi'
+    'GqCjEPF34pQ6KFcEyYGVOMQDQivhK8+oMcy1s0Yhmvuz8f/KxCtMSVxUx5pb8KPJWenr0ncS6H'
+    'nUfiYxfbPnHmVeUAnspNX+yuZtkjVxdxhlAGsSiaLTejDxiVIH8UmVUy75iUs2/9nnf5N5ViWH'
+    'C9KvSwPzar+PSWH7vTVu3aGZiSlLtFP2N1NVeLUCb3Irx335M7GknlrrdD9ot+p+G1aLVrO/XS'
+    'd+oalxYCb5m8yxUa3CUo7xl1X9IdL9xFvqAPHFKwXJ9sDMmd05y9C7zJvyDSHxvjoKUxED0EgR'
+    '1/GO0sB/egxYOzST2rnMkvVRjb6pJLpDtERJHdrod7aaq9u65L1U8uTOJS/x+1Lokxv2n4nb6g'
+    'Q1nXAQmqs2y/uo4OndxVAMvpMKjvujyMkfO0oFckvk1F401Td9GkGHZi58EWlPVemTinyKAcf9'
+    'dtPvdWWqyF8Tr6u9Vf1GolrL1JarkZDiw+rAteWFhXomlytUq66TUGpvNlMqwcPYxFU1rsceTL'
+    'jjVXCG65UyGJnhMuATzkAIn+9XY5n8YhGDjh85KjHcmYnT6vlSuQbf5jIY6FivVTLFWpStSXWa'
+    'XrpZhzfKN4CderlSz+XqxWp1uVDPzWdKcwVkV5dGL0ILKxV4NfxSLDGhTg6/JJR8pgbvxDF4Ol'
+    'deXMrkavXqcvatAvy/UCwV3D1YxdxiprhQL5ZyC8v5ghQPT9+uZ5drtXLJHZvIqidDwytxUiWX'
+    'KsXrmdzN0S08op4sZ6s5jNYuYOmuM/GxAzIeNWZQHCT8Ygm3I3I7y+2smqgUlsqVWr1SwFSa0I'
+    'rrxcKN+ly5jM4BcY5iO6GOLi1nF4paoNTlFTc2+5VHmcu7KbDEU6Rh5C9LMf4krg7Ri7Cott7Z'
+    'bPe3f1fVfkyNfYjfi8LmPxKvqHHtIoBeEvV2Qh0Psyo2ZsW8m7ipjviWOVXHnL0Ulj9SCRnmp2'
+    'wbbBG+qbh+hDJxS7nRt6B9z8HwqeYqxSXqpcVyPjpdjqrDpXLdngPQIUn1VHFxsZAvwnAMP4vN'
+    'Lj3KLKpnR/RJIOp0ZJ2a9vWzDp2j4F91EiR1VlcdEEmBehi5Bs++/Sgzr54ZKV365kK0xo3gYe'
+    'DE8QZ0NvFVN2pdvPXvr6r9YFI/4f6z4zrq32LjB+mvxAwY1HZ8+sxLZDXm7vR7653NdS9DiQsp'
+    'S/yaRy/5BpZ7SlGKTA18G4Kh8yX8WWK+vWw1n/YH2wi1JKHibHVKyuNbvc2ucWglhJyD2NHKHA'
+    'TuVqu91V7rbeAOuBh7KCrMj53m+oNI9BW/pWba+q7aWhBIqaNEefeOJEu7JXw9CPNb+vierzTs'
+    'LKdjTfGr/B/tzwa+BcZrK5DnMYqaPmhiqQ+Y3xjFfQh+n6bfDt0OOK+qdCKG0d2T4FoVUJ79ML'
+    'oBVSZe6aJMIA/XOrosfm/giYW1RlmbZ72XMYSXT7+OQhdz1ifEBz8GhuoJd4+4ePvGfzhuSN8O'
+    'TkFOkpf165g5Gg0D2RJ0BI0QdNw4oScDcUjeQWqAtSfFxySzLLrpqzJ+34BCJKGmOVmS7KDkhY'
+    '8uxR8qxtP9N32VrFEst9bzbrUHq3zJwdZ+JrVftBAbTli4SfG9ChydlFXyfrYJRT6Yaw+Qtfs+'
+    'RrvesBCYYWytEXQqjyaBlTP9RY1ocL0XG8i1/DFz8aVLr1x+7ZWXv9KAUeRaZxMnya8IKA5Q0P'
+    'cLKHGgoOv3h+Zc4hR8k0xKJ4SaDl66f6d3V1AHmEkDb6+HU3Y7SNJrneH1QOlu4uUNFHY3bf4k'
+    'iVtcozt0KsS1Qzztd49blDhQngbXvmm2jc/AN5nkO155h3poBlBl+IRxIzq39OlJgJDYDHWCxR'
+    'hu+ZwJMYbCOgOMHbIocaAgXHpAGQfKUff35eyQKOMPx2GSnHGPu2+od8yW8VnEVE1mSO5pvMAy'
+    'aBOAVCcAAmKYMNsbwsZg1gLshDuYjMDiGG//nQ1xjCrjLHD8nEXBivFYl3doUqBEvoeB9+95tg'
+    'lhaxNLZ3HcO0fQ6Iy2HEVPX++gXF5l5eJQdZgz7hD9tc9N430EUS4OKxdNKhAJ1dxloFxx48lL'
+    '3hJh3HeasKSAtSv1bu5c7yUWjSOK7bK7zz2sZg0FFdcr7h73ZPIsbk7RCOYDfdb+Jvn4ZneT02'
+    'Mes7+FEvHrZyLUGFDxhKNqUR33VXjzcPJNr0TJLmjq8AjVAC50FCdP0ncJtx+jmexLQBEGHCk2'
+    'So0B9UkYoxcsasydRaz/5AmvaspvttbxTnyPLpjbRSC/+Pr+CBULOQi6pExUXJnehK4pQ9e8Tr'
+    'qdHVKj8SkiQGee23nlMZ3Et8/fpMvndUPBEZAFZoruWLJojQB2qoIhMOmb+r9IlcfsCqDaLO3/'
+    'vhGi4vjIu3vd577w+Hg6+j2UjCU8PeJJDJ5gLMFs5InjXoMvnkpOkEyNIEc1bqhUR74+MuJJDJ'
+    '7gxuyVyJOYO49fJE8H9SGIW7PbteFodqgQ2cXPD454ggUfdl2VNU9QoAvQl08l+Wws6EM9Znbo'
+    'Rau7GPBgwRr3we3+BTqADFPjQMVmv2lRHbdEXFzAi1Dte7DedYPkMwM75qHBLDYiLOCaVRpiwa'
+    'GCoyzg2lUiFr4ms8lxwd91mzBvFjzyHQ16/i3O0mu8cWsy9durbcSK22FIIwaK1nUO1bAPNNOs'
+    'oaDol4Hjc4+t6/Qm8jIB2oepMaAiCvmyRXXcG4QUkfGqhHV2p9m93Y60inK1+P4mgTNiXI4edh'
+    'wnhFg1qxEWHCl4MkKNAfVF94Jasqgx9yY19eoXYyGksDAQpT8kAGzWzSEBcE0ogL91LHLcfQ8Z'
+    'Tf7A4fpb4B6kaR9dUP0ejxG6O6qdYDwqoj3f25ugw6fZmFpvnb57Zzvd6rTSnfTt9iDdTHPERn'
+    '1zI02vpO06I21DG+E9So8QpsaAiuD4Vy3qHvdrFPx1ziuQZQXuNp0UCzaRwT3AwIMmYT7YZeLQ'
+    '/xrFVYWpMaCiKsxZ1DG3QX2I99s4lKmJEWUDmKW4K2rAGFBylHJgTuxKu2g8omoMddsYVNigbi'
+    'sJNea2YUZ+HWbkVU92iAiuKTwzLVdm1BScCWYgDpc2zMBjZgbGaAbeRl4eewbGZAbi1ycj1BhQ'
+    'Ear/PYvquB1Sb/PaNrbVK1vEpNcxk70+6l9vS4YrTqWlEfFD6ldKl/KPRKgxoKKWuyHUuNsFmd'
+    '4DmRbI6PCsTdvHF65lyuF47YJwnzXCjZNwN0jtPK5w4yLcjZBuiYtwN0i3/ItjkR13AK9eSP61'
+    '42VaLW8SPN5Bv7M6SF/vtO+mJd0YQ2bSKtZt3x2a32Bc9/p4M8wrihYk9N8uw/dudXwKSJchTl'
+    '+coyLkw3b/PKM+Bm4jH6Rx7Q3LaIy01BH2z0aoMaCed19UWxY15t6liNcV7wZicG9B8+jkbHNl'
+    'rbMqTUqxf9hke6HPmc3WO3LFlTgHzUMJADZ6ProMXRhSGOtm5QqI8Igivkvhs2Eq8oPRs3mzoN'
+    '2HAfZ+8vLOmw68WUR2KOcQ0bv0ejSx03zf+Ex6sbtvnGZ9YHqfnOaAMg6UhPseIXEJZfwvkHjM'
+    '/aq6bNbCB1D0XPIMmTu313or2vbvEwpraNybwvF29wMKrwooWNIB0CcBJQ6UE6BKA8o4UJ5xr5'
+    'HjKRR2PB+Ads2r183q+BEU/rHjziRfHBYdHzl55rCKx5jFHS4CH4W4Q2X9Edk9AcUBSsI9Z1Hi'
+    'QLngpozA0FH+CNw9YOSi9DT7yiDFbzpgU+bVUxaVGvIx0qfVRbPQfgsPu99OesZ23cmA1MeqGF'
+    'dDHx20SA6SdHotR5zkb3Gob0AaR9Jz7luGrbhm61uIKDAv3gMBG2ENryTTnn20IkrP9qZD+2Om'
+    'qj26BJu0F0kHJJWKXky/jds7Fy1SHEmX3ZfVa2YZ/Q6WlIIVO7ywfT4bmOTlO2E2xoCN7yAbT1'
+    'skB0nPgPIMSHEkoebMCmmv+10s6fXkxZ3Wgs/nB/PCfDfMz14u+IAAnTHJQdIp91WLFEfSFfeq'
+    'uk6L/fcxKuUnuOlxzUSl0O679yFvxrPeAA3ei6Dfjra9OQAFB/33HUrI4tKf+9w/QXb/1HH30K'
+    'iP8Q5HQHzZGAafcJjKmVGqrGMzty0CiYnq+iSI9tBmwidBtEdMlNcnHO1xzRgIP3Boz+9lL8/b'
+    'fTtWB6ZV25vEjXLR+JMWA+gG/SDMgMOF7xc0upioKiDh7t0lIcXwekfMPZrEAGCMdiVU6c9pLg'
+    '7mPwvXFuOLIrgjF5DiSMItuYKQ4u5D/OzPQeQw/jA6BGvSh09cJVWmjxV84olC5az6EXLrYTD+'
+    'mDSGJB32GhNt8hBDx5IWiTh4Toy3mGiTh6g1gKm4TUXt90PUckVKfUhBfe6PsNYf49WXN8JhR3'
+    'hONkpqO9pQR3SZwOWPHMp3+aYh4UCkmyBnYWHQVhTuse9uSR23C9CXSU5FyHSZBDMzvWaRHfev'
+    '6JoFjPt8j64tyPHdChk/IT8pUpGjPz4aIceQjLG5MxY55v4Nvvt88jktv8+vAdmjr5IRMhWGnX'
+    'lYum0PXkvSWcNioq2JlLBIDpKOSoqwmGhrICF+6DX646eOHLQlX/EyJghX/EA+D9tNDc2wGsIB'
+    '+FNUQ0dJDcVBDf0d8vIPWg3FWQ0FxMvGhJbLLKdHW1TWyaJMi/j40I0IbU9bNyLi4/aNCBPO9K'
+    '8J9XtRLDnappcwpsMRcK+JfWqsgM+zW+qohdKln2cVPdU4XZO3wSzeXKGDPsbqCqrZoAwXXBuh'
+    'ccXnlrI/iZ1k+3lqScOJ3Wivrb3d7d3tIrCa/9b3jqhxWHCfcC+5rvr5wfGD9Edi5mcHTR5fnc'
+    'TXSweZh8H/b/KKJzsRbPmq0AnqS69qhC/wd6e8HQ5Odz/P3BAm0ivMxDRB5BiYK0r0wl4DOszS'
+    'w0hZ6XTR1EO+NKZ4r69BjBSqGTNNUnzZAB0Mgu7U1w6CaMAgYRWakByTyqDZ6+2BjqJ8McKYH8'
+    '1IRvdO+m26bEsxhpQzYVVLTNHcXdU5MNZEqds1ihMSsAP1ra41O+smkn2YCby3EchCMyEZQQM+'
+    'VMDI/4oPnSQ0kvsMPplGzCM6dVvH0PtOcy0AJzcOn4VxppN2YaNKgnJkh27bY6vbC575Aj2r6G'
+    'YzFdXr+zrREcWLY16AbqtH9/EoO+o65h9gmQwwZwrYlbJVrPSRPmOwGRgsg3K20e/gwJKzrpCb'
+    '6tXmi1WvWr5Wu5GpFDz4vVQpXy/mC3kvexMeFrxceelmpTg3X/Pmywv5QqXqZUp5RGOrVYrZ5V'
+    'q5UlUGwQ2fIDJb4Q+WKoUqwbYVF5cWilBaAOaW8jhaqFiaS3lQglcq15S3UFws1uC9WjlF1Q5/'
+    'h7Bvi4UKxi7VMtniQrF2kyq8VqyVsLJr5Qpi+i9lKrVibnkhU/GWlitL5WrBw5bli9XcQqa4WM'
+    'hPQf1Qp1e4XijVvOp8ZmEh3FDlYVhVRUDnTDO9bAG4zGQXClgVtTNfrBRyNWxQ8CsHwgMGF1LK'
+    'o9AW+AXyKEBzMpWbKSm0WnhnGd6Ch14+s5iZg9ad+zypQMdgTNQicg2iwFiaWrG2XCt4c+Vyno'
+    'RdLVSuF3OF6hVvoVwlgS1XC8BIPlPLUNVQBogLnsPv7HK1SIKj0KnKMkXlnIdevgGSAS4z8G2e'
+    'JFwuYWtxrBTKlZtYLMqBeiDl3ZgvAL2CQiVpZVAMHGBlvwYVghChSUE7vVJhbqE4VyjlCvi4jM'
+    'XcKFYL56HDinhnA8tEMd/IQKXL1GrsKOBL8W9r6KaoP73iNS+Tv15EzuVtGAHVogwXEltuXmQu'
+    'oSAerCZPUygIAtFdoVCQF+Q3Uk/DrzckQIR/j9Np8xPgVjMEH/9G6gvw64LA9fFv/HXWgvY7a6'
+    'D9JuHXKYHr499IPWdBBvLv37I5egn+cJO/xJzShJcL85qP13UANi0FOhCi304TVBJ4UVu9js4y'
+    'iGpwkxAdCbY79D2p4W1MxgLGJ8YQaeSlULK2Hq9jlECW77NQnDelcJb7LRs9ChjSEex8bYATw0'
+    '8R9gMmh0d4S70qaZg3K1Ok5/U3Vr1ss38uYm5MkbVxXsJAfG+H5xHogLeqMITN2Xpw6tSgt+nC'
+    'KcuCXuRAIq9x/0FjKgjQuQTG3ZPGjPr719UQJC9Hc683/Q92sqVOqf2E/7oI72AwH975xOhWxO'
+    'DlP7J/7Iw2sw6ZD7Wp9doukKgjeLoS/HwM4+tnV9R+srd+jaFoX1pfX1pfX1pfX1pfX1pfX1pf'
+    '/4fWl7azThs7Cy2uabGz+Le2uLSd9YKxs85adtZZY2ehxbUkVh3/Hm19/WOKrK9fObIGJv8y5T'
+    'XMWtwIx//q/Knb6ysIOSWRB7Swpxh9IHyrkZ4gcNlUcyJKWZlqIcTBPAZqNG5FKjL3XCnrCeW2'
+    'FDMOuGs2SAE3VvT1X5NoQZuKJoIbCubL541WwxRrx3erIBUMvj210qAk4PQemhKMgqB1Juu87V'
+    'AyWeGAlkoTqqBCcbxNsGjxXLrdNyjAvPzKY4QmadGdff3KVISHNb+n7yzyHV225NpdWFdx+TmH'
+    'wM94snCXElWflq8X6WtomWyyUT5ZxSev9q1tylV2b8Bxbxvm3RTD/LLBG8ABKGx92kqhc6uzBu'
+    'tZKGYZpUUnzxrajPPFibCasDYFKCG689CwDGfV7dwyT/RbCM2DKZaVbbYTjDt30QiOMakmLdQG'
+    'u8MCQGt6s97MjP5rxb4224JHF4M/7+Gb5p6s/rGNL11SNvEbFJuqL7zidjcdxEumOpKGWTxp45'
+    'tjFUQ291LbNGK/oQhfv9OXzXGT1K09CPL90W46j3CBpUA58U1o7FEladrOQ8t/56Y/CJyNzFB+'
+    'c51LmmFvBOVfrEbKTLLR8zsmA6liBSD3ZXGoFnHIGX2jfRMpVge0UyKA0BxK8eQN5hQPAUaBAk'
+    'GJMM9B33ciNdyyXlAEg7WCSaXMeDSp3nlOa61Bg9DkhtY4UJ01TiKwLX2CQQkb6TU05YcGIpgG'
+    'XYYfonmG17RGNsqqT7eq1em3Oa1oL+SGMh5WJ1qsEqM1VLCeRv4AU5pb8iLIGIwQB0t0E/xhYd'
+    'cYskaVmYvSWPqo1ohuYmcYLAO2di0UAdsInUI9JGFEocbKSAfzda3d7EOjtSVNMxwkdkdHOHfI'
+    'VrUytne1/sH8tl1FNyyQRxZAGzwoGFy9HZplbjLTbQOdJgLTTfL9ZZqltHkwSsUuswova5n7NF'
+    '2svuwMq3nfjDqdT1rGrYaUGzT7t9s21AotR7d7kj8VJUlFtqbMPZgQ60YLs5NqolDNabhRyKP1'
+    'MQcEQTdIannqUDylG/Q28SxziudvgDeEwJS+Xll6ysAv8NE78yospMxawQn2/Ah7MEvN+s66c8'
+    'teZGi50ApkWCsFzeGbNkOyT1EMlVSHQw47mlKMdltBStX2PRjqKO1oDSwj7p8ANCLQG2okV9QZ'
+    'WllKGcNaErELQEs2omoysuwO9dhQSaaHeQSyJNQIUQzQIjDyMGsNuLO3df7gkDQsTtROshAkVL'
+    'OYY1Lq0JtSwIhFObwWhVfhUYvw6qz37sX3rWWKM6JKOx+jnpdGFj1jFU2S6gyJtsO2hZi376J9'
+    'O5FCM3d14n391SBiEKw8Hku7tT3lzYSaTzli2sHuA6pd2Wow8ZGoG2BG9TstydOj71vZ+k4Fg5'
+    'iQALWBTdOSBiaasy0dpIfGiSyYk/pUP2qsWJLbxJ3VAMaQV4Fua4SNoyMEtBEouybBYANHgrK4'
+    'dIb5CcyBUL6PlJFIU9eizN7tEHyWZssqzeI6hTZ9q6fEatHFcoN8owVbbKObCR0aQCGLp6dHL8'
+    'trlJ2jRho6VpEhS4fiFYcMHcvOCexrvy0C4MweXnurt7ap9USH13BMdCJ6zjhA4EcAa3j/UFtJ'
+    'CGlHkpNgyT4nbsZH4DOsiWrRIMeMqBLkdQ1GQaeve2jKZOGmYBFMNm4v0eixGNta77sjv7Ikov'
+    '/EPJKdSJdFxalTIeGlZDOMMs92PZgmuLF/C1mn1NoSHxsy32RFW+uFbtoFQ4hWeGUhEg2viYHJ'
+    'xcII0q7gsuSL6WLbvYgoSabYaFNnhKJSQQYsNiHu9Di/beC9kYFz2stBm2Ag9607BQTHSCYmOz'
+    'j0NyF28llIdGkRswbsPT88OM3cl15eytRy88Y6hcKWlmuhyexDeT743lSjj0hTg84qNOYcvkib'
+    'ujrSmTxb8qgQqJI58sUvJre5oN1mqMay4lib4RupoblJnnYks7mA/vBdE/ZrOIu3nVqcUsKvN6'
+    'e4Ip83b6FdbU5IjsikfXLAQRUP2GSbpjh6tBH66VVEH8WPZO8dYWklrAgHIQ5Nvb6uSm9FdqaD'
+    'IWTsZD+KOrXU79GldLP00KVdinXG3N2aunQH/coN+peSb9srkS6LPo0iV4Xu/9pFyvNmq0XZze'
+    'xSqTcE1CtqxTSE4wZNbkLFFshi0zR81bCht50o0slmZiL6BrVuIsQDj4iBRmfTQ8JsWPGwIM2u'
+    'azcV48uRehHpIhWtJuxOoMosd9t6ePqRLalBv42oxGYV6uGrvvd1nARN32DlyiJtprF4l9GRMQ'
+    'RBRuNJ8JEs/EXiB2bSoM4/LTtFelF69/KV4El1c0WXBDajBmSCl167ErFk9Fmm1dMcOLl7j1qd'
+    'iCWU+7u/bjFhfRWsgtJKPNzlo5RzE0GTJ1g9Q9+joj2P/OFStmIgEGV7Q9Rn0KOYNdGctkk7Ix'
+    'Yaqc9t6yxZNCfd0rS2L6wbX3oHUy8Wsvu4hXWxKhe8Zu2AyEYtw0sQQgJ6A41i6XpmoZivZypz'
+    'y7j/3wiWO2SJlBSttutgfGBk4FQAqP4rjAk8oqZNqOenGLT3bPIkX8W0tki5Q7R8AkzjPfxJGA'
+    'z50zDqK9b0KYa/ngiBIX9KwOf6zPp/AIQlirE=')))
+_INDEX = {
+    f.name: {
+      'descriptor': f,
+      'services': {s.name: s for s in f.service},
+    }
+    for f in FILE_DESCRIPTOR_SET.file
+}
+
+
+UsersServiceDescription = {
+  'file_descriptor_set': FILE_DESCRIPTOR_SET,
+  'file_descriptor': _INDEX[u'api/v3/api_proto/users.proto']['descriptor'],
+  'service_descriptor': _INDEX[u'api/v3/api_proto/users.proto']['services'][u'Users'],
+}
diff --git a/api/v3/api_routes.py b/api/v3/api_routes.py
new file mode 100644
index 0000000..b627941
--- /dev/null
+++ b/api/v3/api_routes.py
@@ -0,0 +1,42 @@
+#
+# See the pRPC spec here: https://godoc.org/github.com/luci/luci-go/grpc/prpc
+#
+# Each Servicer corresponds to a service defined in a .proto file in this
+# directory. Each method on that Servicer corresponds to one of the rpcs
+# defined on the service.
+#
+# All APIs are served under the /prpc/* path space. Each service gets its own
+# namespace under that, and each method is an individual endpoints. For example,
+# POST https://bugs.chromium.org/prpc/monorail.v3.Issues/GetIssue
+# would be a call to the api.v3.issues_servicer.IssuesServicer.GetIssue method.
+#
+# Note that this is not a RESTful API, although it is CRUDy. All requests are
+# POSTs, all methods take exactly one input, and all methods return exactly
+# one output.
+#
+# TODO(http://crbug.com/monorail/1703): Actually integrate the rpcexplorer.
+# You can use the API Explorer here: https://bugs.chromium.org/rpcexplorer
+
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+
+from api.v3 import issues_servicer
+from api.v3 import hotlists_servicer
+from api.v3 import frontend_servicer
+from api.v3 import projects_servicer
+from api.v3 import permissions_servicer
+from api.v3 import users_servicer
+
+
+def RegisterApiHandlers(prpc_server, services):
+  """Registers pRPC API services. And makes their routes
+  available in prpc_server.get_routes().
+  """
+
+  prpc_server.add_service(issues_servicer.IssuesServicer(services))
+  prpc_server.add_service(hotlists_servicer.HotlistsServicer(services))
+  prpc_server.add_service(projects_servicer.ProjectsServicer(services))
+  prpc_server.add_service(permissions_servicer.PermissionsServicer(services))
+  prpc_server.add_service(users_servicer.UsersServicer(services))
+  prpc_server.add_service(frontend_servicer.FrontendServicer(services))
diff --git a/api/v3/apps-script-client/IssueService.js b/api/v3/apps-script-client/IssueService.js
new file mode 100644
index 0000000..d1c6c9d
--- /dev/null
+++ b/api/v3/apps-script-client/IssueService.js
@@ -0,0 +1,908 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/* eslint-disable no-unused-vars */
+
+const COMMENT_TYPE_DESCRIPTION = 'DESCRIPTION';
+
+/**
+ * Fetches the issue from Monorail.
+ * @param {string} issueName The resource name of the issue.
+ * @return {Issue}
+ */
+function getIssue(issueName) {
+  const message = {'name': issueName};
+  const url = URL + 'monorail.v3.Issues/GetIssue';
+  return run_(url, message);
+}
+
+/**
+ * Fetches all the given issues from Monorail.
+ * @param {Array<string>} issueNames The resource names of the issues.
+ * @return {Array<Issue>}
+ */
+function batchGetIssues(issueNames) {
+  const message = {'names': issueNames};
+  const url = URL + 'monorail.v3.Issues/BatchGetIssues';
+  return run_(url, message);
+}
+
+/**
+ * Fetches all the ApprovalValues that belong to the given issue.
+ * @param {string} issueName The resource name of the issue.
+ * @return {Array<ApprovalValue>}
+ */
+function listApprovalValues(issueName) {
+  const message = {'parent': issueName};
+  const url = URL + 'monorail.v3.Issues/ListApprovalValues';
+  return run_(url, message);
+}
+
+/**
+ * Calls SearchIssues with the given parameters.
+ * @param {Array<string>} projectNames resource names of the projects to search.
+ * @param {string} query The query to use to search.
+ * @param {string} orderBy The issue fields to order issues by.
+ * @param {Number} pageSize The maximum issues to return.
+ * @param {string} pageToken The page token from the previous call.
+ * @return {Array<SearchIssuesResponse>}
+ */
+function searchIssuesPagination_(
+    projectNames, query, orderBy, pageSize, pageToken) {
+  const message = {
+    'projects': projectNames,
+    'query': query,
+    'orderBy': orderBy,
+    'pageToken': pageToken};
+  if (pageSize) {
+    message['pageSize'] = pageSize;
+  }
+  const url = URL + 'monorail.v3.Issues/SearchIssues';
+  return run_(url, message);
+}
+
+// TODO(crbug.com/monorail/7143): SearchIssues only accepts one project.
+/**
+ * Searches Monorail for issues using the given query.
+ * NOTE: We currently only accept `projectNames` with one and only one project.
+ * @param {Array<string>} projects Resource names of the projects to search
+ *     within.
+ * @param {string=} query The query to use to search.
+ * @param {string=} orderBy The issue fields to order issues by,
+ *    e.g. 'EstDays,Opened,-stars'
+ * @return {Array<Issue>}
+ */
+function searchIssues(projects, query, orderBy) {
+  const pageSize = 100;
+  let pageToken;
+
+  issues = [];
+
+  do {
+    const resp = searchIssuesPagination_(
+        projects, query, orderBy, pageSize, pageToken);
+    issues = issues.concat(resp.issues);
+    pageToken = resp.nextPageToken;
+  }
+  while (pageToken);
+
+  return issues;
+}
+
+/**
+ * Calls ListComments with the given parameters.
+ * @param {string} issueName Resource name of the issue.
+ * @param {string} filter The approval filter query.
+ * @param {Number} pageSize The maximum number of comments to return.
+ * @param {string} pageToken The page token from the previous request.
+ * @return {ListCommentsResponse}
+ */
+function listCommentsPagination_(issueName, filter, pageSize, pageToken) {
+  const message = {
+    'parent': issueName,
+    'pageToken': pageToken,
+    'filter': filter,
+  };
+  if (pageSize) {
+    message['pageSize'] = pageSize;
+  }
+  const url = URL + 'monorail.v3.Issues/ListComments';
+  return run_(url, message);
+}
+
+/**
+ * Returns all comments and previous/current descriptions of an issue.
+ * @param {string} issueName Resource name of the Issue.
+ * @param {string=} filter The filter query filtering out comments.
+ *     We only accept `approval = "<approvalDef resource name>""`.
+ *     e.g. 'approval = "projects/chromium/approvalDefs/34"'
+ * @return {Array<Comment>}
+ */
+function listComments(issueName, filter) {
+  let pageToken;
+
+  let comments = [];
+  do {
+    const resp = listCommentsPagination_(issueName, filter, '', pageToken);
+    comments = comments.concat(resp.comments);
+    pageToken = resp.nextPageToken;
+  }
+  while (pageToken);
+
+  return comments;
+}
+
+/**
+ * Gets the current description of an issue.
+ * @param {string} issueName Resource name of the Issue.
+ * @param {string=} filter The filter query filtering out comments.
+ *     We only accept `approval = "<approvalDef resource name>""`.
+ *     e.g. 'approval = "projects/chromium/approvalDefs/34"'
+ * @return {Comment}
+ */
+function getCurrentDescription(issueName, filter) {
+  const allComments = listComments(issueName, filter);
+  for (let i = (allComments.length - 1); i > -1; i--) {
+    if (allComments[i].type === COMMENT_TYPE_DESCRIPTION) {
+      return allComments[i];
+    }
+  }
+}
+
+/**
+ * Gets the first (non-description) comment of an issue.
+ * @param {string} issueName Resource name of the Issue.
+ * @param {string=} filter The filter query filtering out comments.
+ *     We only accept `approval = "<approvalDef resource name>""`.
+ *      e.g. 'approval = "projects/chromium/approvalDefs/34"'
+ * @return {Comment}
+ */
+function getFirstComment(issueName, filter) {
+  const allComments = listComments(issueName, filter);
+  for (let i = 0; i < allComments.length; i++) {
+    if (allComments[i].type !== COMMENT_TYPE_DESCRIPTION) {
+      return allComments[i];
+    }
+  }
+  return null;
+}
+
+/**
+ * Gets the last (non-description) comment of an issue.
+ * @param {string} issueName The resource name of the issue.
+ * @param {string=} filter The filter query filtering out comments.
+ *     We only accept `approval = "<approvalDef resource name>""`.
+ *     e.g. 'approval = "projects/chromium/approvalDefs/34"'
+ * @return {Issue}
+ */
+function getLastComment(issueName, filter) {
+  const allComments = listComments(issueName, filter);
+  for (let i = (allComments.length - 1); i > -1; i--) {
+    if (allComments[i].type != COMMENT_TYPE_DESCRIPTION) {
+      return allComments[i];
+    }
+  }
+  return null;
+}
+
+/**
+ * Checks if the given label exists in the issue.
+ * @param {Issue} issue The issue to search within for the label.
+ * @param {string} label The label to search for.
+ * @return {boolean}
+ */
+function hasLabel(issue, label) {
+  if (issue.labels) {
+    const testLabel = label.toLowerCase();
+    return issue.labels.some(({label}) => testLabel === label.toLowerCase());
+  }
+  return false;
+}
+
+/**
+ * Checks if the issue has any labels matching the given regex.
+ * @param {Issue} issue The issue to search within for matching labels.
+ * @param {string} regex The regex pattern to use to search for labels.
+ * @return {boolean}
+ */
+function hasLabelMatching(issue, regex) {
+  if (issue.labels) {
+    const re = new RegExp(regex, 'i');
+    return issue.labels.some(({label}) => re.test(label));
+  }
+  return false;
+}
+
+/**
+ * Returns all labels in the issue that match the given regex.
+ * @param {Issue} issue The issue to search within for matching labels.
+ * @param {string} regex The regex pattern to use to search for labels.
+ * @return {Array<string>}
+ */
+function getLabelsMatching(issue, regex) {
+  const labels = [];
+  if (issue.labels) {
+    const re = new RegExp(regex, 'i');
+    for (let i = 0; i < issue.labels.length; i++) {
+      if (re.test(issue.labels[i].label)) {
+        labels.push(issue.labels[i].label);
+      }
+    }
+  }
+  return labels;
+}
+
+/**
+ * Get the comment where the given label was added, if any.
+ * @param {string} issueName The resource name of the issue.
+ * @param {string} label The label that was remove.
+ * @return {Comment}
+ */
+function getLabelSetComment(issueName, label) {
+  const comments = listComments(issueName);
+  for (let i = 0; i < comments.length; i++) {
+    const comment = comments[i];
+    if (comment.amendments) {
+      for (let j = 0; j < comment.amendments.length; j++) {
+        const amendment = comment.amendments[j];
+        if (amendment['fieldName'] === 'Labels' &&
+            amendment['newOrDeltaValue'].toLowerCase() === (
+              label.toLocaleLowerCase())) {
+          return comment;
+        }
+      }
+    }
+  }
+  return null;
+}
+
+/**
+ * Get the comment where the given label was removed, if any.
+ * @param {string} issueName The resource name of the issue.
+ * @param {string} label The label that was remove.
+ * @return {Comment}
+ */
+function getLabelRemoveComment(issueName, label) {
+  const comments = listComments(issueName);
+  for (let i = 0; i < comments.length; i++) {
+    const comment = comments[i];
+    if (comment.amendments) {
+      for (let j = 0; j < comment.amendments.length; j++) {
+        const amendment = comment.amendments[j];
+        if (amendment['fieldName'] === 'Labels' &&
+            amendment[
+                'newOrDeltaValue'].toLowerCase() === (
+              '-' + label.toLocaleLowerCase())) {
+          return comment;
+        }
+      }
+    }
+  }
+  return null;
+}
+
+/**
+ * Updates the issue to have the given label added.
+ * This method does not call Monorail's API to save this change.
+ * Call saveChanges() to send all updates to Monorail.
+ * @param {Issue} issue The issue to update.
+ * @param {string} label The label to add.
+ */
+function addLabel(issue, label) {
+  if (hasLabel(issue, label)) return;
+  maybeCreateDelta_(issue);
+  // Add the label to the issue's delta.labelsAdd.
+  issue.delta.labelsAdd.push(label);
+  // Add the label to the issue.
+  issue.labels.push({label: label});
+  // 'labels' added to updateMask in saveChanges().
+}
+
+/**
+ * Updates the issue to have the given label removed from the issue.
+ * This method does not call Monorail's API to save this change.
+ * Call saveChanges() to send all updates to Monorail.
+ * @param {Issue} issue The issue to update.
+ * @param {string} label The label to remove.
+ */
+function removeLabel(issue, label) {
+  if (!hasLabel(issue, label)) return;
+  maybeCreateDelta_(issue);
+  // Add the label to the issue's delta.labelsRemove.
+  issue.delta.labelsRemove.push(label);
+  // Remove label from issue.
+  for (let i = 0; i < issue.labels.length; i++) {
+    if (issue.labels[i].label.toLowerCase() === label.toLowerCase()) {
+      issue.labels.splice(i, 1);
+      break;
+    }
+  }
+}
+
+/**
+ * Sets the owner of the given issue.
+ * This method does not call Monorail's API to save this change.
+ * Call saveChanges() to send all updates to Monorail.
+ * @param {Issue} issue Issue to change.
+ * @param {string} ownerName The resource name of the new owner,
+ *     e.g. 'users/chicken@email.com'
+*/
+function setOwner(issue, ownerName) {
+  maybeCreateDelta_(issue);
+  issue.owner = {'user': ownerName};
+  if (issue.delta.updateMask.indexOf('owner.user') === -1) {
+    issue.delta.updateMask.push('owner.user');
+  }
+}
+
+/**
+ * Sets the summary of the given issue.
+ * This method does not call Monorail's API to save this change.
+ * Call saveChanges() to send all updates to Monorail.
+ * @param {Issue} issue Issue to change.
+ * @param {string} summary The new summary of the issue.
+*/
+function setSummary(issue, summary) {
+  maybeCreateDelta_(issue);
+  issue.summary = summary;
+  if (issue.delta.updateMask.indexOf('summary') === -1) {
+    issue.delta.updateMask.push('summary');
+  }
+}
+
+/**
+ *Sets the status of the given issue.
+ * This method does not call Monorail's API to save this change.
+ * Call saveChanges() to send all updates to Monorail.
+ * @param {Issue} issue Issue to change.
+ * @param {string} status The new status of the issue e.g. 'Available'.
+*/
+function setStatus(issue, status) {
+  maybeCreateDelta_(issue);
+  issue.status.status = status;
+  if (issue.delta.updateMask.indexOf('status.status') === -1) {
+    issue.delta.updateMask.push('status.status');
+  }
+}
+
+/**
+ * Sets the merged into issue for the given issue.
+ * This method does not call Monorail's API to save this change.
+ * Call saveChanges() to send all updates to Monorail.
+ * @param {Issue} issue Issue to change.
+ * @param {IssueRef} mergedIntoRef IssueRef of the issue to merge into.
+ */
+function setMergedInto(issue, mergedIntoRef) {
+  maybeCreateDelta_(issue);
+  issue.mergedIntoIssueRef = mergedIntoRef;
+  if (issue.delta.updateMask.indexOf('mergedIntoIssueRef') === -1) {
+    issue.delta.updateMask.push('mergedIntoIssueRef');
+  }
+}
+
+/**
+ * Checks if target is found in source.
+ * @param {IssueRef} target The IssueRef to look for.
+ * @param {Array<IssueRef>} source the IssueRefs to look in.
+ * @return {number} index of target in source, -1 if not found.
+ */
+function issueRefExists_(target, source) {
+  for (let i = 0; i < source.length; i++) {
+    if ((source[i].issue === target.issue || (!source[i].issue && !target.issue)
+    ) && (source[i].extIdentifier === target.extIdentifier || (
+      !source[i].extIdentifier && !target.extIdentifier))) {
+      return i;
+    }
+  }
+  return -1;
+}
+
+/**
+ * Makes blocking issue ref changes.
+ * blockingIssuesAdd are added before blockingIssuesRemove are removed.
+ * This method does not call Monorail's API to save this change.
+ * Call saveChanges() to send all updates to Monorail.
+ * @param {Issue} issue Issue to change.
+ * @param {Array<IssueRef>} blockingIssuesAdd issues to add as blocking issues.
+ * @param {Array<IssueRef>} blockingIssuesRemove issues to remove from blocking
+ *     issues.
+ */
+function addBlockingIssueChanges(
+    issue, blockingIssuesAdd, blockingIssuesRemove) {
+  maybeCreateDelta_(issue);
+  blockingIssuesAdd.forEach((addRef) => {
+    const iInIssue = issueRefExists_(addRef, issue.blockingIssueRefs);
+    if (iInIssue === -1) { // addRef not found in issue
+      issue.blockingIssueRefs.push(addRef);
+      issue.delta.blockingAdd.push(addRef);
+      const iInDeltaRemove = issueRefExists_(
+          addRef, issue.delta.blockingRemove);
+      if (iInDeltaRemove != -1) {
+        // Remove addRef from blckingRemove that may have been added earlier.
+        issue.delta.blockingRemove.splice(iInDeltaRemove, 1);
+      }
+      // issue.delta.updateMask is updated in saveChanges()
+    }
+  });
+  // Add blockingIssuesAdd to issue and issue.delta.blockingAdd if not in
+  // issue.blockingIssues
+  blockingIssuesRemove.forEach((removeRef) => {
+    const iInIssue = issueRefExists_(removeRef, issue.blockingIssueRefs);
+    if (iInIssue > -1) {
+      issue.blockingIssueRefs.splice(iInIssue, 1);
+      issue.delta.blockingRemove.push(removeRef);
+      const iInDeltaAdd = issueRefExists_(removeRef, issue.delta.blockingAdd);
+      if (iInDeltaAdd != -1) {
+        issue.delta.blockingAdd.splice(iInDeltaAdd, 1);
+      }
+    }
+  });
+}
+
+/**
+ * Makes blocked-on issue ref changes.
+ * blockedOnIssuesAdd are added before blockedOnIssuesRemove are removed.
+ * This method does not call Monorail's API to save this change.
+ * Call saveChanges() to send all updates to Monorail.
+ * @param {Issue} issue Issue to change.
+ * @param {Array<IssueRef>} blockedOnIssuesAdd issues to add as blockedon
+ *     issues.
+ * @param {Array<IssueRef>} blockedOnIssuesRemove issues to remove from
+ *     blockedon issues.
+ */
+function addBlockedOnIssueChanges(
+    issue, blockedOnIssuesAdd, blockedOnIssuesRemove) {
+  maybeCreateDelta_(issue);
+  blockedOnIssuesAdd.forEach((addRef) => {
+    const iInIssue = issueRefExists_(addRef, issue.blockedOnIssueRefs);
+    if (iInIssue === -1) { // addRef not found in issue
+      issue.blockedOnIssueRefs.push(addRef);
+      issue.delta.blockedOnAdd.push(addRef);
+      const iInDeltaRemove = issueRefExists_(
+          addRef, issue.delta.blockedOnRemove);
+      if (iInDeltaRemove != -1) {
+        // Remove addRef from blckingRemove that may have been added earlier.
+        issue.delta.blockedOnRemove.splice(iInDeltaRemove, 1);
+      }
+      // issue.delta.updateMask is updated in saveChanges()
+    }
+  });
+  // Add blockedOnIssuesAdd to issue and issue.delta.blockedOnAdd if not in
+  // issue.blockedOnIssues.
+  blockedOnIssuesRemove.forEach((removeRef) => {
+    const iInIssue = issueRefExists_(removeRef, issue.blockedOnIssueRefs);
+    if (iInIssue > -1) {
+      issue.blockedOnIssueRefs.splice(iInIssue, 1);
+      issue.delta.blockedOnRemove.push(removeRef);
+      const iInDeltaAdd = issueRefExists_(removeRef, issue.delta.blockedOnAdd);
+      if (iInDeltaAdd != -1) {
+        issue.delta.blockedOnAdd.splice(iInDeltaAdd, 1);
+      }
+    }
+  });
+}
+
+
+/**
+ * Looks for a component name in an Array of ComponentValues.
+ * @param {string} compName Resource name of the Component to look for.
+ * @param {Array<ComponentValue>} compArray List of ComponentValues.
+ * @return {number} Index of compName in compArray, -1 if not found.
+ */
+function componentExists_(compName, compArray) {
+  for (let i = 0; i < compArray.length; i++) {
+    if (compArray[i].component === compName) {
+      return i;
+    }
+  }
+  return -1;
+}
+
+/**
+ * Adds the component changes to the issue.
+ * componentNamesAdd are added before componentNamesremove are removed.
+ * This method does not call Monorail's API to save this change.
+ * Call saveChanges() to send all updates to Monorail.
+ * @param {Issue} issue Issue to change.
+ * @param {Array<string>} componentNamesAdd Array of component resource names.
+ * @param {Array<string>} componentNamesRemove Array or component resource
+ *     names.
+
+*/
+function addComponentChanges(issue, componentNamesAdd, componentNamesRemove) {
+  maybeCreateDelta_(issue);
+  componentNamesAdd.forEach((compName) => {
+    const iInIssue = componentExists_(compName, issue.components);
+    if (iInIssue === -1) { // compName is not in issue.
+      issue.components.push({'component': compName});
+      issue.delta.componentsAdd.push(compName);
+      const iInDeltaRemove = issue.delta.componentsRemove.indexOf(compName);
+      if (iInDeltaRemove != -1) {
+        // Remove compName from issue.delta.componentsRemove that may have been
+        // added before.
+        issue.delta.componentsRemove.splice(iInDeltaRemove, 1);
+      }
+      // issue.delta.updateMask is updated in saveChanges()
+    }
+  });
+
+  componentNamesRemove.forEach((compName) => {
+    const iInIssue = componentExists_(compName, issue.components);
+    if (iInIssue != -1) { // compName was found in issue.
+      issue.components.splice(iInIssue, 1);
+      issue.delta.componentsRemove.push(compName);
+      const iInDeltaAdd = issue.delta.componentsAdd.indexOf(compName);
+      if (iInDeltaAdd != -1) {
+        // Remove compName from issue.delta.componentsAdd that may have been
+        // added before.
+        issue.delta.componentsAdd.splice(iInDeltaAdd, 1);
+      }
+    }
+  });
+}
+
+/**
+ * Checks if the fieldVal is found in fieldValsArray
+ * @param {FieldValue} fieldVal the field to look for.
+ * @param {Array<FieldValue>} fieldValsArray the Array to look within.
+ * @return {number} the index of fieldVal in fieldValsArray, or -1 if not found.
+ */
+function fieldValueExists_(fieldVal, fieldValsArray) {
+  for (let i = 0; i < fieldValsArray.length; i++) {
+    const currFv = fieldValsArray[i];
+    if (currFv.field === fieldVal.field && currFv.value === fieldVal.value && (
+      currFv.phase === fieldVal.phase || (
+        !currFv.phase && !fieldVal.phase))) {
+      return i;
+    }
+  }
+  return -1;
+}
+
+/**
+ * Adds the FieldValue changes to the issue.
+ * fieldValuesAdd are added before fieldValuesRemove are removed.
+ * This method does not call Monorail's API to save this change.
+ * Call saveChanges() to send all updates to Monorail.
+ * @param {Issue} issue Issue to change.
+ * @param {Array<FieldValue>} fieldValuesAdd Array of FieldValues to add.
+ * @param {Array<FieldValue>} fieldValuesRemove Array of FieldValues to remove.
+*/
+function addFieldValueChanges(issue, fieldValuesAdd, fieldValuesRemove) {
+  maybeCreateDelta_(issue);
+  fieldValuesAdd.forEach((fvAdd) => {
+    const iInIssue = fieldValueExists_(fvAdd, issue.fieldValues);
+    if (iInIssue === -1) { // fvAdd is not already in issue, so we can add it.
+      issue.fieldValues.push(fvAdd);
+      issue.delta.fieldValuesAdd.push(fvAdd);
+      const iInDeltaRemove = fieldValueExists_(
+          fvAdd, issue.delta.fieldValuesRemove);
+      if (iInDeltaRemove != -1) {
+        // fvAdd was added to fieldValuesRemove in a previous call.
+        issue.delta.fieldValuesRemove.splice(iInDeltaRemove, 1);
+      }
+      // issue.delta.updateMask is updated in saveChanges()
+    }
+  });
+  // issue.delta.updateMask is updated in saveChanges()
+  fieldValuesRemove.forEach((fvRemove) => {
+    const iInIssue = fieldValueExists_(fvRemove, issue.fieldValues);
+    if (iInIssue != -1) { // fvRemove is in issue, so we can remove it.
+      issue.fieldValues.splice(iInIssue, 1);
+      issue.delta.fieldValuesRemove.push(fvRemove);
+      const iInDeltaAdd = fieldValueExists_(
+          fvRemove, issue.delta.fieldValuesAdd);
+      if (iInDeltaAdd != -1) {
+        // fvRemove was added to fieldValuesAdd in a previous call.
+        issue.delta.fieldValuesAdd.splice(iInDeltaAdd, 1);
+      }
+    }
+  });
+}
+
+/**
+ * Checks for the existence of userName in userValues
+ * @param {string} userName A user resource name to look for.
+ * @param {Array<UserValue>} userValues UserValues to search through.
+ * @return {number} Index of userName's UserValue in userValues or -1 if not
+ *     found.
+ */
+function userValueExists_(userName, userValues) {
+  for (let i = 0; i< userValues.length; i++) {
+    if (userValues[i].user === userName) {
+      return i;
+    }
+  }
+  return -1;
+}
+
+/**
+ * Adds the CC changes to the issue.
+ * ccNamesAdd are added before ccNamesRemove are removed.
+ * This method does not call Monorail's API to save this change.
+ * Call saveChanges() to send all updates to Monorail.
+ * @param {Issue} issue Issue to change.
+ * @param {Array<string>} ccNamesAdd Array if user resource names.
+ * @param {Array<string>} ccNamesRemove Array if user resource names.
+*/
+function addCcChanges(issue, ccNamesAdd, ccNamesRemove) {
+  maybeCreateDelta_(issue);
+  ccNamesAdd.forEach((ccName) => {
+    const iInIssue = userValueExists_(ccName, issue.ccUsers);
+    if (iInIssue === -1) { // User is not in issue, so we can add them.
+      issue.ccUsers.push({'user': ccName});
+      issue.delta.ccsAdd.push(ccName);
+      const iInDeltaRemove = issue.delta.ccsRemove.indexOf(ccName);
+      if (iInDeltaRemove != -1) {
+        // ccName was added to ccsRemove in a previous call.
+        issue.delta.ccsRemove.splice(iInDeltaRemove, 1);
+      }
+    }
+  });
+  ccNamesRemove.forEach((ccName) => {
+    const iInIssue = userValueExists_(ccName, issue.ccUsers);
+    if (iInIssue != -1) { // User is in issue, so we can remove it.
+      issue.ccUsers.splice(iInIssue, 1);
+      issue.delta.ccsRemove.push(ccName);
+      const iInDeltaAdd = issue.delta.ccsAdd.indexOf(ccName);
+      if (iInDeltaAdd != -1) {
+        // ccName was added to delta.ccsAdd in a previous all.
+        issue.delta.ccsAdd.splice(iInDeltaAdd, 1);
+      }
+    }
+  });
+}
+
+/**
+ * Set the pending comment of the issue.
+ * @param {Issue} issue Issue whose comment we want to set.
+ * @param {string} comment Comment that we want for the issue.
+ */
+function setComment(issue, comment) {
+  maybeCreateDelta_(issue);
+  issue.delta.comment = comment;
+}
+
+/**
+ * Get the pending comment for the issue.
+ * @param {Issue} issue Issue whose comment we want.
+ * @return {string}
+ */
+function getPendingComment(issue) {
+  if (issue.delta) {
+    return issue.delta.comment;
+  }
+  return '';
+}
+
+/**
+ * Adds to the existing pending comment
+ * @param {Issue} issue Issue to update.
+ * @param {string} comment The comment string to add to the existing one.
+ */
+function appendComment(issue, comment) {
+  maybeCreateDelta_(issue);
+  issue.delta.comment = issue.delta.comment.concat(comment);
+}
+
+/**
+ * Sets up an issue for pending changes.
+ * @param {Issue} issue The issue that needs to be updated.
+ */
+function maybeCreateDelta_(issue) {
+  if (!issue.delta) {
+    issue.delta = newIssueDelta_();
+    if (!issue.components) {
+      issue.components = [];
+    };
+    if (!issue.blockingIssueRefs) {
+      issue.blockingIssueRefs = [];
+    }
+    if (!issue.blockedOnIssueRefs) {
+      issue.blockedOnIssueRefs = [];
+    }
+    if (!issue.ccUsers) {
+      issue.ccUsers = [];
+    }
+    if (!issue.labels) {
+      issue.labels = [];
+    }
+    if (!issue.fieldValues) {
+      issue.fieldValues = [];
+    }
+  }
+}
+
+/**
+ * Creates an IssueDelta
+ * @return {IssueDelta_}
+ */
+function newIssueDelta_() {
+  return new IssueDelta_();
+}
+
+/** Used to track pending changes to an issue.*/
+function IssueDelta_() {
+  /** Array<string> */ this.updateMask = [];
+
+  // User resource names.
+  /** Array<string> */ this.ccsRemove = [];
+  /** Array<string> */ this.ccsAdd = [];
+
+  /** Array<IssueRef> */ this.blockedOnRemove = [];
+  /** Array<IssueRef> */ this.blockedOnAdd = [];
+  /** Array<IssueRef> */ this.blockingRemove = [];
+  /** Array<IssueRef> */ this.blockingAdd = [];
+
+  // Component resource names.
+  /** Array<string> */ this.componentsRemove = [];
+  /** Array<string> */ this.componentsAdd = [];
+
+  // Label values, e.g. 'Security-Notify'.
+  /** Array<string> */ this.labelsRemove = [];
+  /** Array<string> */ this.labelsAdd = [];
+
+  /** Array<FieldValue> */ this.fieldValuesRemove = [];
+  /** Array<FieldValue> */ this.fieldValuesAdd = [];
+
+  this.comment = '';
+}
+
+/**
+ * Calls Monorail's API to update the issue.
+ * @param {Issue} issue The issue to update where issue['delta'] is expected
+ *     to exist.
+ * @param {boolean} sendEmail True if the update should trigger email
+ *     notifications.
+ * @return {Issue}
+ */
+function saveChanges(issue, sendEmail) {
+  if (!issue.delta) {
+    throw new Error('No pending changes for issue.');
+  }
+
+  const modifyDelta = {
+    'ccsRemove': issue.delta.ccsRemove,
+    'blockedOnIssuesRemove': issue.delta.blockedOnRemove,
+    'blockingIssuesRemove': issue.delta.blockingRemove,
+    'componentsRemove': issue.delta.componentsRemove,
+    'labelsRemove': issue.delta.labelsRemove,
+    'fieldValsRemove': issue.delta.fieldValuesRemove,
+    'issue': {
+      'name': issue.name,
+      'fieldValues': issue.delta.fieldValuesAdd,
+      'blockedOnIssueRefs': issue.delta.blockedOnAdd,
+      'blockingIssueRefs': issue.delta.blockingAdd,
+      'mergedIntoIssueRef': issue.mergedIntoIssueRef,
+      'summary': issue.summary,
+      'status': issue.status,
+      'owner': issue.owner,
+      'labels': [],
+      'ccUsers': [],
+      'components': [],
+    },
+  };
+
+  if (issue.delta.fieldValuesAdd.length > 0) {
+    issue.delta.updateMask.push('fieldValues');
+  }
+
+  if (issue.delta.blockedOnAdd.length > 0) {
+    issue.delta.updateMask.push('blockedOnIssueRefs');
+  }
+
+  if (issue.delta.blockingAdd.length > 0) {
+    issue.delta.updateMask.push('blockingIssueRefs');
+  }
+
+  if (issue.delta.ccsAdd.length > 0) {
+    issue.delta.updateMask.push('ccUsers');
+  }
+  issue.delta.ccsAdd.forEach((userResourceName) => {
+    modifyDelta.issue['ccUsers'].push({'user': userResourceName});
+  });
+
+  if (issue.delta.labelsAdd.length > 0) {
+    issue.delta.updateMask.push('labels');
+  }
+  issue.delta.labelsAdd.forEach((label) => {
+    modifyDelta.issue['labels'].push({'label': label});
+  });
+
+  if (issue.delta.componentsAdd.length > 0) {
+    issue.delta.updateMask.push('components');
+  }
+  issue.delta.componentsAdd.forEach((compResourceName) => {
+    modifyDelta.issue['components'].push({'component': compResourceName});
+  });
+
+  modifyDelta['updateMask'] = issue.delta.updateMask.join();
+
+  const message = {
+    'deltas': [modifyDelta],
+    'notifyType': sendEmail ? 'EMAIL' : 'NO_NOTIFICATION',
+    'commentContent': issue.delta.comment,
+  };
+
+  const url = URL + 'monorail.v3.Issues/ModifyIssues';
+  response = run_(url, message);
+  if (!response.issues) {
+    Logger.log('All changes Noop');
+    return null;
+  }
+  issue = response.issues[0];
+  return issue;
+}
+
+/**
+ * Creates an Issue.
+ * @param {string} projectName: Resource name of the parent project.
+ * @param {string} summary: Summary of the issue.
+ * @param {string} description: Description of the issue.
+ * @param {string} status: Status of the issue, e.g. "Untriaged".
+ * @param {boolean} sendEmail: True if this should trigger email notifications.
+ * @param {string=} ownerName: Resource name of the issue owner.
+ * @param {Array<string>=} ccNames: Resource names of the users to cc.
+ * @param {Array<string>=} labels: Labels to add to the issue,
+ *     e.g. "Restict-View-Google".
+ * @param {Array<string>=} componentNames: Resource names of components to add.
+ * @param {Array<FieldValue>=} fieldValues: FieldValues to add to the issue.
+ * @param {Array<IssueRef>=} blockedOnRefs: IssueRefs for blocked on issues.
+ * @param {Array<IssueRef>=} blockingRefs: IssueRefs for blocking issues.
+ * @return {Issue}
+ */
+function makeIssue(
+    projectName, summary, description, status, sendEmail, ownerName, ccNames,
+    labels, componentNames, fieldValues, blockedOnRefs, blockingRefs) {
+  const issue = {
+    'summary': summary,
+    'status': {'status': status},
+    'ccUsers': [],
+    'components': [],
+    'labels': [],
+  };
+
+  if (ownerName) {
+    issue['owner'] = {'user': ownerName};
+  }
+
+  if (ccNames) {
+    ccNames.forEach((ccName) => {
+      issue['ccUsers'].push({'user': ccName});
+    });
+  };
+
+  if (labels) {
+    labels.forEach((label) => {
+      issue['labels'].push({'label': label});
+    });
+  };
+
+  if (componentNames) {
+    componentNames.forEach((componentName) => {
+      issue['components'].push({'component': componentName});
+    });
+  };
+
+  if (fieldValues) {
+    issue['fieldValues'] = fieldValues;
+  };
+
+  if (blockedOnRefs) {
+    issue['blockedOnIssueRefs'] = blockedOnRefs;
+  };
+
+  if (blockingRefs) {
+    issue['blockingIssueRefs'] = blockingRefs;
+  };
+
+  const message = {
+    'parent': projectName,
+    'issue': issue,
+    'description': description,
+    'notifyType': sendEmail ? 'EMAIL': 'NO_NOTIFICATION',
+  };
+  const url = URL + 'monorail.v3.Issues/MakeIssue';
+  return run_(url, message);
+}
diff --git a/api/v3/apps-script-client/ProjectService.js b/api/v3/apps-script-client/ProjectService.js
new file mode 100644
index 0000000..1487a53
--- /dev/null
+++ b/api/v3/apps-script-client/ProjectService.js
@@ -0,0 +1,52 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/* eslint-disable no-unused-vars */
+
+/**
+ * Creates a ComponentDef.
+ * @param {string} projectName The resource name of the parent project.
+ * @param {string} value The name of the component
+ *     e.g. "Triage" or "Triage>Security".
+ * @param {string=} docstring Short description of the ComponentDef.
+ * @param {Array<string>=} admins Array of User resource names to set as admins.
+ * @param {Array<string>=} ccs Array of User resources names to set as auto-ccs.
+ * @param {Array<string>=} labels Array of labels.
+ * @return {ComponentDef}
+ */
+function createComponentDef(
+    projectName, value, docstring, admins, ccs, labels) {
+  const componentDef = {
+    'value': value,
+    'docstring': docstring,
+  };
+  if (admins) {
+    componentDef['admins'] = admins;
+  }
+  if (ccs) {
+    componentDef['ccs'] = ccs;
+  }
+  if (labels) {
+    componentDef['labels'] = labels;
+  }
+  const message = {
+    'parent': projectName,
+    'componentDef': componentDef,
+  };
+  const url = URL + 'monorail.v3.Projects/CreateComponentDef';
+  return run_(url, message);
+}
+
+/**
+ * Deletes a ComponentDef.
+ * @param {string} componentName Resource name of the ComponentDef to delete.
+ * @return {EmptyProto}
+ */
+function deleteComponentDef(componentName) {
+  const message = {
+    'name': componentName,
+  };
+  const url = URL + 'monorail.v3.Projects/DeleteComponentDef';
+  return run_(url, message);
+}
diff --git a/api/v3/apps-script-client/README.md b/api/v3/apps-script-client/README.md
new file mode 100644
index 0000000..a15a5f9
--- /dev/null
+++ b/api/v3/apps-script-client/README.md
@@ -0,0 +1,8 @@
+## This directory contains code that make up our v3 Apps Script client library.
+
+client.js is purposely omitted.
+
+To make updates to the library:
+1) Update the code here and send in a CL for review.
+2) Merge the Cl and copy-paste the changes into Apps Script at go/monorail-v3-apps-script.
+3) Create a new static version in Apps Script and update the labeled version 'latest' to point to the new static version.
diff --git a/api/v3/apps-script-client/UserService.js b/api/v3/apps-script-client/UserService.js
new file mode 100644
index 0000000..6402db5
--- /dev/null
+++ b/api/v3/apps-script-client/UserService.js
@@ -0,0 +1,27 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/* eslint-disable no-unused-vars */
+
+/**
+ * Fetches the user from Monorai.
+ * @param {string} userName The resource name of the user.
+ * @return {User}
+ */
+function getUser(userName) {
+  const message = {'name': userName};
+  const url = URL + 'monorail.v3.Users/GetUser';
+  return run_(url, message);
+}
+
+/**
+ * Fetches the users from Monorail.
+ * @param {Array<string>} userNames The resource names of the users.
+ * @return {Array<User>}
+ */
+function batchGetUsers(userNames) {
+  const message = {'names': userNames};
+  const url = URL + 'monorail.v3.Users/BatchGetUsers';
+  return run_(url, message);
+}
diff --git a/api/v3/apps-script-client/helpers.js b/api/v3/apps-script-client/helpers.js
new file mode 100644
index 0000000..05ee920
--- /dev/null
+++ b/api/v3/apps-script-client/helpers.js
@@ -0,0 +1,82 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/* eslint-disable no-unused-vars */
+
+/**
+ * Returns the user's resource name.
+ * @param {string|number} user The user's email or user_id
+ * @return {string}
+ */
+function computeUserName(user) {
+  return `users/${user}`;
+}
+
+/**
+ * Returns the users' resource names.
+ * @param {Array<string|number>} users Array of user emails/user_ids.
+ * @return {Array<string>}
+ */
+function computeUserNames(users) {
+  const userNames = [];
+  users.forEach((user) => {
+    userNames.push(computeUserName(user));
+  });
+  return userNames;
+}
+
+
+/**
+ * Returns the issue's resource name.
+ * @param {string} project The name of the project the issue belongs to,
+ *     e.g. 'chromium'.
+ * @param {number} id The issue's id.
+ * @return {string}
+ */
+function computeIssueName(project, id) {
+  return `projects/${project}/issues/${id}`;
+}
+
+/**
+ * Returns the project's resource name.
+ * @param {string} project The display name of the project, e.g. 'chromium'.
+ * @return {string}
+ */
+function computeProjectName(project) {
+  return `projects/${project}`;
+}
+
+/**
+ * Returns the projects' resource names in the same order.
+ * @param {Array<string>} projects The display names of the projects,
+ *     e.g. 'chromium'.
+ * @return {Array<string>}
+ */
+function computeProjectNames(projects) {
+  const projectNames = [];
+  projects.forEach((project) => {
+    projectNames.push(computeProjectName(project));
+  });
+  return projectNames;
+}
+
+/**
+ * Returns the FieldDef's resource name.
+ * @param {string} project The display name of the project, e.g. 'chromium'.
+ * @param {number} fieldId ID of the FieldDef.
+ * @return {string}
+ */
+function computeFieldDefName(project, fieldId) {
+  return `projects/${project}/fieldDefs/${fieldId}`;
+}
+
+/**
+ * Returns the ComponentDef's resource name.
+ * @param {string} project The display name of the project, e.g. 'chromium'.
+ * @param {number|string} componentIdOrPath ID or value of the ComponentDef.
+ * @return {string}
+*/
+function computeComponentDefName(project, componentIdOrPath) {
+  return `projects/${project}/componentDefs/${componentIdOrPath}`;
+}
diff --git a/api/v3/apps-script-client/types.js b/api/v3/apps-script-client/types.js
new file mode 100644
index 0000000..cafba21
--- /dev/null
+++ b/api/v3/apps-script-client/types.js
@@ -0,0 +1,75 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/* eslint-disable max-len */
+
+/**
+ * The label of an issue.
+ * @typedef {Object} LabelValue
+ * @property {string} label - the string label. e.g. 'Target-99'.
+ * @property {string} derivation - How the label was derived. One of 'EXPLICIT', 'RULE'.
+ */
+
+/**
+ * A user involved in an issue.
+ * @typedef {Object} UserValue
+ * @property {string} user - The User resource name.
+ * @property {string} derivation - How the user was derived. One of 'EXPLICIT', 'RULE'.
+ */
+
+/**
+ * A component involved in an issue.
+ * @typedef {Object} ComponentValue
+ * @property {string} component - The ComponentDef resource name.
+ * @property {string} derivation - How the component was derived. One of 'EXPLICIT', 'RULE'.
+ */
+
+/**
+ * A field involved in an issue.
+ * @typedef {Object} FieldValue
+ * @property {string} field - The FieldDef resource name.
+ * @property {string} value - The value associated with the field.
+ * @property {string} derivation - How the value was derived. One of 'EXPLICIT', 'RULE'.
+ * @property {string} phase - The phase of an issue that this value belongs to, if any.
+ */
+
+/**
+ * The status of an issue.
+ * @typedef {Object} StatusValue
+ * @property {string} status - The status. e.g. 'Available'.
+ * @property {string} derivation - How the status was derived. One of 'EXPLICIT', 'RULE'.
+ */
+
+/**
+ * A reference to monorail or external issue.
+ * @typedef {Object} IssueRef
+ * @property {string} [issue] - The resource name of the issue.
+ * @property {string} [extIdentifier] - The identifier of an external issue e.g 'b/123'.
+ */
+
+/**
+ * An Issue.
+ * @typedef {Object} Issue
+ * @property {string} name - The resource name of the issue.
+ * @property {string} summary - The issue summary.
+ * @property {string} state - The current state of the issue. One of 'ACTIVE', 'DELETED', 'SPAM'.
+ * @property {string} reporter - The User resource name of the issue reporter.
+ * @property {UserValue} owner - The issue's owner.
+ * @property {StatusValue} status - The issue status.
+ * @property {IssueRef} mergedIntoIssueRef - The issue this issue is merged into.
+ * @property {Array<IssueRef>} blockedOnIssueRefs - TODO
+ * @property {Array<IssueRef>} blockingIssueRefs - TODO
+ * @property {Array<LabelValue>} labels - The labels of the issue.
+ * @property {Array<FieldValue>} fieldValues - TODO
+ * @property {Array<UserValue>} ccUsers - The users cc'd to this issue.
+ * @property {Array<ComponentValue>} components - The Components added to the issue.
+ * @property {Number} attachmentCount - The number of attachments this issue holds.
+ * @property {Number} starCount - The number of stars this issue has.
+ * @property {Array<FieldValue>} fieldValues - The field values of the issue.
+ * @property {Array<string>} phases - The names of all Phases in this issue.
+ * @property {Object} delta - Holds the pending changes that will be applied with SaveChanges().
+ */
+// TODO(crbug.com/monorail/6456): createTime, closeTime, modifyTime, componentModifyTime, statusModifyTime, ownerModifyTime
+
+// TODO(crbug.com/monorail/6456): Add other classes.
diff --git a/api/v3/converters.py b/api/v3/converters.py
new file mode 100644
index 0000000..60aebd7
--- /dev/null
+++ b/api/v3/converters.py
@@ -0,0 +1,1979 @@
+# Copyright 2020 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style
+# license that can be found in the LICENSE file.
+
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+
+import collections
+import itertools
+import logging
+import time
+
+from google.protobuf import timestamp_pb2
+
+from api import resource_name_converters as rnc
+from api.v3.api_proto import feature_objects_pb2
+from api.v3.api_proto import issues_pb2
+from api.v3.api_proto import issue_objects_pb2
+from api.v3.api_proto import project_objects_pb2
+from api.v3.api_proto import user_objects_pb2
+
+from framework import exceptions
+from framework import filecontent
+from framework import framework_bizobj
+from framework import framework_constants
+from framework import framework_helpers
+from proto import tracker_pb2
+from project import project_helpers
+from tracker import attachment_helpers
+from tracker import field_helpers
+from tracker import tracker_bizobj as tbo
+from tracker import tracker_helpers
+
+Choice = project_objects_pb2.FieldDef.EnumTypeSettings.Choice
+
+# Ingest/convert dicts for ApprovalStatus.
+_V3_APPROVAL_STATUS = issue_objects_pb2.ApprovalValue.ApprovalStatus.Value
+_APPROVAL_STATUS_INGEST = {
+  _V3_APPROVAL_STATUS('APPROVAL_STATUS_UNSPECIFIED'): None,
+  _V3_APPROVAL_STATUS('NOT_SET'): tracker_pb2.ApprovalStatus.NOT_SET,
+  _V3_APPROVAL_STATUS('NEEDS_REVIEW'): tracker_pb2.ApprovalStatus.NEEDS_REVIEW,
+  _V3_APPROVAL_STATUS('NA'): tracker_pb2.ApprovalStatus.NA,
+  _V3_APPROVAL_STATUS('REVIEW_REQUESTED'):
+      tracker_pb2.ApprovalStatus.REVIEW_REQUESTED,
+  _V3_APPROVAL_STATUS('REVIEW_STARTED'):
+      tracker_pb2.ApprovalStatus.REVIEW_STARTED,
+  _V3_APPROVAL_STATUS('NEED_INFO'): tracker_pb2.ApprovalStatus.NEED_INFO,
+  _V3_APPROVAL_STATUS('APPROVED'): tracker_pb2.ApprovalStatus.APPROVED,
+  _V3_APPROVAL_STATUS('NOT_APPROVED'): tracker_pb2.ApprovalStatus.NOT_APPROVED,
+}
+_APPROVAL_STATUS_CONVERT = {
+  val: key for key, val in _APPROVAL_STATUS_INGEST.items()}
+
+
+class Converter(object):
+  """Class to manage converting objects between the API and backend layer."""
+
+  def __init__(self, mc, services):
+    # type: (MonorailContext, Services) -> Converter
+    """Create a Converter with the given MonorailContext and Services.
+
+    Args:
+      mc: MonorailContext object containing the MonorailConnection to the DB
+            and the requester's AuthData object.
+      services: Services object for connections to backend services.
+    """
+    self.cnxn = mc.cnxn
+    self.user_auth = mc.auth
+    self.services = services
+
+  # Hotlists
+
+  def ConvertHotlist(self, hotlist):
+    # type: (proto.feature_objects_pb2.Hotlist)
+    #    -> api_proto.feature_objects_pb2.Hotlist
+    """Convert a protorpc Hotlist into a protoc Hotlist."""
+
+    hotlist_resource_name = rnc.ConvertHotlistName(hotlist.hotlist_id)
+    members_by_id = rnc.ConvertUserNames(
+        hotlist.owner_ids + hotlist.editor_ids)
+    default_columns = self._ComputeIssuesListColumns(hotlist.default_col_spec)
+    if hotlist.is_private:
+      hotlist_privacy = feature_objects_pb2.Hotlist.HotlistPrivacy.Value(
+          'PRIVATE')
+    else:
+      hotlist_privacy = feature_objects_pb2.Hotlist.HotlistPrivacy.Value(
+          'PUBLIC')
+
+    return feature_objects_pb2.Hotlist(
+        name=hotlist_resource_name,
+        display_name=hotlist.name,
+        owner=members_by_id.get(hotlist.owner_ids[0]),
+        editors=[
+            members_by_id.get(editor_id) for editor_id in hotlist.editor_ids
+        ],
+        summary=hotlist.summary,
+        description=hotlist.description,
+        default_columns=default_columns,
+        hotlist_privacy=hotlist_privacy)
+
+  def ConvertHotlists(self, hotlists):
+    # type: (Sequence[proto.feature_objects_pb2.Hotlist])
+    #    -> Sequence[api_proto.feature_objects_pb2.Hotlist]
+    """Convert protorpc Hotlists into protoc Hotlists."""
+    return [self.ConvertHotlist(hotlist) for hotlist in hotlists]
+
+  def ConvertHotlistItems(self, hotlist_id, items):
+    # type: (int, Sequence[proto.features_pb2.HotlistItem]) ->
+    #     Sequence[api_proto.feature_objects_pb2.Hotlist]
+    """Convert a Sequence of protorpc HotlistItems into a Sequence of protoc
+       HotlistItems.
+
+    Args:
+      hotlist_id: ID of the Hotlist the items belong to.
+      items: Sequence of HotlistItem protorpc objects.
+
+    Returns:
+      Sequence of protoc HotlistItems in the same order they are given in
+        `items`.
+      In the rare event that any issues in `items` are not found, they will be
+        omitted from the result.
+    """
+    issue_ids = [item.issue_id for item in items]
+    # Converting HotlistItemNames and IssueNames both require looking up the
+    # issues in the hotlist. However, we want to keep the code clean and
+    # readable so we keep the two processes separate.
+    resource_names_dict = rnc.ConvertHotlistItemNames(
+        self.cnxn, hotlist_id, issue_ids, self.services)
+    issue_names_dict = rnc.ConvertIssueNames(
+        self.cnxn, issue_ids, self.services)
+    adders_by_id = rnc.ConvertUserNames([item.adder_id for item in items])
+
+    # Filter out items whose issues were not found.
+    found_items = [
+        item for item in items if resource_names_dict.get(item.issue_id) and
+        issue_names_dict.get(item.issue_id)
+    ]
+    if len(items) != len(found_items):
+      found_ids = [item.issue_id for item in found_items]
+      missing_ids = [iid for iid in issue_ids if iid not in found_ids]
+      logging.info('HotlistItem issues %r not found' % missing_ids)
+
+    # Generate user friendly ranks (0, 1, 2, 3,...) that are exposed to API
+    # clients, instead of using padded ranks (1, 11, 21, 31,...).
+    sorted_ranks = sorted(item.rank for item in found_items)
+    friendly_ranks_dict = {
+        rank: friendly_rank for friendly_rank, rank in enumerate(sorted_ranks)
+    }
+
+    api_items = []
+    for item in found_items:
+      api_item = feature_objects_pb2.HotlistItem(
+          name=resource_names_dict.get(item.issue_id),
+          issue=issue_names_dict.get(item.issue_id),
+          rank=friendly_ranks_dict[item.rank],
+          adder=adders_by_id.get(item.adder_id),
+          note=item.note)
+      if item.date_added:
+        api_item.create_time.FromSeconds(item.date_added)
+      api_items.append(api_item)
+
+    return api_items
+
+  # Issues
+
+  def _ConvertComponentValues(self, issue):
+    # proto.tracker_pb2.Issue ->
+    #     Sequence[api_proto.issue_objects_pb2.Issue.ComponentValue]
+    """Convert the status string on issue into a ComponentValue."""
+    component_values = []
+    component_ids = itertools.chain(
+        issue.component_ids, issue.derived_component_ids)
+    ids_to_names = rnc.ConvertComponentDefNames(
+        self.cnxn, component_ids, issue.project_id, self.services)
+
+    for component_id in issue.component_ids:
+      if component_id in ids_to_names:
+        component_values.append(
+            issue_objects_pb2.Issue.ComponentValue(
+                component=ids_to_names[component_id],
+                derivation=issue_objects_pb2.Derivation.Value(
+                    'EXPLICIT')))
+    for derived_component_id in issue.derived_component_ids:
+      if derived_component_id in ids_to_names:
+        component_values.append(
+            issue_objects_pb2.Issue.ComponentValue(
+                component=ids_to_names[derived_component_id],
+                derivation=issue_objects_pb2.Derivation.Value('RULE')))
+
+    return component_values
+
+  def _ConvertStatusValue(self, issue):
+    # proto.tracker_pb2.Issue -> api_proto.issue_objects_pb2.Issue.StatusValue
+    """Convert the status string on issue into a StatusValue."""
+    derivation = issue_objects_pb2.Derivation.Value(
+        'DERIVATION_UNSPECIFIED')
+    if issue.status:
+      derivation = issue_objects_pb2.Derivation.Value('EXPLICIT')
+    else:
+      derivation = issue_objects_pb2.Derivation.Value('RULE')
+    return issue_objects_pb2.Issue.StatusValue(
+        status=issue.status or issue.derived_status, derivation=derivation)
+
+  def _ConvertAmendments(self, amendments, user_display_names):
+    # type: (Sequence[proto.tracker_pb2.Amendment], Mapping[int, str]) ->
+    #     Sequence[api_proto.issue_objects_pb2.Comment.Amendment]
+    """Convert protorpc Amendments to protoc Amendments.
+
+    Args:
+      amendments: the amendments to convert
+      user_display_names: map from user_id to display name for all users
+          involved in the amendments.
+
+    Returns:
+      The converted amendments.
+    """
+    results = []
+    for amendment in amendments:
+      field_name = tbo.GetAmendmentFieldName(amendment)
+      new_value = tbo.AmendmentString_New(amendment, user_display_names)
+      results.append(
+          issue_objects_pb2.Comment.Amendment(
+              field_name=field_name,
+              new_or_delta_value=new_value,
+              old_value=amendment.oldvalue))
+    return results
+
+  def _ConvertAttachments(self, attachments, project_name):
+    # type: (Sequence[proto.tracker_pb2.Attachment], str) ->
+    #     Sequence[api_proto.issue_objects_pb2.Comment.Attachment]
+    """Convert protorpc Attachments to protoc Attachments."""
+    results = []
+    for attach in attachments:
+      if attach.deleted:
+        state = issue_objects_pb2.IssueContentState.Value('DELETED')
+        size, thumbnail_uri, view_uri, download_uri = None, None, None, None
+      else:
+        state = issue_objects_pb2.IssueContentState.Value('ACTIVE')
+        size = attach.filesize
+        download_uri = attachment_helpers.GetDownloadURL(attach.attachment_id)
+        view_uri = attachment_helpers.GetViewURL(
+            attach, download_uri, project_name)
+        thumbnail_uri = attachment_helpers.GetThumbnailURL(attach, download_uri)
+      results.append(
+          issue_objects_pb2.Comment.Attachment(
+              filename=attach.filename,
+              state=state,
+              size=size,
+              media_type=attach.mimetype,
+              thumbnail_uri=thumbnail_uri,
+              view_uri=view_uri,
+              download_uri=download_uri))
+    return results
+
+  def ConvertComments(self, issue_id, comments):
+    # type: (int, Sequence[proto.tracker_pb2.IssueComment])
+    #     -> Sequence[api_proto.issue_objects_pb2.Comment]
+    """Convert protorpc IssueComments from issue into protoc Comments."""
+    issue = self.services.issue.GetIssue(self.cnxn, issue_id)
+    users_by_id = self.services.user.GetUsersByIDs(
+        self.cnxn, tbo.UsersInvolvedInCommentList(comments))
+    (user_display_names,
+     _user_display_emails) = framework_bizobj.CreateUserDisplayNamesAndEmails(
+         self.cnxn, self.services, self.user_auth, users_by_id.values())
+    comment_names_dict = rnc.CreateCommentNames(
+        issue.local_id, issue.project_name,
+        [comment.sequence for comment in comments])
+    approval_ids = [
+        comment.approval_id
+        for comment in comments
+        if comment.approval_id is not None  # In case of a 0 approval_id.
+    ]
+    approval_ids_to_names = rnc.ConvertApprovalDefNames(
+        self.cnxn, approval_ids, issue.project_id, self.services)
+
+    converted_comments = []
+    for comment in comments:
+      if comment.is_spam:
+        state = issue_objects_pb2.IssueContentState.Value('SPAM')
+      elif comment.deleted_by:
+        state = issue_objects_pb2.IssueContentState.Value('DELETED')
+      else:
+        state = issue_objects_pb2.IssueContentState.Value('ACTIVE')
+      comment_type = issue_objects_pb2.Comment.Type.Value('COMMENT')
+      if comment.is_description:
+        comment_type = issue_objects_pb2.Comment.Type.Value('DESCRIPTION')
+      converted_attachments = self._ConvertAttachments(
+          comment.attachments, issue.project_name)
+      converted_amendments = self._ConvertAmendments(
+          comment.amendments, user_display_names)
+      converted_comment = issue_objects_pb2.Comment(
+          name=comment_names_dict[comment.sequence],
+          state=state,
+          type=comment_type,
+          create_time=timestamp_pb2.Timestamp(seconds=comment.timestamp),
+          attachments=converted_attachments,
+          amendments=converted_amendments)
+      if comment.content:
+        converted_comment.content = comment.content
+      if comment.user_id:
+        converted_comment.commenter = rnc.ConvertUserName(comment.user_id)
+      if comment.inbound_message:
+        converted_comment.inbound_message = comment.inbound_message
+      if comment.approval_id and comment.approval_id in approval_ids_to_names:
+        converted_comment.approval = approval_ids_to_names[comment.approval_id]
+      converted_comments.append(converted_comment)
+    return converted_comments
+
+  def ConvertIssue(self, issue):
+    # type: (proto.tracker_pb2.Issue) -> api_proto.issue_objects_pb2.Issue
+    """Convert a protorpc Issue into a protoc Issue."""
+    issues = self.ConvertIssues([issue])
+    if len(issues) < 1:
+      raise exceptions.NoSuchIssueException()
+    if len(issues) > 1:
+      logging.warning('More than one converted issue returned: %s', issues)
+    return issues[0]
+
+  def ConvertIssues(self, issues):
+    # type: (Sequence[proto.tracker_pb2.Issue]) ->
+    #     Sequence[api_proto.issue_objects_pb2.Issue]
+    """Convert protorpc Issues into protoc Issues."""
+    issue_ids = [issue.issue_id for issue in issues]
+    issue_names_dict = rnc.ConvertIssueNames(
+        self.cnxn, issue_ids, self.services)
+    found_issues = [
+        issue for issue in issues if issue.issue_id in issue_names_dict
+    ]
+    converted_issues = []
+    for issue in found_issues:
+      status = self._ConvertStatusValue(issue)
+      content_state = issue_objects_pb2.IssueContentState.Value(
+          'STATE_UNSPECIFIED')
+      if issue.is_spam:
+        content_state = issue_objects_pb2.IssueContentState.Value('SPAM')
+      elif issue.deleted:
+        content_state = issue_objects_pb2.IssueContentState.Value('DELETED')
+      else:
+        content_state = issue_objects_pb2.IssueContentState.Value('ACTIVE')
+
+      owner = None
+      # Explicit values override values derived from rules.
+      if issue.owner_id:
+        owner = issue_objects_pb2.Issue.UserValue(
+            derivation=issue_objects_pb2.Derivation.Value('EXPLICIT'),
+            user=rnc.ConvertUserName(issue.owner_id))
+      elif issue.derived_owner_id:
+        owner = issue_objects_pb2.Issue.UserValue(
+            derivation=issue_objects_pb2.Derivation.Value('RULE'),
+            user=rnc.ConvertUserName(issue.derived_owner_id))
+
+      cc_users = []
+      for cc_user_id in issue.cc_ids:
+        cc_users.append(
+            issue_objects_pb2.Issue.UserValue(
+                derivation=issue_objects_pb2.Derivation.Value('EXPLICIT'),
+                user=rnc.ConvertUserName(cc_user_id)))
+      for derived_cc_user_id in issue.derived_cc_ids:
+        cc_users.append(
+            issue_objects_pb2.Issue.UserValue(
+                derivation=issue_objects_pb2.Derivation.Value('RULE'),
+                user=rnc.ConvertUserName(derived_cc_user_id)))
+
+      labels = self.ConvertLabels(
+          issue.labels, issue.derived_labels, issue.project_id)
+      components = self._ConvertComponentValues(issue)
+      non_approval_fvs = self._GetNonApprovalFieldValues(
+          issue.field_values, issue.project_id)
+      field_values = self.ConvertFieldValues(
+          non_approval_fvs, issue.project_id, issue.phases)
+      field_values.extend(
+          self.ConvertEnumFieldValues(
+              issue.labels, issue.derived_labels, issue.project_id))
+      related_issue_ids = (
+          [issue.merged_into] + issue.blocked_on_iids + issue.blocking_iids)
+      issue_names_by_ids = rnc.ConvertIssueNames(
+          self.cnxn, related_issue_ids, self.services)
+      merged_into_issue_ref = None
+      if issue.merged_into and issue.merged_into in issue_names_by_ids:
+        merged_into_issue_ref = issue_objects_pb2.IssueRef(
+            issue=issue_names_by_ids[issue.merged_into])
+      if issue.merged_into_external:
+        merged_into_issue_ref = issue_objects_pb2.IssueRef(
+            ext_identifier=issue.merged_into_external)
+
+      blocked_on_issue_refs = [
+          issue_objects_pb2.IssueRef(issue=issue_names_by_ids[iid])
+          for iid in issue.blocked_on_iids
+          if iid in issue_names_by_ids
+      ]
+      blocked_on_issue_refs.extend(
+          issue_objects_pb2.IssueRef(
+              ext_identifier=blocked_on.ext_issue_identifier)
+          for blocked_on in issue.dangling_blocked_on_refs)
+
+      blocking_issue_refs = [
+          issue_objects_pb2.IssueRef(issue=issue_names_by_ids[iid])
+          for iid in issue.blocking_iids
+          if iid in issue_names_by_ids
+      ]
+      blocking_issue_refs.extend(
+          issue_objects_pb2.IssueRef(
+              ext_identifier=blocking.ext_issue_identifier)
+          for blocking in issue.dangling_blocking_refs)
+      # All other timestamps were set when the issue was created.
+      close_time = None
+      if issue.closed_timestamp:
+        close_time = timestamp_pb2.Timestamp(seconds=issue.closed_timestamp)
+
+      phases = self._ComputePhases(issue.phases)
+
+      result = issue_objects_pb2.Issue(
+          name=issue_names_dict[issue.issue_id],
+          summary=issue.summary,
+          state=content_state,
+          status=status,
+          reporter=rnc.ConvertUserName(issue.reporter_id),
+          owner=owner,
+          cc_users=cc_users,
+          labels=labels,
+          components=components,
+          field_values=field_values,
+          merged_into_issue_ref=merged_into_issue_ref,
+          blocked_on_issue_refs=blocked_on_issue_refs,
+          blocking_issue_refs=blocking_issue_refs,
+          create_time=timestamp_pb2.Timestamp(seconds=issue.opened_timestamp),
+          close_time=close_time,
+          modify_time=timestamp_pb2.Timestamp(seconds=issue.modified_timestamp),
+          component_modify_time=timestamp_pb2.Timestamp(
+              seconds=issue.component_modified_timestamp),
+          status_modify_time=timestamp_pb2.Timestamp(
+              seconds=issue.status_modified_timestamp),
+          owner_modify_time=timestamp_pb2.Timestamp(
+              seconds=issue.owner_modified_timestamp),
+          star_count=issue.star_count,
+          phases=phases)
+      # TODO(crbug.com/monorail/5857): Set attachment_count unconditionally
+      # after the underlying source of negative attachment counts has been
+      # resolved and database has been repaired.
+      if issue.attachment_count >= 0:
+        result.attachment_count = issue.attachment_count
+      converted_issues.append(result)
+    return converted_issues
+
+  def IngestAttachmentUploads(self, attachment_uploads):
+    # type: (Sequence[api_proto.issues_pb2.AttachmentUpload] ->
+    #     Sequence[framework_helpers.AttachmentUpload])
+    """Ingests protoc AttachmentUploads into framework_helpers.AttachUploads."""
+    ingested_uploads = []
+    with exceptions.ErrorAggregator(exceptions.InputException) as err_agg:
+      for up in attachment_uploads:
+        if not up.filename or not up.content:
+          err_agg.AddErrorMessage(
+              'Uploaded atachment missing filename or content')
+        mimetype = filecontent.GuessContentTypeFromFilename(up.filename)
+        ingested_uploads.append(
+            framework_helpers.AttachmentUpload(
+                up.filename, up.content, mimetype))
+
+    return ingested_uploads
+
+  def IngestIssueDeltas(self, issue_deltas):
+    # type: (Sequence[api_proto.issues_pb2.IssueDelta]) ->
+    #     Sequence[Tuple[int, proto.tracker_pb2.IssueDelta]]
+    """Ingests protoc IssueDeltas, into protorpc IssueDeltas.
+
+    Args:
+      issue_deltas: the protoc IssueDeltas to ingest.
+
+    Returns:
+      A list of (issue_id, tracker_pb2.IssueDelta) tuples that contain
+      values found in issue_deltas, ignoring all OUTPUT_ONLY and masked
+      fields.
+
+    Raises:
+      InputException: if any fields in the approval_deltas were invalid.
+      NoSuchProjectException: if any parent projects are not found.
+      NoSuchIssueException: if any issues are not found.
+      NoSuchComponentException: if any components are not found.
+    """
+    issue_names = [delta.issue.name for delta in issue_deltas]
+    issue_ids = rnc.IngestIssueNames(self.cnxn, issue_names, self.services)
+    issues_dict, misses = self.services.issue.GetIssuesDict(
+        self.cnxn, issue_ids)
+    if misses:
+      logging.info(
+          'Issues not found for supposedly valid issue_ids: %r' % misses)
+      raise ValueError('Could not fetch some issues.')
+    configs_by_pid = self.services.config.GetProjectConfigs(
+        self.cnxn, {issue.project_id for issue in issues_dict.values()})
+
+    with exceptions.ErrorAggregator(exceptions.InputException) as err_agg:
+      for api_delta in issue_deltas:
+        if not api_delta.HasField('update_mask'):
+          err_agg.AddErrorMessage(
+              '`update_mask` must be set for {} delta.', api_delta.issue.name)
+        elif not api_delta.update_mask.IsValidForDescriptor(
+            issue_objects_pb2.Issue.DESCRIPTOR):
+          err_agg.AddErrorMessage(
+              'Invalid `update_mask` for {} delta.', api_delta.issue.name)
+
+    ingested = []
+    for iid, api_delta in zip(issue_ids, issue_deltas):
+      delta = tracker_pb2.IssueDelta()
+
+      # Check non-repeated fields before MergeMessage because in an object
+      # where fields are not set and with a FieldMask applied, there is no
+      # way to tell if empty fields were explicitly listed or not listed
+      # in the FieldMask.
+      paths_set = set(api_delta.update_mask.paths)
+      if (not paths_set.isdisjoint({'status', 'status.status'}) and
+          api_delta.issue.status.status):
+        delta.status = api_delta.issue.status.status
+      elif 'status.status' in paths_set and not api_delta.issue.status.status:
+        delta.status = ''
+
+      if (not paths_set.isdisjoint({'owner', 'owner.user'}) and
+          api_delta.issue.owner.user):
+        delta.owner_id = rnc.IngestUserName(
+              self.cnxn, api_delta.issue.owner.user, self.services)
+      elif 'owner.user' in paths_set and not api_delta.issue.owner.user:
+        delta.owner_id = framework_constants.NO_USER_SPECIFIED
+
+      if 'summary' in paths_set:
+        if api_delta.issue.summary:
+          delta.summary = api_delta.issue.summary
+        else:
+          delta.summary = ''
+
+      merge_ref = api_delta.issue.merged_into_issue_ref
+      if 'merged_into_issue_ref' in paths_set:
+        if (api_delta.issue.merged_into_issue_ref.issue or
+            api_delta.issue.merged_into_issue_ref.ext_identifier):
+          ingested_ref = self._IngestIssueRef(merge_ref)
+          if isinstance(ingested_ref, tracker_pb2.DanglingIssueRef):
+            delta.merged_into_external = ingested_ref.ext_issue_identifier
+          else:
+            delta.merged_into = ingested_ref
+      elif 'merged_into_issue_ref.issue' in paths_set:
+        if api_delta.issue.merged_into_issue_ref.issue:
+          delta.merged_into = self._IngestIssueRef(merge_ref)
+        else:
+          delta.merged_into = 0
+      elif 'merged_into_issue_ref.ext_identifier' in paths_set:
+        if api_delta.issue.merged_into_issue_ref.ext_identifier:
+          ingested_ref = self._IngestIssueRef(merge_ref)
+          delta.merged_into_external = ingested_ref.ext_issue_identifier
+        else:
+          delta.merged_into_external = ''
+
+      filtered_api_issue = issue_objects_pb2.Issue()
+      api_delta.update_mask.MergeMessage(
+          api_delta.issue,
+          filtered_api_issue,
+          replace_message_field=True,
+          replace_repeated_field=True)
+
+      cc_names = [name for name in api_delta.ccs_remove] + [
+          user_value.user for user_value in filtered_api_issue.cc_users
+      ]
+      cc_ids = rnc.IngestUserNames(self.cnxn, cc_names, self.services)
+      delta.cc_ids_remove = cc_ids[:len(api_delta.ccs_remove)]
+      delta.cc_ids_add = cc_ids[len(api_delta.ccs_remove):]
+
+      comp_names = [component for component in api_delta.components_remove] + [
+          c_value.component for c_value in filtered_api_issue.components
+      ]
+      project_comp_ids = rnc.IngestComponentDefNames(
+          self.cnxn, comp_names, self.services)
+      comp_ids = [comp_id for (_pid, comp_id) in project_comp_ids]
+      delta.comp_ids_remove = comp_ids[:len(api_delta.components_remove)]
+      delta.comp_ids_add = comp_ids[len(api_delta.components_remove):]
+
+      # Added to delta below, after ShiftEnumFieldsIntoLabels.
+      labels_add = [value.label for value in filtered_api_issue.labels]
+      labels_remove = [label for label in api_delta.labels_remove]
+
+      config = configs_by_pid[issues_dict[iid].project_id]
+      fvs_add, add_enums = self._IngestFieldValues(
+          filtered_api_issue.field_values, config)
+      fvs_remove, remove_enums = self._IngestFieldValues(
+          api_delta.field_vals_remove, config)
+      field_helpers.ShiftEnumFieldsIntoLabels(
+          labels_add, labels_remove, add_enums, remove_enums, config)
+      delta.field_vals_add = fvs_add
+      delta.field_vals_remove = fvs_remove
+      delta.labels_add = labels_add
+      delta.labels_remove = labels_remove
+      assert len(add_enums) == 0  # ShiftEnumFieldsIntoLabels clears all enums.
+      assert len(remove_enums) == 0
+
+      blocked_on_iids_rm, blocked_on_dangling_rm = self._IngestIssueRefs(
+          api_delta.blocked_on_issues_remove)
+      delta.blocked_on_remove = blocked_on_iids_rm
+      delta.ext_blocked_on_remove = [
+          ref.ext_issue_identifier for ref in blocked_on_dangling_rm
+      ]
+
+      blocked_on_iids_add, blocked_on_dangling_add = self._IngestIssueRefs(
+          filtered_api_issue.blocked_on_issue_refs)
+      delta.blocked_on_add = blocked_on_iids_add
+      delta.ext_blocked_on_add = [
+          ref.ext_issue_identifier for ref in blocked_on_dangling_add
+      ]
+
+      blocking_iids_rm, blocking_dangling_rm = self._IngestIssueRefs(
+          api_delta.blocking_issues_remove)
+      delta.blocking_remove = blocking_iids_rm
+      delta.ext_blocking_remove = [
+          ref.ext_issue_identifier for ref in blocking_dangling_rm
+      ]
+
+      blocking_iids_add, blocking_dangling_add = self._IngestIssueRefs(
+          filtered_api_issue.blocking_issue_refs)
+      delta.blocking_add = blocking_iids_add
+      delta.ext_blocking_add = [
+          ref.ext_issue_identifier for ref in blocking_dangling_add
+      ]
+
+      ingested.append((iid, delta))
+
+    return ingested
+
+  def IngestApprovalDeltas(self, approval_deltas, setter_id):
+    # type: (Sequence[api_proto.issues_pb2.ApprovalDelta], int) ->
+    #     Sequence[Tuple[int, int, proto.tracker_pb2.ApprovalDelta]]
+    """Ingests protoc ApprovalDeltas into protorpc ApprovalDeltas.
+
+    Args:
+      approval_deltas: the protoc ApprovalDeltas to ingest.
+      setter_id: The ID for the user setting the deltas.
+
+    Returns:
+      Sequence of (issue_id, approval_id, ApprovalDelta) tuples in the order
+      provided. The ApprovalDeltas ignore all OUTPUT_ONLY and masked fields.
+      The tuples are "delta_specifications;" they identify one requested change.
+
+    Raises:
+      InputException: if any fields in the approval_delta protos were invalid.
+      NoSuchProjectException: if the parent project of any ApprovalValue isn't
+          found.
+      NoSuchIssueException: if the issue of any ApprovalValue isn't found.
+      NoSuchUserException: if any user value was provided with an invalid email.
+          Note that users specified by ID are not checked for existence.
+    """
+    delta_specifications = []
+    set_on = int(time.time())  # Use the same timestamp for all deltas.
+    for approval_delta in approval_deltas:
+      approval_name = approval_delta.approval_value.name
+      # TODO(crbug/monorail/8173): Aggregate errors.
+      project_id, issue_id, approval_id = rnc.IngestApprovalValueName(
+          self.cnxn, approval_name, self.services)
+
+      if not approval_delta.HasField('update_mask'):
+        raise exceptions.InputException(
+            '`update_mask` must be set for %s delta.' % approval_name)
+      elif not approval_delta.update_mask.IsValidForDescriptor(
+          issue_objects_pb2.ApprovalValue.DESCRIPTOR):
+        raise exceptions.InputException(
+            'Invalid `update_mask` for %s delta.' % approval_name)
+      filtered_value = issue_objects_pb2.ApprovalValue()
+      approval_delta.update_mask.MergeMessage(
+          approval_delta.approval_value,
+          filtered_value,
+          replace_message_field=True,
+          replace_repeated_field=True)
+      status = _APPROVAL_STATUS_INGEST[filtered_value.status]
+      # Approvers
+      # No autocreate.
+      # A user may try to remove all existing approvers [a, b] and add another
+      # approver [c]. If they mis-type `c` and we auto-create `c` instead of
+      # raising error, this would cause the ApprovalValue to be editable by no
+      # one but site admins.
+      approver_ids_add = rnc.IngestUserNames(
+          self.cnxn, filtered_value.approvers, self.services, autocreate=False)
+      approver_ids_remove = rnc.IngestUserNames(
+          self.cnxn,
+          approval_delta.approvers_remove,
+          self.services,
+          autocreate=False)
+
+      # Field Values.
+      config = self.services.config.GetProjectConfig(self.cnxn, project_id)
+      approval_fds_by_id = {
+          fd.field_id: fd
+          for fd in config.field_defs
+          if fd.field_type is tracker_pb2.FieldTypes.APPROVAL_TYPE
+      }
+      if approval_id not in approval_fds_by_id:
+        raise exceptions.InputException(
+            'Approval not found in project for %s' % approval_name)
+
+      sub_fvs_add, add_enums = self._IngestFieldValues(
+          filtered_value.field_values, config, approval_id_filter=approval_id)
+      sub_fvs_remove, remove_enums = self._IngestFieldValues(
+          approval_delta.field_vals_remove,
+          config,
+          approval_id_filter=approval_id)
+      labels_add = []
+      labels_remove = []
+      field_helpers.ShiftEnumFieldsIntoLabels(
+          labels_add, labels_remove, add_enums, remove_enums, config)
+      assert len(add_enums) == 0  # ShiftEnumFieldsIntoLabels clears all enums.
+      assert len(remove_enums) == 0
+      delta = tbo.MakeApprovalDelta(
+          status,
+          setter_id,
+          approver_ids_add,
+          approver_ids_remove,
+          sub_fvs_add,
+          sub_fvs_remove, [],
+          labels_add,
+          labels_remove,
+          set_on=set_on)
+      delta_specifications.append((issue_id, approval_id, delta))
+    return delta_specifications
+
+  def IngestIssue(self, issue, project_id):
+    # type: (api_proto.issue_objects_pb2.Issue, int) -> proto.tracker_pb2.Issue
+    """Ingest a protoc Issue into a protorpc Issue.
+
+    Args:
+      issue: the protoc issue to ingest.
+      project_id: The project into which we're ingesting `issue`.
+
+    Returns:
+      protorpc version of issue, ignoring all OUTPUT_ONLY fields.
+
+    Raises:
+      InputException: if any fields in the 'issue' proto were invalid.
+      NoSuchProjectException: if 'project_id' is not found.
+    """
+    # Get config first. We can't ingest the issue if the project isn't found.
+    config = self.services.config.GetProjectConfig(self.cnxn, project_id)
+    ingestedDict = {
+      'project_id': project_id,
+      'summary': issue.summary
+    }
+    with exceptions.ErrorAggregator(exceptions.InputException) as err_agg:
+      self._ExtractOwner(issue, ingestedDict, err_agg)
+
+      # Extract ccs.
+      try:
+        ingestedDict['cc_ids'] = rnc.IngestUserNames(
+            self.cnxn, [cc.user for cc in issue.cc_users], self.services,
+            autocreate=True)
+      except exceptions.InputException as e:
+        err_agg.AddErrorMessage('Error ingesting cc_users: {}', e)
+
+      # Extract status.
+      if issue.HasField('status') and issue.status.status:
+        ingestedDict['status'] = issue.status.status
+      else:
+        err_agg.AddErrorMessage('Status is required when creating an issue')
+
+      # Extract components.
+      try:
+        project_comp_ids = rnc.IngestComponentDefNames(
+            self.cnxn, [cv.component for cv in issue.components], self.services)
+        ingestedDict['component_ids'] = [
+            comp_id for (_pid, comp_id) in project_comp_ids]
+      except (exceptions.InputException, exceptions.NoSuchProjectException,
+              exceptions.NoSuchComponentException) as e:
+        err_agg.AddErrorMessage('Error ingesting components: {}', e)
+
+      # Extract labels and field values.
+      ingestedDict['labels'] = [lv.label for lv in issue.labels]
+      try:
+        ingestedDict['field_values'], enums = self._IngestFieldValues(
+            issue.field_values, config)
+        field_helpers.ShiftEnumFieldsIntoLabels(
+            ingestedDict['labels'], [], enums, [], config)
+        assert len(
+            enums) == 0  # ShiftEnumFieldsIntoLabels must clear all enums.
+      except exceptions.InputException as e:
+        err_agg.AddErrorMessage(e.message)
+
+      # Ingest merged, blocking/blocked_on.
+      self._ExtractIssueRefs(issue, ingestedDict, err_agg)
+    return tracker_pb2.Issue(**ingestedDict)
+
+  def _IngestFieldValues(self, field_values, config, approval_id_filter=None):
+    # type: (Sequence[api_proto.issue_objects.FieldValue],
+    #     proto.tracker_pb2.ProjectIssueConfig, Optional[int]) ->
+    #     Tuple[Sequence[proto.tracker_pb2.FieldValue],
+    #         Mapping[int, Sequence[str]]]
+    """Returns protorpc FieldValues for the given protoc FieldValues.
+
+    Raises exceptions if any field could not be parsed for any reasons such as
+        unsupported field type, non-existent field, field from different
+        projects, or fields with mismatched parent approvals.
+
+    Args:
+      field_values: protoc FieldValues to ingest.
+      config: ProjectIssueConfig for the FieldValues we're ingesting.
+      approval_id_filter: an approval_id, including any FieldValues that does
+          not have this approval as a parent will trigger InputException.
+
+    Returns:
+      A pair 1) Ingested FieldValues. 2) A mapping of field ids to values
+      for ENUM_TYPE fields in 'field_values.'
+
+    Raises:
+      InputException: if any fields_values could not be parsed for any reasons
+          such as unsupported field type, non-existent field, or field from
+          different projects.
+    """
+    fds_by_id = {fd.field_id: fd for fd in config.field_defs}
+    enums = {}
+    ingestedFieldValues = []
+    with exceptions.ErrorAggregator(exceptions.InputException) as err_agg:
+      for fv in field_values:
+        try:
+          project_id, fd_id = rnc.IngestFieldDefName(
+              self.cnxn, fv.field, self.services)
+          fd = fds_by_id[fd_id]
+          # Raise if field does not belong to approval_id_filter (if provided).
+          if (approval_id_filter is not None and
+              fd.approval_id != approval_id_filter):
+            approval_name = rnc.ConvertApprovalDefNames(
+                self.cnxn, [approval_id_filter], project_id,
+                self.services)[approval_id_filter]
+            err_agg.AddErrorMessage(
+                'Field {} does not belong to approval {}', fv.field,
+                approval_name)
+            continue
+          if fd.field_type == tracker_pb2.FieldTypes.ENUM_TYPE:
+            enums.setdefault(fd_id, []).append(fv.value)
+          else:
+            ingestedFieldValues.append(self._IngestFieldValue(fv, fd))
+        except (exceptions.InputException, exceptions.NoSuchProjectException,
+                exceptions.NoSuchFieldDefException, ValueError) as e:
+          err_agg.AddErrorMessage(
+              'Could not ingest value ({}) for FieldDef ({}): {}', fv.value,
+              fv.field, e)
+        except exceptions.NoSuchUserException as e:
+          err_agg.AddErrorMessage(
+              'User ({}) not found when ingesting user field: {}', fv.value,
+              fv.field)
+        except KeyError as e:
+          err_agg.AddErrorMessage('Field {} is not in this project', fv.field)
+    return ingestedFieldValues, enums
+
+  def _IngestFieldValue(self, field_value, field_def):
+    # type: (api_proto.issue_objects.FieldValue, proto.tracker_pb2.FieldDef) ->
+    #     proto.tracker_pb2.FieldValue
+    """Ingest a protoc FieldValue into a protorpc FieldValue.
+
+    Args:
+      field_value: protoc FieldValue to ingest.
+      field_def: protorpc FieldDef associated with 'field_value'.
+          BOOL_TYPE and APPROVAL_TYPE are ignored.
+          Enum values are not allowed. They must be ingested as labels.
+
+    Returns:
+      Ingested protorpc FieldValue.
+
+    Raises:
+      InputException if 'field_def' is USER_TYPE and 'field_value' does not
+          have a valid formatted resource name.
+      NoSuchUserException if specified user in field does not exist.
+      ValueError if 'field_value' could not be parsed for 'field_def'.
+    """
+    assert field_def.field_type != tracker_pb2.FieldTypes.ENUM_TYPE
+    if field_def.field_type == tracker_pb2.FieldTypes.USER_TYPE:
+      return self._ParseOneUserFieldValue(field_value.value, field_def.field_id)
+    fv = field_helpers.ParseOneFieldValue(
+        self.cnxn, self.services.user, field_def, field_value.value)
+    # ParseOneFieldValue currently ignores parsing errors, although it has TODOs
+    # to raise them.
+    if not fv:
+      raise ValueError('Could not parse %s' % field_value.value)
+    return fv
+
+  def _ParseOneUserFieldValue(self, value, field_id):
+    # type: (str, int) -> proto.tracker_pb2.FieldValue
+    """Replacement for the obsolete user parsing in ParseOneFieldValue."""
+    user_id = rnc.IngestUserName(self.cnxn, value, self.services)
+    return tbo.MakeFieldValue(field_id, None, None, user_id, None, None, False)
+
+  def _ExtractOwner(self, issue, ingestedDict, err_agg):
+    # type: (api_proto.issue_objects_pb2.Issue, Dict[str, Any], ErrorAggregator)
+    #     -> None
+    """Fills 'owner' into `ingestedDict`, if it can be extracted."""
+    if issue.HasField('owner'):
+      try:
+        # Unlike for cc's, we require owner be an existing user, thus call we
+        # do not autocreate.
+        ingestedDict['owner_id'] = rnc.IngestUserName(
+            self.cnxn, issue.owner.user, self.services, autocreate=False)
+      except exceptions.InputException as e:
+        err_agg.AddErrorMessage(
+            'Error ingesting owner ({}): {}', issue.owner.user, e)
+      except exceptions.NoSuchUserException as e:
+        err_agg.AddErrorMessage(
+            'User ({}) not found when ingesting owner', e)
+    else:
+      ingestedDict['owner_id'] = framework_constants.NO_USER_SPECIFIED
+
+  def _ExtractIssueRefs(self, issue, ingestedDict, err_agg):
+    # type: (api_proto.issue_objects_pb2.Issue, Dict[str, Any], ErrorAggregator)
+    #     -> None
+    """Fills issue relationships into `ingestedDict` from `issue`."""
+    if issue.HasField('merged_into_issue_ref'):
+      try:
+        merged_into_ref = self._IngestIssueRef(issue.merged_into_issue_ref)
+        if isinstance(merged_into_ref, tracker_pb2.DanglingIssueRef):
+          ingestedDict['merged_into_external'] = (
+              merged_into_ref.ext_issue_identifier)
+        else:
+          ingestedDict['merged_into'] = merged_into_ref
+      except exceptions.InputException as e:
+        err_agg.AddErrorMessage(
+            'Error ingesting ref {}: {}', issue.merged_into_issue_ref, e)
+    try:
+      iids, dangling_refs = self._IngestIssueRefs(issue.blocked_on_issue_refs)
+      ingestedDict['blocked_on_iids'] = iids
+      ingestedDict['dangling_blocked_on_refs'] = dangling_refs
+    except exceptions.InputException as e:
+      err_agg.AddErrorMessage(e.message)
+    try:
+      iids, dangling_refs = self._IngestIssueRefs(issue.blocking_issue_refs)
+      ingestedDict['blocking_iids'] = iids
+      ingestedDict['dangling_blocking_refs'] = dangling_refs
+    except exceptions.InputException as e:
+      err_agg.AddErrorMessage(e.message)
+
+  def _IngestIssueRefs(self, issue_refs):
+    # type: (api_proto.issue_objects.IssueRf) ->
+    #     Tuple[Sequence[int], Sequence[tracker_pb2.DanglingIssueRef]]
+    """Given protoc IssueRefs, returns issue_ids and DanglingIssueRefs."""
+    issue_ids = []
+    external_refs = []
+    with exceptions.ErrorAggregator(exceptions.InputException) as err_agg:
+      for ref in issue_refs:
+        try:
+          ingested_ref = self._IngestIssueRef(ref)
+          if isinstance(ingested_ref, tracker_pb2.DanglingIssueRef):
+            external_refs.append(ingested_ref)
+          else:
+            issue_ids.append(ingested_ref)
+        except (exceptions.InputException, exceptions.NoSuchIssueException,
+                exceptions.NoSuchProjectException) as e:
+          err_agg.AddErrorMessage('Error ingesting ref {}: {}', ref, e)
+
+    return issue_ids, external_refs
+
+  def _IngestIssueRef(self, issue_ref):
+    # type: (api_proto.issue_objects.IssueRef) ->
+    #     Union[int, tracker_pb2.DanglingIssueRef]
+    """Given a protoc IssueRef, returns an issue id or DanglingIssueRef."""
+    if issue_ref.issue and issue_ref.ext_identifier:
+      raise exceptions.InputException(
+        'IssueRefs MUST NOT have both `issue` and `ext_identifier`')
+    if issue_ref.issue:
+      return rnc.IngestIssueName(self.cnxn, issue_ref.issue, self.services)
+    if issue_ref.ext_identifier:
+      # TODO(crbug.com/monorail/7208): Handle ingestion/conversion of CodeSite
+      # refs. We may be able to avoid ever needing to ingest them.
+      return tracker_pb2.DanglingIssueRef(
+          ext_issue_identifier=issue_ref.ext_identifier
+        )
+    raise exceptions.InputException(
+        'IssueRefs MUST have one of `issue` and `ext_identifier`')
+
+  def IngestIssuesListColumns(self, issues_list_columns):
+    # type: (Sequence[proto.issue_objects_pb2.IssuesListColumn] -> str
+    """Ingest a list of protoc IssueListColumns and returns a string."""
+    return ' '.join([col.column for col in issues_list_columns])
+
+  def _ComputeIssuesListColumns(self, columns):
+    # type: (string) -> Sequence[api_proto.issue_objects_pb2.IssuesListColumn]
+    """Convert string representation of columns to protoc IssuesListColumns"""
+    return [
+        issue_objects_pb2.IssuesListColumn(column=col)
+        for col in columns.split()
+    ]
+
+  def IngestNotifyType(self, notify):
+    # type: (issue_pb.NotifyType) -> bool
+    """Ingest a NotifyType to boolean."""
+    if (notify == issues_pb2.NotifyType.Value('NOTIFY_TYPE_UNSPECIFIED') or
+        notify == issues_pb2.NotifyType.Value('EMAIL')):
+      return True
+    elif notify == issues_pb2.NotifyType.Value('NO_NOTIFICATION'):
+      return False
+
+  # Users
+
+  def ConvertUser(self, user):
+    # type: (protorpc.User) -> api_proto.user_objects_pb2.User
+    """Convert a protorpc User into a protoc User.
+
+    Args:
+      user: protorpc User object.
+
+    Returns:
+      The protoc User object.
+    """
+    return self.ConvertUsers([user.user_id])[user.user_id]
+
+
+  # TODO(crbug/monorail/7238): Make this take in a full User object and
+  # return a Sequence, rather than a map, after hotlist users are converted.
+  def ConvertUsers(self, user_ids):
+    # type: (Sequence[int]) -> Map(int, api_proto.user_objects_pb2.User)
+    """Convert list of protorpc Users into list of protoc Users.
+
+    Args:
+      user_ids: List of User IDs.
+
+    Returns:
+      Dict of User IDs to User protos for given user_ids that could be found.
+    """
+    user_ids_to_names = {}
+
+    # Get display names
+    users_by_id = self.services.user.GetUsersByIDs(self.cnxn, user_ids)
+    (display_names_by_id,
+     display_emails_by_id) = framework_bizobj.CreateUserDisplayNamesAndEmails(
+         self.cnxn, self.services, self.user_auth, users_by_id.values())
+
+    for user_id, user in users_by_id.items():
+      name = rnc.ConvertUserNames([user_id]).get(user_id)
+
+      display_name = display_names_by_id.get(user_id)
+      display_email = display_emails_by_id.get(user_id)
+      availability = framework_helpers.GetUserAvailability(user)
+      availability_message, _availability_status = availability
+
+      user_ids_to_names[user_id] = user_objects_pb2.User(
+          name=name,
+          display_name=display_name,
+          email=display_email,
+          availability_message=availability_message)
+
+    return user_ids_to_names
+
+  def ConvertProjectStars(self, user_id, projects):
+    # type: (int, Collection[protorpc.Project]) ->
+    #     Collection[api_proto.user_objects_pb2.ProjectStar]
+    """Convert list of protorpc Projects into protoc ProjectStars.
+
+    Args:
+      user_id: The user the ProjectStar is associated with.
+      projects: All starred projects.
+
+    Returns:
+      List of ProjectStar messages.
+    """
+    api_project_stars = []
+    for proj in projects:
+      name = rnc.ConvertProjectStarName(
+          self.cnxn, user_id, proj.project_id, self.services)
+      star = user_objects_pb2.ProjectStar(name=name)
+      api_project_stars.append(star)
+    return api_project_stars
+
+  # Field Defs
+
+  def ConvertFieldDefs(self, field_defs, project_id):
+    # type: (Sequence[proto.tracker_pb2.FieldDef], int) ->
+    #     Sequence[api_proto.project_objects_pb2.FieldDef]
+    """Convert sequence of protorpc FieldDefs to protoc FieldDefs.
+
+    Args:
+      field_defs: List of protorpc FieldDefs
+      project_id: ID of the Project that is ancestor to all given
+        `field_defs`.
+
+    Returns:
+      Sequence of protoc FieldDef in the same order they are given in
+      `field_defs`. In the event any field_def or the referenced approval_id
+      in `field_defs` is not found, they will be omitted from the result.
+    """
+    field_ids = [fd.field_id for fd in field_defs]
+    resource_names_dict = rnc.ConvertFieldDefNames(
+        self.cnxn, field_ids, project_id, self.services)
+    parent_approval_ids = [
+        fd.approval_id for fd in field_defs if fd.approval_id is not None
+    ]
+    approval_names_dict = rnc.ConvertApprovalDefNames(
+        self.cnxn, parent_approval_ids, project_id, self.services)
+
+    api_fds = []
+    for fd in field_defs:
+      # Skip over approval fields, they have their separate ApprovalDef
+      if fd.field_type == tracker_pb2.FieldTypes.APPROVAL_TYPE:
+        continue
+      if fd.field_id not in resource_names_dict:
+        continue
+
+      name = resource_names_dict.get(fd.field_id)
+      display_name = fd.field_name
+      docstring = fd.docstring
+      field_type = self._ConvertFieldDefType(fd.field_type)
+      applicable_issue_type = fd.applicable_type
+      admins = rnc.ConvertUserNames(fd.admin_ids).values()
+      editors = rnc.ConvertUserNames(fd.editor_ids).values()
+      traits = self._ComputeFieldDefTraits(fd)
+      approval_parent = approval_names_dict.get(fd.approval_id)
+
+      enum_settings = None
+      if field_type == project_objects_pb2.FieldDef.Type.Value('ENUM'):
+        enum_settings = project_objects_pb2.FieldDef.EnumTypeSettings(
+            choices=self._GetEnumFieldChoices(fd))
+
+      int_settings = None
+      if field_type == project_objects_pb2.FieldDef.Type.Value('INT'):
+        int_settings = project_objects_pb2.FieldDef.IntTypeSettings(
+            min_value=fd.min_value, max_value=fd.max_value)
+
+      str_settings = None
+      if field_type == project_objects_pb2.FieldDef.Type.Value('STR'):
+        str_settings = project_objects_pb2.FieldDef.StrTypeSettings(
+            regex=fd.regex)
+
+      user_settings = None
+      if field_type == project_objects_pb2.FieldDef.Type.Value('USER'):
+        user_settings = project_objects_pb2.FieldDef.UserTypeSettings(
+            role_requirements=self._ConvertRoleRequirements(fd.needs_member),
+            notify_triggers=self._ConvertNotifyTriggers(fd.notify_on),
+            grants_perm=fd.grants_perm,
+            needs_perm=fd.needs_perm)
+
+      date_settings = None
+      if field_type == project_objects_pb2.FieldDef.Type.Value('DATE'):
+        date_settings = project_objects_pb2.FieldDef.DateTypeSettings(
+            date_action=self._ConvertDateAction(fd.date_action))
+
+      api_fd = project_objects_pb2.FieldDef(
+          name=name,
+          display_name=display_name,
+          docstring=docstring,
+          type=field_type,
+          applicable_issue_type=applicable_issue_type,
+          admins=admins,
+          traits=traits,
+          approval_parent=approval_parent,
+          enum_settings=enum_settings,
+          int_settings=int_settings,
+          str_settings=str_settings,
+          user_settings=user_settings,
+          date_settings=date_settings,
+          editors=editors)
+      api_fds.append(api_fd)
+    return api_fds
+
+  def _ConvertDateAction(self, date_action):
+    # type: (proto.tracker_pb2.DateAction) ->
+    #     api_proto.project_objects_pb2.FieldDef.DateTypeSettings.DateAction
+    """Convert protorpc DateAction to protoc
+       FieldDef.DateTypeSettings.DateAction"""
+    if date_action == tracker_pb2.DateAction.NO_ACTION:
+      return project_objects_pb2.FieldDef.DateTypeSettings.DateAction.Value(
+          'NO_ACTION')
+    elif date_action == tracker_pb2.DateAction.PING_OWNER_ONLY:
+      return project_objects_pb2.FieldDef.DateTypeSettings.DateAction.Value(
+          'NOTIFY_OWNER')
+    elif date_action == tracker_pb2.DateAction.PING_PARTICIPANTS:
+      return project_objects_pb2.FieldDef.DateTypeSettings.DateAction.Value(
+          'NOTIFY_PARTICIPANTS')
+    else:
+      raise ValueError('Unsupported DateAction Value')
+
+  def _ConvertRoleRequirements(self, needs_member):
+    # type: (bool) ->
+    #     api_proto.project_objects_pb2.FieldDef.
+    #     UserTypeSettings.RoleRequirements
+    """Convert protorpc RoleRequirements to protoc
+       FieldDef.UserTypeSettings.RoleRequirements"""
+
+    proto_user_settings = project_objects_pb2.FieldDef.UserTypeSettings
+    if needs_member:
+      return proto_user_settings.RoleRequirements.Value('PROJECT_MEMBER')
+    else:
+      return proto_user_settings.RoleRequirements.Value('NO_ROLE_REQUIREMENT')
+
+  def _ConvertNotifyTriggers(self, notify_trigger):
+    # type: (proto.tracker_pb2.NotifyTriggers) ->
+    #     api_proto.project_objects_pb2.FieldDef.UserTypeSettings.NotifyTriggers
+    """Convert protorpc NotifyTriggers to protoc
+       FieldDef.UserTypeSettings.NotifyTriggers"""
+    if notify_trigger == tracker_pb2.NotifyTriggers.NEVER:
+      return project_objects_pb2.FieldDef.UserTypeSettings.NotifyTriggers.Value(
+          'NEVER')
+    elif notify_trigger == tracker_pb2.NotifyTriggers.ANY_COMMENT:
+      return project_objects_pb2.FieldDef.UserTypeSettings.NotifyTriggers.Value(
+          'ANY_COMMENT')
+    else:
+      raise ValueError('Unsupported NotifyTriggers Value')
+
+  def _ConvertFieldDefType(self, field_type):
+    # type: (proto.tracker_pb2.FieldTypes) ->
+    #     api_proto.project_objects_pb2.FieldDef.Type
+    """Convert protorpc FieldType to protoc FieldDef.Type
+
+    Args:
+      field_type: protorpc FieldType
+
+    Returns:
+      Corresponding protoc FieldDef.Type
+
+    Raises:
+      ValueError if input `field_type` has no suitable supported FieldDef.Type,
+      or input `field_type` is not a recognized enum option.
+    """
+    if field_type == tracker_pb2.FieldTypes.ENUM_TYPE:
+      return project_objects_pb2.FieldDef.Type.Value('ENUM')
+    elif field_type == tracker_pb2.FieldTypes.INT_TYPE:
+      return project_objects_pb2.FieldDef.Type.Value('INT')
+    elif field_type == tracker_pb2.FieldTypes.STR_TYPE:
+      return project_objects_pb2.FieldDef.Type.Value('STR')
+    elif field_type == tracker_pb2.FieldTypes.USER_TYPE:
+      return project_objects_pb2.FieldDef.Type.Value('USER')
+    elif field_type == tracker_pb2.FieldTypes.DATE_TYPE:
+      return project_objects_pb2.FieldDef.Type.Value('DATE')
+    elif field_type == tracker_pb2.FieldTypes.URL_TYPE:
+      return project_objects_pb2.FieldDef.Type.Value('URL')
+    else:
+      raise ValueError(
+          'Unsupported tracker_pb2.FieldType enum. Boolean types '
+          'are unsupported and approval types are found in ApprovalDefs')
+
+  def _ComputeFieldDefTraits(self, field_def):
+    # type: (proto.tracker_pb2.FieldDef) ->
+    #     Sequence[api_proto.project_objects_pb2.FieldDef.Traits]
+    """Compute sequence of FieldDef.Traits for a given protorpc FieldDef."""
+    trait_protos = []
+    if field_def.is_required:
+      trait_protos.append(project_objects_pb2.FieldDef.Traits.Value('REQUIRED'))
+    if field_def.is_niche:
+      trait_protos.append(
+          project_objects_pb2.FieldDef.Traits.Value('DEFAULT_HIDDEN'))
+    if field_def.is_multivalued:
+      trait_protos.append(
+          project_objects_pb2.FieldDef.Traits.Value('MULTIVALUED'))
+    if field_def.is_phase_field:
+      trait_protos.append(project_objects_pb2.FieldDef.Traits.Value('PHASE'))
+    if field_def.is_restricted_field:
+      trait_protos.append(
+          project_objects_pb2.FieldDef.Traits.Value('RESTRICTED'))
+    return trait_protos
+
+  def _GetEnumFieldChoices(self, field_def):
+    # type: (proto.tracker_pb2.FieldDef) ->
+    #     Sequence[Choice]
+    """Get sequence of choices for an enum field
+
+    Args:
+      field_def: protorpc FieldDef
+
+    Returns:
+      Sequence of valid Choices for enum field `field_def`.
+
+    Raises:
+      ValueError if input `field_def` is not an enum type field.
+    """
+    if field_def.field_type != tracker_pb2.FieldTypes.ENUM_TYPE:
+      raise ValueError('Cannot get value from label for non-enum-type field')
+
+    config = self.services.config.GetProjectConfig(
+        self.cnxn, field_def.project_id)
+    value_docstr_tuples = tracker_helpers._GetEnumFieldValuesAndDocstrings(
+        field_def, config)
+
+    return [
+        Choice(value=value, docstring=docstring)
+        for value, docstring in value_docstr_tuples
+    ]
+
+  # Field Values
+
+  def _GetNonApprovalFieldValues(self, field_values, project_id):
+    # type: (Sequence[proto.tracker_pb2.FieldValue], int) ->
+    #     Sequence[proto.tracker_pb2.FieldValue]
+    """Filter out field values that belong to an approval field."""
+    config = self.services.config.GetProjectConfig(self.cnxn, project_id)
+    approval_fd_ids = set(
+        [fd.field_id for fd in config.field_defs if fd.approval_id])
+
+    return [fv for fv in field_values if fv.field_id not in approval_fd_ids]
+
+  def ConvertFieldValues(self, field_values, project_id, phases):
+    # type: (Sequence[proto.tracker_pb2.FieldValue], int,
+    #     Sequence[proto.tracker_pb2.Phase]) ->
+    #     Sequence[api_proto.issue_objects_pb2.FieldValue]
+    """Convert sequence of field_values to protoc FieldValues.
+
+    This method does not handle enum_type fields.
+
+    Args:
+      field_values: List of FieldValues
+      project_id: ID of the Project that is ancestor to all given
+        `field_values`.
+      phases: List of Phases
+
+    Returns:
+      Sequence of protoc FieldValues in the same order they are given in
+      `field_values`. In the event any field_values in `field_values` are not
+      found, they will be omitted from the result.
+    """
+    phase_names_by_id = {phase.phase_id: phase.name for phase in phases}
+    field_ids = [fv.field_id for fv in field_values]
+    resource_names_dict = rnc.ConvertFieldDefNames(
+        self.cnxn, field_ids, project_id, self.services)
+
+    api_fvs = []
+    for fv in field_values:
+      if fv.field_id not in resource_names_dict:
+        continue
+
+      name = resource_names_dict.get(fv.field_id)
+      value = self._ComputeFieldValueString(fv)
+      derivation = self._ComputeFieldValueDerivation(fv)
+      phase = phase_names_by_id.get(fv.phase_id)
+      api_item = issue_objects_pb2.FieldValue(
+          field=name, value=value, derivation=derivation, phase=phase)
+      api_fvs.append(api_item)
+
+    return api_fvs
+
+  def _ComputeFieldValueString(self, field_value):
+    # type: (proto.tracker_pb2.FieldValue) -> str
+    """Convert a FieldValue's value to a string."""
+    if field_value is None:
+      raise exceptions.InputException('No FieldValue specified')
+    elif field_value.int_value is not None:
+      return str(field_value.int_value)
+    elif field_value.str_value is not None:
+      return field_value.str_value
+    elif field_value.user_id is not None:
+      return rnc.ConvertUserNames([field_value.user_id
+                                  ]).get(field_value.user_id)
+    elif field_value.date_value is not None:
+      return str(field_value.date_value)
+    elif field_value.url_value is not None:
+      return field_value.url_value
+    else:
+      raise exceptions.InputException('FieldValue must have at least one value')
+
+  def _ComputeFieldValueDerivation(self, field_value):
+    # type: (proto.tracker_pb2.FieldValue) ->
+    #     api_proto.issue_objects_pb2.Issue.Derivation
+    """Convert a FieldValue's 'derived' to a protoc Issue.Derivation.
+
+    Args:
+      field_value: protorpc FieldValue
+
+    Returns:
+      Issue.Derivation of given `field_value`
+    """
+    if field_value.derived:
+      return issue_objects_pb2.Derivation.Value('RULE')
+    else:
+      return issue_objects_pb2.Derivation.Value('EXPLICIT')
+
+  # Approval Def
+
+  def ConvertApprovalDefs(self, approval_defs, project_id):
+    # type: (Sequence[proto.tracker_pb2.ApprovalDef], int) ->
+    #     Sequence[api_proto.project_objects_pb2.ApprovalDef]
+    """Convert sequence of protorpc ApprovalDefs to protoc ApprovalDefs.
+
+    Args:
+      approval_defs: List of protorpc ApprovalDefs
+      project_id: ID of the Project the approval_defs belong to.
+
+    Returns:
+      Sequence of protoc ApprovalDefs in the same order they are given in
+      in `approval_defs`. In the event any approval_def in `approval_defs`
+      are not found, they will be omitted from the result.
+    """
+    approval_ids = set([ad.approval_id for ad in approval_defs])
+    resource_names_dict = rnc.ConvertApprovalDefNames(
+        self.cnxn, approval_ids, project_id, self.services)
+
+    # Get matching field defs, needed to fill out protoc ApprovalDefs
+    config = self.services.config.GetProjectConfig(self.cnxn, project_id)
+    fd_by_id = {}
+    for fd in config.field_defs:
+      if (fd.field_type == tracker_pb2.FieldTypes.APPROVAL_TYPE and
+          fd.field_id in approval_ids):
+        fd_by_id[fd.field_id] = fd
+
+    all_users = tbo.UsersInvolvedInApprovalDefs(
+        approval_defs, fd_by_id.values())
+    user_resource_names_dict = rnc.ConvertUserNames(all_users)
+
+    api_ads = []
+    for ad in approval_defs:
+      if (ad.approval_id not in resource_names_dict or
+          ad.approval_id not in fd_by_id):
+        continue
+      matching_fd = fd_by_id.get(ad.approval_id)
+      name = resource_names_dict.get(ad.approval_id)
+      display_name = matching_fd.field_name
+      docstring = matching_fd.docstring
+      survey = ad.survey
+      approvers = [
+          user_resource_names_dict.get(approver_id)
+          for approver_id in ad.approver_ids
+      ]
+      admins = [
+          user_resource_names_dict.get(admin_id)
+          for admin_id in matching_fd.admin_ids
+      ]
+
+      api_ad = project_objects_pb2.ApprovalDef(
+          name=name,
+          display_name=display_name,
+          docstring=docstring,
+          survey=survey,
+          approvers=approvers,
+          admins=admins)
+      api_ads.append(api_ad)
+    return api_ads
+
+  def ConvertApprovalValues(self, approval_values, field_values, phases,
+                            issue_id=None, project_id=None):
+    # type: (Sequence[proto.tracker_pb2.ApprovalValue],
+    #     Sequence[proto.tracker_pb2.FieldValue],
+    #     Sequence[proto.tracker_pb2.Phase], Optional[int], Optional[int]) ->
+    #     Sequence[api_proto.issue_objects_pb2.ApprovalValue]
+    """Convert sequence of approval_values to protoc ApprovalValues.
+
+    `approval_values` may belong to a template or an issue. If they belong to a
+    template, `project_id` should be given for the project the template is in.
+    If these are issue `approval_values` `issue_id` should be given`.
+    So, one of `issue_id` or `project_id` must be provided.
+    If both are given, we ignore `project_id` and assume the `approval_values`
+    belong to an issue.
+
+    Args:
+      approval_values: List of ApprovalValues.
+      field_values: List of FieldValues that may belong to the approval_values.
+      phases: List of Phases that may be associated with the approval_values.
+      issue_id: ID of the Issue that the `approval_values` belong to.
+      project_id: ID of the Project that the `approval_values`
+        template belongs to.
+
+    Returns:
+      Sequence of protoc ApprovalValues in the same order they are given in
+      in `approval_values`. In the event any approval definitions in
+      `approval_values` are not found, they will be omitted from the result.
+
+    Raises:
+      InputException if neither `issue_id` nor `project_id` is given.
+    """
+
+    approval_ids = [av.approval_id for av in approval_values]
+    resource_names_dict = {}
+    if issue_id is not None:
+      # Only issue approval_values have resource names.
+      resource_names_dict = rnc.ConvertApprovalValueNames(
+          self.cnxn, issue_id, self.services)
+      project_id = self.services.issue.GetIssue(self.cnxn, issue_id).project_id
+    elif project_id is None:
+      raise exceptions.InputException(
+          'One  `issue_id` or `project_id` must be given.')
+
+    phase_names_by_id = {phase.phase_id: phase.name for phase in phases}
+    ad_names_dict = rnc.ConvertApprovalDefNames(
+        self.cnxn, approval_ids, project_id, self.services)
+
+    # Organize the field values by the approval values they are
+    # associated with.
+    config = self.services.config.GetProjectConfig(self.cnxn, project_id)
+    fds_by_id = {fd.field_id: fd for fd in config.field_defs}
+    fvs_by_parent_approvals = collections.defaultdict(list)
+    for fv in field_values:
+      fd = fds_by_id.get(fv.field_id)
+      if fd and fd.approval_id:
+        fvs_by_parent_approvals[fd.approval_id].append(fv)
+
+    api_avs = []
+    for av in approval_values:
+      # We only skip missing approval names if we are converting issue approval
+      # values.
+      if issue_id is not None and av.approval_id not in resource_names_dict:
+        continue
+
+      name = resource_names_dict.get(av.approval_id)
+      approval_def = ad_names_dict.get(av.approval_id)
+      approvers = rnc.ConvertUserNames(av.approver_ids).values()
+      status = self._ComputeApprovalValueStatus(av.status)
+      setter = rnc.ConvertUserName(av.setter_id)
+      phase = phase_names_by_id.get(av.phase_id)
+
+      field_values = self.ConvertFieldValues(
+          fvs_by_parent_approvals[av.approval_id], project_id, phases)
+
+      api_item = issue_objects_pb2.ApprovalValue(
+          name=name,
+          approval_def=approval_def,
+          approvers=approvers,
+          status=status,
+          setter=setter,
+          field_values=field_values,
+          phase=phase)
+      if av.set_on:
+        api_item.set_time.FromSeconds(av.set_on)
+      api_avs.append(api_item)
+
+    return api_avs
+
+  def _ComputeApprovalValueStatus(self, status):
+    # type: (proto.tracker_pb2.ApprovalStatus) ->
+    #     api_proto.issue_objects_pb2.Issue.ApprovalStatus
+    """Convert a protorpc ApprovalStatus to a protoc Issue.ApprovalStatus."""
+    try:
+      return _APPROVAL_STATUS_CONVERT[status]
+    except KeyError:
+      raise ValueError('Unrecognized tracker_pb2.ApprovalStatus enum')
+
+  # Projects
+
+  def ConvertIssueTemplates(self, project_id, templates):
+    # type: (int, Sequence[proto.tracker_pb2.TemplateDef]) ->
+    #     Sequence[api_proto.project_objects_pb2.IssueTemplate]
+    """Convert a Sequence of TemplateDefs to protoc IssueTemplates.
+
+    Args:
+      project_id: ID of the Project the templates belong to.
+      templates: Sequence of TemplateDef protorpc objects.
+
+    Returns:
+      Sequence of protoc IssueTemplate in the same order they are given in
+      `templates`. In the rare event that any templates are not found,
+      they will be omitted from the result.
+    """
+    api_templates = []
+
+    resource_names_dict = rnc.ConvertTemplateNames(
+        self.cnxn, project_id, [template.template_id for template in templates],
+        self.services)
+
+    for template in templates:
+      if template.template_id not in resource_names_dict:
+        continue
+      name = resource_names_dict.get(template.template_id)
+      summary_must_be_edited = template.summary_must_be_edited
+      template_privacy = self._ComputeTemplatePrivacy(template)
+      default_owner = self._ComputeTemplateDefaultOwner(template)
+      component_required = template.component_required
+      admins = rnc.ConvertUserNames(template.admin_ids).values()
+      issue = self._FillIssueFromTemplate(template, project_id)
+      approval_values = self.ConvertApprovalValues(
+          template.approval_values, template.field_values, template.phases,
+          project_id=project_id)
+      api_templates.append(
+          project_objects_pb2.IssueTemplate(
+              name=name,
+              display_name=template.name,
+              issue=issue,
+              approval_values=approval_values,
+              summary_must_be_edited=summary_must_be_edited,
+              template_privacy=template_privacy,
+              default_owner=default_owner,
+              component_required=component_required,
+              admins=admins))
+
+    return api_templates
+
+  def _FillIssueFromTemplate(self, template, project_id):
+    # type: (proto.tracker_pb2.TemplateDef, int) ->
+    #     api_proto.issue_objects_pb2.Issue
+    """Convert a TemplateDef to its embedded protoc Issue.
+
+    IssueTemplate does not set the following fields:
+      name
+      reporter
+      cc_users
+      blocked_on_issue_refs
+      blocking_issue_refs
+      create_time
+      close_time
+      modify_time
+      component_modify_time
+      status_modify_time
+      owner_modify_time
+      attachment_count
+      star_count
+
+    Args:
+      template: TemplateDef protorpc objects.
+      project_id: ID of the Project the template belongs to.
+
+    Returns:
+      protoc Issue filled with data from given `template`.
+    """
+    summary = template.summary
+    state = issue_objects_pb2.IssueContentState.Value('ACTIVE')
+    status = issue_objects_pb2.Issue.StatusValue(
+        status=template.status,
+        derivation=issue_objects_pb2.Derivation.Value('EXPLICIT'))
+    owner = None
+    if template.owner_id is not None:
+      owner = issue_objects_pb2.Issue.UserValue(
+          user=rnc.ConvertUserNames([template.owner_id]).get(template.owner_id))
+    labels = self.ConvertLabels(template.labels, [], project_id)
+    components_dict = rnc.ConvertComponentDefNames(
+        self.cnxn, template.component_ids, project_id, self.services)
+    components = []
+    for component_resource_name in components_dict.values():
+      components.append(
+          issue_objects_pb2.Issue.ComponentValue(
+              component=component_resource_name,
+              derivation=issue_objects_pb2.Derivation.Value('EXPLICIT')))
+    non_approval_fvs = self._GetNonApprovalFieldValues(
+        template.field_values, project_id)
+    field_values = self.ConvertFieldValues(
+        non_approval_fvs, project_id, template.phases)
+    field_values.extend(
+        self.ConvertEnumFieldValues(template.labels, [], project_id))
+    phases = self._ComputePhases(template.phases)
+
+    filled_issue = issue_objects_pb2.Issue(
+        summary=summary,
+        state=state,
+        status=status,
+        owner=owner,
+        labels=labels,
+        components=components,
+        field_values=field_values,
+        phases=phases)
+    return filled_issue
+
+  def _ComputeTemplatePrivacy(self, template):
+    # type: (proto.tracker_pb2.TemplateDef) ->
+    #     api_proto.project_objects_pb2.IssueTemplate.TemplatePrivacy
+    """Convert a protorpc TemplateDef to its protoc TemplatePrivacy."""
+    if template.members_only:
+      return project_objects_pb2.IssueTemplate.TemplatePrivacy.Value(
+          'MEMBERS_ONLY')
+    else:
+      return project_objects_pb2.IssueTemplate.TemplatePrivacy.Value('PUBLIC')
+
+  def _ComputeTemplateDefaultOwner(self, template):
+    # type: (proto.tracker_pb2.TemplateDef) ->
+    #     api_proto.project_objects_pb2.IssueTemplate.DefaultOwner
+    """Convert a protorpc TemplateDef to its protoc DefaultOwner."""
+    if template.owner_defaults_to_member:
+      return project_objects_pb2.IssueTemplate.DefaultOwner.Value(
+          'PROJECT_MEMBER_REPORTER')
+    else:
+      return project_objects_pb2.IssueTemplate.DefaultOwner.Value(
+          'DEFAULT_OWNER_UNSPECIFIED')
+
+  def _ComputePhases(self, phases):
+    # type: (proto.tracker_pb2.TemplateDef) -> Sequence[str]
+    """Convert a protorpc TemplateDef to its sorted string phases."""
+    sorted_phases = sorted(phases, key=lambda phase: phase.rank)
+    return [phase.name for phase in sorted_phases]
+
+  def ConvertLabels(self, labels, derived_labels, project_id):
+    # type: (Sequence[str], Sequence[str], int) ->
+    #     Sequence[api_proto.issue_objects_pb2.Issue.LabelValue]
+    """Convert string labels to LabelValues for non-enum-field labels
+
+    Args:
+      labels: Sequence of string labels
+      project_id: ID of the Project these labels belong to.
+
+    Return:
+      Sequence of protoc IssueValues for given `labels` that
+      do not represent enum field values.
+    """
+    config = self.services.config.GetProjectConfig(self.cnxn, project_id)
+    non_fd_labels, non_fd_der_labels = tbo.ExplicitAndDerivedNonMaskedLabels(
+        labels, derived_labels, config)
+    api_labels = []
+    for label in non_fd_labels:
+      api_labels.append(
+          issue_objects_pb2.Issue.LabelValue(
+              label=label,
+              derivation=issue_objects_pb2.Derivation.Value('EXPLICIT')))
+    for label in non_fd_der_labels:
+      api_labels.append(
+          issue_objects_pb2.Issue.LabelValue(
+              label=label,
+              derivation=issue_objects_pb2.Derivation.Value('RULE')))
+    return api_labels
+
+  def ConvertEnumFieldValues(self, labels, derived_labels, project_id):
+    # type: (Sequence[str], Sequence[str], int) ->
+    #     Sequence[api_proto.issue_objects_pb2.FieldValue]
+    """Convert string labels to FieldValues for enum-field labels
+
+    Args:
+      labels: Sequence of string labels
+      project_id: ID of the Project these labels belong to.
+
+    Return:
+      Sequence of protoc FieldValues only for given `labels` that
+      represent enum field values.
+    """
+    config = self.services.config.GetProjectConfig(self.cnxn, project_id)
+    enum_ids_by_name = {
+        fd.field_name.lower(): fd.field_id
+        for fd in config.field_defs
+        if fd.field_type is tracker_pb2.FieldTypes.ENUM_TYPE
+        and not fd.is_deleted
+    }
+    resource_names_dict = rnc.ConvertFieldDefNames(
+        self.cnxn, enum_ids_by_name.values(), project_id, self.services)
+
+    api_fvs = []
+
+    labels_by_prefix = tbo.LabelsByPrefix(labels, enum_ids_by_name.keys())
+    for lower_field_name, values in labels_by_prefix.items():
+      field_id = enum_ids_by_name.get(lower_field_name)
+      resource_name = resource_names_dict.get(field_id)
+      if not resource_name:
+        continue
+      api_fvs.extend(
+          [
+              issue_objects_pb2.FieldValue(
+                  field=resource_name,
+                  value=value,
+                  derivation=issue_objects_pb2.Derivation.Value(
+                      'EXPLICIT')) for value in values
+          ])
+
+    der_labels_by_prefix = tbo.LabelsByPrefix(
+        derived_labels, enum_ids_by_name.keys())
+    for lower_field_name, values in der_labels_by_prefix.items():
+      field_id = enum_ids_by_name.get(lower_field_name)
+      resource_name = resource_names_dict.get(field_id)
+      if not resource_name:
+        continue
+      api_fvs.extend(
+          [
+              issue_objects_pb2.FieldValue(
+                  field=resource_name,
+                  value=value,
+                  derivation=issue_objects_pb2.Derivation.Value('RULE'))
+              for value in values
+          ])
+
+    return api_fvs
+
+  def ConvertProject(self, project):
+    # type: (proto.project_pb2.Project) ->
+    #     api_proto.project_objects_pb2.Project
+    """Convert a protorpc Project to its protoc Project."""
+
+    return project_objects_pb2.Project(
+        name=rnc.ConvertProjectName(
+            self.cnxn, project.project_id, self.services),
+        display_name=project.project_name,
+        summary=project.summary,
+        thumbnail_url=project_helpers.GetThumbnailUrl(project.logo_gcs_id))
+
+  def ConvertProjects(self, projects):
+    # type: (Sequence[proto.project_pb2.Project]) ->
+    #     Sequence[api_proto.project_objects_pb2.Project]
+    """Convert a Sequence of protorpc Projects to protoc Projects."""
+    return [self.ConvertProject(proj) for proj in projects]
+
+  def ConvertProjectConfig(self, project_config):
+    # type: (proto.tracker_pb2.ProjectIssueConfig) ->
+    #     api_proto.project_objects_pb2.ProjectConfig
+    """Convert protorpc ProjectIssueConfig to protoc ProjectConfig."""
+    project = self.services.project.GetProject(
+        self.cnxn, project_config.project_id)
+    project_grid_config = project_objects_pb2.ProjectConfig.GridViewConfig(
+        default_x_attr=project_config.default_x_attr,
+        default_y_attr=project_config.default_y_attr)
+    template_names = rnc.ConvertTemplateNames(
+        self.cnxn, project_config.project_id, [
+            project_config.default_template_for_developers,
+            project_config.default_template_for_users
+        ], self.services)
+    return project_objects_pb2.ProjectConfig(
+        name=rnc.ConvertProjectConfigName(
+            self.cnxn, project_config.project_id, self.services),
+        exclusive_label_prefixes=project_config.exclusive_label_prefixes,
+        member_default_query=project_config.member_default_query,
+        default_sort=project_config.default_sort_spec,
+        default_columns=self._ComputeIssuesListColumns(
+            project_config.default_col_spec),
+        project_grid_config=project_grid_config,
+        member_default_template=template_names.get(
+            project_config.default_template_for_developers),
+        non_members_default_template=template_names.get(
+            project_config.default_template_for_users),
+        revision_url_format=project.revision_url_format,
+        custom_issue_entry_url=project_config.custom_issue_entry_url)
+
+  def CreateProjectMember(self, cnxn, project_id, user_id, role):
+    # type: (MonorailContext, int, int, str) ->
+    #     api_proto.project_objects_pb2.ProjectMember
+    """Creates a ProjectMember object from specified parameters.
+
+    Args:
+      cnxn: MonorailConnection object.
+      project_id: ID of the Project the User is a member of.
+      user_id: ID of the user who is a member.
+      role: str specifying the user's role based on a ProjectRole value.
+
+    Return:
+      A protoc ProjectMember object.
+    """
+    name = rnc.ConvertProjectMemberName(
+        cnxn, project_id, user_id, self.services)
+    return project_objects_pb2.ProjectMember(
+        name=name,
+        role=project_objects_pb2.ProjectMember.ProjectRole.Value(role))
+
+  def ConvertLabelDefs(self, label_defs, project_id):
+    # type: (Sequence[proto.tracker_pb2.LabelDef], int) ->
+    #     Sequence[api_proto.project_objects_pb2.LabelDef]
+    """Convert protorpc LabelDefs to protoc LabelDefs"""
+    resource_names_dict = rnc.ConvertLabelDefNames(
+        self.cnxn, [ld.label for ld in label_defs], project_id, self.services)
+
+    api_lds = []
+    for ld in label_defs:
+      state = project_objects_pb2.LabelDef.LabelDefState.Value('ACTIVE')
+      if ld.deprecated:
+        state = project_objects_pb2.LabelDef.LabelDefState.Value('DEPRECATED')
+      api_lds.append(
+          project_objects_pb2.LabelDef(
+              name=resource_names_dict.get(ld.label),
+              value=ld.label,
+              docstring=ld.label_docstring,
+              state=state))
+    return api_lds
+
+  def ConvertStatusDefs(self, status_defs, project_id):
+    # type: (Sequence[proto.tracker_pb2.StatusDef], int) ->
+    #     Sequence[api_proto.project_objects_pb2.StatusDef]
+    """Convert protorpc StatusDefs to protoc StatusDefs
+
+    Args:
+      status_defs: Sequence of StatusDefs.
+      project_id: ID of the Project these belong to.
+
+    Returns:
+      Sequence of protoc StatusDefs in the same order they are given in
+      `status_defs`.
+    """
+    resource_names_dict = rnc.ConvertStatusDefNames(
+        self.cnxn, [sd.status for sd in status_defs], project_id, self.services)
+    config = self.services.config.GetProjectConfig(self.cnxn, project_id)
+    mergeable_statuses = set(config.statuses_offer_merge)
+
+    # Rank is only surfaced as positional value in well_known_statuses
+    rank_by_status = {}
+    for rank, sd in enumerate(config.well_known_statuses):
+      rank_by_status[sd.status] = rank
+
+    api_sds = []
+    for sd in status_defs:
+      state = project_objects_pb2.StatusDef.StatusDefState.Value('ACTIVE')
+      if sd.deprecated:
+        state = project_objects_pb2.StatusDef.StatusDefState.Value('DEPRECATED')
+
+      if sd.means_open:
+        status_type = project_objects_pb2.StatusDef.StatusDefType.Value('OPEN')
+      else:
+        if sd.status in mergeable_statuses:
+          status_type = project_objects_pb2.StatusDef.StatusDefType.Value(
+              'MERGED')
+        else:
+          status_type = project_objects_pb2.StatusDef.StatusDefType.Value(
+              'CLOSED')
+
+      api_sd = project_objects_pb2.StatusDef(
+          name=resource_names_dict.get(sd.status),
+          value=sd.status,
+          type=status_type,
+          rank=rank_by_status[sd.status],
+          docstring=sd.status_docstring,
+          state=state,
+      )
+      api_sds.append(api_sd)
+    return api_sds
+
+  def ConvertComponentDef(self, component_def):
+    # type: (proto.tracker_pb2.ComponentDef) ->
+    #     api_proto.project_objects.ComponentDef
+    """Convert a protorpc ComponentDef to a protoc ComponentDef."""
+    return self.ConvertComponentDefs([component_def],
+                                     component_def.project_id)[0]
+
+  def ConvertComponentDefs(self, component_defs, project_id):
+    # type: (Sequence[proto.tracker_pb2.ComponentDef], int) ->
+    #     Sequence[api_proto.project_objects.ComponentDef]
+    """Convert sequence of protorpc ComponentDefs to protoc ComponentDefs
+
+    Args:
+      component_defs: Sequence of protoc ComponentDefs.
+      project_id: ID of the Project these belong to.
+
+    Returns:
+      Sequence of protoc ComponentDefs in the same order they are given in
+      `component_defs`.
+    """
+    resource_names_dict = rnc.ConvertComponentDefNames(
+        self.cnxn, [cd.component_id for cd in component_defs], project_id,
+        self.services)
+    involved_user_ids = tbo.UsersInvolvedInComponents(component_defs)
+    user_resource_names_dict = rnc.ConvertUserNames(involved_user_ids)
+
+    all_label_ids = set()
+    for cd in component_defs:
+      all_label_ids.update(cd.label_ids)
+
+    # If this becomes a performance issue, we should add bulk look up.
+    labels_by_id = {
+        label_id: self.services.config.LookupLabel(
+            self.cnxn, project_id, label_id) for label_id in all_label_ids
+    }
+
+    api_cds = []
+    for cd in component_defs:
+      state = project_objects_pb2.ComponentDef.ComponentDefState.Value('ACTIVE')
+      if cd.deprecated:
+        state = project_objects_pb2.ComponentDef.ComponentDefState.Value(
+            'DEPRECATED')
+
+      api_cd = project_objects_pb2.ComponentDef(
+          name=resource_names_dict.get(cd.component_id),
+          value=cd.path,
+          docstring=cd.docstring,
+          state=state,
+          admins=[
+              user_resource_names_dict.get(admin_id)
+              for admin_id in cd.admin_ids
+          ],
+          ccs=[user_resource_names_dict.get(cc_id) for cc_id in cd.cc_ids],
+          creator=user_resource_names_dict.get(cd.creator_id),
+          modifier=user_resource_names_dict.get(cd.modifier_id),
+          create_time=timestamp_pb2.Timestamp(seconds=cd.created),
+          modify_time=timestamp_pb2.Timestamp(seconds=cd.modified),
+          labels=[labels_by_id[label_id] for label_id in cd.label_ids],
+      )
+      api_cds.append(api_cd)
+    return api_cds
+
+  def ConvertProjectSavedQueries(self, saved_queries, project_id):
+    # type: (Sequence[proto.tracker_pb2.SavedQuery], int) ->
+    #     Sequence(api_proto.project_objects.ProjectSavedQuery)
+    """Convert sequence of protorpc SavedQueries to protoc ProjectSavedQueries
+
+    Args:
+      saved_queries: Sequence of SavedQueries.
+      project_id: ID of the Project these belong to.
+
+    Returns:
+      Sequence of protoc ProjectSavedQueries in the same order they are given in
+      `saved_queries`. In the event any items in `saved_queries` are not found
+      or don't belong to the project, they will be omitted from the result.
+    """
+    resource_names_dict = rnc.ConvertProjectSavedQueryNames(
+        self.cnxn, [sq.query_id for sq in saved_queries], project_id,
+        self.services)
+    api_psqs = []
+    for sq in saved_queries:
+      if sq.query_id not in resource_names_dict:
+        continue
+
+      # TODO(crbug/monorail/7756): Remove base_query_id, avoid confusions.
+      # Until then we have to expand the query by including base_query_id.
+      # base_query_id can only be in the set of DEFAULT_CANNED_QUERIES.
+      if sq.base_query_id:
+        query = '{} {}'.format(tbo.GetBuiltInQuery(sq.base_query_id), sq.query)
+      else:
+        query = sq.query
+
+      api_psqs.append(
+          project_objects_pb2.ProjectSavedQuery(
+              name=resource_names_dict.get(sq.query_id),
+              display_name=sq.name,
+              query=query))
+    return api_psqs
diff --git a/api/v3/frontend_servicer.py b/api/v3/frontend_servicer.py
new file mode 100644
index 0000000..7374f1b
--- /dev/null
+++ b/api/v3/frontend_servicer.py
@@ -0,0 +1,107 @@
+# Copyright 2020 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style
+# license that can be found in the LICENSE file or at
+# https://developers.google.com/open-source/licenses/bsd
+
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+
+from api import resource_name_converters as rnc
+from api.v3 import monorail_servicer
+from api.v3.api_proto import frontend_pb2
+from api.v3.api_proto import project_objects_pb2
+from api.v3.api_proto import frontend_prpc_pb2
+from businesslogic import work_env
+
+
+class FrontendServicer(monorail_servicer.MonorailServicer):
+  """Handle frontend specific API requests.
+  Each API request is implemented with a method as defined in the
+  .proto file. Each method does any request-specific validation, uses work_env
+  to safely operate on business objects, and returns a response proto.
+  """
+
+  DESCRIPTION = frontend_prpc_pb2.FrontendServiceDescription
+
+  @monorail_servicer.PRPCMethod
+  def GatherProjectEnvironment(self, mc, request):
+    # type: (MonorailContext, GatherProjectEnvironmentRequest) ->
+    #     GatherProjectEnvironmentResponse
+    """pRPC API method that implements GatherProjectEnvironment.
+
+    Raises:
+      InputException if the project resource name provided is invalid.
+      NoSuchProjectException if the parent project is not found.
+      PermissionException if user is not allowed to view this project.
+    """
+
+    project_id = rnc.IngestProjectName(mc.cnxn, request.parent, self.services)
+
+    with work_env.WorkEnv(mc, self.services) as we:
+      project = we.GetProject(project_id)
+      project_config = we.GetProjectConfig(project_id)
+
+    api_project = self.converter.ConvertProject(project)
+    api_project_config = self.converter.ConvertProjectConfig(project_config)
+    api_status_defs = self.converter.ConvertStatusDefs(
+        project_config.well_known_statuses, project_id)
+    api_label_defs = self.converter.ConvertLabelDefs(
+        project_config.well_known_labels, project_id)
+    api_component_defs = self.converter.ConvertComponentDefs(
+        project_config.component_defs, project_id)
+    api_field_defs = self.converter.ConvertFieldDefs(
+        project_config.field_defs, project_id)
+    api_approval_defs = self.converter.ConvertApprovalDefs(
+        project_config.approval_defs, project_id)
+    saved_queries = self.services.features.GetCannedQueriesByProjectID(
+        mc.cnxn, project_id)
+    api_sqs = self.converter.ConvertProjectSavedQueries(
+        saved_queries, project_id)
+
+    return frontend_pb2.GatherProjectEnvironmentResponse(
+        project=api_project,
+        project_config=api_project_config,
+        statuses=api_status_defs,
+        well_known_labels=api_label_defs,
+        components=api_component_defs,
+        fields=api_field_defs,
+        approval_fields=api_approval_defs,
+        saved_queries=api_sqs)
+
+  @monorail_servicer.PRPCMethod
+  def GatherProjectMembershipsForUser(self, mc, request):
+    # type: (MonorailContext, GatherProjectMembershipsForUserRequest) ->
+    #     GatherProjectMembershipsForUserResponse
+    """pRPC API method that implements GatherProjectMembershipsForUser.
+
+    Raises:
+      NoSuchUserException if the user is not found.
+      InputException if the user resource name is invalid.
+    """
+
+    user_id = rnc.IngestUserName(mc.cnxn, request.user, self.services)
+
+    project_memberships = []
+
+    with work_env.WorkEnv(mc, self.services) as we:
+      owner, committer, contributor = we.GatherProjectMembershipsForUser(
+          user_id)
+
+    for project_id in owner:
+      project_member = self.converter.CreateProjectMember(
+          mc.cnxn, project_id, user_id, 'OWNER')
+      project_memberships.append(project_member)
+
+    for project_id in committer:
+      project_member = self.converter.CreateProjectMember(
+          mc.cnxn, project_id, user_id, 'COMMITTER')
+      project_memberships.append(project_member)
+
+    for project_id in contributor:
+      project_member = self.converter.CreateProjectMember(
+          mc.cnxn, project_id, user_id, 'CONTRIBUTOR')
+      project_memberships.append(project_member)
+
+    return frontend_pb2.GatherProjectMembershipsForUserResponse(
+        project_memberships=project_memberships)
diff --git a/api/v3/hotlists_servicer.py b/api/v3/hotlists_servicer.py
new file mode 100644
index 0000000..2ea2a31
--- /dev/null
+++ b/api/v3/hotlists_servicer.py
@@ -0,0 +1,266 @@
+# Copyright 2020 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style
+# license that can be found in the LICENSE file or at
+# https://developers.google.com/open-source/licenses/bsd
+
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+
+from google.protobuf import empty_pb2
+
+from api import resource_name_converters as rnc
+from api.v3 import monorail_servicer
+from api.v3.api_proto import feature_objects_pb2
+from api.v3.api_proto import hotlists_pb2
+from api.v3.api_proto import hotlists_prpc_pb2
+from businesslogic import work_env
+from framework import exceptions
+from features import features_constants
+from tracker import tracker_constants
+
+
+class HotlistsServicer(monorail_servicer.MonorailServicer):
+  """Handle API requests related to Hotlist objects.
+  Each API request is implemented with a method as defined in the
+  .proto file. Each method does any request-specific validation, uses work_env
+  to safely operate on business objects, and returns a response proto.
+  """
+  # NOTE(crbug/monorail/7614): Until the referenced cleanup is complete,
+  # all servicer methods that are scoped to a single Project need to call
+  # mc.LookupLoggedInUserPerms.
+  # Methods in this file do not because hotlists can span projects.
+
+  DESCRIPTION = hotlists_prpc_pb2.HotlistsServiceDescription
+
+  @monorail_servicer.PRPCMethod
+  def ListHotlistItems(self, mc, request):
+    # type: (MonorailContext, ListHotlistItemsRequest) ->
+    #     ListHotlistItemsResponse
+    """pRPC API method that implements ListHotlistItems.
+
+      Raises:
+        NoSuchHotlistException if the hotlist is not found.
+        PermissionException if the user is not allowed to view the hotlist.
+        InputException if the request.page_token is invalid, the request does
+          not match the previous request that provided the given page_token, or
+          the page_size is a negative value.
+    """
+    hotlist_id = rnc.IngestHotlistName(request.parent)
+    if request.page_size < 0:
+      raise exceptions.InputException('`page_size` cannot be negative.')
+    page_size = request.page_size
+    if (not request.page_size or
+        request.page_size > features_constants.DEFAULT_RESULTS_PER_PAGE):
+      page_size = features_constants.DEFAULT_RESULTS_PER_PAGE
+
+    # TODO(crbug/monorail/7104): take start from request.page_token
+    start = 0
+    sort_spec = request.order_by.replace(',', ' ')
+
+    with work_env.WorkEnv(mc, self.services) as we:
+      list_result = we.ListHotlistItems(
+          hotlist_id, page_size, start,
+          tracker_constants.ALL_ISSUES_CAN, sort_spec, '')
+
+    # TODO(crbug/monorail/7104): plug in next_page_token when it's been
+    # implemented.
+    next_page_token = ''
+    return hotlists_pb2.ListHotlistItemsResponse(
+        items=self.converter.ConvertHotlistItems(hotlist_id, list_result.items),
+        next_page_token=next_page_token)
+
+
+  @monorail_servicer.PRPCMethod
+  def RerankHotlistItems(self, mc, request):
+    # type: (MonorailContext, RerankHotlistItemsRequest) -> Empty
+    """pRPC API method that implements RerankHotlistItems.
+
+    Raises:
+      NoSuchHotlistException if the hotlist is not found.
+      PermissionException if the user is not allowed to rerank the hotlist.
+      InputException if request.target_position is invalid or
+        request.hotlist_items is empty or contains invalid items.
+      NoSuchIssueException if hotlist item does not exist.
+    """
+
+    hotlist_id = rnc.IngestHotlistName(request.name)
+    moved_issue_ids = rnc.IngestHotlistItemNames(
+        mc.cnxn, request.hotlist_items, self.services)
+
+    with work_env.WorkEnv(mc, self.services) as we:
+      we.RerankHotlistItems(
+          hotlist_id, moved_issue_ids, request.target_position)
+
+    return empty_pb2.Empty()
+
+
+  @monorail_servicer.PRPCMethod
+  def RemoveHotlistItems(self, mc, request):
+    # type: (MonorailContext, RemoveHotlistItemsRequest) -> Empty
+    """pPRC API method that implements RemoveHotlistItems.
+
+    Raises:
+      NoSuchHotlistException if the hotlist is not found.
+      PermissionException if the user is not allowed to edit the hotlist.
+      InputException if the items to be removed are not found in the hotlist.
+    """
+
+    hotlist_id = rnc.IngestHotlistName(request.parent)
+    remove_issue_ids = rnc.IngestIssueNames(
+        mc.cnxn, request.issues, self.services)
+
+    with work_env.WorkEnv(mc, self.services) as we:
+      we.RemoveHotlistItems(hotlist_id, remove_issue_ids)
+
+    return empty_pb2.Empty()
+
+
+  @monorail_servicer.PRPCMethod
+  def AddHotlistItems(self, mc, request):
+    # type: (MonorailContext, AddHotlistItemsRequest) -> Empty
+    """pRPC API method that implements AddHotlistItems.
+
+    Raises:
+      NoSuchHotlistException if the hotlist is not found.
+      PermissionException if the user is not allowed to edit the hotlist.
+      InputException if the request.target_position is invalid or the given
+        list of issues to add is empty or invalid.
+    """
+    hotlist_id = rnc.IngestHotlistName(request.parent)
+    new_issue_ids = rnc.IngestIssueNames(mc.cnxn, request.issues, self.services)
+
+    with work_env.WorkEnv(mc, self.services) as we:
+      we.AddHotlistItems(hotlist_id, new_issue_ids, request.target_position)
+
+    return empty_pb2.Empty()
+
+
+  @monorail_servicer.PRPCMethod
+  def RemoveHotlistEditors(self, mc, request):
+    # type: (MonorailContext, RemoveHotlistEditorsRequest) -> Empty
+    """pPRC API method that implements RemoveHotlistEditors.
+
+    Raises:
+      NoSuchHotlistException if the hotlist is not found.
+      PermissionException if the user is not allowed to edit the hotlist.
+      InputException if the editors to be removed are not found in the hotlist.
+    """
+
+    hotlist_id = rnc.IngestHotlistName(request.name)
+    remove_user_ids = rnc.IngestUserNames(
+        mc.cnxn, request.editors, self.services)
+
+    with work_env.WorkEnv(mc, self.services) as we:
+      we.RemoveHotlistEditors(hotlist_id, remove_user_ids)
+
+    return empty_pb2.Empty()
+
+
+  @monorail_servicer.PRPCMethod
+  def GetHotlist(self, mc, request):
+    # type: (MonorailContext, GetHotlistRequest) -> Hotlist
+    """pRPC API method that implements GetHotlist.
+
+    Raises:
+      InputException if the given name does not have a valid format.
+      NoSuchHotlistException if the hotlist is not found.
+      PermissionException if the user is not allowed to view the hotlist.
+    """
+
+    hotlist_id = rnc.IngestHotlistName(request.name)
+
+    with work_env.WorkEnv(mc, self.services) as we:
+      hotlist = we.GetHotlist(hotlist_id)
+
+    return self.converter.ConvertHotlist(hotlist)
+
+  @monorail_servicer.PRPCMethod
+  def GatherHotlistsForUser(self, mc, request):
+    # type: (MonorailContext, GatherHotlistsForUserRequest)
+    #   -> GatherHotlistsForUserResponse
+    """pRPC API method that implements GatherHotlistsForUser.
+
+    Raises:
+      NoSuchUserException if the user is not found.
+      InputException if some request parameters are invalid.
+    """
+
+    user_id = rnc.IngestUserName(mc.cnxn, request.user, self.services)
+
+    with work_env.WorkEnv(mc, self.services) as we:
+      hotlists = we.ListHotlistsByUser(user_id)
+
+    return hotlists_pb2.GatherHotlistsForUserResponse(
+        hotlists=self.converter.ConvertHotlists(hotlists))
+
+  @monorail_servicer.PRPCMethod
+  def UpdateHotlist(self, mc, request):
+    # type: (MonorailContext, UpdateHotlistRequest) -> UpdateHotlistResponse
+    """pRPC API method that implements UpdateHotlist.
+
+    Raises:
+      NoSuchHotlistException if the hotlist is not found.
+      PermissionException if the user is not allowed to make this update.
+      InputException if some request parameters are required and missing or
+        invalid.
+    """
+    if not request.update_mask:
+      raise exceptions.InputException('No paths given in `update_mask`.')
+    if not request.hotlist:
+      raise exceptions.InputException('No `hotlist` param given.')
+
+    if not request.update_mask.IsValidForDescriptor(
+        feature_objects_pb2.Hotlist.DESCRIPTOR):
+      raise exceptions.InputException('Invalid `update_mask` for `hotlist`')
+
+    hotlist_id = rnc.IngestHotlistName(request.hotlist.name)
+
+    update_args = {}
+    hotlist = request.hotlist
+    for path in request.update_mask.paths:
+      if path == 'display_name':
+        update_args['hotlist_name'] = hotlist.display_name
+      elif path == 'owner':
+        owner_id = rnc.IngestUserName(mc.cnxn, hotlist.owner, self.services)
+        update_args['owner_id'] = owner_id
+      elif path == 'editors':
+        add_editor_ids = rnc.IngestUserNames(
+            mc.cnxn, hotlist.editors, self.services)
+        update_args['add_editor_ids'] = add_editor_ids
+      elif path == 'summary':
+        update_args['summary'] = hotlist.summary
+      elif path == 'description':
+        update_args['description'] = hotlist.description
+      elif path == 'hotlist_privacy':
+        update_args['is_private'] = (
+            hotlist.hotlist_privacy == feature_objects_pb2.Hotlist
+            .HotlistPrivacy.Value('PRIVATE'))
+      elif path == 'default_columns':
+        update_args[
+            'default_col_spec'] = self.converter.IngestIssuesListColumns(
+                hotlist.default_columns)
+
+    with work_env.WorkEnv(mc, self.services) as we:
+      we.UpdateHotlist(hotlist_id, **update_args)
+      hotlist = we.GetHotlist(hotlist_id, use_cache=False)
+
+    return self.converter.ConvertHotlist(hotlist)
+
+  @monorail_servicer.PRPCMethod
+  def DeleteHotlist(self, mc, request):
+    # type: (MonorailContext, GetHotlistRequest) -> Empty
+    """pRPC API method that implements DeleteHotlist.
+
+    Raises:
+      InputException if the given name does not have a valid format.
+      NoSuchHotlistException if the hotlist is not found.
+      PermissionException if the user is not allowed to delete the hotlist.
+    """
+
+    hotlist_id = rnc.IngestHotlistName(request.name)
+
+    with work_env.WorkEnv(mc, self.services) as we:
+      we.DeleteHotlist(hotlist_id)
+
+    return empty_pb2.Empty()
diff --git a/api/v3/issues_servicer.py b/api/v3/issues_servicer.py
new file mode 100644
index 0000000..ebd545b
--- /dev/null
+++ b/api/v3/issues_servicer.py
@@ -0,0 +1,396 @@
+# Copyright 2020 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style
+# license that can be found in the LICENSE file or at
+# https://developers.google.com/open-source/licenses/bsd
+
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+
+import re
+
+from api import resource_name_converters as rnc
+from api.v3 import api_constants
+from api.v3 import converters
+from api.v3 import monorail_servicer
+from api.v3 import paginator
+from api.v3.api_proto import issues_pb2
+from api.v3.api_proto import issue_objects_pb2
+from api.v3.api_proto import issues_prpc_pb2
+from businesslogic import work_env
+from framework import exceptions
+
+# We accept only the following filter, and only on ListComments.
+# If we accept more complex filters in the future, introduce a library.
+_APPROVAL_DEF_FILTER_RE = re.compile(
+    r'approval = "(?P<approval_name>%s)"$' % rnc.APPROVAL_DEF_NAME_PATTERN)
+
+
+class IssuesServicer(monorail_servicer.MonorailServicer):
+  """Handle API requests related to Issue objects.
+  Each API request is implemented with a method as defined in the
+  .proto file that does any request-specific validation, uses work_env
+  to safely operate on business objects, and returns a response proto.
+  """
+
+  DESCRIPTION = issues_prpc_pb2.IssuesServiceDescription
+
+  @monorail_servicer.PRPCMethod
+  def GetIssue(self, mc, request):
+    # type: (MonorailContext, GetIssueRequest) -> Issue
+    """pRPC API method that implements GetIssue.
+
+    Raises:
+      InputException: the given name does not have a valid format.
+      NoSuchIssueException: the issue is not found.
+      PermissionException the user is not allowed to view the issue.
+    """
+    issue_id = rnc.IngestIssueName(mc.cnxn, request.name, self.services)
+    with work_env.WorkEnv(mc, self.services) as we:
+      # TODO(crbug/monorail/7614): Eliminate the need to do this lookup.
+      project = we.GetProjectByName(rnc.IngestProjectFromIssue(request.name))
+      mc.LookupLoggedInUserPerms(project)
+      issue = we.GetIssue(issue_id, allow_viewing_deleted=True)
+    return self.converter.ConvertIssue(issue)
+
+  @monorail_servicer.PRPCMethod
+  def BatchGetIssues(self, mc, request):
+    # type: (MonorailContext, BatchGetIssuesRequest) -> BatchGetIssuesResponse
+    """pRPC API method that implements BatchGetIssues.
+
+    Raises:
+      InputException: If `names` is formatted incorrectly. Or if a parent
+          collection in `names` does not match the value in `parent`.
+      NoSuchIssueException: If any of the given issues do not exist.
+      PermissionException If the requester does not have permission to view one
+          (or more) of the given issues.
+    """
+    if len(request.names) > api_constants.MAX_BATCH_ISSUES:
+      raise exceptions.InputException(
+          'Requesting %d issues when the allowed maximum is %d issues.' %
+          (len(request.names), api_constants.MAX_BATCH_ISSUES))
+    if request.parent:
+      parent_match = rnc._GetResourceNameMatch(
+          request.parent, rnc.PROJECT_NAME_RE)
+      parent_project = parent_match.group('project_name')
+      with exceptions.ErrorAggregator(exceptions.InputException) as err_agg:
+        for name in request.names:
+          try:
+            name_match = rnc._GetResourceNameMatch(name, rnc.ISSUE_NAME_RE)
+            issue_project = name_match.group('project')
+            if issue_project != parent_project:
+              err_agg.AddErrorMessage(
+                  '%s is not a child issue of %s.' % (name, request.parent))
+          except exceptions.InputException as e:
+            err_agg.AddErrorMessage(e.message)
+    with work_env.WorkEnv(mc, self.services) as we:
+      # NOTE(crbug/monorail/7614): Until the referenced cleanup is complete,
+      # all servicer methods that are scoped to a single Project need to call
+      # mc.LookupLoggedInUserPerms.
+      #  This method does not because it may be scoped to multiple projects.
+      issue_ids = rnc.IngestIssueNames(mc.cnxn, request.names, self.services)
+      issues_by_iid = we.GetIssuesDict(issue_ids)
+    return issues_pb2.BatchGetIssuesResponse(
+        issues=self.converter.ConvertIssues(
+            [issues_by_iid[issue_id] for issue_id in issue_ids]))
+
+  @monorail_servicer.PRPCMethod
+  def SearchIssues(self, mc, request):
+    # type: (MonorailContext, SearchIssuesRequest) -> SearchIssuesResponse
+    """pRPC API method that implements SearchIssue.
+
+    Raises:
+      InputException: if any given names in `projects` are invalid or if the
+        search query uses invalid syntax (ie: unmatched parentheses).
+    """
+    page_size = paginator.CoercePageSize(
+        request.page_size, api_constants.MAX_ISSUES_PER_PAGE)
+    pager = paginator.Paginator(
+        page_size=page_size,
+        order_by=request.order_by,
+        query=request.query,
+        projects=request.projects)
+
+    project_names = []
+    for resource_name in request.projects:
+      match = rnc._GetResourceNameMatch(resource_name, rnc.PROJECT_NAME_RE)
+      project_names.append(match.group('project_name'))
+
+    with work_env.WorkEnv(mc, self.services) as we:
+      # NOTE(crbug/monorail/7614): Until the referenced cleanup is complete,
+      # all servicer methods that are scoped to a single Project need to call
+      # mc.LookupLoggedInUserPerms.
+      #  This method does not because it may be scoped to multiple projects.
+      list_result = we.SearchIssues(
+          request.query, project_names, mc.auth.user_id, page_size,
+          pager.GetStart(request.page_token), request.order_by)
+
+    return issues_pb2.SearchIssuesResponse(
+        issues=self.converter.ConvertIssues(list_result.items),
+        next_page_token=pager.GenerateNextPageToken(list_result.next_start))
+
+  @monorail_servicer.PRPCMethod
+  def ListComments(self, mc, request):
+    # type: (MonorailContext, ListCommentsRequest) -> ListCommentsResponse
+    """pRPC API method that implements ListComments.
+
+    Raises:
+      InputException: the given name format or page_size are not valid.
+      NoSuchIssueException: the parent is not found.
+      PermissionException: the user is not allowed to view the parent.
+    """
+    issue_id = rnc.IngestIssueName(mc.cnxn, request.parent, self.services)
+    page_size = paginator.CoercePageSize(
+        request.page_size, api_constants.MAX_COMMENTS_PER_PAGE)
+    pager = paginator.Paginator(
+      parent=request.parent, page_size=page_size, filter_str=request.filter)
+    approval_id = None
+    if request.filter:
+      match = _APPROVAL_DEF_FILTER_RE.match(request.filter)
+      if match:
+        approval_id = rnc.IngestApprovalDefName(
+            mc.cnxn, match.group('approval_name'), self.services)
+      if not match:
+        raise exceptions.InputException(
+            'Filtering other than approval not supported.')
+
+    with work_env.WorkEnv(mc, self.services) as we:
+      # TODO(crbug/monorail/7614): Eliminate the need to do this lookup.
+      project = we.GetProjectByName(rnc.IngestProjectFromIssue(request.parent))
+      mc.LookupLoggedInUserPerms(project)
+      list_result = we.SafeListIssueComments(
+          issue_id, page_size, pager.GetStart(request.page_token),
+          approval_id=approval_id)
+    return issues_pb2.ListCommentsResponse(
+        comments=self.converter.ConvertComments(issue_id, list_result.items),
+        next_page_token=pager.GenerateNextPageToken(list_result.next_start))
+
+  @monorail_servicer.PRPCMethod
+  def ListApprovalValues(self, mc, request):
+    # type: (MonorailContext, ListApprovalValuesRequest) ->
+    #     ListApprovalValuesResponse
+    """pRPC API method that implements ListApprovalValues.
+
+    Raises:
+      InputException: the given parent does not have a valid format.
+      NoSuchIssueException: the parent issue is not found.
+      PermissionException the user is not allowed to view the parent issue.
+    """
+    issue_id = rnc.IngestIssueName(mc.cnxn, request.parent, self.services)
+    with work_env.WorkEnv(mc, self.services) as we:
+      # TODO(crbug/monorail/7614): Eliminate the need to do this lookup.
+      project = we.GetProjectByName(rnc.IngestProjectFromIssue(request.parent))
+      mc.LookupLoggedInUserPerms(project)
+      issue = we.GetIssue(issue_id)
+
+    api_avs = self.converter.ConvertApprovalValues(issue.approval_values,
+        issue.field_values, issue.phases, issue_id=issue_id)
+
+    return issues_pb2.ListApprovalValuesResponse(approval_values=api_avs)
+
+  @monorail_servicer.PRPCMethod
+  def MakeIssueFromTemplate(self, _mc, _request):
+    # type: (MonorailContext, MakeIssueFromTemplateRequest) -> Issue
+    """pRPC API method that implements MakeIssueFromTemplate.
+
+    Raises:
+      TODO(crbug/monorail/7197): Document errors when implemented
+    """
+    # Phase 1: Gather info
+    #   Get project id and template name from template resource name.
+    #   Get template pb.
+    #   Make tracker_pb2.IssueDelta from request.template_issue_delta, share
+    #   code with v3/ModifyIssue
+
+    # with work_env.WorkEnv(mc, self.services) as we:
+    #   project = ... get project from template.
+    #   mc.LookupLoggedInUserPerms(project)
+    #   created_issue = we.MakeIssueFromTemplate(template, description, delta)
+
+    # Return newly created API issue.
+    # return converters.ConvertIssue(created_issue)
+
+    return issue_objects_pb2.Issue()
+
+  @monorail_servicer.PRPCMethod
+  def MakeIssue(self, mc, request):
+    # type: (MonorailContext, MakeIssueRequest) -> Issue
+    """pRPC API method that implements MakeIssue.
+
+    Raises:
+      InputException if any given names do not have a valid format or if any
+        fields in the requested issue were invalid.
+      NoSuchProjectException if no project exists with the given parent.
+      FilterRuleException if proposed issue values violate any filter rules
+        that shows error.
+      PermissionException if user lacks sufficient permissions.
+    """
+    project_id = rnc.IngestProjectName(mc.cnxn, request.parent, self.services)
+    with work_env.WorkEnv(mc, self.services) as we:
+      # TODO(crbug/monorail/7614): Eliminate the need to do this lookup.
+      project = we.GetProject(project_id)
+      mc.LookupLoggedInUserPerms(project)
+
+    ingested_issue = self.converter.IngestIssue(
+        request.issue, project_id)
+    send_email = self.converter.IngestNotifyType(request.notify_type)
+
+    with work_env.WorkEnv(mc, self.services) as we:
+      created_issue = we.MakeIssue(
+          ingested_issue, request.description, send_email)
+      starred_issue = we.StarIssue(created_issue, True)
+
+    return self.converter.ConvertIssue(starred_issue)
+
+  @monorail_servicer.PRPCMethod
+  def ModifyIssues(self, mc, request):
+    # type: (MonorailContext, ModifyIssuesRequest) -> ModifyIssuesResponse
+    """pRPC API method that implements ModifyIssues.
+
+    Raises:
+      InputException if any given names do not have a valid format or if any
+        fields in the requested issue were invalid.
+      NoSuchIssueException if some issues weren't found.
+      NoSuchProjectException if no project was found for some given issues.
+      FilterRuleException if proposed issue changes violate any filter rules
+        that shows error.
+      PermissionException if user lacks sufficient permissions.
+    """
+    if not request.deltas:
+      return issues_pb2.ModifyIssuesResponse()
+    if len(request.deltas) > api_constants.MAX_MODIFY_ISSUES:
+      raise exceptions.InputException(
+          'Requesting %d updates when the allowed maximum is %d updates.' %
+          (len(request.deltas), api_constants.MAX_MODIFY_ISSUES))
+    impacted_issues_count = 0
+    for delta in request.deltas:
+      impacted_issues_count += (
+          len(delta.blocked_on_issues_remove) +
+          len(delta.blocking_issues_remove) +
+          len(delta.issue.blocking_issue_refs) +
+          len(delta.issue.blocked_on_issue_refs))
+      if 'merged_into_issue_ref' in delta.update_mask.paths:
+        impacted_issues_count += 1
+    if impacted_issues_count > api_constants.MAX_MODIFY_IMPACTED_ISSUES:
+      raise exceptions.InputException(
+          'Updates include %d impacted issues when the allowed maximum is %d.' %
+          (impacted_issues_count, api_constants.MAX_MODIFY_IMPACTED_ISSUES))
+    iid_delta_pairs = self.converter.IngestIssueDeltas(request.deltas)
+    with work_env.WorkEnv(mc, self.services) as we:
+      issues = we.ModifyIssues(
+          iid_delta_pairs,
+          attachment_uploads=self.converter.IngestAttachmentUploads(
+              request.uploads),
+          comment_content=request.comment_content,
+          send_email=self.converter.IngestNotifyType(request.notify_type))
+
+    return issues_pb2.ModifyIssuesResponse(
+        issues=self.converter.ConvertIssues(issues))
+
+  @monorail_servicer.PRPCMethod
+  def ModifyIssueApprovalValues(self, mc, request):
+    # type: (MonorailContext, ModifyIssueApprovalValuesRequest) ->
+    #     ModifyIssueApprovalValuesResponse
+    """pRPC API method that implements ModifyIssueApprovalValues.
+
+    Raises:
+      InputException if any fields in the delta were invalid.
+      NoSuchIssueException: if the issue of any ApprovalValue isn't found.
+      NoSuchProjectException: if the parent project of any ApprovalValue isn't
+          found.
+      NoSuchUserException: if any user value provided isn't found.
+      PermissionException if user lacks sufficient permissions.
+      # TODO(crbug/monorail/7925): Not all of these are yet thrown.
+    """
+    if len(request.deltas) > api_constants.MAX_MODIFY_APPROVAL_VALUES:
+      raise exceptions.InputException(
+          'Requesting %d updates when the allowed maximum is %d updates.' %
+          (len(request.deltas), api_constants.MAX_MODIFY_APPROVAL_VALUES))
+    response = issues_pb2.ModifyIssueApprovalValuesResponse()
+    delta_specifications = self.converter.IngestApprovalDeltas(
+        request.deltas, mc.auth.user_id)
+    send_email = self.converter.IngestNotifyType(request.notify_type)
+    with work_env.WorkEnv(mc, self.services) as we:
+      # NOTE(crbug/monorail/7614): Until the referenced cleanup is complete,
+      # all servicer methods that are scoped to a single Project need to call
+      # mc.LookupLoggedInUserPerms.
+      # This method does not because it may be scoped to multiple projects.
+      issue_approval_values = we.BulkUpdateIssueApprovalsV3(
+          delta_specifications, request.comment_content, send_email=send_email)
+    api_avs = []
+    for issue, approval_value in issue_approval_values:
+      api_avs.extend(
+          self.converter.ConvertApprovalValues(
+              [approval_value],
+              issue.field_values,
+              issue.phases,
+              issue_id=issue.issue_id))
+    response.approval_values.extend(api_avs)
+    return response
+
+  @monorail_servicer.PRPCMethod
+  def ModifyCommentState(self, mc, request):
+    # type: (MonorailContext, ModifyCommentStateRequest) ->
+    #     ModifyCommentStateResponse
+    """pRPC API method that implements ModifyCommentState.
+
+    We do not support changing between DELETED <-> SPAM. User must
+    undelete or unflag-as-spam first.
+
+    Raises:
+      NoSuchProjectException if the parent Project does not exist.
+      NoSuchIssueException: if the issue does not exist.
+      NoSuchCommentException: if the comment does not exist.
+      PermissionException if user lacks sufficient permissions.
+      ActionNotSupported if user requests unsupported state transitions.
+    """
+    (project_id, issue_id,
+     comment_num) = rnc.IngestCommentName(mc.cnxn, request.name, self.services)
+    with work_env.WorkEnv(mc, self.services) as we:
+      # TODO(crbug/monorail/7614): Eliminate the need to do this lookup.
+      project = we.GetProject(project_id)
+      mc.LookupLoggedInUserPerms(project)
+      issue = we.GetIssue(issue_id, use_cache=False)
+      comments_list = we.SafeListIssueComments(issue_id, 1, comment_num).items
+      try:
+        comment = comments_list[0]
+      except IndexError:
+        raise exceptions.NoSuchCommentException()
+
+      if request.state == issue_objects_pb2.IssueContentState.Value('ACTIVE'):
+        if comment.is_spam:
+          we.FlagComment(issue, comment, False)
+        elif comment.deleted_by != 0:
+          we.DeleteComment(issue, comment, delete=False)
+        else:
+          # No-op if already currently active
+          pass
+      elif request.state == issue_objects_pb2.IssueContentState.Value(
+          'DELETED'):
+        if (not comment.deleted_by) and (not comment.is_spam):
+          we.DeleteComment(issue, comment, delete=True)
+        elif comment.deleted_by and not comment.is_spam:
+          # No-op if already deleted
+          pass
+        else:
+          raise exceptions.ActionNotSupported(
+              'Cannot change comment state from spam to deleted.')
+      elif request.state == issue_objects_pb2.IssueContentState.Value('SPAM'):
+        if (not comment.deleted_by) and (not comment.is_spam):
+          we.FlagComment(issue, comment, True)
+        elif comment.is_spam:
+          # No-op if already spam
+          pass
+        else:
+          raise exceptions.ActionNotSupported(
+              'Cannot change comment state from deleted to spam.')
+      else:
+        raise exceptions.ActionNotSupported('Unsupported target comment state.')
+
+      # FlagComment does not have side effect on comment, must refresh.
+      refreshed_comment = we.SafeListIssueComments(issue_id, 1,
+                                                   comment_num).items[0]
+
+    converted_comment = self.converter.ConvertComments(
+        issue_id, [refreshed_comment])[0]
+    return issues_pb2.ModifyCommentStateResponse(comment=converted_comment)
diff --git a/api/v3/monorail_servicer.py b/api/v3/monorail_servicer.py
new file mode 100644
index 0000000..8f2e26e
--- /dev/null
+++ b/api/v3/monorail_servicer.py
@@ -0,0 +1,434 @@
+# Copyright 2018 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style
+# license that can be found in the LICENSE file or at
+# https://developers.google.com/open-source/licenses/bsd
+
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+
+import cgi
+import functools
+import logging
+import time
+import sys
+
+from google.oauth2 import id_token
+from google.auth.transport import requests as google_requests
+
+from google.appengine.api import oauth
+from google.appengine.api import users
+from google.appengine.api import app_identity
+from google.protobuf import json_format
+from components.prpc import codes
+from components.prpc import server
+
+from framework import monitoring
+
+import settings
+from api.v3 import converters
+from framework import authdata
+from framework import exceptions
+from framework import framework_constants
+from framework import monitoring
+from framework import monorailcontext
+from framework import ratelimiter
+from framework import permissions
+from framework import sql
+from framework import xsrf
+from services import client_config_svc
+from services import features_svc
+
+
+# Header for XSRF token to protect cookie-based auth users.
+XSRF_TOKEN_HEADER = 'x-xsrf-token'
+# Header for test account email.  Only accepted for local dev server.
+TEST_ACCOUNT_HEADER = 'x-test-account'
+# Optional header to help us understand why certain calls were made.
+REASON_HEADER = 'x-reason'
+# Optional header to help prevent double updates.
+REQUEST_ID_HEADER = 'x-request-id'
+# Domain for service account emails.
+SERVICE_ACCOUNT_DOMAIN = 'gserviceaccount.com'
+
+
+def ConvertPRPCStatusToHTTPStatus(context):
+  """pRPC uses internal codes 0..16, but we want to report HTTP codes."""
+  return server._PRPC_TO_HTTP_STATUS.get(context._code, 500)
+
+
+def PRPCMethod(func):
+  @functools.wraps(func)
+  def wrapper(self, request, prpc_context, cnxn=None):
+    return self.Run(
+        func, request, prpc_context, cnxn=cnxn)
+
+  wrapper.wrapped = func
+  return wrapper
+
+
+class MonorailServicer(object):
+  """Abstract base class for API servicers.
+  """
+
+  def __init__(self, services, make_rate_limiter=True, xsrf_timeout=None):
+    self.services = services
+    if make_rate_limiter:
+      self.rate_limiter = ratelimiter.ApiRateLimiter()
+    else:
+      self.rate_limiter = None
+    # We allow subclasses to specify a different timeout. This allows the
+    # RefreshToken method to check the token with a longer expiration and
+    # generate a new one.
+    self.xsrf_timeout = xsrf_timeout or xsrf.TOKEN_TIMEOUT_SEC
+    self.converter = None
+
+  def Run(
+      self, handler, request, prpc_context,
+      cnxn=None, perms=None, start_time=None, end_time=None):
+    """Run a Do* method in an API context.
+
+    Args:
+      handler: API handler method to call with MonorailContext and request.
+      request: API Request proto object.
+      prpc_context: pRPC context object with status code.
+      cnxn: Optional connection to SQL database.
+      perms: PermissionSet passed in during testing.
+      start_time: Int timestamp passed in during testing.
+      end_time: Int timestamp passed in during testing.
+
+    Returns:
+      The response proto returned from the handler or None if that
+      method raised an exception that we handle.
+
+    Raises:
+      Only programming errors should be raised as exceptions.  All
+      exceptions for permission checks and input validation that are
+      raised in the Do* method are converted into pRPC status codes.
+    """
+    start_time = start_time or time.time()
+    cnxn = cnxn or sql.MonorailConnection()
+    if self.services.cache_manager:
+      self.services.cache_manager.DoDistributedInvalidation(cnxn)
+
+    response = None
+    requester_auth = None
+    metadata = dict(prpc_context.invocation_metadata())
+    mc = monorailcontext.MonorailContext(self.services, cnxn=cnxn, perms=perms)
+    try:
+      self.AssertBaseChecks(request, metadata)
+      client_id, requester_auth = self.GetAndAssertRequesterAuth(
+          cnxn, metadata, self.services)
+      logging.info('request proto is:\n%r\n', request)
+      logging.info('requester is %r', requester_auth.email)
+      monitoring.IncrementAPIRequestsCount(
+          'v3', client_id, client_email=requester_auth.email)
+
+      # TODO(crbug.com/monorail/8161)We pass in a None client_id for rate
+      # limiting because CheckStart and CheckEnd will track and limit requests
+      # per email and client_id separately.
+      # So if there are many site users one day, we may end up rate limiting our
+      # own site. With a None client_id we are only rate limiting by emails.
+      if self.rate_limiter:
+        self.rate_limiter.CheckStart(None, requester_auth.email, start_time)
+      mc.auth = requester_auth
+      if not perms:
+        # NOTE(crbug/monorail/7614): We rely on servicer methods to call
+        # to call LookupLoggedInUserPerms() with a project when they need to.
+        mc.LookupLoggedInUserPerms(None)
+
+      self.converter = converters.Converter(mc, self.services)
+      response = handler(self, mc, request)
+
+    except Exception as e:
+      if not self.ProcessException(e, prpc_context, mc):
+        raise e.__class__, e, sys.exc_info()[2]
+    finally:
+      if mc:
+        mc.CleanUp()
+      if self.rate_limiter and requester_auth and requester_auth.email:
+        end_time = end_time or time.time()
+        self.rate_limiter.CheckEnd(
+            None, requester_auth.email, end_time, start_time)
+      self.RecordMonitoringStats(start_time, request, response, prpc_context)
+
+    return response
+
+  def CheckIDToken(self, cnxn, metadata):
+    # type: (MonorailConnection, Mapping[str, str])
+    #     -> Tuple[Optional[str], Optional[authdata.AuthData]]
+    """Authenticate user from an ID token.
+
+    Args:
+      cnxn: connection to the SQL database.
+      metadata: metadata sent by the client.
+
+    Returns:
+      The audience (AKA client_id) and a new AuthData object representing
+      the user making the request or (None, None) if no ID token was found.
+
+    Raises:
+      permissions.PermissionException: If the token is invalid, the client ID
+        is not allowlisted, or no user email was found in the ID token.
+    """
+    bearer = metadata.get('authorization')
+    if not bearer:
+      return None, None
+    if bearer.lower().startswith('bearer '):
+      token = bearer[7:]
+    else:
+      raise permissions.PermissionException('Invalid authorization token.')
+    # TODO(crbug.com/monorail/7724): Use cachecontrol module to cache
+    # certification used for verification.
+    request = google_requests.Request()
+
+    try:
+      id_info = id_token.verify_oauth2_token(token, request)
+      logging.info('ID token info: %r' % id_info)
+    except ValueError:
+      raise permissions.PermissionException(
+          'Invalid bearer token.')
+
+    audience = id_info['aud']
+    email = id_info.get('email')
+    if not email:
+      raise permissions.PermissionException(
+          'No email found in token info. '
+          'Make sure requests are made with scopes `openid` and `email`')
+
+    auth_client_ids, service_account_emails = (
+        client_config_svc.GetClientConfigSvc().GetClientIDEmails())
+
+    if email.endswith(SERVICE_ACCOUNT_DOMAIN):
+      # For service accounts, the email must be allowlisted to call the
+      # API and we must confirm that the ID token was meant for
+      # Monorail by checking the audience.
+
+      # An API call to any <version>-dot-<service>-dot-<app_id>.appspot.com
+      # must have token audience of `https://<app_id>.appspot.com`
+      app_id = app_identity.get_application_id()  # e.g. 'monorail-prod'
+      host = 'https://%s.appspot.com' % app_id
+      if audience != host:
+        raise permissions.PermissionException(
+            'Invalid token audience: %s.' % audience)
+      if email not in service_account_emails:
+        raise permissions.PermissionException(
+            'Account %s is not allowlisted' % email)
+    else:
+      # For users, the audience is the client_id of the site used to make
+      # the call to Monorail's API. The client_id must be allow-listed.
+      if audience not in auth_client_ids:
+        raise permissions.PermissionException(
+            'Client %s is not allowlisted' % audience)
+
+    # We must confirm the client/email is allowlisted before we
+    # potentially auto-create the user account in Monorail.
+    return audience, authdata.AuthData.FromEmail(
+        cnxn, email, self.services, autocreate=True)
+
+  def GetAndAssertRequesterAuth(self, cnxn, metadata, services):
+    # type: (MonorailConnection, Mapping[str, str], Services ->
+    #    Tuple[str, authdata.AuthData]
+    """Gets the requester identity and checks if the user has permission
+       to make the request.
+       Any users successfully authenticated with oauth must be allowlisted or
+       have accounts with the domains in api_allowed_email_domains.
+       Users identified using cookie-based auth must have valid XSRF tokens.
+       Test accounts ending with @example.com are only allowed in the
+       local_mode.
+
+    Args:
+      cnxn: connection to the SQL database.
+      metadata: metadata sent by the client.
+      services: connections to backend services.
+
+    Returns:
+      The client ID and a new AuthData object representing a signed in or
+      anonymous user.
+
+    Raises:
+      exceptions.NoSuchUserException: If the requester does not exist
+      permissions.BannedUserException: If the user has been banned from the site
+      permissions.PermissionException: If the user is not authorized with the
+        Monorail scope, is not allowlisted, and has an invalid token.
+    """
+    # TODO(monorail:6538): Move different authentication methods into separate
+    # functions.
+    requester_auth = None
+    client_id = None
+    # When running on localhost, allow request to specify test account.
+    if TEST_ACCOUNT_HEADER in metadata:
+      if not settings.local_mode:
+        raise exceptions.InputException(
+            'x-test-account only accepted in local_mode')
+      # For local development, we accept any request.
+      # TODO(jrobbins): make this more realistic by requiring a fake XSRF token.
+      test_account = metadata[TEST_ACCOUNT_HEADER]
+      if not test_account.endswith('@example.com'):
+        raise exceptions.InputException(
+            'test_account must end with @example.com')
+      logging.info('Using test_account: %r' % test_account)
+      requester_auth = authdata.AuthData.FromEmail(cnxn, test_account, services)
+
+    # Oauth2 ID token auth.
+    if not requester_auth:
+      client_id, requester_auth = self.CheckIDToken(cnxn, metadata)
+
+    if client_id is None:
+      # TODO(crbug.com/monorail/8160): For site users, we temporarily use
+      # the host as the client_id, until we implement auth in the frontend
+      # to make API requests with ID tokens that include client_ids.
+      client_id = 'https://%s.appspot.com' % app_identity.get_application_id()
+
+
+    # Cookie-based auth for signed in and anonymous users.
+    if not requester_auth:
+      # Check for signed in user
+      user = users.get_current_user()
+      if user:
+        logging.info('Using cookie user: %r', user.email())
+        requester_auth = authdata.AuthData.FromEmail(
+            cnxn, user.email(), services)
+      else:
+        # Create AuthData for anonymous user.
+        requester_auth = authdata.AuthData.FromEmail(cnxn, None, services)
+
+      # Cookie-based auth signed-in and anon users need to have the XSRF
+      # token validate.
+      try:
+        token = metadata.get(XSRF_TOKEN_HEADER)
+        xsrf.ValidateToken(
+            token, requester_auth.user_id, xsrf.XHR_SERVLET_PATH,
+            timeout=self.xsrf_timeout)
+      except xsrf.TokenIncorrect:
+        raise permissions.PermissionException(
+            'Requester %s does not have permission to make this request.'
+            % requester_auth.email)
+
+    if permissions.IsBanned(requester_auth.user_pb, requester_auth.user_view):
+      raise permissions.BannedUserException(
+          'The user %s has been banned from using this site' %
+          requester_auth.email)
+
+    return (client_id, requester_auth)
+
+  def AssertBaseChecks(self, request, metadata):
+    """Reject requests that we refuse to serve."""
+    # TODO(jrobbins): Add read_only check as an exception raised in sql.py.
+    if (settings.read_only and
+        not request.__class__.__name__.startswith(('Get', 'List'))):
+      raise permissions.PermissionException(
+          'This request is not allowed in read-only mode')
+
+    if REASON_HEADER in metadata:
+      logging.info('Request reason: %r', metadata[REASON_HEADER])
+    if REQUEST_ID_HEADER in metadata:
+      # TODO(jrobbins): Ignore requests with duplicate request_ids.
+      logging.info('request_id: %r', metadata[REQUEST_ID_HEADER])
+
+  def ProcessException(self, e, prpc_context, mc):
+    """Return True if we convert an exception to a pRPC status code."""
+    logging.exception(e)
+    logging.info(e.message)
+    exc_type = type(e)
+    if exc_type == exceptions.NoSuchUserException:
+      prpc_context.set_code(codes.StatusCode.NOT_FOUND)
+      prpc_context.set_details('The user does not exist.')
+    elif exc_type == exceptions.NoSuchProjectException:
+      prpc_context.set_code(codes.StatusCode.NOT_FOUND)
+      prpc_context.set_details('The project does not exist.')
+    elif exc_type == exceptions.NoSuchTemplateException:
+      prpc_context.set_code(codes.StatusCode.NOT_FOUND)
+      prpc_context.set_details('The template does not exist.')
+    elif exc_type == exceptions.NoSuchIssueException:
+      prpc_context.set_code(codes.StatusCode.NOT_FOUND)
+      details = 'The issue does not exist.'
+      if e.message:
+        details = cgi.escape(e.message, quote=True)
+      prpc_context.set_details(details)
+    elif exc_type == exceptions.NoSuchIssueApprovalException:
+      prpc_context.set_code(codes.StatusCode.NOT_FOUND)
+      prpc_context.set_details('The issue approval does not exist.')
+    elif exc_type == exceptions.NoSuchCommentException:
+      prpc_context.set_code(codes.StatusCode.INVALID_ARGUMENT)
+      prpc_context.set_details('No such comment')
+    elif exc_type == exceptions.NoSuchComponentException:
+      prpc_context.set_code(codes.StatusCode.NOT_FOUND)
+      prpc_context.set_details('The component does not exist.')
+    elif exc_type == permissions.BannedUserException:
+      prpc_context.set_code(codes.StatusCode.PERMISSION_DENIED)
+      prpc_context.set_details('The requesting user has been banned.')
+    elif exc_type == permissions.PermissionException:
+      logging.info('perms is %r', mc.perms)
+      prpc_context.set_code(codes.StatusCode.PERMISSION_DENIED)
+      prpc_context.set_details('Permission denied.')
+    elif exc_type == exceptions.GroupExistsException:
+      prpc_context.set_code(codes.StatusCode.ALREADY_EXISTS)
+      prpc_context.set_details('The user group already exists.')
+    elif exc_type == features_svc.HotlistAlreadyExists:
+      prpc_context.set_code(codes.StatusCode.ALREADY_EXISTS)
+      prpc_context.set_details('A hotlist with that name already exists.')
+    elif exc_type == exceptions.ComponentDefAlreadyExists:
+      prpc_context.set_code(codes.StatusCode.ALREADY_EXISTS)
+      prpc_context.set_details('A component with that path already exists.')
+    elif exc_type == exceptions.ActionNotSupported:
+      prpc_context.set_code(codes.StatusCode.INVALID_ARGUMENT)
+      prpc_context.set_details('Requested action not supported.')
+    elif exc_type == exceptions.InvalidComponentNameException:
+      prpc_context.set_code(codes.StatusCode.INVALID_ARGUMENT)
+      prpc_context.set_details('That component name is invalid.')
+    elif exc_type == exceptions.FilterRuleException:
+      prpc_context.set_code(codes.StatusCode.INVALID_ARGUMENT)
+      prpc_context.set_details('Violates filter rule that should error.')
+    elif exc_type == exceptions.InputException:
+      prpc_context.set_code(codes.StatusCode.INVALID_ARGUMENT)
+      prpc_context.set_details(
+         'Invalid arguments: %s' % cgi.escape(e.message, quote=True))
+    elif exc_type == exceptions.OverAttachmentQuota:
+      prpc_context.set_code(codes.StatusCode.RESOURCE_EXHAUSTED)
+      prpc_context.set_details(
+          'The request would exceed the attachment quota limit.')
+    elif exc_type == ratelimiter.ApiRateLimitExceeded:
+      prpc_context.set_code(codes.StatusCode.PERMISSION_DENIED)
+      prpc_context.set_details('The requester has exceeded API quotas limit.')
+    elif exc_type == oauth.InvalidOAuthTokenError:
+      prpc_context.set_code(codes.StatusCode.UNAUTHENTICATED)
+      prpc_context.set_details(
+          'The oauth token was not valid or must be refreshed.')
+    elif exc_type == xsrf.TokenIncorrect:
+      logging.info('Bad XSRF token: %r', e.message)
+      prpc_context.set_code(codes.StatusCode.INVALID_ARGUMENT)
+      prpc_context.set_details('Bad XSRF token.')
+    elif exc_type == exceptions.PageTokenException:
+      prpc_context.set_code(codes.StatusCode.INVALID_ARGUMENT)
+      prpc_context.set_details(
+          'Page token invalid or incorrect for the accompanying request')
+    else:
+      prpc_context.set_code(codes.StatusCode.INTERNAL)
+      prpc_context.set_details('Potential programming error.')
+      return False  # Re-raise any exception from programming errors.
+    return True  # It if was one of the cases above, don't reraise.
+
+  def RecordMonitoringStats(
+      self, start_time, request, response, prpc_context, now=None):
+    """Record monitoring info about this request."""
+    now = now or time.time()
+    elapsed_ms = int((now - start_time) * 1000)
+    method_name = request.__class__.__name__
+    if method_name.endswith('Request'):
+      method_name = method_name[:-len('Request')]
+
+    fields = monitoring.GetCommonFields(
+        # pRPC uses its own statuses, but we report HTTP status codes.
+        ConvertPRPCStatusToHTTPStatus(prpc_context),
+        # Use the API name, not the request path, to prevent an explosion in
+        # possible field values.
+        'monorail.v3.' + method_name)
+    monitoring.AddServerDurations(elapsed_ms, fields)
+    monitoring.IncrementServerResponseStatusCount(fields)
+    monitoring.AddServerRequesteBytes(
+        len(json_format.MessageToJson(request)), fields)
+    response_length = 0
+    if response:
+      response_length = len(json_format.MessageToJson(response))
+      monitoring.AddServerResponseBytes(response_length, fields)
diff --git a/api/v3/paginator.py b/api/v3/paginator.py
new file mode 100644
index 0000000..16e66fa
--- /dev/null
+++ b/api/v3/paginator.py
@@ -0,0 +1,91 @@
+# Copyright 2020 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style
+# license that can be found in the LICENSE file.
+
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+
+from framework import exceptions
+from framework import paginate
+from proto import secrets_pb2
+
+
+def CoercePageSize(page_size, max_size, default_size=None):
+  # type: (int, int, Optional[int]) -> int
+  """Validates page_size and coerces it to max_size if needed.
+
+  Args:
+    page_size: The page_size requested by the user.
+    max_size: the maximum page size allowed. Must be > 0.
+        Also used as default if default_size not provided
+    default_size: default size to use if page_size not provided. Must be > 0.
+
+  Returns:
+    The appropriate page size to use for the request, based on the parameters.
+    Specifically this means
+      - page_size if not greater than max_size
+      - max_size if page_size > max_size
+      - max_size if page_size is not provided and default_size is not provided
+      - default_size if page_size is not provided
+
+  Raises:
+    InputException: if page_size is negative.
+  """
+  # These are programming errors. They are not user input.
+  assert max_size > 0
+  assert default_size is None or default_size > 0
+
+  # Check for invalid user provided page_size.
+  if page_size and page_size < 0:
+    raise exceptions.InputException('`page_size` cannot be negative.')
+
+  if not page_size:
+    return default_size or max_size
+  if page_size > max_size:
+    return max_size
+  return page_size
+
+
+class Paginator(object):
+  """Class to manage API pagination.
+
+  Paginator handles the pagination tasks and info of a single List or
+  Search API method implementation, given the contents of the request.
+  """
+
+  def __init__(self, parent=None, page_size=None, order_by=None,
+      filter_str=None, query=None, projects=None):
+    # type: (Optional[str], Optional[int], Optional[str], Optional[str],
+    #   Optional[str], Optional[Collection[str]]]) -> None
+    self.request_contents = secrets_pb2.ListRequestContents(
+        parent=parent, page_size=page_size, order_by=order_by,
+        filter=filter_str, query=query, projects=projects)
+
+  def GetStart(self, page_token):
+    # type: (Optional[str]) -> int
+    """Validates a request.page_token and returns the start index for it."""
+    if page_token:
+      # TODO(crbug.com/monorail/6758): Proto string fields are unicode types in
+      # python 2. In python 3 these unicode strings will be represented with
+      # string types. paginate.ValidateAndParsePageToken requires a string token
+      # during validation (compare_digest()). Once we move to python 3, we can
+      # remove this string casting.
+      token = str(page_token)
+      return paginate.ValidateAndParsePageToken(token, self.request_contents)
+    return 0
+
+  def GenerateNextPageToken(self, next_start):
+    # type: (Optional[int]) -> str
+    """Generates the `next_page_token` for the API response.
+
+    Args:
+      next_start: The start index of the next page, or None if no more results.
+
+    Returns:
+      A string clients can use to request the next page. Returns None if
+      next_start was None
+    """
+    if next_start is None:
+      return None
+    return paginate.GeneratePageToken(self.request_contents, next_start)
diff --git a/api/v3/permission_converters.py b/api/v3/permission_converters.py
new file mode 100644
index 0000000..6837438
--- /dev/null
+++ b/api/v3/permission_converters.py
@@ -0,0 +1,62 @@
+# Copyright 2020 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style
+# license that can be found in the LICENSE file.
+
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+
+from framework import permissions
+from framework import exceptions
+from api.v3.api_proto import permission_objects_pb2
+
+# Global dictionaries to map backend permission strings to
+# API Permission enum values.
+
+HOTLIST_PERMISSIONS_MAP = {
+    permissions.EDIT_HOTLIST:
+        permission_objects_pb2.Permission.Value('HOTLIST_EDIT'),
+    permissions.ADMINISTER_HOTLIST:
+        permission_objects_pb2.Permission.Value('HOTLIST_ADMINISTER')
+}
+
+FIELDDEF_PERMISSIONS_MAP = {
+    permissions.EDIT_FIELD_DEF:
+        permission_objects_pb2.Permission.Value('FIELD_DEF_EDIT'),
+    permissions.EDIT_FIELD_DEF_VALUE:
+        permission_objects_pb2.Permission.Value('FIELD_DEF_VALUE_EDIT')
+}
+
+# TODO(crbug/monorail/7339): Create a common _ConvertPermissions(permissions,
+# permissions_map)
+
+
+def ConvertHotlistPermissions(hotlist_permissions):
+  # type: (Sequence[str]) -> Sequence[permission_objects_pb2.Permission]
+  """Converts hotlist permission strings into protoc Permission enum values."""
+  api_permissions = []
+  for permission in hotlist_permissions:
+    api_permission = HOTLIST_PERMISSIONS_MAP.get(permission)
+    if not api_permission:
+      raise exceptions.InputException(
+          'Unrecognized hotlist permission: %s' % permission)
+    api_permissions.append(api_permission)
+
+  return api_permissions
+
+
+def ConvertFieldDefPermissions(field_permissions):
+  # type: (Sequence[str]) -> Sequence[permission_objects_pb2.Permission]
+  """Converts field permission strings into protoc Permission enum values."""
+  api_permissions = []
+  for permission in field_permissions:
+    api_permission = FIELDDEF_PERMISSIONS_MAP.get(permission)
+    if not api_permission:
+      raise exceptions.InputException(
+          'Unrecognized field permission: %s' % permission)
+    api_permissions.append(api_permission)
+
+  return api_permissions
+
+
+# TODO(crbug/monorail/7339): Implement all ConvertFooPermissions methods.
diff --git a/api/v3/permissions_servicer.py b/api/v3/permissions_servicer.py
new file mode 100644
index 0000000..d544478
--- /dev/null
+++ b/api/v3/permissions_servicer.py
@@ -0,0 +1,87 @@
+# Copyright 2020 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style
+# license that can be found in the LICENSE file.
+
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+
+from google.protobuf import empty_pb2
+
+from api import resource_name_converters as rnc
+from api.v3 import permission_converters as pc
+from api.v3 import monorail_servicer
+from api.v3.api_proto import permission_objects_pb2
+from api.v3.api_proto import permissions_pb2
+from api.v3.api_proto import permissions_prpc_pb2
+from businesslogic import work_env
+from framework import exceptions
+
+
+class PermissionsServicer(monorail_servicer.MonorailServicer):
+  """Handle API requests related to Permissions.
+  Each API request is implemented with a method as defined in the
+  .proto file. Each method does any request-specific validation, uses work_env
+  to safely operate on business objects, and returns a response proto.
+  """
+
+  DESCRIPTION = permissions_prpc_pb2.PermissionsServiceDescription
+
+  @monorail_servicer.PRPCMethod
+  def BatchGetPermissionSets(self, mc, request):
+    # type: (MonorailContext, BatchGetPermissionSetsRequest) ->
+    # BatchGetPermissionSetsResponse
+    """pRPC API method that implements BatchGetPermissionSets.
+
+    Raises:
+      InputException: if any name in request.names is not a valid resource name
+          or a permission string is not recognized.
+      PermissionException: if the requester does not have permission to
+          view one of the resources.
+    """
+    api_permission_sets = []
+    with work_env.WorkEnv(mc, self.services) as we:
+      for name in request.names:
+        api_permission_sets.append(self._GetPermissionSet(mc.cnxn, we, name))
+
+    return permissions_pb2.BatchGetPermissionSetsResponse(
+        permission_sets=api_permission_sets)
+
+  def _GetPermissionSet(self, cnxn, we, name):
+    # type: (sql.MonorailConnection, businesslogic.WorkEnv, str) ->
+    # permission_objects_pb2.PermissionSet
+    """Takes a resource name and returns the PermissionSet for the resource.
+
+      Args:
+        cnxn: MonorailConnection object to the database.
+        we: WorkEnv object to get the permission strings.
+        name: resource name of a resource we want a PermissionSet for.
+
+      Returns:
+        PermissionSet object.
+
+      Raises:
+      InputException: if request.name is not a valid resource name or a
+          permission string is not recognized.
+      PermissionException: if the requester does not have permission to
+          view the resource.
+    """
+    try:
+      hotlist_id = rnc.IngestHotlistName(name)
+      permissions = we.ListHotlistPermissions(hotlist_id)
+      api_permissions = pc.ConvertHotlistPermissions(permissions)
+      return permission_objects_pb2.PermissionSet(
+          resource=name, permissions=api_permissions)
+    except exceptions.InputException:
+      pass
+    try:
+      project_id, field_id = rnc.IngestFieldDefName(cnxn, name, self.services)
+      permissions = we.ListFieldDefPermissions(field_id, project_id)
+      api_permissions = pc.ConvertFieldDefPermissions(permissions)
+      return permission_objects_pb2.PermissionSet(
+          resource=name, permissions=api_permissions)
+    except exceptions.InputException:
+      pass
+    # TODO(crbug/monorail/7339): Add more try-except blocks for other
+    # resource types.
+    raise exceptions.InputException('invalid resource name')
diff --git a/api/v3/projects_servicer.py b/api/v3/projects_servicer.py
new file mode 100644
index 0000000..17d6f93
--- /dev/null
+++ b/api/v3/projects_servicer.py
@@ -0,0 +1,149 @@
+# Copyright 2020 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style
+# license that can be found in the LICENSE file or at
+# https://developers.google.com/open-source/licenses/bsd
+
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+
+from google.protobuf import empty_pb2
+
+from api import resource_name_converters as rnc
+from api.v3 import api_constants
+from api.v3 import monorail_servicer
+from api.v3 import paginator
+from api.v3.api_proto import projects_pb2
+from api.v3.api_proto import projects_prpc_pb2
+from businesslogic import work_env
+
+
+class ProjectsServicer(monorail_servicer.MonorailServicer):
+  """Handle API requests related to Project objects.
+  Each API request is implemented with a method as defined in the
+  .proto file. Each method does any request-specific validation, uses work_env
+  to safely operate on business objects, and returns a response proto.
+  """
+
+  DESCRIPTION = projects_prpc_pb2.ProjectsServiceDescription
+
+  @monorail_servicer.PRPCMethod
+  def ListIssueTemplates(self, mc, request):
+    # type: (MonorailContext, ListIssueTemplatesRequest) ->
+    #   ListIssueTemplatesResponse
+    """pRPC API method that implements ListIssueTemplates.
+
+      Raises:
+        InputException if the request.parent is invalid.
+        NoSuchProjectException if no project exists with the given name.
+    """
+    project_id = rnc.IngestProjectName(mc.cnxn, request.parent, self.services)
+
+    with work_env.WorkEnv(mc, self.services) as we:
+      # TODO(crbug/monorail/7614): Eliminate the need to do this lookup.
+      project = we.GetProject(project_id)
+      mc.LookupLoggedInUserPerms(project)
+      templates = we.ListProjectTemplates(project_id)
+
+    return projects_pb2.ListIssueTemplatesResponse(
+        templates=self.converter.ConvertIssueTemplates(project_id, templates))
+
+  @monorail_servicer.PRPCMethod
+  def ListComponentDefs(self, mc, request):
+    # type: (MonorailContext, ListComponentDefsRequest) ->
+    #   ListComponentDefsResponse
+    """pRPC API method that implements ListComponentDefs.
+
+      Raises:
+        InputException if the request.parent is invalid.
+        NoSuchProjectException if the parent project is not found.
+    """
+    project_id = rnc.IngestProjectName(mc.cnxn, request.parent, self.services)
+
+    with work_env.WorkEnv(mc, self.services) as we:
+      # TODO(crbug/monorail/7614): Eliminate the need to do this lookup.
+      project = we.GetProject(project_id)
+      mc.LookupLoggedInUserPerms(project)
+
+      page_size = paginator.CoercePageSize(
+        request.page_size, api_constants.MAX_COMPONENTS_PER_PAGE)
+      pager = paginator.Paginator(
+          parent=request.parent, page_size=page_size)
+      list_result = we.ListComponentDefs(
+          project_id, page_size, pager.GetStart(request.page_token))
+
+      api_component_defs = self.converter.ConvertComponentDefs(
+          list_result.items, project_id)
+
+    return projects_pb2.ListComponentDefsResponse(
+        component_defs=api_component_defs,
+        next_page_token=pager.GenerateNextPageToken(list_result.next_start))
+
+  @monorail_servicer.PRPCMethod
+  def CreateComponentDef(self, mc, request):
+    # type: (MonorailContext, CreateComponentDefRequest) ->
+    #   ComponentDef
+    """pRPC API method that implements CreateComponentDef.
+
+      Raises:
+        InputException if the request is invalid.
+        NoSuchUserException if any given component admins or ccs do not exist.
+        NoSuchProjectException if the parent project does not exist.
+        PermissionException if the requester is not allowed to create
+          this component.
+    """
+    project_id = rnc.IngestProjectName(mc.cnxn, request.parent, self.services)
+    admin_ids = rnc.IngestUserNames(
+        mc.cnxn, request.component_def.admins, self.services)
+    cc_ids = rnc.IngestUserNames(
+        mc.cnxn, request.component_def.ccs, self.services)
+
+    with work_env.WorkEnv(mc, self.services) as we:
+      component_def = we.CreateComponentDef(
+          project_id, request.component_def.value,
+          request.component_def.docstring, admin_ids, cc_ids,
+          request.component_def.labels)
+
+    return self.converter.ConvertComponentDef(component_def)
+
+  @monorail_servicer.PRPCMethod
+  def DeleteComponentDef(self, mc, request):
+    # type: (MonorailContext, DeleteComponentDefRequest) -> Empty
+    """pRPC API method that implements DeleteComponentDef.
+
+      Raises:
+        InputException if the request in invalid.
+        NoSuchComponentException if the component does not exist.
+        PermissionException if the requester is not allowed to delete
+          this component.
+        NoSuchProjectException if the parent project does not exist.
+    """
+    project_id, component_id = rnc.IngestComponentDefNames(
+        mc.cnxn, [request.name], self.services)[0]
+
+    with work_env.WorkEnv(mc, self.services) as we:
+      we.DeleteComponentDef(project_id, component_id)
+
+    return empty_pb2.Empty()
+
+  @monorail_servicer.PRPCMethod
+  def ListProjects(self, mc, _):
+    # type: (MonorailContext, ListProjectsRequest) -> ListProjectsResponse
+    """pRPC API method that implements ListProjects.
+
+      Raises:
+        InputException if the request.page_token is invalid or the request does
+          not match the previous request that provided the given page_token.
+    """
+    with work_env.WorkEnv(mc, self.services) as we:
+      # NOTE(crbug/monorail/7614): Until the referenced cleanup is complete,
+      # all servicer methods that are scoped to a single Project need to call
+      # mc.LookupLoggedInUserPerms.
+      #  This method does not because it may be scoped to multiple projects.
+      allowed_project_ids = we.ListProjects()
+      projects_dict = we.GetProjects(allowed_project_ids)
+      projects = [projects_dict[proj_id] for proj_id in allowed_project_ids]
+
+    # TODO(crbug.com/monorail/7505): Add pagination logic.
+    return projects_pb2.ListProjectsResponse(
+        projects=self.converter.ConvertProjects(projects))
diff --git a/api/v3/test/__init__.py b/api/v3/test/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/api/v3/test/__init__.py
diff --git a/api/v3/test/converters_test.py b/api/v3/test/converters_test.py
new file mode 100644
index 0000000..1bbd12c
--- /dev/null
+++ b/api/v3/test/converters_test.py
@@ -0,0 +1,3254 @@
+# Copyright 2020 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style
+# license that can be found in the LICENSE file.
+"""Tests for converting internal protorpc to external protoc."""
+
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+
+import copy
+import difflib
+import logging
+import unittest
+
+import mock
+from google.protobuf import field_mask_pb2
+from google.protobuf import timestamp_pb2
+
+from api import resource_name_converters as rnc
+from api.v3 import converters
+from api.v3.api_proto import feature_objects_pb2
+from api.v3.api_proto import issues_pb2
+from api.v3.api_proto import issue_objects_pb2
+from api.v3.api_proto import user_objects_pb2
+from api.v3.api_proto import project_objects_pb2
+from framework import authdata
+from framework import exceptions
+from framework import framework_constants
+from framework import framework_helpers
+from framework import monorailcontext
+from testing import fake
+from testing import testing_helpers
+from tracker import field_helpers
+from services import service_manager
+from proto import tracker_pb2
+from tracker import tracker_bizobj as tbo
+
+EXPLICIT_DERIVATION = issue_objects_pb2.Derivation.Value('EXPLICIT')
+RULE_DERIVATION = issue_objects_pb2.Derivation.Value('RULE')
+Choice = project_objects_pb2.FieldDef.EnumTypeSettings.Choice
+
+CURRENT_TIME = 12346.78
+
+
+class ConverterFunctionsTest(unittest.TestCase):
+
+  def setUp(self):
+    self.services = service_manager.Services(
+        issue=fake.IssueService(),
+        project=fake.ProjectService(),
+        usergroup=fake.UserGroupService(),
+        user=fake.UserService(),
+        config=fake.ConfigService(),
+        template=fake.TemplateService(),
+        features=fake.FeaturesService())
+    self.cnxn = fake.MonorailConnection()
+    self.mc = monorailcontext.MonorailContext(self.services, cnxn=self.cnxn)
+    self.converter = converters.Converter(self.mc, self.services)
+    self.PAST_TIME = int(CURRENT_TIME - 1)
+    self.project_1 = self.services.project.TestAddProject(
+        'proj', project_id=789)
+    self.project_2 = self.services.project.TestAddProject(
+        'goose', project_id=788)
+    self.user_1 = self.services.user.TestAddUser('one@example.com', 111)
+    self.user_2 = self.services.user.TestAddUser('two@example.com', 222)
+    self.user_3 = self.services.user.TestAddUser('three@example.com', 333)
+    self.services.project.TestAddProjectMembers(
+        [self.user_1.user_id], self.project_1, 'CONTRIBUTOR_ROLE')
+
+    self.field_def_1_name = 'test_field_1'
+    self.field_def_1 = self._CreateFieldDef(
+        self.project_1.project_id,
+        self.field_def_1_name,
+        'STR_TYPE',
+        admin_ids=[self.user_1.user_id],
+        is_required=True,
+        is_multivalued=True,
+        is_phase_field=True,
+        regex='abc')
+    self.field_def_2_name = 'test_field_2'
+    self.field_def_2 = self._CreateFieldDef(
+        self.project_1.project_id,
+        self.field_def_2_name,
+        'INT_TYPE',
+        max_value=37,
+        is_niche=True)
+    self.field_def_3_name = 'days'
+    self.field_def_3 = self._CreateFieldDef(
+        self.project_1.project_id, self.field_def_3_name, 'ENUM_TYPE')
+    self.field_def_4_name = 'OS'
+    self.field_def_4 = self._CreateFieldDef(
+        self.project_1.project_id, self.field_def_4_name, 'ENUM_TYPE')
+    self.field_def_5_name = 'yellow'
+    self.field_def_5 = self._CreateFieldDef(
+        self.project_1.project_id, self.field_def_5_name, 'ENUM_TYPE')
+    self.field_def_7_name = 'redredred'
+    self.field_def_7 = self._CreateFieldDef(
+        self.project_1.project_id,
+        self.field_def_7_name,
+        'ENUM_TYPE',
+        is_restricted_field=True,
+        editor_ids=[self.user_1.user_id])
+    self.field_def_8_name = 'dogandcat'
+    self.field_def_8 = self._CreateFieldDef(
+        self.project_1.project_id,
+        self.field_def_8_name,
+        'USER_TYPE',
+        needs_member=True,
+        needs_perm='EDIT_PROJECT',
+        notify_on=tracker_pb2.NotifyTriggers.ANY_COMMENT)
+    self.field_def_9_name = 'catanddog'
+    self.field_def_9 = self._CreateFieldDef(
+        self.project_1.project_id,
+        self.field_def_9_name,
+        'DATE_TYPE',
+        date_action_str='ping_owner_only')
+    self.field_def_10_name = 'url'
+    self.field_def_10 = self._CreateFieldDef(
+        self.project_1.project_id, self.field_def_10_name, 'URL_TYPE')
+    self.field_def_project2_name = 'lorem'
+    self.field_def_project2 = self._CreateFieldDef(
+        self.project_2.project_id, self.field_def_project2_name, 'ENUM_TYPE')
+    self.approval_def_1_name = 'approval_field_1'
+    self.approval_def_1_id = self._CreateFieldDef(
+        self.project_1.project_id,
+        self.approval_def_1_name,
+        'APPROVAL_TYPE',
+        docstring='ad_1_docstring',
+        admin_ids=[self.user_1.user_id])
+    self.approval_def_1 = tracker_pb2.ApprovalDef(
+        approval_id=self.approval_def_1_id,
+        approver_ids=[self.user_2.user_id],
+        survey='approval_def_1 survey')
+    self.approval_def_2_name = 'approval_field_1'
+    self.approval_def_2_id = self._CreateFieldDef(
+        self.project_1.project_id,
+        self.approval_def_2_name,
+        'APPROVAL_TYPE',
+        docstring='ad_2_docstring',
+        admin_ids=[self.user_1.user_id])
+    self.approval_def_2 = tracker_pb2.ApprovalDef(
+        approval_id=self.approval_def_2_id,
+        approver_ids=[self.user_2.user_id],
+        survey='approval_def_2 survey')
+    approval_defs = [self.approval_def_1, self.approval_def_2]
+    self.field_def_6_name = 'simonsays'
+    self.field_def_6 = self._CreateFieldDef(
+        self.project_1.project_id,
+        self.field_def_6_name,
+        'STR_TYPE',
+        approval_id=self.approval_def_1_id)
+    self.dne_field_def_id = 999999
+    self.fv_1_value = u'some_string_field_value'
+    self.fv_1 = fake.MakeFieldValue(
+        field_id=self.field_def_1, str_value=self.fv_1_value, derived=False)
+    self.fv_1_derived = fake.MakeFieldValue(
+        field_id=self.field_def_1, str_value=self.fv_1_value, derived=True)
+    self.fv_6 = fake.MakeFieldValue(
+        field_id=self.field_def_6, str_value=u'touch-nose', derived=False)
+    self.phase_1_id = 123123
+    self.phase_1 = fake.MakePhase(self.phase_1_id, name='some phase name')
+    self.av_1 = fake.MakeApprovalValue(
+        self.approval_def_1_id,
+        setter_id=self.user_1.user_id,
+        set_on=self.PAST_TIME,
+        approver_ids=[self.user_2.user_id],
+        phase_id=self.phase_1_id)
+    self.av_2 = fake.MakeApprovalValue(
+        self.approval_def_1_id,
+        setter_id=self.user_1.user_id,
+        set_on=self.PAST_TIME,
+        approver_ids=[self.user_2.user_id])
+
+    self.issue_1 = fake.MakeTestIssue(
+        self.project_1.project_id,
+        1,
+        'sum',
+        'New',
+        self.user_1.user_id,
+        cc_ids=[self.user_2.user_id],
+        derived_cc_ids=[self.user_3.user_id],
+        project_name=self.project_1.project_name,
+        star_count=1,
+        labels=['label-a', 'label-b', 'days-1'],
+        derived_owner_id=self.user_2.user_id,
+        derived_status='Fixed',
+        derived_labels=['label-derived', 'OS-mac', 'label-derived-2'],
+        component_ids=[1, 2],
+        merged_into_external='b/1',
+        derived_component_ids=[3, 4],
+        attachment_count=5,
+        field_values=[self.fv_1, self.fv_1_derived],
+        opened_timestamp=self.PAST_TIME,
+        modified_timestamp=self.PAST_TIME,
+        approval_values=[self.av_1],
+        phases=[self.phase_1])
+    self.issue_2 = fake.MakeTestIssue(
+        self.project_2.project_id,
+        2,
+        'sum2',
+        None,
+        None,
+        reporter_id=self.user_1.user_id,
+        project_name=self.project_2.project_name,
+        merged_into=self.issue_1.issue_id,
+        opened_timestamp=self.PAST_TIME,
+        modified_timestamp=self.PAST_TIME,
+        closed_timestamp=self.PAST_TIME,
+        derived_status='Fixed',
+        derived_owner_id=self.user_2.user_id,
+        is_spam=True)
+    self.services.issue.TestAddIssue(self.issue_1)
+    self.services.issue.TestAddIssue(self.issue_2)
+
+    self.template_0 = self.services.template.TestAddIssueTemplateDef(
+        11110, self.project_1.project_id, 'template0')
+    self.template_1_label1_value = '2'
+    self.template_1_labels = [
+        'pri-1', '{}-{}'.format(
+            self.field_def_3_name, self.template_1_label1_value)
+    ]
+    self.template_1 = self.services.template.TestAddIssueTemplateDef(
+        11111,
+        self.project_1.project_id,
+        'template1',
+        content='foobar',
+        summary='foo',
+        admin_ids=[self.user_2.user_id],
+        owner_id=self.user_1.user_id,
+        labels=self.template_1_labels,
+        component_ids=[654],
+        field_values=[self.fv_1],
+        approval_values=[self.av_1],
+        phases=[self.phase_1])
+    self.template_2 = self.services.template.TestAddIssueTemplateDef(
+        11112,
+        self.project_1.project_id,
+        'template2',
+        members_only=True,
+        owner_defaults_to_member=True)
+    self.template_3 = self.services.template.TestAddIssueTemplateDef(
+        11113,
+        self.project_1.project_id,
+        'template3',
+        field_values=[self.fv_1],
+        approval_values=[self.av_2],
+    )
+    self.dne_template = tracker_pb2.TemplateDef(
+        name='dne_template_name', template_id=11114)
+    self.labeldef_1 = tracker_pb2.LabelDef(
+        label='white-mountain',
+        label_docstring='test label doc string for white-mountain')
+    self.labeldef_2 = tracker_pb2.LabelDef(
+        label='yellow-submarine',
+        label_docstring='Submarine choice for yellow enum field')
+    self.labeldef_3 = tracker_pb2.LabelDef(
+        label='yellow-basket',
+        label_docstring='Basket choice for yellow enum field')
+    self.labeldef_4 = tracker_pb2.LabelDef(
+        label='yellow-tasket',
+        label_docstring='Deprecated tasket choice for yellow enum field',
+        deprecated=True)
+    self.labeldef_5 = tracker_pb2.LabelDef(
+        label='mont-blanc',
+        label_docstring='test label doc string for mont-blanc',
+        deprecated=True)
+    self.predefined_labels = [
+        self.labeldef_1, self.labeldef_2, self.labeldef_3, self.labeldef_4,
+        self.labeldef_5
+    ]
+    test_label_ids = {}
+    for index, ld in enumerate(self.predefined_labels):
+      test_label_ids[ld.label] = index
+    self.services.config.TestAddLabelsDict(test_label_ids)
+    self.status_1 = tracker_pb2.StatusDef(
+        status='New', means_open=True, status_docstring='status_1 docstring')
+    self.status_2 = tracker_pb2.StatusDef(
+        status='Duplicate',
+        means_open=False,
+        status_docstring='status_2 docstring')
+    self.status_3 = tracker_pb2.StatusDef(
+        status='Accepted',
+        means_open=True,
+        status_docstring='status_3_docstring')
+    self.status_4 = tracker_pb2.StatusDef(
+        status='Gibberish',
+        means_open=True,
+        status_docstring='status_4_docstring',
+        deprecated=True)
+    self.predefined_statuses = [
+        self.status_1, self.status_2, self.status_3, self.status_4
+    ]
+    self.component_def_1_path = 'foo'
+    self.component_def_1_id = self.services.config.CreateComponentDef(
+        self.cnxn, self.project_1.project_id, self.component_def_1_path,
+        'cd1_docstring', False, [self.user_1.user_id], [self.user_2.user_id],
+        self.PAST_TIME, self.user_1.user_id, [0, 1, 2, 3, 4])
+    self.component_def_2_path = 'foo>bar'
+    self.component_def_2_id = self.services.config.CreateComponentDef(
+        self.cnxn, self.project_1.project_id, self.component_def_2_path,
+        'cd2_docstring', True, [self.user_1.user_id], [self.user_2.user_id],
+        self.PAST_TIME, self.user_1.user_id, [])
+    self.services.config.UpdateConfig(
+        self.cnxn,
+        self.project_1,
+        statuses_offer_merge=[self.status_2.status],
+        excl_label_prefixes=['type', 'priority'],
+        default_template_for_developers=self.template_2.template_id,
+        default_template_for_users=self.template_1.template_id,
+        list_prefs=('ID Summary', 'ID', 'status', 'owner', 'owner:me'),
+        # UpdateConfig accepts tuples rather than protorpc *Defs
+        well_known_labels=[
+            (ld.label, ld.label_docstring, ld.deprecated)
+            for ld in self.predefined_labels
+        ],
+        approval_defs=[
+            (ad.approval_id, ad.approver_ids, ad.survey) for ad in approval_defs
+        ],
+        well_known_statuses=[
+            (sd.status, sd.status_docstring, sd.means_open, sd.deprecated)
+            for sd in self.predefined_statuses
+        ])
+    # base_query_id 2 equates to "is:open", defined in tracker_constants.
+    self.psq_1 = tracker_pb2.SavedQuery(
+        query_id=2, name='psq1 name', base_query_id=2, query='foo=bar')
+    self.psq_2 = tracker_pb2.SavedQuery(
+        query_id=3, name='psq2 name', query='fizz=buzz')
+    self.services.features.UpdateCannedQueries(
+        self.cnxn, self.project_1.project_id, [self.psq_1, self.psq_2])
+
+  def _CreateFieldDef(
+      self,
+      project_id,
+      field_name,
+      field_type_str,
+      docstring=None,
+      min_value=None,
+      max_value=None,
+      regex=None,
+      needs_member=None,
+      needs_perm=None,
+      grants_perm=None,
+      notify_on=None,
+      date_action_str=None,
+      admin_ids=None,
+      editor_ids=None,
+      is_required=False,
+      is_niche=False,
+      is_multivalued=False,
+      is_phase_field=False,
+      approval_id=None,
+      is_restricted_field=False):
+    """Calls CreateFieldDef with reasonable defaults, returns the ID."""
+    if admin_ids is None:
+      admin_ids = []
+    if editor_ids is None:
+      editor_ids = []
+    return self.services.config.CreateFieldDef(
+        self.cnxn,
+        project_id,
+        field_name,
+        field_type_str,
+        None,
+        None,
+        is_required,
+        is_niche,
+        is_multivalued,
+        min_value,
+        max_value,
+        regex,
+        needs_member,
+        needs_perm,
+        grants_perm,
+        notify_on,
+        date_action_str,
+        docstring,
+        admin_ids,
+        editor_ids,
+        is_phase_field=is_phase_field,
+        approval_id=approval_id,
+        is_restricted_field=is_restricted_field)
+
+  def _GetFieldDefById(self, project_id, fd_id):
+    config = self.services.config.GetProjectConfig(self.cnxn, project_id)
+    return [fd for fd in config.field_defs if fd.field_id == fd_id][0]
+
+  def _GetApprovalDefById(self, project_id, ad_id):
+    config = self.services.config.GetProjectConfig(self.cnxn, project_id)
+    return [ad for ad in config.approval_defs if ad.approval_id == ad_id][0]
+
+  def testConvertHotlist(self):
+    """We can convert a Hotlist."""
+    hotlist = fake.Hotlist(
+        'Hotlist-Name',
+        240,
+        default_col_spec='chicken goose',
+        is_private=False,
+        owner_ids=[111],
+        editor_ids=[222, 333],
+        summary='Hotlist summary',
+        description='Hotlist Description')
+    expected_api_hotlist = feature_objects_pb2.Hotlist(
+        name='hotlists/240',
+        display_name=hotlist.name,
+        owner= 'users/111',
+        summary=hotlist.summary,
+        description=hotlist.description,
+        editors=['users/222', 'users/333'],
+        hotlist_privacy=feature_objects_pb2.Hotlist.HotlistPrivacy.Value(
+            'PUBLIC'),
+        default_columns=[
+            issue_objects_pb2.IssuesListColumn(column='chicken'),
+            issue_objects_pb2.IssuesListColumn(column='goose')
+        ])
+    self.converter.user_auth = authdata.AuthData.FromUser(
+        self.cnxn, self.user_1, self.services)
+    self.assertEqual(
+        expected_api_hotlist, self.converter.ConvertHotlist(hotlist))
+
+  def testConvertHotlist_DefaultValues(self):
+    """We can convert a Hotlist with some empty or default values."""
+    hotlist = fake.Hotlist(
+        'Hotlist-Name',
+        241,
+        is_private=True,
+        owner_ids=[111],
+        summary='Hotlist summary',
+        description='Hotlist Description',
+        default_col_spec='')
+    expected_api_hotlist = feature_objects_pb2.Hotlist(
+        name='hotlists/241',
+        display_name=hotlist.name,
+        owner='users/111',
+        summary=hotlist.summary,
+        description=hotlist.description,
+        hotlist_privacy=feature_objects_pb2.Hotlist.HotlistPrivacy.Value(
+            'PRIVATE'))
+    self.converter.user_auth = authdata.AuthData.FromUser(
+        self.cnxn, self.user_1, self.services)
+    self.assertEqual(
+        expected_api_hotlist, self.converter.ConvertHotlist(hotlist))
+
+  def testConvertHotlists(self):
+    """We can convert several Hotlists."""
+    hotlists = [
+        fake.Hotlist(
+            'Hotlist-Name',
+            241,
+            owner_ids=[111],
+            summary='Hotlist summary',
+            description='Hotlist Description'),
+        fake.Hotlist(
+            'Hotlist-Name',
+            241,
+            owner_ids=[111],
+            summary='Hotlist summary',
+            description='Hotlist Description')
+    ]
+    self.assertEqual(2, len(self.converter.ConvertHotlists(hotlists)))
+
+  def testConvertHotlistItems(self):
+    """We can convert HotlistItems."""
+    hotlist_item_fields = [
+        (self.issue_1.issue_id, 21, 111, self.PAST_TIME, 'note2'),
+        (78900, 11, 222, self.PAST_TIME, 'note3'),  # Does not exist.
+        (self.issue_2.issue_id, 1, 222, None, 'note1'),
+    ]
+    hotlist = fake.Hotlist(
+        'Hotlist-Name', 241, hotlist_item_fields=hotlist_item_fields)
+    self.converter.user_auth = authdata.AuthData.FromUser(
+        self.cnxn, self.user_1, self.services)
+    api_items = self.converter.ConvertHotlistItems(
+        hotlist.hotlist_id, hotlist.items)
+    expected_create_time = timestamp_pb2.Timestamp()
+    expected_create_time.FromSeconds(self.PAST_TIME)
+    expected_items = [
+        feature_objects_pb2.HotlistItem(
+            name='hotlists/241/items/proj.1',
+            issue='projects/proj/issues/1',
+            rank=1,
+            adder= 'users/111',
+            create_time=expected_create_time,
+            note='note2'),
+        feature_objects_pb2.HotlistItem(
+            name='hotlists/241/items/goose.2',
+            issue='projects/goose/issues/2',
+            rank=0,
+            adder='users/222',
+            note='note1')
+    ]
+    self.assertEqual(api_items, expected_items)
+
+  def testConvertHotlistItems_Empty(self):
+    hotlist = fake.Hotlist('Hotlist-Name', 241)
+    self.converter.user_auth = authdata.AuthData.FromUser(
+        self.cnxn, self.user_1, self.services)
+    api_items = self.converter.ConvertHotlistItems(
+        hotlist.hotlist_id, hotlist.items)
+    self.assertEqual(api_items, [])
+
+  @mock.patch('tracker.attachment_helpers.SignAttachmentID')
+  def testConvertComments(self, mock_SignAttachmentID):
+    """We can convert comments."""
+    mock_SignAttachmentID.return_value = 2
+    attach = tracker_pb2.Attachment(
+        attachment_id=1,
+        mimetype='image/png',
+        filename='example.png',
+        filesize=12345)
+    deleted_attach = tracker_pb2.Attachment(
+        attachment_id=2,
+        mimetype='image/png',
+        filename='deleted_example.png',
+        filesize=67890,
+        deleted=True)
+    initial_comment = tracker_pb2.IssueComment(
+        project_id=self.issue_1.project_id,
+        issue_id=self.issue_1.issue_id,
+        user_id=self.issue_1.reporter_id,
+        timestamp=self.PAST_TIME,
+        content='initial description',
+        sequence=0,
+        is_description=True,
+        description_num='1',
+        attachments=[attach, deleted_attach])
+    deleted_comment = tracker_pb2.IssueComment(
+        project_id=self.issue_1.project_id,
+        issue_id=self.issue_1.issue_id,
+        timestamp=self.PAST_TIME,
+        deleted_by=self.issue_1.reporter_id,
+        sequence=1)
+    amendments = [
+        tracker_pb2.Amendment(
+            field=tracker_pb2.FieldID.SUMMARY, newvalue='new', oldvalue='old'),
+        tracker_pb2.Amendment(
+            field=tracker_pb2.FieldID.OWNER, added_user_ids=[111]),
+        tracker_pb2.Amendment(
+            field=tracker_pb2.FieldID.CC,
+            added_user_ids=[111],
+            removed_user_ids=[222]),
+        tracker_pb2.Amendment(
+            field=tracker_pb2.FieldID.CUSTOM,
+            custom_field_name='EstDays',
+            newvalue='12')
+    ]
+    amendments_comment = tracker_pb2.IssueComment(
+        project_id=self.issue_1.project_id,
+        issue_id=self.issue_1.issue_id,
+        user_id=self.issue_1.reporter_id,
+        timestamp=self.PAST_TIME,
+        content='some amendments',
+        sequence=2,
+        amendments=amendments,
+        importer_id=1,  # Not used in conversion, so nothing to verify.
+        approval_id=self.approval_def_1_id)
+    inbound_spam_comment = tracker_pb2.IssueComment(
+        project_id=self.issue_1.project_id,
+        issue_id=self.issue_1.issue_id,
+        user_id=self.issue_1.reporter_id,
+        timestamp=self.PAST_TIME,
+        content='content',
+        sequence=3,
+        inbound_message='inbound message',
+        is_spam=True)
+    expected_0 = issue_objects_pb2.Comment(
+        name='projects/proj/issues/1/comments/0',
+        state=issue_objects_pb2.IssueContentState.Value('ACTIVE'),
+        type=issue_objects_pb2.Comment.Type.Value('DESCRIPTION'),
+        content='initial description',
+        commenter='users/111',
+        create_time=timestamp_pb2.Timestamp(seconds=self.PAST_TIME),
+        attachments=[
+            issue_objects_pb2.Comment.Attachment(
+                filename='example.png',
+                state=issue_objects_pb2.IssueContentState.Value('ACTIVE'),
+                size=12345,
+                media_type='image/png',
+                thumbnail_uri='attachment?aid=1&signed_aid=2&inline=1&thumb=1',
+                view_uri='attachment?aid=1&signed_aid=2&inline=1',
+                download_uri='attachment?aid=1&signed_aid=2'),
+            issue_objects_pb2.Comment.Attachment(
+                filename='deleted_example.png',
+                state=issue_objects_pb2.IssueContentState.Value('DELETED'),
+                media_type='image/png')
+        ])
+    expected_1 = issue_objects_pb2.Comment(
+        name='projects/proj/issues/1/comments/1',
+        state=issue_objects_pb2.IssueContentState.Value('DELETED'),
+        type=issue_objects_pb2.Comment.Type.Value('COMMENT'),
+        create_time=timestamp_pb2.Timestamp(seconds=self.PAST_TIME))
+    expected_2 = issue_objects_pb2.Comment(
+        name='projects/proj/issues/1/comments/2',
+        state=issue_objects_pb2.IssueContentState.Value('ACTIVE'),
+        type=issue_objects_pb2.Comment.Type.Value('COMMENT'),
+        content='some amendments',
+        commenter='users/111',
+        create_time=timestamp_pb2.Timestamp(seconds=self.PAST_TIME),
+        approval='projects/proj/approvalDefs/%d' % self.approval_def_1_id,
+        amendments=[
+            issue_objects_pb2.Comment.Amendment(
+                field_name='Summary', new_or_delta_value='new',
+                old_value='old'),
+            issue_objects_pb2.Comment.Amendment(
+                field_name='Owner', new_or_delta_value='o...@example.com'),
+            issue_objects_pb2.Comment.Amendment(
+                field_name='Cc',
+                new_or_delta_value='-t...@example.com o...@example.com'),
+            issue_objects_pb2.Comment.Amendment(
+                field_name='EstDays', new_or_delta_value='12')
+        ])
+    expected_3 = issue_objects_pb2.Comment(
+        name='projects/proj/issues/1/comments/3',
+        state=issue_objects_pb2.IssueContentState.Value('SPAM'),
+        type=issue_objects_pb2.Comment.Type.Value('COMMENT'),
+        content='content',
+        commenter='users/111',
+        create_time=timestamp_pb2.Timestamp(seconds=self.PAST_TIME),
+        inbound_message='inbound message')
+
+    comments = [
+        initial_comment, deleted_comment, amendments_comment,
+        inbound_spam_comment
+    ]
+    actual = self.converter.ConvertComments(self.issue_1.issue_id, comments)
+    self.assertEqual(actual, [expected_0, expected_1, expected_2, expected_3])
+
+  def testConvertComments_Empty(self):
+    """We can convert an empty list of comments."""
+    self.assertEqual(
+        self.converter.ConvertComments(self.issue_1.issue_id, []), [])
+
+  def testConvertIssue(self):
+    """We can convert a single issue."""
+    self.assertEqual(self.converter.ConvertIssue(self.issue_1),
+        self.converter.ConvertIssues([self.issue_1])[0])
+
+  def testConvertIssues(self):
+    """We can convert Issues."""
+    blocked_on_1 = fake.MakeTestIssue(
+        self.project_1.project_id,
+        3,
+        'sum3',
+        'New',
+        self.user_1.user_id,
+        issue_id=301,
+        project_name=self.project_1.project_name,
+    )
+    blocked_on_2 = fake.MakeTestIssue(
+        self.project_2.project_id,
+        4,
+        'sum4',
+        'New',
+        self.user_1.user_id,
+        issue_id=401,
+        project_name=self.project_2.project_name,
+    )
+    blocking = fake.MakeTestIssue(
+        self.project_2.project_id,
+        5,
+        'sum5',
+        'New',
+        self.user_1.user_id,
+        issue_id=501,
+        project_name=self.project_2.project_name,
+    )
+    self.services.issue.TestAddIssue(blocked_on_1)
+    self.services.issue.TestAddIssue(blocked_on_2)
+    self.services.issue.TestAddIssue(blocking)
+
+    # Reversing natural ordering to ensure order is respected.
+    self.issue_1.blocked_on_iids = [
+        blocked_on_2.issue_id, blocked_on_1.issue_id
+    ]
+    self.issue_1.dangling_blocked_on_refs = [
+        tracker_pb2.DanglingIssueRef(ext_issue_identifier='b/555'),
+        tracker_pb2.DanglingIssueRef(ext_issue_identifier='b/2')
+    ]
+    self.issue_1.blocking_iids = [blocking.issue_id]
+    self.issue_1.dangling_blocking_refs = [
+        tracker_pb2.DanglingIssueRef(ext_issue_identifier='b/3')
+    ]
+
+    issues = [self.issue_1, self.issue_2]
+    expected_1 = issue_objects_pb2.Issue(
+        name='projects/proj/issues/1',
+        summary='sum',
+        state=issue_objects_pb2.IssueContentState.Value('ACTIVE'),
+        status=issue_objects_pb2.Issue.StatusValue(
+            derivation=EXPLICIT_DERIVATION, status='New'),
+        reporter='users/111',
+        owner=issue_objects_pb2.Issue.UserValue(
+            derivation=EXPLICIT_DERIVATION, user='users/111'),
+        cc_users=[
+            issue_objects_pb2.Issue.UserValue(
+                derivation=EXPLICIT_DERIVATION, user='users/222'),
+            issue_objects_pb2.Issue.UserValue(
+                derivation=RULE_DERIVATION, user='users/333')
+        ],
+        labels=[
+            issue_objects_pb2.Issue.LabelValue(
+                derivation=EXPLICIT_DERIVATION, label='label-a'),
+            issue_objects_pb2.Issue.LabelValue(
+                derivation=EXPLICIT_DERIVATION, label='label-b'),
+            issue_objects_pb2.Issue.LabelValue(
+                derivation=RULE_DERIVATION, label='label-derived'),
+            issue_objects_pb2.Issue.LabelValue(
+                derivation=RULE_DERIVATION, label='label-derived-2')
+        ],
+        components=[
+            issue_objects_pb2.Issue.ComponentValue(
+                derivation=EXPLICIT_DERIVATION,
+                component='projects/proj/componentDefs/1'),
+            issue_objects_pb2.Issue.ComponentValue(
+                derivation=EXPLICIT_DERIVATION,
+                component='projects/proj/componentDefs/2'),
+            issue_objects_pb2.Issue.ComponentValue(
+                derivation=RULE_DERIVATION,
+                component='projects/proj/componentDefs/3'),
+            issue_objects_pb2.Issue.ComponentValue(
+                derivation=RULE_DERIVATION,
+                component='projects/proj/componentDefs/4'),
+        ],
+        field_values=[
+            issue_objects_pb2.FieldValue(
+                derivation=EXPLICIT_DERIVATION,
+                field='projects/proj/fieldDefs/%d' % self.field_def_1,
+                value=self.fv_1_value,
+            ),
+            issue_objects_pb2.FieldValue(
+                derivation=RULE_DERIVATION,
+                field='projects/proj/fieldDefs/%d' % self.field_def_1,
+                value=self.fv_1_value,
+            ),
+            issue_objects_pb2.FieldValue(
+                derivation=EXPLICIT_DERIVATION,
+                field='projects/proj/fieldDefs/%d' % self.field_def_3,
+                value='1',
+            ),
+            issue_objects_pb2.FieldValue(
+                derivation=RULE_DERIVATION,
+                field='projects/proj/fieldDefs/%d' % self.field_def_4,
+                value='mac',
+            )
+        ],
+        merged_into_issue_ref=issue_objects_pb2.IssueRef(ext_identifier='b/1'),
+        blocked_on_issue_refs=[
+            issue_objects_pb2.IssueRef(issue='projects/goose/issues/4'),
+            issue_objects_pb2.IssueRef(issue='projects/proj/issues/3'),
+            issue_objects_pb2.IssueRef(ext_identifier='b/555'),
+            issue_objects_pb2.IssueRef(ext_identifier='b/2')
+        ],
+        blocking_issue_refs=[
+            issue_objects_pb2.IssueRef(issue='projects/goose/issues/5'),
+            issue_objects_pb2.IssueRef(ext_identifier='b/3')
+        ],
+        create_time=timestamp_pb2.Timestamp(seconds=self.PAST_TIME),
+        modify_time=timestamp_pb2.Timestamp(seconds=self.PAST_TIME),
+        component_modify_time=timestamp_pb2.Timestamp(seconds=self.PAST_TIME),
+        status_modify_time=timestamp_pb2.Timestamp(seconds=self.PAST_TIME),
+        owner_modify_time=timestamp_pb2.Timestamp(seconds=self.PAST_TIME),
+        star_count=1,
+        attachment_count=5,
+        phases=[self.phase_1.name])
+    expected_2 = issue_objects_pb2.Issue(
+        name='projects/goose/issues/2',
+        summary='sum2',
+        state=issue_objects_pb2.IssueContentState.Value('SPAM'),
+        status=issue_objects_pb2.Issue.StatusValue(
+            derivation=RULE_DERIVATION, status='Fixed'),
+        reporter='users/111',
+        owner=issue_objects_pb2.Issue.UserValue(
+            derivation=RULE_DERIVATION, user='users/222'),
+        merged_into_issue_ref=issue_objects_pb2.IssueRef(
+            issue='projects/proj/issues/1'),
+        create_time=timestamp_pb2.Timestamp(seconds=self.PAST_TIME),
+        close_time=timestamp_pb2.Timestamp(seconds=self.PAST_TIME),
+        modify_time=timestamp_pb2.Timestamp(seconds=self.PAST_TIME),
+        component_modify_time=timestamp_pb2.Timestamp(seconds=self.PAST_TIME),
+        status_modify_time=timestamp_pb2.Timestamp(seconds=self.PAST_TIME),
+        owner_modify_time=timestamp_pb2.Timestamp(seconds=self.PAST_TIME))
+    self.assertEqual(
+        self.converter.ConvertIssues(issues), [expected_1, expected_2])
+
+  def testConvertIssues_Empty(self):
+    """ConvertIssues works with no issues passed in."""
+    self.assertEqual(self.converter.ConvertIssues([]), [])
+
+  def testConvertIssues_NegativeAttachmentCount(self):
+    """Negative attachment counts are not set on issues."""
+    issue = fake.MakeTestIssue(
+        self.project_1.project_id,
+        3,
+        'sum',
+        'New',
+        owner_id=None,
+        reporter_id=111,
+        attachment_count=-10,
+        project_name=self.project_1.project_name,
+        opened_timestamp=self.PAST_TIME,
+        modified_timestamp=self.PAST_TIME)
+    self.services.issue.TestAddIssue(issue)
+    expected_issue = issue_objects_pb2.Issue(
+        name='projects/proj/issues/3',
+        state=issue_objects_pb2.IssueContentState.Value('ACTIVE'),
+        summary='sum',
+        status=issue_objects_pb2.Issue.StatusValue(
+            derivation=EXPLICIT_DERIVATION, status='New'),
+        reporter='users/111',
+        create_time=timestamp_pb2.Timestamp(seconds=self.PAST_TIME),
+        modify_time=timestamp_pb2.Timestamp(seconds=self.PAST_TIME),
+        component_modify_time=timestamp_pb2.Timestamp(seconds=self.PAST_TIME),
+        status_modify_time=timestamp_pb2.Timestamp(seconds=self.PAST_TIME),
+        owner_modify_time=timestamp_pb2.Timestamp(seconds=self.PAST_TIME),
+    )
+    self.assertEqual(self.converter.ConvertIssues([issue]), [expected_issue])
+
+  def testConvertIssues_FilterApprovalFV(self):
+    issue = fake.MakeTestIssue(
+        self.project_1.project_id,
+        3,
+        'sum',
+        'New',
+        owner_id=None,
+        reporter_id=111,
+        attachment_count=-10,
+        project_name=self.project_1.project_name,
+        opened_timestamp=self.PAST_TIME,
+        modified_timestamp=self.PAST_TIME,
+        field_values=[self.fv_1, self.fv_6])
+    self.services.issue.TestAddIssue(issue)
+    actual = self.converter.ConvertIssues([issue])[0]
+
+    expected_fv = issue_objects_pb2.FieldValue(
+        derivation=EXPLICIT_DERIVATION,
+        field='projects/proj/fieldDefs/%d' % self.field_def_1,
+        value=self.fv_1_value,
+    )
+    self.assertEqual(len(actual.field_values), 1)
+    self.assertEqual(actual.field_values[0], expected_fv)
+
+  def testConvertUser(self):
+    """We can convert a single User."""
+    self.user_1.vacation_message = 'non-empty-string'
+    self.converter.user_auth = authdata.AuthData.FromUser(
+        self.cnxn, self.user_1, self.services)
+
+    expected_user = user_objects_pb2.User(
+        name='users/111',
+        display_name='one@example.com',
+        email='one@example.com',
+        availability_message='non-empty-string')
+    self.assertEqual(self.converter.ConvertUser(self.user_1), expected_user)
+
+
+  def testConvertUsers(self):
+    user_deleted = self.services.user.TestAddUser(
+        '', framework_constants.DELETED_USER_ID)
+    self.user_1.vacation_message = 'non-empty-string'
+    user_ids = [self.user_1.user_id, user_deleted.user_id]
+    self.converter.user_auth = authdata.AuthData.FromUser(
+        self.cnxn, self.user_1, self.services)
+
+    expected_user_dict = {
+        self.user_1.user_id:
+            user_objects_pb2.User(
+                name='users/111',
+                display_name='one@example.com',
+                email='one@example.com',
+                availability_message='non-empty-string'),
+        user_deleted.user_id:
+            user_objects_pb2.User(
+                name='users/1',
+                display_name=framework_constants.DELETED_USER_NAME,
+                email='',
+                availability_message='User never visited'),
+    }
+    self.assertEqual(self.converter.ConvertUsers(user_ids), expected_user_dict)
+
+  def testConvertProjectStars(self):
+    expected_stars = [
+        user_objects_pb2.ProjectStar(name='users/111/projectStars/proj'),
+        user_objects_pb2.ProjectStar(name='users/111/projectStars/goose')
+    ]
+    self.assertEqual(
+        self.converter.ConvertProjectStars(
+            self.user_1.user_id, [self.project_1, self.project_2]),
+        expected_stars)
+
+  def _Issue(self, project_id, local_id):
+    issue = tracker_pb2.Issue(owner_id=0)
+    issue.project_name = 'proj-%d' % project_id
+    issue.project_id = project_id
+    issue.local_id = local_id
+    issue.issue_id = project_id * 100 + local_id
+    return issue
+
+  def testIngestAttachmentUploads(self):
+    up_1 = issues_pb2.AttachmentUpload(
+        filename='clown.gif', content='iTs prOUnOuNcED JIF')
+    up_2 = issues_pb2.AttachmentUpload(
+        filename='mowgli', content='cutest dog')
+
+    ingested = self.converter.IngestAttachmentUploads([up_1, up_2])
+    expected = [framework_helpers.AttachmentUpload(
+        'clown.gif', 'iTs prOUnOuNcED JIF', 'image/gif'),
+                framework_helpers.AttachmentUpload(
+                    'mowgli', 'cutest dog', 'text/plain')]
+    self.assertEqual(ingested, expected)
+
+  def testtIngestAttachmentUploads_Invalid(self):
+    up_1 = issues_pb2.AttachmentUpload(filename='clown.gif')
+    up_2 = issues_pb2.AttachmentUpload(content='cutest dog')
+
+    with self.assertRaisesRegexp(
+        exceptions.InputException, 'Uploaded .+\nUploaded .+'):
+      self.converter.IngestAttachmentUploads([up_1, up_2])
+
+  def testIngestIssueDeltas(self):
+    # Set up.
+    self.services.project.TestAddProject('proj-780', project_id=780)
+    config = fake.MakeTestConfig(780, [], [])
+    self.services.config.StoreConfig(self.cnxn, config)
+
+    issue_1 = self._Issue(780, 1)
+    self.services.issue.TestAddIssue(issue_1)
+    issue_2 = self._Issue(780, 2)
+    self.services.issue.TestAddIssue(issue_2)
+    comp_1 = fake.MakeTestComponentDef(780, 1)
+    comp_2 = fake.MakeTestComponentDef(780, 2)
+    fd_str = fake.MakeTestFieldDef(1, 780, tracker_pb2.FieldTypes.STR_TYPE)
+    fd_enum = fake.MakeTestFieldDef(
+        2, 780, tracker_pb2.FieldTypes.ENUM_TYPE, field_name='Kingdom')
+    config = fake.MakeTestConfig(780, [], [])
+    config.component_defs = [comp_1, comp_2]
+    config.field_defs = [fd_str, fd_enum]
+    self.services.config.StoreConfig(self.cnxn, config)
+
+    # Issue and delta that changes all things.
+    api_issue_all = issue_objects_pb2.Issue(
+        name='projects/proj-780/issues/1',
+        status=issue_objects_pb2.Issue.StatusValue(status='Fixed'),
+        owner=issue_objects_pb2.Issue.UserValue(user='users/111'),
+        summary='honk honk.',
+        cc_users=[issue_objects_pb2.Issue.UserValue(user='users/222')],
+        components=[
+            issue_objects_pb2.Issue.ComponentValue(
+                component='projects/proj-780/componentDefs/1')
+        ],
+        field_values=[
+            issue_objects_pb2.FieldValue(
+                field='projects/proj-780/fieldDefs/1', value='chicken'),
+            issue_objects_pb2.FieldValue(
+                field='projects/proj-780/fieldDefs/2', value='come')
+        ],
+        labels=[issue_objects_pb2.Issue.LabelValue(label='ready')])
+    mask_all = field_mask_pb2.FieldMask(
+        paths=[
+            'status', 'owner', 'summary', 'cc_users', 'labels', 'components',
+            'field_values'
+        ])
+    api_delta_all = issues_pb2.IssueDelta(
+        issue=api_issue_all,
+        update_mask=mask_all,
+        ccs_remove=['users/333'],
+        components_remove=['projects/proj-780/componentDefs/2'],
+        field_vals_remove=[
+            issue_objects_pb2.FieldValue(
+                field='projects/proj-780/fieldDefs/1', value='rooster'),
+            issue_objects_pb2.FieldValue(
+                field='projects/proj-780/fieldDefs/2', value='leave')
+        ],
+        labels_remove=['not-ready'])
+    exp_fvs_add = [
+        field_helpers.ParseOneFieldValue(
+            self.cnxn, self.services.user, fd_str, 'chicken')
+    ]
+    exp_fvs_remove = [
+        field_helpers.ParseOneFieldValue(
+            self.cnxn, self.services.user, fd_str, 'rooster')
+    ]
+    expected_delta_all = tracker_pb2.IssueDelta(
+        status='Fixed',
+        owner_id=111,
+        summary='honk honk.',
+        cc_ids_add=[222],
+        cc_ids_remove=[333],
+        comp_ids_add=[1],
+        comp_ids_remove=[2],
+        field_vals_add=exp_fvs_add,
+        field_vals_remove=exp_fvs_remove,
+        labels_add=['ready', 'Kingdom-come'],
+        labels_remove=['not-ready', 'Kingdom-leave'])
+
+    api_deltas = [api_delta_all]
+
+    # Issue with all fields, but an empty mask.
+    api_issue_all_masked = issue_objects_pb2.Issue(
+        name='projects/proj-780/issues/2',
+        status=issue_objects_pb2.Issue.StatusValue(status='Fixed'),
+        owner=issue_objects_pb2.Issue.UserValue(user='users/111'),
+        summary='honk honk.',
+        cc_users=[issue_objects_pb2.Issue.UserValue(user='users/222')],
+        components=[
+            issue_objects_pb2.Issue.ComponentValue(
+                component='projects/proj-780/componentDefs/1')
+        ],
+        field_values=[
+            issue_objects_pb2.FieldValue(
+                field='projects/proj-780/fieldDefs/1', value='chicken'),
+            issue_objects_pb2.FieldValue(
+                field='projects/proj-780/fieldDefs/2', value='come')
+        ],
+        labels=[issue_objects_pb2.Issue.LabelValue(label='ready')])
+    api_delta_all_masked = issues_pb2.IssueDelta(
+        issue=api_issue_all_masked,
+        update_mask=field_mask_pb2.FieldMask(paths=[]),
+        ccs_remove=['users/333'],
+        components_remove=['projects/proj-780/componentDefs/2'],
+        field_vals_remove=[
+            issue_objects_pb2.FieldValue(
+                field='projects/proj-780/fieldDefs/1', value='rooster'),
+            issue_objects_pb2.FieldValue(
+                field='projects/proj-780/fieldDefs/2', value='leave')
+        ],
+        labels_remove=['not-ready'])
+    expected_delta_all_masked = tracker_pb2.IssueDelta(
+        cc_ids_remove=[333],
+        comp_ids_remove=[2],
+        labels_remove=['not-ready', 'Kingdom-leave'],
+        field_vals_remove=exp_fvs_remove)
+
+    api_deltas.append(api_delta_all_masked)
+
+    actual = self.converter.IngestIssueDeltas(api_deltas)
+    expected = [(78001, expected_delta_all), (78002, expected_delta_all_masked)]
+    self.assertEqual(actual, expected)
+
+  def testIngestIssueDeltas_IssueRefs(self):
+    # Set up.
+    self.services.project.TestAddProject('proj-780', project_id=780)
+    issue = self._Issue(780, 1)
+    self.services.issue.TestAddIssue(issue)
+
+    bo_add = self._Issue(780, 2)
+    self.services.issue.TestAddIssue(bo_add)
+
+    b_add = self._Issue(780, 3)
+    self.services.issue.TestAddIssue(b_add)
+
+    bo_remove = self._Issue(780, 4)
+    self.services.issue.TestAddIssue(bo_remove)
+
+    b_remove = self._Issue(780, 5)
+    self.services.issue.TestAddIssue(b_remove)
+
+    # merge_remove tested in testIngestIssueDeltas_RemoveNonRepeated
+    merge_add = self._Issue(780, 6)
+    self.services.issue.TestAddIssue(merge_add)
+
+    api_issue = issue_objects_pb2.Issue(
+        name='projects/proj-780/issues/1',
+        blocked_on_issue_refs=[
+            issue_objects_pb2.IssueRef(issue='projects/proj-780/issues/2'),
+            issue_objects_pb2.IssueRef(ext_identifier='b/1')
+        ],
+        blocking_issue_refs=[
+            issue_objects_pb2.IssueRef(issue='projects/proj-780/issues/3'),
+            issue_objects_pb2.IssueRef(ext_identifier='b/2')
+        ],
+        merged_into_issue_ref=issue_objects_pb2.IssueRef(
+            issue='projects/proj-780/issues/6'))
+
+    api_delta = issues_pb2.IssueDelta(
+        issue=api_issue,
+        update_mask=field_mask_pb2.FieldMask(
+            paths=[
+                'blocked_on_issue_refs', 'blocking_issue_refs',
+                'merged_into_issue_ref'
+            ]),
+        blocked_on_issues_remove=[
+            issue_objects_pb2.IssueRef(issue='projects/proj-780/issues/4'),
+            issue_objects_pb2.IssueRef(ext_identifier='b/3')
+        ],
+        blocking_issues_remove=[
+            issue_objects_pb2.IssueRef(issue='projects/proj-780/issues/5'),
+            issue_objects_pb2.IssueRef(ext_identifier='b/4')
+        ])
+
+    expected_delta = tracker_pb2.IssueDelta(
+        blocked_on_add=[bo_add.issue_id],
+        blocked_on_remove=[bo_remove.issue_id],
+        blocking_add=[b_add.issue_id],
+        blocking_remove=[b_remove.issue_id],
+        ext_blocked_on_add=['b/1'],
+        ext_blocked_on_remove=['b/3'],
+        ext_blocking_add=['b/2'],
+        ext_blocking_remove=['b/4'],
+        merged_into=merge_add.issue_id)
+
+    # Test adding an external merged_into_issue.
+    api_issue_ext_merged = issue_objects_pb2.Issue(
+        name='projects/proj-780/issues/2',
+        merged_into_issue_ref=issue_objects_pb2.IssueRef(ext_identifier='b/1'))
+    api_delta_ext_merged = issues_pb2.IssueDelta(
+        issue=api_issue_ext_merged,
+        update_mask=field_mask_pb2.FieldMask(paths=['merged_into_issue_ref']))
+    expected_delta_ext_merged = tracker_pb2.IssueDelta(
+        merged_into_external='b/1')
+
+    # Test issue with empty mask.
+    issue_all_masked = self._Issue(780, 11)
+    self.services.issue.TestAddIssue(issue_all_masked)
+
+    api_issue_all_masked = copy.deepcopy(api_issue)
+    api_issue_all_masked.name = 'projects/proj-780/issues/11'
+    api_delta_all_masked = issues_pb2.IssueDelta(
+        issue=api_issue_all_masked, update_mask=field_mask_pb2.FieldMask())
+    expected_all_masked_delta = tracker_pb2.IssueDelta()
+
+    # Check results.
+    actual = self.converter.IngestIssueDeltas(
+        [api_delta, api_delta_ext_merged, api_delta_all_masked])
+
+    expected = [
+        (78001, expected_delta), (78002, expected_delta_ext_merged),
+        (78011, expected_all_masked_delta)
+    ]
+    self.assertEqual(actual, expected)
+
+  def testIngestIssueDeltas_OwnerAndOwnerDotUser(self):
+    # Set up.
+    self.services.project.TestAddProject('proj-780', project_id=780)
+    issue = self._Issue(780, 1)
+    self.services.issue.TestAddIssue(issue)
+
+    api_issue = issue_objects_pb2.Issue(
+        name='projects/proj-780/issues/1',
+        owner=issue_objects_pb2.Issue.UserValue(user='users/111')
+    )
+
+    # Expect ingest to work when update_mask has just 'owner'.
+    api_delta = issues_pb2.IssueDelta(
+        issue=api_issue,
+        update_mask=field_mask_pb2.FieldMask(paths=['owner'])
+    )
+    expected_delta = tracker_pb2.IssueDelta(owner_id=111)
+    expected = [(78001, expected_delta)]
+    actual = self.converter.IngestIssueDeltas([api_delta])
+    self.assertEqual(actual, expected)
+
+    # Expect ingest to also work when update_mask uses 'owner.user' instead.
+    api_delta = issues_pb2.IssueDelta(
+        issue=api_issue,
+        update_mask=field_mask_pb2.FieldMask(paths=['owner.user'])
+    )
+    actual = self.converter.IngestIssueDeltas([api_delta])
+    self.assertEqual(actual, expected)
+
+  def testIngestIssueDeltas_StatusAndStatusDotStatus(self):
+    # Set up.
+    self.services.project.TestAddProject('proj-780', project_id=780)
+    issue = self._Issue(780, 1)
+    self.services.issue.TestAddIssue(issue)
+
+    api_issue = issue_objects_pb2.Issue(
+        name='projects/proj-780/issues/1',
+        owner=issue_objects_pb2.Issue.UserValue(user='users/111'),
+        status=issue_objects_pb2.Issue.StatusValue(status='New')
+    )
+
+    # Expect ingest to work when update_mask has just 'status'.
+    api_delta = issues_pb2.IssueDelta(
+        issue=api_issue,
+        update_mask=field_mask_pb2.FieldMask(paths=['status'])
+    )
+    expected_delta = tracker_pb2.IssueDelta(status='New')
+    expected = [(78001, expected_delta)]
+    actual = self.converter.IngestIssueDeltas([api_delta])
+    self.assertEqual(actual, expected)
+
+    # Expect ingest to also work when update_mask uses 'status.status' instead.
+    api_delta = issues_pb2.IssueDelta(
+        issue=api_issue,
+        update_mask=field_mask_pb2.FieldMask(paths=['status.status'])
+    )
+    actual = self.converter.IngestIssueDeltas([api_delta])
+    self.assertEqual(actual, expected)
+
+  def testIngestIssueDeltas_RemoveNonRepeated(self):
+    # Set up.
+    self.services.project.TestAddProject('proj-780', project_id=780)
+    issue_1 = self._Issue(780, 1)
+    self.services.issue.TestAddIssue(issue_1)
+    issue_2 = self._Issue(780, 2)
+    self.services.issue.TestAddIssue(issue_2)
+
+    # Check we can remove fields without specifying them in the
+    # issue, as long as they're specified in the FieldMask.
+    api_issue = issue_objects_pb2.Issue(
+        name='projects/proj-780/issues/1')
+    api_delta = issues_pb2.IssueDelta(
+        issue=api_issue,
+        update_mask=field_mask_pb2.FieldMask(
+            paths=[
+                'owner.user', 'status.status', 'summary',
+                'merged_into_issue_ref.issue'
+            ]))
+
+    # Check thet setting fields to '' result in same behavior as not
+    # explicitly setting the values at all.
+    api_issue_set = issue_objects_pb2.Issue(
+        name='projects/proj-780/issues/2',
+        summary='',
+        status=issue_objects_pb2.Issue.StatusValue(status=''),
+        owner=issue_objects_pb2.Issue.UserValue(user=''),
+        merged_into_issue_ref=issue_objects_pb2.IssueRef(issue=''))
+    api_delta_set = issues_pb2.IssueDelta(
+        issue=api_issue_set,
+        update_mask=field_mask_pb2.FieldMask(
+            paths=[
+                'owner.user', 'status.status', 'summary',
+                'merged_into_issue_ref.issue'
+            ]))
+
+    expected_delta = tracker_pb2.IssueDelta(
+        owner_id=framework_constants.NO_USER_SPECIFIED,
+        status='',
+        summary='',
+        merged_into=0)
+
+    actual = self.converter.IngestIssueDeltas([api_delta, api_delta_set])
+    expected = [(78001, expected_delta), (78002, expected_delta)]
+    self.assertEqual(actual, expected)
+
+  def testIngestIssueDeltas_InvalidMask(self):
+    self.services.project.TestAddProject('proj-780', project_id=780)
+    issue_1 = self._Issue(780, 1)
+    self.services.issue.TestAddIssue(issue_1)
+    issue_2 = self._Issue(780, 2)
+    self.services.issue.TestAddIssue(issue_2)
+    issue_3 = self._Issue(780, 3)
+    self.services.issue.TestAddIssue(issue_3)
+    api_deltas = []
+    err_msgs = []
+
+    api_issue_1 = issue_objects_pb2.Issue(name='projects/proj-780/issues/1')
+    api_delta_1 = issues_pb2.IssueDelta(issue=api_issue_1)
+    api_deltas.append(api_delta_1)
+    err_msgs.append(
+        '`update_mask` must be set for projects/proj-780/issues/1 delta.')
+
+    api_issue_2 = issue_objects_pb2.Issue(name='projects/proj-780/issues/2')
+    api_delta_2 = issues_pb2.IssueDelta(
+        issue=api_issue_2,
+        update_mask=field_mask_pb2.FieldMask())  # Empty but set is fine.
+    api_deltas.append(api_delta_2)
+
+    api_issue_3 = issue_objects_pb2.Issue(name='projects/proj-780/issues/3')
+    api_delta_3 = issues_pb2.IssueDelta(
+        issue=api_issue_3,
+        update_mask=field_mask_pb2.FieldMask(paths=['chicken']))
+    api_deltas.append(api_delta_3)
+    err_msgs.append(
+        'Invalid `update_mask` for projects/proj-780/issues/3 delta.')
+
+    with self.assertRaisesRegexp(exceptions.InputException,
+                                 '\n'.join(err_msgs)):
+      self.converter.IngestIssueDeltas(api_deltas)
+
+  def testIngestIssueDeltas_OutputOnlyIgnored(self):
+    # Set up.
+    self.services.project.TestAddProject('proj-780', project_id=780)
+    issue_1 = self._Issue(780, 1)
+    self.services.issue.TestAddIssue(issue_1)
+    comp_1 = fake.MakeTestComponentDef(780, 1)
+    fd_str = fake.MakeTestFieldDef(1, 780, tracker_pb2.FieldTypes.STR_TYPE)
+    config = fake.MakeTestConfig(780, [], [])
+    config.component_defs = [comp_1]
+    config.field_defs = [fd_str]
+    self.services.config.StoreConfig(self.cnxn, config)
+
+    api_issue = issue_objects_pb2.Issue(
+        name='projects/proj-780/issues/1',
+        owner=issue_objects_pb2.Issue.UserValue(
+            user='users/111',
+            derivation=issue_objects_pb2.Derivation.Value('RULE')),
+        status=issue_objects_pb2.Issue.StatusValue(
+            status='KingdomCome',
+            derivation=issue_objects_pb2.Derivation.Value('RULE')),
+        state=issue_objects_pb2.IssueContentState.Value('DELETED'),
+        reporter='users/222',
+        cc_users=[
+            issue_objects_pb2.Issue.UserValue(
+                user='users/333',
+                derivation=issue_objects_pb2.Derivation.Value('RULE'))
+        ],
+        labels=[
+            issue_objects_pb2.Issue.LabelValue(
+                label='wikipedia-sections',
+                derivation=issue_objects_pb2.Derivation.Value('RULE'))
+        ],
+        components=[
+            issue_objects_pb2.Issue.ComponentValue(
+                component='projects/proj-780/componentDefs/1',
+                derivation=issue_objects_pb2.Derivation.Value('RULE'))
+        ],
+        field_values=[
+            issue_objects_pb2.FieldValue(
+                field='projects/proj-780/fieldDefs/1',
+                value='bugs',
+                derivation=issue_objects_pb2.Derivation.Value('RULE'))
+        ],
+        create_time=timestamp_pb2.Timestamp(seconds=4044242),
+        close_time=timestamp_pb2.Timestamp(seconds=4044242),
+        modify_time=timestamp_pb2.Timestamp(seconds=4044242),
+        component_modify_time=timestamp_pb2.Timestamp(seconds=4044242),
+        status_modify_time=timestamp_pb2.Timestamp(seconds=4044242),
+        owner_modify_time=timestamp_pb2.Timestamp(seconds=4044242),
+        attachment_count=4,
+        star_count=2,
+        phases=['EarlyLife', 'CrimesBegin', 'CrimesContinue'])
+    paths_with_output_only = [
+        'owner', 'status', 'state', 'reporter', 'cc_users', 'labels',
+        'components', 'field_values', 'create_time', 'close_time',
+        'modify_time', 'component_modify_time', 'status_modify_time',
+        'owner_modify_time', 'attachment_count', 'star_count', 'phases']
+    api_delta = issues_pb2.IssueDelta(
+        issue=api_issue,
+        update_mask=field_mask_pb2.FieldMask(paths=paths_with_output_only))
+
+    expected_delta = tracker_pb2.IssueDelta(
+        # We ignore all Issue.*Value.derivation OUTPUT_ONLY fields.
+        owner_id=111,
+        status='KingdomCome',
+        cc_ids_add=[333],
+        labels_add=['wikipedia-sections'],
+        comp_ids_add=[1],
+        field_vals_add=[
+            field_helpers.ParseOneFieldValue(
+                self.cnxn, self.services.user, fd_str, 'bugs')
+        ])
+
+    actual = self.converter.IngestIssueDeltas([api_delta])
+    expected = [(78001, expected_delta)]
+    self.assertEqual(actual, expected)
+
+
+  def testIngestIssueDeltas_Empty(self):
+    actual = self.converter.IngestIssueDeltas([])
+    self.assertEqual(actual, [])
+
+  def testIngestIssueDeltas_InvalidValuesForFields(self):
+    # Set up.
+    self.services.project.TestAddProject('proj-780', project_id=780)
+    issue_1 = self._Issue(780, 1)
+    self.services.issue.TestAddIssue(issue_1)
+    fd_int = fake.MakeTestFieldDef(1, 780, tracker_pb2.FieldTypes.INT_TYPE)
+    fd_date = fake.MakeTestFieldDef(2, 780, tracker_pb2.FieldTypes.DATE_TYPE)
+    config = fake.MakeTestConfig(780, [], [])
+    config.field_defs = [fd_int, fd_date]
+    self.services.config.StoreConfig(self.cnxn, config)
+
+    api_issue = issue_objects_pb2.Issue(
+        name='projects/proj-780/issues/1',
+        field_values=[
+            issue_objects_pb2.FieldValue(
+                field='projects/proj-780/fieldDefs/1',
+                value='NotAnInt',
+                derivation=issue_objects_pb2.Derivation.Value('RULE')),
+            issue_objects_pb2.FieldValue(
+                field='projects/proj-780/fieldDefs/2',
+                value='NoDate',
+                derivation=issue_objects_pb2.Derivation.Value('EXPLICIT')),
+        ],
+    )
+    api_delta = issues_pb2.IssueDelta(
+        issue=api_issue,
+        update_mask=field_mask_pb2.FieldMask(paths=['field_values']))
+    error_messages = [
+        r'Could not ingest value \(NotAnInt\) for FieldDef \(projects/proj-780/'
+        r'fieldDefs/1\): Could not parse NotAnInt',
+        r'Could not ingest value \(NoDate\) for FieldDef \(projects/proj-780/fi'
+        r'eldDefs/2\): Could not parse NoDate',
+    ]
+    error_messages_re = '\n'.join(error_messages)
+    with self.assertRaisesRegexp(exceptions.InputException, error_messages_re):
+      self.converter.IngestIssueDeltas([api_delta])
+
+  @mock.patch('time.time', mock.MagicMock(return_value=CURRENT_TIME))
+  def testIngestApprovalDeltas(self):
+    mask = field_mask_pb2.FieldMask(
+        paths=['approvers', 'status', 'setter', 'phase', 'set_time'])
+    av_name = (
+        'projects/proj/issues/1/approvalValues/%d' % self.approval_def_1_id)
+    approval_delta = issues_pb2.ApprovalDelta(
+        approval_value=issue_objects_pb2.ApprovalValue(
+            name=av_name,
+            status=issue_objects_pb2.ApprovalValue.ApprovalStatus.Value('NA'),
+            approvers=['users/222', 'users/333'],
+            approval_def='ignored',
+            set_time=timestamp_pb2.Timestamp(),  # Ignored.
+            setter='ignored',
+            phase='ignored'),
+        update_mask=mask,
+        approvers_remove=['users/222'])
+    actual = self.converter.IngestApprovalDeltas(
+        [approval_delta], self.user_1.user_id)
+    expected_delta = tracker_pb2.ApprovalDelta(
+        status=tracker_pb2.ApprovalStatus.NA,
+        setter_id=self.user_1.user_id,
+        set_on=int(CURRENT_TIME),
+        approver_ids_add=[222, 333],
+        approver_ids_remove=[222],
+    )
+    expected_delta_specifications = [
+        (self.issue_1.issue_id, self.approval_def_1_id, expected_delta)
+    ]
+    self.assertEqual(actual, expected_delta_specifications)
+
+  def testIngestApprovalDeltas_EmptyMask(self):
+    av_name = (
+        'projects/proj/issues/1/approvalValues/%d' % self.approval_def_1_id)
+    # field_def_6 belongs to approval_def_1.
+    approval_fv = issue_objects_pb2.FieldValue(
+        field='projects/proj/fieldDefs/%d' % self.field_def_6, value=u'x')
+    approval_delta = issues_pb2.ApprovalDelta(
+        approval_value=issue_objects_pb2.ApprovalValue(
+            name=av_name,
+            status=issue_objects_pb2.ApprovalValue.ApprovalStatus.Value('NA'),
+            approvers=['users/222', 'users/333'],
+            approval_def='ignored',
+            field_values=[approval_fv],
+            set_time=timestamp_pb2.Timestamp(),  # Ignored.
+            setter='ignored',
+            phase='ignored'),
+        update_mask=field_mask_pb2.FieldMask(),
+        approvers_remove=['users/222'])
+    actual = self.converter.IngestApprovalDeltas(
+        [approval_delta], self.user_1.user_id)
+    expected_delta = tracker_pb2.ApprovalDelta(approver_ids_remove=[222])
+    expected_delta_specifications = [
+        (self.issue_1.issue_id, self.approval_def_1_id, expected_delta)
+    ]
+    self.assertEqual(actual, expected_delta_specifications)
+
+  def testIngestApprovalDeltas_InvalidMask(self):
+    av_name = (
+        'projects/proj/issues/1/approvalValues/%d' % self.approval_def_1_id)
+    approval_delta = issues_pb2.ApprovalDelta(
+        approval_value=issue_objects_pb2.ApprovalValue(name=av_name),
+        update_mask=field_mask_pb2.FieldMask(paths=['chicken']))
+    expected_err = 'Invalid `update_mask` for %s delta' % av_name
+    with self.assertRaisesRegexp(exceptions.InputException, expected_err):
+      self.converter.IngestApprovalDeltas([approval_delta], self.user_1.user_id)
+
+  def testIngestApprovalDeltas_FilterFieldValues(self):
+    av_name = (
+        'projects/proj/issues/1/approvalValues/%d' % self.approval_def_1_id)
+
+    # field_def_6 belongs to approval_def_1, should be ingested.
+    approval_fv = issue_objects_pb2.FieldValue(
+        field='projects/proj/fieldDefs/%d' % self.field_def_6,
+        value=u'touch-nose',
+        derivation=RULE_DERIVATION,  # Ignored.
+    )
+    # An enum field belonging to approval_def_1, should be ingested.
+    approval_enum_field_id = self._CreateFieldDef(
+        self.project_1.project_id,
+        'approval2field',
+        'ENUM_TYPE',
+        approval_id=self.approval_def_1_id)
+    approval_enum_fv = issue_objects_pb2.FieldValue(
+        field='projects/proj/fieldDefs/%d' % approval_enum_field_id,
+        value=u'enumval')
+    # Create field value that points to different approval, should raise error.
+    approval_2_fv = issue_objects_pb2.FieldValue(
+        field='projects/proj/fieldDefs/%d' % self.field_def_2, value=u'error')
+    av = issue_objects_pb2.ApprovalValue(
+        name=av_name, field_values=[approval_fv])
+    approval_delta = issues_pb2.ApprovalDelta(
+        update_mask=field_mask_pb2.FieldMask(paths=['field_values']),
+        approval_value=av,
+        field_vals_remove=[approval_enum_fv, approval_2_fv],
+        approvers_remove=['users/222'],
+    )
+    with self.assertRaisesRegexp(exceptions.InputException,
+                                 'Field .* does not belong to approval .*'):
+      self.converter.IngestApprovalDeltas([approval_delta], self.user_1.user_id)
+
+  def testIngestApprovalDeltas_InvalidFieldValues(self):
+    av_name = (
+        'projects/proj/issues/1/approvalValues/%d' % self.approval_def_1_id)
+    approval_fv = issue_objects_pb2.FieldValue(
+        field='projects/proj/fieldDefs/%d' % self.field_def_6,
+        value=u'touch-nose',
+        derivation=RULE_DERIVATION,  # Ignored.
+    )
+    other_fv = issue_objects_pb2.FieldValue(
+        field='projects/proj/fieldDefs/%d' % self.field_def_1,
+        value=u'something',
+    )
+    # This does not exist, and should throw error.
+    dne_fv = issue_objects_pb2.FieldValue(
+        field='projects/proj/fieldDefs/404',
+        value=u'DoesNotExist',
+    )
+    av = issue_objects_pb2.ApprovalValue(
+        name=av_name, field_values=[other_fv, approval_fv, dne_fv])
+    approval_delta = issues_pb2.ApprovalDelta(
+        update_mask=field_mask_pb2.FieldMask(paths=['field_values']),
+        approval_value=av,
+        approvers_remove=['users/222'],
+    )
+    with self.assertRaisesRegexp(
+        exceptions.InputException,
+        'Field projects/proj/fieldDefs/404 is not in this project'):
+      self.converter.IngestApprovalDeltas([approval_delta], self.user_1.user_id)
+
+  def testIngestApprovalDeltas_WrongProject(self):
+    approval_def_project2_name = 'project2_approval'
+    approval_def_project2_id = self._CreateFieldDef(
+        self.project_2.project_id,
+        approval_def_project2_name,
+        'APPROVAL_TYPE',
+        docstring='project2_ad_docstring',
+        admin_ids=[self.user_1.user_id])
+    self.services.config.UpdateConfig(
+        self.cnxn,
+        self.project_2,
+        approval_defs=[
+            (approval_def_project2_id, [self.user_1.user_id], 'survey')
+        ])
+    wrong_project_av_name = (
+        'projects/proj/issues/1/approvalValues/%d' % approval_def_project2_id)
+    approval_delta = issues_pb2.ApprovalDelta(
+        update_mask=field_mask_pb2.FieldMask(),
+        approval_value=issue_objects_pb2.ApprovalValue(
+            name=wrong_project_av_name))
+    with self.assertRaises(exceptions.InputException):
+      self.converter.IngestApprovalDeltas([approval_delta], self.user_1.user_id)
+
+  def testIngestApprovalDeltas_DoesNotExist(self):
+    dne_av_name = ('projects/proj/issues/1/approvalValues/404')
+    approval_delta = issues_pb2.ApprovalDelta(
+        approval_value=issue_objects_pb2.ApprovalValue(name=dne_av_name),
+        update_mask=field_mask_pb2.FieldMask())
+    with self.assertRaises(exceptions.InputException):
+      self.converter.IngestApprovalDeltas([approval_delta], self.user_1.user_id)
+
+  def testIngestApprovalDeltas_NonApproval(self):
+    """We fail if provided a non-approval Field ID in the resource name."""
+    dne_av_name = (
+        'projects/proj/issues/1/approvalValues/%s' % self.field_def_1)
+    approval_delta = issues_pb2.ApprovalDelta(
+        approval_value=issue_objects_pb2.ApprovalValue(name=dne_av_name),
+        update_mask=field_mask_pb2.FieldMask())
+    with self.assertRaises(exceptions.InputException):
+      self.converter.IngestApprovalDeltas([approval_delta], self.user_1.user_id)
+
+  def testIngestApprovalDeltas_IssueDoesNotExist(self):
+    dne_av_name = (
+        'projects/proj/issues/404/approvalValues/%d' % self.approval_def_1_id)
+    approval_delta = issues_pb2.ApprovalDelta(
+        approval_value=issue_objects_pb2.ApprovalValue(name=dne_av_name),
+        update_mask=field_mask_pb2.FieldMask())
+    with self.assertRaises(exceptions.NoSuchIssueException):
+      self.converter.IngestApprovalDeltas([approval_delta], self.user_1.user_id)
+
+  def testIngestApprovalDeltas_EmptyDelta(self):
+    av_name = (
+        'projects/proj/issues/1/approvalValues/%d' % self.approval_def_1_id)
+    approval_delta = issues_pb2.ApprovalDelta(
+        approval_value=issue_objects_pb2.ApprovalValue(name=av_name),
+        update_mask=field_mask_pb2.FieldMask())
+
+    actual = self.converter.IngestApprovalDeltas(
+        [approval_delta], self.user_1.user_id)
+
+    expected_delta = tracker_pb2.ApprovalDelta()
+    expected_delta_specifications = [
+        (self.issue_1.issue_id, self.approval_def_1_id, expected_delta)
+    ]
+    self.assertEqual(actual, expected_delta_specifications)
+
+  def testIngestApprovalDeltas_InvalidName(self):
+    approval_delta = issues_pb2.ApprovalDelta(
+        approval_value=issue_objects_pb2.ApprovalValue(name='x'))
+    with self.assertRaises(exceptions.InputException):
+      self.converter.IngestApprovalDeltas([approval_delta], self.user_1.user_id)
+
+  def testIngestApprovalDeltas_NoName(self):
+    approval_delta = issues_pb2.ApprovalDelta(
+        approval_value=issue_objects_pb2.ApprovalValue(
+            status=issue_objects_pb2.ApprovalValue.ApprovalStatus.Value('NA')))
+    with self.assertRaises(exceptions.InputException):
+      self.converter.IngestApprovalDeltas([approval_delta], self.user_1.user_id)
+
+  def testIngestApprovalDeltas_NoStatus(self):
+    """Setter ID isn't set when status isn't set."""
+    av_name = (
+        'projects/proj/issues/1/approvalValues/%d' % self.approval_def_1_id)
+    approval_delta = issues_pb2.ApprovalDelta(
+        approval_value=issue_objects_pb2.ApprovalValue(
+            name=av_name,
+            status=issue_objects_pb2.ApprovalValue.ApprovalStatus.Value('NA'),
+            approvers=['users/333']),
+        # Status left out of update mask.
+        update_mask=field_mask_pb2.FieldMask(paths=['approvers']),
+        approvers_remove=['users/222'])
+    actual = self.converter.IngestApprovalDeltas(
+        [approval_delta], self.user_1.user_id)
+    expected_delta = tracker_pb2.ApprovalDelta(
+        approver_ids_add=[333], approver_ids_remove=[222])
+    expected_delta_specifications = [
+        (self.issue_1.issue_id, self.approval_def_1_id, expected_delta)
+    ]
+    self.assertEqual(actual, expected_delta_specifications)
+
+  def testIngestApprovalDeltas_ApproverRemoveDoesNotExist(self):
+    av_name = (
+        'projects/proj/issues/1/approvalValues/%d' % self.approval_def_1_id)
+    approval_delta = issues_pb2.ApprovalDelta(
+        approval_value=issue_objects_pb2.ApprovalValue(name=av_name),
+        update_mask=field_mask_pb2.FieldMask(),
+        approvers_remove=['users/nobody@404.com'])
+    with self.assertRaises(exceptions.NoSuchUserException):
+      self.converter.IngestApprovalDeltas([approval_delta], self.user_1.user_id)
+
+  def testIngestApprovalDeltas_ApproverAddDoesNotExist(self):
+    av_name = (
+        'projects/proj/issues/1/approvalValues/%d' % self.approval_def_1_id)
+    approval_delta = issues_pb2.ApprovalDelta(
+        approval_value=issue_objects_pb2.ApprovalValue(
+            name=av_name, approvers=['users/nobody@404.com']),
+        update_mask=field_mask_pb2.FieldMask(paths=['approvers']))
+    with self.assertRaises(exceptions.NoSuchUserException):
+      self.converter.IngestApprovalDeltas([approval_delta], self.user_1.user_id)
+
+  def testIngestApprovalDeltas_FirstErrorRaised(self):
+    """Until we have error aggregation, we raise the first found error."""
+    av_name = (
+        'projects/proj/issues/1/approvalValues/%d' % self.approval_def_1_id)
+    user_dne_delta = issues_pb2.ApprovalDelta(
+        approval_value=issue_objects_pb2.ApprovalValue(
+            name=av_name, approvers=['users/nobody@404.com']),
+        update_mask=field_mask_pb2.FieldMask(paths=['approvers']))
+    invalid_name_delta = issues_pb2.ApprovalDelta(
+        approval_value=issue_objects_pb2.ApprovalValue(name='garbage'))
+    with self.assertRaises(exceptions.NoSuchUserException):
+      self.converter.IngestApprovalDeltas(
+          [user_dne_delta, invalid_name_delta], self.user_1.user_id)
+
+  def testIngestApprovalDeltas_MultipleDeltasSameSetOn(self):
+    av_name = (
+        'projects/proj/issues/1/approvalValues/%d' % self.approval_def_1_id)
+    delta_1 = issues_pb2.ApprovalDelta(
+        approval_value=issue_objects_pb2.ApprovalValue(
+            name=av_name,
+            status=issue_objects_pb2.ApprovalValue.ApprovalStatus.Value('NA'),
+            approvers=['users/222']),
+        update_mask=field_mask_pb2.FieldMask(paths=['approvers', 'status']))
+    # Change status, and also ensure we don't reuse the same mask across deltas
+    # Approvers should be ignored for delta_2 because it is not included in the
+    # mask.
+    delta_2 = issues_pb2.ApprovalDelta(
+        approval_value=issue_objects_pb2.ApprovalValue(
+            name=av_name,
+            status=issue_objects_pb2.ApprovalValue.ApprovalStatus.Value(
+                'NOT_SET'),
+            approvers=['users/222']),
+        update_mask=field_mask_pb2.FieldMask(paths=['status']))
+    actual = self.converter.IngestApprovalDeltas(
+        [delta_1, delta_2], self.user_1.user_id)
+    self.assertEqual(len(actual), 2)
+    actual_iid_1, actual_approval_id_1, actual_delta_1 = actual[0]
+    actual_iid_2, actual_approval_id_2, actual_delta_2 = actual[1]
+    self.assertEqual(actual_iid_1, self.issue_1.issue_id)
+    self.assertEqual(actual_iid_2, self.issue_1.issue_id)
+    self.assertEqual(actual_approval_id_1, self.approval_def_1_id)
+    self.assertEqual(actual_approval_id_2, self.approval_def_1_id)
+
+    self.assertEqual(actual_delta_1.status, tracker_pb2.ApprovalStatus.NA)
+    self.assertEqual(actual_delta_2.status, tracker_pb2.ApprovalStatus.NOT_SET)
+    self.assertEqual(actual_delta_1.setter_id, self.user_1.user_id)
+    self.assertEqual(actual_delta_2.setter_id, self.user_1.user_id)
+    self.assertEqual(actual_delta_1.approver_ids_add, [222])
+    self.assertEqual(actual_delta_2.approver_ids_add, [])
+    # We don't patch time.time, so these would be different if the set_on wasn't
+    # passed in.
+    # Note: More ideal/correct unit test would create a mock that forces
+    # time.time to return an incremented value on its subsequent calls.
+    self.assertEqual(actual_delta_1.set_on, actual_delta_2.set_on)
+
+  def testIngestApprovalDeltas_DifferentProjects(self):
+    # Create an ApprovalDef for project2
+    approval_def_project2_name = 'project2_approval'
+    approval_def_project2_id = self._CreateFieldDef(
+        self.project_2.project_id,
+        approval_def_project2_name,
+        'APPROVAL_TYPE',
+        docstring='project2_ad_docstring',
+        admin_ids=[self.user_1.user_id])
+    self.services.config.UpdateConfig(
+        self.cnxn,
+        self.project_2,
+        approval_defs=[
+            (approval_def_project2_id, [self.user_1.user_id], 'survey')
+        ])
+
+    # Define a field belonging to project_2's ApprovalDef.
+    project2_field_id = self._CreateFieldDef(
+        self.project_2.project_id,
+        'approval2field',
+        'STR_TYPE',
+        approval_id=approval_def_project2_id)
+    project2_fv = issue_objects_pb2.FieldValue(
+        field='projects/proj/fieldDefs/%d' % project2_field_id, value=u'p2')
+
+    # field_def_6 belongs to approval_def_1.
+    project1_fv = issue_objects_pb2.FieldValue(
+        field='projects/proj/fieldDefs/%d' % self.field_def_6,
+        value=u'touch-nose',
+    )
+
+    # Both ApprovalValues are provided both FieldValues, and we expect them
+    # to only include the FieldValues appropriate to their respective approvals.
+    project2_av_name = (
+        'projects/%s/issues/2/approvalValues/%d' %
+        (self.project_2.project_name, approval_def_project2_id))
+    project2_delta = issues_pb2.ApprovalDelta(
+        approval_value=issue_objects_pb2.ApprovalValue(
+            name=project2_av_name, field_values=[project1_fv, project2_fv]),
+        update_mask=field_mask_pb2.FieldMask(paths=['field_values']))
+
+    project1_av_name = (
+        'projects/proj/issues/1/approvalValues/%d' % self.approval_def_1_id)
+    project1_delta = issues_pb2.ApprovalDelta(
+        approval_value=issue_objects_pb2.ApprovalValue(
+            name=project1_av_name, field_values=[project1_fv, project2_fv]),
+        update_mask=field_mask_pb2.FieldMask(paths=['field_values']))
+
+    with self.assertRaisesRegexp(
+        exceptions.InputException,
+        'Field projects/proj/fieldDefs/%d is not in this project' %
+        self.field_def_6):
+      self.converter.IngestApprovalDeltas(
+          [project2_delta, project1_delta], self.user_1.user_id)
+
+  def testIngestIssue(self):
+    ingest = issue_objects_pb2.Issue(
+        summary='sum',
+        status=issue_objects_pb2.Issue.StatusValue(
+            status='new', derivation=RULE_DERIVATION),
+        owner=issue_objects_pb2.Issue.UserValue(
+            derivation=EXPLICIT_DERIVATION, user='users/111'),
+        cc_users=[
+            issue_objects_pb2.Issue.UserValue(
+                derivation=EXPLICIT_DERIVATION, user='users/new@user.com'),
+            issue_objects_pb2.Issue.UserValue(
+                derivation=RULE_DERIVATION, user='users/333')
+        ],
+        components=[
+            issue_objects_pb2.Issue.ComponentValue(
+                component='projects/proj/componentDefs/%d' %
+                self.component_def_1_id),
+            issue_objects_pb2.Issue.ComponentValue(
+                component='projects/proj/componentDefs/%d' %
+                self.component_def_2_id),
+        ],
+        labels=[
+            issue_objects_pb2.Issue.LabelValue(
+                derivation=EXPLICIT_DERIVATION, label='a'),
+            issue_objects_pb2.Issue.LabelValue(
+                derivation=EXPLICIT_DERIVATION, label='key-explicit'),
+            issue_objects_pb2.Issue.LabelValue(
+                derivation=RULE_DERIVATION, label='derived1'),
+            issue_objects_pb2.Issue.LabelValue(
+                derivation=RULE_DERIVATION, label='key-derived')
+        ],
+        field_values=[
+            issue_objects_pb2.FieldValue(
+                derivation=EXPLICIT_DERIVATION,
+                field='projects/proj/fieldDefs/%d' % self.field_def_1,
+                value='multivalue1',
+            ),
+            issue_objects_pb2.FieldValue(
+                derivation=RULE_DERIVATION,
+                field='projects/proj/fieldDefs/%d' % self.field_def_1,
+                value='multivalue2',
+            ),
+            issue_objects_pb2.FieldValue(
+                derivation=EXPLICIT_DERIVATION,
+                field='projects/proj/fieldDefs/%d' % self.field_def_3,
+                value='1',
+            ),
+            issue_objects_pb2.FieldValue(
+                derivation=RULE_DERIVATION,
+                field='projects/proj/fieldDefs/%d' % self.field_def_4,
+                value='mac',
+            ),
+            issue_objects_pb2.FieldValue(
+                field='projects/proj/fieldDefs/%d' % self.field_def_2,
+                value='38',  # Max value not checked.
+            ),
+            issue_objects_pb2.FieldValue(  # Multivalue not checked.
+                field='projects/proj/fieldDefs/%d' % self.field_def_2,
+                value='0'  # Confirm we ingest 0 rather than None.
+            ),
+            issue_objects_pb2.FieldValue(
+                field='projects/proj/fieldDefs/%d' % self.field_def_8,
+                value='users/111',
+            ),
+            issue_objects_pb2.FieldValue(
+                field='projects/proj/fieldDefs/%d' % self.field_def_8,
+                value='users/404',  # User lookup not attempted.
+            ),
+            issue_objects_pb2.FieldValue(
+                field='projects/proj/fieldDefs/%d' % self.field_def_9,
+                value='2020-01-01',
+            ),
+            issue_objects_pb2.FieldValue(
+                field='projects/proj/fieldDefs/%d' % self.field_def_9,
+                value='2100-01-01',
+            ),
+            issue_objects_pb2.FieldValue(
+                field='projects/proj/fieldDefs/%d' % self.field_def_9,
+                value='1000-01-01',
+            ),
+            issue_objects_pb2.FieldValue(
+                field='projects/proj/fieldDefs/%d' % self.field_def_10,
+                value='garbage',
+            ),
+        ],
+        merged_into_issue_ref=issue_objects_pb2.IssueRef(ext_identifier='b/1'),
+        blocked_on_issue_refs=[
+            # Reversing natural ordering to ensure order is respected.
+            issue_objects_pb2.IssueRef(issue='projects/goose/issues/4'),
+            issue_objects_pb2.IssueRef(issue='projects/proj/issues/3'),
+            issue_objects_pb2.IssueRef(ext_identifier='b/555'),
+            issue_objects_pb2.IssueRef(ext_identifier='b/2')
+        ],
+        blocking_issue_refs=[
+            issue_objects_pb2.IssueRef(issue='projects/goose/issues/5'),
+            issue_objects_pb2.IssueRef(ext_identifier='b/3')
+        ],
+        # All the following fields should be ignored.
+        name='projects/proj/issues/1',
+        state=issue_objects_pb2.IssueContentState.Value('SPAM'),
+        reporter='users/111',
+        create_time=timestamp_pb2.Timestamp(seconds=self.PAST_TIME),
+        modify_time=timestamp_pb2.Timestamp(seconds=self.PAST_TIME),
+        component_modify_time=timestamp_pb2.Timestamp(seconds=self.PAST_TIME),
+        status_modify_time=timestamp_pb2.Timestamp(seconds=self.PAST_TIME),
+        owner_modify_time=timestamp_pb2.Timestamp(seconds=self.PAST_TIME),
+        star_count=1,
+        attachment_count=5,
+        phases=[self.phase_1.name])
+
+    blocked_on_1 = fake.MakeTestIssue(
+        self.project_1.project_id,
+        3,
+        'sum3',
+        'New',
+        self.user_1.user_id,
+        issue_id=301,
+        project_name=self.project_1.project_name,
+    )
+    blocked_on_2 = fake.MakeTestIssue(
+        self.project_2.project_id,
+        4,
+        'sum4',
+        'New',
+        self.user_1.user_id,
+        issue_id=401,
+        project_name=self.project_2.project_name,
+    )
+    blocking = fake.MakeTestIssue(
+        self.project_2.project_id,
+        5,
+        'sum5',
+        'New',
+        self.user_1.user_id,
+        issue_id=501,
+        project_name=self.project_2.project_name,
+    )
+    self.services.issue.TestAddIssue(blocked_on_1)
+    self.services.issue.TestAddIssue(blocked_on_2)
+    self.services.issue.TestAddIssue(blocking)
+
+    actual = self.converter.IngestIssue(ingest, self.project_1.project_id)
+
+    expected_cc1_id = self.services.user.LookupUserID(
+        self.cnxn, 'new@user.com', autocreate=False)
+    expected_field_values = [
+        tracker_pb2.FieldValue(
+            field_id=self.field_def_1,
+            str_value=u'multivalue1',
+            derived=False,
+        ),
+        tracker_pb2.FieldValue(
+            field_id=self.field_def_1,
+            str_value=u'multivalue2',
+            derived=False,
+        ),
+        tracker_pb2.FieldValue(
+            field_id=self.field_def_2, int_value=38, derived=False),
+        tracker_pb2.FieldValue(
+            field_id=self.field_def_2, int_value=0, derived=False),
+        tracker_pb2.FieldValue(
+            field_id=self.field_def_8, user_id=111, derived=False),
+        tracker_pb2.FieldValue(
+            field_id=self.field_def_8, user_id=404, derived=False),
+        tracker_pb2.FieldValue(
+            field_id=self.field_def_9, date_value=1577836800, derived=False),
+        tracker_pb2.FieldValue(
+            field_id=self.field_def_9, date_value=4102444800, derived=False),
+        tracker_pb2.FieldValue(
+            field_id=self.field_def_9, date_value=-30610224000, derived=False),
+        tracker_pb2.FieldValue(
+            field_id=self.field_def_10,
+            url_value=u'http://garbage',
+            derived=False),
+    ]
+    expected = tracker_pb2.Issue(
+        project_id=self.project_1.project_id,
+        summary=u'sum',
+        status=u'new',
+        owner_id=111,
+        cc_ids=[expected_cc1_id, 333],
+        component_ids=[self.component_def_1_id, self.component_def_2_id],
+        merged_into_external=u'b/1',
+        labels=[
+            u'a', u'key-explicit', u'derived1', u'key-derived', u'days-1',
+            u'OS-mac'
+        ],
+        field_values=expected_field_values,
+        blocked_on_iids=[blocked_on_2.issue_id, blocked_on_1.issue_id],
+        blocking_iids=[blocking.issue_id],
+        dangling_blocked_on_refs=[
+            tracker_pb2.DanglingIssueRef(ext_issue_identifier=u'b/555'),
+            tracker_pb2.DanglingIssueRef(ext_issue_identifier=u'b/2')
+        ],
+        dangling_blocking_refs=[
+            tracker_pb2.DanglingIssueRef(ext_issue_identifier=u'b/3')
+        ],
+    )
+    self.AssertProtosEqual(actual, expected)
+
+  def AssertProtosEqual(self, actual, expected):
+    """Asserts equal, printing a diff if not."""
+    # TODO(jessan): If others find this useful, move to a shared testing lib.
+    try:
+      self.assertEqual(actual, expected)
+    except AssertionError as e:
+      # Append a diff to the normal error message.
+      expected_str = str(expected).splitlines(1)
+      actual_str = str(actual).splitlines(1)
+      diff = difflib.unified_diff(actual_str, expected_str)
+      err_msg = '%s\nProto actual vs expected diff:\n %s' % (e, ''.join(diff))
+      raise AssertionError(err_msg)
+
+  def testIngestIssue_Minimal(self):
+    """Test IngestIssue with as few fields set as possible."""
+    minimal = issue_objects_pb2.Issue(
+        status=issue_objects_pb2.Issue.StatusValue(status='new')
+    )
+    expected = tracker_pb2.Issue(
+        project_id=self.project_1.project_id,
+        summary='', # Summary gets set to empty str on conversion.
+        status='new',
+        owner_id=0
+    )
+    actual = self.converter.IngestIssue(minimal, self.project_1.project_id)
+    self.assertEqual(actual, expected)
+
+  def testIngestIssue_NoSuchProject(self):
+    self.services.config.strict = True
+    ingest = issue_objects_pb2.Issue(
+        status=issue_objects_pb2.Issue.StatusValue(status='new'))
+    with self.assertRaises(exceptions.NoSuchProjectException):
+      self.converter.IngestIssue(ingest, -1)
+
+  def testIngestIssue_Errors(self):
+    invalid_issue_ref = issue_objects_pb2.IssueRef(
+        ext_identifier='b/1',
+        issue='projects/proj/issues/1')
+    ingest = issue_objects_pb2.Issue(
+        summary='sum',
+        owner=issue_objects_pb2.Issue.UserValue(
+            derivation=EXPLICIT_DERIVATION, user='users/nonexisting@user.com'),
+        cc_users=[
+            issue_objects_pb2.Issue.UserValue(
+                derivation=EXPLICIT_DERIVATION, user='invalidFormat1'),
+            issue_objects_pb2.Issue.UserValue(
+                derivation=RULE_DERIVATION, user='invalidFormat2')
+        ],
+        components=[
+            issue_objects_pb2.Issue.ComponentValue(
+                component='projects/proj/componentDefs/404')
+        ],
+        field_values=[
+            issue_objects_pb2.FieldValue(),
+            issue_objects_pb2.FieldValue(field='garbage'),
+            issue_objects_pb2.FieldValue(
+                field='projects/proj/fieldDefs/%d' % self.field_def_8,
+                value='users/nonexisting@user.com',
+            ),
+        ],
+        merged_into_issue_ref=invalid_issue_ref,
+        blocked_on_issue_refs=[
+            issue_objects_pb2.IssueRef(),
+            issue_objects_pb2.IssueRef(issue='projects/404/issues/1')
+        ],
+        blocking_issue_refs=[
+            issue_objects_pb2.IssueRef(issue='projects/proj/issues/404')
+        ],
+    )
+    error_messages = [
+        r'.+not found when ingesting owner',
+        r'.+cc_users: Invalid resource name: invalidFormat1.',
+        r'Status is required when creating an issue',
+        r'.+components: Component not found: 404.',
+        r'.+: Invalid resource name: .', r'.+: Invalid resource name: garbage.',
+        r'.+not found when ingesting user field:.+',
+        r'.+issue:.+[\n\r]+ext_identifier:.+[\n\r]+: IssueRefs MUST NOT have.+',
+        r'.+: IssueRefs MUST have one of.+',
+        r'.+issue:.+[\n\r]+: Project 404 not found.',
+        r'.+issue:.+[\n\r]+: Issue.+404.+not found'
+    ]
+    error_messages_re = '\n'.join(error_messages)
+    with self.assertRaisesRegexp(exceptions.InputException, error_messages_re):
+      self.converter.IngestIssue(ingest, self.project_1.project_id)
+
+  def testIngestIssuesListColumns(self):
+    columns = [
+        issue_objects_pb2.IssuesListColumn(column='chicken'),
+        issue_objects_pb2.IssuesListColumn(column='boiled-egg')
+    ]
+    self.assertEqual(
+        self.converter.IngestIssuesListColumns(columns), 'chicken boiled-egg')
+
+  def testIngestIssuesListColumns_Empty(self):
+    self.assertEqual(self.converter.IngestIssuesListColumns([]), '')
+
+  def test_ComputeIssuesListColumns(self):
+    """Can convert string to sequence of IssuesListColumns"""
+    expected_columns = [
+        issue_objects_pb2.IssuesListColumn(column='chicken'),
+        issue_objects_pb2.IssuesListColumn(column='boiled-egg')
+    ]
+    self.assertEqual(
+        expected_columns,
+        self.converter._ComputeIssuesListColumns('chicken boiled-egg'))
+
+  def test_ComputeIssuesListColumns_Empty(self):
+    """Can handle empty strings"""
+    self.assertEqual([], self.converter._ComputeIssuesListColumns(''))
+
+  def test_Conversion_IssuesListColumns(self):
+    """_Ingest and _Compute converts to and from each other"""
+    expected_columns = 'foo bar fizz buzz'
+    converted_columns = self.converter._ComputeIssuesListColumns(
+        expected_columns)
+    self.assertEqual(
+        expected_columns,
+        self.converter.IngestIssuesListColumns(converted_columns))
+
+    expected_columns = [
+        issue_objects_pb2.IssuesListColumn(column='foo'),
+        issue_objects_pb2.IssuesListColumn(column='bar'),
+        issue_objects_pb2.IssuesListColumn(column='fizz'),
+        issue_objects_pb2.IssuesListColumn(column='buzz')
+    ]
+    converted_columns = self.converter.IngestIssuesListColumns(expected_columns)
+    self.assertEqual(
+        expected_columns,
+        self.converter._ComputeIssuesListColumns(converted_columns))
+
+  def testIngestNotifyType(self):
+    notify = issues_pb2.NotifyType.Value('NOTIFY_TYPE_UNSPECIFIED')
+    actual = self.converter.IngestNotifyType(notify)
+    self.assertEqual(actual, True)
+    notify = issues_pb2.NotifyType.Value('EMAIL')
+    actual = self.converter.IngestNotifyType(notify)
+    self.assertEqual(actual, True)
+    notify = issues_pb2.NotifyType.Value('NO_NOTIFICATION')
+    actual = self.converter.IngestNotifyType(notify)
+    self.assertEqual(actual, False)
+
+  def test_GetNonApprovalFieldValues(self):
+    """It filters out field values that belong to approvals"""
+    expected_str = 'some_string_field_value'
+    fv_expected = fake.MakeFieldValue(
+        field_id=self.field_def_1, str_value=expected_str, derived=False)
+    actual = self.converter._GetNonApprovalFieldValues(
+        [fv_expected, self.fv_6], self.project_1.project_id)
+    self.assertEqual(len(actual), 1)
+    self.assertEqual(actual[0], fv_expected)
+
+  def test_GetNonApprovalFieldValues_Empty(self):
+    actual = self.converter._GetNonApprovalFieldValues(
+        [], self.project_1.project_id)
+    self.assertEqual(actual, [])
+
+  def testConvertFieldValues(self):
+    """It ignores field values referencing a non-existent field"""
+    expected_str = 'some_string_field_value'
+    fv = fake.MakeFieldValue(
+        field_id=self.field_def_1, str_value=expected_str, derived=False)
+    expected_name = rnc.ConvertFieldDefNames(
+        self.cnxn, [self.field_def_1], self.project_1.project_id,
+        self.services)[self.field_def_1]
+    expected_value = issue_objects_pb2.FieldValue(
+        field=expected_name,
+        value=expected_str,
+        derivation=EXPLICIT_DERIVATION,
+        phase=None)
+    output = self.converter.ConvertFieldValues(
+        [fv], self.project_1.project_id, [])
+    self.assertEqual([expected_value], output)
+
+  def testConvertFieldValues_Empty(self):
+    output = self.converter.ConvertFieldValues(
+        [], self.project_1.project_id, [])
+    self.assertEqual([], output)
+
+  def testConvertFieldValues_PreservesOrder(self):
+    """It ignores field values referencing a non-existent field"""
+    expected_str = 'some_string_field_value'
+    fv_1 = fake.MakeFieldValue(
+        field_id=self.field_def_1, str_value=expected_str, derived=False)
+    name_1 = rnc.ConvertFieldDefNames(
+        self.cnxn, [self.field_def_1], self.project_1.project_id,
+        self.services)[self.field_def_1]
+    expected_1 = issue_objects_pb2.FieldValue(
+        field=name_1,
+        value=expected_str,
+        derivation=EXPLICIT_DERIVATION,
+        phase=None)
+
+    expected_int = 111111
+    fv_2 = fake.MakeFieldValue(
+        field_id=self.field_def_2, int_value=expected_int, derived=True)
+    name_2 = rnc.ConvertFieldDefNames(
+        self.cnxn, [self.field_def_2], self.project_1.project_id,
+        self.services).get(self.field_def_2)
+    expected_2 = issue_objects_pb2.FieldValue(
+        field=name_2,
+        value=str(expected_int),
+        derivation=RULE_DERIVATION,
+        phase=None)
+    output = self.converter.ConvertFieldValues(
+        [fv_1, fv_2], self.project_1.project_id, [])
+    self.assertEqual([expected_1, expected_2], output)
+
+  def testConvertFieldValues_IgnoresNullFieldDefs(self):
+    """It ignores field values referencing a non-existent field"""
+    expected_str = 'some_string_field_value'
+    fv_1 = fake.MakeFieldValue(
+        field_id=self.field_def_1, str_value=expected_str, derived=False)
+    name_1 = rnc.ConvertFieldDefNames(
+        self.cnxn, [self.field_def_1], self.project_1.project_id,
+        self.services)[self.field_def_1]
+    expected_1 = issue_objects_pb2.FieldValue(
+        field=name_1,
+        value=expected_str,
+        derivation=EXPLICIT_DERIVATION,
+        phase=None)
+
+    fv_2 = fake.MakeFieldValue(
+        field_id=self.dne_field_def_id, int_value=111111, derived=True)
+    output = self.converter.ConvertFieldValues(
+        [fv_1, fv_2], self.project_1.project_id, [])
+    self.assertEqual([expected_1], output)
+
+  def test_ComputeFieldValueString_None(self):
+    with self.assertRaises(exceptions.InputException):
+      self.converter._ComputeFieldValueString(None)
+
+  def test_ComputeFieldValueString_INT_TYPE(self):
+    expected = 123158
+    fv = fake.MakeFieldValue(field_id=self.field_def_2, int_value=expected)
+    output = self.converter._ComputeFieldValueString(fv)
+    self.assertEqual(str(expected), output)
+
+  def test_ComputeFieldValueString_STR_TYPE(self):
+    expected = 'some_string_field_value'
+    fv = fake.MakeFieldValue(field_id=self.field_def_1, str_value=expected)
+    output = self.converter._ComputeFieldValueString(fv)
+    self.assertEqual(expected, output)
+
+  def test_ComputeFieldValueString_USER_TYPE(self):
+    user_id = self.user_1.user_id
+    expected = rnc.ConvertUserName(user_id)
+    fv = fake.MakeFieldValue(field_id=self.dne_field_def_id, user_id=user_id)
+    output = self.converter._ComputeFieldValueString(fv)
+    self.assertEqual(expected, output)
+
+  def test_ComputeFieldValueString_DATE_TYPE(self):
+    expected = 1234567890
+    fv = fake.MakeFieldValue(
+        field_id=self.dne_field_def_id, date_value=expected)
+    output = self.converter._ComputeFieldValueString(fv)
+    self.assertEqual(str(expected), output)
+
+  def test_ComputeFieldValueString_URL_TYPE(self):
+    expected = 'some URL'
+    fv = fake.MakeFieldValue(field_id=self.dne_field_def_id, url_value=expected)
+    output = self.converter._ComputeFieldValueString(fv)
+    self.assertEqual(expected, output)
+
+  def test_ComputeFieldValueDerivation_RULE(self):
+    expected = RULE_DERIVATION
+    fv = fake.MakeFieldValue(
+        field_id=self.field_def_1, str_value='something', derived=True)
+    output = self.converter._ComputeFieldValueDerivation(fv)
+    self.assertEqual(expected, output)
+
+  def test_ComputeFieldValueDerivation_EXPLICIT(self):
+    expected = EXPLICIT_DERIVATION
+    fv = fake.MakeFieldValue(
+        field_id=self.field_def_1, str_value='something', derived=False)
+    output = self.converter._ComputeFieldValueDerivation(fv)
+    self.assertEqual(expected, output)
+
+  def testConvertApprovalValues_Issue(self):
+    """We can convert issue approval_values."""
+    name = rnc.ConvertApprovalValueNames(
+        self.cnxn, self.issue_1.issue_id, self.services)[self.av_1.approval_id]
+    approval_def_name = rnc.ConvertApprovalDefNames(
+        self.cnxn, [self.approval_def_1_id], self.project_1.project_id,
+        self.services)[self.approval_def_1_id]
+    approvers = [rnc.ConvertUserName(self.user_2.user_id)]
+    status = issue_objects_pb2.ApprovalValue.ApprovalStatus.Value(
+        'NOT_SET')
+    setter = rnc.ConvertUserName(self.user_1.user_id)
+    api_fvs = self.converter.ConvertFieldValues(
+        [self.fv_6], self.project_1.project_id, [self.phase_1])
+    # Check we can handle converting a None `set_on`.
+    self.av_1.set_on = None
+
+    output = self.converter.ConvertApprovalValues(
+        [self.av_1], [self.fv_1, self.fv_6], [self.phase_1],
+        issue_id=self.issue_1.issue_id)
+    expected = issue_objects_pb2.ApprovalValue(
+        name=name,
+        approval_def=approval_def_name,
+        approvers=approvers,
+        status=status,
+        setter=setter,
+        phase=self.phase_1.name,
+        field_values=api_fvs)
+    self.assertEqual([expected], output)
+
+  def testConvertApprovalValues_Templates(self):
+    """We can convert template approval_values."""
+    approval_def_name = rnc.ConvertApprovalDefNames(
+        self.cnxn, [self.approval_def_1_id], self.project_1.project_id,
+        self.services)[self.approval_def_1_id]
+    approvers = [rnc.ConvertUserName(self.user_2.user_id)]
+    status = issue_objects_pb2.ApprovalValue.ApprovalStatus.Value(
+        'NOT_SET')
+    set_time = timestamp_pb2.Timestamp()
+    set_time.FromSeconds(self.PAST_TIME)
+    setter = rnc.ConvertUserName(self.user_1.user_id)
+    api_fvs = self.converter.ConvertFieldValues(
+        [self.fv_6], self.project_1.project_id, [self.phase_1])
+
+    output = self.converter.ConvertApprovalValues(
+        [self.av_1], [self.fv_1, self.fv_6], [self.phase_1],
+        project_id=self.project_1.project_id)
+    expected = issue_objects_pb2.ApprovalValue(
+        approval_def=approval_def_name,
+        approvers=approvers,
+        status=status,
+        set_time=set_time,
+        setter=setter,
+        phase=self.phase_1.name,
+        field_values=api_fvs)
+    self.assertEqual([expected], output)
+
+  def testConvertApprovalValues_NoPhase(self):
+    approval_def_name = rnc.ConvertApprovalDefNames(
+        self.cnxn, [self.approval_def_1_id], self.project_1.project_id,
+        self.services)[self.approval_def_1_id]
+    approvers = [rnc.ConvertUserName(self.user_2.user_id)]
+    status = issue_objects_pb2.ApprovalValue.ApprovalStatus.Value(
+        'NOT_SET')
+    set_time = timestamp_pb2.Timestamp()
+    set_time.FromSeconds(self.PAST_TIME)
+    setter = rnc.ConvertUserName(self.user_1.user_id)
+    expected = issue_objects_pb2.ApprovalValue(
+        approval_def=approval_def_name,
+        approvers=approvers,
+        status=status,
+        set_time=set_time,
+        setter=setter)
+
+    output = self.converter.ConvertApprovalValues(
+        [self.av_1], [], [], project_id=self.project_1.project_id)
+    self.assertEqual([expected], output)
+
+  def testConvertApprovalValues_Empty(self):
+    output = self.converter.ConvertApprovalValues(
+        [], [], [], project_id=self.project_1.project_id)
+    self.assertEqual([], output)
+
+  def testConvertApprovalValues_IgnoresNullFieldDefs(self):
+    """It ignores approval values referencing a non-existent field"""
+    av = fake.MakeApprovalValue(self.dne_field_def_id)
+
+    output = self.converter.ConvertApprovalValues(
+        [av], [], [], issue_id=self.issue_1.issue_id)
+    self.assertEqual([], output)
+
+  def test_ComputeApprovalValueStatus_NOT_SET(self):
+    self.assertEqual(
+        self.converter._ComputeApprovalValueStatus(
+            tracker_pb2.ApprovalStatus.NOT_SET),
+        issue_objects_pb2.ApprovalValue.ApprovalStatus.Value(
+            'NOT_SET'))
+
+  def test_ComputeApprovalValueStatus_NEEDS_REVIEW(self):
+    self.assertEqual(
+        self.converter._ComputeApprovalValueStatus(
+            tracker_pb2.ApprovalStatus.NEEDS_REVIEW),
+        issue_objects_pb2.ApprovalValue.ApprovalStatus.Value('NEEDS_REVIEW'))
+
+  def test_ComputeApprovalValueStatus_NA(self):
+    self.assertEqual(
+        self.converter._ComputeApprovalValueStatus(
+            tracker_pb2.ApprovalStatus.NA),
+        issue_objects_pb2.ApprovalValue.ApprovalStatus.Value('NA'))
+
+  def test_ComputeApprovalValueStatus_REVIEW_REQUESTED(self):
+    self.assertEqual(
+        self.converter._ComputeApprovalValueStatus(
+            tracker_pb2.ApprovalStatus.REVIEW_REQUESTED),
+        issue_objects_pb2.ApprovalValue.ApprovalStatus.Value(
+            'REVIEW_REQUESTED'))
+
+  def test_ComputeApprovalValueStatus_REVIEW_STARTED(self):
+    self.assertEqual(
+        self.converter._ComputeApprovalValueStatus(
+            tracker_pb2.ApprovalStatus.REVIEW_STARTED),
+        issue_objects_pb2.ApprovalValue.ApprovalStatus.Value('REVIEW_STARTED'))
+
+  def test_ComputeApprovalValueStatus_NEED_INFO(self):
+    self.assertEqual(
+        self.converter._ComputeApprovalValueStatus(
+            tracker_pb2.ApprovalStatus.NEED_INFO),
+        issue_objects_pb2.ApprovalValue.ApprovalStatus.Value('NEED_INFO'))
+
+  def test_ComputeApprovalValueStatus_APPROVED(self):
+    self.assertEqual(
+        self.converter._ComputeApprovalValueStatus(
+            tracker_pb2.ApprovalStatus.APPROVED),
+        issue_objects_pb2.ApprovalValue.ApprovalStatus.Value('APPROVED'))
+
+  def test_ComputeApprovalValueStatus_NOT_APPROVED(self):
+    self.assertEqual(
+        self.converter._ComputeApprovalValueStatus(
+            tracker_pb2.ApprovalStatus.NOT_APPROVED),
+        issue_objects_pb2.ApprovalValue.ApprovalStatus.Value('NOT_APPROVED'))
+
+  def test_ComputeTemplatePrivacy_PUBLIC(self):
+    self.assertEqual(
+        self.converter._ComputeTemplatePrivacy(self.template_1),
+        project_objects_pb2.IssueTemplate.TemplatePrivacy.Value('PUBLIC'))
+
+  def test_ComputeTemplatePrivacy_MEMBERS_ONLY(self):
+    self.assertEqual(
+        self.converter._ComputeTemplatePrivacy(self.template_2),
+        project_objects_pb2.IssueTemplate.TemplatePrivacy.Value('MEMBERS_ONLY'))
+
+  def test_ComputeTemplateDefaultOwner_UNSPECIFIED(self):
+    self.assertEqual(
+        self.converter._ComputeTemplateDefaultOwner(self.template_1),
+        project_objects_pb2.IssueTemplate.DefaultOwner.Value(
+            'DEFAULT_OWNER_UNSPECIFIED'))
+
+  def test_ComputeTemplateDefaultOwner_REPORTER(self):
+    self.assertEqual(
+        self.converter._ComputeTemplateDefaultOwner(self.template_2),
+        project_objects_pb2.IssueTemplate.DefaultOwner.Value(
+            'PROJECT_MEMBER_REPORTER'))
+
+  def test_ComputePhases(self):
+    """It sorts by rank"""
+    phase1 = fake.MakePhase(123111, name='phase1name', rank=3)
+    phase2 = fake.MakePhase(123112, name='phase2name', rank=2)
+    phase3 = fake.MakePhase(123113, name='phase3name', rank=1)
+    expected = ['phase3name', 'phase2name', 'phase1name']
+    self.assertEqual(
+        self.converter._ComputePhases([phase1, phase2, phase3]), expected)
+
+  def test_ComputePhases_EMPTY(self):
+    self.assertEqual(self.converter._ComputePhases([]), [])
+
+  def test_FillIssueFromTemplate(self):
+    result = self.converter._FillIssueFromTemplate(
+        self.template_1, self.project_1.project_id)
+    self.assertFalse(result.name)
+    self.assertEqual(result.summary, self.template_1.summary)
+    self.assertEqual(
+        result.state, issue_objects_pb2.IssueContentState.Value('ACTIVE'))
+    self.assertEqual(result.status.status, 'New')
+    self.assertFalse(result.reporter)
+    self.assertEqual(result.owner.user, 'users/{}'.format(self.user_1.user_id))
+    self.assertEqual(len(result.cc_users), 0)
+    self.assertFalse(result.cc_users)
+    self.assertEqual(len(result.labels), 1)
+    self.assertEqual(result.labels[0].label, self.template_1.labels[0])
+    self.assertEqual(result.labels[0].derivation, EXPLICIT_DERIVATION)
+    self.assertEqual(len(result.components), 1)
+    self.assertEqual(
+        result.components[0].component, 'projects/{}/componentDefs/{}'.format(
+            self.project_1.project_name, self.template_1.component_ids[0]))
+    self.assertEqual(result.components[0].derivation, EXPLICIT_DERIVATION)
+    self.assertEqual(len(result.field_values), 2)
+    self.assertEqual(
+        result.field_values[0].field, 'projects/{}/fieldDefs/{}'.format(
+            self.project_1.project_name, self.field_def_1))
+    self.assertEqual(result.field_values[0].value, self.fv_1_value)
+    self.assertEqual(result.field_values[0].derivation, EXPLICIT_DERIVATION)
+    expected_name = rnc.ConvertFieldDefNames(
+        self.cnxn, [self.field_def_3], self.project_1.project_id,
+        self.services).get(self.field_def_3)
+    self.assertEqual(
+        result.field_values[1],
+        issue_objects_pb2.FieldValue(
+            field=expected_name,
+            value=self.template_1_label1_value,
+            derivation=EXPLICIT_DERIVATION))
+    self.assertFalse(result.blocked_on_issue_refs)
+    self.assertFalse(result.blocking_issue_refs)
+    self.assertFalse(result.attachment_count)
+    self.assertFalse(result.star_count)
+    self.assertEqual(len(result.phases), 1)
+    self.assertEqual(result.phases[0], self.phase_1.name)
+
+  def test_FillIssueFromTemplate_NoPhase(self):
+    result = self.converter._FillIssueFromTemplate(
+        self.template_3, self.project_1.project_id)
+    self.assertEqual(len(result.field_values), 1)
+    self.assertEqual(
+        result.field_values[0].field, 'projects/{}/fieldDefs/{}'.format(
+            self.project_1.project_name, self.field_def_1))
+    self.assertEqual(result.field_values[0].value, self.fv_1_value)
+    self.assertEqual(result.field_values[0].derivation, EXPLICIT_DERIVATION)
+    self.assertEqual(len(result.phases), 0)
+
+  def test_FillIssueFromTemplate_FilterApprovalFV(self):
+    template = self.services.template.TestAddIssueTemplateDef(
+        11114,
+        self.project_1.project_id,
+        'template3',
+        field_values=[self.fv_1, self.fv_6],
+        approval_values=[self.av_2],
+    )
+    result = self.converter._FillIssueFromTemplate(
+        template, self.project_1.project_id)
+    self.assertEqual(len(result.field_values), 1)
+    self.assertEqual(
+        result.field_values[0].field, 'projects/{}/fieldDefs/{}'.format(
+            self.project_1.project_name, self.field_def_1))
+    self.assertEqual(result.field_values[0].value, self.fv_1_value)
+    self.assertEqual(result.field_values[0].derivation, EXPLICIT_DERIVATION)
+
+  def testConvertIssueTemplates(self):
+    result = self.converter.ConvertIssueTemplates(
+        self.project_1.project_id, [self.template_1])
+    self.assertEqual(len(result), 1)
+    actual = result[0]
+    self.assertEqual(
+        actual.name, 'projects/{}/templates/{}'.format(
+            self.project_1.project_name, self.template_1.template_id))
+    self.assertEqual(actual.display_name, self.template_1.name)
+    self.assertEqual(actual.summary_must_be_edited, False)
+    self.assertEqual(
+        actual.template_privacy,
+        project_objects_pb2.IssueTemplate.TemplatePrivacy.Value('PUBLIC'))
+    self.assertEqual(
+        actual.default_owner,
+        project_objects_pb2.IssueTemplate.DefaultOwner.Value(
+            'DEFAULT_OWNER_UNSPECIFIED'))
+    self.assertEqual(actual.component_required, False)
+    self.assertEqual(actual.admins, ['users/{}'.format(self.user_2.user_id)])
+    self.assertEqual(
+        actual.issue,
+        self.converter._FillIssueFromTemplate(
+            self.template_1, self.project_1.project_id))
+    self.assertListEqual(
+        [av for av in actual.approval_values],
+        self.converter.ConvertApprovalValues(
+            self.template_1.approval_values, self.template_1.field_values,
+            self.template_1.phases, project_id=self.project_1.project_id))
+
+  def testConvertIssueTemplates_IgnoresNonExistentTemplate(self):
+    result = self.converter.ConvertIssueTemplates(
+        self.project_1.project_id, [self.dne_template])
+    self.assertEqual(len(result), 0)
+
+  def testConvertLabels_OmitsFieldDefs(self):
+    """It omits field def labels"""
+    input_labels = ['pri-1', '{}-2'.format(self.field_def_3_name)]
+    result = self.converter.ConvertLabels(
+        input_labels, [], self.project_1.project_id)
+    self.assertEqual(len(result), 1)
+    expected = issue_objects_pb2.Issue.LabelValue(
+        label=input_labels[0], derivation=EXPLICIT_DERIVATION)
+    self.assertEqual(result[0], expected)
+
+  def testConvertLabels_DerivedLabels(self):
+    """It handles derived labels"""
+    input_labels = ['pri-1']
+    result = self.converter.ConvertLabels(
+        [], input_labels, self.project_1.project_id)
+    self.assertEqual(len(result), 1)
+    expected = issue_objects_pb2.Issue.LabelValue(
+        label=input_labels[0], derivation=RULE_DERIVATION)
+    self.assertEqual(result[0], expected)
+
+  def testConvertLabels(self):
+    """It includes both non-derived and derived labels"""
+    input_labels = ['pri-1', '{}-2'.format(self.field_def_3_name)]
+    input_der_labels = ['{}-3'.format(self.field_def_3_name), 'job-secret']
+    result = self.converter.ConvertLabels(
+        input_labels, input_der_labels, self.project_1.project_id)
+    self.assertEqual(len(result), 2)
+    expected_0 = issue_objects_pb2.Issue.LabelValue(
+        label=input_labels[0], derivation=EXPLICIT_DERIVATION)
+    self.assertEqual(result[0], expected_0)
+    expected_1 = issue_objects_pb2.Issue.LabelValue(
+        label=input_der_labels[1], derivation=RULE_DERIVATION)
+    self.assertEqual(result[1], expected_1)
+
+  def testConvertLabels_Empty(self):
+    result = self.converter.ConvertLabels([], [], self.project_1.project_id)
+    self.assertEqual(result, [])
+
+  def testConvertEnumFieldValues_OnlyFieldDefs(self):
+    """It only returns enum field values"""
+    expected_value = '2'
+    input_labels = [
+        'pri-1', '{}-{}'.format(self.field_def_3_name, expected_value)
+    ]
+    result = self.converter.ConvertEnumFieldValues(
+        input_labels, [], self.project_1.project_id)
+    self.assertEqual(len(result), 1)
+    expected_name = rnc.ConvertFieldDefNames(
+        self.cnxn, [self.field_def_3], self.project_1.project_id,
+        self.services).get(self.field_def_3)
+    expected = issue_objects_pb2.FieldValue(
+        field=expected_name,
+        value=expected_value,
+        derivation=EXPLICIT_DERIVATION)
+    self.assertEqual(result[0], expected)
+
+  def testConvertEnumFieldValues_DerivedLabels(self):
+    """It handles derived enum field values"""
+    expected_value = '2'
+    input_der_labels = [
+        'pri-1', '{}-{}'.format(self.field_def_3_name, expected_value)
+    ]
+    result = self.converter.ConvertEnumFieldValues(
+        [], input_der_labels, self.project_1.project_id)
+    self.assertEqual(len(result), 1)
+    expected_name = rnc.ConvertFieldDefNames(
+        self.cnxn, [self.field_def_3], self.project_1.project_id,
+        self.services).get(self.field_def_3)
+    expected = issue_objects_pb2.FieldValue(
+        field=expected_name, value=expected_value, derivation=RULE_DERIVATION)
+    self.assertEqual(result[0], expected)
+
+  def testConvertEnumFieldValues_Empty(self):
+    result = self.converter.ConvertEnumFieldValues(
+        [], [], self.project_1.project_id)
+    self.assertEqual(result, [])
+
+  def testConvertEnumFieldValues_ProjectSpecific(self):
+    """It only considers field defs from specified project"""
+    expected_value = '2'
+    input_labels = [
+        '{}-{}'.format(self.field_def_3_name, expected_value),
+        '{}-ipsum'.format(self.field_def_project2_name)
+    ]
+    result = self.converter.ConvertEnumFieldValues(
+        input_labels, [], self.project_1.project_id)
+    self.assertEqual(len(result), 1)
+    expected_name = rnc.ConvertFieldDefNames(
+        self.cnxn, [self.field_def_3], self.project_1.project_id,
+        self.services).get(self.field_def_3)
+    expected = issue_objects_pb2.FieldValue(
+        field=expected_name,
+        value=expected_value,
+        derivation=EXPLICIT_DERIVATION)
+    self.assertEqual(result[0], expected)
+
+  def testConvertEnumFieldValues(self):
+    """It handles derived enum field values"""
+    expected_value_0 = '2'
+    expected_value_1 = 'macOS'
+    input_labels = [
+        'pri-1', '{}-{}'.format(self.field_def_3_name, expected_value_0),
+        '{}-ipsum'.format(self.field_def_project2_name)
+    ]
+    input_der_labels = [
+        '{}-{}'.format(self.field_def_4_name, expected_value_1), 'foo-bar'
+    ]
+    result = self.converter.ConvertEnumFieldValues(
+        input_labels, input_der_labels, self.project_1.project_id)
+    self.assertEqual(len(result), 2)
+    expected_0_name = rnc.ConvertFieldDefNames(
+        self.cnxn, [self.field_def_3], self.project_1.project_id,
+        self.services).get(self.field_def_3)
+    expected_0 = issue_objects_pb2.FieldValue(
+        field=expected_0_name,
+        value=expected_value_0,
+        derivation=EXPLICIT_DERIVATION)
+    self.assertEqual(result[0], expected_0)
+    expected_1_name = rnc.ConvertFieldDefNames(
+        self.cnxn, [self.field_def_4], self.project_1.project_id,
+        self.services).get(self.field_def_4)
+    expected_1 = issue_objects_pb2.FieldValue(
+        field=expected_1_name,
+        value=expected_value_1,
+        derivation=RULE_DERIVATION)
+    self.assertEqual(result[1], expected_1)
+
+  @mock.patch('project.project_helpers.GetThumbnailUrl')
+  def testConvertProject(self, mock_GetThumbnailUrl):
+    """We can convert a Project."""
+    mock_GetThumbnailUrl.return_value = 'xyz'
+    expected_api_project = project_objects_pb2.Project(
+        name='projects/{}'.format(self.project_1.project_name),
+        display_name=self.project_1.project_name,
+        summary=self.project_1.summary,
+        thumbnail_url='xyz')
+    self.assertEqual(
+        expected_api_project, self.converter.ConvertProject(self.project_1))
+
+  @mock.patch('project.project_helpers.GetThumbnailUrl')
+  def testConvertProjects(self, mock_GetThumbnailUrl):
+    """We can convert a Sequence of Projects."""
+    mock_GetThumbnailUrl.return_value = 'xyz'
+    expected_api_projects = [
+        project_objects_pb2.Project(
+            name='projects/{}'.format(self.project_1.project_name),
+            display_name=self.project_1.project_name,
+            summary=self.project_1.summary,
+            thumbnail_url='xyz'),
+        project_objects_pb2.Project(
+            name='projects/{}'.format(self.project_2.project_name),
+            display_name=self.project_2.project_name,
+            summary=self.project_2.summary,
+            thumbnail_url='xyz')
+    ]
+    self.assertEqual(
+        expected_api_projects,
+        self.converter.ConvertProjects([self.project_1, self.project_2]))
+
+  def testConvertProjectConfig(self):
+    """We can convert a project_config"""
+    project_config = self.services.config.GetProjectConfig(
+        self.cnxn, self.project_1.project_id)
+    expected_grid_config = project_objects_pb2.ProjectConfig.GridViewConfig(
+        default_x_attr=project_config.default_x_attr,
+        default_y_attr=project_config.default_y_attr)
+    template_names = rnc.ConvertTemplateNames(
+        self.cnxn, project_config.project_id, [
+            project_config.default_template_for_developers,
+            project_config.default_template_for_users
+        ], self.services)
+    expected_api_config = project_objects_pb2.ProjectConfig(
+        name=rnc.ConvertProjectConfigName(
+            self.cnxn, self.project_1.project_id, self.services),
+        exclusive_label_prefixes=project_config.exclusive_label_prefixes,
+        member_default_query=project_config.member_default_query,
+        default_sort=project_config.default_sort_spec,
+        default_columns=[
+            issue_objects_pb2.IssuesListColumn(column=col)
+            for col in project_config.default_col_spec.split()
+        ],
+        project_grid_config=expected_grid_config,
+        member_default_template=template_names.get(
+            project_config.default_template_for_developers),
+        non_members_default_template=template_names.get(
+            project_config.default_template_for_users),
+        revision_url_format=self.project_1.revision_url_format,
+        custom_issue_entry_url=project_config.custom_issue_entry_url)
+    self.converter.user_auth = authdata.AuthData.FromUser(
+        self.cnxn, self.user_1, self.services)
+    self.assertEqual(
+        expected_api_config,
+        self.converter.ConvertProjectConfig(project_config))
+
+  def testConvertProjectConfig_NonMembers(self):
+    """We can convert a project_config for non project members"""
+    self.converter.user_auth = authdata.AuthData.FromUser(
+        self.cnxn, self.user_2, self.services)
+    project_config = self.services.config.GetProjectConfig(
+        self.cnxn, self.project_1.project_id)
+    api_config = self.converter.ConvertProjectConfig(project_config)
+
+    expected_default_query = project_config.member_default_query
+    self.assertEqual(expected_default_query, api_config.member_default_query)
+
+    expected_member_default_template = rnc.ConvertTemplateNames(
+        self.cnxn, project_config.project_id,
+        [project_config.default_template_for_developers], self.services).get(
+            project_config.default_template_for_developers)
+    self.assertEqual(
+        expected_member_default_template, api_config.member_default_template)
+
+  def testCreateProjectMember(self):
+    """We can create a ProjectMember."""
+    expected_project_member = project_objects_pb2.ProjectMember(
+        name='projects/proj/members/111',
+        role=project_objects_pb2.ProjectMember.ProjectRole.Value('OWNER'))
+    self.assertEqual(
+        expected_project_member,
+        self.converter.CreateProjectMember(self.cnxn, 789, 111, 'OWNER'))
+
+  def test_ConvertDateAction(self):
+    """We can convert from protorpc to protoc FieldDef.DateAction"""
+    date_type_settings = project_objects_pb2.FieldDef.DateTypeSettings
+
+    input_type = tracker_pb2.DateAction.NO_ACTION
+    actual = self.converter._ConvertDateAction(input_type)
+    expected = date_type_settings.DateAction.Value('NO_ACTION')
+    self.assertEqual(expected, actual)
+
+    input_type = tracker_pb2.DateAction.PING_OWNER_ONLY
+    actual = self.converter._ConvertDateAction(input_type)
+    expected = date_type_settings.DateAction.Value('NOTIFY_OWNER')
+    self.assertEqual(expected, actual)
+
+    input_type = tracker_pb2.DateAction.PING_PARTICIPANTS
+    actual = self.converter._ConvertDateAction(input_type)
+    expected = date_type_settings.DateAction.Value('NOTIFY_PARTICIPANTS')
+    self.assertEqual(expected, actual)
+
+  def test_ConvertRoleRequirements(self):
+    """We can convert from protorpc to protoc FieldDef.RoleRequirements"""
+    user_type_settings = project_objects_pb2.FieldDef.UserTypeSettings
+
+    actual = self.converter._ConvertRoleRequirements(False)
+    expected = user_type_settings.RoleRequirements.Value('NO_ROLE_REQUIREMENT')
+    self.assertEqual(expected, actual)
+
+    actual = self.converter._ConvertRoleRequirements(True)
+    expected = user_type_settings.RoleRequirements.Value('PROJECT_MEMBER')
+    self.assertEqual(expected, actual)
+
+  def test_ConvertNotifyTriggers(self):
+    """We can convert from protorpc to protoc FieldDef.NotifyTriggers"""
+    user_type_settings = project_objects_pb2.FieldDef.UserTypeSettings
+
+    input_type = tracker_pb2.NotifyTriggers.NEVER
+    actual = self.converter._ConvertNotifyTriggers(input_type)
+    expected = user_type_settings.NotifyTriggers.Value('NEVER')
+    self.assertEqual(expected, actual)
+
+    input_type = tracker_pb2.NotifyTriggers.ANY_COMMENT
+    actual = self.converter._ConvertNotifyTriggers(input_type)
+    expected = user_type_settings.NotifyTriggers.Value('ANY_COMMENT')
+    self.assertEqual(expected, actual)
+
+  def test_ConvertFieldDefType(self):
+    """We can convert from protorpc FieldType to protoc FieldDef.Type"""
+    input_type = tracker_pb2.FieldTypes.ENUM_TYPE
+    actual = self.converter._ConvertFieldDefType(input_type)
+    expected = project_objects_pb2.FieldDef.Type.Value('ENUM')
+    self.assertEqual(expected, actual)
+
+    input_type = tracker_pb2.FieldTypes.INT_TYPE
+    actual = self.converter._ConvertFieldDefType(input_type)
+    expected = project_objects_pb2.FieldDef.Type.Value('INT')
+    self.assertEqual(expected, actual)
+
+    input_type = tracker_pb2.FieldTypes.STR_TYPE
+    actual = self.converter._ConvertFieldDefType(input_type)
+    expected = project_objects_pb2.FieldDef.Type.Value('STR')
+    self.assertEqual(expected, actual)
+
+    input_type = tracker_pb2.FieldTypes.USER_TYPE
+    actual = self.converter._ConvertFieldDefType(input_type)
+    expected = project_objects_pb2.FieldDef.Type.Value('USER')
+    self.assertEqual(expected, actual)
+
+    input_type = tracker_pb2.FieldTypes.DATE_TYPE
+    actual = self.converter._ConvertFieldDefType(input_type)
+    expected = project_objects_pb2.FieldDef.Type.Value('DATE')
+    self.assertEqual(expected, actual)
+
+    input_type = tracker_pb2.FieldTypes.URL_TYPE
+    actual = self.converter._ConvertFieldDefType(input_type)
+    expected = project_objects_pb2.FieldDef.Type.Value('URL')
+    self.assertEqual(expected, actual)
+
+  def test_ConvertFieldDefType_BOOL(self):
+    """We raise exception for unsupported input type BOOL"""
+    input_type = tracker_pb2.FieldTypes.BOOL_TYPE
+    with self.assertRaises(ValueError) as cm:
+      self.converter._ConvertFieldDefType(input_type)
+    self.assertEqual(
+        'Unsupported tracker_pb2.FieldType enum. Boolean types '
+        'are unsupported and approval types are found in ApprovalDefs',
+        str(cm.exception))
+
+  def test_ConvertFieldDefType_APPROVAL(self):
+    """We raise exception for input type APPROVAL"""
+    input_type = tracker_pb2.FieldTypes.APPROVAL_TYPE
+    with self.assertRaises(ValueError) as cm:
+      self.converter._ConvertFieldDefType(input_type)
+    self.assertEqual(
+        'Unsupported tracker_pb2.FieldType enum. Boolean types '
+        'are unsupported and approval types are found in ApprovalDefs',
+        str(cm.exception))
+
+  def testConvertFieldDefs(self):
+    """We can convert field defs"""
+    project_config = self.services.config.GetProjectConfig(
+        self.cnxn, self.project_1.project_id)
+    input_fds = project_config.field_defs
+    output = self.converter.ConvertFieldDefs(
+        input_fds, self.project_1.project_id)
+    fd1_rn = rnc.ConvertFieldDefNames(
+        self.cnxn, [self.field_def_1], self.project_1.project_id,
+        self.services).get(self.field_def_1)
+    self.assertEqual(fd1_rn, output[0].name)
+    self.assertEqual(self.field_def_1_name, output[0].display_name)
+    self.assertEqual('', output[0].docstring)
+    self.assertEqual(
+        project_objects_pb2.FieldDef.Type.Value('STR'), output[0].type)
+    self.assertEqual(
+        project_objects_pb2.FieldDef.Type.Value('INT'), output[1].type)
+    self.assertEqual('', output[1].applicable_issue_type)
+    fd1_admin_editor = [rnc.ConvertUserName(self.user_1.user_id)]
+    self.assertEqual(fd1_admin_editor, output[0].admins)
+    self.assertEqual(fd1_admin_editor, output[5].editors)
+
+  def testConvertFieldDefs_Traits(self):
+    """We can convert FieldDefs with traits"""
+    input_fd = self._GetFieldDefById(
+        self.project_1.project_id, self.field_def_1)
+    output = self.converter.ConvertFieldDefs(
+        [input_fd], self.project_1.project_id)
+    self.assertEqual(1, len(output))
+    expected_traits = [
+        project_objects_pb2.FieldDef.Traits.Value('REQUIRED'),
+        project_objects_pb2.FieldDef.Traits.Value('MULTIVALUED'),
+        project_objects_pb2.FieldDef.Traits.Value('PHASE')
+    ]
+    self.assertEqual(expected_traits, output[0].traits)
+
+    input_fd = self._GetFieldDefById(
+        self.project_1.project_id, self.field_def_2)
+    output = self.converter.ConvertFieldDefs(
+        [input_fd], self.project_1.project_id)
+    self.assertEqual(1, len(output))
+    expected_traits = [
+        project_objects_pb2.FieldDef.Traits.Value('DEFAULT_HIDDEN')
+    ]
+    self.assertEqual(expected_traits, output[0].traits)
+
+  def testConvertFieldDefs_ApprovalParent(self):
+    """We can convert FieldDef with approval parents"""
+    input_fd = self._GetFieldDefById(
+        self.project_1.project_id, self.field_def_6)
+    output = self.converter.ConvertFieldDefs(
+        [input_fd], self.project_1.project_id)
+    self.assertEqual(1, len(output))
+
+    approval_names_dict = rnc.ConvertApprovalDefNames(
+        self.cnxn, [self.approval_def_1_id], self.project_1.project_id,
+        self.services)
+    expected_approval_parent = approval_names_dict.get(input_fd.approval_id)
+    self.assertEqual(expected_approval_parent, output[0].approval_parent)
+
+  def testConvertFieldDefs_EnumTypeSettings(self):
+    """We can convert enum FieldDef and its settings"""
+    input_fd = self._GetFieldDefById(
+        self.project_1.project_id, self.field_def_5)
+    output = self.converter.ConvertFieldDefs(
+        [input_fd], self.project_1.project_id)
+    self.assertEqual(1, len(output))
+
+    expected_settings = project_objects_pb2.FieldDef.EnumTypeSettings(
+        choices=[
+            Choice(
+                value='submarine', docstring=self.labeldef_2.label_docstring),
+            Choice(value='basket', docstring=self.labeldef_3.label_docstring)
+        ])
+    self.assertEqual(expected_settings, output[0].enum_settings)
+
+  def testConvertFieldDefs_IntTypeSettings(self):
+    """We can convert int FieldDef and its settings"""
+    input_fd = self._GetFieldDefById(
+        self.project_1.project_id, self.field_def_2)
+    output = self.converter.ConvertFieldDefs(
+        [input_fd], self.project_1.project_id)
+    self.assertEqual(1, len(output))
+
+    expected_settings = project_objects_pb2.FieldDef.IntTypeSettings(
+        max_value=37)
+    self.assertEqual(expected_settings, output[0].int_settings)
+
+  def testConvertFieldDefs_StrTypeSettings(self):
+    """We can convert str FieldDef and its settings"""
+    input_fd = self._GetFieldDefById(
+        self.project_1.project_id, self.field_def_1)
+    output = self.converter.ConvertFieldDefs(
+        [input_fd], self.project_1.project_id)
+    self.assertEqual(1, len(output))
+
+    expected_settings = project_objects_pb2.FieldDef.StrTypeSettings(
+        regex='abc')
+    self.assertEqual(expected_settings, output[0].str_settings)
+
+  def testConvertFieldDefs_UserTypeSettings(self):
+    """We can convert user FieldDef and its settings"""
+    input_fd = self._GetFieldDefById(
+        self.project_1.project_id, self.field_def_8)
+    output = self.converter.ConvertFieldDefs(
+        [input_fd], self.project_1.project_id)
+    self.assertEqual(1, len(output))
+
+    user_settings = project_objects_pb2.FieldDef.UserTypeSettings
+    expected_settings = project_objects_pb2.FieldDef.UserTypeSettings(
+        role_requirements=user_settings.RoleRequirements.Value(
+            'PROJECT_MEMBER'),
+        needs_perm='EDIT_PROJECT',
+        notify_triggers=user_settings.NotifyTriggers.Value('ANY_COMMENT'))
+    self.assertEqual(expected_settings, output[0].user_settings)
+
+  def testConvertFieldDefs_DateTypeSettings(self):
+    """We can convert user FieldDef and its settings"""
+    input_fd = self._GetFieldDefById(
+        self.project_1.project_id, self.field_def_9)
+    output = self.converter.ConvertFieldDefs(
+        [input_fd], self.project_1.project_id)
+    self.assertEqual(1, len(output))
+
+    date_settings = project_objects_pb2.FieldDef.DateTypeSettings
+    expected_settings = project_objects_pb2.FieldDef.DateTypeSettings(
+        date_action=date_settings.DateAction.Value('NOTIFY_OWNER'))
+    self.assertEqual(expected_settings, output[0].date_settings)
+
+  def testConvertFieldDefs_SkipsApprovals(self):
+    """We skip over approval defs"""
+    project_config = self.services.config.GetProjectConfig(
+        self.cnxn, self.project_1.project_id)
+    input_fds = project_config.field_defs
+    # project_1 is set up to have 10 non-approval fields and 2 approval fields.
+    self.assertEqual(12, len(input_fds))
+    output = self.converter.ConvertFieldDefs(
+        input_fds, self.project_1.project_id)
+    # assert we skip approval fields
+    self.assertEqual(10, len(output))
+
+  def testConvertFieldDefs_NonexistentID(self):
+    """We skip over any field defs whose ID does not exist."""
+    input_fd = tracker_pb2.FieldDef(
+        field_id=self.dne_field_def_id,
+        project_id=self.project_1.project_id,
+        field_name='foobar',
+        field_type=tracker_pb2.FieldTypes('STR_TYPE'))
+
+    output = self.converter.ConvertFieldDefs(
+        [input_fd], self.project_1.project_id)
+    self.assertEqual(0, len(output))
+
+  def testConvertFieldDefs_Empty(self):
+    """We can handle empty list input"""
+    self.assertEqual(
+        [], self.converter.ConvertFieldDefs([], self.project_1.project_id))
+
+  def test_ComputeFieldDefTraits(self):
+    """We can get Sequence of Traits for a FieldDef"""
+    input_fd = self._GetFieldDefById(
+        self.project_1.project_id, self.field_def_1)
+    actual = self.converter._ComputeFieldDefTraits(input_fd)
+    expected = [
+        project_objects_pb2.FieldDef.Traits.Value('REQUIRED'),
+        project_objects_pb2.FieldDef.Traits.Value('MULTIVALUED'),
+        project_objects_pb2.FieldDef.Traits.Value('PHASE')
+    ]
+    self.assertEqual(expected, actual)
+
+    input_fd = self._GetFieldDefById(
+        self.project_1.project_id, self.field_def_2)
+    actual = self.converter._ComputeFieldDefTraits(input_fd)
+    expected = [project_objects_pb2.FieldDef.Traits.Value('DEFAULT_HIDDEN')]
+    self.assertEqual(expected, actual)
+
+    input_fd = self._GetFieldDefById(
+        self.project_1.project_id, self.field_def_7)
+    actual = self.converter._ComputeFieldDefTraits(input_fd)
+    expected = [project_objects_pb2.FieldDef.Traits.Value('RESTRICTED')]
+    self.assertEqual(expected, actual)
+
+  def test_ComputeFieldDefTraits_Empty(self):
+    """We return an empty Sequence of Traits for plain FieldDef"""
+    input_fd = self._GetFieldDefById(
+        self.project_1.project_id, self.field_def_3)
+    actual = self.converter._ComputeFieldDefTraits(input_fd)
+    self.assertEqual([], actual)
+
+  def test_GetEnumFieldChoices(self):
+    """We can get all choices for an enum field"""
+    input_fd = self._GetFieldDefById(
+        self.project_1.project_id, self.field_def_5)
+    actual = self.converter._GetEnumFieldChoices(input_fd)
+    expected = [
+        Choice(
+            value=self.labeldef_2.label.split('-')[1],
+            docstring=self.labeldef_2.label_docstring),
+        Choice(
+            value=self.labeldef_3.label.split('-')[1],
+            docstring=self.labeldef_3.label_docstring),
+    ]
+    self.assertEqual(expected, actual)
+
+  def test_GetEnumFieldChoices_NotEnumField(self):
+    """We raise exception for non-enum-field"""
+    input_fd = self._GetFieldDefById(
+        self.project_1.project_id, self.field_def_1)
+    with self.assertRaises(ValueError) as cm:
+      self.converter._GetEnumFieldChoices(input_fd)
+    self.assertEqual(
+        'Cannot get value from label for non-enum-type field', str(
+            cm.exception))
+
+  def testConvertApprovalDefs(self):
+    """We can convert ApprovalDefs"""
+    input_ad = self._GetApprovalDefById(
+        self.project_1.project_id, self.approval_def_1_id)
+    actual = self.converter.ConvertApprovalDefs(
+        [input_ad], self.project_1.project_id)
+
+    resource_names_dict = rnc.ConvertApprovalDefNames(
+        self.cnxn, [self.approval_def_1_id], self.project_1.project_id,
+        self.services)
+    expected_name = resource_names_dict.get(self.approval_def_1_id)
+    self.assertEqual(actual[0].name, expected_name)
+    self.assertEqual(actual[0].display_name, self.approval_def_1_name)
+    matching_fd = self._GetFieldDefById(
+        self.project_1.project_id, self.approval_def_1_id)
+    expected_docstring = matching_fd.docstring
+    self.assertEqual(actual[0].docstring, expected_docstring)
+    self.assertEqual(actual[0].survey, self.approval_def_1.survey)
+    expected_approvers = [rnc.ConvertUserName(self.user_2.user_id)]
+    self.assertEqual(actual[0].approvers, expected_approvers)
+    expected_admins = [rnc.ConvertUserName(self.user_1.user_id)]
+    self.assertEqual(actual[0].admins, expected_admins)
+
+  def testConvertApprovalDefs_Empty(self):
+    """We can handle empty case"""
+    actual = self.converter.ConvertApprovalDefs([], self.project_1.project_id)
+    self.assertEqual(actual, [])
+
+  def testConvertApprovalDefs_SkipsNonApprovalDefs(self):
+    """We skip if no matching field def exists"""
+    input_ad = tracker_pb2.ApprovalDef(
+        approval_id=self.dne_field_def_id,
+        approver_ids=[self.user_2.user_id],
+        survey='anything goes')
+    actual = self.converter.ConvertApprovalDefs(
+        [input_ad], self.project_1.project_id)
+    self.assertEqual(actual, [])
+
+  def testConvertLabelDefs(self):
+    """We can convert LabelDefs"""
+    actual = self.converter.ConvertLabelDefs(
+        [self.labeldef_1, self.labeldef_5], self.project_1.project_id)
+    resource_names_dict = rnc.ConvertLabelDefNames(
+        self.cnxn, [self.labeldef_1.label, self.labeldef_5.label],
+        self.project_1.project_id, self.services)
+    expected_0_name = resource_names_dict.get(self.labeldef_1.label)
+    expected_0 = project_objects_pb2.LabelDef(
+        name=expected_0_name,
+        value=self.labeldef_1.label,
+        docstring=self.labeldef_1.label_docstring,
+        state=project_objects_pb2.LabelDef.LabelDefState.Value('ACTIVE'))
+    self.assertEqual(expected_0, actual[0])
+    expected_1_name = resource_names_dict.get(self.labeldef_5.label)
+    expected_1 = project_objects_pb2.LabelDef(
+        name=expected_1_name,
+        value=self.labeldef_5.label,
+        docstring=self.labeldef_5.label_docstring,
+        state=project_objects_pb2.LabelDef.LabelDefState.Value('DEPRECATED'))
+    self.assertEqual(expected_1, actual[1])
+
+  def testConvertLabelDefs_Empty(self):
+    """We can handle empty input case"""
+    actual = self.converter.ConvertLabelDefs([], self.project_1.project_id)
+    self.assertEqual([], actual)
+
+  def testConvertStatusDefs(self):
+    """We can convert StatusDefs"""
+    actual = self.converter.ConvertStatusDefs(
+        self.predefined_statuses, self.project_1.project_id)
+    self.assertEqual(len(actual), 4)
+
+    input_names = [sd.status for sd in self.predefined_statuses]
+    names = rnc.ConvertStatusDefNames(
+        self.cnxn, input_names, self.project_1.project_id, self.services)
+    self.assertEqual(names[self.status_1.status], actual[0].name)
+    self.assertEqual(names[self.status_2.status], actual[1].name)
+    self.assertEqual(names[self.status_3.status], actual[2].name)
+    self.assertEqual(names[self.status_4.status], actual[3].name)
+
+    self.assertEqual(self.status_1.status, actual[0].value)
+    self.assertEqual(
+        project_objects_pb2.StatusDef.StatusDefType.Value('OPEN'),
+        actual[0].type)
+    self.assertEqual(0, actual[0].rank)
+    self.assertEqual(self.status_1.status_docstring, actual[0].docstring)
+    self.assertEqual(
+        project_objects_pb2.StatusDef.StatusDefState.Value('ACTIVE'),
+        actual[0].state)
+
+  def testConvertStatusDefs_Empty(self):
+    """Can handle empty input case"""
+    actual = self.converter.ConvertStatusDefs([], self.project_1.project_id)
+    self.assertEqual([], actual)
+
+  def testConvertStatusDefs_Rank(self):
+    """Rank is indepdendent of input order"""
+    input_sds = [self.status_2, self.status_4, self.status_3, self.status_1]
+    actual = self.converter.ConvertStatusDefs(
+        input_sds, self.project_1.project_id)
+    self.assertEqual(1, actual[0].rank)
+    self.assertEqual(3, actual[1].rank)
+
+  def testConvertStatusDefs_type_MERGED(self):
+    """Includes mergeable status when parsed from project config"""
+    actual = self.converter.ConvertStatusDefs(
+        [self.status_2], self.project_1.project_id)
+    self.assertEqual(
+        project_objects_pb2.StatusDef.StatusDefType.Value('MERGED'),
+        actual[0].type)
+
+  def testConvertStatusDefs_state_DEPRECATED(self):
+    """Includes deprecated status"""
+    actual = self.converter.ConvertStatusDefs(
+        [self.status_4], self.project_1.project_id)
+    self.assertEqual(
+        project_objects_pb2.StatusDef.StatusDefState.Value('DEPRECATED'),
+        actual[0].state)
+
+  def testConvertComponentDef(self):
+    now = 123
+    project = self.services.project.TestAddProject('comp-test', project_id=987)
+    config = fake.MakeTestConfig(project.project_id, [], [])
+    component_def = fake.MakeTestComponentDef(
+        project.project_id, 1, path='Chickens>Dickens')
+    component_def.created = now
+    config.component_defs = [component_def]
+    self.services.config.StoreConfig(self.cnxn, config)
+
+    actual = self.converter.ConvertComponentDef(component_def)
+    expected = project_objects_pb2.ComponentDef(
+        name='projects/comp-test/componentDefs/1',
+        value='Chickens>Dickens',
+        state=project_objects_pb2.ComponentDef.ComponentDefState.Value(
+            'ACTIVE'),
+        create_time=timestamp_pb2.Timestamp(seconds=now),
+        modify_time=timestamp_pb2.Timestamp())
+    self.assertEqual(actual, expected)
+
+  def testConvertComponentDefs(self):
+    """We can convert ComponentDefs"""
+    project_config = self.services.config.GetProjectConfig(
+        self.cnxn, self.project_1.project_id)
+    self.assertEqual(len(project_config.component_defs), 2)
+
+    actual = self.converter.ConvertComponentDefs(
+        project_config.component_defs, self.project_1.project_id)
+    self.assertEqual(2, len(actual))
+
+    resource_names_dict = rnc.ConvertComponentDefNames(
+        self.cnxn, [self.component_def_1_id, self.component_def_2_id],
+        self.project_1.project_id, self.services)
+    self.assertEqual(
+        resource_names_dict.get(self.component_def_1_id), actual[0].name)
+    self.assertEqual(
+        resource_names_dict.get(self.component_def_2_id), actual[1].name)
+    self.assertEqual(self.component_def_1_path, actual[0].value)
+    self.assertEqual(self.component_def_2_path, actual[1].value)
+    self.assertEqual('cd1_docstring', actual[0].docstring)
+    self.assertEqual(
+        project_objects_pb2.ComponentDef.ComponentDefState.Value('ACTIVE'),
+        actual[0].state)
+    self.assertEqual(
+        project_objects_pb2.ComponentDef.ComponentDefState.Value('DEPRECATED'),
+        actual[1].state)
+    # component_def 1 and 2 have the same admins, ccs, creator, and create_time
+    expected_admins = [rnc.ConvertUserName(self.user_1.user_id)]
+    self.assertEqual(expected_admins, actual[0].admins)
+    expected_ccs = [rnc.ConvertUserName(self.user_2.user_id)]
+    self.assertEqual(expected_ccs, actual[0].ccs)
+    expected_creator = rnc.ConvertUserName(self.user_1.user_id)
+    self.assertEqual(expected_creator, actual[0].creator)
+    expected_create_time = timestamp_pb2.Timestamp(seconds=self.PAST_TIME)
+    self.assertEqual(expected_create_time, actual[0].create_time)
+
+    expected_labels = [ld.label for ld in self.predefined_labels]
+    self.assertEqual(expected_labels, actual[0].labels)
+    self.assertEqual([], actual[1].labels)
+
+  def testConvertComponentDefs_Empty(self):
+    """Can handle empty input case"""
+    actual = self.converter.ConvertComponentDefs([], self.project_1.project_id)
+    self.assertEqual([], actual)
+
+  def testConvertProjectSavedQueries(self):
+    """We can convert ProjectSavedQueries"""
+    input_psqs = [self.psq_2]
+    actual = self.converter.ConvertProjectSavedQueries(
+        input_psqs, self.project_1.project_id)
+    self.assertEqual(1, len(actual))
+
+    resource_names_dict = rnc.ConvertProjectSavedQueryNames(
+        self.cnxn, [self.psq_2.query_id], self.project_1.project_id,
+        self.services)
+    self.assertEqual(
+        resource_names_dict.get(self.psq_2.query_id), actual[0].name)
+    self.assertEqual(self.psq_2.name, actual[0].display_name)
+    self.assertEqual(self.psq_2.query, actual[0].query)
+
+  def testConvertProjectSavedQueries_ExpandsBasedOn(self):
+    """We expand query to include base_query_id"""
+    actual = self.converter.ConvertProjectSavedQueries(
+        [self.psq_1], self.project_1.project_id)
+    expected_query = '{} {}'.format(
+        tbo.GetBuiltInQuery(self.psq_1.base_query_id), self.psq_1.query)
+    self.assertEqual(expected_query, actual[0].query)
+
+  def testConvertProjectSavedQueries_NotInProject(self):
+    """We skip over saved queries that don't belong to this project"""
+    psq_not_registered = tracker_pb2.SavedQuery(
+        query_id=4, name='psq no registered name', query='no registered')
+    actual = self.converter.ConvertProjectSavedQueries(
+        [psq_not_registered], self.project_1.project_id)
+    self.assertEqual([], actual)
+
+  def testConvertProjectSavedQueries_Empty(self):
+    """We can handle empty inputs"""
+    actual = self.converter.ConvertProjectSavedQueries(
+        [], self.project_1.project_id)
+    self.assertEqual([], actual)
diff --git a/api/v3/test/frontend_servicer_test.py b/api/v3/test/frontend_servicer_test.py
new file mode 100644
index 0000000..e58f1ab
--- /dev/null
+++ b/api/v3/test/frontend_servicer_test.py
@@ -0,0 +1,237 @@
+# Copyright 2020 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style
+# license that can be found in the LICENSE file or at
+# https://developers.google.com/open-source/licenses/bsd
+"""Tests for the hotlists servicer."""
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+
+import unittest
+
+from google.protobuf import timestamp_pb2
+from mock import patch
+
+from api import resource_name_converters as rnc
+from api.v3 import converters
+from api.v3 import frontend_servicer
+from api.v3.api_proto import frontend_pb2
+from api.v3.api_proto import project_objects_pb2
+from framework import exceptions
+from framework import monorailcontext
+from proto import tracker_pb2
+from services import service_manager
+from testing import fake
+from tracker import tracker_constants
+
+
+class FrontendServicerTest(unittest.TestCase):
+
+  def setUp(self):
+    self.cnxn = fake.MonorailConnection()
+    self.services = service_manager.Services(
+        features=fake.FeaturesService(),
+        issue=fake.IssueService(),
+        project=fake.ProjectService(),
+        config=fake.ConfigService(),
+        user=fake.UserService(),
+        template=fake.TemplateService(),
+        usergroup=fake.UserGroupService())
+    self.frontend_svcr = frontend_servicer.FrontendServicer(
+        self.services, make_rate_limiter=False)
+
+    self.user_1 = self.services.user.TestAddUser('user_111@example.com', 111)
+    self.user_1_resource_name = 'users/111'
+    self.project_1_resource_name = 'projects/proj'
+    self.project_1 = self.services.project.TestAddProject(
+        'proj', project_id=789)
+    self.template_0 = self.services.template.TestAddIssueTemplateDef(
+        11110, self.project_1.project_id, 'template0')
+    self.PAST_TIME = 12345
+    self.component_def_1_path = 'foo'
+    self.component_def_1_id = self.services.config.CreateComponentDef(
+        self.cnxn, self.project_1.project_id, self.component_def_1_path,
+        'cd1_docstring', False, [self.user_1.user_id], [], self.PAST_TIME,
+        self.user_1.user_id, [])
+    self.field_def_1_name = 'test_field_1'
+    self.field_def_1 = self._CreateFieldDef(
+        self.project_1.project_id,
+        self.field_def_1_name,
+        'STR_TYPE',
+        admin_ids=[self.user_1.user_id],
+        is_required=True,
+        is_multivalued=True,
+        is_phase_field=True,
+        regex='abc')
+    self.approval_def_1_name = 'approval_field_1'
+    self.approval_def_1_id = self._CreateFieldDef(
+        self.project_1.project_id,
+        self.approval_def_1_name,
+        'APPROVAL_TYPE',
+        docstring='ad_1_docstring',
+        admin_ids=[self.user_1.user_id])
+    self.approval_def_1 = tracker_pb2.ApprovalDef(
+        approval_id=self.approval_def_1_id,
+        approver_ids=[self.user_1.user_id],
+        survey='approval_def_1 survey')
+    self.services.config.UpdateConfig(
+        self.cnxn,
+        self.project_1,
+        # UpdateConfig accepts tuples rather than protorpc *Defs
+        approval_defs=[
+            (ad.approval_id, ad.approver_ids, ad.survey)
+            for ad in [self.approval_def_1]
+        ])
+
+  def _CreateFieldDef(
+      self,
+      project_id,
+      field_name,
+      field_type_str,
+      docstring=None,
+      min_value=None,
+      max_value=None,
+      regex=None,
+      needs_member=None,
+      needs_perm=None,
+      grants_perm=None,
+      notify_on=None,
+      date_action_str=None,
+      admin_ids=None,
+      editor_ids=None,
+      is_required=False,
+      is_niche=False,
+      is_multivalued=False,
+      is_phase_field=False,
+      approval_id=None,
+      is_restricted_field=False):
+    """Calls CreateFieldDef with reasonable defaults, returns the ID."""
+    if admin_ids is None:
+      admin_ids = []
+    if editor_ids is None:
+      editor_ids = []
+    return self.services.config.CreateFieldDef(
+        self.cnxn,
+        project_id,
+        field_name,
+        field_type_str,
+        None,
+        None,
+        is_required,
+        is_niche,
+        is_multivalued,
+        min_value,
+        max_value,
+        regex,
+        needs_member,
+        needs_perm,
+        grants_perm,
+        notify_on,
+        date_action_str,
+        docstring,
+        admin_ids,
+        editor_ids,
+        is_phase_field=is_phase_field,
+        approval_id=approval_id,
+        is_restricted_field=is_restricted_field)
+
+  def CallWrapped(self, wrapped_handler, mc, *args, **kwargs):
+    self.frontend_svcr.converter = converters.Converter(mc, self.services)
+    return wrapped_handler.wrapped(self.frontend_svcr, mc, *args, **kwargs)
+
+  @patch('project.project_helpers.GetThumbnailUrl')
+  def testGatherProjectEnvironment(self, mock_GetThumbnailUrl):
+    """We can fetch all project related parameters for web frontend."""
+    mock_GetThumbnailUrl.return_value = 'xyz'
+
+    request = frontend_pb2.GatherProjectEnvironmentRequest(
+        parent=self.project_1_resource_name)
+    mc = monorailcontext.MonorailContext(
+        self.services, cnxn=self.cnxn, requester=self.user_1.email)
+    response = self.CallWrapped(
+        self.frontend_svcr.GatherProjectEnvironment, mc, request)
+    project_config = self.services.config.GetProjectConfig(
+        self.cnxn, self.project_1.project_id)
+
+    self.assertEqual(
+        response.project,
+        self.frontend_svcr.converter.ConvertProject(self.project_1))
+    self.assertEqual(
+        response.project_config,
+        self.frontend_svcr.converter.ConvertProjectConfig(project_config))
+
+    self.assertEqual(
+        len(response.statuses),
+        len(tracker_constants.DEFAULT_WELL_KNOWN_STATUSES))
+    self.assertEqual(
+        response.statuses[0],
+        project_objects_pb2.StatusDef(
+            name='projects/{project_name}/statusDefs/{status}'.format(
+                project_name=self.project_1.project_name,
+                status=tracker_constants.DEFAULT_WELL_KNOWN_STATUSES[0][0]),
+            value=tracker_constants.DEFAULT_WELL_KNOWN_STATUSES[0][0],
+            type=project_objects_pb2.StatusDef.StatusDefType.Value('OPEN'),
+            rank=0,
+            docstring=tracker_constants.DEFAULT_WELL_KNOWN_STATUSES[0][1],
+            state=project_objects_pb2.StatusDef.StatusDefState.Value('ACTIVE'),
+        ))
+
+    self.assertEqual(
+        len(response.well_known_labels),
+        len(tracker_constants.DEFAULT_WELL_KNOWN_LABELS))
+    self.assertEqual(
+        response.well_known_labels[0],
+        project_objects_pb2.LabelDef(
+            name='projects/{project_name}/labelDefs/{label}'.format(
+                project_name=self.project_1.project_name,
+                label=tracker_constants.DEFAULT_WELL_KNOWN_LABELS[0][0]),
+            value=tracker_constants.DEFAULT_WELL_KNOWN_LABELS[0][0],
+            docstring=tracker_constants.DEFAULT_WELL_KNOWN_LABELS[0][1],
+            state=project_objects_pb2.LabelDef.LabelDefState.Value('ACTIVE'),
+        ))
+
+    expected = self.frontend_svcr.converter.ConvertComponentDefs(
+        project_config.component_defs, self.project_1.project_id)
+    # Have to use list comprehension to break response sub field into list
+    self.assertEqual([api_cd for api_cd in response.components], expected)
+
+    expected = self.frontend_svcr.converter.ConvertFieldDefs(
+        project_config.field_defs, self.project_1.project_id)
+    self.assertEqual([api_fd for api_fd in response.fields], expected)
+
+    expected = self.frontend_svcr.converter.ConvertApprovalDefs(
+        project_config.approval_defs, self.project_1.project_id)
+    self.assertEqual([api_ad for api_ad in response.approval_fields], expected)
+
+  def testGatherProjectMembershipsForUser(self):
+    """We can list a user's project memberships."""
+    self.services.project.TestAddProject(
+        'owner_proj', project_id=777, owner_ids=[111])
+    self.services.project.TestAddProject(
+        'committer_proj', project_id=888, committer_ids=[111])
+    contributor_proj = self.services.project.TestAddProject(
+        'contributor_proj', project_id=999)
+    contributor_proj.contributor_ids = [111]
+
+    request = frontend_pb2.GatherProjectMembershipsForUserRequest(
+        user=self.user_1_resource_name)
+    mc = monorailcontext.MonorailContext(
+        self.services, cnxn=self.cnxn, requester=self.user_1.email)
+    response = self.CallWrapped(
+        self.frontend_svcr.GatherProjectMembershipsForUser, mc, request)
+
+    owner_membership = project_objects_pb2.ProjectMember(
+        name='projects/{}/members/{}'.format('owner_proj', '111'),
+        role=project_objects_pb2.ProjectMember.ProjectRole.Value('OWNER'))
+    committer_membership = project_objects_pb2.ProjectMember(
+        name='projects/{}/members/{}'.format('committer_proj', '111'),
+        role=project_objects_pb2.ProjectMember.ProjectRole.Value('COMMITTER'))
+    contributor_membership = project_objects_pb2.ProjectMember(
+        name='projects/{}/members/{}'.format('contributor_proj', '111'),
+        role=project_objects_pb2.ProjectMember.ProjectRole.Value('CONTRIBUTOR'))
+    self.assertEqual(
+        response,
+        frontend_pb2.GatherProjectMembershipsForUserResponse(
+            project_memberships=[
+                owner_membership, committer_membership, contributor_membership
+            ]))
diff --git a/api/v3/test/hotlists_servicer_test.py b/api/v3/test/hotlists_servicer_test.py
new file mode 100644
index 0000000..e9808b5
--- /dev/null
+++ b/api/v3/test/hotlists_servicer_test.py
@@ -0,0 +1,397 @@
+# Copyright 2020 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style
+# license that can be found in the LICENSE file or at
+# https://developers.google.com/open-source/licenses/bsd
+
+"""Tests for the hotlists servicer."""
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+
+import unittest
+
+from google.protobuf import empty_pb2
+from google.protobuf import field_mask_pb2
+
+from api import resource_name_converters as rnc
+from api.v3 import hotlists_servicer
+from api.v3 import converters
+from api.v3.api_proto import hotlists_pb2
+from api.v3.api_proto import feature_objects_pb2
+from api.v3.api_proto import issue_objects_pb2
+from api.v3.api_proto import user_objects_pb2
+from framework import exceptions
+from framework import monorailcontext
+from framework import permissions
+from features import features_constants
+from testing import fake
+from services import features_svc
+from services import service_manager
+
+
+class HotlistsServicerTest(unittest.TestCase):
+
+  def setUp(self):
+    self.cnxn = fake.MonorailConnection()
+    self.services = service_manager.Services(
+        features=fake.FeaturesService(),
+        issue=fake.IssueService(),
+        project=fake.ProjectService(),
+        config=fake.ConfigService(),
+        user=fake.UserService(),
+        usergroup=fake.UserGroupService())
+    self.hotlists_svcr = hotlists_servicer.HotlistsServicer(
+        self.services, make_rate_limiter=False)
+    self.converter = None
+    self.PAST_TIME = 12345
+    self.user_1 = self.services.user.TestAddUser('user_111@example.com', 111)
+    self.user_2 = self.services.user.TestAddUser('user_222@example.com', 222)
+    self.user_3 = self.services.user.TestAddUser('user_333@example.com', 333)
+
+    user_ids = [self.user_1.user_id, self.user_2.user_id, self.user_3.user_id]
+    self.user_ids_to_name = rnc.ConvertUserNames(user_ids)
+
+    self.project_1 = self.services.project.TestAddProject(
+        'proj', project_id=789)
+
+    self.issue_1 = fake.MakeTestIssue(
+        self.project_1.project_id, 1, 'sum', 'New', 111,
+        project_name=self.project_1.project_name)
+    self.issue_2 = fake.MakeTestIssue(
+        self.project_1.project_id, 2, 'sum', 'New', 111,
+        project_name=self.project_1.project_name)
+    self.issue_3 = fake.MakeTestIssue(
+        self.project_1.project_id, 3, 'sum', 'New', 111,
+        project_name=self.project_1.project_name)
+    self.issue_4 = fake.MakeTestIssue(
+        self.project_1.project_id, 4, 'sum', 'New', 111,
+        project_name=self.project_1.project_name)
+    self.issue_5 = fake.MakeTestIssue(
+        self.project_1.project_id, 5, 'sum', 'New', 111,
+        project_name=self.project_1.project_name)
+    self.issue_6 = fake.MakeTestIssue(
+        self.project_1.project_id, 6, 'sum', 'New', 111,
+        project_name=self.project_1.project_name)
+    self.services.issue.TestAddIssue(self.issue_1)
+    self.services.issue.TestAddIssue(self.issue_2)
+    self.services.issue.TestAddIssue(self.issue_3)
+    self.services.issue.TestAddIssue(self.issue_4)
+    self.services.issue.TestAddIssue(self.issue_5)
+    self.services.issue.TestAddIssue(self.issue_6)
+    issue_ids = [
+        self.issue_1.issue_id, self.issue_2.issue_id, self.issue_3.issue_id,
+        self.issue_4.issue_id, self.issue_5.issue_id, self.issue_6.issue_id
+    ]
+    self.issue_ids_to_name = rnc.ConvertIssueNames(
+        self.cnxn, issue_ids, self.services)
+
+    hotlist_items = [
+        (
+            self.issue_4.issue_id, 31, self.user_3.user_id, self.PAST_TIME,
+            'note5'),
+        (
+            self.issue_3.issue_id, 21, self.user_1.user_id, self.PAST_TIME,
+            'note1'),
+        (
+            self.issue_2.issue_id, 11, self.user_2.user_id, self.PAST_TIME,
+            'note2'),
+        (
+            self.issue_1.issue_id, 1, self.user_1.user_id, self.PAST_TIME,
+            'note4')
+    ]
+    self.hotlist_1 = self.services.features.TestAddHotlist(
+        'HotlistName',
+        summary='summary',
+        description='description',
+        owner_ids=[self.user_1.user_id],
+        editor_ids=[self.user_2.user_id],
+        hotlist_item_fields=hotlist_items,
+        default_col_spec='',
+        is_private=True)
+    self.hotlist_resource_name = rnc.ConvertHotlistName(
+        self.hotlist_1.hotlist_id)
+
+  def CallWrapped(self, wrapped_handler, mc, *args, **kwargs):
+    self.converter = converters.Converter(mc, self.services)
+    self.hotlists_svcr.converter = self.converter
+    return wrapped_handler.wrapped(self.hotlists_svcr, mc, *args, **kwargs)
+
+  # TODO(crbug/monorail/7104): Add page_token tests when implemented.
+  def testListHotlistItems(self):
+    """We can list a Hotlist's HotlistItems."""
+    request = hotlists_pb2.ListHotlistItemsRequest(
+        parent=self.hotlist_resource_name, page_size=2, order_by='note,stars')
+    mc = monorailcontext.MonorailContext(
+        self.services, cnxn=self.cnxn, requester=self.user_1.email)
+    mc.LookupLoggedInUserPerms(None)
+    response = self.CallWrapped(
+        self.hotlists_svcr.ListHotlistItems, mc, request)
+    expected_items = self.converter.ConvertHotlistItems(
+        self.hotlist_1.hotlist_id,
+        [self.hotlist_1.items[1], self.hotlist_1.items[2]])
+    self.assertEqual(
+        response, hotlists_pb2.ListHotlistItemsResponse(items=expected_items))
+
+  def testListHotlistItems_Empty(self):
+    """We can return a response if the Hotlist has no items"""
+    empty_hotlist = self.services.features.TestAddHotlist(
+        'Empty',
+        owner_ids=[self.user_1.user_id],
+        editor_ids=[self.user_2.user_id],
+        hotlist_item_fields=[])
+    hotlist_resource_name = rnc.ConvertHotlistName(empty_hotlist.hotlist_id)
+    request = hotlists_pb2.ListHotlistItemsRequest(parent=hotlist_resource_name)
+    mc = monorailcontext.MonorailContext(
+        self.services, cnxn=self.cnxn, requester=self.user_1.email)
+    mc.LookupLoggedInUserPerms(None)
+    response = self.CallWrapped(
+        self.hotlists_svcr.ListHotlistItems, mc, request)
+    self.assertEqual(response, hotlists_pb2.ListHotlistItemsResponse(items=[]))
+
+  def testListHotlistItems_InvalidPageSize(self):
+    """We raise an exception if `page_size` is negative."""
+    request = hotlists_pb2.ListHotlistItemsRequest(
+        parent=self.hotlist_resource_name, page_size=-1)
+    mc = monorailcontext.MonorailContext(
+        self.services, cnxn=self.cnxn, requester=self.user_1.email)
+    with self.assertRaises(exceptions.InputException):
+      self.CallWrapped(self.hotlists_svcr.ListHotlistItems, mc, request)
+
+  def testListHotlistItems_DefaultPageSize(self):
+    """We use our default page size when no `page_size` is given."""
+    request = hotlists_pb2.ListHotlistItemsRequest(
+        parent=self.hotlist_resource_name)
+    mc = monorailcontext.MonorailContext(
+        self.services, cnxn=self.cnxn, requester=self.user_1.email)
+    mc.LookupLoggedInUserPerms(None)
+    response = self.CallWrapped(
+        self.hotlists_svcr.ListHotlistItems, mc, request)
+    self.assertEqual(
+        len(response.items),
+        min(
+            features_constants.DEFAULT_RESULTS_PER_PAGE,
+            len(self.hotlist_1.items)))
+
+  def testRerankHotlistItems(self):
+    """We can rerank a Hotlist."""
+    item_names_dict = rnc.ConvertHotlistItemNames(
+        self.cnxn, self.hotlist_1.hotlist_id,
+        [item.issue_id for item in self.hotlist_1.items], self.services)
+    request = hotlists_pb2.RerankHotlistItemsRequest(
+        name=self.hotlist_resource_name,
+        hotlist_items=[
+            item_names_dict[self.issue_4.issue_id],
+            item_names_dict[self.issue_3.issue_id]
+        ],
+        target_position=0)
+
+    mc = monorailcontext.MonorailContext(
+        self.services, cnxn=self.cnxn, requester=self.user_1.email)
+    mc.LookupLoggedInUserPerms(None)
+    self.CallWrapped(self.hotlists_svcr.RerankHotlistItems, mc, request)
+    updated_hotlist = self.services.features.GetHotlist(
+        self.cnxn, self.hotlist_1.hotlist_id)
+    self.assertEqual(
+        [item.issue_id for item in updated_hotlist.items],
+        [self.issue_4.issue_id, self.issue_3.issue_id,
+         self.issue_1.issue_id, self.issue_2.issue_id])
+
+  def testRemoveHotlistItems(self):
+    """We can remove items from a Hotlist."""
+    issue_1_name = self.issue_ids_to_name[self.issue_1.issue_id]
+    issue_2_name = self.issue_ids_to_name[self.issue_2.issue_id]
+    request = hotlists_pb2.RemoveHotlistItemsRequest(
+        parent=self.hotlist_resource_name, issues=[issue_1_name, issue_2_name])
+
+    mc = monorailcontext.MonorailContext(
+        self.services, cnxn=self.cnxn, requester=self.user_1.email)
+    mc.LookupLoggedInUserPerms(None)
+    self.CallWrapped(self.hotlists_svcr.RemoveHotlistItems, mc, request)
+    updated_hotlist = self.services.features.GetHotlist(
+        self.cnxn, self.hotlist_1.hotlist_id)
+    # The hotlist used to have 4 items and we've removed two.
+    self.assertEqual(len(updated_hotlist.items), 2)
+
+  def testAddHotlistItems(self):
+    """We can add items to a Hotlist."""
+    issue_5_name = self.issue_ids_to_name[self.issue_5.issue_id]
+    issue_6_name = self.issue_ids_to_name[self.issue_6.issue_id]
+    request = hotlists_pb2.AddHotlistItemsRequest(
+        parent=self.hotlist_resource_name, issues=[issue_5_name, issue_6_name])
+
+    mc = monorailcontext.MonorailContext(
+        self.services, cnxn=self.cnxn, requester=self.user_1.email)
+    mc.LookupLoggedInUserPerms(None)
+    self.CallWrapped(self.hotlists_svcr.AddHotlistItems, mc, request)
+    updated_hotlist = self.services.features.GetHotlist(
+        self.cnxn, self.hotlist_1.hotlist_id)
+    # The hotlist used to have 4 items and we've added two.
+    self.assertEqual(len(updated_hotlist.items), 6)
+
+  def testRemoveHotlistEditors(self):
+    """We can remove editors from a Hotlist."""
+    user_2_name = self.user_ids_to_name[self.user_2.user_id]
+    request = hotlists_pb2.RemoveHotlistEditorsRequest(
+        name=self.hotlist_resource_name, editors=[user_2_name])
+
+    mc = monorailcontext.MonorailContext(
+        self.services, cnxn=self.cnxn, requester=self.user_1.email)
+    mc.LookupLoggedInUserPerms(None)
+    self.CallWrapped(self.hotlists_svcr.RemoveHotlistEditors, mc, request)
+    updated_hotlist = self.services.features.GetHotlist(
+        self.cnxn, self.hotlist_1.hotlist_id)
+    # User 2 was the only editor in the hotlist, and we removed them.
+    self.assertEqual(len(updated_hotlist.editor_ids), 0)
+
+  def testGetHotlist(self):
+    """We can get a Hotlist."""
+    request = hotlists_pb2.GetHotlistRequest(name=self.hotlist_resource_name)
+
+    mc = monorailcontext.MonorailContext(
+        self.services, cnxn=self.cnxn, requester=self.user_1.email)
+    mc.LookupLoggedInUserPerms(None)
+    api_hotlist = self.CallWrapped(self.hotlists_svcr.GetHotlist, mc, request)
+    self.assertEqual(api_hotlist, self.converter.ConvertHotlist(self.hotlist_1))
+
+  def testGatherHotlistsForUser(self):
+    """We can get all visible hotlists of a user."""
+    request = hotlists_pb2.GatherHotlistsForUserRequest(
+        user=self.user_ids_to_name[self.user_2.user_id])
+
+    mc = monorailcontext.MonorailContext(
+        self.services, cnxn=self.cnxn, requester=self.user_1.email)
+    mc.LookupLoggedInUserPerms(None)
+    response = self.CallWrapped(
+        self.hotlists_svcr.GatherHotlistsForUser, mc, request)
+
+    user_names_by_id = rnc.ConvertUserNames(
+        [self.user_2.user_id, self.user_1.user_id])
+    expected_api_hotlists = [
+        feature_objects_pb2.Hotlist(
+            name=self.hotlist_resource_name,
+            display_name='HotlistName',
+            summary='summary',
+            description='description',
+            hotlist_privacy=feature_objects_pb2.Hotlist.HotlistPrivacy.Value(
+                'PRIVATE'),
+            owner=user_names_by_id[self.user_1.user_id],
+            editors=[user_names_by_id[self.user_2.user_id]])
+    ]
+    self.assertEqual(
+        response,
+        hotlists_pb2.GatherHotlistsForUserResponse(
+            hotlists=expected_api_hotlists))
+
+  def testUpdateHotlist_AllFields(self):
+    """We can update a Hotlist."""
+    request = hotlists_pb2.UpdateHotlistRequest(
+        update_mask=field_mask_pb2.FieldMask(
+            paths=[
+                'summary',
+                'description',
+                'default_columns',
+                'hotlist_privacy',
+                'display_name',
+                'owner',
+                'editors',
+            ]),
+        hotlist=feature_objects_pb2.Hotlist(
+            name=self.hotlist_resource_name,
+            display_name='newName',
+            summary='new summary',
+            description='new description',
+            default_columns=[
+                issue_objects_pb2.IssuesListColumn(column='new-chicken-egg')
+            ],
+            hotlist_privacy=feature_objects_pb2.Hotlist.HotlistPrivacy.Value(
+                'PUBLIC'),
+            owner=self.user_ids_to_name[self.user_2.user_id],
+            editors=[self.user_ids_to_name[self.user_3.user_id]]))
+    mc = monorailcontext.MonorailContext(
+        self.services, cnxn=self.cnxn, requester=self.user_1.email)
+    mc.LookupLoggedInUserPerms(None)
+    api_hotlist = self.CallWrapped(
+        self.hotlists_svcr.UpdateHotlist, mc, request)
+    user_names_by_id = rnc.ConvertUserNames(
+        [self.user_3.user_id, self.user_2.user_id, self.user_1.user_id])
+    expected_hotlist = feature_objects_pb2.Hotlist(
+        name=self.hotlist_resource_name,
+        display_name='newName',
+        summary='new summary',
+        description='new description',
+        default_columns=[
+            issue_objects_pb2.IssuesListColumn(column='new-chicken-egg')
+        ],
+        hotlist_privacy=feature_objects_pb2.Hotlist.HotlistPrivacy.Value(
+            'PUBLIC'),
+        owner=user_names_by_id[self.user_2.user_id],
+        editors=[
+            user_names_by_id[self.user_2.user_id],
+            user_names_by_id[self.user_3.user_id]
+        ])
+    self.assertEqual(api_hotlist, expected_hotlist)
+
+  def testUpdateHotlist_OneField(self):
+    request = hotlists_pb2.UpdateHotlistRequest(
+        update_mask=field_mask_pb2.FieldMask(paths=['summary']),
+        hotlist=feature_objects_pb2.Hotlist(
+            name=self.hotlist_resource_name,
+            display_name='newName',
+            summary='new summary',
+            description='new description',
+            default_columns=[
+                issue_objects_pb2.IssuesListColumn(column='new-chicken-egg')
+            ],
+            hotlist_privacy=feature_objects_pb2.Hotlist.HotlistPrivacy.Value(
+                'PUBLIC')))
+    mc = monorailcontext.MonorailContext(
+        self.services, cnxn=self.cnxn, requester=self.user_1.email)
+    mc.LookupLoggedInUserPerms(None)
+    api_hotlist = self.CallWrapped(
+        self.hotlists_svcr.UpdateHotlist, mc, request)
+    user_names_by_id = rnc.ConvertUserNames(
+        [self.user_2.user_id, self.user_1.user_id])
+    expected_hotlist = feature_objects_pb2.Hotlist(
+        name=self.hotlist_resource_name,
+        display_name='HotlistName',
+        summary='new summary',
+        description='description',
+        default_columns=[],
+        hotlist_privacy=feature_objects_pb2.Hotlist.HotlistPrivacy.Value(
+            'PRIVATE'),
+        owner=user_names_by_id[self.user_1.user_id],
+        editors=[user_names_by_id[self.user_2.user_id]])
+    self.assertEqual(api_hotlist, expected_hotlist)
+
+  def testUpdateHotlist_EmptyFieldMask(self):
+    request = hotlists_pb2.UpdateHotlistRequest(
+        hotlist=feature_objects_pb2.Hotlist(summary='new'))
+    mc = monorailcontext.MonorailContext(
+        self.services, cnxn=self.cnxn, requester=self.user_1.email)
+    mc.LookupLoggedInUserPerms(None)
+    with self.assertRaises(exceptions.InputException):
+      self.CallWrapped(self.hotlists_svcr.UpdateHotlist, mc, request)
+
+  def testUpdateHotlist_EmptyHotlist(self):
+    request = hotlists_pb2.UpdateHotlistRequest(
+        update_mask=field_mask_pb2.FieldMask(paths=['summary']))
+    mc = monorailcontext.MonorailContext(
+        self.services, cnxn=self.cnxn, requester=self.user_1.email)
+    mc.LookupLoggedInUserPerms(None)
+    with self.assertRaises(exceptions.InputException):
+      self.CallWrapped(self.hotlists_svcr.UpdateHotlist, mc, request)
+
+  def testDeleteHotlist(self):
+    """We can delete a Hotlist."""
+    request = hotlists_pb2.GetHotlistRequest(name=self.hotlist_resource_name)
+
+    mc = monorailcontext.MonorailContext(
+        self.services, cnxn=self.cnxn, requester=self.user_1.email)
+    mc.LookupLoggedInUserPerms(None)
+    api_response = self.CallWrapped(
+        self.hotlists_svcr.DeleteHotlist, mc, request)
+    self.assertEqual(api_response, empty_pb2.Empty())
+
+    with self.assertRaises(features_svc.NoSuchHotlistException):
+      self.services.features.GetHotlist(
+          self.cnxn, self.hotlist_1.hotlist_id)
diff --git a/api/v3/test/issues_servicer_test.py b/api/v3/test/issues_servicer_test.py
new file mode 100644
index 0000000..7cfee41
--- /dev/null
+++ b/api/v3/test/issues_servicer_test.py
@@ -0,0 +1,890 @@
+# Copyright 2020 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style
+# license that can be found in the LICENSE file or at
+# https://developers.google.com/open-source/licenses/bsd
+
+"""Tests for the issues servicer."""
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+
+import copy
+import unittest
+import mock
+
+from api.v3 import converters
+from api.v3 import issues_servicer
+from api.v3.api_proto import issues_pb2
+from api.v3.api_proto import issue_objects_pb2
+from framework import exceptions
+from framework import framework_helpers
+from framework import monorailcontext
+from framework import permissions
+from proto import tracker_pb2
+from testing import fake
+from services import service_manager
+
+from google.appengine.ext import testbed
+from google.protobuf import timestamp_pb2
+from google.protobuf import field_mask_pb2
+
+
+def _Issue(project_id, local_id):
+  issue = tracker_pb2.Issue(owner_id=0)
+  issue.project_name = 'proj-%d' % project_id
+  issue.project_id = project_id
+  issue.local_id = local_id
+  issue.issue_id = project_id * 100 + local_id
+  return issue
+
+
+CURRENT_TIME = 12346.78
+
+
+class IssuesServicerTest(unittest.TestCase):
+
+  def setUp(self):
+    # memcache and datastore needed for generating page tokens.
+    self.testbed = testbed.Testbed()
+    self.testbed.activate()
+    self.testbed.init_memcache_stub()
+    self.testbed.init_datastore_v3_stub()
+
+    self.cnxn = fake.MonorailConnection()
+    self.services = service_manager.Services(
+        config=fake.ConfigService(),
+        issue=fake.IssueService(),
+        issue_star=fake.IssueStarService(),
+        project=fake.ProjectService(),
+        features=fake.FeaturesService(),
+        spam=fake.SpamService(),
+        user=fake.UserService(),
+        usergroup=fake.UserGroupService())
+    self.issues_svcr = issues_servicer.IssuesServicer(
+        self.services, make_rate_limiter=False)
+    self.PAST_TIME = int(CURRENT_TIME - 1)
+
+    self.owner = self.services.user.TestAddUser('owner@example.com', 111)
+    self.user_2 = self.services.user.TestAddUser('user_2@example.com', 222)
+
+    self.project_1 = self.services.project.TestAddProject(
+        'chicken', project_id=789)
+    self.issue_1_resource_name = 'projects/chicken/issues/1234'
+    self.issue_1 = fake.MakeTestIssue(
+        self.project_1.project_id,
+        1234,
+        'sum',
+        'New',
+        self.owner.user_id,
+        labels=['find-me', 'pri-3'],
+        project_name=self.project_1.project_name)
+    self.services.issue.TestAddIssue(self.issue_1)
+
+    self.project_2 = self.services.project.TestAddProject('cow', project_id=788)
+    self.issue_2_resource_name = 'projects/cow/issues/1235'
+    self.issue_2 = fake.MakeTestIssue(
+        self.project_2.project_id,
+        1235,
+        'sum',
+        'New',
+        self.user_2.user_id,
+        project_name=self.project_2.project_name)
+    self.services.issue.TestAddIssue(self.issue_2)
+    self.issue_3 = fake.MakeTestIssue(
+        self.project_2.project_id,
+        1236,
+        'sum',
+        'New',
+        self.user_2.user_id,
+        labels=['find-me', 'pri-1'],
+        project_name=self.project_2.project_name)
+    self.services.issue.TestAddIssue(self.issue_3)
+
+  def CallWrapped(self, wrapped_handler, mc, *args, **kwargs):
+    self.issues_svcr.converter = converters.Converter(mc, self.services)
+    return wrapped_handler.wrapped(self.issues_svcr, mc, *args, **kwargs)
+
+  def testGetIssue(self):
+    """We can get an issue."""
+    request = issues_pb2.GetIssueRequest(name=self.issue_1_resource_name)
+    mc = monorailcontext.MonorailContext(
+        self.services, cnxn=self.cnxn, requester=self.owner.email)
+    actual_response = self.CallWrapped(self.issues_svcr.GetIssue, mc, request)
+    self.assertEqual(
+        actual_response, self.issues_svcr.converter.ConvertIssue(self.issue_1))
+
+  def testBatchGetIssues(self):
+    """We can batch get issues."""
+    mc = monorailcontext.MonorailContext(
+        self.services, cnxn=self.cnxn, requester=self.owner.email)
+    request = issues_pb2.BatchGetIssuesRequest(
+        names=['projects/cow/issues/1235', 'projects/cow/issues/1236'])
+    actual_response = self.CallWrapped(
+        self.issues_svcr.BatchGetIssues, mc, request)
+    self.assertEqual(
+        [issue.name for issue in actual_response.issues],
+        ['projects/cow/issues/1235', 'projects/cow/issues/1236'])
+
+  def testBatchGetIssues_Empty(self):
+    """We can return a response if the request has no names."""
+    mc = monorailcontext.MonorailContext(
+        self.services, cnxn=self.cnxn, requester=self.owner.email)
+    request = issues_pb2.BatchGetIssuesRequest(names=[])
+    actual_response = self.CallWrapped(
+        self.issues_svcr.BatchGetIssues, mc, request)
+    self.assertEqual(
+        actual_response, issues_pb2.BatchGetIssuesResponse(issues=[]))
+
+  def testBatchGetIssues_WithParent(self):
+    """We can batch get issues with a given parent."""
+    mc = monorailcontext.MonorailContext(
+        self.services, cnxn=self.cnxn, requester=self.owner.email)
+    request = issues_pb2.BatchGetIssuesRequest(
+        parent='projects/cow',
+        names=['projects/cow/issues/1235', 'projects/cow/issues/1236'])
+    actual_response = self.CallWrapped(
+        self.issues_svcr.BatchGetIssues, mc, request)
+    self.assertEqual(
+        [issue.name for issue in actual_response.issues],
+        ['projects/cow/issues/1235', 'projects/cow/issues/1236'])
+
+  def testBatchGetIssues_FromMultipleProjects(self):
+    """We can batch get issues from multiple projects."""
+    mc = monorailcontext.MonorailContext(
+        self.services, cnxn=self.cnxn, requester=self.owner.email)
+    request = issues_pb2.BatchGetIssuesRequest(
+        names=[
+            'projects/chicken/issues/1234', 'projects/cow/issues/1235',
+            'projects/cow/issues/1236'
+        ])
+    actual_response = self.CallWrapped(
+        self.issues_svcr.BatchGetIssues, mc, request)
+    self.assertEqual(
+        [issue.name for issue in actual_response.issues], [
+            'projects/chicken/issues/1234', 'projects/cow/issues/1235',
+            'projects/cow/issues/1236'
+        ])
+
+  def testBatchGetIssues_WithBadInput(self):
+    """We raise an exception with bad input to batch get issues."""
+    mc = monorailcontext.MonorailContext(
+        self.services, cnxn=self.cnxn, requester=self.owner.email)
+    request = issues_pb2.BatchGetIssuesRequest(
+        parent='projects/cow',
+        names=['projects/cow/issues/1235', 'projects/chicken/issues/1234'])
+    with self.assertRaisesRegexp(
+        exceptions.InputException,
+        'projects/chicken/issues/1234 is not a child issue of projects/cow.'):
+      self.CallWrapped(self.issues_svcr.BatchGetIssues, mc, request)
+
+    request = issues_pb2.BatchGetIssuesRequest(
+        parent='projects/sheep',
+        names=['projects/cow/issues/1235', 'projects/chicken/issues/1234'])
+    with self.assertRaisesRegexp(
+        exceptions.InputException,
+        'projects/cow/issues/1235 is not a child issue of projects/sheep.\n' +
+        'projects/chicken/issues/1234 is not a child issue of projects/sheep.'):
+      self.CallWrapped(self.issues_svcr.BatchGetIssues, mc, request)
+
+    request = issues_pb2.BatchGetIssuesRequest(
+        parent='projects/cow',
+        names=['projects/cow/badformat/1235', 'projects/chicken/issues/1234'])
+    with self.assertRaisesRegexp(
+        exceptions.InputException,
+        'Invalid resource name: projects/cow/badformat/1235.'):
+      self.CallWrapped(self.issues_svcr.BatchGetIssues, mc, request)
+
+  def testBatchGetIssues_NonExistentIssues(self):
+    """We raise an exception with bad input to batch get issues."""
+    mc = monorailcontext.MonorailContext(
+        self.services, cnxn=self.cnxn, requester=self.owner.email)
+    request = issues_pb2.BatchGetIssuesRequest(
+        parent='projects/chicken',
+        names=['projects/chicken/issues/1', 'projects/chicken/issues/2'])
+    with self.assertRaisesRegexp(
+        exceptions.NoSuchIssueException,
+        "\['projects/chicken/issues/1', 'projects/chicken/issues/2'\] not found"
+    ):
+      self.CallWrapped(self.issues_svcr.BatchGetIssues, mc, request)
+
+  @mock.patch('api.v3.api_constants.MAX_BATCH_ISSUES', 2)
+  def testBatchGetIssues(self):
+    mc = monorailcontext.MonorailContext(
+        self.services, cnxn=self.cnxn, requester=self.owner.email)
+    request = issues_pb2.BatchGetIssuesRequest(
+        parent='projects/cow',
+        names=[
+            'projects/cow/issues/1235', 'projects/chicken/issues/1234',
+            'projects/cow/issues/1233'
+        ])
+    with self.assertRaises(exceptions.InputException):
+      self.CallWrapped(self.issues_svcr.BatchGetIssues, mc, request)
+
+  @mock.patch('search.frontendsearchpipeline.FrontendSearchPipeline')
+  @mock.patch('api.v3.api_constants.MAX_ISSUES_PER_PAGE', 2)
+  def testSearchIssues(self, mock_pipeline):
+    """We can search for issues in some projects."""
+    request = issues_pb2.SearchIssuesRequest(
+        projects=['projects/chicken', 'projects/cow'],
+        query='label:find-me',
+        order_by='-pri',
+        page_size=3)
+    mc = monorailcontext.MonorailContext(
+        self.services, cnxn=self.cnxn, requester=self.user_2.email)
+
+    instance = mock.Mock(
+        spec=True, total_count=3, visible_results=[self.issue_1, self.issue_3])
+    mock_pipeline.return_value = instance
+    instance.SearchForIIDs = mock.Mock()
+    instance.MergeAndSortIssues = mock.Mock()
+    instance.Paginate = mock.Mock()
+
+    actual_response = self.CallWrapped(
+        self.issues_svcr.SearchIssues, mc, request)
+    # start index is 0.
+    # number of items is coerced from 3 -> 2
+    mock_pipeline.assert_called_once_with(
+        self.cnxn,
+        self.services,
+        mc.auth, [222],
+        'label:find-me', ['chicken', 'cow'],
+        2,
+        0,
+        1,
+        '',
+        '-pri',
+        mc.warnings,
+        mc.errors,
+        True,
+        mc.profiler,
+        project=None)
+    self.assertEqual(
+        [issue.name for issue in actual_response.issues],
+        ['projects/chicken/issues/1234', 'projects/cow/issues/1236'])
+
+    # Check the `next_page_token` can be used to get the next page of results.
+    request.page_token = actual_response.next_page_token
+    self.CallWrapped(self.issues_svcr.SearchIssues, mc, request)
+    # start index is now 2.
+    mock_pipeline.assert_called_with(
+        self.cnxn,
+        self.services,
+        mc.auth, [222],
+        'label:find-me', ['chicken', 'cow'],
+        2,
+        2,
+        1,
+        '',
+        '-pri',
+        mc.warnings,
+        mc.errors,
+        True,
+        mc.profiler,
+        project=None)
+
+  @mock.patch('search.frontendsearchpipeline.FrontendSearchPipeline')
+  @mock.patch('api.v3.api_constants.MAX_ISSUES_PER_PAGE', 2)
+  def testSearchIssues_PaginationErrorOrderByChanged(self, mock_pipeline):
+    """Error when changing the order_by and using the same page_otoken."""
+    request = issues_pb2.SearchIssuesRequest(
+        projects=['projects/chicken', 'projects/cow'],
+        query='label:find-me',
+        order_by='-pri',
+        page_size=3)
+    mc = monorailcontext.MonorailContext(
+        self.services, cnxn=self.cnxn, requester=self.user_2.email)
+
+    instance = mock.Mock(
+        spec=True, total_count=3, visible_results=[self.issue_1, self.issue_3])
+    mock_pipeline.return_value = instance
+    instance.SearchForIIDs = mock.Mock()
+    instance.MergeAndSortIssues = mock.Mock()
+    instance.Paginate = mock.Mock()
+
+    actual_response = self.CallWrapped(
+        self.issues_svcr.SearchIssues, mc, request)
+
+    # The request should fail if we use `next_page_token` and change parameters.
+    request.page_token = actual_response.next_page_token
+    request.order_by = 'owner'
+    with self.assertRaises(exceptions.PageTokenException):
+      self.CallWrapped(self.issues_svcr.SearchIssues, mc, request)
+
+  # Note the 'empty' case doesn't make sense for ListComments, as one is created
+  # for every issue.
+  def testListComments(self):
+    comment_2 = tracker_pb2.IssueComment(
+        id=123,
+        issue_id=self.issue_1.issue_id,
+        project_id=self.issue_1.project_id,
+        user_id=self.owner.user_id,
+        content='comment 2')
+    self.services.issue.TestAddComment(comment_2, self.issue_1.local_id)
+    request = issues_pb2.ListCommentsRequest(
+        parent=self.issue_1_resource_name, page_size=1)
+    mc = monorailcontext.MonorailContext(
+        self.services, cnxn=self.cnxn, requester=self.owner.email)
+    actual_response = self.CallWrapped(
+        self.issues_svcr.ListComments, mc, request)
+    self.assertEqual(len(actual_response.comments), 1)
+
+    # Check the `next_page_token` can be used to get the next page of results
+    request.page_token = actual_response.next_page_token
+    next_actual_response = self.CallWrapped(
+        self.issues_svcr.ListComments, mc, request)
+    self.assertEqual(len(next_actual_response.comments), 1)
+    self.assertEqual(next_actual_response.comments[0].content, 'comment 2')
+
+  def testListComments_UnsupportedFilter(self):
+    """If anything other than approval is provided, it's an error."""
+    filter_str = 'content = "x"'
+    request = issues_pb2.ListCommentsRequest(
+        parent=self.issue_1_resource_name, page_size=1, filter=filter_str)
+    mc = monorailcontext.MonorailContext(
+        self.services, cnxn=self.cnxn, requester=self.owner.email)
+    with self.assertRaises(exceptions.InputException):
+      self.CallWrapped(self.issues_svcr.ListComments, mc, request)
+
+  def testListComments_TwoApprovalsErrors(self):
+    """If anything other than a single approval is provided, it's an error."""
+    filter_str = (
+        'approval = "projects/chicken/approvalDefs/404" OR '
+        'approval = "projects/chicken/approvalDefs/405')
+    request = issues_pb2.ListCommentsRequest(
+        parent=self.issue_1_resource_name, page_size=1, filter=filter_str)
+    mc = monorailcontext.MonorailContext(
+        self.services, cnxn=self.cnxn, requester=self.owner.email)
+    with self.assertRaises(exceptions.InputException):
+      self.CallWrapped(self.issues_svcr.ListComments, mc, request)
+
+  def testListComments_FilterTypoError(self):
+    """Even an extra space is an error."""
+    filter_str = 'approval = "projects/chicken/approvalDefs/404" '
+    request = issues_pb2.ListCommentsRequest(
+        parent=self.issue_1_resource_name, page_size=1, filter=filter_str)
+    mc = monorailcontext.MonorailContext(
+        self.services, cnxn=self.cnxn, requester=self.owner.email)
+    with self.assertRaises(exceptions.InputException):
+      self.CallWrapped(self.issues_svcr.ListComments, mc, request)
+
+  def testListComments_UnknownApprovalInFilter(self):
+    """Filter with unknown approval returns no error and no comments."""
+    approval_comment = tracker_pb2.IssueComment(
+        id=123,
+        issue_id=self.issue_1.issue_id,
+        project_id=self.issue_1.project_id,
+        user_id=self.owner.user_id,
+        content='comment 2 - approval 1',
+        approval_id=1)
+    self.services.issue.TestAddComment(approval_comment, self.issue_1.local_id)
+    request = issues_pb2.ListCommentsRequest(
+        parent=self.issue_1_resource_name, page_size=1,
+        filter='approval = "projects/chicken/approvalDefs/404"')
+    mc = monorailcontext.MonorailContext(
+        self.services, cnxn=self.cnxn, requester=self.owner.email)
+    response = self.CallWrapped(self.issues_svcr.ListComments, mc, request)
+    self.assertEqual(len(response.comments), 0)
+
+  def testListComments_ApprovalInFilter(self):
+    approval_comment = tracker_pb2.IssueComment(
+        id=123,
+        issue_id=self.issue_1.issue_id,
+        project_id=self.issue_1.project_id,
+        user_id=self.owner.user_id,
+        content='comment 2 - approval 1',
+        approval_id=1)
+    self.services.issue.TestAddComment(approval_comment, self.issue_1.local_id)
+    request = issues_pb2.ListCommentsRequest(
+        parent=self.issue_1_resource_name, page_size=1,
+        filter='approval = "projects/chicken/approvalDefs/1"')
+    mc = monorailcontext.MonorailContext(
+        self.services, cnxn=self.cnxn, requester=self.owner.email)
+    response = self.CallWrapped(self.issues_svcr.ListComments, mc, request)
+    self.assertEqual(len(response.comments), 1)
+    self.assertEqual(response.comments[0].content, approval_comment.content)
+
+  def testListApprovalValues(self):
+    config = fake.MakeTestConfig(self.project_2.project_id, [], [])
+    self.services.config.StoreConfig(self.cnxn, config)
+
+    # Make regular field def and value
+    fd_1 = fake.MakeTestFieldDef(
+        1, self.project_2.project_id, tracker_pb2.FieldTypes.STR_TYPE,
+        field_name='field1')
+    self.services.config.TestAddFieldDef(fd_1)
+    fv_1 = fake.MakeFieldValue(
+        field_id=fd_1.field_id, str_value='value1', derived=False)
+
+    # Make testing approval def and its associated field def
+    approval_gate = fake.MakeTestFieldDef(
+        2, self.project_2.project_id, tracker_pb2.FieldTypes.APPROVAL_TYPE,
+        field_name='approval-gate-1')
+    self.services.config.TestAddFieldDef(approval_gate)
+    ad = fake.MakeTestApprovalDef(2, approver_ids=[self.user_2.user_id])
+    self.services.config.TestAddApprovalDef(ad, self.project_2.project_id)
+
+    # Make approval value
+    av = fake.MakeApprovalValue(2, set_on=self.PAST_TIME,
+          approver_ids=[self.user_2.user_id], setter_id=self.user_2.user_id)
+
+    # Make field def that belongs to above approval_def
+    fd_2 = fake.MakeTestFieldDef(
+        3, self.project_2.project_id, tracker_pb2.FieldTypes.STR_TYPE,
+        field_name='field2', approval_id=2)
+    self.services.config.TestAddFieldDef(fd_2)
+    fv_2 = fake.MakeFieldValue(
+        field_id=fd_2.field_id, str_value='value2', derived=False)
+
+    issue_resource_name = 'projects/cow/issues/1237'
+    issue = fake.MakeTestIssue(
+        self.project_2.project_id,
+        1237,
+        'sum',
+        'New',
+        self.user_2.user_id,
+        project_name=self.project_2.project_name,
+        field_values=[fv_1, fv_2],
+        approval_values=[av])
+    self.services.issue.TestAddIssue(issue)
+
+    request = issues_pb2.ListApprovalValuesRequest(parent=issue_resource_name)
+    mc = monorailcontext.MonorailContext(
+        self.services, cnxn=self.cnxn, requester=self.owner.email)
+    actual_response = self.CallWrapped(
+        self.issues_svcr.ListApprovalValues, mc, request)
+
+    self.assertEqual(len(actual_response.approval_values), 1)
+    expected_fv = issue_objects_pb2.FieldValue(
+        field='projects/cow/fieldDefs/3',
+        value='value2',
+        derivation=issue_objects_pb2.Derivation.Value('EXPLICIT'))
+    expected = issue_objects_pb2.ApprovalValue(
+        name='projects/cow/issues/1237/approvalValues/2',
+        status=issue_objects_pb2.ApprovalValue.ApprovalStatus.Value('NOT_SET'),
+        approvers=['users/222'],
+        approval_def='projects/cow/approvalDefs/2',
+        set_time=timestamp_pb2.Timestamp(seconds=self.PAST_TIME),
+        setter='users/222',
+        field_values=[expected_fv])
+    self.assertEqual(actual_response.approval_values[0], expected)
+
+  def testListApprovalValues_Empty(self):
+    request = issues_pb2.ListApprovalValuesRequest(
+        parent=self.issue_1_resource_name)
+    mc = monorailcontext.MonorailContext(
+        self.services, cnxn=self.cnxn, requester=self.owner.email)
+    actual_response = self.CallWrapped(
+        self.issues_svcr.ListApprovalValues, mc, request)
+    self.assertEqual(len(actual_response.approval_values), 0)
+
+  @mock.patch(
+      'features.send_notifications.PrepareAndSendIssueChangeNotification')
+  def testMakeIssue(self, _fake_pasicn):
+    request_issue = issue_objects_pb2.Issue(
+        summary='sum',
+        status=issue_objects_pb2.Issue.StatusValue(status='New'),
+        cc_users=[issue_objects_pb2.Issue.UserValue(user='users/222')],
+        labels=[issue_objects_pb2.Issue.LabelValue(label='foo-bar')]
+    )
+    request = issues_pb2.MakeIssueRequest(
+        parent='projects/chicken',
+        issue=request_issue,
+        description='description'
+    )
+    mc = monorailcontext.MonorailContext(
+        self.services, cnxn=self.cnxn, requester=self.owner.email)
+    response = self.CallWrapped(
+        self.issues_svcr.MakeIssue, mc, request)
+    self.assertEqual(response.summary, 'sum')
+    self.assertEqual(response.status.status, 'New')
+    self.assertEqual(response.cc_users[0].user, 'users/222')
+    self.assertEqual(response.labels[0].label, 'foo-bar')
+    self.assertEqual(response.star_count, 1)
+
+  @mock.patch(
+      'features.send_notifications.PrepareAndSendIssueChangeNotification')
+  @mock.patch('time.time')
+  def testModifyIssues(self, fake_time, fake_notify):
+    fake_time.return_value = 12345
+
+    issue = _Issue(780, 1)
+    self.services.project.TestAddProject(
+        issue.project_name, project_id=issue.project_id,
+        owner_ids=[self.owner.user_id])
+
+    issue.labels = ['keep-me', 'remove-me']
+    self.services.issue.TestAddIssue(issue)
+    exp_issue = copy.deepcopy(issue)
+
+    self.services.issue.CreateIssueComment = mock.Mock()
+    mc = monorailcontext.MonorailContext(
+        self.services, cnxn=self.cnxn, requester=self.owner.email)
+
+    request = issues_pb2.ModifyIssuesRequest(
+        deltas=[
+            issues_pb2.IssueDelta(
+                issue=issue_objects_pb2.Issue(
+                    name='projects/proj-780/issues/1',
+                    labels=[issue_objects_pb2.Issue.LabelValue(
+                        label='add-me')]),
+                update_mask=field_mask_pb2.FieldMask(paths=['labels']),
+                labels_remove=['remove-me'])],
+        uploads=[issues_pb2.AttachmentUpload(
+            filename='mowgli.gif', content='cute dog')],
+        comment_content='Release the chicken.',
+        notify_type=issues_pb2.NotifyType.Value('NO_NOTIFICATION'))
+
+    response = self.CallWrapped(
+        self.issues_svcr.ModifyIssues, mc, request)
+    exp_issue.labels = ['keep-me', 'add-me']
+    exp_issue.modified_timestamp = 12345
+    exp_api_issue = self.issues_svcr.converter.ConvertIssue(exp_issue)
+    self.assertEqual([iss for iss in response.issues], [exp_api_issue])
+
+    # All updated issues should have been fetched from DB, skipping cache.
+    # So we expect assume_stale=False was applied to all issues during the
+    # the fetch.
+    exp_issue.assume_stale = False
+    # These derived values get set to the following when an issue goes through
+    # the ApplyFilterRules path. (see filter_helpers._ComputeDerivedFields)
+    exp_issue.derived_owner_id = 0
+    exp_issue.derived_status = ''
+    exp_attachments = [framework_helpers.AttachmentUpload(
+        'mowgli.gif', 'cute dog', 'image/gif')]
+    exp_amendments = [tracker_pb2.Amendment(
+        field=tracker_pb2.FieldID.LABELS, newvalue='-remove-me add-me')]
+    self.services.issue.CreateIssueComment.assert_called_once_with(
+        self.cnxn, exp_issue, mc.auth.user_id, 'Release the chicken.',
+        attachments=exp_attachments, amendments=exp_amendments, commit=False)
+    fake_notify.assert_called_once_with(
+        issue.issue_id, 'testing-app.appspot.com', self.owner.user_id,
+        comment_id=mock.ANY, old_owner_id=None, send_email=False)
+
+  def testModifyIssues_Empty(self):
+    mc = monorailcontext.MonorailContext(
+        self.services, cnxn=self.cnxn, requester=self.owner.email)
+    request = issues_pb2.ModifyIssuesRequest()
+    response = self.CallWrapped(self.issues_svcr.ModifyIssues, mc, request)
+    self.assertEqual(response, issues_pb2.ModifyIssuesResponse())
+
+  @mock.patch('api.v3.api_constants.MAX_MODIFY_ISSUES', 2)
+  @mock.patch('api.v3.api_constants.MAX_MODIFY_IMPACTED_ISSUES', 4)
+  def testModifyIssues_TooMany(self):
+    mc = monorailcontext.MonorailContext(
+        self.services, cnxn=self.cnxn, requester=self.owner.email)
+    request = issues_pb2.ModifyIssuesRequest(
+        deltas=[
+            issues_pb2.IssueDelta(),
+            issues_pb2.IssueDelta(),
+            issues_pb2.IssueDelta()
+        ])
+    with self.assertRaisesRegexp(
+        exceptions.InputException,
+        'Requesting 3 updates when the allowed maximum is 2 updates.'):
+      self.CallWrapped(self.issues_svcr.ModifyIssues, mc, request)
+
+    issue_ref_list = [issue_objects_pb2.IssueRef()]
+    request = issues_pb2.ModifyIssuesRequest(
+        deltas=[
+            issues_pb2.IssueDelta(
+                issue=issue_objects_pb2.Issue(
+                    blocked_on_issue_refs=issue_ref_list),
+                blocked_on_issues_remove=issue_ref_list,
+                update_mask=field_mask_pb2.FieldMask(
+                    paths=['merged_into_issue_ref'])),
+            issues_pb2.IssueDelta(
+                issue=issue_objects_pb2.Issue(
+                    blocking_issue_refs=issue_ref_list),
+                blocking_issues_remove=issue_ref_list)
+        ])
+    with self.assertRaisesRegexp(
+        exceptions.InputException,
+        'Updates include 5 impacted issues when the allowed maximum is 4.'):
+      self.CallWrapped(self.issues_svcr.ModifyIssues, mc, request)
+
+  @mock.patch('time.time', mock.MagicMock(return_value=CURRENT_TIME))
+  @mock.patch(
+      'features.send_notifications.PrepareAndSendApprovalChangeNotification')
+  def testModifyIssueApprovalValues(self, fake_notify):
+    self.services.issue.DeltaUpdateIssueApproval = mock.Mock()
+    config = fake.MakeTestConfig(self.project_1.project_id, [], [])
+    self.services.config.StoreConfig(self.cnxn, config)
+
+    # Make testing approval def and its associated field def
+    field_id = 2
+    approval_field_def = fake.MakeTestFieldDef(
+        field_id,
+        self.project_1.project_id,
+        tracker_pb2.FieldTypes.APPROVAL_TYPE,
+        field_name='approval-gate-1')
+    self.services.config.TestAddFieldDef(approval_field_def)
+    ad = fake.MakeTestApprovalDef(field_id, approver_ids=[self.owner.user_id])
+    self.services.config.TestAddApprovalDef(ad, self.project_1.project_id)
+
+    # Make approval value
+    av = fake.MakeApprovalValue(
+        field_id,
+        status=tracker_pb2.ApprovalStatus.NEEDS_REVIEW,
+        set_on=self.PAST_TIME,
+        approver_ids=[self.owner.user_id],
+        setter_id=self.user_2.user_id)
+
+    issue = fake.MakeTestIssue(
+        self.project_1.project_id,
+        1237,
+        'sum',
+        'New',
+        self.owner.user_id,
+        project_name=self.project_1.project_name,
+        approval_values=[av])
+    self.services.issue.TestAddIssue(issue)
+
+    av_name = 'projects/%s/issues/%d/approvalValues/%d' % (
+        self.project_1.project_name, issue.local_id, ad.approval_id)
+    delta = issues_pb2.ApprovalDelta(
+        approval_value=issue_objects_pb2.ApprovalValue(
+            name=av_name,
+            status=issue_objects_pb2.ApprovalValue.ApprovalStatus.Value('NA')),
+        update_mask=field_mask_pb2.FieldMask(paths=['status']))
+
+    request = issues_pb2.ModifyIssueApprovalValuesRequest(deltas=[delta],)
+    mc = monorailcontext.MonorailContext(
+        self.services, cnxn=self.cnxn, requester=self.owner.email)
+    response = self.CallWrapped(
+        self.issues_svcr.ModifyIssueApprovalValues, mc, request)
+    expected_ingested_delta = tracker_pb2.ApprovalDelta(
+        status=tracker_pb2.ApprovalStatus.NA,
+        set_on=int(CURRENT_TIME),
+        setter_id=self.owner.user_id,
+    )
+    # NOTE: Because we mock out DeltaUpdateIssueApproval, the ApprovalValues
+    # returned haven't been changed in this test. We can't test that it was
+    # changed correctly, but we can make sure it's for the right ApprovalValue.
+    self.assertEqual(len(response.approval_values), 1)
+    self.assertEqual(response.approval_values[0].name, av_name)
+    self.services.issue.DeltaUpdateIssueApproval.assert_called_once_with(
+        mc.cnxn,
+        self.owner.user_id,
+        config,
+        issue,
+        av,
+        expected_ingested_delta,
+        comment_content=u'',
+        is_description=False,
+        attachments=None,
+        kept_attachments=None)
+    fake_notify.assert_called_once_with(
+        issue.issue_id,
+        ad.approval_id,
+        'testing-app.appspot.com',
+        mock.ANY,
+        send_email=True)
+
+  @mock.patch('api.v3.api_constants.MAX_MODIFY_APPROVAL_VALUES', 2)
+  def testModifyIssueApprovalValues_TooMany(self):
+    mc = monorailcontext.MonorailContext(
+        self.services, cnxn=self.cnxn, requester=self.owner.email)
+    request = issues_pb2.ModifyIssueApprovalValuesRequest(
+        deltas=[
+            issues_pb2.ApprovalDelta(),
+            issues_pb2.ApprovalDelta(),
+            issues_pb2.ApprovalDelta()
+        ])
+    with self.assertRaises(exceptions.InputException):
+      self.CallWrapped(self.issues_svcr.ModifyIssueApprovalValues, mc, request)
+
+  def testModifyIssueApprovalValues_Empty(self):
+    request = issues_pb2.ModifyIssueApprovalValuesRequest()
+    mc = monorailcontext.MonorailContext(
+        self.services, cnxn=self.cnxn, requester=self.owner.email)
+    response = self.CallWrapped(
+        self.issues_svcr.ModifyIssueApprovalValues, mc, request)
+    self.assertEqual(len(response.approval_values), 0)
+
+  @mock.patch(
+      'businesslogic.work_env.WorkEnv.GetIssue',
+      return_value=tracker_pb2.Issue(
+          owner_id=0,
+          project_name='chicken',
+          project_id=789,
+          local_id=1234,
+          issue_id=80134))
+  def testModifyCommentState(self, mocked_get_issue):
+    name = self.issue_1_resource_name + '/comments/1'
+    state = issue_objects_pb2.IssueContentState.Value('DELETED')
+    request = issues_pb2.ModifyCommentStateRequest(name=name, state=state)
+    mc = monorailcontext.MonorailContext(
+        self.services, cnxn=self.cnxn, requester=self.owner.email)
+    with self.assertRaises(exceptions.NoSuchCommentException):
+      self.CallWrapped(self.issues_svcr.ModifyCommentState, mc, request)
+    mocked_get_issue.assert_any_call(self.issue_1.issue_id, use_cache=False)
+
+  def testModifyCommentState_Delete(self):
+    comment_1 = tracker_pb2.IssueComment(
+        id=124,
+        issue_id=self.issue_1.issue_id,
+        project_id=self.issue_1.project_id,
+        user_id=self.owner.user_id,
+        content='first actual comment')
+    self.services.issue.TestAddComment(comment_1, self.issue_1.local_id)
+
+    name = self.issue_1_resource_name + '/comments/1'
+    state = issue_objects_pb2.IssueContentState.Value('DELETED')
+    request = issues_pb2.ModifyCommentStateRequest(name=name, state=state)
+    mc = monorailcontext.MonorailContext(
+        self.services, cnxn=self.cnxn, requester=self.owner.email)
+    response = self.CallWrapped(
+        self.issues_svcr.ModifyCommentState, mc, request)
+    self.assertEqual(response.comment.state, state)
+    self.assertEqual(response.comment.content, 'first actual comment')
+
+    # Test noop
+    response = self.CallWrapped(
+        self.issues_svcr.ModifyCommentState, mc, request)
+    self.assertEqual(response.comment.state, state)
+
+    # Test undelete
+    state = issue_objects_pb2.IssueContentState.Value('ACTIVE')
+    request = issues_pb2.ModifyCommentStateRequest(name=name, state=state)
+    mc = monorailcontext.MonorailContext(
+        self.services, cnxn=self.cnxn, requester=self.owner.email)
+    response = self.CallWrapped(
+        self.issues_svcr.ModifyCommentState, mc, request)
+    self.assertEqual(response.comment.state, state)
+
+  @mock.patch(
+      'framework.permissions.UpdateIssuePermissions',
+      return_value=permissions.ADMIN_PERMISSIONSET)
+  def testModifyCommentState_Spam(self, _mocked):
+    comment_1 = tracker_pb2.IssueComment(
+        id=124,
+        issue_id=self.issue_1.issue_id,
+        project_id=self.issue_1.project_id,
+        user_id=self.owner.user_id,
+        content='first actual comment')
+    self.services.issue.TestAddComment(comment_1, self.issue_1.local_id)
+
+    name = self.issue_1_resource_name + '/comments/1'
+    state = issue_objects_pb2.IssueContentState.Value('SPAM')
+    request = issues_pb2.ModifyCommentStateRequest(name=name, state=state)
+    mc = monorailcontext.MonorailContext(
+        self.services, cnxn=self.cnxn, requester=self.owner.email)
+    response = self.CallWrapped(
+        self.issues_svcr.ModifyCommentState, mc, request)
+    self.assertEqual(response.comment.state, state)
+
+    # Test noop
+    response = self.CallWrapped(
+        self.issues_svcr.ModifyCommentState, mc, request)
+    self.assertEqual(response.comment.state, state)
+
+    # Test unflag as spam
+    state = issue_objects_pb2.IssueContentState.Value('ACTIVE')
+    request = issues_pb2.ModifyCommentStateRequest(name=name, state=state)
+    mc = monorailcontext.MonorailContext(
+        self.services, cnxn=self.cnxn, requester=self.owner.email)
+    response = self.CallWrapped(
+        self.issues_svcr.ModifyCommentState, mc, request)
+    self.assertEqual(response.comment.state, state)
+
+  def testModifyCommentState_Active(self):
+    comment_1 = tracker_pb2.IssueComment(
+        id=124,
+        issue_id=self.issue_1.issue_id,
+        project_id=self.issue_1.project_id,
+        user_id=self.owner.user_id,
+        content='first actual comment')
+    self.services.issue.TestAddComment(comment_1, self.issue_1.local_id)
+
+    name = self.issue_1_resource_name + '/comments/1'
+    state = issue_objects_pb2.IssueContentState.Value('ACTIVE')
+    request = issues_pb2.ModifyCommentStateRequest(name=name, state=state)
+    mc = monorailcontext.MonorailContext(
+        self.services, cnxn=self.cnxn, requester=self.owner.email)
+    response = self.CallWrapped(
+        self.issues_svcr.ModifyCommentState, mc, request)
+    self.assertEqual(response.comment.state, state)
+
+  def testModifyCommentState_Spam_ActionNotSupported(self):
+    # Cannot transition from deleted to spam
+    comment_1 = tracker_pb2.IssueComment(
+        id=124,
+        issue_id=self.issue_1.issue_id,
+        project_id=self.issue_1.project_id,
+        user_id=self.owner.user_id,
+        content='first actual comment',
+        deleted_by=self.owner.user_id)
+    self.services.issue.TestAddComment(comment_1, self.issue_1.local_id)
+
+    name = self.issue_1_resource_name + '/comments/1'
+    state = issue_objects_pb2.IssueContentState.Value('SPAM')
+    request = issues_pb2.ModifyCommentStateRequest(name=name, state=state)
+    mc = monorailcontext.MonorailContext(
+        self.services, cnxn=self.cnxn, requester=self.owner.email)
+    with self.assertRaises(exceptions.ActionNotSupported):
+      self.CallWrapped(self.issues_svcr.ModifyCommentState, mc, request)
+
+  def testModifyCommentState_Delete_ActionNotSupported(self):
+    # Cannot transition from spam to deleted
+    comment_1 = tracker_pb2.IssueComment(
+        id=124,
+        issue_id=self.issue_1.issue_id,
+        project_id=self.issue_1.project_id,
+        user_id=self.owner.user_id,
+        content='first actual comment',
+        is_spam=True)
+    self.services.issue.TestAddComment(comment_1, self.issue_1.local_id)
+
+    name = self.issue_1_resource_name + '/comments/1'
+    state = issue_objects_pb2.IssueContentState.Value('DELETED')
+    request = issues_pb2.ModifyCommentStateRequest(name=name, state=state)
+    mc = monorailcontext.MonorailContext(
+        self.services, cnxn=self.cnxn, requester=self.owner.email)
+    with self.assertRaises(exceptions.ActionNotSupported):
+      self.CallWrapped(self.issues_svcr.ModifyCommentState, mc, request)
+
+  def testModifyCommentState_NoSuchComment(self):
+    name = self.issue_1_resource_name + '/comments/1'
+    state = issue_objects_pb2.IssueContentState.Value('DELETED')
+    request = issues_pb2.ModifyCommentStateRequest(name=name, state=state)
+    mc = monorailcontext.MonorailContext(
+        self.services, cnxn=self.cnxn, requester=self.owner.email)
+    with self.assertRaises(exceptions.NoSuchCommentException):
+      self.CallWrapped(self.issues_svcr.ModifyCommentState, mc, request)
+
+  def testModifyCommentState_Delete_PermissionException(self):
+    comment_1 = tracker_pb2.IssueComment(
+        id=124,
+        issue_id=self.issue_1.issue_id,
+        project_id=self.issue_1.project_id,
+        user_id=self.owner.user_id,
+        content='first actual comment')
+    self.services.issue.TestAddComment(comment_1, self.issue_1.local_id)
+
+    name = self.issue_1_resource_name + '/comments/1'
+    state = issue_objects_pb2.IssueContentState.Value('DELETED')
+    request = issues_pb2.ModifyCommentStateRequest(name=name, state=state)
+    mc = monorailcontext.MonorailContext(
+        self.services, cnxn=self.cnxn, requester=self.user_2.email)
+    with self.assertRaises(permissions.PermissionException):
+      self.CallWrapped(self.issues_svcr.ModifyCommentState, mc, request)
+
+  @mock.patch(
+      'framework.permissions.UpdateIssuePermissions',
+      return_value=permissions.READ_ONLY_PERMISSIONSET)
+  def testModifyCommentState_Spam_PermissionException(self, _mocked):
+    comment_1 = tracker_pb2.IssueComment(
+        id=124,
+        issue_id=self.issue_1.issue_id,
+        project_id=self.issue_1.project_id,
+        user_id=self.owner.user_id,
+        content='first actual comment')
+    self.services.issue.TestAddComment(comment_1, self.issue_1.local_id)
+
+    name = self.issue_1_resource_name + '/comments/1'
+    state = issue_objects_pb2.IssueContentState.Value('SPAM')
+    request = issues_pb2.ModifyCommentStateRequest(name=name, state=state)
+    mc = monorailcontext.MonorailContext(
+        self.services, cnxn=self.cnxn, requester=self.user_2.email)
+    with self.assertRaises(permissions.PermissionException):
+      self.CallWrapped(self.issues_svcr.ModifyCommentState, mc, request)
diff --git a/api/v3/test/monorail_servicer_test.py b/api/v3/test/monorail_servicer_test.py
new file mode 100644
index 0000000..3569879
--- /dev/null
+++ b/api/v3/test/monorail_servicer_test.py
@@ -0,0 +1,534 @@
+# Copyright 2018 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style
+# license that can be found in the LICENSE file or at
+# https://developers.google.com/open-source/licenses/bsd
+
+"""Tests for MonorailServicer."""
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+
+import time
+import unittest
+import mock
+import mox
+
+from components.prpc import server
+from components.prpc import codes
+from components.prpc import context
+from google.appengine.ext import testbed
+from google.protobuf import json_format
+
+import settings
+from api.v3 import monorail_servicer
+from framework import authdata
+from framework import exceptions
+from framework import framework_constants
+from framework import monorailcontext
+from framework import permissions
+from framework import ratelimiter
+from framework import xsrf
+from services import cachemanager_svc
+from services import config_svc
+from services import service_manager
+from services import features_svc
+from testing import fake
+from testing import testing_helpers
+
+
+class MonorailServicerFunctionsTest(unittest.TestCase):
+
+  def testConvertPRPCStatusToHTTPStatus(self):
+    """We can convert pRPC status codes to http codes for monitoring."""
+    prpc_context = context.ServicerContext()
+
+    prpc_context.set_code(codes.StatusCode.OK)
+    self.assertEqual(
+        200, monorail_servicer.ConvertPRPCStatusToHTTPStatus(prpc_context))
+
+    prpc_context.set_code(codes.StatusCode.INVALID_ARGUMENT)
+    self.assertEqual(
+        400, monorail_servicer.ConvertPRPCStatusToHTTPStatus(prpc_context))
+
+    prpc_context.set_code(codes.StatusCode.PERMISSION_DENIED)
+    self.assertEqual(
+        403, monorail_servicer.ConvertPRPCStatusToHTTPStatus(prpc_context))
+
+    prpc_context.set_code(codes.StatusCode.NOT_FOUND)
+    self.assertEqual(
+        404, monorail_servicer.ConvertPRPCStatusToHTTPStatus(prpc_context))
+
+    prpc_context.set_code(codes.StatusCode.INTERNAL)
+    self.assertEqual(
+        500, monorail_servicer.ConvertPRPCStatusToHTTPStatus(prpc_context))
+
+
+class UpdateSomethingRequest(testing_helpers.Blank):
+  """A fake request that would do a write."""
+  pass
+
+
+class ListSomethingRequest(testing_helpers.Blank):
+  """A fake request that would do a read."""
+  pass
+
+
+class TestableServicer(monorail_servicer.MonorailServicer):
+  """Fake servicer class."""
+
+  def __init__(self, services):
+    super(TestableServicer, self).__init__(services)
+    self.was_called = False
+    self.seen_mc = None
+    self.seen_request = None
+
+  @monorail_servicer.PRPCMethod
+  def CalcSomething(self, mc, request):
+    """Raise the test exception, or return what we got for verification."""
+    self.was_called = True
+    self.seen_mc = mc
+    self.seen_request = request
+    assert mc
+    assert request
+    if request.exc_class:
+      raise request.exc_class()
+    else:
+      return 'fake response proto'
+
+
+class MonorailServicerTest(unittest.TestCase):
+
+  def setUp(self):
+    self.mox = mox.Mox()
+    self.testbed = testbed.Testbed()
+    self.testbed.activate()
+    self.testbed.init_memcache_stub()
+    self.testbed.init_datastore_v3_stub()
+    self.testbed.init_user_stub()
+
+    self.cnxn = fake.MonorailConnection()
+    self.services = service_manager.Services(
+        user=fake.UserService(),
+        usergroup=fake.UserGroupService(),
+        project=fake.ProjectService(),
+        cache_manager=fake.CacheManager())
+    self.project = self.services.project.TestAddProject(
+        'proj', project_id=789, owner_ids=[111])
+    # Allowlisted in testing/api_clients.cfg
+    self.allowlisted_client_id = '98723764876'
+    self.non_member = self.services.user.TestAddUser(
+        'nonmember@example.com', 222)
+    self.test_user = self.services.user.TestAddUser('test@example.com', 420)
+    self.svcr = TestableServicer(self.services)
+    self.nonmember_token = xsrf.GenerateToken(222, xsrf.XHR_SERVLET_PATH)
+    self.request = UpdateSomethingRequest(exc_class=None)
+    self.prpc_context = context.ServicerContext()
+    self.prpc_context.set_code(codes.StatusCode.OK)
+    self.prpc_context._invocation_metadata = [
+        (monorail_servicer.XSRF_TOKEN_HEADER, self.nonmember_token)]
+    # This string is returned by app_identity.get_application_id() when
+    # called in the test env.
+    self.app_id = 'testing-app'
+
+  def tearDown(self):
+    self.mox.UnsetStubs()
+    self.mox.ResetAll()
+    self.testbed.deactivate()
+
+  def SetUpRecordMonitoringStats(self):
+    self.mox.StubOutWithMock(json_format, 'MessageToJson')
+    json_format.MessageToJson(self.request).AndReturn('json of request')
+    json_format.MessageToJson('fake response proto').AndReturn(
+        'json of response')
+    self.mox.ReplayAll()
+
+  def testRun_SiteWide_Normal(self):
+    """Calling the handler through the decorator."""
+    self.testbed.setup_env(user_email=self.non_member.email, overwrite=True)
+    self.SetUpRecordMonitoringStats()
+    # pylint: disable=unexpected-keyword-arg
+    response = self.svcr.CalcSomething(
+        self.request, self.prpc_context, cnxn=self.cnxn)
+    self.assertIsNone(self.svcr.seen_mc.cnxn)  # Because of CleanUp().
+    self.assertEqual(self.svcr.seen_mc.auth.email, self.non_member.email)
+    self.assertIn(permissions.CREATE_HOTLIST.lower(),
+                  self.svcr.seen_mc.perms.perm_names)
+    self.assertNotIn(permissions.ADMINISTER_SITE.lower(),
+                     self.svcr.seen_mc.perms.perm_names)
+    self.assertEqual(self.request, self.svcr.seen_request)
+    self.assertEqual('fake response proto', response)
+    self.assertEqual(codes.StatusCode.OK, self.prpc_context._code)
+
+  def testRun_RequesterBanned(self):
+    """If we reject the request, give PERMISSION_DENIED."""
+    self.non_member.banned = 'Spammer'
+    self.testbed.setup_env(user_email=self.non_member.email, overwrite=True)
+    self.SetUpRecordMonitoringStats()
+    # pylint: disable=unexpected-keyword-arg
+    self.svcr.CalcSomething(
+        self.request, self.prpc_context, cnxn=self.cnxn)
+    self.assertFalse(self.svcr.was_called)
+    self.assertEqual(
+        codes.StatusCode.PERMISSION_DENIED, self.prpc_context._code)
+
+  def testRun_AnonymousRequester(self):
+    """Test we properly process anonymous users with valid tokens."""
+    self.prpc_context._invocation_metadata = [
+        (monorail_servicer.XSRF_TOKEN_HEADER,
+         xsrf.GenerateToken(0, xsrf.XHR_SERVLET_PATH))]
+    self.SetUpRecordMonitoringStats()
+    # pylint: disable=unexpected-keyword-arg
+    response = self.svcr.CalcSomething(
+        self.request, self.prpc_context, cnxn=self.cnxn)
+    self.assertIsNone(self.svcr.seen_mc.cnxn)  # Because of CleanUp().
+    self.assertIsNone(self.svcr.seen_mc.auth.email)
+    self.assertNotIn(permissions.CREATE_HOTLIST.lower(),
+                  self.svcr.seen_mc.perms.perm_names)
+    self.assertNotIn(permissions.ADMINISTER_SITE.lower(),
+                     self.svcr.seen_mc.perms.perm_names)
+    self.assertEqual(self.request, self.svcr.seen_request)
+    self.assertEqual('fake response proto', response)
+    self.assertEqual(codes.StatusCode.OK, self.prpc_context._code)
+
+  def testRun_DistributedInvalidation(self):
+    """The Run method must call DoDistributedInvalidation()."""
+    self.testbed.setup_env(user_email=self.non_member.email, overwrite=True)
+    self.SetUpRecordMonitoringStats()
+    # pylint: disable=unexpected-keyword-arg
+    self.svcr.CalcSomething(
+        self.request, self.prpc_context, cnxn=self.cnxn)
+    self.assertIsNotNone(self.services.cache_manager.last_call)
+
+  def testRun_HandlerErrorResponse(self):
+    """An expected exception in the method causes an error status."""
+    self.testbed.setup_env(user_email=self.non_member.email, overwrite=True)
+    self.SetUpRecordMonitoringStats()
+    # pylint: disable=attribute-defined-outside-init
+    self.request.exc_class = exceptions.NoSuchUserException
+    # pylint: disable=unexpected-keyword-arg
+    response = self.svcr.CalcSomething(
+        self.request, self.prpc_context, cnxn=self.cnxn)
+    self.assertTrue(self.svcr.was_called)
+    self.assertIsNone(self.svcr.seen_mc.cnxn)  # Because of CleanUp().
+    self.assertEqual(self.svcr.seen_mc.auth.email, self.non_member.email)
+    self.assertEqual(self.request, self.svcr.seen_request)
+    self.assertIsNone(response)
+    self.assertEqual(codes.StatusCode.NOT_FOUND, self.prpc_context._code)
+
+  def testRun_HandlerProgrammingError(self):
+    """An unexception in the handler method is re-raised."""
+    self.testbed.setup_env(user_email=self.non_member.email, overwrite=True)
+    self.SetUpRecordMonitoringStats()
+    # pylint: disable=attribute-defined-outside-init
+    self.request.exc_class = NotImplementedError
+    self.assertRaises(
+        NotImplementedError,
+        self.svcr.CalcSomething,
+        self.request, self.prpc_context, cnxn=self.cnxn)
+    self.assertTrue(self.svcr.was_called)
+    self.assertIsNone(self.svcr.seen_mc.cnxn)  # Because of CleanUp().
+
+  def testGetAndAssertRequesterAuth_Cookie_Anon(self):
+    """We get and allow requests from anon user using cookie auth."""
+    metadata = {
+        monorail_servicer.XSRF_TOKEN_HEADER: xsrf.GenerateToken(
+            0, xsrf.XHR_SERVLET_PATH)}
+    # Signed out.
+    client_id, user_auth = self.svcr.GetAndAssertRequesterAuth(
+        self.cnxn, metadata, self.services)
+    self.assertIsNone(user_auth.email)
+    self.assertEqual(client_id, 'https://%s.appspot.com' % self.app_id)
+
+  def testGetAndAssertRequesterAuth_Cookie_SignedIn(self):
+    """We get and allow requests from signed in users using cookie auth."""
+    metadata = dict(self.prpc_context.invocation_metadata())
+    # Signed in with cookie auth.
+    self.testbed.setup_env(user_email=self.non_member.email, overwrite=True)
+    client_id, user_auth = self.svcr.GetAndAssertRequesterAuth(
+        self.cnxn, metadata, self.services)
+    self.assertEqual(self.non_member.email, user_auth.email)
+    self.assertEqual(client_id, 'https://%s.appspot.com' % self.app_id)
+
+  def testGetAndAssertRequester_Anon_BadToken(self):
+    """We get the email address of the signed in user using oauth."""
+    metadata = {}
+    # Anonymous user has invalid token.
+    with self.assertRaises(permissions.PermissionException):
+      self.svcr.GetAndAssertRequesterAuth(self.cnxn, metadata, self.services)
+
+  @mock.patch('google.oauth2.id_token.verify_oauth2_token')
+  def testGetAndAssertRequesterAuth_IDToken_CaseInsensitiveBearer(
+      self, mock_verifier):
+    """We are case-insensitive when looking for the 'bearer' string."""
+    metadata = {'authorization': 'beaReR allowlisted-user-id-token'}
+    some_other_site_user = self.services.user.TestAddUser(
+        'some-human-user@human.test', 888)
+
+    # Signed in with oauth.
+    mock_verifier.return_value = {
+        'aud': self.allowlisted_client_id,
+        'email': some_other_site_user.email,
+    }
+
+    client_id, user_auth = self.svcr.GetAndAssertRequesterAuth(
+        self.cnxn, metadata, self.services)
+    self.assertEqual(client_id, self.allowlisted_client_id)
+    self.assertEqual(user_auth.email, some_other_site_user.email)
+    mock_verifier.assert_called_once_with('allowlisted-user-id-token', mock.ANY)
+
+  @mock.patch('google.oauth2.id_token.verify_oauth2_token')
+  def testGetAndAssertRequesterAuth_IDToken_AutoCreateUser(self, mock_verifier):
+    """We can auto-create Monorail users for the requester."""
+    metadata = {'authorization': 'beaReR allowlisted-user-id-token'}
+    # Signed in with oauth.
+    mock_verifier.return_value = {
+        'aud': self.allowlisted_client_id,
+        'email': 'new-user@email.com',
+    }
+
+    client_id, user_auth = self.svcr.GetAndAssertRequesterAuth(
+        self.cnxn, metadata, self.services)
+    self.assertEqual(client_id, self.allowlisted_client_id)
+    self.assertEqual(user_auth.email, 'new-user@email.com')
+    mock_verifier.assert_called_once_with('allowlisted-user-id-token', mock.ANY)
+
+  def testGetAndAssertRequesterAuth_IDToken_InvalidAuthToken(self):
+    """We raise an exception if 'bearer' is missing from headers."""
+    metadata = {'authorization': 'allowlisted-user-id-token'}
+
+    with self.assertRaises(permissions.PermissionException):
+      self.svcr.GetAndAssertRequesterAuth(self.cnxn, metadata, self.services)
+
+  @mock.patch('google.oauth2.id_token.verify_oauth2_token')
+  def testGetAndAssertRequesterAuth_IDToken_ServiceAccountAllowed(
+      self, mock_verifier):
+    """We allow requests from allowlisted service accounts with correct aud."""
+    metadata = {'authorization': 'Bearer allowlisted-user-id-token'}
+    # Allowlisted in testing/api_clients.cfg
+    allowlisted_service_account_email = self.services.user.TestAddUser(
+        '123456789@developer.gserviceaccount.com', 889)
+
+    aud = 'https://%s.appspot.com' % self.app_id
+    # Signed in with oauth.
+    mock_verifier.return_value = {
+        'aud': aud,
+        'email': allowlisted_service_account_email.email,
+    }
+
+    client_id, user_auth = self.svcr.GetAndAssertRequesterAuth(
+        self.cnxn, metadata, self.services)
+    self.assertEqual(client_id, aud)
+    self.assertEqual(user_auth.email, allowlisted_service_account_email.email)
+    mock_verifier.assert_called_once_with('allowlisted-user-id-token', mock.ANY)
+
+  @mock.patch('google.oauth2.id_token.verify_oauth2_token')
+  def testGetAndAssertRequesterAuth_IDToken_ServiceAccountNotAllowed(
+      self, mock_verifier):
+    """We raise an exception if the service account is not allowlisted"""
+    metadata = {'authorization': 'Bearer non-allowlisted-user-id-token'}
+
+    # Signed in with oauth.
+    mock_verifier.return_value = {
+        'aud': 'https://%s.appspot.com' % self.app_id,
+        # A random service account, not allow-listed.
+        'email': 'bigbadwolf@gserviceaccount.com',
+    }
+
+    with self.assertRaisesRegexp(
+        permissions.PermissionException, r'Account .+ is not allowlisted'):
+      self.svcr.GetAndAssertRequesterAuth(self.cnxn, metadata, self.services)
+
+  @mock.patch('google.oauth2.id_token.verify_oauth2_token')
+  def testGetAndAssertRequesterAuth_IDToken_ServiceAccountBadAud(
+      self, mock_verifier):
+    """We raise an exception when a service account token['aud'] is invalid."""
+    metadata = {'authorization': 'Bearer non-allowlisted-user-id-token'}
+    # Allowlisted in testing/api_clients.cfg
+    allowlisted_service_account_email = self.services.user.TestAddUser(
+        '123456789@developer.gserviceaccount.com', 889)
+
+    # Signed in with oauth.
+    mock_verifier.return_value = {
+        'aud': 'id-token-inteded-for-some-other-site',
+        'email': allowlisted_service_account_email.email,
+    }
+
+    with self.assertRaisesRegexp(
+        permissions.PermissionException, r'Invalid token audience: .+'):
+      self.svcr.GetAndAssertRequesterAuth(self.cnxn, metadata, self.services)
+
+  @mock.patch('google.oauth2.id_token.verify_oauth2_token')
+  def testGetAndAssertRequesterAuth_IDToken_ClientNotAllowed(
+      self, mock_verifier):
+    """We raise an exception if the client ID is not allowlisted."""
+    metadata = {'authorization': 'Bearer non-allowlisted-client-id-token'}
+
+    # Signed in with oauth.
+    mock_verifier.return_value = {
+        # A client ID not allow-listed.
+        'aud': 'some-other-site-client-id',
+        # Some human user that the client is impersonating for the request.
+        'email': 'some-other-site-user@test.com',
+    }
+
+    with self.assertRaisesRegexp(
+        permissions.PermissionException, r'Client .+ is not allowlisted'):
+      self.svcr.GetAndAssertRequesterAuth(self.cnxn, metadata, self.services)
+
+    # Assert some-other-site-user was not auto-created.
+    with self.assertRaises(exceptions.NoSuchUserException):
+      self.services.user.LookupUserID(
+          self.cnxn, 'some-other-site-user@test.com')
+
+  @mock.patch('google.oauth2.id_token.verify_oauth2_token')
+  def testGetAndAssertRequesterAuth_IDToken_NoEmail(self, mock_verifier):
+    """We raise an exception if ID token has no email information."""
+    metadata = {'authorization': 'Bearer allowlisted-user-id-token'}
+
+    # Signed in with oauth.
+    mock_verifier.return_value = {'aud': self.allowlisted_client_id}
+
+    with self.assertRaises(permissions.PermissionException):
+      self.svcr.GetAndAssertRequesterAuth(self.cnxn, metadata, self.services)
+
+  @mock.patch('google.oauth2.id_token.verify_oauth2_token')
+  def testGetAndAssertRequesterAuth_IDToken_InvalidIDToken(self, mock_verifier):
+    """We raise an exception if the ID token is invalid."""
+    metadata = {'authorization': 'Bearer bad-token'}
+
+    mock_verifier.side_effect = ValueError()
+
+    with self.assertRaises(permissions.PermissionException):
+      self.svcr.GetAndAssertRequesterAuth(self.cnxn, metadata, self.services)
+
+  def testGetAndAssertRequesterAuth_Banned(self):
+    self.non_member.banned = 'Spammer'
+    metadata = dict(self.prpc_context.invocation_metadata())
+    # Signed in with cookie auth.
+    self.testbed.setup_env(user_email=self.non_member.email, overwrite=True)
+    with self.assertRaises(permissions.BannedUserException):
+      self.svcr.GetAndAssertRequesterAuth(self.cnxn, metadata, self.services)
+
+  def testGetRequester_TestAccountOnAppspot(self):
+    """Specifying test_account is ignored on deployed server."""
+    # pylint: disable=attribute-defined-outside-init
+    metadata = {'x-test-account': 'test@example.com'}
+    with self.assertRaises(exceptions.InputException):
+      self.svcr.GetAndAssertRequesterAuth(self.cnxn, metadata, self.services)
+
+  def testGetRequester_TestAccountOnDev(self):
+    """For integration testing, we can set test_account on dev_server."""
+    try:
+      orig_local_mode = settings.local_mode
+      settings.local_mode = True
+
+      # pylint: disable=attribute-defined-outside-init
+      metadata = {'x-test-account': 'test@example.com'}
+      client_id, test_auth = self.svcr.GetAndAssertRequesterAuth(
+          self.cnxn, metadata, self.services)
+      self.assertEqual('test@example.com', test_auth.email)
+      self.assertEqual('https://%s.appspot.com' % self.app_id, client_id)
+
+      # pylint: disable=attribute-defined-outside-init
+      metadata = {'x-test-account': 'test@anythingelse.com'}
+      with self.assertRaises(exceptions.InputException):
+        self.svcr.GetAndAssertRequesterAuth(self.cnxn, metadata, self.services)
+    finally:
+      settings.local_mode = orig_local_mode
+
+  def testAssertBaseChecks_SiteIsReadOnly_Write(self):
+    """We reject writes and allow reads when site is read-only."""
+    orig_read_only = settings.read_only
+    try:
+      settings.read_only = True
+      metadata = {}
+      self.assertRaises(
+        permissions.PermissionException,
+        self.svcr.AssertBaseChecks, self.request, metadata)
+    finally:
+      settings.read_only = orig_read_only
+
+  def testAssertBaseChecks_SiteIsReadOnly_Read(self):
+    """We reject writes and allow reads when site is read-only."""
+    orig_read_only = settings.read_only
+    try:
+      settings.read_only = True
+      metadata = {monorail_servicer.XSRF_TOKEN_HEADER: self.nonmember_token}
+
+      # Our default request is an update.
+      with self.assertRaises(permissions.PermissionException):
+        self.svcr.AssertBaseChecks(self.request, metadata)
+
+      # A method name starting with "List" or "Get" will run OK.
+      self.request = ListSomethingRequest(exc_class=None)
+      self.svcr.AssertBaseChecks(self.request, metadata)
+    finally:
+      settings.read_only = orig_read_only
+
+  def CheckExceptionStatus(self, e, expected_code, details=None):
+    mc = monorailcontext.MonorailContext(self.services)
+    self.prpc_context.set_code(codes.StatusCode.OK)
+    processed = self.svcr.ProcessException(e, self.prpc_context, mc)
+    if expected_code:
+      self.assertTrue(processed)
+      self.assertEqual(expected_code, self.prpc_context._code)
+    else:
+      self.assertFalse(processed)
+      # Uncaught exceptions should indicate an error.
+      self.assertEqual(codes.StatusCode.INTERNAL, self.prpc_context._code)
+    if details is not None:
+      self.assertEqual(details, self.prpc_context._details)
+
+  def testProcessException(self):
+    """Expected exceptions are converted to pRPC codes, expected not."""
+    self.CheckExceptionStatus(
+        exceptions.NoSuchUserException(), codes.StatusCode.NOT_FOUND)
+    self.CheckExceptionStatus(
+        exceptions.NoSuchProjectException(), codes.StatusCode.NOT_FOUND)
+    self.CheckExceptionStatus(
+        exceptions.NoSuchIssueException(), codes.StatusCode.NOT_FOUND)
+    self.CheckExceptionStatus(
+        exceptions.NoSuchComponentException(), codes.StatusCode.NOT_FOUND)
+    self.CheckExceptionStatus(
+        permissions.BannedUserException(), codes.StatusCode.PERMISSION_DENIED)
+    self.CheckExceptionStatus(
+        permissions.PermissionException(), codes.StatusCode.PERMISSION_DENIED)
+    self.CheckExceptionStatus(
+        exceptions.GroupExistsException(), codes.StatusCode.ALREADY_EXISTS)
+    self.CheckExceptionStatus(
+        exceptions.InvalidComponentNameException(),
+        codes.StatusCode.INVALID_ARGUMENT)
+    self.CheckExceptionStatus(
+        exceptions.FilterRuleException(),
+        codes.StatusCode.INVALID_ARGUMENT,
+        details='Violates filter rule that should error.')
+    self.CheckExceptionStatus(
+        exceptions.InputException('echoed values'),
+        codes.StatusCode.INVALID_ARGUMENT,
+        details='Invalid arguments: echoed values')
+    self.CheckExceptionStatus(
+        exceptions.OverAttachmentQuota(), codes.StatusCode.RESOURCE_EXHAUSTED)
+    self.CheckExceptionStatus(
+        ratelimiter.ApiRateLimitExceeded('client_id', 'email'),
+        codes.StatusCode.PERMISSION_DENIED)
+    self.CheckExceptionStatus(
+        features_svc.HotlistAlreadyExists(), codes.StatusCode.ALREADY_EXISTS)
+    self.CheckExceptionStatus(NotImplementedError(), None)
+
+  def testProcessException_ErrorMessageEscaped(self):
+    """If we ever echo user input in error messages, it is escaped.."""
+    self.CheckExceptionStatus(
+        exceptions.InputException('echoed <script>"code"</script>'),
+        codes.StatusCode.INVALID_ARGUMENT,
+        details=('Invalid arguments: echoed '
+                 '&lt;script&gt;&quot;code&quot;&lt;/script&gt;'))
+
+  def testRecordMonitoringStats_RequestClassDoesNotEndInRequest(self):
+    """We cope with request proto class names that do not end in 'Request'."""
+    self.request = 'this is a string'
+    self.SetUpRecordMonitoringStats()
+    start_time = 1522559788.939511
+    now = 1522569311.892738
+    self.svcr.RecordMonitoringStats(
+        start_time, self.request, 'fake response proto', self.prpc_context,
+        now=now)
diff --git a/api/v3/test/paginator_test.py b/api/v3/test/paginator_test.py
new file mode 100644
index 0000000..ca0b713
--- /dev/null
+++ b/api/v3/test/paginator_test.py
@@ -0,0 +1,78 @@
+# Copyright 2020 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style
+# license that can be found in the LICENSE file.
+"""Tests for the Paginator class."""
+
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+
+import unittest
+
+from google.appengine.ext import testbed
+
+from api.v3 import paginator
+from api.v3.api_proto import hotlists_pb2
+from framework import exceptions
+from framework import paginate
+
+class PaginatorTest(unittest.TestCase):
+
+  def setUp(self):
+    self.testbed = testbed.Testbed()
+    self.testbed.activate()
+    self.testbed.init_memcache_stub()
+    self.testbed.init_datastore_v3_stub()
+
+    self.paginator = paginator.Paginator(
+        parent='animal/goose/sound/honks', query='chaos')
+
+  def testGetStart(self):
+    """We can get the start index from a page_token."""
+    start = 5
+    page_token = paginate.GeneratePageToken(
+        self.paginator.request_contents, start)
+    self.assertEqual(self.paginator.GetStart(page_token), start)
+
+  def testGetStart_EmptyPageToken(self):
+    """We return the default start for an empty page_token."""
+    request = hotlists_pb2.ListHotlistItemsRequest()
+    self.assertEqual(0, self.paginator.GetStart(request.page_token))
+
+  def testGenerateNextPageToken(self):
+    """We return the next page token."""
+    next_start = 10
+    expected_page_token = paginate.GeneratePageToken(
+        self.paginator.request_contents, next_start)
+    self.assertEqual(
+        self.paginator.GenerateNextPageToken(next_start), expected_page_token)
+
+  def testGenerateNextPageToken_NoStart(self):
+    """We return None if start is not provided."""
+    next_start = None
+    self.assertEqual(self.paginator.GenerateNextPageToken(next_start), None)
+
+  def testCoercePageSize(self):
+    """A valid page_size is used when provided."""
+    self.assertEqual(1, paginator.CoercePageSize(1, 5))
+
+  def testCoercePageSize_Negative(self):
+    """An exception is raised for a negative page_size."""
+    with self.assertRaises(exceptions.InputException):
+      paginator.CoercePageSize(-1, 5)
+
+  def testCoercePageSize_TooBig(self):
+    """A page_size above the max is coerced to the max."""
+    self.assertEqual(5, paginator.CoercePageSize(6, 5, 2))
+
+  def testCoercePageSize_Default(self):
+    """A default page_size different from max_size is used when provided."""
+    self.assertEqual(2, paginator.CoercePageSize(None, 5, 2))
+
+  def testCoercePageSize_NotProvided(self):
+    """max_size is used if no page_size or default_size provided."""
+    self.assertEqual(5, paginator.CoercePageSize(None, 5))
+
+  def testCoercePageSize_Zero(self):
+    """Handles zero equivalently to None."""
+    self.assertEqual(5, paginator.CoercePageSize(0, 5))
\ No newline at end of file
diff --git a/api/v3/test/permissions_converter_test.py b/api/v3/test/permissions_converter_test.py
new file mode 100644
index 0000000..e679eb6
--- /dev/null
+++ b/api/v3/test/permissions_converter_test.py
@@ -0,0 +1,44 @@
+# Copyright 2020 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style
+# license that can be found in the LICENSE file.
+"""Tests for converting permission strings to API permissions enums."""
+
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+
+import unittest
+
+from api.v3 import permission_converters as pc
+from api.v3.api_proto import permission_objects_pb2
+from framework import exceptions
+from framework import permissions
+
+
+class ConverterFunctionsTest(unittest.TestCase):
+
+  def testConvertHotlistPermissions(self):
+    api_perms = pc.ConvertHotlistPermissions(
+        [permissions.ADMINISTER_HOTLIST, permissions.EDIT_HOTLIST])
+    expected_perms = [
+        permission_objects_pb2.Permission.Value('HOTLIST_ADMINISTER'),
+        permission_objects_pb2.Permission.Value('HOTLIST_EDIT')
+    ]
+    self.assertEqual(api_perms, expected_perms)
+
+  def testConvertHotlistPermissions_InvalidPermission(self):
+    with self.assertRaises(exceptions.InputException):
+      pc.ConvertHotlistPermissions(['EatHotlist'])
+
+  def testConvertFieldDefPermissions(self):
+    api_perms = pc.ConvertFieldDefPermissions(
+        [permissions.EDIT_FIELD_DEF_VALUE, permissions.EDIT_FIELD_DEF])
+    expected_perms = [
+        permission_objects_pb2.Permission.Value('FIELD_DEF_VALUE_EDIT'),
+        permission_objects_pb2.Permission.Value('FIELD_DEF_EDIT')
+    ]
+    self.assertEqual(api_perms, expected_perms)
+
+  def testConvertFieldDefPermissions_InvalidPermission(self):
+    with self.assertRaises(exceptions.InputException):
+      pc.ConvertFieldDefPermissions(['EatFieldDef'])
diff --git a/api/v3/test/permissions_servicer_test.py b/api/v3/test/permissions_servicer_test.py
new file mode 100644
index 0000000..076bd40
--- /dev/null
+++ b/api/v3/test/permissions_servicer_test.py
@@ -0,0 +1,105 @@
+# Copyright 2020 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style
+# license that can be found in the LICENSE file.
+"""Tests for the permissions servicer."""
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+
+import unittest
+
+from api.v3 import permission_converters as pc
+from api.v3 import permissions_servicer
+from api.v3.api_proto import permissions_pb2
+from api.v3.api_proto import permission_objects_pb2
+from framework import exceptions
+from framework import monorailcontext
+from framework import permissions
+from testing import fake
+from services import features_svc
+from services import service_manager
+
+
+class PermissionsServicerTest(unittest.TestCase):
+
+  def setUp(self):
+    self.cnxn = fake.MonorailConnection()
+    self.services = service_manager.Services(
+        features=fake.FeaturesService(),
+        issue=fake.IssueService(),
+        project=fake.ProjectService(),
+        config=fake.ConfigService(),
+        user=fake.UserService(),
+        usergroup=fake.UserGroupService())
+    self.project = self.services.project.TestAddProject(
+        'proj', project_id=789, committer_ids=[111])
+    self.permissions_svcr = permissions_servicer.PermissionsServicer(
+        self.services, make_rate_limiter=False)
+    self.user_1 = self.services.user.TestAddUser('goose_1@example.com', 111)
+    self.hotlist_1 = self.services.features.TestAddHotlist(
+        'ThingsToBreak', owner_ids=[self.user_1.user_id])
+    self.services.config.CreateFieldDef(
+        self.cnxn, self.project.project_id, 'Field_1', 'STR_TYPE', None, None,
+        None, None, None, None, None, None, None, None, None, None, None, None,
+        [], [])
+    self.config = self.services.config.GetProjectConfig(
+        self.cnxn, self.project.project_id)
+
+  def CallWrapped(self, wrapped_handler, *args, **kwargs):
+    return wrapped_handler.wrapped(self.permissions_svcr, *args, **kwargs)
+
+  def testBatchGetPermissionSets_Hotlist(self):
+    """We can batch get PermissionSets for hotlists."""
+    hotlist_1_name = 'hotlists/%s' % self.hotlist_1.hotlist_id
+    request = permissions_pb2.BatchGetPermissionSetsRequest(
+        names=[hotlist_1_name])
+    mc = monorailcontext.MonorailContext(
+        self.services, cnxn=self.cnxn, requester=self.user_1.email)
+    mc.LookupLoggedInUserPerms(None)
+    response = self.CallWrapped(
+        self.permissions_svcr.BatchGetPermissionSets, mc, request)
+
+    expected_permission_sets = [
+        permission_objects_pb2.PermissionSet(
+            resource=hotlist_1_name,
+            permissions=[
+                permission_objects_pb2.Permission.Value('HOTLIST_ADMINISTER'),
+                permission_objects_pb2.Permission.Value('HOTLIST_EDIT'),
+            ])
+    ]
+    self.assertEqual(
+        response,
+        permissions_pb2.BatchGetPermissionSetsResponse(
+            permission_sets=expected_permission_sets))
+
+  def testBatchGetPermissionSets_FieldDef(self):
+    """We can batch get PermissionSets for fields."""
+    field = self.config.field_defs[0]
+    field_1_name = 'projects/%s/fieldDefs/%s' % (
+        self.project.project_name, field.field_id)
+    request = permissions_pb2.BatchGetPermissionSetsRequest(
+        names=[field_1_name])
+    mc = monorailcontext.MonorailContext(
+        self.services, cnxn=self.cnxn, requester=self.user_1.email)
+    mc.LookupLoggedInUserPerms(self.project)
+    response = self.CallWrapped(
+        self.permissions_svcr.BatchGetPermissionSets, mc, request)
+
+    expected_permission_sets = [
+        permission_objects_pb2.PermissionSet(
+            resource=field_1_name,
+            permissions=[
+                permission_objects_pb2.Permission.Value('FIELD_DEF_VALUE_EDIT'),
+            ])
+    ]
+    self.assertEqual(
+        response,
+        permissions_pb2.BatchGetPermissionSetsResponse(
+            permission_sets=expected_permission_sets))
+
+  # Each case of recognized resource name is tested in testBatchGetPermissions.
+  def testGetPermissionSet_InvalidName(self):
+    """We raise exception when the resource name is unrecognized."""
+    we = None
+    with self.assertRaises(exceptions.InputException):
+      self.permissions_svcr._GetPermissionSet(self.cnxn, we, 'goose/honk')
diff --git a/api/v3/test/projects_servicer_test.py b/api/v3/test/projects_servicer_test.py
new file mode 100644
index 0000000..83aa8ab
--- /dev/null
+++ b/api/v3/test/projects_servicer_test.py
@@ -0,0 +1,245 @@
+# Copyright 2020 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style
+# license that can be found in the LICENSE file or at
+# https://developers.google.com/open-source/licenses/bsd
+"""Tests for the hotlists servicer."""
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+
+import unittest
+import mock
+import logging
+
+from google.protobuf import timestamp_pb2
+from google.protobuf import empty_pb2
+
+from api import resource_name_converters as rnc
+from api.v3 import projects_servicer
+from api.v3 import converters
+from api.v3.api_proto import projects_pb2
+from api.v3.api_proto import project_objects_pb2
+from api.v3.api_proto import issue_objects_pb2
+from framework import exceptions
+from framework import monorailcontext
+from framework import permissions
+from testing import fake
+from services import service_manager
+
+from google.appengine.ext import testbed
+
+class ProjectsServicerTest(unittest.TestCase):
+
+  def setUp(self):
+    # memcache and datastore needed for generating page tokens.
+    self.testbed = testbed.Testbed()
+    self.testbed.activate()
+    self.testbed.init_memcache_stub()
+    self.testbed.init_datastore_v3_stub()
+
+    self.cnxn = fake.MonorailConnection()
+    self.services = service_manager.Services(
+        features=fake.FeaturesService(),
+        issue=fake.IssueService(),
+        project=fake.ProjectService(),
+        config=fake.ConfigService(),
+        user=fake.UserService(),
+        template=fake.TemplateService(),
+        usergroup=fake.UserGroupService())
+    self.projects_svcr = projects_servicer.ProjectsServicer(
+        self.services, make_rate_limiter=False)
+
+    self.user_1 = self.services.user.TestAddUser('user_111@example.com', 111)
+
+    self.project_1 = self.services.project.TestAddProject(
+        'proj', project_id=789)
+    self.template_1 = self.services.template.TestAddIssueTemplateDef(
+        123, 789, 'template_1_name', content='foo bar', summary='foo')
+    self.project_1_resource_name = 'projects/proj'
+    self.converter = None
+
+  def CallWrapped(self, wrapped_handler, mc, *args, **kwargs):
+    self.converter = converters.Converter(mc, self.services)
+    self.projects_svcr.converter = self.converter
+    return wrapped_handler.wrapped(self.projects_svcr, mc, *args, **kwargs)
+
+  def testListIssueTemplates(self):
+    request = projects_pb2.ListIssueTemplatesRequest(
+        parent=self.project_1_resource_name)
+    mc = monorailcontext.MonorailContext(
+        self.services, cnxn=self.cnxn, requester=self.user_1.email)
+    response = self.CallWrapped(
+        self.projects_svcr.ListIssueTemplates, mc, request)
+
+    expected_issue = issue_objects_pb2.Issue(
+        summary=self.template_1.summary,
+        state=issue_objects_pb2.IssueContentState.Value('ACTIVE'),
+        status=issue_objects_pb2.Issue.StatusValue(
+            status=self.template_1.status,
+            derivation=issue_objects_pb2.Derivation.Value('EXPLICIT')))
+    expected_template = project_objects_pb2.IssueTemplate(
+        name='projects/{}/templates/{}'.format(
+            self.project_1.project_name, self.template_1.template_id),
+        display_name=self.template_1.name,
+        issue=expected_issue,
+        summary_must_be_edited=False,
+        template_privacy=project_objects_pb2.IssueTemplate.TemplatePrivacy
+        .Value('PUBLIC'),
+        default_owner=project_objects_pb2.IssueTemplate.DefaultOwner.Value(
+            'DEFAULT_OWNER_UNSPECIFIED'),
+        component_required=False)
+
+    self.assertEqual(
+        response,
+        projects_pb2.ListIssueTemplatesResponse(templates=[expected_template]))
+
+  @mock.patch('api.v3.api_constants.MAX_COMPONENTS_PER_PAGE', 3)
+  def testListComponentDefs(self):
+    project = self.services.project.TestAddProject(
+        'greece', project_id=987, owner_ids=[self.user_1.user_id])
+    config = fake.MakeTestConfig(project.project_id, [], [])
+    cd_1 = fake.MakeTestComponentDef(project.project_id, 1, path='Circe')
+    cd_2 = fake.MakeTestComponentDef(project.project_id, 2, path='Achilles')
+    cd_3 = fake.MakeTestComponentDef(project.project_id, 3, path='Patroclus')
+    cd_4 = fake.MakeTestComponentDef(project.project_id, 3, path='Galatea')
+    config.component_defs = [cd_1, cd_2, cd_3, cd_4]
+    self.services.config.StoreConfig(self.cnxn, config)
+
+    mc = monorailcontext.MonorailContext(
+        self.services, cnxn=self.cnxn, requester=self.user_1.email)
+
+    request = projects_pb2.ListComponentDefsRequest(parent='projects/greece')
+    response_1 = self.CallWrapped(
+        self.projects_svcr.ListComponentDefs, mc, request)
+    expected_cds_1 = self.converter.ConvertComponentDefs(
+        [cd_1, cd_2, cd_3], project.project_id)
+    self.assertEqual(list(response_1.component_defs), expected_cds_1)
+
+    request = projects_pb2.ListComponentDefsRequest(
+        parent='projects/greece', page_token=response_1.next_page_token)
+    response_2 = self.CallWrapped(
+        self.projects_svcr.ListComponentDefs, mc, request)
+    expected_cds_2 = self.converter.ConvertComponentDefs(
+        [cd_4], project.project_id)
+    self.assertEqual(list(response_2.component_defs), expected_cds_2)
+
+  @mock.patch('api.v3.api_constants.MAX_COMPONENTS_PER_PAGE', 2)
+  def testListComponentDefs_PaginateAndMaxSizeCap(self):
+    project = self.services.project.TestAddProject(
+        'greece', project_id=987, owner_ids=[self.user_1.user_id])
+    config = fake.MakeTestConfig(project.project_id, [], [])
+    cd_1 = fake.MakeTestComponentDef(project.project_id, 1, path='Circe')
+    cd_2 = fake.MakeTestComponentDef(project.project_id, 2, path='Achilles')
+    cd_3 = fake.MakeTestComponentDef(project.project_id, 3, path='Patroclus')
+    cd_4 = fake.MakeTestComponentDef(project.project_id, 4, path='Galatea')
+    cd_5 = fake.MakeTestComponentDef(project.project_id, 5, path='Briseis')
+    config.component_defs = [cd_1, cd_2, cd_3, cd_4, cd_5]
+    self.services.config.StoreConfig(self.cnxn, config)
+
+    mc = monorailcontext.MonorailContext(
+        self.services, cnxn=self.cnxn, requester=self.user_1.email)
+
+    request = projects_pb2.ListComponentDefsRequest(
+        parent='projects/greece', page_size=3)
+    response_1 = self.CallWrapped(
+        self.projects_svcr.ListComponentDefs, mc, request)
+    expected_cds_1 = self.converter.ConvertComponentDefs(
+        [cd_1, cd_2], project.project_id)
+    self.assertEqual(list(response_1.component_defs), expected_cds_1)
+
+    request = projects_pb2.ListComponentDefsRequest(
+        parent='projects/greece', page_size=3,
+        page_token=response_1.next_page_token)
+    response_2 = self.CallWrapped(
+        self.projects_svcr.ListComponentDefs, mc, request)
+    expected_cds_2 = self.converter.ConvertComponentDefs(
+        [cd_3, cd_4], project.project_id)
+    self.assertEqual(list(response_2.component_defs), expected_cds_2)
+
+    request = projects_pb2.ListComponentDefsRequest(
+        parent='projects/greece', page_size=3,
+        page_token=response_2.next_page_token)
+    response_3 = self.CallWrapped(
+        self.projects_svcr.ListComponentDefs, mc, request)
+    expected_cds_3 = self.converter.ConvertComponentDefs(
+        [cd_5], project.project_id)
+    self.assertEqual(response_3, projects_pb2.ListComponentDefsResponse(
+        component_defs=expected_cds_3))
+
+  @mock.patch('time.time')
+  def testCreateComponentDef(self, mockTime):
+    now = 123
+    mockTime.return_value = now
+
+    user_1 = self.services.user.TestAddUser('achilles@test.com', 981)
+    self.services.user.TestAddUser('patroclus@test.com', 982)
+    self.services.user.TestAddUser('circe@test.com', 983)
+
+    project = self.services.project.TestAddProject(
+        'chicken', project_id=987, owner_ids=[user_1.user_id])
+    config = fake.MakeTestConfig(project.project_id, [], [])
+    self.services.config.StoreConfig(self.cnxn, config)
+
+    expected = project_objects_pb2.ComponentDef(
+        value='circe',
+        docstring='You threw me to the crows',
+        admins=['users/983'],
+        ccs=['users/981', 'users/982'],
+        labels=['more-soup', 'beach-day'],
+    )
+    request = projects_pb2.CreateComponentDefRequest(
+        parent='projects/chicken', component_def=expected)
+    mc = monorailcontext.MonorailContext(
+        self.services, cnxn=self.cnxn, requester=user_1.email)
+    response = self.CallWrapped(
+        self.projects_svcr.CreateComponentDef, mc, request)
+
+    self.assertEqual(1, len(config.component_defs))
+    expected.name = 'projects/chicken/componentDefs/%d' % config.component_defs[
+        0].component_id
+    expected.state = project_objects_pb2.ComponentDef.ComponentDefState.Value(
+        'ACTIVE')
+    expected.creator = 'users/981'
+    expected.create_time.FromSeconds(now)
+    expected.modify_time.FromSeconds(0)
+    self.assertEqual(response, expected)
+
+  def testDeleteComponentDef(self):
+    user_1 = self.services.user.TestAddUser('achilles@test.com', 981)
+    project = self.services.project.TestAddProject(
+        'chicken', project_id=987, owner_ids=[user_1.user_id])
+    config = fake.MakeTestConfig(project.project_id, [], [])
+    component_def = fake.MakeTestComponentDef(
+        project.project_id, 1, path='Chickens>Dickens')
+    config.component_defs = [component_def]
+    self.services.config.StoreConfig(self.cnxn, config)
+
+    request = projects_pb2.DeleteComponentDefRequest(
+        name='projects/chicken/componentDefs/1')
+    mc = monorailcontext.MonorailContext(
+        self.services, cnxn=self.cnxn, requester=user_1.email)
+    actual = self.CallWrapped(
+        self.projects_svcr.DeleteComponentDef, mc, request)
+    self.assertEqual(actual, empty_pb2.Empty())
+
+    self.assertEqual(config.component_defs, [])
+
+  @mock.patch('project.project_helpers.GetThumbnailUrl')
+  def testListProjects(self, mock_GetThumbnailUrl):
+    mock_GetThumbnailUrl.return_value = 'xyz'
+
+    request = projects_pb2.ListProjectsRequest()
+
+    mc = monorailcontext.MonorailContext(
+        self.services, cnxn=self.cnxn, requester=self.user_1.email)
+    response = self.CallWrapped(self.projects_svcr.ListProjects, mc, request)
+
+    expected_project = project_objects_pb2.Project(
+        name=self.project_1_resource_name,
+        display_name=self.project_1.project_name,
+        summary=self.project_1.summary,
+        thumbnail_url='xyz')
+
+    self.assertEqual(
+        response,
+        projects_pb2.ListProjectsResponse(projects=[expected_project]))
diff --git a/api/v3/test/users_servicer_test.py b/api/v3/test/users_servicer_test.py
new file mode 100644
index 0000000..8982ec9
--- /dev/null
+++ b/api/v3/test/users_servicer_test.py
@@ -0,0 +1,136 @@
+# Copyright 2020 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style
+# license that can be found in the LICENSE file.
+"""Tests for the users servicer."""
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+
+import unittest
+import mock
+
+from google.protobuf import empty_pb2
+
+from api import resource_name_converters as rnc
+from api.v3 import users_servicer
+from api.v3 import converters
+from api.v3.api_proto import users_pb2
+from api.v3.api_proto import user_objects_pb2
+from framework import exceptions
+from framework import monorailcontext
+from framework import permissions
+from testing import fake
+from testing import testing_helpers
+from services import features_svc
+from services import user_svc
+from services import service_manager
+
+
+class UsersServicerTest(unittest.TestCase):
+
+  def setUp(self):
+    self.cnxn = fake.MonorailConnection()
+    self.services = service_manager.Services(
+        user=fake.UserService(),
+        usergroup=fake.UserGroupService(),
+        project=fake.ProjectService(),
+        project_star=fake.ProjectStarService())
+    self.users_svcr = users_servicer.UsersServicer(
+        self.services, make_rate_limiter=False)
+
+    self.user_1 = self.services.user.TestAddUser('user_111@example.com', 111)
+    self.user_2 = self.services.user.TestAddUser('user_222@example.com', 222)
+    self.user_3 = self.services.user.TestAddUser('user_333@example.com', 333)
+
+    self.project_1 = self.services.project.TestAddProject(
+        'proj', project_id=789)
+
+    self.converter = None
+
+  def CallWrapped(self, wrapped_handler, mc, *args, **kwargs):
+    self.converter = converters.Converter(mc, self.services)
+    self.users_svcr.converter = self.converter
+    return wrapped_handler.wrapped(self.users_svcr, mc, *args, **kwargs)
+
+  def testGetUser(self):
+    request = users_pb2.GetUserRequest(name='users/222')
+    mc = monorailcontext.MonorailContext(
+        self.services, cnxn=self.cnxn, requester=self.user_1.email)
+    mc.LookupLoggedInUserPerms(None)
+    response = self.CallWrapped(self.users_svcr.GetUser, mc, request)
+    expected_response = user_objects_pb2.User(
+        name='users/222',
+        display_name=testing_helpers.ObscuredEmail(self.user_2.email),
+        email=testing_helpers.ObscuredEmail(self.user_2.email),
+        availability_message='User never visited')
+    self.assertEqual(response, expected_response)
+
+  def testBatchGetUsers(self):
+    request = users_pb2.BatchGetUsersRequest(
+        names=['users/222', 'users/333'])
+    mc = monorailcontext.MonorailContext(
+        self.services, cnxn=self.cnxn, requester=self.user_1.email)
+    mc.LookupLoggedInUserPerms(None)
+    response = self.CallWrapped(self.users_svcr.BatchGetUsers, mc, request)
+    expected_users = [
+        user_objects_pb2.User(
+            name='users/222',
+            display_name=testing_helpers.ObscuredEmail(self.user_2.email),
+            email=testing_helpers.ObscuredEmail(self.user_2.email),
+            availability_message='User never visited'),
+        user_objects_pb2.User(
+            name='users/333',
+            display_name=testing_helpers.ObscuredEmail(self.user_3.email),
+            email=testing_helpers.ObscuredEmail(self.user_3.email),
+            availability_message='User never visited')
+    ]
+    self.assertEqual(
+        response, users_pb2.BatchGetUsersResponse(users=expected_users))
+
+  @mock.patch('api.v3.api_constants.MAX_BATCH_USERS', 2)
+  def testBatchGetUsers_TooMany(self):
+    mc = monorailcontext.MonorailContext(
+        self.services, cnxn=self.cnxn, requester=self.user_1.email)
+    request = users_pb2.BatchGetUsersRequest(
+        names=['users/222', 'users/333', 'users/444'])
+    with self.assertRaises(exceptions.InputException):
+      self.CallWrapped(self.users_svcr.BatchGetUsers, mc, request)
+
+  def testStarProject(self):
+    request = users_pb2.StarProjectRequest(project='projects/proj')
+    mc = monorailcontext.MonorailContext(
+        self.services, cnxn=self.cnxn, requester=self.user_1.email)
+    mc.LookupLoggedInUserPerms(None)
+    response = self.CallWrapped(self.users_svcr.StarProject, mc, request)
+    expected_name = 'users/111/projectStars/proj'
+
+    self.assertEqual(response, user_objects_pb2.ProjectStar(name=expected_name))
+
+  def testUnStarProject(self):
+    request = users_pb2.UnStarProjectRequest(project='projects/proj')
+    mc = monorailcontext.MonorailContext(
+        self.services, cnxn=self.cnxn, requester=self.user_1.email)
+    mc.LookupLoggedInUserPerms(None)
+    response = self.CallWrapped(self.users_svcr.UnStarProject, mc, request)
+
+    self.assertEqual(response, empty_pb2.Empty())
+
+    is_starred = self.services.project_star.IsItemStarredBy(self.cnxn, 789, 111)
+    self.assertFalse(is_starred)
+
+  def testListProjectStars(self):
+    request = users_pb2.ListProjectStarsRequest(parent='users/111')
+    mc = monorailcontext.MonorailContext(
+        self.services, cnxn=self.cnxn, requester=self.user_1.email)
+    mc.LookupLoggedInUserPerms(None)
+
+    self.services.project_star.SetStar(
+        self.cnxn, self.project_1.project_id, self.user_1.user_id, True)
+
+    response = self.CallWrapped(self.users_svcr.ListProjectStars, mc, request)
+
+    expected_response = users_pb2.ListProjectStarsResponse(
+        project_stars=[
+            user_objects_pb2.ProjectStar(name='users/111/projectStars/proj')
+        ])
+    self.assertEqual(response, expected_response)
diff --git a/api/v3/users_servicer.py b/api/v3/users_servicer.py
new file mode 100644
index 0000000..cbf70c5
--- /dev/null
+++ b/api/v3/users_servicer.py
@@ -0,0 +1,127 @@
+# Copyright 2020 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style
+# license that can be found in the LICENSE file.
+
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+
+from google.protobuf import empty_pb2
+
+from api import resource_name_converters as rnc
+from api.v3 import monorail_servicer
+from api.v3 import api_constants
+from api.v3.api_proto import users_pb2
+from api.v3.api_proto import user_objects_pb2
+from api.v3.api_proto import users_prpc_pb2
+from businesslogic import work_env
+from framework import exceptions
+
+
+class UsersServicer(monorail_servicer.MonorailServicer):
+  """Handle API requests related to User objects.
+  Each API request is implemented with a method as defined in the
+  .proto file. Each method does any request-specific validation, uses work_env
+  to safely operate on business objects, and returns a response proto.
+  """
+
+  DESCRIPTION = users_prpc_pb2.UsersServiceDescription
+
+  @monorail_servicer.PRPCMethod
+  def GetUser(self, mc, request):
+    # type: (MonorailContext, GetUserRequest) ->
+    # GetUserResponse
+    """pRPC API method that implements GetUser.
+
+      Raises:
+        InputException if a name in request.name is invalid.
+        NoSuchUserException if a User is not found.
+    """
+    user_id = rnc.IngestUserName(mc.cnxn, request.name, self.services)
+
+    with work_env.WorkEnv(mc, self.services) as we:
+      user = we.GetUser(user_id)
+
+    return self.converter.ConvertUser(user)
+
+  @monorail_servicer.PRPCMethod
+  def BatchGetUsers(self, mc, request):
+    # type: (MonorailContext, BatchGetUsersRequest) ->
+    # BatchGetUsersResponse
+    """pRPC API method that implements BatchGetUsers.
+
+      Raises:
+        InputException if a name in request.names is invalid.
+        NoSuchUserException if a User is not found.
+    """
+    if len(request.names) > api_constants.MAX_BATCH_USERS:
+      raise exceptions.InputException(
+          'Requesting %d users when the allowed maximum is %d users.' %
+          (len(request.names), api_constants.MAX_BATCH_USERS))
+    user_ids = rnc.IngestUserNames(mc.cnxn, request.names, self.services)
+
+    with work_env.WorkEnv(mc, self.services) as we:
+      users = we.BatchGetUsers(user_ids)
+
+    api_users_by_id = self.converter.ConvertUsers(
+        [user.user_id for user in users])
+    api_users = [api_users_by_id[user_id] for user_id in user_ids]
+
+    return users_pb2.BatchGetUsersResponse(users=api_users)
+
+  @monorail_servicer.PRPCMethod
+  def StarProject(self, mc, request):
+    # type: (MonorailContext, StarProjectRequest) ->
+    # ProjectStar
+    """pRPC API method that implements StarProject.
+
+      Raises:
+        InputException if the project name in request.project is invalid.
+        NoSuchProjectException if no project exists with the given name.
+    """
+    project_id = rnc.IngestProjectName(mc.cnxn, request.project, self.services)
+
+    with work_env.WorkEnv(mc, self.services) as we:
+      we.StarProject(project_id, True)
+
+    user_id = mc.auth.user_id
+    star_name = rnc.ConvertProjectStarName(
+        mc.cnxn, user_id, project_id, self.services)
+
+    return user_objects_pb2.ProjectStar(name=star_name)
+
+  @monorail_servicer.PRPCMethod
+  def UnStarProject(self, mc, request):
+    # type: (MonorailContext, UnStarProjectRequest) ->
+    # Empty
+    """pRPC API method that implements UnStarProject.
+
+      Raises:
+        InputException if the project name in request.project is invalid.
+        NoSuchProjectException if no project exists with the given name.
+    """
+    project_id = rnc.IngestProjectName(mc.cnxn, request.project, self.services)
+
+    with work_env.WorkEnv(mc, self.services) as we:
+      we.StarProject(project_id, False)
+
+    return empty_pb2.Empty()
+
+  @monorail_servicer.PRPCMethod
+  def ListProjectStars(self, mc, request):
+    # type: (MonorailContext, ListProjectStarsRequest) ->
+    #   ListProjectStarsResponse
+    """pRPC API method that implements ListProjectStars.
+
+      Raises:
+        InputException: if the `page_token` or `parent` is invalid.
+        NoSuchUserException: if the User is not found.
+    """
+    user_id = rnc.IngestUserName(mc.cnxn, request.parent, self.services)
+
+    with work_env.WorkEnv(mc, self.services) as we:
+      projects = we.ListStarredProjects(user_id)
+
+    # TODO(crbug.com/monorail/7175): Add pagination logic.
+    return users_pb2.ListProjectStarsResponse(
+        project_stars=self.converter.ConvertProjectStars(user_id, projects))