Files
llm-wiki/raw/branch-notes/feature-routing-navigation-guard-contract.md

314 lines
30 KiB
Markdown

---
title: branch / feature-routing-navigation-guard-contract
source_type: branch-note
status: raw
branch: feature-routing-navigation-guard-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, application, auth, react, integration]
created: 2026-07-18
target_merge:
status_label: in-progress
id: BR-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-009
kind: project-work-item
project: ca-skeleton-frontend-operational-contract
work_item: WI-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-009
inherits: [DEC-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-ROUTING-001@1, DEC-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-AUTH-BOUNDARY-001@1]
refines: []
overrides: []
depends_on: [WI-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-008, WI-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-007]
contract_packet: 1
contract_packet_sha256: fc5b09275d9cfe6bccc3c7f28c67ca370f6921a9afe79f114398b7761ef660f9
imports: [FE-GATE-008@1, FE-OC-008@1, FE-OC-015@1]
---
# branch: feature-routing-navigation-guard-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`
- **완료 조건**: registry route·param validation·404·redirect-loop·session UX test가 통과한다
<!-- section-id: inherited-project-decisions -->
### 상속한 프로젝트 결정
| Decision Ref | Project Summary | Branch Application | Source |
|---|---|---|---|
| `DEC-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-ROUTING-001@1` | routing default는 React Router Declarative Mode다 | Work Item 완료 조건에 적용 | [[raw/project-notes/ca-skeleton-frontend-operational-contract]] |
| `DEC-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-AUTH-BOUNDARY-001@1` | auth lifecycle은 외부 owner가 소유하고 skeleton은 AuthSessionPort만 소비한다 | 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-wide 계약 `FE-OC-005`("route ID/path/params/access/loading/error owner는 route registry 하나여야 함")를 *구현 착수 가능한 명세*로 내린다. 즉 route 메타데이터의 단일 소유 registry(`FE-REG-ROUTE`, `src/contracts/routes.js`)를 정의하고, [[raw/project-notes/ca-skeleton-frontend-operational-contract]] `FE-D008`(React Router Declarative Mode를 라우팅 default로 채택, `conditional-default`)을 이 registry 위에서 구현한다. 부수적으로 `FE-OC-010`(session state를 소비하되 token lifecycle을 소유하지 않음), `FE-OC-015`(route error/loading surface 소유를 render boundary와 중복하지 않고 reload loop 금지), `FE-OC-024`(sample route는 제거 가능한 fixture)에 기여한다. 아직 frontend repository가 없으므로 본 노트의 모든 구현 주장은 `planned` 등급이다.
- 이슈: (없음 — repository 미생성)
- PR: (없음)
<!-- section-id: branch-scope -->
## 범위
### 포함 범위
- `FE-REG-ROUTE` route registry를 단일 SSOT로 정의: `routeId`/`path`/`paramsSchema`/`searchSchema`/`access`/`loadingSurface`/`errorSurface`/`chunkId` 필드(§5.2 minimum schema) — 등급 `planned`
- React Router Declarative Mode 라우팅(`FE-D008`): `<Routes>`/`<Route>` 컴포넌트 트리 + nested `<Outlet/>` 합성 — 등급 `planned`
- route `access` 분류 enum `{public, session-required, integration-defined}`(§5.2) — 등급 `planned`
- navigation guard(=UX hint): `session-required` route가 `AuthSessionPort` state를 소비, redirect loop 차단(bounded hop) — 등급 `planned`
- unknown route → `NOT_FOUND`(`*`, public) surface, API 요청 없이 처리(§9.3) — 등급 `planned`
- route param/search runtime validation *진입점*: registry가 schema 참조를 선언(검증 엔진은 위임) — 등급 `planned`
- route별 `loadingSurface`/`errorSurface` owner 선언(render boundary와 owner 중복 금지) — 등급 `planned`
- sample route fixtures(`APP_HOME`, `SAMPLE_RESOURCE_LIST`, `NOT_FOUND`)(§5.2 initial rows) — 등급 `planned`
### 제외 범위
> 의도적으로 제외 — 다른 owner 브랜치 또는 외부 소유.
- token lifecycle(code exchange·refresh·rotation·logout·revocation): 외부 auth owner + [[raw/branch-notes/feature-frontend-auth-session-integration-contract]] `FE-OC-010` 소유. 본 브랜치는 `AuthSessionPort` state를 *소비*만.
- backend authorization 결정(최종 권한 판단): backend 소유. guard는 이를 대체하지 않음.
- failure 정규화 taxonomy(`401``AUTH_REQUIRED`, `404``NOT_FOUND`, `RENDER_FAILURE` 등): [[raw/branch-notes/feature-frontend-error-classification-boundary-contract]] `FE-OC-008` 소유.
- runtime schema 검증 *엔진*(Zod): [[raw/branch-notes/feature-runtime-schema-validation-contract]] `FE-OC-007` 소유. 본 registry는 schema *참조*만 선언.
- React error boundary taxonomy + reload-loop guard 구현: [[raw/branch-notes/feature-frontend-render-recovery-boundary-contract]] `FE-OC-015` 소유. 본 registry는 route별 surface owner *선언*만.
- lazy chunk ID ↔ release manifest 매핑 생성: [[raw/branch-notes/feature-frontend-release-cache-rollback-contract]] `FE-OC-016` 소유. route registry는 생성된 `chunkId` 값만 보유.
- 8-registry cross-cutting governance(single-owner·compatibility): [[raw/branch-notes/feature-frontend-contract-registry-governance]] `FE-OC-022` 소유.
## 근거 (필수, 최소 1개+)
| Source | 정당화하는 결정 |
|---|---|
| [[raw/official-docs/react-router-official]] `REACT-ROUTER-C1`·`REACT-ROUTER-C4` | D1 — `<Routes>`/`<Route>`로 URL segment를 UI에 결합하는 선언적 route 구성 + "Declarative Mode"가 파일 기반 Framework Mode와 별개로 존재(client-only Vite SPA 적합) |
| [[raw/official-docs/react-router-official]] `REACT-ROUTER-C2` | D2·D8 — nested route + `<Outlet/>` 합성(레이아웃 아래 보호된 자식 route 중첩) |
| [[raw/official-docs/react-router-official]] `REACT-ROUTER-C3` | D4 — `Link`/`NavLink` 활성 스타일링. **navigation guard(라우트 접근 제어)는 증명하지 않음**(§Usage Boundaries) → guard 결정은 hub project decision + D4 web 조사로 근거화 |
| reactrouter.com/start/declarative/navigating (2026-07-19 web 조사, 미아카이브) | D4 mechanism — Declarative Mode의 `useNavigate` programmatic navigation(로그인/로그아웃 등 비상호작용 redirect) 근거. `RR-NAV-WEB-C1`(§구현 가이드 3 인용) |
| [[raw/project-notes/ca-skeleton-frontend-operational-contract]] `FE-OC-005` (§5.1 owner map, §5.2 route registry schema) | D2·D3·D6·D8 — route registry 단일 소유, access enum, NOT_FOUND, surface owner |
| [[raw/project-notes/ca-skeleton-frontend-operational-contract]] `FE-D008` (§9.3 route behavior, §7.8 auth boundary, §13.2 route guard≠authorization) | D1·D4·D5·D7 — routing default, guard=UX hint, param validation, redirect loop 차단 |
> 미아카이브 web 근거(`RR-NAV-WEB-C1`)는 wiki 승격 전 `wiki-source-summarizer`로 `raw/official-docs/`에 정식 아카이브 필요(React Router 공식 doc의 §메모가 "loader/redirect 패턴 별도 자료 추가 필요"로 이미 flag).
## TODO
- [ ] `FE-REG-ROUTE` route registry 모듈(`routeId`/`path`/`paramsSchema`/`searchSchema`/`access`/`loadingSurface`/`errorSurface`/`chunkId`) — 등급: `planned`
- [ ] registry로부터 Declarative Mode router 구성(`<Routes>`/`<Route>`/`<Outlet>`) — 등급: `planned`
- [ ] access 분류 + navigation guard(UX hint, `AuthSessionPort` 소비) — 등급: `planned`
- [ ] param/search validation 진입점(schema 참조 선언; Zod 검증은 위임) — 등급: `planned`
- [ ] `NOT_FOUND` route + redirect-loop guard(automatic redirect ≤ 1, 동일 pair 반복 금지) — 등급: `planned`
- [ ] route `loadingSurface`/`errorSurface` owner 선언(boundary 중복 금지) — 등급: `planned`
- [ ] 테스트: registry snapshot · param validation · 404 no-API · redirect-loop · session UX — 등급: `planned`
## 진행 중 메모
- `REACT-ROUTER-C3`이 guard를 증명하지 않는다는 점이 이 브랜치의 핵심 함정이다. guard *결정*은 hub project decision(§7.8/§9.3/§13.2)으로, guard *메커니즘*은 `useNavigate` web 조사(`RR-NAV-WEB-C1`)로 근거화하고, 구체 컴포넌트 설계는 `UNSUPPORTED_IMPL_DECISION`으로 남긴다.
- Declarative Mode에는 built-in loader/redirect가 없으므로 param validation과 guard가 모두 component 계층 구현이 된다(§구현 가이드 3·4).
## 결정 사항
> 대안과 근거를 함께 기록. 상세 근거 매핑은 아래 Decision Evidence Map.
- 2026-07-19: 라우팅은 React Router Declarative Mode를 default로 채택 / 이유: client-only Vite SPA는 SSR·file-based convention·route-level loader가 없어 선언적 `<Routes>`/`<Route>` 트리로 충분 / 검토한 대안: data router mode(route 객체 + loader/action), framework mode(파일 기반 컨벤션+SSR) / 근거: [[raw/project-notes/ca-skeleton-frontend-operational-contract]] `FE-D008`, `raw/official-docs/react-router-official.md#REACT-ROUTER-C1`·`#REACT-ROUTER-C4`
- 2026-07-19: route ID/path/params/access/loading/error를 단일 route registry(`FE-REG-ROUTE`)가 소유 / 이유: rename·rollback 영향 범위를 한 곳에서 계산, component literal route path로 인한 분산 방지 / 검토한 대안: 파일 기반/컴포넌트 인라인 route 정의 / 근거: [[raw/project-notes/ca-skeleton-frontend-operational-contract]] `FE-OC-005` (§5.1·§5.2)
- 2026-07-19: route `access``{public, session-required, integration-defined}` 3-값 enum / 이유: 접근 정책을 registry 필드로 고정해 component 분기 제거 / 검토한 대안: boolean `requiresAuth`, role 배열 / 근거: [[raw/project-notes/ca-skeleton-frontend-operational-contract]] `FE-OC-005` (§5.2 access field)
- 2026-07-19: navigation guard는 UX hint일 뿐 authorization이 아니고 backend authorization이 최종 판단 / 이유: client guard는 우회 가능하므로 보안 경계로 삼지 않음(§13.2) / 검토한 대안: client-side 강제(백엔드 authz 없이 route로 접근 통제) / 근거: [[raw/project-notes/ca-skeleton-frontend-operational-contract]] `FE-D008` (§7.8·§9.3·§13.2); guard mechanism은 `RR-NAV-WEB-C1`(`useNavigate`)
- 2026-07-19: route param/search는 application 호출 전 runtime validation, registry가 schema 참조 선언·검증 엔진은 위임 / 이유: 잘못된 URL 입력을 경계에서 차단하되 Zod 채택은 별도 owner 결정 / 검토한 대안: validation 생략(신뢰) / 근거: [[raw/project-notes/ca-skeleton-frontend-operational-contract]] `FE-D008` (§9.3), `FE-D007`(Zod)
- 2026-07-19: unknown route → API 없이 not-found surface, `NOT_FOUND`(`*`, public)를 registry에 포함 / 이유: 존재하지 않는 route에 불필요한 네트워크 요청 금지 / 검토한 대안: 서버 라우팅 위임 / 근거: [[raw/project-notes/ca-skeleton-frontend-operational-contract]] `FE-D008` (§9.3·§8.2·§5.2)
- 2026-07-19: redirect loop 차단 — navigation attempt당 automatic auth redirect ≤ 1, 동일 source→target pair 반복 금지 / 이유: guard redirect가 무한 루프가 되지 않도록 hop 제한 / 검토한 대안: 무제한 redirect / 근거: [[raw/project-notes/ca-skeleton-frontend-operational-contract]] `FE-D008` (§9.3·§7.8·§10.2; `FE-GATE-008` e2e invariant)
- 2026-07-19: route별 `loadingSurface`/`errorSurface` owner를 registry가 선언, route error element와 React error boundary owner 중복 금지 / 이유: 같은 실패를 두 소유자가 처리하는 모호성 제거(§9.3·§10.1) / 검토한 대안: boundary만으로 처리 / 근거: [[raw/project-notes/ca-skeleton-frontend-operational-contract]] `FE-OC-005` (§5.2·§9.3·§10.1)
## 결정-근거 매핑
> `Decision ID`는 본 branch-note 안에서 안정적. `Supporting Claims`의 `FE-D###`·`§n`은 hub project 문서 기준.
| Decision ID | Decision | 선택 조건 (언제 이 결정 / 언제 대안) | Supporting Claims | Evidence Strength | Open Risk |
|---|---|---|---|---|---|
| D1 | React Router Declarative Mode를 라우팅 default로 채택 (`FE-OC-005` 구현 기반) | Declarative Mode 유지: client-only Vite SPA에 loader·SSR·file-based convention 요구가 없을 때. 대안(data router/framework mode)은 route-level data loading·SSR이 product requirement가 될 때 전환 | [[raw/project-notes/ca-skeleton-frontend-operational-contract]] `FE-D008`; `raw/official-docs/react-router-official.md#REACT-ROUTER-C1`, `#REACT-ROUTER-C4` | official-doc + conditional-default | Declarative Mode에는 built-in loader/redirect가 없어 guard·validation을 component 계층에서 구현해야 함(D4·D5 impl 위험) |
| D2 | route ID/path/params/access/loading/error를 단일 route registry(`FE-REG-ROUTE`)가 소유 — component 내 literal route path 금지 | 항상 registry 경유: route 메타데이터가 rename·compatibility 추적 대상일 때(=본 skeleton). literal 경로는 §0.4 throwaway single-route prototype에서만 허용 | [[raw/project-notes/ca-skeleton-frontend-operational-contract]] `FE-OC-005` (§5.1 owner map, §5.2 schema) | project-decision | registry field 확장(신규 access class 등)은 `FE-REG-ROUTE` 변경 프로토콜(§5.10) 필요 |
| D3 | route `access` = `{public, session-required, integration-defined}` 3-값 enum | 이 3-값으로 고정. 새 access class는 `FE-REG-ROUTE` schema 변경 절차를 거칠 때만 추가 | [[raw/project-notes/ca-skeleton-frontend-operational-contract]] `FE-OC-005` (§5.2 access field) | project-decision | `integration-defined` semantics는 auth owner 결정에 의존(§7.8) |
| D4 | navigation guard는 UX hint일 뿐 authorization 아님; backend authorization이 최종 판단; `session-required` route는 `AuthSessionPort` state를 소비 | guard=advisory 유지: backend가 authz를 강제하는 한. client-only 강제(백엔드 authz 부재)가 필요하면 별도 결정 필요(현재 근거 없음 → UNSUPPORTED) | [[raw/project-notes/ca-skeleton-frontend-operational-contract]] `FE-D008` (§7.8·§9.3·§13.2); `RR-NAV-WEB-C1`(useNavigate); `raw/official-docs/react-router-official.md#REACT-ROUTER-C3`(guard 미증명 — UX 링크 스타일만) | project-decision | guard 우회 시 backend authz가 유일 방어선 — client guard를 보안 경계로 오인 금지 |
| D5 | route param·search를 application 호출 전 runtime validation; registry가 `paramsSchema`/`searchSchema` 참조 선언, 검증 엔진(Zod)은 위임 | dynamic param/search 존재 시 validation(conditional field). static route는 schema 없음 | [[raw/project-notes/ca-skeleton-frontend-operational-contract]] `FE-D008` (§9.3), `FE-D007`(Zod, §5.2 conditional field) | project-decision (delegated) | Zod 통합 형태(route wrapper vs effect)는 Declarative Mode에 loader가 없어 impl 미정 |
| D6 | unknown route → API 없이 not-found surface; `NOT_FOUND`(`*`, public) route를 registry에 포함 | catch-all `*` route 상시 존재. API 응답 404는 별도 정규화(`NOT_FOUND` kind)로 error-classification branch 소유 | [[raw/project-notes/ca-skeleton-frontend-operational-contract]] `FE-D008` (§9.3·§8.2, §5.2 NOT_FOUND row) | project-decision | route-level 404 UX와 API 404 UX 일관성은 `FE-OC-008`와 조율 필요 |
| D7 | redirect loop 차단: navigation attempt당 automatic auth redirect ≤ 1, 동일 source→target pair 반복 금지 | 첫 guard redirect 1회 허용; 두 번째 동일 redirect → terminal auth-required/error surface(§7.8 second-`401` terminal, §10.2 guard-record-then-act와 동형) | [[raw/project-notes/ca-skeleton-frontend-operational-contract]] `FE-D008` (§9.3·§7.8·§10.2; `FE-GATE-008` e2e invariant) | project-decision | hop-count 상수·guard 자료구조는 문서 미명세 → `UNSUPPORTED_IMPL_DECISION`(§구현 가이드 5) |
| D8 | route registry가 route별 `loadingSurface`·`errorSurface` owner 선언; route error element와 React error boundary owner 중복 금지 | route-level `errorSurface`는 lazy-chunk/route render 실패 소유; expected operational 실패는 normal async state로 반환(throw 금지) | [[raw/project-notes/ca-skeleton-frontend-operational-contract]] `FE-OC-005` (§5.2·§9.3·§10.1) | project-decision | boundary taxonomy는 `FE-OC-015`(render-recovery) 소유 — surface owner token 어휘 정합 필요 |
## 구현 가이드
> 모든 세부는 `planned`(frontend repository 미생성). 경로는 hub §4.6 planned directory blueprint + §5.1 registry owner map에서 도출된 anchor.
### 1. FE-REG-ROUTE route registry 모듈
> **Trace**: D2 (`FE-OC-005`, `FE-REG-ROUTE`) — hub §5.1(`src/contracts/routes.js` 소유) + §5.2(minimum schema)에서 도출. D6·D8의 필드(`NOT_FOUND` row, `loadingSurface`/`errorSurface`)도 이 모듈이 담는다.
>
> - **UNSUPPORTED_IMPL_DECISION**: 모듈의 JS 형태(frozen descriptor 배열 vs factory 함수) — 문서 미명세. trade-off: snapshot 테스트 용이성을 위해 `Object.freeze`된 route descriptor 배열 + `routeId` 조회 헬퍼로 채택(임의 선택).
> - **UNSUPPORTED_IMPL_DECISION**: 3개 seed row 외 실제 route naming — 문서는 `APP_HOME`/`SAMPLE_RESOURCE_LIST`/`NOT_FOUND`만 제시. trade-off: 신규 route는 `UPPER_SNAKE_CASE` 규칙만 따르고 product route는 sample 제거 후 추가.
필드(§5.2 그대로, `planned`):
| Field | Required | Rule (hub §5.2) |
|---|---|---|
| `routeId` | yes | stable `UPPER_SNAKE_CASE`; rename은 breaking |
| `path` | yes | 중앙 literal; component 내부 literal 금지 |
| `paramsSchema` | conditional | dynamic param 있으면 runtime validation(D5) |
| `searchSchema` | conditional | query string을 application input으로 넘기기 전 validation(D5) |
| `access` | yes | `public` \| `session-required` \| `integration-defined`(D3) |
| `loadingSurface` | yes | route-level fallback owner(D8) |
| `errorSurface` | yes | route-level error owner(D8) |
| `chunkId` | generated | release manifest와 매핑(생성값만 보유; 매핑은 out-of-scope) |
Initial planned rows(§5.2): `APP_HOME`(`/`, public), `SAMPLE_RESOURCE_LIST`(`/sample/resources`, integration-defined), `NOT_FOUND`(`*`, public, no API retry).
### 2. Declarative Mode router 구성
> **Trace**: D1 (`FE-D008`, `REACT-ROUTER-C1`·`C2`·`C4`) — registry rows를 `<Routes>`/`<Route>` 트리로 렌더, nested route는 `<Outlet/>`로 합성. router는 boot order 9단계(§4.5)에서 생성. anchor: `src/presentation/app/`, `src/presentation/routes/`(§4.6).
>
> - **UNSUPPORTED_IMPL_DECISION**: `<BrowserRouter>` 컴포넌트 vs 다른 history 구성 — 문서 미명세. trade-off: Declarative Mode 표준인 `<BrowserRouter>` + registry 기반 `<Route>` 생성 함수 채택. base path는 `VITE_ROUTER_BASE_PATH`(§5.4, default `/`) 소비.
> - **UNSUPPORTED_IMPL_DECISION**: registry→route-element 생성 함수 이름/시그니처 — 임의. trace 가능한 단일 함수로 두어 registry가 유일 SSOT임을 보장.
절차(`planned`): (1) registry 로드(§4.5 step 5) → (2) 각 row를 `<Route path element access>`로 매핑 → (3) 레이아웃 route는 `<Outlet/>`로 자식 중첩(`REACT-ROUTER-C2`) → (4) `NOT_FOUND` catch-all `*`는 마지막 → (5) `<BrowserRouter basename=VITE_ROUTER_BASE_PATH>`로 mount(§4.5 step 10).
### 3. Access 분류 + navigation guard (UX hint)
> **Trace**: D3·D4 — `session-required` route는 [[raw/branch-notes/feature-frontend-auth-session-integration-contract]] `FE-OC-010`의 `AuthSessionPort` state를 application facade 경유로 소비(§7.8). guard가 미인증 시 auth-required surface 렌더 또는 programmatic redirect. guard≠authorization(§13.2). redirect 메커니즘 근거는 `RR-NAV-WEB-C1`.
>
> - **UNSUPPORTED_IMPL_DECISION**: guard를 컴포넌트 wrapper vs route element로 구현, 그리고 `<Navigate>` element vs `useNavigate` effect 중 무엇 — 문서상 `useNavigate`만 근거 확보(`RR-NAV-WEB-C1`), `<Navigate>`는 미검증. trade-off: 우선 route wrapper + `useNavigate`(doc-grounded)로 구현하고 `<Navigate>` 채택은 별도 검증 전 보류.
> - **UNSUPPORTED_IMPL_DECISION**: guard 컴포넌트/훅 명명 및 `integration-defined` access의 정확한 소비 형태 — auth owner 결정에 의존. trade-off: `integration-defined`는 auth adapter가 접근 가부를 반환할 때까지 loading surface 유지.
`RR-NAV-WEB-C1` (web 인용, reactrouter.com/start/declarative/navigating, 2026-07-19):
> "This hook allows the programmer to navigate the user to a new page without the user interacting."
> 문서 예시 용례: "Logging them out after inactivity" — 즉 비상호작용 상황의 programmatic redirect가 `useNavigate`의 정당한 용도이며, guard redirect가 이에 해당.
access별 동작(`planned`): `public`=무조건 렌더 / `session-required`=session 있으면 렌더, 없으면 auth-required surface + (선택) 1회 redirect(D7) / `integration-defined`=auth adapter 판정까지 loading, 판정 후 렌더 or auth-required.
### 4. Param/Search validation 진입점
> **Trace**: D5 (`FE-D008` §9.3, §5.2 conditional field) — registry의 `paramsSchema`/`searchSchema`는 *참조*만 담고, 실제 Zod 검증 엔진은 [[raw/branch-notes/feature-runtime-schema-validation-contract]] `FE-OC-007`가 소유. 검증은 application use case 호출 *전*에 수행.
>
> - **UNSUPPORTED_IMPL_DECISION**: Declarative Mode에는 loader가 없어 검증을 어디서 실행할지(route-entry 훅 vs 컴포넌트 mount effect) 문서 미명세. trade-off: route-entry 훅에서 schema 참조를 조회→검증→실패 시 not-found/route error surface로 분기(임의 선택, loader 부재 대응).
> - **UNSUPPORTED_IMPL_DECISION**: 검증 실패를 `NOT_FOUND`로 볼지 `VALIDATION_REJECTED`로 볼지 — 정규화는 `FE-OC-008` 소유. trace: 잘못된 route param은 존재하지 않는 리소스로 보아 not-found surface가 default(§9.3 "unknown route" 연장), 최종 kind 매핑은 error-classification과 조율.
### 5. NOT_FOUND + redirect-loop 방지
> **Trace**: D6·D7 (`FE-D008` §9.3·§8.2·§7.8·§10.2, `FE-GATE-008` e2e invariant) — `NOT_FOUND` catch-all은 API 요청 없이 not-found surface. guard redirect는 navigation attempt당 ≤ 1이고 동일 source→target pair 반복 금지.
>
> - **UNSUPPORTED_IMPL_DECISION**: source→target pair를 기록하는 guard 자료구조/키 형태와 max hop 상수 — 문서 미명세. trade-off: §10.2 `CHUNK_RELOAD_GUARD` 패턴을 차용해 `(fromRouteId,toRouteId)` 키의 per-navigation guard를 두고 두 번째 동일 pair에서 redirect 중단(임의 설계, 문서 패턴 동형).
절차(`planned`): 첫 미인증 진입 → guard 기록 후 auth-required target으로 1회 redirect → 복귀 후 여전히 미인증이고 동일 pair면 redirect 대신 terminal auth-required surface(§7.8 second-`401` terminal과 동형). unknown path → 즉시 `NOT_FOUND` surface, network 0건.
### 6. Loading/Error surface owner 선언
> **Trace**: D8 (`FE-OC-005` §5.2·§9.3·§10.1) — registry가 route별 `loadingSurface`/`errorSurface` owner token을 선언. route error element와 React error boundary는 owner 중복 금지(§9.3). boundary taxonomy 자체는 [[raw/branch-notes/feature-frontend-render-recovery-boundary-contract]] `FE-OC-015` 소유.
>
> - **UNSUPPORTED_IMPL_DECISION**: surface owner token 어휘 — 문서 미명세. trade-off: §10.1 boundary 명칭(`boot shell`/`route boundary`/`feature boundary`/`async boundary`)을 owner token으로 재사용해 render-recovery branch와 어휘 정합(임의 선택, 문서 표 차용).
원칙(`planned`): expected operational 실패(API 실패 등)는 normal async state로 반환하고 render boundary에 throw하지 않음(§10.1). route render/lazy-chunk 실패만 `errorSurface`가 처리. `loadingSurface`는 route-level fallback owner.
## 엣지·실패·의존
- **실패·엣지 경로**:
- unknown route → `NOT_FOUND` surface, API 요청 0건(§9.3)
- `session-required` route + 미인증 → automatic redirect ≤ 1; 동일 source→target 재발 → terminal auth-required surface(loop 없음)(§7.8·§10.2·`FE-GATE-008`)
- invalid route param/search → application 호출 전 validation 실패 → not-found/route error surface(§9.3·§5.2)
- lazy route chunk fetch 실패 → `CHUNK_LOAD_FAILURE`, controlled reload once(§8.2·§10.2) — reload guard는 render-recovery 소유; 본 registry는 `chunkId`만 매핑
- in-flight 요청 중 navigation abort → `REQUEST_ABORTED`, error toast 금지(§8.2) — API client 소유; route는 `routeId`+`abortReason=navigation`만 공급(§7.2)
- route render throw → `RENDER_FAILURE`(route boundary, §8.2·§10.1) — boundary는 render-recovery 소유; 본 registry는 `errorSurface` owner 선언만
- **다른 계약 의존**:
- [[raw/branch-notes/feature-frontend-auth-session-integration-contract]] `FE-OC-010``AuthSessionPort` session state를 UX hint로 소비. 이 계약이 바뀌면 guard의 session 판정 방식 영향.
- [[raw/branch-notes/feature-frontend-error-classification-boundary-contract]] `FE-OC-008``401``AUTH_REQUIRED`, `404``NOT_FOUND`, `RENDER_FAILURE` 정규화. route-level 404/auth UX의 kind 매핑을 여기서 consume.
- [[raw/branch-notes/feature-frontend-render-recovery-boundary-contract]] `FE-OC-015` — route/React error boundary taxonomy + reload-loop guard. surface owner token 어휘 정합 대상.
- [[raw/branch-notes/feature-runtime-schema-validation-contract]] `FE-OC-007` — param/search schema의 Zod 검증 엔진.
- [[raw/branch-notes/feature-frontend-release-cache-rollback-contract]] `FE-OC-016``chunkId` ↔ release manifest 매핑.
- [[raw/branch-notes/feature-frontend-contract-registry-governance]] `FE-OC-022``FE-REG-ROUTE` single-owner + compatibility governance.
## 검증해야 할 주장
| Claim | Why uncertain | How to verify | Status |
|---|---|---|---|
| route registry가 유일 SSOT — component에 literal route path 0건 | frontend 코드 미존재, registry 우회 가능성 | registry snapshot 테스트(`FE-OC-005` min evidence) + literal-path 정적 검사(component에 route literal 금지) | `needs-confirmation` |
| dynamic route param/search가 application 호출 전 검증됨 | Declarative Mode에 loader가 없어 검증 위치가 impl 의존 | invalid param fixture로 param validation deterministic 테스트(§20 measurable) | `needs-confirmation` |
| unknown route가 API 요청 0건으로 not-found surface 렌더 | 라우팅 setup에 따라 우발적 fetch 가능 | 404 테스트에서 network 호출 0건 assert(§9.3) | `needs-confirmation` |
| navigation guard가 navigation attempt당 automatic redirect ≤ 1, 동일 source→target 반복 없음 | guard 자료구조 미설계(`UNSUPPORTED_IMPL_DECISION`) | redirect-loop e2e 테스트(`FE-GATE-008` invariant: automatic auth redirect ≤ 1, pair 무반복) | `needs-confirmation` |
| `session-required` route가 `AuthSessionPort` state를 UX hint로만 사용, token lifecycle 미소유 | 위임 경계가 코드로 강제되는지 미확인 | session UX 테스트 + token-lifecycle import 금지 assert(§4.3 dependency rule) | `needs-confirmation` |
| route error element와 React error boundary owner가 중복되지 않음 | boundary가 render-recovery 소유라 경계 조율 필요 | route surface owner vs boundary ownership 테스트(render-recovery와 공동)(§9.3·§10.1) | `needs-confirmation` |
| Declarative Mode `<Routes>`/`<Route>`/`<Outlet>`가 registry 트리를 렌더(framework/file-based convention 없이) | 라이브러리 API 정합성 미검증 | registry 기반 route 트리 component 렌더 테스트(`REACT-ROUTER-C1`·`C2`) | `needs-confirmation` |
## 관심사 커버리지 (coverage-auditor 자동 생성 — 있을 때)
> `/coverage`가 채우는 생성물이며 손으로 유지하지 않는다.
| 관심사 | 상태 | owner | 심각도 | 근거 |
|---|---|---|---|---|
| TODO — `/coverage` 실행 전 | missing | (없음) | 미평가 | TODO |
## 마주친 문제
없음 — scaffolding 단계
## 묶음 (이 branch에서 파생된 자료)
<!-- GENERATED: project-contract-imports:start -->
## 가져온 프로젝트 계약
| Ref | Owner | 요약 | Branch 적용 |
|---|---|---|---|
| `FE-GATE-008@1` | [[raw/branch-notes/feature-frontend-test-taxonomy-contract]] | critical e2e 시나리오가 실패하면 merge·release 를 MUST 차단 | import 참조로 적용 |
| `FE-OC-008@1` | [[raw/branch-notes/feature-frontend-error-classification-boundary-contract]] | 모든 failure는 stable frontend error kind로 MUST 정규화하고 raw body·stack을 UI에 노출하면 안 됨 | import 참조로 적용 |
| `FE-OC-015@1` | [[raw/branch-notes/feature-frontend-render-recovery-boundary-contract]] | expected operational error와 render defect를 MUST 분리하고 reload loop를 금지 | 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):