Sub-scope 01 (cache-redis) governance + sdk/config — manifest and negative-space probes
revision=a24ece9cf797f7ea647e33bf846b115208ed1ba5
generatedAt=2026-08-30T03:59:48+00:00
cwd=/shared/codebase/clean-architecture-backend-template/src

=== OWNED FILES ===
d7d9299d8b8d61386d14f09d8c50da1dad758efa CLAUDE.md 143
8598f863777600267efdac00e5624d96d6ceec8f README.md 381
99c8f5bede105ee16ac9312c9aee055c16900076 build.gradle 202
062d16a37189b5de4bae45f3a05424504efda198 gradle.lockfile 164
e4b23adfdf4fe19b64c9d29c58bdd4d721fceba1 src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/config/ConfiguredRedisPermitVerifier.java 94
08397249ba133ab758419f95af9c52ce9ccd17a9 src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/config/ConfiguredRedisPolicyAuthority.java 106
c114b289e2da0b2afd60104af8c50f1561d070b6 src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/config/GrantedAdvancedOperationPermit.java 24
d474a8d29be05c0e6e7eb836736ebd3625936012 src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/config/GrantedMultiKeyPermit.java 21
c4c3b611267c35a0721413acba22cc773914bc06 src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/config/GrantedPersistentKeyPermit.java 21
0bb89c573b6c80d71f0f9122374e7639acd9b57b src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/config/RedisCapabilityProbe.java 176
5c008151853a73ff417482f803602ba6f6418b61 src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/config/RedisCorrectnessRoles.java 62
929a59fa7035c717447b72eac36cdeb57c2e0513 src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/config/RedisCredentialResolver.java 125
5e1ec779b058125188a7e4b86150935f0df37a52 src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/config/RedisHealthContributor.java 88
a699afb72b41717d121dcff3778555da2ab18ea1 src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/config/RedisSdkAutoConfiguration.java 397
2f577f0c64e7d108b9e7d477ccc61010e3da852b src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/config/RedisSdkSettings.java 959
9ac874da2ffd3f901e168aaa3c20e019ae9922bb src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/config/RedisStartupProbe.java 142
47656d4826960bf5b4717d44569527a3ead037cf src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/config/LiveRedisCompositionTest.java 176
10a77ad0f8096326079ae25358863840616084b8 src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/config/LiveRedisTlsTest.java 141
3a266c797630d7cf8c5e917f759c8edbbf4ba675 src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/config/RedisCapabilityProbeTest.java 158
e3ff2763675b302239deee9fbb6cb31d0b526a94 src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/config/RedisPermitProvenanceTest.java 87
6e97086762eb93705c72b009d970002dbd882fd8 src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/config/RedisSdkAutoConfigurationTest.java 447
6b576bbf04cee7791e7c688c9fb910e39935d24d src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/config/RedisSdkSettingsTest.java 201
8bea0f7148c054e816fe876ff4731b3961933a32 src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/config/RedisStartupProbeTest.java 145
ebb0ed7ab8900c0007582e96351ff8bbfd7bb0ad src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/config/RequiredWhenIsEnforcedTest.java 152
223a63b5cefb8be4169e068f5f6404e16a7c2a7f src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 1
1f51d1352a6a3f515b3d1cf4d40aed4f120d42de src/main/resources/redis-sdk/redis-command-policy.yml 1406

=== 8.4 README readiness 표 vs 실제 소스 ===
$ sed -n '23,41p' adapter/outbound/cache-redis/README.md
| Capability | API 구현 | Spring composition 구현 | 실서버 qualification |
| --- | --- | --- | --- |
| Redis SDK typed API (`…cache.redis.sdk`) | 있음 | settings bind + validate 까지만 | 없음 |
| Topology client / connection lifecycle | 없음 | 없음 | 없음 |
| cache / session / idempotency / rate limit / lease semantic port | 없음 | 없음 | 없음 |
| role-aware health·readiness contributor | 없음 | 없음 | 없음 |

즉 현재 `APP_REDIS_ENABLED=true`가 하는 일은 `RedisSdkSettings`를 bind하고 cross-field 규칙을
fail-fast로 검증하는 것까지다. client, connection, gateway, semantic adapter, health contributor는
아직 조립되지 않는다. 남은 단계와 순서는
`docs/superpowers/plans/2026-08-10-redis-optionality-and-composition.md`에 있다.

readiness registry에도 `selected` card가 없으므로 Redis R2 release claim은 없다. 아래 절들은
이전 세대 semantic adapter 세트의 설계 결정을 기록한 것이며, 그 코드는 현재 이 leaf에 없다.
복구 범위는 위 plan의 Phase E가 소유한다.

