Sub-scope 02·03 (notification) catalog + template + platform/dispatch — manifest and probes
revision=a24ece9cf797f7ea647e33bf846b115208ed1ba5
generatedAt=2026-08-30T06:40:03+00:00

=== OWNED FILES (sub-scope 02) ===
589a6a1e880e9f6e3a277bb8873c4dc166caee90 main/java/dev/caskeleton/adapter/outbound/notification/catalog/CompiledNotificationBinding.java 151
6a3825e6e2a64b0a7adb54ec9753c7278b677f21 main/java/dev/caskeleton/adapter/outbound/notification/catalog/NotificationBindingCompiler.java 516
066b7a1d0ae6cfeb428edc3330bdb898830dfc27 main/java/dev/caskeleton/adapter/outbound/notification/catalog/NotificationCanonicalRouteCatalog.java 124
e7e5ef12bf86a5b829f41ccf0ebc5e4a23ada484 main/java/dev/caskeleton/adapter/outbound/notification/catalog/NotificationCatalogException.java 70
2fd0408dfc2faaf79535f2ab7214e8885aa0e5d0 main/java/dev/caskeleton/adapter/outbound/notification/catalog/NotificationCutoverRouteCatalog.java 151
ddcab4056acd253a408b642fb14272733dbfce4f main/java/dev/caskeleton/adapter/outbound/notification/catalog/NotificationPlanAdapter.java 86
4eefb8246e58aeb032496078beafc06d584e6d64 main/java/dev/caskeleton/adapter/outbound/notification/catalog/NotificationProviderCapabilityCard.java 99
a5f6322e2013f6aa647307d1a485bb973b6cd949 main/java/dev/caskeleton/adapter/outbound/notification/catalog/NotificationProviderCapabilityDescriptorSource.java 43
9ebffdeb3505ced57d8607a8764b0cf32b67ee56 main/java/dev/caskeleton/adapter/outbound/notification/catalog/NotificationProviderDescriptor.java 41
da9f2cd56fe0d9498671b4663c2b63037b325d8b main/java/dev/caskeleton/adapter/outbound/notification/catalog/NotificationProviderRuntimeProfile.java 45
e473bfa621c8a655884378b9faadfe0343a91168 main/java/dev/caskeleton/adapter/outbound/notification/catalog/NotificationRouteDescriptor.java 93
b8d3e1709002af836d344aabee0f694e637a68ad main/java/dev/caskeleton/adapter/outbound/notification/catalog/NotificationTemplateDescriptor.java 81
4652840326390e8ae5ef66a9c13688a153f12980 main/java/dev/caskeleton/adapter/outbound/notification/template/LocalEmailRenderer.java 473
17f3acb4ca227f6a17ebd7221f8cdfcc4bff56a1 main/java/dev/caskeleton/adapter/outbound/notification/template/NotificationTemplateCatalog.java 253
0549f405ed971fd795f9dc9d9d96a9e426e1fb60 main/java/dev/caskeleton/adapter/outbound/notification/template/NotificationTemplateManifest.java 175
79524df5cc59327e111645de9010197b4cb526a0 main/java/dev/caskeleton/adapter/outbound/notification/template/NotificationTemplateRenderer.java 10
de1aa1eebb973d60204fc214e8f7379433ee9d69 main/java/dev/caskeleton/adapter/outbound/notification/template/RenderedNotification.java 112
3e61ef2951ac929a0be7e22978156c57b1db0d9b main/java/dev/caskeleton/adapter/outbound/notification/template/SlackBlockKitRenderer.java 124
7627cf28e4ce66e34cc0e7d1466ae4a852efda7f main/java/dev/caskeleton/adapter/outbound/notification/template/TemplateRenderingException.java 15
4e9c525557909ddc38910e77322714a60e5bf2c9 test/java/dev/caskeleton/adapter/outbound/notification/catalog/NotificationBindingCompilerTest.java 489
d0f1992101e31137cf7e50f1480f4c66be10ff3f test/java/dev/caskeleton/adapter/outbound/notification/catalog/NotificationCanonicalRouteCatalogTest.java 54
e162f1a8c256cc78986e97e2b1efcbd0f497fc79 test/java/dev/caskeleton/adapter/outbound/notification/catalog/NotificationCutoverRouteCatalogTest.java 123
979fe69fa706843c2a748a859b981b7b6bb3e88e test/java/dev/caskeleton/adapter/outbound/notification/template/NotificationTemplateRendererTest.java 390
=== OWNED FILES (sub-scope 03) ===
d97258758e960640cc23e46b30ddd29b32f2d046 main/java/dev/caskeleton/adapter/outbound/notification/platform/dispatch/AttemptPermit.java 17
e565164aed62148e5d5c97aae536c7f30f850014 main/java/dev/caskeleton/adapter/outbound/notification/platform/dispatch/CapabilityReconciliationGateway.java 52
009388b82da767c2f96d36721f9f96ba492ccf24 main/java/dev/caskeleton/adapter/outbound/notification/platform/dispatch/ConfiguredProfileCatalog.java 31
b41729af6a8adf105d2cb005a5336ab404a01a2e main/java/dev/caskeleton/adapter/outbound/notification/platform/dispatch/CredentialProbe.java 9
20c658fd89c69e530a8f2fd6accd24f67f94a281 main/java/dev/caskeleton/adapter/outbound/notification/platform/dispatch/CredentialValidationException.java 19
6693cd0cbe52d2f4b425f7c4f221cbd85c1ccaf0 main/java/dev/caskeleton/adapter/outbound/notification/platform/dispatch/JacksonRoutingPlanCodec.java 61
ea00a346eeebafb0d177141e459c0998a9f2af07 main/java/dev/caskeleton/adapter/outbound/notification/platform/dispatch/LeaseRecoveryService.java 144
24e24f3cc32ed115ba61461f76e57b6beaba2d06 main/java/dev/caskeleton/adapter/outbound/notification/platform/dispatch/LoggingInboxSignalPublisher.java 29
b2e3d8dbd976dab548210b801f255a7dfce5b9bb main/java/dev/caskeleton/adapter/outbound/notification/platform/dispatch/MapTemplateRendererRegistry.java 31
3187117e3e1e0479a2a8cc4a1cdea4c9e3b0d5d7 main/java/dev/caskeleton/adapter/outbound/notification/platform/dispatch/NotificationBackgroundWorkers.java 141
63ff2c75298ae8aeb05b4996feacc85481db5802 main/java/dev/caskeleton/adapter/outbound/notification/platform/dispatch/NotificationDispatchProperties.java 55
d761c92316165a52e85e1014777063b5ebf122d9 main/java/dev/caskeleton/adapter/outbound/notification/platform/dispatch/NotificationSchedulerWorker.java 197
2d3e20668e901868f84112567b4a0b6719f69407 main/java/dev/caskeleton/adapter/outbound/notification/platform/dispatch/ProviderAttemptLimiter.java 102
050f8ac3c0272ddeb96d5c44bf8134d8e0c2e659 main/java/dev/caskeleton/adapter/outbound/notification/platform/dispatch/ProviderEventReplayWorker.java 77
61e84aa2f97184dbe1cfb04ecbdcebd0f1e820bd main/java/dev/caskeleton/adapter/outbound/notification/platform/dispatch/ProviderRuntime.java 276
93b652bcf9806dd8fe8a660c8bb999f91b9deff0 main/java/dev/caskeleton/adapter/outbound/notification/platform/dispatch/ProviderRuntimeRegistry.java 140
c9cd5ec389c5bb3ece5af8c05a747cdd280bf30f main/java/dev/caskeleton/adapter/outbound/notification/platform/dispatch/ProviderRuntimeRotator.java 69
807b6f1df09c4dcae4b8e9cd2f04de3aa55221cc main/java/dev/caskeleton/adapter/outbound/notification/platform/dispatch/ReconciliationJobWorker.java 132
9249058d4eabaf9e6c68404350328fcea1463eb8 main/java/dev/caskeleton/adapter/outbound/notification/platform/dispatch/RegistryProviderDispatchGateway.java 76
b5ad665c4752288a260609acc3af2fdadf672461 main/java/dev/caskeleton/adapter/outbound/notification/platform/dispatch/RegistryProviderRuntimeControl.java 50
8db03d0736babf4eafa5043caabaad0de9d08045 main/java/dev/caskeleton/adapter/outbound/notification/platform/dispatch/RuntimeDrainCoordinator.java 46
4b0fae795e4a49db9cb29836cf4bcfbb10b1d64f main/java/dev/caskeleton/adapter/outbound/notification/platform/dispatch/SingleTenantContext.java 27
938430f0e355959ec427b43212ee8cb1176c1070 main/java/dev/caskeleton/adapter/outbound/notification/platform/dispatch/UuidV7Generator.java 54
0fd3c92679909482c3df8e44e1aa6d818d6f3080 test/java/dev/caskeleton/adapter/outbound/notification/platform/dispatch/LeaseRecoveryServiceTest.java 324
96670aa796637412e44af63443abda5096f9ec7e test/java/dev/caskeleton/adapter/outbound/notification/platform/dispatch/ProviderAttemptLimiterConcurrencyTest.java 193
30391872da4734092c3183df872dd002c22b6efe test/java/dev/caskeleton/adapter/outbound/notification/platform/dispatch/ProviderRuntimeRegistryConcurrencyTest.java 194
46a8ac0fafc9355c5c1d33c735730367ba6ce8be test/java/dev/caskeleton/adapter/outbound/notification/platform/dispatch/ProviderRuntimeRegistryTest.java 192
6fe877abe56e01992ebdabace964dce28ea0b8d1 test/java/dev/caskeleton/adapter/outbound/notification/platform/dispatch/ProviderRuntimeRotationTest.java 96
44f27b336d0675c619f11054eea30877706efc7e test/java/dev/caskeleton/adapter/outbound/notification/platform/dispatch/ProviderRuntimeStateTest.java 241
01101163c77558ebc864f75705f3b6e2b673ad81 test/java/dev/caskeleton/adapter/outbound/notification/platform/dispatch/ReconciliationJobWorkerTest.java 180

