# Backend API와 Server State handoff contract - 상태: 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 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 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: "" ``` 를 요구한다. 일치하지 않으면 `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 작업은 명시적으로 남는다.