Files
clean-architecture-frontend…/docs/architecture/protobuf-browser-transport-and-rest-gateway.md
T
DongHyeonkaandClaude Opus 5 8f67974f68 fix: install bounded Browser RPC stream leases
R-04: install the RPC contract bindings as exact immutable snapshots. Registry
and row data are copied from own data descriptors into frozen null-prototype
maps before validation, so a getter is never invoked, extra and symbol keys and
malformed descriptors are composition-time TypeErrors, and the runtime reads
only the snapshot. A post-validation mutation can no longer change replay
policy, deadlines, byte ceilings or transport selection.

R-01: bound transport stream cleanup. The generation is fenced and listeners
released immediately, and iterator.return() is awaited only within a cleanup
bound, so a non-cooperative iterator cannot keep the application generator, its
listeners or the total deadline alive. Unresolved cleanup stays observed.

R-05: reject oversized WebSocket text frames before allocating an encoded copy
and count UTF-8 bytes incrementally with an early exit, matching TextEncoder for
surrogate pairs and lone surrogates.

R-06: canonicalise clock and generation-fence failures into the closed Result
taxonomy instead of letting them escape as native rejections, with listener and
timer cleanup on every exit path.

Browser RPC remains AVAILABLE_NOT_COMPOSED; R-07 transport evidence is still
required before composition.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-14 00:00:15 +09:00

36 KiB

Protobuf browser transport와 REST Gateway

Installed binding snapshot과 stream cleanup bound (R-01, R-04, R-05, R-06)

  • installBrowserRpcContractBindings()가 registry를 parse → validate → install 순서로 처리한다. own data descriptor만 읽어 exact key set으로 null-prototype frozen snapshot을 만들고, 그 snapshot을 검증한 뒤 설치한다. getter/accessor, extra key, symbol key, malformed descriptor, revoked proxy는 composition-time TypeError이며 getter는 호출조차 되지 않는다. runtime과 transport call은 이후 snapshot만 읽으므로 validation 이후 registry mutation이 replay policy·deadline·byte ceiling·transport selection을 바꿀 수 없다.
  • server stream 종료는 transport iterator에 lifecycle authority를 위임하지 않는다. commit/admission generation은 즉시 fence하고 listener는 바로 해제하며, iterator.return()은 cleanup 요청으로서 bound 안에서만 기다린다. 끝나지 않은 cleanup은 관찰만 유지되고(unhandled rejection 없음) application generator는 bound 안에 종료된다. cleanup rejection은 이미 선택된 application failure를 덮지 않는다.
  • WebSocket text frame은 allocation 전에 admission한다. UTF-16 code unit 길이가 이미 cap을 넘으면 encoder를 만들지 않고 거절하고, 나머지는 early exit하는 code-point 누적으로 센다. valid surrogate pair는 4 bytes, lone surrogate는 TextEncoder와 동일하게 replacement 3 bytes다.
  • clock/fence collaborator 예외는 Result 경계를 벗어나지 않는다. clock 실패는 SERVER_FAILURE/RPC_RUNTIME_DEPENDENCY_FAILED, capture 실패는 SCOPE_GENERATION_CHANGED/RPC_SCOPE_GENERATION_UNAVAILABLE, isCurrent 실패는 fail-closed로 canonicalize하며 listener/timer는 단일 exit path에서 정확히 한 번 해제한다.

Browser RPC는 여전히 AVAILABLE_NOT_COMPOSED다. 선택된 Connect/gRPC-Web transport는 enqueue-time maxBufferedBytes, raw/decompressed ceiling, cancel/closed receipt, terminal framing, target browser와 load behavior를 별도로 증명해야 조립할 수 있다 (R-07).

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 경계

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

금지:

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

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:

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:

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

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은 다음을 고정한다.

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는 최소 다음을 고정한다.

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

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

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

허용:

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

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. 규범·공식 근거