--- title: branch / feature-frontend-auth-session-integration-contract source_type: branch-note status: raw id: BR-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-008 kind: project-work-item project: ca-skeleton-frontend-operational-contract work_item: WI-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-008 inherits: [DEC-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-AUTH-BOUNDARY-001@1, DEC-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-PORT-OWNERSHIP-001@1] refines: [] overrides: [] depends_on: [WI-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-002] contract_packet: 1 branch: feature-frontend-auth-session-integration-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, auth, security, javascript, oauth2] created: 2026-07-18 target_merge: status_label: in-progress contract_packet_sha256: ec06d05939cbbe7c12c1a581115a07830894c4328344e6a5053c2162df756ab0 imports: [FE-OC-002@1, FE-OC-005@1, FE-OC-006@1, FE-OC-008@1, FE-OC-019@1] --- # branch: feature-frontend-auth-session-integration-contract > Layer: `raw/branch-notes/` — 단일 브랜치의 TODO·결정·진행 기록. 머지/종료 후 verified 결과는 `/ingest`로 `wiki/projects/`에 추출한다. ## 부모 (필수) - [[raw/project-notes/ca-skeleton-frontend-operational-contract]] ## 브랜치 계약 패킷 - **생성 시 프로젝트 개정**: `1` - **패킷 스키마**: `contract_packet: 1` - **완료 조건**: AuthSessionPort·bounded 401 replay와 token lifecycle import 금지가 test로 고정된다 ### 상속한 프로젝트 결정 | Decision Ref | Project Summary | Branch Application | Source | |---|---|---|---| | `DEC-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-AUTH-BOUNDARY-001@1` | auth lifecycle은 외부 owner가 소유하고 skeleton은 AuthSessionPort만 소비한다 | token lifecycle 비소유와 bounded session recovery 경계에 적용 | [[raw/project-notes/ca-skeleton-frontend-operational-contract]] | | `DEC-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-PORT-OWNERSHIP-001@1` | output port interface는 application이 소유하고 adapter가 구현한다 | application-owned AuthSessionPort와 외부 auth adapter 경계에 적용 | [[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 | |---|---|---|---|---| ## 목표 이 브랜치는 hub의 `FE-OC-010`("skeleton은 session state를 소비하되 token lifecycle을 MUST NOT 소유")을 구현 착수 가능한 명세로 내린다. 구체적으로 [[raw/project-notes/ca-skeleton-frontend-operational-contract]] `FE-D017`(auth lifecycle은 외부 owner, skeleton은 `AuthSessionPort`만 소비)을 근거로, application이 소유하는 `AuthSessionPort` 경계 · bounded 401 recovery state machine · recovery replay policy · auth 실패 정규화 · no-token-lifecycle 불변식을 `planned` 청사진으로 확정한다. 또한 이 경계가 라우팅·API client·browser security에 닿는 지점(`FE-OC-005`·`FE-OC-006`·`FE-OC-019`)에 대해 "무엇을 기여하고 무엇을 다른 owner 브랜치에 위임하는지"를 못박는다. token 발급/저장/refresh/rotation/logout은 외부 auth owner(Keycloak 등, [[raw/project-notes/keycloak-patterns-overview]])가 소유하므로 이 노트는 그것을 *명명만* 하고 명세하지 않는다. 현재 frontend 구현 코드가 없어 모든 항목은 `planned`다. - 이슈: - PR: ## 범위 ### 포함 범위 - `AuthSessionPort`(application 소유 integration boundary port)의 소비 계약: opaque session state 읽기 + request 전 `attach(request)` + unauthenticated transition 통지 콜백 (`FE-OC-010`). - Bounded 401 recovery state machine: `authenticated → recovery-pending → {authenticated | unauthenticated | integration-failed}`, logical request당 recovery callback 최대 1회, 두 번째 401은 terminal (hub §7.8). - Recovery 이후 replay policy: `safe` 1회 replay / `keyed` mutation은 stable idempotency key + backend replay contract일 때만 1회 / `none`(unkeyed) mutation은 replay 금지 (hub §7.8·§7.7). - Auth 실패 정규화 기여: `401→AUTH_REQUIRED`, `403→FORBIDDEN`, attach/recovery adapter fault→`AUTH_INTEGRATION_FAILURE` (hub §8.2·§8.5). - no-token-lifecycle 불변식: skeleton은 token/secret을 browser storage·bundle·env·telemetry·error body에 저장/노출하지 않는다 (`FE-OC-019` 기여, hub §5.5·§6.1). - session-required route가 `AuthSessionPort` state를 UX hint로만 소비하고 backend authorization을 최종 판단으로 두는 규칙 (`FE-OC-005` 기여, hub §9.3). ### 제외 범위 > 의도적으로 제외 — 외부 auth owner 또는 다른 owner 브랜치 소유. 여기서는 *명명*만 하고 명세하지 않는다. - **Token lifecycle 전체** — authorization code exchange, token 저장 위치, access token refresh, refresh token rotation, logout propagation, revocation, IdP redirect detail, backend permission decision. 외부 auth owner 소유([[raw/project-notes/keycloak-patterns-overview]], hub §7.8 "Skeleton does not own"). - **Route registry / navigation guard 메커니즘** (route ID/path/param validation/404/redirect-loop) — [[raw/branch-notes/feature-routing-navigation-guard-contract]] 의 `FE-OC-005` 소유. 나는 session-state hint 소비만 기여. - **Shared HTTP client transport 및 timeout/abort/retry algorithm** — [[raw/branch-notes/feature-api-client-response-envelope-contract]] 의 `FE-OC-006`·`FE-OC-009` 소유. attach·정규화는 그 client 안에서 실행되나 client 뼈대는 그 브랜치가 소유. - **Error registry 구조 및 total-function 정규화** — [[raw/branch-notes/feature-frontend-error-classification-boundary-contract]] 의 `FE-OC-008` 소유. 나는 3개 auth kind의 기대 매핑만 기여. - **Storage registry 스키마** — [[raw/branch-notes/feature-frontend-storage-registry-contract]] 의 `FE-OC-013` 소유. `AUTH_TOKEN` forbidden 행의 불변식만 기여. - **CSP/header/secret-scan browser boundary 메커니즘** — [[raw/branch-notes/feature-frontend-browser-security-boundary-contract]] 의 `FE-OC-019` 소유. 나는 no-token-storage 불변식만 기여. - **Telemetry redaction 인프라** — [[raw/branch-notes/feature-frontend-observability-logging-trace-contract]] 의 `FE-OC-014` 소유. auth 이벤트의 forbidden attribute(token/principal) 규칙만 기여. - **Architecture import-lint 강제 메커니즘** — [[raw/branch-notes/feature-frontend-architecture-enforcement-lint-contract]] 의 `FE-OC-002` 소유. 나는 "token lifecycle 심볼은 auth adapter 밖에서 import 금지"라는 *검사 대상 불변식*만 정의. ## 근거 (필수, 최소 1개+) | Source | 정당화하는 결정 | |---|---| | [[raw/project-notes/ca-skeleton-frontend-operational-contract]] | `FE-D017`/`FE-OC-010` + §4.4(Port ownership) · §7.8(Auth integration boundary) · §8.2(failure matrix) · §5.5·§6.1(browser security) · §9.3(route behavior) — D1~D7의 1차 근거(project decision SSOT) | | [[raw/project-notes/keycloak-patterns-overview]] | 외부 auth owner가 token 발급·저장·refresh·rotation·logout을 소유한다는 external-owner context(§3 token 종류, §2.1 token 위치·인증 강제 주체) — D1(경계 설정)·D6(token 브라우저 미저장)의 배경 근거 | | [[raw/official-docs/react-router-official]] | route composition(``/``, nested ``; `REACT-ROUTER-C1`/`C2`)이 session-required route surface가 얹히는 라우팅 모드에 부합 — D7의 라우팅 context. ⚠ navigation **guard 보안**(`REACT-ROUTER-C3` boundary)은 이 자료가 **정당화하지 않음** → guard=UX hint는 project decision(D7) | ## TODO - [ ] `AuthSessionPort` 인터페이스 정의(application 소유): opaque session state getter + `attach(request)` header/credential callback + unauthenticated-transition callback — 등급: `planned` - [ ] 외부 auth adapter placeholder(`adapters/auth/`)가 port 구현, composition-root boot step 6에서 주입 — 등급: `planned` - [ ] shared HTTP client 경유 bounded 401 state machine + replay policy 구현 — 등급: `planned` - [ ] auth 실패 정규화 매핑(401/403/attach·recovery fault)을 error registry에 기여 — 등급: `planned` - [ ] no-token-lifecycle import test(architecture fixture): token 저장/refresh 심볼을 `adapters/auth/` 밖에서 import 시 실패 — 등급: `planned` - [ ] session-required route UX hint + backend authz 최종성 e2e(guarded route 403 처리) — 등급: `planned` - [ ] negative fixtures: attach throw/reject·recovery invalid state → `AUTH_INTEGRATION_FAILURE`; unkeyed mutation recovery → no replay — 등급: `planned` ## 진행 중 메모 `/branch-spec` 자체 채움(self-map). frontend 구현 repository 미식별 → 전 항목 `planned`. hub와 6개 archived official-doc이 유일 SSOT이며, auth lifecycle 근거는 [[raw/project-notes/keycloak-patterns-overview]]. 인라인 웹 리서치 0건(hub가 이미 충분). ## 결정 사항 - 2026-07-19: auth lifecycle은 외부 owner, skeleton은 `AuthSessionPort`만 소비 / 이유: token 발급·저장·refresh·rotation·logout·revocation은 IdP·backend가 소유하는 관심사이며 client-only SPA가 이를 소유하면 보안·release 경계가 흐려짐 / 검토한 대안: skeleton이 token lifecycle을 직접 소유(독립 auth product) / 근거: [[raw/project-notes/ca-skeleton-frontend-operational-contract]] `FE-D017`·`FE-OC-010`, [[raw/project-notes/keycloak-patterns-overview]]. - 2026-07-19: `AuthSessionPort`는 application이 정의 소유, 외부 adapter가 구현, token 문자열을 domain/application model로 반환하지 않고 opaque state + attach callback 형태 우선 / 이유: dependency inversion 유지 + token이 layer 내부로 스며들지 않게 / 검토한 대안: adapter가 직접 token을 반환해 use case가 소비 / 근거: [[raw/project-notes/ca-skeleton-frontend-operational-contract]] `FE-D010`·`FE-D009`·`FE-OC-002`·§4.4. - 2026-07-19: 401 recovery는 bounded state machine, logical request당 recovery 1회, 2nd 401 terminal / 이유: recovery loop 차단 / 검토한 대안: 무제한 재인증 재시도 / 근거: [[raw/project-notes/ca-skeleton-frontend-operational-contract]] §7.8·`FE-OC-006`. - 2026-07-19: recovery replay는 `safe` 1회 / `keyed`(+backend replay contract) 1회 / `none` 금지 / 이유: duplicate write 방지 / 검토한 대안: 성공 후 무조건 replay / 근거: [[raw/project-notes/ca-skeleton-frontend-operational-contract]] §7.8·§7.7·`FE-D016`. - 2026-07-19: auth 실패 정규화 `401→AUTH_REQUIRED` / `403→FORBIDDEN` / attach·recovery fault→`AUTH_INTEGRATION_FAILURE`, raw body·token 미노출 / 근거: [[raw/project-notes/ca-skeleton-frontend-operational-contract]] §8.2·§8.5·§5.6·`FE-OC-008`. - 2026-07-19: token/secret은 browser storage·bundle·env·telemetry·error body에 미저장·미노출; `AUTH_TOKEN` key forbidden / 근거: [[raw/project-notes/ca-skeleton-frontend-operational-contract]] §5.5·§6.1·§5.8·`FE-OC-019`. - 2026-07-19: session-required route는 `AuthSessionPort` state를 UX hint로만 사용, backend authorization이 최종 / 이유: guard를 security control로 오해 방지 / 근거: [[raw/project-notes/ca-skeleton-frontend-operational-contract]] §9.3·§7.8·`FE-OC-005`. (react-router `REACT-ROUTER-C3`는 guard를 정당화하지 않음.) ## 결정-근거 매핑 > 각 결정과 raw source claim ID의 연결. `FE-D###`/`§` 참조는 hub project 링크에 붙인다(consistency hook 규약). | Decision ID | Decision | 선택 조건 (언제 이 결정 / 언제 대안) | Supporting Claims | Evidence Strength | Open Risk | |---|---|---|---|---|---| | D1 | auth lifecycle은 외부 owner; skeleton은 `AuthSessionPort`만 소비하고 token 발급/저장/refresh/rotation/logout/revocation을 MUST NOT 소유 (`FE-OC-010`) | client-only SPA + 외부 auth owner가 session interface를 제공하는 한 이 결정 유지 / skeleton이 독립 auth product로 scope 변경되면 재검토 (`FE-D017` revisit trigger) | [[raw/project-notes/ca-skeleton-frontend-operational-contract]] `FE-D017`·`FE-OC-010`; [[raw/project-notes/keycloak-patterns-overview]] (외부 owner가 token 종류·위치 소유) | `project-decision` | 통합 adapter owner 미정 (`FE-Q-006`); guard가 security로 오해될 위험 (`FE-RISK-005`) | | D2 | `AuthSessionPort`는 application이 정의 소유, 외부 adapter가 구현, token 문자열을 model로 반환하지 않고 opaque state + attach callback 형태 우선 | dependency inversion 유지(adapter가 port 구현)하는 한 유지 / port가 domain invariant 자체를 표현해야 하는 concrete case면 재검토 (`FE-D010` revisit) | [[raw/project-notes/ca-skeleton-frontend-operational-contract]] `FE-D010`·`FE-D009`·`FE-OC-002` §4.4 (AuthSessionPort row) | `project-decision` | attach 구현 세부(header supplier vs opaque credential)는 auth owner 결정 — §4.4가 "구현 세부는 auth owner가 정한다"로 유보 | | D3 | 401 recovery는 bounded state machine(`authenticated→recovery-pending→{authenticated\|unauthenticated\|integration-failed}`), logical request당 recovery 콜백 ≤1회, 같은 request의 2nd 401은 terminal `AUTH_REQUIRED` | 외부 owner가 bounded recovery callback을 제공하면 이 machine 사용 / owner가 recovery를 안 하면 첫 401이 곧 terminal(unauthenticated). recovery loop 금지 | [[raw/project-notes/ca-skeleton-frontend-operational-contract]] §7.8 (session state table)·`FE-OC-010`·`FE-OC-006` | `project-decision` | recovery callback의 timeout/bound 세부는 owner 계약에 의존 (`FE-Q-006`) | | D4 | recovery 성공 후 replay: `safe`=같은 context 1회 / `keyed`=stable idempotency key + active backend replay contract일 때만 1회 / `none`(unkeyed)=MUST NOT replay(명시적 재시도 요구) | idempotency mode로 분기 — backend replay contract 없으면 `keyed`도 replay 금지 | [[raw/project-notes/ca-skeleton-frontend-operational-contract]] §7.8 (replay policy)·§7.7 (idempotency)·`FE-D016`·`FE-OC-006` | `project-decision` | backend replay contract 존재 여부 미확정 (`FE-Q-005`); unsafe duplicate write 위험 (`FE-RISK-006` 인접) | | D5 | auth 실패 정규화: `401→AUTH_REQUIRED`, `403→FORBIDDEN`, attach/recovery adapter가 throw/reject/invalid state→`AUTH_INTEGRATION_FAILURE`; raw body·token·principal은 failure·telemetry에 미포함 | 이 3 kind는 stable enum. backend가 다른 auth 상태를 쓰면 error 브랜치가 registry에 추가 후 매핑(재정의 아님) | [[raw/project-notes/ca-skeleton-frontend-operational-contract]] §8.2 (failure matrix 3 auth rows)·§8.5 (negative fixtures)·§5.6 (error enum)·`FE-OC-008` | `project-decision` | error registry 구조는 error 브랜치 owner — 나는 매핑 값만 기여(경계 이탈 주의) | | D6 | token/secret은 browser storage·bundle·env·telemetry·error body에 저장/노출 MUST NOT; `AUTH_TOKEN` storage key는 forbidden(`sensitive-forbidden`), token 저장은 외부 auth owner만 | default off(브라우저 token storage 금지) / auth owner가 browser storage를 반드시 써야 하면 별도 threat model + owner evidence 필요(§6.1), skeleton default 아님 | [[raw/project-notes/ca-skeleton-frontend-operational-contract]] §5.5 (`AUTH_TOKEN` forbidden)·§6.1 (secret config)·§5.8 (telemetry forbiddenAttributes)·§8.1·`FE-OC-019` | `project-decision` | XSS surface 시 token이 브라우저에 없어야 완화(keycloak note P2A 함정); CSP/scan은 browser-security 브랜치 owner | | D7 | session-required route는 `AuthSessionPort` state를 UX hint로만 사용, backend authorization이 최종 권한 판단 — navigation guard는 보안 control이 아님 | route access가 `session-required`/`integration-defined`일 때 hint 적용 / `public`이면 미적용. 최종 authz는 항상 backend(`403→FORBIDDEN`) | [[raw/project-notes/ca-skeleton-frontend-operational-contract]] §9.3 (route behavior)·§7.8 ("guard는 UX hint")·`FE-OC-005`. (react-router `REACT-ROUTER-C3`는 guard 보안 미정당화) | `project-decision` | guard가 security로 오해 (`FE-RISK-005`) → e2e에서 403 처리 확인; route registry/redirect는 routing 브랜치 owner | ## 구현 가이드 > 전 항목 `planned` — frontend 코드 없음. 경로는 hub §4.6 Planned directory blueprint / §5.1 registry owner map에서 도출된 `planned` anchor다. ### 1. `AuthSessionPort` 인터페이스 (application 소유) > **Trace**: D1 + D2 · `FE-OC-010`/`FE-OC-002` · [[raw/project-notes/ca-skeleton-frontend-operational-contract]] §4.4 (Port ownership matrix) · §4.6 (blueprint). > > - **UNSUPPORTED_IMPL_DECISION**: 아래 메서드 명(`getSessionState`/`attach`/`onUnauthenticated`)과 "header-supplier callback" 표현은 내가 임의 선택 — hub §4.4는 "구현 세부는 auth owner가 정한다"로 shape만 유보(opaque state + attach callback, token 문자열 미반환). trade-off: §7.8의 attach·unauthenticated-transition 어휘를 거울 삼아 되묻기를 줄이되, 최종 signature는 auth owner 계약 확정 시 조정. | 항목 | `planned` 값 | 근거 | |---|---|---| | Definition owner | `application` (integration boundary) | §4.4 | | Planned 위치 | `src/application/ports/auth-session-port.js` (정의), `src/adapters/auth/` (구현) | §4.6 | | Consumer | routing (session hint) + API client interceptor(attach) | §4.4 | | Input/Output | opaque session state / request-header attach callback (token 문자열 미반환) | §4.4 | | Failure vocabulary | `AuthRequired`, `AuthIntegrationFailure` | §4.4 | | 주입 시점 | composition-root boot step 6 (auth integration adapter 주입) | §4.5 | ### 2. Bounded 401 recovery state machine > **Trace**: D3 · `FE-OC-006`/`FE-OC-010` · [[raw/project-notes/ca-skeleton-frontend-operational-contract]] §7.8 (session state table). 전부 hub 계약에서 도출. > > - **UNSUPPORTED_IMPL_DECISION**: 없음 — 상태·전이·행동이 §7.8 표에 명시됨. 명시돼 있다는 것은 곧 **owner 가 hub §7.8 이라는 뜻**이므로 표를 여기에 복제하지 않는다. **state machine 본문은 [[raw/project-notes/ca-skeleton-frontend-operational-contract]] §7.8 소유다.** 요약 한 줄: `authenticated` 에서 첫 `401` 이 bounded recovery 를 1회만 트리거하고, 같은 logical request 의 2번째 `401` 은 추가 recovery 없이 terminal `AUTH_REQUIRED` 로 끝난다. 본 브랜치가 소유하는 것은 그 전이를 `AuthSessionPort` 계약으로 내리는 부분이다. ### 3. Recovery replay policy > **Trace**: D4 · `FE-OC-006`/`FE-OC-009` · [[raw/project-notes/ca-skeleton-frontend-operational-contract]] §7.8 (replay policy) · §7.7 (idempotency) · `FE-D016`. hub 계약에서 도출. > > - **UNSUPPORTED_IMPL_DECISION**: 없음. **replay policy 본문은 [[raw/project-notes/ca-skeleton-frontend-operational-contract]] §7.8 소유다.** 요약 한 줄: idempotency mode 별로 recovery 성공 후 replay 는 최대 1회이고 `none`(unkeyed) 는 replay 금지다. replay + 일반 retry의 총 시도는 operation registry·test fixture가 추적하며 recovery loop를 만들 수 없다. ### 4. Auth 실패 정규화 매핑 (error registry 기여) > **Trace**: D5 · `FE-OC-008`(error 브랜치 owner에 기여) · [[raw/project-notes/ca-skeleton-frontend-operational-contract]] §8.2 (failure matrix) · §8.5 (negative fixtures) · §5.6 (error enum). > > - **UNSUPPORTED_IMPL_DECISION**: 없음 — kind/action/telemetry가 §8.2·§5.6에 고정. > - **OUT_OF_BRANCH_SCOPE**: error registry의 스키마·total-function 정규화 뼈대는 [[raw/branch-notes/feature-frontend-error-classification-boundary-contract]] 의 `FE-OC-008` 소유. 나는 아래 3행의 기대 매핑만 제공. | Trigger (본 브랜치가 발생시키는 지점) | 기대 kind | |---|---| | HTTP `401` | `AUTH_REQUIRED` | | HTTP `403` | `FORBIDDEN` | | attach/recovery adapter throw·reject·invalid state | `AUTH_INTEGRATION_FAILURE` | 각 kind 의 retry·action·telemetry 규칙은 [[raw/project-notes/ca-skeleton-frontend-operational-contract]] §8.2 소유이며 여기에 옮겨 적지 않는다. ### 5. Browser-security 불변식 (auth) — `FE-OC-019` 기여 > **Trace**: D6 · `FE-OC-019`(browser-security 브랜치에 기여) · [[raw/project-notes/ca-skeleton-frontend-operational-contract]] §5.5 (`AUTH_TOKEN` forbidden) · §6.1 (secret config) · §5.8 (telemetry forbidden) · §8.1 (failure exclusions). > > - **UNSUPPORTED_IMPL_DECISION**: 없음 — 금지 목록이 hub registry/config에 고정. > - **OUT_OF_BRANCH_SCOPE**: CSP/header/secret-scan lint 메커니즘은 [[raw/branch-notes/feature-frontend-browser-security-boundary-contract]] 의 `FE-OC-019`, storage 스키마는 [[raw/branch-notes/feature-frontend-storage-registry-contract]] 의 `FE-OC-013` 소유. - token/secret을 `localStorage`/`sessionStorage`/bundle/`import.meta.env`/`/config.json`에 저장 금지 (§6.1). - `AUTH_TOKEN` storage key = `forbidden` + `sensitive-forbidden`, 외부 auth owner만 token 저장 (§5.5). - telemetry `forbiddenAttributes`에 token·email·raw URL 포함, auth 이벤트는 route ID만 (§5.8·§8.2). - normalized failure에 raw body·token·authorization header·stack 미포함 (§8.1). ### 6. no-token-lifecycle 강제 (측정 항목 "no token lifecycle import tests") > **Trace**: D1 + D6 · [[raw/project-notes/ca-skeleton-frontend-operational-contract]] §4.2·§4.3 (dependency matrix) — §20 Measurable completion의 "no token lifecycle import tests". > > - **UNSUPPORTED_IMPL_DECISION**: token-lifecycle 심볼 집합(예: `refresh`/`rotate`/`token-store`)과 restricted-import glob은 내 임의 제안 — trade-off: `adapters/auth/` 밖에서 token 저장·회전 심볼 import를 차단하는 최소 룰로 시작하되 오탐 시 auth owner 계약에 맞춰 조정. > - **OUT_OF_BRANCH_SCOPE**: import-lint의 실행 메커니즘(dependency-cruiser/ESLint restricted imports fixture)은 [[raw/branch-notes/feature-frontend-architecture-enforcement-lint-contract]] 의 `FE-OC-002` 소유. 나는 *검사 대상 불변식*만 정의. - 불변식: token 발급·저장·refresh·rotation 심볼은 `src/adapters/auth/` 내부에서만 존재/참조. `domain`/`application`/`presentation`은 이를 import 금지 (§4.3). ### 7. Route session-integration 접점 — `FE-OC-005` 기여 > **Trace**: D7 · `FE-OC-005`(routing 브랜치에 기여) · [[raw/project-notes/ca-skeleton-frontend-operational-contract]] §9.3 (route behavior) · §7.8 · §5.2 (route access enum). > > - **UNSUPPORTED_IMPL_DECISION**: 없음. > - **OUT_OF_BRANCH_SCOPE**: route registry 스키마·`access` 필드·redirect-loop·param validation은 [[raw/branch-notes/feature-routing-navigation-guard-contract]] 의 `FE-OC-005` 소유. 나는 아래 2 규칙만 기여. - `access: session-required`/`integration-defined` route는 `AuthSessionPort` state를 **UX hint**로만 소비 (§9.3·§5.2). - backend authorization result가 최종 권한 판단이며, guard는 이를 대체하지 않는다 (§9.3, `FE-RISK-005`). ## 엣지·실패·의존 - **실패·엣지 경로**: - attach callback이 throw/reject → `AUTH_INTEGRATION_FAILURE`, unauthenticated-safe shell (hub §8.5). - recovery가 invalid state 반환 → `AUTH_INTEGRATION_FAILURE`. - 같은 logical request의 두 번째 `401` → 추가 recovery 없이 terminal `AUTH_REQUIRED` (loop 금지). - `none`(unkeyed) mutation이 recovery 성공 → replay 금지, 명시적 재시도 요구. - recovery 대기 중 navigation/user abort → `REQUEST_ABORTED`, 대기 취소(error event 금지). - `keyed` mutation인데 active backend replay contract 부재 → replay 금지. - **다른 계약 의존** (§20 Dependency + hub §4.3): - [[raw/branch-notes/feature-frontend-clean-architecture-layering-contract]] 의 `FE-OC-002`(application-owned port + dependency rule)에 의존 — §20의 hard dependency. 이 계약이 바뀌면 port 소유 위치가 흔들림. - [[raw/branch-notes/feature-api-client-response-envelope-contract]] 의 `FE-OC-006`(shared client)에 의존 — attach·정규화·bounded state machine이 그 client 안에서 실행. timeout/abort/retry는 그 계약이 소유. - [[raw/branch-notes/feature-frontend-error-classification-boundary-contract]] 의 `FE-OC-008`(error registry)에 의존 — auth kind 3종이 그 registry에 존재해야 매핑 성립. - [[raw/branch-notes/feature-routing-navigation-guard-contract]] 의 `FE-OC-005`(route registry)에 의존 — session-required `access` 필드가 존재해야 hint 접점 성립. - [[raw/branch-notes/feature-frontend-storage-registry-contract]] 의 `FE-OC-013`, [[raw/branch-notes/feature-frontend-browser-security-boundary-contract]] 의 `FE-OC-019`, [[raw/branch-notes/feature-frontend-observability-logging-trace-contract]] 의 `FE-OC-014`에 기여 — 각 `AUTH_TOKEN` forbidden·no-token-bundle·telemetry redaction 불변식. ## 검증해야 할 주장 | Claim | Why uncertain | How to verify | Status | |---|---|---|---| | `AuthSessionPort`가 token 문자열을 domain/application model로 반환하지 않는다 | 구현 코드 없음(`planned`) | port contract test + no-token-lifecycle import test(§4.6 architecture fixture) | `needs-confirmation` | | bounded 401 machine이 recovery loop를 만들지 않는다(request당 recovery ≤1, 2nd 401 terminal) | machine 미구현 | deterministic state-transition test(`ClockPort` + 주입된 401 시퀀스, §7.8) | `needs-confirmation` | | recovery replay가 unkeyed mutation을 replay하지 않는다 | 미구현 | negative fixture: recovery succeeds for unkeyed mutation → no replay (§8.5) | `needs-confirmation` | | attach/recovery adapter fault가 `AUTH_INTEGRATION_FAILURE`로 정규화된다 | 미구현 | negative fixture: attach throw/reject, recovery invalid state (§8.5) | `needs-confirmation` | | `401→AUTH_REQUIRED`·`403→FORBIDDEN` 정규화 + raw body/token 미노출 | 미구현 | error catalog test + redaction assertion(telemetry에 principal/token 없음, §8.2) | `needs-confirmation` | | session-required route hint가 backend authz를 대체하지 않는다 | 미구현 | e2e: guarded route에서 backend `403` 처리 확인 (`FE-RISK-005`) | `needs-confirmation` | | skeleton 어디에도 token이 browser storage/bundle에 저장되지 않는다 | 미구현 | secret scan + storage registry test(`AUTH_TOKEN` forbidden, §5.5·§6.1) | `needs-confirmation` | ## 관심사 커버리지 (coverage-auditor 자동 생성 — 있을 때) > `/coverage`가 채우는 생성물이며 손으로 유지하지 않는다. | 관심사 | 상태 | owner | 심각도 | 근거 | |---|---|---|---|---| | TODO — `/coverage` 실행 전 | missing | (없음) | 미평가 | TODO | ## 마주친 문제 없음 — scaffolding 단계 ## 묶음 (이 branch에서 파생된 자료) ## 가져온 프로젝트 계약 | Ref | Owner | 요약 | Branch 적용 | |---|---|---|---| | `FE-OC-002@1` | [[raw/branch-notes/feature-frontend-clean-architecture-layering-contract]] | `domain <- application <- presentation` 의존 방향과 application-owned output port를 MUST 지킴 | import 참조로 적용 | | `FE-OC-005@1` | [[raw/branch-notes/feature-routing-navigation-guard-contract]] | route ID/path/params/access/loading/error owner는 route registry 하나여야 함 | import 참조로 적용 | | `FE-OC-006@1` | [[raw/branch-notes/feature-api-client-response-envelope-contract]] | 모든 HTTP는 shared client를 MUST 통과하고 timeout·abort·response parsing을 page에서 구현하면 안 됨 | 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-019@1` | [[raw/branch-notes/feature-frontend-browser-security-boundary-contract]] | browser bundle에 secret을 넣지 않고 untrusted HTML injection을 기본 금지 | 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):