402 lines
44 KiB
Markdown
402 lines
44 KiB
Markdown
---
|
|
title: branch / feature-api-client-response-envelope-contract
|
|
source_type: branch-note
|
|
status: raw
|
|
branch: feature-api-client-response-envelope-contract
|
|
parent_branch:
|
|
related_projects: [ca-skeleton-frontend, ca-skeleton]
|
|
governing_docs: [raw/project-notes/ca-skeleton-frontend-operational-contract.md]
|
|
tags: [branch, ca-skeleton, frontend, api-design, integration, javascript, api-contract]
|
|
created: 2026-07-18
|
|
target_merge:
|
|
status_label: in-progress
|
|
id: BR-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-005
|
|
kind: project-work-item
|
|
project: ca-skeleton-frontend-operational-contract
|
|
work_item: WI-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-005
|
|
inherits: [DEC-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-TIMEOUT-001@1, DEC-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-RETRY-001@1, DEC-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-IDEMPOTENCY-001@1]
|
|
refines: []
|
|
overrides: []
|
|
depends_on: [WI-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-004, WI-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-002]
|
|
contract_packet: 1
|
|
contract_packet_sha256: e96673571270de028cc143c2c7b806cc8e9e434fbfbaeb7a9f3cac4e86f78505
|
|
imports: [FE-GATE-004@1, FE-GATE-005@1, FE-GATE-007@1, FE-OC-002@1, FE-OC-007@1, FE-OC-010@1, FE-OC-022@1, FE-OC-023@1, FLOW-FE-RESP-004@1, FLOW-FE-RESP-005@1, FLOW-FE-RESP-006@1, FLOW-FE-RESP-007@1, FLOW-FE-RESP-008@1]
|
|
delegates: [DELEG-FE-005@1]
|
|
|
|
---
|
|
|
|
# branch: feature-api-client-response-envelope-contract
|
|
|
|
> Layer: `raw/branch-notes/` — 단일 브랜치의 TODO·결정·진행 기록. 머지/종료 후 verified 결과는 `/ingest`로 `wiki/projects/`에 추출한다.
|
|
|
|
<!-- section-id: branch-parent -->
|
|
## 부모 (필수)
|
|
|
|
- [[raw/project-notes/ca-skeleton-frontend-operational-contract]]
|
|
|
|
<!-- GENERATED: branch-contract:start -->
|
|
<!-- section-id: branch-contract-packet -->
|
|
## 브랜치 계약 패킷
|
|
|
|
- **생성 시 프로젝트 개정**: `1`
|
|
- **패킷 스키마**: `contract_packet: 1`
|
|
- **완료 조건**: API registry와 timeout·abort·retry·idempotency deterministic tests가 통과한다
|
|
|
|
<!-- section-id: inherited-project-decisions -->
|
|
### 상속한 프로젝트 결정
|
|
|
|
| Decision Ref | Project Summary | Branch Application | Source |
|
|
|---|---|---|---|
|
|
| `DEC-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-TIMEOUT-001@1` | request total timeout default는 10초이며 별도 browser connect timeout은 주장하지 않는다 | Work Item 완료 조건에 적용 | [[raw/project-notes/ca-skeleton-frontend-operational-contract]] |
|
|
| `DEC-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-RETRY-001@1` | retry는 initial call 이후 최대 2회, exponential backoff와 full jitter, 2초 cap을 사용한다 | Work Item 완료 조건에 적용 | [[raw/project-notes/ca-skeleton-frontend-operational-contract]] |
|
|
| `DEC-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-IDEMPOTENCY-001@1` | mutation auto-retry는 stable idempotency key와 backend replay contract가 있을 때만 허용한다 | Work Item 완료 조건에 적용 | [[raw/project-notes/ca-skeleton-frontend-operational-contract]] |
|
|
|
|
<!-- section-id: branch-local-decisions -->
|
|
### 브랜치 지역 결정
|
|
|
|
> 기존 branch-local 결정은 아래 `## Decision Evidence Map / 결정-근거 매핑`의 D-row가 소유하며 이 packet에서 복제하지 않는다.
|
|
|
|
| Decision ID | Decision | Relation | Supporting Claims | Status |
|
|
|---|---|---|---|---|
|
|
|
|
<!-- section-id: declared-overrides -->
|
|
### 선언한 예외
|
|
|
|
| Override ID | Overrides | Reason | Approval | Status |
|
|
|---|---|---|---|---|
|
|
<!-- GENERATED: branch-contract:end -->
|
|
|
|
<!-- section-id: branch-goal -->
|
|
## 목표
|
|
|
|
이 브랜치는 project hub 의 두 계약 `FE-OC-006`(모든 HTTP 는 shared client 를 통과하고 timeout·abort·response parsing 을 page 에서 구현하지 않는다)과 `FE-OC-009`(retry 는 safe/idempotent request 에 한정하며 cap·jitter·`Retry-After` 를 적용)을 **구현 착수 가능한 명세**로 내린다. 구체적으로 (1) shared HTTP client boundary 와 request context(hub §7.1~7.2), (2) response envelope 처리 순서(hub §7.3), (3) timeout·abort 분류(hub §7.4), (4) retry 알고리즘·`Retry-After`·retry decision order(hub §7.5·§7.6·§8.3), (5) idempotency 와 mutation replay(hub §7.7·§7.8), (6) `FE-REG-API` operation registry(hub §5.3)를 owner 로서 확정한다. 근거 결정은 `FE-D014`(total timeout 10s), `FE-D015`(retry ≤2 · exponential backoff + full jitter · cap 2s), `FE-D016`(mutation auto-retry 는 idempotency key + backend replay contract 있을 때만). 현재 frontend 구현 repository 가 식별되지 않았으므로(hub §0.3 `NOT_READY`) 본 노트의 모든 구현 항목은 `planned` 이며, 이 브랜치의 완료 측정치는 hub §20 의 "API operation registry + timeout/abort/retry/idempotency deterministic tests" 다.
|
|
|
|
- 이슈: (없음 — 구현 repository·이슈 트래커 미생성)
|
|
- PR: (없음 — scaffolding/spec 단계)
|
|
|
|
<!-- section-id: branch-scope -->
|
|
## 범위
|
|
|
|
### 포함 범위
|
|
|
|
Primary contract IDs `FE-OC-006` + `FE-OC-009` 가 소유하는 것만:
|
|
|
|
- **Shared HTTP client boundary** — 모든 API request 가 통과하는 application output port 구현 adapter, page/component 의 직접 `fetch`·timeout 복제·envelope 해석·retry loop·auth token 읽기 금지 규칙(hub §7.1).
|
|
- **Request context** — `operationId`/`method`/`routeId`/`timeoutMs`/`idempotency`/`attempt`/`abortReason`/`authMode` 필드 계약(hub §7.2).
|
|
- **Response envelope 처리 순서** — transport→content-type→JSON parse→envelope schema→success/failure branch→payload schema→DTO→application model mapper→application result/normalized failure 의 8단계 total order 와 success/failure envelope shape 판별(hub §7.3).
|
|
- 이 중 **stage 1~3 의 owner 는 본 branch** 다 — hub §2.1.4 Flow Stage Registry `FLOW-FE-RESP-001@1`(transport 완료 대기; timeout·abort 를 이 단계가 소유하고 이후 단계로 예외를 넘기지 않는다) · `FLOW-FE-RESP-002@1`(content-type 기대값 검사; 기대와 다르면 본문을 파싱하지 않고 실패 전환) · `FLOW-FE-RESP-003@1`(JSON parse; parse 실패는 raw body 를 버리고 실패 전환). 이 세 단계의 Invariants 를 바꾸려면 본 branch 가 revision 을 올려야 하고, 인접 단계 branch 의 pin 이 낡아 `STALE_IMPORTED_CONTRACT` 로 잡힌다. stage 4~8 은 남의 소유라 `imports` 로만 pin 한다.
|
|
- **Timeout 과 abort 분류** — total 10s timeout(`FE-D014`), navigation/user/superseded/external abort 의 분류·retry·telemetry·UX(hub §7.4).
|
|
- **Retry 정책** — 알고리즘(max 2 · exponential backoff + full jitter · base 250ms · cap 2s, `FE-D015`), retry candidate status 집합, retry decision order, `Retry-After` 처리(hub §7.5·§7.6·§8.3).
|
|
- **Idempotency 와 mutation replay** — keyed mutation 만 자동 retry(`FE-D016`), key lifecycle(memory-only default), 401 recovery 후 replay policy(hub §7.7·§7.8).
|
|
- **`FE-REG-API` operation registry** — method/path/operationId/auth/timeoutMs/idempotency/requestSchema/responseSchema/owner 필드 스키마와 registry-first 강제(hub §5.3, registry owner map §5.1).
|
|
|
|
### 제외 범위
|
|
|
|
> 의도적으로 제외 — 인접 계약이지만 다른 owner branch/외부 시스템이 소유. 여기서 detail 을 정하지 않고 owner 를 가리킨다(CLAUDE.md §15.5 R3).
|
|
|
|
- **Envelope/payload runtime schema 정의(Zod)** — `FE-OC-007` owner [[raw/branch-notes/feature-runtime-schema-validation-contract]]. 본 client 는 §7.3 step 4·6 에서 그 schema 를 *호출*만 한다.
|
|
- **Frontend error kind enum·`FE-REG-ERROR`·normalized failure shape** — `FE-OC-008` owner [[raw/branch-notes/feature-frontend-error-classification-boundary-contract]]. 본 client 는 그 kind 를 *방출*하고 retryability 만 결정한다.
|
|
- **Auth token lifecycle** — 발급·저장·refresh·rotation·logout·revocation·IdP redirect 는 `FE-OC-010` owner [[raw/branch-notes/feature-frontend-auth-session-integration-contract]] + 외부 Keycloak. 본 client 는 `AuthSessionPort.attach` 호출과 401/403 정규화만.
|
|
- **Query cache/invalidation 배선** — `FE-OC-012` owner [[raw/branch-notes/feature-server-state-caching-contract]]. 본 client 는 retry policy 를 *callback* 으로 노출할 뿐 TanStack Query client 를 import 하지 않는다.
|
|
- **Runtime config 로딩·검증** — `REQUEST_TIMEOUT_MS`/`MAX_RETRY_ATTEMPTS`/`API_BASE_URL`/`API_CONTRACT_VERSION` 의 존재·검증은 `FE-OC-004` owner [[raw/branch-notes/feature-frontend-env-runtime-config-contract]]. 본 client 는 검증된 값을 *소비*.
|
|
- **API/schema breaking change 의 migration·version bump 판정** — `FE-OC-023` owner [[raw/branch-notes/feature-frontend-contract-compatibility-governance]].
|
|
- **Registry snapshot·orphan token scan 강제** — `FE-OC-022` owner [[raw/branch-notes/feature-frontend-contract-registry-governance]]. 본 branch 는 `FE-REG-API` 스키마만 소유.
|
|
|
|
## 근거 (필수, 최소 1개+)
|
|
|
|
> 주의: 본 branch 의 primary 결정(`FE-D014`/`FE-D015`/`FE-D016`)은 hub 가 명시적으로 기록한 **project decision / conditional-default** 이며 외부 official-doc 이 근거가 아니다(hub §3.2 Evidence/rationale 열: "project-local initial limit", "retry storm 억제를 위한 project default", "duplicate write 방지 invariant"). 따라서 이들의 SSOT 는 governing hub 자체다. 아래 official-doc 은 envelope 처리 파이프라인이 *위임 호출*하는 schema 계층의 근거로만 매핑된다.
|
|
|
|
| Source | 정당화하는 결정 |
|
|
|---|---|
|
|
| [[raw/project-notes/ca-skeleton-frontend-operational-contract]] (§7.4 `FE-D014`) | D4 — total 10s timeout, 별도 connect timeout 미주장 |
|
|
| [[raw/project-notes/ca-skeleton-frontend-operational-contract]] (§7.5·§8.3 `FE-D015`) | D5 — retry ≤2 · exponential backoff + full jitter · cap 2s · retry candidate 집합 |
|
|
| [[raw/project-notes/ca-skeleton-frontend-operational-contract]] (§7.6) | D6 — `Retry-After` 파싱·30s 상한·terminal `RATE_LIMITED` |
|
|
| [[raw/project-notes/ca-skeleton-frontend-operational-contract]] (§7.7·§7.8 `FE-D016`) | D7·D8 — keyed mutation 만 retry, 401 recovery replay policy |
|
|
| [[raw/project-notes/ca-skeleton-frontend-operational-contract]] (§5.3 `FE-D018`) | D9 — `FE-REG-API` registry-first 강제 |
|
|
| [[raw/official-docs/zod-runtime-schema-validation-official]] (`ZOD-VALID-C3`,`ZOD-VALID-C4`,`ZOD-VALID-C5`) | D3 — envelope/payload 검증을 runtime schema(Zod) 계층에 위임: `.parse()` 검증 관문·`ZodError`·`.safeParse()` discriminated union. 단 schema *정의* 는 `FE-OC-007` sibling 소유 |
|
|
|
|
## TODO
|
|
|
|
측정 완료 기준(hub §20): "API operation registry + timeout/abort/retry/idempotency deterministic tests". 아래는 모두 `planned`(frontend 코드 부재).
|
|
|
|
- [ ] `FE-REG-API` operation registry 모듈 `src/contracts/api-operations.js` 정의(9개 필드 스키마 + `LIST_SAMPLE_RESOURCES`/`CREATE_SAMPLE_RESOURCE` 초기 row) — 등급: `planned`
|
|
- [ ] shared HTTP adapter(`ResourceQueryPort`/`ResourceCommandPort` 구현) `src/adapters/http/` 작성 — 등급: `planned`
|
|
- [ ] response envelope 8단계 처리 파이프라인 구현(200 이어도 invalid 면 success 반환 금지) — 등급: `planned`
|
|
- [ ] `AbortController` 기반 total 10s timeout + abort 5분류(`REQUEST_TIMEOUT`/`REQUEST_ABORTED`/external signal reason 해소 → 미해소 시 `UNKNOWN_FAILURE`) 구현 — 등급: `planned`
|
|
- [ ] retry scheduler(exponential backoff + full jitter, cap 2s, `ClockPort` + injectable random) 구현 — 등급: `planned`
|
|
- [ ] `Retry-After` 처리(30s 상한 → terminal `RATE_LIMITED`) 구현 — 등급: `planned`
|
|
- [ ] idempotency key lifecycle(memory-only) + mutation replay policy 구현 — 등급: `planned`
|
|
- [ ] `AuthSessionPort.attach` 호출 + 401/403 정규화 + bounded 1회 recovery 배선(port 정의는 sibling) — 등급: `planned`
|
|
- [ ] deterministic retry unit test(fake clock) + MSW integration taxonomy + negative fixture("POST without idempotency key receives 503") 작성 — 등급: `planned`
|
|
|
|
## 진행 중 메모
|
|
|
|
없음 — scaffolding 단계
|
|
|
|
## 결정 사항
|
|
|
|
> 아래 Decision Evidence Map(D1~D9)의 산문 요약. 모든 결정은 governing hub 또는 archived official-doc 근거를 가진다(근거 없는 결정 없음 → `UNSUPPORTED_DECISION` 0건).
|
|
|
|
- **D1** shared HTTP client 를 모든 HTTP 의 단일 boundary 로 강제. 이유: page 마다 fetch/timeout/retry 재구현 시 동일 status 가 서로 다른 UX 로 갈라짐(hub §1.3-1). 대안: per-page fetch — route 1개·외부 API 0개 throwaway prototype 에서만(hub §0.4 반대 논거). 근거: hub `FE-OC-006`·§7.1.
|
|
- **D2** response envelope 처리를 8단계 total order 로 고정하고 200 이어도 JSON/envelope/payload invalid 면 success 로 반환하지 않음. 근거: hub §7.3.
|
|
- **D3** envelope/payload 검증을 runtime schema(Zod) 계층에 위임(client 는 순서·envelope discriminator gate 소유, schema 정의는 sibling `FE-OC-007`). 근거: [[raw/official-docs/zod-runtime-schema-validation-official]] `ZOD-VALID-C3/C4/C5` + hub `FE-D007`.
|
|
- **D4** default total request timeout 10s, 별도 connect timeout 미주장(browser fetch 가 portable 하게 제공 안 함). abort 는 hub §7.4 의 5분류를 그대로 소유 — timeout 은 `REQUEST_TIMEOUT`, navigation/user/superseded 는 `REQUEST_ABORTED`(non-retryable), **external signal abort 는 signal reason 을 앞의 4분류 중 하나로 해소해 그 kind 로 귀속**하고 해소 불가 시 `UNKNOWN_FAILURE`; timeout owner 로 해소될 때만 retry 하며 telemetry 는 redacted reason category 만 남긴다. 근거: hub `FE-D014`·§7.4(5 rows)·§8.2.
|
|
- **D5** retry 는 initial 이후 max 2회, exponential backoff + full jitter, base 250ms, cap 2s; network/timeout/429/502/503/504 만 후보이고 parse/envelope/schema/auth/authz/404/409/422 와 generic 500 은 non-retryable default. 근거: hub `FE-D015`·§7.5·§8.3.
|
|
- **D6** `Retry-After` 파싱 후 유효 delay >30s 면 자동 retry 하지 않고 terminal `RATE_LIMITED`, ≤30s 면 local backoff 와 비교해 큰 값 사용. 근거: hub §7.6.
|
|
- **D7** mutation 자동 retry 는 stable idempotency key + active backend replay contract 있을 때만; unkeyed(`none`) mutation 은 recovery 성공 후에도 replay 금지. 근거: hub `FE-D016`·§7.7·§8.5.
|
|
- **D8** auth 는 consume-only: `AuthSessionPort.attach` 호출 + 401→`AUTH_REQUIRED`/403→`FORBIDDEN` 정규화 + logical request 당 bounded 1회 recovery callback + replay policy; token lifecycle 은 외부 owner. 근거: hub §7.8·`FE-D017`.
|
|
- **D9** 모든 shared-client request 는 `FE-REG-API` registry row(9필드)를 먼저 가져야 하며 call site raw config 는 violation. 근거: hub §5.3·`FE-D018`.
|
|
|
|
## 결정-근거 매핑
|
|
|
|
> 각 결정과 raw source Claim ID의 연결은 `/branch-spec`에서 작성한다.
|
|
|
|
| Decision ID | Decision | 선택 조건 (언제 이 결정 / 언제 대안) | Supporting Claims | Evidence Strength | Open Risk |
|
|
|---|---|---|---|---|---|
|
|
| D1 | Shared HTTP client 를 모든 API 호출의 단일 boundary 로 강제; page/component 는 fetch·timeout·envelope 해석·retry·auth token 읽기 금지 (`FE-OC-006`) | client-only SPA 가 공유 backend 계약을 소비하는 한 이 default 유지 / 대안(per-page fetch)은 route 1개·외부 API 0개·배포 0회 throwaway prototype 일 때만(hub §0.4) | `raw/project-notes/ca-skeleton-frontend-operational-contract.md` `FE-OC-006`·§7.1 | `project-decision` | boundary 강제는 architecture lint(`FE-OC-002`)에 의존 — 그 gate 미구현 시 우회 가능 |
|
|
| D2 | Response envelope 처리를 8단계 total order 로 고정; 200 이어도 JSON/envelope/payload invalid 면 success 반환 금지 (`FE-OC-006`) | backend 가 structured JSON envelope 를 제공(hub 가정 C)하는 한 유지 / 여러 backend 가 상이한 protocol·schema 이고 통합 adapter 불가면 재설계(가정 C 무효 조건) | `raw/project-notes/ca-skeleton-frontend-operational-contract.md` §7.3 | `project-decision` | backend envelope/OpenAPI source 미확정(`FE-Q-005`) — 실제 shape 이 §7.3 과 다를 수 있음 |
|
|
| D3 | Envelope/payload 검증을 runtime schema(Zod) 계층에 위임; client 는 처리 순서와 top-level envelope discriminator gate 만 소유 (`FE-OC-006`→`FE-OC-007` 기여) | `FE-D007`(Zod 채택)이 유효한 한 위임 / bundle budget 또는 generated schema pipeline 이 대체안을 요구하면 전환(`FE-D007` revisit) | `raw/official-docs/zod-runtime-schema-validation-official.md#ZOD-VALID-C3`, `#ZOD-VALID-C4`, `#ZOD-VALID-C5`; `...frontend-operational-contract.md` `FE-D007` | `official-vendor-doc + project-decision` | 경계별 `.parse()`(throw) vs `.safeParse()`(non-throw) 선택은 sibling 소유(`ZOD-VALID-C5` "does not prove") — 본 파이프라인은 결과 계약만 소비 |
|
|
| D4 | Default total request timeout 10s; 별도 connect timeout 미주장(browser fetch 가 portable 하게 분리 제공 안 함); override 는 registry row 의 owner 결정 필요. abort 는 §7.4 5분류 전부 소유 — timeout→`REQUEST_TIMEOUT`, navigation/user/superseded→`REQUEST_ABORTED`(no retry), external signal abort→reason 을 앞 4분류로 해소한 kind 로 귀속(해소 불가 시 `UNKNOWN_FAILURE`), timeout owner 로 해소될 때만 retry, telemetry 는 redacted reason category (`FE-D014`, `FE-OC-006`/`FE-OC-009`) | measured p95 가 10s 를 정당하게 초과하거나 streaming 이 도입되기 전까지 10s 유지 / 그 트리거 발생 시 `FE-D014` 재검토. external abort 는 외부 `AbortSignal` 을 client 에 전달하는 caller 가 존재하는 한 유지 / 그런 caller 가 없으면 dead branch | `...frontend-operational-contract.md` `FE-D014`·§7.4(5 rows)·§8.2 abort/unknown row; `FE-REG-ENV` `REQUEST_TIMEOUT_MS`; `FE-NFR-007` | `conditional-default` | measured latency baseline 없음(`FE-NFR-007` current evidence none) — 10s 는 initial limit. external abort 의 `abortReason` 토큰이 §7.2 enum 에 없음 — §3 `UNSUPPORTED_IMPL_DECISION` 참조 |
|
|
| D5 | Retry: initial 이후 max 2회, exponential backoff + full jitter(base 250ms, cap 2s); network/timeout/429/502/503/504 만 후보, parse/envelope/schema/auth/authz/404/409/422·generic 500 은 non-retryable default (`FE-D015`, `FE-OC-009`) | retry storm 억제를 위한 project default; backend SLO·rate-limit contract 가 확정되기 전까지 유지 / 확정 시 `FE-D015` 재검토, generic 500 opt-in 은 operation owner 가 safe 증명 시 | `...frontend-operational-contract.md` `FE-D015`·§7.5·§8.3·§8.2; `FE-NFR-008` | `conditional-default` | `FE-RISK-006` — retry 가 backend overload 를 증폭. mitigation: cap/jitter/`Retry-After`+telemetry; load/degradation test 로 해소 |
|
|
| D6 | `Retry-After` 파싱(delta-seconds 또는 HTTP-date); invalid/negative→local backoff, 유효 >30s→terminal `RATE_LIMITED`(자동 retry 안 함), ≤30s→local backoff 와 max (`FE-OC-009`) | 30s 상한이 project default; backend rate-limit contract 없이는 긴 대기를 자동 소비하지 않음 / contract 확정 시 `FE-D015` 와 함께 재검토 | `...frontend-operational-contract.md` §7.6; §8.2 `429` row | `project-decision` | 30s 임계는 hub 가 준 상수지만 근거 measured 아님 — retry decision order 와 함께 통합 테스트 필요 |
|
|
| D7 | Mutation 자동 retry 는 stable idempotency key + active backend replay contract 있을 때만; `none`(unkeyed) mutation 은 recovery 성공 후에도 replay 금지, 명시적 user 재시도 요구 (`FE-D016`, `FE-OC-009`/`FE-OC-023`) | mutation 이 schema 로 naturally idempotent 임이 증명되기 전까지 keyed-only 유지 / 증명 시 `FE-D016` 재검토 | `...frontend-operational-contract.md` `FE-D016`·§7.7·§7.8 replay·§8.5 fixture | `accepted-documented-only` | backend `Idempotency-Key`·replay contract 미확정(`FE-Q-005`) — 없으면 mutation retry 는 영구 off |
|
|
| D8 | Auth 는 consume-only: request 전 `AuthSessionPort.attach` 호출, 401→`AUTH_REQUIRED`/403→`FORBIDDEN` 정규화, logical request 당 bounded 1회 recovery callback, safe≤1·keyed≤1·none=0 replay; token lifecycle 미소유 (`FE-OC-006`→`FE-OC-010` 기여) | auth lifecycle 이 외부 owner 인 한 consume-only(`FE-D017`) / skeleton 이 독립 auth product 로 scope 변경 시 `FE-D017` 재검토 | `...frontend-operational-contract.md` §7.8 state machine·`FE-D017` | `project-decision (delegated boundary)` | auth adapter owner·interface 미정(`FE-Q-006`); `FE-RISK-005`(guard 를 security control 로 오해) — backend authz 가 최종 판단 |
|
|
| D9 | 모든 shared-client request 는 `FE-REG-API` row(method/path/operationId/auth/timeoutMs/idempotency/requestSchema/responseSchema/owner)를 먼저 가져야 하며 call site raw config 는 violation (`FE-REG-API`, `FE-D018`) | 8-registry governance(`FE-D018`)가 유효한 한 registry-first / code generation SSOT 채택 시 `FE-D018` 재검토 | `...frontend-operational-contract.md` §5.3·§5.1·`FE-D018` | `project-decision` | registry snapshot·orphan token scan 강제는 `FE-OC-022` sibling 소유 — 본 branch 는 스키마만 |
|
|
|
|
## 구현 가이드
|
|
|
|
> 전체 `planned` — frontend 구현 repository 가 아직 없다(hub §0.3 `NOT_READY`). 아래 경로는 hub §4.6 Planned directory blueprint + §5.1 registry owner map 에서 온 grounded anchor 이며, 실제 repository 생성 시 확정된다(`FE-D009` 변경 절차). 클래스·함수·파일명 중 hub 가 규정하지 않은 것은 `UNSUPPORTED_IMPL_DECISION` 으로 표기한다.
|
|
|
|
### 1. Shared HTTP client boundary 와 request context
|
|
|
|
> **Trace**: D1(`FE-OC-006`·§7.1) + D2(§7.3 진입) . `application` 이 `ResourceQueryPort`/`ResourceCommandPort` 를 소유(hub §4.4)하고 `adapters/http` 가 구현(hub §4.2). 배선은 `bootstrap/composition-root.js` 하나(hub §4.5·`FE-D011`).
|
|
>
|
|
> - **UNSUPPORTED_IMPL_DECISION**: adapter 파일명(`src/adapters/http/http-client.js`)·클래스명(`SharedHttpClient`)·request context 객체 필드 순서는 hub 가 규정하지 않음 — blueprint 디렉토리(`src/adapters/http/`)만 grounded, 파일/식별자 명명은 구현자 임의 trade-off(가독성 우선, `FE-REG-API` operationId 와 1:1 연결 유지).
|
|
|
|
| 항목 | 명세 | 근거 |
|
|
|---|---|---|
|
|
| 진입점 | `adapters/http` 가 `application` 의 `ResourceQueryPort`·`ResourceCommandPort` 를 구현, presentation 은 facade 만 호출 | hub §4.2·§4.4 |
|
|
| page 금지 목록 | 직접 `fetch` / `AbortController` timeout 복제 / status→user copy 변환 / raw body log / page-local retry / storage 에서 auth token 읽기 | hub §7.1 |
|
|
| request context 필드 | `operationId`,`method`,`routeId`,`timeoutMs`,`idempotency`,`attempt`(initial=0),`abortReason?`,`authMode` | hub §7.2 |
|
|
| 주입 | `composition-root` 가 `ClockPort`·injectable random·`AuthSessionPort`·validated config 를 client 에 주입 | hub §4.5·§7.5 |
|
|
|
|
### 2. Response envelope 처리 파이프라인
|
|
|
|
> **Trace**: D2(§7.3) + D3(§7.3 step 4·6 → Zod 위임, `ZOD-VALID-C3/C4/C5`). success/failure envelope shape 는 hub §7.3, 위반 시 kind 는 §8.2(error 계층 소유).
|
|
>
|
|
> - **해소됨(2026-07-21) — 근거 있는 결정**: envelope discriminator 의 throw/non-throw 는 owner sibling 이 이미 정했다. `FE-OC-007` owner [[raw/branch-notes/feature-runtime-schema-validation-contract]] D3 = "경계에서 `.safeParse()`(non-throwing) default, `.parse()`(throw)는 정규화 catch 내부 한정". 같은 사실이 hub §2.1.4 `FLOW-FE-RESP-004@1` Invariants("경계 검증은 `.safeParse()` non-throwing — throw 를 상위로 누출하지 않는다")로 고정되어 있고, 본 문서는 그 stage 를 `imports` 로 pin 한다. 본 파이프라인의 요구("invalid→normalized failure")는 그 결정과 정합이다.
|
|
|
|
처리 순서(총 8단계, 각 실패 지점의 normalized kind 는 §8.2 owner 소유):
|
|
|
|
| # | 단계 | 실패 시 kind(§8.2, 위임) |
|
|
|---|---|---|
|
|
| 1 | HTTP transport 완료 | `NETWORK_UNREACHABLE`/`REQUEST_TIMEOUT`/`REQUEST_ABORTED` |
|
|
| 2 | content-type 기대 확인 | `CONTENT_TYPE_MISMATCH` |
|
|
| 3 | JSON parse | `MALFORMED_JSON` |
|
|
| 4 | envelope schema 검증(Zod 위임) | `ENVELOPE_MISMATCH` |
|
|
| 5 | success/failure branch 판별 | HTTP status 기반 §8.2 row |
|
|
| 6 | payload schema 검증(Zod 위임) | `SCHEMA_MISMATCH` |
|
|
| 7 | DTO→application model mapper (`FLOW-FE-RESP-007@1`) | mapper 실패 시 catch-all `UNKNOWN_FAILURE` |
|
|
| 8 | application result 또는 normalized failure 반환 | — |
|
|
|
|
- 불변식: `200` 이어도 3~6 중 하나가 invalid 면 success 로 반환하지 않는다. `4xx/5xx` body 가 invalid 면 status 기반 safe fallback error 를 만들고 raw body 는 폐기(hub §7.3).
|
|
|
|
### 3. Timeout 과 abort 분류
|
|
|
|
> **Trace**: D4(`FE-D014`·§7.4 5 rows). total 10s(`REQUEST_TIMEOUT_MS` 소비, env branch 검증). abort 분류·retry·UX 는 §7.4 표 전체(external signal abort 포함), 미해소 catch-all 은 §8.2 마지막 문단.
|
|
>
|
|
> - **UNSUPPORTED_IMPL_DECISION**: `AbortController` 하나로 timeout·navigation·user·superseded abort 를 모두 표현할지, timeout 용 별도 controller 를 둘지는 hub 미규정 — 구현자 trade-off(단일 controller + `abortReason` 태깅 권장). connect/read timeout 분리는 **금지**(browser fetch 가 portable 제공 안 함, §7.4 마지막 문단).
|
|
> - **UNSUPPORTED_IMPL_DECISION**: hub §7.4 는 external signal abort 를 5번째 행으로 요구하지만 §7.2 `abortReason` 허용값은 `navigation`/`user`/`timeout`/`superseded` 4개뿐이라 이 상황을 표현할 토큰이 없다 — 본 branch 는 `abortReason` 에 `external` 값 1개를 추가하고, 해소된 원인은 별도 필드가 아니라 기존 4값으로 *재분류*해 기록한다(구현자 trade-off: enum 1값 확장이 telemetry·registry 계약 변경 폭이 가장 작다. 대안인 별도 `abortSource` 필드는 §7.2 스키마를 넓히고 §8.1 normalized failure 와 정보가 이중화된다). `external` 추가는 §7.2 스키마 변경이므로 실제 도입 시 hub §3.3 decision change protocol 로 승격한다.
|
|
|
|
| 상황 | kind | retry | telemetry | UX |
|
|
|---|---|---|---|---|
|
|
| 10s total timeout | `REQUEST_TIMEOUT` | safe/keyed 만 | terminal 시 1 event, elapsed bucket | retry action |
|
|
| navigation cancel | `REQUEST_ABORTED` | no | debug counter, error event 금지 | stale surface 제거 |
|
|
| user cancel | `REQUEST_ABORTED` | no | optional interaction event | neutral canceled |
|
|
| superseded query | `REQUEST_ABORTED` | no | none | latest 유지 |
|
|
| external signal abort (caller 가 넘긴 외부 `AbortSignal`) | reason 해소 결과에 귀속 — timeout owner 면 `REQUEST_TIMEOUT`, navigation/user/superseded 로 해소되면 `REQUEST_ABORTED`, 해소 불가면 `UNKNOWN_FAILURE` | no — 단 timeout owner 로 해소된 경우에만 timeout 정책(safe/keyed max 2) 적용 | redacted reason category 만(raw signal `reason` 값·message·stack 금지), 해소된 kind 의 telemetry rule 을 그대로 상속 | context-specific — 해소된 kind 의 UX 를 상속(timeout→`retry`, abort→`none`, 미해소→generic reference) |
|
|
|
|
### 4. Retry 알고리즘 · decision order · `Retry-After`
|
|
|
|
> **Trace**: D5(`FE-D015`·§7.5·§8.3) + D6(§7.6). `ClockPort` + injectable random source 로 결정론 테스트 가능(§7.5 normative).
|
|
>
|
|
> - **UNSUPPORTED_IMPL_DECISION**: retry scheduler 파일/클래스명(`src/adapters/http/retry-policy.js`, `RetryScheduler`)은 hub 미규정 — blueprint 디렉토리만 grounded, 명명은 구현자 trade-off. `MAX_RETRY_ATTEMPTS` 는 env registry(§5.4 default `2`)에서 소비하되 상수 fallback 은 `FE-D015` 값.
|
|
|
|
```text
|
|
maxRetries = 2 # initial 제외, hub §7.5 / FE-D015
|
|
baseDelayMs = 250 # hub §7.5
|
|
maxDelayMs = 2000 # cap, hub §7.5 / FE-D015
|
|
delay(i) = min(maxDelayMs, baseDelayMs * 2^retryIndex) * random(0,1) # full jitter
|
|
```
|
|
|
|
Retry decision order(hub §8.3, 위→아래 우선):
|
|
|
|
```text
|
|
if aborted (navigation/user/superseded) -> no retry
|
|
else if parse/envelope/schema/auth/authz/404/409/422 -> no retry
|
|
else if method is safe -> apply status/network policy
|
|
else if idempotency == keyed AND backend replay active -> apply status/network policy
|
|
else -> no retry
|
|
```
|
|
|
|
- external signal abort 는 위 순서의 **첫 줄 이전에 reason 해소 단계**가 선행한다: 해소 결과가 navigation/user/superseded 면 1번째 줄에 걸려 no retry, timeout 이면 3~4번째 줄의 status/network policy 로 내려가고, 해소 불가면 `UNKNOWN_FAILURE`(non-retryable)로 종결한다. 해소 단계 자체는 hub §7.4 row 5("reason에 따라" / "no unless timeout owner")에서 도출되며 §8.3 의 문장 순서를 바꾸지 않는다(§3 표 참조).
|
|
- **UNSUPPORTED_IMPL_DECISION**: 위 순서 4번째 줄의 조건 "backend replay contract active" 를 표현하는 필드가 `FE-REG-API` 9필드에 없다 — hub §7.7 은 "backend contract 가 `Idempotency-Key` 를 지원한다고 registry 에 명시" 를 요구하지만 §5.3 스키마에서 이를 담을 수 있는 후보는 `idempotency` 하나뿐이다. 본 branch 는 `idempotency=keyed` 를 *backend key 지원 선언* 으로 읽고, §7.8 이 별도로 요구하는 *active replay contract* 는 `FE-Q-005` 해소 전까지 keyed 와 동일시한다(구현자 trade-off: 미검증 backend 정보로 registry 스키마를 늘리지 않는 대신, key 는 수용하지만 replay 결과를 반환하지 않는 backend 를 과신할 위험을 진다 — mutation auto-retry 자체가 `FE-Q-005` 해소 전까지 off 이므로 safe-path 작업은 막히지 않는다). backend 가 key 수용과 replay 반환을 구분하는 것으로 확인되면 10번째 필드(예: `replayContract`)를 hub §5.10 registry change protocol 로 추가한 뒤 이 분기를 두 조건으로 분리한다.
|
|
- retry candidate status: network failure·timeout·`429`·`502`·`503`·`504`(safe/keyed 만). generic `500` 은 default off, operation owner 가 safe 증명 시 opt-in(hub §7.5).
|
|
- backend `error.retryable=true` 는 hint 일 뿐 unsafe mutation 자동 retry 의 충분조건 아님(hub §8.3).
|
|
- `Retry-After`: parse → invalid/negative 면 local backoff → 유효 >30s 면 automatic retry 안 하고 terminal `RATE_LIMITED` → ≤30s 면 local backoff 와 max → abort 시 wait 취소. raw value 는 telemetry 금지, normalized delay bucket 만(hub §7.6).
|
|
- unmount/superseded 시 남은 timer 와 request 취소(hub §7.5 마지막 bullet).
|
|
|
|
### 5. Idempotency 와 401 recovery replay
|
|
|
|
> **Trace**: D7(`FE-D016`·§7.7) + D8(§7.8). key lifecycle 은 auth token lifecycle 과 분리, memory-only default(§7.7).
|
|
>
|
|
> - **UNSUPPORTED_IMPL_DECISION**: idempotency key 생성 방식(UUID v4 vs client-side hash)·single-flight dedup key 도출은 hub 미규정 — 구현자 trade-off(logical action 당 1 key·retry 간 재사용·telemetry/URL/message 노출 금지 제약만 grounded, §7.7). key persistence 가 필요해지면 본 branch 가 아니라 storage registry(`FE-REG-STORAGE`)에 TTL/classification/migration 추가 후.
|
|
|
|
401 recovery state machine(hub §7.8, client 소비 부분만):
|
|
|
|
| 현재 상태 | 이벤트 | 다음 상태 | client 동작 |
|
|
|---|---|---|---|
|
|
| `authenticated` | first `401` | `recovery-pending` | 외부 owner bounded recovery callback 1회 |
|
|
| `recovery-pending` | session restored | `authenticated` | replay policy 적용 |
|
|
| `recovery-pending` | no session | `unauthenticated` | terminal `AUTH_REQUIRED` |
|
|
| `recovery-pending` | adapter throw/reject/invalid | `integration-failed` | terminal `AUTH_INTEGRATION_FAILURE` |
|
|
| any | same request 2nd `401` | `unauthenticated` | 추가 recovery 없이 terminal `AUTH_REQUIRED` |
|
|
|
|
Replay policy(recovery 성공 후): `safe`=최대 1회 replay / `keyed`=같은 key + active replay contract 시 최대 1회 / `none`=replay 금지, 명시적 user 재시도 요구(hub §7.8·§8.5 fixture).
|
|
|
|
### 6. `FE-REG-API` operation registry
|
|
|
|
> **Trace**: D9(§5.3·`FE-D018`). registry owner map §5.1 이 본 branch 를 `FE-REG-API` single owner 로 지정. planned path `src/contracts/api-operations.js`.
|
|
>
|
|
> - **UNSUPPORTED_IMPL_DECISION**: registry 를 plain object map 으로 둘지 factory 함수로 둘지, operationId→row lookup API 모양은 hub 미규정 — 구현자 trade-off(9필드 스키마·`UPPER_SNAKE_CASE` operationId·call-site raw config 금지 제약만 grounded).
|
|
|
|
| Field | Required | Rule(hub §5.3) |
|
|
|---|---|---|
|
|
| `method` | yes | uppercase HTTP method |
|
|
| `path` | yes | path template, query value·host 미포함 |
|
|
| `operationId` | yes | stable `UPPER_SNAKE_CASE`, telemetry·test·owner key |
|
|
| `auth` | yes | `none` 또는 `external-session` |
|
|
| `timeoutMs` | yes | default `10000`, override 는 decision change |
|
|
| `idempotency` | yes | `safe`/`keyed`/`none` |
|
|
| `requestSchema` | yes | body 없으면 explicit `none`, params/search 도 검증 |
|
|
| `responseSchema` | yes | success envelope payload schema reference |
|
|
| `owner` | yes | owning feature/branch slug |
|
|
|
|
초기 planned row(hub §5.3): `LIST_SAMPLE_RESOURCES`(GET `/api/sample/resources`, safe), `CREATE_SAMPLE_RESOURCE`(POST `/api/sample/resources`, keyed) — owner 는 [[raw/branch-notes/feature-sample-feature-slice-contract-fixture]](`FE-OC-024`; 본 branch 는 스키마 소유, sample row 는 fixture branch 가 채움).
|
|
|
|
- `idempotency=keyed` 는 위 9필드 안에서 hub §7.7 의 "backend 가 `Idempotency-Key` 를 지원한다고 registry 에 명시" 요구를 담는 유일한 필드이며, §7.8 의 *active backend replay contract* 조건도 `FE-Q-005` 해소 전까지 여기에 겹쳐 읽는다 — 필드 분리 조건과 trade-off 는 §4 의 `UNSUPPORTED_IMPL_DECISION` 참조.
|
|
|
|
## 엣지·실패·의존
|
|
|
|
- **실패·엣지 경로** (hub §8.2 matrix 중 본 client 가 방출/분기하는 것):
|
|
- `NETWORK_UNREACHABLE`(DNS/offline/CORS-like): safe/keyed max 2 retry, 가능 시 cached safe data, raw URL telemetry 금지.
|
|
- `REQUEST_TIMEOUT`(10s total): safe/keyed max 2, stale data 유지 가능, elapsed bucket.
|
|
- `REQUEST_ABORTED`(navigation/user/superseded): no retry, error toast/event 금지, latest 유지.
|
|
- external signal abort(caller 가 넘긴 외부 `AbortSignal`): reason 을 해소해 `REQUEST_TIMEOUT`(timeout owner) 또는 `REQUEST_ABORTED`(navigation/user/superseded)로 귀속, 해소 불가 시 catch-all `UNKNOWN_FAILURE`. timeout 으로 해소된 경우에만 safe/keyed retry, 그 외 no retry. telemetry 는 redacted reason category 만(raw `reason` 값 금지), UX 는 해소된 kind 를 상속(hub §7.4 row 5·§8.2).
|
|
- `RATE_LIMITED`(`429`): `Retry-After` bounded, >30s 면 terminal, delay bucket.
|
|
- `SERVER_FAILURE`(`502/503/504` safe/keyed max 2; `500` default off; 기타 5xx default off): stale safe data fallback.
|
|
- Non-retryable: `MALFORMED_JSON`·`ENVELOPE_MISMATCH`·`SCHEMA_MISMATCH`·`AUTH_REQUIRED`·`FORBIDDEN`·`NOT_FOUND`·`CONFLICT`·`VALIDATION_REJECTED` — retry 하지 않고 normalized failure 반환.
|
|
- **Total-function normalization**: response/adapter/browser exception 이 named branch 와 안 맞거나 mapper 자체가 실패하면 최종 catch-all 이 raw value 폐기 후 `UNKNOWN_FAILURE` 반환 — normalized failure 를 못 만든 채 throw 를 presentation 으로 통과시키는 경로 금지(hub §8.2 마지막 문단). enum·shape 는 `FE-OC-008` 소유이나 "leak 금지" 불변식은 본 client 책임.
|
|
- 동시성: retry 중 component unmount / query superseded 시 남은 timer·request 취소(hub §7.5).
|
|
- **다른 계약 의존** (대상 branch + consume 하는 contract; hub §20 Dependency·§4.3 dependency matrix):
|
|
- [[raw/branch-notes/feature-frontend-env-runtime-config-contract]] (`FE-OC-004`, 본 노트 D4·D5) — 검증된 `REQUEST_TIMEOUT_MS`·`MAX_RETRY_ATTEMPTS`·`API_BASE_URL`·`API_CONTRACT_VERSION` 소비. 그 config 검증 계약이 바뀌면 client boot 입력 변경.
|
|
- [[raw/branch-notes/feature-frontend-clean-architecture-layering-contract]] (`FE-OC-002`, 본 노트 D1·D5) — `ResourceQueryPort`/`ResourceCommandPort` + `ClockPort` + injectable random source 의 application-owned port 정의와 composition-root 주입(port ownership 결정은 hub 소유, §4.4 port matrix·§4.5 boot order). `ClockPort` 와 random source 는 본 branch 의 완료 측정치인 deterministic retry test 의 전제이며, injectable random 은 §4.4 port matrix 에 행이 없어 주입 형태(별도 port vs adapter 생성자 인자)는 layering branch 가 확정한다. port shape 변경 시 adapter 시그니처 영향.
|
|
- [[raw/branch-notes/feature-runtime-schema-validation-contract]] (`FE-OC-007`, 본 노트 D3) — envelope/payload Zod schema; §7.3 step 4·6 이 호출. schema 계약 변경 시 파이프라인 검증 지점 영향.
|
|
- [[raw/branch-notes/feature-frontend-error-classification-boundary-contract]] (`FE-OC-008`, 본 노트 D2·§8.2) — `FE-REG-ERROR` kind enum·normalized failure shape; client 가 emit·retryability 결정.
|
|
- [[raw/branch-notes/feature-frontend-auth-session-integration-contract]] (`FE-OC-010`, 본 노트 D8) — `AuthSessionPort.attach`·bounded recovery(auth lifecycle 은 외부 owner). port/lifecycle 변경 시 §7.8 소비 영향.
|
|
- [[raw/branch-notes/feature-server-state-caching-contract]] (`FE-OC-012`, 본 노트 D5) — `QueryCachePort`/TanStack adapter 가 client retry policy 를 callback 으로 소비. page-local retry 숫자 금지.
|
|
- [[raw/branch-notes/feature-frontend-contract-compatibility-governance]] (`FE-OC-023`, 본 노트 D7) — `apiContractVersion`·API schema breaking change migration/version bump 판정.
|
|
- [[raw/branch-notes/feature-frontend-operational-runbook-contract]] (`FE-OC-025`, `FE-RB-003`) — backend API degradation 시 technical escalation 이 본 branch → backend operation owner 경로(hub §16.3).
|
|
|
|
## 검증해야 할 주장
|
|
|
|
| Claim | Why uncertain | How to verify | Status |
|
|
|---|---|---|---|
|
|
| retry 는 initial 이후 정확히 ≤2회, backoff sequence 가 deterministic | 코드·fake clock 없음 | deterministic retry unit test(`ClockPort`+injectable random) — `FE-GATE-005`, `FE-NFR-008` | `needs-confirmation` |
|
|
| POST(`idempotency=none`) 가 `503` 을 받아도 자동 retry 하지 않음 | 정책은 문서, 코드 미검증 | negative fixture "POST without idempotency key receives 503"(hub §15.2) + MSW integration — `FE-GATE-007` | `needs-confirmation` |
|
|
| `200` + malformed JSON/invalid envelope 가 success 로 새지 않고 normalized failure 반환 | envelope 파이프라인 미구현 | runtime-schema/integration fixture(success envelope without `data`) — `FE-GATE-004`/`007` | `needs-confirmation` |
|
|
| total timeout 이 10s 에 발화하고 `REQUEST_TIMEOUT` 으로 분류 | `AbortController` timeout 배선 미구현 | fake-clock unit + MSW delay integration — `FE-NFR-007` | `needs-confirmation` |
|
|
| `Retry-After` >30s → 자동 retry 없이 terminal `RATE_LIMITED` | 30s 상한 로직 미구현 | integration fixture(`429` + `Retry-After: 60`) | `needs-confirmation` |
|
|
| navigation/superseded abort 가 in-flight timer·request 취소 + error event 미방출 | 취소 경로 미구현 | component/integration abort fixture | `needs-confirmation` |
|
|
| 외부 `AbortSignal` 로 끊긴 request 가 reason 해소 결과의 kind 로 귀속되고(미해소 시 `UNKNOWN_FAILURE`) timeout 으로 해소된 경우에만 retry, telemetry 에 raw reason 미노출 | reason 해소 로직 미구현 + `abortReason` 에 `external` 토큰 부재(§3 `UNSUPPORTED_IMPL_DECISION`) | external signal abort fixture 3종(timeout owner / navigation reason / 미해소 임의 reason) + telemetry redaction assertion — `FE-GATE-007` | `needs-confirmation` |
|
|
| first `401` 이 bounded 1회 recovery callback, second `401` 은 terminal `AUTH_REQUIRED` | auth adapter·state machine 미구현 | MSW auth-recovery taxonomy integration — `FE-GATE-007` | `needs-confirmation` |
|
|
| unkeyed mutation 은 recovery 성공 후에도 replay 안 함 | replay policy 미구현 | integration fixture(hub §8.5 "recovery succeeds for unkeyed mutation") | `needs-confirmation` |
|
|
| normalization 이 total — 미매핑 exception 이 `UNKNOWN_FAILURE` 로 귀결, throw 가 presentation 으로 새지 않음 | catch-all 경로 미구현 | integration fixture(thrown non-`Error`/mapper exception) | `needs-confirmation` |
|
|
| backend 가 `Idempotency-Key` + replay contract 를 실제 제공 | backend envelope/OpenAPI source 미확정(`FE-Q-005`) | backend owner 확인 + captured fixture 대조 | `needs-confirmation` |
|
|
|
|
## 관심사 커버리지 (coverage-auditor 자동 생성 — 있을 때)
|
|
|
|
> `/coverage`가 채우는 생성물이며 손으로 유지하지 않는다.
|
|
|
|
| 관심사 | 상태 | owner | 심각도 | 근거 |
|
|
|---|---|---|---|---|
|
|
| TODO — `/coverage` 실행 전 | missing | (없음) | 미평가 | TODO |
|
|
|
|
## 마주친 문제
|
|
|
|
없음 — scaffolding 단계
|
|
|
|
## 묶음 (이 branch에서 파생된 자료)
|
|
|
|
<!-- GENERATED: flow:start -->
|
|
### 가져온 흐름 단계
|
|
|
|
| Stage Ref | Order | Owner | Input | Action | Output |
|
|
|---|---:|---|---|---|---|
|
|
| `FLOW-FE-RESP-004@1` | 4 | [[raw/branch-notes/feature-runtime-schema-validation-contract]] | unvalidated JSON | envelope 공유 스키마 검증 | discriminated envelope |
|
|
| `FLOW-FE-RESP-005@1` | 5 | [[raw/branch-notes/feature-runtime-schema-validation-contract]] | discriminated envelope | success/failure 분기 검증 | 분기 확정 envelope |
|
|
| `FLOW-FE-RESP-006@1` | 6 | [[raw/branch-notes/feature-runtime-schema-validation-contract]] | 분기 확정 envelope | payload per-operation 스키마 검증 | 검증된 payload(deep clone) |
|
|
| `FLOW-FE-RESP-007@1` | 7 | [[raw/branch-notes/feature-boundary-mapper-viewmodel-contract]] | 검증된 payload | DTO → application model 매핑 | application model |
|
|
| `FLOW-FE-RESP-008@1` | 8 | [[raw/branch-notes/feature-frontend-error-classification-boundary-contract]] | application model 또는 실패 신호 | 정규화된 결과 반환 | application result 또는 normalized failure |
|
|
<!-- GENERATED: flow:end -->
|
|
|
|
<!-- GENERATED: project-contract-imports:start -->
|
|
## 가져온 프로젝트 계약
|
|
|
|
| Ref | Owner | 요약 | Branch 적용 |
|
|
|---|---|---|---|
|
|
| `FE-GATE-004@1` | [[raw/branch-notes/feature-runtime-schema-validation-contract]] | invalid fixture 가 예상 kind 로 거부되지 않으면 merge 를 MUST 차단 | import 참조로 적용 |
|
|
| `FE-GATE-005@1` | [[raw/branch-notes/feature-frontend-test-taxonomy-contract]] | unit 레벨이 실패하면 merge 를 MUST 차단하고 warning 으로 낮추면 안 됨 | import 참조로 적용 |
|
|
| `FE-GATE-007@1` | [[raw/branch-notes/feature-frontend-test-taxonomy-contract]] | MSW 기반 integration 매트릭스가 미충족이면 merge 를 MUST 차단 | import 참조로 적용 |
|
|
| `FE-OC-002@1` | [[raw/branch-notes/feature-frontend-clean-architecture-layering-contract]] | `domain <- application <- presentation` 의존 방향과 application-owned output port를 MUST 지킴 | import 참조로 적용 |
|
|
| `FE-OC-007@1` | [[raw/branch-notes/feature-runtime-schema-validation-contract]] | JSON envelope와 payload는 boundary에서 runtime schema를 MUST 통과 | import 참조로 적용 |
|
|
| `FE-OC-010@1` | [[raw/branch-notes/feature-frontend-auth-session-integration-contract]] | skeleton은 session state를 소비하되 token lifecycle을 MUST 소유하지 않음 | import 참조로 적용 |
|
|
| `FE-OC-022@1` | [[raw/branch-notes/feature-frontend-contract-registry-governance]] | 8개 registry는 single primary owner와 compatibility impact를 MUST 기록 | import 참조로 적용 |
|
|
| `FE-OC-023@1` | [[raw/branch-notes/feature-frontend-contract-compatibility-governance]] | API/config/storage/release schema의 breaking change는 migration 또는 version bump 없이 배포하면 안 됨 | import 참조로 적용 |
|
|
<!-- GENERATED: project-contract-imports:end -->
|
|
|
|
### Sub-branches (세부 작업)
|
|
|
|
없음 — scaffolding 단계
|
|
|
|
### 오류 기록 (이 branch 작업 중 발생)
|
|
|
|
없음 — scaffolding 단계
|
|
|
|
### 면접 준비 (이 작업에서 나올 수 있는 면접 질문)
|
|
|
|
없음 — scaffolding 단계
|
|
|
|
### 강의 (이 작업을 위해 학습한 강의)
|
|
|
|
없음 — scaffolding 단계
|
|
|
|
### job-posting tie-ins (이 작업에서 파생된 글감)
|
|
|
|
없음 — scaffolding 단계
|
|
|
|
## 관련 일일 노트
|
|
|
|
없음 — scaffolding 단계
|
|
|
|
## 완료 후 정리
|
|
|
|
- PR 링크:
|
|
- 리뷰 메모:
|
|
- 머지 결과 / 배포 환경:
|
|
- **wiki 추출 대상** (verified만, `wiki/projects/`로만 추출):
|
|
- `actually-implemented` 항목:
|
|
- `locally-verified` 항목:
|
|
- `prod-verified` 항목:
|
|
- **추출하지 않을 항목** (planned / documented-only / abandoned):
|