Files

11 KiB

Notification Delivery Platform — module mapping

Source design: docs/superpowers/specs/2026-08-10-notification-platform-design.md

Source plan: 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 (*-apiprovider-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:

  1. notification-email-ses, notification-sms-twilio, notification-push-fcm, notification-push-apns, notification-webpush, notification-webhook-extensionhttpclient platform. adapter-outbound-notification is not allowed to depend on adapter-outbound-httpclient. The provider adapters therefore call dev.caskeleton.adapter.outbound.notification.platform.provider.http.NotificationHttpGateway, an adapter-local port with a JDK java.net.http.HttpClient default implementation. app-bootstrap sees 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).
  2. notification-inbox-jpaoptional messaging outbox integration. adapter-outbound-persistence-jpa may not depend on adapter-outbound-messaging; the inbox publishes through the existing persistence outbox tables plus the NotificationInboxSignalPort application port, and app-bootstrap binds 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.