Files
llm-wiki/raw/branch-notes/feature-api-client-response-envelope-contract.md
T

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
ca-skeleton-frontend
ca-skeleton
raw/project-notes/ca-skeleton-frontend-operational-contract.md
branch
ca-skeleton
frontend
api-design
integration
javascript
api-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
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
WI-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-004
WI-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-002
1 e96673571270de028cc143c2c7b806cc8e9e434fbfbaeb7a9f3cac4e86f78505
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
DELEG-FE-005@1

branch: feature-api-client-response-envelope-contract

Layer: raw/branch-notes/ — 단일 브랜치의 TODO·결정·진행 기록. 머지/종료 후 verified 결과는 /ingestwiki/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 contextoperationId/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).

근거 (필수, 최소 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-006FE-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-006FE-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 진입) . applicationResourceQueryPort/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/httpapplicationResourceQueryPort·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-rootClockPort·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 는 abortReasonexternal 값 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 값.
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-API 9필드에 없다 — hub §7.7 은 "backend contract 가 Idempotency-Key 를 지원한다고 registry 에 명시" 를 요구하지만 §5.3 스키마에서 이를 담을 수 있는 후보는 idempotency 하나뿐이다. 본 branch 는 idempotency=keyedbackend key 지원 선언 으로 읽고, §7.8 이 별도로 요구하는 active replay contractFE-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):

검증해야 할 주장

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 해소 로직 미구현 + abortReasonexternal 토큰 부재(§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):