Files
llm-wiki/raw/branch-notes/feature-frontend-architecture-enforcement-lint-contract.md
T

313 lines
35 KiB
Markdown

---
title: branch / feature-frontend-architecture-enforcement-lint-contract
source_type: branch-note
status: raw
id: BR-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-003
kind: project-work-item
project: ca-skeleton-frontend-operational-contract
work_item: WI-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-003
inherits: [DEC-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-ARCHITECTURE-001@1]
refines: []
overrides: []
depends_on: [WI-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-002, WI-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-017]
contract_packet: 1
branch: feature-frontend-architecture-enforcement-lint-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, architecture, testing, javascript, static-analysis]
created: 2026-07-18
target_merge:
status_label: in-progress
contract_packet_sha256: 12187592363c519ab92cd3b73e1e4b135b2515f0421bd4c671ca45c7e30b2340
imports: [FE-GATE-013@1, FE-OC-002@1, FE-OC-014@1, FE-OC-019@1, FE-OC-020@1]
delegates: [DELEG-FE-002@1, DELEG-FE-003@1]
---
# branch: feature-frontend-architecture-enforcement-lint-contract
> Layer: `raw/branch-notes/` — 단일 브랜치의 TODO·결정·진행 기록. 머지/종료 후 verified 결과는 `/ingest`로 `wiki/projects/`에 추출한다.
<!-- section-id: branch-parent -->
## 부모 (필수)
- [[raw/project-notes/ca-skeleton-frontend-operational-contract]]
형제 branch (같은 부모, 본 branch 가 의존/위임하는 대상):
- [[raw/branch-notes/feature-frontend-clean-architecture-layering-contract]] — 본 branch 가 *기계화*할 allowed-import matrix 의 정의 owner (`FE-OC-002`)
- [[raw/branch-notes/feature-frontend-test-taxonomy-contract]] — gate 등록·artifact 보존·"실패→warning 금지" 정책 owner (`FE-OC-020`)
- [[raw/branch-notes/feature-frontend-project-bootstrap-toolchain-contract]] — ESLint·dependency-cruiser 의 *설치* + base flat-config substrate owner (`FE-OC-003`). 본 branch 의 D1 은 `eslint.config.js`·`.dependency-cruiser.cjs` 가 이미 존재함을 전제하고 거기에 **규칙만 추가**한다.
<!-- GENERATED: branch-contract:start -->
<!-- section-id: branch-contract-packet -->
## 브랜치 계약 패킷
- **생성 시 프로젝트 개정**: `1`
- **패킷 스키마**: `contract_packet: 1`
- **완료 조건**: allowed fixture는 통과하고 forbidden fixture는 실패하며 lint report가 생성된다
<!-- section-id: inherited-project-decisions -->
### 상속한 프로젝트 결정
| Decision Ref | Project Summary | Branch Application | Source |
|---|---|---|---|
| `DEC-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-ARCHITECTURE-001@1` | domain, application, presentation, adapters, bootstrap 책임을 분리한다 | allowed-import matrix의 lint·dependency graph 규칙에 적용 | [[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 -->
## 목표
이 branch 는 `FE-OC-002`(의존 방향 `domain <- application <- presentation` 과 application-owned output port 를 MUST 유지)와 `FE-OC-020`(gate 별 책임·fixture·artifact 분리, 실패를 warning 으로 낮추지 않음)를 **구현 착수 가능한 강제(enforcement) 명세로 내리는** 브랜치다. 본 branch 는 자체 소유 contract 가 없다(§20 `Primary contract IDs = —`) — 대신 hub §4.3 dependency matrix 를 기계 검증 가능하게 만드는 **architecture gate (`FE-GATE-010`)** 을 build 한다: dependency-cruiser 그래프 규칙 + ESLint restricted-import 규칙 + allowed/forbidden fixture + `artifacts/quality/` 로의 dependency report 산출. 즉 layering branch 가 *정의*한 경계를 이 branch 가 *자동으로 집행*하고, test-taxonomy/CI branch 가 소비할 evidence artifact 를 emit 한다. 현재 frontend 코드는 존재하지 않으므로 아래 모든 구현 주장은 등급 `planned` 이다.
- 이슈:
- PR:
<!-- section-id: branch-scope -->
## 범위
### 포함 범위
- dependency-cruiser 설정 — hub §4.3 dependency matrix 를 그래프 reachability 규칙으로 encoding (transitive/indirect 위반 포착) — 등급: `planned`
- ESLint flat-config restricted-import 규칙 — 동일 matrix 를 import-statement(module) 레벨로 encoding — 등급: `planned`
- allowed + forbidden fixture set — `presentation → adapters/http`, 직접 TanStack Query client import, `application → adapter 구체`, `domain → React/browser global` 등 — 등급: `planned`
- **`test fixtures` 행(hub §4.3 row 6)의 import 경계 규칙 + 짝 fixture** — `tests/**` 는 public contract + 명시 test helper 만 import 가능, production secret 모듈·real telemetry endpoint 설정 import 는 fail (D6; 지금까지 owner 미지정이던 행) — 등급: `planned`
- dependency/enforcement **report artifact**`artifacts/quality/` 로 emit + 위반 시 non-zero exit(warning 강등 금지) — 등급: `planned`
- gate pass 조건: allowed fixture pass · forbidden fixture fail · report emitted (`FE-GATE-010` — §20 Measurable completion) — 등급: `planned`
### 제외 범위
> 의도적으로 제외 — 다른 owner branch 가 소유. 여기서 detail 을 정하지 않고 그 branch 를 가리킨다(CLAUDE.md §15.5 R3, `OUT_OF_BRANCH_SCOPE`).
- **allowed-import matrix 의 *정의* 자체 + layer/port 책임 분해** → [[raw/branch-notes/feature-frontend-clean-architecture-layering-contract]] 소유 (`FE-OC-002`). 정의 근거 결정은 [[raw/project-notes/ca-skeleton-frontend-operational-contract]] §3.2 `FE-D009`~`FE-D011`. 본 branch 는 그 matrix 를 *기계화*할 뿐 정의하지 않는다.
- **gate 정의(blocking scope·pass condition·evidence artifact)** → hub §15.1 소유, gate 별 Owner 는 hub §2.1.1. **test level 슬롯 · artifact 보존 정책 · "실패→warning 금지" 정책** → [[raw/branch-notes/feature-frontend-test-taxonomy-contract]] 소유 (`FE-OC-020`). **CI wiring** → [[raw/branch-notes/feature-frontend-ci-quality-gates-contract]] 가 orchestration.
- **forbidden-API(browser-global) lint** (`window`/`localStorage`/`fetch` 직접 사용 금지 — cross-layer import 금지와 별개) → [[raw/branch-notes/feature-frontend-browser-security-boundary-contract]] 소유 (`FE-OC-019``FE-GATE-002` lint 의 forbidden-API 부분). 본 branch 는 forbidden-**import**/layer 부분만.
- **checkJs/type 강제** (`FE-GATE-003`) → [[raw/branch-notes/feature-frontend-project-bootstrap-toolchain-contract]] 소유 (`FE-OC-003`).
- **ESLint / dependency-cruiser 의 *설치* 와 base flat-config substrate** (`eslint.config.js`·`.dependency-cruiser.cjs` 파일 자체의 존재·engine·script wiring) → [[raw/branch-notes/feature-frontend-project-bootstrap-toolchain-contract]] 소유 (`FE-OC-003`). 본 branch 는 그 config 에 **규칙을 추가**할 뿐 toolchain 을 세우지 않는다.
- **QueryCachePort 설계** (`FE-D006`) → [[raw/branch-notes/feature-server-state-caching-contract]]. 본 branch 는 TanStack import 경계만 강제.
## 근거 (필수, 최소 1개+)
| Source | 정당화하는 결정 |
|---|---|
| [[raw/project-notes/ca-skeleton-frontend-operational-contract]] | §4.3 dependency matrix + §15.1 `FE-GATE-010` + §15.2 negative fixture — 본 branch 강제 명세의 SSOT (D1·D2·D3·D5) |
| [[raw/project-notes/ca-skeleton-operational-contract]] | backend Clean-Architecture 운영 계약 철학(왜 port 를 application 이 소유하고 adapter 가 구현하는가, 왜 layer 를 강제하는가) — `FE-D009` 의 rationale, D2 가 기계화하는 대상 |
| [[raw/official-docs/tanstack-query-server-state-official]] | `TSQ-C1`(server-state 전용 라이브러리로 자기 정의) — D4 의 "직접 TanStack import 금지" fixture 근거(TanStack 은 `QueryCachePort` 뒤에 격리) |
| [[raw/official-docs/vite-build-tool-official]] | `VITE-C1`(native ES modules 위에서 동작) — dependency-cruiser·ESLint 가 분석하는 ESM import 그래프의 substrate(`FE-D002` JS ESM / `FE-D003` Vite baseline) — D1 도구 적용 가능성의 맥락 근거 |
## TODO
- [ ] dependency-cruiser 설정으로 §4.3 matrix + forbidden-edge 규칙 encoding — 등급: `planned`
- [ ] ESLint flat-config restricted-import 규칙을 matrix 와 1:1 mirror — 등급: `planned`
- [ ] allowed + forbidden fixture suite 작성 (`presentation→adapters/http`, 직접 TanStack import, `application→adapter 구체`, `domain→React`) — 등급: `planned`
- [ ] dependency/enforcement report 를 `artifacts/quality/` 로 emit + 위반 시 non-zero exit wiring — 등급: `planned`
- [ ] `test fixtures` 행(D6) 규칙 encoding + allowed/forbidden fixture 쌍 작성 — 등급: `planned`
- [ ] `FE-OC-019`(production secret 목록)·`FE-OC-014`(real telemetry endpoint 목록) owner 에게 금지 대상 모듈 목록 발행 요청 — 미발행 동안 D6 fixture 는 placeholder — 등급: `planned`
- [ ] 규칙 catalog 를 layering branch 의 allowed-import matrix 와 cross-check(drift 방지) — 등급: `planned`
## 진행 중 메모
없음 — `/branch-spec` 채움 단계. 모든 항목 `planned`(frontend repo 미생성).
## 결정 사항
> 각 결정의 근거·대안은 아래 Decision Evidence Map 과 1:1. 여기 prose 는 그 요약이다.
- 2026-07-18: **이중 도구 강제(dependency-cruiser 그래프 + ESLint restricted-import), 둘 다 merge-blocking** / 이유: import-statement 레벨(빠름·에디터 내)과 whole-graph reachability(transitive/barrel re-export 포착)를 함께 커버 / 대안: 단일 도구 / 근거: hub §4.3 "Planned enforcement" 열이 두 도구를 명시, `FE-OC-002`. (D1)
- 2026-07-18: **§4.3 dependency matrix 를 규칙의 single source-of-truth 로 강제** (`domain ← application ← presentation`; adapter 는 application port 구현; bootstrap 만 composition root) / 이유: `FE-OC-002` owner 가 정의한 경계를 코드로 집행 / 대안: N/A(matrix 는 layering branch 소유) / 근거: hub §4.3 + §3.2 결정. (D2)
- 2026-07-18: **forbidden fixture 는 반드시 fail, allowed fixture 는 반드시 pass — 실행된 실패 fixture 없는 규칙은 증거 불충분** / 이유: gate 가 실제로 동작함을 증명하려면 deliberately failing fixture 필요 / 대안: rule 존재만 확인 / 근거: hub §15.2 + §15.1 `FE-GATE-010` pass 조건. (D3)
- 2026-07-18: **"직접 TanStack Query client import" forbidden fixture — `adapters/query-cache` 만 TanStack import 허용, presentation/application 직접 import 은 fail** / 이유: `QueryCachePort`(application-owned) 뒤로 TanStack 격리 / 대안: 전역 허용 / 근거: hub §15.1 `FE-GATE-010`("including direct TanStack client import") + §3.2 결정 + `TSQ-C1`. (D4)
- 2026-07-18: **machine-readable dependency/enforcement report 를 `artifacts/quality/` 로 emit, 위반은 warning 으로 강등 금지** / 이유: gate 가 "실행됐다" 인정받으려면 evidence artifact 필요 / 대안: 콘솔 출력만 / 근거: hub §15.1 `FE-GATE-010` evidence artifact + §4.6 blueprint + `FE-OC-020`. (D5)
- 2026-07-20: **hub §4.3 `test fixtures` 행(6번째)의 import 경계 규칙 + 짝 fixture 를 본 branch 가 소유**`tests/**` 는 public contract + 명시 test helper 만 import 가능, production secret 모듈·real telemetry endpoint 설정 import 는 fail / 이유: §4.3 matrix 의 한 행이고 그 matrix 기계화가 본 branch 책임(`FE-GATE-010`)인데 지금까지 어떤 branch 도 owner 로 잡지 않아 owner-less 였음 / 대안: browser-security(`FE-OC-019`) 또는 observability(`FE-OC-014`)에 전부 위임 — 그러나 두 branch 는 *무엇이 secret/endpoint 인가* 를 정의할 뿐 import 그래프 규칙을 집행하지 않으므로 부적합 / 근거: hub §4.3 row 6 (`test config guard`) + `FE-OC-002`. (D6)
## 결정-근거 매핑
> 각 결정과 raw source Claim ID 의 연결. `Decision ID` 는 이 branch-note 안에서 안정적으로 유지한다.
> `Supporting Claims`: hub 결정(project decision)은 `[[hub]] §·FE-D` 로, 외부 스펙은 `raw/official-docs/<slug>.md#<CLAIM>` 로 가리킨다. (`FE-D*` 는 hub §3.2 소유 — 본 branch 는 그 결정을 *기계화*한다.)
| Decision ID | Decision | 선택 조건 (언제 이 결정 / 언제 대안) | Supporting Claims | Evidence Strength | Open Risk |
|---|---|---|---|---|---|
| D1 | 이중 도구 강제: dependency-cruiser(그래프 reachability) + ESLint restricted-import(module 레벨), 둘 다 merge-blocking (`FE-GATE-010`/`FE-GATE-002``FE-OC-002`) | **이 결정:** 경계를 import-statement 레벨 *과* whole-graph 레벨 *양쪽*에서 강제해야 할 때(transitive/indirect 위반은 ESLint 단독으로 못 잡음). **대안(단일 도구):** 한 도구가 완전히 redundant 임이 fixture 로 증명될 때 → revisit | [[raw/project-notes/ca-skeleton-frontend-operational-contract]] §4.3 "Planned enforcement" 열 + §15.1 `FE-GATE-010`·`FE-GATE-002`; `raw/official-docs/vite-build-tool-official.md#VITE-C1` (ESM 그래프 substrate) | `project-decision` (+contextual official-doc) | hub 는 *도구* 만 명시, 정확한 rule config 는 미명시 → 규칙 상세는 `UNSUPPORTED_IMPL_DECISION` |
| D2 | §4.3 dependency matrix 를 규칙의 SSOT 로 강제 (`domain ← application ← presentation`; adapter 는 application port 구현; bootstrap 만 composition root) | **N/A** — matrix 는 `FE-OC-002` owner(layering branch)가 고정. 본 branch 는 기계화만. layer taxonomy 가 바뀌면(FSD fork 승인) 규칙 재생성 | [[raw/project-notes/ca-skeleton-frontend-operational-contract]] §4.3 dependency matrix + §4.2 responsibility + §3.2 `FE-D009`·`FE-D010`·`FE-D011`; 철학 근거 [[raw/project-notes/ca-skeleton-operational-contract]] | `project-decision` (delegated from layering branch) | layering branch 의 concrete allowed-import matrix 발행에 의존 — 그것이 바뀌면 규칙 drift (§엣지·의존 참조) |
| D3 | forbidden fixture 는 MUST fail, allowed fixture 는 MUST pass — 실행된 실패 fixture 없는 규칙은 증거 불충분 | **N/A(invariant)** — canonical negative fixture = `presentation` imports `adapters/http` (§15.2). rule 존재만 확인한 결과는 `locally-verified` 증거로 불충분 | [[raw/project-notes/ca-skeleton-frontend-operational-contract]] §15.2 ("최소 하나의 deliberately failing fixture 필요") + §15.1 `FE-GATE-010` pass 조건("allowed passes, forbidden fails") + §20 Measurable completion | `project-decision` (hub §15.1/§15.2) | fixture set 이 rule set 과 동기 유지돼야 함 — 짝 fixture 없이 rule 추가 시 gate 조용히 degrade |
| D4 | "직접 TanStack Query client import" forbidden fixture: `adapters/query-cache` 만 import 허용, presentation/application 직접 import 은 fail | **이 결정:** `QueryCachePort` 뒤에 TanStack 을 격리하는 동안 유지. **대안:** 그 경계 결정 변경(offline-first normalized cache) 시 재검토 | [[raw/project-notes/ca-skeleton-frontend-operational-contract]] §15.1 `FE-GATE-010` ("including direct TanStack client import") + §3.2 `FE-D006`; `raw/official-docs/tanstack-query-server-state-official.md#TSQ-C1` | `official-doc` (`TSQ-C1`) + `project-decision` (`FE-D006`) | 금지할 정확한 import specifier(`@tanstack/react-query`)는 hub 미명시 → `UNSUPPORTED_IMPL_DECISION` |
| D5 | machine-readable dependency/enforcement report 를 `artifacts/quality/` 로 emit, 위반은 warning 강등 금지, blocking scope=merge | **N/A** — artifact 없으면 gate 가 "실행됨" 으로 인정 안 됨. report format/보존은 test-taxonomy branch(`FE-OC-020`)에 위임 | [[raw/project-notes/ca-skeleton-frontend-operational-contract]] §15.1 `FE-GATE-010` evidence artifact("dependency report") + §4.6 blueprint(`artifacts/quality/`) + §2.1 `FE-OC-020` ("실패를 warning 으로 낮추면 안 됨") | `project-decision` (hub §15.1 + `FE-OC-020`) | 정확한 report filename/format 은 `UNSUPPORTED_IMPL_DECISION`; 보존 정책은 test-taxonomy/CI branch 소유 |
| D6 | hub §4.3 `test fixtures` 행의 import 경계 규칙 + 짝 fixture 를 본 branch 가 소유: `tests/**` 는 public contract + 명시 test helper 만 import 가능, production secret 모듈·real telemetry endpoint 설정 import 는 MUST fail (`test config guard``FE-GATE-010`) | **이 결정:** §4.3 matrix 의 행이고 집행 수단이 import 그래프 규칙인 동안(= 정적 분석으로 판정 가능한 동안) 본 branch 소유. **대안(위임):** 집행이 런타임 값 검사나 secret scanning 으로 바뀌면 `FE-GATE-013` security gate 소유로 이관 → revisit | [[raw/project-notes/ca-skeleton-frontend-operational-contract]] §4.3 dependency matrix row 6(`test fixtures` \| allowed: public contracts and explicit test helpers \| forbidden: production secret, real telemetry endpoint \| enforcement: `test config guard`) + §15.1 `FE-GATE-010`(forbidden import fixtures) + §2.1 `FE-OC-002` | `project-decision` (hub §4.3 row 6) | *무엇이* production secret / real telemetry endpoint 인가의 목록은 `FE-OC-019`·`FE-OC-014` owner 미발행 → 발행 전까지 fixture 대상 모듈이 placeholder. 식별 메커니즘(경로 기반)은 `UNSUPPORTED_IMPL_DECISION` |
## 구현 가이드
> 전부 `planned` blueprint — frontend repo 미생성. 경로는 hub §4.6 Planned directory blueprint + §5.1 registry owner map 에서 유래(grounded)하나 코드는 없다. 3-rule(R1 Trace / R2 UNSUPPORTED_IMPL_DECISION / R3 no OUT_OF_BRANCH_SCOPE) 준수.
### 1. 강제 도구 wiring (dependency-cruiser + ESLint)
> **Trace**: D1 (hub §4.3 "Planned enforcement", `FE-OC-002`) + D2.
>
> - **UNSUPPORTED_IMPL_DECISION**:
> - dependency-cruiser 설정 파일명/형식(`.dependency-cruiser.cjs` 가정) — hub 는 *도구* 만 명시, 파일명 미권고. Trade-off: `.cjs` 는 dependency-cruiser `--init` 의 문서화된 기본 출력.
> - ESLint 규칙 선택(`import/no-restricted-paths`(eslint-plugin-import) vs 빌트인 `no-restricted-imports`) — hub 미권고. Trade-off: `import/no-restricted-paths` 가 zone→zone 금지를 직접 표현해 matrix 대응이 명확; `no-restricted-imports` 는 빌트인이나 pattern 기반. 둘 다 동일 matrix 를 encoding — 최종 선택은 first-impl 로 유예.
| 도구 | 역할(무엇을 잡나) | planned 위치 | 근거 |
|---|---|---|---|
| dependency-cruiser | whole-graph reachability — transitive/indirect/barrel re-export 를 통한 layer 위반 | `.dependency-cruiser.cjs` (repo root) | hub §4.3 "Planned enforcement" 열 |
| ESLint (flat config) | import-statement 레벨 즉시 위반 + 에디터 피드백 | `eslint.config.js` restricted-import 블록 | hub §4.3; `FE-GATE-002` lint |
### 2. Layer boundary 규칙 catalog (matrix 의 기계화)
> **Trace**: D2 (hub §4.3 dependency matrix; §3.2 결정 `FE-D009`·`FE-D010`·`FE-D011`; `FE-OC-002`) + D6 (hub §4.3 `test fixtures` 행).
>
> - **UNSUPPORTED_IMPL_DECISION**: glob 경로 패턴(`src/domain/**` 등)의 정확한 문법 — §4.6 blueprint 는 디렉토리 *이름* 만 주고 glob 은 미명시. Trade-off: blueprint 디렉토리명을 그대로 `src/<layer>/**` glob 으로 승격(가장 단순한 1:1 매핑).
> - **UNSUPPORTED_IMPL_DECISION**: `test fixtures` 행의 glob(`tests/**`) — §4.6 blueprint 는 `tests/{unit,component,integration,e2e}` 만 주고 fixture glob 을 미명시. Trade-off: blueprint 의 `tests/` 루트를 그대로 승격해 4개 레벨을 한 번에 덮음(레벨별 분기 없이 가장 단순).
**집행 유형** 열은 hub §4.3 `Planned enforcement` 열의 각 항목이 *자동 규칙*(gate 가 exit code 로 판정)인지 *수동/자동화 밖*(사람 리뷰)인지 구분한다 — hub 는 두 종류를 한 열에 섞어 적고 구분하지 않으므로, `FE-GATE-010` 의 forbidden-fixture 범위가 어디까지인지 여기서 명시한다.
| From (source) | MUST NOT import (금지 대상) | 집행 도구(§4.3) | 집행 유형 | `FE-GATE-010` fixture 범위 | planned glob |
|---|---|---|---|---|---|
| `domain` | application, presentation, adapters, bootstrap, React, browser globals | dependency-cruiser + ESLint restricted imports | **자동 규칙** | 포함 | `src/domain/**` |
| `application` | presentation, adapters 구체, bootstrap, React, `window`/`localStorage`/`fetch` | architecture fixture | **자동 규칙** | 포함 | `src/application/**` |
| `presentation` | adapters, raw DTO schema, registry storage 구현 | restricted import rule | **자동 규칙** | 포함 | `src/presentation/**` |
| `adapters/*` | presentation, bootstrap internals, 다른 adapter 구체 구현 | dependency graph snapshot | **자동 규칙** | 포함 | `src/adapters/**` |
| `bootstrap` | page-specific business rule | composition-root review | **수동 / 자동화 밖** | **제외** (아래 주석) | `src/bootstrap/**` |
| `test fixtures` | production secret, real telemetry endpoint (허용: public contract + 명시 test helper) | test config guard | **자동 규칙** (import 경계 부분만) | 포함 (D6) | `tests/**` |
> **`bootstrap` 행이 `FE-GATE-010` forbidden-fixture 범위 밖인 이유**: hub §4.3 이 이 행에만 `composition-root review`(사람 리뷰)를 배정했고, 금지 대상이 "page-specific business rule" 이라는 *의미론적* 판정이라 import specifier 로 표현되지 않는다 — 어떤 모듈을 import 했는가가 아니라 그 모듈 안에 무엇을 썼는가의 문제다. 따라서 짝 forbidden fixture 를 만들 수 없고, D3 의 "모든 규칙은 짝 fixture 필요" 불변식은 이 행에 적용되지 않는다. `FE-GATE-010` pass 조건은 나머지 5개 행으로만 판정한다. **UNSUPPORTED_IMPL_DECISION**: bootstrap 행을 자동 gate 에서 제외한 이 판단 자체 — hub 는 "composition-root review" 라고만 적고 gate 범위 포함/제외를 명시하지 않는다. Trade-off: 기계 판정 불가한 행을 gate 에 넣으면 gate 가 항상 vacuous pass 가 되어 D3 증거 기준이 무의미해지므로, 명시적으로 제외하고 수동 리뷰 항목으로 남긴다. bootstrap 의 business-rule 혼입은 코드 리뷰 체크리스트로 다루며, 그 체크리스트 소유는 [[raw/branch-notes/feature-frontend-clean-architecture-layering-contract]] (`FE-OC-002`).
> `application → adapter 구체` 는 `MUST NOT`; output port 정의는 `application` 이 `MUST` 소유; adapter 는 application 이름을 알면 안 됨 (hub §4.3 normative summary — D2).
> `application` 의 browser-global 직접 사용(`window`/`localStorage`/`fetch`) 금지 중 **browser-API 표면 자체의 금지 규칙 카탈로그**는 `FE-OC-019` 소유 → 여기선 layer-cross import 관점만, API 표면 detail 은 [[raw/branch-notes/feature-frontend-browser-security-boundary-contract]] 로 위임(R3).
>
> **`test fixtures` 행 (D6) — 본 branch 가 소유**: hub §4.3 의 6번째 행은 지금까지 어떤 branch 도 owner 로 잡지 않았다. 이 행은 §4.3 dependency matrix 의 일부이고 그 matrix 의 *기계화* 가 본 branch 의 정의된 책임(`FE-GATE-010`)이므로, **test 코드에서의 import 경계 규칙 + 짝 fixture 는 본 branch 가 소유**한다. 규칙: `tests/**` 는 public contract(`src/contracts/**`)와 명시 test helper 만 import 할 수 있고, production secret 모듈과 real telemetry endpoint 설정은 import 할 수 없다. 즉 다른 layer 행과 동일한 종류의 forbidden-import 규칙으로 encoding 되며 `FE-GATE-010` 의 allowed/forbidden fixture 쌍을 갖는다.
> - **UNSUPPORTED_IMPL_DECISION**: "production secret" 을 test config 에서 *어떻게 식별* 하는가(모듈 경로 기반 vs 환경변수 이름 패턴 vs secret registry 조회) — hub §4.3 은 금지 *대상* 만 적고 식별 메커니즘을 권고하지 않는다. Trade-off: 본 branch 는 정적 import 그래프만 볼 수 있으므로 **모듈 경로 기반**(secret 을 노출하는 모듈로 향하는 import edge 금지)으로 좁힌다 — 런타임 값 검사는 정적 분석 밖이고 `FE-GATE-013` security scan 영역이다.
> - **UNSUPPORTED_IMPL_DECISION**: rule id / 규칙 이름 — hub 미명시. Trade-off: §2 의 다른 5개 행과 같은 rule 계열(zone→zone 금지)로 표현해 catalog 일관성을 유지하고, 별도 rule 계열을 만들지 않는다.
> - **위임(reference-only)**: *무엇이* production secret 인가의 정의(어떤 값·어떤 모듈이 secret 인가)는 [[raw/branch-notes/feature-frontend-browser-security-boundary-contract]] (`FE-OC-019`, browser bundle 에 secret 금지) 소유이고, *무엇이* real telemetry endpoint 인가는 [[raw/branch-notes/feature-frontend-observability-logging-trace-contract]] (`FE-OC-014`) 소유다. 본 branch 는 그 두 owner 가 발행하는 목록을 **입력으로 받아 import 금지 규칙으로 encoding** 할 뿐 정의하지 않는다(R3). 두 목록 중 하나라도 미발행이면 본 행의 fixture 는 placeholder 대상 모듈로만 검증되고 상태는 `needs-confirmation` 이다.
### 3. Fixture set (allowed + forbidden)
> **Trace**: D3 (hub §15.2 + §15.1 `FE-GATE-010`) + D4 (hub §15.1 + `FE-D006` + `TSQ-C1`) + D6 (hub §4.3 `test fixtures` 행).
>
> - **UNSUPPORTED_IMPL_DECISION**: fixture 파일 배치(`tests/architecture/fixtures/…` 가정) — hub §4.6 는 `tests/` 하위 레벨(unit/component/integration/e2e)만 주고 architecture-fixture subfolder 미명시. Trade-off: `tests/` 아래 전용 architecture 서브트리로 colocate(다른 gate fixture 와 동일 관례).
> - **UNSUPPORTED_IMPL_DECISION**: 금지할 TanStack import specifier(`@tanstack/react-query`) — hub 는 "direct TanStack client import" 라고만 표현, 패키지명 미명시. Trade-off: TanStack Query 의 표준 React 엔트리 패키지명을 사용, 확정은 first-impl.
| Fixture | 종류 | 기대 결과 | 근거 |
|---|---|---|---|
| `presentation` imports `adapters/http` | forbidden | MUST fail | hub §15.2 canonical negative fixture |
| presentation/application imports `@tanstack/react-query` 직접 | forbidden | MUST fail | hub §15.1 `FE-GATE-010`; `FE-D006`; `TSQ-C1` |
| `application` imports adapter 구체 | forbidden | MUST fail | hub §4.3 normative summary |
| `domain` imports React/browser global | forbidden | MUST fail | hub §4.2/§4.3 |
| `presentation` imports application facade | allowed | MUST pass | hub §4.3 (presentation → application facade) |
| `adapters/query-cache` imports `@tanstack/react-query` | allowed | MUST pass | hub §4.2 (`adapters/query-cache` consumes TanStack Query) |
| test fixture imports public contract + 명시 test helper | allowed | MUST pass (false-positive 방지) | hub §4.3 `test fixtures` 행 (D6) |
| test helper imports production secret 모듈 | forbidden | MUST fail | hub §4.3 `test fixtures` 행 forbidden 열 (D6); secret 목록 소유 `FE-OC-019` |
| test helper imports real telemetry endpoint 설정 | forbidden | MUST fail | hub §4.3 `test fixtures` 행 forbidden 열 (D6); endpoint 목록 소유 `FE-OC-014` |
> gate 를 CI 에 배선하고 artifact 를 보존하는 workflow(YAML/retention)는 본 branch 범위 밖 → [[raw/branch-notes/feature-frontend-test-taxonomy-contract]] + [[raw/branch-notes/feature-frontend-ci-quality-gates-contract]] (R3).
### 4. Report artifact 산출 + 위반 시 exit 정책
> **Trace**: D5 (hub §15.1 `FE-GATE-010` evidence "dependency report" + §4.6 `artifacts/quality/` + `FE-OC-020`).
>
> - **UNSUPPORTED_IMPL_DECISION**: report 파일명/형식(`json` vs `html`/`dot`) — hub 미명시. Trade-off: gate 파싱용 machine-readable(`json`) 을 primary 로, 선택적 `dot`/`svg` 를 human review 용으로 병행.
- dependency-cruiser 가 그래프 report 를 `artifacts/quality/` 로 emit(§4.6 blueprint).
- forbidden fixture 가 pass 하거나 allowed fixture 가 fail 하면 **non-zero exit** → hub §15.1 `FE-GATE-010@1` 의 pass 조건에 매핑(조건 원문은 §15.1 소유). warning 강등 금지(`FE-OC-020`).
- report 형식/보존 기간의 최종 계약은 test-taxonomy branch(`FE-OC-020`)에 위임(R3).
## 엣지·실패·의존
- **실패·엣지 경로**:
- *Rule false-negative (transitive/barrel):* `presentation → shared/index.js → adapters/http` 처럼 barrel re-export 로 우회하면 ESLint 단독은 놓칠 수 있음 → dependency-cruiser 그래프가 잡아야 함(이것이 D1 이중 도구의 이유). 검증 필요.
- *Rule false-positive:* test helper / shared UI primitive 가 layer 를 가로질러 import 하는 정당 케이스 → §4.3 `test fixtures` 행(public contract + 명시 helper 허용)으로 scope-out 필요. over-match 시 정상 코드 block. 이 행의 allowed/forbidden 규칙은 D6 으로 본 branch 가 소유한다.
- *정적 분석 한계:* `import()` 동적 import 로 우회하면 두 도구 모두 정적 그래프에서 못 볼 수 있음 → 잔여 위험으로 기록, `needs-confirmation`.
- *규칙-fixture 비동기:* rule 추가 시 짝 forbidden fixture 미추가 → gate 가 조용히 약화(D3 Open Risk).
- **다른 계약 의존**:
- [[raw/branch-notes/feature-frontend-clean-architecture-layering-contract]] 의 allowed-import matrix(`FE-OC-002`)에 의존 — 그 matrix 가 본 branch 규칙의 입력. 바뀌면 규칙 재생성(D2).
- hub §15.1·§2.1.1 의 `FE-GATE-010@1` 정의(Owner = 본 branch)와 [[raw/branch-notes/feature-frontend-test-taxonomy-contract]] 의 test level 슬롯 + artifact 보존 + "실패→warning 금지" 정책(`FE-OC-020`)에 의존 — report 소비처.
- [[raw/branch-notes/feature-frontend-project-bootstrap-toolchain-contract]] 의 toolchain 설치 + base flat-config(`FE-OC-003`)에 의존 — D1 은 `eslint.config.js`·`.dependency-cruiser.cjs` 와 그 실행 script 가 *이미 존재*함을 전제하고 규칙만 추가한다. 그 branch 가 lint runner/flat-config 형식(또는 package manager script 이름)을 바꾸면 본 branch 의 규칙 블록 배치·실행 진입점이 함께 바뀐다.
- [[raw/branch-notes/feature-server-state-caching-contract]] (QueryCachePort 경계 owner)에 의존 — hub [[raw/project-notes/ca-skeleton-frontend-operational-contract]] §3.2 `FE-D006` 이 D4 TanStack import 금지 fixture 의 근거. 그 결정 변경 시 fixture 재정의.
- hub §4.6 Planned directory blueprint 에 의존 — glob 경로가 디렉토리 layout 을 전제. layout 변경 시 glob 갱신(`FE-D009` 변경 절차).
## 검증해야 할 주장
| Claim | Why uncertain | How to verify | Status |
|---|---|---|---|
| dependency-cruiser + ESLint 가 §4.3 모든 forbidden edge 를 함께 포착 | 도구별 blind spot(동적 import, barrel re-export) | forbidden fixture(직접+transitive+dynamic-import 케이스) 실행 → 각각 fail 확인 (`FE-GATE-010` "forbidden fails") | `needs-confirmation` |
| allowed fixture 가 false-positive 0 으로 pass | 규칙이 test helper/shared primitive 를 over-match 할 수 있음 | allowed fixture(presentation→facade, adapter→TanStack, test-helper cross-import) 실행 → pass 확인 | `needs-confirmation` |
| 직접 TanStack import 금지가 presentation/application 에서만 발화, `adapters/query-cache` 는 예외 | 패키지명 기반 금지는 mis-scope 위험 | forbidden: presentation imports `@tanstack/react-query` → fail; allowed: `adapters/query-cache` import → pass | `needs-confirmation` |
| report artifact 가 `artifacts/quality/` 로 emit 되고 위반 시 gate 가 fail(warning 강등 없음) | artifact wiring + CI exit code 미검증 | seeded 위반으로 gate 실행 → non-zero exit + report 파일 존재 확인 | `needs-confirmation` |
| `tests/**` 가 production secret 모듈·real telemetry endpoint 설정을 import 하면 gate 가 fail (D6) | 금지 대상 모듈 목록이 `FE-OC-019`·`FE-OC-014` owner 미발행 상태 — 현재는 placeholder 경로로만 규칙 표현 가능 | 두 owner 발행 후 실제 경로로 forbidden fixture 실행 → fail 확인; allowed(public contract + test helper) fixture → pass 확인 | `needs-confirmation` |
| 규칙 catalog 가 layering branch allowed-import matrix 와 동기 유지 | matrix 가 외부 소유라 drift 가능 | 변경마다 규칙 catalog vs `FE-OC-002` owner 발행 matrix cross-check | `needs-confirmation` |
## 관심사 커버리지 (coverage-auditor 자동 생성 — 있을 때)
> `/coverage`가 채우는 생성물이며 손으로 유지하지 않는다.
| 관심사 | 상태 | owner | 심각도 | 근거 |
|---|---|---|---|---|
| TODO — `/coverage` 실행 전 | missing | (없음) | 미평가 | TODO |
## 마주친 문제
없음 — `/branch-spec` 채움 단계.
## 묶음 (이 branch에서 파생된 자료)
<!-- GENERATED: project-contract-imports:start -->
## 가져온 프로젝트 계약
| Ref | Owner | 요약 | Branch 적용 |
|---|---|---|---|
| `FE-GATE-013@1` | [[raw/branch-notes/feature-frontend-build-bundle-supply-chain-contract]] | secret·vulnerability·license·dependency review 정책 위반이면 merge·release 를 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-014@1` | [[raw/branch-notes/feature-frontend-observability-logging-trace-contract]] | telemetry는 best-effort이며 render·API success를 차단하면 안 되고 PII·token을 전송하면 안 됨 | import 참조로 적용 |
| `FE-OC-019@1` | [[raw/branch-notes/feature-frontend-browser-security-boundary-contract]] | browser bundle에 secret을 넣지 않고 untrusted HTML injection을 기본 금지 | import 참조로 적용 |
| `FE-OC-020@1` | [[raw/branch-notes/feature-frontend-test-taxonomy-contract]] | gate 종류별 책임·fixture·artifact를 분리하고 실패를 warning으로 낮추면 안 됨 | 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):