1474 lines
47 KiB
Markdown
1474 lines
47 KiB
Markdown
# VD-29: Connect-Web과 browser Protobuf runtime
|
|
|
|
- 상태: Accepted design — common coordinator available, wire adapter pending
|
|
- 결정일: 2026-07-28
|
|
- provider-neutral Browser RPC V3 coordinator:
|
|
`AVAILABLE_NOT_COMPOSED`
|
|
- Connect-Web reference runtime: `DESIGNED_NOT_IMPLEMENTED`
|
|
- product Connect-Web composition: `NOT_SELECTED`
|
|
- browser client-streaming/bidirectional-streaming guarantee:
|
|
`PLATFORM_LIMITED`
|
|
- 관련 결정: VD-13, VD-23, VD-24, VD-25, VD-27, VD-28, VD-30
|
|
- 상세 설계:
|
|
[Protobuf browser transport와 REST Gateway](../protobuf-browser-transport-and-rest-gateway.md)
|
|
- 상위 API 설계:
|
|
[API contract, Schema, Mapper와 Server State](../api-contract-schema-mapper-and-server-state.md)
|
|
|
|
## 배경
|
|
|
|
현재 source에는 provider-neutral operation/profile registry, application port와
|
|
unary/server-stream lifecycle coordinator가 있다. direct dependency,
|
|
`.proto`/descriptor, Buf config, generated output, provider route와 actual
|
|
browser/proxy test에는 Connect-Web capability가 없다.
|
|
문서와 future binding이 있다는 사실은 runtime 구현 또는 backend 연결 증거가 아니다.
|
|
|
|
`@connectrpc/connect-web`은 library 이름이고 protocol 이름이 아니다. 같은 browser
|
|
runtime이 다음 두 wire protocol을 선택적으로 실행할 수 있다.
|
|
|
|
- Connect protocol
|
|
- gRPC-Web protocol
|
|
|
|
두 protocol은 service/method와 Protobuf schema 의미를 공유할 수 있지만 HTTP
|
|
media, framing, status, error와 terminal 규칙은 다르다. application gateway를
|
|
공유할 수 있다는 이유로 decoder와 provider profile을 공유하지 않는다.
|
|
|
|
Connect protocol 자체는 unary, client stream, server stream과 bidirectional
|
|
stream을 정의하지만 browser용 Connect-Web Fetch transport는 unary와 server
|
|
stream만 제공한다. protocol capability와 selected browser runtime capability를
|
|
같다고 보지 않는다.
|
|
|
|
VD-27은 gRPC-Web frame/status state machine을 소유한다. 이 결정은
|
|
Connect-Web-specific runtime matrix와 Connect protocol state machine을 소유한다.
|
|
VD-27의 특정 XHR/base64 runtime 제약을 모든 gRPC-Web runtime에 일반화하지 않고,
|
|
선택한 `(protocol, runtime, version, rpc kind, encoding, browser, proxy)` tuple로
|
|
capability를 판정한다.
|
|
|
|
## 결정
|
|
|
|
### 1. Protocol, runtime, message encoding과 framing을 분리한다
|
|
|
|
다음 축은 서로 대체할 수 없다.
|
|
|
|
```text
|
|
protocol
|
|
CONNECT_V1
|
|
GRPC_WEB_V1
|
|
|
|
client runtime
|
|
CONNECT_ES_WEB_V2_<exact-package-version>
|
|
other explicitly registered runtime
|
|
|
|
rpc kind
|
|
UNARY
|
|
SERVER_STREAM
|
|
|
|
message encoding
|
|
JSON
|
|
PROTO
|
|
|
|
framing
|
|
BARE
|
|
CONNECT_ENVELOPE
|
|
GRPC_WEB_BINARY_ENVELOPE
|
|
GRPC_WEB_BASE64_TEXT
|
|
```
|
|
|
|
금지하는 축약:
|
|
|
|
- `CONNECT_WEB`이라는 값 하나로 protocol과 runtime을 동시에 표현
|
|
- `binary`를 Protobuf encoding과 HTTP transfer/framing 모두의 의미로 사용
|
|
- `text`를 Protobuf JSON과 `grpc-web-text` base64에 함께 사용
|
|
- response `Content-Type`을 보고 client가 protocol/runtime을 자동 선택
|
|
- 같은 body를 Connect envelope와 gRPC-Web frame decoder에 차례로 시도
|
|
|
|
operation은 exact protocol 하나에 binding한다. provider가 같은 procedure path에서
|
|
Connect와 gRPC-Web을 모두 제공하더라도 client operation이 runtime negotiation이나
|
|
failure fallback을 하지 않는다.
|
|
|
|
### 2. Connect-Web browser capability matrix
|
|
|
|
선택한 exact Connect-Web v2 package에 대해 허용할 수 있는 baseline:
|
|
|
|
| protocol | RPC | encoding | method | selected framing | baseline |
|
|
| --- | --- | --- | --- | --- | --- |
|
|
| Connect | unary | JSON | POST | bare JSON | 후보 |
|
|
| Connect | unary | Proto | POST | bare Proto | 후보 |
|
|
| Connect | unary | JSON | GET | URL query | 제한적 후보 |
|
|
| Connect | unary | Proto | GET | URL-safe base64 query | 제한적 후보 |
|
|
| Connect | server stream | JSON | POST | Connect envelope | 후보 |
|
|
| Connect | server stream | Proto | POST | Connect envelope | 후보 |
|
|
| gRPC-Web | unary | Proto | POST | binary envelope | 후보 |
|
|
| gRPC-Web | unary | JSON | POST | binary envelope | provider evidence 필요 |
|
|
| gRPC-Web | server stream | Proto | POST | binary envelope | actual Fetch/proxy evidence 필요 |
|
|
| gRPC-Web | server stream | JSON | POST | binary envelope | provider와 actual Fetch/proxy evidence 필요 |
|
|
|
|
Connect-Web의 gRPC-Web transport는 `grpc-web-text`를 구현하지 않는다. 따라서
|
|
다음 row는 build/boot 전에 거절한다.
|
|
|
|
```text
|
|
CONNECT_ES_WEB_V2_* + GRPC_WEB_BASE64_TEXT
|
|
```
|
|
|
|
Connect-Web Fetch transport에서 다음 row도 거절한다.
|
|
|
|
```text
|
|
CLIENT_STREAM
|
|
BIDIRECTIONAL_STREAM
|
|
```
|
|
|
|
client/bidi가 필요한 use case는 upload/job command, WebSocket, WebTransport 또는
|
|
별도 backend aggregation protocol로 다시 선택한다. browser Fetch request body
|
|
streaming이 일부 환경에 존재한다는 사실만으로 이 platform guarantee를 바꾸지
|
|
않는다.
|
|
|
|
### 3. Application 경계
|
|
|
|
```text
|
|
application port
|
|
-> registered semantic operation
|
|
-> exact WebRpcOperationV1
|
|
-> generated request construction
|
|
-> semantic request validation
|
|
-> selected Connect-Web transport
|
|
-> protocol-specific bounded response validation
|
|
-> generated response decode
|
|
-> semantic runtime validation
|
|
-> handwritten boundary mapper
|
|
-> application result or mapped stream event
|
|
```
|
|
|
|
application, domain, presentation과 Query cache에 다음을 노출하지 않는다.
|
|
|
|
- generated service descriptor/client
|
|
- generated Protobuf message
|
|
- `ConnectError`
|
|
- service/method string
|
|
- raw headers/trailers
|
|
- Connect envelope 또는 gRPC-Web frame
|
|
- protobuf `bigint`, `bytes`, enum/oneof representation
|
|
- transport, base URL, call option와 arbitrary metadata
|
|
|
|
feature는 `client.say(request)` 같은 generated RPC를 직접 호출하지 않는다.
|
|
`getResource`, `searchResources`, `watchJob`처럼 use-case 의미를 가진 port만 사용한다.
|
|
|
|
### 4. Versioned operation binding
|
|
|
|
기존 구현 baseline의 operation registry에는 Connect가 없다. 이 설계의
|
|
`ApiOperationContractV3`는 기존 `GRPC_WEB` 값을 재해석하지 않고
|
|
`CONNECT_HTTP` discriminant를 추가한다. source registry와 runtime이 V3를 실제
|
|
구현하기 전에는 Connect operation을 `COMPOSED`로 판정할 수 없다.
|
|
|
|
```text
|
|
WebRpcOperationV1
|
|
registryVersion
|
|
semanticOperationId
|
|
owner
|
|
providerId
|
|
|
|
protocol = CONNECT_V1 | GRPC_WEB_V1
|
|
protocolRevision
|
|
clientRuntimeId
|
|
clientRuntimeIntegrity
|
|
rpcKind = UNARY | SERVER_STREAM
|
|
messageEncoding = JSON | PROTO
|
|
framingProfileId
|
|
requestMethodProfileId
|
|
|
|
fullyQualifiedService
|
|
method
|
|
requestMessageId
|
|
responseMessageId
|
|
descriptorArtifactId
|
|
descriptorDigest
|
|
idempotencyLevel
|
|
|
|
requestSemanticSchemaId
|
|
responseSemanticSchemaId
|
|
mapperId
|
|
errorProfileId
|
|
statusMediaProfileId
|
|
metadataProfileId
|
|
|
|
authProfileId
|
|
csrfProfileId
|
|
replayPolicy
|
|
idempotencyKeyPolicy
|
|
deadlineProfileId
|
|
retryProfileId
|
|
interceptorChainId
|
|
compressionProfileId
|
|
serverStateProfileId | null
|
|
|
|
maxRequestMessageBytes
|
|
maxBrowserVisibleResponseBytes
|
|
maxEnvelopeBytes
|
|
maxResponseMessageBytes
|
|
maxResponseMessages
|
|
maxTotalResponseBytes
|
|
maxBufferedBytes
|
|
idleDeadlineMs
|
|
totalDeadlineMs
|
|
dataClassification
|
|
```
|
|
|
|
protocol-specific closed binding:
|
|
|
|
```text
|
|
CONNECT_V1
|
|
connectProtocolVersion = 1
|
|
unaryMode = POST_ONLY | GET_NO_SIDE_EFFECTS
|
|
connectStatusProfileId
|
|
connectEndStreamProfileId | null
|
|
|
|
GRPC_WEB_V1
|
|
grpcWebWireSpecRevision
|
|
grpcWebTransportProfileId
|
|
grpcWebHttpStatusProfileId
|
|
grpcWebTrailerProfileId
|
|
```
|
|
|
|
registry가 build/boot 전에 거절:
|
|
|
|
- protocol과 media/framing/status binding 불일치
|
|
- floating runtime version, missing lock integrity 또는 unknown runtime ID
|
|
- descriptor/source/generated digest mismatch
|
|
- unknown service/method/message
|
|
- generated method kind와 registered `rpcKind` 불일치
|
|
- Connect binding을 gRPC-Web decoder에 연결
|
|
- Connect-Web runtime의 `grpc-web-text`
|
|
- Connect-Web runtime의 client/bidi method
|
|
- unary response count가 1이 아님
|
|
- server stream의 message/count/total/buffer/idle/total cap 누락
|
|
- caller-provided endpoint/service/method/header
|
|
- selected runtime/browser/proxy capability matrix에 없는 row
|
|
- non-replayable command retry
|
|
- ordinary Query cache가 stream data owner
|
|
|
|
### 5. Exact provider profile
|
|
|
|
```text
|
|
ConnectWebProviderProfileV1
|
|
providerId
|
|
fixedHttpsOrigin
|
|
pathPrefix
|
|
|
|
clientRuntimeId
|
|
packageName = @connectrpc/connect-web
|
|
exactPackageVersion
|
|
packageLockIntegrity
|
|
runtimeSourceRevision
|
|
runtimeConformanceFixtureDigest
|
|
|
|
protocol = CONNECT_V1 | GRPC_WEB_V1
|
|
protocolRevision
|
|
rpcKind
|
|
messageEncoding
|
|
framingProfileId
|
|
requestMethodProfileId
|
|
statusMediaProfileId
|
|
compressionProfileId
|
|
|
|
transportInstanceId
|
|
fetchImplementationId
|
|
interceptorChainId
|
|
browserCapabilityProfileId
|
|
proxyCapabilityProfileId
|
|
|
|
credentialsMode
|
|
authProfileId
|
|
csrfProfileId
|
|
corsProfileId
|
|
referrerPolicy
|
|
redirect = ERROR
|
|
|
|
maxRequestMessageBytes
|
|
maxBrowserVisibleResponseBytes
|
|
maxEnvelopeBytes
|
|
maxResponseMessageBytes
|
|
maxResponseMessages
|
|
maxTotalResponseBytes
|
|
maxBufferedBytes
|
|
maximumDeadlineMs
|
|
```
|
|
|
|
operation과 provider의 다음 tuple은 exact match해야 한다.
|
|
|
|
```text
|
|
(
|
|
protocol,
|
|
protocolRevision,
|
|
clientRuntimeId,
|
|
exactPackageVersion,
|
|
rpcKind,
|
|
messageEncoding,
|
|
framingProfileId,
|
|
requestMethodProfileId,
|
|
statusMediaProfileId,
|
|
compressionProfileId,
|
|
browserCapabilityProfileId,
|
|
proxyCapabilityProfileId
|
|
)
|
|
```
|
|
|
|
권장 profile ID 예:
|
|
|
|
```text
|
|
CONNECT_ES_WEB_<version>_CONNECT_UNARY_JSON_POST_IDENTITY
|
|
CONNECT_ES_WEB_<version>_CONNECT_UNARY_PROTO_POST_IDENTITY
|
|
CONNECT_ES_WEB_<version>_CONNECT_UNARY_JSON_GET_PUBLIC_HEADERLESS
|
|
CONNECT_ES_WEB_<version>_CONNECT_UNARY_PROTO_GET_PUBLIC_HEADERLESS
|
|
CONNECT_ES_WEB_<version>_CONNECT_SERVER_STREAM_JSON_IDENTITY
|
|
CONNECT_ES_WEB_<version>_CONNECT_SERVER_STREAM_PROTO_IDENTITY
|
|
|
|
CONNECT_ES_WEB_<version>_GRPC_WEB_UNARY_PROTO_IDENTITY
|
|
CONNECT_ES_WEB_<version>_GRPC_WEB_UNARY_JSON_IDENTITY
|
|
CONNECT_ES_WEB_<version>_GRPC_WEB_SERVER_STREAM_PROTO_IDENTITY
|
|
CONNECT_ES_WEB_<version>_GRPC_WEB_SERVER_STREAM_JSON_IDENTITY
|
|
```
|
|
|
|
`latest`, major version만 있는 runtime ID와 runtime-independent
|
|
`GRPC_WEB_BINARY` 같은 profile 이름은 compatibility proof가 아니다.
|
|
|
|
### 6. Protobuf source와 Connect-ES v2 codegen
|
|
|
|
schema source는 backend와 frontend가 공유하는 contract authority다.
|
|
|
|
```text
|
|
authenticated `.proto` or Buf module
|
|
-> immutable source artifact
|
|
-> descriptor set
|
|
-> provenance + digest
|
|
-> Buf lint
|
|
-> production baseline breaking comparison
|
|
-> pinned `protoc-gen-es`
|
|
-> service descriptors + message schemas
|
|
-> adapter-private generated output
|
|
-> semantic validators + mapper
|
|
```
|
|
|
|
Connect-ES v2는 Connect-specific code generator를 요구하지 않는다. reference
|
|
toolchain 후보:
|
|
|
|
```text
|
|
development
|
|
@bufbuild/buf
|
|
@bufbuild/protoc-gen-es
|
|
|
|
runtime
|
|
@bufbuild/protobuf
|
|
@connectrpc/connect
|
|
@connectrpc/connect-web
|
|
```
|
|
|
|
신규 v2 구성에 deprecated Connect generator를 추가하지 않는다.
|
|
|
|
governance:
|
|
|
|
- exact Buf, `protoc-gen-es`, runtime, Node와 TypeScript version pin
|
|
- package lock integrity와 SBOM
|
|
- source/descriptor/generated output digest
|
|
- normal build에서 remote `latest` schema fetch 금지
|
|
- authenticated explicit schema update와 reviewable diff
|
|
- clean checkout regenerate diff 0
|
|
- Buf lint/breaking
|
|
- removed field number와 name reserve
|
|
- package/service/method full name 안정성
|
|
- proto3 presence와 optional 의미
|
|
- enum zero/unknown numeric policy
|
|
- oneof absence/unknown case policy
|
|
- repeated/map/nesting/bytes ceiling
|
|
- Timestamp/Duration range와 nanos
|
|
- int64/uint64 safe projection
|
|
- N/N-1 descriptor/provider conformance fixture
|
|
- generated import boundary와 dependency removal test
|
|
|
|
generated service descriptor의 idempotency option은 GET/retry 검증 입력이다. build
|
|
manifest가 `NO_SIDE_EFFECTS`, `IDEMPOTENT`와 unspecified를 구분하고 handwritten
|
|
operation row와 교차 검증한다.
|
|
|
|
transport가 unknown Protobuf field를 무시하는 default는 forward compatibility
|
|
정책일 수 있지만 semantic proof가 아니다. mapper는 known field만 projection하고
|
|
VD-24 validator가 presence, range, enum, oneof와 collection budget을 검증한다.
|
|
`google.protobuf.Any`와 error detail registry는 allowlisted descriptor만 포함한다.
|
|
|
|
### 7. Request construction
|
|
|
|
```text
|
|
validated application input
|
|
-> generated request shape
|
|
-> request semantic validator
|
|
-> bounded JSON/Proto serialization
|
|
-> protocol-owned method/path/media
|
|
-> registry-owned metadata/auth/deadline
|
|
-> final request invariant
|
|
-> bounded transport
|
|
```
|
|
|
|
invariant:
|
|
|
|
- origin/path/service/method는 registry가 생성
|
|
- application caller가 `Headers`, endpoint와 call options를 받지 않음
|
|
- request serialization 전 collection/string/bytes cap
|
|
- serialization 후 encoded byte cap
|
|
- `Content-Type`, protocol version과 timeout header는 transport owner만 설정
|
|
- credentials가 URL, method, body digest를 바꾸지 않음
|
|
- redirects are errors
|
|
- final URL/origin이 fixed provider와 다르면 response를 읽기 전에 실패
|
|
- auth-required operation은 valid credential이 없으면 network call 0회
|
|
|
|
Connect JSON/Proto와 gRPC-Web JSON/Proto serialization option은 profile에
|
|
고정한다. runtime caller가 `jsonOptions`, `binaryOptions` 또는 type registry를
|
|
override하지 않는다.
|
|
|
|
### 8. Connect unary JSON/Proto(binary) POST
|
|
|
|
Connect unary POST는 gRPC-Web frame을 사용하지 않는다.
|
|
|
|
| encoding | request media | success media | body |
|
|
| --- | --- | --- | --- |
|
|
| JSON | `application/json` | `application/json` | bare Protobuf JSON |
|
|
| Proto | `application/proto` | `application/proto` | bare binary Protobuf |
|
|
|
|
request는 `Connect-Protocol-Version: 1`을 포함하는 selected profile을 기본으로 한다.
|
|
timeout을 server에 전파하면 `Connect-Timeout-Ms`를 사용한다.
|
|
|
|
success:
|
|
|
|
```text
|
|
HTTP 200
|
|
+ exact selected media
|
|
+ bounded exactly one bare response message
|
|
+ semantic validator success
|
|
+ mapper success
|
|
```
|
|
|
|
`204`, empty body, extra JSON/body bytes, wrong media, redirect와 multiple response
|
|
message 의미는 success가 아니다.
|
|
|
|
error:
|
|
|
|
- non-200 Connect error body는 `application/json`이어야 함
|
|
- error body를 별도 작은 byte/depth/detail cap으로 bounded decode
|
|
- valid error code와 HTTP status mapping을 교차 검증
|
|
- missing/malformed body는 exact HTTP-to-Connect mapping profile로 닫음
|
|
- success media를 가진 intermediary HTML/JSON을 application DTO로 해석하지 않음
|
|
- unary trailing metadata는 `Trailer-*` response header profile로만 수용
|
|
|
|
HTTP status가 meaningful하다는 사실은 REST adapter 재사용 근거가 아니다.
|
|
procedure path, Protobuf JSON mapping, error code와 trailing metadata가 다르다.
|
|
|
|
### 9. Connect unary GET
|
|
|
|
GET은 편의 flag가 아니라 별도 threat/cache/provider profile이다.
|
|
|
|
필수 조건:
|
|
|
|
- `rpcKind=UNARY`
|
|
- descriptor idempotency가 정확히 `NO_SIDE_EFFECTS`
|
|
- application semantics가 `QUERY`
|
|
- replay policy가 `SAFE`
|
|
- product owner가 side effect 없음과 representation privacy를 승인
|
|
- deterministic request encoding evidence
|
|
- encoded URL byte ceiling
|
|
- non-sensitive request
|
|
- exact browser/proxy/CDN cache policy
|
|
- redirect 없음
|
|
|
|
`IDEMPOTENT`는 GET 허용 조건이 아니다. 같은 command를 재실행해도 된다는 의미와
|
|
side effect가 없다는 의미를 섞지 않는다.
|
|
|
|
wire query는 runtime이 만들며 caller가 직접 조합하지 않는다.
|
|
|
|
```text
|
|
connect=v1
|
|
encoding=json | proto
|
|
message=<percent-encoded or URL-safe base64 payload>
|
|
base64=1 when required
|
|
compression=<selected profile only>
|
|
```
|
|
|
|
Proto 또는 binary/compressed payload는 URL-safe base64 규칙을 따른다. 같은
|
|
semantic input이 같은 query encoding을 만들지 actual runtime fixture로 검증한다.
|
|
URL/history, CDN, reverse proxy, browser telemetry와 access log에 노출할 수 없는
|
|
field가 있으면 POST를 사용한다.
|
|
|
|
Connect-Web의 `useHttpGet`은 transport option이고 generated method의
|
|
`NO_SIDE_EFFECTS`를 보고 GET으로 바꾼다. GET-enabled transport를 검토되지 않은
|
|
service/method와 공유하지 않는다.
|
|
|
|
```text
|
|
ConnectGetTransportProfile
|
|
transportInstanceId
|
|
allowedSemanticOperationIds[]
|
|
allowedDescriptorMethods[]
|
|
deterministicEncodingFixtureDigest
|
|
maximumUrlBytes
|
|
cacheProfileId
|
|
headerProfileId
|
|
```
|
|
|
|
baseline `PUBLIC_HEADERLESS` GET:
|
|
|
|
- Authorization, CSRF, trace와 application header 없음
|
|
- cookie/private credential 없음
|
|
- `Connect-Timeout-Ms` 없음
|
|
- local AbortSignal deadline만 사용
|
|
- exact `Access-Control-Allow-Origin`
|
|
- public cacheability와 response classification 일치
|
|
|
|
timeout/auth header가 필요하면 GET 자체는 가능해도 “headerless/no-preflight”
|
|
profile이 아니다. `GET_WITH_PREFLIGHT_PRIVATE`를 별도 승인하지 않은 한 POST를
|
|
사용한다.
|
|
|
|
HTTP/CDN cache hit도 response validator와 mapper를 건너뛰지 않는다. HTTP cache,
|
|
TanStack Query memory cache와 persisted cache는 서로 다른 owner다.
|
|
|
|
### 10. Connect server-stream state machine
|
|
|
|
Connect server stream:
|
|
|
|
```text
|
|
POST
|
|
Content-Type: application/connect+json | application/connect+proto
|
|
request body: exactly one non-terminal request envelope
|
|
HTTP success status: exactly 200
|
|
response body: one or more Connect envelopes
|
|
final envelope: exactly one EndStreamResponse
|
|
```
|
|
|
|
browser `SERVER_STREAM` method의 request는 generated input message 정확히 하나다.
|
|
zero/multiple request message, request-side EndStream flag와 compressed/reserved flag를
|
|
거절한다.
|
|
|
|
stream HTTP 200은 application success가 아니다.
|
|
|
|
identity baseline envelope:
|
|
|
|
```text
|
|
1 byte flags
|
|
4 byte unsigned big-endian message length
|
|
N bytes message
|
|
```
|
|
|
|
selected revision에서:
|
|
|
|
- bit 0: compressed message
|
|
- bit 1: `EndStreamResponse`
|
|
- 나머지 bit: reserved
|
|
|
|
reference identity profile은 compressed bit와 reserved bit를 거절한다.
|
|
data envelope는 selected JSON/Proto response message이고 `EndStreamResponse`는
|
|
stream encoding과 무관하게 bounded JSON contract다.
|
|
|
|
state:
|
|
|
|
```text
|
|
INIT
|
|
-> HEADERS_ACCEPTED
|
|
-> DATA*
|
|
-> END_STREAM_SUCCESS
|
|
-> END_STREAM_ERROR
|
|
-> CLOSED
|
|
```
|
|
|
|
terminal invariant:
|
|
|
|
- `EndStreamResponse`가 정확히 한 번 존재
|
|
- terminal은 마지막 envelope
|
|
- terminal 뒤 byte/data/envelope 없음
|
|
- success terminal은 `error` property 없음
|
|
- failure terminal은 valid non-empty error code
|
|
- `error: null`, empty error와 unknown code 거절
|
|
- trailing metadata key/value/count/decoded-byte cap
|
|
- error detail type/count/value/decoded-byte cap
|
|
- EOF 전에 terminal이 없으면 contract failure
|
|
- duplicate/early terminal은 contract failure
|
|
|
|
incremental decoder:
|
|
|
|
```text
|
|
ReadableStream
|
|
-> bounded 5-byte prefix
|
|
-> validate flags
|
|
-> validate declared length before allocation
|
|
-> bounded envelope payload
|
|
-> data or EndStream decoder
|
|
-> semantic message validator
|
|
-> mapper
|
|
-> bounded application queue
|
|
```
|
|
|
|
`Content-Length`와 declared envelope length만 믿지 않는다. 다음 budget을 각각
|
|
enforce한다.
|
|
|
|
- maximum browser-visible total bytes
|
|
- maximum envelope bytes
|
|
- maximum decoded response message bytes
|
|
- maximum message count
|
|
- maximum terminal/error JSON bytes
|
|
- maximum buffered mapped events
|
|
- idle deadline
|
|
- total deadline
|
|
|
|
consumer가 queue ceiling보다 느리면 reader backpressure를 유지한다. 계속 증가하는
|
|
buffer, UI event마다 무제한 render와 stream 전체 `Uint8Array` materialization을
|
|
금지한다.
|
|
|
|
stream에서 이미 전달한 event는 뒤의 terminal error로 rollback되지 않는다.
|
|
application은 typed terminal failure를 받고 reducer/resume 정책을 수행한다.
|
|
partial stream을 Query success value로 cache하지 않는다.
|
|
|
|
### 11. Connect-Web의 gRPC-Web transport
|
|
|
|
`createGrpcWebTransport()`를 선택하면 VD-27의 gRPC-Web frame, trailer와 status
|
|
state machine을 적용한다. Connect envelope와 `EndStreamResponse`를 적용하지
|
|
않는다.
|
|
|
|
Connect-Web runtime-specific 차이:
|
|
|
|
- Fetch 기반 unary/server-stream
|
|
- binary Protobuf가 default
|
|
- Protobuf JSON은 provider가 지원할 때 별도 profile
|
|
- `grpc-web-text` 미지원
|
|
- binary server stream은 exact browser/proxy incremental evidence가 있을 때 후보
|
|
|
|
따라서 VD-27의 다른 runtime용 base64 text row를 삭제하거나 재해석하지 않고
|
|
runtime ID별 capability matrix를 만든다.
|
|
|
|
```text
|
|
GOOGLE_GRPC_WEB_XHR_<version> + its exact supported profiles
|
|
CONNECT_ES_WEB_<version> + binary-envelope profiles only
|
|
```
|
|
|
|
gRPC-Web binary server stream을 Connect-Web에서 선택하려면 Chromium, Firefox,
|
|
WebKit과 actual provider/proxy에서 fragmentation, flush, cancellation,
|
|
backpressure, trailer와 terminal status를 증명해야 한다.
|
|
|
|
### 12. Stock Connect-Web runtime 한계
|
|
|
|
stock runtime dependency 설치와 generated client call 성공만으로 production
|
|
adapter를 `COMPLETE`로 판정하지 않는다.
|
|
|
|
선택한 runtime source와 package conformance에서 확인할 항목:
|
|
|
|
- unary success가 `Response.json()` 또는 `Response.arrayBuffer()`로 전체 body를
|
|
materialize하는지
|
|
- unary error JSON도 bounded read 없이 materialize하는지
|
|
- envelope parser에 registry-owned envelope/message/total cap hook이 있는지
|
|
- compressed output flag 처리
|
|
- terminal exact-once/final enforcement
|
|
- reserved flag 처리
|
|
- abort와 reader cleanup
|
|
- `timeoutMs <= 0` 의미
|
|
|
|
현재 reference 대상 Connect-Web transport API만으로 registry의 raw-byte,
|
|
per-envelope와 per-message ceiling을 모두 전달할 수 있다고 가정하지 않는다.
|
|
|
|
reference implementation에는 다음 중 하나가 필요하다.
|
|
|
|
1. stock client serialization/service descriptor를 사용하고 platform-owned bounded
|
|
`Transport`가 Fetch, media/status와 decode state를 소유
|
|
2. stock transport 앞의 bounded Fetch wrapper와 별도 strict stream conformance
|
|
layer가 모든 ceiling/terminal invariant를 실제로 증명
|
|
3. 필요한 cap hook이 있는 검증된 upstream version을 exact pin
|
|
|
|
bounded Fetch/Transport 요구:
|
|
|
|
- network body가 browser에 노출하는 decoded byte를 incremental count
|
|
- cap 초과 즉시 reader cancel과 typed contract failure
|
|
- success와 error body 모두 같은 ceiling owner 통과
|
|
- final URL/origin, status와 media를 decode 전 검증
|
|
- timeout/AbortSignal을 fetch와 reader에 전달
|
|
- response/reader lock을 모든 terminal path에서 release
|
|
- interceptor 이후가 아니라 wire decode 전에 ceiling 적용
|
|
|
|
Fetch가 content decoding을 끝낸 뒤 제공하는 byte만 client에서 셀 수 있는 경우,
|
|
raw compressed byte와 decompression ratio ceiling은 BFF/proxy가 소유한다. client
|
|
cap을 upstream cap의 대체물로 간주하지 않는다.
|
|
|
|
selected exact package version의 malformed terminal fixture가 protocol의
|
|
exact-once/final 요구를 만족하지 못하면 wrapper가 아니라 custom bounded
|
|
Transport/parser로 닫는다.
|
|
|
|
이 gap이 닫히기 전 status:
|
|
|
|
```text
|
|
DESIGNED_NOT_IMPLEMENTED
|
|
```
|
|
|
|
### 13. Compression profile
|
|
|
|
Connect protocol은 unary HTTP compression과 streaming message compression을
|
|
정의한다. protocol capability가 stock browser runtime implementation 증거는 아니다.
|
|
|
|
reference baseline:
|
|
|
|
```text
|
|
requestMessageCompression = IDENTITY
|
|
streamResponseMessageCompression = IDENTITY
|
|
connectGetCompression = IDENTITY
|
|
```
|
|
|
|
Connect-Web stream decoder가 compressed output을 지원하지 않는 selected version은
|
|
compressed flag를 contract failure로 처리한다. proxy가 임의로 message compression을
|
|
활성화하지 않도록 upstream configuration과 fixture를 둔다.
|
|
|
|
unary whole-body HTTP `Content-Encoding`은 별도 profile이다.
|
|
|
|
```text
|
|
UnaryHttpCompressionProfile
|
|
mode = IDENTITY_ONLY | BROWSER_MANAGED
|
|
allowedCodings[]
|
|
maximumEncodedBytesAtProxy
|
|
maximumDecodedBytesAtProxy
|
|
maximumBrowserVisibleDecodedBytes
|
|
```
|
|
|
|
`BROWSER_MANAGED`는 actual browser가 decode한 body에 client ceiling을 적용하고,
|
|
BFF/proxy가 encoded/decompressed ceiling과 decompression bomb 방어를 증명할 때만
|
|
승인한다. 첫 reference profile은 `IDENTITY_ONLY`다.
|
|
|
|
### 14. Interceptor chain
|
|
|
|
Connect interceptor는 onion 구조이고 configuration array의 마지막 interceptor가
|
|
먼저 적용된다. source array 순서를 읽고 security order를 추측하지 않도록 effective
|
|
execution order를 registry artifact와 test evidence로 만든다.
|
|
|
|
권장 logical order:
|
|
|
|
```text
|
|
total deadline owner
|
|
-> logical-call telemetry
|
|
-> exactly-one replay/retry coordinator
|
|
-> attempt telemetry
|
|
-> credential owner
|
|
-> final request invariant
|
|
-> bounded transport
|
|
```
|
|
|
|
```text
|
|
ConnectInterceptorChainV1
|
|
interceptorChainId
|
|
effectiveOrder[]
|
|
implementationDigests[]
|
|
allowedContextKeyIds[]
|
|
retryOwner
|
|
credentialOwner
|
|
finalMutatorId
|
|
```
|
|
|
|
invariant:
|
|
|
|
- deadline owner는 retry/backoff 전체를 감쌈
|
|
- retry coordinator가 attempt마다 남은 deadline을 계산
|
|
- credential attach 뒤 URL/method/body를 바꾸는 interceptor 없음
|
|
- final invariant 뒤 mutating interceptor 없음
|
|
- retry/auth-refresh/Query/proxy가 같은 call을 중복 replay하지 않음
|
|
- streaming response 관찰은 `AsyncIterable`을 bounded wrapper로 감쌈
|
|
- stream을 logging 목적으로 선소비하거나 tee하여 무제한 buffer하지 않음
|
|
- application caller에게 arbitrary `ContextValues`를 노출하지 않음
|
|
- context key는 typed, collision-free, registry-owned control 값만 사용
|
|
|
|
interceptor logging은 raw request/response, URL query message, metadata, Protobuf
|
|
payload와 backend error message를 기록하지 않는다.
|
|
|
|
### 15. Error boundary
|
|
|
|
Connect-Web은 supported protocol의 실패를 `ConnectError`로 표면화할 수 있지만
|
|
application error type으로 사용하지 않는다.
|
|
|
|
```text
|
|
bounded protocol failure
|
|
-> ConnectError inspection inside adapter
|
|
-> registered error profile
|
|
-> safe AppFailure
|
|
```
|
|
|
|
safe mapping 입력:
|
|
|
|
- protocol
|
|
- registered operation/provider/runtime/profile ID
|
|
- allowlisted Connect/gRPC code
|
|
- local cancel/deadline/retry context
|
|
- allowlisted typed error detail projection
|
|
|
|
기본적으로 폐기 또는 redaction:
|
|
|
|
- `message`와 `rawMessage`
|
|
- arbitrary metadata/header/trailer
|
|
- error detail raw bytes
|
|
- error detail `debug`
|
|
- field values, resource identifiers와 provider stack
|
|
|
|
```text
|
|
WebRpcErrorProfileV1
|
|
errorProfileId
|
|
allowedCodes[]
|
|
codeToAppFailure[]
|
|
allowedDetailTypes[]
|
|
maximumErrorBodyBytes
|
|
maximumErrorDepth
|
|
maximumDetailCount
|
|
maximumDetailValueBytes
|
|
maximumTotalDetailBytes
|
|
metadataAllowlist[]
|
|
```
|
|
|
|
Connect unary는 HTTP status와 explicit error code를 교차 검증한다. malformed/missing
|
|
error body의 HTTP mapping과 valid explicit error mapping을 구분한다. Connect
|
|
stream은 final `EndStreamResponse.error`가 authority다. gRPC-Web은 VD-27의
|
|
terminal status/trailer authority를 사용한다.
|
|
|
|
같은 `ConnectError` API가 세 wire status source를 같게 만들지 않는다.
|
|
|
|
error code만으로 retry하지 않는다. `Unavailable`은 operation replay safety와
|
|
backend effect 여부를 증명하지 않는다.
|
|
|
|
### 16. Total deadline, timeout과 cancellation
|
|
|
|
deadline은 다음 전체를 포함한다.
|
|
|
|
```text
|
|
auth readiness
|
|
+ request validation/serialization
|
|
+ network attempts
|
|
+ retry backoff
|
|
+ response read
|
|
+ generated decode
|
|
+ semantic validation
|
|
+ mapper
|
|
+ stream idle/total lifetime
|
|
```
|
|
|
|
각 attempt 직전:
|
|
|
|
```text
|
|
remaining = deadlineAt - monotonicNow
|
|
if remaining <= 0:
|
|
fail locally, fetch 0회
|
|
else:
|
|
pass remaining as timeoutMs
|
|
```
|
|
|
|
stock runtime에서 `timeoutMs <= 0`이 “timeout 없음”으로 해석될 수 있으므로 0을
|
|
전달해 즉시 timeout을 기대하지 않는다.
|
|
|
|
wire timeout:
|
|
|
|
- Connect: `Connect-Timeout-Ms`
|
|
- gRPC-Web: `Grpc-Timeout`
|
|
|
|
local timer와 AbortSignal도 유지한다. server timeout header만으로 browser reader와
|
|
mapper가 정지한다고 가정하지 않는다.
|
|
|
|
cancel cause를 별도로 분류한다.
|
|
|
|
```text
|
|
USER
|
|
NAVIGATION
|
|
SCOPE_CHANGE
|
|
SESSION_CHANGE
|
|
DEADLINE
|
|
KILL_SWITCH
|
|
CONSUMER_CLOSED
|
|
```
|
|
|
|
Connect-Web이 AbortSignal failure를 `ConnectError(Code.Canceled)`로 정규화해도
|
|
local cause를 잃지 않는다. cancel 시 fetch, stream reader, mapper queue와
|
|
downstream subscription을 함께 닫는다.
|
|
|
|
command abort는 backend effect가 없다는 증거가 아니다. keyed command는 같은
|
|
idempotency key로 reconcile하고 non-replayable command는 outcome unknown을
|
|
표시한다.
|
|
|
|
### 17. Retry와 stream resume
|
|
|
|
한 logical operation의 retry owner는 정확히 하나다.
|
|
|
|
| semantics | baseline |
|
|
| --- | --- |
|
|
| safe unary query | bounded retry 후보 |
|
|
| idempotent unary | descriptor + backend evidence가 있을 때 후보 |
|
|
| keyed command | backend dedupe/reconcile 뒤 같은 key로만 후보 |
|
|
| non-replayable command | retry 금지 |
|
|
| server stream open | 첫 mapped message 전 safe/idempotent row만 후보 |
|
|
| server stream after message | transport retry 금지 |
|
|
|
|
retry decision은 다음 conjunction이다.
|
|
|
|
```text
|
|
operation replay policy
|
|
AND descriptor idempotency
|
|
AND provider evidence
|
|
AND failure category
|
|
AND remaining deadline/backoff budget
|
|
AND attempt ceiling
|
|
```
|
|
|
|
Connect code의 일반 설명만으로 command를 replay하지 않는다. 401 refresh replay,
|
|
interceptor retry, TanStack Query retry와 proxy retry를 별개 owner로 동시에 켜지
|
|
않는다.
|
|
|
|
stream에서 한 message라도 application에 전달한 뒤 reconnect는 새로운 transport
|
|
attempt가 아니라 application resume protocol이다.
|
|
|
|
```text
|
|
sequence
|
|
resume token
|
|
dedupe window
|
|
gap policy
|
|
snapshot/resync
|
|
retention
|
|
reauth
|
|
```
|
|
|
|
이 계약이 backend에 없으면 stream failure 뒤 자동 reconnect하지 않는다.
|
|
|
|
### 18. CORS, credentials, auth와 CSRF
|
|
|
|
same-origin BFF를 기본 권장한다. cross-origin provider는 actual origin에서 exact
|
|
CORS evidence가 필요하다.
|
|
|
|
gRPC-Web baseline:
|
|
|
|
```text
|
|
Access-Control-Allow-Methods: POST
|
|
Access-Control-Allow-Headers:
|
|
Content-Type,
|
|
Grpc-Timeout,
|
|
X-Grpc-Web,
|
|
X-User-Agent
|
|
Access-Control-Expose-Headers:
|
|
Grpc-Status,
|
|
Grpc-Message,
|
|
Grpc-Status-Details-Bin
|
|
```
|
|
|
|
Connect baseline:
|
|
|
|
```text
|
|
Access-Control-Allow-Methods: GET, POST
|
|
Access-Control-Allow-Headers:
|
|
Content-Type,
|
|
Connect-Protocol-Version,
|
|
Connect-Timeout-Ms,
|
|
X-User-Agent
|
|
```
|
|
|
|
Authorization, CSRF, idempotency와 trace header가 있으면 exact allowlist에
|
|
추가한다. custom response header는 expose하고 Connect unary custom trailer는
|
|
`Trailer-<name>`을 expose한다.
|
|
|
|
dynamic preflight response:
|
|
|
|
```text
|
|
Vary:
|
|
Origin,
|
|
Access-Control-Request-Method,
|
|
Access-Control-Request-Headers
|
|
```
|
|
|
|
preflight cache TTL은 rollback propagation budget 안에 둔다. cached permissive
|
|
CORS가 incident response보다 오래 남지 않게 한다.
|
|
|
|
credentials:
|
|
|
|
- wildcard origin과 credentials 조합 금지
|
|
- fixed allowed origin
|
|
- exact `credentialsMode`
|
|
- TLS와 redirect 없음
|
|
- credential owner 외 caller header 금지
|
|
- token/cookie를 GET query message에 넣지 않음
|
|
|
|
Connect GET이 preflight를 피하려면 timeout을 포함한 request header가 없어야 한다.
|
|
response는 origin을 허용해야 한다. header가 추가되면 normal preflight profile로
|
|
판정한다.
|
|
|
|
cookie profile은 protocol이 POST 또는 non-simple media라는 이유만으로 CSRF가
|
|
해결됐다고 보지 않는다. backend가 Origin, Fetch Metadata와 selected CSRF token
|
|
contract를 검증한다. baseline public GET에는 cookie credential을 사용하지 않는다.
|
|
|
|
### 19. Provider, BFF와 proxy evidence
|
|
|
|
Connect-native server/BFF는 content type으로 같은 procedure path의 Connect와
|
|
gRPC-Web을 구분해 제공할 수 있다.
|
|
|
|
```text
|
|
Connect unary:
|
|
application/json
|
|
application/proto
|
|
|
|
Connect stream:
|
|
application/connect+json
|
|
application/connect+proto
|
|
|
|
gRPC-Web:
|
|
application/grpc-web
|
|
application/grpc-web+proto
|
|
application/grpc-web+json
|
|
```
|
|
|
|
client는 이 content type을 protocol negotiation에 사용하지 않고 selected
|
|
provider row의 response 검증에만 사용한다.
|
|
|
|
Envoy `grpc_web` filter가 존재한다는 사실은 Connect protocol endpoint 증거가
|
|
아니다. Connect protocol을 선택하려면 Connect-aware BFF/server/gateway의 exact
|
|
version, route와 fixture가 필요하다.
|
|
|
|
```text
|
|
ProxyCapabilityProfileV1
|
|
proxyProduct
|
|
exactVersion
|
|
configDigest
|
|
downstreamProtocol
|
|
upstreamProtocol
|
|
routeDigest
|
|
corsProfileId
|
|
authProfileId
|
|
requestLimitProfileId
|
|
responseLimitProfileId
|
|
timeoutPropagationProfileId
|
|
streamBuffering = DISABLED
|
|
streamFlushProfileId
|
|
statusPreservationProfileId
|
|
terminalPreservationProfileId
|
|
```
|
|
|
|
staging/production-like evidence:
|
|
|
|
- exact service/method routing
|
|
- request/response content type preservation
|
|
- protocol version/timeout/auth header behavior
|
|
- encoded/decompressed/message/body ceiling
|
|
- Connect unary HTTP/error mapping
|
|
- Connect stream HTTP 200 + final EndStream preservation
|
|
- gRPC-Web body trailer/status preservation
|
|
- no gateway status synthesis without selected fixture
|
|
- stream first-byte and inter-message flush
|
|
- no proxy/CDN antivirus full-body buffering
|
|
- AbortSignal/downstream disconnect propagation
|
|
- slow consumer/backpressure behavior
|
|
- idle/total timeout behavior
|
|
- Chromium, Firefox와 WebKit
|
|
- supported HTTP/1.1/HTTP/2 and enterprise path
|
|
|
|
fake Fetch와 unit mock만으로 provider conformant가 아니다.
|
|
|
|
### 20. Runtime schema와 mapper
|
|
|
|
generated Protobuf decode success 뒤에도 semantic validation을 수행한다.
|
|
|
|
```text
|
|
untrusted bounded bytes/message
|
|
-> generated JSON/Proto decode
|
|
-> operation-specific RuntimeCodec
|
|
-> ValidatedDto
|
|
-> BoundaryMapper
|
|
-> immutable application projection
|
|
```
|
|
|
|
validator/mapper가 소유:
|
|
|
|
- required/presence meaning
|
|
- string/list/map/bytes semantic ceiling
|
|
- int64/uint64 safe range 또는 decimal projection
|
|
- Timestamp/Duration normalization
|
|
- enum zero와 unknown numeric mapping
|
|
- oneof absence/unknown branch
|
|
- NaN/Infinity and numeric domain rule
|
|
- field-level data classification
|
|
- unknown-field projection
|
|
- error detail allowlist
|
|
|
|
mapper는 throw-only 함수가 아니라 typed mapping result를 반환한다. generated
|
|
message, native `Uint8Array`, class instance와 transport metadata가 output에 남으면
|
|
cache admission과 application boundary를 거절한다.
|
|
|
|
server stream은 message마다 validate/map한 뒤 bounded reducer로 넘긴다. raw
|
|
Protobuf message를 realtime JSON envelope로 다시 감싸지 않는다.
|
|
|
|
### 21. Server State와 Connect-Query
|
|
|
|
TanStack Query가 mapped unary server state의 유일한 기본 memory owner다.
|
|
|
|
```text
|
|
Connect/gRPC-Web unary
|
|
-> bounded protocol success
|
|
-> semantic validation
|
|
-> mapper
|
|
-> immutable application read model
|
|
-> TanStack Query
|
|
```
|
|
|
|
Query key는 semantic identity를 사용한다.
|
|
|
|
금지:
|
|
|
|
- service/method string
|
|
- Protobuf request bytes/generated message
|
|
- Connect GET URL/query string
|
|
- protocol name/runtime version
|
|
- raw metadata/trailer
|
|
- `ConnectError`
|
|
|
|
transport가 REST에서 Connect 또는 gRPC-Web으로 바뀌어도 use-case/result 의미가
|
|
같으면 semantic query family를 유지할 수 있다. mapper/result identity나 scope가
|
|
바뀌면 query definition version/release epoch를 바꾼다.
|
|
|
|
`@connectrpc/connect-query`는 reference default dependency가 아니다. generated
|
|
operation을 presentation에서 직접 호출하거나 generated message를 Query cache에
|
|
넣어 기존 gateway/schema/mapper/key policy를 우회하지 않는다. 실제 도입하려면
|
|
별도 ADR이 같은 boundary와 single cache owner를 증명해야 한다.
|
|
|
|
Connect GET의 browser/CDN cache와 TanStack Query는 별도 owner다. HTTP cache
|
|
response도 adapter validation/mapper를 통과하고, private scope를 public cache에서
|
|
복원하지 않는다.
|
|
|
|
command result를 query data owner로 쓰지 않는다. success 뒤 registered invalidation
|
|
topic/version을 발행한다. stream partial state는 ordinary Query cache가 아니라
|
|
bounded feature reducer/snapshot owner가 관리한다.
|
|
|
|
transport retry가 enabled인 query는 TanStack `retry=false`다.
|
|
|
|
### 22. Observability와 privacy
|
|
|
|
low-cardinality dimensions:
|
|
|
|
```text
|
|
semanticOperationId
|
|
providerId
|
|
protocol
|
|
clientRuntimeId
|
|
rpcKind
|
|
messageEncoding
|
|
framingProfileId
|
|
result category
|
|
retry bucket
|
|
cancel cause
|
|
browserCapabilityProfileId
|
|
proxyCapabilityProfileId
|
|
```
|
|
|
|
metrics:
|
|
|
|
- logical calls/attempts/retries
|
|
- auth wait
|
|
- first-byte latency
|
|
- unary total latency
|
|
- stream open/active duration
|
|
- message count/bytes/buffer high-water mark
|
|
- terminal success/error/missing/early/duplicate
|
|
- media/status mismatch
|
|
- byte/envelope/message cap rejection
|
|
- deadline/cancel cause
|
|
- CORS/preflight/provider incompatibility
|
|
- mapper/schema rejection
|
|
|
|
기록하지 않음:
|
|
|
|
- request/response Protobuf or JSON
|
|
- Connect GET `message` query
|
|
- auth/CSRF/idempotency token
|
|
- metadata/trailer raw value
|
|
- backend error message/detail/debug
|
|
- user/resource identifier
|
|
- generated descriptor payload
|
|
|
|
trace propagation은 interceptor-owned allowlisted header만 사용한다. arbitrary
|
|
provider metadata를 trace attribute로 복사하지 않는다.
|
|
|
|
### 23. Security invariants
|
|
|
|
- fixed HTTPS origin/path prefix
|
|
- caller-controlled absolute URL 없음
|
|
- redirects rejected
|
|
- service/method from immutable descriptor/registry only
|
|
- arbitrary header/context value 없음
|
|
- auth/CSRF owner 분리
|
|
- request/response/error/metadata/stream queue cap
|
|
- decompression cap
|
|
- no raw payload logging
|
|
- GET sensitive input 금지
|
|
- application-level authorization은 backend authority
|
|
- frontend schema validation을 authorization으로 간주하지 않음
|
|
- stream event는 현재 session/scope generation과 일치할 때만 적용
|
|
- kill switch가 신규 call/stream admission을 막고 active reader를 cancel
|
|
|
|
같은 origin에 Connect와 gRPC-Web route가 있어도 content-type confusion fixture를
|
|
실행한다. Connect body를 REST JSON endpoint가 받아들이거나 gRPC-Web frame을
|
|
Connect stream으로 해석하는 route를 허용하지 않는다.
|
|
|
|
### 24. Verification matrix
|
|
|
|
registry/build:
|
|
|
|
- duplicate/unknown profile와 descriptor 거절
|
|
- runtime/package integrity mismatch 거절
|
|
- protocol/framing/media/status tuple mismatch 거절
|
|
- Connect-Web + `grpc-web-text` 거절
|
|
- client/bidi 거절
|
|
- GET without `NO_SIDE_EFFECTS` 거절
|
|
- missing cap/deadline/replay policy 거절
|
|
- generated import boundary
|
|
- clean code generation, lint/breaking와 digest
|
|
|
|
Connect unary:
|
|
|
|
- JSON/Proto success
|
|
- wrong media, empty/extra body, 204와 redirect
|
|
- bounded malformed success body
|
|
- all allowlisted explicit error codes
|
|
- malformed/missing/oversized error body
|
|
- HTTP/error code mismatch
|
|
- prefixed trailer allowlist/cap
|
|
- browser-visible byte cap
|
|
|
|
Connect GET:
|
|
|
|
- only `NO_SIDE_EFFECTS`
|
|
- deterministic JSON/Proto encoding
|
|
- URL-safe base64
|
|
- URL ceiling
|
|
- sensitive field rejection
|
|
- headerless/no-preflight profile
|
|
- timeout/auth header가 있는 preflight profile rejection
|
|
- cache key/Vary/private/public behavior
|
|
|
|
Connect server stream:
|
|
|
|
- prefix가 모든 byte 경계에서 fragmentation
|
|
- declared length 0, boundary와 cap 초과
|
|
- truncated prefix/payload
|
|
- compressed/reserved flag rejection
|
|
- JSON/Proto message decode failure
|
|
- zero/many messages followed by valid terminal
|
|
- missing, duplicate, early terminal
|
|
- data/bytes after terminal
|
|
- malformed/oversized terminal JSON
|
|
- terminal error/details/metadata cap
|
|
- slow consumer/backpressure
|
|
- idle/total deadline와 cancellation
|
|
|
|
Connect-Web gRPC-Web:
|
|
|
|
- binary Proto unary/server stream
|
|
- approved JSON provider row
|
|
- `grpc-web-text` rejection
|
|
- VD-27 frame/trailer/status negative corpus
|
|
- actual Fetch incremental behavior
|
|
|
|
retry/cancel:
|
|
|
|
- one retry owner
|
|
- total attempt/backoff/deadline ceiling
|
|
- `timeoutMs <= 0` network call 0회
|
|
- AbortSignal propagates to reader
|
|
- no replay for non-replayable command
|
|
- keyed command same key/reconcile
|
|
- stream first message 이후 reconnect 0회
|
|
|
|
CORS/proxy/browser:
|
|
|
|
- exact preflight allow/expose/Vary
|
|
- credential/wildcard rejection
|
|
- `Trailer-*` exposure
|
|
- content type and terminal preservation
|
|
- no stream buffering
|
|
- Chromium/Firefox/WebKit with actual provider/proxy
|
|
|
|
cache/mapper:
|
|
|
|
- generated/raw values admission rejection
|
|
- semantic key only
|
|
- scope/session generation switch
|
|
- protocol migration without duplicate server-state owner
|
|
- stream partial state Query cache rejection
|
|
|
|
malformed/oversized fixture는 OOM, unhandled rejection과 secret-bearing log 없이
|
|
fail-closed해야 한다.
|
|
|
|
### 25. Capability state와 promotion
|
|
|
|
현재:
|
|
|
|
```text
|
|
design = ACCEPTED
|
|
referenceRuntime = DESIGNED_NOT_IMPLEMENTED
|
|
productComposition = NOT_SELECTED
|
|
clientOrBidi = PLATFORM_LIMITED
|
|
```
|
|
|
|
state transition:
|
|
|
|
```text
|
|
DESIGNED_NOT_IMPLEMENTED
|
|
-> AVAILABLE_NOT_COMPOSED
|
|
-> COMPOSED / TrafficAdmission=DISABLED
|
|
-> INTERNAL_CANARY
|
|
-> LIMITED
|
|
-> ACTIVE
|
|
```
|
|
|
|
`AVAILABLE_NOT_COMPOSED` 조건:
|
|
|
|
- exact dependencies와 generated artifacts 존재
|
|
- versioned registry and runtime profiles 구현
|
|
- bounded unary/stream transport 구현
|
|
- schema/mapper/error/deadline/retry/cache boundary 구현
|
|
- negative/conformance corpus 통과
|
|
- dependency/removal test 통과
|
|
|
|
`COMPOSED` 조건:
|
|
|
|
- product operation/provider/backend owner 선택
|
|
- proto/descriptor and N/N-1 contract
|
|
- auth/CSRF/CORS/cache/retry/resume contract
|
|
- actual provider/proxy/browser evidence
|
|
- runtime config/kill switch/traffic admission
|
|
- observability and incident owner
|
|
|
|
문서만으로 state를 올리지 않는다.
|
|
|
|
### 26. Rollout
|
|
|
|
1. product가 bounded-context operation family와 backend/provider owner를 선택한다.
|
|
2. operation마다 Connect 또는 gRPC-Web protocol 하나를 선택한다.
|
|
3. immutable proto/descriptor, idempotency와 compatibility baseline을 확정한다.
|
|
4. exact Connect-Web/codegen/runtime dependency를 optional adapter boundary에 설치한다.
|
|
5. bounded unary와 stream transport, validator/mapper/error profile을 구현한다.
|
|
6. fake provider negative corpus와 exact package conformance를 통과한다.
|
|
7. actual BFF/proxy/browser matrix를 통과한다.
|
|
8. reference runtime을 `AVAILABLE_NOT_COMPOSED`로 판정한다.
|
|
9. product composition 뒤 `TrafficAdmission=DISABLED`로 배포한다.
|
|
10. safe unary read를 shadow 비교하되 secondary cache/UI write를 금지한다.
|
|
11. internal canary에서 단일 protocol만 admission한다.
|
|
12. server stream은 unary와 별도 canary/evidence로 승격한다.
|
|
13. keyed command는 backend dedupe/reconcile 뒤 별도 canary한다.
|
|
14. client/bidi는 계속 `PLATFORM_LIMITED`로 표시한다.
|
|
|
|
shadow/fallback:
|
|
|
|
- Connect 결과와 REST 결과를 비교해도 cache/UI owner는 primary 하나
|
|
- client가 Connect failure를 REST/gRPC-Web로 자동 재실행하지 않음
|
|
- command dual-write 금지
|
|
- protocol 전환은 새 logical operation/controlled rollout
|
|
|
|
rollback:
|
|
|
|
- 신규 operation/stream traffic admission 중지
|
|
- active reader와 pending call cancel
|
|
- ambiguous command reconcile
|
|
- affected mapped Query cache clear/invalidate
|
|
- frontend/generated descriptor/provider route의 coherent rollback
|
|
- preflight/CDN cache TTL 고려
|
|
- approved previous protocol은 별도 registered operation으로만 복구
|
|
|
|
### 27. Incident와 recovery
|
|
|
|
kill switch granularity:
|
|
|
|
```text
|
|
provider
|
|
protocol
|
|
runtime version
|
|
operation
|
|
rpc kind
|
|
message encoding
|
|
GET profile
|
|
server stream
|
|
```
|
|
|
|
incident triage 순서:
|
|
|
|
1. semantic operation/provider/runtime/profile 확인
|
|
2. admission과 retry 차단
|
|
3. active stream cancel
|
|
4. command outcome/dedupe reconciliation
|
|
5. browser/proxy/provider/backend change correlation
|
|
6. media/status/terminal/cap/deadline bucket 확인
|
|
7. raw payload 없이 approved fixture로 재현
|
|
8. coherent rollback 또는 provider quarantine
|
|
|
|
missing terminal, wrong media와 mapper rejection을 generic network error로 숨기지
|
|
않는다. 사용자는 safe application failure만 보지만 operations telemetry는
|
|
contract/provider category를 구분한다.
|
|
|
|
### 28. Removal
|
|
|
|
1. operation traffic admission 중지와 usage 확인
|
|
2. active stream/call cancel, command reconcile
|
|
3. mapped Query cache/reducer/invalidation listener clear
|
|
4. operation/provider/schema/mapper/error/cache profile 제거
|
|
5. generated files와 descriptor/source artifact reference 제거
|
|
6. Buf/codegen/Connect runtime direct dependency 제거
|
|
7. route/CORS/proxy/CDN config 제거 또는 다른 client owner에게 이관
|
|
8. deprecated proto field reserve와 backend N/N-1 retirement window 준수
|
|
9. lockfile/SBOM/license/manifest에서 dependency 제거 확인
|
|
10. production bundle/module graph와 removal test 통과
|
|
|
|
같은 provider route가 다른 native/mobile/backend client를 지원하면 frontend 제거가
|
|
provider route 삭제 권한을 뜻하지 않는다. owner와 traffic evidence를 확인한다.
|
|
|
|
## 금지 조합 요약
|
|
|
|
- Connect protocol을 `GRPC_WEB` registry row로 가장
|
|
- Connect와 gRPC-Web frame/status decoder 공유
|
|
- Connect-Web + `grpc-web-text`
|
|
- browser Connect-Web client/bidi
|
|
- protocol auto-detection/fallback
|
|
- GET without exact `NO_SIDE_EFFECTS`
|
|
- sensitive/authenticated baseline headerless GET
|
|
- shared unreviewed `useHttpGet=true` transport
|
|
- compressed stream with unsupported runtime
|
|
- unbounded `Response.json()`/`arrayBuffer()`를 production cap으로 간주
|
|
- `timeoutMs=0`을 immediate timeout으로 사용
|
|
- arbitrary interceptor/header/context/endpoint
|
|
- duplicate transport/Query/proxy retry
|
|
- raw `ConnectError`/metadata/detail/debug 노출
|
|
- generated client/message in presentation, application or Query cache
|
|
- Connect-Query direct page usage
|
|
- generic Envoy gRPC-Web filter를 Connect endpoint 증거로 사용
|
|
- fake Fetch만으로 provider/browser conformance 판정
|
|
- partial stream을 ordinary Query success로 cache
|
|
- command protocol failover/dual-write
|
|
|
|
## 규범 기준
|
|
|
|
- [Connect protocol reference](https://connectrpc.com/docs/protocol/)
|
|
- [Choosing Connect or gRPC-Web](https://connectrpc.com/docs/web/choosing-a-protocol/)
|
|
- [Using Connect-Web clients](https://connectrpc.com/docs/web/using-clients/)
|
|
- [Connect GET requests and caching](https://connectrpc.com/docs/web/get-requests-and-caching/)
|
|
- [Connect-Web interceptors](https://connectrpc.com/docs/web/interceptors/)
|
|
- [Connect-Web errors](https://connectrpc.com/docs/web/errors/)
|
|
- [Connect-Web cancellation and timeouts](https://connectrpc.com/docs/web/cancellation-and-timeouts/)
|
|
- [Connect and gRPC-Web CORS](https://connectrpc.com/docs/cors/)
|
|
- [Connect multi-protocol support](https://connectrpc.com/docs/multi-protocol/)
|
|
- [Connect-Web code generation](https://connectrpc.com/docs/web/generating-code/)
|
|
- [Connect-ES v2 migration](https://connectrpc.com/docs/web/migrating-to-v2/)
|
|
- [Connect-Web Connect transport source](https://github.com/connectrpc/connect-es/blob/main/packages/connect-web/src/connect-transport.ts)
|
|
- [Connect-Web gRPC-Web transport source](https://github.com/connectrpc/connect-es/blob/main/packages/connect-web/src/grpc-web-transport.ts)
|
|
|
|
## 완료 기준
|
|
|
|
- Connect와 gRPC-Web이 separate protocol binding/status/framing profile이다.
|
|
- exact runtime package version/integrity/source와 provider tuple이 고정된다.
|
|
- Connect-Web runtime의 gRPC-Web text와 client/bidi 조합이 거절된다.
|
|
- Connect unary JSON/Proto POST가 bounded status/media/error state를 가진다.
|
|
- GET이 exact `NO_SIDE_EFFECTS`, privacy, deterministic encoding, URL/CORS/cache
|
|
profile을 모두 만족한다.
|
|
- Connect server stream이 bounded envelope/message/queue와 exact final
|
|
`EndStreamResponse`를 검증한다.
|
|
- stock runtime의 unary raw-byte와 stream compression/terminal gap이 bounded
|
|
Fetch/Transport 또는 검증된 upstream hook으로 닫힌다.
|
|
- total deadline, positive remaining timeout, cancellation과 exactly-one retry owner가
|
|
증명된다.
|
|
- CORS/auth/CSRF/proxy/browser evidence가 exact provider에서 유효하다.
|
|
- `protoc-gen-es` v2 generation, descriptor provenance/breaking/digest가 재현 가능하다.
|
|
- generated message/client/ConnectError가 adapter 밖으로 나오지 않는다.
|
|
- mapped immutable unary result만 semantic key로 TanStack Query에 admission된다.
|
|
- stream partial state와 raw metadata가 Query cache에 없다.
|
|
- actual Chromium/Firefox/WebKit과 proxy conformance가 통과한다.
|
|
- kill switch, coherent rollback, incident recovery와 dependency/generated/provider
|
|
removal drill이 통과한다.
|
|
- product operation/provider가 선택되기 전 `NOT_SELECTED`를 유지한다.
|