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>
191 lines
12 KiB
Plaintext
191 lines
12 KiB
Plaintext
adapter-outbound-notification — 모듈 인벤토리
|
|
revision=a24ece9cf797f7ea647e33bf846b115208ed1ba5
|
|
generatedAt=2026-08-30T06:35:30+00:00
|
|
|
|
=== tracked file 총계 ===
|
|
$ git ls-files 'adapter/outbound/notification' | wc -l
|
|
240
|
|
exit=0
|
|
|
|
$ for d in $(git ls-files 'adapter/outbound/notification/src' | sed 's|^adapter/outbound/notification/src/||' | cut -d/ -f1 | sort -u); do n=$(git ls-files "adapter/outbound/notification/src/$d" | wc -l); l=$(git ls-files "adapter/outbound/notification/src/$d" | xargs wc -l 2>/dev/null | tail -1 | awk '{print $1}'); echo "$d: $n files, $l LOC"; done
|
|
main: 171 files, 14695 LOC
|
|
test: 65 files, 10045 LOC
|
|
exit=0
|
|
|
|
=== main 패키지 배치 ===
|
|
$ git ls-files 'adapter/outbound/notification/src/main' | grep '\.java$' | sed 's|.*/dev/caskeleton/||' | xargs -n1 dirname | sort | uniq -c | sort -rn
|
|
23 adapter/outbound/notification/platform/dispatch
|
|
15 adapter/outbound/notification/provider
|
|
14 adapter/outbound/notification/platform/template
|
|
12 adapter/outbound/notification/catalog
|
|
11 adapter/outbound/notification/platform/provider/ses
|
|
11 adapter/outbound/notification/platform/autoconfigure
|
|
10 adapter/outbound/notification/platform/provider/webpush
|
|
10 adapter/outbound/notification/platform/provider/fcm
|
|
9 adapter/outbound/notification/platform/provider/twilio
|
|
7 adapter/outbound/notification/template
|
|
7 adapter/outbound/notification/platform/security
|
|
7 adapter/outbound/notification/platform/provider/smtp
|
|
6 adapter/outbound/notification/platform/provider/http
|
|
5 adapter/outbound/notification/platform/observation
|
|
4 adapter/outbound/notification/platform/provider/apns
|
|
3 adapter/outbound/notification/slack/webhook
|
|
3 adapter/outbound/notification/platform/reactor
|
|
3 adapter/outbound/notification/platform/provider/webhook
|
|
3 adapter/outbound/notification/platform/provider
|
|
3 adapter/outbound/notification/email/google
|
|
3 adapter/outbound/notification/core
|
|
2 adapter/outbound/notification
|
|
exit=0
|
|
|
|
=== test 패키지 배치 ===
|
|
$ git ls-files 'adapter/outbound/notification/src/test' | grep '\.java$' | sed 's|.*/dev/caskeleton/||' | xargs -n1 dirname | sort | uniq -c | sort -rn
|
|
9 adapter/outbound/notification/platform/testkit
|
|
7 adapter/outbound/notification/platform/security
|
|
7 adapter/outbound/notification/platform/dispatch
|
|
5 adapter/outbound/notification/provider
|
|
5 adapter/outbound/notification/platform/autoconfigure
|
|
4 adapter/outbound/notification/platform/template
|
|
3 adapter/outbound/notification/platform/provider/twilio
|
|
3 adapter/outbound/notification/platform/provider/http
|
|
3 adapter/outbound/notification/catalog
|
|
2 adapter/outbound/notification/platform/provider/webpush
|
|
2 adapter/outbound/notification/platform/provider/smtp
|
|
2 adapter/outbound/notification/platform/provider/ses
|
|
2 adapter/outbound/notification/platform/provider/fcm
|
|
2 adapter/outbound/notification/platform/provider
|
|
2 adapter/outbound/notification/core
|
|
1 adapter/outbound/notification/template
|
|
1 adapter/outbound/notification/platform/provider/webhook
|
|
1 adapter/outbound/notification/platform/provider/apns
|
|
exit=0
|
|
|
|
=== main 상위 25 ===
|
|
$ git ls-files 'adapter/outbound/notification/src/main' | grep '\.java$' | xargs wc -l | sort -rn | head -26
|
|
14695 total
|
|
516 adapter/outbound/notification/src/main/java/dev/caskeleton/adapter/outbound/notification/catalog/NotificationBindingCompiler.java
|
|
473 adapter/outbound/notification/src/main/java/dev/caskeleton/adapter/outbound/notification/template/LocalEmailRenderer.java
|
|
363 adapter/outbound/notification/src/main/java/dev/caskeleton/adapter/outbound/notification/provider/NotificationProviderAttemptAdapter.java
|
|
276 adapter/outbound/notification/src/main/java/dev/caskeleton/adapter/outbound/notification/platform/dispatch/ProviderRuntime.java
|
|
258 adapter/outbound/notification/src/main/java/dev/caskeleton/adapter/outbound/notification/platform/provider/webhook/WebhookNotificationProviderAdapter.java
|
|
253 adapter/outbound/notification/src/main/java/dev/caskeleton/adapter/outbound/notification/template/NotificationTemplateCatalog.java
|
|
239 adapter/outbound/notification/src/main/java/dev/caskeleton/adapter/outbound/notification/platform/autoconfigure/NotificationProviderAssembly.java
|
|
228 adapter/outbound/notification/src/main/java/dev/caskeleton/adapter/outbound/notification/platform/security/ProviderCredentialManager.java
|
|
220 adapter/outbound/notification/src/main/java/dev/caskeleton/adapter/outbound/notification/platform/security/AesGcmContactPointProtector.java
|
|
211 adapter/outbound/notification/src/main/java/dev/caskeleton/adapter/outbound/notification/platform/autoconfigure/NotificationSmtpProviderConfig.java
|
|
209 adapter/outbound/notification/src/main/java/dev/caskeleton/adapter/outbound/notification/platform/provider/ses/SesRequestMapper.java
|
|
197 adapter/outbound/notification/src/main/java/dev/caskeleton/adapter/outbound/notification/platform/dispatch/NotificationSchedulerWorker.java
|
|
193 adapter/outbound/notification/src/main/java/dev/caskeleton/adapter/outbound/notification/platform/provider/webpush/Rfc8291Aes128GcmEncryptor.java
|
|
190 adapter/outbound/notification/src/main/java/dev/caskeleton/adapter/outbound/notification/platform/autoconfigure/NotificationPlatformSettings.java
|
|
185 adapter/outbound/notification/src/main/java/dev/caskeleton/adapter/outbound/notification/platform/template/CanonicalNotificationRenderer.java
|
|
175 adapter/outbound/notification/src/main/java/dev/caskeleton/adapter/outbound/notification/template/NotificationTemplateManifest.java
|
|
169 adapter/outbound/notification/src/main/java/dev/caskeleton/adapter/outbound/notification/platform/provider/ses/SesNotificationProviderAdapter.java
|
|
164 adapter/outbound/notification/src/main/java/dev/caskeleton/adapter/outbound/notification/platform/autoconfigure/SmtpProviderRuntimeAssembler.java
|
|
163 adapter/outbound/notification/src/main/java/dev/caskeleton/adapter/outbound/notification/provider/NotificationAdmissionReadinessAdapter.java
|
|
162 adapter/outbound/notification/src/main/java/dev/caskeleton/adapter/outbound/notification/platform/template/ThymeleafStringTemplateEngine.java
|
|
159 adapter/outbound/notification/src/main/java/dev/caskeleton/adapter/outbound/notification/platform/security/AesGcmNotificationPayloadProtection.java
|
|
151 adapter/outbound/notification/src/main/java/dev/caskeleton/adapter/outbound/notification/provider/NotificationReconciliationAdapter.java
|
|
151 adapter/outbound/notification/src/main/java/dev/caskeleton/adapter/outbound/notification/catalog/NotificationCutoverRouteCatalog.java
|
|
151 adapter/outbound/notification/src/main/java/dev/caskeleton/adapter/outbound/notification/catalog/CompiledNotificationBinding.java
|
|
147 adapter/outbound/notification/src/main/java/dev/caskeleton/adapter/outbound/notification/platform/provider/smtp/SmtpMimeMessageFactory.java
|
|
exit=0
|
|
|
|
=== 비 Java ===
|
|
$ git ls-files 'adapter/outbound/notification' | grep -v '\.java$'
|
|
adapter/outbound/notification/CLAUDE.md
|
|
adapter/outbound/notification/README.md
|
|
adapter/outbound/notification/build.gradle
|
|
adapter/outbound/notification/gradle.lockfile
|
|
adapter/outbound/notification/src/test/resources/notification/templates/email/contract-v1.html
|
|
adapter/outbound/notification/src/test/resources/notification/templates/email/contract-v1.subject.txt
|
|
adapter/outbound/notification/src/test/resources/notification/templates/email/contract-v1.text.txt
|
|
adapter/outbound/notification/src/test/resources/notification/templates/slack/contract-v1.txt
|
|
exit=0
|
|
|
|
=== 레지스트리 ===
|
|
$ python3 -c "
|
|
import json;d=json.load(open('config/architecture/modules.json'))
|
|
for m in d['modules']:
|
|
if m['id']=='adapter-outbound-notification': print(json.dumps(m,ensure_ascii=False,indent=2))
|
|
"
|
|
{
|
|
"id": "adapter-outbound-notification",
|
|
"gradle_path": ":adapter:outbound:notification",
|
|
"source_path": "src/adapter/outbound/notification",
|
|
"allowed_dependencies": [
|
|
"domain-core",
|
|
"application-core",
|
|
"shared-contract",
|
|
"adapter-outbound-support"
|
|
],
|
|
"runtime_memberships": [
|
|
"app-bootstrap"
|
|
]
|
|
}
|
|
exit=0
|
|
|
|
=== build.gradle ===
|
|
$ cat -n 'adapter/outbound/notification/build.gradle'
|
|
1 dependencies {
|
|
2 implementation project(':application-core')
|
|
3 implementation project(':shared-contract')
|
|
4 implementation project(':adapter:outbound:support')
|
|
5
|
|
6 implementation 'org.springframework.boot:spring-boot-autoconfigure'
|
|
7 implementation 'org.springframework:spring-web' // Slack webhook client (RestClient)
|
|
8 implementation 'org.slf4j:slf4j-api'
|
|
9
|
|
10 // Notification Delivery Platform.
|
|
11 // - mail: the SMTP provider adapter is built on JavaMailSender/MimeMessageHelper, which is where
|
|
12 // multipart/alternative, inline resources and header validation already live. Rebuilding MIME
|
|
13 // by hand to avoid one dependency would be the more dangerous choice.
|
|
14 // - jackson-databind: provider payloads, callback bodies and the canonical variables payload are
|
|
15 // JSON. It stays inside this adapter; application-core never sees a JSON type.
|
|
16 // - reactor-core: only the optional Reactor facade uses it. The core async type stays
|
|
17 // CompletionStage, so nothing else on this classpath depends on Reactor.
|
|
18 implementation 'org.springframework.boot:spring-boot-starter-mail'
|
|
19 implementation 'org.springframework.boot:spring-boot-starter-json'
|
|
20 implementation 'io.projectreactor:reactor-core'
|
|
21 // JSON Schema 2020-12 validation of template variables, using the same validator and version the
|
|
22 // messaging adapter already depends on rather than a second implementation of the same spec.
|
|
23 //
|
|
24 // Jackson's YAML dataformat is excluded because schemas arrive as JSON strings and a second
|
|
25 // parser for a format this leaf never reads is surface for nothing. It does not remove YAML from
|
|
26 // the runtime — org.yaml:snakeyaml is on this classpath via spring-boot-starter, which is how
|
|
27 // Spring Boot reads application.yml. The comment here used to claim the stronger outcome, and
|
|
28 // the resolved graph had said otherwise for as long as it stood; dependencyPolicy below now
|
|
29 // states the claim the build can check.
|
|
30 // Thymeleaf is the reference HTML renderer, added as the engine only — not the Spring
|
|
31 // starter, which would drag a view resolver and a servlet integration onto an outbound
|
|
32 // adapter that renders strings and never serves a request.
|
|
33 implementation 'org.thymeleaf:thymeleaf'
|
|
34
|
|
35 implementation('com.networknt:json-schema-validator:3.0.2') {
|
|
36 exclude group: 'tools.jackson.dataformat', module: 'jackson-dataformat-yaml'
|
|
37 exclude group: 'com.fasterxml.jackson.dataformat', module: 'jackson-dataformat-yaml'
|
|
38 }
|
|
39
|
|
40 annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
|
|
41
|
|
42 testImplementation 'io.projectreactor:reactor-test'
|
|
43 }
|
|
44 tasks.withType(JavaCompile).configureEach { options.encoding = 'UTF-8' }
|
|
45
|
|
46 // The exclusion above, stated as something the build verifies rather than something a comment
|
|
47 // asserts. verifyDependencyPolicy resolves runtimeClasspath and fails if the coordinate is present.
|
|
48 dependencyPolicy {
|
|
49 absent 'com.fasterxml.jackson.dataformat:jackson-dataformat-yaml',
|
|
50 because: 'schemas arrive as JSON strings; a second YAML parser is surface for a format ' +
|
|
51 'this leaf never reads'
|
|
52 absent 'tools.jackson.dataformat:jackson-dataformat-yaml',
|
|
53 because: 'the Jackson 3 coordinate of the same parser, excluded for the same reason'
|
|
54 }
|
|
exit=0
|
|
|
|
=== leaf 밖 소비자 ===
|
|
$ git grep -l 'dev.caskeleton.adapter.outbound.notification' -- . | grep -v '^adapter/outbound/notification/' | sed 's|/src/.*||' | sort | uniq -c
|
|
21 app-bootstrap
|
|
1 gradle/notification-api-surface.gradle
|
|
exit=0
|
|
|