모듈은 Lettuce connection lifecycle,
finite command timeout, reconnect replay 차단, finite request queue/admission, positive/negative
TTL, absolute soft/hard expiry, deterministic bounded TTL jitter, digest-protected v2 binary
exit=0

--- 'Topology client / connection lifecycle: 없음' 대조 ---
$ git ls-files 'adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/connection' | xargs wc -l
   75 adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/connection/RedisConnectionKind.java
   28 adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/connection/RedisConnectionLease.java
  142 adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/connection/RedisConnectionRegistry.java
   32 adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/connection/RedisCredentialRole.java
   44 adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/connection/RedisLease.java
   83 adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/connection/RedisRuntimeClient.java
  316 adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/connection/RedisRuntimeOwner.java
  600 adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/connection/RedisTopologyClientFactory.java
  153 adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/connection/SentinelFailoverObserver.java
 1473 total
exit=0

--- 'semantic port: 없음' / '그 코드는 현재 이 leaf에 없다' 대조 ---
$ git ls-files 'adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/cache' 'adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/idempotency' 'adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/lease' 'adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/ratelimit' 'adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/realtime' 'adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/keyspace' | xargs wc -l
   213 adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/cache/CacheEnvelope.java
   428 adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/cache/RedisCacheRegionAdapter.java
   306 adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/idempotency/IdempotencyScripts.java
   529 adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/idempotency/RedisIdempotencyStoreAdapter.java
   106 adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/keyspace/CapabilityKeyspace.java
   203 adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/lease/LeaseScripts.java
   403 adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/lease/RedisDistributedLeaseAdapter.java
    49 adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/ratelimit/RateLimitKeys.java
   280 adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/ratelimit/RateLimitScripts.java
   187 adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/ratelimit/RedisEdgeRateLimitAdapter.java
    81 adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/realtime/RealtimeKeys.java
   301 adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/realtime/RedisConnectionRegistryAdapter.java
   133 adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/realtime/RedisEphemeralFanoutAdapter.java
    76 adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/realtime/RegistrationCodec.java
  3295 total
exit=0

--- 'role-aware health contributor: 없음' 대조 ---
$ git ls-files 'adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/config' | grep -i 'health\|role' | xargs wc -l
  62 adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/config/RedisCorrectnessRoles.java
  88 adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/config/RedisHealthContributor.java
 150 total
exit=0