=== 8.1 렌더링 컨텍스트 사슬 ===
$ grep -n 'case "' adapter/outbound/notification/src/main/java/dev/caskeleton/adapter/outbound/notification/template/LocalEmailRenderer.java | head -20
182:        case "html_text" -> {
188:        case "html_attr" -> {
194:        case "url_component" -> {
224:            case "header" -> {
231:            case "text" -> raw;
232:            case "html_text" -> html(raw);
233:            case "html_attr" -> htmlAttribute(template, matcher, raw);
234:            case "url_component" -> {
exit=0

$ grep -n 'APPROVED_HTML_TAGS =' -A 6 adapter/outbound/notification/src/main/java/dev/caskeleton/adapter/outbound/notification/template/LocalEmailRenderer.java
20:  private static final java.util.Set<String> APPROVED_HTML_TAGS =
21-      java.util.Set.of(
22-          "a", "b", "body", "br", "div", "em", "h1", "h2", "h3", "h4", "h5", "h6", "head", "html",
23-          "i", "img", "li", "ol", "p", "span", "strong", "table", "tbody", "td", "th", "thead",
24-          "tr", "ul");
25-  private static final java.util.Set<String> APPROVED_INERT_ATTRIBUTES =
26-      java.util.Set.of("alt", "aria-label", "title");
exit=0

$ grep -n 'quoteReplacement\|unresolved parameter\|line break' adapter/outbound/notification/src/main/java/dev/caskeleton/adapter/outbound/notification/template/LocalEmailRenderer.java
62:      throw new TemplateRenderingException("rendered email header contains a line break");
227:                    "rendered email header parameter contains a line break");
243:      matcher.appendReplacement(output, Matcher.quoteReplacement(replacement));
247:      throw new TemplateRenderingException("template has an unresolved parameter");
exit=0

=== 8.2 템플릿 자산이 체크섬으로 고정되는가 ===
$ grep -n 'checksum' adapter/outbound/notification/src/main/java/dev/caskeleton/adapter/outbound/notification/template/NotificationTemplateCatalog.java | head -10
20: * Loads only manifest-named classpath assets, validates exact checksums, and retains immutable
52:              String actual = checksumLoaded(sources);
53:              if (!actual.equals(manifest.checksum())) {
54:                throw new TemplateRenderingException("template manifest checksum mismatch");
92:  public static String checksum(
96:    return checksumLoaded(
125:  private static String checksumLoaded(Map<Locale, LoadedResourceSet> sources) {
exit=0

$ grep -n 'templateChecksum\|rendererRevision' adapter/outbound/notification/src/main/java/dev/caskeleton/adapter/outbound/notification/template/LocalEmailRenderer.java
93:    if (!plan.binding().templateChecksum().equals(manifest.checksum())) {
96:    if (!plan.binding().rendererRevision().equals(manifest.rendererRevision())) {
exit=0

=== 8.3 배경 작업자가 실제로 스케줄되는가 (과거 미배선 수정) ===
$ git grep -rn 'NotificationBackgroundWorkers\|LeaseRecoveryService\|ProviderEventReplayWorker\|ReconciliationJobWorker' -- 'adapter/outbound/notification/src/main' 'app-bootstrap/src/main' | grep -v 'platform/dispatch/' | head
app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/NotificationPlatformWorkerConfig.java:3:import dev.caskeleton.adapter.outbound.notification.platform.dispatch.LeaseRecoveryService;
app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/NotificationPlatformWorkerConfig.java:4:import dev.caskeleton.adapter.outbound.notification.platform.dispatch.NotificationBackgroundWorkers;
app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/NotificationPlatformWorkerConfig.java:7:import dev.caskeleton.adapter.outbound.notification.platform.dispatch.ProviderEventReplayWorker;
app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/NotificationPlatformWorkerConfig.java:8:import dev.caskeleton.adapter.outbound.notification.platform.dispatch.ReconciliationJobWorker;
app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/NotificationPlatformWorkerConfig.java:57:  public ReconciliationJobWorker reconciliationJobWorker(
app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/NotificationPlatformWorkerConfig.java:63:    return new ReconciliationJobWorker(
app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/NotificationPlatformWorkerConfig.java:90:  public LeaseRecoveryService notificationLeaseRecoveryService(
app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/NotificationPlatformWorkerConfig.java:99:    return new LeaseRecoveryService(
app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/NotificationPlatformWorkerConfig.java:119:  public ProviderEventReplayWorker notificationProviderEventReplayWorker(
app-bootstrap/src/main/java/dev/caskeleton/bootstrap/notification/NotificationPlatformWorkerConfig.java:124:    return new ProviderEventReplayWorker(ledger, projection, properties.claimBatchSize());
exit=0

=== 8.4 dispatch test ===
$ git ls-files 'adapter/outbound/notification/src/test/java/dev/caskeleton/adapter/outbound/notification/platform/dispatch' 'adapter/outbound/notification/src/test/java/dev/caskeleton/adapter/outbound/notification/catalog' 'adapter/outbound/notification/src/test/java/dev/caskeleton/adapter/outbound/notification/template' | xargs -n1 basename
NotificationBindingCompilerTest.java
NotificationCanonicalRouteCatalogTest.java
NotificationCutoverRouteCatalogTest.java
LeaseRecoveryServiceTest.java
ProviderAttemptLimiterConcurrencyTest.java
ProviderRuntimeRegistryConcurrencyTest.java
ProviderRuntimeRegistryTest.java
ProviderRuntimeRotationTest.java
ProviderRuntimeStateTest.java
ReconciliationJobWorkerTest.java
NotificationTemplateRendererTest.java
exit=0

$ git grep -h '  void [a-z]' -- 'adapter/outbound/notification/src/test/java/dev/caskeleton/adapter/outbound/notification/platform/dispatch' | sed 's/^ *//' | head -30
void aDeliveryWithNoAttemptIsRequeued() {
void aDeliveryWithAnIncompleteAttemptIsReconciled() {
void anAttemptProvenNeverStartedIsRequeued() {
void anAttemptThatMayHaveStartedIsReconciled() {
void theDecisionDependsOnCertaintyNotValueAlone() {
void recoveryAlsoRetiresExpiredDeliveries() {
void advanceOneSecond() {
void oneSecondAdmitsExactlyTheRate() throws InterruptedException {
void theWindowAfterARolloverAdmitsTheRateOnce() throws InterruptedException {
void aRefusedSlotIsReturnedToItsOwnWindow() {
void aRefundDoesNotCreditTheNextWindow() {
void registeringTwiceIsReported() {
void anOlderGenerationIsRefused() {
void concurrentRotationsLeaveTheHighestGenerationCurrent() throws InterruptedException {
void aDrainingGenerationWithWorkInFlightSurvivesASweep() throws InterruptedException {
void authenticationFailureRejectsNewAttemptsWithoutConsumingPermit() {
void replacementKeepsOldRuntimeDrainingUntilItsAttemptsFinish() {
void newAttemptUsesNewGenerationWhileOldAttemptDrains() {
void failedNewCredentialKeepsOldRuntimeActive() {
void concurrencyLimitFailsFastRatherThanQueueing() {
void rotationAuditRecordsGenerationButNoCredentialMaterial() {
void activationRecordsTheGenerationAndItsKeyIdButNeverTheMaterial() {
void aGenerationThatDoesNotSupersedeTheActiveOneIsRefused() {
void anUnresolvableKeyIdFailsTheRotationRatherThanTheFirstNotification() {
void aKeyIssuedForAnotherPurposeCannotBecomeAProviderCredential() {
void contactEncryptionAndVapidRotationAreRefusedWithTheMigrationTheyActuallyNeed() {
void materialIsResolvedPerCallSoARotationIsNotOutlivedByACachedCredential() {
void closingAPermitTwiceDoesNotRaiseTheCeiling() {
void concurrentDoubleClosesReleaseOneSlotEach() throws InterruptedException {
void aSuccessClearsThrottlingAndDegradation() {
exit=0

