Sub-scope 02 (inbound-web) error + validation + envelope
revision=a24ece9cf797f7ea647e33bf846b115208ed1ba5
generatedAt=2026-08-30T07:15:49+00:00

=== OWNED FILES ===
61c06223ed4dfac5cb94b1788de2afb46b9b3e8e main/java/dev/caskeleton/adapter/inbound/web/envelope/EnvelopeBodyAdvice.java 50
fa09a2bb8f0f1146659853da743de1a3d88943ab main/java/dev/caskeleton/adapter/inbound/web/error/BudgetProblemMapper.java 76
75873269a7a0555d28ba6eabe7cac86180ede801 main/java/dev/caskeleton/adapter/inbound/web/error/ClientSafeErrorMessages.java 65
9507703d56db4f1b45f62a26341180b775dd7c3c main/java/dev/caskeleton/adapter/inbound/web/error/ClientSafeValidationDetails.java 130
542bcb7ab58d02e8450aa83cd9e01be3b7bcbb29 main/java/dev/caskeleton/adapter/inbound/web/error/ErrorResponseFactory.java 35
4d032215718fc9c3dc0c34038cbd6902e95efad4 main/java/dev/caskeleton/adapter/inbound/web/error/GlobalExceptionHandler.java 417
90a8aaa0bdfa27b86bb8f6833a046e9da3ef0190 main/java/dev/caskeleton/adapter/inbound/web/error/ProblemCatalog.java 183
56418a5e7509a0c0fa20d2be7f407b6eae4f67ef main/java/dev/caskeleton/adapter/inbound/web/error/ProblemCode.java 81
5b23ad283fe8c74e61aa0b79a46a8f4534bdca15 main/java/dev/caskeleton/adapter/inbound/web/error/ProblemDefinition.java 34
3a36c4f3abb4b1e797382ed2de037964b489888f main/java/dev/caskeleton/adapter/inbound/web/error/ProblemStatusMismatchException.java 28
f9b319637d88bcff57ea77fc5c06b8115b142c15 main/java/dev/caskeleton/adapter/inbound/web/error/SafeProblemDetailExtensions.java 53
c0c84b9e9af4caa715721bf77cd264fe68df8874 main/java/dev/caskeleton/adapter/inbound/web/error/ThrottleProblemWriter.java 78
1ee96422b2920792abc8d29b7777480f03c87b63 main/java/dev/caskeleton/adapter/inbound/web/error/ValidationIssue.java 33
b7df48bdb01fa677b72255fb33a98fd6da310c16 main/java/dev/caskeleton/adapter/inbound/web/error/WebProblem.java 55
37259f3aa1744258e00e0bedf7891dbfcfb877bf main/java/dev/caskeleton/adapter/inbound/web/error/WebProblemFactory.java 79
2ae0b9baf462bca6b25628e4bbcc9714d6910df5 main/java/dev/caskeleton/adapter/inbound/web/error/WebProblemSanitizer.java 85
d356abf4681c11dbd0280679ed6cf9463304fd7f main/java/dev/caskeleton/adapter/inbound/web/error/WebResourceNotVisibleException.java 37
740cd79b90c855d61f6ebab68161465d10f59849 main/java/dev/caskeleton/adapter/inbound/web/mvc/error/WebMvcProblemExceptionHandler.java 182
65ab7a8a369cd766d71c6f9acaf47dddc0b2056e main/java/dev/caskeleton/adapter/inbound/web/validation/TransportValidationException.java 48
dc30f22ba2988307d72587897843c544606ffe1f main/java/dev/caskeleton/adapter/inbound/web/validation/WebInputPointer.java 66
cdefe58978d3814bc33f85a57e7f82c459224457 main/java/dev/caskeleton/adapter/inbound/web/validation/WebValidationExceptionMapper.java 78
2ca258903c2a3a1916ebbde7617065164695774f main/java/dev/caskeleton/adapter/inbound/web/validation/WebValidationIssueMapper.java 95
e62332952fed5d25d76d901c628784201ffe8711 main/java/dev/caskeleton/adapter/inbound/web/webflux/error/WebFluxProblemExceptionHandler.java 173
ce2655c041e98d80f91e2fc75e3e34462b8539fa test/java/dev/caskeleton/adapter/inbound/web/envelope/EnvelopeBodyAdviceTest.java 88
629d85d8af89581560ce05a76ed770d6f6ed2e6d test/java/dev/caskeleton/adapter/inbound/web/envelope/EnvelopeMetaIntegrationTest.java 73
d6102747d9f6db20895d14d228e9189df77f21c5 test/java/dev/caskeleton/adapter/inbound/web/error/GlobalExceptionHandlerTest.java 628
d1b1169de7e8083a792ab84b7f1f979b2e217963 test/java/dev/caskeleton/adapter/inbound/web/error/IdempotencyExceptionMappingTest.java 54
e27f1e3d6cc396448b9e762d9ceeaf6a8133f51c test/java/dev/caskeleton/adapter/inbound/web/error/NoResourceFoundErrorHandlingTest.java 62
515d315fd2c6d86837f5fe3a65a160adadf4434b test/java/dev/caskeleton/adapter/inbound/web/error/ProblemCatalogTest.java 99
40616be170c3b0afcc9703234471ece3bf2feb0c test/java/dev/caskeleton/adapter/inbound/web/error/SpanErrorRecorderHookTest.java 102
17860c0f5b0640a11f2011d4612df32fb72396a9 test/java/dev/caskeleton/adapter/inbound/web/error/TransportErrorHandlingTest.java 149
58e1e3056f24a8a3a7fb8e9d20443c0a7f32d0c2 test/java/dev/caskeleton/adapter/inbound/web/error/WebProblemFactoryTest.java 122
7cca8a05a6864b353af491e6860deac8d76f82df test/java/dev/caskeleton/adapter/inbound/web/validation/WebValidationExceptionMapperTest.java 125
file_count=33

