Files
tasks/api/contextpropagation/v1/message.pb.go
7b1bbc0743 Use proto-serialized context metadata in gRPC trailers (#10269)
## What changed
- Replaced per-key trailer format with a single protobuf
`ContextMetadata` message serialized into `contextmetadata-bin` trailer
key
- gRPC automatically base64-encodes the `-bin` value, making arbitrary
bytes (including HTTP/2-unsafe control chars) transport-safe
- Writer emits both proto format and legacy per-key format for backward
compatibility during rolling deploys
- Reader prefers proto key, falls back to legacy per-key format for old
writers
- Wired `TrailerToContextMetadataInterceptor` in test server to match
production behavior

## Why
Workflow type names containing control characters (newlines, NUL, etc.)
cause the gRPC HTTP/2 framer to reject trailer values. A single proto
message in a `-bin` key is simpler than per-key `-bin` suffixes: one
trailer key, one serialization, no key naming constraints, cleaner
backward compat removal.

## How tested
- Unit tests for proto round-trip, dual-format emission, reader
preference, legacy fallback, HTTP/2 safety
- Integration test suite (TestWorkflowTypeEncodingSuite) with control
chars, UTF-8, long names, -bin suffix workflow types
- All existing tests pass

## Risks
- During rolling deploy, old writers emit only legacy keys. New readers
handle this via fallback path. No data loss.
- After full rollout, legacy key emission can be removed in a follow-up.

---

> [!NOTE]
> **Medium Risk**
> Changes how context metadata is encoded/decoded in gRPC trailers,
which can affect cross-version compatibility and observability of
propagated metadata. Backward-compatible legacy fallback and extensive
unit/integration tests reduce the rollout risk.
> 
> **Overview**
> **Switches context-metadata propagation in gRPC trailers to a single
proto-encoded payload.** Server-side `ContextMetadataInterceptor` now
serializes all context metadata into a new `ContextMetadata` protobuf
and emits it under `contextmetadata-bin`, avoiding HTTP/2-unsafe control
characters in values.
> 
> **Maintains rolling-deploy compatibility.** Writers still emit legacy
per-key trailers (skipping unsafe values), and the client-side
`TrailerToContextMetadataInterceptor` now *prefers* the proto trailer
and falls back to legacy keys (including unprefixed well-known keys)
when needed.
> 
> Adds the new `contextpropagation/v1` proto + generated Go types, plus
unit tests around proto/legacy behavior and an integration suite
(`WorkflowTypeEncodingSuite`) covering control characters, UTF-8, long
names, and `-bin` suffix workflow types.
> 
> <sup>Reviewed by [Cursor Bugbot]() for commit
e1d772fc3fe8e136fded479415f8f049ac896054. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>

---------

Co-authored-by: Hanzo Dev <[email protected]>
2026-05-18 15:12:25 +00:00

134 lines
5.6 KiB
Go

// Code generated by protoc-gen-go. DO NOT EDIT.
// plugins:
// protoc-gen-go
// protoc
// source: temporal/server/api/contextpropagation/v1/message.proto
package contextpropagation
import (
reflect "reflect"
sync "sync"
unsafe "unsafe"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
// ContextMetadata carries all context metadata key-value pairs in a single
// protobuf message. It is serialized into a gRPC trailer under the
// "contextmetadata-bin" key. The "-bin" suffix causes gRPC to base64-encode
// the value on the wire, making it safe for arbitrary byte sequences
// (including HTTP/2-unsafe control characters in workflow type names).
type ContextMetadata struct {
state protoimpl.MessageState `protogen:"open.v1"`
Entries map[string]string `protobuf:"bytes,1,rep,name=entries,proto3" json:"entries,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *ContextMetadata) Reset() {
*x = ContextMetadata{}
mi := &file_temporal_server_api_contextpropagation_v1_message_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *ContextMetadata) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ContextMetadata) ProtoMessage() {}
func (x *ContextMetadata) ProtoReflect() protoreflect.Message {
mi := &file_temporal_server_api_contextpropagation_v1_message_proto_msgTypes[0]
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 ContextMetadata.ProtoReflect.Descriptor instead.
func (*ContextMetadata) Descriptor() ([]byte, []int) {
return file_temporal_server_api_contextpropagation_v1_message_proto_rawDescGZIP(), []int{0}
}
func (x *ContextMetadata) GetEntries() map[string]string {
if x != nil {
return x.Entries
}
return nil
}
var File_temporal_server_api_contextpropagation_v1_message_proto protoreflect.FileDescriptor
const file_temporal_server_api_contextpropagation_v1_message_proto_rawDesc = "" +
"\n" +
"7temporal/server/api/contextpropagation/v1/message.proto\x12)temporal.server.api.contextpropagation.v1\"\xb0\x01\n" +
"\x0fContextMetadata\x12a\n" +
"\aentries\x18\x01 \x03(\v2G.temporal.server.api.contextpropagation.v1.ContextMetadata.EntriesEntryR\aentries\x1a:\n" +
"\fEntriesEntry\x12\x10\n" +
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01BDZBgo.temporal.io/server/api/contextpropagation/v1;contextpropagationb\x06proto3"
var (
file_temporal_server_api_contextpropagation_v1_message_proto_rawDescOnce sync.Once
file_temporal_server_api_contextpropagation_v1_message_proto_rawDescData []byte
)
func file_temporal_server_api_contextpropagation_v1_message_proto_rawDescGZIP() []byte {
file_temporal_server_api_contextpropagation_v1_message_proto_rawDescOnce.Do(func() {
file_temporal_server_api_contextpropagation_v1_message_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_temporal_server_api_contextpropagation_v1_message_proto_rawDesc), len(file_temporal_server_api_contextpropagation_v1_message_proto_rawDesc)))
})
return file_temporal_server_api_contextpropagation_v1_message_proto_rawDescData
}
var file_temporal_server_api_contextpropagation_v1_message_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
var file_temporal_server_api_contextpropagation_v1_message_proto_goTypes = []any{
(*ContextMetadata)(nil), // 0: temporal.server.api.contextpropagation.v1.ContextMetadata
nil, // 1: temporal.server.api.contextpropagation.v1.ContextMetadata.EntriesEntry
}
var file_temporal_server_api_contextpropagation_v1_message_proto_depIdxs = []int32{
1, // 0: temporal.server.api.contextpropagation.v1.ContextMetadata.entries:type_name -> temporal.server.api.contextpropagation.v1.ContextMetadata.EntriesEntry
1, // [1:1] is the sub-list for method output_type
1, // [1:1] is the sub-list for method input_type
1, // [1:1] is the sub-list for extension type_name
1, // [1:1] is the sub-list for extension extendee
0, // [0:1] is the sub-list for field type_name
}
func init() { file_temporal_server_api_contextpropagation_v1_message_proto_init() }
func file_temporal_server_api_contextpropagation_v1_message_proto_init() {
if File_temporal_server_api_contextpropagation_v1_message_proto != nil {
return
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_temporal_server_api_contextpropagation_v1_message_proto_rawDesc), len(file_temporal_server_api_contextpropagation_v1_message_proto_rawDesc)),
NumEnums: 0,
NumMessages: 2,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_temporal_server_api_contextpropagation_v1_message_proto_goTypes,
DependencyIndexes: file_temporal_server_api_contextpropagation_v1_message_proto_depIdxs,
MessageInfos: file_temporal_server_api_contextpropagation_v1_message_proto_msgTypes,
}.Build()
File_temporal_server_api_contextpropagation_v1_message_proto = out.File
file_temporal_server_api_contextpropagation_v1_message_proto_goTypes = nil
file_temporal_server_api_contextpropagation_v1_message_proto_depIdxs = nil
}