fix: 하네스 제거 및 keycloak 문서 보강

This commit is contained in:
DongHyeonka
2026-07-25 12:53:13 +09:00
parent 6c53ded9cb
commit d71669eb59
2329 changed files with 138239 additions and 172816 deletions
@@ -1 +0,0 @@
../../vault/40-publish/interviews/archunit-manual-importer-vs-analyzeclasses.md
@@ -0,0 +1,43 @@
---
title: interview-prep / archunit-manual-importer-vs-analyzeclasses
source_type: interview-prep
status: raw
related_branches: [feature-test-taxonomy-fixture-contract, feature-architecture-enforcement-rules]
related_projects: [ca-skeleton]
tags: [interview-prep, ca-skeleton, archunit, test-taxonomy, do-not-include-tests, manual-importer]
created: 2026-06-19
status_label: collecting
---
# interview-prep: archunit-manual-importer-vs-analyzeclasses
> Layer: `raw/interviews/` — 면접 질문 원본 수집·연구 노트. 다듬어진 답변은 `/interviewize` 후 `wiki/interview/` 에 별도 작성.
## Parent / 부모
- [[raw/branch-notes/feature-test-taxonomy-fixture-contract]] — Task 2 에서 "contract·architecture 레벨 test 는 Testcontainers 의존 금지" rule 을 구현할 때 부딪힌 핵심 결정.
## 질문 / Question
- 질문 원문: ArchUnit 에서 `@AnalyzeClasses(importOptions = DoNotIncludeTests.class)``new ClassFileImporter().importPackages(...)` 를 각각 언제 쓰나요? 규칙의 _대상_ 이 test 코드 자체일 때 왜 `@AnalyzeClasses` 만으로는 안 되나요?
- 출처: 예상 질문 (실 면접 아님).
- 받은 날짜·맥락: 아직 없음 — 2026-06-19 test-taxonomy-fixture-contract 구현에서 도출.
## 질문 의도 추론 / Why this question
- 핵심 평가 대상:
- ArchUnit 의 import scope 가 _규칙이 무엇을 볼 수 있는가_ 를 결정한다는 것을 이해하는지. 규칙 본문(`noClasses().should()...`)만 보고 "왜 안 잡히지?" 를 import 설정에서 진단할 수 있는지.
- production 규칙과 test-에-대한 규칙을 한 suite 에 섞었을 때 생기는 vacuous-pass 위험을 인지하는지.
## 답변 골자 / Answer skeleton (raw)
- ca-tmpl 의 production 아키텍처 suite(`CleanArchitectureTest`, `DisabledAdapterArchitectureTest`, `NamingConventionTest`)는 전부 `@AnalyzeClasses(packages = "dev.caskeleton", importOptions = ImportOption.DoNotIncludeTests.class)` — production bytecode 만 본다. 그래야 "domain 은 Spring 의존 금지" 같은 규칙이 test util 의 Spring import 때문에 오탐하지 않는다.
- 그런데 test-taxonomy 계약(§테스트 계약 #4: "contract·architecture _테스트_ 가 Testcontainers 에 의존하면 실패")은 _대상이 test 클래스_ 다. `DoNotIncludeTests` 가 그 클래스를 import 단계에서 제거하므로 `@AnalyzeClasses` 규칙은 영원히 빈 subject 를 받아 vacuously pass 한다.
- 해법: 규칙을 `static final ArchRule` 필드로 정의하고, 별도 `@Test` 에서 `new ClassFileImporter().importPackages("dev.caskeleton.bootstrap.contract")` 로 test bytecode 를 명시적으로 로드해 `rule.evaluate(corpus)` 를 직접 호출한다. `ArchitectureViolationFixtureTest` 가 violation fixture 를 같은 방식으로 로드하는 패턴과 동일하다.
- `importPackages``.class` 바이트를 직접 읽어 JVM class loading 을 하지 않으므로 `testCompileOnly` 타입(Testcontainers 등)이 runtime 에 resolve 되지 않아도 안전하다.
- vacuity 방어: 규칙을 정의했으면 _반드시_ positive control 을 둔다 — 본 작업에서는 Testcontainers 를 실제로 쓰는 `bootstrap.integration` 패키지에 같은 규칙을 평가해 `hasViolation() == true` 를 단언했다. (관련: [[raw/interviews/archunit-static-analysis-limits]], [[raw/blog-topics/archunit-violations-as-data-pattern-2026-05-28]])
## 더 팔 거리 / Follow-ups
- `allowEmptyShould(true)` 를 언제 쓰고 왜 위험한가 (빈 subject 를 의도적으로 허용 → positive control 없으면 vacuous). 관련 errors: [[raw/errors/archunit-empty-should-anchor-2026-05-27]].
- production 규칙(`production_code_does_not_depend_on_test_fixtures`)은 `DoNotIncludeTests` 위에서 동작하는데 어떻게 meta-verify 했나 → fixtureleak violation 패키지를 manual importer 로 로드해 같은 rule 객체를 평가.
@@ -1 +0,0 @@
../../vault/40-publish/interviews/archunit-static-analysis-limits.md
@@ -0,0 +1,101 @@
---
title: interview-prep / archunit-static-analysis-limits
source_type: interview-prep
status: raw
related_branches: [feature-architecture-enforcement-rules, feature-application-port-usecase-contract]
related_projects: [ca-skeleton]
tags: [interview-prep, ca-skeleton, archunit, fitness-function, static-analysis, reflection]
created: 2026-05-28
status_label: collecting
---
# interview-prep: archunit-static-analysis-limits
> Layer: `raw/interviews/` — 면접 질문 원본 수집·연구 노트. 다듬어진 답변은 `/interviewize` 후 `wiki/interview/` 에 별도 작성.
## Parent / 부모
- [[raw/branch-notes/feature-architecture-enforcement-rules]] — D11 (`ApplicationContext` 금지) + D12 (string-key bypass 한계) + Claims to Verify 의 violations-as-data 보완.
- [[raw/project-notes/ca-skeleton-operational-contract]] — ca-tmpl skeleton 의 boundary 자동 검증 정책 맥락.
## 질문 / Question
- 질문 원문: ArchUnit 같은 정적 분석 기반 fitness function 의 _한계_ 를 인지하면서 어떻게 _믿을 수 있게_ 만들었나요? runtime reflection 우회 / 빈 scope 의 vacuous pass / generated code 처리 같은 케이스는 어떻게 다뤘나요?
- 출처: 예상 질문 (실 면접 아님).
- 받은 날짜·맥락: 아직 없음.
## 질문 의도 추론 / Why this question
- 핵심 평가 대상:
- 정적 분석의 _한계__구체적_ 으로 인지하는지 (단순히 "있다" 가 아니라 _어떤_ 코드 패턴이 catch 되지 않는지).
- vacuous pass 함정 (scope 가 비어 있을 때도 SUCCESS 반환) 을 인지하고 _negative test 로 보완_ 했는지.
- runtime bypass 를 _code review checklist_ / Sonar / Spring Modulith 같은 _보완 도구_ 로 메우는 감각.
- generated code (MapStruct, Lombok, Spring AOT) 와 fitness function 의 충돌 처리.
- 함정 / 흔히 빠지는 답변 패턴:
- "ArchUnit 으로 다 막을 수 있다" — reflection / `ApplicationContext#getBean(String)` / `Class.forName(String)` 의 catch 불가 인식 없음.
- "rule 이 있으면 catch 된다고 믿는다" — vacuous pass 가능성 인지 못함.
- generated code 를 rule 의 _예외_ 로 처리하지 못해 build 가 깨지는 시나리오.
- 따라올 만한 후속 질문:
- `noClasses().that(...)` rule 이 빈 scope 에서 어떤 동작인가요? 어떻게 _vacuous pass_ 를 막을 수 있나요?
- `ApplicationContext#getBean(String)` 은 왜 ArchUnit 이 못 잡나요? `getBean(Class)` 는 어떻게 다른가요?
- MapStruct generated mapper 를 mapper boundary rule 에 어떻게 _예외_ 처리하나요? Spring AOT 와는?
- custom `ArchCondition` 은 언제 필요하나요? 예시?
## 답변 재료 / Raw answer material
- 사실 1 (근거: `feature-architecture-enforcement-rules.md` D11): ArchUnit 의 banned-class rule (`noClasses().that(pkg).should().dependOnClassesThat().haveFullyQualifiedName("...ApplicationContext")`) 은 _class literal_ 이 bytecode 에 박힌 의존만 catch. ca-tmpl 의 `application_does_not_depend_on_application_context` 가 이 패턴.
- 사실 2 (근거: `feature-architecture-enforcement-rules.md` D12 + `raw/official-docs/archunit-user-guide.md` 의 negative claim): ArchUnit 은 bytecode 의 method/constructor call 만 본다. _string content_ 자체는 bytecode 에 노출되지만 의미 분석은 안 한다. 결과적으로 `getBean("repository")` 같은 string-key bean lookup 과 `Class.forName(System.getenv("FOO"))` 같은 dynamic target 은 catch 불가.
- 사실 3 (근거: 파생 에러 [[raw/errors/archunit-test-scope-sample-ticket-inclusion-2026-05-28]]): ArchUnit 의 vacuous pass 함정 — `@AnalyzeClasses(packages = ...)` 가 패키지 _필터_ 이고 _scan source_ 가 아니다. 분석 대상이 0개일 때도 rule 은 SUCCESS. ca-tmpl 의 첫 시도에서 `app-bootstrap` 의 test classpath 가 `sample-ticket` 을 안 보아 새 rule 이 vacuously pass 한 사례.
- 사실 4 (근거: `feature-architecture-enforcement-rules.md` Claims to Verify 마지막 행 `actually-implemented` + `feature-application-port-usecase-contract.md` 구현 결과 round 2): ca-tmpl 의 보완 — Spring Modulith `example/ninvalid` 패턴 차용. `src/app-bootstrap/src/test/java/.../violations/` 에 6 fixture + `ArchitectureViolationFixtureTest` 에 6 negative test. 각 rule 의 _실 catch 동작_ 을 commit 으로 박음.
- 사실 5 (근거: `feature-architecture-enforcement-rules.md` D9 + `raw/official-docs/mapstruct-generated-annotation-official.md#MS-ANNOT-C1`): MapStruct generated mapper 는 `javax.annotation.processing.Generated` 어노테이션 부착. ArchUnit `.and().areNotAnnotatedWith(Generated.class)`_annotation-based exemption_ 가능. annotation FQN 주의 — MapStruct 와 Spring AOT (`org.springframework.aot.generate.Generated`) 가 다른 클래스.
- 사실 6 (근거: `feature-application-port-usecase-contract.md` D14 + `CleanArchitectureTest#notDeclareKeyedIdempotency`): annotation parameter 의 enum value 검사는 ArchUnit DSL 로 표현 불가 → custom `ArchCondition<JavaClass>` 작성. `JavaAnnotation.get("idempotency")``JavaEnumConstant` 를 반환하므로 _reflection 없이 bytecode 만으로_ enum 값 catch.
- 내가 직접 한 경험:
- ca-tmpl 의 14개 ArchUnit rule 중 `D11` 의 banned-class rule + `D14` 의 custom `ArchCondition` 작성.
- vacuous pass 사례 발견 → `testImplementation project(':sample-ticket')` 으로 scope 확장 → `ArchitectureViolationFixtureTest` 6 negative test 로 catch 동작 보증.
- Lombok 금지 rule (`lombok..` 추가 to `domain_is_pure`) — Lombok 이 generated bytecode 를 만들어서 domain 의 framework 독립성을 흐릴 위험 차단.
- 트레이드오프:
- **정적 분석 한계 인정 vs 만능 도구화**: ArchUnit 으로 _대부분_ 의 boundary 위반은 catch 가능. 단 string-key bypass / reflection / DI runtime lookup 은 catch 불가 — _code review checklist_ + Sonar custom rule 로 보완. ca-tmpl 은 후자를 documented-only 로 유지.
- **rule 작성 비용 vs 위반 catch 정밀도**: 단순 DSL rule 은 빠르지만 vacuous pass 위험. custom condition + negative test fixture 는 catch 정밀도 ↑ 이지만 작성/유지 비용 ↑. ca-tmpl 은 _core rule 14개_ 에만 fixture 적용 (정밀도 우선).
- **generated code exemption**: 너무 넓은 exemption (예: `package..mapper..` 통째 제외) 은 hand-written 위반도 함께 통과. annotation-FQN 기반 exemption 이 _좁고 안전_ — MapStruct `@Generated` vs Spring AOT `@Generated` 의 FQN 차이 인식.
- 한계 / "이건 안 해봤다":
- Sonar custom rule / IDE inspection 으로 string-key bypass 를 _얼마나_ 보완할 수 있는지 정량 측정 미수행.
- Spring Modulith verifier 의 named interface 검증과 ca-tmpl 의 ArchUnit rule 의 _중복/대체_ 비교 미수행.
- `ApplicationContext#getBean(Class)` class-literal 호출이 ca-tmpl 의 D11 rule 로 _실제_ catch 되는지는 negative test 로 보증했지만, 실 사업 도메인에서의 false-positive 비율 측정 안 함.
## Sources / 근거
- [[raw/branch-notes/feature-architecture-enforcement-rules]] — D9, D11, D12 + Claims to Verify status.
- [[raw/branch-notes/feature-application-port-usecase-contract]] — D14 custom ArchCondition + violations-as-data round 2.
- [[raw/errors/archunit-test-scope-sample-ticket-inclusion-2026-05-28]] — vacuous pass 의 실 사례.
- [[raw/official-docs/archunit-user-guide]] — `JavaAnnotation` / `JavaEnumConstant` / `EvaluationResult` 공식 (`ARCHUNIT-UG-C5`, `ARCHUNIT-UG-C6`).
- [[raw/official-docs/mapstruct-generated-annotation-official]] — `@Generated` FQN (`MS-ANNOT-C1`, `MS-ANNOT-C2`).
- [[raw/company-tech-blogs/spring-modulith-archunit-generated-exemption-and-violations-as-data]] — `annotatedWith(Generated.class)` predicate + `example/ninvalid` 패턴 (`SPRING-MOD-AU-C1`, `SPRING-MOD-AU-C2`).
## 미해결 / Unknown
- 모르는 것: Sonar / SpotBugs custom rule 이 _string-key bean lookup_ 류를 얼마나 잘 catch 하는지 — _Sonar Quality Profile_ 의 표준 rule set 보강 필요.
- 모르는 것: Spring Modulith named interface 검증의 internal model 이 ArchUnit 의 `JavaClass` 와 어떻게 다른지 — Modulith 도입 시 중복 rule 청산 비용.
- 확인 방법: `feature-ci-quality-gates-contract` 후속 branch 에서 Sonar custom rule + Modulith verifier 도입 PoC.
## 답변 경계 / Answer boundary
- 자신 있게 말할 수 있는 범위:
- ca-tmpl 의 14 ArchUnit rule + 6 negative test fixture 의 _직접 구현_ 범위.
- vacuous pass 함정 두 갈래 (production 0개 매칭 vs scope 0개 매칭) 의 _구체 사례__보완 방법_.
- custom `ArchCondition` 으로 annotation parameter (enum value) catch 한 D14 의 구현 패턴.
- MapStruct `@Generated` exemption 의 annotation-FQN 기반 패턴 (구현은 안 했지만 `adapter-persistence/CLAUDE.md` 에 example 명시).
- "이 부분은 공식 문서를 다시 보고 답변드리겠습니다" 라고 해야 하는 부분:
- Sonar custom rule 작성의 _Quality Profile_ 표준 운영.
- Spring Modulith verifier 의 named interface 구체 configuration (도입 안 했음).
- 운영 환경에서 ArchUnit rule 변경의 _CI 차단_ 정책 (개인 경험 없음, `feature-ci-quality-gates-contract` 후속).
- **절대 과장하지 말 것**:
- "ArchUnit 으로 모든 boundary 위반을 catch 한다" 표현 금지 — D12 의 string bypass 한계가 명시됨.
- "violations-as-data 가 fitness function 의 _모든_ regression 을 잡는다" 표현 금지 — negative test 자체도 정적이라 reflection bypass 는 못 잡음.
- 운영 환경 검증 경험인 것처럼 표현 금지 — `locally-verified` 등급. ca-tmpl 은 template repository.
## Related / 관련
- 관련 면접 질문 (선행/후속): [[raw/interviews/clean-architecture-boundary-enforcement]] (선행 — boundary 자동 검증의 자매), [[raw/interviews/clean-architecture-module-blueprint]] (선행 — module 분리의 __), [[raw/interviews/transaction-port-vs-spring-transactional]] (자매 — application framework 격리).
- 영감을 받은 채용공고: (없음).
- 관련 블로그 글감: [[raw/blog-topics/archunit-violations-as-data-pattern-2026-05-28]] (같은 작업의 글감).
- 답변 derive 후 위치: 생성 전. 후보 `wiki/interview/architecture/archunit-static-analysis-limits.md`.
@@ -1 +0,0 @@
../../vault/40-publish/interviews/archunit-violations-as-data-pattern-2026-06-02.md
@@ -0,0 +1,32 @@
---
title: ArchUnit violations-as-data 패턴 면접 질문
source_type: interview
status: raw
related_branch: feature-streaming-response-contract
tags: [archunit, violations-as-data, testing, clean-architecture, interview]
created: 2026-06-02
---
# ArchUnit violations-as-data 패턴 면접 질문
## Parent
- [[raw/branch-notes/feature-streaming-response-contract]]
## 질문 목록
**Q1.** ArchUnit 에서 `@AnalyzeClasses(importOptions = DoNotIncludeTests.class)` 를 사용하는 이유는?
> 핵심: production code 만 스캔 대상으로 한정. test fixtures 가 의도적으로 규칙을 위반하더라도 production 아키텍처 테스트가 실패하지 않도록.
**Q2.** "vacuous pass" 문제가 무엇이며 violations-as-data 패턴이 어떻게 해결하는가?
> ArchUnit rule 이 production code 에서 아무것도 매칭하지 못할 때 `allowEmptyShould(true)` 없으면 예외, 있으면 통과. 통과 여부가 "규칙이 실제로 위반을 잡는가" 와 무관 → vacuous pass. violations-as-data: 의도적 위반 fixture 에 대해 `rule.evaluate(fixtures).hasViolation() == true` 를 별도로 단언.
**Q3.** `testCompileOnly` 로 선언된 타입을 ArchUnit 위반 fixture 에서 참조할 때 주의사항은?
> `testCompileOnly` 는 compile-time 전용이라 test execution runtime classpath 에 없음. JUnit 이 fixture class 를 로드할 때 superclass/interface 를 즉시 resolve → `NoClassDefFoundError`. annotation 참조는 lazy-resolve 이므로 안전. 따라서 forbidden type 이 `testCompileOnly` 라면 **annotation 으로만** 참조.
**Q4.** over-block guard test 가 필요한 이유는? 예시를 들어 설명하라.
> "차단하지 말아야 할 것을 차단하지 않는다" 를 검증. 예: `no_response_body_emitter` 는 `ResponseBodyEmitter` 를 차단하되 `StreamingResponseBody` 는 차단하지 않아야 함. `ALLOWED_STREAMING_CLASSES` 에서 `hasViolation() == false` 를 단언 → 규칙 경계가 의도대로임을 보장.
@@ -1 +0,0 @@
../../vault/40-publish/interviews/async-executor-saturation-context-propagation-2026-06-13.md
@@ -0,0 +1,53 @@
---
title: interview / async-executor-saturation-context-propagation-2026-06-13
source_type: interview-prep
status: raw
related_branches: [feature-background-job-async-contract]
related_projects: [ca-skeleton]
tags: [interview, ca-skeleton, async, threadpool, taskdecorator, mdc, graceful-shutdown, micrometer]
created: 2026-06-13
status_label: captured
---
# interview: async-executor-saturation-context-propagation-2026-06-13
> Layer: `raw/interviews/` — 작업에서 정직하게 도출 가능한 면접 질문. 답은 실제 구현/검증 근거에 묶는다.
## Parent / 부모
- [[raw/branch-notes/feature-background-job-async-contract]] — D5/D6/D7/D8 실 구현에서 도출한 질문.
## 질문 / Questions
### Q1. Spring Boot 의 기본 `@Async` executor 를 운영에서 그대로 쓰면 무슨 문제가 있나?
- 핵심: `ThreadPoolTaskExecutor` 의 queue capacity 기본값이 `Integer.MAX_VALUE`(사실상 unbounded). JDK `ThreadPoolExecutor` 는 큐가 가득 찰 때만 core→max 로 성장하므로, unbounded 큐에서는 `maxPoolSize` 가 영원히 발동하지 않는다. 부하가 몰리면 스레드가 아니라 큐(=힙)가 무한정 쌓여 OOM/지연으로 번진다.
- 후속: 어떻게 고치나? → bounded queue 강제 + 직접 executor 빈 등록(자동 구성은 `@ConditionalOnMissingBean(Executor.class)` 로 back-off). `Integer.MAX_VALUE` 큐 용량은 "이름만 bounded" 이므로 설정 검증에서 거부.
### Q2. saturation(거부)이 발생했을 때 무엇이 "조용히 삼켜지는" 위험인가? 어떻게 막나?
- AbortPolicy 는 `RejectedExecutionException` 을 던지지만, fire-and-forget `@Async` 호출이면 호출부가 그 예외를 못 본다. 따라서 거부 핸들러를 감싸 (1) 구조화 ERROR 로그(error.code), (2) 카운터(`executor.rejected.total`) 를 먼저 남기고 예외를 재던진다. 거부율은 alert(p1)로 노출.
- 후속: CallerRunsPolicy 는 왜 기본이 아닌가? → caller 가 request 스레드면 back-pressure 가 요청 지연을 직접 침식한다. use case 차원에서 명시 선언할 때만 허용.
### Q3. `@Async` 작업에 호출 스레드의 MDC(request_id/trace_id 등)를 어떻게 넘기나? 함정은?
- `TaskDecorator` 로 submit 시점에 `MDC.getCopyOfContextMap()` 스냅숏을 떠 worker 에서 복원. 두 함정: (1) **캡처 시점** — run time 이 아니라 decorate(submit) time 에 떠야 호출 당시 컨텍스트가 잡힌다. (2) **대칭 복원** — 작업 후 worker 의 이전 MDC 로 되돌리지 않으면 풀 재사용 스레드가 한 작업의 MDC 를 다음 작업으로 흘린다(MDC bleed).
- 후속: 왜 `InheritableThreadLocal` 을 안 쓰나? → 풀 스레드는 미리 생성/재사용되므로 상속 시점이 호출과 무관해 stale. 명시적 capture/restore 가 정답.
### Q4. SecurityContext(principal)는 왜 기본 전파하지 않나?
- 풀 스레드 재사용 + `MODE_INHERITABLETHREADLOCAL` 조합은 다른 요청의 principal 이 남아있는 stale context 위험. 그래서 기본 전파 대상은 MDC 4키뿐이고(registry 상 user_principal=`propagation: [none]`), principal 이 필요한 use case 만 `DelegatingSecurityContextTaskExecutor` 로 명시적 opt-in.
### Q5. graceful shutdown 에서 in-flight 배경 작업을 어떻게 다루나? 19s 같은 숫자는 어디서 오나?
- `setWaitForTasksToCompleteOnShutdown(true)` + `setAwaitTerminationSeconds(N)`. 예산 계층: executor await(≤19s) < app shutdown(20s) ≤ `spring.lifecycle.timeout-per-shutdown-phase` < k8s `terminationGracePeriodSeconds`(기본 30s). 19 = 20s 1s 정리 마진. grace period 초과 시 SIGKILL 이라 await 가 그 안에 끝나야 한다.
- 후속: interrupt 에 반응 안 하는 blocking call(JDBC)이면? → awaitTermination 초과 → SIGKILL 노출. 그래서 in-flight 가 19s 를 넘으면 멱등 retry-on-next-startup 을 전제로 설계.
### Q6. retry 횟수(retry_attempt)를 metric 태그로 넣으면 안 되는 이유는?
- 카디널리티 폭발. retry_attempt 는 값 범위가 작아 보여도 job_name×outcome×attempt 조합이 시계열을 곱한다. registry 에서 `job.retry.total` 의 태그는 `job_name`+`outcome`(bounded 4: SUCCESS/RETRY/EXHAUSTED/DLQ)뿐이고, retry_attempt 는 **로그 필드**로만 둔다. 메트릭 레코더의 시그니처에 attempt 를 넣지 않는 이유.
## Sources / 근거
- 로컬 검증: `:app-bootstrap:test` 의 async 패키지 30 테스트 green (AsyncContextTaskDecoratorTest 의 submit-time 캡처·대칭 복원·stale clear, LoggingAbortPolicyTest 의 거부 로그+카운터+재던짐, AsyncExecutorConfigTest 의 bounded queue·19s await·decorator-missing fail).
- 외부 근거: [[raw/official-docs/jdk21-threadpoolexecutor-javadoc]], [[raw/official-docs/spring-framework-threadpooltaskexecutor-javadoc]], [[raw/official-docs/spring-executor-configuration-support-javadoc]], [[raw/official-docs/kubernetes-pod-lifecycle-termination]], [[raw/official-docs/spring-security-concurrency-delegating-security-context-executor]].
@@ -1 +0,0 @@
../../vault/40-publish/interviews/ci-release-gate-fan-in-blocking-2026-06-20.md
@@ -0,0 +1,49 @@
---
title: interview-prep / ci-release-gate-fan-in-blocking
source_type: interview-prep
status: raw
related_branches: [feature-ci-quality-gates-contract]
related_projects: [ca-skeleton]
tags: [interview-prep, ca-skeleton, ci, github-actions]
created: 2026-06-20
status_label: collecting
---
# interview-prep: ci-release-gate-fan-in-blocking
> Layer: `raw/interviews/` — 면접 질문 원본 수집·연구 노트.
> `status_label`: `collecting`
## Parent / 부모
- [[raw/branch-notes/feature-ci-quality-gates-contract]] — release-blocking 게이트 fan-in 을 구현하며 "한 게이트가 실패하면 정말 릴리스가 막히나?" 라는 질문이 자연스럽게 도출됨.
## 질문 / Question
- 질문 원문: "여러 CI 게이트(빌드/테스트/정적분석/계약테스트…)를 하나의 required check 로 묶을 때, 그 중 하나라도 실패하면 머지가 *반드시* 막히도록 어떻게 보장했나요?"
- 출처: 예상 질문 (branch 작업에서 유추 — fan-in status 전파는 branch-note Claim C1 의 핵심 불확실성)
- 받은 날짜·맥락: (예상)
## 질문 의도 추론 / Why this question
- 핵심 평가 대상: CI 도구의 *기본 동작* 을 안다고 착각하지 않고 실제로 검증하는가 + "통과처럼 보이지만 차단 안 되는" 위양성 위험 인지.
- 함정 / 흔히 빠지는 답변 패턴: "aggregator 잡을 만들고 `needs` 로 묶었다" 로 끝내는 것. `needs + if: success()` aggregator 는 상위 실패 시 **`failure` 가 아니라 `skipped`** 가 되고, branch protection 이 skipped 를 통과로 오해할 수 있다 → 차단 실패.
## 모범 답안 뼈대 / Answer skeleton
- 결론 먼저: aggregator 를 `if: always()` 로 두고, `needs.*.result` 를 스캔해 `failure`/`cancelled` 가 하나라도 있으면 명시적으로 `exit 1`. 그래야 "1건 실패 → release block" 이 보장된다.
- 근거: GitHub Actions 의 `needs` 기본은 상위 실패 시 하위 잡 skip. `success()` 는 그 기본을 적은 것일 뿐 aggregator 를 *실패* 로 만들지 않는다. skip 은 차단이 아니다.
- 검증: 의도적으로 matrix 잡 1개를 실패시켜 aggregator 가 *fail* 인지 *skip* 인지 직접 확인(공식 문서만 믿지 않음 — evidence-first).
- 세부: PR-only 잡(예: 라벨 게이트)은 push 이벤트에서 `skipped` 이므로 result 스캔에서 skip 은 OK 로 통과시키고, 비차단 잡(flaky `quarantine`)은 애초에 `needs` 에서 제외한다.
- 확장: 워크플로 간 `needs` 는 불가능 → 여러 워크플로의 required 잡 *합집합* 을 branch protection 에 등록해야 전체 release-blocking 집합이 완성된다.
## 꼬리 질문 / Follow-ups
- "`continue-on-error``if: always()` 의 차이는?" → 전자는 잡을 실패해도 성공으로 *보고*(비차단 게이트용), 후자는 상위 결과와 무관히 *실행*(aggregator 용).
- "matrix 잡 일부만 실패하면?" → `fail-fast: false` + result 스캔이면 모든 조합을 돌려 어떤 adapter 가 깨졌는지까지 본 뒤 차단.
## Related / 관련
- 관련 branch: [[raw/branch-notes/feature-ci-quality-gates-contract]]
- 관련 error: [[raw/errors/ci-fan-in-skipped-not-failed-and-gitignored-config-2026-06-20]]
- 관련 blog topic: [[raw/blog-topics/ci-gate-wiring-vs-policy-ownership-2026-06-20]]
@@ -1 +0,0 @@
../../vault/40-publish/interviews/clean-architecture-boundary-enforcement.md
@@ -0,0 +1,104 @@
---
title: interview-prep / clean-architecture-boundary-enforcement
source_type: interview-prep
status: raw
related_branches: [feature-architecture-enforcement-rules]
related_projects: [ca-skeleton]
tags: [interview-prep, ca-skeleton, architecture, testing, archunit, clean-architecture, gradle]
created: 2026-05-28
status_label: collecting
---
# interview-prep: clean-architecture-boundary-enforcement
> Layer: `raw/interviews/` — 면접 질문 원본 수집·연구 노트. 다듬어진 답변은 `/interviewize` 후 `wiki/interview/` 에 별도 작성.
## Parent / 부모
- [[raw/branch-notes/feature-architecture-enforcement-rules]] — Clean Architecture 경계를 Gradle + ArchUnit fitness function 으로 강제한 결정 (D1~D10) + 검증 결과.
- [[raw/project-notes/ca-skeleton-operational-contract]] — ca-tmpl skeleton 의 module boundary / operational contract SSOT.
## 질문 / Question
- 질문 원문: Clean Architecture 템플릿에서 계층 경계가 시간이 지나도 깨지지 않도록 어떤 방식으로 자동 검증했나요?
- 출처: 예상 질문 (실제 면접에서 받은 것 아님).
- 받은 날짜·맥락: 아직 없음.
## 질문 의도 추론 / Why this question
- 핵심 평가 대상:
- 아키텍처 원칙을 _문서가 아니라_ 자동 검증으로 연결한 경험.
- Gradle multi-module dependency 와 ArchUnit bytecode rule 의 _역할 분리_ 인식 (각자 잡는 위반 종류가 다름).
- 정적 분석의 _한계_ 인식 (runtime reflection, generated code, Spring Modulith 등 보완 도구의 자리).
- 단순 "Clean Architecture 적용했다" 선언이 아니라 _실제 위반 코드를 넣어 red/green 검증_ 한 경험.
- 함정 / 흔히 빠지는 답변 패턴:
- "Clean Architecture 적용했다" 로 끝내고 controller/repository/JPA entity leak 을 _구체적으로 어떻게 막았는지_ 설명 못함.
- Gradle 과 ArchUnit 의 _역할 차이_ 를 묻지 않고 "둘 다 썼다" 로 뭉뚱그림.
- 한계 (reflection, MapStruct generated path, Spring Modulith 도입 안 함) 를 솔직히 말하지 않고 만능처럼 표현.
- 따라올 만한 후속 질문:
- Gradle dependency rule 과 ArchUnit rule 은 각각 _어떤 위반_ 을 잡나요? 한쪽만으로는 왜 안 되나요?
- ArchUnit 이 잡지 못하는 위반은 무엇이고 어떻게 보완할 건가요?
- sample module 이 production code 로 역수입되는 걸 어떻게 막았나요?
- 빈 anchor module 은 ArchUnit 에서 어떻게 처리했나요?
- Spring Modulith 를 도입하지 않은 이유는 무엇이고, 추후 도입한다면 무엇이 _중복_ 되고 무엇이 _보완_ 인가요?
## 답변 재료 / Raw answer material
> 사실은 branch-note Decision ID 또는 외부 source claim ID 로 근거 같이 인용. 경험은 _내가 직접 한 것_ 만.
- 사실 1 (근거: `feature-architecture-enforcement-rules.md` D2): ca-tmpl 의 module 구조는 `domain-core` + `application-core` + `adapter-{web,persistence,outbound}` + `shared-contract` + `sample-ticket` + `app-bootstrap` 8개. module boundary 가 _1차 강제선_, module 내부 package 가 _2차 책임 분류_.
- 사실 2 (근거: `feature-architecture-enforcement-rules.md` D1 + 외부 `governance-archunit-official.md#AU-OFF-C1`): boundary 강제는 _두 층_ — Gradle `verifyCleanArchitectureDependencies` task 가 declared module coverage + allowed project dependency 매트릭스를 검사하고, ArchUnit `CleanArchitectureTest` 가 bytecode/import 수준의 12 rule 을 검사.
- 사실 3 (근거: `feature-architecture-enforcement-rules.md` D3, D4, D5, D6, D7, D8): ArchUnit 이 잡는 위반 — domain purity (Spring/JPA/HTTP import 금지), application → adapter/bootstrap 의존 금지, adapter 간 직접 의존 금지, web DTO boundary, sample-ticket production 역수입 금지, application `@Transactional` 직접 import 금지, controller direct domain response 금지, mapper boundary, shared-contract package allowlist.
- 사실 4 (근거: `feature-architecture-enforcement-rules.md` Claims to Verify 의 status 표): 위 8개 rule 모두 `actually-implemented` 또는 `locally-verified`. red/green 검증 (임시 위반 코드 → 실패 → 제거 → 통과) 까지 수행. `cd src && ./gradlew :app-bootstrap:test verifyCleanArchitectureDependencies``cd src && ./gradlew test` 모두 통과.
- 사실 5 (근거: `feature-architecture-enforcement-rules.md` D8 + `feature-application-port-usecase-contract.md` D3): application 의 `@Transactional` 금지는 _Spring 공식 권고와 충돌_ 하는 의도적 소수파 결정. 다수파 (`@Transactional` 직접 부착, hexagonal-reflectoring) 가 reasonable 함을 인정하면서, template repository 의 _격리 학습 비용_ 흡수가 이유.
- 내가 직접 한 경험:
- ca-tmpl `src/build.gradle``verifyCleanArchitectureDependencies``src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/CleanArchitectureTest.java` 두 파일을 함께 보강.
- 임시 위반 코드 4종 (`shared.ticket` package, controller domain return, mapper → application 의존, application `@Transactional`, `app-bootstrap → sample-ticket` Gradle dep) 추가 → 실패 확인 → 제거 → 통과.
- 빈 skeleton anchor module 의 ArchUnit empty-should failure 를 `allowEmptyShould(true)`_선별_ 해결 (모든 rule 에 일괄 적용 ≠ 빈 상태가 의도된 rule 에만 적용) — [[raw/errors/archunit-empty-should-anchor-2026-05-27]].
- Codex sandbox 의 read-only `~/.gradle` 권한 때문에 Gradle wrapper lock 실패 → 사용자 승인 escalation 으로 재실행 — [[raw/errors/gradle-wrapper-readonly-cache-2026-05-28]]. _코드 문제와 환경 문제를 구분_ 한 경험.
- 트레이드오프:
- **Gradle vs ArchUnit 분업**: Gradle 은 _module-level project dependency_ 를 컴파일 단계에서 확실히 차단하지만 _method return type_ 이나 _annotation import_ 같은 세부 규칙은 못 봄. ArchUnit 은 bytecode 수준의 import / class structure 를 잡지만 _module 간 build-graph 사이클_ 은 깔끔하게 못 잡음. 둘이 _역할이 다르고 둘 다 필요_.
- **다수파 vs 소수파**: `@Transactional` 직접 부착 (다수파, Spring 공식 권고, boilerplate 최소) vs `TransactionPort` 추상화 (소수파, 격리 우선, boilerplate 증가). ca-tmpl 은 _template repository 라서_ 소수파를 의도적 선택. 단일 DB / 단일 transactionManager 의 작은 팀은 다수파가 reasonable.
- **Spring Modulith 도입 안 함**: named interface 검증은 더 강력하지만 ca-tmpl 의 boundary drift 차단 비용 대비 효용이 _이 시점에서는_ 낮다고 판단. 후속 검토 후보로 둠 (`feature-architecture-enforcement-rules.md` D5 Open Risk).
- 한계 / "이건 안 해봤다":
- runtime lookup / reflection 우회 (`ApplicationContext#getBean` 류) 가 현재 ArchUnit rule 을 false-pass 하는지 _실험 미수행_ (`planned`).
- MapStruct generated mapper exemption 의 build path 가 빌드 도구 설정에 따라 어떻게 달라지는지 확인 미완 (`needs-confirmation`, D9 `UNSUPPORTED_DECISION`).
- prod 운영 검증 없음 — ca-tmpl 은 template repository.
## Sources / 근거
- [[raw/branch-notes/feature-architecture-enforcement-rules]] — 결정 D1~D10, Claims to Verify status 표, Closure 의 `locally-verified` 5항목.
- [[raw/branch-notes/feature-skeleton-package-blueprint-contract]] — 자매 결정 (D1~D8). module 분리 자체의 __.
- [[raw/branch-notes/feature-application-port-usecase-contract]] — `@Transactional` 다수파 vs 소수파 trade-off 의 근거 (D3, D4 비교).
- [[raw/official-docs/archunit-user-guide]] — ArchUnit rule DSL (`ARCHUNIT-UG-C5`, `ARCHUNIT-UG-C6`).
- [[raw/official-docs/governance-archunit-official]] — architecture test 거버넌스 (`AU-OFF-C1`, `AU-OFF-C2`).
- [[raw/official-docs/archunit-conditional-on-property-3-layer-pattern]] — predicate/condition 모델 (`AUCP-C1` ~ `AUCP-C5`).
- [[raw/company-tech-blogs/woowahan-hexagonal-multimodule]] — Domain / Application / Framework / Bootstrap 4-module 격리 (`WW-HEX-C1`).
- [[raw/company-tech-blogs/modulith-kakaobank-techblog-2025]] — Gradle multi-module + Hexagonal + Spring Modulith (`KAKAOBANK-MOD-C4`).
- [[wiki/concepts/clean-architecture-package-layout]] — 정제된 layout 개념 (canonical).
- [[wiki/projects/ca-tmpl/clean-architecture-package-layout]] — ca-tmpl 적용 (canonical, 갱신 필요).
## 미해결 / Unknown
- 모르는 것: ArchUnit 이 reflection 우회를 _얼마나_ 못 잡는지 정량 측정 안 함. Spring `ApplicationContext#getBean` 류의 일반적 우회 패턴을 위반 코드로 넣어 실제 false-pass 확인 필요.
- 모르는 것: MapStruct generated mapper exemption 의 표준 처리 방식. Maven vs Gradle / annotation processor 위치에 따라 달라지는 generated source path 의 일반적 표현.
- 확인 방법: `feature-architecture-enforcement-rules.md` Claims to Verify 의 `planned` / `needs-confirmation` 항목을 후속 PoC branch 에서 실험.
## 답변 경계 / Answer boundary
- 자신 있게 말할 수 있는 범위: ca-tmpl `src/build.gradle``verifyCleanArchitectureDependencies``src/app-bootstrap/.../CleanArchitectureTest.java` 의 12 ArchUnit rule 을 _직접 구현 + red/green 검증_ 한 범위. `./gradlew test` + `verifyCleanArchitectureDependencies` 로컬 통과까지.
- "이 부분은 공식 문서를 다시 보고 답변드리겠습니다" 라고 해야 하는 부분:
- MapStruct generated code exemption 의 빌드 도구별 표준 처리.
- Spring Modulith named interface 의 구체 configuration (Modulith 를 _도입한 적 없음_).
- prod 환경에서 ArchUnit / Gradle dependency rule 이 CI 어떤 단계에서 실패시키는 게 안전한지 (운영 경험 없음).
- **절대 과장하지 말 것**:
- prod 운영 검증인 것처럼 말하지 말 것. ca-tmpl 은 template repository 이고 검증 등급은 _`locally-verified`_.
- 우아한형제들 / 카카오뱅크 사례를 _industry standard_ 처럼 말하지 말 것. _case study_ 다 (`Evidence Strength: company-case-study`).
- `@Transactional` 소수파 결정이 _다수파보다 우월하다_ 는 식의 표현 금지. _이 맥락 (template repository) 에서의 선택_ 까지만.
## Related / 관련
- 관련 면접 질문 (선행/후속): [[raw/interviews/clean-architecture-module-blueprint]] (선행 — module 분리 자체의 __), [[raw/interviews/shared-contract-and-sample-isolation]] (자매 — shared/sample 책임), [[raw/interviews/transaction-port-vs-spring-transactional]] (자매 — `@Transactional` 다수파/소수파 trade-off), [[raw/interviews/post-implementation-knowledge-capture]] (워크플로우 자매).
- 영감을 받은 채용공고: (없음).
- 관련 블로그 글감: [[raw/blog-topics/clean-architecture-boundary-enforcement-2026-05-28]] (같은 경험의 글감), [[raw/blog-topics/clean-architecture-module-blueprint-2026-05-28]] (자매 글감), [[raw/blog-topics/transaction-port-abstraction-over-spring-transactional-2026-05-28]] (`@Transactional` trade-off 글감).
- 답변 derive 후 위치: 생성 전. 생성 시 `wiki/interview/architecture/clean-architecture-boundary-enforcement.md` 후보.
@@ -1 +0,0 @@
../../vault/40-publish/interviews/clean-architecture-domain-onboarding-guardrails.md
@@ -0,0 +1,61 @@
---
title: interview-prep / clean-architecture-domain-onboarding-guardrails
source_type: interview-prep
status: raw
related_branches: [feature-domain-feature-onboarding-contract]
related_projects: [ca-skeleton]
tags: [interview-prep, ca-skeleton, architecture, testing, clean-architecture, multi-module]
created: 2026-06-25
status_label: collecting
---
# interview-prep: clean-architecture-domain-onboarding-guardrails
> Layer: `raw/interviews/` — 실행 가능한 Clean Architecture onboarding guardrail 경험에서 나온 면접 질문 원석.
## Parent / 부모
- [[raw/branch-notes/feature-domain-feature-onboarding-contract]] — 이 branch에서 문서 checklist를 ArchUnit/JUnit dry-run guardrail 로 구현했기 때문에 나올 수 있는 질문.
## 질문 / Question
- 질문 원문: Clean Architecture 템플릿에서 새 도메인 기능을 추가할 때 계층 경계가 무너지지 않는다는 것을 어떻게 검증했나요?
- 출처: 예상 질문
- 받은 날짜·맥락 (실제 받은 경우): 해당 없음
## 질문 의도 추론 / Why this question
- 핵심 평가 대상: 아키텍처 경계 자동화, 테스트 설계, 문서 계약을 실행 가능한 guardrail 로 전환한 경험.
- 함정 / 흔히 빠지는 답변 패턴: “컨벤션으로 조심했다” 수준에서 끝내고 실패 fixture나 negative test evidence를 제시하지 못하는 답변.
- 따라올 만한 후속 질문: ArchUnit 정적 분석으로 잡지 못하는 한계는 무엇이며 어떻게 보완했나요?
## 답변 재료 / Raw answer material
- 사실 1: onboarding 기준은 `domain-core``application-core``adapter-*` 방향의 module slice다. 근거: [[raw/branch-notes/feature-domain-feature-onboarding-contract]] D1, D2.
- 사실 2: read-only slice는 command/write port 없이 query/use case/mapper/controller와 contract 검증으로 충분하다고 정의했다. 근거: [[raw/branch-notes/feature-domain-feature-onboarding-contract]] D3.
- 사실 3: write slice는 command/use case/write port/persistence/transaction boundary가 함께 있어야 한다. 근거: [[raw/branch-notes/feature-domain-feature-onboarding-contract]] D4, D8.
- 내가 직접 한 경험: `DomainFeatureOnboardingContractTest``dev.caskeleton.onboarding.*` Ticket dry-run fixture, `use_case_capability_matches_transaction_port_boundary` ArchUnit rule, shared-contract negative fixture를 구현하고 `./gradlew test`까지 통과시켰다. 근거: [[raw/branch-notes/feature-domain-feature-onboarding-contract]] §구현 결과.
- 트레이드오프: ArchUnit direct-call 분석은 빠르고 CI 친화적이지만 helper 뒤에 숨은 transaction boundary는 잡지 못한다. 이 한계는 branch D8의 Open Risk로 남겼다.
- 한계 / "이건 안 해봤다": 운영 환경 검증은 없다. 이번 증거 등급은 `locally-verified`다.
## Sources / 근거
- [[raw/branch-notes/feature-domain-feature-onboarding-contract]] — 이번 구현과 검증의 primary evidence.
- [[raw/errors/gradle-wrapper-sandbox-lock-2026-06-25]] — 로컬 검증 중 발생한 Gradle sandbox 문제.
## 미해결 / Unknown
- 모르는 것 1: 실제 downstream fork 에서 같은 fixture strategy가 과도한 boilerplate로 받아들여질지.
- 모르는 것 2: helper-mediated transaction boundary를 자동 분석으로 더 깊게 잡을 필요가 있는지.
- 확인 방법: downstream adoption branch 또는 실제 새 도메인 branch에서 fixture 없이 production slice를 추가해 guardrail false positive/negative를 관찰한다.
## 답변 경계 / Answer boundary
- 자신 있게 말할 수 있는 범위: ca-tmpl local Gradle test/ArchUnit 수준에서 새 도메인 onboarding 계약을 실행 가능한 guardrail 로 구현하고 검증했다.
- "이 부분은 공식 문서를 다시 보고 답변드리겠습니다" 라고 해야 하는 부분: ArchUnit이 Java call graph 전체를 완전 분석한다는 식의 주장은 하지 않는다.
- **절대 과장하지 말 것**: `locally-verified``prod-verified` 또는 범용 best practice로 말하지 말 것.
## Related / 관련
- 관련 블로그 글감: [[raw/blog-topics/executable-clean-architecture-onboarding-2026-06-25]]
- 답변 derive 후 위치: 생성 전
@@ -1 +0,0 @@
../../vault/40-publish/interviews/clean-architecture-identifier-generation.md
@@ -0,0 +1,53 @@
---
title: interview-prep / clean-architecture-identifier-generation
source_type: interview-prep
status: raw
related_branches: [feature-resource-identifier-contract]
related_projects: [ca-skeleton]
tags: [interview-prep, ca-skeleton, identifier, ulid, ddd, clean-architecture, hexagonal]
created: 2026-06-01
status_label: collecting
---
# interview-prep: clean-architecture-identifier-generation
> Layer: `raw/interviews/` — 면접 질문 원본 수집·연구 노트. 다듬어진 답변은 `/interviewize` 후 `wiki/interview/` 에 별도 작성.
## Parent / 부모
- [[raw/branch-notes/feature-resource-identifier-contract]] — D5(도메인 port + application orchestration), D1(ULID), D10(PostgreSQL uuid native) 실 구현.
- [[raw/project-notes/ca-skeleton-operational-contract]] — ca-tmpl skeleton-wide operational contract.
## 질문 / Question
- 질문 원문: 도메인 엔티티의 식별자(ULID)를 인프라(랜덤/시계 소스)에 도메인을 결합시키지 않으면서 server-assigned로 생성하려면 Clean Architecture에서 어느 계층이 책임지나요?
- 출처: 예상 질문 (실 면접 아님).
- 받은 날짜·맥락: 아직 없음. DDD factory / hexagonal port 이해 검증용.
## 질문 의도 추론 / Why this question
- 핵심 평가 대상:
- "도메인이 식별성을 소유"한다는 DDD 명제와 "도메인은 SecureRandom/시계/라이브러리에 결합되면 안 된다"는 순수성 명제를 *동시에* 만족시키는 설계를 아는지.
- factory가 entity가 아니라 도메인 *service/port*라는 Evans DDD의 디테일 인지.
- "도메인 생성 vs application 생성 vs 인프라 생성(Hibernate @GeneratedValue)"의 trade-off를 맥락 의존으로 보는지.
- 함정 / 흔히 빠지는 답변:
- "도메인 entity의 static factory가 `UUID.randomUUID()`를 직접 호출" → 도메인이 JDK 난수에 결합 + 테스트 시 generator 교체 불가 + ULID 같은 라이브러리면 도메인이 인프라 의존.
- "Hibernate `@GeneratedValue`로 DB가 생성" → 도메인이 영속화 메커니즘에 결합, ULID time-ordered/monotonic 보장 불가, PostgreSQL `uuid` native 결정과 충돌.
- "application이 ULID 라이브러리를 직접 호출" → use case가 인프라(UlidCreator)에 결합, ArchUnit `no_uuid_random_in_controller` 위반.
- 따라올 만한 후속 질문:
- 그럼 도메인 port는 누가 호출하나요? (use case) 그건 "application이 생성"하는 것 아닌가요? (생성 *책임*은 도메인 port, *호출 시점*은 orchestration — 구분)
- ULID 26자(Crockford base32)를 DB에는 어떻게 저장하나요? (PostgreSQL `uuid` native 16-byte로 `Ulid.toUuid()` 변환 — external은 ULID, internal은 uuid)
- sealed로 모든 식별자 타입을 닫고 싶은데 모듈 경계 때문에 `permits`가 안 되면? (`no_long_id_pk` ArchUnit rule이 빌드타임 대체)
- resource id / trace id / idempotency-key는 왜 다른 branch가 책임지나요?
## 답변 재료 / Raw answer material
- 구현: 도메인에 `WorkLogIdFactory`(port) 정의 → 인프라 `UlidWorkLogIdFactory`(`@Component`, `UlidCreator.getMonotonicUlid()`, SecureRandom) 구현 → `CreateWorkLogUseCase`가 port를 주입받아 `factory.newId()` 호출 후 `WorkLog.create(id, ...)`로 조립.
- 도메인 `WorkLog``WorkLogId`(26자 regex 검증만 하는 record)만 알고, ULID 라이브러리/난수/시계에 결합 없음.
- "Application layer 생성 거부"라는 단순 표현은 오해를 부른다 — 실제 거부 대상은 *application이 ULID 라이브러리를 직접 호출*하는 것이지, use case가 도메인 port를 orchestrate하는 것은 정합.
- 검증: `WorkLogUseCasesTest`가 fake `WorkLogIdFactory`(테스트는 generator 교체 자유) 주입으로 단위테스트. ArchUnit `no_uuid_random_in_controller`/`no_math_random_for_id`/`no_long_id_pk`가 빌드타임 enforce.
## Related / 관련
- 관련 branch note: [[raw/branch-notes/feature-resource-identifier-contract]]
- 관련 개념: [[raw/blog-topics/identifier-governance-rule-scoping-by-id-kind-2026-06-01]]
@@ -1 +0,0 @@
../../vault/40-publish/interviews/clean-architecture-method-authorization-without-spring-coupling-2026-06-08.md
@@ -0,0 +1,41 @@
---
title: interview / Clean Architecture 에서 Spring 결합 없이 method-level 인가 거는 법
source_type: interview
status: raw
related_branches: [feature-authentication-authorization-contract]
related_projects: [ca-skeleton]
tags: [interview, ca-skeleton, security, authorization, clean-architecture, spring-security]
created: 2026-06-08
---
# interview: framework-free method authorization (authz contract)
> Layer: `raw/interviews/` — feature-authentication-authorization-contract 구현에서 정직하게 도출되는 면접 질문/답.
## Parent / 부모
- [[raw/branch-notes/feature-authentication-authorization-contract]]
## Q1. 왜 `@PreAuthorize` 대신 use-case `AuthorizationPort` 를 만들었나?
`@PreAuthorize` 는 SpEL + Spring Security 타입에 bean 을 결합시킨다. application/domain layer 는 framework-free 여야 하므로(project §5, `TransactionPort` 선례) 인가 *결정* 을 plain Java port(`AuthorizationPort.requirePermission(AuthorizationPrincipal, Permission)`)로 표현하고, *집행 메커니즘* 만 adapter 의 custom `AuthorizationManager<MethodInvocation>` 에 둔다. 결과: use case 는 `@RequiresPermission("worklog:close")`(Spring-free annotation)만 선언, 집행은 adapter. 트레이드오프: `@PreAuthorize` 대비 boilerplate(annotation manager + advisor wiring) ↑, 대신 layer 순수성 유지.
## Q2. permission 중심 RBAC 를 택한 이유? OWASP 는 ABAC 를 권한다는데?
permission(`resource:action`)=집행 단위, role=permission 묶음. 도메인이 role 을 추가해도 enforcement 코드는 불변(role→permission registry 만 갱신). OWASP 는 dynamic attribute 가 필요하면 ABAC 를 선호하지만(OWASP-PM-C1), 정적 permission + 소수 role 규모에선 YAGNI. 핵심: `AuthorizationPort` 인터페이스가 ABAC 전환 path 를 보장 — 구현체만 owner/relationship predicate 로 교체하면 됨.
## Q3. 인가 거부를 어떻게 403 으로 내보내나? (2-hop)
application port 는 Spring-free 라 Spring `AccessDeniedException` 을 못 던진다. (1) port 가 자체 `AuthorizationDeniedException`(RuntimeException) throw → (2) adapter 의 `AuthorizationManager` 가 이를 잡아 `AuthorizationDecision(false)` 반환 → Spring method-security interceptor 가 `AccessDeniedException` 발생 → `GlobalExceptionHandler#handleForbidden``SecurityErrorClassifier.classifyAccessDenied` 위임 → `AUTHZ_INSUFFICIENT_PERMISSION`(403). error code SSOT 는 security-baseline, 본 계약은 emission producer.
## Q4. AOP proxy bypass 위험은?
method security 는 Spring AOP proxy 기반이라 self-invocation(같은 객체 내부 호출)이나 non-Spring-bean 호출은 advisor 를 우회한다. 또 concrete 타입 주입은 CGLIB(`proxyTargetClass=true`) 여야 proxy 가 subtype 이 된다(→ [[raw/errors/method-security-cglib-vs-jdk-proxy-usecase-injection-2026-06-08]]). 보강: 모든 mutating 진입점이 Spring bean 경유인지 ArchUnit 정적 검증(host=architecture-enforcement-rules).
## Q5. role registry key 를 `ROLE_ADMIN` 으로 안 쓰고 raw `admin` 으로 쓴 이유?
`AuthenticatedUser.roles` 는 IdP 원본 raw role(prefix 없음)을 담고, Spring `GrantedAuthority``ROLE_`+upper prefix 를 받는다. application-core 는 Spring-free 라 `GrantedAuthority` 가 아니라 raw role set 을 consume → registry key = raw role(lowercase 정규화, case-insensitive). 잘못해서 `ROLE_ADMIN` 으로 조회하면 0 권한 fail-closed.
## Q6. unauthenticated vs unauthorized 구분?
인증 없음 → method-security 의 deferred auth supplier 가 `AuthenticationException`(401-family). 권한 부족 → `AccessDeniedException`(403). prod 는 filter chain 이 미인증을 401 로 먼저 차단하므로 method-security 의 미인증 경로는 backstop.
@@ -1 +0,0 @@
../../vault/40-publish/interviews/clean-architecture-module-blueprint.md
@@ -0,0 +1,107 @@
---
title: interview-prep / clean-architecture-module-blueprint
source_type: interview-prep
status: raw
related_branches: [feature-skeleton-package-blueprint-contract]
related_projects: [ca-skeleton]
tags: [interview-prep, ca-skeleton, architecture, gradle, clean-architecture, hexagonal, multi-module]
created: 2026-05-28
status_label: collecting
---
# interview-prep: clean-architecture-module-blueprint
> Layer: `raw/interviews/` — 면접 질문 원본 수집·연구 노트. 다듬어진 답변은 `/interviewize` 후 `wiki/interview/` 에 별도 작성.
## Parent / 부모
- [[raw/branch-notes/feature-skeleton-package-blueprint-contract]] — single-module feature-first 결정 (2026-05-22) 을 Gradle multi-module + Hexagonal (2026-05-27) 로 _명시적으로 수정_ 한 결정 (D1~D8 + Default Module Blueprint tree + Module Dependency Rule 표).
- [[raw/project-notes/ca-skeleton-operational-contract]] — ca-tmpl skeleton-wide operational contract SSOT.
## 질문 / Question
- 질문 원문: Clean Architecture 템플릿에서 왜 단일 모듈 package 구조가 아니라 Gradle multi-module 구조를 선택했나요? 그리고 처음부터 그렇게 결정한 건가요?
- 출처: 예상 질문.
- 받은 날짜·맥락: 아직 실제 면접 질문으로 받은 것은 아님.
## 질문 의도 추론 / Why this question
- 핵심 평가 대상:
- Clean Architecture 원칙을 _물리적 module boundary_ 로 옮긴 __.
- package convention 과 build-graph enforcement 의 _차이_ 인식.
- small project vs template repository 의 trade-off 인식.
- _첫 결정을 뒤집은 경험_ (case study 검토 후 의사결정 reversion) — 정직함과 evidence-based 사고.
- 함정 / 흔히 빠지는 답변 패턴:
- "멀티모듈이 더 깔끔해서" — 비용 / 단점 / trade-off 언급 없음.
- "처음부터 멀티모듈이 답이라고 생각했다" — 의사결정의 _과정_ 을 숨김.
- 우아한형제들 / 카카오뱅크 사례를 _industry standard_ 처럼 인용 (실제로는 _case study_).
- 따라올 만한 후속 질문:
- small project 에서는 single-module 이 더 낫지 않나요? 어떤 기준으로 multi-module 을 선택해야 하나요?
- Gradle dependency rule 과 ArchUnit rule 은 각각 _무엇을 보장_ 하나요? 한쪽만으로는 왜 안 되나요?
- `domain-core``shared-contract` 를 참조하는 건 Clean Architecture 위반 아닌가요?
- Spring Modulith 가 multi-module 대체가 될 수 있나요?
- 새 사업 도메인이 추가되면 어느 module 에 어떻게 들어가나요? `adapter-messaging` 같은 새 adapter 가 필요해지면?
## 답변 재료 / Raw answer material
- 사실 1 (근거: `feature-skeleton-package-blueprint-contract.md` D1, §결정 사항 2026-05-22 / 2026-05-27): 초기 결정은 single-module feature-first package layout 이었다. 2026-05-27 에 우아한형제들 / 카카오뱅크 사례 검토 후 Gradle multi-module + Clean Architecture / Hexagonal 로 _명시적으로 수정_. 의사결정의 reversion 자체가 evidence.
- 사실 2 (근거: `feature-skeleton-package-blueprint-contract.md` §Default Module Blueprint + Module Dependency Rule 표): ca-tmpl 의 8 module — `app-bootstrap`, `domain-core`, `application-core`, `adapter-web`, `adapter-persistence`, `adapter-outbound`, `shared-contract`, `sample-ticket`. dependency direction 매트릭스로 _허용/금지_ 가 매 module 별로 명시.
- 사실 3 (근거: `feature-skeleton-package-blueprint-contract.md` D2, D3): `domain-core` 는 framework-neutral POJO (Spring/JPA/HTTP 모름), `application-core``domain-core` + `shared-contract` 에만 의존. adapter 구현체는 adapter module 밖으로 안 새어 나옴.
- 사실 4 (근거: `feature-skeleton-package-blueprint-contract.md` D6): `shared-contract` 는 response envelope / error code / header / MDC / metric / registry / annotation 같은 _skeleton-wide operational contract_ 만. business / domain concept 는 금지.
- 사실 5 (근거: `feature-skeleton-package-blueprint-contract.md` D7 + `feature-architecture-enforcement-rules.md` D7): `sample-ticket` 은 fixture/sample consumer 이며 production module 이 import / dependency 선언 시 _Gradle + ArchUnit 양쪽_ 에서 실패.
- 사실 6 (근거: `feature-skeleton-package-blueprint-contract.md` Closure §`locally-verified`): `./gradlew verifyCleanArchitectureDependencies` + `./gradlew :app-bootstrap:test --tests '*CleanArchitectureTest'` + `./gradlew :adapter-web:test --tests '*SettingsTest'` + `./gradlew test` 모두 통과. 로컬 검증 완료.
- 내가 직접 한 경험:
- 기존 reference code (blog domain) 를 production module 에서 `sample-ticket/src/main/java/dev/caskeleton/sample/ticket/...` 로 격리. production module 은 anchor + `package-info.java` 중심으로 정리.
- production package root `dev.caskeleton` 으로 rename + `BlogApplication``CaSkeletonApplication` + `blog.*` 설정 prefix → `ca-skeleton.*`.
- 빈 anchor module 의 ArchUnit empty-should failure 를 `allowEmptyShould(true)` 로 선별 해결 — [[raw/errors/archunit-empty-should-anchor-2026-05-27]].
- sample-ticket 격리 후 `InvalidBearerTokenException` compile error → `spring-boot-starter-oauth2-resource-server` 명시 추가 — [[raw/errors/sample-ticket-oauth2-resource-server-dependency-2026-05-27]].
- 트레이드오프:
- **single-module 의 장점**: build 설정 단순, IDE 탐색 빠름, 처음 학습 비용 낮음. _작은 프로젝트_ 에는 합리적.
- **multi-module 의 장점**: module boundary 가 _컴파일 단계_ 에서 위반을 차단. template 의 _재사용성_ (다음 프로젝트에서 import 해도 경계가 살아 있음).
- ca-tmpl 이 multi-module 을 택한 _이유_: template repository 라서 _새 프로젝트 시작 시점에 경계가 흐트러지지 않도록 학습 비용을 미리 흡수_`feature-skeleton-package-blueprint-contract.md` D8 Open Risk 와 일치.
- **case study 의 한계**: 우아한형제들 / 카카오뱅크 사례는 `company-case-study` 등급. _공식 표준이 아님_. ca-tmpl 채택의 _부분 정당화_ 까지만.
- 한계 / "이건 안 해봤다":
- Spring Modulith named interface 검증은 _기본값으로 도입하지 않음_ (`feature-skeleton-package-blueprint-contract.md` D5 Open Risk).
- 실제 사업 도메인 (e.g., 결제 / 알림 / 인증) 이 들어왔을 때 module 분할 / 새 adapter 추가가 자연스러운지 _검증 안 함_.
- 운영 배포 검증 없음 — `feature-skeleton-package-blueprint-contract.md` Closure §`prod-verified: 없음`.
## Sources / 근거
- [[raw/branch-notes/feature-skeleton-package-blueprint-contract]] — D1~D8, Default Module Blueprint, Module Dependency Rule.
- [[raw/branch-notes/feature-architecture-enforcement-rules]] — 자매 결정 (boundary 강제). 본 module 분리의 _자동 검증 메커니즘_.
- [[raw/branch-notes/feature-application-port-usecase-contract]] — `application-core` 내부 패키지 구조의 후속 (D1: `*UseCase` / `*Port` naming). canonical 정제 시 통합.
- [[raw/company-tech-blogs/woowahan-hexagonal-multimodule]] — Domain / Application / Framework / Bootstrap 4-module 사례 (`WW-HEX-C1`).
- [[raw/company-tech-blogs/modulith-kakaobank-techblog-2025]] — Gradle multi-module + Hexagonal + Modulith 사례 (`KAKAOBANK-MOD-C4`).
- [[raw/official-docs/hexagonal-cockburn-wikipedia-summary]] — Ports & Adapters 원형 (`HEX-WIKI-C5`).
- [[raw/official-docs/arch-clean-architecture-uncle-bob]] — Dependency Rule 의 클래식 근거.
- [[raw/official-docs/layer-first-baeldung-clean-architecture-spring-boot]] — _대안 1: layer-first_ 입문형 사례.
- [[wiki/concepts/clean-architecture-package-layout]] — Clean Architecture package/module layout 개념.
- [[wiki/projects/ca-tmpl/clean-architecture-package-layout]] — ca-tmpl 적용 (canonical, 갱신 필요).
## 미해결 / Unknown
- 모르는 것: Spring Modulith 를 후속 도입했을 때 Gradle multi-module + ArchUnit 과의 _중복/대체_ 관계.
- 모르는 것: 실제 사업 도메인 추가 시 module 분할 패턴 (e.g., 결제 추가 시 `domain-core` 가 결제 / 사용자 / 주문 등 sub-package 로 비대해지는 시점은 어디인가).
- 확인 방법: `feature-application-port-usecase-contract`, `feature-domain-event-outbox-contract`, `feature-business-rule-validation-contract` 후속 branch 적용 결과 관찰.
## 답변 경계 / Answer boundary
- 자신 있게 말할 수 있는 범위:
- ca-tmpl 에서 module rename / package anchor / Gradle dependency verifier / ArchUnit rule / full local test 까지 _직접 수행_ 한 범위.
- 초기 single-module 결정을 multi-module 로 _뒤집은 의사결정 과정_ 과 근거 (case study 검토).
- `domain-core` / `application-core` / `adapter-{web,persistence,outbound}` / `shared-contract` / `sample-ticket` / `app-bootstrap`_책임과 forbidden import_.
- "이 부분은 공식 문서를 다시 보고 답변드리겠습니다" 라고 해야 하는 부분:
- Spring Modulith named interface 의 구체 configuration (도입한 적 없음).
- 회사별 shared kernel / common module 운영 표준 (ca-tmpl 의 결정은 _이 맥락_ 까지만).
- module 수 (4 vs 8 vs 12) 의 최적값 (case study 가 사례별로 다름).
- **절대 과장하지 말 것**:
- 운영 배포 경험인 것처럼 말하지 말 것. ca-tmpl 은 _template repository_ 이고 검증 등급은 `locally-verified`.
- 우아한형제들 / 카카오뱅크 사례를 _업계 표준_ 처럼 표현 금지 — 둘 다 _case study_ (`company-case-study` 등급).
- "처음부터 multi-module 이 답이라고 알았다" 식의 표현 금지 — 결정의 _reversion_ 사실을 숨기지 않음.
## Related / 관련
- 관련 면접 질문 (선행/후속): [[raw/interviews/shared-contract-and-sample-isolation]] (자매 — shared/sample 책임), [[raw/interviews/clean-architecture-boundary-enforcement]] (후속 — 자동 검증), [[raw/interviews/transaction-port-vs-spring-transactional]] (자매 — application 의 framework 격리).
- 영감을 받은 채용공고: (없음).
- 관련 블로그 글감: [[raw/blog-topics/clean-architecture-module-blueprint-2026-05-28]] (같은 경험의 글감), [[raw/blog-topics/clean-architecture-boundary-enforcement-2026-05-28]].
- 답변 derive 후 위치: 생성 전. 생성 시 `wiki/interview/architecture/clean-architecture-module-blueprint.md` 후보.
@@ -1 +0,0 @@
../../vault/40-publish/interviews/crown-one-query-vs-cqrs-lite-read-model.md
@@ -0,0 +1,61 @@
---
title: interview-prep / crown-one-query-vs-cqrs-lite-read-model
source_type: interview-prep
status: raw
related_branches: [experiment-nplus1-feed-api-replay]
related_projects: [ca-skeleton]
tags: [interview-prep, ca-skeleton, persistence, application, postgresql, cqrs]
created: 2026-07-15
status_label: drafting
---
# interview-prep: crown-one-query-vs-cqrs-lite-read-model
> Layer: `raw/interviews/` — 면접 질문 원본 수집·연구 노트입니다. 다듬어진 답변은 canonical 문서를 만든 뒤 `wiki/interview/`에 별도로 작성합니다.
## Parent / 부모
- [[raw/branch-notes/experiment-nplus1-feed-api-replay]] — D3가 Crown의 one-query endpoint와 L12의 same-store CQRS-lite read port를 병존시킨 이유와 검증 범위를 소유합니다.
## 질문 / Question
- 질문 원문: Crown의 one native query와 L12의 same-store CQRS-lite read model은 무엇이 다르며, 어떤 경우에 각각을 선택하시겠습니까?
- 출처: N+1 replay 작업에서 예상한 면접 질문입니다.
- 받은 날짜·맥락: 실제 면접에서 받은 질문은 아닙니다.
## 질문 의도 추론 / Why this question
- 핵심 평가 대상: N+1 해결과 query 수 최소화를 동일시하지 않는지, read-model 경계와 트레이드오프를 설명할 수 있는지 평가합니다.
- 함정 / 흔히 빠지는 답변 패턴: 두 query라는 사실만으로 L12를 N+1이라고 부르거나, one query가 모든 read endpoint의 정답이라고 일반화하는 답변입니다.
- 따라올 만한 후속 질문: Crown이 L12를 대체하지 않는 이유는 무엇인가요? native query의 SQL과 결과 mapping은 어떻게 검증했나요?
## 답변 재료 / Raw answer material
- 사실 1: Crown 경로는 visible parent keyset과 parent별 Top-3 child를 하나의 native query로 읽는 endpoint-specific 최적화입니다. (근거: [[raw/branch-notes/experiment-nplus1-feed-api-replay]] D3)
- 사실 2: L12는 parent projection 한 번과 child Top-3 query 한 번을 사용하는 same-store application query port입니다. 해당 integration test에서는 entity/collection hydration이 0으로 기록됐지만, Crown의 one-query endpoint를 대체하지 않습니다. (근거: [[raw/branch-notes/experiment-nplus1-feed-api-replay]] D3, §3 Crown과 L12의 의도적 차이)
- 프로젝트 작업에서 확인한 경험: local Docker HTTP smoke에서 Crown은 `prepared=1`, `entityLoads=0`으로, L12는 20개 item과 parent당 최대 Top-3 child로 확인됐습니다. 이는 local 환경 증거입니다. (근거: [[raw/branch-notes/experiment-nplus1-feed-api-replay]] §Docker HTTP + PostgreSQL smoke — final L12 tag)
- 트레이드오프: 이 작업에서는 query 수를 최소화하면서 Top-N + keyset + visibility를 한 endpoint에서 동시에 만족해야 할 때 Crown을 사용합니다. application read port의 분리를 보여 주거나 aggregate hydration 없이 두 projection query로 read shape를 조립할 때는 L12를 사용합니다. 업계 다수파·소수파에 관한 일반화는 이 raw note의 근거 범위 밖입니다. (근거: [[raw/branch-notes/experiment-nplus1-feed-api-replay]] D3)
- 한계 / "이건 안 해봤다": L12는 별도 read store나 outbox 동기화를 둔 Full CQRS가 아니며, Crown과 같은 visibility/keyset 기능을 모두 담지 않습니다. production 부하·latency SLA도 검증하지 않았습니다. (근거: [[raw/branch-notes/experiment-nplus1-feed-api-replay]] §Out of scope, D3)
## Sources / 근거 (답변의 사실 근거)
- [[raw/branch-notes/experiment-nplus1-feed-api-replay]] D3 — Crown 1-query와 L12 2-query CQRS-lite를 병존시키는 결정과 선택 조건.
- [[raw/branch-notes/experiment-nplus1-feed-api-replay]] D5 — `addScalar`는 runtime 결과 타입 mapping이며 Java compiler의 SQL syntax/schema 검증이 아니라는 경계.
## 미해결 / Unknown
- 실제 production 데이터 분포에서 Crown의 native query plan과 L12의 두 query가 어느 latency/throughput 경계에서 갈리는지 확인하지 않았습니다.
- physical read store와 동기화 계약이 필요한 시점의 Full CQRS 전환 기준은 이 작업 범위에 없습니다.
- 확인 방법: representative PostgreSQL 데이터에서 `EXPLAIN (ANALYZE, BUFFERS)`와 부하 측정을 수행하고, 별도 read store가 필요한 요구가 생기면 application query-bypass contract를 기준으로 새 설계를 작성합니다.
## 답변 경계 / Answer boundary
- 자신 있게 말할 수 있는 범위: local Docker PostgreSQL과 HTTP smoke, focused Gradle integration test에서 Crown의 one-query 관찰값과 L12의 two-query projection 동작을 확인한 범위입니다.
- "이 부분은 공식 문서를 다시 보고 답변드리겠습니다" 라고 해야 하는 부분: CQRS의 일반적 정의, physical read store를 둘 때의 동기화 방식, production scale의 성능 우위입니다.
- **절대 과장하지 말 것**: Crown이 모든 상황에서 더 빠르다고 말하지 않습니다. L12를 Full CQRS나 Crown의 기능적 대체물로 말하지 않습니다. local 검증을 production 검증으로 말하지 않습니다. `addScalar`가 SQL을 compile-time에 검증한다고 말하지 않습니다.
## Related / 관련
- 관련 작업: [[raw/branch-notes/experiment-nplus1-feed-api-replay]]
- 후속 raw 질문 후보: `raw/interviews/native-query-addscalar-runtime-validation.md`
- 답변 derive 후 위치: canonical 문서가 준비된 뒤 `wiki/interview/persistence/crown-one-query-vs-cqrs-lite-read-model.md`
@@ -1 +0,0 @@
../../vault/40-publish/interviews/deterministic-logback-asyncappender-drop-metric-test-2026-06-14.md
@@ -0,0 +1,42 @@
---
title: interview / deterministic-logback-asyncappender-drop-metric-test-2026-06-14
source_type: interview-prep
status: raw
related_branches: [feature-log-management-contract]
related_projects: [ca-skeleton]
tags: [interview, ca-skeleton, logback, asyncappender, micrometer, testing, determinism, observability, masking]
created: 2026-06-14
status_label: captured
---
# interview: deterministic-logback-asyncappender-drop-metric-test-2026-06-14
> Layer: `raw/interviews/` — 작업에서 파생된 면접/구두설명 질문 원석.
## Parent / 부모
- [[raw/branch-notes/feature-log-management-contract]] — DRIFT-5(`log.appender.dropped.total`) + DRIFT-2(Layer 1 masking) 구현에서 파생.
## Q1. Logback `AsyncAppender` 의 드롭(discard)을 어떻게 *결정론적으로* 테스트하나?
`AsyncAppender` 는 queue 잔여 용량이 `discardingThreshold` 밑으로 떨어지면 ≤INFO 이벤트를 조용히 버린다. 이 드롭은 worker 스레드 drain 타이밍에 의존 → 단순 burst 테스트는 flaky.
**트릭**: `discardingThreshold > queueSize` 로 설정하면 `getRemainingCapacity()`(최대 queueSize) `< discardingThreshold`**항상 true**`isQueueBelowDiscardingThreshold()` 항상 참 → 모든 discardable(≤INFO) 이벤트가 **호출 스레드에서 동기 드롭**. async worker 타이밍이 식에서 제거되어 카운터 단언이 결정론적. (logback `AsyncAppenderBase.start()``discardingThreshold == -1` 일 때만 `queueSize/5` 로 기본값 설정 — 명시값을 상한 캡 하지 않음을 바이트코드로 확인.) WARN/ERROR 는 `isDiscardable()==false` 라 같은 조건에서도 드롭/카운트 안 됨을 같은 테스트로 검증.
## Q2. Logback 이 Spring 보다 먼저 초기화되는데 custom appender 가 Micrometer 카운터를 어떻게 발행하나?
`io.micrometer.core.instrument.Metrics.globalRegistry`(정적 composite)로 발행. Spring Boot 가 애플리케이션 `MeterRegistry` 를 글로벌 composite 에 추가하므로 logback 이 먼저 떠도 결국 actuator/metrics 에 노출. 테스트는 `SimpleMeterRegistry``Metrics.addRegistry` 로 붙였다 `removeRegistry` 로 떼며 격리. 태그 cardinality 는 레지스트리 SSOT(`metrics.yaml`)의 `level∈{INFO,DEBUG}` 로 제한.
## Q3. 구조화 JSON 로그에서 secret 마스킹은 왜 `%replace`(PatternLayout converter)로 부족한가?
`%replace` 는 PatternLayout 단계 converter. 그러나 `LogstashEncoder` 는 PatternLayout 을 **우회**해 JSON 을 직접 생성 → `%replace` 미적용(마스킹 누락). JSON 경로는 `MaskingJsonGeneratorDecorator`(JSON 생성 시점 value masker), pattern 경로는 별도 converter(`%maskedMsg`)로 같은 정규식. 정규식 catalog 를 단일 SSOT 로 두어 양 경로 일관. 상세: [[raw/blog-topics/logback-layer1-secret-masking-json-vs-pattern-2026-06-14]].
## Q4. `javax.crypto.Mac` 이 thread-safe 하지 않은데 singleton pseudonymizer bean 에서 어떻게 다루나?
`Mac` 은 상태를 가져 thread-safe 하지 않다. 옵션: (a) 호출마다 `Mac.getInstance` 새로 생성(단순·안전), (b) `ThreadLocal<Mac>`, (c) 인스턴스 풀. 본 구현은 (a) — `SecretKeySpec`(불변)만 필드로 보관, `pseudonymize()` 마다 `Mac` 생성+init. HMAC-SHA-256 은 JDK 보장 알고리즘이라 checked 예외는 unchecked 로 래핑(사실상 도달 불가). salt 는 생성자에서 방어적 clone.
## 관련 / Related
- [[raw/branch-notes/feature-log-management-contract]]
- [[raw/blog-topics/logback-layer1-secret-masking-json-vs-pattern-2026-06-14]]
- [[raw/errors/webmvctest-component-filter-constructor-dep-breaks-slice-2026-06-14]]
@@ -1 +0,0 @@
../../vault/40-publish/interviews/digest-first-supply-chain-release-gates.md
@@ -0,0 +1,64 @@
---
title: interview-prep / digest-first-supply-chain-release-gates
source_type: interview-prep
status: raw
related_branches: [feature-build-release-supply-chain-contract]
related_projects: [ca-skeleton]
tags: [interview-prep, ca-skeleton, ci-cd, build-tooling, slsa, supply-chain]
created: 2026-06-21
status_label: ready-for-derive
---
# interview-prep: digest-first-supply-chain-release-gates
> Layer: `raw/interviews/` — 구현 경험에서 정직하게 파생한 공급망 릴리스 설계 질문 원본.
## Parent / 부모
- [[raw/branch-notes/feature-build-release-supply-chain-contract]] — Gradle/OCI/Cosign/SLSA release gate를 실제로 배선한 branch.
## 질문 / Question
- 질문 원문: Java/Gradle 서비스의 컨테이너 릴리스에서 dependency lock, SBOM, 취약점 검사, Cosign 서명, SLSA provenance를 어떤 순서로 release-blocking하게 설계했나요?
- 출처: 구현 경험에서 유추한 예상 질문.
- 받은 날짜·맥락: 해당 없음.
## 질문 의도 추론 / Why this question
- 핵심 평가 대상: supply-chain 개념 이해, immutable artifact 설계, gate ordering, fail-open 경계, 운영 검증 한계 인식.
- 함정 / 흔히 빠지는 답변 패턴: tag를 artifact identity로 취급하거나, signature “존재”만 확인하고 signer identity/issuer를 검증하지 않는 답변.
- 따라올 만한 후속 질문: rollback retention은 어떻게 검증하는가, SLSA builder ID는 왜 exact match인가, deploy-time admission은 누가 소유하는가.
## 답변 재료 / Raw answer material
- 사실 1: artifact version은 SemVer+git sha이고 image는 digest로 build/sign/verify/promotion한다. 근거: [[raw/branch-notes/feature-build-release-supply-chain-contract]] D1, D4, D9.
- 사실 2: Cosign verify는 exact workflow certificate identity와 GitHub OIDC issuer를 모두 검사한다. 근거: 같은 branch D6, D12 및 [[raw/official-docs/cosign-keyless-identity-verification-policy]].
- 사실 3: SLSA verifier는 source tag/URI와 exact generator builder ID를 검사하고 v1 predicate field도 확인한다. 근거: 같은 branch D7, D13 및 [[raw/official-docs/slsa-v1-provenance-schema]].
- 내가 직접 한 경험: strict Gradle lock positive/negative, 두 clean build SHA-256, release manifest/retention fixture를 구현·검증했다. 근거: 같은 branch §구현 결과.
- 트레이드오프: 표준/다수파 방향은 immutable digest와 keyless identity 검증이다. 팀 정책인 recent 10 OR 90일 retention은 rollback 가용성을 높이지만 registry 비용을 늘린다.
- 한계 / "이건 안 해봤다": 실제 GitHub OIDC/Rekor/GHCR release와 Kubernetes admission 배포는 실행하지 않았다.
## Sources / 근거
- [[raw/official-docs/supply-chain-cosign-keyless-sigstore]] — keyless signature와 transparency log.
- [[raw/official-docs/supply-chain-slsa-provenance-framework]] — provenance와 build-level 판단.
- [[raw/official-docs/slsa-v1-provenance-schema]] — official predicate field와 builder ID.
- [[raw/official-docs/supply-chain-gradle-vs-maven-dependency-locking]] — Gradle dependency lock.
## 미해결 / Unknown
- 모르는 것 1: 실제 repository에서 SLSA generator가 발행한 provenance와 exact builder ID의 최종 payload.
- 모르는 것 2: GHCR retention/garbage collection이 signature·attestation referrer 보존에 미치는 실제 영향.
- 확인 방법: release candidate tag로 GitHub Actions 실행 후 Cosign/SLSA verification과 scheduled retention audit 결과를 보관한다.
## 답변 경계 / Answer boundary
- 자신 있게 말할 수 있는 범위: local code, Gradle/Docker/manifest behavior, gate DAG와 fail-closed 조건.
- "이 부분은 공식 문서를 다시 보고 답변드리겠습니다"라고 해야 하는 부분: 운영 중 Rekor/GHCR SLA와 조직별 admission policy.
- **절대 과장하지 말 것**: local fixture와 정적 workflow 검증을 production release 운영 경험처럼 말하지 않는다.
## Related / 관련
- 관련 error: [[raw/errors/sandbox-build-verification-boundaries-2026-06-21]].
- 관련 blog topic: [[raw/blog-topics/digest-first-java-release-pipeline-2026-06-21]].
- 답변 derive 후 위치: canonical 정제 후 결정.
@@ -1 +0,0 @@
../../vault/40-publish/interviews/domain-modeling-guardrails-archunit-2026-06-05.md
@@ -0,0 +1,46 @@
---
title: interview / domain-modeling-guardrails-archunit-2026-06-05
source_type: interview
status: raw
related_branches: [feature-domain-modeling-guardrails]
related_projects: [ca-skeleton]
tags: [interview, ca-skeleton, archunit, ddd, value-object, aggregate, domain-event, clean-architecture]
created: 2026-06-05
status_label: captured
---
# interview: domain-modeling-guardrails-archunit-2026-06-05
> Layer: `raw/interviews/` — 이 작업에서 정직하게 도출 가능한 면접 질문. canonical 승급 전 raw.
## Parent
- [[raw/branch-notes/feature-domain-modeling-guardrails]]
## 질문 목록
**Q1.** DDD 전술 패턴(값 객체/애그리거트/도메인 이벤트)을 "문서 권고"가 아니라 빌드에서 강제하려면 어떻게 하나?
- A: stereotype 마커 애너테이션(`@ValueObject`/`@AggregateRoot`/`@DomainEvent`)을 도메인 코어에 두고, ArchUnit fitness function 이 그 마커를 키로 규칙을 평가. 값 객체 = public no-arg 생성자 부재, 애그리거트 = `set*` 비공개, 도메인 이벤트 = record + transport 패키지 의존 금지.
**Q2.** "도메인 순수성(framework-neutral)" 규칙과 "도메인 logger 금지" 규칙을 왜 한 규칙으로 합치지 않고 분리했나?
- A: owner 경계. 도메인 순수성(`domain_is_pure`)은 `feature-architecture-enforcement-rules` 가 소유. 거기에 logging 패키지를 끼우면 한 branch 의 결정이 다른 branch owner 규칙에 섞여 위반 메시지·소유권이 흐려진다. 별도 `domain_has_no_logger` 로 두면 위반 사유가 명확하고 owner 가 분리된다.
**Q3.** 도메인 logger 금지의 "공식 표준 출처"가 있나?
- A: 없다. clean-architecture 통념이지 RFC/vendor 표준이 아니다. 그래서 프로젝트 자체 규약(UNSUPPORTED_DECISION)으로 확정하고, 외부(면접/README)에서 "표준이라 막았다"고 말하지 않는다. 사실 등급을 격상하지 않는 정직성.
**Q4.** 불변식 위반을 도메인에서 어떻게 표현하나? 왜 로그가 아니라 예외인가?
- A: 안전한 명사형 reason enum 을 가진 도메인 예외(`WorkLogInvariantException(Reason)`). 도메인은 로그/운영 에러코드를 모르고(D2/D3), application/web 이 `reason()` 을 error.category·로그로 번역. security-sensitive 사유는 일반화된 category 로만 노출해 단서 누출 방지.
**Q5.** 값 객체 불변식을 단위 테스트 몇 개로 "충분히" 검증했다고 할 수 있나?
- A: 못 한다. 예시 기반 테스트는 저자가 고른 케이스만 본다. jqwik property-based test 로 입력 공간 전체(canonical ULID, 비-canonical, 제외문자/소문자)를 무작위 생성해 불변식이 유일 생성 경로에서 항상 강제됨을 검증.
**Q6.** 도메인 이벤트를 "transport-free" 로 둔다는 게 무슨 의미이고, 통합(integration) 이벤트와 어떻게 분리하나?
- A: 도메인 이벤트는 도메인 타입만 담는 immutable record. Kafka/HTTP/JAX-RS 타입을 참조하면 안 됨(ArchUnit `domain_events_are_transport_free`). wire 표현으로의 변환(값 객체 → primitive flatten, 직렬화 포맷 선택)은 application 경계의 mapper 책임 → `WorkLogReserved`(domain) → `WorkLogReservedIntegrationEvent`(application).
**Q7.** ArchUnit 로 강제 가능한 범위의 한계는?
- A: `set*` prefix 같은 정적 시그니처는 잡지만, `applyXxx`/`markAsXxx` 같은 임의 상태변경 메서드나 Kotlin `copy()`/record wither 우회는 정적으로 못 잡는다. 그 부분은 코드리뷰·네이밍 컨벤션으로 보완하고 Open Risk 로 명시.
## Cross-links
- [[raw/errors/archunit-testcompileonly-class-loading-2026-06-02]] — fixture 작성 중 부딪힌 JUnit discovery 함정
- [[raw/interviews/archunit-static-analysis-limits]] — ArchUnit 정적 분석 한계 일반론
@@ -1 +0,0 @@
../../vault/40-publish/interviews/formatter-vs-style-linter-responsibility-split-2026-06-20.md
@@ -0,0 +1,49 @@
---
title: interview-prep / formatter-vs-style-linter-responsibility-split-2026-06-20
source_type: interview-prep
status: raw
related_branches: [feature-static-analysis-quality-contract]
related_projects: [ca-skeleton]
tags: [interview-prep, ca-skeleton, static-analysis, spotless, checkstyle, ci, formatter]
created: 2026-06-20
status_label: collecting
---
# interview-prep: formatter-vs-style-linter-responsibility-split-2026-06-20
> Layer: `raw/interviews/` — 면접 질문 원본 수집·연구 노트. 다듬어진 답변은 `/interviewize` 후 `wiki/interview/` 에 별도 작성.
## Parent / 부모
- [[raw/branch-notes/feature-static-analysis-quality-contract]] — Spotless(google-java-format) + Checkstyle 을 한 빌드에 같이 도입할 때 부딪힌 핵심 결정(D1/D2/§3 catalog).
## 질문 / Question
- 질문 원문: 코드 포매터(google-java-format)와 스타일 린터(Checkstyle)를 같은 CI 에 둘 다 넣을 때, 둘의 책임을 어떻게 나눠야 하나요? 나누지 않으면 무슨 일이 일어나나요?
- 출처: 예상 질문 (실 면접 아님).
- 받은 날짜·맥락: 아직 없음 — 2026-06-20 static-analysis-quality-contract 구현에서 도출.
## 질문 의도 추론 / Why this question
- 핵심 평가 대상:
- "도구를 많이 넣는 것" 과 "도구 책임을 분리하는 것" 의 차이를 아는지. 같은 규칙을 두 도구가 강제하면 도구 수가 늘수록 충돌이 는다는 걸 이해하는지.
- CI 가 자기 자신과 싸우는 실패 모드(무한 reformat 루프)를 예측·예방할 수 있는지.
## 답변 뼈대 / Answer skeleton
- **원칙: 한 규칙은 한 도구만 소유한다.** 포매터는 *기계적으로 결정 가능한 표현*(들여쓰기, 줄바꿈, 공백, import 순서)을 소유. 린터는 *포매터가 결정 못 하는 의미*(naming, Javadoc 존재, NeedBraces/FallThrough 같은 logical 규칙)를 소유.
- **나누지 않으면**: google-java-format 이 코드를 A 모양으로 고치고 Checkstyle 의 `Indentation`/`LineLength`/`CustomImportOrder` 가 그걸 위반이라 reject → 개발자가 다시 고치면 포매터가 또 A 로 → CI 무한 reformat 루프(checkstyle 이슈 #6527). 특히 import order 가 양쪽(Spotless `importOrder()` ↔ Checkstyle `CustomImportOrder`)에 다 있으면 영구 충돌.
- **구체적 처리**: Checkstyle ruleset 에서 formatting 모듈(`Indentation`, `LineLength`, `WhitespaceAround`, `LeftCurly`/`RightCurly`, `SeparatorWrap`, `OperatorWrap`, `EmptyLineSeparator`)과 `CustomImportOrder`**아예 빼고**, naming + Javadoc + logical 만 남긴다. google-java-format 은 100-char·결정론적 포맷이라 LineLength 도 포매터가 보장.
- **검증**: `./gradlew spotlessApply && ./gradlew checkstyleMain` 을 연속 실행해 위반 0(서로 안 싸움)을 확인. 의도적 포맷 깨뜨림 후 `spotlessCheck` 가 BUILD FAILED 하는지(gate bites)도 확인.
- **CI 규약**: CI 는 `spotlessApply`(파일 mutate)를 절대 실행하지 않고 `spotlessCheck`(검증)만 — 자동수정은 개발자 로컬에서.
## 꼬리 질문 / Follow-ups
- "그럼 LineLength 를 누가 보장하나?" → 포매터(google-java-format 100-char). 린터에서 빼도 길이는 강제됨.
- "기존 코드가 포맷·Javadoc 을 안 지키면 도입 시 어떻게?" → 포맷은 `spotlessApply` 일괄 적용(표준), Javadoc 처럼 기계수정 불가·대량인 규칙은 warning-tier 로 시작해 점진 승급(또는 ratchet). [[raw/branch-notes/feature-static-analysis-quality-contract]] §3/§4.
- "관용구를 규칙이 false-positive 로 잡으면?" → 코드 rename 말고 규칙 보정(예: ConstantName 이 SLF4J `log` 를 잡으면 패턴에 `log`/`logger` 허용 — Logger 는 Google §5.2.4 상 상수가 아님).
## 관련 / Related
- [[raw/branch-notes/feature-static-analysis-quality-contract]]
- [[raw/blog-topics/gradle9-java21-static-analysis-baseline-2026-06-20]]
@@ -1 +0,0 @@
../../vault/40-publish/interviews/gradle-sample-off-test-classpath-isolation.md
@@ -0,0 +1,33 @@
---
title: Gradle sample-off test classpath isolation
source_type: interview
status: raw
tags: [gradle, testing, clean-architecture, sample-fixture]
created: 2026-06-25
---
# Gradle sample-off test classpath isolation
## Parent
- [[raw/branch-notes/feature-sample-removal-adoption-contract]]
## Question
템플릿 저장소가 sample fixture 모듈을 유지해야 하지만 production/core 계약은 sample 없이도 검증되어야 한다. Gradle 멀티모듈에서 이를 어떻게 설계할 수 있는가?
## Expected answer
- sample module은 production dependency가 아니라 fixture/test dependency로 둔다.
- 일반 `test`는 sample-on 축으로 유지한다.
- 별도 `sampleOffTest` source set/task를 만들어 같은 core contract test source를 실행하되 `sample-portfolio` dependency를 classpath에서 제외한다.
- sample을 직접 import하던 core test는 제거하거나 sample module 소유 테스트로 이동한다.
- CI release gate에는 sample-on과 sample-off를 모두 포함한다.
- ArchUnit 같은 bytecode 스캐너는 custom test output을 production output으로 오인하지 않도록 import option을 보강한다.
## Follow-up probes
- 왜 runtime profile이 아니라 build/test matrix인가?
- Custom source set에서 dependency locking과 main output을 왜 별도로 확인해야 하는가?
- sample 제거 후 빈 ArchUnit corpus는 실패로 볼지 정상으로 볼지 어떻게 결정하는가?
- Hosted CI와 local verification의 증거 등급은 어떻게 구분하는가?
@@ -1 +0,0 @@
../../vault/40-publish/interviews/idempotency-rate-limit-design-tradeoffs-2026-06-09.md
@@ -0,0 +1,53 @@
---
title: interview / idempotency-rate-limit-design-tradeoffs-2026-06-09
source_type: interview-prep
status: raw
related_branches: [feature-rate-limit-idempotency-contract]
related_projects: [ca-skeleton]
tags: [interview, ca-skeleton, idempotency, rate-limit, concurrency]
created: 2026-06-09
---
# interview: 멱등성 / rate-limit 설계 트레이드오프
> Layer: `raw/interviews/` — feature-rate-limit-idempotency-contract 구현에서 나올 수 있는 질문.
## Parent / 부모
- [[raw/branch-notes/feature-rate-limit-idempotency-contract]]
## 예상 질문 / Q&A
- **Q. 동시에 같은 idempotency key가 오면?**
A. DB unique 제약(`(tenant, principal, idempotency_key, use_case_name)`)을 동시성 중재자로 사용.
첫 요청이 IN_FLIGHT row 선점(insert), 후속은 insert 실패 → read. read가 IN_FLIGHT면 200ms까지
poll 후 초과 시 409 IDEMPOTENT_IN_FLIGHT(retryable=false, client는 polling).
- **Q. 200ms wait는 표준인가?**
A. 아니다. IETF draft/Toss는 즉시 409 SHOULD. 200ms는 client retry 친화적 "변형"이고 thread를
잡는 비용이 있어 부하 테스트로 튜닝 대상. 면접에서 "표준 따름"으로 말하면 안 됨.
- **Q. 같은 key + 다른 body는?**
A. body SHA-256 fingerprint 비교 → 다르면 422 IDEMPOTENT_REQUEST_MISMATCH(IETF 422 권고 정합).
단 canonicalization(키 순서/공백) 미적용 시 false mismatch 위험 — 본 구현은 직렬화된 payload 기준.
- **Q. single-tenant인데 unique 제약이 동작하나? (tenant NULL)**
A. PostgreSQL은 NULL을 distinct로 취급 → NULL tenant면 dedup 실패. 그래서 tenant 컬럼을
`NOT NULL DEFAULT ''`로 두고 매퍼가 null↔'' 변환.
- **Q. 만료(TTL) 처리?**
A. 읽기에서 만료 row를 absent 취급 + tryBegin에서 만료 row reclaim(delete 후 insert) + 주기적
reaper(@Scheduled bulk delete) 3중. 읽기 필터와 쓰기 선점이 같은 만료 기준을 공유해야 "유령 충돌"이 없음.
- **Q. rate-limit 알고리즘은?**
A. single-node in-process fixed-window counter(ConcurrentHashMap.compute + AtomicInteger).
장점: X-RateLimit-Reset이 창 종료로 정확. 단점: 창 경계 burst 허용, 멀티 인스턴스면 N배(distributed limiter는 out of scope).
- **Q. 왜 filter가 아니라 interceptor?**
A. unauth key가 `IP + route template`을 요구하는데 servlet filter는 handler mapping 전이라 template을 모름.
interceptor는 `BEST_MATCHING_PATTERN_ATTRIBUTE``/v1/worklogs/{id}`를 얻음.
## 관련
- [[raw/branch-notes/feature-rate-limit-idempotency-contract]]
- [[wiki/concepts/idempotency-key-design]]
@@ -1 +0,0 @@
../../vault/40-publish/interviews/jwt-resource-server-fine-grained-error-classification-2026-06-08.md
@@ -0,0 +1,46 @@
---
title: interview-prep / jwt-resource-server-fine-grained-error-classification
source_type: interview-prep
status: raw
related_branches: [feature-security-operational-baseline]
related_projects: [ca-skeleton]
tags: [interview-prep, ca-skeleton, security, jwt, spring-security, error-handling]
created: 2026-06-08
status_label: collecting
---
# interview-prep: jwt-resource-server-fine-grained-error-classification
> Layer: `raw/interviews/` — 면접 질문 원본 수집. 다듬은 답변은 `/interviewize` 후 `wiki/interview/`.
## Parent / 부모
- [[raw/branch-notes/feature-security-operational-baseline]] — JWT Resource Server 인증/인가 실패의 fine-grained 운영 분류 구현.
## 질문 / Question
- 질문 원문: JWT 인증 실패를 401 하나로 뭉개지 않고, 운영자가 missing/expired/signature/issuer/audience/unknown-kid 를 구분할 수 있게 어떻게 구현했나요? 클라이언트에는 무엇을 노출했나요?
- 출처: 예상 질문 (실제 면접 아님).
## 질문 의도 추론 / Why this question
- 핵심 평가 대상:
- Spring Security resource server 의 **실패 처리 위치** 이해 — bearer 토큰 검증 실패는 `BearerTokenAuthenticationFilter`/`ExceptionTranslationFilter``AuthenticationEntryPoint` 로 보내며 `@RestControllerAdvice`**도달하지 않는다**. 그래서 fine-grained 분류는 EntryPoint/AccessDeniedHandler 에 있어야 한다.
- 보안 응답의 **과노출 방지** — 클라이언트엔 generic message(`Authentication failed`)만, 내부엔 분류 code. issuer/audience/token 값을 응답·로그에 흘리지 않기.
- 표준 정합 — 401 은 `WWW-Authenticate` MUST(RFC 9110 §15.5.2), transient(kid/jwks)엔 `Retry-After`.
- 함정:
- "@RestControllerAdvice 에서 `AuthenticationException` 잡으면 된다" — filter-layer 실패는 거기 안 온다.
- exception → code 매핑을 message 문자열 heuristic 에 의존하는 것의 fragility 를 인정 안 함.
- clock skew 를 default 에 맡기고 "Spring 이 알아서" — 버전 업 시 silent drift.
- 후속 질문:
- `JwtValidationException``BadJwtException` 의 차이, 각각 어떤 실패인가?
- unknown kid 를 왜 retryable=true + Retry-After 로 두나? (rotation 중 JWKS refresh 로 해소)
- clock skew 60s 를 명시 설정한 이유? (default 의존 시 drift)
- 다중 audience/validator 동시 실패 시 어떤 code 를 우선하나?
## 답변 재료 / Raw answer material
- 구현: `SecurityErrorClassifier`(exception graph + validator/Nimbus message heuristic, 우선순위 expired>issuer>audience), `EnvelopeAuthenticationEntryPoint`/`EnvelopeAccessDeniedHandler`(공통 `AuthErrorResponseWriter` → Envelope JSON), `JwtDecoderConfig`(`SupplierJwtDecoder` 로 lazy 60s clock skew + issuer + audience validator).
- 12 code 는 `docs/registries/error-codes.yaml` SSOT 와 `OperationalError` enum 일치(status/category/retryable).
- redaction: 응답 body 는 generic message, 로그는 code/category/method/path 만 — token(`eyJ...`) 미노출. contract test 로 강제.
- 한계(솔직): message 문자열 heuristic 은 Spring/Nimbus 버전 메시지 변경에 취약 → unmapped 는 generic 401 fallback(절대 500 아님). 실 IdP 통합 테스트는 미수행(`prod-verified` 아님).
@@ -1 +0,0 @@
../../vault/40-publish/interviews/manifest-driven-multi-platform-agent-harness.md
@@ -0,0 +1,60 @@
---
title: interview-prep / manifest-driven-multi-platform-agent-harness
source_type: interview-prep
status: raw
related_branches: [chore-harness-policy-engine-alignment]
related_projects: [ca-skeleton]
tags: [interview-prep, ca-skeleton, architecture, build-tooling, multi-module]
created: 2026-07-20
status_label: collecting
---
# interview-prep: manifest-driven-multi-platform-agent-harness
## 부모
- [[raw/branch-notes/chore-harness-policy-engine-alignment]] — 동일 Clean Architecture 하네스를 여러 agent platform에 적용한 실제 설계·검증 경험.
## 질문
- 질문 원문: 여러 AI coding agent 플랫폼에서 모듈 경계와 review evidence를 일관되게 강제하려면 하네스를 어떻게 설계하겠습니까?
- 출처: 이번 작업에서 도출한 예상 질문
- 받은 날짜·맥락: 해당 없음
## 질문 의도 추론
- 핵심 평가 대상: SSOT 설계, fail-closed validation, code generation, risk-based workflow, 한계 인식.
- 함정: prompt 문구만 동기화하고 실제 module topology·hook contract·evidence identity를 검증하지 않는 답변.
- 후속 질문: ignored 파일의 revision identity, platform-specific hook, baseline failure 분리, authenticated E2E 한계.
## 답변 재료
- 사실: 19개 leaf module의 topology와 dependency를 registry 하나로 옮겼다. 근거: branch D1.
- 사실: verdict는 counts equation, command rows, revision/rule hash, upstream artifact를 검증한다. 근거: branch D2.
- 사실: canonical agent 5개에서 네 종류 플랫폼 산출물을 생성하고 hash parity를 검사한다. 근거: branch D3.
- 경험: nested path/import gate blind spot과 ignored guidance hash 누락을 mutation review로 잡았다. 근거: branch §마주친 문제.
- 트레이드오프: strict fail-closed는 stale evidence를 막지만 local workflow 마찰을 늘린다. risk/evidence profile로 저위험 작업의 비용을 줄였다. 근거: branch D4.
- 한계: authenticated 외부 제품 golden run과 production 전체 check green은 달성하지 못했다.
## 근거
- [[raw/branch-notes/chore-harness-policy-engine-alignment]] D1-D5, §검증 결과.
- [[raw/official-docs/google-antigravity-hooks]] — Antigravity hook contract.
## 미해결
- 실제 세 플랫폼의 lifecycle 차이가 static adapter test로 모두 잡히는지.
- Codex/Claude의 공식 hook lifecycle과 Antigravity Stop 재진입 차이를 공통 evidence model이 충분히 흡수하는지.
- 확인 방법: 인증 환경 golden task와 evidence JSON 비교, failure mutation 반복.
## 답변 경계
- 자신 있게 말할 수 있는 범위: repository-local registry, mutation, renderer parity, strict schema 검증은 local verified.
- 공식 문서를 다시 봐야 하는 부분: 제품 버전별 hook event/permission 변화.
- **절대 과장하지 말 것**: static parity를 실제 production/platform E2E 검증이라고 말하지 않는다.
## 관련
- [[raw/blog-topics/manifest-driven-agent-harness-policy-engine]]
- canonical interview: 생성 전.
@@ -1 +0,0 @@
../../vault/40-publish/interviews/native-query-addscalar-runtime-validation.md
@@ -0,0 +1,62 @@
---
title: interview-prep / native-query-addscalar-runtime-validation
source_type: interview-prep
status: raw
related_branches: [experiment-nplus1-feed-api-replay]
related_projects: [ca-skeleton]
tags: [interview-prep, ca-skeleton, persistence, testing, hibernate, postgresql, static-analysis]
created: 2026-07-15
status_label: drafting
---
# interview-prep: native-query-addscalar-runtime-validation
> Layer: `raw/interviews/` — 면접 질문 원본 수집·연구 노트입니다. 다듬어진 답변은 canonical 문서를 만든 뒤 `wiki/interview/`에 별도로 작성합니다.
## Parent / 부모
- [[raw/branch-notes/experiment-nplus1-feed-api-replay]] — D5가 `addScalar`의 runtime 결과 mapping과 SQL compile-time 검증을 구분하고, D4가 실제 PostgreSQL 검증 경계를 소유합니다.
## 질문 / Question
- 질문 원문: Hibernate native query에서 `addScalar`를 썼는데도 SQL 문법이나 table/column 이름 오류를 Java compile-time에 잡을 수 없는 이유는 무엇이며, 어떤 검증으로 보완하셨습니까?
- 출처: N+1 replay의 L12 native child projection을 설명할 때 예상한 면접 질문입니다.
- 받은 날짜·맥락: 실제 면접에서 받은 질문은 아닙니다.
## 질문 의도 추론 / Why this question
- 핵심 평가 대상: Java 타입 검증, ORM 결과 mapping, database SQL 실행 검증의 경계를 구분하는지 평가합니다.
- 함정 / 흔히 빠지는 답변 패턴: `addScalar`가 SQL parser나 schema checker라고 설명하거나, Java compilation만으로 native SQL의 table/column 오류까지 검증됐다고 말하는 답변입니다.
- 따라올 만한 후속 질문: 결과 컬럼의 runtime type이 맞지 않으면 어디서 실패하나요? Testcontainers만으로 query plan이나 production latency까지 말할 수 있나요?
## 답변 재료 / Raw answer material
- 사실 1: 이 작업에서 `addScalar`는 native-query result extraction의 runtime type mapping으로 다뤘습니다. SQL 문자열의 문법, table/column 이름, query plan을 Java compiler가 검증하는 기능은 아닙니다. (근거: [[raw/branch-notes/experiment-nplus1-feed-api-replay]] D5, §3 Crown과 L12의 의도적 차이)
- 사실 2: `addScalar` type이 실제 결과와 맞지 않거나 native SQL이 잘못되면 Java compile이 아니라 integration/runtime 실행에서 실패합니다. (근거: [[raw/branch-notes/experiment-nplus1-feed-api-replay]] §엣지·실패·의존)
- 사실 3: 보완 수단으로 `FeedReadModelUseCaseIT`를 포함한 focused Gradle integration suite와 fresh Docker Compose PostgreSQL HTTP/SQL-row-count smoke를 수행했습니다. 이는 실제 PostgreSQL에서 query를 실행하는 검증입니다. (근거: [[raw/branch-notes/experiment-nplus1-feed-api-replay]] D4, §검증 기록)
- 프로젝트 작업에서 확인한 경험: L12 native child mapping을 `addScalar`로 실행했고, final L12 Docker smoke에서 reset, Crown feed, read-model response, invalid page HTTP 400, marker row count를 local 환경에서 확인한 기록이 있습니다. (근거: [[raw/branch-notes/experiment-nplus1-feed-api-replay]] §Docker HTTP + PostgreSQL smoke — final L12 tag)
- 트레이드오프: 이 작업의 선택은 Java compiler가 확인할 수 있는 코드 오류와 실제 PostgreSQL 실행이 확인할 native SQL 오류를 분리하는 방식입니다. `addScalar`만으로 검증 범위를 넓힌다는 선택은 채택하지 않았습니다. 업계 다수파·소수파에 대한 일반화는 이 raw note의 근거 범위 밖입니다. (근거: [[raw/branch-notes/experiment-nplus1-feed-api-replay]] D5)
- 한계 / "이건 안 해봤다": 실행한 test case와 fixture가 덮지 않은 SQL branch, representative production data에서의 query plan, latency SLA는 이 검증만으로 판단하지 않았습니다. (근거: [[raw/branch-notes/experiment-nplus1-feed-api-replay]] §Out of scope, §검증해야 할 주장)
## Sources / 근거 (답변의 사실 근거)
- [[raw/branch-notes/experiment-nplus1-feed-api-replay]] D5 — `addScalar`를 Java SQL compile-time checker로 설명하지 않는 결정과 runtime mapping 경계.
- [[raw/branch-notes/experiment-nplus1-feed-api-replay]] D4 — Testcontainers에 더해 fresh Docker Compose PostgreSQL에서 HTTP response와 SQL row count를 확인하는 검증 선택.
## 미해결 / Unknown
- PostgreSQL version, migration 순서, 실제 데이터량이 달라질 때 모든 native SQL path가 계속 유효한지는 별도 검증이 필요합니다.
- `addScalar` mapping 변경이 API response contract에 미치는 영향은 fixture 기반 integration test만으로 모두 포괄했다고 말할 수 없습니다.
- 확인 방법: relevant migration을 적용한 PostgreSQL에서 각 native-query endpoint와 `FeedReadModelUseCaseIT`를 실행하고, representative data에서는 `EXPLAIN (ANALYZE, BUFFERS)`와 별도 load test를 수행합니다.
## 답변 경계 / Answer boundary
- 자신 있게 말할 수 있는 범위: 이 repository의 L12 native query에 대해 `addScalar`가 runtime 결과 mapping이고, 실제 PostgreSQL integration/runtime 실행으로 오류를 발견하도록 검증했다는 local evidence 범위입니다.
- "이 부분은 공식 문서를 다시 보고 답변드리겠습니다" 라고 해야 하는 부분: Hibernate version별 API 세부사항, 다른 database vendor의 type coercion, 모든 SQL path의 coverage와 production query plan입니다.
- **절대 과장하지 말 것**: `addScalar`가 SQL syntax/schema를 compile-time에 검증한다고 말하지 않습니다. Testcontainers 결과를 모든 production data와 latency의 검증으로 말하지 않습니다. 한 번의 integration test가 native SQL의 모든 오류를 찾는다고 말하지 않습니다.
## Related / 관련
- 관련 작업: [[raw/branch-notes/experiment-nplus1-feed-api-replay]]
- 관련 면접 질문: [[raw/interviews/crown-one-query-vs-cqrs-lite-read-model]]
- 답변 derive 후 위치: canonical 문서가 준비된 뒤 `wiki/interview/persistence/native-query-addscalar-runtime-validation.md`
@@ -1 +0,0 @@
../../vault/40-publish/interviews/operational-error-envelope-and-observability-foundation.md
@@ -0,0 +1,58 @@
---
title: interview-prep / operational-error-envelope-and-observability-foundation
source_type: interview-prep
status: raw
related_branches: [feature-operational-error-observability-foundation]
related_projects: [ca-skeleton]
tags: [interview-prep, ca-skeleton, error-handling, observability, api-design, logging, security, mdc, testing]
created: 2026-06-01
status_label: collecting
---
# interview-prep: operational-error-envelope-and-observability-foundation
> Layer: `raw/interviews/` — 면접 질문 원본 수집·연구 노트. 다듬어진 답변은 `/interviewize` 후 `wiki/interview/` 에 별도 작성.
## Parent / 부모
- [[raw/branch-notes/feature-operational-error-observability-foundation]] — 운영 실패 분류 enum(10-category) + 응답 envelope `meta`/`error.category` + snake_case MDC + inbound 헤더 sanitization 을 구현·검증한 결정/근거.
- [[raw/project-notes/ca-skeleton-operational-contract]] — ca-tmpl 의 운영 계약 SSOT(§3 응답 envelope, §6 error category, §8 structured log).
## 질문 / Question
후보 질문 묶음 (실제 면접에서 받은 것 아님 — 본 작업에서 정직하게 도출):
1. 응답 포맷을 RFC 7807 ProblemDetail 대신 자체 envelope 으로 가져갔다고 했는데, 이미 운영 중인 envelope 에 `error.category``meta` 객체를 *기존 계약을 깨지 않고* 어떻게 추가했나요?
2. 같은 식별자가 로그에선 `request_id`(snake), JSON 응답에선 `meta.requestId`(camel), HTTP 헤더에선 `X-Request-Id`(kebab) 로 다르게 나오는데, 이게 버그가 아니라 의도된 설계라는 걸 어떻게 보장하나요?
3. 클라이언트가 보낸 `X-Request-Id` 헤더를 로그에 남길 때 어떤 보안 문제가 있고 어떻게 막았나요?
4. `retryable` 을 category 로 계산하지 않고 per-code 로 둔 이유는?
## 질문 의도 추론 / Why this question
- 핵심 평가 대상:
- **하위호환 확장**: 운영 중인 직렬화 계약에 필드를 *additive* 로 추가하는 감각 (record 컴포넌트 추가 시 모든 호출부/테스트가 깨지는 blast radius 를 어떻게 통제했는가 — 본 작업에선 `PortfolioErrorCode`/`BulkEnvelopeTest` 같은 숨은 consumer 까지 빌드로 잡아냄).
- **표현 계층 분리**: 동일 논리 식별자의 case 표현이 계층(MDC/JSON/HTTP/W3C)마다 다른 게 *관례*임을 알고, 매핑을 SSOT(registry)로 고정해 drift 를 막는 인식.
- **보안 기본기**: CWE-117 log injection / log forging 을 *구조화 JSON 로깅 전제* 에서 어떻게 다르게 다루는지 (CR/LF·제어문자 strip vs reject vs encode 의 trade-off).
- **계약 vs 런타임 분리**: category 는 식별(문서/메트릭 차원)이고 retryable 은 per-code 런타임 신호라는 *책임 분리* 인식.
- 함정 / 흔히 빠지는 답변 패턴:
- "envelope 만들었다"로 끝내고 *왜 ProblemDetail 을 거부*했는지(success/error 대칭 + retryable 1급 + 표준 lock-in 회피)와 *그 trade-off*(표준 호환성 손실)를 말 못함.
- snake↔camel↔kebab 을 "그냥 컨벤션"이라 하고 *단일 case 로 통일하면 왜 안 되는지*(HTTP/W3C/JSON 관례 충돌)를 설명 못함.
- log injection 을 "입력 검증"으로 뭉뚱그리고 *구조화 로깅에선 위협이 줄 위조(CR/LF)* 라는 점, strip 의 한계(필드 smuggling/길이 폭주는 length cap 으로 별도 처리)를 모름.
- retryable 을 category default 로 계산한다고 답해 `INTERNAL_ERROR(retryable=true)` 같은 per-code 예외를 설명 못함.
- 따라올 만한 후속 질문:
- 인터페이스에 추상 메서드(`category()`)를 추가했을 때 다운스트림 enum 이 전부 깨지는데, 이걸 컴파일러로 강제하는 게 장점인가 단점인가?
- `meta.traceId` 가 tracing 비활성 환경에서도 비면 안 된다고 했는데(D7) 어떻게 보장하나? (generated opaque id fallback)
- 이 계약 테스트를 `app-bootstrap` 풀 컨텍스트가 아니라 adapter 모듈 standalone MockMvc 로 옮긴 이유는? (→ [[raw/errors/webmvctest-nested-springbootconfiguration-context-pollution-2026-06-01]])
## 답변 뼈대 / Answer skeleton (raw)
- ProblemDetail 거부 = success/error 대칭 + `retryable`/`category` 1급화 + 표준 lock-in 회피. trade-off = RFC 표준 호환성 포기(의식적).
- additive 확장 = `error.category`(필드 추가), `meta`(flat `traceId`→객체) 모두 boundary D5/D6(대칭/ProblemDetail 거부)을 *불변*으로 두고 위에 얹음. 깨지는 consumer 는 컴파일러가 전부 노출 → 한 패스로 마이그레이션.
- 식별자 매핑 = registry(mdc-keys.yaml/headers.yaml)에 `mdc_key`/`envelope_meta_field`/header name 3열을 1:1 로 등록, 변환 지점은 `ResponseMetaFactory.fromMdc()` 단일화(snake→camel).
- log injection = 구조화 JSON 로깅 전제 → CR/LF/제어문자(`<0x20`) strip + length cap, reject/encode 아님(값 보존, 줄 위조만 차단).
## 관련
- [[raw/branch-notes/feature-operational-error-observability-foundation]]
- [[raw/errors/webmvctest-nested-springbootconfiguration-context-pollution-2026-06-01]]
- [[raw/blog-topics/operational-error-envelope-meta-category-migration-2026-06-01]]
@@ -1 +0,0 @@
../../vault/40-publish/interviews/optional-adapter-3-layer-disabled-detection-2026-06-09.md
@@ -0,0 +1,48 @@
---
title: interview / optional-adapter-3-layer-disabled-detection-2026-06-09
source_type: interview-prep
status: raw
related_branches: [feature-integration-adapter-templates]
related_projects: [ca-skeleton]
tags: [interview, ca-skeleton, spring, clean-architecture, adapter, observability]
created: 2026-06-09
status_label: raw
---
# interview: optional-adapter-3-layer-disabled-detection-2026-06-09
> Layer: `raw/interviews/` — 이 작업에서 정직하게 뽑을 수 있는 면접 질문/답변.
## Parent / 부모
- [[raw/branch-notes/feature-integration-adapter-templates]]
## Q1. 선택형 어댑터(Kafka/Redis/Slack/Email)를 "꺼져 있음"으로 안전하게 보장하려면?
3계층으로 검출한다.
- **Layer 1 (startup, runtime)** — Spring `@ConditionalOnProperty(name="app.<domain>.<adapter>.enabled", havingValue="true", matchIfMissing=false)`. flag 미설정/false 면 real adapter bean 미등록(disabled bean count = 0). `matchIfMissing=false` 를 명시해 **누락=disabled** 가 사고가 아니라 의도가 되게 한다.
- **Layer 2 (build, static)** — ArchUnit. (a) application layer 가 optional adapter 패키지를 import 하지 못하게 격리, (b) optional adapter 패키지의 모든 `@Bean``@ConditionalOnProperty` 로 gating 됐는지 검사. 정적 검사는 "후보 클래스가 annotation 을 가짐" 까지만 보장한다.
- **Layer 3 (runtime, fail-fast)** — disabled 일 때 port 를 만족시키는 sentinel(`DisabledMessagePublisher` 등)을 등록해, 우회 호출이 들어오면 `AdapterDisabledException` 으로 즉시 throw(silent no-op/timeout 대기 금지).
## Q2. 각 계층의 한계는?
- Layer 1 의 "bean count = 0 검증" 은 Spring 공식 검증 패턴이 아니라 프로젝트 자체 선택(통합 테스트로 assert).
- Layer 2 는 runtime config 평가를 못 하므로 "실제 active 여부" 는 보장 못 함 → Layer 3 로 위임.
- Layer 1 이 정상 경로에선 bean 자체를 안 만들어 호출 불가이므로, Layer 3 는 "Layer 1·2 를 우회한 호출의 최후 방어선" 일 뿐 정상 경로 코드가 아니다.
## Q3. 어댑터별 실패를 fail-open 으로 둔 이유와 예외는?
- skeleton 기본은 fail-open: 알림/캐시/메시지는 핵심 use case 의 **부수효과**라 전송/캐시 실패가 HTTP 5xx 로 승격되면 안 됨.
- Kafka: publish 실패 → correlationId 부착 로그 + outbox/retry 위임, core 는 성공.
- Redis: unavailable → cache-miss 로 graceful degrade(절대 INTERNAL 로 뭉개지 않음).
- Slack/Email: 전송 실패 → 관측(metric/log)만, 단 provider body/PII 는 로그에 절대 미등장(로거 시그니처에 payload 인자 자체를 없애 구조적으로 차단).
- 예외: notification 이 use case 의 **primary outcome**(예: 비밀번호 재설정 메일 자체가 목적)이면 도메인 branch 가 동기 + fail-closed 로 호출 — skeleton scope 밖.
## Q4. disabled adapter runtime 호출에 startup 의 `REQUIRED_ADAPTER_DISABLED` 코드를 재사용하지 않은 이유?
- 그 코드는 `feature-migration-startup-contract` 소유 + startup-exit(72) 시맨틱(= disabled required adapter 로 app 이 뜨면 실패). runtime invoke 는 **lifecycle 이 다르다**. 하나의 코드로 startup·runtime 두 의미를 표현하면 운영/런북이 혼동된다 → runtime 전용 `ADAPTER_DISABLED`(INTERNAL/500/retryable=false) 를 본 branch owner 로 신설. retryable=false 인 이유: 재배포 전까지 계속 disabled → 재시도로 안 풀리는 결정적 설정 버그(= `INTERNAL_AUTH_MISCONFIGURATION` 과 동류).
## Q5. 왜 spring-kafka/lettuce 같은 실 SDK 를 안 넣었나?
- skeleton 이 모든 선택형 adapter SDK 를 기본 탑재하면 무거워진다. 대신 `KafkaSender`/`RedisClient`/`SlackClient`/`GoogleEmailClient` 같은 **integration seam(interface)** 만 제공하고, 실제 client 구현 + SDK 의존은 해당 adapter 를 켜는 fork 프로젝트가 추가한다. 템플릿은 "실패/관측 계약 + on/off 메커니즘" 을 소유하고, 운영 연동은 소비자가 채운다.
@@ -1 +0,0 @@
../../vault/40-publish/interviews/post-implementation-knowledge-capture.md
@@ -0,0 +1,102 @@
---
title: interview-prep / post-implementation-knowledge-capture
source_type: interview-prep
status: raw
related_branches: [feature-architecture-enforcement-rules]
related_projects: [ca-skeleton]
tags: [interview-prep, ca-skeleton, workflow, documentation, agent-workflow, llm-wiki]
created: 2026-05-28
status_label: collecting
---
# interview-prep: post-implementation-knowledge-capture
> Layer: `raw/interviews/` — 면접 질문 원본 수집·연구 노트. 다듬어진 답변은 `/interviewize` 후 `wiki/interview/` 에 별도 작성.
## Parent / 부모
- [[raw/branch-notes/feature-architecture-enforcement-rules]] — 작업 종료 조건에 LLM Wiki capture 를 _명시적으로_ 포함시킨 결정 (`결정 사항 2026-05-28: non-trivial 구현 종료 조건에 LLM Wiki capture를 포함한다`).
- [[raw/project-notes/ca-skeleton-operational-contract]] — ca-tmpl skeleton 의 workflow 운영 계약 맥락.
## 질문 / Question
- 질문 원문: 구현이 끝난 뒤 _지식 베이스 기록_ 을 누락하지 않도록 어떤 워크플로우를 설계했나요? 단순 "문서도 작성합니다" 가 아니라 _누락을 막는 메커니즘_ 측면에서.
- 출처: 예상 질문.
- 받은 날짜·맥락: 2026-05-28 ca-tmpl workflow rule 반영 중 도출.
## 질문 의도 추론 / Why this question
- 핵심 평가 대상:
- 작업 산출물을 _코드에만 남기지 않고_ 지식 자산으로 연결하는 _습관_.
- "문서화" 를 _후행 작업_ 이 아니라 _완료 조건의 일부_ 로 옮긴 evidence-based 판단.
- workflow automation 에 대한 감각 (CI 강제 vs documented rule vs agent prompt 의 _trade-off_).
- 자기 한계 인식 — "자동화" 를 과장하지 않는 정직함.
- 함정 / 흔히 빠지는 답변 패턴:
- "문서도 작성합니다" 처럼 _구체적인 trigger, template, link rule_ 없이 말하는 것.
- "CI 로 자동 강제합니다" 같이 _실제로 안 한 자동화_ 를 말하는 것.
- canonical wiki / blog / portfolio 와 raw 캡처를 _혼동_ 하는 것 (raw 가 먼저, canonical 은 명시 요청 시).
- 따라올 만한 후속 질문:
- 어떤 문서를 raw 에 남기고 어떤 문서를 canonical wiki 로 _승급_ 하나요? 승급 기준은 무엇인가요?
- 캡처를 4갈래 (branch / errors / interviews / blog-topics) 로 _분리_ 한 이유는 무엇인가요?
- 자동 강제 장치 (CI / git hook) 없이도 누락을 막을 수 있나요?
- 본 워크플로우가 _실제로_ 누락을 줄였다는 증거는 무엇인가요? 몇 사례에 적용해 봤나요?
## 답변 재료 / Raw answer material
- 사실 1 (근거: `feature-architecture-enforcement-rules.md` §결정 사항 2026-05-28 마지막 항목): ca-tmpl repo 의 _4 위치_ 에 capture rule — `AGENTS.md` (프로젝트 authority), 루트 `CLAUDE.md` (always-loaded 요약), `.agents/plugins/ca-superpowers/rules/llm-wiki-capture.md` (rule 본문), `.claude/skills/ca-superpowers-workflow/SKILL.md` (skill 진입점). 각 위치는 트리거가 다름 (대화 시작, 모듈 작업, 비-자명 구현 종료, skill 호출).
- 사실 2 (근거: `llm-wiki-capture.md` §Required Capture Sequence): 캡처 단위 4갈래 — `raw/branch-notes/<branch>.md` (필수), `raw/errors/` (실 에러 발생 시), `raw/interviews/` (면접 질문 도출 시), `raw/blog-topics/` (블로그 글감 도출 시).
- 사실 3 (근거: `llm-wiki-capture.md` §"canonical 추출 요청이 없는 한"): canonical 문서 (`wiki/blog/`, `wiki/interview/`, `wiki/portfolio/`, `wiki/concepts/`, `wiki/projects/`) 는 _사용자가 명시 요청해야_ 생성. raw 가 먼저, canonical 은 _별도 정제 단계_.
- 사실 4 (근거: `llm-wiki-capture.md` 4번 항목): _양방향 nav_ 강제 — 모든 derived note 는 `## Parent` 에서 branch-note 로 upward link, branch-note 는 `## Cluster` 에서 derived note 로 downward link.
- 사실 5 (근거: `llm-wiki-capture.md` §When No Derived Note Is Needed): 파생 문서가 _없을 때_ 도 cluster section 에 "없음" 또는 "추출할 별도 글감 없음" 명시 — _빈 cluster_ 가 "검토 후 없음" 의 증거.
- 사실 6 (근거: `llm-wiki-capture.md` §Final Response Requirement): 종료 응답에 `Wiki capture` 라인 — 갱신된 노트 / 의도적 미생성 / `BLOCKED` 중 하나를 _가시화_.
- 사실 7 (근거: `feature-application-port-usecase-contract.md` §완료 후 정리 + §Cluster): 본 워크플로우의 _첫 적용 사례_ — branch-note 갱신 + 3 derived notes (error, interview, blog-topic) + 종료 응답의 `Wiki capture` 라인.
- 내가 직접 한 경험:
- 본 결정을 _다른 코드 결정과 대등한 격_ 으로 branch-note 의 §결정 사항 마지막 한 줄로 추가. 대안 (a) 사용자 수동 요청 (누락 위험), (b) Wiki vault 내부 규칙만 (ca-tmpl 작업자 인식 못함), (c) ca-tmpl repo-local rule (채택) 까지 명시.
- workflow 문서 패치 도중 도구 자동 승인 검토가 차단 → 사용자 명시 승인 후 재개 — [[raw/errors/apply-patch-auto-approval-rejected-2026-05-28]]. _차단 사례 자체를 error-note 로 남긴_ 메타 사례.
- `feature-application-port-usecase-contract` branch 에서 _첫 적용_ — Wiki capture 라인에 branch-note 갱신 + error / interview / blog-topic 3 derived note 생성을 보고.
- 트레이드오프:
- **자동 강제 (CI / git hook) vs documented rule + agent prompt**: 전자는 누락 0 보장이지만 _과한 marshalling_ 비용 (모든 작업에 적용되면 작은 변경에도 derived note 강제). 후자는 누락 위험이 있지만 _경량_ 이고 _작업 가까이에_ 트리거를 둠. ca-tmpl 은 후자를 _의도적 선택_.
- **canonical 먼저 vs raw 먼저**: canonical 먼저 가면 _premature publishing_ (불완전한 결정을 wiki 로 굳힘) 위험. raw 먼저 가면 _정제 단계_ 가 추가되지만 정직함이 보장 — ca-tmpl 의 `llm-wiki-capture.md` 5번 항목이 raw-first 명시.
- **4갈래 분리 vs 단일 branch-note 통합**: 4갈래는 _분실 방지__검색 가능성_ 의 이득, 단일은 _작성 비용_ 낮음. ca-tmpl 은 _다음 세션 검색 가능성_ 을 우선해 4갈래 채택.
- 한계 / "이건 안 해봤다":
- CI / git hook 으로 자동 강제하지 _않음_. 현재는 _agent workflow rule_ 수준 (`documented-only` 등급).
- 본 워크플로우의 _장기 효과_ 측정 안 함 — 1 사례 (`feature-application-port-usecase-contract`) 적용 검증만 있음.
- agent runtime 이 본 rule 파일들을 _실제로_ 자동 로드하는지는 _plugin/skill 구현 의존_. ca-tmpl repo 외부 의존성.
## Sources / 근거
- [[raw/branch-notes/feature-architecture-enforcement-rules]] — workflow 반영 결정 + 진행 중 메모.
- [[raw/branch-notes/feature-application-port-usecase-contract]] — 본 워크플로우의 첫 적용 사례 (branch-note 갱신 + 3 derived notes + `Wiki capture` 라인).
- [[raw/errors/apply-patch-auto-approval-rejected-2026-05-28]] — workflow 문서 패치 중 도구 차단 사례.
- repo file: `ca-tmpl/.agents/plugins/ca-superpowers/rules/llm-wiki-capture.md` — Authority + Required Capture Sequence + When No Derived Note Is Needed + Final Response Requirement.
- repo file: `ca-tmpl/AGENTS.md` §LLM Wiki 캡처 워크플로우.
- repo file: `ca-tmpl/CLAUDE.md` §LLM Wiki capture.
## 미해결 / Unknown
- 모르는 것: 문서 규칙 __ 으로 _장기적으로_ agent session 누락이 줄어드는지. 현재 1 사례 검증.
- 모르는 것: 자동 강제 장치 (git hook / CI step) 가 _필요한지_, 아니면 documented rule 로 충분한지.
- 모르는 것: 다른 agent runtime (Claude Code / Codex / Gemini CLI) 이 본 rule 파일을 _자동 로드_ 하는지의 일반화.
- 확인 방법: 이후 2~3개 non-trivial branch 작업 종료 시 derived note 가 _자동으로_ 생성되는지 반복 관찰. 자동 강제 추가 비용 / 효과 PoC.
## 답변 경계 / Answer boundary
- 자신 있게 말할 수 있는 범위:
- ca-tmpl repo 의 4 위치에 capture rule 을 _직접 반영_ 한 범위 (AGENTS / CLAUDE / llm-wiki-capture / skill).
- 첫 적용 사례 (`feature-application-port-usecase-contract`) 의 종료 응답 `Wiki capture` 라인이 실제로 _branch-note 갱신 + 3 derived note 생성_ 을 가시화한 사실.
- 4갈래 raw 구조 (`branch / errors / interviews / blog-topics`) 의 분리 _이유__양방향 nav_ 강제.
- "이 부분은 공식 문서를 다시 보고 답변드리겠습니다" 라고 해야 하는 부분:
- 특정 agent runtime 이 본 rule 파일을 _자동 로드_ 하는지 (plugin/skill 구현 의존).
- CI / git hook 자동 강제의 구체 구현 (안 해봄).
- 다른 팀 / 조직 의 knowledge-capture 표준 (`Engineering blog post → ADR → wiki` 류).
- **절대 과장하지 말 것**:
- "자동으로 캡처된다" 표현 금지 — _현재 `documented-only` 등급_, CI 강제 없음.
- "운영에서 검증됐다" 표현 금지 — 1 사례 적용 검증.
- "어떤 runtime 에서도 동작한다" 같은 일반화 금지 — agent plugin / skill 구현 의존.
## Related / 관련
- 관련 면접 질문 (선행/후속): [[raw/interviews/clean-architecture-boundary-enforcement]] (자매 — 같은 branch 의 다른 결정).
- 영감을 받은 채용공고: (없음).
- 관련 블로그 글감: [[raw/blog-topics/post-implementation-knowledge-capture-workflow-2026-05-28]] (같은 결정의 글감).
- 답변 derive 후 위치: 생성 전. 후보 `wiki/interview/workflow/post-implementation-knowledge-capture.md`.
@@ -1 +0,0 @@
../../vault/40-publish/interviews/sample-domain-contract-fixture-clean-architecture.md
@@ -0,0 +1,58 @@
---
title: interview-prep / sample-domain-contract-fixture-clean-architecture
source_type: interview-prep
status: raw
related_branches: [feature-sample-domain-contract-fixture]
related_projects: [ca-skeleton]
tags: [interview-prep, ca-skeleton, architecture, testing, clean-architecture]
created: 2026-06-10
status_label: collecting
---
# interview-prep: sample-domain-contract-fixture-clean-architecture
## Parent / 부모
- [[raw/branch-notes/feature-sample-domain-contract-fixture]] — sample domain을 production 기능이 아니라 skeleton contract fixture로 유지·검증한 작업에서 나온 질문.
## 질문 / Question
- 질문 원문: Clean Architecture 템플릿에서 샘플 도메인을 제거하지 않고 별도 모듈의 contract fixture로 유지한 이유는 무엇인가요?
- 출처: 예상 질문.
- 받은 날짜·맥락: N/A.
## 질문 의도 추론 / Why this question
- 핵심 평가 대상: 아키텍처 경계 보존, 테스트 fixture 설계, sample 코드와 production 코드의 결합도 분리.
- 함정 / 흔히 빠지는 답변 패턴: "예제가 있으면 편하다" 수준으로 답하고, 실제 계약 검증과 production 비의존성을 설명하지 못하는 것.
- 따라올 만한 후속 질문: sample 모듈이 production runtime에 섞이지 않도록 어떤 guardrail을 두었는가?
## 답변 재료 / Raw answer material
- 사실 1: 본 branch D1은 sample domain fixture가 skeleton 계약 검증 도구로 필요하다고 결정했다. 근거: [[raw/branch-notes/feature-sample-domain-contract-fixture]] §Decision Evidence Map D1.
- 사실 2: 2026-06-10 구현에서 `WorkLogStatus` 상태 머신과 `WorkLogOwner` minimum model을 sample-portfolio에 추가하고, domain/application/persistence/web tests로 검증했다. 근거: [[raw/branch-notes/feature-sample-domain-contract-fixture]] §진행 중 메모.
- 내가 직접 한 경험: delegated 항목(idempotency dedup/storage, sample-off/profile isolation, dual-mode CI)은 제외하고 branch-owned gap만 구현했다. 근거: [[raw/branch-notes/feature-sample-domain-contract-fixture]] §Coverage.
- 트레이드오프: sample을 production module에 섞으면 채택자는 빠르게 볼 수 있지만 경계 오염 위험이 커진다. 별도 `sample-portfolio` 모듈은 boilerplate가 늘지만 production 모듈이 sample에 의존하지 않는 guardrail을 유지한다.
- 한계 / "이건 안 해봤다": sample-off dual-mode CI와 prod profile physical exclusion은 이번 branch에서 구현하지 않았고 [[raw/branch-notes/feature-sample-removal-adoption-contract]] owner로 위임되어 있다.
## Sources / 근거
- [[raw/branch-notes/feature-sample-domain-contract-fixture]] — sample fixture 목적, scenario matrix, 2026-06-10 구현/검증 기록.
- [[raw/errors/gradle-wrapper-lock-read-only-sandbox-2026-06-10]] — 검증 중 발생한 sandbox tooling 이슈.
## 미해결 / Unknown
- 모르는 것 1: sample-off dual-mode CI가 실제 릴리즈 게이트로 언제 통합될지.
- 모르는 것 2: canonical `sample-ticket` 명명 drift를 `/ingest`에서 어떤 방향으로 정리할지.
- 확인 방법: `feature-sample-removal-adoption-contract`와 canonical `wiki/projects/ca-tmpl/sample-fixture-and-adoption` 갱신 상태 확인.
## 답변 경계 / Answer boundary
- 자신 있게 말할 수 있는 범위: ca-tmpl 로컬 코드에서 sample fixture의 상태 머신/owner minimum model과 테스트/아키텍처 검증이 통과했다.
- "이 부분은 공식 문서를 다시 보고 답변드리겠습니다" 라고 해야 하는 부분: sample-off physical packaging exclusion과 CI matrix 구현 상태.
- **절대 과장하지 말 것**: 이번 작업은 locally-verified이며 prod-verified 경험이 아니다.
## Related / 관련
- 관련 블로그 글감: [[raw/blog-topics/sample-domain-contract-fixture-clean-architecture-2026-06-10]]
- 답변 derive 후 위치: 생성 전.
@@ -1 +0,0 @@
../../vault/40-publish/interviews/shared-contract-and-sample-isolation.md
@@ -0,0 +1,98 @@
---
title: interview-prep / shared-contract-and-sample-isolation
source_type: interview-prep
status: raw
related_branches: [feature-skeleton-package-blueprint-contract]
related_projects: [ca-skeleton]
tags: [interview-prep, ca-skeleton, architecture, api-design, clean-architecture, shared-kernel]
created: 2026-05-28
status_label: collecting
---
# interview-prep: shared-contract-and-sample-isolation
> Layer: `raw/interviews/` — 면접 질문 원본 수집·연구 노트. 다듬어진 답변은 `/interviewize` 후 `wiki/interview/` 에 별도 작성.
## Parent / 부모
- [[raw/branch-notes/feature-skeleton-package-blueprint-contract]] — `shared-contract``sample-ticket` 의 책임 경계 결정 (D6, D7).
- [[raw/project-notes/ca-skeleton-operational-contract]] — ca-tmpl skeleton-wide operational contract SSOT.
## 질문 / Question
- 질문 원문: Clean Architecture 템플릿에서 `shared-contract``sample-ticket` 은 각각 어떤 책임을 가지고, 왜 production 도메인과 _물리적으로_ 분리했나요?
- 출처: 예상 질문.
- 받은 날짜·맥락: 아직 실제 면접 질문으로 받은 것은 아님.
## 질문 의도 추론 / Why this question
- 핵심 평가 대상:
- "공통이니까 shared 에 넣는다" 라는 _common module dumping ground_ 의 위험을 인식하는지.
- sample / reference code 가 production dependency 로 _새지 않도록_ 막는 메커니즘 인식.
- skeleton-wide operational contract 의 _범위__구체적으로_ 설명할 수 있는지 (8개 sub-package allowlist).
- "예시를 들어내도 경계가 남는다" 라는 template repository 의 _완성도 기준_ 인식.
- 함정 / 흔히 빠지는 답변 패턴:
- "공통이니까 shared 에 넣는다" — boundary drift 의 시작.
- "sample 은 참고용이라 어디서나 import 해도 된다" — production 역수입 위험.
- `shared` 범위를 _구체적으로_ 설명하지 못하고 "공용 유틸" 처럼 추상적으로 표현.
- 따라올 만한 후속 질문:
- error code 나 response envelope 은 _왜 domain 이 아니라_ shared-contract 인가요?
- business / domain concept 가 shared-contract 에 들어오면 _구체적으로_ 어떤 문제가 생기나요?
- sample-ticket 이 production module 에 import 되는 것을 _어떻게 감지_ 하나요? (Gradle vs ArchUnit)
- sample-ticket 을 _아예 지웠을 때_ production 코드가 그대로 빌드되는지 어떻게 보장하나요?
## 답변 재료 / Raw answer material
- 사실 1 (근거: `feature-skeleton-package-blueprint-contract.md` D6 + §Default Module Blueprint): `shared-contract` 는 8개 sub-package 만 허용 — `response/`, `error/`, `headers/`, `logging/`, `tracing/`, `metrics/`, `registry/`, `annotation/`. 모두 _skeleton-wide operational contract_ (운영 계약).
- 사실 2 (근거: `feature-skeleton-package-blueprint-contract.md` §판정 기준 "Forbidden: business/domain concept가 `shared-contract` 또는 adapter module로 이동"): business / domain concept 는 `shared-contract` 진입 _금지_. 위반 시 ArchUnit `shared_contract_contains_only_operational_contract_packages` rule 실패.
- 사실 3 (근거: `feature-skeleton-package-blueprint-contract.md` D7 + `feature-architecture-enforcement-rules.md` D7): `sample-ticket` 은 fixture / sample consumer. production module 이 import / dependency 선언 시 _Gradle `verifyCleanArchitectureDependencies` 와 ArchUnit `production_code_does_not_depend_on_sample_ticket` 양쪽_ 에서 실패.
- 사실 4 (근거: `feature-skeleton-package-blueprint-contract.md` Closure §`actually-implemented`): production package root 가 `dev.caskeleton` 으로 rename + reference code 가 `sample-ticket/src/main/java/dev/caskeleton/sample/ticket/...` 로 격리 + production module 은 `package-info.java` + skeleton anchor 중심.
- 사실 5 (근거: 파생 에러 [[raw/errors/sample-ticket-oauth2-resource-server-dependency-2026-05-27]]): sample-ticket 은 _독립 컴파일 대상_ — production module 의 external dependency 가 자동 전파되지 않으므로 sample 의 `build.gradle` 에 명시 필요. (`InvalidBearerTokenException` import 누락 → `spring-boot-starter-oauth2-resource-server` 명시 추가.)
- 내가 직접 한 경험:
- 기존 reference code (blog domain — User, Post, Service, Repository, Controller, Mapper) 전체를 `sample-ticket` 아래로 격리.
- production module 의 `*Service`, `*Repository`, `*Controller`_완전히 사라진 상태_ 에서 ArchUnit 의 빈 anchor failure 발생 → `allowEmptyShould(true)` 선별 적용 — [[raw/errors/archunit-empty-should-anchor-2026-05-27]].
- sample-ticket 격리 후 `spring-boot-starter-oauth2-resource-server` 누락 compile failure 해결.
- 트레이드오프:
- **`shared-contract` 범위 좁힘**: 좁히면 _중복 코드_ 가 생길 수 있음 (각 adapter 가 비슷한 utility 를 가짐), 넓히면 _domain concept 가 흘러들_ 위험. ca-tmpl 은 _중복 비용 < boundary drift 비용_ 으로 판단해 좁게.
- **sample 격리 비용**: sample 이 별도 module 이라 _build classpath__dependency_ 가 production 과 분리됨. 사례에서 OAuth2 resource-server starter 명시 누락처럼 _실수가 가능_. 격리 비용을 _감수하는 이유_ 는 production 역수입 방지가 더 큰 위험이라는 판단.
- **canonical extraction 의 trade-off**: `shared-contract` 의 registry (error code / header / metric) 가 _어느 branch 에서_ 어떤 API 로 채워질지는 후속 (`feature-contract-registry-governance` 등) — 본 branch 는 _범위와 forbidden_ 까지만 잡고 _내용 자체_ 는 미정.
- 한계 / "이건 안 해봤다":
- 실제 ticket fixture 시나리오 (CRUD + 인증 + 권한) 가 _완성됐다_ 고 말하지 않음 — reference code 격리와 compile/test 검증까지만.
- `shared-contract` 의 실제 contract API (response envelope shape, error code 표준) 는 _후속 branch_ (`feature-api-contract-baseline`, `feature-contract-registry-governance`) 범위.
- 운영 배포 없음 — `feature-skeleton-package-blueprint-contract.md` Closure §`prod-verified: 없음`.
## Sources / 근거
- [[raw/branch-notes/feature-skeleton-package-blueprint-contract]] — D6, D7 + §Default Module Blueprint + §판정 기준 + Closure.
- [[raw/branch-notes/feature-architecture-enforcement-rules]] — D6 (shared-contract package allowlist) + D7 (sample-ticket production 역수입 금지) — _자동 검증_ 측면.
- [[raw/errors/archunit-empty-should-anchor-2026-05-27]] — 빈 anchor 와 `allowEmptyShould(true)` 선별 적용.
- [[raw/errors/sample-ticket-oauth2-resource-server-dependency-2026-05-27]] — sample 의 _독립 컴파일 대상_ 성격을 보여주는 사례.
- [[wiki/concepts/clean-architecture-package-layout]] — module/package layout 일반 개념 (canonical).
- [[wiki/projects/ca-tmpl/clean-architecture-package-layout]] — ca-tmpl 적용 (canonical, 갱신 필요).
## 미해결 / Unknown
- 모르는 것: `shared-contract` 의 registry (error code / header / metric) 구체 API 는 어느 branch 에서 어떤 형태로 채울지 — `feature-contract-registry-governance`, `feature-api-contract-baseline` 후속.
- 모르는 것: `sample-ticket` 이 실제 ticket fixture 로 _완성_ 될 때 production module 과 어떤 compile / test relationship 을 유지할지 — `feature-sample-domain-contract-fixture` 후속.
- 확인 방법: 후속 branch 결과 + canonical wiki 정제.
## 답변 경계 / Answer boundary
- 자신 있게 말할 수 있는 범위:
- `shared-contract` 의 8 sub-package allowlist 와 forbidden (business/domain concept).
- `sample-ticket` 의 production 역수입 금지를 _Gradle + ArchUnit 양쪽_ 으로 막은 메커니즘.
- reference code 격리 작업 (package rename, dependency 재선언, ArchUnit `allowEmptyShould` 조정) 의 _직접 수행 범위_.
- "이 부분은 공식 문서를 다시 보고 답변드리겠습니다" 라고 해야 하는 부분:
- 특정 회사 / 조직의 shared kernel / common module 표준 정책 (DDD bounded context 와의 관계).
- error code / header / metric 표준화의 _업계 best practice_ (RFC 7807, OpenTelemetry semantic conventions 등) — 본 branch 는 _범위_ 만 잡았고 _내용_ 은 후속 branch.
- **절대 과장하지 말 것**:
- `sample-ticket`_실제 ticket 시나리오 (CRUD + 인증 + 권한)__완성됐다_ 고 표현 금지 — 현재는 reference 격리와 compile/test 검증 범위.
- 운영 배포 검증인 것처럼 말하지 말 것 — `locally-verified` 등급.
- `shared-contract` 범위를 _기억_ 으로 답하지 말고 8 sub-package 를 정확히 (`response/error/headers/logging/tracing/metrics/registry/annotation`).
## Related / 관련
- 관련 면접 질문 (선행/후속): [[raw/interviews/clean-architecture-module-blueprint]] (선행 — module 분리 자체), [[raw/interviews/clean-architecture-boundary-enforcement]] (자매 — 자동 검증 메커니즘).
- 영감을 받은 채용공고: (없음).
- 관련 블로그 글감: [[raw/blog-topics/clean-architecture-module-blueprint-2026-05-28]] (같은 결정의 글감).
- 답변 derive 후 위치: 생성 전. 생성 시 `wiki/interview/architecture/shared-contract-and-sample-isolation.md` 후보.
@@ -1 +0,0 @@
../../vault/40-publish/interviews/single-command-local-bootstrap.md
@@ -0,0 +1,58 @@
---
title: interview-prep / single-command local bootstrap contract
source_type: interview-prep
status: raw
related_branches: [feature-developer-experience-contract]
related_projects: [ca-skeleton]
tags: [interview-prep, ca-skeleton, ci-cd, gradle, docker]
created: 2026-06-24
status_label: collecting
---
# interview-prep: single-command-local-bootstrap
## Parent / 부모
- [[raw/branch-notes/feature-developer-experience-contract]] — 실제 5단계 bootstrap 구현과 실패 격리 경험에서 파생.
## 질문 / Question
- 질문 원문: 로컬 개발환경을 단일 명령으로 재현할 때 어떤 단계를 묶고, 실패 위치와 문서 drift는 어떻게 검증하시겠습니까?
- 출처: 구현 경험에서 도출한 예상 질문.
## 질문 의도 추론 / Why this question
- 핵심 평가 대상: reproducibility, 실패 격리, build lifecycle 설계, 문서와 실행 계약의 정합.
- 함정 / 흔히 빠지는 답변 패턴: `docker compose up`만 제공하고 compile/Flyway/smoke 실패를 한 덩어리로 취급하는 답변.
- 따라올 만한 후속 질문: Docker 미기동, 기존 host port 충돌, CI와 local Testcontainers reuse 차이를 어떻게 다루는가?
## 답변 재료 / Raw answer material
- 사실 1: Gradle `bootstrap`을 compile → dependency → startup Flyway → sample contract → HTTP smoke의 task chain으로 구현했다. 근거: [[raw/branch-notes/feature-developer-experience-contract]] D3, §2026-06-24 구현 결과.
- 사실 2: README bash block의 Gradle task/Compose file/Make target drift를 `verifyReadmeCommands``check`에 연결했다. 근거: 같은 branch D4, §2026-06-24 구현 결과.
- 내가 직접 한 경험: host 5432 충돌과 slim JRE RNG provider 누락을 stage별 failure로 찾고 각각 internal-only DB network와 `java.base` RNG bean으로 해결했다.
- 트레이드오프: Gradle은 Spring/Java repository와 정합하고 task별 exit evidence를 제공하지만, Gradle을 쓰지 않는 polyglot repository라면 Make/task runner가 더 자연스러울 수 있다.
- 한계 / 이건 안 해봤다: macOS Apple Silicon과 Windows WSL2 실기 검증, remote CI link-check 실행은 이번 local evidence에 없다.
## Sources / 근거
- [[raw/branch-notes/feature-developer-experience-contract]] D3, D4, D8, D10.
- [[raw/errors/bootstrap-postgres-port-collision-2026-06-24]].
- [[raw/errors/slim-jre-random-generator-missing-2026-06-24]].
## 미해결 / Unknown
- 모르는 것 1: Apple Silicon에서 최초 image build/Testcontainers 시간이 목표를 만족하는지.
- 모르는 것 2: lychee workflow의 실제 GitHub-hosted runner false-positive 목록.
- 확인 방법: 각 OS clean clone 측정과 link-check workflow dispatch 결과 수집.
## 답변 경계 / Answer boundary
- 자신 있게 말할 수 있는 범위: Linux local에서 `./gradlew bootstrap`, focused/full tests, `check`를 실행해 확인한 범위.
- 공식 문서를 다시 보고 답변해야 하는 부분: Testcontainers reuse의 최신 지원/권고와 `@ServiceConnection` 지원 container 범위.
- 절대 과장하지 말 것: local verification을 CI/prod verification으로 표현하지 않는다.
## Related / 관련
- [[raw/blog-topics/five-stage-local-bootstrap-contract-2026-06-24]].
- derived interview: canonical 정제 전이므로 생성하지 않음.
@@ -1 +0,0 @@
../../vault/40-publish/interviews/spring-jpa-flyway-initialization-lifecycle-circular-dependency.md
@@ -0,0 +1,56 @@
---
title: interview / spring-jpa-flyway-initialization-lifecycle-circular-dependency
source_type: interview-prep
status: raw
branch: feature-build-release-supply-chain-contract
related_projects: [ca-skeleton]
tags: [interview, spring, jpa, flyway, lifecycle, circular-dependency]
created: 2026-06-23
updated: 2026-06-23
---
# Spring Boot에서 Flyway와 JPA(EntityManagerFactory) 초기화 순환 참조 및 해결 전략
## Parent
- Parent branch note: [[raw/branch-notes/feature-build-release-supply-chain-contract]]
## 면접 질문 및 핵심 답변
### Q1. Spring Boot 애플리케이션 기동 시, Flyway 마이그레이션과 JPA(Hibernate) 초기화 중 어느 것이 먼저 실행되어야 하며 그 이유는 무엇입니까?
**답변**:
* **실행 순서**: **Flyway 마이그레이션이 항상 JPA 초기화보다 먼저 실행**되어야 합니다.
* **이유**: JPA의 `EntityManagerFactory`가 초기화되는 과정에서 엔티티 매핑 정보를 바탕으로 데이터베이스 스키마 검증(Hibernate `ddl-auto: validate` 또는 `update`)을 수행하거나, 영속성 컨텍스트를 구성하기 때문입니다. 만약 최신 테이블 정의나 변경 사항(DDL)이 데이터베이스에 먼저 반영되어 있지 않다면, JPA 초기화 단계에서 테이블/컬럼 부재로 인해 `SchemaManagementException` 등의 예외를 던지며 애플리케이션 기동이 실패하게 됩니다.
* **Spring Boot의 처리**: Spring Boot는 이를 보장하기 위해 `flywayInitializer` 빈을 `entityManagerFactory` 빈보다 먼저 생성하도록 자동 구성(`DependsOn`)합니다.
---
### Q2. JPA 설정 클래스(@Configuration) 내에서 일반(인스턴스) @Bean 메서드로 `FlywayConfigurationCustomizer`를 정의했을 때 순환 참조(Circular Dependency) 에러가 발생하는 메커니즘을 설명하고, 이를 해결하기 위한 `static @Bean` 적용 원리를 설명해 주세요.
**답변**:
* **순환 참조 발생 메커니즘**:
1. Spring Boot가 스키마 마이그레이션을 수행하기 위해 `flywayInitializer` 빈 생성을 시작합니다.
2. 이 과정에서 커스텀 Flyway 설정을 반영하고자 컨테이너에 등록된 모든 `FlywayConfigurationCustomizer` 빈들을 찾습니다.
3. 만약 이 Customizer 빈이 설정 클래스(`@Configuration`) 내에 일반 `@Bean` 메서드로 선언되어 있다면, Spring은 이 메서드를 호출하기 위해 먼저 부모 설정 클래스의 인스턴스를 생성해야 합니다.
4. 부모 설정 클래스 인스턴스화 과정에서 내부에 선언된 영속성 필드(`@PersistenceContext EntityManager`)나 JPA 관련 종속성 빈 주입을 시도합니다.
5. 이를 주입하려면 `entityManagerFactory` 빈이 먼저 완성되어 있어야 하므로 JPA 초기화를 트리거합니다.
6. 하지만 `entityManagerFactory`는 스키마 보장을 위해 `flywayInitializer`가 끝날 때까지 대기(DependsOn)하므로, `flywayInitializer` -> `Customizer` -> `Configuration` -> `EntityManagerFactory` -> `flywayInitializer`로 이어지는 데드락성 순환 참조가 발생합니다.
* **`static @Bean`을 통한 해결 원리**:
* Spring 프레임워크는 `@Configuration` 클래스 내에 선언된 **`static @Bean` 메서드**를 로드할 때, 부모 클래스의 인스턴스 생성 없이 **클래스 정의 자체에서 직접 정적 메서드를 호출**하여 빈을 등록합니다.
* 따라서, `FlywayConfigurationCustomizer``static`으로 정의되면 부모 설정 클래스의 인스턴스화 및 그에 딸린 `@PersistenceContext EntityManager` 주입 처리가 뒤로 지연(Defer)됩니다.
* 이 덕분에 Flyway 초기화가 아무런 JPA 간섭 없이 완료되고, 그 이후에 비로소 설정 클래스 인스턴스화 및 `entityManagerFactory` 구성이 순차적으로 완료되면서 순환 참조 고리가 완벽히 해소됩니다.
---
### Q3. PostgreSQL 환경에서 엔티티의 대용량 텍스트 필드를 매핑할 때, `@Lob` 어노테이션을 쓰면 발생하는 문제와 클린 아키텍처 관점에서의 대안은 무엇입니까?
**답변**:
* **문제점**:
* Hibernate는 PostgreSQL 환경에서 `@Lob` 어노테이션이 붙은 `String` 필드를 일반 `text` 컬럼이 아니라 **`oid` (Large Object 식별자인 숫자형)** 타입으로 매핑하려고 시도합니다.
* 이로 인해 Flyway 스크립트에서 선언한 실제 데이터 타입인 `text`와 불일치가 발생합니다.
* 개발 환경에서 `ddl-auto: update`가 켜져 있으면 Hibernate가 기존 `text` 컬럼을 `oid` 타입으로 변환하려고 `alter table alter column ... set data type oid` 쿼리를 던지고, PostgreSQL이 이 묵시적 캐스팅을 거부해 `column cannot be cast automatically to type oid` 에러를 유발하며 실행이 중단됩니다.
* **대안**:
* **`@JdbcTypeCode(SqlTypes.LONGVARCHAR)`** 사용:
* 특정 데이터베이스 벤더에 종속적인 `@Column(columnDefinition = "text")` 기술은 클린 아키텍처의 DB 이식성 원칙(ArchUnit 제약 조건)을 위반합니다.
* 반면 `@JdbcTypeCode(SqlTypes.LONGVARCHAR)`는 JDBC 표준 레벨의 대용량 가변 길이 문자열 힌트를 주어, PostgreSQL 환경에서는 안전하게 `text` 컬럼에 매핑되면서도 다른 DB(Oracle, H2 등)로 전환했을 때 벤더 종속성 없이 포터블한 DDL 매핑을 유지해 줍니다.
@@ -1 +0,0 @@
../../vault/40-publish/interviews/startup-fail-fast-config-validation-2026-06-06.md
@@ -0,0 +1,50 @@
---
title: interview / startup-fail-fast-config-validation-2026-06-06
source_type: interview-prep
status: raw
related_branches: [feature-env-driven-runtime-configuration]
related_projects: [ca-skeleton]
tags: [interview, ca-skeleton, spring-boot, configuration, fail-fast, validation, lifecycle]
created: 2026-06-06
status_label: captured
---
# interview: startup-fail-fast-config-validation-2026-06-06
> Layer: `raw/interviews/` — env-driven runtime configuration 구현에서 정직하게 도출 가능한 면접 질문/답변 원석.
## Parent / 부모
- [[raw/branch-notes/feature-env-driven-runtime-configuration]]
## Q1. env 조합 기반 fail-fast 검증을 Spring 라이프사이클의 어디에 두어야 하나? `EnvironmentPostProcessor` / `SmartInitializingSingleton` / `ApplicationReadyEvent` 비교
- **결론**: bean **presence** 검사가 필요하면 `SmartInitializingSingleton` 이 적정.
- 근거:
- `EnvironmentPostProcessor` — bean 정의 **이전**에 실행. property 값은 보지만 bean 존재 여부는 알 수 없음 → multi-instance 5종 bean presence 검사 불가.
- `SmartInitializingSingleton#afterSingletonsInstantiated` — 모든 non-lazy singleton 초기화 **직후**, context refresh 완료 **전** 1회. bean presence 검사 가능 + 위반 시 `throw` 하면 context 가 기동 거부.
- `ApplicationReadyEvent` — 트래픽 수용 **직전**. 너무 늦음(이미 포트 바인딩/warm-up 비용 지불 후 실패).
- 보강: 동일 계약을 contract test 로 이중화해 CI 회귀 방지.
## Q2. `@ConfigurationProperties` 검증을 "선언적 JSR-303" 과 "compact constructor throw" 로 나누는 기준은?
- **단순 제약**(필수·범위·정규식): `@Validated` + JSR-303(`@NotBlank`/`@PositiveOrZero`/`@Min` …) 선언. startup 시 `BindValidationException` 자동 발생.
- **조건부/교차필드**(JSR-303 로 표현 불가): record compact constructor 에서 검사 후 invalid 면 `throw`(fail-fast). 예) `enabled=true` 일 때만 `origins` 필수.
- **정상 default**(absent → 안전한 기본값, 예 `enabled=false` 시 빈 origins)는 invalid 아님 → default 허용.
- 안티패턴: invalid 값을 `log.warn` + 조용히 기본값으로 대체(lenient). 운영 misconfig 가 숨는다.
## Q3. prod 안전 가드에서 Spring profile 매칭을 case-sensitive 로 할까 case-insensitive 로 할까?
- `Environment#matchesProfiles` / `acceptsProfiles`**case-sensitive**. 따라서 `SPRING_PROFILES_ACTIVE=PROD`(대문자 오타)는 `"prod"` 와 매칭되지 않아 prod 가드를 **우회**할 수 있음.
- prod-unsafe 토글(내부 에러 노출 / body 로깅)을 막는 가드라면, 오타로 가드가 풀리는 것이 더 위험 → **의도적으로 `equalsIgnoreCase` 로 대문자 변형까지 잡는 편이 안전**.
- 트레이드오프: case-insensitive 는 profile expression(`!prod`, `prod | staging`)을 지원하지 않음. 표현식이 필요하면 `matchesProfiles` 를, 단순 단일 profile 안전가드면 case-insensitive 동등 비교를 선택.
## Q4. optional capability bean 을 "이름"으로 presence 검사하는 것의 장단점
- 장점: 해당 capability 의 **구체 타입이 아직 존재하지 않아도**(다른 branch 가 미구현) 계약(bean name)만으로 검사 가능 → skeleton 단계에서 cross-branch 계약을 강제.
- 단점: 이름 오타에 취약, 타입 안전성 없음. → 계약 이름을 `static final` 상수 + 주석(owner branch)으로 고정하고 contract test 가 상수를 직접 참조하게 해 drift 를 줄임.
## 관련 / Related
- [[raw/branch-notes/feature-env-driven-runtime-configuration]]
- [[raw/errors/global-sed-env-rename-pitfalls-2026-06-06]]
@@ -1 +0,0 @@
../../vault/40-publish/interviews/transaction-port-vs-spring-transactional.md
@@ -0,0 +1,109 @@
---
title: interview-prep / transaction-port-vs-spring-transactional
source_type: interview-prep
status: raw
related_branches: [feature-application-port-usecase-contract]
related_projects: [ca-skeleton]
tags: [interview-prep, ca-skeleton, transaction, hexagonal, spring, transactional, transaction-port]
created: 2026-05-28
status_label: collecting
---
# interview-prep: transaction-port-vs-spring-transactional
> Layer: `raw/interviews/` — 면접 질문 원본 수집·연구 노트. 다듬어진 답변은 `/interviewize` 후 `wiki/interview/` 에 별도 작성.
## Parent / 부모
- [[raw/branch-notes/feature-application-port-usecase-contract]] — application 계층이 Spring `@Transactional` 을 직접 import 하지 않도록 `TransactionPort` 를 도입한 실 구현 (D3, Decisions 2026-05-28).
- [[raw/project-notes/ca-skeleton-operational-contract]] — ca-tmpl skeleton-wide operational contract.
## 질문 / Question
- 질문 원문: Spring 프로젝트에서 application 계층이 `@Transactional`_직접 부착하지 않고_ `TransactionPort` 같은 추상화로 감싸는 선택의 trade-off 를 설명해 보세요.
- 출처: 예상 질문 (실 면접 아님).
- 받은 날짜·맥락: 아직 없음. Clean Architecture / Hexagonal 패턴 경험 검증용 질문 후보.
## 질문 의도 추론 / Why this question
- 핵심 평가 대상:
- Clean Architecture / Hexagonal 을 했다는 선언이 _framework leakage_ 차단 수준까지 갔는지 변별.
- `@Transactional`_self-invocation 함정_ 인지.
- 다수파 (`@Transactional` 직접) vs 소수파 (TransactionPort) 의 _둘 다 합리적_ 임을 인식하는지.
- 추상이 _enforce 되는_ 형태 (ArchUnit fitness function) 가 함께 있어야 의미가 있다는 점을 알고 있는지.
- 함정 / 흔히 빠지는 답변 패턴:
- "Clean Architecture 라서 추상화" 만 답하고 _구체 이득_ (testability, self-invocation 회피, Spring 의존 surface 축소) 을 설명 못함.
- "boilerplate 가 늘어서 안 쓰는 게 낫다" 만 답하고 다수파의 _proxy leak / self-invocation_ 위험 인식 못함.
- "TransactionPort 가 더 좋다" 같이 한쪽을 _우월_ 로 표현 — 실제로는 _맥락 의존_ 결정.
- 따라올 만한 후속 질문:
- 다수파 (`@Transactional` 직접) 입장이 __ reasonable 한가요?
- `TransactionPort``REQUIRES_NEW` / `noRollbackFor` / `timeout` 까지 표현 가능해야 한다면 API 가 어떻게 커지나요?
- `TransactionTemplate` 기반 구현과 `@Transactional` AOP proxy 기반 구현 중 어느 쪽이 _self-invocation 함정_ 에서 자유롭나요? 왜요?
- `TransactionPort` 가 없으면 application 단위 테스트에서 transaction 동작을 어떻게 _모킹 / fake_ 합니까?
- `application-core` 의 Gradle 에서 `spring-tx`_제거_ 했는데, 왜요?
## 답변 재료 / Raw answer material
- 사실 1 (근거: `feature-application-port-usecase-contract.md` D3 + [[raw/company-tech-blogs/transaction-port-clean-ddd-spring-medium]] `UNIL-TX-C1` + [[raw/company-tech-blogs/transaction-port-vassilis-soum-github-readme]] `VSOUM-TX-C1`): application 계층은 outbound port (`TransactionPort`) 만 호출하고, Spring `@Transactional` 의 직접 import 는 forbidden. Spring 의존은 infrastructure 어댑터 (`SpringTransactionPort`) 에 격리.
- 사실 2 (근거: [[raw/official-docs/at-transactional-spring-official]] `AT-TX-C5`): Spring `@Transactional` AOP proxy 의 _self-invocation 함정_ — 같은 클래스의 메서드가 `this.otherMethod()` 형태로 호출되면 proxy 를 우회해서 transaction 이 적용되지 않음. `TransactionTemplate` 기반 추상화는 proxy 가 아니므로 영향 없음.
- 사실 3 (근거: [[raw/company-tech-blogs/hexagonal-reflectoring-transactional-placement]] `HEX-REFL-C1`, `HEX-REFL-C5`): Hexagonal 표준 _다수파_`@Transactional` 을 application service 에 직접 부착. 이유는 boilerplate 최소화 + Spring 공식 권고 (`AT-TX-C1`) 정합성. 다만 `HEX-REFL-C5` 는 negative claim — 저자가 자기 결정에 대한 명시적 정당화 없이 단순 채택.
- 사실 4 (근거: [[raw/official-docs/spring-tx-management-reference]] `SPRING-TX-MGR-C6`): `readOnly = true` 는 REQUIRED / REQUIRES_NEW propagation 에 _한정_ 해서 적용. ca-tmpl 의 `TransactionPort.inRead` 도 이 제약을 따라 REQUIRED + readOnly 로 구현.
- 사실 5 (근거: `feature-application-port-usecase-contract.md` Decisions 2026-05-28): `application-core` 의 Gradle 에서 `spring-tx`_제거_ 해서 `@Transactional` 어노테이션이 컴파일 classpath 자체에서 _reach 불가능_. ArchUnit rule 과 _belt+suspenders_.
- 사실 6 (근거: 동일 branch-note Decisions 2026-05-28 + 구현 결과): `SpringTransactionPort` 는 mode 별로 미리 빌드된 `TransactionTemplate` 인스턴스 3개 (`writeTemplate`, `readTemplate`, `requiresNewTemplate`) 를 보관해서 호출 시점의 mutation 없이 _동시성 안전_ 보장.
- 사실 7 (근거: 동일 branch-note Claims to Verify "application package의 ArchUnit rule" 행 → `actually-implemented`): `noClasses().that().resideInAPackage("..application..").should().dependOnClassesThat().haveFullyQualifiedName("org.springframework.transaction.annotation.Transactional")` 로 application 의 `@Transactional` import 자동 차단.
- 내가 직접 한 경험:
- ca-tmpl 의 `application-core``TransactionPort` 인터페이스 + 3 메서드 + `TransactionMode` / `Isolation` enum 정의.
- `adapter-persistence``SpringTransactionPort``TransactionTemplate` 기반으로 구현 + 4 unit test (`SpringTransactionPortTest`) — propagation / isolation / readOnly / rollback-on-exception.
- `sample-ticket` 의 aggregate Service (UserService / PostService) 의 모든 `@Transactional``tx.inRead` / `tx.inWrite` 로 일괄 치환. 동작 동등 + Spring 의존 surface 감소.
- ArchUnit rule 추가 후 `app-bootstrap` test classpath 가 `sample-ticket` 을 못 보아 vacuously 통과한 사례를 `testImplementation project(':sample-ticket')` 로 해결 — [[raw/errors/archunit-test-scope-sample-ticket-inclusion-2026-05-28]].
- 트레이드오프:
- **TransactionPort 추상의 _진짜 이득_** 은 testability 가 아니라 (`@Transactional` 메서드도 `@SpringBootTest` 로 테스트 가능) **Spring 의존을 단일 진입점 (`SpringTransactionPort`) 으로 좁히는 것**. Spring 업그레이드 / multi-tenant / multi-DB 시나리오에서 transaction 정책 변경 진입점이 한 클래스.
- **boilerplate 증가는 사실** — 메서드마다 `tx.inWrite(() -> { ... })` 한 단 추가. 작은 팀 / 단일 DB / 단일 transactionManager 환경에서는 다수파 (`@Transactional` 직접) 가 reasonable.
- **소수파 결정의 정당화** 는 _abstraction 자체의 testability 이득_ 이 아니라 _enforce 되는 추상_ 이 함께 있을 때만 성립. ArchUnit fitness function 없이 `TransactionPort` 만 두면 컨벤션이고, fitness function 이 `@Transactional` import 를 실패시키면 _drift 방지 메커니즘_.
- **`TransactionTemplate` vs `@Transactional` AOP proxy**: 후자는 `AT-TX-C5` 의 self-invocation 함정. 전자는 proxy 가 없어서 self-invocation 영향 없음. ca-tmpl 은 후자의 함정을 피하려고 전자 선택.
- 한계 / "이건 안 해봤다":
- `REQUIRES_NEW` 의 실 outbox / audit row 동작 통합 검증 _미수행_`feature-domain-event-outbox-contract` 로 위임.
- `TransactionPort``noRollbackFor` / `timeout` / `transactionManager` (multi-DB) 표현 _불가능_ — 의도적 한정. 필요 시 API 확장 결정.
- Hibernate session statistics 로 `readOnly` flush-mode 측정 PoC _미수행_ — Testcontainers 환경 후.
- prod 운영 검증 없음 — ca-tmpl 은 template repository.
## Sources / 근거
- [[raw/branch-notes/feature-application-port-usecase-contract]] — 결정 D1~D10 + 2026-05-28 구현 결과 + Claims to Verify status.
- [[raw/official-docs/at-transactional-spring-official]] — Spring `@Transactional` 공식 + self-invocation 함정 (`AT-TX-C1`, `AT-TX-C5`).
- [[raw/official-docs/spring-tx-management-reference]] — Spring transaction abstraction (`SPRING-TX-MGR-C3`, `SPRING-TX-MGR-C6`).
- [[raw/official-docs/transaction-template-spring-official]] — `TransactionTemplate` 공식 (`TX-TMPL-C1` ~ `TX-TMPL-C4`).
- [[raw/company-tech-blogs/transaction-port-clean-ddd-spring-medium]] — UNIL TransactionPort 진화 (`UNIL-TX-C1`, `UNIL-TX-C2`).
- [[raw/company-tech-blogs/transaction-port-vassilis-soum-github-readme]] — Vassilis Soum 참고 구현 (`VSOUM-TX-C1` ~ `VSOUM-TX-C4`).
- [[raw/company-tech-blogs/hexagonal-reflectoring-transactional-placement]] — `@Transactional` 직접 부착 다수파 (`HEX-REFL-C1`, `HEX-REFL-C5`).
- [[wiki/concepts/transaction-boundary-abstraction]] — 정제된 trade-off 개념 (canonical 후보, 아직 미생성).
## 미해결 / Unknown
- 모르는 것: `TransactionPort``noRollbackFor` / `timeout` 까지 표현 _해야_ 하는 시점은 언제인가. 현재는 의도적 한정이지만, 실 사업 도메인 들어오면 필요할 수 있음.
- 모르는 것: `TransactionTemplate` 기반 구현이 _완전히_ self-invocation 함정에서 자유로운지의 PoC (port 메서드가 다른 port 메서드 호출 시).
- 모르는 것: multi-DB / multi-tenant 시 `TransactionPort``transactionManager` 선택을 어떻게 표현할지.
- 확인 방법: `feature-domain-event-outbox-contract` (outbox / REQUIRES_NEW), self-invocation PoC, multi-DB 시나리오 추가 branch.
## 답변 경계 / Answer boundary
- 자신 있게 말할 수 있는 범위:
- ca-tmpl `application-core``TransactionPort` 인터페이스 정의 + `adapter-persistence``SpringTransactionPort` 구현 + 4 unit test + sample-ticket 마이그레이션의 _직접 수행_ 범위.
- 다수파 vs 소수파 trade-off 의 _양쪽 근거_ — 다수파의 boilerplate 이득, 소수파의 Spring 의존 surface 축소.
- `@Transactional` AOP proxy 의 self-invocation 함정 vs `TransactionTemplate` 의 직접 호출 차이.
- ArchUnit fitness function + Gradle `spring-tx` 제거의 belt+suspenders 패턴.
- "이 부분은 공식 문서를 다시 보고 답변드리겠습니다" 라고 해야 하는 부분:
- Spring `TransactionInterceptor` / `TransactionAttributeSource`_내부_ 동작 (안 깊이 봄).
- multi-DB 시 `PlatformTransactionManager` 선택의 _운영 best practice_ (안 해봄).
- Hibernate session 의 `readOnly` flush-mode 변경 _구체 동작_ (`needs-confirmation`, Testcontainers PoC 후).
- **절대 과장하지 말 것**:
- `TransactionPort` 가 다수파보다 _우월_ 하다는 식 금지 — _이 맥락 (template repository, 격리 우선)_ 까지만.
- prod 운영 검증 없음 — `locally-verified` 등급.
- `REQUIRES_NEW`_실 outbox 시나리오에서_ 동작 검증됐다고 표현 금지 — 단위 테스트의 `PROPAGATION_REQUIRES_NEW` 설정만 확인.
## Related / 관련
- 관련 면접 질문 (선행/후속): [[raw/interviews/clean-architecture-boundary-enforcement]] (자매 — ArchUnit 의 자동 검증 측면), [[raw/interviews/clean-architecture-module-blueprint]] (선행 — module 분리 자체).
- 영감을 받은 채용공고: (없음).
- 관련 블로그 글감: [[raw/blog-topics/transaction-port-abstraction-over-spring-transactional-2026-05-28]] (같은 주제의 글감).
- 답변 derive 후 위치: 생성 전. 후보 `wiki/interview/architecture/transaction-port-vs-spring-transactional.md`.
@@ -1 +0,0 @@
../../vault/40-publish/interviews/transactional-outbox-skip-locked-implementation-2026-06-11.md
@@ -0,0 +1,40 @@
---
title: interview / transactional-outbox-skip-locked-implementation-2026-06-11
source_type: interview-prep
status: raw
related_branches: [feature-domain-event-outbox-contract]
related_projects: [ca-skeleton]
tags: [interview, ca-skeleton, outbox, skip-locked, messaging, concurrency, clean-architecture]
created: 2026-06-11
---
# interview: transactional outbox (SKIP LOCKED polling) 구현
> Layer: `raw/interviews/` — feature-domain-event-outbox-contract 실구현(Phase C2)에서 정직하게 나올 수 있는 질문.
## Parent / 부모
- [[raw/branch-notes/feature-domain-event-outbox-contract]]
## 예상 질문 / Q&A
- **Q. 왜 outbox 인가? 그냥 트랜잭션 커밋 후 publish 하면 안 되나?**
A. dual-write 문제. DB 커밋과 broker publish 는 원자적으로 묶을 수 없다(2PC 비현실적). 커밋 후 publish 전에 프로세스가 죽으면 이벤트 유실. outbox 는 상태 변경과 같은 트랜잭션으로 outbox row 를 insert 하고(if-and-only-if commit), 별도 relay 가 폴링해 발행한다. ca-tmpl 에서는 `OutboxAppendPort.append` 를 use case 의 `tx.inWrite` 안에서 호출하고, rollback 시 row 가 없음을 계약 테스트(`OutboxAppendTransactionalContractTest`)로 고정했다.
- **Q. 멀티 인스턴스에서 같은 이벤트를 두 publisher 가 잡지 않는 보장은?**
A. 별도 leader election 인프라(Redis/ZooKeeper) 없이 DB row-level claim: `SELECT ... FOR UPDATE SKIP LOCKED`. 락을 못 잡는 row 는 대기 없이 skip 되므로 두 인스턴스가 서로 다른 row 를 가져간다. 계약 테스트로 2개 Spring context 가 1000 row 를 나눠 발행해 합계 1000·중복 0 을 단언했다. `StartupSafetyValidator` 는 multi-instance 모드에서 `outboxLeaderElection` bean 존재를 기동 시 강제한다.
- **Q. per-aggregate 순서(FIFO)는 어떻게 보장하나? SKIP LOCKED 는 순서를 깨지 않나?**
A. 깬다(공식 문서가 inconsistent view 명시). 그래서 claim query 에 게이트를 넣었다: `NOT EXISTS (같은 aggregate 의 더 이른 occurred_at row 가 PUBLISHED 가 아닌 상태)` — 배치에는 aggregate 당 head 1건만 들어온다. head 가 FAILED/IN_FLIGHT/DEAD 인 동안 후행은 차단(strict FIFO). 트레이드오프: poison event 1건이 그 aggregate 스트림을 멈춤 → DEAD runbook 의 수동 처분(재발행 또는 skip)으로 해제. global ordering 은 보장하지 않는다고 명시.
- **Q. publisher 가 claim 후 죽으면(IN_FLIGHT orphan)?**
A. 별도 컬럼 없이 `next_attempt_at` 을 visibility timeout 으로 재사용: claim 시 `IN_FLIGHT + next_attempt_at = now + PT5M`. 만료된 IN_FLIGHT 는 재claim 가능. publish 직후 상태 갱신 전 crash 면 재발행되므로 at-least-once — consumer 의 idempotencyKey dedupe 가 흡수(계약 테스트: 동일 key 5회 전달 → 1회 처리).
- **Q. 발행 실패 분류는?**
A. 일시 실패 → `OUTBOX_PUBLISH_FAILED`(TRANSIENT_DEPENDENCY) + FAILED + 지수 backoff(30s × 2^(n-1) + full jitter), max attempts 3 소진 → `OUTBOX_DEAD_LETTER`(INTERNAL) + DEAD + runbook. 분류 코드·메트릭(`outbox.publisher.published.total{outcome}`, `outbox.pending.size{status}`, `outbox.publisher.lag`)은 registry 기존 값 재사용.
- **Q. 기존 KafkaMessagePublisher 가 fail-open(실패 삼킴)인데 relay 가 그걸 쓰면?**
A. 못 쓴다 — relay 는 실패를 봐야 FAILED/DEAD 상태머신을 돌린다. 그래서 fail-closed 전용 포트(`OutboxMessagePublishPort`)를 application-core 에 정의하고 adapter-outbound 에서 `KafkaSender` seam 에 직결해 예외를 전파시켰다. fail-open 경로는 "use case 직발행 + outbox 가 durability 담당" 시나리오용이라 공존이 맞다.
- **Q. claim 트랜잭션 isolation 은?**
A. `READ_COMMITTED` 명시 pin (vendor default 금지 — MySQL InnoDB 는 REPEATABLE READ). claim 은 짧고 단일 배치 단위라 SERIALIZABLE 불필요. publish 는 claim 트랜잭션 밖에서 수행.
@@ -1 +0,0 @@
../../vault/40-publish/interviews/trivy-suppression-dual-control-governance-2026-06-20.md
@@ -0,0 +1,62 @@
---
title: interview-prep / trivy-suppression-dual-control-governance
source_type: interview-prep
status: raw
related_branches: [feature-dependency-vulnerability-management-contract]
related_projects: [ca-skeleton]
tags: [interview-prep, ca-skeleton, security, supply-chain, ci]
created: 2026-06-20
status_label: collecting
---
# interview-prep: trivy-suppression-dual-control-governance
> Layer: `raw/interviews/` — 면접 질문 원본 수집·연구 노트.
> `status_label`: `collecting`
## Parent / 부모
- [[raw/branch-notes/feature-dependency-vulnerability-management-contract]] — 이 branch 의 D5(Suppression governance) 를 구현하며 "취약점 suppression 을 어떻게 통제했나" 라는 질문이 자연스럽게 도출됨.
## 질문 / Question
- 질문 원문: "취약점 스캐너의 false-positive 나 accepted-risk 를 suppress 해야 할 때, 그 suppression 이 영구적인 silent bypass 가 되지 않도록 어떻게 통제했나요?"
- 출처: 예상 질문 (branch 작업에서 유추 — 2026-05-25 ca-tmpl audit 가 실제로 발견한 구멍)
- 받은 날짜·맥락: (예상)
## 질문 의도 추론 / Why this question
- 핵심 평가 대상: 운영 trade-off 인식 + 보안 게이트를 *우회 가능하게* 만들지 않는 설계 감각 + "정책과 강제(enforcement)의 분리".
- 함정 / 흔히 빠지는 답변 패턴: "`.trivyignore` 에 추가하면 된다" 로 끝내는 것 — *누가/언제까지/왜* suppress 했는지 통제하지 않으면 그 자체가 백도어가 된다.
- 따라올 만한 후속 질문: "CODEOWNERS 만으로 충분하지 않은 이유는?", "만료일 상한은 왜 90일인가?", "이미 만료된 suppression 은 어떻게 처리되나?"
## 답변 재료 / Raw answer material
- 사실 1 (근거: [[raw/branch-notes/feature-dependency-vulnerability-management-contract]] D5): suppression 은 단일 구조화 파일 `.trivyignore.yaml` 하나로만 허용. 인라인 `# trivy:ignore` 주석·CLI ad-hoc 무시는 금지.
- 사실 2 (근거: 동 branch D5 §3): **이중 통제** — (a) `verifyTrivyignore` Gradle gate 가 각 항목의 `statement`(사유)·`expired_at`(만료일) *필드 존재/유효*를 CI 에서 검증, (b) `.github/CODEOWNERS` + branch protection 이 *파일 변경 자체*에 보안 owner 승인을 merge-time 에 강제. 둘은 대체재가 아니라 보완재 — CODEOWNERS 는 "누가 바꾸나"만, gate 는 "필드가 갖춰졌나"만 잡는다.
- 사실 3 (근거: [[raw/official-docs/trivy-filtering-suppression-policy]] C4): Trivy 는 `expired_at` 이 없으면 **영구 유효**로 취급 → 만료일 누락 자체를 빌드 실패로 막아야 영구 ignore 를 차단할 수 있다.
- 내가 직접 한 경험 (근거: 동 branch §진행 중 메모 2026-06-20): `verifyTrivyignore` 를 line-based parser 로 구현하고 6-케이스(누락/만료/창초과/유효/nested/빈seed)로 pass·fail 을 직접 검증. `./gradlew check` green.
- 트레이드오프: 만료 창 길이 — 짧으면(예: 30일) 재검토 부담↑, 길면(예: 1년) 사실상 영구 ignore. **다수파/표준 없음** → team-policy 90일 default(`UNSUPPORTED_IMPL_DECISION` 로 명시). Trivy 공식 문서는 `expired_at` 필드 *존재*만 보장하고 상한은 권고하지 않는다.
- 한계 / "이건 안 해봤다": 실제 CI 러너에서 `.trivyignore.yaml` 변경 PR 이 CODEOWNERS 승인 없이 merge 차단되는지는 GitHub branch protection 설정에 의존 — `needs-confirmation`(로컬에선 Gradle gate 만 검증).
## Sources / 근거 (답변의 사실 근거)
- [[raw/branch-notes/feature-dependency-vulnerability-management-contract]] — D5, §구현가이드 §3, §진행 중 메모.
- [[raw/official-docs/trivy-filtering-suppression-policy]] — `expired_at`/`statement` 필드 의미(C3·C4·C5).
## 미해결 / Unknown
- 모르는 것 1: CODEOWNERS protected-path 가 force-push/admin override 로 우회되는 경로의 잔여 리스크.
- 모르는 것 2: 만료된 suppression 이 release 직전에 갑자기 빌드를 깨뜨릴 때의 운영 핸드오프(누가 renew 책임).
- 확인 방법: GitHub branch protection 문서 재확인 + 테스트 repo 에서 만료일·사유 없는 row PR 로 CI fail + merge block 실증.
## 답변 경계 / Answer boundary
- 자신 있게 말할 수 있는 범위: Gradle gate 의 필드 검증 로직과 6-케이스 검증 결과(로컬), 이중 통제 설계의 *이유*.
- "공식 문서를 다시 보고 답변드리겠습니다" 라고 해야 하는 부분: GitHub CODEOWNERS+branch-protection 의 정확한 merge 차단 시맨틱, force-push 예외.
- **절대 과장하지 말 것**: 이건 `locally-verified`(Gradle gate)다. CI 러너에서 워크플로/CODEOWNERS 가 실제로 차단하는 것은 아직 실증 안 함(`needs-confirmation`) — "운영에서 막아봤다"고 말하지 말 것.
## Related / 관련
- 관련 블로그 글감: [[raw/blog-topics/trivy-suppression-governance-static-gate-2026-06-20]]
- 답변 derive 후 위치: `[[wiki/interview/...]]` (생성되면)