=== 8.4b build.gradle의 'Zero imports' 주장 3건 대조 ===
$ sed -n '6,16p;33,39p' adapter/outbound/cache-redis/build.gradle
dependencies {
    // Registered edges the semantic port adapters need. The SDK itself imports nothing from them
    // today (0 imports across main source) — the semantic cache/session/idempotency/rate-limit
    // adapters that did were removed and are restored by Phase E of
    // docs/superpowers/plans/2026-08-10-redis-optionality-and-composition.md. They stay declared
    // because that restoration is the module's stated responsibility, not because anything here
    // compiles against them.
    implementation project(':application-core')
    implementation project(':shared-contract')
    implementation project(':adapter:outbound:support')

    // Deliberately absent:
    //   org.springframework.data:spring-data-redis — the SDK owns its own typed API and command
    //     policy on purpose; routing through Spring Data would reintroduce the untyped, unguarded
    //     command surface the catalog exists to prevent. Zero imports.
    //   io.micrometer:micrometer-core — observation leaves this leaf as RedisObservation through a
    //     Consumer sink; binding it to a meter registry belongs to the composition root, not here.
    //     Zero imports.
exit=0

--- application-core / shared-contract / support import 실측 ---
$ git grep -c 'import dev.caskeleton.application\.' -- 'adapter/outbound/cache-redis/src/main' | head -20
adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/cache/RedisCacheRegionAdapter.java:9
adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/idempotency/RedisIdempotencyStoreAdapter.java:18
adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/lease/RedisDistributedLeaseAdapter.java:10
adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/realtime/RealtimeKeys.java:2
adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/realtime/RedisConnectionRegistryAdapter.java:5
adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/realtime/RedisEphemeralFanoutAdapter.java:3
adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/realtime/RegistrationCodec.java:4
exit=0

$ git grep -l 'import dev.caskeleton.shared\.' -- 'adapter/outbound/cache-redis/src/main' | head -20
adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/ratelimit/RateLimitKeys.java
adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/ratelimit/RateLimitScripts.java
adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/ratelimit/RedisEdgeRateLimitAdapter.java
exit=0

$ git grep -l 'import dev.caskeleton.adapter.outbound.support' -- 'adapter/outbound/cache-redis/src/main' | head
exit=0

--- spring-data-redis / micrometer import 실측 ---
$ git grep -c 'org.springframework.data.redis\|io.micrometer' -- 'adapter/outbound/cache-redis/src/main'
exit=1

=== 8.1 조립: auto-configuration이 실제로 만드는 bean ===
$ cat adapter/outbound/cache-redis/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
dev.caskeleton.adapter.outbound.cache.redis.sdk.config.RedisSdkAutoConfiguration
exit=0

$ grep -n '@Bean\|@Conditional\|class \|record ' adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/config/RedisSdkAutoConfiguration.java
35: * <p>{@code app.redis.enabled} is the whole switch. While it is false this class contributes
54:@ConditionalOnProperty(prefix = "app.redis", name = "enabled", havingValue = "true")
55:// Registers the binding post-processor that populates the @ConfigurationProperties @Bean below.
59:public class RedisSdkAutoConfiguration {
83:  @Bean
101:  @Bean
156:  @Bean
226:  @Bean
273:  @Bean(destroyMethod = "close")
297:  @Bean(RedisCorrectnessRoles.OPTIONAL_HEALTH_CONTRIBUTOR)
324:  @Bean(RedisCorrectnessRoles.REQUIRED_HEALTH_CONTRIBUTOR)
325:  @Conditional(RedisCorrectnessRoleBound.class)
344:  static final class RedisCorrectnessRoleBound implements Condition {
364:  public record RedisResolvedCredentials(
391:  public record RedisSdkSettingsValidation(List<String> warnings) {
exit=0

=== 8.2 활성화 게이트 ===
$ grep -rn 'APP_REDIS_ENABLED\|app.cache.redis\|matchIfMissing\|ConditionalOnProperty' adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/config/*.java | head -30
adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/config/RedisSdkAutoConfiguration.java:18:import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/config/RedisSdkAutoConfiguration.java:54:@ConditionalOnProperty(prefix = "app.redis", name = "enabled", havingValue = "true")
exit=0

=== 8.3 permit 타입 3종 ===
$ cat adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/config/GrantedAdvancedOperationPermit.java adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/config/GrantedPersistentKeyPermit.java adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/config/GrantedMultiKeyPermit.java
package dev.caskeleton.adapter.outbound.cache.redis.sdk.config;

import dev.caskeleton.adapter.outbound.cache.redis.sdk.api.command.AdvancedOperationPermit;
import java.util.Objects;

/**
 * The only advanced permit implementation the verifier accepts.
 *
 * <p>Package private on purpose: application code can implement the public interface, but it cannot
 * construct this type, and the verifier checks the concrete type as well as the signature.
 *
 * @param policyName the approved policy name
 * @param issuerId identity of the issuing authority instance
 * @param signature signature the issuing authority computed over the policy name
 */
record GrantedAdvancedOperationPermit(String policyName, String issuerId, String signature)
    implements AdvancedOperationPermit {

  GrantedAdvancedOperationPermit {
    Objects.requireNonNull(policyName, "policyName must be non-null");
    Objects.requireNonNull(issuerId, "issuerId must be non-null");
    Objects.requireNonNull(signature, "signature must be non-null");
  }
}
package dev.caskeleton.adapter.outbound.cache.redis.sdk.config;

import dev.caskeleton.adapter.outbound.cache.redis.sdk.api.command.PersistentKeyPermit;
import java.util.Objects;

/**
 * The only persistent-key permit implementation the verifier accepts.
 *
 * @param policyName the approved policy name
 * @param issuerId identity of the issuing authority instance
 * @param signature signature the issuing authority computed over the policy name
 */
record GrantedPersistentKeyPermit(String policyName, String issuerId, String signature)
    implements PersistentKeyPermit {

  GrantedPersistentKeyPermit {
    Objects.requireNonNull(policyName, "policyName must be non-null");
    Objects.requireNonNull(issuerId, "issuerId must be non-null");
    Objects.requireNonNull(signature, "signature must be non-null");
  }
}
package dev.caskeleton.adapter.outbound.cache.redis.sdk.config;

import dev.caskeleton.adapter.outbound.cache.redis.sdk.api.command.MultiKeyPermit;
import java.util.Objects;

/**
 * The only multi-key permit implementation the verifier accepts.
 *
 * @param policyName the approved policy name
 * @param issuerId identity of the issuing authority instance
 * @param signature signature the issuing authority computed over the policy name
 */
record GrantedMultiKeyPermit(String policyName, String issuerId, String signature)
    implements MultiKeyPermit {

  GrantedMultiKeyPermit {
    Objects.requireNonNull(policyName, "policyName must be non-null");
    Objects.requireNonNull(issuerId, "issuerId must be non-null");
    Objects.requireNonNull(signature, "signature must be non-null");
  }
}
exit=0

=== 8.4c config test 목록 ===
$ git ls-files 'adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/config' | xargs -n1 basename
LiveRedisCompositionTest.java
LiveRedisTlsTest.java
RedisCapabilityProbeTest.java
RedisPermitProvenanceTest.java
RedisSdkAutoConfigurationTest.java
RedisSdkSettingsTest.java
RedisStartupProbeTest.java
RequiredWhenIsEnforcedTest.java
exit=0

$ git grep -h '  void [a-z]' -- 'adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/config' | sed 's/^ *//' | head -60
void aWrongPasswordIsRefused() throws Exception {
void theModeProducesOneMatchingClient() {
void aLeaseReachesTheServer() throws Exception {
void theOptionalContributorReportsUp() {
void closingTheContextTearsEverythingDown() {
void aFilesystemTrustReferenceWorks() {
void aClasspathTrustReferenceWorks() throws IOException {
void unreadableTrustMaterialFailsAtStartup() {
void plaintextAgainstATlsOnlyServerFails() {
void redisSevenTwoHasNoHashFieldExpiration() {
void redisSevenFourGainsHashFieldExpiration() {
void aNewEnoughVersionWithoutTheCommandIsNotACapability() {
void anExplicitlyEnabledCapabilityThatIsAbsentFailsStartup() {
void aServerBelowTheBaselineFailsStartup() {
void clusterWithANonZeroDatabaseFailsStartup() {
void refusesAReplicatedDeploymentThatCannotKeepTheWritesItAcknowledges() {
void acceptsAReplicatedDeploymentWithAWriteGuarantee() {
void rejectsAReplicaCountWithNoLagBound() {
void allowsTheLossOnlyWhenTheDeploymentDeclaredIt() {
void doesNotRequireAWriteGuaranteeFromStandalone() {
void acceptsAPermitTheAuthorityIssued() {
void rejectsAPermitTheCallerImplementedItself() {
void rejectsAPermitIssuedForADifferentPolicy() {
void rejectsAPermitIssuedByAnotherAuthorityInstance() {
void refusesToIssueAPolicyConfigurationDidNotEnable() {
void aPermitCarriesNoAuthorityMaterialInItsPolicyName() {
void absentSwitchRegistersNothing() {
void disabledRegistersNothing() {
void disabledIgnoresMalformedRedisConfiguration() {
void disabledNeverAsksForASecretOrAConnection() {
void enabledResolvesEachConfiguredRoleOnce() {
void enabledBindsSettings() {
void enabledRejectsClusterWithANonZeroDatabase() {
void enabledRejectsAnUnboundedBlock() {
void enabledRejectsARawGatewayWithoutAnAllowlist() {
void enabledRejectsAMissingRawAllowlistResource() {
void enabledAcceptsAReadableRawAllowlistResource() {
void standaloneWithSeveralNodesIsRefused() {
void sentinelWithoutAMasterNameIsRefused() {
void enabledWithoutAnApplicationCredentialIsRefused() {
void anonymousAccessCanBeDeclared() {
void configuredAccountsAreResolvedPerRole() {
void anUnresolvableCredentialReferenceFailsAtStartup() {
void aLiteralCredentialIsRefused() {
void clusterBuildsAClusterClient() {
void theRuntimeOwnerFollowsTheContext() {
void enabledRejectsAnAdminPlaneWithoutItsOwnCredential() {
void clusterRejectsDatabaseOtherThanZero() {
void standaloneAcceptsANonZeroDatabase() {
void namespaceTokensAreValidatedAtStartup() {
void aFastTimeoutAboveTheGuardrailWarnsAndFarAboveItFails() {
void blockingMustNeverBeUnbounded() {
void theRawGatewayRequiresAnAllowlistAndItsOwnCredential() {
void theAdminPlaneRequiresItsOwnCredential() {
void aClientCertificateWithoutItsKeyIsRefusedAtStartup() {
void oneWayTlsNeedsNoClientCertificateAndNoTrustMaterial() {
void advancedPoliciesCannotBeConfiguredWhileAdvancedOperationsAreDisabled() {
void defaultsMatchTheDesignGuardrails() {
void authenticationIsRequiredUnlessAnonymousIsDeclared() {
void aSingleAccountDeploymentIsToldThatScriptingRunsAsTheApplicationAccount() {
exit=0


=== 8.1d reachability: startup probe / capability probe / permit authority ===
$ git grep -n 'RedisStartupProbe\|RedisCapabilityProbe' -- . | grep -v 'sdk/config/RedisStartupProbe.java' | grep -v 'sdk/config/RedisCapabilityProbe.java'
adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/config/RedisSdkSettings.java:888:   * signal exists for it; see {@link RedisCapabilityProbe#requireWriteDurability}.
adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/config/RedisCapabilityProbeTest.java:17:class RedisCapabilityProbeTest {
adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/config/RedisCapabilityProbeTest.java:19:  private final RedisCapabilityProbe probe = new RedisCapabilityProbe();
adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/config/RedisCapabilityProbeTest.java:28:            RedisCapabilityProbeTest::everyCommand,
adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/config/RedisCapabilityProbeTest.java:42:            RedisCapabilityProbeTest::everyCommand,
adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/config/RedisCapabilityProbeTest.java:86:                    RedisCapabilityProbeTest::everyCommand,
adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/config/RedisCapabilityProbeTest.java:100:                    RedisCapabilityProbeTest::everyCommand,
adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/config/RedisStartupProbeTest.java:16:class RedisStartupProbeTest {
adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/config/RedisStartupProbeTest.java:18:  private final RedisStartupProbe probe = new RedisStartupProbe(new RedisCapabilityProbe());
adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/config/RedisStartupProbeTest.java:53:            RedisStartupProbe.ServerFacts.from(
adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/config/RedisStartupProbeTest.java:71:                    RedisStartupProbe.ServerFacts.from(
adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/config/RedisStartupProbeTest.java:85:                    RedisStartupProbe.ServerFacts.from(
adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/config/RedisStartupProbeTest.java:99:                    RedisStartupProbe.ServerFacts.from(
adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/config/RedisStartupProbeTest.java:113:                RedisStartupProbe.ServerFacts.from(
adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/config/RedisStartupProbeTest.java:124:                RedisStartupProbe.ServerFacts.from(
adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/config/RedisStartupProbeTest.java:139:            RedisStartupProbe.ServerFacts.from(
exit=0

$ git grep -n 'ConfiguredRedisPolicyAuthority\|ConfiguredRedisPermitVerifier' -- . | grep -v 'sdk/config/ConfiguredRedis'
adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/config/RedisPermitProvenanceTest.java:17:  private final ConfiguredRedisPolicyAuthority authority =
adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/config/RedisPermitProvenanceTest.java:18:      new ConfiguredRedisPolicyAuthority(
adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/config/RedisPermitProvenanceTest.java:21:  private final ConfiguredRedisPermitVerifier verifier =
adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/config/RedisPermitProvenanceTest.java:22:      new ConfiguredRedisPermitVerifier(authority, RedisDeploymentMode.STANDALONE);
adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/config/RedisPermitProvenanceTest.java:64:    ConfiguredRedisPolicyAuthority other =
adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/config/RedisPermitProvenanceTest.java:65:        new ConfiguredRedisPolicyAuthority(List.of("collection-full-read"));
adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/command/CommandPolicyGuardTest.java:23:import dev.caskeleton.adapter.outbound.cache.redis.sdk.config.ConfiguredRedisPermitVerifier;
adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/command/CommandPolicyGuardTest.java:24:import dev.caskeleton.adapter.outbound.cache.redis.sdk.config.ConfiguredRedisPolicyAuthority;
adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/command/CommandPolicyGuardTest.java:39:  private final ConfiguredRedisPolicyAuthority authority =
adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/command/CommandPolicyGuardTest.java:40:      new ConfiguredRedisPolicyAuthority(
adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/command/CommandPolicyGuardTest.java:266:        new ConfiguredRedisPermitVerifier(authority, mode),
adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/command/ObservationIsolationTest.java:18:import dev.caskeleton.adapter.outbound.cache.redis.sdk.config.ConfiguredRedisPermitVerifier;
adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/command/ObservationIsolationTest.java:19:import dev.caskeleton.adapter.outbound.cache.redis.sdk.config.ConfiguredRedisPolicyAuthority;
adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/command/ObservationIsolationTest.java:43:  private final ConfiguredRedisPolicyAuthority authority =
adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/command/ObservationIsolationTest.java:44:      new ConfiguredRedisPolicyAuthority(List.of());
adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/command/ObservationIsolationTest.java:49:          new ConfiguredRedisPermitVerifier(authority, RedisDeploymentMode.STANDALONE),
adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/operations/LiveRedisOperationsFixture.java:12:import dev.caskeleton.adapter.outbound.cache.redis.sdk.config.ConfiguredRedisPermitVerifier;
adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/operations/LiveRedisOperationsFixture.java:13:import dev.caskeleton.adapter.outbound.cache.redis.sdk.config.ConfiguredRedisPolicyAuthority;
adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/operations/LiveRedisOperationsFixture.java:67:  final ConfiguredRedisPolicyAuthority authority =
adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/operations/LiveRedisOperationsFixture.java:68:      new ConfiguredRedisPolicyAuthority(ENABLED_POLICIES);
adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/operations/LiveRedisOperationsFixture.java:123:    ConfiguredRedisPermitVerifier verifier =
adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/operations/LiveRedisOperationsFixture.java:124:        new ConfiguredRedisPermitVerifier(authority, endpoint.mode());
adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/operations/RedisOperationsFixture.java:12:import dev.caskeleton.adapter.outbound.cache.redis.sdk.config.ConfiguredRedisPermitVerifier;
adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/operations/RedisOperationsFixture.java:13:import dev.caskeleton.adapter.outbound.cache.redis.sdk.config.ConfiguredRedisPolicyAuthority;
adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/operations/RedisOperationsFixture.java:67:  final ConfiguredRedisPolicyAuthority authority =
adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/operations/RedisOperationsFixture.java:68:      new ConfiguredRedisPolicyAuthority(ENABLED_POLICIES);
adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/operations/RedisOperationsFixture.java:149:    ConfiguredRedisPermitVerifier verifier =
adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/operations/RedisOperationsFixture.java:150:        new ConfiguredRedisPermitVerifier(authority, RedisDeploymentMode.STANDALONE);
adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/operations/RedisTransactionContractTest.java:17:import dev.caskeleton.adapter.outbound.cache.redis.sdk.config.ConfiguredRedisPermitVerifier;
adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/operations/RedisTransactionContractTest.java:18:import dev.caskeleton.adapter.outbound.cache.redis.sdk.config.ConfiguredRedisPolicyAuthority;
adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/operations/RedisTransactionContractTest.java:58:  private final ConfiguredRedisPolicyAuthority authority =
adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/operations/RedisTransactionContractTest.java:59:      new ConfiguredRedisPolicyAuthority(
adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/operations/RedisTransactionContractTest.java:84:    ConfiguredRedisPermitVerifier verifier =
adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/operations/RedisTransactionContractTest.java:85:        new ConfiguredRedisPermitVerifier(authority, RedisDeploymentMode.STANDALONE);
adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/operations/RedisTransactionSlotContractTest.java:18:import dev.caskeleton.adapter.outbound.cache.redis.sdk.config.ConfiguredRedisPermitVerifier;
adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/operations/RedisTransactionSlotContractTest.java:19:import dev.caskeleton.adapter.outbound.cache.redis.sdk.config.ConfiguredRedisPolicyAuthority;
adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/operations/RedisTransactionSlotContractTest.java:63:  private final ConfiguredRedisPolicyAuthority authority =
adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/operations/RedisTransactionSlotContractTest.java:64:      new ConfiguredRedisPolicyAuthority(
adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/operations/RedisTransactionSlotContractTest.java:78:    ConfiguredRedisPermitVerifier verifier =
adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/lettuce/operations/RedisTransactionSlotContractTest.java:79:        new ConfiguredRedisPermitVerifier(authority, RedisDeploymentMode.CLUSTER);
adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/programmability/LiveRedisClusterTransactionTest.java:20:import dev.caskeleton.adapter.outbound.cache.redis.sdk.config.ConfiguredRedisPermitVerifier;
adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/programmability/LiveRedisClusterTransactionTest.java:21:import dev.caskeleton.adapter.outbound.cache.redis.sdk.config.ConfiguredRedisPolicyAuthority;
adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/programmability/LiveRedisClusterTransactionTest.java:110:    ConfiguredRedisPolicyAuthority authority =
adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/programmability/LiveRedisClusterTransactionTest.java:111:        new ConfiguredRedisPolicyAuthority(
adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/programmability/LiveRedisClusterTransactionTest.java:116:    ConfiguredRedisPermitVerifier verifier =
adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/programmability/LiveRedisClusterTransactionTest.java:117:        new ConfiguredRedisPermitVerifier(authority, endpoint.mode());
exit=0

$ git grep -n 'requireWriteDurability\|\.confirm(' -- 'adapter/outbound/cache-redis/src'
adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/config/RedisCapabilityProbe.java:133:  public void requireWriteDurability(
adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/config/RedisSdkSettings.java:888:   * signal exists for it; see {@link RedisCapabilityProbe#requireWriteDurability}.
adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/config/RedisStartupProbe.java:61:    probe.requireWriteDurability(
adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/config/RedisCapabilityProbeTest.java:113:      assertThatThrownBy(() -> probe.requireWriteDurability(mode, 0, 10, false))
adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/config/RedisCapabilityProbeTest.java:122:    probe.requireWriteDurability(RedisDeploymentMode.SENTINEL, 1, 10, false);
adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/config/RedisCapabilityProbeTest.java:123:    probe.requireWriteDurability(RedisDeploymentMode.CLUSTER, 2, 5, false);
adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/config/RedisCapabilityProbeTest.java:133:      assertThatThrownBy(() -> probe.requireWriteDurability(mode, 2, 0, false))
adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/config/RedisCapabilityProbeTest.java:142:    probe.requireWriteDurability(RedisDeploymentMode.SENTINEL, 0, 0, true);
adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/config/RedisCapabilityProbeTest.java:148:    probe.requireWriteDurability(RedisDeploymentMode.STANDALONE, 0, 0, false);
adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/config/RedisStartupProbeTest.java:51:        probe.confirm(
adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/config/RedisStartupProbeTest.java:69:                probe.confirm(
adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/config/RedisStartupProbeTest.java:83:                probe.confirm(
adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/config/RedisStartupProbeTest.java:97:                probe.confirm(
adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/config/RedisStartupProbeTest.java:137:        probe.confirm(
exit=0

=== 8.1e app-bootstrap가 RedisCorrectnessRoles를 실제로 읽는가 ===
$ git grep -rn 'RedisCorrectnessRoles\|redisRequired\|redisOptional' -- app-bootstrap | head -20
app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/RedisReadinessGroupPostProcessor.java:3:import dev.caskeleton.adapter.outbound.cache.redis.sdk.config.RedisCorrectnessRoles;
app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/RedisReadinessGroupPostProcessor.java:20: * contributor 'redisRequired' in group 'readiness' does not exist}. So a static {@code
app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/RedisReadinessGroupPostProcessor.java:21: * readiness.include} listing {@code redisRequired} is not "tolerated when Redis is off" — it is a
app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/RedisReadinessGroupPostProcessor.java:30: * <p>Hence this post-processor. It reads {@link RedisCorrectnessRoles} — the identical predicate
app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/RedisReadinessGroupPostProcessor.java:45:    if (!redisEnabled(environment) || !RedisCorrectnessRoles.anySelected(environment)) {
app-bootstrap/src/main/java/dev/caskeleton/bootstrap/runtime/RedisReadinessGroupPostProcessor.java:54:    if (!members.add(RedisCorrectnessRoles.REQUIRED_HEALTH_CONTRIBUTOR)) {
app-bootstrap/src/main/resources/application.yml:272:      # unconditional contributors, and the one conditional member — `redisRequired` — is appended
app-bootstrap/src/main/resources/application.yml:292:        # `redisRequired` is NOT listed here, and must not be: group membership does not tolerate a
app-bootstrap/src/main/resources/application.yml:295:        # startup with "Included health contributor 'redisRequired' in group 'readiness' does not
app-bootstrap/src/main/resources/application.yml:298:        # `redisOptional` is never appended: a cache outage is reported as degraded detail and never
app-bootstrap/src/test/java/dev/caskeleton/bootstrap/runtime/RedisReadinessGroupPostProcessorTest.java:107:      assertThat(readiness.isMember("redisRequired"))
app-bootstrap/src/test/java/dev/caskeleton/bootstrap/runtime/RedisReadinessGroupPostProcessorTest.java:110:      assertThat(context.getBeansOfType(HealthIndicator.class)).doesNotContainKey("redisRequired");
app-bootstrap/src/test/java/dev/caskeleton/bootstrap/runtime/RedisReadinessGroupPostProcessorTest.java:123:          .containsKey("redisOptional")
app-bootstrap/src/test/java/dev/caskeleton/bootstrap/runtime/RedisReadinessGroupPostProcessorTest.java:124:          .doesNotContainKey("redisRequired");
app-bootstrap/src/test/java/dev/caskeleton/bootstrap/runtime/RedisReadinessGroupPostProcessorTest.java:125:      assertThat(readiness.isMember("redisRequired")).isFalse();
app-bootstrap/src/test/java/dev/caskeleton/bootstrap/runtime/RedisReadinessGroupPostProcessorTest.java:126:      assertThat(readiness.isMember("redisOptional"))
app-bootstrap/src/test/java/dev/caskeleton/bootstrap/runtime/RedisReadinessGroupPostProcessorTest.java:139:      assertThat(context.getBeansOfType(HealthIndicator.class)).containsKey("redisRequired");
app-bootstrap/src/test/java/dev/caskeleton/bootstrap/runtime/RedisReadinessGroupPostProcessorTest.java:140:      assertThat(readiness.isMember("redisRequired"))
app-bootstrap/src/test/java/dev/caskeleton/bootstrap/runtime/RedisReadinessGroupPostProcessorTest.java:166:                    .isMember("redisRequired"))
app-bootstrap/src/test/java/dev/caskeleton/bootstrap/runtime/RuntimeHealthLifecycleContractTest.java:98:    // The shipped file names only contributors that always exist. Both `redisRequired` and `db` are
exit=0

=== 8.2b RedisSdkSettings.validate 규칙 목록 ===
$ grep -n 'private void require\|violations.add\|warnings.add\|throw new IllegalStateException' adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/config/RedisSdkSettings.java | head -60
61:      throw new IllegalStateException("Cluster supports database 0 only");
64:      throw new IllegalStateException("database index must not be negative");
67:      throw new IllegalStateException("at least one Redis node must be configured");
77:      throw new IllegalStateException("blocking commands must not be unbounded");
80:      throw new IllegalStateException("dedicated connection lanes need a positive ceiling");
84:      throw new IllegalStateException(
95:      throw new IllegalStateException(
101:      warnings.add(
110:      throw new IllegalStateException("the raw gateway requires an allowlist resource");
114:      throw new IllegalStateException(
120:      throw new IllegalStateException(
125:      throw new IllegalStateException(
138:      throw new IllegalStateException(name + " timeout must be positive");
141:      throw new IllegalStateException(
145:      warnings.add(
255:        throw new IllegalStateException(
268:        throw new IllegalStateException("every Redis SDK limit must be positive");
409:          throw new IllegalStateException(
418:        warnings.add(
426:        warnings.add(
570:        throw new IllegalStateException("the client name must not be blank");
578:        throw new IllegalStateException("the shutdown quiet period must not be negative");
581:        throw new IllegalStateException(
589:        throw new IllegalStateException(name + " timeout must be positive");
658:        throw new IllegalStateException("cluster maximum redirects must be positive");
663:        throw new IllegalStateException("the cluster topology refresh period must be positive");
694:        throw new IllegalStateException("every Redis capacity ceiling must be positive");
748:        throw new IllegalStateException("the pub/sub buffer capacity must be positive");
751:        throw new IllegalStateException(
exit=0

$ grep -n 'public List<String> validate' -A 40 adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/config/RedisSdkSettings.java | head -60
58:  public List<String> validate() {
59-    List<String> warnings = new ArrayList<>();
60-    if (mode == RedisDeploymentMode.CLUSTER && database != 0) {
61-      throw new IllegalStateException("Cluster supports database 0 only");
62-    }
63-    if (database < 0) {
64-      throw new IllegalStateException("database index must not be negative");
65-    }
66-    if (nodes == null || nodes.isEmpty()) {
67-      throw new IllegalStateException("at least one Redis node must be configured");
68-    }
69-    namespace.validate();
70-    limits.validate();
71-    validateTimeout("fast", timeout.getFast(), warnings);
72-    validateTimeout("collection", timeout.getCollection(), warnings);
73-    validateTimeout("script", timeout.getScript(), warnings);
74-    validateTimeout("batch", timeout.getBatch(), warnings);
75-    validateTimeout("admin", timeout.getAdmin(), warnings);
76-    if (blocking.getMaxBlock().isZero() || blocking.getMaxBlock().isNegative()) {
77-      throw new IllegalStateException("blocking commands must not be unbounded");
78-    }
79-    if (blocking.getMaxConnections() < 1 || transaction.getMaxConnections() < 1) {
80-      throw new IllegalStateException("dedicated connection lanes need a positive ceiling");
81-    }
82-    if (mode == RedisDeploymentMode.SENTINEL
83-        && (sentinel.getMasterName() == null || sentinel.getMasterName().isBlank())) {
84-      throw new IllegalStateException(
85-          "app.redis.sentinel.master-name is required when app.redis.mode=sentinel: a Sentinel"
86-              + " deployment must name the monitored primary; without it the client cannot resolve"
87-              + " a primary at all, let alone follow a promotion");
88-    }
89-    // A certificate without its key cannot build a key manager. Refused here rather than at the
90-    // first connection, where it surfaced as a NullPointerException from inside the SSL options.
91-    if (tls.isEnabled()
92-        && tls.getClientCertificateResource() != null
93-        && !tls.getClientCertificateResource().isBlank()
94-        && (tls.getClientKeyReference() == null || tls.getClientKeyReference().isBlank())) {
95-      throw new IllegalStateException(
96-          "app.redis.tls.client-key-reference is required when"
97-              + " app.redis.tls.client-certificate-resource is configured: mutual TLS presents a"
98-              + " certificate, and a certificate without its private key cannot be presented");
exit=0

=== 8.3b raw allowlist 기본 리소스가 실재하는가 ===
$ grep -n 'raw-command-allowlist\|policyResource' adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/sdk/config/RedisSdkSettings.java | head
800:    private String policyResource = "classpath:redis-sdk/raw-command-allowlist.yml";
812:      return policyResource;
815:    public void setPolicyResource(String policyResource) {
816:      this.policyResource = policyResource;
exit=0

$ git ls-files | grep -i 'raw-command-allowlist' | head
exit=0

$ git ls-files 'adapter/outbound/cache-redis/src/main/resources'
adapter/outbound/cache-redis/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
adapter/outbound/cache-redis/src/main/resources/redis-sdk/redis-command-policy.yml
exit=0

