Files
tech-log-frontend/docs/architecture/protobuf-browser-transport-and-rest-gateway.md
T

776 lines
34 KiB
Markdown

# Protobuf browser transport와 REST Gateway
- 상태: production design accepted, product/provider selection pending
- 기준일: 2026-07-28
- 범위: gRPC-Web, Connect-Web, Connect protocol, Protobuf contract/codegen,
REST/JSON Gateway
- 현재 source 상태: provider-neutral Browser RPC V3 계약·application port·공통
unary/server-stream lifecycle runtime은 `AVAILABLE_NOT_COMPOSED`;
vendor runtime/dependency/proto/descriptor/proxy는 없음
- 관련 결정:
- [VD-23 API transport selection과 REST execution](./decisions/VD-23-api-transport-selection-and-rest-execution.md)
- [VD-24 Runtime schema와 boundary mapper](./decisions/VD-24-runtime-schema-and-boundary-mapper.md)
- [VD-25 Server state cache lifecycle](./decisions/VD-25-server-state-cache-lifecycle.md)
- [VD-27 gRPC-Web unary와 server stream](./decisions/VD-27-grpc-web-unary-and-server-stream.md)
- [VD-29 Connect-Web와 browser Protobuf runtime](./decisions/VD-29-connect-web-and-browser-protobuf-runtime.md)
- [VD-30 Protobuf contract와 REST Gateway](./decisions/VD-30-protobuf-contract-and-rest-gateway.md)
- backend handoff:
[Backend API와 Server State contract](./backend-api-and-server-state-contract.md)
- 운영 절차:
[API contract와 server-state recovery](../operations/api-contract-and-server-state-recovery.md)
## 1. 먼저 축을 분리한다
네 이름은 같은 종류의 대안이 아니다.
| 축 | 선택지 | 소유하는 것 |
| --- | --- | --- |
| contract/serialization | Protobuf binary, ProtoJSON | message/service schema, field number, presence, codegen |
| browser RPC transport | Connect protocol, gRPC-Web | HTTP framing, media, status/error, timeout, stream terminal |
| browser client runtime | Connect-Web, official grpc-web 또는 승인 runtime | Fetch/XHR, generated client, interceptors, cancellation |
| HTTP exposure | curated REST BFF, generated gRPC-Gateway, Envoy JSON transcoder | resource URL, method, JSON/status/cache/CORS contract |
예를 들어 Connect-Web은 같은 generated service descriptor로 Connect protocol과
gRPC-Web transport를 모두 만들 수 있다. 반대로 REST Gateway는 Protobuf service를
ProtoJSON HTTP API로 노출할 수 있지만, 그것이 자동으로 좋은 browser REST
contract가 된다는 뜻은 아니다.
한 semantic operation은 frontend registry에서 정확히 하나의 active wire
profile에 binding한다. runtime이 media type을 보고 Connect/gRPC-Web/REST를
추측하거나 장애 시 다른 protocol로 같은 command를 자동 replay하지 않는다.
## 2. 현재 상태
| capability | current status | 근거 | promotion 필요 |
| --- | --- | --- | --- |
| Browser RPC V3 공통 계약/runtime | `AVAILABLE_NOT_COMPOSED` | exact operation/profile/schema/mapper/encoder/transport join, unary retry·total deadline·abort, bounded server-stream·idle deadline·terminal, generation fence와 unavailable adapter test | selected generated client를 감싸는 protocol transport, raw-byte cap과 actual provider/browser conformance |
| Protobuf schema/codegen | `DESIGNED_NOT_IMPLEMENTED` | `.proto`, `buf.yaml`, descriptor, runtime dependency 없음 | authenticated schema source와 owner |
| Connect-Web client runtime | `DESIGNED_NOT_IMPLEMENTED` | `@connectrpc/*`, `@bufbuild/protobuf` 없음 | selected service, provider와 bundle budget |
| Connect protocol unary | `DESIGNED_NOT_IMPLEMENTED` | endpoint/profile/fixture 없음 | exact JSON 또는 binary profile |
| Connect protocol server stream | `DESIGNED_NOT_IMPLEMENTED` | stream runtime/proxy evidence 없음 | bounded stream protocol과 browser evidence |
| gRPC-Web unary/server stream | `DESIGNED_NOT_IMPLEMENTED` | VD-27만 존재 | runtime/proxy/descriptor와 conformance |
| REST Gateway reference contract/harness | `DESIGNED_NOT_IMPLEMENTED` | native REST reference만 있고 transcoder/fixture 없음 | selected kind의 deterministic/provider conformance |
| product REST Gateway composition | `NOT_SELECTED` | route/provider/owner 없음 | curated BFF 또는 selected transcoder의 제품 승인 |
| browser client/bidi stream | `PLATFORM_LIMITED` | request streaming을 target browser 공통 계약으로 보장 못함 | 다른 transport/application protocol |
| product traffic | `NOT_SELECTED` | operation/provider/owner 없음 | product ADR와 traffic admission |
이 문서가 추가돼도 dependency나 generated source를 기본 bundle에 넣지 않는다.
공통 runtime source가 생긴 뒤에도 같은 원칙을 유지한다. 공통 runtime은
Connect/gRPC-Web wire를 직접 decode하지 않고 selected transport가 반환한
bounded message/terminal/failure만 처리하므로, 실제 Connect-Web 또는 gRPC-Web
adapter가 구현됐다는 증거가 아니다.
## 3. 기본 선택
### 3.1 권장 순서
1. 기존 REST가 제품 의미와 운영 요구를 충족하면 REST를 유지한다.
2. backend가 Protobuf-first이고 browser RPC가 필요하면 Connect-Web +
Connect protocol을 우선 평가한다.
3. backend가 gRPC-Web만 노출하거나 기존 Envoy/gRPC-Web conformance 자산을
재사용해야 하면 Connect-Web의 gRPC-Web transport 또는 official grpc-web
runtime 중 하나를 고정한다.
4. public HTTP API, CDN/conditional cache, 링크 가능한 resource URL,
broad HTTP tooling이 핵심이면 curated REST BFF를 우선한다.
5. `google.api.http` annotation만으로 제품 REST 의미를 온전히 표현할 수 있을
때만 generated gRPC-Gateway/Envoy transcoding을 선택한다.
### 3.2 선택 matrix
| 요구 | 기본 후보 | 주의 |
| --- | --- | --- |
| 내부 web UI + Protobuf-first backend + unary | Connect-Web/Connect | JSON/binary를 operation profile로 고정 |
| 내부 web UI + 기존 gRPC-Web gateway | selected gRPC-Web runtime | runtime별 streaming capability가 다름 |
| bounded server→browser stream | Connect server stream 또는 gRPC-Web server stream | idle/total/queue/sequence/resume 필수 |
| public/resource-oriented HTTP API | curated REST BFF | Protobuf service shape를 그대로 노출하지 않음 |
| 단순 proto HTTP annotation과 broad JSON client | generated REST Gateway | ProtoJSON/status/error semantic fixture 필요 |
| HTTP cache/ETag/Range/file download | REST/BFF | RPC transcoder로 억지로 만들지 않음 |
| browser client/bidi | WebSocket/WebTransport/별도 session API | Connect protocol 자체 지원과 browser 지원을 혼동 금지 |
| 기존 REST backend뿐임 | REST 유지 | Protobuf/gateway를 미래 대비로 추가하지 않음 |
Connect가 항상 gRPC-Web보다 우월하거나 REST Gateway가 수동 REST보다 항상
저렴하다고 가정하지 않는다. actual provider, proxy, browser, bundle,
observability와 조직 운영 비용을 evidence로 비교한다.
## 4. 공통 application 경계
```text
presentation
-> feature application input
-> semantic gateway port
-> installed operation registry
-> REST adapter
-> Connect adapter
-> gRPC-Web adapter
-> transport decode
-> semantic runtime schema
-> boundary mapper
-> immutable application projection
```
금지:
```text
page/use-case -> generated service client
page/use-case -> protobuf message or descriptor
page/use-case -> transport/base URL/metadata
generated message -> TanStack cache
raw Connect/gRPC error -> presentation
REST gateway DTO -> domain without runtime schema/mapper
```
application port는 `listResources`, `createResource`, `watchJob` 같은 의미를
표현한다. `callRpc(service, method, bytes)``executeProto<T>()`를 노출하지 않는다.
## 5. Protocol-neutral operation과 exact binding
```text
ApiOperationContractV3
semanticOperationId
owner
semantics = QUERY | COMMAND | SERVER_STREAM
protocol = REST | CONNECT_HTTP | GRPC_WEB
replayPolicy
idempotencyKeyPolicy
authProfileId
csrfProfileId
deadlineProfileId
retryProfileId
requestSemanticSchemaId
responseSemanticSchemaId
mapperId
serverStateProfileId | null
dataClassification
compatibility
protocolBinding
```
현재 source에서는 설치된 REST V2 registry를 위험한 union migration으로 바꾸지
않고 `BrowserRpcOperationV3` sibling registry를 추가했다. 제품 operation이
선택되면 composition root가 semantic feature gateway 뒤에서 REST 또는 Browser
RPC 중 정확히 하나를 bind한다. use-case가 두 registry를 보거나 runtime fallback을
결정하지 않는다.
Connect/gRPC-Web binding:
```text
BrowserRpcOperationV3 + BrowserRpcProviderProfile
providerId
clientRuntimeId
clientRuntimeVersion
transportRuntimeKind = CONNECT_WEB_FETCH | OFFICIAL_GRPC_WEB_XHR | CUSTOM_FETCH_FRAMED
clientApiKind = PROMISE | ASYNC_ITERABLE | CALLBACK_STREAM
protocolRevision
wireProfileId
encoding = PROTO_BINARY | PROTO_JSON
fullyQualifiedService
method
rpcKind = UNARY | SERVER_STREAM
requestMessageId
responseMessageId
descriptorArtifactId
descriptorDigest
errorProfileId
corsProfileId
compressionProfileId
deadlineDialect
retryOwner = FRONTEND_ADAPTER | EDGE_PROXY | NONE
maxRequestBytes
maxHeaderBytes
maxHeaderFields
maxMessageBytes
maxResponseMessages
maxTotalResponseBytes
idleDeadlineMs | null
```
구현 경로는 `src/contracts/browser-rpc.ts`,
`src/application/ports/browser-rpc`, `src/adapters/browser-rpc`다. 공통 profile은
runtime identity/digest, fixed base URL, descriptor digest, allowed procedure,
auth/CSRF/CORS/error/deadline/retry owner와 raw-byte ceiling owner까지 고정한다.
`maxHeaderBytes`, media/trailer parsing과 실제 raw queue ceiling은 공통 coordinator가
추측하지 않고 selected wire transport가 VD-27/VD-29에 따라 추가로 집행한다.
REST Gateway binding:
```text
RestGatewayBindingV1
providerId
gatewayKind = CURATED_BFF | GRPC_GATEWAY | ENVOY_TRANSCODER
gatewayRuntimeId
gatewayRuntimeVersion
httpRuleArtifactId
httpRuleDigest
method
relativePathTemplate
requestProjection
protoJsonProfileId | null
responseEnvelopeProfileId
statusErrorProfileId
cacheConditionalProfileId
```
registry는 다음을 boot/build 전에 거절한다.
- protocol/profile/runtime/provider tuple 불일치
- descriptor 또는 HTTP rule digest 불일치
- caller-provided endpoint, method, metadata 또는 message type
- `SERVER_STREAM`인데 ordinary Query profile 사용
- browser client/bidi method
- Connect와 gRPC-Web decoder의 runtime auto-negotiation
- runtime kind와 deadline/cancel/status API가 맞지 않는 client API 조합
- frontend와 edge proxy가 동시에 retry owner인 profile
- gateway kind가 다른 두 route의 last-write-wins collision
- JSON exposure인데 binary-only compatibility gate만 통과
- non-replayable command retry/fallback
- hard ceiling보다 큰 message/frame/deadline/URL
## 6. Protobuf contract source
### 6.1 Source of truth
```text
authenticated Proto/Buf module
-> immutable source commit/module digest
-> lint
-> breaking comparison
-> FileDescriptorSet
-> deterministic codegen
-> generated artifact digest
-> operation/schema/mapper binding
-> release contract set
```
runtime endpoint에서 latest schema를 받아 build하지 않는다. schema update는
review 가능한 explicit workflow이며 source, dependency lock, descriptor,
plugin/runtime와 generated output digest를 함께 보존한다.
### 6.2 Evolution
- field number 재사용/renumber 금지
- 삭제 field number와 JSON name reserve
- package/service/method full name 안정성
- enum zero value와 unknown numeric policy
- proto3 optional/edition presence를 명시
- oneof absence/unknown case 처리
- map ordering을 identity/digest로 사용하지 않음
- unknown field가 binary↔JSON conversion에서 보존되지 않을 수 있음을 반영
- Timestamp/Duration range와 nanos
- int64/uint64의 JS/ProtoJSON projection
- bytes와 repeated/nesting ceiling
JSON을 한 곳이라도 사용하면 binary wire compatibility만으로 충분하지 않다.
최소 `WIRE_JSON` breaking category를 요구한다. generated SDK의 import/source
compatibility를 외부 소비자가 의존하면 `PACKAGE` 또는 `FILE`을 선택한다.
Buf gate와 별도로 domain meaning, authorization, default/presence, pagination,
revision과 idempotency semantic diff를 수행한다.
### 6.3 ProtoJSON profile
operation은 다음을 고정한다.
```text
ProtoJsonProfileV1
emitDefaultValues
useProtoFieldNames
enumEncoding = NAME | NUMBER
ignoreUnknownFields
int64Projection
bytesProjection
wellKnownTypePolicy
```
runtime/library default에 맡기지 않는다. 기본 방향:
- request unknown field reject
- response는 generated decoder 뒤 known semantic projection만 mapper에 전달
- int64/uint64는 decimal string 또는 bounded application type
- bytes는 base64 decode 전후 byte ceiling
- non-finite float는 domain 승인 없으면 거절
- Timestamp/Duration은 generated decode 성공 뒤 semantic range 재검증
- `Any`는 allowlisted type URL만
ProtoJSON은 ordinary JSON schema의 임의 union을 대체하지 않으며 binary보다
evolution 보장이 약하다.
## 7. Deterministic codegen과 공급망
선택 branch는 최소 다음을 고정한다.
```text
buf.yaml
buf.lock
buf.gen.yaml
schema/module digest
protoc or Buf version
plugin name/version/revision/digest
@bufbuild/protobuf version
@connectrpc/connect version
@connectrpc/connect-web version
Buf image digest
FileDescriptorSet digest
canonical HttpRule manifest digest
generated OpenAPI digest when selected
generated output digest
```
CI:
- format/lint/breaking
- dependency lock과 source provenance
- clean checkout generate diff 0
- generated directory 수동 수정 금지
- generated import boundary
- descriptor↔generated symbol exact join
- generated runtime↔runtime compatibility matrix
- canonical HttpRule/OpenAPI semantic diff; custom option을 Buf breaking에 위임하지 않음
- N/N-1 fixture
- license/SBOM/vulnerability/secret scan
- production bundle inventory와 budget
- capability removal 뒤 generated/runtime/proxy reference 0
generated files는 `src/generated` 같은 전역 public API가 아니라 selected
feature adapter-private root에 둔다. 여러 feature가 공유하는 service라도 좁은
platform adapter facade만 재사용한다.
## 8. Connect-Web
Connect-Web은 browser client runtime이다. `createConnectTransport()`는 Connect
protocol, `createGrpcWebTransport()`는 gRPC-Web protocol을 사용한다. 같은 package를
쓴다고 두 wire protocol이 호환되거나 자동 failover 가능한 것은 아니다.
Connect-ES v2 generation은 `protoc-gen-es`가 message와 service descriptor를
함께 생성하며 과거 Connect 전용 generator를 신규 toolchain에 넣지 않는다.
### 8.1 Connect unary
```text
validated semantic input
-> generated request
-> bounded encode (ProtoJSON or binary)
-> POST fixed /package.Service/Method
-> bounded response/error decode
-> generated message
-> semantic validation
-> mapper
-> generation fence
```
Connect unary는 bare Protobuf/JSON body와 의미 있는 HTTP status를 사용한다.
success content type과 selected encoding을 exact 검증한다. error는 non-2xx JSON
Connect error profile로만 decode하며 intermediary HTML/JSON을 Connect error로
추측하지 않는다.
stock runtime이 unary body를 whole JSON/ArrayBuffer로 읽는 selected version이면
interceptor가 raw-byte ceiling을 대신한다고 기록하지 않는다. edge의 encoded/
decompressed cap과 platform-owned bounded Fetch transport 또는 해당 exact
runtime의 overflow conformance가 있어야 production evidence가 닫힌다.
GET은 다음을 모두 만족할 때만 별도 profile로 허용한다.
- protobuf method `idempotency_level = NO_SIDE_EFFECTS`
- public/non-sensitive bounded request
- URL byte ceiling과 canonical encoding
- header/credential/preflight 정책
- exact CDN/browser cache key, `Vary`, ETag와 retention
- request URL이 log/history/referrer에 노출돼도 허용되는 classification
인증/private query의 기본은 POST다.
### 8.2 Connect server stream
Connect streaming은 framed `application/connect+proto|json`이고 HTTP status
`200` 안의 final EndStream envelope가 RPC error/trailer authority다.
decoder는:
- incremental 5-byte envelope prefix
- flag/compression/declared length
- per-message/decompressed/total/count ceiling
- exactly one final EndStream envelope
- data after EndStream, missing EndStream와 truncation
- bounded error/detail/trailer projection
- idle + total deadline
- reader cancel/release와 bounded consumer queue
를 검증한다. network EOF는 success가 아니다. stock Connect-Web runtime을
사용하면 exact package version이 이 state machine을 얼마나 집행하는지
conformance로 증명하고, 부족한 hard ceiling은 proxy 또는 custom Transport가
소유한다. selected stock runtime의 streaming output compression은
`IDENTITY_ONLY`로 고정하고 지원하지 않는 compressed envelope를 광고하지 않는다.
### 8.3 Interceptor policy
interceptor가 허용되는 책임:
- registry-owned auth/CSRF patch
- remaining deadline/timeout
- fixed low-cardinality diagnostics
- exact safe retry coordinator
- safe error normalization
금지:
- arbitrary URL/service/method rewrite
- raw message/error logging
- caller metadata passthrough
- operation semantic retry를 transport가 임의 결정
- cache write와 domain mapping
transport/client는 runtime/scope 단위로 재사용하되 base URL/profile이 다른
provider 사이에서 공유하지 않는다.
interceptor array의 선언 순서가 아니라 실제 onion execution order를 manifest와
test에 보존한다. remaining total deadline이 0 이하면 `timeoutMs=0`을 넘기지 않고
호출 전에 local deadline failure로 닫는다.
## 9. gRPC-Web
상세 frame/status state machine은 VD-27을 따른다.
- browser는 native gRPC/HTTP2 transport를 직접 사용한다고 가정하지 않는다.
- official `grpc-web` runtime은 XHR와 runtime-owned frame/status decoder,
`ClientReadableStream.cancel()`을 사용한다. raw `ReadableStream` frame parser와
`AbortSignal`을 이 경로의 frontend 보장으로 기록하지 않는다.
- custom Fetch runtime만 `fetch`/`AbortController`/incremental raw-frame decoder
profile을 사용한다. 두 runtime은 `transportRuntimeKind`로 분리한다.
- gRPC-Web response trailer는 body trailer frame 또는 trailers-only response
header에서 해석한다.
- `grpc-status`가 있으면 그것이 authoritative다. 없으면 native gRPC의 공식
HTTP→gRPC fallback mapping으로 internal status를 만들며 HTTP success만으로
RPC success를 판정하지 않는다.
- official `grpc-web` JavaScript runtime은 binary unary와 text unary/server
streaming capability를 구분한다.
- Connect-Web gRPC-Web transport를 선택하면 별도 `clientRuntimeId`와 그 runtime의
conformance matrix를 사용한다.
- text/base64와 binary를 같은 byte budget으로 취급하지 않는다.
- text decoder는 browser chunk 하나를 base64 entity 하나로 가정하지 않고,
중간 padding이 있는 연속 base64 entity를 처리해야 한다.
- Envoy `grpc_web` filter 또는 selected gateway의 exact version/config를
provider profile에 binding한다.
- Envoy profile은 filter order, upstream HTTP/2, route/idle/max-stream timeout,
gRPC timeout offset, buffering/flush, header/message ceiling과 local reply
mapping을 고정한다. server stream에는 default route timeout을 그대로 쓰지 않는다.
- retry owner는 정확히 하나다. server stream replay/hedge는 금지하고, frontend와
Envoy retry를 동시에 켜지 않는다.
gRPC-Web과 Connect stream은 terminal envelope가 서로 다르다. 공통
`ReadableStream` helper를 재사용할 수 있어도 decoder/state machine을 합치지 않는다.
## 10. REST Gateway
### 10.1 세 종류를 분리한다
`CURATED_BFF`
- browser/resource contract를 별도로 설계
- REST envelope, status, ETag/304/412, idempotency, pagination과 CORS를 직접 소유
- 내부에서 gRPC/Connect service를 호출할 수 있으나 public DTO는 별도 mapper
`GRPC_GATEWAY`
- `google.api.http` annotation에서 reverse proxy와 optional OpenAPI를 생성
- proto request field를 path/query/body로 projection
- ProtoJSON과 gRPC status mapping을 exact profile로 고정
- initial reference 후보는 unary만이며 REST streaming은 별도 ADR 없이는 선택하지 않음
`ENVOY_TRANSCODER`
- descriptor와 `google.api.http` annotation으로 proxy filter가 JSON↔gRPC 변환
- filter/runtime config와 descriptor를 coherent artifact로 배포
- application-specific envelope/cache/idempotency 의미는 별도 filter/BFF 없이는
자동 생성되지 않음
한 route는 하나의 gateway kind만 소유한다.
현재 reference REST의 `{ success, data|error, meta }` envelope와
`200|201`, 향후 `204/304/412` 의미는 direct generated gateway의 기본 출력과
같지 않다. 따라서 기존 reference operation은 curated BFF를 유지한다. direct
gateway는 ProtoJSON/HTTP rule/status/error 자체를 새 versioned public contract로
승인한 operation에만 적용한다.
### 10.2 Automatic transcoding의 한계
HTTP annotation은 path/method/body projection을 정의하지만 다음을 자동으로
완성하지 않는다.
- frontend의 strict success/failure envelope
- product authorization와 existence hiding
- idempotency store/effect certainty
- CursorPage snapshot 의미
- ETag/If-None-Match/If-Match와 revision CAS
- CDN/private cache policy
- domain error vocabulary와 validation detail redaction
- file Range/streaming download/upload
- browser-compatible server event/reconnect protocol
- OpenAPI와 runtime response/error rewrite의 자동 coherence
따라서 현재 reference REST envelope에 generated gateway를 바로 연결할 수 없다.
gateway adapter가 exact envelope/status를 제공하거나 frontend에 새 operation
contract를 versioned로 추가해야 한다.
### 10.3 REST mapping
- resource name/path는 stable API meaning을 가져야 한다.
- path field를 body/query에도 중복 projection하지 않는다.
- unbound fields의 query mapping과 repeated/nested encoding을 fixture로 고정한다.
- `body: "*"`는 query surface와 HTTP semantics를 숨길 수 있어 default 금지다.
- additional binding collision과 ambiguous path template를 build에서 거절한다.
- `.proto` annotation을 기본 source of truth로 삼고 external service config를
병용하면 override precedence와 두 artifact digest를 고정한다.
- `generate_unbound_methods`는 기본 금지하며 GET/DELETE body, path field type,
path unescape mode와 PATCH FieldMask behavior를 fixture로 고정한다.
- Buf generic breaking check가 custom HTTP option의 제품 의미까지 증명한다고
간주하지 않는다. canonical route manifest와 generated OpenAPI의 method/path/
body/query/additional-binding diff를 별도 gate로 검사한다.
- `response_body` projection은 전체 response schema와 mapper binding을 별도로
갖는다.
- ProtoJSON JSON name/default/enum/int64/unknown policy를 고정한다.
- request/response body와 URL ceiling은 transcoder 앞/뒤 모두 적용한다.
server-stream을 JSON array로 buffer해 반환하는 transcoder 동작을 realtime
stream으로 간주하지 않는다. SSE/NDJSON/streaming JSON이 필요하면 별도 protocol
ADR과 framing/content type/terminal contract를 만든다.
## 11. Auth, CSRF와 CORS
same-origin BFF를 권장한다. cross-origin이면 protocol별 exact profile이 필요하다.
Connect:
- POST와 optional GET
- `Content-Type`, `Connect-Protocol-Version`, `Connect-Timeout-Ms`,
selected compression/auth headers
- custom unary trailer를 expose할 때 `Trailer-<Name>`
gRPC-Web:
- POST
- `Content-Type`, `Grpc-Timeout`, `X-Grpc-Web`, `X-User-Agent`,
selected auth headers
- `Grpc-Status`, `Grpc-Message`, `Grpc-Status-Details-Bin` expose
REST Gateway:
- operation별 method/header/media
- bearer 또는 cookie+CSRF profile
- conditional/idempotency header allow/expose
공통:
- exact allow-origin, credential mode와 `Vary`
- wildcard credential 금지
- redirect login/HTML response 금지
- Origin/Fetch Metadata/CSRF를 cookie unsafe method에서 검증
- generated client/caller가 arbitrary metadata를 제출하지 못함
- auth attach 뒤 endpoint/path/method/body digest 불변
## 12. Deadline, cancellation, retry와 command
```text
total logical deadline
= auth attach
+ transport attempts/backoff
+ body/frame read/decompression
+ generated decode
+ semantic validation
+ mapper
```
- Connect-Web call은 AbortSignal과 bounded timeout을 받는다.
- local deadline과 wire timeout 중 더 짧은 값만 사용한다.
- stream은 idle/total deadline 둘 다 갖는다.
- abort가 backend command rollback을 의미하지 않는다.
- generated/runtime interceptor retry와 Query retry를 중복하지 않는다.
- `SAFE | IDEMPOTENT | KEYED_COMMAND`의 exact evidence가 있는 operation만 retry한다.
- keyed command는 protocol과 무관하게 backend atomic idempotency/reconcile이
필요하다.
- gateway의 header/message mapping은 idempotency key를 전달할 뿐 durable
dedupe, receipt, retention과 reconcile을 구현하지 않는다.
- browser abort는 이미 commit된 command의 rollback 증거가 아니다. edge→upstream
cancel/deadline 전파와 backend cooperative cancellation을 staging에서 검증한다.
- protocol 장애를 이유로 Connect↔gRPC-Web↔REST command를 자동 replay하지 않는다.
- read fallback도 새 logical operation으로 시작하고 old result를 cache에 쓰지 않는다.
## 13. Status와 error mapping
common safe vocabulary에 mapping하되 wire authority를 섞지 않는다.
| source | authoritative outcome |
| --- | --- |
| Connect unary | HTTP status + bounded Connect JSON error |
| Connect stream | HTTP admission + final EndStream envelope |
| gRPC-Web | HTTP admission + terminal grpc-status source |
| REST Gateway | selected HTTP/envelope/problem profile |
mapping은 `UNAUTHENTICATED/AUTH_REQUIRED`, `PERMISSION_DENIED/FORBIDDEN`,
`NOT_FOUND`, `CONFLICT/ABORTED`, validation, rate limit, unavailable,
deadline/cancel을 공통 `AppFailure`로 투영한다. raw message, arbitrary Any/detail,
metadata/trailer와 vendor error는 application/log에 전달하지 않는다.
같은 semantic backend error라도 transport별 status body가 다를 수 있으므로
conformance suite가 최종 `AppFailure`와 retry/effect certainty가 같은지 검증한다.
## 14. Server State와 cache
- query key는 semantic application input에서만 파생한다.
- service/method/protobuf bytes/REST URL을 key에 넣지 않는다.
- generated message/Connect response를 cache하지 않고 mapper output만 admission한다.
- transport retry가 있으면 Query retry는 off다.
- scope/generation mismatch result는 폐기한다.
- Connect GET/CDN cache, browser HTTP cache와 TanStack cache owner를 operation별로
하나씩 명시한다.
- stream event는 ordinary query result가 아니다.
- finite stream aggregate는 terminal success 뒤 atomic commit한다.
- long-running stream은 bounded reducer 또는 invalidate-only hint를 사용한다.
## 15. Security와 privacy
- descriptor/generated code는 신뢰된 source에서만
- fixed provider/service/method/route
- message/body/frame/decompressed/collection/depth ceiling
- recursive schema와 Any type allowlist
- frontend generated validation을 backend authorization으로 간주 금지
- raw payload, ProtoJSON, debug stringifier, metadata/trailer/cursor/revision log 금지
- request/response message에 credential을 넣지 않음
- query cache/persistence에 generated message 없음
- gateway가 unknown field/duplicate JSON key를 어떻게 처리하는지 fixture로 고정
## 16. Observability
허용:
```text
semantic operation ID
protocol/client runtime/provider profile ID
descriptor/http-rule artifact version
HTTP status group / safe RPC code
attempt/duration/message-size/count bucket
stream terminal/idle/gap/overflow bucket
cache admission outcome
```
금지:
- service request/response content
- protobuf debug JSON/string
- metadata/trailer/error message/detail
- URL query/GET message
- credential/idempotency/cursor/revision
- resource/account identifier actual value
protocol별 metric을 비교할 때 semantic operation ID를 join key로 쓰고 raw
service/method를 high-cardinality label로 사용하지 않는다.
## 17. Test와 provider evidence
### 17.1 Deterministic
- proto format/lint/breaking/descriptor/codegen digest
- enum/oneof/presence/int64/time/bytes/unknown-field fixture
- Connect unary JSON/binary success/error/media/status
- Connect stream partial prefix, length, compression, EndStream, truncation
- gRPC-Web binary/text frame/trailer/status matrix
- REST annotation path/query/body/response mapping
- ProtoJSON default/name/enum/int64/null/unknown behavior
- retry/deadline/cancel/auth/idempotency
- scope/generation/cache admission
- generated import/bundle/removal
### 17.2 Actual provider/browser
- selected Connect/gRPC-Web server or Envoy/gateway version
- exact CORS/preflight/auth/CSRF
- HTTP/1.1/2 proxy buffering and stream flush
- media, compression, terminal status/trailer preservation
- message/body/time limit
- Chromium/Firefox/WebKit cancel/stream/backpressure
- REST Gateway N/N-1 and OpenAPI/descriptor coherence
- browser abort→gateway context→upstream cancel/deadline propagation
- HttpRule route manifest/OpenAPI/runtime response rewrite coherence
- kill switch, rollback and dependency/proxy removal drill
memory fake/MSW만으로 provider conformance를 주장하지 않는다.
## 18. Rollout
```text
product operation selected
-> schema/provider/gateway owner
-> immutable proto + descriptor
-> codegen and semantic mapper
-> provider-neutral adapter/fake
-> actual gateway/browser conformance
-> AVAILABLE_NOT_COMPOSED
-> bootstrap TrafficAdmission=DISABLED
-> COMPOSED
-> read-only shadow
-> canary
-> enabled
```
- 처음에는 safe unary read 하나만 선택한다.
- shadow result는 UI/cache에 쓰지 않는다.
- Connect와 gRPC-Web을 동시에 canary하지 않는다.
- server stream은 unary와 별도 gate다.
- command는 backend idempotency/reconcile 뒤 별도 gate다.
- REST fallback은 사전 등록된 read operation만 새 logical query로 실행한다.
- rollback은 frontend/generated descriptor/gateway/backend를 coherent set으로 한다.
## 19. Removal
1. 신규 call/stream admission을 닫는다.
2. query/stream cancel, command effect reconcile.
3. cache/reducer/invalidation listener를 clear한다.
4. operation/schema/mapper/provider profile을 제거한다.
5. generated source, descriptor/proto input과 codegen config를 제거한다.
6. Connect/gRPC runtime dependency와 proxy/transcoder route를 제거한다.
7. backend method/HTTP binding은 N/N-1 client window 뒤 retirement한다.
8. bundle, SBOM, lockfile, proxy config와 source reference 0을 증명한다.
## 20. 구현 work package
| 순서 | package | exit |
| --- | --- | --- |
| PB-01 | schema governance | authenticated module, Buf lint/breaking, descriptor/digest |
| PB-02 | generated boundary | deterministic TS generation, private imports, mapper |
| PB-03 | Connect unary | exact JSON/binary profile, error/deadline/cancel |
| PB-04 | gRPC-Web unary | selected runtime/proxy/frame/status conformance |
| PB-05 | bounded server stream | queue/idle/total/terminal/sequence/resume |
| PB-06 | REST Gateway | selected kind, HTTP annotation/ProtoJSON/error/cache fixture |
| PB-07 | operations | browser/provider evidence, canary/kill/rollback/removal |
PB-03~06은 제품에서 선택한 branch만 구현한다. “미래 대비”로 모두 설치하지 않는다.
## 21. 완료 기준
- [ ] Protobuf, client runtime, wire protocol과 gateway 축이 분리돼 있다.
- [ ] 한 operation은 한 active transport/provider profile만 가진다.
- [ ] descriptor/codegen/runtime/gateway artifact가 release digest에 binding된다.
- [ ] generated type이 adapter 밖으로 나오지 않는다.
- [ ] ProtoJSON과 binary compatibility policy가 각각 닫혀 있다.
- [ ] Connect unary/stream과 gRPC-Web decoder가 서로의 terminal 규칙을 섞지 않는다.
- [ ] REST Gateway가 envelope/cache/idempotency를 자동 제공한다고 가장하지 않는다.
- [ ] client/bidi streaming을 browser 공통 capability로 표시하지 않는다.
- [ ] actual proxy와 세 browser evidence가 있다.
- [ ] command fallback/replay가 backend effect certainty를 우회하지 않는다.
- [ ] coherent rollback과 complete removal drill이 통과한다.
## 22. 규범·공식 근거
- [Connect protocol reference](https://connectrpc.com/docs/protocol/)
- [Connect-Web protocol selection](https://connectrpc.com/docs/web/choosing-a-protocol/)
- [Connect-Web code generation](https://connectrpc.com/docs/web/generating-code/)
- [Connect and gRPC-Web CORS](https://connectrpc.com/docs/cors/)
- [gRPC-Web protocol delta](https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-WEB.md)
- [Official grpc-web runtime](https://github.com/grpc/grpc-web)
- [Envoy gRPC-Web filter](https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/grpc_web_filter.html)
- [gRPC HTTP status fallback mapping](https://github.com/grpc/grpc/blob/master/doc/http-grpc-status-mapping.md)
- [Envoy timeout configuration](https://www.envoyproxy.io/docs/envoy/latest/faq/configuration/timeouts.html)
- [Protocol Buffers language guide](https://protobuf.dev/programming-guides/proto3/)
- [ProtoJSON format](https://protobuf.dev/programming-guides/json/)
- [Buf breaking changes](https://buf.build/docs/breaking/)
- [gRPC-Gateway introduction](https://grpc-ecosystem.github.io/grpc-gateway/docs/tutorials/introduction/)
- [gRPC-Gateway customization](https://grpc-ecosystem.github.io/grpc-gateway/docs/mapping/customizing_your_gateway/)
- [Google API HTTP annotation](https://github.com/googleapis/googleapis/blob/master/google/api/http.proto)
- [Envoy gRPC-JSON transcoder](https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/grpc_json_transcoder_filter)