Files
tech-log-frontend/docs/architecture/backend-api-and-server-state-contract.md
T

719 lines
30 KiB
Markdown

# Backend API와 Server State handoff contract
> **정본 안내 (non-authoritative for runtime capability decisions)**
>
> Runtime Config/boot, Fetch HTTP client, Router, Query/Mutation, realtime 공통 경계, Web Worker,
> Service Worker, offline command와 Background Sync의 구현 결정은
> [프론트엔드 런타임 Capability 저장소 정합형 구현 결정 폐쇄 상세 설계](./2026-07-30-frontend-runtime-capability-repository-aligned-implementation-closed-deep-design.md)가 정본이다.
> 본 문서의 해당 서술이 정본과 충돌하면 정본을 따른다.
- 상태: frontend handoff design accepted, backend implementation/evidence pending
- 기준일: 2026-07-28
- 대상: Web API/BFF, application service, persistence, identity, GraphQL router,
Connect/gRPC-Web gateway, Protobuf REST Gateway와 운영 owner
- frontend 기준:
[API contract, Schema, Mapper와 Server State](./api-contract-schema-mapper-and-server-state.md)
- browser Protobuf/gateway 기준:
[Protobuf browser transport와 REST Gateway](./protobuf-browser-transport-and-rest-gateway.md)
- 파일 전송 backend 기준:
[Server file capability infrastructure](./server-file-capability-infrastructure.md)
- 복구 절차:
[API contract와 server-state recovery](../operations/api-contract-and-server-state-recovery.md)
## 1. 문서의 경계
이 문서는 이 frontend template이 실제 제품 backend와 연결될 때 backend가
제공해야 하는 구조, wire contract, 상태 의미와 운영 증거를 정의한다. 특정
언어·framework·cloud 제품을 강제하지 않는다. Spring, Nest/Fastify, Go,
.NET 또는 다른 stack을 사용해도 아래 불변조건은 동일하다.
이 repository에는 backend source, database migration, identity provider,
GraphQL schema/router, protobuf descriptor, Connect/gRPC-Web runtime/proxy,
REST transcoder와 실제 provider evidence가 없다. 따라서 이 문서는 backend 구현
완료 증거가 아니다.
파일 업로드·다운로드, object storage, presigned URL, multipart와 Image CDN은
별도 server file 문서가 소유한다. 이 문서는 ordinary REST/GraphQL/Connect/
gRPC-Web application API, Protobuf REST Gateway와 frontend Server State 계약만
소유한다.
## 2. 권장 논리 구조
```text
Browser
-> CDN / reverse proxy / WAF
-> Browser-facing Web API or BFF
-> authentication + authorization
-> exact operation registry
-> request schema / byte / rate limit
-> REST controller
-> optional persisted GraphQL router
-> optional Connect browser RPC gateway
-> optional gRPC-Web gateway
-> optional Protobuf REST transcoder
-> application service
-> command transaction
-> query/read-model service
-> idempotency coordinator
-> revision/validator owner
-> outbox/event owner
-> primary database
-> idempotency store
-> read replica/read model
-> event broker when selected
```
Browser-facing contract owner와 내부 service contract owner를 분리한다.
- Browser API/BFF는 CORS, cookie/CSRF 또는 bearer, public DTO, envelope,
body ceiling, status/media와 redaction을 소유한다.
- Application service는 authorization 재검사, transaction, idempotency,
conflict, revision과 domain invariant를 소유한다.
- Persistence adapter는 SQL/NoSQL/Redis vendor type, row version과 cursor
implementation을 외부 DTO에 노출하지 않는다.
- GraphQL router, Connect/gRPC-Web gateway와 REST transcoder는 선택 adapter다.
다른 protocol로 임의 fallback하거나 frontend에 내부 service address를
노출하지 않는다.
작은 제품은 이 논리 모듈을 하나의 deployable로 구현할 수 있다. deployable을
나누는 것보다 transaction/idempotency/authorization owner가 하나로 명확한지가
우선이다.
## 3. 공통 contract artifact
Backend와 frontend release는 다음 bounded contract set을 공유한다.
```text
ApiContractSetV1
globalApiContractVersion
restArtifactId + digest
runtimeSchemaManifestId + digest
mapperSemanticManifestId + digest
errorVocabularyVersion
minimumFrontendVersion
minimumBackendVersion
effectiveAt
retirementEpoch | null
optional:
graphqlSchemaId + digest
persistedGraphqlManifestId + digest
protobufDescriptorId + digest
protobufSourceOrModuleId + digest
protobufCodegenProfileId
connectProviderProfileId
grpcWebProviderProfileId
protobufRestGatewayProfileId
httpRuleArtifactId + digest
protoJsonProfileId
gatewayOpenApiArtifactId + digest
```
최소 산출물:
- authenticated OpenAPI 또는 동등한 REST schema source
- exact status/media/envelope fixture
- stable error code vocabulary
- request/response byte와 collection ceiling
- scalar/date/null/enum 의미
- N/N-1 compatibility 결과
- backend build와 frontend release가 참조하는 immutable digest
runtime config의 version 문자열 일치만 compatibility 증거로 사용하지 않는다.
artifact digest가 없는 동안에는 실제 staging conformance fixture와 수동 승인
evidence가 필요하다.
## 4. 현재 reference REST 계약
현재 frontend에 실제 조립된 operation은 다음 세 개다.
| operation | request | success |
| --- | --- | --- |
| `LIST_REFERENCE_RESOURCES` | `GET /api/reference-resources?cursor&limit&tags` | `200 application/json` |
| `GET_REFERENCE_RESOURCE` | `GET /api/reference-resources/{resourceId}` | `200 application/json` |
| `CREATE_REFERENCE_RESOURCE` | `POST /api/reference-resources` | `200` 또는 `201 application/json` |
현재 list payload는 `ReferenceResource[]`다. `cursor` 입력이 존재하더라도
`CursorPage` 출력 계약은 아직 아니다. backend가 같은 operation에서 배열을
page object로 조용히 바꾸면 schema mismatch로 실패한다.
resource DTO:
```text
ReferenceResourceDtoV1
id: non-empty string, maximum 120 characters
name: non-empty string, maximum 240 characters
createdAt?: RFC 3339 date-time
```
create command:
```text
CreateReferenceResourceCommandV1
name: trimmed string, 1..120
note?: trimmed string, 0..500
```
Backend는 frontend validation을 신뢰하지 않고 동일하거나 더 좁은 validation과
authorization을 다시 수행한다.
### 4.1 JSON envelope
모든 현재 JSON success/failure는 다음 envelope를 사용한다.
```json
{
"success": true,
"data": {},
"meta": {
"requestId": "server-request-id",
"traceId": "server-trace-id",
"correlationId": "client-correlation-id"
}
}
```
```json
{
"success": false,
"error": {
"code": "STABLE_MACHINE_CODE",
"category": "optional-safe-category",
"message": "optional non-sensitive copy",
"retryable": false,
"details": {}
},
"meta": {
"requestId": "server-request-id",
"traceId": "server-trace-id",
"correlationId": "client-correlation-id"
}
}
```
Envelope 최상위 unknown field는 현재 거절된다. ordinary resource DTO의 unknown
field는 frontend schema에서 strip되지만 additive compatibility는 contract
review와 fixture를 먼저 통과해야 한다.
`requestId`, `traceId`, `correlationId`는 각각 1..128 범위의 안전한 opaque
identifier다. credential, user data, cursor, validator와 database key를
identifier에 encode하지 않는다.
### 4.2 Status와 error
| HTTP status | 의미 |
| --- | --- |
| `400` | malformed request 또는 closed request contract 위반 |
| `401` | 인증 없음/만료. 이미 적용된 command를 401로 반환하지 않음 |
| `403` | authenticated principal에게 권한 없음 |
| `404` | authorization 정책상 공개 가능한 not-found |
| `409` | idempotency fingerprint, domain revision 또는 semantic conflict |
| `412` | selected conditional write의 `If-Match` precondition 실패 |
| `422` | field validation. bounded `details.issues[]`만 허용 |
| `429` | rate limit. 유효한 `Retry-After`와 operation 정책 제공 |
| `500/502/503/504` | server/provider failure. command effect certainty 별도 |
현재 frontend의 ordinary status mapper는 `412` 전용 처리를 아직 연결하지
않았다. conditional mutation을 선택할 때 frontend failure vocabulary와
transaction을 함께 승격해야 한다.
Backend `error.code`는 machine-readable stable code다. stack, SQL/vendor error,
raw validation value, authorization reason과 내부 service address를 반환하지
않는다.
## 5. 인증, CSRF와 CORS
현재 reference operation은 다음 profile로 조립돼 있다.
```text
auth = external bearer
Authorization: Bearer <credential>
fetch credentials = omit
CSRF profile = none
redirect = error
referrer policy = no-referrer
```
Backend/BFF는 bearer의 issuer, audience, signature algorithm, time claims와
revocation/session policy를 검증하고 operation별 authorization을 적용한다.
401과 403을 구분하며 frontend cache를 authorization authority로 사용하지 않는다.
쿠키 session으로 전환할 경우 같은 profile로 간주하지 않는다. 별도
`SAME_ORIGIN_COOKIE` profile에 다음을 함께 승인한다.
- `Secure`, `HttpOnly`, 명시적 `SameSite`와 host/path scope
- unsafe method의 CSRF token/header와 Origin/Sec-Fetch-Site 검증
- credentialed CORS에서 wildcard origin 금지
- login/logout/session rotation과 cache generation 전환
- session fixation, token rotation과 concurrent tab 동작
Cross-origin bearer provider 최소 CORS:
- exact allow-origin 목록과 bounded preflight cache
- `Authorization`, `Content-Type`, `Idempotency-Key`,
`X-Correlation-ID`, 향후 `If-None-Match`, `If-Match` 허용
- 필요한 경우 `ETag`, `Retry-After`, request/trace header만 expose
- redirect login page, HTML error body와 wildcard credential 금지
## 6. Command와 idempotency
`CREATE_REFERENCE_RESOURCE`는 keyed command다. frontend memory single-flight는
backend idempotency를 대체하지 않는다.
idempotency identity:
```text
principal/tenant
+ semantic operation ID and contract version
+ Idempotency-Key
+ canonical request fingerprint
```
권장 record:
```text
IdempotencyRecord
principalFingerprint
operationId
contractVersion
idempotencyKeyHash
requestFingerprint
state = IN_PROGRESS | COMMITTED | FAILED_SAFE | EFFECT_UNKNOWN
responseStatus
responseEnvelopeReference
resourceRevision | null
leaseOwner + leaseExpiry
retentionExpiry
createdAt + completedAt
```
불변조건:
- claim과 command transaction의 관계가 원자적이거나 crash reconciliation
가능해야 한다.
- 같은 key와 같은 fingerprint replay는 같은 authoritative receipt를 반환한다.
- 같은 key와 다른 fingerprint는 `409 IDEMPOTENCY_KEY_REUSED`다.
- concurrent replay는 하나만 실행하고 나머지는 같은 result를 기다리거나
bounded `IN_PROGRESS` 결과를 받는다.
- commit 뒤 response 유실은 새 resource를 만들지 않는다.
- `EFFECT_UNKNOWN`은 새 key로 자동 재시도하지 않고 status/reconcile endpoint로
확인한다.
- retention은 frontend retry/recovery 최대 window보다 길고 quota/abuse limit이
있다.
- key 원문과 request body를 log/metric label에 넣지 않는다.
Database unique constraint 또는 durable compare-and-set이 최종 중복 방지
authority여야 한다. process-local map/lock만 사용하지 않는다.
## 7. Cursor pagination과 snapshot
Backend가 pagination을 선택할 때 새 response schema/operation version으로 다음
contract를 제공한다.
```text
CursorPage<T>
items: T[]
nextCursor: opaque string | null
hasMore: boolean
snapshotToken: opaque string | null
```
필수 불변조건:
- `hasMore === (nextCursor !== null)`
- 동일 chain의 `snapshotToken`은 모든 page에서 동일
- cursor는 principal/tenant, filter, sort, contract version과 snapshot에 binding
- cursor는 opaque, 무결성 보호, 만료와 key rotation 정책 보유
- offset이 아니라 stable keyset ordering 사용
- total order의 마지막 tie-breaker는 immutable unique ID
- deleted/inserted row가 duplicate/gap을 만드는 의미를 snapshot 정책으로 결정
- empty page인데 `hasMore=true`인 sparse page 허용 여부를 operation profile에 고정
- cursor 최대 encoded byte, page size와 total scan/cost ceiling을 server도 강제
- invalid, expired, wrong-principal, wrong-filter cursor의 safe error code를 고정
권장 query ordering 예:
```text
ORDER BY created_at DESC, resource_id DESC
cursor payload = version + snapshot watermark + last(created_at, resource_id)
+ filter digest + principal/tenant binding + expiry
```
Cursor 원문은 log, trace, analytics와 frontend persistent storage에 넣지 않는다.
### 7.1 배열에서 page로의 migration
1. `ReferenceResourceListPagePayloadV2` schema와 새 operation/version을 추가한다.
2. backend가 N/N-1 동안 기존 배열과 page contract를 동시에 제공한다.
3. frontend가 cursor runtime을 새 bound query/infinite query에 연결한다.
4. loop/snapshot/ceiling/abort conformance를 staging에서 검증한다.
5. 새 operation을 canary한 뒤 기존 배열 operation을 retirement한다.
동일 media/status에서 payload shape만 바꾸는 in-place migration은 금지한다.
## 8. Conditional read와 revision/CAS
### 8.1 Read validator
Backend가 application-managed revalidation을 선택하면 exact mapped
representation마다 ETag를 제공한다.
```text
GET without validator
-> 200 + JSON envelope + ETag
GET with If-None-Match
-> representation unchanged: 304 + empty body
-> changed: 200 + JSON envelope + new ETag
```
불변조건:
- validator는 principal/tenant, authorization-visible representation,
response schema/mapper semantics와 encoding variant에 binding
- weak/strong 선택을 operation profile에 고정
- user-private response를 shared CDN/public cache에 저장하지 않음
- cross-origin이면 `ETag`를 expose하고 `If-None-Match`를 preflight 허용
- 304에는 JSON success envelope를 넣지 않음
- validator 원문을 log/metric/diagnostics에 넣지 않음
- `Vary``Cache-Control` owner를 명확히 하고 browser HTTP cache와
TanStack/application revalidation이 서로 다른 value owner가 되지 않게 함
Frontend는 validator와 mapped cache value의 scope, query identity,
representation version과 cache revision이 모두 일치할 때만 304를 success로
받는다. cache value가 없으면 unconditional refetch 또는 safe failure로 닫는다.
### 8.2 Conditional command
수정/삭제 command가 선택되면 DTO에 opaque domain `revision`을 추가하고:
```text
If-Match: "<revision validator>"
```
를 요구한다. 일치하지 않으면 `412` 또는 승인된 `409` contract 하나만
사용한다. frontend optimistic layer의 commit/rollback은 backend revision
authority를 대체하지 않는다.
## 9. Optimistic mutation을 위한 backend 의미
Frontend ordered optimistic layer runtime은 구현돼 있지만 제품 operation에
연결하려면 backend가 다음을 결정해야 한다.
- resource/list membership을 결정하는 canonical filter와 sort
- command가 생성/수정/삭제하는 stable identity
- server-assigned ID와 client correlation의 reconcile 방법
- authoritative resource/list revision
- conflict status와 stable error code
- commit response가 complete resource인지 receipt인지
- effect certainty와 idempotency status/reconcile endpoint
- event/outbox가 있을 때 sequence/gap/snapshot reset 의미
Create가 server-assigned ID를 사용하는 경우 temporary UI ID를 backend ID로
원자적으로 교체하고 관련 detail/list key를 reconcile하는 정책이 필요하다.
이 의미 없이 generic optimistic append를 기본 활성화하지 않는다.
## 10. Database와 application service baseline
구현 예시는 다음 논리 table/constraint를 만족해야 한다.
```text
reference_resource
tenant_id
resource_id
display_name
note
revision
created_at
updated_at
deleted_at | null
unique(tenant_id, resource_id)
idempotency_record
principal/tenant fingerprint
operation + contract version
key hash
request fingerprint
state + receipt
lease/retention timestamps
unique(principal/tenant, operation, contract version, key hash)
outbox_event when selected
aggregate identity + revision
event type/version
sequence
payload reference or bounded safe projection
publication state
```
Application service transaction은 authorization scope와 tenant predicate를
모든 read/write에 적용하고, resource mutation과 revision/outbox 기록을 같은
transaction boundary에 둔다. cache/replica lag를 고려해 command 직후 read
consistency와 invalidation owner를 선언한다.
## 11. GraphQL 선택 시 추가 구조
GraphQL은 제품 operation이 REST보다 aggregation 이점을 실제로 가질 때만
선택한다.
```text
Browser
-> persisted-operation endpoint
-> manifest allowlist
-> auth/CSRF/rate/cost/depth/alias enforcement
-> GraphQL router
-> application services/loaders
```
Backend handoff:
- authenticated immutable schema artifact와 digest
- named operation source와 persisted ID/hash manifest
- variables/result runtime fixtures
- selected GraphQL-over-HTTP revision과 exact media/status profile
- partial data policy와 safe error extension vocabulary
- field/row authorization, cost/depth/alias/list ceiling
- N/N-1 router/frontend manifest rollout과 retirement
Production endpoint는 arbitrary document와 persisted miss 후 full-document
fallback을 받지 않는다. normalized frontend entity cache는 별도 제품 선택이다.
## 12. gRPC-Web 선택 시 추가 구조
gRPC-Web은 browser-facing gateway/proxy가 실제 선택된 unary 또는 bounded
server-stream operation에만 사용한다.
```text
Browser
-> same-origin BFF/Envoy/gRPC-Web gateway
-> exact service/method allowlist
-> frame/message/deadline/status/trailer enforcement
-> internal gRPC application service
```
Backend handoff:
- authenticated proto source와 immutable descriptor digest
- Buf/protoc lint/breaking 및 deterministic generation evidence
- exact service/method/rpc-kind allowlist
- selected gRPC-Web runtime kind, client API와 binary/JSON/text/wire revision;
official XHR와 Connect-Web Fetch profile을 분리
- proxy CORS, content-type, terminal status/trailer behavior
- Envoy를 선택하면 exact version/config digest, filter order, upstream HTTP/2,
route/idle/max-stream timeout, timeout offset와 buffering/flush
- message/frame/count/queue/idle/total budget
- server-stream sequence, gap, resume와 snapshot reset protocol
- actual browser/proxy conformance
client streaming과 bidirectional streaming은 common gRPC-Web browser contract로
간주하지 않는다. upload는 REST transfer, duplex는 별도 protocol을 선택한다.
## 13. Connect-Web/Connect 선택 시 추가 구조
Connect는 Protobuf-first backend의 selected unary 또는 bounded server-stream
operation에만 사용한다. Connect protocol과 Connect-Web의 gRPC-Web transport는
서로 다른 provider row다.
```text
Browser Connect-Web adapter
-> same-origin BFF 또는 exact cross-origin Connect endpoint
-> auth/CSRF/CORS + service/method allowlist
-> Connect protocol handler
-> application service
```
Backend handoff:
- authenticated proto/Buf source, descriptor와 generated-service digest
- exact Connect-Web/client runtime과 server/gateway version
- protocol revision, JSON/binary encoding, POST 또는 approved GET
- unary HTTP/error profile 또는 stream EndStream terminal profile
- request/response/envelope/message/count/queue byte ceiling
- unary/stream compression capability; stock browser stream은 identity-only
- total/idle timeout, browser abort→server context→downstream cancellation 전파
- exact CORS allow/expose/preflight와 auth/CSRF profile
- actual Chromium/Firefox/WebKit와 selected proxy/server conformance
GET은 descriptor `NO_SIDE_EFFECTS`, non-sensitive bounded input, URL/cache key,
`Vary`와 credential policy가 모두 승인된 unary에만 허용한다. browser
client-streaming/bidi는 Connect protocol 자체 기능과 별개로 `PLATFORM_LIMITED`다.
## 14. Protobuf REST Gateway 선택 시 추가 구조
한 route는 `CURATED_BFF | GRPC_GATEWAY | ENVOY_TRANSCODER` 중 하나만 소유한다.
현재 reference REST의 envelope와 `200|201`, 향후 `204/304/412` 의미를 유지하는
기본 선택은 curated BFF다.
Direct gateway는 ProtoJSON/HttpRule/status/error 자체를 새 public contract로
승인한 unary operation에서만 선택한다. Backend handoff:
- `.proto` annotation 또는 precedence가 고정된 service config의 immutable source
- descriptor/Buf image, canonical HttpRule route manifest와 digest
- pinned gateway/runtime/generator/plugin과 generated OpenAPI artifact
- ProtoJSON name/default/enum/int64/bytes/null/presence/unknown-field profile
- exact method/path/query/body/response-body/additional-binding와 path escaping
- safe status/error/header mapping과 raw `google.rpc.Status` detail redaction
- CORS/auth/CSRF, body/header/query ceiling와 rate limit
- browser abort/deadline의 upstream gRPC/application work 전파
- N/N-1 route/OpenAPI/runtime conformance와 coherent rollback
Gateway는 durable idempotency, pagination snapshot, ETag/HTTP conditional,
product envelope, authorization와 file transfer semantics를 자동 구현하지 않는다.
필요한 operation은 application service와 BFF가 계속 소유한다. generated
REST streaming은 별도 framing/terminal/cache ADR 없이는 `NOT_SELECTED`다.
## 15. Invalidation과 realtime
현재 frontend cross-tab invalidation은 같은 browser origin 안의 opaque
invalidate-only hint다. backend event delivery를 의미하지 않는다.
Backend-driven invalidation/realtime을 선택하면:
- transactional outbox 또는 동등한 durable publication
- principal/tenant authorization을 통과한 event projection
- event type/version, aggregate revision, sequence와 dedupe identity
- reconnect cursor, gap detection과 snapshot reset
- retention, replay ceiling과 slow-consumer policy
를 제공해야 한다. event payload를 authoritative resource snapshot으로 쓸지
query invalidate hint로만 쓸지 operation별 reducer contract가 필요하다.
## 16. Rate limit, deadline와 retry
- backend deadline은 frontend total deadline보다 짧거나 cancellation을 전파할 수
있어야 한다.
- disconnect/cancel 뒤 불필요한 query 작업은 중단한다.
- keyed command는 disconnect가 transaction rollback을 보장하지 않으므로
idempotency receipt로 effect를 판정한다.
- `Retry-After`는 selected status에서만 bounded delta/date 형식으로 제공한다.
- retry-safe read와 keyed command를 구분한다.
- proxy, BFF와 service retry가 겹쳐 retry amplification을 만들지 않게 한 owner만
재시도한다.
- rate limit key는 principal/tenant/operation과 abuse policy에 binding하며 raw
credential/IP를 metric label에 넣지 않는다.
## 17. Observability와 privacy
허용되는 공통 dimension:
```text
operation ID
contract/profile version
status group / safe error code
attempt bucket
duration bucket
provider/runtime health
traffic admission stage
```
금지:
- Authorization, cookie, CSRF와 idempotency key
- request/response body와 validation value
- URL query, cursor, snapshot, ETag/revision
- GraphQL variables/path/raw error/extensions
- protobuf bytes, metadata와 trailer 원문
- user ID/email/file name을 metric label이나 trace attribute로 사용
Request ID와 trace ID는 browser에 반환할 수 있지만 credential 역할을 하지 않으며
추측 가능한 database primary key를 포함하지 않는다.
필수 SLO/alert 후보:
- operation availability와 latency
- 401/403/409/412/422/429 및 5xx rate
- schema/mapper/contract mismatch
- idempotency in-progress age, collision과 unknown effect
- cursor invalid/expired/loop-equivalent server detection
- conditional hit/miss와 invalid 304
- GraphQL persisted miss/cost reject
- Connect missing/duplicate EndStream, whole-body/queue overflow와 compression mismatch
- gRPC-Web missing terminal status, frame/idle/queue overflow
- REST Gateway route/OpenAPI/runtime rewrite drift와 cancel propagation loss
## 18. 배포, compatibility와 rollback
권장 순서:
1. contract artifact와 compatibility diff를 생성한다.
2. backend가 N/N-1 fixture를 통과한 상태로 먼저 배포한다.
3. frontend operation은 traffic disabled 상태에서 staging conformance를 실행한다.
4. read-only shadow/canary 뒤 query traffic을 올린다.
5. keyed command는 idempotency/reconcile fault injection 뒤 별도 canary한다.
6. pagination, conditional, optimistic, GraphQL, Connect, gRPC-Web과 REST
Gateway는 각각 독립 gate로 승격한다.
7. provider/browser/operations evidence가 완료된 operation만 enabled한다.
Rollback은 frontend/backend/contract artifact를 coherent set으로 되돌린다.
unknown-effect command를 다른 protocol이나 새 idempotency key로 replay하지 않는다.
Backend가 old contract를 제거하는 시점은 실제 frontend support window와 cache/CDN
retention 뒤다.
## 19. Conformance와 fault-injection matrix
Backend 완료 판정에는 unit test 외에 actual staging provider evidence가 필요하다.
| 범위 | 필수 증거 |
| --- | --- |
| REST | exact path/query/body, media/status/envelope, max body, malformed/truncated JSON |
| Auth | missing/expired credential, 401/403, rotation, cross-origin preflight |
| Command | concurrent same-key replay, fingerprint mismatch, commit 뒤 response loss |
| Cursor | filter/sort binding, expiry, snapshot stability, loop/gap/duplicate 방지 |
| Conditional | 200→304, cache-missing 304 방지, representation change, 412 |
| Schema | additive/breaking/null/enum/time/number fixtures와 N/N-1 |
| GraphQL | persisted hit/miss/hash mismatch, partial, cost/depth, router rollout |
| Connect | JSON/binary unary, GET restriction, EndStream, body/message cap, compression, cancel/deadline와 CORS |
| gRPC-Web | proxy media/status/trailer, oversized frame, cancel, idle, gap/resume |
| REST Gateway | HttpRule path/query/body, ProtoJSON, OpenAPI/status/error rewrite, abort propagation과 N/N-1 |
| Operations | deadline/retry amplification, rate limit, kill switch, coherent rollback |
## 20. Backend handoff checklist
- [ ] Browser-facing API/BFF owner와 on-call이 정해졌다.
- [ ] reference REST exact endpoint/envelope/status/media fixture가 있다.
- [ ] bearer 또는 cookie+CSRF 중 하나의 실제 profile과 CORS evidence가 있다.
- [ ] stable error vocabulary와 redaction contract가 있다.
- [ ] keyed command idempotency store, TTL, receipt와 reconcile이 있다.
- [ ] CursorPage를 선택했다면 opaque cursor/snapshot contract가 있다.
- [ ] conditional을 선택했다면 ETag/304/412와 cache owner가 있다.
- [ ] optimistic을 선택했다면 identity/membership/revision/conflict 의미가 있다.
- [ ] OpenAPI/runtime schema/mapper semantic artifact와 digest가 release에 binding됐다.
- [ ] GraphQL을 선택했다면 schema/persisted manifest/router evidence가 있다.
- [ ] Connect를 선택했다면 descriptor/runtime/server/browser evidence가 있다.
- [ ] gRPC-Web을 선택했다면 descriptor/proxy/browser evidence가 있다.
- [ ] REST Gateway를 선택했다면 kind/HttpRule/ProtoJSON/OpenAPI와 runtime
conformance evidence가 있다.
- [ ] staging conformance, fault injection, canary, kill switch와 rollback drill이
통과했다.
## 21. Frontend 완료 경계
Backend 구현과 별개로 현재 frontend 상태를 다음처럼 해석한다.
| 범위 | 현재 상태 | 남은 owner |
| --- | --- | --- |
| REST path/provider/auth/deadline/bounded JSON | `COMPOSED` | actual provider conformance는 backend/operations |
| runtime schema와 mapper registry | `COMPOSED` | artifact digest/source provenance는 backend contract source + frontend/platform |
| session generation과 query identity | `COMPOSED` | account identity projection은 identity integration + frontend |
| Cursor runtime | `AVAILABLE_NOT_COMPOSED` | CursorPage backend 계약 후 frontend query binding |
| conditional validator store | `AVAILABLE_NOT_COMPOSED` | ETag/304/412 backend 계약 후 frontend HTTP/cache transaction |
| ordered optimistic layer | `AVAILABLE_NOT_COMPOSED` | product membership/revision 승인 후 frontend mutation definition |
| GraphQL adapter | `DESIGNED_NOT_IMPLEMENTED` | product/backend 선택 뒤 frontend adapter/codegen |
| Browser RPC V3 공통 계약/coordinator | `AVAILABLE_NOT_COMPOSED` | selected descriptor/generated client와 protocol transport 확정 뒤 frontend provider adapter |
| Protobuf schema/codegen | `DESIGNED_NOT_IMPLEMENTED` | authenticated backend contract source 선택 뒤 pinned frontend generation |
| Connect-Web adapter | `DESIGNED_NOT_IMPLEMENTED` | product/backend/server 선택 뒤 frontend adapter/codegen |
| gRPC-Web adapter | `DESIGNED_NOT_IMPLEMENTED` | product/backend/proxy 선택 뒤 frontend adapter/codegen |
| Protobuf REST Gateway | `NOT_SELECTED` | gateway kind와 public HTTP contract 승인 뒤 REST adapter binding |
| persisted query/offline command | `NOT_SELECTED` | 별도 product ADR와 backend durability 계약 |
따라서 “backend만 구현하면 frontend가 아무 변경 없이 모든 capability를 자동
사용한다”는 의미는 아니다. 현재 선택된 REST reference vertical의 공통 frontend
기반은 완료됐지만, backend contract가 확정되면 Cursor/conditional/optimistic의
마지막 composition과 schema/mapper 변경이 frontend에 남는다. GraphQL,
Connect/gRPC-Web과 Protobuf REST Gateway는 제품이 선택되지 않았다. 공통 Browser
RPC operation/profile registry, application port와 lifecycle coordinator는
구현했지만, wire별 generated client/decoder/provider binding은 아직 구현하지
않았다. 따라서 backend contract가 정해져도 frontend provider adapter와
composition 작업은 명시적으로 남는다.