71 KiB
title, source_type, status, branch, parent_branch, related_projects, tags, created, last_reviewed, target_merge, status_label, last_implementation_pass, ingest_note, id, kind, project, work_item, inherits, refines, overrides, depends_on, contract_packet, contract_packet_sha256
| title | source_type | status | branch | parent_branch | related_projects | tags | created | last_reviewed | target_merge | status_label | last_implementation_pass | ingest_note | id | kind | project | work_item | inherits | refines | overrides | depends_on | contract_packet | contract_packet_sha256 | |||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| branch / feature-boundary-validation-mapping-contract | branch-note | verified | feature-boundary-validation-mapping-contract |
|
|
2026-05-21 | 2026-06-04 | in-progress | 2026-05-29 (4th pass — Forbidden 정적 강제 + B5 sample 보강 + 문서 구현 가이드) | 2026-06-04 /ingest — ca-tmpl @fccb033 ground-truth 대조 후 verified. wiki/projects/ca-tmpl/boundary-validation-mapping.md + wiki/concepts/boundary-validation-and-dto-mapping.md 추출. 대조 결과: controller-return-type / valid_cascade_depth ArchUnit rule 은 노트의 planned 표기와 달리 fccb033 에 실제 구현됨(actually-implemented 로 격상). MappingException 위치는 노트 errors 로그의 application.exception 이 아니라 fccb033 에서 shared.error. sample 은 fccb033 에 이미 sample-portfolio(WorkLog), wire 테스트는 WorkLogControllerWireTest. ./gradlew test verifyCleanArchitectureDependencies → 126 tests / 0 failures. | BR-CA-SKELETON-OPERATIONAL-CONTRACT-002 | project-work-item | ca-skeleton-operational-contract | WI-CA-SKELETON-OPERATIONAL-CONTRACT-002 |
|
1 | 4c15ac1bd65a18209652e97e9c30583cf8326a361f4979f7fc588e0ab66cb67a |
branch: feature-boundary-validation-mapping-contract
Layer:
raw/branch-notes/— request/application/domain/response/filter 경계의 validation과 mapper 계약을 정의합니다.
부모 (필수)
- Parent project (canonical SSOT): raw/project-notes/ca-skeleton-operational-contract
ca-skeleton 은 별도 root branch 없이 project-note 가 SSOT 역할. 본 feature branch 는 project-note 의 운영 계약 중 해당 영역 (§<관련 섹션>) 의 결정/근거/금지 사항을 정제한다.
묶음
- 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/arch-acl-microsoft-pattern
- raw/official-docs/google-api-error-format
- raw/official-docs/graphql-errors-spec
- raw/official-docs/json-api-errors-spec
- raw/official-docs/patch-json-merge-rfc7396
- raw/official-docs/problem-detail-rfc-7807
- raw/official-docs/runtime-spring-boot-virtual-threads
- raw/official-docs/schema-jackson-polymorphic-deserialization
- raw/official-docs/spring-mvc-rest-exception-handling
- raw/official-docs/spring-problem-detail
- raw/official-docs/validation-jakarta-bean-validation-3.0-spec
- raw/blog-topics/archunit-jackson-default-typing-cve-2019-14379-block-2026-05-29
- raw/blog-topics/boundary-validation-mapper-responsibility-map-2026-07-02
- raw/blog-topics/identifier-governance-rule-scoping-by-id-kind-2026-06-01
본 feature branch 는 leaf — 자식 자료 없음. Phase C2 실 코드 작성 단계에서 errors / interview prep / lectures 가 누적되면 본 섹션에서 그룹화.
근거 자료
- raw/official-docs/patch-json-merge-rfc7396 — PATCH null=deletion IETF normative 근거 (B2 블라인드)
- raw/official-docs/arch-acl-microsoft-pattern — Microsoft Azure Architecture Center ACL 패턴 공식 정의. outbound HTTP 응답 → domain 변환이 mapper 범위 안에 포함된다는 scope 명확화 근거 (블라인드 B7).
- raw/official-docs/spring-mvc-rest-exception-handling — Spring MVC
ErrorResponse계약,ResponseEntityExceptionHandler처리 예외 목록,HttpMessageNotReadableException/MethodArgumentNotValidExceptionnormative 처리 근거 (B3 블라인드 해소) - raw/official-docs/validation-jakarta-bean-validation-3.0-spec — Jakarta Bean Validation 3.0 normative spec. class-level constraint 목적, group sequence short-circuit, @Valid cascade, TYPE_USE container element 위치 정의 (B4 블라인드 해소)
- raw/official-docs/runtime-spring-boot-virtual-threads — Spring Boot 공식 레퍼런스:
spring.threads.virtual.enabledsemantics + virtual thread 활성화 시 executor/scheduler 전환 근거 (B6 블라인드) - raw/official-docs/schema-jackson-polymorphic-deserialization — Jackson polymorphic deserialization 보안 지침.
enableDefaultTyping()금지 (@Deprecatedsince 2.10) +PolymorphicTypeValidator/BasicPolymorphicTypeValidator공식 allowlist API + CVE-2019-14379 gadget chain RCE 근거 (B5 블라인드 해소)
오류 기록 (본 feature 작업 중 발생)
- raw/errors/mapping-exception-location-archunit-catch-2026-05-29 — B7 outbound ACL 매퍼가
MappingException을 던지자outbound_adapter_does_not_depend_on_web_or_persistence_adaptersArchUnit 규칙이 cross-adapter 의존을 catch. 해소 = sentinel 을application.exception으로 이전. fitness function 이 contract 변경 비용을 정확히 측정한 정상 동작 의 기록. - (Jackson
DeserializationFeatureenum 이 app-bootstrap 의 test classpath 에 없어 컴파일 실패한 1회는testImplementation 'spring-boot-starter-json'추가로 해소 — 1회성 환경 정렬이므로raw/errors/등재 생략.)
면접 준비 (이 작업에서 나올 수 있는 면접 질문)
- (해당 enforcement 패스에서 면접 질문 단독 추출 없음. RFC 7807 거부 + custom envelope, CVE-2019-14379 + ArchUnit 정적 차단 같은 질문 후보는 sibling branch
feature-business-rule-validation-contract의 cluster 와 신규 raw/blog-topics/archunit-jackson-default-typing-cve-2019-14379-block-2026-05-29 에서 다룰 영역과 중복.)
Blog topics (이 작업에서 파생)
- raw/blog-topics/archunit-jackson-default-typing-cve-2019-14379-block-2026-05-29 — Jackson
enableDefaultTyping()/LaissezFaireSubTypeValidator의 RCE 게이트를 ArchUnit fitness function 으로 정적 차단한 1차 enforcement 사례.
브랜치 계약 패킷
- 생성 시 프로젝트 개정:
1 - 패킷 스키마:
contract_packet: 1 - 완료 조건: boundary·mapping 6필드 contract와 negative fixture가 통과한다
상속한 프로젝트 결정
| 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 |
브랜치 지역 결정
기존 branch-local 결정은 아래
## Decision Evidence Map / 결정-근거 매핑의 D-row가 소유하며 이 packet에서 복제하지 않는다.
| Decision ID | Decision | Relation | Supporting Claims | Status |
|---|
선언한 예외
| Override ID | Overrides | Reason | Approval | Status |
|---|
목표
CA skeleton에서 경계가 흐려지면 DTO, domain object, persistence model이 서로 새어 나갑니다. 이 branch는 각 경계가 무엇을 검증하고 어떤 mapper를 통과해야 하는지 고정합니다.
- 이슈:
- PR:
범위
포함 범위
- request DTO validation.
- request DTO -> application command/query mapper.
- application command/query invariant validation.
- domain object -> response DTO 직접 노출 금지.
- response mapper public field 정책.
- filter/interceptor request context propagation.
제외 범위
- 특정 도메인 validator 구현.
- DB/JPA exception mapping.
- outbound adapter retry 구현.
TODO
TODO drained 2026-05-22 — 결정은 아래 "결정 사항" / "Mapper Tool Contract" / "판정 기준" / "테스트 계약" 참조. request DTO validation/request→command mapper/application invariant/domain object 노출 금지/response mapper 정책/filter context/boundary 우회 탐지 모두 결정 라인 또는 표 row로 반영됨. 잔존 TODO 없음.
본 branch는 Mapper Tool Contract와 validation 4-layer 분류 자체가 결정 표 등가. 별도 Decisionized Work Items 표는 작성하지 않음.
진행 중 메모
- mapper는 단순 변환기가 아니라 허용/차단/정규화/마스킹 경계입니다.
결정 사항 (decisions)
- 2026-05-21: 모든 경계에 validation/mapping 책임을 둠.
- 2026-05-22: validation은 syntax, policy, invariant, persistence integrity로 책임을 분리.
- 2026-05-22: mapper는 변환뿐 아니라 normalization, masking, public field selection의 경계로 취급.
- 2026-05-22: mapper 도구 기본값은 수기 mapper + record canonical constructor. MapStruct는 optional이며 사용 시 generated code architecture exemption과 mapper contract test가 필요.
- 2026-05-28: (B1) Jackson deserialization 정책 —
spring.jackson.deserialization.fail-on-unknown-properties=true명시 (Jackson default 와 동일, 회귀 방지).FAIL_ON_NULL_FOR_PRIMITIVES=true또는 request DTO 가 wrapper type (Integer,Long,Boolean) 만 사용. 클래스 단위@JsonIgnoreProperties(ignoreUnknown=true)는 ArchUnit rule 로 금지. - 2026-05-28: (B2) PATCH 요청 mapper 는 RFC 7396 의 null=deletion semantics 를 채택하지 않음 (envelope success/error 대칭 정책과 충돌). PATCH endpoint 는 absent 필드 = 변경 없음 / null 필드 = 명시적 null 의미로 처리하며,
JsonNullable(openapi-generator) 또는Optional<T>wrapper 로 absent vs null 을 구분. RFC 7396 미채택 사실을 OpenAPI 문서에 명시. - 2026-05-28: (B3) Mapping exception 분류 —
HttpMessageNotReadableException/MethodArgumentNotValidException은 SpringResponseEntityExceptionHandler가 normative 처리하므로VALIDATION카테고리. mapper-internal 예외 (IllegalArgumentException, record canonical constructorIllegalStateException, MapStruct generated NPE) 는 별도@ExceptionHandler에서 잡아 ca-tmpl operational contract 의MAPPING_FAILED신규 code 로 분류 (canonical SSOT §6 갱신 필요). - 2026-05-28: (B4) Cross-field 와 class-level Bean Validation 의 책임 — class-level constraint 는 syntax 레이어 (request DTO 의 multi-property 형식 검증), domain invariant 는 application/domain layer 의 별도 검증.
@GroupSequence로 syntax → invariant 단계 short-circuit 패턴 채택.@Validcascade depth 는 ArchUnit / runtime limit 으로 nested 3 단계 이내 제한. - 2026-05-28: (B5) Polymorphic deserialization —
ObjectMapper.enableDefaultTyping()/activateDefaultTyping(LaissezFaireSubTypeValidator)금지 (ArchUnit). sealedCommandinterface + record subtypes 는@JsonTypeInfo(use = NAME)+@JsonSubTypes명시 또는BasicPolymorphicTypeValidatorallowlist 로만 deserialize. - 2026-05-28: (B6) Virtual thread —
spring.threads.virtual.enabled=true활성화 시 Tomcat connector /@Asyncexecutor 가SimpleAsyncTaskExecutor로 전환되므로 filter/interceptor 의ThreadLocal기반 context propagation (RequestContextHolder, MDC) 안전성을 contract test 로 검증. MDC 는 SLF4J 2.0+ (Loom 호환) 또는 Micrometer Context Propagation 위임.InheritableThreadLocal사용 금지. - 2026-05-28: (B7) 본 branch 의 mapper 범위는 inbound
request→application+application→response뿐 아니라 outboundexternal-response→domain도 포함 (ACL 패턴). outbound adapter 의 응답 → domain 변환에도 동일한 normalization / masking / public field selection 책임이 적용된다. ACL 의 inline (인-프로세스) 구현은 허용, 별도 서비스 추출은 out-of-scope. - 2026-05-28: (B8) Bulk endpoint 의 partial success — envelope 의 top-level
successflag 는 전체 성공 시에만 true. 부분 실패는success: false+error.code = BATCH_PARTIAL_FAILURE+error.details[]에 항목별 결과 배열. 단일 항목 endpoint 와 schema 가 다르므로 OpenAPI 에서 별도 response shape 으로 분기. Google rpc.Status typed details / JSON:API errors[] / GraphQL data+errors 패턴이 선례. - (B9) Resource identifier ArchUnit rules cross-cite — raw/branch-notes/feature-resource-identifier-contract D17 의 5개 rule (
no_long_id_pk,no_uuid_random_in_controller,no_math_random_for_id,no_varchar_255_for_id_column,no_find_by_id_without_tenant) 를 본 branch 의 ArchUnit suite 에 등록. 구현 skeleton 은 resource-identifier branch §구현 가이드 §6. ArchUnit version = archunit-junit5 1.3.0 per project §34 Stack Commitment.
근거 (필수, 최소 1개+)
본 branch의 결정 근거. 상세 비교는 §외부 근거 / 대안 조사 (있다면) 참조.
| Source | 정당화하는 결정 |
|---|---|
| raw/official-docs/schema-jackson-unknown-field-handling | Jackson DeserializationFeature default 4종 — B1 블라인드: request boundary 직전 FAIL_ON_UNKNOWN_PROPERTIES / FAIL_ON_NULL_FOR_PRIMITIVES / FAIL_ON_IGNORED_PROPERTIES / READ_UNKNOWN_ENUM_VALUES_AS_NULL 정책 강제 근거 |
| raw/official-docs/schema-jackson-polymorphic-deserialization | Jackson polymorphic deserialization 보안 지침 — B5 블라인드: enableDefaultTyping() 금지 (@Deprecated 2.10) + PolymorphicTypeValidator allowlist + CVE-2019-14379 gadget chain RCE 근거 |
| raw/official-docs/patch-json-merge-rfc7396 | PATCH null=deletion IETF normative semantics — B2 블라인드: null vs absent 구분 강제 근거 |
| raw/official-docs/spring-mvc-rest-exception-handling | Spring MVC ErrorResponse 계약, ResponseEntityExceptionHandler 처리 예외 목록 — B3 블라인드: HttpMessageNotReadableException / MethodArgumentNotValidException → VALIDATION 분류 근거 |
| raw/official-docs/validation-jakarta-bean-validation-3.0-spec | Jakarta Bean Validation 3.0 normative — B4 블라인드: class-level constraint, group sequence short-circuit, @Valid cascade, TYPE_USE container element 위치 정의 |
| raw/official-docs/runtime-spring-boot-virtual-threads | Spring Boot spring.threads.virtual.enabled + virtual thread executor/scheduler 전환 — B6 블라인드: filter/interceptor ThreadLocal context propagation 안전성 |
| raw/official-docs/arch-acl-microsoft-pattern | ACL 패턴 공식 정의 — B7 블라인드: outbound HTTP 응답 → domain 변환이 mapper 범위 안에 포함된다는 scope 명확화 |
| 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급. B8 블라인드: typed details 다형성으로 bulk partial-result 표현 |
| raw/official-docs/json-api-errors-spec | B8 블라인드: 다중 error 객체 배열 — bulk partial success 표현 |
| raw/official-docs/graphql-errors-spec | partial success 1급. B8 블라인드: data + errors 공존 모델 |
| raw/company-tech-blogs/github-api-error-format | — |
판정 기준
| 구분 | 기준 |
|---|---|
| Decision | 모든 외부 입력/출력은 mapper와 validation 경계를 통과 (B7: outbound 응답 → domain ACL mapper 포함) |
| Allowed | 단순 query DTO도 mapper를 거쳐 command/query로 변환. MapStruct는 optional generated mapper로만 허용. sealed Command interface 의 polymorphic deserialization 은 @JsonTypeInfo + @JsonSubTypes 또는 BasicPolymorphicTypeValidator allowlist 로만 허용 (B5). PATCH endpoint 는 absent vs null 구분 mapper 만 허용 (B2) |
| Forbidden | request DTO -> domain 직접 생성, domain/persistence model -> response 직접 반환, mapper 없는 public field 노출, ObjectMapper.enableDefaultTyping() / activateDefaultTyping(LaissezFaireSubTypeValidator) 호출 (B5), 클래스 단위 @JsonIgnoreProperties(ignoreUnknown=true) (B1), InheritableThreadLocal 직접 사용 (B6), RFC 7396 application/merge-patch+json content type 사용 (B2 — 미채택), outbound 응답 raw → domain 직접 mapping (B7 — ACL bypass) |
| Required validation | request syntax (class-level constraint 포함), command/query invariant, use case policy, domain invariant, response public field. @GroupSequence 로 syntax → invariant short-circuit (B4). @Valid cascade depth ≤ 3 (B4) |
| Failure condition | 경계 우회로 private/internal field가 응답에 노출되거나 domain invariant가 bypass되면 실패. mapper-internal exception 이 MAPPING_FAILED 가 아닌 INTERNAL 로 분류되면 실패 (B3). bulk endpoint 의 부분 실패가 success: true 로 반환되면 실패 (B8). virtual thread 환경에서 requestId/traceId/MDC 가 application layer 까지 propagate 되지 않으면 실패 (B6) |
구현 가이드
문서가 모호해서 구현자가 임의로 정해야 했던 결정 카탈로그.
1. Error code → HTTP status → retryable 표 (B3/B8 보강)
Trace: 본 표의 row 는 모두 본 branch (boundary/validation/mapping) 결정 영역. 도메인 특화 code (예:
USER_NOT_FOUND) 와 다른 branch 결정 영역 (security/conflict/infra-failure) 의 row 는 raw/project-notes/ca-skeleton-operational-contract §6 Operational Error Category 통합 정의 — 본 표는 §6 의 부분 view.
VALIDATION_FAILED→ D10 +SPRING-MVC-EXC-C1/C4/C5,JBV-3.0-C5MAPPING_FAILED→ D10 (canonical SSOT §6 등록 완료 2026-05-29)BATCH_PARTIAL_FAILURE→ D14 +GOOG-ERR-C3,GQL-ERR-C3,JSONAPI-ERR-C1(HTTP 200 은 partial-response 선례 차용; canonical SSOT §6 등록 완료 2026-05-29)
| code | HTTP | retryable | 의미 | 사용 |
|---|---|---|---|---|
VALIDATION_FAILED |
400 | false | Bean Validation 실패, JSON 파싱 실패, unknown field, 알 수 없는 enum value, polymorphic discriminator 불일치 — D10 의 "VALIDATION 카테고리" 일체 | HttpMessageNotReadableException, MethodArgumentNotValidException, ConstraintViolationException 모두 라우팅 |
MAPPING_FAILED |
400 | false | Mapper-internal 실패 (record canonical constructor IllegalArgumentException wrap, MapStruct NPE, ACL normalization 실패). 반드시 MappingException 으로 명시적 wrap. |
handleMapping |
BATCH_PARTIAL_FAILURE |
200 | false | Bulk endpoint 의 부분/전체 실패. HTTP 200 + envelope.success=false (단일 항목 endpoint 와 동일 응답 표면, 분기는 envelope.success 로). | BulkEnvelope.partial(...) |
MALFORMED_REQUEST는 제거되었다. 초기 구현은 unknown field 를MALFORMED_REQUEST(400) 로 매핑했으나 D10 의 "HttpMessageNotReadableException → VALIDATION category" 와 본 branch §테스트 계약 "(B1) 400 + VALIDATION_FAILED" 와 충돌.VALIDATION_FAILED로 통합하고 구체적 실패 모드(UnrecognizedPropertyException / InvalidTypeIdException / JsonParseException 등) 는error.details.cause로 surface 한다.
2. error.details shape (코드별)
Trace: 본 표는 §1 의 in-scope row 와 1:1 대응. 도메인/HTTP-표준 row 의 shape 는 raw/project-notes/ca-skeleton-operational-contract §6 통합 정의.
VALIDATION_FAILED (MethodArgumentNotValid)→ SpringFieldErrorAPI 표준 (SPRING-MVC-EXC-C5의 message arg{1}=field errors차용)VALIDATION_FAILED (ConstraintViolation)→ JakartaConstraintViolationAPI 표준 (JBV-3.0-C2)VALIDATION_FAILED (HttpMessageNotReadable)→ D11 +SJUF-C1~C4(Jackson exception 종류)BATCH_PARTIAL_FAILURE→ D14 +GOOG-ERR-C3typed details /JSONAPI-ERR-C1errors array 패턴- UNSUPPORTED_IMPL_DECISION: field 영문 키 이름 (
cause,index,status,id등) — 근거 raw 가 구조 는 권고하나 키 이름 은 권고하지 않음. OpenAPI 정의 시 명시 필요.
| code | details shape |
|---|---|
VALIDATION_FAILED (from MethodArgumentNotValidException) |
List<{field, rejectedValue, message}> (Spring FieldError) |
VALIDATION_FAILED (from ConstraintViolationException) |
List<{field, message}> |
VALIDATION_FAILED (from HttpMessageNotReadableException) |
{cause: <Jackson exception simple-name>} |
BATCH_PARTIAL_FAILURE |
List<BulkItemResult{index, status, id, code, message}> |
| 그 외 | null |
OpenAPI 분기는
oneOf로 표현. OpenAPI 스펙 자체가 부재해서 구현은 보류 — 별도 PR.
3. MappingException 라우팅 규약
Trace: mapper-internal 라우팅 흐름은 D10 직접 권고 (mapper-internal 예외 분류).
- UNSUPPORTED_IMPL_DECISION: ①
MappingException이라는 wrap 클래스 이름 (D10 은 wrap 강제만 권고, 클래스명은 임의). ②"정적 강제는 두지 않음" trade-off (false positive 우려 + mapper 코드 양이 적어 review 로 충분이라는 사용자 판단) — 근거 raw 없음, trade-off articulation 기록 으로 보존.
- Mapper 내부 (web/outbound/persistence ACL 어디든) 가 던진 논리적 mapping 실패 는 반드시
MappingException으로 wrap 해서 던진다. 그러면handleMapping이MAPPING_FAILED로 라우팅. - 이 규약을 컨벤션 으로 두고 정적 강제는 두지 않는다. 정적 강제는 너무 광범위해서 false positive 가 많고, mapper 코드는 양이 적어 review 로 충분하다는 판단.
4. Envelope wrap 적용 범위
Trace (audit 2026-05-29):
- In-scope: 모든
@RestController응답Envelope<T>자동 wrap 자체 → D6 직접 권고 (success flag + envelope 대칭).BulkEnvelopepass-through → D14 직접 권고 (bulk partial success shape 분리).- HTTP 표준 차용: DELETE / 204 No Content body skip — HTTP 표준, 본 branch 결정 외 자연 결과.
- UNSUPPORTED_IMPL_DECISION: ①
EnvelopeBodyAdvice의 SpringResponseBodyAdvice메커니즘 선택 자체 (D6 는 wrap 만 권고, 메커니즘은 임의). ②컨트롤러 직접 반환 pass-through 로직 (재wrap 방지). ③Envelope/BulkEnvelope라는 클래스 명명. ④Envelope.ok(...),BulkEnvelope.partial(...),BulkEnvelope.allOk(...)의 static factory API 모양 — D6/D14 가 권고하지 않음, 사용자 임의 design.- 운영 영향 anchor: probe / monitoring 이
$.status→$.data.status로 갱신 필요 — 근거 기반 결정의 운영 영향 으로 §11 운영 회복력 검토 후보.
- 모든
@RestController응답 (sample-portfolio 의 도메인 컨트롤러 + productionHealthcheckController포함) 은EnvelopeBodyAdvice가 자동으로Envelope<T>로 wrap. - 컨트롤러가 직접
Envelope.ok(...)반환하면 advice 가 재wrap 하지 않음 (pass-through). 명시적 envelope 구성이 필요한 경우 직접 반환 OK. BulkEnvelope<T>도 advice 의 pass-through 대상 — bulk 엔드포인트는 직접BulkEnvelope.partial(...)/BulkEnvelope.allOk(...)반환.- DELETE / 204 No Content 는 body 가 없으므로 wrap 대상이 아님 (advice 가 null body skip).
- 운영 영향: probe / monitoring 이
$.status같은 평탄 path 를 직접 읽고 있었다면$.data.status로 갱신 필요.
5. Cascade depth ≤ 3 정적 강제 메커니즘 (B4-2)
Trace (audit 2026-05-29):
- In-scope:
@Validcascade depth ≤ 3 결정 자체 → 본 branch B4 결정 라인 + D2 (4-layer validation) 직접 권고 ("ArchUnit / runtime limit 으로 nested 3 단계 이내 제한").JBV-3.0-C4(@Validcascade) 가 cascade 메커니즘 을 normative 로 다룸 → depth limit 자체는 본 branch 의 trade-off 결정 (DoS 방어).- UNSUPPORTED_IMPL_DECISION: ①ArchUnit rule 이름
valid_cascade_depth_at_most_three(임의 명명). ②depth 계산 algorithm (직접@Valid필드 = depth 1, 재귀 depth +1) — 근거 raw 가 depth 의 조작적 정의 를 권고하지 않음, 사용자 임의 정의. ③외부 라이브러리 (java.*,jakarta.*) cascade 무시 — false positive 회피의 사용자 trade-off, 근거 없음. ④limit 값3자체 — 1, 5, 7 도 가능했으나 사용자 임의 선택 (DoS 위험과 표현력의 균형 판단).
- ArchUnit
valid_cascade_depth_at_most_three규칙이..adapter.web..dto..패키지 클래스의@Valid필드를 재귀 따라가며 도메인 내부 클래스 사이의 cascade 깊이를 계산. - depth 1 = 직접
@Valid필드. depth 2 =@Valid필드의@Valid필드. 등등. - 외부 라이브러리 (
java.*,jakarta.*) 로의 cascade 는 무시 (자기 도메인 외부는 depth 측정 안 함). - 위반 시 build 실패. 신규 nested DTO 작성 시 양 3 단계 안에서 펼치거나 별도 매퍼/validator 로 분리.
6. Polymorphic deserialize 정적 강제 좁힘 (B5)
Trace (audit 2026-05-29):
- In-scope (strong):
enableDefaultTyping()차단 → D12 +JACK-POLY-C3(enableDefaultTyping()2.10@Deprecated, 대체activateDefaultTyping(PolymorphicTypeValidator));LaissezFaireSubTypeValidator차단 → D12 +JACK-POLY-C1+JACK-POLY-C5(gadget chain CVE-2019-14379 normative 위험);activateDefaultTyping(BasicPolymorphicTypeValidator)허용 → D12 +JACK-POLY-C4(allowlist 표준 구현체).- UNSUPPORTED_IMPL_DECISION: ①ArchUnit rule 이름
no_jackson_enable_default_typing_call,no_jackson_laissez_faire_subtype_validator(임의 명명). ②sample-portfolio 의BasicPolymorphicTypeValidatorAllowlistTest의 4-case 선택 (Cat, Dog, 비허용 subtype, 임의 JDK 클래스) — pin 패턴의 사용자 임의 design, raw 가 권고하지 않음.- 참고: 본 sub-section 은 모든 in-scope 결정이 normative claim 으로 지원되는 가장 깨끗한 sub-section. 다른 sub-section 의 audit 기준점으로 사용 가능.
enableDefaultTyping()(no-arg, deprecated) 호출 → 차단 (ArchUnitno_jackson_enable_default_typing_call).LaissezFaireSubTypeValidator클래스 참조 → 차단 (ArchUnitno_jackson_laissez_faire_subtype_validator).activateDefaultTyping(BasicPolymorphicTypeValidator allowlist)→ 허용. 차단 대상 아님. 안전한 allowlist 패턴이며sample-portfolio의BasicPolymorphicTypeValidatorAllowlistTest가 4 case 로 pin (allowlisted Cat/Dog 통과, 비허용 subtype 거부, 임의 JDK 클래스 거부).- 두 가지 정적 강제 + 두 가지 sample (sealed
@JsonTypeInfo/@JsonSubTypes와BasicPolymorphicTypeValidator) 모두 D12 에 기록된 normative 패턴.
7. Controller 반환 / Application 파라미터 정적 강제 (§Forbidden 직접 강제)
Trace (audit 2026-05-29):
- In-scope (decision): controller return type 차단 → D1 + D8 (response mapper public field 만 노출, domain 직접 노출 금지). application method DTO 파라미터 차단 → D7 (request DTO → command/query mapper 강제).
- Decision Evidence 강도 한계: D1, D8 의 Supporting Claims 는 부분 normative —
RFC7807-C5(debug 정보 분리 사상),JSONAPI-ERR-C5(호출별 불변 사상) 이 원칙 만 권고, ArchUnit 강제는 직접 도출 X. D7 도RFC7396-C2~C4가 PATCH semantics 만 다룸. 즉 정적 강제 메커니즘 자체 는 사용자 trade-off 결정 (review-only vs static enforcement).- Cross-reference 보강: 패키지 패턴
..domain.entity..,..adapter.persistence.entity..,..adapter.web..dto..의 정확한 glob → raw/project-notes/ca-skeleton-operational-contract §20 Skeleton Blueprint Contract 의 package convention 에서 도출. trace: SUPPORTED via canonical SSOT.- UNSUPPORTED_IMPL_DECISION: ①ArchUnit rule 이름
controllers_do_not_return_domain_or_entity_types,application_methods_do_not_accept_web_dtos. ②package glob 의 정확한..wildcard 위치 (canonical SSOT 의 anchor 와 일치하지만 glob 변환은 사용자 결정).
controllers_do_not_return_domain_or_entity_types— controller method 반환 타입이..domain.entity..또는..adapter.persistence.entity..또는..repository..에 거주하면 build 실패.EnvelopeBodyAdvice의 자동 wrap 이 도메인 객체를 silent 직렬화하는 회귀를 정적 으로 차단.application_methods_do_not_accept_web_dtos— application package 의 public method 가..adapter.web..dto..파라미터를 받으면 build 실패. controller 가 DTO → Command/Query 변환을 우회하는 회귀 차단.
8. ProblemDetail + RFC 7396 정적 강제 (D5 + B2)
Trace (audit 2026-05-29):
- In-scope (strong): ProblemDetail import 차단 → D5 직접 결정 +
SPRING-PD-C1(SpringProblemDetail= RFC 9457 representation),SPRING-PD-C2(모든 Spring MVC 예외가ErrorResponse구현 — envelope 와 충돌),SPRING-MVC-EXC-C1(corroborate). 정적 강제 목표 는 SUPPORTED.- In-scope (partial):
application/merge-patch+jsoncontent type 차단 → D7 (B2 결정 라인) +RFC7396-C2(null=deletion normative),RFC7396-C3(explicit null 부적합 경고). RFC 7396 의 미채택 결정 자체가 ca-tmpl envelope 정책 (D5, D6) 과 정합 — content type 차단으로 정적 강제.- UNSUPPORTED_IMPL_DECISION: ①ArchUnit rule 이름
no_problem_detail_usage,no_merge_patch_json_media_type_string(임의 명명). ②import-level 차단 vs class-reference 차단 vs annotation-value 차단의 메커니즘 선택 — D5/D7 이 직접 권고하지 않음, false positive vs 회귀 차단의 사용자 trade-off.
no_problem_detail_usage—org.springframework.http.ProblemDetailimport 자체를 차단. D5 의 "RFC 7807 명시적 거부" 가 코드 단계에서 강제됨. 신규 작업자가 무심코ProblemDetail을 부활시키면 build 실패.no_merge_patch_json_media_type_string—@RequestMapping(consumes="application/merge-patch+json")같은 RFC 7396 도입을 build 실패로 차단. B2 의 "RFC 7396 미채택" 정적 강제.
Mapper Tool Contract
| item | default |
|---|---|
| mapper implementation | 수기 mapper |
| command/query normalization | record canonical constructor 또는 static factory |
| generated mapper | MapStruct only, optional |
| generated code exemption | architecture rule에 package/path 명시 필수 |
| Jackson deserialization defaults (B1) | FAIL_ON_UNKNOWN_PROPERTIES=true 명시 (spring.jackson.deserialization.fail-on-unknown-properties=true), FAIL_ON_NULL_FOR_PRIMITIVES=true 또는 wrapper type only, READ_UNKNOWN_ENUM_VALUES_AS_NULL=false 유지 |
| polymorphic deserialization (B5) | @JsonTypeInfo(use = NAME) + @JsonSubTypes 명시 또는 BasicPolymorphicTypeValidator allowlist 만 허용 |
| PATCH semantics (B2) | absent / null / 값 3-상태 구분; JsonNullable 또는 Optional<T> wrapper 사용; RFC 7396 미채택 |
| outbound ACL mapper (B7) | outbound HTTP adapter 의 응답 → domain 변환에도 동일한 normalization / masking / public field selection 책임 적용 |
| bulk partial success (B8) | success: false + error.code = BATCH_PARTIAL_FAILURE + error.details[] 항목별 결과 배열 shape |
| virtual thread context (B6) | filter/interceptor 는 SLF4J 2.0+ MDC + RequestContextHolder 만 사용; InheritableThreadLocal 금지 |
| forbidden | reflection-based implicit mapping, entity/domain direct response serialization, enableDefaultTyping() / LaissezFaireSubTypeValidator, 클래스 단위 @JsonIgnoreProperties(ignoreUnknown=true), InheritableThreadLocal |
결정-근거 매핑
각 결정이 어떤 raw source claim 으로 뒷받침되는지 명시. company-tech-blog 출처는
company-case-study로 라벨링하며 공식 best practice 로 격상하지 않는다.
| Decision ID | Decision | Supporting Claims | Evidence Strength | Open Risk |
|---|---|---|---|---|
| D1 | 모든 경계에 validation/mapping 책임을 둠 (2026-05-21) | UNSUPPORTED_DECISION (Clean Architecture / Hexagonal boundary 책임 원칙은 일반 design wisdom 이지만 본 branch 가 cite 한 sources — Stripe/Toss/RFC 7807/Spring/Google/JSON:API/GraphQL/GitHub — 중 normative 진술 없음) | N/A | DDD boundary / Hexagonal port-adapter 패턴의 raw 인용 (예: Vaughn Vernon, Reflectoring) 별도 보강 필요 |
| D2 | validation 책임 분리 — syntax, policy, invariant, persistence integrity (4-layer) | MECHANISM SUPPORTED, TAXONOMY UNSUPPORTED_DECISION. raw/official-docs/validation-jakarta-bean-validation-3.0-spec.md#JBV-3.0-C1 (class-level constraint = validates state of class = multi-property → invariant mechanism), #JBV-3.0-C2 (ConstraintValidator receives class instance → 여러 field 동시 접근 가능), #JBV-3.0-C3 (group sequence short-circuit → syntax 선 실행 후 invariant 실행 패턴의 normative 근거). 4-layer 이름(syntax/policy/invariant/persistence integrity) 자체는 ca-tmpl internal decision — JBV spec 은 이 taxonomy 를 정의하지 않음. |
official-standard (mechanism) + UNSUPPORTED_DECISION (taxonomy naming + layer assignment) |
sibling branch 와 4-layer 정의의 정합성 cross-review 필수. JBV-3.0-C1~C3 은 Bean Validation 이 syntax/invariant 구분 없이 실행됨을 보여줌 — 분리를 강제하는 것은 application 설계 결정임을 명시 필요 |
| D3 | mapper 는 변환뿐 아니라 normalization, masking, public field selection 의 경계 | 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" — masking 의도와 정합), raw/official-docs/google-api-error-format.md#GOOG-ERR-C2 ("message is a developer-facing ... debug message" — public field 분리 사상) |
official-standard + official-vendor-doc |
mapper = security boundary 라는 강한 정의 자체는 cited sources 가 직접 권고하지 않음 — 일반 보안 원칙 (OWASP) 별도 raw 보강 권장 |
| D4 | mapper 도구 기본값 — 수기 mapper + record canonical constructor; MapStruct optional (사용 시 architecture exemption + contract test 필요) | UNSUPPORTED_DECISION (project-internal tool selection; cited sources 중 mapper 도구 선택 관련 normative / vendor 진술 없음) | N/A | 수기 mapper 의 boilerplate 비용 vs MapStruct generated 코드의 architecture leak 위험 trade-off 는 별도 측정 / vendor 비교 필요 |
| D5 | error envelope shape — custom 채택, RFC 7807 ProblemDetail 명시적 거부 (sibling branch feature-business-rule-validation-contract 와 동일 결정 공유) |
raw/official-docs/problem-detail-rfc-7807.md#RFC7807-C1 (canonical model application/problem+json), #RFC7807-C2 (type URI primary identifier), #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 구현 — envelope 와 충돌), raw/official-docs/spring-mvc-rest-exception-handling.md#SPRING-MVC-EXC-C1 (동일 사실의 primary source — 모든 Spring MVC 내장 예외는 ErrorResponse 구현), 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} 평면) |
official-standard + official-vendor-doc + company-case-study |
sibling branch D5 와 동일 evidence — cross-branch 일관성 확보됨. SPRING-MVC-EXC-C1 이 SPRING-PD-C2 를 corroborate. 단 RFC 7807 미채택 trade-off 의 ca-tmpl 측 해석은 cited sources 가 직접 권고하지 않음 |
| D10 | B3 블라인드 해소 — HttpMessageNotReadableException (JSON 역직렬화 실패) 은 VALIDATION 카테고리로 분류; MethodArgumentNotValidException (Bean Validation 실패) 은 VALIDATION 카테고리로 분류; mapper 내부 예외 (IllegalArgumentException, MapStruct NPE, record canonical constructor IllegalStateException) 는 Spring 이 자동 처리하지 않으므로 별도 @ExceptionHandler 로 처리하며 ca-tmpl operational contract 에서 MAPPING_FAILED 카테고리로 분류 |
raw/official-docs/spring-mvc-rest-exception-handling.md#SPRING-MVC-EXC-C1 (모든 Spring MVC 내장 예외는 ErrorResponse 구현 — HttpMessageNotReadableException 포함), #SPRING-MVC-EXC-C2 (ResponseEntityExceptionHandler 가 모든 Spring MVC 내장 예외 + ErrorResponseException 처리), #SPRING-MVC-EXC-C4 (HttpMessageNotReadableException 은 normative 처리 목록에 있음), #SPRING-MVC-EXC-C5 (MethodArgumentNotValidException 은 normative 처리 목록에 있음, {0}=global errors, {1}=field errors); raw/official-docs/validation-jakarta-bean-validation-3.0-spec.md#JBV-3.0-C5 (PARAMETER ElementType → Bean Validation 으로 method parameter 검증 → MethodArgumentNotValidException 발생 경로의 2차 normative 확인); mapper-internal 예외의 MAPPING_FAILED 카테고리 코드 자체는 UNSUPPORTED_DECISION — ca-tmpl 고유 operational contract |
official-vendor-doc (HttpMessageNotReadableException / MethodArgumentNotValidException → Spring normative) + official-standard (JBV-3.0-C5 corroboration) + UNSUPPORTED (MAPPING_FAILED 카테고리 코드 및 mapper-internal 예외 분류) |
Spring 이 HttpMessageNotReadableException 의 HTTP status 를 400 으로 설정한다는 것은 spring-mvc-rest-exception-handling.md 의 message code 표에서 직접 명시되지 않음 — ErrorResponse 구현체 내부(Spring source)에서 정의됨. MAPPING_FAILED 라는 category code 를 Operational Error Category 에 추가하는 결정은 ca-tmpl 내부 결정이며 별도 project-note 갱신 필요 |
| D6 | retryable 1급 + success flag — 어떤 표준에도 1:1 매칭 없음 (sibling branch D6 와 동일) | raw/official-docs/google-api-error-format.md#GOOG-ERR-C3 (typed details 다형성), #GOOG-ERR-C5 (표준 detail payloads), 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 |
sibling branch 와 동일 evidence; top-level 1급 retryable 은 ca-tmpl 고유 결정 |
| D7 | request DTO → application command/query mapper 강제 (DTO 의 service 직접 전달 금지); PATCH 요청 시 mapper 가 null vs absent 를 구분해야 함 (B2 블라인드) | raw/official-docs/patch-json-merge-rfc7396.md#RFC7396-C2 ("Null values in the merge patch are given special meaning to indicate the removal of existing values in the target." — null=deletion normative), #RFC7396-C3 (merge patch 는 explicit null 사용 시 부적합), #RFC7396-C4 (배열 부분 수정 불가 — merge patch 한계) |
official-standard (null=deletion 근거) + UNSUPPORTED (Hexagonal boundary 원칙 자체) |
RFC7396 은 null=deletion 의 normative 근거를 제공하나, Java record mapper 에서 absent field 를 별도 처리하는 구현 방법은 직접 권고하지 않음. Hexagonal port-adapter boundary 원칙 raw (예: Reflectoring, Woowahan) 별도 인용 보강 권장 |
| D8 | domain object → response DTO 직접 노출 금지; response mapper 가 public field 만 선택 | raw/official-docs/problem-detail-rfc-7807.md#RFC7807-C5 (detail 은 client correct 목적 — debug 정보 분리), raw/official-docs/json-api-errors-spec.md#JSONAPI-ERR-C5 (title 은 호출별 불변 — public field 안정성 사상) |
official-standard |
response mapper 의 public field selection 강제 메커니즘 자체는 일반 design 원칙 — @JsonView / DTO record 같은 구체적 구현 표준 없음 |
| D9 | filter/interceptor 가 request context propagation 담당 | UNSUPPORTED_DECISION (project-internal middleware 결정; 외부 표준 근거 없음) | N/A | Servlet filter chain 의 ordering / context propagation 보장은 별도 ArchUnit / integration test 필요 |
| D11 | B1 블라인드 해소 — Jackson deserialization 정책: FAIL_ON_UNKNOWN_PROPERTIES=true 명시, request DTO 는 wrapper type 또는 FAIL_ON_NULL_FOR_PRIMITIVES=true, 클래스 단위 @JsonIgnoreProperties(ignoreUnknown=true) ArchUnit 금지 |
raw/official-docs/schema-jackson-unknown-field-handling.md#SJUF-C1 (Jackson 2.13+ default — unknown property → JsonMappingException), #SJUF-C2 (FAIL_ON_NULL_FOR_PRIMITIVES=false default → JSON null → 0 silently — ca-tmpl 의 null/empty/missing 분리와 불일치), #SJUF-C3 (FAIL_ON_IGNORED_PROPERTIES=false default — silently skip), #SJUF-C4 (READ_UNKNOWN_ENUM_VALUES_AS_NULL=false default — exception throw) |
official-vendor-doc |
Spring Boot JacksonProperties 가 default 를 override 하지 않는다는 보장은 별도 — application.yaml 명시 설정 검증 필요. @JsonIgnoreProperties(ignoreUnknown=true) 클래스 단위 사용 금지를 강제하는 ArchUnit rule 자체는 project-internal |
| D12 | B5 블라인드 해소 — sealed Command interface + record subtypes 의 Jackson polymorphic deserialization: ObjectMapper.enableDefaultTyping() / activateDefaultTyping(LaissezFaireSubTypeValidator) 금지, @JsonTypeInfo(use = NAME) + @JsonSubTypes 명시 또는 BasicPolymorphicTypeValidator allowlist |
raw/official-docs/schema-jackson-polymorphic-deserialization.md#JACK-POLY-C1 (PolymorphicTypeValidator = default typing + @JsonTypeInfo class-name 기반 subtype 검증 공식 인터페이스, @since 2.10), #JACK-POLY-C2 ("pluggable allow lists to avoid security problems that occur with unlimited class names"), #JACK-POLY-C3 (enableDefaultTyping() = 2.10 @Deprecated, 대체 activateDefaultTyping(PolymorphicTypeValidator)), #JACK-POLY-C4 (BasicPolymorphicTypeValidator = class hierarchy/name pattern allowlist 표준 구현체), #JACK-POLY-C5 (NVD CVE-2019-14379: default typing + ehcache gadget → RCE, CVSS 9.8) |
official-vendor-doc + official-standard |
ArchUnit 으로 enableDefaultTyping() import / 호출 금지를 강제하는 rule 자체는 project-internal. sealed interface 패턴 사용 시 Jackson 의 sealed type 자동 인식 (Jackson 2.15+) 적용 여부는 별도 확인 필요 |
| D13 | B6 블라인드 해소 — Virtual thread (spring.threads.virtual.enabled=true) 활성화 시 filter/interceptor ThreadLocal context propagation 안전성 contract test 강제, MDC 는 SLF4J 2.0+ 위임, InheritableThreadLocal 금지 |
raw/official-docs/runtime-spring-boot-virtual-threads.md#SPRING-VT-C1 (virtual thread 활성화 시 task executor 는 SimpleAsyncTaskExecutor 로 전환), #SPRING-VT-C2 (비활성화 시 ThreadPoolTaskExecutor), #SPRING-VT-C3 (scheduler 는 SimpleAsyncTaskScheduler 로 전환, pooling 속성 무시), #SPRING-VT-C4 (builder bean 도 virtual thread 조건 충족 시 auto-config) |
official-vendor-doc (executor/scheduler 전환) + UNSUPPORTED_DECISION (Tomcat connector 전환 + RequestContextHolder / MDC virtual-thread 호환성) |
Spring Boot reference 의 task-execution 페이지는 executor/scheduler 전환만 명시. Tomcat embedded connector 의 virtual thread 적용 여부, RequestContextHolder 의 virtual thread 호환성, MDC 의 Loom 호환성은 별도 raw (Tomcat docs / SLF4J 2.0 docs / JEP 444) 보강 필요 |
| D14 | B8 블라인드 해소 — Bulk endpoint partial success: envelope success flag = 전체 성공 시에만 true, 부분 실패는 success: false + error.code = BATCH_PARTIAL_FAILURE + error.details[] 항목별 결과 배열, OpenAPI 에서 별도 response shape 분기 |
raw/official-docs/google-api-error-format.md#GOOG-ERR-C3 (typed details 다형성 — item별 결과 표현 모델), #GOOG-ERR-C5 (표준 detail payloads 카탈로그), raw/official-docs/json-api-errors-spec.md#JSONAPI-ERR-C1 (errors array 다중 표현 — 적용 시), raw/official-docs/graphql-errors-spec.md#GQL-ERR-C3 (partial response — data+errors 공존 — 분리 envelope 의 영감) |
official-vendor-doc + official-standard (선례 다형성 / partial response 패턴) + UNSUPPORTED_DECISION (BATCH_PARTIAL_FAILURE code 명명 자체는 ca-tmpl 고유) |
BATCH_PARTIAL_FAILURE code 를 Operational Error Category (canonical SSOT §6) 에 신규 등록 필요. 별도 BatchResult<T> envelope 도입 대안은 ca-tmpl success/error 대칭 정책과 충돌 위험 — 측정/리뷰 후 결정 |
| D15 | B9 cross-cite — Resource identifier ArchUnit rules 4개 (no_long_id_pk — ..domain.. 한정, no_uuid_random_in_controller, no_math_random_for_id, no_varchar_255_for_id_column) 를 본 branch ArchUnit suite 에 등록. 구현 skeleton 은 resource-identifier branch §구현 가이드 §6. 5번째 rule no_find_by_id_without_tenant 는 feature-tenant-context-policy (예정 branch) 로 이관 — tenant 모델 부재 시 production code 가 모두 깨지는 false positive 차단 |
raw/branch-notes/feature-resource-identifier-contract D17 (rule SSOT — 4 rules), D5 (ID generation = domain port + application 주입), D10 (PostgreSQL uuid native), D13 (ID 내 tenant 인코딩 거부 — 형식적 위치만). project §34 Stack Commitment (archunit-junit5 1.3.0) |
cross-branch-SSOT (resource-identifier D17) + project-ssot (§34 archunit-junit5 version) |
haveExplicitColumnLength() custom ArchCondition 의 archunit-junit5 1.3.0 API 호환성 검증 필요 (resource-identifier branch §구현 가이드 §6 UNSUPPORTED_IMPL_DECISION). 본 4개 rule 의 실제 코드는 boundary branch ArchUnit suite 가 호스팅, 결정 SSOT 는 resource-identifier branch D17. no_find_by_id_without_tenant 활성화는 multi-tenancy-contract 도착 시 |
검증해야 할 주장
공식 문서/사례는 근거지만 내 프로젝트에서의 동작을 자동 보장하지 않는다. 구현 전/중/후 실제로 검증해야 하는 주장.
| Claim | Why uncertain | How to verify | Status |
|---|---|---|---|
| controller 가 domain object 를 직접 반환하지 않는지 (response mapper boundary 강제) | implicit Jackson serialization 으로 domain object 가 직접 직렬화될 위험 | ArchUnit rule (controller method return type 은 DTO/record/ResponseEntity<DTO> 만) + integration test |
partially-implemented (2026-05-29 3차 패스: EnvelopeBodyAdvice 가 모든 컨트롤러 응답을 Envelope<T> 로 wrap, 기존 컨트롤러는 DTO record 만 반환 컨벤션. 컨트롤러 반환 타입의 정적 ArchUnit rule 은 미작성 — planned 잔존.) |
| request DTO 가 application service 의 method signature 에 직접 나타나지 않는지 | DTO 가 service layer 까지 leak 가능성 | ArchUnit rule (service package method 의 parameter type 은 Command/Query record 만) |
planned |
| MapStruct generated code 가 architecture exemption 없이 architecture rule 우회하지 않는지 | target/generated-sources 의 generated mapper 가 domain access 시 silent rule bypass |
ArchUnit rule 의 generated code exemption package 명시 + generated code 의 domain access pattern 검증 | planned |
| filter/interceptor 가 request context (traceId, principal, tenant) 를 application layer 까지 propagate 하는지 | Spring RequestContextHolder 또는 MDC propagation 누락 가능 |
integration test (downstream service 에서 context 값 접근 가능 검증) + @Async boundary test |
planned |
| mapper 가 PII / sensitive field 를 mask 하는지 (e.g., 카드번호, 주민번호, 이메일) | mapper 가 단순 변환만 하고 masking 누락 가능 | DLP scan + 의도적 PII field test (response body grep) | planned |
| MapStruct 사용 시 generated code 가 architecture exemption package 에 격리되는지 | exemption 없이 사용 시 ArchUnit rule 우회 | build 시 generated code path 검증 + ArchUnit rule 의 exemption 명시 확인 | needs-confirmation |
| internal diagnostic context (debug info, stacktrace, internal IDs) 가 response payload 에 섞이지 않는지 | exception handler 또는 mapper 에서 internal context 누출 가능 | response leakage contract test (debug field regex grep) + production log audit | planned |
RFC 7807 미채택이 Spring 6 의 autoconfigure (spring.mvc.problemdetails.enabled, SPRING-PD-C4) 와 충돌하지 않는지 (sibling branch D5 와 동일 우려) |
sibling branch business-rule-validation 과 동일한 risk | spring.mvc.problemdetails.enabled=false 명시 설정 검증 + Spring MVC error response shape contract test |
actually-implemented (2026-05-29 3차 패스: GlobalExceptionHandler 가 ProblemDetail import 완전 제거 + Envelope<Void> 반환, BoundaryDemoControllerWireTest 의 11 케이스가 envelope shape 을 wire-level 로 pin. Spring 의 ProblemDetail 자동 활성화도 우리 핸들러가 우선이므로 충돌 없음.) |
(B1) spring.jackson.deserialization.fail-on-unknown-properties=true 가 실제 설정되어 unknown field 가 400 으로 거부되는지 |
Spring Boot JacksonProperties 가 Jackson default 를 silent override 가능 |
application.yaml 명시 검증 + integration test (unknown field 가 포함된 JSON 요청 → 400 응답 + VALIDATION_FAILED code) |
unknown-field 거부와 4종 binding 설정은 기존 테스트 기록이 있으나 당시 envelope 기대값이 제거된 MALFORMED_REQUEST였다. D10의 VALIDATION_FAILED로 갱신한 wire test 재실행 전까지 needs-confirmation |
(B1) request DTO 중 primitive type 이 있는지 (있다면 FAIL_ON_NULL_FOR_PRIMITIVES=true 또는 wrapper 전환 필요) |
Jackson default 는 JSON null → primitive 0 silently | ArchUnit rule (request DTO record 의 component type 은 wrapper 또는 Optional 만) + Jackson configuration test |
planned (스위치는 locally-verified. component-type ArchUnit rule 은 미작성.) |
(B1) 클래스 단위 @JsonIgnoreProperties(ignoreUnknown=true) 사용 여부 |
정책 우회 risk | ArchUnit rule (request DTO 패키지 내 @JsonIgnoreProperties 사용 금지) |
actually-implemented (2026-05-29: request_dtos_do_not_silence_unknown_fields + JsonIgnoreUnknownRequestFixture 위반-증명 테스트.) |
| (B2) PATCH endpoint 가 absent / null / 빈 값을 mapper 에서 구분하여 처리하는지 | record 기본값으로 mapping 시 PATCH 가 null 로 덮어쓰는 silent overwrite | PATCH integration test (3 케이스: field 없음 → 무변경, field=null → 명시적 null, field=value → 갱신) + JSON Schema validation | actually-implemented (2026-05-29 3차 패스: BoundaryDemoControllerWireTest#b2_patch_field_absent_is_distinguished_from_explicit_null_and_value 가 PATCH /demo/boundary/patch-demo 로 3 케이스 wire-level pin. 기존 UpdateProfileRequest + UpdateProfileCommand + UserService.updateProfile 도 JsonNullable<T> / Patch<T> 로 마이그레이션 — silent overwrite 위험 제거.) |
(B2) RFC 7396 미채택 사실이 OpenAPI 문서에 명시되는지 (application/merge-patch+json content type 사용 안 함) |
클라이언트가 RFC 7396 semantics 를 가정할 risk | OpenAPI spec 검토 + content type assertion test | planned |
(B3) mapper 내부 예외 (record canonical constructor IllegalArgumentException, MapStruct NPE) 가 별도 @ExceptionHandler 로 잡혀 MAPPING_FAILED 카테고리로 분류되는지 |
Spring 이 자동 처리하지 않으므로 INTERNAL 로 새어 나가는 risk |
controller advice integration test (의도적 mapper exception 발생 → MAPPING_FAILED 응답 검증) |
actually-implemented (2026-05-29 3차 패스: BoundaryDemoControllerWireTest#b3_mapping_exception_surfaces_as_mapping_failed_envelope 가 POST /demo/boundary/mapping-failure 로 advice integration 검증. GlobalExceptionHandlerTest 가 unit 레벨 + envelope shape pin.) |
(B3) MAPPING_FAILED 신규 code 가 ca-tmpl operational contract canonical SSOT §6 에 등록되었는지 |
code 누락 시 sibling branch error envelope 와 정합 깨짐 | raw/project-notes/ca-skeleton-operational-contract §6 갱신 PR 검증 | actually-implemented (2026-05-29 §6 등록 완료) |
(B4) request DTO 에 @GroupSequence 로 syntax → invariant short-circuit 패턴 적용되는지 |
Bean Validation default 는 모든 group 평탄 실행 — invariant 가 syntax 실패 후에도 평가됨 | Bean Validation integration test (의도적 syntax 실패 → invariant validator 호출되지 않음 검증) | actually-implemented (2026-05-29: SampleGroupSequenceRequest + SampleGroupSequenceRequestTest. invariant 메서드가 null 필드와 만나면 IllegalStateException 을 던지도록 만들어 short-circuit 회귀 시 테스트가 빨갛게 떨어진다.) |
(B4) @Valid cascade depth 가 3 단계 이내인지 (DoS 방어) |
nested 객체 deep recursion 시 CPU 소모 | ArchUnit rule (nested @Valid annotation depth scan) + load test |
planned (현 패스에서 nested DTO sample 부재로 ArchUnit 동적 검사 미작성 — cascade depth 컨벤션은 adapter-web/CLAUDE.md 에 문서화.) |
(B5) ObjectMapper.enableDefaultTyping() / activateDefaultTyping(LaissezFaireSubTypeValidator) 호출이 코드 어디에도 없는지 |
CVE-2019-14379 류 gadget chain RCE risk | ArchUnit rule (enableDefaultTyping / LaissezFaireSubTypeValidator import 금지) + dependency check (jackson-databind 버전 최소 2.10+) |
actually-implemented (2026-05-29: no_jackson_laissez_faire_subtype_validator + no_jackson_enable_default_typing_call 두 ArchUnit rule, DefaultTypingFixture 가 violations-as-data 로 catch 검증. jackson-databind 버전 확인은 별도 supply-chain branch 책임.) |
(B5) sealed Command interface 가 @JsonTypeInfo + @JsonSubTypes 명시 또는 BasicPolymorphicTypeValidator allowlist 로만 deserialize 되는지 |
명시 누락 시 sealed type 도 deserialize 불가 | polymorphic deserialization integration test (각 subtype 정상 deserialize + allowlist 외 type 거부) | actually-implemented (2026-05-29: SamplePolymorphicRequest sealed interface + record subtypes + @JsonTypeInfo/@JsonSubTypes + SamplePolymorphicRequestTest 4 케이스. allowlist 외 discriminator → InvalidTypeIdException pin.) |
(B6) spring.threads.virtual.enabled=true 환경에서 filter/interceptor 의 RequestContextHolder + MDC propagation 이 application layer 까지 도달하는지 |
Loom virtual thread 의 ThreadLocal semantics 미검증 |
@SpringBootTest(properties = "spring.threads.virtual.enabled=true") integration test (downstream service 에서 requestId / traceId / MDC.get() 접근 가능 검증) |
actually-implemented (2026-05-29 3차 패스: VirtualThreadMdcE2ETest 가 @SpringBootTest(RANDOM_PORT) + 가상 스레드 + 실 RequestLoggingFilter + TestRestTemplate 로 server-generated requestId 와 client-supplied X-Request-Id 두 경로 모두 컨트롤러까지 도달함을 wire-level 로 pin.) |
(B6) InheritableThreadLocal 직접 사용이 없는지 + MDC 가 SLF4J 2.0+ 사용하는지 |
virtual thread 환경에서 InheritableThreadLocal 누설 가능 |
ArchUnit rule (InheritableThreadLocal import 금지) + SLF4J 버전 dependency check |
actually-implemented (2026-05-29: no_inheritable_thread_local rule + InheritableThreadLocalFixture 위반 catch 검증. SLF4J 2.0+ 버전 확인은 별도 supply-chain branch.) |
| (B7) outbound HTTP adapter 의 응답 → domain 변환 mapper 가 ACL 책임 (normalization / masking / public field selection) 을 inbound mapper 와 동일하게 적용하는지 | outbound 응답이 domain 으로 raw leak 가능 | ArchUnit rule (outbound adapter RestClient / WebClient 반환 타입 = ACL mapper 통과 후 domain type 만) + integration test (외부 응답 raw 가 domain object 에 그대로 leak 되지 않음) |
actually-implemented (2026-05-29: WeatherSummary (domain) + WeatherForecastPort (application) + RawWeatherResponse (package-private, adapter-only) + WeatherForecastAclMapper + WeatherForecastClient + WeatherForecastClientTest 3 케이스. 부수 효과로 outbound_adapter_does_not_depend_on_web_or_persistence_adapters ArchUnit 규칙이 MappingException 의 잘못된 위치를 catch — application.exception 으로 이전.) |
(B8) Bulk endpoint 의 response 가 success: false + error.code = BATCH_PARTIAL_FAILURE + error.details[] 항목별 결과 shape 을 따르는지 |
단일 항목 endpoint 와 schema 혼동 risk | bulk endpoint contract test (전체 성공 / 전체 실패 / 부분 실패 3 케이스) + OpenAPI shape 분기 검증 | actually-implemented (2026-05-29 3차 패스: BoundaryDemoControllerWireTest 의 3 bulk 케이스 (b8_all_success, b8_partial_failure, b8_all_failures_take_the_same_partial_branch) 가 POST /demo/boundary/bulk 로 wire-level 검증. OpenAPI 분기는 스펙 자체가 부재라 별도.) |
(B8) BATCH_PARTIAL_FAILURE 신규 code 가 canonical SSOT §6 에 등록되었는지 |
code 누락 시 envelope 일관성 깨짐 | raw/project-notes/ca-skeleton-operational-contract §6 갱신 PR 검증 | actually-implemented (2026-05-29 §6 등록 완료) |
구현 결과
후속 audit (
<project>/docs/superpowers/specs/2026-05-29-module-placement-audit-report.md) 에서 이전 4개 브랜치가 만든 skeleton-wide 운영 계약이 sample-portfolio 에만 구현되어 실행 앱(app-bootstrap)에서 누락되는 High 결함(Finding 1)을 발견. app-bootstrap 은 sample-portfolio 을 런타임 의존하지 않으므로(testImplementationonly) fork 후 sample 삭제 시 envelope/error 계약이 통째로 사라짐. 이를 production 모듈로 승격하는 리팩터를 TDD + subagent-driven 으로 수행.
승격 내역 (동작 보존, 패키지/모듈 이동 중심)
- shared-contract (stdlib-only):
error/ApiErrorCode인터페이스 신설(code/httpStatus(int)/retryable — SpringHttpStatus대신 전송중립 int 로 stdlib 제약 충족) +error/OperationalErrorenum(운영/전송/보안 코드) +error/MappingException이전 +response/BulkEnvelope·BulkItemResult이전(OperationalError.BATCH_PARTIAL_FAILURE사용). - adapter-web:
error/GlobalExceptionHandler(base @RestControllerAdvice, 운영/전송/보안/framework 예외만) +error/ErrorResponseFactory(int→HttpStatus.valueOf+ MDC traceId, envelope 빌드 단일 지점) +envelope/EnvelopeBodyAdvice이전 +config/JacksonNullableConfig이전(+jackson-databind-nullable의존). - sample-portfolio:
ApiErrorCodeenum →SampleErrorCode(도메인 코드만, shared 인터페이스 구현) +DomainExceptionHandler(도메인 예외 전용 advice, base 와 Spring 합성). 기존 단일GlobalExceptionHandler(운영+도메인 혼재) 삭제. - app-bootstrap: 코드 변경 0.
OperationalContractRuntimeTest(@WebMvcTest,CaSkeletonApplication앵커 + raw probe) 신설 — 실행 컨텍스트에 advice/handler 빈 존재 + raw body 가 실제로 wrap 됨을 pin → Finding 1 회귀 방지. - 문서:
shared-contract/CLAUDE.md신설(부재했음),adapter-web/CLAUDE.md의 "handler 가 sample 에 있다" 구절을 "production 모듈로 승격됨"으로 갱신.
검증
- 9 Task TDD, task 마다
./gradlew test verifyCleanArchitectureDependenciesgreen. 최종./gradlew clean test verifyCleanArchitectureDependencies→ BUILD SUCCESSFUL, 119 tests / 0 failures. - ArchUnit 24 규칙 + violation fixture 전부 green. shared-contract Spring/Jackson/JPA import 0 (grep 확인).
production_code_does_not_depend_on_sample_portfoliogreen. - 최종 리뷰: ca-architect-sentinel PASS, ca-quality-reviewer 의 Important 2건(BulkEnvelope double-wrap 분기 미테스트 / DomainExceptionHandler 라우팅 미테스트) 보강 테스트 추가 후 green, minor(stale Javadoc,
.toList()일관화, deadINTEGRITY_VIOLATION제거, inline FQN→import) 처리.
잔여 / 후속
- 커밋은 사용자가 일괄 수행 예정(현재 working tree 미커밋). 본 5차 패스는
feature/boundary-validation-mapping-contract브랜치 작업 트리에 존재. - sub-project B: sample 도메인을 포트폴리오 게시판으로 교체 + 모듈 rename — 별도 spec/plan 예정.
- 설계/계획 문서:
<project>/docs/superpowers/specs/2026-05-29-operational-contract-promotion-design.md,<project>/docs/superpowers/plans/2026-05-29-operational-contract-promotion.md(repo/docsgitignore 로 untracked).
구현 결과
감사 Finding 4(sample 도메인·데모 비일관) 해소. sample 모듈을 사용자의 엔지니어링 작업물을 보여주는 포트폴리오 게시판(WorkLog) 으로 교체하고, production 모듈 경계를 거울처럼 보여주는 adapter-mirrored 레이아웃으로 정리. production 모듈·ArchUnit 본체는 불변(glob/매트릭스 키만 rename).
Phase B-1 — rename + restructure (동작 보존)
sample-portfolio→sample-portfolio, 패키지dev.caskeleton.sample.portfolio→dev.caskeleton.sample.portfolio. settings.gradle /verifyCleanArchitectureDependencies매트릭스 키 / app-bootstraptestImplementation/ ArchUnitproduction_code_does_not_depend_on_sample_portfolioglob(..sample.portfolio..→..sample.portfolio..) 전부 갱신. (glob 미갱신 시 vacuous-pass → production→sample 미탐지, 계약 보존 필수 포인트.)- 절반-마이그레이션 빈
.gitkeepanchor(domain/model, application/usecase/port/in 등) 제거. 모듈 CLAUDE.md(adapter-web/app-bootstrap/domain-core)의 stalecom.example.blog.*→dev.caskeleton.*교정.
Phase B-2 — WorkLog 도메인 (adapter-mirrored)
- domain/worklog:
WorkLog(POJO 엔티티),WorkCategory(INFRASTRUCTURE/DATABASE/BACKEND/PLATFORM),Period(vo),RepoStats(vo),WorkLogRepository(port). - application/worklog:
Create/Update/Delete/Get/ListWorkLogsUseCase+GetRepoStatsUseCase— sample에서 처음으로 application-port-usecase 계약 실증(CommandUseCase/QueryUseCase+@UseCaseCapability+TransactionPort,@Transactional미사용). command/query/exception 분리. - adapter/web:
WorkLogController(목록=메인화면 + CRUD + bulk import + repo-stats), DTO(B4@GroupSequence, B2JsonNullable→Patch, B1 unknown-field),WorkLogWebMapper(B3MappingException),PortfolioErrorCode,DomainExceptionHandler(@Order(HIGHEST_PRECEDENCE)— base catch-all보다 앞서야 도메인 예외가 INTERNAL로 안 빨려듦). - adapter/persistence:
WorkLogEntity(@ElementCollection LAZY),WorkLogJpaRepository,WorkLogRepositoryAdapter(page 기반),WorkLogPersistenceMapper. - adapter/outbound/repostats: B7 ACL(
RawRepoStatsResponsepackage-private +RepoStatsAclMappernormalization/masking +RepoStatsPortClient) — weather 대체,GetRepoStatsUseCase로 실제 소비(orphan 아님). - B1/B2/B3/B4/B8 계약을 WorkLog 엔드포인트로 re-home, B5(polymorphic)는
SamplePolymorphicRequestTest단위테스트로 유지, B6(virtual-thread MDC)는 self-contained probe로 재배치. User/Post/BoundaryDemo/weather 전체 제거. - README(
src/sample-portfolio/README.md) + 루트 README/CLAUDE.md/AGENTS.md의sample-portfolio→sample-portfolio갱신. 시드 2건(Keycloak+k3s+Vault 인증위임 / DB 쿼리튜닝)은 README curl 예시.
검증 / 리뷰
- subagent-driven 9 Task, 단계마다 green. 최종
./gradlew clean test verifyCleanArchitectureDependencies→ BUILD SUCCESSFUL, 126 tests / 0 failures. ArchUnit 24규칙 + 19 픽스처 green(use-case 규칙이 이제 WorkLog로 실제 검증). - 부수 발견:
src/build.gradle에-parameters컴파일 플래그 누락(Spring@PathVariable/@RequestParam이름 해석 실패) → 프로젝트 전역 추가. - 최종 리뷰: ca-architect-sentinel PASS(URI-check/bulk-branching은 boundary/transport, 위반 아님), ca-quality-reviewer Important 5건(findAll offset→page 버그, bulk catch granularity, findAll 테스트 공백, PATCH @Valid+explicit-null 미테스트+dead @Size, RepoStatsPort dead code) 보강 후 green.
잔여
- 커밋은 사용자가 A+B 일괄 수행 예정(working tree 미커밋).
@Version낙관적 락 / 실 WebClient+WireMock / @DataJpaTest 통합 /@MockBean→@MockitoBean는 후속.- 설계/계획:
<project>/docs/superpowers/specs/2026-05-29-sample-portfolio-domain-design.md,<project>/docs/superpowers/plans/2026-05-29-sample-portfolio-domain.md.
엣지·실패·의존
본 branch 가 의존하거나 깨질 수 있는 경계 조건. 상세 검증 항목은 §Claims To Verify, 운영 영향은 §구현 가이드 §4 참조.
- Edge: PATCH 의 absent vs explicit-null vs value 3-state — 구분 실패 시 silent overwrite (B2). bulk endpoint 의 전체 실패도 부분 실패와 동일
BATCH_PARTIAL_FAILURE(HTTP 200) branch 를 타며, 분기는envelope.success로만 (B8). - Failure mode: ① mapper-internal 예외가
MappingExceptionwrap 누락 시INTERNAL_ERROR로 새어 분류 오류 (B3). ② virtual thread 환경에서ThreadLocal/MDC context 가 application layer 까지 propagate 안 되면 traceId 유실 (B6). ③ ArchUnit 정적 강제는 바이트코드 carrier(어노테이션/import/호출)만 탐지 — 메서드 본문 free-form 문자열은 한계. - Dependency:
MAPPING_FAILED/BATCH_PARTIAL_FAILURE신규 code 는 canonical SSOT raw/project-notes/ca-skeleton-operational-contract §6 등록에 의존 (등록 완료). package convention glob 은 §20 Skeleton Blueprint 에 의존. B9 ArchUnit rule 은 raw/branch-notes/feature-resource-identifier-contract D17 을 cross-cite.
관련 일일 노트
- (해당 enforcement 패스에서 단독 daily-note 추출 없음. 구현 진행은 §"구현 결과" 5/6차 패스 + §"마주친 문제" 에 직접 기록.)
마주친 문제
- 2026-05-29 (a):
JacksonDeserializationPolicyTest첫 컴파일 시com.fasterxml.jackson.databind.DeserializationFeature가 app-bootstrap 의 test classpath 에 없어 컴파일 실패. app-bootstrap 의 mainspring-boot-starter는 jackson 을 transitive 로 가져오지 않고, rootsubprojects { ... testImplementation 'spring-boot-starter-test' }도 jackson-databind 를 guarantee 하지 않음.testImplementation 'org.springframework.boot:spring-boot-starter-json'추가로 해소. 1회성 환경 정렬이므로 별도raw/errors/등재는 생략. - 2026-05-29 (c): B8 응답 타입 promotion.
BulkEnvelope<T>+BulkItemResult를sample.portfolio.adapter.web.dto.response에서 stdlib-onlyshared-contract의dev.caskeleton.shared.response로 이전 (기존Envelope/ApiError옆).BulkEnvelope.partial(...)은 Task 1 에서 추가된dev.caskeleton.shared.error.OperationalError.BATCH_PARTIAL_FAILURE의.code()/.retryable()를 사용해 하드코딩 문자열을 제거. shared-contract 는 Spring/Jackson 미의존 — 두 타입 모두java.util.List+ 공유ApiError만 쓰는 plain record 라 제약 충족. 소비자 import 갱신:EnvelopeBodyAdvice,BoundaryDemoController, 그리고 same-package resolution 에 의존하던BulkEnvelopeTest(명시 import 2 줄 추가). 동작 동일 — 패키지 이동만. 회귀 게이트:./gradlew test verifyCleanArchitectureDependencies→ BUILD SUCCESSFUL, 112 tests / 0 failures / 0 errors (BulkEnvelopeTest 3 케이스 포함). 단순 이전이라 별도raw/errors/등재 불요. - 2026-05-29 (b): B7 outbound ACL 참조 추가 후
outbound_adapter_does_not_depend_on_web_or_persistence_adaptersArchUnit 규칙이 fail. 원인:MappingException이sample.portfolio.adapter.web.error패키지에 있어WeatherForecastAclMapper(outbound) 가 web 에 의존하게 됨. fitness function 이 dependency-direction 회귀를 정확히 catch 한 사례. 해소:MappingException을sample.portfolio.application.exception으로 이전 (다른 application exception 들과 같은 위치). adapter-web 의GlobalExceptionHandler와 outbound adapter 의 ACL mapper 모두 application 패키지에 의존하므로 의존성 방향이 다시 맞아 떨어진다.
완료 후 정리
- PR 링크:
- 리뷰 메모:
- 머지 결과 / 배포 환경:
- wiki 추출 대상 (verified만,
wiki/projects/로만 추출):actually-implemented항목:- B1 정적 차단 (ArchUnit
request_dtos_do_not_silence_unknown_fields+ violation fixture) + wire-level (BoundaryDemoControllerWireTest#b1_unknown_json_field_is_rejected_via_envelope) - B2 PATCH 3-state + wire-level (
BoundaryDemoControllerWireTest#b2_patch_field_absent_is_distinguished_from_explicit_null_and_value3 케이스) + 기존UpdateProfileRequest/UpdateProfileCommand/UserService마이그레이션 완료 - B3
MappingException→MAPPING_FAILEDwire-level (BoundaryDemoControllerWireTest#b3_mapping_exception_surfaces_as_mapping_failed_envelope+ unit) - B4
@GroupSequenceshort-circuit + wire-level (BoundaryDemoControllerWireTest의 b4 3 케이스) - B5 Jackson default typing /
LaissezFaireSubTypeValidator차단 (ArchUnit + violation fixture, CVE-2019-14379 대응) - B5 sealed type +
@JsonTypeInfo/@JsonSubTypes패턴 (unitSamplePolymorphicRequestTest+ wireBoundaryDemoControllerWireTest의 b5 2 케이스) - B6
InheritableThreadLocal차단 (ArchUnit + violation fixture) - B6 virtual thread MDC propagation (
VirtualThreadMdcPropagationTestunit +VirtualThreadMdcE2ETest실 Tomcat + 실 가상스레드 + 실RequestLoggingFilter2 케이스) - B7 outbound ACL mapper (Weather adapter +
WeatherForecastClientTest) - B8 bulk envelope (unit
BulkEnvelopeTest3 케이스 + wireBoundaryDemoControllerWireTestb8 3 케이스) - D5 RFC 7807 거부 완료:
GlobalExceptionHandler가ProblemDetailimport 완전 제거 +Envelope<Void>반환. shared-contract 의 skeleton-wideEnvelope<T>/ApiError타입 신설. - success/error 대칭:
EnvelopeBodyAdvice가 모든 controller success 응답을Envelope.ok(...)로 자동 wrap.
- B1 정적 차단 (ArchUnit
locally-verified항목:- B1 Jackson 4-종 deserialization 스위치 (
JacksonDeserializationPolicyTest)
- B1 Jackson 4-종 deserialization 스위치 (
prod-verified항목: (해당 없음 — 본 패스는 enforcement + reference + unit/contract + wire-level + e2e 단계, prod 트래픽 검증 미수행)
- 추출하지 않을 항목 (planned / documented-only / abandoned):
- controller 반환 타입의 정적 ArchUnit rule —
planned(현 패스는EnvelopeBodyAdvice자동 wrap 으로 우회). - B4-2
@Validcascade depth ≤ 3 동적 ArchUnit —planned(nested DTO sample 부재). - B7-2 실
WebClient/RestClient+ WireMock 통합 —planned(현 패스는 HTTP fetch 추상화). - B8-2 OpenAPI shape 분기 명시 —
planned(OpenAPI 스펙 부재). MAPPING_FAILED/BATCH_PARTIAL_FAILURE의 canonical SSOT raw/project-notes/ca-skeleton-operational-contract §6 등재 (별도 envelope SSOT 갱신 PR 책임)
- controller 반환 타입의 정적 ArchUnit rule —