36 KiB
title, source_type, status, branch, parent_branch, related_projects, governing_docs, tags, created, target_merge, status_label, id, kind, project, work_item, inherits, refines, overrides, depends_on, contract_packet, contract_packet_sha256
| title | source_type | status | branch | parent_branch | related_projects | governing_docs | tags | created | target_merge | status_label | id | kind | project | work_item | inherits | refines | overrides | depends_on | contract_packet | contract_packet_sha256 | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| branch / feature-business-rule-validation-contract | branch-note | raw | feature-business-rule-validation-contract |
|
|
|
2026-05-22 | in-progress | BR-CA-SKELETON-OPERATIONAL-CONTRACT-037 | project-work-item | ca-skeleton-operational-contract | WI-CA-SKELETON-OPERATIONAL-CONTRACT-037 |
|
1 | 9fe64ae8128001379c77396ee11cfe7afe9196c837a5de4b2500c0c443b6213b |
branch: feature-business-rule-validation-contract
Layer:
raw/branch-notes/— syntax validation, use case policy, business invariant, persistence integrity 검증 책임을 분리합니다.
부모 (필수)
- Parent project (canonical SSOT): raw/project-notes/ca-skeleton-operational-contract
ca-skeleton 은 별도 root branch 없이 project-note 가 SSOT 역할. 본 feature branch 는 project-note 의 운영 계약 중 해당 영역 (§<관련 섹션>) 의 결정/근거/금지 사항을 정제한다.
브랜치 계약 패킷
- 생성 시 프로젝트 개정:
1 - 패킷 스키마:
contract_packet: 1 - 완료 조건: validation ownership·mapper failure contract test가 통과한다
상속한 프로젝트 결정
| Decision Ref | Project Summary | Branch Application | Source |
|---|---|---|---|
DEC-CA-SKELETON-OPERATIONAL-CONTRACT-MAPPING-001@1 |
수기 mapper와 record canonical constructor가 default이며 MapStruct는 optional profile이다 | Work Item 완료 조건에 적용 | raw/project-notes/ca-skeleton-operational-contract |
DEC-CA-SKELETON-OPERATIONAL-CONTRACT-ERROR-ENVELOPE-001@1 |
foundation이 envelope schema와 error.category enum의 단일 owner다 | Work Item 완료 조건에 적용 | raw/project-notes/ca-skeleton-operational-contract |
브랜치 지역 결정
기존 branch-local 결정은 아래
## Decision Evidence Map / 결정-근거 매핑의 D-row가 소유하며 이 packet에서 복제하지 않는다.
| Decision ID | Decision | Relation | Supporting Claims | Status |
|---|
선언한 예외
| Override ID | Overrides | Reason | Approval | Status |
|---|
목표
validation이라는 이름으로 모든 규칙이 controller DTO나 DB constraint에 몰리면 도메인 적용 후 유지보수가 무너집니다. 어떤 규칙을 어느 경계에서 검증할지 명확히 분리합니다.
- 이슈:
- PR:
범위
포함 범위
- request syntax/shape validation.
- application policy validation.
- domain invariant validation.
- persistence uniqueness/integrity handling.
- duplicate validation 허용 기준.
- validation error response/log 기준.
제외 범위
- 특정 비즈니스 규칙 설계.
- frontend validation 정책.
- database schema design 전체.
TODO
TODO drained 2026-05-22 — 결정은 아래 "결정 사항" / "Decisionized Work Items" / "판정 기준" 참조. syntax/policy/invariant/persistence/duplicate/validation details 모두 표 row로 반영됨. 잔존 TODO 없음.
진행 중 메모
작업하며 떠오른 메모. 자유 형식.
- 현재 documented-only 단계 — D1~D9 결정·근거 + §구현 가이드 명세 작성 완료, 실제 코드 미착수.
- D5/D6/D7 (envelope shape + code→category 매핑) 은 sibling
feature-boundary-validation-mapping-contract와 결정이 중첩 — 구현 명세는 sibling 소유로 정제(§Audit F1/F3). 본 branch 는 4-layer 책임 view 에 집중. - 2026-06-02 ca-tmpl ground-truth 패스 (실 코드/registry 대조):
- F5 RESOLVED —
PERSISTENCEenum 은 실재하지 않음(Category.java10-enum). 실제 매핑DB_UNIQUE_VIOLATION→CONFLICT /DB_NULL·FK·CHECK→DATA_INTEGRITY 로 전 표 정합. - persistence integrity 핸들러 미구현 확인 —
GlobalExceptionHandler에DataIntegrityViolationException핸들러 없음. ownerfeature-persistence-failure-baseline(documented-only). §2 에planned명시. - F2 보강 — policy → AUTHZ 실재 코드(
AUTHZ_INSUFFICIENT_PERMISSION/AUTHZ_TENANT_MISMATCH) 확인, 단 owner 는 security/tenant branch → consume. D-ID gap 은 여전히 open.
- F5 RESOLVED —
- open gap (잔존): use case policy layer D-ID 미부여(§Audit F2). D1/D2 외부 근거 보강 deferred(§Audit F4).
error-codes.yaml:580주석의 stalePERSISTENCE는 ca-tmpl 레포 측 정리 대상.
Work Item Contract
각 TODO는 아래 판정 단위로 재작성되어야 canonical 승급 가능합니다. TODO가 단순히 기준 작성으로 남아 있으면 이 branch는 완료로 보지 않습니다.
| field | required | rule |
|---|---|---|
| Decision | yes | 구현자가 선택해야 하는 기본값 |
| Allowed | yes | 허용되는 예외와 조건 |
| Forbidden | yes | 절대 금지되는 구현/문서 상태 |
| Required registry update | conditional | error/env/header/log/metric/capability 변경 시 필수 |
| Required contract test | yes | 계약 위반 시 실패해야 하는 테스트 |
| Failure condition | yes | review/build에서 실패로 판정할 상태 |
| Canonical extraction target | yes | wiki/projects 승급 위치 |
결정 사항
- 2026-05-22: request DTO validation은 입력 모양 검증만 담당.
- 2026-05-22: business invariant는 domain에서 검증.
- 2026-05-22: persistence integrity error는 operational error로 변환하되 client-safe message만 응답.
- 2026-05-22: 이 branch의 TODO도 Work Item Contract를 따라야 하며 아래 Decisionized Work Items가 canonical 승급 기준이다.
근거 (필수, 최소 1개+)
본 branch의 결정 근거. 상세 비교는 §외부 근거 / 대안 조사 (있다면) 참조.
| Source | 정당화하는 결정 |
|---|---|
| raw/company-tech-blogs/stripe-error-format | endpoint dimension 명시 사례 |
| raw/company-tech-blogs/toss-payments-error-format | 한국 컨벤션 reference |
| raw/official-docs/problem-detail-rfc-7807 | IETF 표준이지만 실패 전용, success/error 비대칭 |
| raw/official-docs/spring-problem-detail | Spring 6 기본 지원이지만 ca-tmpl envelope과 충돌 |
| raw/official-docs/google-api-error-format | 가장 표현력 풍부, retryable detail 1급 |
| raw/official-docs/json-api-errors-spec | — |
| raw/official-docs/graphql-errors-spec | partial success 1급 |
| raw/company-tech-blogs/github-api-error-format | — |
외부 근거 / 대안 조사 (2026-05-22 — Topic 4)
본 branch의 business invariant violation → CONFLICT/VALIDATION mapping 결정에 대한 외부 source 조사. error.category enum과 1:1.
- 채택 결정 (custom envelope, success/error 대칭, retryable 1급):
- (어떤 표준도 1:1 매칭 없음 — Stripe/Toss와 가장 유사하나 success flag는 ca-tmpl 고유)
- raw/company-tech-blogs/stripe-error-format — endpoint dimension 명시 사례
- raw/company-tech-blogs/toss-payments-error-format — 한국 컨벤션 reference
- 명시적으로 거부한 표준:
- raw/official-docs/problem-detail-rfc-7807 — IETF 표준이지만 실패 전용, success/error 비대칭
- raw/official-docs/spring-problem-detail — Spring 6 기본 지원이지만 ca-tmpl envelope과 충돌
- 검토한 대안:
- 대안 1: RFC 7807 ProblemDetail — 위 2개
- 대안 2: Google rpc.Status (gRPC) — raw/official-docs/google-api-error-format (가장 표현력 풍부, retryable detail 1급)
- 대안 3: JSON:API errors — raw/official-docs/json-api-errors-spec
- 대안 4: GraphQL errors — raw/official-docs/graphql-errors-spec (partial success 1급)
- 대안 5: GitHub custom envelope — raw/company-tech-blogs/github-api-error-format
- 비교 핵심: ca-tmpl의
successflag +retryable1급은 어떤 표준에도 없음. Google rpc.Status만 retryable을 detail로 가짐. ProblemDetail은 실패 전용 평면이라 ca-tmpl의 운영 요구와 구조적 충돌. → ca-tmpl이 ProblemDetail을 거부한 trade-off: 표준 lock-in 회피 + success/error 대칭 + 운영 메타 1급화.
Decisionized Work Items
| field | Decision | Allowed | Forbidden | Required registry update | Required contract test | Failure condition |
|---|---|---|---|---|---|---|
| syntax/shape | request DTO validation | frontend duplicate validation | domain-only syntax validation | error-registry row 변경 시 VALIDATION 코드 추가 | malformed request test | malformed request가 VALIDATION envelope로 매핑되지 않으면 실패 |
| use case policy | application policy validation | domain service if pure domain rule | controller-only authorization policy | error-registry row 변경 시 AUTHZ/CONFLICT 코드 추가 | policy conflict test | policy violation이 AUTHZ/CONFLICT envelope로 매핑되지 않으면 실패 |
| domain invariant | domain model/value object | pre-check for UX/perf | DB constraint as only invariant | error-registry row 변경 시 CONFLICT/VALIDATION 코드 추가 | invariant test | invariant violation이 infrastructure exception으로 표현되면 실패 |
| persistence integrity | infrastructure maps to operational error | application pre-check | raw SQL/constraint in response | error-registry row 변경 시 DATA_INTEGRITY(null/FK/check) / CONFLICT(unique) 코드 추가 | integrity mapping | unique/integrity failure가 raw SQL/constraint name을 client에 노출하면 실패 |
| duplicate validation | allowed with canonical owner | documented redundancy | contradictory duplicate rules | 없음 (boundary 책임만) | boundary test | canonical owner 없는 duplicate rule이 추가되면 실패 |
| validation details | safe field errors only | no details for security | raw object/body/SQL detail | 없음 (error-registry envelope shape에 종속) | leakage test | raw object/body/SQL detail이 response에 노출되면 실패 |
판정 기준
| 구분 | 기준 |
|---|---|
| Decision | validation 책임을 boundary별로 분리 |
| Allowed | 같은 규칙을 UX/성능 목적으로 사전 검증하되 canonical owner를 명시 |
| Forbidden | DB constraint만으로 business invariant를 대체 |
| Required mapping | syntax -> VALIDATION, policy -> AUTHZ/CONFLICT, invariant -> CONFLICT/VALIDATION, persistence -> DATA_INTEGRITY(null/FK/check)/CONFLICT(unique) |
| Failure condition | raw persistence exception이나 domain exception이 presentation까지 새면 실패 |
테스트 계약
- malformed request는 structured validation error로 변환되어야 함.
- business invariant violation이 infrastructure exception으로 표현되면 실패.
- unique constraint failure가 SQL/constraint raw name을 클라이언트에 노출하면 실패.
- 이 branch의 TODO가 Decision/Allowed/Forbidden/Test 없이 남으면 canonical 승급 실패.
결정-근거 매핑
각 결정이 어떤 raw source claim 으로 뒷받침되는지 명시. company-tech-blog 출처는
company-case-study로 라벨링하며 공식 best practice 로 격상하지 않는다.
| Decision ID | Decision | Supporting Claims | Evidence Strength | Open Risk |
|---|---|---|---|---|
| D1 | request DTO validation 은 입력 모양 (syntax/shape) 검증만 담당 (2026-05-22) | UNSUPPORTED_DECISION (4-layer validation 분리는 project-internal architectural decision; 외부 표준이 boundary 별 책임 분할을 normative 로 강제하지 않음) | N/A | layer 책임의 정합성은 sibling branch (feature-boundary-validation-mapping-contract) 와 cross-review 필수 — 동일 4-layer 결정이 양쪽에 분산됨 |
| D2 | business invariant 는 domain 에서 검증 | UNSUPPORTED_DECISION (DDD aggregate invariant 책임 패턴은 일반 design wisdom 이지만 본 branch 가 cite 한 sources — Stripe/Toss/RFC 7807/Spring/Google/JSON:API/GraphQL/GitHub — 중 normative 진술 없음) | N/A | DDD aggregate / value object 책임 패턴의 raw 인용 (예: Vaughn Vernon, Fowler anemic vs rich) 별도 보강 필요 |
| D3 | persistence integrity error 는 operational error 로 변환하되 client-safe message 만 응답 | raw/official-docs/problem-detail-rfc-7807.md#RFC7807-C5 ("detail ... ought to focus on helping the client correct the problem, rather than giving debugging information"), raw/company-tech-blogs/github-api-error-format.md#GH-ERR-C4 (validation error code 어휘 — custom 은 message-driven 의 escape hatch) |
official-standard + official-vendor-doc |
RFC7807-C5 는 "ought to" 약한 어조; SQL constraint name 차단은 raw 인용보다 보안 일반 원칙 — 별도 raw (예: OWASP error handling) 보강 권장 |
| D4 | 이 branch 의 TODO 도 Work Item Contract 준수; Decisionized Work Items 표가 canonical 승급 기준 | UNSUPPORTED_DECISION (project-internal process gate; 외부 표준 근거 없음) | N/A | process gate 가 문서에만 있으면 silent skip — /lint 또는 PR template 으로 enforce 필요 |
| D5 | error envelope shape — custom {success, data, error.{code,category,message,retryable,details}, meta} 채택, RFC 7807 ProblemDetail 명시적 거부 |
raw/official-docs/problem-detail-rfc-7807.md#RFC7807-C1 (canonical model 은 JSON application/problem+json), #RFC7807-C2 (type URI 가 primary identifier — custom code 와 충돌), #RFC7807-C3 (extension 가능하나 unknown 은 ignore), raw/official-docs/spring-problem-detail.md#SPRING-PD-C1 (Spring ProblemDetail 은 RFC 9457 representation), #SPRING-PD-C2 (모든 Spring MVC 예외가 ErrorResponse 구현 — ca-tmpl envelope 와 직접 충돌), raw/company-tech-blogs/stripe-error-format.md#STRIPE-ERR-C5 (Stripe 4-종 type enum 사례), raw/company-tech-blogs/toss-payments-error-format.md#TOSS-ERR-C1 (Toss {code, message} 평면 shape) |
official-standard + official-vendor-doc + company-case-study |
RFC 7807 미채택의 trade-off (표준 lock-in 회피 vs client 라이브러리 호환성) 는 인용된 source 들이 직접 권고하지 않음 — ca-tmpl 의 운영 해석. Stripe / Toss 는 company-case-study (best practice 격상 금지) |
| D6 | retryable 1급 필드 + success flag — 어떤 표준에도 1:1 매칭 없음 | raw/official-docs/google-api-error-format.md#GOOG-ERR-C3 (details 에 typed payload — RetryInfo 등 포함 가능), #GOOG-ERR-C5 (표준 detail payloads — BadRequest, ErrorInfo, LocalizedMessage 등), raw/official-docs/graphql-errors-spec.md#GQL-ERR-C3 (partial response — data + errors 공존), #GQL-ERR-C4 (extensions free-form map) |
official-vendor-doc + official-standard |
Google rpc.Status 만 retryable 을 detail 로 가짐 — top-level 1급 필드는 어떤 표준에도 없음 (ca-tmpl 고유 결정). GraphQL partial success 도 envelope success flag 와 다른 모델 |
| D7 | validation error mapping — syntax → VALIDATION, policy → AUTHZ/CONFLICT, invariant → CONFLICT/VALIDATION, persistence → DATA_INTEGRITY(null/FK/check)/CONFLICT(unique) | raw/official-docs/json-api-errors-spec.md#JSONAPI-ERR-C3 (source.pointer JSON Pointer 로 field-level 오류 위치), #JSONAPI-ERR-C5 (title 은 호출별 불변), raw/company-tech-blogs/github-api-error-format.md#GH-ERR-C3 (validation 실패 = 422), #GH-ERR-C4 (validation code 어휘 6개); category 명칭은 ca-tmpl/docs/registries/error-codes.yaml + shared/error/Category.java SSOT 확인 (2026-06-02) |
official-standard + official-vendor-doc + code-verified(category) |
category 분류 체계 자체의 외부 표준은 없음 — ca-tmpl 운영 결정. 실제 enum 은 10종 (VALIDATION/AUTH/AUTHZ/NOT_FOUND/CONFLICT/RATE_LIMIT/TRANSIENT_DEPENDENCY/PERMANENT_DEPENDENCY/DATA_INTEGRITY/INTERNAL); PERSISTENCE 는 없음 |
| D8 | duplicate validation 허용 — canonical owner 명시 시 UX/perf 사전 검증 가능 | UNSUPPORTED_DECISION (project-internal architectural decision; 외부 표준 근거 없음) | N/A | canonical owner 정합성은 PR 단위에서 review — silent duplication 위험 |
| D9 | validation details — safe field errors only; raw object/body/SQL detail 금지 | raw/official-docs/problem-detail-rfc-7807.md#RFC7807-C5 ("detail ought to focus on helping the client correct the problem"), raw/company-tech-blogs/github-api-error-format.md#GH-ERR-C5 (custom code 의 message-driven escape hatch — i18n 위험 명시) |
official-standard + official-vendor-doc |
"safe field error" 정의는 ca-tmpl 운영 해석; SQL/constraint name leak 차단은 일반 보안 원칙 — 별도 raw (OWASP) 보강 권장 |
구현 가이드
결정 (D1~D9) 이 "무엇 을 검증할 것인가" 라면, 본 §는 "어느 layer 에서 어떤 메커니즘으로" 검증·변환·차단되는가의 사전 명세. 본 branch 의 핵심은 검증 책임의 layer 배치 다.
error envelope 의 shape (D5/D6) 과 error code → HTTP → category 매핑 구현 (D7) 은 본 §에서 재명세하지 않는다 — sibling raw/branch-notes/feature-boundary-validation-mapping-contract §구현 가이드 + canonical raw/project-notes/ca-skeleton-operational-contract §6 이 소유 (R3 정제, §Audit & Findings 참조).
1. 4-layer validation 책임 배치 + 정적 강제
Trace:
- syntax/shape = controller boundary 전용 → D1 (UNSUPPORTED_DECISION — 외부 표준이 boundary 별 책임 분할을 normative 강제하지 않음; sibling
feature-boundary-validation-mapping-contract와 동일 4-layer 결정 분산이므로 cross-review 필수). 검증은 §Claims To Verify row 1.- business invariant = domain model/value object 전용 → D2 (UNSUPPORTED_DECISION — DDD aggregate wisdom, 인용 source 8개 중 normative 진술 없음). 검증은 §Claims To Verify row 2.
- use case policy layer 는 Decision Evidence Map 에 대응 D-ID 가 없음 (gap — §Audit & Findings F2). 아래 표 row 는 Decisionized Work Items 의 "use case policy" row 에서만 도출되며 외부 근거 미연결.
- UNSUPPORTED_IMPL_DECISION: ①ArchUnit rule 이름 (
valid_only_in_controller,domain_invariant_on_all_mutations등) 임의 명명. ②"모든 mutation 경로" 의 조작적 정의 (생성자 / setter / 도메인 메서드 중 어디까지를 mutation 으로 보는지) — raw 권고 없음, 사용자 임의. ③layer 별 package glob (..adapter.web../..application../..domain..) — canonical raw/project-notes/ca-skeleton-operational-contract §20 Skeleton Blueprint package convention 에서 도출(SUPPORTED via canonical SSOT), glob 변환만 임의.
| layer | 검증 책임 | 배치 위치 | 정적 강제 (계획) | Trace |
|---|---|---|---|---|
| syntax / shape | 입력 모양 (required / type / format / size) | @Valid + Bean Validation @ controller DTO (..adapter.web..dto..) |
@Valid 가 controller package 밖에 등장하면 build 실패 (ArchUnit) |
D1 |
| use case policy | application 권한·상태전이 정책 | application service (..application..) |
정적 강제 없음 — review-only (근거 없음, 아래 trade-off) | Decisionized WI "use case policy" (no D-ID, F2) |
| domain invariant | 비즈니스 불변식 | domain model / value object (..domain..) |
invariant method 가 모든 mutation 경로에서 호출되는지 ArchUnit + bypass test | D2 |
| persistence integrity | unique / FK / 무결성 | infrastructure adapter → operational error 변환 (§2) | §2 참조 | D3 |
- UNSUPPORTED_IMPL_DECISION (policy layer 정적 강제 부재): use case policy 를 ArchUnit 으로 강제하지 않고 review-only 로 두는 것은 사용자 trade-off — application 정책은 도메인/요청 문맥 의존이 커서 정적 규칙의 false positive 가 많다는 판단. 근거 raw 없음.
2. Persistence integrity → operational error 변환 지점
Trace: persistence integrity error 는 operational error 로 변환하되 client-safe message 만 응답 → D3 +
raw/official-docs/problem-detail-rfc-7807.md#RFC7807-C5("detailought to focus on helping the client correct the problem, rather than giving debugging information"),raw/company-tech-blogs/github-api-error-format.md#GH-ERR-C4. 검증은 §Claims To Verify row 3.
- 메커니즘 (ground truth 2026-06-02, ca-tmpl 코드 확인):
src/adapter-web/.../error/GlobalExceptionHandler.java(@RestControllerAdvice) +ErrorResponseFactory는actually-implemented지만, 현재@ExceptionHandler목록(MappingException / IllegalArgumentException / ConstraintViolation / MethodArgumentTypeMismatch / InvalidBearerToken / Authentication / AccessDenied / PreconditionFailed / PageValidation / Cursor / Exception)에DataIntegrityViolationException핸들러가 없음 — persistence integrity 변환은planned. owner 는 raw/branch-notes/feature-persistence-failure-baseline (documented-only). 본 branch 는 그 핸들러를 consume 하며, integrity handler 추가는 owner branch 책임.- 카테고리 매핑 (registry SSOT,
ca-tmpl/docs/registries/error-codes.yaml): unique 위반 →CONFLICT(DB_UNIQUE_VIOLATION, 409); null/FK/check 위반 →DATA_INTEGRITY(DB_NULL_VIOLATION/DB_FK_VIOLATION/DB_CHECK_VIOLATION).PERSISTENCEenum 은 존재하지 않음 (src/shared-contract/.../error/Category.java10-enum 확인).- UNSUPPORTED_IMPL_DECISION: SQL/constraint name 차단은 RFC7807-C5("ought to" 약한 어조)보다 일반 보안 원칙 — 별도 raw (OWASP error handling) 보강 권장(D3 Open Risk).
DataIntegrityViolationException/OptimisticLockingFailureException등 persistence 예외는 infrastructure→presentation 으로 raw 전파 금지. exception handler 가DATA_INTEGRITY(null/FK/check) 또는CONFLICT(unique) category 의 operational error envelope 로 변환. 현재 미구현 — owner:feature-persistence-failure-baseline.- 응답
error.message는 client-safe 고정 문구만 (registryclient_safe_message, 예:DB_UNIQUE_VIOLATION= "Resource already exists"). SQL 문장·constraint 이름·table/column 명을message/details어디에도 노출 금지. - 구체적 envelope shape 은 본 branch 범위 밖 → canonical §6 (OUT_OF_BRANCH_SCOPE, §Audit F1).
3. Validation detail leakage 차단
Trace: validation details — safe field errors only, raw object/body/SQL detail 금지 → D9 +
#RFC7807-C5,raw/company-tech-blogs/github-api-error-format.md#GH-ERR-C5(customcode 의 message-driven escape hatch — i18n/leak 위험). 검증은 §Claims To Verify row 7.
- UNSUPPORTED_IMPL_DECISION: "safe field error" 의 정의 (어떤 필드 메타까지 허용 — field 경로? rejected value 포함? message?) 는 ca-tmpl 운영 해석, raw 가 권고하지 않음.
details에 허용: field 경로 + validation message (i18n key). 금지: 직렬화된 raw request object/body, SQLException message, stacktrace, constraint name.- 의도적
SQLException발생 → response body grep 으로 leak 회귀를 contract test 로 pin (§Claims row 7).
4. Duplicate validation canonical owner 표기
Trace: duplicate validation 허용 — canonical owner 명시 시 UX/perf 사전 검증 가능 → D8 (UNSUPPORTED_DECISION — project-internal architectural decision, 외부 근거 없음). 검증은 §Claims To Verify row 8.
- UNSUPPORTED_IMPL_DECISION: owner 표기 메커니즘 (코드 주석 vs annotation vs 문서 표) 전부 사용자 임의 — D8 자체가 무근거이므로 detail 도 무근거.
- 같은 규칙을 두 layer 에서 검증하는 것은 허용하되, canonical owner 를 명시. owner 없는 duplicate rule 추가 시 silent contradiction → 금지.
- 기본값 (착수 가능 수준): 코드 주석
// canonical-owner: <layer>(예:// canonical-owner: domain-invariant) — 단순, 도구 불필요. duplicate 검증 지점마다 owner layer 한 줄 명시. - (대안) annotation 강제(ArchUnit): raw/branch-notes/feature-boundary-validation-mapping-contract §Mapper Tool Contract 의 annotation 패턴 참조 후 별도 결정 — D8 무근거이므로 도입 여부는 review 판단.
엣지·실패·의존
R4(깊이 게이트) 캡처용. 정상 경로 외 구현 중 부딪힐 실패/엣지/다른 계약 의존.
- 실패·엣지 경로:
- malformed JSON (
HttpMessageNotReadableException) vs Bean Validation 실패 (MethodArgumentNotValidException) — 둘 다 syntax layer 지만 다른 exception. 둘 다VALIDATIONcategory 로 수렴해야 함(sibling D10 과 정합). - 동시성 하 unique constraint race: application 사전 check(D8 duplicate)가 통과해도 DB 레벨에서 integrity violation 발생 가능 → persistence layer(D3)가 최종 방어선. 사전 check 는 UX 목적일 뿐 invariant 보장 아님.
- nested DTO
@Validcascade 깊이 — sibling 의 cascade depth ≤ 3 정적 강제(B4)에 의존.
- malformed JSON (
- 다른 계약 의존:
- raw/branch-notes/feature-boundary-validation-mapping-contract 의
D10(exception → error code → category 매핑) 을 consume — 본 branch 의 4-layer 가 어느 category 로 떨어지는지는 sibling 이 결정. sibling 매핑이 바뀌면 본 branch 의 §판정 기준 Required mapping 표가 영향. - raw/project-notes/ca-skeleton-operational-contract §6 (Operational Error Category 통합 정의) + §20 (package convention) 을 consume — envelope shape·package glob 의 SSOT.
- 구현 순서 의존 (2026-06-02 ground truth): 본 branch 의 Claims row 3(persistence integrity 매핑)은 raw/branch-notes/feature-persistence-failure-baseline 가
GlobalExceptionHandler에DataIntegrityViolationException핸들러를 구현한 후에야planned→verified전환 가능. 현재 그 핸들러는 부재(코드 확인). policy AUTHZ 코드는 raw/branch-notes/feature-security-operational-baseline 소유.
- raw/branch-notes/feature-boundary-validation-mapping-contract 의
Audit & Findings
R3 정제 history + 발견된 gap 보존 (§구현 가이드 본문에서 제외한 항목의 이관 근거).
| ID | 유형 | 내용 | 조치 |
|---|---|---|---|
| F1 | OUT_OF_BRANCH_SCOPE | D5 (envelope custom shape), D6 (retryable/success flag) 의 구현 명세 — EnvelopeBodyAdvice/Envelope/BulkEnvelope 클래스·factory API — 는 sibling raw/branch-notes/feature-boundary-validation-mapping-contract §구현 가이드 §4 + canonical §6 이 소유. 본 §구현 가이드에서 재명세 제외. D5/D6 결정 기록 은 Decision Evidence Map 에 유지(error-format Topic 4 공유 조사 산물). |
sibling/canonical 참조로 대체 |
| F2 | DECISION_GAP | Decisionized Work Items 의 "use case policy" row + §1 표의 policy layer 가 Decision Evidence Map 에 대응 D-ID 가 없음. syntax(D1)/invariant(D2)/persistence(D3)/duplicate(D8)/details(D9)는 D-ID 보유하나 policy 만 누락. | 착수 기본값 (registry owner_branch 확인 2026-06-02): 인가 정책 violation → AUTHZ (실재 코드 AUTHZ_INSUFFICIENT_PERMISSION + AUTHZ_TENANT_MISMATCH, 403, 둘 다 owner feature-security-operational-baseline); 상태 전이 충돌 → CONFLICT. (주의: feature-tenant-context-policy 는 AUTHZ 코드 소유자 아님 — TENANT_NOT_SUPPORTED(VALIDATION/400) 별도 소유.) 본 branch 는 이 코드들을 consume. 매핑 자체는 여전히 UNSUPPORTED (본 노트 D-ID 없음) — 코드 착수 후 policy layer 책임을 D10(본 노트)로 승격하거나 owner branch 와 cross-link 하여 확정 필요. 추측을 FACT 로 기재 금지. |
| F3 | OUT_OF_BRANCH_SCOPE | D7 의 코드→category 매핑 구현 은 sibling D10 영역. 본 branch 는 어느 layer 가 어느 category 후보인지 의 책임 view 만 제공. persistence 코드(DB_UNIQUE_VIOLATION→CONFLICT, DB_NULL/FK/CHECK_VIOLATION→DATA_INTEGRITY)는 owner raw/branch-notes/feature-persistence-failure-baseline 소유. |
sibling/owner 참조 |
| F4 | DEFERRED_RESEARCH | D1 (4-layer 분리), D2 (DDD invariant 책임) 의 외부 근거 보강 — D2 Open Risk 가 Vernon/Fowler(anemic vs rich domain) raw 인용을 명시. wiki-decision-researcher 자동조사 후보지만 web-fetch(outward) 라 사용자 opt-in 대기. |
/branch-spec ... --research D1,D2 또는 수동 |
| F5 | CATEGORY_DRIFT → RESOLVED 2026-06-02 | 본 노트가 쓰던 PERSISTENCE category 는 실재하지 않음 — src/shared-contract/.../error/Category.java 의 10-enum(VALIDATION/AUTH/AUTHZ/NOT_FOUND/CONFLICT/RATE_LIMIT/TRANSIENT_DEPENDENCY/PERMANENT_DEPENDENCY/DATA_INTEGRITY/INTERNAL)에 없음. registry error-codes.yaml 의 실제 매핑: DB_UNIQUE_VIOLATION→CONFLICT(409), DB_NULL/FK/CHECK_VIOLATION→DATA_INTEGRITY. (error-codes.yaml:580 주석에도 동일 stale 매핑이 전파돼 있었음 — ca-tmpl 레포 측 별도 정리 대상.) |
반영 완료: D3/D7/§판정 기준/Decisionized WI/§구현 가이드 §2/§엣지/Claims 의 PERSISTENCE 를 DATA_INTEGRITY(null/FK/check)/CONFLICT(unique) 로 정합 (코드+registry 근거). |
검증해야 할 주장
공식 문서/사례는 근거지만 내 프로젝트에서의 동작을 자동 보장하지 않는다. 구현 전/중/후 실제로 검증해야 하는 주장.
| Claim | Why uncertain | How to verify | Status |
|---|---|---|---|
| request DTO validation 이 controller boundary 에서만 트리거되고 domain layer 로 새지 않는지 | @Valid annotation 위치 / interceptor 체인 misconfiguration 가능성 |
ArchUnit rule (@Valid annotation 은 controller package 만) + integration test |
planned |
| business invariant 가 domain model / value object 안에서 강제되며 application service bypass 불가한지 | service-layer invariant check 로 domain bypass 가능성 | ArchUnit rule (domain model 의 invariant method 가 모든 mutation 경로에서 호출) + 의도적 bypass test | planned |
persistence integrity exception (e.g., DataIntegrityViolationException) 이 envelope 의 DATA_INTEGRITY(null/FK/check) / CONFLICT(unique) category 로 매핑되며 SQL/constraint name leak 안 되는지 |
현재 GlobalExceptionHandler 에 DataIntegrityViolationException 핸들러 자체가 없음(2026-06-02 확인) — owner feature-persistence-failure-baseline 미구현 |
owner branch 구현 후 exception handler contract test + DLP scan (constraint name regex grep on response) | planned (owner: feature-persistence-failure-baseline) |
| 4-layer mapping (syntax → VALIDATION, policy → AUTHZ/CONFLICT, invariant → CONFLICT/VALIDATION, persistence → DATA_INTEGRITY/CONFLICT) 가 모든 exception 에 일관 적용되는지 | category 분류의 silent miscategorization 가능성 | exception → category 매핑 contract test (각 layer 의 대표 exception 별 category 검증) | planned |
envelope 의 retryable 플래그가 category 와 정합한지 (registry 확인: VALIDATION/CONFLICT/DATA_INTEGRITY 모두 retryable=false) |
retryable 은 per-code (registry error-codes.yaml), category 에서 계산 금지 (Category.java javadoc) |
category × retryable matrix contract test + registry 대조 | planned |
RFC 7807 ProblemDetail 미채택이 Spring 6 의 autoconfigure (spring.mvc.problemdetails.enabled, SPRING-PD-C4) 와 충돌하지 않는지 |
Spring Boot default 가 true 인지 모름 → 자동 활성화 시 envelope override 필요 | sibling raw/branch-notes/feature-boundary-validation-mapping-contract 가 동일 위험을 actually-implemented 로 해소 (2026-05-29: GlobalExceptionHandler 가 ProblemDetail import 완전 제거, 우리 핸들러 우선이라 자동 활성화와 충돌 없음, BoundaryDemoControllerWireTest 11 케이스 wire-pin) → 본 branch 재검증 불필요 |
verified (sibling) |
details 필드에 raw object / body / SQL detail 이 절대 leak 안 되는지 |
exception handler 의 detail 직렬화 path 에서 누락 가능 | leakage contract test (의도적 SQLException 발생 → response body grep) + production log scrub | planned |
| duplicate validation 의 canonical owner 가 코드 주석 / 문서에 명시되는지 | duplication 자체는 허용이지만 owner 누락 시 silent contradiction 가능 | code review checklist + ArchUnit rule (duplicate validator 는 owner annotation 필수) | needs-confirmation |
관심사 커버리지 (coverage-auditor 자동 생성)
/coverage가 생성하는 생성물 — 손유지 금지. 기준:rules/coverage-gate.md. governing_docs:clean-architecture-package-layout+api-error-envelope-design. 마지막 감사: 2026-06-02 → Covered (Blocking 0 / Should-fix 0 / Advisory 4).
| 관심사 | 상태 | owner | 심각도 | 근거 |
|---|---|---|---|---|
| 4-layer validation 책임 배치 (syntax/policy/invariant/persistence) | covered-here | — | — | D1·D2 |
| domain purity — infrastructure exception raw 전파 금지 | covered-here | — | — | D3 |
| @Valid 정적 강제 (controller 패키지 밖 금지) | covered-here | — | — | D1 (Claims row 1, planned) |
| business invariant violation → error.category 분류 | covered-here | — | — | D7 |
| persistence integrity → DATA_INTEGRITY(null/FK/check) / CONFLICT(unique) 매핑 | covered-here | — | — | D3·D7 (Audit F5 RESOLVED) |
| exception leak 금지 (SQL/constraint name/stacktrace) | covered-here | — | — | D9 |
| retryable 필드 정합 (VALIDATION/CONFLICT/DATA_INTEGRITY = false) | covered-here | — | — | Claims row 5 (registry SSOT) |
| web DTO containment — domain 직렬화 금지 | delegated | raw/branch-notes/feature-boundary-validation-mapping-contract D8 | — | owner actually-implemented (controllers_do_not_return_domain_or_entity_types) |
| validation 실패 → error.details[] 항목별 오류 매핑 | delegated | raw/branch-notes/feature-boundary-validation-mapping-contract D10 | — | owner actually-implemented (VALIDATION_FAILED details shape) |
| i18n 검증 메시지 정책 | governing 문서 비열거 | — | Advisory | 두 governing 문서 모두 미열거 — 프로젝트 레벨 owner 여부는 /coverage --project 영역 |
| 입력 정규화/sanitization before validation | governing 문서 비열거 | — | Advisory | 미열거. 실코드 trim/sanitize 는 header/pagination 맥락 |
| fail-fast vs collect-all 오류 수집 정책 | governing 문서 비열거 | — | Advisory | 미열거. 형제 B4 @GroupSequence 가 사실상 결정 |
| cross-field/conditional validation | governing 문서 비열거 | — | Advisory | 미열거. 형제 B4 class-level constraint actually-implemented |
완료 후 wiki 추출 대상
wiki/projects/ca-skeleton-operational-contract.md의 business rule validation canonical section.
마주친 문제
- 아직 없음(문서 단계).
묶음
- raw/company-tech-blogs/github-api-error-format
- raw/company-tech-blogs/stripe-error-format
- raw/company-tech-blogs/toss-payments-error-format
- raw/official-docs/google-api-error-format
- raw/official-docs/graphql-errors-spec
- raw/official-docs/json-api-errors-spec
- raw/official-docs/problem-detail-rfc-7807
- raw/official-docs/spring-mvc-rest-exception-handling
- raw/official-docs/spring-problem-detail
- raw/official-docs/validation-jakarta-bean-validation-3.0-spec
본 feature branch 는 leaf — 자식 자료 없음. Phase C2 실 코드 작성 단계에서 errors / interview prep / lectures 가 누적되면 본 섹션에서 그룹화.
오류 기록 (본 feature 작업 중 발생)
- (없음 — 현재 documented-only 단계)
면접 준비 (이 작업에서 나올 수 있는 면접 질문)
- (없음 — Phase C2 실 구현 단계에 누적)
관련 일일 노트
이 브랜치를 작업한 날짜들. 양방향 nav 유지.
- (아직 없음 — documented-only 단계. 실 구현 착수 시
[[raw/daily-notes/YYYY-MM-DD]]누적)
완료 후 정리
머지/종료 시점에 채움.
- PR 링크:
- 리뷰 메모:
- 머지 결과 / 배포 환경:
- wiki 추출 대상 (verified만,
wiki/projects/로만 추출):actually-implemented항목:locally-verified항목:prod-verified항목:
- 추출하지 않을 항목 (planned / documented-only / abandoned):