Files
document-haness/docs/clean-architecture-backend-template/final/evidence/raw/194-inbound-web-idempotency-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

133 lines
12 KiB
Plaintext

Sub-scope 05 (inbound-web) idempotency + operation + operationasync + evidence
revision=a24ece9cf797f7ea647e33bf846b115208ed1ba5
generatedAt=2026-08-30T07:26:46+00:00
=== OWNED FILES ===
09b8372242c82d595ee2f9c3666ca578b676fe27 main/java/dev/caskeleton/adapter/inbound/web/evidence/WebApplicationEvidence.java 44
2b2d95ea7c6b1c08efa9daa27ea35dd2ce7f8bfe main/java/dev/caskeleton/adapter/inbound/web/evidence/WebEvidenceAxis.java 15
9d50db0ade99d79433193c095679d079fdb34770 main/java/dev/caskeleton/adapter/inbound/web/evidence/WebExecutionEvidence.java 57
3a6dc3e2292e7ea77f973bd77a68c3461fbe6a48 main/java/dev/caskeleton/adapter/inbound/web/evidence/WebExecutionEvidenceTracker.java 125
ea8eed7e43c59f73c57c29219e59977680a52dcc main/java/dev/caskeleton/adapter/inbound/web/evidence/WebRequestPhase.java 44
269ab58d05e149e802c6d1f1ba8965960b8804f7 main/java/dev/caskeleton/adapter/inbound/web/evidence/WebResponseEvidence.java 46
401ffe547bfb75201bdcb493ec2ad1b31bbde4fe main/java/dev/caskeleton/adapter/inbound/web/idempotency/DeterministicCommandEncoder.java 74
e97ca5b43539cc0cbab27197a30f15cdfb41b6be main/java/dev/caskeleton/adapter/inbound/web/idempotency/FingerprintHeaderPolicy.java 102
6bc27398702703e6296473ce7d5fdbf7a382bc25 main/java/dev/caskeleton/adapter/inbound/web/idempotency/IdempotencyAdmission.java 85
02e93a8b2b9bf830369021194517d05c8f90383e main/java/dev/caskeleton/adapter/inbound/web/idempotency/IdempotencyKey.java 33
0dc1a346d1d8d51249f39a1069ecea0d1f669864 main/java/dev/caskeleton/adapter/inbound/web/idempotency/IdempotencyKeySupport.java 66
3daf9dbddd62b03584f1e3d675eae8253b6a3b03 main/java/dev/caskeleton/adapter/inbound/web/idempotency/IdempotencyResponsePlan.java 97
e6563e02b7814ec4374cb66a67d2ea756672fa5c main/java/dev/caskeleton/adapter/inbound/web/idempotency/JsonIdempotentResponseCodec.java 44
bcdecedf0ae006aceb630d4377fc020ac3bf7598 main/java/dev/caskeleton/adapter/inbound/web/idempotency/SemanticRequestFingerprintFactory.java 94
d4c5c53c1ef4609e56b1c2057ae673920512c83c main/java/dev/caskeleton/adapter/inbound/web/idempotency/WebIdempotencyGate.java 180
7a0ba511685fb7086e06f62eb8aa79ca139ccd0a main/java/dev/caskeleton/adapter/inbound/web/mvc/idempotency/IdempotentResponseWriter.java 84
aceb3928ef9d8321e1c130a8ee0a4536bbe7d43e main/java/dev/caskeleton/adapter/inbound/web/mvc/idempotency/WebMvcIdempotentInvoker.java 108
0f5c4dfde6c99cf4e5f762f1cd962a07465e26fd main/java/dev/caskeleton/adapter/inbound/web/mvc/operation/OperationHttpController.java 105
f333b4eae4f8169f71a6b1e46088f199c416ceef main/java/dev/caskeleton/adapter/inbound/web/operation/AdmissionProfileName.java 35
6a9b1ebfadd3b7fad907d6b4d3849af2229e83cb main/java/dev/caskeleton/adapter/inbound/web/operation/AuthorizationProfileName.java 35
d9715d2f988d62a1630000dad36f7f9e52a989c1 main/java/dev/caskeleton/adapter/inbound/web/operation/CachePolicyName.java 35
1d36292c292382371d5ebed1b32b0b1fe619592c main/java/dev/caskeleton/adapter/inbound/web/operation/HttpMethodSemantic.java 54
3a0f6f5784d856454b7b5ff8778f07db12fcaf8f main/java/dev/caskeleton/adapter/inbound/web/operation/IdempotencyPolicy.java 20
dc03e5d24fa694498053cc7ce988182507d761cc main/java/dev/caskeleton/adapter/inbound/web/operation/InMemoryWebOperationCatalog.java 61
826c260984a2d3de253cb01aaf3ed82cd64528c2 main/java/dev/caskeleton/adapter/inbound/web/operation/MutationKind.java 38
916ad6adb9ae107737789fb43efd472ba56852da main/java/dev/caskeleton/adapter/inbound/web/operation/PreconditionPolicy.java 21
1c17d83ecbe49279203ac45eb1e16489c879bf47 main/java/dev/caskeleton/adapter/inbound/web/operation/ResponseProfileName.java 35
53572651a5b29d37b9a6994ab206496b3a2f4ce9 main/java/dev/caskeleton/adapter/inbound/web/operation/WebOperationCatalog.java 28
225984be514179238b0740670f724ee6ce662a9a main/java/dev/caskeleton/adapter/inbound/web/operation/WebOperationProfile.java 119
1bc8022b2b9b08660c06c66b29c7c3f28c2791ba main/java/dev/caskeleton/adapter/inbound/web/operationasync/OperationAccessPolicy.java 42
ee641d0b4f594fb8cc82b4f7886d834153c76b37 main/java/dev/caskeleton/adapter/inbound/web/operationasync/OperationId.java 29
1c2a0d2dc8569e4af92ccd7a1fe9de8368ab54d8 main/java/dev/caskeleton/adapter/inbound/web/operationasync/OperationProgress.java 50
ba9351e26f8dbf3f3f8d1d8cd81fcc49bf48aece main/java/dev/caskeleton/adapter/inbound/web/operationasync/OperationQueryService.java 74
37a69e38dd86724c908e5e347bf29856b453f45c main/java/dev/caskeleton/adapter/inbound/web/operationasync/OperationResource.java 159
441aaca5389c99c568f16eefe7eeda13066695d8 main/java/dev/caskeleton/adapter/inbound/web/operationasync/OperationResourceFactory.java 112
c3c340fe86f9f816711d42988ddb8b8cba101484 main/java/dev/caskeleton/adapter/inbound/web/operationasync/OperationResponse.java 56
00453ac2722e568d28d4e33d50db2fdb99dfc1e4 main/java/dev/caskeleton/adapter/inbound/web/operationasync/OperationResponseMapper.java 43
fd905407b7c0ca864c80eccc3f7d6e988e081b18 main/java/dev/caskeleton/adapter/inbound/web/operationasync/OperationStatus.java 40
8129f0895798453076bd9af97440617c9f412e20 main/java/dev/caskeleton/adapter/inbound/web/webflux/idempotency/WebFluxIdempotentInvoker.java 168
23ad8ee4b0a4113872e15374b44ad2efdda0ef42 main/java/dev/caskeleton/adapter/inbound/web/webflux/operation/ReactiveOperationHttpController.java 106
9dc9f2412a5d6287533e0b85cb49073131a92c57 test/java/dev/caskeleton/adapter/inbound/web/evidence/WebExecutionEvidenceTrackerTest.java 156
b531bd2a9f21c6c92c66f3e903a462cbdfaa8bf3 test/java/dev/caskeleton/adapter/inbound/web/idempotency/IdempotencyKeySupportTest.java 80
6b41124404aa453a69157860a914a4a2bdb89398 test/java/dev/caskeleton/adapter/inbound/web/idempotency/InMemoryIdempotencyStore.java 70
03ddbb79c4c27e9923a5fa30916443b42e4a73a7 test/java/dev/caskeleton/adapter/inbound/web/idempotency/SemanticRequestFingerprintFactoryTest.java 140
bd2825015460b787bdba7bdc463d38c1e4f33778 test/java/dev/caskeleton/adapter/inbound/web/idempotency/WebIdempotencyGateTest.java 343
2636c7fb45114d566c58c720fe82a3879b9aa433 test/java/dev/caskeleton/adapter/inbound/web/mvc/idempotency/WebMvcIdempotentInvokerTest.java 288
93b8a0dcf5ac710ffaa337c7dafe7efdf0c0c06a test/java/dev/caskeleton/adapter/inbound/web/operation/WebOperationCatalogTest.java 141
787e4a5fbd4b4bc4863e92a590e035ca9668300a test/java/dev/caskeleton/adapter/inbound/web/operationasync/OperationAccessPolicyTest.java 122
5ea653c8698c2aab6095b1ac45b41a3a3abdcc04 test/java/dev/caskeleton/adapter/inbound/web/operationasync/OperationResourceTest.java 230
aa70c62a7d7441f545e241e4c48954658731af97 test/java/dev/caskeleton/adapter/inbound/web/webflux/idempotency/WebFluxIdempotentInvokerTest.java 268
file_count=50
=== 8.1 도달성: 멱등성 실행 계층과 durable-operation 표면 ===
WebIdempotencyGate 5 test 2 testkit
WebMvcIdempotentInvoker 1 test 1 testkit
WebFluxIdempotentInvoker 1 test 1 testkit
IdempotentResponseWriter 1 test 1 testkit
SemanticRequestFingerprintFactory 6 test 2 testkit
OperationQueryService 1 testkit
OperationResourceFactory 1 testkit
WebExecutionEvidenceTracker 1 main
$ grep -rn 'inbound.web.idempotency\|WebIdempotencyGate\|IdempotencyKeySupport' --include=*.java app-bootstrap/src/main | wc -l
0
exit=0
$ grep -rn 'IdempotencyKeySupport' --include=*.java adapter/inbound/web/src/main sample-portfolio/src/main | grep -v IdempotencyKeySupport.java | wc -l
0
exit=0
=== 8.2 durable-operation 컨트롤러의 게이트와 의존 ===
$ grep -n 'ConditionalOnProperty' -A 4 adapter/inbound/web/src/main/java/dev/caskeleton/adapter/inbound/web/mvc/operation/OperationHttpController.java | tail -5
35:@ConditionalOnProperty(
36- prefix = "app.web-platform.durable-operations",
37- name = "enabled",
38- havingValue = "true")
39-@RequestMapping(OperationHttpController.BASE_PATH)
exit=0
$ grep -rn 'app.web-platform.durable-operations' --include=*.yml --include=*.yaml --include=*.java . | sed 's|adapter/inbound/web/src/||'
./main/java/dev/caskeleton/adapter/inbound/web/mvc/operation/OperationHttpController.java:36: prefix = "app.web-platform.durable-operations",
./main/java/dev/caskeleton/adapter/inbound/web/webflux/operation/ReactiveOperationHttpController.java:37: prefix = "app.web-platform.durable-operations",
exit=0
$ grep -rn 'OperationQueryService' --include=*.java adapter/inbound/web/src/main app-bootstrap/src/main | grep -v 'OperationQueryService.java' | sed 's|.*/web/||'
mvc/operation/OperationHttpController.java:6:import dev.caskeleton.adapter.inbound.web.operationasync.OperationQueryService;
mvc/operation/OperationHttpController.java:45: private final OperationQueryService operations;
mvc/operation/OperationHttpController.java:52: public OperationHttpController(OperationQueryService operations) {
webflux/operation/ReactiveOperationHttpController.java:6:import dev.caskeleton.adapter.inbound.web.operationasync.OperationQueryService;
webflux/operation/ReactiveOperationHttpController.java:28: * {@link OperationQueryService}, which both call. What differs is only what has to: the store is
webflux/operation/ReactiveOperationHttpController.java:46: private final OperationQueryService operations;
webflux/operation/ReactiveOperationHttpController.java:56: OperationQueryService operations, Scheduler blockingScheduler) {
exit=0
=== 8.3 WebOperationCatalog 를 읽는 쪽 ===
$ grep -rn 'WebOperationCatalog' --include=*.java adapter/inbound/web/src/main app-bootstrap/src/main | grep -v 'operation/WebOperationCatalog.java\|InMemoryWebOperationCatalog.java' | sed 's|.*/web/||'
mvc/autoconfigure/WebMvcPlatformAutoConfiguration.java:17:import dev.caskeleton.adapter.inbound.web.operation.InMemoryWebOperationCatalog;
mvc/autoconfigure/WebMvcPlatformAutoConfiguration.java:18:import dev.caskeleton.adapter.inbound.web.operation.WebOperationCatalog;
mvc/autoconfigure/WebMvcPlatformAutoConfiguration.java:58: @ConditionalOnMissingBean(WebOperationCatalog.class)
mvc/autoconfigure/WebMvcPlatformAutoConfiguration.java:59: public InMemoryWebOperationCatalog webOperationCatalog() {
mvc/autoconfigure/WebMvcPlatformAutoConfiguration.java:60: return new InMemoryWebOperationCatalog();
webflux/autoconfigure/WebFluxPlatformAutoConfiguration.java:13:import dev.caskeleton.adapter.inbound.web.operation.InMemoryWebOperationCatalog;
webflux/autoconfigure/WebFluxPlatformAutoConfiguration.java:14:import dev.caskeleton.adapter.inbound.web.operation.WebOperationCatalog;
webflux/autoconfigure/WebFluxPlatformAutoConfiguration.java:76: @ConditionalOnMissingBean(WebOperationCatalog.class)
webflux/autoconfigure/WebFluxPlatformAutoConfiguration.java:77: public InMemoryWebOperationCatalog webOperationCatalog() {
webflux/autoconfigure/WebFluxPlatformAutoConfiguration.java:78: return new InMemoryWebOperationCatalog();
admin/route/WebRouteInventory.java:4:import dev.caskeleton.adapter.inbound.web.operation.WebOperationCatalog;
admin/route/WebRouteInventory.java:50: public void requireRegisteredOperations(WebOperationCatalog catalog) {
advanced/functional/FunctionalRoutePolicyValidator.java:3:import dev.caskeleton.adapter.inbound.web.operation.WebOperationCatalog;
advanced/functional/FunctionalRoutePolicyValidator.java:22: private final WebOperationCatalog catalog;
advanced/functional/FunctionalRoutePolicyValidator.java:24: public FunctionalRoutePolicyValidator(WebOperationCatalog catalog) {
advanced/functional/FunctionalRouteRegistry.java:3:import dev.caskeleton.adapter.inbound.web.operation.WebOperationCatalog;
advanced/functional/FunctionalRouteRegistry.java:25: public FunctionalRouteRegistry(WebOperationCatalog catalog) {
exit=0
=== 8.4 fingerprint 정규화 형식 ===
$ grep -n 'SEPARATOR\|canonical.append' adapter/inbound/web/src/main/java/dev/caskeleton/adapter/inbound/web/idempotency/SemanticRequestFingerprintFactory.java
36: private static final char SEPARATOR = '\u001f';
68: canonical.append(operation.value());
71: (name, value) -> canonical.append(SEPARATOR).append(name).append('=').append(value));
72: canonical.append(SEPARATOR).append(encoder.canonicalize(command));
76: (name, value) -> canonical.append(SEPARATOR).append(name).append(':').append(value));
exit=0
$ grep -rn 'ByteBuffer.allocate(Integer.BYTES)\|putInt(encoded.length)' --include=*.java adapter | wc -l
17
exit=0