45 KiB
VD-27: gRPC-Web unary와 server stream
- 상태: Accepted design — common coordinator available, wire adapter pending
- 결정일: 2026-07-28
- provider-neutral Browser RPC V3 coordinator:
AVAILABLE_NOT_COMPOSED - gRPC-Web unary/server-stream reference adapter:
DESIGNED_NOT_IMPLEMENTED - product gRPC-Web composition:
NOT_SELECTED - client-streaming/bidirectional-streaming guarantee:
PLATFORM_LIMITED - 관련 결정: VD-13, VD-23, VD-24, VD-25, VD-28, VD-29, VD-30
- 상세 설계: Protobuf browser transport와 REST Gateway
- 상위 API 설계: API contract, Schema, Mapper와 Server State
배경
현재 source에는 provider-neutral operation/profile registry, application port와
unary/server-stream lifecycle coordinator가 있다. package direct dependency,
generated output, .proto/descriptor, Buf/protoc config, proxy route와 actual
browser test에는 gRPC-Web capability가 없다.
browser는 native gRPC transport를 직접 실행하지 않는다. gRPC-Web 또는 선택한 Connect protocol을 지원하는 BFF/Envoy/gateway가 필요하다. protobuf backend가 있다는 이유만으로 browser client에 generated SDK를 노출하지 않는다.
gRPC-Web은 unary와 server streaming에 적합할 수 있지만 browser baseline에서 client streaming과 bidirectional streaming을 일반 gRPC처럼 보장하지 않는다. 요구 의미에 따라 upload/job은 REST, one-way server event는 SSE, 진짜 duplex는 WebSocket/WebTransport 또는 별도 protocol로 다시 설계한다.
결정
1. Protocol profile을 혼용하지 않는다
선택 가능한 provider profile 예:
GRPC_WEB_BINARY
GRPC_WEB_TEXT
GRPC_WEB_BINARY | GRPC_WEB_TEXT는 wire transfer/framing profile이고 Protobuf
binary와 JSON message encoding을 뜻하는 이름이 아니다. Connect protocol
CONNECT_V1은 이 목록에서 선택하지 않으며 VD-29의 Connect state machine과
registry가 별도로 소유한다.
gRPC-Web transport implementation도 별도 discriminant로 고정한다.
GrpcWebTransportRuntimeKind
= OFFICIAL_GRPC_WEB_XHR
| CONNECT_WEB_GRPC_WEB_FETCH
| CUSTOM_FETCH_FRAMED
GrpcWebClientApiKind
= CALLBACK_STREAM
| PROMISE_UNARY
| ASYNC_ITERABLE
GrpcWebMessageEncoding
= PROTO
| JSON
한 operation/provider는 하나의 exact profile에 binding한다.
- gRPC-Web framing/status와 Connect envelope/status를 같은 decoder로 추측하지 않음
- content type을 보고 runtime이 vendor를 자동 선택하지 않음
OFFICIAL_GRPC_WEB_XHR는 pinned officialgrpc-webruntime이 XHR, request framing, text base64, response framing과 status 해석을 소유한다. 이를 custom Fetch/raw-frame 경로라고 기록하지 않음CONNECT_WEB_GRPC_WEB_FETCH는 VD-29가 소유하는 pinned Connect-WebcreateGrpcWebTransport()다. Fetch를 사용하지만 request/response gRPC-Web binary envelope, JSON/Proto serialization, status와 trailer decode를 selected runtime이 소유하므로CUSTOM_FETCH_FRAMEDraw decoder로 분류하지 않음CUSTOM_FETCH_FRAMED만 browser FetchReadableStream,AbortController와 adapter-owned raw frame decoder를 사용함- official grpc-web runtime의 unary는 binary 또는 text, server-stream은
grpcwebtext만 허용 - official runtime의
SERVER_STREAM + GRPC_WEB_BINARYregistry row는 거절 - Connect-Web gRPC-Web runtime은 binary envelope의 Proto unary/server-stream을
후보로 허용한다. JSON은 exact provider/runtime fixture가 있을 때만 별도
messageEncoding=JSONrow이고GRPC_WEB_TEXT는 항상 거절한다. - Connect-Web gRPC-Web server stream은
ASYNC_ITERABLEAPI와 Chromium, Firefox, WebKit 및 actual proxy의 incremental-read/cancel/backpressure/trailer evidence가 있을 때만 허용 - custom Fetch binary stream은 selected runtime ID, 세 browser와 actual proxy의 incremental-read/cancel/backpressure evidence가 있을 때만 별도 profile
- official production composition은 cancel handle과 terminal status event를
유지하는
CALLBACK_STREAM을 기본으로 한다. officialPROMISE_UNARY는.on(...)metadata/status event와 direct call handle을 제공하지 않는다. pinned runtime/codegen이PromiseCallOptions.signal을 지원하면 cancellation은 그AbortSignal로 충족할 수 있지만, required status/metadata event는 충족하지 못하므로 registry에서 거절 - text mode의 base64 overhead를 message/total budget에 반영
이 ADR의 state machine은 gRPC-Web을 기준으로 한다. Connect-Web runtime이
CONNECT_V1을 선택하는 경우는 이 ADR의 runtime row가 아니라 VD-29의 별도
protocol/profile/fixture를 사용하고 gRPC-Web frame을 재사용하지 않는다.
2. Application 경계
feature gateway
-> registered semantic operation
-> GrpcWebOperationDefinition
-> generated request mapping
-> selected gRPC-Web transport runtime
-> runtime-owned OR custom adapter-owned frame/status/trailer validation
-> selected runtime/generated response decode
-> semantic runtime validation
-> boundary mapper
-> application result or mapped stream event
application/domain/presentation/query cache에 노출하지 않는다.
- generated service client
- generated protobuf message
- service/method string
- endpoint/metadata
- raw frame/trailer/status details
- protobuf bigint/bytes representation
3. Operation definition
GrpcWebOperationV1
protocol = GRPC_WEB_V1
semanticOperationId
providerId
clientRuntimeId
transportRuntimeKind
clientApiKind
grpcWebWireSpecRevision
transportProfile
messageEncoding = PROTO | JSON
fullyQualifiedService
method
rpcKind = UNARY | SERVER_STREAM
requestMessageId
responseMessageId
descriptorArtifactId
descriptorDigest
requestSemanticSchemaId
responseSemanticSchemaId
mapperId
statusProfileId
responseHttpStatusProfileId
authProfileId
csrfProfileId
replayPolicy
deadlineProfileId
retryProfileId
retryOwner = FRONTEND_ADAPTER | ENVOY | NONE
proxyProfileId
headerBudgetProfileId
serverStateProfileId | null
maxRequestMessageBytes
maxResponseMessageBytes
maxResponseMessages
maxTotalResponseBytes
idleDeadlineMs
totalDeadlineMs
compressionProfile
owner
registry가 거절:
- client/bidi method
- selected
(rpcKind, transportProfile, clientRuntimeId, browser/proxy profile)이 capability matrix에 없음 - operation/provider의 runtime kind 또는 client API kind가 다름
- official runtime인데 custom Fetch/raw-frame/AbortSignal capability를 요구하거나 custom runtime인데 adapter-owned framing/status decoder가 없음
- official runtime인데
messageEncoding=JSON이거나 Connect-Web runtime인데transportProfile=GRPC_WEB_TEXT임 - Connect-Web JSON row에 exact provider media/status/generated-code fixture가 없거나
Connect-Web server stream이
clientApiKind=ASYNC_ITERABLE이 아님 - official
PROMISE_UNARY인데 terminal metadata/status event가 operation requirement이거나 pinned runtime/codegen에PromiseCallOptions.signalcapability가 없는데 explicit cancellation이 requirement임 - unknown service/method/message/descriptor
- descriptor digest mismatch
- unary인데 response max count가 1이 아님
- server stream인데 queue/idle/total/message/count budget 없음
- non-replayable command에 retry
- frontend/Envoy/backend 중 retry owner가 둘 이상이거나 selected owner의 exact attempt/backoff/deadline policy가 없음
- executable proxy/header budget profile이 없음
- long-running stream에 ordinary Query profile
- caller-provided endpoint/metadata
4. Protobuf source와 codegen
authenticated proto/Buf module
-> immutable source/descriptor set
-> provenance + digest
-> lint
-> breaking comparison against production baseline
-> pinned deterministic generation
-> adapter-private client/messages
-> semantic validators + mapper
governance:
- field number 재사용 금지
- removed field number/name reserve
- package/service/method full name 안정성
- proto3 presence/optional 의미
- enum zero/unknown numeric value
- oneof absence/unknown case
- map ordering을 application identity로 사용하지 않음
- bytes/message nesting/repeated count ceiling
- Timestamp/Duration valid range/nanos
- int64/uint64 projection policy
- unknown fields library behavior와 mapper projection
generated decoder success는 semantic domain proof가 아니다. VD-24 runtime semantic validator가 range, presence, enum/oneof와 collection budget을 확인한다.
CI:
- pinned Buf/protoc/plugin/runtime/Node versions
- source/descriptor/generated digest
- clean regenerate diff 0
- Buf lint/breaking
- generated import boundary
- N/N-1 descriptor fixture
- generated code dependency/SBOM/removal
5. Provider endpoint와 proxy
GrpcWebProviderProfile
providerId
fixedHttpsOrigin
pathPrefix
clientRuntimeId
transportRuntimeKind
clientApiKind
grpcWebWireSpecRevision
transportProfile
messageEncoding
browserCapabilityProfileId
proxyCapabilityProfileId
proxyProfileId
headerBudgetProfileId
responseHttpStatusProfileId
credentialsMode
corsProfileId
referrerPolicy
redirect = ERROR
maximumEnvelopeBytes
operation과 provider의 runtime kind/client API/runtime version/wire spec/transport/
message encoding/proxy/header tuple은 exact match해야 한다. moving latest wire
spec이나 library auto-detection을 compatibility proof로 사용하지 않는다.
GrpcWebHttpStatusProfile
allowedGrpcMediaHttpStatuses[]
missingGrpcStatusMap = CANONICAL_HTTP_TO_GRPC_V1
emitSyntheticGrpcStatusWireHeader = false
httpGrpcConsistencyFixtureId
status/media/source matrix:
-
valid
grpc-status가 있으면 HTTP status와 무관하게 그 값이 authoritative다. accepted gRPC-Web media와 terminal source가 있으면 bounded decode한 뒤 selected provider의 HTTP↔gRPC consistency fixture를 별도로 검사한다. -
grpc-status가 없으면 client는 wire response를 변조하지 않고 아래 canonical HTTP→gRPC mapping으로 internal normalized status를 만든다. synthetic response header를 생성하거나 이 표를 server-side gRPC→HTTP 역매핑으로 사용하지 않는다.HTTP status missing grpc-status의 normalized gRPC status400INTERNAL401UNAUTHENTICATED403PERMISSION_DENIED404UNIMPLEMENTED429,502,503,504UNAVAILABLE그 밖의 모든 status UNKNOWN -
invalid/missing gRPC media, missing terminal source와 malformed body는 위 normalized status와 별개로
protocolViolation=true인 transport/contract failure로 기록한다. HTTP 2xx라도 missinggrpc-status를 success로 만들지 않는다. -
429를 domainRATE_LIMITED로 곧바로 해석하지 않는다. canonicalUNAVAILABLEnormalization 뒤 selected application status mapper가 별도 증거가 있을 때만 rate-limit 의미를 부여한다. -
operation/provider
responseHttpStatusProfileId가 다르면 composition을 거절한다.
same-origin BFF/Envoy gateway를 권장한다.
GrpcWebCorsProfile
topology = SAME_ORIGIN | CROSS_ORIGIN
allowedOrigins[]
allowCredentials
allowMethods = [POST, OPTIONS]
allowHeaders[]
exposeHeaders[]
forwardNotMatchingPreflights = false
clientSuppressCorsPreflight = false
maxAgeSeconds
cross-origin profile과 actual provider evidence:
- exact allow-origin, wildcard credential 금지
POST,OPTIONS외 method 금지- 최소
content-type,x-grpc-web,x-user-agent, selected deadline의grpc-timeout과 exact auth/CSRF/idempotency/trace header만 allowlist - trailers-only response header를 위해
grpc-status,grpc-message, rich details를 선택한 profile은grpc-status-details-bin까지 expose - official runtime의 preflight 억제/query-parameter metadata 광고를 사용하지
않는다. credential/metadata가 URL, browser history, access log로 이동하지 않게
client option
suppressCorsPreflight=false로 고정 - Envoy CORS filter는 non-matching preflight를 upstream으로 전달하지 않고 fail-closed한다.
- TLS
- redirect 없음
- stream buffering 없음
same-origin profile도 CSRF/Fetch Metadata/Origin 검증을 생략한다는 뜻이 아니다.
GrpcWebHeaderBudgetProfile
maxRequestHeaderListBytes
maxResponseHeaderListBytes
maxTrailerListBytes
maxHeaderFieldCount
maxBinaryMetadataDecodedBytes
- 각 값은 양의 유한값이며 operation/provider/proxy에서 exact match한다. native gRPC protocol이 제안하는 header-list별 8 KiB를 initial ceiling으로 검토하되 제품이 다른 값을 택하면 actual runtime/proxy fixture와 함께 고정한다.
- gateway는 browser/runtime allocation 전에 request/response header hard cap을 집행하고 custom trailer parser는 field count, encoded/decoded bytes cap을 집행한다. official runtime은 raw frame 이전 cap을 application code가 소유한다고 주장하지 않는다.
- registered
-binmetadata만 허용한다. padded/unpadded base64를 처리하고, transport가 duplicate binary header를 comma-join했다면 각 value를 먼저 분리한 뒤 개별 decode한다. unknown metadata는 application에 projection하지 않는다.
GrpcWebProxyProfile
gatewayKind = ENVOY_GRPC_WEB | SELECTED_BFF
gatewayVersion
immutableConfigDigest
downstreamTlsProfileId
upstreamProtocol = HTTP2
filterOrder = [grpc_web, cors, router]
routeTimeoutMs = DISABLED | positive-integer
hcmStreamIdleTimeoutMs
routeIdleTimeoutMs
maxStreamDurationMs
grpcTimeoutHeaderMaxMs
grpcTimeoutHeaderOffsetMs
buffering = DISABLED
flushProfileId
localReplyProfileId
retryOwner
headerBudgetProfileId
Envoy profile은 개념 필드로만 끝내지 않고 pinned v3 config artifact의 다음 위치로 render한다.
| 요구 | Envoy v3 config 위치 |
|---|---|
| bridge/filter order | HCM http_filters의 envoy.filters.http.grpc_web, envoy.filters.http.cors, envoy.filters.http.router 순서 |
| exact operation route | Route.match의 registered service/method path와 RouteAction.cluster |
| upstream HTTP/2 | cluster typed_extension_protocol_options의 envoy.extensions.upstreams.http.v3.HttpProtocolOptions.explicit_http_config.http2_protocol_options |
| total/idle/max duration | RouteAction.timeout, RouteAction.idle_timeout, RouteAction.max_stream_duration 및 HCM stream_idle_timeout |
| deadline clamp | RouteAction.max_stream_duration.grpc_timeout_header_max와 grpc_timeout_header_offset |
| CORS | route/virtual-host typed_per_filter_config[envoy.filters.http.cors]; deprecated cors shortcut을 새 profile에 사용하지 않음 |
| buffer | envoy.filters.http.buffer를 설치하지 않거나 selected route의 BufferPerRoute를 disabled |
| retry | RouteAction.retry_policy; owner가 Envoy가 아니면 absent, hedge policy absent |
Envoy grpc_web filter 또는 selected gateway가:
- pinned version/config digest와 exact route/service/method allowlist
- Envoy일 때
grpc_web -> cors -> routerfilter 순서와 upstream HTTP/2 - exact content type/framing, local reply와 missing-status fixture
- message/body/header/deadline cap
- terminal trailers/status preservation
- server stream flush/backpressure
- auth/CSRF/Origin
- retry owner와 upstream status mapping
을 staging에서 증명해야 한다. Envoy route timeout 기본값에 기대지 않는다.
server stream route는 timeout: 0s 또는 application total deadline보다 긴 exact
finite value를 선택하고, 별도 finite stream-idle/max-stream-duration cap을 둔다.
grpc_timeout_header_max와 offset은 proxy가 browser deadline을 늘리지 않고
필요하면 browser보다 먼저 종료하도록 맞춘다. route/HCM idle과 buffer/flush도
명시하며 selected route에서 complete-body buffer filter를 비활성화한다. fake
fetch나 15초 미만 fixture만으로 provider conformant가 아니다.
6. Request
validated application input
-> generated request message
-> semantic validation
-> bounded selected Proto/JSON encode
-> transport-owned metadata/auth/CSRF
-> final invariant
-> selected transport runtime
-> OFFICIAL_GRPC_WEB_XHR: runtime-owned frame/base64 + XHR
-> CONNECT_WEB_GRPC_WEB_FETCH: runtime-owned frame/status + Fetch
-> CUSTOM_FETCH_FRAMED: adapter-owned frame/base64 + Fetch
- service/method path는 registry가 만든다.
- wire method는
POST로 고정한다. - binary Proto baseline은
Content-Type: application/grpc-web+proto, Connect-Web JSON provider row는Content-Type: application/grpc-web+json, text baseline은Content-Type: application/grpc-web-text와Accept: application/grpc-web-text를 사용한다. exact selected media type은 transport profile에 고정하고 response media를 검증한다. X-Grpc-Web: 1과X-User-Agent를 selected runtime이 소유한다. browserUser-Agent를 대신 설정하거나 caller가 이 fixed header를 override하지 못한다.- caller가 endpoint, metadata,
grpc-timeout또는 transport header를 제출하지 않는다. - request message byte cap을 selected Proto/JSON encode 전후 검사한다.
- official runtime은 request framing/base64를 소유하고 generated client에 validated
message만 넘긴다. Connect-Web runtime은 selected Proto/JSON serialization과
binary envelope를 소유하고 VD-29의 bounded
Transport/Fetch evidence를 충족한다. custom runtime만 encoded Protobuf를 bounded gRPC-Web data frame으로 직접 감싼다. - official grpc-web baseline의 message compression flag는 disabled이며 set bit를
거절한다. unary full-body HTTP
Content-Encoding은 별도 provider profile과 decoded-body ceiling으로 다룬다. custom message compression은 exact runtime/proxy fixture 없이는 승인하지 않는다. - credential owner가 URL/path/method/body digest를 바꿀 수 없다.
- auth-required operation은 unavailable/unauthenticated에서 fetch 0회다.
- cookie profile은 POST/custom content type만으로 CSRF가 해결됐다고 보지 않고 exact Origin/Fetch Metadata/CSRF proof를 요구한다.
7. Frame decoder
raw frame pipeline은 CUSTOM_FETCH_FRAMED에만 적용한다. gRPC-Web binary response를
whole Uint8Array로 무제한 materialize하지 않는다.
Fetch ReadableStream
-> bounded incremental frame prefix
-> validate flag/type/compression
-> validate declared frame length
-> bounded exact frame payload
-> data message OR trailer block
OFFICIAL_GRPC_WEB_XHR에서는 pinned official runtime이 XHR body, binary/text
framing, base64와 terminal status parsing을 소유한다. adapter가 이미 decoded된
message를 raw frame처럼 다시 parse하지 않는다. wire/header/message hard cap은
gateway/upstream이 allocation 전에 집행하고, adapter는 runtime output의
message-count/semantic/collection/total budget을 다시 검사한다. official runtime이
내부에서 거절해야 하는 malformed framing/status는 pinned-version conformance
fixture로 증명한다.
CONNECT_WEB_GRPC_WEB_FETCH도 selected Connect-Web Transport가 Fetch, binary
envelope, Proto/JSON decode와 terminal status를 소유한다. stock transport 앞의
bounded Fetch wrapper 또는 verified upstream hook이 response byte ceiling을
집행할 수는 있지만 gRPC-Web frame을 별도 custom decoder로 다시 추측하지 않는다.
required pre-decode cap/terminal invariant hook과 malformed corpus를 exact package
version에서 증명하지 못하면 VD-29와 이 ADR 모두 DESIGNED_NOT_IMPLEMENTED를
유지한다.
검증:
- truncated prefix/payload
- unknown/reserved flag
- negative/overflow/over-limit length
- disallowed compression flag
- decompressed message cap
- trailer before/after allowed data count
- duplicate body trailer 또는 body/header terminal source 충돌
- data after terminal trailer
- extra bytes after terminal
- total message/frame/byte ceiling
- idle and total deadline
reader는 failure/cancel/limit/terminal 뒤 cancel/release한다.
text mode:
CONNECT_WEB_GRPC_WEB_FETCH는 이 branch에 들어오지 않고 build/boot 전에 거절한다.- response 전체가 단일 valid base64 entity라고 가정하지 않는다. runtime flush로 padding이 중간에 나타난 뒤 다음 base64 entity가 이어질 수 있으므로 concatenated padded/unpadded base64 entities를 수용한다.
- custom runtime은 stateful incremental decoder를 사용한다. padding은 해당 entity를 끝내고 decoder quantum을 reset하지만 padding 또는 새 entity가 browser chunk, gRPC-Web frame과 일치한다고 추정하지 않는다.
- invalid alphabet/padding/whitespace policy
- encoded와 decoded byte cap
- browser chunk가 base64 quantum/frame prefix와 일치한다고 가정하지 않음
- decoded byte stream만 공통 bounded frame decoder에 전달하고
Content-Transfer-Encoding에 의존하지 않음 - binary와 별도 conformance fixture
8. Trailer와 status
success는 HTTP success만으로 결정하지 않는다.
HTTP admission
+ valid gRPC-Web frame sequence
+ exactly one TerminalStatusSource
+ exactly one valid grpc-status
+ grpc-status = 0
TerminalStatusSource
= BODY_TRAILER_FRAME
| ZERO_BODY_TRAILERS_ONLY_RESPONSE_HEADERS
custom runtime은 두 source를 raw parser에서 직접 판정한다. official runtime은
pinned runtime이 같은 protocol rule을 집행하고 CALLBACK_STREAM의 normalized
status event를 adapter가 소비한다. Connect-Web runtime은 VD-29의 bounded
Transport와 generated Promise/AsyncIterable error/terminal contract로 같은
gRPC-Web authority를 보존한다. 두 runtime 모두 raw source를 application code가
직접 관찰했다고 거짓으로 기록하지 않고 malformed/missing/duplicate source fixture를
selected runtime conformance에서 통과시킨다. missing grpc-status로 section 5의
HTTP mapping을 실행한 결과는 valid terminal source나 success가 아니다.
BODY_TRAILER_FRAME과 response-header grpc-status가 동시에 있거나 값이
충돌하면 contract failure다. trailers-only header source는 body/data frame이
0개일 때만 허용한다. 이후 unary/server-stream cardinality를 별도로 판정한다.
trailer/status validator:
- bounded ASCII/header parser
- body trailer name은 lowercase만 허용하고 모든 source는 case-normalized duplicate detection
- allowed name/value syntax와
GrpcWebHeaderBudgetProfile의 bounded field count/encoded/decoded bytes grpc-statusexactly once, canonical decimal0..16- percent-decoded grpc-message byte cap
- malformed
grpc-message는 raw 노출 없이 safe replacement/omission하되 valid authoritative status 자체를 잃지 않음 - unknown/raw trailer 폐기
grpc-status-details-bin은 count/decoded byte cap과 allowlisted protobufAnytype만 허용- decoded outer
google.rpc.Status.code가grpc-status와 exact match - rich details는 non-OK status에서만 허용
raw grpc-message, status details와 metadata를 application/log에 보내지 않는다.
9. Unary state machine
순서가 있는 판정:
- network/CORS/final URL 실패처럼 HTTP response 자체가 없으면 exact transport failure mapping이며 gRPC status가 있다고 가장하지 않는다.
- invalid/unaccepted gRPC-Web media, malformed local reply 또는 redirect는 body를 gRPC frame으로 추측하지 않고 contract/transport failure로 닫는다. valid exposed status가 없으면 section 5의 canonical HTTP→gRPC normalization도 함께 기록한다.
- accepted gRPC-Web media는 profile이 허용한 HTTP status에서 frame과 terminal source를 bounded decode한다. invalid/truncated/oversize frame은 contract failure.
- accepted media인데 valid terminal source 또는
grpc-status가 없으면 canonical HTTP→gRPC normalized status와protocolViolation=true로 실패한다. 모든 2xx와 mapping 표에 없는 status는UNKNOWN이며 cache/data success는 0이다. - HTTP status와 terminal source/status matrix mismatch: contract failure.
- non-OK terminal status: status mapping, data/cache success 0.
- OK status + data message 0개:
contract failure.
google.protobuf.Empty도 payload length가 0인 data frame 하나다. - OK status + data message 정확히 1개: generated decode → semantic schema → mapper → generation fence → success.
- OK status + data message 2개 이상: contract failure.
trailers-only non-OK response는 safe failure가 될 수 있다. trailers-only OK + unary zero message는 contract failure다. non-OK에서 받은 data message를 partial success로 사용하지 않는다. server-stream의 zero-event + OK는 stream profile이 허용할 수 있다.
10. Status mapping
operation status profile이 exact allowlist/mapping을 소유한다.
| gRPC status | 기본 safe mapping |
|---|---|
OK |
success state machine 계속 |
CANCELLED |
local signal이면 REQUEST_ABORTED, 아니면 provider failure |
DEADLINE_EXCEEDED |
REQUEST_TIMEOUT |
UNAUTHENTICATED |
AUTH_REQUIRED |
PERMISSION_DENIED |
FORBIDDEN |
NOT_FOUND |
NOT_FOUND 또는 existence-hiding |
ALREADY_EXISTS |
CONFLICT |
ABORTED |
CONFLICT/optimistic concurrency |
FAILED_PRECONDITION |
typed precondition/contract profile |
INVALID_ARGUMENT, OUT_OF_RANGE |
VALIDATION_REJECTED |
RESOURCE_EXHAUSTED |
RATE_LIMITED 또는 bounded resource failure |
UNAVAILABLE |
retry-eligible server failure |
UNIMPLEMENTED |
provider/contract incompatible |
INTERNAL, UNKNOWN |
safe server failure |
DATA_LOSS |
integrity/contract failure, retry default off |
validation details는 allowlisted google.rpc.BadRequest 같은 registered message
type과 bounded field path/code만 projection한다. arbitrary Any type URL/message를
decode하거나 application에 전달하지 않는다.
11. Deadline과 cancellation
total logical deadline
-> local scheduler
-> remaining attempt deadline
-> OFFICIAL_GRPC_WEB_XHR
-> absolute Unix epoch-ms `deadline` metadata
-> official runtime-owned grpc-timeout/XHR timer
-> returned call.cancel()
| pinned PromiseCallOptions.signal
-> CONNECT_WEB_GRPC_WEB_FETCH
-> positive remaining timeoutMs
-> runtime-owned grpc-timeout/Fetch timer
-> AbortSignal + AsyncIterator close
-> CUSTOM_FETCH_FRAMED
-> bounded grpc-timeout wire metadata
-> Fetch AbortController
-> response reader/idle timer
deadline은 auth attach, network, retry/backoff, frame read, protobuf decode, semantic validation과 mapper를 포함한다.
- official runtime public API에는 현재 시각 기준 남은 duration이 아니라 absolute
Unix timestamp milliseconds인
deadlinemetadata를 전달한다. pinned runtime이 이를 wiregrpc-timeout과 XHR timer로 변환한다. - Connect-Web runtime에는 VD-29의 positive remaining
timeoutMs와 localAbortSignal을 전달하고 runtime이 wireGrpc-Timeout을 소유한다.timeoutMs0 이하를 timeout 없음으로 해석할 수 있으므로 remaining이 0 이하이면 runtime을 호출하지 않는다. - custom runtime만 remaining duration을
grpc-timeout으로 encode한다. 값은 최대 8자리 양의 정수와H | M | S | m | u | nunit grammar를 지키고 local remaining deadline을 늘리지 않게 clamp한다. - retry/auth recovery의 각 physical attempt 직전에 elapsed time을 차감해 absolute
deadline 또는
grpc-timeout을 다시 계산한다. 최초 attempt 값을 재사용하지 않는다. - attempt timer는 remaining total보다 길지 않음
- server stream은 idle + total deadline 둘 다
- Envoy의 route/max-stream/
grpc_timeout_header_max/offset은 frontend deadline을 늘리지 않는다. proxy가 먼저 끊는 profile이면 그 차이를 명시하고 normalized terminal reason을 검증한다. - official callback unary/server-stream은 보관한 returned call의
cancel(), Promise unary는 pinned runtime/codegen이 지원하는PromiseCallOptions.signal로 중단한다. Promise API에 direct call handle이나.on(...)status/metadata event가 있다고 가장하지 않는다. Connect-Web call은AbortSignal과 server-streamAsyncIteratorclose로 중단한다. custom call은AbortController.abort()와 reader cancel/release로 중단한다. - caller/navigation/logout/runtime close를 별도 cancellation reason으로 유지
- local abort가 server command rollback을 보장하지 않음
- gateway는 downstream disconnect를 upstream cancel로 전달하고 backend handler는 cancellation을 cooperative하게 관찰하며 downstream RPC에도 remaining deadline과 cancel을 전파함
- timer/listener/reader를 모든 terminal path에서 정리
12. Replay와 retry
모든 gRPC method가 HTTP POST여도 semantic safety는 method registry가 결정한다.
SAFE
IDEMPOTENT
KEYED_COMMAND
NON_REPLAYABLE
GrpcWebRetryProfile
retryOwner = FRONTEND_ADAPTER | ENVOY | NONE
maxPhysicalAttempts
maxSleepMs
maxElapsedMs
initialBackoffMs
maxBackoffMs
backoffMultiplier
jitterProfile
retryableTransportOutcomes[]
retryableGrpcStatuses[]
retryPushbackProfile = UNSUPPORTED | SELECTED_EXTENSION
hedging = DISABLED
gRPC-Web protocol 자체는 browser retry를 규정하지 않고 official JS runtime은 native gRPC service-config retry를 제공하지 않는다. 따라서:
FRONTEND_ADAPTER는 application/provider extension retry다. Envoy/BFF/Query/ downstream service의 같은 logical operation retry를 끄고 physical attempt를 adapter가 계수한다.ENVOY를 선택하면 frontend와 Query retry를 끈다. exact route retry policy, per-try timeout과 total route/deadline budget을 고정한다. Envoy가grpc-statusresponse header에 대해서만 gRPC status retry를 할 수 있고 response trailers의 status로는 retry하지 못한다는 제약을 capability matrix에 기록한다. server stream route retry/hedging은 금지한다.NONE이면 모든 layer의 automatic retry를 끈다.- caller-provided
x-envoy-*retry/timeout header는 gateway에서 제거한다. grpc-retry-pushback-ms같은 metadata는 기본UNSUPPORTED다. exact runtime/provider/proxy fixture와 bounded parser를 가진 selected extension일 때만 사용하며 native gRPC transparent retry라고 표시하지 않는다.- route timeout은 모든 Envoy attempt를 포함한다. per-try와 backoff가 logical total deadline을 넘어가지 않으며 frontend/Envoy/backend의 retry multiplication을 fault injection으로 거절한다.
unary:
- safe/idempotent 또는 backend-proven keyed operation만 retry
- selected transient network/
UNAVAILABLE/approved resource condition만 - deadline/schema/status/frame/mapper failure retry 금지
- total attempt/sleep/elapsed ceiling
- hedging은 모든 production profile에서 off
auth recovery:
UNAUTHENTICATED에서 replay-safe operation만 one-time single-flight refresh- same request semantic input/idempotency/deadline
keyed command:
- logical command key + principal + service/method + deterministic request digest
- backend atomic dedupe/status/TTL evidence
- ambiguous outcome은 new key retry가 아니라 reconcile
server stream:
- Envoy retry는 off다. 첫 application event 전달 전 frontend opening retry는
retryOwner=FRONTEND_ADAPTER인SAFE | IDEMPOTENToperation만 original total deadline, monotonic physical-attempt와 sleep cap 안에서 가능 KEYED_COMMAND | NON_REPLAYABLEserver stream은 explicit backend resume/reconcile/dedupe profile이 없으면 registry에서 거절- event 전달 뒤 transport-only automatic replay 금지
- resume하려면 backend sequence/snapshot/resume token protocol 필요
13. Server-stream state machine
OPENING
-> STREAMING
-> COMPLETING
-> COMPLETED
OPENING | STREAMING
-> FAILED | CANCELLED | GAP | OVERFLOW
ServerStreamPort<Event>
open(bound request, signal)
-> AsyncIterable<Result<Event>>
-> close()
각 data frame:
- message/frame/total budget
- generated protobuf decode
- semantic schema
- boundary mapper
- scope/runtime generation
- sequence/snapshot protocol
- bounded queue admission
ServerStreamPort는 use case가 명시적으로 연 operation-bound outbound result로
남을 수 있다. 오직 제품이 runtime-wide external notification projection을
선택한 branch에서만 mapper 이후 event를 VD-28의 common scope/generation/
dedupe/gap/resync coordinator 또는 FeatureEventInput에 전달한다. protobuf
frame/message를 REALTIME_EVENT_V1 JSON으로 다시 감싸지 않으며 reconnect는 위
opening/resume 규칙이 계속 소유한다.
stream policy:
- max messages/bytes/duration
- idle deadline
- consumer backpressure
- bounded queue/high-water mark
- overflow outcome
- duplicate/gap/order
- resume token expiry
- terminal status source
- cancellation/unsubscribe
worker/timer keepalive로 infinite correctness를 주장하지 않는다.
14. Query cache integration
unary:
- mapped application result만 VD-25 TanStack query에 admission
- service/method/descriptor/protobuf bytes를 query key/value에 넣지 않음
retryOwner값과 무관하게 Query retry는 false다.NONE을 Query가 암묵적으로 대체하지 않으며 selected gRPC-Web retry owner만 physical attempt를 만든다.
finite server-stream aggregate:
mapped events
-> bounded staging reducer
-> valid terminal OK status
-> completeness/integrity/schema
-> generation fence
-> immutable aggregate
-> atomic Query commit
long-running stream:
- ordinary queryFn 아님
- registered reducer가 bounded snapshot을 만들거나 invalidation hint 발행
- event history 무한 cache 금지
- partial/truncated/gap stream을 complete query success로 쓰지 않음
- reconnect를 Query retry로 하지 않음
15. Protobuf mapping
VD-24 scalar policy를 사용한다.
- int64/uint64를 JS number로 자동 변환하지 않음
- safe range를 증명하거나 decimal string/application value로 mapping
- Timestamp/Duration range/nanos 확인
- bytes copy/size/classification
- map ordering 독립
- enum zero/unknown numeric
- oneof presence
- wrappers/optional/default distinction
- unknown field를 domain에 passthrough하지 않음
- generated message mutation/reuse 금지, immutable application projection 생성
16. Client/bidi streaming
primary status는 PLATFORM_LIMITED.
다음으로 fallback/re-design한다.
| 요구 | 대안 |
|---|---|
| large upload stream | REST/presigned multipart upload |
| browser event duplex | WebSocket/WebTransport 또는 별도 duplex protocol |
| server→client one-way + 분리 가능한 command | SSE + registered HTTP command |
| command sequence | server-owned job/session API |
| telemetry batch | bounded unary/REST batch |
generated service가 client/bidi RPC를 가진다는 이유로 browser에서 unary loop로 흉내 내지 않는다. ordering/backpressure/half-close semantics가 달라진다.
17. Security와 privacy
- fixed endpoint/service/method
- caller metadata/header 금지
x-envoy-*, transport/deadline header caller override 제거- preflight를 피하려 metadata/credential을 query parameter로 이동 금지
- credential/CSRF adapter confinement/final invariant
- request/response header, trailer, frame, message, decompressed cap
- rich status details allowlist
- raw grpc-message/payload/metadata/trailer log 금지
- generated debug JSON/stringifier를 production logging에 사용 금지
- server/proxy authorization와 method-level resource authorization
- frontend protobuf validation을 authorization으로 간주 금지
- query/cache에 generated message/token/metadata 없음
18. Observability
허용:
- semantic operation ID
- allowlisted service/method profile ID
- gRPC status bucket + HTTP status group
- logical/physical attempt, auth recovery, deadline bucket
- request/response message/total bytes/count bucket
- stream terminal reason/idle/gap/overflow bucket
- descriptor/provider/browser version low-cardinality bucket
- cache admission/invalidation outcome
금지:
- message/proto JSON
- metadata/trailer raw value
- grpc-message/status-details bytes
- resource/account/request ID actual value
- resume token/sequence high-cardinality value
19. Testing
protobuf/codegen:
- source provenance/digest
- Buf lint/breaking
- field reserve/presence/enum/oneof/int64/time fixture
- clean generation
- generated import boundary/removal
frame/status:
- official XHR, Connect-Web runtime-owned Fetch와 custom raw-frame Fetch runtime의 framing/status 책임이 섞이지 않음
- Connect-Web Proto/JSON binary-envelope row와
grpc-web-textrejection - partial prefix/payload
- unknown flag/over-limit length
- compressed/decompression overflow
- body trailer vs trailers-only header source, missing/duplicate/conflicting status
- missing
grpc-status의400/401/403/404/429/502/503/504/othercanonical mapping, valid status 존재 시 HTTP mapping 미적용, server-side 역매핑 금지 - status canonical
0..16, body lowercase names, rich-details outer-code mismatch - data after trailer/extra bytes
- unary 0/1/2 messages including one zero-length
Emptyframe - non-OK with data
- text base64 quantum/padding을 browser chunk 여러 개로 분할
- padded entity 뒤 추가 data/trailer entity, 연속 padded/unpadded entity와 invalid alphabet/padding/whitespace
- header/trailer field-count/byte overflow, padded/unpadded
-bin, comma-joined duplicate binary metadata - rich details allowlist/redaction
deadline/retry/auth:
- official absolute epoch-ms
deadlinemetadata, returned callcancel()과 pinned PromiseAbortSignal - Connect-Web positive remaining
timeoutMs, AbortSignal과 AsyncIterator close - custom 8-digit/unit
grpc-timeout, Fetch abort와 reader cancel - headers 전/후와 첫 stream event 전/후 caller cancel, attempt/total/idle timeout
- retry/auth recovery마다 remaining deadline 재계산, Envoy timeout max/offset ordering
- reader/timer/listener cleanup
- UNAUTHENTICATED single-flight
- SAFE/IDEMPOTENT/KEYED_COMMAND/NON_REPLAYABLE
FRONTEND_ADAPTER | ENVOY | NONEexact-one owner와 Query retry off- Envoy response-header
grpc-statusretry와 trailer-status no-retry, route total budget - transient status exact retry, pushback unsupported default,
x-envoy-*caller strip - frontend/Envoy/backend retry amplification과 hedging 거절
- ambiguous command reconcile
server stream:
- zero/many events
- backpressure/high-water/overflow
- duplicate/order/gap/resume
- terminal/non-terminal/truncated
- account/logout/generation
- finite aggregate atomic cache commit
provider/browser:
- runtime kind/client API/spec/rpcKind/wire-profile capability matrix와 official Promise
status-event rejection/
PromiseCallOptions.signalversion capability - Connect-Web unary Promise/server-stream
ASYNC_ITERABLE, Proto/JSON exact media와GRPC_WEB_TEXTrejection - exact POST/media/Accept/
X-Grpc-Web/X-User-Agentwire header - pinned actual Envoy/BFF version/config digest, upstream HTTP/2와
grpc_web -> cors -> routerorder - exact-origin CORS positive/negative preflight, credential/CSRF, exposed status headers, non-matching preflight fail-closed와 preflight suppression 금지
- local reply/missing-status, compression/message/header/deadline cap
- stream이 15초를 넘는 fixture, explicit route/idle/max-stream timeout과 no buffering/flush
- Chromium/Firefox/WebKit cancellation/status/trailer/backpressure
- Safari/enterprise proxy buffering and selected text fallback
MSW/memory frame fixture는 actual gateway evidence가 아니다.
20. Rollout
- product가 protobuf-first operation family와 gateway owner를 선택한다.
- proto/descriptor/proxy/auth/status/deadline contract를 확정한다.
- selected runtime별 official-XHR, Connect-Web runtime-owned Fetch 또는 custom raw-frame Fetch transport adapter와 generated boundary를 구현한다. 세 runtime의 framing/cancel 책임을 합치지 않는다.
- unary read operation을 REST shadow와 비교하되 secondary cache/UI write 금지.
- actual proxy/browser conformance 통과.
- reference runtime을
AVAILABLE_NOT_COMPOSED로 판정. - product composition 뒤
COMPOSED,TrafficAdmission=DISABLED로 시작. - unary safe read internal canary.
- keyed command는 backend dedupe/reconcile 뒤 별도 canary.
- finite server stream은 unary와 별도 evidence/traffic gate.
- client/bidi는 계속
PLATFORM_LIMITED.
rollback:
- 신규 operation/stream admission 중지
- read/stream cancel, command effect reconcile
- current scope gRPC-mapped cache clear/invalidate
- coherent frontend/generated descriptor/proxy/backend rollback
- approved REST read fallback은 새 logical query로만 실행
21. Removal
- operation traffic과 backend method retirement window 시작
- unary/stream cancel, command reconcile
- Query cache/reducer/invalidation listener clear
- operation/schema/mapper/query profile 제거
- generated files, proto/descriptor, generator/runtime dependency 제거
- proxy route/CORS/config 제거
- backend method는 N/N-1 client window 뒤 제거/reserve
- production module/dependency/SBOM/removal test 통과
규범 기준
아래 링크는 upstream 기준 위치다. production profile과 evidence는 master/latest
문자열이 아니라 검증한 immutable commit, package version과 Envoy release를
별도로 기록한다.
- gRPC-Web protocol delta
- Official grpc-web runtime support matrix
- Official grpc-web browser features와 CORS
- Official grpc-web XHR/deadline implementation
- Official grpc-web cancellation handle
- VD-29 Connect-Web과 browser Protobuf runtime
- Connect-Web gRPC-Web transport source
- gRPC HTTP/2 protocol와 timeout/header 규칙
- Canonical HTTP→gRPC status mapping
- gRPC deadline guide
- gRPC cancellation guide
- gRPC retry guide
- gRFC A6 client retries
- Envoy gRPC-Web filter
- Official grpc-web Envoy example
- Envoy timeout guidance
- Envoy route timeout API
- Envoy router retry constraints
- Envoy CORS filter
- Envoy CORS API
- Envoy buffer filter
- Buf breaking-change policy
완료 기준
- exact provider/profile/service/method/descriptor에 operation이 binding된다.
- official XHR/Connect-Web runtime-owned Fetch/custom raw-frame Fetch runtime kind, client API, runtime version과 gRPC-Web wire-spec revision이 고정되고 책임이 섞이지 않는다. official runtime의 binary server stream, Connect-Web의 text와 evidence 없는 JSON/server-stream 조합이 거절된다.
- missing
grpc-status가 canonical HTTP→gRPC 표로 failure normalization되고 wire header 합성이나 reverse mapping을 하지 않는다. - pinned executable Envoy/BFF profile이 upstream HTTP/2, filter order, CORS, route/idle/max-stream/deadline, buffering, header budget과 config digest를 닫는다.
- deadline/cancel dialect가 runtime별로 고정되고 retry owner가 frontend/Envoy/none 중 정확히 하나이며 Query/backend와 amplification을 만들지 않는다.
- text runtime은 concatenated padded/unpadded base64 entity와 arbitrary browser chunk 경계를 처리한다.
- protobuf generated type이 adapter 밖으로 나오지 않는다.
- frame/message/trailer/status/deadline/cancel/retry가 closed state machine이다.
- unary success가 exact one message + body terminal trailer의 유일한 OK status로 증명되고, trailers-only header status는 zero-message unary를 success로 만들지 않는다.
- stream이 bounded backpressure/sequence/gap/resume/terminal 계약을 가진다.
- int64/time/enum/oneof/bytes semantic mapper가 닫힌다.
- actual proxy와 Chromium/Firefox/WebKit evidence가 유효하다.
- client/bidi를 지원한다고 표시하지 않는다.
- cache에 raw/generated/partial stream state가 없다.
- kill switch, coherent rollback과 dependency/proxy/generated removal drill이 통과한다.