merge: integrate notification production capability
# Conflicts: # docs/superpowers/plans/2026-07-28-notification-production-capability.md # src/adapter/outbound/persistence-jpa/build.gradle # src/adapter/outbound/persistence-jpa/gradle.lockfile # src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/transaction/SpringTransactionPort.java # src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/architecture/CleanArchitectureTest.java # src/application-core/src/main/java/dev/caskeleton/application/notification/NotificationCapabilityCompatibilityValidator.java # src/application-core/src/main/java/dev/caskeleton/application/notification/NotificationDeliveryStorePort.java # src/application-core/src/main/java/dev/caskeleton/application/notification/NotificationDispatchUseCase.java # src/application-core/src/main/java/dev/caskeleton/application/notification/NotificationFrozenPlan.java # src/application-core/src/main/java/dev/caskeleton/application/notification/NotificationMaintenanceCommand.java # src/application-core/src/main/java/dev/caskeleton/application/notification/NotificationMaintenanceResult.java # src/application-core/src/main/java/dev/caskeleton/application/notification/NotificationMaintenanceStorePort.java # src/application-core/src/test/java/dev/caskeleton/application/notification/NotificationCapabilityCompatibilityValidatorTest.java # src/application-core/src/test/java/dev/caskeleton/application/notification/NotificationDispatchUseCaseTest.java # src/application-core/src/test/java/dev/caskeleton/application/notification/NotificationMaintenanceUseCaseTest.java # src/application-core/src/test/java/dev/caskeleton/application/notification/NotificationPlanningBoundaryTest.java # src/application-core/src/test/java/dev/caskeleton/application/notification/NotificationValueContractTest.java # src/application-core/src/test/java/dev/caskeleton/application/notification/ReconcileNotificationDeliveriesUseCaseTest.java
This commit is contained in:
@@ -7,7 +7,13 @@
|
||||
> RED/GREEN/verification 규칙을 수동으로 명시한다.
|
||||
|
||||
- 작성일: 2026-07-28
|
||||
- 상태: proposed implementation plan, 구현 미착수
|
||||
- 상태: Wave A/B 및 Task 8 구현 checkpoint를 main 통합. Task 9 이후는 미착수
|
||||
- 원 작업 위치: `codex/notification-production-capability` 격리 worktree
|
||||
- 통합 위치: `/home/donghyeon/workspace/clean-architecture-backend-template` (`main`)
|
||||
- 현재 checkpoint: Wave A application/transaction foundation, Wave B catalog/render/provider
|
||||
fake protocol, Task 8 local crypto primitive 구현. Task 9의 migration/persistence model과 이후
|
||||
provider/runtime 범위는 미착수다. 아래 isolated-worktree 검증 기록과 main 통합 검증 결과는
|
||||
서로 다른 증거로 구분한다.
|
||||
- 설계 정본:
|
||||
[Notification Production Capability Deep Design](../specs/2026-07-28-notification-production-capability-design.md)
|
||||
- 비교한 실행 계획:
|
||||
@@ -783,7 +789,7 @@ removing bindings, but these types must remain while compiled consumers exist.
|
||||
|
||||
### Wave A exit gate
|
||||
|
||||
- [ ] Run:
|
||||
- [x] Run:
|
||||
|
||||
```bash
|
||||
cd src && ./gradlew :application-core:check \
|
||||
@@ -795,14 +801,11 @@ removing bindings, but these types must remain while compiled consumers exist.
|
||||
--tests '*CleanArchitectureTest' --console=plain
|
||||
```
|
||||
|
||||
Current evidence: the first command passes. The wildcard architecture command executes
|
||||
`CleanArchitectureTest` successfully (59/59) but remains blocked by the unrelated
|
||||
`DisabledAdapterArchitectureTest` Redis optional-bean gating violation. The exact
|
||||
`dev.caskeleton.bootstrap.architecture.CleanArchitectureTest` command passes.
|
||||
Isolated-worktree evidence: both commands pass, including the wildcard architecture test.
|
||||
|
||||
- [x] Confirm application bytecode/import scan contains no Spring, JPA, Slack, AWS, JSON or HTTP
|
||||
provider type.
|
||||
- [ ] Request an application/transaction boundary review before Wave B.
|
||||
- [x] Request an application/transaction boundary review before Wave B.
|
||||
- [x] Update the LLM Wiki branch-note with Wave A evidence and an explicit derived-document decision.
|
||||
|
||||
---
|
||||
@@ -832,9 +835,10 @@ removing bindings, but these types must remain while compiled consumers exist.
|
||||
- `src/adapter/outbound/notification/src/test/java/dev/caskeleton/adapter/outbound/notification/catalog/NotificationCanonicalRouteCatalogTest.java`
|
||||
- `src/adapter/outbound/notification/src/test/java/dev/caskeleton/adapter/outbound/notification/catalog/NotificationCutoverRouteCatalogTest.java`
|
||||
|
||||
- [ ] RED cases: unknown/blank/duplicate local catalog entry; channel/provider mismatch;
|
||||
- [x] RED cases: unknown/blank/duplicate local catalog entry; channel/provider mismatch;
|
||||
durable + legacy/fail-open; receipt-required + unsupported provider; unsafe fallback after
|
||||
indeterminate; target/retry/reconcile/amplification bound; cyclic binding.
|
||||
indeterminate; target/retry/reconcile/amplification bound; unsupported non-`SINGLE` binding.
|
||||
Initial R1 intentionally compiles only the three reviewed `SINGLE` cards.
|
||||
- [ ] RED cutover-catalog cases:
|
||||
`NotificationCanonicalRouteCatalog` is the retained key-only canonical SSOT and maps with the
|
||||
trusted runtime target config to application-owned `NotificationCanonicalWriterRouteSet`.
|
||||
@@ -858,7 +862,7 @@ removing bindings, but these types must remain while compiled consumers exist.
|
||||
A catalog route
|
||||
with no live legacy consumer is initialized as closed LEGACY predecessor and switched through
|
||||
the audited protocol; it is never directly seeded canonical in PRE.
|
||||
- [ ] Verify RED:
|
||||
- [x] Verify RED:
|
||||
|
||||
```bash
|
||||
cd src && ./gradlew :adapter:outbound:notification:test \
|
||||
@@ -870,14 +874,14 @@ removing bindings, but these types must remain while compiled consumers exist.
|
||||
|
||||
Expected failure: canonical descriptors/compiler do not exist.
|
||||
|
||||
- [ ] Implement a pure, deterministic compiler over explicit input; do not inspect Spring beans,
|
||||
- [x] Implement a pure, deterministic compiler over explicit input; do not inspect Spring beans,
|
||||
application context, persistence schema or inbound adapters.
|
||||
- [ ] Keep `expected-state`, exact actual/expected binding IDs, application mode/admission matching,
|
||||
- [x] Keep `expected-state`, exact actual/expected binding IDs, application mode/admission matching,
|
||||
store capability and ingress topology out of this sibling-local compiler. Task 17 passes
|
||||
provider-neutral descriptors to the application compatibility validator for those checks.
|
||||
- [ ] Register only the three initial card IDs. Legacy descriptors must explicitly advertise R0,
|
||||
- [x] Register only the three initial card IDs. Legacy descriptors must explicitly advertise R0,
|
||||
no durable/receipt capability.
|
||||
- [ ] Emit a sorted immutable binding graph and manifest digest; unknown inputs fail closed.
|
||||
- [x] Emit a sorted immutable binding graph and manifest digest; unknown inputs fail closed.
|
||||
- [ ] Emit one immutable `NotificationCutoverRouteCatalog` and digest from the same route descriptor
|
||||
inputs over the retained immutable `NotificationCanonicalRouteCatalog`. Bootstrap converts the
|
||||
canonical catalog to `NotificationCanonicalWriterRouteSet` and the PRE decorator to
|
||||
@@ -891,14 +895,16 @@ removing bindings, but these types must remain while compiled consumers exist.
|
||||
route has zero permits. `NotificationCutoverRouteCatalogTest` freezes the digest algorithm and
|
||||
proves the exact registry value that batch initialization must persist; after initialization,
|
||||
PRE composition rejects any persisted/catalog mismatch instead of silently refreshing it.
|
||||
- [ ] Implement `NotificationPlanPort` by converting the selected adapter-local compiled binding into
|
||||
Static catalog/digest/key-set validation is implemented; persisted-registry comparison and
|
||||
bootstrap enforcement remain explicitly deferred to Task 17.
|
||||
- [x] Implement `NotificationPlanPort` by converting the selected adapter-local compiled binding into
|
||||
an application-owned `NotificationFrozenPlan`. The conversion freezes policy/route/template/
|
||||
renderer/provider-leg revisions and contains no credential, SDK, settings or adapter type.
|
||||
- [ ] Derive the application-owned provider capability descriptor from the actual compiled cards,
|
||||
- [x] Derive the application-owned provider capability descriptor from the actual compiled cards,
|
||||
renderer and client capabilities. Do not reconstruct “actual” provider facts from the expected
|
||||
bootstrap settings.
|
||||
- [ ] Verify GREEN with the same command.
|
||||
- [ ] Acceptance claim: local graph compatibility only, not actual composition/readiness.
|
||||
- [x] Verify GREEN with the same command.
|
||||
- [x] Acceptance claim: local graph compatibility only, not actual composition/readiness.
|
||||
|
||||
**Rollback checkpoint:** compiler can coexist dark with the legacy router until Task 18 canonical
|
||||
composition succeeds.
|
||||
@@ -923,24 +929,24 @@ composition succeeds.
|
||||
- `src/adapter/outbound/notification/src/test/resources/notification/templates/email/contract-v1.html`
|
||||
- `src/adapter/outbound/notification/src/test/resources/notification/templates/slack/contract-v1.txt`
|
||||
|
||||
- [ ] RED cases: checksum/revision drift; missing/unknown/unused parameter; exact locale fallback
|
||||
- [x] RED cases: checksum/revision drift; missing/unknown/unused parameter; exact locale fallback
|
||||
independent of JVM default; email header CR/LF; HTML text/attribute/URL escaping; Slack
|
||||
mrkdwn/plain-text/mention escaping; output byte/block/depth limits; no file/network/reflection
|
||||
include; redacted failures.
|
||||
- [ ] Verify RED:
|
||||
- [x] Verify RED:
|
||||
|
||||
```bash
|
||||
cd src && ./gradlew :adapter:outbound:notification:test \
|
||||
--tests '*NotificationTemplateRendererTest' --console=plain
|
||||
```
|
||||
|
||||
- [ ] Implement checked-in resource loading by exact manifest/checksum. Keep business-specific
|
||||
- [x] Implement checked-in resource loading by exact manifest/checksum. Keep business-specific
|
||||
assets out of production main resources until a consuming project supplies a reviewed catalog;
|
||||
use test resources only for the generic contract proof.
|
||||
- [ ] Produce local email subject/text/HTML and Slack Block Kit through typed builders; never accept
|
||||
- [x] Produce local email subject/text/HTML and Slack Block Kit through typed builders; never accept
|
||||
caller-supplied arbitrary JSON or provider block objects.
|
||||
- [ ] Verify GREEN with the same command.
|
||||
- [ ] Acceptance claim: deterministic local render R1; no provider call.
|
||||
- [x] Verify GREEN with the same command.
|
||||
- [x] Acceptance claim: deterministic local render R1; no provider call.
|
||||
|
||||
**Rollback checkpoint:** retained intent template revisions prevent later asset deletion. Before
|
||||
durable append, this task is independently reversible.
|
||||
@@ -973,11 +979,11 @@ durable append, this task is independently reversible.
|
||||
- `src/adapter/outbound/notification/src/test/java/dev/caskeleton/adapter/outbound/notification/provider/NotificationReconciliationAdapterTest.java`
|
||||
- `src/adapter/outbound/notification/src/test/java/dev/caskeleton/adapter/outbound/notification/provider/NotificationAdmissionReadinessAdapterTest.java`
|
||||
|
||||
- [ ] RED cases: `prepare` has no I/O; one authorization invokes client exactly once; deadline is
|
||||
- [x] RED cases: `prepare` has no I/O; one authorization invokes client exactly once; deadline is
|
||||
absolute/bounded; pre-wire validation maps to definitely-not-applied; possible write timeout
|
||||
maps indeterminate; accepted response stores only opaque provider reference; SDK exceptions
|
||||
never escape to application.
|
||||
- [ ] Verify RED:
|
||||
- [x] Verify RED:
|
||||
|
||||
```bash
|
||||
cd src && ./gradlew :adapter:outbound:notification:test \
|
||||
@@ -989,40 +995,40 @@ durable append, this task is independently reversible.
|
||||
--console=plain
|
||||
```
|
||||
|
||||
- [ ] Keep this SPI adapter-internal. Implement the application
|
||||
- [x] Keep this SPI adapter-internal. Implement the application
|
||||
`NotificationProviderAttemptPort` with compiled binding + renderer + internal client lookup.
|
||||
- [ ] Keep secret material resolution, control-plane readiness and provider-local rate admission
|
||||
- [x] Keep secret material resolution, control-plane readiness and provider-local rate admission
|
||||
behind adapter-owned interfaces. Profiles contain secret references/generations only; readiness
|
||||
snapshots contain bounded non-secret identity/capability facts.
|
||||
- [ ] Secret acquisition returns a versioned `AutoCloseable` mutable byte/char handle. Acquire it per
|
||||
- [x] Secret acquisition returns a versioned `AutoCloseable` mutable byte/char handle. Acquire it per
|
||||
provider operation, close it on success/exception/cancellation, wipe on close, reject use after
|
||||
close, and redact `toString`/exceptions. Never store the raw token in an adapter-owned
|
||||
record/String/settings field; the wipe claim covers only the adapter-facing mutable copy.
|
||||
- [ ] Implement the application-owned `NotificationAdmissionReadinessPort` with the adapter-internal
|
||||
- [x] Implement the application-owned `NotificationAdmissionReadinessPort` with the adapter-internal
|
||||
readiness probes. Application admission use cases must never import the internal probe type.
|
||||
- [ ] Freeze the outbound binding matrix:
|
||||
- [x] Freeze the outbound binding matrix:
|
||||
`NotificationPlanPort -> NotificationPlanAdapter`,
|
||||
`InlineNotificationAttemptPort -> InlineNotificationAttemptAdapter`,
|
||||
`NotificationProviderAttemptPort -> NotificationProviderAttemptAdapter`,
|
||||
`NotificationReconciliationPort -> NotificationReconciliationAdapter`,
|
||||
`NotificationAdmissionReadinessPort -> NotificationAdmissionReadinessAdapter`.
|
||||
Every implementation has a focused contract test before composition.
|
||||
- [ ] Keep attempt correlation, optional provider operation key and post-response message reference
|
||||
- [x] Keep attempt correlation, optional provider operation key and post-response message reference
|
||||
as distinct types.
|
||||
- [ ] Verify GREEN with the same command and:
|
||||
- [x] Verify GREEN with the same command and:
|
||||
|
||||
```bash
|
||||
cd src && ./gradlew :adapter:outbound:notification:check --console=plain
|
||||
```
|
||||
|
||||
- [ ] Acceptance claim: deterministic fake protocol R1, no exact provider card qualification.
|
||||
- [x] Acceptance claim: deterministic fake protocol R1, no exact provider card qualification.
|
||||
|
||||
**Rollback checkpoint:** no network resources are created until a canonical profile is bound in
|
||||
Task 18.
|
||||
|
||||
### Wave B exit gate
|
||||
|
||||
- [ ] Run:
|
||||
- [x] Run:
|
||||
|
||||
```bash
|
||||
cd src && ./gradlew :application-core:check \
|
||||
@@ -1031,10 +1037,11 @@ Task 18.
|
||||
--console=plain
|
||||
```
|
||||
|
||||
- [ ] Verify the notification leaf has no project dependency on persistence, inbound-web or
|
||||
- [x] Verify the notification leaf has no project dependency on persistence, inbound-web or
|
||||
httpclient.
|
||||
- [ ] Request catalog/template/provider-SPI review.
|
||||
- [ ] Update the LLM Wiki branch-note with Wave B evidence and an explicit derived-document decision.
|
||||
- [x] Request catalog/template/provider-SPI review.
|
||||
Final independent re-review: Blocker 0 / High 0.
|
||||
- [x] Update the LLM Wiki branch-note with Wave B evidence and an explicit derived-document decision.
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user