Files
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

200 lines
16 KiB
Plaintext

Sub-scope 03 (cache-redis) sdk/api/key + error + command + reactive — manifest and probes
revision=a24ece9cf797f7ea647e33bf846b115208ed1ba5
generatedAt=2026-08-30T04:41:02+00:00
=== OWNED FILES ===
7d962093375f0c36434d719a09907a754381fba3 main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/command/AdvancedOperationPermit.java 19
e4a4f7d278375104757f5ecd0a3c346d5ca7da31 main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/command/CommandAccess.java 17
131e7c9bbdd7a2e23f0d8d0957c72c37cb6c585d main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/command/CommandId.java 85
6112df8fa18da163230367f89313984aba55c367 main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/command/CommandSupport.java 17
360a583fbefc2bdd84587bb477ece62a5e20a2a5 main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/command/KeySpec.java 76
fe9a3abb85761461514966f8d1405ee531625cd8 main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/command/MultiKeyPermit.java 16
05028e3b1776e895906398640fb1dcbabedef274 main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/command/OperationBudget.java 54
c37cf3b690f18a08ada8832eab4cfe3994dd8148 main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/command/PersistentKeyPermit.java 16
4bf3808adfef86de7ef1052a3141a7c8fedeec4c main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/command/RedisCommandDescriptor.java 60
afb4b1bf976b42e9688efc76cea1aacded73d96c main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/command/RedisPermitVerifier.java 34
169daebddd2191ef5d74820b224aad5561ec9133 main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/command/RedisPolicyAuthority.java 34
81a060369f496b1d00cc107b8d299b0acef1052a main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/command/RedisRiskLevel.java 31
447e777029e7682f6db4560a3fad669d4ee7861c main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/command/TimeoutProfile.java 42
2cbe99146bf79f371b9e3a171645cf5efa168c02 main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/error/RedisAccessDeniedException.java 28
50d3fdc25bd971caa27a8491939a62b13210d1fa main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/error/RedisAmbiguousExecutionException.java 31
582d3dc835dee3ac3e3e1f82a79b1086dd335ab1 main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/error/RedisBusyException.java 28
d3e2640731f096877327f5b001786ee25eb911a4 main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/error/RedisCapabilityUnavailableException.java 29
4c40194c7e1f58b3c86c2d6b600e7cdb8ddc9121 main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/error/RedisCommandRejectedException.java 29
c4a8e084d5670092265d860b93a316004bccdf35 main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/error/RedisConnectionException.java 28
3fc7929765ff43d2f45c295e6dcc45b137595aea main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/error/RedisCrossSlotException.java 28
0572a35da76bd519b6a330bf6e7d18ed627e775f main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/error/RedisDataTypeMismatchException.java 29
a402d3b6c7370647521d0168e1f4e8eaaa3f7615 main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/error/RedisFailureMetadata.java 106
a96ed0bf7af82ec61fcb1b04d0728c54def23140 main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/error/RedisNoScriptException.java 28
d558d87666acd779b42692013bb760890f65b62b main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/error/RedisOperationException.java 58
11985da1f6c97dbeb55d7337c5677255ec990c50 main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/error/RedisRedirectionException.java 28
4f6cc4740449cc386983c7d561bbd50570783fb5 main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/error/RedisSerializationException.java 32
db98a87e30b031319274ca50008877b6d7531994 main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/error/RedisTimeoutException.java 28
3b16f5498a0845e2b229434dd97b7f60010f9162 main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/key/BitmapKey.java 17
eb8c5391da357f39ef13dfda4f62a1551bd551d2 main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/key/GeoKey.java 19
e73bac153261577fc9be192a7d88df4e8b932429 main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/key/HashKey.java 24
46d7ff5dfd5dc47c1e16264fe84bd07c79ba0751 main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/key/HyperLogLogKey.java 20
21f1498ac07bbdfabc99b57dbb809f46d1a9c226 main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/key/ListKey.java 20
c572cb9c1d6c4ddddfcb8d47fd0506c098094847 main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/key/QualifiedRedisKey.java 49
5dfaa3350559cff62d0ea587d964ed4d38dc9ee5 main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/key/RedisKeyName.java 15
f9ec9cd3ac14adcd50f1816fef01fa689ded8370 main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/key/RedisKeyRenderer.java 69
55a7b9d74b3c8e48e872de189f318ab475edad1a main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/key/RedisKeyRules.java 101
56c73270d657b105ffd1454671efad1cddfded65 main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/key/RedisNamespace.java 29
6203eeb20f8e52ef8fc25cd569f121fa67e4c113 main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/key/RedisSlotTag.java 17
3b6ab0a06a2b425f0b56c189722f70291fd58e60 main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/key/RedisTypedKey.java 26
73238fa9db52df2498392e599c5e92e2fd966e69 main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/key/SetKey.java 19
fd2d1444bca91ed05c71eb1cbb60a1fcf035a440 main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/key/SortedSetKey.java 20
9de8ed5736db8ea61abecbfc3a673afa4c889518 main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/key/StreamKey.java 20
ce887cd0141b876bdf057fc3e791271886e34aa1 main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/key/TypedRedisKeys.java 217
4a0371eb46afe4aa9bc3bf866bf456f86c00a1e1 main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/key/ValueKey.java 20
544c98b1e8070aceec7cb0ac72145a9738c57454 main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/reactive/ReactiveRedisBatchOperations.java 19
fb776ae0d021527e7d943c2caf2827ef7d254cb4 main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/reactive/ReactiveRedisBitFieldOperations.java 28
a58ae7b617c8a459906224680ff6571a08aeb76f main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/reactive/ReactiveRedisBitmapOperations.java 69
6168d8c02497f01f218aef7b96d24c3173f3a1bb main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/reactive/ReactiveRedisBlockingListOperations.java 48
e11b517f2a1921b5b40f5d79eee79b9eb3d41922 main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/reactive/ReactiveRedisBlockingStreamOperations.java 46
2f6a10d87deea0ac91617bb01c37f0229fc0b5e5 main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/reactive/ReactiveRedisGeoOperations.java 82
598e00d8481b8fe1aef4ce9253f6e4a7a167258c main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/reactive/ReactiveRedisHashFieldExpirationOperations.java 51
269ce5a7289e3e771e47c20e317929d0dffe6d17 main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/reactive/ReactiveRedisHashOperations.java 150
fe7a26f6e8c33e39b8bfde63fe4eed0ef1b689b4 main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/reactive/ReactiveRedisHyperLogLogOperations.java 42
682733bb7aea5fa65521d6ecda60256bb1e9428f main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/reactive/ReactiveRedisKeyOperations.java 133
f895b644161fbfbb40c29e107ab87fdbb1a51586 main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/reactive/ReactiveRedisListOperations.java 160
8f8c69eb989cfd1d1537e4729b939aa6dabeef7c main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/reactive/ReactiveRedisPubSubOperations.java 44
650a75218a60ae8a655551f86070b728601cea8b main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/reactive/ReactiveRedisSetOperations.java 168
1b61d246aa7b5cb154566e7765a0617d373b1052 main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/reactive/ReactiveRedisShardedPubSubOperations.java 29
26655ff49e817146815e436d1facaf97099d819a main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/reactive/ReactiveRedisSortedSetOperations.java 196
5443d0091ca147737ba0932d5ebbb8f25bc1efd9 main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/reactive/ReactiveRedisStreamOperations.java 202
621356218e9051ba5b09709f6cf57c40889369e5 main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/reactive/ReactiveRedisValueOperations.java 163
d5b25f5ff10a32318609756fb03006d76faa6982 test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/command/OperationBudgetTest.java 36
8576733068fdf68d13b00d8b54d0952bbbb65225 test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/key/RedisKeyRendererTest.java 64
5439821ee078d9eee8a0a327e529ae1ce5e1f517 test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/key/RedisKeyRulesTest.java 69
main: 61 test: 3
=== 8.1 도달 불가 검증 분기: requireIdentifier의 다섯 검사 ===
$ sed -n '22,30p;60,80p' adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/key/RedisKeyRules.java
private static final Pattern IDENTIFIER = Pattern.compile("^[A-Za-z0-9][A-Za-z0-9._~-]{0,127}$");
private static final Pattern JSON_WEB_TOKEN =
Pattern.compile("^[A-Za-z0-9_-]{8,}\\.[A-Za-z0-9_-]{8,}\\.[A-Za-z0-9_-]{8,}$");
private static final Pattern INTERNATIONAL_PHONE = Pattern.compile("^\\+\\d[\\d.~-]{7,}$");
private RedisKeyRules() {
if (value == null || !IDENTIFIER.matcher(value).matches()) {
throw new IllegalArgumentException(
"identifier must be 1..128 characters of [A-Za-z0-9._~-] and must not contain a"
+ " key separator");
}
String lowerCase = value.toLowerCase(Locale.ROOT);
if (value.indexOf('@') >= 0) {
throw new IllegalArgumentException("identifier must not contain a mail address");
}
if (JSON_WEB_TOKEN.matcher(value).matches()) {
throw new IllegalArgumentException("identifier must not contain a JSON web token");
}
if (INTERNATIONAL_PHONE.matcher(value).matches()) {
throw new IllegalArgumentException("identifier must not contain a phone number");
}
if (lowerCase.startsWith("bearer") || lowerCase.startsWith("eyj")) {
throw new IllegalArgumentException("identifier must not contain authentication material");
}
return value;
}
exit=0
--- IDENTIFIER 문자 클래스가 '@'와 '+'를 이미 배제하는가 ---
$ grep -n 'IDENTIFIER =' adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/key/RedisKeyRules.java
23: private static final Pattern IDENTIFIER = Pattern.compile("^[A-Za-z0-9][A-Za-z0-9._~-]{0,127}$");
exit=0
$ grep -n 'rejectsEmailInIdentifier' -A 5 adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/key/RedisKeyRulesTest.java
12: void rejectsEmailInIdentifier() {
13- assertThatThrownBy(() -> new RedisKeyName("user", "person@example.com"))
14- .isInstanceOf(IllegalArgumentException.class);
15- }
16-
17- @Test
exit=0
$ grep -n 'rejectsInternationalPhoneNumbersInIdentifier' -A 8 adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/key/RedisKeyRulesTest.java
31: void rejectsInternationalPhoneNumbersInIdentifier() {
32- assertThatThrownBy(() -> new RedisKeyName("contact", "+821012345678"))
33- .isInstanceOf(IllegalArgumentException.class);
34- }
35-
36- @Test
37- void rejectsSeparatorInjectionInEveryKeyPart() {
38- assertThatThrownBy(() -> new RedisKeyName("user", "1:2"))
39- .isInstanceOf(IllegalArgumentException.class);
exit=0
=== 8.1b 선언되었으나 읽히지 않는 술어 ===
$ git grep -n 'requiresPermit()' -- 'adapter/outbound/cache-redis/src'
adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/command/RedisRiskLevel.java:19: public boolean requiresPermit() {
exit=0
$ git grep -n 'deniedToApplications()' -- 'adapter/outbound/cache-redis/src'
adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/command/RedisRiskLevel.java:28: public boolean deniedToApplications() {
adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/command/CommandPolicyGuard.java:126: if (policy.riskLevel().deniedToApplications()
adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/raw/RawCommandApprovals.java:116: if (policy.riskLevel().deniedToApplications()) {
exit=0
=== 8.2 sync/reactive 대칭: getRange 포함 실제 대조 ===
$ grep -c ';' adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/operations/RedisValueOperations.java adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/reactive/ReactiveRedisValueOperations.java
adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/operations/RedisValueOperations.java:20
adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/reactive/ReactiveRedisValueOperations.java:25
exit=0
$ diff <(grep -oE '[a-z][A-Za-z]*\(' adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/operations/RedisValueOperations.java | sort -u) <(grep -oE '[a-z][A-Za-z]*\(' adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/reactive/ReactiveRedisValueOperations.java | sort -u)
exit=0
=== 8.2b RedisCommandDescriptor 교차 필드 불변식 ===
$ grep -n 'throw new IllegalArgumentException' -B 4 adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/command/RedisCommandDescriptor.java
30- Objects.requireNonNull(access, "access must be non-null");
31- Objects.requireNonNull(keySpec, "key specification must be non-null");
32- Objects.requireNonNull(timeoutProfile, "timeout profile must be non-null");
33- if (support == CommandSupport.BLOCKED && access != CommandAccess.NONE) {
34: throw new IllegalArgumentException("a blocked command must not carry an ACL account");
35- }
36- if (riskLevel == RedisRiskLevel.R4 && support != CommandSupport.BLOCKED) {
37: throw new IllegalArgumentException("an R4 command must be blocked");
38- }
39- if (riskLevel == RedisRiskLevel.R3
40- && support != CommandSupport.ADMIN_ONLY
41- && support != CommandSupport.BLOCKED) {
42: throw new IllegalArgumentException("an R3 command must be admin-only or blocked");
43- }
44- if (!readOnly && retrySafe && mayBeAmbiguous) {
45: throw new IllegalArgumentException(
exit=0
=== 8.3 실패 메타데이터: 모호 실행과 재시도의 배타 ===
$ grep -n 'ambiguousExecution\|retryable\|storedDataCorruption\|notSent' adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/error/RedisFailureMetadata.java | head -20
21: boolean retryable,
22: boolean ambiguousExecution,
41: if (retryable && ambiguousExecution) {
42: throw new IllegalArgumentException("an ambiguous execution must never be marked retryable");
61: * <p>Deliberately not {@link #notSent}: that factory derives {@code retryable} from {@code
71: * @return metadata marked not retryable and not ambiguous
73: public static RedisFailureMetadata storedDataCorruption(
90: public static RedisFailureMetadata notSent(
exit=0
$ git grep -n 'storedDataCorruption(' -- 'adapter/outbound/cache-redis/src/main' | head
adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/error/RedisFailureMetadata.java:73: public static RedisFailureMetadata storedDataCorruption(
adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/codec/JsonEnvelopeFraming.java:202: RedisFailureMetadata.storedDataCorruption(
adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/codec/VersionedJsonCodec.java:103: RedisFailureMetadata.storedDataCorruption(
exit=0
=== 8.4 예외 계층: transient metadata ===
$ grep -n 'transient\|serialVersionUID' adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/error/RedisOperationException.java
14: private static final long serialVersionUID = 1L;
16: private final transient RedisFailureMetadata metadata;
exit=0
$ git ls-files 'adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/api/error' | wc -l
14
exit=0