diff --git a/api/agentmesh.pb.go b/api/agentmesh.pb.go index 6190a93f..a5fae49c 100644 --- a/api/agentmesh.pb.go +++ b/api/agentmesh.pb.go @@ -384,7 +384,7 @@ func (x ExtProcProcessingMode_HeaderMode) Number() protoreflect.EnumNumber { // Deprecated: Use ExtProcProcessingMode_HeaderMode.Descriptor instead. func (ExtProcProcessingMode_HeaderMode) EnumDescriptor() ([]byte, []int) { - return file_api_agentmesh_proto_rawDescGZIP(), []int{24, 0} + return file_api_agentmesh_proto_rawDescGZIP(), []int{25, 0} } type ExtProcProcessingMode_BodyMode int32 @@ -439,7 +439,7 @@ func (x ExtProcProcessingMode_BodyMode) Number() protoreflect.EnumNumber { // Deprecated: Use ExtProcProcessingMode_BodyMode.Descriptor instead. func (ExtProcProcessingMode_BodyMode) EnumDescriptor() ([]byte, []int) { - return file_api_agentmesh_proto_rawDescGZIP(), []int{24, 1} + return file_api_agentmesh_proto_rawDescGZIP(), []int{25, 1} } type AuthFrame struct { @@ -1467,7 +1467,12 @@ type ControlPlaneInfoResponse struct { // among them: its attested labels, to select or prefer routers by // placement, and its load as of its last lease, to prefer the ones with // room. - Routers []*RouterInfo `protobuf:"bytes,6,rep,name=routers,proto3" json:"routers,omitempty"` + Routers []*RouterInfo `protobuf:"bytes,6,rep,name=routers,proto3" json:"routers,omitempty"` + // How the mesh keeps its directory. Members and routers read these here + // and nowhere else, so they agree without configuration of their own; a + // control plane that leaves it unset leaves them on the defaults the + // api package names. + Directory *DirectoryParameters `protobuf:"bytes,7,opt,name=directory,proto3" json:"directory,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -1544,6 +1549,84 @@ func (x *ControlPlaneInfoResponse) GetRouters() []*RouterInfo { return nil } +func (x *ControlPlaneInfoResponse) GetDirectory() *DirectoryParameters { + if x != nil { + return x.Directory + } + return nil +} + +// DirectoryParameters are the settings of the DHT the routers serve, set +// once per mesh on the control plane (agentmesh-control-plane +// --dht-replication, --dht-reprovide-interval) and published on /info. +type DirectoryParameters struct { + state protoimpl.MessageState `protogen:"open.v1"` + // How many routers hold each record a member publishes and answer each + // lookup it makes: the routers closest to the key. A member holds a + // session with each of them besides the routers it is attached to. + Replication int32 `protobuf:"varint,1,opt,name=replication,proto3" json:"replication,omitempty"` + // How often a member re-announces each service it publishes. + ReprovideInterval *durationpb.Duration `protobuf:"bytes,2,opt,name=reprovide_interval,json=reprovideInterval,proto3" json:"reprovide_interval,omitempty"` + // How long a router keeps a record after its last announcement: three + // reprovide intervals, so a provider that stopped without unpublishing + // leaves within it and two missed announcements do not unlist one that + // did not. + RecordTtl *durationpb.Duration `protobuf:"bytes,3,opt,name=record_ttl,json=recordTtl,proto3" json:"record_ttl,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DirectoryParameters) Reset() { + *x = DirectoryParameters{} + mi := &file_api_agentmesh_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DirectoryParameters) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DirectoryParameters) ProtoMessage() {} + +func (x *DirectoryParameters) ProtoReflect() protoreflect.Message { + mi := &file_api_agentmesh_proto_msgTypes[14] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DirectoryParameters.ProtoReflect.Descriptor instead. +func (*DirectoryParameters) Descriptor() ([]byte, []int) { + return file_api_agentmesh_proto_rawDescGZIP(), []int{14} +} + +func (x *DirectoryParameters) GetReplication() int32 { + if x != nil { + return x.Replication + } + return 0 +} + +func (x *DirectoryParameters) GetReprovideInterval() *durationpb.Duration { + if x != nil { + return x.ReprovideInterval + } + return nil +} + +func (x *DirectoryParameters) GetRecordTtl() *durationpb.Duration { + if x != nil { + return x.RecordTtl + } + return nil +} + // RouterInfo is one router as the control plane lists it on /info. type RouterInfo struct { state protoimpl.MessageState `protogen:"open.v1"` @@ -1569,7 +1652,7 @@ type RouterInfo struct { func (x *RouterInfo) Reset() { *x = RouterInfo{} - mi := &file_api_agentmesh_proto_msgTypes[14] + mi := &file_api_agentmesh_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1581,7 +1664,7 @@ func (x *RouterInfo) String() string { func (*RouterInfo) ProtoMessage() {} func (x *RouterInfo) ProtoReflect() protoreflect.Message { - mi := &file_api_agentmesh_proto_msgTypes[14] + mi := &file_api_agentmesh_proto_msgTypes[15] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1594,7 +1677,7 @@ func (x *RouterInfo) ProtoReflect() protoreflect.Message { // Deprecated: Use RouterInfo.ProtoReflect.Descriptor instead. func (*RouterInfo) Descriptor() ([]byte, []int) { - return file_api_agentmesh_proto_rawDescGZIP(), []int{14} + return file_api_agentmesh_proto_rawDescGZIP(), []int{15} } func (x *RouterInfo) GetPeerId() string { @@ -1662,7 +1745,7 @@ type RouterLeaseRequest struct { func (x *RouterLeaseRequest) Reset() { *x = RouterLeaseRequest{} - mi := &file_api_agentmesh_proto_msgTypes[15] + mi := &file_api_agentmesh_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1674,7 +1757,7 @@ func (x *RouterLeaseRequest) String() string { func (*RouterLeaseRequest) ProtoMessage() {} func (x *RouterLeaseRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_agentmesh_proto_msgTypes[15] + mi := &file_api_agentmesh_proto_msgTypes[16] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1687,7 +1770,7 @@ func (x *RouterLeaseRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RouterLeaseRequest.ProtoReflect.Descriptor instead. func (*RouterLeaseRequest) Descriptor() ([]byte, []int) { - return file_api_agentmesh_proto_rawDescGZIP(), []int{15} + return file_api_agentmesh_proto_rawDescGZIP(), []int{16} } func (x *RouterLeaseRequest) GetPeerId() string { @@ -1771,7 +1854,7 @@ type RouterLeaseResponse struct { func (x *RouterLeaseResponse) Reset() { *x = RouterLeaseResponse{} - mi := &file_api_agentmesh_proto_msgTypes[16] + mi := &file_api_agentmesh_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1783,7 +1866,7 @@ func (x *RouterLeaseResponse) String() string { func (*RouterLeaseResponse) ProtoMessage() {} func (x *RouterLeaseResponse) ProtoReflect() protoreflect.Message { - mi := &file_api_agentmesh_proto_msgTypes[16] + mi := &file_api_agentmesh_proto_msgTypes[17] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1796,7 +1879,7 @@ func (x *RouterLeaseResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RouterLeaseResponse.ProtoReflect.Descriptor instead. func (*RouterLeaseResponse) Descriptor() ([]byte, []int) { - return file_api_agentmesh_proto_rawDescGZIP(), []int{16} + return file_api_agentmesh_proto_rawDescGZIP(), []int{17} } func (x *RouterLeaseResponse) GetSuccess() bool { @@ -1838,7 +1921,7 @@ type PolicyRole struct { func (x *PolicyRole) Reset() { *x = PolicyRole{} - mi := &file_api_agentmesh_proto_msgTypes[17] + mi := &file_api_agentmesh_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1850,7 +1933,7 @@ func (x *PolicyRole) String() string { func (*PolicyRole) ProtoMessage() {} func (x *PolicyRole) ProtoReflect() protoreflect.Message { - mi := &file_api_agentmesh_proto_msgTypes[17] + mi := &file_api_agentmesh_proto_msgTypes[18] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1863,7 +1946,7 @@ func (x *PolicyRole) ProtoReflect() protoreflect.Message { // Deprecated: Use PolicyRole.ProtoReflect.Descriptor instead. func (*PolicyRole) Descriptor() ([]byte, []int) { - return file_api_agentmesh_proto_rawDescGZIP(), []int{17} + return file_api_agentmesh_proto_rawDescGZIP(), []int{18} } func (x *PolicyRole) GetName() string { @@ -1930,7 +2013,7 @@ type HTTPGrant struct { func (x *HTTPGrant) Reset() { *x = HTTPGrant{} - mi := &file_api_agentmesh_proto_msgTypes[18] + mi := &file_api_agentmesh_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1942,7 +2025,7 @@ func (x *HTTPGrant) String() string { func (*HTTPGrant) ProtoMessage() {} func (x *HTTPGrant) ProtoReflect() protoreflect.Message { - mi := &file_api_agentmesh_proto_msgTypes[18] + mi := &file_api_agentmesh_proto_msgTypes[19] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1955,7 +2038,7 @@ func (x *HTTPGrant) ProtoReflect() protoreflect.Message { // Deprecated: Use HTTPGrant.ProtoReflect.Descriptor instead. func (*HTTPGrant) Descriptor() ([]byte, []int) { - return file_api_agentmesh_proto_rawDescGZIP(), []int{18} + return file_api_agentmesh_proto_rawDescGZIP(), []int{19} } func (x *HTTPGrant) GetService() string { @@ -2023,7 +2106,7 @@ type EgressDestination struct { func (x *EgressDestination) Reset() { *x = EgressDestination{} - mi := &file_api_agentmesh_proto_msgTypes[19] + mi := &file_api_agentmesh_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2035,7 +2118,7 @@ func (x *EgressDestination) String() string { func (*EgressDestination) ProtoMessage() {} func (x *EgressDestination) ProtoReflect() protoreflect.Message { - mi := &file_api_agentmesh_proto_msgTypes[19] + mi := &file_api_agentmesh_proto_msgTypes[20] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2048,7 +2131,7 @@ func (x *EgressDestination) ProtoReflect() protoreflect.Message { // Deprecated: Use EgressDestination.ProtoReflect.Descriptor instead. func (*EgressDestination) Descriptor() ([]byte, []int) { - return file_api_agentmesh_proto_rawDescGZIP(), []int{19} + return file_api_agentmesh_proto_rawDescGZIP(), []int{20} } func (x *EgressDestination) GetName() string { @@ -2133,7 +2216,7 @@ type Inspection struct { func (x *Inspection) Reset() { *x = Inspection{} - mi := &file_api_agentmesh_proto_msgTypes[20] + mi := &file_api_agentmesh_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2145,7 +2228,7 @@ func (x *Inspection) String() string { func (*Inspection) ProtoMessage() {} func (x *Inspection) ProtoReflect() protoreflect.Message { - mi := &file_api_agentmesh_proto_msgTypes[20] + mi := &file_api_agentmesh_proto_msgTypes[21] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2158,7 +2241,7 @@ func (x *Inspection) ProtoReflect() protoreflect.Message { // Deprecated: Use Inspection.ProtoReflect.Descriptor instead. func (*Inspection) Descriptor() ([]byte, []int) { - return file_api_agentmesh_proto_rawDescGZIP(), []int{20} + return file_api_agentmesh_proto_rawDescGZIP(), []int{21} } func (x *Inspection) GetInspectors() []*Inspector { @@ -2181,7 +2264,7 @@ type Inspector struct { func (x *Inspector) Reset() { *x = Inspector{} - mi := &file_api_agentmesh_proto_msgTypes[21] + mi := &file_api_agentmesh_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2193,7 +2276,7 @@ func (x *Inspector) String() string { func (*Inspector) ProtoMessage() {} func (x *Inspector) ProtoReflect() protoreflect.Message { - mi := &file_api_agentmesh_proto_msgTypes[21] + mi := &file_api_agentmesh_proto_msgTypes[22] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2206,7 +2289,7 @@ func (x *Inspector) ProtoReflect() protoreflect.Message { // Deprecated: Use Inspector.ProtoReflect.Descriptor instead. func (*Inspector) Descriptor() ([]byte, []int) { - return file_api_agentmesh_proto_rawDescGZIP(), []int{21} + return file_api_agentmesh_proto_rawDescGZIP(), []int{22} } func (x *Inspector) GetKind() isInspector_Kind { @@ -2271,7 +2354,7 @@ type ModelArmor struct { func (x *ModelArmor) Reset() { *x = ModelArmor{} - mi := &file_api_agentmesh_proto_msgTypes[22] + mi := &file_api_agentmesh_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2283,7 +2366,7 @@ func (x *ModelArmor) String() string { func (*ModelArmor) ProtoMessage() {} func (x *ModelArmor) ProtoReflect() protoreflect.Message { - mi := &file_api_agentmesh_proto_msgTypes[22] + mi := &file_api_agentmesh_proto_msgTypes[23] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2296,7 +2379,7 @@ func (x *ModelArmor) ProtoReflect() protoreflect.Message { // Deprecated: Use ModelArmor.ProtoReflect.Descriptor instead. func (*ModelArmor) Descriptor() ([]byte, []int) { - return file_api_agentmesh_proto_rawDescGZIP(), []int{22} + return file_api_agentmesh_proto_rawDescGZIP(), []int{23} } func (x *ModelArmor) GetTemplate() string { @@ -2354,7 +2437,7 @@ type ExtProc struct { func (x *ExtProc) Reset() { *x = ExtProc{} - mi := &file_api_agentmesh_proto_msgTypes[23] + mi := &file_api_agentmesh_proto_msgTypes[24] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2366,7 +2449,7 @@ func (x *ExtProc) String() string { func (*ExtProc) ProtoMessage() {} func (x *ExtProc) ProtoReflect() protoreflect.Message { - mi := &file_api_agentmesh_proto_msgTypes[23] + mi := &file_api_agentmesh_proto_msgTypes[24] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2379,7 +2462,7 @@ func (x *ExtProc) ProtoReflect() protoreflect.Message { // Deprecated: Use ExtProc.ProtoReflect.Descriptor instead. func (*ExtProc) Descriptor() ([]byte, []int) { - return file_api_agentmesh_proto_rawDescGZIP(), []int{23} + return file_api_agentmesh_proto_rawDescGZIP(), []int{24} } func (x *ExtProc) GetTarget() string { @@ -2452,7 +2535,7 @@ type ExtProcProcessingMode struct { func (x *ExtProcProcessingMode) Reset() { *x = ExtProcProcessingMode{} - mi := &file_api_agentmesh_proto_msgTypes[24] + mi := &file_api_agentmesh_proto_msgTypes[25] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2464,7 +2547,7 @@ func (x *ExtProcProcessingMode) String() string { func (*ExtProcProcessingMode) ProtoMessage() {} func (x *ExtProcProcessingMode) ProtoReflect() protoreflect.Message { - mi := &file_api_agentmesh_proto_msgTypes[24] + mi := &file_api_agentmesh_proto_msgTypes[25] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2477,7 +2560,7 @@ func (x *ExtProcProcessingMode) ProtoReflect() protoreflect.Message { // Deprecated: Use ExtProcProcessingMode.ProtoReflect.Descriptor instead. func (*ExtProcProcessingMode) Descriptor() ([]byte, []int) { - return file_api_agentmesh_proto_rawDescGZIP(), []int{24} + return file_api_agentmesh_proto_rawDescGZIP(), []int{25} } func (x *ExtProcProcessingMode) GetRequestHeaderMode() ExtProcProcessingMode_HeaderMode { @@ -2537,7 +2620,7 @@ type CredentialBroker struct { func (x *CredentialBroker) Reset() { *x = CredentialBroker{} - mi := &file_api_agentmesh_proto_msgTypes[25] + mi := &file_api_agentmesh_proto_msgTypes[26] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2549,7 +2632,7 @@ func (x *CredentialBroker) String() string { func (*CredentialBroker) ProtoMessage() {} func (x *CredentialBroker) ProtoReflect() protoreflect.Message { - mi := &file_api_agentmesh_proto_msgTypes[25] + mi := &file_api_agentmesh_proto_msgTypes[26] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2562,7 +2645,7 @@ func (x *CredentialBroker) ProtoReflect() protoreflect.Message { // Deprecated: Use CredentialBroker.ProtoReflect.Descriptor instead. func (*CredentialBroker) Descriptor() ([]byte, []int) { - return file_api_agentmesh_proto_rawDescGZIP(), []int{25} + return file_api_agentmesh_proto_rawDescGZIP(), []int{26} } func (x *CredentialBroker) GetKind() isCredentialBroker_Kind { @@ -2659,7 +2742,7 @@ type OIDCFederation struct { func (x *OIDCFederation) Reset() { *x = OIDCFederation{} - mi := &file_api_agentmesh_proto_msgTypes[26] + mi := &file_api_agentmesh_proto_msgTypes[27] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2671,7 +2754,7 @@ func (x *OIDCFederation) String() string { func (*OIDCFederation) ProtoMessage() {} func (x *OIDCFederation) ProtoReflect() protoreflect.Message { - mi := &file_api_agentmesh_proto_msgTypes[26] + mi := &file_api_agentmesh_proto_msgTypes[27] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2684,7 +2767,7 @@ func (x *OIDCFederation) ProtoReflect() protoreflect.Message { // Deprecated: Use OIDCFederation.ProtoReflect.Descriptor instead. func (*OIDCFederation) Descriptor() ([]byte, []int) { - return file_api_agentmesh_proto_rawDescGZIP(), []int{26} + return file_api_agentmesh_proto_rawDescGZIP(), []int{27} } func (x *OIDCFederation) GetTokenEndpoint() string { @@ -2726,7 +2809,7 @@ type AWSAssumeRole struct { func (x *AWSAssumeRole) Reset() { *x = AWSAssumeRole{} - mi := &file_api_agentmesh_proto_msgTypes[27] + mi := &file_api_agentmesh_proto_msgTypes[28] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2738,7 +2821,7 @@ func (x *AWSAssumeRole) String() string { func (*AWSAssumeRole) ProtoMessage() {} func (x *AWSAssumeRole) ProtoReflect() protoreflect.Message { - mi := &file_api_agentmesh_proto_msgTypes[27] + mi := &file_api_agentmesh_proto_msgTypes[28] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2751,7 +2834,7 @@ func (x *AWSAssumeRole) ProtoReflect() protoreflect.Message { // Deprecated: Use AWSAssumeRole.ProtoReflect.Descriptor instead. func (*AWSAssumeRole) Descriptor() ([]byte, []int) { - return file_api_agentmesh_proto_rawDescGZIP(), []int{27} + return file_api_agentmesh_proto_rawDescGZIP(), []int{28} } func (x *AWSAssumeRole) GetRoleArn() string { @@ -2777,7 +2860,7 @@ type PlatformIdentity struct { func (x *PlatformIdentity) Reset() { *x = PlatformIdentity{} - mi := &file_api_agentmesh_proto_msgTypes[28] + mi := &file_api_agentmesh_proto_msgTypes[29] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2789,7 +2872,7 @@ func (x *PlatformIdentity) String() string { func (*PlatformIdentity) ProtoMessage() {} func (x *PlatformIdentity) ProtoReflect() protoreflect.Message { - mi := &file_api_agentmesh_proto_msgTypes[28] + mi := &file_api_agentmesh_proto_msgTypes[29] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2802,7 +2885,7 @@ func (x *PlatformIdentity) ProtoReflect() protoreflect.Message { // Deprecated: Use PlatformIdentity.ProtoReflect.Descriptor instead. func (*PlatformIdentity) Descriptor() ([]byte, []int) { - return file_api_agentmesh_proto_rawDescGZIP(), []int{28} + return file_api_agentmesh_proto_rawDescGZIP(), []int{29} } func (x *PlatformIdentity) GetScopes() []string { @@ -2822,7 +2905,7 @@ type PolicyBinding struct { func (x *PolicyBinding) Reset() { *x = PolicyBinding{} - mi := &file_api_agentmesh_proto_msgTypes[29] + mi := &file_api_agentmesh_proto_msgTypes[30] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2834,7 +2917,7 @@ func (x *PolicyBinding) String() string { func (*PolicyBinding) ProtoMessage() {} func (x *PolicyBinding) ProtoReflect() protoreflect.Message { - mi := &file_api_agentmesh_proto_msgTypes[29] + mi := &file_api_agentmesh_proto_msgTypes[30] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2847,7 +2930,7 @@ func (x *PolicyBinding) ProtoReflect() protoreflect.Message { // Deprecated: Use PolicyBinding.ProtoReflect.Descriptor instead. func (*PolicyBinding) Descriptor() ([]byte, []int) { - return file_api_agentmesh_proto_rawDescGZIP(), []int{29} + return file_api_agentmesh_proto_rawDescGZIP(), []int{30} } func (x *PolicyBinding) GetRole() string { @@ -2879,7 +2962,7 @@ type PolicyConfig struct { func (x *PolicyConfig) Reset() { *x = PolicyConfig{} - mi := &file_api_agentmesh_proto_msgTypes[30] + mi := &file_api_agentmesh_proto_msgTypes[31] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2891,7 +2974,7 @@ func (x *PolicyConfig) String() string { func (*PolicyConfig) ProtoMessage() {} func (x *PolicyConfig) ProtoReflect() protoreflect.Message { - mi := &file_api_agentmesh_proto_msgTypes[30] + mi := &file_api_agentmesh_proto_msgTypes[31] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2904,7 +2987,7 @@ func (x *PolicyConfig) ProtoReflect() protoreflect.Message { // Deprecated: Use PolicyConfig.ProtoReflect.Descriptor instead. func (*PolicyConfig) Descriptor() ([]byte, []int) { - return file_api_agentmesh_proto_rawDescGZIP(), []int{30} + return file_api_agentmesh_proto_rawDescGZIP(), []int{31} } func (x *PolicyConfig) GetRoles() []*PolicyRole { @@ -2936,7 +3019,7 @@ type PolicyConfigGetRequest struct { func (x *PolicyConfigGetRequest) Reset() { *x = PolicyConfigGetRequest{} - mi := &file_api_agentmesh_proto_msgTypes[31] + mi := &file_api_agentmesh_proto_msgTypes[32] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2948,7 +3031,7 @@ func (x *PolicyConfigGetRequest) String() string { func (*PolicyConfigGetRequest) ProtoMessage() {} func (x *PolicyConfigGetRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_agentmesh_proto_msgTypes[31] + mi := &file_api_agentmesh_proto_msgTypes[32] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2961,7 +3044,7 @@ func (x *PolicyConfigGetRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use PolicyConfigGetRequest.ProtoReflect.Descriptor instead. func (*PolicyConfigGetRequest) Descriptor() ([]byte, []int) { - return file_api_agentmesh_proto_rawDescGZIP(), []int{31} + return file_api_agentmesh_proto_rawDescGZIP(), []int{32} } // PolicyConfigGetResponse answers GET /policies for a mesh member holding a @@ -2977,7 +3060,7 @@ type PolicyConfigGetResponse struct { func (x *PolicyConfigGetResponse) Reset() { *x = PolicyConfigGetResponse{} - mi := &file_api_agentmesh_proto_msgTypes[32] + mi := &file_api_agentmesh_proto_msgTypes[33] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2989,7 +3072,7 @@ func (x *PolicyConfigGetResponse) String() string { func (*PolicyConfigGetResponse) ProtoMessage() {} func (x *PolicyConfigGetResponse) ProtoReflect() protoreflect.Message { - mi := &file_api_agentmesh_proto_msgTypes[32] + mi := &file_api_agentmesh_proto_msgTypes[33] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3002,7 +3085,7 @@ func (x *PolicyConfigGetResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use PolicyConfigGetResponse.ProtoReflect.Descriptor instead. func (*PolicyConfigGetResponse) Descriptor() ([]byte, []int) { - return file_api_agentmesh_proto_rawDescGZIP(), []int{32} + return file_api_agentmesh_proto_rawDescGZIP(), []int{33} } func (x *PolicyConfigGetResponse) GetDatalogRules() []string { @@ -3022,7 +3105,7 @@ type PolicyConfigUpdateResponse struct { func (x *PolicyConfigUpdateResponse) Reset() { *x = PolicyConfigUpdateResponse{} - mi := &file_api_agentmesh_proto_msgTypes[33] + mi := &file_api_agentmesh_proto_msgTypes[34] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3034,7 +3117,7 @@ func (x *PolicyConfigUpdateResponse) String() string { func (*PolicyConfigUpdateResponse) ProtoMessage() {} func (x *PolicyConfigUpdateResponse) ProtoReflect() protoreflect.Message { - mi := &file_api_agentmesh_proto_msgTypes[33] + mi := &file_api_agentmesh_proto_msgTypes[34] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3047,7 +3130,7 @@ func (x *PolicyConfigUpdateResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use PolicyConfigUpdateResponse.ProtoReflect.Descriptor instead. func (*PolicyConfigUpdateResponse) Descriptor() ([]byte, []int) { - return file_api_agentmesh_proto_rawDescGZIP(), []int{33} + return file_api_agentmesh_proto_rawDescGZIP(), []int{34} } func (x *PolicyConfigUpdateResponse) GetSuccess() bool { @@ -3072,7 +3155,7 @@ type EgressAssignmentsRequest struct { func (x *EgressAssignmentsRequest) Reset() { *x = EgressAssignmentsRequest{} - mi := &file_api_agentmesh_proto_msgTypes[34] + mi := &file_api_agentmesh_proto_msgTypes[35] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3084,7 +3167,7 @@ func (x *EgressAssignmentsRequest) String() string { func (*EgressAssignmentsRequest) ProtoMessage() {} func (x *EgressAssignmentsRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_agentmesh_proto_msgTypes[34] + mi := &file_api_agentmesh_proto_msgTypes[35] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3097,7 +3180,7 @@ func (x *EgressAssignmentsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use EgressAssignmentsRequest.ProtoReflect.Descriptor instead. func (*EgressAssignmentsRequest) Descriptor() ([]byte, []int) { - return file_api_agentmesh_proto_rawDescGZIP(), []int{34} + return file_api_agentmesh_proto_rawDescGZIP(), []int{35} } // EgressAssignmentsResponse answers GET /egress for a mesh member holding a @@ -3113,7 +3196,7 @@ type EgressAssignmentsResponse struct { func (x *EgressAssignmentsResponse) Reset() { *x = EgressAssignmentsResponse{} - mi := &file_api_agentmesh_proto_msgTypes[35] + mi := &file_api_agentmesh_proto_msgTypes[36] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3125,7 +3208,7 @@ func (x *EgressAssignmentsResponse) String() string { func (*EgressAssignmentsResponse) ProtoMessage() {} func (x *EgressAssignmentsResponse) ProtoReflect() protoreflect.Message { - mi := &file_api_agentmesh_proto_msgTypes[35] + mi := &file_api_agentmesh_proto_msgTypes[36] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3138,7 +3221,7 @@ func (x *EgressAssignmentsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use EgressAssignmentsResponse.ProtoReflect.Descriptor instead. func (*EgressAssignmentsResponse) Descriptor() ([]byte, []int) { - return file_api_agentmesh_proto_rawDescGZIP(), []int{35} + return file_api_agentmesh_proto_rawDescGZIP(), []int{36} } func (x *EgressAssignmentsResponse) GetEgress() []*EgressDestination { @@ -3165,7 +3248,7 @@ type KeysResponse struct { func (x *KeysResponse) Reset() { *x = KeysResponse{} - mi := &file_api_agentmesh_proto_msgTypes[36] + mi := &file_api_agentmesh_proto_msgTypes[37] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3177,7 +3260,7 @@ func (x *KeysResponse) String() string { func (*KeysResponse) ProtoMessage() {} func (x *KeysResponse) ProtoReflect() protoreflect.Message { - mi := &file_api_agentmesh_proto_msgTypes[36] + mi := &file_api_agentmesh_proto_msgTypes[37] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3190,7 +3273,7 @@ func (x *KeysResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use KeysResponse.ProtoReflect.Descriptor instead. func (*KeysResponse) Descriptor() ([]byte, []int) { - return file_api_agentmesh_proto_rawDescGZIP(), []int{36} + return file_api_agentmesh_proto_rawDescGZIP(), []int{37} } func (x *KeysResponse) GetPublicKeys() [][]byte { @@ -3243,7 +3326,7 @@ type TokenRefreshRequest struct { func (x *TokenRefreshRequest) Reset() { *x = TokenRefreshRequest{} - mi := &file_api_agentmesh_proto_msgTypes[37] + mi := &file_api_agentmesh_proto_msgTypes[38] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3255,7 +3338,7 @@ func (x *TokenRefreshRequest) String() string { func (*TokenRefreshRequest) ProtoMessage() {} func (x *TokenRefreshRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_agentmesh_proto_msgTypes[37] + mi := &file_api_agentmesh_proto_msgTypes[38] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3268,7 +3351,7 @@ func (x *TokenRefreshRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use TokenRefreshRequest.ProtoReflect.Descriptor instead. func (*TokenRefreshRequest) Descriptor() ([]byte, []int) { - return file_api_agentmesh_proto_rawDescGZIP(), []int{37} + return file_api_agentmesh_proto_rawDescGZIP(), []int{38} } func (x *TokenRefreshRequest) GetChallengeSignature() []byte { @@ -3310,7 +3393,7 @@ type TokenRefreshResponse struct { func (x *TokenRefreshResponse) Reset() { *x = TokenRefreshResponse{} - mi := &file_api_agentmesh_proto_msgTypes[38] + mi := &file_api_agentmesh_proto_msgTypes[39] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3322,7 +3405,7 @@ func (x *TokenRefreshResponse) String() string { func (*TokenRefreshResponse) ProtoMessage() {} func (x *TokenRefreshResponse) ProtoReflect() protoreflect.Message { - mi := &file_api_agentmesh_proto_msgTypes[38] + mi := &file_api_agentmesh_proto_msgTypes[39] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3335,7 +3418,7 @@ func (x *TokenRefreshResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use TokenRefreshResponse.ProtoReflect.Descriptor instead. func (*TokenRefreshResponse) Descriptor() ([]byte, []int) { - return file_api_agentmesh_proto_rawDescGZIP(), []int{38} + return file_api_agentmesh_proto_rawDescGZIP(), []int{39} } func (x *TokenRefreshResponse) GetBiscuitToken() []byte { @@ -3372,7 +3455,7 @@ type NodeCatalogReport struct { func (x *NodeCatalogReport) Reset() { *x = NodeCatalogReport{} - mi := &file_api_agentmesh_proto_msgTypes[39] + mi := &file_api_agentmesh_proto_msgTypes[40] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3384,7 +3467,7 @@ func (x *NodeCatalogReport) String() string { func (*NodeCatalogReport) ProtoMessage() {} func (x *NodeCatalogReport) ProtoReflect() protoreflect.Message { - mi := &file_api_agentmesh_proto_msgTypes[39] + mi := &file_api_agentmesh_proto_msgTypes[40] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3397,7 +3480,7 @@ func (x *NodeCatalogReport) ProtoReflect() protoreflect.Message { // Deprecated: Use NodeCatalogReport.ProtoReflect.Descriptor instead. func (*NodeCatalogReport) Descriptor() ([]byte, []int) { - return file_api_agentmesh_proto_rawDescGZIP(), []int{39} + return file_api_agentmesh_proto_rawDescGZIP(), []int{40} } func (x *NodeCatalogReport) GetServices() []*ServiceInfo { @@ -3416,7 +3499,7 @@ type TokenRevokeRequest struct { func (x *TokenRevokeRequest) Reset() { *x = TokenRevokeRequest{} - mi := &file_api_agentmesh_proto_msgTypes[40] + mi := &file_api_agentmesh_proto_msgTypes[41] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3428,7 +3511,7 @@ func (x *TokenRevokeRequest) String() string { func (*TokenRevokeRequest) ProtoMessage() {} func (x *TokenRevokeRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_agentmesh_proto_msgTypes[40] + mi := &file_api_agentmesh_proto_msgTypes[41] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3441,7 +3524,7 @@ func (x *TokenRevokeRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use TokenRevokeRequest.ProtoReflect.Descriptor instead. func (*TokenRevokeRequest) Descriptor() ([]byte, []int) { - return file_api_agentmesh_proto_rawDescGZIP(), []int{40} + return file_api_agentmesh_proto_rawDescGZIP(), []int{41} } func (x *TokenRevokeRequest) GetPeerId() string { @@ -3461,7 +3544,7 @@ type TokenRevokeResponse struct { func (x *TokenRevokeResponse) Reset() { *x = TokenRevokeResponse{} - mi := &file_api_agentmesh_proto_msgTypes[41] + mi := &file_api_agentmesh_proto_msgTypes[42] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3473,7 +3556,7 @@ func (x *TokenRevokeResponse) String() string { func (*TokenRevokeResponse) ProtoMessage() {} func (x *TokenRevokeResponse) ProtoReflect() protoreflect.Message { - mi := &file_api_agentmesh_proto_msgTypes[41] + mi := &file_api_agentmesh_proto_msgTypes[42] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3486,7 +3569,7 @@ func (x *TokenRevokeResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use TokenRevokeResponse.ProtoReflect.Descriptor instead. func (*TokenRevokeResponse) Descriptor() ([]byte, []int) { - return file_api_agentmesh_proto_rawDescGZIP(), []int{41} + return file_api_agentmesh_proto_rawDescGZIP(), []int{42} } func (x *TokenRevokeResponse) GetSuccess() bool { @@ -3531,7 +3614,7 @@ type BootstrapTokenCreateRequest struct { func (x *BootstrapTokenCreateRequest) Reset() { *x = BootstrapTokenCreateRequest{} - mi := &file_api_agentmesh_proto_msgTypes[42] + mi := &file_api_agentmesh_proto_msgTypes[43] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3543,7 +3626,7 @@ func (x *BootstrapTokenCreateRequest) String() string { func (*BootstrapTokenCreateRequest) ProtoMessage() {} func (x *BootstrapTokenCreateRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_agentmesh_proto_msgTypes[42] + mi := &file_api_agentmesh_proto_msgTypes[43] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3556,7 +3639,7 @@ func (x *BootstrapTokenCreateRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use BootstrapTokenCreateRequest.ProtoReflect.Descriptor instead. func (*BootstrapTokenCreateRequest) Descriptor() ([]byte, []int) { - return file_api_agentmesh_proto_rawDescGZIP(), []int{42} + return file_api_agentmesh_proto_rawDescGZIP(), []int{43} } func (x *BootstrapTokenCreateRequest) GetRole() string { @@ -3617,7 +3700,7 @@ type BootstrapTokenCreateResponse struct { func (x *BootstrapTokenCreateResponse) Reset() { *x = BootstrapTokenCreateResponse{} - mi := &file_api_agentmesh_proto_msgTypes[43] + mi := &file_api_agentmesh_proto_msgTypes[44] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3629,7 +3712,7 @@ func (x *BootstrapTokenCreateResponse) String() string { func (*BootstrapTokenCreateResponse) ProtoMessage() {} func (x *BootstrapTokenCreateResponse) ProtoReflect() protoreflect.Message { - mi := &file_api_agentmesh_proto_msgTypes[43] + mi := &file_api_agentmesh_proto_msgTypes[44] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3642,7 +3725,7 @@ func (x *BootstrapTokenCreateResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use BootstrapTokenCreateResponse.ProtoReflect.Descriptor instead. func (*BootstrapTokenCreateResponse) Descriptor() ([]byte, []int) { - return file_api_agentmesh_proto_rawDescGZIP(), []int{43} + return file_api_agentmesh_proto_rawDescGZIP(), []int{44} } func (x *BootstrapTokenCreateResponse) GetId() string { @@ -3701,7 +3784,7 @@ type BootstrapToken struct { func (x *BootstrapToken) Reset() { *x = BootstrapToken{} - mi := &file_api_agentmesh_proto_msgTypes[44] + mi := &file_api_agentmesh_proto_msgTypes[45] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3713,7 +3796,7 @@ func (x *BootstrapToken) String() string { func (*BootstrapToken) ProtoMessage() {} func (x *BootstrapToken) ProtoReflect() protoreflect.Message { - mi := &file_api_agentmesh_proto_msgTypes[44] + mi := &file_api_agentmesh_proto_msgTypes[45] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3726,7 +3809,7 @@ func (x *BootstrapToken) ProtoReflect() protoreflect.Message { // Deprecated: Use BootstrapToken.ProtoReflect.Descriptor instead. func (*BootstrapToken) Descriptor() ([]byte, []int) { - return file_api_agentmesh_proto_rawDescGZIP(), []int{44} + return file_api_agentmesh_proto_rawDescGZIP(), []int{45} } func (x *BootstrapToken) GetId() string { @@ -3808,7 +3891,7 @@ type BootstrapTokenListResponse struct { func (x *BootstrapTokenListResponse) Reset() { *x = BootstrapTokenListResponse{} - mi := &file_api_agentmesh_proto_msgTypes[45] + mi := &file_api_agentmesh_proto_msgTypes[46] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3820,7 +3903,7 @@ func (x *BootstrapTokenListResponse) String() string { func (*BootstrapTokenListResponse) ProtoMessage() {} func (x *BootstrapTokenListResponse) ProtoReflect() protoreflect.Message { - mi := &file_api_agentmesh_proto_msgTypes[45] + mi := &file_api_agentmesh_proto_msgTypes[46] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3833,7 +3916,7 @@ func (x *BootstrapTokenListResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use BootstrapTokenListResponse.ProtoReflect.Descriptor instead. func (*BootstrapTokenListResponse) Descriptor() ([]byte, []int) { - return file_api_agentmesh_proto_rawDescGZIP(), []int{45} + return file_api_agentmesh_proto_rawDescGZIP(), []int{46} } func (x *BootstrapTokenListResponse) GetTokens() []*BootstrapToken { @@ -3864,7 +3947,7 @@ type EnrollmentRequest struct { func (x *EnrollmentRequest) Reset() { *x = EnrollmentRequest{} - mi := &file_api_agentmesh_proto_msgTypes[46] + mi := &file_api_agentmesh_proto_msgTypes[47] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3876,7 +3959,7 @@ func (x *EnrollmentRequest) String() string { func (*EnrollmentRequest) ProtoMessage() {} func (x *EnrollmentRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_agentmesh_proto_msgTypes[46] + mi := &file_api_agentmesh_proto_msgTypes[47] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3889,7 +3972,7 @@ func (x *EnrollmentRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use EnrollmentRequest.ProtoReflect.Descriptor instead. func (*EnrollmentRequest) Descriptor() ([]byte, []int) { - return file_api_agentmesh_proto_rawDescGZIP(), []int{46} + return file_api_agentmesh_proto_rawDescGZIP(), []int{47} } func (x *EnrollmentRequest) GetId() string { @@ -3957,7 +4040,7 @@ type EnrollmentRequestListResponse struct { func (x *EnrollmentRequestListResponse) Reset() { *x = EnrollmentRequestListResponse{} - mi := &file_api_agentmesh_proto_msgTypes[47] + mi := &file_api_agentmesh_proto_msgTypes[48] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3969,7 +4052,7 @@ func (x *EnrollmentRequestListResponse) String() string { func (*EnrollmentRequestListResponse) ProtoMessage() {} func (x *EnrollmentRequestListResponse) ProtoReflect() protoreflect.Message { - mi := &file_api_agentmesh_proto_msgTypes[47] + mi := &file_api_agentmesh_proto_msgTypes[48] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3982,7 +4065,7 @@ func (x *EnrollmentRequestListResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use EnrollmentRequestListResponse.ProtoReflect.Descriptor instead. func (*EnrollmentRequestListResponse) Descriptor() ([]byte, []int) { - return file_api_agentmesh_proto_rawDescGZIP(), []int{47} + return file_api_agentmesh_proto_rawDescGZIP(), []int{48} } func (x *EnrollmentRequestListResponse) GetRequests() []*EnrollmentRequest { @@ -4008,7 +4091,7 @@ type User struct { func (x *User) Reset() { *x = User{} - mi := &file_api_agentmesh_proto_msgTypes[48] + mi := &file_api_agentmesh_proto_msgTypes[49] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4020,7 +4103,7 @@ func (x *User) String() string { func (*User) ProtoMessage() {} func (x *User) ProtoReflect() protoreflect.Message { - mi := &file_api_agentmesh_proto_msgTypes[48] + mi := &file_api_agentmesh_proto_msgTypes[49] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4033,7 +4116,7 @@ func (x *User) ProtoReflect() protoreflect.Message { // Deprecated: Use User.ProtoReflect.Descriptor instead. func (*User) Descriptor() ([]byte, []int) { - return file_api_agentmesh_proto_rawDescGZIP(), []int{48} + return file_api_agentmesh_proto_rawDescGZIP(), []int{49} } func (x *User) GetId() string { @@ -4093,7 +4176,7 @@ type EnrolledNode struct { func (x *EnrolledNode) Reset() { *x = EnrolledNode{} - mi := &file_api_agentmesh_proto_msgTypes[49] + mi := &file_api_agentmesh_proto_msgTypes[50] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4105,7 +4188,7 @@ func (x *EnrolledNode) String() string { func (*EnrolledNode) ProtoMessage() {} func (x *EnrolledNode) ProtoReflect() protoreflect.Message { - mi := &file_api_agentmesh_proto_msgTypes[49] + mi := &file_api_agentmesh_proto_msgTypes[50] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4118,7 +4201,7 @@ func (x *EnrolledNode) ProtoReflect() protoreflect.Message { // Deprecated: Use EnrolledNode.ProtoReflect.Descriptor instead. func (*EnrolledNode) Descriptor() ([]byte, []int) { - return file_api_agentmesh_proto_rawDescGZIP(), []int{49} + return file_api_agentmesh_proto_rawDescGZIP(), []int{50} } func (x *EnrolledNode) GetPeerId() string { @@ -4207,7 +4290,7 @@ type RouterLease struct { func (x *RouterLease) Reset() { *x = RouterLease{} - mi := &file_api_agentmesh_proto_msgTypes[50] + mi := &file_api_agentmesh_proto_msgTypes[51] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4219,7 +4302,7 @@ func (x *RouterLease) String() string { func (*RouterLease) ProtoMessage() {} func (x *RouterLease) ProtoReflect() protoreflect.Message { - mi := &file_api_agentmesh_proto_msgTypes[50] + mi := &file_api_agentmesh_proto_msgTypes[51] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4232,7 +4315,7 @@ func (x *RouterLease) ProtoReflect() protoreflect.Message { // Deprecated: Use RouterLease.ProtoReflect.Descriptor instead. func (*RouterLease) Descriptor() ([]byte, []int) { - return file_api_agentmesh_proto_rawDescGZIP(), []int{50} + return file_api_agentmesh_proto_rawDescGZIP(), []int{51} } func (x *RouterLease) GetPeerId() string { @@ -4289,7 +4372,7 @@ type NodeServices struct { func (x *NodeServices) Reset() { *x = NodeServices{} - mi := &file_api_agentmesh_proto_msgTypes[51] + mi := &file_api_agentmesh_proto_msgTypes[52] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4301,7 +4384,7 @@ func (x *NodeServices) String() string { func (*NodeServices) ProtoMessage() {} func (x *NodeServices) ProtoReflect() protoreflect.Message { - mi := &file_api_agentmesh_proto_msgTypes[51] + mi := &file_api_agentmesh_proto_msgTypes[52] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4314,7 +4397,7 @@ func (x *NodeServices) ProtoReflect() protoreflect.Message { // Deprecated: Use NodeServices.ProtoReflect.Descriptor instead. func (*NodeServices) Descriptor() ([]byte, []int) { - return file_api_agentmesh_proto_rawDescGZIP(), []int{51} + return file_api_agentmesh_proto_rawDescGZIP(), []int{52} } func (x *NodeServices) GetServices() []*ServiceInfo { @@ -4349,7 +4432,7 @@ type AdminStatusResponse struct { func (x *AdminStatusResponse) Reset() { *x = AdminStatusResponse{} - mi := &file_api_agentmesh_proto_msgTypes[52] + mi := &file_api_agentmesh_proto_msgTypes[53] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4361,7 +4444,7 @@ func (x *AdminStatusResponse) String() string { func (*AdminStatusResponse) ProtoMessage() {} func (x *AdminStatusResponse) ProtoReflect() protoreflect.Message { - mi := &file_api_agentmesh_proto_msgTypes[52] + mi := &file_api_agentmesh_proto_msgTypes[53] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4374,7 +4457,7 @@ func (x *AdminStatusResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use AdminStatusResponse.ProtoReflect.Descriptor instead. func (*AdminStatusResponse) Descriptor() ([]byte, []int) { - return file_api_agentmesh_proto_rawDescGZIP(), []int{52} + return file_api_agentmesh_proto_rawDescGZIP(), []int{53} } func (x *AdminStatusResponse) GetUsers() []*User { @@ -4442,7 +4525,7 @@ type UserStatusResponse struct { func (x *UserStatusResponse) Reset() { *x = UserStatusResponse{} - mi := &file_api_agentmesh_proto_msgTypes[53] + mi := &file_api_agentmesh_proto_msgTypes[54] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4454,7 +4537,7 @@ func (x *UserStatusResponse) String() string { func (*UserStatusResponse) ProtoMessage() {} func (x *UserStatusResponse) ProtoReflect() protoreflect.Message { - mi := &file_api_agentmesh_proto_msgTypes[53] + mi := &file_api_agentmesh_proto_msgTypes[54] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4467,7 +4550,7 @@ func (x *UserStatusResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UserStatusResponse.ProtoReflect.Descriptor instead. func (*UserStatusResponse) Descriptor() ([]byte, []int) { - return file_api_agentmesh_proto_rawDescGZIP(), []int{53} + return file_api_agentmesh_proto_rawDescGZIP(), []int{54} } func (x *UserStatusResponse) GetUser() *User { @@ -4519,7 +4602,7 @@ type IdentityEvidenceResponse struct { func (x *IdentityEvidenceResponse) Reset() { *x = IdentityEvidenceResponse{} - mi := &file_api_agentmesh_proto_msgTypes[54] + mi := &file_api_agentmesh_proto_msgTypes[55] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4531,7 +4614,7 @@ func (x *IdentityEvidenceResponse) String() string { func (*IdentityEvidenceResponse) ProtoMessage() {} func (x *IdentityEvidenceResponse) ProtoReflect() protoreflect.Message { - mi := &file_api_agentmesh_proto_msgTypes[54] + mi := &file_api_agentmesh_proto_msgTypes[55] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4544,7 +4627,7 @@ func (x *IdentityEvidenceResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use IdentityEvidenceResponse.ProtoReflect.Descriptor instead. func (*IdentityEvidenceResponse) Descriptor() ([]byte, []int) { - return file_api_agentmesh_proto_rawDescGZIP(), []int{54} + return file_api_agentmesh_proto_rawDescGZIP(), []int{55} } func (x *IdentityEvidenceResponse) GetPeerId() string { @@ -4605,7 +4688,7 @@ type PeerEvidenceResponse struct { func (x *PeerEvidenceResponse) Reset() { *x = PeerEvidenceResponse{} - mi := &file_api_agentmesh_proto_msgTypes[55] + mi := &file_api_agentmesh_proto_msgTypes[56] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4617,7 +4700,7 @@ func (x *PeerEvidenceResponse) String() string { func (*PeerEvidenceResponse) ProtoMessage() {} func (x *PeerEvidenceResponse) ProtoReflect() protoreflect.Message { - mi := &file_api_agentmesh_proto_msgTypes[55] + mi := &file_api_agentmesh_proto_msgTypes[56] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4630,7 +4713,7 @@ func (x *PeerEvidenceResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use PeerEvidenceResponse.ProtoReflect.Descriptor instead. func (*PeerEvidenceResponse) Descriptor() ([]byte, []int) { - return file_api_agentmesh_proto_rawDescGZIP(), []int{55} + return file_api_agentmesh_proto_rawDescGZIP(), []int{56} } func (x *PeerEvidenceResponse) GetPeerId() string { @@ -4715,7 +4798,7 @@ type MemberCredential struct { func (x *MemberCredential) Reset() { *x = MemberCredential{} - mi := &file_api_agentmesh_proto_msgTypes[56] + mi := &file_api_agentmesh_proto_msgTypes[57] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4727,7 +4810,7 @@ func (x *MemberCredential) String() string { func (*MemberCredential) ProtoMessage() {} func (x *MemberCredential) ProtoReflect() protoreflect.Message { - mi := &file_api_agentmesh_proto_msgTypes[56] + mi := &file_api_agentmesh_proto_msgTypes[57] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4740,7 +4823,7 @@ func (x *MemberCredential) ProtoReflect() protoreflect.Message { // Deprecated: Use MemberCredential.ProtoReflect.Descriptor instead. func (*MemberCredential) Descriptor() ([]byte, []int) { - return file_api_agentmesh_proto_rawDescGZIP(), []int{56} + return file_api_agentmesh_proto_rawDescGZIP(), []int{57} } func (x *MemberCredential) GetControlPlaneUrl() string { @@ -4805,7 +4888,7 @@ type TrustedSigningKey struct { func (x *TrustedSigningKey) Reset() { *x = TrustedSigningKey{} - mi := &file_api_agentmesh_proto_msgTypes[57] + mi := &file_api_agentmesh_proto_msgTypes[58] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4817,7 +4900,7 @@ func (x *TrustedSigningKey) String() string { func (*TrustedSigningKey) ProtoMessage() {} func (x *TrustedSigningKey) ProtoReflect() protoreflect.Message { - mi := &file_api_agentmesh_proto_msgTypes[57] + mi := &file_api_agentmesh_proto_msgTypes[58] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4830,7 +4913,7 @@ func (x *TrustedSigningKey) ProtoReflect() protoreflect.Message { // Deprecated: Use TrustedSigningKey.ProtoReflect.Descriptor instead. func (*TrustedSigningKey) Descriptor() ([]byte, []int) { - return file_api_agentmesh_proto_rawDescGZIP(), []int{57} + return file_api_agentmesh_proto_rawDescGZIP(), []int{58} } func (x *TrustedSigningKey) GetPublicKey() []byte { @@ -4859,7 +4942,7 @@ type OIDCSession struct { func (x *OIDCSession) Reset() { *x = OIDCSession{} - mi := &file_api_agentmesh_proto_msgTypes[58] + mi := &file_api_agentmesh_proto_msgTypes[59] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4871,7 +4954,7 @@ func (x *OIDCSession) String() string { func (*OIDCSession) ProtoMessage() {} func (x *OIDCSession) ProtoReflect() protoreflect.Message { - mi := &file_api_agentmesh_proto_msgTypes[58] + mi := &file_api_agentmesh_proto_msgTypes[59] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4884,7 +4967,7 @@ func (x *OIDCSession) ProtoReflect() protoreflect.Message { // Deprecated: Use OIDCSession.ProtoReflect.Descriptor instead. func (*OIDCSession) Descriptor() ([]byte, []int) { - return file_api_agentmesh_proto_rawDescGZIP(), []int{58} + return file_api_agentmesh_proto_rawDescGZIP(), []int{59} } func (x *OIDCSession) GetIssuer() string { @@ -4936,7 +5019,7 @@ type TaskAuthorizationRule struct { func (x *TaskAuthorizationRule) Reset() { *x = TaskAuthorizationRule{} - mi := &file_api_agentmesh_proto_msgTypes[59] + mi := &file_api_agentmesh_proto_msgTypes[60] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4948,7 +5031,7 @@ func (x *TaskAuthorizationRule) String() string { func (*TaskAuthorizationRule) ProtoMessage() {} func (x *TaskAuthorizationRule) ProtoReflect() protoreflect.Message { - mi := &file_api_agentmesh_proto_msgTypes[59] + mi := &file_api_agentmesh_proto_msgTypes[60] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4961,7 +5044,7 @@ func (x *TaskAuthorizationRule) ProtoReflect() protoreflect.Message { // Deprecated: Use TaskAuthorizationRule.ProtoReflect.Descriptor instead. func (*TaskAuthorizationRule) Descriptor() ([]byte, []int) { - return file_api_agentmesh_proto_rawDescGZIP(), []int{59} + return file_api_agentmesh_proto_rawDescGZIP(), []int{60} } func (x *TaskAuthorizationRule) GetName() string { @@ -5013,7 +5096,7 @@ type TaskRule struct { func (x *TaskRule) Reset() { *x = TaskRule{} - mi := &file_api_agentmesh_proto_msgTypes[60] + mi := &file_api_agentmesh_proto_msgTypes[61] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5025,7 +5108,7 @@ func (x *TaskRule) String() string { func (*TaskRule) ProtoMessage() {} func (x *TaskRule) ProtoReflect() protoreflect.Message { - mi := &file_api_agentmesh_proto_msgTypes[60] + mi := &file_api_agentmesh_proto_msgTypes[61] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5038,7 +5121,7 @@ func (x *TaskRule) ProtoReflect() protoreflect.Message { // Deprecated: Use TaskRule.ProtoReflect.Descriptor instead. func (*TaskRule) Descriptor() ([]byte, []int) { - return file_api_agentmesh_proto_rawDescGZIP(), []int{60} + return file_api_agentmesh_proto_rawDescGZIP(), []int{61} } func (x *TaskRule) GetDescription() string { @@ -5087,7 +5170,7 @@ type TaskOperation struct { func (x *TaskOperation) Reset() { *x = TaskOperation{} - mi := &file_api_agentmesh_proto_msgTypes[61] + mi := &file_api_agentmesh_proto_msgTypes[62] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5099,7 +5182,7 @@ func (x *TaskOperation) String() string { func (*TaskOperation) ProtoMessage() {} func (x *TaskOperation) ProtoReflect() protoreflect.Message { - mi := &file_api_agentmesh_proto_msgTypes[61] + mi := &file_api_agentmesh_proto_msgTypes[62] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5112,7 +5195,7 @@ func (x *TaskOperation) ProtoReflect() protoreflect.Message { // Deprecated: Use TaskOperation.ProtoReflect.Descriptor instead. func (*TaskOperation) Descriptor() ([]byte, []int) { - return file_api_agentmesh_proto_rawDescGZIP(), []int{61} + return file_api_agentmesh_proto_rawDescGZIP(), []int{62} } func (x *TaskOperation) GetAllowedTools() []string { @@ -5172,7 +5255,7 @@ type TokenExchangeRequest struct { func (x *TokenExchangeRequest) Reset() { *x = TokenExchangeRequest{} - mi := &file_api_agentmesh_proto_msgTypes[62] + mi := &file_api_agentmesh_proto_msgTypes[63] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5184,7 +5267,7 @@ func (x *TokenExchangeRequest) String() string { func (*TokenExchangeRequest) ProtoMessage() {} func (x *TokenExchangeRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_agentmesh_proto_msgTypes[62] + mi := &file_api_agentmesh_proto_msgTypes[63] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5197,7 +5280,7 @@ func (x *TokenExchangeRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use TokenExchangeRequest.ProtoReflect.Descriptor instead. func (*TokenExchangeRequest) Descriptor() ([]byte, []int) { - return file_api_agentmesh_proto_rawDescGZIP(), []int{62} + return file_api_agentmesh_proto_rawDescGZIP(), []int{63} } func (x *TokenExchangeRequest) GetSubjectToken() string { @@ -5247,7 +5330,7 @@ type TokenExchangeResponse struct { func (x *TokenExchangeResponse) Reset() { *x = TokenExchangeResponse{} - mi := &file_api_agentmesh_proto_msgTypes[63] + mi := &file_api_agentmesh_proto_msgTypes[64] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5259,7 +5342,7 @@ func (x *TokenExchangeResponse) String() string { func (*TokenExchangeResponse) ProtoMessage() {} func (x *TokenExchangeResponse) ProtoReflect() protoreflect.Message { - mi := &file_api_agentmesh_proto_msgTypes[63] + mi := &file_api_agentmesh_proto_msgTypes[64] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5272,7 +5355,7 @@ func (x *TokenExchangeResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use TokenExchangeResponse.ProtoReflect.Descriptor instead. func (*TokenExchangeResponse) Descriptor() ([]byte, []int) { - return file_api_agentmesh_proto_rawDescGZIP(), []int{63} + return file_api_agentmesh_proto_rawDescGZIP(), []int{64} } func (x *TokenExchangeResponse) GetBiscuitToken() []byte { @@ -5328,7 +5411,7 @@ type STSTokenRequest struct { func (x *STSTokenRequest) Reset() { *x = STSTokenRequest{} - mi := &file_api_agentmesh_proto_msgTypes[64] + mi := &file_api_agentmesh_proto_msgTypes[65] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5340,7 +5423,7 @@ func (x *STSTokenRequest) String() string { func (*STSTokenRequest) ProtoMessage() {} func (x *STSTokenRequest) ProtoReflect() protoreflect.Message { - mi := &file_api_agentmesh_proto_msgTypes[64] + mi := &file_api_agentmesh_proto_msgTypes[65] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5353,7 +5436,7 @@ func (x *STSTokenRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use STSTokenRequest.ProtoReflect.Descriptor instead. func (*STSTokenRequest) Descriptor() ([]byte, []int) { - return file_api_agentmesh_proto_rawDescGZIP(), []int{64} + return file_api_agentmesh_proto_rawDescGZIP(), []int{65} } func (x *STSTokenRequest) GetBiscuit() []byte { @@ -5404,7 +5487,7 @@ type STSTokenResponse struct { func (x *STSTokenResponse) Reset() { *x = STSTokenResponse{} - mi := &file_api_agentmesh_proto_msgTypes[65] + mi := &file_api_agentmesh_proto_msgTypes[66] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5416,7 +5499,7 @@ func (x *STSTokenResponse) String() string { func (*STSTokenResponse) ProtoMessage() {} func (x *STSTokenResponse) ProtoReflect() protoreflect.Message { - mi := &file_api_agentmesh_proto_msgTypes[65] + mi := &file_api_agentmesh_proto_msgTypes[66] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5429,7 +5512,7 @@ func (x *STSTokenResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use STSTokenResponse.ProtoReflect.Descriptor instead. func (*STSTokenResponse) Descriptor() ([]byte, []int) { - return file_api_agentmesh_proto_rawDescGZIP(), []int{65} + return file_api_agentmesh_proto_rawDescGZIP(), []int{66} } func (x *STSTokenResponse) GetJwt() string { @@ -5480,7 +5563,7 @@ type RevocationsResponse struct { func (x *RevocationsResponse) Reset() { *x = RevocationsResponse{} - mi := &file_api_agentmesh_proto_msgTypes[66] + mi := &file_api_agentmesh_proto_msgTypes[67] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5492,7 +5575,7 @@ func (x *RevocationsResponse) String() string { func (*RevocationsResponse) ProtoMessage() {} func (x *RevocationsResponse) ProtoReflect() protoreflect.Message { - mi := &file_api_agentmesh_proto_msgTypes[66] + mi := &file_api_agentmesh_proto_msgTypes[67] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5505,7 +5588,7 @@ func (x *RevocationsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RevocationsResponse.ProtoReflect.Descriptor instead. func (*RevocationsResponse) Descriptor() ([]byte, []int) { - return file_api_agentmesh_proto_rawDescGZIP(), []int{66} + return file_api_agentmesh_proto_rawDescGZIP(), []int{67} } func (x *RevocationsResponse) GetRevocationIds() []string { @@ -5627,7 +5710,7 @@ const file_api_agentmesh_proto_rawDesc = "" + "\rannounce_time\x18\b \x01(\v2\x1a.google.protobuf.TimestampR\fannounceTime\x1a9\n" + "\vLabelsEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + - "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xfb\x01\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xbc\x02\n" + "\x18ControlPlaneInfoResponse\x12\x1f\n" + "\voidc_issuer\x18\x01 \x01(\tR\n" + "oidcIssuer\x12\x1b\n" + @@ -5635,7 +5718,13 @@ const file_api_agentmesh_proto_rawDesc = "" + "\baudience\x18\x03 \x01(\tR\baudience\x12)\n" + "\x10router_addresses\x18\x04 \x03(\tR\x0frouterAddresses\x12&\n" + "\x0fbanned_peer_ids\x18\x05 \x03(\tR\rbannedPeerIds\x122\n" + - "\arouters\x18\x06 \x03(\v2\x18.agentmesh.v1.RouterInfoR\arouters\"\x89\x02\n" + + "\arouters\x18\x06 \x03(\v2\x18.agentmesh.v1.RouterInfoR\arouters\x12?\n" + + "\tdirectory\x18\a \x01(\v2!.agentmesh.v1.DirectoryParametersR\tdirectory\"\xbb\x01\n" + + "\x13DirectoryParameters\x12 \n" + + "\vreplication\x18\x01 \x01(\x05R\vreplication\x12H\n" + + "\x12reprovide_interval\x18\x02 \x01(\v2\x19.google.protobuf.DurationR\x11reprovideInterval\x128\n" + + "\n" + + "record_ttl\x18\x03 \x01(\v2\x19.google.protobuf.DurationR\trecordTtl\"\x89\x02\n" + "\n" + "RouterInfo\x12\x17\n" + "\apeer_id\x18\x01 \x01(\tR\x06peerId\x12\x1c\n" + @@ -6010,7 +6099,7 @@ func file_api_agentmesh_proto_rawDescGZIP() []byte { } var file_api_agentmesh_proto_enumTypes = make([]protoimpl.EnumInfo, 8) -var file_api_agentmesh_proto_msgTypes = make([]protoimpl.MessageInfo, 76) +var file_api_agentmesh_proto_msgTypes = make([]protoimpl.MessageInfo, 77) var file_api_agentmesh_proto_goTypes = []any{ (EnrollmentStatus)(0), // 0: agentmesh.v1.EnrollmentStatus (ServiceType)(0), // 1: agentmesh.v1.ServiceType @@ -6034,170 +6123,174 @@ var file_api_agentmesh_proto_goTypes = []any{ (*DiscoveredProvider)(nil), // 19: agentmesh.v1.DiscoveredProvider (*ServiceAnnounce)(nil), // 20: agentmesh.v1.ServiceAnnounce (*ControlPlaneInfoResponse)(nil), // 21: agentmesh.v1.ControlPlaneInfoResponse - (*RouterInfo)(nil), // 22: agentmesh.v1.RouterInfo - (*RouterLeaseRequest)(nil), // 23: agentmesh.v1.RouterLeaseRequest - (*RouterLeaseResponse)(nil), // 24: agentmesh.v1.RouterLeaseResponse - (*PolicyRole)(nil), // 25: agentmesh.v1.PolicyRole - (*HTTPGrant)(nil), // 26: agentmesh.v1.HTTPGrant - (*EgressDestination)(nil), // 27: agentmesh.v1.EgressDestination - (*Inspection)(nil), // 28: agentmesh.v1.Inspection - (*Inspector)(nil), // 29: agentmesh.v1.Inspector - (*ModelArmor)(nil), // 30: agentmesh.v1.ModelArmor - (*ExtProc)(nil), // 31: agentmesh.v1.ExtProc - (*ExtProcProcessingMode)(nil), // 32: agentmesh.v1.ExtProcProcessingMode - (*CredentialBroker)(nil), // 33: agentmesh.v1.CredentialBroker - (*OIDCFederation)(nil), // 34: agentmesh.v1.OIDCFederation - (*AWSAssumeRole)(nil), // 35: agentmesh.v1.AWSAssumeRole - (*PlatformIdentity)(nil), // 36: agentmesh.v1.PlatformIdentity - (*PolicyBinding)(nil), // 37: agentmesh.v1.PolicyBinding - (*PolicyConfig)(nil), // 38: agentmesh.v1.PolicyConfig - (*PolicyConfigGetRequest)(nil), // 39: agentmesh.v1.PolicyConfigGetRequest - (*PolicyConfigGetResponse)(nil), // 40: agentmesh.v1.PolicyConfigGetResponse - (*PolicyConfigUpdateResponse)(nil), // 41: agentmesh.v1.PolicyConfigUpdateResponse - (*EgressAssignmentsRequest)(nil), // 42: agentmesh.v1.EgressAssignmentsRequest - (*EgressAssignmentsResponse)(nil), // 43: agentmesh.v1.EgressAssignmentsResponse - (*KeysResponse)(nil), // 44: agentmesh.v1.KeysResponse - (*TokenRefreshRequest)(nil), // 45: agentmesh.v1.TokenRefreshRequest - (*TokenRefreshResponse)(nil), // 46: agentmesh.v1.TokenRefreshResponse - (*NodeCatalogReport)(nil), // 47: agentmesh.v1.NodeCatalogReport - (*TokenRevokeRequest)(nil), // 48: agentmesh.v1.TokenRevokeRequest - (*TokenRevokeResponse)(nil), // 49: agentmesh.v1.TokenRevokeResponse - (*BootstrapTokenCreateRequest)(nil), // 50: agentmesh.v1.BootstrapTokenCreateRequest - (*BootstrapTokenCreateResponse)(nil), // 51: agentmesh.v1.BootstrapTokenCreateResponse - (*BootstrapToken)(nil), // 52: agentmesh.v1.BootstrapToken - (*BootstrapTokenListResponse)(nil), // 53: agentmesh.v1.BootstrapTokenListResponse - (*EnrollmentRequest)(nil), // 54: agentmesh.v1.EnrollmentRequest - (*EnrollmentRequestListResponse)(nil), // 55: agentmesh.v1.EnrollmentRequestListResponse - (*User)(nil), // 56: agentmesh.v1.User - (*EnrolledNode)(nil), // 57: agentmesh.v1.EnrolledNode - (*RouterLease)(nil), // 58: agentmesh.v1.RouterLease - (*NodeServices)(nil), // 59: agentmesh.v1.NodeServices - (*AdminStatusResponse)(nil), // 60: agentmesh.v1.AdminStatusResponse - (*UserStatusResponse)(nil), // 61: agentmesh.v1.UserStatusResponse - (*IdentityEvidenceResponse)(nil), // 62: agentmesh.v1.IdentityEvidenceResponse - (*PeerEvidenceResponse)(nil), // 63: agentmesh.v1.PeerEvidenceResponse - (*MemberCredential)(nil), // 64: agentmesh.v1.MemberCredential - (*TrustedSigningKey)(nil), // 65: agentmesh.v1.TrustedSigningKey - (*OIDCSession)(nil), // 66: agentmesh.v1.OIDCSession - (*TaskAuthorizationRule)(nil), // 67: agentmesh.v1.TaskAuthorizationRule - (*TaskRule)(nil), // 68: agentmesh.v1.TaskRule - (*TaskOperation)(nil), // 69: agentmesh.v1.TaskOperation - (*TokenExchangeRequest)(nil), // 70: agentmesh.v1.TokenExchangeRequest - (*TokenExchangeResponse)(nil), // 71: agentmesh.v1.TokenExchangeResponse - (*STSTokenRequest)(nil), // 72: agentmesh.v1.STSTokenRequest - (*STSTokenResponse)(nil), // 73: agentmesh.v1.STSTokenResponse - (*RevocationsResponse)(nil), // 74: agentmesh.v1.RevocationsResponse - nil, // 75: agentmesh.v1.EnrollRequest.LabelsEntry - nil, // 76: agentmesh.v1.BootstrapEnrollRequest.LabelsEntry - nil, // 77: agentmesh.v1.CommandBackend.EnvEntry - nil, // 78: agentmesh.v1.ServiceAnnounce.LabelsEntry - nil, // 79: agentmesh.v1.RouterInfo.LabelsEntry - nil, // 80: agentmesh.v1.EnrollmentRequest.LabelsEntry - nil, // 81: agentmesh.v1.EnrolledNode.LabelsEntry - nil, // 82: agentmesh.v1.AdminStatusResponse.NodeCatalogEntry - nil, // 83: agentmesh.v1.PeerEvidenceResponse.LabelsEntry - (*durationpb.Duration)(nil), // 84: google.protobuf.Duration - (*timestamppb.Timestamp)(nil), // 85: google.protobuf.Timestamp + (*DirectoryParameters)(nil), // 22: agentmesh.v1.DirectoryParameters + (*RouterInfo)(nil), // 23: agentmesh.v1.RouterInfo + (*RouterLeaseRequest)(nil), // 24: agentmesh.v1.RouterLeaseRequest + (*RouterLeaseResponse)(nil), // 25: agentmesh.v1.RouterLeaseResponse + (*PolicyRole)(nil), // 26: agentmesh.v1.PolicyRole + (*HTTPGrant)(nil), // 27: agentmesh.v1.HTTPGrant + (*EgressDestination)(nil), // 28: agentmesh.v1.EgressDestination + (*Inspection)(nil), // 29: agentmesh.v1.Inspection + (*Inspector)(nil), // 30: agentmesh.v1.Inspector + (*ModelArmor)(nil), // 31: agentmesh.v1.ModelArmor + (*ExtProc)(nil), // 32: agentmesh.v1.ExtProc + (*ExtProcProcessingMode)(nil), // 33: agentmesh.v1.ExtProcProcessingMode + (*CredentialBroker)(nil), // 34: agentmesh.v1.CredentialBroker + (*OIDCFederation)(nil), // 35: agentmesh.v1.OIDCFederation + (*AWSAssumeRole)(nil), // 36: agentmesh.v1.AWSAssumeRole + (*PlatformIdentity)(nil), // 37: agentmesh.v1.PlatformIdentity + (*PolicyBinding)(nil), // 38: agentmesh.v1.PolicyBinding + (*PolicyConfig)(nil), // 39: agentmesh.v1.PolicyConfig + (*PolicyConfigGetRequest)(nil), // 40: agentmesh.v1.PolicyConfigGetRequest + (*PolicyConfigGetResponse)(nil), // 41: agentmesh.v1.PolicyConfigGetResponse + (*PolicyConfigUpdateResponse)(nil), // 42: agentmesh.v1.PolicyConfigUpdateResponse + (*EgressAssignmentsRequest)(nil), // 43: agentmesh.v1.EgressAssignmentsRequest + (*EgressAssignmentsResponse)(nil), // 44: agentmesh.v1.EgressAssignmentsResponse + (*KeysResponse)(nil), // 45: agentmesh.v1.KeysResponse + (*TokenRefreshRequest)(nil), // 46: agentmesh.v1.TokenRefreshRequest + (*TokenRefreshResponse)(nil), // 47: agentmesh.v1.TokenRefreshResponse + (*NodeCatalogReport)(nil), // 48: agentmesh.v1.NodeCatalogReport + (*TokenRevokeRequest)(nil), // 49: agentmesh.v1.TokenRevokeRequest + (*TokenRevokeResponse)(nil), // 50: agentmesh.v1.TokenRevokeResponse + (*BootstrapTokenCreateRequest)(nil), // 51: agentmesh.v1.BootstrapTokenCreateRequest + (*BootstrapTokenCreateResponse)(nil), // 52: agentmesh.v1.BootstrapTokenCreateResponse + (*BootstrapToken)(nil), // 53: agentmesh.v1.BootstrapToken + (*BootstrapTokenListResponse)(nil), // 54: agentmesh.v1.BootstrapTokenListResponse + (*EnrollmentRequest)(nil), // 55: agentmesh.v1.EnrollmentRequest + (*EnrollmentRequestListResponse)(nil), // 56: agentmesh.v1.EnrollmentRequestListResponse + (*User)(nil), // 57: agentmesh.v1.User + (*EnrolledNode)(nil), // 58: agentmesh.v1.EnrolledNode + (*RouterLease)(nil), // 59: agentmesh.v1.RouterLease + (*NodeServices)(nil), // 60: agentmesh.v1.NodeServices + (*AdminStatusResponse)(nil), // 61: agentmesh.v1.AdminStatusResponse + (*UserStatusResponse)(nil), // 62: agentmesh.v1.UserStatusResponse + (*IdentityEvidenceResponse)(nil), // 63: agentmesh.v1.IdentityEvidenceResponse + (*PeerEvidenceResponse)(nil), // 64: agentmesh.v1.PeerEvidenceResponse + (*MemberCredential)(nil), // 65: agentmesh.v1.MemberCredential + (*TrustedSigningKey)(nil), // 66: agentmesh.v1.TrustedSigningKey + (*OIDCSession)(nil), // 67: agentmesh.v1.OIDCSession + (*TaskAuthorizationRule)(nil), // 68: agentmesh.v1.TaskAuthorizationRule + (*TaskRule)(nil), // 69: agentmesh.v1.TaskRule + (*TaskOperation)(nil), // 70: agentmesh.v1.TaskOperation + (*TokenExchangeRequest)(nil), // 71: agentmesh.v1.TokenExchangeRequest + (*TokenExchangeResponse)(nil), // 72: agentmesh.v1.TokenExchangeResponse + (*STSTokenRequest)(nil), // 73: agentmesh.v1.STSTokenRequest + (*STSTokenResponse)(nil), // 74: agentmesh.v1.STSTokenResponse + (*RevocationsResponse)(nil), // 75: agentmesh.v1.RevocationsResponse + nil, // 76: agentmesh.v1.EnrollRequest.LabelsEntry + nil, // 77: agentmesh.v1.BootstrapEnrollRequest.LabelsEntry + nil, // 78: agentmesh.v1.CommandBackend.EnvEntry + nil, // 79: agentmesh.v1.ServiceAnnounce.LabelsEntry + nil, // 80: agentmesh.v1.RouterInfo.LabelsEntry + nil, // 81: agentmesh.v1.EnrollmentRequest.LabelsEntry + nil, // 82: agentmesh.v1.EnrolledNode.LabelsEntry + nil, // 83: agentmesh.v1.AdminStatusResponse.NodeCatalogEntry + nil, // 84: agentmesh.v1.PeerEvidenceResponse.LabelsEntry + (*durationpb.Duration)(nil), // 85: google.protobuf.Duration + (*timestamppb.Timestamp)(nil), // 86: google.protobuf.Timestamp } var file_api_agentmesh_proto_depIdxs = []int32{ 4, // 0: agentmesh.v1.RouterGoAway.reason:type_name -> agentmesh.v1.RouterGoAway.Reason - 84, // 1: agentmesh.v1.RouterGoAway.retry_after:type_name -> google.protobuf.Duration + 85, // 1: agentmesh.v1.RouterGoAway.retry_after:type_name -> google.protobuf.Duration 5, // 2: agentmesh.v1.MeshEvent.type:type_name -> agentmesh.v1.MeshEvent.Type - 85, // 3: agentmesh.v1.MeshEvent.event_time:type_name -> google.protobuf.Timestamp - 75, // 4: agentmesh.v1.EnrollRequest.labels:type_name -> agentmesh.v1.EnrollRequest.LabelsEntry - 85, // 5: agentmesh.v1.EnrollResponse.expire_time:type_name -> google.protobuf.Timestamp - 76, // 6: agentmesh.v1.BootstrapEnrollRequest.labels:type_name -> agentmesh.v1.BootstrapEnrollRequest.LabelsEntry + 86, // 3: agentmesh.v1.MeshEvent.event_time:type_name -> google.protobuf.Timestamp + 76, // 4: agentmesh.v1.EnrollRequest.labels:type_name -> agentmesh.v1.EnrollRequest.LabelsEntry + 86, // 5: agentmesh.v1.EnrollResponse.expire_time:type_name -> google.protobuf.Timestamp + 77, // 6: agentmesh.v1.BootstrapEnrollRequest.labels:type_name -> agentmesh.v1.BootstrapEnrollRequest.LabelsEntry 0, // 7: agentmesh.v1.BootstrapEnrollResponse.status:type_name -> agentmesh.v1.EnrollmentStatus - 85, // 8: agentmesh.v1.BootstrapEnrollResponse.expire_time:type_name -> google.protobuf.Timestamp + 86, // 8: agentmesh.v1.BootstrapEnrollResponse.expire_time:type_name -> google.protobuf.Timestamp 1, // 9: agentmesh.v1.ServiceInfo.type:type_name -> agentmesh.v1.ServiceType - 77, // 10: agentmesh.v1.CommandBackend.env:type_name -> agentmesh.v1.CommandBackend.EnvEntry + 78, // 10: agentmesh.v1.CommandBackend.env:type_name -> agentmesh.v1.CommandBackend.EnvEntry 16, // 11: agentmesh.v1.RegisterServiceRequest.service:type_name -> agentmesh.v1.ServiceInfo 17, // 12: agentmesh.v1.RegisterServiceRequest.command:type_name -> agentmesh.v1.CommandBackend 1, // 13: agentmesh.v1.ServiceAnnounce.type:type_name -> agentmesh.v1.ServiceType - 78, // 14: agentmesh.v1.ServiceAnnounce.labels:type_name -> agentmesh.v1.ServiceAnnounce.LabelsEntry - 85, // 15: agentmesh.v1.ServiceAnnounce.announce_time:type_name -> google.protobuf.Timestamp - 22, // 16: agentmesh.v1.ControlPlaneInfoResponse.routers:type_name -> agentmesh.v1.RouterInfo - 79, // 17: agentmesh.v1.RouterInfo.labels:type_name -> agentmesh.v1.RouterInfo.LabelsEntry - 84, // 18: agentmesh.v1.RouterLeaseRequest.ttl:type_name -> google.protobuf.Duration - 85, // 19: agentmesh.v1.RouterLeaseResponse.expire_time:type_name -> google.protobuf.Timestamp - 26, // 20: agentmesh.v1.PolicyRole.http:type_name -> agentmesh.v1.HTTPGrant - 33, // 21: agentmesh.v1.EgressDestination.broker:type_name -> agentmesh.v1.CredentialBroker - 28, // 22: agentmesh.v1.EgressDestination.inspection:type_name -> agentmesh.v1.Inspection - 2, // 23: agentmesh.v1.EgressDestination.mode:type_name -> agentmesh.v1.EgressMode - 29, // 24: agentmesh.v1.Inspection.inspectors:type_name -> agentmesh.v1.Inspector - 30, // 25: agentmesh.v1.Inspector.model_armor:type_name -> agentmesh.v1.ModelArmor - 31, // 26: agentmesh.v1.Inspector.ext_proc:type_name -> agentmesh.v1.ExtProc - 3, // 27: agentmesh.v1.ModelArmor.response:type_name -> agentmesh.v1.ResponseInspection - 84, // 28: agentmesh.v1.ModelArmor.timeout:type_name -> google.protobuf.Duration - 32, // 29: agentmesh.v1.ExtProc.processing_mode:type_name -> agentmesh.v1.ExtProcProcessingMode - 84, // 30: agentmesh.v1.ExtProc.message_timeout:type_name -> google.protobuf.Duration - 6, // 31: agentmesh.v1.ExtProcProcessingMode.request_header_mode:type_name -> agentmesh.v1.ExtProcProcessingMode.HeaderMode - 6, // 32: agentmesh.v1.ExtProcProcessingMode.response_header_mode:type_name -> agentmesh.v1.ExtProcProcessingMode.HeaderMode - 7, // 33: agentmesh.v1.ExtProcProcessingMode.request_body_mode:type_name -> agentmesh.v1.ExtProcProcessingMode.BodyMode - 7, // 34: agentmesh.v1.ExtProcProcessingMode.response_body_mode:type_name -> agentmesh.v1.ExtProcProcessingMode.BodyMode - 6, // 35: agentmesh.v1.ExtProcProcessingMode.request_trailer_mode:type_name -> agentmesh.v1.ExtProcProcessingMode.HeaderMode - 6, // 36: agentmesh.v1.ExtProcProcessingMode.response_trailer_mode:type_name -> agentmesh.v1.ExtProcProcessingMode.HeaderMode - 34, // 37: agentmesh.v1.CredentialBroker.oidc_federation:type_name -> agentmesh.v1.OIDCFederation - 35, // 38: agentmesh.v1.CredentialBroker.aws_assume_role:type_name -> agentmesh.v1.AWSAssumeRole - 36, // 39: agentmesh.v1.CredentialBroker.platform_identity:type_name -> agentmesh.v1.PlatformIdentity - 25, // 40: agentmesh.v1.PolicyConfig.roles:type_name -> agentmesh.v1.PolicyRole - 37, // 41: agentmesh.v1.PolicyConfig.bindings:type_name -> agentmesh.v1.PolicyBinding - 27, // 42: agentmesh.v1.PolicyConfig.egress:type_name -> agentmesh.v1.EgressDestination - 27, // 43: agentmesh.v1.EgressAssignmentsResponse.egress:type_name -> agentmesh.v1.EgressDestination - 85, // 44: agentmesh.v1.KeysResponse.sign_time:type_name -> google.protobuf.Timestamp - 85, // 45: agentmesh.v1.TokenRefreshResponse.expire_time:type_name -> google.protobuf.Timestamp - 16, // 46: agentmesh.v1.NodeCatalogReport.services:type_name -> agentmesh.v1.ServiceInfo - 85, // 47: agentmesh.v1.BootstrapTokenCreateResponse.expire_time:type_name -> google.protobuf.Timestamp - 85, // 48: agentmesh.v1.BootstrapToken.create_time:type_name -> google.protobuf.Timestamp - 85, // 49: agentmesh.v1.BootstrapToken.expire_time:type_name -> google.protobuf.Timestamp - 85, // 50: agentmesh.v1.BootstrapToken.revoke_time:type_name -> google.protobuf.Timestamp - 52, // 51: agentmesh.v1.BootstrapTokenListResponse.tokens:type_name -> agentmesh.v1.BootstrapToken - 0, // 52: agentmesh.v1.EnrollmentRequest.status:type_name -> agentmesh.v1.EnrollmentStatus - 80, // 53: agentmesh.v1.EnrollmentRequest.labels:type_name -> agentmesh.v1.EnrollmentRequest.LabelsEntry - 85, // 54: agentmesh.v1.EnrollmentRequest.create_time:type_name -> google.protobuf.Timestamp - 85, // 55: agentmesh.v1.EnrollmentRequest.resolve_time:type_name -> google.protobuf.Timestamp - 54, // 56: agentmesh.v1.EnrollmentRequestListResponse.requests:type_name -> agentmesh.v1.EnrollmentRequest - 85, // 57: agentmesh.v1.User.create_time:type_name -> google.protobuf.Timestamp - 81, // 58: agentmesh.v1.EnrolledNode.labels:type_name -> agentmesh.v1.EnrolledNode.LabelsEntry - 85, // 59: agentmesh.v1.EnrolledNode.enroll_time:type_name -> google.protobuf.Timestamp - 85, // 60: agentmesh.v1.EnrolledNode.expire_time:type_name -> google.protobuf.Timestamp - 85, // 61: agentmesh.v1.RouterLease.last_renewal_time:type_name -> google.protobuf.Timestamp - 85, // 62: agentmesh.v1.RouterLease.expire_time:type_name -> google.protobuf.Timestamp - 16, // 63: agentmesh.v1.NodeServices.services:type_name -> agentmesh.v1.ServiceInfo - 85, // 64: agentmesh.v1.NodeServices.report_time:type_name -> google.protobuf.Timestamp - 56, // 65: agentmesh.v1.AdminStatusResponse.users:type_name -> agentmesh.v1.User - 58, // 66: agentmesh.v1.AdminStatusResponse.active_routers:type_name -> agentmesh.v1.RouterLease - 57, // 67: agentmesh.v1.AdminStatusResponse.enrolled_nodes:type_name -> agentmesh.v1.EnrolledNode - 54, // 68: agentmesh.v1.AdminStatusResponse.enrollment_requests:type_name -> agentmesh.v1.EnrollmentRequest - 52, // 69: agentmesh.v1.AdminStatusResponse.bootstrap_tokens:type_name -> agentmesh.v1.BootstrapToken - 38, // 70: agentmesh.v1.AdminStatusResponse.policy:type_name -> agentmesh.v1.PolicyConfig - 82, // 71: agentmesh.v1.AdminStatusResponse.node_catalog:type_name -> agentmesh.v1.AdminStatusResponse.NodeCatalogEntry - 56, // 72: agentmesh.v1.UserStatusResponse.user:type_name -> agentmesh.v1.User - 57, // 73: agentmesh.v1.UserStatusResponse.enrolled_nodes:type_name -> agentmesh.v1.EnrolledNode - 52, // 74: agentmesh.v1.UserStatusResponse.bootstrap_tokens:type_name -> agentmesh.v1.BootstrapToken - 58, // 75: agentmesh.v1.UserStatusResponse.active_routers:type_name -> agentmesh.v1.RouterLease - 38, // 76: agentmesh.v1.UserStatusResponse.policy:type_name -> agentmesh.v1.PolicyConfig - 85, // 77: agentmesh.v1.IdentityEvidenceResponse.biscuit_expire_time:type_name -> google.protobuf.Timestamp - 85, // 78: agentmesh.v1.IdentityEvidenceResponse.check_time:type_name -> google.protobuf.Timestamp - 83, // 79: agentmesh.v1.PeerEvidenceResponse.labels:type_name -> agentmesh.v1.PeerEvidenceResponse.LabelsEntry - 85, // 80: agentmesh.v1.PeerEvidenceResponse.expire_time:type_name -> google.protobuf.Timestamp - 85, // 81: agentmesh.v1.PeerEvidenceResponse.check_time:type_name -> google.protobuf.Timestamp - 85, // 82: agentmesh.v1.MemberCredential.expire_time:type_name -> google.protobuf.Timestamp - 65, // 83: agentmesh.v1.MemberCredential.trusted_keys:type_name -> agentmesh.v1.TrustedSigningKey - 66, // 84: agentmesh.v1.MemberCredential.oidc_session:type_name -> agentmesh.v1.OIDCSession - 85, // 85: agentmesh.v1.TrustedSigningKey.receive_time:type_name -> google.protobuf.Timestamp - 68, // 86: agentmesh.v1.TaskAuthorizationRule.rules:type_name -> agentmesh.v1.TaskRule - 85, // 87: agentmesh.v1.TaskAuthorizationRule.expire_time:type_name -> google.protobuf.Timestamp - 69, // 88: agentmesh.v1.TaskRule.operation:type_name -> agentmesh.v1.TaskOperation - 67, // 89: agentmesh.v1.TokenExchangeRequest.task_rule:type_name -> agentmesh.v1.TaskAuthorizationRule - 85, // 90: agentmesh.v1.TokenExchangeResponse.expire_time:type_name -> google.protobuf.Timestamp - 85, // 91: agentmesh.v1.STSTokenResponse.expire_time:type_name -> google.protobuf.Timestamp - 59, // 92: agentmesh.v1.AdminStatusResponse.NodeCatalogEntry.value:type_name -> agentmesh.v1.NodeServices - 93, // [93:93] is the sub-list for method output_type - 93, // [93:93] is the sub-list for method input_type - 93, // [93:93] is the sub-list for extension type_name - 93, // [93:93] is the sub-list for extension extendee - 0, // [0:93] is the sub-list for field type_name + 79, // 14: agentmesh.v1.ServiceAnnounce.labels:type_name -> agentmesh.v1.ServiceAnnounce.LabelsEntry + 86, // 15: agentmesh.v1.ServiceAnnounce.announce_time:type_name -> google.protobuf.Timestamp + 23, // 16: agentmesh.v1.ControlPlaneInfoResponse.routers:type_name -> agentmesh.v1.RouterInfo + 22, // 17: agentmesh.v1.ControlPlaneInfoResponse.directory:type_name -> agentmesh.v1.DirectoryParameters + 85, // 18: agentmesh.v1.DirectoryParameters.reprovide_interval:type_name -> google.protobuf.Duration + 85, // 19: agentmesh.v1.DirectoryParameters.record_ttl:type_name -> google.protobuf.Duration + 80, // 20: agentmesh.v1.RouterInfo.labels:type_name -> agentmesh.v1.RouterInfo.LabelsEntry + 85, // 21: agentmesh.v1.RouterLeaseRequest.ttl:type_name -> google.protobuf.Duration + 86, // 22: agentmesh.v1.RouterLeaseResponse.expire_time:type_name -> google.protobuf.Timestamp + 27, // 23: agentmesh.v1.PolicyRole.http:type_name -> agentmesh.v1.HTTPGrant + 34, // 24: agentmesh.v1.EgressDestination.broker:type_name -> agentmesh.v1.CredentialBroker + 29, // 25: agentmesh.v1.EgressDestination.inspection:type_name -> agentmesh.v1.Inspection + 2, // 26: agentmesh.v1.EgressDestination.mode:type_name -> agentmesh.v1.EgressMode + 30, // 27: agentmesh.v1.Inspection.inspectors:type_name -> agentmesh.v1.Inspector + 31, // 28: agentmesh.v1.Inspector.model_armor:type_name -> agentmesh.v1.ModelArmor + 32, // 29: agentmesh.v1.Inspector.ext_proc:type_name -> agentmesh.v1.ExtProc + 3, // 30: agentmesh.v1.ModelArmor.response:type_name -> agentmesh.v1.ResponseInspection + 85, // 31: agentmesh.v1.ModelArmor.timeout:type_name -> google.protobuf.Duration + 33, // 32: agentmesh.v1.ExtProc.processing_mode:type_name -> agentmesh.v1.ExtProcProcessingMode + 85, // 33: agentmesh.v1.ExtProc.message_timeout:type_name -> google.protobuf.Duration + 6, // 34: agentmesh.v1.ExtProcProcessingMode.request_header_mode:type_name -> agentmesh.v1.ExtProcProcessingMode.HeaderMode + 6, // 35: agentmesh.v1.ExtProcProcessingMode.response_header_mode:type_name -> agentmesh.v1.ExtProcProcessingMode.HeaderMode + 7, // 36: agentmesh.v1.ExtProcProcessingMode.request_body_mode:type_name -> agentmesh.v1.ExtProcProcessingMode.BodyMode + 7, // 37: agentmesh.v1.ExtProcProcessingMode.response_body_mode:type_name -> agentmesh.v1.ExtProcProcessingMode.BodyMode + 6, // 38: agentmesh.v1.ExtProcProcessingMode.request_trailer_mode:type_name -> agentmesh.v1.ExtProcProcessingMode.HeaderMode + 6, // 39: agentmesh.v1.ExtProcProcessingMode.response_trailer_mode:type_name -> agentmesh.v1.ExtProcProcessingMode.HeaderMode + 35, // 40: agentmesh.v1.CredentialBroker.oidc_federation:type_name -> agentmesh.v1.OIDCFederation + 36, // 41: agentmesh.v1.CredentialBroker.aws_assume_role:type_name -> agentmesh.v1.AWSAssumeRole + 37, // 42: agentmesh.v1.CredentialBroker.platform_identity:type_name -> agentmesh.v1.PlatformIdentity + 26, // 43: agentmesh.v1.PolicyConfig.roles:type_name -> agentmesh.v1.PolicyRole + 38, // 44: agentmesh.v1.PolicyConfig.bindings:type_name -> agentmesh.v1.PolicyBinding + 28, // 45: agentmesh.v1.PolicyConfig.egress:type_name -> agentmesh.v1.EgressDestination + 28, // 46: agentmesh.v1.EgressAssignmentsResponse.egress:type_name -> agentmesh.v1.EgressDestination + 86, // 47: agentmesh.v1.KeysResponse.sign_time:type_name -> google.protobuf.Timestamp + 86, // 48: agentmesh.v1.TokenRefreshResponse.expire_time:type_name -> google.protobuf.Timestamp + 16, // 49: agentmesh.v1.NodeCatalogReport.services:type_name -> agentmesh.v1.ServiceInfo + 86, // 50: agentmesh.v1.BootstrapTokenCreateResponse.expire_time:type_name -> google.protobuf.Timestamp + 86, // 51: agentmesh.v1.BootstrapToken.create_time:type_name -> google.protobuf.Timestamp + 86, // 52: agentmesh.v1.BootstrapToken.expire_time:type_name -> google.protobuf.Timestamp + 86, // 53: agentmesh.v1.BootstrapToken.revoke_time:type_name -> google.protobuf.Timestamp + 53, // 54: agentmesh.v1.BootstrapTokenListResponse.tokens:type_name -> agentmesh.v1.BootstrapToken + 0, // 55: agentmesh.v1.EnrollmentRequest.status:type_name -> agentmesh.v1.EnrollmentStatus + 81, // 56: agentmesh.v1.EnrollmentRequest.labels:type_name -> agentmesh.v1.EnrollmentRequest.LabelsEntry + 86, // 57: agentmesh.v1.EnrollmentRequest.create_time:type_name -> google.protobuf.Timestamp + 86, // 58: agentmesh.v1.EnrollmentRequest.resolve_time:type_name -> google.protobuf.Timestamp + 55, // 59: agentmesh.v1.EnrollmentRequestListResponse.requests:type_name -> agentmesh.v1.EnrollmentRequest + 86, // 60: agentmesh.v1.User.create_time:type_name -> google.protobuf.Timestamp + 82, // 61: agentmesh.v1.EnrolledNode.labels:type_name -> agentmesh.v1.EnrolledNode.LabelsEntry + 86, // 62: agentmesh.v1.EnrolledNode.enroll_time:type_name -> google.protobuf.Timestamp + 86, // 63: agentmesh.v1.EnrolledNode.expire_time:type_name -> google.protobuf.Timestamp + 86, // 64: agentmesh.v1.RouterLease.last_renewal_time:type_name -> google.protobuf.Timestamp + 86, // 65: agentmesh.v1.RouterLease.expire_time:type_name -> google.protobuf.Timestamp + 16, // 66: agentmesh.v1.NodeServices.services:type_name -> agentmesh.v1.ServiceInfo + 86, // 67: agentmesh.v1.NodeServices.report_time:type_name -> google.protobuf.Timestamp + 57, // 68: agentmesh.v1.AdminStatusResponse.users:type_name -> agentmesh.v1.User + 59, // 69: agentmesh.v1.AdminStatusResponse.active_routers:type_name -> agentmesh.v1.RouterLease + 58, // 70: agentmesh.v1.AdminStatusResponse.enrolled_nodes:type_name -> agentmesh.v1.EnrolledNode + 55, // 71: agentmesh.v1.AdminStatusResponse.enrollment_requests:type_name -> agentmesh.v1.EnrollmentRequest + 53, // 72: agentmesh.v1.AdminStatusResponse.bootstrap_tokens:type_name -> agentmesh.v1.BootstrapToken + 39, // 73: agentmesh.v1.AdminStatusResponse.policy:type_name -> agentmesh.v1.PolicyConfig + 83, // 74: agentmesh.v1.AdminStatusResponse.node_catalog:type_name -> agentmesh.v1.AdminStatusResponse.NodeCatalogEntry + 57, // 75: agentmesh.v1.UserStatusResponse.user:type_name -> agentmesh.v1.User + 58, // 76: agentmesh.v1.UserStatusResponse.enrolled_nodes:type_name -> agentmesh.v1.EnrolledNode + 53, // 77: agentmesh.v1.UserStatusResponse.bootstrap_tokens:type_name -> agentmesh.v1.BootstrapToken + 59, // 78: agentmesh.v1.UserStatusResponse.active_routers:type_name -> agentmesh.v1.RouterLease + 39, // 79: agentmesh.v1.UserStatusResponse.policy:type_name -> agentmesh.v1.PolicyConfig + 86, // 80: agentmesh.v1.IdentityEvidenceResponse.biscuit_expire_time:type_name -> google.protobuf.Timestamp + 86, // 81: agentmesh.v1.IdentityEvidenceResponse.check_time:type_name -> google.protobuf.Timestamp + 84, // 82: agentmesh.v1.PeerEvidenceResponse.labels:type_name -> agentmesh.v1.PeerEvidenceResponse.LabelsEntry + 86, // 83: agentmesh.v1.PeerEvidenceResponse.expire_time:type_name -> google.protobuf.Timestamp + 86, // 84: agentmesh.v1.PeerEvidenceResponse.check_time:type_name -> google.protobuf.Timestamp + 86, // 85: agentmesh.v1.MemberCredential.expire_time:type_name -> google.protobuf.Timestamp + 66, // 86: agentmesh.v1.MemberCredential.trusted_keys:type_name -> agentmesh.v1.TrustedSigningKey + 67, // 87: agentmesh.v1.MemberCredential.oidc_session:type_name -> agentmesh.v1.OIDCSession + 86, // 88: agentmesh.v1.TrustedSigningKey.receive_time:type_name -> google.protobuf.Timestamp + 69, // 89: agentmesh.v1.TaskAuthorizationRule.rules:type_name -> agentmesh.v1.TaskRule + 86, // 90: agentmesh.v1.TaskAuthorizationRule.expire_time:type_name -> google.protobuf.Timestamp + 70, // 91: agentmesh.v1.TaskRule.operation:type_name -> agentmesh.v1.TaskOperation + 68, // 92: agentmesh.v1.TokenExchangeRequest.task_rule:type_name -> agentmesh.v1.TaskAuthorizationRule + 86, // 93: agentmesh.v1.TokenExchangeResponse.expire_time:type_name -> google.protobuf.Timestamp + 86, // 94: agentmesh.v1.STSTokenResponse.expire_time:type_name -> google.protobuf.Timestamp + 60, // 95: agentmesh.v1.AdminStatusResponse.NodeCatalogEntry.value:type_name -> agentmesh.v1.NodeServices + 96, // [96:96] is the sub-list for method output_type + 96, // [96:96] is the sub-list for method input_type + 96, // [96:96] is the sub-list for extension type_name + 96, // [96:96] is the sub-list for extension extendee + 0, // [0:96] is the sub-list for field type_name } func init() { file_api_agentmesh_proto_init() } @@ -6209,11 +6302,11 @@ func file_api_agentmesh_proto_init() { (*RegisterServiceRequest_TargetUrl)(nil), (*RegisterServiceRequest_Command)(nil), } - file_api_agentmesh_proto_msgTypes[21].OneofWrappers = []any{ + file_api_agentmesh_proto_msgTypes[22].OneofWrappers = []any{ (*Inspector_ModelArmor)(nil), (*Inspector_ExtProc)(nil), } - file_api_agentmesh_proto_msgTypes[25].OneofWrappers = []any{ + file_api_agentmesh_proto_msgTypes[26].OneofWrappers = []any{ (*CredentialBroker_StaticSecret)(nil), (*CredentialBroker_OidcFederation)(nil), (*CredentialBroker_AwsAssumeRole)(nil), @@ -6225,7 +6318,7 @@ func file_api_agentmesh_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_api_agentmesh_proto_rawDesc), len(file_api_agentmesh_proto_rawDesc)), NumEnums: 8, - NumMessages: 76, + NumMessages: 77, NumExtensions: 0, NumServices: 0, }, diff --git a/api/agentmesh.proto b/api/agentmesh.proto index adced379..ecf4e7f1 100644 --- a/api/agentmesh.proto +++ b/api/agentmesh.proto @@ -216,6 +216,28 @@ message ControlPlaneInfoResponse { // placement, and its load as of its last lease, to prefer the ones with // room. repeated RouterInfo routers = 6; + // How the mesh keeps its directory. Members and routers read these here + // and nowhere else, so they agree without configuration of their own; a + // control plane that leaves it unset leaves them on the defaults the + // api package names. + DirectoryParameters directory = 7; +} + +// DirectoryParameters are the settings of the DHT the routers serve, set +// once per mesh on the control plane (agentmesh-control-plane +// --dht-replication, --dht-reprovide-interval) and published on /info. +message DirectoryParameters { + // How many routers hold each record a member publishes and answer each + // lookup it makes: the routers closest to the key. A member holds a + // session with each of them besides the routers it is attached to. + int32 replication = 1; + // How often a member re-announces each service it publishes. + google.protobuf.Duration reprovide_interval = 2; + // How long a router keeps a record after its last announcement: three + // reprovide intervals, so a provider that stopped without unpublishing + // leaves within it and two missed announcements do not unlist one that + // did not. + google.protobuf.Duration record_ttl = 3; } // RouterInfo is one router as the control plane lists it on /info. diff --git a/api/directory.go b/api/directory.go new file mode 100644 index 00000000..b00fcce7 --- /dev/null +++ b/api/directory.go @@ -0,0 +1,102 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package api + +import ( + "fmt" + "time" + + "google.golang.org/protobuf/types/known/durationpb" +) + +// The directory parameters a mesh runs on when its control plane sets +// none (ControlPlaneInfoResponse.directory): three routers per key, a +// re-announcement every five minutes, and records that outlive three of +// them. Every member and router derives its settings from these three +// numbers, so a member started against no control plane at all behaves as +// one of a default mesh. +const ( + DefaultDHTReplication = 3 + DefaultReprovideInterval = 5 * time.Minute + recordTTLReprovideMultiple = 3 +) + +// RecordTTL is how long a router keeps a record after its last +// announcement for a mesh that re-announces every reprovide: three +// intervals, so a provider that stopped without unpublishing leaves within +// it and two missed announcements do not unlist one that did not. +func RecordTTL(reprovide time.Duration) time.Duration { + return recordTTLReprovideMultiple * reprovide +} + +// NewDirectoryParameters is the message a control plane publishes for a +// replication factor and a reprovide interval, the TTL derived. +func NewDirectoryParameters(replication int, reprovide time.Duration) *DirectoryParameters { + return &DirectoryParameters{ + Replication: int32(replication), + ReprovideInterval: durationpb.New(reprovide), + RecordTtl: durationpb.New(RecordTTL(reprovide)), + } +} + +// ValidateDirectoryParameters checks the two numbers an operator sets. +func ValidateDirectoryParameters(replication int, reprovide time.Duration) error { + if replication < 1 { + return fmt.Errorf("dht replication %d: at least one router must hold each record", replication) + } + if reprovide <= 0 { + return fmt.Errorf("dht reprovide interval %s must be positive", reprovide) + } + return nil +} + +// DirectorySettings are the directory parameters as a member or router +// applies them. +type DirectorySettings struct { + Replication int + Reprovide time.Duration + RecordTTL time.Duration +} + +// DefaultDirectorySettings is what a member or router runs on when the +// control plane publishes no directory parameters. +func DefaultDirectorySettings() DirectorySettings { + return DirectorySettings{ + Replication: DefaultDHTReplication, + Reprovide: DefaultReprovideInterval, + RecordTTL: RecordTTL(DefaultReprovideInterval), + } +} + +// DirectorySettingsFrom reads the parameters a control plane published, +// with the default for each it left unset or unusable, so a member or +// router never runs with a zero. +func DirectorySettingsFrom(p *DirectoryParameters) DirectorySettings { + s := DefaultDirectorySettings() + if p == nil { + return s + } + if r := int(p.GetReplication()); r >= 1 { + s.Replication = r + } + if d := p.GetReprovideInterval().AsDuration(); p.GetReprovideInterval() != nil && d > 0 { + s.Reprovide = d + s.RecordTTL = RecordTTL(d) + } + if d := p.GetRecordTtl().AsDuration(); p.GetRecordTtl() != nil && d > s.Reprovide { + s.RecordTTL = d + } + return s +} diff --git a/api/directory_test.go b/api/directory_test.go new file mode 100644 index 00000000..79a918c4 --- /dev/null +++ b/api/directory_test.go @@ -0,0 +1,62 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package api + +import ( + "testing" + "time" + + "google.golang.org/protobuf/types/known/durationpb" +) + +func TestDirectorySettingsFrom(t *testing.T) { + def := DefaultDirectorySettings() + for _, tc := range []struct { + name string + in *DirectoryParameters + want DirectorySettings + }{ + {name: "unset", in: nil, want: def}, + {name: "empty", in: &DirectoryParameters{}, want: def}, + {name: "published", in: NewDirectoryParameters(5, 20*time.Minute), want: DirectorySettings{Replication: 5, Reprovide: 20 * time.Minute, RecordTTL: time.Hour}}, + {name: "ttl not derived", in: &DirectoryParameters{Replication: 2, ReprovideInterval: durationpb.New(10 * time.Minute), RecordTtl: durationpb.New(2 * time.Hour)}, want: DirectorySettings{Replication: 2, Reprovide: 10 * time.Minute, RecordTTL: 2 * time.Hour}}, + {name: "ttl not longer than the interval is replaced", in: &DirectoryParameters{ReprovideInterval: durationpb.New(10 * time.Minute), RecordTtl: durationpb.New(10 * time.Minute)}, want: DirectorySettings{Replication: def.Replication, Reprovide: 10 * time.Minute, RecordTTL: 30 * time.Minute}}, + {name: "zero and negative keep the defaults", in: &DirectoryParameters{Replication: -1, ReprovideInterval: durationpb.New(-time.Minute)}, want: def}, + } { + t.Run(tc.name, func(t *testing.T) { + if got := DirectorySettingsFrom(tc.in); got != tc.want { + t.Fatalf("DirectorySettingsFrom(%v) = %+v, want %+v", tc.in, got, tc.want) + } + }) + } +} + +func TestValidateDirectoryParameters(t *testing.T) { + for _, tc := range []struct { + replication int + reprovide time.Duration + wantErr bool + }{ + {DefaultDHTReplication, DefaultReprovideInterval, false}, + {1, time.Second, false}, + {0, DefaultReprovideInterval, true}, + {DefaultDHTReplication, 0, true}, + {DefaultDHTReplication, -time.Minute, true}, + } { + if err := ValidateDirectoryParameters(tc.replication, tc.reprovide); (err != nil) != tc.wantErr { + t.Errorf("ValidateDirectoryParameters(%d, %s) = %v, want error %v", tc.replication, tc.reprovide, err, tc.wantErr) + } + } +} diff --git a/cmd/agentmesh-control-plane/main.go b/cmd/agentmesh-control-plane/main.go index fbf6e486..a529f420 100644 --- a/cmd/agentmesh-control-plane/main.go +++ b/cmd/agentmesh-control-plane/main.go @@ -48,6 +48,8 @@ var ( keyRotationInterval time.Duration keyGracePeriod time.Duration leaseDuration time.Duration + dhtReplication int + dhtReprovideInterval time.Duration biscuitTTL time.Duration oidcSessionTTL time.Duration workloadSessionTTL time.Duration @@ -130,6 +132,8 @@ func main() { LeaseDuration: leaseDuration, KeyRotationInterval: keyRotationInterval, KeyGracePeriod: keyGracePeriod, + DHTReplication: dhtReplication, + DHTReprovideInterval: dhtReprovideInterval, InsecureSkipTLSVerify: insecureSkipTLSVerify, BiscuitTimeout: 10 * time.Second, BiscuitTTL: biscuitTTL, @@ -193,6 +197,8 @@ func main() { rootCmd.Flags().DurationVar(&keyRotationInterval, "key-rotation-interval", 24*time.Hour, "Key rotation interval (e.g. 24h). 0 disables rotation.") rootCmd.Flags().DurationVar(&keyGracePeriod, "key-grace-period", 1*time.Hour, "Key grace period for rotated keys.") rootCmd.Flags().DurationVar(&leaseDuration, "lease-duration", 15*time.Minute, "Router lease registration TTL.") + rootCmd.Flags().IntVar(&dhtReplication, "dht-replication", api.DefaultDHTReplication, "How many routers hold each service a member publishes and answer each lookup: the routers closest to the key. Every member holds a session with each of them besides its --routers. More replicas survive more routers down at once; each one is a session more per key on every member. Published on /info for the whole mesh.") + rootCmd.Flags().DurationVar(&dhtReprovideInterval, "dht-reprovide-interval", api.DefaultReprovideInterval, "How often a member re-announces each service it publishes. A provider that stopped without unpublishing stays listed for three intervals; the mesh pays providers / interval announcements a minute. Published on /info for the whole mesh.") rootCmd.Flags().DurationVar(&biscuitTTL, "biscuit-ttl", api.BiscuitTokenTTL, "Lifespan minted into every issued Biscuit's expiration fact. Capped to the OIDC token's own expiry when shorter.") rootCmd.Flags().DurationVar(&oidcSessionTTL, "oidc-session-ttl", api.OIDCSessionTTL, "How long an OIDC enrollment stays refreshable before the identity must re-authenticate with the OIDC provider. Shorter values keep the provider authoritative for offboarding at the cost of more frequent interactive re-enrollment.") rootCmd.Flags().DurationVar(&workloadSessionTTL, "workload-session-ttl", controlplane.DefaultWorkloadSessionTTL, "How long a workload-issuer enrollment stays refreshable without presenting a fresh platform JWT on /refresh.") diff --git a/cmd/agentmesh-node/main.go b/cmd/agentmesh-node/main.go index 95a4f555..9609dfcc 100644 --- a/cmd/agentmesh-node/main.go +++ b/cmd/agentmesh-node/main.go @@ -106,8 +106,6 @@ var ( tlsCertFlag string tlsKeyFlag string tlsCAFlag string - dhtProviderAddrTTLFlag time.Duration - dhtMaxRecordAgeFlag time.Duration dhtLookupLimitFlag int discoveryConcurrencyFlag int backendProbeTimeoutFlag time.Duration @@ -465,8 +463,6 @@ func main() { RouterConnectTimeout: routerConnectTimeoutFlag, RequiredRole: api.RoleNode, ControlPlaneSyncInterval: controlPlaneSyncIntervalFlag, - DHTProviderAddrTTL: dhtProviderAddrTTLFlag, - DHTMaxRecordAge: dhtMaxRecordAgeFlag, DHTLookupLimit: dhtLookupLimitFlag, DiscoveryConcurrency: discoveryConcurrencyFlag, BackendProbeTimeout: backendProbeTimeoutFlag, @@ -860,7 +856,7 @@ func main() { runCmd.Flags().StringVar(&socketPathFlag, "socket-path", "", "Unix socket serving the same API, where the socket's owner-only permissions replace the API token (defaults to /"+node.DefaultSocketName+"; pass an empty value to disable)") runCmd.Flags().StringVar(&metricsAddrFlag, "metrics-addr", "", "Serve Prometheus /metrics, /healthz and /readyz on this address without authentication (e.g. 0.0.0.0:9090), for scrapers and probes that hold no API token; off by default") runCmd.Flags().StringVar(&pprofAddrFlag, "pprof-addr", "", "Serve Go runtime profiles under /debug/pprof/ on this address (e.g. 127.0.0.1:6060); unauthenticated and a heap profile is a memory dump, so bind it to loopback and reach it through a port-forward; off by default") - runCmd.Flags().StringVar(&discoveryIntervalFlag, "discovery-interval", node.DefaultDiscoveryInterval, "Delay before the first announcement of this node's services to the DHT, capped at 5s; they are re-announced every 5 minutes after that") + runCmd.Flags().StringVar(&discoveryIntervalFlag, "discovery-interval", node.DefaultDiscoveryInterval, "Delay before the first announcement of this node's services to the DHT, capped at 5s; they are re-announced every reprovide interval of the mesh after that (agentmesh-control-plane --dht-reprovide-interval)") runCmd.Flags().DurationVar(&monitorBootstrapFlag, "monitor-bootstrap", 2*time.Minute, "Initial wait before monitoring router connection") runCmd.Flags().DurationVar(&monitorCheckIntervalFlag, "monitor-interval", 1*time.Minute, "Interval for checking router connection") runCmd.Flags().DurationVar(&autoRelayMinIntervalFlag, "autorelay-min-interval", 30*time.Second, "AutoRelay Min Interval") @@ -889,8 +885,6 @@ func main() { runCmd.Flags().StringVar(&tlsCertFlag, "tls-cert", "", "Path to TLS certificate for sidecar API") runCmd.Flags().StringVar(&tlsKeyFlag, "tls-key", "", "Path to TLS key for sidecar API") runCmd.Flags().StringVar(&tlsCAFlag, "tls-ca", "", "Path to TLS CA for sidecar API mTLS") - runCmd.Flags().DurationVar(&dhtProviderAddrTTLFlag, "dht-provider-addr-ttl", node.DefaultDHTProviderAddrTTL, "How long a DHT provider record this node stores lives after its last announcement; longer than the 5m between announcements, three times it by default") - runCmd.Flags().DurationVar(&dhtMaxRecordAgeFlag, "dht-max-record-age", 0, "Maximum age for DHT records (0s uses library default)") runCmd.Flags().IntVar(&dhtLookupLimitFlag, "dht-lookup-limit", 0, "Maximum number of providers to query from the DHT (0 uses default 20)") runCmd.Flags().IntVar(&discoveryConcurrencyFlag, "discovery-concurrency", 0, "Max concurrent catalog fetches during discovery (0 uses default 10)") runCmd.Flags().DurationVar(&controlPlaneSyncIntervalFlag, "control-plane-sync-interval", node.DefaultControlPlaneSyncInterval, "How often signing keys, bans, router addresses and mesh policy are pulled from the control plane. Keep it well below the control plane's --key-grace-period; raise it on large meshes.") diff --git a/cmd/agentmesh-one/main.go b/cmd/agentmesh-one/main.go index 5f049b2a..9247fb3c 100644 --- a/cmd/agentmesh-one/main.go +++ b/cmd/agentmesh-one/main.go @@ -225,6 +225,8 @@ func main() { // Embedded control plane tunables. rootCmd.Flags().DurationVar(&cpTunables.LeaseDuration, "control-plane-lease-duration", 0, "Router lease validity (0 keeps the component default)") + rootCmd.Flags().IntVar(&cpTunables.DHTReplication, "dht-replication", 0, "How many routers hold each service a member publishes and answer each lookup (0 keeps the component default of 3); published on /info for the whole mesh") + rootCmd.Flags().DurationVar(&cpTunables.DHTReprovideInterval, "dht-reprovide-interval", 0, "How often a member re-announces each service it publishes (0 keeps the component default of 5m); a stopped provider stays listed for three intervals; published on /info for the whole mesh") rootCmd.Flags().DurationVar(&cpTunables.KeyRotationInterval, "control-plane-key-rotation-interval", 0, "Biscuit signing key rotation interval (0 keeps the component default)") rootCmd.Flags().DurationVar(&cpTunables.KeyGracePeriod, "control-plane-key-grace-period", 0, "How long rotated-out keys stay valid for verification (0 keeps the component default)") rootCmd.Flags().DurationVar(&cpTunables.BiscuitTTL, "control-plane-biscuit-ttl", 0, "Lifespan minted into issued biscuits (0 keeps the component default)") @@ -237,8 +239,6 @@ func main() { rootCmd.Flags().IntVar(&routerTunables.LowWaterMark, "router-low-watermark", 0, "Connection manager low watermark (0 is 15% below the high watermark)") rootCmd.Flags().IntVar(&routerTunables.HighWaterMark, "router-high-watermark", 0, "Connection manager high watermark (0 keeps the component default)") rootCmd.Flags().IntVar(&routerTunables.ConnsPerSourceIP, "router-conns-per-source-ip", 0, "Per-source-IP connection budget (0 follows the high watermark; proxied peers share source IPs)") - rootCmd.Flags().DurationVar(&routerTunables.DHTProviderAddrTTL, "router-dht-provider-addr-ttl", 0, "How long a DHT provider record lives after its last announcement (0 keeps the component default of 15m)") - rootCmd.Flags().DurationVar(&routerTunables.DHTMaxRecordAge, "router-dht-max-record-age", 0, "DHT record max age (0 keeps the library default)") rootCmd.Flags().DurationVar(&routerTunables.RelayLimitDuration, "router-relay-limit-duration", 0, "Relayed connection lifetime (0 keeps the component default of 1h; use e.g. 24h for longer)") rootCmd.Flags().Var(&routerTunables.RelayLimitData, "router-relay-limit-data", "Bytes relayed per direction per connection, e.g. 512MiB (0 keeps the component default: no limit)") rootCmd.Flags().DurationVar(&routerTunables.ShutdownLeaseTTL, "router-shutdown-lease-ttl", router.DefaultShutdownLeaseTTL, "Sent in the embedded router's last lease when it stops: how long it expects to be away, which is how long the control plane keeps listing it (0 sends nothing)") diff --git a/cmd/agentmesh-router/main.go b/cmd/agentmesh-router/main.go index d5424051..5b423999 100644 --- a/cmd/agentmesh-router/main.go +++ b/cmd/agentmesh-router/main.go @@ -47,8 +47,6 @@ var ( connsPerSourceIP int logLevel string logFormat string - dhtProviderAddrTTL time.Duration - dhtMaxRecordAge time.Duration lowWaterMark int highWaterMark int metricsAddr string @@ -86,8 +84,6 @@ func main() { KeysDBPath: keysPath, AllowLoopback: allowLoopback, ConnsPerSourceIP: connsPerSourceIP, - DHTProviderAddrTTL: dhtProviderAddrTTL, - DHTMaxRecordAge: dhtMaxRecordAge, LowWaterMark: lowWaterMark, HighWaterMark: highWaterMark, MetricsAddr: metricsAddr, @@ -146,8 +142,6 @@ func main() { rootCmd.Flags().IntVar(&connsPerSourceIP, "conns-per-source-ip", 0, "Max inbound connections per source IP; 0 is a quarter of --high-watermark, so filling the router takes at least four addresses. Raise it where more members share one address") rootCmd.Flags().StringVar(&logLevel, "log-level", "info", "Log level (debug, info, warn, error)") rootCmd.Flags().StringVar(&logFormat, "log-format", "text", "Log format (text, json)") - rootCmd.Flags().DurationVar(&dhtProviderAddrTTL, "dht-provider-addr-ttl", router.DefaultDHTProviderAddrTTL, "How long a DHT provider record lives after its last announcement") - rootCmd.Flags().DurationVar(&dhtMaxRecordAge, "dht-max-record-age", 0, "Maximum age for DHT records (0s uses library default)") rootCmd.Flags().IntVar(&lowWaterMark, "low-watermark", 0, "Connections the router trims down to once it is over --high-watermark; 0 is 15% below the high watermark, so a trim sheds that share and no more") rootCmd.Flags().IntVar(&highWaterMark, "high-watermark", router.DefaultHighWaterMark, "Connections the router accepts before it trims; every member holds one, so this is the size of the mesh") rootCmd.Flags().StringVar(&metricsAddr, "metrics-addr", "", "Serve Prometheus /metrics, /healthz and /readyz on this address (e.g. 0.0.0.0:9090); unauthenticated, off by default") diff --git a/internal/controlplane/config.go b/internal/controlplane/config.go index fad39cd4..8e1924d0 100644 --- a/internal/controlplane/config.go +++ b/internal/controlplane/config.go @@ -32,12 +32,17 @@ type Options struct { // human user tokens. Tokens from a workload issuer are accepted at // /register, /refresh, and /token/exchange, and refused at /user/* and // /oauth/authorize. - WorkloadIssuer string - OIDCClientID string // OAuth client id advertised via /info; defaults to the first allowed audience - AllowedAudiences []string - LeaseDuration time.Duration - KeyRotationInterval time.Duration - KeyGracePeriod time.Duration + WorkloadIssuer string + OIDCClientID string // OAuth client id advertised via /info; defaults to the first allowed audience + AllowedAudiences []string + LeaseDuration time.Duration + KeyRotationInterval time.Duration + KeyGracePeriod time.Duration + // DHTReplication and DHTReprovideInterval are the mesh's directory + // parameters, published on /info for every member and router + // (api.DirectoryParameters); 0 is the api default of each. + DHTReplication int + DHTReprovideInterval time.Duration InsecureSkipTLSVerify bool BiscuitTimeout time.Duration BiscuitTTL time.Duration // Lifespan minted into every issued Biscuit's expiration() fact; defaults to api.BiscuitTokenTTL @@ -104,6 +109,12 @@ func (o *Options) Default() { if o.KeyGracePeriod <= 0 { o.KeyGracePeriod = 1 * time.Hour } + if o.DHTReplication == 0 { + o.DHTReplication = api.DefaultDHTReplication + } + if o.DHTReprovideInterval == 0 { + o.DHTReprovideInterval = api.DefaultReprovideInterval + } if o.BiscuitTTL <= 0 { o.BiscuitTTL = api.BiscuitTokenTTL } @@ -135,5 +146,8 @@ func (o *Options) Validate() error { if o.DataSourceName == "" { return fmt.Errorf("DataSourceName must be specified") } + if err := api.ValidateDirectoryParameters(o.DHTReplication, o.DHTReprovideInterval); err != nil { + return err + } return nil } diff --git a/internal/controlplane/server.go b/internal/controlplane/server.go index 0480ffdf..b628c749 100644 --- a/internal/controlplane/server.go +++ b/internal/controlplane/server.go @@ -671,6 +671,7 @@ func (s *Server) HandleInfo(w http.ResponseWriter, r *http.Request) { RouterAddresses: routerAddrs, // Reused this field for back-compatibility with bootstrap routers list Routers: s.routerInfos(r.Context(), activeRouters), BannedPeerIds: bannedPeerIDs, + Directory: api.NewDirectoryParameters(s.config.DHTReplication, s.config.DHTReprovideInterval), } respData, err := proto.Marshal(resp) diff --git a/internal/controlplane/server_test.go b/internal/controlplane/server_test.go index 4f6f379b..4b7ccc5e 100644 --- a/internal/controlplane/server_test.go +++ b/internal/controlplane/server_test.go @@ -4693,3 +4693,66 @@ func TestRefreshWithPlatformJWT(t *testing.T) { t.Errorf("expected updated ClaimsJSON to include new group claim, got %s", updatedRec.ClaimsJSON) } } + +// /info carries the directory parameters every member and router runs on: +// the two the operator sets and the TTL derived from them, with the api +// defaults when nothing was set. +func TestHandleInfoPublishesDirectoryParameters(t *testing.T) { + issuer, _ := startCustomMockOIDC(t) + for _, tc := range []struct { + name string + override func(*Options) + wantReplication int32 + wantReprovide time.Duration + }{ + {name: "defaults", override: func(*Options) {}, wantReplication: api.DefaultDHTReplication, wantReprovide: api.DefaultReprovideInterval}, + {name: "set", override: func(o *Options) { o.DHTReplication = 5; o.DHTReprovideInterval = 20 * time.Minute }, wantReplication: 5, wantReprovide: 20 * time.Minute}, + } { + t.Run(tc.name, func(t *testing.T) { + srv, st, _ := setupTestServer(t, issuer, tc.override) + defer func() { + _ = srv.Close() + _ = st.Close() + }() + rec := httptest.NewRecorder() + srv.HandleInfo(rec, httptest.NewRequest(http.MethodGet, "/info", nil)) + if rec.Code != http.StatusOK { + t.Fatalf("/info status = %d", rec.Code) + } + var info api.ControlPlaneInfoResponse + if err := proto.Unmarshal(rec.Body.Bytes(), &info); err != nil { + t.Fatalf("unmarshal /info: %v", err) + } + d := info.GetDirectory() + if d.GetReplication() != tc.wantReplication { + t.Errorf("directory.replication = %d, want %d", d.GetReplication(), tc.wantReplication) + } + if got := d.GetReprovideInterval().AsDuration(); got != tc.wantReprovide { + t.Errorf("directory.reprovide_interval = %s, want %s", got, tc.wantReprovide) + } + if got := d.GetRecordTtl().AsDuration(); got != api.RecordTTL(tc.wantReprovide) { + t.Errorf("directory.record_ttl = %s, want %s", got, api.RecordTTL(tc.wantReprovide)) + } + }) + } +} + +// An operator cannot publish a directory no member could run on. +func TestOptionsRefuseAnUnusableDirectory(t *testing.T) { + for _, tc := range []struct { + name string + set func(*Options) + }{ + {name: "no replica", set: func(o *Options) { o.DHTReplication = -1 }}, + {name: "no announcements", set: func(o *Options) { o.DHTReprovideInterval = -time.Second }}, + } { + t.Run(tc.name, func(t *testing.T) { + o := Options{DriverName: "sqlite", DataSourceName: "x"} + tc.set(&o) + o.Default() + if err := o.Validate(); err == nil { + t.Fatalf("Validate accepted %+v", o) + } + }) + } +} diff --git a/internal/node/controlplane_sync.go b/internal/node/controlplane_sync.go index 83b9eb94..741cc15b 100644 --- a/internal/node/controlplane_sync.go +++ b/internal/node/controlplane_sync.go @@ -212,6 +212,7 @@ func (n *AgentMeshNode) syncMeshInfo(ctx context.Context, controlPlaneURL string // the stored addresses do not carry. n.setRouterCatalog(candidatesFromInfo(ctx, info)) } + n.adoptDirectory(info.GetDirectory()) n.reconcileBannedPeers(info.GetBannedPeerIds(), fetchedAt) return nil } diff --git a/internal/node/debug_handlers.go b/internal/node/debug_handlers.go index 5924c457..f9b05cac 100644 --- a/internal/node/debug_handlers.go +++ b/internal/node/debug_handlers.go @@ -132,9 +132,12 @@ type meshInfoResponse struct { PeerID string `json:"peer_id"` ConnectedPeers []string `json:"connected_peers"` AttachedRouters []string `json:"attached_routers"` - DHTSize int `json:"dht_size"` - RouterPeerID string `json:"router_peer_id"` - LocalAPISocket string `json:"local_api_socket,omitempty"` + // DirectoryRouters are the routers the node holds a session with for + // its DHT alone: the ones its keys landed on, not attached. + DirectoryRouters []string `json:"directory_routers"` + DHTSize int `json:"dht_size"` + RouterPeerID string `json:"router_peer_id"` + LocalAPISocket string `json:"local_api_socket,omitempty"` } type connectivityResponse struct { @@ -191,14 +194,21 @@ func (n *AgentMeshNode) meshInfo() (*meshInfoResponse, error) { attachedRouters = append(attachedRouters, p.String()) } slices.Sort(attachedRouters) + directory := n.directoryRouters() + directoryRouters := make([]string, 0, len(directory)) + for _, p := range directory { + directoryRouters = append(directoryRouters, p.String()) + } + slices.Sort(directoryRouters) return &meshInfoResponse{ - PeerID: n.Host.ID().String(), - ConnectedPeers: connectedPeers, - AttachedRouters: attachedRouters, - DHTSize: n.DHT.RoutingTable().Size(), - RouterPeerID: n.RouterPeerID.String(), - LocalAPISocket: n.BoundSocketPath, + PeerID: n.Host.ID().String(), + ConnectedPeers: connectedPeers, + AttachedRouters: attachedRouters, + DirectoryRouters: directoryRouters, + DHTSize: n.DHT.RoutingTable().Size(), + RouterPeerID: n.RouterPeerID.String(), + LocalAPISocket: n.BoundSocketPath, }, nil } diff --git a/internal/node/dht.go b/internal/node/dht.go new file mode 100644 index 00000000..4add2d56 --- /dev/null +++ b/internal/node/dht.go @@ -0,0 +1,64 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package node + +import ( + dht "github.com/libp2p/go-libp2p-kad-dht" + "github.com/libp2p/go-libp2p-kad-dht/records" + + "github.com/google/agentmesh/api" +) + +// dhtOptions configures the member's DHT. A member is a DHT client whatever +// its reachability: the routers are the directory, and a member that served +// it would sit in every other member's routing table and take their lookups +// on a 400-connection budget, answer whoever connects, and be trusted for +// unsigned provider records. A public member is still dialled directly; the +// role in the directory and the way it is reached are separate things. +// +// The table is not refreshed. kad-dht treats a table of ten peers or fewer +// as starved and, every two minutes, pings every entry and runs a lookup +// for the member's own ID against all of them; with the routers as the only +// servers that is a connection to every router the member is not attached +// to, closed by the router at its auth deadline and opened again two +// minutes later, for nothing the member needs: the servers are the routers +// the control plane lists, learned on attach and from closer-peer answers, +// and a router that leaves the catalog leaves the table (setRouterCatalog). +// Bootstrap still fills the table once at start. +// +// The record TTL and the replication factor are the mesh's +// (ControlPlaneInfoResponse.directory). The TTL is the same figure the +// routers keep a record for, so a record the member's own store holds and +// one a router holds expire alike. The replication factor r is kademlia's +// k on the member: a record is stored on the r routers closest to its key, +// a lookup asks those r, r at a time. The routers keep every router in +// their own tables (router.dhtOptions) and answer FIND_NODE with the +// closest routers to the key in order, so a member learns the r closest +// from one answer of a router it is attached to and reaches them in one +// hop. A member therefore holds sessions with its K attached routers plus +// the r closest to each key it publishes or looks up, whatever the number +// of routers; with the library's k of 20 that would be every router of a +// mesh with fewer than 20. +func dhtOptions(directory api.DirectorySettings) []dht.Option { + return []dht.Option{ + dht.Mode(dht.ModeClient), + dht.DisableAutoRefresh(), + dht.ProtocolPrefix(api.DHTProtocolPrefix), + dht.BucketSize(directory.Replication), + dht.Concurrency(directory.Replication), + dht.Resiliency(directory.Replication), + dht.ProviderManagerOpts(records.ProviderAddrTTL(directory.RecordTTL), records.ProvideValidity(directory.RecordTTL)), + } +} diff --git a/internal/node/dht_test.go b/internal/node/dht_test.go new file mode 100644 index 00000000..0b3b43e8 --- /dev/null +++ b/internal/node/dht_test.go @@ -0,0 +1,161 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package node + +import ( + "context" + "fmt" + "slices" + "testing" + "time" + + "github.com/libp2p/go-libp2p" + dht "github.com/libp2p/go-libp2p-kad-dht" + "github.com/libp2p/go-libp2p/core/host" + "github.com/libp2p/go-libp2p/core/peer" + "github.com/libp2p/go-libp2p/core/protocol" + "github.com/libp2p/go-libp2p/core/test" + + "github.com/google/agentmesh/api" +) + +// TestMemberDHTStaysClientWhenPublic: a member whose reachability is public +// must not serve the DHT protocol, or it lands in every other member's +// routing table. The mode is the decision; the protocol list is what other +// members see of it. The control DHT, in the mode the node used before, +// shows that this host setup does flip a ModeAuto DHT to server. +func TestMemberDHTStaysClientWhenPublic(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + + newHost := func() host.Host { + h, err := libp2p.New(libp2p.ListenAddrStrings("/ip4/127.0.0.1/tcp/0"), libp2p.ForceReachabilityPublic()) + if err != nil { + t.Fatalf("libp2p.New: %v", err) + } + t.Cleanup(func() { _ = h.Close() }) + return h + } + serves := func(h host.Host) bool { + return slices.Contains(h.Mux().Protocols(), protocol.ID(api.DHTProtocolID)) + } + + control := newHost() + controlDHT, err := dht.New(control, dht.Mode(dht.ModeAuto), dht.ProtocolPrefix(api.DHTProtocolPrefix)) + if err != nil { + t.Fatalf("control dht.New: %v", err) + } + t.Cleanup(func() { _ = controlDHT.Close() }) + + member := newHost() + memberDHT, err := dht.New(member, dhtOptions(api.DefaultDirectorySettings())...) + if err != nil { + t.Fatalf("member dht.New: %v", err) + } + t.Cleanup(func() { _ = memberDHT.Close() }) + if err := memberDHT.Bootstrap(ctx); err != nil { + t.Fatalf("Bootstrap: %v", err) + } + + deadline := time.Now().Add(10 * time.Second) + for !serves(control) { + if time.Now().After(deadline) { + t.Fatalf("control DHT in ModeAuto on a public host never served %s; protocols: %v", api.DHTProtocolID, control.Mux().Protocols()) + } + time.Sleep(20 * time.Millisecond) + } + + if got := memberDHT.Mode(); got != dht.ModeClient { + t.Errorf("member DHT mode = %v, want %v", got, dht.ModeClient) + } + if serves(member) { + t.Errorf("member on a public host serves %s; protocols: %v", api.DHTProtocolID, member.Mux().Protocols()) + } +} + +// TestSetRouterCatalogPrunesDHTTable: with kad-dht's liveness pings off, the +// catalog is what takes a router out of the member's DHT table. +func TestSetRouterCatalogPrunesDHTTable(t *testing.T) { + h, err := libp2p.New(libp2p.NoListenAddrs) + if err != nil { + t.Fatalf("libp2p.New: %v", err) + } + t.Cleanup(func() { _ = h.Close() }) + kdht, err := dht.New(h, dhtOptions(api.DefaultDirectorySettings())...) + if err != nil { + t.Fatalf("dht.New: %v", err) + } + t.Cleanup(func() { _ = kdht.Close() }) + + listed, gone := test.RandPeerIDFatal(t), test.RandPeerIDFatal(t) + for _, p := range []peer.ID{listed, gone} { + if added, err := kdht.RoutingTable().TryAddPeer(p, false, false); err != nil || !added { + t.Fatalf("TryAddPeer(%s) = %v, %v; want added", p, added, err) + } + } + + n := &AgentMeshNode{config: Options{Routers: 2}, Host: h, DHT: kdht} + n.setRouterCatalog([]routerCandidate{{AddrInfo: peer.AddrInfo{ID: listed}}}) + + got := kdht.RoutingTable().ListPeers() + if len(got) != 1 || got[0] != listed { + t.Fatalf("routing table after the catalog dropped %s = %v, want [%s]", gone, got, listed) + } + + // An empty catalog says nothing about the routers and must not empty the table. + n.setRouterCatalog(nil) + if got := kdht.RoutingTable().ListPeers(); len(got) != 1 { + t.Fatalf("routing table after an empty catalog = %v, want [%s]", got, listed) + } +} + +// TestMemberDHTBucketHoldsTheReplicationFactor: the mesh's replication +// factor is kademlia's k on the member, and a bucket of its table holds +// that many. +func TestMemberDHTBucketHoldsTheReplicationFactor(t *testing.T) { + for _, r := range []int{2, api.DefaultDHTReplication, 5} { + t.Run(fmt.Sprintf("replication=%d", r), func(t *testing.T) { + h, err := libp2p.New(libp2p.NoListenAddrs) + if err != nil { + t.Fatalf("libp2p.New: %v", err) + } + t.Cleanup(func() { _ = h.Close() }) + settings := api.DefaultDirectorySettings() + settings.Replication = r + kdht, err := dht.New(h, dhtOptions(settings)...) + if err != nil { + t.Fatalf("dht.New: %v", err) + } + t.Cleanup(func() { _ = kdht.Close() }) + rt := kdht.RoutingTable() + added := 0 + for i := 0; i < r+1; i++ { + p, err := rt.GenRandPeerID(0) + if err != nil { + t.Fatalf("GenRandPeerID: %v", err) + } + // The last one is refused for capacity; any other refusal is a test bug. + if ok, err := rt.TryAddPeer(p, true, false); ok { + added++ + } else if added < r { + t.Fatalf("TryAddPeer #%d refused before the bucket was full: %v", i, err) + } + } + if added != r { + t.Fatalf("a bucket took %d peers, want %d", added, r) + } + }) + } +} diff --git a/internal/node/directory.go b/internal/node/directory.go new file mode 100644 index 00000000..ec44351d --- /dev/null +++ b/internal/node/directory.go @@ -0,0 +1,85 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package node + +import ( + "context" + + "github.com/libp2p/go-libp2p/core/peer" +) + +// authDirectoryRouter runs the credential handshake with a router this node +// just connected to without being attached to it. The DHT client opens such +// connections for a lookup or an announcement, to the routers kademlia puts +// closest to the key; left anonymous, the router closes them at its auth +// deadline a minute later and the next lookup opens them again. Handshaken, +// they are sessions: the router keeps them, counts them, and the node's +// lookups reuse them. The routers a node holds this way are its K attached +// ones plus those its keys land on, which is how a mesh with more routers +// spreads them; a node's own connection manager trims the idle ones first. +// Attached routers handshake on attach, and a connection that arrives while +// that handshake is in flight waits for it (authRouter). +func (n *AgentMeshNode) authDirectoryRouter(ctx context.Context, router peer.ID) { + if n.isAttached(router) || n.isAuthenticatedAndConnected(router) || !n.isCatalogRouter(router) { + return + } + go func() { + biscuitBytes, err := n.loadIdentityForAuth() + if err != nil { + logger.Debugf("[AuthN] Cannot authenticate to directory router %s without an identity: %v", router, err) + return + } + if err := n.authRouter(ctx, peer.AddrInfo{ID: router}, biscuitBytes); err != nil { + directoryHandshakesTotal.WithLabelValues("failed").Inc() + logger.Warnf("[AuthN] Directory router %s did not admit this node; the router will close the connection at its auth deadline: %v", router, err) + return + } + directoryHandshakesTotal.WithLabelValues("ok").Inc() + logger.Infof("[AuthN] Authenticated to directory router %s", router) + }() +} + +// isCatalogRouter reports whether p is a router the control plane lists. +func (n *AgentMeshNode) isCatalogRouter(p peer.ID) bool { + n.mu.Lock() + defer n.mu.Unlock() + for _, c := range n.routerCatalog { + if c.ID == p { + return true + } + } + return false +} + +// directoryRouters are the routers this node holds an authenticated session +// with for its DHT alone: connected and admitted, not attached. +func (n *AgentMeshNode) directoryRouters() []peer.ID { + attached := map[peer.ID]bool{} + n.mu.Lock() + for r := range n.attached { + attached[r] = true + } + n.mu.Unlock() + var out []peer.ID + for _, r := range n.authenticatedRouterIDs() { + if !attached[r] { + out = append(out, r) + } + } + return out +} + +// DirectoryRouters is how many routers this node holds for its DHT alone. +func (n *AgentMeshNode) DirectoryRouters() int { return len(n.directoryRouters()) } diff --git a/internal/node/directory_test.go b/internal/node/directory_test.go new file mode 100644 index 00000000..01dc190f --- /dev/null +++ b/internal/node/directory_test.go @@ -0,0 +1,72 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package node + +import ( + "context" + "testing" + "time" + + "github.com/libp2p/go-libp2p/core/peer" +) + +// TestConnectingToAnUnattachedRouterHandshakes: a connection the node opens +// to a catalog router it is not attached to, as its DHT does for a lookup, +// is handshaken once and counted as a directory session; the attached +// router's handshake is not repeated by it. +func TestConnectingToAnUnattachedRouterHandshakes(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) + defer cancel() + + r1 := newFakeRouter(t, "/ip4/127.0.0.1/tcp/0") + r2 := newFakeRouterWithKey(t, r1.cpPub, r1.cpPriv, "/ip4/127.0.0.1/tcp/0") + node := r1.startNode(t, ctx, append(r1.routerAddrs, r2.routerAddrs...), func(o *Options) { o.Routers = 1 }) + + attached := node.attachedRouters() + if len(attached) != 1 { + t.Fatalf("attached routers = %v, want one", attached) + } + other := r2 + if attached[0] == r2.h.ID() { + other = r1 + } + if got := node.DirectoryRouters(); got != 0 { + t.Fatalf("directory routers before any lookup = %d, want 0", got) + } + + // What the DHT client does when a key lands on the other router. + if err := node.Host.Connect(ctx, peer.AddrInfo{ID: other.h.ID(), Addrs: other.h.Addrs()}); err != nil { + t.Fatalf("Connect: %v", err) + } + deadline := time.Now().Add(5 * time.Second) + for !node.isAuthenticatedAndConnected(other.h.ID()) { + if time.Now().After(deadline) { + t.Fatalf("node did not authenticate to %s after connecting to it; handshakes seen there: %d", other.h.ID(), other.handshakes.Load()) + } + time.Sleep(20 * time.Millisecond) + } + if got := other.handshakes.Load(); got != 1 { + t.Errorf("unattached router saw %d handshakes, want 1", got) + } + if got := node.DirectoryRouters(); got != 1 { + t.Errorf("directory routers = %d, want 1", got) + } + if got := node.AttachedRouters(); got != 1 { + t.Errorf("attached routers after the directory handshake = %d, want 1", got) + } + if r1.handshakes.Load()+r2.handshakes.Load() != 2 { + t.Errorf("handshakes in all = %d, want 2: one per router", r1.handshakes.Load()+r2.handshakes.Load()) + } +} diff --git a/internal/node/node.go b/internal/node/node.go index 93a99902..f8c3c651 100644 --- a/internal/node/node.go +++ b/internal/node/node.go @@ -50,7 +50,6 @@ import ( "github.com/libp2p/go-libp2p" gostream "github.com/libp2p/go-libp2p-gostream" dht "github.com/libp2p/go-libp2p-kad-dht" - records "github.com/libp2p/go-libp2p-kad-dht/records" pubsub "github.com/libp2p/go-libp2p-pubsub" "github.com/libp2p/go-libp2p/core/crypto" "github.com/libp2p/go-libp2p/core/event" @@ -96,18 +95,10 @@ const ( // meshes, where an operator raising the grace period can raise this too. DefaultControlPlaneSyncInterval = 15 * time.Minute - // Reprovide interval - ReprovideInterval = 5 * time.Minute - - // DefaultDHTProviderAddrTTL is three of a node's 5-minute reprovides: when - // ModeAuto promotes a dialable node to a DHT server, a peer that is gone - // drops out of its provider store within this window while one late - // reprovide does not. - DefaultDHTProviderAddrTTL = 3 * ReprovideInterval - // reprovideRetryInterval is the first retry after a backend was withheld, - // doubling up to ReprovideInterval. Backends commonly start after the node - // does, and the full interval is far too long to wait for one of those. + // doubling up to the mesh's reprovide interval. Backends commonly start + // after the node does, and the full interval is far too long to wait for + // one of those. reprovideRetryInterval = 5 * time.Second ) @@ -166,6 +157,9 @@ type AgentMeshNode struct { authenticatedRouters map[peer.ID]bool // redialing holds the routers a redialRouter goroutine is working on. redialing map[peer.ID]bool + // handshaking holds the routers a handshake is in flight with; a second + // caller waits for that one (authRouter). + handshaking map[peer.ID]chan struct{} // routerCatalog is every router the node knows of; attached are the // Options.Routers of them it keeps a session with; shunned are the ones // it stays off until the time given, having been sent away or having @@ -176,20 +170,23 @@ type AgentMeshNode struct { transitCatalogRefreshedAt time.Time attached map[peer.ID]bool shunned map[peer.ID]time.Time - connMgr *connmgr.BasicConnMgr - peerLastEventTime map[string]int64 - mu sync.Mutex - nodeConfig *NodeConfigComplete - revokedPeers *lru.Cache[string, int64] - revokedTokens *lru.Cache[string, time.Time] - delegatedBiscuitCache *lru.Cache[string, *api.TokenExchangeResponse] - stsTokenCache *lru.Cache[string, *api.STSTokenResponse] - peerLabelGate *lru.Cache[string, time.Time] - authPeers sync.Map - trustedKeys []TrustedKey - keysMu sync.RWMutex - MeshPolicyRules []biscuit.Rule - MeshPolicyMu sync.RWMutex + // directory is how the mesh keeps its DHT, as the control plane publishes + // it on /info (syncMeshInfo); the api defaults until it has answered. + directory api.DirectorySettings + connMgr *connmgr.BasicConnMgr + peerLastEventTime map[string]int64 + mu sync.Mutex + nodeConfig *NodeConfigComplete + revokedPeers *lru.Cache[string, int64] + revokedTokens *lru.Cache[string, time.Time] + delegatedBiscuitCache *lru.Cache[string, *api.TokenExchangeResponse] + stsTokenCache *lru.Cache[string, *api.STSTokenResponse] + peerLabelGate *lru.Cache[string, time.Time] + authPeers sync.Map + trustedKeys []TrustedKey + keysMu sync.RWMutex + MeshPolicyRules []biscuit.Rule + MeshPolicyMu sync.RWMutex // pendingEgress holds assignments that arrived before Start created the // service registry (SyncControlPlane runs first); Start applies them. pendingEgress []*api.EgressDestination @@ -431,6 +428,7 @@ func NewAgentMeshNode(cfg Options) (*AgentMeshNode, error) { trustedKeys: trustedKeys, peerLastEventTime: make(map[string]int64), authenticatedRouters: make(map[peer.ID]bool), + directory: api.DefaultDirectorySettings(), nodeConfig: cfg.NodeConfig, AllowLoopback: cfg.AllowLoopback, authSuccess: make(chan struct{}), @@ -641,7 +639,7 @@ func (n *AgentMeshNode) Start(ctx context.Context) error { n.Host = h h.Network().Notify(&network.NotifyBundle{ - ConnectedF: func(n network.Network, c network.Conn) { + ConnectedF: func(_ network.Network, c network.Conn) { remotePeer := c.RemotePeer() remoteAddr := c.RemoteMultiaddr() if hasCircuit(remoteAddr) || !isPrivateIP(remoteAddr) { @@ -650,6 +648,9 @@ func (n *AgentMeshNode) Start(ctx context.Context) error { } logger.Debugf("[Discovery] Peer %s connected via relay/public IP (%s), marking private IP as failed", remotePeer, remoteAddr) } + if c.Stat().Direction == network.DirOutbound { + n.authDirectoryRouter(ctx, remotePeer) + } }, // The router drops us from its authenticated set the moment our last // connection to it closes, so a stale entry here would report a @@ -688,19 +689,7 @@ func (n *AgentMeshNode) Start(ctx context.Context) error { } // Initialize Rendezvous (DHT Client) - dhtOpts := []dht.Option{ - dht.Mode(dht.ModeAuto), - dht.ProtocolPrefix(api.DHTProtocolPrefix), - } - providerTTL := n.config.DHTProviderAddrTTL - if providerTTL <= 0 { - providerTTL = DefaultDHTProviderAddrTTL - } - dhtOpts = append(dhtOpts, dht.ProviderManagerOpts(records.ProviderAddrTTL(providerTTL), records.ProvideValidity(providerTTL))) - if n.config.DHTMaxRecordAge > 0 { - dhtOpts = append(dhtOpts, dht.MaxRecordAge(n.config.DHTMaxRecordAge)) - } - kdht, err := dht.New(h, dhtOpts...) + kdht, err := dht.New(h, dhtOptions(n.directorySettings())...) if err != nil { return err } @@ -803,7 +792,7 @@ func (n *AgentMeshNode) Start(ctx context.Context) error { go p2plimits.ObserveStreamPressure(n.Host.Network().Conns, busiestConnStreams, logger.Warnf).Run(ctx, streamPressureInterval) // Periodically and on-demand reprovide registered services to the DHT - n.startReprovideLoop(ctx, ReprovideInterval) + n.startReprovideLoop(ctx) // Everything read from the control plane (keys, bans, router addresses, // policy) in one loop; gossip events only bring the next pull forward. @@ -825,7 +814,10 @@ func (n *AgentMeshNode) triggerReprovide() { } } -func (n *AgentMeshNode) startReprovideLoop(ctx context.Context, interval time.Duration) { +// startReprovideLoop re-announces the node's services every reprovide +// interval of the mesh, read at each cycle so a control plane that changed +// it is followed at the next one. +func (n *AgentMeshNode) startReprovideLoop(ctx context.Context) { go func() { initialDelay := 5 * time.Second if n.config.DiscoveryInterval != "" { @@ -852,6 +844,7 @@ func (n *AgentMeshNode) startReprovideLoop(ctx context.Context, interval time.Du } } + interval := n.directorySettings().Reprovide next := interval if n.services.ReprovideAll(ctx) > 0 { // A backend that is not answering yet is usually one that is @@ -867,6 +860,28 @@ func (n *AgentMeshNode) startReprovideLoop(ctx context.Context, interval time.Du }() } +// directorySettings is how the mesh keeps its DHT, as last read from the +// control plane. +func (n *AgentMeshNode) directorySettings() api.DirectorySettings { + n.mu.Lock() + defer n.mu.Unlock() + return n.directory +} + +// adoptDirectory takes the directory parameters /info published. The +// reprovide interval applies from the next cycle; the record TTL is set +// when the DHT is built, so a change to it applies at the next start. +func (n *AgentMeshNode) adoptDirectory(p *api.DirectoryParameters) { + settings := api.DirectorySettingsFrom(p) + n.mu.Lock() + changed := settings != n.directory + n.directory = settings + n.mu.Unlock() + if changed { + logger.Infof("[DHT] Directory parameters from the control plane: replication %d, reprovide every %s, records kept %s", settings.Replication, settings.Reprovide, settings.RecordTTL) + } +} + func (n *AgentMeshNode) IsConnected() bool { n.mu.Lock() defer n.mu.Unlock() @@ -1175,12 +1190,41 @@ func (n *AgentMeshNode) loadIdentityForAuth() ([]byte, error) { // and runs the handshake, unless a live authenticated session to it already // exists: Host.Connect is a no-op on an existing connection, but the // handshake is not, and repeating it trips the router's per-peer handshake -// limiter. The dial is bounded by RouterConnectTimeout so a router that is -// down does not hold its caller for long. +// limiter. For the same reason two callers reaching for the same router at +// once, as the attach path and the directory path do when its connection +// comes up, run one handshake: the second waits for the first. The dial is +// bounded by RouterConnectTimeout so a router that is down does not hold +// its caller for long. func (n *AgentMeshNode) authRouter(ctx context.Context, router peer.AddrInfo, biscuitBytes []byte) error { if n.isAuthenticatedAndConnected(router.ID) { return nil } + n.mu.Lock() + if done, inFlight := n.handshaking[router.ID]; inFlight { + n.mu.Unlock() + select { + case <-done: + case <-ctx.Done(): + return ctx.Err() + } + if n.isAuthenticatedAndConnected(router.ID) { + return nil + } + return fmt.Errorf("router %s: the handshake in flight did not admit this node", router.ID) + } + if n.handshaking == nil { + n.handshaking = map[peer.ID]chan struct{}{} + } + done := make(chan struct{}) + n.handshaking[router.ID] = done + n.mu.Unlock() + defer func() { + n.mu.Lock() + delete(n.handshaking, router.ID) + n.mu.Unlock() + close(done) + }() + routerCtx, cancel := context.WithTimeout(ctx, n.config.RouterConnectTimeout) defer cancel() if err := n.Host.Connect(routerCtx, router); err != nil { diff --git a/internal/node/node_metrics.go b/internal/node/node_metrics.go index ebd65547..f8da526f 100644 --- a/internal/node/node_metrics.go +++ b/internal/node/node_metrics.go @@ -101,6 +101,17 @@ var transitHandshakesTotal = promauto.NewCounterVec( []string{"router", "result"}, ) +// directoryHandshakesTotal counts the handshakes run with a router this node +// connected to for its DHT without being attached to it, so the router +// keeps the connection as a session; see authDirectoryRouter. +var directoryHandshakesTotal = promauto.NewCounterVec( + prometheus.CounterOpts{ + Name: "agentmesh_node_directory_handshakes_total", + Help: "Handshakes with a router this node connected to for a DHT lookup or announcement without being attached to it, by result", + }, + []string{"result"}, +) + // nodeStateCollector exports the node's own view of its mesh membership: the // same figures GET /debug/mesh-info and /debug/token-info answer on demand, // as gauges a scraper can keep. A node that runs unattended, such as a @@ -152,7 +163,7 @@ func newNodeStateCollector(n *AgentMeshNode) *nodeStateCollector { []string{"type"}, nil), routersDesc: prometheus.NewDesc( "agentmesh_node_routers", - "Routers this node keeps a session with: attached now, and wanted (--routers, or every router listed when fewer)", + "Routers this node keeps a session with: attached now, wanted (--routers, or every router listed when fewer), and directory (authenticated for DHT lookups and announcements without being attached)", []string{"state"}, nil), routerCandsDesc: prometheus.NewDesc( "agentmesh_node_router_candidates", @@ -188,6 +199,7 @@ func (c *nodeStateCollector) Collect(ch chan<- prometheus.Metric) { ch <- prometheus.MustNewConstMetric(c.routersDesc, prometheus.GaugeValue, float64(n.AttachedRouters()), "attached") ch <- prometheus.MustNewConstMetric(c.routersDesc, prometheus.GaugeValue, float64(n.wantRouters()), "wanted") + ch <- prometheus.MustNewConstMetric(c.routersDesc, prometheus.GaugeValue, float64(n.DirectoryRouters()), "directory") cands := n.routerCandidates() _, matching := selectRouters(cands, 0, n.config.RouterSelector, nil, nil) ch <- prometheus.MustNewConstMetric(c.routerCandsDesc, prometheus.GaugeValue, float64(len(cands)), "any") diff --git a/internal/node/node_test.go b/internal/node/node_test.go index 43da0fd8..7606e3ce 100644 --- a/internal/node/node_test.go +++ b/internal/node/node_test.go @@ -791,8 +791,6 @@ func TestNewAgentMeshNode_DHTOptions(t *testing.T) { PrivKey: priv, Store: store, ListenAddrs: []string{"/ip4/127.0.0.1/tcp/0"}, - DHTProviderAddrTTL: 10 * time.Minute, - DHTMaxRecordAge: 15 * time.Second, DHTLookupLimit: 50, DiscoveryConcurrency: 5, } @@ -802,12 +800,6 @@ func TestNewAgentMeshNode_DHTOptions(t *testing.T) { t.Fatalf("failed to create node with DHT options: %v", err) } - if node.config.DHTProviderAddrTTL != 10*time.Minute { - t.Errorf("expected DHTProviderAddrTTL to be 10m, got %v", node.config.DHTProviderAddrTTL) - } - if node.config.DHTMaxRecordAge != 15*time.Second { - t.Errorf("expected DHTMaxRecordAge to be 15s, got %v", node.config.DHTMaxRecordAge) - } if node.config.DHTLookupLimit != 50 { t.Errorf("expected DHTLookupLimit to be 50, got %d", node.config.DHTLookupLimit) } diff --git a/internal/node/options.go b/internal/node/options.go index afb45332..d2565f14 100644 --- a/internal/node/options.go +++ b/internal/node/options.go @@ -128,10 +128,6 @@ type Options struct { // BiscuitTimeout bounds Datalog evaluation when verifying biscuit tokens. BiscuitTimeout time.Duration // DHT Options - // DHTProviderAddrTTL is how long a provider record lives after its last - // announcement; 0 is DefaultDHTProviderAddrTTL. - DHTProviderAddrTTL time.Duration - DHTMaxRecordAge time.Duration DHTLookupLimit int DiscoveryConcurrency int // RequiredRole restricts enrollment and startup to only accept tokens containing this role. @@ -283,10 +279,5 @@ func (o *Options) Validate() error { if len(o.ControlPlanePubKey) > 0 && len(o.ControlPlanePubKey) != ed25519.PublicKeySize { return fmt.Errorf("control plane public key must be %d bytes, got %d", ed25519.PublicKeySize, len(o.ControlPlanePubKey)) } - // A record that expires between two announcements is absent most of the - // time; the node refuses the configuration rather than announcing faster. - if o.DHTProviderAddrTTL != 0 && o.DHTProviderAddrTTL <= ReprovideInterval { - return fmt.Errorf("DHTProviderAddrTTL %s must be strictly longer than the %s between announcements; %s is used by default", o.DHTProviderAddrTTL, ReprovideInterval, DefaultDHTProviderAddrTTL) - } return nil } diff --git a/internal/node/options_test.go b/internal/node/options_test.go index c003e3e5..109370b2 100644 --- a/internal/node/options_test.go +++ b/internal/node/options_test.go @@ -19,7 +19,6 @@ import ( "crypto/rand" "strings" "testing" - "time" "github.com/google/agentmesh/api" "github.com/libp2p/go-libp2p/core/crypto" @@ -57,37 +56,6 @@ func TestOptionsValidateControlPlaneKeySize(t *testing.T) { } } -// A provider record that expires between two announcements is absent most -// of the time. The node refuses the configuration instead of compensating. -func TestOptionsValidateProviderTTLOutlivesReprovide(t *testing.T) { - priv, _, err := crypto.GenerateEd25519Key(rand.Reader) - if err != nil { - t.Fatal(err) - } - store, err := NewStore(t.TempDir()) - if err != nil { - t.Fatal(err) - } - defer func() { _ = store.Close() }() - base := Options{PrivKey: priv, Store: store, RequiredRole: api.RoleNode} - - for _, ttl := range []time.Duration{0, DefaultDHTProviderAddrTTL, time.Hour} { - o := base - o.DHTProviderAddrTTL = ttl - if err := o.Validate(); err != nil { - t.Errorf("DHTProviderAddrTTL %s rejected: %v", ttl, err) - } - } - for _, ttl := range []time.Duration{ReprovideInterval, ReprovideInterval - time.Second} { - o := base - o.DHTProviderAddrTTL = ttl - err = o.Validate() - if err == nil || !strings.Contains(err.Error(), "between announcements") { - t.Errorf("DHTProviderAddrTTL %s: err = %v, want a refusal naming the announcement interval", o.DHTProviderAddrTTL, err) - } - } -} - func TestOptionsReachability(t *testing.T) { priv, _, err := crypto.GenerateEd25519Key(rand.Reader) if err != nil { diff --git a/internal/node/routers.go b/internal/node/routers.go index 7f1ba033..b287c16d 100644 --- a/internal/node/routers.go +++ b/internal/node/routers.go @@ -162,6 +162,30 @@ func (n *AgentMeshNode) setRouterCatalog(cands []routerCandidate) { n.Host.Peerstore().AddAddrs(c.ID, c.Addrs, peerstore.PermanentAddrTTL) } } + n.pruneDHTTable(cands) +} + +// pruneDHTTable drops from the DHT routing table every peer the catalog +// does not list. The routers are the only DHT servers, so the catalog is +// the table's source of truth; kad-dht's own liveness pings are off +// (dhtOptions), and without this a router that left would stay in the +// table until a lookup failed against it. +func (n *AgentMeshNode) pruneDHTTable(cands []routerCandidate) { + if n.DHT == nil || len(cands) == 0 { + return + } + listed := make(map[peer.ID]bool, len(cands)) + for _, c := range cands { + listed[c.ID] = true + } + rt := n.DHT.RoutingTable() + for _, p := range rt.ListPeers() { + if listed[p] { + continue + } + rt.RemovePeer(p) + logger.Infof("[DHT] Dropped %s from the routing table: the control plane no longer lists it as a router", p) + } } func (n *AgentMeshNode) routerCandidates() []routerCandidate { diff --git a/internal/router/config.go b/internal/router/config.go index 51e21f66..ecd67fc9 100644 --- a/internal/router/config.go +++ b/internal/router/config.go @@ -51,10 +51,6 @@ type Options struct { AllowLoopback bool BiscuitTimeout time.Duration LogVerbose bool - // DHTProviderAddrTTL is how long a provider record lives after its last - // announcement; 0 is DefaultDHTProviderAddrTTL. - DHTProviderAddrTTL time.Duration - DHTMaxRecordAge time.Duration LowWaterMark int HighWaterMark int // RelayLimitDuration / RelayLimitData cap each relayed connection; 0 is diff --git a/internal/router/dht.go b/internal/router/dht.go new file mode 100644 index 00000000..083ef6c8 --- /dev/null +++ b/internal/router/dht.go @@ -0,0 +1,64 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package router + +import ( + "fmt" + "time" + + dht "github.com/libp2p/go-libp2p-kad-dht" + "github.com/libp2p/go-libp2p-kad-dht/records" + + "github.com/google/agentmesh/api" +) + +// dhtTableBucketSize is kademlia's k on a router: how many routers one +// bucket of its table holds, and how many closest routers it names in a +// FIND_NODE answer. The routers are a full mesh, so a table that holds them +// all costs nothing, and it is what lets a member find the routers closest +// to a key in one answer from a router it is attached to (node.dhtOptions): +// the answer lists the closest routers in order and the member keeps the +// first 2r. A mesh with more routers than this per bucket still works; a +// lookup takes a hop more. +const dhtTableBucketSize = 64 + +// dhtOptions configures the router's DHT: a server for the members and the +// other routers, with every router in its table. The record TTL is the +// mesh's (syncDirectory): three of the members' reprovide intervals, so a +// node that is gone drops out of discovery within it and two missed +// announcements do not unlist one that is not. The library default (48h) +// would keep every past identity listed. +func dhtOptions(directory api.DirectorySettings) []dht.Option { + return []dht.Option{ + dht.Mode(dht.ModeServer), + dht.ProtocolPrefix(api.DHTProtocolPrefix), + dht.BucketSize(dhtTableBucketSize), + dht.ProviderManagerOpts(records.ProviderAddrTTL(directory.RecordTTL), records.ProvideValidity(directory.RecordTTL)), + } +} + +// syncDirectory reads the mesh's directory parameters from /info before the +// DHT is built, since its record TTL is fixed at construction. The control +// plane answered the keys sync a moment ago, so a failure here is one to +// stop on rather than to run a TTL of the router's own. +func (r *Router) syncDirectory() error { + info, err := r.controlPlane(10 * time.Second).FetchInfo(r.ctx) + if err != nil { + return fmt.Errorf("directory parameters: %w", err) + } + r.directory = api.DirectorySettingsFrom(info.GetDirectory()) + logger.Infof("[DHT] Directory parameters from the control plane: replication %d, reprovide every %s, records kept %s", r.directory.Replication, r.directory.Reprovide, r.directory.RecordTTL) + return nil +} diff --git a/internal/router/dht_test.go b/internal/router/dht_test.go new file mode 100644 index 00000000..4f51cbd1 --- /dev/null +++ b/internal/router/dht_test.go @@ -0,0 +1,55 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package router + +import ( + "testing" + + "github.com/libp2p/go-libp2p" + dht "github.com/libp2p/go-libp2p-kad-dht" + + "github.com/google/agentmesh/api" +) + +// TestRouterDHTTableHoldsEveryRouter: a router's table bucket takes more +// routers than the library's 20, so a member's lookup gets the closest +// routers to its key from one answer. +func TestRouterDHTTableHoldsEveryRouter(t *testing.T) { + h, err := libp2p.New(libp2p.NoListenAddrs) + if err != nil { + t.Fatalf("libp2p.New: %v", err) + } + t.Cleanup(func() { _ = h.Close() }) + kdht, err := dht.New(h, dhtOptions(api.DefaultDirectorySettings())...) + if err != nil { + t.Fatalf("dht.New: %v", err) + } + t.Cleanup(func() { _ = kdht.Close() }) + if got := kdht.Mode(); got != dht.ModeServer { + t.Fatalf("router DHT mode = %v, want %v", got, dht.ModeServer) + } + + rt := kdht.RoutingTable() + const routers = 40 + for i := 0; i < routers; i++ { + p, err := rt.GenRandPeerID(0) + if err != nil { + t.Fatalf("GenRandPeerID: %v", err) + } + if ok, err := rt.TryAddPeer(p, true, false); !ok { + t.Fatalf("router #%d refused from the table: %v; a bucket must hold at least %d", i+1, err, routers) + } + } +} diff --git a/internal/router/router.go b/internal/router/router.go index 235f64af..6b778146 100644 --- a/internal/router/router.go +++ b/internal/router/router.go @@ -40,7 +40,6 @@ import ( golog "github.com/ipfs/go-log/v2" "github.com/libp2p/go-libp2p" dht "github.com/libp2p/go-libp2p-kad-dht" - records "github.com/libp2p/go-libp2p-kad-dht/records" pubsub "github.com/libp2p/go-libp2p-pubsub" coreconnmgr "github.com/libp2p/go-libp2p/core/connmgr" "github.com/libp2p/go-libp2p/core/control" @@ -90,10 +89,6 @@ const ( // orders by tag value, lowest first, so the untagged go before members. memberTag = "agentmesh-member" memberTagValue = 100 - // DefaultDHTProviderAddrTTL is three of a node's 5-minute reprovides: a - // node that is gone drops out of discovery within it, one late reprovide - // does not. The library default (48h) keeps every past identity listed. - DefaultDHTProviderAddrTTL = 15 * time.Minute ) var _ coreconnmgr.ConnectionGater = (*routerConnGate)(nil) @@ -334,6 +329,9 @@ type Router struct { EventTopic *pubsub.Topic authenticatedPeers sync.Map bannedPeers sync.Map + // directory is how the mesh keeps its DHT, read from /info at start + // (syncDirectory). + directory api.DirectorySettings // peerRouters is the other routers of the mesh, as /info lists them or as // a handshake proved them: held whatever the load and never sent away. peerRouters sync.Map @@ -454,6 +452,9 @@ func (r *Router) Start() (retErr error) { if err := r.syncKeys(); err != nil { return fmt.Errorf("failed initial keys sync: %w", err) } + if err := r.syncDirectory(); err != nil { + return err + } // 4. Initialize libp2p host cm, err := connmgr.NewConnManager(r.config.LowWaterMark, r.config.HighWaterMark, connmgr.WithGracePeriod(ConnGracePeriod)) @@ -503,19 +504,7 @@ func (r *Router) Start() (retErr error) { r.Host = hostNode // Setup DHT - dhtOpts := []dht.Option{ - dht.Mode(dht.ModeServer), - dht.ProtocolPrefix(api.DHTProtocolPrefix), - } - providerTTL := r.config.DHTProviderAddrTTL - if providerTTL <= 0 { - providerTTL = DefaultDHTProviderAddrTTL - } - dhtOpts = append(dhtOpts, dht.ProviderManagerOpts(records.ProviderAddrTTL(providerTTL), records.ProvideValidity(providerTTL))) - if r.config.DHTMaxRecordAge > 0 { - dhtOpts = append(dhtOpts, dht.MaxRecordAge(r.config.DHTMaxRecordAge)) - } - kadDHT, err := dht.New(hostNode, dhtOpts...) + kadDHT, err := dht.New(hostNode, dhtOptions(r.directory)...) if err != nil { return err } diff --git a/internal/standalone/standalone.go b/internal/standalone/standalone.go index 230601e9..a66d3f31 100644 --- a/internal/standalone/standalone.go +++ b/internal/standalone/standalone.go @@ -128,6 +128,10 @@ type ControlPlaneTunables struct { KeyRotationInterval time.Duration // KeyGracePeriod keeps rotated-out keys valid for verification. KeyGracePeriod time.Duration + // DHTReplication and DHTReprovideInterval are the mesh's directory + // parameters; see controlplane.Options. + DHTReplication int + DHTReprovideInterval time.Duration // BiscuitTTL is the lifespan minted into issued biscuits. BiscuitTTL time.Duration // WorkloadSessionTTL is how long a workload-issuer enrollment stays @@ -151,9 +155,6 @@ type RouterTunables struct { // the connection manager high watermark (proxied deployments share // source IPs, so the global cap should be what binds). ConnsPerSourceIP int - // DHTProviderAddrTTL / DHTMaxRecordAge tune DHT record lifetimes. - DHTProviderAddrTTL time.Duration - DHTMaxRecordAge time.Duration // RelayLimitDuration / RelayLimitData cap each relayed connection. RelayLimitDuration time.Duration RelayLimitData router.ByteSize @@ -285,6 +286,8 @@ func (s *Server) Start(ctx context.Context) (retErr error) { AllowedAudiences: s.opts.AllowedAudiences, LeaseDuration: s.opts.ControlPlane.LeaseDuration, KeyRotationInterval: s.opts.ControlPlane.KeyRotationInterval, + DHTReplication: s.opts.ControlPlane.DHTReplication, + DHTReprovideInterval: s.opts.ControlPlane.DHTReprovideInterval, KeyGracePeriod: s.opts.ControlPlane.KeyGracePeriod, BiscuitTTL: s.opts.ControlPlane.BiscuitTTL, WorkloadSessionTTL: s.opts.ControlPlane.WorkloadSessionTTL, @@ -374,8 +377,6 @@ func (s *Server) Start(ctx context.Context) (retErr error) { LeaseRenewInterval: s.opts.Router.LeaseRenewInterval, LowWaterMark: s.opts.Router.LowWaterMark, HighWaterMark: s.opts.Router.HighWaterMark, - DHTProviderAddrTTL: s.opts.Router.DHTProviderAddrTTL, - DHTMaxRecordAge: s.opts.Router.DHTMaxRecordAge, RelayLimitDuration: s.opts.Router.RelayLimitDuration, RelayLimitData: int64(s.opts.Router.RelayLimitData), ShutdownLeaseTTL: s.opts.Router.ShutdownLeaseTTL, diff --git a/sdk/js/src/discovery.ts b/sdk/js/src/discovery.ts index 0ddee318..9a3669d0 100644 --- a/sdk/js/src/discovery.ts +++ b/sdk/js/src/discovery.ts @@ -22,6 +22,14 @@ import { sha256 } from "multiformats/hashes/sha2"; /** The DHT protocol, go-libp2p-kad-dht with dht.ProtocolPrefix("/mesh"). */ export const DHT_PROTOCOL = "/mesh/kad/1.0.0"; +/** + * How many routers hold each record and answer each lookup when the control + * plane publishes no directory parameters on /info: api.DefaultDHTReplication. + * A member reads the mesh's value from /info (AgentMesh.dhtReplication) and + * runs its DHT client on it (createMeshHost), as agentmesh-node does. + */ +export const DEFAULT_DHT_REPLICATION = 3; + export type ServiceType = "mcp" | "inference" | "a2a" | "egress"; export function isServiceType(value: unknown): value is ServiceType { diff --git a/sdk/js/src/gen/agentmesh_pb.ts b/sdk/js/src/gen/agentmesh_pb.ts index d90d9ba5..75b3e8ce 100644 --- a/sdk/js/src/gen/agentmesh_pb.ts +++ b/sdk/js/src/gen/agentmesh_pb.ts @@ -26,7 +26,7 @@ import type { Message } from "@bufbuild/protobuf"; * Describes the file agentmesh.proto. */ export const file_agentmesh: GenFile = /*@__PURE__*/ - fileDesc("Cg9hZ2VudG1lc2gucHJvdG8SDGFnZW50bWVzaC52MSI0CglBdXRoRnJhbWUSDwoHYmlzY3VpdBgBIAEoDBIWCg50YXJnZXRfc2VydmljZRgCIAEoCSI/CgxBdXRoUmVzcG9uc2USDwoHc3VjY2VzcxgBIAEoCBINCgVlcnJvchgCIAEoCRIPCgdiaXNjdWl0GAMgASgMIrEBCgxSb3V0ZXJHb0F3YXkSMQoGcmVhc29uGAEgASgOMiEuYWdlbnRtZXNoLnYxLlJvdXRlckdvQXdheS5SZWFzb24SLgoLcmV0cnlfYWZ0ZXIYAiABKAsyGS5nb29nbGUucHJvdG9idWYuRHVyYXRpb24iPgoGUmVhc29uEhYKElJFQVNPTl9VTlNQRUNJRklFRBAAEgwKCERSQUlOSU5HEAESDgoKT1ZFUkxPQURFRBACItwBCglNZXNoRXZlbnQSKgoEdHlwZRgBIAEoDjIcLmFnZW50bWVzaC52MS5NZXNoRXZlbnQuVHlwZRIPCgdwZWVyX2lkGAIgASgJEi4KCmV2ZW50X3RpbWUYAyABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEhYKDm5ld19wdWJsaWNfa2V5GAQgASgMEhEKCXNpZ25hdHVyZRgFIAEoDCI3CgRUeXBlEgoKBkJBTk5FRBAAEhAKDEtFWV9ST1RBVElPThABEhEKDVBPTElDWV9VUERBVEUQAiL5AQoNRW5yb2xsUmVxdWVzdBILCgNqd3QYASABKAkSDwoHcGVlcl9pZBgCIAEoCRISCgpwdWJsaWNfa2V5GAMgASgMEhYKDnJlcXVlc3RlZF9yb2xlGAQgASgJEjcKBmxhYmVscxgFIAMoCzInLmFnZW50bWVzaC52MS5FbnJvbGxSZXF1ZXN0LkxhYmVsc0VudHJ5EhkKEWNoYWxsZW5nZV91bml4X21zGAYgASgDEhsKE2NoYWxsZW5nZV9zaWduYXR1cmUYByABKAwaLQoLTGFiZWxzRW50cnkSCwoDa2V5GAEgASgJEg0KBXZhbHVlGAIgASgJOgI4ASKrAQoORW5yb2xsUmVzcG9uc2USFQoNYmlzY3VpdF90b2tlbhgBIAEoDBIVCg1lcnJvcl9tZXNzYWdlGAIgASgJEiAKGGNvbnRyb2xfcGxhbmVfcHVibGljX2tleRgDIAEoDBIYChByb3V0ZXJfYWRkcmVzc2VzGAQgAygJEi8KC2V4cGlyZV90aW1lGAUgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcCKXAgoWQm9vdHN0cmFwRW5yb2xsUmVxdWVzdBIXCg9ib290c3RyYXBfdG9rZW4YASABKAkSDwoHcGVlcl9pZBgCIAEoCRISCgpwdWJsaWNfa2V5GAMgASgMEhYKDnJlcXVlc3RlZF9yb2xlGAQgASgJEkAKBmxhYmVscxgFIAMoCzIwLmFnZW50bWVzaC52MS5Cb290c3RyYXBFbnJvbGxSZXF1ZXN0LkxhYmVsc0VudHJ5EhkKEWNoYWxsZW5nZV91bml4X21zGAYgASgDEhsKE2NoYWxsZW5nZV9zaWduYXR1cmUYByABKAwaLQoLTGFiZWxzRW50cnkSCwoDa2V5GAEgASgJEg0KBXZhbHVlGAIgASgJOgI4ASKDAgoXQm9vdHN0cmFwRW5yb2xsUmVzcG9uc2USLgoGc3RhdHVzGAEgASgOMh4uYWdlbnRtZXNoLnYxLkVucm9sbG1lbnRTdGF0dXMSFQoNYmlzY3VpdF90b2tlbhgCIAEoDBIdChVwb2xsX2ludGVydmFsX3NlY29uZHMYAyABKAUSFQoNZXJyb3JfbWVzc2FnZRgEIAEoCRIgChhjb250cm9sX3BsYW5lX3B1YmxpY19rZXkYBSABKAwSGAoQcm91dGVyX2FkZHJlc3NlcxgGIAMoCRIvCgtleHBpcmVfdGltZRgHIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXAiWQoLU2VydmljZUluZm8SJwoEdHlwZRgBIAEoDjIZLmFnZW50bWVzaC52MS5TZXJ2aWNlVHlwZRIMCgRuYW1lGAIgASgJEhMKC2Rlc2NyaXB0aW9uGAMgASgJIoEBCg5Db21tYW5kQmFja2VuZBIPCgdjb21tYW5kGAEgAygJEjIKA2VudhgCIAMoCzIlLmFnZW50bWVzaC52MS5Db21tYW5kQmFja2VuZC5FbnZFbnRyeRoqCghFbnZFbnRyeRILCgNrZXkYASABKAkSDQoFdmFsdWUYAiABKAk6AjgBIpYBChZSZWdpc3RlclNlcnZpY2VSZXF1ZXN0EioKB3NlcnZpY2UYASABKAsyGS5hZ2VudG1lc2gudjEuU2VydmljZUluZm8SFAoKdGFyZ2V0X3VybBgCIAEoCUgAEi8KB2NvbW1hbmQYAyABKAsyHC5hZ2VudG1lc2gudjEuQ29tbWFuZEJhY2tlbmRIAEIJCgdiYWNrZW5kImkKEkRpc2NvdmVyZWRQcm92aWRlchIPCgdwZWVyX2lkGAEgASgJEhcKD2xvY2FsX3Byb3h5X3VybBgCIAEoCRIQCghzcnZfbmFtZRgDIAEoCRIXCg9zcnZfZGVzY3JpcHRpb24YBCABKAkivgIKD1NlcnZpY2VBbm5vdW5jZRIPCgdwZWVyX2lkGAEgASgJEicKBHR5cGUYAiABKA4yGS5hZ2VudG1lc2gudjEuU2VydmljZVR5cGUSFAoMc2VydmljZV9uYW1lGAMgASgJEgwKBGtleXMYBCADKAkSOQoGbGFiZWxzGAUgAygLMikuYWdlbnRtZXNoLnYxLlNlcnZpY2VBbm5vdW5jZS5MYWJlbHNFbnRyeRIXCg9hY3RpdmVfcmVxdWVzdHMYBiABKA0SFwoPbGF0ZW5jeV9ld21hX21zGAcgASgBEjEKDWFubm91bmNlX3RpbWUYCCABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wGi0KC0xhYmVsc0VudHJ5EgsKA2tleRgBIAEoCRINCgV2YWx1ZRgCIAEoCToCOAEisgEKGENvbnRyb2xQbGFuZUluZm9SZXNwb25zZRITCgtvaWRjX2lzc3VlchgBIAEoCRIRCgljbGllbnRfaWQYAiABKAkSEAoIYXVkaWVuY2UYAyABKAkSGAoQcm91dGVyX2FkZHJlc3NlcxgEIAMoCRIXCg9iYW5uZWRfcGVlcl9pZHMYBSADKAkSKQoHcm91dGVycxgGIAMoCzIYLmFnZW50bWVzaC52MS5Sb3V0ZXJJbmZvIsQBCgpSb3V0ZXJJbmZvEg8KB3BlZXJfaWQYASABKAkSEQoJYWRkcmVzc2VzGAIgAygJEjQKBmxhYmVscxgDIAMoCzIkLmFnZW50bWVzaC52MS5Sb3V0ZXJJbmZvLkxhYmVsc0VudHJ5EhMKC2Nvbm5lY3Rpb25zGAQgASgFEhgKEGNvbm5lY3Rpb25fbGltaXQYBSABKAUaLQoLTGFiZWxzRW50cnkSCwoDa2V5GAEgASgJEg0KBXZhbHVlGAIgASgJOgI4ASKDAgoSUm91dGVyTGVhc2VSZXF1ZXN0Eg8KB3BlZXJfaWQYASABKAkSEQoJYWRkcmVzc2VzGAIgAygJEg8KB2Jpc2N1aXQYAyABKAwSFwoPY29ubmVjdGVkX3BlZXJzGAQgAygJEhAKCGRodF9zaXplGAUgASgFEhkKEWNoYWxsZW5nZV91bml4X21zGAYgASgDEhsKE2NoYWxsZW5nZV9zaWduYXR1cmUYByABKAwSJgoDdHRsGAggASgLMhkuZ29vZ2xlLnByb3RvYnVmLkR1cmF0aW9uEhMKC2Nvbm5lY3Rpb25zGAkgASgFEhgKEGNvbm5lY3Rpb25fbGltaXQYCiABKAUiZgoTUm91dGVyTGVhc2VSZXNwb25zZRIPCgdzdWNjZXNzGAEgASgIEg0KBWVycm9yGAIgASgJEi8KC2V4cGlyZV90aW1lGAMgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcCKkAQoKUG9saWN5Um9sZRIMCgRuYW1lGAEgASgJEhcKD2FsbG93ZWRfdGFyZ2V0cxgCIAMoCRIYChBhbGxvd2VkX3NlcnZpY2VzGAMgAygJEhYKDmN1c3RvbV9kYXRhbG9nGAQgAygJEhYKDmFsbG93ZWRfbGFiZWxzGAUgAygJEiUKBGh0dHAYBiADKAsyFy5hZ2VudG1lc2gudjEuSFRUUEdyYW50IjwKCUhUVFBHcmFudBIPCgdzZXJ2aWNlGAEgASgJEg8KB21ldGhvZHMYAiADKAkSDQoFcGF0aHMYAyADKAkioQIKEUVncmVzc0Rlc3RpbmF0aW9uEgwKBG5hbWUYASABKAkSEgoKdGFyZ2V0X3VybBgCIAEoCRISCgpjcmVkZW50aWFsGAMgASgJEhEKCXNlcnZlZF9ieRgEIAMoCRIuCgZicm9rZXIYBSABKAsyHi5hZ2VudG1lc2gudjEuQ3JlZGVudGlhbEJyb2tlchIsCgppbnNwZWN0aW9uGAYgASgLMhguYWdlbnRtZXNoLnYxLkluc3BlY3Rpb24SJgoEbW9kZRgHIAEoDjIYLmFnZW50bWVzaC52MS5FZ3Jlc3NNb2RlEg0KBXBvcnRzGAggAygNEhUKDXByZXNlcnZlX2hvc3QYCSABKAgSFwoPZm9yd2FyZF9jb250ZXh0GAogASgIIjkKCkluc3BlY3Rpb24SKwoKaW5zcGVjdG9ycxgBIAMoCzIXLmFnZW50bWVzaC52MS5JbnNwZWN0b3IibwoJSW5zcGVjdG9yEi8KC21vZGVsX2FybW9yGAEgASgLMhguYWdlbnRtZXNoLnYxLk1vZGVsQXJtb3JIABIpCghleHRfcHJvYxgCIAEoCzIVLmFnZW50bWVzaC52MS5FeHRQcm9jSABCBgoEa2luZCKRAQoKTW9kZWxBcm1vchIQCgh0ZW1wbGF0ZRgBIAEoCRIyCghyZXNwb25zZRgCIAEoDjIgLmFnZW50bWVzaC52MS5SZXNwb25zZUluc3BlY3Rpb24SEQoJZmFpbF9vcGVuGAMgASgIEioKB3RpbWVvdXQYBCABKAsyGS5nb29nbGUucHJvdG9idWYuRHVyYXRpb24iiAIKB0V4dFByb2MSDgoGdGFyZ2V0GAEgASgJEgoKAmNhGAIgASgJEhoKEmNsaWVudF9jZXJ0aWZpY2F0ZRgDIAEoCRI8Cg9wcm9jZXNzaW5nX21vZGUYBCABKAsyIy5hZ2VudG1lc2gudjEuRXh0UHJvY1Byb2Nlc3NpbmdNb2RlEhsKE2FsbG93X21vZGVfb3ZlcnJpZGUYBSABKAgSMgoPbWVzc2FnZV90aW1lb3V0GAYgASgLMhkuZ29vZ2xlLnByb3RvYnVmLkR1cmF0aW9uEhoKEmZhaWx1cmVfbW9kZV9hbGxvdxgHIAEoCBIaChJtYXhfYnVmZmVyZWRfYnl0ZXMYCCABKA0i/wQKFUV4dFByb2NQcm9jZXNzaW5nTW9kZRJLChNyZXF1ZXN0X2hlYWRlcl9tb2RlGAEgASgOMi4uYWdlbnRtZXNoLnYxLkV4dFByb2NQcm9jZXNzaW5nTW9kZS5IZWFkZXJNb2RlEkwKFHJlc3BvbnNlX2hlYWRlcl9tb2RlGAIgASgOMi4uYWdlbnRtZXNoLnYxLkV4dFByb2NQcm9jZXNzaW5nTW9kZS5IZWFkZXJNb2RlEkcKEXJlcXVlc3RfYm9keV9tb2RlGAMgASgOMiwuYWdlbnRtZXNoLnYxLkV4dFByb2NQcm9jZXNzaW5nTW9kZS5Cb2R5TW9kZRJIChJyZXNwb25zZV9ib2R5X21vZGUYBCABKA4yLC5hZ2VudG1lc2gudjEuRXh0UHJvY1Byb2Nlc3NpbmdNb2RlLkJvZHlNb2RlEkwKFHJlcXVlc3RfdHJhaWxlcl9tb2RlGAUgASgOMi4uYWdlbnRtZXNoLnYxLkV4dFByb2NQcm9jZXNzaW5nTW9kZS5IZWFkZXJNb2RlEk0KFXJlc3BvbnNlX3RyYWlsZXJfbW9kZRgGIAEoDjIuLmFnZW50bWVzaC52MS5FeHRQcm9jUHJvY2Vzc2luZ01vZGUuSGVhZGVyTW9kZSI5CgpIZWFkZXJNb2RlEhcKE0hFQURFUl9NT0RFX0RFRkFVTFQQABIICgRTRU5EEAESCAoEU0tJUBACImAKCEJvZHlNb2RlEggKBE5PTkUQABIMCghTVFJFQU1FRBABEgwKCEJVRkZFUkVEEAISFAoQQlVGRkVSRURfUEFSVElBTBADEhgKFEZVTExfRFVQTEVYX1NUUkVBTUVEEAQi4QEKEENyZWRlbnRpYWxCcm9rZXISFwoNc3RhdGljX3NlY3JldBgBIAEoCUgAEjcKD29pZGNfZmVkZXJhdGlvbhgCIAEoCzIcLmFnZW50bWVzaC52MS5PSURDRmVkZXJhdGlvbkgAEjYKD2F3c19hc3N1bWVfcm9sZRgDIAEoCzIbLmFnZW50bWVzaC52MS5BV1NBc3N1bWVSb2xlSAASOwoRcGxhdGZvcm1faWRlbnRpdHkYBCABKAsyHi5hZ2VudG1lc2gudjEuUGxhdGZvcm1JZGVudGl0eUgAQgYKBGtpbmQiXwoOT0lEQ0ZlZGVyYXRpb24SFgoOdG9rZW5fZW5kcG9pbnQYASABKAkSEAoIYXVkaWVuY2UYAiABKAkSEwoLaW1wZXJzb25hdGUYAyABKAkSDgoGc2NvcGVzGAQgAygJIjkKDUFXU0Fzc3VtZVJvbGUSEAoIcm9sZV9hcm4YASABKAkSFgoOc2Vzc2lvbl9wb2xpY3kYAiABKAkiIgoQUGxhdGZvcm1JZGVudGl0eRIOCgZzY29wZXMYASADKAkiLgoNUG9saWN5QmluZGluZxIMCgRyb2xlGAEgASgJEg8KB21lbWJlcnMYAiADKAkilwEKDFBvbGljeUNvbmZpZxInCgVyb2xlcxgBIAMoCzIYLmFnZW50bWVzaC52MS5Qb2xpY3lSb2xlEi0KCGJpbmRpbmdzGAIgAygLMhsuYWdlbnRtZXNoLnYxLlBvbGljeUJpbmRpbmcSLwoGZWdyZXNzGAMgAygLMh8uYWdlbnRtZXNoLnYxLkVncmVzc0Rlc3RpbmF0aW9uIhgKFlBvbGljeUNvbmZpZ0dldFJlcXVlc3QiMAoXUG9saWN5Q29uZmlnR2V0UmVzcG9uc2USFQoNZGF0YWxvZ19ydWxlcxgBIAMoCSI8ChpQb2xpY3lDb25maWdVcGRhdGVSZXNwb25zZRIPCgdzdWNjZXNzGAEgASgIEg0KBWVycm9yGAIgASgJIhoKGEVncmVzc0Fzc2lnbm1lbnRzUmVxdWVzdCJMChlFZ3Jlc3NBc3NpZ25tZW50c1Jlc3BvbnNlEi8KBmVncmVzcxgBIAMoCzIfLmFnZW50bWVzaC52MS5FZ3Jlc3NEZXN0aW5hdGlvbiJmCgxLZXlzUmVzcG9uc2USEwoLcHVibGljX2tleXMYASADKAwSLQoJc2lnbl90aW1lGAIgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBISCgpzaWduYXR1cmVzGAMgAygMImsKE1Rva2VuUmVmcmVzaFJlcXVlc3QSGwoTY2hhbGxlbmdlX3NpZ25hdHVyZRgBIAEoDBIZChFjaGFsbGVuZ2VfdW5peF9tcxgCIAEoAxIPCgdwZWVyX2lkGAMgASgJEgsKA2p3dBgEIAEoCSJ1ChRUb2tlblJlZnJlc2hSZXNwb25zZRIVCg1iaXNjdWl0X3Rva2VuGAEgASgMEi8KC2V4cGlyZV90aW1lGAIgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIVCg1lcnJvcl9tZXNzYWdlGAMgASgJIkAKEU5vZGVDYXRhbG9nUmVwb3J0EisKCHNlcnZpY2VzGAEgAygLMhkuYWdlbnRtZXNoLnYxLlNlcnZpY2VJbmZvIiUKElRva2VuUmV2b2tlUmVxdWVzdBIPCgdwZWVyX2lkGAEgASgJIjUKE1Rva2VuUmV2b2tlUmVzcG9uc2USDwoHc3VjY2VzcxgBIAEoCBINCgVlcnJvchgCIAEoCSKWAQobQm9vdHN0cmFwVG9rZW5DcmVhdGVSZXF1ZXN0EgwKBHJvbGUYASABKAkSEAoIb3duZXJfaWQYAiABKAkSEQoJdHRsX2hvdXJzGAMgASgFEhIKCm1heF91c2FnZXMYBCABKAUSEwoLZGVzY3JpcHRpb24YBSABKAkSGwoTYXV0b25vbW91c19yZWNvdmVyeRgGIAEoCCKKAQocQm9vdHN0cmFwVG9rZW5DcmVhdGVSZXNwb25zZRIKCgJpZBgBIAEoCRINCgV0b2tlbhgCIAEoCRIMCgRyb2xlGAMgASgJEhAKCG93bmVyX2lkGAQgASgJEi8KC2V4cGlyZV90aW1lGAUgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcCKrAgoOQm9vdHN0cmFwVG9rZW4SCgoCaWQYASABKAkSDAoEcm9sZRgCIAEoCRIQCghvd25lcl9pZBgDIAEoCRISCgptYXhfdXNhZ2VzGAQgASgFEhQKDHVzYWdlc19jb3VudBgFIAEoBRITCgtkZXNjcmlwdGlvbhgGIAEoCRIvCgtjcmVhdGVfdGltZRgHIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASLwoLZXhwaXJlX3RpbWUYCCABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEi8KC3Jldm9rZV90aW1lGAkgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIbChNhdXRvbm9tb3VzX3JlY292ZXJ5GAogASgIIkoKGkJvb3RzdHJhcFRva2VuTGlzdFJlc3BvbnNlEiwKBnRva2VucxgBIAMoCzIcLmFnZW50bWVzaC52MS5Cb290c3RyYXBUb2tlbiLWAgoRRW5yb2xsbWVudFJlcXVlc3QSCgoCaWQYASABKAkSDwoHcGVlcl9pZBgCIAEoCRIQCgh0b2tlbl9pZBgDIAEoCRIuCgZzdGF0dXMYBCABKA4yHi5hZ2VudG1lc2gudjEuRW5yb2xsbWVudFN0YXR1cxI7CgZsYWJlbHMYBSADKAsyKy5hZ2VudG1lc2gudjEuRW5yb2xsbWVudFJlcXVlc3QuTGFiZWxzRW50cnkSLwoLY3JlYXRlX3RpbWUYBiABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEjAKDHJlc29sdmVfdGltZRgHIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASEwoLcmVzb2x2ZWRfYnkYCCABKAkaLQoLTGFiZWxzRW50cnkSCwoDa2V5GAEgASgJEg0KBXZhbHVlGAIgASgJOgI4ASJSCh1FbnJvbGxtZW50UmVxdWVzdExpc3RSZXNwb25zZRIxCghyZXF1ZXN0cxgBIAMoCzIfLmFnZW50bWVzaC52MS5FbnJvbGxtZW50UmVxdWVzdCJwCgRVc2VyEgoKAmlkGAEgASgJEg4KBmlzc3VlchgCIAEoCRINCgVlbWFpbBgDIAEoCRIMCgRyb2xlGAQgASgJEi8KC2NyZWF0ZV90aW1lGAUgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcCLjAgoMRW5yb2xsZWROb2RlEg8KB3BlZXJfaWQYASABKAkSDAoEcm9sZRgCIAEoCRIXCg9lbnJvbGxtZW50X3R5cGUYAyABKAkSEwoLY2xhaW1zX2pzb24YBCABKAkSEAoIb3duZXJfaWQYBSABKAkSNgoGbGFiZWxzGAYgAygLMiYuYWdlbnRtZXNoLnYxLkVucm9sbGVkTm9kZS5MYWJlbHNFbnRyeRIvCgtlbnJvbGxfdGltZRgHIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASLwoLZXhwaXJlX3RpbWUYCCABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEg4KBmJhbm5lZBgJIAEoCBIbChNhdXRvbm9tb3VzX3JlY292ZXJ5GAogASgIGi0KC0xhYmVsc0VudHJ5EgsKA2tleRgBIAEoCRINCgV2YWx1ZRgCIAEoCToCOAEixAEKC1JvdXRlckxlYXNlEg8KB3BlZXJfaWQYASABKAkSEQoJYWRkcmVzc2VzGAIgAygJEjUKEWxhc3RfcmVuZXdhbF90aW1lGAMgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIvCgtleHBpcmVfdGltZRgEIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASFwoPY29ubmVjdGVkX3BlZXJzGAUgAygJEhAKCGRodF9zaXplGAYgASgFImwKDE5vZGVTZXJ2aWNlcxIrCghzZXJ2aWNlcxgBIAMoCzIZLmFnZW50bWVzaC52MS5TZXJ2aWNlSW5mbxIvCgtyZXBvcnRfdGltZRgCIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXAi2wMKE0FkbWluU3RhdHVzUmVzcG9uc2USIQoFdXNlcnMYASADKAsyEi5hZ2VudG1lc2gudjEuVXNlchIxCg5hY3RpdmVfcm91dGVycxgCIAMoCzIZLmFnZW50bWVzaC52MS5Sb3V0ZXJMZWFzZRIyCg5lbnJvbGxlZF9ub2RlcxgDIAMoCzIaLmFnZW50bWVzaC52MS5FbnJvbGxlZE5vZGUSPAoTZW5yb2xsbWVudF9yZXF1ZXN0cxgEIAMoCzIfLmFnZW50bWVzaC52MS5FbnJvbGxtZW50UmVxdWVzdBI2ChBib290c3RyYXBfdG9rZW5zGAUgAygLMhwuYWdlbnRtZXNoLnYxLkJvb3RzdHJhcFRva2VuEioKBnBvbGljeRgGIAEoCzIaLmFnZW50bWVzaC52MS5Qb2xpY3lDb25maWcSSAoMbm9kZV9jYXRhbG9nGAcgAygLMjIuYWdlbnRtZXNoLnYxLkFkbWluU3RhdHVzUmVzcG9uc2UuTm9kZUNhdGFsb2dFbnRyeRpOChBOb2RlQ2F0YWxvZ0VudHJ5EgsKA2tleRgBIAEoCRIpCgV2YWx1ZRgCIAEoCzIaLmFnZW50bWVzaC52MS5Ob2RlU2VydmljZXM6AjgBIoECChJVc2VyU3RhdHVzUmVzcG9uc2USIAoEdXNlchgBIAEoCzISLmFnZW50bWVzaC52MS5Vc2VyEjIKDmVucm9sbGVkX25vZGVzGAIgAygLMhouYWdlbnRtZXNoLnYxLkVucm9sbGVkTm9kZRI2ChBib290c3RyYXBfdG9rZW5zGAMgAygLMhwuYWdlbnRtZXNoLnYxLkJvb3RzdHJhcFRva2VuEjEKDmFjdGl2ZV9yb3V0ZXJzGAQgAygLMhkuYWdlbnRtZXNoLnYxLlJvdXRlckxlYXNlEioKBnBvbGljeRgFIAEoCzIaLmFnZW50bWVzaC52MS5Qb2xpY3lDb25maWci5AEKGElkZW50aXR5RXZpZGVuY2VSZXNwb25zZRIPCgdwZWVyX2lkGAEgASgJEg8KB2Jpc2N1aXQYAiABKAwSNwoTYmlzY3VpdF9leHBpcmVfdGltZRgDIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASGQoRY29udHJvbF9wbGFuZV91cmwYBCABKAkSIgoadHJ1c3RlZF9jb250cm9sX3BsYW5lX2tleXMYBSADKAwSLgoKY2hlY2tfdGltZRgGIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXAixgIKFFBlZXJFdmlkZW5jZVJlc3BvbnNlEg8KB3BlZXJfaWQYASABKAkSDwoHYmlzY3VpdBgCIAEoDBIVCg12ZXJpZnlpbmdfa2V5GAMgASgMEg0KBXJvbGVzGAQgAygJEj4KBmxhYmVscxgFIAMoCzIuLmFnZW50bWVzaC52MS5QZWVyRXZpZGVuY2VSZXNwb25zZS5MYWJlbHNFbnRyeRIvCgtleHBpcmVfdGltZRgGIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASFgoOcmV2b2NhdGlvbl9pZHMYByADKAkSLgoKY2hlY2tfdGltZRgIIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXAaLQoLTGFiZWxzRW50cnkSCwoDa2V5GAEgASgJEg0KBXZhbHVlGAIgASgJOgI4ASKMAgoQTWVtYmVyQ3JlZGVudGlhbBIZChFjb250cm9sX3BsYW5lX3VybBgBIAEoCRIPCgdiaXNjdWl0GAIgASgMEi8KC2V4cGlyZV90aW1lGAMgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBI1Cgx0cnVzdGVkX2tleXMYBCADKAsyHy5hZ2VudG1lc2gudjEuVHJ1c3RlZFNpZ25pbmdLZXkSGQoRaXNzdWVkX3VuZGVyX2tleXMYBSADKAwSGAoQcm91dGVyX2FkZHJlc3NlcxgGIAMoCRIvCgxvaWRjX3Nlc3Npb24YByABKAsyGS5hZ2VudG1lc2gudjEuT0lEQ1Nlc3Npb24iWQoRVHJ1c3RlZFNpZ25pbmdLZXkSEgoKcHVibGljX2tleRgBIAEoDBIwCgxyZWNlaXZlX3RpbWUYAiABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wIlkKC09JRENTZXNzaW9uEg4KBmlzc3VlchgBIAEoCRIRCgljbGllbnRfaWQYAiABKAkSEAoIYXVkaWVuY2UYAyABKAkSFQoNcmVmcmVzaF90b2tlbhgEIAEoCSKTAQoVVGFza0F1dGhvcml6YXRpb25SdWxlEgwKBG5hbWUYASABKAkSFAoMZGlzcGxheV9uYW1lGAIgASgJEiUKBXJ1bGVzGAMgAygLMhYuYWdlbnRtZXNoLnYxLlRhc2tSdWxlEi8KC2V4cGlyZV90aW1lGAQgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcCKEAQoIVGFza1J1bGUSEwoLZGVzY3JpcHRpb24YASABKAkSGAoQYWxsb3dlZF9zZXJ2aWNlcxgCIAMoCRIuCglvcGVyYXRpb24YAyABKAsyGy5hZ2VudG1lc2gudjEuVGFza09wZXJhdGlvbhIZChFhbGxvd2VkX3Jlc291cmNlcxgEIAMoCSJzCg1UYXNrT3BlcmF0aW9uEhUKDWFsbG93ZWRfdG9vbHMYASADKAkSFwoPYWxsb3dlZF9tZXRob2RzGAIgAygJEhUKDWFsbG93ZWRfcGF0aHMYAyADKAkSGwoTYWxsb3dlZF9wZXJtaXNzaW9ucxgEIAMoCSKrAQoUVG9rZW5FeGNoYW5nZVJlcXVlc3QSFQoNc3ViamVjdF90b2tlbhgBIAEoCRI2Cgl0YXNrX3J1bGUYAiABKAsyIy5hZ2VudG1lc2gudjEuVGFza0F1dGhvcml6YXRpb25SdWxlEgwKBHNlYWwYAyABKAgSGQoRY2hhbGxlbmdlX3VuaXhfbXMYBCABKAMSGwoTY2hhbGxlbmdlX3NpZ25hdHVyZRgFIAEoDCJ/ChVUb2tlbkV4Y2hhbmdlUmVzcG9uc2USFQoNYmlzY3VpdF90b2tlbhgBIAEoDBIvCgtleHBpcmVfdGltZRgCIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASDQoFcm9sZXMYAyADKAkSDwoHc3ViamVjdBgEIAEoCSKBAQoPU1RTVG9rZW5SZXF1ZXN0Eg8KB2Jpc2N1aXQYASABKAwSEwoLZGVzdGluYXRpb24YAiABKAkSEAoIYXVkaWVuY2UYAyABKAkSGQoRY2hhbGxlbmdlX3VuaXhfbXMYBCABKAMSGwoTY2hhbGxlbmdlX3NpZ25hdHVyZRgFIAEoDCKDAQoQU1RTVG9rZW5SZXNwb25zZRILCgNqd3QYASABKAkSLwoLZXhwaXJlX3RpbWUYAiABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEg8KB3N1YmplY3QYAyABKAkSDQoFcm9sZXMYBCADKAkSEQoJdGFza19uYW1lGAUgASgJIkYKE1Jldm9jYXRpb25zUmVzcG9uc2USFgoOcmV2b2NhdGlvbl9pZHMYASADKAkSFwoPYmFubmVkX3BlZXJfaWRzGAIgAygJKpQBChBFbnJvbGxtZW50U3RhdHVzEiEKHUVOUk9MTE1FTlRfU1RBVFVTX1VOU1BFQ0lGSUVEEAASHQoZRU5ST0xMTUVOVF9TVEFUVVNfUEVORElORxABEh4KGkVOUk9MTE1FTlRfU1RBVFVTX0FQUFJPVkVEEAISHgoaRU5ST0xMTUVOVF9TVEFUVVNfUkVKRUNURUQQAyqMAQoLU2VydmljZVR5cGUSHAoYU0VSVklDRV9UWVBFX1VOU1BFQ0lGSUVEEAASFAoQU0VSVklDRV9UWVBFX01DUBABEhoKFlNFUlZJQ0VfVFlQRV9JTkZFUkVOQ0UQAhIUChBTRVJWSUNFX1RZUEVfQTJBEAMSFwoTU0VSVklDRV9UWVBFX0VHUkVTUxAEKjcKCkVncmVzc01vZGUSFAoQRUdSRVNTX01PREVfSFRUUBAAEhMKD0VHUkVTU19NT0RFX1RDUBABKlwKElJlc3BvbnNlSW5zcGVjdGlvbhIgChxSRVNQT05TRV9JTlNQRUNUSU9OX0JVRkZFUkVEEAASJAogUkVTUE9OU0VfSU5TUEVDVElPTl9SRVFVRVNUX09OTFkQAUIhWh9naXRodWIuY29tL2dvb2dsZS9hZ2VudG1lc2gvYXBpYgZwcm90bzM", [file_google_protobuf_duration, file_google_protobuf_timestamp]); + fileDesc("Cg9hZ2VudG1lc2gucHJvdG8SDGFnZW50bWVzaC52MSI0CglBdXRoRnJhbWUSDwoHYmlzY3VpdBgBIAEoDBIWCg50YXJnZXRfc2VydmljZRgCIAEoCSI/CgxBdXRoUmVzcG9uc2USDwoHc3VjY2VzcxgBIAEoCBINCgVlcnJvchgCIAEoCRIPCgdiaXNjdWl0GAMgASgMIrEBCgxSb3V0ZXJHb0F3YXkSMQoGcmVhc29uGAEgASgOMiEuYWdlbnRtZXNoLnYxLlJvdXRlckdvQXdheS5SZWFzb24SLgoLcmV0cnlfYWZ0ZXIYAiABKAsyGS5nb29nbGUucHJvdG9idWYuRHVyYXRpb24iPgoGUmVhc29uEhYKElJFQVNPTl9VTlNQRUNJRklFRBAAEgwKCERSQUlOSU5HEAESDgoKT1ZFUkxPQURFRBACItwBCglNZXNoRXZlbnQSKgoEdHlwZRgBIAEoDjIcLmFnZW50bWVzaC52MS5NZXNoRXZlbnQuVHlwZRIPCgdwZWVyX2lkGAIgASgJEi4KCmV2ZW50X3RpbWUYAyABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEhYKDm5ld19wdWJsaWNfa2V5GAQgASgMEhEKCXNpZ25hdHVyZRgFIAEoDCI3CgRUeXBlEgoKBkJBTk5FRBAAEhAKDEtFWV9ST1RBVElPThABEhEKDVBPTElDWV9VUERBVEUQAiL5AQoNRW5yb2xsUmVxdWVzdBILCgNqd3QYASABKAkSDwoHcGVlcl9pZBgCIAEoCRISCgpwdWJsaWNfa2V5GAMgASgMEhYKDnJlcXVlc3RlZF9yb2xlGAQgASgJEjcKBmxhYmVscxgFIAMoCzInLmFnZW50bWVzaC52MS5FbnJvbGxSZXF1ZXN0LkxhYmVsc0VudHJ5EhkKEWNoYWxsZW5nZV91bml4X21zGAYgASgDEhsKE2NoYWxsZW5nZV9zaWduYXR1cmUYByABKAwaLQoLTGFiZWxzRW50cnkSCwoDa2V5GAEgASgJEg0KBXZhbHVlGAIgASgJOgI4ASKrAQoORW5yb2xsUmVzcG9uc2USFQoNYmlzY3VpdF90b2tlbhgBIAEoDBIVCg1lcnJvcl9tZXNzYWdlGAIgASgJEiAKGGNvbnRyb2xfcGxhbmVfcHVibGljX2tleRgDIAEoDBIYChByb3V0ZXJfYWRkcmVzc2VzGAQgAygJEi8KC2V4cGlyZV90aW1lGAUgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcCKXAgoWQm9vdHN0cmFwRW5yb2xsUmVxdWVzdBIXCg9ib290c3RyYXBfdG9rZW4YASABKAkSDwoHcGVlcl9pZBgCIAEoCRISCgpwdWJsaWNfa2V5GAMgASgMEhYKDnJlcXVlc3RlZF9yb2xlGAQgASgJEkAKBmxhYmVscxgFIAMoCzIwLmFnZW50bWVzaC52MS5Cb290c3RyYXBFbnJvbGxSZXF1ZXN0LkxhYmVsc0VudHJ5EhkKEWNoYWxsZW5nZV91bml4X21zGAYgASgDEhsKE2NoYWxsZW5nZV9zaWduYXR1cmUYByABKAwaLQoLTGFiZWxzRW50cnkSCwoDa2V5GAEgASgJEg0KBXZhbHVlGAIgASgJOgI4ASKDAgoXQm9vdHN0cmFwRW5yb2xsUmVzcG9uc2USLgoGc3RhdHVzGAEgASgOMh4uYWdlbnRtZXNoLnYxLkVucm9sbG1lbnRTdGF0dXMSFQoNYmlzY3VpdF90b2tlbhgCIAEoDBIdChVwb2xsX2ludGVydmFsX3NlY29uZHMYAyABKAUSFQoNZXJyb3JfbWVzc2FnZRgEIAEoCRIgChhjb250cm9sX3BsYW5lX3B1YmxpY19rZXkYBSABKAwSGAoQcm91dGVyX2FkZHJlc3NlcxgGIAMoCRIvCgtleHBpcmVfdGltZRgHIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXAiWQoLU2VydmljZUluZm8SJwoEdHlwZRgBIAEoDjIZLmFnZW50bWVzaC52MS5TZXJ2aWNlVHlwZRIMCgRuYW1lGAIgASgJEhMKC2Rlc2NyaXB0aW9uGAMgASgJIoEBCg5Db21tYW5kQmFja2VuZBIPCgdjb21tYW5kGAEgAygJEjIKA2VudhgCIAMoCzIlLmFnZW50bWVzaC52MS5Db21tYW5kQmFja2VuZC5FbnZFbnRyeRoqCghFbnZFbnRyeRILCgNrZXkYASABKAkSDQoFdmFsdWUYAiABKAk6AjgBIpYBChZSZWdpc3RlclNlcnZpY2VSZXF1ZXN0EioKB3NlcnZpY2UYASABKAsyGS5hZ2VudG1lc2gudjEuU2VydmljZUluZm8SFAoKdGFyZ2V0X3VybBgCIAEoCUgAEi8KB2NvbW1hbmQYAyABKAsyHC5hZ2VudG1lc2gudjEuQ29tbWFuZEJhY2tlbmRIAEIJCgdiYWNrZW5kImkKEkRpc2NvdmVyZWRQcm92aWRlchIPCgdwZWVyX2lkGAEgASgJEhcKD2xvY2FsX3Byb3h5X3VybBgCIAEoCRIQCghzcnZfbmFtZRgDIAEoCRIXCg9zcnZfZGVzY3JpcHRpb24YBCABKAkivgIKD1NlcnZpY2VBbm5vdW5jZRIPCgdwZWVyX2lkGAEgASgJEicKBHR5cGUYAiABKA4yGS5hZ2VudG1lc2gudjEuU2VydmljZVR5cGUSFAoMc2VydmljZV9uYW1lGAMgASgJEgwKBGtleXMYBCADKAkSOQoGbGFiZWxzGAUgAygLMikuYWdlbnRtZXNoLnYxLlNlcnZpY2VBbm5vdW5jZS5MYWJlbHNFbnRyeRIXCg9hY3RpdmVfcmVxdWVzdHMYBiABKA0SFwoPbGF0ZW5jeV9ld21hX21zGAcgASgBEjEKDWFubm91bmNlX3RpbWUYCCABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wGi0KC0xhYmVsc0VudHJ5EgsKA2tleRgBIAEoCRINCgV2YWx1ZRgCIAEoCToCOAEi6AEKGENvbnRyb2xQbGFuZUluZm9SZXNwb25zZRITCgtvaWRjX2lzc3VlchgBIAEoCRIRCgljbGllbnRfaWQYAiABKAkSEAoIYXVkaWVuY2UYAyABKAkSGAoQcm91dGVyX2FkZHJlc3NlcxgEIAMoCRIXCg9iYW5uZWRfcGVlcl9pZHMYBSADKAkSKQoHcm91dGVycxgGIAMoCzIYLmFnZW50bWVzaC52MS5Sb3V0ZXJJbmZvEjQKCWRpcmVjdG9yeRgHIAEoCzIhLmFnZW50bWVzaC52MS5EaXJlY3RvcnlQYXJhbWV0ZXJzIpABChNEaXJlY3RvcnlQYXJhbWV0ZXJzEhMKC3JlcGxpY2F0aW9uGAEgASgFEjUKEnJlcHJvdmlkZV9pbnRlcnZhbBgCIAEoCzIZLmdvb2dsZS5wcm90b2J1Zi5EdXJhdGlvbhItCgpyZWNvcmRfdHRsGAMgASgLMhkuZ29vZ2xlLnByb3RvYnVmLkR1cmF0aW9uIsQBCgpSb3V0ZXJJbmZvEg8KB3BlZXJfaWQYASABKAkSEQoJYWRkcmVzc2VzGAIgAygJEjQKBmxhYmVscxgDIAMoCzIkLmFnZW50bWVzaC52MS5Sb3V0ZXJJbmZvLkxhYmVsc0VudHJ5EhMKC2Nvbm5lY3Rpb25zGAQgASgFEhgKEGNvbm5lY3Rpb25fbGltaXQYBSABKAUaLQoLTGFiZWxzRW50cnkSCwoDa2V5GAEgASgJEg0KBXZhbHVlGAIgASgJOgI4ASKDAgoSUm91dGVyTGVhc2VSZXF1ZXN0Eg8KB3BlZXJfaWQYASABKAkSEQoJYWRkcmVzc2VzGAIgAygJEg8KB2Jpc2N1aXQYAyABKAwSFwoPY29ubmVjdGVkX3BlZXJzGAQgAygJEhAKCGRodF9zaXplGAUgASgFEhkKEWNoYWxsZW5nZV91bml4X21zGAYgASgDEhsKE2NoYWxsZW5nZV9zaWduYXR1cmUYByABKAwSJgoDdHRsGAggASgLMhkuZ29vZ2xlLnByb3RvYnVmLkR1cmF0aW9uEhMKC2Nvbm5lY3Rpb25zGAkgASgFEhgKEGNvbm5lY3Rpb25fbGltaXQYCiABKAUiZgoTUm91dGVyTGVhc2VSZXNwb25zZRIPCgdzdWNjZXNzGAEgASgIEg0KBWVycm9yGAIgASgJEi8KC2V4cGlyZV90aW1lGAMgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcCKkAQoKUG9saWN5Um9sZRIMCgRuYW1lGAEgASgJEhcKD2FsbG93ZWRfdGFyZ2V0cxgCIAMoCRIYChBhbGxvd2VkX3NlcnZpY2VzGAMgAygJEhYKDmN1c3RvbV9kYXRhbG9nGAQgAygJEhYKDmFsbG93ZWRfbGFiZWxzGAUgAygJEiUKBGh0dHAYBiADKAsyFy5hZ2VudG1lc2gudjEuSFRUUEdyYW50IjwKCUhUVFBHcmFudBIPCgdzZXJ2aWNlGAEgASgJEg8KB21ldGhvZHMYAiADKAkSDQoFcGF0aHMYAyADKAkioQIKEUVncmVzc0Rlc3RpbmF0aW9uEgwKBG5hbWUYASABKAkSEgoKdGFyZ2V0X3VybBgCIAEoCRISCgpjcmVkZW50aWFsGAMgASgJEhEKCXNlcnZlZF9ieRgEIAMoCRIuCgZicm9rZXIYBSABKAsyHi5hZ2VudG1lc2gudjEuQ3JlZGVudGlhbEJyb2tlchIsCgppbnNwZWN0aW9uGAYgASgLMhguYWdlbnRtZXNoLnYxLkluc3BlY3Rpb24SJgoEbW9kZRgHIAEoDjIYLmFnZW50bWVzaC52MS5FZ3Jlc3NNb2RlEg0KBXBvcnRzGAggAygNEhUKDXByZXNlcnZlX2hvc3QYCSABKAgSFwoPZm9yd2FyZF9jb250ZXh0GAogASgIIjkKCkluc3BlY3Rpb24SKwoKaW5zcGVjdG9ycxgBIAMoCzIXLmFnZW50bWVzaC52MS5JbnNwZWN0b3IibwoJSW5zcGVjdG9yEi8KC21vZGVsX2FybW9yGAEgASgLMhguYWdlbnRtZXNoLnYxLk1vZGVsQXJtb3JIABIpCghleHRfcHJvYxgCIAEoCzIVLmFnZW50bWVzaC52MS5FeHRQcm9jSABCBgoEa2luZCKRAQoKTW9kZWxBcm1vchIQCgh0ZW1wbGF0ZRgBIAEoCRIyCghyZXNwb25zZRgCIAEoDjIgLmFnZW50bWVzaC52MS5SZXNwb25zZUluc3BlY3Rpb24SEQoJZmFpbF9vcGVuGAMgASgIEioKB3RpbWVvdXQYBCABKAsyGS5nb29nbGUucHJvdG9idWYuRHVyYXRpb24iiAIKB0V4dFByb2MSDgoGdGFyZ2V0GAEgASgJEgoKAmNhGAIgASgJEhoKEmNsaWVudF9jZXJ0aWZpY2F0ZRgDIAEoCRI8Cg9wcm9jZXNzaW5nX21vZGUYBCABKAsyIy5hZ2VudG1lc2gudjEuRXh0UHJvY1Byb2Nlc3NpbmdNb2RlEhsKE2FsbG93X21vZGVfb3ZlcnJpZGUYBSABKAgSMgoPbWVzc2FnZV90aW1lb3V0GAYgASgLMhkuZ29vZ2xlLnByb3RvYnVmLkR1cmF0aW9uEhoKEmZhaWx1cmVfbW9kZV9hbGxvdxgHIAEoCBIaChJtYXhfYnVmZmVyZWRfYnl0ZXMYCCABKA0i/wQKFUV4dFByb2NQcm9jZXNzaW5nTW9kZRJLChNyZXF1ZXN0X2hlYWRlcl9tb2RlGAEgASgOMi4uYWdlbnRtZXNoLnYxLkV4dFByb2NQcm9jZXNzaW5nTW9kZS5IZWFkZXJNb2RlEkwKFHJlc3BvbnNlX2hlYWRlcl9tb2RlGAIgASgOMi4uYWdlbnRtZXNoLnYxLkV4dFByb2NQcm9jZXNzaW5nTW9kZS5IZWFkZXJNb2RlEkcKEXJlcXVlc3RfYm9keV9tb2RlGAMgASgOMiwuYWdlbnRtZXNoLnYxLkV4dFByb2NQcm9jZXNzaW5nTW9kZS5Cb2R5TW9kZRJIChJyZXNwb25zZV9ib2R5X21vZGUYBCABKA4yLC5hZ2VudG1lc2gudjEuRXh0UHJvY1Byb2Nlc3NpbmdNb2RlLkJvZHlNb2RlEkwKFHJlcXVlc3RfdHJhaWxlcl9tb2RlGAUgASgOMi4uYWdlbnRtZXNoLnYxLkV4dFByb2NQcm9jZXNzaW5nTW9kZS5IZWFkZXJNb2RlEk0KFXJlc3BvbnNlX3RyYWlsZXJfbW9kZRgGIAEoDjIuLmFnZW50bWVzaC52MS5FeHRQcm9jUHJvY2Vzc2luZ01vZGUuSGVhZGVyTW9kZSI5CgpIZWFkZXJNb2RlEhcKE0hFQURFUl9NT0RFX0RFRkFVTFQQABIICgRTRU5EEAESCAoEU0tJUBACImAKCEJvZHlNb2RlEggKBE5PTkUQABIMCghTVFJFQU1FRBABEgwKCEJVRkZFUkVEEAISFAoQQlVGRkVSRURfUEFSVElBTBADEhgKFEZVTExfRFVQTEVYX1NUUkVBTUVEEAQi4QEKEENyZWRlbnRpYWxCcm9rZXISFwoNc3RhdGljX3NlY3JldBgBIAEoCUgAEjcKD29pZGNfZmVkZXJhdGlvbhgCIAEoCzIcLmFnZW50bWVzaC52MS5PSURDRmVkZXJhdGlvbkgAEjYKD2F3c19hc3N1bWVfcm9sZRgDIAEoCzIbLmFnZW50bWVzaC52MS5BV1NBc3N1bWVSb2xlSAASOwoRcGxhdGZvcm1faWRlbnRpdHkYBCABKAsyHi5hZ2VudG1lc2gudjEuUGxhdGZvcm1JZGVudGl0eUgAQgYKBGtpbmQiXwoOT0lEQ0ZlZGVyYXRpb24SFgoOdG9rZW5fZW5kcG9pbnQYASABKAkSEAoIYXVkaWVuY2UYAiABKAkSEwoLaW1wZXJzb25hdGUYAyABKAkSDgoGc2NvcGVzGAQgAygJIjkKDUFXU0Fzc3VtZVJvbGUSEAoIcm9sZV9hcm4YASABKAkSFgoOc2Vzc2lvbl9wb2xpY3kYAiABKAkiIgoQUGxhdGZvcm1JZGVudGl0eRIOCgZzY29wZXMYASADKAkiLgoNUG9saWN5QmluZGluZxIMCgRyb2xlGAEgASgJEg8KB21lbWJlcnMYAiADKAkilwEKDFBvbGljeUNvbmZpZxInCgVyb2xlcxgBIAMoCzIYLmFnZW50bWVzaC52MS5Qb2xpY3lSb2xlEi0KCGJpbmRpbmdzGAIgAygLMhsuYWdlbnRtZXNoLnYxLlBvbGljeUJpbmRpbmcSLwoGZWdyZXNzGAMgAygLMh8uYWdlbnRtZXNoLnYxLkVncmVzc0Rlc3RpbmF0aW9uIhgKFlBvbGljeUNvbmZpZ0dldFJlcXVlc3QiMAoXUG9saWN5Q29uZmlnR2V0UmVzcG9uc2USFQoNZGF0YWxvZ19ydWxlcxgBIAMoCSI8ChpQb2xpY3lDb25maWdVcGRhdGVSZXNwb25zZRIPCgdzdWNjZXNzGAEgASgIEg0KBWVycm9yGAIgASgJIhoKGEVncmVzc0Fzc2lnbm1lbnRzUmVxdWVzdCJMChlFZ3Jlc3NBc3NpZ25tZW50c1Jlc3BvbnNlEi8KBmVncmVzcxgBIAMoCzIfLmFnZW50bWVzaC52MS5FZ3Jlc3NEZXN0aW5hdGlvbiJmCgxLZXlzUmVzcG9uc2USEwoLcHVibGljX2tleXMYASADKAwSLQoJc2lnbl90aW1lGAIgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBISCgpzaWduYXR1cmVzGAMgAygMImsKE1Rva2VuUmVmcmVzaFJlcXVlc3QSGwoTY2hhbGxlbmdlX3NpZ25hdHVyZRgBIAEoDBIZChFjaGFsbGVuZ2VfdW5peF9tcxgCIAEoAxIPCgdwZWVyX2lkGAMgASgJEgsKA2p3dBgEIAEoCSJ1ChRUb2tlblJlZnJlc2hSZXNwb25zZRIVCg1iaXNjdWl0X3Rva2VuGAEgASgMEi8KC2V4cGlyZV90aW1lGAIgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIVCg1lcnJvcl9tZXNzYWdlGAMgASgJIkAKEU5vZGVDYXRhbG9nUmVwb3J0EisKCHNlcnZpY2VzGAEgAygLMhkuYWdlbnRtZXNoLnYxLlNlcnZpY2VJbmZvIiUKElRva2VuUmV2b2tlUmVxdWVzdBIPCgdwZWVyX2lkGAEgASgJIjUKE1Rva2VuUmV2b2tlUmVzcG9uc2USDwoHc3VjY2VzcxgBIAEoCBINCgVlcnJvchgCIAEoCSKWAQobQm9vdHN0cmFwVG9rZW5DcmVhdGVSZXF1ZXN0EgwKBHJvbGUYASABKAkSEAoIb3duZXJfaWQYAiABKAkSEQoJdHRsX2hvdXJzGAMgASgFEhIKCm1heF91c2FnZXMYBCABKAUSEwoLZGVzY3JpcHRpb24YBSABKAkSGwoTYXV0b25vbW91c19yZWNvdmVyeRgGIAEoCCKKAQocQm9vdHN0cmFwVG9rZW5DcmVhdGVSZXNwb25zZRIKCgJpZBgBIAEoCRINCgV0b2tlbhgCIAEoCRIMCgRyb2xlGAMgASgJEhAKCG93bmVyX2lkGAQgASgJEi8KC2V4cGlyZV90aW1lGAUgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcCKrAgoOQm9vdHN0cmFwVG9rZW4SCgoCaWQYASABKAkSDAoEcm9sZRgCIAEoCRIQCghvd25lcl9pZBgDIAEoCRISCgptYXhfdXNhZ2VzGAQgASgFEhQKDHVzYWdlc19jb3VudBgFIAEoBRITCgtkZXNjcmlwdGlvbhgGIAEoCRIvCgtjcmVhdGVfdGltZRgHIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASLwoLZXhwaXJlX3RpbWUYCCABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEi8KC3Jldm9rZV90aW1lGAkgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIbChNhdXRvbm9tb3VzX3JlY292ZXJ5GAogASgIIkoKGkJvb3RzdHJhcFRva2VuTGlzdFJlc3BvbnNlEiwKBnRva2VucxgBIAMoCzIcLmFnZW50bWVzaC52MS5Cb290c3RyYXBUb2tlbiLWAgoRRW5yb2xsbWVudFJlcXVlc3QSCgoCaWQYASABKAkSDwoHcGVlcl9pZBgCIAEoCRIQCgh0b2tlbl9pZBgDIAEoCRIuCgZzdGF0dXMYBCABKA4yHi5hZ2VudG1lc2gudjEuRW5yb2xsbWVudFN0YXR1cxI7CgZsYWJlbHMYBSADKAsyKy5hZ2VudG1lc2gudjEuRW5yb2xsbWVudFJlcXVlc3QuTGFiZWxzRW50cnkSLwoLY3JlYXRlX3RpbWUYBiABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEjAKDHJlc29sdmVfdGltZRgHIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASEwoLcmVzb2x2ZWRfYnkYCCABKAkaLQoLTGFiZWxzRW50cnkSCwoDa2V5GAEgASgJEg0KBXZhbHVlGAIgASgJOgI4ASJSCh1FbnJvbGxtZW50UmVxdWVzdExpc3RSZXNwb25zZRIxCghyZXF1ZXN0cxgBIAMoCzIfLmFnZW50bWVzaC52MS5FbnJvbGxtZW50UmVxdWVzdCJwCgRVc2VyEgoKAmlkGAEgASgJEg4KBmlzc3VlchgCIAEoCRINCgVlbWFpbBgDIAEoCRIMCgRyb2xlGAQgASgJEi8KC2NyZWF0ZV90aW1lGAUgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcCLjAgoMRW5yb2xsZWROb2RlEg8KB3BlZXJfaWQYASABKAkSDAoEcm9sZRgCIAEoCRIXCg9lbnJvbGxtZW50X3R5cGUYAyABKAkSEwoLY2xhaW1zX2pzb24YBCABKAkSEAoIb3duZXJfaWQYBSABKAkSNgoGbGFiZWxzGAYgAygLMiYuYWdlbnRtZXNoLnYxLkVucm9sbGVkTm9kZS5MYWJlbHNFbnRyeRIvCgtlbnJvbGxfdGltZRgHIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASLwoLZXhwaXJlX3RpbWUYCCABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEg4KBmJhbm5lZBgJIAEoCBIbChNhdXRvbm9tb3VzX3JlY292ZXJ5GAogASgIGi0KC0xhYmVsc0VudHJ5EgsKA2tleRgBIAEoCRINCgV2YWx1ZRgCIAEoCToCOAEixAEKC1JvdXRlckxlYXNlEg8KB3BlZXJfaWQYASABKAkSEQoJYWRkcmVzc2VzGAIgAygJEjUKEWxhc3RfcmVuZXdhbF90aW1lGAMgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIvCgtleHBpcmVfdGltZRgEIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASFwoPY29ubmVjdGVkX3BlZXJzGAUgAygJEhAKCGRodF9zaXplGAYgASgFImwKDE5vZGVTZXJ2aWNlcxIrCghzZXJ2aWNlcxgBIAMoCzIZLmFnZW50bWVzaC52MS5TZXJ2aWNlSW5mbxIvCgtyZXBvcnRfdGltZRgCIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXAi2wMKE0FkbWluU3RhdHVzUmVzcG9uc2USIQoFdXNlcnMYASADKAsyEi5hZ2VudG1lc2gudjEuVXNlchIxCg5hY3RpdmVfcm91dGVycxgCIAMoCzIZLmFnZW50bWVzaC52MS5Sb3V0ZXJMZWFzZRIyCg5lbnJvbGxlZF9ub2RlcxgDIAMoCzIaLmFnZW50bWVzaC52MS5FbnJvbGxlZE5vZGUSPAoTZW5yb2xsbWVudF9yZXF1ZXN0cxgEIAMoCzIfLmFnZW50bWVzaC52MS5FbnJvbGxtZW50UmVxdWVzdBI2ChBib290c3RyYXBfdG9rZW5zGAUgAygLMhwuYWdlbnRtZXNoLnYxLkJvb3RzdHJhcFRva2VuEioKBnBvbGljeRgGIAEoCzIaLmFnZW50bWVzaC52MS5Qb2xpY3lDb25maWcSSAoMbm9kZV9jYXRhbG9nGAcgAygLMjIuYWdlbnRtZXNoLnYxLkFkbWluU3RhdHVzUmVzcG9uc2UuTm9kZUNhdGFsb2dFbnRyeRpOChBOb2RlQ2F0YWxvZ0VudHJ5EgsKA2tleRgBIAEoCRIpCgV2YWx1ZRgCIAEoCzIaLmFnZW50bWVzaC52MS5Ob2RlU2VydmljZXM6AjgBIoECChJVc2VyU3RhdHVzUmVzcG9uc2USIAoEdXNlchgBIAEoCzISLmFnZW50bWVzaC52MS5Vc2VyEjIKDmVucm9sbGVkX25vZGVzGAIgAygLMhouYWdlbnRtZXNoLnYxLkVucm9sbGVkTm9kZRI2ChBib290c3RyYXBfdG9rZW5zGAMgAygLMhwuYWdlbnRtZXNoLnYxLkJvb3RzdHJhcFRva2VuEjEKDmFjdGl2ZV9yb3V0ZXJzGAQgAygLMhkuYWdlbnRtZXNoLnYxLlJvdXRlckxlYXNlEioKBnBvbGljeRgFIAEoCzIaLmFnZW50bWVzaC52MS5Qb2xpY3lDb25maWci5AEKGElkZW50aXR5RXZpZGVuY2VSZXNwb25zZRIPCgdwZWVyX2lkGAEgASgJEg8KB2Jpc2N1aXQYAiABKAwSNwoTYmlzY3VpdF9leHBpcmVfdGltZRgDIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASGQoRY29udHJvbF9wbGFuZV91cmwYBCABKAkSIgoadHJ1c3RlZF9jb250cm9sX3BsYW5lX2tleXMYBSADKAwSLgoKY2hlY2tfdGltZRgGIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXAixgIKFFBlZXJFdmlkZW5jZVJlc3BvbnNlEg8KB3BlZXJfaWQYASABKAkSDwoHYmlzY3VpdBgCIAEoDBIVCg12ZXJpZnlpbmdfa2V5GAMgASgMEg0KBXJvbGVzGAQgAygJEj4KBmxhYmVscxgFIAMoCzIuLmFnZW50bWVzaC52MS5QZWVyRXZpZGVuY2VSZXNwb25zZS5MYWJlbHNFbnRyeRIvCgtleHBpcmVfdGltZRgGIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASFgoOcmV2b2NhdGlvbl9pZHMYByADKAkSLgoKY2hlY2tfdGltZRgIIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXAaLQoLTGFiZWxzRW50cnkSCwoDa2V5GAEgASgJEg0KBXZhbHVlGAIgASgJOgI4ASKMAgoQTWVtYmVyQ3JlZGVudGlhbBIZChFjb250cm9sX3BsYW5lX3VybBgBIAEoCRIPCgdiaXNjdWl0GAIgASgMEi8KC2V4cGlyZV90aW1lGAMgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBI1Cgx0cnVzdGVkX2tleXMYBCADKAsyHy5hZ2VudG1lc2gudjEuVHJ1c3RlZFNpZ25pbmdLZXkSGQoRaXNzdWVkX3VuZGVyX2tleXMYBSADKAwSGAoQcm91dGVyX2FkZHJlc3NlcxgGIAMoCRIvCgxvaWRjX3Nlc3Npb24YByABKAsyGS5hZ2VudG1lc2gudjEuT0lEQ1Nlc3Npb24iWQoRVHJ1c3RlZFNpZ25pbmdLZXkSEgoKcHVibGljX2tleRgBIAEoDBIwCgxyZWNlaXZlX3RpbWUYAiABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wIlkKC09JRENTZXNzaW9uEg4KBmlzc3VlchgBIAEoCRIRCgljbGllbnRfaWQYAiABKAkSEAoIYXVkaWVuY2UYAyABKAkSFQoNcmVmcmVzaF90b2tlbhgEIAEoCSKTAQoVVGFza0F1dGhvcml6YXRpb25SdWxlEgwKBG5hbWUYASABKAkSFAoMZGlzcGxheV9uYW1lGAIgASgJEiUKBXJ1bGVzGAMgAygLMhYuYWdlbnRtZXNoLnYxLlRhc2tSdWxlEi8KC2V4cGlyZV90aW1lGAQgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcCKEAQoIVGFza1J1bGUSEwoLZGVzY3JpcHRpb24YASABKAkSGAoQYWxsb3dlZF9zZXJ2aWNlcxgCIAMoCRIuCglvcGVyYXRpb24YAyABKAsyGy5hZ2VudG1lc2gudjEuVGFza09wZXJhdGlvbhIZChFhbGxvd2VkX3Jlc291cmNlcxgEIAMoCSJzCg1UYXNrT3BlcmF0aW9uEhUKDWFsbG93ZWRfdG9vbHMYASADKAkSFwoPYWxsb3dlZF9tZXRob2RzGAIgAygJEhUKDWFsbG93ZWRfcGF0aHMYAyADKAkSGwoTYWxsb3dlZF9wZXJtaXNzaW9ucxgEIAMoCSKrAQoUVG9rZW5FeGNoYW5nZVJlcXVlc3QSFQoNc3ViamVjdF90b2tlbhgBIAEoCRI2Cgl0YXNrX3J1bGUYAiABKAsyIy5hZ2VudG1lc2gudjEuVGFza0F1dGhvcml6YXRpb25SdWxlEgwKBHNlYWwYAyABKAgSGQoRY2hhbGxlbmdlX3VuaXhfbXMYBCABKAMSGwoTY2hhbGxlbmdlX3NpZ25hdHVyZRgFIAEoDCJ/ChVUb2tlbkV4Y2hhbmdlUmVzcG9uc2USFQoNYmlzY3VpdF90b2tlbhgBIAEoDBIvCgtleHBpcmVfdGltZRgCIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASDQoFcm9sZXMYAyADKAkSDwoHc3ViamVjdBgEIAEoCSKBAQoPU1RTVG9rZW5SZXF1ZXN0Eg8KB2Jpc2N1aXQYASABKAwSEwoLZGVzdGluYXRpb24YAiABKAkSEAoIYXVkaWVuY2UYAyABKAkSGQoRY2hhbGxlbmdlX3VuaXhfbXMYBCABKAMSGwoTY2hhbGxlbmdlX3NpZ25hdHVyZRgFIAEoDCKDAQoQU1RTVG9rZW5SZXNwb25zZRILCgNqd3QYASABKAkSLwoLZXhwaXJlX3RpbWUYAiABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEg8KB3N1YmplY3QYAyABKAkSDQoFcm9sZXMYBCADKAkSEQoJdGFza19uYW1lGAUgASgJIkYKE1Jldm9jYXRpb25zUmVzcG9uc2USFgoOcmV2b2NhdGlvbl9pZHMYASADKAkSFwoPYmFubmVkX3BlZXJfaWRzGAIgAygJKpQBChBFbnJvbGxtZW50U3RhdHVzEiEKHUVOUk9MTE1FTlRfU1RBVFVTX1VOU1BFQ0lGSUVEEAASHQoZRU5ST0xMTUVOVF9TVEFUVVNfUEVORElORxABEh4KGkVOUk9MTE1FTlRfU1RBVFVTX0FQUFJPVkVEEAISHgoaRU5ST0xMTUVOVF9TVEFUVVNfUkVKRUNURUQQAyqMAQoLU2VydmljZVR5cGUSHAoYU0VSVklDRV9UWVBFX1VOU1BFQ0lGSUVEEAASFAoQU0VSVklDRV9UWVBFX01DUBABEhoKFlNFUlZJQ0VfVFlQRV9JTkZFUkVOQ0UQAhIUChBTRVJWSUNFX1RZUEVfQTJBEAMSFwoTU0VSVklDRV9UWVBFX0VHUkVTUxAEKjcKCkVncmVzc01vZGUSFAoQRUdSRVNTX01PREVfSFRUUBAAEhMKD0VHUkVTU19NT0RFX1RDUBABKlwKElJlc3BvbnNlSW5zcGVjdGlvbhIgChxSRVNQT05TRV9JTlNQRUNUSU9OX0JVRkZFUkVEEAASJAogUkVTUE9OU0VfSU5TUEVDVElPTl9SRVFVRVNUX09OTFkQAUIhWh9naXRodWIuY29tL2dvb2dsZS9hZ2VudG1lc2gvYXBpYgZwcm90bzM", [file_google_protobuf_duration, file_google_protobuf_timestamp]); /** * @generated from message agentmesh.v1.AuthFrame @@ -656,6 +656,16 @@ export type ControlPlaneInfoResponse = Message<"agentmesh.v1.ControlPlaneInfoRes * @generated from field: repeated agentmesh.v1.RouterInfo routers = 6; */ routers: RouterInfo[]; + + /** + * How the mesh keeps its directory. Members and routers read these here + * and nowhere else, so they agree without configuration of their own; a + * control plane that leaves it unset leaves them on the defaults the + * api package names. + * + * @generated from field: agentmesh.v1.DirectoryParameters directory = 7; + */ + directory?: DirectoryParameters | undefined; }; /** @@ -665,6 +675,48 @@ export type ControlPlaneInfoResponse = Message<"agentmesh.v1.ControlPlaneInfoRes export const ControlPlaneInfoResponseSchema: GenMessage = /*@__PURE__*/ messageDesc(file_agentmesh, 13); +/** + * DirectoryParameters are the settings of the DHT the routers serve, set + * once per mesh on the control plane (agentmesh-control-plane + * --dht-replication, --dht-reprovide-interval) and published on /info. + * + * @generated from message agentmesh.v1.DirectoryParameters + */ +export type DirectoryParameters = Message<"agentmesh.v1.DirectoryParameters"> & { + /** + * How many routers hold each record a member publishes and answer each + * lookup it makes: the routers closest to the key. A member holds a + * session with each of them besides the routers it is attached to. + * + * @generated from field: int32 replication = 1; + */ + replication: number; + + /** + * How often a member re-announces each service it publishes. + * + * @generated from field: google.protobuf.Duration reprovide_interval = 2; + */ + reprovideInterval?: Duration | undefined; + + /** + * How long a router keeps a record after its last announcement: three + * reprovide intervals, so a provider that stopped without unpublishing + * leaves within it and two missed announcements do not unlist one that + * did not. + * + * @generated from field: google.protobuf.Duration record_ttl = 3; + */ + recordTtl?: Duration | undefined; +}; + +/** + * Describes the message agentmesh.v1.DirectoryParameters. + * Use `create(DirectoryParametersSchema)` to create a new message. + */ +export const DirectoryParametersSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_agentmesh, 14); + /** * RouterInfo is one router as the control plane lists it on /info. * @@ -716,7 +768,7 @@ export type RouterInfo = Message<"agentmesh.v1.RouterInfo"> & { * Use `create(RouterInfoSchema)` to create a new message. */ export const RouterInfoSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_agentmesh, 14); + messageDesc(file_agentmesh, 15); /** * @generated from message agentmesh.v1.RouterLeaseRequest @@ -793,7 +845,7 @@ export type RouterLeaseRequest = Message<"agentmesh.v1.RouterLeaseRequest"> & { * Use `create(RouterLeaseRequestSchema)` to create a new message. */ export const RouterLeaseRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_agentmesh, 15); + messageDesc(file_agentmesh, 16); /** * @generated from message agentmesh.v1.RouterLeaseResponse @@ -820,7 +872,7 @@ export type RouterLeaseResponse = Message<"agentmesh.v1.RouterLeaseResponse"> & * Use `create(RouterLeaseResponseSchema)` to create a new message. */ export const RouterLeaseResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_agentmesh, 16); + messageDesc(file_agentmesh, 17); /** * @generated from message agentmesh.v1.PolicyRole @@ -868,7 +920,7 @@ export type PolicyRole = Message<"agentmesh.v1.PolicyRole"> & { * Use `create(PolicyRoleSchema)` to create a new message. */ export const PolicyRoleSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_agentmesh, 17); + messageDesc(file_agentmesh, 18); /** * HTTPGrant narrows one allowed_services entry to HTTP methods and paths. @@ -910,7 +962,7 @@ export type HTTPGrant = Message<"agentmesh.v1.HTTPGrant"> & { * Use `create(HTTPGrantSchema)` to create a new message. */ export const HTTPGrantSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_agentmesh, 18); + messageDesc(file_agentmesh, 19); /** * EgressDestination is a destination outside the mesh that selected nodes @@ -1009,7 +1061,7 @@ export type EgressDestination = Message<"agentmesh.v1.EgressDestination"> & { * Use `create(EgressDestinationSchema)` to create a new message. */ export const EgressDestinationSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_agentmesh, 19); + messageDesc(file_agentmesh, 20); /** * Inspection lists the inspectors the egress node runs, in order; the first @@ -1030,7 +1082,7 @@ export type Inspection = Message<"agentmesh.v1.Inspection"> & { * Use `create(InspectionSchema)` to create a new message. */ export const InspectionSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_agentmesh, 20); + messageDesc(file_agentmesh, 21); /** * @generated from message agentmesh.v1.Inspector @@ -1059,7 +1111,7 @@ export type Inspector = Message<"agentmesh.v1.Inspector"> & { * Use `create(InspectorSchema)` to create a new message. */ export const InspectorSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_agentmesh, 21); + messageDesc(file_agentmesh, 22); /** * ModelArmor calls sanitizeUserPrompt / sanitizeModelResponse directly over @@ -1104,7 +1156,7 @@ export type ModelArmor = Message<"agentmesh.v1.ModelArmor"> & { * Use `create(ModelArmorSchema)` to create a new message. */ export const ModelArmorSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_agentmesh, 22); + messageDesc(file_agentmesh, 23); /** * ExtProc runs an Envoy external processor (envoy.service.ext_proc.v3 @@ -1174,7 +1226,7 @@ export type ExtProc = Message<"agentmesh.v1.ExtProc"> & { * Use `create(ExtProcSchema)` to create a new message. */ export const ExtProcSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_agentmesh, 23); + messageDesc(file_agentmesh, 24); /** * @generated from message agentmesh.v1.ExtProcProcessingMode @@ -1216,7 +1268,7 @@ export type ExtProcProcessingMode = Message<"agentmesh.v1.ExtProcProcessingMode" * Use `create(ExtProcProcessingModeSchema)` to create a new message. */ export const ExtProcProcessingModeSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_agentmesh, 24); + messageDesc(file_agentmesh, 25); /** * @generated from enum agentmesh.v1.ExtProcProcessingMode.HeaderMode @@ -1242,7 +1294,7 @@ export enum ExtProcProcessingMode_HeaderMode { * Describes the enum agentmesh.v1.ExtProcProcessingMode.HeaderMode. */ export const ExtProcProcessingMode_HeaderModeSchema: GenEnum = /*@__PURE__*/ - enumDesc(file_agentmesh, 24, 0); + enumDesc(file_agentmesh, 25, 0); /** * @generated from enum agentmesh.v1.ExtProcProcessingMode.BodyMode @@ -1278,7 +1330,7 @@ export enum ExtProcProcessingMode_BodyMode { * Describes the enum agentmesh.v1.ExtProcProcessingMode.BodyMode. */ export const ExtProcProcessingMode_BodyModeSchema: GenEnum = /*@__PURE__*/ - enumDesc(file_agentmesh, 24, 1); + enumDesc(file_agentmesh, 25, 1); /** * @generated from message agentmesh.v1.CredentialBroker @@ -1324,7 +1376,7 @@ export type CredentialBroker = Message<"agentmesh.v1.CredentialBroker"> & { * Use `create(CredentialBrokerSchema)` to create a new message. */ export const CredentialBrokerSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_agentmesh, 25); + messageDesc(file_agentmesh, 26); /** * OIDCFederation exchanges the control plane's JWT at a provider STS. @@ -1369,7 +1421,7 @@ export type OIDCFederation = Message<"agentmesh.v1.OIDCFederation"> & { * Use `create(OIDCFederationSchema)` to create a new message. */ export const OIDCFederationSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_agentmesh, 26); + messageDesc(file_agentmesh, 27); /** * @generated from message agentmesh.v1.AWSAssumeRole @@ -1393,7 +1445,7 @@ export type AWSAssumeRole = Message<"agentmesh.v1.AWSAssumeRole"> & { * Use `create(AWSAssumeRoleSchema)` to create a new message. */ export const AWSAssumeRoleSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_agentmesh, 27); + messageDesc(file_agentmesh, 28); /** * @generated from message agentmesh.v1.PlatformIdentity @@ -1410,7 +1462,7 @@ export type PlatformIdentity = Message<"agentmesh.v1.PlatformIdentity"> & { * Use `create(PlatformIdentitySchema)` to create a new message. */ export const PlatformIdentitySchema: GenMessage = /*@__PURE__*/ - messageDesc(file_agentmesh, 28); + messageDesc(file_agentmesh, 29); /** * @generated from message agentmesh.v1.PolicyBinding @@ -1432,7 +1484,7 @@ export type PolicyBinding = Message<"agentmesh.v1.PolicyBinding"> & { * Use `create(PolicyBindingSchema)` to create a new message. */ export const PolicyBindingSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_agentmesh, 29); + messageDesc(file_agentmesh, 30); /** * PolicyConfig is the mesh policy as the operator writes it: roles and @@ -1464,7 +1516,7 @@ export type PolicyConfig = Message<"agentmesh.v1.PolicyConfig"> & { * Use `create(PolicyConfigSchema)` to create a new message. */ export const PolicyConfigSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_agentmesh, 30); + messageDesc(file_agentmesh, 31); /** * @generated from message agentmesh.v1.PolicyConfigGetRequest @@ -1477,7 +1529,7 @@ export type PolicyConfigGetRequest = Message<"agentmesh.v1.PolicyConfigGetReques * Use `create(PolicyConfigGetRequestSchema)` to create a new message. */ export const PolicyConfigGetRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_agentmesh, 31); + messageDesc(file_agentmesh, 32); /** * PolicyConfigGetResponse answers GET /policies for a mesh member holding a @@ -1500,7 +1552,7 @@ export type PolicyConfigGetResponse = Message<"agentmesh.v1.PolicyConfigGetRespo * Use `create(PolicyConfigGetResponseSchema)` to create a new message. */ export const PolicyConfigGetResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_agentmesh, 32); + messageDesc(file_agentmesh, 33); /** * @generated from message agentmesh.v1.PolicyConfigUpdateResponse @@ -1522,7 +1574,7 @@ export type PolicyConfigUpdateResponse = Message<"agentmesh.v1.PolicyConfigUpdat * Use `create(PolicyConfigUpdateResponseSchema)` to create a new message. */ export const PolicyConfigUpdateResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_agentmesh, 33); + messageDesc(file_agentmesh, 34); /** * @generated from message agentmesh.v1.EgressAssignmentsRequest @@ -1535,7 +1587,7 @@ export type EgressAssignmentsRequest = Message<"agentmesh.v1.EgressAssignmentsRe * Use `create(EgressAssignmentsRequestSchema)` to create a new message. */ export const EgressAssignmentsRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_agentmesh, 34); + messageDesc(file_agentmesh, 35); /** * EgressAssignmentsResponse answers GET /egress for a mesh member holding a @@ -1557,7 +1609,7 @@ export type EgressAssignmentsResponse = Message<"agentmesh.v1.EgressAssignmentsR * Use `create(EgressAssignmentsResponseSchema)` to create a new message. */ export const EgressAssignmentsResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_agentmesh, 35); + messageDesc(file_agentmesh, 36); /** * @generated from message agentmesh.v1.KeysResponse @@ -1592,7 +1644,7 @@ export type KeysResponse = Message<"agentmesh.v1.KeysResponse"> & { * Use `create(KeysResponseSchema)` to create a new message. */ export const KeysResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_agentmesh, 36); + messageDesc(file_agentmesh, 37); /** * @generated from message agentmesh.v1.TokenRefreshRequest @@ -1645,7 +1697,7 @@ export type TokenRefreshRequest = Message<"agentmesh.v1.TokenRefreshRequest"> & * Use `create(TokenRefreshRequestSchema)` to create a new message. */ export const TokenRefreshRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_agentmesh, 37); + messageDesc(file_agentmesh, 38); /** * @generated from message agentmesh.v1.TokenRefreshResponse @@ -1672,7 +1724,7 @@ export type TokenRefreshResponse = Message<"agentmesh.v1.TokenRefreshResponse"> * Use `create(TokenRefreshResponseSchema)` to create a new message. */ export const TokenRefreshResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_agentmesh, 38); + messageDesc(file_agentmesh, 39); /** * NodeCatalogReport is the body of POST /nodes/catalog: a node's @@ -1694,7 +1746,7 @@ export type NodeCatalogReport = Message<"agentmesh.v1.NodeCatalogReport"> & { * Use `create(NodeCatalogReportSchema)` to create a new message. */ export const NodeCatalogReportSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_agentmesh, 39); + messageDesc(file_agentmesh, 40); /** * @generated from message agentmesh.v1.TokenRevokeRequest @@ -1711,7 +1763,7 @@ export type TokenRevokeRequest = Message<"agentmesh.v1.TokenRevokeRequest"> & { * Use `create(TokenRevokeRequestSchema)` to create a new message. */ export const TokenRevokeRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_agentmesh, 40); + messageDesc(file_agentmesh, 41); /** * @generated from message agentmesh.v1.TokenRevokeResponse @@ -1733,7 +1785,7 @@ export type TokenRevokeResponse = Message<"agentmesh.v1.TokenRevokeResponse"> & * Use `create(TokenRevokeResponseSchema)` to create a new message. */ export const TokenRevokeResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_agentmesh, 41); + messageDesc(file_agentmesh, 42); /** * BootstrapTokenCreateRequest is the body of POST /admin/bootstrap-tokens @@ -1796,7 +1848,7 @@ export type BootstrapTokenCreateRequest = Message<"agentmesh.v1.BootstrapTokenCr * Use `create(BootstrapTokenCreateRequestSchema)` to create a new message. */ export const BootstrapTokenCreateRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_agentmesh, 42); + messageDesc(file_agentmesh, 43); /** * BootstrapTokenCreateResponse is returned (201) when a token is minted. @@ -1837,7 +1889,7 @@ export type BootstrapTokenCreateResponse = Message<"agentmesh.v1.BootstrapTokenC * Use `create(BootstrapTokenCreateResponseSchema)` to create a new message. */ export const BootstrapTokenCreateResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_agentmesh, 43); + messageDesc(file_agentmesh, 44); /** * BootstrapToken is a minted token as operators list it. @@ -1904,7 +1956,7 @@ export type BootstrapToken = Message<"agentmesh.v1.BootstrapToken"> & { * Use `create(BootstrapTokenSchema)` to create a new message. */ export const BootstrapTokenSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_agentmesh, 44); + messageDesc(file_agentmesh, 45); /** * @generated from message agentmesh.v1.BootstrapTokenListResponse @@ -1921,7 +1973,7 @@ export type BootstrapTokenListResponse = Message<"agentmesh.v1.BootstrapTokenLis * Use `create(BootstrapTokenListResponseSchema)` to create a new message. */ export const BootstrapTokenListResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_agentmesh, 45); + messageDesc(file_agentmesh, 46); /** * EnrollmentRequest is a bootstrap enrollment awaiting or past an operator @@ -1982,7 +2034,7 @@ export type EnrollmentRequest = Message<"agentmesh.v1.EnrollmentRequest"> & { * Use `create(EnrollmentRequestSchema)` to create a new message. */ export const EnrollmentRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_agentmesh, 46); + messageDesc(file_agentmesh, 47); /** * @generated from message agentmesh.v1.EnrollmentRequestListResponse @@ -1999,7 +2051,7 @@ export type EnrollmentRequestListResponse = Message<"agentmesh.v1.EnrollmentRequ * Use `create(EnrollmentRequestListResponseSchema)` to create a new message. */ export const EnrollmentRequestListResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_agentmesh, 47); + messageDesc(file_agentmesh, 48); /** * User is a human identity known to the mesh. @@ -2042,7 +2094,7 @@ export type User = Message<"agentmesh.v1.User"> & { * Use `create(UserSchema)` to create a new message. */ export const UserSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_agentmesh, 48); + messageDesc(file_agentmesh, 49); /** * EnrolledNode is a member's enrollment record without its credential. @@ -2112,7 +2164,7 @@ export type EnrolledNode = Message<"agentmesh.v1.EnrolledNode"> & { * Use `create(EnrolledNodeSchema)` to create a new message. */ export const EnrolledNodeSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_agentmesh, 49); + messageDesc(file_agentmesh, 50); /** * RouterLease is a router's current registration with the control plane. @@ -2158,7 +2210,7 @@ export type RouterLease = Message<"agentmesh.v1.RouterLease"> & { * Use `create(RouterLeaseSchema)` to create a new message. */ export const RouterLeaseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_agentmesh, 50); + messageDesc(file_agentmesh, 51); /** * NodeServices is the services one node last reported (see @@ -2183,7 +2235,7 @@ export type NodeServices = Message<"agentmesh.v1.NodeServices"> & { * Use `create(NodeServicesSchema)` to create a new message. */ export const NodeServicesSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_agentmesh, 51); + messageDesc(file_agentmesh, 52); /** * AdminStatusResponse answers GET /admin/status: everything the console @@ -2235,7 +2287,7 @@ export type AdminStatusResponse = Message<"agentmesh.v1.AdminStatusResponse"> & * Use `create(AdminStatusResponseSchema)` to create a new message. */ export const AdminStatusResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_agentmesh, 52); + messageDesc(file_agentmesh, 53); /** * UserStatusResponse answers GET /user/status: the caller and what it owns. @@ -2276,7 +2328,7 @@ export type UserStatusResponse = Message<"agentmesh.v1.UserStatusResponse"> & { * Use `create(UserStatusResponseSchema)` to create a new message. */ export const UserStatusResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_agentmesh, 53); + messageDesc(file_agentmesh, 54); /** * @generated from message agentmesh.v1.IdentityEvidenceResponse @@ -2320,7 +2372,7 @@ export type IdentityEvidenceResponse = Message<"agentmesh.v1.IdentityEvidenceRes * Use `create(IdentityEvidenceResponseSchema)` to create a new message. */ export const IdentityEvidenceResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_agentmesh, 54); + messageDesc(file_agentmesh, 55); /** * @generated from message agentmesh.v1.PeerEvidenceResponse @@ -2376,7 +2428,7 @@ export type PeerEvidenceResponse = Message<"agentmesh.v1.PeerEvidenceResponse"> * Use `create(PeerEvidenceResponseSchema)` to create a new message. */ export const PeerEvidenceResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_agentmesh, 55); + messageDesc(file_agentmesh, 56); /** * @generated from message agentmesh.v1.MemberCredential @@ -2441,7 +2493,7 @@ export type MemberCredential = Message<"agentmesh.v1.MemberCredential"> & { * Use `create(MemberCredentialSchema)` to create a new message. */ export const MemberCredentialSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_agentmesh, 56); + messageDesc(file_agentmesh, 57); /** * @generated from message agentmesh.v1.TrustedSigningKey @@ -2468,7 +2520,7 @@ export type TrustedSigningKey = Message<"agentmesh.v1.TrustedSigningKey"> & { * Use `create(TrustedSigningKeySchema)` to create a new message. */ export const TrustedSigningKeySchema: GenMessage = /*@__PURE__*/ - messageDesc(file_agentmesh, 57); + messageDesc(file_agentmesh, 58); /** * @generated from message agentmesh.v1.OIDCSession @@ -2500,7 +2552,7 @@ export type OIDCSession = Message<"agentmesh.v1.OIDCSession"> & { * Use `create(OIDCSessionSchema)` to create a new message. */ export const OIDCSessionSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_agentmesh, 58); + messageDesc(file_agentmesh, 59); /** * TaskAuthorizationRule narrows a credential's authority for a specific task or @@ -2544,7 +2596,7 @@ export type TaskAuthorizationRule = Message<"agentmesh.v1.TaskAuthorizationRule" * Use `create(TaskAuthorizationRuleSchema)` to create a new message. */ export const TaskAuthorizationRuleSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_agentmesh, 59); + messageDesc(file_agentmesh, 60); /** * @generated from message agentmesh.v1.TaskRule @@ -2588,7 +2640,7 @@ export type TaskRule = Message<"agentmesh.v1.TaskRule"> & { * Use `create(TaskRuleSchema)` to create a new message. */ export const TaskRuleSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_agentmesh, 60); + messageDesc(file_agentmesh, 61); /** * @generated from message agentmesh.v1.TaskOperation @@ -2630,7 +2682,7 @@ export type TaskOperation = Message<"agentmesh.v1.TaskOperation"> & { * Use `create(TaskOperationSchema)` to create a new message. */ export const TaskOperationSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_agentmesh, 61); + messageDesc(file_agentmesh, 62); /** * TokenExchangeRequest is the body of POST /token/exchange on the control @@ -2688,7 +2740,7 @@ export type TokenExchangeRequest = Message<"agentmesh.v1.TokenExchangeRequest"> * Use `create(TokenExchangeRequestSchema)` to create a new message. */ export const TokenExchangeRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_agentmesh, 62); + messageDesc(file_agentmesh, 63); /** * @generated from message agentmesh.v1.TokenExchangeResponse @@ -2720,7 +2772,7 @@ export type TokenExchangeResponse = Message<"agentmesh.v1.TokenExchangeResponse" * Use `create(TokenExchangeResponseSchema)` to create a new message. */ export const TokenExchangeResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_agentmesh, 63); + messageDesc(file_agentmesh, 64); /** * STSTokenRequest is the body of POST /sts/token on the control plane: an @@ -2774,7 +2826,7 @@ export type STSTokenRequest = Message<"agentmesh.v1.STSTokenRequest"> & { * Use `create(STSTokenRequestSchema)` to create a new message. */ export const STSTokenRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_agentmesh, 64); + messageDesc(file_agentmesh, 65); /** * @generated from message agentmesh.v1.STSTokenResponse @@ -2811,7 +2863,7 @@ export type STSTokenResponse = Message<"agentmesh.v1.STSTokenResponse"> & { * Use `create(STSTokenResponseSchema)` to create a new message. */ export const STSTokenResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_agentmesh, 65); + messageDesc(file_agentmesh, 66); /** * RevocationsResponse answers GET /revocations on the control plane: the set of @@ -2837,7 +2889,7 @@ export type RevocationsResponse = Message<"agentmesh.v1.RevocationsResponse"> & * Use `create(RevocationsResponseSchema)` to create a new message. */ export const RevocationsResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_agentmesh, 66); + messageDesc(file_agentmesh, 67); /** * @generated from enum agentmesh.v1.EnrollmentStatus diff --git a/sdk/js/src/host.ts b/sdk/js/src/host.ts index 3d691eaf..388874e2 100644 --- a/sdk/js/src/host.ts +++ b/sdk/js/src/host.ts @@ -28,7 +28,7 @@ import { kadDHT, passthroughMapper } from "@libp2p/kad-dht"; import { ping } from "@libp2p/ping"; import type { Multiaddr } from "@multiformats/multiaddr"; import { createLibp2p, type Libp2pOptions } from "libp2p"; -import { DHT_PROTOCOL } from "./discovery.ts"; +import { DEFAULT_DHT_REPLICATION, DHT_PROTOCOL } from "./discovery.ts"; import type { Identity } from "./identity.ts"; import { connectionEncrypters, transports } from "./platform/transports.ts"; @@ -47,6 +47,14 @@ export interface MeshHostOptions { banned?: { has(peerId: string): boolean }; /** The resolver for `/dnsaddr` and `/dns*` addresses; the system's by default. */ dns?: Libp2pOptions["dns"]; + /** + * The mesh's replication factor, from /info (AgentMesh.dhtReplication): + * kademlia's k on this member. A record lives on that many routers, the + * closest to its key, and a lookup asks those, that many at a time. The + * same figure on every member of a mesh, which is why it comes from the + * control plane; DEFAULT_DHT_REPLICATION when it published none. + */ + dhtReplication?: number; } /** The services a mesh host runs; `services.pubsub` carries the control plane's events. */ @@ -55,6 +63,7 @@ export type MeshHost = Libp2p<{ pubsub: GossipSub }>; export async function createMeshHost(identity: Identity, options: MeshHostOptions = {}): Promise { const banned = options.banned ?? { has: () => false }; const denyBanned = (peerId: PeerId) => banned.has(peerId.toString()); + const replication = options.dhtReplication !== undefined && options.dhtReplication >= 1 ? Math.floor(options.dhtReplication) : DEFAULT_DHT_REPLICATION; return createLibp2p({ privateKey: privateKeyFromProtobuf(identity.toLibp2pPrivateKey()), addresses: { listen: options.listenAddrs ?? [] }, @@ -89,7 +98,7 @@ export async function createMeshHost(identity: Identity, options: MeshHostOption // A client of the mesh DHT: it looks providers up and does not hold // records. Peers keep their private addresses; a mesh member often // is one, and the router relays to it. - dht: kadDHT({ protocol: DHT_PROTOCOL, clientMode: true, peerInfoMapper: passthroughMapper, initialQuerySelfInterval: 20 }), + dht: kadDHT({ protocol: DHT_PROTOCOL, clientMode: true, peerInfoMapper: passthroughMapper, initialQuerySelfInterval: 20, kBucketSize: replication, alpha: replication }), // The control plane's events reach members through the routers. // StrictSign, as every Go component pins it: the pubsub envelope is // signed by the author, and the event inside by the control plane. diff --git a/sdk/js/src/mesh.ts b/sdk/js/src/mesh.ts index 9f34ac6f..f1b9bc0e 100644 --- a/sdk/js/src/mesh.ts +++ b/sdk/js/src/mesh.ts @@ -15,6 +15,7 @@ import type { MessageInitShape } from "@bufbuild/protobuf"; import { toHex } from "./bytes.ts"; import { ControlPlaneClient, KeysNotTrustedError, ROLE_NODE, type Enrollment } from "./controlplane.ts"; +import { DEFAULT_DHT_REPLICATION } from "./discovery.ts"; import { attenuateCredential, credentialFromJSON, @@ -144,6 +145,7 @@ export class AgentMesh { readonly controlPlane: ControlPlaneClient; #credential: MeshCredential; #routers: RouterInfo[] = []; + #dhtReplication = DEFAULT_DHT_REPLICATION; readonly #state: StateStore | undefined; readonly #jwtSource: (() => Promise) | undefined; // Refreshes run one after another, as agentmesh-node's refreshMu: the control @@ -178,6 +180,16 @@ export class AgentMesh { return this.#routers; } + /** + * How many routers hold each record and answer each lookup on this mesh, + * as /info last published it (ControlPlaneInfoResponse.directory); + * DEFAULT_DHT_REPLICATION until the control plane has answered or when it + * publishes none. + */ + get dhtReplication(): number { + return this.#dhtReplication; + } + /** Returns a new MeshCredential with a tar_block appended offline in memory. */ attenuate(rule: MessageInitShape): Promise { return attenuateCredential(this.#credential, rule); @@ -407,6 +419,9 @@ export class AgentMesh { const info = await this.controlPlane.info(); routers = info.routers; this.#routers = info.routers; + if (info.directory !== undefined && info.directory.replication >= 1) { + this.#dhtReplication = info.directory.replication; + } const discovered = info.routerAddresses.length > 0 ? info.routerAddresses diff --git a/sdk/js/src/session.ts b/sdk/js/src/session.ts index 579c403f..91bb2b4b 100644 --- a/sdk/js/src/session.ts +++ b/sdk/js/src/session.ts @@ -1360,7 +1360,7 @@ export async function joinMesh(mesh: AgentMesh, options: JoinOptions = {}): Prom } const banned = new BanSet(); - const node = await createMeshHost(mesh.identity, { ...options, banned }); + const node = await createMeshHost(mesh.identity, { ...options, banned, dhtReplication: mesh.dhtReplication }); const authenticatedPeers = new Map(); let session: MeshSession | undefined; try { diff --git a/sdk/js/src/sync.test.ts b/sdk/js/src/sync.test.ts index 049e7bc2..63817e06 100644 --- a/sdk/js/src/sync.test.ts +++ b/sdk/js/src/sync.test.ts @@ -35,6 +35,7 @@ import { AUTH_PROTOCOL, authenticateWithPeer, authStreamHandler } from "./auth.t import { ROLE_ROUTER, loadBiscuit, verifyPeerBiscuit } from "./biscuit.ts"; import { ROLE_NODE } from "./controlplane.ts"; import { credentialPredatesRotation } from "./credential.ts"; +import { DEFAULT_DHT_REPLICATION } from "./discovery.ts"; import { AuthFrameSchema, BootstrapEnrollRequestSchema, @@ -80,6 +81,8 @@ class SigningKey { class FakeControlPlane { keys: SigningKey[]; banned: string[] = []; + /** The replication /info publishes; undefined is a control plane that predates the field. */ + replication: number | undefined; refreshes = 0; constructor(initial: SigningKey) { this.keys = [initial]; @@ -115,7 +118,16 @@ class FakeControlPlane { return proto(toBinary(KeysResponseSchema, create(KeysResponseSchema, { ...unsigned, signatures: this.keys.map((k) => k.identity.sign(payload)) }))); } if (req.method === "GET" && path === "/info") { - return proto(toBinary(ControlPlaneInfoResponseSchema, create(ControlPlaneInfoResponseSchema, { routerAddresses: [routerAddr], bannedPeerIds: this.banned }))); + return proto( + toBinary( + ControlPlaneInfoResponseSchema, + create(ControlPlaneInfoResponseSchema, { + routerAddresses: [routerAddr], + bannedPeerIds: this.banned, + ...(this.replication !== undefined ? { directory: { replication: this.replication } } : {}), + }), + ), + ); } if (req.method === "POST" && path === "/refresh") { this.refreshes++; @@ -212,6 +224,25 @@ test("a mesh event verifies only under a trusted key and only when fresh", () => assert.equal(verifyMeshEvent(bannedNobody, [key.pub], now), undefined, "not a peer id"); }); +test("a pull takes the mesh's replication from /info, and the default when it publishes none", async () => { + const mesh = await AgentMesh.enroll({ controlPlaneUrl: "http://127.0.0.1:1", bootstrapToken: "sbt", fetch: cp.fetch }); + assert.equal(mesh.dhtReplication, DEFAULT_DHT_REPLICATION, "before any pull"); + cp.replication = 5; + try { + await mesh.syncControlPlane(); + assert.equal(mesh.dhtReplication, 5); + // A control plane that stops publishing it, or publishes nothing usable, leaves the last value. + cp.replication = 0; + await mesh.syncControlPlane(); + assert.equal(mesh.dhtReplication, 5); + cp.replication = undefined; + await mesh.syncControlPlane(); + assert.equal(mesh.dhtReplication, 5); + } finally { + cp.replication = undefined; + } +}); + test("a pull learns a key rotation and refreshes the credential under the new key", async () => { const mesh = await AgentMesh.enroll({ controlPlaneUrl: "http://127.0.0.1:1", bootstrapToken: "sbt", fetch: cp.fetch }); const session = await mesh.join({ refreshLeadMs: 0, controlPlaneSyncIntervalMs: 0 }); diff --git a/sdk/python/src/agent_mesh/_proto/agentmesh_pb2.py b/sdk/python/src/agent_mesh/_proto/agentmesh_pb2.py index 240f72fb..c64cd5dc 100644 --- a/sdk/python/src/agent_mesh/_proto/agentmesh_pb2.py +++ b/sdk/python/src/agent_mesh/_proto/agentmesh_pb2.py @@ -15,7 +15,7 @@ from google.protobuf import timestamp_pb2 as google_dot_protobuf_dot_timestamp__pb2 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0f\x61gentmesh.proto\x12\x0c\x61gentmesh.v1\x1a\x1egoogle/protobuf/duration.proto\x1a\x1fgoogle/protobuf/timestamp.proto\"4\n\tAuthFrame\x12\x0f\n\x07\x62iscuit\x18\x01 \x01(\x0c\x12\x16\n\x0etarget_service\x18\x02 \x01(\t\"?\n\x0c\x41uthResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\r\n\x05\x65rror\x18\x02 \x01(\t\x12\x0f\n\x07\x62iscuit\x18\x03 \x01(\x0c\"\xb1\x01\n\x0cRouterGoAway\x12\x31\n\x06reason\x18\x01 \x01(\x0e\x32!.agentmesh.v1.RouterGoAway.Reason\x12.\n\x0bretry_after\x18\x02 \x01(\x0b\x32\x19.google.protobuf.Duration\">\n\x06Reason\x12\x16\n\x12REASON_UNSPECIFIED\x10\x00\x12\x0c\n\x08\x44RAINING\x10\x01\x12\x0e\n\nOVERLOADED\x10\x02\"\xdc\x01\n\tMeshEvent\x12*\n\x04type\x18\x01 \x01(\x0e\x32\x1c.agentmesh.v1.MeshEvent.Type\x12\x0f\n\x07peer_id\x18\x02 \x01(\t\x12.\n\nevent_time\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x16\n\x0enew_public_key\x18\x04 \x01(\x0c\x12\x11\n\tsignature\x18\x05 \x01(\x0c\"7\n\x04Type\x12\n\n\x06\x42\x41NNED\x10\x00\x12\x10\n\x0cKEY_ROTATION\x10\x01\x12\x11\n\rPOLICY_UPDATE\x10\x02\"\xf9\x01\n\rEnrollRequest\x12\x0b\n\x03jwt\x18\x01 \x01(\t\x12\x0f\n\x07peer_id\x18\x02 \x01(\t\x12\x12\n\npublic_key\x18\x03 \x01(\x0c\x12\x16\n\x0erequested_role\x18\x04 \x01(\t\x12\x37\n\x06labels\x18\x05 \x03(\x0b\x32\'.agentmesh.v1.EnrollRequest.LabelsEntry\x12\x19\n\x11\x63hallenge_unix_ms\x18\x06 \x01(\x03\x12\x1b\n\x13\x63hallenge_signature\x18\x07 \x01(\x0c\x1a-\n\x0bLabelsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xab\x01\n\x0e\x45nrollResponse\x12\x15\n\rbiscuit_token\x18\x01 \x01(\x0c\x12\x15\n\rerror_message\x18\x02 \x01(\t\x12 \n\x18\x63ontrol_plane_public_key\x18\x03 \x01(\x0c\x12\x18\n\x10router_addresses\x18\x04 \x03(\t\x12/\n\x0b\x65xpire_time\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"\x97\x02\n\x16\x42ootstrapEnrollRequest\x12\x17\n\x0f\x62ootstrap_token\x18\x01 \x01(\t\x12\x0f\n\x07peer_id\x18\x02 \x01(\t\x12\x12\n\npublic_key\x18\x03 \x01(\x0c\x12\x16\n\x0erequested_role\x18\x04 \x01(\t\x12@\n\x06labels\x18\x05 \x03(\x0b\x32\x30.agentmesh.v1.BootstrapEnrollRequest.LabelsEntry\x12\x19\n\x11\x63hallenge_unix_ms\x18\x06 \x01(\x03\x12\x1b\n\x13\x63hallenge_signature\x18\x07 \x01(\x0c\x1a-\n\x0bLabelsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\x83\x02\n\x17\x42ootstrapEnrollResponse\x12.\n\x06status\x18\x01 \x01(\x0e\x32\x1e.agentmesh.v1.EnrollmentStatus\x12\x15\n\rbiscuit_token\x18\x02 \x01(\x0c\x12\x1d\n\x15poll_interval_seconds\x18\x03 \x01(\x05\x12\x15\n\rerror_message\x18\x04 \x01(\t\x12 \n\x18\x63ontrol_plane_public_key\x18\x05 \x01(\x0c\x12\x18\n\x10router_addresses\x18\x06 \x03(\t\x12/\n\x0b\x65xpire_time\x18\x07 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"Y\n\x0bServiceInfo\x12\'\n\x04type\x18\x01 \x01(\x0e\x32\x19.agentmesh.v1.ServiceType\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x03 \x01(\t\"\x81\x01\n\x0e\x43ommandBackend\x12\x0f\n\x07\x63ommand\x18\x01 \x03(\t\x12\x32\n\x03\x65nv\x18\x02 \x03(\x0b\x32%.agentmesh.v1.CommandBackend.EnvEntry\x1a*\n\x08\x45nvEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\x96\x01\n\x16RegisterServiceRequest\x12*\n\x07service\x18\x01 \x01(\x0b\x32\x19.agentmesh.v1.ServiceInfo\x12\x14\n\ntarget_url\x18\x02 \x01(\tH\x00\x12/\n\x07\x63ommand\x18\x03 \x01(\x0b\x32\x1c.agentmesh.v1.CommandBackendH\x00\x42\t\n\x07\x62\x61\x63kend\"i\n\x12\x44iscoveredProvider\x12\x0f\n\x07peer_id\x18\x01 \x01(\t\x12\x17\n\x0flocal_proxy_url\x18\x02 \x01(\t\x12\x10\n\x08srv_name\x18\x03 \x01(\t\x12\x17\n\x0fsrv_description\x18\x04 \x01(\t\"\xbe\x02\n\x0fServiceAnnounce\x12\x0f\n\x07peer_id\x18\x01 \x01(\t\x12\'\n\x04type\x18\x02 \x01(\x0e\x32\x19.agentmesh.v1.ServiceType\x12\x14\n\x0cservice_name\x18\x03 \x01(\t\x12\x0c\n\x04keys\x18\x04 \x03(\t\x12\x39\n\x06labels\x18\x05 \x03(\x0b\x32).agentmesh.v1.ServiceAnnounce.LabelsEntry\x12\x17\n\x0f\x61\x63tive_requests\x18\x06 \x01(\r\x12\x17\n\x0flatency_ewma_ms\x18\x07 \x01(\x01\x12\x31\n\rannounce_time\x18\x08 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x1a-\n\x0bLabelsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xb2\x01\n\x18\x43ontrolPlaneInfoResponse\x12\x13\n\x0boidc_issuer\x18\x01 \x01(\t\x12\x11\n\tclient_id\x18\x02 \x01(\t\x12\x10\n\x08\x61udience\x18\x03 \x01(\t\x12\x18\n\x10router_addresses\x18\x04 \x03(\t\x12\x17\n\x0f\x62\x61nned_peer_ids\x18\x05 \x03(\t\x12)\n\x07routers\x18\x06 \x03(\x0b\x32\x18.agentmesh.v1.RouterInfo\"\xc4\x01\n\nRouterInfo\x12\x0f\n\x07peer_id\x18\x01 \x01(\t\x12\x11\n\taddresses\x18\x02 \x03(\t\x12\x34\n\x06labels\x18\x03 \x03(\x0b\x32$.agentmesh.v1.RouterInfo.LabelsEntry\x12\x13\n\x0b\x63onnections\x18\x04 \x01(\x05\x12\x18\n\x10\x63onnection_limit\x18\x05 \x01(\x05\x1a-\n\x0bLabelsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\x83\x02\n\x12RouterLeaseRequest\x12\x0f\n\x07peer_id\x18\x01 \x01(\t\x12\x11\n\taddresses\x18\x02 \x03(\t\x12\x0f\n\x07\x62iscuit\x18\x03 \x01(\x0c\x12\x17\n\x0f\x63onnected_peers\x18\x04 \x03(\t\x12\x10\n\x08\x64ht_size\x18\x05 \x01(\x05\x12\x19\n\x11\x63hallenge_unix_ms\x18\x06 \x01(\x03\x12\x1b\n\x13\x63hallenge_signature\x18\x07 \x01(\x0c\x12&\n\x03ttl\x18\x08 \x01(\x0b\x32\x19.google.protobuf.Duration\x12\x13\n\x0b\x63onnections\x18\t \x01(\x05\x12\x18\n\x10\x63onnection_limit\x18\n \x01(\x05\"f\n\x13RouterLeaseResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\r\n\x05\x65rror\x18\x02 \x01(\t\x12/\n\x0b\x65xpire_time\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"\xa4\x01\n\nPolicyRole\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x17\n\x0f\x61llowed_targets\x18\x02 \x03(\t\x12\x18\n\x10\x61llowed_services\x18\x03 \x03(\t\x12\x16\n\x0e\x63ustom_datalog\x18\x04 \x03(\t\x12\x16\n\x0e\x61llowed_labels\x18\x05 \x03(\t\x12%\n\x04http\x18\x06 \x03(\x0b\x32\x17.agentmesh.v1.HTTPGrant\"<\n\tHTTPGrant\x12\x0f\n\x07service\x18\x01 \x01(\t\x12\x0f\n\x07methods\x18\x02 \x03(\t\x12\r\n\x05paths\x18\x03 \x03(\t\"\xa1\x02\n\x11\x45gressDestination\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x12\n\ntarget_url\x18\x02 \x01(\t\x12\x12\n\ncredential\x18\x03 \x01(\t\x12\x11\n\tserved_by\x18\x04 \x03(\t\x12.\n\x06\x62roker\x18\x05 \x01(\x0b\x32\x1e.agentmesh.v1.CredentialBroker\x12,\n\ninspection\x18\x06 \x01(\x0b\x32\x18.agentmesh.v1.Inspection\x12&\n\x04mode\x18\x07 \x01(\x0e\x32\x18.agentmesh.v1.EgressMode\x12\r\n\x05ports\x18\x08 \x03(\r\x12\x15\n\rpreserve_host\x18\t \x01(\x08\x12\x17\n\x0f\x66orward_context\x18\n \x01(\x08\"9\n\nInspection\x12+\n\ninspectors\x18\x01 \x03(\x0b\x32\x17.agentmesh.v1.Inspector\"o\n\tInspector\x12/\n\x0bmodel_armor\x18\x01 \x01(\x0b\x32\x18.agentmesh.v1.ModelArmorH\x00\x12)\n\x08\x65xt_proc\x18\x02 \x01(\x0b\x32\x15.agentmesh.v1.ExtProcH\x00\x42\x06\n\x04kind\"\x91\x01\n\nModelArmor\x12\x10\n\x08template\x18\x01 \x01(\t\x12\x32\n\x08response\x18\x02 \x01(\x0e\x32 .agentmesh.v1.ResponseInspection\x12\x11\n\tfail_open\x18\x03 \x01(\x08\x12*\n\x07timeout\x18\x04 \x01(\x0b\x32\x19.google.protobuf.Duration\"\x88\x02\n\x07\x45xtProc\x12\x0e\n\x06target\x18\x01 \x01(\t\x12\n\n\x02\x63\x61\x18\x02 \x01(\t\x12\x1a\n\x12\x63lient_certificate\x18\x03 \x01(\t\x12<\n\x0fprocessing_mode\x18\x04 \x01(\x0b\x32#.agentmesh.v1.ExtProcProcessingMode\x12\x1b\n\x13\x61llow_mode_override\x18\x05 \x01(\x08\x12\x32\n\x0fmessage_timeout\x18\x06 \x01(\x0b\x32\x19.google.protobuf.Duration\x12\x1a\n\x12\x66\x61ilure_mode_allow\x18\x07 \x01(\x08\x12\x1a\n\x12max_buffered_bytes\x18\x08 \x01(\r\"\xff\x04\n\x15\x45xtProcProcessingMode\x12K\n\x13request_header_mode\x18\x01 \x01(\x0e\x32..agentmesh.v1.ExtProcProcessingMode.HeaderMode\x12L\n\x14response_header_mode\x18\x02 \x01(\x0e\x32..agentmesh.v1.ExtProcProcessingMode.HeaderMode\x12G\n\x11request_body_mode\x18\x03 \x01(\x0e\x32,.agentmesh.v1.ExtProcProcessingMode.BodyMode\x12H\n\x12response_body_mode\x18\x04 \x01(\x0e\x32,.agentmesh.v1.ExtProcProcessingMode.BodyMode\x12L\n\x14request_trailer_mode\x18\x05 \x01(\x0e\x32..agentmesh.v1.ExtProcProcessingMode.HeaderMode\x12M\n\x15response_trailer_mode\x18\x06 \x01(\x0e\x32..agentmesh.v1.ExtProcProcessingMode.HeaderMode\"9\n\nHeaderMode\x12\x17\n\x13HEADER_MODE_DEFAULT\x10\x00\x12\x08\n\x04SEND\x10\x01\x12\x08\n\x04SKIP\x10\x02\"`\n\x08\x42odyMode\x12\x08\n\x04NONE\x10\x00\x12\x0c\n\x08STREAMED\x10\x01\x12\x0c\n\x08\x42UFFERED\x10\x02\x12\x14\n\x10\x42UFFERED_PARTIAL\x10\x03\x12\x18\n\x14\x46ULL_DUPLEX_STREAMED\x10\x04\"\xe1\x01\n\x10\x43redentialBroker\x12\x17\n\rstatic_secret\x18\x01 \x01(\tH\x00\x12\x37\n\x0foidc_federation\x18\x02 \x01(\x0b\x32\x1c.agentmesh.v1.OIDCFederationH\x00\x12\x36\n\x0f\x61ws_assume_role\x18\x03 \x01(\x0b\x32\x1b.agentmesh.v1.AWSAssumeRoleH\x00\x12;\n\x11platform_identity\x18\x04 \x01(\x0b\x32\x1e.agentmesh.v1.PlatformIdentityH\x00\x42\x06\n\x04kind\"_\n\x0eOIDCFederation\x12\x16\n\x0etoken_endpoint\x18\x01 \x01(\t\x12\x10\n\x08\x61udience\x18\x02 \x01(\t\x12\x13\n\x0bimpersonate\x18\x03 \x01(\t\x12\x0e\n\x06scopes\x18\x04 \x03(\t\"9\n\rAWSAssumeRole\x12\x10\n\x08role_arn\x18\x01 \x01(\t\x12\x16\n\x0esession_policy\x18\x02 \x01(\t\"\"\n\x10PlatformIdentity\x12\x0e\n\x06scopes\x18\x01 \x03(\t\".\n\rPolicyBinding\x12\x0c\n\x04role\x18\x01 \x01(\t\x12\x0f\n\x07members\x18\x02 \x03(\t\"\x97\x01\n\x0cPolicyConfig\x12\'\n\x05roles\x18\x01 \x03(\x0b\x32\x18.agentmesh.v1.PolicyRole\x12-\n\x08\x62indings\x18\x02 \x03(\x0b\x32\x1b.agentmesh.v1.PolicyBinding\x12/\n\x06\x65gress\x18\x03 \x03(\x0b\x32\x1f.agentmesh.v1.EgressDestination\"\x18\n\x16PolicyConfigGetRequest\"0\n\x17PolicyConfigGetResponse\x12\x15\n\rdatalog_rules\x18\x01 \x03(\t\"<\n\x1aPolicyConfigUpdateResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\r\n\x05\x65rror\x18\x02 \x01(\t\"\x1a\n\x18\x45gressAssignmentsRequest\"L\n\x19\x45gressAssignmentsResponse\x12/\n\x06\x65gress\x18\x01 \x03(\x0b\x32\x1f.agentmesh.v1.EgressDestination\"f\n\x0cKeysResponse\x12\x13\n\x0bpublic_keys\x18\x01 \x03(\x0c\x12-\n\tsign_time\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x12\n\nsignatures\x18\x03 \x03(\x0c\"k\n\x13TokenRefreshRequest\x12\x1b\n\x13\x63hallenge_signature\x18\x01 \x01(\x0c\x12\x19\n\x11\x63hallenge_unix_ms\x18\x02 \x01(\x03\x12\x0f\n\x07peer_id\x18\x03 \x01(\t\x12\x0b\n\x03jwt\x18\x04 \x01(\t\"u\n\x14TokenRefreshResponse\x12\x15\n\rbiscuit_token\x18\x01 \x01(\x0c\x12/\n\x0b\x65xpire_time\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x15\n\rerror_message\x18\x03 \x01(\t\"@\n\x11NodeCatalogReport\x12+\n\x08services\x18\x01 \x03(\x0b\x32\x19.agentmesh.v1.ServiceInfo\"%\n\x12TokenRevokeRequest\x12\x0f\n\x07peer_id\x18\x01 \x01(\t\"5\n\x13TokenRevokeResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\r\n\x05\x65rror\x18\x02 \x01(\t\"\x96\x01\n\x1b\x42ootstrapTokenCreateRequest\x12\x0c\n\x04role\x18\x01 \x01(\t\x12\x10\n\x08owner_id\x18\x02 \x01(\t\x12\x11\n\tttl_hours\x18\x03 \x01(\x05\x12\x12\n\nmax_usages\x18\x04 \x01(\x05\x12\x13\n\x0b\x64\x65scription\x18\x05 \x01(\t\x12\x1b\n\x13\x61utonomous_recovery\x18\x06 \x01(\x08\"\x8a\x01\n\x1c\x42ootstrapTokenCreateResponse\x12\n\n\x02id\x18\x01 \x01(\t\x12\r\n\x05token\x18\x02 \x01(\t\x12\x0c\n\x04role\x18\x03 \x01(\t\x12\x10\n\x08owner_id\x18\x04 \x01(\t\x12/\n\x0b\x65xpire_time\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"\xab\x02\n\x0e\x42ootstrapToken\x12\n\n\x02id\x18\x01 \x01(\t\x12\x0c\n\x04role\x18\x02 \x01(\t\x12\x10\n\x08owner_id\x18\x03 \x01(\t\x12\x12\n\nmax_usages\x18\x04 \x01(\x05\x12\x14\n\x0cusages_count\x18\x05 \x01(\x05\x12\x13\n\x0b\x64\x65scription\x18\x06 \x01(\t\x12/\n\x0b\x63reate_time\x18\x07 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12/\n\x0b\x65xpire_time\x18\x08 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12/\n\x0brevoke_time\x18\t \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x1b\n\x13\x61utonomous_recovery\x18\n \x01(\x08\"J\n\x1a\x42ootstrapTokenListResponse\x12,\n\x06tokens\x18\x01 \x03(\x0b\x32\x1c.agentmesh.v1.BootstrapToken\"\xd6\x02\n\x11\x45nrollmentRequest\x12\n\n\x02id\x18\x01 \x01(\t\x12\x0f\n\x07peer_id\x18\x02 \x01(\t\x12\x10\n\x08token_id\x18\x03 \x01(\t\x12.\n\x06status\x18\x04 \x01(\x0e\x32\x1e.agentmesh.v1.EnrollmentStatus\x12;\n\x06labels\x18\x05 \x03(\x0b\x32+.agentmesh.v1.EnrollmentRequest.LabelsEntry\x12/\n\x0b\x63reate_time\x18\x06 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x30\n\x0cresolve_time\x18\x07 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x13\n\x0bresolved_by\x18\x08 \x01(\t\x1a-\n\x0bLabelsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"R\n\x1d\x45nrollmentRequestListResponse\x12\x31\n\x08requests\x18\x01 \x03(\x0b\x32\x1f.agentmesh.v1.EnrollmentRequest\"p\n\x04User\x12\n\n\x02id\x18\x01 \x01(\t\x12\x0e\n\x06issuer\x18\x02 \x01(\t\x12\r\n\x05\x65mail\x18\x03 \x01(\t\x12\x0c\n\x04role\x18\x04 \x01(\t\x12/\n\x0b\x63reate_time\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"\xe3\x02\n\x0c\x45nrolledNode\x12\x0f\n\x07peer_id\x18\x01 \x01(\t\x12\x0c\n\x04role\x18\x02 \x01(\t\x12\x17\n\x0f\x65nrollment_type\x18\x03 \x01(\t\x12\x13\n\x0b\x63laims_json\x18\x04 \x01(\t\x12\x10\n\x08owner_id\x18\x05 \x01(\t\x12\x36\n\x06labels\x18\x06 \x03(\x0b\x32&.agentmesh.v1.EnrolledNode.LabelsEntry\x12/\n\x0b\x65nroll_time\x18\x07 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12/\n\x0b\x65xpire_time\x18\x08 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x0e\n\x06\x62\x61nned\x18\t \x01(\x08\x12\x1b\n\x13\x61utonomous_recovery\x18\n \x01(\x08\x1a-\n\x0bLabelsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xc4\x01\n\x0bRouterLease\x12\x0f\n\x07peer_id\x18\x01 \x01(\t\x12\x11\n\taddresses\x18\x02 \x03(\t\x12\x35\n\x11last_renewal_time\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12/\n\x0b\x65xpire_time\x18\x04 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x17\n\x0f\x63onnected_peers\x18\x05 \x03(\t\x12\x10\n\x08\x64ht_size\x18\x06 \x01(\x05\"l\n\x0cNodeServices\x12+\n\x08services\x18\x01 \x03(\x0b\x32\x19.agentmesh.v1.ServiceInfo\x12/\n\x0breport_time\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"\xdb\x03\n\x13\x41\x64minStatusResponse\x12!\n\x05users\x18\x01 \x03(\x0b\x32\x12.agentmesh.v1.User\x12\x31\n\x0e\x61\x63tive_routers\x18\x02 \x03(\x0b\x32\x19.agentmesh.v1.RouterLease\x12\x32\n\x0e\x65nrolled_nodes\x18\x03 \x03(\x0b\x32\x1a.agentmesh.v1.EnrolledNode\x12<\n\x13\x65nrollment_requests\x18\x04 \x03(\x0b\x32\x1f.agentmesh.v1.EnrollmentRequest\x12\x36\n\x10\x62ootstrap_tokens\x18\x05 \x03(\x0b\x32\x1c.agentmesh.v1.BootstrapToken\x12*\n\x06policy\x18\x06 \x01(\x0b\x32\x1a.agentmesh.v1.PolicyConfig\x12H\n\x0cnode_catalog\x18\x07 \x03(\x0b\x32\x32.agentmesh.v1.AdminStatusResponse.NodeCatalogEntry\x1aN\n\x10NodeCatalogEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12)\n\x05value\x18\x02 \x01(\x0b\x32\x1a.agentmesh.v1.NodeServices:\x02\x38\x01\"\x81\x02\n\x12UserStatusResponse\x12 \n\x04user\x18\x01 \x01(\x0b\x32\x12.agentmesh.v1.User\x12\x32\n\x0e\x65nrolled_nodes\x18\x02 \x03(\x0b\x32\x1a.agentmesh.v1.EnrolledNode\x12\x36\n\x10\x62ootstrap_tokens\x18\x03 \x03(\x0b\x32\x1c.agentmesh.v1.BootstrapToken\x12\x31\n\x0e\x61\x63tive_routers\x18\x04 \x03(\x0b\x32\x19.agentmesh.v1.RouterLease\x12*\n\x06policy\x18\x05 \x01(\x0b\x32\x1a.agentmesh.v1.PolicyConfig\"\xe4\x01\n\x18IdentityEvidenceResponse\x12\x0f\n\x07peer_id\x18\x01 \x01(\t\x12\x0f\n\x07\x62iscuit\x18\x02 \x01(\x0c\x12\x37\n\x13\x62iscuit_expire_time\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x19\n\x11\x63ontrol_plane_url\x18\x04 \x01(\t\x12\"\n\x1atrusted_control_plane_keys\x18\x05 \x03(\x0c\x12.\n\ncheck_time\x18\x06 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"\xc6\x02\n\x14PeerEvidenceResponse\x12\x0f\n\x07peer_id\x18\x01 \x01(\t\x12\x0f\n\x07\x62iscuit\x18\x02 \x01(\x0c\x12\x15\n\rverifying_key\x18\x03 \x01(\x0c\x12\r\n\x05roles\x18\x04 \x03(\t\x12>\n\x06labels\x18\x05 \x03(\x0b\x32..agentmesh.v1.PeerEvidenceResponse.LabelsEntry\x12/\n\x0b\x65xpire_time\x18\x06 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x16\n\x0erevocation_ids\x18\x07 \x03(\t\x12.\n\ncheck_time\x18\x08 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x1a-\n\x0bLabelsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\x8c\x02\n\x10MemberCredential\x12\x19\n\x11\x63ontrol_plane_url\x18\x01 \x01(\t\x12\x0f\n\x07\x62iscuit\x18\x02 \x01(\x0c\x12/\n\x0b\x65xpire_time\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x35\n\x0ctrusted_keys\x18\x04 \x03(\x0b\x32\x1f.agentmesh.v1.TrustedSigningKey\x12\x19\n\x11issued_under_keys\x18\x05 \x03(\x0c\x12\x18\n\x10router_addresses\x18\x06 \x03(\t\x12/\n\x0coidc_session\x18\x07 \x01(\x0b\x32\x19.agentmesh.v1.OIDCSession\"Y\n\x11TrustedSigningKey\x12\x12\n\npublic_key\x18\x01 \x01(\x0c\x12\x30\n\x0creceive_time\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"Y\n\x0bOIDCSession\x12\x0e\n\x06issuer\x18\x01 \x01(\t\x12\x11\n\tclient_id\x18\x02 \x01(\t\x12\x10\n\x08\x61udience\x18\x03 \x01(\t\x12\x15\n\rrefresh_token\x18\x04 \x01(\t\"\x93\x01\n\x15TaskAuthorizationRule\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x14\n\x0c\x64isplay_name\x18\x02 \x01(\t\x12%\n\x05rules\x18\x03 \x03(\x0b\x32\x16.agentmesh.v1.TaskRule\x12/\n\x0b\x65xpire_time\x18\x04 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"\x84\x01\n\x08TaskRule\x12\x13\n\x0b\x64\x65scription\x18\x01 \x01(\t\x12\x18\n\x10\x61llowed_services\x18\x02 \x03(\t\x12.\n\toperation\x18\x03 \x01(\x0b\x32\x1b.agentmesh.v1.TaskOperation\x12\x19\n\x11\x61llowed_resources\x18\x04 \x03(\t\"s\n\rTaskOperation\x12\x15\n\rallowed_tools\x18\x01 \x03(\t\x12\x17\n\x0f\x61llowed_methods\x18\x02 \x03(\t\x12\x15\n\rallowed_paths\x18\x03 \x03(\t\x12\x1b\n\x13\x61llowed_permissions\x18\x04 \x03(\t\"\xab\x01\n\x14TokenExchangeRequest\x12\x15\n\rsubject_token\x18\x01 \x01(\t\x12\x36\n\ttask_rule\x18\x02 \x01(\x0b\x32#.agentmesh.v1.TaskAuthorizationRule\x12\x0c\n\x04seal\x18\x03 \x01(\x08\x12\x19\n\x11\x63hallenge_unix_ms\x18\x04 \x01(\x03\x12\x1b\n\x13\x63hallenge_signature\x18\x05 \x01(\x0c\"\x7f\n\x15TokenExchangeResponse\x12\x15\n\rbiscuit_token\x18\x01 \x01(\x0c\x12/\n\x0b\x65xpire_time\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\r\n\x05roles\x18\x03 \x03(\t\x12\x0f\n\x07subject\x18\x04 \x01(\t\"\x81\x01\n\x0fSTSTokenRequest\x12\x0f\n\x07\x62iscuit\x18\x01 \x01(\x0c\x12\x13\n\x0b\x64\x65stination\x18\x02 \x01(\t\x12\x10\n\x08\x61udience\x18\x03 \x01(\t\x12\x19\n\x11\x63hallenge_unix_ms\x18\x04 \x01(\x03\x12\x1b\n\x13\x63hallenge_signature\x18\x05 \x01(\x0c\"\x83\x01\n\x10STSTokenResponse\x12\x0b\n\x03jwt\x18\x01 \x01(\t\x12/\n\x0b\x65xpire_time\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x0f\n\x07subject\x18\x03 \x01(\t\x12\r\n\x05roles\x18\x04 \x03(\t\x12\x11\n\ttask_name\x18\x05 \x01(\t\"F\n\x13RevocationsResponse\x12\x16\n\x0erevocation_ids\x18\x01 \x03(\t\x12\x17\n\x0f\x62\x61nned_peer_ids\x18\x02 \x03(\t*\x94\x01\n\x10\x45nrollmentStatus\x12!\n\x1d\x45NROLLMENT_STATUS_UNSPECIFIED\x10\x00\x12\x1d\n\x19\x45NROLLMENT_STATUS_PENDING\x10\x01\x12\x1e\n\x1a\x45NROLLMENT_STATUS_APPROVED\x10\x02\x12\x1e\n\x1a\x45NROLLMENT_STATUS_REJECTED\x10\x03*\x8c\x01\n\x0bServiceType\x12\x1c\n\x18SERVICE_TYPE_UNSPECIFIED\x10\x00\x12\x14\n\x10SERVICE_TYPE_MCP\x10\x01\x12\x1a\n\x16SERVICE_TYPE_INFERENCE\x10\x02\x12\x14\n\x10SERVICE_TYPE_A2A\x10\x03\x12\x17\n\x13SERVICE_TYPE_EGRESS\x10\x04*7\n\nEgressMode\x12\x14\n\x10\x45GRESS_MODE_HTTP\x10\x00\x12\x13\n\x0f\x45GRESS_MODE_TCP\x10\x01*\\\n\x12ResponseInspection\x12 \n\x1cRESPONSE_INSPECTION_BUFFERED\x10\x00\x12$\n RESPONSE_INSPECTION_REQUEST_ONLY\x10\x01\x42!Z\x1fgithub.com/google/agentmesh/apib\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0f\x61gentmesh.proto\x12\x0c\x61gentmesh.v1\x1a\x1egoogle/protobuf/duration.proto\x1a\x1fgoogle/protobuf/timestamp.proto\"4\n\tAuthFrame\x12\x0f\n\x07\x62iscuit\x18\x01 \x01(\x0c\x12\x16\n\x0etarget_service\x18\x02 \x01(\t\"?\n\x0c\x41uthResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\r\n\x05\x65rror\x18\x02 \x01(\t\x12\x0f\n\x07\x62iscuit\x18\x03 \x01(\x0c\"\xb1\x01\n\x0cRouterGoAway\x12\x31\n\x06reason\x18\x01 \x01(\x0e\x32!.agentmesh.v1.RouterGoAway.Reason\x12.\n\x0bretry_after\x18\x02 \x01(\x0b\x32\x19.google.protobuf.Duration\">\n\x06Reason\x12\x16\n\x12REASON_UNSPECIFIED\x10\x00\x12\x0c\n\x08\x44RAINING\x10\x01\x12\x0e\n\nOVERLOADED\x10\x02\"\xdc\x01\n\tMeshEvent\x12*\n\x04type\x18\x01 \x01(\x0e\x32\x1c.agentmesh.v1.MeshEvent.Type\x12\x0f\n\x07peer_id\x18\x02 \x01(\t\x12.\n\nevent_time\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x16\n\x0enew_public_key\x18\x04 \x01(\x0c\x12\x11\n\tsignature\x18\x05 \x01(\x0c\"7\n\x04Type\x12\n\n\x06\x42\x41NNED\x10\x00\x12\x10\n\x0cKEY_ROTATION\x10\x01\x12\x11\n\rPOLICY_UPDATE\x10\x02\"\xf9\x01\n\rEnrollRequest\x12\x0b\n\x03jwt\x18\x01 \x01(\t\x12\x0f\n\x07peer_id\x18\x02 \x01(\t\x12\x12\n\npublic_key\x18\x03 \x01(\x0c\x12\x16\n\x0erequested_role\x18\x04 \x01(\t\x12\x37\n\x06labels\x18\x05 \x03(\x0b\x32\'.agentmesh.v1.EnrollRequest.LabelsEntry\x12\x19\n\x11\x63hallenge_unix_ms\x18\x06 \x01(\x03\x12\x1b\n\x13\x63hallenge_signature\x18\x07 \x01(\x0c\x1a-\n\x0bLabelsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xab\x01\n\x0e\x45nrollResponse\x12\x15\n\rbiscuit_token\x18\x01 \x01(\x0c\x12\x15\n\rerror_message\x18\x02 \x01(\t\x12 \n\x18\x63ontrol_plane_public_key\x18\x03 \x01(\x0c\x12\x18\n\x10router_addresses\x18\x04 \x03(\t\x12/\n\x0b\x65xpire_time\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"\x97\x02\n\x16\x42ootstrapEnrollRequest\x12\x17\n\x0f\x62ootstrap_token\x18\x01 \x01(\t\x12\x0f\n\x07peer_id\x18\x02 \x01(\t\x12\x12\n\npublic_key\x18\x03 \x01(\x0c\x12\x16\n\x0erequested_role\x18\x04 \x01(\t\x12@\n\x06labels\x18\x05 \x03(\x0b\x32\x30.agentmesh.v1.BootstrapEnrollRequest.LabelsEntry\x12\x19\n\x11\x63hallenge_unix_ms\x18\x06 \x01(\x03\x12\x1b\n\x13\x63hallenge_signature\x18\x07 \x01(\x0c\x1a-\n\x0bLabelsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\x83\x02\n\x17\x42ootstrapEnrollResponse\x12.\n\x06status\x18\x01 \x01(\x0e\x32\x1e.agentmesh.v1.EnrollmentStatus\x12\x15\n\rbiscuit_token\x18\x02 \x01(\x0c\x12\x1d\n\x15poll_interval_seconds\x18\x03 \x01(\x05\x12\x15\n\rerror_message\x18\x04 \x01(\t\x12 \n\x18\x63ontrol_plane_public_key\x18\x05 \x01(\x0c\x12\x18\n\x10router_addresses\x18\x06 \x03(\t\x12/\n\x0b\x65xpire_time\x18\x07 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"Y\n\x0bServiceInfo\x12\'\n\x04type\x18\x01 \x01(\x0e\x32\x19.agentmesh.v1.ServiceType\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x03 \x01(\t\"\x81\x01\n\x0e\x43ommandBackend\x12\x0f\n\x07\x63ommand\x18\x01 \x03(\t\x12\x32\n\x03\x65nv\x18\x02 \x03(\x0b\x32%.agentmesh.v1.CommandBackend.EnvEntry\x1a*\n\x08\x45nvEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\x96\x01\n\x16RegisterServiceRequest\x12*\n\x07service\x18\x01 \x01(\x0b\x32\x19.agentmesh.v1.ServiceInfo\x12\x14\n\ntarget_url\x18\x02 \x01(\tH\x00\x12/\n\x07\x63ommand\x18\x03 \x01(\x0b\x32\x1c.agentmesh.v1.CommandBackendH\x00\x42\t\n\x07\x62\x61\x63kend\"i\n\x12\x44iscoveredProvider\x12\x0f\n\x07peer_id\x18\x01 \x01(\t\x12\x17\n\x0flocal_proxy_url\x18\x02 \x01(\t\x12\x10\n\x08srv_name\x18\x03 \x01(\t\x12\x17\n\x0fsrv_description\x18\x04 \x01(\t\"\xbe\x02\n\x0fServiceAnnounce\x12\x0f\n\x07peer_id\x18\x01 \x01(\t\x12\'\n\x04type\x18\x02 \x01(\x0e\x32\x19.agentmesh.v1.ServiceType\x12\x14\n\x0cservice_name\x18\x03 \x01(\t\x12\x0c\n\x04keys\x18\x04 \x03(\t\x12\x39\n\x06labels\x18\x05 \x03(\x0b\x32).agentmesh.v1.ServiceAnnounce.LabelsEntry\x12\x17\n\x0f\x61\x63tive_requests\x18\x06 \x01(\r\x12\x17\n\x0flatency_ewma_ms\x18\x07 \x01(\x01\x12\x31\n\rannounce_time\x18\x08 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x1a-\n\x0bLabelsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xe8\x01\n\x18\x43ontrolPlaneInfoResponse\x12\x13\n\x0boidc_issuer\x18\x01 \x01(\t\x12\x11\n\tclient_id\x18\x02 \x01(\t\x12\x10\n\x08\x61udience\x18\x03 \x01(\t\x12\x18\n\x10router_addresses\x18\x04 \x03(\t\x12\x17\n\x0f\x62\x61nned_peer_ids\x18\x05 \x03(\t\x12)\n\x07routers\x18\x06 \x03(\x0b\x32\x18.agentmesh.v1.RouterInfo\x12\x34\n\tdirectory\x18\x07 \x01(\x0b\x32!.agentmesh.v1.DirectoryParameters\"\x90\x01\n\x13\x44irectoryParameters\x12\x13\n\x0breplication\x18\x01 \x01(\x05\x12\x35\n\x12reprovide_interval\x18\x02 \x01(\x0b\x32\x19.google.protobuf.Duration\x12-\n\nrecord_ttl\x18\x03 \x01(\x0b\x32\x19.google.protobuf.Duration\"\xc4\x01\n\nRouterInfo\x12\x0f\n\x07peer_id\x18\x01 \x01(\t\x12\x11\n\taddresses\x18\x02 \x03(\t\x12\x34\n\x06labels\x18\x03 \x03(\x0b\x32$.agentmesh.v1.RouterInfo.LabelsEntry\x12\x13\n\x0b\x63onnections\x18\x04 \x01(\x05\x12\x18\n\x10\x63onnection_limit\x18\x05 \x01(\x05\x1a-\n\x0bLabelsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\x83\x02\n\x12RouterLeaseRequest\x12\x0f\n\x07peer_id\x18\x01 \x01(\t\x12\x11\n\taddresses\x18\x02 \x03(\t\x12\x0f\n\x07\x62iscuit\x18\x03 \x01(\x0c\x12\x17\n\x0f\x63onnected_peers\x18\x04 \x03(\t\x12\x10\n\x08\x64ht_size\x18\x05 \x01(\x05\x12\x19\n\x11\x63hallenge_unix_ms\x18\x06 \x01(\x03\x12\x1b\n\x13\x63hallenge_signature\x18\x07 \x01(\x0c\x12&\n\x03ttl\x18\x08 \x01(\x0b\x32\x19.google.protobuf.Duration\x12\x13\n\x0b\x63onnections\x18\t \x01(\x05\x12\x18\n\x10\x63onnection_limit\x18\n \x01(\x05\"f\n\x13RouterLeaseResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\r\n\x05\x65rror\x18\x02 \x01(\t\x12/\n\x0b\x65xpire_time\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"\xa4\x01\n\nPolicyRole\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x17\n\x0f\x61llowed_targets\x18\x02 \x03(\t\x12\x18\n\x10\x61llowed_services\x18\x03 \x03(\t\x12\x16\n\x0e\x63ustom_datalog\x18\x04 \x03(\t\x12\x16\n\x0e\x61llowed_labels\x18\x05 \x03(\t\x12%\n\x04http\x18\x06 \x03(\x0b\x32\x17.agentmesh.v1.HTTPGrant\"<\n\tHTTPGrant\x12\x0f\n\x07service\x18\x01 \x01(\t\x12\x0f\n\x07methods\x18\x02 \x03(\t\x12\r\n\x05paths\x18\x03 \x03(\t\"\xa1\x02\n\x11\x45gressDestination\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x12\n\ntarget_url\x18\x02 \x01(\t\x12\x12\n\ncredential\x18\x03 \x01(\t\x12\x11\n\tserved_by\x18\x04 \x03(\t\x12.\n\x06\x62roker\x18\x05 \x01(\x0b\x32\x1e.agentmesh.v1.CredentialBroker\x12,\n\ninspection\x18\x06 \x01(\x0b\x32\x18.agentmesh.v1.Inspection\x12&\n\x04mode\x18\x07 \x01(\x0e\x32\x18.agentmesh.v1.EgressMode\x12\r\n\x05ports\x18\x08 \x03(\r\x12\x15\n\rpreserve_host\x18\t \x01(\x08\x12\x17\n\x0f\x66orward_context\x18\n \x01(\x08\"9\n\nInspection\x12+\n\ninspectors\x18\x01 \x03(\x0b\x32\x17.agentmesh.v1.Inspector\"o\n\tInspector\x12/\n\x0bmodel_armor\x18\x01 \x01(\x0b\x32\x18.agentmesh.v1.ModelArmorH\x00\x12)\n\x08\x65xt_proc\x18\x02 \x01(\x0b\x32\x15.agentmesh.v1.ExtProcH\x00\x42\x06\n\x04kind\"\x91\x01\n\nModelArmor\x12\x10\n\x08template\x18\x01 \x01(\t\x12\x32\n\x08response\x18\x02 \x01(\x0e\x32 .agentmesh.v1.ResponseInspection\x12\x11\n\tfail_open\x18\x03 \x01(\x08\x12*\n\x07timeout\x18\x04 \x01(\x0b\x32\x19.google.protobuf.Duration\"\x88\x02\n\x07\x45xtProc\x12\x0e\n\x06target\x18\x01 \x01(\t\x12\n\n\x02\x63\x61\x18\x02 \x01(\t\x12\x1a\n\x12\x63lient_certificate\x18\x03 \x01(\t\x12<\n\x0fprocessing_mode\x18\x04 \x01(\x0b\x32#.agentmesh.v1.ExtProcProcessingMode\x12\x1b\n\x13\x61llow_mode_override\x18\x05 \x01(\x08\x12\x32\n\x0fmessage_timeout\x18\x06 \x01(\x0b\x32\x19.google.protobuf.Duration\x12\x1a\n\x12\x66\x61ilure_mode_allow\x18\x07 \x01(\x08\x12\x1a\n\x12max_buffered_bytes\x18\x08 \x01(\r\"\xff\x04\n\x15\x45xtProcProcessingMode\x12K\n\x13request_header_mode\x18\x01 \x01(\x0e\x32..agentmesh.v1.ExtProcProcessingMode.HeaderMode\x12L\n\x14response_header_mode\x18\x02 \x01(\x0e\x32..agentmesh.v1.ExtProcProcessingMode.HeaderMode\x12G\n\x11request_body_mode\x18\x03 \x01(\x0e\x32,.agentmesh.v1.ExtProcProcessingMode.BodyMode\x12H\n\x12response_body_mode\x18\x04 \x01(\x0e\x32,.agentmesh.v1.ExtProcProcessingMode.BodyMode\x12L\n\x14request_trailer_mode\x18\x05 \x01(\x0e\x32..agentmesh.v1.ExtProcProcessingMode.HeaderMode\x12M\n\x15response_trailer_mode\x18\x06 \x01(\x0e\x32..agentmesh.v1.ExtProcProcessingMode.HeaderMode\"9\n\nHeaderMode\x12\x17\n\x13HEADER_MODE_DEFAULT\x10\x00\x12\x08\n\x04SEND\x10\x01\x12\x08\n\x04SKIP\x10\x02\"`\n\x08\x42odyMode\x12\x08\n\x04NONE\x10\x00\x12\x0c\n\x08STREAMED\x10\x01\x12\x0c\n\x08\x42UFFERED\x10\x02\x12\x14\n\x10\x42UFFERED_PARTIAL\x10\x03\x12\x18\n\x14\x46ULL_DUPLEX_STREAMED\x10\x04\"\xe1\x01\n\x10\x43redentialBroker\x12\x17\n\rstatic_secret\x18\x01 \x01(\tH\x00\x12\x37\n\x0foidc_federation\x18\x02 \x01(\x0b\x32\x1c.agentmesh.v1.OIDCFederationH\x00\x12\x36\n\x0f\x61ws_assume_role\x18\x03 \x01(\x0b\x32\x1b.agentmesh.v1.AWSAssumeRoleH\x00\x12;\n\x11platform_identity\x18\x04 \x01(\x0b\x32\x1e.agentmesh.v1.PlatformIdentityH\x00\x42\x06\n\x04kind\"_\n\x0eOIDCFederation\x12\x16\n\x0etoken_endpoint\x18\x01 \x01(\t\x12\x10\n\x08\x61udience\x18\x02 \x01(\t\x12\x13\n\x0bimpersonate\x18\x03 \x01(\t\x12\x0e\n\x06scopes\x18\x04 \x03(\t\"9\n\rAWSAssumeRole\x12\x10\n\x08role_arn\x18\x01 \x01(\t\x12\x16\n\x0esession_policy\x18\x02 \x01(\t\"\"\n\x10PlatformIdentity\x12\x0e\n\x06scopes\x18\x01 \x03(\t\".\n\rPolicyBinding\x12\x0c\n\x04role\x18\x01 \x01(\t\x12\x0f\n\x07members\x18\x02 \x03(\t\"\x97\x01\n\x0cPolicyConfig\x12\'\n\x05roles\x18\x01 \x03(\x0b\x32\x18.agentmesh.v1.PolicyRole\x12-\n\x08\x62indings\x18\x02 \x03(\x0b\x32\x1b.agentmesh.v1.PolicyBinding\x12/\n\x06\x65gress\x18\x03 \x03(\x0b\x32\x1f.agentmesh.v1.EgressDestination\"\x18\n\x16PolicyConfigGetRequest\"0\n\x17PolicyConfigGetResponse\x12\x15\n\rdatalog_rules\x18\x01 \x03(\t\"<\n\x1aPolicyConfigUpdateResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\r\n\x05\x65rror\x18\x02 \x01(\t\"\x1a\n\x18\x45gressAssignmentsRequest\"L\n\x19\x45gressAssignmentsResponse\x12/\n\x06\x65gress\x18\x01 \x03(\x0b\x32\x1f.agentmesh.v1.EgressDestination\"f\n\x0cKeysResponse\x12\x13\n\x0bpublic_keys\x18\x01 \x03(\x0c\x12-\n\tsign_time\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x12\n\nsignatures\x18\x03 \x03(\x0c\"k\n\x13TokenRefreshRequest\x12\x1b\n\x13\x63hallenge_signature\x18\x01 \x01(\x0c\x12\x19\n\x11\x63hallenge_unix_ms\x18\x02 \x01(\x03\x12\x0f\n\x07peer_id\x18\x03 \x01(\t\x12\x0b\n\x03jwt\x18\x04 \x01(\t\"u\n\x14TokenRefreshResponse\x12\x15\n\rbiscuit_token\x18\x01 \x01(\x0c\x12/\n\x0b\x65xpire_time\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x15\n\rerror_message\x18\x03 \x01(\t\"@\n\x11NodeCatalogReport\x12+\n\x08services\x18\x01 \x03(\x0b\x32\x19.agentmesh.v1.ServiceInfo\"%\n\x12TokenRevokeRequest\x12\x0f\n\x07peer_id\x18\x01 \x01(\t\"5\n\x13TokenRevokeResponse\x12\x0f\n\x07success\x18\x01 \x01(\x08\x12\r\n\x05\x65rror\x18\x02 \x01(\t\"\x96\x01\n\x1b\x42ootstrapTokenCreateRequest\x12\x0c\n\x04role\x18\x01 \x01(\t\x12\x10\n\x08owner_id\x18\x02 \x01(\t\x12\x11\n\tttl_hours\x18\x03 \x01(\x05\x12\x12\n\nmax_usages\x18\x04 \x01(\x05\x12\x13\n\x0b\x64\x65scription\x18\x05 \x01(\t\x12\x1b\n\x13\x61utonomous_recovery\x18\x06 \x01(\x08\"\x8a\x01\n\x1c\x42ootstrapTokenCreateResponse\x12\n\n\x02id\x18\x01 \x01(\t\x12\r\n\x05token\x18\x02 \x01(\t\x12\x0c\n\x04role\x18\x03 \x01(\t\x12\x10\n\x08owner_id\x18\x04 \x01(\t\x12/\n\x0b\x65xpire_time\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"\xab\x02\n\x0e\x42ootstrapToken\x12\n\n\x02id\x18\x01 \x01(\t\x12\x0c\n\x04role\x18\x02 \x01(\t\x12\x10\n\x08owner_id\x18\x03 \x01(\t\x12\x12\n\nmax_usages\x18\x04 \x01(\x05\x12\x14\n\x0cusages_count\x18\x05 \x01(\x05\x12\x13\n\x0b\x64\x65scription\x18\x06 \x01(\t\x12/\n\x0b\x63reate_time\x18\x07 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12/\n\x0b\x65xpire_time\x18\x08 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12/\n\x0brevoke_time\x18\t \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x1b\n\x13\x61utonomous_recovery\x18\n \x01(\x08\"J\n\x1a\x42ootstrapTokenListResponse\x12,\n\x06tokens\x18\x01 \x03(\x0b\x32\x1c.agentmesh.v1.BootstrapToken\"\xd6\x02\n\x11\x45nrollmentRequest\x12\n\n\x02id\x18\x01 \x01(\t\x12\x0f\n\x07peer_id\x18\x02 \x01(\t\x12\x10\n\x08token_id\x18\x03 \x01(\t\x12.\n\x06status\x18\x04 \x01(\x0e\x32\x1e.agentmesh.v1.EnrollmentStatus\x12;\n\x06labels\x18\x05 \x03(\x0b\x32+.agentmesh.v1.EnrollmentRequest.LabelsEntry\x12/\n\x0b\x63reate_time\x18\x06 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x30\n\x0cresolve_time\x18\x07 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x13\n\x0bresolved_by\x18\x08 \x01(\t\x1a-\n\x0bLabelsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"R\n\x1d\x45nrollmentRequestListResponse\x12\x31\n\x08requests\x18\x01 \x03(\x0b\x32\x1f.agentmesh.v1.EnrollmentRequest\"p\n\x04User\x12\n\n\x02id\x18\x01 \x01(\t\x12\x0e\n\x06issuer\x18\x02 \x01(\t\x12\r\n\x05\x65mail\x18\x03 \x01(\t\x12\x0c\n\x04role\x18\x04 \x01(\t\x12/\n\x0b\x63reate_time\x18\x05 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"\xe3\x02\n\x0c\x45nrolledNode\x12\x0f\n\x07peer_id\x18\x01 \x01(\t\x12\x0c\n\x04role\x18\x02 \x01(\t\x12\x17\n\x0f\x65nrollment_type\x18\x03 \x01(\t\x12\x13\n\x0b\x63laims_json\x18\x04 \x01(\t\x12\x10\n\x08owner_id\x18\x05 \x01(\t\x12\x36\n\x06labels\x18\x06 \x03(\x0b\x32&.agentmesh.v1.EnrolledNode.LabelsEntry\x12/\n\x0b\x65nroll_time\x18\x07 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12/\n\x0b\x65xpire_time\x18\x08 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x0e\n\x06\x62\x61nned\x18\t \x01(\x08\x12\x1b\n\x13\x61utonomous_recovery\x18\n \x01(\x08\x1a-\n\x0bLabelsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xc4\x01\n\x0bRouterLease\x12\x0f\n\x07peer_id\x18\x01 \x01(\t\x12\x11\n\taddresses\x18\x02 \x03(\t\x12\x35\n\x11last_renewal_time\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12/\n\x0b\x65xpire_time\x18\x04 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x17\n\x0f\x63onnected_peers\x18\x05 \x03(\t\x12\x10\n\x08\x64ht_size\x18\x06 \x01(\x05\"l\n\x0cNodeServices\x12+\n\x08services\x18\x01 \x03(\x0b\x32\x19.agentmesh.v1.ServiceInfo\x12/\n\x0breport_time\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"\xdb\x03\n\x13\x41\x64minStatusResponse\x12!\n\x05users\x18\x01 \x03(\x0b\x32\x12.agentmesh.v1.User\x12\x31\n\x0e\x61\x63tive_routers\x18\x02 \x03(\x0b\x32\x19.agentmesh.v1.RouterLease\x12\x32\n\x0e\x65nrolled_nodes\x18\x03 \x03(\x0b\x32\x1a.agentmesh.v1.EnrolledNode\x12<\n\x13\x65nrollment_requests\x18\x04 \x03(\x0b\x32\x1f.agentmesh.v1.EnrollmentRequest\x12\x36\n\x10\x62ootstrap_tokens\x18\x05 \x03(\x0b\x32\x1c.agentmesh.v1.BootstrapToken\x12*\n\x06policy\x18\x06 \x01(\x0b\x32\x1a.agentmesh.v1.PolicyConfig\x12H\n\x0cnode_catalog\x18\x07 \x03(\x0b\x32\x32.agentmesh.v1.AdminStatusResponse.NodeCatalogEntry\x1aN\n\x10NodeCatalogEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12)\n\x05value\x18\x02 \x01(\x0b\x32\x1a.agentmesh.v1.NodeServices:\x02\x38\x01\"\x81\x02\n\x12UserStatusResponse\x12 \n\x04user\x18\x01 \x01(\x0b\x32\x12.agentmesh.v1.User\x12\x32\n\x0e\x65nrolled_nodes\x18\x02 \x03(\x0b\x32\x1a.agentmesh.v1.EnrolledNode\x12\x36\n\x10\x62ootstrap_tokens\x18\x03 \x03(\x0b\x32\x1c.agentmesh.v1.BootstrapToken\x12\x31\n\x0e\x61\x63tive_routers\x18\x04 \x03(\x0b\x32\x19.agentmesh.v1.RouterLease\x12*\n\x06policy\x18\x05 \x01(\x0b\x32\x1a.agentmesh.v1.PolicyConfig\"\xe4\x01\n\x18IdentityEvidenceResponse\x12\x0f\n\x07peer_id\x18\x01 \x01(\t\x12\x0f\n\x07\x62iscuit\x18\x02 \x01(\x0c\x12\x37\n\x13\x62iscuit_expire_time\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x19\n\x11\x63ontrol_plane_url\x18\x04 \x01(\t\x12\"\n\x1atrusted_control_plane_keys\x18\x05 \x03(\x0c\x12.\n\ncheck_time\x18\x06 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"\xc6\x02\n\x14PeerEvidenceResponse\x12\x0f\n\x07peer_id\x18\x01 \x01(\t\x12\x0f\n\x07\x62iscuit\x18\x02 \x01(\x0c\x12\x15\n\rverifying_key\x18\x03 \x01(\x0c\x12\r\n\x05roles\x18\x04 \x03(\t\x12>\n\x06labels\x18\x05 \x03(\x0b\x32..agentmesh.v1.PeerEvidenceResponse.LabelsEntry\x12/\n\x0b\x65xpire_time\x18\x06 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x16\n\x0erevocation_ids\x18\x07 \x03(\t\x12.\n\ncheck_time\x18\x08 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x1a-\n\x0bLabelsEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\x8c\x02\n\x10MemberCredential\x12\x19\n\x11\x63ontrol_plane_url\x18\x01 \x01(\t\x12\x0f\n\x07\x62iscuit\x18\x02 \x01(\x0c\x12/\n\x0b\x65xpire_time\x18\x03 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x35\n\x0ctrusted_keys\x18\x04 \x03(\x0b\x32\x1f.agentmesh.v1.TrustedSigningKey\x12\x19\n\x11issued_under_keys\x18\x05 \x03(\x0c\x12\x18\n\x10router_addresses\x18\x06 \x03(\t\x12/\n\x0coidc_session\x18\x07 \x01(\x0b\x32\x19.agentmesh.v1.OIDCSession\"Y\n\x11TrustedSigningKey\x12\x12\n\npublic_key\x18\x01 \x01(\x0c\x12\x30\n\x0creceive_time\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"Y\n\x0bOIDCSession\x12\x0e\n\x06issuer\x18\x01 \x01(\t\x12\x11\n\tclient_id\x18\x02 \x01(\t\x12\x10\n\x08\x61udience\x18\x03 \x01(\t\x12\x15\n\rrefresh_token\x18\x04 \x01(\t\"\x93\x01\n\x15TaskAuthorizationRule\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x14\n\x0c\x64isplay_name\x18\x02 \x01(\t\x12%\n\x05rules\x18\x03 \x03(\x0b\x32\x16.agentmesh.v1.TaskRule\x12/\n\x0b\x65xpire_time\x18\x04 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\"\x84\x01\n\x08TaskRule\x12\x13\n\x0b\x64\x65scription\x18\x01 \x01(\t\x12\x18\n\x10\x61llowed_services\x18\x02 \x03(\t\x12.\n\toperation\x18\x03 \x01(\x0b\x32\x1b.agentmesh.v1.TaskOperation\x12\x19\n\x11\x61llowed_resources\x18\x04 \x03(\t\"s\n\rTaskOperation\x12\x15\n\rallowed_tools\x18\x01 \x03(\t\x12\x17\n\x0f\x61llowed_methods\x18\x02 \x03(\t\x12\x15\n\rallowed_paths\x18\x03 \x03(\t\x12\x1b\n\x13\x61llowed_permissions\x18\x04 \x03(\t\"\xab\x01\n\x14TokenExchangeRequest\x12\x15\n\rsubject_token\x18\x01 \x01(\t\x12\x36\n\ttask_rule\x18\x02 \x01(\x0b\x32#.agentmesh.v1.TaskAuthorizationRule\x12\x0c\n\x04seal\x18\x03 \x01(\x08\x12\x19\n\x11\x63hallenge_unix_ms\x18\x04 \x01(\x03\x12\x1b\n\x13\x63hallenge_signature\x18\x05 \x01(\x0c\"\x7f\n\x15TokenExchangeResponse\x12\x15\n\rbiscuit_token\x18\x01 \x01(\x0c\x12/\n\x0b\x65xpire_time\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\r\n\x05roles\x18\x03 \x03(\t\x12\x0f\n\x07subject\x18\x04 \x01(\t\"\x81\x01\n\x0fSTSTokenRequest\x12\x0f\n\x07\x62iscuit\x18\x01 \x01(\x0c\x12\x13\n\x0b\x64\x65stination\x18\x02 \x01(\t\x12\x10\n\x08\x61udience\x18\x03 \x01(\t\x12\x19\n\x11\x63hallenge_unix_ms\x18\x04 \x01(\x03\x12\x1b\n\x13\x63hallenge_signature\x18\x05 \x01(\x0c\"\x83\x01\n\x10STSTokenResponse\x12\x0b\n\x03jwt\x18\x01 \x01(\t\x12/\n\x0b\x65xpire_time\x18\x02 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12\x0f\n\x07subject\x18\x03 \x01(\t\x12\r\n\x05roles\x18\x04 \x03(\t\x12\x11\n\ttask_name\x18\x05 \x01(\t\"F\n\x13RevocationsResponse\x12\x16\n\x0erevocation_ids\x18\x01 \x03(\t\x12\x17\n\x0f\x62\x61nned_peer_ids\x18\x02 \x03(\t*\x94\x01\n\x10\x45nrollmentStatus\x12!\n\x1d\x45NROLLMENT_STATUS_UNSPECIFIED\x10\x00\x12\x1d\n\x19\x45NROLLMENT_STATUS_PENDING\x10\x01\x12\x1e\n\x1a\x45NROLLMENT_STATUS_APPROVED\x10\x02\x12\x1e\n\x1a\x45NROLLMENT_STATUS_REJECTED\x10\x03*\x8c\x01\n\x0bServiceType\x12\x1c\n\x18SERVICE_TYPE_UNSPECIFIED\x10\x00\x12\x14\n\x10SERVICE_TYPE_MCP\x10\x01\x12\x1a\n\x16SERVICE_TYPE_INFERENCE\x10\x02\x12\x14\n\x10SERVICE_TYPE_A2A\x10\x03\x12\x17\n\x13SERVICE_TYPE_EGRESS\x10\x04*7\n\nEgressMode\x12\x14\n\x10\x45GRESS_MODE_HTTP\x10\x00\x12\x13\n\x0f\x45GRESS_MODE_TCP\x10\x01*\\\n\x12ResponseInspection\x12 \n\x1cRESPONSE_INSPECTION_BUFFERED\x10\x00\x12$\n RESPONSE_INSPECTION_REQUEST_ONLY\x10\x01\x42!Z\x1fgithub.com/google/agentmesh/apib\x06proto3') _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals()) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'agentmesh_pb2', globals()) @@ -41,14 +41,14 @@ _ADMINSTATUSRESPONSE_NODECATALOGENTRY._serialized_options = b'8\001' _PEEREVIDENCERESPONSE_LABELSENTRY._options = None _PEEREVIDENCERESPONSE_LABELSENTRY._serialized_options = b'8\001' - _ENROLLMENTSTATUS._serialized_start=10925 - _ENROLLMENTSTATUS._serialized_end=11073 - _SERVICETYPE._serialized_start=11076 - _SERVICETYPE._serialized_end=11216 - _EGRESSMODE._serialized_start=11218 - _EGRESSMODE._serialized_end=11273 - _RESPONSEINSPECTION._serialized_start=11275 - _RESPONSEINSPECTION._serialized_end=11367 + _ENROLLMENTSTATUS._serialized_start=11126 + _ENROLLMENTSTATUS._serialized_end=11274 + _SERVICETYPE._serialized_start=11277 + _SERVICETYPE._serialized_end=11417 + _EGRESSMODE._serialized_start=11419 + _EGRESSMODE._serialized_end=11474 + _RESPONSEINSPECTION._serialized_start=11476 + _RESPONSEINSPECTION._serialized_end=11568 _AUTHFRAME._serialized_start=98 _AUTHFRAME._serialized_end=150 _AUTHRESPONSE._serialized_start=152 @@ -88,125 +88,127 @@ _SERVICEANNOUNCE_LABELSENTRY._serialized_start=825 _SERVICEANNOUNCE_LABELSENTRY._serialized_end=870 _CONTROLPLANEINFORESPONSE._serialized_start=2395 - _CONTROLPLANEINFORESPONSE._serialized_end=2573 - _ROUTERINFO._serialized_start=2576 - _ROUTERINFO._serialized_end=2772 + _CONTROLPLANEINFORESPONSE._serialized_end=2627 + _DIRECTORYPARAMETERS._serialized_start=2630 + _DIRECTORYPARAMETERS._serialized_end=2774 + _ROUTERINFO._serialized_start=2777 + _ROUTERINFO._serialized_end=2973 _ROUTERINFO_LABELSENTRY._serialized_start=825 _ROUTERINFO_LABELSENTRY._serialized_end=870 - _ROUTERLEASEREQUEST._serialized_start=2775 - _ROUTERLEASEREQUEST._serialized_end=3034 - _ROUTERLEASERESPONSE._serialized_start=3036 - _ROUTERLEASERESPONSE._serialized_end=3138 - _POLICYROLE._serialized_start=3141 - _POLICYROLE._serialized_end=3305 - _HTTPGRANT._serialized_start=3307 - _HTTPGRANT._serialized_end=3367 - _EGRESSDESTINATION._serialized_start=3370 - _EGRESSDESTINATION._serialized_end=3659 - _INSPECTION._serialized_start=3661 - _INSPECTION._serialized_end=3718 - _INSPECTOR._serialized_start=3720 - _INSPECTOR._serialized_end=3831 - _MODELARMOR._serialized_start=3834 - _MODELARMOR._serialized_end=3979 - _EXTPROC._serialized_start=3982 - _EXTPROC._serialized_end=4246 - _EXTPROCPROCESSINGMODE._serialized_start=4249 - _EXTPROCPROCESSINGMODE._serialized_end=4888 - _EXTPROCPROCESSINGMODE_HEADERMODE._serialized_start=4733 - _EXTPROCPROCESSINGMODE_HEADERMODE._serialized_end=4790 - _EXTPROCPROCESSINGMODE_BODYMODE._serialized_start=4792 - _EXTPROCPROCESSINGMODE_BODYMODE._serialized_end=4888 - _CREDENTIALBROKER._serialized_start=4891 - _CREDENTIALBROKER._serialized_end=5116 - _OIDCFEDERATION._serialized_start=5118 - _OIDCFEDERATION._serialized_end=5213 - _AWSASSUMEROLE._serialized_start=5215 - _AWSASSUMEROLE._serialized_end=5272 - _PLATFORMIDENTITY._serialized_start=5274 - _PLATFORMIDENTITY._serialized_end=5308 - _POLICYBINDING._serialized_start=5310 - _POLICYBINDING._serialized_end=5356 - _POLICYCONFIG._serialized_start=5359 - _POLICYCONFIG._serialized_end=5510 - _POLICYCONFIGGETREQUEST._serialized_start=5512 - _POLICYCONFIGGETREQUEST._serialized_end=5536 - _POLICYCONFIGGETRESPONSE._serialized_start=5538 - _POLICYCONFIGGETRESPONSE._serialized_end=5586 - _POLICYCONFIGUPDATERESPONSE._serialized_start=5588 - _POLICYCONFIGUPDATERESPONSE._serialized_end=5648 - _EGRESSASSIGNMENTSREQUEST._serialized_start=5650 - _EGRESSASSIGNMENTSREQUEST._serialized_end=5676 - _EGRESSASSIGNMENTSRESPONSE._serialized_start=5678 - _EGRESSASSIGNMENTSRESPONSE._serialized_end=5754 - _KEYSRESPONSE._serialized_start=5756 - _KEYSRESPONSE._serialized_end=5858 - _TOKENREFRESHREQUEST._serialized_start=5860 - _TOKENREFRESHREQUEST._serialized_end=5967 - _TOKENREFRESHRESPONSE._serialized_start=5969 - _TOKENREFRESHRESPONSE._serialized_end=6086 - _NODECATALOGREPORT._serialized_start=6088 - _NODECATALOGREPORT._serialized_end=6152 - _TOKENREVOKEREQUEST._serialized_start=6154 - _TOKENREVOKEREQUEST._serialized_end=6191 - _TOKENREVOKERESPONSE._serialized_start=6193 - _TOKENREVOKERESPONSE._serialized_end=6246 - _BOOTSTRAPTOKENCREATEREQUEST._serialized_start=6249 - _BOOTSTRAPTOKENCREATEREQUEST._serialized_end=6399 - _BOOTSTRAPTOKENCREATERESPONSE._serialized_start=6402 - _BOOTSTRAPTOKENCREATERESPONSE._serialized_end=6540 - _BOOTSTRAPTOKEN._serialized_start=6543 - _BOOTSTRAPTOKEN._serialized_end=6842 - _BOOTSTRAPTOKENLISTRESPONSE._serialized_start=6844 - _BOOTSTRAPTOKENLISTRESPONSE._serialized_end=6918 - _ENROLLMENTREQUEST._serialized_start=6921 - _ENROLLMENTREQUEST._serialized_end=7263 + _ROUTERLEASEREQUEST._serialized_start=2976 + _ROUTERLEASEREQUEST._serialized_end=3235 + _ROUTERLEASERESPONSE._serialized_start=3237 + _ROUTERLEASERESPONSE._serialized_end=3339 + _POLICYROLE._serialized_start=3342 + _POLICYROLE._serialized_end=3506 + _HTTPGRANT._serialized_start=3508 + _HTTPGRANT._serialized_end=3568 + _EGRESSDESTINATION._serialized_start=3571 + _EGRESSDESTINATION._serialized_end=3860 + _INSPECTION._serialized_start=3862 + _INSPECTION._serialized_end=3919 + _INSPECTOR._serialized_start=3921 + _INSPECTOR._serialized_end=4032 + _MODELARMOR._serialized_start=4035 + _MODELARMOR._serialized_end=4180 + _EXTPROC._serialized_start=4183 + _EXTPROC._serialized_end=4447 + _EXTPROCPROCESSINGMODE._serialized_start=4450 + _EXTPROCPROCESSINGMODE._serialized_end=5089 + _EXTPROCPROCESSINGMODE_HEADERMODE._serialized_start=4934 + _EXTPROCPROCESSINGMODE_HEADERMODE._serialized_end=4991 + _EXTPROCPROCESSINGMODE_BODYMODE._serialized_start=4993 + _EXTPROCPROCESSINGMODE_BODYMODE._serialized_end=5089 + _CREDENTIALBROKER._serialized_start=5092 + _CREDENTIALBROKER._serialized_end=5317 + _OIDCFEDERATION._serialized_start=5319 + _OIDCFEDERATION._serialized_end=5414 + _AWSASSUMEROLE._serialized_start=5416 + _AWSASSUMEROLE._serialized_end=5473 + _PLATFORMIDENTITY._serialized_start=5475 + _PLATFORMIDENTITY._serialized_end=5509 + _POLICYBINDING._serialized_start=5511 + _POLICYBINDING._serialized_end=5557 + _POLICYCONFIG._serialized_start=5560 + _POLICYCONFIG._serialized_end=5711 + _POLICYCONFIGGETREQUEST._serialized_start=5713 + _POLICYCONFIGGETREQUEST._serialized_end=5737 + _POLICYCONFIGGETRESPONSE._serialized_start=5739 + _POLICYCONFIGGETRESPONSE._serialized_end=5787 + _POLICYCONFIGUPDATERESPONSE._serialized_start=5789 + _POLICYCONFIGUPDATERESPONSE._serialized_end=5849 + _EGRESSASSIGNMENTSREQUEST._serialized_start=5851 + _EGRESSASSIGNMENTSREQUEST._serialized_end=5877 + _EGRESSASSIGNMENTSRESPONSE._serialized_start=5879 + _EGRESSASSIGNMENTSRESPONSE._serialized_end=5955 + _KEYSRESPONSE._serialized_start=5957 + _KEYSRESPONSE._serialized_end=6059 + _TOKENREFRESHREQUEST._serialized_start=6061 + _TOKENREFRESHREQUEST._serialized_end=6168 + _TOKENREFRESHRESPONSE._serialized_start=6170 + _TOKENREFRESHRESPONSE._serialized_end=6287 + _NODECATALOGREPORT._serialized_start=6289 + _NODECATALOGREPORT._serialized_end=6353 + _TOKENREVOKEREQUEST._serialized_start=6355 + _TOKENREVOKEREQUEST._serialized_end=6392 + _TOKENREVOKERESPONSE._serialized_start=6394 + _TOKENREVOKERESPONSE._serialized_end=6447 + _BOOTSTRAPTOKENCREATEREQUEST._serialized_start=6450 + _BOOTSTRAPTOKENCREATEREQUEST._serialized_end=6600 + _BOOTSTRAPTOKENCREATERESPONSE._serialized_start=6603 + _BOOTSTRAPTOKENCREATERESPONSE._serialized_end=6741 + _BOOTSTRAPTOKEN._serialized_start=6744 + _BOOTSTRAPTOKEN._serialized_end=7043 + _BOOTSTRAPTOKENLISTRESPONSE._serialized_start=7045 + _BOOTSTRAPTOKENLISTRESPONSE._serialized_end=7119 + _ENROLLMENTREQUEST._serialized_start=7122 + _ENROLLMENTREQUEST._serialized_end=7464 _ENROLLMENTREQUEST_LABELSENTRY._serialized_start=825 _ENROLLMENTREQUEST_LABELSENTRY._serialized_end=870 - _ENROLLMENTREQUESTLISTRESPONSE._serialized_start=7265 - _ENROLLMENTREQUESTLISTRESPONSE._serialized_end=7347 - _USER._serialized_start=7349 - _USER._serialized_end=7461 - _ENROLLEDNODE._serialized_start=7464 - _ENROLLEDNODE._serialized_end=7819 + _ENROLLMENTREQUESTLISTRESPONSE._serialized_start=7466 + _ENROLLMENTREQUESTLISTRESPONSE._serialized_end=7548 + _USER._serialized_start=7550 + _USER._serialized_end=7662 + _ENROLLEDNODE._serialized_start=7665 + _ENROLLEDNODE._serialized_end=8020 _ENROLLEDNODE_LABELSENTRY._serialized_start=825 _ENROLLEDNODE_LABELSENTRY._serialized_end=870 - _ROUTERLEASE._serialized_start=7822 - _ROUTERLEASE._serialized_end=8018 - _NODESERVICES._serialized_start=8020 - _NODESERVICES._serialized_end=8128 - _ADMINSTATUSRESPONSE._serialized_start=8131 - _ADMINSTATUSRESPONSE._serialized_end=8606 - _ADMINSTATUSRESPONSE_NODECATALOGENTRY._serialized_start=8528 - _ADMINSTATUSRESPONSE_NODECATALOGENTRY._serialized_end=8606 - _USERSTATUSRESPONSE._serialized_start=8609 - _USERSTATUSRESPONSE._serialized_end=8866 - _IDENTITYEVIDENCERESPONSE._serialized_start=8869 - _IDENTITYEVIDENCERESPONSE._serialized_end=9097 - _PEEREVIDENCERESPONSE._serialized_start=9100 - _PEEREVIDENCERESPONSE._serialized_end=9426 + _ROUTERLEASE._serialized_start=8023 + _ROUTERLEASE._serialized_end=8219 + _NODESERVICES._serialized_start=8221 + _NODESERVICES._serialized_end=8329 + _ADMINSTATUSRESPONSE._serialized_start=8332 + _ADMINSTATUSRESPONSE._serialized_end=8807 + _ADMINSTATUSRESPONSE_NODECATALOGENTRY._serialized_start=8729 + _ADMINSTATUSRESPONSE_NODECATALOGENTRY._serialized_end=8807 + _USERSTATUSRESPONSE._serialized_start=8810 + _USERSTATUSRESPONSE._serialized_end=9067 + _IDENTITYEVIDENCERESPONSE._serialized_start=9070 + _IDENTITYEVIDENCERESPONSE._serialized_end=9298 + _PEEREVIDENCERESPONSE._serialized_start=9301 + _PEEREVIDENCERESPONSE._serialized_end=9627 _PEEREVIDENCERESPONSE_LABELSENTRY._serialized_start=825 _PEEREVIDENCERESPONSE_LABELSENTRY._serialized_end=870 - _MEMBERCREDENTIAL._serialized_start=9429 - _MEMBERCREDENTIAL._serialized_end=9697 - _TRUSTEDSIGNINGKEY._serialized_start=9699 - _TRUSTEDSIGNINGKEY._serialized_end=9788 - _OIDCSESSION._serialized_start=9790 - _OIDCSESSION._serialized_end=9879 - _TASKAUTHORIZATIONRULE._serialized_start=9882 - _TASKAUTHORIZATIONRULE._serialized_end=10029 - _TASKRULE._serialized_start=10032 - _TASKRULE._serialized_end=10164 - _TASKOPERATION._serialized_start=10166 - _TASKOPERATION._serialized_end=10281 - _TOKENEXCHANGEREQUEST._serialized_start=10284 - _TOKENEXCHANGEREQUEST._serialized_end=10455 - _TOKENEXCHANGERESPONSE._serialized_start=10457 - _TOKENEXCHANGERESPONSE._serialized_end=10584 - _STSTOKENREQUEST._serialized_start=10587 - _STSTOKENREQUEST._serialized_end=10716 - _STSTOKENRESPONSE._serialized_start=10719 - _STSTOKENRESPONSE._serialized_end=10850 - _REVOCATIONSRESPONSE._serialized_start=10852 - _REVOCATIONSRESPONSE._serialized_end=10922 + _MEMBERCREDENTIAL._serialized_start=9630 + _MEMBERCREDENTIAL._serialized_end=9898 + _TRUSTEDSIGNINGKEY._serialized_start=9900 + _TRUSTEDSIGNINGKEY._serialized_end=9989 + _OIDCSESSION._serialized_start=9991 + _OIDCSESSION._serialized_end=10080 + _TASKAUTHORIZATIONRULE._serialized_start=10083 + _TASKAUTHORIZATIONRULE._serialized_end=10230 + _TASKRULE._serialized_start=10233 + _TASKRULE._serialized_end=10365 + _TASKOPERATION._serialized_start=10367 + _TASKOPERATION._serialized_end=10482 + _TOKENEXCHANGEREQUEST._serialized_start=10485 + _TOKENEXCHANGEREQUEST._serialized_end=10656 + _TOKENEXCHANGERESPONSE._serialized_start=10658 + _TOKENEXCHANGERESPONSE._serialized_end=10785 + _STSTOKENREQUEST._serialized_start=10788 + _STSTOKENREQUEST._serialized_end=10917 + _STSTOKENRESPONSE._serialized_start=10920 + _STSTOKENRESPONSE._serialized_end=11051 + _REVOCATIONSRESPONSE._serialized_start=11053 + _REVOCATIONSRESPONSE._serialized_end=11123 # @@protoc_insertion_point(module_scope) diff --git a/sdk/python/src/agent_mesh/_proto/agentmesh_pb2.pyi b/sdk/python/src/agent_mesh/_proto/agentmesh_pb2.pyi index 35a7c31e..2d228e22 100644 --- a/sdk/python/src/agent_mesh/_proto/agentmesh_pb2.pyi +++ b/sdk/python/src/agent_mesh/_proto/agentmesh_pb2.pyi @@ -191,20 +191,22 @@ class CommandBackend(_message.Message): def __init__(self, command: _Optional[_Iterable[str]] = ..., env: _Optional[_Mapping[str, str]] = ...) -> None: ... class ControlPlaneInfoResponse(_message.Message): - __slots__ = ["audience", "banned_peer_ids", "client_id", "oidc_issuer", "router_addresses", "routers"] + __slots__ = ["audience", "banned_peer_ids", "client_id", "directory", "oidc_issuer", "router_addresses", "routers"] AUDIENCE_FIELD_NUMBER: _ClassVar[int] BANNED_PEER_IDS_FIELD_NUMBER: _ClassVar[int] CLIENT_ID_FIELD_NUMBER: _ClassVar[int] + DIRECTORY_FIELD_NUMBER: _ClassVar[int] OIDC_ISSUER_FIELD_NUMBER: _ClassVar[int] ROUTERS_FIELD_NUMBER: _ClassVar[int] ROUTER_ADDRESSES_FIELD_NUMBER: _ClassVar[int] audience: str banned_peer_ids: _containers.RepeatedScalarFieldContainer[str] client_id: str + directory: DirectoryParameters oidc_issuer: str router_addresses: _containers.RepeatedScalarFieldContainer[str] routers: _containers.RepeatedCompositeFieldContainer[RouterInfo] - def __init__(self, oidc_issuer: _Optional[str] = ..., client_id: _Optional[str] = ..., audience: _Optional[str] = ..., router_addresses: _Optional[_Iterable[str]] = ..., banned_peer_ids: _Optional[_Iterable[str]] = ..., routers: _Optional[_Iterable[_Union[RouterInfo, _Mapping]]] = ...) -> None: ... + def __init__(self, oidc_issuer: _Optional[str] = ..., client_id: _Optional[str] = ..., audience: _Optional[str] = ..., router_addresses: _Optional[_Iterable[str]] = ..., banned_peer_ids: _Optional[_Iterable[str]] = ..., routers: _Optional[_Iterable[_Union[RouterInfo, _Mapping]]] = ..., directory: _Optional[_Union[DirectoryParameters, _Mapping]] = ...) -> None: ... class CredentialBroker(_message.Message): __slots__ = ["aws_assume_role", "oidc_federation", "platform_identity", "static_secret"] @@ -218,6 +220,16 @@ class CredentialBroker(_message.Message): static_secret: str def __init__(self, static_secret: _Optional[str] = ..., oidc_federation: _Optional[_Union[OIDCFederation, _Mapping]] = ..., aws_assume_role: _Optional[_Union[AWSAssumeRole, _Mapping]] = ..., platform_identity: _Optional[_Union[PlatformIdentity, _Mapping]] = ...) -> None: ... +class DirectoryParameters(_message.Message): + __slots__ = ["record_ttl", "replication", "reprovide_interval"] + RECORD_TTL_FIELD_NUMBER: _ClassVar[int] + REPLICATION_FIELD_NUMBER: _ClassVar[int] + REPROVIDE_INTERVAL_FIELD_NUMBER: _ClassVar[int] + record_ttl: _duration_pb2.Duration + replication: int + reprovide_interval: _duration_pb2.Duration + def __init__(self, replication: _Optional[int] = ..., reprovide_interval: _Optional[_Union[_duration_pb2.Duration, _Mapping]] = ..., record_ttl: _Optional[_Union[_duration_pb2.Duration, _Mapping]] = ...) -> None: ... + class DiscoveredProvider(_message.Message): __slots__ = ["local_proxy_url", "peer_id", "srv_description", "srv_name"] LOCAL_PROXY_URL_FIELD_NUMBER: _ClassVar[int] diff --git a/sdk/python/src/agent_mesh/discovery.py b/sdk/python/src/agent_mesh/discovery.py index f4571746..3d465189 100644 --- a/sdk/python/src/agent_mesh/discovery.py +++ b/sdk/python/src/agent_mesh/discovery.py @@ -50,6 +50,13 @@ _MAX_PEERS_PER_ROUND = 8 MAX_DHT_MESSAGE_BYTES = 256 * 1024 +# How many routers hold each record and answer each lookup when the control +# plane publishes no directory parameters on /info: api.DefaultDHTReplication. +# A member reads the mesh's value from /info (Mesh.dht_replication) and +# follows that many of the closer routers an answer names, as agentmesh-node +# asks that many at a time. +DEFAULT_DHT_REPLICATION = 3 + @dataclass class DiscoveredProvider: @@ -109,7 +116,7 @@ async def _query(host: IHost, peer_id: ID, req: kad.Message) -> kad.Message | No return resp if resp.type == req.type else None -async def find_peer(host: IHost, target: ID, seeds: Iterable[ID]) -> list[multiaddr.Multiaddr]: +async def find_peer(host: IHost, target: ID, seeds: Iterable[ID], replication: int = DEFAULT_DHT_REPLICATION) -> list[multiaddr.Multiaddr]: """The addresses the DHT knows for a peer, as go-libp2p-kad-dht's FindPeer learns them: a router answers FIND_NODE for a peer it is connected to with that peer and its addresses, its relayed one included, and names the @@ -123,11 +130,11 @@ def take(resp: kad.Message) -> bool: return True return False - await _walk(host, kad.Message(type=kad.Message.FIND_NODE, key=target.to_bytes()), seeds, take, skip={target}) + await _walk(host, kad.Message(type=kad.Message.FIND_NODE, key=target.to_bytes()), seeds, take, skip={target}, replication=replication) return found -async def find_providers(host: IHost, key: bytes, seeds: Iterable[ID], limit: int = 20) -> list[DiscoveredProvider]: +async def find_providers(host: IHost, key: bytes, seeds: Iterable[ID], limit: int = 20, replication: int = DEFAULT_DHT_REPLICATION) -> list[DiscoveredProvider]: """Asks the seed peers (the routers we are connected to) for providers of key and follows the closer peers they name for a few rounds.""" found: dict[str, DiscoveredProvider] = {} @@ -143,21 +150,31 @@ def take(resp: kad.Message) -> bool: entry.addrs.append(str(ma)) return bool(found) - await _walk(host, kad.Message(type=kad.Message.GET_PROVIDERS, key=key), seeds, take) + await _walk(host, kad.Message(type=kad.Message.GET_PROVIDERS, key=key), seeds, take, replication=replication) return list(found.values())[:limit] -async def _walk(host: IHost, req: kad.Message, seeds: Iterable[ID], take: Callable[[kad.Message], bool], skip: frozenset[ID] | set[ID] = frozenset()) -> None: +async def _walk( + host: IHost, + req: kad.Message, + seeds: Iterable[ID], + take: Callable[[kad.Message], bool], + skip: frozenset[ID] | set[ID] = frozenset(), + replication: int = DEFAULT_DHT_REPLICATION, +) -> None: """The Kademlia walk both lookups share. A round asks its peers at once and ends when an answer satisfies `take`; only otherwise are the closer peers the answers named dialed, at once as well. A round thus costs one query and one dial timeout, not one per peer: right after a rollout the routers' tables still name the pods it replaced, and the control plane may list a router this member cannot reach. A peer that did not answer - its dial is not dialed again.""" + its dial is not dialed again. A router names the routers closest to the + key in order, and a record lives on the `replication` closest, so only + that many of them are followed from one round to the next.""" self_id = host.get_id() asked: set[ID] = set() frontier: list[ID] = list(seeds) + fanout = min(max(replication, 1), _MAX_PEERS_PER_ROUND) for _ in range(_MAX_ROUNDS): batch = [p for p in frontier if p not in asked and p != self_id][:_MAX_PEERS_PER_ROUND] if not batch: @@ -183,6 +200,8 @@ async def ask(peer_id: ID) -> None: closer: dict[ID, PeerInfo] = {} for resp in answers: for info in _peer_infos(resp.closerPeers): + if len(closer) >= fanout: + break if info.peer_id not in asked and info.peer_id != self_id and info.peer_id not in skip: closer.setdefault(info.peer_id, info) connected = set(host.get_connected_peers()) diff --git a/sdk/python/src/agent_mesh/mesh.py b/sdk/python/src/agent_mesh/mesh.py index dc6c2ba1..a7132837 100644 --- a/sdk/python/src/agent_mesh/mesh.py +++ b/sdk/python/src/agent_mesh/mesh.py @@ -24,6 +24,7 @@ from ._proto import agentmesh_pb2 as pb from .controlplane import ROLE_NODE, ControlPlaneClient, Enrollment, KeysNotTrustedError, Transport from .credential import MeshCredential, encode_auth_frame +from .discovery import DEFAULT_DHT_REPLICATION from .identity import Identity _IDENTITY_FILE = "identity.key" @@ -98,6 +99,7 @@ def __init__( self.control_plane = control_plane self._credential = credential self._routers: list[pb.RouterInfo] = [] + self._dht_replication = DEFAULT_DHT_REPLICATION self._state_dir = state_dir self._jwt_source = jwt_source # refresh() and sync_control_plane() run in worker threads of the @@ -118,6 +120,14 @@ def routers(self) -> list[pb.RouterInfo]: """The router catalog (ControlPlaneInfoResponse.routers) from the most recent /info pull.""" return list(self._routers) + @property + def dht_replication(self) -> int: + """How many routers hold each record and answer each lookup on this mesh, + as /info last published it (ControlPlaneInfoResponse.directory); + DEFAULT_DHT_REPLICATION until the control plane has answered or when it + publishes none.""" + return self._dht_replication + def attenuate(self, rule: pb.TaskAuthorizationRule) -> MeshCredential: """Returns a new MeshCredential with a tar_block appended offline in memory.""" return self._credential.attenuate(rule) @@ -316,6 +326,8 @@ def _sync_control_plane(self) -> ControlPlaneSync: info = self.control_plane.info() routers = list(info.routers) self._routers = routers + if info.HasField("directory") and info.directory.replication >= 1: + self._dht_replication = info.directory.replication if info.router_addresses: self._credential = replace(self._credential, router_addresses=list(info.router_addresses)) elif routers: diff --git a/sdk/python/src/agent_mesh/session.py b/sdk/python/src/agent_mesh/session.py index f1add048..08f3734d 100644 --- a/sdk/python/src/agent_mesh/session.py +++ b/sdk/python/src/agent_mesh/session.py @@ -585,7 +585,7 @@ async def _routed_addresses(self, target: ID) -> tuple[list[multiaddr.Multiaddr] relays: list[multiaddr.Multiaddr] = [] seeds = [ID.from_base58(r.peer_id) for r in self.routers] suffix = f"/p2p/{target}" - for ma in await find_peer(self.host, target, seeds): + for ma in await find_peer(self.host, target, seeds, self.mesh.dht_replication): text = str(ma) if "/p2p-circuit" not in text: try: @@ -649,7 +649,7 @@ async def discover(self, service: str, name: str | None = None, limit: int = 20) if service_type not in ("mcp", "inference", "a2a", "egress"): raise ValueError(f"service type must be mcp, inference, a2a or egress, got {service_type!r}") seeds = [ID.from_base58(r.peer_id) for r in self.routers] - return await find_providers(self.host, service_key(service_type, service_name), seeds, limit) + return await find_providers(self.host, service_key(service_type, service_name), seeds, limit, self.mesh.dht_replication) def open_mcp( self, diff --git a/sdk/python/tests/test_discovery.py b/sdk/python/tests/test_discovery.py index 3da18716..dd849719 100644 --- a/sdk/python/tests/test_discovery.py +++ b/sdk/python/tests/test_discovery.py @@ -254,3 +254,29 @@ async def main(): trio.run(main) + + +def test_the_walk_follows_only_the_replication_closest_routers(): + """A router names the routers closest to the key in order, as many as it + knows; a record lives on the mesh's replication of them, so only that + many are followed. With the default of three the fourth is never dialed; + with a replication of one, only the first.""" + + def knows_nothing(req): + return kad.Message( + type=req.type, + closerPeers=[ + _peer(GONE_POD, "/ip4/10.84.1.7/tcp/5002"), + _peer(GONE_POD_2, "/ip4/10.84.2.9/tcp/5002"), + _peer(DARK_ROUTER, "/ip4/203.0.113.7/tcp/4501"), + _peer(TARGET, "/ip4/10.0.0.9/tcp/4001"), + ], + ) + + async def main(replication, want): + host = _Host({ROUTER: knows_nothing, ROUTER_B: knows_nothing}) + assert await find_providers(host, b"key", [ROUTER, ROUTER_B], replication=replication) == [] + assert sorted(map(str, host.dials)) == sorted(map(str, want)) + + trio.run(main, 3, [GONE_POD, GONE_POD_2, DARK_ROUTER], clock=trio.testing.MockClock(autojump_threshold=0)) + trio.run(main, 1, [GONE_POD], clock=trio.testing.MockClock(autojump_threshold=0)) diff --git a/site/content/docs/guides/scalability.md b/site/content/docs/guides/scalability.md index 74f4dfda..314c34d6 100644 --- a/site/content/docs/guides/scalability.md +++ b/site/content/docs/guides/scalability.md @@ -9,25 +9,25 @@ members. It explains what each component spends per member, which setting bounds what, how to size a deployment for the fleet you expect, how to place members on routers with labels, and which metrics tell you that a bound is near. The numbers come from the public testnet: six routers on -2 vCPU held 10,000 members, each router at 8800 to 9250 connections, and -every member stayed on the mesh for the 40 minutes the fleet was held; -an earlier fleet of 3200 stayed through a rollout of every router and -both control plane replicas. +2 vCPU held 10,000 members, and every member stayed on the mesh for the +40 minutes the fleet was held; an earlier fleet of 3200 stayed through a +rollout of every router and both control plane replicas. ## What a member costs A member is one `agentmesh-node` process. It holds a session with two routers (`--routers`), chosen from those the control plane lists, and a relay reservation on each. Through those sessions it keeps a GossipSub -subscription for control plane events. Its DHT client also connects to -the other routers: a router closes such a connection a minute later -(`--auth-deadline`), since no session was opened on it, and the DHT -client opens it again at its next lookup or at its next table refresh, -which runs every two minutes and queries every router. A router -therefore carries two kinds of connection: the sessions of its own -members, which the watermarks count, and a connection from most of the -other members most of the time. On the testnet at 10,000 members each -router held 3200 to 5100 sessions and about 9000 connections. +subscription for control plane events. Its DHT client reaches the +routers kademlia puts closest to each key it publishes or looks up, +three of them (the mesh's replication, `agentmesh-control-plane +--dht-replication`), named in one answer by a router it is attached to, +and runs the credential handshake with each, so those connections are +sessions as well: the router keeps them, and the next lookup or +announcement (every 5 minutes, the mesh's `--dht-reprovide-interval`) +reuses them. The client keeps no table maintenance of its own. A router +therefore carries sessions of two kinds, both counted by the watermarks: +the members attached to it, and the members whose keys land on it. A member connects to another member only to call it, through that member's router, where it runs the credential handshake before asking for the circuit. At rest it uses about 55 MiB of memory, 300 goroutines and @@ -82,7 +82,7 @@ dial tries every address at once. | Role | What it is for the network | What it opens | What opens to it | |---|---|---|---| | Router | The dialable endpoint. Relay for members behind NAT, the DHT server every lookup reaches, the point of admission (credential handshake, relay ACL, bans), the hub GossipSub events pass through, and the record of which members hold a reservation on it. | Connections to the other routers (every pair), for the DHT and GossipSub. | Every member that holds it, every member's DHT client when it queries, the control plane's event publisher. | -| Member (`agentmesh-node`, an SDK) | A client of the routers. It announces where it is reached, looks peers and services up, verifies the peers it talks to and carries the data plane. | K connections to the routers it holds (`--routers`, 2), one to each other router it queries, one to each peer it reaches directly. | Peers that call it: directly when they can, through a router otherwise. | +| Member (`agentmesh-node`, an SDK) | A client of the routers. It announces where it is reached, looks peers and services up, verifies the peers it talks to and carries the data plane. It is a DHT client whatever its reachability: a public member is dialled directly, and still serves no lookups for others. | K connections to the routers it holds (`--routers`, 2), one to each other router it queries, one to each peer it reaches directly. | Peers that call it: directly when they can, through a router otherwise. | | Provider | A member that publishes a service: the fan-in point of the mesh. | As a member. | One relayed connection per caller, each a stream on its connection to a router. | | Control plane | Enrollment, the router catalog, keys, policy and leases, over HTTP. Its event publisher is a member that holds every router. | HTTP only, and the publisher's connections to the routers. | Members and routers, over HTTP. | @@ -158,15 +158,11 @@ NAT entry at each end. ### What a middlebox sees per member A member holds K long-lived connections to its routers, kept alive by -the transport, so K entries that do not churn. Its DHT client holds a -connection to each other router; the router closes that connection at -`--auth-deadline` (a minute) since no session was opened on it, and the -DHT client's next lookup or table refresh, every two minutes, opens it -again, so a mesh of R routers adds R − K entries per member that are -created and torn down about once a minute. At 10,000 members on six -routers that is about 3700 new connections a minute arriving at each -router. A member that reaches a peer directly holds one entry per such -peer, at both ends. A relayed connection adds none. +the transport, plus one to each router its keys land on, up to +the mesh's replication per key, opened at the first lookup or announcement +and kept: entries that do not churn, K + D of them for the D directory +routers the member uses. A member that reaches a peer directly holds one +entry per such peer, at both ends. A relayed connection adds none. A firewall between a member and the routers needs TCP (and UDP for QUIC, when the member is allowed to fall back to it) to the routers' @@ -185,6 +181,7 @@ them. | Bound | Where | Default | At the bound | |---|---|---|---| | Routers per member | `agentmesh-node --routers` | 2 | A member holds this many routers; one is enough to be on the mesh, the second covers a router restart. | +| Routers per key | `agentmesh-control-plane --dht-replication` | 3 | A record a member publishes lives on this many routers, the closest to its key, and a lookup asks them; the member holds a session with each. Published on `/info`, so every member of the mesh runs the same value. | | Connections per router | `agentmesh-router --high-watermark`, `--low-watermark` | 4000, 15% below (3400) | At the high mark of members the router sends members away, at random, down to the low mark; they attach to another router and stay off this one for five minutes. A mesh of N routers holds about N × 4000 / 2 members. | | Connections per source address | `agentmesh-router --conns-per-source-ip` | a quarter of the high mark (1000) | The router refuses the next connection from that address. Members behind one NAT or one cluster's egress share an address. | | Relay reservations per router | `agentmesh-router --relay-max-reservations` | the high mark (4000) | A member that cannot reserve is not reachable through that router. | @@ -195,43 +192,68 @@ them. | Connections per source address at a node | `agentmesh-node`, fixed | a quarter of the high mark (100) | The node refuses the next direct connection from that address, counted in `agentmesh_node_inbound_connections_refused_total`. A service called by a fleet behind one NAT or one cluster's egress sees them all from one address. A caller that arrives through a router is not charged to the router's address: it is bounded by the node's connection budget and per-peer scope, and at the router by the relay's per-peer and per-address budgets. | | Router lease | `agentmesh-control-plane --lease-duration`, `agentmesh-router --shutdown-lease-ttl` | 15m, 30s | A router that stops without the last lease stays listed for the lease duration. | -The first two rows are the model. A fleet of F members on N routers, each -member holding K of them, puts about F × K / N sessions on each router, so -adding a router adds members: the fleet a mesh holds is N × H / K for a -high watermark H. A member picks the routers with the most room when it -joins, so a new router fills from the members that join after it and from -those the full routers send away; the members already placed do not move -on their own. When a router stops, its members spread over the N − 1 -others, which is where the headroom in the next section goes. - -Connections are more than sessions. The members on the other routers -keep a DHT connection to this one about four fifths of the time, so a -router holds about F × K / N + 0.8 × F × (N − K) / N connections, which -at six routers is 0.9 × F: adding a router divides the sessions, and -leaves the connections where they were. What bounds the connections is -the router's libp2p connection cap, twice the high watermark, and the -watermarks do not move it: a router past the cap refuses the next -connection, session or not. A fleet is therefore also bounded by -2 × H members on any one router, whatever N. +The first three rows are the model. A fleet of F members on N routers, +each member holding K of them, puts about F × K / N attached sessions on +each router, so adding a router adds members: the fleet a mesh holds is +N × H / K for a high watermark H. A member picks the routers with the +most room when it joins, so a new router fills from the members that +join after it and from those the full routers send away; the members +already placed do not move on their own. When a router stops, its +members spread over the N − 1 others, which is where the headroom in the +next section goes. + +The directory adds sessions of its own. A member also holds a session +with each router its keys land on: at most r (the mesh's replication) per +key it publishes or looks up, overlapping across keys, never more than +N − K in all. Call that D. On few routers D reaches N − K and every +member holds every router; on many, a member that publishes one service +and calls a few holds D of about r to 2r whatever N is. The keys are +spread over the routers by hash, so a router holds about F × (K + D) / N +sessions in all, and that is the figure the watermarks see: with 10,000 +members, K = 2 and D = 4, about 10,000 per router on six routers, 5000 +on twelve, 2000 on thirty. Adding routers divides both terms. A key that +every member publishes, such as a service type, lands on the same r +routers for all of them, and those r routers see a session from every +publisher. + +What bounds the total is the router's libp2p connection cap, twice the +high watermark: a router past it refuses the next connection, session or +not. What is left in passing, a call to a peer on this router, two +connections each while members enroll, fits in the gap between the +watermark and the cap. ## Sizing the routers Start from the fleet you expect, F members on N routers with `--routers` -at its default of 2, and size for the day one router is down: - -- `--high-watermark` at least 1.2 × F × 2 / (N − 1), and at least - 0.55 × F so that the connection cap, twice the high mark, holds the - sessions and the DHT connections of the whole fleet with a router out. - Each router then holds F × 2 / N sessions in normal running, with room - for the members of a stopped router and for the connections members - open in passing (a call to a peer on this router, two connections each - while they enroll). 5000 members on four routers need 4000; 10,000 - members need six routers at 6000, where the second rule is the one - that binds. The testnets deploy with 6000 (`ROUTER_HIGH_WATERMARK` in - the deploy workflow) on six routers (`ROUTER_REPLICAS`), which held - 10,000 members at about 9000 connections per router; with one router - out the five others carry about the same, since the DHT connections - are most of the count, of a cap of 12,000. +at its default of 2 and the mesh's replication at its default of 3, and +size for the day +one router is down: + +- `--high-watermark` at least 1.2 × F × (K + D) / (N − 1), with D the + directory sessions a member holds: N − K on few routers, 3 to 6 on + many. Each router then holds F × (K + D) / N sessions in normal + running, with room for the members of a stopped router, and the + connection cap at twice the high mark holds what members open in + passing. 5000 members on four routers (D = 2) need 8000; 10,000 + members on twelve routers (D = 4) need 6600, on twenty (D = 5) need + 4500. The testnets deploy with 6000 (`ROUTER_HIGH_WATERMARK` in the + deploy workflow) on six routers (`ROUTER_REPLICAS`), which holds about + 4000 members that use the directory with one router out; 10,000 take + twelve routers. +- The three knobs, and what each buys. One is a member's own: `--routers` + (K) is what a router restart costs it; a member with two attached + routers stays on the mesh through one, and each one more is a session + more per member on some router. Two are the mesh's, set on the control + plane and read by every member and router from `/info`: + `--dht-replication` (r) is what losing routers costs the directory, a + record on three routers survives two of them down at once, and each + one more is a session more per key per member on the router the key + lands on; `--dht-reprovide-interval` is how soon a provider that + stopped without unpublishing leaves the directory, three intervals, + which is also how long routers keep a record, against the + announcements a minute the routers absorb, providers divided by the + interval. None of the three adds capacity: routers do, by dividing + every term above by N. - `--low-watermark` at its default, 15% below the high mark. The gap is what one shedding moves to the other routers; a smaller gap sheds more often, a larger one moves more members at once. A router that sheds is @@ -347,7 +369,9 @@ A member that enrolls needs its control plane and its routers reachable from where it runs. Behind a NAT it is reached through a relay, which costs a reservation on the router and nothing else; a member with a public address may run `--reachability auto` and be dialled directly once AutoNAT -has confirmed the address, which takes it off the relay. +has confirmed the address, which takes it off the relay. Reachability +decides how a member is reached and nothing else: it stays a DHT client, +so it never enters other members' routing tables or serves their lookups. ## Sizing a service @@ -432,6 +456,7 @@ The router and the control plane serve Prometheus metrics on | `agentmesh_control_plane_http_request_duration_seconds` | The control plane's own latency; p99 above a second under a join burst means the database is the bound. | | `agentmesh_node_mesh_connected` | On every node: `0` for five minutes is a member off the mesh. | | `agentmesh_node_routers{state="attached"}` against `{state="wanted"}` | On every node: fewer attached than wanted for long is a member that cannot find a router with room, or one whose selector too few routers carry. `agentmesh_node_router_candidates{match="selector"}` is how many it could choose from. | +| `agentmesh_node_routers{state="directory"}` | On every node: the routers it holds a session with for its keys alone, the D of the model above. Summed over a fleet and divided by the routers, it is the share of each router's sessions that the directory costs; `agentmesh_node_directory_handshakes_total{result="failed"}` rising is a router that refuses members it is not attached to. | An autoscaler for the routers has its inputs in the first two rows: scale out when the free capacity of the mesh is below what one router holds, so diff --git a/site/content/docs/reference/agentmesh-node.md b/site/content/docs/reference/agentmesh-node.md index e430990d..43958ed7 100644 --- a/site/content/docs/reference/agentmesh-node.md +++ b/site/content/docs/reference/agentmesh-node.md @@ -115,11 +115,9 @@ explains how to enroll. | `--router-selector` | none | Attach only to routers whose credential attests this `key=value` label, repeatable, every pair required: `--router-selector region=eu`. The labels are the ones the control plane lists on `/info`, attested at the router's enrollment. A selector no router attests leaves the node retrying as if no router answered, with a warning naming the selector and the labels it saw. | | `--router-prefer` | none | Attach to routers attesting this `key=value` label before others, repeatable; a router attesting more of the pairs comes earlier, and among equals the one with the most room. `--router-prefer zone=eu-a` keeps a node on its zone's routers while they have capacity and lets it fall to the others. | | `--enable-relay` | `false` | Relay traffic for other peers. | -| `--discovery-interval` | `30s` | Delay before the first announcement of this node's services to the DHT, capped at 5 s; services are re-announced every 5 minutes after that. | +| `--discovery-interval` | `30s` | Delay before the first announcement of this node's services to the DHT, capped at 5 s; services are re-announced every reprovide interval of the mesh after that (`agentmesh-control-plane --dht-reprovide-interval`, 5 minutes by default). | | `--dht-lookup-limit` | `20` | Providers to query per DHT lookup. | | `--discovery-concurrency` | `10` | Concurrent catalog fetches during discovery. | -| `--dht-provider-addr-ttl` | `15m` | How long a service announcement this node stores lives after the announcing node last made it. Nodes re-announce every 5 minutes, so a node that is gone drops out of discovery within this time. A value of 5 minutes or less is refused at startup: the record would expire between two announcements. `0` keeps the default. | -| `--dht-max-record-age` | library default | DHT value record lifetime. | | `--backend-probe-timeout` | `2s` | How long a service backend may take to answer before the node declines to advertise it. Raise it for subprocesses that start slowly. | | `--secrets-dir` | `/etc/agentmesh/secrets` | Directory holding the credentials that the control plane's [egress destinations](../policy/#egress-destinations) name, one file per credential name. The platform puts the files there; the node reads a file on every request to the destination. | | `--control-plane-sync-interval` | `15m` | How often signing keys, the ban set, router addresses and the mesh policy are pulled from the control plane. Keep it well below the control plane's `--key-grace-period`; raise it on large meshes. | diff --git a/site/content/docs/reference/agentmesh-one.md b/site/content/docs/reference/agentmesh-one.md index 8c2fb82b..c47cc539 100644 --- a/site/content/docs/reference/agentmesh-one.md +++ b/site/content/docs/reference/agentmesh-one.md @@ -59,6 +59,7 @@ of zero leaves the default of that component unchanged. | Flag | Corresponds to | |---|---| | `--control-plane-lease-duration` | `agentmesh-control-plane --lease-duration` | +| `--dht-replication`, `--dht-reprovide-interval` | the same flags of `agentmesh-control-plane`: the mesh's directory parameters, published on `/info`. | | `--control-plane-key-rotation-interval` | `--key-rotation-interval` | | `--control-plane-key-grace-period` | `--key-grace-period` | | `--control-plane-biscuit-ttl` | `--biscuit-ttl` | @@ -68,7 +69,6 @@ of zero leaves the default of that component unchanged. | `--router-lease-renew-interval` | `--lease-renew-interval` | | `--router-low-watermark`, `--router-high-watermark` | `--low-watermark`, `--high-watermark` | | `--router-conns-per-source-ip` | `--conns-per-source-ip`. `0` follows the high watermark, because peers behind a proxy share source IPs. | -| `--router-dht-provider-addr-ttl`, `--router-dht-max-record-age` | `--dht-provider-addr-ttl`, `--dht-max-record-age`. `0` keeps `15m` and the library default. | | `--router-relay-limit-duration`, `--router-relay-limit-data` | `--relay-limit-duration`, `--relay-limit-data`. `0` keeps `1h` and no data limit, so set a long duration such as `24h` instead of an unlimited one. | | `--router-shutdown-lease-ttl` | `--shutdown-lease-ttl`, default `30s`. | | `--router-auth-deadline` | `--auth-deadline`, default `1m`. | diff --git a/site/content/docs/reference/control-plane.md b/site/content/docs/reference/control-plane.md index ef55ee3e..c1da3caf 100644 --- a/site/content/docs/reference/control-plane.md +++ b/site/content/docs/reference/control-plane.md @@ -37,6 +37,8 @@ agentmesh-control-plane admin unban --peer lift a ban | `--key-rotation-interval` | `24h` | How often a new signing key is generated. `0` disables rotation. | | `--key-grace-period` | `1h` | How long a rotated-out key stays accepted. Credentials signed by a retired key cannot be verified or refreshed. Nodes and routers must pull `/keys` well within this window (`agentmesh-node --control-plane-sync-interval`, `agentmesh-router --keys-sync-interval`). | | `--lease-duration` | `15m` | How long a router lease lasts without renewal. | +| `--dht-replication` | `3` | How many routers hold each service a member publishes and answer each lookup: the routers closest to the key. Every member holds a session with each of them besides its `--routers`, so the figure is a session more per key on every member for each replica, and a mesh with more routers spreads them. Raise it for records that must survive more routers being down at once. Published on `/info`; every member and router of the mesh runs on it. | +| `--dht-reprovide-interval` | `5m` | How often a member re-announces each service it publishes. A provider that stopped without unpublishing stays listed for three intervals, which is how long routers keep a record after its last announcement; the mesh pays its providers divided by the interval in announcements a minute. Published on `/info`. | | `--node-retention` | `720h` (30 days) | How long the record of an enrolled node is kept after its session expires. Banned nodes are kept forever. `0` keeps every record. | | `--mesh-reconnect-interval` | `30s` | How often the event publisher re-reads the router leases and dials any router it is not connected to. On each router it runs the mesh handshake with a credential it mints for its own peer under the signing key, with the role `mesh:role:control-plane`, so the router holds its connection as a member's. | | `--log-level` | `info` | `debug`, `info`, `warn`, `error`. | @@ -60,7 +62,7 @@ Responses that carry credentials are sent with `Cache-Control: no-store`. | `GET /healthz` | Liveness. | | `GET /readyz` | Readiness. Returns `503` when the database is unreachable. | | `GET /metrics` | Prometheus metrics. | -| `GET /info` | `ControlPlaneInfoResponse`: the OIDC issuer, client ID and audience, the routers with live leases (`routers`: peer ID, addresses, the labels attested at the router's enrollment, and its connections against its limit as of its last lease; `router_addresses` is the same routers flattened), and the list of banned peer IDs. This is what a node needs before it can enroll, and what it chooses its routers from. | +| `GET /info` | `ControlPlaneInfoResponse`: the OIDC issuer, client ID and audience, the routers with live leases (`routers`: peer ID, addresses, the labels attested at the router's enrollment, and its connections against its limit as of its last lease; `router_addresses` is the same routers flattened), the list of banned peer IDs, and the directory parameters of the mesh (`directory`: `replication`, `reprovide_interval`, and the `record_ttl` derived from them). This is what a node needs before it can enroll, what it chooses its routers from, and what it runs its DHT on. | | `GET /keys` | The current set of signing public keys, signed by every key in the set. A caller accepts the set only if one signature verifies under a key it already trusts. | | `GET /.well-known/openid-configuration`, `GET /.well-known/oauth-authorization-server` | OIDC Discovery and OAuth 2.1 Authorization Server metadata for outbound STS federation and MCP OAuth 2.1 clients. | | `GET /jwks` | JSON Web Key Set (`ES256` public keys) for verifying border JWTs minted by `POST /sts/token`. | diff --git a/site/content/docs/reference/router.md b/site/content/docs/reference/router.md index 46fbb663..aaa5342b 100644 --- a/site/content/docs/reference/router.md +++ b/site/content/docs/reference/router.md @@ -42,9 +42,7 @@ replica count. | `--lease-renew-interval` | `300s` | How often the lease is renewed. Must be well below the control plane's `--lease-duration`. | | `--allow-loopback` | `false` | Announce and accept loopback and link-local addresses. For a router and nodes on one host. | | `--conns-per-source-ip` | a quarter of `--high-watermark` (`1000`) | Inbound connections accepted per source address. One address can hold at most this share of the router's connection budget, so filling a router takes at least four addresses. Members behind a NAT or a cluster's SNAT share one address and each holds one connection per router, two while enrolling. | -| `--low-watermark`, `--high-watermark` | 15% below the high mark (`3400`), `4000` | How many members the router holds: peers that passed the handshake, other routers aside. At the high mark the router sends members away (see below) until it is back at the low mark, so the gap between the two is what one shedding moves; the default moves 15%. The same numbers bound connections: above the high mark the connection manager closes connections, oldest first among those that carry no session, then members; connections to the other routers are never closed. A connection without a session lasts at most `--auth-deadline`, and a member attached elsewhere opens one to this router about once a minute for its DHT, so at scale a router holds about as many of those as it has sessions, or more; the connection manager leaves them alone while they are younger than its grace period, and libp2p's connection cap, twice the high mark, is what bounds the total (see the [scalability guide](../../guides/scalability/#what-bounds-the-mesh)). | -| `--dht-provider-addr-ttl` | `15m` | How long a service announcement lives after a node last made it. Nodes re-announce every 5 minutes, so a node that is gone drops out of discovery within this time. `0` keeps the default. | -| `--dht-max-record-age` | library default | DHT value record lifetime. | +| `--low-watermark`, `--high-watermark` | 15% below the high mark (`3400`), `4000` | How many members the router holds: peers that passed the handshake, other routers aside. At the high mark the router sends members away (see below) until it is back at the low mark, so the gap between the two is what one shedding moves; the default moves 15%. The same numbers bound connections: above the high mark the connection manager closes connections, oldest first among those that carry no session, then members; connections to the other routers are never closed. A connection without a session lasts at most `--auth-deadline`; a member attached elsewhere opens one to this router when it looks a peer or service up or re-announces a service, so those come and go with the mesh's activity; the connection manager leaves them alone while they are younger than its grace period, and libp2p's connection cap, twice the high mark, is what bounds the total (see the [scalability guide](../../guides/scalability/#what-bounds-the-mesh)). | | `--relay-limit-duration`, `--relay-limit-data` | `1h`, `0` | Caps on each relayed connection: lifetime, and bytes per direction (`512MiB`, `1GB`). The relay cuts the connection when either is reached. `0` means no limit. | | `--relay-max-reservations` | `--high-watermark` (`4000`) | Members that may hold a relay reservation on this router at once, which is how many members it can reach on behalf of callers. Per source address the cap is `--conns-per-source-ip`. | | `--relay-max-circuits` | `1024` | Relayed connections one peer may hold at once through this router, as caller or as destination. A service that many members call at once needs this many open circuits. | @@ -120,7 +118,7 @@ The metrics address exposes, next to the Go runtime metrics: | `agentmesh_router_dht_routing_table_size` | DHT routing table size. | | `agentmesh_router_auth_handshakes_total{result}`, `agentmesh_router_lease_renewals_total{result}` | Handshakes and lease renewals by outcome. | | `agentmesh_router_goaway_sent_total{reason,result}` | Members sent away, by reason (`DRAINING`, `OVERLOADED`) and outcome: `ok` when the member took the message, `unsupported` when it does not speak the protocol and was closed instead, `failed` when the stream broke. A rising `OVERLOADED` count on a router that is not draining is a fleet that has outgrown its routers. | -| `agentmesh_router_unauthenticated_connections_closed_total` | Connections closed at `--auth-deadline` without a handshake. A steady rate is DHT traffic from members attached to other routers; a burst is a scanner. | +| `agentmesh_router_unauthenticated_connections_closed_total` | Connections closed at `--auth-deadline` without a handshake. Members handshake with every router they connect to, so a steady rate is members running a node release that predates that, and a burst is a scanner. | | `agentmesh_router_inbound_connections_refused_total` | Inbound connections refused by the per-address cap. | | `agentmesh_router_streams_refused_total{scope,protocol}` | Streams the resource manager refused, by scope and protocol or service. A count on `/mesh/kad/1.0.0` or a `/meshsub/*` protocol is members the router keeps out of discovery; see the scalability guide. | | `agentmesh_router_busiest_connection_streams{transport}` | The most streams any one connection carries. The connection to a service the router relays for holds one stream per relayed caller, and QUIC admits 256 per connection; see the scalability guide. | diff --git a/tests/integration/router_attachment_test.go b/tests/integration/router_attachment_test.go index 59293f5d..fc3cc180 100644 --- a/tests/integration/router_attachment_test.go +++ b/tests/integration/router_attachment_test.go @@ -26,6 +26,7 @@ import ( "os/exec" "path/filepath" "slices" + "strconv" "strings" "syscall" "testing" @@ -151,17 +152,43 @@ func routersConnectedTo(t *testing.T, cpPort int, peerID peer.ID) []string { // meshInfo reads the node's own view: the routers it holds and every peer // it has a connection with. func meshInfo(t *testing.T, n *backgroundNode) (attached, connected []string) { + t.Helper() + attached, _, connected = meshView(t, n) + return attached, connected +} + +// meshView is meshInfo with the directory routers as well: the routers the +// node holds a session with for its DHT alone. +func meshView(t *testing.T, n *backgroundNode) (attached, directory, connected []string) { t.Helper() var info struct { - AttachedRouters []string `json:"attached_routers"` - ConnectedPeers []string `json:"connected_peers"` + AttachedRouters []string `json:"attached_routers"` + DirectoryRouters []string `json:"directory_routers"` + ConnectedPeers []string `json:"connected_peers"` } if err := json.Unmarshal([]byte(debugGetWithToken(t, n.apiAddr, n.token, "/debug/mesh-info")), &info); err != nil { t.Fatalf("decode mesh-info: %v", err) } slices.Sort(info.AttachedRouters) + slices.Sort(info.DirectoryRouters) slices.Sort(info.ConnectedPeers) - return info.AttachedRouters, info.ConnectedPeers + return info.AttachedRouters, info.DirectoryRouters, info.ConnectedPeers +} + +// nodeCounter reads one counter series from the node's /metrics, 0 when it +// has not been emitted. +func nodeCounter(t *testing.T, n *backgroundNode, series string) float64 { + t.Helper() + for _, line := range strings.Split(debugGetWithToken(t, n.apiAddr, n.token, "/metrics"), "\n") { + if strings.HasPrefix(line, series+" ") { + v, err := strconv.ParseFloat(strings.TrimSpace(strings.TrimPrefix(line, series)), 64) + if err != nil { + t.Fatalf("parse %s: %v", line, err) + } + return v + } + } + return 0 } // attachedRouters reads the routers the node holds as relays from its own @@ -585,11 +612,25 @@ func TestIdleNodesHoldOnlyTheirRouters(t *testing.T) { } } } + // A router's sessions are its own node, the two peer routers, the + // control plane's event publisher, and the nodes whose keys landed on + // it: the directory routers each node reports, which are routers and + // nothing else. for _, s := range sites { - // Its own node, the two peer routers and the control plane's - // event publisher. - if got := routers[s].gauge(t, "agentmesh_router_authenticated_peers"); got != 4 { - t.Errorf("router %s authenticated %v peers, want 4 (its node, two routers, the control plane)", s, got) + want := 4.0 + for _, other := range sites { + _, directory, _ := meshView(t, nodes[other]) + for _, r := range directory { + if !slices.Contains(peerIDStrings(routers["a"].peerID, routers["b"].peerID, routers["c"].peerID), r) { + t.Errorf("node %s holds a directory session with %s, which is not a router", other, r) + } + if r == routers[s].peerID.String() { + want++ + } + } + } + if got := routers[s].gauge(t, "agentmesh_router_authenticated_peers"); got != want { + t.Errorf("router %s authenticated %v peers, want %v (its node, two routers, the control plane, and the nodes that hold it for their keys)", s, got, want) } } if t.Failed() { @@ -706,13 +747,20 @@ func TestCallerTransitsTheRouterItShares(t *testing.T) { callAcrossMesh(t, caller, "token-c", provider, "shared-tool") - // Router a authenticated the provider, the two routers and the control - // plane's publisher; the caller never came. Router b has both nodes. - if got := routerA.gauge(t, "agentmesh_router_authenticated_peers"); got != 4 { - t.Errorf("router a authenticated %v peers, want 4: the caller must transit router b, which it holds", got) + // The caller holds router b, which the provider's record names, so the + // call went through it and no handshake was run for transit. Router a + // may still hold the caller for its DHT: the provider's key lands on + // the closest routers, which on a mesh of three is all of them. + for _, kind := range []string{"listed", "unlisted"} { + for _, result := range []string{"ok", "failed"} { + series := fmt.Sprintf(`agentmesh_node_transit_handshakes_total{result="%s",router="%s"}`, result, kind) + if got := nodeCounter(t, caller, series); got != 0 { + t.Errorf("%s = %v, want 0: the caller must transit router b, which it holds", series, got) + } + } } - if got := routerB.gauge(t, "agentmesh_router_authenticated_peers"); got != 5 { - t.Errorf("router b authenticated %v peers, want 5", got) + if got := routerB.gauge(t, "agentmesh_router_authenticated_peers"); got < 5 { + t.Errorf("router b authenticated %v peers, want at least 5: both nodes, two routers, the control plane", got) } if t.Failed() { t.Logf("--- caller ---\n%s", caller.log())