44 KiB
title, source_type, status, branch, parent_branch, related_projects, governing_docs, tags, created, target_merge, status_label, id, kind, project, work_item, inherits, refines, overrides, depends_on, contract_packet, contract_packet_sha256, imports, delegates
| title | source_type | status | branch | parent_branch | related_projects | governing_docs | tags | created | target_merge | status_label | id | kind | project | work_item | inherits | refines | overrides | depends_on | contract_packet | contract_packet_sha256 | imports | delegates | |||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| branch / feature-api-client-response-envelope-contract | branch-note | raw | feature-api-client-response-envelope-contract |
|
|
|
2026-07-18 | in-progress | BR-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-005 | project-work-item | ca-skeleton-frontend-operational-contract | WI-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-005 |
|
|
1 | e96673571270de028cc143c2c7b806cc8e9e434fbfbaeb7a9f3cac4e86f78505 |
|
|
branch: feature-api-client-response-envelope-contract
Layer:
raw/branch-notes/— 단일 브랜치의 TODO·결정·진행 기록. 머지/종료 후 verified 결과는/ingest로wiki/projects/에 추출한다.
부모 (필수)
브랜치 계약 패킷
- 생성 시 프로젝트 개정:
1 - 패킷 스키마:
contract_packet: 1 - 완료 조건: API registry와 timeout·abort·retry·idempotency deterministic tests가 통과한다
상속한 프로젝트 결정
| 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 |
브랜치 지역 결정
기존 branch-local 결정은 아래
## Decision Evidence Map / 결정-근거 매핑의 D-row가 소유하며 이 packet에서 복제하지 않는다.
| Decision ID | Decision | Relation | Supporting Claims | Status |
|---|
선언한 예외
| Override ID | Overrides | Reason | Approval | Status |
|---|
목표
이 브랜치는 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 단계)
범위
포함 범위
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 한다.
- 이 중 stage 1~3 의 owner 는 본 branch 다 — hub §2.1.4 Flow Stage Registry
- 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-APIoperation 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-007owner 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-008owner raw/branch-notes/feature-frontend-error-classification-boundary-contract. 본 client 는 그 kind 를 방출하고 retryability 만 결정한다. - Auth token lifecycle — 발급·저장·refresh·rotation·logout·revocation·IdP redirect 는
FE-OC-010owner raw/branch-notes/feature-frontend-auth-session-integration-contract + 외부 Keycloak. 본 client 는AuthSessionPort.attach호출과 401/403 정규화만. - Query cache/invalidation 배선 —
FE-OC-012owner 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-004owner raw/branch-notes/feature-frontend-env-runtime-config-contract. 본 client 는 검증된 값을 소비. - API/schema breaking change 의 migration·version bump 판정 —
FE-OC-023owner raw/branch-notes/feature-frontend-contract-compatibility-governance. - Registry snapshot·orphan token scan 강제 —
FE-OC-022owner 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-APIoperation 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 상한 → terminalRATE_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_DECISION0건).
- 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-officialZOD-VALID-C3/C4/C5+ hubFE-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 만 남긴다. 근거: hubFE-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 하지 않고 terminalRATE_LIMITED, ≤30s 면 local backoff 와 비교해 큰 값 사용. 근거: hub §7.6. - D7 mutation 자동 retry 는 stable idempotency key + active backend replay contract 있을 때만; unkeyed(
none) mutation 은 recovery 성공 후에도 replay 금지. 근거: hubFE-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-APIregistry 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.3NOT_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-APIoperationId 와 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-007owner raw/branch-notes/feature-runtime-schema-validation-contract D3 = "경계에서.safeParse()(non-throwing) default,.parse()(throw)는 정규화 catch 내부 한정". 같은 사실이 hub §2.1.4FLOW-FE-RESP-004@1Invariants("경계 검증은.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/5xxbody 가 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/superseded4개뿐이라 이 상황을 표현할 토큰이 없다 — 본 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 default2)에서 소비하되 상수 fallback 은FE-D015값.
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, 위→아래 우선):
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-API9필드에 없다 — 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 만). generic500은 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 안 하고 terminalRATE_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-APIsingle owner 로 지정. planned pathsrc/contracts/api-operations.js.
- UNSUPPORTED_IMPL_DECISION: registry 를 plain object map 으로 둘지 factory 함수로 둘지, operationId→row lookup API 모양은 hub 미규정 — 구현자 trade-off(9필드 스키마·
UPPER_SNAKE_CASEoperationId·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-allUNKNOWN_FAILURE. timeout 으로 해소된 경우에만 safe/keyed retry, 그 외 no retry. telemetry 는 redacted reason category 만(rawreason값 금지), UX 는 해소된 kind 를 상속(hub §7.4 row 5·§8.2). RATE_LIMITED(429):Retry-Afterbounded, >30s 면 terminal, delay bucket.SERVER_FAILURE(502/503/504safe/keyed max 2;500default 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-ERRORkind 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).
- raw/branch-notes/feature-frontend-env-runtime-config-contract (
검증해야 할 주장
| 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에서 파생된 자료)
가져온 흐름 단계
| 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 |
가져온 프로젝트 계약
| 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 참조로 적용 |
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):