--- title: branch / experiment-nplus1-feed-api-replay (11-stage real DB and HTTP replay) source_type: branch-note status: raw id: BR-NPLUS1-PRESENTATION-PREP-002 kind: project-work-item project: nplus1-presentation-prep work_item: WI-NPLUS1-PRESENTATION-PREP-002 inherits: - DEC-NPLUS1-PRESENTATION-PREP-LEARNING-001@1 - DEC-NPLUS1-PRESENTATION-PREP-SUBSTRATE-001@1 - DEC-NPLUS1-PRESENTATION-PREP-EVIDENCE-001@1 - DEC-NPLUS1-PRESENTATION-PREP-SCOPE-001@1 refines: [] overrides: [] depends_on: [WI-NPLUS1-PRESENTATION-PREP-001] contract_packet: 1 branch: experiment-nplus1-feed-api-replay git_branch: lab/nplus1-api-replay parent_branch: related_projects: [nplus1-presentation-prep, ca-skeleton] tags: [branch, nplus1-presentation-prep, persistence, testing, api-design, postgresql, hands-on-lab] created: 2026-07-15 target_merge: status_label: review evidence_grade: locally-verified contract_packet_sha256: 88f5e6fd5ec219c6017f8213d076cde1803e63e62f978fe926f86ddcbc616f41 --- # branch: experiment-nplus1-feed-api-replay > Layer: `raw/branch-notes/` — 기존 [[raw/branch-notes/experiment-nplus1-highlight-feed]]의 L1~L16/Crown/L12 결과를, 실제 PostgreSQL과 HTTP로 한 단계씩 재현할 수 있게 만든 11-checkpoint replay 브랜치다. > 실제 Git branch는 `lab/nplus1-api-replay`다. wiki slug는 파일명 규칙에 맞춘 별도 식별자다. ## 부모 (필수) - [[raw/project-notes/nplus1-presentation-prep]] - 관련 선행 작업: [[raw/branch-notes/experiment-nplus1-highlight-feed]] — 각 랩의 원래 문제·측정·해법 사슬을 소유한다. 이 노트는 그 결과를 checkout 가능한 API/DB 학습 경로로 만드는 작업만 소유한다. ## 브랜치 계약 패킷 - **생성 시 프로젝트 개정**: `1` - **패킷 스키마**: `contract_packet: 1` - **완료 조건**: 11개 replay tag와 guide mapping을 고정하고, clean clone/worktree에서 Compose·HTTP·PostgreSQL smoke 및 full-check 상태를 재검증하며 lab profile의 deployment 비활성 증거를 기록한다. ### 상속한 프로젝트 결정 | Decision Ref | Project Summary | Branch Application | Source | |---|---|---|---| | `DEC-NPLUS1-PRESENTATION-PREP-LEARNING-001@1` | Measure→Break→Diagnose→Fix→Re-measure→Generalize를 랩 완료 루프로 사용한다. | 11개 checkout point마다 동일한 reset→HTTP→DB 관찰 절차를 제공한다. | [[raw/project-notes/nplus1-presentation-prep]] | | `DEC-NPLUS1-PRESENTATION-PREP-SUBSTRATE-001@1` | ca-tmpl production substrate를 사용하고 학습 API·측정 경로는 profile과 sibling 경로로 격리한다. | `/api/lab/**`와 marker-owned fixture를 `lab` profile에 한정한다. | [[raw/project-notes/nplus1-presentation-prep]] | | `DEC-NPLUS1-PRESENTATION-PREP-EVIDENCE-001@1` | local·Testcontainers 결과는 locally-verified로만 기록하고 prod evidence로 승격하지 않는다. | Compose/Testcontainers 결과를 `locally-verified`로만 기록한다. | [[raw/project-notes/nplus1-presentation-prep]] | | `DEC-NPLUS1-PRESENTATION-PREP-SCOPE-001@1` | same-store CQRS-lite까지를 현재 범위로 두고 full CQRS는 ca-tmpl contract escalation 이후에만 허용한다. | Crown 1-query와 L12 2-query endpoint를 병존시킨다. | [[raw/project-notes/nplus1-presentation-prep]] | ### 브랜치 지역 결정 기존 branch-local 결정은 아래 `## Decision Evidence Map`의 D1~D5가 소유한다. ### 선언한 예외 - 없음. ## 목표 마지막 Crown/L12 상태만 남은 작업 트리에서는 L1의 컬렉션 N+1부터 Crown의 통합 쿼리까지를 HTTP와 실제 DB로 순서대로 관찰하기 어렵다. 이를 11개의 독립 checkout point로 고정한다. - 각 tag에서 Docker PostgreSQL을 띄우고 lab fixture를 reset한 뒤 API 응답과 DB row를 직접 확인한다. - 정상 `/api/feed` 동작은 바꾸지 않고, `lab` profile에서만 학습용 `/api/lab/**` 경로를 제공한다. - Crown의 1-query read와 L12의 same-store CQRS-lite 2-query read를 같은 것으로 포장하지 않고, 별도 endpoint와 문서로 비교 가능하게 둔다. - 이슈: 사용자 요청 — N+1 랩을 실 API/DB로 단계별 학습 - PR: 없음 (로컬 replay branch) ## 범위 ### 포함 범위 - L1, L2, L3, L4, L5, L6, L14, L15, L16, Crown, L12 순서의 정확히 11개 commit/tag. - lab profile, marker-safe fixture, HTTP 관찰 endpoint, PostgreSQL 확인 절차와 단계별 가이드. - Crown 및 L12의 실제 PostgreSQL/Testcontainers 검증과 이력 tag의 L1 smoke 검증. ### 제외 범위 - `/api/feed`의 production 계약 또는 기본 보안 정책 변경. - L12를 별도 read store·outbox 동기화가 있는 Full CQRS로 확장. - 프로덕션 배포, 부하/latency SLA, 성능 수치의 운영 일반화. ## 근거 | Source | 정당화하는 결정 | |---|---| | [[raw/official-docs/cqrs-pattern-azure-architecture-center]] | D3: 같은 저장소에서 read/write 논리를 분리하는 CQRS-lite와 별도 저장소 CQRS를 구분한다. | | [[raw/official-docs/spring-data-jpa-projections-spring-official]] | D3: application 반환용 DTO/projection을 통해 aggregate hydration과 read shape를 분리하는 선택지를 뒷받침한다. | | [[raw/official-docs/test-taxonomy-testcontainers-official]] | D4: in-memory 대체물이 아닌 Docker의 실제 PostgreSQL로 integration evidence를 얻는 선택을 뒷받침한다. | ## TODO - [x] 11개 replay commit/tag를 사용자 지정 학습 순서로 고정 — 등급: `actually-implemented` - [x] `lab` profile에서 실제 DB reset 및 HTTP feed 관찰 경로 제공 — 등급: `actually-implemented` - [x] Crown/L12 최신 상태의 Docker HTTP + PostgreSQL smoke 수행 — 등급: `locally-verified` - [x] L1 historical tag의 독립 Docker HTTP smoke 수행 — 등급: `locally-verified` - [x] 누적 focused Gradle suite 및 dependency/public-path/env verifier 수행 — 등급: `locally-verified` - [ ] 전체 `./gradlew check`를 branch 변경과 무관한 base architecture failure 없이 통과 — 등급: `needs-confirmation` (아래 §검증 기록 참조) ## 진행 중 메모 - application repository의 replay branch는 `54cf7e7` / `nplus1-replay-l12`까지 tag가 완료된 상태다. - full `check`의 유일한 base failure는 별도 수정 범위로 남겼다. 이 노트의 evidence grade는 local Docker/Gradle 검증까지만 나타낸다. ## 결정 사항 - 2026-07-15 D1: 학습 순서를 원래 구현 시간순이 아니라 `L1 → L2 → L3 → L4 → L5 → L6 → L14 → L15 → L16 → Crown → L12`의 11개 tag로 고정한다. / 이유: 사용자가 각 commit으로 이동해 API/DB를 직접 관찰해야 한다. / 검토한 대안: 마지막 코드 하나와 문서만 제공. / 근거: 사용자 요구; 구체 tag 순서는 외부 자료가 정하지 않으므로 `UNSUPPORTED_IMPL_DECISION`. - 2026-07-15 D2: 공개 학습 reset은 `lab` profile의 `/api/lab/**`에만 두고, anonymous access는 `lab:reset` 하나에만 허용한다. / 이유: 실제 HTTP 재현은 가능해야 하지만 정상 profile의 feed/권한 정책을 약화하면 안 된다. / 검토한 대안: `/api/feed`에 reset/debug 파라미터 추가 또는 lab profile 전체 anonymous 허용. / 근거: D4 및 사용자 범위; profile/permission의 구체 모양은 `UNSUPPORTED_IMPL_DECISION`. - 2026-07-15 D3: Crown은 1 native query API, L12는 same-store CQRS-lite 2-query read port API로 병존시킨다. / 이유: 쿼리 수 최소화와 application read-model 분리는 서로 다른 선택지다. / 검토한 대안: L12가 Crown endpoint를 조용히 대체. / 근거: `AZURE-CQRS-C2`, `AZURE-CQRS-C3`, `SPRING-PROJ-C4`. - 2026-07-15 D4: Testcontainers 검증에 더해 fresh Docker Compose PostgreSQL에서 HTTP response와 SQL row count를 확인한다. / 이유: test-only assertion으로는 사용자가 직접 API/DB를 따라 보는 목표를 충족하지 못한다. / 검토한 대안: integration test 결과만 보관. / 근거: `TC-OFFICIAL-C1`, `TC-OFFICIAL-C3`, `TC-OFFICIAL-C5`. - 2026-07-15 D5: Hibernate `addScalar`를 Java SQL compile-time checker로 설명하지 않는다. / 이유: 이는 native-query result extraction의 runtime type mapping이며 SQL 문법/컬럼 존재성은 실행 시점에 검증된다. / 검토한 대안: `addScalar`가 SQL 안전성을 보장한다고 문서화. / 근거: 구현 관찰; `UNSUPPORTED_IMPL_DECISION`. ## 결정-근거 매핑 | Decision ID | Decision | 선택 조건 (언제 이 결정 / 언제 대안) | Supporting Claims | Evidence Strength | Open Risk | |---|---|---|---|---|---| | D1 | 11개의 checkout 가능한 learning checkpoint | 사용자가 단계별 API/DB 관찰을 원할 때; 단일 현재 상태만 필요하면 하나의 branch 상태로 충분 | User request; `UNSUPPORTED_IMPL_DECISION` | user-scoped requirement | history를 rewrite하면 tag/문서 매핑도 함께 갱신해야 함 | | D2 | lab-only reset/API/anonymous boundary | 로컬 학습 profile일 때만; 정상 runtime에서는 lab bean/route 자체를 등록하지 않음 | `TC-OFFICIAL-C1`, `TC-OFFICIAL-C3`; `UNSUPPORTED_IMPL_DECISION` | official-vendor-doc + unit/HTTP local verification | lab profile을 production에 실수로 활성화하지 않는 운영 절차는 별도 확인 필요 | | D3 | Crown 1-query와 L12 2-query CQRS-lite 병존 | endpoint-specific optimization을 비교할 때; 별도 store가 필요하면 Full CQRS contract를 별도 설계 | `raw/official-docs/cqrs-pattern-azure-architecture-center.md#AZURE-CQRS-C2`, `#AZURE-CQRS-C3`, `raw/official-docs/spring-data-jpa-projections-spring-official.md#SPRING-PROJ-C4` | official-vendor-doc | L12은 keyset/visibility를 Crown처럼 모두 포함하지 않음 | | D4 | real PostgreSQL HTTP+DB smoke | SQL dialect, container wiring, public response shape를 함께 확인할 때 | `raw/official-docs/test-taxonomy-testcontainers-official.md#TC-OFFICIAL-C1`, `#TC-OFFICIAL-C3`, `#TC-OFFICIAL-C5` | official-vendor-doc + local runtime evidence | production traffic/permissions을 검증한 것은 아님 | | D5 | `addScalar` compile-time 보장 부정 | native query mapping 설명 시 항상 적용 | Local code/runtime behavior; `UNSUPPORTED_IMPL_DECISION` | locally verified implementation fact | native SQL의 syntax/plan error는 CI compile이 아니라 query execution에서 발견됨 | ## 구현 가이드 ### 1. 고정 replay checkpoint > **Trace**: D1. 단계 순서와 tag 이름은 사용자 학습 요구에서 정한 `UNSUPPORTED_IMPL_DECISION`이다. 각 checkpoint의 원래 N+1 원인/해결 설명은 [[raw/branch-notes/experiment-nplus1-highlight-feed]]를 참조한다. | 순서 | Stage | Commit | Tag | checkout 후 주 관찰점 | |---:|---|---|---|---| | 1 | L1 | `e68dd67` | `nplus1-replay-l1` | lazy highlights 컬렉션 N+1 | | 2 | L2 | `138eb67` | `nplus1-replay-l2` | EAGER ToOne fetch 수 | | 3 | L3 | `dc7495c` | `nplus1-replay-l3` | two-bag fetch의 예상 실패 | | 4 | L4 | `f257196` | `nplus1-replay-l4` | collection fetch join + paging의 in-memory paging | | 5 | L5 | `be2a123` | `nplus1-replay-l5` | batch fetch paging | | 6 | L6 | `cad3c15` | `nplus1-replay-l6` | DTO scalar projection, entity load 0 | | 7 | L14 | `26b196c` | `nplus1-replay-l14` | window query로 parent별 Top-N | | 8 | L15 | `8ff0771` | `nplus1-replay-l15` | keyset cursor | | 9 | L16 | `3310897` | `nplus1-replay-l16` | viewer visibility + keyset | | 10 | Crown Task 4 | `3f0b82e` | `nplus1-replay-crown` | Top-N + keyset + visibility one query | | 11 | L12 | `54cf7e7` | `nplus1-replay-l12` | CQRS-lite projection port, parent/child two queries | 각 stage의 세부 실습은 application repository의 `docs/superpowers/plans/*nplus1*lab-guide.md`, `docs/notes/L*.md`, `docs/notes/crown.md`를 사용한다. `L12`를 마지막 tag로 둔 것은 원래 번호가 아니라 이 replay의 학습 순서다. ### 2. lab profile의 API/권한 경계 > **Trace**: D2, D4 / `TC-OFFICIAL-C1`, `TC-OFFICIAL-C3`. > > - **UNSUPPORTED_IMPL_DECISION**: `lab` profile과 `lab:reset` permission 이름, marker 소유 방식, anonymous allowlist의 구체 구현은 외부 자료가 정하지 않는다. 정상 profile과 분리된 학습 reset 및 최소 권한 허용이라는 사용자 범위를 우선했다. | 항목 | replay 계약 | |---|---| | profile | lab controller/usecase/fixture는 local `lab` profile에만 등록된다. | | normal runtime | 기존 advisor를 유지하고 lab usecase/route를 등록하지 않는다. `/api/feed`의 기존 계약을 바꾸지 않는다. | | reset authorization | `POST /api/lab/feed:reset?count=N`은 `lab:reset`을 선언한다. lab profile의 security advisor는 `AnonymousAuthenticationToken`을 인식해 anonymous에는 `lab:reset` 하나만 허용하고, 다른 permission은 거부한다. unit+HTTP로 확인했다. | | fixture ownership | `created_by = nplus1-lab` marker 데이터만 삭제/재생성한다. | | input guard | `page=10001`은 HTTP 400, `VALIDATION_FAILED`다. | ### 3. Crown과 L12의 의도적 차이 > **Trace**: D3 / `AZURE-CQRS-C2`, `AZURE-CQRS-C3`, `SPRING-PROJ-C4`. | 경로 | 목적 | SQL 관찰값 | 포함 범위 | |---|---|---|---| | `GET /api/lab/feed` at Crown/L12 | Crown Task 4 최적화 | `CROWN_TASK4_ONE_QUERY`, prepared statement 1, entity load 0 | visible parent keyset + Top-3 child를 하나의 native query로 읽음 | | `GET /api/lab/feed/read-model` at L12 | CQRS-lite read model | parent projection 1 + child Top-3 query 1, integration test에서 entity/collection hydration 0 | same store의 application query port; Crown을 대체하지 않음 | L12의 native child mapping에 사용한 `addScalar`는 runtime 결과 타입 매핑이다. SQL 문자열의 문법, table/column 이름, plan을 Java compiler가 검증하게 만드는 기능은 아니다. 따라서 native SQL은 Testcontainers/실제 PostgreSQL 실행으로 검증한다. ## 검증 기록 ### Docker HTTP + PostgreSQL smoke — final L12 tag fresh `nplus1-final` Compose stack에서 `nplus1-replay-l12`(`54cf7e7`)을 실행했다. | 수행 | 결과 | 등급 | |---|---|---| | `POST /api/lab/feed:reset?count=100` | `success=true`, feed item 100개, highlight 1,961개 | `locally-verified` | | `GET /api/lab/feed?size=20&viewer=lab-user-008` | item 20개, `strategy=CROWN_TASK4_ONE_QUERY`, `prepared=1`, `entityLoads=0`, parent당 Top-3 최대 3개 | `locally-verified` | | `GET /api/lab/feed/read-model?page=0&size=20` | `success=true`, item 20개, parent당 Top-3 최대 3개 | `locally-verified` | | `GET` with `page=10001` | HTTP 400, `VALIDATION_FAILED` | `locally-verified` | | PostgreSQL `psql` | `created_by = nplus1-lab` row count 100 | `locally-verified` | | anonymous authorization | `AnonymousAuthenticationToken`의 `lab:reset`만 허용하고 다른 permission은 거부하는 unit+HTTP 검증 | `locally-verified` | ### Historical L1 smoke fresh stack에서 `nplus1-replay-l1`(`e68dd67`)을 별도로 실행했다. | 수행 | 결과 | 등급 | |---|---|---| | reset `count=10` | `success=true` | `locally-verified` | | feed request | item 10개, `strategy=L1_LAZY_HIGHLIGHTS`, `prepared=24`, `collectionFetch=10` | `locally-verified` | ### Focused regression suite 다음 filtered cumulative suite는 `BUILD SUCCESSFUL`, test result의 `failures=0`, `errors=0`이었다. ```bash cd /home/donghyeon/workspace/ca-tmpl-nplus1-api/src ./gradlew spotlessApply :application-core:test --tests dev.caskeleton.application.feed.GetFeedReadModelUseCaseTest --tests dev.caskeleton.application.feed.lab.LabFeedUseCaseTest --tests dev.caskeleton.application.feed.lab.LabFeedCursorTest :adapter:inbound:web:test --tests dev.caskeleton.adapter.inbound.web.controller.lab.LabFeedControllerTest :app-bootstrap:test --tests dev.caskeleton.bootstrap.contract.DeveloperExperienceContractTest --tests dev.caskeleton.adapter.outbound.jpa.feed.FeedReadModelUseCaseIT --tests dev.caskeleton.adapter.outbound.jpa.feed.FeedCrownIT --tests dev.caskeleton.adapter.outbound.jpa.feed.FeedVisibilityIT --tests dev.caskeleton.adapter.outbound.jpa.feed.FeedKeysetIT --tests dev.caskeleton.adapter.outbound.jpa.feed.FeedTopNIT --tests dev.caskeleton.adapter.outbound.jpa.feed.FeedProjectionIT --tests dev.caskeleton.adapter.outbound.jpa.feed.FeedBatchFetchIT --tests dev.caskeleton.adapter.outbound.jpa.feed.FeedFetchJoinPagingIT --tests dev.caskeleton.adapter.outbound.jpa.feed.FeedToOneEagerIT --tests dev.caskeleton.adapter.outbound.jpa.feed.FeedMultipleBagIT --tests dev.caskeleton.bootstrap.lab.LabFeedPersistenceIT ``` 명령에 포함된 L1~L6, L14~L16, Crown, L12의 stage integration test class는 모두 통과했다. 다음 verifier도 통과했다. ```bash ./gradlew verifyCleanArchitectureDependencies verifyPublicPathSnapshot verifyEnvKeys ``` ### Full check의 기준선 실패 `./gradlew check`에는 정확히 하나의 잔여 architecture failure가 있었다. 이는 이 replay branch가 수정하지 않은 base commit `6f0b0d6`의 `IdempotencyRecordEntity.requestHash`에 있는 `columnDefinition = "char(64)"`가 vendor-neutral entity rule을 위반한 것이다. 따라서 이 노트는 full check를 green이라고 주장하지 않으며, 재현 브랜치의 실패로 귀속하지 않는다. ## 엣지·실패·의존 - **실패·엣지 경로**: lab profile 밖에서 lab reset을 사용하려 하면 endpoint/usecase가 등록되지 않아야 한다. lab profile에서도 anonymous access는 `lab:reset` 하나에만 한정되고, 다른 permission은 거부된다. - **실패·엣지 경로**: native query의 `addScalar` 타입이 결과와 맞지 않거나 SQL이 잘못되면 compile이 아니라 integration/runtime 실행에서 실패한다. - **실패·엣지 경로**: L12 read model이 Crown과 동등한 visibility/keyset solution이라고 가정하면 안 된다. L12은 same-store read port의 2-query projection이고 Crown 최적화 endpoint는 유지된다. - **다른 계약 의존**: [[raw/branch-notes/experiment-nplus1-highlight-feed]]의 각 랩 의미와 [[raw/branch-notes/feature-application-query-bypass-contract]]의 CQRS-lite/read-port 경계를 소비한다. Full CQRS physical read store는 후자 D2의 escalation 범위다. - **검증 환경 의존**: `DeveloperExperienceContractTest`가 root `AGENTS.md` 존재를 요구해 replay worktree에 일시적인 ignored bridge를 두고 test 후 제거했다. 이는 application commit에 포함되지 않는다. ## 검증해야 할 주장 | Claim | Why uncertain | How to verify | Status | |---|---|---|---| | 11개 tag가 다른 machine에서도 compose/API guide대로 재현된다 | 로컬 Docker, Gradle cache, port 상태에 의존 | 깨끗한 clone/worktree에서 tag별 compose smoke 수행 | `needs-confirmation` | | lab profile이 배포 환경에서 활성화되지 않는다 | local profile boundary는 production deployment policy를 증명하지 않음 | deployment manifest/env registry audit | `needs-confirmation` | | `IdempotencyRecordEntity` failure를 수정한 뒤 full `check`가 green이 된다 | 현재 branch가 해당 base failure를 고치지 않음 | 별도 base-fix branch에서 full check 실행 | `planned` | ## 마주친 문제 - `DeveloperExperienceContractTest`가 checkout worktree root의 `AGENTS.md`를 요구했다. - 원인: replay worktree의 contract discovery 조건. - 시도: test 실행 중 ignored bridge를 일시적으로 제공. - 해결: test 통과 후 bridge를 삭제했고 application history에는 포함하지 않았다. - 별도 오류 노트: 아래 Cluster의 raw error 노트. - full `check`가 `IdempotencyRecordEntity.requestHash` vendor-specific `columnDefinition`에서 멈췄다. - 원인: base `6f0b0d6`에 이미 존재한 rule violation. - 해결: replay scope 밖으로 남기고 base failure로 명시했다. ## 묶음 - [[raw/interviews/crown-one-query-vs-cqrs-lite-read-model]] - [[raw/interviews/native-query-addscalar-runtime-validation]] - [[raw/errors/developer-experience-contract-agents-bridge-2026-07-15]] - [[raw/errors/idempotency-column-definition-base-check-failure-2026-07-15]] - [[raw/blog-topics/nplus1-lab-checkoutable-api-replay-2026-07-15]] 아래 raw leaf는 생성되었고, 이 branch를 `## Parent` upward link로 가진다. 두 번째 블로그 주제는 별도 raw 문서로 추출하지 않았다. ### Sub-branches (세부 작업) - 없음 — 11개 checkpoint는 하나의 replay branch history로 관리한다. ### 오류 기록 (이 branch 작업 중 발생) - [[raw/errors/developer-experience-contract-agents-bridge-2026-07-15]] — replay worktree root discovery와 임시 ignored bridge. - [[raw/errors/idempotency-column-definition-base-check-failure-2026-07-15]] — base `6f0b0d6`의 vendor-neutral entity rule failure. ### 면접 준비 (이 작업에서 나올 수 있는 면접 질문) - [[raw/interviews/crown-one-query-vs-cqrs-lite-read-model]] — one-query optimization과 same-store CQRS-lite를 구분하는 기준. - [[raw/interviews/native-query-addscalar-runtime-validation]] — native mapping type과 SQL compile-time validation의 차이. ### job-posting tie-ins (이 작업에서 파생된 글감) - [[raw/blog-topics/nplus1-lab-checkoutable-api-replay-2026-07-15]] — 테스트 코드를 실제 API/DB 학습 환경으로 변환한 방법. - `raw/blog-topics/crown-query-and-cqrs-lite-boundary-2026-07-15.md` — 이번 capture에서는 별도 raw 문서로 추출하지 않았다. - derived blog: 생성 전. canonical 검증 후 `wiki/blog/` 후보를 결정한다. ## 관련 일일 노트 - 해당 없음 — 이 캡처 시점에는 별도 daily-note를 만들지 않았다. ## 완료 후 정리 - PR 링크: 없음. - 리뷰 메모: 11개 replay tag와 L1/final Docker smoke, focused suite, architecture/public-path/env verifier를 local에서 확인했다. - 머지 결과 / 배포 환경: local Docker Compose + local PostgreSQL만 검증. production 배포 검증 없음. - **wiki 추출 대상** (verified만, `wiki/projects/`로만 추출): - `actually-implemented` 항목: lab-profile replay API, 11 stage tag catalog, Crown/L12 분리 경로. - `locally-verified` 항목: L1 historical smoke, final Docker HTTP/PostgreSQL smoke, filtered Gradle suite/verifier. - `prod-verified` 항목: 없음. - **추출하지 않을 항목** (planned / documented-only / abandoned): base `IdempotencyRecordEntity` rule failure 해결, production profile/deployment verification.