Files
llm-wiki/raw/branch-notes/feature-web-vitals-performance-budget-contract.md

261 lines
24 KiB
Markdown

---
title: branch / feature-web-vitals-performance-budget-contract
source_type: branch-note
status: raw
branch: feature-web-vitals-performance-budget-contract
parent_branch:
related_projects: [ca-skeleton-frontend, ca-skeleton]
governing_docs: [raw/project-notes/ca-skeleton-frontend-operational-contract]
tags: [branch, ca-skeleton, frontend, observability, react, histogram-quantile]
created: 2026-07-18
target_merge:
status_label: in-progress
id: BR-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-025
kind: project-work-item
project: ca-skeleton-frontend-operational-contract
work_item: WI-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-025
inherits: [DEC-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-BUILD-001@1, DEC-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-TEST-STACK-001@1]
refines: []
overrides: []
depends_on: [WI-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-020, WI-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-016]
contract_packet: 1
contract_packet_sha256: 4579fd193d1c3d1a19d54a732084315a1ae27a23bfdecf664e511eef29e83cbe
imports: [ART-FE-002@1, FE-GATE-012@1, FE-OC-014@1, FE-OC-020@1, FE-OC-026@1]
---
# branch: feature-web-vitals-performance-budget-contract
> Layer: `raw/branch-notes/` — TODO·결정·진행 기록. 구현 결과는 검증 뒤 `/ingest`로만 추출한다.
<!-- 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`
- **완료 조건**: context metadata와 lab·bundle·28-day field report가 생성된다
<!-- section-id: inherited-project-decisions -->
### 상속한 프로젝트 결정
| Decision Ref | Project Summary | Branch Application | Source |
|---|---|---|---|
| `DEC-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-BUILD-001@1` | Vite client-only SPA를 build baseline으로 사용한다 | Work Item 완료 조건에 적용 | [[raw/project-notes/ca-skeleton-frontend-operational-contract]] |
| `DEC-CA-SKELETON-FRONTEND-OPERATIONAL-CONTRACT-TEST-STACK-001@1` | test stack default는 Vitest, RTL, MSW, Playwright, axe다 | 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 -->
## 목표
이 branch 는 project-wide 계약 `FE-OC-021` ("NFR 은 device/network/cache/build context 와 함께 MUST 측정, 최소 증거 = machine-readable report") 를 *구현 착수 가능한 명세* 로 내린다. 구체적으로 (1) 측정 context 모델([[raw/project-notes/ca-skeleton-frontend-operational-contract]] §14.1 의 `FE-NFR-C01`~`FE-NFR-C04`), (2) initial target matrix(bundle `FE-NFR-001`/`FE-NFR-002`, lab `FE-NFR-003`~`FE-NFR-005`, field `FE-NFR-013`~`FE-NFR-015`), (3) 세 개의 machine-readable evidence report(`bundle.json` / `lab.json` / `field-web-vitals.json`) 를 정의한다. 이 branch 는 세 performance gate(`FE-GATE-012` bundle, `FE-GATE-026` lab, `FE-GATE-018` field)의 pass-condition 을 정의해 `FE-OC-020`(test taxonomy) 에 기여하고, release-time bundle/lab gate 를 통해 `FE-OC-016`(release readiness) 에 기여한다. **현재 frontend 코드는 존재하지 않으므로 모든 구현 항목은 `planned`** 이다.
- 이슈: 없음 (스캐폴딩 단계)
- PR: 없음
<!-- section-id: branch-scope -->
## 범위
### 포함 범위
- `FE-OC-021` measurement-context 계약: 모든 NFR 수치는 4-context(device/runtime · network/cache · route/data · build) metadata 와 함께만 evidence 로 인정 (§14.1).
- Initial target matrix 정의 + revisit 절차: `FE-NFR-001`/`FE-NFR-002`(bundle gzip budget), `FE-NFR-003`~`FE-NFR-005`(lab), `FE-NFR-013`~`FE-NFR-015`(field p75).
- 세 machine-readable report schema: bundle(`FE-GATE-012`), lab(`FE-GATE-026`), 28-day field Web Vitals(`FE-GATE-018`).
- lab ≠ field 불변식 + negative fixture(context metadata 누락 / named threshold 초과).
- 세 performance gate 의 pass-condition + required-context 정의.
### 제외 범위
> 의도적으로 제외한 것. 각 항목은 owner branch 에 위임한다 (근거 범위 밖 detail 을 여기서 정하지 않음 — CLAUDE.md §15.5 R3).
- 실제 production RUM 수집·telemetry sink·consent/privacy 정책 — [[raw/branch-notes/feature-frontend-observability-logging-trace-contract]] (`FE-OC-014`) + open question `FE-Q-008` 소유.
- bundle 을 생성하는 build baseline(Vite production build, code splitting)·supply-chain build gate — [[raw/branch-notes/feature-frontend-build-bundle-supply-chain-contract]] (`FE-OC-018`) 소유.
- CI gate wiring · blocking scope · artifact retention — [[raw/branch-notes/feature-frontend-ci-quality-gates-contract]] + [[raw/branch-notes/feature-frontend-test-taxonomy-contract]] (`FE-OC-020`) 소유. 본 branch 는 pass-condition 만 제공.
- API total timeout(`FE-NFR-007`)·retry count(`FE-NFR-008`) 메커니즘 — [[raw/branch-notes/feature-api-client-response-envelope-contract]] (`FE-OC-006`, `FE-OC-009`) 소유. 본 branch 는 그 NFR *값* 을 target matrix 로 참조만 한다.
- browser support matrix(`FE-Q-007`), 실제 CI runner CPU·throttling profile 확정(repo/CI 생성 전 불가), browser vendor-specific tuning.
## 근거 (필수, 최소 1개+)
| Source | 정당화하는 결정 |
|---|---|
| [[raw/official-docs/vite-build-tool-official]] `VITE-C2` | "production build 는 Rolldown 으로 코드를 번들링해 최적화된 정적 자산을 산출" — bundle report 가 측정하는 build artifact 의 공식 근거 (D2 bundle, D6 gate). |
| [[raw/project-notes/ca-skeleton-frontend-operational-contract]] §14(FE-NFR-C01~C04·FE-NFR-001~015) · §14.3(command→artifact) · §15.1(FE-GATE-012/018/026) · §15.2(negative fixture) · FE-OC-021 | measurement context 모델·initial target·three-report split·gate pass-condition 의 project SSOT (D1·D2·D5·D6). |
| web.dev Core Web Vitals (researched 2026-07-19, `https://web.dev/articles/vitals`) | LCP/INP/CLS 정의 + good threshold(2.5s / 200ms / 0.1) + 75th-percentile + lab≠field 구분의 공식 표준 근거 (D3·D4). |
## TODO
- [ ] measurement-context schema(device/runtime · network/cache · route/data · build) 정의 + 각 report 가 embed 할 metadata 필드 명세 — 등급: `planned`
- [ ] bundle report schema (`artifacts/performance/bundle.json`: initial JS gzip, lazy chunk gzip vs `FE-NFR-001`/`FE-NFR-002`) — 등급: `planned`
- [ ] lab report schema (`artifacts/performance/lab.json`: LCP/CLS/interaction-latency + context metadata vs `FE-NFR-003`~`FE-NFR-005`) — 등급: `planned`
- [ ] 28-day field report schema (`artifacts/performance/field-web-vitals.json`: p75 LCP/CLS/INP + consent·route-ID·release-ID·eligible-sample metadata vs `FE-NFR-013`~`FE-NFR-015`) — 등급: `planned`
- [ ] 세 performance gate pass-condition + negative fixture(context 누락 / threshold 초과) 명세 — 등급: `planned`
- [ ] deferred minimum eligible sample threshold 해소 절차 문서화 (telemetry baseline 확보 이후) — 등급: `planned`
## 진행 중 메모
- vitals threshold(LCP 2.5s / CLS 0.10 / INP 200ms, p75)는 Core Web Vitals "good" 값(web.dev). bundle budget(200/120 KiB)은 project-local initial 값이며 `FE-RISK-010`(threshold 가 실제 device UX 와 무관할 위험)로 첫 측정 후 revisit 대상.
- 28-day window 는 hub/CrUX convention 이며 web.dev 문서는 28일을 *명시하지 않음* → 28-day 는 project decision 으로 grounding.
- CI runner CPU·throttling profile 미확정(§14.1) → 값을 지금 고정하지 않고 command 실행 시 report metadata 에 기록.
## 결정 사항
- 2026-07-19: measurement-context 계약 — 모든 NFR 수치는 4-context 와 함께만 evidence / 이유: context 없는 숫자는 재현·비교 불가 / 대안: 단일 숫자만 기록(reject) / 근거: [[raw/project-notes/ca-skeleton-frontend-operational-contract]] §14.1 + FE-OC-021.
- 2026-07-19: three machine-readable report split(bundle / lab / 28-day field) / 이유: build-repro · synthetic lab · RUM 은 서로 다른 context / 대안: 단일 통합 report / 근거: hub §14.3 + §20 measurable completion.
- 2026-07-19: lab ≠ field 불변식 — lab 결과를 production percentile 로 표현 금지 / 근거: hub §14.2 note + web.dev(field vs lab).
- 2026-07-19: initial target = Core Web Vitals good threshold(LCP 2.5s / CLS 0.10 / INP 200ms, p75) + project bundle budget(200/120 KiB) / 대안: device-class 별 커스텀 threshold / 조건: 첫 실측·field data 확보 후 revisit(`FE-RISK-010`) / 근거: web.dev + hub §14.2.
- 2026-07-19: 28-day field window + eligibility metadata; minimum eligible sample threshold 는 deferred(telemetry baseline 이후) → `FE-GATE-018` 은 그 전까지 PASS 불가 / 근거: hub §14.2 note + §14.3 + FE-GATE-018.
- 2026-07-19: 세 performance gate(FE-GATE-012 bundle / FE-GATE-026 lab / FE-GATE-018 field)에 **NFR threshold 값과 negative fixture 를 공급**; CI wiring 은 위임 / 근거: hub §15.1 + §15.2. (2026-07-21 정정: gate 의 pass condition 자체는 hub §15.1 소유이고 `FE-GATE-012` 의 Owner 는 build-bundle 이다 — hub §2.1.1.)
## 결정-근거 매핑
> `Supporting Claims` 의 `[[hub]]` 는 [[raw/project-notes/ca-skeleton-frontend-operational-contract]] 를 가리킨다. 이 branch 는 `FE-OC-021` owner 이며, `FE-D*` decision row 중 이 slug 를 owner 로 갖는 것은 없다 — 아래 결정은 `FE-OC-021` 계약 조항과 §14 메커니즘을 branch-local decision(D1~D6)으로 내린 것.
| Decision ID | Decision | 선택 조건 (언제 이 결정 / 언제 대안) | Supporting Claims | Evidence Strength | Open Risk |
|---|---|---|---|---|---|
| D1 | Context-mandatory measurement: 모든 NFR 수치는 4-context(device/runtime · network/cache · route/data · build) metadata 와 함께만 evidence (`FE-OC-021`) | 항상 적용되는 contract invariant. 구체 context 값(CI runner CPU · throttling)은 §14.1 대로 run time 에 report metadata 로 기록 — 지금 고정 불가. 분기 없음 | [[raw/project-notes/ca-skeleton-frontend-operational-contract]] §14.1 (`FE-NFR-C01`~`FE-NFR-C04`, "context 가 없는 숫자는 evidence 로 인정하지 않는다"), `FE-OC-021` | `project-decision` | CI runner spec · throttling profile 미확정 → repo/CI 생성 전 실제 context 값 확정 불가 (`FE-NFR-C01` note, `FE-Q-002`/`FE-Q-007`) |
| D2 | Three machine-readable report split: bundle(`bundle.json`) · lab(`lab.json`) · 28-day field(`field-web-vitals.json`) | three-report split 이 default; lab/field 경계를 보존하는 단일 통합 pipeline 이 등장하면 통합 재검토 | [[raw/project-notes/ca-skeleton-frontend-operational-contract]] §14.3 (command→artifact 표), §20 measurable completion ("context metadata + lab/bundle/28-day field reports"); [[raw/official-docs/vite-build-tool-official]] `VITE-C2` (bundle 대상 = production build artifact) | `project-decision + official-doc` | 세 report 모두 `PLANNED_NOT_EXECUTED` — schema · collector 미구현 |
| D3 | Lab ≠ field 불변식: lab(`FE-NFR-C01` synthetic Playwright)을 production percentile 로 표현 금지, field(`FE-NFR-C03` RUM p75)와 분리 | 불변식 — 대안 없음(분리 위반 = reject). 어떤 조건에서도 lab 값을 field SLO 로 승격하지 않음 | [[raw/project-notes/ca-skeleton-frontend-operational-contract]] §14.2 note ("lab result 를 production percentile 로 표현하지 않는다"), `FE-OC-026`; web.dev Core Web Vitals (researched: "Only field measurement can accurately capture the complete picture" / "Lab measurement is the best way to test performance ... before they've been released") | `project-decision + official-standard` | collector 가 lab/field 를 혼동해 리포트하면 evidence 신뢰 붕괴 → negative fixture 로 강제 필요 |
| D4 | Initial target matrix: LCP lab/field ≤ 2.5s, CLS ≤ 0.10, interaction/INP ≤ 200ms(p75), initial JS gzip ≤ 200 KiB, lazy chunk gzip ≤ 120 KiB | conditional-default: 프로젝트 초기값. device-class 별 커스텀 threshold 는 첫 실측·field data 가 threshold 의 device-UX 무관성을 보일 때 채택(`FE-RISK-010` revisit trigger = "first measurement") | web.dev Core Web Vitals (researched: LCP "2.5 seconds", INP "200 milliseconds", CLS "0.1", "75th percentile of page loads"); [[raw/project-notes/ca-skeleton-frontend-operational-contract]] §14.2 (bundle budget = project-local initial), `FE-RISK-010` | `official-standard (vitals) + conditional-default (bundle budget)` | `FE-RISK-010` — bundle/threshold 가 실제 device UX 와 무관할 수 있음; 첫 측정 후 evidence 로 revisit |
| D5 | 28-day field window + eligibility metadata(consent/privacy boundary · route-ID aggregation · production release ID · eligible sample); minimum eligible sample threshold = `deferred` | 28-day window 는 default; min-sample threshold 는 telemetry baseline 확보 후 owner 가 확정 — 그 전엔 `FE-GATE-018` PASS 금지 | [[raw/project-notes/ca-skeleton-frontend-operational-contract]] §14.2 note, §14.3 (`collect:web-vitals-evidence` = "28-day context + p75 + eligible sample metadata"), §15.1 `FE-GATE-018` (28-day 는 hub/CrUX convention — web.dev 는 28일 미명시) | `project-decision` | min-sample threshold deferred → `FE-GATE-018` blocked; consent/privacy · sink 는 telemetry branch(`FE-OC-014`, `FE-Q-008`)에 의존 |
| D6 | 세 performance gate 에 NFR threshold 값 + negative fixture 공급: `FE-GATE-012@1`(bundle — Owner 는 build-bundle), `FE-GATE-026@1`·`FE-GATE-018@1`(Owner 는 본 branch). pass condition 원문은 hub §15.1 소유 | contract 정의(분기 N/A). 단 CI wiring · blocking scope · artifact retention 은 위임(Open Risk 참조) | [[raw/project-notes/ca-skeleton-frontend-operational-contract]] §15.1 (gate rows), §15.2 (lab negative fixture = "context metadata missing 또는 one named threshold exceeded") | `project-decision` | gate CI wiring/실행은 `feature-frontend-ci-quality-gates-contract` · `feature-frontend-test-taxonomy-contract`(`FE-OC-020`)이 소유 — 본 branch 는 pass-condition 만 정의 |
## 구현 가이드
> 모든 경로(`artifacts/performance/*`, `src/contracts/*`)는 hub §4.6 Planned directory blueprint 에서 온 `planned` anchor 다. **frontend 코드가 없으므로 전 항목 `planned`.**
### 1. Measurement context metadata schema
> **Trace**: D1 (`FE-OC-021`, hub §14.1). 각 report 는 아래 4-context 를 embed 해야 evidence 로 인정된다.
>
> - **UNSUPPORTED_IMPL_DECISION**(`lab.json`·`field-web-vitals.json` 한정): 실제 JSON key 이름(`context.runner`, `context.throttling`, `context.cache` 등)은 hub 가 아직 명명하지 않음. 명명 *스타일* 은 hub §2.1.3 이 정한 **camelCase**(`artifacts/**` report 한정)를 따른다 — 이전 판의 snake_case 제안은 그 규약 이전 것이라 폐기한다. `bundle.json` 은 `ART-FE-002@1` 스키마가 이미 확정했으므로 이 항목 대상이 아니다. trade-off: Lighthouse/Playwright reporter 가 자체 schema 를 고정하면 그 형태로 맞춘다. CI runner CPU/throttling *값* 은 미확정이라 여기서 상수화하지 않고 run time 기록(§14.1) 으로 남긴다.
| Context ID | 무엇을 기록 | 어느 report 가 embed | 근거 |
|---|---|---|---|
| `FE-NFR-C01` | Playwright Chromium, CI runner spec, cold cache, throttling profile | `lab.json` | hub §14.1 |
| `FE-NFR-C03` | production field data, real network, 28-day window, top route IDs | `field-web-vitals.json` | hub §14.1 |
| `FE-NFR-C04` | build runner image + Node/pnpm version | `bundle.json` | hub §14.1 |
- 규칙(§14.1): context 가 없는 숫자는 evidence 로 인정하지 않는다 → context block 부재 = gate FAIL (negative fixture, §4 참조).
### 2. Three report artifacts + threshold binding
> **Trace**: D2 (hub §14.3, §20) + D4 (web.dev vitals threshold + hub §14.2 bundle budget). command·artifact·NFR 매핑은 hub §14.3 표의 도출이다.
>
> - **UNSUPPORTED_IMPL_DECISION**(`lab.json`·`field-web-vitals.json` 한정): 두 report 의 *내부 JSON 구조*(필드 계층·배열 shape)는 아직 미등록 → threshold pass/fail + context block + metric 값을 담는 flat object 로 제안. `bundle.json` 은 hub §2.1.3 `ART-FE-002@1` 스키마가 정본이다. trade-off: downstream gate parser 가 확정되면 그 shape 로 조정.
| Report | Planned command | Planned artifact | NFR IDs | Threshold (initial) |
|---|---|---|---|---|
| bundle | `pnpm check:bundle` | `artifacts/performance/bundle.json` | `FE-NFR-001`, `FE-NFR-002` | initial JS gzip ≤ 200 KiB, lazy chunk gzip ≤ 120 KiB |
| lab | `pnpm test:performance` | `artifacts/performance/lab.json` | `FE-NFR-003`~`FE-NFR-005` | LCP ≤ 2.5s, CLS ≤ 0.10, named interaction ≤ 200ms + context metadata |
| field | `pnpm collect:web-vitals-evidence` | `artifacts/performance/field-web-vitals.json` | `FE-NFR-013`~`FE-NFR-015` | p75 LCP ≤ 2.5s, CLS ≤ 0.10, INP ≤ 200ms + eligible-sample metadata |
### 3. Field Web Vitals eligibility + deferred threshold
> **Trace**: D5 (hub §14.2 note, §14.3, §15.1 `FE-GATE-018`). field report 가 반드시 담아야 할 metadata 와 deferred 결정의 처리.
>
> - **UNSUPPORTED_IMPL_DECISION**: minimum eligible sample threshold 의 *수치* 는 `deferred`(telemetry baseline 확보 전 확정 불가) → 값을 임의로 지어내지 않고 미정으로 둔다. trade-off: 값이 없으면 `FE-GATE-018` 을 PASS 로 못 올리는 것을 *의도적 안전 기본값* 으로 수용.
> - **OUT_OF_BRANCH_SCOPE**: consent/privacy boundary 의 실제 구현·telemetry sink 는 [[raw/branch-notes/feature-frontend-observability-logging-trace-contract]] (`FE-OC-014`) 소유 → 여기서 필드 *요구사항* 만 열거하고 수집 pipeline 은 명세하지 않음.
- field report 필수 metadata: consent/privacy boundary flag · route-ID aggregation · 28-day window · production release ID · eligible-sample count.
- deferred 처리: telemetry baseline 획득 → owner 가 min eligible sample threshold 확정 → 그때까지 `FE-GATE-018``FAIL_UNVERIFIED` 유지(hub §14.2 note).
### 4. Gate pass-conditions + negative fixtures
> **Trace**: D6 (hub §15.1 gate rows, §15.2 negative fixture) + D3 (lab≠field invariant). 세 gate 의 pass 조건과 "실제로 동작함" 을 보이는 deliberately-failing fixture.
>
> - **UNSUPPORTED_IMPL_DECISION**: 없음 — pass 조건·negative fixture 는 hub §15.1/§15.2 에서 직접 도출.
각 gate 의 blocking scope·pass condition 은 hub §15.1 소유다. 본 브랜치가 공급하는 것은 **NFR threshold 값과 그 negative fixture** 다.
| Gate ID | 본 브랜치가 공급하는 NFR | Negative fixture |
|---|---|---|
| `FE-GATE-012@1` | `FE-NFR-001`, `FE-NFR-002` | chunk 가 budget 초과 → FAIL |
| `FE-GATE-026@1` | `FE-NFR-003`~`FE-NFR-005` | context metadata 누락 또는 하나의 named threshold 초과 → FAIL (§15.2) |
| `FE-GATE-018@1` | `FE-NFR-013`~`FE-NFR-015` | 28-day eligible sample 부족 / min-sample 미해소 → PASS 불가 |
## 엣지·실패·의존
- **실패·엣지 경로**:
- context metadata 누락 → gate FAIL (lab negative fixture, hub §15.2). context 없는 숫자는 evidence 아님.
- named threshold(LCP/CLS/INP/bundle) 하나라도 초과 → 해당 gate FAIL.
- field eligible sample 이 (deferred) min threshold 미만 → `FE-GATE-018` PASS 불가(fail-safe, fail-open 아님).
- cold vs warm cache · network variance → context 로 구분 기록, 평균으로 뭉개지 않음.
- lab 결과를 field percentile 로 오표기(D3 위반) → invariant 위반, negative fixture/answer-boundary 로 차단.
- **다른 계약 의존**:
- [[raw/branch-notes/feature-frontend-build-bundle-supply-chain-contract]] (`FE-OC-018`) — bundle report 는 이 branch 가 만드는 frozen production build artifact 를 측정. build baseline 변경 시 bundle budget 재보정 (§20 Dependency).
- [[raw/branch-notes/feature-sample-feature-slice-contract-fixture]] (`FE-OC-024`) — lab/field 측정 대상 route/data 는 sample contract-fixture slice. fixture 제거/변경 시 lab context(route/data) 갱신 (§20 Dependency).
- [[raw/branch-notes/feature-frontend-observability-logging-trace-contract]] (`FE-OC-014`) — field Web Vitals 수집 pipeline·consent/privacy boundary·telemetry sink 소유. 본 branch 는 field report 의 required metadata 만 정의하고 수집을 소비.
- [[raw/branch-notes/feature-frontend-test-taxonomy-contract]] (`FE-OC-020`) + [[raw/branch-notes/feature-frontend-ci-quality-gates-contract]] — gate CI wiring·blocking scope·artifact retention 소유. 본 branch 는 pass-condition 만 제공.
- [[raw/branch-notes/feature-api-client-response-envelope-contract]] (`FE-OC-006`, `FE-OC-009`) — `FE-NFR-007`(timeout 10s)·`FE-NFR-008`(retry ≤2) 메커니즘 소유. 본 branch 는 그 NFR 값을 target matrix 로 참조만.
## 검증해야 할 주장
| Claim | Why uncertain | How to verify | Status |
|---|---|---|---|
| bundle initial JS gzip ≤ 200 KiB & lazy chunk gzip ≤ 120 KiB | build/collector 없음 | `pnpm check:bundle``bundle.json` threshold 검사 (`FE-GATE-012`); budget 초과 negative fixture | `needs-confirmation` |
| lab LCP/CLS/interaction 이 recorded context 와 함께 threshold 이하 | lab runner·throttling profile 미확정 | `pnpm test:performance``lab.json` + reproducibility metadata (`FE-GATE-026`); negative fixture: context 누락/threshold 초과 | `needs-confirmation` |
| field p75 LCP/CLS/INP 가 28-day eligible sample 에서 threshold 이하 | RUM·consent·min-sample threshold 모두 deferred | `pnpm collect:web-vitals-evidence``field-web-vitals.json` (`FE-GATE-018`) — deferred threshold 해소 전 PASS 불가 | `needs-confirmation` |
| context 없는 숫자가 gate 에서 reject 된다 | 강제 로직 없음 | lab negative fixture(§15.2): context metadata 제거 시 gate FAIL 확인 | `planned` |
| lab 결과가 field percentile 로 표현되지 않는다 (D3) | 관례상 혼동하기 쉬움 | report schema 검사 + answer-boundary 체크(`FE-OC-026`) | `planned` |
## 관심사 커버리지 (coverage-auditor 자동 생성 — 있을 때)
- 스캐폴딩 단계: `/coverage` 실행 전 수동 행을 만들지 않는다.
## 마주친 문제
- 없음 — 스캐폴딩 단계.
## 묶음 (이 branch에서 파생된 자료)
<!-- GENERATED: artifact-imports:start -->
### 가져온 artifact 계약
| Artifact Ref | Owner | Producer | Schema Ref |
|---|---|---|---|
| `ART-FE-002@1` | [[raw/branch-notes/feature-frontend-build-bundle-supply-chain-contract]] | [[raw/branch-notes/feature-frontend-build-bundle-supply-chain-contract]] | `harness/source/artifact-schemas/ca-skeleton-frontend/bundle-report.schema.json` |
<!-- GENERATED: artifact-imports:end -->
<!-- GENERATED: project-contract-imports:start -->
## 가져온 프로젝트 계약
| Ref | Owner | 요약 | Branch 적용 |
|---|---|---|---|
| `FE-GATE-012@1` | [[raw/branch-notes/feature-frontend-build-bundle-supply-chain-contract]] | 번들 NFR threshold 초과면 release 를 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-020@1` | [[raw/branch-notes/feature-frontend-test-taxonomy-contract]] | gate 종류별 책임·fixture·artifact를 분리하고 실패를 warning으로 낮추면 안 됨 | import 참조로 적용 |
| `FE-OC-026@1` | [[raw/project-notes/ca-skeleton-frontend-operational-contract]] | 외부 답변은 evidence grade를 MUST 보존하고 목표 수치를 측정 결과처럼 말하면 안 됨 | import 참조로 적용 |
<!-- GENERATED: project-contract-imports:end -->
- 없음 — 자식 자료는 생성 후 controller가 parent Cluster와 함께 등록한다.
## 관련 일일 노트
- 없음 — daily note는 이 작업에서 수정하지 않는다.
## 완료 후 정리
- PR 링크: 없음
- 리뷰 메모: 없음
- 머지 결과 / 배포 환경: `planned`
- **wiki 추출 대상** (verified만): 없음
- **추출하지 않을 항목** (planned / documented-only / abandoned): 현재 전체