11 KiB
Notification Delivery Platform — module mapping
Source design:
notification-superpowers-package/docs/superpowers/specs/2026-08-10-notification-platform-design.mdSource plan:
notification-superpowers-package/docs/superpowers/plans/2026-08-10-notification-platform-implementation-plan.md
Why a mapping exists
The plan was written against a hypothetical repository (modules/notification/**, root package
io.backend.skeleton.notification, 31 Gradle projects). This repository is a fail-closed
19-leaf Clean Architecture template: src/settings.gradle rejects any registry that does not
contain exactly the 19 modules in src/config/architecture/modules.json, and
verifyCleanArchitectureDependencies rejects any project edge outside allowed_dependencies.
Creating 31 new Gradle projects would violate HARD-STOP #5 of AGENTS.md. The package README
anticipates this and instructs the implementer to map dependency catalog and package/file paths onto
the host repository's rules while preserving the public contracts and reliability semantics.
Every logical module of the plan is therefore implemented as a package inside the registered leaf that owns its responsibility. No public contract, evidence rule, or reliability semantic is dropped.
Logical module → registered leaf
| Plan module | Registered leaf | Package |
|---|---|---|
notification-core-api |
application-core |
dev.caskeleton.application.notification.platform.api |
notification-content-api |
application-core |
…platform.api.content |
notification-contact-api |
application-core |
…platform.contact |
notification-template-api |
application-core |
…platform.template |
notification-policy |
application-core |
…platform.policy |
notification-provider-spi |
application-core |
…platform.provider |
notification-callback-api |
application-core |
…platform.callback |
notification-email-api |
application-core |
…platform.email |
notification-sms-api |
application-core |
…platform.sms |
notification-push-api |
application-core |
…platform.push |
notification-webpush (API half) |
application-core |
…platform.webpush |
notification-inbox-api |
application-core |
…platform.inbox |
notification-admin-api |
application-core |
…platform.admin |
notification-security (ports + redaction) |
application-core |
…platform.security |
notification-observability (ports) |
application-core |
…platform.observation |
notification-dispatch-runtime |
adapter:outbound:notification |
dev.caskeleton.adapter.outbound.notification.platform.dispatch |
notification-security (AES-GCM/HMAC impl) |
adapter:outbound:notification |
…platform.security |
notification-template-thymeleaf (reference renderer) |
adapter:outbound:notification |
…platform.template |
notification-email-smtp |
adapter:outbound:notification |
…platform.provider.smtp |
notification-email-ses |
adapter:outbound:notification |
…platform.provider.ses |
notification-sms-twilio |
adapter:outbound:notification |
…platform.provider.twilio |
notification-push-fcm |
adapter:outbound:notification |
…platform.provider.fcm |
notification-push-apns |
adapter:outbound:notification |
…platform.provider.apns |
notification-webpush (transport + crypto) |
adapter:outbound:notification |
…platform.provider.webpush |
notification-webhook-extension |
adapter:outbound:notification |
…platform.provider.webhook |
notification-observability (Micrometer impl) |
adapter:outbound:notification |
…platform.observation |
notification-admin-runtime |
adapter:outbound:notification |
…platform.admin |
notification-reactor |
adapter:outbound:notification |
…platform.reactor |
notification-spring-boot-starter |
adapter:outbound:notification (+ app-bootstrap wiring) |
…platform.autoconfigure |
notification-persistence-jpa |
adapter:outbound:persistence-jpa |
dev.caskeleton.adapter.outbound.persistence.notification.platform |
notification-inbox-jpa |
adapter:outbound:persistence-jpa |
…persistence.notification.platform.inbox |
notification-callback-mvc |
adapter:inbound:web |
dev.caskeleton.adapter.inbound.web.notification.platform.callback |
notification-callback-webflux |
adapter:inbound:web |
…callback.reactive |
notification-testkit |
test source sets of the owning leaves | …platform.testkit |
Dependency-direction consequences
The plan's module DAG (*-api → provider-spi/policy → runtime/adapters → starter) is preserved
by the leaf DAG that the registry already enforces:
application-core (all *-api, provider SPI, policy, callback contracts)
↑ ↑ ↑
adapter:outbound:notification adapter:outbound:persistence-jpa adapter:inbound:web
↑ ↑ ↑
app-bootstrap
Two plan edges cannot be expressed as project edges in this repository, and are replaced by ports:
notification-email-ses,notification-sms-twilio,notification-push-fcm,notification-push-apns,notification-webpush,notification-webhook-extension→httpclient platform.adapter-outbound-notificationis not allowed to depend onadapter-outbound-httpclient. The provider adapters therefore calldev.caskeleton.adapter.outbound.notification.platform.provider.http.NotificationHttpGateway, an adapter-local port with a JDKjava.net.http.HttpClientdefault implementation.app-bootstrapsees both leaves and is the supported place to substitute an implementation backed by the HTTP Client Platform (TLS/timeout/circuit-breaker/SSRF/dynamic-target policy reuse).notification-inbox-jpa→optional messaging outbox integration.adapter-outbound-persistence-jpamay not depend onadapter-outbound-messaging; the inbox publishes through the existing persistence outbox tables plus theNotificationInboxSignalPortapplication port, andapp-bootstrapbinds the relay.
Commit policy
AGENTS.md pins commit policy to human-only. Step 5 (git add / git commit) of every plan task
is therefore intentionally not executed by the agent; the working tree carries the change and the
human owner commits.
R1 public type disposition (NOTIF-ADR-005)
NOTIF-ADR-005 makes ..notification.platform.. canonical and keeps the R1 namespace in place
without new consumers. The ADR owns the rule; this table owns the list, so the two do not drift
apart by being written twice.
NOTIFICATION_R1_AND_PLATFORM_DO_NOT_DEPEND_ON_EACH_OTHER in CleanArchitectureTest enforces the
boundary: production dependencies between the two namespaces are zero, and the only permitted
exception is dev.caskeleton.application.notification.compatibility.r1.
| Disposition | Meaning | Types |
|---|---|---|
replace (27) |
the platform has an equivalent; new consumers use it | ApplyNotificationReceiptCommand, ApplyNotificationReceiptResult, ApplyNotificationReceiptUseCase, Channel, InlineNotificationAttemptPort, NormalizedNotificationReceiptCommand, NotificationAttemptId, NotificationDeliveryId, NotificationDeliveryStorePort, NotificationDispatchCommand, NotificationDispatchResult, NotificationDispatchUseCase, NotificationEvidenceTrustSnapshot, NotificationFrozenPlan, NotificationPlanPort, NotificationPlanningResult, NotificationProviderAttemptPort, NotificationReceiptEventId, NotificationReceiptFact, NotificationReceiptIngressCapabilityDescriptor, NotificationReceiptProjection, NotificationReceiptStorePort, NotificationSignedEvidenceHeader, NotificationWriterInventoryEvidence, NotificationWriterInventoryEvidenceVerifierPort, ProviderAttemptOutcome, TargetAttemptOutcome |
bridge (28) |
conversion needed if an R1 caller remains; conversion lives only in the ACL | InitializeNotificationWriterFencesCommand, InitializeNotificationWriterFencesOperation, InitializeNotificationWriterFencesResult, InitializeNotificationWriterFencesUseCase, NotificationAdmissionGateCommand, NotificationAdmissionGateUseCase, NotificationAppendResult, NotificationLegacyWriterPermitCommand, NotificationLegacyWriterPermitResult, NotificationLegacyWriterPermitUseCase, NotificationMaintenanceCommand, NotificationMaintenanceResult, NotificationMaintenanceUseCase, NotificationOperationsSnapshot, NotificationOperationsSnapshotQuery, NotificationOperationsSnapshotUseCase, NotificationRequestResult, ReconcileNotificationDeliveriesCommand, ReconcileNotificationDeliveriesResult, ReconcileNotificationDeliveriesUseCase, SwitchNotificationWriterOwnershipCommand, SwitchNotificationWriterOwnershipOperation, SwitchNotificationWriterOwnershipResult, SwitchNotificationWriterOwnershipUseCase, TerminalizeExpiredNotificationWriterPermitsCommand, TerminalizeExpiredNotificationWriterPermitsOperation, TerminalizeExpiredNotificationWriterPermitsResult, TerminalizeExpiredNotificationWriterPermitsUseCase |
retain (45) |
a concern the platform does not cover; left as it is | ConsentCheckMode, EmailRecipientReference, Notification, NotificationAdmissionClass, NotificationAdmissionReadinessPort, NotificationApplicationException, NotificationCanonicalWriterFenceGuard, NotificationCanonicalWriterFencePort, NotificationCanonicalWriterRouteSet, NotificationCapabilityCompatibilityValidator, NotificationChannel, NotificationFaultScope, NotificationIntentAppendPort, NotificationIntentDraft, NotificationIntentId, NotificationKindId, NotificationKindPolicy, NotificationMaintenanceStorePort, NotificationMode, NotificationOperationsSnapshotPort, NotificationPort, NotificationProviderCapabilityDescriptor, NotificationReasonCode, NotificationRecipientReference, NotificationReconciliationPort, NotificationRouteId, NotificationRouteStrategy, NotificationStoreCapabilityDescriptor, NotificationTechnicalSuppressionPort, NotificationTemplateParameters, NotificationTemplateRef, NotificationTemplateValue, NotificationWriterCutoverPort, NotificationWriterOwnership, NotificationWriterQuiescenceAttestationPort, NotificationWriterRouteSet, RecordNotificationWriterQuiescenceAttestationCommand, RecordNotificationWriterQuiescenceAttestationOperation, RecordNotificationWriterQuiescenceAttestationResult, RecordNotificationWriterQuiescenceAttestationUseCase, RetryDisposition, SignedNotificationWriterInventoryManifest, SignedNotificationWriterQuiescenceManifest, SlackAudienceReference, SubmissionCertainty |
Total: 100 public types, every one classified.
No type carries @Deprecated(forRemoval = true): no removal release is fixed, and
forRemoval without a date is a promise the codebase cannot keep. The boundary is
enforced by the ArchUnit rule instead.