Files
llm-wiki/raw/branch-notes/feature-frontend-project-bootstrap-toolchain-contract.md
T

308 lines
31 KiB
Markdown

---
title: branch / feature-frontend-project-bootstrap-toolchain-contract
source_type: branch-note
status: raw
id: BR-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-001
kind: project-work-item
project: ca-skeleton-frontend-operational-contract
work_item: WI-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-001
inherits: [DEC-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-TOOLCHAIN-001@1, DEC-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-LANGUAGE-001@1, DEC-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-BUILD-001@1]
refines: []
overrides: []
depends_on: []
contract_packet: 1
branch: feature-frontend-project-bootstrap-toolchain-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, build-tooling]
created: 2026-07-18
target_merge:
status_label: in-progress
contract_packet_sha256: 96291fb32a210358e477a7242241d20382c2d978ad8d6c137fcb4735b9dff6d8
imports: [FE-GATE-011@1, FE-OC-007@1, FE-OC-016@1, FE-OC-018@1, FE-OC-020@1, FE-OC-021@1]
accepts_delegations: [DELEG-FE-002@1]
---
# branch: feature-frontend-project-bootstrap-toolchain-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`
- **완료 조건**: manifest·engines·pnpm lock·checkJs scripts와 frozen install evidence가 존재한다
<!-- section-id: inherited-project-decisions -->
### 상속한 프로젝트 결정
| Decision Ref | Project Summary | Branch Application | Source |
|---|---|---|---|
| `DEC-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-TOOLCHAIN-001@1` | package manager default는 pnpm이며 packageManager field와 pnpm-lock.yaml을 commit한다 | manifest·lockfile·frozen install 계약에 적용한다 | [[raw/project-notes/ca-skeleton-frontend-operational-contract]] |
| `DEC-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-LANGUAGE-001@1` | JavaScript ESM과 tsc allowJs/checkJs/noEmit을 typecheck-equivalent baseline으로 사용한다 | source language와 check:types script에 적용한다 | [[raw/project-notes/ca-skeleton-frontend-operational-contract]] |
| `DEC-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-BUILD-001@1` | Vite client-only SPA를 build baseline으로 사용한다 | Vite build scaffold와 build artifact gate에 적용한다 | [[raw/project-notes/ca-skeleton-frontend-operational-contract]] |
<!-- section-id: branch-local-decisions -->
### 브랜치 지역 결정
| Decision ID | Decision | Relation | Supporting Claims | Status |
|---|---|---|---|---|
| D1 | pnpm과 committed lockfile을 toolchain baseline으로 사용한다 | `local` | [[raw/project-notes/ca-skeleton-frontend-operational-contract]] | `proposed` |
| D2 | JavaScript ESM과 checkJs를 source/typecheck baseline으로 사용한다 | `local` | [[raw/project-notes/ca-skeleton-frontend-operational-contract]] | `proposed` |
| D3 | Vite client-only SPA를 build baseline으로 사용한다 | `local` | `raw/official-docs/vite-build-tool-official.md#VITE-C2` | `proposed` |
<!-- section-id: declared-overrides -->
### 선언한 예외
| Override ID | Overrides | Reason | Approval | Status |
|---|---|---|---|---|
없음.
<!-- GENERATED: branch-contract:end -->
<!-- section-id: branch-goal -->
## 목표
`ca-skeleton-frontend`의 project-wide bootstrap 계약 `FE-OC-003`("package manager, engine, lockfile, source language, checkJs command를 한 곳에서 MUST 고정")을 되묻지 않아도 코드를 작성할 수 있는 implementation-ready 명세로 내린다. 이 branch는 §20 Branch Decomposition에서 **Dependency `—`** 인 branch DAG의 root이며, 다른 27개 branch가 의존하는 toolchain 그릇(manifest·lockfile·source 언어·typecheck·build baseline)을 확정한다. 근거 결정은 [[raw/project-notes/ca-skeleton-frontend-operational-contract]]의 `FE-D001`(pnpm)·`FE-D002`(JavaScript ESM + `tsc --allowJs --checkJs --noEmit``FE-D003`(Vite client-only SPA)이다. Measurable completion(§20)은 "manifest/engines/pnpm lock/checkJs scripts + frozen install evidence"이며, 이는 `FE-GATE-001`(manifest/lockfile)·`FE-GATE-003`(typecheck-equivalent)·`FE-GATE-011`(build) 로 판정된다. 또한 `FE-OC-018`(supply-chain: frozen lockfile)·`FE-OC-020`(test taxonomy: gate script 배선)에 **contributes-to** 로 참여한다. 현재 frontend repository는 존재하지 않으므로 본 노트의 모든 구현 주장은 `planned` 등급이다.
- 이슈:
- PR:
<!-- section-id: branch-scope -->
## 범위
### 포함 범위
- `package.json` 매니페스트 확정 — `type: module`(ESM), `packageManager: pnpm@<pin>`, `engines`(Node/pnpm), script 슬롯 — 등급: `planned` (`FE-OC-003`, `FE-D001`/`FE-D002`)
- `pnpm-lock.yaml` commit + `pnpm install --frozen-lockfile` 재현성 계약 — 등급: `planned` (`FE-OC-003``FE-OC-018` 기여, `FE-D001`, `FE-GATE-001`)
- source 언어 = JavaScript ESM 고정 + `tsconfig.json`(`allowJs`/`checkJs`/`noEmit`) + `check:types` script — 등급: `planned` (`FE-OC-003`, `FE-D002`, `FE-GATE-003`)
- Vite client-only SPA build baseline + 최소 `vite.config.js` + `dev`/`build` script — 등급: `planned` (`FE-OC-003`, `FE-D003`, `FE-GATE-011`)
- Node/pnpm engine pin + engine 강제 정책 — 등급: `planned` (`FE-OC-003`, FE-NFR-C04 build context)
### 제외 범위
> 의도적으로 제외한 것. 다른 owner branch 소유 관심사이며 본 §구현 가이드에 detail을 남기지 않는다 (CLAUDE.md §15.5 R3).
- **build/runtime/secret env config 분리·runtime config 검증** — `FE-OC-004`, owner [[raw/branch-notes/feature-frontend-env-runtime-config-contract]]. 본 branch는 Vite가 `import.meta.env` 정적 치환 메커니즘을 제공한다는 사실만 확정하고 registry·검증은 위임.
- **dependency lint rule / restricted-import 규칙 내용** — `FE-OC-002`, owner [[raw/branch-notes/feature-frontend-clean-architecture-layering-contract]] + [[raw/branch-notes/feature-frontend-architecture-enforcement-lint-contract]]. 본 branch는 `lint` script 슬롯만 예약, 규칙 정의 위임.
- **test suite 내용·gate 오케스트레이션·artifact 보존** — `FE-OC-020`, owner [[raw/branch-notes/feature-frontend-test-taxonomy-contract]]. 본 branch는 `check:types`만 소유, level별 test·CI 배선 위임.
- **bundle budget·secret scan·SBOM·dependency review** — `FE-OC-018`/`FE-OC-021`, owner [[raw/branch-notes/feature-frontend-build-bundle-supply-chain-contract]] + [[raw/branch-notes/feature-web-vitals-performance-budget-contract]]. 본 branch는 frozen lockfile evidence만 기여.
- **8-registry 스키마·single-owner governance** — `FE-OC-022`, owner [[raw/branch-notes/feature-frontend-contract-registry-governance]]. 본 branch는 registry를 소유하지 않는다.
- **runtime schema(Zod) 검증** — `FE-OC-007`, owner [[raw/branch-notes/feature-runtime-schema-validation-contract]]. checkJs는 JSDoc 타입 검사만 제공하고 boundary runtime 검증은 위임.
## 근거 (필수, 최소 1개+)
| Source | 정당화하는 결정 |
|---|---|
| [[raw/official-docs/vite-build-tool-official]] | D3 — Vite production build가 Rolldown으로 최적화된 정적 자산을 산출(`VITE-C2`)하고 dev server가 native ESM 위에서 동작(`VITE-C1`)하므로 client-only SPA를 build baseline으로 채택 |
| [[raw/project-notes/ca-skeleton-frontend-operational-contract]] | D1·D2·D3 — Decision Register(`FE-D001`/`FE-D002`/`FE-D003`)와 contract index(`FE-OC-003`), supply-chain 최소값(§13.1), planned command 계약(§14.3), gate matrix(§15.1)의 governing SSOT |
| [[raw/project-notes/ca-skeleton-operational-contract]] | D1~D3의 상위 철학 precedent — backend skeleton의 운영 계약(port는 application 소유·sample은 제거 가능 fixture) 원칙을 frontend toolchain이 담을 그릇으로 확정 (사실 인용이 아닌 rationale precedent) |
## TODO
- [ ] `package.json` 작성 — `type: module`, `packageManager: pnpm@<pin>`, `engines`, script 슬롯 배치 — 등급: `planned`
- [ ] `pnpm-lock.yaml` commit + clean checkout에서 `pnpm install --frozen-lockfile` exit 0 / drift 시 non-zero 재현 — 등급: `planned`
- [ ] `tsconfig.json`(`allowJs`/`checkJs`/`noEmit`) + `check:types` script + checkJs negative fixture 배치 — 등급: `planned`
- [ ] 최소 `vite.config.js` + `dev`/`build` script (Vite client-only SPA baseline) — 등급: `planned`
- [ ] Node/pnpm 버전 pin(`.nvmrc` + engine 강제) + FE-NFR-C04 build context(Node/pnpm 버전) 기록 배선 — 등급: `planned`
## 진행 중 메모
`/branch-spec` 채움 완료 (2026-07-19). frontend repository 미생성 — 모든 항목 `planned`. 실제 코드 착수 전까지 evidence 등급 상향 금지.
## 결정 사항
> 각 결정은 아래 Decision Evidence Map의 prose 미러이다. 근거는 Sources 또는 hub Decision Register를 가리킨다.
- 2026-07-18: package manager를 **pnpm**으로 고정하고 `pnpm-lock.yaml` + `packageManager` 필드를 commit / 이유: project-local 재현성 default(lockfile drift·PM 혼용 방지) / 검토한 대안: npm·yarn·Bun / 근거: [[raw/project-notes/ca-skeleton-frontend-operational-contract]] `FE-D001`, §13.1 supply-chain 최소값(pnpm + committed lockfile).
- 2026-07-18: source 언어를 **JavaScript ESM**으로 고정하고 typecheck는 `tsc --allowJs --checkJs --noEmit`로 대체 / 이유: 사용자 제약 + boundary runtime schema(Zod) 필요성 하에서 타입 안전성 확보 / 검토한 대안: TypeScript strict 소스 / 근거: [[raw/project-notes/ca-skeleton-frontend-operational-contract]] `FE-D002`.
- 2026-07-18: build baseline을 **Vite client-only SPA**로 채택 / 이유: production build가 최적화된 정적 자산을 산출해 정적 호스팅 배포에 적합 / 검토한 대안: SSR/메타 프레임워크(Next 등)·edge rendering / 근거: [[raw/official-docs/vite-build-tool-official]] `VITE-C2`, [[raw/project-notes/ca-skeleton-frontend-operational-contract]] `FE-D003`.
## 결정-근거 매핑
> 각 결정과 raw source Claim ID의 연결. `Decision ID`(D1·D2·D3)는 본 노트 안에서 안정적으로 유지한다.
| Decision ID | Decision | 선택 조건 (언제 이 결정 / 언제 대안) | Supporting Claims | Evidence Strength | Open Risk |
|---|---|---|---|---|---|
| D1 | package manager = pnpm; `pnpm-lock.yaml` + `packageManager` 필드 commit (`FE-D001` / `FE-OC-003`, 기여 `FE-OC-018`·`FE-OC-020`) | target CI가 pnpm을 지원하고 조직이 특정 PM을 강제하지 않는 동안 → pnpm. 조직 표준이 npm/yarn/Bun을 강제하거나 target CI가 pnpm을 미지원 → 해당 PM으로 교체하되 lockfile·`packageManager` 필드·frozen install script를 동시 변경 | [[raw/project-notes/ca-skeleton-frontend-operational-contract]] `FE-D001`, §13.1(pnpm + committed lockfile) | `conditional-default` (project-decision) | pnpm 미지원 CI runner 채택 시 재현성 계약 재작성; lockfile drift가 gate로 실제 차단되는지 미검증 |
| D2 | source = JavaScript ESM; typecheck-equivalent = `tsc --allowJs --checkJs --noEmit` (`FE-D002` / `FE-OC-003`·`FE-OC-007`·`FE-OC-020`) | 사용자 제약(JS 유지) + runtime schema 경계 검증이 있는 동안 → JS ESM + checkJs. TypeScript strict 전환이 승인되면 → `.ts` 소스 + strict `tsconfig`로 이행하고 checkJs 경로 폐기 | [[raw/project-notes/ca-skeleton-frontend-operational-contract]] `FE-D002`, §14.3(`pnpm check:types` → checkJs diagnostic 0), §15.1 `FE-GATE-003` | `project-decision` (accepted-documented-only) | checkJs가 strict TS 수준 타입 안전을 보장하지 않음 — JSDoc 커버리지 공백 존재 가능; 실제 diagnostic 0 여부 미검증 |
| D3 | build baseline = Vite client-only SPA (`FE-D003` / `FE-OC-003`, 기여 `FE-OC-016`·`FE-OC-021`) | 제품 요구가 client-only SPA(정적 호스팅)로 충분한 동안 → Vite SPA. SSR/SEO/edge rendering이 제품 요구가 되면 → 별도 project fork로 Vite SSR 또는 메타 프레임워크 재평가(`FE-D003` revisit) | [[raw/official-docs/vite-build-tool-official]] `VITE-C2`(Rolldown production build → 최적화된 정적 자산), `VITE-C1`(dev server native ESM); [[raw/project-notes/ca-skeleton-frontend-operational-contract]] `FE-D003` | `official-doc` (official-vendor-doc) | `VITE-C2`는 정적 자산 산출만 증명하고 이 프로젝트 bundle/성능 threshold(`FE-OC-021`)는 별도 검증 필요; `pnpm build` exit 0 + manifest 산출 미검증 |
## 구현 가이드
> `planned` blueprint. 경로는 hub §4.5/§4.6 planned directory blueprint에서 도출되므로 grounded이지만, frontend 코드가 없으므로 전 구간 `planned`. 각 sub-section은 CLAUDE.md §15.5 3-rule(R1 Trace·R2 UNSUPPORTED_IMPL_DECISION·R3 no OUT_OF_BRANCH_SCOPE)을 따른다.
### 1. `package.json` 매니페스트 계약
> **Trace**: D1(`FE-D001`) + D2(`FE-D002`) + D3(`FE-D003`) → `FE-OC-003`. planned 경로 `package.json`(repo root) + engine 강제 파일(`.npmrc`/`.nvmrc`, 아래 UNSUPPORTED 참조), 소비자는 pnpm·Vite·tsc.
>
> - **UNSUPPORTED_IMPL_DECISION**: `packageManager` 의 정확한 pnpm 버전 pin(예: `pnpm@9.x`) — hub는 "pnpm"만 지정하고 버전을 못박지 않음. trade-off: 최신 pnpm major는 lockfile 포맷 변화 위험 → 착수 시점 pnpm LTS major로 pin하고 FE-NFR-C04에 기록.
> - **UNSUPPORTED_IMPL_DECISION**: `engines` 의 정확한 Node 범위(예: `>=20 <21`) — hub §14.1은 "Node/pnpm versions recorded"만 요구하고 특정 버전을 명시하지 않음. trade-off: Node LTS 경계 선택은 임의 → 착수 시점 active LTS major로 pin.
> - **UNSUPPORTED_IMPL_DECISION**: engine **강제(enforcement) 메커니즘** — hub `FE-OC-003`은 "engine을 한 곳에서 MUST 고정"만 요구하고, hub §17 `FE-Q-002`의 검증 조건도 "manifest `engines` + fresh clone pass"까지만 명시할 뿐 *무엇이 버전 위반 install을 실제로 거부하는가* 는 지정하지 않는다. `package.json` 의 `engines` 필드 단독은 기본 설정에서 경고에 그칠 수 있어(강제 여부는 package manager 설정 의존) 별도 장치가 없으면 no-op이 될 수 있다. 후보: (a) `.npmrc` 의 `engine-strict=true` + Node 버전 단일 소스 `.nvmrc`, (b) Corepack(`packageManager` 필드로 pnpm 버전 자체를 고정), (c) `preinstall` guard script. trade-off: (a)+(b) 조합을 기본값으로 채택 — `engine-strict` 가 Node/pnpm 범위 위반 install을 non-zero로 떨어뜨리고 `packageManager` 필드가 pnpm 버전 축을 덮어 런타임/PM 두 축이 모두 강제되며, `.nvmrc` 는 로컬 버전 전환용 단일 소스로만 쓰고 gate 판정 근거로는 쓰지 않는다. (c)는 커스텀 스크립트 유지비 때문에 보류. 세 후보의 실제 거부 동작은 미검증이므로 착수 시 §Claims To Verify의 engine 강제 항목으로 확정한다.
| 필드 | planned 값 | 근거 | 소유 경계 |
|---|---|---|---|
| `type` | `"module"` (ESM) | D2 (`FE-D002` JavaScript ESM) | this branch |
| `packageManager` | `"pnpm@<LTS-major>"` | D1 (`FE-D001`) | this branch (버전 pin은 UNSUPPORTED_IMPL) |
| `engines.node` / `engines.pnpm` | `<active-LTS>` 범위 | `FE-OC-003`("engine을 한 곳에서 고정") | this branch (버전 UNSUPPORTED_IMPL) |
| engine 강제 메커니즘 (`.npmrc` `engine-strict=true` + `.nvmrc`, `packageManager` 필드 병행) | 범위 위반 install을 non-zero로 거부 | `FE-OC-003`(engine 고정) + hub §17 `FE-Q-002` 검증 조건("manifest `engines` + fresh clone pass") | this branch (메커니즘 선택은 UNSUPPORTED_IMPL — 위 3번째 라벨) |
| `scripts.dev` / `scripts.build` | `vite` / `vite build` | D3 (`FE-D003`), §14.3 `pnpm build` | this branch |
| `scripts.check:types` | `tsc --allowJs --checkJs --noEmit` | D2 (`FE-D002`), §14.3 `pnpm check:types` | this branch |
| `scripts.lint`·`test:*`·`check:bundle`·`scan:security` 등 | 이름 슬롯만 예약 | §14.3 script 계약 | **delegated** — 각 owner branch가 구현 정의(§5 아래 슬롯 표) |
### 2. Lockfile + frozen install 재현성
> **Trace**: D1(`FE-D001`) → `FE-OC-003` 소유 + `FE-OC-018` 기여. planned 경로 `pnpm-lock.yaml`(commit) + `artifacts/quality/install.txt`. gate `FE-GATE-001@1`(manifest/lockfile — blocking scope·pass condition 은 hub §15.1 소유), §14.3 `pnpm install --frozen-lockfile`.
>
> - **UNSUPPORTED_IMPL_DECISION**: 없음 — frozen install 메커니즘(`--frozen-lockfile`)·evidence 경로(`artifacts/quality/install.txt`)·gate(`FE-GATE-001`)·supply-chain 최소값(§13.1 lockfile-check)이 모두 hub에 grounded.
- `pnpm-lock.yaml`을 repo에 commit; manifest range와 lockfile이 drift하면 `pnpm install --frozen-lockfile`이 non-zero exit → `FE-GATE-001` FAIL로 merge 차단.
- evidence artifact: install 로그(`artifacts/quality/install.txt`, §14.3) + lockfile 검증(`artifacts/quality/lockfile-check.txt`, §13.1).
- SBOM·secret scan·dependency review는 본 branch 산출물(lockfile)을 소비하지만 owner는 [[raw/branch-notes/feature-frontend-build-bundle-supply-chain-contract]] — 본 §에 detail 미기재(R3).
### 3. Source 언어 + typecheck-equivalent 툴체인
> **Trace**: D2(`FE-D002`) → `FE-OC-003`·`FE-OC-007`·`FE-OC-020`. planned 경로 `tsconfig.json`(repo root, checkJs 전용) + checkJs negative fixture. gate `FE-GATE-003@1`(typecheck-equivalent — blocking scope 는 hub §15.1 소유), §14.3 `pnpm check:types` → checkJs diagnostic 0.
>
> - **UNSUPPORTED_IMPL_DECISION**: `tsconfig.json`의 `allowJs`/`checkJs`/`noEmit` 외 부수 옵션(`target`/`moduleResolution`/`lib`) — `FE-D002`는 세 flag만 명시. trade-off: Vite ESM·최신 브라우저 전제 하에 임의 선택 → 착수 시 Vite 권장 preset에 맞춰 확정하고 fixture로 검증.
> - **UNSUPPORTED_IMPL_DECISION**: checkJs negative fixture의 파일 경로·형태 — hub는 "JSDoc/checkJs negative fixture"(§15.1 `FE-GATE-003`)만 요구. trade-off: fixture 위치는 임의 → `tests/` 하위 typecheck fixture 컨벤션으로 확정.
- `tsconfig.json`은 emit 없이(`noEmit`) `.js`를 검사(`allowJs`+`checkJs`)한다. 별도 `.ts` 소스는 생성하지 않는다(D2).
- `pnpm check:types`는 production 소스에서 diagnostic 0이어야 하고, negative fixture는 의도적으로 fail해야 `FE-GATE-003@1`이 PASS(pass condition 원문은 hub §15.1 소유).
- boundary runtime 검증(Zod)은 `FE-OC-007` owner [[raw/branch-notes/feature-runtime-schema-validation-contract]] — checkJs는 compile-time JSDoc 타입만 담당(R3).
### 4. Vite build baseline 스캐폴딩
> **Trace**: D3(`FE-D003`) → `FE-OC-003` 소유 + `FE-OC-016`·`FE-OC-021` 기여. planned 경로 `vite.config.js`(repo root) + `src/bootstrap/main.jsx`(hub §4.5 composition root). gate `FE-GATE-011@1`(build — owner 는 [[raw/branch-notes/feature-frontend-build-bundle-supply-chain-contract]], blocking scope·pass condition 은 hub §15.1 소유), §14.3 `pnpm build` → `artifacts/release/build-manifest.json`.
>
> - **UNSUPPORTED_IMPL_DECISION**: `vite.config.js`의 정확한 plugin 목록(예: React JSX plugin 패키지명) — hub는 plugin을 명시하지 않음. JSX 컴파일은 React 채택(`FE-D004`, owner [[raw/branch-notes/feature-async-ui-state-contract]]) 때문에 필요하나 plugin 패키지 선택은 미근거. trade-off: 착수 시 Vite 공식 React plugin 채택하고 build fixture로 검증.
> - **UNSUPPORTED_IMPL_DECISION**: build output/asset hashing 세부 설정 — release cache 정책(`FE-OC-016` hashed asset immutable)은 owner [[raw/branch-notes/feature-frontend-release-cache-rollback-contract]] 소유. 본 §은 build가 hashed 정적 자산을 산출한다는 baseline만 확정하고 cache header 정책은 위임(R3).
> - **UNSUPPORTED_IMPL_DECISION**: `artifacts/release/build-manifest.json` **산출(emission) 메커니즘** — hub §12.1은 이 파일을 expected artifact로 열거하고 §14.3은 `pnpm build` 의 assertion을 "exit 0 + manifest present"로 두지만, *어떤 경로로 그 파일이 계약 경로에 생기는가* 는 지정하지 않는다(§12.1: "실제 path는 repository가 생기면 owner branch에서 확정한다"). 근거 source 의 `VITE-C2` 는 "최적화된 정적 자산 산출"만 증명할 뿐 manifest 파일의 이름·위치·스키마를 증명하지 않으므로, 번들러 기본 manifest 경로/형식은 본 노트에서 확정된 사실이 아니다. 후보: (a) 번들러 manifest 옵션을 켜고 산출물을 계약 경로로 옮기는 post-build wrapper script, (b) 번들러 출력 설정만으로 계약 경로에 직접 쓰기. trade-off: (a)를 기본값으로 채택 — 번들러 기본 출력 규약과 계약 artifact 경로를 분리해 두면 번들러/옵션이 바뀌어도 downstream gate(`FE-GATE-011`) 계약 경로가 깨지지 않는다. 착수 시 실제 산출 경로를 확인해 확정.
> - **해소됨(2026-07-21) — 근거 있는 결정**: `FE-NFR-C04` build context 의 기록 위치·필드명은 이제 스키마가 정한다. hub §2.1.3 `ART-FE-001@1`(Schema Owner = 본 branch)의 `build-manifest.schema.json` 이 `buildContext.nodeVersion` · `buildContext.packageManagerVersion` · `buildContext.runnerImage` 를 required 로 고정한다. 이전 판이 제안하던 top-level `pnpmVersion` 은 그 스키마의 `buildContext.packageManagerVersion` 으로 확정됐다(패키지 매니저를 pnpm 으로 못박지 않기 위함). 필드 추가·rename 은 Schema Owner 단독 결정이고 소비 branch 는 `imports` pin 으로 따라온다.
- 최소 `vite.config.js` + `pnpm dev`/`pnpm build` script로 client-only SPA build baseline을 확정.
- `pnpm build`는 exit 0 + build manifest(`artifacts/release/build-manifest.json`)를 산출해야 `FE-GATE-011` PASS.
- **manifest 산출 책임 경계**: `artifacts/release/build-manifest.json`*생성* 은 본 branch 가 소유한다 — 근거는 gate owner 가 아니라 hub §2.1.3 `ART-FE-001@1` 의 Producer·Schema Owner 등록이다(`FE-GATE-011` 자체의 owner 는 [[raw/branch-notes/feature-frontend-build-bundle-supply-chain-contract]]). release tuple 파일(`dist/release-manifest.json`)과 cache header 정책은 `FE-OC-016`/`FE-OC-017` owner [[raw/branch-notes/feature-frontend-release-cache-rollback-contract]] 소유이며 본 §에 detail 미기재(R3).
- **build context 기록 vs 소비 경계**: `FE-NFR-C04`(Node/pnpm 버전 등) 값을 build manifest에 *기록* 하는 것은 본 branch, 그 값을 bundle threshold 판정 맥락으로 *소비* 하는 것은 `FE-OC-021` owner [[raw/branch-notes/feature-web-vitals-performance-budget-contract]] — threshold·판정 로직은 본 §에 미기재(R3).
- bundle size threshold(`FE-NFR-001`/`002`)와 성능 예산은 `FE-OC-021` owner [[raw/branch-notes/feature-web-vitals-performance-budget-contract]] — 본 §에 threshold 미기재(R3).
### 5. §14.3 script 슬롯 vs owner 위임 (FE-OC-020 기여)
> **Trace**: `FE-OC-003`(command 한 곳 고정) + `FE-OC-020` 기여(gate script 배선). §14.3 planned command 계약의 script 이름은 project-wide SSOT이며, 본 branch는 매니페스트에 슬롯을 예약하되 non-owned script의 구현은 정의하지 않는다.
>
> - **UNSUPPORTED_IMPL_DECISION**: 없음 — 어떤 script를 본 branch가 소유하고 어떤 것을 위임하는지는 §15.1 gate ownership + §5.1 registry owner map으로 결정론적으로 도출됨.
| §14.3 script | 소유 | 본 branch 역할 |
|---|---|---|
| `pnpm install --frozen-lockfile` | this branch | 정의 + evidence (`FE-GATE-001`) |
| `pnpm check:types` | this branch | 정의 (`FE-GATE-003`) |
| `pnpm build` | this branch | baseline 정의 (`FE-GATE-011`) |
| `pnpm lint` | [[raw/branch-notes/feature-frontend-architecture-enforcement-lint-contract]] | 슬롯만 예약 |
| `pnpm test:unit`/`test:component`/`test:integration`/`test:e2e`/`test:a11y` | [[raw/branch-notes/feature-frontend-test-taxonomy-contract]] | 슬롯만 예약 |
| `pnpm check:bundle`/`test:performance` | [[raw/branch-notes/feature-web-vitals-performance-budget-contract]] | 슬롯만 예약 |
| `pnpm scan:security` | [[raw/branch-notes/feature-frontend-build-bundle-supply-chain-contract]] | 슬롯만 예약 |
## 엣지·실패·의존
- **실패·엣지 경로**:
- **lockfile drift**: manifest range와 `pnpm-lock.yaml` 불일치 → `pnpm install --frozen-lockfile` non-zero exit → `FE-GATE-001` FAIL. 기대 동작: CI가 merge 차단, 부분 install 없음.
- **engine mismatch**: 로컬/CI Node·pnpm이 `engines` 범위 밖 → engine 강제로 install 거부. 기대 동작: 명확한 에러 + silent 진행 금지. (강제 메커니즘 = §구현 가이드 1의 "engine 강제 메커니즘" 행 + 같은 § 3번째 `UNSUPPORTED_IMPL_DECISION` 라벨 — 후보 (a)/(b)/(c) 중 미확정)
- **checkJs diagnostic > 0**: production 소스 타입 오류 → `pnpm check:types` non-zero → `FE-GATE-003` FAIL. 기대 동작: merge 차단. negative fixture는 반대로 fail해야 정상.
- **Vite build 실패/manifest 부재**: `pnpm build` non-zero 또는 `build-manifest.json` 미산출 → `FE-GATE-011` FAIL.
- **script 이름 drift**: §14.3 script rename을 gate/artifact mapping 갱신 없이 수행 → downstream gate가 없는 script 참조. 기대 동작: §14.3 규칙("script 이름을 바꾸면 acceptance gate와 artifact mapping을 동시에 갱신")으로 방지.
- **다른 계약 의존**:
- **상류 의존 해당 없음** — 본 branch는 §20 Dependency `—` 인 branch DAG root. sibling 계약에서 consume하는 것 없음.
- **하류 소비자(역의존)**: 본 산출물(pnpm/lockfile·`type: module`·`check:types`·Vite baseline)을 [[raw/branch-notes/feature-frontend-clean-architecture-layering-contract]], [[raw/branch-notes/feature-frontend-env-runtime-config-contract]], [[raw/branch-notes/feature-frontend-test-taxonomy-contract]], [[raw/branch-notes/feature-tailwind-design-token-styling-contract]], [[raw/branch-notes/feature-frontend-build-bundle-supply-chain-contract]] 가 §20 Dependency로 consume. 이 계약(script 이름·lockfile 정책)이 바뀌면 해당 branch 영향.
- **기여(contributes-to)**: `FE-OC-018` owner [[raw/branch-notes/feature-frontend-build-bundle-supply-chain-contract]] 의 supply-chain gate가 본 frozen lockfile evidence를 consume; `FE-OC-020` owner [[raw/branch-notes/feature-frontend-test-taxonomy-contract]] 가 `check:types``FE-GATE-003`으로 배선.
- **CA 철학 precedent**: [[raw/project-notes/ca-skeleton-operational-contract]] 의 운영 계약(port ownership·sample fixture 원칙) — 본 toolchain이 그 구조를 담을 그릇을 만든다(사실 의존이 아닌 설계 precedent).
## 검증해야 할 주장
| Claim | Why uncertain | How to verify | Status |
|---|---|---|---|
| clean checkout에서 `pnpm install --frozen-lockfile`이 exit 0, lockfile drift 시 non-zero | repo·lockfile 미생성 | `FE-GATE-001` frozen install; evidence `artifacts/quality/install.txt` + `lockfile-check.txt` (§14.3 / §13.1) | `needs-confirmation` |
| `pnpm check:types`가 production 소스에서 checkJs diagnostic 0, negative fixture에서 fail | `tsconfig` checkJs 설정 실효성 미검증 | `FE-GATE-003` typecheck; JSDoc/checkJs negative fixture; `artifacts/quality/check-types.txt` (§15.1) | `needs-confirmation` |
| `pnpm build`(Vite)가 exit 0 + `build-manifest.json` 산출 | `vite.config.js` 미작성 | `FE-GATE-011` build; `artifacts/release/build-manifest.json` (§14.3) | `needs-confirmation` |
| engine 강제(Node/pnpm 범위)가 버전 불일치 install을 실제 차단 | 강제 메커니즘 후보 (a) `.npmrc engine-strict` (b) Corepack (c) `preinstall` guard 중 미확정·미검증 (§구현 가이드 1 UNSUPPORTED) | 로컬 Node 버전을 `engines` 범위 밖으로 변조 후 install → non-zero exit 재현; fresh clone pass(hub §17 `FE-Q-002`) | `needs-confirmation` |
| `artifacts/release/build-manifest.json` 이 계약 경로에 실제 산출되고 `FE-NFR-C04` build context(Node/패키지 매니저 버전 + runner image)를 포함 | 산출 메커니즘(wrapper vs 번들러 직접 출력) 미확정 — 필드명은 `ART-FE-001@1` 스키마로 확정됨 | `pnpm build` 후 경로 존재 + context 필드 존재 확인; `FE-GATE-011` assertion + hub §14.1 context 요구 대조 | `needs-confirmation` |
| §14.3 script 이름이 downstream gate(`FE-GATE-001`/`003`/`011`)와 일치 유지 | script rename drift 위험 | gate matrix ↔ 매니페스트 script cross-ref (ci-quality-gates 협업) | `needs-confirmation` |
## 관심사 커버리지 (coverage-auditor 자동 생성 — 있을 때)
> `/coverage`가 채우는 생성물이며 손으로 유지하지 않는다.
| 관심사 | 상태 | owner | 심각도 | 근거 |
|---|---|---|---|---|
| TODO — `/coverage` 실행 전 | missing | (없음) | 미평가 | TODO |
## 마주친 문제
없음 — scaffolding 단계
## 묶음 (이 branch에서 파생된 자료)
<!-- GENERATED: received-delegations:start -->
### 수신한 위임
| Delegation Ref | From | Concern | Status |
|---|---|---|---|
| `DELEG-FE-002@1` | [[raw/branch-notes/feature-frontend-architecture-enforcement-lint-contract]] | `fe.deleg.lint-toolchain-substrate` | accepted |
<!-- GENERATED: received-delegations:end -->
<!-- GENERATED: project-contract-imports:start -->
## 가져온 프로젝트 계약
| Ref | Owner | 요약 | Branch 적용 |
|---|---|---|---|
| `FE-GATE-011@1` | [[raw/branch-notes/feature-frontend-build-bundle-supply-chain-contract]] | clean production build 가 실패하거나 기대 artifact 가 없으면 merge·release 를 MUST 차단 | import 참조로 적용 |
| `FE-OC-007@1` | [[raw/branch-notes/feature-runtime-schema-validation-contract]] | JSON envelope와 payload는 boundary에서 runtime schema를 MUST 통과 | import 참조로 적용 |
| `FE-OC-016@1` | [[raw/branch-notes/feature-frontend-release-cache-rollback-contract]] | HTML, asset, runtime config, release manifest cache policy를 MUST 구분 | import 참조로 적용 |
| `FE-OC-018@1` | [[raw/branch-notes/feature-frontend-build-bundle-supply-chain-contract]] | frozen lockfile, dependency review, secret scan, SBOM 또는 dependency inventory를 release gate에 MUST 포함 | import 참조로 적용 |
| `FE-OC-020@1` | [[raw/branch-notes/feature-frontend-test-taxonomy-contract]] | gate 종류별 책임·fixture·artifact를 분리하고 실패를 warning으로 낮추면 안 됨 | import 참조로 적용 |
| `FE-OC-021@1` | [[raw/branch-notes/feature-web-vitals-performance-budget-contract]] | NFR은 device/network/cache/build context와 함께 MUST 측정 | 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):