Files
document-haness/docs/clean-architecture-backend-template/final/evidence/raw/190-inbound-web-error-probes.txt
T
DongHyeonkaandClaude Opus 5 b2963105a8 docs(keycloak-session-store): import the session-storage lab as a new project
The keycloak project ended with four open questions that design could not
settle. A two-VM lab was built to answer them by measurement, and this is
that material: 26 experiments, 125 raw command outputs, 22 browser captures.

Follows the import procedure in README.md.

  source/     the originating repository verbatim — 78 documents, 28 SVGs,
              8 manifests, plus .source-revision recording the commit
  final/      the SSOT
    document.md   729 lines written from the 29 experiment documents, not
                  concatenated: what was predicted, what was measured, and
                  where the measurement itself was wrong
    evidence/raw    125 outputs, flattened to <experiment>__<file> because
                    the originals collided (01-baseline.txt appeared three
                    times) and the audit only globs the top level
    evidence/meta   one per raw file; command and exitCode are null and the
                    README says why rather than inventing them
    evidence/browser  22 captures
    assets/       three diagrams through techviz
    .techviz/     their VizSpecs

A separate project rather than an addition to keycloak: the B-layer answers
that project's four questions, but the A, C and D layers are about cluster
failure, SSO and operations, and one document.md should hold one subject.
The four question records there can point here through 관계.

Recorded rather than papered over: only three of the 28 diagrams were
remade. The repository forbids hand-drawn SVG and forbids titles inside the
canvas; all 28 originals carry both, so converting them is redrawing, not
reformatting. They stay in source/ and the gap is written into the document.

verify-pipeline.py passes. audit-records.py reports no issues.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 22:51:59 +09:00

157 lines
11 KiB
Plaintext

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