docs(branch): 런타임 capability branch-note 6개 스캐폴딩

WI-...-028~033 에 대응하는 FE-OC-027~032 owner 노트.

- 각 노트의 제외 범위에 'use case·domain model·business rule' 을 명시해
  이 skeleton 이 port 와 adapter 계약까지임을 못박았다
- 근거 절에 '근거 등급 경계' 문단을 넣어 project-local default 와
  외부 근거를 구분했다 (수집은 FE-Q-011~014 가 소유)
- 구현 가이드 절은 비워 두고 사유를 남겼다 — 근거 raw 없이 채우면
  모든 cell 이 UNSUPPORTED_IMPL_DECISION 이 된다
- multi-protocol 노트는 '신규 port 0개' 를 제외 범위에 명시했다.
  port 가 늘어나면 그 자체가 회귀 신호다
This commit is contained in:
DongHyeonka
2026-07-28 14:36:43 +09:00
parent 7b5ff2f80b
commit d1ae257efe
6 changed files with 1501 additions and 0 deletions
@@ -0,0 +1,248 @@
---
title: branch / feature-frontend-multi-protocol-api-transport-contract
source_type: branch-note
status: raw
id: BR-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-031
kind: project-work-item
project: ca-skeleton-frontend-operational-contract
work_item: WI-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-031
inherits: [DEC-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-CAPABILITY-001@1, DEC-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-PROTOCOL-001@1, DEC-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-VALIDATION-001@1]
refines: []
overrides: []
depends_on: [WI-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-005, WI-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-006, WI-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-007]
imports: [FE-GATE-030@1, FE-OC-006@1, FE-OC-007@1, FE-OC-008@1, FLOW-FE-RESP-004@1, FLOW-FE-RESP-005@1, FLOW-FE-RESP-006@1]
delegates: [DELEG-FE-010]
accepts_delegations: []
contract_packet: 1
branch: feature-frontend-multi-protocol-api-transport-contract
parent_branch:
related_projects: [ca-skeleton-frontend, ca-skeleton]
tags: [branch, ca-skeleton-frontend, api, protocol, graphql, grpc-web]
created: 2026-07-28
target_merge:
status_label: in-progress
---
# branch: feature-frontend-multi-protocol-api-transport-contract
<!-- section-id: branch-parent -->
## 부모 (필수)
- [[raw/project-notes/ca-skeleton-frontend-operational-contract]]
형제 branch (같은 부모, 이번 확장에서 함께 생성):
- [[raw/branch-notes/feature-frontend-binary-file-io-store-contract]]
- [[raw/branch-notes/feature-frontend-cache-tier-cross-tab-invalidation-contract]]
- [[raw/branch-notes/feature-frontend-large-object-transfer-contract]]
- [[raw/branch-notes/feature-frontend-realtime-subscription-lifecycle-contract]]
- [[raw/branch-notes/feature-frontend-background-execution-worker-contract]]
<!-- section-id: branch-contract-packet -->
## 브랜치 계약 패킷
- **생성 시 프로젝트 개정**: `2`
- **패킷 스키마**: `contract_packet: 1`
- **완료 조건**: protocol별 성공/실패 정규화와 gateway fallback fixture가 통과한다
<!-- section-id: inherited-project-decisions -->
### 상속한 프로젝트 결정
| Decision Ref | Project Summary | Branch Application | Source |
|---|---|---|---|
| `DEC-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-CAPABILITY-001@1` | 신규 runtime capability 6종은 FE-REG-CAPABILITY flag로 default OFF이며 활성화는 owner·gate·runbook을 동반한다 | `CAP_FE_ALT_PROTOCOL` 을 이 branch 가 소유하고 default OFF 로 유지한다 | [[raw/project-notes/ca-skeleton-frontend-operational-contract]] |
| `DEC-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-PROTOCOL-001@1` | transport default는 REST이고 GraphQL·gRPC-Web·Connect-Web은 FE-REG-API의 protocol 필드로 opt-in하며 미지원 환경은 REST gateway로 fallback한다 | `FE-REG-API.protocol` 값에 따른 adapter 선택 규칙에 적용한다 | [[raw/project-notes/ca-skeleton-frontend-operational-contract]] |
| `DEC-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-VALIDATION-001@1` | boundary runtime validation은 Zod schema로 수행한다 | codec 디코드 결과도 예외 없이 스키마 검증을 거친다 | [[raw/project-notes/ca-skeleton-frontend-operational-contract]] |
<!-- section-id: branch-local-decisions -->
### 브랜치 지역 결정
| Decision ID | Decision | Relation | Supporting Claims | Status |
|---|---|---|---|---|
| D1 | 이 branch 는 **신규 port 를 정의하지 않는다**. GraphQL·gRPC-Web·Connect-Web adapter 는 기존 `ResourceQueryPort`/`ResourceCommandPort` 를 구현한다 | `refines DEC-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-PROTOCOL-001@1` | 근거 raw 미수집 (`FE-Q-011`) | `proposed` |
| D2 | transport status 만으로 성공을 판정하지 않는다. protocol 별 성공 판정 함수가 별도로 존재한다 | `local` | 근거 raw 미수집 (`FE-Q-011`) | `proposed` |
| D3 | GraphQL `200 OK` + `errors[]` 는 부분 성공이 아니라 `PARTIAL_RESULT_FAILURE` 로 정규화한다 | `local` | 근거 raw 미수집 (`FE-Q-011`) | `proposed` |
<!-- section-id: declared-overrides -->
### 선언한 예외
해당 없음.
<!-- GENERATED: artifact-imports:start -->
### 가져온 artifact 계약
| Artifact Ref | Owner | Producer | Schema Ref |
|---|---|---|---|
<!-- GENERATED: artifact-imports:end -->
<!-- GENERATED: project-contract-imports:start -->
## 가져온 프로젝트 계약
| Ref | Owner | 요약 | Branch 적용 |
|---|---|---|---|
| `FE-GATE-030@1` | [[raw/project-notes/ca-skeleton-frontend-operational-contract]] | protocol 별 성공/실패 정규화 fixture 가 실패하면 merge 를 MUST 차단 | 이 branch 가 owner 로서 fixture 와 report 를 산출 |
| `FE-OC-006@1` | [[raw/branch-notes/feature-api-client-response-envelope-contract]] | 모든 HTTP는 shared client를 MUST 통과하고 timeout·abort·response parsing을 page에서 구현하면 안 됨 | protocol adapter 도 shared client 위에 얹힌다 |
| `FE-OC-007@1` | [[raw/branch-notes/feature-runtime-schema-validation-contract]] | JSON envelope와 payload는 boundary에서 runtime schema를 MUST 통과 | 디코드 후 검증을 `DELEG-FE-010` 으로 위임 |
| `FE-OC-008@1` | [[raw/branch-notes/feature-frontend-error-classification-boundary-contract]] | 모든 failure는 stable frontend error kind로 MUST 정규화하고 raw body·stack을 UI에 노출하면 안 됨 | protocol 실패 3종의 정규화 대상 |
<!-- GENERATED: project-contract-imports:end -->
<!-- GENERATED: received-delegations:start -->
### 수신한 위임
없음. 이 branch 는 `DELEG-FE-010` 의 delegator 다.
<!-- GENERATED: received-delegations:end -->
<!-- GENERATED: flow:start -->
### 가져온 흐름 단계
| Stage Ref | Order | Owner | Input | Action | Output |
|---|---:|---|---|---|---|
| `FLOW-FE-RESP-004@1` | 4 | `feature-runtime-schema-validation-contract` | unvalidated JSON | envelope 공유 스키마 검증 | discriminated envelope |
| `FLOW-FE-RESP-005@1` | 5 | `feature-runtime-schema-validation-contract` | discriminated envelope | success/failure 분기 검증 | 분기 확정 envelope |
| `FLOW-FE-RESP-006@1` | 6 | `feature-runtime-schema-validation-contract` | 분기 확정 envelope | payload per-operation 스키마 검증 | 검증된 payload(deep clone) |
<!-- GENERATED: flow:end -->
<!-- section-id: branch-goal -->
## 목표
GraphQL·gRPC-Web·Connect-Web adapter 가 기존 `ResourceQueryPort`/`ResourceCommandPort` 를 구현하도록 고정하고, protocol 별 성공/실패 판정을 정규화된 failure 로 매핑한다. 이 계약이 없으면 `200 OK` + `errors[]` 응답이 success 로 반환되어 빈 화면이 정상처럼 보이고, HTTP 200 + `grpc-status: 13` 이 성공으로 처리된다.
- 이슈:
- PR:
<!-- section-id: branch-scope -->
## 범위
### 포함 범위
- `FE-REG-API``protocol`·`operationRef`·`transferMode` 소비와 adapter 선택
- GraphQL codec — persisted-document ID 기반 요청, `errors[]` 처리
- gRPC-Web·Connect-Web codec — protobuf 인코딩/디코딩, `grpc-status` ↔ 정규화 kind 매핑
- REST gateway fallback (`disabledFallback: degraded-alternative`)
- 신규 실패 3종 정규화: `PROTOCOL_STATUS_MISMATCH`·`CODEC_DECODE_FAILURE`·`PARTIAL_RESULT_FAILURE`
- `CAP_FE_ALT_PROTOCOL` capability 행 소유
### 제외 범위
- **use case, domain model, business rule** — adapter 계약까지만 정의한다
- **신규 port 정의** — 0개가 이 branch 의 설계 결론이다. 프로토콜이 application 에 새 인터페이스로 새면 `FE-D010` dependency inversion 이 무너진다
- GraphQL 스키마 설계, protobuf 서비스·메시지 정의
- 스키마 생성 파이프라인의 SSOT — `FE-Q-013`
- 디코드 이후 payload 의 runtime schema 검증 — `DELEG-FE-010` 로 [[raw/branch-notes/feature-runtime-schema-validation-contract]] 에 위임
- 스트림 protocol(`sse`·`websocket`·`poll`) — [[raw/branch-notes/feature-frontend-realtime-subscription-lifecycle-contract]] 소유
## 근거 (필수, 최소 1개+)
| Source | 정당화하는 결정 |
|---|---|
| `[[docs/superpowers/specs/2026-07-28-ca-skeleton-frontend-runtime-adapter-features-design]]` §5.2·§6.2 | 신규 port 0개 결론과 `FE-REG-API` 확장 |
| [[raw/official-docs/zod-runtime-schema-validation-official]] | 디코드 후 검증의 상위 근거 |
| [[raw/project-notes/ca-skeleton-frontend-operational-contract]] §2.1.4·§7.3·§8.2 | 응답 흐름 8단계와 실패 정규화 |
**근거 등급 경계**: `FE-D031`(protocol opt-in)의 rationale 은 `project-local default, 외부 source claim 아님` 이다. gRPC-Web·Connect 프로토콜 명세와 GraphQL over HTTP 규약을 다룬 raw 자료는 이 repo 에 없다(`FE-Q-011`). 특히 `grpc-status` ↔ 정규화 kind 매핑표는 명세 확인 없이 확정할 수 없으므로 §구현 가이드를 비워 둔다.
## TODO
- [ ] `FE-REG-API.protocol` 별 adapter 선택 규칙 확정 — 등급: `planned`
- [ ] protocol 별 성공 판정 함수 분리 — 등급: `planned`
- [ ] GraphQL `errors[]``PARTIAL_RESULT_FAILURE` 매핑 — 등급: `planned`
- [ ] `grpc-status` → 정규화 kind 매핑표 (명세 확인 후) — 등급: `planned`
- [ ] codec decode 실패 → `CODEC_DECODE_FAILURE` — 등급: `planned`
- [ ] REST gateway fallback 경로 — 등급: `planned`
- [ ] `FE-GATE-030` protocol mapping report 산출 — 등급: `planned`
## 진행 중 메모
이 branch 의 가치는 "무엇을 추가했는가" 보다 "무엇을 추가하지 않았는가" 에 있다. 신규 port 0개라는 결론이 유지되어야 backend 가 REST 에서 gRPC 로 옮겨갈 때 use case 를 다시 쓰지 않는다. 리뷰 시 port 가 늘어나 있으면 그 자체가 회귀 신호다.
## 결정 사항
- 2026-07-28: 신규 port 0개 / 이유: 프로토콜은 registry 데이터이지 타입이 아니며, port 로 새면 dependency inversion 이 무너짐 / 검토한 대안: `GraphQLPort`·`GrpcWebPort` 분리 / 근거: 설계문서 §5.2
- 2026-07-28: `200 + errors[]` 를 실패로 정규화 / 이유: 부분 데이터를 성공으로 취급하면 빈 화면이 정상처럼 보임 / 검토한 대안: 부분 성공 상태 신설 / 근거: 근거 raw 미수집, project-local 판단 (`FE-Q-011`)
<!-- section-id: decision-evidence -->
## 결정-근거 매핑
| Decision ID | Decision | 선택 조건 (언제 이 결정 / 언제 대안) | Supporting Claims | Evidence Strength | Open Risk |
|---|---|---|---|---|---|
| D1 | 신규 port 0개 | 항상. 프로토콜 고유 기능(gRPC 양방향 스트림 등)이 use case 레벨에 필요해지면 재검토 | 없음 — `FE-Q-011` | `project decision` | 추상화가 새는 프로토콜 기능이 있을 수 있음 |
| D2 | protocol 별 성공 판정 함수 분리 | 항상 | 없음 — `FE-Q-011` | `project-local default` | 판정 로직이 프로토콜마다 흩어져 중복될 수 있음 |
| D3 | `200 + errors[]``PARTIAL_RESULT_FAILURE` | 항상. 제품이 부분 데이터를 의미 있게 쓸 수 있으면 재검토 | 없음 — `FE-Q-011` | `project-local default` | 일부 필드만 실패한 응답을 통째로 버리게 됨 |
<!-- section-id: implementation -->
## 구현 가이드
> 근거 raw 자료(`FE-Q-011`, `FE-Q-013`) 수집 전까지 비워 둔다. `grpc-status` 코드별 매핑과 Connect 의 error 표현은 명세를 읽지 않고 쓸 수 없으며, 추측으로 쓰면 전부 `UNSUPPORTED_IMPL_DECISION` 이다.
<!-- section-id: edge-failure-dependency -->
## 엣지·실패·의존
- **실패·엣지 경로**
- HTTP 200 + `grpc-status` 비0 → `PROTOCOL_STATUS_MISMATCH`. protocol status 가 재시도 가능일 때만 safe/keyed 재시도
- protobuf/GraphQL 디코드 실패 → `CODEC_DECODE_FAILURE`, 본문을 telemetry 에 남기지 않음
- GraphQL `200 OK` + `errors[]``PARTIAL_RESULT_FAILURE`, error path count 만 telemetry
- capability OFF 또는 브라우저 미지원 → REST gateway 로 fallback (`degraded-alternative`)
- gateway 도 없으면 `CAPABILITY_UNSUPPORTED`
- **다른 계약 의존**
- [[raw/branch-notes/feature-api-client-response-envelope-contract]] 의 timeout·retry·idempotency 에 의존
- [[raw/branch-notes/feature-runtime-schema-validation-contract]] 의 `FLOW-FE-RESP-004`~`006` 에 의존 (`DELEG-FE-010`)
- [[raw/branch-notes/feature-frontend-error-classification-boundary-contract]] 의 총함수 정규화에 의존 — 신규 3종 kind 가 매핑되어야 함
- [[raw/branch-notes/feature-boundary-mapper-viewmodel-contract]] 의 DTO→model 매핑에 의존 — 디코드 산출물이 mapper 입력
<!-- section-id: claims-to-verify -->
## 검증해야 할 주장
| Claim | Why uncertain | How to verify | Status |
|---|---|---|---|
| `200 OK` + `errors[]` 가 success 로 반환되지 않는다 | GraphQL 클라이언트 기본 동작이 부분 성공 | negative fixture — 해당 응답 주입 후 `PARTIAL_RESULT_FAILURE` 확인 | `planned` |
| HTTP 200 + `grpc-status: 13` 이 실패로 정규화된다 | transport status 만 보는 구현이 흔함 | negative fixture — trailer 주입 후 kind 확인 | `planned` |
| protocol adapter 가 신규 port 를 만들지 않았다 | 구현 중 편의로 port 가 늘어나기 쉬움 | architecture fixture — `application/ports/` 파일 수가 늘지 않았는지 | `planned` |
| REST gateway fallback 이 실제로 도달한다 | capability OFF 경로가 테스트에서 빠지기 쉬움 | integration test — flag OFF 로 같은 operation 호출 | `planned` |
| codec 산출물이 반드시 스키마 검증을 거친다 | 디코드가 이미 타입을 보장한다고 착각하기 쉬움 | negative fixture — 스키마 위반 디코드 결과 주입 후 `SCHEMA_MISMATCH` | `planned` |
| `grpc-status` 매핑표가 명세와 일치한다 | 명세 미확인 상태 | `FE-Q-011` 수집 후 명세 대조 | `needs-confirmation` |
## 관심사 커버리지 (coverage-auditor 자동 생성 — 있을 때)
미생성.
## 마주친 문제
없음.
## 묶음 (이 branch에서 파생된 자료)
### Sub-branches (세부 작업)
아직 없음.
### 오류 기록 (이 branch 작업 중 발생)
아직 없음.
### 면접 준비 (이 작업에서 나올 수 있는 면접 질문)
아직 없음.
### 강의 (이 작업을 위해 학습한 강의)
아직 없음.
### job-posting tie-ins (이 작업에서 파생된 글감)
아직 없음.
## 관련 일일 노트
- 아직 없음
## 완료 후 정리
- PR 링크:
- 리뷰 메모:
- 머지 결과 / 배포 환경:
- **wiki 추출 대상** (verified만, `wiki/projects/`로만 추출):
- `actually-implemented` 항목: 없음
- `locally-verified` 항목: 없음
- `prod-verified` 항목: 없음
- **추출하지 않을 항목**: 현재 전 항목 `planned`