=== 8.1 두 에러 계약이 한 컨텍스트에 함께 등록되는가 ===
$ grep -n 'RestControllerAdvice' -B 2 -A 12 adapter/inbound/web/src/main/java/dev/caskeleton/adapter/inbound/web/error/GlobalExceptionHandler.java | head -20
38-import org.springframework.web.bind.MethodArgumentNotValidException;
39-import org.springframework.web.bind.annotation.ExceptionHandler;
40:import org.springframework.web.bind.annotation.RestControllerAdvice;
41-import org.springframework.web.context.request.WebRequest;
42-import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
43-import org.springframework.web.multipart.MaxUploadSizeExceededException;
44-import org.springframework.web.servlet.NoHandlerFoundException;
45-import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
46-import org.springframework.web.servlet.resource.NoResourceFoundException;
47-
48-/**
49- * Skeleton-wide base error → {@link Envelope} converter. Handles operational, transport, and
50- * security exceptions only; domain exceptions are handled by a separate
51: * {@code @RestControllerAdvice} in the consuming module. See README for the design rationale.
52- */
53:@RestControllerAdvice
54-public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
55-
56-  private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
57-
exit=0

$ grep -n '@RestControllerAdvice\|@Order\|@ConditionalOn' adapter/inbound/web/src/main/java/dev/caskeleton/adapter/inbound/web/mvc/error/WebMvcProblemExceptionHandler.java
41:@RestControllerAdvice
46:@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
51:// Not @ConditionalOnBean: that condition is only reliable inside an auto-configuration class, where
55:@ConditionalOnProperty(
60:@Order(Ordered.HIGHEST_PRECEDENCE + 10)
exit=0

$ grep -c '@ExceptionHandler' adapter/inbound/web/src/main/java/dev/caskeleton/adapter/inbound/web/error/GlobalExceptionHandler.java
17
exit=0

$ grep -n 'ExceptionHandler(' adapter/inbound/web/src/main/java/dev/caskeleton/adapter/inbound/web/mvc/error/WebMvcProblemExceptionHandler.java
71:  public WebMvcProblemExceptionHandler(WebProblemFactory problems) {
77:  @ExceptionHandler(MethodArgumentNotValidException.class)
99:  @ExceptionHandler(HttpMessageNotReadableException.class)
110:  @ExceptionHandler(HttpMediaTypeNotSupportedException.class)
121:  @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
147:  @ExceptionHandler(WebResourceNotVisibleException.class)
158:  @ExceptionHandler(NoResourceFoundException.class)
exit=0

$ grep -n 'protected ResponseEntity<Object> handle' adapter/inbound/web/src/main/java/dev/caskeleton/adapter/inbound/web/error/GlobalExceptionHandler.java
285:  protected ResponseEntity<Object> handleMethodArgumentNotValid(
301:  protected ResponseEntity<Object> handleHttpMessageNotReadable(
315:  protected ResponseEntity<Object> handleHttpRequestMethodNotSupported(
341:  protected ResponseEntity<Object> handleHttpMediaTypeNotSupported(
362:  protected ResponseEntity<Object> handleMaxUploadSizeExceededException(
377:  protected ResponseEntity<Object> handleHttpMediaTypeNotAcceptable(
398:  protected ResponseEntity<Object> handleNoHandlerFoundException(
404:  protected ResponseEntity<Object> handleNoResourceFoundException(
exit=0

=== 8.2 겹치는 예외 타입에서 어느 쪽이 이기는가 ===
$ grep -rn 'class GlobalExceptionHandler' adapter/inbound/web/src/main/java/dev/caskeleton/adapter/inbound/web/error/GlobalExceptionHandler.java
54:public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
exit=0

$ grep -rn 'implements Ordered\|@Order' adapter/inbound/web/src/main/java/dev/caskeleton/adapter/inbound/web/error/GlobalExceptionHandler.java | wc -l
0
exit=0

=== 8.3 문서가 무엇을 선언하는가 ===
$ grep -n 'D5' adapter/inbound/web/README.md
168:- **D5: RFC 7807 `ProblemDetail` 표현은 거부**하고 자체 `Envelope` 형식을 쓴다.
245:- **W3C `traceparent`(D5/D7/D4).** 유효한 인바운드 traceparent 가 있으면 채택해 그 `traceId`→MDC `trace_id`,
exit=0

$ grep -c 'problem+json\|RFC 9457\|ProblemCode' adapter/inbound/web/README.md adapter/inbound/web/CLAUDE.md
adapter/inbound/web/README.md:0
adapter/inbound/web/CLAUDE.md:0
exit=1

$ sed -n '92,94p' adapter/inbound/web/CLAUDE.md
Domain `@RestControllerAdvice` in a consuming module must be annotated
`@Order(Ordered.HIGHEST_PRECEDENCE)` (or otherwise ordered ahead of this
module's base `GlobalExceptionHandler`), because the base handler's catch-all
exit=0

=== 8.4 테스트가 두 advice 를 함께 세우는가 ===
$ grep -rln 'WebMvcProblemExceptionHandler' adapter/inbound/web/src/test adapter/inbound/web/src/testkit
adapter/inbound/web/src/testkit/java/dev/caskeleton/webtestkit/ContractFixtureApplication.java
exit=0

$ grep -n 'Import\|jsonPath' adapter/inbound/web/src/test/java/dev/caskeleton/adapter/inbound/web/error/NoResourceFoundErrorHandlingTest.java | head -14
7:import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
18:import org.springframework.context.annotation.Import;
28:@Import({
43:        .andExpect(jsonPath("$.success").value(false))
44:        .andExpect(jsonPath("$.error.code").value("ROUTE_NOT_FOUND"))
45:        .andExpect(jsonPath("$.error.category").value("NOT_FOUND"))
46:        .andExpect(jsonPath("$.error.retryable").value(false))
47:        .andExpect(jsonPath("$.error.message").value("Requested route was not found"))
exit=0

=== 8.4b 미도달 유틸 ===
$ grep -rn 'alreadySafe' --include=*.java adapter/inbound/web/src | wc -l
1
exit=0

$ grep -rn 'SafeProblemDetailExtensions' --include=*.java adapter/inbound/web/src/main | grep -v 'SafeProblemDetailExtensions.java' | wc -l
0
exit=0

$ grep -rn 'requireStatusAgreement' --include=*.java adapter/inbound/web/src/main | grep -v 'WebProblemFactory.java'
adapter/inbound/web/src/main/java/dev/caskeleton/adapter/inbound/web/error/BudgetProblemMapper.java:16: * with its own body would be caught by {@code requireStatusAgreement} only after shipping.
adapter/inbound/web/src/main/java/dev/caskeleton/adapter/inbound/web/error/BudgetProblemMapper.java:73:    problems.requireStatusAgreement(statusFor(violation), problem);
adapter/inbound/web/src/main/java/dev/caskeleton/adapter/inbound/web/budget/WebBudgetOutcome.java:8: * something different for two of them, and {@code requireStatusAgreement} refused the response —
adapter/inbound/web/src/main/java/dev/caskeleton/adapter/inbound/web/mvc/idempotency/IdempotentResponseWriter.java:68:    problems.requireStatusAgreement(plan.status(), problem);
adapter/inbound/web/src/main/java/dev/caskeleton/adapter/inbound/web/webflux/idempotency/WebFluxIdempotentInvoker.java:154:    problems.requireStatusAgreement(plan.status(), problem);
exit=0

