Files
document-haness/docs/clean-architecture-backend-template/final/evidence/raw/182-notification-activation-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

132 lines
12 KiB
Plaintext

Sub-scope 01 (notification) governance + 루트 + platform/autoconfigure — manifest and probes
revision=a24ece9cf797f7ea647e33bf846b115208ed1ba5
generatedAt=2026-08-30T06:37:36+00:00
=== OWNED FILES ===
660f77ed47c90d8da6c66022c2b5d66e5a706942 CLAUDE.md 47
4fbe71c26464d9e2a3dfa420876338c9f5cdf89f README.md 78
517a950162f15d2bf8c4f55bb0323859ab4db99e build.gradle 54
b30c50f91495749ea97560903b4662d887a5e543 gradle.lockfile 167
23a2136015f44e177bdab5a477f7f498ec5d8ebb src/main/java/dev/caskeleton/adapter/outbound/notification/NotificationConfig.java 40
416c2cb2d052d3a834e03dfdd8fb996224aa9c37 src/main/java/dev/caskeleton/adapter/outbound/notification/NotificationRoutesSettings.java 28
928df24c90847cd9db99a489e3b7149656ec207a src/main/java/dev/caskeleton/adapter/outbound/notification/platform/autoconfigure/AssembledProvider.java 63
c114dfcd96177d602589eff07dc731a88eec3fa2 src/main/java/dev/caskeleton/adapter/outbound/notification/platform/autoconfigure/NotificationPlatformAutoConfiguration.java 118
182c790f192069fc98caeb269e460b659ff5aca1 src/main/java/dev/caskeleton/adapter/outbound/notification/platform/autoconfigure/NotificationPlatformMode.java 24
fb23f3b75a076d06de387da7ee9f2c16f977d219 src/main/java/dev/caskeleton/adapter/outbound/notification/platform/autoconfigure/NotificationPlatformSettings.java 190
eba7ccdc3d555b0b7a2ad72c05797ca638d2fd95 src/main/java/dev/caskeleton/adapter/outbound/notification/platform/autoconfigure/NotificationProviderAssembly.java 239
4ce10c47f9c74c5e426d92093b010b69ced464ed src/main/java/dev/caskeleton/adapter/outbound/notification/platform/autoconfigure/NotificationSecretRequirements.java 98
8870a02465769a3a8c4987c8e0e935ed0e614984 src/main/java/dev/caskeleton/adapter/outbound/notification/platform/autoconfigure/NotificationSmtpProviderConfig.java 211
c6393dabcd7bd61abadedc6b2c9387d19554ed36 src/main/java/dev/caskeleton/adapter/outbound/notification/platform/autoconfigure/NotificationSmtpSettings.java 60
aac01be3780329c32f76e3b857f7e8de3c3b5501 src/main/java/dev/caskeleton/adapter/outbound/notification/platform/autoconfigure/ProviderRuntimeAssembler.java 35
c75b240b6b23bff058b58df5ddf13fa4fed95d17 src/main/java/dev/caskeleton/adapter/outbound/notification/platform/autoconfigure/ProviderType.java 87
d73384a60e4797e54127f12e5a018d58bf90350d src/main/java/dev/caskeleton/adapter/outbound/notification/platform/autoconfigure/SmtpProviderRuntimeAssembler.java 164
6f630e9813d2ed3db38bc429931d1b0d74cb3bf9 src/test/java/dev/caskeleton/adapter/outbound/notification/platform/autoconfigure/NotificationPlatformSettingsTest.java 166
f9fac342d9de203c1777315cc05b1cd55890a6c0 src/test/java/dev/caskeleton/adapter/outbound/notification/platform/autoconfigure/NotificationProviderAssemblyTest.java 267
5f8df14bba9f88525e41d483ce8444ccaf8df4fc src/test/java/dev/caskeleton/adapter/outbound/notification/platform/autoconfigure/NotificationReleaseGateTest.java 63
92052e37ceccc1af9f5f72b3f9d69e94cbff3928 src/test/java/dev/caskeleton/adapter/outbound/notification/platform/autoconfigure/NotificationSecretRequirementsTest.java 173
1831898eba674f57a4be0f318ba667421fbcc4a8 src/test/java/dev/caskeleton/adapter/outbound/notification/platform/autoconfigure/SmtpProviderRuntimeAssemblerTest.java 133
=== 8.1 활성화 게이트 전수 ===
$ grep -rn 'ConditionalOn' adapter/outbound/notification/src/main/java/dev/caskeleton/adapter/outbound/notification/platform/autoconfigure/*.java adapter/outbound/notification/src/main/java/dev/caskeleton/adapter/outbound/notification/*.java | sed 's|.*/notification/||'
platform/autoconfigure/NotificationPlatformAutoConfiguration.java:19:import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
platform/autoconfigure/NotificationPlatformAutoConfiguration.java:20:import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
platform/autoconfigure/NotificationPlatformAutoConfiguration.java:34:@ConditionalOnProperty(
platform/autoconfigure/NotificationPlatformAutoConfiguration.java:42: @ConditionalOnMissingBean
platform/autoconfigure/NotificationPlatformAutoConfiguration.java:49: @ConditionalOnMissingBean
platform/autoconfigure/NotificationPlatformAutoConfiguration.java:56: @ConditionalOnMissingBean
platform/autoconfigure/NotificationPlatformAutoConfiguration.java:70: @ConditionalOnMissingBean
platform/autoconfigure/NotificationPlatformAutoConfiguration.java:88: * <p>{@code @ConditionalOnBean(SecretMaterialProvider.class)} used to guard this, to express
platform/autoconfigure/NotificationPlatformAutoConfiguration.java:102: @ConditionalOnMissingBean
platform/autoconfigure/NotificationPlatformAutoConfiguration.java:114: @ConditionalOnMissingBean
platform/autoconfigure/NotificationSmtpProviderConfig.java:17:import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
platform/autoconfigure/NotificationSmtpProviderConfig.java:18:import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
platform/autoconfigure/NotificationSmtpProviderConfig.java:58:@ConditionalOnProperty(name = "spring.mail.host")
platform/autoconfigure/NotificationSmtpProviderConfig.java:64: @ConditionalOnMissingBean
platform/autoconfigure/NotificationSmtpProviderConfig.java:78: @ConditionalOnMissingBean
platform/autoconfigure/NotificationSmtpProviderConfig.java:88: @ConditionalOnMissingBean
platform/autoconfigure/NotificationSmtpProviderConfig.java:102: @ConditionalOnMissingBean
platform/autoconfigure/NotificationSmtpProviderConfig.java:113: @ConditionalOnMissingBean
platform/autoconfigure/NotificationSmtpProviderConfig.java:128: @ConditionalOnMissingBean(name = "notificationSmtpExecutor")
platform/autoconfigure/NotificationSmtpProviderConfig.java:162: @ConditionalOnMissingBean(name = "smtpProviderRuntimeAssembler")
exit=0
$ git ls-files 'adapter/outbound/notification/src/main/resources' 2>/dev/null | head; echo 'main resources: none tracked'
main resources: none tracked
exit=0
$ git grep -rn 'NotificationPlatformAutoConfiguration\|NotificationConfig' -- 'app-bootstrap/src/main' | head
app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/NotificationRootAutoConfiguration.java:3:import dev.caskeleton.adapter.outbound.notification.NotificationConfig;
app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/NotificationRootAutoConfiguration.java:6:import dev.caskeleton.adapter.outbound.notification.platform.autoconfigure.NotificationPlatformAutoConfiguration;
app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/NotificationRootAutoConfiguration.java:27: * <p>{@link NotificationPlatformAutoConfiguration} is named here for a blunter reason: nothing
app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/NotificationRootAutoConfiguration.java:47: NotificationPlatformAutoConfiguration.class,
app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/NotificationRootAutoConfiguration.java:51: NotificationConfig.class,
app-bootstrap/src/main/resources/application.yml:959: # Add a provider = new files only (NotificationConfig); select it here.
exit=0
=== 8.2 INGEST_ONLY 가 실제로 readiness 를 내리는가 ===
$ git grep -rn 'INGEST_ONLY' -- 'adapter/outbound/notification/src/main' 'app-bootstrap/src/main' | head -20
adapter/outbound/notification/src/main/java/dev/caskeleton/adapter/outbound/notification/platform/autoconfigure/NotificationPlatformMode.java:23: INGEST_ONLY
adapter/outbound/notification/src/main/java/dev/caskeleton/adapter/outbound/notification/platform/autoconfigure/NotificationProviderAssembly.java:35: * <li>zero providers without {@link NotificationPlatformMode#INGEST_ONLY}, because a platform
adapter/outbound/notification/src/main/java/dev/caskeleton/adapter/outbound/notification/platform/autoconfigure/NotificationProviderAssembly.java:141: routes.isEmpty() ? NotificationPlatformMode.INGEST_ONLY : NotificationPlatformMode.SERVING);
adapter/outbound/notification/src/main/java/dev/caskeleton/adapter/outbound/notification/platform/autoconfigure/NotificationProviderAssembly.java:183: if (routes.isEmpty() && mode != NotificationPlatformMode.INGEST_ONLY) {
adapter/outbound/notification/src/main/java/dev/caskeleton/adapter/outbound/notification/platform/autoconfigure/NotificationProviderAssembly.java:187: + " declare ca-skeleton.notification.platform.mode=INGEST_ONLY so readiness reports"
adapter/outbound/notification/src/main/java/dev/caskeleton/adapter/outbound/notification/platform/autoconfigure/NotificationSecretRequirements.java:83: * The purposes every mode needs, including {@code INGEST_ONLY}.
app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/NotificationPlatformWorkerConfig.java:128: * Everything that starts a thread, and therefore everything {@code INGEST_ONLY} must not have.
app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/NotificationPlatformWorkerConfig.java:132: * method — so a deployment in {@code INGEST_ONLY}, the mode whose whole purpose is to accept and
app-bootstrap/src/main/resources/application.yml:487: # SERVING | INGEST_ONLY. SERVING refuses to start without a working provider; INGEST_ONLY
exit=0
=== 8.3 실행 probe: dependencyPolicy 와 API 표면 ===
$ ./gradlew :adapter:outbound:notification:verifyDependencyPolicy → BUILD SUCCESSFUL (EXIT=0)
$ ./gradlew verifyNotificationApiSurface → OK — 586 public types, unchanged (EXIT=0)
$ wc -l ../docs/notification/api-surface-snapshot.txt
589 ../docs/notification/api-surface-snapshot.txt
exit=0
=== 8.4 비밀 요구가 능력에 따라 좁아지는가 ===
$ grep -n 'ALWAYS\|required.add' adapter/outbound/notification/src/main/java/dev/caskeleton/adapter/outbound/notification/platform/autoconfigure/NotificationSecretRequirements.java
36: Set<SecretPurpose> required = EnumSet.copyOf(ALWAYS);
42: required.add(SecretPurpose.CALLBACK_SIGNING);
43: required.add(SecretPurpose.CALLBACK_FINGERPRINT_HMAC);
54: required.add(SecretPurpose.PROVIDER_CREDENTIAL);
57: required.add(SecretPurpose.VAPID_SIGNING);
63: required.add(SecretPurpose.CALLBACK_SIGNING);
92: private static final Set<SecretPurpose> ALWAYS =
exit=0
$ git grep -h ' void [a-z]' -- 'adapter/outbound/notification/src/test/java/dev/caskeleton/adapter/outbound/notification/platform/autoconfigure' | sed 's/^ *//'
void productionWebPushWithoutVapidKeyFailsStartup() {
void apnsWithoutTopicFailsStartup() {
void aCallbackCapableProviderRequiresASigningSecret() {
void aDisabledProfileIsNotValidated() {
void ambiguousFallbackCannotBeEnabled() {
void anUnboundedClaimBatchIsRefused() {
void aLeaseShorterThanThePollIntervalIsRefused() {
void defaultsAreUsableAndConservative() {
void aConfiguredProfileBecomesARuntimeAndARoute() {
void anUnknownProviderTypeFailsTheBoot() {
void aFamilyWithNoAssemblerIsRefused() {
void twoProfilesOnOneChannelNeedAPrimary() {
void aMarkedPrimaryResolvesTheAmbiguity() {
void twoPrimariesAreRefused() {
void aDisabledProfileAssemblesNothing() {
void anEnabledPlatformWithNoProviderRefusesToStart() {
void ingestOnlyIsADeclaration() {
void twoAssemblersForOneFamilyAreRefused() {
void requiredDocumentationAndAdrsExist() {
void supportMatrixDoesNotClaimGuaranteedDelivery() throws IOException {
void theEvidenceDocumentStatesTheForbiddenPromotions() throws IOException {
void theAcceptPathKeysAreAlwaysRequired() {
void anSmtpOnlyPlatformIsNotAskedForAProviderCredential() {
void aDisabledProfileDoesNotDemandItsKeys() {
void anEnabledWebPushProfileDemandsItsKeys() {
void callbacksOffDoNotDemandTheCallbackKeys() {
void callbacksOnDemandBothCallbackKeys() {
void aProfileNamingASigningRefDemandsTheSigningPurpose() {
void theAssemblerServesSmtpOnEmail() {
void aConfiguredProfileAssemblesIntoARuntime() {
void smtpClaimsNothingItCannotDo() {
void thePerProfileBoundsComeFromTheProfile() {
exit=0