Maps the 31-module plan onto the registry's 19 leaves as packages; the two edges the registry forbids (provider->httpclient, inbox->messaging) are replaced by application-owned ports. See docs/notification/module-mapping.md. Acceptance is not delivery: ProviderSubmissionResult refuses to carry a delivery outcome, and AMBIGUOUS is a first-class terminal state that blocks automatic retry and fallback until reconciliation resolves it. Providers: SES (SigV4 + SNS callback), Twilio (X-Twilio-Signature + reconciliation), FCM (FID-primary batch), APNs, Web Push (RFC 8030/8291/8292), SMTP and webhook. Contact points are AES-256-GCM encrypted with a separate HMAC lookup fingerprint; nothing raw reaches a log, metric tag or exception. Dispatch commits the attempt row, calls the provider with no transaction open, then records the outcome; the durable queue uses FOR UPDATE SKIP LOCKED. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
95 lines
6.3 KiB
Markdown
95 lines
6.3 KiB
Markdown
# Notification Delivery Platform — module mapping
|
|
|
|
> Source design: `notification-superpowers-package/docs/superpowers/specs/2026-08-10-notification-platform-design.md`
|
|
>
|
|
> Source 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:
|
|
|
|
```text
|
|
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-extension`
|
|
→ `httpclient 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-jpa` → `optional 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.
|